sequoia-openpgp-2.0.0/.cargo_vcs_info.json0000644000000001450000000000100141470ustar { "git": { "sha1": "79d5be167284e4ad00a5ef406b8aea2b25bab3a9" }, "path_in_vcs": "openpgp" }sequoia-openpgp-2.0.0/Cargo.lock0000644000002053540000000000100121330ustar # This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 3 [[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", "zeroize", ] [[package]] name = "aes-gcm" version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "831010a0f742e1209b3bcea8fab6a8e149051ba6099432c8cb2cc117dec3ead1" dependencies = [ "aead", "aes", "cipher", "ctr", "ghash", "subtle", ] [[package]] name = "aho-corasick" version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" dependencies = [ "memchr", ] [[package]] name = "android-tzdata" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" [[package]] name = "android_system_properties" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" dependencies = [ "libc", ] [[package]] name = "anes" version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" [[package]] name = "anstyle" version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" [[package]] name = "anyhow" version = "1.0.96" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6b964d184e89d9b6b67dd2715bc8e74cf3107fb2b529990c90cf517326150bf4" [[package]] name = "argon2" version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c3610892ee6e0cbce8ae2700349fcf8f98adb0dbfbee85aec3c9179d29cc072" dependencies = [ "base64ct", "blake2", "cpufeatures", "password-hash", ] [[package]] name = "ascii-canvas" version = "3.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8824ecca2e851cec16968d54a01dd372ef8f95b244fb84b84e70128be347c3c6" dependencies = [ "term", ] [[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 = "bindgen" version = "0.70.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f49d8fed880d473ea71efb9bf597651e77201bdd4893efe54c9e5d65ae04ce6f" dependencies = [ "bitflags", "cexpr", "clang-sys", "itertools 0.13.0", "proc-macro2", "quote", "regex", "rustc-hash", "shlex", "syn", ] [[package]] name = "bit-set" version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1" dependencies = [ "bit-vec", ] [[package]] name = "bit-vec" version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" [[package]] name = "bitflags" version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f68f53c83ab957f72c32642f3868eec03eb974d1fb82e453128456482613d36" [[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 = "botan" version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d4c7647d67c53194fa0740404c6c508880aef2bfe99a9868dbb4b86f090377" dependencies = [ "botan-sys", ] [[package]] name = "botan-sys" version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "04285fa0c094cc9961fe435b1b279183db9394844ad82ce483aa6196c0e6da38" [[package]] name = "buffered-reader" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "db26bf1f092fd5e05b5ab3be2f290915aeb6f3f20c4e9f86ce0f07f336c2412f" dependencies = [ "bzip2", "flate2", "libc", ] [[package]] name = "bumpalo" version = "3.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf" [[package]] name = "byteorder" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bzip2" version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "75b89e7c29231c673a61a46e722602bcd138298f6b9e81e71119693534585f5c" dependencies = [ "bzip2-sys", ] [[package]] name = "bzip2-sys" version = "0.1.12+1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72ebc2f1a417f01e1da30ef264ee86ae31d2dcd2d603ea283d3c244a883ca2a9" dependencies = [ "cc", "libc", "pkg-config", ] [[package]] name = "camellia" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3264e2574e9ef2b53ce6f536dea83a69ac0bc600b762d1523ff83fe07230ce30" dependencies = [ "byteorder", "cipher", ] [[package]] name = "cast" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" [[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.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c736e259eea577f443d5c86c304f9f4ae0295c43f3ba05c21f1d66b5f06001af" dependencies = [ "shlex", ] [[package]] name = "cexpr" version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" dependencies = [ "nom", ] [[package]] name = "cfb-mode" version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "738b8d467867f80a71351933f70461f5b56f24d5c93e0cf216e59229c968d330" dependencies = [ "cipher", ] [[package]] name = "cfg-if" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "chrono" version = "0.4.40" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1a7964611d71df112cb1730f2ee67324fcf4d0fc6606acbbe9bfe06df124637c" dependencies = [ "android-tzdata", "iana-time-zone", "js-sys", "num-traits", "wasm-bindgen", "windows-link", ] [[package]] name = "ciborium" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42e69ffd6f0917f5c029256a24d0161db17cea3997d185db0d35926308770f0e" dependencies = [ "ciborium-io", "ciborium-ll", "serde", ] [[package]] name = "ciborium-io" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "05afea1e0a06c9be33d539b876f1ce3692f4afea2cb41f740e7743225ed1c757" [[package]] name = "ciborium-ll" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9" dependencies = [ "ciborium-io", "half", ] [[package]] name = "cipher" version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" dependencies = [ "crypto-common", "inout", "zeroize", ] [[package]] name = "clang-sys" version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" dependencies = [ "glob", "libc", "libloading", ] [[package]] name = "clap" version = "4.5.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "027bb0d98429ae334a8698531da7077bdf906419543a35a55c2cb1b66437d767" dependencies = [ "clap_builder", ] [[package]] name = "clap_builder" version = "4.5.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5589e0cba072e0f3d23791efac0fd8627b49c829c196a492e88168e6a669d863" dependencies = [ "anstyle", "clap_lex", ] [[package]] name = "clap_lex" version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" [[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 = "crc32fast" version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" dependencies = [ "cfg-if", ] [[package]] name = "criterion" version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2b12d017a929603d80db1831cd3a24082f8137ce19c69e6447f54f5fc8d692f" dependencies = [ "anes", "cast", "ciborium", "clap", "criterion-plot", "is-terminal", "itertools 0.10.5", "num-traits", "once_cell", "oorandom", "plotters", "rayon", "regex", "serde", "serde_derive", "serde_json", "tinytemplate", "walkdir", ] [[package]] name = "criterion-plot" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6b50826342786a51a89e2da3a28f1c32b06e387201bc2d19791f622c673706b1" dependencies = [ "cast", "itertools 0.10.5", ] [[package]] name = "crossbeam-deque" version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" dependencies = [ "crossbeam-epoch", "crossbeam-utils", ] [[package]] name = "crossbeam-epoch" version = "0.9.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" dependencies = [ "crossbeam-utils", ] [[package]] name = "crossbeam-utils" version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" [[package]] name = "crunchy" version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "43da5946c66ffcc7745f48db692ffbb10a83bfe0afd96235c5c2a4fb23994929" [[package]] name = "crypto-bigint" version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" dependencies = [ "generic-array", "rand_core", "subtle", "zeroize", ] [[package]] name = "crypto-common" version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" dependencies = [ "generic-array", "rand_core", "typenum", ] [[package]] name = "ctr" version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835" dependencies = [ "cipher", ] [[package]] name = "curve25519-dalek" version = "4.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be" dependencies = [ "cfg-if", "cpufeatures", "curve25519-dalek-derive", "digest", "fiat-crypto", "rustc_version", "subtle", "zeroize", ] [[package]] name = "curve25519-dalek-derive" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "dbl" version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bd2735a791158376708f9347fe8faba9667589d82427ef3aed6794a8981de3d9" dependencies = [ "generic-array", ] [[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 = "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 = "dirs-next" version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1" dependencies = [ "cfg-if", "dirs-sys-next", ] [[package]] name = "dirs-sys-next" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d" dependencies = [ "libc", "redox_users", "winapi", ] [[package]] name = "displaydoc" version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "doc-comment" version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" [[package]] name = "dsa" version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "48bc224a9084ad760195584ce5abb3c2c34a225fa312a128ad245a6b412b7689" dependencies = [ "digest", "num-bigint-dig", "num-traits", "pkcs8", "rfc6979", "sha2", "signature", "zeroize", ] [[package]] name = "dyn-clone" version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "feeef44e73baff3a26d371801df019877a9866a8c493d315ab00177843314f35" [[package]] name = "eax" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9954fabd903b82b9d7a68f65f97dc96dd9ad368e40ccc907a7c19d53e6bfac28" dependencies = [ "aead", "cipher", "cmac", "ctr", "subtle", ] [[package]] name = "ecb" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1a8bfa975b1aec2145850fcaa1c6fe269a16578c44705a532ae3edc92b8881c7" dependencies = [ "cipher", ] [[package]] name = "ecdsa" version = "0.16.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" dependencies = [ "der", "digest", "elliptic-curve", "rfc6979", "signature", "spki", ] [[package]] name = "ed25519" version = "2.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53" dependencies = [ "pkcs8", "signature", ] [[package]] name = "ed25519-dalek" version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4a3daa8e81a3963a60642bcc1f90a670680bd4a77535faa384e9d1c79d620871" dependencies = [ "curve25519-dalek", "ed25519", "rand_core", "serde", "sha2", "subtle", "zeroize", ] [[package]] name = "either" version = "1.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b7914353092ddf589ad78f25c5c1c21b7f80b0ff8621e7c814c3485b5306da9d" [[package]] name = "elliptic-curve" version = "0.13.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" dependencies = [ "base16ct", "crypto-bigint", "digest", "ff", "generic-array", "group", "hkdf", "pem-rfc7468", "pkcs8", "rand_core", "sec1", "subtle", "zeroize", ] [[package]] name = "ena" version = "0.14.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d248bdd43ce613d87415282f69b9bb99d947d290b10962dd6c56233312c2ad5" dependencies = [ "log", ] [[package]] name = "equivalent" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" [[package]] name = "errno" version = "0.3.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" dependencies = [ "libc", "windows-sys 0.52.0", ] [[package]] name = "fastrand" version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" [[package]] name = "ff" version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ded41244b729663b1e574f1b4fb731469f69f79c17667b5d776b16cda0479449" dependencies = [ "rand_core", "subtle", ] [[package]] name = "fiat-crypto" version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" [[package]] name = "fixedbitset" version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" [[package]] name = "flate2" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "11faaf5a5236997af9848be0bef4db95824b1d534ebc64d0f0c6cf3e67bd38dc" dependencies = [ "crc32fast", "miniz_oxide", ] [[package]] name = "foreign-types" version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" dependencies = [ "foreign-types-shared", ] [[package]] name = "foreign-types-shared" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" [[package]] name = "generic-array" version = "0.14.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" dependencies = [ "typenum", "version_check", "zeroize", ] [[package]] name = "getrandom" version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" dependencies = [ "cfg-if", "js-sys", "libc", "wasi 0.11.0+wasi-snapshot-preview1", "wasm-bindgen", ] [[package]] name = "getrandom" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "43a49c392881ce6d5c3b8cb70f98717b7c07aabbdff06687b9030dbfbe2725f8" dependencies = [ "cfg-if", "libc", "wasi 0.13.3+wasi-0.2.2", "windows-targets 0.52.6", ] [[package]] name = "ghash" version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0d8a4362ccb29cb0b265253fb0a2728f592895ee6854fd9bc13f2ffda266ff1" dependencies = [ "opaque-debug", "polyval", ] [[package]] name = "glob" version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2" [[package]] name = "group" version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" dependencies = [ "ff", "rand_core", "subtle", ] [[package]] name = "half" version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6dd08c532ae367adf81c312a4580bc67f1d0fe8bc9c460520283f4c0ff277888" dependencies = [ "cfg-if", "crunchy", ] [[package]] name = "hashbrown" version = "0.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" [[package]] name = "hermit-abi" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fbf6a919d6cf397374f7dfeeea91d974c7c0a7221d0d0f4f20d859d329e53fcc" [[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 = "icu_collections" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526" dependencies = [ "displaydoc", "yoke", "zerofrom", "zerovec", ] [[package]] name = "icu_locid" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637" dependencies = [ "displaydoc", "litemap", "tinystr", "writeable", "zerovec", ] [[package]] name = "icu_locid_transform" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e" dependencies = [ "displaydoc", "icu_locid", "icu_locid_transform_data", "icu_provider", "tinystr", "zerovec", ] [[package]] name = "icu_locid_transform_data" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fdc8ff3388f852bede6b579ad4e978ab004f139284d7b28715f773507b946f6e" [[package]] name = "icu_normalizer" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f" dependencies = [ "displaydoc", "icu_collections", "icu_normalizer_data", "icu_properties", "icu_provider", "smallvec", "utf16_iter", "utf8_iter", "write16", "zerovec", ] [[package]] name = "icu_normalizer_data" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8cafbf7aa791e9b22bec55a167906f9e1215fd475cd22adfcf660e03e989516" [[package]] name = "icu_properties" version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "93d6020766cfc6302c15dbbc9c8778c37e62c14427cb7f6e601d849e092aeef5" dependencies = [ "displaydoc", "icu_collections", "icu_locid_transform", "icu_properties_data", "icu_provider", "tinystr", "zerovec", ] [[package]] name = "icu_properties_data" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "67a8effbc3dd3e4ba1afa8ad918d5684b8868b3b26500753effea8d2eed19569" [[package]] name = "icu_provider" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9" dependencies = [ "displaydoc", "icu_locid", "icu_provider_macros", "stable_deref_trait", "tinystr", "writeable", "yoke", "zerofrom", "zerovec", ] [[package]] name = "icu_provider_macros" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "idea" version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "075557004419d7f2031b8bb7f44bb43e55a83ca7b63076a8fb8fe75753836477" dependencies = [ "cipher", ] [[package]] name = "idna" version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" dependencies = [ "idna_adapter", "smallvec", "utf8_iter", ] [[package]] name = "idna_adapter" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "daca1df1c957320b2cf139ac61e7bd64fed304c5040df000a745aa1de3b4ef71" dependencies = [ "icu_normalizer", "icu_properties", ] [[package]] name = "indexmap" version = "2.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8c9c992b02b5b4c94ea26e32fe5bccb7aa7d9f390ab5c1221ff895bc7ea8b652" dependencies = [ "equivalent", "hashbrown", ] [[package]] name = "inout" version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01" dependencies = [ "block-padding", "generic-array", ] [[package]] name = "is-terminal" version = "0.4.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e19b23d53f35ce9f56aebc7d1bb4e6ac1e9c0db7ac85c8d1760c04379edced37" dependencies = [ "hermit-abi", "libc", "windows-sys 0.52.0", ] [[package]] name = "itertools" version = "0.10.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" dependencies = [ "either", ] [[package]] name = "itertools" version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57" dependencies = [ "either", ] [[package]] name = "itertools" version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" dependencies = [ "either", ] [[package]] name = "itoa" version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" [[package]] name = "js-sys" version = "0.3.77" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" dependencies = [ "once_cell", "wasm-bindgen", ] [[package]] name = "keccak" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ecc2af9a1119c51f12a14607e783cb977bde58bc069ff0c3da1095e635d70654" dependencies = [ "cpufeatures", ] [[package]] name = "lalrpop" version = "0.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "55cb077ad656299f160924eb2912aa147d7339ea7d69e1b5517326fdcec3c1ca" dependencies = [ "ascii-canvas", "bit-set", "ena", "itertools 0.11.0", "lalrpop-util", "petgraph", "regex", "regex-syntax", "string_cache", "term", "tiny-keccak", "unicode-xid", "walkdir", ] [[package]] name = "lalrpop-util" version = "0.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "507460a910eb7b32ee961886ff48539633b788a36b65692b95f225b844c82553" dependencies = [ "regex-automata", ] [[package]] name = "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.170" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "875b3680cb2f8f71bdcf9a30f38d48282f5d3c95cbf9b3fa57269bb5d5c06828" [[package]] name = "libloading" version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc2f4eb4bc735547cfed7c0a4922cbd04a4655978c09b54f1f7b228750664c34" dependencies = [ "cfg-if", "windows-targets 0.48.5", ] [[package]] name = "libm" version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8355be11b20d696c8f18f6cc018c4e372165b1fa8126cef092399c9951984ffa" [[package]] name = "libredox" version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" dependencies = [ "bitflags", "libc", ] [[package]] name = "linux-raw-sys" version = "0.4.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" [[package]] name = "litemap" version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "643cb0b8d4fcc284004d5fd0d67ccf61dfffadb7f75e1e71bc420f4688a3a704" [[package]] name = "lock_api" version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" dependencies = [ "autocfg", "scopeguard", ] [[package]] name = "log" version = "0.4.26" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "30bde2b3dc3671ae49d8e2e9f044c7c005836e7a023ee57cffa25ab82764bb9e" [[package]] name = "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 = "memsec" version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c797b9d6bb23aab2fc369c65f871be49214f5c759af65bde26ffaaa2b646b492" [[package]] name = "minimal-lexical" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "miniz_oxide" version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e3e04debbb59698c15bacbb6d93584a8c0ca9cc3213cb423d31f760d8843ce5" dependencies = [ "adler2", ] [[package]] name = "nettle" version = "7.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "44e6ff4a94e5d34a1fd5abbd39418074646e2fa51b257198701330f22fcd6936" dependencies = [ "getrandom 0.2.15", "libc", "nettle-sys", "thiserror", "typenum", ] [[package]] name = "nettle-sys" version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "61a3f5406064d310d59b1a219d3c5c9a49caf4047b6496032e3f930876488c34" dependencies = [ "bindgen", "cc", "libc", "pkg-config", "tempfile", "vcpkg", ] [[package]] name = "new_debug_unreachable" version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" [[package]] name = "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", "smallvec", "zeroize", ] [[package]] name = "num-integer" version = "0.1.46" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" dependencies = [ "num-traits", ] [[package]] name = "num-iter" version = "0.1.45" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" dependencies = [ "autocfg", "num-integer", "num-traits", ] [[package]] name = "num-traits" version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", "libm", ] [[package]] name = "ocb3" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c196e0276c471c843dd5777e7543a36a298a4be942a2a688d8111cd43390dedb" dependencies = [ "aead", "cipher", "ctr", "subtle", ] [[package]] name = "once_cell" version = "1.20.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "945462a4b81e43c4e3ba96bd7b49d834c6f61198356aa858733bc4acf3cbe62e" [[package]] name = "oorandom" version = "11.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b410bbe7e14ab526a0e86877eb47c6996a2bd7746f027ba551028c925390e4e9" [[package]] name = "opaque-debug" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" [[package]] name = "openssl" version = "0.10.71" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e14130c6a98cd258fdcb0fb6d744152343ff729cbfcb28c656a9d12b999fbcd" dependencies = [ "bitflags", "cfg-if", "foreign-types", "libc", "once_cell", "openssl-macros", "openssl-sys", ] [[package]] name = "openssl-macros" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "openssl-sys" version = "0.9.106" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8bb61ea9811cc39e3c2069f40b8b8e2e70d8569b361f879786cc7ed48b777cdd" dependencies = [ "cc", "libc", "pkg-config", "vcpkg", ] [[package]] name = "p256" version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c9863ad85fa8f4460f9c48cb909d38a0d689dba1f6f6988a5e3e0d31071bcd4b" dependencies = [ "ecdsa", "elliptic-curve", "primeorder", "sha2", ] [[package]] name = "p384" version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fe42f1670a52a47d448f14b6a5c61dd78fce51856e68edaa38f7ae3a46b8d6b6" dependencies = [ "ecdsa", "elliptic-curve", "primeorder", "sha2", ] [[package]] name = "p521" version = "0.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fc9e2161f1f215afdfce23677034ae137bbd45016a880c2eb3ba8eb95f085b2" dependencies = [ "base16ct", "ecdsa", "elliptic-curve", "primeorder", "rand_core", "sha2", ] [[package]] name = "parking_lot" version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" dependencies = [ "lock_api", "parking_lot_core", ] [[package]] name = "parking_lot_core" version = "0.9.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" dependencies = [ "cfg-if", "libc", "redox_syscall", "smallvec", "windows-targets 0.52.6", ] [[package]] name = "password-hash" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "346f04948ba92c43e8469c1ee6736c7563d71012b17d40745260fe106aac2166" dependencies = [ "base64ct", "rand_core", "subtle", ] [[package]] name = "pem-rfc7468" version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" dependencies = [ "base64ct", ] [[package]] name = "petgraph" version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db" dependencies = [ "fixedbitset", "indexmap", ] [[package]] name = "phf_shared" version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5" dependencies = [ "siphasher", ] [[package]] name = "pkcs1" version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c8ffb9f10fa047879315e6625af03c164b16962a5368d724ed16323b68ace47f" dependencies = [ "der", "pkcs8", "spki", ] [[package]] name = "pkcs8" version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" dependencies = [ "der", "spki", ] [[package]] name = "pkg-config" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" [[package]] name = "plotters" version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5aeb6f403d7a4911efb1e33402027fc44f29b5bf6def3effcc22d7bb75f2b747" dependencies = [ "num-traits", "plotters-backend", "plotters-svg", "wasm-bindgen", "web-sys", ] [[package]] name = "plotters-backend" version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df42e13c12958a16b3f7f4386b9ab1f3e7933914ecea48da7139435263a4172a" [[package]] name = "plotters-svg" version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "51bae2ac328883f7acdfea3d66a7c35751187f870bc81f94563733a154d7a670" dependencies = [ "plotters-backend", ] [[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 = "precomputed-hash" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" [[package]] name = "primeorder" version = "0.13.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "353e1ca18966c16d9deb1c69278edbc5f194139612772bd9537af60ac231e1e6" dependencies = [ "elliptic-curve", ] [[package]] name = "proc-macro2" version = "1.0.93" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99" dependencies = [ "unicode-ident", ] [[package]] name = "quickcheck" version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "588f6378e4dd99458b60ec275b4477add41ce4fa9f64dcba6f15adccb19b50d6" dependencies = [ "rand", ] [[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", ] [[package]] name = "rand_chacha" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" dependencies = [ "ppv-lite86", "rand_core", ] [[package]] name = "rand_core" version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ "getrandom 0.2.15", ] [[package]] name = "rayon" version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" dependencies = [ "either", "rayon-core", ] [[package]] name = "rayon-core" version = "1.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" dependencies = [ "crossbeam-deque", "crossbeam-utils", ] [[package]] name = "redox_syscall" version = "0.5.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "82b568323e98e49e2a0899dcee453dd679fae22d69adf9b11dd508d1549b7e2f" dependencies = [ "bitflags", ] [[package]] name = "redox_users" version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" dependencies = [ "getrandom 0.2.15", "libredox", "thiserror", ] [[package]] name = "regex" version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" dependencies = [ "aho-corasick", "memchr", "regex-automata", "regex-syntax", ] [[package]] name = "regex-automata" version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" dependencies = [ "aho-corasick", "memchr", "regex-syntax", ] [[package]] name = "regex-syntax" version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" [[package]] name = "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 = "rpassword" version = "7.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "80472be3c897911d0137b2d2b9055faf6eeac5b14e324073d83bc17b191d7e3f" dependencies = [ "libc", "rtoolbox", "windows-sys 0.48.0", ] [[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", "signature", "spki", "subtle", "zeroize", ] [[package]] name = "rtoolbox" version = "0.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c247d24e63230cdb56463ae328478bd5eac8b8faa8c69461a77e8e323afac90e" dependencies = [ "libc", "windows-sys 0.48.0", ] [[package]] name = "rustc-hash" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" [[package]] name = "rustc_version" version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" dependencies = [ "semver", ] [[package]] name = "rustix" version = "0.38.44" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" dependencies = [ "bitflags", "errno", "libc", "linux-raw-sys", "windows-sys 0.52.0", ] [[package]] name = "rustversion" version = "1.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f7c45b9784283f1b2e7fb61b42047c2fd678ef0960d4f6f1eba131594cc369d4" [[package]] name = "ryu" version = "1.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ea1a2d0a644769cc99faa24c3ad26b379b786fe7c36fd3c546254801650e6dd" [[package]] name = "same-file" version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" dependencies = [ "winapi-util", ] [[package]] name = "scopeguard" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "sec1" version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" dependencies = [ "base16ct", "der", "generic-array", "pkcs8", "subtle", "zeroize", ] [[package]] name = "semver" version = "1.0.25" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f79dfe2d285b0488816f30e700a7438c5a73d816b5b7d3ac72fbc48b0d185e03" [[package]] name = "sequoia-openpgp" version = "2.0.0" dependencies = [ "aes", "aes-gcm", "anyhow", "argon2", "base64", "block-padding", "blowfish", "botan", "buffered-reader", "bzip2", "camellia", "cast5", "cfb-mode", "chrono", "cipher", "criterion", "des", "digest", "dsa", "dyn-clone", "eax", "ecb", "ecdsa", "ed25519", "ed25519-dalek", "flate2", "getrandom 0.2.15", "hkdf", "idea", "idna", "lalrpop", "lalrpop-util", "libc", "md-5", "memsec", "nettle", "num-bigint-dig", "ocb3", "openssl", "openssl-sys", "p256", "p384", "p521", "quickcheck", "rand", "rand_core", "regex", "regex-syntax", "ripemd", "rpassword", "rsa", "sha1collisiondetection", "sha2", "sha3", "thiserror", "twofish", "typenum", "win-crypto-ng", "winapi", "x25519-dalek", "xxhash-rust", ] [[package]] name = "serde" version = "1.0.218" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e8dfc9d19bdbf6d17e22319da49161d5d0108e4188e8b680aef6299eed22df60" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" version = "1.0.218" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f09503e191f4e797cb8aac08e9a4a4695c5edf6a2e70e376d961ddd5c969f82b" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "serde_json" version = "1.0.139" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "44f86c3acccc9c65b153fe1b85a3be07fe5515274ec9f0653b4a0875731c72a6" dependencies = [ "itoa", "memchr", "ryu", "serde", ] [[package]] name = "sha1collisiondetection" version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1f606421e4a6012877e893c399822a4ed4b089164c5969424e1b9d1e66e6964b" dependencies = [ "const-oid", "digest", "generic-array", ] [[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", ] [[package]] name = "siphasher" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" [[package]] name = "smallvec" version = "1.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7fcf8323ef1faaee30a44a340193b1ac6814fd9b7b4e88e9d4519a3e4abe1cfd" [[package]] name = "spin" version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" [[package]] name = "spki" version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" dependencies = [ "base64ct", "der", ] [[package]] name = "stable_deref_trait" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" [[package]] name = "string_cache" version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "938d512196766101d333398efde81bc1f37b00cb42c2f8350e5df639f040bbbe" dependencies = [ "new_debug_unreachable", "parking_lot", "phf_shared", "precomputed-hash", ] [[package]] name = "subtle" version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "syn" version = "2.0.98" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "36147f1a48ae0ec2b5b3bc5b537d267457555a10dc06f3dbc8cb11ba3006d3b1" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] [[package]] name = "synstructure" version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "tempfile" version = "3.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "22e5a0acb1f3f55f65cc4a866c361b2fb2a0ff6366785ae6fbb5f85df07ba230" dependencies = [ "cfg-if", "fastrand", "getrandom 0.3.1", "once_cell", "rustix", "windows-sys 0.52.0", ] [[package]] name = "term" version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c59df8ac95d96ff9bede18eb7300b0fda5e5d8d90960e76f8e14ae765eedbf1f" dependencies = [ "dirs-next", "rustversion", "winapi", ] [[package]] name = "thiserror" version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "tiny-keccak" version = "2.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" dependencies = [ "crunchy", ] [[package]] name = "tinystr" version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f" dependencies = [ "displaydoc", "zerovec", ] [[package]] name = "tinytemplate" version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc" dependencies = [ "serde", "serde_json", ] [[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.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" [[package]] name = "unicode-ident" version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "00e2473a93778eb0bad35909dff6a10d28e63f792f16ed15e404fca9d5eeedbe" [[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 = "utf16_iter" version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246" [[package]] name = "utf8_iter" version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" [[package]] name = "vcpkg" version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" [[package]] name = "version_check" version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" [[package]] name = "walkdir" version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" dependencies = [ "same-file", "winapi-util", ] [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasi" version = "0.13.3+wasi-0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26816d2e1a4a36a2940b96c5296ce403917633dff8f3440e9b236ed6f6bacad2" dependencies = [ "wit-bindgen-rt", ] [[package]] name = "wasm-bindgen" version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" dependencies = [ "cfg-if", "once_cell", "rustversion", "wasm-bindgen-macro", ] [[package]] name = "wasm-bindgen-backend" version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" dependencies = [ "bumpalo", "log", "proc-macro2", "quote", "syn", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-macro" version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" dependencies = [ "quote", "wasm-bindgen-macro-support", ] [[package]] name = "wasm-bindgen-macro-support" version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" dependencies = [ "proc-macro2", "quote", "syn", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" dependencies = [ "unicode-ident", ] [[package]] name = "web-sys" version = "0.3.77" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2" dependencies = [ "js-sys", "wasm-bindgen", ] [[package]] name = "win-crypto-ng" version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "99abfb435a71e54ab2971d8d8c32f1a7e006cdbf527f71743b1d45b93517bb92" dependencies = [ "cipher", "doc-comment", "rand_core", "winapi", "zeroize", ] [[package]] name = "winapi" version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" dependencies = [ "winapi-i686-pc-windows-gnu", "winapi-x86_64-pc-windows-gnu", ] [[package]] name = "winapi-i686-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-util" version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" dependencies = [ "windows-sys 0.48.0", ] [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "windows-core" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" dependencies = [ "windows-targets 0.52.6", ] [[package]] name = "windows-link" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6dccfd733ce2b1753b03b6d3c65edf020262ea35e20ccdf3e288043e6dd620e3" [[package]] name = "windows-sys" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" dependencies = [ "windows-targets 0.48.5", ] [[package]] name = "windows-sys" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ "windows-targets 0.52.6", ] [[package]] name = "windows-targets" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" dependencies = [ "windows_aarch64_gnullvm 0.48.5", "windows_aarch64_msvc 0.48.5", "windows_i686_gnu 0.48.5", "windows_i686_msvc 0.48.5", "windows_x86_64_gnu 0.48.5", "windows_x86_64_gnullvm 0.48.5", "windows_x86_64_msvc 0.48.5", ] [[package]] name = "windows-targets" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ "windows_aarch64_gnullvm 0.52.6", "windows_aarch64_msvc 0.52.6", "windows_i686_gnu 0.52.6", "windows_i686_gnullvm", "windows_i686_msvc 0.52.6", "windows_x86_64_gnu 0.52.6", "windows_x86_64_gnullvm 0.52.6", "windows_x86_64_msvc 0.52.6", ] [[package]] name = "windows_aarch64_gnullvm" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" [[package]] name = "windows_aarch64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" [[package]] name = "windows_aarch64_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" [[package]] name = "windows_aarch64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" [[package]] name = "windows_i686_gnu" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" [[package]] name = "windows_i686_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" [[package]] name = "windows_i686_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" [[package]] name = "windows_i686_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" [[package]] name = "windows_i686_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" [[package]] name = "windows_x86_64_gnu" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" [[package]] name = "windows_x86_64_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" [[package]] name = "windows_x86_64_gnullvm" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" [[package]] name = "windows_x86_64_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" [[package]] name = "windows_x86_64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "wit-bindgen-rt" version = "0.33.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3268f3d866458b787f390cf61f4bbb563b922d091359f9608842999eaee3943c" dependencies = [ "bitflags", ] [[package]] name = "write16" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936" [[package]] name = "writeable" version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" [[package]] name = "x25519-dalek" version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c7e468321c81fb07fa7f4c636c3972b9100f0346e5b6a9f2bd0603a52f7ed277" dependencies = [ "curve25519-dalek", "rand_core", "zeroize", ] [[package]] name = "xxhash-rust" version = "0.8.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fdd20c5420375476fbd4394763288da7eb0cc0b8c11deed431a91562af7335d3" [[package]] name = "yoke" version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "120e6aef9aa629e3d4f52dc8cc43a015c7724194c97dfaf45180d2daf2b77f40" dependencies = [ "serde", "stable_deref_trait", "yoke-derive", "zerofrom", ] [[package]] name = "yoke-derive" version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154" dependencies = [ "proc-macro2", "quote", "syn", "synstructure", ] [[package]] name = "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 = "zerofrom" version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "91ec111ce797d0e0784a1116d0ddcdbea84322cd79e5d5ad173daeba4f93ab55" dependencies = [ "zerofrom-derive", ] [[package]] name = "zerofrom-derive" version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" dependencies = [ "proc-macro2", "quote", "syn", "synstructure", ] [[package]] name = "zeroize" version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" 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", ] [[package]] name = "zerovec" version = "0.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aa2b893d79df23bfb12d5461018d408ea19dfafe76c2c7ef6d4eba614f8ff079" dependencies = [ "yoke", "zerofrom", "zerovec-derive", ] [[package]] name = "zerovec-derive" version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" dependencies = [ "proc-macro2", "quote", "syn", ] sequoia-openpgp-2.0.0/Cargo.toml0000644000000173330000000000100121540ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2021" rust-version = "1.79" name = "sequoia-openpgp" version = "2.0.0" authors = [ "Igor Matuszewski ", "Justus Winter ", "Kai Michaelis ", "Neal H. Walfield ", "Nora Widdecke ", "Wiktor Kwapisiewicz ", ] build = "build.rs" autobenches = false description = "OpenPGP data types and associated machinery" homepage = "https://sequoia-pgp.org/" documentation = "https://docs.rs/sequoia-openpgp" readme = "README.md" keywords = [ "cryptography", "openpgp", "pgp", "encryption", "signing", ] categories = [ "cryptography", "authentication", "email", ] license = "LGPL-2.0-or-later" repository = "https://gitlab.com/sequoia-pgp/sequoia" [lib] bench = false [[example]] name = "pad" required-features = ["compression-deflate"] [[example]] name = "secret-leak-detector" path = "tests/secret-leak-detector/detector.rs" [[bench]] name = "run_benchmarks" harness = false [dependencies.aes] version = "0.8" features = ["zeroize"] optional = true [dependencies.aes-gcm] version = "0.10" features = ["std"] optional = true [dependencies.anyhow] version = "1.0.18" [dependencies.argon2] version = "0.5" [dependencies.base64] version = ">= 0.21, < 0.23" [dependencies.block-padding] version = "0.3" optional = true [dependencies.blowfish] version = "0.9" features = ["zeroize"] optional = true [dependencies.botan] version = ">= 0.10.6, < 0.12" optional = true [dependencies.buffered-reader] version = "1.3.0" default-features = false [dependencies.bzip2] version = ">= 0.4, < 0.6" optional = true [dependencies.camellia] version = "0.1" features = ["zeroize"] optional = true [dependencies.cast5] version = "0.11" features = ["zeroize"] optional = true [dependencies.cfb-mode] version = "0.8" optional = true [dependencies.cipher] version = "0.4" features = [ "std", "zeroize", ] optional = true [dependencies.des] version = "0.8" features = ["zeroize"] optional = true [dependencies.digest] version = "0.10" optional = true [dependencies.dsa] version = "0.6" optional = true [dependencies.dyn-clone] version = "1" [dependencies.eax] version = "0.5" optional = true [dependencies.ecb] version = "0.1" optional = true [dependencies.ecdsa] version = "0.16" features = [ "hazmat", "arithmetic", ] optional = true [dependencies.ed25519] version = "2" features = ["std"] optional = true default-features = false [dependencies.ed25519-dalek] version = "2" features = [ "rand_core", "zeroize", ] optional = true [dependencies.flate2] version = "1.0.1" optional = true [dependencies.hkdf] version = "0.12" optional = true [dependencies.idea] version = "0.5" features = ["zeroize"] optional = true [dependencies.idna] version = ">= 1.0.3, < 2" [dependencies.lalrpop-util] version = "0.20" [dependencies.libc] version = "0.2.66" [dependencies.md-5] version = "0.10" features = ["oid"] optional = true [dependencies.memsec] version = ">=0.5, <0.8" default-features = false [dependencies.nettle] version = "7.3" optional = true [dependencies.num-bigint-dig] version = "0.8" optional = true default-features = false [dependencies.ocb3] version = "0.1" optional = true default-features = false [dependencies.openssl] version = "0.10.55" optional = true [dependencies.openssl-sys] version = "0.9.90" optional = true [dependencies.p256] version = "0.13" features = [ "ecdh", "ecdsa", ] optional = true [dependencies.p384] version = "0.13" features = [ "ecdh", "ecdsa", ] optional = true [dependencies.p521] version = "0.13" features = [ "ecdh", "ecdsa", ] optional = true [dependencies.rand] version = "0.8" optional = true default-features = false [dependencies.rand_core] version = "0.6" optional = true [dependencies.regex] version = "1" [dependencies.regex-syntax] version = "0.8" [dependencies.ripemd] version = "0.1" features = ["oid"] optional = true [dependencies.rsa] version = "0.9.0" optional = true [dependencies.sha1collisiondetection] version = "0.3.1" features = ["std"] default-features = false [dependencies.sha2] version = "0.10" features = ["oid"] optional = true [dependencies.sha3] version = "0.10" features = ["oid"] optional = true [dependencies.thiserror] version = ">=1, <3" [dependencies.twofish] version = "0.7" features = ["zeroize"] optional = true [dependencies.typenum] version = "1.12.0" optional = true [dependencies.x25519-dalek] version = "2" features = [ "static_secrets", "zeroize", ] optional = true default-features = false [dependencies.xxhash-rust] version = "0.8" features = ["xxh3"] [dev-dependencies.criterion] version = "0.5" features = ["html_reports"] [dev-dependencies.quickcheck] version = "1" default-features = false [dev-dependencies.rand] version = "0.8" features = [ "std", "std_rng", ] default-features = false [dev-dependencies.rpassword] version = "7.0" [build-dependencies.lalrpop] version = "0.20" default-features = false [features] __implicit-crypto-backend-for-tests = [] allow-experimental-crypto = [] allow-variable-time-crypto = [] compression = [ "compression-deflate", "compression-bzip2", ] compression-bzip2 = [ "dep:bzip2", "buffered-reader/compression-bzip2", ] compression-deflate = [ "dep:flate2", "buffered-reader/compression-deflate", ] crypto-botan = [ "dep:botan", "botan?/botan3", ] crypto-botan2 = ["dep:botan"] crypto-cng = [ "dep:cipher", "dep:eax", "dep:winapi", "dep:win-crypto-ng", "dep:ed25519", "dep:ed25519-dalek", "dep:num-bigint-dig", "dep:aes-gcm", "dep:rand_core", "dep:hkdf", "dep:sha2", "dep:ocb3", ] crypto-fuzzing = [] crypto-nettle = ["dep:nettle"] crypto-openssl = [ "dep:openssl", "dep:openssl-sys", ] crypto-rust = [ "dep:aes", "dep:block-padding", "dep:blowfish", "dep:camellia", "dep:cast5", "dep:cfb-mode", "dep:cipher", "dep:des", "dep:digest", "dep:eax", "dep:ecb", "dep:ed25519", "dep:ed25519-dalek", "dep:idea", "dep:md-5", "dep:num-bigint-dig", "dep:ripemd", "dep:rsa", "dep:sha2", "dep:sha3", "sha1collisiondetection/digest-trait", "sha1collisiondetection/oid", "dep:twofish", "dep:typenum", "dep:x25519-dalek", "dep:ocb3", "dep:p256", "dep:p384", "dep:p521", "dep:rand", "rand?/getrandom", "dep:rand_core", "rand_core?/getrandom", "dep:ecdsa", "dep:aes-gcm", "dep:dsa", "dep:hkdf", ] default = [ "compression", "crypto-nettle", ] [target."cfg(all(target_arch = \"wasm32\", target_os = \"unknown\"))".dependencies.chrono] version = "0.4.10" features = [ "std", "wasmbind", "clock", ] default-features = false [target."cfg(all(target_arch = \"wasm32\", target_os = \"unknown\"))".dependencies.getrandom] version = "0.2" features = ["js"] [target."cfg(windows)".dependencies.win-crypto-ng] version = "0.5.1" features = [ "rand", "block-cipher", ] optional = true [target."cfg(windows)".dependencies.winapi] version = "0.3.8" features = ["bcrypt"] optional = true default-features = false [badges.gitlab] repository = "sequoia-pgp/sequoia" [badges.maintenance] status = "actively-developed" [lints.rust.unexpected_cfgs] level = "warn" priority = 0 sequoia-openpgp-2.0.0/Cargo.toml.orig000064400000000000000000000167671046102023000156470ustar 00000000000000[package] name = "sequoia-openpgp" description = "OpenPGP data types and associated machinery" version = "2.0.0" authors = [ "Igor Matuszewski ", "Justus Winter ", "Kai Michaelis ", "Neal H. Walfield ", "Nora Widdecke ", "Wiktor Kwapisiewicz ", ] build = "build.rs" documentation = "https://docs.rs/sequoia-openpgp" autobenches = false homepage = "https://sequoia-pgp.org/" repository = "https://gitlab.com/sequoia-pgp/sequoia" readme = "README.md" keywords = ["cryptography", "openpgp", "pgp", "encryption", "signing"] categories = ["cryptography", "authentication", "email"] license = "LGPL-2.0-or-later" edition = "2021" rust-version = "1.79" [badges] gitlab = { repository = "sequoia-pgp/sequoia" } maintenance = { status = "actively-developed" } [dependencies] anyhow = "1.0.18" argon2 = "0.5" buffered-reader = { path = "../buffered-reader", version = "1.3.0", default-features = false } base64 = ">= 0.21, < 0.23" bzip2 = { version = ">= 0.4, < 0.6", optional = true } dyn-clone = "1" flate2 = { version = "1.0.1", optional = true } idna = ">= 1.0.3, < 2" lalrpop-util = "0.20" libc = "0.2.66" memsec = { version = ">=0.5, <0.8", default-features = false } nettle = { version = "7.3", optional = true } regex = "1" regex-syntax = "0.8" sha1collisiondetection = { version = "0.3.1", default-features = false, features = ["std"] } thiserror = ">=1, <3" xxhash-rust = { version = "0.8", features = ["xxh3"] } # At least 0.10.55 is needed due `no-ocb` check: # https://github.com/sfackler/rust-openssl/blob/master/openssl/CHANGELOG.md openssl = { version = "0.10.55", optional = true } # We need to directly depend on the sys crate so that the metadata produced # in its build script is passed to sequoia-openpgp's build script # see: https://doc.rust-lang.org/cargo/reference/build-scripts.html#the-links-manifest-key openssl-sys = { version = "0.9.90", optional = true } # Botan. botan = { version = ">= 0.10.6, < 0.12", optional = true } # RustCrypto crates. aes = { version = "0.8", optional = true, features = ["zeroize"] } aes-gcm = { version = "0.10", optional = true, features = ["std"] } block-padding = { version = "0.3", optional = true } blowfish = { version = "0.9", optional = true, features = ["zeroize"] } camellia = { version = "0.1", optional = true, features = ["zeroize"] } cast5 = { version = "0.11", optional = true, features = ["zeroize"] } cipher = { version = "0.4", optional = true, features = ["std", "zeroize"] } cfb-mode = { version = "0.8", optional = true } des = { version = "0.8", optional = true, features = ["zeroize"] } digest = { version = "0.10", optional = true } dsa = { version = "0.6", optional = true } eax = { version = "0.5", optional = true } ecb = { version = "0.1", optional = true } ecdsa = { version = "0.16", optional = true, features = ["hazmat", "arithmetic"] } # XXX # We don't directly use ed25519, but ed25519-dalek reexports it and we # need the std feature, at least so that ed25519::Error implements # std::error::Error. ed25519 = { version = "2", default-features = false, features = ["std"], optional = true } ed25519-dalek = { version = "2", features = ["rand_core", "zeroize"], optional = true } hkdf = { version = "0.12", optional = true } idea = { version = "0.5", optional = true, features = ["zeroize"] } md-5 = { version = "0.10", features = ["oid"], optional = true } num-bigint-dig = { version = "0.8", default-features = false, optional = true } ocb3 = { version = "0.1", default-features = false, optional = true } p256 = { version = "0.13", optional = true, features = ["ecdh", "ecdsa"] } p384 = { version = "0.13", optional = true, features = ["ecdh", "ecdsa"] } p521 = { version = "0.13", optional = true, features = ["ecdh", "ecdsa"] } rand = { version = "0.8", optional = true, default-features = false } rand_core = { version = "0.6", optional = true } ripemd = { version = "0.1", features = ["oid"], optional = true } rsa = { version = "0.9.0", optional = true } sha2 = { version = "0.10", features = ["oid"], optional = true } sha3 = { version = "0.10", features = ["oid"], optional = true } twofish = { version = "0.7", optional = true, features = ["zeroize"] } typenum = { version = "1.12.0", optional = true } x25519-dalek = { version = "2", optional = true, default-features = false, features = ["static_secrets", "zeroize"] } [target.'cfg(windows)'.dependencies] win-crypto-ng = { version = "0.5.1", features = ["rand", "block-cipher"], optional = true } winapi = { version = "0.3.8", default-features = false, features = ["bcrypt"], optional = true } [target.'cfg(all(target_arch = "wasm32", target_os = "unknown"))'.dependencies] chrono = { version = "0.4.10", default-features = false, features = ["std", "wasmbind", "clock"] } getrandom = { version = "0.2", features = ["js"] } [build-dependencies] lalrpop = { version = "0.20", default-features = false } [dev-dependencies] quickcheck = { version = "1", default-features = false } rand = { version = "0.8", default-features = false, features = ["std", "std_rng"] } rpassword = "7.0" criterion = { version = "0.5", features = ["html_reports"] } [features] default = ["compression", "crypto-nettle"] # TODO(#333): Allow for/implement more backends crypto-nettle = ["dep:nettle"] crypto-rust = [ "dep:aes", "dep:block-padding", "dep:blowfish", "dep:camellia", "dep:cast5", "dep:cfb-mode", "dep:cipher", "dep:des", "dep:digest", "dep:eax", "dep:ecb", "dep:ed25519", "dep:ed25519-dalek", "dep:idea", "dep:md-5", "dep:num-bigint-dig", "dep:ripemd", "dep:rsa", "dep:sha2", "dep:sha3", "sha1collisiondetection/digest-trait", "sha1collisiondetection/oid", "dep:twofish", "dep:typenum", "dep:x25519-dalek", "dep:ocb3", "dep:p256", "dep:p384", "dep:p521", "dep:rand", "rand?/getrandom", "dep:rand_core", "rand_core?/getrandom", "dep:ecdsa", "dep:aes-gcm", "dep:dsa", "dep:hkdf", ] crypto-cng = [ "dep:cipher", "dep:eax", "dep:winapi", "dep:win-crypto-ng", "dep:ed25519", "dep:ed25519-dalek", "dep:num-bigint-dig", "dep:aes-gcm", "dep:rand_core", "dep:hkdf", "dep:sha2", "dep:ocb3", ] crypto-openssl = ["dep:openssl", "dep:openssl-sys"] crypto-botan = ["dep:botan", "botan?/botan3"] crypto-botan2 = ["dep:botan"] crypto-fuzzing = [] __implicit-crypto-backend-for-tests = [] # Experimental and variable-time cryptographic backends opt-ins allow-experimental-crypto = [] allow-variable-time-crypto = [] # The compression algorithms. compression = ["compression-deflate", "compression-bzip2"] compression-deflate = ["dep:flate2", "buffered-reader/compression-deflate"] compression-bzip2 = ["dep:bzip2", "buffered-reader/compression-bzip2"] [lib] bench = false [[example]] name = "pad" required-features = ["compression-deflate"] [[bench]] name = "run_benchmarks" harness = false [[example]] name = "secret-leak-detector" path = "tests/secret-leak-detector/detector.rs" # rustc version 1.80 adds the `unexpected_cfgs` warning, which warns # about unexpected cfg condition names. Extend the list of cfg # condition names to include those that we use. # # https://blog.rust-lang.org/2024/05/06/check-cfg.html # https://doc.rust-lang.org/stable/rustc/check-cfg.html#well-known-names-and-values [lints.rust] unexpected_cfgs = { level = "warn", check-cfg = [ 'cfg(osslconf, values("OPENSSL_NO_BF"))', 'cfg(osslconf, values("OPENSSL_NO_CAMELLIA"))', 'cfg(osslconf, values("OPENSSL_NO_CAST"))', 'cfg(osslconf, values("OPENSSL_NO_IDEA"))', 'cfg(osslconf, values("OPENSSL_NO_OCB"))', ] } sequoia-openpgp-2.0.0/LICENSE.txt000064400000000000000000000627341046102023000145760ustar 00000000000000Sequoia PGP is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Sequoia PGP is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. --- GNU LIBRARY GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1991 Free Software Foundation, Inc. 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. [This is the first released version of the library GPL. It is numbered 2 because it goes with version 2 of the ordinary GPL.] Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public Licenses are intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This license, the Library General Public License, applies to some specially designated Free Software Foundation software, and to any other libraries whose authors decide to use it. You can use it for your libraries, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the library, or if you modify it. For example, if you distribute copies of the library, whether gratis or for a fee, you must give the recipients all the rights that we gave you. You must make sure that they, too, receive or can get the source code. If you link a program with the library, you must provide complete object files to the recipients so that they can relink them with the library, after making changes to the library and recompiling it. And you must show them these terms so they know their rights. Our method of protecting your rights has two steps: (1) copyright the library, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the library. Also, for each distributor's protection, we want to make certain that everyone understands that there is no warranty for this free library. If the library is modified by someone else and passed on, we want its recipients to know that what they have is not the original version, so that any problems introduced by others will not reflect on the original authors' reputations. Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that companies distributing free software will individually obtain patent licenses, thus in effect transforming the program into proprietary software. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. Most GNU software, including some libraries, is covered by the ordinary GNU General Public License, which was designed for utility programs. This license, the GNU Library General Public License, applies to certain designated libraries. This license is quite different from the ordinary one; be sure to read it in full, and don't assume that anything in it is the same as in the ordinary license. The reason we have a separate public license for some libraries is that they blur the distinction we usually make between modifying or adding to a program and simply using it. Linking a program with a library, without changing the library, is in some sense simply using the library, and is analogous to running a utility program or application program. However, in a textual and legal sense, the linked executable is a combined work, a derivative of the original library, and the ordinary General Public License treats it as such. Because of this blurred distinction, using the ordinary General Public License for libraries did not effectively promote software sharing, because most developers did not use the libraries. We concluded that weaker conditions might promote sharing better. However, unrestricted linking of non-free programs would deprive the users of those programs of all benefit from the free status of the libraries themselves. This Library General Public License is intended to permit developers of non-free programs to use free libraries, while preserving your freedom as a user of such programs to change the free libraries that are incorporated in them. (We have not seen how to achieve this as regards changes in header files, but we have achieved it as regards changes in the actual functions of the Library.) The hope is that this will lead to faster development of free libraries. The precise terms and conditions for copying, distribution and modification follow. Pay close attention to the difference between a "work based on the library" and a "work that uses the library". The former contains code derived from the library, while the latter only works together with the library. Note that it is possible for a library to be covered by the ordinary General Public License rather than by this special one. GNU LIBRARY GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License Agreement applies to any software library which contains a notice placed by the copyright holder or other authorized party saying it may be distributed under the terms of this Library General Public License (also called "this License"). Each licensee is addressed as "you". A "library" means a collection of software functions and/or data prepared so as to be conveniently linked with application programs (which use some of those functions and data) to form executables. The "Library", below, refers to any such software library or work which has been distributed under these terms. A "work based on the Library" means either the Library or any derivative work under copyright law: that is to say, a work containing the Library or a portion of it, either verbatim or with modifications and/or translated straightforwardly into another language. (Hereinafter, translation is included without limitation in the term "modification".) "Source code" for a work means the preferred form of the work for making modifications to it. For a library, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the library. Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running a program using the Library is not restricted, and output from such a program is covered only if its contents constitute a work based on the Library (independent of the use of the Library in a tool for writing it). Whether that is true depends on what the Library does and what the program that uses the Library does. 1. You may copy and distribute verbatim copies of the Library's complete source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and distribute a copy of this License along with the Library. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Library or any portion of it, thus forming a work based on the Library, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) The modified work must itself be a software library. b) You must cause the files modified to carry prominent notices stating that you changed the files and the date of any change. c) You must cause the whole of the work to be licensed at no charge to all third parties under the terms of this License. d) If a facility in the modified Library refers to a function or a table of data to be supplied by an application program that uses the facility, other than as an argument passed when the facility is invoked, then you must make a good faith effort to ensure that, in the event an application does not supply such function or table, the facility still operates, and performs whatever part of its purpose remains meaningful. (For example, a function in a library to compute square roots has a purpose that is entirely well-defined independent of the application. Therefore, Subsection 2d requires that any application-supplied function or table used by this function must be optional: if the application does not supply it, the square root function must still compute square roots.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Library, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Library, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Library. In addition, mere aggregation of another work not based on the Library with the Library (or with a work based on the Library) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may opt to apply the terms of the ordinary GNU General Public License instead of this License to a given copy of the Library. To do this, you must alter all the notices that refer to this License, so that they refer to the ordinary GNU General Public License, version 2, instead of to this License. (If a newer version than version 2 of the ordinary GNU General Public License has appeared, then you can specify that version instead if you wish.) Do not make any other change in these notices. Once this change is made in a given copy, it is irreversible for that copy, so the ordinary GNU General Public License applies to all subsequent copies and derivative works made from that copy. This option is useful when you wish to copy part of the code of the Library into a program that is not a library. 4. You may copy and distribute the Library (or a portion or derivative of it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange. If distribution of object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place satisfies the requirement to distribute the source code, even though third parties are not compelled to copy the source along with the object code. 5. A program that contains no derivative of any portion of the Library, but is designed to work with the Library by being compiled or linked with it, is called a "work that uses the Library". Such a work, in isolation, is not a derivative work of the Library, and therefore falls outside the scope of this License. However, linking a "work that uses the Library" with the Library creates an executable that is a derivative of the Library (because it contains portions of the Library), rather than a "work that uses the library". The executable is therefore covered by this License. Section 6 states terms for distribution of such executables. When a "work that uses the Library" uses material from a header file that is part of the Library, the object code for the work may be a derivative work of the Library even though the source code is not. Whether this is true is especially significant if the work can be linked without the Library, or if the work is itself a library. The threshold for this to be true is not precisely defined by law. If such an object file uses only numerical parameters, data structure layouts and accessors, and small macros and small inline functions (ten lines or less in length), then the use of the object file is unrestricted, regardless of whether it is legally a derivative work. (Executables containing this object code plus portions of the Library will still fall under Section 6.) Otherwise, if the work is a derivative of the Library, you may distribute the object code for the work under the terms of Section 6. Any executables containing that work also fall under Section 6, whether or not they are linked directly with the Library itself. 6. As an exception to the Sections above, you may also compile or link a "work that uses the Library" with the Library to produce a work containing portions of the Library, and distribute that work under terms of your choice, provided that the terms permit modification of the work for the customer's own use and reverse engineering for debugging such modifications. You must give prominent notice with each copy of the work that the Library is used in it and that the Library and its use are covered by this License. You must supply a copy of this License. If the work during execution displays copyright notices, you must include the copyright notice for the Library among them, as well as a reference directing the user to the copy of this License. Also, you must do one of these things: a) Accompany the work with the complete corresponding machine-readable source code for the Library including whatever changes were used in the work (which must be distributed under Sections 1 and 2 above); and, if the work is an executable linked with the Library, with the complete machine-readable "work that uses the Library", as object code and/or source code, so that the user can modify the Library and then relink to produce a modified executable containing the modified Library. (It is understood that the user who changes the contents of definitions files in the Library will not necessarily be able to recompile the application to use the modified definitions.) b) Accompany the work with a written offer, valid for at least three years, to give the same user the materials specified in Subsection 6a, above, for a charge no more than the cost of performing this distribution. c) If distribution of the work is made by offering access to copy from a designated place, offer equivalent access to copy the above specified materials from the same place. d) Verify that the user has already received a copy of these materials or that you have already sent this user a copy. For an executable, the required form of the "work that uses the Library" must include any data and utility programs needed for reproducing the executable from it. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. It may happen that this requirement contradicts the license restrictions of other proprietary libraries that do not normally accompany the operating system. Such a contradiction means you cannot use both them and the Library together in an executable that you distribute. 7. You may place library facilities that are a work based on the Library side-by-side in a single library together with other library facilities not covered by this License, and distribute such a combined library, provided that the separate distribution of the work based on the Library and of the other library facilities is otherwise permitted, and provided that you do these two things: a) Accompany the combined library with a copy of the same work based on the Library, uncombined with any other library facilities. This must be distributed under the terms of the Sections above. b) Give prominent notice with the combined library of the fact that part of it is a work based on the Library, and explaining where to find the accompanying uncombined form of the same work. 8. You may not copy, modify, sublicense, link with, or distribute the Library except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense, link with, or distribute the Library is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 9. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Library or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Library (or any work based on the Library), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Library or works based on it. 10. Each time you redistribute the Library (or any work based on the Library), the recipient automatically receives a license from the original licensor to copy, distribute, link with or modify the Library subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. 11. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Library at all. For example, if a patent license would not permit royalty-free redistribution of the Library by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Library. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply, and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 12. If the distribution and/or use of the Library is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Library under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 13. The Free Software Foundation may publish revised and/or new versions of the Library General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Library specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Library does not specify a license version number, you may choose any version ever published by the Free Software Foundation. 14. If you wish to incorporate parts of the Library into other free programs whose distribution conditions are incompatible with these, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Libraries If you develop a new library, and you want it to be of the greatest possible use to the public, we recommend making it free software that everyone can redistribute and change. You can do so by permitting redistribution under these terms (or, alternatively, under the terms of the ordinary General Public License). To apply these terms, attach the following notices to the library. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Also add information on how to contact you by electronic and paper mail. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the library, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the library `Frob' (a library for tweaking knobs) written by James Random Hacker. , 1 April 1990 Ty Coon, President of Vice That's all there is to it! sequoia-openpgp-2.0.0/NEWS000064400000000000000000001021021046102023000134320ustar 00000000000000 -*- org -*- #+TITLE: sequoia-openpgp NEWS – history of user-visible changes #+STARTUP: content hidestars * Changes in 2.0.0 ** Notable changes - This release adds support for RFC 9580, which revises RFC 4880. The revision adds new cryptographic algorithms, deprecates and removes old ones, and supports authenticated encryption. The legacy protocol version is still supported. ** Notable fixes - We now gracefully handle memory allocation failures when computing Argon2. - We fixed an internal mapping from asymmetric algorithm to slots in a policy data structure. * Changes in 2.0.0.alpha.2 ** Notable changes - Our MSRV is now 1.79. ** API changes - ValidKeyAmalgamation::into_key_amalgamation removed - serialize::stream::Recipient::new now takes a KeyHandle - serialize::stream::Recipient::keyid renamed to key_handle, now returns an Option - serialize::stream::Recipient::set_keyid renamed to set_key_handle, now takes an Option, and is fallible - serialize::stream::Recipient new now also takes a Features argument - serialize::stream::Recipient now only implements From and From - KeyAmalgamationIter::key_handles2 renamed to key_handles - ValidKeyAmalgamationIter::key_handles2 renamed to key_handles - Cert::insert_packets2 renamed to insert_packets - Fingerprint no longer implements Borrow<[u8]> - KeyID no longer implements Borrow<[u8]> - packet::signature::subpacket::CLOCK_SKEW_TOLERANCE is now simply a std::time::Duration - cert::Preferences::preferred_aead_algorithms removed - Curve::len removed - PacketParser::encrypted removed - StandardPolicy::packet_tag_cutoff removed - Features::supports_mdc removed - Features::set_mdc removed - Features::clear_mdc removed - SignatureBuilder::set_preferred_aead_algorithms removed - SubpacketAreas::preferred_aead_algorithms removed - SubpacketValue::PreferredAEADAlgorithms removed ** New functionality - ValidCert::key_handle - ValidCert::fingerprint - ValidCert::keyid - ValidComponentAmalgamation::amalgamation - ValidKeyAmalgamation::amalgamation - armor::Writer::set_profile - CertBuilder::set_features ** Notable fixes - Generation of v6 certification approval signatures is fixed - Key encryption now only uses AEAD by default for v6 keys - Messages using SEIPDv1 for a v6 recipient, and those using SEIPDv2 for a v4 recipient are now correctly encrypted - stream::padding::Padder now emits padding packets when creating RFC9580 messages, and nothing when creating RFC4880 messages - X448 encryption and decryption fixed - Sequoia no longer ignores formatting errors when reading secret keys. This reverts a change introduced with version 1.17.0. * Changes in 2.0.0.alpha.1 ** API changes - ComponentBundle::self_signatures2 renamed to ComponentBundle::self_signatures - ComponentBundle::certifications2 renamed to ComponentBundle::certifications - ComponentBundle::self_revocations2 renamed to ComponentBundle::self_revocations - ComponentBundle::other_revocations2 renamed to ComponentBundle::other_revocations - Signature::verify renamed to Signature::verify_document - armor::Reader::new removed - Padding::with_policy now takes a closure - PKESK3::new now takes Option - PKESK3::recipient now returns Option - message::Token is now private - message::MessageParserError is now private - cert::CipherSuite no longer implements PartialOrd, Ord - SignatureBuilder::set_reference_time is now fallible - Fingerprint::Invalid is now called Fingerprint::Unknown and has a version field - Fingerprint::from_bytes now takes a version and is fallible - Padding::new is now fallible - SessionKey::new is now fallible - mem::Encrypted::new is now fallible - crypto::random is now fallible - RegexSet::everything is now infallible - Cert::revocation_keys now returns an unboxed iterator - ValidCert::revocation_keys no longer takes a policy argument - ValidCert::revocation_keys now returns an unboxed iterator - All hash functions defined on Signature and SignatureBuilder, such as Signature::hash_standalone, are now fallible - All hash functions defined on Signature and SignatureBuilder, such as Signature::hash_standalone, now check the signature type - crypto::hash::Hash::hash is now fallible - stream::Signer::new, stream::Signer::with_template, and stream::Signer::add_signer are now fallible: the streaming signer now computes the set of acceptable hash algorithms as signers are added via the constructors and Signer::add_signer, and returns an error if that set ever gets empty - stream::Signer::hash_algo now returns an error if the given algorithm is not acceptable for the set of signers - Error::UnsupportedCert2 renamed to Error::UnsupportedCert - TSK::set_filter now requires the filter to be Send + Sync - TSK::into_packets now returns an iterator that is Send + Sync - Cert no longer implements conversion into Vec - Cert no longer implements IntoIterator - Cert::into_packets2 renamed to Cert::into_packets - ValidAmalgamation::cert renamed to ValidAmalgamation::valid_cert - Message no longer derefs to PacketPile, use Message::packets - CertRevocationBuilder no longer derefs to SignatureBuilder - SubkeyRevocationBuilder no longer derefs to SignatureBuilder - UserIDRevocationBuilder no longer derefs to SignatureBuilder - UserAttributeRevocationBuilder no longer derefs to SignatureBuilder - Parse::from_buffered_reader is now mandatory - Parse::from_reader is no longer mandatory - SKESK6 no longer derefs to SKESK4 - PacketPileParser no longer derefs to PacketParserResult - ValidCert no longer derefs to Cert - ValidateAmalgamation::with_policy now takes a &self - ComponentBundle no longer derefs to the component - SignatureType::AttestationKey renamed to SignatureType::CertificationApproval - SubpacketTag::AttestedCertifications renamed to SubpacketTag::ApprovedCertifications - SubpacketValue::AttestedCertifications renamed to SubpacketValue::ApprovedCertifications - ComponentBundle::attestations renamed to ComponentBundle::approvals - SubpacketAreas::attested_certifications renamed to SubpacketAreas::approved_certifications - SignatureBuilder::set_attested_certifications renamed to SignatureBuilder::set_approved_certifications - ValidUserIDAmalgamation::attested_certifications renamed to ValidUserIDAmalgamation::approved_certifications - ValidUserIDAmalgamation::attestation_key_signatures renamed to ValidUserIDAmalgamation::certification_approval_key_signatures - ValidUserAttributeAmalgamation::attested_certifications renamed to ValidUserAttributeAmalgamation::approved_certifications - ValidUserAttributeAmalgamation::attestation_key_signatures renamed to ValidUserAttributeAmalgamation::certification_approval_key_signatures - Packet::AED removed - packet::aed::AED1 removed - serialize::stream::Encryptor2 renamed to Encryptor - crypto::ecdh::decrypt_unwrap2 renamed to decrypt_unwrap2 - UserID::name2 renamed to UserID::name - UserID::comment2 renamed to UserID::comment - UserID::email2 renamed to UserID::email - UserID::uri2 renamed to UserID::uri - Features::supports_aead removed - Features::set_aead removed - Features::clear_aead removed - cert::amalgamation::ValidAmalgamation::map has been moved to cert::amalgamation::ValidBindingSignature::map - cert::amalgamation::ValidAmalgamation is now dyn-compatible - crypto::AEADAlgorithm::iv_size removed - DecryptionHelper::decrypt now uses dynamic dispatch for the `decryption` parameter - DecryptionHelper::decrypt now returns a Result> - UserIDAmalgamation::attest_certifications2 renamed to attest_certifications - UserAttributeAmalgamation::attest_certifications2 renamed to attest_certifications - ValidUserAttributeAmalgamation::attest_certifications renamed to approve_of_certifications - ValidUserIDAmalgamation::attest_certifications renamed to approve_of_certifications - UserAttributeAmalgamation::attest_certifications renamed to approve_of_certifications - UserIDAmalgamation::attest_certifications renamed to approve_of_certifications - ComponentAmalgamation no longer derefs to the ComponentBundle - KeyAmalgamation no longer derefs to ComponentAmalgamation - ValidComponentAmalgamation no longer derefs to ComponentAmalgamation - ValidKeyAmalgamation no longer derefs to KeyAmalgamation - Signature::hash_userid_attestation renamed to hash_userid_approval - Signature::hash_user_attribute_attestation renamed to hash_user_attribute_approval - Signature::verify_userid_attestation renamed to verify_userid_approval - Signature::verify_user_attribute_attestation renamed to hash_user_attribute_approval - packet::CompressedData::container_ref - packet::CompressedData::container_mut - packet::CompressedData no longer derefs to Container - packet::seip::SEIP1::container_ref - packet::seip::SEIP1::container_mut - packet::seip::SEIP1 no longer derefs to Container - packet::seip::SEIP2::container_ref - packet::seip::SEIP2::container_mut - packet::seip::SEIP2 no longer derefs to Container - PacketPile::pretty_print removed - parse::stream::MessageStructure no longer derefs to [MessageLayer] - packet::Container no longer derefs to packet::Body - Key6::import_public_cv25519 renamed to import_public_x25519 - Key6::import_public_x25519 now produces a X25519 key, not an ECDH key - Key6::import_public_ed25519 now produces an Ed25519 key, not an EdDSALegacy key - CertBuilder::general_purpose is more ergonomic now. Its first parameter is dropped, the second can take any number of user IDs. - UserID::from_address now uses distinct types for its arguments. The name and comment argument now take an Into>. - UserID::from_unchecked address now uses distinct types for its arguments. The name and comment argument now take an Into>. - SignatureBuilder::set_revocation_key now takes a single key - parse::stream::VerificationError is now marked non-exhaustive - Parse is now sealed ** New functionality - Signature::hash_userid_attestation - Signature::hash_user_attribute_attestation - Message::packets - PacketPileParser::packet - PacketPileParser::packet_mut - crypto::PublicKeyAlgorithm - crypto::Curve - crypto::SymmetricAlgorithm - crypto::AEADAlgorithm - crypto::HashAlgorithm - crypto::Curve::BrainpoolP384 - public re-export of anyhow - cert::amalgamation::ValidBindingSignature - impl Borrow<[u8]> for Fingerprint - impl Borrow<[u8]> for KeyID - ComponentAmalgamation::binding_signature - ComponentAmalgamation::approvals - UserIDAmalgamation::revocation_status - UserAttributeAmalgamation::revocation_status - UnknownComponentAmalgamation::unknown - KeyAmalgamation::key - KeyAmalgamation::revocation_status - PrimaryKeyAmalgamation::binding_signature - SubordinateKeyAmalgamation::binding_signature - KeyAmalgamation::cert - KeyAmalgamation::bundle - KeyAmalgamation::component - KeyAmalgamation::self_signatures - KeyAmalgamation::certifications - KeyAmalgamation::self_revocations - KeyAmalgamation::other_revocations - KeyAmalgamation::signatures - ValidComponentAmalgamation::cert - ValidComponentAmalgamation::binding_signature - ValidComponentAmalgamation::bundle - ValidComponentAmalgamation::component - ValidComponentAmalgamation::approvals - ValidUserIDAmalgamation::userid - ValidUserAttributeAmalgamation::user_attribute - ValidKeyAmalgamation::cert - ValidKeyAmalgamation::binding_signature - ValidKeyAmalgamation::bundle - ValidKeyAmalgamation::component - ValidKeyAmalgamation::self_signatures - ValidKeyAmalgamation::certifications - ValidKeyAmalgamation::self_revocations - ValidKeyAmalgamation::other_revocations - ValidKeyAmalgamation::approvals - ValidKeyAmalgamation::signatures - parse::stream::MessageStructure::iter - Key6::import_secret_x25519 - Key6::import_secret_ed25519 - Key6::import_public_x448 - Key6::import_secret_x448 - Key6::import_public_ed448 - Key6::import_secret_ed448 - SignatureBuilder::add_revocation_key - parse::stream::VerificationError::UnknownSignature - RawCert::into_owned ** Deprecated functionality - Cert::insert_packets, use Cert::insert_packets2 instead ** Notable fixes - Fixed an integer overflow parsing PKESKv6 packets. * Changes in 2.0.0.alpha.0 The major new feature is the support for RFC9580, which modernizes the cryptographic primitives used in OpenPGP. Notably, it adds Authenticated Encryption, Elliptic Curve Cryptography, and deprecates old algorithms. We have also used the opportunity to clean up our API. Most programs should only require minor adjustments. ** API changes - Fingerprint::V5 removed - KeyID::V4 removed - HashAlgorithm::context now returns crypto::hash::Builder - crypto::Hash::hash now takes a &mut crypto::hash::Context - crypto::hash::Digest removed - SKESK::V5 removed - SKESK5 removed - PacketParser::decrypt: The parameter `algo` is now polymorphic over `Into>`. - DecryptionHelper::decrypt: The function `decrypt` now takes an optional symmetric algorithm. - SKESK::decrypt now returns an optional symmetric algorithm. - PKESK::decrypt now returns an optional symmetric algorithm. - key::SecretKeyMaterial::decrypt now takes a &Key. - key::SecretKeyMaterial::decrypt_in_place now takes a &Key. - key::SecretKeyMaterial::encrypt now takes a &Key. - key::SecretKeyMaterial::encrypt_in_place now takes a &Key. - key::Encrypted::decrypt now takes a &Key. - key::Unencrypted::encrypt now takes a &Key. - Curve is now marked as non-exhaustive - armor::Kind is now marked as non-exhaustive - armor::ReaderMode is now marked as non-exhaustive - cert::Ciphersuite is now marked as non-exhaustive - Profile is now marked as non-exhaustive - packet::Tag is now marked as non-exhaustive - user_attribute::Subpacket is now marked as non-exhaustive - user_attribute::Image is now marked as non-exhaustive - parse::Dearmor is now marked as non-exhaustive - impl From for ProtectedMPI has been removed - NullPolicy::new is now marked as unsafe - DataFormat::MIME removed - DataFormat::Text is deprecated in favor of DataFormat::Unicode - DataFormat::Unknown is a u8 now - Curve::bits now returns Result - HashAlgorithm::from_str now returns Result ** Policy changes - Version 4 DSA keys and signatures will be rejected from the 2030-02-01 on. - Version 4 ElGamal keys will be rejected from the 2025-02-01 on. - Version 1 SEIPD packets with IDEA or CAST5 be rejected from the 2025-02-01 on. ** New functionality - Fingerprint::V6 - KeyID::Long - S2K::Argon2 - HashAlgorithm::SHA3_256 - HashAlgorithm::SHA3_512 - types::Features::clear_seipdv2 - types::Features::set_seipdv2 - types::Features::supports_seipdv2 - SubpacketTag::PreferredAEADCiphersuites - SubpacketValue::PreferredAEADCiphersuites - SubpacketAreas::preferred_aead_ciphersuites - SignatureBuilder::set_preferred_aead_ciphersuites - Preferences::preferred_aead_ciphersuites - Profile - Key::V6 - Key6 - CertBuilder::set_profile - key::Encrypted::new_aead - key::Encrypted::aead_algo - key::Encrypted::aead_iv - mpi::PublicKey::X25519 - mpi::PublicKey::X448 - mpi::PublicKey::Ed25519 - mpi::PublicKey::Ed448 - mpi::SecretKeyMaterial::X25519 - mpi::SecretKeyMaterial::X448 - mpi::SecretKeyMaterial::Ed25519 - mpi::SecretKeyMaterial::Ed448 - mpi::Ciphertext::X25519 - mpi::Ciphertext::X448 - mpi::Signature::Ed25519 - mpi::Signature::Ed448 - Key4::generate_x25519 - Key4::generate_x448 - Key4::generate_ed25519 - Key4::generate_ed448 - policy::AsymmetricAlgorithm::X25519 - policy::AsymmetricAlgorithm::X448 - policy::AsymmetricAlgorithm::Ed25519 - policy::AsymmetricAlgorithm::Ed448 - PublicKeyAlgorithm::X25519 - PublicKeyAlgorithm::X448 - PublicKeyAlgorithm::Ed25519 - PublicKeyAlgorithm::Ed448 - crypto::hash::Context - HashAlgorithm::digest_size - crypto::hash::Builder - Signature::V6 - Signature6 - SignatureBuilder::prefix_salt - SignatureBuilder::set_prefix_salt - HashAlgorithm::salt_size - OnePassSig::V6 - OnePassSig6 - Packet::Padding - Padding - Tag::Padding - KeyAmalgamationIter::key_handles2 - ValidKeyAmalgamationIter::key_handles2 - SKESK::V6 - SKESK6 - impl From> for KeyID - PKESK::V6 - PKESK6 - SEIP::V2 - SEIP2 - serialize::stream::Encryptor2::aead_algo - key::KeyRole::role - key::KeyRoleRT - key::SecretKeyMaterial::encrypt_with - key::SecretKeyMaterial::encrypt_in_place_with - key::Unencrypted::encrypt_with - The RustCrypto backend now supports OCB. - The Windows CNG backend backend now supports OCB implemented using the OCB implementation from RustCrypto. - Packet::is_critical - Tag::is_critical ** Deprecated functionality - SubpacketTag::PreferredAEADAlgorithms - SubpacketValue::PreferredAEADAlgorithms - SubpacketAreas::preferred_aead_algorithms - SignatureBuilder::set_preferred_aead_algorithms - KeyAmalgamationIter::key_handles - ValidKeyAmalgamationIter::key_handles - SymmetricAlgorithm::IDEA - SymmetricAlgorithm::TripleDES - SymmetricAlgorithm::CAST5 ** Notable fixes * Changes in 1.22.0 ** New functionality - DetachedVerifier::verify_buffered_reader ** Notable fixes - UserIdAmalgamation::attest_certifications2, UserAttributeAmalgamation::attest_certifications2, ValidUserIdAmalgamation::attest_certifications, and ValidUserAttributeAmalgamation::attest_certifications now correctly set the creation time of newly created signatures. * Changes in 1.21.2 ** Notable fixes - A set of constructors for KeyFlags added in 1.21.0 mistakenly consumed a KeyFlags. This patch release fixes that, even though it is technically an API break. * Changes in 1.21.1 ** Notable fixes - Leftover debugging statements introduced in 1.21.0 printed long-term Cv25519 secret key material to stderr during decryption. This patch release fixes that. * Changes in 1.21.0 ** Notable fixes - The RawCertParser now correctly advance the input stream when encountering unsupported cert (primary key) versions. Previously, this resulted in an infinite loop. - Sequoia built with the OpenSSL backend or the RustCrypto backend will now make sure that the secret primes of generated RSA keys are ordered the way OpenPGP demands (i.e. `p` < `q`). - Sequoia built with the OpenSSL backend, the CNG backend, or the RustCrypto backend will now make sure that the secret primes of imported RSA keys are ordered the way OpenPGP demands. - Loosen trait bounds on Key::clone and Key4::clone. ** Notable changes - All signature verification methods now take an immutable reference to the signature. This will cause existing code to emit warnings about unused mutability, but should not break anything. - Sequoia now lazily verifies self signatures in certificates. Previously, they were eagerly verified during certificate canonicalization, incurring a substantial cost even for signatures that were not otherwise considered. ** New functionality - CipherSuite::variants. - ComponentBundle::certifications2 - ComponentBundle::other_revocations2 - ComponentBundle::self_revocations2 - ComponentBundle::self_signatures2 - Key::::steal_secret - Key::::steal_secret - Key4::::steal_secret - Key4::::steal_secret - cert::raw::Error::UnsupportedCert - packet::Unknown::into_error - The RustCrypto backend now supports ECDH and ECDSA over the NIST curve P-384. - The RustCrypto backend now supports ECDH and ECDSA over the NIST curve P-521. - UserAttributeAmalgamation::attest_certifications2 - UserIDAmalgamation::attest_certifications2 - KeyFlags::certification - KeyFlags::signing - KeyFlags::transport_encryption - KeyFlags::storage_encryption - KeyFlags::authentication - KeyFlags::split_key - KeyFlags::group_key - ValidKeyAmalgamation::primary_key_binding_signature - Fingerprint::aliases - KeyID::aliases ** Deprecated functionality - ComponentBundle::certifications - ComponentBundle::other_revocations - ComponentBundle::self_revocations - ComponentBundle::self_signatures - Add a signature verification cache, which is automatically consulted by the low-level signature verification functions, like `Signature::verify_digest`. - openpgp::packet::signature::cache::SignatureVerificationCache - openpgp::packet::signature::cache::Entry - UserAttributeAmalgamation::attest_certifications - UserIDAmalgamation::attest_certifications * Changes in 1.20.0 ** New functionality - S2K::Implicit - Signature::verify_signature * Changes in 1.19.0 ** Notable fixes - Key4::import_secret_cv25519 will now clamp some bits of the given secret scalar to make the generated secret key packet more compatible with implementations that do not implicitly do the clamping before decryption. - Sequoia built with the OpenSSL backend will now use the correct representation of points on Weierstrass curves. OpenPGP uses the uncompressed representation. Previously, the OpenSSL backend used the compressed representation by mistake. ** New functionality - Curve::variants * Changes in 1.18.0 ** New functionality - ComponentAmalgamation::certifications_by_key - UserIDAmalgamation::valid_certifications_by_key - KeyAmalgamation::valid_certifications_by_key - UserIDAmalgamation::active_certifications_by_key - KeyAmalgamation::active_certifications_by_key - UserIDAmalgamation::valid_third_party_revocations_by_key - KeyAmalgamation::valid_third_party_revocations_by_key - Parse::from_buffered_reader - armor::Reader::from_buffered_reader - Cert::exportable - CertBuilder::set_exportable - UserID::from_static_bytes - Error::ShortKeyID - Cert::into_packets2 - TSK::into_packets ** Deprecated functionality - Cert::into_packets * Changes in 1.17.0 ** Notable fixes - Sequoia now ignores some formatting errors when reading secret keys. Being lenient in this case helps the user recover their valuable key material. - Previously, Sequoia would buffer packet bodies when mapping is enabled in the parser, even if the packet parser is not configured to buffer the bodies. This adds considerable overhead. Starting with this version, Sequoia no longer includes the packet bodies in the maps unless the parser is configured to buffer any unread content. This makes parsing packets faster if you don't rely on the packet body in the map, but changes the default behavior. If you need the old behavior, please do adjust your code to buffer unread content. - To increase compatibility with early v4 certificates, if there is no key flags subpacket on either the active binding signature or the active direct key signature, we infer the key flags from the key's role and public key algorithm. - When creating an authentication-capable subkey, Sequoia now also adds a primary key binding signature. - The MSRV is now 1.67. - serialize::stream::Encryptor2 replaces serialize::stream::Encryptor, which fixes an issue with the lifetimes. ** New functionality - The RustCrypto backend now supports DSA. - cert::amalgamation::key::KeyAmalgamationIter::encrypted_secret - cert::amalgamation::key::ValidKeyAmalgamationIter::encrypted_secret - crypto::SessionKey::as_protected - crypto::ecdh::decrypt_unwrap2 - packet::Key::generate_dsa - packet::Key::generate_elgamal - packet::UserID::comment2 - packet::UserID::email2 - packet::UserID::name2 - packet::UserID::uri2 - parse::PacketParser::start_hashing - parse::PacketParserBuilder::automatic_hashing - impl Eq, PartialEq for regex::Regex - regex::Regex::as_str - impl Eq, PartialEq for regex::RegexSet - regex::RegexSet::as_bytes - impl Default for types::AEADAlgorithm - serialize::stream::Encryptor2 - types::AEADAlgorithm::GCM - types::Bitfield - types::Features::clear_seipdv1 - types::Features::set_seipdv1 - types::Features::supports_seipdv1 - types::Features::as_bitfield - types::KeyFlags::as_bitfield - types::KeyServerPreferences::as_bitfield ** Deprecated functionality - cert::Preferences::preferred_aead_algorithms - crypto::ecdh::decrypt_unwrap - packet::UserID::comment - packet::UserID::email - packet::UserID::name - packet::UserID::uri - packet::signature::SignatureBuilder::set_preferred_aead_algorithms - packet::signature::subpacket::SubpacketAreas::preferred_aead_algorithms - packet::signature::subpacket::SubpacketTag::PreferredAEADAlgorithms - packet::signature::subpacket::SubpacketValue::PreferredAEADAlgorithms - serialize::stream::Encryptor - types::Curve::len, use types::Curve::bits instead - types::Features::clear_mdc - types::Features::set_mdc - types::Features::supports_mdc - types::Features::clear_aead - types::Features::set_aead - types::Features::supports_aead * Changes in 1.16.0 ** New functionality - Add KeyFlags::set_certification_to. - Add KeyFlags::set_signing_to. - Add KeyFlags::set_transport_encryption_to. - Add KeyFlags::set_storage_encryption_to. - Add KeyFlags::set_split_key_to. - Add KeyFlags::set_group_key_to. ** Notable fixes - Several parser bugs were fixed. These are all low-severity as Rust correctly detects the out of bounds access and panics. ** Notable changes - The crypto/botan feature now selects Botan's v3 interface. The crypt/botan2 feature can be used to select Botan's v2 interface. * Changes in 1.15.0 ** New functionality - StandardPolicy::accept_hash_property ** Notable changes - Updated the crypto-rust backend. - Updated the crypto-cng backend. * Changes in 1.14.0 ** New cryptographic backends - We added a backend that uses Botan. ** New functionality - crypto::mem::Protected::new - crypto::mpi::SecretKeyMaterial::from_bytes - crypto::mpi::SecretKeyMaterial::from_bytes_with_checksum - fmt::hex::Dumper::with_offset - parse::buffered_reader re-export - policy::AsymmetricAlgorithm::BrainpoolP384 - RawCert implements Parse ** Deprecated functionality - crypto::mpi::SecretKeyMaterial::parse - crypto::mpi::SecretKeyMaterial::parse_with_checksum * Changes in 1.13.0 ** New cryptographic backends - We added a backend that uses OpenSSL. ** New functionality - RawCertParser - RawCert - RawPacket * Changes in 1.12.0 - Bug fix release. * Changes in 1.11.0 ** New functionality - Signature3 implements support for parsing, verifying, and reserializing version 3 signature packages. - AsymmetricAlgorithm implements PartialEq, Eq, and Copy. - AsymmetricAlgorithm::variants. - PublicKeyAlgorithm::variants. - SymmetricAlgorithm::variants. - AEADAlgorithm::variants. - CompressionAlgorithm::variants. - HashAlgorithm::variants. - SignatureType::variants. - ReasonForRevocation::variants. - DataFormat::variants. - packet::Tag::variants. - SubpacketTag::variants. - StandardPolicy::reject_all_hashes - StandardPolicy::reject_all_critical_subpackets - StandardPolicy::reject_all_asymmetric_algos - StandardPolicy::reject_all_symmetric_algos - StandardPolicy::reject_all_aead_algos - StandardPolicy::reject_all_packet_tags - StandardPolicy::accept_packet_tag_version - StandardPolicy::reject_packet_tag_version - StandardPolicy::reject_packet_tag_version_at - StandardPolicy::packet_tag_version_cutoff ** Deprecated functionality - StandardPolicy::packet_tag_cutoff * Changes in 1.10.0 ** New functionality - Cert::insert_packets2 - Cert::insert_packets_merge - crypto::ecdh::aes_key_wrap - crypto::ecdh::aes_key_unwrap - Error::UnsupportedCert2 - TryFrom for Unknown - types::{Curve, SymmetricAlgorithm, AEADAlgorithm, PublicKeyAlgorithm}'s Display implementation now provides short names by default. The long descriptions are provided by the alternate formatter (e.g. =format!("{:#}", ...)=) - cert::KeyBuilder - cert::SubkeyBuilder - HashAlgorithm::oid is available on all crypto backends (previously only on Nettle) ** Deprecated functionality - Error::UnsupportedCert, use Error::UnsupportedCert2 instead - DataFormat::MIME, no replacement, see #863 for details - PacketParser::encrypted, use the negation of PacketParser::processed * Changes in 1.9.0 ** New functionality - AEADAlgorithm::nonce_size replaces AEADAlgorithm::iv_size - crypto::backend - Curve::field_size - MPI::is_zero - MPI::zero - packet::Any - Packet::version - SignatureBuilder::set_reference_time - SignatureBuilder::effective_signature_creation_time ** Deprecated functionality - armor::Reader::new, use armor::Reader::from_reader instead - message::Token is not covered by SemVer guarantees, DO NOT match on it - AEADAlgorithm::iv_size, use AEADAlgorithm::nonce_size * Changes in 1.8.0 ** New functionality - crypto::Signer::acceptable_hashes - Fingerprint::V5 * Changes in 1.7.0 ** Notable fixes - sequoia-openpgp can now be compiled to WASM. - The MSRV is now 1.56.1. * Changes in 1.6.0 ** Notable fixes - Decryption of encrypted messages and verification of inline-signed messages is now considerably faster, as is ASCII Armor encoding and decoding. ** New functionality - CertRevocationBuilder::add_notation - CertRevocationBuilder::set_notation - KeyFlags::clear_group_key - SubkeyRevocationBuilder::add_notation - SubkeyRevocationBuilder::set_notation - UserAttributeRevocationBuilder::add_notation - UserAttributeRevocationBuilder::set_notation - UserIDRevocationBuilder::add_notation - UserIDRevocationBuilder::set_notation * Changes in 1.5.0 ** Notable changes - This crate is now licensed under the LGPL 2.0 or later. * Changes in 1.4.0 ** New cryptographic backends - We added a backend based on the RustCrypto crates. ** New functionality - CipherSuite::is_supported - MPI::value_padded - Preferences::policy_uri - ProtectedMPI::value_padded - TSK::eq - ValidAmalgamation::revocation_keys - ValidCert::policy_uri - ValidCert::revocation_keys ** Notable fixes - Filters set using CertParser::unvalidated_cert_filter are now preserved during iterations. * Changes in 1.3.1 ** Notable fixes - Fixed a crash resulting from unconstrained, attacker-controlled heap allocations. * Changes in 1.3.0 ** New functionality - CertBuilder::add_subkey_with - CertBuilder::add_user_attribute_with - CertBuilder::add_userid_with - ComponentBundle::attestations - Encryptor::with_session_key - Signature::verify_user_attribute_attestation - Signature::verify_userid_attestation - SignatureBuilder::pre_sign - SignatureBuilder::set_attested_certifications - SignatureType::AttestationKey - SubpacketAreas::MAX_SIZE - SubpacketAreas::attested_certifications - SubpacketTag::AttestedCertifications - SubpacketValue::AttestedCertifications - UserAttributeAmalgamation::attest_certifications - UserIDAmalgamation::attest_certifications - ValidUserAttributeAmalgamation::attest_certifications - ValidUserAttributeAmalgamation::attestation_key_signatures - ValidUserAttributeAmalgamation::attested_certifications - ValidUserIDAmalgamation::attest_certifications - ValidUserIDAmalgamation::attestation_key_signatures - ValidUserIDAmalgamation::attested_certifications ** Notable fixes - Improve Cert::insert_packets runtime from O(n^2) to O(n log n). - CertParser returned errors out of order (#699). * Changes in 1.1.0 ** New functionality - The new regex module provides regular expression support for scoping trust signatures. - Sequoia now supports the Cleartext Signature Framework. - ComponentAmalgamation::signatures - ComponentBundle::signatures - Fingerprint::to_spaced_hex - HashAlgorithm::text_name - KeyHandle now implements FromStr - KeyHandle::is_invalid - KeyHandle::to_hex - KeyHandle::to_spaced_hex - KeyID::to_spaced_hex - Signature4::hash_for_confirmation - Signature::hash_for_confirmation - TSK::armored - ValidComponentAmalgamation::signatures ** Notable fixes - Fixed two crashes related to detached signature verification. - Fixed a parsing bug where the parser did not consume all data in an compressed data packet. * Changes in 1.0.0 This is the initial stable release. sequoia-openpgp-2.0.0/README.md000064400000000000000000000210221046102023000142130ustar 00000000000000This crate aims to provide a complete implementation of OpenPGP as defined by [RFC 9580] as well as the deprecated OpenPGP as defined by [RFC 4880]. OpenPGP is a standard by the IETF. It was derived from the PGP software, which was created by Phil Zimmermann in 1991. This crate also includes support for unbuffered message processing. A few features that the OpenPGP community considers to be deprecated (e.g., version 3 compatibility) have been left out. We have also updated some OpenPGP defaults to avoid foot guns (e.g., we selected modern algorithm defaults). If some functionality is missing, please file a bug report. A non-goal of this crate is support for any sort of high-level, bolted-on functionality. For instance, [RFC 9580] does not define trust models, such as the web of trust, direct trust, or TOFU. Neither does this crate. [RFC 9580] does provide some mechanisms for creating trust models (specifically, UserID certifications), and this crate does expose those mechanisms. We also try hard to avoid dictating how OpenPGP should be used. This doesn't mean that we don't have opinions about how OpenPGP should be used in a number of common scenarios (for instance, message validation). But, in this crate, we refrain from expressing those opinions; we will expose an opinionated, high-level interface in the future. In order to figure out the most appropriate high-level interfaces, we look at existing users. If you are using Sequoia, please get in contact so that we can learn from your use cases, discuss your opinions, and develop a high-level interface based on these experiences in the future. Despite —or maybe because of— its unopinionated nature we found it easy to develop opinionated OpenPGP software based on Sequoia. [RFC 9580]: https://www.rfc-editor.org/rfc/rfc9580.html [RFC 4880]: https://tools.ietf.org/html/rfc4880 # Experimental Features This crate may implement extensions where the standardization effort is still ongoing. These experimental features are marked as such in the documentation. We invite you to experiment with them, but please do expect the semantics and possibly even the wire format to evolve. # Feature flags This crate uses *features* to enable or disable optional functionality. You can tweak the features in your `Cargo.toml` file, like so: ```toml sequoia-openpgp = { version = "*", default-features = false, features = ["compression", ...] } ``` By default, Sequoia is built using Nettle as cryptographic backend with all compression algorithms enabled. Using the default features is only appropriate for leaf crates, see [this section]. [this section]: #how-to-select-crypto-backends-in-crates Note that if you use `default-features = false`, you need to explicitly enable a crypto backend, and also enable compression features. ## Crypto backends Sequoia supports multiple cryptographic libraries that can be selected at compile time. Currently, these libraries are available: - The Nettle cryptographic library. This is the default backend, and is selected by the default feature set. If you use `default-features = false`, you need to explicitly include the `crypto-nettle` feature to enable it. - The OpenSSL backend. To select this backend, use `default-features = false`, and explicitly include the `crypto-openssl` feature to enable it. - The Botan backend. To select this backend, use `default-features = false`, and explicitly include the `crypto-botan` feature to enable it. `crypto-botan` defaults to Botan v3, which was release in April 2023. Use `crypto-botan2` to use v2. - The Windows Cryptography API: Next Generation (CNG). To select this backend, use `default-features = false`, and explicitly include the `crypto-cng` feature to enable it. Currently, the CNG backend requires at least Windows 10. - The RustCrypto crates. To select this backend, use `default-features = false`, and explicitly include the `crypto-rust` feature to enable it. As of this writing, the RustCrypto crates are not recommended for general use as they cannot offer the same security guarantees as more mature cryptographic libraries. ### Experimental and variable-time cryptographic backends Some cryptographic backends are not yet considered mature enough for general consumption. The use of such backends requires explicit opt-in using the feature flag `allow-experimental-crypto`. Some cryptographic backends can not guarantee that cryptographic operations require a constant amount of time. This may leak secret keys in some settings. The use of such backends requires explicit opt-in using the feature flag `allow-variable-time-crypto`. ### How to select crypto backends in crates In Rust, [features are unified], and consequently features should be additive, i.e. it should be safe to enable any combination of features. But, this does not hold for crypto backends, because exactly one cryptographic backend has to be selected in order to compile Sequoia. [features are unified]: https://doc.rust-lang.org/cargo/reference/features.html#feature-unification To accommodate this, we came up with the following rule: in any project using Sequoia, exactly one crate may select the cryptographic backend, and that crate is the leaf crate (i.e. the binary or cdylib crate). Any non-leaf, library crate must refrain from selecting a crypto backend, including the default one, by disabling the default features. To recap, follow these rules depending on what kind of crate you are developing: #### Leaf crate The leaf crate should pick a default backend (you may defer to Sequoia to pick the default one), but should allow your downstream users to switch backends: ```toml # Cargo.toml [dependencies] sequoia-openpgp = { version = "*", default-features = false } # If you need compression features, enable them here: # sequoia-openpgp = { version = "*", default-features = false, features = ["compression"] } [features] # Pick a crypto backend enabled by default (here we defer to Sequoia # to pick the default): default = ["sequoia-openpgp/default"] # .. but allow others to select a different backend, as well crypto-nettle = ["sequoia-openpgp/crypto-nettle"] crypto-openssl = ["sequoia-openpgp/crypto-openssl"] crypto-botan = ["sequoia-openpgp/crypto-botan"] crypto-botan2 = ["sequoia-openpgp/crypto-botan2"] crypto-rust = ["sequoia-openpgp/crypto-rust"] crypto-cng = ["sequoia-openpgp/crypto-cng"] # Experimental and variable-time cryptographic backend opt-ins allow-experimental-crypto = ["sequoia-openpgp/allow-experimental-crypto"] allow-variable-time-crypto = ["sequoia-openpgp/allow-variable-time-crypto"] ``` #### Intermediate crate Non-leaf crates must not select a cryptographic backend, and must disable the default features. Additionally, to make `cargo test` work without having to select a crypto backend, and to enable `docs.rs` to build your documentation, do selectively enable crypto backends for those cases: ```toml # Cargo.toml [dependencies] sequoia-openpgp = { version = "*", default-features = false } # If you need compression features, enable them here: # sequoia-openpgp = { version = "*", default-features = false, features = ["compression"] } # Enables a crypto backend for the tests: [target.'cfg(not(windows))'.dev-dependencies] sequoia-openpgp = { version = "1", default-features = false, features = ["crypto-nettle", "__implicit-crypto-backend-for-tests"] } # Enables a crypto backend for the tests: [target.'cfg(windows)'.dev-dependencies] sequoia-openpgp = { version = "1", default-features = false, features = ["crypto-cng", "__implicit-crypto-backend-for-tests"] } # Enables a crypto backend for the docs.rs generation: [package.metadata.docs.rs] features = ["sequoia-openpgp/default"] ``` ## Compression algorithms Use the `compression` flag to enable support for all compression algorithms, `compression-deflate` to enable *DEFLATE* and *zlib* compression support, and `compression-bzip2` to enable *bzip2* support. # Compiling to WASM With the right feature flags, Sequoia can be compiled to WASM. To do that, enable the RustCrypto backend, and make sure not to enable *bzip2* compression support: ```toml sequoia-openpgp = { version = "*", default-features = false, features = ["crypto-rust", "allow-experimental-crypto", "allow-variable-time-crypto"] } ``` Or, with `compression-deflate` support: ```toml sequoia-openpgp = { version = "*", default-features = false, features = ["crypto-rust", "allow-experimental-crypto", "allow-variable-time-crypto", "compression-deflate"] } ``` # Minimum Supported Rust Version (MSRV) `sequoia-openpgp` requires Rust 1.67. sequoia-openpgp-2.0.0/benches/README.md000064400000000000000000000021761046102023000156330ustar 00000000000000# Benchmarks We use [`criterion`](https://crates.io/crates/criterion) as a benchmark framework. It is * statistics driven, * configurable, * produces nice plots * and is compatible with stable Rust. ### Usage To compare your work to `main`: ``` git switch main cargo bench -- --save-baseline main git switch branchname cargo bench --baseline main ``` The html report can then be found at `sequoia/target/criterion/report/index.html`. You can also create a report for two stored baselines without running the benchmarks again: ``` cargo bench --load-baseline my_baseline --baseline main ``` #### Critmp Criterion can only include up to two baselines in one report. If you'd like to compare more than two stored baselines, or see a report on the command line, use [`critcmp`], e.g. ``` critcmp my_baseline my_other_baseline main ``` [`critcmp`]: https://crates.io/crates/critcmp #### Useful commands To run the benchmarks: ``` cargo bench ``` To run a specific benchmark: ``` cargo bench -- benchmark_name ``` To test the benchmarks: ``` cargo test --benches ``` To test a specific benchmark: ``` cargo test --benches -- benchmark_name ``` sequoia-openpgp-2.0.0/benches/common/decrypt.rs000064400000000000000000000166421046102023000176670ustar 00000000000000use sequoia_openpgp as openpgp; use openpgp::cert::Cert; use openpgp::crypto::{Password, SessionKey}; use openpgp::packet::prelude::*; use openpgp::packet::{PKESK, SKESK}; use openpgp::parse::stream::{ DecryptionHelper, DecryptorBuilder, MessageLayer, MessageStructure, VerificationHelper, VerifierBuilder, }; use openpgp::parse::Parse; use openpgp::policy::StandardPolicy; use openpgp::types::SymmetricAlgorithm; use openpgp::{KeyHandle, Result}; use std::io::Write; // Borrowed from the examples at // openpgp::parse::stream::DecryptionHelper // openpgp::parse::stream::Decryptor struct PasswordHelper { password: Password, } impl VerificationHelper for PasswordHelper { fn get_certs(&mut self, _ids: &[KeyHandle]) -> Result> { Ok(Vec::new()) } fn check(&mut self, _structure: MessageStructure) -> Result<()> { Ok(()) } } impl DecryptionHelper for PasswordHelper { fn decrypt( &mut self, _pkesks: &[PKESK], skesks: &[SKESK], _sym_algo: Option, decrypt: &mut dyn FnMut(Option, &SessionKey) -> bool, ) -> Result> { // Finally, try to decrypt using the SKESKs. for skesk in skesks { if skesk .decrypt(&self.password) .map(|(algo, sk)| decrypt(algo, &sk)) .unwrap_or(false) { return Ok(None); } } Err(anyhow::anyhow!("Wrong password!")) } } /// Decrypts the given message using the given password. pub fn decrypt_with_password( sink: &mut dyn Write, ciphertext: &[u8], password: &str, ) -> openpgp::Result<()> { let password = password.into(); // Make a helper that that feeds the password to the decryptor. let helper = PasswordHelper { password }; // Now, create a decryptor with a helper using the given Certs. let p = &StandardPolicy::new(); let mut decryptor = DecryptorBuilder::from_bytes(ciphertext)? .with_policy(p, None, helper)?; // Decrypt the data. std::io::copy(&mut decryptor, sink)?; Ok(()) } // Borrowed from the examples at // openpgp::parse::stream::DecryptionHelper // openpgp::parse::stream::Decryptor struct CertHelper<'a> { sender: Option<&'a Cert>, recipient: Option<&'a Cert>, } impl VerificationHelper for CertHelper<'_> { // get candidates for having created the signature fn get_certs(&mut self, _ids: &[KeyHandle]) -> Result> { let mut certs = Vec::new(); // maybe check that the cert matches (one of the) ids if let Some(sender) = self.sender { certs.push(sender.clone()); } Ok(certs) } // does the signature match the policy // e.g. am I the intended recipient fn check(&mut self, structure: MessageStructure) -> Result<()> { for (i, layer) in structure.into_iter().enumerate() { match layer { MessageLayer::Encryption { .. } if i == 0 => (), MessageLayer::Compression { .. } if i == 0 || i == 1 => (), MessageLayer::SignatureGroup { ref results } if i == 0 || i == 1 || i == 2 => { if !results.iter().any(|r| r.is_ok()) { for result in results { let error = result.as_ref().err().unwrap(); println!("{:?}", error); } return Err(anyhow::anyhow!("No valid signature")); } } _ => { return Err(anyhow::anyhow!( "Unexpected message structure {:?} at level {}", layer, i )) } } } Ok(()) } } impl DecryptionHelper for CertHelper<'_> { fn decrypt( &mut self, pkesks: &[PKESK], _skesks: &[SKESK], sym_algo: Option, decrypt: &mut dyn FnMut(Option, &SessionKey) -> bool, ) -> Result> { let p = &StandardPolicy::new(); let cand_secret_keys: Vec> = self.recipient .expect("Cannot decrypt without recipient's cert.") .keys() .with_policy(p, None) .for_transport_encryption() .for_storage_encryption() .secret() .map(|amalgamation| amalgamation.key().clone()) .collect(); // check that pkesk has right recipient // if yes, use decrypt function let successful_key = cand_secret_keys .iter() .cloned() .filter_map(|key| { pkesks .iter() .find(|pkesk| pkesk.recipient().map( |r| r.aliases(&key.key_handle())).unwrap_or(false)) .map(|pkesk| (pkesk, key)) }) .find(|(pkesk, key)| { let mut keypair = key.clone().into_keypair().unwrap(); pkesk .decrypt(&mut keypair, sym_algo) .map(|(algo, sk)| decrypt(algo, &sk)) .unwrap_or(false) }) .map(|(_, key)| key.fingerprint()); match successful_key { Some(_) => Ok(self.recipient.cloned()), None => Err(anyhow::anyhow!("Wrong cert!")), } } } /// Decrypts the given message using the given password. pub fn decrypt_with_cert( sink: &mut dyn Write, ciphertext: &[u8], cert: &Cert, ) -> openpgp::Result<()> { // Make a helper that that feeds the password to the decryptor. let helper = CertHelper { sender: None, recipient: Some(cert), }; // Now, create a decryptor with a helper using the given Certs. let p = &StandardPolicy::new(); let mut decryptor = DecryptorBuilder::from_bytes(ciphertext)? .with_policy(p, None, helper)?; // Decrypt the data. std::io::copy(&mut decryptor, sink)?; Ok(()) } /// Decrypts the given message using the given password. pub fn decrypt_and_verify( sink: &mut dyn Write, ciphertext: &[u8], sender: &Cert, recipient: &Cert, ) -> openpgp::Result<()> { // Make a helper that that feeds the password to the decryptor. let helper = CertHelper { sender: Some(sender), recipient: Some(recipient), }; // Now, create a decryptor with a helper using the given Certs. let p = &StandardPolicy::new(); let mut decryptor = DecryptorBuilder::from_bytes(ciphertext)? .with_policy(p, None, helper)?; // Decrypt the data. std::io::copy(&mut decryptor, sink)?; Ok(()) } /// Verifies the given message using the given sender's cert. pub fn verify( sink: &mut dyn Write, ciphertext: &[u8], sender: &Cert, ) -> openpgp::Result<()> { // Make a helper that that feeds the sender's cert to the verifier. let helper = CertHelper { sender: Some(sender), recipient: None, }; // Now, create a verifier with a helper using the given Certs. let p = &StandardPolicy::new(); let mut verifier = VerifierBuilder::from_bytes(ciphertext)? .with_policy(p, None, helper)?; // Verify the data. std::io::copy(&mut verifier, sink)?; Ok(()) } sequoia-openpgp-2.0.0/benches/common/encrypt.rs000064400000000000000000000077161046102023000177030ustar 00000000000000use std::sync::OnceLock; use sequoia_openpgp as openpgp; use openpgp::cert::Cert; use openpgp::parse::Parse; use openpgp::policy::StandardPolicy; use openpgp::serialize::stream::{ Armorer, Encryptor, LiteralWriter, Message, Signer, }; use std::io::Write; /// Encrypt with password, using a minimal writer stack. pub fn encrypt_with_password( bytes: &[u8], password: &str, ) -> openpgp::Result> { let mut sink = vec![]; let message = Encryptor::with_passwords(Message::new(&mut sink), Some(password)) .build()?; let mut w = LiteralWriter::new(message).build()?; w.write_all(bytes)?; w.finalize()?; Ok(sink) } /// Encrypt ignoring revocation or expiration. /// Uses a minimal writer stack. pub fn encrypt_to_cert( bytes: &[u8], cert: &Cert, ) -> openpgp::Result> { let mut sink = vec![]; let p = &StandardPolicy::new(); let recipients = cert .keys() .with_policy(p, None) .supported() .for_transport_encryption() .for_storage_encryption(); let message = Encryptor::for_recipients(Message::new(&mut sink), recipients) .build()?; let mut w = LiteralWriter::new(message).build()?; w.write_all(bytes)?; w.finalize()?; Ok(sink) } /// Sign ignoring revocation or expiration. pub fn sign(bytes: &[u8], sender: &Cert) -> openpgp::Result> { let mut sink = vec![]; let p = &StandardPolicy::new(); let signing_keypair = sender .keys() .with_policy(p, None) .secret() .for_signing() .next() .unwrap() .key() .clone() .into_keypair()?; let message = Message::new(&mut sink); let message = Signer::new(message, signing_keypair)?.build()?; let mut w = LiteralWriter::new(message).build()?; w.write_all(bytes)?; w.finalize()?; Ok(sink) } /// Encrypt and sign, ignoring revocation or expiration. /// Uses a realistic writer stack with padding and armor. pub fn encrypt_to_cert_and_sign( bytes: &[u8], sender: &Cert, recipient: &Cert, ) -> openpgp::Result> { let mut sink = vec![]; let p = &StandardPolicy::new(); let signing_keypair = sender .keys() .with_policy(p, None) .secret() .for_signing() .next() .unwrap() .key() .clone() .into_keypair()?; let recipients = recipient .keys() .with_policy(p, None) .supported() .for_transport_encryption() .for_storage_encryption(); let message = Message::new(&mut sink); let message = Armorer::new(message).build()?; let message = Encryptor::for_recipients(message, recipients).build()?; let message = Signer::new(message, signing_keypair)? //.add_intended_recipient(&recipient) .build()?; let mut w = LiteralWriter::new(message).build()?; w.write_all(bytes)?; w.finalize()?; Ok(sink) } fn zeros_1_mb() -> &'static [u8] { static ZEROS_1_MB: OnceLock> = OnceLock::new(); ZEROS_1_MB.get_or_init(|| vec![0; 1024 * 1024]) } fn zeros_10_mb() -> &'static [u8] { static ZEROS_10_MB: OnceLock> = OnceLock::new(); ZEROS_10_MB.get_or_init(|| vec![0; 10 * 1024 * 1024]) } /// Encrypt a very short, medium and very long message. pub fn messages() -> impl Iterator { [b"Hello world.", zeros_1_mb(), zeros_10_mb()] .into_iter() } /// Returns the sender key. pub fn sender() -> &'static Cert { static CERT: OnceLock = OnceLock::new(); CERT.get_or_init(|| { Cert::from_bytes( &include_bytes!("../../tests/data/keys/sender.pgp")[..]) .unwrap() }) } /// Returns the recipient key. pub fn recipient() -> &'static Cert { static CERT: OnceLock = OnceLock::new(); CERT.get_or_init(|| { Cert::from_bytes( &include_bytes!("../../tests/data/keys/recipient.pgp")[..]) .unwrap() }) } sequoia-openpgp-2.0.0/benches/common/mod.rs000064400000000000000000000000601046102023000167570ustar 00000000000000pub(super) mod decrypt; pub(super) mod encrypt; sequoia-openpgp-2.0.0/benches/decrypt_message.rs000064400000000000000000000034051046102023000200740ustar 00000000000000use std::sync::OnceLock; use criterion::{ criterion_group, criterion_main, BenchmarkId, Criterion, Throughput, }; use sequoia_openpgp as openpgp; use openpgp::cert::Cert; use openpgp::parse::Parse; use crate::common::{decrypt, encrypt}; static PASSWORD: &str = "password"; fn testy() -> &'static Cert { static CERT: OnceLock = OnceLock::new(); CERT.get_or_init(|| { Cert::from_bytes( &include_bytes!("../tests/data/keys/testy-private.pgp")[..]) .unwrap() }) } fn decrypt_cert(bytes: &[u8], cert: &Cert) { let mut sink = Vec::new(); decrypt::decrypt_with_cert(&mut sink, bytes, cert).unwrap(); } fn decrypt_password(bytes: &[u8]) { let mut sink = Vec::new(); decrypt::decrypt_with_password(&mut sink, bytes, PASSWORD).unwrap(); } fn bench_decrypt(c: &mut Criterion) { let mut group = c.benchmark_group("decrypt message"); // Encrypt and decrypt with password encrypt::messages().for_each(|m| { let encrypted = encrypt::encrypt_with_password(m, PASSWORD).unwrap(); group.throughput(Throughput::Bytes(encrypted.len() as u64)); group.bench_with_input( BenchmarkId::new("password", m.len()), &encrypted, |b, e| b.iter(|| decrypt_password(e)), ); }); // Encrypt and decrypt with a cert encrypt::messages().for_each(|m| { let encrypted = encrypt::encrypt_to_cert(m, testy()).unwrap(); group.throughput(Throughput::Bytes(encrypted.len() as u64)); group.bench_with_input( BenchmarkId::new("cert", m.len()), &encrypted, |b, e| b.iter(|| decrypt_cert(e, testy())), ); }); group.finish(); } criterion_group!(benches, bench_decrypt); criterion_main!(benches); sequoia-openpgp-2.0.0/benches/decrypt_verify_message.rs000064400000000000000000000023301046102023000214540ustar 00000000000000use criterion::{ criterion_group, criterion_main, BenchmarkId, Criterion, Throughput, }; use sequoia_openpgp as openpgp; use openpgp::cert::Cert; use crate::common::{decrypt, encrypt}; fn decrypt_and_verify(bytes: &[u8], sender: &Cert, recipient: &Cert) { let mut sink = Vec::new(); decrypt::decrypt_and_verify(&mut sink, bytes, sender, recipient).unwrap(); } fn bench_decrypt_verify(c: &mut Criterion) { let mut group = c.benchmark_group("decrypt and verify message"); encrypt::messages().for_each(|m| { let encrypted = encrypt::encrypt_to_cert_and_sign(m, encrypt::sender(), encrypt::recipient()).unwrap(); group.throughput(Throughput::Bytes(encrypted.len() as u64)); group.bench_with_input( BenchmarkId::new("decrypt and verify", m.len()), &encrypted, |b, e| b.iter(|| decrypt_and_verify(e, encrypt::sender(), encrypt::recipient())), ); }); group.finish(); } criterion_group!(benches, bench_decrypt_verify); criterion_main!(benches); sequoia-openpgp-2.0.0/benches/encrypt_message.rs000064400000000000000000000022211046102023000201010ustar 00000000000000use criterion::{criterion_group, BenchmarkId, Criterion, Throughput}; use sequoia_openpgp as openpgp; use openpgp::cert::Cert; use openpgp::parse::Parse; use crate::common::encrypt; pub fn encrypt_to_testy(bytes: &[u8]) { let testy = Cert::from_bytes(&include_bytes!("../tests/data/keys/testy.pgp")[..]) .unwrap(); encrypt::encrypt_to_cert(bytes, &testy).unwrap(); } pub fn encrypt_with_password(bytes: &[u8]) { let password = "ściśle tajne"; encrypt::encrypt_with_password(bytes, password).unwrap(); } fn bench_encrypt(c: &mut Criterion) { let mut group = c.benchmark_group("encrypt message"); for message in encrypt::messages() { group.throughput(Throughput::Bytes(message.len() as u64)); group.bench_with_input( BenchmarkId::new("password", message.len()), &message, |b, m| b.iter(|| encrypt_with_password(m)), ); group.bench_with_input( BenchmarkId::new("cert", message.len()), &message, |b, m| b.iter(|| encrypt_to_testy(m)), ); } group.finish(); } criterion_group!(benches, bench_encrypt); sequoia-openpgp-2.0.0/benches/encrypt_sign_message.rs000064400000000000000000000020571046102023000211300ustar 00000000000000use criterion::{criterion_group, BenchmarkId, Criterion, Throughput}; use sequoia_openpgp as openpgp; use openpgp::cert::Cert; use openpgp::parse::Parse; use crate::common::encrypt; pub fn encrypt_to_donald_sign_by_ivanka(bytes: &[u8]) { let sender = Cert::from_bytes( &include_bytes!("../tests/data/keys/ivanka-private.gpg")[..], ) .unwrap(); let recipient = Cert::from_bytes( &include_bytes!("../tests/data/keys/the-donald-private.gpg")[..], ) .unwrap(); encrypt::encrypt_to_cert_and_sign(bytes, &sender, &recipient).unwrap(); } fn bench_encrypt_sign(c: &mut Criterion) { let mut group = c.benchmark_group("encrypt and sign message"); for message in encrypt::messages() { group.throughput(Throughput::Bytes(message.len() as u64)); group.bench_with_input( BenchmarkId::new("encrypt and sign", message.len()), &message, |b, m| b.iter(|| encrypt_to_donald_sign_by_ivanka(m)), ); } group.finish(); } criterion_group!(benches, bench_encrypt_sign); sequoia-openpgp-2.0.0/benches/generate_cert.rs000064400000000000000000000021351046102023000175240ustar 00000000000000use criterion::{criterion_group, Criterion}; use sequoia_openpgp as openpgp; use openpgp::cert::{CertBuilder, CipherSuite}; fn generate_cert(cipher: CipherSuite) { // Parse the cert, ignore any errors let _ = CertBuilder::general_purpose( Some("Alice Lovelace ")) .set_cipher_suite(cipher) .generate() .unwrap(); } fn bench_generate_certs(c: &mut Criterion) { let mut group = c.benchmark_group("generate cert"); let cipher = CipherSuite::Cv25519; group.bench_function(format!("{:?}", cipher), |b| { b.iter(|| generate_cert(cipher)) }); let cipher = CipherSuite::P256; group.bench_function(format!("{:?}", cipher), |b| { b.iter(|| generate_cert(cipher)) }); let cipher = CipherSuite::P384; group.bench_function(format!("{:?}", cipher), |b| { b.iter(|| generate_cert(cipher)) }); let cipher = CipherSuite::P521; group.bench_function(format!("{:?}", cipher), |b| { b.iter(|| generate_cert(cipher)) }); group.finish(); } criterion_group!(benches, bench_generate_certs); sequoia-openpgp-2.0.0/benches/merge_cert.rs000064400000000000000000000011061046102023000170260ustar 00000000000000use criterion::{ criterion_group, Criterion, }; use sequoia_openpgp as openpgp; use openpgp::cert::Cert; use openpgp::parse::Parse; /// Benchmark merging a typical cert with itself. fn bench_merge_certs(c: &mut Criterion) { let mut group = c.benchmark_group("merge cert with itself"); let neal = Cert::from_bytes(include_bytes!("../tests/data/keys/neal.pgp")) .unwrap(); group.bench_function("neal.pgp", |b| b.iter(|| { neal.clone().merge_public(neal.clone()).unwrap(); })); group.finish(); } criterion_group!(benches, bench_merge_certs); sequoia-openpgp-2.0.0/benches/mod.rs000064400000000000000000000000141046102023000154660ustar 00000000000000mod common; sequoia-openpgp-2.0.0/benches/parse_cert.rs000064400000000000000000000062271046102023000170520ustar 00000000000000use criterion::{ criterion_group, BenchmarkGroup, BenchmarkId, Criterion, Throughput, }; use sequoia_openpgp as openpgp; use openpgp::cert::{Cert, CertBuilder}; use openpgp::packet::prelude::*; use openpgp::packet::{Signature, UserID}; use openpgp::parse::Parse; use openpgp::serialize::SerializeInto; use openpgp::types::{Curve, KeyFlags, SignatureType}; use openpgp::Result; use std::convert::TryInto; fn generate_certifications<'a>( userid: &'a UserID, cert: &'a Cert, count: usize, ) -> Result + 'a> { let k: Key = Key4::generate_ecc(true, Curve::Ed25519)?.into(); let mut keypair = k.into_keypair()?; let iter = (0..count).map(move |_| { userid .certify( &mut keypair, cert, SignatureType::PositiveCertification, None, None, ) .unwrap() }); Ok(iter) } fn generate_flooded_cert( key_count: usize, sigs_per_key: usize, ) -> Result> { // Generate a Cert for to be flooded let (mut floodme, _) = CertBuilder::new() .set_primary_key_flags(KeyFlags::empty().set_certification()) .add_userid("flood.me@example.org") .generate()?; let floodme_cloned = floodme.clone(); let userid = floodme_cloned.userids().next().unwrap(); let certifications = (0..key_count).flat_map(|_| { generate_certifications(userid.userid(), &floodme_cloned, sigs_per_key) .unwrap() }); floodme = floodme.insert_packets(certifications)?.0; floodme.export_to_vec() } /// Parse the cert, unwrap to notice errors fn read_cert(bytes: &[u8]) { Cert::from_bytes(bytes).unwrap(); } /// Generate the cert and benchmark parsing. /// The generated cert is signed by multiple other keys, 1 signature per key. fn parse_cert_generated( group: &mut BenchmarkGroup<'_, criterion::measurement::WallTime>, name: &str, signature_count: usize, ) { let bytes = generate_flooded_cert(signature_count, 1).unwrap(); group.throughput(Throughput::Bytes(bytes.len().try_into().unwrap())); group.bench_with_input( BenchmarkId::new(name, signature_count), &bytes, |b, bytes| b.iter(|| read_cert(bytes)), ); } /// Benchmark parsing a generated cert with a given number of signatures fn bench_parse_certs_generated(c: &mut Criterion) { let mut group = c.benchmark_group("parse flooded cert"); parse_cert_generated(&mut group, "flooded", 100); parse_cert_generated(&mut group, "flooded", 316); parse_cert_generated(&mut group, "flooded", 1000); parse_cert_generated(&mut group, "flooded", 3162); group.finish(); } /// Benchmark parsing a typical cert fn bench_parse_certs(c: &mut Criterion) { let mut group = c.benchmark_group("parse typical cert"); let bytes = include_bytes!("../tests/data/keys/neal.pgp"); group.throughput(Throughput::Bytes(bytes.len().try_into().unwrap())); group.bench_function("neal.pgp", |b| b.iter(|| read_cert(bytes))); group.finish(); } criterion_group!(benches, bench_parse_certs, bench_parse_certs_generated); sequoia-openpgp-2.0.0/benches/run_benchmarks.rs000064400000000000000000000014511046102023000177160ustar 00000000000000use criterion::criterion_main; mod common; mod sign_message; use sign_message::benches as sign; mod verify_message; use verify_message::benches as verify; mod encrypt_message; use encrypt_message::benches as encrypt; mod decrypt_message; use decrypt_message::benches as decrypt; mod encrypt_sign_message; use encrypt_sign_message::benches as encrypt_sign; mod decrypt_verify_message; use decrypt_verify_message::benches as decrypt_verify; mod generate_cert; use generate_cert::benches as generate_cert; mod parse_cert; use parse_cert::benches as parse_cert; mod merge_cert; use merge_cert::benches as merge_cert; // Add all benchmark functions here criterion_main!( sign, verify, encrypt_sign, decrypt_verify, encrypt, decrypt, generate_cert, parse_cert, merge_cert, ); sequoia-openpgp-2.0.0/benches/sign_message.rs000064400000000000000000000015561046102023000173670ustar 00000000000000use criterion::{ criterion_group, criterion_main, BenchmarkId, Criterion, Throughput, }; use sequoia_openpgp as openpgp; use openpgp::cert::Cert; use openpgp::parse::Parse; use crate::common::encrypt; pub fn sign_by_testy(bytes: &[u8]) { let testy = Cert::from_bytes( &include_bytes!("../tests/data/keys/testy-new-private.pgp")[..], ) .unwrap(); encrypt::sign(bytes, &testy).unwrap(); } fn bench_sign(c: &mut Criterion) { let mut group = c.benchmark_group("sign message"); for message in encrypt::messages() { group.throughput(Throughput::Bytes(message.len() as u64)); group.bench_with_input( BenchmarkId::new("cert", message.len()), &message, |b, m| b.iter(|| sign_by_testy(m)), ); } group.finish(); } criterion_group!(benches, bench_sign); criterion_main!(benches); sequoia-openpgp-2.0.0/benches/verify_message.rs000064400000000000000000000015261046102023000177300ustar 00000000000000use criterion::{criterion_group, BenchmarkId, Criterion, Throughput}; use sequoia_openpgp as openpgp; use openpgp::cert::Cert; use crate::common::{decrypt, encrypt}; fn verify(bytes: &[u8], sender: &Cert) { let mut sink = Vec::new(); decrypt::verify(&mut sink, bytes, sender).unwrap(); } fn bench_verify(c: &mut Criterion) { let mut group = c.benchmark_group("verify message"); encrypt::messages() .for_each(|m| { let signed = encrypt::sign(m, encrypt::sender()).unwrap(); group.throughput(Throughput::Bytes(signed.len() as u64)); group.bench_with_input( BenchmarkId::new("verify", m.len()), &signed, |b, s| b.iter(|| verify(s, encrypt::sender())), ); }); group.finish(); } criterion_group!(benches, bench_verify); sequoia-openpgp-2.0.0/build.rs000064400000000000000000000143061046102023000144100ustar 00000000000000use std::env; use std::fs; use std::io::{self, Write}; use std::path::PathBuf; use std::process::exit; fn main() { crypto_backends_sanity_check(); include_openssl_conf(); lalrpop::Configuration::new().use_cargo_dir_conventions().process().unwrap(); include_test_data().unwrap(); } /// Optionally include configuration passed from openssl-sys build /// script. This configuration is then exposed as a set of `osslconf` /// parameters and is used by OpenSSL backend to enable or disable /// algorithms available by the current environment. fn include_openssl_conf() { if let Ok(vars) = env::var("DEP_OPENSSL_CONF") { for var in vars.split(',') { println!("cargo:rustc-cfg=osslconf=\"{}\"", var); } } } /// Builds the index of the test data for use with the `::tests` /// module. fn include_test_data() -> io::Result<()> { let cwd = env::current_dir()?; let mut sink = fs::File::create( PathBuf::from(env::var_os("OUT_DIR").unwrap()) .join("tests.index.rs.inc")).unwrap(); writeln!(&mut sink, "{{")?; let mut dirs = vec![PathBuf::from("tests/data")]; while let Some(dir) = dirs.pop() { println!("rerun-if-changed={}", dir.to_str().unwrap()); for entry in fs::read_dir(dir).unwrap() { let entry = entry?; let path = entry.path(); if path.is_file() { writeln!( &mut sink, " add!({:?}, {:?});", path.components().skip(2) .map(|c| c.as_os_str().to_str().expect("valid UTF-8")) .collect::>().join("/"), cwd.join(path))?; } else if path.is_dir() { dirs.push(path.clone()); } } } writeln!(&mut sink, "}}")?; Ok(()) } fn crypto_backends_sanity_check() { #[allow(dead_code)] struct Backend { name: &'static str, production_ready: bool, constant_time: bool, } let backends = vec![ (cfg!(all(feature = "crypto-nettle", not(all(feature = "__implicit-crypto-backend-for-tests", any(feature = "crypto-openssl", feature = "crypto-botan", feature = "crypto-botan2", feature = "crypto-fuzzing", feature = "crypto-rust"))))), Backend { name: "Nettle", production_ready: true, constant_time: true, }), (cfg!(all(feature = "crypto-cng", not(all(feature = "__implicit-crypto-backend-for-tests", any(feature = "crypto-nettle", feature = "crypto-openssl", feature = "crypto-botan", feature = "crypto-botan2", feature = "crypto-fuzzing", feature = "crypto-rust"))))), Backend { name: "Windows CNG", production_ready: true, constant_time: true, }), (cfg!(feature = "crypto-rust"), Backend { name: "RustCrypto", production_ready: false, constant_time: false, }), (cfg!(feature = "crypto-openssl"), Backend { name: "OpenSSL", production_ready: true, constant_time: true, }), (cfg!(feature = "crypto-botan"), Backend { name: "Botan", production_ready: true, constant_time: true, }), (cfg!(feature = "crypto-botan2"), Backend { name: "Botan", production_ready: true, constant_time: true, }), (cfg!(feature = "crypto-fuzzing"), Backend { name: "Fuzzing", production_ready: false, constant_time: false, }), ].into_iter().filter_map(|(selected, backend)| { if selected { Some(backend) } else { None } }).collect::>(); match backends.len() { 0 => { eprintln!("No cryptographic backend selected. Sequoia requires a cryptographic backend. This backend is selected at compile time using feature flags. See https://crates.io/crates/sequoia-openpgp#crypto-backends"); exit(1); }, 1 => { eprintln!("Selected cryptographic backend: {}", backends[0].name); }, _ => { eprintln!("Multiple cryptographic backends selected. Sequoia requires exactly one cryptographic backend. This backend is selected at compile time using feature flags. Unfortunately, you have selected multiple backends: {} See https://crates.io/crates/sequoia-openpgp#crypto-backends", backends.iter().map(|b| b.name).collect::>().join(", ")); exit(1); }, } // We now have exactly one backend. assert_eq!(backends.len(), 1); let backend = &backends[0]; // Check its properties. if ! (backend.production_ready || cfg!(feature = "allow-experimental-crypto")) { eprintln!(" The cryptographic backend {} is not considered production ready. If you know what you are doing, you can opt-in to using experimental cryptographic backends using the feature flag allow-experimental-crypto See https://crates.io/crates/sequoia-openpgp#crypto-backends", backend.name); exit(1); } if ! (backend.constant_time || cfg!(feature = "allow-variable-time-crypto")) { eprintln!(" The cryptographic backend {} does not provide constant-time operations. This has the potential of leaking cryptographic secrets, enable attackers to forge signatures, or cause other mayhem. If you are not using Sequoia in an interactive setting, using variable-time cryptographic operations is probably safe. If you know what you are doing, you can opt-in to using variable-time cryptographic operations using the feature flag allow-variable-time-crypto See https://crates.io/crates/sequoia-openpgp#crypto-backends", backend.name); exit(1); } } sequoia-openpgp-2.0.0/examples/decrypt-with.rs000064400000000000000000000113021046102023000175430ustar 00000000000000/// Decrypts asymmetrically-encrypted OpenPGP messages using the /// openpgp crate, Sequoia's low-level API. use std::sync::Arc; use std::collections::HashMap; use std::env; use std::io; use anyhow::Context; use sequoia_openpgp as openpgp; use openpgp::*; use openpgp::crypto::{KeyPair, SessionKey}; use openpgp::types::SymmetricAlgorithm; use openpgp::parse::{ Parse, stream::{ DecryptionHelper, DecryptorBuilder, VerificationHelper, GoodChecksum, MessageStructure, MessageLayer, }, }; use openpgp::policy::Policy; use openpgp::policy::StandardPolicy as P; pub fn main() -> openpgp::Result<()> { let p = &P::new(); let args: Vec = env::args().collect(); if args.len() < 2 { return Err(anyhow::anyhow!("A simple decryption filter.\n\n\ Usage: {} [...] output\n", args[0])); } // Read the transferable secret keys from the given files. let certs = args[1..].iter().map(|f| { openpgp::Cert::from_file(f) }).collect::>>() .context("Failed to read key")?; // Now, create a decryptor with a helper using the given Certs. let mut decryptor = DecryptorBuilder::from_reader(io::stdin())? .with_policy(p, None, Helper::new(p, certs))?; // Finally, stream the decrypted data to stdout. io::copy(&mut decryptor, &mut io::stdout()) .context("Decryption failed")?; Ok(()) } /// This helper provides secrets for the decryption, fetches public /// keys for the signature verification and implements the /// verification policy. struct Helper { keys: HashMap, KeyPair)>, } impl Helper { /// Creates a Helper for the given Certs with appropriate secrets. fn new(p: &dyn Policy, certs: Vec) -> Self { // Map (sub)KeyIDs to certs and secrets. let mut keys = HashMap::new(); for cert in certs { let cert = Arc::new(cert); for ka in cert.keys().unencrypted_secret().with_policy(p, None) .supported() .for_storage_encryption().for_transport_encryption() { keys.insert(ka.key().keyid(), (cert.clone(), ka.key().clone().into_keypair().unwrap())); } } Helper { keys, } } } impl DecryptionHelper for Helper { fn decrypt(&mut self, pkesks: &[openpgp::packet::PKESK], _skesks: &[openpgp::packet::SKESK], sym_algo: Option, decrypt: &mut dyn FnMut(Option, &SessionKey) -> bool) -> openpgp::Result> { // Try each PKESK until we succeed. let mut recipient = None; for pkesk in pkesks { if let Some((cert, pair)) = self.keys.get_mut(&KeyID::from(pkesk.recipient())) { if pkesk.decrypt(pair, sym_algo) .map(|(algo, session_key)| decrypt(algo, &session_key)) .unwrap_or(false) { recipient = Some(cert.as_ref().clone()); break; } } } Ok(recipient) } } impl VerificationHelper for Helper { fn get_certs(&mut self, _ids: &[openpgp::KeyHandle]) -> openpgp::Result> { Ok(Vec::new()) // Feed the Certs to the verifier here. } fn check(&mut self, structure: MessageStructure) -> openpgp::Result<()> { for layer in structure.iter() { match layer { MessageLayer::Compression { algo } => eprintln!("Compressed using {}", algo), MessageLayer::Encryption { sym_algo, aead_algo } => if let Some(aead_algo) = aead_algo { eprintln!("Encrypted and protected using {}/{}", sym_algo, aead_algo); } else { eprintln!("Encrypted using {}", sym_algo); }, MessageLayer::SignatureGroup { ref results } => for result in results { match result { Ok(GoodChecksum { ka, .. }) => { eprintln!("Good signature from {}", ka.cert()); }, Err(e) => eprintln!("Error: {:?}", e), } } } } Ok(()) // Implement your verification policy here. } } sequoia-openpgp-2.0.0/examples/encrypt-for.rs000064400000000000000000000053071046102023000174000ustar 00000000000000/// Asymmetrically encrypts OpenPGP messages using the openpgp crate, /// Sequoia's low-level API. use std::env; use std::io; use anyhow::Context; use sequoia_openpgp as openpgp; use crate::openpgp::serialize::stream::Armorer; use crate::openpgp::types::KeyFlags; use crate::openpgp::parse::Parse; use crate::openpgp::serialize::stream::{ Message, LiteralWriter, Encryptor, }; use crate::openpgp::policy::StandardPolicy as P; fn main() -> openpgp::Result<()> { let p = &P::new(); let args: Vec = env::args().collect(); if args.len() < 3 { return Err(anyhow::anyhow!("A simple encryption filter.\n\n\ Usage: {} [at-rest|for-transport] [...] \ output\n", args[0])); } let mode = match args[1].as_ref() { "at-rest" => KeyFlags::empty().set_storage_encryption(), "for-transport" => KeyFlags::empty().set_transport_encryption(), x => return Err(anyhow::anyhow!("invalid mode: {:?}, \ must be either 'at-rest' or 'for-transport'", x)), }; // Read the certificates from the given files. let certs: Vec = args[2..].iter().map(|f| { openpgp::Cert::from_file(f) }).collect::>>() .context("Failed to read key")?; // Build a list of recipient subkeys. let mut recipients = Vec::new(); for cert in certs.iter() { // Make sure we add at least one subkey from every // certificate. let mut found_one = false; for key in cert.keys().with_policy(p, None) .supported().alive().revoked(false).key_flags(&mode) { recipients.push(key); found_one = true; } if ! found_one { return Err(anyhow::anyhow!("No suitable encryption subkey for {}", cert)); } } // Compose a writer stack corresponding to the output format and // packet structure we want. let mut sink = io::stdout(); // Stream an OpenPGP message. let message = Message::new(&mut sink); let message = Armorer::new(message).build()?; // We want to encrypt a literal data packet. let message = Encryptor::for_recipients(message, recipients) .build().context("Failed to create encryptor")?; let mut message = LiteralWriter::new(message).build() .context("Failed to create literal writer")?; // Copy stdin to our writer stack to encrypt the data. io::copy(&mut io::stdin(), &mut message) .context("Failed to encrypt")?; // Finally, finalize the OpenPGP message by tearing down the // writer stack. message.finalize()?; Ok(()) } sequoia-openpgp-2.0.0/examples/generate-encrypt-decrypt.rs000064400000000000000000000076351046102023000220620ustar 00000000000000/// Generates a key, then encrypts and decrypts a message. use std::io::{self, Write}; use sequoia_openpgp as openpgp; use crate::openpgp::cert::prelude::*; use crate::openpgp::crypto::SessionKey; use crate::openpgp::types::SymmetricAlgorithm; use crate::openpgp::serialize::stream::*; use crate::openpgp::parse::{Parse, stream::*}; use crate::openpgp::policy::Policy; use crate::openpgp::policy::StandardPolicy as P; const MESSAGE: &str = "дружба"; fn main() -> openpgp::Result<()> { let p = &P::new(); // Generate a key. let key = generate()?; // Encrypt the message. let mut ciphertext = Vec::new(); encrypt(p, &mut ciphertext, MESSAGE, &key)?; // Decrypt the message. let mut plaintext = Vec::new(); decrypt(p, &mut plaintext, &ciphertext, &key)?; assert_eq!(MESSAGE.as_bytes(), &plaintext[..]); Ok(()) } /// Generates an encryption-capable key. fn generate() -> openpgp::Result { let (cert, _revocation) = CertBuilder::new() .add_userid("someone@example.org") .add_transport_encryption_subkey() .generate()?; // Save the revocation certificate somewhere. Ok(cert) } /// Encrypts the given message. fn encrypt(p: &dyn Policy, sink: &mut (dyn Write + Send + Sync), plaintext: &str, recipient: &openpgp::Cert) -> openpgp::Result<()> { let recipients = recipient.keys().with_policy(p, None).supported().alive().revoked(false) .for_transport_encryption(); // Start streaming an OpenPGP message. let message = Message::new(sink); // We want to encrypt a literal data packet. let message = Encryptor::for_recipients(message, recipients) .build()?; // Emit a literal data packet. let mut message = LiteralWriter::new(message).build()?; // Encrypt the data. message.write_all(plaintext.as_bytes())?; // Finalize the OpenPGP message to make sure that all data is // written. message.finalize()?; Ok(()) } /// Decrypts the given message. fn decrypt(p: &dyn Policy, sink: &mut dyn Write, ciphertext: &[u8], recipient: &openpgp::Cert) -> openpgp::Result<()> { // Make a helper that that feeds the recipient's secret key to the // decryptor. let helper = Helper { secret: recipient, policy: p, }; // Now, create a decryptor with a helper using the given Certs. let mut decryptor = DecryptorBuilder::from_bytes(ciphertext)? .with_policy(p, None, helper)?; // Decrypt the data. io::copy(&mut decryptor, sink)?; Ok(()) } struct Helper<'a> { secret: &'a openpgp::Cert, policy: &'a dyn Policy, } impl<'a> VerificationHelper for Helper<'a> { fn get_certs(&mut self, _ids: &[openpgp::KeyHandle]) -> openpgp::Result> { // Return public keys for signature verification here. Ok(Vec::new()) } fn check(&mut self, _structure: MessageStructure) -> openpgp::Result<()> { // Implement your signature verification policy here. Ok(()) } } impl<'a> DecryptionHelper for Helper<'a> { fn decrypt(&mut self, pkesks: &[openpgp::packet::PKESK], _skesks: &[openpgp::packet::SKESK], sym_algo: Option, decrypt: &mut dyn FnMut(Option, &SessionKey) -> bool) -> openpgp::Result> { let key = self.secret.keys().unencrypted_secret() .with_policy(self.policy, None) .for_transport_encryption().next().unwrap().key().clone(); // The secret key is not encrypted. let mut pair = key.into_keypair()?; pkesks[0].decrypt(&mut pair, sym_algo) .map(|(algo, session_key)| decrypt(algo, &session_key)); // XXX: In production code, return the Fingerprint of the // recipient's Cert here Ok(None) } } sequoia-openpgp-2.0.0/examples/generate-group-key.rs000064400000000000000000000072741046102023000206470ustar 00000000000000//! Illustrates how to create keys and certificates for group //! conversations. //! //! This example, when run, generates four artifacts: //! //! ```text //! % cargo run -p sequoia-openpgp --example generate-group-key cabal@example.org //! [...] //! Writing certificate to cabal@example.org.cert.pgp //! Writing full key to cabal@example.org.key.pgp //! Writing key with detached primary to cabal@example.org.only_subkey.pgp //! Writing revocation certificate to cabal@example.org.revocation.pgp //! ``` //! //! - If you want to receive unsolicited encrypted messages on the //! address (e.g. in case of a security contact or a public mailing //! list), you should publish this in a suitable way, e.g. using //! WKD. Consider authenticating this certificate using OpenPGP-CA. //! //! - Distribute the certificate freely if you want to receive //! unsolicited encrypted messages. //! //! - Distribute the key with the detached primary among the group, //! for example by including it in an email encrypted to all //! members. //! //! - Make a backup of full key and revocation certificate. //! //! - Consider distributing the revocation certificate among a select, //! trusted group that can help when disaster strikes. The security //! implication of handing out the revocation certificate is a //! denial-of-service vector. use std::fs::File; use std::time::Duration; use sequoia_openpgp as openpgp; use openpgp::armor; use openpgp::cert::prelude::*; use openpgp::types::KeyFlags; use openpgp::serialize::Serialize; fn main() -> openpgp::Result<()> { let args: Vec = std::env::args().collect(); let name = if let Some(n) = args.get(1).cloned() { n } else { return Err(anyhow::anyhow!( "Missing list address parameter.\n\n\ Usage: {} ", args.get(0).cloned().unwrap_or("generate-group-key".into()))); }; // Generate the key. let (cert, revocation) = CertBuilder::new() .set_validity_period(Duration::new(5 * 365 * 24 * 60 * 60, 0)) .add_userid(format!("<{}>", name)) .add_subkey(KeyFlags::empty() .set_transport_encryption() .set_group_key(), None, None) .generate()?; // First, emit the certificate. let n = format!("{}.cert.pgp", name); eprintln!("Writing certificate to {}", n); cert.armored().serialize(&mut File::create(n)?)?; // Second, emit the key. This includes all secret key material. // Back this up. let n = format!("{}.key.pgp", name); eprintln!("Writing full key to {}", n); cert.as_tsk().armored().serialize(&mut File::create(n)?)?; // Third, emit they key, but only include the encryption subkey's // secret key material. let n = format!("{}.only_subkey.pgp", name); eprintln!("Writing key with detached primary to {}", n); cert.as_tsk() .set_filter(|k| k.fingerprint() != cert.fingerprint()) .emit_secret_key_stubs(true) // Enable GnuPG-style. .armored() .serialize(&mut File::create(n)?)?; // Finally, emit a revocation certificate. Back this up. let n = format!("{}.revocation.pgp", name); eprintln!("Writing revocation certificate to {}", n); // Be fancy and include comments in the revocation cert. let mut comments = cert.armor_headers(); comments.insert(0, "Revocation certificate for the following key:".into()); comments.insert(1, "".into()); let mut w = armor::Writer::with_headers( File::create(n)?, armor::Kind::PublicKey, comments.iter().map(|c| ("Comment", c)))?; openpgp::Packet::from(revocation).serialize(&mut w)?; w.finalize()?; Ok(()) } sequoia-openpgp-2.0.0/examples/generate-sign-verify.rs000064400000000000000000000102271046102023000211570ustar 00000000000000/// Generates a key, then signs and verifies a message. use std::io::{self, Write}; use sequoia_openpgp as openpgp; use crate::openpgp::cert::prelude::*; use crate::openpgp::serialize::stream::*; use crate::openpgp::parse::{Parse, stream::*}; use crate::openpgp::policy::Policy; use crate::openpgp::policy::StandardPolicy as P; const MESSAGE: &str = "дружба"; fn main() -> openpgp::Result<()> { let p = &P::new(); // Generate a key. let key = generate()?; // Sign the message. let mut signed_message = Vec::new(); sign(p, &mut signed_message, MESSAGE, &key)?; // Verify the message. let mut plaintext = Vec::new(); verify(p, &mut plaintext, &signed_message, &key)?; assert_eq!(MESSAGE.as_bytes(), &plaintext[..]); Ok(()) } /// Generates an signing-capable key. fn generate() -> openpgp::Result { let (cert, _revocation) = CertBuilder::new() .add_userid("someone@example.org") .add_signing_subkey() .generate()?; // Save the revocation certificate somewhere. Ok(cert) } /// Signs the given message. fn sign(p: &dyn Policy, sink: &mut (dyn Write + Send + Sync), plaintext: &str, tsk: &openpgp::Cert) -> openpgp::Result<()> { // Get the keypair to do the signing from the Cert. let keypair = tsk .keys().unencrypted_secret() .with_policy(p, None).supported().alive().revoked(false).for_signing() .next().unwrap().key().clone().into_keypair()?; // Start streaming an OpenPGP message. let message = Message::new(sink); // We want to sign a literal data packet. let signer = Signer::new(message, keypair)?.build()?; // Emit a literal data packet. let mut literal_writer = LiteralWriter::new(signer).build()?; // Sign the data. literal_writer.write_all(plaintext.as_bytes())?; // Finalize the OpenPGP message to make sure that all data is // written. literal_writer.finalize()?; Ok(()) } /// Verifies the given message. fn verify(p: &dyn Policy, sink: &mut dyn Write, signed_message: &[u8], sender: &openpgp::Cert) -> openpgp::Result<()> { // Make a helper that that feeds the sender's public key to the // verifier. let helper = Helper { cert: sender, }; // Now, create a verifier with a helper using the given Certs. let mut verifier = VerifierBuilder::from_bytes(signed_message)? .with_policy(p, None, helper)?; // Verify the data. io::copy(&mut verifier, sink)?; Ok(()) } struct Helper<'a> { cert: &'a openpgp::Cert, } impl<'a> VerificationHelper for Helper<'a> { fn get_certs(&mut self, _ids: &[openpgp::KeyHandle]) -> openpgp::Result> { // Return public keys for signature verification here. Ok(vec![self.cert.clone()]) } fn check(&mut self, structure: MessageStructure) -> openpgp::Result<()> { // In this function, we implement our signature verification // policy. let mut good = false; for (i, layer) in structure.into_iter().enumerate() { match (i, layer) { // First, we are interested in signatures over the // data, i.e. level 0 signatures. (0, MessageLayer::SignatureGroup { results }) => { // Finally, given a VerificationResult, which only says // whether the signature checks out mathematically, we apply // our policy. match results.into_iter().next() { Some(Ok(_)) => good = true, Some(Err(e)) => return Err(openpgp::Error::from(e).into()), None => return Err(anyhow::anyhow!("No signature")), } }, _ => return Err(anyhow::anyhow!( "Unexpected message structure")), } } if good { Ok(()) // Good signature. } else { Err(anyhow::anyhow!("Signature verification failed")) } } } sequoia-openpgp-2.0.0/examples/notarize.rs000064400000000000000000000102371046102023000167610ustar 00000000000000/// Notarizes OpenPGP messages using the openpgp crate, Sequoia's /// low-level API. use std::env; use std::io; use anyhow::Context; use sequoia_openpgp as openpgp; use crate::openpgp::{ Packet, parse::{Parse, PacketParserResult}, serialize::{Marshal, stream::Armorer}, }; use crate::openpgp::serialize::stream::{Message, LiteralWriter, Signer}; use crate::openpgp::policy::StandardPolicy as P; fn main() -> openpgp::Result<()> { let p = &P::new(); let args: Vec = env::args().collect(); if args.len() < 2 { return Err(anyhow::anyhow!("A simple notarizing filter.\n\n\ Usage: {} [...] \ output\n", args[0])); } // Read the transferable secret keys from the given files. let mut keys = Vec::new(); for filename in &args[1..] { let tsk = openpgp::Cert::from_file(filename) .context("Failed to read key")?; let mut n = 0; for skb in tsk.keys() .with_policy(p, None).alive().revoked(false).for_signing().secret() { keys.push({ let mut key = skb.key().clone(); if key.secret().is_encrypted() { let password = rpassword::prompt_password(format!( "Please enter password to decrypt {}/{}: ", tsk, key ))?; key.secret_mut() .decrypt_in_place(skb.key(), &password.into()) .context("decryption failed")?; } n += 1; key.into_keypair()? }); } if n == 0 { return Err(anyhow::anyhow!("Found no suitable signing key on {}", tsk)); } } // Compose a writer stack corresponding to the output format and // packet structure we want. let mut sink = io::stdout(); // Stream an OpenPGP message. let message = Message::new(&mut sink); let message = Armorer::new(message).build()?; // Now, create a signer that emits the signature(s). let mut signer = Signer::new(message, keys.pop().context("No key for signing")?)?; for s in keys { signer = signer.add_signer(s)?; } let mut message = signer.build().context("Failed to create signer")?; // Create a parser for the message to be notarized. let mut input = io::stdin(); let mut ppr = openpgp::parse::PacketParser::from_reader(&mut input) .context("Failed to build parser")?; while let PacketParserResult::Some(mut pp) = ppr { if let Err(err) = pp.possible_message() { return Err(anyhow::anyhow!("Malformed OpenPGP message: {}", err)); } match pp.packet { Packet::PKESK(_) | Packet::SKESK(_) => return Err(anyhow::anyhow!("Encrypted messages are not supported")), Packet::OnePassSig(ref ops) => ops.serialize(&mut message).context("Failed to serialize")?, Packet::Literal(_) => { // Then, create a literal writer to wrap the data in a // literal message packet. let mut literal = LiteralWriter::new(message).build() .context("Failed to create literal writer")?; // Copy all the data. io::copy(&mut pp, &mut literal) .context("Failed to sign data")?; message = literal.finalize_one() .context("Failed to sign data")? .unwrap(); }, Packet::Signature(ref sig) => sig.serialize(&mut message).context("Failed to serialize")?, _ => (), } ppr = pp.recurse().context("Failed to recurse")?.1; } if let PacketParserResult::EOF(eof) = ppr { if let Err(err) = eof.is_message() { return Err(anyhow::anyhow!("Malformed OpenPGP message: {}", err)); } } else { unreachable!() } // Finally, teardown the stack to ensure all the data is written. message.finalize() .context("Failed to write data")?; Ok(()) } sequoia-openpgp-2.0.0/examples/pad.rs000064400000000000000000000054151046102023000156740ustar 00000000000000/// Asymmetrically encrypts and pads OpenPGP messages using the /// openpgp crate, Sequoia's low-level API. use std::env; use std::io; use anyhow::Context; use sequoia_openpgp as openpgp; use crate::openpgp::types::KeyFlags; use crate::openpgp::parse::Parse; use crate::openpgp::serialize::stream::{ Armorer, Message, LiteralWriter, Encryptor, padding::*, }; use crate::openpgp::policy::StandardPolicy as P; fn main() -> openpgp::Result<()> { let p = &P::new(); let args: Vec = env::args().collect(); if args.len() < 3 { return Err(anyhow::anyhow!("A simple encryption filter.\n\n\ Usage: {} [at-rest|for-transport] [...] \ output\n", args[0])); } let mode = match args[1].as_ref() { "at-rest" => KeyFlags::empty().set_storage_encryption(), "for-transport" => KeyFlags::empty().set_transport_encryption(), x => return Err(anyhow::anyhow!("invalid mode: {:?}, \ must be either 'at-rest' or 'for-transport'", x)), }; // Read the certificates from the given files. let certs: Vec = args[2..].iter().map(|f| { openpgp::Cert::from_file(f) }).collect::>>().context("Failed to read key")?; // Build a list of recipient subkeys. let mut recipients = Vec::new(); for cert in certs.iter() { // Make sure we add at least one subkey from every // certificate. let mut found_one = false; for key in cert.keys().with_policy(p, None) .supported().alive().revoked(false).key_flags(&mode) { recipients.push(key); found_one = true; } if ! found_one { return Err(anyhow::anyhow!("No suitable encryption subkey for {}", cert)); } } // Compose a writer stack corresponding to the output format and // packet structure we want. let mut sink = io::stdout(); // Stream an OpenPGP message. let message = Message::new(&mut sink); let message = Armorer::new(message).build()?; // We want to encrypt a literal data packet. let message = Encryptor::for_recipients(message, recipients) .build().context("Failed to create encryptor")?; let message = Padder::new(message) .build().context("Failed to create padder")?; let mut message = LiteralWriter::new(message).build() .context("Failed to create literal writer")?; // Copy stdin to our writer stack to encrypt the data. io::copy(&mut io::stdin(), &mut message) .context("Failed to encrypt")?; // Finally, finalize the OpenPGP message by tearing down the // writer stack. message.finalize()?; Ok(()) } sequoia-openpgp-2.0.0/examples/reply-encrypted.rs000064400000000000000000000206041046102023000202530ustar 00000000000000//! Demonstrates how to reply to an encrypted message without having //! everyone's certs. //! //! This example demonstrates how to fall back to the original //! message's session key in order to encrypt a reply. //! //! Replying to an encrypted message usually requires the encryption //! (sub)keys for every recipient. If even one key is not available, //! it is not possible to encrypt the new session key. Rather than //! falling back to replying unencrypted, one can reuse the original //! message's session key that was encrypted for every recipient and //! reuse the original PKESKs. //! //! Decrypts an asymmetrically-encrypted OpenPGP message using the //! openpgp crate, Sequoia's low-level API, remembering the session //! key and PKESK packets. It then encrypts a new message reusing //! both the session key and PKESK packets. //! //! # Examples //! //! First, we generate two keys. Second, we encrypt a message for //! both certs. We then decrypt the original message using Alice's //! key and this example program, composing an encrypted reply reusing //! the session key and PKESK packets. Finally, we decrypt the reply //! using Bob's key. //! //! ```sh //! $ sqop generate-key alice@example.org > alice.pgp //! $ sqop generate-key bob@example.org > bob.pgp //! $ echo Original message | sqop encrypt alice.pgp bob.pgp > original.pgp //! $ echo Reply | cargo run -p sequoia-openpgp --example reply-encrypted -- \ //! original.pgp alice.pgp > reply.pgp //! $ sqop decrypt --session-key-out original.sk bob.pgp < reply.pgp //! Encrypted using AES with 256-bit key //! - Original message: //! Original message //! - Reusing (AES with 256-bit key, 62F3EADC...) with 2 PKESK packets //! Reply //! $ cat original.sk //! 9:62F3EADC98E1D3D34495E79264B5959391B4FABB2B2A2B7E03861F92D0B03161 //! ``` use std::sync::Arc; use std::collections::HashMap; use std::env; use std::io; use anyhow::Context; use sequoia_openpgp as openpgp; use openpgp::{Cert, KeyID}; use openpgp::packet::prelude::*; use openpgp::crypto::{KeyPair, SessionKey}; use openpgp::types::SymmetricAlgorithm; use openpgp::parse::{Parse, stream::*}; use openpgp::serialize::{Serialize, stream::*}; use openpgp::policy::Policy; use openpgp::policy::StandardPolicy as P; pub fn main() -> openpgp::Result<()> { let p = &P::new(); let args: Vec = env::args().collect(); if args.len() < 3 { return Err(anyhow::anyhow!("Reply-to-all without having all certs.\n\n\ Usage: {} [...] \ ciphertext\n", args[0])); } let encrypted_message = &args[1]; // Read the transferable secret keys from the given files. let certs = args[2..].iter().map(|f| { openpgp::Cert::from_file(f) }).collect::<openpgp::Result<Vec<_>>>() .context("Failed to read key")?; // Now, create a decryptor with a helper using the given Certs. let mut decryptor = DecryptorBuilder::from_file(encrypted_message)? .with_policy(p, None, Helper::new(p, certs))?; // Finally, stream the decrypted data to stderr. eprintln!("- Original message:"); io::copy(&mut decryptor, &mut io::stderr()) .context("Decryption failed")?; let (algo, sk, pkesks) = decryptor.into_helper().recycling_bin.unwrap(); eprintln!("- Reusing ({:?}, {}) with {} PKESK packets", algo, openpgp::fmt::hex::encode(&sk), pkesks.len()); // Compose a writer stack corresponding to the output format and // packet structure we want. let mut sink = io::stdout(); // Stream an OpenPGP message. let message = Message::new(&mut sink); let mut message = Armorer::new(message).build()?; // Emit the stashed PKESK packets. for p in pkesks { openpgp::Packet::from(p).serialize(&mut message)?; } // We want to encrypt a literal data packet. let message = Encryptor::with_session_key(message, algo.expect("XXX seipdv2"), sk)? .build().context("Failed to create encryptor")?; let mut message = LiteralWriter::new(message).build() .context("Failed to create literal writer")?; // Copy stdin to our writer stack to encrypt the data. io::copy(&mut io::stdin(), &mut message) .context("Failed to encrypt")?; // Finally, finalize the OpenPGP message by tearing down the // writer stack. message.finalize()?; Ok(()) } /// This helper provides secrets for the decryption, fetches public /// keys for the signature verification and implements the /// verification policy. struct Helper { keys: HashMap<KeyID, (Arc<Cert>, KeyPair)>, recycling_bin: Option<(Option<SymmetricAlgorithm>, SessionKey, Vec<PKESK>)>, } impl Helper { /// Creates a Helper for the given Certs with appropriate secrets. fn new(p: &dyn Policy, certs: Vec<openpgp::Cert>) -> Self { // Map (sub)KeyIDs to certs and secrets. let mut keys = HashMap::new(); for cert in certs { let cert = Arc::new(cert); for ka in cert.keys().unencrypted_secret().with_policy(p, None) .supported() .for_storage_encryption().for_transport_encryption() { keys.insert(ka.key().keyid(), (cert.clone(), ka.key().clone().into_keypair().unwrap())); } } Helper { keys, recycling_bin: None, } } } impl DecryptionHelper for Helper { fn decrypt(&mut self, pkesks: &[openpgp::packet::PKESK], _skesks: &[openpgp::packet::SKESK], sym_algo: Option<SymmetricAlgorithm>, decrypt: &mut dyn FnMut(Option<SymmetricAlgorithm>, &SessionKey) -> bool) -> openpgp::Result<Option<Cert>> { // Try each PKESK until we succeed. let mut recipient = None; let mut encryption_context = None; for pkesk in pkesks { if let Some((cert, pair)) = self.keys.get_mut(&KeyID::from(pkesk.recipient())) { if pkesk.decrypt(pair, sym_algo) .map(|(algo, session_key)| { let success = decrypt(algo, &session_key); if success { // Keep a copy the algorithm, session key, // and all PKESK packets for the reply. encryption_context = Some(( algo, session_key.clone(), pkesks.iter().cloned().collect(), )); } success }) .unwrap_or(false) { recipient = Some(cert.as_ref().clone()); break; } } } // Store for later use. self.recycling_bin = encryption_context; Ok(recipient) } } impl VerificationHelper for Helper { fn get_certs(&mut self, _ids: &[openpgp::KeyHandle]) -> openpgp::Result<Vec<openpgp::Cert>> { Ok(Vec::new()) // Feed the Certs to the verifier here. } fn check(&mut self, structure: MessageStructure) -> openpgp::Result<()> { for layer in structure.iter() { match layer { MessageLayer::Compression { algo } => eprintln!("Compressed using {}", algo), MessageLayer::Encryption { sym_algo, aead_algo } => if let Some(aead_algo) = aead_algo { eprintln!("Encrypted and protected using {}/{}", sym_algo, aead_algo); } else { eprintln!("Encrypted using {}", sym_algo); }, MessageLayer::SignatureGroup { ref results } => for result in results { match result { Ok(GoodChecksum { ka, .. }) => { eprintln!("Good signature from {}", ka.cert()); }, Err(e) => eprintln!("Error: {:?}", e), } } } } Ok(()) // Implement your verification policy here. } } ����������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/examples/sign-detached.rs�����������������������������������������������������0000644�0000000�0000000�00000005103�10461020230�0017621�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/// This program demonstrates how to make a detached signature. use std::env; use std::io; use anyhow::Context; use sequoia_openpgp as openpgp; use crate::openpgp::parse::Parse; use crate::openpgp::serialize::stream::{Armorer, Message, Signer}; use crate::openpgp::policy::StandardPolicy as P; fn main() -> openpgp::Result<()> { let p = &P::new(); let args: Vec<String> = env::args().collect(); if args.len() < 2 { return Err(anyhow::anyhow!("A simple filter creating a detached signature.\n\n\ Usage: {} <secret-keyfile> [<secret-keyfile>...] \ <input >output\n", args[0])); } // Read the transferable secret keys from the given files. let mut keys = Vec::new(); for filename in &args[1..] { let tsk = openpgp::Cert::from_file(filename) .context("Failed to read key")?; let mut n = 0; for skb in tsk .keys().with_policy(p, None).alive().revoked(false).for_signing().secret() { keys.push({ let mut key = skb.key().clone(); if key.secret().is_encrypted() { let password = rpassword::prompt_password(format!( "Please enter password to decrypt {}/{}: ", tsk, key ))?; key.secret_mut() .decrypt_in_place(skb.key(), &password.into()) .context("decryption failed")?; } n += 1; key.into_keypair()? }); } if n == 0 { return Err(anyhow::anyhow!("Found no suitable signing key on {}", tsk)); } } // Compose a writer stack corresponding to the output format and // packet structure we want. let mut sink = io::stdout(); // Stream an OpenPGP message. let message = Message::new(&mut sink); let message = Armorer::new(message) .kind(openpgp::armor::Kind::Signature) .build()?; // Now, create a signer that emits the detached signature(s). let mut signer = Signer::new(message, keys.pop().context("No key for signing")?)?; for s in keys { signer = signer.add_signer(s)?; } let mut message = signer.detached().build().context("Failed to create signer")?; // Copy all the data. io::copy(&mut io::stdin(), &mut message) .context("Failed to sign data")?; // Finally, teardown the stack to ensure all the data is written. message.finalize() .context("Failed to write data")?; Ok(()) } �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/examples/sign.rs��������������������������������������������������������������0000644�0000000�0000000�00000005256�10461020230�0016073�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/// Signs data using the openpgp crate, Sequoia's low-level API. use std::env; use std::io; use anyhow::Context; use sequoia_openpgp as openpgp; use crate::openpgp::parse::Parse; use crate::openpgp::serialize::stream::{Armorer, Message, LiteralWriter, Signer}; use crate::openpgp::policy::StandardPolicy as P; fn main() -> openpgp::Result<()> { let p = &P::new(); let args: Vec<String> = env::args().collect(); if args.len() < 2 { return Err(anyhow::anyhow!("A simple signing filter.\n\n\ Usage: {} <secret-keyfile> [<secret-keyfile>...] \ <input >output\n", args[0])); } // Read the transferable secret keys from the given files. let mut keys = Vec::new(); for filename in &args[1..] { let tsk = openpgp::Cert::from_file(filename) .context("Failed to read key")?; let mut n = 0; for skb in tsk.keys() .with_policy(p, None).alive().revoked(false).for_signing().secret() { keys.push({ let mut key = skb.key().clone(); if key.secret().is_encrypted() { let password = rpassword::prompt_password(format!( "Please enter password to decrypt {}/{}: ", tsk, key ))?; key.secret_mut() .decrypt_in_place(skb.key(), &password.into()) .context("decryption failed")?; } n += 1; key.into_keypair()? }); } if n == 0 { return Err(anyhow::anyhow!("Found no suitable signing key on {}", tsk)); } } // Compose a writer stack corresponding to the output format and // packet structure we want. let mut sink = io::stdout(); // Stream an OpenPGP message. let message = Message::new(&mut sink); let message = Armorer::new(message).build()?; // Now, create a signer that emits the signature(s). let mut signer = Signer::new(message, keys.pop().context("No key for signing")?)?; for s in keys { signer = signer.add_signer(s)?; } let message = signer.build().context("Failed to create signer")?; // Then, create a literal writer to wrap the data in a literal // message packet. let mut message = LiteralWriter::new(message).build() .context("Failed to create literal writer")?; // Copy all the data. io::copy(&mut io::stdin(), &mut message) .context("Failed to sign data")?; // Finally, teardown the stack to ensure all the data is written. message.finalize() .context("Failed to write data")?; Ok(()) } ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/examples/statistics.rs��������������������������������������������������������0000644�0000000�0000000�00000074203�10461020230�0017323�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/// Collects statistics about the SKS packet dump using the openpgp /// crate, Sequoia's low-level API. /// /// Note that to achieve reasonable performance, you need to compile /// Sequoia and this program with optimizations: /// /// % cargo run -p sequoia-openpgp --example statistics --release \ /// -- <packet-dump> use std::env; use std::collections::HashMap; use anyhow::Context; use sequoia_openpgp as openpgp; use crate::openpgp::{Packet, Fingerprint, KeyID, KeyHandle}; use crate::openpgp::crypto::mpi; use crate::openpgp::types::*; use crate::openpgp::packet::{user_attribute, header::BodyLength, Tag}; use crate::openpgp::packet::signature::subpacket::SubpacketTag; use crate::openpgp::parse::{Parse, PacketParserResult, PacketParser}; use crate::openpgp::serialize::MarshalInto; fn main() -> openpgp::Result<()> { let args: Vec<String> = env::args().collect(); if args.len() < 2 { return Err(anyhow::anyhow!("Collects statistics about OpenPGP packet dumps.\n\n\ Usage: {} <packet-dump> [<packet-dump>...]\n", args[0])); } // Global stats. let mut packet_count = 0; let mut packet_size = 0 as usize; // Per-tag statistics. let mut tags_count = vec![0; 64]; let mut tags_unknown = vec![0; 64]; let mut tags_size_bytes = vec![0 as usize; 64]; let mut tags_size_count = vec![0; 64]; let mut tags_size_min = vec![::std::u32::MAX; 64]; let mut tags_size_max = vec![0; 64]; // Signature statistics. let mut sigs_count = vec![0; 256]; let mut sigs_count_1st_party = vec![0; 256]; // Signature Subpacket statistics. let mut sigs_subpacket_tags_count = vec![0; 256]; let mut sigs_subpacket_tags_unknown = vec![0; 256]; let mut sigs_subpacket_tags_size_bytes = vec![0 as usize; 256]; let mut sigs_subpacket_tags_size_count = vec![0; 256]; let mut sigs_subpacket_tags_size_min = vec![::std::u32::MAX; 256]; let mut sigs_subpacket_tags_size_max = vec![0; 256]; let mut sigs_subpacket_exportable_true = 0; let mut sigs_subpacket_exportable_false = 0; let mut sigs_subpacket_re_zero_terminated = 0; let mut sigs_subpacket_re_inner_zero = 0; // Per-Signature statistics. let mut signature_min = PerSignature::max(); let mut signature_max = PerSignature::min(); // Various SubpacketValue-related counters. let mut key_flags: HashMap<KeyFlags, usize> = Default::default(); let mut p_sym: HashMap<Vec<SymmetricAlgorithm>, usize> = Default::default(); let mut p_hashes: HashMap<Vec<HashAlgorithm>, usize> = Default::default(); let mut p_comp: HashMap<Vec<CompressionAlgorithm>, usize> = Default::default(); let mut p_aead_ciphersuites: HashMap<Vec<(SymmetricAlgorithm, AEADAlgorithm)>, usize> = Default::default(); // Per-Cert statistics. let mut cert_count = 0; let mut cert = PerCert::min(); let mut cert_min = PerCert::max(); let mut cert_max = PerCert::min(); // UserAttribute statistics. let mut ua_image_count = vec![0; 256]; let mut ua_image_unknown_variant_count = 0; let mut ua_unknown_count = vec![0; 256]; let mut ua_invalid_count = 0; let mut ua_unknown_variant_count = 0; // Key statistics. let mut pk_algo_size: HashMap<PublicKeyAlgorithm, HashMap<usize, usize>> = Default::default(); // ECDH Parameter (KDF and KEK) statistics. let mut ecdh_params: HashMap<(HashAlgorithm, SymmetricAlgorithm), usize> = Default::default(); let mut ecdh_params_by_curve: HashMap<(Curve, HashAlgorithm, SymmetricAlgorithm), usize> = Default::default(); // Current certificate. let mut current_fingerprint = KeyHandle::Fingerprint(Fingerprint::from_bytes(4, &vec![0; 20])?); let mut current_keyid = KeyHandle::KeyID(KeyID::wildcard()); // For each input file, create a parser. for input in &args[1..] { eprintln!("Parsing {}...", input); let mut ppr = PacketParser::from_file(input) .context("Failed to create reader")?; // Iterate over all packets. while let PacketParserResult::Some(pp) = ppr { // While the packet is in the parser, get some data for later. let size = match pp.header().length() { &BodyLength::Full(n) => Some(n), _ => None, }; // Get the packet and advance the parser. let (packet, tmp) = pp.next().context("Failed to get next packet")?; ppr = tmp; packet_count += 1; if let Some(n) = size { packet_size += n as usize; } let i = u8::from(packet.tag()) as usize; tags_count[i] += 1; match packet { // If a new Cert starts, update Cert statistics. Packet::PublicKey(ref k) => { if cert_count > 0 { cert.update_min_max(&mut cert_min, &mut cert_max); } cert_count += 1; cert = PerCert::min(); current_fingerprint = k.fingerprint().into(); current_keyid = k.keyid().into(); }, Packet::SecretKey(ref k) => { if cert_count > 0 { cert.update_min_max(&mut cert_min, &mut cert_max); } cert_count += 1; cert = PerCert::min(); current_fingerprint = k.fingerprint().into(); current_keyid = k.keyid().into(); }, Packet::Signature(ref sig) => { sigs_count[u8::from(sig.typ()) as usize] += 1; let issuers = sig.get_issuers(); if issuers.contains(&current_keyid) || issuers.contains(&current_fingerprint) { sigs_count_1st_party[u8::from(sig.typ()) as usize] += 1; } cert.sigs[u8::from(sig.typ()) as usize] += 1; let mut signature = PerSignature::min(); for sub in sig.hashed_area().iter() .chain(sig.unhashed_area().iter()) { use crate::openpgp::packet::signature::subpacket::*; let i = u8::from(sub.tag()) as usize; sigs_subpacket_tags_count[i] += 1; cert.sigs_subpacket_tags_count[i] += 1; signature.subpacket_tags_count[i] += 1; if let SubpacketValue::Unknown { .. } = sub.value() { sigs_subpacket_tags_unknown [u8::from(sub.tag()) as usize] += 1; } else { let len = sub.serialized_len(); sigs_subpacket_tags_size_bytes[i] += len; sigs_subpacket_tags_size_count[i] += 1; let len = len as u32; if len < sigs_subpacket_tags_size_min[i] { sigs_subpacket_tags_size_min[i] = len; } if len > sigs_subpacket_tags_size_max[i] { sigs_subpacket_tags_size_max[i] = len; } #[allow(deprecated)] match sub.value() { SubpacketValue::Unknown { .. } => unreachable!(), SubpacketValue::KeyFlags(k) => if let Some(count) = key_flags.get_mut(k) { *count += 1; } else { key_flags.insert(k.clone(), 1); }, SubpacketValue::PreferredSymmetricAlgorithms(a) => if let Some(count) = p_sym.get_mut(a) { *count += 1; } else { p_sym.insert(a.clone(), 1); }, SubpacketValue::PreferredHashAlgorithms(a) => if let Some(count) = p_hashes.get_mut(a) { *count += 1; } else { p_hashes.insert(a.clone(), 1); }, SubpacketValue::PreferredCompressionAlgorithms(a) => if let Some(count) = p_comp.get_mut(a) { *count += 1; } else { p_comp.insert(a.clone(), 1); }, SubpacketValue::PreferredAEADCiphersuites(a) => if let Some(count) = p_aead_ciphersuites.get_mut(a) { *count += 1; } else { p_aead_ciphersuites.insert(a.clone(), 1); }, SubpacketValue::ExportableCertification(v) => if *v { sigs_subpacket_exportable_true += 1; } else { sigs_subpacket_exportable_false += 1; }, SubpacketValue::RegularExpression(r) => if r.last() == Some(&0) { sigs_subpacket_re_zero_terminated += 1; } else if r.iter().any(|&b| b == 0) { sigs_subpacket_re_inner_zero += 1; }, _ => (), } } } signature.update_min_max(&mut signature_min, &mut signature_max); }, Packet::UserAttribute(ref ua) => { use crate::user_attribute::Subpacket; use crate::user_attribute::Image; for subpacket in ua.subpackets() { match subpacket { Ok(Subpacket::Image(i)) => match i { Image::JPEG(_) => ua_image_count[1] += 1, Image::Private(n, _) => ua_image_count[n as usize] += 1, Image::Unknown(n, _) => ua_image_count[n as usize] += 1, _ => ua_image_unknown_variant_count += 1, }, Ok(Subpacket::Unknown(n, _)) => ua_unknown_count[n as usize] += 1, Ok(_) => ua_unknown_variant_count += 1, Err(_) => ua_invalid_count += 1, } } }, _ => (), } // Public key algorithm and size statistics. let mut handle_key = |k: &openpgp::packet::Key<_, _>| { let pk = k.pk_algo(); let bits = k.mpis().bits().unwrap_or(0); if let Some(size_hash) = pk_algo_size.get_mut(&pk) { if let Some(count) = size_hash.get_mut(&bits) { *count = *count + 1; } else { size_hash.insert(bits, 1); } } else { let mut size_hash: HashMap<usize, usize> = Default::default(); size_hash.insert(bits, 1); pk_algo_size.insert(pk, size_hash); } fn inc<T>(counter: &mut HashMap<T, usize>, key: T) where T: std::hash::Hash + Eq, { if let Some(count) = counter.get_mut(&key) { *count += 1; } else { counter.insert(key, 1); } } if let mpi::PublicKey::ECDH { curve, hash, sym, .. } = k.mpis() { inc(&mut ecdh_params, (hash.clone(), sym.clone())); inc(&mut ecdh_params_by_curve, (curve.clone(), hash.clone(), sym.clone())); } }; match packet { Packet::PublicKey(ref k) => handle_key(k.parts_as_public().role_as_unspecified()), Packet::SecretKey(ref k) => handle_key(k.parts_as_public().role_as_unspecified()), Packet::PublicSubkey(ref k) => handle_key(k.parts_as_public().role_as_unspecified()), Packet::SecretSubkey(ref k) => handle_key(k.parts_as_public().role_as_unspecified()), _ => (), } if let Packet::Unknown(_) = packet { tags_unknown[i] += 1; } else { // Only record size statistics of packets we successfully // parsed. if let Some(n) = size { tags_size_bytes[i] += n as usize; tags_size_count[i] += 1; if n < tags_size_min[i] { tags_size_min[i] = n; } if n > tags_size_max[i] { tags_size_max[i] = n; } cert.bytes += n as usize; } cert.packets += 1; cert.tags[i] += 1; } } cert.update_min_max(&mut cert_min, &mut cert_max); } // Print statistics. println!("# Packet statistics"); println!(); println!("{:>14} {:>9} {:>9} {:>9} {:>9} {:>9} {:>12}", "", "count", "unknown", "min size", "mean size", "max size", "sum size"); println!("-------------------------------------------------------\ -----------------------"); for t in 0..64 { let count = tags_count[t]; if count > 0 { println!("{:>14} {:>9} {:>9} {:>9} {:>9} {:>9} {:>12}", format!("{:?}", Tag::from(t as u8)), count, tags_unknown[t], tags_size_min[t], tags_size_bytes[t] / tags_size_count[t], tags_size_max[t], tags_size_bytes[t]); } } let signature_count = tags_count[u8::from(Tag::Signature) as usize]; if signature_count > 0 { println!(); println!("# Signature statistics"); println!(); println!("{:>22} {:>9}", "", "count",); println!("--------------------------------"); for t in 0..256 { let max = cert_max.sigs[t]; if max > 0 { println!("{:>22} {:>9}", format!("{:?}", SignatureType::from(t as u8)), sigs_count[t]); println!("{:>22} {:>9}", "1st party", sigs_count_1st_party[t]); println!("{:>22} {:>9}", "3rd party", sigs_count[t] - sigs_count_1st_party[t]); } } println!(); println!("# Per-Signature Subpacket statistics"); println!(); println!("{:>30} {:>9} {:>9} {:>9}", "", "min", "mean", "max"); println!("----------------------------------------------------\ --------"); for t in 0..256 { let max = signature_max.subpacket_tags_count[t]; if max > 0 { println!("{:>30} {:>9} {:>9} {:>9}", subpacket_short_name(t), signature_min.subpacket_tags_count[t], sigs_subpacket_tags_count[t] / signature_count, max); } } println!(); println!("# Signature Subpacket statistics"); println!(); println!("{:>30} {:>8} {:>6} {:>4} {:>4} {:>5} {:>14}", "", "", "", "min", "mean", "max", "sum"); println!("{:>30} {:>8} {:>6} {:>4} {:>4} {:>5} {:>14}", "", "#", "?", "size", "size", "size", "size"); println!("-------------------------------------------------------\ ----------------------"); for t in 0..256 { let count = sigs_subpacket_tags_count[t]; let size_count = sigs_subpacket_tags_size_count[t]; if size_count > 0 { println!("{:>30} {:>8} {:>6} {:>4} {:>4} {:>5} {:>14}", subpacket_short_name(t), count, sigs_subpacket_tags_unknown[t], sigs_subpacket_tags_size_min[t], sigs_subpacket_tags_size_bytes[t] / size_count, sigs_subpacket_tags_size_max[t], sigs_subpacket_tags_size_bytes[t]); } else if count > 0 { println!("{:>30} {:>8} {:>6} {:>4} {:>4} {:>5} {:>14}", subpacket_short_name(t), count, sigs_subpacket_tags_unknown[t], "-", "-", "-", "-"); } match SubpacketTag::from(t as u8) { SubpacketTag::ExportableCertification => { if sigs_subpacket_exportable_true > 0 { println!("{:>30} {:>8}", "ExportableCertification(true)", sigs_subpacket_exportable_true); } if sigs_subpacket_exportable_false > 0 { println!("{:>30} {:>8}", "ExportableCertification(false)", sigs_subpacket_exportable_false); } }, SubpacketTag::RegularExpression => { println!("{:>30} {:>8}", "RegularExpression 0-terminated", sigs_subpacket_re_zero_terminated); println!("{:>30} {:>8}", "RegularExpression inner 0", sigs_subpacket_re_inner_zero); }, _ => (), } } } if !key_flags.is_empty() { println!(); println!("# KeyFlags statistics"); println!(); println!("{:>22} {:>9}", "", "count",); println!("--------------------------------"); // Sort by the number of occurrences. let mut kf = key_flags.iter().map(|(f, n)| (format!("{:?}", f), n)) .collect::<Vec<_>>(); kf.sort_unstable_by(|a, b| b.1.cmp(a.1)); for (f, n) in kf.iter() { println!("{:>22} {:>9}", f, n); } } if !p_sym.is_empty() { println!(); println!("# PreferredSymmetricAlgorithms statistics"); println!(); println!("{:>70} {:>9}", "", "count",); println!("----------------------------------------\ ----------------------------------------"); // Sort by the number of occurrences. let mut preferences = p_sym.iter().map(|(a, n)| { let a = format!("{:?}", a); (a[1..a.len()-1].to_string(), n) }).collect::<Vec<_>>(); preferences.sort_unstable_by(|a, b| b.1.cmp(a.1)); for (a, n) in preferences { println!("{:>70} {:>9}", a, n); } } if !p_hashes.is_empty() { println!(); println!("# PreferredHashlgorithms statistics"); println!(); println!("{:>70} {:>9}", "", "count",); println!("----------------------------------------\ ----------------------------------------"); // Sort by the number of occurrences. let mut preferences = p_hashes.iter().map(|(a, n)| { let a = format!("{:?}", a); (a[1..a.len()-1].to_string(), n) }).collect::<Vec<_>>(); preferences.sort_unstable_by(|a, b| b.1.cmp(a.1)); for (a, n) in preferences { let a = format!("{:?}", a); println!("{:>70} {:>9}", &a[1..a.len()-1], n); } } if !p_comp.is_empty() { println!(); println!("# PreferredCompressionAlgorithms statistics"); println!(); println!("{:>70} {:>9}", "", "count",); println!("----------------------------------------\ ----------------------------------------"); // Sort by the number of occurrences. let mut preferences = p_comp.iter().map(|(a, n)| { let a = format!("{:?}", a); (a[1..a.len()-1].to_string(), n) }).collect::<Vec<_>>(); preferences.sort_unstable_by(|a, b| b.1.cmp(a.1)); for (a, n) in preferences { let a = format!("{:?}", a); println!("{:>70} {:>9}", &a[1..a.len()-1], n); } } if !p_aead_ciphersuites.is_empty() { println!(); println!("# PreferredAEADCiphersuites statistics"); println!(); println!("{:>70} {:>9}", "", "count",); println!("----------------------------------------\ ----------------------------------------"); for (a, n) in p_aead_ciphersuites.iter() { let a = format!("{:?}", a); println!("{:>70} {:>9}", &a[1..a.len()-1], n); } } if ua_invalid_count > 0 || ua_image_count.iter().any(|c| *c > 0) || ua_unknown_count.iter().any(|c| *c > 0) { println!(); println!("# User Attribute Subpacket statistics"); println!(); println!("{:>21} {:>9}", "", "count",); println!("----------------------------"); for t in 0..256 { let n = ua_image_count[t]; if n > 0 { println!("{:>21} {:>9}", match t { 1 => "Image::JPEG".into(), 100..=110 => format!("Image::Private({})", t), _ => format!("Image::Unknown({})", t), }, n); } } if ua_image_unknown_variant_count > 0 { println!("{:>21} {:>9}", "Unknown image variant", ua_image_unknown_variant_count); } for t in 0..256 { let n = ua_unknown_count[t]; if n > 0 { println!("{:>21} {:>9}", format!("Unknown({})", t), n); } } if ua_unknown_variant_count > 0 { println!("{:>21} {:>9}", "Unknown variant", ua_unknown_variant_count); } if ua_invalid_count > 0 { println!("{:>21} {:>9}", "Invalid", ua_invalid_count); } } if cert_count == 0 { return Ok(()); } println!(); println!("# Key statistics\n\n\ {:>50} {:>9} {:>9}", "Algorithm", "Key Size", "count"); println!("----------------------------------------------------------------------"); for t in 0..255u8 { let pk = PublicKeyAlgorithm::from(t); if let Some(size_hash) = pk_algo_size.get(&pk) { let mut sizes: Vec<_> = size_hash.iter().collect(); sizes.sort_by_key(|(size, _count)| *size); for (size, count) in sizes { println!("{:>50} {:>9} {:>9}", pk.to_string(), size, count); } } } if !ecdh_params.is_empty() { println!(); println!("# ECDH Parameter statistics"); println!(); println!("{:>70} {:>9}", "", "count",); println!("----------------------------------------\ ----------------------------------------"); // Sort by the number of occurrences. let mut params = ecdh_params.iter() .map(|((hash, sym), count)| { (format!("{:?}, {:?}", hash, sym), count) }).collect::<Vec<_>>(); params.sort_unstable_by(|a, b| b.1.cmp(a.1)); for (a, n) in params { println!("{:>70} {:>9}", a, n); } println!(); println!("# ECDH Parameter statistics by curve"); println!(); println!("{:>70} {:>9}", "", "count",); println!("----------------------------------------\ ----------------------------------------"); // Sort by the number of occurrences. let mut params = ecdh_params_by_curve.iter() .map(|((curve, hash, sym), count)| { (format!("{:?}, {:?}, {:?}", curve, hash, sym), count) }).collect::<Vec<_>>(); params.sort_unstable_by(|a, b| b.1.cmp(a.1)); for (a, n) in params { println!("{:>70} {:>9}", a, n); } } println!(); println!("# Cert statistics\n\n\ {:>30} {:>9} {:>9} {:>9}", "", "min", "mean", "max"); println!("------------------------------------------------------------"); println!("{:>30} {:>9} {:>9} {:>9}", "Size (packets)", cert_min.packets, packet_count / cert_count, cert_max.packets); println!("{:>30} {:>9} {:>9} {:>9}", "Size (bytes)", cert_min.bytes, packet_size / cert_count, cert_max.bytes); println!("\n{:>30}", "- Packets -"); for t in 0..64 { let max = cert_max.tags[t]; if t as u8 != Tag::PublicKey.into() && max > 0 { println!("{:>30} {:>9} {:>9} {:>9}", format!("{:?}", Tag::from(t as u8)), cert_min.tags[t], tags_count[t] / cert_count, max); } } println!("\n{:>30}", "- Signatures -"); for t in 0..256 { let max = cert_max.sigs[t]; if max > 0 { println!("{:>30} {:>9} {:>9} {:>9}", format!("{:?}", SignatureType::from(t as u8)), cert_min.sigs[t], sigs_count[t] / cert_count, max); } } println!("\n{:>30}", "- Signature Subpackets -"); for t in 0..256 { let max = cert_max.sigs_subpacket_tags_count[t]; if max > 0 { println!("{:>30} {:>9} {:>9} {:>9}", subpacket_short_name(t), cert_min.sigs_subpacket_tags_count[t], sigs_subpacket_tags_count[t] / cert_count, max); } } Ok(()) } fn subpacket_short_name(t: usize) -> String { let tag_name = format!("{:?}", SubpacketTag::from(t as u8)); String::from_utf8_lossy( tag_name.as_bytes().chunks(30).next().unwrap()).into() } struct PerCert { packets: usize, bytes: usize, tags: Vec<u32>, sigs: Vec<u32>, sigs_subpacket_tags_count: Vec<u32>, } impl PerCert { fn min() -> Self { PerCert { packets: 0, bytes: 0, tags: vec![0; 64], sigs: vec![0; 256], sigs_subpacket_tags_count: vec![0; 256], } } fn max() -> Self { PerCert { packets: ::std::usize::MAX, bytes: ::std::usize::MAX, tags: vec![::std::u32::MAX; 64], sigs: vec![::std::u32::MAX; 256], sigs_subpacket_tags_count: vec![::std::u32::MAX; 256], } } fn update_min_max(&self, min: &mut PerCert, max: &mut PerCert) { if self.packets < min.packets { min.packets = self.packets; } if self.packets > max.packets { max.packets = self.packets; } if self.bytes < min.bytes { min.bytes = self.bytes; } if self.bytes > max.bytes { max.bytes = self.bytes; } for i in 0..64 { if self.tags[i] < min.tags[i] { min.tags[i] = self.tags[i]; } if self.tags[i] > max.tags[i] { max.tags[i] = self.tags[i]; } } for i in 0..256 { if self.sigs[i] < min.sigs[i] { min.sigs[i] = self.sigs[i]; } if self.sigs[i] > max.sigs[i] { max.sigs[i] = self.sigs[i]; } } for i in 0..256 { if self.sigs_subpacket_tags_count[i] < min.sigs_subpacket_tags_count[i] { min.sigs_subpacket_tags_count[i] = self.sigs_subpacket_tags_count[i]; } if self.sigs_subpacket_tags_count[i] > max.sigs_subpacket_tags_count[i] { max.sigs_subpacket_tags_count[i] = self.sigs_subpacket_tags_count[i]; } } } } struct PerSignature { subpacket_tags_count: Vec<u32>, } impl PerSignature { fn min() -> Self { PerSignature { subpacket_tags_count: vec![0; 256], } } fn max() -> Self { PerSignature { subpacket_tags_count: vec![::std::u32::MAX; 256], } } fn update_min_max(&self, min: &mut PerSignature, max: &mut PerSignature) { for i in 0..256 { if self.subpacket_tags_count[i] < min.subpacket_tags_count[i] { min.subpacket_tags_count[i] = self.subpacket_tags_count[i]; } if self.subpacket_tags_count[i] > max.subpacket_tags_count[i] { max.subpacket_tags_count[i] = self.subpacket_tags_count[i]; } } } } ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/examples/supported-algorithms.rs����������������������������������������������0000644�0000000�0000000�00000002601�10461020230�0021316�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������//! This example prints all algorithms supported by the currently //! selected cryptographic backend. use sequoia_openpgp as openpgp; use openpgp::types::*; use openpgp::cert::CipherSuite; fn main() { println!("Cipher suites:"); for a in CipherSuite::variants() { println!(" - {:70} {:?}", format!("{:?}", a), a.is_supported().is_ok()); } println!(); println!("Public-Key algorithms:"); for a in PublicKeyAlgorithm::variants() { println!(" - {:70} {:?}", a.to_string(), a.is_supported()); } println!(); println!("ECC algorithms:"); for a in Curve::variants() { println!(" - {:70} {:?}", a.to_string(), a.is_supported()); } println!(); println!("Symmetric algorithms:"); for a in SymmetricAlgorithm::variants() { println!(" - {:70} {:?}", a.to_string(), a.is_supported()); } println!(); println!("AEAD algorithms:"); for a in AEADAlgorithm::variants() { println!(" - {:70} {:?}", a.to_string(), a.is_supported()); } println!(); println!("Hash algorithms:"); for a in HashAlgorithm::variants() { println!(" - {:70} {:?}", a.to_string(), a.is_supported()); } println!(); println!("Compression algorithms:"); for a in CompressionAlgorithm::variants().skip(1) { println!(" - {:70} {:?}", a.to_string(), a.is_supported()); } println!(); } �������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/examples/web-of-trust.rs������������������������������������������������������0000644�0000000�0000000�00000004311�10461020230�0017460�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/// Extracts the Web-Of-Trust, i.e. the certification relation, from /// SKS packet dump using the openpgp crate, Sequoia's low-level API. /// /// Note that to achieve reasonable performance, you need to compile /// Sequoia and this program with optimizations: /// /// % cargo run -p sequoia-openpgp --example web-of-trust --release \ /// -- <packet-dump> [<packet-dump> ...] use std::env; use anyhow::Context; use sequoia_openpgp as openpgp; use crate::openpgp::KeyID; use crate::openpgp::cert::prelude::*; use crate::openpgp::parse::Parse; fn main() -> openpgp::Result<()> { let args: Vec<String> = env::args().collect(); if args.len() < 2 { return Err(anyhow::anyhow!("Extracts the certification relation from OpenPGP packet dumps.\ \n\nUsage: {} <packet-dump> [<packet-dump> ...]\n", args[0])); } // The issuer refers to a (sub)key, but we want to use the primary // keys as identifiers. But, because there are no tools besides // Sequoia that support certification-capable subkeys, we will // assume for now that the issuer is always a primary key. eprintln!("Format: certifier, user-id, key"); // For each input file, create a parser. for input in &args[1..] { eprintln!("Parsing {}...", input); let parser = CertParser::from_file(input) .context("Failed to create reader")?; for cert in parser { match cert { Ok(cert) => { let keyid = cert.keyid(); for uidb in cert.userids() { for tps in uidb.certifications() { for issuer in tps.get_issuers() { println!("{}, {:?}, {}", KeyID::from(issuer).as_u64()?, String::from_utf8_lossy( uidb.userid().value()), keyid.as_u64()?); } } } }, Err(e) => eprintln!("Parsing Cert failed: {}", e), } } } Ok(()) } �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/examples/wrap-literal.rs������������������������������������������������������0000644�0000000�0000000�00000002502�10461020230�0017525�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/// Wraps a stream of data into a literal data packet using the /// openpgp crate, Sequoia's low-level API. /// /// It is also used to generate test vectors for the armor subsystem. use std::env; use std::io; use anyhow::Context; use sequoia_openpgp as openpgp; use crate::openpgp::serialize::stream::{Armorer, Message, LiteralWriter}; fn main() -> openpgp::Result<()> { let args: Vec<String> = env::args().collect(); if args.len() != 1 { return Err(anyhow::anyhow!("A simple filter wrapping data into a literal data packet.\n\n\ Usage: {} <input >output\n", args[0])); } // Compose a writer stack corresponding to the output format and // packet structure we want. let mut sink = io::stdout(); // Stream an OpenPGP message. let message = Message::new(&mut sink); let message = Armorer::new(message).build()?; // Then, create a literal writer to wrap the data in a literal // message packet. let mut message = LiteralWriter::new(message).build() .context("Failed to create literal writer")?; // Copy all the data. io::copy(&mut io::stdin(), &mut message) .context("Failed to sign data")?; // Finally, teardown the stack to ensure all the data is written. message.finalize() .context("Failed to write data")?; Ok(()) } ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/src/armor/base64_utils.rs�����������������������������������������������������0000644�0000000�0000000�00000015257�10461020230�0017532�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������use std::{ borrow::Cow, }; use base64::Engine; use base64::engine::general_purpose::STANDARD as base64std; use crate::{ packet::Header, }; /// Remove whitespace, etc. from the base64 data. /// /// This function returns the filtered base64 data (i.e., stripped of /// all skippable data like whitespace), and the amount of unfiltered /// data that corresponds to. Thus, if we have the following 7 bytes: /// /// ```text /// ab cde /// 0123456 /// ``` /// /// This function returns ("abcd", 6), because the 'd' is the last /// character in the last complete base64 chunk, and it is at offset 5. /// /// If 'd' is followed by whitespace, it is undefined whether that /// whitespace is included in the count. /// /// This function only returns full chunks of base64 data. As a /// consequence, if base64_data_max is less than 4, then this will not /// return any data. /// /// This function will stop after it sees base64 padding, and if it /// sees invalid base64 data. pub fn base64_filter(mut bytes: Cow<[u8]>, base64_data_max: usize, mut prefix_remaining: usize, prefix_len: usize) -> (Cow<[u8]>, usize, usize) { let mut leading_whitespace = 0; // Round down to the nearest chunk size. let base64_data_max = base64_data_max / 4 * 4; // Number of bytes of base64 data. Since we update `bytes` in // place, the base64 data is `&bytes[..base64_len]`. let mut base64_len = 0; // Offset of the next byte of unfiltered data to process. let mut unfiltered_offset = 0; // Offset of the last byte of the last ***complete*** base64 chunk // in the unfiltered data. let mut unfiltered_complete_len = 0; // Number of bytes of padding that we've seen so far. let mut padding = 0; while unfiltered_offset < bytes.len() && base64_len < base64_data_max // A valid base64 chunk never starts with padding. && ! (padding > 0 && base64_len % 4 == 0) { // If we have some prefix to skip, skip it. if prefix_remaining > 0 { prefix_remaining -= 1; if unfiltered_offset == 0 { match bytes { Cow::Borrowed(s) => { // We're at the beginning. Avoid moving // data by cutting off the start of the // slice. bytes = Cow::Borrowed(&s[1..]); leading_whitespace += 1; continue; } Cow::Owned(_) => (), } } unfiltered_offset += 1; continue; } match bytes[unfiltered_offset] { // White space. c if c.is_ascii_whitespace() => { if c == b'\n' { prefix_remaining = prefix_len; } if unfiltered_offset == 0 { match bytes { Cow::Borrowed(s) => { // We're at the beginning. Avoid moving // data by cutting off the start of the // slice. bytes = Cow::Borrowed(&s[1..]); leading_whitespace += 1; continue; } Cow::Owned(_) => (), } } } // Padding. b'=' => { if padding == 2 { // There can never be more than two bytes of // padding. break; } if base64_len % 4 == 0 { // Padding can never occur at the start of a // base64 chunk. break; } if unfiltered_offset != base64_len { bytes.to_mut()[base64_len] = b'='; } base64_len += 1; if base64_len % 4 == 0 { unfiltered_complete_len = unfiltered_offset + 1; } padding += 1; } // The only thing that can occur after padding is // whitespace or padding. Those cases were covered above. _ if padding > 0 => break, // Base64 data! b if is_base64_char(&b) => { if unfiltered_offset != base64_len { bytes.to_mut()[base64_len] = b; } base64_len += 1; if base64_len % 4 == 0 { unfiltered_complete_len = unfiltered_offset + 1; } } // Not base64 data. _ => break, } unfiltered_offset += 1; } let base64_len = base64_len - (base64_len % 4); unfiltered_complete_len += leading_whitespace; match bytes { Cow::Borrowed(s) => (Cow::Borrowed(&s[..base64_len]), unfiltered_complete_len, prefix_remaining), Cow::Owned(mut v) => { crate::vec_truncate(&mut v, base64_len); (Cow::Owned(v), unfiltered_complete_len, prefix_remaining) } } } /// Checks whether the given bytes contain armored OpenPGP data. pub fn is_armored_pgp_blob(bytes: &[u8]) -> bool { // Get up to 32 bytes of base64 data. That's 24 bytes of data // (ignoring padding), which is more than enough to get the first // packet's header. let (bytes, _, _) = base64_filter(Cow::Borrowed(bytes), 32, 0, 0); match base64std.decode(bytes) { Ok(d) => { // Don't consider an empty message to be valid. if d.is_empty() { false } else { let mut br = buffered_reader::Memory::new(&d); if let Ok(header) = Header::parse(&mut br) { header.ctb().tag().valid_start_of_message() && header.valid(false).is_ok() } else { false } } }, Err(_err) => false, } } /// Checks whether the given byte is in the base64 character set. pub fn is_base64_char(b: &u8) -> bool { b.is_ascii_alphanumeric() || *b == b'+' || *b == b'/' } /// Returns the number of bytes of base64 data are needed to encode /// `s` bytes of raw data. pub fn base64_size(s: usize) -> usize { (s + 3 - 1) / 3 * 4 } #[test] fn base64_size_test() { assert_eq!(base64_size(0), 0); assert_eq!(base64_size(1), 4); assert_eq!(base64_size(2), 4); assert_eq!(base64_size(3), 4); assert_eq!(base64_size(4), 8); assert_eq!(base64_size(5), 8); assert_eq!(base64_size(6), 8); assert_eq!(base64_size(7), 12); } �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/src/armor/crc.rs��������������������������������������������������������������0000644�0000000�0000000�00000005442�10461020230�0015770�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������//! Computes the CRC-24, (see [Section 6.1.1 of RFC 9580]). //! //! [Section 6.1.1 of RFC 9580]: https://www.rfc-editor.org/rfc/rfc9580.html#section-6.1.1 const CRC24_INIT: u32 = 0xB704CE; const CRC24_POLY: u32 = 0x864CFB; #[derive(Debug)] pub struct Crc { n: u32, } /// Computes the CRC-24, (see [Section 6.1.1 of RFC 9580]). /// /// [Section 6.1.1 of RFC 9580]: https://www.rfc-editor.org/rfc/rfc9580.html#section-6.1.1 impl Crc { pub fn new() -> Self { Self { n: CRC24_INIT } } /// Updates the CRC sum using the given data. /// /// This implementation uses a lookup table. See: /// /// Sarwate, Dilip V. "Computation of cyclic redundancy checks via /// table look-up." Communications of the ACM 31.8 (1988): /// 1008-1013. pub fn update(&mut self, buf: &[u8]) -> &Self { use std::sync::OnceLock; static TABLE: OnceLock<Vec<u32>> = OnceLock::new(); let table = TABLE.get_or_init(|| { let mut t = vec![0u32; 256]; let mut crc = 0x80_0000; // 24 bit polynomial let mut i = 1; loop { if crc & 0x80_0000 > 0 { crc = (crc << 1) ^ CRC24_POLY; } else { crc <<= 1; } for j in 0..i { t[i + j] = crc ^ t[j]; } i <<= 1; if i == 256 { break; } } t }); for octet in buf { self.n = (self.n << 8) ^ table[(*octet ^ ((self.n >> 16) as u8)) as usize]; } self } pub fn finalize(&self) -> u32 { self.n & 0xFFFFFF } } #[cfg(test)] mod tests { use super::*; #[test] fn foobarbaz() { let b = b"foobarbaz"; let crcs = [ 0xb704ce, 0x6d2804, 0xa2d10d, 0x4fc255, 0x7aafca, 0xc79c46, 0x7334de, 0x77dc72, 0x000f65, 0xf40d86, ]; for len in 0..b.len() + 1 { assert_eq!(Crc::new().update(&b[..len]).finalize(), crcs[len]); } } /// Reference implementation of the iterative CRC24 computation. fn iterative(buf: &[u8]) -> u32 { let mut n = CRC24_INIT; for octet in buf { n ^= (*octet as u32) << 16; for _ in 0..8 { n <<= 1; if n & 0x1000000 > 0 { n ^= CRC24_POLY; } } } n & 0xFFFFFF } quickcheck! { fn compare(b: Vec<u8>) -> bool { let mut c = Crc::new(); c.update(&b); assert_eq!(c.finalize(), iterative(&b)); true } } } ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/src/armor.rs������������������������������������������������������������������0000644�0000000�0000000�00000253017�10461020230�0015224�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������//! ASCII Armor. //! //! This module deals with ASCII Armored data (see [Section 6 of RFC //! 9580]). //! //! [Section 6 of RFC 9580]: https://www.rfc-editor.org/rfc/rfc9580.html#section-6 //! //! # Scope //! //! This implements a subset of the ASCII Armor specification. Not //! supported multipart messages. //! //! # Memory allocations //! //! Both the reader and the writer allocate memory in the order of the //! size of chunks read or written. //! //! # Examples //! //! ```rust, no_run //! # fn main() -> sequoia_openpgp::Result<()> { //! use sequoia_openpgp as openpgp; //! use std::fs::File; //! use openpgp::armor::{Reader, ReaderMode, Kind}; //! //! let mut file = File::open("somefile.asc")?; //! let mut r = Reader::from_reader(&mut file, ReaderMode::Tolerant(Some(Kind::File))); //! # Ok(()) } //! ``` use buffered_reader::BufferedReader; use std::convert::TryFrom; use std::fmt; use std::io; use std::io::{Cursor, Read, Write}; use std::io::{Result, Error, ErrorKind}; use std::path::Path; use std::cmp; use std::str; use std::borrow::Cow; #[cfg(test)] use quickcheck::{Arbitrary, Gen}; use base64::Engine; use base64::engine::general_purpose::STANDARD as base64std; use base64::engine::general_purpose::STANDARD_NO_PAD as base64nopad; use crate::Profile; use crate::packet::prelude::*; use crate::packet::header::{BodyLength, CTBNew, CTBOld}; use crate::parse::Cookie; use crate::serialize::MarshalInto; use crate::{vec_resize, vec_truncate}; mod base64_utils; use base64_utils::*; mod crc; use crc::Crc; /// Whether to trace execution by default (on stderr). const TRACE: bool = false; /// The encoded output stream must be represented in lines of no more /// than 76 characters each (see [Section 6 of RFC 9580]). GnuPG /// uses 64. /// /// [Section 6 of RFC 9580]: https://www.rfc-editor.org/rfc/rfc9580.html#section-6 pub(crate) const LINE_LENGTH: usize = 64; const LINE_ENDING: &str = "\n"; /// Specifies the type of data (see [Section 6.2 of RFC 9580]). /// /// [Section 6.2 of RFC 9580]: https://www.rfc-editor.org/rfc/rfc9580.html#section-6.2 #[derive(Copy, Clone, Debug, PartialEq)] #[non_exhaustive] pub enum Kind { /// A generic OpenPGP message. (Since its structure hasn't been /// validated, in this crate's terminology, this is just a /// `PacketPile`.) Message, /// A certificate. PublicKey, /// A transferable secret key. SecretKey, /// A detached signature. Signature, /// A generic file. This is a GnuPG extension. File, } assert_send_and_sync!(Kind); #[cfg(test)] impl Arbitrary for Kind { fn arbitrary(g: &mut Gen) -> Self { use self::Kind::*; match u8::arbitrary(g) % 5 { 0 => Message, 1 => PublicKey, 2 => SecretKey, 3 => Signature, 4 => File, _ => unreachable!(), } } } /// Specifies the kind of data as indicated by the label. /// /// This is a non-public variant of `Kind` that is currently only used /// for detecting the kind on consumption. /// /// See also <https://gitlab.com/sequoia-pgp/sequoia/-/issues/672>. #[derive(Clone, Copy, Debug, PartialEq, Eq)] enum Label { /// A generic OpenPGP message. (Since its structure hasn't been /// validated, in this crate's terminology, this is just a /// `PacketPile`.) Message, /// A certificate. PublicKey, /// A transferable secret key. SecretKey, /// A detached signature. Signature, /// A message using the Cleartext Signature Framework. /// /// See [Section 7 of RFC 9580]. /// /// [Section 7 of RFC 9580]: https://www.rfc-editor.org/rfc/rfc9580.html#section-7 CleartextSignature, /// A generic file. This is a GnuPG extension. File, } assert_send_and_sync!(Label); impl TryFrom<Label> for Kind { type Error = crate::Error; fn try_from(l: Label) -> std::result::Result<Self, Self::Error> { match l { Label::Message => Ok(Kind::Message), Label::PublicKey => Ok(Kind::PublicKey), Label::SecretKey => Ok(Kind::SecretKey), Label::Signature => Ok(Kind::Signature), Label::File => Ok(Kind::File), Label::CleartextSignature => Err(crate::Error::InvalidOperation( "armor::Kind cannot express cleartext signatures".into())), } } } impl Label { /// Detects the header returning the kind and length of the /// header. fn detect_header(blurb: &[u8]) -> Option<(Self, usize)> { let (leading_dashes, rest) = dash_prefix(blurb); // Skip over "BEGIN PGP " if ! rest.starts_with(b"BEGIN PGP ") { return None; } let rest = &rest[b"BEGIN PGP ".len()..]; // Detect kind. let kind = if rest.starts_with(b"MESSAGE") { Label::Message } else if rest.starts_with(b"PUBLIC KEY BLOCK") { Label::PublicKey } else if rest.starts_with(b"PRIVATE KEY BLOCK") { Label::SecretKey } else if rest.starts_with(b"SIGNATURE") { Label::Signature } else if rest.starts_with(b"SIGNED MESSAGE") { Label::CleartextSignature } else if rest.starts_with(b"ARMORED FILE") { Label::File } else { return None; }; let (trailing_dashes, _) = dash_prefix(&rest[kind.blurb().len()..]); Some((kind, leading_dashes.len() + b"BEGIN PGP ".len() + kind.blurb().len() + trailing_dashes.len())) } fn blurb(&self) -> &str { match self { Label::Message => "MESSAGE", Label::PublicKey => "PUBLIC KEY BLOCK", Label::SecretKey => "PRIVATE KEY BLOCK", Label::Signature => "SIGNATURE", Label::CleartextSignature => "SIGNED MESSAGE", Label::File => "ARMORED FILE", } } } impl Kind { /// Detects the footer returning length of the footer. fn detect_footer(&self, blurb: &[u8]) -> Option<usize> { tracer!(TRACE, "armor::Kind::detect_footer"); t!("Looking for footer in {:?}", String::from_utf8_lossy(blurb)); let (leading_dashes, rest) = dash_prefix(blurb); // Skip over "END PGP " if ! rest.starts_with(b"END PGP ") { return None; } let rest = &rest[b"END PGP ".len()..]; let ident = self.blurb().as_bytes(); if ! rest.starts_with(ident) { return None; } let (trailing_dashes, _) = dash_prefix(&rest[ident.len()..]); Some(leading_dashes.len() + b"END PGP ".len() + ident.len() + trailing_dashes.len()) } fn blurb(&self) -> &str { match self { Kind::Message => "MESSAGE", Kind::PublicKey => "PUBLIC KEY BLOCK", Kind::SecretKey => "PRIVATE KEY BLOCK", Kind::Signature => "SIGNATURE", Kind::File => "ARMORED FILE", } } fn begin(&self) -> String { format!("-----BEGIN PGP {}-----", self.blurb()) } fn end(&self) -> String { format!("-----END PGP {}-----", self.blurb()) } } /// A filter that applies ASCII Armor to the data written to it. pub struct Writer<W: Write> { sink: W, profile: Option<Profile>, kind: Kind, stash: Vec<u8>, column: usize, crc: Crc, header: Vec<u8>, dirty: bool, scratch: Vec<u8>, } assert_send_and_sync!(Writer<W> where W: Write); impl<W: Write> Writer<W> { /// Constructs a new filter for the given type of data. /// /// # Examples /// /// ``` /// use std::io::{Read, Write, Cursor}; /// use sequoia_openpgp as openpgp; /// use openpgp::armor::{Writer, Kind}; /// /// # fn main() -> std::io::Result<()> { /// let mut writer = Writer::new(Vec::new(), Kind::File)?; /// writer.write_all(b"Hello world!")?; /// let buffer = writer.finalize()?; /// assert_eq!( /// String::from_utf8_lossy(&buffer), /// "-----BEGIN PGP ARMORED FILE----- /// /// SGVsbG8gd29ybGQh /// =s4Gu /// -----END PGP ARMORED FILE----- /// "); /// # Ok(()) /// # } /// ``` pub fn new(inner: W, kind: Kind) -> Result<Self> { Self::with_headers(inner, kind, Option::<(&str, &str)>::None) } /// Constructs a new filter for the given type of data. /// /// # Examples /// /// ``` /// use std::io::{Read, Write, Cursor}; /// use sequoia_openpgp as openpgp; /// use openpgp::armor::{Writer, Kind}; /// /// # fn main() -> std::io::Result<()> { /// let mut writer = Writer::with_headers(Vec::new(), Kind::File, /// vec![("Key", "Value")])?; /// writer.write_all(b"Hello world!")?; /// let buffer = writer.finalize()?; /// assert_eq!( /// String::from_utf8_lossy(&buffer), /// "-----BEGIN PGP ARMORED FILE----- /// Key: Value /// /// SGVsbG8gd29ybGQh /// =s4Gu /// -----END PGP ARMORED FILE----- /// "); /// # Ok(()) /// # } /// ``` pub fn with_headers<I, K, V>(inner: W, kind: Kind, headers: I) -> Result<Self> where I: IntoIterator<Item = (K, V)>, K: AsRef<str>, V: AsRef<str>, { let mut w = Writer { sink: inner, profile: None, kind, stash: Vec::<u8>::with_capacity(2), column: 0, crc: Crc::new(), header: Vec::with_capacity(128), dirty: false, scratch: vec![0; 4096], }; { let mut cur = Cursor::new(&mut w.header); write!(&mut cur, "{}{}", kind.begin(), LINE_ENDING)?; for h in headers { write!(&mut cur, "{}: {}{}", h.0.as_ref(), h.1.as_ref(), LINE_ENDING)?; } // A blank line separates the headers from the body. write!(&mut cur, "{}", LINE_ENDING)?; } Ok(w) } /// Sets the version of OpenPGP to generate ASCII Armor for. /// /// This function can only be called once. Calling it repeatedly /// will return an error. /// /// # Examples /// /// ``` /// use std::io::{Read, Write, Cursor}; /// use sequoia_openpgp as openpgp; /// use openpgp::armor::{Writer, Kind}; /// /// # fn main() -> std::io::Result<()> { /// let mut writer = Writer::new(Vec::new(), Kind::File)?; /// /// writer.set_profile(openpgp::Profile::RFC9580)?; /// writer.set_profile(openpgp::Profile::RFC9580).unwrap_err(); /// writer.set_profile(openpgp::Profile::RFC4880).unwrap_err(); /// /// writer.write_all(b"Hello world!")?; /// let buffer = writer.finalize()?; /// assert_eq!( /// String::from_utf8_lossy(&buffer), /// "-----BEGIN PGP ARMORED FILE----- /// /// SGVsbG8gd29ybGQh /// -----END PGP ARMORED FILE----- /// "); /// # Ok(()) /// # } /// ``` pub fn set_profile(&mut self, profile: Profile) -> Result<()> { if self.profile.is_some() { return Err(io::Error::new( io::ErrorKind::Other, "profile already selected")); } self.profile = Some(profile); Ok(()) } /// Returns a reference to the inner writer. pub fn get_ref(&self) -> &W { &self.sink } /// Returns a mutable reference to the inner writer. pub fn get_mut(&mut self) -> &mut W { &mut self.sink } fn finalize_headers(&mut self) -> Result<()> { if ! self.dirty { self.dirty = true; self.sink.write_all(&self.header)?; // Release memory. crate::vec_truncate(&mut self.header, 0); self.header.shrink_to_fit(); } Ok(()) } /// Writes the footer. /// /// This function needs to be called explicitly before the writer is dropped. pub fn finalize(mut self) -> Result<W> { if ! self.dirty { // No data was written to us, don't emit anything. return Ok(self.sink); } self.finalize_armor()?; Ok(self.sink) } /// Writes the footer. fn finalize_armor(&mut self) -> Result<()> { if ! self.dirty { // No data was written to us, don't emit anything. return Ok(()); } self.finalize_headers()?; // Write any stashed bytes and pad. if !self.stash.is_empty() { self.sink.write_all(base64std.encode(&self.stash).as_bytes())?; self.column += 4; } // Inserts a line break if necessary. // // Unfortunately, we cannot use //self.linebreak()?; // // Therefore, we inline it here. This is a bit sad. assert!(self.column <= LINE_LENGTH); if self.column == LINE_LENGTH { write!(self.sink, "{}", LINE_ENDING)?; self.column = 0; } if self.column > 0 { write!(self.sink, "{}", LINE_ENDING)?; } match self.profile { None | Some(Profile::RFC4880) => { // 24-bit CRC let crc = self.crc.finalize(); let bytes = &crc.to_be_bytes()[1..4]; // Emit the CRC line. write!(self.sink, "={}{}", base64nopad.encode(&bytes), LINE_ENDING)?; }, Some(Profile::RFC9580) => (), } // Finally, emit the footer. write!(self.sink, "{}{}", self.kind.end(), LINE_ENDING)?; self.dirty = false; crate::vec_truncate(&mut self.scratch, 0); Ok(()) } /// Inserts a line break if necessary. fn linebreak(&mut self) -> Result<()> { assert!(self.column <= LINE_LENGTH); if self.column == LINE_LENGTH { write!(self.sink, "{}", LINE_ENDING)?; self.column = 0; } Ok(()) } } impl<W: Write> Write for Writer<W> { fn write(&mut self, buf: &[u8]) -> Result<usize> { self.finalize_headers()?; assert!(self.dirty); match self.profile { None | Some(Profile::RFC4880) => { // Update CRC on the unencoded data. self.crc.update(buf); }, Some(Profile::RFC9580) => (), } let mut input = buf; let mut written = 0; // First of all, if there are stashed bytes, fill the stash // and encode it. If writing out the stash fails below, we // might end up with a stash of size 3. assert!(self.stash.len() <= 3); if !self.stash.is_empty() { let missing = 3 - self.stash.len(); let n = missing.min(input.len()); self.stash.extend_from_slice(&input[..n]); input = &input[n..]; written += n; if input.is_empty() { // We exhausted the input. Return now, any stashed // bytes are encoded when finalizing the writer. return Ok(written); } assert_eq!(self.stash.len(), 3); // If this fails for some reason, and the caller retries // the write, we might end up with a stash of size 3. self.sink .write_all(base64nopad.encode(&self.stash).as_bytes())?; self.column += 4; self.linebreak()?; crate::vec_truncate(&mut self.stash, 0); } // Encode all whole blocks of 3 bytes. let n_blocks = input.len() / 3; let input_bytes = n_blocks * 3; if input_bytes > 0 { // Encrypt whole blocks. let encoded_bytes = n_blocks * 4; if self.scratch.len() < encoded_bytes { vec_resize(&mut self.scratch, encoded_bytes); } written += input_bytes; base64nopad.encode_slice(&input[..input_bytes], &mut self.scratch[..encoded_bytes]) .expect("buffer correctly sized"); let mut n = 0; while ! self.scratch[n..encoded_bytes].is_empty() { let m = self.scratch[n..encoded_bytes].len() .min(LINE_LENGTH - self.column); self.sink.write_all(&self.scratch[n..n + m])?; n += m; self.column += m; self.linebreak()?; } } // Stash rest for later. input = &input[input_bytes..]; assert!(input.is_empty() || self.stash.is_empty()); self.stash.extend_from_slice(input); written += input.len(); assert_eq!(written, buf.len()); Ok(written) } fn flush(&mut self) -> Result<()> { self.sink.flush() } } /// How an ArmorReader should act. #[derive(Debug, Clone, Copy, PartialEq)] #[non_exhaustive] pub enum ReaderMode { /// Makes the armor reader tolerant of simple errors. /// /// The armor reader will be tolerant of common formatting errors, /// such as incorrect line folding, but the armor header line /// (e.g., `----- BEGIN PGP MESSAGE -----`) and the footer must be /// intact. /// /// If a Kind is specified, then only ASCII Armor blocks with the /// appropriate header are recognized. /// /// This mode is appropriate when reading from a file. Tolerant(Option<Kind>), /// Makes the armor reader very tolerant of errors. /// /// Unlike in `Tolerant` mode, in this mode, the armor reader /// doesn't require an armor header line. Instead, it examines /// chunks that look like valid base64 data, and attempts to parse /// them. /// /// Although this mode looks for OpenPGP fingerprints before /// invoking the full parser, due to the number of false /// positives, this mode of operation is CPU intense, particularly /// on large text files. It is primarily appropriate when reading /// text that the user cut and pasted into a text area. VeryTolerant, } assert_send_and_sync!(ReaderMode); /// A filter that strips ASCII Armor from a stream of data. #[derive(Debug)] pub struct Reader<'a> { // The following fields are the state of an embedded // buffered_reader::Generic. We need to be able to access the // cookie in Self::initialize, therefore using // buffered_reader::Generic as we used to is no longer an option. // // XXX: Directly implement the BufferedReader protocol. This may // actually simplify the code and reduce the required buffering. buffer: Option<Vec<u8>>, /// Currently unused buffer, a cache. unused_buffer: Option<Vec<u8>>, // The next byte to read in the buffer. cursor: usize, // The preferred chunk size. This is just a hint. preferred_chunk_size: usize, // The wrapped reader. source: Box<dyn BufferedReader<Cookie> + 'a>, // Stashed error, if any. error: Option<Error>, /// Whether we hit EOF on the underlying reader. eof: bool, // The user settable cookie. cookie: Cookie, // End fields of the embedded generic reader. kind: Option<Kind>, mode: ReaderMode, decode_buffer: Vec<u8>, initialized: bool, headers: Vec<(String, String)>, finalized: bool, prefix: Vec<u8>, prefix_remaining: usize, /// Controls the transformation of messages using the Cleartext /// Signature Framework into inline signed messages. enable_csft: bool, /// State for the CSF transformer. csft: Option<CSFTransformer>, } assert_send_and_sync!(Reader<'_>); // The default buffer size. const DEFAULT_BUF_SIZE: usize = 32 * 1024; impl<'a> fmt::Display for Reader<'a> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "armor::Reader") } } impl Default for ReaderMode { fn default() -> Self { ReaderMode::Tolerant(None) } } /// State for transforming a message using the Cleartext Signature /// Framework into an inline signed message. #[derive(Clone, Copy, Debug, PartialEq, Eq)] enum CSFTransformer { Transform, Read, } impl Default for CSFTransformer { fn default() -> Self { CSFTransformer::Transform } } impl<'a> Reader<'a> { /// Constructs a new `Reader` from the given `BufferedReader`. pub fn from_buffered_reader<R, M>(reader: R, mode: M) -> Result<Self> where R: BufferedReader<Cookie> + 'a, M: Into<Option<ReaderMode>>, { Ok(Self::from_cookie_reader(reader.into_boxed(), mode, Default::default())) } /// Constructs a new `Reader` from the given `io::Read`er. /// /// [ASCII Armor], designed to protect OpenPGP data in transit, /// has been a source of problems if the armor structure is /// damaged. For example, copying data manually from one program /// to another might introduce or drop newlines. /// /// By default, the reader operates in tolerant mode. It will /// ignore common formatting errors but the header and footer /// lines must be intact. /// /// To select stricter mode, specify the kind argument for /// tolerant mode. In this mode only ASCII Armor blocks with the /// appropriate header are recognized. /// /// There is also very tolerant mode that is appropriate when /// reading text that the user cut and pasted into a text area. /// This mode of operation is CPU intense, particularly on large /// text files. /// /// [ASCII Armor]: https://www.rfc-editor.org/rfc/rfc9580.html#section-6.2 /// /// # Examples /// /// ``` /// use std::io::{self, Read}; /// use sequoia_openpgp as openpgp; /// use openpgp::Message; /// use openpgp::armor::{Reader, ReaderMode}; /// use openpgp::parse::Parse; /// /// # fn main() -> openpgp::Result<()> { /// let data = "yxJiAAAAAABIZWxsbyB3b3JsZCE="; // base64 over literal data packet /// /// let mut cursor = io::Cursor::new(&data); /// let mut reader = Reader::from_reader(&mut cursor, ReaderMode::VeryTolerant); /// /// let mut buf = Vec::new(); /// reader.read_to_end(&mut buf)?; /// /// let message = Message::from_bytes(&buf)?; /// assert_eq!(message.body().unwrap().body(), /// b"Hello world!"); /// # Ok(()) /// # } /// ``` /// /// Or, in strict mode: /// /// ``` /// use std::io::{self, Result, Read}; /// use sequoia_openpgp as openpgp; /// use openpgp::armor::{Reader, ReaderMode, Kind}; /// /// # fn main() -> Result<()> { /// let data = /// "-----BEGIN PGP ARMORED FILE----- /// /// SGVsbG8gd29ybGQh /// =s4Gu /// -----END PGP ARMORED FILE-----"; /// /// let mut cursor = io::Cursor::new(&data); /// let mut reader = Reader::from_reader(&mut cursor, ReaderMode::Tolerant(Some(Kind::File))); /// /// let mut content = String::new(); /// reader.read_to_string(&mut content)?; /// assert_eq!(content, "Hello world!"); /// assert_eq!(reader.kind(), Some(Kind::File)); /// # Ok(()) /// # } /// ``` pub fn from_reader<R, M>(reader: R, mode: M) -> Self where R: 'a + Read + Send + Sync, M: Into<Option<ReaderMode>> { Self::from_cookie_reader( Box::new(buffered_reader::Generic::with_cookie(reader, None, Default::default())), mode, Default::default()) } /// Creates a `Reader` from a file. pub fn from_file<P, M>(path: P, mode: M) -> Result<Self> where P: AsRef<Path>, M: Into<Option<ReaderMode>> { Ok(Self::from_cookie_reader( Box::new(buffered_reader::File::with_cookie(path, Default::default())?), mode, Default::default())) } /// Creates a `Reader` from a buffer. pub fn from_bytes<M>(bytes: &'a [u8], mode: M) -> Self where M: Into<Option<ReaderMode>> { Self::from_cookie_reader( Box::new(buffered_reader::Memory::with_cookie(bytes, Default::default())), mode, Default::default()) } pub(crate) fn from_cookie_reader<M>( inner: Box<dyn BufferedReader<Cookie> + 'a>, mode: M, cookie: Cookie) -> Self where M: Into<Option<ReaderMode>> { Self::from_cookie_reader_csft(inner, mode.into(), cookie, false) } pub(crate) fn from_cookie_reader_csft( inner: Box<dyn BufferedReader<Cookie> + 'a>, mode: Option<ReaderMode>, cookie: Cookie, enable_csft: bool, ) -> Self { let mode = mode.unwrap_or_default(); Reader { // The embedded generic reader's fields. buffer: None, unused_buffer: None, cursor: 0, preferred_chunk_size: DEFAULT_BUF_SIZE, source: inner, error: None, eof: false, cookie, // End of the embedded generic reader's fields. kind: None, mode, decode_buffer: Vec::<u8>::with_capacity(1024), headers: Vec::new(), initialized: false, finalized: false, prefix: Vec::with_capacity(0), prefix_remaining: 0, enable_csft, csft: None, } } /// Returns the kind of data this reader is for. /// /// Useful if the kind of data is not known in advance. If the /// header has not been encountered yet (try reading some data /// first!), this function returns None. pub fn kind(&self) -> Option<Kind> { self.kind } /// Returns the armored headers. /// /// The tuples contain a key and a value. /// /// Note: if a key occurs multiple times, then there are multiple /// entries in the vector with the same key; values with the same /// key are *not* combined. /// /// # Examples /// /// ``` /// use std::io::{self, Read}; /// use sequoia_openpgp as openpgp; /// use openpgp::armor::{Reader, ReaderMode, Kind}; /// /// # fn main() -> std::io::Result<()> { /// let data = /// "-----BEGIN PGP ARMORED FILE----- /// First: value /// Header: value /// /// SGVsbG8gd29ybGQh /// =s4Gu /// -----END PGP ARMORED FILE-----"; /// /// let mut cursor = io::Cursor::new(&data); /// let mut reader = Reader::from_reader(&mut cursor, ReaderMode::Tolerant(Some(Kind::File))); /// /// let mut content = String::new(); /// reader.read_to_string(&mut content)?; /// assert_eq!(reader.headers()?, /// &[("First".into(), "value".into()), /// ("Header".into(), "value".into())]); /// # Ok(()) /// # } /// ``` pub fn headers(&mut self) -> Result<&[(String, String)]> { self.initialize()?; Ok(&self.headers[..]) } } impl<'a> Reader<'a> { /// Consumes the header if not already done. fn initialize(&mut self) -> Result<()> { tracer!(TRACE, "armor::Reader::initialize"); t!("self.initialized is {:?}", self.initialized); if self.initialized { return Ok(()) } // The range of the first 6 bits of a message is limited. // Save cpu cycles by only considering base64 data that starts // with one of those characters. fn start_chars_very_tolerant() -> &'static [u8] { use std::sync::OnceLock; static START_CHARS_VERY_TOLERANT: OnceLock<Vec<u8>> = OnceLock::new(); START_CHARS_VERY_TOLERANT.get_or_init(|| { let mut valid_start = Vec::new(); for &tag in &[ Tag::PKESK, Tag::SKESK, Tag::OnePassSig, Tag::Signature, Tag::PublicKey, Tag::SecretKey, Tag::CompressedData, Tag::Literal, Tag::Marker, ] { let mut ctb = [ 0u8; 1 ]; let mut o = [ 0u8; 4 ]; CTBNew::new(tag).serialize_into(&mut ctb[..]).unwrap(); base64std.encode_slice(&ctb[..], &mut o[..]) .expect("buffer correctly sized"); valid_start.push(o[0]); CTBOld::new(tag, BodyLength::Full(0)).unwrap() .serialize_into(&mut ctb[..]).unwrap(); base64std.encode_slice(&ctb[..], &mut o[..]) .expect("buffer correctly sized"); valid_start.push(o[0]); } // Add all first bytes of Unicode characters from the // "Dash Punctuation" category. let mut b = [0; 4]; // Enough to hold any UTF-8 character. for d in dashes() { d.encode_utf8(&mut b); valid_start.push(b[0]); } // If there are no dashes at all, match on the BEGIN. valid_start.push(b'B'); valid_start.sort_unstable(); valid_start.dedup(); valid_start }) } fn start_chars_tolerant() -> &'static [u8] { use std::sync::OnceLock; static START_CHARS_TOLERANT: OnceLock<Vec<u8>> = OnceLock::new(); START_CHARS_TOLERANT.get_or_init(|| { let mut valid_start = Vec::new(); // Add all first bytes of Unicode characters from the // "Dash Punctuation" category. let mut b = [0; 4]; // Enough to hold any UTF-8 character. for d in dashes() { d.encode_utf8(&mut b); valid_start.push(b[0]); } // If there are no dashes at all, match on the BEGIN. valid_start.push(b'B'); valid_start.sort_unstable(); valid_start.dedup(); valid_start }) } // Look for the Armor Header Line, skipping any garbage in the // process. let mut found_blob = false; let start_chars = if self.mode != ReaderMode::VeryTolerant { start_chars_tolerant() } else { start_chars_very_tolerant() }; let mut lines = 0; let mut prefix = Vec::new(); let n = 'search: loop { if lines > 0 { // Find the start of the next line. self.source.drop_through(&[b'\n'], true)?; crate::vec_truncate(&mut prefix, 0); } lines += 1; // Ignore leading whitespace, etc. while matches!(self.source.data_hard(1)?[0], // Skip some whitespace (previously .is_ascii_whitespace()) b' ' | b'\t' | b'\r' | b'\n' | // Also skip common quote characters b'>' | b'|' | b']' | b'}' ) { let c = self.source.data(1)?[0]; if c == b'\n' { // We found a newline while walking whitespace, reset prefix crate::vec_truncate(&mut prefix, 0); } else { prefix.push(self.source.data_hard(1)?[0]); } self.source.consume(1); } // Don't bother if the first byte is not plausible. let start = self.source.data_hard(1)?[0]; if !start_chars.binary_search(&start).is_ok() { self.source.consume(1); continue; } { let mut input = self.source.data(128)?; let n = input.len(); if n == 0 { return Err( Error::new(ErrorKind::InvalidInput, "Reached EOF looking for Armor Header Line")); } if n > 128 { input = &input[..128]; } // Possible ASCII-armor header. if let Some((label, len)) = Label::detect_header(input) { t!("Found the label {:?}", label); if label == Label::CleartextSignature && ! self.enable_csft { // We found a message using the Cleartext // Signature Framework, but the CSF // transformation is not enabled. Continue // searching until we find the bare signature. continue 'search; } if label == Label::CleartextSignature && self.enable_csft { // Initialize the transformer. self.csft = Some(CSFTransformer::default()); // Signal to the parser stack that the CSF // transformation is happening. This will be // used by the HashedReader (specifically, in // Cookie::processing_csf_message and // Cookie::hash_update) to select the correct // hashing method. self.cookie.set_processing_csf_message(); // We'll be looking for the signature framing next. self.kind = Some(Kind::Signature); break 'search len; } let kind = Kind::try_from(label) .expect("cleartext signature handled above"); let mut expected_kind = None; if let ReaderMode::Tolerant(Some(kind)) = self.mode { expected_kind = Some(kind); } if expected_kind == None { // Found any! self.kind = Some(kind); break 'search len; } if expected_kind == Some(kind) { // Found it! self.kind = Some(kind); break 'search len; } } if self.mode == ReaderMode::VeryTolerant { // The user did not specify what kind of data she // wants. We aggressively try to decode any data, // even if we do not see a valid header. if is_armored_pgp_blob(input) { found_blob = true; break 'search 0; } } } }; self.source.consume(n); t!("self.kind is {:?} after consuming {} bytes", self.kind, n); if found_blob { // Skip the rest of the initialization. self.initialized = true; self.prefix_remaining = prefix.len(); self.prefix = prefix; return Ok(()); } self.prefix = prefix; self.read_headers() } /// Reads headers and finishes the initialization. fn read_headers(&mut self) -> Result<()> { tracer!(TRACE, "armor::Reader::read_headers"); // We consumed the header above, but not any trailing // whitespace and the trailing new line. We do that now. // Other data between the header and the new line are not // allowed. But, instead of failing, we try to recover, by // stopping at the first non-whitespace character. let n = { let line = self.source.read_to(b'\n')?; line.iter().position(|&c| { !c.is_ascii_whitespace() }).unwrap_or(line.len()) }; self.source.consume(n); t!("consumed {} bytes of whitespace", n); let next_prefix = &self.source.data_hard(self.prefix.len())?[..self.prefix.len()]; if self.prefix != next_prefix { // If the next line doesn't start with the same prefix, we assume // it was garbage on the front and drop the prefix so long as it // was purely whitespace. Any non-whitespace remains an error // while searching for the armor header if it's not repeated. if self.prefix.iter().all(|b| (*b as char).is_ascii_whitespace()) { crate::vec_truncate(&mut self.prefix, 0); } else { // Nope, we have actually failed to read this properly return Err( Error::new(ErrorKind::InvalidInput, "Inconsistent quoting of armored data")); } } // Read the key-value headers. let mut n = 0; // Sometimes, we find a truncated prefix. In these cases, the // length is not prefix.len(), but this. let mut prefix_len = None; let mut lines = 0; loop { // Skip any known prefix on lines. // // IMPORTANT: We need to buffer the prefix so that we can // consume it here. So at every point in this loop where // the control flow wraps around, we need to make sure // that we buffer the prefix in addition to the line. self.source.consume( prefix_len.take().unwrap_or_else(|| self.prefix.len())); self.source.consume(n); // Buffer the next line. let line = self.source.read_to(b'\n')?; n = line.len(); t!("{}: {:?}", lines, String::from_utf8_lossy(&line)); lines += 1; let line = str::from_utf8(line); // Ignore---don't error out---lines that are not valid UTF8. if line.is_err() { // Buffer the next line and the prefix that is going // to be consumed in the next iteration. let next_prefix = &self.source.data_hard(n + self.prefix.len())? [n..n + self.prefix.len()]; if self.prefix != next_prefix { return Err( Error::new(ErrorKind::InvalidInput, "Inconsistent quoting of armored data")); } continue; } let line = line.unwrap(); // The line almost certainly ends with \n: the only reason // it couldn't is if we encountered EOF. We need to strip // it. But, if it ends with \r\n, then we also want to // strip the \r too. let line = if let Some(rest) = line.strip_suffix("\r\n") { // \r\n. rest } else if let Some(rest) = line.strip_suffix('\n') { // \n. rest } else { // EOF. line }; /* Process headers. */ let key_value = line.splitn(2, ": ").collect::<Vec<&str>>(); if key_value.len() == 1 { if line.trim_start().is_empty() { // Empty line. break; } else if lines == 1 { // This is the first line and we don't have a // key-value pair. It seems more likely that // we're just missing a newline and this invalid // header is actually part of the body. n = 0; break; } } else { let key = key_value[0].trim_start(); let value = key_value[1]; self.headers.push((key.into(), value.into())); } // Buffer the next line and the prefix that is going to be // consumed in the next iteration. let next_prefix = &self.source.data_hard(n + self.prefix.len())? [n..n + self.prefix.len()]; // Sometimes, we find a truncated prefix. let l = common_prefix(&self.prefix, next_prefix); let full_prefix = l == self.prefix.len(); if ! (full_prefix // Truncation is okay if the rest of the prefix // contains only whitespace. || self.prefix[l..].iter().all(|c| c.is_ascii_whitespace())) { return Err( Error::new(ErrorKind::InvalidInput, "Inconsistent quoting of armored data")); } if ! full_prefix { // Make sure to only consume the truncated prefix in // the next loop iteration. prefix_len = Some(l); } } self.source.consume(n); self.initialized = true; self.prefix_remaining = self.prefix.len(); Ok(()) } } /// Computes the length of the common prefix. fn common_prefix<A: AsRef<[u8]>, B: AsRef<[u8]>>(a: A, b: B) -> usize { a.as_ref().iter().zip(b.as_ref().iter()).take_while(|(a, b)| a == b).count() } impl<'a> Reader<'a> { fn read_armored_data(&mut self, buf: &mut [u8]) -> Result<usize> { assert!(self.csft.is_none()); let (consumed, decoded) = if !self.decode_buffer.is_empty() { // We have something buffered, use that. let amount = cmp::min(buf.len(), self.decode_buffer.len()); buf[..amount].copy_from_slice(&self.decode_buffer[..amount]); crate::vec_drain_prefix(&mut self.decode_buffer, amount); (0, amount) } else { // We need to decode some data. We consider three cases, // all a function of the size of `buf`: // // - Tiny: if `buf` can hold less than three bytes, then // we almost certainly have to double buffer: except // at the very end, a base64 chunk consists of 3 bytes // of data. // // Note: this happens if the caller does `for c in // Reader::from_reader(...).bytes() ...`. Then it // reads one byte of decoded data at a time. // // - Small: if the caller only requests a few bytes at a // time, we may as well double buffer to reduce // decoding overhead. // // - Large: if `buf` is large, we can decode directly // into `buf` and avoid double buffering. But, // because we ignore whitespace, it is hard to // determine exactly how much data to read to // maximally fill `buf`. // We use 64, because ASCII-armor text usually contains 64 // characters of base64 data per line, and this prevents // turning the borrow into an own. const THRESHOLD : usize = 64; let to_read = cmp::max( // Tiny or small: THRESHOLD + 2, // Large: a heuristic: base64_size(buf.len()) // Assume about 2 bytes of whitespace (crlf) per // 64 character line. + 2 * ((buf.len() + 63) / 64)); let base64data = self.source.data(to_read)?; let base64data = if base64data.len() > to_read { &base64data[..to_read] } else { base64data }; let (base64data, consumed, prefix_remaining) = base64_filter(Cow::Borrowed(base64data), // base64_size rounds up, but we want // to round down as we have to double // buffer partial chunks. cmp::max(THRESHOLD, buf.len() / 3 * 4), self.prefix_remaining, self.prefix.len()); // We shouldn't have any partial chunks. assert_eq!(base64data.len() % 4, 0); let decoded = if base64data.len() / 4 * 3 > buf.len() { // We need to double buffer. Decode into a vector. // (Note: the computed size *might* be a slight // overestimate, because the last base64 chunk may // include padding.) self.decode_buffer = base64std.decode(&base64data) .map_err(|e| Error::new(ErrorKind::InvalidData, e))?; let copied = cmp::min(buf.len(), self.decode_buffer.len()); buf[..copied].copy_from_slice(&self.decode_buffer[..copied]); crate::vec_drain_prefix(&mut self.decode_buffer, copied); copied } else { // We can decode directly into the caller-supplied // buffer. base64std.decode_slice(&base64data, buf) .map_err(|e| Error::new(ErrorKind::InvalidData, e))? }; self.prefix_remaining = prefix_remaining; (consumed, decoded) }; self.source.consume(consumed); if decoded == 0 { self.finalized = true; /* Look for CRC. The CRC is optional. */ let consumed = { // Skip whitespace. while !self.source.data(1)?.is_empty() && self.source.buffer()[0].is_ascii_whitespace() { self.source.consume(1); } let data = self.source.data(5)?; let data = if data.len() > 5 { &data[..5] } else { data }; if data.len() == 5 && data[0] == b'=' && data[1..5].iter().all(is_base64_char) { /* Found. */ 5 } else { 0 } }; self.source.consume(consumed); // Skip any expected prefix self.source.data_consume_hard(self.prefix.len())?; // Look for a footer. let consumed = { // Skip whitespace. while !self.source.data(1)?.is_empty() && self.source.buffer()[0].is_ascii_whitespace() { self.source.consume(1); } // If we had a header, we require a footer. if let Some(kind) = self.kind { let footer_lookahead = 128; // Why not. let got = self.source.data(footer_lookahead)?; let got = if got.len() > footer_lookahead { &got[..footer_lookahead] } else { got }; if let Some(footer_len) = kind.detect_footer(got) { footer_len } else { return Err(Error::new(ErrorKind::InvalidInput, "Invalid ASCII Armor footer.")); } } else { 0 } }; self.source.consume(consumed); } Ok(decoded) } /// Reads a message using the Cleartext Signature Framework, /// transforms it into an inline-signed message, then feeds that /// to the consumer. fn read_clearsigned_message(&mut self, buf: &mut [u8]) -> crate::Result<usize> { assert!(self.csft.is_some()); use crate::{ parse::{ Dearmor, PacketParserBuilder, PacketParserResult, Parse, }, serialize::Serialize, types::DataFormat, }; if self.csft == Some(CSFTransformer::Transform) { // Read the text body. let literal = { let mut text = Vec::new(); loop { let prefixed_line = self.source.read_to(b'\n')?; if prefixed_line.is_empty() { // Truncated? break; } // Treat lines shorter than the prefix as // empty lines. let n = prefixed_line.len().min(self.prefix.len()); let prefix = &prefixed_line[..n]; let mut line = &prefixed_line[n..]; // Check that we see the correct prefix. let l = common_prefix(&self.prefix, prefix); let full_prefix = l == self.prefix.len(); if ! (full_prefix // Truncation is okay if the rest of the prefix // contains only whitespace. || self.prefix[l..].iter().all( |c| c.is_ascii_whitespace())) { return Err( Error::new(ErrorKind::InvalidInput, "Inconsistent quoting of \ armored data").into()); } let (dashes, rest) = dash_prefix(line); if dashes.len() > 2 // XXX: heuristic... && rest.starts_with(b"BEGIN PGP SIGNATURE") { // We reached the end of the signed // message. Consuming this line and break // the loop. let l = prefixed_line.len(); self.source.consume(l); break; } // Undo the dash-escaping. if line.starts_with(b"- ") { line = &line[2..]; } // Trim trailing whitespace according to Section // 7.1 of RFC4880, i.e. "spaces (0x20) and tabs // (0x09)". We do this here, because we transform // the CSF message into an inline signed message, // which does not make a distinction between the // literal text and the signed text (modulo the // newline normalization). // First, split off the line ending. let crlf_line_end = line.ends_with(b"\r\n"); line = &line[..line.len().saturating_sub( if crlf_line_end { 2 } else { 1 })]; // Now, trim whitespace off the line. while Some(&b' ') == line.last() || Some(&b'\t') == line.last() { line = &line[..line.len().saturating_sub(1)]; } text.extend_from_slice(line); if crlf_line_end { text.extend_from_slice(&b"\r\n"[..]); } else { text.extend_from_slice(&b"\n"[..]); } // Finally, consume this line. let l = prefixed_line.len(); self.source.consume(l); } // Trim the final newline, it is not part of the // message, but separates the signature marker from // the text. let c = text.pop(); assert!(c.is_none() || c == Some(b'\n')); if text.ends_with(b"\r") { text.pop(); } // Now, we have the whole text. // XXX: We optimistically assume that it is UTF-8 encoded. let mut literal = Literal::new(DataFormat::Unicode); literal.set_body(text); literal }; // Then, read and parse all the signatures. To that end, // we need to temporarily disable the CSF transformation, // and doing that will finalize the reader, which we'll // have to undo later on. self.csft = None; // We found the signature marker, now consume any armor // headers. self.read_headers()?; let mut sigs: Vec<Packet> = Vec::new(); let mut ppr = PacketParserBuilder::from_reader(self.by_ref())? .dearmor(Dearmor::Disabled) .build()?; while let PacketParserResult::Some(pp) = ppr { let (p, ppr_) = pp.next()?; match p { Packet::Signature(sig) => sigs.push(sig.into()), Packet::Marker(_) => (), Packet::Unknown(u) if u.tag() == Tag::Signature => sigs.push(u.into()), p => return Err(crate::Error::MalformedMessage( format!("Unexpected {} packet in \ cleartext signed message", p.tag())) .into()), } ppr = ppr_; } drop(ppr); assert!(self.finalized); // Now assemble an inline-signed message from the text // and components. // First, create one-pass-signature packets, and mark // the last of them as being the last. let mut opss = Vec::with_capacity(sigs.len()); for p in sigs.iter().rev() { if let Packet::Signature(sig) = p { if let Ok(ops) = OnePassSig::try_from(sig) { opss.push(ops); } } } if let Some(ops) = opss.last_mut() { ops.set_last(true); } // Now write everything out to our buffer. for ops in opss { Packet::from(ops).serialize(&mut self.decode_buffer)?; } Packet::from(literal).serialize(&mut self.decode_buffer)?; for p in sigs { p.serialize(&mut self.decode_buffer)?; } // We have placed the assembled message into our decode // buffer. Now revert the reader to a state so that the // caller can extract it. self.finalized = false; self.eof = false; self.csft = Some(CSFTransformer::Read); } let amount = cmp::min(buf.len(), self.decode_buffer.len()); buf[..amount].copy_from_slice(&self.decode_buffer[..amount]); crate::vec_drain_prefix(&mut self.decode_buffer, amount); Ok(amount) } /// The io::Read interface that the embedded generic reader uses /// to implement the BufferedReader protocol. fn do_read(&mut self, buf: &mut [u8]) -> Result<usize> { if ! self.initialized { self.initialize()?; } if buf.is_empty() { // Short-circuit here. Otherwise, we copy 0 bytes into // the buffer, which means we decoded 0 bytes, and we // wrongfully assume that we reached the end of the // armored block. return Ok(0); } if self.finalized { assert_eq!(self.decode_buffer.len(), 0); return Ok(0); } if self.csft.is_some() { self.read_clearsigned_message(buf) .map_err(|e| { match e.downcast::<io::Error>() { Ok(e) => e, Err(e) => io::Error::new(io::ErrorKind::Other, e), } }) } else { self.read_armored_data(buf) } } /// Return the buffer. Ensure that it contains at least `amount` /// bytes. // XXX: This is a verbatim copy of // buffered_reader::Generic::data_helper, the only modification is // that it uses the above do_read function. fn data_helper(&mut self, amount: usize, hard: bool, and_consume: bool) -> io::Result<&[u8]> { tracer!(TRACE, "armor::Reader::data_helper"); t!("amount: {}, hard: {}, and_consume: {} (cursor: {}, buffer: {:?})", amount, hard, and_consume, self.cursor, self.buffer.as_ref().map(|buffer| buffer.len())); if let Some(ref buffer) = self.buffer { // We have a buffer. Make sure `cursor` is sane. assert!(self.cursor <= buffer.len()); } else { // We don't have a buffer. Make sure cursor is 0. assert_eq!(self.cursor, 0); } let amount_buffered = self.buffer.as_ref().map(|b| b.len() - self.cursor).unwrap_or(0); if amount > amount_buffered { // The caller wants more data than we have readily // available. Read some more. let capacity : usize = amount.saturating_add( DEFAULT_BUF_SIZE.max( self.preferred_chunk_size.saturating_mul(2))); let mut buffer_new = self.unused_buffer.take() .map(|mut v| { vec_resize(&mut v, capacity); v }) .unwrap_or_else(|| vec![0u8; capacity]); let mut amount_read = 0; while amount_buffered + amount_read < amount { t!("Have {} bytes, need {} bytes", amount_buffered + amount_read, amount); if self.eof { t!("Hit EOF on the underlying reader, don't poll again."); break; } // See if there is an error from the last invocation. if let Some(e) = &self.error { t!("We have a stashed error, don't poll again: {}", e); break; } match self.do_read(&mut buffer_new [amount_buffered + amount_read..]) { Ok(read) => { t!("Read {} bytes", read); if read == 0 { self.eof = true; break; } else { amount_read += read; continue; } }, Err(ref err) if err.kind() == ErrorKind::Interrupted => continue, Err(err) => { // Don't return yet, because we may have // actually read something. self.error = Some(err); break; }, } } if amount_read > 0 { // We read something. if let Some(ref buffer) = self.buffer { // We need to copy in the old data. buffer_new[0..amount_buffered] .copy_from_slice( &buffer[self.cursor..self.cursor + amount_buffered]); } vec_truncate(&mut buffer_new, amount_buffered + amount_read); self.unused_buffer = self.buffer.take(); self.buffer = Some(buffer_new); self.cursor = 0; } } let amount_buffered = self.buffer.as_ref().map(|b| b.len() - self.cursor).unwrap_or(0); if self.error.is_some() { t!("Encountered an error: {}", self.error.as_ref().unwrap()); // An error occurred. If we have enough data to fulfill // the caller's request, then don't return the error. if hard && amount > amount_buffered { t!("Not enough data to fulfill request, returning error"); return Err(self.error.take().unwrap()); } if !hard && amount_buffered == 0 { t!("No data data buffered, returning error"); return Err(self.error.take().unwrap()); } } if hard && amount_buffered < amount { t!("Unexpected EOF"); Err(Error::new(ErrorKind::UnexpectedEof, "EOF")) } else if amount == 0 || amount_buffered == 0 { t!("Returning zero-length slice"); Ok(&b""[..]) } else { let buffer = self.buffer.as_ref().unwrap(); if and_consume { let amount_consumed = cmp::min(amount_buffered, amount); self.cursor += amount_consumed; assert!(self.cursor <= buffer.len()); t!("Consuming {} bytes, returning {} bytes", amount_consumed, buffer[self.cursor-amount_consumed..].len()); Ok(&buffer[self.cursor-amount_consumed..]) } else { t!("Returning {} bytes", buffer[self.cursor..].len()); Ok(&buffer[self.cursor..]) } } } } impl io::Read for Reader<'_> { fn read(&mut self, buf: &mut [u8]) -> Result<usize> { buffered_reader::buffered_reader_generic_read_impl(self, buf) } } impl BufferedReader<Cookie> for Reader<'_> { fn buffer(&self) -> &[u8] { if let Some(ref buffer) = self.buffer { &buffer[self.cursor..] } else { &b""[..] } } fn data(&mut self, amount: usize) -> Result<&[u8]> { self.data_helper(amount, false, false) } fn data_hard(&mut self, amount: usize) -> Result<&[u8]> { self.data_helper(amount, true, false) } fn consume(&mut self, amount: usize) -> &[u8] { // println!("Generic.consume({}) \ // (cursor: {}, buffer: {:?})", // amount, self.cursor, // if let Some(ref buffer) = self.buffer { Some(buffer.len()) } // else { None }); // The caller can't consume more than is buffered! if let Some(ref buffer) = self.buffer { assert!(self.cursor <= buffer.len()); assert!(amount <= buffer.len() - self.cursor, "buffer contains just {} bytes, but you are trying to \ consume {} bytes. Did you forget to call data()?", buffer.len() - self.cursor, amount); self.cursor += amount; return &self.buffer.as_ref().unwrap()[self.cursor - amount..]; } else { assert_eq!(amount, 0); &b""[..] } } fn data_consume(&mut self, amount: usize) -> Result<&[u8]> { self.data_helper(amount, false, true) } fn data_consume_hard(&mut self, amount: usize) -> Result<&[u8]> { self.data_helper(amount, true, true) } fn get_mut(&mut self) -> Option<&mut dyn BufferedReader<Cookie>> { Some(&mut self.source) } fn get_ref(&self) -> Option<&dyn BufferedReader<Cookie>> { Some(&self.source) } fn into_inner<'b>(self: Box<Self>) -> Option<Box<dyn BufferedReader<Cookie> + 'b>> where Self: 'b { Some(self.source) } fn cookie_set(&mut self, cookie: Cookie) -> Cookie { std::mem::replace(&mut self.cookie, cookie) } fn cookie_ref(&self) -> &Cookie { &self.cookie } fn cookie_mut(&mut self) -> &mut Cookie { &mut self.cookie } } /// Returns all character from Unicode's "Dash Punctuation" category. fn dashes() -> impl Iterator<Item = char> { ['\u{002D}', // - (Hyphen-Minus) '\u{058A}', // ֊ (Armenian Hyphen) '\u{05BE}', // ־ (Hebrew Punctuation Maqaf) '\u{1400}', // ᐀ (Canadian Syllabics Hyphen) '\u{1806}', // ᠆ (Mongolian Todo Soft Hyphen) '\u{2010}', // ‐ (Hyphen) '\u{2011}', // ‑ (Non-Breaking Hyphen) '\u{2012}', // ‒ (Figure Dash) '\u{2013}', // – (En Dash) '\u{2014}', // — (Em Dash) '\u{2015}', // ― (Horizontal Bar) '\u{2E17}', // ⸗ (Double Oblique Hyphen) '\u{2E1A}', // ⸚ (Hyphen with Diaeresis) '\u{2E3A}', // ⸺ (Two-Em Dash) '\u{2E3B}', // ⸻ (Three-Em Dash) '\u{2E40}', // ⹀ (Double Hyphen) '\u{301C}', // 〜 (Wave Dash) '\u{3030}', // 〰 (Wavy Dash) '\u{30A0}', // ゠ (Katakana-Hiragana Double Hyphen) '\u{FE31}', // ︱ (Presentation Form For Vertical Em Dash) '\u{FE32}', // ︲ (Presentation Form For Vertical En Dash) '\u{FE58}', // ﹘ (Small Em Dash) '\u{FE63}', // ﹣ (Small Hyphen-Minus) '\u{FF0D}', // - (Fullwidth Hyphen-Minus) ].iter().cloned() } /// Splits the given slice into a prefix of dashes and the rest. /// /// Accepts any character from Unicode's "Dash Punctuation" category. /// Assumes that the prefix containing the dashes is ASCII or UTF-8. fn dash_prefix(d: &[u8]) -> (&[u8], &[u8]) { // First, compute a valid UTF-8 prefix. let p = match std::str::from_utf8(d) { Ok(u) => u, Err(e) => std::str::from_utf8(&d[..e.valid_up_to()]) .expect("valid up to this point"), }; let mut prefix_len = 0; for c in p.chars() { // Keep going while we see characters from the Category "Dash // Punctuation". match c { '\u{002D}' // - (Hyphen-Minus) | '\u{058A}' // ֊ (Armenian Hyphen) | '\u{05BE}' // ־ (Hebrew Punctuation Maqaf) | '\u{1400}' // ᐀ (Canadian Syllabics Hyphen) | '\u{1806}' // ᠆ (Mongolian Todo Soft Hyphen) | '\u{2010}' // ‐ (Hyphen) | '\u{2011}' // ‑ (Non-Breaking Hyphen) | '\u{2012}' // ‒ (Figure Dash) | '\u{2013}' // – (En Dash) | '\u{2014}' // — (Em Dash) | '\u{2015}' // ― (Horizontal Bar) | '\u{2E17}' // ⸗ (Double Oblique Hyphen) | '\u{2E1A}' // ⸚ (Hyphen with Diaeresis) | '\u{2E3A}' // ⸺ (Two-Em Dash) | '\u{2E3B}' // ⸻ (Three-Em Dash) | '\u{2E40}' // ⹀ (Double Hyphen) | '\u{301C}' // 〜 (Wave Dash) | '\u{3030}' // 〰 (Wavy Dash) | '\u{30A0}' // ゠ (Katakana-Hiragana Double Hyphen) | '\u{FE31}' // ︱ (Presentation Form For Vertical Em Dash) | '\u{FE32}' // ︲ (Presentation Form For Vertical En Dash) | '\u{FE58}' // ﹘ (Small Em Dash) | '\u{FE63}' // ﹣ (Small Hyphen-Minus) | '\u{FF0D}' // - (Fullwidth Hyphen-Minus) => prefix_len += c.len_utf8(), _ => break, } } (&d[..prefix_len], &d[prefix_len..]) } #[cfg(test)] mod test { use std::io::{Cursor, Read, Write}; use super::Kind; use super::Writer; macro_rules! t { ( $path: expr ) => { include_bytes!(concat!("../tests/data/armor/", $path)) } } macro_rules! vectors { ( $prefix: expr, $suffix: expr ) => { &[t!(concat!($prefix, "-0", $suffix)), t!(concat!($prefix, "-1", $suffix)), t!(concat!($prefix, "-2", $suffix)), t!(concat!($prefix, "-3", $suffix)), t!(concat!($prefix, "-47", $suffix)), t!(concat!($prefix, "-48", $suffix)), t!(concat!($prefix, "-49", $suffix)), t!(concat!($prefix, "-50", $suffix)), t!(concat!($prefix, "-51", $suffix))] } } const TEST_BIN: &[&[u8]] = vectors!("test", ".bin"); const TEST_ASC: &[&[u8]] = vectors!("test", ".asc"); const LITERAL_BIN: &[&[u8]] = vectors!("literal", ".bin"); const LITERAL_ASC: &[&[u8]] = vectors!("literal", ".asc"); const LITERAL_NO_HEADER_ASC: &[&[u8]] = vectors!("literal", "-no-header.asc"); const LITERAL_NO_HEADER_WITH_CHKSUM_ASC: &[&[u8]] = vectors!("literal", "-no-header-with-chksum.asc"); const LITERAL_NO_NEWLINES_ASC: &[&[u8]] = vectors!("literal", "-no-newlines.asc"); #[test] fn enarmor() { for (i, (bin, asc)) in TEST_BIN.iter().zip(TEST_ASC.iter()).enumerate() { eprintln!("Test {}", i); let mut w = Writer::new(Vec::new(), Kind::File).unwrap(); w.write(&[]).unwrap(); // Avoid zero-length optimization. w.write_all(bin).unwrap(); let buf = w.finalize().unwrap(); assert_eq!(String::from_utf8_lossy(&buf), String::from_utf8_lossy(asc)); } } #[test] fn enarmor_bytewise() { for (bin, asc) in TEST_BIN.iter().zip(TEST_ASC.iter()) { let mut w = Writer::new(Vec::new(), Kind::File).unwrap(); w.write(&[]).unwrap(); // Avoid zero-length optimization. for b in bin.iter() { w.write(&[*b]).unwrap(); } let buf = w.finalize().unwrap(); assert_eq!(String::from_utf8_lossy(&buf), String::from_utf8_lossy(asc)); } } #[test] fn drop_writer() { // No ASCII frame shall be emitted if the writer is dropped // unused. assert!(Writer::new(Vec::new(), Kind::File).unwrap() .finalize().unwrap().is_empty()); // However, if the user insists, we will encode a zero-byte // string. let mut w = Writer::new(Vec::new(), Kind::File).unwrap(); w.write(&[]).unwrap(); let buf = w.finalize().unwrap(); assert_eq!( &buf[..], &b"-----BEGIN PGP ARMORED FILE-----\n\ \n\ =twTO\n\ -----END PGP ARMORED FILE-----\n"[..]); } use super::{Reader, ReaderMode}; #[test] fn dearmor_robust() { for (i, reference) in LITERAL_BIN.iter().enumerate() { for test in &[LITERAL_ASC[i], LITERAL_NO_HEADER_WITH_CHKSUM_ASC[i], LITERAL_NO_HEADER_ASC[i], LITERAL_NO_NEWLINES_ASC[i]] { let mut r = Reader::from_reader(Cursor::new(test), ReaderMode::VeryTolerant); let mut dearmored = Vec::<u8>::new(); r.read_to_end(&mut dearmored).unwrap(); assert_eq!(&dearmored, reference); } } } #[test] fn dearmor_binary() { for bin in TEST_BIN.iter() { let mut r = Reader::from_reader( Cursor::new(bin), ReaderMode::Tolerant(Some(Kind::Message))); let mut buf = [0; 5]; let e = r.read(&mut buf); assert!(e.is_err()); } } #[test] fn dearmor_wrong_kind() { let mut r = Reader::from_reader( Cursor::new(&include_bytes!("../tests/data/armor/test-0.asc")[..]), ReaderMode::Tolerant(Some(Kind::Message))); let mut buf = [0; 5]; let e = r.read(&mut buf); assert!(e.is_err()); } #[test] fn dearmor_wrong_crc() { let mut r = Reader::from_reader( Cursor::new( &include_bytes!("../tests/data/armor/test-0.bad-crc.asc")[..]), ReaderMode::Tolerant(Some(Kind::File))); let mut buf = [0; 5]; let e = r.read(&mut buf); // Quoting RFC4880++: // // > An implementation MUST NOT reject an OpenPGP object when // > the CRC24 footer is present, missing, malformed, or // > disagrees with the computed CRC24 sum. assert!(e.is_ok()); } #[test] fn dearmor_wrong_footer() { let mut r = Reader::from_reader( Cursor::new( &include_bytes!("../tests/data/armor/test-2.bad-footer.asc")[..] ), ReaderMode::Tolerant(Some(Kind::File))); let mut read = 0; loop { let mut buf = [0; 5]; match r.read(&mut buf) { Ok(0) => panic!("Reached EOF, but expected an error!"), Ok(r) => read += r, Err(_) => break, } } assert!(read <= 2); } #[test] fn dearmor_no_crc() { let mut r = Reader::from_reader( Cursor::new( &include_bytes!("../tests/data/armor/test-1.no-crc.asc")[..]), ReaderMode::Tolerant(Some(Kind::File))); let mut buf = [0; 5]; let e = r.read(&mut buf); assert!(e.unwrap() == 1 && buf[0] == 0xde); } #[test] fn dearmor_with_header() { let mut r = Reader::from_reader( Cursor::new( &include_bytes!("../tests/data/armor/test-3.with-headers.asc")[..] ), ReaderMode::Tolerant(Some(Kind::File))); assert_eq!(r.headers().unwrap(), &[("Comment".into(), "Some Header".into()), ("Comment".into(), "Another one".into())]); let mut buf = [0; 5]; let e = r.read(&mut buf); assert!(e.is_ok()); assert_eq!(e.unwrap(), 3); assert_eq!(&buf[..3], TEST_BIN[3]); } #[test] fn dearmor_any() { let mut r = Reader::from_reader( Cursor::new( &include_bytes!("../tests/data/armor/test-3.with-headers.asc")[..] ), ReaderMode::VeryTolerant); let mut buf = [0; 5]; let e = r.read(&mut buf); assert_eq!(r.kind(), Some(Kind::File)); assert!(e.is_ok()); assert_eq!(e.unwrap(), 3); assert_eq!(&buf[..3], TEST_BIN[3]); } #[test] fn dearmor_with_garbage() { let armored = include_bytes!("../tests/data/armor/test-3.with-headers.asc"); // Slap some garbage in front and make sure it still reads ok. let mut b: Vec<u8> = "Some\ngarbage\nlines\n\t\r ".into(); b.extend_from_slice(armored); let mut r = Reader::from_reader(Cursor::new(b), ReaderMode::VeryTolerant); let mut buf = [0; 5]; let e = r.read(&mut buf); assert_eq!(r.kind(), Some(Kind::File)); assert!(e.is_ok()); assert_eq!(e.unwrap(), 3); assert_eq!(&buf[..3], TEST_BIN[3]); // Again, but this time add a non-whitespace character in the // line of the header. let mut b: Vec<u8> = "Some\ngarbage\nlines\n\t.\r ".into(); b.extend_from_slice(armored); let mut r = Reader::from_reader(Cursor::new(b), ReaderMode::VeryTolerant); let mut buf = [0; 5]; let e = r.read(&mut buf); assert!(e.is_err()); } #[test] fn dearmor() { for (bin, asc) in TEST_BIN.iter().zip(TEST_ASC.iter()) { let mut r = Reader::from_reader( Cursor::new(asc), ReaderMode::Tolerant(Some(Kind::File))); let mut dearmored = Vec::<u8>::new(); r.read_to_end(&mut dearmored).unwrap(); assert_eq!(&dearmored, bin); } } #[test] fn dearmor_bytewise() { for (bin, asc) in TEST_BIN.iter().zip(TEST_ASC.iter()) { let r = Reader::from_reader( Cursor::new(asc), ReaderMode::Tolerant(Some(Kind::File))); let mut dearmored = Vec::<u8>::new(); for c in r.bytes() { dearmored.push(c.unwrap()); } assert_eq!(&dearmored, bin); } } #[test] fn dearmor_yuge() { let yuge_key = crate::tests::key("yuge-key-so-yuge-the-yugest.asc"); let mut r = Reader::from_reader(Cursor::new(yuge_key), ReaderMode::VeryTolerant); let mut dearmored = Vec::<u8>::new(); r.read_to_end(&mut dearmored).unwrap(); let r = Reader::from_reader(Cursor::new(yuge_key), ReaderMode::VeryTolerant); let mut dearmored = Vec::<u8>::new(); for c in r.bytes() { dearmored.push(c.unwrap()); } } #[test] fn dearmor_quoted() { let mut r = Reader::from_reader( Cursor::new( &include_bytes!("../tests/data/armor/test-3.with-headers-quoted.asc")[..] ), ReaderMode::VeryTolerant); let mut buf = [0; 5]; let e = r.read(&mut buf); assert_eq!(r.kind(), Some(Kind::File)); assert!(e.is_ok()); assert_eq!(e.unwrap(), 3); assert_eq!(&buf[..3], TEST_BIN[3]); } #[test] fn dearmor_quoted_stripped() { let mut r = Reader::from_reader( Cursor::new( &include_bytes!("../tests/data/armor/test-3.with-headers-quoted-stripped.asc")[..] ), ReaderMode::VeryTolerant); let mut buf = [0; 5]; let e = r.read(&mut buf); assert_eq!(r.kind(), Some(Kind::File)); assert!(e.is_ok()); assert_eq!(e.unwrap(), 3); assert_eq!(&buf[..3], TEST_BIN[3]); } #[test] fn dearmor_quoted_a_lot() { let mut r = Reader::from_reader( Cursor::new( &include_bytes!("../tests/data/armor/test-3.with-headers-quoted-a-lot.asc")[..] ), ReaderMode::VeryTolerant); let mut buf = [0; 5]; let e = r.read(&mut buf); assert_eq!(r.kind(), Some(Kind::File)); assert!(e.is_ok()); assert_eq!(e.unwrap(), 3); assert_eq!(&buf[..3], TEST_BIN[3]); } #[test] fn dearmor_quoted_badly() { let mut r = Reader::from_reader( Cursor::new( &include_bytes!("../tests/data/armor/test-3.with-headers-quoted-badly.asc")[..] ), ReaderMode::VeryTolerant); let mut buf = [0; 5]; let e = r.read(&mut buf); assert!(e.is_err()); } quickcheck! { fn roundtrip(kind: Kind, payload: Vec<u8>) -> bool { if payload.is_empty() { // Empty payloads do not emit an armor framing unless // one does an explicit empty write (and .write_all() // does not). return true; } let mut w = Writer::new(Vec::new(), kind).unwrap(); w.write_all(&payload).unwrap(); let encoded = w.finalize().unwrap(); let mut recovered = Vec::new(); Reader::from_reader(Cursor::new(&encoded), ReaderMode::Tolerant(Some(kind))) .read_to_end(&mut recovered) .unwrap(); let mut recovered_any = Vec::new(); Reader::from_reader(Cursor::new(&encoded), ReaderMode::VeryTolerant) .read_to_end(&mut recovered_any) .unwrap(); payload == recovered && payload == recovered_any } } /// Tests issue #404, zero-sized reads break reader. /// /// See: https://gitlab.com/sequoia-pgp/sequoia/-/issues/404 #[test] fn zero_sized_read() { let mut r = Reader::from_bytes(crate::tests::file("armor/test-1.asc"), None); let mut buf = Vec::new(); r.read(&mut buf).unwrap(); r.read(&mut buf).unwrap(); } /// Crash in armor parser due to indexing not aligned with UTF-8 /// characters. /// /// See: https://gitlab.com/sequoia-pgp/sequoia/-/issues/515 #[test] fn issue_515() { let data = [63, 9, 45, 10, 45, 10, 45, 45, 45, 45, 45, 66, 69, 71, 73, 78, 32, 80, 71, 80, 32, 77, 69, 83, 83, 65, 71, 69, 45, 45, 45, 45, 45, 45, 152, 152, 152, 152, 152, 152, 255, 29, 152, 152, 152, 152, 152, 152, 152, 152, 152, 152, 10, 91, 45, 10, 45, 14, 0, 36, 0, 0, 30, 122, 4, 2, 204, 152]; let mut reader = Reader::from_bytes(&data[..], None); let mut buf = Vec::new(); // `data` is malformed, expect an error. reader.read_to_end(&mut buf).unwrap_err(); } /// Crash in armor parser due to improper use of the buffered /// reader protocol when consuming quoting prefix. /// /// See: https://gitlab.com/sequoia-pgp/sequoia/-/issues/516 #[test] fn issue_516() { let data = [ 144, 32, 19, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 125, 13, 125, 125, 93, 125, 125, 93, 125, 13, 13, 125, 125, 45, 45, 45, 45, 45, 66, 69, 71, 73, 78, 32, 80, 71, 80, 32, 77, 69, 83, 83, 65, 71, 69, 45, 45, 45, 45, 45, 125, 13, 125, 125, 93, 125, 125, 93, 125, 13, 13, 125, 125, 45, 0, 0, 0, 0, 0, 0, 0, 0, 125, 205, 21, 1, 21, 21, 21, 1, 1, 1, 1, 21, 149, 21, 21, 21, 21, 32, 4, 141, 141, 141, 141, 202, 74, 11, 125, 8, 21, 50, 50, 194, 48, 147, 93, 174, 23, 23, 23, 23, 23, 23, 147, 147, 147, 23, 23, 23, 23, 23, 23, 48, 125, 125, 93, 125, 13, 125, 125, 125, 93, 125, 125, 13, 13, 125, 125, 13, 13, 93, 125, 13, 125, 45, 125, 125, 45, 45, 66, 69, 71, 73, 78, 32, 80, 71, 45, 45, 125, 10, 45, 45, 0, 0, 10, 45, 45, 210, 10, 0, 0, 87, 0, 0, 0, 150, 10, 0, 0, 241, 87, 45, 0, 0, 121, 121, 10, 10, 21, 58]; let mut reader = Reader::from_bytes(&data[..], None); let mut buf = Vec::new(); // `data` is malformed, expect an error. reader.read_to_end(&mut buf).unwrap_err(); } /// Crash in armor parser due to improper use of the buffered /// reader protocol when consuming quoting prefix. /// /// See: https://gitlab.com/sequoia-pgp/sequoia/-/issues/517 #[test] fn issue_517() { let data = [13, 45, 45, 45, 45, 45, 66, 69, 71, 73, 78, 32, 80, 71, 80, 32, 77, 69, 83, 83, 65, 71, 69, 45, 45, 45, 45, 45, 10, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 139]; let mut reader = Reader::from_bytes(&data[..], None); let mut buf = Vec::new(); // `data` is malformed, expect an error. reader.read_to_end(&mut buf).unwrap_err(); } #[test] fn common_prefix() { use super::common_prefix as cp; assert_eq!(cp("", ""), 0); assert_eq!(cp("a", ""), 0); assert_eq!(cp("", "a"), 0); assert_eq!(cp("a", "a"), 1); assert_eq!(cp("aa", "a"), 1); assert_eq!(cp("a", "aa"), 1); assert_eq!(cp("ac", "ab"), 1); } /// A certificate was mangled turning -- into n-dash, --- into /// m-dash. Fun with Unicode. #[test] fn issue_610() { let mut buf = Vec::new(); // First, we now accept any dash character, not only '-'. let mut reader = Reader::from_bytes( crate::tests::file("armor/test-3.unicode-dashes.asc"), None); reader.read_to_end(&mut buf).unwrap(); // Second, the transformation changed the number of dashes. let mut reader = Reader::from_bytes( crate::tests::file("armor/test-3.unbalanced-dashes.asc"), None); reader.read_to_end(&mut buf).unwrap(); // Third, as it is not about the dashes, we even accept none. let mut reader = Reader::from_bytes( crate::tests::file("armor/test-3.no-dashes.asc"), None); reader.read_to_end(&mut buf).unwrap(); } /// Tests the transformation of a cleartext signed message into a /// signed message. /// /// This test is merely concerned with the transformation, not /// with the signature verification. #[test] fn cleartext_signed_message() -> crate::Result<()> { use crate::{ Packet, parse::Parse, types::HashAlgorithm, }; fn f<R>(clearsig: &[u8], reference: R, hash: HashAlgorithm) -> crate::Result<()> where R: AsRef<[u8]> { let mut reader = Reader::from_cookie_reader_csft( Box::new(buffered_reader::Memory::with_cookie( clearsig, Default::default())), None, Default::default(), true); let mut buf = Vec::new(); reader.read_to_end(&mut buf)?; let message = crate::Message::from_bytes(&buf)?; assert_eq!(message.packets().children().count(), 3); // First, an one-pass-signature packet. if let Some(Packet::OnePassSig(ops)) = message.packets().path_ref(&[0]) { assert_eq!(ops.hash_algo(), hash); } else { panic!("expected an OPS packet"); } // A literal packet. assert_eq!(message.body().unwrap().body(), reference.as_ref()); // And, the signature. if let Some(Packet::Signature(sig)) = message.packets().path_ref(&[2]) { assert_eq!(sig.hash_algo(), hash); } else { panic!("expected an signature packet"); } // If we parse it without enabling the CSF transformation, // we should only find the signature. let mut reader = Reader::from_cookie_reader_csft( Box::new(buffered_reader::Memory::with_cookie( clearsig, Default::default())), None, Default::default(), false); let mut buf = Vec::new(); reader.read_to_end(&mut buf)?; let pp = crate::PacketPile::from_bytes(&buf)?; assert_eq!(pp.children().count(), 1); // The signature. if let Some(Packet::Signature(sig)) = pp.path_ref(&[0]) { assert_eq!(sig.hash_algo(), hash); } else { panic!("expected an signature packet"); } Ok(()) } f(crate::tests::message("a-problematic-poem.txt.cleartext.sig"), { // The test vector, created by GnuPG, does not preserve // the final newline. let mut reference = crate::tests::message("a-problematic-poem.txt").to_vec(); assert_eq!(reference.pop(), Some(b'\n')); reference }, HashAlgorithm::SHA256)?; f(crate::tests::file("crypto-refresh/cleartext-signed-message.txt"), crate::tests::file("crypto-refresh/cleartext-signed-message.txt.plain"), HashAlgorithm::SHA512)?; Ok(()) } } �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/src/cert/amalgamation/iter.rs�������������������������������������������������0000644�0000000�0000000�00000030424�10461020230�0020431�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������use std::slice; use std::fmt; use std::time::SystemTime; use crate::{ types::RevocationStatus, cert::prelude::*, packet::{ Unknown, UserAttribute, UserID, }, policy::Policy, }; /// An iterator over components. /// /// Using the [`ComponentAmalgamationIter::with_policy`], it is /// possible to change the iterator to only return /// [`ComponentAmalgamation`]s for valid components. In this case, /// `ComponentAmalgamationIter::with_policy` transforms the /// `ComponentAmalgamationIter` into a /// [`ValidComponentAmalgamationIter`], which returns /// [`ValidComponentAmalgamation`]s. `ValidComponentAmalgamation` /// offers additional filters. /// /// `ComponentAmalgamationIter` follows the builder pattern. There is /// no need to explicitly finalize it: it already implements the /// `Iterator` trait. /// /// A `ComponentAmalgamationIter` is returned by [`Cert::userids`], /// [`Cert::user_attributes`], and [`Cert::unknowns`]. /// ([`Cert::keys`] returns a [`KeyAmalgamationIter`].) /// /// # Examples /// /// Iterate over the User IDs in a certificate: /// /// ``` /// # use sequoia_openpgp as openpgp; /// use openpgp::cert::prelude::*; /// /// # /// # fn main() -> openpgp::Result<()> { /// # let (cert, _) = /// # CertBuilder::general_purpose(Some("alice@example.org")) /// # .generate()?; /// # let fpr = cert.fingerprint(); /// // Iterate over all User IDs. /// for ua in cert.userids() { /// // ua is a `ComponentAmalgamation`, specifically, a `UserIDAmalgamation`. /// } /// # Ok(()) /// # } /// ``` /// /// Only return valid User IDs. /// /// ``` /// # use sequoia_openpgp as openpgp; /// use openpgp::cert::prelude::*; /// use openpgp::policy::StandardPolicy; /// # /// # fn main() -> openpgp::Result<()> { /// let p = &StandardPolicy::new(); /// /// # let (cert, _) = /// # CertBuilder::general_purpose(Some("alice@example.org")) /// # .generate()?; /// # let fpr = cert.fingerprint(); /// // Iterate over all valid User IDs. /// for ua in cert.userids().with_policy(p, None) { /// // ua is a `ValidComponentAmalgamation`, specifically, a /// // `ValidUserIDAmalgamation`. /// } /// # Ok(()) /// # } /// ``` /// /// [`ComponentAmalgamationIter::with_policy`]: ComponentAmalgamationIter::with_policy() /// [`Cert::userids`]: super::Cert::userids() /// [`Cert::user_attributes`]: super::Cert::user_attributes() /// [`Cert::unknowns`]: super::Cert::unknowns() /// [`Cert::keys`]: super::Cert::keys() pub struct ComponentAmalgamationIter<'a, C> { cert: &'a Cert, iter: slice::Iter<'a, ComponentBundle<C>>, } assert_send_and_sync!(ComponentAmalgamationIter<'_, C> where C); /// An iterator over `UserIDAmalgamtion`s. /// /// A specialized version of [`ComponentAmalgamationIter`]. /// pub type UserIDAmalgamationIter<'a> = ComponentAmalgamationIter<'a, UserID>; /// An iterator over `UserAttributeAmalgamtion`s. /// /// A specialized version of [`ComponentAmalgamationIter`]. /// pub type UserAttributeAmalgamationIter<'a> = ComponentAmalgamationIter<'a, UserAttribute>; /// An iterator over `UnknownComponentAmalgamtion`s. /// /// A specialized version of [`ComponentAmalgamationIter`]. /// pub type UnknownComponentAmalgamationIter<'a> = ComponentAmalgamationIter<'a, Unknown>; impl<'a, C> fmt::Debug for ComponentAmalgamationIter<'a, C> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.debug_struct("ComponentAmalgamationIter") .finish() } } impl<'a, C> Iterator for ComponentAmalgamationIter<'a, C> { type Item = ComponentAmalgamation<'a, C>; fn next(&mut self) -> Option<Self::Item> { self.iter.next().map(|c| ComponentAmalgamation::new(self.cert, c)) } } impl<'a, C> ComponentAmalgamationIter<'a, C> { /// Returns a new `ComponentAmalgamationIter` instance. pub(crate) fn new(cert: &'a Cert, iter: std::slice::Iter<'a, ComponentBundle<C>>) -> Self where Self: 'a { ComponentAmalgamationIter { cert, iter, } } /// Changes the iterator to only return components that are valid /// according to the policy at the specified time. /// /// If `time` is None, then the current time is used. /// /// Refer to the [`ValidateAmalgamation`] trait for a definition /// of a valid component. /// /// [`ValidateAmalgamation`]: super::ValidateAmalgamation /// /// # Examples /// /// ``` /// # use sequoia_openpgp as openpgp; /// use openpgp::cert::prelude::*; /// use openpgp::policy::StandardPolicy; /// # /// # fn main() -> openpgp::Result<()> { /// let p = &StandardPolicy::new(); /// /// # let (cert, _) = /// # CertBuilder::general_purpose(Some("alice@example.org")) /// # .generate()?; /// # let fpr = cert.fingerprint(); /// // Iterate over all valid User Attributes. /// for ua in cert.user_attributes().with_policy(p, None) { /// // ua is a `ValidComponentAmalgamation`, specifically, a /// // `ValidUserAttributeAmalgamation`. /// } /// # Ok(()) /// # } /// ``` /// pub fn with_policy<T>(self, policy: &'a dyn Policy, time: T) -> ValidComponentAmalgamationIter<'a, C> where T: Into<Option<SystemTime>> { ValidComponentAmalgamationIter { cert: self.cert, iter: self.iter, time: time.into().unwrap_or_else(crate::now), policy, revoked: None, } } } /// An iterator over valid components. /// /// A `ValidComponentAmalgamationIter` is a /// [`ComponentAmalgamationIter`] with a policy and a reference time. /// /// This allows it to filter the returned components based on /// information available in the components' binding signatures. For /// instance, [`ValidComponentAmalgamationIter::revoked`] filters the /// returned components by whether they are revoked. /// /// `ValidComponentAmalgamationIter` follows the builder pattern. /// There is no need to explicitly finalize it: it already implements /// the `Iterator` trait. /// /// A `ValidComponentAmalgamationIter` is returned by /// [`ComponentAmalgamationIter::with_policy`]. /// /// # Examples /// /// ``` /// # use sequoia_openpgp as openpgp; /// use openpgp::cert::prelude::*; /// use openpgp::policy::StandardPolicy; /// # /// # fn main() -> openpgp::Result<()> { /// let p = &StandardPolicy::new(); /// /// # let (cert, _) = /// # CertBuilder::general_purpose(Some("alice@example.org")) /// # .generate()?; /// # let fpr = cert.fingerprint(); /// // Iterate over all valid User Attributes. /// for ua in cert.userids().with_policy(p, None) { /// // ua is a `ValidComponentAmalgamation`, specifically, a /// // `ValidUserIDAmalgamation`. /// } /// # Ok(()) /// # } /// ``` /// /// [`ValidComponentAmalgamationIter::revoked`]: ValidComponentAmalgamationIter::revoked() /// [`ComponentAmalgamationIter::with_policy`]: ComponentAmalgamationIter::with_policy() pub struct ValidComponentAmalgamationIter<'a, C> { // This is an option to make it easier to create an empty ValidComponentAmalgamationIter. cert: &'a Cert, iter: slice::Iter<'a, ComponentBundle<C>>, policy: &'a dyn Policy, // The time. time: SystemTime, // If not None, filters by whether the component is revoked or not // at time `t`. revoked: Option<bool>, } assert_send_and_sync!(ValidComponentAmalgamationIter<'_, C> where C); /// An iterator over `ValidUserIDAmalgamtion`s. /// /// This is just a specialized version of `ValidComponentAmalgamationIter`. pub type ValidUserIDAmalgamationIter<'a> = ValidComponentAmalgamationIter<'a, UserID>; /// An iterator over `ValidUserAttributeAmalgamtion`s. /// /// This is just a specialized version of `ValidComponentAmalgamationIter`. pub type ValidUserAttributeAmalgamationIter<'a> = ValidComponentAmalgamationIter<'a, UserAttribute>; impl<'a, C> fmt::Debug for ValidComponentAmalgamationIter<'a, C> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.debug_struct("ValidComponentAmalgamationIter") .field("time", &self.time) .field("revoked", &self.revoked) .finish() } } impl<'a, C> Iterator for ValidComponentAmalgamationIter<'a, C> where C: std::fmt::Debug + Send + Sync, { type Item = ValidComponentAmalgamation<'a, C>; fn next(&mut self) -> Option<Self::Item> { tracer!(false, "ValidComponentAmalgamationIter::next", 0); t!("ValidComponentAmalgamationIter: {:?}", self); loop { let ca = ComponentAmalgamation::new(self.cert, self.iter.next()?); t!("Considering component: {:?}", ca.component()); let vca = match ca.with_policy(self.policy, self.time) { Ok(vca) => vca, Err(e) => { t!("Rejected: {}", e); continue; }, }; if let Some(want_revoked) = self.revoked { if let RevocationStatus::Revoked(_) = vca.revocation_status() { // The component is definitely revoked. if ! want_revoked { t!("Component revoked... skipping."); continue; } } else { // The component is probably not revoked. if want_revoked { t!("Component not revoked... skipping."); continue; } } } return Some(vca); } } } impl<'a, C> ExactSizeIterator for ComponentAmalgamationIter<'a, C> { fn len(&self) -> usize { self.iter.len() } } impl<'a, C> ValidComponentAmalgamationIter<'a, C> { /// Filters by whether a component is definitely revoked. /// /// A value of None disables this filter. /// /// If you call this function multiple times on the same iterator, /// only the last value is used. /// /// This filter only checks if the component is not revoked; it /// does not check whether the certificate not revoked. /// /// This filter checks whether a component's revocation status is /// [`RevocationStatus::Revoked`] or not. The latter (i.e., /// `revoked(false)`) is equivalent to: /// /// ```rust /// use sequoia_openpgp as openpgp; /// # use openpgp::Result; /// use openpgp::cert::prelude::*; /// use openpgp::types::RevocationStatus; /// use sequoia_openpgp::policy::StandardPolicy; /// /// # fn main() -> Result<()> { /// let p = &StandardPolicy::new(); /// /// # let (cert, _) = /// # CertBuilder::general_purpose(Some("alice@example.org")) /// # .generate()?; /// # let timestamp = None; /// let non_revoked_uas = cert /// .user_attributes() /// .with_policy(p, timestamp) /// .filter(|ca| { /// match ca.revocation_status() { /// RevocationStatus::Revoked(_) => /// // It's definitely revoked, skip it. /// false, /// RevocationStatus::CouldBe(_) => /// // There is a designated revoker that we /// // should check, but don't (or can't). To /// // avoid a denial of service arising from fake /// // revocations, we assume that the component has not /// // been revoked and return it. /// true, /// RevocationStatus::NotAsFarAsWeKnow => /// // We have no evidence to suggest that the component /// // is revoked. /// true, /// } /// }) /// .collect::<Vec<_>>(); /// # Ok(()) /// # } /// ``` /// /// As the example shows, this filter is significantly less /// flexible than using `ValidComponentAmalgamation::revocation_status`. /// However, this filter implements a typical policy, and does not /// preclude using `filter` to realize alternative policies. /// /// [`RevocationStatus::Revoked`]: crate::types::RevocationStatus::Revoked pub fn revoked<T>(mut self, revoked: T) -> Self where T: Into<Option<bool>> { self.revoked = revoked.into(); self } } ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/src/cert/amalgamation/key/iter.rs���������������������������������������������0000644�0000000�0000000�00000172767�10461020230�0021242�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������use std::fmt; use std::convert::TryInto; use std::time::SystemTime; use std::borrow::Borrow; use std::slice; use crate::{ KeyHandle, types::RevocationStatus, packet::key, types::KeyFlags, cert::prelude::*, policy::Policy, }; /// Evaluates whether to skip the use of a secret key when filtering. /// /// This function takes three optional boolean values, indicating whether /// an encrypted secret is present, /// whether to filter for encrypted secret keys /// and whether to filter for unencrypted secret keys. /// /// An optional string provides context in the case where a key is skipped. fn skip_secret( have_secret_encrypted: Option<bool>, want_encrypted_key: Option<bool>, want_unencrypted_secret: Option<bool>, ) -> Option<&'static str> { match (have_secret_encrypted, want_encrypted_key, want_unencrypted_secret) { (Some(_), Some(true), Some(true)) | (_, None, None) | (Some(true), Some(true), _) | (Some(true), None, Some(false)) | (Some(false), _, Some(true)) | (Some(false), Some(false), None) => None, // do not skip the key (Some(true), Some(false), _) | (Some(true), None, Some(true)) => { Some("Encrypted secret... skipping.") } (Some(false), _, Some(false)) | (Some(false), Some(true), None) => { Some("Unencrypted secret... skipping.") } (None, Some(_), None) | (None, None, Some(_)) | (None, Some(_), Some(_)) => { Some("No secret... skipping.") } } } /// An iterator over `Key`s. /// /// An iterator over [`KeyAmalgamation`]s. /// /// A `KeyAmalgamationIter` is like a [`ComponentAmalgamationIter`], /// but specialized for keys. Refer to the [module documentation] for /// an explanation of why a different type is necessary. /// /// Using the [`KeyAmalgamationIter::with_policy`], it is possible to /// change the iterator to only return [`KeyAmalgamation`]s for valid /// `Key`s. In this case, `KeyAmalgamationIter::with_policy` /// transforms the `KeyAmalgamationIter` into a /// [`ValidKeyAmalgamationIter`], which returns /// [`ValidKeyAmalgamation`]s. `ValidKeyAmalgamation` offers /// additional filters. /// /// `KeyAmalgamationIter` supports other filters. For instance /// [`KeyAmalgamationIter::secret`] filters on whether secret key /// material is present, [`KeyAmalgamationIter::encrypted_secret`] /// filters on whether secret key material is present and encrypted, and /// [`KeyAmalgamationIter::unencrypted_secret`] filters on whether /// secret key material is present and unencrypted. Of course, since /// `KeyAmalgamationIter` implements `Iterator`, it is possible to use /// [`Iterator::filter`] to implement custom filters. /// /// `KeyAmalgamationIter` follows the builder pattern. There is no /// need to explicitly finalize it: it already implements the /// `Iterator` trait. /// /// A `KeyAmalgamationIter` is returned by [`Cert::keys`]. /// /// [`ComponentAmalgamationIter`]: super::super::ComponentAmalgamationIter /// [module documentation]: super /// [`KeyAmalgamationIter::with_policy`]: super::ValidateAmalgamation /// [`KeyAmalgamationIter::secret`]: KeyAmalgamationIter::secret() /// [`KeyAmalgamationIter::unencrypted_secret`]: KeyAmalgamationIter::unencrypted_secret() /// [`Iterator::filter`]: std::iter::Iterator::filter() /// [`Cert::keys`]: super::super::Cert::keys() pub struct KeyAmalgamationIter<'a, P, R> where P: key::KeyParts, R: key::KeyRole, { // This is an option to make it easier to create an empty KeyAmalgamationIter. cert: Option<&'a Cert>, primary: bool, subkey_iter: slice::Iter<'a, KeyBundle<key::PublicParts, key::SubordinateRole>>, // If not None, filters by whether a key has an encrypted secret. encrypted_secret: Option<bool>, // If not None, filters by whether a key has an unencrypted // secret. unencrypted_secret: Option<bool>, /// Only return keys in this set. key_handles: Vec<KeyHandle>, // If not None, filters by whether we support the key's asymmetric // algorithm. supported: Option<bool>, _p: std::marker::PhantomData<P>, _r: std::marker::PhantomData<R>, } assert_send_and_sync!(KeyAmalgamationIter<'_, P, R> where P: key::KeyParts, R: key::KeyRole, ); impl<'a, P, R> fmt::Debug for KeyAmalgamationIter<'a, P, R> where P: key::KeyParts, R: key::KeyRole, { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.debug_struct("KeyAmalgamationIter") .field("encrypted_secret", &self.encrypted_secret) .field("unencrypted_secret", &self.unencrypted_secret) .field("key_handles", &self.key_handles) .field("supported", &self.supported) .finish() } } macro_rules! impl_iterator { ($parts:path, $role:path, $item:ty) => { impl<'a> Iterator for KeyAmalgamationIter<'a, $parts, $role> { type Item = $item; fn next(&mut self) -> Option<Self::Item> { // We unwrap the result of the conversion. But, this // is safe by construction: next_common only returns // keys that can be correctly converted. self.next_common().map(|k| k.try_into().expect("filtered")) } } } } impl_iterator!(key::PublicParts, key::PrimaryRole, PrimaryKeyAmalgamation<'a, key::PublicParts>); impl_iterator!(key::SecretParts, key::PrimaryRole, PrimaryKeyAmalgamation<'a, key::SecretParts>); impl_iterator!(key::UnspecifiedParts, key::PrimaryRole, PrimaryKeyAmalgamation<'a, key::UnspecifiedParts>); impl_iterator!(key::PublicParts, key::SubordinateRole, SubordinateKeyAmalgamation<'a, key::PublicParts>); impl_iterator!(key::SecretParts, key::SubordinateRole, SubordinateKeyAmalgamation<'a, key::SecretParts>); impl_iterator!(key::UnspecifiedParts, key::SubordinateRole, SubordinateKeyAmalgamation<'a, key::UnspecifiedParts>); impl_iterator!(key::PublicParts, key::UnspecifiedRole, ErasedKeyAmalgamation<'a, key::PublicParts>); impl_iterator!(key::SecretParts, key::UnspecifiedRole, ErasedKeyAmalgamation<'a, key::SecretParts>); impl_iterator!(key::UnspecifiedParts, key::UnspecifiedRole, ErasedKeyAmalgamation<'a, key::UnspecifiedParts>); impl<'a, P, R> KeyAmalgamationIter<'a, P, R> where P: key::KeyParts, R: key::KeyRole, { fn next_common(&mut self) -> Option<ErasedKeyAmalgamation<'a, key::PublicParts>> { tracer!(false, "KeyAmalgamationIter::next", 0); t!("KeyAmalgamationIter: {:?}", self); let cert = self.cert?; loop { let ka : ErasedKeyAmalgamation<key::PublicParts> = if ! self.primary { self.primary = true; PrimaryKeyAmalgamation::new(cert).into() } else { SubordinateKeyAmalgamation::new( cert, self.subkey_iter.next()?).into() }; t!("Considering key: {:?}", ka.key()); if ! self.key_handles.is_empty() && ! self.key_handles.iter() .any(|h| h.aliases(ka.key().key_handle())) { t!("{} is not one of the keys that we are looking for ({:?})", ka.key().fingerprint(), self.key_handles); continue; } if let Some(want_supported) = self.supported { if ka.key().pk_algo().is_supported() { // It is supported. if ! want_supported { t!("PK algo is supported... skipping."); continue; } } else if want_supported { t!("PK algo is not supported... skipping."); continue; } } if let Some(msg) = skip_secret( ka.key().optional_secret().map(|x| x.is_encrypted()), self.encrypted_secret, self.unencrypted_secret, ) { t!(msg); continue; } return Some(ka); } } } impl<'a, P, R> KeyAmalgamationIter<'a, P, R> where P: key::KeyParts, R: key::KeyRole, { /// Returns a new `KeyAmalgamationIter` instance. pub(crate) fn new(cert: &'a Cert) -> Self where Self: 'a { KeyAmalgamationIter { cert: Some(cert), primary: false, subkey_iter: cert.subkeys.iter(), // The filters. encrypted_secret: None, unencrypted_secret: None, key_handles: Default::default(), supported: None, _p: std::marker::PhantomData, _r: std::marker::PhantomData, } } /// Changes the iterator to only return keys with secret key /// material. /// /// # Examples /// /// ```rust /// # use sequoia_openpgp as openpgp; /// # use openpgp::Result; /// # use openpgp::cert::prelude::*; /// # fn main() -> Result<()> { /// # let (cert, _) = /// # CertBuilder::new().set_password(Some("password".into())) /// # .generate()?; /// for ka in cert.keys().secret() { /// // Use it. /// } /// # assert!(cert.keys().secret().count() == 1); /// # /// # let (cert, _) = CertBuilder::new().generate()?; /// # assert!(cert.keys().secret().count() == 1); /// # Ok(()) /// # } /// ``` pub fn secret(self) -> KeyAmalgamationIter<'a, key::SecretParts, R> { KeyAmalgamationIter { cert: self.cert, primary: self.primary, subkey_iter: self.subkey_iter, // The filters. encrypted_secret: Some(true), unencrypted_secret: Some(true), key_handles: self.key_handles, supported: self.supported, _p: std::marker::PhantomData, _r: std::marker::PhantomData, } } /// Changes the iterator to only return keys with encrypted secret key /// material. /// /// # Examples /// /// ```rust /// # use sequoia_openpgp as openpgp; /// # use openpgp::Result; /// # use openpgp::cert::prelude::*; /// # fn main() -> Result<()> { /// # let (cert, _) = /// # CertBuilder::new().set_password(Some("password".into())) /// # .generate()?; /// for ka in cert.keys().encrypted_secret() { /// // Use it. /// } /// # assert!(cert.keys().encrypted_secret().count() == 1); /// # Ok(()) /// # } /// ``` pub fn encrypted_secret( self, ) -> KeyAmalgamationIter<'a, key::SecretParts, R> { KeyAmalgamationIter { cert: self.cert, primary: self.primary, subkey_iter: self.subkey_iter, // The filters. encrypted_secret: Some(true), unencrypted_secret: self.unencrypted_secret, key_handles: self.key_handles, supported: self.supported, _p: std::marker::PhantomData, _r: std::marker::PhantomData, } } /// Changes the iterator to only return keys with unencrypted /// secret key material. /// /// # Examples /// /// ```rust /// # use sequoia_openpgp as openpgp; /// # use openpgp::Result; /// # use openpgp::cert::prelude::*; /// # fn main() -> Result<()> { /// # let (cert, _) = /// # CertBuilder::new().generate()?; /// for ka in cert.keys().unencrypted_secret() { /// // Use it. /// } /// # assert!(cert.keys().secret().unencrypted_secret().count() == 1); /// # Ok(()) /// # } /// ``` pub fn unencrypted_secret(self) -> KeyAmalgamationIter<'a, key::SecretParts, R> { KeyAmalgamationIter { cert: self.cert, primary: self.primary, subkey_iter: self.subkey_iter, // The filters. encrypted_secret: self.encrypted_secret, unencrypted_secret: Some(true), key_handles: self.key_handles, supported: self.supported, _p: std::marker::PhantomData, _r: std::marker::PhantomData, } } /// Changes the iterator to only return a key if it matches one of /// the specified `KeyHandle`s. /// /// This function is cumulative. If you call this function (or /// [`key_handles`]) multiple times, then the iterator returns a key /// if it matches *any* of the specified [`KeyHandle`s]. /// /// This function uses [`KeyHandle::aliases`] to compare key /// handles. /// /// # Examples /// /// ```rust /// # use sequoia_openpgp as openpgp; /// # use openpgp::Result; /// # use openpgp::cert::prelude::*; /// # fn main() -> Result<()> { /// # let (cert, _) = /// # CertBuilder::general_purpose(Some("alice@example.org")) /// # .generate()?; /// # let key_handle = cert.primary_key().key().key_handle(); /// # let mut i = 0; /// for ka in cert.keys().key_handle(key_handle) { /// // Use it. /// # i += 1; /// } /// # assert_eq!(i, 1); /// # Ok(()) /// # } /// ``` /// /// [`KeyHandle`s]: super::super::super::KeyHandle /// [`key_handles`]: KeyAmalgamationIter::key_handles() /// [`KeyHandle::aliases`]: super::super::super::KeyHandle::aliases() pub fn key_handle<H>(mut self, h: H) -> Self where H: Into<KeyHandle> { self.key_handles.push(h.into()); self } /// Changes the iterator to only return a key if it matches one of /// the specified `KeyHandle`s. /// /// If the given `handles` iterator is empty, the set of returned /// keys is not constrained. /// /// This function is cumulative. If you call this function (or /// [`key_handle`]) multiple times, then the iterator returns a key /// if it matches *any* of the specified [`KeyHandle`s]. /// /// This function uses [`KeyHandle::aliases`] to compare key /// handles. /// /// # Examples /// /// ```rust /// # use sequoia_openpgp as openpgp; /// # use openpgp::Result; /// # use openpgp::cert::prelude::*; /// # fn main() -> Result<()> { /// # let (cert, _) = /// # CertBuilder::general_purpose(Some("alice@example.org")) /// # .generate()?; /// # let key_handles = &[cert.primary_key().key().key_handle()][..]; /// # let mut i = 0; /// for ka in cert.keys().key_handles(key_handles) { /// // Use it. /// # i += 1; /// } /// # assert_eq!(i, 1); /// # Ok(()) /// # } /// ``` /// /// [`KeyHandle`s]: super::super::super::KeyHandle /// [`key_handle`]: KeyAmalgamationIter::key_handle() /// [`KeyHandle::aliases`]: super::super::super::KeyHandle::aliases() pub fn key_handles<H, K>(mut self, handles: H) -> Self where H: IntoIterator<Item=K>, K: Borrow<KeyHandle>, { let mut handles = handles.into_iter() .map(|h| h.borrow().clone()) .collect::<Vec<_>>(); self.key_handles.append(&mut handles); self } /// Changes the iterator to only return a key if it is supported /// by Sequoia's cryptographic backend. /// /// Which public key encryption algorithms Sequoia supports /// depends on the cryptographic backend selected at compile time. /// This filter makes sure that only supported keys are returned. /// /// # Examples /// /// ```rust /// # fn main() -> sequoia_openpgp::Result<()> { /// # use sequoia_openpgp as openpgp; /// # use openpgp::cert::prelude::*; /// # let (cert, _) = /// # CertBuilder::general_purpose(Some("alice@example.org")) /// # .generate()?; /// # let mut i = 0; /// for ka in cert.keys().supported() { /// // Use it. /// # i += 1; /// } /// # assert_eq!(i, 3); /// # Ok(()) } /// ``` pub fn supported(mut self) -> Self { self.supported = Some(true); self } /// Changes the iterator to only return subkeys. /// /// This function also changes the return type. Instead of the /// iterator returning a [`ErasedKeyAmalgamation`], it returns a /// [`SubordinateKeyAmalgamation`]. /// /// # Examples /// /// ```rust /// # use sequoia_openpgp as openpgp; /// # use openpgp::Result; /// # use openpgp::cert::prelude::*; /// # /// # fn main() -> Result<()> { /// # let (cert, _) = CertBuilder::new() /// # .add_signing_subkey() /// # .add_certification_subkey() /// # .add_transport_encryption_subkey() /// # .add_storage_encryption_subkey() /// # .add_authentication_subkey() /// # .generate()?; /// # let mut i = 0; /// for ka in cert.keys().subkeys() { /// // Use it. /// assert!(! ka.primary()); /// # i += 1; /// } /// # assert_eq!(i, 5); /// # Ok(()) /// # } /// ``` /// pub fn subkeys(self) -> KeyAmalgamationIter<'a, P, key::SubordinateRole> { KeyAmalgamationIter { cert: self.cert, primary: true, subkey_iter: self.subkey_iter, // The filters. encrypted_secret: self.encrypted_secret, unencrypted_secret: self.unencrypted_secret, key_handles: self.key_handles, supported: self.supported, _p: std::marker::PhantomData, _r: std::marker::PhantomData, } } /// Changes the iterator to only return valid `Key`s. /// /// If `time` is None, then the current time is used. /// /// This also makes a number of additional filters like [`alive`] /// and [`revoked`] available. /// /// Refer to the [`ValidateAmalgamation`] trait for a definition /// of a valid component. /// /// # Examples /// /// ``` /// # use sequoia_openpgp as openpgp; /// # use openpgp::cert::prelude::*; /// use openpgp::policy::StandardPolicy; /// # /// # fn main() -> openpgp::Result<()> { /// let p = &StandardPolicy::new(); /// /// # let (cert, _) = /// # CertBuilder::general_purpose(Some("alice@example.org")) /// # .generate()?; /// # let fpr = cert.fingerprint(); /// // Iterate over all valid User Attributes. /// for ka in cert.keys().with_policy(p, None) { /// // ka is a `ValidKeyAmalgamation`, specifically, an /// // `ValidErasedKeyAmalgamation`. /// } /// # Ok(()) /// # } /// ``` /// /// [`ValidateAmalgamation`]: super::ValidateAmalgamation /// [`alive`]: ValidKeyAmalgamationIter::alive() /// [`revoked`]: ValidKeyAmalgamationIter::revoked() pub fn with_policy<T>(self, policy: &'a dyn Policy, time: T) -> ValidKeyAmalgamationIter<'a, P, R> where T: Into<Option<SystemTime>> { ValidKeyAmalgamationIter { cert: self.cert, primary: self.primary, subkey_iter: self.subkey_iter, policy, time: time.into().unwrap_or_else(crate::now), // The filters. encrypted_secret: self.encrypted_secret, unencrypted_secret: self.unencrypted_secret, key_handles: self.key_handles, supported: self.supported, flags: None, alive: None, revoked: None, _p: self._p, _r: self._r, } } } /// An iterator over valid `Key`s. /// /// An iterator over [`ValidKeyAmalgamation`]s. /// /// A `ValidKeyAmalgamationIter` is a [`KeyAmalgamationIter`] /// that includes a [`Policy`] and a reference time, which it firstly /// uses to only return valid `Key`s. (For a definition of valid /// keys, see the documentation for [`ValidateAmalgamation`].) /// /// A `ValidKeyAmalgamationIter` also provides additional /// filters based on information available in the `Key`s' binding /// signatures. For instance, [`ValidKeyAmalgamationIter::revoked`] /// filters the returned `Key`s by whether they are revoked. /// And, [`ValidKeyAmalgamationIter::alive`] changes the iterator to /// only return `Key`s that are live. /// /// `ValidKeyAmalgamationIter` follows the builder pattern. But, /// there is no need to explicitly finalize it: it already implements /// the `Iterator` trait. /// /// A `ValidKeyAmalgamationIter` is returned by /// [`KeyAmalgamationIter::with_policy`] and [`ValidCert::keys`]. /// /// # Examples /// /// Find a key that we can use to sign a document: /// /// ``` /// # use sequoia_openpgp as openpgp; /// # use openpgp::cert::prelude::*; /// use openpgp::policy::StandardPolicy; /// use openpgp::types::RevocationStatus; /// /// # fn main() -> openpgp::Result<()> { /// let p = &StandardPolicy::new(); /// /// # let (cert, _) = /// # CertBuilder::general_purpose(Some("alice@example.org")) /// # .generate()?; /// # let mut i = 0; /// // The certificate *and* keys need to be valid. /// let cert = cert.with_policy(p, None)?; /// /// if let RevocationStatus::Revoked(_) = cert.revocation_status() { /// // Certificate is revoked. /// } else if let Err(_err) = cert.alive() { /// // The certificate is not alive. /// } else { /// // Iterate over all valid keys. /// // /// // Note: using the combinator interface (instead of checking /// // the individual keys) makes it harder to report exactly why no /// // key was usable. /// for ka in cert.keys() /// // Not revoked. /// .revoked(false) /// // Alive. /// .alive() /// // Be signing capable. /// .for_signing() /// // And have unencrypted secret material. /// .unencrypted_secret() /// { /// // We can use it. /// # i += 1; /// } /// } /// # assert_eq!(i, 1); /// # Ok(()) /// # } /// ``` /// /// [`Policy`]: crate::policy::Policy /// [`ValidateAmalgamation`]: super::ValidateAmalgamation /// [`ValidKeyAmalgamationIter::revoked`]: ValidKeyAmalgamationIter::revoked() /// [`ValidKeyAmalgamationIter::alive`]: ValidKeyAmalgamationIter::alive() /// [`KeyAmalgamationIter::with_policy`]: KeyAmalgamationIter::with_policy() /// [`ValidCert::keys`]: super::super::ValidCert::keys() pub struct ValidKeyAmalgamationIter<'a, P, R> where P: key::KeyParts, R: key::KeyRole, { // This is an option to make it easier to create an empty ValidKeyAmalgamationIter. cert: Option<&'a Cert>, primary: bool, subkey_iter: slice::Iter<'a, KeyBundle<key::PublicParts, key::SubordinateRole>>, // The policy. policy: &'a dyn Policy, // The time. time: SystemTime, // If not None, filters by whether a key has an encrypted secret. encrypted_secret: Option<bool>, // If not None, filters by whether a key has an unencrypted // secret. unencrypted_secret: Option<bool>, /// Only return keys in this set. key_handles: Vec<KeyHandle>, // If not None, filters by whether we support the key's asymmetric // algorithm. supported: Option<bool>, // If not None, only returns keys with the specified flags. flags: Option<KeyFlags>, // If not None, filters by whether a key is alive at time `t`. alive: Option<()>, // If not None, filters by whether the key is revoked or not at // time `t`. revoked: Option<bool>, _p: std::marker::PhantomData<P>, _r: std::marker::PhantomData<R>, } assert_send_and_sync!(ValidKeyAmalgamationIter<'_, P, R> where P: key::KeyParts, R: key::KeyRole, ); impl<'a, P, R> fmt::Debug for ValidKeyAmalgamationIter<'a, P, R> where P: key::KeyParts, R: key::KeyRole, { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.debug_struct("ValidKeyAmalgamationIter") .field("policy", &self.policy) .field("time", &self.time) .field("encrypted_secret", &self.encrypted_secret) .field("unencrypted_secret", &self.unencrypted_secret) .field("key_handles", &self.key_handles) .field("supported", &self.supported) .field("flags", &self.flags) .field("alive", &self.alive) .field("revoked", &self.revoked) .finish() } } macro_rules! impl_iterator { ($parts:path, $role:path, $item:ty) => { impl<'a> Iterator for ValidKeyAmalgamationIter<'a, $parts, $role> { type Item = $item; fn next(&mut self) -> Option<Self::Item> { // We unwrap the result of the conversion. But, this // is safe by construction: next_common only returns // keys that can be correctly converted. self.next_common().map(|k| k.try_into().expect("filtered")) } } } } impl_iterator!(key::PublicParts, key::PrimaryRole, ValidPrimaryKeyAmalgamation<'a, key::PublicParts>); impl_iterator!(key::SecretParts, key::PrimaryRole, ValidPrimaryKeyAmalgamation<'a, key::SecretParts>); impl_iterator!(key::UnspecifiedParts, key::PrimaryRole, ValidPrimaryKeyAmalgamation<'a, key::UnspecifiedParts>); impl_iterator!(key::PublicParts, key::SubordinateRole, ValidSubordinateKeyAmalgamation<'a, key::PublicParts>); impl_iterator!(key::SecretParts, key::SubordinateRole, ValidSubordinateKeyAmalgamation<'a, key::SecretParts>); impl_iterator!(key::UnspecifiedParts, key::SubordinateRole, ValidSubordinateKeyAmalgamation<'a, key::UnspecifiedParts>); impl_iterator!(key::PublicParts, key::UnspecifiedRole, ValidErasedKeyAmalgamation<'a, key::PublicParts>); impl_iterator!(key::SecretParts, key::UnspecifiedRole, ValidErasedKeyAmalgamation<'a, key::SecretParts>); impl_iterator!(key::UnspecifiedParts, key::UnspecifiedRole, ValidErasedKeyAmalgamation<'a, key::UnspecifiedParts>); impl<'a, P, R> ValidKeyAmalgamationIter<'a, P, R> where P: key::KeyParts, R: key::KeyRole, { fn next_common(&mut self) -> Option<ValidErasedKeyAmalgamation<'a, key::PublicParts>> { tracer!(false, "ValidKeyAmalgamationIter::next", 0); t!("ValidKeyAmalgamationIter: {:?}", self); let cert = self.cert?; if let Some(flags) = self.flags.as_ref() { if flags.is_empty() { // Nothing to do. t!("short circuiting: flags is empty"); return None; } } loop { let ka = if ! self.primary { self.primary = true; let ka : ErasedKeyAmalgamation<'a, key::PublicParts> = PrimaryKeyAmalgamation::new(cert).into(); match ka.with_policy(self.policy, self.time) { Ok(ka) => ka, Err(err) => { // The primary key is bad. Abort. t!("Getting primary key: {:?}", err); return None; } } } else { let ka : ErasedKeyAmalgamation<'a, key::PublicParts> = SubordinateKeyAmalgamation::new( cert, self.subkey_iter.next()?).into(); match ka.with_policy(self.policy, self.time) { Ok(ka) => ka, Err(err) => { // The subkey is bad, abort. t!("Getting subkey: {:?}", err); continue; } } }; let key = ka.key(); t!("Considering key: {:?}", key); if ! self.key_handles.is_empty() && ! self.key_handles.iter() .any(|h| h.aliases(key.key_handle())) { t!("{} is not one of the keys that we are looking for ({:?})", key.key_handle(), self.key_handles); continue; } if let Some(want_supported) = self.supported { if ka.key().pk_algo().is_supported() { // It is supported. if ! want_supported { t!("PK algo is supported... skipping."); continue; } } else if want_supported { t!("PK algo is not supported... skipping."); continue; } } if let Some(flags) = self.flags.as_ref() { if !ka.has_any_key_flag(flags) { t!("Have flags: {:?}, want flags: {:?}... skipping.", ka.key_flags(), flags); continue; } } if let Some(()) = self.alive { if let Err(err) = ka.alive() { t!("Key not alive: {:?}", err); continue; } } if let Some(want_revoked) = self.revoked { if let RevocationStatus::Revoked(_) = ka.revocation_status() { // The key is definitely revoked. if ! want_revoked { t!("Key revoked... skipping."); continue; } } else { // The key is probably not revoked. if want_revoked { t!("Key not revoked... skipping."); continue; } } } if let Some(msg) = skip_secret( ka.key().optional_secret().map(|x| x.is_encrypted()), self.encrypted_secret, self.unencrypted_secret, ) { t!(msg); continue; } return Some(ka); } } } impl<'a, P, R> ValidKeyAmalgamationIter<'a, P, R> where P: key::KeyParts, R: key::KeyRole, { /// Returns keys that have the at least one of the flags specified /// in `flags`. /// /// If you call this function (or one of `for_certification`, /// `for_signing`, etc.) multiple times, the *union* of /// the values is used. /// /// Note: [Section 10.1 of RFC 9580] says that the primary key is /// certification capable independent of the `Key Flags` /// subpacket: /// /// > In a V4 key, the primary key MUST be a key capable of /// > certification. /// /// This function only reflects what is stored in the `Key Flags` /// packet; it does not implicitly set this flag. In practice, /// there are keys whose primary key's `Key Flags` do not have the /// certification capable flag set. Some versions of netpgp, for /// instance, create keys like this. Sequoia's higher-level /// functionality correctly handles these keys by always /// considering the primary key to be certification capable. /// Users of this interface should too. /// /// The key flags are looked up as described in /// [`ValidKeyAmalgamation::key_flags`]. /// /// # Examples /// /// ``` /// # use sequoia_openpgp as openpgp; /// # use openpgp::cert::prelude::*; /// use openpgp::policy::StandardPolicy; /// use openpgp::types::KeyFlags; /// /// # fn main() -> openpgp::Result<()> { /// let p = &StandardPolicy::new(); /// /// # let (cert, _) = CertBuilder::new() /// # .add_signing_subkey() /// # .add_certification_subkey() /// # .add_transport_encryption_subkey() /// # .add_storage_encryption_subkey() /// # .add_authentication_subkey() /// # .generate()?; /// # let mut i = 0; /// for ka in cert.keys() /// .with_policy(p, None) /// .key_flags(KeyFlags::empty() /// .set_transport_encryption() /// .set_storage_encryption()) /// { /// // Valid encryption-capable keys. /// # i += 1; /// } /// # assert_eq!(i, 2); /// # Ok(()) } /// ``` /// /// [Section 10.1 of RFC 9580]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.29 /// [`ValidKeyAmalgamation::key_flags`]: ValidKeyAmalgamation::key_flags() pub fn key_flags<F>(mut self, flags: F) -> Self where F: Borrow<KeyFlags> { let flags = flags.borrow(); if let Some(flags_old) = self.flags { self.flags = Some(flags | &flags_old); } else { self.flags = Some(flags.clone()); } self } /// Returns certification-capable keys. /// /// If you call this function (or one of `key_flags`, /// `for_signing`, etc.) multiple times, the *union* of /// the values is used. /// /// Note: [Section 10.1 of RFC 9580] says that the primary key is /// certification capable independent of the `Key Flags` /// subpacket: /// /// > In a V4 key, the primary key MUST be a key capable of /// > certification. /// /// This function only reflects what is stored in the `Key Flags` /// packet; it does not implicitly set this flag. In practice, /// there are keys whose primary key's `Key Flags` do not have the /// certification capable flag set. Some versions of netpgp, for /// instance, create keys like this. Sequoia's higher-level /// functionality correctly handles these keys by always /// considering the primary key to be certification capable. /// Users of this interface should too. /// /// The key flags are looked up as described in /// [`ValidKeyAmalgamation::key_flags`]. /// /// # Examples /// /// ``` /// # use sequoia_openpgp as openpgp; /// # use openpgp::cert::prelude::*; /// use openpgp::policy::StandardPolicy; /// /// # fn main() -> openpgp::Result<()> { /// let p = &StandardPolicy::new(); /// /// # let (cert, _) = CertBuilder::new() /// # .add_signing_subkey() /// # .add_certification_subkey() /// # .add_transport_encryption_subkey() /// # .add_storage_encryption_subkey() /// # .add_authentication_subkey() /// # .generate()?; /// # let mut i = 0; /// for ka in cert.keys() /// .with_policy(p, None) /// .for_certification() /// { /// // Valid certification-capable keys. /// # i += 1; /// } /// # assert_eq!(i, 2); /// # Ok(()) } /// ``` /// /// [`ValidKeyAmalgamation::for_certification`]: ValidKeyAmalgamation::for_certification() /// [Section 10.1 of RFC 9580]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.29 /// [`ValidKeyAmalgamation::key_flags`]: ValidKeyAmalgamation::key_flags() pub fn for_certification(self) -> Self { self.key_flags(KeyFlags::empty().set_certification()) } /// Returns signing-capable keys. /// /// If you call this function (or one of `key_flags`, /// `for_certification`, etc.) multiple times, the *union* of /// the values is used. /// /// Refer to [`ValidKeyAmalgamation::for_signing`] for additional /// details and caveats. /// /// # Examples /// /// ``` /// # use sequoia_openpgp as openpgp; /// # use openpgp::cert::prelude::*; /// use openpgp::policy::StandardPolicy; /// /// # fn main() -> openpgp::Result<()> { /// let p = &StandardPolicy::new(); /// /// # let (cert, _) = CertBuilder::new() /// # .add_signing_subkey() /// # .add_certification_subkey() /// # .add_transport_encryption_subkey() /// # .add_storage_encryption_subkey() /// # .add_authentication_subkey() /// # .generate()?; /// # let mut i = 0; /// for ka in cert.keys() /// .with_policy(p, None) /// .for_signing() /// { /// // Valid signing-capable keys. /// # i += 1; /// } /// # assert_eq!(i, 1); /// # Ok(()) } /// ``` /// /// [`ValidKeyAmalgamation::for_signing`]: ValidKeyAmalgamation::for_signing() pub fn for_signing(self) -> Self { self.key_flags(KeyFlags::empty().set_signing()) } /// Returns authentication-capable keys. /// /// If you call this function (or one of `key_flags`, /// `for_certification`, etc.) multiple times, the /// *union* of the values is used. /// /// Refer to [`ValidKeyAmalgamation::for_authentication`] for /// additional details and caveats. /// /// # Examples /// /// ``` /// # use sequoia_openpgp as openpgp; /// # use openpgp::cert::prelude::*; /// use openpgp::policy::StandardPolicy; /// /// # fn main() -> openpgp::Result<()> { /// let p = &StandardPolicy::new(); /// /// # let (cert, _) = CertBuilder::new() /// # .add_authentication_subkey() /// # .add_certification_subkey() /// # .add_transport_encryption_subkey() /// # .add_storage_encryption_subkey() /// # .add_authentication_subkey() /// # .generate()?; /// # let mut i = 0; /// for ka in cert.keys() /// .with_policy(p, None) /// .for_authentication() /// { /// // Valid authentication-capable keys. /// # i += 1; /// } /// # assert_eq!(i, 2); /// # Ok(()) } /// ``` /// /// [`ValidKeyAmalgamation::for_authentication`]: ValidKeyAmalgamation::for_authentication() pub fn for_authentication(self) -> Self { self.key_flags(KeyFlags::empty().set_authentication()) } /// Returns encryption-capable keys for data at rest. /// /// If you call this function (or one of `key_flags`, /// `for_certification`, etc.) multiple times, the /// *union* of the values is used. /// /// Refer to [`ValidKeyAmalgamation::for_storage_encryption`] for /// additional details and caveats. /// /// # Examples /// /// ``` /// # use sequoia_openpgp as openpgp; /// # use openpgp::cert::prelude::*; /// use openpgp::policy::StandardPolicy; /// /// # fn main() -> openpgp::Result<()> { /// let p = &StandardPolicy::new(); /// /// # let (cert, _) = CertBuilder::new() /// # .add_authentication_subkey() /// # .add_certification_subkey() /// # .add_transport_encryption_subkey() /// # .add_storage_encryption_subkey() /// # .add_authentication_subkey() /// # .generate()?; /// # let mut i = 0; /// for ka in cert.keys() /// .with_policy(p, None) /// .for_storage_encryption() /// { /// // Valid encryption-capable keys for data at rest. /// # i += 1; /// } /// # assert_eq!(i, 1); /// # Ok(()) } /// ``` /// /// [`ValidKeyAmalgamation::for_storage_encryption`]: ValidKeyAmalgamation::for_storage_encryption() pub fn for_storage_encryption(self) -> Self { self.key_flags(KeyFlags::empty().set_storage_encryption()) } /// Returns encryption-capable keys for data in transit. /// /// If you call this function (or one of `key_flags`, /// `for_certification`, etc.) multiple times, the /// *union* of the values is used. /// /// Refer to [`ValidKeyAmalgamation::for_transport_encryption`] for /// additional details and caveats. /// /// # Examples /// /// ``` /// # use sequoia_openpgp as openpgp; /// # use openpgp::cert::prelude::*; /// use openpgp::policy::StandardPolicy; /// /// # fn main() -> openpgp::Result<()> { /// let p = &StandardPolicy::new(); /// /// # let (cert, _) = CertBuilder::new() /// # .add_authentication_subkey() /// # .add_certification_subkey() /// # .add_transport_encryption_subkey() /// # .add_transport_encryption_subkey() /// # .add_authentication_subkey() /// # .generate()?; /// # let mut i = 0; /// for ka in cert.keys() /// .with_policy(p, None) /// .for_transport_encryption() /// { /// // Valid encryption-capable keys for data in transit. /// # i += 1; /// } /// # assert_eq!(i, 2); /// # Ok(()) } /// ``` /// /// [`ValidKeyAmalgamation::for_transport_encryption`]: ValidKeyAmalgamation::for_transport_encryption() pub fn for_transport_encryption(self) -> Self { self.key_flags(KeyFlags::empty().set_transport_encryption()) } /// Returns keys that are alive. /// /// A `ValidKeyAmalgamation` is guaranteed to have a live *binding /// signature*. This is independent of whether the *key* is live, /// or the *certificate* is live, i.e., if you care about those /// things, you need to check them too. /// /// For a definition of liveness, see the [`key_alive`] method. /// /// # Examples /// /// ``` /// # use sequoia_openpgp as openpgp; /// # use openpgp::cert::prelude::*; /// use openpgp::policy::StandardPolicy; /// /// # fn main() -> openpgp::Result<()> { /// let p = &StandardPolicy::new(); /// /// # let (cert, _) = CertBuilder::new() /// # .add_authentication_subkey() /// # .add_certification_subkey() /// # .add_transport_encryption_subkey() /// # .add_transport_encryption_subkey() /// # .add_authentication_subkey() /// # .generate()?; /// for ka in cert.keys() /// .with_policy(p, None) /// .alive() /// { /// // ka is alive. /// } /// # Ok(()) } /// ``` /// /// [`key_alive`]: crate::packet::signature::subpacket::SubpacketAreas::key_alive() pub fn alive(mut self) -> Self { self.alive = Some(()); self } /// Returns keys based on their revocation status. /// /// A value of `None` disables this filter. /// /// If you call this function multiple times on the same /// `ValidKeyAmalgamationIter`, only the last value is used. /// /// This filter checks the key's revocation status; it does /// not check the certificate's revocation status. /// /// This filter only checks whether the key has no valid-self /// revocations at the specified time. It does not check /// third-party revocations. /// /// # Examples /// /// ``` /// # use sequoia_openpgp as openpgp; /// # use openpgp::cert::prelude::*; /// use openpgp::policy::StandardPolicy; /// /// # fn main() -> openpgp::Result<()> { /// let p = &StandardPolicy::new(); /// /// # let (cert, _) = CertBuilder::new() /// # .add_authentication_subkey() /// # .add_certification_subkey() /// # .add_transport_encryption_subkey() /// # .add_transport_encryption_subkey() /// # .add_authentication_subkey() /// # .generate()?; /// for ka in cert.keys() /// .with_policy(p, None) /// .revoked(false) /// { /// // ka has no self-revocations; recall: this filter doesn't check /// // third-party revocations. /// } /// # Ok(()) } /// ``` /// /// This filter checks whether a key's revocation status is /// `RevocationStatus::Revoked` or not. /// `ValidKeyAmalgamationIter::revoked(false)` is equivalent to: /// /// ```rust /// # use sequoia_openpgp as openpgp; /// # use openpgp::Result; /// use openpgp::types::RevocationStatus; /// # use openpgp::cert::prelude::*; /// use openpgp::policy::StandardPolicy; /// /// # fn main() -> Result<()> { /// # let (cert, _) = /// # CertBuilder::general_purpose(Some("alice@example.org")) /// # .generate()?; /// let p = &StandardPolicy::new(); /// /// # let timestamp = None; /// let non_revoked_keys = cert /// .keys() /// .with_policy(p, timestamp) /// .filter(|ka| { /// match ka.revocation_status() { /// RevocationStatus::Revoked(_) => /// // It's definitely revoked, skip it. /// false, /// RevocationStatus::CouldBe(_) => /// // There is a designated revoker that we /// // could check, but don't (or can't). To /// // avoid a denial-of-service attack arising from /// // fake revocations, we assume that the key has /// // not been revoked and return it. /// true, /// RevocationStatus::NotAsFarAsWeKnow => /// // We have no evidence to suggest that the key /// // is revoked. /// true, /// } /// }) /// .map(|ka| ka.key()) /// .collect::<Vec<_>>(); /// # Ok(()) /// # } /// ``` /// /// As the example shows, this filter is significantly less /// flexible than using `KeyAmalgamation::revocation_status`. /// However, this filter implements a typical policy, and does not /// preclude using something like `Iter::filter` to implement /// alternative policies. pub fn revoked<T>(mut self, revoked: T) -> Self where T: Into<Option<bool>> { self.revoked = revoked.into(); self } /// Changes the iterator to only return keys with secret key /// material. /// /// # Examples /// /// ```rust /// # use sequoia_openpgp as openpgp; /// # use openpgp::Result; /// # use openpgp::cert::prelude::*; /// use openpgp::policy::StandardPolicy; /// /// # fn main() -> Result<()> { /// let p = &StandardPolicy::new(); /// /// # let (cert, _) = /// # CertBuilder::new().set_password(Some("password".into())) /// # .generate()?; /// for ka in cert.keys().with_policy(p, None).secret() { /// // Use it. /// } /// # assert!(cert.keys().with_policy(p, None).secret().count() == 1); /// # /// # let (cert, _) = CertBuilder::new().generate()?; /// # assert!(cert.keys().with_policy(p, None).secret().count() == 1); /// # Ok(()) /// # } /// ``` pub fn secret(self) -> ValidKeyAmalgamationIter<'a, key::SecretParts, R> { ValidKeyAmalgamationIter { cert: self.cert, primary: self.primary, subkey_iter: self.subkey_iter, time: self.time, policy: self.policy, // The filters. encrypted_secret: Some(true), unencrypted_secret: Some(true), key_handles: self.key_handles, supported: self.supported, flags: self.flags, alive: self.alive, revoked: self.revoked, _p: std::marker::PhantomData, _r: std::marker::PhantomData, } } /// Changes the iterator to only return keys with encrypted secret key /// material. /// /// # Examples /// /// ```rust /// # use sequoia_openpgp as openpgp; /// # use openpgp::Result; /// # use openpgp::cert::prelude::*; /// use openpgp::policy::StandardPolicy; /// /// # fn main() -> Result<()> { /// let p = &StandardPolicy::new(); /// /// # let (cert, _) = /// # CertBuilder::new().set_password(Some("password".into())) /// # .generate()?; /// for ka in cert.keys().with_policy(p, None).encrypted_secret() { /// // Use it. /// } /// # assert!(cert.keys().with_policy(p, None).encrypted_secret().count() == 1); /// # Ok(()) /// # } /// ``` pub fn encrypted_secret( self, ) -> ValidKeyAmalgamationIter<'a, key::SecretParts, R> { ValidKeyAmalgamationIter { cert: self.cert, primary: self.primary, subkey_iter: self.subkey_iter, time: self.time, policy: self.policy, // The filters. encrypted_secret: Some(true), unencrypted_secret: self.unencrypted_secret, key_handles: self.key_handles, supported: self.supported, flags: self.flags, alive: self.alive, revoked: self.revoked, _p: std::marker::PhantomData, _r: std::marker::PhantomData, } } /// Changes the iterator to only return keys with unencrypted /// secret key material. /// /// # Examples /// /// ```rust /// # use sequoia_openpgp as openpgp; /// # use openpgp::Result; /// # use openpgp::cert::prelude::*; /// use openpgp::policy::StandardPolicy; /// /// # fn main() -> Result<()> { /// let p = &StandardPolicy::new(); /// /// # let (cert, _) = CertBuilder::new().generate()?; /// for ka in cert.keys().with_policy(p, None).unencrypted_secret() { /// // Use it. /// } /// # assert!(cert.keys().with_policy(p, None).unencrypted_secret().count() == 1); /// # Ok(()) /// # } /// ``` pub fn unencrypted_secret(self) -> ValidKeyAmalgamationIter<'a, key::SecretParts, R> { ValidKeyAmalgamationIter { cert: self.cert, primary: self.primary, subkey_iter: self.subkey_iter, time: self.time, policy: self.policy, // The filters. encrypted_secret: self.encrypted_secret, unencrypted_secret: Some(true), key_handles: self.key_handles, supported: self.supported, flags: self.flags, alive: self.alive, revoked: self.revoked, _p: std::marker::PhantomData, _r: std::marker::PhantomData, } } /// Changes the iterator to only return a key if it matches one of /// the specified `KeyHandle`s. /// /// This function is cumulative. If you call this function (or /// [`key_handles`]) multiple times, then the iterator returns a /// key if it matches *any* of the specified [`KeyHandle`s]. /// /// This function uses [`KeyHandle::aliases`] to compare key /// handles. /// /// # Examples /// /// ```rust /// # use sequoia_openpgp as openpgp; /// # use openpgp::Result; /// # use openpgp::cert::prelude::*; /// use openpgp::policy::StandardPolicy; /// /// # fn main() -> Result<()> { /// let p = &StandardPolicy::new(); /// /// # let (cert, _) = /// # CertBuilder::general_purpose(Some("alice@example.org")) /// # .generate()?; /// # let key_handle = cert.primary_key().key().key_handle(); /// # let mut i = 0; /// for ka in cert.keys().with_policy(p, None).key_handle(key_handle) { /// // Use it. /// # i += 1; /// } /// # assert_eq!(i, 1); /// # Ok(()) /// # } /// ``` /// /// [`KeyHandle`s]: super::super::super::KeyHandle /// [`key_handles`]: ValidKeyAmalgamationIter::key_handles() /// [`KeyHandle::aliases`]: super::super::super::KeyHandle::aliases() pub fn key_handle<H>(mut self, h: H) -> Self where H: Into<KeyHandle> { self.key_handles.push(h.into()); self } /// Changes the iterator to only return a key if it matches one of /// the specified `KeyHandle`s. /// /// If the given `handles` iterator is empty, the set of returned /// keys is not constrained. /// /// This function is cumulative. If you call this function (or /// [`key_handle`]) multiple times, then the iterator returns a key /// if it matches *any* of the specified [`KeyHandle`s]. /// /// This function uses [`KeyHandle::aliases`] to compare key /// handles. /// /// # Examples /// /// ```rust /// # use sequoia_openpgp as openpgp; /// # use openpgp::Result; /// # use openpgp::cert::prelude::*; /// use openpgp::policy::StandardPolicy; /// /// # fn main() -> Result<()> { /// let p = &StandardPolicy::new(); /// /// # let (cert, _) = /// # CertBuilder::general_purpose(Some("alice@example.org")) /// # .generate()?; /// # let key_handles = &[cert.primary_key().key().key_handle()][..]; /// # let mut i = 0; /// for ka in cert.keys().with_policy(p, None).key_handles(key_handles) { /// // Use it. /// # i += 1; /// } /// # assert_eq!(i, 1); /// # Ok(()) /// # } /// ``` /// /// [`KeyHandle`s]: super::super::super::KeyHandle /// [`key_handle`]: KeyAmalgamationIter::key_handle() /// [`KeyHandle::aliases`]: super::super::super::KeyHandle::aliases() pub fn key_handles<H, K>(mut self, handles: H) -> Self where H: IntoIterator<Item=K>, K: Borrow<KeyHandle>, { let mut handles = handles.into_iter() .map(|h| h.borrow().clone()) .collect::<Vec<_>>(); self.key_handles.append(&mut handles); self } /// Changes the iterator to only return a key if it is supported /// by Sequoia's cryptographic backend. /// /// Which public key encryption algorithms Sequoia supports /// depends on the cryptographic backend selected at compile time. /// This filter makes sure that only supported keys are returned. /// /// # Examples /// /// ```rust /// # fn main() -> sequoia_openpgp::Result<()> { /// # use sequoia_openpgp as openpgp; /// # use openpgp::cert::prelude::*; /// # let (cert, _) = /// # CertBuilder::general_purpose(Some("alice@example.org")) /// # .generate()?; /// # let mut i = 0; /// use openpgp::policy::StandardPolicy; /// /// let p = &StandardPolicy::new(); /// /// for ka in cert.keys().with_policy(p, None).supported() { /// // Use it. /// # i += 1; /// } /// # assert_eq!(i, 3); /// # Ok(()) } /// ``` pub fn supported(mut self) -> Self { self.supported = Some(true); self } /// Changes the iterator to skip the primary key. /// /// This also changes the iterator's return type. Instead of /// returning a [`ValidErasedKeyAmalgamation`], it returns a /// [`ValidSubordinateKeyAmalgamation`]. /// /// # Examples /// /// ``` /// # use sequoia_openpgp as openpgp; /// # use openpgp::cert::prelude::*; /// use openpgp::policy::StandardPolicy; /// /// # fn main() -> openpgp::Result<()> { /// let p = &StandardPolicy::new(); /// /// # let (cert, _) = CertBuilder::new() /// # .add_signing_subkey() /// # .add_certification_subkey() /// # .add_transport_encryption_subkey() /// # .add_storage_encryption_subkey() /// # .add_authentication_subkey() /// # .generate()?; /// # let mut i = 0; /// for ka in cert.keys().with_policy(p, None).subkeys() { /// assert!(! ka.primary()); /// # i += 1; /// } /// # assert_eq!(cert.keys().count(), 6); /// # assert_eq!(i, 5); /// # Ok(()) } /// ``` /// pub fn subkeys(self) -> ValidKeyAmalgamationIter<'a, P, key::SubordinateRole> { ValidKeyAmalgamationIter { cert: self.cert, primary: true, subkey_iter: self.subkey_iter, time: self.time, policy: self.policy, // The filters. encrypted_secret: self.encrypted_secret, unencrypted_secret: self.unencrypted_secret, key_handles: self.key_handles, supported: self.supported, flags: self.flags, alive: self.alive, revoked: self.revoked, _p: std::marker::PhantomData, _r: std::marker::PhantomData, } } } #[cfg(test)] mod test { use super::*; use crate::{ parse::Parse, cert::builder::CertBuilder, }; use crate::policy::StandardPolicy as P; #[test] fn key_iter_test() { let key = Cert::from_bytes(crate::tests::key("neal.pgp")).unwrap(); assert_eq!(1 + key.subkeys().count(), key.keys().count()); } #[test] fn select_no_keys() { let p = &P::new(); let (cert, _) = CertBuilder::new() .generate().unwrap(); let flags = KeyFlags::empty().set_transport_encryption(); assert_eq!(cert.keys().with_policy(p, None).key_flags(flags).count(), 0); } #[test] fn select_valid_and_right_flags() { let p = &P::new(); let (cert, _) = CertBuilder::new() .add_transport_encryption_subkey() .generate().unwrap(); let flags = KeyFlags::empty().set_transport_encryption(); assert_eq!(cert.keys().with_policy(p, None).key_flags(flags).count(), 1); } #[test] fn select_valid_and_wrong_flags() { let p = &P::new(); let (cert, _) = CertBuilder::new() .add_transport_encryption_subkey() .add_signing_subkey() .generate().unwrap(); let flags = KeyFlags::empty().set_transport_encryption(); assert_eq!(cert.keys().with_policy(p, None).key_flags(flags).count(), 1); } #[test] fn select_invalid_and_right_flags() { let p = &P::new(); let (cert, _) = CertBuilder::new() .add_transport_encryption_subkey() .generate().unwrap(); let flags = KeyFlags::empty().set_transport_encryption(); let now = crate::now() - std::time::Duration::new(52 * 7 * 24 * 60 * 60, 0); assert_eq!(cert.keys().with_policy(p, now).key_flags(flags).alive().count(), 0); } #[test] fn select_primary() { let p = &P::new(); let (cert, _) = CertBuilder::new() .add_certification_subkey() .generate().unwrap(); let flags = KeyFlags::empty().set_certification(); assert_eq!(cert.keys().with_policy(p, None).key_flags(flags).count(), 2); } #[test] fn selectors() { let p = &P::new(); let (cert, _) = CertBuilder::new() .add_signing_subkey() .add_certification_subkey() .add_transport_encryption_subkey() .add_storage_encryption_subkey() .add_authentication_subkey() .generate().unwrap(); assert_eq!(cert.keys().with_policy(p, None).alive().revoked(false) .for_certification().count(), 2); assert_eq!(cert.keys().with_policy(p, None).alive().revoked(false) .for_transport_encryption().count(), 1); assert_eq!(cert.keys().with_policy(p, None).alive().revoked(false) .for_storage_encryption().count(), 1); assert_eq!(cert.keys().with_policy(p, None).alive().revoked(false) .for_signing().count(), 1); assert_eq!(cert.keys().with_policy(p, None).alive().revoked(false) .key_flags(KeyFlags::empty().set_authentication()) .count(), 1); } #[test] fn select_key_handle() { let p = &P::new(); let (cert, _) = CertBuilder::new() .add_signing_subkey() .add_certification_subkey() .add_transport_encryption_subkey() .add_storage_encryption_subkey() .add_authentication_subkey() .generate().unwrap(); let keys = cert.keys().count(); assert_eq!(keys, 6); let keyids = cert.keys().map(|ka| ka.key().keyid()).collect::<Vec<_>>(); fn check(got: &[KeyHandle], expected: &[KeyHandle]) { if expected.len() != got.len() { panic!("Got {}, expected {} handles", got.len(), expected.len()); } for (g, e) in got.iter().zip(expected.iter()) { if !e.aliases(g) { panic!(" Got: {:?}\nExpected: {:?}", got, expected); } } } for i in 1..keys { for keyids in keyids[..].windows(i) { let keyids : Vec<KeyHandle> = keyids.iter().map(Into::into).collect(); assert_eq!(keyids.len(), i); check( &cert.keys().key_handles(keyids.iter()) .map(|ka| ka.key().key_handle()) .collect::<Vec<KeyHandle>>(), &keyids); check( &cert.keys().with_policy(p, None).key_handles(keyids.iter()) .map(|ka| ka.key().key_handle()) .collect::<Vec<KeyHandle>>(), &keyids); check( &cert.keys().key_handles(keyids.iter()).with_policy(p, None) .map(|ka| ka.key().key_handle()) .collect::<Vec<KeyHandle>>(), &keyids); } } } #[test] #[allow(deprecated)] fn select_supported() -> crate::Result<()> { use crate::types::PublicKeyAlgorithm; if ! PublicKeyAlgorithm::DSA.is_supported() || PublicKeyAlgorithm::ElGamalEncrypt.is_supported() { return Ok(()); // Skip on this backend. } let cert = Cert::from_bytes(crate::tests::key("dsa2048-elgamal3072.pgp"))?; assert_eq!(cert.keys().count(), 2); assert_eq!(cert.keys().supported().count(), 1); let p = unsafe { &crate::policy::NullPolicy::new() }; assert_eq!(cert.keys().with_policy(p, None).count(), 2); assert_eq!(cert.keys().with_policy(p, None).supported().count(), 1); Ok(()) } } ���������sequoia-openpgp-2.0.0/src/cert/amalgamation/key.rs��������������������������������������������������0000644�0000000�0000000�00000436171�10461020230�0020267�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������//! Keys, their associated signatures, and some useful methods. //! //! A [`KeyAmalgamation`] is similar to a [`ComponentAmalgamation`], //! but a `KeyAmalgamation` includes some additional functionality //! that is needed to correctly implement a [`Key`] component's //! semantics. In particular, unlike other components where the //! binding signature stores the component's meta-data, a Primary Key //! doesn't have a binding signature (it is the thing that other //! components are bound to!), and, as a consequence, the associated //! meta-data is stored elsewhere. //! //! Unfortunately, a primary Key's meta-data is usually not stored on //! a direct key signature, which would be convenient as it is located //! at the same place as a binding signature would be, but on the //! primary User ID's binding signature. This requires some //! acrobatics on the implementation side to realize the correct //! semantics. In particular, a `Key` needs to memorize its role //! (i.e., whether it is a primary key or a subkey) in order to know //! whether to consider its own self signatures or the primary User //! ID's self signatures when looking for its meta-data. //! //! Ideally, a `KeyAmalgamation`'s role would be encoded in its type. //! This increases safety, and reduces the run-time overhead. //! However, we want [`Cert::keys`] to return an iterator over all //! keys; we don't want the user to have to specially handle the //! primary key when that fact is not relevant. This means that //! `Cert::keys` has to erase the returned `Key`s' roles: all items in //! an iterator must have the same type. To support this, we have to //! keep track of a `KeyAmalgamation`'s role at run-time. //! //! But, just because we need to erase a `KeyAmalgamation`'s role to //! implement `Cert::keys` doesn't mean that we have to always erase //! it. To achieve this, we use three data types: //! [`PrimaryKeyAmalgamation`], [`SubordinateKeyAmalgamation`], and //! [`ErasedKeyAmalgamation`]. The first two encode the role //! information in their type, and the last one stores it at run time. //! We provide conversion functions to convert the static type //! information into dynamic type information, and vice versa. //! //! Note: `KeyBundle`s and `KeyAmalgamation`s have a notable //! difference: whereas a `KeyBundle`'s role is a marker, a //! `KeyAmalgamation`'s role determines its semantics. A consequence //! of this is that it is not possible to convert a //! `PrimaryKeyAmalgamation` into a `SubordinateAmalgamation`s, or //! vice versa even though we support changing a `KeyBundle`'s role: //! //! ``` //! # fn main() -> sequoia_openpgp::Result<()> { //! # use std::convert::TryInto; //! # use sequoia_openpgp as openpgp; //! # use openpgp::cert::prelude::*; //! # use openpgp::packet::prelude::*; //! # let (cert, _) = CertBuilder::new() //! # .add_userid("Alice") //! # .add_signing_subkey() //! # .add_transport_encryption_subkey() //! # .generate()?; //! // This works: //! cert.primary_key().bundle().role_as_subordinate(); //! //! // But this doesn't: //! let ka: ErasedKeyAmalgamation<_> = cert.keys().nth(0).expect("primary key"); //! let ka: openpgp::Result<SubordinateKeyAmalgamation<key::PublicParts>> = ka.try_into(); //! assert!(ka.is_err()); //! # Ok(()) } //! ``` //! //! The use of the prefix `Erased` instead of `Unspecified` //! (cf. [`KeyRole::UnspecifiedRole`]) emphasizes this. //! //! # Selecting Keys //! //! It is essential to choose the right keys, and to make sure that //! they are appropriate. Below, we present some guidelines for the most //! common situations. //! //! ## Encrypting and Signing Messages //! //! As a general rule of thumb, when encrypting or signing a message, //! you want to use keys that are alive, not revoked, and have the //! appropriate capabilities right now. For example, the following //! code shows how to find a key, which is appropriate for signing a //! message: //! //! ```rust //! # use sequoia_openpgp as openpgp; //! # use openpgp::Result; //! # use openpgp::cert::prelude::*; //! use openpgp::types::RevocationStatus; //! use sequoia_openpgp::policy::StandardPolicy; //! //! # fn main() -> Result<()> { //! # let (cert, _) = //! # CertBuilder::general_purpose(Some("alice@example.org")) //! # .generate()?; //! # let mut i = 0; //! let p = &StandardPolicy::new(); //! //! let cert = cert.with_policy(p, None)?; //! //! if let RevocationStatus::Revoked(_) = cert.revocation_status() { //! // The certificate is revoked, don't use any keys from it. //! # unreachable!(); //! } else if let Err(_) = cert.alive() { //! // The certificate is not alive, don't use any keys from it. //! # unreachable!(); //! } else { //! for ka in cert.keys() { //! if let RevocationStatus::Revoked(_) = ka.revocation_status() { //! // The key is revoked. //! # unreachable!(); //! } else if let Err(_) = ka.alive() { //! // The key is not alive. //! # unreachable!(); //! } else if ! ka.for_signing() { //! // The key is not signing capable. //! } else { //! // Use it! //! # i += 1; //! } //! } //! } //! # assert_eq!(i, 1); //! # Ok(()) //! # } //! ``` //! //! ## Verifying a Message //! //! When verifying a message, you only want to use keys that were //! alive, not revoked, and signing capable *when the message was //! signed*. These are the keys that the signer would have used, and //! they reflect the signer's policy when they made the signature. //! (See the [`Policy` discussion] for an explanation.) //! //! For version 4 Signature packets, the `Signature Creation Time` //! subpacket indicates when the signature was allegedly created. For //! the purpose of finding the key to verify the signature, this time //! stamp should be trusted: if the key is authenticated and the //! signature is valid, then the time stamp is valid; if the signature //! is not valid, then forging the time stamp won't help an attacker. //! //! ```rust //! # use sequoia_openpgp as openpgp; //! # use openpgp::Result; //! # use openpgp::cert::prelude::*; //! use openpgp::types::RevocationStatus; //! use sequoia_openpgp::policy::StandardPolicy; //! //! # fn main() -> Result<()> { //! let p = &StandardPolicy::new(); //! //! # let (cert, _) = //! # CertBuilder::general_purpose(Some("alice@example.org")) //! # .generate()?; //! # let timestamp = None; //! # let issuer = cert.with_policy(p, None)?.keys() //! # .for_signing().nth(0).unwrap().key().fingerprint(); //! # let mut i = 0; //! let cert = cert.with_policy(p, timestamp)?; //! if let RevocationStatus::Revoked(_) = cert.revocation_status() { //! // The certificate is revoked, don't use any keys from it. //! # unreachable!(); //! } else if let Err(_) = cert.alive() { //! // The certificate is not alive, don't use any keys from it. //! # unreachable!(); //! } else { //! for ka in cert.keys().key_handle(issuer) { //! if let RevocationStatus::Revoked(_) = ka.revocation_status() { //! // The key is revoked, don't use it! //! # unreachable!(); //! } else if let Err(_) = ka.alive() { //! // The key was not alive when the signature was made! //! // Something fishy is going on. //! # unreachable!(); //! } else if ! ka.for_signing() { //! // The key was not signing capable! Better be safe //! // than sorry. //! # unreachable!(); //! } else { //! // Try verifying the message with this key. //! # i += 1; //! } //! } //! } //! # assert_eq!(i, 1); //! # Ok(()) //! # } //! ``` //! //! ## Decrypting a Message //! //! When decrypting a message, it seems like one ought to only use keys //! that were alive, not revoked, and encryption-capable when the //! message was encrypted. Unfortunately, we don't know when a //! message was encrypted. But anyway, due to the slow propagation of //! revocation certificates, we can't assume that senders won't //! mistakenly use a revoked key. //! //! However, wanting to decrypt a message encrypted using an expired //! or revoked key is reasonable. If someone is trying to decrypt a //! message using an expired key, then they are the certificate //! holder, and probably attempting to access archived data using a //! key that they themselves revoked! We don't want to prevent that. //! //! We do, however, want to check whether a key is really encryption //! capable. [This discussion] explains why using a signing key to //! decrypt a message can be dangerous. Since we need a binding //! signature to determine this, but we don't have the time that the //! message was encrypted, we need a workaround. One approach would //! be to check whether the key is encryption capable now. Since a //! key's key flags don't typically change, this will correctly filter //! out keys that are not encryption capable. But, it will skip keys //! whose self signature has expired. But that is not a problem //! either: no one sets self signatures to expire; if anything, they //! set keys to expire. Thus, this will not result in incorrectly //! failing to decrypt messages in practice, and is a reasonable //! approach. //! //! ```rust //! # use sequoia_openpgp as openpgp; //! # use openpgp::Result; //! # use openpgp::cert::prelude::*; //! use sequoia_openpgp::policy::StandardPolicy; //! //! # fn main() -> Result<()> { //! let p = &StandardPolicy::new(); //! //! # let (cert, _) = //! # CertBuilder::general_purpose(Some("alice@example.org")) //! # .generate()?; //! let decryption_keys = cert.keys().with_policy(p, None) //! .for_storage_encryption().for_transport_encryption() //! .collect::<Vec<_>>(); //! # Ok(()) //! # } //! ``` //! //! [`ComponentAmalgamation`]: super::ComponentAmalgamation //! [`Key`]: crate::packet::key //! [`Cert::keys`]: super::super::Cert::keys() //! [`PrimaryKeyAmalgamation`]: super::PrimaryKeyAmalgamation //! [`SubordinateKeyAmalgamation`]: super::SubordinateKeyAmalgamation //! [`ErasedKeyAmalgamation`]: super::ErasedKeyAmalgamation //! [`KeyRole::UnspecifiedRole`]: crate::packet::key::KeyRole //! [`Policy` discussion]: super //! [This discussion]: https://crypto.stackexchange.com/a/12138 use std::time; use std::time::SystemTime; use std::borrow::Borrow; use std::convert::TryFrom; use std::convert::TryInto; use anyhow::Context; use crate::{ Cert, cert::bundle::KeyBundle, cert::amalgamation::{ ComponentAmalgamation, key::signature::subpacket::SubpacketValue, ValidAmalgamation, ValidBindingSignature, ValidateAmalgamation, }, cert::ValidCert, crypto::Signer, Error, packet::Key, packet::key, packet::Signature, packet::signature, packet::signature::subpacket::SubpacketTag, policy::Policy, Result, seal, types::{ KeyFlags, RevocationKey, RevocationStatus, SignatureType, }, }; mod iter; pub use iter::{ KeyAmalgamationIter, ValidKeyAmalgamationIter, }; /// Whether the key is a primary key. /// /// This trait is an implementation detail. It exists so that we can /// have a blanket implementation of [`ValidAmalgamation`] for /// [`ValidKeyAmalgamation`], for instance, even though we only have /// specialized implementations of `PrimaryKey`. /// /// [`ValidAmalgamation`]: super::ValidAmalgamation /// /// # Sealed trait /// /// This trait is [sealed] and cannot be implemented for types outside this crate. /// Therefore it can be extended in a non-breaking way. /// If you want to implement the trait inside the crate /// you also need to implement the `seal::Sealed` marker trait. /// /// [sealed]: https://rust-lang.github.io/api-guidelines/future-proofing.html#sealed-traits-protect-against-downstream-implementations-c-sealed pub trait PrimaryKey<'a, P, R>: seal::Sealed where P: 'a + key::KeyParts, R: 'a + key::KeyRole, { /// Returns whether the key amalgamation is a primary key /// amalgamation. /// /// # Examples /// /// ``` /// # use sequoia_openpgp as openpgp; /// # use openpgp::cert::prelude::*; /// # use openpgp::policy::StandardPolicy; /// # /// # fn main() -> openpgp::Result<()> { /// # let p = &StandardPolicy::new(); /// # let (cert, _) = /// # CertBuilder::general_purpose(Some("alice@example.org")) /// # .generate()?; /// # let fpr = cert.fingerprint(); /// // This works if the type is concrete: /// let ka: PrimaryKeyAmalgamation<_> = cert.primary_key(); /// assert!(ka.primary()); /// /// // Or if it has been erased: /// for (i, ka) in cert.keys().enumerate() { /// let ka: ErasedKeyAmalgamation<_> = ka; /// if i == 0 { /// // The primary key is always the first key returned by /// // `Cert::keys`. /// assert!(ka.primary()); /// } else { /// // The rest are subkeys. /// assert!(! ka.primary()); /// } /// } /// # Ok(()) } /// ``` fn primary(&self) -> bool; } /// A key, and its associated data, and useful methods. /// /// A `KeyAmalgamation` is like a [`ComponentAmalgamation`], but /// specialized for keys. Due to the requirement to keep track of the /// key's role when it is erased ([see the module's documentation] for /// more details), this is a different data structure rather than a /// specialized type alias. /// /// Generally, you won't use this type directly, but instead use /// [`PrimaryKeyAmalgamation`], [`SubordinateKeyAmalgamation`], or /// [`ErasedKeyAmalgamation`]. /// /// A `KeyAmalgamation` is returned by [`Cert::primary_key`], and /// [`Cert::keys`]. /// /// `KeyAmalgamation` implements [`ValidateAmalgamation`], which /// allows you to turn a `KeyAmalgamation` into a /// [`ValidKeyAmalgamation`] using [`KeyAmalgamation::with_policy`]. /// /// # Examples /// /// Iterating over all keys: /// /// ``` /// # use sequoia_openpgp as openpgp; /// # use openpgp::cert::prelude::*; /// # use openpgp::policy::StandardPolicy; /// # /// # fn main() -> openpgp::Result<()> { /// # let p = &StandardPolicy::new(); /// # let (cert, _) = /// # CertBuilder::general_purpose(Some("alice@example.org")) /// # .generate()?; /// # let fpr = cert.fingerprint(); /// for ka in cert.keys() { /// let ka: ErasedKeyAmalgamation<_> = ka; /// } /// # Ok(()) /// # } /// ``` /// /// Getting the primary key: /// /// ``` /// # use sequoia_openpgp as openpgp; /// # use openpgp::cert::prelude::*; /// # use openpgp::policy::StandardPolicy; /// # /// # fn main() -> openpgp::Result<()> { /// # let p = &StandardPolicy::new(); /// # let (cert, _) = /// # CertBuilder::general_purpose(Some("alice@example.org")) /// # .generate()?; /// # let fpr = cert.fingerprint(); /// let ka: PrimaryKeyAmalgamation<_> = cert.primary_key(); /// # Ok(()) /// # } /// ``` /// /// Iterating over just the subkeys: /// /// ``` /// # use sequoia_openpgp as openpgp; /// # use openpgp::cert::prelude::*; /// # use openpgp::policy::StandardPolicy; /// # /// # fn main() -> openpgp::Result<()> { /// # let p = &StandardPolicy::new(); /// # let (cert, _) = /// # CertBuilder::general_purpose(Some("alice@example.org")) /// # .generate()?; /// # let fpr = cert.fingerprint(); /// // We can skip the primary key (it's always first): /// for ka in cert.keys().skip(1) { /// let ka: ErasedKeyAmalgamation<_> = ka; /// } /// /// // Or use `subkeys`, which returns a more accurate type: /// for ka in cert.keys().subkeys() { /// let ka: SubordinateKeyAmalgamation<_> = ka; /// } /// # Ok(()) /// # } /// ``` /// /// [`ComponentAmalgamation`]: super::ComponentAmalgamation /// [see the module's documentation]: self /// [`Cert::primary_key`]: crate::cert::Cert::primary_key() /// [`Cert::keys`]: crate::cert::Cert::keys() /// [`ValidateAmalgamation`]: super::ValidateAmalgamation /// [`KeyAmalgamation::with_policy`]: super::ValidateAmalgamation::with_policy() #[derive(Debug, PartialEq)] pub struct KeyAmalgamation<'a, P, R, R2> where P: 'a + key::KeyParts, R: 'a + key::KeyRole, { ca: ComponentAmalgamation<'a, Key<P, R>>, primary: R2, } assert_send_and_sync!(KeyAmalgamation<'_, P, R, R2> where P: key::KeyParts, R: key::KeyRole, R2, ); impl<'a, P, R> ComponentAmalgamation<'a, Key<P, R>> where P: key::KeyParts, R: key::KeyRole, { /// Returns a reference to the key. /// /// This is just a type-specific alias for /// [`ComponentAmalgamation::component`]. /// /// [`ComponentAmalgamation::component`]: ComponentAmalgamation::component() /// /// # Examples /// /// ``` /// # use sequoia_openpgp as openpgp; /// # use openpgp::cert::prelude::*; /// # /// # fn main() -> openpgp::Result<()> { /// # let (cert, _) = /// # CertBuilder::general_purpose(Some("alice@example.org")) /// # .generate()?; /// // Display some information about the keys. /// for ka in cert.keys() { /// eprintln!(" - {:?}", ka.key()); /// } /// # Ok(()) } /// ``` pub fn key(&self) -> &Key<P, R> { self.component() } pub(crate) fn set_role(&mut self, _: key::KeyRoleRT) { // The amalgamation only has an immutable reference, we cannot // change the role. } /// Forwarder for the conversion macros. pub(crate) fn has_secret(&self) -> bool { self.key().has_secret() } } // derive(Clone) doesn't work with generic parameters that don't // implement clone. But, we don't need to require that C implements // Clone, because we're not cloning C, just the reference. // // See: https://github.com/rust-lang/rust/issues/26925 impl<'a, P, R, R2> Clone for KeyAmalgamation<'a, P, R, R2> where P: 'a + key::KeyParts, R: 'a + key::KeyRole, R2: Copy, { fn clone(&self) -> Self { Self { ca: self.ca.clone(), primary: self.primary, } } } /// A primary key amalgamation. /// /// A specialized version of [`KeyAmalgamation`]. /// pub type PrimaryKeyAmalgamation<'a, P> = KeyAmalgamation<'a, P, key::PrimaryRole, ()>; /// A subordinate key amalgamation. /// /// A specialized version of [`KeyAmalgamation`]. /// pub type SubordinateKeyAmalgamation<'a, P> = KeyAmalgamation<'a, P, key::SubordinateRole, ()>; impl<'a, P> SubordinateKeyAmalgamation<'a, P> where P: key::KeyParts, { /// Returns the subkey's revocation status at time `t`. /// /// A subkey is revoked at time `t` if: /// /// - There is a live revocation at time `t` that is newer than /// all live self signatures at time `t`, or /// /// - There is a hard revocation (even if it is not live at /// time `t`, and even if there is a newer self-signature). /// /// Note: Certs and subkeys have different criteria from User IDs /// and User Attributes. /// /// Note: this only returns whether this subkey is revoked; it /// does not imply anything about the Cert or other components. /// /// # Examples /// /// ``` /// # use sequoia_openpgp as openpgp; /// # use openpgp::cert::prelude::*; /// use openpgp::policy::StandardPolicy; /// # /// # fn main() -> openpgp::Result<()> { /// let p = &StandardPolicy::new(); /// /// # let (cert, _) = /// # CertBuilder::general_purpose(Some("alice@example.org")) /// # .generate()?; /// // Display the subkeys' revocation status. /// for ka in cert.keys().subkeys() { /// eprintln!(" Revocation status of {}: {:?}", /// ka.key().fingerprint(), ka.revocation_status(p, None)); /// } /// # Ok(()) } /// ``` pub fn revocation_status<T>(&self, policy: &dyn Policy, t: T) -> RevocationStatus where T: Into<Option<time::SystemTime>>, { let t = t.into(); self.bundle().revocation_status(policy, t) } } /// An amalgamation whose role is not known at compile time. /// /// A specialized version of [`KeyAmalgamation`]. /// /// Unlike a [`Key`] or a [`KeyBundle`] with an unspecified role, an /// `ErasedKeyAmalgamation` remembers its role; it is just not exposed /// to the type system. For details, see the [module-level /// documentation]. /// /// [`Key`]: crate::packet::key /// [`KeyBundle`]: super::super::bundle /// [module-level documentation]: self pub type ErasedKeyAmalgamation<'a, P> = KeyAmalgamation<'a, P, key::UnspecifiedRole, bool>; impl<'a, P> seal::Sealed for PrimaryKeyAmalgamation<'a, P> where P: 'a + key::KeyParts {} impl<'a, P> ValidateAmalgamation<'a, Key<P, key::PrimaryRole>> for PrimaryKeyAmalgamation<'a, P> where P: 'a + key::KeyParts { type V = ValidPrimaryKeyAmalgamation<'a, P>; fn with_policy<T>(&self, policy: &'a dyn Policy, time: T) -> Result<Self::V> where T: Into<Option<time::SystemTime>> { let ka : ErasedKeyAmalgamation<P> = self.clone().into(); Ok(ka.with_policy(policy, time)? .try_into().expect("conversion is symmetric")) } } impl<'a, P> seal::Sealed for SubordinateKeyAmalgamation<'a, P> where P: 'a + key::KeyParts {} impl<'a, P> ValidateAmalgamation<'a, Key<P, key::SubordinateRole>> for SubordinateKeyAmalgamation<'a, P> where P: 'a + key::KeyParts { type V = ValidSubordinateKeyAmalgamation<'a, P>; fn with_policy<T>(&self, policy: &'a dyn Policy, time: T) -> Result<Self::V> where T: Into<Option<time::SystemTime>> { let ka : ErasedKeyAmalgamation<P> = self.clone().into(); Ok(ka.with_policy(policy, time)? .try_into().expect("conversion is symmetric")) } } impl<'a, P> seal::Sealed for ErasedKeyAmalgamation<'a, P> where P: 'a + key::KeyParts {} impl<'a, P> ValidateAmalgamation<'a, Key<P, key::UnspecifiedRole>> for ErasedKeyAmalgamation<'a, P> where P: 'a + key::KeyParts { type V = ValidErasedKeyAmalgamation<'a, P>; fn with_policy<T>(&self, policy: &'a dyn Policy, time: T) -> Result<Self::V> where T: Into<Option<time::SystemTime>> { let time = time.into().unwrap_or_else(crate::now); // We need to make sure the certificate is okay. This means // checking the primary key. But, be careful: we don't need // to double-check. if ! self.primary() { let pka = PrimaryKeyAmalgamation::new(self.cert()); pka.with_policy(policy, time).context("primary key")?; } let binding_signature = self.binding_signature(policy, time)?; let cert = self.ca.cert(); let vka = ValidErasedKeyAmalgamation { ka: KeyAmalgamation { ca: self.ca.clone().parts_into_public(), primary: self.primary, }, // We need some black magic to avoid infinite // recursion: a ValidCert must be valid for the // specified policy and reference time. A ValidCert // is considered valid if the primary key is valid. // ValidCert::with_policy checks that by calling this // function. So, if we call ValidCert::with_policy // here we'll recurse infinitely. // // But, hope is not lost! We know that if we get // here, we've already checked that the primary key is // valid (see above), or that we're in the process of // evaluating the primary key's validity and we just // need to check the user's policy. So, it is safe to // create a ValidCert from scratch. cert: ValidCert { cert, policy, time, }, binding_signature }; policy.key(&vka)?; Ok(ValidErasedKeyAmalgamation { ka: KeyAmalgamation { ca: P::convert_key_amalgamation( vka.ka.ca.parts_into_unspecified()).expect("roundtrip"), primary: vka.ka.primary, }, cert: vka.cert, binding_signature, }) } } impl<'a, P> PrimaryKey<'a, P, key::PrimaryRole> for PrimaryKeyAmalgamation<'a, P> where P: 'a + key::KeyParts { fn primary(&self) -> bool { true } } impl<'a, P> PrimaryKey<'a, P, key::SubordinateRole> for SubordinateKeyAmalgamation<'a, P> where P: 'a + key::KeyParts { fn primary(&self) -> bool { false } } impl<'a, P> PrimaryKey<'a, P, key::UnspecifiedRole> for ErasedKeyAmalgamation<'a, P> where P: 'a + key::KeyParts { fn primary(&self) -> bool { self.primary } } impl<'a, P: 'a + key::KeyParts> From<PrimaryKeyAmalgamation<'a, P>> for ErasedKeyAmalgamation<'a, P> { fn from(ka: PrimaryKeyAmalgamation<'a, P>) -> Self { ErasedKeyAmalgamation { ca: ka.ca.role_into_unspecified(), primary: true, } } } impl<'a, P: 'a + key::KeyParts> From<SubordinateKeyAmalgamation<'a, P>> for ErasedKeyAmalgamation<'a, P> { fn from(ka: SubordinateKeyAmalgamation<'a, P>) -> Self { ErasedKeyAmalgamation { ca: ka.ca.role_into_unspecified(), primary: false, } } } // We can infallibly convert part X to part Y for everything but // Public -> Secret and Unspecified -> Secret. macro_rules! impl_conversion { ($s:ident, $primary:expr, $p1:path, $p2:path) => { impl<'a> From<$s<'a, $p1>> for ErasedKeyAmalgamation<'a, $p2> { fn from(ka: $s<'a, $p1>) -> Self { ErasedKeyAmalgamation { ca: ka.ca.into(), primary: $primary, } } } } } impl_conversion!(PrimaryKeyAmalgamation, true, key::SecretParts, key::PublicParts); impl_conversion!(PrimaryKeyAmalgamation, true, key::SecretParts, key::UnspecifiedParts); impl_conversion!(PrimaryKeyAmalgamation, true, key::PublicParts, key::UnspecifiedParts); impl_conversion!(PrimaryKeyAmalgamation, true, key::UnspecifiedParts, key::PublicParts); impl_conversion!(SubordinateKeyAmalgamation, false, key::SecretParts, key::PublicParts); impl_conversion!(SubordinateKeyAmalgamation, false, key::SecretParts, key::UnspecifiedParts); impl_conversion!(SubordinateKeyAmalgamation, false, key::PublicParts, key::UnspecifiedParts); impl_conversion!(SubordinateKeyAmalgamation, false, key::UnspecifiedParts, key::PublicParts); impl<'a, P, P2> TryFrom<ErasedKeyAmalgamation<'a, P>> for PrimaryKeyAmalgamation<'a, P2> where P: 'a + key::KeyParts, P2: 'a + key::KeyParts, { type Error = anyhow::Error; fn try_from(ka: ErasedKeyAmalgamation<'a, P>) -> Result<Self> { if ka.primary { Ok(Self { ca: P2::convert_key_amalgamation( ka.ca.role_into_primary().parts_into_unspecified())?, primary: (), }) } else { Err(Error::InvalidArgument( "can't convert a SubordinateKeyAmalgamation \ to a PrimaryKeyAmalgamation".into()).into()) } } } impl<'a, P, P2> TryFrom<ErasedKeyAmalgamation<'a, P>> for SubordinateKeyAmalgamation<'a, P2> where P: 'a + key::KeyParts, P2: 'a + key::KeyParts, { type Error = anyhow::Error; fn try_from(ka: ErasedKeyAmalgamation<'a, P>) -> Result<Self> { if ka.primary { Err(Error::InvalidArgument( "can't convert a PrimaryKeyAmalgamation \ to a SubordinateKeyAmalgamation".into()).into()) } else { Ok(Self { ca: P2::convert_key_amalgamation( ka.ca.role_into_subordinate().parts_into_unspecified())?, primary: (), }) } } } impl<'a> PrimaryKeyAmalgamation<'a, key::PublicParts> { pub(crate) fn new(cert: &'a Cert) -> Self { PrimaryKeyAmalgamation { ca: ComponentAmalgamation::new(cert, &cert.primary), primary: (), } } } impl<'a, P> PrimaryKeyAmalgamation<'a, P> where P: key::KeyParts, { /// Returns the active binding signature at time `t`. /// /// The active binding signature is the most recent, non-revoked /// self-signature that is valid according to the `policy` and /// alive at time `t` (`creation time <= t`, `t < expiry`). If /// there are multiple such signatures then the signatures are /// ordered by their MPIs interpreted as byte strings. /// /// # Examples /// /// ``` /// # use sequoia_openpgp as openpgp; /// # use openpgp::cert::prelude::*; /// use openpgp::policy::StandardPolicy; /// # /// # fn main() -> openpgp::Result<()> { /// let p = &StandardPolicy::new(); /// /// # let (cert, _) = /// # CertBuilder::general_purpose(Some("alice@example.org")) /// # .generate()?; /// // Display information about the primary key's current active /// // binding signature (the `time` parameter is `None`), if any. /// eprintln!("{:?}", cert.primary_key().binding_signature(p, None)); /// # Ok(()) } /// ``` pub fn binding_signature<T>(&self, policy: &dyn Policy, time: T) -> Result<&'a Signature> where T: Into<Option<time::SystemTime>> { let time = time.into().unwrap_or_else(crate::now); self.bundle().binding_signature(policy, time) } } impl<'a, P: 'a + key::KeyParts> SubordinateKeyAmalgamation<'a, P> { pub(crate) fn new( cert: &'a Cert, bundle: &'a KeyBundle<P, key::SubordinateRole>) -> Self { SubordinateKeyAmalgamation { ca: ComponentAmalgamation::new(cert, bundle), primary: (), } } /// Returns the active binding signature at time `t`. /// /// The active binding signature is the most recent, non-revoked /// self-signature that is valid according to the `policy` and /// alive at time `t` (`creation time <= t`, `t < expiry`). If /// there are multiple such signatures then the signatures are /// ordered by their MPIs interpreted as byte strings. /// /// # Examples /// /// ``` /// # use sequoia_openpgp as openpgp; /// # use openpgp::cert::prelude::*; /// use openpgp::policy::StandardPolicy; /// # /// # fn main() -> openpgp::Result<()> { /// let p = &StandardPolicy::new(); /// /// # let (cert, _) = /// # CertBuilder::general_purpose(Some("alice@example.org")) /// # .generate()?; /// // Display information about each keys' current active /// // binding signature (the `time` parameter is `None`), if any. /// for k in cert.keys().subkeys() { /// eprintln!("{:?}", k.binding_signature(p, None)); /// } /// # Ok(()) } /// ``` pub fn binding_signature<T>(&self, policy: &dyn Policy, time: T) -> Result<&'a Signature> where T: Into<Option<time::SystemTime>> { let time = time.into().unwrap_or_else(crate::now); self.bundle().binding_signature(policy, time) } } impl<'a, P: 'a + key::KeyParts> ErasedKeyAmalgamation<'a, P> { /// Returns the key's binding signature as of the reference time, /// if any. /// /// Note: this function is not exported. Users of this interface /// should instead do: `ka.with_policy(policy, /// time)?.binding_signature()`. fn binding_signature<T>(&self, policy: &'a dyn Policy, time: T) -> Result<&'a Signature> where T: Into<Option<time::SystemTime>> { let time = time.into().unwrap_or_else(crate::now); if self.primary { self.cert().primary_userid_relaxed(policy, time, false) .map(|u| u.binding_signature()) .or_else(|e0| { // Lookup of the primary user id binding failed. // Look for direct key signatures. self.cert().primary_key().bundle() .binding_signature(policy, time) .map_err(|e1| { // Both lookups failed. Keep the more // meaningful error. if let Some(Error::NoBindingSignature(_)) = e1.downcast_ref() { e0 // Return the original error. } else { e1 } }) }) } else { self.bundle().binding_signature(policy, time) } } } impl<'a, P, R, R2> KeyAmalgamation<'a, P, R, R2> where P: 'a + key::KeyParts, R: 'a + key::KeyRole, { /// Returns the component's associated certificate. /// /// ``` /// # use sequoia_openpgp as openpgp; /// # use openpgp::cert::prelude::*; /// # /// # fn main() -> openpgp::Result<()> { /// # let (cert, _) = /// # CertBuilder::general_purpose(Some("alice@example.org")) /// # .generate()?; /// for k in cert.keys() { /// // It's not only an identical `Cert`, it's the same one. /// assert!(std::ptr::eq(k.cert(), &cert)); /// } /// # Ok(()) } /// ``` pub fn cert(&self) -> &'a Cert { self.ca.cert() } /// Returns this amalgamation's bundle. pub fn bundle(&self) -> &'a crate::cert::ComponentBundle<Key<P, R>> { self.ca.bundle() } /// Returns this amalgamation's component. /// /// # Examples /// /// ``` /// # use sequoia_openpgp as openpgp; /// # use openpgp::cert::prelude::*; /// # /// # fn main() -> openpgp::Result<()> { /// # let (cert, _) = /// # CertBuilder::general_purpose(Some("alice@example.org")) /// # .generate()?; /// // Display some information about any unknown components. /// for k in cert.keys() { /// eprintln!(" - {:?}", k.component()); /// } /// # Ok(()) } /// ``` pub fn component(&self) -> &'a Key<P, R> { self.bundle().component() } /// Returns the `KeyAmalgamation`'s key. pub fn key(&self) -> &'a Key<P, R> { self.component() } /// Returns the component's self-signatures. /// /// The signatures are validated, and they are sorted by their /// creation time, most recent first. /// /// # Examples /// /// ``` /// # use sequoia_openpgp as openpgp; /// # use openpgp::cert::prelude::*; /// # /// # fn main() -> openpgp::Result<()> { /// # let (cert, _) = /// # CertBuilder::general_purpose(Some("alice@example.org")) /// # .generate()?; /// for (i, ka) in cert.keys().enumerate() { /// eprintln!("Key #{} ({}) has {:?} self signatures", /// i, ka.key().fingerprint(), /// ka.self_signatures().count()); /// } /// # Ok(()) } /// ``` pub fn self_signatures(&self) -> impl Iterator<Item=&'a Signature> + Send + Sync { self.ca.self_signatures() } /// Returns the component's third-party certifications. /// /// The signatures are *not* validated. They are sorted by their /// creation time, most recent first. /// /// # Examples /// /// ``` /// # use sequoia_openpgp as openpgp; /// # use openpgp::cert::prelude::*; /// # /// # fn main() -> openpgp::Result<()> { /// # let (cert, _) = /// # CertBuilder::general_purpose(Some("alice@example.org")) /// # .generate()?; /// for k in cert.keys() { /// eprintln!("Key {} has {:?} unverified, third-party certifications", /// k.key().fingerprint(), /// k.certifications().count()); /// } /// # Ok(()) } /// ``` pub fn certifications(&self) -> impl Iterator<Item=&'a Signature> + Send + Sync { self.ca.certifications() } /// Returns the component's revocations that were issued by the /// certificate holder. /// /// The revocations are validated, and they are sorted by their /// creation time, most recent first. /// /// # Examples /// /// ``` /// # use sequoia_openpgp as openpgp; /// # use openpgp::cert::prelude::*; /// use openpgp::policy::StandardPolicy; /// # /// # fn main() -> openpgp::Result<()> { /// let p = &StandardPolicy::new(); /// /// # let (cert, _) = /// # CertBuilder::general_purpose(Some("alice@example.org")) /// # .generate()?; /// for k in cert.keys() { /// eprintln!("Key {} has {:?} revocation certificates.", /// k.key().fingerprint(), /// k.self_revocations().count()); /// } /// # Ok(()) } /// ``` pub fn self_revocations(&self) -> impl Iterator<Item=&'a Signature> + Send + Sync { self.ca.self_revocations() } /// Returns the component's revocations that were issued by other /// certificates. /// /// The revocations are *not* validated. They are sorted by their /// creation time, most recent first. /// /// # Examples /// /// ``` /// # use sequoia_openpgp as openpgp; /// # use openpgp::cert::prelude::*; /// use openpgp::policy::StandardPolicy; /// # /// # fn main() -> openpgp::Result<()> { /// let p = &StandardPolicy::new(); /// /// # let (cert, _) = /// # CertBuilder::general_purpose(Some("alice@example.org")) /// # .generate()?; /// for k in cert.keys() { /// eprintln!("Key {} has {:?} unverified, third-party revocation certificates.", /// k.key().fingerprint(), /// k.other_revocations().count()); /// } /// # Ok(()) } /// ``` pub fn other_revocations(&self) -> impl Iterator<Item=&'a Signature> + Send + Sync { self.ca.other_revocations() } /// Returns all of the component's signatures. /// /// Only the self-signatures are validated. The signatures are /// sorted first by type, then by creation time. The self /// revocations come first, then the self signatures, /// then any certification approval key signatures, /// certifications, and third-party revocations coming last. This /// function may return additional types of signatures that could /// be associated to this component. /// /// # Examples /// /// ``` /// # use sequoia_openpgp as openpgp; /// # use openpgp::cert::prelude::*; /// use openpgp::policy::StandardPolicy; /// # /// # fn main() -> openpgp::Result<()> { /// let p = &StandardPolicy::new(); /// /// # let (cert, _) = /// # CertBuilder::general_purpose(Some("alice@example.org")) /// # .generate()?; /// for (i, ka) in cert.keys().enumerate() { /// eprintln!("Key #{} ({}) has {:?} signatures", /// i, ka.key().fingerprint(), /// ka.signatures().count()); /// } /// # Ok(()) } /// ``` pub fn signatures(&self) -> impl Iterator<Item = &'a Signature> + Send + Sync { self.ca.signatures() } /// Forwarder for the conversion macros. pub(crate) fn has_secret(&self) -> bool { self.key().has_secret() } } impl<'a, P, R, R2> KeyAmalgamation<'a, P, R, R2> where Self: PrimaryKey<'a, P, R>, P: 'a + key::KeyParts, R: 'a + key::KeyRole, { /// Returns the third-party certifications issued by the specified /// key, and valid at the specified time. /// /// This function returns the certifications issued by the /// specified key. Specifically, it returns a certification if: /// /// - it is well-formed, /// - it is live with respect to the reference time, /// - it conforms to the policy, and /// - the signature is cryptographically valid. /// /// This method is implemented on a [`KeyAmalgamation`] and not /// a [`ValidKeyAmalgamation`], because a third-party /// certification does not require the key to be self-signed. /// /// # Examples /// /// Alice has certified that a certificate belongs to Bob on two /// occasions. Whereas /// [`KeyAmalgamation::valid_certifications_by_key`] returns /// both certifications, /// [`KeyAmalgamation::active_certifications_by_key`] only /// returns the most recent certification. /// /// ```rust /// use sequoia_openpgp as openpgp; /// use openpgp::cert::prelude::*; /// # use openpgp::packet::signature::SignatureBuilder; /// # use openpgp::packet::UserID; /// use openpgp::policy::StandardPolicy; /// # use openpgp::types::SignatureType; /// /// const P: &StandardPolicy = &StandardPolicy::new(); /// /// # fn main() -> openpgp::Result<()> { /// # let epoch = std::time::SystemTime::now() /// # - std::time::Duration::new(100, 0); /// # let t0 = epoch; /// # /// # let (alice, _) = CertBuilder::new() /// # .set_creation_time(t0) /// # .add_userid("<alice@example.org>") /// # .generate() /// # .unwrap(); /// let alice: Cert = // ... /// # alice; /// # /// # let bob_userid = "<bob@example.org>"; /// # let (bob, _) = CertBuilder::new() /// # .set_creation_time(t0) /// # .add_userid(bob_userid) /// # .generate() /// # .unwrap(); /// let bob: Cert = // ... /// # bob; /// /// # // Alice has not certified Bob's User ID. /// # let ka = bob.primary_key(); /// # assert_eq!( /// # ka.active_certifications_by_key( /// # P, t0, alice.primary_key().key()).count(), /// # 0); /// # /// # // Have Alice certify Bob's certificate. /// # let mut alice_signer = alice /// # .keys() /// # .with_policy(P, None) /// # .for_certification() /// # .next().expect("have a certification-capable key") /// # .key() /// # .clone() /// # .parts_into_secret().expect("have unencrypted key material") /// # .into_keypair().expect("have unencrypted key material"); /// # /// # let mut bob = bob; /// # for i in 1..=2usize { /// # let ti = t0 + std::time::Duration::new(i as u64, 0); /// # /// # let certification = SignatureBuilder::new(SignatureType::DirectKey) /// # .set_signature_creation_time(ti)? /// # .sign_direct_key( /// # &mut alice_signer, /// # bob.primary_key().key())?; /// # bob = bob.insert_packets(certification)?.0; /// # /// # let ka = bob.primary_key(); /// # assert_eq!( /// # ka.valid_certifications_by_key( /// # P, ti, alice.primary_key().key()).count(), /// # i); /// # /// # assert_eq!( /// # ka.active_certifications_by_key( /// # P, ti, alice.primary_key().key()).count(), /// # 1); /// # } /// let bob_pk = bob.primary_key(); /// /// let valid_certifications = bob_pk.valid_certifications_by_key( /// P, None, alice.primary_key().key()); /// // Alice certified Bob's certificate twice. /// assert_eq!(valid_certifications.count(), 2); /// /// let active_certifications = bob_pk.active_certifications_by_key( /// P, None, alice.primary_key().key()); /// // But only the most recent one is active. /// assert_eq!(active_certifications.count(), 1); /// # Ok(()) } /// ``` pub fn valid_certifications_by_key<T, PK>(&self, policy: &'a dyn Policy, reference_time: T, issuer: PK) -> impl Iterator<Item=&Signature> + Send + Sync where T: Into<Option<time::SystemTime>>, PK: Into<&'a Key<key::PublicParts, key::UnspecifiedRole>>, { let reference_time = reference_time.into(); let issuer = issuer.into(); let primary = self.primary(); self.ca.valid_certifications_by_key_( policy, reference_time, issuer, false, self.certifications(), move |sig| { if primary { sig.clone().verify_direct_key( issuer, self.component().role_as_primary()) } else { sig.clone().verify_subkey_binding( issuer, self.cert().primary_key().key(), self.component().role_as_subordinate()) } }) } /// Returns any active third-party certifications issued by the /// specified key. /// /// This function is like /// [`KeyAmalgamation::valid_certifications_by_key`], but it /// only returns active certifications. Active certifications are /// the most recent valid certifications with respect to the /// reference time. /// /// Although there is normally only a single active certification, /// there can be multiple certifications with the same timestamp. /// In this case, all of them are returned. /// /// Unlike self-signatures, multiple third-party certifications /// issued by the same key at the same time can be sensible. For /// instance, Alice may fully trust a CA for user IDs in a /// particular domain, and partially trust it for everything else. /// This can only be expressed using multiple certifications. /// /// This method is implemented on a [`KeyAmalgamation`] and not /// a [`ValidKeyAmalgamation`], because a third-party /// certification does not require the user ID to be self-signed. /// /// # Examples /// /// See the examples for /// [`KeyAmalgamation::valid_certifications_by_key`]. pub fn active_certifications_by_key<T, PK>(&self, policy: &'a dyn Policy, reference_time: T, issuer: PK) -> impl Iterator<Item=&Signature> + Send + Sync where T: Into<Option<time::SystemTime>>, PK: Into<&'a Key<key::PublicParts, key::UnspecifiedRole>>, { let reference_time = reference_time.into(); let issuer = issuer.into(); let primary = self.primary(); self.ca.valid_certifications_by_key_( policy, reference_time, issuer, true, self.certifications(), move |sig| { if primary { sig.clone().verify_direct_key( issuer, self.component().role_as_primary()) } else { sig.clone().verify_subkey_binding( issuer, self.cert().primary_key().key(), &self.component().role_as_subordinate()) } }) } /// Returns the third-party revocations issued by the specified /// key, and valid at the specified time. /// /// This function returns the revocations issued by the specified /// key. Specifically, it returns a revocation if: /// /// - it is well-formed, /// - it is a [hard revocation](crate::types::RevocationType), /// or it is live with respect to the reference time, /// - it conforms to the policy, and /// - the signature is cryptographically valid. /// /// This method is implemented on a [`KeyAmalgamation`] and not /// a [`ValidKeyAmalgamation`], because a third-party /// revocation does not require the key to be self-signed. /// /// # Examples /// /// Alice revoked Bob's certificate. /// /// ```rust /// use sequoia_openpgp as openpgp; /// use openpgp::cert::prelude::*; /// # use openpgp::Packet; /// # use openpgp::packet::signature::SignatureBuilder; /// # use openpgp::packet::UserID; /// use openpgp::policy::StandardPolicy; /// # use openpgp::types::ReasonForRevocation; /// # use openpgp::types::SignatureType; /// /// const P: &StandardPolicy = &StandardPolicy::new(); /// /// # fn main() -> openpgp::Result<()> { /// # let epoch = std::time::SystemTime::now() /// # - std::time::Duration::new(100, 0); /// # let t0 = epoch; /// # let t1 = epoch + std::time::Duration::new(1, 0); /// # /// # let (alice, _) = CertBuilder::new() /// # .set_creation_time(t0) /// # .add_userid("<alice@example.org>") /// # .generate() /// # .unwrap(); /// let alice: Cert = // ... /// # alice; /// # /// # let bob_userid = "<bob@example.org>"; /// # let (bob, _) = CertBuilder::new() /// # .set_creation_time(t0) /// # .add_userid(bob_userid) /// # .generate() /// # .unwrap(); /// let bob: Cert = // ... /// # bob; /// /// # // Have Alice certify Bob's certificate. /// # let mut alice_signer = alice /// # .keys() /// # .with_policy(P, None) /// # .for_certification() /// # .next().expect("have a certification-capable key") /// # .key() /// # .clone() /// # .parts_into_secret().expect("have unencrypted key material") /// # .into_keypair().expect("have unencrypted key material"); /// # /// # let certification = SignatureBuilder::new(SignatureType::KeyRevocation) /// # .set_signature_creation_time(t1)? /// # .set_reason_for_revocation( /// # ReasonForRevocation::KeyRetired, b"")? /// # .sign_direct_key( /// # &mut alice_signer, /// # bob.primary_key().key())?; /// # let bob = bob.insert_packets(certification)?.0; /// let ka = bob.primary_key(); /// /// let revs = ka.valid_third_party_revocations_by_key( /// P, None, alice.primary_key().key()); /// // Alice revoked Bob's certificate. /// assert_eq!(revs.count(), 1); /// # Ok(()) } /// ``` pub fn valid_third_party_revocations_by_key<T, PK>(&self, policy: &'a dyn Policy, reference_time: T, issuer: PK) -> impl Iterator<Item=&Signature> + Send + Sync where T: Into<Option<time::SystemTime>>, PK: Into<&'a Key<key::PublicParts, key::UnspecifiedRole>>, { let issuer = issuer.into(); let reference_time = reference_time.into(); let primary = self.primary(); self.ca.valid_certifications_by_key_( policy, reference_time, issuer, false, self.other_revocations(), move |sig| { if primary { sig.clone().verify_primary_key_revocation( issuer, self.component().role_as_primary()) } else { sig.clone().verify_subkey_revocation( issuer, self.cert().primary_key().key(), &self.component().role_as_subordinate()) } }) } } /// A `KeyAmalgamation` plus a `Policy` and a reference time. /// /// In the same way that a [`ValidComponentAmalgamation`] extends a /// [`ComponentAmalgamation`], a `ValidKeyAmalgamation` extends a /// [`KeyAmalgamation`]: a `ValidKeyAmalgamation` combines a /// `KeyAmalgamation`, a [`Policy`], and a reference time. This /// allows it to implement the [`ValidAmalgamation`] trait, which /// provides methods like [`ValidAmalgamation::binding_signature`] that require a /// `Policy` and a reference time. Although `KeyAmalgamation` could /// implement these methods by requiring that the caller explicitly /// pass them in, embedding them in the `ValidKeyAmalgamation` helps /// ensure that multipart operations, even those that span multiple /// functions, use the same `Policy` and reference time. /// /// A `ValidKeyAmalgamation` can be obtained by transforming a /// `KeyAmalgamation` using [`ValidateAmalgamation::with_policy`]. A /// [`KeyAmalgamationIter`] can also be changed to yield /// `ValidKeyAmalgamation`s. /// /// A `ValidKeyAmalgamation` is guaranteed to come from a valid /// certificate, and have a valid and live *binding* signature at the /// specified reference time. Note: this only means that the binding /// signatures are live; it says nothing about whether the /// *certificate* or the *`Key`* is live and non-revoked. If you care /// about those things, you need to check them separately. /// /// # Examples: /// /// Find all non-revoked, live, signing-capable keys: /// /// ``` /// # use sequoia_openpgp as openpgp; /// # use openpgp::cert::prelude::*; /// use openpgp::policy::StandardPolicy; /// use openpgp::types::RevocationStatus; /// /// # fn main() -> openpgp::Result<()> { /// let p = &StandardPolicy::new(); /// /// # let (cert, _) = CertBuilder::new() /// # .add_userid("Alice") /// # .add_signing_subkey() /// # .add_transport_encryption_subkey() /// # .generate().unwrap(); /// // `with_policy` ensures that the certificate and any components /// // that it returns have valid *binding signatures*. But, we still /// // need to check that the certificate and `Key` are not revoked, /// // and live. /// // /// // Note: `ValidKeyAmalgamation::revocation_status`, etc. use the /// // embedded policy and timestamp. Even though we used `None` for /// // the timestamp (i.e., now), they are guaranteed to use the same /// // timestamp, because `with_policy` eagerly transforms it into /// // the current time. /// let cert = cert.with_policy(p, None)?; /// if let RevocationStatus::Revoked(_revs) = cert.revocation_status() { /// // Revoked by the certificate holder. (If we care about /// // designated revokers, then we need to check those /// // ourselves.) /// # unreachable!(); /// } else if let Err(_err) = cert.alive() { /// // Certificate was created in the future or is expired. /// # unreachable!(); /// } else { /// // `ValidCert::keys` returns `ValidKeyAmalgamation`s. /// for ka in cert.keys() { /// if let RevocationStatus::Revoked(_revs) = ka.revocation_status() { /// // Revoked by the key owner. (If we care about /// // designated revokers, then we need to check those /// // ourselves.) /// # unreachable!(); /// } else if let Err(_err) = ka.alive() { /// // Key was created in the future or is expired. /// # unreachable!(); /// } else if ! ka.for_signing() { /// // We're looking for a signing-capable key, skip this one. /// } else { /// // Use it! /// } /// } /// } /// # Ok(()) } /// ``` /// /// [`ValidComponentAmalgamation`]: super::ValidComponentAmalgamation /// [`ComponentAmalgamation`]: super::ComponentAmalgamation /// [`Policy`]: crate::policy::Policy /// [`ValidAmalgamation`]: super::ValidAmalgamation /// [`ValidAmalgamation::binding_signature`]: super::ValidAmalgamation::binding_signature() /// [`ValidateAmalgamation::with_policy`]: super::ValidateAmalgamation::with_policy #[derive(Debug, Clone)] pub struct ValidKeyAmalgamation<'a, P, R, R2> where P: 'a + key::KeyParts, R: 'a + key::KeyRole, R2: Copy, { // Ouch, ouch, ouch! ka is a `KeyAmalgamation`, which contains a // reference to a `Cert`. `cert` is a `ValidCert` and contains a // reference to the same `Cert`! We do this so that // `ValidKeyAmalgamation` can deref to a `KeyAmalgamation` and // `ValidKeyAmalgamation::cert` can return a `&ValidCert`. ka: KeyAmalgamation<'a, P, R, R2>, cert: ValidCert<'a>, // The binding signature at time `time`. (This is just a cache.) binding_signature: &'a Signature, } assert_send_and_sync!(ValidKeyAmalgamation<'_, P, R, R2> where P: key::KeyParts, R: key::KeyRole, R2: Copy, ); impl<'a, P, R, R2> ValidKeyAmalgamation<'a, P, R, R2> where P: 'a + key::KeyParts, R: 'a + key::KeyRole, R2: Copy, { /// Returns the component's associated certificate. /// /// ``` /// # use sequoia_openpgp as openpgp; /// # use openpgp::cert::prelude::*; /// use openpgp::policy::StandardPolicy; /// # /// # fn main() -> openpgp::Result<()> { /// let p = &StandardPolicy::new(); /// /// # let (cert, _) = /// # CertBuilder::general_purpose(Some("alice@example.org")) /// # .generate()?; /// for k in cert.with_policy(p, None)?.keys() { /// // It's not only an identical `Cert`, it's the same one. /// assert!(std::ptr::eq(k.cert(), &cert)); /// } /// # Ok(()) } /// ``` pub fn cert(&self) -> &'a Cert { self.ka.cert() } /// Returns the valid amalgamation's active binding signature. /// /// The active binding signature is the most recent, non-revoked /// self-signature that is valid according to the `policy` and /// alive at time `t` (`creation time <= t`, `t < expiry`). If /// there are multiple such signatures then the signatures are /// ordered by their MPIs interpreted as byte strings. /// /// # Examples /// /// ``` /// # use sequoia_openpgp as openpgp; /// # use openpgp::cert::prelude::*; /// use openpgp::policy::StandardPolicy; /// # /// # fn main() -> openpgp::Result<()> { /// let p = &StandardPolicy::new(); /// /// # let (cert, _) = /// # CertBuilder::general_purpose(Some("alice@example.org")) /// # .generate()?; /// // Display information about each User ID's current active /// // binding signature (the `time` parameter is `None`), if any. /// for ua in cert.with_policy(p, None)?.userids() { /// eprintln!("{:?}", ua.binding_signature()); /// } /// # Ok(()) } /// ``` pub fn binding_signature(&self) -> &'a Signature { self.binding_signature } /// Returns the valid amalgamation's amalgamation. /// /// # Examples /// /// ``` /// # use sequoia_openpgp as openpgp; /// # use openpgp::cert::prelude::*; /// use openpgp::policy::StandardPolicy; /// /// # fn main() -> openpgp::Result<()> { /// let p = &StandardPolicy::new(); /// /// # let (cert, _) = CertBuilder::new() /// # .add_userid("Alice") /// # .add_signing_subkey() /// # .add_transport_encryption_subkey() /// # .generate()?; /// // Get a key amalgamation. /// let ka = cert.primary_key(); /// /// // Validate it, yielding a valid key amalgamation. /// let vka = ka.with_policy(p, None)?; /// /// // And here we get the amalgamation back. /// let ka2 = vka.amalgamation(); /// assert_eq!(&ka, ka2); /// # Ok(()) } /// ``` pub fn amalgamation(&self) -> &KeyAmalgamation<'a, P, R, R2> { &self.ka } /// Returns this amalgamation's bundle. pub fn bundle(&self) -> &'a crate::cert::ComponentBundle<Key<P, R>> { self.ka.bundle() } /// Returns this amalgamation's component. /// /// # Examples /// /// ``` /// # use sequoia_openpgp as openpgp; /// # use openpgp::cert::prelude::*; /// use openpgp::policy::StandardPolicy; /// # /// # fn main() -> openpgp::Result<()> { /// let p = &StandardPolicy::new(); /// /// # let (cert, _) = /// # CertBuilder::general_purpose(Some("alice@example.org")) /// # .generate()?; /// // Display some information about any unknown components. /// for k in cert.with_policy(p, None)?.keys() { /// eprintln!(" - {:?}", k.component()); /// } /// # Ok(()) } /// ``` pub fn component(&self) -> &'a Key<P, R> { self.bundle().component() } /// Returns the `KeyAmalgamation`'s key. pub fn key(&self) -> &'a Key<P, R> { self.component() } /// Returns the component's self-signatures. /// /// The signatures are validated, and they are sorted by their /// creation time, most recent first. /// /// # Examples /// /// ``` /// # use sequoia_openpgp as openpgp; /// # use openpgp::cert::prelude::*; /// use openpgp::policy::StandardPolicy; /// # /// # fn main() -> openpgp::Result<()> { /// let p = &StandardPolicy::new(); /// /// # let (cert, _) = /// # CertBuilder::general_purpose(Some("alice@example.org")) /// # .generate()?; /// for (i, ka) in cert.with_policy(p, None)?.keys().enumerate() { /// eprintln!("Key #{} ({}) has {:?} self signatures", /// i, ka.key().fingerprint(), /// ka.self_signatures().count()); /// } /// # Ok(()) } /// ``` pub fn self_signatures(&self) -> impl Iterator<Item=&'a Signature> + Send + Sync { self.ka.self_signatures() } /// Returns the component's third-party certifications. /// /// The signatures are *not* validated. They are sorted by their /// creation time, most recent first. /// /// # Examples /// /// ``` /// # use sequoia_openpgp as openpgp; /// # use openpgp::cert::prelude::*; /// use openpgp::policy::StandardPolicy; /// # /// # fn main() -> openpgp::Result<()> { /// let p = &StandardPolicy::new(); /// /// # let (cert, _) = /// # CertBuilder::general_purpose(Some("alice@example.org")) /// # .generate()?; /// for k in cert.with_policy(p, None)?.keys() { /// eprintln!("Key {} has {:?} unverified, third-party certifications", /// k.key().fingerprint(), /// k.certifications().count()); /// } /// # Ok(()) } /// ``` pub fn certifications(&self) -> impl Iterator<Item=&'a Signature> + Send + Sync { self.ka.certifications() } /// Returns the component's revocations that were issued by the /// certificate holder. /// /// The revocations are validated, and they are sorted by their /// creation time, most recent first. /// /// # Examples /// /// ``` /// # use sequoia_openpgp as openpgp; /// # use openpgp::cert::prelude::*; /// use openpgp::policy::StandardPolicy; /// # /// # fn main() -> openpgp::Result<()> { /// let p = &StandardPolicy::new(); /// /// # let (cert, _) = /// # CertBuilder::general_purpose(Some("alice@example.org")) /// # .generate()?; /// for k in cert.with_policy(p, None)?.keys() { /// eprintln!("Key {} has {:?} revocation certificates.", /// k.key().fingerprint(), /// k.self_revocations().count()); /// } /// # Ok(()) } /// ``` pub fn self_revocations(&self) -> impl Iterator<Item=&'a Signature> + Send + Sync { self.ka.self_revocations() } /// Returns the component's revocations that were issued by other /// certificates. /// /// The revocations are *not* validated. They are sorted by their /// creation time, most recent first. /// /// # Examples /// /// ``` /// # use sequoia_openpgp as openpgp; /// # use openpgp::cert::prelude::*; /// use openpgp::policy::StandardPolicy; /// # /// # fn main() -> openpgp::Result<()> { /// let p = &StandardPolicy::new(); /// /// # let (cert, _) = /// # CertBuilder::general_purpose(Some("alice@example.org")) /// # .generate()?; /// for k in cert.with_policy(p, None)?.keys() { /// eprintln!("Key {} has {:?} unverified, third-party revocation certificates.", /// k.key().fingerprint(), /// k.other_revocations().count()); /// } /// # Ok(()) } /// ``` pub fn other_revocations(&self) -> impl Iterator<Item=&'a Signature> + Send + Sync { self.ka.other_revocations() } /// Returns all of the component's signatures. /// /// Only the self-signatures are validated. The signatures are /// sorted first by type, then by creation time. The self /// revocations come first, then the self signatures, /// then any certification approval key signatures, /// certifications, and third-party revocations coming last. This /// function may return additional types of signatures that could /// be associated to this component. /// /// # Examples /// /// ``` /// # use sequoia_openpgp as openpgp; /// # use openpgp::cert::prelude::*; /// use openpgp::policy::StandardPolicy; /// # /// # fn main() -> openpgp::Result<()> { /// let p = &StandardPolicy::new(); /// /// # let (cert, _) = /// # CertBuilder::general_purpose(Some("alice@example.org")) /// # .generate()?; /// for (i, ka) in cert.with_policy(p, None)?.keys().enumerate() { /// eprintln!("Key #{} ({}) has {:?} signatures", /// i, ka.key().fingerprint(), /// ka.signatures().count()); /// } /// # Ok(()) } /// ``` pub fn signatures(&self) -> impl Iterator<Item = &'a Signature> + Send + Sync { self.ka.signatures() } /// Forwarder for the conversion macros. pub(crate) fn has_secret(&self) -> bool { self.key().has_secret() } } /// A Valid primary Key, and its associated data. /// /// A specialized version of [`ValidKeyAmalgamation`]. /// pub type ValidPrimaryKeyAmalgamation<'a, P> = ValidKeyAmalgamation<'a, P, key::PrimaryRole, ()>; /// A Valid subkey, and its associated data. /// /// A specialized version of [`ValidKeyAmalgamation`]. /// pub type ValidSubordinateKeyAmalgamation<'a, P> = ValidKeyAmalgamation<'a, P, key::SubordinateRole, ()>; /// A valid key whose role is not known at compile time. /// /// A specialized version of [`ValidKeyAmalgamation`]. /// pub type ValidErasedKeyAmalgamation<'a, P> = ValidKeyAmalgamation<'a, P, key::UnspecifiedRole, bool>; impl<'a, P, R, R2> From<ValidKeyAmalgamation<'a, P, R, R2>> for KeyAmalgamation<'a, P, R, R2> where P: 'a + key::KeyParts, R: 'a + key::KeyRole, R2: Copy, { fn from(vka: ValidKeyAmalgamation<'a, P, R, R2>) -> Self { assert!(std::ptr::eq(vka.ka.cert(), vka.cert.cert())); vka.ka } } impl<'a, P: 'a + key::KeyParts> From<ValidPrimaryKeyAmalgamation<'a, P>> for ValidErasedKeyAmalgamation<'a, P> { fn from(vka: ValidPrimaryKeyAmalgamation<'a, P>) -> Self { assert!(std::ptr::eq(vka.ka.cert(), vka.cert.cert())); ValidErasedKeyAmalgamation { ka: vka.ka.into(), cert: vka.cert, binding_signature: vka.binding_signature, } } } impl<'a, P: 'a + key::KeyParts> From<&ValidPrimaryKeyAmalgamation<'a, P>> for ValidErasedKeyAmalgamation<'a, P> { fn from(vka: &ValidPrimaryKeyAmalgamation<'a, P>) -> Self { assert!(std::ptr::eq(vka.ka.cert(), vka.cert.cert())); ValidErasedKeyAmalgamation { ka: vka.ka.clone().into(), cert: vka.cert.clone(), binding_signature: vka.binding_signature, } } } impl<'a, P: 'a + key::KeyParts> From<ValidSubordinateKeyAmalgamation<'a, P>> for ValidErasedKeyAmalgamation<'a, P> { fn from(vka: ValidSubordinateKeyAmalgamation<'a, P>) -> Self { assert!(std::ptr::eq(vka.ka.cert(), vka.cert.cert())); ValidErasedKeyAmalgamation { ka: vka.ka.into(), cert: vka.cert, binding_signature: vka.binding_signature, } } } impl<'a, P: 'a + key::KeyParts> From<&ValidSubordinateKeyAmalgamation<'a, P>> for ValidErasedKeyAmalgamation<'a, P> { fn from(vka: &ValidSubordinateKeyAmalgamation<'a, P>) -> Self { assert!(std::ptr::eq(vka.ka.cert(), vka.cert.cert())); ValidErasedKeyAmalgamation { ka: vka.ka.clone().into(), cert: vka.cert.clone(), binding_signature: vka.binding_signature, } } } // We can infallibly convert part X to part Y for everything but // Public -> Secret and Unspecified -> Secret. macro_rules! impl_conversion { ($s:ident, $p1:path, $p2:path) => { impl<'a> From<$s<'a, $p1>> for ValidErasedKeyAmalgamation<'a, $p2> { fn from(vka: $s<'a, $p1>) -> Self { assert!(std::ptr::eq(vka.ka.cert(), vka.cert.cert())); ValidErasedKeyAmalgamation { ka: vka.ka.into(), cert: vka.cert, binding_signature: vka.binding_signature, } } } impl<'a> From<&$s<'a, $p1>> for ValidErasedKeyAmalgamation<'a, $p2> { fn from(vka: &$s<'a, $p1>) -> Self { assert!(std::ptr::eq(vka.ka.cert(), vka.cert.cert())); ValidErasedKeyAmalgamation { ka: vka.ka.clone().into(), cert: vka.cert.clone(), binding_signature: vka.binding_signature, } } } } } impl_conversion!(ValidPrimaryKeyAmalgamation, key::SecretParts, key::PublicParts); impl_conversion!(ValidPrimaryKeyAmalgamation, key::SecretParts, key::UnspecifiedParts); impl_conversion!(ValidPrimaryKeyAmalgamation, key::PublicParts, key::UnspecifiedParts); impl_conversion!(ValidPrimaryKeyAmalgamation, key::UnspecifiedParts, key::PublicParts); impl_conversion!(ValidSubordinateKeyAmalgamation, key::SecretParts, key::PublicParts); impl_conversion!(ValidSubordinateKeyAmalgamation, key::SecretParts, key::UnspecifiedParts); impl_conversion!(ValidSubordinateKeyAmalgamation, key::PublicParts, key::UnspecifiedParts); impl_conversion!(ValidSubordinateKeyAmalgamation, key::UnspecifiedParts, key::PublicParts); impl<'a, P, P2> TryFrom<ValidErasedKeyAmalgamation<'a, P>> for ValidPrimaryKeyAmalgamation<'a, P2> where P: 'a + key::KeyParts, P2: 'a + key::KeyParts, { type Error = anyhow::Error; fn try_from(vka: ValidErasedKeyAmalgamation<'a, P>) -> Result<Self> { assert!(std::ptr::eq(vka.ka.cert(), vka.cert.cert())); Ok(ValidPrimaryKeyAmalgamation { ka: vka.ka.try_into()?, cert: vka.cert, binding_signature: vka.binding_signature, }) } } impl<'a, P, P2> TryFrom<ValidErasedKeyAmalgamation<'a, P>> for ValidSubordinateKeyAmalgamation<'a, P2> where P: 'a + key::KeyParts, P2: 'a + key::KeyParts, { type Error = anyhow::Error; fn try_from(vka: ValidErasedKeyAmalgamation<'a, P>) -> Result<Self> { Ok(ValidSubordinateKeyAmalgamation { ka: vka.ka.try_into()?, cert: vka.cert, binding_signature: vka.binding_signature, }) } } impl<'a, P> ValidateAmalgamation<'a, Key<P, key::PrimaryRole>> for ValidPrimaryKeyAmalgamation<'a, P> where P: 'a + key::KeyParts { type V = Self; fn with_policy<T>(&self, policy: &'a dyn Policy, time: T) -> Result<Self::V> where T: Into<Option<time::SystemTime>>, Self: Sized { assert!(std::ptr::eq(self.ka.cert(), self.cert.cert())); self.ka.with_policy(policy, time) .map(|vka| { assert!(std::ptr::eq(vka.ka.cert(), vka.cert.cert())); vka }) } } impl<'a, P> ValidateAmalgamation<'a, Key<P, key::SubordinateRole>> for ValidSubordinateKeyAmalgamation<'a, P> where P: 'a + key::KeyParts { type V = Self; fn with_policy<T>(&self, policy: &'a dyn Policy, time: T) -> Result<Self::V> where T: Into<Option<time::SystemTime>>, Self: Sized { assert!(std::ptr::eq(self.ka.cert(), self.cert.cert())); self.ka.with_policy(policy, time) .map(|vka| { assert!(std::ptr::eq(vka.ka.cert(), vka.cert.cert())); vka }) } } impl<'a, P> ValidateAmalgamation<'a, Key<P, key::UnspecifiedRole>> for ValidErasedKeyAmalgamation<'a, P> where P: 'a + key::KeyParts { type V = Self; fn with_policy<T>(&self, policy: &'a dyn Policy, time: T) -> Result<Self::V> where T: Into<Option<time::SystemTime>>, Self: Sized { assert!(std::ptr::eq(self.ka.cert(), self.cert.cert())); self.ka.with_policy(policy, time) .map(|vka| { assert!(std::ptr::eq(vka.ka.cert(), vka.cert.cert())); vka }) } } impl<'a, P, R, R2> seal::Sealed for ValidKeyAmalgamation<'a, P, R, R2> where P: 'a + key::KeyParts, R: 'a + key::KeyRole, R2: Copy, Self: PrimaryKey<'a, P, R>, {} impl<'a, P, R, R2> ValidAmalgamation<'a, Key<P, R>> for ValidKeyAmalgamation<'a, P, R, R2> where P: 'a + key::KeyParts, R: 'a + key::KeyRole, R2: Copy, Self: PrimaryKey<'a, P, R>, { fn valid_cert(&self) -> &ValidCert<'a> { assert!(std::ptr::eq(self.ka.cert(), self.cert.cert())); &self.cert } fn time(&self) -> SystemTime { self.cert.time() } fn policy(&self) -> &'a dyn Policy { assert!(std::ptr::eq(self.ka.cert(), self.cert.cert())); self.cert.policy() } fn binding_signature(&self) -> &'a Signature { self.binding_signature } fn revocation_status(&self) -> RevocationStatus<'a> { if self.primary() { self.cert.revocation_status() } else { self.bundle()._revocation_status(self.policy(), self.time(), true, Some(self.binding_signature)) } } fn revocation_keys(&self) -> Box<dyn Iterator<Item = &'a RevocationKey> + 'a> { let mut keys = std::collections::HashSet::new(); let policy = self.policy(); let pk_sec = self.cert().primary_key().key().hash_algo_security(); // All valid self-signatures. let sec = self.bundle().hash_algo_security; self.self_signatures() .filter(move |sig| { policy.signature(sig, sec).is_ok() }) // All direct-key signatures. .chain(self.cert().primary_key() .self_signatures() .filter(|sig| { policy.signature(sig, pk_sec).is_ok() })) .flat_map(|sig| sig.revocation_keys()) .for_each(|rk| { keys.insert(rk); }); Box::new(keys.into_iter()) } } impl<'a, P, R, R2> ValidBindingSignature<'a, Key<P, R>> for ValidKeyAmalgamation<'a, P, R, R2> where P: 'a + key::KeyParts, R: 'a + key::KeyRole, R2: Copy, Self: PrimaryKey<'a, P, R>, {} impl<'a, P> PrimaryKey<'a, P, key::PrimaryRole> for ValidPrimaryKeyAmalgamation<'a, P> where P: 'a + key::KeyParts { fn primary(&self) -> bool { true } } impl<'a, P> PrimaryKey<'a, P, key::SubordinateRole> for ValidSubordinateKeyAmalgamation<'a, P> where P: 'a + key::KeyParts { fn primary(&self) -> bool { false } } impl<'a, P> PrimaryKey<'a, P, key::UnspecifiedRole> for ValidErasedKeyAmalgamation<'a, P> where P: 'a + key::KeyParts { fn primary(&self) -> bool { self.ka.primary } } impl<'a, P, R, R2> ValidKeyAmalgamation<'a, P, R, R2> where P: 'a + key::KeyParts, R: 'a + key::KeyRole, R2: Copy, Self: ValidAmalgamation<'a, Key<P, R>>, Self: PrimaryKey<'a, P, R>, { /// Returns whether the key is alive as of the amalgamation's /// reference time. /// /// A `ValidKeyAmalgamation` is guaranteed to have a live binding /// signature. This is independent of whether the component is /// live. /// /// If the certificate is not alive as of the reference time, no /// subkey can be alive. /// /// This function considers both the binding signature and the /// direct key signature. Information in the binding signature /// takes precedence over the direct key signature. See [Section /// 5.2.3.10 of RFC 9580]. /// /// [Section 5.2.3.10 of RFC 9580]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.10 /// /// For a definition of liveness, see the [`key_alive`] method. /// /// [`key_alive`]: crate::packet::signature::subpacket::SubpacketAreas::key_alive() /// /// # Examples /// /// ``` /// # use sequoia_openpgp as openpgp; /// # use openpgp::cert::prelude::*; /// use openpgp::policy::StandardPolicy; /// /// # fn main() -> openpgp::Result<()> { /// let p = &StandardPolicy::new(); /// /// # let (cert, _) = CertBuilder::new() /// # .add_userid("Alice") /// # .add_signing_subkey() /// # .add_transport_encryption_subkey() /// # .generate()?; /// let ka = cert.primary_key().with_policy(p, None)?; /// if let Err(_err) = ka.alive() { /// // Not alive. /// # unreachable!(); /// } /// # Ok(()) } /// ``` pub fn alive(&self) -> Result<()> { if ! self.primary() { // First, check the certificate. self.valid_cert().alive() .context("The certificate is not live")?; } let sig = { let binding : &Signature = self.binding_signature(); if binding.key_validity_period().is_some() { Some(binding) } else { self.direct_key_signature().ok() } }; if let Some(sig) = sig { sig.key_alive(self.key(), self.time()) .with_context(|| if self.primary() { "The primary key is not live" } else { "The subkey is not live" }) } else { // There is no key expiration time on the binding // signature. This key does not expire. Ok(()) } } } impl<'a, P, R, R2> ValidKeyAmalgamation<'a, P, R, R2> where P: key::KeyParts, R: key::KeyRole, R2: Copy, Self: PrimaryKey<'a, P, R>, { /// Returns the key's primary key binding signature, if any. /// /// The [primary key binding signature] is embedded inside a /// subkey binding signature. It is made by the subkey to /// indicate that it should be associated with the primary key. /// This prevents an attack in which an attacker creates a /// certificate, and associates the victim's subkey with it /// thereby creating confusion about the certificate that issued a /// signature. /// /// [primary key binding signature]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.1 /// /// Not all keys have primary key binding signatures. First, /// primary keys don't have them, because they don't need them. /// Second, encrypt-capable subkeys don't have them because they /// are not (usually) able to issue signatures. /// /// # Examples /// /// ``` /// # use sequoia_openpgp as openpgp; /// # use openpgp::cert::prelude::*; /// # use openpgp::policy::StandardPolicy; /// # /// # const P: &StandardPolicy = &StandardPolicy::new(); /// # /// # fn main() -> openpgp::Result<()> { /// # let (cert, _) = /// # CertBuilder::general_purpose(Some("alice@example.org")) /// # .generate()?; /// # let fpr = cert.fingerprint(); /// let vc = cert.with_policy(P, None)?; /// /// assert!(vc.primary_key().primary_key_binding_signature().is_none()); /// /// // A signing key has to have a primary key binding signature. /// for ka in vc.keys().for_signing() { /// assert!(ka.primary_key_binding_signature().is_some()); /// } /// /// // Encryption keys normally can't have a primary key binding /// // signature, because they can't issue signatures. /// for ka in vc.keys().for_transport_encryption() { /// assert!(ka.primary_key_binding_signature().is_none()); /// } /// # Ok(()) /// # } /// ``` pub fn primary_key_binding_signature(&self) -> Option<&Signature> { let subkey = if self.primary() { // A primary key has no backsig. return None; } else { self.key().role_as_subordinate() }; let pk = self.cert().primary_key().key(); for backsig in self.binding_signature.subpackets(SubpacketTag::EmbeddedSignature) { if let SubpacketValue::EmbeddedSignature(sig) = backsig.value() { if sig.verify_primary_key_binding(pk, subkey).is_ok() { // Mark the subpacket as authenticated by the // embedded signature. backsig.set_authenticated(true); return Some(sig); } } else { unreachable!("subpackets(EmbeddedSignature) returns \ EmbeddedSignatures"); } } None } } impl<'a, P> ValidPrimaryKeyAmalgamation<'a, P> where P: 'a + key::KeyParts { /// Sets the key to expire in delta seconds. /// /// Note: the time is relative to the key's creation time, not the /// current time! /// /// This function exists to facilitate testing, which is why it is /// not exported. #[cfg(test)] pub(crate) fn set_validity_period_as_of(&self, primary_signer: &mut dyn Signer, expiration: Option<time::Duration>, now: time::SystemTime) -> Result<Vec<Signature>> { ValidErasedKeyAmalgamation::<P>::from(self) .set_validity_period_as_of(primary_signer, None, expiration, now) } /// Creates signatures that cause the key to expire at the specified time. /// /// This function creates new binding signatures that cause the /// key to expire at the specified time when integrated into the /// certificate. For the primary key, it is necessary to /// create a new self-signature for each non-revoked User ID, and /// to create a direct key signature. This is needed, because the /// primary User ID is first consulted when determining the /// primary key's expiration time, and certificates can be /// distributed with a possibly empty subset of User IDs. /// /// Setting a key's expiry time means updating an existing binding /// signature---when looking up information, only one binding /// signature is normally considered, and we don't want to drop /// the other information stored in the current binding signature. /// This function uses the binding signature determined by /// `ValidKeyAmalgamation`'s policy and reference time for this. /// /// # Examples /// /// ``` /// use std::time; /// # use sequoia_openpgp as openpgp; /// # use openpgp::cert::prelude::*; /// use openpgp::policy::StandardPolicy; /// /// # fn main() -> openpgp::Result<()> { /// let p = &StandardPolicy::new(); /// /// # let t = time::SystemTime::now() - time::Duration::from_secs(10); /// # let (cert, _) = CertBuilder::new() /// # .set_creation_time(t) /// # .add_userid("Alice") /// # .add_signing_subkey() /// # .add_transport_encryption_subkey() /// # .generate()?; /// let vc = cert.with_policy(p, None)?; /// /// // Assert that the primary key is not expired. /// assert!(vc.primary_key().alive().is_ok()); /// /// // Make the primary key expire in a week. /// let t = time::SystemTime::now() /// + time::Duration::from_secs(7 * 24 * 60 * 60); /// /// // We assume that the secret key material is available, and not /// // password protected. /// let mut signer = vc.primary_key() /// .key().clone().parts_into_secret()?.into_keypair()?; /// /// let sigs = vc.primary_key().set_expiration_time(&mut signer, Some(t))?; /// let cert = cert.insert_packets(sigs)?.0; /// /// // The primary key isn't expired yet. /// let vc = cert.with_policy(p, None)?; /// assert!(vc.primary_key().alive().is_ok()); /// /// // But in two weeks, it will be... /// let t = time::SystemTime::now() /// + time::Duration::from_secs(2 * 7 * 24 * 60 * 60); /// let vc = cert.with_policy(p, t)?; /// assert!(vc.primary_key().alive().is_err()); /// # Ok(()) } pub fn set_expiration_time(&self, primary_signer: &mut dyn Signer, expiration: Option<time::SystemTime>) -> Result<Vec<Signature>> { ValidErasedKeyAmalgamation::<P>::from(self) .set_expiration_time(primary_signer, None, expiration) } } impl<'a, P> ValidSubordinateKeyAmalgamation<'a, P> where P: 'a + key::KeyParts { /// Creates signatures that cause the key to expire at the specified time. /// /// This function creates new binding signatures that cause the /// key to expire at the specified time when integrated into the /// certificate. For subkeys, a single `Signature` is returned. /// /// Setting a key's expiry time means updating an existing binding /// signature---when looking up information, only one binding /// signature is normally considered, and we don't want to drop /// the other information stored in the current binding signature. /// This function uses the binding signature determined by /// `ValidKeyAmalgamation`'s policy and reference time for this. /// /// When updating the expiration time of signing-capable subkeys, /// we need to create a new [primary key binding signature]. /// Therefore, we need a signer for the subkey. If /// `subkey_signer` is `None`, and this is a signing-capable /// subkey, this function fails with [`Error::InvalidArgument`]. /// Likewise, this function fails if `subkey_signer` is not `None` /// when updating the expiration of a non signing-capable subkey. /// /// [primary key binding signature]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.1 /// [`Error::InvalidArgument`]: super::super::super::Error::InvalidArgument /// /// # Examples /// /// ``` /// use std::time; /// # use sequoia_openpgp as openpgp; /// # use openpgp::cert::prelude::*; /// use openpgp::policy::StandardPolicy; /// /// # fn main() -> openpgp::Result<()> { /// let p = &StandardPolicy::new(); /// /// # let t = time::SystemTime::now() - time::Duration::from_secs(10); /// # let (cert, _) = CertBuilder::new() /// # .set_creation_time(t) /// # .add_userid("Alice") /// # .add_signing_subkey() /// # .add_transport_encryption_subkey() /// # .generate()?; /// let vc = cert.with_policy(p, None)?; /// /// // Assert that the keys are not expired. /// for ka in vc.keys() { /// assert!(ka.alive().is_ok()); /// } /// /// // Make the keys expire in a week. /// let t = time::SystemTime::now() /// + time::Duration::from_secs(7 * 24 * 60 * 60); /// /// // We assume that the secret key material is available, and not /// // password protected. /// let mut primary_signer = vc.primary_key() /// .key().clone().parts_into_secret()?.into_keypair()?; /// let mut signing_subkey_signer = vc.keys().for_signing().nth(0).unwrap() /// .key().clone().parts_into_secret()?.into_keypair()?; /// /// let mut sigs = Vec::new(); /// for ka in vc.keys() { /// if ! ka.for_signing() { /// // Non-signing-capable subkeys are easy to update. /// sigs.append(&mut ka.set_expiration_time(&mut primary_signer, /// None, Some(t))?); /// } else { /// // Signing-capable subkeys need to create a primary /// // key binding signature with the subkey: /// assert!(ka.set_expiration_time(&mut primary_signer, /// None, Some(t)).is_err()); /// /// // Here, we need the subkey's signer: /// sigs.append(&mut ka.set_expiration_time(&mut primary_signer, /// Some(&mut signing_subkey_signer), /// Some(t))?); /// } /// } /// let cert = cert.insert_packets(sigs)?.0; /// /// // They aren't expired yet. /// let vc = cert.with_policy(p, None)?; /// for ka in vc.keys() { /// assert!(ka.alive().is_ok()); /// } /// /// // But in two weeks, they will be... /// let t = time::SystemTime::now() /// + time::Duration::from_secs(2 * 7 * 24 * 60 * 60); /// let vc = cert.with_policy(p, t)?; /// for ka in vc.keys() { /// assert!(ka.alive().is_err()); /// } /// # Ok(()) } pub fn set_expiration_time(&self, primary_signer: &mut dyn Signer, subkey_signer: Option<&mut dyn Signer>, expiration: Option<time::SystemTime>) -> Result<Vec<Signature>> { ValidErasedKeyAmalgamation::<P>::from(self) .set_expiration_time(primary_signer, subkey_signer, expiration) } } impl<'a, P> ValidErasedKeyAmalgamation<'a, P> where P: 'a + key::KeyParts { /// Sets the key to expire in delta seconds. /// /// Note: the time is relative to the key's creation time, not the /// current time! /// /// This function exists to facilitate testing, which is why it is /// not exported. pub(crate) fn set_validity_period_as_of(&self, primary_signer: &mut dyn Signer, subkey_signer: Option<&mut dyn Signer>, expiration: Option<time::Duration>, now: time::SystemTime) -> Result<Vec<Signature>> { let mut sigs = Vec::new(); // There are two cases to consider. If we are extending the // validity of the primary key, we also need to create new // binding signatures for all userids. if self.primary() { // First, update or create a direct key signature. let template = self.direct_key_signature() .map(|sig| { signature::SignatureBuilder::from(sig.clone()) }) .unwrap_or_else(|_| { let mut template = signature::SignatureBuilder::from( self.binding_signature().clone()) .set_type(SignatureType::DirectKey); // We're creating a direct signature from a User // ID self signature. Remove irrelevant packets. use SubpacketTag::*; let ha = template.hashed_area_mut(); ha.remove_all(ExportableCertification); ha.remove_all(Revocable); ha.remove_all(TrustSignature); ha.remove_all(RegularExpression); ha.remove_all(PrimaryUserID); ha.remove_all(SignersUserID); ha.remove_all(ReasonForRevocation); ha.remove_all(SignatureTarget); ha.remove_all(EmbeddedSignature); template }); let mut builder = template .set_signature_creation_time(now)? .set_key_validity_period(expiration)?; builder.hashed_area_mut().remove_all( signature::subpacket::SubpacketTag::PrimaryUserID); // Generate the signature. sigs.push(builder.sign_direct_key(primary_signer, None)?); // Second, generate a new binding signature for every // userid. We need to be careful not to change the // primary userid, so we make it explicit using the // primary userid subpacket. for userid in self.valid_cert().userids().revoked(false) { // To extend the validity of the subkey, create a new // binding signature with updated key validity period. let binding_signature = userid.binding_signature(); let builder = signature::SignatureBuilder::from(binding_signature.clone()) .set_signature_creation_time(now)? .set_key_validity_period(expiration)? .set_primary_userid( self.valid_cert().primary_userid().map(|primary| { userid.userid() == primary.userid() }).unwrap_or(false))?; sigs.push(builder.sign_userid_binding(primary_signer, self.cert().primary_key().component(), userid.userid())?); } } else { // To extend the validity of the subkey, create a new // binding signature with updated key validity period. let backsig = if self.for_certification() || self.for_signing() || self.for_authentication() { if let Some(subkey_signer) = subkey_signer { Some(signature::SignatureBuilder::new( SignatureType::PrimaryKeyBinding) .set_signature_creation_time(now)? .set_hash_algo(self.binding_signature.hash_algo()) .sign_primary_key_binding( subkey_signer, self.cert().primary_key().key(), self.key().role_as_subordinate())?) } else { return Err(Error::InvalidArgument( "Changing expiration of signing-capable subkeys \ requires subkey signer".into()).into()); } } else { if subkey_signer.is_some() { return Err(Error::InvalidArgument( "Subkey signer given but subkey is not signing-capable" .into()).into()); } None }; let mut sig = signature::SignatureBuilder::from( self.binding_signature().clone()) .set_signature_creation_time(now)? .set_key_validity_period(expiration)?; if let Some(bs) = backsig { sig = sig.set_embedded_signature(bs)?; } sigs.push(sig.sign_subkey_binding( primary_signer, self.cert().primary_key().component(), self.key().role_as_subordinate())?); } Ok(sigs) } /// Creates signatures that cause the key to expire at the specified time. /// /// This function creates new binding signatures that cause the /// key to expire at the specified time when integrated into the /// certificate. For subkeys, only a single `Signature` is /// returned. For the primary key, however, it is necessary to /// create a new self-signature for each non-revoked User ID, and /// to create a direct key signature. This is needed, because the /// primary User ID is first consulted when determining the /// primary key's expiration time, and certificates can be /// distributed with a possibly empty subset of User IDs. /// /// Setting a key's expiry time means updating an existing binding /// signature---when looking up information, only one binding /// signature is normally considered, and we don't want to drop /// the other information stored in the current binding signature. /// This function uses the binding signature determined by /// `ValidKeyAmalgamation`'s policy and reference time for this. /// /// When updating the expiration time of signing-capable subkeys, /// we need to create a new [primary key binding signature]. /// Therefore, we need a signer for the subkey. If /// `subkey_signer` is `None`, and this is a signing-capable /// subkey, this function fails with [`Error::InvalidArgument`]. /// Likewise, this function fails if `subkey_signer` is not `None` /// when updating the expiration of the primary key, or a non /// signing-capable subkey. /// /// [primary key binding signature]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.1 /// [`Error::InvalidArgument`]: super::super::super::Error::InvalidArgument /// /// # Examples /// /// ``` /// use std::time; /// # use sequoia_openpgp as openpgp; /// # use openpgp::cert::prelude::*; /// use openpgp::policy::StandardPolicy; /// /// # fn main() -> openpgp::Result<()> { /// let p = &StandardPolicy::new(); /// /// # let t = time::SystemTime::now() - time::Duration::from_secs(10); /// # let (cert, _) = CertBuilder::new() /// # .set_creation_time(t) /// # .add_userid("Alice") /// # .add_signing_subkey() /// # .add_transport_encryption_subkey() /// # .generate()?; /// let vc = cert.with_policy(p, None)?; /// /// // Assert that the keys are not expired. /// for ka in vc.keys() { /// assert!(ka.alive().is_ok()); /// } /// /// // Make the keys expire in a week. /// let t = time::SystemTime::now() /// + time::Duration::from_secs(7 * 24 * 60 * 60); /// /// // We assume that the secret key material is available, and not /// // password protected. /// let mut primary_signer = vc.primary_key() /// .key().clone().parts_into_secret()?.into_keypair()?; /// let mut signing_subkey_signer = vc.keys().for_signing().nth(0).unwrap() /// .key().clone().parts_into_secret()?.into_keypair()?; /// /// let mut sigs = Vec::new(); /// for ka in vc.keys() { /// if ! ka.for_signing() { /// // Non-signing-capable subkeys are easy to update. /// sigs.append(&mut ka.set_expiration_time(&mut primary_signer, /// None, Some(t))?); /// } else { /// // Signing-capable subkeys need to create a primary /// // key binding signature with the subkey: /// assert!(ka.set_expiration_time(&mut primary_signer, /// None, Some(t)).is_err()); /// /// // Here, we need the subkey's signer: /// sigs.append(&mut ka.set_expiration_time(&mut primary_signer, /// Some(&mut signing_subkey_signer), /// Some(t))?); /// } /// } /// let cert = cert.insert_packets(sigs)?.0; /// /// // They aren't expired yet. /// let vc = cert.with_policy(p, None)?; /// for ka in vc.keys() { /// assert!(ka.alive().is_ok()); /// } /// /// // But in two weeks, they will be... /// let t = time::SystemTime::now() /// + time::Duration::from_secs(2 * 7 * 24 * 60 * 60); /// let vc = cert.with_policy(p, t)?; /// for ka in vc.keys() { /// assert!(ka.alive().is_err()); /// } /// # Ok(()) } pub fn set_expiration_time(&self, primary_signer: &mut dyn Signer, subkey_signer: Option<&mut dyn Signer>, expiration: Option<time::SystemTime>) -> Result<Vec<Signature>> { let expiration = if let Some(e) = expiration.map(crate::types::normalize_systemtime) { let ct = self.key().creation_time(); match e.duration_since(ct) { Ok(v) => Some(v), Err(_) => return Err(Error::InvalidArgument( format!("Expiration time {:?} predates creation time \ {:?}", e, ct)).into()), } } else { None }; self.set_validity_period_as_of(primary_signer, subkey_signer, expiration, crate::now()) } } impl<'a, P, R, R2> ValidKeyAmalgamation<'a, P, R, R2> where P: 'a + key::KeyParts, R: 'a + key::KeyRole, R2: Copy, Self: ValidAmalgamation<'a, Key<P, R>>, Self: ValidBindingSignature<'a, Key<P, R>>, { /// Returns the key's `Key Flags`. /// /// A Key's [`Key Flags`] holds information about the key. As of /// RFC 9580, this information is primarily concerned with the /// key's capabilities (e.g., whether it may be used for signing). /// The other information that has been defined is: whether the /// key has been split using something like [SSS], and whether the /// primary key material is held by multiple parties. In /// practice, the latter two flags are ignored. /// /// As per [Section 5.2.3.10 of RFC 9580], when looking for the /// `Key Flags`, the key's binding signature is first consulted /// (in the case of the primary Key, this is the binding signature /// of the primary User ID). If the `Key Flags` subpacket is not /// present, then the direct key signature is consulted. /// /// Since the key flags are taken from the active self signature, /// a key's flags may change depending on the policy and the /// reference time. /// /// To increase compatibility with early v4 certificates, if there /// is no key flags subpacket on the considered signatures, we /// infer the key flags from the key's role and public key /// algorithm. /// /// [`Key Flags`]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.29 /// [SSS]: https://de.wikipedia.org/wiki/Shamir%E2%80%99s_Secret_Sharing /// [Section 5.2.3.10 of RFC 9580]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.10 /// /// # Examples /// /// ``` /// # use sequoia_openpgp as openpgp; /// # use openpgp::cert::prelude::*; /// # use openpgp::policy::{Policy, StandardPolicy}; /// # /// # fn main() -> openpgp::Result<()> { /// # let p: &dyn Policy = &StandardPolicy::new(); /// # let (cert, _) = /// # CertBuilder::general_purpose(Some("alice@example.org")) /// # .generate()?; /// # let cert = cert.with_policy(p, None)?; /// let ka = cert.primary_key(); /// println!("Primary Key's Key Flags: {:?}", ka.key_flags()); /// # assert!(ka.key_flags().unwrap().for_certification()); /// # Ok(()) } /// ``` pub fn key_flags(&self) -> Option<KeyFlags> { self.map(|s| s.key_flags()) .or_else(|| { // There is no key flags subpacket. Match on the key // role and algorithm and synthesize one. We do this // to better support very early v4 certificates, where // either the binding signature is a v3 signature and // cannot contain subpackets, or it is a v4 signature, // but the key's capabilities were implied by the // public key algorithm. use crate::types::PublicKeyAlgorithm; // XXX: We cannot know whether this is a primary key // or not because of // https://gitlab.com/sequoia-pgp/sequoia/-/issues/1036 let is_primary = false; // We only match on public key algorithms used at the // time. #[allow(deprecated)] match (is_primary, self.key().pk_algo()) { (true, PublicKeyAlgorithm::RSAEncryptSign) => Some(KeyFlags::empty() .set_certification() .set_transport_encryption() .set_storage_encryption() .set_signing()), (true, _) => Some(KeyFlags::empty() .set_certification() .set_signing()), (false, PublicKeyAlgorithm::RSAEncryptSign) => Some(KeyFlags::empty() .set_transport_encryption() .set_storage_encryption() .set_signing()), (false, | PublicKeyAlgorithm::RSASign | PublicKeyAlgorithm::DSA) => Some(KeyFlags::empty().set_signing()), (false, | PublicKeyAlgorithm::RSAEncrypt | PublicKeyAlgorithm::ElGamalEncrypt | PublicKeyAlgorithm::ElGamalEncryptSign) => Some(KeyFlags::empty() .set_transport_encryption() .set_storage_encryption()), // Be conservative: newer algorithms don't get to // benefit from implicit key flags. (false, _) => None, } }) } /// Returns whether the key has at least one of the specified key /// flags. /// /// The key flags are looked up as described in /// [`ValidKeyAmalgamation::key_flags`]. /// /// # Examples /// /// Finds keys that may be used for transport encryption (data in /// motion) *or* storage encryption (data at rest): /// /// ``` /// # use sequoia_openpgp as openpgp; /// # use openpgp::cert::prelude::*; /// use openpgp::policy::StandardPolicy; /// use openpgp::types::KeyFlags; /// /// # fn main() -> openpgp::Result<()> { /// let p = &StandardPolicy::new(); /// /// # let (cert, _) = /// # CertBuilder::general_purpose(Some("alice@example.org")) /// # .generate()?; /// for ka in cert.keys().with_policy(p, None) { /// if ka.has_any_key_flag(KeyFlags::empty() /// .set_storage_encryption() /// .set_transport_encryption()) /// { /// // `ka` is encryption capable. /// } /// } /// # Ok(()) } /// ``` /// /// [`ValidKeyAmalgamation::key_flags`]: ValidKeyAmalgamation::key_flags() pub fn has_any_key_flag<F>(&self, flags: F) -> bool where F: Borrow<KeyFlags> { let our_flags = self.key_flags().unwrap_or_else(KeyFlags::empty); !(&our_flags & flags.borrow()).is_empty() } /// Returns whether the key is certification capable. /// /// Note: [Section 10.1 of RFC 9580] says that the primary key is /// certification capable independent of the `Key Flags` /// subpacket: /// /// > In a V4 key, the primary key MUST be a key capable of /// > certification. /// /// This function only reflects what is stored in the `Key Flags` /// packet; it does not implicitly set this flag. In practice, /// there are keys whose primary key's `Key Flags` do not have the /// certification capable flag set. Some versions of netpgp, for /// instance, create keys like this. Sequoia's higher-level /// functionality correctly handles these keys by always /// considering the primary key to be certification capable. /// Users of this interface should too. /// /// The key flags are looked up as described in /// [`ValidKeyAmalgamation::key_flags`]. /// /// # Examples /// /// Finds keys that are certification capable: /// /// ``` /// # use sequoia_openpgp as openpgp; /// # use openpgp::cert::prelude::*; /// use openpgp::policy::StandardPolicy; /// /// # fn main() -> openpgp::Result<()> { /// let p = &StandardPolicy::new(); /// /// # let (cert, _) = /// # CertBuilder::general_purpose(Some("alice@example.org")) /// # .generate()?; /// for ka in cert.keys().with_policy(p, None) { /// if ka.primary() || ka.for_certification() { /// // `ka` is certification capable. /// } /// } /// # Ok(()) } /// ``` /// /// [Section 10.1 of RFC 9580]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.29 /// [`ValidKeyAmalgamation::key_flags`]: ValidKeyAmalgamation::key_flags() pub fn for_certification(&self) -> bool { self.has_any_key_flag(KeyFlags::empty().set_certification()) } /// Returns whether the key is signing capable. /// /// The key flags are looked up as described in /// [`ValidKeyAmalgamation::key_flags`]. /// /// # Examples /// /// Finds keys that are signing capable: /// /// ``` /// # use sequoia_openpgp as openpgp; /// # use openpgp::cert::prelude::*; /// use openpgp::policy::StandardPolicy; /// /// # fn main() -> openpgp::Result<()> { /// let p = &StandardPolicy::new(); /// /// # let (cert, _) = /// # CertBuilder::general_purpose(Some("alice@example.org")) /// # .generate()?; /// for ka in cert.keys().with_policy(p, None) { /// if ka.for_signing() { /// // `ka` is signing capable. /// } /// } /// # Ok(()) } /// ``` /// /// [`ValidKeyAmalgamation::key_flags`]: ValidKeyAmalgamation::key_flags() pub fn for_signing(&self) -> bool { self.has_any_key_flag(KeyFlags::empty().set_signing()) } /// Returns whether the key is authentication capable. /// /// The key flags are looked up as described in /// [`ValidKeyAmalgamation::key_flags`]. /// /// # Examples /// /// Finds keys that are authentication capable: /// /// ``` /// # use sequoia_openpgp as openpgp; /// # use openpgp::cert::prelude::*; /// use openpgp::policy::StandardPolicy; /// /// # fn main() -> openpgp::Result<()> { /// let p = &StandardPolicy::new(); /// /// # let (cert, _) = /// # CertBuilder::general_purpose(Some("alice@example.org")) /// # .generate()?; /// for ka in cert.keys().with_policy(p, None) { /// if ka.for_authentication() { /// // `ka` is authentication capable. /// } /// } /// # Ok(()) } /// ``` /// /// [`ValidKeyAmalgamation::key_flags`]: ValidKeyAmalgamation::key_flags() pub fn for_authentication(&self) -> bool { self.has_any_key_flag(KeyFlags::empty().set_authentication()) } /// Returns whether the key is storage-encryption capable. /// /// OpenPGP distinguishes two types of encryption keys: those for /// storage ([data at rest]) and those for transport ([data in /// transit]). Most OpenPGP implementations, however, don't /// distinguish between them in practice. Instead, when they /// create a new encryption key, they just set both flags. /// Likewise, when encrypting a message, it is not typically /// possible to indicate the type of protection that is needed. /// Sequoia supports creating keys with only one of these flags /// set, and makes it easy to select the right type of key when /// encrypting messages. /// /// The key flags are looked up as described in /// [`ValidKeyAmalgamation::key_flags`]. /// /// # Examples /// /// Finds keys that are storage-encryption capable: /// /// ``` /// # use sequoia_openpgp as openpgp; /// # use openpgp::cert::prelude::*; /// use openpgp::policy::StandardPolicy; /// /// # fn main() -> openpgp::Result<()> { /// let p = &StandardPolicy::new(); /// /// # let (cert, _) = /// # CertBuilder::general_purpose(Some("alice@example.org")) /// # .generate()?; /// for ka in cert.keys().with_policy(p, None) { /// if ka.for_storage_encryption() { /// // `ka` is storage-encryption capable. /// } /// } /// # Ok(()) } /// ``` /// /// [data at rest]: https://en.wikipedia.org/wiki/Data_at_rest /// [data in transit]: https://en.wikipedia.org/wiki/Data_in_transit /// [`ValidKeyAmalgamation::key_flags`]: ValidKeyAmalgamation::key_flags() pub fn for_storage_encryption(&self) -> bool { self.has_any_key_flag(KeyFlags::empty().set_storage_encryption()) } /// Returns whether the key is transport-encryption capable. /// /// OpenPGP distinguishes two types of encryption keys: those for /// storage ([data at rest]) and those for transport ([data in /// transit]). Most OpenPGP implementations, however, don't /// distinguish between them in practice. Instead, when they /// create a new encryption key, they just set both flags. /// Likewise, when encrypting a message, it is not typically /// possible to indicate the type of protection that is needed. /// Sequoia supports creating keys with only one of these flags /// set, and makes it easy to select the right type of key when /// encrypting messages. /// /// The key flags are looked up as described in /// [`ValidKeyAmalgamation::key_flags`]. /// /// # Examples /// /// Finds keys that are transport-encryption capable: /// /// ``` /// # use sequoia_openpgp as openpgp; /// # use openpgp::cert::prelude::*; /// use openpgp::policy::StandardPolicy; /// /// # fn main() -> openpgp::Result<()> { /// let p = &StandardPolicy::new(); /// /// # let (cert, _) = /// # CertBuilder::general_purpose(Some("alice@example.org")) /// # .generate()?; /// for ka in cert.keys().with_policy(p, None) { /// if ka.for_transport_encryption() { /// // `ka` is transport-encryption capable. /// } /// } /// # Ok(()) } /// ``` /// /// [data at rest]: https://en.wikipedia.org/wiki/Data_at_rest /// [data in transit]: https://en.wikipedia.org/wiki/Data_in_transit /// [`ValidKeyAmalgamation::key_flags`]: ValidKeyAmalgamation::key_flags() pub fn for_transport_encryption(&self) -> bool { self.has_any_key_flag(KeyFlags::empty().set_transport_encryption()) } /// Returns how long the key is live. /// /// This returns how long the key is live relative to its creation /// time. Use [`ValidKeyAmalgamation::key_expiration_time`] to /// get the key's absolute expiry time. /// /// This function considers both the binding signature and the /// direct key signature. Information in the binding signature /// takes precedence over the direct key signature. See [Section /// 5.2.3.10 of RFC 9580]. /// /// # Examples /// /// ``` /// use std::time; /// use std::convert::TryInto; /// # use sequoia_openpgp as openpgp; /// # use openpgp::cert::prelude::*; /// use openpgp::policy::StandardPolicy; /// use openpgp::types::Timestamp; /// /// # fn main() -> openpgp::Result<()> { /// let p = &StandardPolicy::new(); /// /// // OpenPGP Timestamps have a one-second resolution. Since we /// // want to round trip the time, round it down. /// let now: Timestamp = time::SystemTime::now().try_into()?; /// let now: time::SystemTime = now.try_into()?; /// /// let a_week = time::Duration::from_secs(7 * 24 * 60 * 60); /// /// let (cert, _) = /// CertBuilder::general_purpose(Some("alice@example.org")) /// .set_creation_time(now) /// .set_validity_period(a_week) /// .generate()?; /// /// assert_eq!(cert.primary_key().with_policy(p, None)?.key_validity_period(), /// Some(a_week)); /// # Ok(()) } /// ``` /// /// [`ValidKeyAmalgamation::key_expiration_time`]: ValidKeyAmalgamation::key_expiration_time() /// [Section 5.2.3.10 of RFC 9580]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.10 pub fn key_validity_period(&self) -> Option<std::time::Duration> { self.map(|s| s.key_validity_period()) } /// Returns the key's expiration time. /// /// If this function returns `None`, the key does not expire. /// /// This returns the key's expiration time. Use /// [`ValidKeyAmalgamation::key_validity_period`] to get the /// duration of the key's lifetime. /// /// This function considers both the binding signature and the /// direct key signature. Information in the binding signature /// takes precedence over the direct key signature. See [Section /// 5.2.3.10 of RFC 9580]. /// /// # Examples /// /// ``` /// use std::time; /// use std::convert::TryInto; /// # use sequoia_openpgp as openpgp; /// # use openpgp::cert::prelude::*; /// use openpgp::policy::StandardPolicy; /// use openpgp::types::Timestamp; /// /// # fn main() -> openpgp::Result<()> { /// let p = &StandardPolicy::new(); /// /// // OpenPGP Timestamps have a one-second resolution. Since we /// // want to round trip the time, round it down. /// let now: Timestamp = time::SystemTime::now().try_into()?; /// let now: time::SystemTime = now.try_into()?; // /// let a_week = time::Duration::from_secs(7 * 24 * 60 * 60); /// let a_week_later = now + a_week; /// /// let (cert, _) = /// CertBuilder::general_purpose(Some("alice@example.org")) /// .set_creation_time(now) /// .set_validity_period(a_week) /// .generate()?; /// /// assert_eq!(cert.primary_key().with_policy(p, None)?.key_expiration_time(), /// Some(a_week_later)); /// # Ok(()) } /// ``` /// /// [`ValidKeyAmalgamation::key_validity_period`]: ValidKeyAmalgamation::key_validity_period() /// [Section 5.2.3.10 of RFC 9580]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.10 pub fn key_expiration_time(&self) -> Option<time::SystemTime> { match self.key_validity_period() { Some(vp) if vp.as_secs() > 0 => Some(self.key().creation_time() + vp), _ => None, } } // NOTE: If you add a method to ValidKeyAmalgamation that takes // ownership of self, then don't forget to write a forwarder for // it for ValidPrimaryKeyAmalgamation. } #[cfg(test)] mod test { use std::time::Duration; use std::time::UNIX_EPOCH; use crate::policy::StandardPolicy as P; use crate::cert::prelude::*; use crate::packet::Packet; use crate::packet::signature::SignatureBuilder; use crate::types::ReasonForRevocation; use crate::types::RevocationType; use super::*; #[test] fn expire_subkeys() { let p = &P::new(); // Timeline: // // -1: Key created with no key expiration. // 0: Setkeys set to expire in 1 year // 1: Subkeys expire let now = crate::now(); let a_year = time::Duration::from_secs(365 * 24 * 60 * 60); let in_a_year = now + a_year; let in_two_years = now + 2 * a_year; let (cert, _) = CertBuilder::new() .set_creation_time(now - a_year) .add_signing_subkey() .add_transport_encryption_subkey() .generate().unwrap(); for ka in cert.keys().with_policy(p, None) { assert!(ka.alive().is_ok()); } let mut primary_signer = cert.primary_key().key().clone() .parts_into_secret().unwrap().into_keypair().unwrap(); let mut signing_subkey_signer = cert.with_policy(p, None).unwrap() .keys().for_signing().next().unwrap() .key().clone().parts_into_secret().unwrap() .into_keypair().unwrap(); // Only expire the subkeys. let sigs = cert.keys().subkeys().with_policy(p, None) .flat_map(|ka| { if ! ka.for_signing() { ka.set_expiration_time(&mut primary_signer, None, Some(in_a_year)).unwrap() } else { ka.set_expiration_time(&mut primary_signer, Some(&mut signing_subkey_signer), Some(in_a_year)).unwrap() } .into_iter() .map(Into::into) }) .collect::<Vec<Packet>>(); let cert = cert.insert_packets(sigs).unwrap().0; for ka in cert.keys().with_policy(p, None) { assert!(ka.alive().is_ok()); } // Primary should not be expired two years from now. assert!(cert.primary_key().with_policy(p, in_two_years).unwrap() .alive().is_ok()); // But the subkeys should be. for ka in cert.keys().subkeys().with_policy(p, in_two_years) { assert!(ka.alive().is_err()); } } /// Test that subkeys of expired certificates are also considered /// expired. #[test] fn issue_564() -> Result<()> { use crate::parse::Parse; use crate::packet::signature::subpacket::SubpacketTag; let p = &P::new(); let cert = Cert::from_bytes(crate::tests::key("testy.pgp"))?; assert!(cert.with_policy(p, None)?.alive().is_err()); let subkey = cert.with_policy(p, None)?.keys().nth(1).unwrap(); assert!(subkey.binding_signature().hashed_area() .subpacket(SubpacketTag::KeyExpirationTime).is_none()); assert!(subkey.alive().is_err()); Ok(()) } /// When setting the primary key's validity period, we create a /// direct key signature. Check that this works even when the /// original certificate doesn't have a direct key signature. #[test] fn set_expiry_on_certificate_without_direct_signature() -> Result<()> { use crate::policy::StandardPolicy; let p = &StandardPolicy::new(); let (cert, _) = CertBuilder::general_purpose(Some("alice@example.org")) .set_validity_period(None) .generate()?; // Remove the direct key signatures. let cert = Cert::from_packets( cert.as_tsk().into_packets() .filter(|p| ! matches!( p, Packet::Signature(s) if s.typ() == SignatureType::DirectKey )))?; let vc = cert.with_policy(p, None)?; // Assert that the keys are not expired. for ka in vc.keys() { assert!(ka.alive().is_ok()); } // Make the primary key expire in a week. let t = crate::now() + time::Duration::from_secs(7 * 24 * 60 * 60); let mut signer = vc .primary_key().key().clone().parts_into_secret()? .into_keypair()?; let sigs = vc.primary_key() .set_expiration_time(&mut signer, Some(t))?; assert!(sigs.iter().any(|s| { s.typ() == SignatureType::DirectKey })); let cert = cert.insert_packets(sigs)?.0; // Make sure the primary key *and* all subkeys expire in a // week: the subkeys inherit the KeyExpirationTime subpacket // from the direct key signature. for ka in cert.keys() { let ka = ka.with_policy(p, None)?; assert!(ka.alive().is_ok()); let ka = ka.with_policy(p, t + std::time::Duration::new(1, 0))?; assert!(ka.alive().is_err()); } Ok(()) } #[test] fn key_amalgamation_certifications_by_key() -> Result<()> { // Alice and Bob certify Carol's certificate. We then check // that valid_certifications_by_key and // active_certifications_by_key return them. let p = &crate::policy::StandardPolicy::new(); // $ date -u -d '2024-01-02 13:00' +%s let t0 = UNIX_EPOCH + Duration::new(1704200400, 0); // $ date -u -d '2024-01-02 14:00' +%s let t1 = UNIX_EPOCH + Duration::new(1704204000, 0); // $ date -u -d '2024-01-02 15:00' +%s let t2 = UNIX_EPOCH + Duration::new(1704207600, 0); let (alice, _) = CertBuilder::new() .set_creation_time(t0) .add_userid("<alice@example.example>") .generate() .unwrap(); let alice_primary = alice.primary_key().key(); let (bob, _) = CertBuilder::new() .set_creation_time(t0) .add_userid("<bob@example.example>") .generate() .unwrap(); let bob_primary = bob.primary_key().key(); let carol_userid = "<carol@example.example>"; let (carol, _) = CertBuilder::new() .set_creation_time(t0) .add_userid(carol_userid) .generate() .unwrap(); let ka = alice.primary_key(); assert_eq!( ka.valid_certifications_by_key(p, None, alice_primary).count(), 0); assert_eq!( ka.active_certifications_by_key(p, None, alice_primary).count(), 0); // Alice has not certified Bob's User ID. let ka = bob.primary_key(); assert_eq!( ka.valid_certifications_by_key(p, None, alice_primary).count(), 0); assert_eq!( ka.active_certifications_by_key(p, None, alice_primary).count(), 0); // Alice has not certified Carol's User ID. let ka = carol.primary_key(); assert_eq!( ka.valid_certifications_by_key(p, None, alice_primary).count(), 0); assert_eq!( ka.active_certifications_by_key(p, None, alice_primary).count(), 0); // Have Alice certify Carol's certificate at t1. let mut alice_signer = alice_primary .clone() .parts_into_secret().expect("have unencrypted key material") .into_keypair().expect("have unencrypted key material"); let certification = SignatureBuilder::new(SignatureType::DirectKey) .set_signature_creation_time(t1)? .sign_direct_key( &mut alice_signer, carol.primary_key().key())?; let carol = carol.insert_packets(certification.clone())?.0; // Check that it is returned. let ka = carol.primary_key(); assert_eq!(ka.certifications().count(), 1); assert_eq!( ka.valid_certifications_by_key(p, t0, alice_primary).count(), 0); assert_eq!( ka.active_certifications_by_key(p, t0, alice_primary).count(), 0); assert_eq!( ka.valid_certifications_by_key(p, t1, alice_primary).count(), 1); assert_eq!( ka.active_certifications_by_key(p, t1, alice_primary).count(), 1); assert_eq!( ka.valid_certifications_by_key(p, t1, bob_primary).count(), 0); assert_eq!( ka.active_certifications_by_key(p, t1, bob_primary).count(), 0); // Have Alice certify Carol's certificate at t1 (again). // Since both certifications were created at t1, they should // both be returned. let mut alice_signer = alice_primary .clone() .parts_into_secret().expect("have unencrypted key material") .into_keypair().expect("have unencrypted key material"); let certification = SignatureBuilder::new(SignatureType::DirectKey) .set_signature_creation_time(t1)? .sign_direct_key( &mut alice_signer, carol.primary_key().key())?; let carol = carol.insert_packets(certification.clone())?.0; // Check that it is returned. let ka = carol.primary_key(); assert_eq!(ka.certifications().count(), 2); assert_eq!( ka.valid_certifications_by_key(p, t0, alice_primary).count(), 0); assert_eq!( ka.active_certifications_by_key(p, t0, alice_primary).count(), 0); assert_eq!( ka.valid_certifications_by_key(p, t1, alice_primary).count(), 2); assert_eq!( ka.active_certifications_by_key(p, t1, alice_primary).count(), 2); assert_eq!( ka.valid_certifications_by_key(p, t2, alice_primary).count(), 2); assert_eq!( ka.active_certifications_by_key(p, t2, alice_primary).count(), 2); assert_eq!( ka.valid_certifications_by_key(p, t0, bob_primary).count(), 0); assert_eq!( ka.active_certifications_by_key(p, t0, bob_primary).count(), 0); // Have Alice certify Carol's certificate at t2. Now we only // have one active certification. let mut alice_signer = alice_primary .clone() .parts_into_secret().expect("have unencrypted key material") .into_keypair().expect("have unencrypted key material"); let certification = SignatureBuilder::new(SignatureType::DirectKey) .set_signature_creation_time(t2)? .sign_direct_key( &mut alice_signer, carol.primary_key().key())?; let carol = carol.insert_packets(certification.clone())?.0; // Check that it is returned. let ka = carol.primary_key(); assert_eq!(ka.certifications().count(), 3); assert_eq!( ka.valid_certifications_by_key(p, t0, alice_primary).count(), 0); assert_eq!( ka.active_certifications_by_key(p, t0, alice_primary).count(), 0); assert_eq!( ka.valid_certifications_by_key(p, t1, alice_primary).count(), 2); assert_eq!( ka.active_certifications_by_key(p, t1, alice_primary).count(), 2); assert_eq!( ka.valid_certifications_by_key(p, t2, alice_primary).count(), 3); assert_eq!( ka.active_certifications_by_key(p, t2, alice_primary).count(), 1); assert_eq!( ka.valid_certifications_by_key(p, t0, bob_primary).count(), 0); assert_eq!( ka.active_certifications_by_key(p, t0, bob_primary).count(), 0); // Have Bob certify Carol's certificate at t1 and have it expire at t2. let mut bob_signer = bob.primary_key() .key() .clone() .parts_into_secret().expect("have unencrypted key material") .into_keypair().expect("have unencrypted key material"); let certification = SignatureBuilder::new(SignatureType::DirectKey) .set_signature_creation_time(t1)? .set_signature_validity_period(t2.duration_since(t1)?)? .sign_direct_key( &mut bob_signer, carol.primary_key().key())?; let carol = carol.insert_packets(certification.clone())?.0; // Check that it is returned. let ka = carol.primary_key(); assert_eq!(ka.certifications().count(), 4); assert_eq!( ka.valid_certifications_by_key(p, t0, alice_primary).count(), 0); assert_eq!( ka.active_certifications_by_key(p, t0, alice_primary).count(), 0); assert_eq!( ka.valid_certifications_by_key(p, t1, alice_primary).count(), 2); assert_eq!( ka.active_certifications_by_key(p, t1, alice_primary).count(), 2); assert_eq!( ka.valid_certifications_by_key(p, t2, alice_primary).count(), 3); assert_eq!( ka.active_certifications_by_key(p, t2, alice_primary).count(), 1); assert_eq!( ka.valid_certifications_by_key(p, t0, bob_primary).count(), 0); assert_eq!( ka.active_certifications_by_key(p, t0, bob_primary).count(), 0); assert_eq!( ka.valid_certifications_by_key(p, t1, bob_primary).count(), 1); assert_eq!( ka.active_certifications_by_key(p, t1, bob_primary).count(), 1); // It expired. assert_eq!( ka.valid_certifications_by_key(p, t2, bob_primary).count(), 0); assert_eq!( ka.active_certifications_by_key(p, t2, bob_primary).count(), 0); // Have Bob certify Carol's certificate at t1 again. This // time don't have it expire. let mut bob_signer = bob.primary_key() .key() .clone() .parts_into_secret().expect("have unencrypted key material") .into_keypair().expect("have unencrypted key material"); let certification = SignatureBuilder::new(SignatureType::DirectKey) .set_signature_creation_time(t1)? .sign_direct_key( &mut bob_signer, carol.primary_key().key())?; let carol = carol.insert_packets(certification.clone())?.0; // Check that it is returned. let ka = carol.primary_key(); assert_eq!(ka.certifications().count(), 5); assert_eq!( ka.valid_certifications_by_key(p, t0, alice_primary).count(), 0); assert_eq!( ka.active_certifications_by_key(p, t0, alice_primary).count(), 0); assert_eq!( ka.valid_certifications_by_key(p, t1, alice_primary).count(), 2); assert_eq!( ka.active_certifications_by_key(p, t1, alice_primary).count(), 2); assert_eq!( ka.valid_certifications_by_key(p, t2, alice_primary).count(), 3); assert_eq!( ka.active_certifications_by_key(p, t2, alice_primary).count(), 1); assert_eq!( ka.valid_certifications_by_key(p, t0, bob_primary).count(), 0); assert_eq!( ka.active_certifications_by_key(p, t0, bob_primary).count(), 0); assert_eq!( ka.valid_certifications_by_key(p, t1, bob_primary).count(), 2); assert_eq!( ka.active_certifications_by_key(p, t1, bob_primary).count(), 2); // One of the certifications expired. assert_eq!( ka.valid_certifications_by_key(p, t2, bob_primary).count(), 1); assert_eq!( ka.active_certifications_by_key(p, t2, bob_primary).count(), 1); Ok(()) } fn key_amalgamation_valid_third_party_revocations_by_key( reason: ReasonForRevocation) -> Result<()> { // Hard revocations are returned independent of the reference // time and independent of their expiration. They are always // live. let soft = reason.revocation_type() == RevocationType::Soft; // Alice and Bob revoke Carol's certificate. We then check // that valid_third_party_revocations_by_key returns them. let p = &crate::policy::StandardPolicy::new(); // $ date -u -d '2024-01-02 13:00' +%s let t0 = UNIX_EPOCH + Duration::new(1704200400, 0); // $ date -u -d '2024-01-02 14:00' +%s let t1 = UNIX_EPOCH + Duration::new(1704204000, 0); // $ date -u -d '2024-01-02 15:00' +%s let t2 = UNIX_EPOCH + Duration::new(1704207600, 0); let (alice, _) = CertBuilder::new() .set_creation_time(t0) .add_userid("<alice@example.example>") .generate() .unwrap(); let alice_primary = alice.primary_key().key(); let (bob, _) = CertBuilder::new() .set_creation_time(t0) .add_userid("<bob@example.example>") .generate() .unwrap(); let bob_primary = bob.primary_key().key(); let carol_userid = "<carol@example.example>"; let (carol, _) = CertBuilder::new() .set_creation_time(t0) .add_userid(carol_userid) .generate() .unwrap(); let ka = alice.primary_key(); assert_eq!( ka.valid_third_party_revocations_by_key(p, None, alice_primary).count(), 0); // Alice has not revoked Bob's certificate. let ka = bob.primary_key(); assert_eq!( ka.valid_third_party_revocations_by_key(p, None, alice_primary).count(), 0); // Alice has not revoked Carol's certificate. let ka = carol.primary_key(); assert_eq!( ka.valid_third_party_revocations_by_key(p, None, alice_primary).count(), 0); // Have Alice revoke Carol's revoke at t1. let mut alice_signer = alice_primary .clone() .parts_into_secret().expect("have unencrypted key material") .into_keypair().expect("have unencrypted key material"); let rev = SignatureBuilder::new(SignatureType::KeyRevocation) .set_signature_creation_time(t1)? .set_reason_for_revocation( reason, b"")? .sign_direct_key( &mut alice_signer, carol.primary_key().key())?; let carol = carol.insert_packets(rev)?.0; // Check that it is returned. let ka = carol.primary_key(); assert_eq!(ka.other_revocations().count(), 1); assert_eq!( ka.valid_third_party_revocations_by_key(p, t0, alice_primary).count(), if soft { 0 } else { 1 }); assert_eq!( ka.valid_third_party_revocations_by_key(p, t1, alice_primary).count(), 1); assert_eq!( ka.valid_third_party_revocations_by_key(p, t1, bob_primary).count(), 0); // Have Alice revoke Carol's certificate at t1 (again). let mut alice_signer = alice_primary .clone() .parts_into_secret().expect("have unencrypted key material") .into_keypair().expect("have unencrypted key material"); let rev = SignatureBuilder::new(SignatureType::KeyRevocation) .set_signature_creation_time(t1)? .set_reason_for_revocation(reason, b"")? .sign_direct_key( &mut alice_signer, carol.primary_key().key())?; let carol = carol.insert_packets(rev)?.0; // Check that it is returned. let ka = carol.primary_key(); assert_eq!(ka.other_revocations().count(), 2); assert_eq!( ka.valid_third_party_revocations_by_key(p, t0, alice_primary).count(), if soft { 0 } else { 2 }); assert_eq!( ka.valid_third_party_revocations_by_key(p, t1, alice_primary).count(), 2); assert_eq!( ka.valid_third_party_revocations_by_key(p, t2, alice_primary).count(), 2); assert_eq!( ka.valid_third_party_revocations_by_key(p, t0, bob_primary).count(), 0); // Have Alice revoke Carol's certificate at t2. let mut alice_signer = alice_primary .clone() .parts_into_secret().expect("have unencrypted key material") .into_keypair().expect("have unencrypted key material"); let rev = SignatureBuilder::new(SignatureType::KeyRevocation) .set_signature_creation_time(t2)? .set_reason_for_revocation(reason, b"")? .sign_direct_key( &mut alice_signer, carol.primary_key().key())?; let carol = carol.insert_packets(rev)?.0; // Check that it is returned. let ka = carol.primary_key(); assert_eq!(ka.other_revocations().count(), 3); assert_eq!( ka.valid_third_party_revocations_by_key(p, t0, alice_primary).count(), if soft { 0 } else { 3 }); assert_eq!( ka.valid_third_party_revocations_by_key(p, t1, alice_primary).count(), if soft { 2 } else { 3 }); assert_eq!( ka.valid_third_party_revocations_by_key(p, t2, alice_primary).count(), 3); assert_eq!( ka.valid_third_party_revocations_by_key(p, t0, bob_primary).count(), 0); // Have Bob revoke Carol's certificate at t1 and have it expire at t2. let mut bob_signer = bob.primary_key() .key() .clone() .parts_into_secret().expect("have unencrypted key material") .into_keypair().expect("have unencrypted key material"); let rev = SignatureBuilder::new(SignatureType::KeyRevocation) .set_signature_creation_time(t1)? .set_signature_validity_period(t2.duration_since(t1)?)? .set_reason_for_revocation(reason, b"")? .sign_direct_key( &mut bob_signer, carol.primary_key().key())?; let carol = carol.insert_packets(rev)?.0; // Check that it is returned. let ka = carol.primary_key(); assert_eq!(ka.other_revocations().count(), 4); assert_eq!( ka.valid_third_party_revocations_by_key(p, t0, alice_primary).count(), if soft { 0 } else { 3 }); assert_eq!( ka.valid_third_party_revocations_by_key(p, t1, alice_primary).count(), if soft { 2 } else { 3 }); assert_eq!( ka.valid_third_party_revocations_by_key(p, t2, alice_primary).count(), 3); assert_eq!( ka.valid_third_party_revocations_by_key(p, t0, bob_primary).count(), if soft { 0 } else { 1 }); assert_eq!( ka.valid_third_party_revocations_by_key(p, t1, bob_primary).count(), 1); // It expired. assert_eq!( ka.valid_third_party_revocations_by_key(p, t2, bob_primary).count(), if soft { 0 } else { 1 }); // Have Bob revoke Carol's certificate at t1 again. This // time don't have it expire. let mut bob_signer = bob.primary_key() .key() .clone() .parts_into_secret().expect("have unencrypted key material") .into_keypair().expect("have unencrypted key material"); let rev = SignatureBuilder::new(SignatureType::KeyRevocation) .set_signature_creation_time(t1)? .set_reason_for_revocation(reason, b"")? .sign_direct_key( &mut bob_signer, carol.primary_key().key())?; let carol = carol.insert_packets(rev)?.0; // Check that it is returned. let ka = carol.primary_key(); assert_eq!( ka.other_revocations().count(), 5); assert_eq!( ka.valid_third_party_revocations_by_key(p, t0, alice_primary).count(), if soft { 0 } else { 3 }); assert_eq!( ka.valid_third_party_revocations_by_key(p, t1, alice_primary).count(), if soft { 2 } else { 3 }); assert_eq!( ka.valid_third_party_revocations_by_key(p, t2, alice_primary).count(), 3); assert_eq!( ka.valid_third_party_revocations_by_key(p, t0, bob_primary).count(), if soft { 0 } else { 2 }); assert_eq!( ka.valid_third_party_revocations_by_key(p, t1, bob_primary).count(), 2); // One of the revocations expired. assert_eq!( ka.valid_third_party_revocations_by_key(p, t2, bob_primary).count(), if soft { 1 } else { 2 }); Ok(()) } #[test] fn key_amalgamation_valid_third_party_revocations_by_key_soft() -> Result<()> { key_amalgamation_valid_third_party_revocations_by_key( ReasonForRevocation::KeyRetired) } #[test] fn key_amalgamation_valid_third_party_revocations_by_key_hard() -> Result<()> { key_amalgamation_valid_third_party_revocations_by_key( ReasonForRevocation::KeyCompromised) } } �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/src/cert/amalgamation.rs������������������������������������������������������0000644�0000000�0000000�00000415541�10461020230�0017475�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������//! Components, their associated signatures, and some useful methods. //! //! Whereas a [`ComponentBundle`] owns a `Component` and its //! associated [`Signature`]s, a [`ComponentAmalgamation`] references //! a `ComponentBundle` and its containing [`Cert`]. This additional //! context means that a `ComponentAmalgamation` can implement more of //! OpenPGP's high-level semantics than a `ComponentBundle` can. For //! instance, most of the information about a primary key, such as its //! capabilities, is on the primary User ID's binding signature. A //! `ComponentAmalgamation` can find the certificate's primary User //! ID; a `ComponentBundle` can't. Similarly, when looking up a //! subpacket, if it isn't present in the component's binding //! signature, then an OpenPGP implementation [is supposed to] consult //! the certificate's direct key signatures. A //! `ComponentAmalgamation` has access to this information; a //! `ComponentBundle` doesn't. //! //! Given the limitations of a `ComponentBundle`, it would seem more //! useful to just change it to include a reference to its containing //! certificate. That change would make `ComponentAmalgamation`s //! redundant. Unfortunately, this isn't possible, because it would //! result in a self-referential data structure, which Rust doesn't //! allow. To understand how this arises, consider a certificate `C`, //! which contains a `ComponentBundle` `B`. If `B` contains a //! reference to `C`, then `C` references itself, because `C` contains //! `B`! //! //! ```text //! Cert:[ Bundle:[ &Cert ] ] //! ^ | //! `------------' //! ``` //! //! # Policy //! //! Although a `ComponentAmalgamation` contains the information //! necessary to realize high-level OpenPGP functionality, components //! can have multiple self signatures, and functions that consult the //! binding signature need to determine the best one to use. There //! are two main concerns here. //! //! First, we need to protect the user from forgeries. As attacks //! improve, cryptographic algorithms that were once considered secure //! now provide insufficient security margins. For instance, in 2007 //! it was possible to find [MD5 collisions] using just a few seconds //! of computing time on a desktop computer. Sequoia provides a //! flexible mechanism, called [`Policy`] objects, that allow users to //! implement this type of filtering: before a self signature is used, //! a policy object is queried to determine whether the `Signature` //! should be rejected. If so, then it is skipped. //! //! Second, we need an algorithm to determine the most appropriate //! self signature. Obvious non-candidate self signatures are self //! signatures whose creation time is in the future. We don't assume //! that these self signatures are bad per se, but that they represent //! a policy that should go into effect some time in the future. //! //! We extend this idea of a self signature representing a policy for //! a certain period of time to all self signatures. In particular, //! Sequoia takes the view that *a binding signature represents a //! policy that is valid from its creation time until its expiry*. //! Thus, when considering what self signature to use, we need a //! reference time. Given the reference time, we then use the self //! signature that was in effect at that time, i.e., the most recent, //! non-expired, non-revoked self signature that was created at or //! prior to the reference time. In other words, we ignore self //! signatures created after the reference time. We take the position //! that if the certificate holder wants a new policy to apply to //! existing signatures, then the new self signature should be //! backdated, and existing self signatures revoked, if necessary. //! //! Consider evaluating a signature over a document. Sequoia's //! [streaming verifier] uses the signature's creation time as the //! reference time. Thus, if the signature was created on June 9th, //! 2011, then, when evaluating that signature, the streaming verifier //! uses a self signature that was live at that time, since that was //! the self signature that represented the signer's policy at the //! time the signature over the document was created. //! //! A consequence of this approach is that even if the self signature //! were considered expired at the time the signature was evaluated //! (e.g., "now"), this fact doesn't invalidate the signature. That //! is, a self signature's lifetime does not impact a signature's //! lifetime; a signature's lifetime is defined by its own creation //! time and expiry. Similarly, a key's lifetime is defined by its //! own creation time and expiry. //! //! This interpretation of lifetimes removes a major disadvantage that //! comes with fast rotation of subkeys: if an implementation binds //! the lifetime of signatures to the signing key, and the key //! expires, then old signatures are considered invalid. Consider a //! user who generates a new signature subkey each week, and sets it //! to expire after exactly one week. If we use the policy that the //! signature is only valid while the key *and* the self signature are //! live, then if someone checks the signature a week after receiving //! it, the signature will be considered invalid, because the key has //! expired. The practical result is that all old messages from this //! user will be considered invalid! Unfortunately, this will result //! in users becoming accustomed to seeing invalid signatures, and //! cause them to be less suspcious of them. //! //! Sequoia's low-level mechanisms support this interpretation of self //! signatures, but they do *not* enforce it. It is still possible to //! realize other policies using this low-level API. //! //! The possibility of abuse of this interpretation of signature //! lifetimes is limited. If a key has been compromised, then the //! right thing to do is to revoke it. Expiry doesn't help: the //! attacker can simply create self-signatures that say whatever she //! wants. Assuming the secret key material has not been compromised, //! then an attacker could still reuse a message that would otherwise //! be considered expired. However, the attacker will not be able to //! change the signature's creation time, so, assuming a mail context //! and MUAs that check that the time in the message's headers matches //! the signature's creation time, the mails will appear old. //! Further, this type of attack will be mitigated by the proposed //! "[Intended Recipients]" subpacket, which more tightly binds the //! message to its context. //! //! # [`ValidComponentAmalgamation`] //! //! Most operations need to query a `ComponentAmalgamation` for //! multiple pieces of information. Accidentally using a different //! `Policy` or a different reference time for one of the queries is //! easy, especially when the queries are spread across multiple //! functions. Further, using `None` for the reference time can //! result in subtle timing bugs as each function translates it to the //! current time on demand. In these cases, the correct approach //! would be for the user of the library to get the current time at //! the start of the operation. But, this is less convenient. //! Finally, passing a `Policy` and a reference time to most function //! calls clutters the code. //! //! To mitigate these issues, we have a separate data structure, //! `ValidComponentAmalgamation`, which combines a //! `ComponetAmalgamation`, a `Policy` and a reference time. It //! implements methods that require a `Policy` and reference time, but //! instead of requiring the caller to pass them in, it uses the ones //! embedded in the data structure. Further, when the //! `ValidComponentAmalgamation` constructor is passed `None` for the //! reference time, it eagerly stores the current time, and uses that //! for all operations. This approach elegantly solves all the //! aforementioned problems. //! //! [`ComponentBundle`]: super::bundle //! [`Signature`]: crate::packet::signature //! [`Cert`]: super //! [is supposed to]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.10 //! [`std::iter::map`]: std::iter::Map //! [MD5 collisions]: https://en.wikipedia.org/wiki/MD5 //! [`Policy`]: crate::policy::Policy //! [streaming verifier]: crate::parse::stream //! [Intended Recipients]: https://www.rfc-editor.org/rfc/rfc9580.html#intended-recipient-fingerprint //! [signature expirations]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.18 use std::time; use std::time::{ Duration, SystemTime, }; use std::clone::Clone; use std::borrow::Borrow; use crate::{ cert::prelude::*, crypto::{Signer, hash::Hash}, Error, KeyHandle, packet, packet::{ Key, Signature, Unknown, UserAttribute, UserID, key::{PrimaryRole, PublicParts}, }, Result, policy::{ HashAlgoSecurity, Policy, }, seal, types::{ AEADAlgorithm, CompressionAlgorithm, Features, HashAlgorithm, KeyServerPreferences, RevocationKey, RevocationStatus, RevocationType, SignatureType, SymmetricAlgorithm, }, }; mod iter; pub use iter::{ ComponentAmalgamationIter, UnknownComponentAmalgamationIter, UserAttributeAmalgamationIter, UserIDAmalgamationIter, ValidComponentAmalgamationIter, ValidUserAttributeAmalgamationIter, ValidUserIDAmalgamationIter, }; pub mod key; /// Embeds a policy and a reference time in an amalgamation. /// /// This is used to turn a [`ComponentAmalgamation`] into a /// [`ValidComponentAmalgamation`], and a [`KeyAmalgamation`] into a /// [`ValidKeyAmalgamation`]. /// /// A certificate or a component is considered valid if: /// /// - It has a self signature that is live at time `t`. /// /// - The policy considers it acceptable. /// /// - The certificate is valid. /// /// # Sealed trait /// /// This trait is [sealed] and cannot be implemented for types outside this crate. /// Therefore it can be extended in a non-breaking way. /// If you want to implement the trait inside the crate /// you also need to implement the `seal::Sealed` marker trait. /// /// [sealed]: https://rust-lang.github.io/api-guidelines/future-proofing.html#sealed-traits-protect-against-downstream-implementations-c-sealed /// /// # Examples /// /// ``` /// # use sequoia_openpgp as openpgp; /// use openpgp::cert::prelude::*; /// use openpgp::policy::{Policy, StandardPolicy}; /// /// const POLICY: &dyn Policy = &StandardPolicy::new(); /// /// fn f(ua: UserIDAmalgamation) -> openpgp::Result<()> { /// let ua = ua.with_policy(POLICY, None)?; /// // ... /// # Ok(()) /// } /// # fn main() -> openpgp::Result<()> { /// # let (cert, _) = /// # CertBuilder::general_purpose(Some("alice@example.org")) /// # .generate()?; /// # let ua = cert.userids().nth(0).expect("User IDs"); /// # f(ua); /// # Ok(()) /// # } /// ``` /// pub trait ValidateAmalgamation<'a, C: 'a>: seal::Sealed { /// The type returned by `with_policy`. /// /// This is either a [`ValidComponentAmalgamation`] or /// a [`ValidKeyAmalgamation`]. /// type V; /// Uses the specified `Policy` and reference time with the amalgamation. /// /// If `time` is `None`, the current time is used. fn with_policy<T>(&self, policy: &'a dyn Policy, time: T) -> Result<Self::V> where T: Into<Option<time::SystemTime>>, Self: Sized; } /// Applies a policy to an amalgamation. /// /// This is an internal variant of `ValidateAmalgamation`, which /// allows validating a component for an otherwise invalid /// certificate. See `ValidComponentAmalgamation::primary` for an /// explanation. trait ValidateAmalgamationRelaxed<'a, C: 'a> { /// The type returned by `with_policy`. type V; /// Changes the amalgamation's policy. /// /// If `time` is `None`, the current time is used. /// /// If `valid_cert` is `false`, then this does not also check /// whether the certificate is valid; it only checks whether the /// component is valid. Normally, this should be `true`. This /// option is only expose to allow breaking an infinite recursion: /// /// - To check if a certificate is valid, we check if the /// primary key is valid. /// /// - To check if the primary key is valid, we need the primary /// key's self signature /// /// - To find the primary key's self signature, we need to find /// the primary user id /// /// - To find the primary user id, we need to check if the user /// id is valid. /// /// - To check if the user id is valid, we need to check that /// the corresponding certificate is valid. fn with_policy_relaxed<T>(&self, policy: &'a dyn Policy, time: T, valid_cert: bool) -> Result<Self::V> where T: Into<Option<time::SystemTime>>, Self: Sized; } /// Methods for valid amalgamations. /// /// The methods exposed by a `ValidComponentAmalgamation` are similar /// to those exposed by a `ComponentAmalgamation`, but the policy and /// reference time are included in the `ValidComponentAmalgamation`. /// This helps prevent using different policies or different reference /// times when using a component, which can easily happen when the /// checks span multiple functions. /// /// # Sealed trait /// /// This trait is [sealed] and cannot be implemented for types outside this crate. /// Therefore it can be extended in a non-breaking way. /// If you want to implement the trait inside the crate /// you also need to implement the `seal::Sealed` marker trait. /// /// [sealed]: https://rust-lang.github.io/api-guidelines/future-proofing.html#sealed-traits-protect-against-downstream-implementations-c-sealed pub trait ValidAmalgamation<'a, C: 'a>: seal::Sealed { /// Returns the valid amalgamation's associated certificate. /// /// # Examples /// /// ``` /// # use sequoia_openpgp as openpgp; /// # use openpgp::cert::prelude::*; /// # use openpgp::policy::StandardPolicy; /// # /// fn f(ua: &ValidUserIDAmalgamation) { /// let vcert = ua.valid_cert(); /// // ... /// } /// # fn main() -> openpgp::Result<()> { /// # let p = &StandardPolicy::new(); /// # let (cert, _) = /// # CertBuilder::general_purpose(Some("alice@example.org")) /// # .generate()?; /// # let fpr = cert.fingerprint(); /// # let ua = cert.userids().nth(0).expect("User IDs"); /// # assert_eq!(ua.cert().fingerprint(), fpr); /// # f(&ua.with_policy(p, None)?); /// # Ok(()) /// # } /// ``` fn valid_cert(&self) -> &ValidCert<'a>; /// Returns the amalgamation's reference time. /// /// # Examples /// /// ``` /// # use std::time::{SystemTime, Duration, UNIX_EPOCH}; /// # /// # use sequoia_openpgp as openpgp; /// # use openpgp::cert::prelude::*; /// # use openpgp::policy::StandardPolicy; /// fn f(ua: &ValidUserIDAmalgamation) { /// let t = ua.time(); /// // ... /// } /// # fn main() -> openpgp::Result<()> { /// # let p = &StandardPolicy::new(); /// # let t = UNIX_EPOCH + Duration::from_secs(1554542220); /// # let (cert, _) = /// # CertBuilder::general_purpose(Some("alice@example.org")) /// # .set_creation_time(t) /// # .generate()?; /// # let ua = cert.userids().nth(0).expect("User IDs"); /// # let ua = ua.with_policy(p, t)?; /// # assert_eq!(t, ua.time()); /// # f(&ua); /// # Ok(()) /// # } /// ``` fn time(&self) -> SystemTime; /// Returns the amalgamation's policy. /// /// # Examples /// /// ``` /// # use sequoia_openpgp as openpgp; /// # use openpgp::cert::prelude::*; /// # use openpgp::policy::{Policy, StandardPolicy}; /// # /// fn f(ua: &ValidUserIDAmalgamation) { /// let policy = ua.policy(); /// // ... /// } /// # fn main() -> openpgp::Result<()> { /// # let p: &dyn Policy = &StandardPolicy::new(); /// # let (cert, _) = /// # CertBuilder::general_purpose(Some("alice@example.org")) /// # .generate()?; /// # let ua = cert.userids().nth(0).expect("User IDs"); /// # let ua = ua.with_policy(p, None)?; /// # assert!(std::ptr::eq(p, ua.policy())); /// # f(&ua); /// # Ok(()) /// # } /// ``` fn policy(&self) -> &'a dyn Policy; /// Returns the component's binding signature as of the reference time. /// /// # Examples /// /// ``` /// # use sequoia_openpgp as openpgp; /// # use openpgp::cert::prelude::*; /// # use openpgp::policy::{Policy, StandardPolicy}; /// # /// fn f(ua: &ValidUserIDAmalgamation) { /// let sig = ua.binding_signature(); /// // ... /// } /// # fn main() -> openpgp::Result<()> { /// # let p: &dyn Policy = &StandardPolicy::new(); /// # let (cert, _) = /// # CertBuilder::general_purpose(Some("alice@example.org")) /// # .generate()?; /// # let ua = cert.userids().nth(0).expect("User IDs"); /// # let ua = ua.with_policy(p, None)?; /// # f(&ua); /// # Ok(()) /// # } /// ``` fn binding_signature(&self) -> &'a Signature; /// Returns the certificate's direct key signature as of the /// reference time, if any. /// /// Subpackets on direct key signatures apply to all components of /// the certificate, cf. [Section 5.2.3.10 of RFC 9580]. /// /// [Section 5.2.3.10 of RFC 9580]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.10 /// /// # Examples /// /// ``` /// # use sequoia_openpgp as openpgp; /// # use openpgp::cert::prelude::*; /// # use openpgp::policy::{Policy, StandardPolicy}; /// # /// fn f(ua: &ValidUserIDAmalgamation) { /// let sig = ua.direct_key_signature(); /// // ... /// } /// # fn main() -> openpgp::Result<()> { /// # let p: &dyn Policy = &StandardPolicy::new(); /// # let (cert, _) = /// # CertBuilder::general_purpose(Some("alice@example.org")) /// # .generate()?; /// # let cert = cert.with_policy(p, None)?; /// # let ua = cert.userids().nth(0).expect("User IDs"); /// # assert!(std::ptr::eq(ua.direct_key_signature().unwrap(), /// # cert.direct_key_signature().unwrap())); /// # f(&ua); /// # Ok(()) /// # } /// ``` fn direct_key_signature(&self) -> Result<&'a Signature> { self.valid_cert().cert().primary_key() .binding_signature(self.policy(), self.time()) } /// Returns the component's revocation status as of the amalgamation's /// reference time. /// /// This does *not* check whether the certificate has been /// revoked. For that, use `Cert::revocation_status()`. /// /// Note, as per [Section 5.2.3.31 of RFC 9580], a key is considered to be revoked at /// some time if there were no soft revocations created as of that /// time, and no hard revocations: /// /// > If a key has been revoked because of a compromise, all signatures /// > created by that key are suspect. However, if it was merely /// > superseded or retired, old signatures are still valid. /// /// [Section 5.2.3.31 of RFC 9580]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.31 /// /// # Examples /// /// ``` /// # use sequoia_openpgp as openpgp; /// use openpgp::cert::prelude::*; /// # use openpgp::policy::StandardPolicy; /// use openpgp::types::RevocationStatus; /// /// # fn main() -> openpgp::Result<()> { /// # let p = &StandardPolicy::new(); /// # let (cert, _) = /// # CertBuilder::general_purpose(Some("alice@example.org")) /// # .generate()?; /// # let cert = cert.with_policy(p, None)?; /// # let ua = cert.userids().nth(0).expect("User IDs"); /// match ua.revocation_status() { /// RevocationStatus::Revoked(revs) => { /// // The certificate holder revoked the User ID. /// # unreachable!(); /// } /// RevocationStatus::CouldBe(revs) => { /// // There are third-party revocations. You still need /// // to check that they are valid (this is necessary, /// // because without the Certificates are not normally /// // available to Sequoia). /// # unreachable!(); /// } /// RevocationStatus::NotAsFarAsWeKnow => { /// // We have no evidence that the User ID is revoked. /// } /// } /// # Ok(()) /// # } /// ``` fn revocation_status(&self) -> RevocationStatus<'a>; /// Returns a list of any designated revokers for this component. /// /// This function returns the designated revokers listed on the /// components' binding signatures and the certificate's direct /// key signatures. /// /// Note: the returned list is deduplicated. /// /// # Examples /// /// ``` /// use sequoia_openpgp as openpgp; /// # use openpgp::Result; /// use openpgp::cert::prelude::*; /// use openpgp::policy::StandardPolicy; /// use openpgp::types::RevocationKey; /// /// # fn main() -> Result<()> { /// let p = &StandardPolicy::new(); /// /// let (alice, _) = /// CertBuilder::general_purpose(Some("alice@example.org")) /// .generate()?; /// // Make Alice a designated revoker for Bob. /// let (bob, _) = /// CertBuilder::general_purpose(Some("bob@example.org")) /// .set_revocation_keys(vec![(&alice).into()]) /// .generate()?; /// /// // Make sure Alice is listed as a designated revoker for Bob's /// // primary user id. /// assert_eq!(bob.with_policy(p, None)?.primary_userid()? /// .revocation_keys().collect::<Vec<&RevocationKey>>(), /// vec![&(&alice).into()]); /// /// // Make sure Alice is listed as a designated revoker for Bob's /// // encryption subkey. /// assert_eq!(bob.with_policy(p, None)? /// .keys().for_transport_encryption().next().unwrap() /// .revocation_keys().collect::<Vec<&RevocationKey>>(), /// vec![&(&alice).into()]); /// # Ok(()) } /// ``` fn revocation_keys(&self) -> Box<dyn Iterator<Item = &'a RevocationKey> + 'a>; } #[test] fn valid_amalgamation_is_dyn_compatible() { let _t: Option<Box<dyn ValidAmalgamation<()>>> = None; } /// Locates information on the active binding signature or direct key /// signature. /// /// # Sealed trait /// /// This trait is [sealed] and cannot be implemented for types outside /// this crate. Therefore it can be extended in a non-breaking way. /// If you want to implement the trait inside the crate you also need /// to implement the `seal::Sealed` marker trait. /// /// [sealed]: https://rust-lang.github.io/api-guidelines/future-proofing.html#sealed-traits-protect-against-downstream-implementations-c-sealed pub trait ValidBindingSignature<'a, C: 'a>: ValidAmalgamation<'a, C> + seal::Sealed { /// Maps the given function over binding and direct key signature. /// /// Makes `f` consider both the binding signature and the direct /// key signature. Information in the binding signature takes /// precedence over the direct key signature. See also [Section /// 5.2.3.10 of RFC 9580]. /// /// [Section 5.2.3.10 of RFC 9580]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.10 fn map<F: Fn(&'a Signature) -> Option<T>, T>(&self, f: F) -> Option<T> { f(self.binding_signature()) .or_else(|| self.direct_key_signature().ok().and_then(f)) } } /// A certificate component, its associated data, and useful methods. /// /// [`Cert::userids`], [`ValidCert::primary_userid`], [`Cert::user_attributes`], and /// [`Cert::unknowns`] return `ComponentAmalgamation`s. /// /// `ComponentAmalgamation` implements [`ValidateAmalgamation`], which /// allows you to turn a `ComponentAmalgamation` into a /// [`ValidComponentAmalgamation`] using /// [`ComponentAmalgamation::with_policy`]. /// /// [See the module's documentation] for more details. /// /// # Examples /// /// ``` /// # use sequoia_openpgp as openpgp; /// # use openpgp::cert::prelude::*; /// # use openpgp::policy::StandardPolicy; /// # /// # fn main() -> openpgp::Result<()> { /// # let p = &StandardPolicy::new(); /// # let (cert, _) = /// # CertBuilder::general_purpose(Some("alice@example.org")) /// # .generate()?; /// # let fpr = cert.fingerprint(); /// // Iterate over all User IDs. /// for ua in cert.userids() { /// // ua is a `ComponentAmalgamation`, specifically, a `UserIDAmalgamation`. /// } /// # Ok(()) /// # } /// ``` /// /// [`Cert`]: super::Cert /// [`Cert::userids`]: super::Cert::userids() /// [`ValidCert::primary_userid`]: super::ValidCert::primary_userid() /// [`Cert::user_attributes`]: super::Cert::user_attributes() /// [`Cert::unknowns`]: super::Cert::unknowns() /// [`ComponentAmalgamation::with_policy`]: ValidateAmalgamation::with_policy() /// [See the module's documentation]: self #[derive(Debug, PartialEq)] pub struct ComponentAmalgamation<'a, C> { cert: &'a Cert, bundle: &'a ComponentBundle<C>, } assert_send_and_sync!(ComponentAmalgamation<'_, C> where C); /// A User ID and its associated data. /// /// A specialized version of [`ComponentAmalgamation`]. /// pub type UserIDAmalgamation<'a> = ComponentAmalgamation<'a, UserID>; /// A User Attribute and its associated data. /// /// A specialized version of [`ComponentAmalgamation`]. /// pub type UserAttributeAmalgamation<'a> = ComponentAmalgamation<'a, UserAttribute>; /// An Unknown component and its associated data. /// /// A specialized version of [`ComponentAmalgamation`]. /// pub type UnknownComponentAmalgamation<'a> = ComponentAmalgamation<'a, Unknown>; // derive(Clone) doesn't work with generic parameters that don't // implement clone. But, we don't need to require that C implements // Clone, because we're not cloning C, just the reference. // // See: https://github.com/rust-lang/rust/issues/26925 impl<'a, C> Clone for ComponentAmalgamation<'a, C> { fn clone(&self) -> Self { Self { cert: self.cert, bundle: self.bundle, } } } impl<'a, C> ComponentAmalgamation<'a, C> { /// Creates a new amalgamation. pub(crate) fn new(cert: &'a Cert, bundle: &'a ComponentBundle<C>) -> Self { Self { cert, bundle, } } /// Returns the amalgamations's associated certificate. /// /// ``` /// # use sequoia_openpgp as openpgp; /// # use openpgp::cert::prelude::*; /// # /// # fn main() -> openpgp::Result<()> { /// # let (cert, _) = /// # CertBuilder::general_purpose(Some("alice@example.org")) /// # .generate()?; /// for u in cert.userids() { /// // It's not only an identical `Cert`, it's the same one. /// assert!(std::ptr::eq(u.cert(), &cert)); /// } /// # Ok(()) } /// ``` pub fn cert(&self) -> &'a Cert { self.cert } /// Returns the active binding signature at time `t`. /// /// The active binding signature is the most recent, non-revoked /// self-signature that is valid according to the `policy` and /// alive at time `t` (`creation time <= t`, `t < expiry`). If /// there are multiple such signatures then the signatures are /// ordered by their MPIs interpreted as byte strings. /// /// # Examples /// /// ``` /// # use sequoia_openpgp as openpgp; /// # use openpgp::cert::prelude::*; /// use openpgp::policy::StandardPolicy; /// # /// # fn main() -> openpgp::Result<()> { /// let p = &StandardPolicy::new(); /// /// # let (cert, _) = /// # CertBuilder::general_purpose(Some("alice@example.org")) /// # .generate()?; /// // Display information about each User ID's current active /// // binding signature (the `time` parameter is `None`), if any. /// for ua in cert.userids() { /// eprintln!("{:?}", ua.binding_signature(p, None)); /// } /// # Ok(()) } /// ``` pub fn binding_signature<T>(&self, policy: &dyn Policy, time: T) -> Result<&'a Signature> where T: Into<Option<time::SystemTime>> { let time = time.into().unwrap_or_else(crate::now); self.bundle().binding_signature(policy, time) } /// Returns this amalgamation's bundle. /// /// # Examples /// /// ``` /// # fn main() -> sequoia_openpgp::Result<()> { /// # use sequoia_openpgp as openpgp; /// use openpgp::cert::prelude::*; /// use openpgp::packet::prelude::*; /// /// # let (cert, _) = CertBuilder::new() /// # .add_userid("Alice") /// # .add_signing_subkey() /// # .add_transport_encryption_subkey() /// # .generate()?; /// cert.userids() /// .map(|ua| ua.bundle()) /// .collect::<Vec<&ComponentBundle<_>>>(); /// # Ok(()) } /// ``` pub fn bundle(&self) -> &'a ComponentBundle<C> { self.bundle } /// Returns this amalgamation's component. /// /// # Examples /// /// ``` /// # use sequoia_openpgp as openpgp; /// # use openpgp::cert::prelude::*; /// # /// # fn main() -> openpgp::Result<()> { /// # let (cert, _) = /// # CertBuilder::general_purpose(Some("alice@example.org")) /// # .generate()?; /// // Display some information about any unknown components. /// for u in cert.unknowns() { /// eprintln!(" - {:?}", u.component()); /// } /// # Ok(()) } /// ``` pub fn component(&self) -> &'a C { self.bundle().component() } /// Returns the component's self-signatures. /// /// The signatures are validated, and they are sorted by their /// creation time, most recent first. /// /// # Examples /// /// ``` /// # use sequoia_openpgp as openpgp; /// # use openpgp::cert::prelude::*; /// use openpgp::policy::StandardPolicy; /// # /// # fn main() -> openpgp::Result<()> { /// let p = &StandardPolicy::new(); /// /// # let (cert, _) = /// # CertBuilder::general_purpose(Some("alice@example.org")) /// # .generate()?; /// for (i, ka) in cert.keys().enumerate() { /// eprintln!("Key #{} ({}) has {:?} self signatures", /// i, ka.key().fingerprint(), /// ka.self_signatures().count()); /// } /// # Ok(()) } /// ``` pub fn self_signatures(&self) -> impl Iterator<Item=&'a Signature> + Send + Sync { self.bundle().self_signatures() } /// Returns the component's third-party certifications. /// /// The signatures are *not* validated. They are sorted by their /// creation time, most recent first. /// /// # Examples /// /// ``` /// # use sequoia_openpgp as openpgp; /// # use openpgp::cert::prelude::*; /// use openpgp::policy::StandardPolicy; /// # /// # fn main() -> openpgp::Result<()> { /// let p = &StandardPolicy::new(); /// /// # let (cert, _) = /// # CertBuilder::general_purpose(Some("alice@example.org")) /// # .generate()?; /// for ua in cert.userids() { /// eprintln!("User ID {} has {:?} unverified, third-party certifications", /// String::from_utf8_lossy(ua.userid().value()), /// ua.certifications().count()); /// } /// # Ok(()) } /// ``` pub fn certifications(&self) -> impl Iterator<Item=&'a Signature> + Send + Sync { self.bundle().certifications() } /// Returns third-party certifications that appear to issued by /// any of the specified keys. /// /// A certification is returned if one of the provided key handles /// matches an [Issuer subpacket] or [Issuer Fingerprint /// subpacket] in the certification. /// /// [Issuer subpacket]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.12 /// [Issuer Fingerprint subpacket]: https://www.rfc-editor.org/rfc/rfc9580.html#name-intended-recipient-fingerpr /// /// This function does not check that a certification is valid. /// It can't. To check that a certification was actually issued /// by a specific key, we also need a policy and the public key, /// which we don't have. To only get valid certifications, use /// [`UserIDAmalgamation::valid_certifications_by_key`] or /// [`UserIDAmalgamation::active_certifications_by_key`] instead /// of this function. pub fn certifications_by_key<'b>(&'b self, issuers: &'b [ KeyHandle ]) -> impl Iterator<Item=&'a Signature> + Send + Sync + 'b { self.certifications().filter(|certification| { certification.get_issuers().into_iter().any(|certification_issuer| { issuers.iter().any(|issuer| { certification_issuer.aliases(issuer) }) }) }) } /// Returns the component's revocations that were issued by the /// certificate holder. /// /// The revocations are validated, and they are sorted by their /// creation time, most recent first. /// /// # Examples /// /// ``` /// # use sequoia_openpgp as openpgp; /// # use openpgp::cert::prelude::*; /// use openpgp::policy::StandardPolicy; /// # /// # fn main() -> openpgp::Result<()> { /// let p = &StandardPolicy::new(); /// /// # let (cert, _) = /// # CertBuilder::general_purpose(Some("alice@example.org")) /// # .generate()?; /// for u in cert.userids() { /// eprintln!("User ID {} has {:?} revocation certificates.", /// String::from_utf8_lossy(u.userid().value()), /// u.self_revocations().count()); /// } /// # Ok(()) } /// ``` pub fn self_revocations(&self) -> impl Iterator<Item=&'a Signature> + Send + Sync { self.bundle().self_revocations() } /// Returns the component's revocations that were issued by other /// certificates. /// /// The revocations are *not* validated. They are sorted by their /// creation time, most recent first. /// /// # Examples /// /// ``` /// # use sequoia_openpgp as openpgp; /// # use openpgp::cert::prelude::*; /// use openpgp::policy::StandardPolicy; /// # /// # fn main() -> openpgp::Result<()> { /// let p = &StandardPolicy::new(); /// /// # let (cert, _) = /// # CertBuilder::general_purpose(Some("alice@example.org")) /// # .generate()?; /// for u in cert.userids() { /// eprintln!("User ID {} has {:?} unverified, third-party revocation certificates.", /// String::from_utf8_lossy(u.userid().value()), /// u.other_revocations().count()); /// } /// # Ok(()) } /// ``` pub fn other_revocations(&self) -> impl Iterator<Item=&'a Signature> + Send + Sync { self.bundle().other_revocations() } /// Returns all the component's Certification Approval Key /// Signatures. /// /// This feature is [experimental](crate#experimental-features). /// /// The signatures are validated, and they are sorted by their /// creation time, most recent first. /// /// A certificate owner can use Certification Approval Key /// Signatures to approve of third party certifications. /// Currently, only userid and user attribute certifications can /// be approved of. See [Approved Certifications subpacket] for /// details. /// /// [Approved Certifications subpacket]: https://www.ietf.org/archive/id/draft-dkg-openpgp-1pa3pc-02.html#approved-certifications-subpacket /// /// # Examples /// /// ``` /// # use sequoia_openpgp as openpgp; /// # fn main() -> openpgp::Result<()> { /// # use openpgp::cert::prelude::*; /// use openpgp::policy::StandardPolicy; /// let p = &StandardPolicy::new(); /// /// # let (cert, _) = /// # CertBuilder::general_purpose(Some("alice@example.org")) /// # .generate()?; /// for (i, uid) in cert.userids().enumerate() { /// eprintln!("UserID #{} ({:?}) has {:?} certification approval key signatures", /// i, uid.userid().email(), /// uid.approvals().count()); /// } /// # Ok(()) } /// ``` pub fn approvals(&self) -> impl Iterator<Item=&'a Signature> + Send + Sync + 'a { self.bundle().approvals() } /// Returns all the component's signatures. /// /// Only the self-signatures are validated. The signatures are /// sorted first by type, then by creation time. The self /// revocations come first, then the self signatures, /// then any certification approval key signatures, /// certifications, and third-party revocations coming last. This /// function may return additional types of signatures that could /// be associated to this component. /// /// # Examples /// /// ``` /// # use sequoia_openpgp as openpgp; /// # use openpgp::cert::prelude::*; /// use openpgp::policy::StandardPolicy; /// # /// # fn main() -> openpgp::Result<()> { /// let p = &StandardPolicy::new(); /// /// # let (cert, _) = /// # CertBuilder::general_purpose(Some("alice@example.org")) /// # .generate()?; /// for (i, ka) in cert.keys().enumerate() { /// eprintln!("Key #{} ({}) has {:?} signatures", /// i, ka.key().fingerprint(), /// ka.signatures().count()); /// } /// # Ok(()) } /// ``` pub fn signatures(&self) -> impl Iterator<Item = &'a Signature> + Send + Sync { self.bundle().signatures() } // Used to implement // [`UserIDAmalgamation::valid_certifications_by_key`], // [`KeyAmalgamation::valid_certifications_by_key`], // [`UserIDAmalgamation::active_certifications_by_key`], and // [`KeyAmalgamation::active_certifications_by_key`]. fn valid_certifications_by_key_<'b, F>( &self, policy: &'a dyn Policy, reference_time: Option<time::SystemTime>, issuer: &'a packet::Key<packet::key::PublicParts, packet::key::UnspecifiedRole>, only_active: bool, certifications: impl Iterator<Item=&'b Signature> + Send + Sync, verify_certification: F) -> impl Iterator<Item=&'b Signature> + Send + Sync where F: Fn(&Signature) -> Result<()> { let reference_time = reference_time.unwrap_or_else(crate::now); let issuer_handle = issuer.key_handle(); let issuer_handle = &issuer_handle; let mut certifications: Vec<(&Signature, _)> = certifications .filter_map(|certification| { // Extract the signature's creation time. Ignore // certifications without a creation time: those are // malformed. certification .signature_creation_time() .map(|ct| (certification, ct)) }) .filter(|(certification, _ct)| { // Filter out certifications that definitely aren't // from `issuer`. certification.get_issuers().into_iter().any(|sig_issuer| { sig_issuer.aliases(issuer_handle) }) }) .map(|(certification, ct)| { let hard = if matches!(certification.typ(), SignatureType::KeyRevocation | SignatureType::SubkeyRevocation | SignatureType::CertificationRevocation) { certification.reason_for_revocation() .map(|(reason, _text)| { reason.revocation_type() == RevocationType::Hard }) // Interpret an unspecified reason as a hard // revocation. .unwrap_or(true) } else { false }; (certification, ct, hard) }) .filter(|(_certification, ct, hard)| { // Skip certifications created after the reference // time, unless they are hard revocations. *ct <= reference_time || *hard }) .filter(|(certification, ct, hard)| { // Check that the certification is not expired as of // the reference time. if *hard { // Hard revocations don't expire. true } else if let Some(validity) = certification.signature_validity_period() { if validity == Duration::new(0, 0) { // "If this is not present or has a value of // zero, it never expires." // // https://www.rfc-editor.org/rfc/rfc9580.html#name-key-expiration-time true } else { // "the number of seconds after the signature // creation time that the signature expires" // // Assume validity = 1 second, then: // // expiry time reference time status // ----------- -------------- ------ // > ct live // ct + 1 = ct + 1 expired // < ct + 2 expired *ct + validity > reference_time } } else { true } }) .filter(|(_certification, ct, hard)| { // Make sure the certification was created after the // certificate, unless they are hard revocations. self.cert.primary_key().key().creation_time() <= *ct || *hard }) .filter(|(certification, _ct, _hard)| { // Make sure the certification conforms to the policy. policy .signature(certification, HashAlgoSecurity::CollisionResistance) .is_ok() }) .map(|(certification, ct, _hard)| (certification, ct)) .collect(); // Sort the certifications by creation time so that the newest // certifications come first. certifications.sort_unstable_by(|(_, a), (_, b)| { a.cmp(b).reverse() }); // Check that the issuer actually made the signatures, and // collect the most recent certifications. let mut valid = Vec::new(); for (certification, ct) in certifications.into_iter() { if only_active { if let Some((_active, active_ct)) = valid.get(0) { if *active_ct != ct { // This certification is further in the past. // We're done. break; } } } if let Ok(()) = verify_certification(certification) { valid.push((certification, ct)); } } valid.into_iter() .map(|(certification, _creation_time)| certification) .collect::<Vec<&Signature>>() .into_iter() } } macro_rules! impl_with_policy { ($func:ident, $value:ident $(, $arg:ident: $type:ty )*) => { fn $func<T>(&self, policy: &'a dyn Policy, time: T, $($arg: $type, )*) -> Result<Self::V> where T: Into<Option<time::SystemTime>>, Self: Sized { let time = time.into().unwrap_or_else(crate::now); if $value { self.cert.with_policy(policy, time)?; } let binding_signature = self.binding_signature(policy, time)?; let cert = self.cert; // We can't do `Cert::with_policy` as that would // result in infinite recursion. But at this point, // we know the certificate is valid (unless the caller // doesn't care). Ok(ValidComponentAmalgamation { ca: self.clone(), cert: ValidCert { cert, policy, time, }, binding_signature, }) } } } impl<'a, C> seal::Sealed for ComponentAmalgamation<'a, C> {} impl<'a, C> ValidateAmalgamation<'a, C> for ComponentAmalgamation<'a, C> { type V = ValidComponentAmalgamation<'a, C>; impl_with_policy!(with_policy, true); } impl<'a, C> ValidateAmalgamationRelaxed<'a, C> for ComponentAmalgamation<'a, C> { type V = ValidComponentAmalgamation<'a, C>; impl_with_policy!(with_policy_relaxed, valid_cert, valid_cert: bool); } impl<'a> UserIDAmalgamation<'a> { /// Returns a reference to the User ID. /// /// This is just a type-specific alias for /// [`ComponentAmalgamation::component`]. /// /// # Examples /// /// ``` /// # use sequoia_openpgp as openpgp; /// # use openpgp::cert::prelude::*; /// # /// # fn main() -> openpgp::Result<()> { /// # let (cert, _) = /// # CertBuilder::general_purpose(Some("alice@example.org")) /// # .generate()?; /// // Display some information about the User IDs. /// for ua in cert.userids() { /// eprintln!(" - {:?}", ua.userid()); /// } /// # Ok(()) } /// ``` pub fn userid(&self) -> &'a UserID { self.component() } /// Returns the User ID's revocation status at time `t`.<a /// name="userid_revocation_status"></a> /// /// <!-- Why we have the above anchor: /// https://github.com/rust-lang/rust/issues/71912 --> /// /// A User ID is revoked at time `t` if: /// /// - There is a live revocation at time `t` that is newer than /// all live self signatures at time `t`. /// /// Note: Certs and subkeys have different criteria from User IDs /// and User Attributes. /// /// Note: this only returns whether this User ID is revoked; it /// does not imply anything about the Cert or other components. // /// # Examples /// /// ``` /// # use sequoia_openpgp as openpgp; /// # use openpgp::cert::prelude::*; /// use openpgp::policy::StandardPolicy; /// # /// # fn main() -> openpgp::Result<()> { /// let p = &StandardPolicy::new(); /// /// # let (cert, _) = /// # CertBuilder::general_purpose(Some("alice@example.org")) /// # .generate()?; /// // Display the User IDs' revocation status. /// for ua in cert.userids() { /// eprintln!(" Revocation status of {}: {:?}", /// String::from_utf8_lossy(ua.userid().value()), /// ua.revocation_status(p, None)); /// } /// # Ok(()) } /// ``` pub fn revocation_status<T>(&self, policy: &dyn Policy, t: T) -> RevocationStatus where T: Into<Option<time::SystemTime>>, { let t = t.into(); self.bundle().revocation_status(policy, t) } /// Returns the third-party certifications issued by the specified /// key, and valid at the specified time. /// /// This function returns the certifications issued by the /// specified key. Specifically, it returns a certification if: /// /// - it is well-formed, /// - it is live with respect to the reference time, /// - it conforms to the policy, and /// - the signature is cryptographically valid. /// /// This method is implemented on a [`UserIDAmalgamation`] and not /// a [`ValidUserIDAmalgamation`], because a third-party /// certification does not require the user ID to be self-signed. /// /// # Examples /// /// Alice has certified that a certificate belongs to Bob on two /// occasions. Whereas /// [`UserIDAmalgamation::valid_certifications_by_key`] returns /// both certifications, /// [`UserIDAmalgamation::active_certifications_by_key`] only /// returns the most recent certification. /// /// ```rust /// use sequoia_openpgp as openpgp; /// use openpgp::cert::prelude::*; /// # use openpgp::packet::signature::SignatureBuilder; /// # use openpgp::packet::UserID; /// use openpgp::policy::StandardPolicy; /// # use openpgp::types::SignatureType; /// /// const P: &StandardPolicy = &StandardPolicy::new(); /// /// # fn main() -> openpgp::Result<()> { /// # let epoch = std::time::SystemTime::now() /// # - std::time::Duration::new(100, 0); /// # let t0 = epoch; /// # /// # let (alice, _) = CertBuilder::new() /// # .set_creation_time(t0) /// # .add_userid("<alice@example.org>") /// # .generate() /// # .unwrap(); /// let alice: Cert = // ... /// # alice; /// # /// # let bob_userid = "<bob@example.org>"; /// # let (bob, _) = CertBuilder::new() /// # .set_creation_time(t0) /// # .add_userid(bob_userid) /// # .generate() /// # .unwrap(); /// let bob: Cert = // ... /// # bob; /// /// # // Alice has not certified Bob's User ID. /// # let ua = bob.userids().next().expect("have a user id"); /// # assert_eq!( /// # ua.active_certifications_by_key( /// # P, t0, alice.primary_key().key()).count(), /// # 0); /// # /// # // Have Alice certify Bob's certificate. /// # let mut alice_signer = alice /// # .keys() /// # .with_policy(P, None) /// # .for_certification() /// # .next().expect("have a certification-capable key") /// # .key() /// # .clone() /// # .parts_into_secret().expect("have unencrypted key material") /// # .into_keypair().expect("have unencrypted key material"); /// # /// # let mut bob = bob; /// # for i in 1..=2usize { /// # let ti = t0 + std::time::Duration::new(i as u64, 0); /// # /// # let certification = SignatureBuilder::new(SignatureType::GenericCertification) /// # .set_signature_creation_time(ti)? /// # .sign_userid_binding( /// # &mut alice_signer, /// # bob.primary_key().key(), /// # &UserID::from(bob_userid))?; /// # bob = bob.insert_packets(certification)?.0; /// # /// # let ua = bob.userids().next().expect("have a user id"); /// # assert_eq!( /// # ua.valid_certifications_by_key( /// # P, ti, alice.primary_key().key()).count(), /// # i); /// # /// # assert_eq!( /// # ua.active_certifications_by_key( /// # P, ti, alice.primary_key().key()).count(), /// # 1); /// # } /// let ua = bob.userids().next().expect("have user id"); /// /// let valid_certifications = ua.valid_certifications_by_key( /// P, None, alice.primary_key().key()); /// // Alice certified Bob's certificate twice. /// assert_eq!(valid_certifications.count(), 2); /// /// let active_certifications = ua.active_certifications_by_key( /// P, None, alice.primary_key().key()); /// // But only the most recent one is active. /// assert_eq!(active_certifications.count(), 1); /// # Ok(()) } /// ``` pub fn valid_certifications_by_key<T, PK>(&self, policy: &'a dyn Policy, reference_time: T, issuer: PK) -> impl Iterator<Item=&Signature> + Send + Sync where T: Into<Option<time::SystemTime>>, PK: Into<&'a packet::Key<packet::key::PublicParts, packet::key::UnspecifiedRole>>, { let reference_time = reference_time.into(); let issuer = issuer.into(); self.valid_certifications_by_key_( policy, reference_time, issuer, false, self.certifications(), |sig| { sig.clone().verify_userid_binding( issuer, self.cert.primary_key().key(), self.userid()) }) } /// Returns any active third-party certifications issued by the /// specified key. /// /// This function is like /// [`UserIDAmalgamation::valid_certifications_by_key`], but it /// only returns active certifications. Active certifications are /// the most recent valid certifications with respect to the /// reference time. /// /// Although there is normally only a single active certification, /// there can be multiple certifications with the same timestamp. /// In this case, all of them are returned. /// /// Unlike self-signatures, multiple third-party certifications /// issued by the same key at the same time can be sensible. For /// instance, Alice may fully trust a CA for user IDs in a /// particular domain, and partially trust it for everything else. /// This can only be expressed using multiple certifications. /// /// This method is implemented on a [`UserIDAmalgamation`] and not /// a [`ValidUserIDAmalgamation`], because a third-party /// certification does not require the user ID to be self-signed. /// /// # Examples /// /// See the examples for /// [`UserIDAmalgamation::valid_certifications_by_key`]. pub fn active_certifications_by_key<T, PK>(&self, policy: &'a dyn Policy, reference_time: T, issuer: PK) -> impl Iterator<Item=&Signature> + Send + Sync where T: Into<Option<time::SystemTime>>, PK: Into<&'a packet::Key<packet::key::PublicParts, packet::key::UnspecifiedRole>>, { let reference_time = reference_time.into(); let issuer = issuer.into(); self.valid_certifications_by_key_( policy, reference_time, issuer, true, self.certifications(), |sig| { sig.clone().verify_userid_binding( issuer, self.cert.primary_key().key(), self.userid()) }) } /// Returns the third-party revocations issued by the specified /// key, and valid at the specified time. /// /// This function returns the revocations issued by the specified /// key. Specifically, it returns a revocation if: /// /// - it is well-formed, /// - it is live with respect to the reference time, /// - it conforms to the policy, and /// - the signature is cryptographically valid. /// /// This method is implemented on a [`UserIDAmalgamation`] and not /// a [`ValidUserIDAmalgamation`], because a third-party /// revocation does not require the user ID to be self-signed. /// /// # Examples /// /// Alice revokes a user ID on Bob's certificate. /// /// ```rust /// use sequoia_openpgp as openpgp; /// use openpgp::cert::prelude::*; /// # use openpgp::Packet; /// # use openpgp::packet::signature::SignatureBuilder; /// # use openpgp::packet::UserID; /// use openpgp::policy::StandardPolicy; /// # use openpgp::types::ReasonForRevocation; /// # use openpgp::types::SignatureType; /// /// const P: &StandardPolicy = &StandardPolicy::new(); /// /// # fn main() -> openpgp::Result<()> { /// # let epoch = std::time::SystemTime::now() /// # - std::time::Duration::new(100, 0); /// # let t0 = epoch; /// # let t1 = epoch + std::time::Duration::new(1, 0); /// # /// # let (alice, _) = CertBuilder::new() /// # .set_creation_time(t0) /// # .add_userid("<alice@example.org>") /// # .generate() /// # .unwrap(); /// let alice: Cert = // ... /// # alice; /// # /// # let bob_userid = "<bob@example.org>"; /// # let (bob, _) = CertBuilder::new() /// # .set_creation_time(t0) /// # .add_userid(bob_userid) /// # .generate() /// # .unwrap(); /// let bob: Cert = // ... /// # bob; /// /// # // Alice has not certified Bob's User ID. /// # let ua = bob.userids().next().expect("have a user id"); /// # assert_eq!( /// # ua.active_certifications_by_key( /// # P, t0, alice.primary_key().key()).count(), /// # 0); /// # /// # // Have Alice certify Bob's certificate. /// # let mut alice_signer = alice /// # .keys() /// # .with_policy(P, None) /// # .for_certification() /// # .next().expect("have a certification-capable key") /// # .key() /// # .clone() /// # .parts_into_secret().expect("have unencrypted key material") /// # .into_keypair().expect("have unencrypted key material"); /// # /// # let certification = SignatureBuilder::new(SignatureType::CertificationRevocation) /// # .set_signature_creation_time(t1)? /// # .set_reason_for_revocation( /// # ReasonForRevocation::UIDRetired, b"")? /// # .sign_userid_binding( /// # &mut alice_signer, /// # bob.primary_key().key(), /// # &UserID::from(bob_userid))?; /// # let bob = bob.insert_packets([ /// # Packet::from(UserID::from(bob_userid)), /// # Packet::from(certification), /// # ])?.0; /// let ua = bob.userids().next().expect("have user id"); /// /// let revs = ua.valid_third_party_revocations_by_key( /// P, None, alice.primary_key().key()); /// // Alice revoked the User ID. /// assert_eq!(revs.count(), 1); /// # Ok(()) } /// ``` pub fn valid_third_party_revocations_by_key<T, PK>(&self, policy: &'a dyn Policy, reference_time: T, issuer: PK) -> impl Iterator<Item=&Signature> + Send + Sync where T: Into<Option<time::SystemTime>>, PK: Into<&'a packet::Key<packet::key::PublicParts, packet::key::UnspecifiedRole>>, { let reference_time = reference_time.into(); let issuer = issuer.into(); self.valid_certifications_by_key_( policy, reference_time, issuer, false, self.other_revocations(), |sig| { sig.clone().verify_userid_revocation( issuer, self.cert.primary_key().key(), self.userid()) }) } /// Approves of third-party certifications. /// /// This feature is [experimental](crate#experimental-features). /// /// Allows the certificate owner to approve of third party /// certifications. See [Approved Certifications subpacket] for /// details. This can be used to address certificate flooding /// concerns. /// /// [Approved Certifications subpacket]: https://www.ietf.org/archive/id/draft-dkg-openpgp-1pa3pc-02.html#approved-certifications-subpacket /// /// A policy is needed, because the expiration is updated by /// updating the current binding signatures. /// /// # Examples /// /// ``` /// # use sequoia_openpgp as openpgp; /// # fn main() -> openpgp::Result<()> { /// # use openpgp::cert::prelude::*; /// # use openpgp::packet::signature::SignatureBuilder; /// # use openpgp::types::*; /// # let policy = &openpgp::policy::StandardPolicy::new(); /// let (alice, _) = CertBuilder::new() /// .add_userid("alice@example.org") /// .generate()?; /// let mut alice_signer = /// alice.primary_key().key().clone().parts_into_secret()? /// .into_keypair()?; /// /// let (bob, _) = CertBuilder::new() /// .add_userid("bob@example.org") /// .generate()?; /// let mut bob_signer = /// bob.primary_key().key().clone().parts_into_secret()? /// .into_keypair()?; /// let bob_pristine = bob.clone(); /// /// // Have Alice certify the binding between "bob@example.org" and /// // Bob's key. /// let alice_certifies_bob /// = bob.userids().next().unwrap().userid().bind( /// &mut alice_signer, &bob, /// SignatureBuilder::new(SignatureType::GenericCertification))?; /// let bob = bob.insert_packets(vec![alice_certifies_bob.clone()])?.0; /// /// // Have Bob approve of that certification. /// let bobs_uid = bob.userids().next().unwrap(); /// let approvals = /// bobs_uid.approve_of_certifications( /// policy, /// None, /// &mut bob_signer, /// bobs_uid.certifications())?; /// let bob = bob.insert_packets(approvals)?.0; /// /// assert_eq!(bob.bad_signatures().count(), 0); /// assert_eq!(bob.userids().next().unwrap().certifications().next(), /// Some(&alice_certifies_bob)); /// # Ok(()) } /// ``` pub fn approve_of_certifications<T, C, S>(&self, policy: &dyn Policy, time: T, primary_signer: &mut dyn Signer, certifications: C) -> Result<Vec<Signature>> where T: Into<Option<time::SystemTime>>, C: IntoIterator<Item = S>, S: Borrow<Signature>, { let time = time.into(); let certifications = certifications.into_iter() .collect::<Vec<_>>(); // Check if there is a previous attestation. If so, we need // that to robustly override it. let old = self.clone() .with_policy(policy, time) .ok() .and_then( |v| v.certification_approval_key_signatures().next().cloned()); approve_of_certifications_common(self.cert().primary_key().key(), self.userid(), old, time, primary_signer, &certifications) } } impl<'a> UserAttributeAmalgamation<'a> { /// Returns a reference to the User Attribute. /// /// This is just a type-specific alias for /// [`ComponentAmalgamation::component`]. /// /// # Examples /// /// ``` /// # use sequoia_openpgp as openpgp; /// # use openpgp::cert::prelude::*; /// # /// # fn main() -> openpgp::Result<()> { /// # let (cert, _) = /// # CertBuilder::general_purpose(Some("alice@example.org")) /// # .generate()?; /// // Display some information about the User Attributes /// for ua in cert.user_attributes() { /// eprintln!(" - {:?}", ua.user_attribute()); /// } /// # Ok(()) } /// ``` pub fn user_attribute(&self) -> &'a UserAttribute { self.component() } /// Returns the User Attribute's revocation status at time `t`. /// /// A User Attribute is revoked at time `t` if: /// /// - There is a live revocation at time `t` that is newer than /// all live self signatures at time `t`. /// /// Note: Certs and subkeys have different criteria from User IDs /// and User Attributes. /// /// Note: this only returns whether this User Attribute is revoked; /// it does not imply anything about the Cert or other components. // /// # Examples /// /// ``` /// # use sequoia_openpgp as openpgp; /// # use openpgp::cert::prelude::*; /// use openpgp::policy::StandardPolicy; /// # /// # fn main() -> openpgp::Result<()> { /// let p = &StandardPolicy::new(); /// /// # let (cert, _) = /// # CertBuilder::general_purpose(Some("alice@example.org")) /// # .generate()?; /// // Display the User Attributes' revocation status. /// for (i, ua) in cert.user_attributes().enumerate() { /// eprintln!(" Revocation status of User Attribute #{}: {:?}", /// i, ua.revocation_status(p, None)); /// } /// # Ok(()) } /// ``` pub fn revocation_status<T>(&self, policy: &dyn Policy, t: T) -> RevocationStatus where T: Into<Option<time::SystemTime>>, { let t = t.into(); self.bundle().revocation_status(policy, t) } /// Approves of third-party certifications. /// /// This feature is [experimental](crate#experimental-features). /// /// Allows the certificate owner to approve of third party /// certifications. See [Approved Certifications subpacket] for /// details. This can be used to address certificate flooding /// concerns. /// /// A policy is needed, because the expiration is updated by /// updating the current binding signatures. /// /// [Approved Certifications subpacket]: https://www.ietf.org/archive/id/draft-dkg-openpgp-1pa3pc-02.html#approved-certifications-subpacket /// /// # Examples /// /// See [`UserIDAmalgamation::approve_of_certifications#examples`]. /// /// [`UserIDAmalgamation::approve_of_certifications#examples`]: UserIDAmalgamation#examples // The explicit link works around a bug in rustdoc. pub fn approve_of_certifications<T, C, S>(&self, policy: &dyn Policy, time: T, primary_signer: &mut dyn Signer, certifications: C) -> Result<Vec<Signature>> where T: Into<Option<time::SystemTime>>, C: IntoIterator<Item = S>, S: Borrow<Signature>, { let time = time.into(); let certifications = certifications.into_iter() .collect::<Vec<_>>(); // Check if there is a previous attestation. If so, we need // that to robustly override it. let old = self.clone() .with_policy(policy, time) .ok() .and_then( |v| v.certification_approval_key_signatures().next().cloned()); approve_of_certifications_common(self.cert().primary_key().key(), self.user_attribute(), old, time, primary_signer, &certifications) } } /// Approves of third-party certifications. fn approve_of_certifications_common<S>(key: &Key<PublicParts, PrimaryRole>, component: &dyn Hash, old_attestation: Option<Signature>, time: Option<SystemTime>, primary_signer: &mut dyn Signer, certifications: &[S]) -> Result<Vec<Signature>> where S: Borrow<Signature>, { use crate::{ packet::signature::{SignatureBuilder, subpacket::SubpacketArea}, serialize::MarshalInto, }; // Fix the time. let now = time.unwrap_or_else(crate::now); // Fix the algorithm. let hash_algo = HashAlgorithm::default(); let digest_size = hash_algo.digest_size()?; let mut attestations = Vec::new(); for certification in certifications { let mut h = hash_algo.context()? .for_signature(primary_signer.public().version()); certification.borrow().hash_for_confirmation(&mut h)?; attestations.push(h.into_digest()?); } // Hashes SHOULD be sorted. attestations.sort(); // All attestation signatures we generate for this component // should have the same creation time. Fix it now. We also like // our signatures to be newer than any existing signatures. Do so // by using the old attestation as template. let template = if let Some(old) = old_attestation { let mut s = SignatureBuilder::from(old) .set_reference_time(now)?; s.hashed_area_mut().clear(); s.unhashed_area_mut().clear(); s } else { // Backdate the signature a little so that we can immediately // override it. use crate::packet::signature::SIG_BACKDATE_BY; let mut creation_time = now - time::Duration::new(SIG_BACKDATE_BY, 0); // ... but don't backdate it further than the key's creation // time, which would make it invalid. let key_creation_time = primary_signer.public().creation_time(); if creation_time < key_creation_time { // ... unless that would make it is later than now. creation_time = key_creation_time.min(now); } let template = SignatureBuilder::new(SignatureType::CertificationApproval) .set_signature_creation_time(creation_time)?; template }; let template = template .set_hash_algo(hash_algo); // Compute the available space in the hashed area. For this, // it is important that template.pre_sign has been called. let available_space = { // But, we do it on a clone, so that `template` is still not // initialized. let t = template.clone().pre_sign(primary_signer)?; SubpacketArea::MAX_SIZE - t.hashed_area().serialized_len() }; // Reserve space for the subpacket header, length and tag. const SUBPACKET_HEADER_MAX_LEN: usize = 5 + 1; // Compute the chunk size for each signature. let digests_per_sig = (available_space - SUBPACKET_HEADER_MAX_LEN) / digest_size; // Now create the signatures. let mut sigs = Vec::new(); for digests in attestations.chunks(digests_per_sig) { // Hash the components. First, initialize the salt. let t = template.clone().pre_sign(primary_signer)?; let mut hash = hash_algo.context()? .for_signature(primary_signer.public().version()); if let Some(salt) = t.sb_version.salt() { hash.update(salt); } key.hash(&mut hash)?; component.hash(&mut hash)?; sigs.push(t .set_approved_certifications(digests)? .sign_hash(primary_signer, hash)?); } if attestations.is_empty() { // The certificate owner can withdraw attestations by issuing // an empty attestation key signature. assert!(sigs.is_empty()); // Hash the components. First, initialize the salt. let t = template.clone().pre_sign(primary_signer)?; let mut hash = hash_algo.context()? .for_signature(primary_signer.public().version()); if let Some(salt) = t.sb_version.salt() { hash.update(salt); } key.hash(&mut hash)?; component.hash(&mut hash)?; sigs.push(t .set_approved_certifications(Option::<&[u8]>::None)? .sign_hash(primary_signer, hash.clone())?); } Ok(sigs) } impl<'a> UnknownComponentAmalgamation<'a> { /// Returns a reference to the Unknown packet. /// /// This is just a type-specific alias for /// [`ComponentAmalgamation::component`]. /// /// # Examples /// /// ``` /// # use sequoia_openpgp as openpgp; /// # use openpgp::cert::prelude::*; /// # /// # fn main() -> openpgp::Result<()> { /// # let (cert, _) = /// # CertBuilder::general_purpose(Some("alice@example.org")) /// # .generate()?; /// // Display some information about the Unknown components. /// for u in cert.unknowns() { /// eprintln!(" - {:?}", u.unknown()); /// } /// # Ok(()) } /// ``` pub fn unknown(&self) -> &'a Unknown { self.component() } } /// A `ComponentAmalgamation` plus a `Policy` and a reference time. /// /// A `ValidComponentAmalgamation` combines a /// [`ComponentAmalgamation`] with a [`Policy`] and a reference time. /// This allows it to implement the [`ValidAmalgamation`] trait, which /// provides methods that require a [`Policy`] and a reference time. /// Although `ComponentAmalgamation` could implement these methods by /// requiring that the caller explicitly pass them in, embedding them /// in the `ValidComponentAmalgamation` helps ensure that multipart /// operations, even those that span multiple functions, use the same /// `Policy` and reference time. /// /// A `ValidComponentAmalgamation` is typically obtained by /// transforming a `ComponentAmalgamation` using /// [`ValidateAmalgamation::with_policy`]. A /// [`ComponentAmalgamationIter`] can also be changed to yield /// `ValidComponentAmalgamation`s. /// /// A `ValidComponentAmalgamation` is guaranteed to come from a valid /// certificate, and have a valid and live binding signature at the /// specified reference time. Note: this only means that the binding /// signatures are live; it says nothing about whether the /// *certificate* is live. If you care about that, then you need to /// check it separately. /// /// # Examples /// /// Print out information about all non-revoked User IDs. /// /// ``` /// # use sequoia_openpgp as openpgp; /// use openpgp::cert::prelude::*; /// use openpgp::packet::prelude::*; /// use openpgp::policy::StandardPolicy; /// use openpgp::types::RevocationStatus; /// /// # fn main() -> openpgp::Result<()> { /// let p = &StandardPolicy::new(); /// # let (cert, _) = CertBuilder::new() /// # .add_userid("Alice") /// # .add_signing_subkey() /// # .add_transport_encryption_subkey() /// # .generate()?; /// for u in cert.userids() { /// // Create a `ValidComponentAmalgamation`. This may fail if /// // there are no binding signatures that are accepted by the /// // policy and that are live right now. /// let u = u.with_policy(p, None)?; /// /// // Before using the User ID, we still need to check that it is /// // not revoked; `ComponentAmalgamation::with_policy` ensures /// // that there is a valid *binding signature*, not that the /// // `ComponentAmalgamation` is valid. /// // /// // Note: `ValidComponentAmalgamation::revocation_status` and /// // `Preferences::preferred_symmetric_algorithms` use the /// // embedded policy and timestamp. Even though we used `None` for /// // the timestamp (i.e., now), they are guaranteed to use the same /// // timestamp, because `with_policy` eagerly transforms it into /// // the current time. /// // /// // Note: we only check whether the User ID is not revoked. If /// // we were using a key, we'd also want to check that it is alive. /// // (Keys can expire, but User IDs cannot.) /// if let RevocationStatus::Revoked(_revs) = u.revocation_status() { /// // Revoked by the key owner. (If we care about /// // designated revokers, then we need to check those /// // ourselves.) /// } else { /// // Print information about the User ID. /// eprintln!("{}: preferred symmetric algorithms: {:?}", /// String::from_utf8_lossy(u.userid().value()), /// u.preferred_symmetric_algorithms()); /// } /// } /// # Ok(()) } /// ``` /// /// [`Policy`]: crate::policy::Policy #[derive(Debug)] pub struct ValidComponentAmalgamation<'a, C> { ca: ComponentAmalgamation<'a, C>, cert: ValidCert<'a>, // The binding signature at time `time`. (This is just a cache.) binding_signature: &'a Signature, } assert_send_and_sync!(ValidComponentAmalgamation<'_, C> where C); impl<'a, C> ValidComponentAmalgamation<'a, C> { /// Returns the valid amalgamation's associated certificate. /// /// ``` /// # use sequoia_openpgp as openpgp; /// # use openpgp::cert::prelude::*; /// # /// # fn main() -> openpgp::Result<()> { /// # let (cert, _) = /// # CertBuilder::general_purpose(Some("alice@example.org")) /// # .generate()?; /// for u in cert.userids() { /// // It's not only an identical `Cert`, it's the same one. /// assert!(std::ptr::eq(u.cert(), &cert)); /// } /// # Ok(()) } /// ``` pub fn cert(&self) -> &'a Cert { self.ca.cert() } /// Returns the valid amalgamation's active binding signature. /// /// The active binding signature is the most recent, non-revoked /// self-signature that is valid according to the `policy` and /// alive at time `t` (`creation time <= t`, `t < expiry`). If /// there are multiple such signatures then the signatures are /// ordered by their MPIs interpreted as byte strings. /// /// # Examples /// /// ``` /// # use sequoia_openpgp as openpgp; /// # use openpgp::cert::prelude::*; /// use openpgp::policy::StandardPolicy; /// # /// # fn main() -> openpgp::Result<()> { /// let p = &StandardPolicy::new(); /// /// # let (cert, _) = /// # CertBuilder::general_purpose(Some("alice@example.org")) /// # .generate()?; /// // Display information about each User ID's current active /// // binding signature (the `time` parameter is `None`), if any. /// for ua in cert.with_policy(p, None)?.userids() { /// eprintln!("{:?}", ua.binding_signature()); /// } /// # Ok(()) } /// ``` pub fn binding_signature(&self) -> &'a Signature { self.binding_signature } /// Returns the valid amalgamation's amalgamation. /// /// # Examples /// /// ``` /// # use sequoia_openpgp as openpgp; /// # use openpgp::cert::prelude::*; /// use openpgp::policy::StandardPolicy; /// /// # fn main() -> openpgp::Result<()> { /// let p = &StandardPolicy::new(); /// /// # let (cert, _) = CertBuilder::new() /// # .add_userid("Alice") /// # .add_signing_subkey() /// # .add_transport_encryption_subkey() /// # .generate()?; /// // Get a user ID amalgamation. /// let ua = cert.userids().next().expect("added one"); /// /// // Validate it, yielding a valid component amalgamation. /// let vua = ua.with_policy(p, None)?; /// /// // And here we get the amalgamation back. /// let ua2 = vua.amalgamation(); /// assert_eq!(&ua, ua2); /// # Ok(()) } /// ``` pub fn amalgamation(&self) -> &ComponentAmalgamation<'a, C> { &self.ca } /// Returns this valid amalgamation's bundle. /// /// # Examples /// /// ``` /// # fn main() -> sequoia_openpgp::Result<()> { /// # use sequoia_openpgp as openpgp; /// use openpgp::cert::prelude::*; /// use openpgp::packet::prelude::*; /// let p = &openpgp::policy::StandardPolicy::new(); /// /// # let (cert, _) = CertBuilder::new() /// # .add_userid("Alice") /// # .add_signing_subkey() /// # .add_transport_encryption_subkey() /// # .generate()?; /// cert.with_policy(p, None)?.userids() /// .map(|ua| ua.bundle()) /// .collect::<Vec<&ComponentBundle<_>>>(); /// # Ok(()) } /// ``` pub fn bundle(&self) -> &'a ComponentBundle<C> { self.ca.bundle() } /// Returns this valid amalgamation's component. /// /// # Examples /// /// ``` /// # use sequoia_openpgp as openpgp; /// # use openpgp::cert::prelude::*; /// # fn main() -> openpgp::Result<()> { /// # let (cert, _) = /// # CertBuilder::general_purpose(Some("alice@example.org")) /// # .generate()?; /// let p = &openpgp::policy::StandardPolicy::new(); /// /// // Display some information about any userid components. /// for u in cert.with_policy(p, None)?.userids() { /// eprintln!(" - {:?}", u.component()); /// } /// # Ok(()) } /// ``` pub fn component(&self) -> &'a C { self.bundle().component() } } impl<'a, C> ValidComponentAmalgamation<'a, C> where C: Send + Sync, { /// Returns the valid amalgamation's self-signatures. /// /// The signatures are validated, and they are sorted by their /// creation time, most recent first. This method only returns /// signatures that are valid under the current policy. /// /// # Examples /// /// ``` /// # use sequoia_openpgp as openpgp; /// # use openpgp::cert::prelude::*; /// use openpgp::policy::StandardPolicy; /// # /// # fn main() -> openpgp::Result<()> { /// let p = &StandardPolicy::new(); /// /// # let (cert, _) = /// # CertBuilder::general_purpose(Some("alice@example.org")) /// # .generate()?; /// for (i, ka) in cert.with_policy(p, None)?.keys().enumerate() { /// eprintln!("Key #{} ({}) has {:?} self signatures", /// i, ka.key().fingerprint(), /// ka.self_signatures().count()); /// } /// # Ok(()) } /// ``` pub fn self_signatures(&self) -> impl Iterator<Item=&'a Signature> + Send + Sync + 'a { let policy = self.cert.policy(); let has = self.ca.bundle().hash_algo_security; self.ca.self_signatures() .filter(move |sig| policy.signature(sig, has).is_ok()) } /// Returns the component's third-party certifications. /// /// The signatures are *not* validated. They are sorted by their /// creation time, most recent first. This method only returns /// signatures that are valid under the current policy. /// /// # Examples /// /// ``` /// # use sequoia_openpgp as openpgp; /// # use openpgp::cert::prelude::*; /// use openpgp::policy::StandardPolicy; /// # /// # fn main() -> openpgp::Result<()> { /// let p = &StandardPolicy::new(); /// /// # let (cert, _) = /// # CertBuilder::general_purpose(Some("alice@example.org")) /// # .generate()?; /// for ua in cert.with_policy(p, None)?.userids() { /// eprintln!("User ID {} has {:?} unverified, third-party certifications", /// String::from_utf8_lossy(ua.userid().value()), /// ua.certifications().count()); /// } /// # Ok(()) } /// ``` pub fn certifications(&self) -> impl Iterator<Item=&'a Signature> + Send + Sync + 'a { let policy = self.cert.policy(); self.ca.certifications() .filter(move |sig| policy.signature(sig, HashAlgoSecurity::CollisionResistance).is_ok()) } /// Returns the valid amalgamation's revocations that were issued /// by the certificate holder. /// /// The revocations are validated, and they are sorted by their /// creation time, most recent first. This method only returns /// signatures that are valid under the current policy. /// /// # Examples /// /// ``` /// # use sequoia_openpgp as openpgp; /// # use openpgp::cert::prelude::*; /// use openpgp::policy::StandardPolicy; /// # /// # fn main() -> openpgp::Result<()> { /// let p = &StandardPolicy::new(); /// /// # let (cert, _) = /// # CertBuilder::general_purpose(Some("alice@example.org")) /// # .generate()?; /// for u in cert.with_policy(p, None)?.userids() { /// eprintln!("User ID {} has {:?} revocation certificates.", /// String::from_utf8_lossy(u.userid().value()), /// u.self_revocations().count()); /// } /// # Ok(()) } /// ``` pub fn self_revocations(&self) -> impl Iterator<Item=&'a Signature> + Send + Sync + 'a { let policy = self.cert.policy(); let has = self.ca.bundle().hash_algo_security; self.ca.self_revocations() .filter(move |sig| policy.signature(sig, has).is_ok()) } /// Returns the valid amalgamation's revocations that were issued /// by other certificates. /// /// The revocations are *not* validated. They are sorted by their /// creation time, most recent first. This method only returns /// signatures that are valid under the current policy. /// /// # Examples /// /// ``` /// # use sequoia_openpgp as openpgp; /// # use openpgp::cert::prelude::*; /// use openpgp::policy::StandardPolicy; /// # /// # fn main() -> openpgp::Result<()> { /// let p = &StandardPolicy::new(); /// /// # let (cert, _) = /// # CertBuilder::general_purpose(Some("alice@example.org")) /// # .generate()?; /// for u in cert.with_policy(p, None)?.userids() { /// eprintln!("User ID {} has {:?} unverified, third-party revocation certificates.", /// String::from_utf8_lossy(u.userid().value()), /// u.other_revocations().count()); /// } /// # Ok(()) } /// ``` pub fn other_revocations(&self) -> impl Iterator<Item=&'a Signature> + Send + Sync + 'a { let policy = self.cert.policy(); self.ca.other_revocations() .filter(move |sig| policy.signature(sig, HashAlgoSecurity::CollisionResistance).is_ok()) } /// Returns all of the valid amalgamation's Certification Approval /// Key Signatures. /// /// This feature is [experimental](crate#experimental-features). /// /// The signatures are validated, and they are sorted by their /// creation time, most recent first. /// /// A certificate owner can use Certification Approval Key /// Signatures to approve of third party certifications. /// Currently, only userid and user attribute certifications can /// be approved. See [Approved Certifications subpacket] for /// details. /// /// [Approved Certifications subpacket]: https://www.ietf.org/archive/id/draft-dkg-openpgp-1pa3pc-02.html#approved-certifications-subpacket /// /// # Examples /// /// ``` /// # use sequoia_openpgp as openpgp; /// # fn main() -> openpgp::Result<()> { /// # use openpgp::cert::prelude::*; /// use openpgp::policy::StandardPolicy; /// let p = &StandardPolicy::new(); /// /// # let (cert, _) = /// # CertBuilder::general_purpose(Some("alice@example.org")) /// # .generate()?; /// for (i, uid) in cert.with_policy(p, None)?.userids().enumerate() { /// eprintln!("UserID #{} ({:?}) has {:?} certification approval key signatures", /// i, uid.userid().email(), /// uid.approvals().count()); /// } /// # Ok(()) } /// ``` pub fn approvals(&self) -> impl Iterator<Item = &'a Signature> + Send + Sync + 'a { let policy = self.cert.policy(); let has = self.ca.bundle().hash_algo_security; self.ca.approvals() .filter(move |sig| policy.signature(sig, has).is_ok()) } /// Returns all of the valid amalgamations's signatures. /// /// Only the self-signatures are validated. The signatures are /// sorted first by type, then by creation time. The self /// revocations come first, then the self signatures, /// then any certification approval key signatures, /// certifications, and third-party revocations coming last. This /// function may return additional types of signatures that could /// be associated to this component. /// /// This method only returns signatures that are valid under the /// current policy. /// /// # Examples /// /// ``` /// # use sequoia_openpgp as openpgp; /// # use openpgp::cert::prelude::*; /// use openpgp::policy::StandardPolicy; /// # /// # fn main() -> openpgp::Result<()> { /// let p = &StandardPolicy::new(); /// /// # let (cert, _) = /// # CertBuilder::general_purpose(Some("alice@example.org")) /// # .generate()?; /// for (i, ka) in cert.with_policy(p, None)?.keys().enumerate() { /// eprintln!("Key #{} ({}) has {:?} signatures", /// i, ka.key().fingerprint(), /// ka.signatures().count()); /// } /// # Ok(()) } /// ``` pub fn signatures(&self) -> impl Iterator<Item = &'a Signature> + Send + Sync + 'a { let policy = self.cert.policy(); self.ca.signatures() .filter(move |sig| policy.signature(sig, HashAlgoSecurity::CollisionResistance).is_ok()) } } /// A Valid User ID and its associated data. /// /// A specialized version of [`ValidComponentAmalgamation`]. /// pub type ValidUserIDAmalgamation<'a> = ValidComponentAmalgamation<'a, UserID>; impl<'a> ValidUserIDAmalgamation<'a> { /// Returns a reference to the User ID. /// /// This is just a type-specific alias for /// [`ValidComponentAmalgamation::component`]. /// /// # Examples /// /// ``` /// # use sequoia_openpgp as openpgp; /// # use openpgp::cert::prelude::*; /// # /// # fn main() -> openpgp::Result<()> { /// # let (cert, _) = /// # CertBuilder::general_purpose(Some("alice@example.org")) /// # .generate()?; /// // Display some information about the User IDs. /// for ua in cert.userids() { /// eprintln!(" - {:?}", ua.userid()); /// } /// # Ok(()) } /// ``` pub fn userid(&self) -> &'a UserID { self.component() } /// Returns the user ID's approved third-party certifications. /// /// This feature is [experimental](crate#experimental-features). /// /// Allows the certificate owner to approve of third party /// certifications. See [Approved Certification subpacket] for /// details. This can be used to address certificate flooding /// concerns. /// /// This method only returns signatures that are valid under the /// current policy and are approved by the certificate holder. /// /// [Approved Certification subpacket]: https://www.ietf.org/archive/id/draft-dkg-openpgp-1pa3pc-02.html#approved-certifications-subpacket pub fn approved_certifications(&self) -> impl Iterator<Item=&Signature> + Send + Sync { let mut hash_algo = None; let digests: std::collections::HashSet<_> = self.certification_approval_key_signatures() .filter_map(|sig| { sig.approved_certifications().ok() .map(|digest_iter| (sig, digest_iter)) }) .flat_map(|(sig, digest_iter)| { hash_algo = Some(sig.hash_algo()); digest_iter }) .collect(); self.certifications() .filter_map(move |sig| { let mut hash = hash_algo.and_then(|a| a.context().ok())? .for_signature(sig.version()); sig.hash_for_confirmation(&mut hash).ok()?; let digest = hash.into_digest().ok()?; if digests.contains(&digest[..]) { Some(sig) } else { None } }) } /// Returns set of active certification approval key signatures. /// /// This feature is [experimental](crate#experimental-features). /// /// Returns the set of signatures with the newest valid signature /// creation time. Older signatures are not returned. The sum of /// all digests in these signatures are the set of approved /// third-party certifications. /// /// This interface is useful for pruning old certification /// approval key signatures when filtering a certificate. /// /// Note: This is a low-level interface. Consider using /// [`ValidUserIDAmalgamation::approved_certifications`] to /// iterate over all approved certifications. /// /// [`ValidUserIDAmalgamation::approved_certifications`]: ValidUserIDAmalgamation#method.approved_certifications // The explicit link works around a bug in rustdoc. pub fn certification_approval_key_signatures(&'a self) -> impl Iterator<Item=&'a Signature> + Send + Sync { let mut first = None; // The newest valid signature will be returned first. self.ca.approvals() // First, filter out any invalid (e.g. too new) signatures. .filter(move |sig| self.cert.policy().signature( sig, HashAlgoSecurity::CollisionResistance).is_ok()) .take_while(move |sig| { let time_hash = ( if let Some(t) = sig.signature_creation_time() { (t, sig.hash_algo()) } else { // Something is off. Just stop. return false; }, sig.hash_algo()); if let Some(reference) = first { // Stop looking once we see an older signature or one // with a different hash algo. reference == time_hash } else { first = Some(time_hash); true } }) } /// Approves of third-party certifications. /// /// This feature is [experimental](crate#experimental-features). /// /// Allows the certificate owner to approve of third party /// certifications. See [Approved Certifications subpacket] for /// details. This can be used to address certificate flooding /// concerns. /// /// [Approved Certifications subpacket]: https://www.ietf.org/archive/id/draft-dkg-openpgp-1pa3pc-02.html#approved-certifications-subpacket /// /// # Examples /// /// ``` /// # use sequoia_openpgp as openpgp; /// # fn main() -> openpgp::Result<()> { /// # use openpgp::cert::prelude::*; /// # use openpgp::packet::signature::SignatureBuilder; /// # use openpgp::types::*; /// # let policy = &openpgp::policy::StandardPolicy::new(); /// let (alice, _) = CertBuilder::new() /// .add_userid("alice@example.org") /// .generate()?; /// let mut alice_signer = /// alice.primary_key().key().clone().parts_into_secret()? /// .into_keypair()?; /// /// let (bob, _) = CertBuilder::new() /// .add_userid("bob@example.org") /// .generate()?; /// let mut bob_signer = /// bob.primary_key().key().clone().parts_into_secret()? /// .into_keypair()?; /// let bob_pristine = bob.clone(); /// /// // Have Alice certify the binding between "bob@example.org" and /// // Bob's key. /// let alice_certifies_bob /// = bob.userids().next().unwrap().userid().bind( /// &mut alice_signer, &bob, /// SignatureBuilder::new(SignatureType::GenericCertification))?; /// let bob = bob.insert_packets(vec![alice_certifies_bob.clone()])?.0; /// /// // Have Bob approve of that certification. /// let bobs_uid = bob.with_policy(policy, None)?.userids().next().unwrap(); /// let approvals = /// bobs_uid.approve_of_certifications( /// &mut bob_signer, /// bobs_uid.certifications())?; /// let bob = bob.insert_packets(approvals)?.0; /// /// assert_eq!(bob.bad_signatures().count(), 0); /// assert_eq!(bob.userids().next().unwrap().certifications().next(), /// Some(&alice_certifies_bob)); /// # Ok(()) } /// ``` pub fn approve_of_certifications<C, S>(&self, primary_signer: &mut dyn Signer, certifications: C) -> Result<Vec<Signature>> where C: IntoIterator<Item = S>, S: Borrow<Signature>, { self.ca .approve_of_certifications(self.policy(), self.time(), primary_signer, certifications) } } /// A Valid User Attribute and its associated data. /// /// A specialized version of [`ValidComponentAmalgamation`]. /// pub type ValidUserAttributeAmalgamation<'a> = ValidComponentAmalgamation<'a, UserAttribute>; impl<'a> ValidUserAttributeAmalgamation<'a> { /// Returns a reference to the User Attribute. /// /// This is just a type-specific alias for /// [`ValidComponentAmalgamation::component`]. /// /// # Examples /// /// ``` /// # use sequoia_openpgp as openpgp; /// # use openpgp::cert::prelude::*; /// # /// # fn main() -> openpgp::Result<()> { /// # let (cert, _) = /// # CertBuilder::general_purpose(Some("alice@example.org")) /// # .generate()?; /// // Display some information about the User IDs. /// for ua in cert.user_attributes() { /// eprintln!(" - {:?}", ua.user_attribute()); /// } /// # Ok(()) } /// ``` pub fn user_attribute(&self) -> &'a UserAttribute { self.component() } /// Returns the user attributes' approved third-party certifications. /// /// This feature is [experimental](crate#experimental-features). /// /// Allows the certificate owner to approve of third party /// certifications. See [Approved Certifications subpacket] for /// details. This can be used to address certificate flooding /// concerns. /// /// This method only returns signatures that are valid under the /// current policy and are approved by the certificate holder. /// /// [Approved Certifications subpacket]: https://www.ietf.org/archive/id/draft-dkg-openpgp-1pa3pc-02.html#approved-certifications-subpacket pub fn approved_certifications(&self) -> impl Iterator<Item=&Signature> + Send + Sync { let mut hash_algo = None; let digests: std::collections::HashSet<_> = self.certification_approval_key_signatures() .filter_map(|sig| { sig.approved_certifications().ok() .map(|digest_iter| (sig, digest_iter)) }) .flat_map(|(sig, digest_iter)| { hash_algo = Some(sig.hash_algo()); digest_iter }) .collect(); self.certifications() .filter_map(move |sig| { let mut hash = hash_algo.and_then(|a| a.context().ok())? .for_signature(sig.version()); sig.hash_for_confirmation(&mut hash).ok()?; let digest = hash.into_digest().ok()?; if digests.contains(&digest[..]) { Some(sig) } else { None } }) } /// Returns set of active certification approval key signatures. /// /// This feature is [experimental](crate#experimental-features). /// /// Returns the set of signatures with the newest valid signature /// creation time. Older signatures are not returned. The sum of /// all digests in these signatures are the set of approved /// third-party certifications. /// /// This interface is useful for pruning old certification /// approval key signatures when filtering a certificate. /// /// Note: This is a low-level interface. Consider using /// [`ValidUserAttributeAmalgamation::approved_certifications`] to /// iterate over all approved certifications. /// /// [`ValidUserAttributeAmalgamation::approved_certifications`]: ValidUserAttributeAmalgamation#method.approved_certifications // The explicit link works around a bug in rustdoc. pub fn certification_approval_key_signatures(&'a self) -> impl Iterator<Item=&'a Signature> + Send + Sync { let mut first = None; // The newest valid signature will be returned first. self.ca.approvals() // First, filter out any invalid (e.g. too new) signatures. .filter(move |sig| self.cert.policy().signature( sig, HashAlgoSecurity::CollisionResistance).is_ok()) .take_while(move |sig| { let time_hash = ( if let Some(t) = sig.signature_creation_time() { (t, sig.hash_algo()) } else { // Something is off. Just stop. return false; }, sig.hash_algo()); if let Some(reference) = first { // Stop looking once we see an older signature or one // with a different hash algo. reference == time_hash } else { first = Some(time_hash); true } }) } /// Approves of third-party certifications. /// /// This feature is [experimental](crate#experimental-features). /// /// Allows the certificate owner to approve of third party /// certifications. See [Approved Certifications subpacket] for /// details. This can be used to address certificate flooding /// concerns. /// /// [Approved Certifications subpacket]: https://www.ietf.org/archive/id/draft-dkg-openpgp-1pa3pc-02.html#approved-certifications-subpacket /// /// # Examples /// /// See [`ValidUserIDAmalgamation::approve_of_certifications#examples`]. /// /// [`ValidUserIDAmalgamation::approve_of_certifications#examples`]: ValidUserIDAmalgamation#examples // The explicit link works around a bug in rustdoc. pub fn approve_of_certifications<C, S>(&self, primary_signer: &mut dyn Signer, certifications: C) -> Result<Vec<Signature>> where C: IntoIterator<Item = S>, S: Borrow<Signature>, { self.ca .approve_of_certifications(self.policy(), self.time(), primary_signer, certifications) } } // derive(Clone) doesn't work with generic parameters that don't // implement clone. But, we don't need to require that C implements // Clone, because we're not cloning C, just the reference. // // See: https://github.com/rust-lang/rust/issues/26925 impl<'a, C> Clone for ValidComponentAmalgamation<'a, C> { fn clone(&self) -> Self { Self { ca: self.ca.clone(), cert: self.cert.clone(), binding_signature: self.binding_signature, } } } impl<'a, C: 'a> From<ValidComponentAmalgamation<'a, C>> for ComponentAmalgamation<'a, C> { fn from(vca: ValidComponentAmalgamation<'a, C>) -> Self { assert!(std::ptr::eq(vca.ca.cert(), vca.cert.cert())); vca.ca } } impl<'a, C> ValidComponentAmalgamation<'a, C> where C: Ord + Send + Sync { /// Returns the amalgamated primary component at time `time` /// /// If `time` is None, then the current time is used. /// `ValidComponentAmalgamationIter` for the definition of a valid component. /// /// The primary component is determined by taking the components that /// are alive at time `t`, and sorting them as follows: /// /// - non-revoked first /// - primary first /// - signature creation first /// /// If there is more than one, then one is selected in a /// deterministic, but undefined manner. /// /// If `valid_cert` is `false`, then this does not also check /// whether the certificate is valid; it only checks whether the /// component is valid. Normally, this should be `true`. This /// option is only exposed to allow breaking an infinite recursion: /// /// - To check if a certificate is valid, we check if the /// primary key is valid. /// /// - To check if the primary key is valid, we need the primary /// key's self signature /// /// - To find the primary key's self signature, we need to find /// the primary user id /// /// - To find the primary user id, we need to check if the user /// id is valid. /// /// - To check if the user id is valid, we need to check that /// the corresponding certificate is valid. pub(super) fn primary(cert: &'a Cert, iter: std::slice::Iter<'a, ComponentBundle<C>>, policy: &'a dyn Policy, t: SystemTime, valid_cert: bool) -> Result<ValidComponentAmalgamation<'a, C>> { use std::cmp::Ordering; let mut error = None; // Filter out components that are not alive at time `t`. // // While we have the binding signature, extract a few // properties to avoid recomputing the same thing multiple // times. iter.filter_map(|c| { // No binding signature at time `t` => not alive. let sig = match c.binding_signature(policy, t) { Ok(sig) => Some(sig), Err(e) => { error = Some(e); None }, }?; let revoked = c._revocation_status(policy, t, false, Some(sig)); let primary = sig.primary_userid().unwrap_or(false); let signature_creation_time = match sig.signature_creation_time() { Some(time) => Some(time), None => { error = Some(Error::MalformedPacket( "Signature has no creation time".into()).into()); None }, }?; Some(((c, sig, revoked), primary, signature_creation_time)) }) .max_by(|(a, a_primary, a_signature_creation_time), (b, b_primary, b_signature_creation_time)| { match (matches!(&a.2, RevocationStatus::Revoked(_)), matches!(&b.2, RevocationStatus::Revoked(_))) { (true, false) => return Ordering::Less, (false, true) => return Ordering::Greater, _ => (), } match (a_primary, b_primary) { (true, false) => return Ordering::Greater, (false, true) => return Ordering::Less, _ => (), } match a_signature_creation_time.cmp(b_signature_creation_time) { Ordering::Less => return Ordering::Less, Ordering::Greater => return Ordering::Greater, Ordering::Equal => (), } // Fallback to a lexographical comparison. Prefer // the "smaller" one. match a.0.component().cmp(b.0.component()) { Ordering::Less => Ordering::Greater, Ordering::Greater => Ordering::Less, Ordering::Equal => panic!("non-canonicalized Cert (duplicate components)"), } }) .ok_or_else(|| { error.map(|e| e.context(format!( "No binding signature at time {}", crate::fmt::time(&t)))) .unwrap_or_else(|| Error::NoBindingSignature(t).into()) }) .and_then(|c| ComponentAmalgamation::new(cert, (c.0).0) .with_policy_relaxed(policy, t, valid_cert)) } } impl<'a, C> seal::Sealed for ValidComponentAmalgamation<'a, C> {} impl<'a, C> ValidateAmalgamation<'a, C> for ValidComponentAmalgamation<'a, C> { type V = Self; fn with_policy<T>(&self, policy: &'a dyn Policy, time: T) -> Result<Self::V> where T: Into<Option<time::SystemTime>>, Self: Sized, { assert!(std::ptr::eq(self.ca.cert(), self.cert.cert())); let time = time.into().unwrap_or_else(crate::now); self.ca.with_policy(policy, time) } } impl<'a, C> ValidAmalgamation<'a, C> for ValidComponentAmalgamation<'a, C> where C: Send + Sync, { fn valid_cert(&self) -> &ValidCert<'a> { assert!(std::ptr::eq(self.ca.cert(), self.cert.cert())); &self.cert } fn time(&self) -> SystemTime { assert!(std::ptr::eq(self.ca.cert(), self.cert.cert())); self.cert.time } fn policy(&self) -> &'a dyn Policy { assert!(std::ptr::eq(self.ca.cert(), self.cert.cert())); self.cert.policy } fn binding_signature(&self) -> &'a Signature { assert!(std::ptr::eq(self.ca.cert(), self.cert.cert())); self.binding_signature } fn revocation_status(&self) -> RevocationStatus<'a> { self.bundle()._revocation_status(self.policy(), self.cert.time, false, Some(self.binding_signature)) } fn revocation_keys(&self) -> Box<dyn Iterator<Item = &'a RevocationKey> + 'a> { let mut keys = std::collections::HashSet::new(); let policy = self.policy(); let pk_sec = self.cert().primary_key().key().hash_algo_security(); // All valid self-signatures. let sec = self.bundle().hash_algo_security; self.self_signatures() .filter(move |sig| { policy.signature(sig, sec).is_ok() }) // All direct-key signatures. .chain(self.cert().primary_key() .self_signatures() .filter(|sig| { policy.signature(sig, pk_sec).is_ok() })) .flat_map(|sig| sig.revocation_keys()) .for_each(|rk| { keys.insert(rk); }); Box::new(keys.into_iter()) } } impl<'a, C> ValidBindingSignature<'a, C> for ValidComponentAmalgamation<'a, C> where C: Send + Sync, {} impl<'a, C> crate::cert::Preferences<'a> for ValidComponentAmalgamation<'a, C> where C: Send + Sync, { fn preferred_symmetric_algorithms(&self) -> Option<&'a [SymmetricAlgorithm]> { self.map(|s| s.preferred_symmetric_algorithms()) } fn preferred_hash_algorithms(&self) -> Option<&'a [HashAlgorithm]> { self.map(|s| s.preferred_hash_algorithms()) } fn preferred_compression_algorithms(&self) -> Option<&'a [CompressionAlgorithm]> { self.map(|s| s.preferred_compression_algorithms()) } fn preferred_aead_ciphersuites( &self) -> Option<&'a [(SymmetricAlgorithm, AEADAlgorithm)]> { self.map(|s| s.preferred_aead_ciphersuites()) } fn key_server_preferences(&self) -> Option<KeyServerPreferences> { self.map(|s| s.key_server_preferences()) } fn preferred_key_server(&self) -> Option<&'a [u8]> { self.map(|s| s.preferred_key_server()) } fn policy_uri(&self) -> Option<&'a [u8]> { self.map(|s| s.policy_uri()) } fn features(&self) -> Option<Features> { self.map(|s| s.features()) } } #[cfg(test)] mod test { use super::*; use std::time::UNIX_EPOCH; use crate::policy::StandardPolicy as P; use crate::Packet; use crate::packet::signature::SignatureBuilder; use crate::packet::UserID; use crate::types::SignatureType; use crate::types::ReasonForRevocation; // derive(Clone) doesn't work with generic parameters that don't // implement clone. Make sure that our custom implementations // work. // // See: https://github.com/rust-lang/rust/issues/26925 #[test] fn clone() { let p = &P::new(); let (cert, _) = CertBuilder::new() .add_userid("test@example.example") .generate() .unwrap(); let userid : UserIDAmalgamation = cert.userids().next().unwrap(); assert_eq!(userid.userid(), userid.clone().userid()); let userid : ValidUserIDAmalgamation = userid.with_policy(p, None).unwrap(); let c = userid.clone(); assert_eq!(userid.userid(), c.userid()); assert_eq!(userid.time(), c.time()); } #[test] fn map() { // The reference returned by `ComponentAmalgamation::userid` // and `ComponentAmalgamation::user_attribute` is bound by the // reference to the `Component` in the // `ComponentAmalgamation`, not the `ComponentAmalgamation` // itself. let (cert, _) = CertBuilder::new() .add_userid("test@example.example") .generate() .unwrap(); let _ = cert.userids().map(|ua| ua.userid()) .collect::<Vec<_>>(); let _ = cert.user_attributes().map(|ua| ua.user_attribute()) .collect::<Vec<_>>(); } #[test] fn component_amalgamation_certifications_by_key() -> Result<()> { // Alice and Bob certify Carol's certificate. We then check // that certifications_by_key returns them. let (alice, _) = CertBuilder::new() .add_userid("<alice@example.example>") .generate() .unwrap(); let (bob, _) = CertBuilder::new() .add_userid("<bob@example.example>") .generate() .unwrap(); let carol_userid = "<carol@example.example>"; let (carol, _) = CertBuilder::new() .add_userid(carol_userid) .generate() .unwrap(); let ua = alice.userids().next().expect("have a user id"); assert_eq!(ua.certifications_by_key(&[ alice.key_handle() ]).count(), 0); // Alice has not certified Bob's User ID. let ua = bob.userids().next().expect("have a user id"); assert_eq!(ua.certifications_by_key(&[ alice.key_handle() ]).count(), 0); // Alice has not certified Carol's User ID. let ua = carol.userids().next().expect("have a user id"); assert_eq!(ua.certifications_by_key(&[ alice.key_handle() ]).count(), 0); // Have Alice certify Carol's certificate. let mut alice_signer = alice.primary_key() .key() .clone() .parts_into_secret().expect("have unencrypted key material") .into_keypair().expect("have unencrypted key material"); let certification = SignatureBuilder::new(SignatureType::GenericCertification) .sign_userid_binding( &mut alice_signer, carol.primary_key().key(), &UserID::from(carol_userid))?; let carol = carol.insert_packets(certification)?.0; // Check that it is returned. let ua = carol.userids().next().expect("have a user id"); assert_eq!(ua.certifications().count(), 1); assert_eq!(ua.certifications_by_key(&[ alice.key_handle() ]).count(), 1); assert_eq!(ua.certifications_by_key(&[ bob.key_handle() ]).count(), 0); // Have Bob certify Carol's certificate. let mut bob_signer = bob.primary_key() .key() .clone() .parts_into_secret().expect("have unencrypted key material") .into_keypair().expect("have unencrypted key material"); let certification = SignatureBuilder::new(SignatureType::GenericCertification) .sign_userid_binding( &mut bob_signer, carol.primary_key().key(), &UserID::from(carol_userid))?; let carol = carol.insert_packets(certification)?.0; // Check that it is returned. let ua = carol.userids().next().expect("have a user id"); assert_eq!(ua.certifications().count(), 2); assert_eq!(ua.certifications_by_key(&[ alice.key_handle() ]).count(), 1); assert_eq!(ua.certifications_by_key(&[ bob.key_handle() ]).count(), 1); // Again. let certification = SignatureBuilder::new(SignatureType::GenericCertification) .sign_userid_binding( &mut bob_signer, carol.primary_key().key(), &UserID::from(carol_userid))?; let carol = carol.insert_packets(certification)?.0; // Check that it is returned. let ua = carol.userids().next().expect("have a user id"); assert_eq!(ua.certifications().count(), 3); assert_eq!(ua.certifications_by_key(&[ alice.key_handle() ]).count(), 1); assert_eq!(ua.certifications_by_key(&[ bob.key_handle() ]).count(), 2); Ok(()) } #[test] fn user_id_amalgamation_certifications_by_key() -> Result<()> { // Alice and Bob certify Carol's certificate. We then check // that valid_certifications_by_key and // active_certifications_by_key return them. let p = &crate::policy::StandardPolicy::new(); // $ date -u -d '2024-01-02 13:00' +%s let t0 = UNIX_EPOCH + Duration::new(1704200400, 0); // $ date -u -d '2024-01-02 14:00' +%s let t1 = UNIX_EPOCH + Duration::new(1704204000, 0); // $ date -u -d '2024-01-02 15:00' +%s let t2 = UNIX_EPOCH + Duration::new(1704207600, 0); let (alice, _) = CertBuilder::new() .set_creation_time(t0) .add_userid("<alice@example.example>") .generate() .unwrap(); let alice_primary = alice.primary_key().key(); let (bob, _) = CertBuilder::new() .set_creation_time(t0) .add_userid("<bob@example.example>") .generate() .unwrap(); let bob_primary = bob.primary_key().key(); let carol_userid = "<carol@example.example>"; let (carol, _) = CertBuilder::new() .set_creation_time(t0) .add_userid(carol_userid) .generate() .unwrap(); let ua = alice.userids().next().expect("have a user id"); assert_eq!(ua.valid_certifications_by_key(p, None, alice_primary).count(), 0); assert_eq!(ua.active_certifications_by_key(p, None, alice_primary).count(), 0); // Alice has not certified Bob's User ID. let ua = bob.userids().next().expect("have a user id"); assert_eq!(ua.valid_certifications_by_key(p, None, alice_primary).count(), 0); assert_eq!(ua.active_certifications_by_key(p, None, alice_primary).count(), 0); // Alice has not certified Carol's User ID. let ua = carol.userids().next().expect("have a user id"); assert_eq!(ua.valid_certifications_by_key(p, None, alice_primary).count(), 0); assert_eq!(ua.active_certifications_by_key(p, None, alice_primary).count(), 0); // Have Alice certify Carol's certificate at t1. let mut alice_signer = alice_primary .clone() .parts_into_secret().expect("have unencrypted key material") .into_keypair().expect("have unencrypted key material"); let certification = SignatureBuilder::new(SignatureType::GenericCertification) .set_signature_creation_time(t1)? .sign_userid_binding( &mut alice_signer, carol.primary_key().key(), &UserID::from(carol_userid))?; let carol = carol.insert_packets(certification.clone())?.0; // Check that it is returned. let ua = carol.userids().next().expect("have a user id"); assert_eq!(ua.certifications().count(), 1); assert_eq!(ua.valid_certifications_by_key(p, t0, alice_primary).count(), 0); assert_eq!(ua.active_certifications_by_key(p, t0, alice_primary).count(), 0); assert_eq!(ua.valid_certifications_by_key(p, t1, alice_primary).count(), 1); assert_eq!(ua.active_certifications_by_key(p, t1, alice_primary).count(), 1); assert_eq!(ua.valid_certifications_by_key(p, t1, bob_primary).count(), 0); assert_eq!(ua.active_certifications_by_key(p, t1, bob_primary).count(), 0); // Have Alice certify Carol's certificate at t1 (again). // Since both certifications were created at t1, they should // both be returned. let mut alice_signer = alice_primary .clone() .parts_into_secret().expect("have unencrypted key material") .into_keypair().expect("have unencrypted key material"); let certification = SignatureBuilder::new(SignatureType::GenericCertification) .set_signature_creation_time(t1)? .sign_userid_binding( &mut alice_signer, carol.primary_key().key(), &UserID::from(carol_userid))?; let carol = carol.insert_packets(certification.clone())?.0; // Check that it is returned. let ua = carol.userids().next().expect("have a user id"); assert_eq!(ua.certifications().count(), 2); assert_eq!(ua.valid_certifications_by_key(p, t0, alice_primary).count(), 0); assert_eq!(ua.active_certifications_by_key(p, t0, alice_primary).count(), 0); assert_eq!(ua.valid_certifications_by_key(p, t1, alice_primary).count(), 2); assert_eq!(ua.active_certifications_by_key(p, t1, alice_primary).count(), 2); assert_eq!(ua.valid_certifications_by_key(p, t2, alice_primary).count(), 2); assert_eq!(ua.active_certifications_by_key(p, t2, alice_primary).count(), 2); assert_eq!(ua.valid_certifications_by_key(p, t0, bob_primary).count(), 0); assert_eq!(ua.active_certifications_by_key(p, t0, bob_primary).count(), 0); // Have Alice certify Carol's certificate at t2. Now we only // have one active certification. let mut alice_signer = alice_primary .clone() .parts_into_secret().expect("have unencrypted key material") .into_keypair().expect("have unencrypted key material"); let certification = SignatureBuilder::new(SignatureType::GenericCertification) .set_signature_creation_time(t2)? .sign_userid_binding( &mut alice_signer, carol.primary_key().key(), &UserID::from(carol_userid))?; let carol = carol.insert_packets(certification.clone())?.0; // Check that it is returned. let ua = carol.userids().next().expect("have a user id"); assert_eq!(ua.certifications().count(), 3); assert_eq!(ua.valid_certifications_by_key(p, t0, alice_primary).count(), 0); assert_eq!(ua.active_certifications_by_key(p, t0, alice_primary).count(), 0); assert_eq!(ua.valid_certifications_by_key(p, t1, alice_primary).count(), 2); assert_eq!(ua.active_certifications_by_key(p, t1, alice_primary).count(), 2); assert_eq!(ua.valid_certifications_by_key(p, t2, alice_primary).count(), 3); assert_eq!(ua.active_certifications_by_key(p, t2, alice_primary).count(), 1); assert_eq!(ua.valid_certifications_by_key(p, t0, bob_primary).count(), 0); assert_eq!(ua.active_certifications_by_key(p, t0, bob_primary).count(), 0); // Have Bob certify Carol's certificate at t1 and have it expire at t2. let mut bob_signer = bob.primary_key() .key() .clone() .parts_into_secret().expect("have unencrypted key material") .into_keypair().expect("have unencrypted key material"); let certification = SignatureBuilder::new(SignatureType::GenericCertification) .set_signature_creation_time(t1)? .set_signature_validity_period(t2.duration_since(t1)?)? .sign_userid_binding( &mut bob_signer, carol.primary_key().key(), &UserID::from(carol_userid))?; let carol = carol.insert_packets(certification.clone())?.0; // Check that it is returned. let ua = carol.userids().next().expect("have a user id"); assert_eq!(ua.certifications().count(), 4); assert_eq!(ua.valid_certifications_by_key(p, t0, alice_primary).count(), 0); assert_eq!(ua.active_certifications_by_key(p, t0, alice_primary).count(), 0); assert_eq!(ua.valid_certifications_by_key(p, t1, alice_primary).count(), 2); assert_eq!(ua.active_certifications_by_key(p, t1, alice_primary).count(), 2); assert_eq!(ua.valid_certifications_by_key(p, t2, alice_primary).count(), 3); assert_eq!(ua.active_certifications_by_key(p, t2, alice_primary).count(), 1); assert_eq!(ua.valid_certifications_by_key(p, t0, bob_primary).count(), 0); assert_eq!(ua.active_certifications_by_key(p, t0, bob_primary).count(), 0); assert_eq!(ua.valid_certifications_by_key(p, t1, bob_primary).count(), 1); assert_eq!(ua.active_certifications_by_key(p, t1, bob_primary).count(), 1); // It expired. assert_eq!(ua.valid_certifications_by_key(p, t2, bob_primary).count(), 0); assert_eq!(ua.active_certifications_by_key(p, t2, bob_primary).count(), 0); // Have Bob certify Carol's certificate at t1 again. This // time don't have it expire. let mut bob_signer = bob.primary_key() .key() .clone() .parts_into_secret().expect("have unencrypted key material") .into_keypair().expect("have unencrypted key material"); let certification = SignatureBuilder::new(SignatureType::GenericCertification) .set_signature_creation_time(t1)? .sign_userid_binding( &mut bob_signer, carol.primary_key().key(), &UserID::from(carol_userid))?; let carol = carol.insert_packets(certification.clone())?.0; // Check that it is returned. let ua = carol.userids().next().expect("have a user id"); assert_eq!(ua.certifications().count(), 5); assert_eq!(ua.valid_certifications_by_key(p, t0, alice_primary).count(), 0); assert_eq!(ua.active_certifications_by_key(p, t0, alice_primary).count(), 0); assert_eq!(ua.valid_certifications_by_key(p, t1, alice_primary).count(), 2); assert_eq!(ua.active_certifications_by_key(p, t1, alice_primary).count(), 2); assert_eq!(ua.valid_certifications_by_key(p, t2, alice_primary).count(), 3); assert_eq!(ua.active_certifications_by_key(p, t2, alice_primary).count(), 1); assert_eq!(ua.valid_certifications_by_key(p, t0, bob_primary).count(), 0); assert_eq!(ua.active_certifications_by_key(p, t0, bob_primary).count(), 0); assert_eq!(ua.valid_certifications_by_key(p, t1, bob_primary).count(), 2); assert_eq!(ua.active_certifications_by_key(p, t1, bob_primary).count(), 2); // One of the certifications expired. assert_eq!(ua.valid_certifications_by_key(p, t2, bob_primary).count(), 1); assert_eq!(ua.active_certifications_by_key(p, t2, bob_primary).count(), 1); Ok(()) } #[test] fn user_id_amalgamation_third_party_revocations_by_key() -> Result<()> { // Alice and Bob revoke Carol's User ID. We then check // that valid_third_party_revocations_by_key returns them. let p = &crate::policy::StandardPolicy::new(); // $ date -u -d '2024-01-02 13:00' +%s let t0 = UNIX_EPOCH + Duration::new(1704200400, 0); // $ date -u -d '2024-01-02 14:00' +%s let t1 = UNIX_EPOCH + Duration::new(1704204000, 0); // $ date -u -d '2024-01-02 15:00' +%s let t2 = UNIX_EPOCH + Duration::new(1704207600, 0); let (alice, _) = CertBuilder::new() .set_creation_time(t0) .add_userid("<alice@example.example>") .generate() .unwrap(); let alice_primary = alice.primary_key().key(); let (bob, _) = CertBuilder::new() .set_creation_time(t0) .add_userid("<bob@example.example>") .generate() .unwrap(); let bob_primary = bob.primary_key().key(); let carol_userid = "<carol@example.example>"; let (carol, _) = CertBuilder::new() .set_creation_time(t0) .add_userid(carol_userid) .generate() .unwrap(); let carol_userid = UserID::from(carol_userid); let ua = alice.userids().next().expect("have a user id"); assert_eq!(ua.valid_third_party_revocations_by_key(p, None, alice_primary).count(), 0); // Alice has not certified Bob's User ID. let ua = bob.userids().next().expect("have a user id"); assert_eq!(ua.valid_third_party_revocations_by_key(p, None, alice_primary).count(), 0); // Alice has not certified Carol's User ID. let ua = carol.userids().next().expect("have a user id"); assert_eq!(ua.valid_third_party_revocations_by_key(p, None, alice_primary).count(), 0); // Have Alice revoke Carol's certificate at t1. let mut alice_signer = alice_primary .clone() .parts_into_secret().expect("have unencrypted key material") .into_keypair().expect("have unencrypted key material"); let certification = SignatureBuilder::new(SignatureType::CertificationRevocation) .set_signature_creation_time(t1)? .set_reason_for_revocation( ReasonForRevocation::UIDRetired, b"")? .sign_userid_binding( &mut alice_signer, carol.primary_key().key(), &carol_userid)?; let carol = carol.insert_packets([ Packet::from(carol_userid.clone()), Packet::from(certification.clone()), ])?.0; // Check that it is returned. let ua = carol.userids().next().expect("have a user id"); assert_eq!(ua.other_revocations().count(), 1); assert_eq!(ua.valid_third_party_revocations_by_key(p, t0, alice_primary).count(), 0); assert_eq!(ua.valid_third_party_revocations_by_key(p, t1, alice_primary).count(), 1); assert_eq!(ua.valid_third_party_revocations_by_key(p, t1, bob_primary).count(), 0); // Have Alice certify Carol's certificate at t1 (again). // Since both certifications were created at t1, they should // both be returned. let mut alice_signer = alice_primary .clone() .parts_into_secret().expect("have unencrypted key material") .into_keypair().expect("have unencrypted key material"); let certification = SignatureBuilder::new(SignatureType::CertificationRevocation) .set_signature_creation_time(t1)? .set_reason_for_revocation(ReasonForRevocation::UIDRetired, b"")? .sign_userid_binding( &mut alice_signer, carol.primary_key().key(), &carol_userid)?; let carol = carol.insert_packets([ Packet::from(carol_userid.clone()), Packet::from(certification.clone()), ])?.0; // Check that it is returned. let ua = carol.userids().next().expect("have a user id"); assert_eq!(ua.other_revocations().count(), 2); assert_eq!(ua.valid_third_party_revocations_by_key(p, t0, alice_primary).count(), 0); assert_eq!(ua.valid_third_party_revocations_by_key(p, t1, alice_primary).count(), 2); assert_eq!(ua.valid_third_party_revocations_by_key(p, t2, alice_primary).count(), 2); assert_eq!(ua.valid_third_party_revocations_by_key(p, t0, bob_primary).count(), 0); // Have Alice certify Carol's certificate at t2. Now we only // have one active certification. let mut alice_signer = alice_primary .clone() .parts_into_secret().expect("have unencrypted key material") .into_keypair().expect("have unencrypted key material"); let certification = SignatureBuilder::new(SignatureType::CertificationRevocation) .set_signature_creation_time(t2)? .set_reason_for_revocation(ReasonForRevocation::UIDRetired, b"")? .sign_userid_binding( &mut alice_signer, carol.primary_key().key(), &carol_userid)?; let carol = carol.insert_packets([ Packet::from(carol_userid.clone()), Packet::from(certification.clone()), ])?.0; // Check that it is returned. let ua = carol.userids().next().expect("have a user id"); assert_eq!(ua.other_revocations().count(), 3); assert_eq!(ua.valid_third_party_revocations_by_key(p, t0, alice_primary).count(), 0); assert_eq!(ua.valid_third_party_revocations_by_key(p, t1, alice_primary).count(), 2); assert_eq!(ua.valid_third_party_revocations_by_key(p, t2, alice_primary).count(), 3); assert_eq!(ua.valid_third_party_revocations_by_key(p, t0, bob_primary).count(), 0); // Have Bob certify Carol's certificate at t1 and have it expire at t2. let mut bob_signer = bob.primary_key() .key() .clone() .parts_into_secret().expect("have unencrypted key material") .into_keypair().expect("have unencrypted key material"); let certification = SignatureBuilder::new(SignatureType::CertificationRevocation) .set_signature_creation_time(t1)? .set_signature_validity_period(t2.duration_since(t1)?)? .set_reason_for_revocation(ReasonForRevocation::UIDRetired, b"")? .sign_userid_binding( &mut bob_signer, carol.primary_key().key(), &carol_userid)?; let carol = carol.insert_packets([ Packet::from(carol_userid.clone()), Packet::from(certification.clone()), ])?.0; // Check that it is returned. let ua = carol.userids().next().expect("have a user id"); assert_eq!(ua.other_revocations().count(), 4); assert_eq!(ua.valid_third_party_revocations_by_key(p, t0, alice_primary).count(), 0); assert_eq!(ua.valid_third_party_revocations_by_key(p, t1, alice_primary).count(), 2); assert_eq!(ua.valid_third_party_revocations_by_key(p, t2, alice_primary).count(), 3); assert_eq!(ua.valid_third_party_revocations_by_key(p, t0, bob_primary).count(), 0); assert_eq!(ua.valid_third_party_revocations_by_key(p, t1, bob_primary).count(), 1); // It expired. assert_eq!(ua.valid_third_party_revocations_by_key(p, t2, bob_primary).count(), 0); // Have Bob certify Carol's certificate at t1 again. This // time don't have it expire. let mut bob_signer = bob.primary_key() .key() .clone() .parts_into_secret().expect("have unencrypted key material") .into_keypair().expect("have unencrypted key material"); let certification = SignatureBuilder::new(SignatureType::CertificationRevocation) .set_signature_creation_time(t1)? .set_reason_for_revocation(ReasonForRevocation::UIDRetired, b"")? .sign_userid_binding( &mut bob_signer, carol.primary_key().key(), &carol_userid)?; let carol = carol.insert_packets([ Packet::from(carol_userid.clone()), Packet::from(certification.clone()), ])?.0; // Check that it is returned. let ua = carol.userids().next().expect("have a user id"); assert_eq!(ua.other_revocations().count(), 5); assert_eq!(ua.valid_third_party_revocations_by_key(p, t0, alice_primary).count(), 0); assert_eq!(ua.valid_third_party_revocations_by_key(p, t1, alice_primary).count(), 2); assert_eq!(ua.valid_third_party_revocations_by_key(p, t2, alice_primary).count(), 3); assert_eq!(ua.valid_third_party_revocations_by_key(p, t0, bob_primary).count(), 0); assert_eq!(ua.valid_third_party_revocations_by_key(p, t1, bob_primary).count(), 2); // One of the certifications expired. assert_eq!(ua.valid_third_party_revocations_by_key(p, t2, bob_primary).count(), 1); Ok(()) } } ���������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/src/cert/bindings.rs����������������������������������������������������������0000644�0000000�0000000�00000033775�10461020230�0016645�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������use std::time; use crate::Error; use crate::Result; use crate::Cert; use crate::types::{HashAlgorithm, SignatureType}; use crate::crypto::Signer; use crate::packet::{UserID, UserAttribute, key, Key, signature, Signature}; impl<P: key::KeyParts> Key<P, key::SubordinateRole> { /// Creates a binding signature. /// /// The signature binds this subkey to `cert`. `signer` will be used /// to create a signature using `signature` as builder. /// The`hash_algo` defaults to SHA512, `creation_time` to the /// current time. /// /// Note that subkeys with signing capabilities need a [primary /// key binding signature]. If you are creating this binding /// signature from a previous binding signature, you can reuse the /// primary key binding signature if it is still valid and meets /// current algorithm requirements. Otherwise, you can create one /// using [`SignatureBuilder::sign_primary_key_binding`]. /// /// [primary key binding signature]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.1 /// [`SignatureBuilder::sign_primary_key_binding`]: signature::SignatureBuilder::sign_primary_key_binding() /// /// This function adds a creation time subpacket, an issuer /// fingerprint subpacket, and an issuer subpacket to the /// signature. /// /// # Examples /// /// This example demonstrates how to bind this key to a Cert. Note /// that in general, the `CertBuilder` is a better way to add /// subkeys to a Cert. /// /// ``` /// # use sequoia_openpgp::{*, packet::prelude::*, types::*, cert::*}; /// # fn main() -> Result<()> { /// use sequoia_openpgp::policy::StandardPolicy; /// let p = &StandardPolicy::new(); /// /// // Generate a Cert, and create a keypair from the primary key. /// let (cert, _) = CertBuilder::new().generate()?; /// let mut keypair = cert.primary_key().key().clone() /// .parts_into_secret()?.into_keypair()?; /// /// // Let's add an encryption subkey. /// let flags = KeyFlags::empty().set_storage_encryption(); /// assert_eq!(cert.keys().with_policy(p, None).alive().revoked(false) /// .key_flags(&flags).count(), /// 0); /// /// // Generate a subkey and a binding signature. /// let subkey: Key<_, key::SubordinateRole> = /// Key6::generate_ecc(false, Curve::Cv25519)? /// .into(); /// let builder = signature::SignatureBuilder::new(SignatureType::SubkeyBinding) /// .set_key_flags(flags.clone())?; /// let binding = subkey.bind(&mut keypair, &cert, builder)?; /// /// // Now merge the key and binding signature into the Cert. /// let cert = cert.insert_packets(vec![Packet::from(subkey), /// binding.into()])?.0; /// /// // Check that we have an encryption subkey. /// assert_eq!(cert.keys().with_policy(p, None).alive().revoked(false) /// .key_flags(flags).count(), /// 1); /// # Ok(()) } pub fn bind(&self, signer: &mut dyn Signer, cert: &Cert, signature: signature::SignatureBuilder) -> Result<Signature> { signature.sign_subkey_binding( signer, cert.primary_key().key(), self) } } impl UserID { /// Creates a binding signature. /// /// The signature binds this User ID to `cert`. `signer` will be used /// to create a signature using `signature` as builder. /// The`hash_algo` defaults to SHA512, `creation_time` to the /// current time. /// /// This function adds a creation time subpacket, an issuer /// fingerprint subpacket, and an issuer subpacket to the /// signature. /// /// # Examples /// /// This example demonstrates how to bind this User ID to a Cert. /// Note that in general, the `CertBuilder` is a better way to add /// User IDs to a Cert. /// /// ``` /// # use sequoia_openpgp::{*, packet::prelude::*, types::*, cert::*}; /// # fn main() -> Result<()> { /// // Generate a Cert, and create a keypair from the primary key. /// let (cert, _) = CertBuilder::new().generate()?; /// let mut keypair = cert.primary_key().key().clone() /// .parts_into_secret()?.into_keypair()?; /// assert_eq!(cert.userids().len(), 0); /// /// // Generate a User ID and a binding signature. /// let userid = UserID::from("test@example.org"); /// let builder = /// signature::SignatureBuilder::new(SignatureType::PositiveCertification); /// let binding = userid.bind(&mut keypair, &cert, builder)?; /// /// // Now merge the User ID and binding signature into the Cert. /// let cert = cert.insert_packets(vec![Packet::from(userid), /// binding.into()])?.0; /// /// // Check that we have a User ID. /// assert_eq!(cert.userids().len(), 1); /// # Ok(()) } pub fn bind(&self, signer: &mut dyn Signer, cert: &Cert, signature: signature::SignatureBuilder) -> Result<Signature> { signature.sign_userid_binding( signer, cert.primary_key().key(), self) } /// Returns a certification for the User ID. /// /// The signature binds this User ID to `cert`. `signer` will be /// used to create a certification signature of type /// `signature_type`. `signature_type` defaults to /// `SignatureType::GenericCertification`, `hash_algo` to SHA512, /// `creation_time` to the current time. /// /// This function adds a creation time subpacket, an issuer /// fingerprint subpacket, and an issuer subpacket to the /// signature. /// /// # Errors /// /// Returns `Error::InvalidArgument` if `signature_type` is not /// one of `SignatureType::{Generic, Persona, Casual, /// Positive}Certification` /// /// # Examples /// /// This example demonstrates how to certify a User ID. /// /// ``` /// # use sequoia_openpgp::{*, packet::prelude::*, types::*, cert::*}; /// # fn main() -> Result<()> { /// // Generate a Cert, and create a keypair from the primary key. /// let (alice, _) = CertBuilder::new() /// .set_primary_key_flags(KeyFlags::empty().set_certification()) /// .add_userid("alice@example.org") /// .generate()?; /// let mut keypair = alice.primary_key().key().clone() /// .parts_into_secret()?.into_keypair()?; /// /// // Generate a Cert for Bob. /// let (bob, _) = CertBuilder::new() /// .set_primary_key_flags(KeyFlags::empty().set_certification()) /// .add_userid("bob@example.org") /// .generate()?; /// /// // Alice now certifies the binding between `bob@example.org` and `bob`. /// let certification = /// bob.userids().nth(0).unwrap() /// .userid() /// .certify(&mut keypair, &bob, SignatureType::PositiveCertification, /// None, None)?; /// /// // `certification` can now be used, e.g. by merging it into `bob`. /// let bob = bob.insert_packets(certification)?.0; /// /// // Check that we have a certification on the User ID. /// assert_eq!(bob.userids().nth(0).unwrap() /// .certifications().count(), 1); /// # Ok(()) } pub fn certify<S, H, T>(&self, signer: &mut dyn Signer, cert: &Cert, signature_type: S, hash_algo: H, creation_time: T) -> Result<Signature> where S: Into<Option<SignatureType>>, H: Into<Option<HashAlgorithm>>, T: Into<Option<time::SystemTime>> { let typ = signature_type.into(); let typ = match typ { Some(SignatureType::GenericCertification) | Some(SignatureType::PersonaCertification) | Some(SignatureType::CasualCertification) | Some(SignatureType::PositiveCertification) => typ.unwrap(), Some(t) => return Err(Error::InvalidArgument( format!("Invalid signature type: {}", t)).into()), None => SignatureType::GenericCertification, }; let mut sig = signature::SignatureBuilder::new(typ); if let Some(algo) = hash_algo.into() { sig = sig.set_hash_algo(algo); } if let Some(creation_time) = creation_time.into() { sig = sig.set_signature_creation_time(creation_time)?; } self.bind(signer, cert, sig) } } impl UserAttribute { /// Creates a binding signature. /// /// The signature binds this user attribute to `cert`. `signer` /// will be used to create a signature using `signature` as /// builder. The`hash_algo` defaults to SHA512, `creation_time` /// to the current time. /// /// This function adds a creation time subpacket, an issuer /// fingerprint subpacket, and an issuer subpacket to the /// signature. /// /// # Examples /// /// This example demonstrates how to bind this user attribute to a /// Cert. Note that in general, the `CertBuilder` is a better way /// to add User IDs to a Cert. /// /// ``` /// # use sequoia_openpgp::{*, packet::prelude::*, types::*, cert::*, /// # packet::user_attribute::*}; /// # fn main() -> Result<()> { /// // Generate a Cert, and create a keypair from the primary key. /// let (cert, _) = CertBuilder::new() /// .generate()?; /// let mut keypair = cert.primary_key().key().clone() /// .parts_into_secret()?.into_keypair()?; /// assert_eq!(cert.userids().len(), 0); /// /// // Generate a user attribute and a binding signature. /// let user_attr = UserAttribute::new(&[ /// Subpacket::Image( /// Image::Private(100, vec![0, 1, 2].into_boxed_slice())), /// ])?; /// let builder = /// signature::SignatureBuilder::new(SignatureType::PositiveCertification); /// let binding = user_attr.bind(&mut keypair, &cert, builder)?; /// /// // Now merge the user attribute and binding signature into the Cert. /// let cert = cert.insert_packets(vec![Packet::from(user_attr), /// binding.into()])?.0; /// /// // Check that we have a user attribute. /// assert_eq!(cert.user_attributes().count(), 1); /// # Ok(()) } pub fn bind(&self, signer: &mut dyn Signer, cert: &Cert, signature: signature::SignatureBuilder) -> Result<Signature> { signature.sign_user_attribute_binding( signer, cert.primary_key().key(), self) } /// Returns a certification for the user attribute. /// /// The signature binds this user attribute to `cert`. `signer` will be /// used to create a certification signature of type /// `signature_type`. `signature_type` defaults to /// `SignatureType::GenericCertification`, `hash_algo` to SHA512, /// `creation_time` to the current time. /// /// This function adds a creation time subpacket, an issuer /// fingerprint subpacket, and an issuer subpacket to the /// signature. /// /// # Errors /// /// Returns `Error::InvalidArgument` if `signature_type` is not /// one of `SignatureType::{Generic, Persona, Casual, /// Positive}Certification` /// /// # Examples /// /// This example demonstrates how to certify a User ID. /// /// ``` /// # use sequoia_openpgp::{*, packet::prelude::*, types::*, cert::*, /// # packet::user_attribute::*}; /// # fn main() -> Result<()> { /// // Generate a Cert, and create a keypair from the primary key. /// let (alice, _) = CertBuilder::new() /// .add_userid("alice@example.org") /// .generate()?; /// let mut keypair = alice.primary_key().key().clone() /// .parts_into_secret()?.into_keypair()?; /// /// // Generate a Cert for Bob. /// let user_attr = UserAttribute::new(&[ /// Subpacket::Image( /// Image::Private(100, vec![0, 1, 2].into_boxed_slice())), /// ])?; /// let (bob, _) = CertBuilder::new() /// .set_primary_key_flags(KeyFlags::empty().set_certification()) /// .add_user_attribute(user_attr) /// .generate()?; /// /// // Alice now certifies the binding between `bob@example.org` and `bob`. /// let certification = /// bob.user_attributes().nth(0).unwrap() /// .user_attribute() /// .certify(&mut keypair, &bob, SignatureType::PositiveCertification, /// None, None)?; /// /// // `certification` can now be used, e.g. by merging it into `bob`. /// let bob = bob.insert_packets(certification)?.0; /// /// // Check that we have a certification on the User ID. /// assert_eq!(bob.user_attributes().nth(0).unwrap() /// .certifications().count(), /// 1); /// # Ok(()) } pub fn certify<S, H, T>(&self, signer: &mut dyn Signer, cert: &Cert, signature_type: S, hash_algo: H, creation_time: T) -> Result<Signature> where S: Into<Option<SignatureType>>, H: Into<Option<HashAlgorithm>>, T: Into<Option<time::SystemTime>> { let typ = signature_type.into(); let typ = match typ { Some(SignatureType::GenericCertification) | Some(SignatureType::PersonaCertification) | Some(SignatureType::CasualCertification) | Some(SignatureType::PositiveCertification) => typ.unwrap(), Some(t) => return Err(Error::InvalidArgument( format!("Invalid signature type: {}", t)).into()), None => SignatureType::GenericCertification, }; let mut sig = signature::SignatureBuilder::new(typ); if let Some(algo) = hash_algo.into() { sig = sig.set_hash_algo(algo); } if let Some(creation_time) = creation_time.into() { sig = sig.set_signature_creation_time(creation_time)?; } self.bind(signer, cert, sig) } } ���sequoia-openpgp-2.0.0/src/cert/builder/key.rs�������������������������������������������������������0000644�0000000�0000000�00000111541�10461020230�0017252�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������use std::time::{Duration, SystemTime}; use crate::packet::{ Key, key, }; use crate::Result; use crate::Packet; use crate::packet::signature::{ SignatureBuilder, SIG_BACKDATE_BY, subpacket::SubpacketTag, }; use crate::cert::prelude::*; use crate::Error; use crate::Profile; use crate::crypto::{Password, Signer}; use crate::types::{ HashAlgorithm, KeyFlags, SignatureType, }; /// A Key builder. /// /// A `KeyBuilder` is used to create a key, which can then be attached /// to an existing certificate as a subkey using /// [`KeyBuilder::subkey`]. /// /// # Examples /// /// Generate a signing key and attach it to a certificate: /// /// ``` /// use sequoia_openpgp as openpgp; /// use openpgp::cert::prelude::*; /// use openpgp::policy::StandardPolicy; /// use openpgp::types::KeyFlags; /// /// # fn main() -> openpgp::Result<()> { /// let p = &StandardPolicy::new(); /// /// # let (cert, _) = /// # CertBuilder::general_purpose(Some("alice@example.org")) /// # .generate()?; /// # /// let vc = cert.with_policy(p, None)?; /// # let vc1 = vc.clone(); /// let cert_new = KeyBuilder::new(KeyFlags::empty().set_signing()) /// .subkey(vc)? /// .attach_cert()?; /// # let vc2 = cert_new.with_policy(p, None)?; /// # assert_eq!(vc1.keys().count() + 1, vc2.keys().count()); /// # Ok(()) /// # } /// ``` pub struct KeyBuilder { flags: KeyFlags, cipher_suite: CipherSuite, password: Option<Password>, creation_time: Option<SystemTime>, } assert_send_and_sync!(KeyBuilder); impl KeyBuilder { /// Returns a new `KeyBuilder`. /// /// Use [`KeyBuilder::subkey`] to generate a subkey and get a /// [`SubkeyBuilder`], which can be used to add the subkey to a /// certificate. pub fn new(flags: KeyFlags) -> Self { KeyBuilder { flags, cipher_suite: Default::default(), creation_time: None, password: None, } } /// Returns the selected cipher suite. pub fn cipher_suite(&self) -> CipherSuite { self.cipher_suite } /// Sets the cipher suite. pub fn set_cipher_suite(mut self, cipher_suite: CipherSuite) -> Self { self.cipher_suite = cipher_suite; self } /// Returns the creation time. /// /// Returns `None` if the creation time hasn't been specified. In /// that case, the creation time will be set to the current time /// when the key material is generated by [`KeyBuilder::subkey`]. pub fn creation_time(&self) -> Option<SystemTime> { self.creation_time } /// Sets the creation time. /// /// If `None`, then the creation time will be set to the current /// time when the key material is generated by /// [`KeyBuilder::subkey`]. pub fn set_creation_time<T>(mut self, creation_time: T) -> Self where T: Into<Option<SystemTime>> { self.creation_time = creation_time.into(); self } /// Returns the password, if any. pub fn password(&self) -> Option<&Password> { self.password.as_ref() } /// Sets the password. pub fn set_password<T>(mut self, password: T) -> Self where T: Into<Option<Password>> { self.password = password.into(); self } /// Generates a key, and returns a `SubkeyBuilder`. /// /// The [`SubkeyBuilder`] will add the key to the specified /// certificate. /// /// If the key creation time has not been explicitly set using /// [`KeyBuilder::set_creation_time`], then the key's creation /// time is set to the current time minus a few seconds. /// /// Setting the creation time to a short time in the past solves /// two problems. First, when a new binding signature is created, /// it must have a newer time than the previous binding signature. /// This policy ensures that if a second binding signature is /// immediately created after the key is created it does not need /// to be postdated and thus can be used immediately. Second, if /// the key is immediately transferred to another computer and its /// clock is not quite synchronized, the key may appear to have /// been created in the future and will thus be ignored. Although /// NTP is widely used, empirically it seems that some virtual /// machines have laggy clocks. pub fn subkey(self, vc: ValidCert) -> Result<SubkeyBuilder<'_>> { let profile = match vc.primary_key().key().version() { 4 => Profile::RFC4880, 6 => Profile::RFC9580, n => return Err(Error::InvalidOperation(format!( "Cannot generate a subkey for version {} primary key", n)) .into()), }; let mut key: Key<key::SecretParts, key::SubordinateRole> = self.cipher_suite.generate_key(&self.flags, profile)? .role_into_subordinate(); let ct = self.creation_time.unwrap_or_else(|| { crate::now() - Duration::new(SIG_BACKDATE_BY, 0) }); key.set_creation_time(ct)?; let signer = key.clone().into_keypair().unwrap(); if let Some(ref password) = self.password { let (k, mut secret) = key.take_secret(); secret.encrypt_in_place(&k, password)?; key = k.add_secret(secret).0; } let mut builder = SubkeyBuilder::new( vc, key.parts_into_unspecified(), self.flags)?; builder = builder.set_signature_creation_time(ct)?; Ok(builder.set_subkey_signer(signer)) } } /// A Subkey builder. /// /// This builder simplifies attaching a subkey to a certificate, or /// updating an existing subkey's binding signature. It is a more /// high-level variant of [`SignatureBuilder`], which should be used /// if more control is needed than this builder provides. /// /// # Security Considerations: Key Expiration /// /// **It is essential that keys have reasonable expiration times.** If /// a binding signature is accidentally published without an /// expiration time, it is effectively impossible to retract this by /// publishing a new binding signature that has an expiration. This /// is because an attacker may be able to withhold the newer binding /// signature thereby causing a victim to use a key that is actually /// expired. /// /// The heuristic described below takes this security consideration /// into account. However, because the heuristic never extends a /// key's expiration on its own, there are still cases where it is /// necessary to set the expiration manually. /// /// # Binding Signature /// /// To attach a subkey to a certificate, the primary key needs to /// issue a [subkey binding signature]. This binding signature /// provides information about the key including its validity period /// (i.e., when it expires), and may contain auxiliary information /// like notations. A subkey binding signature usually contains the /// following information: /// /// [subkey binding signature]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.1 /// /// - [Signature creation time](https://datatracker.ietf.org/doc/html/rfc4880#section-5.2.3.4) /// /// - [Key flags](https://datatracker.ietf.org/doc/html/rfc4880#section-5.2.3.21) /// /// - [Issuer](https://datatracker.ietf.org/doc/html/rfc4880#section-5.2.3.5) and Issuer Fingerprint. /// /// - [Primary key binding signature](https://datatracker.ietf.org/doc/html/rfc4880#section-5.2.3.26) (if the key is signing capable) /// /// The following information is also meaningful in the context of /// a subkey binding signature: /// /// - [Key expiration time](https://datatracker.ietf.org/doc/html/rfc4880#section-5.2.3.6) /// (relative to the key's creation time, not the signature's /// creation time!) /// /// - [Signature exiration time](https://datatracker.ietf.org/doc/html/rfc4880#section-5.2.3.10) /// /// - [Exportable certification](https://datatracker.ietf.org/doc/html/rfc4880#section-5.2.3.11) /// /// - [Notations](https://datatracker.ietf.org/doc/html/rfc4880#section-5.2.3.16) /// /// Because a `SubkeyBuilder` is just a wrapper around a /// [`SignatureBuilder`], refer [`SignatureBuilder`]'s documentation /// about to understand how some of these subpackets are automatically /// set. /// /// It is possible to change the signature's creation time and key /// expiration time using the /// [`SubkeyBuilder::set_signature_creation_time`] and /// [`SubkeyBuilder::set_key_expiration_time`] methods. Other /// subpackets can be modified using /// [`SubkeyBuilder::with_signature_template`]. /// /// ## Heuristic /// /// This builder uses a heuristic to select a binding signature to use /// as a template and to select a key expiration. It is possible to /// use your own binding signature by calling /// [`SubkeyBuilder::set_signature_template`], and override the key /// expiration time using [`SubkeyBuilder::set_key_expiration_time`]. /// In general, you should use an existing binding signature as a /// template to preserve any customizations that the user may have /// made. /// /// Because forgetting to set an expiration time can be security /// relevant, this heuristic acts conservatively. If possible, the /// user interface should show the expiration time, and allow the user /// to adjust it manually. /// /// The heuristic is: /// /// - If the subkey is already present on the certificate, the default /// binding signature is based on the subkey's active binding /// signature, and the key expiration time is reused. /// /// If the key would expire before the binding signature becomes /// valid then [`SubkeyBuilder::attach`] will fail. /// /// Note: if the subkey is present, but it does not have a valid /// binding signature, then the subkey is treated as a new subkey. /// /// - If the subkey is new, then the active binding signature of the /// newest live, non-revoked, valid subkey is used as the binding /// signature template. Newest means the key with the latest /// Key Creation Time and not necessarily the newest binding /// signature. (If multiple keys have the same key creation time, /// the key to use is chosen in an undefined, but deterministic /// manner.) /// /// If the certificate does not have a subkey, then a default /// binding signature is created. In this case, the default /// expiration is set to the same expiration as the primary key, if /// any. /// /// As above, if the key would expire before the binding signature /// becomes valid then [`SubkeyBuilder::attach`] will fail. /// /// # Examples /// /// Add a new, signing-capable subkey to a certificate: /// /// ``` /// use sequoia_openpgp as openpgp; /// use openpgp::cert::prelude::*; /// use openpgp::policy::StandardPolicy; /// use openpgp::types::KeyFlags; /// /// # fn main() -> openpgp::Result<()> { /// let p = &StandardPolicy::new(); /// /// # let (cert, _) = /// # CertBuilder::general_purpose(Some("alice@example.org")) /// # .generate()?; /// # /// let vc = cert.with_policy(p, None)?; /// # let vc1 = vc.clone(); /// let cert_new = KeyBuilder::new(KeyFlags::empty().set_signing()) /// .subkey(vc)? /// .attach_cert()?; /// # let vc2 = cert_new.with_policy(p, None)?; /// # assert_eq!(vc1.keys().count() + 1, vc2.keys().count()); /// # Ok(()) /// # } /// ``` /// /// Import a raw encryption key: /// /// ``` /// use std::time::SystemTime; /// /// use sequoia_openpgp as openpgp; /// use openpgp::cert::prelude::*; /// use openpgp::packet::Key; /// use openpgp::packet::key::Key4; /// use openpgp::policy::StandardPolicy; /// use openpgp::types::KeyFlags; /// /// # fn main() -> openpgp::Result<()> { /// let p = &StandardPolicy::new(); /// /// # let q = b"\x57\x15\x45\x1B\x68\xA5\x13\xA2\x20\x0F\x71\x9D\xE3\x05\x3B\xED\xA2\x21\xDE\x61\x5A\xF5\x67\x45\xBB\x97\x99\x43\x53\x59\x7C\x3F"; /// let k: Key<_, _> /// = Key4::import_public_ed25519(q, SystemTime::now())?.into(); /// /// # let (cert, _) = CertBuilder::new().generate()?; /// # /// let vc = cert.with_policy(p, None)?; /// # let vc1 = vc.clone(); /// let mut cert2 = SubkeyBuilder::new( /// vc, k.parts_into_unspecified(), /// KeyFlags::empty().set_transport_encryption())? /// .attach_cert()?; /// # /// # let vc2 = cert2.with_policy(p, None)?; /// # assert_eq!(vc1.keys().count() + 1, vc2.keys().count()); /// # Ok(()) /// # } /// ``` /// /// Change all valid, non-revoked subkeys to expire in a year from now: /// /// ``` /// use std::time::{SystemTime, Duration}; /// /// use sequoia_openpgp as openpgp; /// use openpgp::cert::prelude::*; /// use openpgp::Packet; /// use openpgp::policy::StandardPolicy; /// use openpgp::types::KeyFlags; /// /// # fn main() -> openpgp::Result<()> { /// let p = &StandardPolicy::new(); /// /// let now = SystemTime::now(); /// let e = now + Duration::new(365 * 24 * 60 * 60, 0); /// /// # let v = Duration::new(24 * 60 * 60, 0); /// # let (cert, _) = /// # CertBuilder::new() /// # .set_creation_time(now - Duration::new(60, 0)) /// # .add_subkey(KeyFlags::empty().set_storage_encryption(), /// # v, None) /// # .add_subkey(KeyFlags::empty().set_signing(), /// # v, None) /// # .generate()?; /// # assert_eq!(cert.keys().subkeys().count(), 2); /// let vc = cert.with_policy(p, None)?; /// # assert_eq!(vc.keys().subkeys().count(), 2); /// # for ka in vc.keys().subkeys() { /// # assert_eq!(ka.key_validity_period(), Some(v)); /// # } /// /// // If you only want to extend non-expired keys, then add .alive(). /// let packets = vc.keys().subkeys().revoked(false) /// .map(|ka| { /// SubkeyBuilder::from(ka) /// .set_signature_creation_time(now)? /// .set_key_expiration_time(e)? /// .attach() /// }) /// .collect::<Result<Vec<Vec<Packet>>, _>>()?; /// let cert = cert.insert_packets(packets.into_iter().flatten())?.0; /// /// let vc = cert.with_policy(p, now)?; /// # assert_eq!(vc.keys().subkeys().count(), 2); /// for ka in vc.keys().subkeys().revoked(false) { /// // Check that the key's expiration time is really e. Note: We /// // need to take into account that SystemTime has a subsecond /// // resolution, but OpenPGP's timestamps only have a 1-second /// // resolution. /// assert!(e.duration_since(ka.key_expiration_time().unwrap()).unwrap() /// < Duration::new(1, 0)); /// } /// # Ok(()) /// # } /// ``` pub struct SubkeyBuilder<'a> { vc: ValidCert<'a>, primary_signer: Option<Box<dyn Signer + Send + Sync + 'a>>, subkey: Key<key::UnspecifiedParts, key::SubordinateRole>, subkey_signer: Option<Box<dyn Signer + Send + Sync + 'a>>, template: SignatureBuilder, } assert_send_and_sync!(SubkeyBuilder<'_>); impl<'a> SubkeyBuilder<'a> { /// Returns a SubkeyBuilder that will add the key to the specified /// certificate. /// /// If the subkey is already present on the certificate, then the /// `SubkeyBuilder` effectively adds a new binding signature to /// the certificate. pub fn new<P>(vc: ValidCert<'a>, subkey: Key<P, key::SubordinateRole>, subkey_flags: KeyFlags) -> Result<Self> where P: key::KeyParts, { // If the key is already present on the certificate, then we // use the current self signature on that subkey as the // template. let (template, key_expiration): (SignatureBuilder, Option<SystemTime>) = vc.keys().subkeys() .filter_map(|ka| { if ka.key().parts_as_unspecified().public_eq(&subkey) { let sig = ka.binding_signature().clone(); let e = sig.key_validity_period().map(|v| { ka.key().creation_time() + v }); Some((sig.into(), e)) } else { None } }) .next() .or_else(|| { // The key is completely new. Use the active self // signature on the newest, non-revoked, non-expired // subkey. vc.keys().subkeys().revoked(false).alive() // Fallback to sorting by fingerprint to ensure // this is deterministic. .max_by_key(|ka| (ka.key().creation_time(), ka.key().fingerprint())) .map(|ka| { let sig = ka.binding_signature().clone(); let e = sig.key_validity_period().map(|v| { ka.key().creation_time() + v }); (sig.into(), e) }) }) .unwrap_or_else(|| { // The certificate doesn't have any valid subkeys, so // we don't have existing signatures that we can use // as a template. In this case, we use a default // binding signature, and the primary key's expiration // time. (SignatureBuilder::new(SignatureType::SubkeyBinding), vc.primary_key().key_validity_period().map(|v| { vc.primary_key().key().creation_time() + v })) }); let template = template.set_key_flags(subkey_flags)?; let mut builder = SubkeyBuilder { vc, primary_signer: None, subkey: subkey.parts_into_unspecified(), subkey_signer: None, template: SignatureBuilder::new(SignatureType::SubkeyBinding), }; builder = builder.set_signature_template(template); builder = builder.set_key_expiration_time(key_expiration)?; Ok(builder) } /// Like SubkeyBuilder::new, but the binding signature is supplied. /// /// # Security Considerations /// /// The key validity period (i.e., the [Key Expiration Time /// subpacket]) is left as is. **The Key Expiration Time /// subpacket contains a relative time.** Thus, if you are using a /// signature from another key with a different key creation time /// as a template, the effective key expiration time will be /// different! In this case, you should set the key expiration /// time explicitly by calling /// [`SubkeyBuilder::set_key_expiration_time`] or /// [`SubkeyBuilder::set_key_validity_period`]. /// /// [Key Expiration Time subpacket]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.13 /// /// # Examples /// /// Adjusting the key expiration time: /// /// ``` /// use std::time::{SystemTime, Duration}; /// /// use sequoia_openpgp as openpgp; /// use openpgp::cert::prelude::*; /// use openpgp::packet::Key; /// use openpgp::packet::key::Key4; /// use openpgp::policy::StandardPolicy; /// use openpgp::types::KeyFlags; /// /// # fn main() -> openpgp::Result<()> { /// let p = &StandardPolicy::new(); /// /// let now = SystemTime::now(); /// let year = Duration::new(365 * 24 * 60 * 60, 0); /// let last_year = now - year; /// // cert was created last year and expires after two years. /// let (cert, _) = /// CertBuilder::new() /// .set_creation_time(now - year) /// .add_subkey(KeyFlags::empty().set_transport_encryption(), /// 2 * year, None) /// .generate()?; /// /// // Import a raw key and add it to the certificate. We /// // explicitly reuse the existing subkey's signature, and adjust /// // the key expiration time. /// /// # let q = b"\x57\x15\x45\x1B\x68\xA5\x13\xA2\x20\x0F\x71\x9D\xE3\x05\x3B\xED\xA2\x21\xDE\x61\x5A\xF5\x67\x45\xBB\x97\x99\x43\x53\x59\x7C\x3F"; /// let k: Key<_, _> = Key4::import_public_ed25519(q, now)?.into(); /// /// let vc = cert.with_policy(p, now)?; /// let template /// = vc.keys().subkeys().next().unwrap().binding_signature().clone(); /// # let vc1 = vc.clone(); /// let cert2 = SubkeyBuilder::new_with(vc, k, template) /// .set_key_validity_period(year)? /// .attach_cert()?; /// let vc2 = cert2.with_policy(p, now)?; /// # assert_eq!(vc1.keys().count() + 1, vc2.keys().count()); /// /// // Observe that both keys expire one year from now. If we /// // hadn't adjusted the validity period of the new key, it would /// // have expired in two years from now, because the key validity /// // period is relative to the key's creation time! /// vc2.keys().subkeys().for_each(|sig| { /// // SystemTime has a subsection resolution. /// assert!((now + year) /// .duration_since(sig.key_expiration_time().unwrap()) /// .unwrap() /// < Duration::new(1, 0)); /// }); /// # Ok(()) /// # } /// ``` pub fn new_with<P, T>(vc: ValidCert<'a>, subkey: Key<P, key::SubordinateRole>, template: T) -> Self where P: key::KeyParts, T: Into<SignatureBuilder>, { let template = template.into(); let mut builder = SubkeyBuilder { vc, primary_signer: None, subkey: subkey.parts_into_unspecified(), subkey_signer: None, template: SignatureBuilder::new(SignatureType::SubkeyBinding), }; builder = builder.set_signature_template(template); builder } /// Sets the signature template that will be used for the binding /// signature. /// /// This effectively discards any previous calls to /// [`SubkeyBuilder::set_signature_creation_time`], /// [`SubkeyBuilder::set_key_expiration_time`], etc. /// /// This function modifies the template as follows: /// /// - The hash algorithm is set to a safe default. /// /// These changes can be overridden by using /// [`SubkeyBuilder::with_signature_template`]. /// /// # Security Considerations /// /// The key validity period (i.e., the [Key Expiration Time /// subpacket]) is left as is. **This packet contains a relative /// time.** Thus, if you are using a Signature from another key /// with a different key creation time as a template, the /// effective key expiration time will be different! In this /// case, you should set the key expiration time explicitly by /// calling [`SubkeyBuilder::set_key_expiration_time`] or /// [`SubkeyBuilder::set_key_validity_period`]. /// /// [Key Expiration Time subpacket]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.13 pub fn set_signature_template<T>(mut self, template: T) -> Self where T: Into<SignatureBuilder>, { self.template = template.into(); // GnuPG wants at least a 512-bit hash for P521 keys. self.template = self.template.set_hash_algo(HashAlgorithm::SHA512); self } /// Allows a function to directly modify the signature template. /// /// This function does not fail; it returns the result of the /// callback function. /// /// # Examples /// /// Add a notation to an existing key: /// /// ``` /// use sequoia_openpgp as openpgp; /// use openpgp::cert::prelude::*; /// use openpgp::packet::signature::subpacket::NotationDataFlags; /// use openpgp::policy::StandardPolicy; /// use openpgp::types::KeyFlags; /// /// # fn main() -> openpgp::Result<()> { /// let p = &StandardPolicy::new(); /// /// # let (cert, _) = CertBuilder::new().add_signing_subkey().generate()?; /// let vc = cert.with_policy(p, None)?; /// let cert2 = SubkeyBuilder::from(vc.keys().subkeys().next().unwrap()) /// .with_signature_template(|sig| { /// sig.add_notation("policy@example.org", b"1", /// NotationDataFlags::empty().set_human_readable(), /// false /* critical */) /// })? /// .attach_cert()?; /// # let vc2 = cert2.with_policy(p, None)?; /// # assert_eq!(vc2.keys().count(), 2); /// # let ka = vc2.keys().subkeys().next().unwrap(); /// # assert_eq!(ka.self_signatures().count(), 2); /// # assert_eq!( /// # ka.binding_signature().notation("policy@example.org") /// # .collect::<Vec<_>>(), /// # vec![ b"1" ]); /// # Ok(()) /// # } /// ``` pub fn with_signature_template<F>(mut self, f: F) -> Result<Self> where F: FnOnce(SignatureBuilder) -> Result<SignatureBuilder> { self.template = f(self.template.clone())?; Ok(self) } /// Sets the binding signature's creation time. /// /// This directly modifies the current signature template. /// /// This just calls /// [`SignatureBuilder::set_signature_creation_time`] on the /// signature template. pub fn set_signature_creation_time<T>(mut self, creation_time: T) -> Result<Self> where T: Into<SystemTime> { self.template = self.template.set_signature_creation_time( creation_time.into())?; Ok(self) } /// Preserves the signature creation time set in the template. /// /// This directly modifies the current signature template. /// /// This just calls /// [`SignatureBuilder::preserve_signature_creation_time`] on the /// signature template. pub fn preserve_signature_creation_time(mut self) -> Result<Self> { self.template = self.template.preserve_signature_creation_time()?; Ok(self) } /// Sets the key's expiration time. /// /// This directly modifies the current signature template. /// /// This returns an error if the expiration time is before the /// key's creation time. pub fn set_key_expiration_time<T>(mut self, key_expiration_time: T) -> Result<Self> where T: Into<Option<SystemTime>> { let key_expiration_time = key_expiration_time.into(); let validity_period = key_expiration_time .map(|e| { e.duration_since(self.subkey.creation_time()) .map_err(|_| { Error::InvalidArgument( "expiration time precedes creation time".into()) }) }) .transpose()?; self = self.with_signature_template(|sig| { sig.set_key_validity_period(validity_period) })?; Ok(self) } /// Sets the key's validity period. /// /// The validity period is the amount of time after the key's /// creation time that the key is considered fresh (i.e., not /// expired). /// /// This directly modifies the current signature template. pub fn set_key_validity_period<T>(mut self, validity: T) -> Result<Self> where T: Into<Option<Duration>> { self = self.with_signature_template(|sig| { sig.set_key_validity_period(validity.into()) })?; Ok(self) } /// Returns a reference to the subkey. pub fn key(&self) -> &Key<key::UnspecifiedParts, key::SubordinateRole> { &self.subkey } /// Adds a signer for the primary key. /// /// In order to attach a subkey to a certificate one or more /// signatures need to be issued. First, the primary key needs to /// issue a [subkey binding signature]. If the subkey is signing /// capable, then it also needs to issue a [primary key binding /// signature]. By default, [`SubkeyBuilder::attach`] will /// automatically derive the signers from the key material. This /// only works, however, if the key material is present, and it is /// unencrypted. This method allows you to explicitly provide a /// signer for the primary key. /// /// [subkey binding signature]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.1 /// [primary binding signature]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.1 pub fn set_primary_key_signer<S>(mut self, signer: S) -> Self where S: Signer + Send + Sync + 'a, { self.primary_signer = Some(Box::new(signer)); self } /// Adds a signer for the subkey. /// /// In order to attach a subkey to a certificate one or more /// signatures need to be issued. First, the primary key needs to /// issue a [subkey binding signature]. If the subkey is signing /// capable, then it also needs to issue a [primary key binding /// signature]. By default, [`SubkeyBuilder::attach`] will /// automatically derive the signers from the key material. This /// only works, however, if the key material is present, and it is /// unencrypted. This method allows you to explicitly provide a /// signer for the subkey. /// /// [subkey binding signature]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.1 /// [primary binding signature]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.1 pub fn set_subkey_signer<S>(mut self, signer: S) -> Self where S: Signer + Send + Sync + 'a, { self.subkey_signer = Some(Box::new(signer)); self } /// Attaches the subkey to the certificate. /// /// This method generates the appropriate signatures to attach the /// subkey to the certificate. /// /// This function returns an error if the expiration time would /// cause the key to expire before the binding signature's /// expiration time. /// /// This method returns a number of packets, which need to be /// merged into the cert. This can be done using /// [`Cert::insert_packets`]. pub fn attach(self) -> Result<Vec<Packet>> { let SubkeyBuilder { vc, primary_signer, subkey, subkey_signer, template, } = self; if template.typ() != SignatureType::SubkeyBinding { return Err(Error::InvalidArgument( format!("Expected a SubkeyBinding signature, got a {}", template.typ())).into()); } let mut builder = template; let creation_time = builder.effective_signature_creation_time()?; // creation_time is only None if // preserve_signature_creation_time is done and that's a // Highly Advanced Interface that doesn't need sanity checks. if let Some(sig_ct) = creation_time { if let Some(v) = builder.key_validity_period() { let e = subkey.creation_time() + v; if let Err(err) = e.duration_since(sig_ct) { return Err(Error::InvalidArgument( format!( "key expiration precedes signature creation time \ (by {:?})", err.duration())).into()); } } } if let Some(flags) = builder.key_flags() { if flags.for_certification() || flags.for_signing() || flags.for_authentication() { // We need to create a primary key binding signature. let mut subkey_signer = if let Some(signer) = subkey_signer { signer } else { Box::new( subkey.clone().parts_into_secret()?.into_keypair()?) }; let mut backsig = SignatureBuilder::new( SignatureType::PrimaryKeyBinding) // GnuPG wants at least a 512-bit hash for P521 keys. .set_hash_algo(HashAlgorithm::SHA512) .set_reference_time(creation_time)?; if let Some(t) = creation_time { backsig = backsig.set_reference_time(t)?; } else { backsig = backsig.preserve_signature_creation_time()?; } let backsig = backsig.sign_primary_key_binding( &mut *subkey_signer, vc.primary_key().key(), &subkey)?; builder = builder.set_embedded_signature(backsig)?; } else { // We don't need the embedded signature, remove it. builder.hashed_area_mut() .remove_all(SubpacketTag::EmbeddedSignature); builder.unhashed_area_mut() .remove_all(SubpacketTag::EmbeddedSignature); } } let mut primary_signer = if let Some(signer) = primary_signer { signer } else { Box::new( vc.primary_key().key().clone() .parts_into_secret()?.into_keypair()?) }; let signature = subkey.bind( &mut *primary_signer, vc.cert(), builder)?; let subkey = if subkey.has_secret() { Packet::SecretSubkey(subkey.parts_into_secret().unwrap()) } else { Packet::PublicSubkey(subkey.parts_into_public()) }; Ok(vec![subkey, signature.into()]) } /// Attaches the subkey directly to the certificate. /// /// This function is like [`SubkeyBuilder::attach`], but it also /// merges the resulting packets into the certificate. /// /// Note: if you are adding multiple subkeys to a certificate or /// updating multiple subkeys, it is usually more efficient to use /// [`SubkeyBuilder::attach`], and then merge all the packets /// at once. /// /// # Examples /// /// ``` /// use sequoia_openpgp as openpgp; /// use openpgp::cert::prelude::*; /// use openpgp::policy::StandardPolicy; /// use openpgp::types::KeyFlags; /// /// # fn main() -> openpgp::Result<()> { /// let p = &StandardPolicy::new(); /// /// # let (cert, _) = /// # CertBuilder::general_purpose(Some("alice@example.org")) /// # .generate()?; /// # /// let vc = cert.with_policy(p, None)?; /// # let vc1 = vc.clone(); /// let cert2 = KeyBuilder::new(KeyFlags::empty().set_signing()) /// .subkey(vc)? /// .attach_cert()?; /// # let vc2 = cert2.with_policy(p, None)?; /// # assert_eq!(vc1.keys().count() + 1, vc2.keys().count()); /// # Ok(()) /// # } /// ``` pub fn attach_cert(self) -> Result<Cert> { let cert = self.vc.cert().clone(); let packets = self.attach()?; Ok(cert.insert_packets(packets)?.0) } } impl<'a, P> From<ValidPrimaryKeyAmalgamation<'a, P>> for SubkeyBuilder<'a> where P: key::KeyParts + Clone, { fn from(ka: ValidPrimaryKeyAmalgamation<'a, P>) -> Self { ValidErasedKeyAmalgamation::from(ka).into() } } impl<'a, P> From<ValidSubordinateKeyAmalgamation<'a, P>> for SubkeyBuilder<'a> where P: key::KeyParts + Clone, { fn from(ka: ValidSubordinateKeyAmalgamation<'a, P>) -> Self { ValidErasedKeyAmalgamation::from(ka).into() } } impl<'a, P> From<ValidErasedKeyAmalgamation<'a, P>> for SubkeyBuilder<'a> where P: key::KeyParts + Clone, { fn from(ka: ValidErasedKeyAmalgamation<'a, P>) -> SubkeyBuilder<'a> { let key = ka.key().clone().role_into_subordinate(); SubkeyBuilder::new_with( ka.valid_cert().clone(), key, ka.binding_signature().clone()) } } #[cfg(test)] mod test { use super::*; use std::time::{Duration, UNIX_EPOCH}; use crate::policy::StandardPolicy; #[test] fn expiry() -> Result<()> { let p = &StandardPolicy::new(); // t0: Create certificate, keys expire at t2. // t1: Add a new key, heuristic should have it expire at t2. // t2: All keys expire. // We do it all in the past to make sure the current time is // never used. // Avoid milliseconds. let t1 = crate::now() - Duration::new(7 * 24 * 60 * 60, 0); let t1 = t1.duration_since(UNIX_EPOCH)?.as_secs(); let t1 = UNIX_EPOCH + Duration::new(t1, 0); let t0 = t1 - Duration::new(60 * 60, 0); let t2 = t1 + Duration::new(60 * 60, 0); let validity = t2.duration_since(t0).unwrap(); let (pre, _) = CertBuilder::general_purpose(Some("alice@example.org")) .set_creation_time(t0) .set_validity_period(validity) .generate()?; let vc_pre = pre.with_policy(p, t1)?; let post = KeyBuilder::new(KeyFlags::empty().set_signing()) .set_creation_time(t1) .subkey(vc_pre)? .set_signature_creation_time(t1)? .attach_cert()?; let vc_post = post.with_policy(p, t1).unwrap(); // We should have one more key. assert_eq!(pre.keys().count() + 1, post.keys().count()); // Make sure the signature and backsig are valid. assert_eq!(post.keys().count(), vc_post.keys().count()); // And the new key should have inherited the other keys' // expiration. eprintln!("t0: {:?}", t0); eprintln!("t1: {:?}", t1); eprintln!("t2: {:?}", t2); assert!(vc_post.keys().all(|ka| { eprintln!("{}: {:?} -> {:?}", ka.key().fingerprint(), ka.key().creation_time(), ka.key_expiration_time()); ka.key_expiration_time() == Some(t2) })); Ok(()) } } ���������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/src/cert/builder.rs�����������������������������������������������������������0000644�0000000�0000000�00000243262�10461020230�0016470�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������use std::time; use std::marker::PhantomData; use crate::packet; use crate::packet::{ Key, key::Key4, key::Key6, key::UnspecifiedRole, key::SecretKey as KeySecretKey, key::SecretParts as KeySecretParts, }; use crate::Profile; use crate::Result; use crate::packet::Signature; use crate::packet::signature::{ self, SignatureBuilder, subpacket::SubpacketTag, }; use crate::cert::prelude::*; use crate::Error; use crate::crypto::{Password, Signer}; use crate::types::{ Features, HashAlgorithm, KeyFlags, SignatureType, SymmetricAlgorithm, RevocationKey, }; mod key; pub use key::{ KeyBuilder, SubkeyBuilder, }; /// Groups symmetric and asymmetric algorithms. /// /// This is used to select a suite of ciphers. /// /// # Examples /// /// ``` /// use sequoia_openpgp as openpgp; /// use openpgp::cert::prelude::*; /// use openpgp::types::PublicKeyAlgorithm; /// /// # fn main() -> openpgp::Result<()> { /// let (ecc, _) = /// CertBuilder::general_purpose(Some("alice@example.org")) /// .set_cipher_suite(CipherSuite::Cv25519) /// .generate()?; /// assert_eq!(ecc.primary_key().key().pk_algo(), PublicKeyAlgorithm::EdDSA); /// /// let (rsa, _) = /// CertBuilder::general_purpose(Some("alice@example.org")) /// .set_cipher_suite(CipherSuite::RSA4k) /// .generate()?; /// assert_eq!(rsa.primary_key().key().pk_algo(), PublicKeyAlgorithm::RSAEncryptSign); /// # Ok(()) /// # } /// ``` #[derive(Clone, Copy, PartialEq, Eq, Debug)] #[non_exhaustive] pub enum CipherSuite { /// EdDSA and ECDH over Curve25519 with SHA512 and AES256 Cv25519, /// 3072 bit RSA with SHA512 and AES256 RSA3k, /// EdDSA and ECDH over NIST P-256 with SHA256 and AES256 P256, /// EdDSA and ECDH over NIST P-384 with SHA384 and AES256 P384, /// EdDSA and ECDH over NIST P-521 with SHA512 and AES256 P521, /// 2048 bit RSA with SHA512 and AES256 RSA2k, /// 4096 bit RSA with SHA512 and AES256 RSA4k, // If you add a variant here, be sure to update // CipherSuite::variants below. } assert_send_and_sync!(CipherSuite); impl Default for CipherSuite { fn default() -> Self { CipherSuite::Cv25519 } } impl CipherSuite { /// Returns an iterator over `CipherSuite`'s variants. pub fn variants() -> impl Iterator<Item=CipherSuite> { use CipherSuite::*; [ Cv25519, RSA3k, P256, P384, P521, RSA2k, RSA4k ] .into_iter() } /// Returns whether the currently selected cryptographic backend /// supports the encryption and signing algorithms that the cipher /// suite selects. pub fn is_supported(&self) -> Result<()> { use crate::types::{Curve, PublicKeyAlgorithm}; use CipherSuite::*; macro_rules! check_pk { ($pk: expr) => { if ! $pk.is_supported() { return Err(Error::UnsupportedPublicKeyAlgorithm($pk) .into()); } } } macro_rules! check_curve { ($curve: expr) => { if ! $curve.is_supported() { return Err(Error::UnsupportedEllipticCurve($curve) .into()); } } } match self { Cv25519 => { check_pk!(PublicKeyAlgorithm::EdDSA); check_curve!(Curve::Ed25519); check_pk!(PublicKeyAlgorithm::ECDH); check_curve!(Curve::Cv25519); }, RSA2k | RSA3k | RSA4k => { check_pk!(PublicKeyAlgorithm::RSAEncryptSign); }, P256 => { check_pk!(PublicKeyAlgorithm::ECDSA); check_curve!(Curve::NistP256); check_pk!(PublicKeyAlgorithm::ECDH); }, P384 => { check_pk!(PublicKeyAlgorithm::ECDSA); check_curve!(Curve::NistP384); check_pk!(PublicKeyAlgorithm::ECDH); }, P521 => { check_pk!(PublicKeyAlgorithm::ECDSA); check_curve!(Curve::NistP521); check_pk!(PublicKeyAlgorithm::ECDH); }, } Ok(()) } fn generate_key<K>(self, flags: K, profile: Profile) -> Result<Key<KeySecretParts, UnspecifiedRole>> where K: AsRef<KeyFlags>, { match profile { Profile::RFC9580 => Ok(self.generate_v6_key(flags)?.into()), Profile::RFC4880 => Ok(self.generate_v4_key(flags)?.into()), } } fn generate_v4_key<K>(self, flags: K) -> Result<Key4<KeySecretParts, UnspecifiedRole>> where K: AsRef<KeyFlags>, { use crate::types::Curve; match self { CipherSuite::RSA2k => Key4::generate_rsa(2048), CipherSuite::RSA3k => Key4::generate_rsa(3072), CipherSuite::RSA4k => Key4::generate_rsa(4096), CipherSuite::Cv25519 | CipherSuite::P256 | CipherSuite::P384 | CipherSuite::P521 => { let flags = flags.as_ref(); let sign = flags.for_certification() || flags.for_signing() || flags.for_authentication(); let encrypt = flags.for_transport_encryption() || flags.for_storage_encryption(); let curve = match self { CipherSuite::Cv25519 if sign => Curve::Ed25519, CipherSuite::Cv25519 if encrypt => Curve::Cv25519, CipherSuite::Cv25519 => { return Err(Error::InvalidOperation( "No key flags set".into()) .into()); } CipherSuite::P256 => Curve::NistP256, CipherSuite::P384 => Curve::NistP384, CipherSuite::P521 => Curve::NistP521, _ => unreachable!(), }; match (sign, encrypt) { (true, false) => Key4::generate_ecc(true, curve), (false, true) => Key4::generate_ecc(false, curve), (true, true) => Err(Error::InvalidOperation( "Can't use key for encryption and signing".into()) .into()), (false, false) => Err(Error::InvalidOperation( "No key flags set".into()) .into()), } }, } } fn generate_v6_key<K>(self, flags: K) -> Result<Key6<KeySecretParts, UnspecifiedRole>> where K: AsRef<KeyFlags>, { use crate::types::Curve; let flags = flags.as_ref(); let sign = flags.for_certification() || flags.for_signing() || flags.for_authentication(); let encrypt = flags.for_transport_encryption() || flags.for_storage_encryption(); match self { CipherSuite::Cv25519 => match (sign, encrypt) { (true, false) => Key6::generate_ed25519(), (false, true) => Key6::generate_x25519(), (true, true) => Err(Error::InvalidOperation( "Can't use key for encryption and signing".into()) .into()), (false, false) => Err(Error::InvalidOperation( "No key flags set".into()) .into()), }, CipherSuite::RSA2k => Key6::generate_rsa(2048), CipherSuite::RSA3k => Key6::generate_rsa(3072), CipherSuite::RSA4k => Key6::generate_rsa(4096), CipherSuite::P256 | CipherSuite::P384 | CipherSuite::P521 => { let curve = match self { CipherSuite::Cv25519 if sign => Curve::Ed25519, CipherSuite::Cv25519 if encrypt => Curve::Cv25519, CipherSuite::Cv25519 => { return Err(Error::InvalidOperation( "No key flags set".into()) .into()); } CipherSuite::P256 => Curve::NistP256, CipherSuite::P384 => Curve::NistP384, CipherSuite::P521 => Curve::NistP521, _ => unreachable!(), }; match (sign, encrypt) { (true, false) => Key6::generate_ecc(true, curve), (false, true) => Key6::generate_ecc(false, curve), (true, true) => Err(Error::InvalidOperation( "Can't use key for encryption and signing".into()) .into()), (false, false) => Err(Error::InvalidOperation( "No key flags set".into()) .into()), } }, } } } #[derive(Clone, Debug)] pub struct KeyBlueprint { flags: KeyFlags, validity: Option<time::Duration>, // If not None, uses the specified ciphersuite. Otherwise, uses // CertBuilder::ciphersuite. ciphersuite: Option<CipherSuite>, } assert_send_and_sync!(KeyBlueprint); /// Simplifies the generation of OpenPGP certificates. /// /// A builder to generate complex certificate hierarchies with multiple /// [`UserID`s], [`UserAttribute`s], and [`Key`s]. /// /// This builder does not aim to be as flexible as creating /// certificates manually, but it should be sufficiently powerful to /// cover most use cases. /// /// [`UserID`s]: crate::packet::UserID /// [`UserAttribute`s]: crate::packet::user_attribute::UserAttribute /// [`Key`s]: crate::packet::Key /// /// # Security considerations /// /// ## Expiration /// /// There are two ways to invalidate cryptographic key material: /// revocation and freshness. Both variants come with their own /// challenges. Revocations rely on a robust channel to update /// certificates (and attackers may interfere with that). /// /// On the other hand, freshness involves creating key material that /// expires after a certain time, then periodically extending the /// expiration time. Again, consumers need a way to update /// certificates, but should that fail (maybe because it was /// interfered with), the consumer errs on the side of no longer /// trusting that key material. /// /// Because of the way metadata is added to OpenPGP certificates, /// attackers who control the certificate lookup and update mechanism /// may strip components like signatures from the certificate. This /// has implications for the robustness of relying on freshness. /// /// If you first create a certificate that does not expire, and then /// change your mind and set an expiration time, an attacker can /// simply strip off that update, yielding the original certificate /// that does not expire. /// /// Hence, to ensure robust key expiration, you must set an expiration /// with [`CertBuilder::set_validity_period`] when you create the /// certificate. /// /// By default, the `CertBuilder` creates certificates that do not /// expire, because the expiration time is a policy decision and /// depends on the use case. For general purpose certificates, /// [`CertBuilder::general_purpose`] sets the validity period to /// roughly three years. /// /// # Examples /// /// Generate a general-purpose certificate with one User ID: /// /// ``` /// use sequoia_openpgp as openpgp; /// use openpgp::cert::prelude::*; /// /// # fn main() -> openpgp::Result<()> { /// let (cert, rev) = /// CertBuilder::general_purpose(Some("alice@example.org")) /// .generate()?; /// # Ok(()) /// # } /// ``` pub struct CertBuilder<'a> { creation_time: Option<std::time::SystemTime>, ciphersuite: CipherSuite, profile: Profile, /// Advertised features. features: Features, primary: KeyBlueprint, subkeys: Vec<(Option<SignatureBuilder>, KeyBlueprint)>, userids: Vec<(Option<SignatureBuilder>, packet::UserID)>, user_attributes: Vec<(Option<SignatureBuilder>, packet::UserAttribute)>, password: Option<Password>, revocation_keys: Option<Vec<RevocationKey>>, exportable: bool, phantom: PhantomData<&'a ()>, } assert_send_and_sync!(CertBuilder<'_>); impl CertBuilder<'_> { /// Returns a new `CertBuilder`. /// /// The returned builder is configured to generate a minimal /// OpenPGP certificate, a certificate with just a /// certification-capable primary key. You'll typically want to /// add at least one User ID (using /// [`CertBuilder::add_userid`]). and some subkeys (using /// [`CertBuilder::add_signing_subkey`], /// [`CertBuilder::add_transport_encryption_subkey`], etc.). /// /// By default, the generated certificate does not expire. It is /// recommended to set a suitable validity period using /// [`CertBuilder::set_validity_period`]. See [this /// section](CertBuilder#expiration) of the type's documentation /// for security considerations of key expiration. /// /// # Examples /// /// ``` /// use sequoia_openpgp as openpgp; /// use openpgp::cert::prelude::*; /// /// # fn main() -> openpgp::Result<()> { /// let (cert, rev) = /// CertBuilder::new() /// .add_userid("Alice Lovelace <alice@lovelace.name>") /// .add_signing_subkey() /// .add_transport_encryption_subkey() /// .add_storage_encryption_subkey() /// .generate()?; /// # assert_eq!(cert.keys().count(), 1 + 3); /// # assert_eq!(cert.userids().count(), 1); /// # assert_eq!(cert.user_attributes().count(), 0); /// # Ok(()) /// # } /// ``` pub fn new() -> Self { CertBuilder { creation_time: None, ciphersuite: CipherSuite::default(), profile: Default::default(), features: Features::sequoia(), primary: KeyBlueprint{ flags: KeyFlags::empty().set_certification(), validity: None, ciphersuite: None, }, subkeys: vec![], userids: vec![], user_attributes: vec![], password: None, revocation_keys: None, exportable: true, phantom: PhantomData, } } /// Generates a general-purpose certificate. /// /// The returned builder is set to generate a certificate with a /// certification-capable primary key, a signing-capable subkey, /// and an encryption-capable subkey. The encryption subkey is /// marked as being appropriate for both data in transit and data /// at rest. /// /// The certificate and all subkeys are valid for approximately /// three years. /// /// # Examples /// /// ``` /// use sequoia_openpgp as openpgp; /// use openpgp::cert::prelude::*; /// /// # fn main() -> openpgp::Result<()> { /// let (cert, rev) = /// CertBuilder::general_purpose(["Alice Lovelace", "<alice@example.org>"]) /// .generate()?; /// # assert_eq!(cert.keys().count(), 3); /// # assert_eq!(cert.userids().count(), 2); /// /// let (cert, rev) = /// CertBuilder::general_purpose(["Alice Lovelace <alice@example.org>"]) /// .generate()?; /// # assert_eq!(cert.keys().count(), 3); /// # assert_eq!(cert.userids().count(), 1); /// # Ok(()) /// # } /// ``` pub fn general_purpose<U>(userids: U) -> Self where U: IntoIterator, U::Item: Into<packet::UserID>, { let mut builder = Self::new() .set_primary_key_flags(KeyFlags::empty().set_certification()) .set_validity_period( time::Duration::new(3 * 52 * 7 * 24 * 60 * 60, 0)) .add_signing_subkey() .add_subkey(KeyFlags::empty() .set_transport_encryption() .set_storage_encryption(), None, None); for u in userids { builder = builder.add_userid(u.into()); } builder } /// Sets the creation time. /// /// If `creation_time` is not `None`, this causes the /// `CertBuilder` to use that time when [`CertBuilder::generate`] /// is called. If it is `None`, the default, then the current /// time minus 60 seconds is used as creation time. Backdating /// the certificate by a minute has the advantage that the /// certificate can immediately be customized: /// /// In order to reliably override a binding signature, the /// overriding binding signature must be newer than the existing /// signature. If, however, the existing signature is created /// `now`, any newer signature must have a future creation time, /// and is considered invalid by Sequoia. To avoid this, we /// backdate certificate creation times (and hence binding /// signature creation times), so that there is "space" between /// the creation time and now for signature updates. /// /// Warning: this function takes a [`SystemTime`]. A `SystemTime` /// has a higher resolution, and a larger range than an OpenPGP /// [`Timestamp`]. Assuming the `creation_time` is in range, it /// will automatically be truncated to the nearest time that is /// representable by a `Timestamp`. If it is not in range, /// [`generate`] will return an error. /// /// [`CertBuilder::generate`]: CertBuilder::generate() /// [`SystemTime`]: std::time::SystemTime /// [`Timestamp`]: crate::types::Timestamp /// [`generate`]: CertBuilder::generate() /// /// # Examples /// /// Generate a backdated certificate: /// /// ``` /// use std::time::{SystemTime, Duration}; /// use std::convert::TryFrom; /// /// use sequoia_openpgp as openpgp; /// use openpgp::cert::prelude::*; /// use openpgp::types::Timestamp; /// /// # fn main() -> openpgp::Result<()> { /// let t = SystemTime::now() - Duration::from_secs(365 * 24 * 60 * 60); /// // Roundtrip the time so that the assert below works. /// let t = SystemTime::from(Timestamp::try_from(t)?); /// /// let (cert, rev) = /// CertBuilder::general_purpose(Some("Alice Lovelace <alice@example.org>")) /// .set_creation_time(t) /// .generate()?; /// assert_eq!(cert.primary_key().self_signatures().nth(0).unwrap() /// .signature_creation_time(), /// Some(t)); /// # Ok(()) /// # } /// ``` pub fn set_creation_time<T>(mut self, creation_time: T) -> Self where T: Into<Option<std::time::SystemTime>>, { self.creation_time = creation_time.into(); self } /// Returns the configured creation time, if any. /// /// # Examples /// /// ``` /// use std::time::SystemTime; /// /// use sequoia_openpgp as openpgp; /// use openpgp::cert::prelude::*; /// /// # fn main() -> openpgp::Result<()> { /// let mut builder = CertBuilder::new(); /// assert!(builder.creation_time().is_none()); /// /// let now = std::time::SystemTime::now(); /// builder = builder.set_creation_time(Some(now)); /// assert_eq!(builder.creation_time(), Some(now)); /// /// builder = builder.set_creation_time(None); /// assert!(builder.creation_time().is_none()); /// # Ok(()) /// # } /// ``` pub fn creation_time(&self) -> Option<std::time::SystemTime> { self.creation_time } /// Sets the default asymmetric algorithms. /// /// This method controls the set of algorithms that is used to /// generate the certificate's keys. /// /// # Examples /// /// ``` /// use sequoia_openpgp as openpgp; /// use openpgp::cert::prelude::*; /// use openpgp::types::PublicKeyAlgorithm; /// /// # fn main() -> openpgp::Result<()> { /// let (ecc, _) = /// CertBuilder::general_purpose(Some("alice@example.org")) /// .set_cipher_suite(CipherSuite::Cv25519) /// .generate()?; /// assert_eq!(ecc.primary_key().key().pk_algo(), PublicKeyAlgorithm::EdDSA); /// /// let (rsa, _) = /// CertBuilder::general_purpose(Some("alice@example.org")) /// .set_cipher_suite(CipherSuite::RSA2k) /// .generate()?; /// assert_eq!(rsa.primary_key().key().pk_algo(), PublicKeyAlgorithm::RSAEncryptSign); /// # Ok(()) /// # } /// ``` pub fn set_cipher_suite(mut self, cs: CipherSuite) -> Self { self.ciphersuite = cs; self } /// Sets whether the certificate is exportable. /// /// This method controls whether the certificate is exportable. /// If the certificate builder is configured to make a /// non-exportable certificate, then all the signatures that it /// creates include the [Exportable Certification] subpacket /// that is set to `false`. /// /// [Exportable Certification]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.19 /// /// # Examples /// /// When exporting a non-exportable certificate, nothing will be /// exported. This is also the case when the output is ASCII /// armored. /// /// ``` /// use sequoia_openpgp as openpgp; /// use openpgp::Result; /// use openpgp::cert::prelude::*; /// use openpgp::parse::Parse; /// use openpgp::serialize::Serialize; /// /// # fn main() -> openpgp::Result<()> { /// let (cert, _) = /// CertBuilder::general_purpose(Some("alice@example.org")) /// .set_exportable(false) /// .generate()?; /// let mut exported = Vec::new(); /// cert.armored().export(&mut exported)?; /// /// let certs = CertParser::from_bytes(&exported)? /// .collect::<Result<Vec<Cert>>>()?; /// assert_eq!(certs.len(), 0); /// assert_eq!(exported.len(), 0, "{}", String::from_utf8_lossy(&exported)); /// # Ok(()) /// # } /// ``` pub fn set_exportable(mut self, exportable: bool) -> Self { self.exportable = exportable; self } /// Sets the version of OpenPGP to generate keys for. /// /// Supported are [`Profile::RFC9580`] which will generate version /// 6 keys, and [`Profile::RFC4880`] which will version 4 keys. /// By default, we generate version 6 keys. /// /// # Examples /// /// ``` /// use sequoia_openpgp as openpgp; /// use openpgp::cert::prelude::*; /// /// # fn main() -> openpgp::Result<()> { /// let (key, _) = /// CertBuilder::general_purpose(Some("alice@example.org")) /// .set_profile(openpgp::Profile::RFC9580)? /// .generate()?; /// assert_eq!(key.primary_key().key().version(), 6); /// /// let (key, _) = /// CertBuilder::general_purpose(Some("bob@example.org")) /// .set_profile(openpgp::Profile::RFC4880)? /// .generate()?; /// assert_eq!(key.primary_key().key().version(), 4); /// # Ok(()) /// # } /// ``` pub fn set_profile(mut self, profile: Profile) -> Result<Self> { self.profile = profile; Ok(self) } /// Sets the features the certificate will advertise. /// /// # Examples /// /// ``` /// use sequoia_openpgp as openpgp; /// use openpgp::cert::prelude::*; /// use openpgp::types::Features; /// /// # fn main() -> openpgp::Result<()> { /// let (key, _) = /// CertBuilder::general_purpose(Some("alice@example.org")) /// .set_profile(openpgp::Profile::RFC9580)? /// .generate()?; /// assert_eq!(key.primary_key().key().version(), 6); /// /// // Bob is old-school: he needs this key to work on an old /// // phone. Therefore, he doesn't want to get SEIPDv2 messages, /// // which his phone doesn't handle. He advertises support for /// // SEIPDv1 only. /// let (key, _) = /// CertBuilder::general_purpose(Some("bob@example.org")) /// .set_profile(openpgp::Profile::RFC4880)? /// .set_features(Features::empty().set_seipdv1())? /// .generate()?; /// assert_eq!(key.primary_key().key().version(), 4); /// # Ok(()) /// # } /// ``` pub fn set_features(mut self, features: Features) -> Result<Self> { self.features = features; Ok(self) } /// Adds a User ID. /// /// Adds a User ID to the certificate. The first User ID that is /// added, whether via this interface or another interface, e.g., /// [`CertBuilder::general_purpose`], will have the [primary User /// ID flag] set. /// /// [`CertBuilder::general_purpose`]: CertBuilder::general_purpose() /// [primary User ID flag]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.27 /// /// # Examples /// /// ``` /// use sequoia_openpgp as openpgp; /// use openpgp::cert::prelude::*; /// use openpgp::packet::prelude::*; /// use openpgp::policy::StandardPolicy; /// /// # fn main() -> openpgp::Result<()> { /// let p = &StandardPolicy::new(); /// /// let (cert, rev) = /// CertBuilder::general_purpose(Some("Alice Lovelace <alice@example.org>")) /// .add_userid("Alice Lovelace <alice@lovelace.name>") /// .generate()?; /// /// assert_eq!(cert.userids().count(), 2); /// let mut userids = cert.with_policy(p, None)?.userids().collect::<Vec<_>>(); /// // Sort lexicographically. /// userids.sort_by(|a, b| a.userid().value().cmp(b.userid().value())); /// assert_eq!(userids[0].userid(), /// &UserID::from("Alice Lovelace <alice@example.org>")); /// assert_eq!(userids[1].userid(), /// &UserID::from("Alice Lovelace <alice@lovelace.name>")); /// /// /// assert_eq!(userids[0].binding_signature().primary_userid().unwrap_or(false), true); /// assert_eq!(userids[1].binding_signature().primary_userid().unwrap_or(false), false); /// # Ok(()) /// # } /// ``` pub fn add_userid<U>(mut self, uid: U) -> Self where U: Into<packet::UserID> { self.userids.push((None, uid.into())); self } /// Adds a User ID with a binding signature based on `builder`. /// /// Adds a User ID to the certificate, creating the binding /// signature using `builder`. The `builder`s signature type must /// be a certification signature (i.e. either /// [`GenericCertification`], [`PersonaCertification`], /// [`CasualCertification`], or [`PositiveCertification`]). /// /// The key generation step uses `builder` as a template, but /// tweaks it so the signature is a valid binding signature. If /// you need more control, consider using /// [`UserID::bind`](crate::packet::UserID::bind). /// /// The following modifications are performed on `builder`: /// /// - An appropriate hash algorithm is selected. /// /// - The creation time is set. /// /// - Primary key metadata is added (key flags, key validity period). /// /// - Certificate metadata is added (feature flags, algorithm /// preferences). /// /// - The [`CertBuilder`] marks exactly one User ID or User /// Attribute as primary: The first one provided to /// [`CertBuilder::add_userid_with`] or /// [`CertBuilder::add_user_attribute_with`] (the UserID takes /// precedence) that is marked as primary, or the first User /// ID or User Attribute added to the [`CertBuilder`]. /// /// [`GenericCertification`]: crate::types::SignatureType::GenericCertification /// [`PersonaCertification`]: crate::types::SignatureType::PersonaCertification /// [`CasualCertification`]: crate::types::SignatureType::CasualCertification /// [`PositiveCertification`]: crate::types::SignatureType::PositiveCertification /// [primary User ID flag]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.27 /// /// # Examples /// /// This example very casually binds a User ID to a certificate. /// /// ``` /// # fn main() -> sequoia_openpgp::Result<()> { /// # use sequoia_openpgp as openpgp; /// # use openpgp::cert::prelude::*; /// # use openpgp::packet::{prelude::*, signature::subpacket::*}; /// # use openpgp::policy::StandardPolicy; /// # use openpgp::types::*; /// # let policy = &StandardPolicy::new(); /// # /// let (cert, revocation_cert) = /// CertBuilder::general_purpose( /// Some("Alice Lovelace <alice@example.org>")) /// .add_userid_with( /// "trinity", /// SignatureBuilder::new(SignatureType::CasualCertification) /// .set_notation("rabbit@example.org", b"follow me", /// NotationDataFlags::empty().set_human_readable(), /// false)?)? /// .generate()?; /// /// assert_eq!(cert.userids().count(), 2); /// let mut userids = cert.with_policy(policy, None)?.userids().collect::<Vec<_>>(); /// // Sort lexicographically. /// userids.sort_by(|a, b| a.userid().value().cmp(b.userid().value())); /// assert_eq!(userids[0].userid(), /// &UserID::from("Alice Lovelace <alice@example.org>")); /// assert_eq!(userids[1].userid(), /// &UserID::from("trinity")); /// /// assert!(userids[0].binding_signature().primary_userid().unwrap_or(false)); /// assert!(! userids[1].binding_signature().primary_userid().unwrap_or(false)); /// assert_eq!(userids[1].binding_signature().notation("rabbit@example.org") /// .next().unwrap(), b"follow me"); /// # Ok(()) } /// ``` pub fn add_userid_with<U, B>(mut self, uid: U, builder: B) -> Result<Self> where U: Into<packet::UserID>, B: Into<SignatureBuilder>, { let builder = builder.into(); match builder.typ() { SignatureType::GenericCertification | SignatureType::PersonaCertification | SignatureType::CasualCertification | SignatureType::PositiveCertification => { self.userids.push((Some(builder), uid.into())); Ok(self) }, t => Err(Error::InvalidArgument(format!( "Signature type is not a certification: {}", t)).into()), } } /// Adds a new User Attribute. /// /// Adds a User Attribute to the certificate. If there are no /// User IDs, the first User attribute that is added, whether via /// this interface or another interface, will have the [primary /// User ID flag] set. /// /// [primary User ID flag]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.27 /// /// # Examples /// /// When there are no User IDs, the first User Attribute has the /// primary User ID flag set: /// /// ``` /// # use openpgp::packet::user_attribute::Subpacket; /// use sequoia_openpgp as openpgp; /// use openpgp::cert::prelude::*; /// use openpgp::packet::prelude::*; /// use openpgp::policy::StandardPolicy; /// /// # fn main() -> openpgp::Result<()> { /// let p = &StandardPolicy::new(); /// # /// # // Create some user attribute. Doctests do not pass cfg(test), /// # // so UserAttribute::arbitrary is not available /// # let sp = Subpacket::Unknown(7, vec![7; 7].into_boxed_slice()); /// # let user_attribute = UserAttribute::new(&[sp])?; /// /// let (cert, rev) = /// CertBuilder::new() /// .add_user_attribute(user_attribute) /// .generate()?; /// /// assert_eq!(cert.userids().count(), 0); /// assert_eq!(cert.user_attributes().count(), 1); /// let mut uas = cert.with_policy(p, None)?.user_attributes().collect::<Vec<_>>(); /// assert_eq!(uas[0].binding_signature().primary_userid().unwrap_or(false), true); /// # Ok(()) /// # } /// ``` /// /// Where there are User IDs, then the primary User ID flag is not /// set: /// /// ``` /// # use openpgp::packet::user_attribute::Subpacket; /// use sequoia_openpgp as openpgp; /// use openpgp::cert::prelude::*; /// use openpgp::packet::prelude::*; /// use openpgp::policy::StandardPolicy; /// /// # fn main() -> openpgp::Result<()> { /// let p = &StandardPolicy::new(); /// # /// # // Create some user attribute. Doctests do not pass cfg(test), /// # // so UserAttribute::arbitrary is not available /// # let sp = Subpacket::Unknown(7, vec![7; 7].into_boxed_slice()); /// # let user_attribute = UserAttribute::new(&[sp])?; /// /// let (cert, rev) = /// CertBuilder::new() /// .add_userid("alice@example.org") /// .add_user_attribute(user_attribute) /// .generate()?; /// /// assert_eq!(cert.userids().count(), 1); /// assert_eq!(cert.user_attributes().count(), 1); /// let mut uas = cert.with_policy(p, None)?.user_attributes().collect::<Vec<_>>(); /// assert_eq!(uas[0].binding_signature().primary_userid().unwrap_or(false), false); /// # Ok(()) /// # } /// ``` pub fn add_user_attribute<U>(mut self, ua: U) -> Self where U: Into<packet::UserAttribute> { self.user_attributes.push((None, ua.into())); self } /// Adds a User Attribute with a binding signature based on `builder`. /// /// Adds a User Attribute to the certificate, creating the binding /// signature using `builder`. The `builder`s signature type must /// be a certification signature (i.e. either /// [`GenericCertification`], [`PersonaCertification`], /// [`CasualCertification`], or [`PositiveCertification`]). /// /// The key generation step uses `builder` as a template, but /// tweaks it so the signature is a valid binding signature. If /// you need more control, consider using /// [`UserAttribute::bind`](crate::packet::UserAttribute::bind). /// /// The following modifications are performed on `builder`: /// /// - An appropriate hash algorithm is selected. /// /// - The creation time is set. /// /// - Primary key metadata is added (key flags, key validity period). /// /// - Certificate metadata is added (feature flags, algorithm /// preferences). /// /// - The [`CertBuilder`] marks exactly one User ID or User /// Attribute as primary: The first one provided to /// [`CertBuilder::add_userid_with`] or /// [`CertBuilder::add_user_attribute_with`] (the UserID takes /// precedence) that is marked as primary, or the first User /// ID or User Attribute added to the [`CertBuilder`]. /// /// [`GenericCertification`]: crate::types::SignatureType::GenericCertification /// [`PersonaCertification`]: crate::types::SignatureType::PersonaCertification /// [`CasualCertification`]: crate::types::SignatureType::CasualCertification /// [`PositiveCertification`]: crate::types::SignatureType::PositiveCertification /// [primary User ID flag]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.27 /// /// # Examples /// /// This example very casually binds a user attribute to a /// certificate. /// /// ``` /// # fn main() -> sequoia_openpgp::Result<()> { /// # use sequoia_openpgp as openpgp; /// # use openpgp::packet::user_attribute::Subpacket; /// # use openpgp::cert::prelude::*; /// # use openpgp::packet::{prelude::*, signature::subpacket::*}; /// # use openpgp::policy::StandardPolicy; /// # use openpgp::types::*; /// # /// # let policy = &StandardPolicy::new(); /// # /// # // Create some user attribute. Doctests do not pass cfg(test), /// # // so UserAttribute::arbitrary is not available /// # let user_attribute = /// # UserAttribute::new(&[Subpacket::Unknown(7, vec![7; 7].into())])?; /// let (cert, revocation_cert) = /// CertBuilder::general_purpose( /// Some("Alice Lovelace <alice@example.org>")) /// .add_user_attribute_with( /// user_attribute, /// SignatureBuilder::new(SignatureType::CasualCertification) /// .set_notation("rabbit@example.org", b"follow me", /// NotationDataFlags::empty().set_human_readable(), /// false)?)? /// .generate()?; /// /// let uas = cert.with_policy(policy, None)?.user_attributes().collect::<Vec<_>>(); /// assert_eq!(uas.len(), 1); /// assert!(! uas[0].binding_signature().primary_userid().unwrap_or(false)); /// assert_eq!(uas[0].binding_signature().notation("rabbit@example.org") /// .next().unwrap(), b"follow me"); /// # Ok(()) } /// ``` pub fn add_user_attribute_with<U, B>(mut self, ua: U, builder: B) -> Result<Self> where U: Into<packet::UserAttribute>, B: Into<SignatureBuilder>, { let builder = builder.into(); match builder.typ() { SignatureType::GenericCertification | SignatureType::PersonaCertification | SignatureType::CasualCertification | SignatureType::PositiveCertification => { self.user_attributes.push((Some(builder), ua.into())); Ok(self) }, t => Err(Error::InvalidArgument(format!( "Signature type is not a certification: {}", t)).into()), } } /// Adds a signing-capable subkey. /// /// The key uses the default cipher suite (see /// [`CertBuilder::set_cipher_suite`]), and is not set to expire. /// Use [`CertBuilder::add_subkey`] if you need to change these /// parameters. /// /// [`CertBuilder::set_cipher_suite`]: CertBuilder::set_cipher_suite() /// [`CertBuilder::add_subkey`]: CertBuilder::add_subkey() /// /// # Examples /// /// ``` /// use sequoia_openpgp as openpgp; /// use openpgp::cert::prelude::*; /// use openpgp::policy::StandardPolicy; /// use openpgp::types::KeyFlags; /// /// # fn main() -> openpgp::Result<()> { /// let p = &StandardPolicy::new(); /// /// let (cert, rev) = /// CertBuilder::new() /// .add_signing_subkey() /// .generate()?; /// /// assert_eq!(cert.keys().count(), 2); /// let ka = cert.with_policy(p, None)?.keys().nth(1).unwrap(); /// assert_eq!(ka.key_flags(), /// Some(KeyFlags::empty().set_signing())); /// # Ok(()) /// # } /// ``` pub fn add_signing_subkey(self) -> Self { self.add_subkey(KeyFlags::empty().set_signing(), None, None) } /// Adds a subkey suitable for transport encryption. /// /// The key uses the default cipher suite (see /// [`CertBuilder::set_cipher_suite`]), and is not set to expire. /// Use [`CertBuilder::add_subkey`] if you need to change these /// parameters. /// /// [`CertBuilder::set_cipher_suite`]: CertBuilder::set_cipher_suite() /// [`CertBuilder::add_subkey`]: CertBuilder::add_subkey() /// /// /// # Examples /// /// ``` /// use sequoia_openpgp as openpgp; /// use openpgp::cert::prelude::*; /// use openpgp::policy::StandardPolicy; /// use openpgp::types::KeyFlags; /// /// # fn main() -> openpgp::Result<()> { /// let p = &StandardPolicy::new(); /// /// let (cert, rev) = /// CertBuilder::new() /// .add_transport_encryption_subkey() /// .generate()?; /// /// assert_eq!(cert.keys().count(), 2); /// let ka = cert.with_policy(p, None)?.keys().nth(1).unwrap(); /// assert_eq!(ka.key_flags(), /// Some(KeyFlags::empty().set_transport_encryption())); /// # Ok(()) /// # } /// ``` pub fn add_transport_encryption_subkey(self) -> Self { self.add_subkey(KeyFlags::empty().set_transport_encryption(), None, None) } /// Adds a subkey suitable for storage encryption. /// /// The key uses the default cipher suite (see /// [`CertBuilder::set_cipher_suite`]), and is not set to expire. /// Use [`CertBuilder::add_subkey`] if you need to change these /// parameters. /// /// [`CertBuilder::set_cipher_suite`]: CertBuilder::set_cipher_suite() /// [`CertBuilder::add_subkey`]: CertBuilder::add_subkey() /// /// /// # Examples /// /// ``` /// use sequoia_openpgp as openpgp; /// use openpgp::cert::prelude::*; /// use openpgp::policy::StandardPolicy; /// use openpgp::types::KeyFlags; /// /// # fn main() -> openpgp::Result<()> { /// let p = &StandardPolicy::new(); /// /// let (cert, rev) = /// CertBuilder::new() /// .add_storage_encryption_subkey() /// .generate()?; /// /// assert_eq!(cert.keys().count(), 2); /// let ka = cert.with_policy(p, None)?.keys().nth(1).unwrap(); /// assert_eq!(ka.key_flags(), /// Some(KeyFlags::empty().set_storage_encryption())); /// # Ok(()) /// # } /// ``` pub fn add_storage_encryption_subkey(self) -> Self { self.add_subkey(KeyFlags::empty().set_storage_encryption(), None, None) } /// Adds a certification-capable subkey. /// /// The key uses the default cipher suite (see /// [`CertBuilder::set_cipher_suite`]), and is not set to expire. /// Use [`CertBuilder::add_subkey`] if you need to change these /// parameters. /// /// [`CertBuilder::set_cipher_suite`]: CertBuilder::set_cipher_suite() /// [`CertBuilder::add_subkey`]: CertBuilder::add_subkey() /// /// /// # Examples /// /// ``` /// use sequoia_openpgp as openpgp; /// use openpgp::cert::prelude::*; /// use openpgp::policy::StandardPolicy; /// use openpgp::types::KeyFlags; /// /// # fn main() -> openpgp::Result<()> { /// let p = &StandardPolicy::new(); /// /// let (cert, rev) = /// CertBuilder::new() /// .add_certification_subkey() /// .generate()?; /// /// assert_eq!(cert.keys().count(), 2); /// let ka = cert.with_policy(p, None)?.keys().nth(1).unwrap(); /// assert_eq!(ka.key_flags(), /// Some(KeyFlags::empty().set_certification())); /// # Ok(()) /// # } /// ``` pub fn add_certification_subkey(self) -> Self { self.add_subkey(KeyFlags::empty().set_certification(), None, None) } /// Adds an authentication-capable subkey. /// /// The key uses the default cipher suite (see /// [`CertBuilder::set_cipher_suite`]), and is not set to expire. /// Use [`CertBuilder::add_subkey`] if you need to change these /// parameters. /// /// [`CertBuilder::set_cipher_suite`]: CertBuilder::set_cipher_suite() /// [`CertBuilder::add_subkey`]: CertBuilder::add_subkey() /// /// /// # Examples /// /// ``` /// use sequoia_openpgp as openpgp; /// use openpgp::cert::prelude::*; /// use openpgp::policy::StandardPolicy; /// use openpgp::types::KeyFlags; /// /// # fn main() -> openpgp::Result<()> { /// let p = &StandardPolicy::new(); /// /// let (cert, rev) = /// CertBuilder::new() /// .add_authentication_subkey() /// .generate()?; /// /// assert_eq!(cert.keys().count(), 2); /// let ka = cert.with_policy(p, None)?.keys().nth(1).unwrap(); /// assert_eq!(ka.key_flags(), /// Some(KeyFlags::empty().set_authentication())); /// # Ok(()) /// # } /// ``` pub fn add_authentication_subkey(self) -> Self { self.add_subkey(KeyFlags::empty().set_authentication(), None, None) } /// Adds a custom subkey. /// /// If `validity` is `None`, the subkey will be valid for the same /// period as the primary key. /// /// Likewise, if `cs` is `None`, the same cipher suite is used as /// for the primary key. /// /// # Examples /// /// Generates a certificate with an encryption subkey that is for /// protecting *both* data in transit and data at rest, and /// expires at a different time from the primary key: /// /// ``` /// use sequoia_openpgp as openpgp; /// # use openpgp::Result; /// use openpgp::cert::prelude::*; /// use openpgp::policy::StandardPolicy; /// use openpgp::types::KeyFlags; /// /// # fn main() -> Result<()> { /// let p = &StandardPolicy::new(); /// /// let now = std::time::SystemTime::now(); /// let y = std::time::Duration::new(365 * 24 * 60 * 60, 0); /// /// // Make the certificate expire in 2 years, and the subkey /// // expire in a year. /// let (cert,_) = CertBuilder::new() /// .set_creation_time(now) /// .set_validity_period(2 * y) /// .add_subkey(KeyFlags::empty() /// .set_storage_encryption() /// .set_transport_encryption(), /// y, /// None) /// .generate()?; /// /// assert_eq!(cert.with_policy(p, now)?.keys().alive().count(), 2); /// assert_eq!(cert.with_policy(p, now + y)?.keys().alive().count(), 1); /// assert_eq!(cert.with_policy(p, now + 2 * y)?.keys().alive().count(), 0); /// /// let ka = cert.with_policy(p, None)?.keys().nth(1).unwrap(); /// assert_eq!(ka.key_flags(), /// Some(KeyFlags::empty() /// .set_storage_encryption() /// .set_transport_encryption())); /// # Ok(()) } /// ``` pub fn add_subkey<T, C>(mut self, flags: KeyFlags, validity: T, cs: C) -> Self where T: Into<Option<time::Duration>>, C: Into<Option<CipherSuite>>, { self.subkeys.push((None, KeyBlueprint { flags, validity: validity.into(), ciphersuite: cs.into(), })); self } /// Adds a subkey with a binding signature based on `builder`. /// /// Adds a subkey to the certificate, creating the binding /// signature using `builder`. The `builder`s signature type must /// be [`SubkeyBinding`]. /// /// The key generation step uses `builder` as a template, but adds /// all subpackets that the signature needs to be a valid binding /// signature. If you need more control, or want to adopt /// existing keys, consider using /// [`Key::bind`](crate::packet::Key::bind). /// /// The following modifications are performed on `builder`: /// /// - An appropriate hash algorithm is selected. /// /// - The creation time is set. /// /// - Key metadata is added (key flags, key validity period). /// /// [`SubkeyBinding`]: crate::types::SignatureType::SubkeyBinding /// /// If `validity` is `None`, the subkey will be valid for the same /// period as the primary key. /// /// # Examples /// /// This example binds a signing subkey to a certificate, /// restricting its use to authentication of software. /// /// ``` /// # fn main() -> sequoia_openpgp::Result<()> { /// # use sequoia_openpgp as openpgp; /// # use openpgp::packet::user_attribute::Subpacket; /// # use openpgp::cert::prelude::*; /// # use openpgp::packet::{prelude::*, signature::subpacket::*}; /// # use openpgp::policy::StandardPolicy; /// # use openpgp::types::*; /// let (cert, revocation_cert) = /// CertBuilder::general_purpose( /// Some("Alice Lovelace <alice@example.org>")) /// .add_subkey_with( /// KeyFlags::empty().set_signing(), None, None, /// SignatureBuilder::new(SignatureType::SubkeyBinding) /// // Add a critical notation! /// .set_notation("code-signing@policy.example.org", b"", /// NotationDataFlags::empty(), true)?)? /// .generate()?; /// /// // Under the standard policy, the additional signing subkey /// // is not bound. /// let p = StandardPolicy::new(); /// assert_eq!(cert.with_policy(&p, None)?.keys().for_signing().count(), 1); /// /// // However, software implementing the notation see the additional /// // signing subkey. /// let mut p = StandardPolicy::new(); /// p.good_critical_notations(&["code-signing@policy.example.org"]); /// assert_eq!(cert.with_policy(&p, None)?.keys().for_signing().count(), 2); /// # Ok(()) } /// ``` pub fn add_subkey_with<T, C, B>(mut self, flags: KeyFlags, validity: T, cs: C, builder: B) -> Result<Self> where T: Into<Option<time::Duration>>, C: Into<Option<CipherSuite>>, B: Into<SignatureBuilder>, { let builder = builder.into(); match builder.typ() { SignatureType::SubkeyBinding => { self.subkeys.push((Some(builder), KeyBlueprint { flags, validity: validity.into(), ciphersuite: cs.into(), })); Ok(self) }, t => Err(Error::InvalidArgument(format!( "Signature type is not a subkey binding: {}", t)).into()), } } /// Sets the primary key's key flags. /// /// By default, the primary key is set to only be certification /// capable. This allows the caller to set additional flags. /// /// # Examples /// /// Makes the primary key signing-capable but not /// certification-capable. /// /// ``` /// use sequoia_openpgp as openpgp; /// # use openpgp::Result; /// use openpgp::cert::prelude::*; /// use openpgp::policy::StandardPolicy; /// use openpgp::types::KeyFlags; /// /// # fn main() -> Result<()> { /// let p = &StandardPolicy::new(); /// /// let (cert, rev) = /// CertBuilder::general_purpose(Some("Alice Lovelace <alice@example.org>")) /// .set_primary_key_flags(KeyFlags::empty().set_signing()) /// .generate()?; /// /// // Observe that the primary key's certification capability is /// // set implicitly. /// assert_eq!(cert.with_policy(p, None)?.primary_key().key_flags(), /// Some(KeyFlags::empty().set_signing())); /// # Ok(()) } /// ``` pub fn set_primary_key_flags(mut self, flags: KeyFlags) -> Self { self.primary.flags = flags; self } /// Sets a password to encrypt the secret keys with. /// /// The password is used to encrypt all secret key material. /// /// # Examples /// /// ``` /// use sequoia_openpgp as openpgp; /// # use openpgp::Result; /// use openpgp::cert::prelude::*; /// /// # fn main() -> Result<()> { /// // Make the certificate expire in 10 minutes. /// let (cert, rev) = /// CertBuilder::general_purpose(Some("Alice Lovelace <alice@example.org>")) /// .set_password(Some("1234".into())) /// .generate()?; /// /// for ka in cert.keys() { /// assert!(ka.key().has_secret()); /// } /// # Ok(()) } /// ``` pub fn set_password(mut self, password: Option<Password>) -> Self { self.password = password; self } /// Sets the certificate's validity period. /// /// The determines how long the certificate is valid. That is, /// after the validity period, the certificate is considered to be /// expired. /// /// The validity period starts with the creation time (see /// [`CertBuilder::set_creation_time`]). /// /// A value of `None` means that the certificate never expires. /// /// See [this section](CertBuilder#expiration) of the type's /// documentation for security considerations of key expiration. /// /// # Examples /// /// ``` /// use sequoia_openpgp as openpgp; /// # use openpgp::Result; /// use openpgp::cert::prelude::*; /// use openpgp::policy::StandardPolicy; /// use openpgp::types::RevocationKey; /// /// # fn main() -> Result<()> { /// let p = &StandardPolicy::new(); /// /// let now = std::time::SystemTime::now(); /// let s = std::time::Duration::new(1, 0); /// /// // Make the certificate expire in 10 minutes. /// let (cert,_) = CertBuilder::new() /// .set_creation_time(now) /// .set_validity_period(600 * s) /// .generate()?; /// /// assert!(cert.with_policy(p, now)?.primary_key().alive().is_ok()); /// assert!(cert.with_policy(p, now + 599 * s)?.primary_key().alive().is_ok()); /// assert!(cert.with_policy(p, now + 600 * s)?.primary_key().alive().is_err()); /// # Ok(()) } /// ``` pub fn set_validity_period<T>(mut self, validity: T) -> Self where T: Into<Option<time::Duration>> { self.primary.validity = validity.into(); self } /// Sets designated revokers. /// /// Adds designated revokers to the primary key. This allows the /// designated revoker to issue revocation certificates on behalf /// of the primary key. /// /// # Examples /// /// Make Alice a designated revoker for Bob: /// /// ``` /// use sequoia_openpgp as openpgp; /// # use openpgp::Result; /// use openpgp::cert::prelude::*; /// use openpgp::policy::StandardPolicy; /// use openpgp::types::RevocationKey; /// /// # fn main() -> Result<()> { /// let p = &StandardPolicy::new(); /// /// let (alice, _) = /// CertBuilder::general_purpose(Some("alice@example.org")) /// .generate()?; /// let (bob, _) = /// CertBuilder::general_purpose(Some("bob@example.org")) /// .set_revocation_keys(vec![(&alice).into()]) /// .generate()?; /// /// // Make sure Alice is listed as a designated revoker for Bob. /// assert_eq!(bob.revocation_keys(p).collect::<Vec<&RevocationKey>>(), /// vec![&(&alice).into()]); /// # Ok(()) } /// ``` pub fn set_revocation_keys(mut self, revocation_keys: Vec<RevocationKey>) -> Self { self.revocation_keys = Some(revocation_keys); self } /// Generates a certificate. /// /// # Examples /// /// ``` /// use sequoia_openpgp as openpgp; /// # use openpgp::Result; /// use openpgp::cert::prelude::*; /// use openpgp::policy::StandardPolicy; /// use openpgp::types::RevocationKey; /// /// # fn main() -> Result<()> { /// let p = &StandardPolicy::new(); /// /// let (alice, _) = /// CertBuilder::general_purpose(Some("alice@example.org")) /// .generate()?; /// # Ok(()) } /// ``` pub fn generate(mut self) -> Result<(Cert, Signature)> { use crate::Packet; use crate::types::ReasonForRevocation; let creation_time = self.creation_time.unwrap_or_else(|| { use crate::packet::signature::SIG_BACKDATE_BY; crate::now() - time::Duration::new(SIG_BACKDATE_BY, 0) }); // Generate & self-sign primary key. let (primary, sig, mut signer) = self.primary_key(creation_time)?; // Construct a skeleton cert. We need this to bind the new // components to. let cert = Cert::try_from(vec![ Packet::SecretKey({ let mut primary = primary.clone(); if let Some(ref password) = self.password { let (k, mut secret) = primary.take_secret(); secret.encrypt_in_place(&k, password)?; primary = k.add_secret(secret).0; } primary }), ])?; // We will, however, collect any signatures and components in // a separate vector, and only add them in the end, so that we // canonicalize the new certificate just once. let mut acc = vec![ Packet::from(sig), ]; // We want to mark exactly one User ID or Attribute as primary. // First, figure out whether one of the binding signature // templates have the primary flag set. let have_primary_user_thing = { let is_primary = |osig: &Option<SignatureBuilder>| -> bool { osig.as_ref().and_then(|s| s.primary_userid()).unwrap_or(false) }; self.userids.iter().map(|(s, _)| s).any(is_primary) || self.user_attributes.iter().map(|(s, _)| s).any(is_primary) }; let mut emitted_primary_user_thing = false; // Sign UserIDs. for (template, uid) in std::mem::take(&mut self.userids).into_iter() { let sig = template.unwrap_or_else( || SignatureBuilder::new(SignatureType::PositiveCertification)); let sig = Self::signature_common(sig, creation_time, self.exportable)?; let mut sig = self.add_primary_key_metadata(sig)?; // Make sure we mark exactly one User ID or Attribute as // primary. if emitted_primary_user_thing { sig = sig.modify_hashed_area(|mut a| { a.remove_all(SubpacketTag::PrimaryUserID); Ok(a) })?; } else if have_primary_user_thing { // Check if this is the first explicitly selected // user thing. emitted_primary_user_thing |= sig.primary_userid().unwrap_or(false); } else { // Implicitly mark the first as primary. sig = sig.set_primary_userid(true)?; emitted_primary_user_thing = true; } let signature = uid.bind(&mut signer, &cert, sig)?; acc.push(uid.into()); acc.push(signature.into()); } // Sign UserAttributes. for (template, ua) in std::mem::take(&mut self.user_attributes) { let sig = template.unwrap_or_else( || SignatureBuilder::new(SignatureType::PositiveCertification)); let sig = Self::signature_common( sig, creation_time, self.exportable)?; let mut sig = self.add_primary_key_metadata(sig)?; // Make sure we mark exactly one User ID or Attribute as // primary. if emitted_primary_user_thing { sig = sig.modify_hashed_area(|mut a| { a.remove_all(SubpacketTag::PrimaryUserID); Ok(a) })?; } else if have_primary_user_thing { // Check if this is the first explicitly selected // user thing. emitted_primary_user_thing |= sig.primary_userid().unwrap_or(false); } else { // Implicitly mark the first as primary. sig = sig.set_primary_userid(true)?; emitted_primary_user_thing = true; } let signature = ua.bind(&mut signer, &cert, sig)?; acc.push(ua.into()); acc.push(signature.into()); } // Sign subkeys. for (template, blueprint) in self.subkeys { let flags = &blueprint.flags; let mut subkey = blueprint.ciphersuite .unwrap_or(self.ciphersuite) .generate_key(flags, self.profile)? .role_into_subordinate(); subkey.set_creation_time(creation_time)?; let sig = template.unwrap_or_else( || SignatureBuilder::new(SignatureType::SubkeyBinding)); let sig = Self::signature_common( sig, creation_time, self.exportable)?; let mut builder = sig .set_key_flags(flags.clone())? .set_key_validity_period(blueprint.validity.or(self.primary.validity))?; if flags.for_certification() || flags.for_signing() || flags.for_authentication() { // We need to create a primary key binding signature. let mut subkey_signer = subkey.clone().into_keypair().unwrap(); let backsig = signature::SignatureBuilder::new(SignatureType::PrimaryKeyBinding) .set_signature_creation_time(creation_time)? // GnuPG wants at least a 512-bit hash for P521 keys. .set_hash_algo(HashAlgorithm::SHA512) .sign_primary_key_binding(&mut subkey_signer, &primary, &subkey)?; builder = builder.set_embedded_signature(backsig)?; } let signature = subkey.bind(&mut signer, &cert, builder)?; if let Some(ref password) = self.password { let (k, mut secret) = subkey.take_secret(); secret.encrypt_in_place(&k, password)?; subkey = k.add_secret(secret).0; } acc.push(subkey.into()); acc.push(signature.into()); } // Now add the new components and canonicalize once. let cert = cert.insert_packets(acc)?.0; let revocation = CertRevocationBuilder::new() .set_signature_creation_time(creation_time)? .set_reason_for_revocation( ReasonForRevocation::Unspecified, b"Unspecified")? .build(&mut signer, &cert, None)?; // keys generated by the builder are never invalid assert!(cert.bad.is_empty()); assert!(cert.unknowns.is_empty()); Ok((cert, revocation)) } /// Creates the primary key and a direct key signature. fn primary_key(&self, creation_time: std::time::SystemTime) -> Result<(KeySecretKey, Signature, Box<dyn Signer>)> { let mut key = self.primary.ciphersuite .unwrap_or(self.ciphersuite) .generate_key(KeyFlags::empty().set_certification(), self.profile)? .role_into_primary(); key.set_creation_time(creation_time)?; let sig = SignatureBuilder::new(SignatureType::DirectKey); let sig = Self::signature_common( sig, creation_time, self.exportable)?; let mut sig = self.add_primary_key_metadata(sig)?; if let Some(ref revocation_keys) = self.revocation_keys { for k in revocation_keys.into_iter().cloned() { sig = sig.add_revocation_key(k)?; } } let mut signer = key.clone().into_keypair() .expect("key generated above has a secret"); let sig = sig.sign_direct_key(&mut signer, key.parts_as_public())?; Ok((key, sig, Box::new(signer))) } /// Common settings for generated signatures. fn signature_common(builder: SignatureBuilder, creation_time: time::SystemTime, exportable: bool) -> Result<SignatureBuilder> { let mut builder = builder // GnuPG wants at least a 512-bit hash for P521 keys. .set_hash_algo(HashAlgorithm::SHA512) .set_signature_creation_time(creation_time)?; if ! exportable { builder = builder.set_exportable_certification(false)?; } Ok(builder) } /// Adds primary key metadata to the signature. fn add_primary_key_metadata(&self, builder: SignatureBuilder) -> Result<SignatureBuilder> { builder .set_features(self.features.clone())? .set_key_flags(self.primary.flags.clone())? .set_key_validity_period(self.primary.validity)? .set_preferred_hash_algorithms(vec![ HashAlgorithm::SHA512, HashAlgorithm::SHA256, ])? .set_preferred_symmetric_algorithms(vec![ SymmetricAlgorithm::AES256, SymmetricAlgorithm::AES128, ]) } } #[cfg(test)] mod tests { use std::str::FromStr; use super::*; use crate::Fingerprint; use crate::packet::signature::subpacket::{SubpacketTag, SubpacketValue}; use crate::types::PublicKeyAlgorithm; use crate::parse::Parse; use crate::policy::StandardPolicy as P; use crate::serialize::Serialize; #[test] fn all_opts() { let p = &P::new(); let (cert, _) = CertBuilder::new() .set_cipher_suite(CipherSuite::Cv25519) .add_userid("test1@example.com") .add_userid("test2@example.com") .add_signing_subkey() .add_transport_encryption_subkey() .add_certification_subkey() .generate().unwrap(); let mut userids = cert.userids().with_policy(p, None) .map(|u| String::from_utf8_lossy(u.userid().value()).into_owned()) .collect::<Vec<String>>(); userids.sort(); assert_eq!(userids, &[ "test1@example.com", "test2@example.com", ][..]); assert_eq!(cert.subkeys().count(), 3); } #[test] fn direct_key_sig() { let p = &P::new(); let (cert, _) = CertBuilder::new() .set_cipher_suite(CipherSuite::Cv25519) .add_signing_subkey() .add_transport_encryption_subkey() .add_certification_subkey() .generate().unwrap(); assert_eq!(cert.userids().count(), 0); assert_eq!(cert.subkeys().count(), 3); let sig = cert.primary_key().with_policy(p, None).unwrap().binding_signature(); assert_eq!(sig.typ(), crate::types::SignatureType::DirectKey); assert!(sig.features().unwrap().supports_seipdv1()); } #[test] fn setter() { let (cert1, _) = CertBuilder::new() .set_cipher_suite(CipherSuite::Cv25519) .set_cipher_suite(CipherSuite::RSA3k) .set_cipher_suite(CipherSuite::Cv25519) .generate().unwrap(); assert_eq!(cert1.primary_key().key().pk_algo(), PublicKeyAlgorithm::EdDSA); let (cert2, _) = CertBuilder::new() .set_cipher_suite(CipherSuite::RSA3k) .add_userid("test2@example.com") .add_transport_encryption_subkey() .generate().unwrap(); assert_eq!(cert2.primary_key().key().pk_algo(), PublicKeyAlgorithm::RSAEncryptSign); assert_eq!(cert2.subkeys().next().unwrap().key().pk_algo(), PublicKeyAlgorithm::RSAEncryptSign); } #[test] fn defaults() { let p = &P::new(); let (cert1, _) = CertBuilder::new() .add_userid("test2@example.com") .generate().unwrap(); assert_eq!(cert1.primary_key().key().pk_algo(), PublicKeyAlgorithm::EdDSA); assert!(cert1.subkeys().next().is_none()); assert!(cert1.with_policy(p, None).unwrap().primary_userid().unwrap() .binding_signature().features().unwrap().supports_seipdv1()); } #[test] fn not_always_certify() { let p = &P::new(); let (cert1, _) = CertBuilder::new() .set_cipher_suite(CipherSuite::Cv25519) .set_primary_key_flags(KeyFlags::empty()) .add_transport_encryption_subkey() .generate().unwrap(); assert!(! cert1.primary_key().with_policy(p, None).unwrap().for_certification()); assert_eq!(cert1.keys().subkeys().count(), 1); } #[test] fn gen_wired_subkeys() { let (cert1, _) = CertBuilder::new() .set_cipher_suite(CipherSuite::Cv25519) .set_primary_key_flags(KeyFlags::empty()) .add_subkey(KeyFlags::empty().set_certification(), None, None) .generate().unwrap(); let sig_pkts = cert1.subkeys().next().unwrap().bundle() .self_signatures().next().unwrap().hashed_area(); match sig_pkts.subpacket(SubpacketTag::KeyFlags).unwrap().value() { SubpacketValue::KeyFlags(ref ks) => assert!(ks.for_certification()), v => panic!("Unexpected subpacket: {:?}", v), } assert_eq!(cert1.subkeys().count(), 1); } #[test] fn generate_revocation_certificate() { let p = &P::new(); use crate::types::RevocationStatus; let (cert, revocation) = CertBuilder::new() .set_cipher_suite(CipherSuite::Cv25519) .generate().unwrap(); assert_eq!(cert.revocation_status(p, None), RevocationStatus::NotAsFarAsWeKnow); let cert = cert.insert_packets(revocation.clone()).unwrap().0; assert_eq!(cert.revocation_status(p, None), RevocationStatus::Revoked(vec![ &revocation ])); } #[test] fn builder_roundtrip() { use std::convert::TryFrom; let (cert,_) = CertBuilder::new() .set_cipher_suite(CipherSuite::Cv25519) .add_signing_subkey() .generate().unwrap(); let pile = cert.clone().into_packet_pile().into_children().collect::<Vec<_>>(); let exp = Cert::try_from(pile).unwrap(); assert_eq!(cert, exp); } #[test] fn encrypted_secrets() { let (cert,_) = CertBuilder::new() .set_cipher_suite(CipherSuite::Cv25519) .set_password(Some(String::from("streng geheim").into())) .generate().unwrap(); assert!(cert.primary_key().key().optional_secret().unwrap().is_encrypted()); } #[test] fn all_ciphersuites() { use self::CipherSuite::*; for cs in vec![Cv25519, RSA3k, P256, P384, P521, RSA2k, RSA4k] .into_iter().filter(|cs| cs.is_supported().is_ok()) { assert!(CertBuilder::new() .set_cipher_suite(cs) .generate().is_ok()); } } #[test] fn validity_periods() { let p = &P::new(); let now = crate::now(); let s = std::time::Duration::new(1, 0); let (cert,_) = CertBuilder::new() .set_creation_time(now) .set_validity_period(600 * s) .add_subkey(KeyFlags::empty().set_signing(), 300 * s, None) .add_subkey(KeyFlags::empty().set_authentication(), None, None) .generate().unwrap(); let key = cert.primary_key().key(); let sig = &cert.primary_key().bundle().self_signatures().next().unwrap(); assert!(sig.key_alive(key, now).is_ok()); assert!(sig.key_alive(key, now + 590 * s).is_ok()); assert!(! sig.key_alive(key, now + 610 * s).is_ok()); let ka = cert.keys().with_policy(p, now).alive().revoked(false) .for_signing().next().unwrap(); assert!(ka.alive().is_ok()); assert!(ka.clone().with_policy(p, now + 290 * s).unwrap().alive().is_ok()); assert!(! ka.clone().with_policy(p, now + 310 * s).unwrap().alive().is_ok()); let ka = cert.keys().with_policy(p, now).alive().revoked(false) .for_authentication().next().unwrap(); assert!(ka.alive().is_ok()); assert!(ka.clone().with_policy(p, now + 590 * s).unwrap().alive().is_ok()); assert!(! ka.clone().with_policy(p, now + 610 * s).unwrap().alive().is_ok()); } #[test] fn creation_time() { let p = &P::new(); use std::time::UNIX_EPOCH; let (cert, rev) = CertBuilder::new() .set_creation_time(UNIX_EPOCH) .set_cipher_suite(CipherSuite::Cv25519) .add_userid("foo") .add_signing_subkey() .generate().unwrap(); assert_eq!(cert.primary_key().key().creation_time(), UNIX_EPOCH); assert_eq!(cert.primary_key().with_policy(p, None).unwrap() .binding_signature() .signature_creation_time().unwrap(), UNIX_EPOCH); assert_eq!(cert.primary_key().with_policy(p, None).unwrap() .direct_key_signature().unwrap() .signature_creation_time().unwrap(), UNIX_EPOCH); assert_eq!(rev.signature_creation_time().unwrap(), UNIX_EPOCH); // (Sub)Keys. assert_eq!(cert.keys().with_policy(p, None).count(), 2); for ka in cert.keys().with_policy(p, None) { assert_eq!(ka.key().creation_time(), UNIX_EPOCH); assert_eq!(ka.binding_signature() .signature_creation_time().unwrap(), UNIX_EPOCH); } // UserIDs. assert_eq!(cert.userids().count(), 1); for ui in cert.userids().with_policy(p, None) { assert_eq!(ui.binding_signature() .signature_creation_time().unwrap(), UNIX_EPOCH); } } #[test] fn designated_revokers() -> Result<()> { use std::collections::HashSet; let p = &P::new(); let fpr1 = "C03F A641 1B03 AE12 5764 6118 7223 B566 78E0 2528"; let fpr2 = "50E6 D924 308D BF22 3CFB 510A C2B8 1905 6C65 2598"; let revokers = vec![ RevocationKey::new(PublicKeyAlgorithm::RSAEncryptSign, Fingerprint::from_str(fpr1)?, false), RevocationKey::new(PublicKeyAlgorithm::ECDSA, Fingerprint::from_str(fpr2)?, false) ]; let (cert,_) = CertBuilder::general_purpose(Some("alice@example.org")) .set_revocation_keys(revokers.clone()) .generate()?; let cert = cert.with_policy(p, None)?; assert_eq!(cert.revocation_keys().collect::<HashSet<_>>(), revokers.iter().collect::<HashSet<_>>()); // Do it again, with a key that has no User IDs. let (cert,_) = CertBuilder::new() .set_revocation_keys(revokers.clone()) .generate()?; let cert = cert.with_policy(p, None)?; assert!(cert.primary_userid().is_err()); assert_eq!(cert.revocation_keys().collect::<HashSet<_>>(), revokers.iter().collect::<HashSet<_>>()); // The designated revokers on all signatures should be // considered. let now = crate::types::Timestamp::now(); let then = now.checked_add(crate::types::Duration::days(1)?).unwrap(); let (cert,_) = CertBuilder::new() .set_revocation_keys(revokers.clone()) .set_creation_time(now) .generate()?; // Add a newer direct key signature. use crate::crypto::hash::Hash; let mut primary_signer = cert.primary_key().key().clone().parts_into_secret()? .into_keypair()?; let mut hash = HashAlgorithm::SHA512.context()? .for_signature(primary_signer.public().version()); cert.primary_key().key().hash(&mut hash)?; let sig = signature::SignatureBuilder::new(SignatureType::DirectKey) .set_signature_creation_time(then)? .sign_hash(&mut primary_signer, hash)?; let cert = cert.insert_packets(sig)?.0; assert!(cert.with_policy(p, then)?.primary_userid().is_err()); assert_eq!(cert.revocation_keys(p).collect::<HashSet<_>>(), revokers.iter().collect::<HashSet<_>>()); Ok(()) } /// Checks that the builder emits exactly one user id or attribute /// marked as primary. #[test] fn primary_user_things() -> Result<()> { fn count_primary_user_things(c: Cert) -> usize { c.into_packets().map(|p| match p { Packet::Signature(s) if s.primary_userid().unwrap_or(false) => 1, _ => 0, }).sum() } use crate::packet::{prelude::*, user_attribute::Subpacket}; let ua_foo = UserAttribute::new(&[Subpacket::Unknown(7, vec![7; 7].into())])?; let ua_bar = UserAttribute::new(&[Subpacket::Unknown(11, vec![11; 11].into())])?; let p = &P::new(); let positive = SignatureType::PositiveCertification; let (c, _) = CertBuilder::new().generate()?; assert_eq!(count_primary_user_things(c), 0); let (c, _) = CertBuilder::new() .add_userid("foo") .generate()?; assert_eq!(count_primary_user_things(c), 1); let (c, _) = CertBuilder::new() .add_userid("foo") .add_userid("bar") .generate()?; assert_eq!(count_primary_user_things(c), 1); let (c, _) = CertBuilder::new() .add_user_attribute(ua_foo.clone()) .generate()?; assert_eq!(count_primary_user_things(c), 1); let (c, _) = CertBuilder::new() .add_user_attribute(ua_foo.clone()) .add_user_attribute(ua_bar.clone()) .generate()?; assert_eq!(count_primary_user_things(c), 1); let (c, _) = CertBuilder::new() .add_userid("foo") .add_user_attribute(ua_foo.clone()) .generate()?; let vc = c.with_policy(p, None)?; assert_eq!(vc.primary_userid()?.binding_signature().primary_userid(), Some(true)); assert_eq!(vc.primary_user_attribute()?.binding_signature().primary_userid(), None); assert_eq!(count_primary_user_things(c), 1); let (c, _) = CertBuilder::new() .add_user_attribute(ua_foo.clone()) .add_userid("foo") .generate()?; let vc = c.with_policy(p, None)?; assert_eq!(vc.primary_userid()?.binding_signature().primary_userid(), Some(true)); assert_eq!(vc.primary_user_attribute()?.binding_signature().primary_userid(), None); assert_eq!(count_primary_user_things(c), 1); let (c, _) = CertBuilder::new() .add_userid("foo") .add_userid_with( "buz", SignatureBuilder::new(positive).set_primary_userid(false)?)? .add_userid_with( "bar", SignatureBuilder::new(positive).set_primary_userid(true)?)? .add_userid_with( "baz", SignatureBuilder::new(positive).set_primary_userid(true)?)? .generate()?; let vc = c.with_policy(p, None)?; assert_eq!(vc.primary_userid()?.userid().value(), b"bar"); assert_eq!(count_primary_user_things(c), 1); Ok(()) } #[test] fn non_exportable_cert() -> Result<()> { // Make sure that when we export a non-exportable cert, // nothing is exported. let (cert, _) = CertBuilder::general_purpose(Some("alice@example.org")) .set_exportable(false) .generate()?; let (bob, _) = CertBuilder::general_purpose(Some("bob@example.org")) .generate()?; // Have Bob certify Alice's primary User ID with an exportable // signature. This shouldn't make Alice's certificate // exportable. let mut keypair = bob.primary_key().key().clone() .parts_into_secret()?.into_keypair()?; let certification = cert.userids().nth(0).unwrap() .userid() .certify(&mut keypair, &cert, SignatureType::PositiveCertification, None, None)?; let cert = cert.insert_packets(certification)?.0; macro_rules! check { ($cert: expr, $export: ident, $expected: expr) => { let mut exported = Vec::new(); $cert.$export(&mut exported)?; let certs = CertParser::from_bytes(&exported)? .collect::<Result<Vec<Cert>>>()?; assert_eq!(certs.len(), $expected); if $expected == 0 { assert_eq!(exported.len(), 0, "{}", String::from_utf8_lossy(&exported)); } else { assert!(exported.len() > 0); } } } // Binary cert: check!(cert, export, 0); check!(cert, serialize, 1); // Binary TSK: check!(cert.as_tsk(), export, 0); check!(cert.as_tsk(), serialize, 1); // Armored cert: check!(cert.armored(), export, 0); check!(cert.armored(), serialize, 1); // Armored TSK: check!(cert.as_tsk().armored(), export, 0); check!(cert.as_tsk().armored(), serialize, 1); // Have Alice add an exportable self signature. Now her // certificate should be exportable. let mut keypair = cert.primary_key().key().clone() .parts_into_secret()?.into_keypair()?; let certification = cert.userids().nth(0).unwrap() .userid() .certify(&mut keypair, &cert, SignatureType::PositiveCertification, None, None)?; let cert = cert.insert_packets(certification)?.0; macro_rules! check { ($cert: expr, $export: ident, $expected: expr) => { let mut exported = Vec::new(); $cert.$export(&mut exported)?; let certs = CertParser::from_bytes(&exported)? .collect::<Result<Vec<Cert>>>()?; assert_eq!(certs.len(), $expected); if $expected == 0 { assert_eq!(exported.len(), 0, "{}", String::from_utf8_lossy(&exported)); } else { assert!(exported.len() > 0); } } } // Binary cert: check!(cert, export, 1); check!(cert, serialize, 1); // Binary TSK: check!(cert.as_tsk(), export, 1); check!(cert.as_tsk(), serialize, 1); // Armored cert: check!(cert.armored(), export, 1); check!(cert.armored(), serialize, 1); // Armored TSK: check!(cert.as_tsk().armored(), export, 1); check!(cert.as_tsk().armored(), serialize, 1); Ok(()) } } ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/src/cert/bundle.rs������������������������������������������������������������0000644�0000000�0000000�00000133477�10461020230�0016321�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������//! A certificate component and its associated signatures. //! //! Certificates ([`Cert`]s) are a collection of components where each //! component corresponds to a [`Packet`], and each component has zero //! or more associated [`Signature`]s. A [`ComponentBundle`] //! encapsulates a component and its associated signatures. //! //! Sequoia supports four different kinds of components: [`Key`]s, //! [`UserID`]s, [`UserAttribute`]s, and [`Unknown`] components. The //! `Unknown` component has two purposes. First, it is used to store //! packets that appear in a certificate and have an unknown [`Tag`]. //! By not silently dropping these packets, it is possible to round //! trip certificates without losing any information. This provides a //! measure of future compatibility. Second, the `Unknown` component //! is used to store unsupported components. For instance, Sequoia //! doesn't support v3 `Key`s, which are deprecated, or v5 `Key`s, //! which are still being standardized. Because these keys are //! effectively unusable, they are stored as `Unknown` components //! instead of `Key`s. //! //! There are four types of signatures associated with a component: //! self signatures, self revocations, third-party signatures, and //! third-party revocations. When parsing a certificate, self //! signatures and self revocations are checked for validity and //! invalid signatures and revocations are discarded. Since the keys //! are not normally available, third-party signatures and third-party //! revocations cannot be rigorously (i.e., cryptographically) checked //! for validity. //! //! With the exception of the primary key, a component's self //! signatures are binding signatures. A binding signature firstly //! binds the component to the certificate. That is, it provides //! cryptographic evidence that the certificate holder intended for //! the component to be associated with the certificate. Binding //! signatures also provide information about the component. For //! instance, the binding signature for a subkey includes its //! capabilities, and its expiry time. //! //! Since the primary key is the embodiment of the certificate, there //! is nothing to bind it to. Correspondingly, self signatures on a //! primary key are called direct key signatures. Direct key //! signatures are used to provide information about the whole //! certificate. For instance, they can include the default `Key` //! expiry time. This is used if a subkey's binding signature doesn't //! include an expiry. //! //! Self-revocations are revocation certificates issued by the key //! certificate holder. //! //! Third-party signatures are typically signatures certifying that a //! `User ID` or `User Attribute` accurately describes the certificate //! holder. This information is used by trust models, like the Web of //! Trust, to indirectly authenticate keys. //! //! Third-party revocations are revocations issued by another //! certificate. They should normally only be respected if the //! certificate holder made the issuer a so-called [designated //! revoker]. //! //! # Important //! //! When looking up information about a component, it is generally //! better to use the [`ComponentAmalgamation`] or [`KeyAmalgamation`] //! data structures. These data structures provide convenience //! methods that implement the [complicated semantics] for correctly //! locating information. //! //! [`Cert`]: super //! [`Packet`]: crate::packet //! [`Signature`]: crate::packet::signature //! [`Key`]: crate::packet::key //! [`UserID`]: crate::packet::UserID //! [`UserAttribute`]: crate::packet::user_attribute //! [`Unknown`]: crate::packet::Unknown //! [`Tag`]: crate::packet::Tag //! [designated revoker]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.23 //! [`ComponentAmalgamation`]: super::amalgamation //! [`KeyAmalgamation`]: super::amalgamation::key::KeyAmalgamation //! [complicated semantics]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.10 use std::time; use std::cmp::{self, Ordering}; use std::ops::{Deref, DerefMut}; use std::sync::Arc; use crate::{ cert::lazysigs::{LazySignatures, SigState}, Error, packet::Signature, packet::Key, packet::key, packet::UserID, packet::UserAttribute, packet::Unknown, Packet, policy::HashAlgoSecurity, policy::Policy, Result, }; use crate::types::{ RevocationType, RevocationStatus, }; use super::{ sig_cmp, canonical_signature_order, }; /// A certificate component and its associated signatures. /// /// [See the module level documentation](self) for a detailed /// description. #[derive(Debug, Clone, PartialEq)] pub struct ComponentBundle<C> { component: C, pub(super) hash_algo_security: HashAlgoSecurity, // Self signatures. pub(super) self_signatures: LazySignatures, /// If set, is equal to `component`, and provides context to /// verify primary key binding signatures. backsig_signer: Option<Key<key::PublicParts, key::SubordinateRole>>, // Third-party certifications. (In general, this will only be by // designated revokers.) pub(super) certifications: Vec<Signature>, // Attestation key signatures. pub(super) attestations: LazySignatures, // Self revocations. pub(super) self_revocations: LazySignatures, // Third-party revocations (e.g., designated revokers). pub(super) other_revocations: Vec<Signature>, } assert_send_and_sync!(ComponentBundle<C> where C); /// A key (primary or subkey, public or private) and any associated /// signatures. /// /// [See the module level documentation.](self) pub type KeyBundle<KeyPart, KeyRole> = ComponentBundle<Key<KeyPart, KeyRole>>; /// A primary key and any associated signatures. /// /// [See the module level documentation.](self) pub type PrimaryKeyBundle<KeyPart> = KeyBundle<KeyPart, key::PrimaryRole>; /// A subkey and any associated signatures. /// /// [See the module level documentation.](self) pub type SubkeyBundle<KeyPart> = KeyBundle<KeyPart, key::SubordinateRole>; /// A User ID and any associated signatures. /// /// [See the module level documentation.](self) pub type UserIDBundle = ComponentBundle<UserID>; /// A User Attribute and any associated signatures. /// /// [See the module level documentation.](self) pub type UserAttributeBundle = ComponentBundle<UserAttribute>; /// An unknown component and any associated signatures. /// /// Note: all signatures are stored as certifications. /// /// [See the module level documentation.](self) pub type UnknownBundle = ComponentBundle<Unknown>; impl<C> ComponentBundle<C> { /// Creates a new component. /// /// Should only be used from the cert parser. However, we cannot /// use `pub(in ...)` because the cert parser isn't an ancestor of /// this module. pub(crate) fn new(component: C, hash_algo_security: HashAlgoSecurity, sigs: Vec<Signature>, primary_key: Arc<Key<key::PublicParts, key::PrimaryRole>>) -> ComponentBundle<C> { ComponentBundle { component, hash_algo_security, self_signatures: LazySignatures::new(primary_key.clone()), backsig_signer: None, certifications: sigs, attestations: LazySignatures::new(primary_key.clone()), self_revocations: LazySignatures::new(primary_key), other_revocations: vec![], } } /// Returns a reference to the bundle's component. /// /// # Examples /// /// ``` /// # use sequoia_openpgp as openpgp; /// # use openpgp::cert::prelude::*; /// # /// # fn main() -> openpgp::Result<()> { /// # let (cert, _) = /// # CertBuilder::general_purpose(Some("alice@example.org")) /// # .generate()?; /// // Display some information about any unknown components. /// for u in cert.unknowns() { /// eprintln!(" - {:?}", u.bundle().component()); /// } /// # Ok(()) } /// ``` pub fn component(&self) -> &C { &self.component } /// Returns a mutable reference to the component. pub(crate) fn component_mut(&mut self) -> &mut C { &mut self.component } /// Returns the active binding signature at time `t`. /// /// The active binding signature is the most recent, non-revoked /// self-signature that is valid according to the `policy` and /// alive at time `t` (`creation time <= t`, `t < expiry`). If /// there are multiple such signatures then the signatures are /// ordered by their MPIs interpreted as byte strings. /// /// # Examples /// /// ``` /// # use sequoia_openpgp as openpgp; /// # use openpgp::cert::prelude::*; /// use openpgp::policy::StandardPolicy; /// # /// # fn main() -> openpgp::Result<()> { /// let p = &StandardPolicy::new(); /// /// # let (cert, _) = /// # CertBuilder::general_purpose(Some("alice@example.org")) /// # .generate()?; /// // Display information about each User ID's current active /// // binding signature (the `time` parameter is `None`), if any. /// for ua in cert.userids() { /// eprintln!("{:?}", ua.bundle().binding_signature(p, None)); /// } /// # Ok(()) } /// ``` pub fn binding_signature<T>(&self, policy: &dyn Policy, t: T) -> Result<&Signature> where T: Into<Option<time::SystemTime>> { let t = t.into().unwrap_or_else(crate::now); /// Finds the active binding signature. /// /// This function does not depend on the type of `C`, but it /// is unfortunately monomorphized for every `C`. Prevent /// this by moving the code to a function independent of `C`. fn find_binding_signature<'s>(policy: &dyn Policy, self_signatures: &'s LazySignatures, backsig_signer: Option<&Key<key::PublicParts, key::SubordinateRole>>, hash_algo_security: HashAlgoSecurity, t: time::SystemTime) -> Result<&'s Signature> { // Recall: the signatures are sorted by their creation time in // descending order, i.e., newest first. // // We want the newest signature that is older than `t`, or // that has been created at `t`. So, search for `t`. // We search all signatures without triggering the signature // verification. Later, we will verify the candidates, and // reject bad signatures. let unverified_self_signatures = self_signatures.as_slice_unverified(); let i = // Usually, the first signature is what we are looking for. // Short circuit the binary search. if unverified_self_signatures.get(0) .filter(|s| s.signature_creation_time().map(|c| t >= c) .unwrap_or(false)) .filter( // Verify the signature now. |_| matches!(self_signatures.verify_sig(0, backsig_signer), Ok(SigState::Good))) .is_some() { 0 } else { match unverified_self_signatures.binary_search_by( |s| canonical_signature_order( s.signature_creation_time(), Some(t))) { // If there are multiple matches, then we need to search // backwards to find the first one. Consider: // // t: 9 8 8 8 8 7 // i: 0 1 2 3 4 5 // // If we are looking for t == 8, then binary_search could // return index 1, 2, 3 or 4. Ok(mut i) => { while i > 0 && unverified_self_signatures[i - 1].signature_creation_time() == Some(t) { i -= 1; } i } // There was no match. `i` is where a new element could // be inserted while maintaining the sorted order. // Consider: // // t: 9 8 6 5 // i: 0 1 2 3 // // If we are looing for t == 7, then binary_search will // return i == 2. That's exactly where we should start // looking. Err(i) => i, } }; let mut sig = None; // Prefer the first error, which is the error arising from the // most recent binding signature that wasn't created after // `t`. let mut error = None; 'next_sig: for (j, s) in unverified_self_signatures[i..].iter() .enumerate() { if let Err(e) = s.signature_alive(t, time::Duration::new(0, 0)) { // We know that t >= signature's creation time. So, // it is expired. But an older signature might not // be. So, keep trying. if error.is_none() { error = Some(e); } continue; } if let Err(e) = policy.signature(s, hash_algo_security) { if error.is_none() { error = Some(e); } continue; } // Verify the signature now. if ! matches!(self_signatures.verify_sig(i + j, backsig_signer), Ok(SigState::Good)) { // Reject bad signatures. continue; } // The signature is good, but we may still need to verify the // back sig. if s.typ() == crate::types::SignatureType::SubkeyBinding && s.key_flags().map(|kf| kf.for_signing()).unwrap_or(false) { let mut n = 0; let mut one_good_backsig = false; 'next_backsig: for backsig in s.embedded_signatures() { n += 1; if let Err(e) = backsig.signature_alive( t, time::Duration::new(0, 0)) { // The primary key binding signature is not // alive. if error.is_none() { error = Some(e); } continue 'next_backsig; } if let Err(e) = policy .signature(backsig, hash_algo_security) { if error.is_none() { error = Some(e); } continue 'next_backsig; } one_good_backsig = true; } if n == 0 { // This shouldn't happen because // Signature::verify_subkey_binding checks for the // primary key binding signature. But, better be // safe. if error.is_none() { error = Some(Error::BadSignature( "Primary key binding signature missing".into()) .into()); } continue 'next_sig; } if ! one_good_backsig { continue 'next_sig; } } sig = Some(s); break; } if let Some(sig) = sig { Ok(sig) } else if let Some(err) = error { Err(err) } else { Err(Error::NoBindingSignature(t).into()) } } find_binding_signature( policy, &self.self_signatures, self.backsig_signer.as_ref(), self.hash_algo_security, t) } /// Returns the component's self-signatures. /// /// The signatures are validated, and they are sorted by their /// creation time, most recent first. /// /// # Examples /// /// ``` /// # use sequoia_openpgp as openpgp; /// # use openpgp::cert::prelude::*; /// use openpgp::policy::StandardPolicy; /// # /// # fn main() -> openpgp::Result<()> { /// let p = &StandardPolicy::new(); /// /// # let (cert, _) = /// # CertBuilder::general_purpose(Some("alice@example.org")) /// # .generate()?; /// for (i, ka) in cert.keys().enumerate() { /// eprintln!("Key #{} ({}) has {:?} self signatures", /// i, ka.key().fingerprint(), /// ka.bundle().self_signatures().count()); /// } /// # Ok(()) } /// ``` pub fn self_signatures(&self) -> impl Iterator<Item=&Signature> + Send + Sync { self.self_signatures.iter_verified(self.backsig_signer.as_ref()) } /// Returns the component's third-party certifications. /// /// The signatures are *not* validated. They are sorted by their /// creation time, most recent first. /// /// # Examples /// /// ``` /// # use sequoia_openpgp as openpgp; /// # use openpgp::cert::prelude::*; /// use openpgp::policy::StandardPolicy; /// # /// # fn main() -> openpgp::Result<()> { /// let p = &StandardPolicy::new(); /// /// # let (cert, _) = /// # CertBuilder::general_purpose(Some("alice@example.org")) /// # .generate()?; /// for ua in cert.userids() { /// eprintln!("User ID {} has {:?} unverified, third-party certifications", /// String::from_utf8_lossy(ua.userid().value()), /// ua.bundle().certifications().count()); /// } /// # Ok(()) } /// ``` pub fn certifications(&self) -> impl Iterator<Item=&Signature> + Send + Sync { self.certifications.iter() } /// Returns the component's revocations that were issued by the /// certificate holder. /// /// The revocations are validated, and they are sorted by their /// creation time, most recent first. /// /// # Examples /// /// ``` /// # use sequoia_openpgp as openpgp; /// # use openpgp::cert::prelude::*; /// use openpgp::policy::StandardPolicy; /// # /// # fn main() -> openpgp::Result<()> { /// let p = &StandardPolicy::new(); /// /// # let (cert, _) = /// # CertBuilder::general_purpose(Some("alice@example.org")) /// # .generate()?; /// for u in cert.userids() { /// eprintln!("User ID {} has {:?} revocation certificates.", /// String::from_utf8_lossy(u.userid().value()), /// u.bundle().self_revocations().count()); /// } /// # Ok(()) } /// ``` pub fn self_revocations(&self) -> impl Iterator<Item=&Signature> + Send + Sync { self.self_revocations.iter_verified(self.backsig_signer.as_ref()) } /// Returns the component's revocations that were issued by other /// certificates. /// /// The revocations are *not* validated. They are sorted by their /// creation time, most recent first. /// /// # Examples /// /// ``` /// # use sequoia_openpgp as openpgp; /// # use openpgp::cert::prelude::*; /// use openpgp::policy::StandardPolicy; /// # /// # fn main() -> openpgp::Result<()> { /// let p = &StandardPolicy::new(); /// /// # let (cert, _) = /// # CertBuilder::general_purpose(Some("alice@example.org")) /// # .generate()?; /// for u in cert.userids() { /// eprintln!("User ID {} has {:?} unverified, third-party revocation certificates.", /// String::from_utf8_lossy(u.userid().value()), /// u.bundle().other_revocations().count()); /// } /// # Ok(()) } /// ``` pub fn other_revocations(&self) -> impl Iterator<Item=&Signature> + Send + Sync { self.other_revocations.iter() } /// Returns all the component's Certification Approval Key /// Signatures. /// /// This feature is [experimental](crate#experimental-features). /// /// The signatures are validated, and they are sorted by their /// creation time, most recent first. /// /// A certificate owner can use Certification Approval Key /// Signatures to approve of third party certifications. /// Currently, only user ID and user attribute certifications can /// be approved. See [Approved Certifications subpacket] for /// details. /// /// [Approved Certifications subpacket]: https://www.ietf.org/archive/id/draft-dkg-openpgp-1pa3pc-02.html#approved-certifications-subpacket /// /// # Examples /// /// ``` /// # use sequoia_openpgp as openpgp; /// # fn main() -> openpgp::Result<()> { /// # use openpgp::cert::prelude::*; /// use openpgp::policy::StandardPolicy; /// let p = &StandardPolicy::new(); /// /// # let (cert, _) = /// # CertBuilder::general_purpose(Some("alice@example.org")) /// # .generate()?; /// for (i, uid) in cert.userids().enumerate() { /// eprintln!("UserID #{} ({:?}) has {:?} certification approval key signatures", /// i, uid.userid().email(), /// uid.bundle().approvals().count()); /// } /// # Ok(()) } /// ``` pub fn approvals(&self) -> impl Iterator<Item = &Signature> + Send + Sync { self.attestations.iter_verified(None) } /// Returns all the component's signatures. /// /// Only the self-signatures are validated. The signatures are /// sorted first by type, then by creation time. The self /// revocations come first, then the self signatures, /// then any certification approval key signatures, /// certifications, and third-party revocations coming last. This /// function may return additional types of signatures that could /// be associated to this component. /// /// # Examples /// /// ``` /// # use sequoia_openpgp as openpgp; /// # use openpgp::cert::prelude::*; /// use openpgp::policy::StandardPolicy; /// # /// # fn main() -> openpgp::Result<()> { /// let p = &StandardPolicy::new(); /// /// # let (cert, _) = /// # CertBuilder::general_purpose(Some("alice@example.org")) /// # .generate()?; /// for (i, ka) in cert.keys().enumerate() { /// eprintln!("Key #{} ({}) has {:?} signatures", /// i, ka.key().fingerprint(), /// ka.bundle().signatures().count()); /// } /// # Ok(()) } /// ``` pub fn signatures(&self) -> impl Iterator<Item = &Signature> + Send + Sync { self.self_revocations() .chain(self.self_signatures()) .chain(self.approvals()) .chain(self.certifications()) .chain(self.other_revocations()) } /// Returns all the bundles' bad signatures. pub(crate) fn bad_signatures(&self) -> impl Iterator<Item = &Signature> + Send + Sync { self.self_signatures.iter_bad(self.backsig_signer.as_ref()) .chain(self.self_revocations.iter_bad(self.backsig_signer.as_ref())) } /// Returns the component's revocation status at time `t`. /// /// A component is considered to be revoked at time `t` if: /// /// - There is a live revocation at time `t` that is newer than /// all live self signatures at time `t`. /// /// - `hard_revocations_are_final` is true, and there is a hard /// revocation (even if it is not yet live at time `t`, and /// even if there is a newer self-signature). /// /// selfsig must be the newest live self signature at time `t`. pub(crate) fn _revocation_status<'a, T>(&'a self, policy: &dyn Policy, t: T, hard_revocations_are_final: bool, selfsig: Option<&Signature>) -> RevocationStatus<'a> where T: Into<Option<time::SystemTime>> { // Fallback time. let time_zero = || time::UNIX_EPOCH; let t = t.into().unwrap_or_else(crate::now); let selfsig_creation_time = selfsig.and_then(|s| s.signature_creation_time()) .unwrap_or_else(time_zero); tracer!(super::TRACE, "ComponentBundle::_revocation_status", 0); t!("hard_revocations_are_final: {}, selfsig: {:?}, t: {:?}", hard_revocations_are_final, selfsig_creation_time, t); if let Some(selfsig) = selfsig { assert!( selfsig.signature_alive(t, time::Duration::new(0, 0)).is_ok()); } let check = |revs: &mut dyn Iterator<Item=&'a Signature>, sec: HashAlgoSecurity| -> Option<Vec<&'a Signature>> { let revs = revs.filter(|rev| { if let Err(err) = policy.signature(rev, sec) { t!(" revocation rejected by caller policy: {}", err); false } else if hard_revocations_are_final && rev.reason_for_revocation() .map(|(r, _)| { r.revocation_type() == RevocationType::Hard }) // If there is no Reason for Revocation // packet, assume that it is a hard // revocation. .unwrap_or(true) { t!(" got a hard revocation: {:?}, {:?}", rev.signature_creation_time() .unwrap_or_else(time_zero), rev.reason_for_revocation() .map(|r| (r.0, String::from_utf8_lossy(r.1)))); true } else if selfsig_creation_time > rev.signature_creation_time().unwrap_or_else(time_zero) { // This comes after the hard revocation check, // because a hard revocation is always valid. t!(" newer binding signature trumps soft revocation ({:?} > {:?})", selfsig_creation_time, rev.signature_creation_time().unwrap_or_else(time_zero)); false } else if let Err(err) = rev.signature_alive(t, time::Duration::new(0, 0)) { // This comes after the hard revocation check, // because a hard revocation is always valid. t!(" revocation not alive ({:?} - {:?}): {}", rev.signature_creation_time().unwrap_or_else(time_zero), rev.signature_validity_period() .unwrap_or_else(|| time::Duration::new(0, 0)), err); false } else { t!(" got a revocation: {:?} ({:?})", rev.signature_creation_time().unwrap_or_else(time_zero), rev.reason_for_revocation() .map(|r| (r.0, String::from_utf8_lossy(r.1)))); true } }).collect::<Vec<&Signature>>(); if revs.is_empty() { None } else { Some(revs) } }; if let Some(revs) = check(&mut self.self_revocations.iter_verified(self.backsig_signer.as_ref()), self.hash_algo_security) { t!("-> RevocationStatus::Revoked({})", revs.len()); RevocationStatus::Revoked(revs) } else if let Some(revs) = check(&mut self.other_revocations.iter(), Default::default()) { t!("-> RevocationStatus::CouldBe({})", revs.len()); RevocationStatus::CouldBe(revs) } else { t!("-> RevocationStatus::NotAsFarAsWeKnow"); RevocationStatus::NotAsFarAsWeKnow } } /// Turns the `ComponentBundle` into an iterator over its /// `Packet`s. /// /// The signatures are ordered as follows: /// /// - Self revocations, /// - Self signatures, /// - Third-party signatures, and /// - Third-party revocations. /// /// For a given type of signature, the signatures are ordered by /// their creation time, most recent first. /// /// When turning the `Key` in a `KeyBundle` into a `Packet`, this /// function uses the component's type (`C`) to determine the /// packet's type; the type is not a function of whether the key /// has secret key material. pub(crate) fn into_packets(self) -> impl Iterator<Item=Packet> + Send + Sync where Packet: From<C> { let p : Packet = self.component.into(); std::iter::once(p) .chain(self.self_revocations.into_unverified().map(|s| s.into())) .chain(self.self_signatures.into_unverified().map(|s| s.into())) .chain(self.attestations.into_unverified().map(|s| s.into())) .chain(self.certifications.into_iter().map(|s| s.into())) .chain(self.other_revocations.into_iter().map(|s| s.into())) } // Sorts and dedups the binding's signatures. // // Note: this uses Signature::normalized_eq to compare signatures. // That function ignores unhashed packets. If there are two // signatures that only differ in their unhashed subpackets, they // will be deduped. The unhashed areas are merged as discussed in // Signature::merge. pub(crate) fn sort_and_dedup(&mut self) { // `same_bucket` function for Vec::dedup_by that compares // signatures and merges them if they are equal modulo // unhashed subpackets. fn sig_merge(a: &mut Signature, b: &mut Signature) -> bool { // If a == b, a is removed. Hence, we merge into b. if a.normalized_eq(b) { b.merge_internal(a) .expect("checked for equality above"); true } else { false } } // If two signatures are merged, we also do some fixups. Make // sure we also do this to signatures that are not merged, so // that `cert.merge(cert) == cert`. fn sig_fixup(sig: &mut Signature) { // Add missing issuer information. This is a best effort // though. If the unhashed area is full, there is nothing // we can do. let _ = sig.add_missing_issuers(); // Merging Signatures sorts the unhashed subpacket area. // Do the same. sig.unhashed_area_mut().sort(); } self.self_signatures.sort_by(Signature::normalized_cmp); self.self_signatures.dedup_by(sig_merge); // Order self signatures so that the most recent one comes // first. self.self_signatures.sort_by(sig_cmp); self.self_signatures.iter_mut_unverified().for_each(sig_fixup); self.attestations.sort_by(Signature::normalized_cmp); self.attestations.dedup_by(sig_merge); self.attestations.sort_by(sig_cmp); self.attestations.iter_mut_unverified().for_each(sig_fixup); self.certifications.sort_by(Signature::normalized_cmp); self.certifications.dedup_by(sig_merge); // There is no need to sort the certifications, but doing so // has the advantage that the most recent ones (and therefore // presumably the more relevant ones) come first. Also, // cert::test::signature_order checks that the signatures are // sorted. self.certifications.sort_by(sig_cmp); self.certifications.iter_mut().for_each(sig_fixup); self.self_revocations.sort_by(Signature::normalized_cmp); self.self_revocations.dedup_by(sig_merge); self.self_revocations.sort_by(sig_cmp); self.self_revocations.iter_mut_unverified().for_each(sig_fixup); self.other_revocations.sort_by(Signature::normalized_cmp); self.other_revocations.dedup_by(sig_merge); self.other_revocations.sort_by(sig_cmp); self.other_revocations.iter_mut().for_each(sig_fixup); } } impl<P: key::KeyParts, R: key::KeyRole> ComponentBundle<Key<P, R>> { /// Returns a reference to the key. /// /// This is just a type-specific alias for /// [`ComponentBundle::component`]. /// /// [`ComponentBundle::component`]: ComponentBundle::component() /// /// # Examples /// /// ``` /// # use sequoia_openpgp as openpgp; /// # use openpgp::cert::prelude::*; /// # /// # fn main() -> openpgp::Result<()> { /// # let (cert, _) = /// # CertBuilder::general_purpose(Some("alice@example.org")) /// # .generate()?; /// // Display some information about the keys. /// for ka in cert.keys() { /// eprintln!(" - {:?}", ka.bundle().key()); /// } /// # Ok(()) } /// ``` pub fn key(&self) -> &Key<P, R> { self.component() } /// Returns a mut reference to the key. pub(crate) fn key_mut(&mut self) -> &mut Key<P, R> { self.component_mut() } pub(crate) fn set_role(&mut self, role: key::KeyRoleRT) { self.key_mut().set_role(role); } /// Forwarder for the conversion macros. pub(crate) fn has_secret(&self) -> bool { self.key().has_secret() } } impl<P: key::KeyParts> ComponentBundle<Key<P, key::SubordinateRole>> { /// Creates a new subkey component. /// /// Should only be used from the cert parser. However, we cannot /// use `pub(in ...)` because the cert parser isn't an ancestor of /// this module. pub(crate) fn new_subkey(component: Key<P, key::SubordinateRole>, hash_algo_security: HashAlgoSecurity, sigs: Vec<Signature>, primary_key: Arc<Key<key::PublicParts, key::PrimaryRole>>) -> Self { let backsig_signer = component.pk_algo().for_signing() .then(|| component.parts_as_public().clone()); ComponentBundle { component, hash_algo_security, self_signatures: LazySignatures::new(primary_key.clone()), backsig_signer, certifications: sigs, attestations: LazySignatures::new(primary_key.clone()), self_revocations: LazySignatures::new(primary_key), other_revocations: vec![], } } /// Returns the subkey's revocation status at time `t`. /// /// A subkey is revoked at time `t` if: /// /// - There is a live revocation at time `t` that is newer than /// all live self signatures at time `t`, or /// /// - There is a hard revocation (even if it is not live at /// time `t`, and even if there is a newer self-signature). /// /// Note: Certs and subkeys have different criteria from User IDs /// and User Attributes. /// /// Note: this only returns whether this subkey is revoked; it /// does not imply anything about the Cert or other components. /// /// # Examples /// /// ``` /// # use sequoia_openpgp as openpgp; /// # use openpgp::cert::prelude::*; /// use openpgp::policy::StandardPolicy; /// # /// # fn main() -> openpgp::Result<()> { /// let p = &StandardPolicy::new(); /// /// # let (cert, _) = /// # CertBuilder::general_purpose(Some("alice@example.org")) /// # .generate()?; /// // Display the subkeys' revocation status. /// for ka in cert.keys().subkeys() { /// eprintln!(" Revocation status of {}: {:?}", /// ka.key().fingerprint(), /// ka.bundle().revocation_status(p, None)); /// } /// # Ok(()) } /// ``` pub fn revocation_status<T>(&self, policy: &dyn Policy, t: T) -> RevocationStatus where T: Into<Option<time::SystemTime>> { let t = t.into(); self._revocation_status(policy, t, true, self.binding_signature(policy, t).ok()) } } impl ComponentBundle<UserID> { /// Returns a reference to the User ID. /// /// This is just a type-specific alias for /// [`ComponentBundle::component`]. /// /// [`ComponentBundle::component`]: ComponentBundle::component() /// /// # Examples /// /// ``` /// # use sequoia_openpgp as openpgp; /// # use openpgp::cert::prelude::*; /// # /// # fn main() -> openpgp::Result<()> { /// # let (cert, _) = /// # CertBuilder::general_purpose(Some("alice@example.org")) /// # .generate()?; /// // Display some information about the User IDs. /// for ua in cert.userids() { /// eprintln!(" - {:?}", ua.bundle().userid()); /// } /// # Ok(()) } /// ``` pub fn userid(&self) -> &UserID { self.component() } /// Returns the User ID's revocation status at time `t`.<a /// name="userid_revocation_status"></a> /// /// <!-- Why we have the above anchor: /// https://github.com/rust-lang/rust/issues/71912 --> /// /// A User ID is revoked at time `t` if: /// /// - There is a live revocation at time `t` that is newer than /// all live self signatures at time `t`. /// /// Note: Certs and subkeys have different criteria from User IDs /// and User Attributes. /// /// Note: this only returns whether this User ID is revoked; it /// does not imply anything about the Cert or other components. // /// # Examples /// /// ``` /// # use sequoia_openpgp as openpgp; /// # use openpgp::cert::prelude::*; /// use openpgp::policy::StandardPolicy; /// # /// # fn main() -> openpgp::Result<()> { /// let p = &StandardPolicy::new(); /// /// # let (cert, _) = /// # CertBuilder::general_purpose(Some("alice@example.org")) /// # .generate()?; /// // Display the User IDs' revocation status. /// for ua in cert.userids() { /// eprintln!(" Revocation status of {}: {:?}", /// String::from_utf8_lossy(ua.userid().value()), /// ua.bundle().revocation_status(p, None)); /// } /// # Ok(()) } /// ``` pub fn revocation_status<T>(&self, policy: &dyn Policy, t: T) -> RevocationStatus where T: Into<Option<time::SystemTime>> { let t = t.into(); self._revocation_status(policy, t, false, self.binding_signature(policy, t).ok()) } } impl ComponentBundle<UserAttribute> { /// Returns a reference to the User Attribute. /// /// This is just a type-specific alias for /// [`ComponentBundle::component`]. /// /// [`ComponentBundle::component`]: ComponentBundle::component() /// /// # Examples /// /// ``` /// # use sequoia_openpgp as openpgp; /// # use openpgp::cert::prelude::*; /// # /// # fn main() -> openpgp::Result<()> { /// # let (cert, _) = /// # CertBuilder::general_purpose(Some("alice@example.org")) /// # .generate()?; /// // Display some information about the User Attributes /// for ua in cert.user_attributes() { /// eprintln!(" - {:?}", ua.bundle().user_attribute()); /// } /// # Ok(()) } /// ``` pub fn user_attribute(&self) -> &UserAttribute { self.component() } /// Returns the User Attribute's revocation status at time `t`. /// /// A User Attribute is revoked at time `t` if: /// /// - There is a live revocation at time `t` that is newer than /// all live self signatures at time `t`. /// /// Note: Certs and subkeys have different criteria from User IDs /// and User Attributes. /// /// Note: this only returns whether this User Attribute is revoked; /// it does not imply anything about the Cert or other components. // /// # Examples /// /// ``` /// # use sequoia_openpgp as openpgp; /// # use openpgp::cert::prelude::*; /// use openpgp::policy::StandardPolicy; /// # /// # fn main() -> openpgp::Result<()> { /// let p = &StandardPolicy::new(); /// /// # let (cert, _) = /// # CertBuilder::general_purpose(Some("alice@example.org")) /// # .generate()?; /// // Display the User Attributes' revocation status. /// for (i, ua) in cert.user_attributes().enumerate() { /// eprintln!(" Revocation status of User Attribute #{}: {:?}", /// i, ua.bundle().revocation_status(p, None)); /// } /// # Ok(()) } /// ``` pub fn revocation_status<T>(&self, policy: &dyn Policy, t: T) -> RevocationStatus where T: Into<Option<time::SystemTime>> { let t = t.into(); self._revocation_status(policy, t, false, self.binding_signature(policy, t).ok()) } } impl ComponentBundle<Unknown> { /// Returns a reference to the unknown component. /// /// This is just a type-specific alias for /// [`ComponentBundle::component`]. /// /// [`ComponentBundle::component`]: ComponentBundle::component() /// /// # Examples /// /// ``` /// # use sequoia_openpgp as openpgp; /// # use openpgp::cert::prelude::*; /// # /// # fn main() -> openpgp::Result<()> { /// # let (cert, _) = /// # CertBuilder::general_purpose(Some("alice@example.org")) /// # .generate()?; /// // Display some information about the User Attributes /// for u in cert.unknowns() { /// eprintln!(" - {:?}", u.unknown()); /// } /// # Ok(()) } /// ``` pub fn unknown(&self) -> &Unknown { self.component() } } /// A collection of `ComponentBundles`. /// /// Note: we need this, because we can't `impl Vec<ComponentBundles>`. #[derive(Debug, Clone, PartialEq)] pub(super) struct ComponentBundles<C> where ComponentBundle<C>: cmp::PartialEq { bundles: Vec<ComponentBundle<C>>, } impl<C> Default for ComponentBundles<C> where ComponentBundle<C>: cmp::PartialEq, { fn default() -> Self { ComponentBundles { bundles: vec![], } } } impl<C> Deref for ComponentBundles<C> where ComponentBundle<C>: cmp::PartialEq { type Target = Vec<ComponentBundle<C>>; fn deref(&self) -> &Self::Target { &self.bundles } } impl<C> DerefMut for ComponentBundles<C> where ComponentBundle<C>: cmp::PartialEq { fn deref_mut(&mut self) -> &mut Vec<ComponentBundle<C>> { &mut self.bundles } } impl<C> From<ComponentBundles<C>> for Vec<ComponentBundle<C>> where ComponentBundle<C>: cmp::PartialEq { fn from(cb: ComponentBundles<C>) -> Vec<ComponentBundle<C>> { cb.bundles } } impl<C> From<Vec<ComponentBundle<C>>> for ComponentBundles<C> where ComponentBundle<C>: cmp::PartialEq { fn from(bundles: Vec<ComponentBundle<C>>) -> ComponentBundles<C> { ComponentBundles { bundles, } } } impl<C> IntoIterator for ComponentBundles<C> where ComponentBundle<C>: cmp::PartialEq { type Item = ComponentBundle<C>; type IntoIter = std::vec::IntoIter<Self::Item>; fn into_iter(self) -> Self::IntoIter { self.bundles.into_iter() } } impl<C> ComponentBundles<C> where ComponentBundle<C>: cmp::PartialEq { // Sort and dedup the components. // // `cmp` is a function to sort the components for deduping. // // `merge` is a function that merges the first component into the // second component. pub(super) fn sort_and_dedup<F, F2>(&mut self, cmp: F, merge: F2) where F: Fn(&C, &C) -> Ordering, F2: Fn(&mut C, &mut C) { // We dedup by component (not bundles!). To do this, we need // to sort the bundles by their components. self.bundles.sort_by( |a, b| cmp(&a.component, &b.component)); self.bundles.dedup_by(|a, b| { if cmp(&a.component, &b.component) == Ordering::Equal { // Merge. merge(&mut a.component, &mut b.component); // Recall: if a and b are equal, a will be dropped. // Also, the elements are given in the opposite order // from their order in the vector. b.self_signatures.append(&mut a.self_signatures); b.attestations.append(&mut a.attestations); b.certifications.append(&mut a.certifications); b.self_revocations.append(&mut a.self_revocations); b.other_revocations.append(&mut a.other_revocations); true } else { false } }); // And sort the certificates. for b in self.bundles.iter_mut() { b.sort_and_dedup(); } } } /// A vecor of key (primary or subkey, public or private) and any /// associated signatures. pub(super) type KeyBundles<KeyPart, KeyRole> = ComponentBundles<Key<KeyPart, KeyRole>>; /// A vector of subkeys and any associated signatures. pub(super) type SubkeyBundles<KeyPart> = KeyBundles<KeyPart, key::SubordinateRole>; /// A vector of key (primary or subkey, public or private) and any /// associated signatures. #[allow(dead_code)] pub(super) type GenericKeyBundles = ComponentBundles<Key<key::UnspecifiedParts, key::UnspecifiedRole>>; /// A vector of User ID bundles and any associated signatures. pub(super) type UserIDBundles = ComponentBundles<UserID>; /// A vector of User Attribute bundles and any associated signatures. pub(super) type UserAttributeBundles = ComponentBundles<UserAttribute>; /// A vector of unknown components and any associated signatures. /// /// Note: all signatures are stored as certifications. pub(super) type UnknownBundles = ComponentBundles<Unknown>; �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/src/cert/lazysigs.rs����������������������������������������������������������0000644�0000000�0000000�00000023770�10461020230�0016707�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������//! Lazily verified signatures. //! //! In the original implementation of `Cert::canonicalize`, all //! self-signatures were verified. This has turned out to be very //! expensive. Instead, we should only verify the signatures we are //! actually interested in. //! //! To preserve the semantics, every self signature we hand out from //! the `Cert` API must have been verified first. However, we can do //! that lazily. And, when we reason over the cert (i.e. we are //! looking for the right self-signature), we can search the //! signatures without triggering the verification, and only verify //! the one we are really interested in. use std::{ cmp::Ordering, mem, sync::{Arc, Mutex}, }; use crate::{ Error, Result, packet::{ Key, Signature, key, signature::subpacket::{SubpacketTag, SubpacketValue}, }, }; /// Lazily verified signatures, similar to a `Vec<Signature>`. /// /// We use two distinct vectors to store the signatures and their /// state. The reason for that is that we need to modify the /// signature states while the signatures are borrowed. /// /// We provide a subset of `Vec<Signature>`'s interface to make it /// (mostly) a drop-in replacement. /// /// # Invariant /// /// - There are as many signatures as signature states. #[derive(Debug)] pub struct LazySignatures { /// The primary key to verify the signatures with. primary_key: Arc<Key<key::PublicParts, key::PrimaryRole>>, /// The signatures. sigs: Vec<Signature>, /// The signature states. states: Mutex<Vec<SigState>>, } impl PartialEq for LazySignatures { fn eq(&self, other: &Self) -> bool { self.assert_invariant(); other.assert_invariant(); self.primary_key == other.primary_key && self.sigs == other.sigs } } impl Clone for LazySignatures { fn clone(&self) -> Self { self.assert_invariant(); LazySignatures { primary_key: self.primary_key.clone(), sigs: self.sigs.clone(), // Avoid blocking. If we fail to get the lock, reset the // signature states to unverified in the clone. states: if let Ok(states) = self.states.try_lock() { states.clone() } else { vec![SigState::Unverified; self.sigs.len()] }.into(), } } } /// Verification state of a signature. #[derive(Debug, Clone, PartialEq, Eq)] pub enum SigState { /// Not yet verified. Unverified, /// Verification was successful. Good, /// Verification failed. Bad, } impl LazySignatures { /// Asserts the invariant. fn assert_invariant(&self) { debug_assert_eq!(self.sigs.len(), self.states.lock().unwrap().len()); } /// Creates a vector of lazily verified signatures. /// /// The provided `primary_key` is used to verify the signatures. /// It should be shared across the certificate. pub fn new(primary_key: Arc<Key<key::PublicParts, key::PrimaryRole>>) -> Self { LazySignatures { primary_key, sigs: Default::default(), states: Default::default(), } } /// Like [`slice::is_empty`]. pub fn is_empty(&self) -> bool { self.assert_invariant(); self.sigs.is_empty() } /// Like [`std::mem::take`]. pub fn take(&mut self) -> Vec<Signature> { self.assert_invariant(); self.states.lock().unwrap().clear(); let r = mem::replace(&mut self.sigs, Vec::new()); self.assert_invariant(); r } /// Like [`Vec::push`]. pub fn push(&mut self, s: Signature) { self.assert_invariant(); self.sigs.push(s); self.states.lock().unwrap().push(SigState::Unverified); self.assert_invariant(); } /// Like [`Vec::append`]. pub fn append(&mut self, other: &mut LazySignatures) { // XXX check context self.assert_invariant(); other.assert_invariant(); self.sigs.append(&mut other.sigs); self.states.lock().unwrap().append(&mut other.states.lock().unwrap()); self.assert_invariant(); } /// Like [`slice::sort_by`]. pub fn sort_by<F>(&mut self, compare: F) where F: FnMut(&Signature, &Signature) -> Ordering, { self.assert_invariant(); self.sigs.sort_by(compare); self.states.lock().unwrap().iter_mut().for_each(|p| *p = SigState::Unverified); self.assert_invariant(); } /// Like [`Vec::dedup_by`]. pub fn dedup_by<F>(&mut self, same_bucket: F) where F: FnMut(&mut Signature, &mut Signature) -> bool, { self.assert_invariant(); self.sigs.dedup_by(same_bucket); { let mut states = self.states.lock().unwrap(); states.truncate(self.sigs.len()); states.iter_mut().for_each(|p| *p = SigState::Unverified); } self.assert_invariant(); } /// Like [`slice::iter_mut`], but gives out **potentially /// unverified** signatures. pub fn iter_mut_unverified(&mut self) -> impl Iterator<Item = &mut Signature> { self.assert_invariant(); self.sigs.iter_mut() } /// Like [`Vec::into_iter`], but gives out **potentially /// unverified** signatures. pub fn into_unverified(self) -> impl Iterator<Item = Signature> { self.assert_invariant(); self.sigs.into_iter() } /// Like [`Vec::as_slice`], but gives out **potentially /// unverified** signatures. pub fn as_slice_unverified(&self) -> &[Signature] { self.sigs.as_slice() } /// Like [`slice::iter`], but only gives out verified signatures. /// /// If this is a subkey binding, `subkey` must be the bundle's /// subkey. pub fn iter_verified<'a>(&'a self, subkey: Option<&'a Key<key::PublicParts, key::SubordinateRole>>) -> impl Iterator<Item = &'a Signature> + 'a { self.iter_intern(subkey) .filter_map(|(state, s)| match state { SigState::Good => Some(s), SigState::Bad => None, SigState::Unverified => unreachable!(), }) } /// Like [`slice::iter`], but only gives out bad signatures. /// /// If this is a subkey binding, `subkey` must be the bundle's /// subkey. pub fn iter_bad<'a>(&'a self, subkey: Option<&'a Key<key::PublicParts, key::SubordinateRole>>) -> impl Iterator<Item = &'a Signature> + 'a { self.iter_intern(subkey) .filter_map(|(state, s)| match state { SigState::Good => None, SigState::Bad => Some(s), SigState::Unverified => unreachable!(), }) } /// Like [`slice::iter`], but lazily verifies the signatures. /// /// If this is a subkey binding, `subkey` must be the bundle's /// subkey. fn iter_intern<'a>(&'a self, subkey: Option<&'a Key<key::PublicParts, key::SubordinateRole>>) -> impl Iterator<Item = (SigState, &'a Signature)> + 'a { self.assert_invariant(); self.sigs.iter().enumerate() .map(move |(i, s)| (self.verify_sig(i, subkey).expect("in bounds"), s)) } /// Verifies the `i`th signature. /// /// If this is a subkey binding, `subkey` must be the bundle's /// subkey. /// /// Returns an error if `i` is out of bounds. pub fn verify_sig(&self, i: usize, subkey: Option<&Key<key::PublicParts, key::SubordinateRole>>) -> Result<SigState> { self.assert_invariant(); if ! i < self.sigs.len() { return Err(Error::InvalidArgument(format!( "signature {} out of bound 0..{}", i, self.sigs.len())).into()); } let state = self.states.lock().unwrap().get(i).cloned(); match state { None => unreachable!("LazySignatures invariant violated"), Some(SigState::Unverified) => { let s = &self.sigs[i]; let mut r = s.verify_signature(&self.primary_key); if r.is_ok() && subkey.is_some() && s.key_flags().map(|kf| kf.for_signing()) .unwrap_or(false) { // The signature is good, but we still // need to verify the back sig. let mut any_backsig = Err(Error::BadSignature( "Primary key binding signature missing".into()).into()); for backsig in s.subpackets(SubpacketTag::EmbeddedSignature) { let result = if let SubpacketValue::EmbeddedSignature(sig) = backsig.value() { sig.verify_primary_key_binding( &self.primary_key, subkey.unwrap()) } else { unreachable!("subpackets(EmbeddedSignature) \ returns EmbeddedSignatures"); }; if result.is_ok() { // Mark the subpacket as authenticated by the // embedded signature. backsig.set_authenticated(true); } if any_backsig.is_err() { any_backsig = result; } } r = any_backsig; } let state = if r.is_ok() { SigState::Good } else { SigState::Bad }; // Remember the result. self.states.lock().unwrap()[i] = state.clone(); Ok(state) }, // Already verified, return the result. Some(state) => Ok(state), } } } ��������sequoia-openpgp-2.0.0/src/cert/parser/low_level/bundle.rs�������������������������������������������0000644�0000000�0000000�00000006023�10461020230�0021567�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������//! A context-free certificate component and its associated signatures. use std::sync::Arc; use crate::{ cert::ComponentBundle, packet::{ Key, Packet, Signature, Unknown, UserAttribute, UserID, key, }, policy::HashAlgoSecurity, }; /// A context-free certificate component and its associated signatures. #[derive(Debug)] pub struct PreBundle<C> { component: C, hash_algo_security: HashAlgoSecurity, signatures: Vec<Signature>, } assert_send_and_sync!(PreBundle<C> where C); /// A key (primary or subkey, public or private) and any associated /// signatures. /// /// [See the module level documentation.](self) pub type KeyBundle<KeyPart, KeyRole> = PreBundle<Key<KeyPart, KeyRole>>; /// A primary key and any associated signatures. /// /// [See the module level documentation.](self) pub type PrimaryKeyBundle<KeyPart> = KeyBundle<KeyPart, key::PrimaryRole>; /// A subkey and any associated signatures. /// /// [See the module level documentation.](self) pub type SubkeyBundle<KeyPart> = KeyBundle<KeyPart, key::SubordinateRole>; /// A User ID and any associated signatures. /// /// [See the module level documentation.](self) pub type UserIDBundle = PreBundle<UserID>; /// A User Attribute and any associated signatures. /// /// [See the module level documentation.](self) pub type UserAttributeBundle = PreBundle<UserAttribute>; /// An unknown component and any associated signatures. /// /// Note: all signatures are stored as certifications. /// /// [See the module level documentation.](self) pub type UnknownBundle = PreBundle<Unknown>; impl<C> PreBundle<C> { /// Creates a new component. pub fn new(component: C, hash_algo_security: HashAlgoSecurity, signatures: Vec<Signature>) -> PreBundle<C> { PreBundle { component, hash_algo_security, signatures, } } pub fn with_context(self, primary_key: Arc<Key<key::PublicParts, key::PrimaryRole>>) -> ComponentBundle<C> { ComponentBundle::new(self.component, self.hash_algo_security, self.signatures, primary_key) } pub fn into_packets(self) -> impl Iterator<Item=Packet> + Send + Sync where Packet: From<C>, { let p: Packet = self.component.into(); std::iter::once(p) .chain(self.signatures.into_iter().map(Into::into)) } } impl<P> SubkeyBundle<P> where P: key::KeyParts, { pub fn with_subkey_context(self, primary_key: Arc<Key<key::PublicParts, key::PrimaryRole>>) -> ComponentBundle<Key<P, key::SubordinateRole>> { ComponentBundle::new_subkey(self.component, self.hash_algo_security, self.signatures, primary_key) } } �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/src/cert/parser/low_level/grammar.lalrpop�������������������������������������0000644�0000000�0000000�00000024370�10461020230�0022776�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������// -*- mode: Rust; -*- use std::convert::TryInto; use std::sync::Arc; use crate::Error; use crate::packet::Any; use crate::packet::Signature; use crate::packet::UserID; use crate::packet::UserAttribute; use crate::packet::{key, Key}; use crate::packet::Unknown; use crate::Packet; use crate::policy::HashAlgoSecurity::CollisionResistance; use crate::cert::prelude::*; use crate::cert::parser::low_level::lexer; use crate::cert::parser::low_level::lexer::{Token, Component}; use crate::cert::parser::low_level::grammar_util::*; use crate::cert::ComponentBundles; use crate::cert::parser::low_level::bundle::{ PrimaryKeyBundle, SubkeyBundle, UserIDBundle, UserAttributeBundle, UnknownBundle, }; use lalrpop_util::ParseError; grammar; // The parser is used in two ways: it can either be used to check // whether a sequence of packets forms a Cert, or to build a Cert from a // sequence of packets. In the former case, we only need the packet // tags; in the latter case, we also need the packets. To handle both // situations, the token includes the tag and an optional packet. // When invoking the parser, it is essential, that either *all* tokens // have no packet, or they all have a packet; mixing the two types of // tokens will result in a crash. pub Cert: Option<Cert> = { <p:Primary> <c:OptionalComponents> =>? { match p { Some((Packet::PublicKey(_), _)) | Some((Packet::SecretKey(_), _)) => { let (key, sigs) = match p { Some((Packet::PublicKey(key), sigs)) => (key, sigs), Some((Packet::SecretKey(key), sigs)) => (key.into(), sigs), _ => unreachable!(), }; let c = c.unwrap(); let sec = key.hash_algo_security(); let pk = Arc::new(key.clone().take_secret().0); let mut cert = Cert { primary: PrimaryKeyBundle::new(key, sec, sigs) .with_context(pk.clone()), subkeys: ComponentBundles::default(), userids: ComponentBundles::default(), user_attributes: ComponentBundles::default(), unknowns: ComponentBundles::default(), bad: vec![], }; for c in c.into_iter() { match c { Component::SubkeyBundle(b) => cert.subkeys.push(b.with_subkey_context(pk.clone())), Component::UserIDBundle(b) => cert.userids.push(b.with_context(pk.clone())), Component::UserAttributeBundle(b) => cert.user_attributes.push(b.with_context(pk.clone())), Component::UnknownBundle(b) => cert.unknowns.push(b.with_context(pk.clone())), } } Ok(Some(cert)) } Some((Packet::Unknown(unknown), sigs)) => { let msg = unknown.error().to_string(); let mut packets: Vec<Packet> = Default::default(); packets.push(unknown.into()); for sig in sigs { packets.push(sig.into()); } for c in c.unwrap_or_default().into_iter() { match c { Component::SubkeyBundle(b) => b.into_packets().for_each(|p| packets.push(p)), Component::UserIDBundle(b) => b.into_packets().for_each(|p| packets.push(p)), Component::UserAttributeBundle(b) => b.into_packets().for_each(|p| packets.push(p)), Component::UnknownBundle(b) => b.into_packets().for_each(|p| packets.push(p)), } } Err(ParseError::User { error: Error::UnsupportedCert( format!("Unsupported primary key: {}", msg), packets), }) } None => { // Just validating a cert... assert!(c.is_none() || c.unwrap().len() == 0); Ok(None) } Some((pkt, _)) => unreachable!("Expected key or unknown packet, got {:?}", pkt), } } }; Primary: Option<(Packet, Vec<Signature>)> = { <pk:PrimaryKey> <sigs:OptionalSignatures> => { if let Some(pk) = pk { Some((pk, sigs.unwrap())) } else { // Just validating a cert... assert!(sigs.is_none() || sigs.unwrap().len() == 0); None } } } PrimaryKey: Option<Packet> = { <t:PUBLIC_KEY> => t.into(), <t:SECRET_KEY> => t.into(), }; OptionalSignatures: Option<Vec<Signature>> = { => Some(vec![]), <sigs:OptionalSignatures> <sig:SIGNATURE> => { match sig { Token::Signature(Some(Packet::Signature(sig))) => { assert!(sigs.is_some()); let mut sigs = sigs.unwrap(); sigs.push(sig); Some(sigs) } Token::Signature(Some(Packet::Unknown(_sig))) => { // Ignore unsupported / bad signatures. assert!(sigs.is_some()); sigs } // Just validating a cert... Token::Signature(None) => return None, tok => unreachable!("Expected signature token, got {:?}", tok), } }, // A trust packet can go wherever a signature can go, but they // are ignored. <OptionalSignatures> TRUST, } OptionalComponents: Option<Vec<Component>> = { => Some(vec![]), <cs:OptionalComponents> <c:Component> => { if let Some(c) = c { let mut cs = cs.unwrap(); cs.push(c); Some(cs) } else { // Just validating a cert... None } }, } Component: Option<Component> = { <key:Subkey> <sigs:OptionalSignatures> => { match key { Some(Ok(key)) => { let sigs = sigs.unwrap(); let sec = key.hash_algo_security(); Some(Component::SubkeyBundle( SubkeyBundle::new(key, sec, sigs))) }, Some(Err(u)) => Some(Component::UnknownBundle( UnknownBundle::new(u, CollisionResistance, sigs.unwrap_or_default()))), // Just validating a cert... None => None, } }, <u:UserID> <sigs:OptionalSignatures> => { match u { Some(Ok(u)) => { let sigs = sigs.unwrap(); let sec = u.hash_algo_security(); Some(Component::UserIDBundle( UserIDBundle::new(u, sec, sigs))) }, Some(Err(u)) => Some(Component::UnknownBundle( UnknownBundle::new(u, CollisionResistance, sigs.unwrap_or_default()))), // Just validating a cert... None => None, } }, <u:UserAttribute> <sigs:OptionalSignatures> => { match u { Some(Ok(u)) => { let sigs = sigs.unwrap(); let sec = u.hash_algo_security(); Some(Component::UserAttributeBundle( UserAttributeBundle::new(u, sec, sigs))) }, Some(Err(u)) => Some(Component::UnknownBundle( UnknownBundle::new(u, CollisionResistance, sigs.unwrap_or_default()))), // Just validating a cert... None => None, } }, <u:Unknown> <sigs:OptionalSignatures> => { match u { Some(u) => { let sigs = sigs.unwrap(); let sec = u.hash_algo_security(); Some(Component::UnknownBundle( UnknownBundle::new(u, sec, sigs))) }, // Just validating a cert... None => None, } }, } Subkey: Option<PacketOrUnknown<Key<key::PublicParts, key::SubordinateRole>>> = { <t:PUBLIC_SUBKEY> => { match Option::<Packet>::from(t) { Some(p) => Some(p.downcast().map_err( |p| p.try_into().expect("infallible for unknown and this packet"))), // Just validating a cert... None => None, } }, <t:SECRET_SUBKEY> => { match Option::<Packet>::from(t) { Some(p) => Some(Any::<key::SecretSubkey>::downcast(p) .map_err( |p| p.try_into().expect("infallible for unknown and this packet")) .map(|sk| sk.parts_into_public())), // Just validating a cert... None => None, } }, } UserID: Option<PacketOrUnknown<UserID>> = { <t:USERID> => { match Option::<Packet>::from(t) { Some(p) => Some(p.downcast().map_err( |p| p.try_into().expect("infallible for unknown and this packet"))), // Just validating a cert... None => None, } }, } UserAttribute: Option<PacketOrUnknown<UserAttribute>> = { <t:USER_ATTRIBUTE> => { match Option::<Packet>::from(t) { Some(p) => Some(p.downcast().map_err( |p| p.try_into().expect("infallible for unknown and this packet"))), // Just validating a cert... None => None, } }, } Unknown: Option<Unknown> = { <t:UNKNOWN> => { match Option::<Packet>::from(t) { Some(p) => p.try_into().ok(), // Just validating a cert... None => None, } }, } extern { type Location = usize; type Error = Error; enum lexer::Token { PUBLIC_KEY => lexer::Token::PublicKey(_), SECRET_KEY => lexer::Token::SecretKey(_), PUBLIC_SUBKEY => lexer::Token::PublicSubkey(_), SECRET_SUBKEY => lexer::Token::SecretSubkey(_), USERID => lexer::Token::UserID(_), USER_ATTRIBUTE => lexer::Token::UserAttribute(_), SIGNATURE => lexer::Token::Signature(_), TRUST => lexer::Token::Trust(_), UNKNOWN => lexer::Token::Unknown(_, _), } } ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/src/cert/parser/low_level/grammar_util.rs�������������������������������������0000644�0000000�0000000�00000000606�10461020230�0023002�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������//! Contains utility functions and definitions for the grammar. use crate::{ packet::Unknown, }; /// A local alias that is either a concrete packet body or a (likely /// Unknown) packet. /// /// This is used in the parser grammar, but cannot be defined there. /// lalrpop's parser doesn't allow polymorphic type aliases. pub type PacketOrUnknown<T> = std::result::Result<T, Unknown>; ��������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/src/cert/parser/low_level/lexer.rs��������������������������������������������0000644�0000000�0000000�00000011435�10461020230�0021440�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������use std::fmt; use crate::Error; use crate::Packet; use crate::packet::Tag; use crate::cert::parser::low_level::bundle::{ SubkeyBundle, UserIDBundle, UserAttributeBundle, UnknownBundle, }; use crate::packet::key; // The type of the parser's input. // // The parser iterators over tuples consisting of the token's starting // position, the token itself, and the token's ending position. pub(crate) type LexerItem<Tok, Loc, Error> = ::std::result::Result<(Loc, Tok, Loc), Error>; /// The components of an OpenPGP Message. #[derive(Debug, Clone, PartialEq)] pub enum Token { /// A `PublicKey` packet. PublicKey(Option<Packet>), /// A `SecretKey` packet. SecretKey(Option<Packet>), /// A `PublicSubkey` packet. PublicSubkey(Option<Packet>), /// A `SecretSubkey` packet. SecretSubkey(Option<Packet>), /// A `UserID` packet. UserID(Option<Packet>), /// A `UserAttribute` packet. UserAttribute(Option<Packet>), /// A `Signature` packet. Signature(Option<Packet>), /// A `Trust` packet Trust(Option<Packet>), /// An `Unknown` packet. Unknown(Tag, Option<Packet>), } assert_send_and_sync!(Token); /// Internal data-structure used by the parser. /// /// Due to the way the parser code is generated, it must be marked as /// public. But, since this module is not public, it will not /// actually be exported to users of the library. #[derive(Debug)] pub enum Component { SubkeyBundle(SubkeyBundle<key::PublicParts>), UserIDBundle(UserIDBundle), UserAttributeBundle(UserAttributeBundle), UnknownBundle(UnknownBundle), } assert_send_and_sync!(Component); impl<'a> From<&'a Token> for Tag { fn from(token: &'a Token) -> Self { match token { Token::PublicKey(_) => Tag::PublicKey, Token::SecretKey(_) => Tag::SecretKey, Token::PublicSubkey(_) => Tag::PublicSubkey, Token::SecretSubkey(_) => Tag::SecretSubkey, Token::UserID(_) => Tag::UserID, Token::UserAttribute(_) => Tag::UserAttribute, Token::Signature(_) => Tag::Signature, Token::Trust(_) => Tag::Trust, Token::Unknown(tag, _) => *tag, } } } impl From<Token> for Tag { fn from(token: Token) -> Self { (&token).into() } } impl From<Token> for Option<Packet> { fn from(token: Token) -> Self { match token { Token::PublicKey(p @ Some(_)) => p, Token::SecretKey(p @ Some(_)) => p, Token::PublicSubkey(p @ Some(_)) => p, Token::SecretSubkey(p @ Some(_)) => p, Token::UserID(p @ Some(_)) => p, Token::UserAttribute(p @ Some(_)) => p, Token::Signature(p @ Some(_)) => p, Token::Trust(p @ Some(_)) => p, Token::Unknown(_, p @ Some(_)) => p, Token::PublicKey(None) | Token::SecretKey(None) | Token::PublicSubkey(None) | Token::SecretSubkey(None) | Token::UserID(None) | Token::UserAttribute(None) | Token::Signature(None) | Token::Trust(None) | Token::Unknown(_, None) => None, } } } impl std::convert::TryFrom<Packet> for Token { type Error = Packet; fn try_from(p: Packet) -> std::result::Result<Self, Self::Error> { match p.tag() { Tag::PublicKey => Ok(Token::PublicKey(Some(p))), Tag::SecretKey => Ok(Token::SecretKey(Some(p))), Tag::PublicSubkey => Ok(Token::PublicSubkey(Some(p))), Tag::SecretSubkey => Ok(Token::SecretSubkey(Some(p))), Tag::UserID => Ok(Token::UserID(Some(p))), Tag::UserAttribute => Ok(Token::UserAttribute(Some(p))), Tag::Signature => Ok(Token::Signature(Some(p))), Tag::Trust => Ok(Token::Trust(Some(p))), t @ Tag::Unknown(_) => Ok(Token::Unknown(t, Some(p))), t @ Tag::Private(_) => Ok(Token::Unknown(t, Some(p))), _ => Err(p), } } } impl fmt::Display for Token { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{:?}", self) } } pub(crate) struct Lexer<'input> { iter: Box<dyn Iterator<Item=(usize, &'input Token)> + 'input>, } impl<'input> Iterator for Lexer<'input> { type Item = LexerItem<Token, usize, Error>; fn next(&mut self) -> Option<Self::Item> { let n = self.iter.next().map(|(pos, tok)| (pos, tok.clone())); if let Some((pos, tok)) = n { Some(Ok((pos, tok, pos))) } else { None } } } impl<'input> Lexer<'input> { /// Uses a raw sequence of tokens as input to the parser. pub(crate) fn from_tokens(raw: &'input [Token]) -> Self { Lexer { iter: Box::new(raw.iter().enumerate()) } } } �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/src/cert/parser/low_level/mod.rs����������������������������������������������0000644�0000000�0000000�00000005305�10461020230�0021077�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������use lalrpop_util::ParseError; use crate::{ Error, packet::Tag, }; pub(crate) mod lexer; lalrpop_util::lalrpop_mod!( #[allow(clippy::all)] #[allow(unused_parens)] grammar, "/cert/parser/low_level/grammar.rs" ); pub(crate) mod bundle; pub(crate) mod grammar_util; pub(crate) use self::lexer::Token; pub(crate) use self::lexer::Lexer; pub(crate) use self::grammar::CertParser; // Converts a ParseError<usize, Token, Error> to a // ParseError<usize, Tag, Error>. // // Justification: a Token is a tuple containing a Tag and a Packet. // This function essentially drops the Packet. Dropping the packet was // necessary, because packets were not Sync, but Error, which we want // to convert ParseErrors to, was. Since we don't need the packet in // general anyways, changing the Token to a Tag is a simple and // sufficient fix. Unfortunately, this conversion is a bit ugly and // will break if lalrpop ever extends ParseError. // // This justification is no longer up-to-date because Packet is now // also Sync. However, we didn't catch this when we made Packet Sync, // and now the justification is simply that CertParserError::Parser(_) // expects a Tag, not a Packet. It is not public, so we could change // it. pub(crate) fn parse_error_downcast(e: ParseError<usize, Token, Error>) -> ParseError<usize, Tag, Error> { match e { ParseError::UnrecognizedToken { token: (start, t, end), expected, } => ParseError::UnrecognizedToken { token: (start, t.into(), end), expected, }, ParseError::ExtraToken { token: (start, t, end), } => ParseError::ExtraToken { token: (start, t.into(), end), }, ParseError::InvalidToken { location } => ParseError::InvalidToken { location }, ParseError::User { error } => ParseError::User { error }, ParseError::UnrecognizedEof { location, expected } => ParseError::UnrecognizedEof { location, expected }, } } pub(crate) fn parse_error_to_openpgp_error(e: ParseError<usize, Tag, Error>) -> Error { match e { ParseError::User { error } => error, e => Error::MalformedCert(format!("{}", e)), } } /// Errors that CertValidator::check may return. #[derive(Debug, Clone)] pub enum CertParserError { /// A parser error. Parser(ParseError<usize, Tag, Error>), /// An OpenPGP error. OpenPGP(Error), } assert_send_and_sync!(CertParserError); impl From<CertParserError> for anyhow::Error { fn from(err: CertParserError) -> Self { match err { CertParserError::Parser(p) => p.into(), CertParserError::OpenPGP(p) => p.into(), } } } ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/src/cert/parser/mod.rs��������������������������������������������������������0000644�0000000�0000000�00000177423�10461020230�0017122�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������use std::io; use std::mem; use std::vec; use buffered_reader::BufferedReader; use lalrpop_util::ParseError; use crate::{ Error, KeyHandle, packet::Tag, Packet, parse::{ Cookie, Parse, PacketParserResult, PacketParser }, Result, cert::bundle::ComponentBundle, Cert, }; mod low_level; use low_level::{ Lexer, CertParser as CertLowLevelParser, CertParserError, Token, parse_error_downcast, }; const TRACE : bool = false; /// Whether a packet sequence is a valid keyring. /// /// This is used #[derive(Debug)] pub(crate) enum KeyringValidity { /// The packet sequence is a valid keyring. Keyring, /// The packet sequence is a valid keyring prefix. KeyringPrefix, /// The packet sequence is definitely not a keyring. Error(anyhow::Error), } #[allow(unused)] impl KeyringValidity { /// Returns whether the packet sequence is a valid keyring. /// /// Note: a `KeyringValidator` will only return this after /// `KeyringValidator::finish` has been called. pub fn is_keyring(&self) -> bool { matches!(self, KeyringValidity::Keyring) } /// Returns whether the packet sequence is a valid Keyring prefix. /// /// Note: a `KeyringValidator` will only return this before /// `KeyringValidator::finish` has been called. pub fn is_keyring_prefix(&self) -> bool { matches!(self, KeyringValidity::KeyringPrefix) } /// Returns whether the packet sequence is definitely not a valid /// keyring. pub fn is_err(&self) -> bool { matches!(self, KeyringValidity::Error(_)) } } /// Used to help validate that a packet sequence is a valid keyring. #[derive(Debug)] pub(crate) struct KeyringValidator { tokens: Vec<Token>, n_keys: usize, n_packets: usize, finished: bool, // If we know that the packet sequence is invalid. error: Option<CertParserError>, } impl Default for KeyringValidator { fn default() -> Self { KeyringValidator::new() } } #[allow(unused)] impl KeyringValidator { /// Instantiates a new `KeyringValidator`. pub fn new() -> Self { KeyringValidator { tokens: vec![], n_keys: 0, n_packets: 0, finished: false, error: None, } } /// Returns whether the packet sequence is a valid keyring. /// /// Note: a `KeyringValidator` will only return this after /// `KeyringValidator::finish` has been called. pub fn is_keyring(&self) -> bool { self.check().is_keyring() } /// Returns whether the packet sequence forms a valid keyring /// prefix. /// /// Note: a `KeyringValidator` will only return this before /// `KeyringValidator::finish` has been called. pub fn is_keyring_prefix(&self) -> bool { self.check().is_keyring_prefix() } /// Returns whether the packet sequence is definitely not a valid /// keyring. pub fn is_err(&self) -> bool { self.check().is_err() } /// Add the token `token` to the token stream. pub fn push_token(&mut self, token: Token) { assert!(!self.finished); if self.error.is_some() { return; } if let Token::PublicKey(_) | Token::SecretKey(_) = token { self.tokens.clear(); self.n_keys += 1; } self.n_packets += 1; match (&token, self.tokens.last()) { (Token::Signature(None), Some(Token::Signature(None))) => { // Compress multiple signatures in a row. This is // essential for dealing with flooded keys }, _ => self.tokens.push(token), } } /// Add a packet of type `tag` to the token stream. pub fn push(&mut self, tag: Tag) { let token = match tag { Tag::PublicKey => Token::PublicKey(None), Tag::SecretKey => Token::SecretKey(None), Tag::PublicSubkey => Token::PublicSubkey(None), Tag::SecretSubkey => Token::SecretSubkey(None), Tag::UserID => Token::UserID(None), Tag::UserAttribute => Token::UserAttribute(None), Tag::Signature => Token::Signature(None), Tag::Trust => Token::Trust(None), Tag::Marker => { // Ignore Marker Packet. RFC4880, section 5.8: // // Such a packet MUST be ignored when received. return; }, Tag::Unknown(_) => Token::Unknown(tag, None), Tag::Private(_) => Token::Unknown(tag, None), _ => { // Unknown token. self.error = Some(CertParserError::OpenPGP( Error::MalformedMessage( format!("Invalid Cert: {:?} packet (#{}) not expected", tag, self.n_packets)))); self.tokens.clear(); return; } }; self.push_token(token) } /// Notes that the entire message has been seen. /// /// This function may only be called once. /// /// Once called, this function will no longer return /// `KeyringValidity::KeyringPrefix`. pub fn finish(&mut self) { assert!(!self.finished); self.finished = true; } /// Returns whether the token stream corresponds to a valid /// keyring. /// /// This returns a tri-state: if the packet sequence is a valid /// Keyring, it returns `KeyringValidity::Keyring`, if the packet /// sequence is invalid, then it returns `KeyringValidity::Error`. /// If the packet sequence that has been processed so far is a /// valid prefix, then it returns /// `KeyringValidity::KeyringPrefix`. /// /// Note: if `KeyringValidator::finish()` *hasn't* been called, /// then this function will only ever return either /// `KeyringValidity::KeyringPrefix` or `KeyringValidity::Error`. /// Once `KeyringValidity::finish()` has been called, then it will /// only return either `KeyringValidity::Keyring` or /// `KeyringValidity::Error`. pub fn check(&self) -> KeyringValidity { if let Some(ref err) = self.error { return KeyringValidity::Error((*err).clone().into()); } let r = CertLowLevelParser::new().parse( Lexer::from_tokens(&self.tokens)); if self.finished { match r { Ok(_) => KeyringValidity::Keyring, Err(err) => KeyringValidity::Error( CertParserError::Parser(parse_error_downcast(err)).into()), } } else { match r { Ok(_) => KeyringValidity::KeyringPrefix, Err(ParseError::UnrecognizedEof { .. }) => KeyringValidity::KeyringPrefix, Err(err) => KeyringValidity::Error( CertParserError::Parser(parse_error_downcast(err)).into()), } } } } /// Whether a packet sequence is a valid Cert. #[derive(Debug)] #[allow(unused)] pub(crate) enum CertValidity { /// The packet sequence is a valid Cert. Cert, /// The packet sequence is a valid Cert prefix. CertPrefix, /// The packet sequence is definitely not a Cert. Error(anyhow::Error), } #[allow(unused)] impl CertValidity { /// Returns whether the packet sequence is a valid Cert. /// /// Note: a `CertValidator` will only return this after /// `CertValidator::finish` has been called. pub fn is_cert(&self) -> bool { matches!(self, CertValidity::Cert) } /// Returns whether the packet sequence is a valid Cert prefix. /// /// Note: a `CertValidator` will only return this before /// `CertValidator::finish` has been called. pub fn is_cert_prefix(&self) -> bool { matches!(self, CertValidity::CertPrefix) } /// Returns whether the packet sequence is definitely not a valid /// Cert. pub fn is_err(&self) -> bool { matches!(self, CertValidity::Error(_)) } } /// Used to help validate that a packet sequence is a valid Cert. #[derive(Debug)] pub(crate) struct CertValidator(KeyringValidator); impl Default for CertValidator { fn default() -> Self { CertValidator::new() } } impl CertValidator { /// Instantiates a new `CertValidator`. pub fn new() -> Self { CertValidator(Default::default()) } /// Add the token `token` to the token stream. #[cfg(test)] pub fn push_token(&mut self, token: Token) { self.0.push_token(token) } /// Add a packet of type `tag` to the token stream. pub fn push(&mut self, tag: Tag) { self.0.push(tag) } /// Note that the entire message has been seen. /// /// This function may only be called once. /// /// Once called, this function will no longer return /// `CertValidity::CertPrefix`. pub fn finish(&mut self) { self.0.finish() } /// Returns whether the token stream corresponds to a valid /// Cert. /// /// This returns a tri-state: if the packet sequence is a valid /// Cert, it returns `CertValidity::Cert`, if the packet sequence /// is invalid, then it returns `CertValidity::Error`. If the /// packet sequence that has been processed so far is a valid /// prefix, then it returns `CertValidity::CertPrefix`. /// /// Note: if `CertValidator::finish()` *hasn't* been called, then /// this function will only ever return either /// `CertValidity::CertPrefix` or `CertValidity::Error`. Once /// `CertValidity::finish()` has been called, then it will only /// return either `CertValidity::Cert` or `CertValidity::Error`. pub fn check(&self) -> CertValidity { if self.0.n_keys > 1 { return CertValidity::Error(Error::MalformedMessage( "More than one key found, this is a keyring".into()).into()); } match self.0.check() { KeyringValidity::Keyring => CertValidity::Cert, KeyringValidity::KeyringPrefix => CertValidity::CertPrefix, KeyringValidity::Error(e) => CertValidity::Error(e), } } } /// An iterator over a sequence of certificates, i.e., an OpenPGP keyring. /// /// The source of packets is a fallible iterator over [`Packet`]s. In /// this way, it is possible to propagate parse errors. /// /// A `CertParser` returns each [`TPK`] or [`TSK`] that it encounters. /// Note: if you don't actually need all the certificates, it is /// usually faster to use a [`RawCertParser`] and only fully parse and /// canonicalize those certificates that are relevant. /// /// [`RawCertParser`]: crate::cert::raw::RawCertParser /// /// A `CertParser`'s behavior can be modeled using a simple state /// machine. /// /// In the first and initial state, it looks for the start of a /// certificate, a [`Public Key`] packet or a [`Secret Key`] packet. /// When it encounters such a packet it buffers it, and transitions to /// the second state. Any other packet or an error causes it to emit /// an error and stay in the same state. When the source of packets /// is exhausted, it enters the `End` state. /// /// In the second state, it looks for packets that belong to a /// certificate's body. If it encounters a valid body packet, then it /// buffers it and stays in the same state. If it encounters the /// start of a certificate, then it emits the buffered certificate, /// buffers the packet, and stays in the same state. If it encounters /// an invalid packet (e.g., a [`Literal Data`] packet), it emits two /// items, the buffered certificate, and an error, and then it /// transitions back to the initial state. When the source of packets /// is exhausted, it emits the buffered certificate and enters the end /// state. /// /// In the end state, it emits `None`. /// /// ```text /// Invalid Packet / Error /// ,------------------------. /// v | /// Not a +---------+ +---------+ /// Start .-> | Looking | -------------> | Looking | <-. Cert /// of Cert | | for | Start | for | | Body /// Packet | | Start | of Cert | Cert | | Packet /// / Error `-- | of Cert | Packet | Body | --' /// +---------+ .-> +---------+ /// | | | | /// | `------' | /// | Start of Cert Packet | /// | | /// EOF | +-----+ | EOF /// `------> | End | <---------' /// +-----+ /// | ^ /// `--' /// ``` /// /// The parser does not recurse into containers, thus when it /// encounters a container like a [`Compressed Data`] Packet, it will /// return an error even if the container contains a valid /// certificate. /// /// The parser considers unknown packets to be valid body packets. /// (In a [`Cert`], these show up as [`Unknown`] components.) The /// goal is to provide some future compatibility. /// /// [`Packet`]: crate::packet::Packet /// [`TPK`]: https://www.rfc-editor.org/rfc/rfc9580.html#section-10.1 /// [`TSK`]: https://www.rfc-editor.org/rfc/rfc9580.html#section-10.2 /// [`Public Key`]: super::Packet::PublicKey /// [`Secret Key`]: super::Packet::SecretKey /// [`Literal Data`]: super::Packet::Literal /// [`Compressed Data`]: super::Packet::CompressedData /// [`Cert`]: super::Cert /// [`Unknown`]: super::Packet::Unknown /// /// # Examples /// /// Print information about all certificates in a keyring: /// /// ```rust /// use sequoia_openpgp as openpgp; /// # use openpgp::Result; /// use openpgp::parse::Parse; /// use openpgp::parse::PacketParser; /// # use openpgp::serialize::Serialize; /// use openpgp::cert::prelude::*; /// /// # fn main() -> Result<()> { /// # let (alice, _) = /// # CertBuilder::general_purpose(Some("alice@example.org")) /// # .generate()?; /// # let (bob, _) = /// # CertBuilder::general_purpose(Some("bob@example.org")) /// # .generate()?; /// # /// # let mut keyring = Vec::new(); /// # alice.serialize(&mut keyring)?; /// # bob.serialize(&mut keyring)?; /// # /// # let mut count = 0; /// let ppr = PacketParser::from_bytes(&keyring)?; /// for certo in CertParser::from(ppr) { /// match certo { /// Ok(cert) => { /// println!("Key: {}", cert.fingerprint()); /// for ua in cert.userids() { /// println!(" User ID: {}", ua.userid()); /// } /// # count += 1; /// } /// Err(err) => { /// eprintln!("Error reading keyring: {}", err); /// # unreachable!(); /// } /// } /// } /// # assert_eq!(count, 2); /// # Ok(()) /// # } /// ``` /// /// When an invalid packet is encountered, an error is returned and /// parsing continues: /// /// ```rust /// use sequoia_openpgp as openpgp; /// # use openpgp::Result; /// # use openpgp::serialize::Serialize; /// use openpgp::cert::prelude::*; /// use openpgp::packet::prelude::*; /// use openpgp::types::DataFormat; /// /// # fn main() -> Result<()> { /// let mut lit = Literal::new(DataFormat::Unicode); /// lit.set_body(b"test".to_vec()); /// /// let (alice, _) = /// CertBuilder::general_purpose(Some("alice@example.org")) /// .generate()?; /// let (bob, _) = /// CertBuilder::general_purpose(Some("bob@example.org")) /// .generate()?; /// /// let mut packets : Vec<Packet> = Vec::new(); /// packets.extend(alice.clone().into_packets()); /// packets.push(lit.clone().into()); /// packets.push(lit.clone().into()); /// packets.extend(bob.clone().into_packets()); /// /// let r : Vec<Result<Cert>> = CertParser::from(packets).collect(); /// assert_eq!(r.len(), 4); /// assert_eq!(r[0].as_ref().unwrap().fingerprint(), alice.fingerprint()); /// assert!(r[1].is_err()); /// assert!(r[2].is_err()); /// assert_eq!(r[3].as_ref().unwrap().fingerprint(), bob.fingerprint()); /// # Ok(()) /// # } /// ``` #[derive(Default)] pub struct CertParser<'a> { source: Option<Box<dyn Iterator<Item=Result<Packet>> + 'a + Send + Sync>>, packets: Vec<Packet>, queued_error: Option<anyhow::Error>, filter: Vec<Box<dyn Send + Sync + Fn(&Cert, bool) -> bool + 'a>>, } assert_send_and_sync!(CertParser<'_>); // When using a `PacketParser`, we never use the `Iter` variant. // Nevertheless, we need to provide a concrete type. // vec::IntoIter<Packet> is about as good as any other. impl<'a> From<PacketParserResult<'a>> for CertParser<'a> { /// Initializes a `CertParser` from a `PacketParser`. fn from(ppr: PacketParserResult<'a>) -> Self { use std::io::ErrorKind::UnexpectedEof; let mut parser : Self = Default::default(); if let PacketParserResult::Some(pp) = ppr { let mut ppp : Box<Option<PacketParser>> = Box::new(Some(pp)); let mut retry_with_reader = Box::new(None); parser.source = Some( Box::new(std::iter::from_fn(move || { tracer!(TRACE, "PacketParserResult::next", 0); if let Some(reader) = retry_with_reader.take() { // Try to find the next (armored) blob. match PacketParser::from_cookie_reader(reader) { Ok(PacketParserResult::Some(pp)) => { // We read at least one packet. Try // to parse the next cert. *ppp = Some(pp); }, Ok(PacketParserResult::EOF(_)) => (), // No dice. Err(err) => { // See if we just reached the end. if let Some(e) = err.downcast_ref::<io::Error>() { if e.kind() == UnexpectedEof { return None; } } return Some(Err(err)); }, } } if let Some(mut pp) = ppp.take() { if let Packet::Unknown(_) = pp.packet { // Buffer unknown packets. This may be a // signature that we don't understand, and // keeping it intact is important. if let Err(e) = pp.buffer_unread_content() { return Some(Err(e)); } } match pp.next() { Ok((packet, ppr)) => { match ppr { PacketParserResult::Some(pp) => *ppp = Some(pp), PacketParserResult::EOF(eof) => *retry_with_reader = Some(eof.into_reader()), } t!("PacketParser::next yielded a {}", packet.tag()); Some(Ok(packet)) }, Err(err) => { t!("PacketParser::next returned an error: {}.", err); Some(Err(err)) } } } else { None } }))); } parser } } impl<'a> From<Vec<Result<Packet>>> for CertParser<'a> { fn from(p: Vec<Result<Packet>>) -> CertParser<'a> { CertParser::from_iter(p) } } impl<'a> From<Vec<Packet>> for CertParser<'a> { fn from(p: Vec<Packet>) -> CertParser<'a> { CertParser::from_iter(p) } } impl<'a> Parse<'a, CertParser<'a>> for CertParser<'a> { /// Initializes a `CertParser` from a `BufferedReader`. fn from_buffered_reader<R>(reader: R) -> Result<CertParser<'a>> where R: BufferedReader<Cookie> + 'a, { Ok(Self::from(PacketParser::from_buffered_reader(reader.into_boxed())?)) } } impl<'a> crate::seal::Sealed for CertParser<'a> {} impl<'a> CertParser<'a> { /// Creates a `CertParser` from a `Result<Packet>` iterator. /// /// Note: because we implement `From<Packet>` for /// `Result<Packet>`, it is possible to pass in an iterator over /// `Packet`s. /// /// # Examples /// /// From a `Vec<Packet>`: /// /// ```rust /// use sequoia_openpgp as openpgp; /// # use openpgp::Result; /// # use openpgp::PacketPile; /// # use openpgp::parse::Parse; /// # use openpgp::serialize::Serialize; /// use openpgp::cert::prelude::*; /// use openpgp::packet::prelude::*; /// /// # fn main() -> Result<()> { /// # let (alice, _) = /// # CertBuilder::general_purpose(Some("alice@example.org")) /// # .generate()?; /// # let (bob, _) = /// # CertBuilder::general_purpose(Some("bob@example.org")) /// # .generate()?; /// # /// # let mut keyring = Vec::new(); /// # alice.serialize(&mut keyring)?; /// # bob.serialize(&mut keyring)?; /// # /// # let mut count = 0; /// # let pp = PacketPile::from_bytes(&keyring)?; /// # let packets : Vec<Packet> = pp.into(); /// for certo in CertParser::from_iter(packets) { /// match certo { /// Ok(cert) => { /// println!("Key: {}", cert.fingerprint()); /// for ua in cert.userids() { /// println!(" User ID: {}", ua.userid()); /// } /// # count += 1; /// } /// Err(err) => { /// eprintln!("Error reading keyring: {}", err); /// # unreachable!(); /// } /// } /// } /// # assert_eq!(count, 2); /// # Ok(()) /// # } /// ``` pub fn from_iter<I, J>(iter: I) -> Self where I: 'a + IntoIterator<Item=J>, J: 'a + Into<Result<Packet>>, <I as IntoIterator>::IntoIter: Send + Sync, { Self { source: Some(Box::new(iter.into_iter().map(Into::into))), ..Default::default() } } /// Filters the Certs prior to validation. /// /// By default, the `CertParser` only returns valdiated [`Cert`]s. /// Checking that a certificate's self-signatures are valid, /// however, is computationally expensive, and not always /// necessary. For example, when looking for a small number of /// certificates in a large keyring, most certificates can be /// immediately discarded. That is, it is more efficient to /// filter, validate, and double check, than to validate and /// filter. (It is necessary to double-check, because the check /// might have been on an invalid part. For example, if searching /// for a key with a particular Key ID, a matching key might not /// have any self signatures.) /// /// If the `CertParser` gave out unvalidated `Cert`s, and provided /// an interface to validate them, then the caller could implement /// this check-validate-double-check pattern. Giving out /// unvalidated `Cert`s, however, is dangerous: inevitably, a /// `Cert` will be used without having been validated in a context /// where it should have been. /// /// This function avoids this class of bugs while still providing /// a mechanism to filter `Cert`s prior to validation: the caller /// provides a callback that is invoked on the *unvalidated* /// `Cert`. If the callback returns `true`, then the parser /// validates the `Cert`, and invokes the callback *a second time* /// to make sure the `Cert` is really wanted. If the callback /// returns false, then the `Cert` is skipped. /// /// Note: calling this function multiple times on a single /// `CertParser` will not replace the existing filter, but install /// multiple filters. /// /// [`Cert`]: super::Cert /// /// # Examples /// /// ```rust /// use sequoia_openpgp as openpgp; /// # use openpgp::Result; /// # use openpgp::parse::{Parse, PacketParser}; /// use openpgp::cert::prelude::*; /// /// # fn main() -> Result<()> { /// # let ppr = PacketParser::from_bytes(b"")?; /// # let some_keyid = "C2B819056C652598".parse()?; /// for certr in CertParser::from(ppr) /// .unvalidated_cert_filter(|cert, _| { /// for component in cert.keys() { /// if component.key().keyid() == some_keyid { /// return true; /// } /// } /// false /// }) /// { /// match certr { /// Ok(cert) => { /// // The Cert contains the subkey. /// } /// Err(err) => { /// eprintln!("Error reading keyring: {}", err); /// } /// } /// } /// # Ok(()) /// # } /// ``` pub fn unvalidated_cert_filter<F>(mut self, filter: F) -> Self where F: 'a + Send + Sync + Fn(&Cert, bool) -> bool { self.filter.push(Box::new(filter)); self } // Parses the next packet in the packet stream. // // If we complete parsing a Cert, returns the Cert. Otherwise, // returns None. fn parse(&mut self, p: Packet) -> Result<Option<Cert>> { tracer!(TRACE, "CertParser::parse", 0); match p { Packet::Marker(_) => { // Ignore Marker Packet. RFC4880, section 5.8: // // Such a packet MUST be ignored when received. return Ok(None); }, Packet::Padding(_) => { // "[Padding packets] MUST be ignored when received.", // section 5.14 of RFC 9580. return Ok(None); }, p if Cert::valid_packet(&p).is_err() && ! p.tag().is_critical() => { // "Unknown, non-critical packets MUST be ignored when // received.", section 4.3 of RFC 9580. return Ok(None); }, _ => {}, } if !self.packets.is_empty() { if self.packets.len() == 1 { if let Err(err) = Cert::valid_start(&self.packets[0]) { t!("{}", err); return self.cert(Some(p)); } } if Cert::valid_start(&p).is_ok() { t!("Encountered the start of a new certificate ({}), \ finishing buffered certificate", p.tag()); return self.cert(Some(p)); } else if let Err(err) = Cert::valid_packet(&p) { t!("Encountered an invalid packet ({}), \ finishing buffered certificate: {}", p.tag(), err); return self.cert(Some(p)); } } self.packets.push(p); Ok(None) } // Resets the parser so that it starts parsing a new cert. // // Returns the old state. Note: the packet iterator is preserved. fn reset(&mut self) -> Self { // We need to preserve `source` and `filter`. let mut orig = mem::take(self); self.source = orig.source.take(); mem::swap(&mut self.filter, &mut orig.filter); orig } // Finalizes the current Cert and returns it. Sets the parser up to // begin parsing the next Cert. // // `pk` is buffered for the next certificate. fn cert(&mut self, pk: Option<Packet>) -> Result<Option<Cert>> { tracer!(TRACE, "CertParser::cert", 0); let orig = self.reset(); if let Some(pk) = pk { self.packets.push(pk); } let n_packets = orig.packets.len(); t!("Finalizing certificate with {} packets", n_packets); // Convert to tokens, but preserve packets if it fails. let mut failed = false; let mut packets: Vec<Packet> = Vec::with_capacity(0); let mut tokens: Vec<Token> = Vec::with_capacity(n_packets); for p in orig.packets { if failed { // Just stash the packet. packets.push(p); } else { match p.try_into() { Ok(t) => tokens.push(t), Err(p) => { // Conversion failed. Revert the whole process. packets.reserve(n_packets); for t in tokens.drain(..) { packets.push({ let p: Option<Packet> = t.into(); p.expect("token created with packet") }); } packets.push(p); failed = true; }, } } } if failed { // There was at least one packet that doesn't belong in a // Cert. Fail now. let err = Error::UnsupportedCert( "Packet sequence includes non-Cert packets.".into(), packets); t!("Invalid certificate: {}", err); return Err(err.into()); } t!("{} tokens: {:?}", tokens.len(), tokens); let certo = match CertLowLevelParser::new() .parse(Lexer::from_tokens(&tokens)) { Ok(certo) => certo, Err(err) => { let err = low_level::parse_error_to_openpgp_error( low_level::parse_error_downcast(err)); t!("Low level parser: {}", err); return Err(err.into()); } }.and_then(|cert| { for filter in &self.filter { if !filter(&cert, true) { t!("Rejected by filter"); return None; } } Some(cert) }).and_then(|mut cert| { let primary_fp: KeyHandle = cert.key_handle(); // The parser puts all the signatures on the // certifications field. Split them now. split_sigs(&primary_fp, &mut cert.primary); for b in cert.userids.iter_mut() { split_sigs(&primary_fp, b); } for b in cert.user_attributes.iter_mut() { split_sigs(&primary_fp, b); } for b in cert.subkeys.iter_mut() { split_sigs(&primary_fp, b); } let cert = cert.canonicalize(); // Make sure it is still wanted. for filter in &self.filter { if !filter(&cert, true) { t!("Rejected by filter"); return None; } } Some(cert) }); t!("Returning {:?}, constructed from {} packets", certo.as_ref().map(|c| c.fingerprint()), n_packets); Ok(certo) } } /// Splits the signatures in b.certifications into the correct /// vectors. fn split_sigs<C>(primary: &KeyHandle, b: &mut ComponentBundle<C>) { debug_assert!(b.self_signatures.is_empty()); debug_assert!(b.self_revocations.is_empty()); debug_assert!(b.other_revocations.is_empty()); for sig in mem::replace(&mut b.certifications, Vec::with_capacity(0)) { let typ = sig.typ(); let issuers = sig.get_issuers(); let is_selfsig = issuers.is_empty() || issuers.iter().any(|kh| kh.aliases(primary)); use crate::SignatureType::*; if typ == CertificationApproval { b.attestations.push(sig); } else if typ == KeyRevocation || typ == SubkeyRevocation || typ == CertificationRevocation { if is_selfsig { b.self_revocations.push(sig); } else { b.other_revocations.push(sig); } } else if is_selfsig { b.self_signatures.push(sig); } else { b.certifications.push(sig); } } } impl<'a> Iterator for CertParser<'a> { type Item = Result<Cert>; fn next(&mut self) -> Option<Self::Item> { tracer!(TRACE, "CertParser::next", 0); if let Some(err) = self.queued_error.take() { t!("Returning queued error: {}", err); return Some(Err(err)); } loop { match self.source.take() { None => { t!("EOF."); if self.packets.is_empty() { return None; } match self.cert(None) { Ok(Some(cert)) => return Some(Ok(cert)), Ok(None) => return None, Err(err) => return Some(Err(err)), } }, Some(mut iter) => { let r = match iter.next() { Some(Ok(packet)) => { t!("Got packet #{} ({}{})", self.packets.len(), packet.tag(), match &packet { Packet::PublicKey(k) => Some(k.fingerprint().to_hex()), Packet::SecretKey(k) => Some(k.fingerprint().to_hex()), Packet::PublicSubkey(k) => Some(k.fingerprint().to_hex()), Packet::SecretSubkey(k) => Some(k.fingerprint().to_hex()), Packet::UserID(u) => Some(String::from_utf8_lossy(u.value()) .into()), Packet::Signature(s) => Some(format!("{}", s.typ())), _ => None, } .map(|s| format!(", {}", s)) .unwrap_or_else(|| "".into()) ); self.source = Some(iter); self.parse(packet) } Some(Err(err)) => { self.source = Some(iter); t!("Error getting packet: {}", err); if ! self.packets.is_empty() { // Returned any queued certificate first. match self.cert(None) { Ok(Some(cert)) => { self.queued_error = Some(err); return Some(Ok(cert)); } Ok(None) => { return Some(Err(err)); } Err(err2) => { // Return the first error, // queue the second error. self.queued_error = Some(err2); return Some(Err(err)); } } } else { return Some(Err(err)); } } None if self.packets.is_empty() => { t!("Packet iterator was empty"); Ok(None) } None => { t!("Packet iterator exhausted after {} packets", self.packets.len()); self.cert(None) } }; match r { Ok(Some(cert)) => { t!(" => {}", cert.fingerprint()); return Some(Ok(cert)); } Ok(None) => (), Err(err) => return Some(Err(err)), } }, } } } } #[cfg(test)] mod test { use super::*; use std::collections::HashSet; use std::iter::FromIterator; use crate::Fingerprint; use crate::cert::prelude::*; use crate::packet::prelude::*; use crate::parse::RECOVERY_THRESHOLD; use crate::serialize::Serialize; use crate::types::DataFormat; use crate::tests; #[test] fn push_tokens() { use crate::cert::parser::low_level::lexer::{Token, Lexer}; use crate::cert::parser::low_level::lexer::Token::*; use crate::cert::parser::low_level::CertParser; struct TestVector<'a> { s: &'a [Token], result: bool, } let test_vectors = [ TestVector { s: &[ PublicKey(None) ], result: true, }, TestVector { s: &[ SecretKey(None) ], result: true, }, TestVector { s: &[ PublicKey(None), Signature(None) ], result: true, }, TestVector { s: &[ PublicKey(None), Signature(None), Signature(None) ], result: true, }, TestVector { s: &[ PublicKey(None), Signature(None), Signature(None), UserID(None) ], result: true, }, TestVector { s: &[ PublicKey(None), Signature(None), Signature(None), UserID(None), Signature(None) ], result: true, }, TestVector { s: &[ PublicKey(None), Signature(None), Signature(None), UserAttribute(None) ], result: true, }, TestVector { s: &[ PublicKey(None), Signature(None), Signature(None), UserAttribute(None), Signature(None) ], result: true, }, TestVector { s: &[ PublicKey(None), Signature(None), Signature(None), PublicSubkey(None) ], result: true, }, TestVector { s: &[ PublicKey(None), Signature(None), Signature(None), PublicSubkey(None), Signature(None) ], result: true, }, TestVector { s: &[ PublicKey(None), Signature(None), Signature(None), SecretSubkey(None) ], result: true, }, TestVector { s: &[ PublicKey(None), Signature(None), Signature(None), SecretSubkey(None), Signature(None) ], result: true, }, TestVector { s: &[ PublicKey(None), Signature(None), Signature(None), SecretSubkey(None), Signature(None), SecretSubkey(None), Signature(None), SecretSubkey(None), Signature(None), SecretSubkey(None), Signature(None), SecretSubkey(None), Signature(None), UserID(None), Signature(None), Signature(None), Signature(None), SecretSubkey(None), Signature(None), UserAttribute(None), Signature(None), Signature(None), Signature(None), SecretSubkey(None), Signature(None), UserID(None), UserAttribute(None), Signature(None), Signature(None), Signature(None), ], result: true, }, TestVector { s: &[ PublicKey(None), Signature(None), Signature(None), PublicKey(None), Signature(None), Signature(None), ], result: false, }, TestVector { s: &[ PublicKey(None), Signature(None), Signature(None), SecretKey(None), Signature(None), Signature(None), ], result: false, }, TestVector { s: &[ SecretKey(None), Signature(None), Signature(None), SecretKey(None), Signature(None), Signature(None), ], result: false, }, TestVector { s: &[ SecretKey(None), Signature(None), Signature(None), PublicKey(None), Signature(None), Signature(None), ], result: false, }, TestVector { s: &[ SecretSubkey(None), Signature(None), Signature(None), PublicSubkey(None), Signature(None), Signature(None), ], result: false, }, TestVector { s: &[ SecretKey(None), Signature(None), UserID(None), Signature(None), SecretSubkey(None), Signature(None), SecretSubkey(None), Signature(None), Unknown(Tag::Private(61), None), ], result: true, }, ]; for v in &test_vectors { if v.result { let mut l = CertValidator::new(); for token in v.s.into_iter() { l.push_token((*token).clone()); assert_match!(CertValidity::CertPrefix = l.check()); } l.finish(); assert_match!(CertValidity::Cert = l.check()); } match CertParser::new().parse(Lexer::from_tokens(v.s)) { Ok(r) => assert!(v.result, "Parsing: {:?} => {:?}", v.s, r), Err(e) => assert!(! v.result, "Parsing: {:?} => {:?}", v.s, e), } } } #[test] fn push_tags() { use Tag::*; struct TestVector<'a> { s: &'a [Tag], result: bool, } let test_vectors = [ TestVector { s: &[ PublicKey ], result: true, }, TestVector { s: &[ SecretKey, Signature, UserID, Signature, SecretSubkey, Signature, SecretSubkey, Signature, Tag::Private(61), ], result: true, }, TestVector { s: &[ SecretKey, Signature, UserID, Signature, SecretSubkey, Signature, SecretSubkey, Signature, Tag::Unknown(61), ], result: true, }, TestVector { s: &[ SecretKey, Signature, UserID, Signature, SecretSubkey, Signature, SecretSubkey, Signature, Tag::Unknown(61), SecretKey, Signature, UserID, Signature, SecretSubkey, Signature, SecretSubkey, Signature, ], // This is a keyring, not a cert. result: false, }, ]; for v in &test_vectors { if v.result { let mut l = CertValidator::new(); for &tag in v.s.into_iter() { l.push(tag.clone()); assert_match!(CertValidity::CertPrefix = l.check()); } l.finish(); assert_match!(CertValidity::Cert = l.check()); } } } #[test] fn marker_packet_ignored() { use crate::serialize::Serialize; let mut testy_with_marker = Vec::new(); Packet::Marker(Default::default()) .serialize(&mut testy_with_marker).unwrap(); testy_with_marker.extend_from_slice(crate::tests::key("testy.pgp")); CertParser::from( PacketParser::from_bytes(&testy_with_marker).unwrap()) .next().unwrap().unwrap(); let mut testy_with_marker = Vec::new(); testy_with_marker.extend_from_slice(crate::tests::key("testy.pgp")); Packet::Marker(Default::default()) .serialize(&mut testy_with_marker).unwrap(); CertParser::from( PacketParser::from_bytes(&testy_with_marker).unwrap()) .next().unwrap().unwrap(); } #[test] fn invalid_packets() -> Result<()> { tracer!(TRACE, "invalid_packets", 0); fn cert_cmp(a: &Result<Cert>, b: &Vec<Packet>) { let a = a.as_ref().unwrap().clone().into_packets().collect::<Vec<_>>(); for (i, (a, b)) in a.iter().zip(b).enumerate() { if a != b { panic!("Differ at element #{}:\n {:?}\n {:?}", i, a, b); } } if a.len() != b.len() { panic!("Different lengths (common prefix identical): {} vs. {}", a.len(), b.len()); } } let (cert, _) = CertBuilder::general_purpose(Some("alice@example.org")) .generate()?; let cert = cert.into_packets().collect::<Vec<_>>(); // A userid packet. let userid : Packet = cert.clone() .into_iter() .filter(|p| p.tag() == Tag::UserID) .next() .unwrap(); // An unknown packet. let tag = Tag::Private(61); let unknown : Packet = Unknown::new(tag, Error::UnsupportedPacketType(tag).into()) .into(); // A literal packet. (This is a valid OpenPGP Message.) let mut lit = Literal::new(DataFormat::Unicode); lit.set_body(b"test".to_vec()); let lit = Packet::from(lit); // A compressed data packet containing a literal data packet. // (This is a valid OpenPGP Message.) let cd = { use crate::types::CompressionAlgorithm; use crate::packet; use crate::PacketPile; use crate::serialize::Serialize; use crate::parse::Parse; let mut cd = CompressedData::new( CompressionAlgorithm::Uncompressed); let mut body = Vec::new(); lit.serialize(&mut body)?; cd.set_body(packet::Body::Processed(body)); let cd = Packet::from(cd); // Make sure we created the message correctly: serialize, // parse it, and then check its form. let mut bytes = Vec::new(); cd.serialize(&mut bytes)?; let pp = PacketPile::from_bytes(&bytes[..])?; assert_eq!(pp.descendants().count(), 2); assert_eq!(pp.path_ref(&[ 0 ]).unwrap().tag(), packet::Tag::CompressedData); assert_eq!(pp.path_ref(&[ 0, 0 ]), Some(&lit)); cd }; t!("A single cert."); let cp = CertParser::from_iter(cert.clone()).collect::<Vec<_>>(); assert_eq!(cp.len(), 1); cert_cmp(&cp[0], &cert); t!("Two certificates."); let cp = CertParser::from_iter( cert.clone().into_iter().chain(cert.clone())).collect::<Vec<_>>(); assert_eq!(cp.len(), 2); cert_cmp(&cp[0], &cert); cert_cmp(&cp[1], &cert); fn interleave(cert: &Vec<Packet>, p: &Packet) { t!("A certificate, a {}.", p.tag()); let cp = CertParser::from_iter( cert.clone().into_iter() .chain(p.clone())) .collect::<Vec<_>>(); assert_eq!(cp.len(), 2); cert_cmp(&cp[0], cert); assert!(cp[1].is_err()); t!("A certificate, two {}.", p.tag()); let cp = CertParser::from_iter( cert.clone().into_iter() .chain(p.clone()) .chain(p.clone())) .collect::<Vec<_>>(); assert_eq!(cp.len(), 3); cert_cmp(&cp[0], cert); assert!(cp[1].is_err()); assert!(cp[2].is_err()); t!("A {}, a certificate.", p.tag()); let cp = CertParser::from_iter( p.clone().into_iter() .chain(cert.clone())) .collect::<Vec<_>>(); assert_eq!(cp.len(), 2); assert!(cp[0].is_err()); cert_cmp(&cp[1], cert); t!("Two {}, a certificate.", p.tag()); let cp = CertParser::from_iter( p.clone().into_iter() .chain(p.clone()) .chain(cert.clone())) .collect::<Vec<_>>(); assert_eq!(cp.len(), 3); assert!(cp[0].is_err()); assert!(cp[1].is_err()); cert_cmp(&cp[2], cert); t!("Two {}, a certificate, two {}.", p.tag(), p.tag()); let cp = CertParser::from_iter( p.clone().into_iter() .chain(p.clone()) .chain(cert.clone()) .chain(p.clone()) .chain(p.clone())) .collect::<Vec<_>>(); assert_eq!(cp.len(), 5); assert!(cp[0].is_err()); assert!(cp[1].is_err()); cert_cmp(&cp[2], cert); assert!(cp[3].is_err()); assert!(cp[4].is_err()); t!("Two {}, two certificates, two {}, a certificate."); let cp = CertParser::from_iter( p.clone().into_iter() .chain(p.clone()) .chain(cert.clone()) .chain(cert.clone()) .chain(p.clone()) .chain(p.clone()) .chain(cert.clone())) .collect::<Vec<_>>(); assert_eq!(cp.len(), 7); assert!(cp[0].is_err()); assert!(cp[1].is_err()); cert_cmp(&cp[2], cert); cert_cmp(&cp[3], cert); assert!(cp[4].is_err()); assert!(cp[5].is_err()); cert_cmp(&cp[6], cert); } interleave(&cert, &lit); // The certificate parser shouldn't recurse into containers. // So, the compressed data packets should show up as a single // error. interleave(&cert, &cd); // The certificate parser should treat unknown packets as // valid certificate components. let mut cert_plus = cert.clone(); cert_plus.push(unknown.clone()); t!("A certificate, an unknown."); let cp = CertParser::from_iter( cert.clone().into_iter() .chain(unknown.clone())) .collect::<Vec<_>>(); assert_eq!(cp.len(), 1); cert_cmp(&cp[0], &cert_plus); t!("An unknown, a certificate."); let cp = CertParser::from_iter( unknown.clone().into_iter() .chain(cert.clone())) .collect::<Vec<_>>(); assert_eq!(cp.len(), 2); assert!(cp[0].is_err()); cert_cmp(&cp[1], &cert); t!("A certificate, two unknowns."); let cp = CertParser::from_iter( cert.clone().into_iter() .chain(unknown.clone()) .chain(unknown.clone())) .collect::<Vec<_>>(); assert_eq!(cp.len(), 1); cert_cmp(&cp[0], &cert_plus); t!("A certificate, an unknown, a certificate."); let cp = CertParser::from_iter( cert.clone().into_iter() .chain(unknown.clone()) .chain(cert.clone())) .collect::<Vec<_>>(); assert_eq!(cp.len(), 2); cert_cmp(&cp[0], &cert_plus); cert_cmp(&cp[1], &cert); t!("A Literal, two User IDs"); let cp = CertParser::from_iter( lit.clone().into_iter() .chain(userid.clone()) .chain(userid.clone())) .collect::<Vec<_>>(); assert_eq!(cp.len(), 3); assert!(cp[0].is_err()); assert!(cp[1].is_err()); assert!(cp[2].is_err()); t!("A User ID, a certificate"); let cp = CertParser::from_iter( userid.clone().into_iter() .chain(cert.clone())) .collect::<Vec<_>>(); assert_eq!(cp.len(), 2); assert!(cp[0].is_err()); cert_cmp(&cp[1], &cert); t!("Two User IDs, a certificate"); let cp = CertParser::from_iter( userid.clone().into_iter() .chain(userid.clone()) .chain(cert.clone())) .collect::<Vec<_>>(); assert_eq!(cp.len(), 3); assert!(cp[0].is_err()); assert!(cp[1].is_err()); cert_cmp(&cp[2], &cert); Ok(()) } #[test] fn concatenated_armored_certs() -> Result<()> { let mut keyring = Vec::new(); keyring.extend_from_slice(b"some\ntext\n"); keyring.extend_from_slice(crate::tests::key("testy.asc")); keyring.extend_from_slice(crate::tests::key("testy.asc")); keyring.extend_from_slice(b"some\ntext\n"); keyring.extend_from_slice(crate::tests::key("testy.asc")); keyring.extend_from_slice(b"some\ntext\n"); let certs = CertParser::from_bytes(&keyring)?.collect::<Vec<_>>(); assert_eq!(certs.len(), 3); assert!(certs.iter().all(|c| c.is_ok())); Ok(()) } fn parse_test(n: usize, literal: bool, bad: usize) -> Result<()> { tracer!(TRACE, "t", 0); // Parses keyrings with different numbers of keys and // different errors. // n: number of keys // literal: whether to interleave literal packets. // bad: whether to insert invalid data (NUL bytes where // the start of a certificate is expected). let nulls = vec![ 0; bad ]; t!("n: {}, literals: {}, bad data: {}", n, literal, bad); let mut data = Vec::new(); let mut certs_orig = vec![]; for i in 0..n { let (cert, _) = CertBuilder::general_purpose( Some(format!("{}@example.org", i))) .generate()?; cert.as_tsk().serialize(&mut data)?; certs_orig.push(cert); if literal { let mut lit = Literal::new(DataFormat::Unicode); lit.set_body(b"data".to_vec()); Packet::from(lit).serialize(&mut data)?; } // Push some NUL bytes. data.extend(&nulls[..bad]); } if n == 0 { // Push some NUL bytes even if we didn't add any packets. data.extend(&nulls[..bad]); } assert_eq!(certs_orig.len(), n); t!("Start of data: {} {}", if let Some(x) = data.get(0) { format!("{:02X}", x) } else { "XX".into() }, if let Some(x) = data.get(1) { format!("{:02X}", x) } else { "XX".into() }); let certs_parsed = CertParser::from_bytes(&data); let certs_parsed = if n == 0 && bad > 0 { // Junk at the beginning of the file results in an // immediate parse error. assert!(certs_parsed.is_err()); return Ok(()); } else { certs_parsed.expect("Valid init") }; let certs_parsed: Vec<_> = certs_parsed.collect(); certs_parsed.iter().enumerate().for_each(|(i, r)| { t!("{}. {}", i, match r { Ok(c) => c.fingerprint().to_string(), Err(err) => err.to_string(), }); }); let n = if bad > RECOVERY_THRESHOLD { // We stop once we see the junk. certs_orig.drain(1..); std::cmp::min(n, 1) } else { n }; let modulus = if literal && bad > 0 { 3 } else { 2 }; let certs_parsed: Vec<Cert> = certs_parsed.into_iter() .enumerate() .filter_map(|(i, c)| { if literal && i % modulus == 1 { // Literals should be errors. assert!(c.is_err()); None } else if bad > 0 && n == 0 && i == 0 { // The first byte in the input is the NUL // byte. assert!(c.is_err()); None } else if bad > 0 && i % modulus == modulus - 1 { // NUL bytes are inserted after the // certificate / literal data packet. So the // second element will be the parse error. assert!(c.is_err()); None } else { Some(c.unwrap()) } }) .collect(); assert_eq!(certs_orig.len(), certs_parsed.len(), "number of parsed certificates: expected vs. got"); let fpr_orig = certs_orig.iter() .map(|c| { c.fingerprint() }) .collect::<Vec<_>>(); let fpr_parsed = certs_parsed.iter() .map(|c| { c.fingerprint() }) .collect::<Vec<_>>(); if fpr_orig != fpr_parsed { t!("{} certificates in orig; {} is parsed", fpr_orig.len(), fpr_parsed.len()); let fpr_set_orig: HashSet<&Fingerprint> = HashSet::from_iter(fpr_orig.iter()); let fpr_set_parsed = HashSet::from_iter(fpr_parsed.iter()); t!("Only in orig:\n {}", fpr_set_orig.difference(&fpr_set_parsed) .map(|f| f.to_string()) .collect::<Vec<_>>() .join(",\n ")); t!("Only in parsed:\n {}", fpr_set_parsed.difference(&fpr_set_orig) .map(|f| f.to_string()) .collect::<Vec<_>>() .join(",\n ")); assert_eq!(fpr_orig, fpr_parsed); } // Go packet by packet. (This makes finding an error a // lot easier.) for (i, (c_orig, c_parsed)) in certs_orig .into_iter() .zip(certs_parsed.into_iter()) .enumerate() { let ps_orig: Vec<Packet> = c_orig.as_tsk().into_packets().collect(); let ps_parsed: Vec<Packet> = c_parsed.as_tsk().into_packets().collect(); assert_eq!(ps_orig.len(), ps_parsed.len(), "number of packets: expected vs. got"); for (j, (p_orig, p_parsed)) in ps_orig .into_iter() .zip(ps_parsed.into_iter()) .enumerate() { assert_eq!(p_orig, p_parsed, "Cert {}, packet: {}", i, j); } } Ok(()) } #[test] fn parse_keyring_simple() -> Result<()> { for n in [1, 100, 0].iter() { parse_test(*n, false, 0)?; } Ok(()) } #[test] fn parse_keyring_interleaved_literals() -> Result<()> { for n in [1, 100, 0].iter() { parse_test(*n, true, 0)?; } Ok(()) } #[test] fn parse_keyring_interleaved_small_junk() -> Result<()> { for n in [1, 100, 0].iter() { parse_test(*n, false, 1)?; } Ok(()) } #[test] fn parse_keyring_interleaved_unrecoverable_junk() -> Result<()> { // PacketParser is pretty good at recovering from junk in the // middle: it will search the next RECOVERY_THRESHOLD bytes // for a valid packet. If it finds it, it will turn the junk // into a reserved packet and resume. Insert a lot of NULs to // prevent the recovery mechanism from working. for n in [1, 100, 0].iter() { parse_test(*n, false, 2 * RECOVERY_THRESHOLD)?; } Ok(()) } #[test] fn parse_keyring_interleaved_literal_and_small_junk() -> Result<()> { for n in [1, 100, 0].iter() { parse_test(*n, true, 1)?; } Ok(()) } #[test] fn parse_keyring_interleaved_literal_and_unrecoverable_junk() -> Result<()> { for n in [1, 100, 0].iter() { parse_test(*n, true, 2 * RECOVERY_THRESHOLD)?; } Ok(()) } #[test] fn parse_keyring_no_public_key() -> Result<()> { tracer!(TRACE, "parse_keyring_no_public_key", 0); // The first few packets are not the valid start of a // certificate. Each of those should return in an Error. // But, that shouldn't stop us from parsing the rest of the // keyring. let (cert_1, _) = CertBuilder::general_purpose( Some("ea@example.org")) .generate()?; let cert_1_packets: Vec<Packet> = cert_1.into_packets().collect(); let (cert_2, _) = CertBuilder::general_purpose( Some("eb@example.org")) .generate()?; for n in 1..cert_1_packets.len() { t!("n: {}", n); let mut data = Vec::new(); for i in n..cert_1_packets.len() { cert_1_packets[i].serialize(&mut data)?; } cert_2.as_tsk().serialize(&mut data)?; let certs_parsed = CertParser::from_bytes(&data) .expect("Valid parse"); let mut iter = certs_parsed; for _ in n..cert_1_packets.len() { assert!(iter.next().unwrap().is_err()); } assert_eq!(iter.next().unwrap().as_ref().unwrap(), &cert_2); assert!(iter.next().is_none()); assert!(iter.next().is_none()); } Ok(()) } #[test] fn filter() { let fp = Fingerprint::from_hex( "CBCD8F030588653EEDD7E2659B7DD433F254904A", ).unwrap(); let cp = CertParser::from_bytes(tests::key("bad-subkey-keyring.pgp")) .unwrap() .unvalidated_cert_filter(|cert, _| { cert.fingerprint() == fp }); let certs = cp.collect::<Result<Vec<Cert>>>().unwrap(); assert_eq!(certs.len(), 1); assert!(certs[0].fingerprint() == fp); } #[test] fn packet_source_includes_an_error() -> Result<()> { let mut ppr = PacketParser::from_bytes(crate::tests::key("testy.pgp"))?; let mut testy = Vec::new(); while let PacketParserResult::Some(pp) = ppr { let (packet, ppr_) = pp.next()?; testy.push(packet); ppr = ppr_; } // A cert, two errors, another cert. let mut packets: Vec<Result<Packet>> = Vec::new(); for p in testy.iter() { packets.push(Ok(p.clone())); } packets.push(Err(anyhow::anyhow!("An error"))); packets.push(Err(anyhow::anyhow!("Another error"))); for p in testy.iter() { packets.push(Ok(p.clone())); } let certs = CertParser::from(packets).collect::<Vec<Result<Cert>>>(); assert_eq!(certs.len(), 4); assert!(certs[0].is_ok()); assert!(certs[1].is_err()); assert!(certs[2].is_err()); assert!(certs[3].is_ok()); Ok(()) } } ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/src/cert/prelude.rs�����������������������������������������������������������0000644�0000000�0000000�00000004141�10461020230�0016471�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������//! Brings most relevant types and traits into scope for working with //! certificates. //! //! Less often used types and traits that are more likely to lead to a //! naming conflict are not brought into scope. //! //! Traits are brought into scope anonymously. //! //! ``` //! # #![allow(unused_imports)] //! # use sequoia_openpgp as openpgp; //! use openpgp::cert::prelude::*; //! ``` #![allow(unused_imports)] pub use crate::cert::{ Cert, CertBuilder, CertParser, CertRevocationBuilder, CipherSuite, KeyBuilder, SubkeyBuilder, Preferences as _, SubkeyRevocationBuilder, UserAttributeRevocationBuilder, UserIDRevocationBuilder, ValidCert, amalgamation::ComponentAmalgamation, amalgamation::ComponentAmalgamationIter, amalgamation::UnknownComponentAmalgamation, amalgamation::UnknownComponentAmalgamationIter, amalgamation::UserAttributeAmalgamation, amalgamation::UserAttributeAmalgamationIter, amalgamation::UserIDAmalgamation, amalgamation::UserIDAmalgamationIter, amalgamation::ValidAmalgamation as _, amalgamation::ValidComponentAmalgamation, amalgamation::ValidComponentAmalgamationIter, amalgamation::ValidUserAttributeAmalgamation, amalgamation::ValidUserAttributeAmalgamationIter, amalgamation::ValidUserIDAmalgamation, amalgamation::ValidUserIDAmalgamationIter, amalgamation::ValidateAmalgamation as _, amalgamation::key::ErasedKeyAmalgamation, amalgamation::key::KeyAmalgamation, amalgamation::key::KeyAmalgamationIter, amalgamation::key::PrimaryKey as _, amalgamation::key::PrimaryKeyAmalgamation, amalgamation::key::SubordinateKeyAmalgamation, amalgamation::key::ValidErasedKeyAmalgamation, amalgamation::key::ValidKeyAmalgamation, amalgamation::key::ValidKeyAmalgamationIter, amalgamation::key::ValidPrimaryKeyAmalgamation, amalgamation::key::ValidSubordinateKeyAmalgamation, bundle::ComponentBundle, bundle::KeyBundle, bundle::PrimaryKeyBundle, bundle::SubkeyBundle, bundle::UnknownBundle, bundle::UserAttributeBundle, bundle::UserIDBundle, }; �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/src/cert/raw/iter.rs����������������������������������������������������������0000644�0000000�0000000�00000012031�10461020230�0016562�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������use std::fmt; use crate::cert::raw::RawCert; use crate::packet::Key; use crate::packet::key; /// A key iterator for `RawCert`s. /// /// This is returned by [`RawCert::keys`]. It is analogous to /// [`KeyAmalgamationIter`], but for `RawCert`s. /// /// [`KeyAmalgamationIter`]: crate::cert::amalgamation::key::KeyAmalgamationIter pub struct KeyIter<'a, P, R> where P: key::KeyParts, R: key::KeyRole, { key_iter: Box<dyn Iterator<Item=Key<key::PublicParts, key::UnspecifiedRole>> + Send + Sync + 'a>, // Whether the primary key has been returned. returned_primary: bool, // Whether the primary key should be returned. want_primary: bool, _p: std::marker::PhantomData<P>, _r: std::marker::PhantomData<R>, } assert_send_and_sync!(KeyIter<'_, P, R> where P: key::KeyParts, R: key::KeyRole, ); impl<'a, P, R> fmt::Debug for KeyIter<'a, P, R> where P: key::KeyParts, R: key::KeyRole, { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.debug_struct("KeyIter") .field("want_primary", &self.want_primary) .finish() } } macro_rules! impl_iterator { ($parts:path, $role:path, $item:ty) => { impl<'a> Iterator for KeyIter<'a, $parts, $role> { type Item = $item; fn next(&mut self) -> Option<Self::Item> { // We unwrap the result of the conversion. But, this // is safe by construction: next_common only returns // keys that can be correctly converted. self.next_common() } } } } impl_iterator!(key::PublicParts, key::UnspecifiedRole, Key<key::PublicParts, key::UnspecifiedRole>); impl_iterator!(key::PublicParts, key::SubordinateRole, Key<key::PublicParts, key::SubordinateRole>); impl<'a, P, R> KeyIter<'a, P, R> where P: key::KeyParts, R: key::KeyRole, { fn next_common(&mut self) -> Option<Key<P, R>> { tracer!(false, "KeyIter::next", 0); t!("{:?}", self); loop { if ! self.returned_primary { if ! self.want_primary { // Discard the primary key. let _ = self.key_iter.next(); } self.returned_primary = true; } let key = self.key_iter.next()? .parts_into_unspecified() .role_into_unspecified(); let key = if let Ok(key) = P::convert_key(key) { key } else { // The caller wants secret keys, but this is no secret // key, skip it. continue; }; let key = R::convert_key(key); // Apply any filters. return Some(key); } } } impl<'a, P, R> KeyIter<'a, P, R> where P: key::KeyParts, R: key::KeyRole, { /// Returns a new `KeyIter` instance. pub(crate) fn new(cert: &'a RawCert) -> Self where Self: 'a { KeyIter { key_iter: Box::new(cert.keys_internal()), want_primary: true, returned_primary: false, _p: std::marker::PhantomData, _r: std::marker::PhantomData, } } /// Changes the iterator to only return subkeys. /// /// This function also changes the return type. Instead of the /// iterator returning a [`Key`] whose role is /// [`key::UnspecifiedRole`], the role is [`key::SubordinateRole`] /// /// # Examples /// /// ```rust /// # use sequoia_openpgp as openpgp; /// # use openpgp::Result; /// # use openpgp::cert::prelude::*; /// # use openpgp::cert::raw::RawCertParser; /// # use openpgp::parse::Parse; /// # use openpgp::serialize::Serialize; /// # /// # fn main() -> Result<()> { /// # let (cert, _) = CertBuilder::new() /// # .add_signing_subkey() /// # .add_certification_subkey() /// # .add_transport_encryption_subkey() /// # .add_storage_encryption_subkey() /// # .add_authentication_subkey() /// # .generate()?; /// # /// # let mut bytes = Vec::new(); /// # cert.serialize(&mut bytes); /// # let mut parser = RawCertParser::from_bytes(&bytes)?; /// # /// # let rawcert = parser.next().expect("have one").expect("valid"); /// # assert!(parser.next().is_none()); /// # let mut i = 0; /// for subkey in rawcert.keys().subkeys() { /// // Use it. /// println!("{}", subkey.fingerprint()); /// # i += 1; /// } /// # assert_eq!(i, 5); /// # Ok(()) /// # } /// ``` /// pub fn subkeys(self) -> KeyIter<'a, P, key::SubordinateRole> { KeyIter { key_iter: self.key_iter, want_primary: false, returned_primary: self.returned_primary, _p: std::marker::PhantomData, _r: std::marker::PhantomData, } } } �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/src/cert/raw.rs���������������������������������������������������������������0000644�0000000�0000000�00000157523�10461020230�0015637�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������//! Functionality for dealing with mostly unparsed certificates. //! //! Parsing a certificate is not cheap. When reading a keyring, most //! certificates are discarded or never used as they are not relevant. //! This module provides the [`RawCertParser`] and [`RawCert`] data //! structures that can help reduce the amount of unnecessary //! computation. //! //! [`RawCertParser`] splits a keyring into [`RawCert`]s by looking //! primarily at the packet framing and the packet headers. This is //! much faster than parsing the packets' contents, as the //! [`CertParser`] does. //! //! [`CertParser`]: crate::cert::CertParser //! //! [`RawCert`] exposes just enough functionality to allow the user to //! quickly check if a certificate is not relevant. Note: to check if //! a certificate is really relevant, the check usually needs to be //! repeated after canonicalizing it (by using, e.g., [`Cert::from`]) //! and validating it (by using [`Cert::with_policy`]). //! //! [`Cert::from`]: From<RawCert> //! //! # Examples //! //! Search for a specific certificate in a keyring: //! //! ```rust //! # use std::convert::TryFrom; //! # //! use sequoia_openpgp as openpgp; //! //! # use openpgp::Result; //! use openpgp::cert::prelude::*; //! use openpgp::cert::raw::RawCertParser; //! use openpgp::parse::Parse; //! # use openpgp::serialize::Serialize; //! # //! # fn main() -> Result<()> { //! # fn doit() -> Result<Cert> { //! # let (cert, _) = CertBuilder::new() //! # .generate()?; //! # let fpr = cert.fingerprint(); //! # //! # let mut bytes = Vec::new(); //! # cert.serialize(&mut bytes); //! for cert in RawCertParser::from_bytes(&bytes)? { //! /// Ignore corrupt and invalid certificates. //! let cert = if let Ok(cert) = cert { //! cert //! } else { //! continue; //! }; //! //! if cert.fingerprint() == fpr { //! // Found it! Try to convert it to a Cert. //! return Cert::try_from(cert); //! } //! } //! //! // Not found. //! return Err(anyhow::anyhow!("Not found!").into()); //! # } //! # doit().expect("Found the certificate"); //! # Ok(()) //! # } //! ``` use std::borrow::Cow; use std::convert::TryFrom; use std::fmt; use buffered_reader::{BufferedReader, Dup, EOF, Memory}; use crate::Fingerprint; use crate::KeyID; use crate::Result; use crate::armor; use crate::cert::Cert; use crate::packet::Header; use crate::packet::Key; use crate::packet::Packet; use crate::packet::Tag; use crate::packet::UserID; use crate::packet::header::BodyLength; use crate::packet::header::CTB; use crate::packet::key; use crate::parse::Cookie; use crate::parse::PacketParser; use crate::parse::Parse; use crate::parse::RECOVERY_THRESHOLD; use super::TRACE; mod iter; pub use iter::KeyIter; /// A mostly unparsed `Packet`. /// /// This is returned by [`RawCert::packets`]. /// /// The data includes the OpenPGP framing (i.e., the CTB, and length /// information). [`RawPacket::body`] returns just the bytes /// corresponding to the packet's body, i.e., without the OpenPGP /// framing. /// /// You can convert it to a [`Packet`] using `TryFrom`. /// /// # Examples /// /// ```rust /// use sequoia_openpgp as openpgp; /// # use openpgp::Result; /// # use openpgp::cert::prelude::*; /// # use openpgp::cert::raw::RawCert; /// use openpgp::packet::Packet; /// use openpgp::packet::Tag; /// # use openpgp::parse::Parse; /// # use openpgp::serialize::Serialize; /// # /// # fn main() -> Result<()> { /// # let (cert, _) = CertBuilder::new() /// # .add_signing_subkey() /// # .add_certification_subkey() /// # .add_transport_encryption_subkey() /// # .add_storage_encryption_subkey() /// # .add_authentication_subkey() /// # .generate()?; /// # /// # let mut bytes = Vec::new(); /// # cert.as_tsk().serialize(&mut bytes); /// # let mut count = 0; /// # /// # let rawcert = RawCert::from_bytes(&bytes)?; /// for p in rawcert.packets() { /// if p.tag() == Tag::SecretSubkey { /// if let Ok(packet) = Packet::try_from(p) { /// // Do something with the packet. /// # count += 1; /// } /// # else { panic!("Failed to parse packet"); } /// } /// } /// # assert_eq!(count, 5); /// # Ok(()) /// # } /// ``` #[derive(Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] pub struct RawPacket<'a> { tag: Tag, header_len: usize, data: &'a [u8], } assert_send_and_sync!(RawPacket<'_>); impl fmt::Debug for RawPacket<'_> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("RawPacket") .field("tag", &self.tag) .field("data (bytes)", &self.data.len()) .finish() } } impl<'a> RawPacket<'a> { fn new(tag: Tag, header_len: usize, bytes: &'a [u8]) -> Self { Self { tag, header_len, data: bytes, } } /// Returns the packet's tag. pub fn tag(&self) -> Tag { self.tag } /// Returns the packet's bytes. pub fn as_bytes(&self) -> &[u8] { self.data } /// Return the packet's body without the OpenPGP framing. pub fn body(&self) -> &[u8] { &self.data[self.header_len..] } } impl<'a> TryFrom<RawPacket<'a>> for Packet { type Error = anyhow::Error; fn try_from(p: RawPacket<'a>) -> Result<Self> { Packet::from_bytes(p.as_bytes()) } } impl<'a> crate::seal::Sealed for RawPacket<'a> {} impl<'a> crate::serialize::Marshal for RawPacket<'a> { fn serialize(&self, o: &mut dyn std::io::Write) -> Result<()> { o.write_all(self.as_bytes())?; Ok(()) } } /// A mostly unparsed `Cert`. /// /// This data structure contains the unparsed packets for a /// certificate or key. The packet sequence is well-formed in the /// sense that the sequence of tags conforms to the [Transferable /// Public Key grammar] or [Transferable Secret Key grammar], and that /// it can extract the primary key's fingerprint. Beyond that, the /// packets are not guaranteed to be valid. /// /// [Transferable Public Key grammar]: https://www.rfc-editor.org/rfc/rfc9580.html#section-10.1 /// [Transferable Secret Key grammar]: https://www.rfc-editor.org/rfc/rfc9580.html#section-10.2 /// /// This data structure exists to quickly split a large keyring, and /// only parse those certificates that appear to be relevant. #[derive(Clone)] pub struct RawCert<'a> { data: Cow<'a, [u8]>, primary_key: Key<key::PublicParts, key::PrimaryRole>, // The packet's tag, the length of the header, and the offset of // the start of the packet (including the header) into data. packets: Vec<(Tag, usize, usize)>, } assert_send_and_sync!(RawCert<'_>); impl<'a> fmt::Debug for RawCert<'a> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("RawCert") .field("fingerprint", &self.fingerprint()) .field("packets", &self.packets .iter() .map(|p| format!("{} (offset: {})", p.0, p.1)) .collect::<Vec<String>>() .join(", ")) .field("data (bytes)", &self.data.as_ref().len()) .finish() } } impl<'a> PartialEq for RawCert<'a> { fn eq(&self, other: &Self) -> bool { self.data == other.data } } impl<'a> Eq for RawCert<'a> { } impl<'a> RawCert<'a> { /// Returns the certificate's bytes. /// /// If you want an individual packet's bytes, use /// [`RawCert::packet`] or [`RawCert::packets`], and then call /// [`RawPacket::as_bytes`]. pub fn as_bytes(&'a self) -> &'a [u8] { self.data.as_ref() } /// Returns the certificate's fingerprint. pub fn fingerprint(&self) -> Fingerprint { self.primary_key.fingerprint() } /// Returns the certificate's Key ID. pub fn keyid(&self) -> KeyID { KeyID::from(self.fingerprint()) } /// Returns the ith packet. pub fn packet(&self, i: usize) -> Option<RawPacket> { let data: &[u8] = self.data.as_ref(); let &(tag, header_len, start) = self.packets.get(i)?; let following = self.packets .get(i + 1) .map(|&(_, _, offset)| offset) .unwrap_or(data.len()); Some(RawPacket::new(tag, header_len, &data[start..following])) } /// Returns an iterator over each raw packet. pub fn packets(&self) -> impl Iterator<Item=RawPacket> { let data: &[u8] = self.data.as_ref(); let count = self.packets.len(); (0..count) .map(move |i| { let (tag, header_len, start) = self.packets[i]; let following = self.packets .get(i + 1) .map(|&(_, _, offset)| offset) .unwrap_or(data.len()); RawPacket::new(tag, header_len, &data[start..following]) }) } /// Returns the number of packets. pub fn count(&self) -> usize { self.packets.len() } /// Returns an iterator over the certificate's keys. /// /// Note: this parses the key packets, but it does not verify any /// binding signatures. As such, this can only be used as part of /// a precheck. If the certificate appears to match, then the /// caller must convert the [`RawCert`] to a [`Cert`] or a /// [`ValidCert`], depending on the requirements, and perform the /// check again. /// /// [`ValidCert`]: crate::cert::ValidCert /// /// Use [`subkeys`] to just return the subkeys. This function /// also changes the return type. Instead of the iterator /// returning a [`Key`] whose role is [`key::UnspecifiedRole`], /// the role is [`key::SubordinateRole`]. /// /// [`subkeys`]: KeyIter::subkeys /// /// # Examples /// /// ```rust /// use sequoia_openpgp as openpgp; // /// # use openpgp::Result; /// # use openpgp::cert::prelude::*; /// use openpgp::cert::raw::RawCertParser; /// use openpgp::parse::Parse; /// # use openpgp::serialize::Serialize; /// # /// # fn main() -> Result<()> { /// # let (cert, _) = CertBuilder::new() /// # .add_signing_subkey() /// # .add_certification_subkey() /// # .add_transport_encryption_subkey() /// # .add_storage_encryption_subkey() /// # .add_authentication_subkey() /// # .generate()?; /// # /// # let mut bytes = Vec::new(); /// # cert.serialize(&mut bytes); /// # let mut certs = 0; /// # let mut keys = 0; /// for cert in RawCertParser::from_bytes(&bytes)? { /// /// Ignore corrupt and invalid certificates. /// let cert = if let Ok(cert) = cert { /// cert /// } else { /// continue; /// }; /// /// // Iterate over the keys. Note: this parses the Key /// // packets. /// for key in cert.keys() { /// println!("{}", key.fingerprint()); /// # keys += 1; /// } /// # certs += 1; /// } /// # assert_eq!(certs, 1); /// # assert_eq!(keys, 6); /// # Ok(()) /// # } /// ``` pub fn keys(&self) -> KeyIter<key::PublicParts, key::UnspecifiedRole> { KeyIter::new(self) } // Returns an iterator over the certificate's keys. // // This is used by `KeyIter`, which implements a number of // filters. fn keys_internal(&self) -> impl Iterator<Item=Key<key::PublicParts, key::UnspecifiedRole>> + '_ { std::iter::once(self.primary_key().clone().role_into_unspecified()) .chain(self.packets() .filter(|p| matches!(p.tag(), Tag::PublicKey | Tag::PublicSubkey | Tag::SecretKey | Tag::SecretSubkey)) .skip(1) // The primary key. .filter_map(|p| Key::from_bytes(p.body()) .ok() .map(|k| k.parts_into_public()))) } /// Returns the certificate's primary key. /// /// Note: this parses the primary key packet, but it does not /// verify any binding signatures. As such, this can only be used /// as part of a precheck. If the certificate appears to match, /// then the caller must convert the [`RawCert`] to a [`Cert`] or /// a [`ValidCert`], depending on the requirements, and perform /// the check again. /// /// [`ValidCert`]: crate::cert::ValidCert pub fn primary_key(&self) -> Key<key::PublicParts, key::PrimaryRole> { self.primary_key.clone() } /// Returns the certificate's User IDs. /// /// Note: this parses the User ID packets, but it does not verify /// any binding signatures. That is, there is no guarantee that /// the User IDs should actually be associated with the primary /// key. As such, this can only be used as part of a precheck. /// If a User ID appears to match, then the caller must convert /// the [`RawCert`] to a [`Cert`] or a [`ValidCert`], depending on /// the requirements, and perform the check again. /// /// [`ValidCert`]: crate::cert::ValidCert pub fn userids(&self) -> impl Iterator<Item=UserID> + '_ { self.packets() .filter_map(|p| { if p.tag() == Tag::UserID { UserID::try_from(p.body()).ok() } else { None } }) } /// Changes the `RawCert`'s lifetime to the static lifetime. /// /// Returns a `RawCert` with a static lifetime by copying any /// referenced data. /// /// [`RawCertParser::next`] returns a `RawCert` with the same /// lifetime as its reader. In certain situations, /// `RawCertParser::next` can take advantage of this to avoid /// copying data. Tying the `RawCert`'s lifetime to the reader is /// inconvenient when the `RawCert` needs to outlive the reader, /// however. This function copies any referenced data thereby /// breaking the dependency. /// /// ``` /// # use sequoia_openpgp::Result; /// # use sequoia_openpgp::cert::raw::RawCert; /// # use sequoia_openpgp::cert::raw::RawCertParser; /// # use sequoia_openpgp::parse::Parse; /// /// # fn main() -> Result<()> { /// fn read_certs<'a>() -> Result<Vec<RawCert<'static>>> { /// let input = // ... /// # Vec::new(); /// /// // The lifetime of the returned certs is tied to input. /// // We use into_owned to break the dependency. /// let parser = RawCertParser::from_bytes(&input)?; /// let certs = parser /// .map(|r| r.map(|c| c.into_owned())) /// .collect::<Result<Vec<_>>>()?; /// Ok(certs) /// } /// /// let cert = read_certs()?; /// # assert_eq!(cert.len(), 0); /// # Ok(()) } /// ``` pub fn into_owned(self) -> RawCert<'static> { match self.data { Cow::Owned(data) => { RawCert { data: Cow::Owned(data), primary_key: self.primary_key, packets: self.packets, } } Cow::Borrowed(data) => { RawCert { data: Cow::Owned(data.to_vec()), primary_key: self.primary_key, packets: self.packets, } } } } } impl<'a> TryFrom<&RawCert<'a>> for Cert { type Error = anyhow::Error; fn try_from(c: &RawCert) -> Result<Self> { Cert::from_bytes(c.as_bytes()) } } impl<'a> TryFrom<RawCert<'a>> for Cert { type Error = anyhow::Error; fn try_from(c: RawCert) -> Result<Self> { Cert::try_from(&c) } } impl<'a> Parse<'a, RawCert<'a>> for RawCert<'a> { /// Returns the first RawCert encountered in the reader. /// /// Returns an error if there are multiple certificates. fn from_buffered_reader<R>(reader: R) -> Result<RawCert<'a>> where R: BufferedReader<Cookie> + 'a { fn parse<'a>(reader: Box<dyn BufferedReader<Cookie> + 'a>) -> Result<RawCert<'a>> { let mut parser = RawCertParser::from_buffered_reader(reader)?; if let Some(cert_result) = parser.next() { if parser.next().is_some() { Err(crate::Error::MalformedCert( "Additional packets found, is this a keyring?".into() ).into()) } else { cert_result } } else { Err(crate::Error::MalformedCert("No data".into()).into()) } } parse(reader.into_boxed()) } } impl<'a> crate::seal::Sealed for RawCert<'a> {} impl<'a> crate::serialize::Marshal for RawCert<'a> { fn serialize(&self, o: &mut dyn std::io::Write) -> Result<()> { o.write_all(self.as_bytes())?; Ok(()) } } /// An iterator over a sequence of unparsed certificates, i.e., an /// OpenPGP keyring. /// /// A `RawCertParser` returns each certificate that it encounters. /// /// It implements the same state machine as [`CertParser`], however, a /// `CertParser` is stricter. Specifically, a `CertParser` performs /// some sanity checks on the content of the packets whereas a /// `RawCertParser` doesn't do those checks, because it avoids parsing /// the packets' contents; it primarily looks at the packets' framing, /// and their headers. /// /// [`CertParser`]: crate::cert::CertParser /// /// `RawCertParser` checks that the packet sequence is well-formed in /// the sense that the sequence of tags conforms to the [Transferable /// Public Key grammar] or [Transferable Secret Key grammar], and it /// performs a few basic checks. See the documentation for /// [`RawCert`] for details. /// /// [Transferable Public Key grammar]: https://www.rfc-editor.org/rfc/rfc9580.html#section-10.1 /// [Transferable Secret Key grammar]: https://www.rfc-editor.org/rfc/rfc9580.html#section-10.2 /// /// Because a `RawCertParser` doesn't parse the contents of the /// packets, it is significantly faster than a [`CertParser`] when /// many of the certificates in a keyring are irrelevant. /// /// # Examples /// /// Search for a specific certificate in a keyring: /// /// ```rust /// # use std::convert::TryFrom; /// # /// use sequoia_openpgp as openpgp; /// /// # use openpgp::Result; /// use openpgp::cert::prelude::*; /// use openpgp::cert::raw::RawCertParser; /// use openpgp::parse::Parse; /// # use openpgp::serialize::Serialize; /// # /// # fn main() -> Result<()> { /// # fn doit() -> Result<Cert> { /// # let (cert, _) = CertBuilder::new() /// # .generate()?; /// # let fpr = cert.fingerprint(); /// # /// # let mut bytes = Vec::new(); /// # cert.serialize(&mut bytes); /// for cert in RawCertParser::from_bytes(&bytes)? { /// /// Ignore corrupt and invalid certificates. /// let cert = if let Ok(cert) = cert { /// cert /// } else { /// continue; /// }; /// /// if cert.fingerprint() == fpr { /// // Found it! Try to convert it to a Cert. /// if let cert = Cert::try_from(cert) { /// return cert; /// } /// } /// } /// /// // Not found. /// return Err(anyhow::anyhow!("Not found!").into()); /// # } /// # doit().expect("Found the certificate"); /// # Ok(()) /// # } /// ``` pub struct RawCertParser<'a> { // If the data is being read from a slice, then the slice. This // is used to avoid copying the data into the RawCert. slice: Option<&'a [u8]>, // Where `RawCertParser` reads the data. When reading from a // slice, this is a `buffered_reader::Memory`. Note: the slice // field will not be set, if the input needs to be transferred // (i.e., dearmored). reader: Box<dyn BufferedReader<Cookie> + 'a>, // Whether we are dearmoring the input. dearmor: bool, // The total number of bytes read. bytes_read: usize, // Any pending error. pending_error: Option<anyhow::Error>, // Whether there was an unrecoverable error. done: bool, } assert_send_and_sync!(RawCertParser<'_>); impl<'a> RawCertParser<'a> { fn new(reader: Box<dyn BufferedReader<Cookie> + 'a>) -> Result<Self> { // Check that we can read the first header and that it is // reasonable. Note: an empty keyring is not an error; we're // just checking for bad data here. If not, try again after // dearmoring the input. let mut dearmor = false; let mut dup = Dup::with_cookie(reader, Default::default()); if ! dup.eof() { match Header::parse(&mut dup) { Ok(header) => { let tag = header.ctb().tag(); if matches!(tag, Tag::Unknown(_) | Tag::Private(_)) { return Err(crate::Error::MalformedCert( format!("A certificate must start with a \ public key or a secret key packet, \ got a {}", tag)) .into()); } } Err(_err) => { // We failed to read a header. Try to dearmor the // input. dearmor = true; } } } // Strip the Dup reader. let mut reader = dup.into_boxed().into_inner().expect("inner"); if dearmor { reader = armor::Reader::from_cookie_reader( reader, armor::ReaderMode::Tolerant(None), Default::default()).into_boxed(); let mut dup = Dup::with_cookie(reader, Default::default()); match Header::parse(&mut dup) { Ok(header) => { let tag = header.ctb().tag(); if matches!(tag, Tag::Unknown(_) | Tag::Private(_)) { return Err(crate::Error::MalformedCert( format!("A certificate must start with a \ public key or a secret key packet, \ got a {}", tag)) .into()); } } Err(err) => { return Err(err); } } reader = dup.into_boxed().into_inner().expect("inner"); } Ok(RawCertParser { slice: None, reader, dearmor, bytes_read: 0, pending_error: None, done: false, }) } } impl<'a> Parse<'a, RawCertParser<'a>> for RawCertParser<'a> { /// Initializes a `RawCertParser` from a `BufferedReader`. fn from_buffered_reader<R>(reader: R) -> Result<RawCertParser<'a>> where R: BufferedReader<Cookie> + 'a { RawCertParser::new(reader.into_boxed()) } /// Initializes a `RawCertParser` from a byte string. fn from_bytes<D: AsRef<[u8]> + ?Sized + Send + Sync>(data: &'a D) -> Result<Self> { let data = data.as_ref(); let mut p = RawCertParser::new( Memory::with_cookie(data, Default::default()).into_boxed())?; // If we are dearmoring the input, then the slice doesn't // reflect the raw packets. if ! p.dearmor { p.slice = Some(data); } Ok(p) } } impl<'a> crate::seal::Sealed for RawCertParser<'a> {} impl<'a> Iterator for RawCertParser<'a> { type Item = Result<RawCert<'a>>; fn next(&mut self) -> Option<Self::Item> { tracer!(TRACE, "RawCertParser::next", 0); // Return the pending error. if let Some(err) = self.pending_error.take() { t!("Returning the queued error: {}", err); return Some(Err(err)); } if self.done { return None; } if self.reader.eof() && self.dearmor { // We are dearmoring and hit EOF. Maybe there is a second // armor block next to this one! // Get the reader, let reader = std::mem::replace( &mut self.reader, EOF::with_cookie(Default::default()).into_boxed()); // peel off the armor reader, let reader = reader.into_inner().expect("the armor reader"); // and install a new one! self.reader = armor::Reader::from_cookie_reader( reader, armor::ReaderMode::Tolerant(None), Default::default()).into_boxed(); } if self.reader.eof() { return None; } let mut reader = Dup::with_cookie( std::mem::replace(&mut self.reader, Box::new(EOF::with_cookie(Default::default()))), Default::default()); // The absolute start of this certificate in the stream. let cert_start_absolute = self.bytes_read; // The number of bytes processed relative to the start of the // dup'ed buffered reader. This may be less than the number // of bytes read, e.g., when we encounter a new certificate, // we read the header, but we don't necessarily want to // consider it consumed. let mut processed = 0; // The certificate's span relative to the start of the dup'ed // buffered reader. The start will be larger than zero when // we skip a marker packet. let mut cert_start = 0; let mut cert_end = 0; // (Tag, header length, offset from start of the certificate) let mut packets: Vec<(Tag, usize, usize)> = Vec::new(); let mut primary_key = None; let mut pending_error = None; 'packet_parser: loop { if reader.eof() { break; } let packet_start = reader.total_out(); processed = packet_start; let mut skip = 0; let mut header_len = 0; let header = loop { match Header::parse(&mut reader) { Err(err) => { if skip == 0 { t!("Reading the next packet's header: {}", err); } if skip >= RECOVERY_THRESHOLD { pending_error = Some(err.context( format!("Splitting keyring at offset {}", self.bytes_read + packet_start))); processed = reader.total_out(); // We tried to recover and failed. Once // we return the above error, we're done. self.done = true; break 'packet_parser; } else if reader.eof() { t!("EOF while trying to recover"); skip += 1; break Header::new(CTB::new(Tag::Reserved), BodyLength::Full(skip as u32)); } else { skip += 1; reader.rewind(); reader.consume(packet_start + skip); } } Ok(header) if skip > 0 => { if PacketParser::plausible_cert(&mut reader, &header) .is_ok() { // We recovered. First return an error. The // next time this function is called, we'll // resume here. t!("Found a valid header after {} bytes \ of junk: {:?}", skip, header); break Header::new(CTB::new(Tag::Reserved), BodyLength::Full(skip as u32)); } else { skip += 1; reader.rewind(); reader.consume(packet_start + skip); } } Ok(header) => { header_len = reader.total_out() - packet_start; break header; } } }; if skip > 0 { // Fabricate a header. t!("Recovered after {} bytes of junk", skip); pending_error = Some(crate::Error::MalformedPacket( format!("Encountered {} bytes of junk at offset {}", skip, self.bytes_read)).into()); // Be careful: if we recovered, then we // reader.total_out() includes the good header. processed += skip; break; } let tag = header.ctb().tag(); t!("Found a {:?}, length: {:?}", tag, header.length()); if packet_start > cert_start && (tag == Tag::PublicKey || tag == Tag::SecretKey) { // Start of new cert. Note: we don't advanced // processed! That would consume the header that // we want to read the next time this function is // called. t!("Stopping: found the start of a new cert ({})", tag); break; } match header.length() { BodyLength::Full(l) => { let l = *l as usize; match reader.data_consume_hard(l) { Err(err) => { t!("Stopping: reading {}'s body: {}", tag, err); // If we encountered an EOF while reading // the packet body, then we're done. if err.kind() == std::io::ErrorKind::UnexpectedEof { t!("Got an unexpected EOF, done."); self.done = true; } pending_error = Some( anyhow::Error::from(err).context(format!( "While reading {}'s body", tag))); break; } Ok(data) => { if tag == Tag::PublicKey || tag == Tag::SecretKey { let data = &data[..l]; match Key::from_bytes(data) { Err(err) => { t!("Stopping: parsing public key: {}", err); primary_key = Some(Err(err)); } Ok(key) => primary_key = Some( Ok(key.parts_into_public() .role_into_primary())), } } } } } BodyLength::Partial(_) => { t!("Stopping: Partial body length not allowed \ for {} packets", tag); pending_error = Some( crate::Error::MalformedPacket( format!("Packet {} uses partial body length \ encoding, which is not allowed in \ certificates", tag)) .into()); self.done = true; break; } BodyLength::Indeterminate => { t!("Stopping: Indeterminate length not allowed \ for {} packets", tag); pending_error = Some( crate::Error::MalformedPacket( format!("Packet {} uses intedeterminite length \ encoding, which is not allowed in \ certificates", tag)) .into()); self.done = true; break; } } let end = reader.total_out(); processed = end; let r = if packet_start == cert_start { if tag == Tag::Marker { // Silently skip marker packets at the start of a // packet sequence. cert_start = end; Ok(()) } else { packets.push((tag, header_len, packet_start)); Cert::valid_start(tag) } } else { packets.push((tag, header_len, packet_start)); Cert::valid_packet(tag) }; if let Err(err) = r { t!("Stopping: {:?} => not a certificate: {}", header, err); pending_error = Some(err); if self.bytes_read == 0 && packet_start == cert_start && matches!(tag, Tag::Unknown(_) | Tag::Private(_)) { // The very first packet is not known. Don't // bother to parse anything else. self.done = true; } break; } cert_end = end; } t!("{} bytes processed; RawCert @ offset {}, {} bytes", processed, self.bytes_read + cert_start, cert_end - cert_start); assert!(cert_start <= cert_end); assert!(cert_end <= processed); self.bytes_read += processed; // Strip the buffered_reader::Dup. self.reader = Box::new(reader).into_inner() .expect("just put it there"); // Consume the data. let cert_data = &self.reader .data_consume_hard(processed) .expect("just read it")[cert_start..cert_end]; if let Some(err) = pending_error.take() { if cert_start == cert_end { // We didn't read anything. t!("Directly returning the error"); return Some(Err(err)); } else { t!("Queuing the error"); self.pending_error = Some(err); } } if cert_start == cert_end { t!("No data."); return None; } match primary_key.expect("set") { Ok(primary_key) => Some(Ok(RawCert { data: if let Some(slice) = self.slice.as_ref() { let data = &slice[cert_start_absolute + cert_start ..cert_start_absolute + cert_end]; assert_eq!(data, cert_data); Cow::Borrowed(data) } else { Cow::Owned(cert_data.to_vec()) }, primary_key, packets, })), Err(err) => Some(Err(Error::UnsupportedCert(err, cert_data.into()).into())), } } } /// Errors used in this module. #[non_exhaustive] #[derive(thiserror::Error, Debug)] pub enum Error { /// Unsupported Cert. /// /// This usually occurs, because the primary key is in an /// unsupported format. In particular, Sequoia does not support /// version 3 keys. #[error("Unsupported Cert: {0}")] UnsupportedCert(anyhow::Error, Vec<u8>), } #[cfg(test)] mod test { use super::*; use crate::cert::CertParser; use crate::cert::CertBuilder; use crate::packet::Literal; use crate::parse::RECOVERY_THRESHOLD; use crate::parse::PacketParserResult; use crate::serialize::Serialize; use crate::types::DataFormat; use crate::packet::Unknown; use crate::packet::CompressedData; fn cert_cmp(a: Cert, b: Cert) { if a == b { return; } let a = a.into_tsk().into_packets().collect::<Vec<_>>(); let b = b.into_tsk().into_packets().collect::<Vec<_>>(); for (i, (a, b)) in a.iter().zip(b.iter()).enumerate() { if a != b { panic!("Differ at element #{}:\n {:?}\n {:?}", i, a, b); } } if a.len() > b.len() { eprintln!("Left has more packets:"); for p in &a[b.len()..] { eprintln!(" - {}", p.tag()); } } if b.len() > a.len() { eprintln!("Right has more packets:"); for p in &b[a.len()..] { eprintln!(" - {}", p.tag()); } } if a.len() != b.len() { panic!("Different lengths (common prefix identical): {} vs. {}", a.len(), b.len()); } } // Compares the result of a RawCertParser with the results of a // CertParser on a particular byte stream. fn compare_parse(bytes: &[u8]) -> Vec<RawCert> { let mut result = Vec::new(); // We do the comparison two times: once with a byte stream // (this exercises the Cow::Borrowed path), and one // with a buffered reader (this exercises the Cow::Owned // code path). for &from_bytes in [true, false].iter() { let cp = CertParser::from_bytes(bytes); let rp = if from_bytes { eprintln!("=== RawCertParser::from_bytes"); RawCertParser::from_bytes(bytes) } else { eprintln!("=== RawCertParser::from_reader"); RawCertParser::from_reader(std::io::Cursor::new(bytes)) }; assert_eq!(cp.is_err(), rp.is_err(), "CertParser: {:?}; RawCertParser: {:?}", cp.map(|_| "Parsed"), rp.map(|_| "Parsed")); if cp.is_err() && rp.is_err() { return Vec::new(); } let mut cp = cp.expect("valid"); let mut rp = rp.expect("valid"); let mut raw_certs = Vec::new(); loop { eprintln!("=== NEXT CERTPARSER"); let c = cp.next(); eprintln!("=== END CERTPARSER"); eprintln!("=== NEXT RAWCERTPARSER"); let r = rp.next(); eprintln!("=== END RAWCERTPARSER"); let (c, r) = match (c, r) { // Both return ok. (Some(Ok(c)), Some(Ok(r))) => (c, r), // Both return an error. (Some(Err(_)), Some(Err(_))) => continue, // Both return EOF. (None, None) => break, (c, r) => { panic!("\n\ CertParser returned: {:?}\n\ RawCertParser returned: {:?}", c, r); } }; assert_eq!(c.fingerprint(), r.fingerprint()); eprintln!("CertParser says:"); for (i, p) in c.clone().into_tsk().into_packets().enumerate() { eprintln!(" - {}. {}", i, p.tag()); } let rp = Cert::from_bytes(r.as_bytes()).unwrap(); eprintln!("RawCertParser says:"); for (i, p) in rp.clone().into_tsk().into_packets().enumerate() { eprintln!(" - {}. {}", i, p.tag()); } cert_cmp(c.clone(), rp); raw_certs.push(r); } result = raw_certs; } result } #[test] fn empty() { let bytes = &[]; let certs = compare_parse(bytes); assert_eq!(certs.len(), 0); } #[test] fn a_cert() { let testy = crate::tests::key("testy.pgp"); let bytes = testy; let certs = compare_parse(bytes); assert_eq!(certs.len(), 1); let cert = &certs[0]; assert_eq!(cert.as_bytes(), testy); let tags = &[ Tag::PublicKey, Tag::UserID, Tag::Signature, Tag::PublicSubkey, Tag::Signature ]; assert_eq!( &cert.packets().map(|p| p.tag()).collect::<Vec<Tag>>()[..], tags); // Check that we can parse the individual packets and that // they have the correct tag. for (p, tag) in cert.packets().zip(tags.iter()) { let ppr = PacketParser::from_bytes(p.as_bytes()).expect("valid"); if let PacketParserResult::Some(pp) = ppr { let (p, pp) = pp.next().expect("valid"); assert_eq!(p.tag(), *tag); assert!(matches!(pp, PacketParserResult::EOF(_))); } else { panic!("Unexpected EOF"); } } } #[test] fn two_certs() { let testy = crate::tests::key("testy.pgp"); let mut bytes = testy.to_vec(); bytes.extend_from_slice(testy); let certs = compare_parse(&bytes[..]); assert_eq!(certs.len(), 2); for cert in certs.into_iter() { assert_eq!(cert.as_bytes(), testy); assert_eq!( &cert.packets().map(|p| p.tag()).collect::<Vec<Tag>>()[..], &[ Tag::PublicKey, Tag::UserID, Tag::Signature, Tag::PublicSubkey, Tag::Signature ]); } } #[test] fn marker_packet_ignored() { use crate::serialize::Serialize; // Only a marker packet. let mut marker = Vec::new(); Packet::Marker(Default::default()) .serialize(&mut marker).unwrap(); compare_parse(&marker[..]); // Marker at the start. let mut testy_with_marker = Vec::new(); Packet::Marker(Default::default()) .serialize(&mut testy_with_marker).unwrap(); testy_with_marker.extend_from_slice(crate::tests::key("testy.pgp")); compare_parse(&testy_with_marker[..]); // Marker at the end. let mut testy_with_marker = Vec::new(); testy_with_marker.extend_from_slice(crate::tests::key("testy.pgp")); Packet::Marker(Default::default()) .serialize(&mut testy_with_marker).unwrap(); compare_parse(&testy_with_marker[..]); } #[test] fn invalid_packets() -> Result<()> { tracer!(TRACE, "invalid_packets", 0); let (cert, _) = CertBuilder::general_purpose(Some("alice@example.org")) .generate()?; let cert = cert.into_packets().collect::<Vec<_>>(); // A userid packet. let userid : Packet = cert.clone() .into_iter() .filter(|p| p.tag() == Tag::UserID) .next() .unwrap(); // An unknown packet. let tag = Tag::Private(61); let unknown : Packet = Unknown::new(tag, crate::Error::UnsupportedPacketType(tag).into()) .into(); // A literal packet. (This is a valid OpenPGP Message.) let mut lit = Literal::new(DataFormat::Unicode); lit.set_body(b"test".to_vec()); let lit = Packet::from(lit); // A compressed data packet containing a literal data packet. // (This is a valid OpenPGP Message.) let cd = { use crate::types::CompressionAlgorithm; use crate::packet; use crate::PacketPile; use crate::serialize::Serialize; use crate::parse::Parse; let mut cd = CompressedData::new( CompressionAlgorithm::Uncompressed); let mut body = Vec::new(); lit.serialize(&mut body)?; cd.set_body(packet::Body::Processed(body)); let cd = Packet::from(cd); // Make sure we created the message correctly: serialize, // parse it, and then check its form. let mut bytes = Vec::new(); cd.serialize(&mut bytes)?; let pp = PacketPile::from_bytes(&bytes[..])?; assert_eq!(pp.descendants().count(), 2); assert_eq!(pp.path_ref(&[ 0 ]).unwrap().tag(), packet::Tag::CompressedData); assert_eq!(pp.path_ref(&[ 0, 0 ]), Some(&lit)); cd }; fn check(input: impl Iterator<Item=Packet>) { let mut bytes = Vec::new(); for p in input { p.serialize(&mut bytes).unwrap(); } compare_parse(&bytes[..]); } fn interleave(cert: &Vec<Packet>, p: &Packet) { t!("A certificate, a {}.", p.tag()); check( cert.clone().into_iter() .chain(p.clone())); t!("A certificate, two {}.", p.tag()); check( cert.clone().into_iter() .chain(p.clone()) .chain(p.clone())); t!("A {}, a certificate.", p.tag()); check( p.clone().into_iter() .chain(cert.clone())); t!("Two {}, a certificate.", p.tag()); check( p.clone().into_iter() .chain(p.clone()) .chain(cert.clone())); t!("Two {}, a certificate, two {}.", p.tag(), p.tag()); check( p.clone().into_iter() .chain(p.clone()) .chain(cert.clone()) .chain(p.clone()) .chain(p.clone())); t!("Two {}, two certificates, two {}, a certificate."); check( p.clone().into_iter() .chain(p.clone()) .chain(cert.clone()) .chain(cert.clone()) .chain(p.clone()) .chain(p.clone()) .chain(cert.clone())); } interleave(&cert, &lit); // The certificate parser shouldn't recurse into containers. // So, the compressed data packets should show up as a single // error. interleave(&cert, &cd); // The certificate parser should treat unknown packets as // valid certificate components. let mut cert_plus = cert.clone(); cert_plus.push(unknown.clone()); t!("A certificate, an unknown."); check( cert.clone().into_iter() .chain(unknown.clone())); t!("An unknown, a certificate."); check( unknown.clone().into_iter() .chain(cert.clone())); t!("A certificate, two unknowns."); check( cert.clone().into_iter() .chain(unknown.clone()) .chain(unknown.clone())); t!("A certificate, an unknown, a certificate."); check( cert.clone().into_iter() .chain(unknown.clone()) .chain(cert.clone())); t!("A Literal, two User IDs"); check( lit.clone().into_iter() .chain(userid.clone()) .chain(userid.clone())); t!("A User ID, a certificate"); check( userid.clone().into_iter() .chain(cert.clone())); t!("Two User IDs, a certificate"); check( userid.clone().into_iter() .chain(userid.clone()) .chain(cert.clone())); Ok(()) } fn parse_test(n: usize, literal: bool, bad: usize) -> Result<()> { tracer!(TRACE, "t", 0); // Parses keyrings with different numbers of keys and // different errors. // n: number of keys // literal: whether to interleave literal packets. // bad: whether to insert invalid data (NUL bytes where // the start of a certificate is expected). let nulls = vec![ 0; bad ]; t!("n: {}, literals: {}, bad data: {}", n, literal, bad); let mut data = Vec::new(); let mut certs_orig = vec![]; for i in 0..n { let (cert, _) = CertBuilder::general_purpose( Some(format!("{}@example.org", i))) .generate()?; cert.as_tsk().serialize(&mut data)?; certs_orig.push(cert); if literal { let mut lit = Literal::new(DataFormat::Unicode); lit.set_body(b"data".to_vec()); Packet::from(lit).serialize(&mut data)?; } // Push some NUL bytes. data.extend(&nulls[..bad]); } if n == 0 { // Push some NUL bytes even if we didn't add any packets. data.extend(&nulls[..bad]); } assert_eq!(certs_orig.len(), n); t!("Start of data: {} {}", if let Some(x) = data.get(0) { format!("{:02X}", x) } else { "XX".into() }, if let Some(x) = data.get(1) { format!("{:02X}", x) } else { "XX".into() }); compare_parse(&data); Ok(()) } #[test] fn parse_keyring_simple() -> Result<()> { for n in [1, 100, 0].iter() { parse_test(*n, false, 0)?; } Ok(()) } #[test] fn parse_keyring_interleaved_literals() -> Result<()> { for n in [1, 100, 0].iter() { parse_test(*n, true, 0)?; } Ok(()) } #[test] fn parse_keyring_interleaved_small_junk() -> Result<()> { for n in [1, 100, 0].iter() { parse_test(*n, false, 1)?; } Ok(()) } #[test] fn parse_keyring_interleaved_unrecoverable_junk() -> Result<()> { // PacketParser is pretty good at recovering from junk in the // middle: it will search the next RECOVERY_THRESHOLD bytes // for a valid packet. If it finds it, it will turn the junk // into a reserved packet and resume. Insert a lot of NULs to // prevent the recovery mechanism from working. for n in [1, 100, 0].iter() { parse_test(*n, false, 2 * RECOVERY_THRESHOLD)?; } Ok(()) } #[test] fn parse_keyring_interleaved_literal_and_small_junk() -> Result<()> { for n in [1, 100, 0].iter() { parse_test(*n, true, 1)?; } Ok(()) } #[test] fn parse_keyring_interleaved_literal_and_unrecoverable_junk() -> Result<()> { for n in [1, 100, 0].iter() { parse_test(*n, true, 2 * RECOVERY_THRESHOLD)?; } Ok(()) } #[test] fn parse_keyring_no_public_key() -> Result<()> { tracer!(TRACE, "parse_keyring_no_public_key", 0); // The first few packets are not the valid start of a // certificate. Each of those should return in an Error. // But, that shouldn't stop us from parsing the rest of the // keyring. let (cert_1, _) = CertBuilder::general_purpose( Some("a@example.org")) .generate()?; let cert_1_packets: Vec<Packet> = cert_1.into_packets().collect(); let (cert_2, _) = CertBuilder::general_purpose( Some("b@example.org")) .generate()?; for n in 1..cert_1_packets.len() { t!("n: {}", n); let mut data = Vec::new(); for i in n..cert_1_packets.len() { cert_1_packets[i].serialize(&mut data)?; } cert_2.as_tsk().serialize(&mut data)?; compare_parse(&data); } Ok(()) } #[test] fn accessors() { let testy = crate::tests::key("testy.pgp"); let certs = RawCertParser::from_bytes(testy) .expect("valid") .collect::<Result<Vec<RawCert>>>() .expect("valid"); assert_eq!(certs.len(), 1); let cert = &certs[0]; assert_eq!(cert.as_bytes(), testy); assert_eq!(cert.primary_key().fingerprint(), "3E8877C877274692975189F5D03F6F865226FE8B" .parse().expect("valid")); assert_eq!(cert.keys().map(|k| k.fingerprint()).collect::<Vec<_>>(), vec![ "3E8877C877274692975189F5D03F6F865226FE8B" .parse().expect("valid"), "01F187575BD45644046564C149E2118166C92632" .parse().expect("valid") ]); assert_eq!(cert.keys().subkeys() .map(|k| k.fingerprint()).collect::<Vec<_>>(), vec![ "01F187575BD45644046564C149E2118166C92632" .parse().expect("valid") ]); assert_eq!( cert.userids() .map(|u| { String::from_utf8_lossy(u.value()).into_owned() }) .collect::<Vec<_>>(), vec![ "Testy McTestface <testy@example.org>" ]); } // Test the raw cert parser implementation. #[test] fn raw_cert_parser_impl() { // Read one certificate. let testy = crate::tests::key("testy.pgp"); let raw = RawCert::from_bytes(testy).expect("valid"); let cert = Cert::from_bytes(testy).expect("valid"); assert_eq!( raw.keys().map(|k| k.fingerprint()).collect::<Vec<_>>(), cert.keys().map(|k| k.key().fingerprint()).collect::<Vec<_>>()); assert_eq!( raw.userids().collect::<Vec<_>>(), cert.userids().map(|ua| ua.userid().clone()).collect::<Vec<_>>()); // Parse zero certificates. eprintln!("Parsing 0 bytes"); let raw = RawCert::from_bytes(b""); match &raw { Ok(_) => eprintln!("raw: Ok"), Err(err) => eprintln!("raw: {}", err), } let cert = Cert::from_bytes(b""); match &cert { Ok(_) => eprintln!("cert: Ok"), Err(err) => eprintln!("cert: {}", err), } assert!( matches!(cert.map_err(|e| e.downcast::<crate::Error>()), Err(Ok(crate::Error::MalformedCert(_))))); assert!( matches!(raw.map_err(|e| e.downcast::<crate::Error>()), Err(Ok(crate::Error::MalformedCert(_))))); // Parse two certificates. let mut bytes = Vec::new(); bytes.extend(testy); bytes.extend(testy); let parser = CertParser::from_bytes(&bytes).expect("valid"); assert_eq!(parser.count(), 2); eprintln!("Parsing two certificates"); let raw = RawCert::from_bytes(&bytes); match &raw { Ok(_) => eprintln!("raw: Ok"), Err(err) => eprintln!("raw: {}", err), } let cert = Cert::from_bytes(&bytes); match &cert { Ok(_) => eprintln!("cert: Ok"), Err(err) => eprintln!("cert: {}", err), } assert!( matches!(cert.map_err(|e| e.downcast::<crate::Error>()), Err(Ok(crate::Error::MalformedCert(_))))); assert!( matches!(raw.map_err(|e| e.downcast::<crate::Error>()), Err(Ok(crate::Error::MalformedCert(_))))); } #[test] fn concatenated_armored_certs() -> Result<()> { let mut keyring = Vec::new(); keyring.extend_from_slice(b"some\ntext\n"); keyring.extend_from_slice(crate::tests::key("testy.asc")); keyring.extend_from_slice(crate::tests::key("testy.asc")); keyring.extend_from_slice(b"some\ntext\n"); keyring.extend_from_slice(crate::tests::key("testy.asc")); keyring.extend_from_slice(b"some\ntext\n"); let certs = RawCertParser::from_bytes(&keyring)?.collect::<Vec<_>>(); assert_eq!(certs.len(), 3); assert!(certs.iter().all(|c| c.is_ok())); Ok(()) } } �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/src/cert/revoke.rs������������������������������������������������������������0000644�0000000�0000000�00000145773�10461020230�0016345�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������use std::convert::TryFrom; use std::time; use crate::{ HashAlgorithm, Result, SignatureType, }; use crate::types::{ ReasonForRevocation, }; use crate::crypto::Signer; use crate::packet::{ Key, key, signature, Signature, UserAttribute, UserID, }; use crate::packet::signature::subpacket::NotationDataFlags; use crate::cert::prelude::*; /// A builder for revocation certificates for OpenPGP certificates. /// /// A revocation certificate for an OpenPGP certificate (as opposed /// to, say, a subkey) has two degrees of freedom: the certificate, /// and the key used to sign the revocation certificate. /// /// Normally, the key used to sign the revocation certificate is the /// certificate's primary key. However, this is not required. For /// instance, if Alice has marked Robert's certificate (`R`) as a /// [designated revoker] for her certificate (`A`), then `R` can /// revoke `A` or parts of `A`. In this case, the certificate is `A`, /// and the key used to sign the revocation certificate comes from /// `R`. /// /// [designated revoker]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.23 /// /// # Examples /// /// Revoke `cert`, which was compromised yesterday: /// /// ```rust /// use sequoia_openpgp as openpgp; /// # use openpgp::Result; /// use openpgp::cert::prelude::*; /// use openpgp::policy::StandardPolicy; /// use openpgp::types::ReasonForRevocation; /// use openpgp::types::RevocationStatus; /// use openpgp::types::SignatureType; /// /// # fn main() -> Result<()> { /// let p = &StandardPolicy::new(); /// /// # let (cert, _) = CertBuilder::new() /// # .generate()?; /// # assert_eq!(RevocationStatus::NotAsFarAsWeKnow, /// # cert.revocation_status(p, None)); /// # /// // Create and sign a revocation certificate. /// let mut signer = cert.primary_key().key().clone() /// .parts_into_secret()?.into_keypair()?; /// # let yesterday = std::time::SystemTime::now(); /// let sig = CertRevocationBuilder::new() /// // Don't use the current time, since the certificate was /// // actually compromised yesterday. /// .set_signature_creation_time(yesterday)? /// .set_reason_for_revocation(ReasonForRevocation::KeyCompromised, /// b"It was the maid :/")? /// .build(&mut signer, &cert, None)?; /// /// // Merge it into the certificate. /// let cert = cert.insert_packets(sig.clone())?.0; /// /// // Now it's revoked. /// assert_eq!(RevocationStatus::Revoked(vec![&sig]), /// cert.revocation_status(p, None)); /// # Ok(()) /// # } pub struct CertRevocationBuilder { builder: signature::SignatureBuilder, } assert_send_and_sync!(CertRevocationBuilder); impl CertRevocationBuilder { /// Returns a new `CertRevocationBuilder`. /// /// # Examples /// /// ```rust /// use sequoia_openpgp as openpgp; /// # use openpgp::Result; /// use openpgp::cert::prelude::*; /// /// # fn main() -> Result<()> { /// let builder = CertRevocationBuilder::new(); /// # Ok(()) /// # } pub fn new() -> Self { Self { builder: signature::SignatureBuilder::new(SignatureType::KeyRevocation) } } /// Sets the reason for revocation. /// /// # Examples /// /// ```rust /// use sequoia_openpgp as openpgp; /// # use openpgp::Result; /// use openpgp::cert::prelude::*; /// use openpgp::types::ReasonForRevocation; /// /// # fn main() -> Result<()> { /// let builder = CertRevocationBuilder::new() /// .set_reason_for_revocation(ReasonForRevocation::KeyRetired, /// b"I'm retiring this key. \ /// Please use my new OpenPGP certificate (FPR)"); /// # Ok(()) /// # } pub fn set_reason_for_revocation(self, code: ReasonForRevocation, reason: &[u8]) -> Result<Self> { Ok(Self { builder: self.builder.set_reason_for_revocation(code, reason)? }) } /// Sets the revocation certificate's creation time. /// /// The creation time is interpreted as the time at which the /// certificate should be considered revoked. For a soft /// revocation, artifacts created prior to the revocation are /// still considered valid. /// /// You'll usually want to set this explicitly and not use the /// current time. /// /// First, the creation time should reflect the time of the event /// that triggered the revocation. As such, if it is discovered /// that a certificate was compromised a week ago, then the /// revocation certificate should be backdated appropriately. /// /// Second, because access to secret key material can be lost, it /// can be useful to create a revocation certificate in advance. /// Of course, such a revocation certificate will inevitably be /// outdated. To mitigate this problem, a number of revocation /// certificates can be created with different creation times. /// Then should a revocation certificate be needed, the most /// appropriate one can be used. /// /// # Examples /// /// ```rust /// use std::time::{SystemTime, Duration}; /// use sequoia_openpgp as openpgp; /// # use openpgp::Result; /// use openpgp::cert::prelude::*; /// /// # fn main() -> Result<()> { /// let now = SystemTime::now(); /// let month = Duration::from_secs(((365.24 / 12.) * 24. * 60. * 60.) as u64); /// /// // Pre-generate revocation certificates, one for each month /// // for the next 48 months. /// for i in 0..48 { /// let builder = CertRevocationBuilder::new() /// .set_signature_creation_time(now + i * month); /// // ... /// } /// # Ok(()) /// # } pub fn set_signature_creation_time(self, creation_time: time::SystemTime) -> Result<Self> { Ok(Self { builder: self.builder.set_signature_creation_time(creation_time)? }) } /// Adds a notation to the revocation certificate. /// /// Unlike the [`CertRevocationBuilder::set_notation`] method, this function /// does not first remove any existing notation with the specified name. /// /// See [`SignatureBuilder::add_notation`] for further documentation. /// /// [`SignatureBuilder::add_notation`]: crate::packet::signature::SignatureBuilder::add_notation() /// /// # Examples /// /// ```rust /// use sequoia_openpgp as openpgp; /// # use openpgp::Result; /// use openpgp::cert::prelude::*; /// use openpgp::packet::signature::subpacket::NotationDataFlags; /// /// # fn main() -> Result<()> { /// let builder = CertRevocationBuilder::new().add_notation( /// "revocation-policy@example.org", /// "https://policy.example.org/cert-revocation-policy", /// NotationDataFlags::empty().set_human_readable(), /// false, /// ); /// # Ok(()) /// # } pub fn add_notation<N, V, F>(self, name: N, value: V, flags: F, critical: bool) -> Result<Self> where N: AsRef<str>, V: AsRef<[u8]>, F: Into<Option<NotationDataFlags>>, { Ok(Self { builder: self.builder.add_notation(name, value, flags, critical)? }) } /// Sets a notation to the revocation certificate. /// /// Unlike the [`CertRevocationBuilder::add_notation`] method, this function /// first removes any existing notation with the specified name. /// /// See [`SignatureBuilder::set_notation`] for further documentation. /// /// [`SignatureBuilder::set_notation`]: crate::packet::signature::SignatureBuilder::set_notation() /// /// # Examples /// /// ```rust /// use sequoia_openpgp as openpgp; /// # use openpgp::Result; /// use openpgp::cert::prelude::*; /// use openpgp::packet::signature::subpacket::NotationDataFlags; /// /// # fn main() -> Result<()> { /// let builder = CertRevocationBuilder::new().set_notation( /// "revocation-policy@example.org", /// "https://policy.example.org/cert-revocation-policy", /// NotationDataFlags::empty().set_human_readable(), /// false, /// ); /// # Ok(()) /// # } pub fn set_notation<N, V, F>(self, name: N, value: V, flags: F, critical: bool) -> Result<Self> where N: AsRef<str>, V: AsRef<[u8]>, F: Into<Option<NotationDataFlags>>, { Ok(Self { builder: self.builder.set_notation(name, value, flags, critical)? }) } /// Returns a signed revocation certificate. /// /// A revocation certificate is generated for `cert` and signed /// using `signer` with the specified hash algorithm. Normally, /// you should pass `None` to select the default hash algorithm. /// /// # Examples /// /// ```rust /// use sequoia_openpgp as openpgp; /// # use openpgp::Result; /// use openpgp::cert::prelude::*; /// use openpgp::policy::StandardPolicy; /// use openpgp::types::ReasonForRevocation; /// # use openpgp::types::RevocationStatus; /// # use openpgp::types::SignatureType; /// /// # fn main() -> Result<()> { /// let p = &StandardPolicy::new(); /// /// # let (cert, _) = CertBuilder::new() /// # .generate()?; /// # /// // Create and sign a revocation certificate. /// let mut signer = cert.primary_key().key().clone() /// .parts_into_secret()?.into_keypair()?; /// let sig = CertRevocationBuilder::new() /// .set_reason_for_revocation(ReasonForRevocation::KeyRetired, /// b"Left Foo Corp.")? /// .build(&mut signer, &cert, None)?; /// /// # assert_eq!(sig.typ(), SignatureType::KeyRevocation); /// # /// # // Merge it into the certificate. /// # let cert = cert.insert_packets(sig.clone())?.0; /// # /// # // Now it's revoked. /// # assert_eq!(RevocationStatus::Revoked(vec![&sig]), /// # cert.revocation_status(p, None)); /// # Ok(()) /// # } pub fn build<H>(self, signer: &mut dyn Signer, cert: &Cert, hash_algo: H) -> Result<Signature> where H: Into<Option<HashAlgorithm>> { self.builder .set_hash_algo(hash_algo.into().unwrap_or(HashAlgorithm::SHA512)) .sign_direct_key(signer, cert.primary_key().key()) } } impl TryFrom<signature::SignatureBuilder> for CertRevocationBuilder { type Error = anyhow::Error; fn try_from(builder: signature::SignatureBuilder) -> Result<Self> { if builder.typ() != SignatureType::KeyRevocation { return Err( crate::Error::InvalidArgument( format!("Expected signature type to be KeyRevocation but got {}", builder.typ())).into()); } Ok(Self { builder }) } } /// A builder for revocation certificates for subkeys. /// /// A revocation certificate for a subkey has three degrees of /// freedom: the certificate, the key used to generate the revocation /// certificate, and the subkey being revoked. /// /// Normally, the key used to sign the revocation certificate is the /// certificate's primary key, and the subkey is a subkey that is /// bound to the certificate. However, this is not required. For /// instance, if Alice has marked Robert's certificate (`R`) as a /// [designated revoker] for her certificate (`A`), then `R` can /// revoke `A` or parts of `A`. In such a case, the certificate is /// `A`, the key used to sign the revocation certificate comes from /// `R`, and the subkey being revoked is bound to `A`. /// /// But, the subkey doesn't technically need to be bound to the /// certificate either. For instance, it is technically possible for /// `R` to create a revocation certificate for a subkey in the context /// of `A`, even if that subkey is not bound to `A`. Semantically, /// such a revocation certificate is currently meaningless. /// /// [designated revoker]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.23 /// /// # Examples /// /// Revoke a subkey, which is now considered to be too weak: /// /// ```rust /// use sequoia_openpgp as openpgp; /// # use openpgp::Result; /// use openpgp::cert::prelude::*; /// use openpgp::policy::StandardPolicy; /// use openpgp::types::ReasonForRevocation; /// use openpgp::types::RevocationStatus; /// use openpgp::types::SignatureType; /// /// # fn main() -> Result<()> { /// let p = &StandardPolicy::new(); /// /// # let (cert, _) = CertBuilder::new() /// # .add_transport_encryption_subkey() /// # .generate()?; /// # assert_eq!(RevocationStatus::NotAsFarAsWeKnow, /// # cert.revocation_status(p, None)); /// # /// // Create and sign a revocation certificate. /// let mut signer = cert.primary_key().key().clone() /// .parts_into_secret()?.into_keypair()?; /// let subkey = cert.keys().subkeys().nth(0).unwrap(); /// let sig = SubkeyRevocationBuilder::new() /// .set_reason_for_revocation(ReasonForRevocation::KeyRetired, /// b"Revoking due to the recent crypto vulnerabilities.")? /// .build(&mut signer, &cert, subkey.key(), None)?; /// /// // Merge it into the certificate. /// let cert = cert.insert_packets(sig.clone())?.0; /// /// // Now it's revoked. /// let subkey = cert.keys().subkeys().nth(0).unwrap(); /// if let RevocationStatus::Revoked(revocations) = subkey.revocation_status(p, None) { /// assert_eq!(revocations.len(), 1); /// assert_eq!(*revocations[0], sig); /// } else { /// panic!("Subkey is not revoked."); /// } /// /// // But the certificate isn't. /// assert_eq!(RevocationStatus::NotAsFarAsWeKnow, /// cert.revocation_status(p, None)); /// # Ok(()) } /// ``` pub struct SubkeyRevocationBuilder { builder: signature::SignatureBuilder, } assert_send_and_sync!(SubkeyRevocationBuilder); impl SubkeyRevocationBuilder { /// Returns a new `SubkeyRevocationBuilder`. /// /// # Examples /// /// ```rust /// use sequoia_openpgp as openpgp; /// # use openpgp::Result; /// use openpgp::cert::prelude::*; /// /// # fn main() -> Result<()> { /// let builder = SubkeyRevocationBuilder::new(); /// # Ok(()) /// # } pub fn new() -> Self { Self { builder: signature::SignatureBuilder::new(SignatureType::SubkeyRevocation) } } /// Sets the reason for revocation. /// /// # Examples /// /// Revoke a possibly compromised subkey: /// /// ```rust /// use sequoia_openpgp as openpgp; /// # use openpgp::Result; /// use openpgp::cert::prelude::*; /// use openpgp::types::ReasonForRevocation; /// /// # fn main() -> Result<()> { /// let builder = SubkeyRevocationBuilder::new() /// .set_reason_for_revocation(ReasonForRevocation::KeyCompromised, /// b"I lost my smartcard."); /// # Ok(()) /// # } pub fn set_reason_for_revocation(self, code: ReasonForRevocation, reason: &[u8]) -> Result<Self> { Ok(Self { builder: self.builder.set_reason_for_revocation(code, reason)? }) } /// Sets the revocation certificate's creation time. /// /// The creation time is interpreted as the time at which the /// subkey should be considered revoked. For a soft revocation, /// artifacts created prior to the revocation are still considered /// valid. /// /// You'll usually want to set this explicitly and not use the /// current time. In particular, if a subkey is compromised, /// you'll want to set this to the time when the compromise /// happened. /// /// # Examples /// /// Create a revocation certificate for a subkey that was /// compromised yesterday: /// /// ```rust /// use sequoia_openpgp as openpgp; /// # use openpgp::Result; /// use openpgp::cert::prelude::*; /// /// # fn main() -> Result<()> { /// # let yesterday = std::time::SystemTime::now(); /// let builder = SubkeyRevocationBuilder::new() /// .set_signature_creation_time(yesterday); /// # Ok(()) /// # } pub fn set_signature_creation_time(self, creation_time: time::SystemTime) -> Result<Self> { Ok(Self { builder: self.builder.set_signature_creation_time(creation_time)? }) } /// Adds a notation to the revocation certificate. /// /// Unlike the [`SubkeyRevocationBuilder::set_notation`] method, this function /// does not first remove any existing notation with the specified name. /// /// See [`SignatureBuilder::add_notation`] for further documentation. /// /// [`SignatureBuilder::add_notation`]: crate::packet::signature::SignatureBuilder::add_notation() /// /// # Examples /// /// ```rust /// use sequoia_openpgp as openpgp; /// # use openpgp::Result; /// use openpgp::cert::prelude::*; /// use openpgp::packet::signature::subpacket::NotationDataFlags; /// /// # fn main() -> Result<()> { /// let builder = CertRevocationBuilder::new().add_notation( /// "revocation-policy@example.org", /// "https://policy.example.org/cert-revocation-policy", /// NotationDataFlags::empty().set_human_readable(), /// false, /// ); /// # Ok(()) /// # } pub fn add_notation<N, V, F>(self, name: N, value: V, flags: F, critical: bool) -> Result<Self> where N: AsRef<str>, V: AsRef<[u8]>, F: Into<Option<NotationDataFlags>>, { Ok(Self { builder: self.builder.add_notation(name, value, flags, critical)? }) } /// Sets a notation to the revocation certificate. /// /// Unlike the [`SubkeyRevocationBuilder::add_notation`] method, this function /// first removes any existing notation with the specified name. /// /// See [`SignatureBuilder::set_notation`] for further documentation. /// /// [`SignatureBuilder::set_notation`]: crate::packet::signature::SignatureBuilder::set_notation() /// /// # Examples /// /// ```rust /// use sequoia_openpgp as openpgp; /// # use openpgp::Result; /// use openpgp::cert::prelude::*; /// use openpgp::packet::signature::subpacket::NotationDataFlags; /// /// # fn main() -> Result<()> { /// let builder = CertRevocationBuilder::new().set_notation( /// "revocation-policy@example.org", /// "https://policy.example.org/cert-revocation-policy", /// NotationDataFlags::empty().set_human_readable(), /// false, /// ); /// # Ok(()) /// # } pub fn set_notation<N, V, F>(self, name: N, value: V, flags: F, critical: bool) -> Result<Self> where N: AsRef<str>, V: AsRef<[u8]>, F: Into<Option<NotationDataFlags>>, { Ok(Self { builder: self.builder.set_notation(name, value, flags, critical)? }) } /// Returns a signed revocation certificate. /// /// A revocation certificate is generated for `cert` and `key` and /// signed using `signer` with the specified hash algorithm. /// Normally, you should pass `None` to select the default hash /// algorithm. /// /// # Examples /// /// Revoke a subkey, which is now considered to be too weak: /// /// ```rust /// use sequoia_openpgp as openpgp; /// # use openpgp::Result; /// use openpgp::cert::prelude::*; /// use openpgp::policy::StandardPolicy; /// use openpgp::types::ReasonForRevocation; /// # use openpgp::types::RevocationStatus; /// # use openpgp::types::SignatureType; /// /// # fn main() -> Result<()> { /// let p = &StandardPolicy::new(); /// /// # let (cert, _) = CertBuilder::new() /// # .add_transport_encryption_subkey() /// # .generate()?; /// # /// // Create and sign a revocation certificate. /// let mut signer = cert.primary_key().key().clone() /// .parts_into_secret()?.into_keypair()?; /// let subkey = cert.keys().subkeys().nth(0).unwrap(); /// let sig = SubkeyRevocationBuilder::new() /// .set_reason_for_revocation(ReasonForRevocation::KeyRetired, /// b"Revoking due to the recent crypto vulnerabilities.")? /// .build(&mut signer, &cert, subkey.key(), None)?; /// /// # assert_eq!(sig.typ(), SignatureType::SubkeyRevocation); /// # /// # // Merge it into the certificate. /// # let cert = cert.insert_packets(sig.clone())?.0; /// # /// # // Now it's revoked. /// # assert_eq!(RevocationStatus::Revoked(vec![&sig]), /// # cert.keys().subkeys().nth(0).unwrap().revocation_status(p, None)); /// # Ok(()) /// # } pub fn build<H, P>(mut self, signer: &mut dyn Signer, cert: &Cert, key: &Key<P, key::SubordinateRole>, hash_algo: H) -> Result<Signature> where H: Into<Option<HashAlgorithm>>, P: key::KeyParts, { self.builder = self.builder .set_hash_algo(hash_algo.into().unwrap_or(HashAlgorithm::SHA512)); key.bind(signer, cert, self.builder) } } impl TryFrom<signature::SignatureBuilder> for SubkeyRevocationBuilder { type Error = anyhow::Error; fn try_from(builder: signature::SignatureBuilder) -> Result<Self> { if builder.typ() != SignatureType::SubkeyRevocation { return Err( crate::Error::InvalidArgument( format!("Expected signature type to be SubkeyRevocation but got {}", builder.typ())).into()); } Ok(Self { builder }) } } /// A builder for revocation certificates for User ID. /// /// A revocation certificate for a [User ID] has three degrees of /// freedom: the certificate, the key used to generate the revocation /// certificate, and the User ID being revoked. /// /// Normally, the key used to sign the revocation certificate is the /// certificate's primary key, and the User ID is a User ID that is /// bound to the certificate. However, this is not required. For /// instance, if Alice has marked Robert's certificate (`R`) as a /// [designated revoker] for her certificate (`A`), then `R` can /// revoke `A` or parts of `A`. In such a case, the certificate is /// `A`, the key used to sign the revocation certificate comes from /// `R`, and the User ID being revoked is bound to `A`. /// /// But, the User ID doesn't technically need to be bound to the /// certificate either. For instance, it is technically possible for /// `R` to create a revocation certificate for a User ID in the /// context of `A`, even if that User ID is not bound to `A`. /// Semantically, such a revocation certificate is currently /// meaningless. /// /// [User ID]: crate::packet::UserID /// [designated revoker]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.23 /// /// # Examples /// /// Revoke a User ID that is no longer valid: /// /// ```rust /// use sequoia_openpgp as openpgp; /// # use openpgp::Result; /// use openpgp::cert::prelude::*; /// use openpgp::policy::StandardPolicy; /// use openpgp::types::ReasonForRevocation; /// use openpgp::types::RevocationStatus; /// use openpgp::types::SignatureType; /// /// # fn main() -> Result<()> { /// let p = &StandardPolicy::new(); /// /// # let (cert, _) = CertBuilder::new() /// # .add_userid("some@example.org") /// # .generate()?; /// # assert_eq!(RevocationStatus::NotAsFarAsWeKnow, /// # cert.revocation_status(p, None)); /// # /// // Create and sign a revocation certificate. /// let mut signer = cert.primary_key().key().clone() /// .parts_into_secret()?.into_keypair()?; /// let ua = cert.userids().nth(0).unwrap(); /// let sig = UserIDRevocationBuilder::new() /// .set_reason_for_revocation(ReasonForRevocation::UIDRetired, /// b"Left example.org.")? /// .build(&mut signer, &cert, ua.userid(), None)?; /// /// // Merge it into the certificate. /// let cert = cert.insert_packets(sig.clone())?.0; /// /// // Now it's revoked. /// let ua = cert.userids().nth(0).unwrap(); /// if let RevocationStatus::Revoked(revocations) = ua.revocation_status(p, None) { /// assert_eq!(revocations.len(), 1); /// assert_eq!(*revocations[0], sig); /// } else { /// panic!("User ID is not revoked."); /// } /// /// // But the certificate isn't. /// assert_eq!(RevocationStatus::NotAsFarAsWeKnow, /// cert.revocation_status(p, None)); /// # Ok(()) } /// ``` pub struct UserIDRevocationBuilder { builder: signature::SignatureBuilder, } assert_send_and_sync!(UserIDRevocationBuilder); impl UserIDRevocationBuilder { /// Returns a new `UserIDRevocationBuilder`. /// /// # Examples /// /// ```rust /// use sequoia_openpgp as openpgp; /// # use openpgp::Result; /// use openpgp::cert::prelude::*; /// /// # fn main() -> Result<()> { /// let builder = UserIDRevocationBuilder::new(); /// # Ok(()) /// # } pub fn new() -> Self { Self { builder: signature::SignatureBuilder::new(SignatureType::CertificationRevocation) } } /// Sets the reason for revocation. /// /// Note: of the assigned reasons for revocation, only /// [`ReasonForRevocation::UIDRetired`] is appropriate for User /// IDs. This parameter is not fixed, however, to allow the use /// of the [private name space]. /// /// [`ReasonForRevocation::UIDRetired`]: crate::types::ReasonForRevocation::UIDRetired /// [private name space]: crate::types::ReasonForRevocation::Private /// /// /// # Examples /// /// Revoke a User ID that is no longer valid: /// /// ```rust /// use sequoia_openpgp as openpgp; /// # use openpgp::Result; /// use openpgp::cert::prelude::*; /// use openpgp::types::ReasonForRevocation; /// /// # fn main() -> Result<()> { /// let builder = UserIDRevocationBuilder::new() /// .set_reason_for_revocation(ReasonForRevocation::UIDRetired, /// b"Left example.org."); /// # Ok(()) /// # } pub fn set_reason_for_revocation(self, code: ReasonForRevocation, reason: &[u8]) -> Result<Self> { Ok(Self { builder: self.builder.set_reason_for_revocation(code, reason)? }) } /// Sets the revocation certificate's creation time. /// /// The creation time is interpreted as the time at which the User /// ID should be considered revoked. /// /// You'll usually want to set this explicitly and not use the /// current time. In particular, if a User ID is retired, you'll /// want to set this to the time when the User ID was actually /// retired. /// /// # Examples /// /// Create a revocation certificate for a User ID that was /// retired yesterday: /// /// ```rust /// use sequoia_openpgp as openpgp; /// # use openpgp::Result; /// use openpgp::cert::prelude::*; /// /// # fn main() -> Result<()> { /// # let yesterday = std::time::SystemTime::now(); /// let builder = UserIDRevocationBuilder::new() /// .set_signature_creation_time(yesterday); /// # Ok(()) /// # } pub fn set_signature_creation_time(self, creation_time: time::SystemTime) -> Result<Self> { Ok(Self { builder: self.builder.set_signature_creation_time(creation_time)? }) } /// Adds a notation to the revocation certificate. /// /// Unlike the [`UserIDRevocationBuilder::set_notation`] method, this function /// does not first remove any existing notation with the specified name. /// /// See [`SignatureBuilder::add_notation`] for further documentation. /// /// [`SignatureBuilder::add_notation`]: crate::packet::signature::SignatureBuilder::add_notation() /// /// # Examples /// /// ```rust /// use sequoia_openpgp as openpgp; /// # use openpgp::Result; /// use openpgp::cert::prelude::*; /// use openpgp::packet::signature::subpacket::NotationDataFlags; /// /// # fn main() -> Result<()> { /// let builder = CertRevocationBuilder::new().add_notation( /// "revocation-policy@example.org", /// "https://policy.example.org/cert-revocation-policy", /// NotationDataFlags::empty().set_human_readable(), /// false, /// ); /// # Ok(()) /// # } pub fn add_notation<N, V, F>(self, name: N, value: V, flags: F, critical: bool) -> Result<Self> where N: AsRef<str>, V: AsRef<[u8]>, F: Into<Option<NotationDataFlags>>, { Ok(Self { builder: self.builder.add_notation(name, value, flags, critical)? }) } /// Sets a notation to the revocation certificate. /// /// Unlike the [`UserIDRevocationBuilder::add_notation`] method, this function /// first removes any existing notation with the specified name. /// /// See [`SignatureBuilder::set_notation`] for further documentation. /// /// [`SignatureBuilder::set_notation`]: crate::packet::signature::SignatureBuilder::set_notation() /// /// # Examples /// /// ```rust /// use sequoia_openpgp as openpgp; /// # use openpgp::Result; /// use openpgp::cert::prelude::*; /// use openpgp::packet::signature::subpacket::NotationDataFlags; /// /// # fn main() -> Result<()> { /// let builder = CertRevocationBuilder::new().set_notation( /// "revocation-policy@example.org", /// "https://policy.example.org/cert-revocation-policy", /// NotationDataFlags::empty().set_human_readable(), /// false, /// ); /// # Ok(()) /// # } pub fn set_notation<N, V, F>(self, name: N, value: V, flags: F, critical: bool) -> Result<Self> where N: AsRef<str>, V: AsRef<[u8]>, F: Into<Option<NotationDataFlags>>, { Ok(Self { builder: self.builder.set_notation(name, value, flags, critical)? }) } /// Returns a signed revocation certificate. /// /// A revocation certificate is generated for `cert` and `userid` /// and signed using `signer` with the specified hash algorithm. /// Normally, you should pass `None` to select the default hash /// algorithm. /// /// # Examples /// /// Revoke a User ID, because the user has left the organization: /// /// ```rust /// use sequoia_openpgp as openpgp; /// # use openpgp::Result; /// use openpgp::cert::prelude::*; /// use openpgp::policy::StandardPolicy; /// use openpgp::types::ReasonForRevocation; /// # use openpgp::types::RevocationStatus; /// # use openpgp::types::SignatureType; /// /// # fn main() -> Result<()> { /// let p = &StandardPolicy::new(); /// /// # let (cert, _) = CertBuilder::new() /// # .add_userid("some@example.org") /// # .generate()?; /// # /// // Create and sign a revocation certificate. /// let mut signer = cert.primary_key().key().clone() /// .parts_into_secret()?.into_keypair()?; /// let ua = cert.userids().nth(0).unwrap(); /// let sig = UserIDRevocationBuilder::new() /// .set_reason_for_revocation(ReasonForRevocation::UIDRetired, /// b"Left example.org.")? /// .build(&mut signer, &cert, ua.userid(), None)?; /// /// # assert_eq!(sig.typ(), SignatureType::CertificationRevocation); /// # /// # // Merge it into the certificate. /// # let cert = cert.insert_packets(sig.clone())?.0; /// # /// # // Now it's revoked. /// # assert_eq!(RevocationStatus::Revoked(vec![&sig]), /// # cert.userids().nth(0).unwrap().revocation_status(p, None)); /// # Ok(()) /// # } pub fn build<H>(mut self, signer: &mut dyn Signer, cert: &Cert, userid: &UserID, hash_algo: H) -> Result<Signature> where H: Into<Option<HashAlgorithm>> { self.builder = self.builder .set_hash_algo(hash_algo.into().unwrap_or(HashAlgorithm::SHA512)); userid.bind(signer, cert, self.builder) } } impl TryFrom<signature::SignatureBuilder> for UserIDRevocationBuilder { type Error = anyhow::Error; fn try_from(builder: signature::SignatureBuilder) -> Result<Self> { if builder.typ() != SignatureType::CertificationRevocation { return Err( crate::Error::InvalidArgument( format!("Expected signature type to be CertificationRevocation but got {}", builder.typ())).into()); } Ok(Self { builder }) } } /// A builder for revocation certificates for User Attributes. /// /// A revocation certificate for a [User Attribute] has three degrees of /// freedom: the certificate, the key used to generate the revocation /// certificate, and the User Attribute being revoked. /// /// Normally, the key used to sign the revocation certificate is the /// certificate's primary key, and the User Attribute is a User /// Attribute that is bound to the certificate. However, this is not /// required. For instance, if Alice has marked Robert's certificate /// (`R`) as a [designated revoker] for her certificate (`A`), then /// `R` can revoke `A` or parts of `A`. In such a case, the /// certificate is `A`, the key used to sign the revocation /// certificate comes from `R`, and the User Attribute being revoked /// is bound to `A`. /// /// But, the User Attribute doesn't technically need to be bound to /// the certificate either. For instance, it is technically possible /// for `R` to create a revocation certificate for a User Attribute in /// the context of `A`, even if that User Attribute is not bound to /// `A`. Semantically, such a revocation certificate is currently /// meaningless. /// /// [User Attribute]: crate::packet::user_attribute /// [designated revoker]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.23 /// /// # Examples /// /// Revoke a User Attribute that is no longer valid: /// /// ```rust /// # use openpgp::packet::user_attribute::Subpacket; /// use sequoia_openpgp as openpgp; /// # use openpgp::Result; /// use openpgp::cert::prelude::*; /// # use openpgp::packet::UserAttribute; /// use openpgp::policy::StandardPolicy; /// use openpgp::types::ReasonForRevocation; /// use openpgp::types::RevocationStatus; /// use openpgp::types::SignatureType; /// /// # fn main() -> Result<()> { /// let p = &StandardPolicy::new(); /// /// # // Create some user attribute. Doctests do not pass cfg(test), /// # // so UserAttribute::arbitrary is not available /// # let sp = Subpacket::Unknown(7, vec![7; 7].into_boxed_slice()); /// # let user_attribute = UserAttribute::new(&[sp])?; /// # /// # let (cert, _) = CertBuilder::new() /// # .add_user_attribute(user_attribute) /// # .generate()?; /// # assert_eq!(RevocationStatus::NotAsFarAsWeKnow, /// # cert.revocation_status(p, None)); /// # /// // Create and sign a revocation certificate. /// let mut signer = cert.primary_key().key().clone() /// .parts_into_secret()?.into_keypair()?; /// let ua = cert.user_attributes().nth(0).unwrap(); /// let sig = UserAttributeRevocationBuilder::new() /// .set_reason_for_revocation(ReasonForRevocation::UIDRetired, /// b"Lost the beard.")? /// .build(&mut signer, &cert, ua.user_attribute(), None)?; /// /// // Merge it into the certificate. /// let cert = cert.insert_packets(sig.clone())?.0; /// /// // Now it's revoked. /// let ua = cert.user_attributes().nth(0).unwrap(); /// if let RevocationStatus::Revoked(revocations) = ua.revocation_status(p, None) { /// assert_eq!(revocations.len(), 1); /// assert_eq!(*revocations[0], sig); /// } else { /// panic!("User Attribute is not revoked."); /// } /// /// // But the certificate isn't. /// assert_eq!(RevocationStatus::NotAsFarAsWeKnow, /// cert.revocation_status(p, None)); /// # Ok(()) } /// ``` pub struct UserAttributeRevocationBuilder { builder: signature::SignatureBuilder, } assert_send_and_sync!(UserAttributeRevocationBuilder); impl UserAttributeRevocationBuilder { /// Returns a new `UserAttributeRevocationBuilder`. /// /// # Examples /// /// ```rust /// use sequoia_openpgp as openpgp; /// # use openpgp::Result; /// use openpgp::cert::prelude::*; /// /// # fn main() -> Result<()> { /// let builder = UserAttributeRevocationBuilder::new(); /// # Ok(()) /// # } pub fn new() -> Self { Self { builder: signature::SignatureBuilder::new(SignatureType::CertificationRevocation) } } /// Sets the reason for revocation. /// /// Note: of the assigned reasons for revocation, only /// [`ReasonForRevocation::UIDRetired`] is appropriate for User /// Attributes. This parameter is not fixed, however, to allow /// the use of the [private name space]. /// /// [`ReasonForRevocation::UIDRetired`]: crate::types::ReasonForRevocation::UIDRetired /// [private name space]: crate::types::ReasonForRevocation::Private /// /// # Examples /// /// Revoke a User Attribute that is no longer valid: /// /// ```rust /// use sequoia_openpgp as openpgp; /// # use openpgp::Result; /// use openpgp::cert::prelude::*; /// use openpgp::types::ReasonForRevocation; /// /// # fn main() -> Result<()> { /// let builder = UserAttributeRevocationBuilder::new() /// .set_reason_for_revocation(ReasonForRevocation::UIDRetired, /// b"Lost the beard."); /// # Ok(()) /// # } pub fn set_reason_for_revocation(self, code: ReasonForRevocation, reason: &[u8]) -> Result<Self> { Ok(Self { builder: self.builder.set_reason_for_revocation(code, reason)? }) } /// Sets the revocation certificate's creation time. /// /// The creation time is interpreted as the time at which the User /// Attribute should be considered revoked. /// /// You'll usually want to set this explicitly and not use the /// current time. In particular, if a User Attribute is retired, /// you'll want to set this to the time when the User Attribute /// was actually retired. /// /// # Examples /// /// Create a revocation certificate for a User Attribute that was /// retired yesterday: /// /// ```rust /// use sequoia_openpgp as openpgp; /// # use openpgp::Result; /// use openpgp::cert::prelude::*; /// /// # fn main() -> Result<()> { /// # let yesterday = std::time::SystemTime::now(); /// let builder = UserAttributeRevocationBuilder::new() /// .set_signature_creation_time(yesterday); /// # Ok(()) /// # } pub fn set_signature_creation_time(self, creation_time: time::SystemTime) -> Result<Self> { Ok(Self { builder: self.builder.set_signature_creation_time(creation_time)? }) } /// Adds a notation to the revocation certificate. /// /// Unlike the [`UserAttributeRevocationBuilder::set_notation`] method, this function /// does not first remove any existing notation with the specified name. /// /// See [`SignatureBuilder::add_notation`] for further documentation. /// /// [`SignatureBuilder::add_notation`]: crate::packet::signature::SignatureBuilder::add_notation() /// /// # Examples /// /// ```rust /// use sequoia_openpgp as openpgp; /// # use openpgp::Result; /// use openpgp::cert::prelude::*; /// use openpgp::packet::signature::subpacket::NotationDataFlags; /// /// # fn main() -> Result<()> { /// let builder = CertRevocationBuilder::new().add_notation( /// "revocation-policy@example.org", /// "https://policy.example.org/cert-revocation-policy", /// NotationDataFlags::empty().set_human_readable(), /// false, /// ); /// # Ok(()) /// # } pub fn add_notation<N, V, F>(self, name: N, value: V, flags: F, critical: bool) -> Result<Self> where N: AsRef<str>, V: AsRef<[u8]>, F: Into<Option<NotationDataFlags>>, { Ok(Self { builder: self.builder.add_notation(name, value, flags, critical)? }) } /// Sets a notation to the revocation certificate. /// /// Unlike the [`UserAttributeRevocationBuilder::add_notation`] method, this function /// first removes any existing notation with the specified name. /// /// See [`SignatureBuilder::set_notation`] for further documentation. /// /// [`SignatureBuilder::set_notation`]: crate::packet::signature::SignatureBuilder::set_notation() /// /// # Examples /// /// ```rust /// use sequoia_openpgp as openpgp; /// # use openpgp::Result; /// use openpgp::cert::prelude::*; /// use openpgp::packet::signature::subpacket::NotationDataFlags; /// /// # fn main() -> Result<()> { /// let builder = CertRevocationBuilder::new().set_notation( /// "revocation-policy@example.org", /// "https://policy.example.org/cert-revocation-policy", /// NotationDataFlags::empty().set_human_readable(), /// false, /// ); /// # Ok(()) /// # } pub fn set_notation<N, V, F>(self, name: N, value: V, flags: F, critical: bool) -> Result<Self> where N: AsRef<str>, V: AsRef<[u8]>, F: Into<Option<NotationDataFlags>>, { Ok(Self { builder: self.builder.set_notation(name, value, flags, critical)? }) } /// Returns a signed revocation certificate. /// /// A revocation certificate is generated for `cert` and `ua` and /// signed using `signer` with the specified hash algorithm. /// Normally, you should pass `None` to select the default hash /// algorithm. /// /// # Examples /// /// Revoke a User Attribute, because the identity is no longer /// valid: /// /// ```rust /// # use openpgp::packet::user_attribute::Subpacket; /// use sequoia_openpgp as openpgp; /// # use openpgp::Result; /// use openpgp::cert::prelude::*; /// # use openpgp::packet::UserAttribute; /// use openpgp::policy::StandardPolicy; /// use openpgp::types::ReasonForRevocation; /// # use openpgp::types::RevocationStatus; /// # use openpgp::types::SignatureType; /// /// # fn main() -> Result<()> { /// let p = &StandardPolicy::new(); /// /// # // Create some user attribute. Doctests do not pass cfg(test), /// # // so UserAttribute::arbitrary is not available /// # let sp = Subpacket::Unknown(7, vec![7; 7].into_boxed_slice()); /// # let user_attribute = UserAttribute::new(&[sp])?; /// # /// # let (cert, _) = CertBuilder::new() /// # .add_user_attribute(user_attribute) /// # .generate()?; /// // Create and sign a revocation certificate. /// let mut signer = cert.primary_key().key().clone() /// .parts_into_secret()?.into_keypair()?; /// let ua = cert.user_attributes().nth(0).unwrap(); /// let sig = UserAttributeRevocationBuilder::new() /// .set_reason_for_revocation(ReasonForRevocation::UIDRetired, /// b"Lost the beard.")? /// .build(&mut signer, &cert, ua.user_attribute(), None)?; /// /// # assert_eq!(sig.typ(), SignatureType::CertificationRevocation); /// # /// # // Merge it into the certificate. /// # let cert = cert.insert_packets(sig.clone())?.0; /// # /// # // Now it's revoked. /// # assert_eq!(RevocationStatus::Revoked(vec![&sig]), /// # cert.user_attributes().nth(0).unwrap().revocation_status(p, None)); /// # Ok(()) /// # } pub fn build<H>(mut self, signer: &mut dyn Signer, cert: &Cert, ua: &UserAttribute, hash_algo: H) -> Result<Signature> where H: Into<Option<HashAlgorithm>> { self.builder = self.builder .set_hash_algo(hash_algo.into().unwrap_or(HashAlgorithm::SHA512)); ua.bind(signer, cert, self.builder) } } impl TryFrom<signature::SignatureBuilder> for UserAttributeRevocationBuilder { type Error = anyhow::Error; fn try_from(builder: signature::SignatureBuilder) -> Result<Self> { if builder.typ() != SignatureType::CertificationRevocation { return Err( crate::Error::InvalidArgument( format!("Expected signature type to be CertificationRevocation but got {}", builder.typ())).into()); } Ok(Self { builder }) } } #[cfg(test)] mod tests { #[test] fn try_into_cert_revocation_builder_success() -> crate::Result<()> { use std::convert::TryInto; use crate as openpgp; use openpgp::cert::prelude::*; use openpgp::packet::signature::SignatureBuilder; use openpgp::cert::CertRevocationBuilder; use openpgp::types::SignatureType; let (cert, _) = CertBuilder::new() .generate()?; // Create and sign a revocation certificate. let mut signer = cert.primary_key().key().clone() .parts_into_secret()?.into_keypair()?; let builder = SignatureBuilder::new(SignatureType::KeyRevocation); let revocation_builder: CertRevocationBuilder = builder.try_into()?; let sig = revocation_builder.build(&mut signer, &cert, None)?; assert_eq!(sig.typ(), SignatureType::KeyRevocation); Ok(()) } #[test] fn try_into_cert_revocation_builder_failure() -> crate::Result<()> { use std::convert::TryInto; use crate as openpgp; use openpgp::packet::signature::SignatureBuilder; use openpgp::cert::CertRevocationBuilder; use openpgp::types::SignatureType; let builder = SignatureBuilder::new(SignatureType::Binary); let result: openpgp::Result<CertRevocationBuilder> = builder.try_into(); assert!(result.is_err()); Ok(()) } #[test] fn try_into_subkey_revocation_builder_success() -> crate::Result<()> { use std::convert::TryInto; use crate as openpgp; use openpgp::cert::prelude::*; use openpgp::packet::signature::SignatureBuilder; use openpgp::cert::SubkeyRevocationBuilder; use openpgp::types::SignatureType; let (cert, _) = CertBuilder::new() .add_transport_encryption_subkey() .generate()?; // Create and sign a revocation certificate. let mut signer = cert.primary_key().key().clone() .parts_into_secret()?.into_keypair()?; let subkey = cert.keys().subkeys().nth(0).unwrap(); let builder = SignatureBuilder::new(SignatureType::SubkeyRevocation); let revocation_builder: SubkeyRevocationBuilder = builder.try_into()?; let sig = revocation_builder.build(&mut signer, &cert, subkey.key(), None)?; assert_eq!(sig.typ(), SignatureType::SubkeyRevocation); Ok(()) } #[test] fn try_into_subkey_revocation_builder_failure() -> crate::Result<()> { use std::convert::TryInto; use crate as openpgp; use openpgp::packet::signature::SignatureBuilder; use openpgp::cert::SubkeyRevocationBuilder; use openpgp::types::SignatureType; let builder = SignatureBuilder::new(SignatureType::Binary); let result: openpgp::Result<SubkeyRevocationBuilder> = builder.try_into(); assert!(result.is_err()); Ok(()) } #[test] fn try_into_userid_revocation_builder_success() -> crate::Result<()> { use std::convert::TryInto; use crate as openpgp; use openpgp::cert::prelude::*; use openpgp::packet::signature::SignatureBuilder; use openpgp::cert::UserIDRevocationBuilder; use openpgp::types::SignatureType; let (cert, _) = CertBuilder::new() .add_userid("test@example.com") .generate()?; // Create and sign a revocation certificate. let mut signer = cert.primary_key().key().clone() .parts_into_secret()?.into_keypair()?; let user_id = cert.userids().next().unwrap().userid(); let builder = SignatureBuilder::new(SignatureType::CertificationRevocation); let revocation_builder: UserIDRevocationBuilder = builder.try_into()?; let sig = revocation_builder.build(&mut signer, &cert, user_id, None)?; assert_eq!(sig.typ(), SignatureType::CertificationRevocation); Ok(()) } #[test] fn try_into_userid_revocation_builder_failure() -> crate::Result<()> { use std::convert::TryInto; use crate as openpgp; use openpgp::packet::signature::SignatureBuilder; use openpgp::cert::UserIDRevocationBuilder; use openpgp::types::SignatureType; let builder = SignatureBuilder::new(SignatureType::Binary); let result: openpgp::Result<UserIDRevocationBuilder> = builder.try_into(); assert!(result.is_err()); Ok(()) } #[test] fn try_into_userattribute_revocation_builder_success() -> crate::Result<()> { use std::convert::TryInto; use crate as openpgp; use openpgp::cert::prelude::*; use openpgp::packet::prelude::*; use openpgp::packet::signature::SignatureBuilder; use openpgp::packet::user_attribute::Subpacket; use openpgp::cert::UserAttributeRevocationBuilder; use openpgp::types::SignatureType; let sp = Subpacket::Unknown(7, vec![7; 7].into_boxed_slice()); let user_attribute = UserAttribute::new(&[sp])?; let (cert, _) = CertBuilder::new() .add_user_attribute(user_attribute) .generate()?; // Create and sign a revocation certificate. let mut signer = cert.primary_key().key().clone() .parts_into_secret()?.into_keypair()?; let user_attribute = cert.user_attributes().next().unwrap().user_attribute(); let builder = SignatureBuilder::new(SignatureType::CertificationRevocation); let revocation_builder: UserAttributeRevocationBuilder = builder.try_into()?; let sig = revocation_builder.build(&mut signer, &cert, user_attribute, None)?; assert_eq!(sig.typ(), SignatureType::CertificationRevocation); Ok(()) } #[test] fn try_into_userattribute_revocation_builder_failure() -> crate::Result<()> { use std::convert::TryInto; use crate as openpgp; use openpgp::packet::signature::SignatureBuilder; use openpgp::cert::UserAttributeRevocationBuilder; use openpgp::types::SignatureType; let builder = SignatureBuilder::new(SignatureType::Binary); let result: openpgp::Result<UserAttributeRevocationBuilder> = builder.try_into(); assert!(result.is_err()); Ok(()) } } �����sequoia-openpgp-2.0.0/src/cert.rs�������������������������������������������������������������������0000644�0000000�0000000�00001112327�10461020230�0015040�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������//! Certificates and related data structures. //! //! An OpenPGP certificate, often called a `PGP key` or just a `key,` //! is a collection of keys, identity information, and certifications //! about those keys and identities. //! //! The foundation of an OpenPGP certificate is the so-called primary //! key. A primary key has three essential functions. First, the //! primary key is used to derive a universally unique identifier //! (UUID) for the certificate, the certificate's so-called //! fingerprint. Second, the primary key is used to certify //! assertions that the certificate holder makes about their //! certificate. For instance, to associate a subkey or a User ID //! with a certificate, the certificate holder uses the primary key to //! create a self signature called a binding signature. This binding //! signature is distributed with the certificate. It allows anyone //! who has the certificate to verify that the certificate holder //! (identified by the primary key) really intended for the subkey to //! be associated with the certificate. Finally, the primary key can //! be used to make assertions about other certificates. For //! instance, Alice can make a so-called third-party certification //! that attests that she is convinced that `Bob` (as described by //! some User ID) controls a particular certificate. These //! third-party certifications are typically distributed alongside the //! signee's certificate, and are used by trust models like the Web of //! Trust to authenticate certificates. //! //! # Common Operations //! //! - *Generating a certificate*: See the [`CertBuilder`] module. //! - *Parsing a certificate*: See the [`Parser` implementation] for `Cert`. //! - *Parsing a keyring*: See the [`CertParser`] module. //! - *Serializing a certificate*: See the [`Serialize` //! implementation] for `Cert`, and the [`Cert::as_tsk`] method to //! also include any secret key material. //! - *Using a certificate*: See the [`Cert`] and [`ValidCert`] data structures. //! - *Revoking a certificate*: See the [`CertRevocationBuilder`] data structure. //! - *Decrypt or encrypt secret keys*: See [`packet::Key::encrypt_secret`]'s example. //! - *Merging packets*: See the [`Cert::insert_packets`] method. //! - *Merging certificates*: See the [`Cert::merge_public`] method. //! - *Creating third-party certifications*: See the [`UserID::certify`] //! and [`UserAttribute::certify`] methods. //! - *Using User IDs and User Attributes*: See the [`ComponentAmalgamation`] module. //! - *Using keys*: See the [`KeyAmalgamation`] module. //! - *Updating a binding signature*: See the [`UserID::bind`], //! [`UserAttribute::bind`], and [`Key::bind`] methods. //! - *Checking third-party signatures*: See the //! [`Signature::verify_direct_key`], //! [`Signature::verify_userid_binding`], and //! [`Signature::verify_user_attribute_binding`] methods. //! - *Checking third-party revocations*: See the //! [`ValidCert::revocation_keys`], //! [`ValidAmalgamation::revocation_keys`], //! [`Signature::verify_primary_key_revocation`], //! [`Signature::verify_userid_revocation`], //! [`Signature::verify_user_attribute_revocation`] methods. //! //! # Data Structures //! //! ## `Cert` //! //! The [`Cert`] data structure closely mirrors the transferable //! public key (`TPK`) data structure described in [Section 10.1 of //! RFC 9580]: it contains the certificate's `Component`s and their //! associated signatures. //! //! ## `Component`s //! //! In Sequoia, we refer to `User ID`s, `User Attribute`s, and `Key`s //! as `Component`s. To accommodate unsupported components (e.g., //! deprecated v3 keys) and unknown components (e.g., the //! yet-to-be-defined `Xyzzy Property`), we also define an `Unknown` //! component. //! //! ## `ComponentBundle`s //! //! We call a Component and any associated signatures a //! [`ComponentBundle`]. There are four types of associated //! signatures: self signatures, third-party signatures, self //! revocations, and third-party revocations. //! //! Although some information about a given `Component` is stored in //! the `Component` itself, most of the information is stored on the //! associated signatures. For instance, a key's creation time is //! stored in the key packet, but the key's capabilities (e.g., //! whether it can be used for encryption or signing), and its expiry //! are stored in the associated self signatures. Thus, to use a //! component, we usually need its corresponding self signature. //! //! When a certificate is parsed, Sequoia ensures that all components //! (except the primary key) have at least one valid self signature. //! However, when using a component, it is still necessary to find the //! right self signature. And, unfortunately, finding the //! self signature for the primary `Key` is non-trivial: that's the //! primary User ID's self signature. Another complication is that if //! the self signature doesn't contain the required information, then //! the implementation should look for the information on a direct key //! signature. Thus, a `ComponentBundle` doesn't contain all of the //! information that is needed to use a component. //! //! ## `ComponentAmalgamation`s //! //! To workaround this lack of context, we introduce another data //! structure called a [`ComponentAmalgamation`]. A //! `ComponentAmalgamation` references a `ComponentBundle` and its //! associated `Cert`. Unfortunately, we can't include a reference to //! the `Cert` in the `ComponentBundle`, because the `Cert` owns the //! `ComponentBundle`, and that would create a self-referential data //! structure, which is currently not supported in Rust. //! //! [Section 10.1 of RFC 9580]: https://www.rfc-editor.org/rfc/rfc9580.html#section-10.1 //! [`ComponentBundle`]: bundle::ComponentBundle //! [`ComponentAmalgamation`]: amalgamation::ComponentAmalgamation //! [`Parser` implementation]: struct.Cert.html#impl-Parse%3C%27a%2C%20Cert%3E //! [`Serialize` implementation]: struct.Cert.html#impl-Serialize //! [`UserID::certify`]: crate::packet::UserID::certify() //! [`UserAttribute::certify`]: crate::packet::user_attribute::UserAttribute::certify() //! [`KeyAmalgamation`]: amalgamation::key //! [`UserID::bind`]: crate::packet::UserID::bind() //! [`UserAttribute::bind`]: crate::packet::user_attribute::UserAttribute::bind() //! [`Key::bind`]: crate::packet::Key::bind() //! [`Signature::verify_direct_key`]: crate::packet::Signature::verify_direct_key() //! [`Signature::verify_userid_binding`]: crate::packet::Signature::verify_userid_binding() //! [`Signature::verify_user_attribute_binding`]: crate::packet::Signature::verify_user_attribute_binding() //! [`ValidAmalgamation::revocation_keys`]: amalgamation::ValidAmalgamation::revocation_keys //! [`Signature::verify_primary_key_revocation`]: crate::packet::Signature::verify_primary_key_revocation() //! [`Signature::verify_userid_revocation`]: crate::packet::Signature::verify_userid_revocation() //! [`Signature::verify_user_attribute_revocation`]: crate::packet::Signature::verify_user_attribute_revocation() use std::collections::btree_map::BTreeMap; use std::collections::btree_map::Entry; use std::collections::hash_map::DefaultHasher; use std::cmp::Ordering; use std::convert::TryFrom; use std::hash::Hasher; use std::mem; use std::fmt; use std::time; use buffered_reader::BufferedReader; use crate::{ crypto::{ Signer, }, Error, Result, SignatureType, packet, packet::Signature, packet::Key, packet::key, packet::Tag, packet::UserID, packet::UserAttribute, packet::Unknown, Packet, PacketPile, seal, KeyID, Fingerprint, KeyHandle, policy::Policy, }; use crate::parse::{Cookie, Parse, PacketParserResult, PacketParser}; use crate::types::{ AEADAlgorithm, CompressionAlgorithm, Features, HashAlgorithm, KeyServerPreferences, ReasonForRevocation, RevocationKey, RevocationStatus, SymmetricAlgorithm, }; pub mod amalgamation; mod builder; mod bindings; pub mod bundle; use bundle::{ ComponentBundles, UserIDBundles, UserAttributeBundles, SubkeyBundles, UnknownBundles, }; mod lazysigs; mod parser; pub mod raw; mod revoke; pub use self::builder::{CertBuilder, CipherSuite, KeyBuilder, SubkeyBuilder}; pub use parser::{ CertParser, }; pub(crate) use parser::{ CertValidator, CertValidity, KeyringValidator, KeyringValidity, }; pub use revoke::{ SubkeyRevocationBuilder, CertRevocationBuilder, UserAttributeRevocationBuilder, UserIDRevocationBuilder, }; pub mod prelude; use prelude::*; const TRACE : bool = false; // Helper functions. /// Compare the creation time of two signatures. Order them so that /// the more recent signature is first. fn canonical_signature_order(a: Option<time::SystemTime>, b: Option<time::SystemTime>) -> Ordering { // Note: None < Some, so the normal ordering is: // // None, Some(old), Some(new) // // Reversing the ordering puts the signatures without a creation // time at the end, which is where they belong. a.cmp(&b).reverse() } /// Compares two signatures by creation time using the MPIs as tie-breaker. /// /// Useful to sort signatures so that the most recent ones are at the /// front. fn sig_cmp(a: &Signature, b: &Signature) -> Ordering { match canonical_signature_order(a.signature_creation_time(), b.signature_creation_time()) { Ordering::Equal => a.mpis().cmp(b.mpis()), r => r } } impl fmt::Display for Cert { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{}", self.fingerprint()) } } /// Returns the certificate holder's preferences. /// /// OpenPGP provides a mechanism for a certificate holder to transmit /// information about communication preferences, and key management to /// communication partners in an asynchronous manner. This /// information is attached to the certificate itself. Specifically, /// the different types of information are stored as signature /// subpackets in the User IDs' self signatures, and in the /// certificate's direct key signature. /// /// OpenPGP allows the certificate holder to specify different /// information depending on the way the certificate is addressed. /// When addressed by User ID, that User ID's self signature is first /// checked for the subpacket in question. If the subpacket is not /// present or the certificate is addressed is some other way, for /// instance, by its fingerprint, then the primary User ID's /// self signature is checked. If the subpacket is also not there, /// then the direct key signature is checked. This policy and its /// justification are described in [Section 5.2.3.10 of RFC 9580]. /// /// Note: User IDs may be stripped. For instance, the [WKD] standard /// requires User IDs that are unrelated to the WKD's domain be /// stripped from the certificate prior to publication. As such, any /// User ID may be considered the primary User ID. Consequently, if /// any User ID includes a particular subpacket, then all User IDs /// should include it. Furthermore, [Section 10.1.1 of RFC 9580] /// allows certificates without any User ID packets. To handle this /// case, certificates should also create a direct key signature with /// this information. /// /// [Section 5.2.3.10 of RFC 9580]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.10 /// [Section 10.1.1 of RFC 9580]: https://www.rfc-editor.org/rfc/rfc9580.html#section-10.1.1 /// [WKD]: https://tools.ietf.org/html/draft-koch-openpgp-webkey-service-09#section-5 /// /// # Algorithm Preferences /// /// Algorithms are ordered with the most preferred algorithm first. /// If an algorithm is not listed, then the /// implementation should assume that it is not supported by the /// certificate holder's software (see e.g. [Section 5.2.3.15 of RFC /// 9580]). /// /// [Section 5.2.3.15 of RFC 9580]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.15 /// /// # Examples /// /// ``` /// use sequoia_openpgp as openpgp; /// # use openpgp::Result; /// use openpgp::cert::prelude::*; /// use sequoia_openpgp::policy::StandardPolicy; /// /// # fn main() -> Result<()> { /// let p = &StandardPolicy::new(); /// /// # let (cert, _) = /// # CertBuilder::general_purpose(Some("alice@example.org")) /// # .generate()?; /// match cert.with_policy(p, None)?.primary_userid()?.preferred_symmetric_algorithms() { /// Some(algos) => { /// println!("Certificate Holder's preferred symmetric algorithms:"); /// for (i, algo) in algos.iter().enumerate() { /// println!("{}. {}", i, algo); /// } /// } /// None => { /// println!("Certificate Holder did not specify any preferred \ /// symmetric algorithms, or the subpacket is missing."); /// } /// } /// # Ok(()) } /// ``` /// /// # Sealed trait /// /// This trait is [sealed] and cannot be implemented for types outside this crate. /// Therefore it can be extended in a non-breaking way. /// If you want to implement the trait inside the crate /// you also need to implement the `seal::Sealed` marker trait. /// /// [sealed]: https://rust-lang.github.io/api-guidelines/future-proofing.html#sealed-traits-protect-against-downstream-implementations-c-sealed pub trait Preferences<'a>: seal::Sealed { /// Returns the supported symmetric algorithms ordered by /// preference. /// /// The algorithms are ordered according to the certificate /// holder's preference. fn preferred_symmetric_algorithms(&self) -> Option<&'a [SymmetricAlgorithm]>; /// Returns the supported hash algorithms ordered by preference. /// /// The algorithms are ordered according to the certificate /// holder's preference. fn preferred_hash_algorithms(&self) -> Option<&'a [HashAlgorithm]>; /// Returns the supported compression algorithms ordered by /// preference. /// /// The algorithms are ordered according to the certificate /// holder's preference. fn preferred_compression_algorithms(&self) -> Option<&'a [CompressionAlgorithm]>; /// Returns the supported AEAD ciphersuites ordered by preference. /// /// The algorithms are ordered according to the certificate holder's /// preference. fn preferred_aead_ciphersuites( &self) -> Option<&'a [(SymmetricAlgorithm, AEADAlgorithm)]>; /// Returns the certificate holder's keyserver preferences. fn key_server_preferences(&self) -> Option<KeyServerPreferences>; /// Returns the certificate holder's preferred keyserver for /// updates. fn preferred_key_server(&self) -> Option<&'a [u8]>; /// Returns the certificate holder's feature set. fn features(&self) -> Option<Features>; /// Returns the URI of a document describing the policy /// the certificate was issued under. fn policy_uri(&self) -> Option<&'a [u8]>; } /// A collection of components and their associated signatures. /// /// The `Cert` data structure mirrors the [TPK and TSK data /// structures] defined in RFC 9580. Specifically, it contains /// components ([`Key`]s, [`UserID`]s, and [`UserAttribute`]s), their /// associated self signatures, self revocations, third-party /// signatures, and third-party revocations, as well as useful methods. /// /// [TPK and TSK data structures]: https://www.rfc-editor.org/rfc/rfc9580.html#section-10 /// [`Key`]: crate::packet::Key /// [`UserID`]: crate::packet::UserID /// [`UserAttribute`]: crate::packet::user_attribute::UserAttribute /// /// `Cert`s are canonicalized in the sense that their `Component`s are /// deduplicated, and their signatures and revocations are /// deduplicated and checked for validity. The canonicalization /// routine does *not* throw away components that have no self /// signatures. These are returned as usual by, e.g., /// [`Cert::userids`]. /// /// [`Cert::userids`]: Cert::userids() /// /// Keys are deduplicated by comparing their public bits using /// [`Key::public_cmp`]. If two keys are considered equal, and only /// one of them has secret key material, the key with the secret key /// material is preferred. If both keys have secret material, then /// one of them is chosen in a deterministic, but undefined manner, /// which is subject to change. ***Note***: the secret key material /// is not integrity checked. Hence when updating a certificate with /// secret key material, it is essential to first strip the secret key /// material from copies that came from an untrusted source. /// /// [`Key::public_cmp`]: crate::packet::Key::public_cmp() /// /// Signatures are deduplicated using [their `Eq` implementation], /// which compares the data that is hashed and the MPIs. That is, it /// does not compare [the unhashed data], the digest prefix and the /// unhashed subpacket area. If two signatures are considered equal, /// but have different unhashed data, the unhashed data are merged in /// a deterministic, but undefined manner, which is subject to change. /// This policy prevents an attacker from flooding a certificate with /// valid signatures that only differ in their unhashed data. /// /// [their `Eq` implementation]: crate::packet::Signature#a-note-on-equality /// [the unhashed data]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3 /// /// Self signatures and self revocations are checked for validity by /// making sure that the signature is *mathematically* correct. At /// this point, the signature is *not* checked against a [`Policy`]. /// /// Third-party signatures and revocations are checked for validity by /// making sure the computed digest matches the [digest prefix] stored /// in the signature packet. This is *not* an integrity check and is /// easily spoofed. Unfortunately, at the time of canonicalization, /// the actual signatures cannot be checked, because the public keys /// are not available. If you rely on these signatures, it is up to /// you to check their validity by using an appropriate signature /// verification method, e.g., [`Signature::verify_userid_binding`] /// or [`Signature::verify_userid_revocation`]. /// /// [`Policy`]: crate::policy::Policy /// [digest prefix]: crate::packet::signature::Signature4::digest_prefix() /// [`Signature::verify_userid_binding`]: crate::packet::Signature::verify_userid_binding() /// [`Signature::verify_userid_revocation`]: crate::packet::Signature::verify_userid_revocation() /// /// If a signature or a revocation is not valid, /// we check to see whether it is simply out of place (i.e., belongs /// to a different component) and, if so, we reorder it. If not, it /// is added to a list of bad signatures. These can be retrieved /// using [`Cert::bad_signatures`]. /// /// [`Cert::bad_signatures`]: Cert::bad_signatures() /// /// Signatures and revocations are sorted so that the newest signature /// comes first. Components are sorted, but in an undefined manner /// (i.e., when parsing the same certificate multiple times, the /// components will be in the same order, but we reserve the right to /// change the sort function between versions). /// /// # Secret Keys /// /// Any key in a certificate may include secret key material. To /// protect secret key material from being leaked, secret keys are not /// written out when a `Cert` is serialized. To also serialize secret /// key material, you need to serialize the object returned by /// [`Cert::as_tsk()`]. /// /// /// Secret key material may be protected with a password. In such /// cases, it needs to be decrypted before it can be used to decrypt /// data or generate a signature. Refer to [`Key::decrypt_secret`] /// for details. /// /// [`Key::decrypt_secret`]: crate::packet::Key::decrypt_secret() /// /// # Filtering Certificates /// /// Component-wise filtering of userids, user attributes, and subkeys /// can be done with [`Cert::retain_userids`], /// [`Cert::retain_user_attributes`], and [`Cert::retain_subkeys`]. /// /// [`Cert::retain_userids`]: Cert::retain_userids() /// [`Cert::retain_user_attributes`]: Cert::retain_user_attributes() /// [`Cert::retain_subkeys`]: Cert::retain_subkeys() /// /// If you need even more control, iterate over all components, clone /// what you want to keep, and then reassemble the certificate. The /// following example simply copies all the packets, and can be /// adapted to suit your policy: /// /// ```rust /// # use sequoia_openpgp as openpgp; /// # use openpgp::Result; /// # use openpgp::parse::{Parse, PacketParserResult, PacketParser}; /// use std::convert::TryFrom; /// use openpgp::cert::prelude::*; /// /// # fn main() -> Result<()> { /// fn identity_filter(cert: &Cert) -> Result<Cert> { /// // Iterate over all the Cert components, pushing packets we /// // want to keep into the accumulator. /// let mut acc = Vec::new(); /// /// // Primary key and related signatures. /// let c = cert.primary_key(); /// acc.push(c.key().clone().into()); /// for s in c.self_signatures() { acc.push(s.clone().into()) } /// for s in c.certifications() { acc.push(s.clone().into()) } /// for s in c.self_revocations() { acc.push(s.clone().into()) } /// for s in c.other_revocations() { acc.push(s.clone().into()) } /// /// // UserIDs and related signatures. /// for c in cert.userids() { /// acc.push(c.userid().clone().into()); /// for s in c.self_signatures() { acc.push(s.clone().into()) } /// for s in c.approvals() { acc.push(s.clone().into()) } /// for s in c.certifications() { acc.push(s.clone().into()) } /// for s in c.self_revocations() { acc.push(s.clone().into()) } /// for s in c.other_revocations() { acc.push(s.clone().into()) } /// } /// /// // UserAttributes and related signatures. /// for c in cert.user_attributes() { /// acc.push(c.user_attribute().clone().into()); /// for s in c.self_signatures() { acc.push(s.clone().into()) } /// for s in c.approvals() { acc.push(s.clone().into()) } /// for s in c.certifications() { acc.push(s.clone().into()) } /// for s in c.self_revocations() { acc.push(s.clone().into()) } /// for s in c.other_revocations() { acc.push(s.clone().into()) } /// } /// /// // Subkeys and related signatures. /// for c in cert.keys().subkeys() { /// acc.push(c.key().clone().into()); /// for s in c.self_signatures() { acc.push(s.clone().into()) } /// for s in c.certifications() { acc.push(s.clone().into()) } /// for s in c.self_revocations() { acc.push(s.clone().into()) } /// for s in c.other_revocations() { acc.push(s.clone().into()) } /// } /// /// // Unknown components and related signatures. /// for c in cert.unknowns() { /// acc.push(c.unknown().clone().into()); /// for s in c.self_signatures() { acc.push(s.clone().into()) } /// for s in c.certifications() { acc.push(s.clone().into()) } /// for s in c.self_revocations() { acc.push(s.clone().into()) } /// for s in c.other_revocations() { acc.push(s.clone().into()) } /// } /// /// // Any signatures that we could not associate with a component. /// for s in cert.bad_signatures() { acc.push(s.clone().into()) } /// /// // Finally, parse into Cert. /// Cert::try_from(acc) /// } /// /// let (cert, _) = /// CertBuilder::general_purpose(Some("alice@example.org")) /// .generate()?; /// assert_eq!(cert, identity_filter(&cert)?); /// # Ok(()) /// # } /// ``` /// /// # A note on equality /// /// We define equality on `Cert` as the equality of the serialized /// form as defined by RFC 9580. That is, two certs are considered /// equal if and only if their serialized forms are equal, modulo the /// OpenPGP packet framing (see [`Packet`#a-note-on-equality]). /// /// Because secret key material is not emitted when a `Cert` is /// serialized, two certs are considered equal even if only one of /// them has secret key material. To take secret key material into /// account, compare the [`TSK`s](crate::serialize::TSK) instead: /// /// ```rust /// # fn main() -> sequoia_openpgp::Result<()> { /// # use sequoia_openpgp as openpgp; /// use openpgp::cert::prelude::*; /// /// // Generate a cert with secrets. /// let (cert_with_secrets, _) = /// CertBuilder::general_purpose(Some("alice@example.org")) /// .generate()?; /// /// // Derive a cert without secrets. /// let cert_without_secrets = /// cert_with_secrets.clone().strip_secret_key_material(); /// /// // Both are considered equal. /// assert!(cert_with_secrets == cert_without_secrets); /// /// // But not if we compare their TSKs: /// assert!(cert_with_secrets.as_tsk() != cert_without_secrets.as_tsk()); /// # Ok(()) } /// ``` /// /// # Examples /// /// Parse a certificate: /// /// ```rust /// use std::convert::TryFrom; /// use sequoia_openpgp as openpgp; /// # use openpgp::Result; /// # use openpgp::parse::{Parse, PacketParserResult, PacketParser}; /// use openpgp::Cert; /// /// # fn main() -> Result<()> { /// # let ppr = PacketParser::from_bytes(&b""[..])?; /// match Cert::try_from(ppr) { /// Ok(cert) => { /// println!("Key: {}", cert.fingerprint()); /// for uid in cert.userids() { /// println!("User ID: {}", uid.userid()); /// } /// } /// Err(err) => { /// eprintln!("Error parsing Cert: {}", err); /// } /// } /// /// # Ok(()) /// # } /// ``` #[derive(Debug, Clone, PartialEq)] pub struct Cert { primary: PrimaryKeyBundle<key::PublicParts>, userids: UserIDBundles, user_attributes: UserAttributeBundles, subkeys: SubkeyBundles<key::PublicParts>, // Unknown components, e.g., some UserAttribute++ packet from the // future. unknowns: UnknownBundles, // Signatures that we couldn't find a place for. bad: Vec<packet::Signature>, } assert_send_and_sync!(Cert); impl std::str::FromStr for Cert { type Err = anyhow::Error; /// Parses and returns a certificate. /// /// `s` must return an OpenPGP-encoded certificate. /// /// If `s` contains multiple certificates, this returns an error. /// Use [`CertParser`] if you want to parse a keyring. fn from_str(s: &str) -> std::result::Result<Self, Self::Err> { Self::from_bytes(s.as_bytes()) } } impl<'a> Parse<'a, Cert> for Cert { /// Parses and returns a certificate. /// /// The reader must return an OpenPGP-encoded certificate. /// /// If `reader` contains multiple certificates, this returns an /// error. Use [`CertParser`] if you want to parse a keyring. fn from_buffered_reader<R>(reader: R) -> Result<Cert> where R: BufferedReader<Cookie> + 'a, { Cert::try_from(PacketParser::from_buffered_reader(reader.into_boxed())?) } } impl Cert { /// Returns the primary key. /// /// Unlike getting the certificate's primary key using the /// [`Cert::keys`] method, this method does not erase the key's /// role. /// /// A key's secret key material may be protected with a password. /// In such cases, it needs to be decrypted before it can be used /// to decrypt data or generate a signature. Refer to /// [`Key::decrypt_secret`] for details. /// /// [`Cert::keys`]: Cert::keys() /// [`Key::decrypt_secret`]: crate::packet::Key::decrypt_secret() /// /// # Examples /// /// The first key returned by [`Cert::keys`] is the primary key, /// but its role has been erased: /// /// ``` /// # use sequoia_openpgp as openpgp; /// # use openpgp::cert::prelude::*; /// # fn main() -> openpgp::Result<()> { /// # let (cert, _) = CertBuilder::new() /// # .add_userid("Alice") /// # .add_signing_subkey() /// # .add_transport_encryption_subkey() /// # .generate()?; /// assert_eq!(cert.primary_key().key().role_as_unspecified(), /// cert.keys().nth(0).unwrap().key()); /// # Ok(()) /// # } /// ``` pub fn primary_key(&self) -> PrimaryKeyAmalgamation<key::PublicParts> { PrimaryKeyAmalgamation::new(self) } /// Returns the certificate's revocation status. /// /// Normally, methods that take a policy and a reference time are /// only provided by [`ValidCert`]. This method is provided here /// because there are two revocation criteria, and one of them is /// independent of the reference time. That is, even if it is not /// possible to turn a `Cert` into a `ValidCert` at time `t`, it /// may still be considered revoked at time `t`. /// /// /// A certificate is considered revoked at time `t` if: /// /// - There is a valid and live revocation at time `t` that is /// newer than all valid and live self signatures at time `t`, /// or /// /// - There is a valid [hard revocation] (even if it is not live /// at time `t`, and even if there is a newer self signature). /// /// [hard revocation]: crate::types::RevocationType::Hard /// /// Note: certificates and subkeys have different revocation /// criteria from [User IDs] and [User Attributes]. /// // Pending https://github.com/rust-lang/rust/issues/85960, should be // [User IDs]: bundle::ComponentBundle<UserID>::revocation_status // [User Attributes]: bundle::ComponentBundle<UserAttribute>::revocation_status /// [User IDs]: bundle::ComponentBundle#method.revocation_status-1 /// [User Attributes]: bundle::ComponentBundle#method.revocation_status-2 /// /// # Examples /// /// ``` /// use sequoia_openpgp as openpgp; /// use openpgp::cert::prelude::*; /// use openpgp::types::RevocationStatus; /// use openpgp::policy::StandardPolicy; /// /// # fn main() -> openpgp::Result<()> { /// let p = &StandardPolicy::new(); /// /// let (cert, rev) = /// CertBuilder::general_purpose(Some("alice@example.org")) /// .generate()?; /// /// assert_eq!(cert.revocation_status(p, None), RevocationStatus::NotAsFarAsWeKnow); /// /// // Merge the revocation certificate. `cert` is now considered /// // to be revoked. /// let cert = cert.insert_packets(rev.clone())?.0; /// assert_eq!(cert.revocation_status(p, None), /// RevocationStatus::Revoked(vec![&rev.into()])); /// # Ok(()) /// # } /// ``` pub fn revocation_status<T>(&self, policy: &dyn Policy, t: T) -> RevocationStatus where T: Into<Option<time::SystemTime>> { let t = t.into(); // Both a primary key signature and the primary userid's // binding signature can override a soft revocation. Compute // the most recent one. let vkao = self.primary_key().with_policy(policy, t).ok(); let mut sig = vkao.as_ref().map(|vka| vka.binding_signature()); if let Some(direct) = vkao.as_ref() .and_then(|vka| vka.direct_key_signature().ok()) { match (direct.signature_creation_time(), sig.and_then(|s| s.signature_creation_time())) { (Some(ds), Some(bs)) if ds > bs => sig = Some(direct), _ => () } } self.primary_key().bundle()._revocation_status(policy, t, true, sig) } /// Generates a revocation certificate. /// /// This is a convenience function around /// [`CertRevocationBuilder`] to generate a revocation /// certificate. To use the revocation certificate, merge it into /// the certificate using [`Cert::insert_packets`]. /// /// /// If you want to revoke an individual component, use /// [`SubkeyRevocationBuilder`], [`UserIDRevocationBuilder`], or /// [`UserAttributeRevocationBuilder`], as appropriate. /// /// /// # Examples /// /// ```rust /// use sequoia_openpgp as openpgp; /// # use openpgp::Result; /// use openpgp::types::{ReasonForRevocation, RevocationStatus, SignatureType}; /// use openpgp::cert::prelude::*; /// use openpgp::crypto::KeyPair; /// use openpgp::parse::Parse; /// use openpgp::policy::StandardPolicy; /// /// # fn main() -> Result<()> { /// let p = &StandardPolicy::new(); /// /// let (cert, rev) = CertBuilder::new() /// .set_cipher_suite(CipherSuite::Cv25519) /// .generate()?; /// /// // A new certificate is not revoked. /// assert_eq!(cert.revocation_status(p, None), /// RevocationStatus::NotAsFarAsWeKnow); /// /// // The default revocation certificate is a generic /// // revocation. /// assert_eq!(rev.reason_for_revocation().unwrap().0, /// ReasonForRevocation::Unspecified); /// /// // Create a revocation to explain what *really* happened. /// let mut keypair = cert.primary_key() /// .key().clone().parts_into_secret()?.into_keypair()?; /// let rev = cert.revoke(&mut keypair, /// ReasonForRevocation::KeyCompromised, /// b"It was the maid :/")?; /// let cert = cert.insert_packets(rev)?.0; /// if let RevocationStatus::Revoked(revs) = cert.revocation_status(p, None) { /// assert_eq!(revs.len(), 1); /// let rev = revs[0]; /// /// assert_eq!(rev.typ(), SignatureType::KeyRevocation); /// assert_eq!(rev.reason_for_revocation(), /// Some((ReasonForRevocation::KeyCompromised, /// "It was the maid :/".as_bytes()))); /// } else { /// unreachable!() /// } /// # Ok(()) /// # } /// ``` pub fn revoke(&self, primary_signer: &mut dyn Signer, code: ReasonForRevocation, reason: &[u8]) -> Result<Signature> { CertRevocationBuilder::new() .set_reason_for_revocation(code, reason)? .build(primary_signer, self, None) } /// Sets the key to expire in delta seconds. /// /// Note: the time is relative to the key's creation time, not the /// current time! /// /// This function exists to facilitate testing, which is why it is /// not exported. #[cfg(test)] fn set_validity_period_as_of(self, policy: &dyn Policy, primary_signer: &mut dyn Signer, expiration: Option<time::Duration>, now: time::SystemTime) -> Result<Cert> { let primary = self.primary_key().with_policy(policy, now)?; let sigs = primary.set_validity_period_as_of(primary_signer, expiration, now)?; Ok(self.insert_packets(sigs)?.0) } /// Sets the certificate to expire at the specified time. /// /// If no time (`None`) is specified, then the certificate is set /// to not expire. /// /// This function creates new binding signatures that cause the /// certificate to expire at the specified time. Specifically, it /// updates the current binding signature on each of the valid, /// non-revoked User IDs, and the direct key signature, if any. /// This is necessary, because the primary User ID is first /// consulted when determining the certificate's expiration time, /// and certificates can be distributed with a possibly empty /// subset of User IDs. /// /// A policy is needed, because the expiration is updated by /// updating the current binding signatures. /// /// # Examples /// /// ```rust /// use std::time; /// use sequoia_openpgp as openpgp; /// # use openpgp::Result; /// use openpgp::cert::prelude::*; /// use openpgp::crypto::KeyPair; /// use openpgp::policy::StandardPolicy; /// /// # fn main() -> Result<()> { /// let p = &StandardPolicy::new(); /// /// # let t0 = time::SystemTime::now() - time::Duration::from_secs(1); /// # let (cert, _) = CertBuilder::new() /// # .set_cipher_suite(CipherSuite::Cv25519) /// # .set_creation_time(t0) /// # .generate()?; /// // The certificate is alive (not expired). /// assert!(cert.with_policy(p, None)?.alive().is_ok()); /// /// // Make cert expire now. /// let mut keypair = cert.primary_key() /// .key().clone().parts_into_secret()?.into_keypair()?; /// let sigs = cert.set_expiration_time(p, None, &mut keypair, /// Some(time::SystemTime::now()))?; /// /// let cert = cert.insert_packets(sigs)?.0; /// assert!(cert.with_policy(p, None)?.alive().is_err()); /// # Ok(()) /// # } /// ``` pub fn set_expiration_time<T>(&self, policy: &dyn Policy, t: T, primary_signer: &mut dyn Signer, expiration: Option<time::SystemTime>) -> Result<Vec<Signature>> where T: Into<Option<time::SystemTime>>, { let primary = self.primary_key().with_policy(policy, t.into())?; primary.set_expiration_time(primary_signer, expiration) } /// Returns the primary User ID at the reference time, if any. fn primary_userid_relaxed<'a, T>(&'a self, policy: &'a dyn Policy, t: T, valid_cert: bool) -> Result<ValidUserIDAmalgamation<'a>> where T: Into<Option<std::time::SystemTime>> { let t = t.into().unwrap_or_else(crate::now); ValidComponentAmalgamation::primary(self, self.userids.iter(), policy, t, valid_cert) } /// Returns an iterator over the certificate's User IDs. /// /// **Note:** This returns all User IDs, even those without a /// binding signature. This is not what you want, unless you are /// doing a low-level inspection of the certificate. Use /// [`ValidCert::userids`] instead. (You turn a `Cert` into a /// [`ValidCert`] by using [`Cert::with_policy`].) /// /// # Examples /// /// ``` /// # use sequoia_openpgp as openpgp; /// # use openpgp::cert::prelude::*; /// # use openpgp::packet::prelude::*; /// # /// # fn main() -> openpgp::Result<()> { /// # let (cert, rev) = /// # CertBuilder::general_purpose(Some("alice@example.org")) /// # .generate()?; /// println!("{}'s User IDs:", cert.fingerprint()); /// for ua in cert.userids() { /// println!(" {}", String::from_utf8_lossy(ua.userid().value())); /// } /// # // Add a User ID without a binding signature and make sure /// # // it is still returned. /// # let userid = UserID::from("alice@example.net"); /// # let cert = cert.insert_packets(userid)?.0; /// # assert_eq!(cert.userids().count(), 2); /// # Ok(()) /// # } /// ``` pub fn userids(&self) -> UserIDAmalgamationIter { ComponentAmalgamationIter::new(self, self.userids.iter()) } /// Returns an iterator over the certificate's User Attributes. /// /// **Note:** This returns all User Attributes, even those without /// a binding signature. This is not what you want, unless you /// are doing a low-level inspection of the certificate. Use /// [`ValidCert::user_attributes`] instead. (You turn a `Cert` /// into a [`ValidCert`] by using [`Cert::with_policy`].) /// /// # Examples /// /// ``` /// # use sequoia_openpgp as openpgp; /// # use openpgp::cert::prelude::*; /// # /// # fn main() -> openpgp::Result<()> { /// # let (cert, rev) = /// # CertBuilder::general_purpose(Some("alice@example.org")) /// # .generate()?; /// println!("{}'s has {} User Attributes.", /// cert.fingerprint(), /// cert.user_attributes().count()); /// # assert_eq!(cert.user_attributes().count(), 0); /// # Ok(()) /// # } /// ``` pub fn user_attributes(&self) -> UserAttributeAmalgamationIter { ComponentAmalgamationIter::new(self, self.user_attributes.iter()) } /// Returns an iterator over the certificate's keys. /// /// That is, this returns an iterator over the primary key and any /// subkeys. /// /// **Note:** This returns all keys, even those without a binding /// signature. This is not what you want, unless you are doing a /// low-level inspection of the certificate. Use /// [`ValidCert::keys`] instead. (You turn a `Cert` into a /// [`ValidCert`] by using [`Cert::with_policy`].) /// /// By necessity, this function erases the returned keys' roles. /// If you are only interested in the primary key, use /// [`Cert::primary_key`]. If you are only interested in the /// subkeys, use [`KeyAmalgamationIter::subkeys`]. These /// functions preserve the keys' role in the type system. /// /// A key's secret key material may be protected with a /// password. In such cases, it needs to be decrypted before it /// can be used to decrypt data or generate a signature. Refer to /// [`Key::decrypt_secret`] for details. /// /// [`Cert::primary_key`]: Cert::primary_key() /// [`KeyAmalgamationIter::subkeys`]: amalgamation::key::KeyAmalgamationIter::subkeys() /// [`Key::decrypt_secret`]: crate::packet::Key::decrypt_secret() /// /// # Examples /// /// ``` /// # use sequoia_openpgp as openpgp; /// # use openpgp::cert::prelude::*; /// # use openpgp::packet::Tag; /// # use std::convert::TryInto; /// # /// # fn main() -> openpgp::Result<()> { /// # let (cert, _) = CertBuilder::new() /// # .add_userid("Alice") /// # .add_signing_subkey() /// # .add_transport_encryption_subkey() /// # .generate()?; /// println!("{}'s has {} keys.", /// cert.fingerprint(), /// cert.keys().count()); /// # assert_eq!(cert.keys().count(), 1 + 2); /// # /// # // Make sure that we keep all keys even if they don't have /// # // any self signatures. /// # let packets = cert.into_packets() /// # .filter(|p| p.tag() != Tag::Signature) /// # .collect::<Vec<_>>(); /// # let cert : Cert = packets.try_into()?; /// # assert_eq!(cert.keys().count(), 1 + 2); /// # /// # Ok(()) /// # } /// ``` pub fn keys(&self) -> KeyAmalgamationIter<key::PublicParts, key::UnspecifiedRole> { KeyAmalgamationIter::new(self) } /// Returns an iterator over the certificate's subkeys. /// /// This is used in many test. But, its convenience and /// availability made us use it here and there in the code. /// Nowadays, we use it in tests, and it is merely an alias for /// the public interface. Do not use it for new tests. #[cfg(test)] pub(crate) fn subkeys(&self) -> KeyAmalgamationIter<key::PublicParts, key::SubordinateRole> { self.keys().subkeys() } /// Returns an iterator over the certificate's unknown components. /// /// This function returns all unknown components even those /// without a binding signature. /// /// # Examples /// /// ``` /// # use sequoia_openpgp as openpgp; /// # use openpgp::packet::prelude::*; /// # use openpgp::cert::prelude::*; /// # /// # fn main() -> openpgp::Result<()> { /// # let (cert, _) = /// # CertBuilder::general_purpose(Some("alice@example.org")) /// # .generate()?; /// # let tag = Tag::Private(61); /// # let unknown /// # = Unknown::new(tag, openpgp::Error::UnsupportedPacketType(tag).into()); /// # let cert = cert.insert_packets(unknown)?.0; /// println!("{}'s has {} unknown components.", /// cert.fingerprint(), /// cert.unknowns().count()); /// for ua in cert.unknowns() { /// println!(" Unknown component with tag {} ({}), error: {}", /// ua.unknown().tag(), u8::from(ua.unknown().tag()), ua.unknown().error()); /// } /// # assert_eq!(cert.unknowns().count(), 1); /// # assert_eq!(cert.unknowns().nth(0).unwrap().unknown().tag(), tag); /// # Ok(()) /// # } /// ``` pub fn unknowns(&self) -> UnknownComponentAmalgamationIter { ComponentAmalgamationIter::new(self, self.unknowns.iter()) } /// Returns the bad signatures. /// /// Bad signatures are signatures and revocations that we could /// not associate with one of the certificate's components. /// /// For self signatures and self revocations, we check that the /// signature is correct. For third-party signatures and /// third-party revocations, we only check that the [digest /// prefix] is correct, because third-party keys are not /// available. Checking the digest prefix is *not* an integrity /// check; third party-signatures and third-party revocations may /// be invalid and must still be checked for validity before use. /// /// [digest prefix]: packet::signature::Signature4::digest_prefix() /// /// # Examples /// /// ``` /// # use sequoia_openpgp as openpgp; /// # use openpgp::cert::prelude::*; /// # /// # fn main() -> openpgp::Result<()> { /// # let (cert, rev) = /// # CertBuilder::general_purpose(Some("alice@example.org")) /// # .generate()?; /// println!("{}'s has {} bad signatures.", /// cert.fingerprint(), /// cert.bad_signatures().count()); /// # assert_eq!(cert.bad_signatures().count(), 0); /// # Ok(()) /// # } /// ``` pub fn bad_signatures(&self) -> impl Iterator<Item = &Signature> + Send + Sync { self.primary.bad_signatures() .chain(self.userids.iter().flat_map(|u| u.bad_signatures())) .chain(self.user_attributes.iter().flat_map(|u| u.bad_signatures())) .chain(self.subkeys.iter().flat_map(|u| u.bad_signatures())) .chain(self.unknowns.iter().flat_map(|u| u.bad_signatures())) .chain(self.bad.iter()) } /// Returns a list of any designated revokers for this certificate. /// /// This function returns the designated revokers listed on the /// primary key's binding signatures and the certificate's direct /// key signatures. /// /// Note: the returned list is deduplicated. /// /// # Examples /// /// ``` /// use sequoia_openpgp as openpgp; /// # use openpgp::Result; /// use openpgp::cert::prelude::*; /// use openpgp::policy::StandardPolicy; /// use openpgp::types::RevocationKey; /// /// # fn main() -> Result<()> { /// let p = &StandardPolicy::new(); /// /// let (alice, _) = /// CertBuilder::general_purpose(Some("alice@example.org")) /// .generate()?; /// // Make Alice a designated revoker for Bob. /// let (bob, _) = /// CertBuilder::general_purpose(Some("bob@example.org")) /// .set_revocation_keys(vec![(&alice).into()]) /// .generate()?; /// /// // Make sure Alice is listed as a designated revoker for Bob. /// assert_eq!(bob.revocation_keys(p).collect::<Vec<&RevocationKey>>(), /// vec![&(&alice).into()]); /// # Ok(()) } /// ``` pub fn revocation_keys<'a>(&'a self, policy: &dyn Policy) -> impl Iterator<Item = &'a RevocationKey> + 'a { let mut keys = std::collections::HashSet::new(); let pk_sec = self.primary_key().key().hash_algo_security(); // All user ids. self.userids() .flat_map(|ua| { // All valid self-signatures. let sec = ua.userid().hash_algo_security(); ua.self_signatures() .filter(move |sig| { policy.signature(sig, sec).is_ok() }) }) // All direct-key signatures. .chain(self.primary_key() .self_signatures() .filter(|sig| { policy.signature(sig, pk_sec).is_ok() })) .flat_map(|sig| sig.revocation_keys()) .for_each(|rk| { keys.insert(rk); }); keys.into_iter() } /// Converts the certificate into an iterator over a sequence of /// packets. /// /// This function strips secrets from the keys, similar to how /// serializing a [`Cert`] would not serialize secret keys. This /// behavior makes it harder to accidentally leak secret key /// material. /// /// If you do want to preserve secret key material, use /// [`Cert::into_tsk`] to opt in to getting the secret key /// material, then use [`TSK::into_packets`] to convert to a /// packet stream. /// /// # Examples /// /// ``` /// # use sequoia_openpgp as openpgp; /// # use openpgp::cert::prelude::*; /// # /// # fn main() -> openpgp::Result<()> { /// # let (cert, _) = /// # CertBuilder::general_purpose(Some("alice@example.org")) /// # .generate()?; /// assert!(cert.is_tsk()); /// // But: /// assert!(! Cert::from_packets(cert.into_packets())?.is_tsk()); /// # Ok(()) } /// ``` pub fn into_packets(self) -> impl Iterator<Item=Packet> + Send + Sync { /// Strips the secret key material. fn rewrite(mut p: impl Iterator<Item=Packet> + Send + Sync) -> impl Iterator<Item=Packet> + Send + Sync { let k: Packet = match p.next().unwrap() { Packet::PublicKey(k) => Packet::PublicKey(k.take_secret().0), Packet::PublicSubkey(k) => Packet::PublicSubkey(k.take_secret().0), _ => unreachable!(), }; std::iter::once(k).chain(p) } rewrite(self.primary.into_packets()) .chain(self.userids.into_iter().flat_map(|b| b.into_packets())) .chain(self.user_attributes.into_iter().flat_map(|b| b.into_packets())) .chain(self.subkeys.into_iter().flat_map(|b| rewrite(b.into_packets()))) .chain(self.unknowns.into_iter().flat_map(|b| b.into_packets())) .chain(self.bad.into_iter().map(|s| s.into())) } /// Returns the first certificate found in the sequence of packets. /// /// If the sequence of packets does not start with a certificate /// (specifically, if it does not start with a primary key /// packet), then this fails. /// /// If the sequence contains multiple certificates (i.e., it is a /// keyring), or the certificate is followed by an invalid packet /// this function will fail. To parse keyrings, use /// [`CertParser`] instead of this function. /// /// # Examples /// /// ``` /// use sequoia_openpgp as openpgp; /// use openpgp::cert::prelude::*; /// use openpgp::packet::prelude::*; /// use openpgp::PacketPile; /// /// # fn main() -> openpgp::Result<()> { /// let (cert, rev) = /// CertBuilder::general_purpose(Some("alice@example.org")) /// .generate()?; /// /// // We should be able to turn a certificate into a PacketPile /// // and back. /// assert!(Cert::from_packets(cert.into_packets()).is_ok()); /// /// // But a revocation certificate is not a certificate, so this /// // will fail. /// let p : Vec<Packet> = vec![rev.into()]; /// assert!(Cert::from_packets(p.into_iter()).is_err()); /// # Ok(()) /// # } /// ``` pub fn from_packets(p: impl Iterator<Item=Packet> + Send + Sync) -> Result<Self> { let mut i = parser::CertParser::from_iter(p); if let Some(cert_result) = i.next() { if i.next().is_some() { Err(Error::MalformedCert( "Additional packets found, is this a keyring?".into() ).into()) } else { cert_result } } else { Err(Error::MalformedCert("No data".into()).into()) } } /// Converts the certificate into a `PacketPile`. /// /// # Examples /// /// ``` /// # use sequoia_openpgp as openpgp; /// # use openpgp::PacketPile; /// # use openpgp::cert::prelude::*; /// # /// # fn main() -> openpgp::Result<()> { /// # let (cert, _) = /// # CertBuilder::general_purpose(Some("alice@example.org")) /// # .generate()?; /// let pp = cert.into_packet_pile(); /// # let _ : PacketPile = pp; /// # Ok(()) /// # } /// ``` pub fn into_packet_pile(self) -> PacketPile { self.into() } /// Sorts and deduplicates all components and all signatures of /// all components. /// /// Signatures are compared using [`Signature::normalized_eq`] /// (i.e., the unhashed subpacket area is ignored). If two /// signatures are considered equal, the one that comes first is /// kept. /// /// Keys are compares using [`Key::public_cmp`]. If two keys are /// considered equivalent, then the one with secret key material /// is kept. If they both have secret key material, then the one /// that comes first is kept. fn sort_and_dedup(&mut self) { self.primary.sort_and_dedup(); self.bad.sort_by(Signature::normalized_cmp); self.bad.dedup_by(|a, b| a.normalized_eq(b)); // Order bad signatures so that the most recent one comes // first. self.bad.sort_by(sig_cmp); self.userids.sort_and_dedup(UserID::cmp, |_, _| {}); self.user_attributes.sort_and_dedup(UserAttribute::cmp, |_, _| {}); // XXX: If we have two keys with the same public parts and // different non-empty secret parts, then the one that comes // first will be dropped, the one that comes later will be // kept. // // This can happen if: // // - One is corrupted // - There are two versions that are encrypted differently // // If the order of the keys is unpredictable, this effect is // unpredictable! However, if we merge two canonicalized // certs with Cert::merge_public_and_secret, then we know the // order: the version in `self` comes first, the version in // `other` comes last. self.subkeys.sort_and_dedup(Key::public_cmp, |a, b| { // Recall: if a and b are equal, a will be dropped. // Also, the elements are given in the opposite order // from their order in the vector. // // Prefer the secret in `a`, i.e. the "later" one. if a.has_secret() { std::mem::swap(a, b); } }); self.unknowns.sort_and_dedup(Unknown::best_effort_cmp, |_, _| {}); } fn canonicalize(mut self) -> Self { tracer!(TRACE, "canonicalize", 0); t!("Canonicalizing {}", self.primary_key().key().fingerprint()); use SignatureType::*; // Before we do anything, we'll order and deduplicate the // components. If two components are the same, they will be // merged, and their signatures will also be deduplicated. // This improves the performance considerably when we update a // certificate, because the certificates will be most likely // almost identical, and we avoid about half of the signature // verifications. self.sort_and_dedup(); // Now we verify the self signatures. There are a few things // that we need to be aware of: // // - Signatures may be invalid. These should be dropped. // // - Signatures may be out of order. These should be // reordered so that we have the latest self signature and // we don't drop a userid or subkey that is actually // valid. // We collect bad signatures here in self.bad. Below, we'll // test whether they are just out of order by checking them // against all userids and subkeys. Furthermore, this may be // a partial Cert that is merged into an older copy. // desc: a description of the component // binding: the binding to check // sigs: a vector of sigs in $binding to check macro_rules! check { ($desc:expr, $binding:expr, $sigs:ident, $hash_method:ident, // method to hash the signature $sig_type_pat:pat, // pattern to test signature types against $($hash_args:expr),* // additional arguments to pass to hash_method ) => ({ let sigs = $binding.$sigs.take(); t!("check!({}, {}, {} ({:?}), {}, ...)", $desc, stringify!($binding), stringify!($sigs), sigs, stringify!($hash_method)); for sig in sigs.into_iter() { // Use hash prefix as heuristic. let key = self.primary.key(); match sig.hash_algo().context().and_then(|ctx| { let mut ctx = ctx.for_signature(sig.version()); if matches!(sig.typ(), $sig_type_pat) { sig.$hash_method(&mut ctx, key, $($hash_args),*)?; ctx.into_digest() } else { Err(Error::UnsupportedSignatureType(sig.typ()).into()) } }) { Ok(hash) => { if &sig.digest_prefix()[..] == &hash[..2] { sig.set_computed_digest(Some(hash)); $binding.$sigs.push(sig); } else { t!("Sig {:02X}{:02X}, type = {} \ doesn't belong to {} (computed hash's prefix: {:02X}{:02X})", sig.digest_prefix()[0], sig.digest_prefix()[1], sig.typ(), $desc, hash[0], hash[1]); self.bad.push(sig); } }, Err(e) => { // Hashing failed, we likely don't support the // hash algorithm, or the signature type was // bad. t!("Sig {:02X}{:02X}, type = {}: {}", sig.digest_prefix()[0], sig.digest_prefix()[1], sig.typ(), e); self.bad.push(sig); }, } } }); ($desc:expr, $binding:expr, $sigs:ident, $hash_method:ident, $sig_type_pat:pat) => ({ check!($desc, $binding, $sigs, $hash_method, $sig_type_pat, ) }); } // The same as check!, but for third party signatures. If we // do have the key that made the signature, we can verify it // like in check!. Otherwise, we use the hash prefix as // heuristic approximating the verification. macro_rules! check_3rd_party { ($desc:expr, // a description of the component $binding:expr, // the binding to check $sigs:ident, // a vector of sigs in $binding to check $lookup_fn:expr, // a function to lookup keys $verify_method:ident, // the method to call to verify it $hash_method:ident, // the method to call to compute the hash $sig_type_pat:pat, // pattern to test signature types against $($verify_args:expr),* // additional arguments to pass to the above ) => ({ let sigs = mem::take(&mut $binding.$sigs); t!("check_3rd_party!({}, {}, {} ({:?}_, {}, {}, ...)", $desc, stringify!($binding), stringify!($sigs), sigs, stringify!($verify_method), stringify!($hash_method)); for sig in sigs { // Use hash prefix as heuristic. let key = self.primary.key(); match sig.hash_algo().context().and_then(|ctx| { let mut ctx = ctx.for_signature(sig.version()); if matches!(sig.typ(), $sig_type_pat) { sig.$hash_method(&mut ctx, key, $($verify_args),*)?; ctx.into_digest() } else { Err(Error::UnsupportedSignatureType(sig.typ()).into()) } }) { Ok(hash) => { if &sig.digest_prefix()[..] == &hash[..2] { // See if we can get the key for a // positive verification. if let Some(key) = $lookup_fn(&sig) { if let Ok(()) = sig.$verify_method( &key, self.primary.key(), $($verify_args),*) { $binding.$sigs.push(sig); } else { t!("Sig {:02X}{:02X}, type = {} \ doesn't belong to {}", sig.digest_prefix()[0], sig.digest_prefix()[1], sig.typ(), $desc); self.bad.push(sig); } } else { // No key, we need to trust our heuristic. sig.set_computed_digest(Some(hash)); $binding.$sigs.push(sig); } } else { t!("Sig {:02X}{:02X}, type = {} \ doesn't belong to {} (computed hash's prefix: {:02X}{:02X})", sig.digest_prefix()[0], sig.digest_prefix()[1], sig.typ(), $desc, hash[0], hash[1]); self.bad.push(sig); } }, Err(e) => { // Hashing failed, we likely don't support the // hash algorithm, or the signature type was // bad. t!("Sig {:02X}{:02X}, type = {}: {}", sig.digest_prefix()[0], sig.digest_prefix()[1], sig.typ(), e); self.bad.push(sig); }, } } }); ($desc:expr, $binding:expr, $sigs:ident, $lookup_fn:expr, $verify_method:ident, $hash_method:ident, $sig_type_pat:pat) => ({ check_3rd_party!($desc, $binding, $sigs, $lookup_fn, $verify_method, $hash_method, $sig_type_pat, ) }); } // Placeholder lookup function. fn lookup_fn(_: &Signature) -> Option<Key<key::PublicParts, key::UnspecifiedRole>> { None } check!("primary key", self.primary, self_signatures, hash_direct_key, DirectKey); check!("primary key", self.primary, self_revocations, hash_direct_key, KeyRevocation); check_3rd_party!("primary key", self.primary, certifications, lookup_fn, verify_direct_key, hash_direct_key, DirectKey); check_3rd_party!("primary key", self.primary, other_revocations, lookup_fn, verify_primary_key_revocation, hash_direct_key, KeyRevocation); // Attestations are never associated with a primary key. If // there are any, they need to be reordered. self.bad.append(&mut self.primary.attestations.take()); for ua in self.userids.iter_mut() { check!(format!("userid \"{}\"", String::from_utf8_lossy(ua.userid().value())), ua, self_signatures, hash_userid_binding, GenericCertification | PersonaCertification | CasualCertification | PositiveCertification, ua.userid()); check!(format!("userid \"{}\"", String::from_utf8_lossy(ua.userid().value())), ua, self_revocations, hash_userid_binding, CertificationRevocation, ua.userid()); check!(format!("userid \"{}\"", String::from_utf8_lossy(ua.userid().value())), ua, attestations, hash_userid_approval, CertificationApproval, ua.userid()); check_3rd_party!( format!("userid \"{}\"", String::from_utf8_lossy(ua.userid().value())), ua, certifications, lookup_fn, verify_userid_binding, hash_userid_binding, GenericCertification | PersonaCertification | CasualCertification | PositiveCertification, ua.userid()); check_3rd_party!( format!("userid \"{}\"", String::from_utf8_lossy(ua.userid().value())), ua, other_revocations, lookup_fn, verify_userid_revocation, hash_userid_binding, CertificationRevocation, ua.userid()); } for binding in self.user_attributes.iter_mut() { check!("user attribute", binding, self_signatures, hash_user_attribute_binding, GenericCertification | PersonaCertification | CasualCertification | PositiveCertification, binding.user_attribute()); check!("user attribute", binding, self_revocations, hash_user_attribute_binding, CertificationRevocation, binding.user_attribute()); check!("user attribute", binding, attestations, hash_user_attribute_approval, CertificationApproval, binding.user_attribute()); check_3rd_party!( "user attribute", binding, certifications, lookup_fn, verify_user_attribute_binding, hash_user_attribute_binding, GenericCertification | PersonaCertification | CasualCertification | PositiveCertification, binding.user_attribute()); check_3rd_party!( "user attribute", binding, other_revocations, lookup_fn, verify_user_attribute_revocation, hash_user_attribute_binding, CertificationRevocation, binding.user_attribute()); } for binding in self.subkeys.iter_mut() { check!(format!("subkey {}", binding.key().keyid()), binding, self_signatures, hash_subkey_binding, SubkeyBinding, binding.key()); check!(format!("subkey {}", binding.key().keyid()), binding, self_revocations, hash_subkey_binding, SubkeyRevocation, binding.key()); check_3rd_party!( format!("subkey {}", binding.key().keyid()), binding, certifications, lookup_fn, verify_subkey_binding, hash_subkey_binding, SubkeyBinding, binding.key()); check_3rd_party!( format!("subkey {}", binding.key().keyid()), binding, other_revocations, lookup_fn, verify_subkey_revocation, hash_subkey_binding, SubkeyRevocation, binding.key()); // Attestations are never associated with a subkey. If // there are any, they need to be reordered. self.bad.append(&mut binding.attestations.take()); } // See if the signatures that didn't validate are just out of // place. let mut bad_sigs: Vec<(Option<usize>, Signature)> = std::mem::take(&mut self.bad).into_iter() .map(|sig| { t!("We're going to reconsider bad signature {:?}", sig); (None, sig) }) .collect(); t!("Attempting to reorder {} signatures", bad_sigs.len()); // Do the same for signatures on unknown components, but // remember where we took them from. for (i, c) in self.unknowns.iter_mut().enumerate() { for sig in c.self_signatures.take().into_iter() .chain( std::mem::take(&mut c.certifications).into_iter()) .chain( c.attestations.take().into_iter()) .chain( c.self_revocations.take().into_iter()) .chain( std::mem::take(&mut c.other_revocations).into_iter()) { t!("We're going to reconsider {:?} on unknown component #{}", sig, i); bad_sigs.push((Some(i), sig)); } } let primary_fp: KeyHandle = self.key_handle(); 'outer: for (unknown_idx, sig) in bad_sigs { // Did we find a new place for sig? let mut found_component = false; // Is this signature a self-signature? let issuers = sig.get_issuers(); let is_selfsig = issuers.is_empty() || issuers.iter().any(|kh| kh.aliases(&primary_fp)); macro_rules! check_one { ($desc:expr, // a description of the component $sigs:expr, // where to put $sig if successful $sig:ident, // the signature to check $hash_method:ident, // the method to compute the hash $($verify_args:expr),* // additional arguments for the above ) => ({ if is_selfsig { t!("check_one!({}, {:?}, {:?}/{}, {}, ...)", $desc, $sigs, $sig, $sig.typ(), stringify!($hash_method)); // Use hash prefix as heuristic. let key = self.primary.key(); match $sig.hash_algo().context() .and_then(|ctx| { let mut ctx = ctx.for_signature($sig.version()); $sig.$hash_method(&mut ctx, key, $($verify_args),*)?; ctx.into_digest() }) { Ok(hash) => { if &$sig.digest_prefix()[..] == &hash[..2] { t!("Sig {:02X}{:02X}, {:?} \ was out of place. Likely belongs to {}.", $sig.digest_prefix()[0], $sig.digest_prefix()[1], $sig.typ(), $desc); $sigs.push({ let sig = $sig.clone(); sig.set_computed_digest(Some(hash)); sig }); // The cost of missing a revocation // certificate merely because we put // it into the wrong place seem to // outweigh the cost of duplicating // it. t!("Will keep trying to match this sig to \ other components (found before? {:?})...", found_component); found_component = true; } else { t!("Sig {:02X}{:02X}, {:?} \ does not belong to {}: \ hash prefix mismatch {}", $sig.digest_prefix()[0], $sig.digest_prefix()[1], $sig.typ(), $desc, crate::fmt::hex::encode(&hash)); } }, Err(e) => { t!("Sig {:02X}{:02X}, type = {}: {}", $sig.digest_prefix()[0], $sig.digest_prefix()[1], $sig.typ(), e); }, } } }); ($desc:expr, $sigs:expr, $sig:ident, $hash_method:ident) => ({ check_one!($desc, $sigs, $sig, $hash_method,) }); } // The same as check_one!, but for third party signatures. // If we do have the key that made the signature, we can // verify it like in check!. Otherwise, we use the hash // prefix as heuristic approximating the verification. macro_rules! check_one_3rd_party { ($desc:expr, // a description of the component $sigs:expr, // where to put $sig if successful $sig:ident, // the signature to check $lookup_fn:expr, // a function to lookup keys $verify_method:ident, // the method to verify it $hash_method:ident, // the method to compute the hash $($verify_args:expr),* // additional arguments for the above ) => ({ if ! is_selfsig { t!("check_one_3rd_party!({}, {}, {:?}, {}, {}, ...)", $desc, stringify!($sigs), $sig, stringify!($verify_method), stringify!($hash_method)); if let Some(key) = $lookup_fn(&$sig) { match $sig.$verify_method(&key, self.primary.key(), $($verify_args),*) { Ok(()) => { t!("Sig {:02X}{:02X}, {:?} \ was out of place. Belongs to {}.", $sig.digest_prefix()[0], $sig.digest_prefix()[1], $sig.typ(), $desc); $sigs.push($sig); continue 'outer; }, Err(err) => { t!("Sig {:02X}{:02X}, type = {} \ doesn't belong to {}: {:?}", $sig.digest_prefix()[0], $sig.digest_prefix()[1], $sig.typ(), $desc, err); }, } } else { // Use hash prefix as heuristic. let key = self.primary.key(); match $sig.hash_algo().context() .and_then(|ctx| { let mut ctx = ctx.for_signature($sig.version()); $sig.$hash_method(&mut ctx, key, $($verify_args),*)?; ctx.into_digest() }) { Ok(hash) => { if &$sig.digest_prefix()[..] == &hash[..2] { t!("Sig {:02X}{:02X}, {:?} \ was out of place. Likely belongs to {}.", $sig.digest_prefix()[0], $sig.digest_prefix()[1], $sig.typ(), $desc); $sigs.push({ let sig = $sig.clone(); sig.set_computed_digest(Some(hash)); sig }); // The cost of missing a revocation // certificate merely because we put // it into the wrong place seem to // outweigh the cost of duplicating // it. t!("Will keep trying to match this sig to \ other components (found before? {:?})...", found_component); found_component = true; } else { t!("Sig {:02X}{:02X}, {:?} \ does not belong to {}: \ hash prefix mismatch {}", $sig.digest_prefix()[0], $sig.digest_prefix()[1], $sig.typ(), $desc, crate::fmt::hex::encode(&hash)); } }, Err(e) => { t!("Sig {:02X}{:02X}, type = {}: {}", $sig.digest_prefix()[0], $sig.digest_prefix()[1], $sig.typ(), e); }, } } } }); ($desc:expr, $sigs:expr, $sig:ident, $lookup_fn:expr, $verify_method:ident, $hash_method:ident) => ({ check_one_3rd_party!($desc, $sigs, $sig, $lookup_fn, $verify_method, $hash_method, ) }); } match sig.typ() { DirectKey => { check_one!("primary key", self.primary.self_signatures, sig, hash_direct_key); check_one_3rd_party!( "primary key", self.primary.certifications, sig, lookup_fn, verify_direct_key, hash_direct_key); }, KeyRevocation => { check_one!("primary key", self.primary.self_revocations, sig, hash_direct_key); check_one_3rd_party!( "primary key", self.primary.other_revocations, sig, lookup_fn, verify_primary_key_revocation, hash_direct_key); }, GenericCertification | PersonaCertification | CasualCertification | PositiveCertification => { for binding in self.userids.iter_mut() { check_one!(format!("userid \"{}\"", String::from_utf8_lossy( binding.userid().value())), binding.self_signatures, sig, hash_userid_binding, binding.userid()); check_one_3rd_party!( format!("userid \"{}\"", String::from_utf8_lossy( binding.userid().value())), binding.certifications, sig, lookup_fn, verify_userid_binding, hash_userid_binding, binding.userid()); } for binding in self.user_attributes.iter_mut() { check_one!("user attribute", binding.self_signatures, sig, hash_user_attribute_binding, binding.user_attribute()); check_one_3rd_party!( "user attribute", binding.certifications, sig, lookup_fn, verify_user_attribute_binding, hash_user_attribute_binding, binding.user_attribute()); } }, crate::types::SignatureType::CertificationApproval => { for binding in self.userids.iter_mut() { check_one!(format!("userid \"{}\"", String::from_utf8_lossy( binding.userid().value())), binding.attestations, sig, hash_userid_approval, binding.userid()); } for binding in self.user_attributes.iter_mut() { check_one!("user attribute", binding.attestations, sig, hash_user_attribute_approval, binding.user_attribute()); } }, CertificationRevocation => { for binding in self.userids.iter_mut() { check_one!(format!("userid \"{}\"", String::from_utf8_lossy( binding.userid().value())), binding.self_revocations, sig, hash_userid_binding, binding.userid()); check_one_3rd_party!( format!("userid \"{}\"", String::from_utf8_lossy( binding.userid().value())), binding.other_revocations, sig, lookup_fn, verify_userid_revocation, hash_userid_binding, binding.userid()); } for binding in self.user_attributes.iter_mut() { check_one!("user attribute", binding.self_revocations, sig, hash_user_attribute_binding, binding.user_attribute()); check_one_3rd_party!( "user attribute", binding.other_revocations, sig, lookup_fn, verify_user_attribute_revocation, hash_user_attribute_binding, binding.user_attribute()); } }, SubkeyBinding => { for binding in self.subkeys.iter_mut() { check_one!(format!("subkey {}", binding.key().keyid()), binding.self_signatures, sig, hash_subkey_binding, binding.key()); check_one_3rd_party!( format!("subkey {}", binding.key().keyid()), binding.certifications, sig, lookup_fn, verify_subkey_binding, hash_subkey_binding, binding.key()); } }, SubkeyRevocation => { for binding in self.subkeys.iter_mut() { check_one!(format!("subkey {}", binding.key().keyid()), binding.self_revocations, sig, hash_subkey_binding, binding.key()); check_one_3rd_party!( format!("subkey {}", binding.key().keyid()), binding.other_revocations, sig, lookup_fn, verify_subkey_revocation, hash_subkey_binding, binding.key()); } }, typ => { t!("Odd signature type: {:?}", typ); }, } if found_component { continue; } // Keep them for later. t!("{} {:02X}{:02X}, {:?}, originally found on {:?} \ doesn't belong to any known component or is bad.", if is_selfsig { "Self-sig" } else { "3rd-party-sig" }, sig.digest_prefix()[0], sig.digest_prefix()[1], sig.typ(), unknown_idx); if let Some(i) = unknown_idx { let is_revocation = match sig.typ() { CertificationRevocation | KeyRevocation | SubkeyRevocation => true, _ => false, }; match (is_selfsig, is_revocation) { (false, false) => self.unknowns[i].certifications.push(sig), (false, true) => self.unknowns[i].other_revocations.push(sig), (true, false) => self.unknowns[i].self_signatures.push(sig), (true, true) => self.unknowns[i].self_revocations.push(sig), } } else { self.bad.push(sig); } } if !self.bad.is_empty() { t!("{}: ignoring {} bad self signatures", self.keyid(), self.bad.len()); } // Sort again. We may have moved signatures to the right // component, and we need to ensure they are in the right spot // (i.e. newest first). self.sort_and_dedup(); // XXX: Check if the sigs in other_sigs issuer are actually // designated revokers for this key (listed in a "Revocation // Key" subpacket in *any* non-revoked self signature). Only // if that is the case should a sig be considered a potential // revocation. (This applies to // self.primary_other_revocations as well as // self.userids().other_revocations, etc.) If not, put the // sig on the bad list. // // Note: just because the Cert doesn't indicate that a key is a // designed revoker doesn't mean that it isn't---we might just // be missing the signature. In other words, this is a policy // decision, but given how easy it could be to create rogue // revocations, is probably the better to reject such // signatures than to keep them around and have many keys // being shown as "potentially revoked". // XXX Do some more canonicalization. self } /// Returns the certificate's fingerprint as a `KeyHandle`. /// /// # Examples /// /// ``` /// # use sequoia_openpgp as openpgp; /// # use openpgp::cert::prelude::*; /// # use openpgp::KeyHandle; /// # /// # fn main() -> openpgp::Result<()> { /// # let (cert, _) = /// # CertBuilder::general_purpose(Some("alice@example.org")) /// # .generate()?; /// # /// println!("{}", cert.key_handle()); /// /// // This always returns a fingerprint. /// match cert.key_handle() { /// KeyHandle::Fingerprint(_) => (), /// KeyHandle::KeyID(_) => unreachable!(), /// } /// # /// # Ok(()) /// # } /// ``` pub fn key_handle(&self) -> KeyHandle { self.primary.key().key_handle() } /// Returns the certificate's fingerprint. /// /// # Examples /// /// ``` /// # use sequoia_openpgp as openpgp; /// # use openpgp::cert::prelude::*; /// # /// # fn main() -> openpgp::Result<()> { /// # let (cert, _) = /// # CertBuilder::general_purpose(Some("alice@example.org")) /// # .generate()?; /// # /// println!("{}", cert.fingerprint()); /// # /// # Ok(()) /// # } /// ``` pub fn fingerprint(&self) -> Fingerprint { self.primary.key().fingerprint() } /// Returns the certificate's Key ID. /// /// As a general rule of thumb, you should prefer the fingerprint /// as it is possible to create keys with a colliding Key ID using /// a [birthday attack]. /// /// [birthday attack]: https://nullprogram.com/blog/2019/07/22/ /// /// # Examples /// /// ``` /// # use sequoia_openpgp as openpgp; /// # use openpgp::cert::prelude::*; /// # /// # fn main() -> openpgp::Result<()> { /// # let (cert, _) = /// # CertBuilder::general_purpose(Some("alice@example.org")) /// # .generate()?; /// # /// println!("{}", cert.keyid()); /// # /// # Ok(()) /// # } /// ``` pub fn keyid(&self) -> KeyID { self.primary.key().keyid() } /// Merges `other` into `self`, ignoring secret key material in /// `other`. /// /// If `other` is a different certificate, then an error is /// returned. /// /// Merging two versions of a certificate is complicated, because /// there may be multiple variants of the same key or signature /// packet. It is possible to have multiple variants of a key /// packet if one contains secret key material, and the other /// does not, or if both contain secret key material that is /// protected in different ways, e.g., a different algorithm, or a /// different password. Multiple variants of a signature packet /// are possible when the unhashed subpacket areas differ. /// /// This routine is different from [`Cert::insert_packets`] in the /// following ways: /// /// - `Cert::merge_public` strictly prefers keys in `self` to /// those in `other`. That is, if a primary key or subkey /// appears in both `self` and `other`, the version in `self` /// is kept. In contrast, [`Cert::insert_packets`] prefers /// the new variant. /// /// - If `other` contains a new subkey, `Cert::merge_public` /// merges it into the certificate, but strips any secret key /// material. In contrast, [`Cert::insert_packets`] preserves /// the secret key material. /// /// - If both `self` and `other` contain two variants of a /// signature (that is, a signature packet that is identical /// expect for the contents of the unhashed subpacket area), /// `Cert::merge_public` merges the two variants using /// [`Signature::merge`], which combines the unhashed /// subpacket areas. [`Cert::insert_packets`] just takes the /// new signature packet. /// /// This function is appropriate to merge certificate material /// from untrusted sources like keyservers, because it only adds /// data to the existing certificate, it never overwrites existing /// data, and it doesn't import secret key material, which may /// have been manipulated by an attacker. /// /// [`Cert::merge_public_and_secret`] is similar to this function, /// but merges in secret key material from `other`. /// /// # Examples /// /// Merge a certificate from an untrusted source: /// /// ``` /// # use sequoia_openpgp as openpgp; /// # use openpgp::cert::prelude::*; /// # /// # fn main() -> openpgp::Result<()> { /// # let (local, _) = /// # CertBuilder::general_purpose(Some("alice@example.org")) /// # .generate()?; /// # let keyserver = local.clone(); /// // Merge the local version with the version from the keyserver. /// let cert = local.merge_public(keyserver)?; /// # let _ = cert; /// # Ok(()) } /// ``` /// /// Secret key material in `other` is stripped, even if the /// variant of the packet in `self` doesn't have secret key /// material: /// /// ``` /// use sequoia_openpgp as openpgp; /// use openpgp::cert::CertBuilder; /// /// # fn main() -> openpgp::Result<()> { /// // Create a new key. /// let (cert, rev) = /// CertBuilder::general_purpose(Some("alice@example.org")) /// .generate()?; /// assert!(cert.is_tsk()); /// /// let stripped = cert.clone().strip_secret_key_material(); /// assert!(! stripped.is_tsk()); /// /// // Merge `cert` into `stripped`. /// let merged = stripped.merge_public(cert).expect("same certificate"); /// assert!(! merged.is_tsk()); /// /// # Ok(()) } /// ``` /// /// Secret key material from `self` is preferred to secret key /// material from `other`: /// /// ``` /// use sequoia_openpgp as openpgp; /// use openpgp::crypto::Password; /// use openpgp::cert::prelude::*; /// use openpgp::Packet; /// /// # fn main() -> openpgp::Result<()> { /// let p0 = Password::from("old password"); /// let p1 = Password::from("new password"); /// /// // Create a new key. /// let (cert, rev) = /// CertBuilder::general_purpose(Some("alice@example.org")) /// .set_password(Some(p0.clone())) /// .generate()?; /// assert!(cert.is_tsk()); /// /// // Change the password for the primary key. /// let pk = cert.primary_key().key().clone().parts_into_secret()? /// .decrypt_secret(&p0)? /// .encrypt_secret(&p1)?; /// let other = Cert::try_from(vec![ Packet::from(pk) ]) /// .expect("a primary key is a certificate"); /// /// // Merge `other` into `cert`. /// let merged = cert.merge_public(other).expect("same certificate"); /// /// // `merged` has the secret key material from `cert`, which is /// // password protected with `p0`, not `other`, which is password /// // protected with `p1`. /// assert!(merged.primary_key().key().clone().parts_into_secret()? /// .decrypt_secret(&p0).is_ok()); /// # Ok(()) } /// ``` /// /// The unhashed subpacket areas of two variants of a signature /// are merged: /// /// ``` /// use sequoia_openpgp as openpgp; /// use openpgp::Packet; /// use openpgp::cert::prelude::*; /// use openpgp::packet::signature::subpacket::Subpacket; /// use openpgp::packet::signature::subpacket::SubpacketTag; /// use openpgp::packet::signature::subpacket::SubpacketValue; /// /// # fn main() -> openpgp::Result<()> { /// // Create a new key. /// let (cert, rev) = /// CertBuilder::general_purpose(Some("alice@example.org")) /// .generate()?; /// assert!(cert.is_tsk()); /// /// // Add a subpacket to the unhashed subpacket area. /// let subpacket_a = Subpacket::new( /// SubpacketValue::Unknown { /// tag: SubpacketTag::Private(100), /// body: Vec::new(), /// }, /// false).expect("valid"); /// let subpacket_b = Subpacket::new( /// SubpacketValue::Unknown { /// tag: SubpacketTag::Private(101), /// body: Vec::new(), /// }, /// false).expect("valid"); /// /// let mut cert_a = cert.clone().into_packets().collect::<Vec<Packet>>(); /// match cert_a[1] { /// Packet::Signature(ref mut sig) => { /// let unhashed_area = sig.unhashed_area_mut(); /// assert!(unhashed_area.subpacket(subpacket_a.tag()).is_none()); /// assert!(unhashed_area.subpacket(subpacket_b.tag()).is_none()); /// unhashed_area.add(subpacket_a.clone()); /// } /// _ => panic!("Second packet is the direct signature packet."), /// }; /// let cert_a = Cert::try_from(cert_a).expect("valid"); /// /// let mut cert_b = cert.clone().into_packets().collect::<Vec<Packet>>(); /// match cert_b[1] { /// Packet::Signature(ref mut sig) => { /// let unhashed_area = sig.unhashed_area_mut(); /// assert!(unhashed_area.subpacket(subpacket_a.tag()).is_none()); /// assert!(unhashed_area.subpacket(subpacket_b.tag()).is_none()); /// unhashed_area.add(subpacket_b.clone()); /// } /// _ => panic!("Second packet is the direct signature packet."), /// }; /// let cert_b = Cert::try_from(cert_b).expect("valid"); /// /// // When we merge `cert_b` into `cert_a`, the signature packets /// // are merged: /// let merged = cert_a.clone().merge_public(cert_b.clone()) /// .expect("same certificate") /// .into_packets() /// .collect::<Vec<Packet>>(); /// match merged[1] { /// Packet::Signature(ref sig) => { /// let unhashed_area = sig.unhashed_area(); /// assert!(unhashed_area.subpacket(subpacket_a.tag()).is_some()); /// assert!(unhashed_area.subpacket(subpacket_b.tag()).is_some()); /// } /// _ => panic!("Second packet is the direct signature packet."), /// }; /// /// // Likewise, when we merge `cert_a` into `cert_b`, the signature /// // packets are merged: /// let merged = cert_b.clone().merge_public(cert_a.clone()) /// .expect("same certificate") /// .into_packets() /// .collect::<Vec<Packet>>(); /// match merged[1] { /// Packet::Signature(ref sig) => { /// let unhashed_area = sig.unhashed_area(); /// assert!(unhashed_area.subpacket(subpacket_a.tag()).is_some()); /// assert!(unhashed_area.subpacket(subpacket_b.tag()).is_some()); /// } /// _ => panic!("Second packet is the direct signature packet."), /// }; /// # Ok(()) } /// ``` pub fn merge_public(self, other: Cert) -> Result<Self> { // Strip all secrets from `other`. let other_public = other.strip_secret_key_material(); // Then merge it. self.merge_public_and_secret(other_public) } /// Merges `other` into `self`, including secret key material. /// /// If `other` is a different certificate, then an error is /// returned. /// /// This function is like [`Cert::merge_public`] except: /// /// - if two variants of the same key have secret key material, /// then the version in `other` is preferred, /// /// - if there are two variants of the same key, and one has /// secret key material, that variant is preferred. /// /// This is different from [`Cert::insert_packets`], which /// unconditionally prefers keys in the packets that are being /// merged into the certificate. /// /// It is important to only merge key material from trusted /// sources using this function, because it may be used to import /// secret key material. Secret key material is not authenticated /// by OpenPGP, and there are plausible attack scenarios where a /// malicious actor injects secret key material. /// /// To merge only public key material, which is always safe, use /// [`Cert::merge_public`]. /// /// # Examples /// /// Merge a certificate from a trusted source: /// /// ``` /// # use sequoia_openpgp as openpgp; /// # use openpgp::cert::prelude::*; /// # /// # fn main() -> openpgp::Result<()> { /// # let (local, _) = /// # CertBuilder::general_purpose(Some("alice@example.org")) /// # .generate()?; /// # let other_device = local.clone(); /// // Merge the local version with the version from your other device. /// let cert = local.merge_public_and_secret(other_device)?; /// # let _ = cert; /// # Ok(()) } /// ``` /// /// Secret key material is preferred to no secret key material: /// /// ``` /// use sequoia_openpgp as openpgp; /// use openpgp::cert::CertBuilder; /// /// # fn main() -> openpgp::Result<()> { /// // Create a new key. /// let (cert, rev) = /// CertBuilder::general_purpose(Some("alice@example.org")) /// .generate()?; /// assert!(cert.is_tsk()); /// /// let stripped = cert.clone().strip_secret_key_material(); /// assert!(! stripped.is_tsk()); /// /// // If we merge `cert` into `stripped`, the secret key material is /// // preserved: /// let merged = stripped.clone().merge_public_and_secret(cert.clone()) /// .expect("same certificate"); /// assert!(merged.is_tsk()); /// /// // Likewise if we merge `stripped` into `cert`: /// let merged = cert.merge_public_and_secret(stripped) /// .expect("same certificate"); /// assert!(merged.is_tsk()); /// /// # Ok(()) } /// ``` /// /// Secret key material in `other` is preferred: /// /// ``` /// use sequoia_openpgp as openpgp; /// use openpgp::crypto::Password; /// use openpgp::cert::prelude::*; /// use openpgp::Packet; /// /// # fn main() -> openpgp::Result<()> { /// let p0 = Password::from("old password"); /// let p1 = Password::from("new password"); /// /// // Create a new key. /// let (cert, rev) = /// CertBuilder::general_purpose(Some("alice@example.org")) /// .set_password(Some(p0.clone())) /// .generate()?; /// assert!(cert.is_tsk()); /// /// // Change the password for the primary key. /// let pk = cert.primary_key().key().clone().parts_into_secret()? /// .decrypt_secret(&p0)? /// .encrypt_secret(&p1)?; /// let other = Cert::try_from(vec![ Packet::from(pk) ]) /// .expect("a primary key is a certificate"); /// /// // Merge `other` into `cert`. /// let merged = cert.merge_public_and_secret(other).expect("same certificate"); /// /// // `merged` has the secret key material from `other`, which is /// // password protected with `p1`, not `self`, which is password /// // protected with `p0`. /// assert!(merged.primary_key().key().clone().parts_into_secret()? /// .decrypt_secret(&p1).is_ok()); /// # Ok(()) } /// ``` /// /// The unhashed subpacket areas of two variants of a signature /// are merged: /// /// ``` /// use sequoia_openpgp as openpgp; /// use openpgp::Packet; /// use openpgp::cert::prelude::*; /// use openpgp::packet::signature::subpacket::Subpacket; /// use openpgp::packet::signature::subpacket::SubpacketTag; /// use openpgp::packet::signature::subpacket::SubpacketValue; /// /// # fn main() -> openpgp::Result<()> { /// // Create a new key. /// let (cert, rev) = /// CertBuilder::general_purpose(Some("alice@example.org")) /// .generate()?; /// assert!(cert.is_tsk()); /// /// // Add a subpacket to the unhashed subpacket area. /// let subpacket_a = Subpacket::new( /// SubpacketValue::Unknown { /// tag: SubpacketTag::Private(100), /// body: Vec::new(), /// }, /// false).expect("valid"); /// let subpacket_b = Subpacket::new( /// SubpacketValue::Unknown { /// tag: SubpacketTag::Private(101), /// body: Vec::new(), /// }, /// false).expect("valid"); /// /// let mut cert_a = cert.clone().into_packets().collect::<Vec<Packet>>(); /// match cert_a[1] { /// Packet::Signature(ref mut sig) => { /// let unhashed_area = sig.unhashed_area_mut(); /// assert!(unhashed_area.subpacket(subpacket_a.tag()).is_none()); /// assert!(unhashed_area.subpacket(subpacket_b.tag()).is_none()); /// unhashed_area.add(subpacket_a.clone()); /// } /// _ => panic!("Second packet is the direct signature packet."), /// }; /// let cert_a = Cert::try_from(cert_a).expect("valid"); /// /// let mut cert_b = cert.clone().into_packets().collect::<Vec<Packet>>(); /// match cert_b[1] { /// Packet::Signature(ref mut sig) => { /// let unhashed_area = sig.unhashed_area_mut(); /// assert!(unhashed_area.subpacket(subpacket_a.tag()).is_none()); /// assert!(unhashed_area.subpacket(subpacket_b.tag()).is_none()); /// unhashed_area.add(subpacket_b.clone()); /// } /// _ => panic!("Second packet is the direct signature packet."), /// }; /// let cert_b = Cert::try_from(cert_b).expect("valid"); /// /// // When we merge `cert_b` into `cert_a`, the signature packets /// // are merged: /// let merged = cert_a.clone().merge_public_and_secret(cert_b.clone()) /// .expect("same certificate") /// .into_packets() /// .collect::<Vec<Packet>>(); /// match merged[1] { /// Packet::Signature(ref sig) => { /// let unhashed_area = sig.unhashed_area(); /// assert!(unhashed_area.subpacket(subpacket_a.tag()).is_some()); /// assert!(unhashed_area.subpacket(subpacket_b.tag()).is_some()); /// } /// _ => panic!("Second packet is the direct signature packet."), /// }; /// /// // Likewise, when we merge `cert_a` into `cert_b`, the signature /// // packets are merged: /// let merged = cert_b.clone().merge_public_and_secret(cert_a.clone()) /// .expect("same certificate") /// .into_packets() /// .collect::<Vec<Packet>>(); /// match merged[1] { /// Packet::Signature(ref sig) => { /// let unhashed_area = sig.unhashed_area(); /// assert!(unhashed_area.subpacket(subpacket_a.tag()).is_some()); /// assert!(unhashed_area.subpacket(subpacket_b.tag()).is_some()); /// } /// _ => panic!("Second packet is the direct signature packet."), /// }; /// # Ok(()) } /// ``` pub fn merge_public_and_secret(mut self, mut other: Cert) -> Result<Self> { if self.fingerprint() != other.fingerprint() { // The primary key is not the same. There is nothing to // do. return Err(Error::InvalidArgument( "Primary key mismatch".into()).into()); } // Prefer the secret in `other`. if other.primary.key().has_secret() { std::mem::swap(self.primary.key_mut(), other.primary.key_mut()); } self.primary.self_signatures.append( &mut other.primary.self_signatures); self.primary.attestations.append( &mut other.primary.attestations); self.primary.certifications.append( &mut other.primary.certifications); self.primary.self_revocations.append( &mut other.primary.self_revocations); self.primary.other_revocations.append( &mut other.primary.other_revocations); self.userids.append(&mut other.userids); self.user_attributes.append(&mut other.user_attributes); self.subkeys.append(&mut other.subkeys); self.bad.append(&mut other.bad); Ok(self.canonicalize()) } // Returns whether the specified packet is a valid start of a // certificate. fn valid_start<T>(tag: T) -> Result<()> where T: Into<Tag> { let tag = tag.into(); match tag { Tag::SecretKey | Tag::PublicKey => Ok(()), _ => Err(Error::MalformedCert( format!("A certificate does not start with a {}", tag)).into()), } } // Returns whether the specified packet can occur in a // certificate. // // This function rejects all packets that are known to not belong // in a certificate. It conservatively accepts unknown packets // based on the assumption that they are some new component type // from the future. fn valid_packet<T>(tag: T) -> Result<()> where T: Into<Tag> { let tag = tag.into(); match tag { // Packets that definitely don't belong in a certificate. Tag::Reserved | Tag::PKESK | Tag::SKESK | Tag::OnePassSig | Tag::CompressedData | Tag::SED | Tag::Literal | Tag::SEIP | Tag::MDC | Tag::AED => { Err(Error::MalformedCert( format!("A certificate cannot not include a {}", tag)).into()) } // The rest either definitely belong in a certificate or // are unknown (and conservatively accepted for future // compatibility). _ => Ok(()), } } /// Adds packets to the certificate. /// /// This function turns the certificate into a sequence of /// packets, appends the packets to the end of it, and /// canonicalizes the result. [Known packets that don't belong in /// a TPK or TSK] cause this function to return an error. Unknown /// packets are retained and added to the list of [unknown /// components]. The goal is to provide some future /// compatibility. /// /// If a key is merged that already exists in the certificate, it /// replaces the existing key. This way, secret key material can /// be added, removed, encrypted, or decrypted. /// /// Similarly, if a signature is merged that already exists in the /// certificate, it replaces the existing signature. This way, /// the unhashed subpacket area can be updated. /// /// On success, this function returns the certificate with the /// packets merged in, and a boolean indicating whether the /// certificate actually changed. Changed here means that at /// least one new packet was added, or an existing packet was /// updated. Alternatively, changed means that the serialized /// form has changed. /// /// [Known packets that don't belong in a TPK or TSK]: https://www.rfc-editor.org/rfc/rfc9580.html#section-10 /// [unknown components]: Cert::unknowns() /// /// # Examples /// /// ``` /// use sequoia_openpgp as openpgp; /// use openpgp::cert::prelude::*; /// use openpgp::packet::prelude::*; /// use openpgp::serialize::Serialize; /// use openpgp::parse::Parse; /// use openpgp::types::DataFormat; /// /// # fn main() -> openpgp::Result<()> { /// // Create a new key. /// let (cert, rev) = /// CertBuilder::general_purpose(Some("alice@example.org")) /// .generate()?; /// assert!(cert.is_tsk()); /// /// /// // Merging in the certificate doesn't change it. /// let identical_cert = cert.clone(); /// let (cert, changed) = /// cert.insert_packets(identical_cert.into_tsk().into_packets())?; /// assert!(! changed); /// /// /// // Merge in the revocation certificate. /// assert_eq!(cert.primary_key().self_revocations().count(), 0); /// let (cert, changed) = cert.insert_packets(rev)?; /// assert!(changed); /// assert_eq!(cert.primary_key().self_revocations().count(), 1); /// /// /// // Add an unknown packet. /// let tag = Tag::Private(61.into()); /// let unknown = Unknown::new(tag, /// openpgp::Error::UnsupportedPacketType(tag).into()); /// /// // It shows up as an unknown component. /// let (cert, changed) = cert.insert_packets(unknown)?; /// assert!(changed); /// assert_eq!(cert.unknowns().count(), 1); /// for p in cert.unknowns() { /// assert_eq!(p.unknown().tag(), tag); /// } /// /// /// // Try and merge a literal data packet. /// let mut lit = Literal::new(DataFormat::Unicode); /// lit.set_body(b"test".to_vec()); /// /// // Merging packets that are known to not belong to a /// // certificate result in an error. /// assert!(cert.insert_packets(lit).is_err()); /// # Ok(()) /// # } /// ``` /// /// Remove secret key material: /// /// ``` /// use sequoia_openpgp as openpgp; /// use openpgp::cert::prelude::*; /// use openpgp::packet::prelude::*; /// /// # fn main() -> openpgp::Result<()> { /// // Create a new key. /// let (cert, _) = /// CertBuilder::general_purpose(Some("alice@example.org")) /// .generate()?; /// assert!(cert.is_tsk()); /// /// // We just created the key, so all the keys have secret key /// // material. /// let mut pk = cert.primary_key().key().clone(); /// /// // Split off the secret key material. /// let (pk, sk) = pk.take_secret(); /// assert!(sk.is_some()); /// assert!(! pk.has_secret()); /// /// // Merge in the public key. Recall: the packets that are /// // being merged into the certificate take precedence. /// let (cert, changed) = cert.insert_packets(pk)?; /// assert!(changed); /// /// // The secret key material is stripped. /// assert!(! cert.primary_key().key().has_secret()); /// # Ok(()) /// # } /// ``` /// /// Update a binding signature's unhashed subpacket area: /// /// ``` /// # fn main() -> sequoia_openpgp::Result<()> { /// use sequoia_openpgp as openpgp; /// use openpgp::cert::prelude::*; /// use openpgp::packet::prelude::*; /// use openpgp::packet::signature::subpacket::*; /// /// // Create a new key. /// let (cert, _) = /// CertBuilder::general_purpose(Some("alice@example.org")) /// .generate()?; /// assert_eq!(cert.userids().nth(0).unwrap().self_signatures().count(), 1); /// /// // Grab the binding signature so that we can modify it. /// let mut sig = /// cert.userids().nth(0).unwrap().self_signatures().nth(0) /// .unwrap().clone(); /// /// // Add a notation subpacket. Note that the information is not /// // authenticated, therefore it may only be trusted if the /// // certificate with the signature is placed in a trusted store. /// let notation = NotationData::new("retrieved-from@example.org", /// "generated-locally", /// NotationDataFlags::empty() /// .set_human_readable()); /// sig.unhashed_area_mut().add( /// Subpacket::new(SubpacketValue::NotationData(notation), false)?)?; /// /// // Merge in the signature. Recall: the packets that are /// // being merged into the certificate take precedence. /// let (cert, changed) = cert.insert_packets(sig)?; /// assert!(changed); /// /// // The old binding signature is replaced. /// assert_eq!(cert.userids().nth(0).unwrap().self_signatures().count(), 1); /// assert_eq!(cert.userids().nth(0).unwrap().self_signatures().nth(0) /// .unwrap() /// .unhashed_area() /// .subpackets(SubpacketTag::NotationData).count(), 1); /// # Ok(()) } /// ``` pub fn insert_packets<I>(self, packets: I) -> Result<(Self, bool)> where I: IntoIterator, I::Item: Into<Packet>, { self.insert_packets_merge(packets, |_old, new| Ok(new)) } /// Adds packets to the certificate with an explicit merge policy. /// /// Like [`Cert::insert_packets`], but also takes a function that /// will be called on inserts and replacements that can be used to /// log changes to the certificate, and to influence how packets /// are merged. The merge function takes two parameters, an /// optional existing packet, and the packet to be merged in. /// /// If a new packet is inserted, there is no packet currently in /// the certificate. Hence, the first parameter to the merge /// function is `None`. /// /// If an existing packet is updated, there is a packet currently /// in the certificate that matches the given packet. Hence, the /// first parameter to the merge function is /// `Some(existing_packet)`. /// /// Both packets given to the merge function are considered equal /// when considering the normalized form (only comparing public /// key parameters and ignoring unhashed signature subpackets, see /// [`Packet::normalized_hash`]). It must return a packet that /// equals the input packet. In practice that means that the /// merge function returns either the old packet, the new packet, /// or a combination of both packets. If the merge function /// returns a different packet, this function returns /// [`Error::InvalidOperation`]. /// /// If the merge function returns the existing packet, this /// function will still consider this as a change to the /// certificate. In other words, it may return that the /// certificate has changed even if the serialized representation /// has not changed. /// /// # Examples /// /// In the first example, we give an explicit merge function that /// just returns the new packet. This policy prefers the new /// packet. This is the policy used by [`Cert::insert_packets`]. /// /// ``` /// use sequoia_openpgp as openpgp; /// use openpgp::crypto::Password; /// use openpgp::cert::prelude::CertBuilder; /// /// # fn main() -> openpgp::Result<()> { /// let p0 = Password::from("old password"); /// let p1 = Password::from("new password"); /// /// // Create a new key. /// let (cert, rev) = /// CertBuilder::general_purpose(Some("alice@example.org")) /// .set_password(Some(p0.clone())) /// .generate()?; /// assert!(cert.is_tsk()); /// /// // Change the password for the primary key. /// let pk = cert.primary_key().key().clone().parts_into_secret()? /// .decrypt_secret(&p0)? /// .encrypt_secret(&p1)?; /// /// // Merge it back in, with a policy projecting to the new packet. /// let (cert, changed) = /// cert.insert_packets_merge(pk, |_old, new| Ok(new))?; /// assert!(changed); /// /// // Make sure we can still decrypt the primary key using the /// // new password. /// assert!(cert.primary_key().key().clone().parts_into_secret()? /// .decrypt_secret(&p1).is_ok()); /// # Ok(()) } /// ``` /// /// In the second example, we give an explicit merge function that /// returns the old packet if given, falling back to the new /// packet, if not. This policy prefers the existing packets. /// /// ``` /// use sequoia_openpgp as openpgp; /// use openpgp::crypto::Password; /// use openpgp::cert::prelude::CertBuilder; /// /// # fn main() -> openpgp::Result<()> { /// let p0 = Password::from("old password"); /// let p1 = Password::from("new password"); /// /// // Create a new key. /// let (cert, rev) = /// CertBuilder::general_purpose(Some("alice@example.org")) /// .set_password(Some(p0.clone())) /// .generate()?; /// assert!(cert.is_tsk()); /// /// // Change the password for the primary key. /// let pk = cert.primary_key().key().clone().parts_into_secret()? /// .decrypt_secret(&p0)? /// .encrypt_secret(&p1)?; /// /// // Merge it back in, with a policy preferring the old packet. /// let (cert, changed) = /// cert.insert_packets_merge(pk, |old, new| Ok(old.unwrap_or(new)))?; /// assert!(changed); // Overestimates changes. /// /// // Make sure we can still decrypt the primary key using the /// // old password. /// assert!(cert.primary_key().key().clone().parts_into_secret()? /// .decrypt_secret(&p0).is_ok()); /// # Ok(()) } /// ``` pub fn insert_packets_merge<P, I>(self, packets: P, merge: I) -> Result<(Self, bool)> where P: IntoIterator, P::Item: Into<Packet>, I: FnMut(Option<Packet>, Packet) -> Result<Packet>, { self.insert_packets_(&mut packets.into_iter().map(Into::into), Box::new(merge)) } /// Adds packets to the certificate with an explicit merge policy. /// /// This implements all the Cert::insert_packets* functions. Its /// arguments `packets` and `merge` use dynamic dispatch so that /// we avoid the cost of monomorphization. fn insert_packets_<'a>(self, packets: &mut dyn Iterator<Item = Packet>, mut merge: Box<dyn FnMut(Option<Packet>, Packet) -> Result<Packet> + 'a>) -> Result<(Self, bool)> { let mut changed = false; let mut combined = self.as_tsk().into_packets().collect::<Vec<_>>(); // Hashes a packet ignoring the unhashed subpacket area and // any secret key material. let hash_packet = |p: &Packet| -> u64 { let mut hasher = DefaultHasher::new(); p.normalized_hash(&mut hasher); hasher.finish() }; // BTreeMap of (hash) -> Vec<index in combined>. // // We don't use a HashMap, because the key would be a // reference to the packets in combined, which would prevent // us from modifying combined. // // Note: we really don't want to dedup components now, because // we want to keep signatures immediately after their // components. let mut packet_map: BTreeMap<u64, Vec<usize>> = BTreeMap::new(); for (i, p) in combined.iter().enumerate() { match packet_map.entry(hash_packet(p)) { Entry::Occupied(mut oe) => { oe.get_mut().push(i) } Entry::Vacant(ve) => { ve.insert(vec![ i ]); } } } enum Action { Drop, Overwrite(usize), Insert, } use Action::*; // Now we merge in the new packets. for p in packets { Cert::valid_packet(&p)?; let hash = hash_packet(&p); let mut action = Insert; if let Some(combined_i) = packet_map.get(&hash) { for i in combined_i { let i: usize = *i; let (same, identical) = match (&p, &combined[i]) { // For keys, only compare the public bits. If // they match, then we keep whatever is in the // new key. (Packet::PublicKey(a), Packet::PublicKey(b)) => (a.public_cmp(b) == Ordering::Equal, a == b), (Packet::SecretKey(a), Packet::SecretKey(b)) => (a.public_cmp(b) == Ordering::Equal, a == b), (Packet::PublicKey(a), Packet::SecretKey(b)) => (a.public_cmp(b) == Ordering::Equal, false), (Packet::SecretKey(a), Packet::PublicKey(b)) => (a.public_cmp(b) == Ordering::Equal, false), (Packet::PublicSubkey(a), Packet::PublicSubkey(b)) => (a.public_cmp(b) == Ordering::Equal, a == b), (Packet::SecretSubkey(a), Packet::SecretSubkey(b)) => (a.public_cmp(b) == Ordering::Equal, a == b), (Packet::PublicSubkey(a), Packet::SecretSubkey(b)) => (a.public_cmp(b) == Ordering::Equal, false), (Packet::SecretSubkey(a), Packet::PublicSubkey(b)) => (a.public_cmp(b) == Ordering::Equal, false), // For signatures, don't compare the unhashed // subpacket areas. If it's the same // signature, then we keep what is the new // signature's unhashed subpacket area. (Packet::Signature(a), Packet::Signature(b)) => (a.normalized_eq(b), a == b), (a, b) => { let identical = a == b; (identical, identical) } }; if same { if identical { action = Drop; } else { action = Overwrite(i); } break; } } } match action { Drop => (), Overwrite(i) => { // Existing packet. let existing = std::mem::replace(&mut combined[i], Packet::Marker(Default::default())); let merged = merge(Some(existing), p)?; let merged_hash = hash_packet(&merged); if hash != merged_hash { return Err(Error::InvalidOperation( format!("merge function changed packet hash \ (expected: {}, got: {})", hash, merged_hash)).into()); } combined[i] = merged; changed = true; }, Insert => { // New packet. let merged = merge(None, p)?; let merged_hash = hash_packet(&merged); if hash != merged_hash { return Err(Error::InvalidOperation( format!("merge function changed packet hash \ (expected: {}, got: {})", hash, merged_hash)).into()); } // Add it to combined. combined.push(merged); changed = true; // Because the caller might insert the same packet // multiple times, we need to also add it to // packet_map. let i = combined.len() - 1; match packet_map.entry(hash) { Entry::Occupied(mut oe) => { oe.get_mut().push(i) } Entry::Vacant(ve) => { ve.insert(vec![ i ]); } } } } } Cert::try_from(combined).map(|cert| (cert, changed)) } /// Returns whether at least one of the keys includes secret /// key material. /// /// This returns true if either the primary key or at least one of /// the subkeys includes secret key material. /// /// # Examples /// /// ``` /// use sequoia_openpgp as openpgp; /// use openpgp::cert::prelude::*; /// use openpgp::policy::StandardPolicy; /// use openpgp::serialize::Serialize; /// use openpgp::parse::Parse; /// /// # fn main() -> openpgp::Result<()> { /// let p = &StandardPolicy::new(); /// /// // Create a new key. /// let (cert, _) = /// CertBuilder::general_purpose(Some("alice@example.org")) /// .generate()?; /// assert!(cert.is_tsk()); /// /// // If we serialize the certificate, the secret key material is /// // stripped, unless we first convert it to a TSK. /// /// let mut buffer = Vec::new(); /// cert.as_tsk().serialize(&mut buffer); /// let cert = Cert::from_bytes(&buffer)?; /// assert!(cert.is_tsk()); /// /// // Now round trip it without first converting it to a TSK. This /// // drops the secret key material. /// let mut buffer = Vec::new(); /// cert.serialize(&mut buffer); /// let cert = Cert::from_bytes(&buffer)?; /// assert!(!cert.is_tsk()); /// # Ok(()) /// # } /// ``` pub fn is_tsk(&self) -> bool { if self.primary_key().has_secret() { return true; } self.keys().subkeys().any(|sk| { sk.key().has_secret() }) } /// Strips any secret key material. /// /// # Examples /// /// ``` /// use sequoia_openpgp as openpgp; /// use openpgp::cert::prelude::*; /// /// # fn main() -> openpgp::Result<()> { /// /// // Create a new key. /// let (cert, _) = /// CertBuilder::general_purpose(Some("alice@example.org")) /// .generate()?; /// assert!(cert.is_tsk()); /// /// let cert = cert.strip_secret_key_material(); /// assert!(! cert.is_tsk()); /// # Ok(()) /// # } /// ``` pub fn strip_secret_key_material(mut self) -> Cert { self.primary.key_mut().steal_secret(); self.subkeys.iter_mut().for_each(|sk| { sk.key_mut().steal_secret(); }); self } /// Retains only the userids specified by the predicate. /// /// Removes all the userids for which the given predicate returns /// false. /// /// # Warning /// /// Because userid binding signatures are traditionally used to /// provide additional information like the certificate holder's /// algorithm preferences (see [`Preferences`]) and primary key /// flags (see [`ValidKeyAmalgamation::key_flags`]). Removing a /// userid may inadvertently change this information. /// /// [`ValidKeyAmalgamation::key_flags`]: amalgamation::key::ValidKeyAmalgamation::key_flags() /// /// # Examples /// /// ``` /// # fn main() -> sequoia_openpgp::Result<()> { /// use sequoia_openpgp as openpgp; /// use openpgp::cert::prelude::*; /// /// // Create a new key. /// let (cert, _) = /// CertBuilder::general_purpose(Some("alice@example.org")) /// .add_userid("Alice Lovelace <alice@lovelace.name>") /// .generate()?; /// assert_eq!(cert.userids().count(), 2); /// /// let cert = cert.retain_userids(|ua| { /// if let Ok(Some(address)) = ua.userid().email() { /// address == "alice@example.org" // Only keep this one. /// } else { /// false // Drop malformed userids. /// } /// }); /// assert_eq!(cert.userids().count(), 1); /// assert_eq!(cert.userids().nth(0).unwrap().userid().email()?.unwrap(), /// "alice@example.org"); /// # Ok(()) } /// ``` pub fn retain_userids<P>(mut self, mut predicate: P) -> Cert where P: FnMut(UserIDAmalgamation) -> bool, { let mut keep = vec![false; self.userids.len()]; for (i, a) in self.userids().enumerate() { keep[i] = predicate(a); } // Note: Vec::retain visits the elements in the original // order. let mut keep = keep.iter(); self.userids.retain(|_| *keep.next().unwrap()); self } /// Retains only the user attributes specified by the predicate. /// /// Removes all the user attributes for which the given predicate /// returns false. /// /// # Examples /// /// ``` /// # fn main() -> sequoia_openpgp::Result<()> { /// use sequoia_openpgp as openpgp; /// use openpgp::cert::prelude::*; /// /// // Create a new key. /// let (cert, _) = /// CertBuilder::general_purpose(Some("alice@example.org")) /// // Add nonsensical user attribute. /// .add_user_attribute(vec![0, 1, 2]) /// .generate()?; /// assert_eq!(cert.user_attributes().count(), 1); /// /// // Strip all user attributes /// let cert = cert.retain_user_attributes(|_| false); /// assert_eq!(cert.user_attributes().count(), 0); /// # Ok(()) } /// ``` pub fn retain_user_attributes<P>(mut self, mut predicate: P) -> Cert where P: FnMut(UserAttributeAmalgamation) -> bool, { let mut keep = vec![false; self.user_attributes.len()]; for (i, a) in self.user_attributes().enumerate() { keep[i] = predicate(a); } // Note: Vec::retain visits the elements in the original // order. let mut keep = keep.iter(); self.user_attributes.retain(|_| *keep.next().unwrap()); self } /// Retains only the subkeys specified by the predicate. /// /// Removes all the subkeys for which the given predicate returns /// false. /// /// # Examples /// /// ``` /// # fn main() -> sequoia_openpgp::Result<()> { /// use sequoia_openpgp as openpgp; /// use openpgp::policy::StandardPolicy; /// use openpgp::cert::prelude::*; /// /// // Create a new key. /// let (cert, _) = /// CertBuilder::new() /// .add_userid("Alice Lovelace <alice@lovelace.name>") /// .add_transport_encryption_subkey() /// .add_storage_encryption_subkey() /// .generate()?; /// assert_eq!(cert.keys().subkeys().count(), 2); /// /// // Retain only the transport encryption subkey. For that, we /// // need to examine the key flags, therefore we need to turn /// // the `KeyAmalgamation` into a `ValidKeyAmalgamation` under a /// // policy. /// let p = &StandardPolicy::new(); /// let cert = cert.retain_subkeys(|ka| { /// if let Ok(vka) = ka.with_policy(p, None) { /// vka.key_flags().map(|flags| flags.for_transport_encryption()) /// .unwrap_or(false) // Keep transport encryption keys. /// } else { /// false // Drop unbound keys. /// } /// }); /// assert_eq!(cert.keys().subkeys().count(), 1); /// assert!(cert.with_policy(p, None)?.keys().subkeys().nth(0).unwrap() /// .key_flags().unwrap().for_transport_encryption()); /// # Ok(()) } /// ``` pub fn retain_subkeys<P>(mut self, mut predicate: P) -> Cert where P: FnMut(SubordinateKeyAmalgamation<crate::packet::key::PublicParts>) -> bool, { let mut keep = vec![false; self.subkeys.len()]; for (i, a) in self.keys().subkeys().enumerate() { keep[i] = predicate(a); } // Note: Vec::retain visits the elements in the original // order. let mut keep = keep.iter(); self.subkeys.retain(|_| *keep.next().unwrap()); self } /// Associates a policy and a reference time with the certificate. /// /// This is used to turn a `Cert` into a /// [`ValidCert`]. (See also [`ValidateAmalgamation`], /// which does the same for component amalgamations.) /// /// A certificate is considered valid if: /// /// - It has a self signature that is live at time `t`. /// /// - The policy considers it acceptable. /// /// This doesn't say anything about whether the certificate itself /// is alive (see [`ValidCert::alive`]) or revoked (see /// [`ValidCert::revocation_status`]). /// /// [`ValidateAmalgamation`]: amalgamation::ValidateAmalgamation /// [`ValidCert::alive`]: ValidCert::alive() /// [`ValidCert::revocation_status`]: ValidCert::revocation_status() /// /// # Examples /// /// ``` /// use sequoia_openpgp as openpgp; /// # use openpgp::cert::prelude::*; /// use openpgp::policy::StandardPolicy; /// /// # fn main() -> openpgp::Result<()> { /// let p = &StandardPolicy::new(); /// /// # let (cert, _) = /// # CertBuilder::general_purpose(Some("alice@example.org")) /// # .generate()?; /// let vc = cert.with_policy(p, None)?; /// # assert!(std::ptr::eq(vc.policy(), p)); /// # Ok(()) /// # } /// ``` pub fn with_policy<'a, T>(&'a self, policy: &'a dyn Policy, time: T) -> Result<ValidCert<'a>> where T: Into<Option<time::SystemTime>>, { let time = time.into().unwrap_or_else(crate::now); self.primary_key().with_policy(policy, time)?; Ok(ValidCert { cert: self, policy, time, }) } } use crate::serialize::TSK; impl<'a> TSK<'a> { /// Converts the certificate into an iterator over a sequence of /// packets. /// /// This function emits secret key packets, modulo the keys that /// are filtered (see [`TSK::set_filter`]). If requested, missing /// secret key material is replaced by stubs (see /// [`TSK::emit_secret_key_stubs`]). /// /// # Examples /// /// ``` /// # use sequoia_openpgp as openpgp; /// # use openpgp::cert::prelude::*; /// # use openpgp::serialize::{Serialize, SerializeInto}; /// # /// # fn main() -> openpgp::Result<()> { /// # let (cert, _) = /// # CertBuilder::general_purpose(Some("alice@example.org")) /// # .generate()?; /// assert!(cert.is_tsk()); /// let a = cert.as_tsk().to_vec()?; /// let mut b = Vec::new(); /// cert.into_tsk().into_packets() /// .for_each(|p| p.serialize(&mut b).unwrap()); /// assert_eq!(a, b); /// # Ok(()) } /// ``` pub fn into_packets(self) -> impl Iterator<Item=Packet> + Send + Sync + 'a { /// Strips the secret key material if the filter rejects it, /// and optionally inserts secret key stubs. fn rewrite<'a>( filter: &Box<dyn Fn(&key::UnspecifiedSecret) -> bool + Send + Sync + 'a>, emit_secret_key_stubs: bool, mut p: impl Iterator<Item=Packet> + Send + Sync) -> impl Iterator<Item=Packet> + Send + Sync { let k: Packet = match p.next().unwrap() { Packet::PublicKey(mut k) => { if ! k.role_as_unspecified().parts_as_secret() .map(|k| (filter)(k)) .unwrap_or(false) { k = k.take_secret().0; } if ! k.has_secret() && emit_secret_key_stubs { k = TSK::add_stub(k).into(); } if k.has_secret() { Packet::SecretKey(k.parts_into_secret().unwrap()) } else { Packet::PublicKey(k) } } Packet::PublicSubkey(mut k) => { if ! k.role_as_unspecified().parts_as_secret() .map(|k| (filter)(k)) .unwrap_or(false) { k = k.take_secret().0; } if ! k.has_secret() && emit_secret_key_stubs { k = TSK::add_stub(k).into(); } if k.has_secret() { Packet::SecretSubkey(k.parts_into_secret().unwrap()) } else { Packet::PublicSubkey(k) } } _ => unreachable!(), }; std::iter::once(k).chain(p) } let (cert, filter, emit_secret_key_stubs) = self.decompose(); let cert = cert.into_owned(); rewrite(&filter, emit_secret_key_stubs, cert.primary.into_packets()) .chain(cert.userids.into_iter().flat_map(|b| b.into_packets())) .chain(cert.user_attributes.into_iter().flat_map(|b| b.into_packets())) .chain(cert.subkeys.into_iter().flat_map( move |b| rewrite(&filter, emit_secret_key_stubs, b.into_packets()))) .chain(cert.unknowns.into_iter().flat_map(|b| b.into_packets())) .chain(cert.bad.into_iter().map(|s| s.into())) } } impl TryFrom<PacketParserResult<'_>> for Cert { type Error = anyhow::Error; /// Returns the Cert found in the packet stream. /// /// If the sequence contains multiple certificates (i.e., it is a /// keyring), or the certificate is followed by an invalid packet /// this function will fail. To parse keyrings, use /// [`CertParser`] instead of this function. fn try_from(ppr: PacketParserResult) -> Result<Self> { let mut parser = parser::CertParser::from(ppr); if let Some(cert_result) = parser.next() { if parser.next().is_some() { Err(Error::MalformedCert( "Additional packets found, is this a keyring?".into() ).into()) } else { cert_result } } else { Err(Error::MalformedCert("No data".into()).into()) } } } impl TryFrom<Vec<Packet>> for Cert { type Error = anyhow::Error; fn try_from(p: Vec<Packet>) -> Result<Self> { Cert::from_packets(p.into_iter()) } } impl TryFrom<Packet> for Cert { type Error = anyhow::Error; fn try_from(p: Packet) -> Result<Self> { Cert::from_packets(std::iter::once(p)) } } impl TryFrom<PacketPile> for Cert { type Error = anyhow::Error; /// Returns the certificate found in the `PacketPile`. /// /// If the [`PacketPile`] does not start with a certificate /// (specifically, if it does not start with a primary key /// packet), then this fails. /// /// If the sequence contains multiple certificates (i.e., it is a /// keyring), or the certificate is followed by an invalid packet /// this function will fail. To parse keyrings, use /// [`CertParser`] instead of this function. /// /// # Examples /// /// ``` /// use sequoia_openpgp as openpgp; /// use openpgp::cert::prelude::*; /// use openpgp::packet::prelude::*; /// use openpgp::PacketPile; /// use std::convert::TryFrom; /// /// # fn main() -> openpgp::Result<()> { /// let (cert, rev) = /// CertBuilder::general_purpose(Some("alice@example.org")) /// .generate()?; /// /// // We should be able to turn a certificate into a PacketPile /// // and back. /// let pp : PacketPile = cert.into(); /// assert!(Cert::try_from(pp).is_ok()); /// /// // But a revocation certificate is not a certificate, so this /// // will fail. /// let pp : PacketPile = Packet::from(rev).into(); /// assert!(Cert::try_from(pp).is_err()); /// # Ok(()) /// # } /// ``` fn try_from(p: PacketPile) -> Result<Self> { Self::from_packets(p.into_children()) } } /// A `Cert` plus a `Policy` and a reference time. /// /// A `ValidCert` combines a [`Cert`] with a [`Policy`] and a /// reference time. This allows it to implement methods that require /// a `Policy` and a reference time without requiring the caller to /// explicitly pass them in. Embedding them in the `ValidCert` data /// structure rather than having the caller pass them in explicitly /// helps ensure that multipart operations, even those that span /// multiple functions, use the same `Policy` and reference time. /// This avoids a subtle class of bugs in which different views of a /// certificate are unintentionally used. /// /// A `ValidCert` is typically obtained by transforming a `Cert` using /// [`Cert::with_policy`]. /// /// A `ValidCert` is guaranteed to have a valid and live binding /// signature at the specified reference time. Note: this only means /// that the binding signature is live; it says nothing about whether /// the certificate or any component is live. If you care about those /// things, then you need to check them separately. /// /// [`Policy`]: crate::policy::Policy /// [`Cert::with_policy`]: Cert::with_policy() /// /// # Examples /// /// ``` /// use sequoia_openpgp as openpgp; /// # use openpgp::cert::prelude::*; /// use openpgp::policy::StandardPolicy; /// /// # fn main() -> openpgp::Result<()> { /// let p = &StandardPolicy::new(); /// /// # let (cert, _) = CertBuilder::new() /// # .add_userid("Alice") /// # .add_signing_subkey() /// # .add_transport_encryption_subkey() /// # .generate()?; /// let vc = cert.with_policy(p, None)?; /// # assert!(std::ptr::eq(vc.policy(), p)); /// # Ok(()) } /// ``` #[derive(Debug, Clone)] pub struct ValidCert<'a> { cert: &'a Cert, policy: &'a dyn Policy, // The reference time. time: time::SystemTime, } assert_send_and_sync!(ValidCert<'_>); impl<'a> fmt::Display for ValidCert<'a> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{}", self.cert().fingerprint()) } } impl<'a> ValidCert<'a> { /// Returns the underlying certificate. /// /// # Examples /// /// ``` /// use sequoia_openpgp as openpgp; /// # use openpgp::cert::prelude::*; /// use openpgp::policy::StandardPolicy; /// /// # fn main() -> openpgp::Result<()> { /// let p = &StandardPolicy::new(); /// /// # let (cert, _) = CertBuilder::new() /// # .add_userid("Alice") /// # .add_signing_subkey() /// # .add_transport_encryption_subkey() /// # .generate()?; /// let vc = cert.with_policy(p, None)?; /// assert!(std::ptr::eq(vc.cert(), &cert)); /// # assert!(std::ptr::eq(vc.policy(), p)); /// # Ok(()) } /// ``` pub fn cert(&self) -> &'a Cert { self.cert } /// Returns the associated reference time. /// /// # Examples /// /// ``` /// # use std::time::{SystemTime, Duration, UNIX_EPOCH}; /// # /// use sequoia_openpgp as openpgp; /// # use openpgp::cert::prelude::*; /// use openpgp::policy::StandardPolicy; /// /// # fn main() -> openpgp::Result<()> { /// let p = &StandardPolicy::new(); /// /// let t = UNIX_EPOCH + Duration::from_secs(1307732220); /// # let (cert, _) = /// # CertBuilder::general_purpose(Some("alice@example.org")) /// # .set_creation_time(t) /// # .generate()?; /// let vc = cert.with_policy(p, t)?; /// assert_eq!(vc.time(), t); /// # Ok(()) /// # } /// ``` pub fn time(&self) -> time::SystemTime { self.time } /// Returns the associated policy. /// /// # Examples /// /// ``` /// use sequoia_openpgp as openpgp; /// # use openpgp::cert::prelude::*; /// use openpgp::policy::StandardPolicy; /// /// # fn main() -> openpgp::Result<()> { /// let p = &StandardPolicy::new(); /// /// # let (cert, _) = /// # CertBuilder::general_purpose(Some("alice@example.org")) /// # .generate()?; /// let vc = cert.with_policy(p, None)?; /// assert!(std::ptr::eq(vc.policy(), p)); /// # Ok(()) /// # } /// ``` pub fn policy(&self) -> &'a dyn Policy { self.policy } /// Changes the associated policy and reference time. /// /// If `time` is `None`, the current time is used. /// /// Returns an error if the certificate is not valid for the given /// policy at the specified time. /// /// # Examples /// /// ``` /// use sequoia_openpgp as openpgp; /// # use openpgp::cert::prelude::*; /// use openpgp::policy::{StandardPolicy, NullPolicy}; /// /// # fn main() -> openpgp::Result<()> { /// # let (cert, _) = /// # CertBuilder::general_purpose(Some("alice@example.org")) /// # .generate()?; /// let sp = &StandardPolicy::new(); /// let vc = cert.with_policy(sp, None)?; /// /// // ... /// /// // Now with a different policy. /// let np = unsafe { &NullPolicy::new() }; /// let vc = vc.with_policy(np, None)?; /// # Ok(()) /// # } /// ``` pub fn with_policy<T>(self, policy: &'a dyn Policy, time: T) -> Result<ValidCert<'a>> where T: Into<Option<time::SystemTime>>, { self.cert.with_policy(policy, time) } /// Returns the certificate's direct key signature as of the /// reference time. /// /// Subpackets on direct key signatures apply to all components of /// the certificate, cf. [Section 5.2.3.10 of RFC 9580]. /// /// [Section 5.2.3.10 of RFC 9580]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.10 /// /// # Examples /// /// ``` /// use sequoia_openpgp as openpgp; /// # use openpgp::cert::prelude::*; /// use sequoia_openpgp::policy::StandardPolicy; /// /// # fn main() -> openpgp::Result<()> { /// let p = &StandardPolicy::new(); /// /// # let (cert, _) = CertBuilder::new() /// # .add_userid("Alice") /// # .add_signing_subkey() /// # .add_transport_encryption_subkey() /// # .generate()?; /// let vc = cert.with_policy(p, None)?; /// println!("{:?}", vc.direct_key_signature()); /// # assert!(vc.direct_key_signature().is_ok()); /// # Ok(()) } /// ``` pub fn direct_key_signature(&self) -> Result<&'a Signature> { self.cert.primary.binding_signature(self.policy(), self.time()) } /// Returns the certificate's revocation status. /// /// A certificate is considered revoked at time `t` if: /// /// - There is a valid and live revocation at time `t` that is /// newer than all valid and live self signatures at time `t`, /// or /// /// - There is a valid [hard revocation] (even if it is not live /// at time `t`, and even if there is a newer self signature). /// /// [hard revocation]: crate::types::RevocationType::Hard /// /// Note: certificates and subkeys have different revocation /// criteria from [User IDs] and [User Attributes]. /// // Pending https://github.com/rust-lang/rust/issues/85960, should be // [User IDs]: bundle::ComponentBundle<UserID>::revocation_status // [User Attributes]: bundle::ComponentBundle<UserAttribute>::revocation_status /// [User IDs]: bundle::ComponentBundle#method.revocation_status-1 /// [User Attributes]: bundle::ComponentBundle#method.revocation_status-2 /// /// # Examples /// /// ``` /// use sequoia_openpgp as openpgp; /// use openpgp::cert::prelude::*; /// use openpgp::types::RevocationStatus; /// use openpgp::policy::StandardPolicy; /// /// # fn main() -> openpgp::Result<()> { /// let p = &StandardPolicy::new(); /// /// let (cert, rev) = /// CertBuilder::general_purpose(Some("alice@example.org")) /// .generate()?; /// /// // Not revoked. /// assert_eq!(cert.with_policy(p, None)?.revocation_status(), /// RevocationStatus::NotAsFarAsWeKnow); /// /// // Merge the revocation certificate. `cert` is now considered /// // to be revoked. /// let cert = cert.insert_packets(rev.clone())?.0; /// assert_eq!(cert.with_policy(p, None)?.revocation_status(), /// RevocationStatus::Revoked(vec![&rev.into()])); /// # Ok(()) /// # } /// ``` pub fn revocation_status(&self) -> RevocationStatus<'a> { self.cert.revocation_status(self.policy, self.time) } /// Returns whether the certificate is alive at the /// reference time. /// /// A certificate is considered to be alive at time `t` if the /// primary key is alive at time `t`. /// /// A valid certificate's primary key is guaranteed to have [a live /// binding signature], however, that does not mean that the /// [primary key is necessarily alive]. /// /// [a live binding signature]: amalgamation::ValidateAmalgamation /// [primary key is necessarily alive]: amalgamation::key::ValidKeyAmalgamation::alive() /// /// # Examples /// /// ``` /// use std::time; /// use sequoia_openpgp as openpgp; /// use openpgp::cert::prelude::*; /// use openpgp::policy::StandardPolicy; /// /// # fn main() -> openpgp::Result<()> { /// let p = &StandardPolicy::new(); /// /// let a_second = time::Duration::from_secs(1); /// /// let creation_time = time::SystemTime::now(); /// let before_creation = creation_time - a_second; /// let validity_period = 60 * a_second; /// let expiration_time = creation_time + validity_period; /// let before_expiration_time = expiration_time - a_second; /// let after_expiration_time = expiration_time + a_second; /// /// let (cert, _) = CertBuilder::new() /// .add_userid("Alice") /// .set_creation_time(creation_time) /// .set_validity_period(validity_period) /// .generate()?; /// /// // There is no binding signature before the certificate was created. /// assert!(cert.with_policy(p, before_creation).is_err()); /// assert!(cert.with_policy(p, creation_time)?.alive().is_ok()); /// assert!(cert.with_policy(p, before_expiration_time)?.alive().is_ok()); /// // The binding signature is still alive, but the key has expired. /// assert!(cert.with_policy(p, expiration_time)?.alive().is_err()); /// assert!(cert.with_policy(p, after_expiration_time)?.alive().is_err()); /// # Ok(()) } pub fn alive(&self) -> Result<()> { self.primary_key().alive() } /// Returns the certificate's primary key. /// /// A key's secret key material may be protected with a /// password. In such cases, it needs to be decrypted before it /// can be used to decrypt data or generate a signature. Refer to /// [`Key::decrypt_secret`] for details. /// /// [`Key::decrypt_secret`]: crate::packet::Key::decrypt_secret() /// /// # Examples /// /// ``` /// # use sequoia_openpgp as openpgp; /// # use openpgp::cert::prelude::*; /// # use openpgp::policy::StandardPolicy; /// # /// # fn main() -> openpgp::Result<()> { /// # let p = &StandardPolicy::new(); /// # let (cert, _) = CertBuilder::new() /// # .add_userid("Alice") /// # .generate()?; /// # let vc = cert.with_policy(p, None)?; /// # /// let primary = vc.primary_key(); /// // The certificate's fingerprint *is* the primary key's fingerprint. /// assert_eq!(vc.cert().fingerprint(), primary.key().fingerprint()); /// # Ok(()) } pub fn primary_key(&self) -> ValidPrimaryKeyAmalgamation<'a, key::PublicParts> { self.cert.primary_key().with_policy(self.policy, self.time) .expect("A ValidKeyAmalgamation must have a ValidPrimaryKeyAmalgamation") } /// Returns an iterator over the certificate's valid keys. /// /// That is, this returns an iterator over the primary key and any /// subkeys. /// /// The iterator always returns the primary key first. The order /// of the subkeys is undefined. /// /// To only iterate over the certificate's subkeys, call /// [`ValidKeyAmalgamationIter::subkeys`] on the returned iterator /// instead of skipping the first key: this causes the iterator to /// return values with a more accurate type. /// /// A key's secret key material may be protected with a /// password. In such cases, it needs to be decrypted before it /// can be used to decrypt data or generate a signature. Refer to /// [`Key::decrypt_secret`] for details. /// /// [`ValidKeyAmalgamationIter::subkeys`]: amalgamation::key::ValidKeyAmalgamationIter::subkeys() /// [`Key::decrypt_secret`]: crate::packet::Key::decrypt_secret() /// /// # Examples /// /// ``` /// use sequoia_openpgp as openpgp; /// use openpgp::cert::prelude::*; /// use openpgp::policy::StandardPolicy; /// /// # fn main() -> openpgp::Result<()> { /// let p = &StandardPolicy::new(); /// /// // Create a key with two subkeys: one for signing and one for /// // encrypting data in transit. /// let (cert, _) = CertBuilder::new() /// .add_userid("Alice") /// .add_signing_subkey() /// .add_transport_encryption_subkey() /// .generate()?; /// // They should all be valid. /// assert_eq!(cert.with_policy(p, None)?.keys().count(), 1 + 2); /// # Ok(()) /// # } /// ``` pub fn keys(&self) -> ValidKeyAmalgamationIter<'a, key::PublicParts, key::UnspecifiedRole> { self.cert.keys().with_policy(self.policy, self.time) } /// Returns the primary User ID at the reference time, if any. /// /// A certificate may not have a primary User ID if it doesn't /// have any valid User IDs. If a certificate has at least one /// valid User ID at time `t`, then it has a primary User ID at /// time `t`. /// /// The primary User ID is determined as follows: /// /// - Discard User IDs that are not valid or not alive at time `t`. /// /// - Order the remaining User IDs by whether a User ID does not /// have a valid self-revocation (i.e., non-revoked first, /// ignoring third-party revocations). /// /// - Break ties by ordering by whether the User ID is [marked /// as being the primary User ID]. /// /// - Break ties by ordering by the binding signature's creation /// time, most recent first. /// /// If there are multiple User IDs that are ordered first, then /// one is chosen in a deterministic, but undefined manner /// (currently, we order the value of the User IDs /// lexographically, but you shouldn't rely on this). /// /// [marked as being the primary User ID]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.27 /// /// # Examples /// /// ``` /// use std::time; /// use sequoia_openpgp as openpgp; /// use openpgp::cert::prelude::*; /// use openpgp::packet::prelude::*; /// use openpgp::policy::StandardPolicy; /// /// # fn main() -> openpgp::Result<()> { /// let p = &StandardPolicy::new(); /// /// let t1 = time::SystemTime::now(); /// let t2 = t1 + time::Duration::from_secs(1); /// /// let (cert, _) = CertBuilder::new() /// .set_creation_time(t1) /// .add_userid("Alice") /// .generate()?; /// let mut signer = cert /// .primary_key().key().clone().parts_into_secret()?.into_keypair()?; /// /// // There is only one User ID. It must be the primary User ID. /// let vc = cert.with_policy(p, t1)?; /// let alice = vc.primary_userid().unwrap(); /// assert_eq!(alice.userid().value(), b"Alice"); /// // By default, the primary User ID flag is set. /// assert!(alice.binding_signature().primary_userid().is_some()); /// /// let template: signature::SignatureBuilder /// = alice.binding_signature().clone().into(); /// /// // Add another user id whose creation time is after the /// // existing User ID, and doesn't have the User ID set. /// let sig = template.clone() /// .set_signature_creation_time(t2)? /// .set_primary_userid(false)?; /// let bob: UserID = "Bob".into(); /// let sig = bob.bind(&mut signer, &cert, sig)?; /// let cert = cert.insert_packets(vec![Packet::from(bob), sig.into()])?.0; /// # assert_eq!(cert.userids().count(), 2); /// /// // Alice should still be the primary User ID, because it has the /// // primary User ID flag set. /// let alice = cert.with_policy(p, t2)?.primary_userid().unwrap(); /// assert_eq!(alice.userid().value(), b"Alice"); /// /// /// // Add another User ID, whose binding signature's creation /// // time is after Alice's and also has the primary User ID flag set. /// let sig = template.clone() /// .set_signature_creation_time(t2)?; /// let carol: UserID = "Carol".into(); /// let sig = carol.bind(&mut signer, &cert, sig)?; /// let cert = cert.insert_packets(vec![Packet::from(carol), sig.into()])?.0; /// # assert_eq!(cert.userids().count(), 3); /// /// // It should now be the primary User ID, because it is the /// // newest User ID with the primary User ID bit is set. /// let carol = cert.with_policy(p, t2)?.primary_userid().unwrap(); /// assert_eq!(carol.userid().value(), b"Carol"); /// # Ok(()) } pub fn primary_userid(&self) -> Result<ValidUserIDAmalgamation<'a>> { self.cert.primary_userid_relaxed(self.policy(), self.time(), true) } /// Returns an iterator over the certificate's valid User IDs. /// /// # Examples /// /// ``` /// # use std::time; /// use sequoia_openpgp as openpgp; /// # use openpgp::cert::prelude::*; /// use openpgp::packet::prelude::*; /// use openpgp::policy::StandardPolicy; /// /// # fn main() -> openpgp::Result<()> { /// let p = &StandardPolicy::new(); /// /// # let t0 = time::SystemTime::now() - time::Duration::from_secs(10); /// # let t1 = t0 + time::Duration::from_secs(1); /// # let t2 = t1 + time::Duration::from_secs(1); /// # let (cert, _) = /// # CertBuilder::general_purpose(Some("alice@example.org")) /// # .set_creation_time(t0) /// # .generate()?; /// // `cert` was created at t0. Add a second User ID at t1. /// let userid = UserID::from("alice@example.com"); /// // Use the primary User ID's current binding signature as the /// // basis for the new User ID's binding signature. /// let template : signature::SignatureBuilder /// = cert.with_policy(p, None)? /// .primary_userid()? /// .binding_signature() /// .clone() /// .into(); /// let sig = template.set_signature_creation_time(t1)?; /// let mut signer = cert /// .primary_key().key().clone().parts_into_secret()?.into_keypair()?; /// let binding = userid.bind(&mut signer, &cert, sig)?; /// // Merge it. /// let cert = cert.insert_packets( /// vec![Packet::from(userid), binding.into()])?.0; /// /// // At t0, the new User ID is not yet valid (it doesn't have a /// // binding signature that is live at t0). Thus, it is not /// // returned. /// let vc = cert.with_policy(p, t0)?; /// assert_eq!(vc.userids().count(), 1); /// // But, at t1, we see both User IDs. /// let vc = cert.with_policy(p, t1)?; /// assert_eq!(vc.userids().count(), 2); /// # Ok(()) /// # } /// ``` pub fn userids(&self) -> ValidUserIDAmalgamationIter<'a> { self.cert.userids().with_policy(self.policy, self.time) } /// Returns the primary User Attribute, if any. /// /// If a certificate has any valid User Attributes, then it has a /// primary User Attribute. In other words, it will not have a /// primary User Attribute at time `t` if there are no valid User /// Attributes at time `t`. /// /// The primary User Attribute is determined in the same way as /// the primary User ID. See the documentation of /// [`ValidCert::primary_userid`] for details. /// /// [`ValidCert::primary_userid`]: ValidCert::primary_userid() /// /// # Examples /// /// ``` /// use sequoia_openpgp as openpgp; /// # use openpgp::cert::prelude::*; /// use openpgp::policy::StandardPolicy; /// /// # fn main() -> openpgp::Result<()> { /// let p = &StandardPolicy::new(); /// /// # let (cert, _) = /// # CertBuilder::general_purpose(Some("alice@example.org")) /// # .generate()?; /// let vc = cert.with_policy(p, None)?; /// let ua = vc.primary_user_attribute(); /// # // We don't have a user attributes. So, this should return an /// # // error. /// # assert!(ua.is_err()); /// # Ok(()) /// # } /// ``` pub fn primary_user_attribute(&self) -> Result<ValidComponentAmalgamation<'a, UserAttribute>> { ValidComponentAmalgamation::primary(self.cert, self.cert.user_attributes.iter(), self.policy(), self.time(), true) } /// Returns an iterator over the certificate's valid /// `UserAttribute`s. /// /// # Examples /// /// ``` /// use sequoia_openpgp as openpgp; /// # use openpgp::cert::prelude::*; /// # use openpgp::packet::prelude::*; /// # use openpgp::packet::user_attribute::Subpacket; /// use openpgp::policy::StandardPolicy; /// /// # fn main() -> openpgp::Result<()> { /// let p = &StandardPolicy::new(); /// /// # let (cert, _) = /// # CertBuilder::general_purpose(Some("alice@example.org")) /// # .generate()?; /// # /// # // Create some user attribute. Doctests do not pass cfg(test), /// # // so UserAttribute::arbitrary is not available /// # let sp = Subpacket::Unknown(7, vec![7; 7].into_boxed_slice()); /// # let ua = UserAttribute::new(&[sp]); /// # /// // Add a User Attribute without a self-signature to the certificate. /// let cert = cert.insert_packets(ua)?.0; /// assert_eq!(cert.user_attributes().count(), 1); /// /// // Without a self-signature, it is definitely not valid. /// let vc = cert.with_policy(p, None)?; /// assert_eq!(vc.user_attributes().count(), 0); /// # Ok(()) /// # } /// ``` pub fn user_attributes(&self) -> ValidUserAttributeAmalgamationIter<'a> { self.cert.user_attributes().with_policy(self.policy, self.time) } /// Returns a list of any designated revokers for this certificate. /// /// This function returns the designated revokers listed on the /// primary key's binding signatures and the certificate's direct /// key signatures. /// /// Note: the returned list is deduplicated. /// /// In order to preserve our API during the 1.x series, this /// function takes an optional policy argument. It should be /// `None`, but if it is `Some(_)`, it will be used instead of the /// `ValidCert`'s policy. This makes the function signature /// compatible with [`Cert::revocation_keys`]. /// /// # Examples /// /// ``` /// use sequoia_openpgp as openpgp; /// # use openpgp::Result; /// use openpgp::cert::prelude::*; /// use openpgp::policy::StandardPolicy; /// use openpgp::types::RevocationKey; /// /// # fn main() -> Result<()> { /// let p = &StandardPolicy::new(); /// /// let (alice, _) = /// CertBuilder::general_purpose(Some("alice@example.org")) /// .generate()?; /// // Make Alice a designated revoker for Bob. /// let (bob, _) = /// CertBuilder::general_purpose(Some("bob@example.org")) /// .set_revocation_keys(vec![(&alice).into()]) /// .generate()?; /// /// // Make sure Alice is listed as a designated revoker for Bob. /// assert_eq!(bob.with_policy(p, None)?.revocation_keys() /// .collect::<Vec<&RevocationKey>>(), /// vec![&(&alice).into()]); /// # Ok(()) } /// ``` pub fn revocation_keys(&self) -> impl Iterator<Item = &'a RevocationKey> + 'a { self.cert.revocation_keys(self.policy()) } /// Returns the certificate's fingerprint as a `KeyHandle`. /// /// # Examples /// /// ``` /// # use sequoia_openpgp as openpgp; /// # use openpgp::cert::prelude::*; /// # use openpgp::KeyHandle; /// # use openpgp::policy::StandardPolicy; /// # /// # fn main() -> openpgp::Result<()> { /// let p = &StandardPolicy::new(); /// /// # let (cert, _) = /// # CertBuilder::general_purpose(Some("alice@example.org")) /// # .generate()?; /// # /// println!("{}", cert.with_policy(p, None)?.key_handle()); /// /// // This always returns a fingerprint. /// match cert.with_policy(p, None)?.key_handle() { /// KeyHandle::Fingerprint(_) => (), /// KeyHandle::KeyID(_) => unreachable!(), /// } /// # /// # Ok(()) /// # } /// ``` pub fn key_handle(&self) -> KeyHandle { self.cert().key_handle() } /// Returns the certificate's fingerprint. /// /// # Examples /// /// ``` /// # use sequoia_openpgp as openpgp; /// # use openpgp::cert::prelude::*; /// # use openpgp::policy::StandardPolicy; /// # /// # fn main() -> openpgp::Result<()> { /// let p = &StandardPolicy::new(); /// /// # let (cert, _) = /// # CertBuilder::general_purpose(Some("alice@example.org")) /// # .generate()?; /// # /// println!("{}", cert.with_policy(p, None)?.fingerprint()); /// # /// # Ok(()) /// # } /// ``` pub fn fingerprint(&self) -> Fingerprint { self.cert().fingerprint() } /// Returns the certificate's Key ID. /// /// As a general rule of thumb, you should prefer the fingerprint /// as it is possible to create keys with a colliding Key ID using /// a [birthday attack]. /// /// [birthday attack]: https://nullprogram.com/blog/2019/07/22/ /// /// # Examples /// /// ``` /// # use sequoia_openpgp as openpgp; /// # use openpgp::cert::prelude::*; /// # use openpgp::policy::StandardPolicy; /// # /// # fn main() -> openpgp::Result<()> { /// let p = &StandardPolicy::new(); /// /// # let (cert, _) = /// # CertBuilder::general_purpose(Some("alice@example.org")) /// # .generate()?; /// # /// println!("{}", cert.with_policy(p, None)?.keyid()); /// # /// # Ok(()) /// # } /// ``` pub fn keyid(&self) -> KeyID { self.cert().keyid() } } macro_rules! impl_pref { ($subpacket:ident, $rt:ty) => { #[allow(deprecated)] fn $subpacket(&self) -> Option<$rt> { // When addressed by the fingerprint or keyid, we first // look on the primary User ID and then fall back to the // direct key signature. We need to be careful to handle // the case where there are no User IDs. #[allow(deprecated)] if let Ok(u) = self.primary_userid() { u.$subpacket() } else if let Ok(sig) = self.direct_key_signature() { sig.$subpacket() } else { None } } } } impl<'a> seal::Sealed for ValidCert<'a> {} impl<'a> Preferences<'a> for ValidCert<'a> { impl_pref!(preferred_symmetric_algorithms, &'a [SymmetricAlgorithm]); impl_pref!(preferred_hash_algorithms, &'a [HashAlgorithm]); impl_pref!(preferred_compression_algorithms, &'a [CompressionAlgorithm]); impl_pref!(preferred_aead_ciphersuites, &'a [(SymmetricAlgorithm, AEADAlgorithm)]); impl_pref!(key_server_preferences, KeyServerPreferences); impl_pref!(preferred_key_server, &'a [u8]); impl_pref!(policy_uri, &'a [u8]); impl_pref!(features, Features); } #[cfg(test)] mod test { use std::convert::TryInto; use crate::serialize::Serialize; use crate::policy::StandardPolicy as P; use crate::types::Curve; use crate::packet::signature; use crate::policy::HashAlgoSecurity; use super::*; use crate::{ KeyID, types::KeyFlags, }; fn parse_cert(data: &[u8], as_message: bool) -> Result<Cert> { if as_message { let pile = PacketPile::from_bytes(data).unwrap(); Cert::try_from(pile) } else { Cert::from_bytes(data) } } #[test] fn broken() { use crate::types::Timestamp; for i in 0..2 { let cert = parse_cert(crate::tests::key("testy-broken-no-pk.pgp"), i == 0); assert_match!(Error::MalformedCert(_) = cert.err().unwrap().downcast::<Error>().unwrap()); // According to 4880, a Cert must have a UserID. But, we // don't require it. let cert = parse_cert(crate::tests::key("testy-broken-no-uid.pgp"), i == 0); assert!(cert.is_ok()); // We have: // // [ pk, user id, sig, subkey ] let cert = parse_cert(crate::tests::key("testy-broken-no-sig-on-subkey.pgp"), i == 0).unwrap(); assert_eq!(cert.primary.key().creation_time(), Timestamp::from(1511355130).into()); assert_eq!(cert.userids.len(), 1); assert_eq!(cert.userids[0].userid().value(), &b"Testy McTestface <testy@example.org>"[..]); assert_eq!(cert.userids[0].self_signatures().count(), 1); assert_eq!(cert.userids[0].self_signatures().next().unwrap() .digest_prefix(), &[ 0xc6, 0x8f ]); assert_eq!(cert.user_attributes.len(), 0); assert_eq!(cert.subkeys.len(), 1); } } #[test] fn basics() { use crate::types::Timestamp; for i in 0..2 { let cert = parse_cert(crate::tests::key("testy.pgp"), i == 0).unwrap(); assert_eq!(cert.primary.key().creation_time(), Timestamp::from(1511355130).into()); assert_eq!(format!("{:X}", cert.fingerprint()), "3E8877C877274692975189F5D03F6F865226FE8B"); assert_eq!(cert.userids.len(), 1, "number of userids"); assert_eq!(cert.userids[0].userid().value(), &b"Testy McTestface <testy@example.org>"[..]); assert_eq!(cert.userids[0].self_signatures().count(), 1); assert_eq!(cert.userids[0].self_signatures().next().unwrap() .digest_prefix(), &[ 0xc6, 0x8f ]); assert_eq!(cert.user_attributes.len(), 0); assert_eq!(cert.subkeys.len(), 1, "number of subkeys"); assert_eq!(cert.subkeys[0].key().creation_time(), Timestamp::from(1511355130).into()); assert_eq!(cert.subkeys[0].self_signatures().next().unwrap() .digest_prefix(), &[ 0xb7, 0xb9 ]); let cert = parse_cert(crate::tests::key("testy-no-subkey.pgp"), i == 0).unwrap(); assert_eq!(cert.primary.key().creation_time(), Timestamp::from(1511355130).into()); assert_eq!(format!("{:X}", cert.fingerprint()), "3E8877C877274692975189F5D03F6F865226FE8B"); assert_eq!(cert.user_attributes.len(), 0); assert_eq!(cert.userids.len(), 1, "number of userids"); assert_eq!(cert.userids[0].userid().value(), &b"Testy McTestface <testy@example.org>"[..]); assert_eq!(cert.userids[0].self_signatures().count(), 1); assert_eq!(cert.userids[0].self_signatures().next().unwrap() .digest_prefix(), &[ 0xc6, 0x8f ]); assert_eq!(cert.subkeys.len(), 0, "number of subkeys"); let cert = parse_cert(crate::tests::key("testy.asc"), i == 0).unwrap(); assert_eq!(format!("{:X}", cert.fingerprint()), "3E8877C877274692975189F5D03F6F865226FE8B"); } } #[test] fn only_a_public_key() { // Make sure the Cert parser can parse a key that just consists // of a public key---no signatures, no user ids, nothing. let cert = Cert::from_bytes(crate::tests::key("testy-only-a-pk.pgp")).unwrap(); assert_eq!(cert.userids.len(), 0); assert_eq!(cert.user_attributes.len(), 0); assert_eq!(cert.subkeys.len(), 0); } #[test] fn merge() { use crate::tests::key; let cert_base = Cert::from_bytes(key("bannon-base.gpg")).unwrap(); // When we merge it with itself, we should get the exact same // thing. let merged = cert_base.clone().merge_public_and_secret(cert_base.clone()).unwrap(); assert_eq!(cert_base, merged); let cert_add_uid_1 = Cert::from_bytes(key("bannon-add-uid-1-whitehouse.gov.gpg")) .unwrap(); let cert_add_uid_2 = Cert::from_bytes(key("bannon-add-uid-2-fox.com.gpg")) .unwrap(); // Duplicate user id, but with a different self-sig. let cert_add_uid_3 = Cert::from_bytes(key("bannon-add-uid-3-whitehouse.gov-dup.gpg")) .unwrap(); let cert_all_uids = Cert::from_bytes(key("bannon-all-uids.gpg")) .unwrap(); // We have four User ID packets, but one has the same User ID, // just with a different self-signature. assert_eq!(cert_all_uids.userids.len(), 3); // Merge in order. let merged = cert_base.clone().merge_public_and_secret(cert_add_uid_1.clone()).unwrap() .merge_public_and_secret(cert_add_uid_2.clone()).unwrap() .merge_public_and_secret(cert_add_uid_3.clone()).unwrap(); assert_eq!(cert_all_uids, merged); // Merge in reverse order. let merged = cert_base.clone() .merge_public_and_secret(cert_add_uid_3.clone()).unwrap() .merge_public_and_secret(cert_add_uid_2.clone()).unwrap() .merge_public_and_secret(cert_add_uid_1.clone()).unwrap(); assert_eq!(cert_all_uids, merged); let cert_add_subkey_1 = Cert::from_bytes(key("bannon-add-subkey-1.gpg")).unwrap(); let cert_add_subkey_2 = Cert::from_bytes(key("bannon-add-subkey-2.gpg")).unwrap(); let cert_add_subkey_3 = Cert::from_bytes(key("bannon-add-subkey-3.gpg")).unwrap(); let cert_all_subkeys = Cert::from_bytes(key("bannon-all-subkeys.gpg")).unwrap(); // Merge the first user, then the second, then the third. let merged = cert_base.clone().merge_public_and_secret(cert_add_subkey_1.clone()).unwrap() .merge_public_and_secret(cert_add_subkey_2.clone()).unwrap() .merge_public_and_secret(cert_add_subkey_3.clone()).unwrap(); assert_eq!(cert_all_subkeys, merged); // Merge the third user, then the second, then the first. let merged = cert_base.clone().merge_public_and_secret(cert_add_subkey_3.clone()).unwrap() .merge_public_and_secret(cert_add_subkey_2.clone()).unwrap() .merge_public_and_secret(cert_add_subkey_1.clone()).unwrap(); assert_eq!(cert_all_subkeys, merged); // Merge a lot. let merged = cert_base.clone() .merge_public_and_secret(cert_add_subkey_1.clone()).unwrap() .merge_public_and_secret(cert_add_subkey_1.clone()).unwrap() .merge_public_and_secret(cert_add_subkey_3.clone()).unwrap() .merge_public_and_secret(cert_add_subkey_1.clone()).unwrap() .merge_public_and_secret(cert_add_subkey_2.clone()).unwrap() .merge_public_and_secret(cert_add_subkey_3.clone()).unwrap() .merge_public_and_secret(cert_add_subkey_3.clone()).unwrap() .merge_public_and_secret(cert_add_subkey_1.clone()).unwrap() .merge_public_and_secret(cert_add_subkey_2.clone()).unwrap(); assert_eq!(cert_all_subkeys, merged); let cert_all = Cert::from_bytes(key("bannon-all-uids-subkeys.gpg")) .unwrap(); // Merge all the subkeys with all the uids. let merged = cert_all_subkeys.clone() .merge_public_and_secret(cert_all_uids.clone()).unwrap(); assert_eq!(cert_all, merged); // Merge all uids with all the subkeys. let merged = cert_all_uids.clone() .merge_public_and_secret(cert_all_subkeys.clone()).unwrap(); assert_eq!(cert_all, merged); // All the subkeys and the uids in a mixed up order. let merged = cert_base.clone() .merge_public_and_secret(cert_add_subkey_1.clone()).unwrap() .merge_public_and_secret(cert_add_uid_2.clone()).unwrap() .merge_public_and_secret(cert_add_uid_1.clone()).unwrap() .merge_public_and_secret(cert_add_subkey_3.clone()).unwrap() .merge_public_and_secret(cert_add_subkey_1.clone()).unwrap() .merge_public_and_secret(cert_add_uid_3.clone()).unwrap() .merge_public_and_secret(cert_add_subkey_2.clone()).unwrap() .merge_public_and_secret(cert_add_subkey_1.clone()).unwrap() .merge_public_and_secret(cert_add_uid_2.clone()).unwrap(); assert_eq!(cert_all, merged); // Certifications. let cert_donald_signs_base = Cert::from_bytes(key("bannon-the-donald-signs-base.gpg")) .unwrap(); let cert_donald_signs_all = Cert::from_bytes(key("bannon-the-donald-signs-all-uids.gpg")) .unwrap(); let cert_ivanka_signs_base = Cert::from_bytes(key("bannon-ivanka-signs-base.gpg")) .unwrap(); let cert_ivanka_signs_all = Cert::from_bytes(key("bannon-ivanka-signs-all-uids.gpg")) .unwrap(); assert!(cert_donald_signs_base.userids.len() == 1); assert!(cert_donald_signs_base.userids[0].self_signatures().count() == 1); assert!(cert_base.userids[0].certifications.is_empty()); assert!(cert_donald_signs_base.userids[0].certifications.len() == 1); let merged = cert_donald_signs_base.clone() .merge_public_and_secret(cert_ivanka_signs_base.clone()).unwrap(); assert!(merged.userids.len() == 1); assert!(merged.userids[0].self_signatures().count() == 1); assert!(merged.userids[0].certifications.len() == 2); let merged = cert_donald_signs_base.clone() .merge_public_and_secret(cert_donald_signs_all.clone()).unwrap(); assert!(merged.userids.len() == 3); assert!(merged.userids[0].self_signatures().count() == 1); // There should be two certifications from the Donald on the // first user id. assert!(merged.userids[0].certifications.len() == 2); assert!(merged.userids[1].certifications.len() == 1); assert!(merged.userids[2].certifications.len() == 1); let merged = cert_donald_signs_base.clone() .merge_public_and_secret(cert_donald_signs_all.clone()).unwrap() .merge_public_and_secret(cert_ivanka_signs_base.clone()).unwrap() .merge_public_and_secret(cert_ivanka_signs_all.clone()).unwrap(); assert!(merged.userids.len() == 3); assert!(merged.userids[0].self_signatures().count() == 1); // There should be two certifications from each of the Donald // and Ivanka on the first user id, and one each on the rest. assert!(merged.userids[0].certifications.len() == 4); assert!(merged.userids[1].certifications.len() == 2); assert!(merged.userids[2].certifications.len() == 2); // Same as above, but redundant. let merged = cert_donald_signs_base.clone() .merge_public_and_secret(cert_ivanka_signs_base.clone()).unwrap() .merge_public_and_secret(cert_donald_signs_all.clone()).unwrap() .merge_public_and_secret(cert_donald_signs_all.clone()).unwrap() .merge_public_and_secret(cert_ivanka_signs_all.clone()).unwrap() .merge_public_and_secret(cert_ivanka_signs_base.clone()).unwrap() .merge_public_and_secret(cert_donald_signs_all.clone()).unwrap() .merge_public_and_secret(cert_donald_signs_all.clone()).unwrap() .merge_public_and_secret(cert_ivanka_signs_all.clone()).unwrap(); assert!(merged.userids.len() == 3); assert!(merged.userids[0].self_signatures().count() == 1); // There should be two certifications from each of the Donald // and Ivanka on the first user id, and one each on the rest. assert!(merged.userids[0].certifications.len() == 4); assert!(merged.userids[1].certifications.len() == 2); assert!(merged.userids[2].certifications.len() == 2); } #[test] fn out_of_order_self_sigs_test() { // neal-out-of-order.pgp contains all the self-signatures, // but some are out of order. The canonicalization step // should reorder them. // // original order/new order: // // 1/ 1. pk // 2/ 2. user id #1: neal@walfield.org (good) // 3/ 3. sig over user ID #1 // // 4/ 4. user id #2: neal@gnupg.org (good) // 5/ 7. sig over user ID #3 // 6/ 5. sig over user ID #2 // // 7/ 6. user id #3: neal@g10code.com (bad) // // 8/ 8. user ID #4: neal@pep.foundation (bad) // 9/11. sig over user ID #5 // // 10/10. user id #5: neal@pep-project.org (bad) // 11/ 9. sig over user ID #4 // // 12/12. user ID #6: neal@sequoia-pgp.org (good) // 13/13. sig over user ID #6 // // ---------------------------------------------- // // 14/14. signing subkey #1: 7223B56678E02528 (good) // 15/15. sig over subkey #1 // 16/16. sig over subkey #1 // // 17/17. encryption subkey #2: C2B819056C652598 (good) // 18/18. sig over subkey #2 // 19/21. sig over subkey #3 // 20/22. sig over subkey #3 // // 21/20. auth subkey #3: A3506AFB820ABD08 (bad) // 22/19. sig over subkey #2 let cert = Cert::from_bytes(crate::tests::key("neal-sigs-out-of-order.pgp")) .unwrap(); let mut userids = cert.userids() .map(|u| String::from_utf8_lossy(u.userid().value()).into_owned()) .collect::<Vec<String>>(); userids.sort(); assert_eq!(userids, &[ "Neal H. Walfield <neal@g10code.com>", "Neal H. Walfield <neal@gnupg.org>", "Neal H. Walfield <neal@pep-project.org>", "Neal H. Walfield <neal@pep.foundation>", "Neal H. Walfield <neal@sequoia-pgp.org>", "Neal H. Walfield <neal@walfield.org>", ]); let mut subkeys = cert.subkeys() .map(|sk| Some(sk.key().keyid())) .collect::<Vec<Option<KeyID>>>(); subkeys.sort(); assert_eq!(subkeys, &[ "7223B56678E02528".parse().ok(), "A3506AFB820ABD08".parse().ok(), "C2B819056C652598".parse().ok(), ]); // DKG's key has all the self-signatures moved to the last // subkey; all user ids/user attributes/subkeys have nothing. let cert = Cert::from_bytes(crate::tests::key("dkg-sigs-out-of-order.pgp")).unwrap(); let mut userids = cert.userids() .map(|u| String::from_utf8_lossy(u.userid().value()).into_owned()) .collect::<Vec<String>>(); userids.sort(); assert_eq!(userids, &[ "Daniel Kahn Gillmor <dkg-debian.org@fifthhorseman.net>", "Daniel Kahn Gillmor <dkg@aclu.org>", "Daniel Kahn Gillmor <dkg@astro.columbia.edu>", "Daniel Kahn Gillmor <dkg@debian.org>", "Daniel Kahn Gillmor <dkg@fifthhorseman.net>", "Daniel Kahn Gillmor <dkg@openflows.com>", ]); assert_eq!(cert.user_attributes.len(), 1); let mut subkeys = cert.subkeys() .map(|sk| Some(sk.key().keyid())) .collect::<Vec<Option<KeyID>>>(); subkeys.sort(); assert_eq!(subkeys, &[ "1075 8EBD BD7C FAB5".parse().ok(), "1258 68EA 4BFA 08E4".parse().ok(), "1498 ADC6 C192 3237".parse().ok(), "24EC FF5A FF68 370A".parse().ok(), "3714 7292 14D5 DA70".parse().ok(), "3B7A A7F0 14E6 9B5A".parse().ok(), "5B58 DCF9 C341 6611".parse().ok(), "A524 01B1 1BFD FA5C".parse().ok(), "A70A 96E1 439E A852".parse().ok(), "C61B D3EC 2148 4CFF".parse().ok(), "CAEF A883 2167 5333".parse().ok(), "DC10 4C4E 0CA7 57FB".parse().ok(), "E3A3 2229 449B 0350".parse().ok(), ]); } /// Tests how we deal with v3 keys, certs, and certifications. #[test] fn v3_packets() { // v3 primary keys are not supported. let cert = Cert::from_bytes(crate::tests::key("john-v3.pgp")); assert_match!(Error::UnsupportedCert(..) = cert.err().unwrap().downcast::<Error>().unwrap()); let cert = Cert::from_bytes(crate::tests::key("john-v3-secret.pgp")); assert_match!(Error::UnsupportedCert(..) = cert.err().unwrap().downcast::<Error>().unwrap()); // Lutz's key is a v3 key. let cert = Cert::from_bytes(crate::tests::key("lutz.gpg")); assert_match!(Error::UnsupportedCert(..) = cert.err().unwrap().downcast::<Error>().unwrap()); // v3 certifications are not supported // dkg's includes some v3 signatures. let cert = Cert::from_bytes(crate::tests::key("dkg.gpg")); assert!(cert.is_ok(), "dkg.gpg: {:?}", cert); } #[test] fn keyring_with_v3_public_keys() { let dkg = crate::tests::key("dkg.gpg"); let lutz = crate::tests::key("lutz.gpg"); let cert = Cert::from_bytes(dkg); assert!(cert.is_ok(), "dkg.gpg: {:?}", cert); // Keyring with two good keys let mut combined = vec![]; combined.extend_from_slice(dkg); combined.extend_from_slice(dkg); let certs = CertParser::from_bytes(&combined[..]).unwrap() .map(|certr| certr.is_ok()) .collect::<Vec<bool>>(); assert_eq!(certs, &[ true, true ]); // Keyring with a good key, and a bad key. let mut combined = vec![]; combined.extend_from_slice(dkg); combined.extend_from_slice(lutz); let certs = CertParser::from_bytes(&combined[..]).unwrap() .map(|certr| certr.is_ok()) .collect::<Vec<bool>>(); assert_eq!(certs, &[ true, false ]); // Keyring with a bad key, and a good key. let mut combined = vec![]; combined.extend_from_slice(lutz); combined.extend_from_slice(dkg); let certs = CertParser::from_bytes(&combined[..]).unwrap() .map(|certr| certr.is_ok()) .collect::<Vec<bool>>(); assert_eq!(certs, &[ false, true ]); // Keyring with a good key, a bad key, and a good key. let mut combined = vec![]; combined.extend_from_slice(dkg); combined.extend_from_slice(lutz); combined.extend_from_slice(dkg); let certs = CertParser::from_bytes(&combined[..]).unwrap() .map(|certr| certr.is_ok()) .collect::<Vec<bool>>(); assert_eq!(certs, &[ true, false, true ]); // Keyring with a good key, a bad key, and a bad key. let mut combined = vec![]; combined.extend_from_slice(dkg); combined.extend_from_slice(lutz); combined.extend_from_slice(lutz); let certs = CertParser::from_bytes(&combined[..]).unwrap() .map(|certr| certr.is_ok()) .collect::<Vec<bool>>(); assert_eq!(certs, &[ true, false, false ]); // Keyring with a good key, a bad key, a bad key, and a good key. let mut combined = vec![]; combined.extend_from_slice(dkg); combined.extend_from_slice(lutz); combined.extend_from_slice(lutz); combined.extend_from_slice(dkg); let certs = CertParser::from_bytes(&combined[..]).unwrap() .map(|certr| certr.is_ok()) .collect::<Vec<bool>>(); assert_eq!(certs, &[ true, false, false, true ]); } #[test] fn merge_with_incomplete_update() { let p = &P::new(); let cert = Cert::from_bytes(crate::tests::key("about-to-expire.expired.pgp")) .unwrap(); cert.primary_key().with_policy(p, None).unwrap().alive().unwrap_err(); let update = Cert::from_bytes(crate::tests::key("about-to-expire.update-no-uid.pgp")) .unwrap(); let cert = cert.merge_public_and_secret(update).unwrap(); cert.primary_key().with_policy(p, None).unwrap().alive().unwrap(); } #[test] fn packet_pile_roundtrip() { // Make sure Cert::try_from(Cert::to_packet_pile(cert)) // does a clean round trip. let cert = Cert::from_bytes(crate::tests::key("already-revoked.pgp")).unwrap(); let cert2 = Cert::try_from(cert.clone().into_packet_pile()).unwrap(); assert_eq!(cert, cert2); let cert = Cert::from_bytes( crate::tests::key("already-revoked-direct-revocation.pgp")).unwrap(); let cert2 = Cert::try_from(cert.clone().into_packet_pile()).unwrap(); assert_eq!(cert, cert2); let cert = Cert::from_bytes( crate::tests::key("already-revoked-userid-revocation.pgp")).unwrap(); let cert2 = Cert::try_from(cert.clone().into_packet_pile()).unwrap(); assert_eq!(cert, cert2); let cert = Cert::from_bytes( crate::tests::key("already-revoked-subkey-revocation.pgp")).unwrap(); let cert2 = Cert::try_from(cert.clone().into_packet_pile()).unwrap(); assert_eq!(cert, cert2); } #[test] fn insert_packets_add_sig() { use crate::armor; use crate::packet::Tag; // Merge the revocation certificate into the Cert and make sure // it shows up. let cert = Cert::from_bytes(crate::tests::key("already-revoked.pgp")).unwrap(); let rev = crate::tests::key("already-revoked.rev"); let rev = PacketPile::from_reader(armor::Reader::from_reader(rev, None)) .unwrap(); let rev : Vec<Packet> = rev.into_children().collect(); assert_eq!(rev.len(), 1); assert_eq!(rev[0].tag(), Tag::Signature); let packets_pre_merge = cert.clone().into_packets().count(); let cert = cert.insert_packets(rev).unwrap().0; let packets_post_merge = cert.clone().into_packets().count(); assert_eq!(packets_post_merge, packets_pre_merge + 1); } #[test] fn insert_packets_update_sig() -> Result<()> { use std::time::Duration; use crate::packet::signature::subpacket::Subpacket; use crate::packet::signature::subpacket::SubpacketValue; let (cert, _) = CertBuilder::general_purpose(Some("Test")) .generate()?; let packets = cert.clone().into_packets().count(); // Merge a signature with different unhashed subpacket areas. // Make sure only the last variant is merged. let sig = cert.primary_key().self_signatures().next() .expect("binding signature"); let a = Subpacket::new( SubpacketValue::SignatureExpirationTime( Duration::new(1, 0).try_into()?), false)?; let b = Subpacket::new( SubpacketValue::SignatureExpirationTime( Duration::new(2, 0).try_into()?), false)?; let mut sig_a = sig.clone(); sig_a.unhashed_area_mut().add(a)?; let mut sig_b = sig.clone(); sig_b.unhashed_area_mut().add(b)?; // Insert sig_a, make sure it (and it alone) appears. let cert2 = cert.clone().insert_packets(sig_a.clone())?.0; let mut sigs = cert2.primary_key().self_signatures(); assert_eq!(sigs.next(), Some(&sig_a)); assert!(sigs.next().is_none()); assert_eq!(cert2.clone().into_packets().count(), packets); // Insert sig_b, make sure it (and it alone) appears. let cert2 = cert.clone().insert_packets(sig_b.clone())?.0; let mut sigs = cert2.primary_key().self_signatures(); assert_eq!(sigs.next(), Some(&sig_b)); assert!(sigs.next().is_none()); assert_eq!(cert2.clone().into_packets().count(), packets); // Insert sig_a and sig_b. Make sure sig_b (and it alone) // appears. let cert2 = cert.clone().insert_packets( vec![ sig_a.clone(), sig_b.clone() ])?.0; let mut sigs = cert2.primary_key().self_signatures(); assert_eq!(sigs.next(), Some(&sig_b)); assert!(sigs.next().is_none()); assert_eq!(cert2.clone().into_packets().count(), packets); // Insert sig_b and sig_a. Make sure sig_a (and it alone) // appears. let cert2 = cert.clone().insert_packets( vec![ sig_b.clone(), sig_a.clone() ])?.0; let mut sigs = cert2.primary_key().self_signatures(); assert_eq!(sigs.next(), Some(&sig_a)); assert!(sigs.next().is_none()); assert_eq!(cert2.clone().into_packets().count(), packets); Ok(()) } #[test] fn insert_packets_add_userid() -> Result<()> { let (cert, _) = CertBuilder::general_purpose(Some("a")) .generate()?; let packets = cert.clone().into_packets().count(); let uid_a = UserID::from("a"); let uid_b = UserID::from("b"); // Insert a, make sure it appears once. let cert2 = cert.clone().insert_packets(uid_a.clone())?.0; let mut uids = cert2.userids(); assert_eq!(uids.next().unwrap().userid(), &uid_a); assert!(uids.next().is_none()); assert_eq!(cert2.clone().into_packets().count(), packets); // Insert b, make sure it also appears. let cert2 = cert.clone().insert_packets(uid_b.clone())?.0; let mut uids: Vec<UserID> = cert2.userids().map(|ua| ua.userid().clone()).collect(); uids.sort(); let mut uids = uids.iter(); assert_eq!(uids.next().unwrap(), &uid_a); assert_eq!(uids.next().unwrap(), &uid_b); assert!(uids.next().is_none()); assert_eq!(cert2.clone().into_packets().count(), packets + 1); Ok(()) } #[test] fn insert_packets_update_key() -> Result<()> { use crate::crypto::Password; let (cert, _) = CertBuilder::new().generate()?; let packets = cert.clone().into_packets().count(); assert_eq!(cert.keys().count(), 1); let key = cert.keys().secret().next().unwrap().key() .role_as_primary(); assert!(key.has_secret()); let key_a = key.clone().encrypt_secret(&Password::from("a"))?; let key_b = key.clone().encrypt_secret(&Password::from("b"))?; // Insert variant a. let cert2 = cert.clone().insert_packets(key_a.clone())?.0; assert_eq!(cert2.primary_key().key().parts_as_secret().unwrap(), &key_a); assert_eq!(cert2.clone().into_packets().count(), packets); // Insert variant b. let cert2 = cert.clone().insert_packets(key_b.clone())?.0; assert_eq!(cert2.primary_key().key().parts_as_secret().unwrap(), &key_b); assert_eq!(cert2.clone().into_packets().count(), packets); // Insert variant a then b. We should keep b. let cert2 = cert.clone().insert_packets( vec![ key_a.clone(), key_b.clone() ])?.0; assert_eq!(cert2.primary_key().key().parts_as_secret().unwrap(), &key_b); assert_eq!(cert2.clone().into_packets().count(), packets); // Insert variant b then a. We should keep a. let cert2 = cert.clone().insert_packets( vec![ key_b.clone(), key_a.clone() ])?.0; assert_eq!(cert2.primary_key().key().parts_as_secret().unwrap(), &key_a); assert_eq!(cert2.clone().into_packets().count(), packets); Ok(()) } #[test] fn set_validity_period() { let p = &P::new(); let (cert, _) = CertBuilder::general_purpose(Some("Test")) .generate().unwrap(); assert_eq!(cert.clone().into_packet_pile().children().count(), 1 // primary key + 1 // direct key signature + 1 // userid + 1 // binding signature + 1 // subkey + 1 // binding signature + 1 // subkey + 1 // binding signature ); let cert = check_set_validity_period(p, cert); assert_eq!(cert.clone().into_packet_pile().children().count(), 1 // primary key + 1 // direct key signature + 2 // two new direct key signatures + 1 // userid + 1 // binding signature + 2 // two new binding signatures + 1 // subkey + 1 // binding signature + 1 // subkey + 1 // binding signature ); } #[test] fn set_validity_period_two_uids() -> Result<()> { use quickcheck::{Arbitrary, Gen}; let mut gen = Gen::new(16); let p = &P::new(); let userid1 = UserID::arbitrary(&mut gen); // The two user ids need to be unique. let mut userid2 = UserID::arbitrary(&mut gen); while userid1 == userid2 { userid2 = UserID::arbitrary(&mut gen); } let (cert, _) = CertBuilder::general_purpose( Some(userid1)) .add_userid(userid2) .generate()?; let primary_uid = cert.with_policy(p, None)?.primary_userid()?.userid().clone(); assert_eq!(cert.clone().into_packet_pile().children().count(), 1 // primary key + 1 // direct key signature + 1 // userid + 1 // binding signature + 1 // userid + 1 // binding signature + 1 // subkey + 1 // binding signature + 1 // subkey + 1 // binding signature ); let cert = check_set_validity_period(p, cert); assert_eq!(cert.clone().into_packet_pile().children().count(), 1 // primary key + 1 // direct key signature + 2 // two new direct key signatures + 1 // userid + 1 // binding signature + 2 // two new binding signatures + 1 // userid + 1 // binding signature + 2 // two new binding signatures + 1 // subkey + 1 // binding signature + 1 // subkey + 1 // binding signature ); assert_eq!(&primary_uid, cert.with_policy(p, None)?.primary_userid()?.userid()); Ok(()) } #[test] fn set_validity_period_uidless() { use crate::types::Duration; let p = &P::new(); let (cert, _) = CertBuilder::new() .set_validity_period(None) // Just to assert this works. .set_validity_period(Some(Duration::weeks(52).unwrap().try_into().unwrap())) .generate().unwrap(); assert_eq!(cert.clone().into_packet_pile().children().count(), 1 // primary key + 1 // direct key signature ); let cert = check_set_validity_period(p, cert); assert_eq!(cert.clone().into_packet_pile().children().count(), 1 // primary key + 1 // direct key signature + 2 // two new direct key signatures ); } fn check_set_validity_period(policy: &dyn Policy, cert: Cert) -> Cert { let now = cert.primary_key().key().creation_time(); let a_sec = time::Duration::new(1, 0); let expiry_orig = cert.primary_key().with_policy(policy, now).unwrap() .key_validity_period() .expect("Keys expire by default."); let mut keypair = cert.primary_key().key().clone().parts_into_secret() .unwrap().into_keypair().unwrap(); // Clear the expiration. let as_of1 = now + time::Duration::new(10, 0); let cert = cert.set_validity_period_as_of( policy, &mut keypair, None, as_of1).unwrap(); { // If t < as_of1, we should get the original expiry. assert_eq!(cert.primary_key().with_policy(policy, now).unwrap() .key_validity_period(), Some(expiry_orig)); assert_eq!(cert.primary_key().with_policy(policy, as_of1 - a_sec).unwrap() .key_validity_period(), Some(expiry_orig)); // If t >= as_of1, we should get the new expiry. assert_eq!(cert.primary_key().with_policy(policy, as_of1).unwrap() .key_validity_period(), None); } // Shorten the expiry. (The default expiration should be at // least a few weeks, so removing an hour should still keep us // over 0.) let expiry_new = expiry_orig - time::Duration::new(60 * 60, 0); assert!(expiry_new > time::Duration::new(0, 0)); let as_of2 = as_of1 + time::Duration::new(10, 0); let cert = cert.set_validity_period_as_of( policy, &mut keypair, Some(expiry_new), as_of2).unwrap(); { // If t < as_of1, we should get the original expiry. assert_eq!(cert.primary_key().with_policy(policy, now).unwrap() .key_validity_period(), Some(expiry_orig)); assert_eq!(cert.primary_key().with_policy(policy, as_of1 - a_sec).unwrap() .key_validity_period(), Some(expiry_orig)); // If as_of1 <= t < as_of2, we should get the second // expiry (None). assert_eq!(cert.primary_key().with_policy(policy, as_of1).unwrap() .key_validity_period(), None); assert_eq!(cert.primary_key().with_policy(policy, as_of2 - a_sec).unwrap() .key_validity_period(), None); // If t <= as_of2, we should get the new expiry. assert_eq!(cert.primary_key().with_policy(policy, as_of2).unwrap() .key_validity_period(), Some(expiry_new)); } cert } #[test] fn direct_key_sig() { use crate::types::SignatureType; // XXX: testing sequoia against itself isn't optimal, but I couldn't // find a tool to generate direct key signatures :-( let p = &P::new(); let (cert1, _) = CertBuilder::new().generate().unwrap(); let mut buf = Vec::default(); cert1.serialize(&mut buf).unwrap(); let cert2 = Cert::from_bytes(&buf).unwrap(); assert_eq!( cert2.primary_key().with_policy(p, None).unwrap() .direct_key_signature().unwrap().typ(), SignatureType::DirectKey); assert_eq!(cert2.userids().count(), 0); } #[test] fn revoked() { fn check(cert: &Cert, direct_revoked: bool, userid_revoked: bool, subkey_revoked: bool) { let p = &P::new(); // If we have a user id---even if it is revoked---we have // a primary key signature. let typ = cert.primary_key().with_policy(p, None).unwrap() .binding_signature().typ(); assert_eq!(typ, SignatureType::PositiveCertification, "{:#?}", cert); let revoked = cert.revocation_status(p, None); if direct_revoked { assert_match!(RevocationStatus::Revoked(_) = revoked, "{:#?}", cert); } else { assert_eq!(revoked, RevocationStatus::NotAsFarAsWeKnow, "{:#?}", cert); } for userid in cert.userids().with_policy(p, None) { let typ = userid.binding_signature().typ(); assert_eq!(typ, SignatureType::PositiveCertification, "{:#?}", cert); let revoked = userid.revocation_status(); if userid_revoked { assert_match!(RevocationStatus::Revoked(_) = revoked); } else { assert_eq!(RevocationStatus::NotAsFarAsWeKnow, revoked, "{:#?}", cert); } } for subkey in cert.subkeys() { let typ = subkey.binding_signature(p, None).unwrap().typ(); assert_eq!(typ, SignatureType::SubkeyBinding, "{:#?}", cert); let revoked = subkey.revocation_status(p, None); if subkey_revoked { assert_match!(RevocationStatus::Revoked(_) = revoked); } else { assert_eq!(RevocationStatus::NotAsFarAsWeKnow, revoked, "{:#?}", cert); } } } let cert = Cert::from_bytes(crate::tests::key("already-revoked.pgp")).unwrap(); check(&cert, false, false, false); let d = Cert::from_bytes( crate::tests::key("already-revoked-direct-revocation.pgp")).unwrap(); check(&d, true, false, false); check(&cert.clone().merge_public_and_secret(d.clone()).unwrap(), true, false, false); // Make sure the merge order does not matter. check(&d.clone().merge_public_and_secret(cert.clone()).unwrap(), true, false, false); let u = Cert::from_bytes( crate::tests::key("already-revoked-userid-revocation.pgp")).unwrap(); check(&u, false, true, false); check(&cert.clone().merge_public_and_secret(u.clone()).unwrap(), false, true, false); check(&u.clone().merge_public_and_secret(cert.clone()).unwrap(), false, true, false); let k = Cert::from_bytes( crate::tests::key("already-revoked-subkey-revocation.pgp")).unwrap(); check(&k, false, false, true); check(&cert.clone().merge_public_and_secret(k.clone()).unwrap(), false, false, true); check(&k.clone().merge_public_and_secret(cert.clone()).unwrap(), false, false, true); // direct and user id revocation. check(&d.clone().merge_public_and_secret(u.clone()).unwrap(), true, true, false); check(&u.clone().merge_public_and_secret(d.clone()).unwrap(), true, true, false); // direct and subkey revocation. check(&d.clone().merge_public_and_secret(k.clone()).unwrap(), true, false, true); check(&k.clone().merge_public_and_secret(d.clone()).unwrap(), true, false, true); // user id and subkey revocation. check(&u.clone().merge_public_and_secret(k.clone()).unwrap(), false, true, true); check(&k.clone().merge_public_and_secret(u.clone()).unwrap(), false, true, true); // direct, user id and subkey revocation. check(&d.clone().merge_public_and_secret(u.clone().merge_public_and_secret(k.clone()).unwrap()).unwrap(), true, true, true); check(&d.clone().merge_public_and_secret(k.clone().merge_public_and_secret(u.clone()).unwrap()).unwrap(), true, true, true); } #[test] fn revoke() { let p = &P::new(); let (cert, _) = CertBuilder::general_purpose(Some("Test")) .generate().unwrap(); assert_eq!(RevocationStatus::NotAsFarAsWeKnow, cert.revocation_status(p, None)); let mut keypair = cert.primary_key().key().clone().parts_into_secret() .unwrap().into_keypair().unwrap(); let sig = CertRevocationBuilder::new() .set_reason_for_revocation( ReasonForRevocation::KeyCompromised, b"It was the maid :/").unwrap() .build(&mut keypair, &cert, None) .unwrap(); assert_eq!(sig.typ(), SignatureType::KeyRevocation); assert_eq!(sig.issuers().collect::<Vec<_>>(), vec![ &cert.keyid() ]); assert_eq!(sig.issuer_fingerprints().collect::<Vec<_>>(), vec![ &cert.fingerprint() ]); let cert = cert.insert_packets(sig).unwrap().0; assert_match!(RevocationStatus::Revoked(_) = cert.revocation_status(p, None)); // Have other revoke cert. let (other, _) = CertBuilder::general_purpose(Some("Test 2")) .generate().unwrap(); let mut keypair = other.primary_key().key().clone().parts_into_secret() .unwrap().into_keypair().unwrap(); let sig = CertRevocationBuilder::new() .set_reason_for_revocation( ReasonForRevocation::KeyCompromised, b"It was the maid :/").unwrap() .build(&mut keypair, &cert, None) .unwrap(); assert_eq!(sig.typ(), SignatureType::KeyRevocation); assert_eq!(sig.issuers().collect::<Vec<_>>(), vec![ &other.keyid() ]); assert_eq!(sig.issuer_fingerprints().collect::<Vec<_>>(), vec![ &other.fingerprint() ]); } #[test] fn revoke_subkey() { let p = &P::new(); let (cert, _) = CertBuilder::new() .add_transport_encryption_subkey() .generate().unwrap(); let sig = { let subkey = cert.subkeys().next().unwrap(); assert_eq!(RevocationStatus::NotAsFarAsWeKnow, subkey.revocation_status(p, None)); let mut keypair = cert.primary_key().key().clone().parts_into_secret() .unwrap().into_keypair().unwrap(); SubkeyRevocationBuilder::new() .set_reason_for_revocation( ReasonForRevocation::UIDRetired, b"It was the maid :/").unwrap() .build(&mut keypair, &cert, subkey.key(), None) .unwrap() }; assert_eq!(sig.typ(), SignatureType::SubkeyRevocation); let cert = cert.insert_packets(sig).unwrap().0; assert_eq!(RevocationStatus::NotAsFarAsWeKnow, cert.revocation_status(p, None)); let subkey = cert.subkeys().next().unwrap(); assert_match!(RevocationStatus::Revoked(_) = subkey.revocation_status(p, None)); } #[test] fn revoke_uid() { let p = &P::new(); let (cert, _) = CertBuilder::new() .add_userid("Test1") .add_userid("Test2") .generate().unwrap(); let sig = { let uid = cert.userids().with_policy(p, None).nth(1).unwrap(); assert_eq!(RevocationStatus::NotAsFarAsWeKnow, uid.revocation_status()); let mut keypair = cert.primary_key().key().clone().parts_into_secret() .unwrap().into_keypair().unwrap(); UserIDRevocationBuilder::new() .set_reason_for_revocation( ReasonForRevocation::UIDRetired, b"It was the maid :/").unwrap() .build(&mut keypair, &cert, uid.userid(), None) .unwrap() }; assert_eq!(sig.typ(), SignatureType::CertificationRevocation); let cert = cert.insert_packets(sig).unwrap().0; assert_eq!(RevocationStatus::NotAsFarAsWeKnow, cert.revocation_status(p, None)); let uid = cert.userids().with_policy(p, None).nth(1).unwrap(); assert_match!(RevocationStatus::Revoked(_) = uid.revocation_status()); } #[test] fn key_revoked() { use crate::types::Features; use crate::packet::key::Key6; use rand::{thread_rng, Rng, distributions::Open01}; let p = &P::new(); /* * t1: 1st binding sig ctime * t2: soft rev sig ctime * t3: 2nd binding sig ctime * t4: hard rev sig ctime * * [0,t1): invalid, but not revoked * [t1,t2): valid (not revocations) * [t2,t3): revoked (soft revocation) * [t3,t4): valid again (new self sig) * [t4,inf): hard revocation (hard revocation) * * Once the hard revocation is merged, then the Cert is * considered revoked at all times. */ let t1 = time::UNIX_EPOCH + time::Duration::new(946681200, 0); // 2000-1-1 let t2 = time::UNIX_EPOCH + time::Duration::new(978303600, 0); // 2001-1-1 let t3 = time::UNIX_EPOCH + time::Duration::new(1009839600, 0); // 2002-1-1 let t4 = time::UNIX_EPOCH + time::Duration::new(1041375600, 0); // 2003-1-1 let mut key: key::SecretKey = Key6::generate_ecc(true, Curve::Ed25519).unwrap().into(); key.set_creation_time(t1).unwrap(); let mut pair = key.clone().into_keypair().unwrap(); let (bind1, rev1, bind2, rev2) = { let bind1 = signature::SignatureBuilder::new(SignatureType::DirectKey) .set_features(Features::sequoia()).unwrap() .set_key_flags(KeyFlags::empty()).unwrap() .set_signature_creation_time(t1).unwrap() .set_key_validity_period(Some(time::Duration::new(10 * 52 * 7 * 24 * 60 * 60, 0))).unwrap() .set_preferred_hash_algorithms(vec![HashAlgorithm::SHA512]).unwrap() .sign_direct_key(&mut pair, key.parts_as_public()).unwrap(); let rev1 = signature::SignatureBuilder::new(SignatureType::KeyRevocation) .set_signature_creation_time(t2).unwrap() .set_reason_for_revocation(ReasonForRevocation::KeySuperseded, &b""[..]).unwrap() .sign_direct_key(&mut pair, key.parts_as_public()).unwrap(); let bind2 = signature::SignatureBuilder::new(SignatureType::DirectKey) .set_features(Features::sequoia()).unwrap() .set_key_flags(KeyFlags::empty()).unwrap() .set_signature_creation_time(t3).unwrap() .set_key_validity_period(Some(time::Duration::new(10 * 52 * 7 * 24 * 60 * 60, 0))).unwrap() .set_preferred_hash_algorithms(vec![HashAlgorithm::SHA512]).unwrap() .sign_direct_key(&mut pair, key.parts_as_public()).unwrap(); let rev2 = signature::SignatureBuilder::new(SignatureType::KeyRevocation) .set_signature_creation_time(t4).unwrap() .set_reason_for_revocation(ReasonForRevocation::KeyCompromised, &b""[..]).unwrap() .sign_direct_key(&mut pair, key.parts_as_public()).unwrap(); (bind1, rev1, bind2, rev2) }; let pk : key::PublicKey = key.into(); let cert = Cert::try_from(vec![ pk.into(), bind1.into(), bind2.into(), rev1.into() ]).unwrap(); let f1: f32 = thread_rng().sample(Open01); let f2: f32 = thread_rng().sample(Open01); let f3: f32 = thread_rng().sample(Open01); let f4: f32 = thread_rng().sample(Open01); let te1 = t1 - time::Duration::new((60. * 60. * 24. * 300.0 * f1) as u64, 0); let t12 = t1 + time::Duration::new((60. * 60. * 24. * 300.0 * f2) as u64, 0); let t23 = t2 + time::Duration::new((60. * 60. * 24. * 300.0 * f3) as u64, 0); let t34 = t3 + time::Duration::new((60. * 60. * 24. * 300.0 * f4) as u64, 0); assert_eq!(cert.revocation_status(p, te1), RevocationStatus::NotAsFarAsWeKnow); assert_eq!(cert.revocation_status(p, t12), RevocationStatus::NotAsFarAsWeKnow); assert_match!(RevocationStatus::Revoked(_) = cert.revocation_status(p, t23)); assert_eq!(cert.revocation_status(p, t34), RevocationStatus::NotAsFarAsWeKnow); // Merge in the hard revocation. let cert = cert.insert_packets(rev2).unwrap().0; assert_match!(RevocationStatus::Revoked(_) = cert.revocation_status(p, te1)); assert_match!(RevocationStatus::Revoked(_) = cert.revocation_status(p, t12)); assert_match!(RevocationStatus::Revoked(_) = cert.revocation_status(p, t23)); assert_match!(RevocationStatus::Revoked(_) = cert.revocation_status(p, t34)); assert_match!(RevocationStatus::Revoked(_) = cert.revocation_status(p, t4)); assert_match!(RevocationStatus::Revoked(_) = cert.revocation_status(p, crate::now())); } #[test] fn key_revoked2() { tracer!(true, "cert_revoked2", 0); let p = &P::new(); fn cert_revoked<T>(p: &dyn Policy, cert: &Cert, t: T) -> bool where T: Into<Option<time::SystemTime>> { !matches!( cert.revocation_status(p, t), RevocationStatus::NotAsFarAsWeKnow ) } fn subkey_revoked<T>(p: &dyn Policy, cert: &Cert, t: T) -> bool where T: Into<Option<time::SystemTime>> { !matches!( cert.subkeys().next().unwrap().bundle().revocation_status(p, t), RevocationStatus::NotAsFarAsWeKnow ) } let tests : [(&str, Box<dyn Fn(&dyn Policy, &Cert, _) -> bool>); 2] = [ ("cert", Box::new(cert_revoked)), ("subkey", Box::new(subkey_revoked)), ]; for (f, revoked) in tests.iter() { t!("Checking {} revocation", f); t!("Normal key"); let cert = Cert::from_bytes( crate::tests::key( &format!("really-revoked-{}-0-public.pgp", f))).unwrap(); let selfsig0 = cert.primary_key().with_policy(p, None).unwrap() .binding_signature().signature_creation_time().unwrap(); assert!(!revoked(p, &cert, Some(selfsig0))); assert!(!revoked(p, &cert, None)); t!("Soft revocation"); let cert = cert.merge_public_and_secret( Cert::from_bytes( crate::tests::key( &format!("really-revoked-{}-1-soft-revocation.pgp", f)) ).unwrap()).unwrap(); // A soft revocation made after `t` is ignored when // determining whether the key is revoked at time `t`. assert!(!revoked(p, &cert, Some(selfsig0))); assert!(revoked(p, &cert, None)); t!("New self signature"); let cert = cert.merge_public_and_secret( Cert::from_bytes( crate::tests::key( &format!("really-revoked-{}-2-new-self-sig.pgp", f)) ).unwrap()).unwrap(); assert!(!revoked(p, &cert, Some(selfsig0))); // Newer self-sig override older soft revocations. assert!(!revoked(p, &cert, None)); t!("Hard revocation"); let cert = cert.merge_public_and_secret( Cert::from_bytes( crate::tests::key( &format!("really-revoked-{}-3-hard-revocation.pgp", f)) ).unwrap()).unwrap(); // Hard revocations trump all. assert!(revoked(p, &cert, Some(selfsig0))); assert!(revoked(p, &cert, None)); t!("New self signature"); let cert = cert.merge_public_and_secret( Cert::from_bytes( crate::tests::key( &format!("really-revoked-{}-4-new-self-sig.pgp", f)) ).unwrap()).unwrap(); assert!(revoked(p, &cert, Some(selfsig0))); assert!(revoked(p, &cert, None)); } } #[test] fn userid_revoked2() { fn check_userids<T>(p: &dyn Policy, cert: &Cert, revoked: bool, t: T) where T: Into<Option<time::SystemTime>>, T: Copy { assert_match!(RevocationStatus::NotAsFarAsWeKnow = cert.revocation_status(p, None)); let mut slim_shady = false; let mut eminem = false; for b in cert.userids().with_policy(p, t) { if b.userid().value() == b"Slim Shady" { assert!(!slim_shady); slim_shady = true; if revoked { assert_match!(RevocationStatus::Revoked(_) = b.revocation_status()); } else { assert_match!(RevocationStatus::NotAsFarAsWeKnow = b.revocation_status()); } } else { assert!(!eminem); eminem = true; assert_match!(RevocationStatus::NotAsFarAsWeKnow = b.revocation_status()); } } assert!(slim_shady); assert!(eminem); } fn check_uas<T>(p: &dyn Policy, cert: &Cert, revoked: bool, t: T) where T: Into<Option<time::SystemTime>>, T: Copy { assert_match!(RevocationStatus::NotAsFarAsWeKnow = cert.revocation_status(p, None)); assert_eq!(cert.user_attributes().count(), 1); let ua = cert.user_attributes().next().unwrap(); if revoked { assert_match!(RevocationStatus::Revoked(_) = ua.revocation_status(p, t)); } else { assert_match!(RevocationStatus::NotAsFarAsWeKnow = ua.revocation_status(p, t)); } } tracer!(true, "userid_revoked2", 0); let p = &P::new(); let tests : [(&str, Box<dyn Fn(&dyn Policy, &Cert, bool, _)>); 2] = [ ("userid", Box::new(check_userids)), ("user-attribute", Box::new(check_uas)), ]; for (f, check) in tests.iter() { t!("Checking {} revocation", f); t!("Normal key"); let cert = Cert::from_bytes( crate::tests::key( &format!("really-revoked-{}-0-public.pgp", f))).unwrap(); let now = crate::now(); let selfsig0 = cert.userids().with_policy(p, now).map(|b| { b.binding_signature().signature_creation_time().unwrap() }) .max().unwrap(); check(p, &cert, false, selfsig0); check(p, &cert, false, now); // A soft-revocation. let cert = cert.merge_public_and_secret( Cert::from_bytes( crate::tests::key( &format!("really-revoked-{}-1-soft-revocation.pgp", f)) ).unwrap()).unwrap(); check(p, &cert, false, selfsig0); check(p, &cert, true, now); // A new self signature. This should override the soft-revocation. let cert = cert.merge_public_and_secret( Cert::from_bytes( crate::tests::key( &format!("really-revoked-{}-2-new-self-sig.pgp", f)) ).unwrap()).unwrap(); check(p, &cert, false, selfsig0); check(p, &cert, false, now); // A hard revocation. Unlike for Certs, this does NOT trump // everything. let cert = cert.merge_public_and_secret( Cert::from_bytes( crate::tests::key( &format!("really-revoked-{}-3-hard-revocation.pgp", f)) ).unwrap()).unwrap(); check(p, &cert, false, selfsig0); check(p, &cert, true, now); // A newer self signature. let cert = cert.merge_public_and_secret( Cert::from_bytes( crate::tests::key( &format!("really-revoked-{}-4-new-self-sig.pgp", f)) ).unwrap()).unwrap(); check(p, &cert, false, selfsig0); check(p, &cert, false, now); } } #[test] fn unrevoked() { let p = &P::new(); let cert = Cert::from_bytes(crate::tests::key("un-revoked-userid.pgp")).unwrap(); for uid in cert.userids().with_policy(p, None) { assert_eq!(uid.revocation_status(), RevocationStatus::NotAsFarAsWeKnow); } } #[test] fn is_tsk() { let cert = Cert::from_bytes( crate::tests::key("already-revoked.pgp")).unwrap(); assert!(! cert.is_tsk()); let cert = Cert::from_bytes( crate::tests::key("already-revoked-private.pgp")).unwrap(); assert!(cert.is_tsk()); } #[test] fn export_only_exports_public_key() { let cert = Cert::from_bytes( crate::tests::key("testy-new-private.pgp")).unwrap(); assert!(cert.is_tsk()); let mut v = Vec::new(); cert.serialize(&mut v).unwrap(); let cert = Cert::from_bytes(&v).unwrap(); assert!(! cert.is_tsk()); } // Make sure that when merging two Certs, the primary key and // subkeys with and without a private key are merged. #[test] fn public_private_merge() { let (tsk, _) = CertBuilder::general_purpose(Some("foo@example.com")) .generate().unwrap(); // tsk is now a cert, but it still has its private bits. assert!(tsk.primary.key().has_secret()); assert!(tsk.is_tsk()); let subkey_count = tsk.subkeys().count(); assert!(subkey_count > 0); assert!(tsk.subkeys().all(|k| k.key().has_secret())); // This will write out the tsk as a cert, i.e., without any // private bits. let mut cert_bytes = Vec::new(); tsk.serialize(&mut cert_bytes).unwrap(); // Reading it back in, the private bits have been stripped. let cert = Cert::from_bytes(&cert_bytes[..]).unwrap(); assert!(! cert.primary.key().has_secret()); assert!(!cert.is_tsk()); assert!(cert.subkeys().all(|k| ! k.key().has_secret())); let merge1 = cert.clone().merge_public_and_secret(tsk.clone()).unwrap(); assert!(merge1.is_tsk()); assert!(merge1.primary.key().has_secret()); assert_eq!(merge1.subkeys().count(), subkey_count); assert!(merge1.subkeys().all(|k| k.key().has_secret())); let merge2 = tsk.clone().merge_public_and_secret(cert.clone()).unwrap(); assert!(merge2.is_tsk()); assert!(merge2.primary.key().has_secret()); assert_eq!(merge2.subkeys().count(), subkey_count); assert!(merge2.subkeys().all(|k| k.key().has_secret())); } #[test] fn issue_120() { let cert = " -----BEGIN PGP ARMORED FILE----- xcBNBFoVcvoBCACykTKOJddF8SSUAfCDHk86cNTaYnjCoy72rMgWJsrMLnz/V16B J9M7l6nrQ0JMnH2Du02A3w+kNb5q97IZ/M6NkqOOl7uqjyRGPV+XKwt0G5mN/ovg 8630BZAYS3QzavYf3tni9aikiGH+zTFX5pynTNfYRXNBof3Xfzl92yad2bIt4ITD NfKPvHRko/tqWbclzzEn72gGVggt1/k/0dKhfsGzNogHxg4GIQ/jR/XcqbDFR3RC /JJjnTOUPGsC1y82Xlu8udWBVn5mlDyxkad5laUpWWg17anvczEAyx4TTOVItLSu 43iPdKHSs9vMXWYID0bg913VusZ2Ofv690nDABEBAAHNJFRlc3R5IE1jVGVzdGZh Y2UgPHRlc3R5QGV4YW1wbGUub3JnPsLAlAQTAQgAPhYhBD6Id8h3J0aSl1GJ9dA/ b4ZSJv6LBQJaFXL6AhsDBQkDwmcABQsJCAcCBhUICQoLAgQWAgMBAh4BAheAAAoJ ENA/b4ZSJv6Lxo8H/1XMt+Nqa6e0SG/up3ypKe5nplA0p/9j/s2EIsP8S8uPUd+c WS17XOmPwkNDmHeL3J6hzwL74NlYSLEtyf7WoOV74xAKQA9WkqaKPHCtpll8aFWA ktQDLWTPeKuUuSlobAoRtO17ZmheSQzmm7JYt4Ahkxt3agqGT05OsaAey6nIKqpq ArokvdHTZ7AFZeSJIWmuCoT9M1lo3LAtLnRGOhBMJ5dDIeOwflJwNBXlJVi4mDPK +fumV0MbSPvZd1/ivFjSpQyudWWtv1R1nAK7+a4CPTGxPvAQkLtRsL/V+Q7F3BJG jAn4QVx8p4t3NOPuNgcoZpLBE3sc4Nfs5/CphMLHwE0EWhVy+gEIALSpjYD+tuWC rj6FGP6crQjQzVlH+7axoM1ooTwiPs4fzzt2iLw3CJyDUviM5F9ZBQTei635RsAR a/CJTSQYAEU5yXXxhoe0OtwnuvsBSvVT7Fox3pkfNTQmwMvkEbodhfKpqBbDKCL8 f5A8Bb7aISsLf0XRHWDkHVqlz8LnOR3f44wEWiTeIxLc8S1QtwX/ExyW47oPsjs9 ShCmwfSpcngH/vGBRTO7WeI54xcAtKSm/20B/MgrUl5qFo17kUWot2C6KjuZKkHk 3WZmJwQz+6rTB11w4AXt8vKkptYQCkfat2FydGpgRO5dVg6aWNJefOJNkC7MmlzC ZrrAK8FJ6jcAEQEAAcLAdgQYAQgAIBYhBD6Id8h3J0aSl1GJ9dA/b4ZSJv6LBQJa FXL6AhsMAAoJENA/b4ZSJv6Lt7kH/jPr5wg8lcamuLj4lydYiLttvvTtDTlD1TL+ IfwVARB/ruoerlEDr0zX1t3DCEcvJDiZfOqJbXtHt70+7NzFXrYxfaNFmikMgSQT XqHrMQho4qpseVOeJPWGzGOcrxCdw/ZgrWbkDlAU5KaIvk+M4wFPivjbtW2Ro2/F J4I/ZHhJlIPmM+hUErHC103b08pBENXDQlXDma7LijH5kWhyfF2Ji7Ft0EjghBaW AeGalQHjc5kAZu5R76Mwt06MEQ/HL1pIvufTFxkr/SzIv8Ih7Kexb0IrybmfD351 Pu1xwz57O4zo1VYf6TqHJzVC3OMvMUM2hhdecMUe5x6GorNaj6g= =1Vzu -----END PGP ARMORED FILE----- "; assert!(Cert::from_bytes(cert).is_err()); } #[test] fn missing_uids() { let (cert, _) = CertBuilder::new() .add_userid("test1@example.com") .add_userid("test2@example.com") .add_transport_encryption_subkey() .add_certification_subkey() .generate().unwrap(); assert_eq!(cert.subkeys().count(), 2); let pile = cert .into_packet_pile() .into_children() .filter(|pkt| { match pkt { &Packet::PublicKey(_) | &Packet::PublicSubkey(_) | &Packet::SecretKey(_) | &Packet::SecretSubkey(_) => true, &Packet::Signature(ref sig) => { sig.typ() == SignatureType::DirectKey || sig.typ() == SignatureType::SubkeyBinding } e => { eprintln!("{:?}", e); false } } }) .collect::<Vec<_>>(); eprintln!("parse back"); let cert = Cert::try_from(pile).unwrap(); assert_eq!(cert.subkeys().count(), 2); } #[test] fn signature_order() { let p = &P::new(); let neal = Cert::from_bytes(crate::tests::key("neal.pgp")).unwrap(); // This test is useless if we don't have some lists with more // than one signature. let mut cmps = 0; for uid in neal.userids() { for sigs in [ uid.self_signatures().collect::<Vec<_>>(), uid.certifications().collect::<Vec<_>>(), uid.self_revocations().collect::<Vec<_>>(), uid.other_revocations().collect::<Vec<_>>() ].iter() { for sigs in sigs.windows(2) { cmps += 1; assert!(sigs[0].signature_creation_time() >= sigs[1].signature_creation_time()); } } // Make sure we return the most recent first. assert_eq!(uid.self_signatures().next().unwrap(), uid.binding_signature(p, None).unwrap()); } assert!(cmps > 0); } #[test] fn cert_reject_keyrings() { let mut keyring = Vec::new(); keyring.extend_from_slice(crate::tests::key("neal.pgp")); keyring.extend_from_slice(crate::tests::key("neal.pgp")); assert!(Cert::from_bytes(&keyring).is_err()); } #[test] fn primary_userid() { // 'really-revoked-userid' has two user ids. One of them is // revoked and then restored. Neither of the user ids has the // primary userid bit set. // // This test makes sure that Cert::primary_userid prefers // unrevoked user ids to revoked user ids, even if the latter // have newer self signatures. let p = &P::new(); let cert = Cert::from_bytes( crate::tests::key("really-revoked-userid-0-public.pgp")).unwrap(); let now = crate::now(); let selfsig0 = cert.userids().with_policy(p, now).map(|b| { b.binding_signature().signature_creation_time().unwrap() }) .max().unwrap(); // The self-sig for: // // Slim Shady: 2019-09-14T14:21 // Eminem: 2019-09-14T14:22 assert_eq!(cert.with_policy(p, selfsig0).unwrap() .primary_userid().unwrap().userid().value(), b"Eminem"); assert_eq!(cert.with_policy(p, now).unwrap() .primary_userid().unwrap().userid().value(), b"Eminem"); // A soft-revocation for "Slim Shady". let cert = cert.merge_public_and_secret( Cert::from_bytes( crate::tests::key("really-revoked-userid-1-soft-revocation.pgp") ).unwrap()).unwrap(); assert_eq!(cert.with_policy(p, selfsig0).unwrap() .primary_userid().unwrap().userid().value(), b"Eminem"); assert_eq!(cert.with_policy(p, now).unwrap() .primary_userid().unwrap().userid().value(), b"Eminem"); // A new self signature for "Slim Shady". This should // override the soft-revocation. let cert = cert.merge_public_and_secret( Cert::from_bytes( crate::tests::key("really-revoked-userid-2-new-self-sig.pgp") ).unwrap()).unwrap(); assert_eq!(cert.with_policy(p, selfsig0).unwrap() .primary_userid().unwrap().userid().value(), b"Eminem"); assert_eq!(cert.with_policy(p, now).unwrap() .primary_userid().unwrap().userid().value(), b"Slim Shady"); // A hard revocation for "Slim Shady". let cert = cert.merge_public_and_secret( Cert::from_bytes( crate::tests::key("really-revoked-userid-3-hard-revocation.pgp") ).unwrap()).unwrap(); assert_eq!(cert.with_policy(p, selfsig0).unwrap() .primary_userid().unwrap().userid().value(), b"Eminem"); assert_eq!(cert.with_policy(p, now).unwrap() .primary_userid().unwrap().userid().value(), b"Eminem"); // A newer self signature for "Slim Shady". Unlike for Certs, this // does NOT trump everything. let cert = cert.merge_public_and_secret( Cert::from_bytes( crate::tests::key("really-revoked-userid-4-new-self-sig.pgp") ).unwrap()).unwrap(); assert_eq!(cert.with_policy(p, selfsig0).unwrap() .primary_userid().unwrap().userid().value(), b"Eminem"); assert_eq!(cert.with_policy(p, now).unwrap() .primary_userid().unwrap().userid().value(), b"Slim Shady"); // Play with the primary user id flag. let cert = Cert::from_bytes( crate::tests::key("primary-key-0-public.pgp")).unwrap(); let selfsig0 = cert.userids().with_policy(p, now).map(|b| { b.binding_signature().signature_creation_time().unwrap() }) .max().unwrap(); // There is only a single User ID. assert_eq!(cert.with_policy(p, selfsig0).unwrap() .primary_userid().unwrap().userid().value(), b"aaaaa"); assert_eq!(cert.with_policy(p, now).unwrap() .primary_userid().unwrap().userid().value(), b"aaaaa"); // Add a second user id. Since neither is marked primary, the // newer one should be considered primary. let cert = cert.merge_public_and_secret( Cert::from_bytes( crate::tests::key("primary-key-1-add-userid-bbbbb.pgp") ).unwrap()).unwrap(); assert_eq!(cert.with_policy(p, selfsig0).unwrap() .primary_userid().unwrap().userid().value(), b"aaaaa"); assert_eq!(cert.with_policy(p, now).unwrap() .primary_userid().unwrap().userid().value(), b"bbbbb"); // Mark aaaaa as primary. It is now primary and the newest one. let cert = cert.merge_public_and_secret( Cert::from_bytes( crate::tests::key("primary-key-2-make-aaaaa-primary.pgp") ).unwrap()).unwrap(); assert_eq!(cert.with_policy(p, selfsig0).unwrap() .primary_userid().unwrap().userid().value(), b"aaaaa"); assert_eq!(cert.with_policy(p, now).unwrap() .primary_userid().unwrap().userid().value(), b"aaaaa"); // Update the preferences on bbbbb. It is now the newest, but // it is not marked as primary. let cert = cert.merge_public_and_secret( Cert::from_bytes( crate::tests::key("primary-key-3-make-bbbbb-new-self-sig.pgp") ).unwrap()).unwrap(); assert_eq!(cert.with_policy(p, selfsig0).unwrap() .primary_userid().unwrap().userid().value(), b"aaaaa"); assert_eq!(cert.with_policy(p, now).unwrap() .primary_userid().unwrap().userid().value(), b"aaaaa"); // Mark bbbbb as primary. It is now the newest and marked as // primary. let cert = cert.merge_public_and_secret( Cert::from_bytes( crate::tests::key("primary-key-4-make-bbbbb-primary.pgp") ).unwrap()).unwrap(); assert_eq!(cert.with_policy(p, selfsig0).unwrap() .primary_userid().unwrap().userid().value(), b"aaaaa"); assert_eq!(cert.with_policy(p, now).unwrap() .primary_userid().unwrap().userid().value(), b"bbbbb"); // Update the preferences on aaaaa. It is now has the newest // self sig, but that self sig does not say that it is // primary. let cert = cert.merge_public_and_secret( Cert::from_bytes( crate::tests::key("primary-key-5-make-aaaaa-self-sig.pgp") ).unwrap()).unwrap(); assert_eq!(cert.with_policy(p, selfsig0).unwrap() .primary_userid().unwrap().userid().value(), b"aaaaa"); assert_eq!(cert.with_policy(p, now).unwrap() .primary_userid().unwrap().userid().value(), b"bbbbb"); // Hard revoke aaaaa. Unlike with Certs, a hard revocation is // not treated specially. let cert = cert.merge_public_and_secret( Cert::from_bytes( crate::tests::key("primary-key-6-revoked-aaaaa.pgp") ).unwrap()).unwrap(); assert_eq!(cert.with_policy(p, selfsig0).unwrap() .primary_userid().unwrap().userid().value(), b"aaaaa"); assert_eq!(cert.with_policy(p, now).unwrap() .primary_userid().unwrap().userid().value(), b"bbbbb"); } #[test] fn binding_signature_lookup() { // Check that searching for the right binding signature works // even when there are signatures with the same time. use crate::types::Features; use crate::packet::key::Key6; let p = &P::new(); let a_sec = time::Duration::new(1, 0); let time_zero = time::UNIX_EPOCH; let t1 = time::UNIX_EPOCH + time::Duration::new(946681200, 0); // 2000-1-1 let t2 = time::UNIX_EPOCH + time::Duration::new(978303600, 0); // 2001-1-1 let t3 = time::UNIX_EPOCH + time::Duration::new(1009839600, 0); // 2002-1-1 let t4 = time::UNIX_EPOCH + time::Duration::new(1041375600, 0); // 2003-1-1 let mut key: key::SecretKey = Key6::generate_ecc(true, Curve::Ed25519).unwrap().into(); key.set_creation_time(t1).unwrap(); let mut pair = key.clone().into_keypair().unwrap(); let pk : key::PublicKey = key.clone().into(); let mut cert = Cert::try_from(vec![ pk.into(), ]).unwrap(); let uid: UserID = "foo@example.org".into(); let sig = uid.certify(&mut pair, &cert, SignatureType::PositiveCertification, None, t1).unwrap(); cert = cert.insert_packets( vec![Packet::from(uid), sig.into()]).unwrap().0; const N: usize = 5; for (t, offset) in &[ (t2, 0), (t4, 0), (t3, 1 * N), (t1, 3 * N) ] { for i in 0..N { let binding = signature::SignatureBuilder::new(SignatureType::DirectKey) .set_features(Features::sequoia()).unwrap() .set_key_flags(KeyFlags::empty()).unwrap() .set_signature_creation_time(t1).unwrap() // Vary this... .set_key_validity_period(Some( time::Duration::new((1 + i as u64) * 24 * 60 * 60, 0))) .unwrap() .set_preferred_hash_algorithms(vec![HashAlgorithm::SHA512]).unwrap() .set_signature_creation_time(*t).unwrap() .sign_direct_key(&mut pair, key.parts_as_public()).unwrap(); let binding : Packet = binding.into(); cert = cert.insert_packets(binding).unwrap().0; // A time that matches multiple signatures. let direct_signatures = cert.primary_key().bundle().self_signatures() .collect::<Vec<_>>(); assert_eq!(cert.primary_key().with_policy(p, *t).unwrap() .direct_key_signature().ok(), direct_signatures.get(*offset).cloned()); // A time that doesn't match any signature. assert_eq!(cert.primary_key().with_policy(p, *t + a_sec).unwrap() .direct_key_signature().ok(), direct_signatures.get(*offset).cloned()); // The current time, which should use the first signature. assert_eq!(cert.primary_key().with_policy(p, None).unwrap() .direct_key_signature().ok(), direct_signatures.get(0).cloned()); // The beginning of time, which should return no // binding signatures. assert!(cert.primary_key().with_policy(p, time_zero).is_err()); } } } #[test] fn keysigning_party() { use crate::packet::signature; for cs in &[ CipherSuite::Cv25519, CipherSuite::P256, CipherSuite::P384, CipherSuite::P521, CipherSuite::RSA2k ] { if cs.is_supported().is_err() { eprintln!("Skipping {:?} because it is not supported.", cs); continue; } let (alice, _) = CertBuilder::new() .set_cipher_suite(*cs) .add_userid("alice@foo.com") .generate().unwrap(); let (bob, _) = CertBuilder::new() .set_cipher_suite(*cs) .add_userid("bob@bar.com") .add_signing_subkey() .generate().unwrap(); assert_eq!(bob.userids().len(), 1); let bob_userid_binding = bob.userids().next().unwrap(); assert_eq!(bob_userid_binding.userid().value(), b"bob@bar.com"); let sig_template = signature::SignatureBuilder::new(SignatureType::GenericCertification) .set_trust_signature(255, 120) .unwrap(); // Have alice certify the binding "bob@bar.com" and bob's key. let alice_certifies_bob = bob_userid_binding.userid().bind( &mut alice.primary_key().key().clone().parts_into_secret() .unwrap().into_keypair().unwrap(), &bob, sig_template).unwrap(); let bob = bob.insert_packets(alice_certifies_bob.clone()).unwrap().0; // Make sure the certification is merged, and put in the right // place. assert_eq!(bob.userids().len(), 1); let bob_userid_binding = bob.userids().next().unwrap(); assert_eq!(bob_userid_binding.userid().value(), b"bob@bar.com"); // Canonicalizing Bob's cert without having Alice's key // has to resort to a heuristic to order third party // signatures. However, since we know the signature's // type (GenericCertification), we know that it can only // go to the only userid, so there is no ambiguity in this // case. assert_eq!(bob_userid_binding.certifications().collect::<Vec<_>>(), vec![&alice_certifies_bob]); // Make sure the certification is correct. alice_certifies_bob .verify_userid_binding(alice.primary_key().key(), bob.primary_key().key(), bob_userid_binding.userid()).unwrap(); } } #[test] fn decrypt_encrypt_secrets() -> Result<()> { let p: crate::crypto::Password = "streng geheim".into(); let (mut cert, _) = CertBuilder::new() .add_transport_encryption_subkey() .set_password(Some(p.clone())) .generate()?; assert_eq!(cert.keys().secret().count(), 2); assert_eq!(cert.keys().unencrypted_secret().count(), 0); for (i, ka) in cert.clone().keys().secret().enumerate() { let key = ka.key().clone().decrypt_secret(&p)?; cert = if i == 0 { cert.insert_packets(key.role_into_primary())?.0 } else { cert.insert_packets(key.role_into_subordinate())?.0 }; assert_eq!(cert.keys().secret().count(), 2); assert_eq!(cert.keys().unencrypted_secret().count(), i + 1); } assert_eq!(cert.keys().secret().count(), 2); assert_eq!(cert.keys().unencrypted_secret().count(), 2); for (i, ka) in cert.clone().keys().secret().enumerate() { let key = ka.key().clone().encrypt_secret(&p)?; cert = if i == 0 { cert.insert_packets(key.role_into_primary())?.0 } else { cert.insert_packets(key.role_into_subordinate())?.0 }; assert_eq!(cert.keys().secret().count(), 2); assert_eq!(cert.keys().unencrypted_secret().count(), 2 - 1 - i); } assert_eq!(cert.keys().secret().count(), 2); assert_eq!(cert.keys().unencrypted_secret().count(), 0); Ok(()) } /// Tests that Cert:.into_packets() and Cert::serialize(..) agree. #[test] fn test_into_packets() -> Result<()> { use crate::serialize::SerializeInto; let dkg = Cert::from_bytes(crate::tests::key("dkg.gpg"))?; let mut buf = Vec::new(); for p in dkg.clone().into_packets() { p.serialize(&mut buf)?; } let dkg = dkg.to_vec()?; if false && buf != dkg { std::fs::write("/tmp/buf", &buf)?; std::fs::write("/tmp/dkg", &dkg)?; } assert_eq!(buf, dkg); Ok(()) } #[test] fn test_canonicalization() -> Result<()> { let p = crate::policy::StandardPolicy::new(); let primary: Key<_, key::PrimaryRole> = key::Key6::generate_ecc(true, Curve::Ed25519)?.into(); let cert = Cert::try_from(vec![primary.into()])?; // We now add components without binding signatures. They // should be kept, be enumerable, but ignored if a policy is // applied. // Add a bare userid. let uid = UserID::from("foo@example.org"); let cert = cert.insert_packets(uid)?.0; assert_eq!(cert.userids().count(), 1); assert_eq!(cert.userids().with_policy(&p, None).count(), 0); // Add a bare user attribute. use packet::user_attribute::{Subpacket, Image}; let ua = UserAttribute::new(&[ Subpacket::Image( Image::Private(100, vec![0, 1, 2].into_boxed_slice())), ])?; let cert = cert.insert_packets(ua)?.0; assert_eq!(cert.user_attributes().count(), 1); assert_eq!(cert.user_attributes().with_policy(&p, None).count(), 0); // Add a bare signing subkey. let signing_subkey: Key<_, key::SubordinateRole> = key::Key6::generate_ecc(true, Curve::Ed25519)?.into(); let _signing_subkey_pair = signing_subkey.clone().into_keypair()?; let cert = cert.insert_packets(signing_subkey)?.0; assert_eq!(cert.keys().subkeys().count(), 1); assert_eq!(cert.keys().subkeys().with_policy(&p, None).count(), 0); // Add a component that Sequoia doesn't understand. let mut fake_key = packet::Unknown::new( packet::Tag::PublicSubkey, anyhow::anyhow!("fake key")); fake_key.set_body("fake key".into()); let cert = cert.insert_packets(vec![Packet::from(fake_key)])?.0; assert_eq!(cert.unknowns().count(), 1); assert_eq!(cert.unknowns().next().unwrap().unknown().tag(), packet::Tag::PublicSubkey); Ok(()) } #[test] #[allow(deprecated)] fn canonicalize_with_v3_sig() -> Result<()> { if ! crate::types::PublicKeyAlgorithm::DSA.is_supported() { eprintln!("Skipping because DSA is not supported"); return Ok(()); } // This test relies on being able to validate SHA-1 // signatures. The standard policy rejects SHA-1. So, use a // custom policy. let p = &P::new(); let sha1 = p.hash_cutoff( HashAlgorithm::SHA1, HashAlgoSecurity::CollisionResistance) .unwrap(); let p = &P::at(sha1 - std::time::Duration::from_secs(1)); let cert = Cert::from_bytes( crate::tests::key("eike-v3-v4.pgp"))?; dbg!(&cert); assert_eq!(cert.userids() .with_policy(p, None) .count(), 1); Ok(()) } /// Asserts that key expiration times on direct key signatures are /// honored. #[test] fn issue_215() { let p = &P::new(); let cert = Cert::from_bytes(crate::tests::key( "issue-215-expiration-on-direct-key-sig.pgp")).unwrap(); assert_match!( Error::Expired(_) = cert.with_policy(p, None).unwrap().alive() .unwrap_err().downcast().unwrap()); assert_match!( Error::Expired(_) = cert.primary_key().with_policy(p, None).unwrap() .alive().unwrap_err().downcast().unwrap()); } /// Tests that secrets are kept when merging. #[test] fn merge_keeps_secrets() -> Result<()> { let (cert_s, _) = CertBuilder::general_purpose(Some("uid")).generate()?; let cert_p = cert_s.clone().strip_secret_key_material(); // Merge key into cert. let cert = cert_p.clone().merge_public_and_secret(cert_s.clone())?; assert!(cert.keys().all(|ka| ka.has_secret())); // Merge cert into key. let cert = cert_s.clone().merge_public_and_secret(cert_p.clone())?; assert!(cert.keys().all(|ka| ka.has_secret())); Ok(()) } /// Tests that secrets that are merged in are preferred to /// existing secrets. #[test] fn merge_prefers_merged_in_secrets() -> Result<()> { let pw: crate::crypto::Password = "foo".into(); let (cert_encrypted_secrets, _) = CertBuilder::general_purpose(Some("uid")) .set_password(Some(pw.clone())) .generate()?; let mut cert_plain_secrets = cert_encrypted_secrets.clone(); for ka in cert_encrypted_secrets.keys().secret() { assert!(! ka.key().has_unencrypted_secret()); let key = ka.key().clone().decrypt_secret(&pw)?; assert!(key.has_unencrypted_secret()); let key: Packet = if ka.primary() { key.role_into_primary().into() } else { key.role_into_subordinate().into() }; cert_plain_secrets = cert_plain_secrets.insert_packets(vec![key])?.0; } assert!( cert_plain_secrets.keys().all(|ka| ka.key().has_unencrypted_secret())); // Merge unencrypted secrets into encrypted secrets. let cert = cert_encrypted_secrets.clone().merge_public_and_secret( cert_plain_secrets.clone())?; assert!(cert.keys().all(|ka| ka.key().has_unencrypted_secret())); // Merge encrypted secrets into unencrypted secrets. let cert = cert_plain_secrets.clone().merge_public_and_secret( cert_encrypted_secrets.clone())?; assert!(cert.keys().all(|ka| ka.has_secret() && ! ka.key().has_unencrypted_secret())); Ok(()) } /// Tests that secrets are kept when canonicalizing. #[test] fn canonicalizing_keeps_secrets() -> Result<()> { let primary: Key<_, key::PrimaryRole> = key::Key6::generate_ecc(true, Curve::Ed25519)?.into(); let mut primary_pair = primary.clone().into_keypair()?; let cert = Cert::try_from(vec![primary.clone().into()])?; let subkey_sec: Key<_, key::SubordinateRole> = key::Key6::generate_ecc(false, Curve::Cv25519)?.into(); let subkey_pub = subkey_sec.clone().take_secret().0; let builder = signature::SignatureBuilder::new(SignatureType::SubkeyBinding) .set_key_flags(KeyFlags::empty() .set_transport_encryption())?; let binding = subkey_sec.bind(&mut primary_pair, &cert, builder)?; let cert = Cert::try_from(vec![ primary.clone().into(), subkey_pub.clone().into(), binding.clone().into(), subkey_sec.clone().into(), binding.clone().into(), ])?; assert_eq!(cert.keys().subkeys().count(), 1); assert_eq!(cert.keys().unencrypted_secret().subkeys().count(), 1); let cert = Cert::try_from(vec![ primary.clone().into(), subkey_sec.clone().into(), binding.clone().into(), subkey_pub.clone().into(), binding.clone().into(), ])?; assert_eq!(cert.keys().subkeys().count(), 1); assert_eq!(cert.keys().unencrypted_secret().subkeys().count(), 1); Ok(()) } /// Demonstrates that subkeys are kept if a userid is later added /// without any keyflags. #[test] fn issue_361() -> Result<()> { let (cert, _) = CertBuilder::new() .add_transport_encryption_subkey() .generate()?; let p = &P::new(); let cert_at = cert.with_policy(p, cert.primary_key().key().creation_time() + time::Duration::new(300, 0)) .unwrap(); assert_eq!(cert_at.userids().count(), 0); assert_eq!(cert_at.keys().count(), 2); let mut primary_pair = cert.primary_key().key().clone() .parts_into_secret()?.into_keypair()?; let uid: UserID = "foo@example.org".into(); let sig = uid.bind( &mut primary_pair, &cert, signature::SignatureBuilder::new(SignatureType::PositiveCertification))?; let cert = cert.insert_packets(vec![ Packet::from(uid), sig.into(), ])?.0; let cert_at = cert.with_policy(p, cert.primary_key().key().creation_time() + time::Duration::new(300, 0)) .unwrap(); assert_eq!(cert_at.userids().count(), 1); assert_eq!(cert_at.keys().count(), 2); Ok(()) } /// Demonstrates that binding signatures are considered valid even /// if the primary key is not marked as certification-capable. #[test] fn issue_321() -> Result<()> { let cert = Cert::from_bytes( crate::tests::file("contrib/pep/pEpkey-netpgp.asc"))?; assert_eq!(cert.userids().count(), 1); assert_eq!(cert.keys().count(), 1); let mut p = P::new(); p.accept_hash(HashAlgorithm::SHA1); let cert_at = cert.with_policy(&p, cert.primary_key().key().creation_time()) .unwrap(); assert_eq!(cert_at.userids().count(), 1); assert_eq!(cert_at.keys().count(), 1); Ok(()) } #[test] fn policy_uri_some() -> Result<()> { use crate::packet::prelude::SignatureBuilder; use crate::policy::StandardPolicy; let p = &StandardPolicy::new(); let (alice, _) = CertBuilder::new().add_userid("Alice").generate()?; let sig = SignatureBuilder::from( alice .with_policy(p, None)? .direct_key_signature().expect("Direct key signature") .clone() ) .set_policy_uri("https://example.org/~alice/signing-policy.txt")?; assert_eq!(sig.policy_uri(), Some("https://example.org/~alice/signing-policy.txt".as_bytes())); Ok(()) } #[test] fn policy_uri_none() -> Result<()> { use crate::packet::prelude::SignatureBuilder; use crate::policy::StandardPolicy; let p = &StandardPolicy::new(); let (alice, _) = CertBuilder::new().add_userid("Alice").generate()?; let sig = SignatureBuilder::from( alice .with_policy(p, None)? .direct_key_signature().expect("Direct key signature") .clone() ); assert_eq!(sig.policy_uri(), None); Ok(()) } #[test] #[allow(deprecated)] fn different_preferences() -> Result<()> { use crate::cert::Preferences; let p = &crate::policy::StandardPolicy::new(); // This key returns different preferences depending on how you // address it. (It has two user ids and the user ids have // different preference packets on their respective self // signatures.) let cert = Cert::from_bytes( crate::tests::key("different-preferences.asc"))?; assert_eq!(cert.userids().count(), 2); if let Some(userid) = cert.userids().next() { assert_eq!(userid.userid().value(), &b"Alice Confusion <alice@example.com>"[..]); let userid = userid.with_policy(p, None).expect("valid"); use crate::types::SymmetricAlgorithm::*; assert_eq!(userid.preferred_symmetric_algorithms(), Some(&[ AES256, AES192, AES128, TripleDES ][..])); use crate::types::HashAlgorithm::*; assert_eq!(userid.preferred_hash_algorithms(), Some(&[ SHA512, SHA384, SHA256, SHA224, SHA1 ][..])); use crate::types::CompressionAlgorithm::*; assert_eq!(userid.preferred_compression_algorithms(), Some(&[ Zlib, BZip2, Zip ][..])); assert_eq!(userid.preferred_aead_ciphersuites(), None); // assert_eq!(userid.key_server_preferences(), // Some(KeyServerPreferences::new(&[]))); assert_eq!(userid.features(), Some(Features::new(&[]).set_seipdv1())); } else { panic!("two user ids"); } if let Some(userid) = cert.userids().next() { assert_eq!(userid.userid().value(), &b"Alice Confusion <alice@example.com>"[..]); let userid = userid.with_policy(p, None).expect("valid"); use crate::types::SymmetricAlgorithm::*; assert_eq!(userid.preferred_symmetric_algorithms(), Some(&[ AES256, AES192, AES128, TripleDES ][..])); use crate::types::HashAlgorithm::*; assert_eq!(userid.preferred_hash_algorithms(), Some(&[ SHA512, SHA384, SHA256, SHA224, SHA1 ][..])); use crate::types::CompressionAlgorithm::*; assert_eq!(userid.preferred_compression_algorithms(), Some(&[ Zlib, BZip2, Zip ][..])); assert_eq!(userid.preferred_aead_ciphersuites(), None); assert_eq!(userid.key_server_preferences(), Some(KeyServerPreferences::new(&[0x80]))); assert_eq!(userid.features(), Some(Features::new(&[]).set_seipdv1())); // Using the certificate should choose the primary user // id, which is this one (because it is lexicographically // earlier). let cert = cert.with_policy(p, None).expect("valid"); assert_eq!(userid.preferred_symmetric_algorithms(), cert.preferred_symmetric_algorithms()); assert_eq!(userid.preferred_hash_algorithms(), cert.preferred_hash_algorithms()); assert_eq!(userid.preferred_compression_algorithms(), cert.preferred_compression_algorithms()); assert_eq!(userid.preferred_aead_ciphersuites(), cert.preferred_aead_ciphersuites()); assert_eq!(userid.key_server_preferences(), cert.key_server_preferences()); assert_eq!(userid.features(), cert.features()); } else { panic!("two user ids"); } if let Some(userid) = cert.userids().nth(1) { assert_eq!(userid.userid().value(), &b"Alice Confusion <alice@example.net>"[..]); let userid = userid.with_policy(p, None).expect("valid"); use crate::types::SymmetricAlgorithm::*; assert_eq!(userid.preferred_symmetric_algorithms(), Some(&[ AES192, AES256, AES128, TripleDES ][..])); use crate::types::HashAlgorithm::*; assert_eq!(userid.preferred_hash_algorithms(), Some(&[ SHA384, SHA512, SHA256, SHA224, SHA1 ][..])); use crate::types::CompressionAlgorithm::*; assert_eq!(userid.preferred_compression_algorithms(), Some(&[ BZip2, Zlib, Zip ][..])); assert_eq!(userid.preferred_aead_ciphersuites(), None); assert_eq!(userid.key_server_preferences(), Some(KeyServerPreferences::new(&[0x80]))); assert_eq!(userid.features(), Some(Features::new(&[]).set_seipdv1())); } else { panic!("two user ids"); } Ok(()) } #[test] fn unsigned_components() -> Result<()> { // We have a certificate with an unsigned User ID, User // Attribute, encryption-capable subkey, and signing-capable // subkey. (Actually, they are signed, but the signatures are // bad.) We expect that when we parse such a certificate the // unsigned components are not dropped and they appear when // iterating over the components using, e.g., Cert::userids, // but not when we check for valid components. let p = &crate::policy::StandardPolicy::new(); let cert = Cert::from_bytes( crate::tests::key("certificate-with-unsigned-components.asc"))?; assert_eq!(cert.userids().count(), 2); assert_eq!(cert.userids().with_policy(p, None).count(), 1); assert_eq!(cert.user_attributes().count(), 2); assert_eq!(cert.user_attributes().with_policy(p, None).count(), 1); assert_eq!(cert.keys().count(), 1 + 4); assert_eq!(cert.keys().with_policy(p, None).count(), 1 + 2); Ok(()) } #[test] fn issue_504() -> Result<()> { let mut keyring = crate::tests::key("testy.pgp").to_vec(); keyring.extend_from_slice(crate::tests::key("testy-new.pgp")); // TryFrom<PacketPile> let pp = PacketPile::from_bytes(&keyring)?; assert!(matches!( Cert::try_from(pp.clone()).unwrap_err().downcast().unwrap(), Error::MalformedCert(_) )); // Cert::TryFrom<Vec<Packet>> let v: Vec<Packet> = pp.into(); assert!(matches!( Cert::try_from(v.clone()).unwrap_err().downcast().unwrap(), Error::MalformedCert(_) )); // Cert::from_packet assert!(matches!( Cert::from_packets(v.into_iter()).unwrap_err().downcast().unwrap(), Error::MalformedCert(_) )); // Cert::TryFrom<PacketParserResult> let ppr = PacketParser::from_bytes(&keyring)?; assert!(matches!( Cert::try_from(ppr).unwrap_err().downcast().unwrap(), Error::MalformedCert(_) )); Ok(()) } /// Tests whether the policy is applied to primary key binding /// signatures. #[test] fn issue_531() -> Result<()> { let cert = Cert::from_bytes(crate::tests::key("peter-sha1-backsig.pgp"))?; let p = unsafe { &crate::policy::NullPolicy::new() }; assert_eq!(cert.with_policy(p, None)?.keys().for_signing().count(), 1); let mut p = crate::policy::StandardPolicy::new(); p.reject_hash(HashAlgorithm::SHA1); assert_eq!(cert.with_policy(&p, None)?.keys().for_signing().count(), 0); Ok(()) } /// Tests whether expired primary key binding signatures are /// rejected. #[test] fn issue_539() -> Result<()> { let cert = Cert::from_bytes(crate::tests::key("peter-expired-backsig.pgp"))?; let p = unsafe { &crate::policy::NullPolicy::new() }; assert_eq!(cert.with_policy(p, None)?.keys().for_signing().count(), 0); let p = &crate::policy::StandardPolicy::new(); assert_eq!(cert.with_policy(p, None)?.keys().for_signing().count(), 0); Ok(()) } /// Tests whether signatures are properly deduplicated. #[test] fn issue_568() -> Result<()> { use crate::packet::signature::subpacket::*; let (cert, _) = CertBuilder::general_purpose( Some("alice@example.org")).generate().unwrap(); assert_eq!(cert.userids().count(), 1); assert_eq!(cert.subkeys().count(), 2); assert_eq!(cert.unknowns().count(), 0); assert_eq!(cert.bad_signatures().count(), 0); assert_eq!(cert.userids().next().unwrap().self_signatures().count(), 1); assert_eq!(cert.subkeys().next().unwrap().self_signatures().count(), 1); assert_eq!(cert.subkeys().nth(1).unwrap().self_signatures().count(), 1); // Create a variant of cert where the signatures have // additional information in the unhashed area. let cert_b = cert.clone(); let mut packets = crate::PacketPile::from(cert_b).into_children() .collect::<Vec<_>>(); for p in packets.iter_mut() { if let Packet::Signature(sig) = p { assert_eq!(sig.hashed_area().subpackets( SubpacketTag::IssuerFingerprint).count(), 1); sig.unhashed_area_mut().add(Subpacket::new( SubpacketValue::Issuer("AAAA BBBB CCCC DDDD".parse()?), false)?)?; } } let cert_b = Cert::from_packets(packets.into_iter())?; let cert = cert.merge_public_and_secret(cert_b)?; assert_eq!(cert.userids().count(), 1); assert_eq!(cert.subkeys().count(), 2); assert_eq!(cert.unknowns().count(), 0); assert_eq!(cert.bad_signatures().count(), 0); assert_eq!(cert.userids().next().unwrap().self_signatures().count(), 1); assert_eq!(cert.subkeys().next().unwrap().self_signatures().count(), 1); assert_eq!(cert.subkeys().nth(1).unwrap().self_signatures().count(), 1); Ok(()) } /// Checks that missing or bad embedded signatures cause the /// signature to be considered bad. #[test] fn missing_backsig_is_bad() -> Result<()> { use crate::packet::{ key::Key6, signature::{ SignatureBuilder, subpacket::{Subpacket, SubpacketValue}, }, }; // We'll study this certificate, because it contains a // signing-capable subkey. let cert = crate::Cert::from_bytes(crate::tests::key( "emmelie-dorothea-dina-samantha-awina-ed25519.pgp"))?; let mut pp = crate::PacketPile::from_bytes(crate::tests::key( "emmelie-dorothea-dina-samantha-awina-ed25519.pgp"))?; assert_eq!(pp.children().count(), 5); if let Some(Packet::Signature(sig)) = pp.path_ref_mut(&[4]) { // Add a bogus but plausible embedded signature subpacket. let key: key::SecretKey = Key6::generate_ecc(true, Curve::Ed25519)?.into(); let mut pair = key.into_keypair()?; sig.unhashed_area_mut().replace(Subpacket::new( SubpacketValue::EmbeddedSignature( SignatureBuilder::new(SignatureType::PrimaryKeyBinding) .sign_primary_key_binding( &mut pair, cert.primary_key().key(), cert.keys().subkeys().next().unwrap().key())?), false)?)?; } else { panic!("expected a signature"); } // Parse into cert. use std::convert::TryFrom; let malicious_cert = Cert::try_from(pp)?; // The subkey binding signature should no longer check out. let p = &crate::policy::StandardPolicy::new(); assert_eq!(malicious_cert.with_policy(p, None)?.keys().subkeys() .for_signing().count(), 0); // Instead, it should be considered bad. assert_eq!(malicious_cert.bad_signatures().count(), 1); Ok(()) } /// Checks that multiple embedded signatures are correctly /// handled. #[test] fn multiple_embedded_signatures() -> Result<()> { use crate::packet::{ key::Key6, signature::{ SignatureBuilder, subpacket::{Subpacket, SubpacketValue}, }, }; // We'll study this certificate, because it contains a // signing-capable subkey. let cert = crate::Cert::from_bytes(crate::tests::key( "emmelie-dorothea-dina-samantha-awina-ed25519.pgp"))?; // Add a bogus but plausible embedded signature subpacket with // this key. let key: key::SecretKey = Key6::generate_ecc(true, Curve::Ed25519)?.into(); let mut pair = key.into_keypair()?; // Create a malicious cert to merge in. let mut pp = crate::PacketPile::from_bytes(crate::tests::key( "emmelie-dorothea-dina-samantha-awina-ed25519.pgp"))?; assert_eq!(pp.children().count(), 5); if let Some(Packet::Signature(sig)) = pp.path_ref_mut(&[4]) { // Prepend a bad backsig. let backsig = sig.embedded_signatures().next().unwrap().clone(); sig.unhashed_area_mut().replace(Subpacket::new( SubpacketValue::EmbeddedSignature( SignatureBuilder::new(SignatureType::PrimaryKeyBinding) .sign_primary_key_binding( &mut pair, cert.primary_key().key(), cert.keys().subkeys().next().unwrap().key())?), false)?)?; sig.unhashed_area_mut().add(Subpacket::new( SubpacketValue::EmbeddedSignature(backsig), false)?)?; } else { panic!("expected a signature"); } // Parse into cert. use std::convert::TryFrom; let malicious_cert = Cert::try_from(pp)?; // The subkey binding signature should still be fine. let p = &crate::policy::StandardPolicy::new(); assert_eq!(malicious_cert.with_policy(p, None)?.keys().subkeys() .for_signing().count(), 1); assert_eq!(malicious_cert.bad_signatures().count(), 0); // Now try to merge it in. let merged = cert.clone().merge_public_and_secret(malicious_cert.clone())?; // The subkey binding signature should still be fine. assert_eq!(merged.with_policy(p, None)?.keys().subkeys() .for_signing().count(), 1); let sig = merged.with_policy(p, None)?.keys().subkeys() .for_signing().next().unwrap().binding_signature(); assert_eq!(sig.embedded_signatures().count(), 2); // Now the other way around. let merged = malicious_cert.clone().merge_public_and_secret(cert.clone())?; // The subkey binding signature should still be fine. assert_eq!(merged.with_policy(p, None)?.keys().subkeys() .for_signing().count(), 1); let sig = merged.with_policy(p, None)?.keys().subkeys() .for_signing().next().unwrap().binding_signature(); assert_eq!(sig.embedded_signatures().count(), 2); Ok(()) } /// Checks that Cert::merge(cert, cert) == cert. #[test] fn issue_579() -> Result<()> { use std::convert::TryFrom; use crate::packet::signature::subpacket::SubpacketTag; let mut pp = crate::PacketPile::from_bytes(crate::tests::key( "emmelie-dorothea-dina-samantha-awina-ed25519.pgp"))?; assert_eq!(pp.children().count(), 5); // Drop issuer information from the unhashed areas. if let Some(Packet::Signature(sig)) = pp.path_ref_mut(&[2]) { sig.unhashed_area_mut().remove_all(SubpacketTag::Issuer); } else { panic!("expected a signature"); } if let Some(Packet::Signature(sig)) = pp.path_ref_mut(&[4]) { sig.unhashed_area_mut().remove_all(SubpacketTag::Issuer); } else { panic!("expected a signature"); } let cert = Cert::try_from(pp)?; assert_eq!(cert.clone().merge_public_and_secret(cert.clone())?, cert); Ok(()) } /// Checks that Cert::merge_public ignores secret key material. #[test] fn merge_public() -> Result<()> { let cert = Cert::from_bytes(crate::tests::key("testy-new.pgp"))?; let key = Cert::from_bytes(crate::tests::key("testy-new-private.pgp"))?; assert!(! cert.is_tsk()); assert!(key.is_tsk()); // Secrets are ignored in `other`. let merged = cert.clone().merge_public(key.clone())?; assert!(! merged.is_tsk()); assert_eq!(merged, cert); // Secrets are retained in `self`. let merged = key.clone().merge_public(cert.clone())?; assert!(merged.is_tsk()); assert_eq!(merged, key); Ok(()) } /// Make sure we can parse a key where the primary key is its own /// subkeys. #[test] fn primary_key_is_subkey() -> Result<()> { let p = &crate::policy::StandardPolicy::new(); let cert = Cert::from_bytes(crate::tests::key("primary-key-is-also-subkey.pgp"))?; // There should be three keys: // // Fingerprint: 8E8C 33FA 4626 3379 76D9 7978 069C 0C34 8DD8 2C19 // Public-key algo: EdDSA Edwards-curve Digital Signature Algorithm // Public-key size: 256 bits // Secret key: Unencrypted // Creation time: 2018-06-11 14:12:09 UTC // Key flags: certification, signing // // Subkey: 8E8C 33FA 4626 3379 76D9 7978 069C 0C34 8DD8 2C19 // Public-key algo: EdDSA Edwards-curve Digital Signature Algorithm // Public-key size: 256 bits // Secret key: Unencrypted // Creation time: 2018-06-11 14:12:09 UTC // Key flags: certification, signing // // Subkey: 061C 3CA4 4AFF 0EC5 8DC6 6E95 22E3 FAFE 96B5 6C32 // Public-key algo: EdDSA Edwards-curve Digital Signature Algorithm // Public-key size: 256 bits // Secret key: Unencrypted // Creation time: 2018-08-27 10:55:43 UTC // Key flags: signing // // UserID: Emmelie Dorothea Dina Samantha Awina Ed25519 assert_eq!(cert.keys().count(), 3); // Make sure there is a subkey with the same fingerprint as // the primary key. assert!(cert.keys().subkeys().any(|k| { k.key().fingerprint() == cert.primary_key().key().fingerprint() })); // Make sure the self sig is valid, too. assert_eq!(cert.keys().count(), 3); let vc = cert.with_policy(p, None)?; assert!(vc.keys().subkeys().any(|k| { k.key().fingerprint() == vc.primary_key().key().fingerprint() })); Ok(()) } /// Makes sure that certification approval key signatures are /// correctly handled. #[test] fn certificaton_approval_signatures() -> Result<()> { use crate::{ packet::signature::SignatureBuilder, types::*, }; let p = &crate::policy::StandardPolicy::new(); let (alice, _) = CertBuilder::new() .add_userid("alice@foo.com") .generate()?; let mut alice_signer = alice.primary_key().key().clone().parts_into_secret()? .into_keypair()?; let (bob, _) = CertBuilder::new() .add_userid("bob@bar.com") .generate()?; let mut bob_signer = bob.primary_key().key().clone().parts_into_secret()? .into_keypair()?; let bob_pristine = bob.clone(); // Have Alice certify the binding between "bob@bar.com" and // Bob's key. let alice_certifies_bob = bob.userids().next().unwrap().userid().bind( &mut alice_signer, &bob, SignatureBuilder::new(SignatureType::GenericCertification))?; let bob = bob.insert_packets(vec![ alice_certifies_bob.clone(), ])?.0; assert_eq!(bob.with_policy(p, None)?.userids().next().unwrap() .certifications().count(), 1); assert_eq!(bob.with_policy(p, None)?.userids().next().unwrap() .approved_certifications().count(), 0); // Have Bob attest that certification. let attestations = bob.userids().next().unwrap().approve_of_certifications( p, None, &mut bob_signer, vec![&alice_certifies_bob])?; assert_eq!(attestations.len(), 1); let attestation = attestations[0].clone(); let bob = bob.insert_packets(vec![ attestation.clone(), ])?.0; assert_eq!(bob.bad_signatures().count(), 0); assert_eq!(bob.userids().next().unwrap().certifications().next(), Some(&alice_certifies_bob)); assert_eq!(bob.userids().next().unwrap().bundle().approvals().next().unwrap(), &attestation); assert_eq!(bob.with_policy(p, None)?.userids().next().unwrap() .certifications().count(), 1); assert_eq!(bob.with_policy(p, None)?.userids().next().unwrap() .approved_certifications().count(), 1); // Check that attested key signatures are kept over merges. let bob_ = bob.clone().merge_public(bob_pristine.clone())?; assert_eq!(bob_.bad_signatures().count(), 0); assert_eq!(bob_.userids().next().unwrap().certifications().next(), Some(&alice_certifies_bob)); assert_eq!(bob_.userids().next().unwrap().bundle().approvals().next().unwrap(), &attestation); assert_eq!(bob_.with_policy(p, None)?.userids().next().unwrap() .approved_certifications().count(), 1); // And the other way around. let bob_ = bob_pristine.clone().merge_public(bob.clone())?; assert_eq!(bob_.bad_signatures().count(), 0); assert_eq!(bob_.userids().next().unwrap().certifications().next(), Some(&alice_certifies_bob)); assert_eq!(bob_.userids().next().unwrap().bundle().approvals().next().unwrap(), &attestation); assert_eq!(bob_.with_policy(p, None)?.userids().next().unwrap() .approved_certifications().count(), 1); // Have Bob withdraw any prior attestations. let attestations = bob.userids().next().unwrap().approve_of_certifications( p, None, &mut bob_signer, &[])?; assert_eq!(attestations.len(), 1); let attestation = attestations[0].clone(); let bob = bob.insert_packets(vec![ attestation.clone(), ])?.0; assert_eq!(bob.bad_signatures().count(), 0); assert_eq!(bob.userids().next().unwrap().certifications().next(), Some(&alice_certifies_bob)); assert_eq!(bob.userids().next().unwrap().bundle().approvals().next().unwrap(), &attestation); assert_eq!(bob.with_policy(p, None)?.userids().next().unwrap() .certifications().count(), 1); assert_eq!(bob.with_policy(p, None)?.userids().next().unwrap() .approved_certifications().count(), 0); Ok(()) } /// Makes sure that certification approval key signatures are /// correctly handled. #[test] fn certification_approval_key_signatures_dkgpg() -> Result<()> { const DUMP: bool = false; let p = &crate::policy::StandardPolicy::new(); let test = Cert::from_bytes(crate::tests::key("1pa3pc-dkgpg.pgp"))?; assert_eq!(test.bad_signatures().count(), 0); assert_eq!(test.userids().next().unwrap().certifications().count(), 1); assert_eq!(test.userids().next().unwrap().bundle().approvals().count(), 1); let attestation = test.userids().next().unwrap().bundle().approvals().next().unwrap(); if DUMP { for (i, d) in attestation.approved_certifications()?.enumerate() { crate::fmt::hex::Dumper::new(std::io::stderr(), "") .write(d, format!("expected digest {}", i))?; } } let digests: std::collections::HashSet<_> = attestation.approved_certifications()?.collect(); for (i, certification) in test.userids().next().unwrap().certifications().enumerate() { // Hash the certification. let mut h = attestation.hash_algo().context()? .for_signature(attestation.version()); certification.hash_for_confirmation(&mut h)?; let digest = h.into_digest()?; if DUMP { crate::fmt::hex::Dumper::new(std::io::stderr(), "") .write(&digest, format!("computed digest {}", i))?; } assert!(digests.contains(&digest[..])); } assert_eq!(test.with_policy(p, None)?.userids().next().unwrap() .certifications().count(), 1); assert_eq!(test.with_policy(p, None)?.userids().next().unwrap() .approved_certifications().count(), 1); Ok(()) } /// Makes sure that certification approval key signatures are /// correctly reordered. #[test] fn certification_approval_key_signature_out_of_order() -> Result<()> { let p = &crate::policy::StandardPolicy::new(); let (alice, _) = CertBuilder::general_purpose( Some("alice@example.org")).generate().unwrap(); assert!(alice.keys().subkeys().count() > 0); let mut alice_signer = alice.primary_key().key().clone().parts_into_secret()? .into_keypair()?; // Now, create new attestation signatures. let mut attestation_signatures = Vec::new(); for uid in alice.userids() { attestation_signatures.append(&mut uid.approve_of_certifications( p, None, &mut alice_signer, uid.certifications(), )?); } // Add the new signatures. This appends the attestation // signature so that it is considered part of last component, // a subkey. let alice2 = alice.insert_packets(attestation_signatures)?.0; // Now we make sure the attestation signature was correctly reordered. assert_eq!(alice2.bad_signatures().count(), 0); let ua = alice2.userids().next().unwrap(); assert_eq!(ua.approvals().count(), 1); Ok(()) } /// Makes sure that marker packets are ignored when parsing certs. #[test] fn marker_packets() -> Result<()> { let cert = Cert::from_bytes(crate::tests::key("neal.pgp"))?; let mut buf = Vec::new(); Packet::Marker(Default::default()).serialize(&mut buf)?; cert.serialize(&mut buf)?; let cert_ = Cert::from_bytes(&buf)?; assert_eq!(cert, cert_); Ok(()) } /// Checks that messing with a revocation signature merely /// invalidates the signature and keeps the cert's revocation /// status unchanged. #[test] fn issue_486() -> Result<()> { use crate::{ crypto::mpi, types::RevocationStatus::*, packet::signature::Signature4, policy::StandardPolicy, }; let p = &StandardPolicy::new(); let (cert, revocation) = CertBuilder::new().generate()?; // Base case. let c = cert.clone().insert_packets(Some(revocation.clone()))?.0; if let Revoked(_) = c.revocation_status(p, None) { // cert is considered revoked } else { panic!("Should be revoked, but is not: {:?}", c.revocation_status(p, None)); } // Breaking the revocation signature by changing the MPIs. let c = cert.clone().insert_packets(Some( Signature4::new( revocation.typ(), revocation.pk_algo(), revocation.hash_algo(), revocation.hashed_area().clone(), revocation.unhashed_area().clone(), *revocation.digest_prefix(), // MPI is replaced with a dummy one mpi::Signature::RSA { s: mpi::MPI::from(vec![1, 2, 3]) })))?.0; if let NotAsFarAsWeKnow = c.revocation_status(p, None) { assert_eq!(c.bad_signatures().count(), 1); } else { panic!("Should not be revoked, but is: {:?}", c.revocation_status(p, None)); } // Breaking the revocation signature by changing the MPIs and // the digest prefix. let c = cert.clone().insert_packets(Some( Signature4::new( revocation.typ(), revocation.pk_algo(), revocation.hash_algo(), revocation.hashed_area().clone(), revocation.unhashed_area().clone(), // Prefix replaced with a dummy one [0, 1], // MPI is replaced with a dummy one mpi::Signature::RSA { s: mpi::MPI::from(vec![1, 2, 3]) })))?.0; if let NotAsFarAsWeKnow = c.revocation_status(p, None) { assert_eq!(c.bad_signatures().count(), 1); } else { panic!("Should not be revoked, but is: {:?}", c.revocation_status(p, None)); } Ok(()) } /// Tests v3 binding signatures. #[test] #[allow(deprecated)] fn v3_binding_signature() -> Result<()> { if ! crate::types::PublicKeyAlgorithm::DSA.is_supported() { eprintln!("Skipping because DSA is not supported"); return Ok(()); } let c = Cert::from_bytes( crate::tests::key("pgp5-dsa-elg-v3-subkey-binding.pgp"))?; assert_eq!(c.bad_signatures().count(), 0); let np = unsafe { crate::policy::NullPolicy::new() }; // The subkey is interesting because it is bound using a v3 // signature. let vcert = c.with_policy(&np, None)?; assert_eq!(vcert.keys().subkeys().count(), 1); // A v3 signature has no subpackets, so there are no key // flags. But, we then consider the key role and public key // algorithm. assert_eq!(vcert.keys().for_signing().count(), 1); assert_eq!(vcert.keys().for_transport_encryption().count(), 1); // The subkey is interesting because it is bound using a v3 // signature. assert_eq!(c.keys().subkeys().with_policy(&np, None).count(), 1); // A v3 signature has no subpackets, so there are no key // flags. But, we then consider the key role and public key // algorithm. assert_eq!(c.keys().with_policy(&np, None).for_signing().count(), 1); assert_eq!(c.keys().with_policy(&np, None) .for_transport_encryption().count(), 1); Ok(()) } /// Tests v3 revocation signatures. #[test] fn v3_revocation_signature() -> Result<()> { if ! crate::types::PublicKeyAlgorithm::ECDSA.is_supported() || ! crate::types::Curve::NistP521.is_supported() { eprintln!("Skipping because ECDSA/NistP521 is not supported"); return Ok(()); } let c = Cert::from_bytes( crate::tests::key("v4-revoked-by-v3.pgp"))?; assert_eq!(c.bad_signatures().count(), 0); let sp = crate::policy::StandardPolicy::new(); assert!(matches!(c.revocation_status(&sp, None), RevocationStatus::Revoked(_))); Ok(()) } #[test] fn v6_minimal_cert() -> Result<()> { let p = &crate::policy::StandardPolicy::new(); let t = None; // XXX let cert = Cert::from_bytes( crate::tests::file("crypto-refresh/v6-minimal-cert.key"))?; assert_eq!(cert.userids().count(), 0); let vcert = cert.with_policy(p, t)?; assert_eq!(vcert.keys().count(), 2); assert_eq!(vcert.keys().encrypted_secret().count(), 0); assert_eq!(vcert.keys().unencrypted_secret().count(), 0); assert_eq!(vcert.keys().for_signing().count(), 1); assert_eq!(vcert.keys().for_transport_encryption().count(), 1); let cert = Cert::from_bytes( crate::tests::file("crypto-refresh/v6-minimal-secret.key")).unwrap(); assert_eq!(cert.userids().count(), 0); let vcert = cert.with_policy(p, t)?; assert_eq!(vcert.keys().count(), 2); assert_eq!(vcert.keys().encrypted_secret().count(), 0); assert_eq!(vcert.keys().unencrypted_secret().count(), 2); assert_eq!(vcert.keys().for_signing().count(), 1); assert_eq!(vcert.keys().for_transport_encryption().count(), 1); // The following key uses Argon2, and it takes 2 GiB to // efficiently derive the KEK. This isn't viable on 32 bit // architectures. let name = if cfg!(target_pointer_width = "16") { return Ok(()); // No chance we even got here. } else if cfg!(target_pointer_width = "32") { // For 32 bit architectures, we have a test vector which // uses the "SECOND RECOMMENDED" parameter choice for // memory constrained systems (see Section 4 of RFC 9106). "v6-minimal-secret-locked-for-constrained-envs.key" } else { // 64 bit or weird. Good luck. "v6-minimal-secret-locked.key" }; let cert = Cert::from_bytes( crate::tests::file(&format!("crypto-refresh/{}", name)))?; assert_eq!(cert.userids().count(), 0); let vcert = cert.with_policy(p, t)?; assert_eq!(vcert.keys().count(), 2); assert_eq!(vcert.keys().encrypted_secret().count(), 2); assert_eq!(vcert.keys().unencrypted_secret().count(), 0); assert_eq!(vcert.keys().for_signing().count(), 1); assert_eq!(vcert.keys().for_transport_encryption().count(), 1); let password = "correct horse battery staple".into(); for skb in vcert.keys().encrypted_secret() { skb.key().secret().clone().decrypt(skb.key(), &password)?; } Ok(()) } } ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/src/crypto/aead.rs������������������������������������������������������������0000644�0000000�0000000�00000071373�10461020230�0016321�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������use std::cmp; use std::convert::TryInto; use std::fmt; use std::io; use buffered_reader::BufferedReader; use crate::types::{ AEADAlgorithm, SymmetricAlgorithm, }; use crate::utils::{ write_be_u64, }; use crate::Error; use crate::Result; use crate::crypto::SessionKey; use crate::seal; use crate::parse::Cookie; use crate::crypto::backend::{Backend, interface::Kdf}; /// Minimum AEAD chunk size. /// /// Implementations MUST support chunk sizes down to 64B. const MIN_CHUNK_SIZE: usize = 1 << 6; // 64B /// Maximum AEAD chunk size. /// /// Implementations MUST support chunk sizes up to 4MiB. const MAX_CHUNK_SIZE: usize = 1 << 22; // 4MiB /// Maximum size of any Nonce used by an AEAD mode. pub const MAX_NONCE_LEN: usize = 16; /// Converts a chunk size to a usize. pub(crate) fn chunk_size_usize(chunk_size: u64) -> Result<usize> { chunk_size.try_into() .map_err(|_| Error::InvalidOperation( format!("AEAD chunk size exceeds size of \ virtual memory: {}", chunk_size)).into()) } /// An AEAD mode of operation. /// /// # Sealed trait /// /// This trait is [sealed] and cannot be implemented for types outside this crate. /// Therefore it can be extended in a non-breaking way. /// If you want to implement the trait inside the crate /// you also need to implement the `seal::Sealed` marker trait. /// /// [sealed]: https://rust-lang.github.io/api-guidelines/future-proofing.html#sealed-traits-protect-against-downstream-implementations-c-sealed pub trait Aead : seal::Sealed { /// Encrypts one chunk `src` to `dst` adding a digest. /// /// Note: `dst` must be large enough to accommodate both the /// ciphertext and the digest! fn encrypt_seal(&mut self, dst: &mut [u8], src: &[u8]) -> Result<()>; /// Length of the digest in bytes. #[allow(dead_code)] // Used in debug assertions. fn digest_size(&self) -> usize; /// Decrypt one chunk `src` to `dst` and verify that the digest is /// correct. fn decrypt_verify(&mut self, dst: &mut [u8], src: &[u8]) -> Result<()>; } /// Whether AEAD cipher is used for data encryption or decryption. pub(crate) enum CipherOp { /// Cipher is used for data encryption. Encrypt, /// Cipher is used for data decryption. Decrypt, } impl AEADAlgorithm { /// Returns the digest size of the AEAD algorithm. pub fn digest_size(&self) -> Result<usize> { use self::AEADAlgorithm::*; match self { // See https://www.rfc-editor.org/rfc/rfc9580.html#name-eax-mode EAX => Ok(16), // See https://www.rfc-editor.org/rfc/rfc9580.html#name-ocb-mode OCB => Ok(16), // See https://www.rfc-editor.org/rfc/rfc9580.html#name-gcm-mode GCM => Ok(16), _ => Err(Error::UnsupportedAEADAlgorithm(*self).into()), } } /// Returns the nonce size of the AEAD algorithm. pub fn nonce_size(&self) -> Result<usize> { use self::AEADAlgorithm::*; match self { // See https://www.rfc-editor.org/rfc/rfc9580.html#name-eax-mode EAX => Ok(16), // See https://www.rfc-editor.org/rfc/rfc9580.html#name-ocb-mode OCB => Ok(15), // See https://www.rfc-editor.org/rfc/rfc9580.html#name-gcm-mode GCM => Ok(12), _ => Err(Error::UnsupportedAEADAlgorithm(*self).into()), } } } /// Schedules nonce and additional authenticated data for use with /// each AEAD chunk. pub trait Schedule: Send + Sync { /// Calls `fun` with the appropriate nonce and additional /// authenticated data. /// /// This is appropriate for all but the last chunk. /// /// `index` is the current chunk index. fn next_chunk<F, R>(&self, index: u64, fun: F) -> R where F: FnMut(&[u8], &[u8]) -> R; /// Calls `fun` with the appropriate nonce and additional /// authenticated data for the last chunk. /// /// This is appropriate for the last chunk. /// /// `index` is the current chunk index. fn final_chunk<F, R>(&self, index: u64, length: u64, fun: F) -> R where F: FnMut(&[u8], &[u8]) -> R; } const SEIP2AD_PREFIX_LEN: usize = 5; pub(crate) struct SEIPv2Schedule { nonce: Box<[u8]>, ad: [u8; SEIP2AD_PREFIX_LEN], nonce_len: usize, } impl SEIPv2Schedule { pub(crate) fn new(session_key: &SessionKey, sym_algo: SymmetricAlgorithm, aead: AEADAlgorithm, chunk_size: usize, salt: &[u8]) -> Result<(SessionKey, Self)> { if !(MIN_CHUNK_SIZE..=MAX_CHUNK_SIZE).contains(&chunk_size) { return Err(Error::InvalidArgument( format!("Invalid AEAD chunk size: {}", chunk_size)).into()); } // Derive the message key and initialization vector. let key_size = sym_algo.key_size()?; // The NONCE size is NONCE_LEN - 8 bytes taken from the KDF. let nonce_size = aead.nonce_size()? - 8; let mut key_nonce: SessionKey = vec![0; key_size + nonce_size].into(); let ad = [ 0xd2, // Tag. 2, // Version. sym_algo.into(), aead.into(), chunk_size.trailing_zeros() as u8 - 6, ]; Backend::hkdf_sha256(session_key, Some(salt), &ad, &mut key_nonce)?; let key = Vec::from(&key_nonce[..key_size]).into(); let nonce = Vec::from(&key_nonce[key_size..]).into(); Ok((key, Self { nonce, ad, nonce_len: aead.nonce_size()?, })) } } impl Schedule for SEIPv2Schedule { fn next_chunk<F, R>(&self, index: u64, mut fun: F) -> R where F: FnMut(&[u8], &[u8]) -> R, { // The nonce is the NONCE (NONCE_LEN - 8 bytes taken from the // KDF) concatenated with the chunk index. let index_be: [u8; 8] = index.to_be_bytes(); let mut nonce_store = [0u8; MAX_NONCE_LEN]; let nonce = &mut nonce_store[..self.nonce_len]; nonce[..self.nonce.len()].copy_from_slice(&self.nonce); nonce[self.nonce.len()..].copy_from_slice(&index_be); fun(nonce, &self.ad) } fn final_chunk<F, R>(&self, index: u64, length: u64, mut fun: F) -> R where F: FnMut(&[u8], &[u8]) -> R, { // Prepare the associated data. let mut ad = [0u8; SEIP2AD_PREFIX_LEN + 8]; ad[..SEIP2AD_PREFIX_LEN].copy_from_slice(&self.ad); write_be_u64(&mut ad[SEIP2AD_PREFIX_LEN..], length); // The nonce is the NONCE (NONCE_LEN - 8 bytes taken from the // KDF) concatenated with the chunk index. let index_be: [u8; 8] = index.to_be_bytes(); let mut nonce_store = [0u8; MAX_NONCE_LEN]; let nonce = &mut nonce_store[..self.nonce_len]; nonce[..self.nonce.len()].copy_from_slice(&self.nonce); nonce[self.nonce.len()..].copy_from_slice(&index_be); fun(nonce, &ad) } } /// A `Read`er for decrypting AEAD-encrypted data. pub struct Decryptor<'a, S: Schedule> { // The encrypted data. source: Box<dyn BufferedReader<Cookie> + 'a>, sym_algo: SymmetricAlgorithm, aead: AEADAlgorithm, key: SessionKey, schedule: S, digest_size: usize, chunk_size: usize, chunk_index: u64, bytes_decrypted: u64, // Up to a chunk of unread data. buffer: Vec<u8>, } assert_send_and_sync!(Decryptor<'_, S> where S: Schedule); impl<'a, S: Schedule> Decryptor<'a, S> { /// Instantiate a new AEAD decryptor. /// /// `source` is the source to wrap. pub fn new<R>(sym_algo: SymmetricAlgorithm, aead: AEADAlgorithm, chunk_size: usize, schedule: S, key: SessionKey, source: R) -> Result<Self> where R: io::Read + Send + Sync + 'a { Self::from_cookie_reader( sym_algo, aead, chunk_size, schedule, key, Box::new(buffered_reader::Generic::with_cookie( source, None, Default::default()))) } pub fn from_cookie_reader(sym_algo: SymmetricAlgorithm, aead: AEADAlgorithm, chunk_size: usize, schedule: S, key: SessionKey, source: Box<dyn 'a + BufferedReader<Cookie>>) -> Result<Self> { Ok(Decryptor { source, sym_algo, aead, key, schedule, digest_size: aead.digest_size()?, chunk_size, chunk_index: 0, bytes_decrypted: 0, buffer: Vec::with_capacity(chunk_size), }) } // Note: this implementation tries *very* hard to make sure we don't // gratuitiously do a short read. Specifically, if the return value // is less than `plaintext.len()`, then it is either because we // reached the end of the input or an error occurred. fn read_helper(&mut self, plaintext: &mut [u8]) -> Result<usize> { let mut pos = 0; // 1. Copy any buffered data. if !self.buffer.is_empty() { let to_copy = cmp::min(self.buffer.len(), plaintext.len()); plaintext[..to_copy].copy_from_slice(&self.buffer[..to_copy]); crate::vec_drain_prefix(&mut self.buffer, to_copy); pos = to_copy; if pos == plaintext.len() { return Ok(pos); } } // 2. Decrypt the data a chunk at a time until we've filled // `plaintext`. // // Unfortunately, framing is hard. // // Recall: AEAD data is of the form: // // [ chunk1 ][ tag1 ] ... [ chunkN ][ tagN ][ tagF ] // // And, all chunks are the same size except for the last // chunk, which may be shorter. // // The naive approach to decryption is to read a chunk and a // tag at a time. Unfortunately, this may not work if the // last chunk is a partial chunk. // // Assume that the chunk size is 32 bytes and the digest size // is 16 bytes, and consider a message with 17 bytes of data. // That message will be encrypted as follows: // // [ chunk1 ][ tag1 ][ tagF ] // 17B 16B 16B // // If we read a chunk and a digest, we'll successfully read 48 // bytes of data. Unfortunately, we'll have over read: the // last 15 bytes are from the final tag. // // To correctly handle this case, we have to make sure that // there are at least a tag worth of bytes left over when we // read a chunk and a tag. let n_chunks = (plaintext.len() - pos + self.chunk_size - 1) / self.chunk_size; let chunk_digest_size = self.chunk_size + self.digest_size; let final_digest_size = self.digest_size; for _ in 0..n_chunks { // Do a little dance to avoid exclusively locking // `self.source`. let to_read = chunk_digest_size + final_digest_size; let result = { match self.source.data(to_read) { Ok(_) => Ok(self.source.buffer()), Err(err) => Err(err), } }; let check_final_tag; let chunk = match result { Ok(chunk) => { if chunk.is_empty() { // Exhausted source. return Ok(pos); } if chunk.len() < final_digest_size { return Err(Error::ManipulatedMessage.into()); } check_final_tag = chunk.len() < to_read; // Return the chunk. &chunk[..cmp::min(chunk.len(), to_read) - final_digest_size] }, Err(e) => return Err(e.into()), }; assert!(chunk.len() <= chunk_digest_size); if chunk.is_empty() { // There is nothing to decrypt: all that is left is // the final tag. } else if chunk.len() <= self.digest_size { // A chunk has to include at least one byte and a tag. return Err(Error::ManipulatedMessage.into()); } else { let mut aead = self.schedule.next_chunk( self.chunk_index, |iv, ad| { self.aead.context(self.sym_algo, &self.key, ad, iv, CipherOp::Decrypt) })?; // Decrypt the chunk and check the tag. let to_decrypt = chunk.len() - self.digest_size; // If plaintext doesn't have enough room for the whole // chunk, then we have to double buffer. let double_buffer = to_decrypt > plaintext.len() - pos; let buffer = if double_buffer { self.buffer.resize(to_decrypt, 0); &mut self.buffer[..] } else { &mut plaintext[pos..pos + to_decrypt] }; aead.decrypt_verify(buffer, chunk)?; if double_buffer { let to_copy = plaintext.len() - pos; assert!(0 < to_copy); assert!(to_copy < self.chunk_size); plaintext[pos..pos + to_copy] .copy_from_slice(&self.buffer[..to_copy]); crate::vec_drain_prefix(&mut self.buffer, to_copy); pos += to_copy; } else { pos += to_decrypt; } // Increase index, update position in plaintext. self.chunk_index += 1; self.bytes_decrypted += to_decrypt as u64; // Consume the data only on success so that we keep // returning the error. let chunk_len = chunk.len(); self.source.consume(chunk_len); } if check_final_tag { // We read the whole ciphertext, now check the final digest. let mut aead = self.schedule.final_chunk( self.chunk_index, self.bytes_decrypted, |iv, ad| { self.aead.context(self.sym_algo, &self.key, ad, iv, CipherOp::Decrypt) })?; let final_digest = self.source.data(final_digest_size)?; aead.decrypt_verify(&mut [], final_digest)?; // Consume the data only on success so that we keep // returning the error. self.source.consume(final_digest_size); break; } } Ok(pos) } } // Note: this implementation tries *very* hard to make sure we don't // gratuitiously do a short read. Specifically, if the return value // is less than `plaintext.len()`, then it is either because we // reached the end of the input or an error occurred. impl<'a, S: Schedule> io::Read for Decryptor<'a, S> { fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> { match self.read_helper(buf) { Ok(n) => Ok(n), Err(e) => match e.downcast::<io::Error>() { // An io::Error. Pass as-is. Ok(e) => Err(e), // A failure. Wrap it. Err(e) => Err(io::Error::new(io::ErrorKind::Other, e)), }, } } } /// A `BufferedReader` that decrypts AEAD-encrypted data as it is /// read. pub(crate) struct BufferedReaderDecryptor<'a, S: Schedule> { reader: buffered_reader::Generic<Decryptor<'a, S>, Cookie>, } impl<'a, S: Schedule> BufferedReaderDecryptor<'a, S> { /// Like `new()`, but sets a cookie, which can be retrieved using /// the `cookie_ref` and `cookie_mut` methods, and set using /// the `cookie_set` method. pub fn with_cookie(sym_algo: SymmetricAlgorithm, aead: AEADAlgorithm, chunk_size: usize, schedule: S, key: SessionKey, source: Box<dyn BufferedReader<Cookie> + 'a>, cookie: Cookie) -> Result<Self> { Ok(BufferedReaderDecryptor { reader: buffered_reader::Generic::with_cookie( Decryptor::from_cookie_reader( sym_algo, aead, chunk_size, schedule, key, source)?, None, cookie), }) } } impl<'a, S: Schedule> io::Read for BufferedReaderDecryptor<'a, S> { fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> { self.reader.read(buf) } } impl<'a, S: Schedule> fmt::Display for BufferedReaderDecryptor<'a, S> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "BufferedReaderDecryptor") } } impl<'a, S: Schedule> fmt::Debug for BufferedReaderDecryptor<'a, S> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.debug_struct("BufferedReaderDecryptor") .field("reader", &self.get_ref().unwrap()) .finish() } } impl<'a, S: Schedule> BufferedReader<Cookie> for BufferedReaderDecryptor<'a, S> { fn buffer(&self) -> &[u8] { self.reader.buffer() } fn data(&mut self, amount: usize) -> io::Result<&[u8]> { self.reader.data(amount) } fn data_hard(&mut self, amount: usize) -> io::Result<&[u8]> { self.reader.data_hard(amount) } fn data_eof(&mut self) -> io::Result<&[u8]> { self.reader.data_eof() } fn consume(&mut self, amount: usize) -> &[u8] { self.reader.consume(amount) } fn data_consume(&mut self, amount: usize) -> io::Result<&[u8]> { self.reader.data_consume(amount) } fn data_consume_hard(&mut self, amount: usize) -> io::Result<&[u8]> { self.reader.data_consume_hard(amount) } fn read_be_u16(&mut self) -> io::Result<u16> { self.reader.read_be_u16() } fn read_be_u32(&mut self) -> io::Result<u32> { self.reader.read_be_u32() } fn steal(&mut self, amount: usize) -> io::Result<Vec<u8>> { self.reader.steal(amount) } fn steal_eof(&mut self) -> io::Result<Vec<u8>> { self.reader.steal_eof() } fn get_mut(&mut self) -> Option<&mut dyn BufferedReader<Cookie>> { Some(&mut self.reader.reader_mut().source) } fn get_ref(&self) -> Option<&dyn BufferedReader<Cookie>> { Some(&self.reader.reader_ref().source) } fn into_inner<'b>(self: Box<Self>) -> Option<Box<dyn BufferedReader<Cookie> + 'b>> where Self: 'b { Some(self.reader.into_reader().source.into_boxed()) } fn cookie_set(&mut self, cookie: Cookie) -> Cookie { self.reader.cookie_set(cookie) } fn cookie_ref(&self) -> &Cookie { self.reader.cookie_ref() } fn cookie_mut(&mut self) -> &mut Cookie { self.reader.cookie_mut() } } /// A `Write`r for AEAD encrypting data. pub struct Encryptor<W: io::Write, S: Schedule> { inner: Option<W>, sym_algo: SymmetricAlgorithm, aead: AEADAlgorithm, key: SessionKey, schedule: S, digest_size: usize, chunk_size: usize, chunk_index: u64, bytes_encrypted: u64, // Up to a chunk of unencrypted data. buffer: Vec<u8>, // A place to write encrypted data into. scratch: Vec<u8>, } assert_send_and_sync!(Encryptor<W, S> where W: io::Write, S: Schedule); impl<W: io::Write, S: Schedule> Encryptor<W, S> { /// Instantiate a new AEAD encryptor. pub fn new(sym_algo: SymmetricAlgorithm, aead: AEADAlgorithm, chunk_size: usize, schedule: S, key: SessionKey, sink: W) -> Result<Self> { Ok(Encryptor { inner: Some(sink), sym_algo, aead, key, schedule, digest_size: aead.digest_size()?, chunk_size, chunk_index: 0, bytes_encrypted: 0, buffer: Vec::with_capacity(chunk_size), scratch: vec![0; chunk_size + aead.digest_size()?], }) } // Like io::Write, but returns our Result. fn write_helper(&mut self, mut buf: &[u8]) -> Result<usize> { if self.inner.is_none() { return Err(io::Error::new(io::ErrorKind::BrokenPipe, "Inner writer was taken").into()); } let amount = buf.len(); // First, fill the buffer if there is something in it. if !self.buffer.is_empty() { let n = cmp::min(buf.len(), self.chunk_size - self.buffer.len()); self.buffer.extend_from_slice(&buf[..n]); assert!(self.buffer.len() <= self.chunk_size); buf = &buf[n..]; // And possibly encrypt the chunk. if self.buffer.len() == self.chunk_size { let mut aead = self.schedule.next_chunk(self.chunk_index, |iv, ad| { self.aead.context(self.sym_algo, &self.key, ad, iv, CipherOp::Encrypt) })?; let inner = self.inner.as_mut().unwrap(); // Encrypt the chunk. aead.encrypt_seal(&mut self.scratch, &self.buffer)?; self.bytes_encrypted += self.chunk_size as u64; self.chunk_index += 1; // XXX: clear plaintext buffer. crate::vec_truncate(&mut self.buffer, 0); inner.write_all(&self.scratch)?; } } // Then, encrypt all whole chunks. for chunk in buf.chunks(self.chunk_size) { if chunk.len() == self.chunk_size { // Complete chunk. let mut aead = self.schedule.next_chunk(self.chunk_index, |iv, ad| { self.aead.context(self.sym_algo, &self.key, ad, iv, CipherOp::Encrypt) })?; let inner = self.inner.as_mut().unwrap(); // Encrypt the chunk. aead.encrypt_seal(&mut self.scratch, chunk)?; self.bytes_encrypted += self.chunk_size as u64; self.chunk_index += 1; inner.write_all(&self.scratch)?; } else { // Stash for later. assert!(self.buffer.is_empty()); self.buffer.extend_from_slice(chunk); } } Ok(amount) } /// Finish encryption and write last partial chunk. pub fn finish(&mut self) -> Result<W> { if let Some(mut inner) = self.inner.take() { if !self.buffer.is_empty() { let mut aead = self.schedule.next_chunk(self.chunk_index, |iv, ad| { self.aead.context(self.sym_algo, &self.key, ad, iv, CipherOp::Encrypt) })?; // Encrypt the chunk. unsafe { // Safety: remaining data is less than the chunk // size. The vector has capacity chunk size plus // digest size. debug_assert!(self.buffer.len() < self.chunk_size); self.scratch.set_len(self.buffer.len() + self.digest_size) } aead.encrypt_seal(&mut self.scratch, &self.buffer)?; self.bytes_encrypted += self.buffer.len() as u64; self.chunk_index += 1; // XXX: clear plaintext buffer crate::vec_truncate(&mut self.buffer, 0); inner.write_all(&self.scratch)?; } // Write final digest. let mut aead = self.schedule.final_chunk( self.chunk_index, self.bytes_encrypted, |iv, ad| { self.aead.context(self.sym_algo, &self.key, ad, iv, CipherOp::Encrypt) })?; debug_assert!(self.digest_size <= self.scratch.len()); aead.encrypt_seal(&mut self.scratch[..self.digest_size], b"")?; inner.write_all(&self.scratch[..self.digest_size])?; Ok(inner) } else { Err(io::Error::new(io::ErrorKind::BrokenPipe, "Inner writer was taken").into()) } } /// Acquires a reference to the underlying writer. pub fn get_ref(&self) -> Option<&W> { self.inner.as_ref() } /// Acquires a mutable reference to the underlying writer. #[allow(dead_code)] pub fn get_mut(&mut self) -> Option<&mut W> { self.inner.as_mut() } } impl<W: io::Write, S: Schedule> io::Write for Encryptor<W, S> { fn write(&mut self, buf: &[u8]) -> io::Result<usize> { match self.write_helper(buf) { Ok(n) => Ok(n), Err(e) => match e.downcast::<io::Error>() { // An io::Error. Pass as-is. Ok(e) => Err(e), // A failure. Wrap it. Err(e) => Err(io::Error::new(io::ErrorKind::Other, e)), }, } } fn flush(&mut self) -> io::Result<()> { // It is not clear how we can implement this, because we can // only operate on chunk sizes. We will, however, ask our // inner writer to flush. if let Some(ref mut inner) = self.inner { inner.flush() } else { Err(io::Error::new(io::ErrorKind::BrokenPipe, "Inner writer was taken")) } } } impl<W: io::Write, S: Schedule> Drop for Encryptor<W, S> { fn drop(&mut self) { // Unfortunately, we cannot handle errors here. If error // handling is a concern, call finish() and properly handle // errors there. let _ = self.finish(); } } #[cfg(test)] mod tests { use super::*; use std::io::{Read, Write}; /// This test tries to encrypt, then decrypt some data. #[test] fn roundtrip() { use std::io::Cursor; // EAX and OCB can be used with all symmetric algorithms using // a 16-byte block size. for sym_algo in [SymmetricAlgorithm::AES128, SymmetricAlgorithm::AES192, SymmetricAlgorithm::AES256, SymmetricAlgorithm::Twofish, SymmetricAlgorithm::Camellia128, SymmetricAlgorithm::Camellia192, SymmetricAlgorithm::Camellia256] .iter() .filter(|algo| algo.is_supported()) { for aead in [ AEADAlgorithm::EAX, AEADAlgorithm::OCB, AEADAlgorithm::GCM, ].iter().filter(|algo| algo.is_supported() && algo.supports_symmetric_algo(sym_algo)) { let chunk_size = 64; let mut key = vec![0; sym_algo.key_size().unwrap()]; crate::crypto::random(&mut key).unwrap(); let key: SessionKey = key.into(); let mut iv = vec![0; aead.nonce_size().unwrap()]; crate::crypto::random(&mut iv).unwrap(); let mut ciphertext = Vec::new(); { let (message_key, schedule) = SEIPv2Schedule::new( &key, *sym_algo, *aead, chunk_size, &iv).expect("valid parameters"); let mut encryptor = Encryptor::new(*sym_algo, *aead, chunk_size, schedule, message_key, &mut ciphertext) .unwrap(); encryptor.write_all(crate::tests::manifesto()).unwrap(); } let mut plaintext = Vec::new(); { let (message_key, schedule) = SEIPv2Schedule::new( &key, *sym_algo, *aead, chunk_size, &iv).expect("valid parameters"); let mut decryptor = Decryptor::new(*sym_algo, *aead, chunk_size, schedule, message_key, Cursor::new(&ciphertext)) .unwrap(); decryptor.read_to_end(&mut plaintext).unwrap(); } assert_eq!(&plaintext[..], crate::tests::manifesto()); } } } } ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/src/crypto/asymmetric.rs������������������������������������������������������0000644�0000000�0000000�00000032635�10461020230�0017602�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������//! Asymmetric crypto operations. use crate::packet::{self, key, Key}; use crate::crypto::SessionKey; use crate::crypto::mpi; use crate::types::{ Curve, HashAlgorithm, PublicKeyAlgorithm, SymmetricAlgorithm, }; use crate::{Error, Result}; /// Creates a signature. /// /// Used in the streaming [`Signer`], the methods binding components /// to certificates (e.g. [`UserID::bind`]), [`SignatureBuilder`]'s /// signing functions (e.g. [`SignatureBuilder::sign_standalone`]), /// and likely many more places. /// /// [`Signer`]: crate::serialize::stream::Signer /// [`UserID::bind`]: crate::packet::UserID::bind() /// [`SignatureBuilder`]: crate::packet::signature::SignatureBuilder /// [`SignatureBuilder::sign_standalone`]: crate::packet::signature::SignatureBuilder::sign_standalone() /// /// This is a low-level mechanism to produce an arbitrary OpenPGP /// signature. Using this trait allows Sequoia to perform all /// operations involving signing to use a variety of secret key /// storage mechanisms (e.g. smart cards). /// /// A signer consists of the public key and a way of creating a /// signature. This crate implements `Signer` for [`KeyPair`], which /// is a tuple containing the public and unencrypted secret key in /// memory. Other crates may provide their own implementations of /// `Signer` to utilize keys stored in various places. Currently, the /// following implementations exist: /// /// - [`KeyPair`]: In-memory keys. /// - [`sequoia_rpc::gnupg::KeyPair`]: Connects to the `gpg-agent`. /// /// [`sequoia_rpc::gnupg::KeyPair`]: https://docs.sequoia-pgp.org/sequoia_ipc/gnupg/struct.KeyPair.html pub trait Signer { /// Returns a reference to the public key. fn public(&self) -> &Key<key::PublicParts, key::UnspecifiedRole>; /// Returns a list of hashes that this signer accepts. /// /// Some cryptographic libraries or hardware modules support signing digests /// produced with only a limited set of hashing algorithms. This function /// indicates to callers which algorithm digests are supported by this signer. /// /// The default implementation of this function allows all hash algorithms to /// be used. Provide an explicit implementation only when a smaller subset /// of hashing algorithms is valid for this `Signer` implementation. fn acceptable_hashes(&self) -> &[HashAlgorithm] { crate::crypto::hash::default_hashes_sorted() } /// Creates a signature over the `digest` produced by `hash_algo`. fn sign(&mut self, hash_algo: HashAlgorithm, digest: &[u8]) -> Result<mpi::Signature>; } impl Signer for Box<dyn Signer> { fn public(&self) -> &Key<key::PublicParts, key::UnspecifiedRole> { self.as_ref().public() } fn acceptable_hashes(&self) -> &[HashAlgorithm] { self.as_ref().acceptable_hashes() } fn sign(&mut self, hash_algo: HashAlgorithm, digest: &[u8]) -> Result<mpi::Signature> { self.as_mut().sign(hash_algo, digest) } } impl Signer for Box<dyn Signer + Send + Sync> { fn public(&self) -> &Key<key::PublicParts, key::UnspecifiedRole> { self.as_ref().public() } fn acceptable_hashes(&self) -> &[HashAlgorithm] { self.as_ref().acceptable_hashes() } fn sign(&mut self, hash_algo: HashAlgorithm, digest: &[u8]) -> Result<mpi::Signature> { self.as_mut().sign(hash_algo, digest) } } /// Decrypts a message. /// /// Used by [`PKESK::decrypt`] to decrypt session keys. /// /// [`PKESK::decrypt`]: crate::packet::PKESK#method.decrypt /// /// This is a low-level mechanism to decrypt an arbitrary OpenPGP /// ciphertext. Using this trait allows Sequoia to perform all /// operations involving decryption to use a variety of secret key /// storage mechanisms (e.g. smart cards). /// /// A decryptor consists of the public key and a way of decrypting a /// session key. This crate implements `Decryptor` for [`KeyPair`], /// which is a tuple containing the public and unencrypted secret key /// in memory. Other crates may provide their own implementations of /// `Decryptor` to utilize keys stored in various places. Currently, the /// following implementations exist: /// /// - [`KeyPair`]: In-memory keys. /// - [`sequoia_rpc::gnupg::KeyPair`]: Connects to the `gpg-agent`. /// /// [`sequoia_rpc::gnupg::KeyPair`]: https://docs.sequoia-pgp.org/sequoia_ipc/gnupg/struct.KeyPair.html pub trait Decryptor { /// Returns a reference to the public key. fn public(&self) -> &Key<key::PublicParts, key::UnspecifiedRole>; /// Decrypts `ciphertext`, returning the plain session key. fn decrypt(&mut self, ciphertext: &mpi::Ciphertext, plaintext_len: Option<usize>) -> Result<SessionKey>; } impl Decryptor for Box<dyn Decryptor> { fn public(&self) -> &Key<key::PublicParts, key::UnspecifiedRole> { self.as_ref().public() } fn decrypt(&mut self, ciphertext: &mpi::Ciphertext, plaintext_len: Option<usize>) -> Result<SessionKey> { self.as_mut().decrypt(ciphertext, plaintext_len) } } impl Decryptor for Box<dyn Decryptor + Send + Sync> { fn public(&self) -> &Key<key::PublicParts, key::UnspecifiedRole> { self.as_ref().public() } fn decrypt(&mut self, ciphertext: &mpi::Ciphertext, plaintext_len: Option<usize>) -> Result<SessionKey> { self.as_mut().decrypt(ciphertext, plaintext_len) } } /// A cryptographic key pair. /// /// A `KeyPair` is a combination of public and secret key. If both /// are available in memory, a `KeyPair` is a convenient /// implementation of [`Signer`] and [`Decryptor`]. /// /// /// # Examples /// /// ``` /// # fn main() -> sequoia_openpgp::Result<()> { /// use sequoia_openpgp as openpgp; /// use openpgp::types::Curve; /// use openpgp::cert::prelude::*; /// use openpgp::packet::prelude::*; /// /// // Conveniently create a KeyPair from a bare key: /// let keypair = /// Key4::<_, key::UnspecifiedRole>::generate_ecc(false, Curve::Cv25519)? /// .into_keypair()?; /// /// // Or from a query over a certificate: /// let (cert, _) = /// CertBuilder::general_purpose(Some("alice@example.org")) /// .generate()?; /// let keypair = /// cert.keys().unencrypted_secret().nth(0).unwrap().key().clone() /// .into_keypair()?; /// # Ok(()) } /// ``` #[derive(Clone)] pub struct KeyPair { public: Key<key::PublicParts, key::UnspecifiedRole>, secret: packet::key::Unencrypted, } assert_send_and_sync!(KeyPair); impl KeyPair { /// Creates a new key pair. pub fn new(public: Key<key::PublicParts, key::UnspecifiedRole>, secret: packet::key::Unencrypted) -> Result<Self> { Ok(Self { public, secret, }) } /// Returns a reference to the public key. pub fn public(&self) -> &Key<key::PublicParts, key::UnspecifiedRole> { &self.public } /// Returns a reference to the secret key. pub fn secret(&self) -> &packet::key::Unencrypted { &self.secret } } impl From<KeyPair> for Key<key::SecretParts, key::UnspecifiedRole> { fn from(p: KeyPair) -> Self { let (key, secret) = (p.public, p.secret); key.add_secret(secret.into()).0 } } impl Signer for KeyPair { fn public(&self) -> &Key<key::PublicParts, key::UnspecifiedRole> { KeyPair::public(self) } fn sign(&mut self, hash_algo: HashAlgorithm, digest: &[u8]) -> Result<mpi::Signature> { use crate::crypto::backend::{Backend, interface::Asymmetric}; self.secret().map(|secret| { match (self.public().pk_algo(), self.public().mpis(), secret) { (PublicKeyAlgorithm::Ed25519, mpi::PublicKey::Ed25519 { a }, mpi::SecretKeyMaterial::Ed25519 { x }) => { Ok(mpi::Signature::Ed25519 { s: Box::new(Backend::ed25519_sign(x, a, digest)?), }) }, (PublicKeyAlgorithm::Ed448, mpi::PublicKey::Ed448 { a }, mpi::SecretKeyMaterial::Ed448 { x }) => { Ok(mpi::Signature::Ed448 { s: Box::new(Backend::ed448_sign(x, a, digest)?), }) }, (PublicKeyAlgorithm::EdDSA, mpi::PublicKey::EdDSA { curve, q }, mpi::SecretKeyMaterial::EdDSA { scalar }) => match curve { Curve::Ed25519 => { let public = q.decode_point(&Curve::Ed25519)?.0 .try_into()?; let secret = scalar.value_padded(32); let sig = Backend::ed25519_sign(&secret, &public, digest)?; Ok(mpi::Signature::EdDSA { r: mpi::MPI::new(&sig[..32]), s: mpi::MPI::new(&sig[32..]), }) }, _ => Err( Error::UnsupportedEllipticCurve(curve.clone()).into()), }, (_algo, _public, secret) => self.sign_backend(secret, hash_algo, digest), } }) } } impl Decryptor for KeyPair { fn public(&self) -> &Key<key::PublicParts, key::UnspecifiedRole> { KeyPair::public(self) } fn decrypt(&mut self, ciphertext: &mpi::Ciphertext, plaintext_len: Option<usize>) -> Result<SessionKey> { use crate::crypto::ecdh::aes_key_unwrap; use crate::crypto::backend::{Backend, interface::{Asymmetric, Kdf}}; self.secret().map(|secret| { #[allow(non_snake_case)] match (self.public().mpis(), secret, ciphertext) { (mpi::PublicKey::X25519 { u: U }, mpi::SecretKeyMaterial::X25519 { x }, mpi::Ciphertext::X25519 { e: E, key }) => { // Compute the shared point S = xE; let S = Backend::x25519_shared_point(x, E)?; // Compute the wrap key. let wrap_algo = SymmetricAlgorithm::AES128; let mut ikm: SessionKey = vec![0; 32 + 32 + 32].into(); // Yes clippy, this operation will always return // zero. This is the intended outcome. Chill. #[allow(clippy::erasing_op)] ikm[0 * 32..1 * 32].copy_from_slice(&E[..]); ikm[1 * 32..2 * 32].copy_from_slice(&U[..]); ikm[2 * 32..3 * 32].copy_from_slice(&S[..]); let mut kek = vec![0; wrap_algo.key_size()?].into(); Backend::hkdf_sha256(&ikm, None, b"OpenPGP X25519", &mut kek)?; Ok(aes_key_unwrap(wrap_algo, kek.as_protected(), key)?.into()) }, (mpi::PublicKey::X448 { u: U }, mpi::SecretKeyMaterial::X448 { x }, mpi::Ciphertext::X448 { e: E, key }) => { // Compute the shared point S = xE; let S = Backend::x448_shared_point(x, E)?; // Compute the wrap key. let wrap_algo = SymmetricAlgorithm::AES256; let mut ikm: SessionKey = vec![0; 56 + 56 + 56].into(); // Yes clippy, this operation will always return // zero. This is the intended outcome. Chill. #[allow(clippy::erasing_op)] ikm[0 * 56..1 * 56].copy_from_slice(&E[..]); ikm[1 * 56..2 * 56].copy_from_slice(&U[..]); ikm[2 * 56..3 * 56].copy_from_slice(&S[..]); let mut kek = vec![0; wrap_algo.key_size()?].into(); Backend::hkdf_sha512(&ikm, None, b"OpenPGP X448", &mut kek)?; Ok(aes_key_unwrap(wrap_algo, kek.as_protected(), key)?.into()) }, (mpi::PublicKey::ECDH { curve: Curve::Cv25519, .. }, mpi::SecretKeyMaterial::ECDH { scalar, }, mpi::Ciphertext::ECDH { e, .. }) => { // Get the public part V of the ephemeral key. let V = e.decode_point(&Curve::Cv25519)?.0; // X25519 expects the private key to be exactly 32 // bytes long but OpenPGP allows leading zeros to // be stripped. Padding has to be unconditional; // otherwise we have a secret-dependent branch. let mut r = scalar.value_padded(32); // Reverse the scalar. See // https://lists.gnupg.org/pipermail/gnupg-devel/2018-February/033437.html r.reverse(); // Compute the shared point S = rV = rvG, where // (r, R) is the recipient's key pair. let S = Backend::x25519_shared_point(&r, &V.try_into()?)?; crate::crypto::ecdh::decrypt_unwrap( self.public(), &S, ciphertext, plaintext_len) }, (_public, secret, _ciphertext) => self.decrypt_backend(secret, ciphertext, plaintext_len), } }) } } ���������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/src/crypto/backend/botan/aead.rs����������������������������������������������0000644�0000000�0000000�00000004033�10461020230�0021000�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������//! Implementation of AEAD using the Botan cryptographic library. use crate::{Error, Result}; use crate::crypto::aead::{Aead, CipherOp}; use crate::seal; use crate::types::{AEADAlgorithm, SymmetricAlgorithm}; struct Cipher(botan::Cipher, usize); impl seal::Sealed for Cipher {} impl Aead for Cipher { fn encrypt_seal(&mut self, dst: &mut [u8], src: &[u8]) -> Result<()> { debug_assert_eq!(dst.len(), src.len() + self.digest_size()); self.0.finish_into(src, dst)?; Ok(()) } fn decrypt_verify(&mut self, dst: &mut [u8], src: &[u8]) -> Result<()> { debug_assert_eq!(dst.len() + self.digest_size(), src.len()); self.0.finish_into(src, dst)?; Ok(()) } fn digest_size(&self) -> usize { self.1 } } impl AEADAlgorithm { /// Returns the name of the algorithm for use with Botan's /// constructor. fn botan_name(self) -> Result<&'static str> { match self { AEADAlgorithm::EAX => Ok("EAX"), AEADAlgorithm::OCB => Ok("OCB"), AEADAlgorithm::GCM => Ok("GCM"), _ => Err(Error::UnsupportedAEADAlgorithm(self).into()), } } pub(crate) fn context(&self, sym_algo: SymmetricAlgorithm, key: &[u8], aad: &[u8], nonce: &[u8], op: CipherOp) -> Result<Box<dyn Aead>> { let mut cipher = botan::Cipher::new( &format!("{}/{}", sym_algo.botan_name()?, self.botan_name()?), match op { CipherOp::Encrypt => botan::CipherDirection::Encrypt, CipherOp::Decrypt => botan::CipherDirection::Decrypt, }) // XXX it could be the cipher that is not supported. .map_err(|_| Error::UnsupportedAEADAlgorithm(*self))?; cipher.set_key(key)?; cipher.set_associated_data(aad)?; cipher.start(nonce)?; Ok(Box::new(Cipher(cipher, self.digest_size()?))) } } �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/src/crypto/backend/botan/asymmetric.rs����������������������������������������0000644�0000000�0000000�00000054571�10461020230�0022277�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������//! Hold the implementation of [`Signer`] and [`Decryptor`] for [`KeyPair`]. //! //! [`Signer`]: super::super::asymmetric::Signer //! [`Decryptor`]: super::super::asymmetric::Decryptor //! [`KeyPair`]: super::super::asymmetric::KeyPair use std::time::SystemTime; use botan::{ RandomNumberGenerator, Pubkey, Privkey, }; use crate::{ Error, Result, crypto::{ asymmetric::KeyPair, backend::interface::Asymmetric, mem::Protected, mpi::{self, MPI, ProtectedMPI, PublicKey}, SessionKey, }, packet::{ key::{self, Key4, SecretParts}, Key, }, types::{ Curve, HashAlgorithm, PublicKeyAlgorithm, }, }; impl Asymmetric for super::Backend { fn supports_algo(algo: PublicKeyAlgorithm) -> bool { use PublicKeyAlgorithm::*; #[allow(deprecated)] match algo { X25519 | Ed25519 | RSAEncryptSign | RSAEncrypt | RSASign | DSA | ECDH | ECDSA | EdDSA | ElGamalEncrypt | ElGamalEncryptSign => true, X448 | Ed448 | Private(_) | Unknown(_) => false, } } fn supports_curve(curve: &Curve) -> bool { use Curve::*; match curve { NistP256 | NistP384 | NistP521 | Ed25519 | Cv25519 | BrainpoolP256 | BrainpoolP384 | BrainpoolP512 => true, Unknown(_) => false, } } fn x25519_generate_key() -> Result<(Protected, [u8; 32])> { let mut rng = RandomNumberGenerator::new_userspace()?; let secret = Privkey::create("Curve25519", "", &mut rng)?; let mut public = [0u8; 32]; public.copy_from_slice(&secret.pubkey()?.get_x25519_key()?); let secret: Protected = secret.get_x25519_key()?.into(); Ok((secret, public)) } fn x25519_derive_public(secret: &Protected) -> Result<[u8; 32]> { let secret = Privkey::load_x25519(secret)?; Ok(<[u8; 32]>::try_from(&secret.pubkey()?.get_x25519_key()?[..])?) } fn x25519_shared_point(secret: &Protected, public: &[u8; 32]) -> Result<Protected> { let secret = Privkey::load_x25519(&secret)?; Ok(secret.agree(public, 32, b"", "Raw")?.into()) } fn ed25519_generate_key() -> Result<(Protected, [u8; 32])> { let mut rng = RandomNumberGenerator::new_userspace()?; let secret = Privkey::create("Ed25519", "", &mut rng)?; let (public, secret) = secret.get_ed25519_key()?; Ok((secret.into(), public.as_slice().try_into()?)) } fn ed25519_derive_public(secret: &Protected) -> Result<[u8; 32]> { let secret = Privkey::load_ed25519(secret)?; let (public, secret) = secret.get_ed25519_key()?; let _ = Protected::from(secret); // Securely dispose. Ok(public.as_slice().try_into()?) } fn ed25519_sign(secret: &Protected, _public: &[u8; 32], digest: &[u8]) -> Result<[u8; 64]> { let mut rng = RandomNumberGenerator::new_userspace()?; let secret = Privkey::load_ed25519(&secret)?; Ok(secret.sign(digest, "", &mut rng)?.as_slice().try_into()?) } fn ed25519_verify(public: &[u8; 32], digest: &[u8], signature: &[u8; 64]) -> Result<bool> { let pk = Pubkey::load_ed25519(public)?; Ok(pk.verify(digest, signature, "")?) } fn dsa_generate_key(p_bits: usize) -> Result<(MPI, MPI, MPI, MPI, ProtectedMPI)> { let mut rng = RandomNumberGenerator::new_userspace()?; let q_bits = if p_bits <= 1024 { 160 } else { 256 }; let secret = Privkey::create_dsa(p_bits, q_bits, &mut rng)?; let public = secret.pubkey()?; Ok((public.get_field("p")?.try_into()?, public.get_field("q")?.try_into()?, public.get_field("g")?.try_into()?, public.get_field("y")?.try_into()?, secret.get_field("x")?.try_into()?)) } fn elgamal_generate_key(p_bits: usize) -> Result<(MPI, MPI, MPI, ProtectedMPI)> { let mut rng = RandomNumberGenerator::new_userspace()?; let q_bits = if p_bits <= 1024 { 160 } else { 256 }; let secret = Privkey::create_elgamal(p_bits, q_bits, &mut rng)?; let public = secret.pubkey()?; Ok((public.get_field("p")?.try_into()?, public.get_field("g")?.try_into()?, public.get_field("y")?.try_into()?, secret.get_field("x")?.try_into()?)) } } // CONFIDENTIALITY: Botan clears the MPIs after use. impl TryFrom<&ProtectedMPI> for botan::MPI { type Error = anyhow::Error; fn try_from(mpi: &ProtectedMPI) -> anyhow::Result<botan::MPI> { Ok(botan::MPI::new_from_bytes(mpi.value())?) } } impl TryFrom<&botan::MPI> for ProtectedMPI { type Error = anyhow::Error; fn try_from(bn: &botan::MPI) -> anyhow::Result<Self> { Ok(bn.to_bin()?.into()) } } impl TryFrom<botan::MPI> for ProtectedMPI { type Error = anyhow::Error; fn try_from(bn: botan::MPI) -> anyhow::Result<Self> { Ok(bn.to_bin()?.into()) } } impl TryFrom<&MPI> for botan::MPI { type Error = anyhow::Error; fn try_from(mpi: &MPI) -> anyhow::Result<botan::MPI> { Ok(botan::MPI::new_from_bytes(mpi.value())?) } } impl TryFrom<&botan::MPI> for MPI { type Error = anyhow::Error; fn try_from(bn: &botan::MPI) -> anyhow::Result<Self> { Ok(bn.to_bin()?.into()) } } impl TryFrom<botan::MPI> for MPI { type Error = anyhow::Error; fn try_from(bn: botan::MPI) -> anyhow::Result<Self> { Ok(bn.to_bin()?.into()) } } impl KeyPair { pub(crate) fn sign_backend(&self, secret: &mpi::SecretKeyMaterial, hash_algo: HashAlgorithm, digest: &[u8]) -> Result<mpi::Signature> { use crate::PublicKeyAlgorithm::*; let mut rng = RandomNumberGenerator::new_userspace()?; #[allow(deprecated)] match (self.public().pk_algo(), self.public().mpis(), secret) { (RSASign, PublicKey::RSA { e, .. }, mpi::SecretKeyMaterial::RSA { p, q, .. }) | (RSAEncryptSign, PublicKey::RSA { e, .. }, mpi::SecretKeyMaterial::RSA { p, q, .. }) => { let secret = Privkey::load_rsa(&p.try_into()?, &q.try_into()?, &e.try_into()?)?; let sig = secret.sign( digest, &format!("PKCS1v15(Raw,{})", hash_algo.botan_name()?), &mut rng)?; Ok(mpi::Signature::RSA { s: MPI::new(&sig), }) }, (DSA, PublicKey::DSA { p, q, g, .. }, mpi::SecretKeyMaterial::DSA { x }) => { let secret = Privkey::load_dsa(&p.try_into()?, &q.try_into()?, &g.try_into()?, &x.try_into()?)?; let size = q.value().len(); let truncated_digest = &digest[..size.min(digest.len())]; let sig = secret.sign(truncated_digest, "Raw", &mut rng)?; if sig.len() != size * 2 { return Err(Error::MalformedMPI( format!("Expected signature with length {}, got {}", size * 2, sig.len())).into()); } Ok(mpi::Signature::DSA { r: MPI::new(&sig[..size]), s: MPI::new(&sig[size..]), }) }, (ECDSA, PublicKey::ECDSA { curve, .. }, mpi::SecretKeyMaterial::ECDSA { scalar }) => { let size = curve.field_size()?; let secret = Privkey::load_ecdsa( &scalar.try_into()?, curve.botan_name()?)?; let sig = secret.sign(digest, "Raw", &mut rng)?; if sig.len() != size * 2 { return Err(Error::MalformedMPI( format!("Expected signature with length {}, got {}", size * 2, sig.len())).into()); } Ok(mpi::Signature::ECDSA { r: MPI::new(&sig[..size]), s: MPI::new(&sig[size..]), }) }, (pk_algo, _, _) => Err(Error::InvalidOperation(format!( "unsupported combination of algorithm {:?}, key {:?}, \ and secret key {:?}", pk_algo, self.public(), self.secret())).into()), } } } impl KeyPair { pub(crate) fn decrypt_backend(&self, secret: &mpi::SecretKeyMaterial, ciphertext: &mpi::Ciphertext, plaintext_len: Option<usize>) -> Result<SessionKey> { fn bad(e: impl ToString) -> anyhow::Error { // XXX: Not a great error to return. Error::MalformedMessage(e.to_string()).into() } Ok(match (self.public().mpis(), secret, ciphertext) { (PublicKey::RSA { e, .. }, mpi::SecretKeyMaterial::RSA { p, q, .. }, mpi::Ciphertext::RSA { c }) => { let secret = Privkey::load_rsa(&p.try_into()?, &q.try_into()?, &e.try_into()?)?; secret.decrypt(c.value(), "PKCS1v15")?.into() }, (PublicKey::ElGamal{ p, g, .. }, mpi::SecretKeyMaterial::ElGamal{ x }, mpi::Ciphertext::ElGamal{ e, c }) => { // OpenPGP encodes E and C separately, but our // cryptographic library expects them to be // concatenated. let size = p.value().len(); let mut ctxt = Vec::with_capacity(2 * size); // We need to zero-pad them at the front, because // the MPI encoding drops leading zero bytes. ctxt.extend_from_slice(&e.value_padded(size).map_err(bad)?); ctxt.extend_from_slice(&c.value_padded(size).map_err(bad)?); let secret = Privkey::load_elgamal(&p.try_into()?, &g.try_into()?, &x.try_into()?)?; secret.decrypt(&ctxt, "PKCS1v15")?.into() }, (PublicKey::ECDH{ .. }, mpi::SecretKeyMaterial::ECDH { .. }, mpi::Ciphertext::ECDH { .. }) => crate::crypto::ecdh::decrypt(self.public(), secret, ciphertext, plaintext_len)?, (public, secret, ciphertext) => return Err(Error::InvalidOperation(format!( "unsupported combination of key pair {:?}/{:?} \ and ciphertext {:?}", public, secret, ciphertext)).into()), }) } } impl<P: key::KeyParts, R: key::KeyRole> Key<P, R> { /// Encrypts the given data with this key. pub(crate) fn encrypt_backend(&self, data: &SessionKey) -> Result<mpi::Ciphertext> { use crate::PublicKeyAlgorithm::*; #[allow(deprecated)] match self.pk_algo() { RSAEncryptSign | RSAEncrypt => if let mpi::PublicKey::RSA { e, n } = self.mpis() { // The ciphertext has the length of the modulus. let ciphertext_len = n.value().len(); if data.len() + 11 > ciphertext_len { return Err(Error::InvalidArgument( "Plaintext data too large".into()).into()); } let mut rng = RandomNumberGenerator::new_userspace()?; let pk = Pubkey::load_rsa(&n.try_into()?, &e.try_into()?)?; let esk = pk.encrypt(data, "PKCS1v15", &mut rng)?; Ok(mpi::Ciphertext::RSA { c: MPI::new(&esk), }) } else { Err(Error::MalformedPacket(format!( "Expected RSA public key, got {:?}", self.mpis())).into()) }, ElGamalEncryptSign | ElGamalEncrypt => if let mpi::PublicKey::ElGamal { p, g, y } = self.mpis() { // OpenPGP encodes E and C separately, but our // cryptographic library concatenates them. let size = p.value().len(); let mut rng = RandomNumberGenerator::new_userspace()?; let pk = Pubkey::load_elgamal(&p.try_into()?, &g.try_into()?, &y.try_into()?)?; let esk = pk.encrypt(data, "PKCS1v15", &mut rng)?; if esk.len() != size * 2 { return Err(Error::MalformedMPI( format!("Expected ciphertext with length {}, got {}", size * 2, esk.len())).into()); } Ok(mpi::Ciphertext::ElGamal { e: MPI::new(&esk[..size]), c: MPI::new(&esk[size..]), }) } else { Err(Error::MalformedPacket(format!( "Expected ElGamal public key, got {:?}", self.mpis())).into()) }, ECDH => crate::crypto::ecdh::encrypt(self.parts_as_public(), data), RSASign | DSA | ECDSA | EdDSA | Ed25519 | Ed448 => Err(Error::InvalidOperation( format!("{} is not an encryption algorithm", self.pk_algo()) ).into()), X25519 | X448 | Private(_) | Unknown(_) => Err(Error::UnsupportedPublicKeyAlgorithm(self.pk_algo()).into()), } } /// Verifies the given signature. pub(crate) fn verify_backend(&self, sig: &mpi::Signature, hash_algo: HashAlgorithm, digest: &[u8]) -> Result<()> { use crate::crypto::mpi::Signature; fn bad(e: impl ToString) -> anyhow::Error { Error::BadSignature(e.to_string()).into() } let ok = match (self.mpis(), sig) { (PublicKey::RSA { e, n }, Signature::RSA { s }) => { let pk = Pubkey::load_rsa(&n.try_into()?, &e.try_into()?)?; pk.verify(digest, s.value(), &format!("PKCS1v15(Raw,{})", hash_algo.botan_name()?))? }, (PublicKey::DSA { y, q, p, g }, Signature::DSA { s, r }) => { // OpenPGP encodes R and S separately, but our // cryptographic library expects them to be // concatenated. let size = q.value().len(); let mut sig = Vec::with_capacity(2 * size); // We need to zero-pad them at the front, because // the MPI encoding drops leading zero bytes. sig.extend_from_slice(&r.value_padded(size).map_err(bad)?); sig.extend_from_slice(&s.value_padded(size).map_err(bad)?); let pk = Pubkey::load_dsa(&p.try_into()?, &q.try_into()?, &g.try_into()?, &y.try_into()?)?; let truncated_digest = &digest[..size.min(digest.len())]; pk.verify(truncated_digest, &sig, "Raw").unwrap() }, (PublicKey::ECDSA { curve, q }, Signature::ECDSA { s, r }) => { // OpenPGP encodes R and S separately, but our // cryptographic library expects them to be // concatenated. let size = curve.field_size()?; let mut sig = Vec::with_capacity(2 * size); // We need to zero-pad them at the front, because // the MPI encoding drops leading zero bytes. sig.extend_from_slice(&r.value_padded(size).map_err(bad)?); sig.extend_from_slice(&s.value_padded(size).map_err(bad)?); let (x, y) = q.decode_point(curve)?; let pk = Pubkey::load_ecdsa(&botan::MPI::new_from_bytes(&x)?, &botan::MPI::new_from_bytes(&y)?, curve.botan_name()?)?; pk.verify(digest, &sig, "Raw")? }, _ => return Err(Error::MalformedPacket(format!( "unsupported combination of key {} and signature {:?}.", self.pk_algo(), sig)).into()), }; if ok { Ok(()) } else { Err(Error::ManipulatedMessage.into()) } } } impl<R> Key4<SecretParts, R> where R: key::KeyRole, { /// Creates a new OpenPGP public key packet for an existing RSA key. /// /// The RSA key will use public exponent `e` and modulo `n`. The key will /// have its creation date set to `ctime` or the current time if `None` /// is given. pub fn import_secret_rsa<T>(d: &[u8], p: &[u8], q: &[u8], ctime: T) -> Result<Self> where T: Into<Option<SystemTime>> { let d = botan::MPI::new_from_bytes(d)?; let p = botan::MPI::new_from_bytes(p)?; let q = botan::MPI::new_from_bytes(q)?; // Compute e ≡ d⁻¹ (mod 𝜙). let phi = p.mp_sub_u32(1)?.mp_mul(&q.mp_sub_u32(1)?)?; let e = botan::MPI::modular_inverse(&d, &phi)?; let secret = Privkey::load_rsa(&p.try_into()?, &q.try_into()?, &e.try_into()?)?; let (public, secret) = rsa_rfc4880(secret)?; Self::with_secret( ctime.into().unwrap_or_else(crate::now), PublicKeyAlgorithm::RSAEncryptSign, public, secret.into()) } /// Generates a new RSA key with a public modulus of size `bits`. pub fn generate_rsa(bits: usize) -> Result<Self> { let mut rng = RandomNumberGenerator::new_userspace()?; let secret = Privkey::create("RSA", &format!("{}", bits), &mut rng)?; let (public, secret) = rsa_rfc4880(secret)?; Self::with_secret( crate::now(), PublicKeyAlgorithm::RSAEncryptSign, public, secret.into()) } /// Generates a new ECC key over `curve`. /// /// If `for_signing` is false a ECDH key, if it's true either a /// EdDSA or ECDSA key is generated. Giving `for_signing == true` and /// `curve == Cv25519` will produce an error. Likewise /// `for_signing == false` and `curve == Ed25519` will produce an error. pub(crate) fn generate_ecc_backend(for_signing: bool, curve: Curve) -> Result<(PublicKeyAlgorithm, mpi::PublicKey, mpi::SecretKeyMaterial)> { let mut rng = RandomNumberGenerator::new_userspace()?; let hash = crate::crypto::ecdh::default_ecdh_kdf_hash(&curve); let sym = crate::crypto::ecdh::default_ecdh_kek_cipher(&curve); let field_sz_bits = curve.bits()?; match (curve, for_signing) { (Curve::Ed25519, true) => unreachable!("handled in Key4::generate_ecc"), (Curve::Cv25519, false) => unreachable!("handled in Key4::generate_ecc"), (curve, true) => { let secret = Privkey::create("ECDSA", curve.botan_name()?, &mut rng)?; let public = secret.pubkey()?; let public_mpis = mpi::PublicKey::ECDSA { curve, q: MPI::new_point(&public.get_field("public_x")?.to_bin()?, &public.get_field("public_y")?.to_bin()?, field_sz_bits), }; let private_mpis = mpi::SecretKeyMaterial::ECDSA { scalar: secret.get_field("x")?.try_into()?, }; Ok((PublicKeyAlgorithm::ECDSA, public_mpis, private_mpis)) }, (curve, false) => { let secret = Privkey::create("ECDH", curve.botan_name()?, &mut rng)?; let public = secret.pubkey()?; let public_mpis = mpi::PublicKey::ECDH { curve, q: MPI::new_point(&public.get_field("public_x")?.to_bin()?, &public.get_field("public_y")?.to_bin()?, field_sz_bits), hash, sym, }; let private_mpis = mpi::SecretKeyMaterial::ECDH { scalar: secret.get_field("x")?.try_into()?, }; Ok((PublicKeyAlgorithm::ECDH, public_mpis, private_mpis)) }, } } } /// Returns an RSA secret key in the format that OpenPGP /// expects. fn rsa_rfc4880(secret: Privkey) -> Result<(mpi::PublicKey, mpi::SecretKeyMaterial)> { let public = secret.pubkey()?; let e = public.get_field("e")?; let n = public.get_field("n")?; let d = secret.get_field("d")?; let p = secret.get_field("p")?; let q = secret.get_field("q")?; let (p, q, u) = if p.compare(&q)? == std::cmp::Ordering::Less { let u = botan::MPI::modular_inverse(&p, &q)?; (p, q, u) } else { let c = secret.get_field("c")?; (q, p, c) }; let public = mpi::PublicKey::RSA { e: e.try_into()?, n: n.try_into()?, }; let secret = mpi::SecretKeyMaterial::RSA { d: d.try_into()?, p: p.try_into()?, q: q.try_into()?, u: u.try_into()?, }; Ok((public, secret)) } impl Curve { /// Returns the name of the algorithm for use with Botan's /// constructor. pub(crate) fn botan_name(&self) -> Result<&'static str> { use Curve::*; match self { NistP256 => Ok("secp256r1"), NistP384 => Ok("secp384r1"), NistP521 => Ok("secp521r1"), BrainpoolP256 => Ok("brainpool256r1"), BrainpoolP384 => Ok("brainpool384r1"), BrainpoolP512 => Ok("brainpool512r1"), Ed25519 | // Handled differently. Cv25519 | // Handled differently. Unknown(_) => Err(Error::UnsupportedEllipticCurve(self.clone()).into()), } } } ���������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/src/crypto/backend/botan/ecdh.rs����������������������������������������������0000644�0000000�0000000�00000010216�10461020230�0021011�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������//! Elliptic Curve Diffie-Hellman. use botan::{ RandomNumberGenerator, Privkey, }; use crate::{ Error, Result, }; use crate::crypto::SessionKey; use crate::crypto::ecdh::{encrypt_wrap, decrypt_unwrap}; use crate::crypto::mem::Protected; use crate::crypto::mpi::{ MPI, PublicKey, SecretKeyMaterial, Ciphertext}; use crate::packet::{key, Key}; use crate::types::Curve; /// Wraps a session key using Elliptic Curve Diffie-Hellman. #[allow(non_snake_case)] pub fn encrypt<R>(recipient: &Key<key::PublicParts, R>, session_key: &SessionKey) -> Result<Ciphertext> where R: key::KeyRole { let mut rng = RandomNumberGenerator::new_userspace()?; if let PublicKey::ECDH { ref curve, ref q,.. } = recipient.mpis() { match curve { Curve::Cv25519 => Err(Error::InvalidArgument("implemented elsewhere".into()).into()), // N/A Curve::Unknown(_) => Err(Error::UnsupportedEllipticCurve(curve.clone()).into()), Curve::Ed25519 => Err(Error::UnsupportedEllipticCurve(curve.clone()).into()), Curve::NistP256 | Curve::NistP384 | Curve::NistP521 | Curve::BrainpoolP256 | Curve::BrainpoolP384 | Curve::BrainpoolP512 => { // Obtain the recipient public key R let R = &q.value(); // Generate an ephemeral key pair {v, V=vG} let field_size = curve.field_size()?; let v = Privkey::create("ECDH", curve.botan_name()?, &mut rng)?; let Vx = v.pubkey()?.get_field("public_x")?; let Vy = v.pubkey()?.get_field("public_y")?; // Compute the shared point S = vR; let S: Protected = v.agree(&R, 32, b"", "Raw")?.into(); let Sx: Protected = S[..field_size].into(); encrypt_wrap(recipient, session_key, MPI::new_point(&Vx.to_bin()?, &Vy.to_bin()?, field_size * 8), &Sx.into()) } } } else { Err(Error::InvalidArgument("Expected an ECDHPublicKey".into()).into()) } } /// Unwraps a session key using Elliptic Curve Diffie-Hellman. #[allow(non_snake_case)] pub fn decrypt<R>(recipient: &Key<key::PublicParts, R>, recipient_sec: &SecretKeyMaterial, ciphertext: &Ciphertext, plaintext_len: Option<usize>) -> Result<SessionKey> where R: key::KeyRole { match (recipient.mpis(), recipient_sec, ciphertext) { (PublicKey::ECDH { ref curve, ..}, SecretKeyMaterial::ECDH { ref scalar, }, Ciphertext::ECDH { ref e, .. }) => { let S: Protected = match curve { Curve::Cv25519 => return Err(Error::InvalidArgument("implemented elsewhere".into()).into()), // N/A Curve::Unknown(_) => return Err(Error::UnsupportedEllipticCurve(curve.clone()).into()), Curve::Ed25519 => return Err(Error::UnsupportedEllipticCurve(curve.clone()).into()), Curve::NistP256 | Curve::NistP384 | Curve::NistP521 | Curve::BrainpoolP256 | Curve::BrainpoolP384 | Curve::BrainpoolP512 => { // Get the public part V of the ephemeral key. let V = &e.value(); // Get our secret key. let r = Privkey::load_ecdh( &botan::MPI::new_from_bytes(scalar.value())?, curve.botan_name()?)?; // Compute the shared point S = rV = rvG, where (r, R) // is the recipient's key pair. r.agree(V, curve.field_size()?, b"", "Raw")?.into() }, }; decrypt_unwrap(recipient.role_as_unspecified(), &S, ciphertext, plaintext_len) } _ => Err(Error::InvalidArgument("Expected an ECDHPublicKey".into()).into()), } } ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/src/crypto/backend/botan/hash.rs����������������������������������������������0000644�0000000�0000000�00000005612�10461020230�0021035�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������use std::io; use crate::crypto::hash::Digest; use crate::{Error, Result}; use crate::types::HashAlgorithm; #[derive(Clone)] struct Hash(botan::HashFunction); impl Digest for Hash { fn update(&mut self, data: &[u8]) { self.0.update(data).expect("infallible"); } fn digest(&mut self, digest: &mut [u8]) -> Result<()> { let d = self.0.finish().expect("infallible"); let l = d.len().min(digest.len()); digest[..l].copy_from_slice(&d[..l]); Ok(()) } } impl io::Write for Hash { fn write(&mut self, buf: &[u8]) -> io::Result<usize> { self.update(buf); Ok(buf.len()) } fn flush(&mut self) -> io::Result<()> { // Do nothing. Ok(()) } } impl HashAlgorithm { /// Whether Sequoia supports this algorithm. pub fn is_supported(self) -> bool { match self { HashAlgorithm::SHA1 => true, HashAlgorithm::SHA224 => true, HashAlgorithm::SHA256 => true, HashAlgorithm::SHA384 => true, HashAlgorithm::SHA512 => true, HashAlgorithm::SHA3_256 => true, HashAlgorithm::SHA3_512 => true, HashAlgorithm::RipeMD => true, HashAlgorithm::MD5 => true, HashAlgorithm::Private(_) => false, HashAlgorithm::Unknown(_) => false, } } /// Creates a new hash context for this algorithm. /// /// # Errors /// /// Fails with `Error::UnsupportedHashAlgorithm` if Sequoia does /// not support this algorithm. See /// [`HashAlgorithm::is_supported`]. /// /// [`HashAlgorithm::is_supported`]: HashAlgorithm::is_supported() pub(crate) fn new_hasher(self) -> Result<Box<dyn Digest>> { Ok(Box::new(Hash( botan::HashFunction::new(self.botan_name()?) .map_err(|_| Error::UnsupportedHashAlgorithm(self))?))) } } impl HashAlgorithm { /// Returns the name of the algorithm for use with Botan's /// constructor. pub(crate) fn botan_name(self) -> Result<&'static str> { match self { HashAlgorithm::SHA1 => Ok("SHA-1"), HashAlgorithm::SHA224 => Ok("SHA-224"), HashAlgorithm::SHA256 => Ok("SHA-256"), HashAlgorithm::SHA384 => Ok("SHA-384"), HashAlgorithm::SHA512 => Ok("SHA-512"), HashAlgorithm::SHA3_256 => Ok("SHA-3(256)"), HashAlgorithm::SHA3_512 => Ok("SHA-3(512)"), HashAlgorithm::MD5 => Ok("MD5"), HashAlgorithm::RipeMD => Ok("RIPEMD-160"), HashAlgorithm::Private(_) | HashAlgorithm::Unknown(_) => Err(Error::UnsupportedHashAlgorithm(self).into()), } } } #[cfg(test)] mod tests { use super::*; #[test] fn is_supported() { for h in (0..=255).into_iter().map(HashAlgorithm::from) { assert_eq!(h.is_supported(), h.context().is_ok()); } } } ����������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/src/crypto/backend/botan/kdf.rs�����������������������������������������������0000644�0000000�0000000�00000002740�10461020230�0020655�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������use crate::{ Result, crypto::{ SessionKey, backend::interface::Kdf, }, }; impl Kdf for super::Backend { fn hkdf_sha256(ikm: &SessionKey, salt: Option<&[u8]>, info: &[u8], okm: &mut SessionKey) -> Result<()> { assert!(okm.len() <= 255 * 32); const NO_SALT: [u8; 32] = [0; 32]; let salt = salt.unwrap_or(&NO_SALT); // XXX: It'd be nice to write that directly to `okm`, but botan-rs // does not have such an interface. let okm_heap: SessionKey = botan::kdf("HKDF(SHA-256)", okm.len(), &*ikm, salt, info)? .into(); // XXX: Now copy the secret. let l = okm.len().min(okm_heap.len()); okm[..l].copy_from_slice(&okm_heap[..l]); Ok(()) } fn hkdf_sha512(ikm: &SessionKey, salt: Option<&[u8]>, info: &[u8], okm: &mut SessionKey) -> Result<()> { assert!(okm.len() <= 255 * 64); const NO_SALT: [u8; 64] = [0; 64]; let salt = salt.unwrap_or(&NO_SALT); // XXX: It'd be nice to write that directly to `okm`, but botan-rs // does not have such an interface. let okm_heap: SessionKey = botan::kdf("HKDF(SHA-512)", okm.len(), &*ikm, salt, info)? .into(); // XXX: Now copy the secret. let l = okm.len().min(okm_heap.len()); okm[..l].copy_from_slice(&okm_heap[..l]); Ok(()) } } ��������������������������������sequoia-openpgp-2.0.0/src/crypto/backend/botan/symmetric.rs�����������������������������������������0000644�0000000�0000000�00000010036�10461020230�0022122�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������use crate::crypto::symmetric::Mode; use crate::{Error, Result}; use crate::types::SymmetricAlgorithm; struct Ecb(botan::BlockCipher, usize); impl Mode for Ecb { fn block_size(&self) -> usize { self.1 } fn encrypt(&mut self, dst: &mut [u8], src: &[u8]) -> Result<()> { debug_assert_eq!(dst.len(), src.len()); let l = dst.len().min(src.len()); dst[..l].copy_from_slice(&src[..l]); self.0.encrypt_in_place(dst)?; Ok(()) } fn decrypt(&mut self, dst: &mut [u8], src: &[u8]) -> Result<()> { debug_assert_eq!(dst.len(), src.len()); let l = dst.len().min(src.len()); dst[..l].copy_from_slice(&src[..l]); self.0.decrypt_in_place(dst)?; Ok(()) } } struct Cfb(botan::Cipher, usize); impl Mode for Cfb { fn block_size(&self) -> usize { self.1 } fn encrypt(&mut self, dst: &mut [u8], src: &[u8]) -> Result<()> { debug_assert_eq!(dst.len(), src.len()); self.0.finish_into(src, dst)?; Ok(()) } fn decrypt(&mut self, dst: &mut [u8], src: &[u8]) -> Result<()> { debug_assert_eq!(dst.len(), src.len()); self.0.finish_into(src, dst)?; Ok(()) } } impl SymmetricAlgorithm { /// Returns whether this algorithm is supported by the crypto backend. pub(crate) fn is_supported_by_backend(&self) -> bool { use self::SymmetricAlgorithm::*; #[allow(deprecated)] match &self { TripleDES | IDEA | CAST5 | Blowfish | AES128 | AES192 | AES256 | Twofish | Camellia128 | Camellia192 | Camellia256 => true, Unencrypted | Private(_) | Unknown(_) => false, } } /// Returns the name of the algorithm for use with Botan's /// constructor. pub(crate) fn botan_name(self) -> Result<&'static str> { #[allow(deprecated)] match self { SymmetricAlgorithm::IDEA => Ok("IDEA"), SymmetricAlgorithm::TripleDES => Ok("3DES"), SymmetricAlgorithm::CAST5 => Ok("CAST-128"), SymmetricAlgorithm::Blowfish => Ok("Blowfish"), SymmetricAlgorithm::AES128 => Ok("AES-128"), SymmetricAlgorithm::AES192 => Ok("AES-192"), SymmetricAlgorithm::AES256 => Ok("AES-256"), SymmetricAlgorithm::Twofish => Ok("Twofish"), SymmetricAlgorithm::Camellia128 => Ok("Camellia-128"), SymmetricAlgorithm::Camellia192 => Ok("Camellia-192"), SymmetricAlgorithm::Camellia256 => Ok("Camellia-256"), SymmetricAlgorithm::Unencrypted | SymmetricAlgorithm::Unknown(_) | SymmetricAlgorithm::Private(_) => Err(Error::UnsupportedSymmetricAlgorithm(self).into()), } } /// Creates a context for encrypting in CFB mode. pub(crate) fn make_encrypt_cfb(self, key: &[u8], iv: Vec<u8>) -> Result<Box<dyn Mode>> { let mut cipher = botan::Cipher::new( &format!("{}/CFB", self.botan_name()?), botan::CipherDirection::Encrypt)?; cipher.set_key(key)?; cipher.start(&iv)?; Ok(Box::new(Cfb(cipher, self.block_size()?))) } /// Creates a context for decrypting in CFB mode. pub(crate) fn make_decrypt_cfb(self, key: &[u8], iv: Vec<u8>) -> Result<Box<dyn Mode>> { let mut cipher = botan::Cipher::new( &format!("{}/CFB", self.botan_name()?), botan::CipherDirection::Decrypt)?; cipher.set_key(key)?; cipher.start(&iv)?; Ok(Box::new(Cfb(cipher, self.block_size()?))) } /// Creates a context for encrypting in ECB mode. pub(crate) fn make_encrypt_ecb(self, key: &[u8]) -> Result<Box<dyn Mode>> { let mut cipher = botan::BlockCipher::new(self.botan_name()?)?; cipher.set_key(key)?; Ok(Box::new(Ecb(cipher, self.block_size()?))) } /// Creates a context for decrypting in ECB mode. pub(crate) fn make_decrypt_ecb(self, key: &[u8]) -> Result<Box<dyn Mode>> { self.make_encrypt_ecb(key) } } ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/src/crypto/backend/botan.rs���������������������������������������������������0000644�0000000�0000000�00000004270�10461020230�0020111�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������//! Implementation of Sequoia crypto API using the Botan cryptographic library. use crate::{ types::*, }; pub mod aead; pub mod asymmetric; pub mod ecdh; pub mod hash; pub mod kdf; pub mod symmetric; pub struct Backend(()); impl super::interface::Backend for Backend { fn backend() -> String { "Botan".to_string() } fn random(buf: &mut [u8]) -> crate::Result<()> { let mut rng = botan::RandomNumberGenerator::new_system()?; rng.fill(buf)?; Ok(()) } } impl AEADAlgorithm { /// Returns the best AEAD mode supported by the backend. /// /// This SHOULD return OCB, which is the mandatory-to-implement /// algorithm and the most performing one, but fall back to any /// supported algorithm. pub(crate) const fn const_default() -> AEADAlgorithm { AEADAlgorithm::OCB } pub(crate) fn is_supported_by_backend(&self) -> bool { use self::AEADAlgorithm::*; match &self { EAX | OCB | GCM => true, Private(_) | Unknown(_) => false, } } #[cfg(test)] pub(crate) fn supports_symmetric_algo(&self, algo: &SymmetricAlgorithm) -> bool { match &self { AEADAlgorithm::EAX => match algo { SymmetricAlgorithm::AES128 | SymmetricAlgorithm::AES192 | SymmetricAlgorithm::AES256 | SymmetricAlgorithm::Twofish | SymmetricAlgorithm::Camellia128 | SymmetricAlgorithm::Camellia192 | SymmetricAlgorithm::Camellia256 => true, _ => false, }, AEADAlgorithm::OCB => match algo { SymmetricAlgorithm::AES128 | SymmetricAlgorithm::AES192 | SymmetricAlgorithm::AES256 | SymmetricAlgorithm::Twofish | SymmetricAlgorithm::Camellia128 | SymmetricAlgorithm::Camellia192 | SymmetricAlgorithm::Camellia256 => true, _ => false, }, _ => false } } } ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/src/crypto/backend/cng/aead.rs������������������������������������������������0000644�0000000�0000000�00000034220�10461020230�0020445�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������//! Implementation of AEAD using Windows CNG API. use std::cmp::{self, Ordering}; use crate::{Error, Result}; use crate::crypto::aead::{Aead, CipherOp}; use crate::crypto::mem::secure_cmp; use crate::seal; use crate::types::{AEADAlgorithm, SymmetricAlgorithm}; use cipher::generic_array::{GenericArray, ArrayLength}; use cipher::generic_array::typenum::{U12, U15, U16, U128, U192, U256}; use cipher::{BlockCipher, BlockDecrypt, BlockEncrypt, BlockSizeUser, KeyInit, Unsigned}; use eax::online::{Eax, Encrypt, Decrypt}; use win_crypto_ng::symmetric::{BlockCipherKey, Aes}; /// Disables authentication checks. /// /// This is DANGEROUS, and is only useful for debugging problems with /// malformed AEAD-encrypted messages. const DANGER_DISABLE_AUTHENTICATION: bool = false; trait GenericArrayExt<T, N: ArrayLength<T>> { const LEN: usize; /// Like [`GenericArray::from_slice`], but fallible. fn try_from_slice(slice: &[T]) -> Result<&GenericArray<T, N>> { if slice.len() == Self::LEN { Ok(GenericArray::from_slice(slice)) } else { Err(Error::InvalidArgument( format!("Invalid slice length, want {}, got {}", Self::LEN, slice.len())).into()) } } } impl<T, N: ArrayLength<T>> GenericArrayExt<T, N> for GenericArray<T, N> { const LEN: usize = N::USIZE; } impl AEADAlgorithm { pub(crate) fn context( &self, sym_algo: SymmetricAlgorithm, key: &[u8], aad: &[u8], nonce: &[u8], op: CipherOp, ) -> Result<Box<dyn Aead>> { #[allow(deprecated)] match self { AEADAlgorithm::EAX => match sym_algo { SymmetricAlgorithm::AES128 => match op { CipherOp::Encrypt => { let mut ctx = Eax::<BlockCipherKey<Aes, U128>, Encrypt>::with_key_and_nonce( GenericArray::try_from_slice(key)?, GenericArray::try_from_slice(nonce)?); ctx.update_assoc(aad); Ok(Box::new(ctx)) }, CipherOp::Decrypt => { let mut ctx = Eax::<BlockCipherKey<Aes, U128>, Decrypt>::with_key_and_nonce( GenericArray::try_from_slice(key)?, GenericArray::try_from_slice(nonce)?); ctx.update_assoc(aad); Ok(Box::new(ctx)) }, }, SymmetricAlgorithm::AES192 => match op { CipherOp::Encrypt => { let mut ctx = Eax::<BlockCipherKey<Aes, U192>, Encrypt>::with_key_and_nonce( GenericArray::try_from_slice(key)?, GenericArray::try_from_slice(nonce)?); ctx.update_assoc(aad); Ok(Box::new(ctx)) }, CipherOp::Decrypt => { let mut ctx = Eax::<BlockCipherKey<Aes, U192>, Decrypt>::with_key_and_nonce( GenericArray::try_from_slice(key)?, GenericArray::try_from_slice(nonce)?); ctx.update_assoc(aad); Ok(Box::new(ctx)) }, }, SymmetricAlgorithm::AES256 => match op { CipherOp::Encrypt => { let mut ctx = Eax::<BlockCipherKey<Aes, U256>, Encrypt>::with_key_and_nonce( GenericArray::try_from_slice(key)?, GenericArray::try_from_slice(nonce)?); ctx.update_assoc(aad); Ok(Box::new(ctx)) }, CipherOp::Decrypt => { let mut ctx = Eax::<BlockCipherKey<Aes, U256>, Decrypt>::with_key_and_nonce( GenericArray::try_from_slice(key)?, GenericArray::try_from_slice(nonce)?); ctx.update_assoc(aad); Ok(Box::new(ctx)) }, }, _ => Err(Error::UnsupportedSymmetricAlgorithm(sym_algo).into()), }, AEADAlgorithm::OCB => { use ocb3::{Ocb3, Nonce}; match sym_algo { SymmetricAlgorithm::AES128 => { let nonce = Nonce::try_from_slice(nonce)?; let cipher = Ocb3::<BlockCipherKey<Aes, U128>, U15>::new_from_slice(key)?; Ok(Box::new(Ocb { cipher, nonce: *nonce, aad: aad.to_vec() })) }, SymmetricAlgorithm::AES192 => { let nonce = Nonce::try_from_slice(nonce)?; let cipher = Ocb3::<BlockCipherKey<Aes, U192>, U15>::new_from_slice(key)?; Ok(Box::new(Ocb { cipher, nonce: *nonce, aad: aad.to_vec() })) }, SymmetricAlgorithm::AES256 => { let nonce = Nonce::try_from_slice(nonce)?; let cipher = Ocb3::<BlockCipherKey<Aes, U256>, U15>::new_from_slice(key)?; Ok(Box::new(Ocb { cipher, nonce: *nonce, aad: aad.to_vec() })) }, | SymmetricAlgorithm::IDEA | SymmetricAlgorithm::TripleDES | SymmetricAlgorithm::CAST5 | SymmetricAlgorithm::Blowfish | SymmetricAlgorithm::Twofish | SymmetricAlgorithm::Camellia128 | SymmetricAlgorithm::Camellia192 | SymmetricAlgorithm::Camellia256 | SymmetricAlgorithm::Private(_) | SymmetricAlgorithm::Unknown(_) | SymmetricAlgorithm::Unencrypted => Err(Error::UnsupportedSymmetricAlgorithm(sym_algo).into()), } }, AEADAlgorithm::GCM => { use aes_gcm::{AesGcm, Nonce}; match sym_algo { SymmetricAlgorithm::AES128 => { let nonce = Nonce::try_from_slice(nonce)?; let cipher = AesGcm::<BlockCipherKey<Aes, U128>, U12>::new_from_slice(key)?; Ok(Box::new(Gcm { cipher, nonce: *nonce, aad: aad.to_vec() })) }, SymmetricAlgorithm::AES192 => { let nonce = Nonce::try_from_slice(nonce)?; let cipher = AesGcm::<BlockCipherKey<Aes, U192>, U12>::new_from_slice(key)?; Ok(Box::new(Gcm { cipher, nonce: *nonce, aad: aad.to_vec() })) }, SymmetricAlgorithm::AES256 => { let nonce = Nonce::try_from_slice(nonce)?; let cipher = AesGcm::<BlockCipherKey<Aes, U256>, U12>::new_from_slice(key)?; Ok(Box::new(Gcm { cipher, nonce: *nonce, aad: aad.to_vec() })) }, | SymmetricAlgorithm::IDEA | SymmetricAlgorithm::TripleDES | SymmetricAlgorithm::CAST5 | SymmetricAlgorithm::Blowfish | SymmetricAlgorithm::Twofish | SymmetricAlgorithm::Camellia128 | SymmetricAlgorithm::Camellia192 | SymmetricAlgorithm::Camellia256 | SymmetricAlgorithm::Private(_) | SymmetricAlgorithm::Unknown(_) | SymmetricAlgorithm::Unencrypted => Err(Error::UnsupportedSymmetricAlgorithm(sym_algo).into()), } }, AEADAlgorithm::Private(_) | AEADAlgorithm::Unknown(_) => Err(Error::UnsupportedAEADAlgorithm(*self).into()), } } } type TagLen = U16; macro_rules! impl_aead { ($($type: ty),*) => { $( impl Aead for Eax<$type, Encrypt, TagLen> { fn digest_size(&self) -> usize { TagLen::USIZE } fn encrypt_seal(&mut self, dst: &mut [u8], src: &[u8]) -> Result<()> { debug_assert_eq!(dst.len(), src.len() + self.digest_size()); let len = core::cmp::min(dst.len(), src.len()); dst[..len].copy_from_slice(&src[..len]); Eax::<$type, Encrypt>::encrypt(self, &mut dst[..len]); let tag = self.tag_clone(); dst[src.len()..].copy_from_slice(&tag[..]); Ok(()) } fn decrypt_verify(&mut self, _dst: &mut [u8], _src: &[u8]) -> Result<()> { panic!("AEAD decryption called in the encryption context") } } impl seal::Sealed for Eax<$type, Encrypt> {} )* $( impl Aead for Eax<$type, Decrypt> { fn digest_size(&self) -> usize { TagLen::USIZE } fn encrypt_seal(&mut self, _dst: &mut [u8], _src: &[u8]) -> Result<()> { panic!("AEAD encryption called in the decryption context") } fn decrypt_verify(&mut self, dst: &mut [u8], src: &[u8]) -> Result<()> { debug_assert_eq!(dst.len() + self.digest_size(), src.len()); // Split src into ciphertext and digest. let l = self.digest_size(); let digest = &src[src.len().saturating_sub(l)..]; let src = &src[..src.len().saturating_sub(l)]; let len = core::cmp::min(dst.len(), src.len()); dst[..len].copy_from_slice(&src[..len]); self.decrypt_unauthenticated_hazmat(&mut dst[..len]); let chunk_digest = self.tag_clone(); if secure_cmp(&chunk_digest[..], digest) != Ordering::Equal && ! DANGER_DISABLE_AUTHENTICATION { return Err(Error::ManipulatedMessage.into()); } Ok(()) } } impl seal::Sealed for Eax<$type, Decrypt> {} )* }; } impl_aead!(BlockCipherKey<Aes, U128>, BlockCipherKey<Aes, U192>, BlockCipherKey<Aes, U256>); struct Ocb<Cipher: BlockCipher + BlockSizeUser<BlockSize = U16> + BlockDecrypt + BlockEncrypt> { cipher: ocb3::Ocb3<Cipher, U15>, nonce: GenericArray<u8, U15>, aad: Vec<u8>, } impl<Cipher> Aead for Ocb<Cipher> where Cipher: BlockCipher + BlockSizeUser<BlockSize = U16> + BlockDecrypt + BlockEncrypt, { fn digest_size(&self) -> usize { TagLen::USIZE } fn encrypt_seal(&mut self, dst: &mut [u8], src: &[u8]) -> Result<()> { debug_assert_eq!(dst.len(), src.len() + self.digest_size()); use ocb3::AeadInPlace; let len = cmp::min(dst.len(), src.len()); dst[..len].copy_from_slice(&src[..len]); let tag = self.cipher.encrypt_in_place_detached(&self.nonce, &self.aad, &mut dst[..len])?; debug_assert_eq!(dst[len..].len(), tag.len()); let tag_len = cmp::min(dst[len..].len(), tag.len()); dst[len..len + tag_len].copy_from_slice(&tag[..tag_len]); Ok(()) } fn decrypt_verify(&mut self, dst: &mut [u8], src: &[u8]) -> Result<()> { debug_assert_eq!(dst.len() + self.digest_size(), src.len()); use ocb3::AeadInPlace; // Split src into ciphertext and digest. let len = src.len().saturating_sub(self.digest_size()); let digest = &src[len..]; let src = &src[..len]; debug_assert_eq!(dst.len(), src.len()); let len = core::cmp::min(dst.len(), src.len()); dst[..len].copy_from_slice(&src[..len]); self.cipher.decrypt_in_place_detached(&self.nonce, &self.aad, dst, digest.try_into()?)?; Ok(()) } } impl<'a, Cipher> seal::Sealed for Ocb<Cipher> where Cipher: BlockCipher + BlockSizeUser<BlockSize = U16> + BlockDecrypt + BlockEncrypt, {} struct Gcm<Cipher: BlockCipher + BlockSizeUser<BlockSize = U16> + BlockEncrypt> { cipher: aes_gcm::AesGcm<Cipher, U12>, nonce: GenericArray<u8, U12>, aad: Vec<u8>, } impl<Cipher> Aead for Gcm<Cipher> where Cipher: BlockCipher + BlockSizeUser<BlockSize = U16> + BlockEncrypt, { fn digest_size(&self) -> usize { TagLen::USIZE } fn encrypt_seal(&mut self, dst: &mut [u8], src: &[u8]) -> Result<()> { debug_assert_eq!(dst.len(), src.len() + self.digest_size()); use aes_gcm::AeadInPlace; let len = cmp::min(dst.len(), src.len()); dst[..len].copy_from_slice(&src[..len]); let tag = self.cipher.encrypt_in_place_detached(&self.nonce, &self.aad, &mut dst[..len])?; debug_assert_eq!(dst[len..].len(), tag.len()); let tag_len = cmp::min(dst[len..].len(), tag.len()); dst[len..len + tag_len].copy_from_slice(&tag[..tag_len]); Ok(()) } fn decrypt_verify(&mut self, dst: &mut [u8], src: &[u8]) -> Result<()> { debug_assert_eq!(dst.len() + self.digest_size(), src.len()); use aes_gcm::AeadInPlace; // Split src into ciphertext and digest. let len = src.len().saturating_sub(self.digest_size()); let digest = &src[len..]; let src = &src[..len]; debug_assert_eq!(dst.len(), src.len()); let len = core::cmp::min(dst.len(), src.len()); dst[..len].copy_from_slice(&src[..len]); self.cipher.decrypt_in_place_detached(&self.nonce, &self.aad, dst, digest.try_into()?)?; Ok(()) } } impl<'a, Cipher> seal::Sealed for Gcm<Cipher> where Cipher: BlockCipher + BlockSizeUser<BlockSize = U16> + BlockEncrypt, {} ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/src/crypto/backend/cng/asymmetric.rs������������������������������������������0000644�0000000�0000000�00000123052�10461020230�0021732�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������//! Implementation of asymmetric cryptography using Windows CNG API. #![allow(unused_variables)] use std::time::SystemTime; use std::convert::TryInto; use crate::{Error, Result}; use crate::crypto::asymmetric::KeyPair; use crate::crypto::backend::interface::Asymmetric; use crate::crypto::mem::Protected; use crate::crypto::mpi::{self, MPI, ProtectedMPI}; use crate::crypto::SessionKey; use crate::crypto::{pad, pad_at_least, pad_truncating}; use crate::packet::key::{Key4, SecretParts}; use crate::packet::{key, Key}; use crate::types::PublicKeyAlgorithm; use crate::types::{Curve, HashAlgorithm}; use num_bigint_dig::{traits::ModInverse, BigInt, BigUint}; use win_crypto_ng as cng; impl TryFrom<&Protected> for Box<ed25519_dalek::SigningKey> { type Error = anyhow::Error; fn try_from(value: &Protected) -> Result<Self> { if value.len() != ed25519_dalek::SECRET_KEY_LENGTH { return Err(crate::Error::InvalidArgument( "Bad Ed25519 secret length".into()).into()); } Ok(Box::new(ed25519_dalek::SigningKey::from_bytes( value.as_ref().try_into().map_err( |e: std::array::TryFromSliceError| { Error::InvalidKey(e.to_string()) })?))) } } impl Asymmetric for super::Backend { fn supports_algo(algo: PublicKeyAlgorithm) -> bool { use PublicKeyAlgorithm::*; #[allow(deprecated)] match algo { X25519 | Ed25519 | RSAEncryptSign | RSAEncrypt | RSASign | DSA | ECDH | ECDSA | EdDSA => true, X448 | Ed448 | ElGamalEncrypt | ElGamalEncryptSign | Private(_) | Unknown(_) => false, } } fn supports_curve(curve: &Curve) -> bool { use Curve::*; match curve { NistP256 | NistP384 | NistP521 | Ed25519 | Cv25519 => true, BrainpoolP256 | BrainpoolP384 | BrainpoolP512 | Unknown(_) => false, } } fn x25519_generate_key() -> Result<(Protected, [u8; 32])> { use cng::asymmetric::{Ecdh, AsymmetricKey, Export}; use cng::asymmetric::ecc::Curve25519; let pair = AsymmetricKey::builder(Ecdh(Curve25519)).build()?.export()?; let mut public = [0u8; 32]; public.copy_from_slice(pair.x()); let mut clamped_secret = pair.d().into(); Self::x25519_clamp_secret(&mut clamped_secret); Ok((clamped_secret, public)) } fn x25519_derive_public(secret: &Protected) -> Result<[u8; 32]> { use cng::asymmetric::{AsymmetricAlgorithm, AsymmetricAlgorithmId, Ecdh, Private, AsymmetricKey, Export}; use cng::asymmetric::ecc::{Curve25519, NamedCurve}; let provider = AsymmetricAlgorithm::open( AsymmetricAlgorithmId::Ecdh(NamedCurve::Curve25519) )?; let mut clamped_secret = secret.clone(); Self::x25519_clamp_secret(&mut clamped_secret); let key = AsymmetricKey::<Ecdh<Curve25519>, Private>::import_from_parts( &provider, &clamped_secret, )?; Ok(<[u8; 32]>::try_from(&key.export()?.x()[..])?) } fn x25519_shared_point(secret: &Protected, public: &[u8; 32]) -> Result<Protected> { use cng::asymmetric::{Ecdh, AsymmetricKey, Public, Private, AsymmetricAlgorithm, AsymmetricAlgorithmId}; use cng::asymmetric::agreement::secret_agreement; use cng::asymmetric::ecc::{NamedCurve, Curve25519}; let provider = AsymmetricAlgorithm::open( AsymmetricAlgorithmId::Ecdh(NamedCurve::Curve25519))?; let public = AsymmetricKey::<Ecdh<Curve25519>, Public>::import_from_parts( &provider, public, )?; let mut clamped_secret = secret.clone(); Self::x25519_clamp_secret(&mut clamped_secret); let secret = AsymmetricKey::<Ecdh<Curve25519>, Private>::import_from_parts( &provider, &clamped_secret, )?; let shared = secret_agreement(&secret, &public)?; let mut shared = Protected::from(shared.derive_raw()?); // Returned secret is little-endian, flip it to big-endian shared.reverse(); Ok(shared) } fn ed25519_generate_key() -> Result<(Protected, [u8; 32])> { let mut rng = cng::random::RandomNumberGenerator::system_preferred(); let pair = ed25519_dalek::SigningKey::generate(&mut rng); Ok((pair.to_bytes().into(), pair.verifying_key().to_bytes())) } fn ed25519_derive_public(secret: &Protected) -> Result<[u8; 32]> { use ed25519_dalek::SigningKey; let secret: Box<SigningKey> = secret.try_into()?; let public = secret.verifying_key(); Ok(public.to_bytes()) } fn ed25519_sign(secret: &Protected, _public: &[u8; 32], digest: &[u8]) -> Result<[u8; 64]> { use ed25519_dalek::{SigningKey, Signer}; let secret: Box<SigningKey> = secret.try_into()?; Ok(secret.sign(digest).to_bytes().try_into()?) } fn ed25519_verify(public: &[u8; 32], digest: &[u8], signature: &[u8; 64]) -> Result<bool> { use ed25519_dalek::{VerifyingKey, Verifier, Signature}; let public = VerifyingKey::from_bytes(public).map_err(|e| { Error::InvalidKey(e.to_string()) })?; let signature = signature.as_ref().try_into().map_err(|e: std::array::TryFromSliceError| { Error::InvalidArgument(e.to_string()) })?; let signature = Signature::from_bytes(signature); Ok(public.verify(digest, &signature).is_ok()) } fn dsa_generate_key(p_bits: usize) -> Result<(MPI, MPI, MPI, MPI, ProtectedMPI)> { // XXX: DSA key generation needs fixes upstream, or at least I // didn't figure out how to do that properly, see // https://github.com/emgre/win-crypto-ng/issues/47 let _ = p_bits; #[allow(deprecated)] Err(Error::UnsupportedPublicKeyAlgorithm( PublicKeyAlgorithm::DSA).into()) } } impl KeyPair { pub(crate) fn sign_backend(&self, secret: &mpi::SecretKeyMaterial, hash_algo: HashAlgorithm, digest: &[u8]) -> Result<mpi::Signature> { use cng::asymmetric::{AsymmetricAlgorithm, AsymmetricAlgorithmId}; use cng::asymmetric::{AsymmetricKey, Private, Rsa}; use cng::asymmetric::signature::{Signer, SignaturePadding}; use cng::key_blob::RsaKeyPrivatePayload; use cng::key_blob::EccKeyPrivatePayload; use cng::asymmetric::ecc::NamedCurve; #[allow(deprecated)] Ok(match (self.public().pk_algo(), self.public().mpis(), secret) { (PublicKeyAlgorithm::RSAEncryptSign, &mpi::PublicKey::RSA { ref e, ref n }, &mpi::SecretKeyMaterial::RSA { ref p, ref q, ref d, .. }) | (PublicKeyAlgorithm::RSASign, &mpi::PublicKey::RSA { ref e, ref n }, &mpi::SecretKeyMaterial::RSA { ref p, ref q, ref d, .. }) => { let provider = AsymmetricAlgorithm::open(AsymmetricAlgorithmId::Rsa)?; let key = AsymmetricKey::<Rsa, Private>::import_from_parts( &provider, &RsaKeyPrivatePayload { modulus: n.value(), pub_exp: e.value(), prime1: p.value(), prime2: q.value(), } )?; // As described in [Section 5.2.2 and 5.2.3 of RFC 9580], // to verify the signature, we need to encode the // signature data in a PKCS1-v1.5 packet. // // [Section 5.2.2 and 5.2.3 of RFC 9580]: // https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.2 let hash = hash_algo.try_into()?; let padding = SignaturePadding::pkcs1(hash); let sig = key.sign(digest, Some(padding))?; mpi::Signature::RSA { s: mpi::MPI::new(&*sig) } }, (PublicKeyAlgorithm::ECDSA, mpi::PublicKey::ECDSA { curve, q }, mpi::SecretKeyMaterial::ECDSA { scalar }) => { let (x, y) = q.decode_point(curve)?; // It's expected for the private key to be exactly 32/48/66 // (respective curve field size) bytes long but OpenPGP // allows leading zeros to be stripped. // Padding has to be unconditional; otherwise we have a // secret-dependent branch. let curve_bytes = curve.field_size()?; let secret = scalar.value_padded(curve_bytes); use cng::asymmetric::{ecc::{NistP256, NistP384, NistP521}, Ecdsa}; // TODO: Improve CNG public API let sig = match curve { Curve::NistP256 => { let provider = AsymmetricAlgorithm::open( AsymmetricAlgorithmId::Ecdsa(NamedCurve::NistP256) )?; let key = AsymmetricKey::<Ecdsa<NistP256>, Private>::import_from_parts( &provider, &EccKeyPrivatePayload { x, y, d: &secret } )?; key.sign(digest, None)? }, Curve::NistP384 => { let provider = AsymmetricAlgorithm::open( AsymmetricAlgorithmId::Ecdsa(NamedCurve::NistP384) )?; let key = AsymmetricKey::<Ecdsa<NistP384>, Private>::import_from_parts( &provider, &EccKeyPrivatePayload { x, y, d: &secret } )?; key.sign(digest, None)? }, Curve::NistP521 => { let provider = AsymmetricAlgorithm::open( AsymmetricAlgorithmId::Ecdsa(NamedCurve::NistP521) )?; let key = AsymmetricKey::<Ecdsa<NistP521>, Private>::import_from_parts( &provider, &EccKeyPrivatePayload { x, y, d: &secret } )?; key.sign(digest, None)? }, _ => return Err( Error::UnsupportedEllipticCurve(curve.clone()).into()), }; // CNG outputs a P1363 formatted signature - r || s let (r, s) = sig.split_at(sig.len() / 2); mpi::Signature::ECDSA { r: mpi::MPI::new(r), s: mpi::MPI::new(s), } }, (PublicKeyAlgorithm::DSA, mpi:: PublicKey::DSA { y, p, q, g }, mpi::SecretKeyMaterial::DSA { x }, ) => { use win_crypto_ng::key_blob::{DsaKeyPrivateV2Payload, DsaKeyPrivateV2Blob}; use win_crypto_ng::key_blob::{DsaKeyPrivatePayload, DsaKeyPrivateBlob}; use win_crypto_ng::asymmetric::{Dsa, DsaPrivateBlob}; use win_crypto_ng::helpers::Blob; let y = y.value_padded(p.value().len()) .map_err(|e| Error::InvalidKey(e.to_string()))?; if y.len() > 3072 / 8 { return Err(Error::InvalidOperation( "DSA keys are supported up to 3072-bits".to_string()).into() ); } enum Version { V1, V2 } // 1024-bit DSA keys are handled differently let version = if y.len() <= 128 { Version::V1 } else { Version::V2 }; let blob: DsaPrivateBlob = match version { Version::V1 => { let mut group = [0; 20]; if let Ok(v) = q.value_padded(group.len()) { group[..].copy_from_slice(&v); } else { return Err(Error::InvalidOperation( "DSA keys' group parameter exceeds 160 bits" .to_string()).into()); } DsaPrivateBlob::V1(Blob::<DsaKeyPrivateBlob>::clone_from_parts( &winapi::shared::bcrypt::BCRYPT_DSA_KEY_BLOB { dwMagic: winapi::shared::bcrypt::BCRYPT_DSA_PUBLIC_MAGIC, cbKey: y.len() as u32, Count: [0; 4], // unused Seed: [0; 20], // unused q: group, }, &DsaKeyPrivatePayload { modulus: p.value(), generator: g.value(), public: &y, priv_exp: x.value(), }, )) }, Version::V2 => { // https://github.com/dotnet/runtime/blob/67d74fca70d4670ad503e23dba9d6bc8a1b5909e/src/libraries/Common/src/System/Security/Cryptography/DSACng.ImportExport.cs#L276-L282 let hash = match q.value().len() { 20 => 0, 32 => 1, 64 => 2, _ => return Err(Error::InvalidOperation( "CNG accepts DSA q with length of either length of 20, 32 or 64".into()) .into()), }; // We don't use counter/seed values so set them to 0. // CNG pre-checks that the seed is at least |Q| long, // so we can't use an empty buffer here. let (count, seed) = ([0x0; 4], vec![0x0; q.value().len()]); let group_size = std::cmp::min(q.value().len(), 32); let key_size = y.len(); DsaPrivateBlob::V2(Blob::<DsaKeyPrivateV2Blob>::clone_from_parts( &winapi::shared::bcrypt::BCRYPT_DSA_KEY_BLOB_V2 { dwMagic: winapi::shared::bcrypt::BCRYPT_DSA_PRIVATE_MAGIC_V2, Count: count, // Size of the prime number q. // Currently, if the key is less than 128 // bits, q is 20 bytes long. // If the key exceeds 256 bits, q is 32 bytes long. cbGroupSize: group_size as u32, cbKey: key_size as u32, cbSeedLength: seed.len() as u32, hashAlgorithm: hash, standardVersion: 1, // FIPS 186-3 }, &DsaKeyPrivateV2Payload { seed: &seed, group: &q.value_padded(group_size)?, modulus: &p.value_padded(key_size)?, generator: &g.value_padded(key_size)?, public: &y, priv_exp: &x.value_padded(group_size), }, )) }, }; use win_crypto_ng::asymmetric::{Import}; let provider = AsymmetricAlgorithm::open(AsymmetricAlgorithmId::Dsa)?; let pair = AsymmetricKey::<Dsa, Private>::import( Dsa, &provider, blob )?; // CNG accepts only hash and Q of equal length. Either trim the // digest or pad it with zeroes (since it's treated as a // big-endian number). // See https://github.com/dotnet/runtime/blob/67d74fca70d4670ad503e23dba9d6bc8a1b5909e/src/libraries/Common/src/System/Security/Cryptography/DSACng.SignVerify.cs#L148. let digest = pad_truncating(&digest, q.value().len()); assert_eq!(q.value().len(), digest.len()); let sig = pair.sign(&digest, None)?; // https://tools.ietf.org/html/rfc8032#section-5.1.6 let (r, s) = sig.split_at(sig.len() / 2); mpi::Signature::DSA { r: mpi::MPI::new(r), s: mpi::MPI::new(s), } }, (pk_algo, _, _) => Err(Error::InvalidOperation(format!( "unsupported combination of algorithm {:?}, key {:?}, \ and secret key {:?}", pk_algo, self.public(), self.secret())))?, }) } } impl KeyPair { pub(crate) fn decrypt_backend( &self, secret: &mpi::SecretKeyMaterial, ciphertext: &mpi::Ciphertext, plaintext_len: Option<usize>, ) -> Result<SessionKey> { use crate::PublicKeyAlgorithm::*; #[allow(deprecated)] Ok(match (self.public().mpis(), secret, ciphertext) { (mpi::PublicKey::RSA { ref e, ref n }, mpi::SecretKeyMaterial::RSA { ref p, ref q, ref d, .. }, mpi::Ciphertext::RSA { ref c }) => { use cng::asymmetric::{AsymmetricAlgorithm, AsymmetricAlgorithmId}; use cng::asymmetric::{AsymmetricKey, Private, Rsa}; use cng::asymmetric::EncryptionPadding; use cng::key_blob::RsaKeyPrivatePayload; let provider = AsymmetricAlgorithm::open(AsymmetricAlgorithmId::Rsa)?; let key = AsymmetricKey::<Rsa, Private>::import_from_parts( &provider, &RsaKeyPrivatePayload { modulus: n.value(), pub_exp: e.value(), prime1: p.value(), prime2: q.value(), } )?; // CNG expects RSA ciphertext length to be a multiple of 8 // bytes. Since this is a big endian MPI, left-pad it with zeros let pad_to = round_up_to_multiple_of(c.value().len(), 8); assert!(pad_to >= c.value().len()); let c = c.value_padded(pad_to).expect("we don't truncate"); let decrypted = key.decrypt(Some(EncryptionPadding::Pkcs1), &c)?; SessionKey::from(decrypted) } (mpi::PublicKey::ElGamal { .. }, mpi::SecretKeyMaterial::ElGamal { .. }, mpi::Ciphertext::ElGamal { .. }) => return Err( Error::UnsupportedPublicKeyAlgorithm(ElGamalEncrypt).into()), (mpi::PublicKey::ECDH{ .. }, mpi::SecretKeyMaterial::ECDH { .. }, mpi::Ciphertext::ECDH { .. }) => crate::crypto::ecdh::decrypt(self.public(), secret, ciphertext, plaintext_len)?, (public, secret, ciphertext) => return Err(Error::InvalidOperation(format!( "unsupported combination of key pair {:?}/{:?} \ and ciphertext {:?}", public, secret, ciphertext)).into()), }) } } impl<P: key::KeyParts, R: key::KeyRole> Key<P, R> { /// Encrypts the given data with this key. pub(crate) fn encrypt_backend(&self, data: &SessionKey) -> Result<mpi::Ciphertext> { use cng::asymmetric::{AsymmetricAlgorithm, AsymmetricAlgorithmId}; use cng::asymmetric::{AsymmetricKey, Public, Rsa}; use cng::key_blob::RsaKeyPublicPayload; use PublicKeyAlgorithm::*; #[allow(deprecated)] match self.pk_algo() { RSAEncryptSign | RSAEncrypt => { // Extract the public recipient. match self.mpis() { mpi::PublicKey::RSA { e, n } => { // The ciphertext has the length of the modulus. let ciphertext_len = n.value().len(); if data.len() + 11 > ciphertext_len { return Err(Error::InvalidArgument( "Plaintext data too large".into()).into()); } let provider = AsymmetricAlgorithm::open(AsymmetricAlgorithmId::Rsa)?; let key = AsymmetricKey::<Rsa, Public>::import_from_parts( &provider, &RsaKeyPublicPayload { modulus: n.value(), pub_exp: e.value(), } )?; let padding = win_crypto_ng::asymmetric::EncryptionPadding::Pkcs1; let ciphertext = key.encrypt(Some(padding), data)?; Ok(mpi::Ciphertext::RSA { c: mpi::MPI::new(ciphertext.as_ref()), }) }, pk => { Err(Error::MalformedPacket( format!( "Key: Expected RSA public key, got {:?}", pk)).into()) }, } }, ECDH => crate::crypto::ecdh::encrypt(self.parts_as_public(), data), RSASign | DSA | ECDSA | EdDSA | Ed25519 | Ed448 => Err(Error::InvalidOperation( format!("{} is not an encryption algorithm", self.pk_algo()) ).into()), ElGamalEncrypt | ElGamalEncryptSign | X25519 | X448 | Private(_) | Unknown(_) => Err(Error::UnsupportedPublicKeyAlgorithm(self.pk_algo()).into()), } } /// Verifies the given signature. pub(crate) fn verify_backend(&self, sig: &mpi::Signature, hash_algo: HashAlgorithm, digest: &[u8]) -> Result<()> { use cng::asymmetric::{AsymmetricAlgorithm, AsymmetricAlgorithmId}; use cng::asymmetric::{AsymmetricKey, Public, Rsa}; use cng::asymmetric::ecc::NamedCurve; use cng::asymmetric::signature::{Verifier, SignaturePadding}; use cng::key_blob::RsaKeyPublicPayload; fn bad(e: impl ToString) -> anyhow::Error { Error::BadSignature(e.to_string()).into() } let ok = match (self.mpis(), sig) { (mpi::PublicKey::RSA { e, n }, mpi::Signature::RSA { s }) => { // CNG accepts only full-size signatures. Since for RSA it's a // big-endian number, just left-pad with zeroes as necessary. let s = pad(s.value(), n.value().len()).map_err(bad)?; // CNG supports RSA keys that are at least 512 bit long. // Since it just checks the MPI length rather than data itself, // just pad it with zeroes as necessary. let n = pad_at_least(n.value(), 512); let provider = AsymmetricAlgorithm::open(AsymmetricAlgorithmId::Rsa)?; let key = AsymmetricKey::<Rsa, Public>::import_from_parts( &provider, &RsaKeyPublicPayload { modulus: &n, pub_exp: e.value(), } )?; // As described in [Section 5.2.2 and 5.2.3 of RFC 9580], // to verify the signature, we need to encode the // signature data in a PKCS1-v1.5 packet. // // [Section 5.2.2 and 5.2.3 of RFC 9580]: // https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.2 let hash = hash_algo.try_into()?; let padding = SignaturePadding::pkcs1(hash); key.verify(digest, &s, Some(padding)).map(|_| true)? }, (mpi::PublicKey::DSA { y, p, q, g }, mpi::Signature::DSA { r, s }) => { use win_crypto_ng::key_blob::{DsaKeyPublicPayload, DsaKeyPublicBlob}; use win_crypto_ng::key_blob::{DsaKeyPublicV2Payload, DsaKeyPublicV2Blob}; use win_crypto_ng::asymmetric::{Dsa, DsaPublicBlob}; use win_crypto_ng::helpers::Blob; let y = y.value_padded(p.value().len()) .map_err(|e| Error::InvalidKey(e.to_string()))?; if y.len() > 3072 / 8 { return Err(Error::InvalidOperation( "DSA keys are supported up to 3072-bits".to_string()).into() ); } // CNG expects full-sized signatures let field_sz = q.value().len(); let mut signature = vec![0u8; 2 * field_sz]; // We need to zero-pad them at the front, because // the MPI encoding drops leading zero bytes. signature[..field_sz].copy_from_slice( &r.value_padded(field_sz).map_err(bad)?); signature[field_sz..].copy_from_slice( &s.value_padded(field_sz).map_err(bad)?); enum Version { V1, V2 } // 1024-bit DSA keys are handled differently let version = if y.len() <= 128 { Version::V1 } else { Version::V2 }; let blob: DsaPublicBlob = match version { Version::V1 => { let mut group = [0; 20]; if let Ok(v) = q.value_padded(group.len()) { group[..].copy_from_slice(&v); } else { return Err(Error::InvalidOperation( "DSA keys' group parameter exceeds 160 bits" .to_string()).into()); } DsaPublicBlob::V1(Blob::<DsaKeyPublicBlob>::clone_from_parts( &winapi::shared::bcrypt::BCRYPT_DSA_KEY_BLOB { dwMagic: winapi::shared::bcrypt::BCRYPT_DSA_PUBLIC_MAGIC, cbKey: y.len() as u32, Count: [0; 4], // unused Seed: [0; 20], // unused q: group, }, &DsaKeyPublicPayload { modulus: p.value(), generator: g.value(), public: &y, }, )) }, Version::V2 => { // https://github.com/dotnet/runtime/blob/67d74fca70d4670ad503e23dba9d6bc8a1b5909e/src/libraries/Common/src/System/Security/Cryptography/DSACng.ImportExport.cs#L276-L282 let hash = match q.value().len() { 20 => 0, 32 => 1, 64 => 2, _ => return Err(Error::InvalidOperation( "CNG accepts DSA q with length of either length of 20, 32 or 64".into()) .into()), }; // We don't use counter/seed values so set them to 0. // CNG pre-checks that the seed is at least |Q| long, // so we can't use an empty buffer here. let (count, seed) = ([0x0; 4], vec![0x0; q.value().len()]); DsaPublicBlob::V2(Blob::<DsaKeyPublicV2Blob>::clone_from_parts( &winapi::shared::bcrypt::BCRYPT_DSA_KEY_BLOB_V2 { dwMagic: winapi::shared::bcrypt::BCRYPT_DSA_PUBLIC_MAGIC_V2, Count: count, // Size of the prime number q . // Currently, if the key is less than 128 // bits, q is 20 bytes long. // If the key exceeds 256 bits, q is 32 bytes long. cbGroupSize: q.value().len() as u32, cbKey: y.len() as u32, // https://csrc.nist.gov/csrc/media/publications/fips/186/3/archive/2009-06-25/documents/fips_186-3.pdf // Length of the seed used to generate the // prime number q. cbSeedLength: seed.len() as u32, hashAlgorithm: hash, standardVersion: 1, // FIPS 186-3 }, &DsaKeyPublicV2Payload { seed: &seed, group: q.value(), modulus: p.value(), generator: g.value(), public: &y, }, )) }, }; use win_crypto_ng::asymmetric::Import; let provider = AsymmetricAlgorithm::open(AsymmetricAlgorithmId::Dsa)?; let key = AsymmetricKey::<Dsa, Public>::import( Dsa, &provider, blob )?; // CNG accepts only hash and Q of equal length. Either trim the // digest or pad it with zeroes (since it's treated as a // big-endian number). // See https://github.com/dotnet/runtime/blob/67d74fca70d4670ad503e23dba9d6bc8a1b5909e/src/libraries/Common/src/System/Security/Cryptography/DSACng.SignVerify.cs#L148. let digest = pad_truncating(&digest, q.value().len()); assert_eq!(q.value().len(), digest.len()); key.verify(&digest, &signature, None).map(|_| true)? }, (mpi::PublicKey::ECDSA { curve, q }, mpi::Signature::ECDSA { s, r }) => { let (x, y) = q.decode_point(curve)?; // CNG expects full-sized signatures let field_sz = x.len(); let mut signature = vec![0u8; 2 * field_sz]; // We need to zero-pad them at the front, because // the MPI encoding drops leading zero bytes. signature[..field_sz].copy_from_slice( &r.value_padded(field_sz).map_err(bad)?); signature[field_sz..].copy_from_slice( &s.value_padded(field_sz).map_err(bad)?); use cng::key_blob::EccKeyPublicPayload; use cng::asymmetric::{ecc::{NistP256, NistP384, NistP521}, Ecdsa}; // TODO: Improve CNG public API match curve { Curve::NistP256 => { let provider = AsymmetricAlgorithm::open( AsymmetricAlgorithmId::Ecdsa(NamedCurve::NistP256) )?; let key = AsymmetricKey::<Ecdsa<NistP256>, Public>::import_from_parts( &provider, &EccKeyPublicPayload { x, y } )?; key.verify(digest, &signature, None).map(|_| true)? }, Curve::NistP384 => { let provider = AsymmetricAlgorithm::open( AsymmetricAlgorithmId::Ecdsa(NamedCurve::NistP384) )?; let key = AsymmetricKey::<Ecdsa<NistP384>, Public>::import_from_parts( &provider, &EccKeyPublicPayload { x, y } )?; key.verify(digest, &signature, None).map(|_| true)? }, Curve::NistP521 => { let provider = AsymmetricAlgorithm::open( AsymmetricAlgorithmId::Ecdsa(NamedCurve::NistP521) )?; let key = AsymmetricKey::<Ecdsa<NistP521>, Public>::import_from_parts( &provider, &EccKeyPublicPayload { x, y } )?; key.verify(digest, &signature, None).map(|_| true)? }, _ => return Err( Error::UnsupportedEllipticCurve(curve.clone()).into()), } }, _ => return Err(Error::MalformedPacket(format!( "unsupported combination of key {} and signature {:?}.", self.pk_algo(), sig)).into()), }; if ok { Ok(()) } else { Err(Error::ManipulatedMessage.into()) } } } impl<R> Key4<SecretParts, R> where R: key::KeyRole, { /// Creates a new OpenPGP public key packet for an existing RSA key. /// /// The RSA key will use public exponent `e` and modulo `n`. The key will /// have its creation date set to `ctime` or the current time if `None` /// is given. pub fn import_secret_rsa<T>(d: &[u8], p: &[u8], q: &[u8], ctime: T) -> Result<Self> where T: Into<Option<SystemTime>>, { // RFC 4880: `p < q` let (p, q) = crate::crypto::rsa_sort_raw_pq(p, q); // CNG can't compute the public key from the private one, so do it ourselves let big_p = BigUint::from_bytes_be(p); let big_q = BigUint::from_bytes_be(q); let n = big_p.clone() * big_q.clone(); let big_d = BigUint::from_bytes_be(d); let big_phi = (big_p.clone() - 1u32) * (big_q.clone() - 1u32); let e = big_d.mod_inverse(big_phi) // e ≡ d⁻¹ (mod 𝜙) .and_then(|x: BigInt| x.to_biguint()) .ok_or_else(|| Error::MalformedMPI("RSA: `d` and `(p-1)(q-1)` aren't coprime".into()))?; let u: BigUint = big_p.mod_inverse(big_q) // RFC 4880: u ≡ p⁻¹ (mod q) .and_then(|x: BigInt| x.to_biguint()) .ok_or_else(|| Error::MalformedMPI("RSA: `p` and `q` aren't coprime".into()))?; Self::with_secret( ctime.into().unwrap_or_else(crate::now), PublicKeyAlgorithm::RSAEncryptSign, mpi::PublicKey::RSA { e: mpi::MPI::new(&e.to_bytes_be()), n: mpi::MPI::new(&n.to_bytes_be()), }, mpi::SecretKeyMaterial::RSA { d: d.into(), p: p.into(), q: q.into(), u: u.to_bytes_be().into(), }.into() ) } /// Generates a new RSA key with a public modulus of size `bits`. pub fn generate_rsa(bits: usize) -> Result<Self> { use win_crypto_ng::asymmetric::{AsymmetricKey, Rsa}; let blob = AsymmetricKey::builder(Rsa) .key_bits(bits as u32) .build()? .export_full()?; let public = mpi::PublicKey::RSA { e: mpi::MPI::new(blob.pub_exp()).into(), n: mpi::MPI::new(blob.modulus()).into(), }; let p = mpi::ProtectedMPI::from(blob.prime1()); let q = mpi::ProtectedMPI::from(blob.prime2()); // RSA prime generation in CNG returns them in arbitrary order but // RFC 4880 expects `p < q` let (p, q) = rsa_sort_pq(p, q); // CNG `coeff` is `prime1`^-1 mod `prime2` so adjust for possible p,q reorder let big_p = BigUint::from_bytes_be(p.value()); let big_q = BigUint::from_bytes_be(q.value()); let u = big_p.mod_inverse(big_q) // RFC 4880: u ≡ p⁻¹ (mod q) .and_then(|x: BigInt| x.to_biguint()) .expect("CNG to generate a valid RSA key (where p, q are coprime)"); let private = mpi::SecretKeyMaterial::RSA { p: p, q: q, d: blob.priv_exp().into(), u: u.to_bytes_be().into(), }; Self::with_secret( crate::now(), PublicKeyAlgorithm::RSAEncryptSign, public, private.into() ) } /// Generates a new ECC key over `curve`. /// /// If `for_signing` is false a ECDH key, if it's true either a /// EdDSA or ECDSA key is generated. Giving `for_signing == true` /// and `curve == Cv25519` will produce an error. Similar for /// `for_signing == false` and `curve == Ed25519`. /// signing/encryption pub(crate) fn generate_ecc_backend(for_signing: bool, curve: Curve) -> Result<(PublicKeyAlgorithm, mpi::PublicKey, mpi::SecretKeyMaterial)> { use cng::asymmetric::{ecc, Export}; use cng::asymmetric::{AsymmetricKey, AsymmetricAlgorithmId}; match (curve.clone(), for_signing) { (Curve::Ed25519, true) => unreachable!("handled in Key4::generate_ecc"), (Curve::Cv25519, false) => unreachable!("handled in Key4::generate_ecc"), (Curve::NistP256, ..) | (Curve::NistP384, ..) | (Curve::NistP521, ..) => { let cng_curve = match curve { Curve::NistP256 => ecc::NamedCurve::NistP256, Curve::NistP384 => ecc::NamedCurve::NistP384, Curve::NistP521 => ecc::NamedCurve::NistP521, _ => unreachable!() }; let ecc_algo = if for_signing { AsymmetricAlgorithmId::Ecdsa(cng_curve) } else { AsymmetricAlgorithmId::Ecdh(cng_curve) }; let blob = AsymmetricKey::builder(ecc_algo).build()?.export()?; let blob = match blob.try_into::<cng::key_blob::EccKeyPrivateBlob>() { Ok(blob) => blob, // Dynamic algorithm specified is either ECDSA or ECDH so // exported blob should be of appropriate type Err(..) => unreachable!() }; let field_sz = cng_curve.key_bits() as usize; let q = mpi::MPI::new_point(blob.x(), blob.y(), field_sz); let scalar = mpi::ProtectedMPI::from(blob.d()); if for_signing { Ok(( PublicKeyAlgorithm::ECDSA, mpi::PublicKey::ECDSA { curve, q }, mpi::SecretKeyMaterial::ECDSA { scalar }, )) } else { let hash = crate::crypto::ecdh::default_ecdh_kdf_hash(&curve); let sym = crate::crypto::ecdh::default_ecdh_kek_cipher(&curve); Ok(( PublicKeyAlgorithm::ECDH, mpi::PublicKey::ECDH { curve, q, hash, sym }, mpi::SecretKeyMaterial::ECDH { scalar }, )) } }, _ => Err(Error::UnsupportedEllipticCurve(curve).into()), } } } /// Rounds `n` up to the next multiple of `m`. fn round_up_to_multiple_of(n: usize, m: usize) -> usize { ((n + m - 1) / m) * m } /// Given the secret prime values `p` and `q`, returns the pair of /// primes so that the smaller one comes first. /// /// Section 5.5.3 of RFC4880 demands that `p < q`. This function can /// be used to order `p` and `q` accordingly. /// /// Note: even though this function seems trivial, we introduce it as /// explicit abstraction. The reason is that the function's /// expression also "works" (as in it compiles) for byte slices, but /// does the wrong thing, see [`crate::crypto::rsa_sort_raw_pq`]. fn rsa_sort_pq(p: mpi::ProtectedMPI, q: mpi::ProtectedMPI) -> (mpi::ProtectedMPI, mpi::ProtectedMPI) { if p < q { (p, q) } else { (q, p) } } #[cfg(test)] mod tests { quickcheck! { fn round_up_to_multiple_of(n: usize, m: usize) -> bool { if n.checked_add(m).is_none() { // cannot round up because it overflows return true; } if m == 0 { // avoid dividing by zero return true; } let rounded_up = super::round_up_to_multiple_of(n, m); assert!(rounded_up >= n); assert!(rounded_up - n < m); assert_eq!(rounded_up % m, 0); true } } } ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/src/crypto/backend/cng/ecdh.rs������������������������������������������������0000644�0000000�0000000�00000023177�10461020230�0020467�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������//! Elliptic Curve Diffie-Hellman. use crate::crypto::mem::Protected; use crate::crypto::mpi::{self, Ciphertext, SecretKeyMaterial, MPI}; use crate::crypto::SessionKey; use crate::packet::{key, Key}; use crate::types::Curve; use crate::{Error, Result}; use crate::crypto::ecdh::{encrypt_wrap, decrypt_unwrap}; use win_crypto_ng as cng; use cng::asymmetric::{Ecdh, AsymmetricKey, Export}; use cng::asymmetric::{Public, Private, AsymmetricAlgorithm, AsymmetricAlgorithmId}; use cng::asymmetric::ecc::{NamedCurve, NistP256, NistP384, NistP521}; use cng::key_blob::{EccKeyPublicPayload, EccKeyPrivatePayload}; /// Wraps a session key using Elliptic Curve Diffie-Hellman. #[allow(non_snake_case)] pub fn encrypt<R>( recipient: &Key<key::PublicParts, R>, session_key: &SessionKey, ) -> Result<mpi::Ciphertext> where R: key::KeyRole, { let (curve, q) = match recipient.mpis() { mpi::PublicKey::ECDH { curve, q, .. } => (curve, q), _ => return Err(Error::InvalidArgument("Expected an ECDHPublicKey".into()).into()), }; match curve { Curve::Cv25519 => return Err(Error::InvalidArgument("implemented elsewhere".into()).into()), Curve::NistP256 | Curve::NistP384 | Curve::NistP521 => { let (Rx, Ry) = q.decode_point(curve)?; let (VB, S) = match curve { Curve::NistP256 => { // Obtain the authenticated recipient public key R and // generate an ephemeral private key v. let provider = AsymmetricAlgorithm::open(AsymmetricAlgorithmId::Ecdh(NamedCurve::NistP256))?; let R = AsymmetricKey::<Ecdh<NistP256>, Public>::import_from_parts( &provider, &EccKeyPublicPayload { x: Rx, y: Ry }, )?; let v = AsymmetricKey::builder(Ecdh(NistP256)).build().unwrap(); let VB = v.export()?; let VB = MPI::new_point(&VB.x(), &VB.y(), 256); // Compute the shared point S = vR let secret = cng::asymmetric::agreement::secret_agreement(&v, &R)?; // Get the X coordinate let mut S = Protected::from(secret.derive_raw()?); // Returned secret is little-endian, flip it to big-endian S.reverse(); assert_eq!(S.len(), 32); (VB, S) } Curve::NistP384 => { // Obtain the authenticated recipient public key R and // generate an ephemeral private key v. let provider = AsymmetricAlgorithm::open(AsymmetricAlgorithmId::Ecdh(NamedCurve::NistP384))?; let R = AsymmetricKey::<Ecdh<NistP384>, Public>::import_from_parts( &provider, &EccKeyPublicPayload { x: Rx, y: Ry }, )?; let v = AsymmetricKey::builder(Ecdh(NistP384)).build().unwrap(); let VB = v.export()?; let VB = MPI::new_point(&VB.x(), &VB.y(), 384); // Compute the shared point S = vR let secret = cng::asymmetric::agreement::secret_agreement(&v, &R)?; // Get the X coordinate let mut S = Protected::from(secret.derive_raw()?); // Returned secret is little-endian, flip it to big-endian S.reverse(); assert_eq!(S.len(), 48); (VB, S) } Curve::NistP521 => { // Obtain the authenticated recipient public key R and // generate an ephemeral private key v. let provider = AsymmetricAlgorithm::open(AsymmetricAlgorithmId::Ecdh(NamedCurve::NistP521))?; let R = AsymmetricKey::<Ecdh<NistP521>, Public>::import_from_parts( &provider, &EccKeyPublicPayload { x: Rx, y: Ry }, )?; let v = AsymmetricKey::builder(Ecdh(NistP521)).build().unwrap(); let VB = v.export()?; let VB = MPI::new_point(&VB.x(), &VB.y(), 521); // Compute the shared point S = vR let secret = cng::asymmetric::agreement::secret_agreement(&v, &R)?; // Get the X coordinate let mut S = Protected::from(secret.derive_raw()?); // Returned secret is little-endian, flip it to big-endian S.reverse(); assert_eq!(S.len(), 66); (VB, S) } _ => unreachable!(), }; encrypt_wrap(recipient, session_key, VB, &S) } // Not implemented in CNG. Curve::BrainpoolP256 | Curve::BrainpoolP384 | Curve::BrainpoolP512 => Err(Error::UnsupportedEllipticCurve(curve.clone()).into()), // N/A Curve::Unknown(_) | Curve::Ed25519 => Err(Error::UnsupportedEllipticCurve(curve.clone()).into()), } } /// Unwraps a session key using Elliptic Curve Diffie-Hellman. #[allow(non_snake_case)] pub fn decrypt<R>( recipient: &Key<key::PublicParts, R>, recipient_sec: &SecretKeyMaterial, ciphertext: &Ciphertext, plaintext_len: Option<usize>, ) -> Result<SessionKey> where R: key::KeyRole, { let (curve, scalar, e) = match (recipient.mpis(), recipient_sec, ciphertext) { (mpi::PublicKey::ECDH { ref curve, ..}, SecretKeyMaterial::ECDH { ref scalar, }, Ciphertext::ECDH { ref e, .. }) => (curve, scalar, e), _ => return Err(Error::InvalidArgument("Expected an ECDHPublicKey".into()).into()), }; let S: Protected = match curve { Curve::Cv25519 => return Err(Error::InvalidArgument("implemented elsewhere".into()).into()), Curve::NistP256 | Curve::NistP384 | Curve::NistP521 => { // Get the public part V of the ephemeral key and // compute the shared point S = rV = rvG, where (r, R) // is the recipient's key pair. let (Vx, Vy) = e.decode_point(curve)?; match curve { Curve::NistP256 => { let provider = AsymmetricAlgorithm::open(AsymmetricAlgorithmId::Ecdh(NamedCurve::NistP256))?; let V = AsymmetricKey::<Ecdh<NistP256>, Public>::import_from_parts( &provider, &EccKeyPublicPayload { x: Vx, y: Vy }, )?; let d = scalar.value_padded(32); let r = AsymmetricKey::<Ecdh<NistP256>, Private>::import_from_parts( &provider, &EccKeyPrivatePayload { x: &[0; 32], y: &[0; 32], d: &d, } )?; let secret = cng::asymmetric::agreement::secret_agreement(&r, &V)?; // Returned secret is little-endian, flip it to big-endian let mut secret = secret.derive_raw()?; secret.reverse(); secret.into() } Curve::NistP384 => { let provider = AsymmetricAlgorithm::open(AsymmetricAlgorithmId::Ecdh(NamedCurve::NistP384))?; let V = AsymmetricKey::<Ecdh<NistP384>, Public>::import_from_parts( &provider, &EccKeyPublicPayload { x: Vx, y: Vy }, )?; let d = scalar.value_padded(48); let r = AsymmetricKey::<Ecdh<NistP384>, Private>::import_from_parts( &provider, &EccKeyPrivatePayload { x: &[0; 48], y: &[0; 48], d: &d, } )?; let secret = cng::asymmetric::agreement::secret_agreement(&r, &V)?; // Returned secret is little-endian, flip it to big-endian let mut secret = secret.derive_raw()?; secret.reverse(); secret.into() } Curve::NistP521 => { let provider = AsymmetricAlgorithm::open(AsymmetricAlgorithmId::Ecdh(NamedCurve::NistP521))?; let V = AsymmetricKey::<Ecdh<NistP521>, Public>::import_from_parts( &provider, &EccKeyPublicPayload { x: Vx, y: Vy }, )?; let d = scalar.value_padded(66); let r = AsymmetricKey::<Ecdh<NistP521>, Private>::import_from_parts( &provider, &EccKeyPrivatePayload { x: &[0; 66], y: &[0; 66], d: &d, } )?; let secret = cng::asymmetric::agreement::secret_agreement(&r, &V)?; // Returned secret is little-endian, flip it to big-endian let mut secret = secret.derive_raw()?; secret.reverse(); secret.into() } _ => unreachable!(), } }, _ => { return Err(Error::UnsupportedEllipticCurve(curve.clone()).into()); } }; decrypt_unwrap(recipient.role_as_unspecified(), &S, ciphertext, plaintext_len) } �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/src/crypto/backend/cng/hash.rs������������������������������������������������0000644�0000000�0000000�00000011532�10461020230�0020477�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������use core::convert::TryFrom; use std::io; use std::sync::Mutex; use crate::crypto::hash::Digest; use crate::types::HashAlgorithm; use crate::{Error, Result}; use win_crypto_ng::hash as cng; struct Hash(Mutex<cng::Hash>); impl From<cng::Hash> for Hash { fn from(h: cng::Hash) -> Self { Hash(Mutex::new(h)) } } impl Clone for Hash { fn clone(&self) -> Self { self.0.lock().expect("Mutex not to be poisoned").clone().into() } } impl Digest for Hash { fn update(&mut self, data: &[u8]) { let _ = self.0.lock().expect("Mutex not to be poisoned").hash(data); } fn digest(&mut self, digest: &mut [u8]) -> Result<()> { // TODO: Replace with CNG reusable hash objects, supported from Windows 8 // This would allow us to not re-create the CNG hash object each time we // want to finish digest calculation let algorithm = self.0.lock().expect("Mutex not to be poisoned") .hash_algorithm() .expect("CNG hash object to know its algorithm"); let new = cng::HashAlgorithm::open(algorithm) .expect("CNG to open a new correct hash provider") .new_hash() .expect("Failed to create a new CNG hash object"); let old = std::mem::replace( self.0.get_mut().expect("Mutex not to be poisoned"), new); let buffer = old.finish() .expect("CNG to not fail internally"); let l = buffer.len().min(digest.len()); digest[..l].copy_from_slice(&buffer.as_slice()[..l]); Ok(()) } } impl io::Write for Hash { fn write(&mut self, buf: &[u8]) -> io::Result<usize> { self.update(buf); Ok(buf.len()) } fn flush(&mut self) -> io::Result<()> { // Do nothing. Ok(()) } } impl TryFrom<HashAlgorithm> for cng::HashAlgorithmId { type Error = Error; fn try_from(value: HashAlgorithm) -> std::result::Result<Self, Self::Error> { Ok(match value { HashAlgorithm::SHA1 => cng::HashAlgorithmId::Sha1, HashAlgorithm::SHA256 => cng::HashAlgorithmId::Sha256, HashAlgorithm::SHA384 => cng::HashAlgorithmId::Sha384, HashAlgorithm::SHA512 => cng::HashAlgorithmId::Sha512, HashAlgorithm::MD5 => cng::HashAlgorithmId::Md5, // SHA3 support is on the horizon, see // https://blogs.windows.com/windows-insider/2023/03/23/announcing-windows-11-insider-preview-build-25324/ HashAlgorithm::SHA3_256 | HashAlgorithm::SHA3_512 => return Err(Error::UnsupportedHashAlgorithm(value)), HashAlgorithm::SHA224 | HashAlgorithm::RipeMD | HashAlgorithm::Private(_) | HashAlgorithm::Unknown(_) => return Err(Error::UnsupportedHashAlgorithm(value)), }) } } impl TryFrom<cng::HashAlgorithmId> for HashAlgorithm { type Error = Error; fn try_from(value: cng::HashAlgorithmId) -> std::result::Result<Self, Self::Error> { Ok(match value { cng::HashAlgorithmId::Sha1 => HashAlgorithm::SHA1, cng::HashAlgorithmId::Sha256 => HashAlgorithm::SHA256, cng::HashAlgorithmId::Sha384 => HashAlgorithm::SHA384, cng::HashAlgorithmId::Sha512 => HashAlgorithm::SHA512, cng::HashAlgorithmId::Md5 => HashAlgorithm::MD5, algo => Err(Error::InvalidArgument( format!("Algorithm {:?} not representable", algo)))?, }) } } impl HashAlgorithm { /// Whether Sequoia supports this algorithm. pub fn is_supported(self) -> bool { match self { HashAlgorithm::SHA1 => true, HashAlgorithm::SHA256 => true, HashAlgorithm::SHA384 => true, HashAlgorithm::SHA512 => true, HashAlgorithm::MD5 => true, // SHA3 support is on the horizon, see // https://blogs.windows.com/windows-insider/2023/03/23/announcing-windows-11-insider-preview-build-25324/ HashAlgorithm::SHA3_256 | HashAlgorithm::SHA3_512 => false, HashAlgorithm::SHA224 | HashAlgorithm::RipeMD | HashAlgorithm::Private(_) | HashAlgorithm::Unknown(_) => false, } } /// Creates a new hash context for this algorithm. /// /// # Errors /// /// Fails with `Error::UnsupportedHashAlgorithm` if the selected crypto /// backend does not support this algorithm. See /// [`HashAlgorithm::is_supported`]. /// /// [`HashAlgorithm::is_supported`]: Hash::is_supported() pub(crate) fn new_hasher(self) -> Result<Box<dyn Digest>> { let algo = cng::HashAlgorithmId::try_from(self)?; let algo = cng::HashAlgorithm::open(algo)?; Ok(Box::new(Hash::from(algo.new_hash().expect( "CNG to always create a hasher object for valid algo", )))) } } ����������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/src/crypto/backend/cng/kdf.rs�������������������������������������������������0000644�0000000�0000000�00000001373�10461020230�0020322�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������use hkdf::Hkdf; use sha2::{Sha256, Sha512}; use crate::{ Result, crypto::{ SessionKey, backend::interface::Kdf, }, }; impl Kdf for super::Backend { fn hkdf_sha256(ikm: &SessionKey, salt: Option<&[u8]>, info: &[u8], okm: &mut SessionKey) -> Result<()> { Ok(Hkdf::<Sha256>::new(salt, &ikm).expand(info, okm) .map_err(|e| crate::Error::InvalidOperation(e.to_string()))?) } fn hkdf_sha512(ikm: &SessionKey, salt: Option<&[u8]>, info: &[u8], okm: &mut SessionKey) -> Result<()> { Ok(Hkdf::<Sha512>::new(salt, &ikm).expand(info, okm) .map_err(|e| crate::Error::InvalidOperation(e.to_string()))?) } } ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/src/crypto/backend/cng/symmetric.rs�������������������������������������������0000644�0000000�0000000�00000012313�10461020230�0021566�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������use std::convert::TryFrom; use std::sync::Mutex; use win_crypto_ng::symmetric as cng; use crate::crypto::mem::Protected; use crate::crypto::symmetric::Mode; use crate::{Error, Result}; use crate::types::SymmetricAlgorithm; struct KeyWrapper { key: Mutex<cng::SymmetricAlgorithmKey>, iv: Option<Protected>, } impl KeyWrapper { fn new(key: cng::SymmetricAlgorithmKey, iv: Option<Vec<u8>>) -> KeyWrapper { KeyWrapper { key: Mutex::new(key), iv: iv.map(|iv| iv.into()), } } } impl Mode for KeyWrapper { fn block_size(&self) -> usize { self.key.lock().expect("Mutex not to be poisoned") .block_size().expect("CNG not to fail internally") } fn encrypt( &mut self, dst: &mut [u8], src: &[u8], ) -> Result<()> { let block_size = Mode::block_size(self); // If necessary, round up to the next block size and pad with zeroes // NOTE: In theory CFB doesn't need this but CNG always requires // passing full blocks. let mut _src = vec![]; let missing = (block_size - (src.len() % block_size)) % block_size; let src = if missing != 0 { _src = vec![0u8; src.len() + missing]; _src[..src.len()].copy_from_slice(src); &_src } else { src }; let len = std::cmp::min(src.len(), dst.len()); let buffer = cng::SymmetricAlgorithmKey::encrypt( &*self.key.lock().expect("Mutex not to be poisoned"), self.iv.as_deref_mut(), src, None)?; Ok(dst[..len].copy_from_slice(&buffer.as_slice()[..len])) } fn decrypt( &mut self, dst: &mut [u8], src: &[u8], ) -> Result<()> { let block_size = Mode::block_size(self); // If necessary, round up to the next block size and pad with zeroes // NOTE: In theory CFB doesn't need this but CNG always requires // passing full blocks. let mut _src = vec![]; let missing = (block_size - (src.len() % block_size)) % block_size; let src = if missing != 0 { _src = vec![0u8; src.len() + missing]; _src[..src.len()].copy_from_slice(src); &_src } else { src }; let len = std::cmp::min(src.len(), dst.len()); let buffer = cng::SymmetricAlgorithmKey::decrypt( &*self.key.lock().expect("Mutex not to be poisoned"), self.iv.as_deref_mut(), src, None)?; dst[..len].copy_from_slice(&buffer.as_slice()[..len]); Ok(()) } } #[derive(Debug, thiserror::Error)] #[error("Unsupported algorithm: {0}")] pub struct UnsupportedAlgorithm(SymmetricAlgorithm); assert_send_and_sync!(UnsupportedAlgorithm); impl From<UnsupportedAlgorithm> for Error { fn from(value: UnsupportedAlgorithm) -> Error { Error::UnsupportedSymmetricAlgorithm(value.0) } } impl TryFrom<SymmetricAlgorithm> for (cng::SymmetricAlgorithmId, usize) { type Error = UnsupportedAlgorithm; fn try_from(value: SymmetricAlgorithm) -> std::result::Result<Self, Self::Error> { #[allow(deprecated)] Ok(match value { SymmetricAlgorithm::TripleDES => (cng::SymmetricAlgorithmId::TripleDes, 168), SymmetricAlgorithm::AES128 => (cng::SymmetricAlgorithmId::Aes, 128), SymmetricAlgorithm::AES192 => (cng::SymmetricAlgorithmId::Aes, 192), SymmetricAlgorithm::AES256 => (cng::SymmetricAlgorithmId::Aes, 256), algo => Err(UnsupportedAlgorithm(algo))?, }) } } impl SymmetricAlgorithm { /// Returns whether this algorithm is supported by the crypto backend. pub(crate) fn is_supported_by_backend(&self) -> bool { use self::SymmetricAlgorithm::*; #[allow(deprecated)] match self { AES128 | AES192 | AES256 | TripleDES => true, _ => false, } } /// Creates a symmetric cipher context for encrypting in CFB mode. pub(crate) fn make_encrypt_cfb(self, key: &[u8], iv: Vec<u8>) -> Result<Box<dyn Mode>> { let (algo, _) = TryFrom::try_from(self)?; let algo = cng::SymmetricAlgorithm::open(algo, cng::ChainingMode::Cfb)?; let mut key = algo.new_key(key)?; // Use full-block CFB mode as expected everywhere else (by default it's // set to 8-bit CFB) key.set_msg_block_len(key.block_size()?)?; Ok(Box::new(KeyWrapper::new(key, Some(iv)))) } /// Creates a symmetric cipher context for decrypting in CFB mode. pub(crate) fn make_decrypt_cfb(self, key: &[u8], iv: Vec<u8>) -> Result<Box<dyn Mode>> { Self::make_encrypt_cfb(self, key, iv) } /// Creates a symmetric cipher context for encrypting in ECB mode. pub(crate) fn make_encrypt_ecb(self, key: &[u8]) -> Result<Box<dyn Mode>> { let (algo, _) = TryFrom::try_from(self)?; let algo = cng::SymmetricAlgorithm::open(algo, cng::ChainingMode::Ecb)?; let key = algo.new_key(key)?; Ok(Box::new(KeyWrapper::new(key, None))) } /// Creates a symmetric cipher context for decrypting in ECB mode. pub(crate) fn make_decrypt_ecb(self, key: &[u8]) -> Result<Box<dyn Mode>> { Self::make_encrypt_ecb(self, key) } } ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/src/crypto/backend/cng.rs�����������������������������������������������������0000644�0000000�0000000�00000004252�10461020230�0017555�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������//! Implementation of crypto primitives using the Windows CNG (Cryptographic API: Next Generation). use crate::types::*; use win_crypto_ng::random::RandomNumberGenerator; pub mod aead; pub mod asymmetric; pub mod ecdh; pub mod hash; pub mod kdf; pub mod symmetric; pub struct Backend(()); impl super::interface::Backend for Backend { fn backend() -> String { // XXX: can we include features and the version? "Windows CNG".to_string() } fn random(buf: &mut [u8]) -> crate::Result<()> { RandomNumberGenerator::system_preferred() .gen_random(buf)?; Ok(()) } } impl AEADAlgorithm { /// Returns the best AEAD mode supported by the backend. /// /// This SHOULD return OCB, which is the mandatory-to-implement /// algorithm and the most performing one, but fall back to any /// supported algorithm. pub(crate) const fn const_default() -> AEADAlgorithm { AEADAlgorithm::EAX } pub(crate) fn is_supported_by_backend(&self) -> bool { use self::AEADAlgorithm::*; match &self { EAX => true, OCB => false, GCM => true, Private(_) | Unknown(_) => false, } } #[cfg(test)] pub(crate) fn supports_symmetric_algo(&self, algo: &SymmetricAlgorithm) -> bool { match &self { AEADAlgorithm::EAX => match algo { SymmetricAlgorithm::AES128 | SymmetricAlgorithm::AES192 | SymmetricAlgorithm::AES256 => true, _ => false, }, AEADAlgorithm::OCB => match algo { SymmetricAlgorithm::AES128 | SymmetricAlgorithm::AES192 | SymmetricAlgorithm::AES256 => true, _ => false, }, AEADAlgorithm::GCM => match algo { SymmetricAlgorithm::AES128 | SymmetricAlgorithm::AES192 | SymmetricAlgorithm::AES256 => true, _ => false, }, _ => false, } } } ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/src/crypto/backend/fuzzing/aead.rs��������������������������������������������0000644�0000000�0000000�00000002023�10461020230�0021366�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������//! Implementation of AEAD using Nettle cryptographic library. use crate::Result; use crate::crypto::aead::{Aead, CipherOp}; use crate::seal; use crate::types::{AEADAlgorithm, SymmetricAlgorithm}; struct NullAEADMode {} const DIGEST_SIZE: usize = 16; impl seal::Sealed for NullAEADMode {} impl Aead for NullAEADMode { fn encrypt_seal(&mut self, dst: &mut [u8], src: &[u8]) -> Result<()> { let l = dst.len() - DIGEST_SIZE; dst[..l].copy_from_slice(src); dst[l..].iter_mut().for_each(|p| *p = 0x04); Ok(()) } fn decrypt_verify(&mut self, dst: &mut [u8], src: &[u8]) -> Result<()> { dst.copy_from_slice(&src[..src.len() - DIGEST_SIZE]); Ok(()) } fn digest_size(&self) -> usize { DIGEST_SIZE } } impl AEADAlgorithm { pub(crate) fn context( &self, sym_algo: SymmetricAlgorithm, key: &[u8], aad: &[u8], nonce: &[u8], _op: CipherOp, ) -> Result<Box<dyn Aead>> { Ok(Box::new(NullAEADMode {})) } } �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/src/crypto/backend/fuzzing/asymmetric.rs��������������������������������������0000644�0000000�0000000�00000012750�10461020230�0022661�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������use crate::{Error, Result}; use crate::packet::{key, Key}; use crate::crypto::asymmetric::KeyPair; use crate::crypto::backend::interface::Asymmetric; use crate::crypto::mem::Protected; use crate::crypto::mpi::{self, MPI, ProtectedMPI}; use crate::crypto::SessionKey; use crate::types::{Curve, HashAlgorithm, PublicKeyAlgorithm}; impl Asymmetric for super::Backend { fn supports_algo(_: PublicKeyAlgorithm) -> bool { true } fn supports_curve(_: &Curve) -> bool { true } fn x25519_generate_key() -> Result<(Protected, [u8; 32])> { Ok((vec![4; 32].into(), [4; 32])) } fn x25519_derive_public(_: &Protected) -> Result<[u8; 32]> { Ok([4; 32]) } fn x25519_shared_point(_: &Protected, _: &[u8; 32]) -> Result<Protected> { Ok(vec![4; 32].into()) } fn ed25519_generate_key() -> Result<(Protected, [u8; 32])> { Ok((vec![4; 32].into(), [4; 32])) } fn ed25519_derive_public(_: &Protected) -> Result<[u8; 32]> { Ok([4; 32]) } fn ed25519_sign(_: &Protected, _: &[u8; 32], _: &[u8]) -> Result<[u8; 64]> { Ok([4; 64]) } fn ed25519_verify(_: &[u8; 32], _: &[u8], _: &[u8; 64]) -> Result<bool> { Ok(true) } fn dsa_generate_key(p_bits: usize) -> Result<(MPI, MPI, MPI, MPI, ProtectedMPI)> { let four = MPI::new(&[4]); Ok((four.clone(), four.clone(), four.clone(), four.clone(), vec![4].into())) } fn elgamal_generate_key(p_bits: usize) -> Result<(MPI, MPI, MPI, ProtectedMPI)> { let four = MPI::new(&[4]); Ok((four.clone(), four.clone(), four.clone(), vec![4].into())) } } impl KeyPair { pub(crate) fn sign_backend(&self, _: &mpi::SecretKeyMaterial, _: HashAlgorithm, _: &[u8]) -> Result<mpi::Signature> { Err(Error::InvalidOperation("not implemented".into()).into()) } pub(crate) fn decrypt_backend(&self, _: &mpi::SecretKeyMaterial, ciphertext: &mpi::Ciphertext, _: Option<usize>) -> Result<SessionKey> { match ciphertext { mpi::Ciphertext::RSA { c } | mpi::Ciphertext::ElGamal { c, .. } => Ok(Vec::from(c.value()).into()), mpi::Ciphertext::ECDH { key, .. } => Ok(Vec::from(&key[..]).into()), _ => Err(Error::InvalidOperation("not implemented".into()).into()), } } } impl<P: key::KeyParts, R: key::KeyRole> Key<P, R> { /// Encrypts the given data with this key. pub(crate) fn encrypt_backend(&self, data: &SessionKey) -> Result<mpi::Ciphertext> { use crate::PublicKeyAlgorithm::*; #[allow(deprecated)] match self.pk_algo() { RSAEncryptSign | RSAEncrypt => Ok(mpi::Ciphertext::RSA { c: MPI::new(&data), }), ElGamalEncrypt | ElGamalEncryptSign => Ok(mpi::Ciphertext::ElGamal { e: MPI::new(&data), c: MPI::new(&data), }), ECDH => Ok(mpi::Ciphertext::ECDH { e: MPI::new(&data), key: Vec::from(&data[..]).into_boxed_slice(), }), _ => Err(Error::InvalidOperation("not implemented".into()).into()), } } /// Verifies the given signature. pub(crate) fn verify_backend(&self, _: &mpi::Signature, _: HashAlgorithm, _: &[u8]) -> Result<()> { let ok = true; // XXX maybe we also want to have bad signatures? if ok { Ok(()) } else { Err(Error::ManipulatedMessage.into()) } } } use std::time::SystemTime; use crate::packet::key::{Key4, SecretParts}; impl<R> Key4<SecretParts, R> where R: key::KeyRole, { /// Creates a new OpenPGP public key packet for an existing RSA key. /// /// The RSA key will use public exponent `e` and modulo `n`. The key will /// have its creation date set to `ctime` or the current time if `None` /// is given. pub fn import_secret_rsa<T>(d: &[u8], p: &[u8], q: &[u8], ctime: T) -> Result<Self> where T: Into<Option<SystemTime>> { Err(Error::InvalidOperation("not implemented".into()).into()) } /// Generates a new RSA key with a public modulos of size `bits`. pub fn generate_rsa(bits: usize) -> Result<Self> { Err(Error::InvalidOperation("not implemented".into()).into()) } /// Generates a new ECC key over `curve`. /// /// If `for_signing` is false a ECDH key, if it's true either a /// EdDSA or ECDSA key is generated. Giving `for_signing == true` and /// `curve == Cv25519` will produce an error. Likewise /// `for_signing == false` and `curve == Ed25519` will produce an error. pub(crate) fn generate_ecc_backend(for_signing: bool, curve: Curve) -> Result<(PublicKeyAlgorithm, mpi::PublicKey, mpi::SecretKeyMaterial)> { Err(Error::InvalidOperation("not implemented".into()).into()) } } ������������������������sequoia-openpgp-2.0.0/src/crypto/backend/fuzzing/ecdh.rs��������������������������������������������0000644�0000000�0000000�00000002065�10461020230�0021405�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������//! Elliptic Curve Diffie-Hellman. use crate::{Error, Result}; use crate::crypto::SessionKey; use crate::crypto::mpi::{MPI, Ciphertext, SecretKeyMaterial}; use crate::packet::{key, Key}; /// Wraps a session key using Elliptic Curve Diffie-Hellman. #[allow(dead_code)] pub fn encrypt<R>(recipient: &Key<key::PublicParts, R>, session_key: &SessionKey) -> Result<Ciphertext> where R: key::KeyRole { Ok(Ciphertext::ECDH { e: MPI::new(&session_key), key: Vec::from(&session_key[..]).into_boxed_slice(), }) } /// Unwraps a session key using Elliptic Curve Diffie-Hellman. #[allow(dead_code)] pub fn decrypt<R>(recipient: &Key<key::PublicParts, R>, recipient_sec: &SecretKeyMaterial, ciphertext: &Ciphertext, plaintext_len: Option<usize>) -> Result<SessionKey> where R: key::KeyRole { match ciphertext { Ciphertext::ECDH { key, .. } => Ok(Vec::from(&key[..]).into()), _ => Err(Error::InvalidArgument("not a ecdh ciphertext".into()).into()), } } ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/src/crypto/backend/fuzzing/hash.rs��������������������������������������������0000644�0000000�0000000�00000002174�10461020230�0021426�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������use std::io; use crate::crypto::hash::Digest; use crate::Result; use crate::types::{HashAlgorithm}; #[derive(Clone)] struct NullHasher(HashAlgorithm); impl io::Write for NullHasher { fn write(&mut self, buf: &[u8]) -> io::Result<usize> { Ok(buf.len()) } fn flush(&mut self) -> io::Result<()> { Ok(()) } } impl Digest for NullHasher { fn update(&mut self, data: &[u8]) { // Nop. } fn digest(&mut self, digest: &mut [u8]) -> Result<()> { digest.iter_mut().enumerate().for_each(|(i, b)| *b = i as u8); Ok(()) } } impl HashAlgorithm { /// Whether Sequoia supports this algorithm. pub fn is_supported(self) -> bool { true } /// Creates a new hash context for this algorithm. /// /// # Errors /// /// Fails with `Error::UnsupportedHashAlgorithm` if Sequoia does /// not support this algorithm. See /// [`HashAlgorithm::is_supported`]. /// /// [`HashAlgorithm::is_supported`]: HashAlgorithm::is_supported() pub(crate) fn new_hasher(self) -> Result<Box<dyn Digest>> { Ok(Box::new(NullHasher(self))) } } ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/src/crypto/backend/fuzzing/kdf.rs���������������������������������������������0000644�0000000�0000000�00000001212�10461020230�0021237�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������use crate::{ Result, crypto::{ SessionKey, backend::interface::Kdf, }, }; impl Kdf for super::Backend { fn hkdf_sha256(ikm: &SessionKey, salt: Option<&[u8]>, info: &[u8], okm: &mut SessionKey) -> Result<()> { assert!(okm.len() <= 255 * 32); okm.iter_mut().for_each(|p| *p = 4); Ok(()) } fn hkdf_sha512(ikm: &SessionKey, salt: Option<&[u8]>, info: &[u8], okm: &mut SessionKey) -> Result<()> { assert!(okm.len() <= 255 * 32); okm.iter_mut().for_each(|p| *p = 8); Ok(()) } } ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/src/crypto/backend/fuzzing/symmetric.rs���������������������������������������0000644�0000000�0000000�00000004225�10461020230�0022516�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������use crate::crypto::symmetric::Mode; use crate::Result; use crate::types::SymmetricAlgorithm; struct NullCipher(usize); impl Mode for NullCipher { fn block_size(&self) -> usize { self.0 } fn encrypt( &mut self, dst: &mut [u8], src: &[u8], ) -> Result<()> { dst.copy_from_slice(src); Ok(()) } fn decrypt( &mut self, dst: &mut [u8], src: &[u8], ) -> Result<()> { dst.copy_from_slice(src); Ok(()) } } impl SymmetricAlgorithm { /// Returns whether this algorithm is supported by the crypto backend. /// /// All backends support all the AES variants. /// /// # Examples /// /// ```rust /// use sequoia_openpgp as openpgp; /// use openpgp::types::SymmetricAlgorithm; /// /// assert!(SymmetricAlgorithm::AES256.is_supported()); /// assert!(SymmetricAlgorithm::TripleDES.is_supported()); /// /// assert!(!SymmetricAlgorithm::IDEA.is_supported()); /// assert!(!SymmetricAlgorithm::Unencrypted.is_supported()); /// assert!(!SymmetricAlgorithm::Private(101).is_supported()); /// ``` pub(crate) fn is_supported_by_backend(&self) -> bool { true } /// Creates a Nettle context for encrypting in CFB mode. pub(crate) fn make_encrypt_cfb(self, key: &[u8], iv: Vec<u8>) -> Result<Box<dyn Mode>> { Ok(Box::new(NullCipher(self.block_size().unwrap_or(16)))) } /// Creates a Nettle context for decrypting in CFB mode. pub(crate) fn make_decrypt_cfb(self, key: &[u8], iv: Vec<u8>) -> Result<Box<dyn Mode>> { Ok(Box::new(NullCipher(self.block_size().unwrap_or(16)))) } /// Creates a Nettle context for encrypting in ECB mode. pub(crate) fn make_encrypt_ecb(self, key: &[u8]) -> Result<Box<dyn Mode>> { Ok(Box::new(NullCipher(self.block_size().unwrap_or(16)))) } /// Creates a Nettle context for decrypting in ECB mode. pub(crate) fn make_decrypt_ecb(self, key: &[u8]) -> Result<Box<dyn Mode>> { Ok(Box::new(NullCipher(self.block_size().unwrap_or(16)))) } } ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/src/crypto/backend/fuzzing.rs�������������������������������������������������0000644�0000000�0000000�00000002274�10461020230�0020504�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������//! Implementation of Sequoia crypto API using a fuzzing-friendly null //! backend. use crate::types::*; #[allow(unused_variables)] pub mod aead; #[allow(unused_variables)] pub mod asymmetric; #[allow(unused_variables)] pub mod ecdh; #[allow(unused_variables)] pub mod hash; #[allow(unused_variables)] pub mod kdf; #[allow(unused_variables)] pub mod symmetric; pub struct Backend(()); impl super::interface::Backend for Backend { fn backend() -> String { "Fuzzing".to_string() } fn random(buf: &mut [u8]) -> crate::Result<()> { buf.iter_mut().for_each(|b| *b = 4); Ok(()) } } impl AEADAlgorithm { /// Returns the best AEAD mode supported by the backend. /// /// This SHOULD return OCB, which is the mandatory-to-implement /// algorithm and the most performing one, but fall back to any /// supported algorithm. pub(crate) const fn const_default() -> AEADAlgorithm { AEADAlgorithm::OCB } pub(crate) fn is_supported_by_backend(&self) -> bool { true } #[cfg(test)] pub(crate) fn supports_symmetric_algo(&self, _: &SymmetricAlgorithm) -> bool { true } } ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/src/crypto/backend/interface.rs�����������������������������������������������0000644�0000000�0000000�00000017164�10461020230�0020754�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������//! The crypto-backend abstraction. use crate::{ Error, Result, crypto::{ SessionKey, mem::Protected, mpi::{MPI, ProtectedMPI}, }, types::{Curve, PublicKeyAlgorithm}, }; /// Abstracts over the cryptographic backends. pub trait Backend: Asymmetric + Kdf { /// Returns a short, human-readable description of the backend. /// /// This starts with the name of the backend, possibly a version, /// and any optional features that are available. This is meant /// for inclusion in version strings to improve bug reports. fn backend() -> String; /// Fills the given buffer with random data. /// /// Fills the given buffer with random data produced by a /// cryptographically secure pseudorandom number generator /// (CSPRNG). The output may be used as session keys or to derive /// long-term cryptographic keys from. fn random(buf: &mut [u8]) -> Result<()>; } /// Public-key cryptography interface. pub trait Asymmetric { /// Returns whether the given public key cryptography algorithm is /// supported by this backend. /// /// Note: when implementing this function, match exhaustively on /// `algo`, do not use a catch-all. This way, when new algorithms /// are introduced, we will see where we may need to add support. fn supports_algo(algo: PublicKeyAlgorithm) -> bool; /// Returns whether the given elliptic curve is supported by this /// backend. /// /// Note: when implementing this function, match exhaustively on /// `curve`, do not use a catch-all. This way, when new algorithms /// are introduced, we will see where we may need to add support. fn supports_curve(curve: &Curve) -> bool; /// Generates an X25519 key pair. /// /// Returns a tuple containing the secret and public key. fn x25519_generate_key() -> Result<(Protected, [u8; 32])>; /// Clamp the X25519 secret key scalar. /// /// X25519 does the clamping implicitly, but OpenPGP's ECDH over /// Curve25519 requires the secret to be clamped. To increase /// compatibility with OpenPGP implementations that do not /// implicitly clamp the secrets before use, we do that before we /// store the secrets in OpenPGP data structures. /// /// Note: like every function in this trait, this function expects /// `secret` to be in native byte order. fn x25519_clamp_secret(secret: &mut Protected) { secret[0] &= 0b1111_1000; secret[31] &= !0b1000_0000; secret[31] |= 0b0100_0000; } /// Computes the public key for a given secret key. fn x25519_derive_public(secret: &Protected) -> Result<[u8; 32]>; /// Computes the shared point. fn x25519_shared_point(secret: &Protected, public: &[u8; 32]) -> Result<Protected>; /// Generates an X448 key pair. /// /// Returns a tuple containing the secret and public key. fn x448_generate_key() -> Result<(Protected, [u8; 56])> { Err(Error::UnsupportedPublicKeyAlgorithm(PublicKeyAlgorithm::X448).into()) } /// Computes the public key for a given secret key. fn x448_derive_public(_secret: &Protected) -> Result<[u8; 56]> { Err(Error::UnsupportedPublicKeyAlgorithm(PublicKeyAlgorithm::X448).into()) } /// Computes the shared point. fn x448_shared_point(_secret: &Protected, _public: &[u8; 56]) -> Result<Protected> { Err(Error::UnsupportedPublicKeyAlgorithm(PublicKeyAlgorithm::X448).into()) } /// Generates an Ed25519 key pair. /// /// Returns a tuple containing the secret and public key. fn ed25519_generate_key() -> Result<(Protected, [u8; 32])>; /// Computes the public key for a given secret key. fn ed25519_derive_public(secret: &Protected) -> Result<[u8; 32]>; /// Creates an Ed25519 signature. fn ed25519_sign(secret: &Protected, public: &[u8; 32], digest: &[u8]) -> Result<[u8; 64]>; /// Verifies an Ed25519 signature. fn ed25519_verify(public: &[u8; 32], digest: &[u8], signature: &[u8; 64]) -> Result<bool>; /// Generates an Ed448 key pair. /// /// Returns a tuple containing the secret and public key. fn ed448_generate_key() -> Result<(Protected, [u8; 57])> { Err(Error::UnsupportedPublicKeyAlgorithm(PublicKeyAlgorithm::Ed448).into()) } /// Computes the public key for a given secret key. fn ed448_derive_public(_secret: &Protected) -> Result<[u8; 57]> { Err(Error::UnsupportedPublicKeyAlgorithm(PublicKeyAlgorithm::Ed448).into()) } /// Creates an Ed448 signature. fn ed448_sign(_secret: &Protected, _public: &[u8; 57], _digest: &[u8]) -> Result<[u8; 114]> { Err(Error::UnsupportedPublicKeyAlgorithm(PublicKeyAlgorithm::Ed448).into()) } /// Verifies an Ed448 signature. fn ed448_verify(_public: &[u8; 57], _digest: &[u8], _signature: &[u8; 114]) -> Result<bool> { Err(Error::UnsupportedPublicKeyAlgorithm(PublicKeyAlgorithm::Ed448).into()) } /// Generates a DSA key pair. /// /// `p_bits` denotes the desired size of the parameter `p`. /// Returns a tuple containing the parameters `p`, `q`, `g`, the /// public key `y`, and the secret key `x`. fn dsa_generate_key(p_bits: usize) -> Result<(MPI, MPI, MPI, MPI, ProtectedMPI)>; /// Generates an ElGamal key pair. /// /// `p_bits` denotes the desired size of the parameter `p`. /// Returns a tuple containing the parameters `p`, `g`, the public /// key `y`, and the secret key `x`. fn elgamal_generate_key(p_bits: usize) -> Result<(MPI, MPI, MPI, ProtectedMPI)> { let _ = p_bits; #[allow(deprecated)] Err(Error::UnsupportedPublicKeyAlgorithm( PublicKeyAlgorithm::ElGamalEncrypt).into()) } } #[cfg(test)] mod tests { use crate::crypto::backend::{Backend, interface::Asymmetric}; #[test] pub fn ed25519_generate_key_private_and_public_not_equal() { let (secret, public) = Backend::ed25519_generate_key().unwrap(); assert_ne!(secret.as_ref(), public); } } /// Key-Derivation-Functions. pub trait Kdf { /// HKDF instantiated with SHA256. /// /// Used to derive message keys from session keys, and key /// encapsulating keys from S2K mechanisms. In both cases, using /// a KDF that includes algorithm information in the given `info` /// provides key space separation between cipher algorithms and /// modes. /// /// `salt`, if given, SHOULD be 32 bytes of salt matching the /// digest size of the hash function. If it is not given, 32 /// zeros are used instead. /// /// `okm` must not be larger than 255 * 32 (the size of the hash /// digest). fn hkdf_sha256(ikm: &SessionKey, salt: Option<&[u8]>, info: &[u8], okm: &mut SessionKey) -> Result<()>; /// HKDF instantiated with SHA512. /// /// Used to derive message keys from session keys, and key /// encapsulating keys from S2K mechanisms. In both cases, using /// a KDF that includes algorithm information in the given `info` /// provides key space separation between cipher algorithms and /// modes. /// /// `salt`, if given, SHOULD be 64 bytes of salt matching the /// digest size of the hash function. If it is not given, 64 /// zeros are used instead. /// /// `okm` must not be larger than 255 * 64 (the size of the hash /// digest). fn hkdf_sha512(ikm: &SessionKey, salt: Option<&[u8]>, info: &[u8], okm: &mut SessionKey) -> Result<()>; } ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/src/crypto/backend/nettle/aead.rs���������������������������������������������0000644�0000000�0000000�00000020300�10461020230�0021163�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������//! Implementation of AEAD using Nettle cryptographic library. use std::cmp::Ordering; use nettle::{ aead::{ self, Aead as _, typenum::consts::U16, }, cipher, }; use crate::{Error, Result}; use crate::crypto::aead::{Aead, CipherOp}; use crate::crypto::mem::secure_cmp; use crate::seal; use crate::types::{AEADAlgorithm, SymmetricAlgorithm}; /// Disables authentication checks. /// /// This is DANGEROUS, and is only useful for debugging problems with /// malformed AEAD-encrypted messages. const DANGER_DISABLE_AUTHENTICATION: bool = false; impl<T: nettle::aead::Aead> seal::Sealed for T {} impl<T: nettle::aead::Aead> Aead for T { fn encrypt_seal(&mut self, dst: &mut [u8], src: &[u8]) -> Result<()> { debug_assert_eq!(dst.len(), src.len() + self.digest_size()); self.encrypt(dst, src); self.digest(&mut dst[src.len()..]); Ok(()) } fn decrypt_verify(&mut self, dst: &mut [u8], src: &[u8]) -> Result<()> { debug_assert!(src.len() >= self.digest_size()); debug_assert_eq!(dst.len() + self.digest_size(), src.len()); // Split src into ciphertext and digest. let l = self.digest_size(); let ciphertext = &src[..src.len().saturating_sub(l)]; let digest = &src[src.len().saturating_sub(l)..]; // Decrypt the chunk. self.decrypt(dst, ciphertext); // Compute the digest, storing it on the stack. let mut chunk_digest_store = [0u8; 16]; debug_assert!(chunk_digest_store.len() >= l); let chunk_digest = &mut chunk_digest_store[..l]; self.digest(chunk_digest); // Authenticate the chunk. if secure_cmp(&chunk_digest[..], digest) != Ordering::Equal && ! DANGER_DISABLE_AUTHENTICATION { return Err(Error::ManipulatedMessage.into()); } Ok(()) } fn digest_size(&self) -> usize { self.digest_size() } } impl AEADAlgorithm { pub(crate) fn context( &self, sym_algo: SymmetricAlgorithm, key: &[u8], aad: &[u8], nonce: &[u8], _op: CipherOp, ) -> Result<Box<dyn Aead>> { match self { AEADAlgorithm::EAX => match sym_algo { SymmetricAlgorithm::AES128 => { let mut ctx = aead::Eax::<cipher::Aes128>::with_key_and_nonce(key, nonce)?; ctx.update(aad); Ok(Box::new(ctx)) }, SymmetricAlgorithm::AES192 => { let mut ctx = aead::Eax::<cipher::Aes192>::with_key_and_nonce(key, nonce)?; ctx.update(aad); Ok(Box::new(ctx)) }, SymmetricAlgorithm::AES256 => { let mut ctx = aead::Eax::<cipher::Aes256>::with_key_and_nonce(key, nonce)?; ctx.update(aad); Ok(Box::new(ctx)) }, SymmetricAlgorithm::Twofish => { let mut ctx = aead::Eax::<cipher::Twofish>::with_key_and_nonce(key, nonce)?; ctx.update(aad); Ok(Box::new(ctx)) }, SymmetricAlgorithm::Camellia128 => { let mut ctx = aead::Eax::<cipher::Camellia128>::with_key_and_nonce(key, nonce)?; ctx.update(aad); Ok(Box::new(ctx)) }, SymmetricAlgorithm::Camellia192 => { let mut ctx = aead::Eax::<cipher::Camellia192>::with_key_and_nonce(key, nonce)?; ctx.update(aad); Ok(Box::new(ctx)) }, SymmetricAlgorithm::Camellia256 => { let mut ctx = aead::Eax::<cipher::Camellia256>::with_key_and_nonce(key, nonce)?; ctx.update(aad); Ok(Box::new(ctx)) }, _ => Err(Error::UnsupportedSymmetricAlgorithm(sym_algo).into()), }, AEADAlgorithm::OCB => match sym_algo { SymmetricAlgorithm::AES128 => { let mut ctx = aead::Ocb::<cipher::Aes128, U16>::with_key_and_nonce(key, nonce)?; ctx.update(aad); Ok(Box::new(ctx)) }, SymmetricAlgorithm::AES192 => { let mut ctx = aead::Ocb::<cipher::Aes192, U16>::with_key_and_nonce(key, nonce)?; ctx.update(aad); Ok(Box::new(ctx)) }, SymmetricAlgorithm::AES256 => { let mut ctx = aead::Ocb::<cipher::Aes256, U16>::with_key_and_nonce(key, nonce)?; ctx.update(aad); Ok(Box::new(ctx)) }, SymmetricAlgorithm::Twofish => { let mut ctx = aead::Ocb::<cipher::Twofish, U16>::with_key_and_nonce(key, nonce)?; ctx.update(aad); Ok(Box::new(ctx)) }, SymmetricAlgorithm::Camellia128 => { let mut ctx = aead::Ocb::<cipher::Camellia128, U16>::with_key_and_nonce(key, nonce)?; ctx.update(aad); Ok(Box::new(ctx)) }, SymmetricAlgorithm::Camellia192 => { let mut ctx = aead::Ocb::<cipher::Camellia192, U16>::with_key_and_nonce(key, nonce)?; ctx.update(aad); Ok(Box::new(ctx)) }, SymmetricAlgorithm::Camellia256 => { let mut ctx = aead::Ocb::<cipher::Camellia256, U16>::with_key_and_nonce(key, nonce)?; ctx.update(aad); Ok(Box::new(ctx)) }, _ => Err(Error::UnsupportedSymmetricAlgorithm(sym_algo).into()), }, AEADAlgorithm::GCM => match sym_algo { SymmetricAlgorithm::AES128 => { let mut ctx = aead::Gcm::<cipher::Aes128>::with_key_and_nonce(key, nonce)?; ctx.update(aad); Ok(Box::new(ctx)) }, SymmetricAlgorithm::AES192 => { let mut ctx = aead::Gcm::<cipher::Aes192>::with_key_and_nonce(key, nonce)?; ctx.update(aad); Ok(Box::new(ctx)) }, SymmetricAlgorithm::AES256 => { let mut ctx = aead::Gcm::<cipher::Aes256>::with_key_and_nonce(key, nonce)?; ctx.update(aad); Ok(Box::new(ctx)) }, SymmetricAlgorithm::Twofish => { let mut ctx = aead::Gcm::<cipher::Twofish>::with_key_and_nonce(key, nonce)?; ctx.update(aad); Ok(Box::new(ctx)) }, SymmetricAlgorithm::Camellia128 => { let mut ctx = aead::Gcm::<cipher::Camellia128>::with_key_and_nonce(key, nonce)?; ctx.update(aad); Ok(Box::new(ctx)) }, SymmetricAlgorithm::Camellia192 => { let mut ctx = aead::Gcm::<cipher::Camellia192>::with_key_and_nonce(key, nonce)?; ctx.update(aad); Ok(Box::new(ctx)) }, SymmetricAlgorithm::Camellia256 => { let mut ctx = aead::Gcm::<cipher::Camellia256>::with_key_and_nonce(key, nonce)?; ctx.update(aad); Ok(Box::new(ctx)) }, _ => Err(Error::UnsupportedSymmetricAlgorithm(sym_algo).into()), }, _ => Err(Error::UnsupportedAEADAlgorithm(*self).into()), } } } ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/src/crypto/backend/nettle/asymmetric.rs���������������������������������������0000644�0000000�0000000�00000054577�10461020230�0022475�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������//! Hold the implementation of [`Signer`] and [`Decryptor`] for [`KeyPair`]. //! //! [`Signer`]: crate::crypto::Signer //! [`Decryptor`]: crate::crypto::Decryptor //! [`KeyPair`]: crate::crypto::KeyPair use nettle::{ curve25519, curve448, dsa, ecc, ecdh, ecdsa, ed25519, ed448, rsa, random::Yarrow, }; use crate::{Error, Result}; use crate::packet::{key, Key}; use crate::crypto::asymmetric::KeyPair; use crate::crypto::backend::interface::Asymmetric; use crate::crypto::mpi::{self, MPI, ProtectedMPI, PublicKey}; use crate::crypto::{ SessionKey, mem::Protected, }; use crate::types::{Curve, HashAlgorithm}; impl Asymmetric for super::Backend { fn supports_algo(algo: PublicKeyAlgorithm) -> bool { use PublicKeyAlgorithm::*; #[allow(deprecated)] match algo { X25519 | Ed25519 | RSAEncryptSign | RSAEncrypt | RSASign | DSA | ECDH | ECDSA | EdDSA => true, X448 | Ed448 => curve448::IS_SUPPORTED, ElGamalEncrypt | ElGamalEncryptSign | Private(_) | Unknown(_) => false, } } fn supports_curve(curve: &Curve) -> bool { use Curve::*; match curve { NistP256 | NistP384 | NistP521 | Ed25519 | Cv25519 => true, BrainpoolP256 | BrainpoolP384 | BrainpoolP512 | Unknown(_) => false, } } fn x25519_generate_key() -> Result<(Protected, [u8; 32])> { debug_assert_eq!(curve25519::CURVE25519_SIZE, 32); let mut rng = Yarrow::default(); let secret = curve25519::private_key(&mut rng); let mut public = [0; 32]; curve25519::mul_g(&mut public, &secret)?; Ok((secret.into(), public)) } fn x25519_derive_public(secret: &Protected) -> Result<[u8; 32]> { debug_assert_eq!(curve25519::CURVE25519_SIZE, 32); let mut public = [0; 32]; curve25519::mul_g(&mut public, secret)?; Ok(public) } fn x25519_shared_point(secret: &Protected, public: &[u8; 32]) -> Result<Protected> { debug_assert_eq!(curve25519::CURVE25519_SIZE, 32); let mut s: Protected = vec![0; 32].into(); curve25519::mul(&mut s, secret, public)?; Ok(s) } fn x448_generate_key() -> Result<(Protected, [u8; 56])> { debug_assert_eq!(curve448::CURVE448_SIZE, 56); if ! curve448::IS_SUPPORTED { return Err(Error::UnsupportedPublicKeyAlgorithm( PublicKeyAlgorithm::Ed448).into()); } let mut rng = Yarrow::default(); let secret = curve448::private_key(&mut rng); let mut public = [0; 56]; curve448::mul_g(&mut public, &secret)?; Ok((secret.into(), public)) } fn x448_derive_public(secret: &Protected) -> Result<[u8; 56]> { debug_assert_eq!(curve448::CURVE448_SIZE, 56); if ! curve448::IS_SUPPORTED { return Err(Error::UnsupportedPublicKeyAlgorithm( PublicKeyAlgorithm::Ed448).into()); } let mut public = [0; 56]; curve448::mul_g(&mut public, secret)?; Ok(public) } fn x448_shared_point(secret: &Protected, public: &[u8; 56]) -> Result<Protected> { debug_assert_eq!(curve448::CURVE448_SIZE, 56); if ! curve448::IS_SUPPORTED { return Err(Error::UnsupportedPublicKeyAlgorithm( PublicKeyAlgorithm::Ed448).into()); } let mut s: Protected = vec![0; 56].into(); curve448::mul(&mut s, secret, public)?; Ok(s) } fn ed25519_generate_key() -> Result<(Protected, [u8; 32])> { debug_assert_eq!(ed25519::ED25519_KEY_SIZE, 32); let mut rng = Yarrow::default(); let mut public = [0; 32]; let secret: Protected = ed25519::private_key(&mut rng).into(); ed25519::public_key(&mut public, &secret)?; Ok((secret, public)) } fn ed25519_derive_public(secret: &Protected) -> Result<[u8; 32]> { debug_assert_eq!(ed25519::ED25519_KEY_SIZE, 32); let mut public = [0; 32]; ed25519::public_key(&mut public, secret)?; Ok(public) } fn ed25519_sign(secret: &Protected, public: &[u8; 32], digest: &[u8]) -> Result<[u8; 64]> { debug_assert_eq!(ed25519::ED25519_KEY_SIZE, 32); debug_assert_eq!(ed25519::ED25519_SIGNATURE_SIZE, 64); let mut sig = [0u8; 64]; ed25519::sign(public, secret, digest, &mut sig)?; Ok(sig) } fn ed25519_verify(public: &[u8; 32], digest: &[u8], signature: &[u8; 64]) -> Result<bool> { debug_assert_eq!(ed25519::ED25519_KEY_SIZE, 32); debug_assert_eq!(ed25519::ED25519_SIGNATURE_SIZE, 64); Ok(ed25519::verify(public, digest, signature)?) } fn ed448_generate_key() -> Result<(Protected, [u8; 57])> { debug_assert_eq!(ed448::ED448_KEY_SIZE, 57); let mut rng = Yarrow::default(); let mut public = [0; 57]; let secret: Protected = ed448::private_key(&mut rng).into(); ed448::public_key(&mut public, &secret)?; Ok((secret, public)) } fn ed448_derive_public(secret: &Protected) -> Result<[u8; 57]> { debug_assert_eq!(ed448::ED448_KEY_SIZE, 57); let mut public = [0; 57]; ed448::public_key(&mut public, secret)?; Ok(public) } fn ed448_sign(secret: &Protected, public: &[u8; 57], digest: &[u8]) -> Result<[u8; 114]> { debug_assert_eq!(ed448::ED448_KEY_SIZE, 57); debug_assert_eq!(ed448::ED448_SIGNATURE_SIZE, 114); let mut sig = [0u8; 114]; ed448::sign(public, secret, digest, &mut sig)?; Ok(sig) } fn ed448_verify(public: &[u8; 57], digest: &[u8], signature: &[u8; 114]) -> Result<bool> { debug_assert_eq!(ed448::ED448_KEY_SIZE, 57); debug_assert_eq!(ed448::ED448_SIGNATURE_SIZE, 114); Ok(ed448::verify(public, digest, signature)?) } fn dsa_generate_key(p_bits: usize) -> Result<(MPI, MPI, MPI, MPI, ProtectedMPI)> { let mut rng = Yarrow::default(); let q_bits = if p_bits <= 1024 { 160 } else { 256 }; let params = dsa::Params::generate(&mut rng, p_bits, q_bits)?; let (p, q) = params.primes(); let g = params.g(); let (y, x) = dsa::generate_keypair(&params, &mut rng); Ok((p.into(), q.into(), g.into(), y.as_bytes().into(), x.as_bytes().into())) } } impl KeyPair { pub(crate) fn sign_backend(&self, secret: &mpi::SecretKeyMaterial, hash_algo: HashAlgorithm, digest: &[u8]) -> Result<mpi::Signature> { use crate::PublicKeyAlgorithm::*; let mut rng = Yarrow::default(); #[allow(deprecated)] match (self.public().pk_algo(), self.public().mpis(), secret) { (RSASign, &PublicKey::RSA { ref e, ref n }, &mpi::SecretKeyMaterial::RSA { ref p, ref q, ref d, .. }) | (RSAEncryptSign, &PublicKey::RSA { ref e, ref n }, &mpi::SecretKeyMaterial::RSA { ref p, ref q, ref d, .. }) => { let public = rsa::PublicKey::new(n.value(), e.value())?; let secret = rsa::PrivateKey::new(d.value(), p.value(), q.value(), Option::None)?; // The signature has the length of the modulus. let mut sig = vec![0u8; n.value().len()]; // As described in [Section 5.2.2 and 5.2.3 of RFC 9580], // to verify the signature, we need to encode the // signature data in a PKCS1-v1.5 packet. // // [Section 5.2.2 and 5.2.3 of RFC 9580]: // https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.2 rsa::sign_digest_pkcs1(&public, &secret, digest, hash_algo.oid()?, &mut rng, &mut sig)?; Ok(mpi::Signature::RSA { s: MPI::new(&sig), }) }, (DSA, &PublicKey::DSA { ref p, ref q, ref g, .. }, &mpi::SecretKeyMaterial::DSA { ref x }) => { let params = dsa::Params::new(p.value(), q.value(), g.value()); let secret = dsa::PrivateKey::new(x.value()); let sig = dsa::sign(&params, &secret, digest, &mut rng)?; Ok(mpi::Signature::DSA { r: MPI::new(&sig.r()), s: MPI::new(&sig.s()), }) }, (ECDSA, &PublicKey::ECDSA { ref curve, .. }, &mpi::SecretKeyMaterial::ECDSA { ref scalar }) => { let secret = match curve { Curve::NistP256 => ecc::Scalar::new::<ecc::Secp256r1>( scalar.value())?, Curve::NistP384 => ecc::Scalar::new::<ecc::Secp384r1>( scalar.value())?, Curve::NistP521 => ecc::Scalar::new::<ecc::Secp521r1>( scalar.value())?, _ => return Err( Error::UnsupportedEllipticCurve(curve.clone()) .into()), }; let sig = ecdsa::sign(&secret, digest, &mut rng); Ok(mpi::Signature::ECDSA { r: MPI::new(&sig.r()), s: MPI::new(&sig.s()), }) }, (pk_algo, _, _) => Err(Error::InvalidOperation(format!( "unsupported combination of algorithm {:?}, key {:?}, \ and secret key {:?}", pk_algo, self.public(), self.secret())).into()), } } } impl KeyPair { pub(crate) fn decrypt_backend(&self, secret: &mpi::SecretKeyMaterial, ciphertext: &mpi::Ciphertext, plaintext_len: Option<usize>) -> Result<SessionKey> { use crate::PublicKeyAlgorithm::*; Ok(match (self.public().mpis(), secret, ciphertext) { (PublicKey::RSA{ ref e, ref n }, mpi::SecretKeyMaterial::RSA{ ref p, ref q, ref d, .. }, mpi::Ciphertext::RSA{ ref c }) => { let public = rsa::PublicKey::new(n.value(), e.value())?; let secret = rsa::PrivateKey::new(d.value(), p.value(), q.value(), Option::None)?; let mut rand = Yarrow::default(); if let Some(l) = plaintext_len { let mut plaintext: SessionKey = vec![0; l].into(); rsa::decrypt_pkcs1(&public, &secret, &mut rand, c.value(), plaintext.as_mut())?; plaintext } else { rsa::decrypt_pkcs1_insecure(&public, &secret, &mut rand, c.value())? .into() } } (PublicKey::ElGamal{ .. }, mpi::SecretKeyMaterial::ElGamal{ .. }, mpi::Ciphertext::ElGamal{ .. }) => { #[allow(deprecated)] return Err( Error::UnsupportedPublicKeyAlgorithm(ElGamalEncrypt).into()); }, (PublicKey::ECDH{ .. }, mpi::SecretKeyMaterial::ECDH { .. }, mpi::Ciphertext::ECDH { .. }) => crate::crypto::ecdh::decrypt(self.public(), secret, ciphertext, plaintext_len)?, (public, secret, ciphertext) => return Err(Error::InvalidOperation(format!( "unsupported combination of key pair {:?}/{:?} \ and ciphertext {:?}", public, secret, ciphertext)).into()), }) } } impl<P: key::KeyParts, R: key::KeyRole> Key<P, R> { /// Encrypts the given data with this key. pub(crate) fn encrypt_backend(&self, data: &SessionKey) -> Result<mpi::Ciphertext> { use crate::PublicKeyAlgorithm::*; #[allow(deprecated)] match self.pk_algo() { RSAEncryptSign | RSAEncrypt => { // Extract the public recipient. match self.mpis() { mpi::PublicKey::RSA { e, n } => { // The ciphertext has the length of the modulus. let ciphertext_len = n.value().len(); if data.len() + 11 > ciphertext_len { return Err(Error::InvalidArgument( "Plaintext data too large".into()).into()); } let mut esk = vec![0u8; ciphertext_len]; let mut rng = Yarrow::default(); let pk = rsa::PublicKey::new(n.value(), e.value())?; rsa::encrypt_pkcs1(&pk, &mut rng, data, &mut esk)?; Ok(mpi::Ciphertext::RSA { c: MPI::new(&esk), }) }, pk => { Err(Error::MalformedPacket( format!( "Key: Expected RSA public key, got {:?}", pk)).into()) }, } }, ECDH => crate::crypto::ecdh::encrypt(self.parts_as_public(), data), RSASign | DSA | ECDSA | EdDSA | Ed25519 | Ed448 => Err(Error::InvalidOperation( format!("{} is not an encryption algorithm", self.pk_algo()) ).into()), X25519 | // Handled in common code. X448 | // Handled in common code. ElGamalEncrypt | ElGamalEncryptSign | Private(_) | Unknown(_) => Err(Error::UnsupportedPublicKeyAlgorithm(self.pk_algo()).into()), } } /// Verifies the given signature. pub(crate) fn verify_backend(&self, sig: &mpi::Signature, hash_algo: HashAlgorithm, digest: &[u8]) -> Result<()> { use crate::crypto::mpi::Signature; let ok = match (self.mpis(), sig) { (PublicKey::RSA { e, n }, Signature::RSA { s }) => { let key = rsa::PublicKey::new(n.value(), e.value())?; // As described in [Section 5.2.2 and 5.2.3 of RFC 9580], // to verify the signature, we need to encode the // signature data in a PKCS1-v1.5 packet. // // [Section 5.2.2 and 5.2.3 of RFC 9580]: // https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.2 rsa::verify_digest_pkcs1(&key, digest, hash_algo.oid()?, s.value())? }, (PublicKey::DSA { y, p, q, g }, Signature::DSA { s, r }) => { let key = dsa::PublicKey::new(y.value()); let params = dsa::Params::new(p.value(), q.value(), g.value()); let signature = dsa::Signature::new(r.value(), s.value()); dsa::verify(&params, &key, digest, &signature) }, (PublicKey::ECDSA { curve, q }, Signature::ECDSA { s, r }) => { let (x, y) = q.decode_point(curve)?; let key = match curve { Curve::NistP256 => ecc::Point::new::<ecc::Secp256r1>(x, y)?, Curve::NistP384 => ecc::Point::new::<ecc::Secp384r1>(x, y)?, Curve::NistP521 => ecc::Point::new::<ecc::Secp521r1>(x, y)?, _ => return Err( Error::UnsupportedEllipticCurve(curve.clone()).into()), }; let signature = dsa::Signature::new(r.value(), s.value()); ecdsa::verify(&key, digest, &signature) }, _ => return Err(Error::MalformedPacket(format!( "unsupported combination of key {} and signature {:?}.", self.pk_algo(), sig)).into()), }; if ok { Ok(()) } else { Err(Error::ManipulatedMessage.into()) } } } use std::time::SystemTime; use crate::packet::key::{Key4, SecretParts}; use crate::types::PublicKeyAlgorithm; impl<R> Key4<SecretParts, R> where R: key::KeyRole, { /// Creates a new OpenPGP public key packet for an existing RSA key. /// /// The RSA key will use public exponent `e` and modulo `n`. The key will /// have its creation date set to `ctime` or the current time if `None` /// is given. pub fn import_secret_rsa<T>(d: &[u8], p: &[u8], q: &[u8], ctime: T) -> Result<Self> where T: Into<Option<SystemTime>> { let sec = rsa::PrivateKey::new(d, p, q, None)?; let key = sec.public_key()?; let (a, b, c) = sec.as_rfc4880(); Self::with_secret( ctime.into().unwrap_or_else(crate::now), PublicKeyAlgorithm::RSAEncryptSign, mpi::PublicKey::RSA { e: mpi::MPI::new(&key.e()[..]), n: mpi::MPI::new(&key.n()[..]), }, mpi::SecretKeyMaterial::RSA { d: d.into(), p: a.into(), q: b.into(), u: c.into(), }.into()) } /// Generates a new RSA key with a public modulus of size `bits`. pub fn generate_rsa(bits: usize) -> Result<Self> { let mut rng = Yarrow::default(); let (public, private) = rsa::generate_keypair(&mut rng, bits as u32)?; let (p, q, u) = private.as_rfc4880(); let public_mpis = PublicKey::RSA { e: MPI::new(&*public.e()), n: MPI::new(&*public.n()), }; let private_mpis = mpi::SecretKeyMaterial::RSA { d: private.d().into(), p: p.into(), q: q.into(), u: u.into(), }; Self::with_secret( crate::now(), PublicKeyAlgorithm::RSAEncryptSign, public_mpis, private_mpis.into()) } /// Generates a new ECC key over `curve`. /// /// If `for_signing` is false a ECDH key, if it's true either a /// EdDSA or ECDSA key is generated. Giving `for_signing == true` and /// `curve == Cv25519` will produce an error. Likewise /// `for_signing == false` and `curve == Ed25519` will produce an error. pub(crate) fn generate_ecc_backend(for_signing: bool, curve: Curve) -> Result<(PublicKeyAlgorithm, mpi::PublicKey, mpi::SecretKeyMaterial)> { let mut rng = Yarrow::default(); match (curve.clone(), for_signing) { (Curve::Ed25519, true) => unreachable!("handled in Key4::generate_ecc"), (Curve::Cv25519, false) => unreachable!("handled in Key4::generate_ecc"), (Curve::NistP256, true) | (Curve::NistP384, true) | (Curve::NistP521, true) => { let (public, private, field_sz) = match curve { Curve::NistP256 => { let (pu, sec) = ecdsa::generate_keypair::<ecc::Secp256r1, _>(&mut rng)?; (pu, sec, 256) } Curve::NistP384 => { let (pu, sec) = ecdsa::generate_keypair::<ecc::Secp384r1, _>(&mut rng)?; (pu, sec, 384) } Curve::NistP521 => { let (pu, sec) = ecdsa::generate_keypair::<ecc::Secp521r1, _>(&mut rng)?; (pu, sec, 521) } _ => unreachable!(), }; let (pub_x, pub_y) = public.as_bytes(); let public_mpis = mpi::PublicKey::ECDSA{ curve, q: MPI::new_point(&pub_x, &pub_y, field_sz), }; let private_mpis = mpi::SecretKeyMaterial::ECDSA{ scalar: private.as_bytes().into(), }; Ok((PublicKeyAlgorithm::ECDSA, public_mpis, private_mpis)) } (Curve::NistP256, false) | (Curve::NistP384, false) | (Curve::NistP521, false) => { let (private, field_sz) = match curve { Curve::NistP256 => { let pv = ecc::Scalar::new_random::<ecc::Secp256r1, _>(&mut rng); (pv, 256) } Curve::NistP384 => { let pv = ecc::Scalar::new_random::<ecc::Secp384r1, _>(&mut rng); (pv, 384) } Curve::NistP521 => { let pv = ecc::Scalar::new_random::<ecc::Secp521r1, _>(&mut rng); (pv, 521) } _ => unreachable!(), }; let public = ecdh::point_mul_g(&private); let (pub_x, pub_y) = public.as_bytes(); let public_mpis = mpi::PublicKey::ECDH{ q: MPI::new_point(&pub_x, &pub_y, field_sz), hash: crate::crypto::ecdh::default_ecdh_kdf_hash(&curve), sym: crate::crypto::ecdh::default_ecdh_kek_cipher(&curve), curve, }; let private_mpis = mpi::SecretKeyMaterial::ECDH{ scalar: private.as_bytes().into(), }; Ok((PublicKeyAlgorithm::ECDH, public_mpis, private_mpis)) } _ => Err(Error::UnsupportedEllipticCurve(curve).into()), } } } ���������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/src/crypto/backend/nettle/ecdh.rs���������������������������������������������0000644�0000000�0000000�00000015664�10461020230�0021215�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������//! Elliptic Curve Diffie-Hellman. use nettle::{ecc, ecdh, random::Yarrow}; use crate::{Error, Result}; use crate::crypto::SessionKey; use crate::crypto::ecdh::{encrypt_wrap, decrypt_unwrap}; use crate::crypto::mem::Protected; use crate::crypto::mpi::{MPI, PublicKey, SecretKeyMaterial, Ciphertext}; use crate::packet::{key, Key}; use crate::types::Curve; /// Wraps a session key using Elliptic Curve Diffie-Hellman. #[allow(non_snake_case)] pub fn encrypt<R>(recipient: &Key<key::PublicParts, R>, session_key: &SessionKey) -> Result<Ciphertext> where R: key::KeyRole { let mut rng = Yarrow::default(); if let PublicKey::ECDH { ref curve, ref q,.. } = recipient.mpis() { match curve { Curve::Cv25519 => Err(Error::InvalidArgument("implemented elsewhere".into()).into()), Curve::NistP256 | Curve::NistP384 | Curve::NistP521 => { // Obtain the authenticated recipient public key R and // generate an ephemeral private key v. // Note: ecc::Point and ecc::Scalar are cleaned up by // nettle. let (Rx, Ry) = q.decode_point(curve)?; let (R, v, field_sz) = match curve { Curve::NistP256 => { let R = ecc::Point::new::<ecc::Secp256r1>(Rx, Ry)?; let v = ecc::Scalar::new_random::<ecc::Secp256r1, _>(&mut rng); let field_sz = 256; (R, v, field_sz) } Curve::NistP384 => { let R = ecc::Point::new::<ecc::Secp384r1>(Rx, Ry)?; let v = ecc::Scalar::new_random::<ecc::Secp384r1, _>(&mut rng); let field_sz = 384; (R, v, field_sz) } Curve::NistP521 => { let R = ecc::Point::new::<ecc::Secp521r1>(Rx, Ry)?; let v = ecc::Scalar::new_random::<ecc::Secp521r1, _>(&mut rng); let field_sz = 521; (R, v, field_sz) } _ => unreachable!(), }; // Compute the public key. let VB = ecdh::point_mul_g(&v); let (VBx, VBy) = VB.as_bytes(); let VB = MPI::new_point(&VBx, &VBy, field_sz); // Compute the shared point S = vR; let S = ecdh::point_mul(&v, &R)?; // Get the X coordinate, safely dispose of Y. let (Sx, Sy) = S.as_bytes(); let _ = Protected::from(Sy); // Just a precaution. // Zero-pad to the size of the underlying field, // rounded to the next byte. let mut Sx = Vec::from(Sx); while Sx.len() < (field_sz + 7) / 8 { Sx.insert(0, 0); } encrypt_wrap(recipient, session_key, VB, &Sx.into()) } // Not implemented in Nettle Curve::BrainpoolP256 | Curve::BrainpoolP384 | Curve::BrainpoolP512 => Err(Error::UnsupportedEllipticCurve(curve.clone()).into()), // N/A Curve::Unknown(_) | Curve::Ed25519 => Err(Error::UnsupportedEllipticCurve(curve.clone()).into()), } } else { Err(Error::InvalidArgument("Expected an ECDHPublicKey".into()).into()) } } /// Unwraps a session key using Elliptic Curve Diffie-Hellman. #[allow(non_snake_case)] pub fn decrypt<R>(recipient: &Key<key::PublicParts, R>, recipient_sec: &SecretKeyMaterial, ciphertext: &Ciphertext, plaintext_len: Option<usize>) -> Result<SessionKey> where R: key::KeyRole { match (recipient.mpis(), recipient_sec, ciphertext) { (PublicKey::ECDH { ref curve, ..}, SecretKeyMaterial::ECDH { ref scalar, }, Ciphertext::ECDH { ref e, .. }) => { let S: Protected = match curve { Curve::Cv25519 => return Err(Error::InvalidArgument("implemented elsewhere".into()).into()), Curve::NistP256 | Curve::NistP384 | Curve::NistP521 => { // Get the public part V of the ephemeral key and // compute the shared point S = rV = rvG, where (r, R) // is the recipient's key pair. let (Vx, Vy) = e.decode_point(curve)?; let (V, r, field_sz) = match curve { Curve::NistP256 => { let V = ecc::Point::new::<ecc::Secp256r1>(Vx, Vy)?; let r = ecc::Scalar::new::<ecc::Secp256r1>(scalar.value())?; (V, r, 256) } Curve::NistP384 => { let V = ecc::Point::new::<ecc::Secp384r1>(Vx, Vy)?; let r = ecc::Scalar::new::<ecc::Secp384r1>(scalar.value())?; (V, r, 384) } Curve::NistP521 => { let V = ecc::Point::new::<ecc::Secp521r1>(Vx, Vy)?; let r = ecc::Scalar::new::<ecc::Secp521r1>(scalar.value())?; (V, r, 521) } _ => unreachable!(), }; let S = ecdh::point_mul(&r, &V)?; // Get the X coordinate, safely dispose of Y. let (Sx, Sy) = S.as_bytes(); let _ = Protected::from(Sy); // Just a precaution. // Zero-pad to the size of the underlying field, // rounded to the next byte. let mut Sx = Vec::from(Sx); while Sx.len() < (field_sz + 7) / 8 { Sx.insert(0, 0); } Sx.into() } // Not implemented in Nettle Curve::BrainpoolP256 | Curve::BrainpoolP384 | Curve::BrainpoolP512 => return Err(Error::UnsupportedEllipticCurve(curve.clone()).into()), // N/A Curve::Unknown(_) | Curve::Ed25519 => return Err(Error::UnsupportedEllipticCurve(curve.clone()).into()), }; decrypt_unwrap(recipient.role_as_unspecified(), &S, ciphertext, plaintext_len) } _ => Err(Error::InvalidArgument("Expected an ECDHPublicKey".into()).into()), } } ����������������������������������������������������������������������������sequoia-openpgp-2.0.0/src/crypto/backend/nettle/hash.rs���������������������������������������������0000644�0000000�0000000�00000007173�10461020230�0021231�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������use crate::crypto::hash::Digest; use crate::{Error, Result}; use crate::types::{HashAlgorithm}; macro_rules! impl_digest_for { ($t: path, $algo: ident) => { impl Digest for $t { fn update(&mut self, data: &[u8]) { nettle::hash::Hash::update(self, data); } fn digest(&mut self, digest: &mut [u8]) -> Result<()> { nettle::hash::Hash::digest(self, digest); Ok(()) } } } } impl_digest_for!(nettle::hash::Sha224, SHA224); impl_digest_for!(nettle::hash::Sha256, SHA256); impl_digest_for!(nettle::hash::Sha384, SHA384); impl_digest_for!(nettle::hash::Sha512, SHA512); impl_digest_for!(nettle::hash::Sha3_256, SHA3_256); impl_digest_for!(nettle::hash::Sha3_512, SHA3_512); impl_digest_for!(nettle::hash::insecure_do_not_use::Sha1, SHA1); impl_digest_for!(nettle::hash::insecure_do_not_use::Md5, MD5); impl_digest_for!(nettle::hash::insecure_do_not_use::Ripemd160, RipeMD); impl HashAlgorithm { /// Whether Sequoia supports this algorithm. pub fn is_supported(self) -> bool { match self { HashAlgorithm::SHA1 => true, HashAlgorithm::SHA224 => true, HashAlgorithm::SHA256 => true, HashAlgorithm::SHA384 => true, HashAlgorithm::SHA512 => true, HashAlgorithm::SHA3_256 => true, HashAlgorithm::SHA3_512 => true, HashAlgorithm::RipeMD => true, HashAlgorithm::MD5 => true, HashAlgorithm::Private(_) => false, HashAlgorithm::Unknown(_) => false, } } /// Creates a new hash context for this algorithm. /// /// # Errors /// /// Fails with `Error::UnsupportedHashAlgorithm` if Sequoia does /// not support this algorithm. See /// [`HashAlgorithm::is_supported`]. /// /// [`HashAlgorithm::is_supported`]: HashAlgorithm::is_supported() pub(crate) fn new_hasher(self) -> Result<Box<dyn Digest>> { use nettle::hash::{Sha224, Sha256, Sha384, Sha512, Sha3_256, Sha3_512}; use nettle::hash::insecure_do_not_use::{ Sha1, Md5, Ripemd160, }; match self { HashAlgorithm::SHA1 => Ok(Box::new(Sha1::default())), HashAlgorithm::SHA224 => Ok(Box::new(Sha224::default())), HashAlgorithm::SHA256 => Ok(Box::new(Sha256::default())), HashAlgorithm::SHA384 => Ok(Box::new(Sha384::default())), HashAlgorithm::SHA512 => Ok(Box::new(Sha512::default())), HashAlgorithm::SHA3_256 => Ok(Box::new(Sha3_256::default())), HashAlgorithm::SHA3_512 => Ok(Box::new(Sha3_512::default())), HashAlgorithm::MD5 => Ok(Box::new(Md5::default())), HashAlgorithm::RipeMD => Ok(Box::new(Ripemd160::default())), HashAlgorithm::Private(_) | HashAlgorithm::Unknown(_) => Err(Error::UnsupportedHashAlgorithm(self).into()), } } } #[cfg(all(test, feature = "crypto-nettle"))] mod tests { use super::*; use nettle::rsa; #[test] fn oids_match_nettle() { assert_eq!(HashAlgorithm::SHA1.oid().unwrap(), rsa::ASN1_OID_SHA1); assert_eq!(HashAlgorithm::SHA224.oid().unwrap(), rsa::ASN1_OID_SHA224); assert_eq!(HashAlgorithm::SHA256.oid().unwrap(), rsa::ASN1_OID_SHA256); assert_eq!(HashAlgorithm::SHA384.oid().unwrap(), rsa::ASN1_OID_SHA384); assert_eq!(HashAlgorithm::SHA512.oid().unwrap(), rsa::ASN1_OID_SHA512); assert_eq!(HashAlgorithm::MD5.oid().unwrap(), rsa::ASN1_OID_MD5); assert_eq!(HashAlgorithm::RipeMD.oid().unwrap(), rsa::ASN1_OID_RIPEMD160); } } �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/src/crypto/backend/nettle/kdf.rs����������������������������������������������0000644�0000000�0000000�00000001603�10461020230�0021042�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������use nettle::{ kdf::hkdf, hash::{Sha256, Sha512}, }; use crate::{ Result, crypto::{ SessionKey, backend::interface::Kdf, }, }; impl Kdf for super::Backend { fn hkdf_sha256(ikm: &SessionKey, salt: Option<&[u8]>, info: &[u8], okm: &mut SessionKey) -> Result<()> { assert!(okm.len() <= 255 * 32); const NO_SALT: [u8; 32] = [0; 32]; let salt = salt.unwrap_or(&NO_SALT); hkdf::<Sha256>(&ikm[..], salt, info, okm); Ok(()) } fn hkdf_sha512(ikm: &SessionKey, salt: Option<&[u8]>, info: &[u8], okm: &mut SessionKey) -> Result<()> { assert!(okm.len() <= 255 * 64); const NO_SALT: [u8; 64] = [0; 64]; let salt = salt.unwrap_or(&NO_SALT); hkdf::<Sha512>(&ikm[..], salt, info, okm); Ok(()) } } �����������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/src/crypto/backend/nettle/symmetric.rs����������������������������������������0000644�0000000�0000000�00000025255�10461020230�0022323�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������use nettle::cipher::{self, Cipher}; use nettle::mode::{self}; use crate::crypto::mem::Protected; use crate::crypto::symmetric::Mode; use crate::{Error, Result}; use crate::types::SymmetricAlgorithm; struct ModeWrapper<M> { mode: M, iv: Protected, } impl<M> ModeWrapper<M> where M: nettle::mode::Mode + Send + Sync + 'static, { fn new(mode: M, iv: Vec<u8>) -> Box<dyn Mode> { Box::new(ModeWrapper { mode, iv: iv.into(), }) } } impl<M> Mode for ModeWrapper<M> where M: nettle::mode::Mode + Send + Sync, { fn block_size(&self) -> usize { self.mode.block_size() } fn encrypt( &mut self, dst: &mut [u8], src: &[u8], ) -> Result<()> { self.mode.encrypt(&mut self.iv, dst, src)?; Ok(()) } fn decrypt( &mut self, dst: &mut [u8], src: &[u8], ) -> Result<()> { self.mode.decrypt(&mut self.iv, dst, src)?; Ok(()) } } impl<C> Mode for C where C: Cipher + Send + Sync, { fn block_size(&self) -> usize { C::BLOCK_SIZE } fn encrypt( &mut self, dst: &mut [u8], src: &[u8], ) -> Result<()> { self.encrypt(dst, src); Ok(()) } fn decrypt( &mut self, dst: &mut [u8], src: &[u8], ) -> Result<()> { self.decrypt(dst, src); Ok(()) } } impl SymmetricAlgorithm { /// Returns whether this algorithm is supported by the crypto backend. pub(crate) fn is_supported_by_backend(&self) -> bool { use self::SymmetricAlgorithm::*; #[allow(deprecated)] match &self { TripleDES | CAST5 | Blowfish | AES128 | AES192 | AES256 | Twofish | Camellia128 | Camellia192 | Camellia256 => true, Unencrypted | IDEA | Private(_) | Unknown(_) => false, } } /// Creates a Nettle context for encrypting in CFB mode. pub(crate) fn make_encrypt_cfb(self, key: &[u8], iv: Vec<u8>) -> Result<Box<dyn Mode>> { #[allow(deprecated)] match self { SymmetricAlgorithm::TripleDES => Ok(ModeWrapper::new( mode::Cfb::<cipher::Des3>::with_encrypt_key(key)?, iv)), SymmetricAlgorithm::CAST5 => Ok(ModeWrapper::new( mode::Cfb::<cipher::Cast128>::with_encrypt_key(key)?, iv)), SymmetricAlgorithm::Blowfish => Ok(ModeWrapper::new( mode::Cfb::<cipher::Blowfish>::with_encrypt_key(key)?, iv)), SymmetricAlgorithm::AES128 => Ok(ModeWrapper::new( mode::Cfb::<cipher::Aes128>::with_encrypt_key(key)?, iv)), SymmetricAlgorithm::AES192 => Ok(ModeWrapper::new( mode::Cfb::<cipher::Aes192>::with_encrypt_key(key)?, iv)), SymmetricAlgorithm::AES256 => Ok(ModeWrapper::new( mode::Cfb::<cipher::Aes256>::with_encrypt_key(key)?, iv)), SymmetricAlgorithm::Twofish => Ok(ModeWrapper::new( mode::Cfb::<cipher::Twofish>::with_encrypt_key(key)?, iv)), SymmetricAlgorithm::Camellia128 => Ok(ModeWrapper::new( mode::Cfb::<cipher::Camellia128>::with_encrypt_key(key)?, iv)), SymmetricAlgorithm::Camellia192 => Ok(ModeWrapper::new( mode::Cfb::<cipher::Camellia192>::with_encrypt_key(key)?, iv)), SymmetricAlgorithm::Camellia256 => Ok(ModeWrapper::new( mode::Cfb::<cipher::Camellia256>::with_encrypt_key(key)?, iv)), _ => Err(Error::UnsupportedSymmetricAlgorithm(self).into()), } } /// Creates a Nettle context for decrypting in CFB mode. pub(crate) fn make_decrypt_cfb(self, key: &[u8], iv: Vec<u8>) -> Result<Box<dyn Mode>> { #[allow(deprecated)] match self { SymmetricAlgorithm::TripleDES => Ok(ModeWrapper::new( mode::Cfb::<cipher::Des3>::with_decrypt_key(key)?, iv)), SymmetricAlgorithm::CAST5 => Ok(ModeWrapper::new( mode::Cfb::<cipher::Cast128>::with_decrypt_key(key)?, iv)), SymmetricAlgorithm::Blowfish => Ok(ModeWrapper::new( mode::Cfb::<cipher::Blowfish>::with_decrypt_key(key)?, iv)), SymmetricAlgorithm::AES128 => Ok(ModeWrapper::new( mode::Cfb::<cipher::Aes128>::with_decrypt_key(key)?, iv)), SymmetricAlgorithm::AES192 => Ok(ModeWrapper::new( mode::Cfb::<cipher::Aes192>::with_decrypt_key(key)?, iv)), SymmetricAlgorithm::AES256 => Ok(ModeWrapper::new( mode::Cfb::<cipher::Aes256>::with_decrypt_key(key)?, iv)), SymmetricAlgorithm::Twofish => Ok(ModeWrapper::new( mode::Cfb::<cipher::Twofish>::with_decrypt_key(key)?, iv)), SymmetricAlgorithm::Camellia128 => Ok(ModeWrapper::new( mode::Cfb::<cipher::Camellia128>::with_decrypt_key(key)?, iv)), SymmetricAlgorithm::Camellia192 => Ok(ModeWrapper::new( mode::Cfb::<cipher::Camellia192>::with_decrypt_key(key)?, iv)), SymmetricAlgorithm::Camellia256 => Ok(ModeWrapper::new( mode::Cfb::<cipher::Camellia256>::with_decrypt_key(key)?, iv)), _ => Err(Error::UnsupportedSymmetricAlgorithm(self).into()) } } /// Creates a Nettle context for encrypting in ECB mode. pub(crate) fn make_encrypt_ecb(self, key: &[u8]) -> Result<Box<dyn Mode>> { #[allow(deprecated)] match self { SymmetricAlgorithm::TripleDES => Ok(Box::new(cipher::Des3::with_encrypt_key(key)?)), SymmetricAlgorithm::CAST5 => Ok(Box::new(cipher::Cast128::with_encrypt_key(key)?)), SymmetricAlgorithm::Blowfish => Ok(Box::new(cipher::Blowfish::with_encrypt_key(key)?)), SymmetricAlgorithm::AES128 => Ok(Box::new(cipher::Aes128::with_encrypt_key(key)?)), SymmetricAlgorithm::AES192 => Ok(Box::new(cipher::Aes192::with_encrypt_key(key)?)), SymmetricAlgorithm::AES256 => Ok(Box::new(cipher::Aes256::with_encrypt_key(key)?)), SymmetricAlgorithm::Twofish => Ok(Box::new(cipher::Twofish::with_encrypt_key(key)?)), SymmetricAlgorithm::Camellia128 => Ok(Box::new(cipher::Camellia128::with_encrypt_key(key)?)), SymmetricAlgorithm::Camellia192 => Ok(Box::new(cipher::Camellia192::with_encrypt_key(key)?)), SymmetricAlgorithm::Camellia256 => Ok(Box::new(cipher::Camellia256::with_encrypt_key(key)?)), _ => Err(Error::UnsupportedSymmetricAlgorithm(self).into()) } } /// Creates a Nettle context for decrypting in ECB mode. pub(crate) fn make_decrypt_ecb(self, key: &[u8]) -> Result<Box<dyn Mode>> { #[allow(deprecated)] match self { SymmetricAlgorithm::TripleDES => Ok(Box::new(cipher::Des3::with_decrypt_key(key)?)), SymmetricAlgorithm::CAST5 => Ok(Box::new(cipher::Cast128::with_decrypt_key(key)?)), SymmetricAlgorithm::Blowfish => Ok(Box::new(cipher::Blowfish::with_decrypt_key(key)?)), SymmetricAlgorithm::AES128 => Ok(Box::new(cipher::Aes128::with_decrypt_key(key)?)), SymmetricAlgorithm::AES192 => Ok(Box::new(cipher::Aes192::with_decrypt_key(key)?)), SymmetricAlgorithm::AES256 => Ok(Box::new(cipher::Aes256::with_decrypt_key(key)?)), SymmetricAlgorithm::Twofish => Ok(Box::new(cipher::Twofish::with_decrypt_key(key)?)), SymmetricAlgorithm::Camellia128 => Ok(Box::new(cipher::Camellia128::with_decrypt_key(key)?)), SymmetricAlgorithm::Camellia192 => Ok(Box::new(cipher::Camellia192::with_decrypt_key(key)?)), SymmetricAlgorithm::Camellia256 => Ok(Box::new(cipher::Camellia256::with_decrypt_key(key)?)), _ => Err(Error::UnsupportedSymmetricAlgorithm(self).into()) } } } #[cfg(test)] #[allow(deprecated)] mod tests { use super::*; /// Anchors the constants used in Sequoia with the ones from /// Nettle. #[test] fn key_size() -> Result<()> { assert_eq!(SymmetricAlgorithm::TripleDES.key_size()?, cipher::Des3::KEY_SIZE); assert_eq!(SymmetricAlgorithm::CAST5.key_size()?, cipher::Cast128::KEY_SIZE); // RFC4880, Section 9.2: Blowfish (128 bit key, 16 rounds) assert_eq!(SymmetricAlgorithm::Blowfish.key_size()?, 16); assert_eq!(SymmetricAlgorithm::AES128.key_size()?, cipher::Aes128::KEY_SIZE); assert_eq!(SymmetricAlgorithm::AES192.key_size()?, cipher::Aes192::KEY_SIZE); assert_eq!(SymmetricAlgorithm::AES256.key_size()?, cipher::Aes256::KEY_SIZE); assert_eq!(SymmetricAlgorithm::Twofish.key_size()?, cipher::Twofish::KEY_SIZE); assert_eq!(SymmetricAlgorithm::Camellia128.key_size()?, cipher::Camellia128::KEY_SIZE); assert_eq!(SymmetricAlgorithm::Camellia192.key_size()?, cipher::Camellia192::KEY_SIZE); assert_eq!(SymmetricAlgorithm::Camellia256.key_size()?, cipher::Camellia256::KEY_SIZE); Ok(()) } /// Anchors the constants used in Sequoia with the ones from /// Nettle. #[test] fn block_size() -> Result<()> { assert_eq!(SymmetricAlgorithm::TripleDES.block_size()?, cipher::Des3::BLOCK_SIZE); assert_eq!(SymmetricAlgorithm::CAST5.block_size()?, cipher::Cast128::BLOCK_SIZE); assert_eq!(SymmetricAlgorithm::Blowfish.block_size()?, cipher::Blowfish::BLOCK_SIZE); assert_eq!(SymmetricAlgorithm::AES128.block_size()?, cipher::Aes128::BLOCK_SIZE); assert_eq!(SymmetricAlgorithm::AES192.block_size()?, cipher::Aes192::BLOCK_SIZE); assert_eq!(SymmetricAlgorithm::AES256.block_size()?, cipher::Aes256::BLOCK_SIZE); assert_eq!(SymmetricAlgorithm::Twofish.block_size()?, cipher::Twofish::BLOCK_SIZE); assert_eq!(SymmetricAlgorithm::Camellia128.block_size()?, cipher::Camellia128::BLOCK_SIZE); assert_eq!(SymmetricAlgorithm::Camellia192.block_size()?, cipher::Camellia192::BLOCK_SIZE); assert_eq!(SymmetricAlgorithm::Camellia256.block_size()?, cipher::Camellia256::BLOCK_SIZE); Ok(()) } } ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/src/crypto/backend/nettle.rs��������������������������������������������������0000644�0000000�0000000�00000006043�10461020230�0020301�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������//! Implementation of Sequoia crypto API using the Nettle cryptographic library. use crate::types::*; use nettle::random::{Random, Yarrow}; pub mod aead; pub mod asymmetric; pub mod ecdh; pub mod hash; pub mod kdf; pub mod symmetric; pub struct Backend(()); impl super::interface::Backend for Backend { fn backend() -> String { let (major, minor) = nettle::version(); format!( "Nettle {}.{} (Cv448: {:?}, OCB: {:?})", major, minor, nettle::curve448::IS_SUPPORTED, nettle::aead::OCB_IS_SUPPORTED, ) } fn random(buf: &mut [u8]) -> crate::Result<()> { Yarrow::default().random(buf); Ok(()) } } impl AEADAlgorithm { /// Returns the best AEAD mode supported by the backend. /// /// This SHOULD return OCB, which is the mandatory-to-implement /// algorithm and the most performing one, but fall back to any /// supported algorithm. pub(crate) const fn const_default() -> AEADAlgorithm { if nettle::aead::OCB_IS_SUPPORTED { AEADAlgorithm::OCB } else { AEADAlgorithm::EAX } } pub(crate) fn is_supported_by_backend(&self) -> bool { use self::AEADAlgorithm::*; match &self { EAX => true, OCB => nettle::aead::OCB_IS_SUPPORTED, GCM => true, Private(_) | Unknown(_) => false, } } #[cfg(test)] pub(crate) fn supports_symmetric_algo(&self, algo: &SymmetricAlgorithm) -> bool { match &self { AEADAlgorithm::EAX => match algo { SymmetricAlgorithm::AES128 | SymmetricAlgorithm::AES192 | SymmetricAlgorithm::AES256 | SymmetricAlgorithm::Twofish | SymmetricAlgorithm::Camellia128 | SymmetricAlgorithm::Camellia192 | SymmetricAlgorithm::Camellia256 => true, _ => false, }, AEADAlgorithm::OCB => match algo { SymmetricAlgorithm::AES128 | SymmetricAlgorithm::AES192 | SymmetricAlgorithm::AES256 | SymmetricAlgorithm::Twofish | SymmetricAlgorithm::Camellia128 | SymmetricAlgorithm::Camellia192 | SymmetricAlgorithm::Camellia256 => true, _ => false, }, AEADAlgorithm::GCM => match algo { SymmetricAlgorithm::AES128 | SymmetricAlgorithm::AES192 | SymmetricAlgorithm::AES256 | SymmetricAlgorithm::Twofish | SymmetricAlgorithm::Camellia128 | SymmetricAlgorithm::Camellia192 | SymmetricAlgorithm::Camellia256 => true, _ => false, }, _ => false } } } ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/src/crypto/backend/openssl/aead.rs��������������������������������������������0000644�0000000�0000000�00000012036�10461020230�0021362�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������//! Implementation of AEAD using OpenSSL cryptographic library. use crate::{Error, Result}; use crate::crypto::aead::{Aead, CipherOp}; use crate::types::{AEADAlgorithm, SymmetricAlgorithm}; use openssl::cipher::Cipher; use openssl::cipher_ctx::CipherCtx; struct OpenSslContext { ctx: CipherCtx, digest_size: usize, } impl Aead for OpenSslContext { fn encrypt_seal(&mut self, dst: &mut [u8], src: &[u8]) -> Result<()> { debug_assert_eq!(dst.len(), src.len() + self.digest_size()); // SAFETY: Process completely one full chunk. Since `update` // is not being called again with partial block info and the // cipher is finalized afterwards these two calls are safe. let size = unsafe { self.ctx.cipher_update_unchecked(src, Some(dst))? }; unsafe { self.ctx.cipher_final_unchecked(&mut dst[size..])? }; self.ctx.tag(&mut dst[src.len()..])?; Ok(()) } fn decrypt_verify(&mut self, dst: &mut [u8], src: &[u8]) -> Result<()> { debug_assert!(src.len() >= self.digest_size()); debug_assert_eq!(dst.len() + self.digest_size(), src.len()); // Split src into ciphertext and tag. let l = self.digest_size(); let ciphertext = &src[..src.len().saturating_sub(l)]; let tag = &src[src.len().saturating_sub(l)..]; // SAFETY: Process completely one full chunk. Since `update` // is not being called again with partial block info and the // cipher is finalized afterwards these two calls are safe. let size = unsafe { self.ctx.cipher_update_unchecked(ciphertext, Some(dst))? }; self.ctx.set_tag(tag)?; unsafe { self.ctx.cipher_final_unchecked(&mut dst[size..])? }; Ok(()) } fn digest_size(&self) -> usize { self.digest_size } } impl crate::seal::Sealed for OpenSslContext {} impl AEADAlgorithm { pub(crate) fn context( &self, sym_algo: SymmetricAlgorithm, key: &[u8], aad: &[u8], nonce: &[u8], op: CipherOp, ) -> Result<Box<dyn Aead>> { match self { #[cfg(not(osslconf = "OPENSSL_NO_OCB"))] AEADAlgorithm::OCB => { let cipher = match sym_algo { SymmetricAlgorithm::AES128 => Cipher::aes_128_ocb(), SymmetricAlgorithm::AES192 => Cipher::aes_192_ocb(), SymmetricAlgorithm::AES256 => Cipher::aes_256_ocb(), _ => return Err(Error::UnsupportedSymmetricAlgorithm(sym_algo).into()), }; let mut ctx = CipherCtx::new()?; match op { CipherOp::Encrypt => ctx.encrypt_init(Some(cipher), Some(key), None)?, CipherOp::Decrypt => ctx.decrypt_init(Some(cipher), Some(key), None)?, } // We have to set the IV length before supplying the // IV. Otherwise, it will be silently truncated. ctx.set_iv_length(self.nonce_size()?)?; match op { CipherOp::Encrypt => ctx.encrypt_init(None, None, Some(nonce))?, CipherOp::Decrypt => ctx.decrypt_init(None, None, Some(nonce))?, } ctx.set_padding(false); ctx.cipher_update(aad, None)?; Ok(Box::new(OpenSslContext { ctx, digest_size: self.digest_size()?, })) }, AEADAlgorithm::GCM => { let cipher = match sym_algo { SymmetricAlgorithm::AES128 => Cipher::aes_128_gcm(), SymmetricAlgorithm::AES192 => Cipher::aes_192_gcm(), SymmetricAlgorithm::AES256 => Cipher::aes_256_gcm(), _ => return Err(Error::UnsupportedSymmetricAlgorithm(sym_algo).into()), }; let mut ctx = CipherCtx::new()?; match op { CipherOp::Encrypt => ctx.encrypt_init(Some(cipher), Some(key), None)?, CipherOp::Decrypt => ctx.decrypt_init(Some(cipher), Some(key), None)?, } // We have to set the IV length before supplying the // IV. Otherwise, it will be silently truncated. ctx.set_iv_length(self.nonce_size()?)?; match op { CipherOp::Encrypt => ctx.encrypt_init(None, None, Some(nonce))?, CipherOp::Decrypt => ctx.decrypt_init(None, None, Some(nonce))?, } ctx.set_padding(false); ctx.cipher_update(aad, None)?; Ok(Box::new(OpenSslContext { ctx, digest_size: self.digest_size()?, })) }, _ => Err(Error::UnsupportedAEADAlgorithm(*self).into()), } } } ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/src/crypto/backend/openssl/asymmetric.rs��������������������������������������0000644�0000000�0000000�00000056343�10461020230�0022656�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������use crate::{Error, Result}; use crate::crypto::asymmetric::KeyPair; use crate::crypto::backend::interface::Asymmetric; use crate::crypto::mpi; use crate::crypto::mpi::{ProtectedMPI, MPI}; use crate::crypto::mem::Protected; use crate::crypto::SessionKey; use crate::packet::key::{Key4, SecretParts}; use crate::packet::{key, Key}; use crate::types::{Curve, HashAlgorithm, PublicKeyAlgorithm}; use std::convert::{TryFrom, TryInto}; use std::time::SystemTime; use openssl::bn::{BigNum, BigNumRef, BigNumContext}; use openssl::derive::Deriver; use openssl::ec::{EcGroup, EcKey, EcPoint, PointConversionForm}; use openssl::ecdsa::EcdsaSig; use openssl::nid::Nid; use openssl::pkey::PKey; use openssl::pkey_ctx::PkeyCtx; use openssl::rsa::{Padding, Rsa, RsaPrivateKeyBuilder}; use openssl::sign::Signer as OpenSslSigner; use openssl::sign::Verifier; impl Asymmetric for super::Backend { fn supports_algo(algo: PublicKeyAlgorithm) -> bool { use PublicKeyAlgorithm::*; #[allow(deprecated)] match algo { X25519 | Ed25519 | X448 | Ed448 | RSAEncryptSign | RSAEncrypt | RSASign => true, DSA => true, ECDH | ECDSA | EdDSA => true, ElGamalEncrypt | ElGamalEncryptSign | Private(_) | Unknown(_) => false, } } fn supports_curve(curve: &Curve) -> bool { if matches!(curve, Curve::Ed25519 | Curve::Cv25519) { // 25519-based algorithms are special-cased and supported true } else { // the rest of EC algorithms are supported via the same // codepath if let Ok(nid) = openssl::nid::Nid::try_from(curve) { openssl::ec::EcGroup::from_curve_name(nid).is_ok() } else { false } } } fn x25519_generate_key() -> Result<(Protected, [u8; 32])> { let pair = openssl::pkey::PKey::generate_x25519()?; Ok((pair.raw_private_key()?.into(), pair.raw_public_key()?.as_slice().try_into()?)) } fn x25519_derive_public(secret: &Protected) -> Result<[u8; 32]> { let key = PKey::private_key_from_raw_bytes( secret, openssl::pkey::Id::X25519)?; Ok(key.raw_public_key()?.as_slice().try_into()?) } fn x25519_shared_point(secret: &Protected, public: &[u8; 32]) -> Result<Protected> { let public = PKey::public_key_from_raw_bytes( public, openssl::pkey::Id::X25519)?; let secret = PKey::private_key_from_raw_bytes( secret, openssl::pkey::Id::X25519)?; let mut deriver = Deriver::new(&secret)?; deriver.set_peer(&public)?; Ok(deriver.derive_to_vec()?.into()) } fn x448_generate_key() -> Result<(Protected, [u8; 56])> { let pair = openssl::pkey::PKey::generate_x448()?; Ok((pair.raw_private_key()?.into(), pair.raw_public_key()?.as_slice().try_into()?)) } fn x448_derive_public(secret: &Protected) -> Result<[u8; 56]> { let key = PKey::private_key_from_raw_bytes( secret, openssl::pkey::Id::X448)?; Ok(key.raw_public_key()?.as_slice().try_into()?) } fn x448_shared_point(secret: &Protected, public: &[u8; 56]) -> Result<Protected> { let public = PKey::public_key_from_raw_bytes( public, openssl::pkey::Id::X448)?; let secret = PKey::private_key_from_raw_bytes( secret, openssl::pkey::Id::X448)?; let mut deriver = Deriver::new(&secret)?; deriver.set_peer(&public)?; Ok(deriver.derive_to_vec()?.into()) } fn ed25519_generate_key() -> Result<(Protected, [u8; 32])> { let pair = openssl::pkey::PKey::generate_ed25519()?; Ok((pair.raw_private_key()?.into(), pair.raw_public_key()?.as_slice().try_into()?)) } fn ed25519_derive_public(secret: &Protected) -> Result<[u8; 32]> { let key = PKey::private_key_from_raw_bytes( secret, openssl::pkey::Id::ED25519)?; Ok(key.raw_public_key()?.as_slice().try_into()?) } fn ed25519_sign(secret: &Protected, _public: &[u8; 32], digest: &[u8]) -> Result<[u8; 64]> { let key = PKey::private_key_from_raw_bytes( secret, openssl::pkey::Id::ED25519)?; let mut signer = OpenSslSigner::new_without_digest(&key)?; Ok(signer.sign_oneshot_to_vec(digest)?.as_slice().try_into()?) } fn ed25519_verify(public: &[u8; 32], digest: &[u8], signature: &[u8; 64]) -> Result<bool> { let key = PKey::public_key_from_raw_bytes( public, openssl::pkey::Id::ED25519)?; let mut verifier = Verifier::new_without_digest(&key)?; Ok(verifier.verify_oneshot(signature, digest)?) } fn ed448_generate_key() -> Result<(Protected, [u8; 57])> { let pair = openssl::pkey::PKey::generate_ed448()?; Ok((pair.raw_private_key()?.into(), pair.raw_public_key()?.as_slice().try_into()?)) } fn ed448_derive_public(secret: &Protected) -> Result<[u8; 57]> { let key = PKey::private_key_from_raw_bytes( secret, openssl::pkey::Id::ED448)?; Ok(key.raw_public_key()?.as_slice().try_into()?) } fn ed448_sign(secret: &Protected, _public: &[u8; 57], digest: &[u8]) -> Result<[u8; 114]> { let key = PKey::private_key_from_raw_bytes( secret, openssl::pkey::Id::ED448)?; let mut signer = OpenSslSigner::new_without_digest(&key)?; Ok(signer.sign_oneshot_to_vec(digest)?.as_slice().try_into()?) } fn ed448_verify(public: &[u8; 57], digest: &[u8], signature: &[u8; 114]) -> Result<bool> { let key = PKey::public_key_from_raw_bytes( public, openssl::pkey::Id::ED448)?; let mut verifier = Verifier::new_without_digest(&key)?; Ok(verifier.verify_oneshot(signature, digest)?) } fn dsa_generate_key(p_bits: usize) -> Result<(MPI, MPI, MPI, MPI, ProtectedMPI)> { use openssl::dsa::*; let key = Dsa::<openssl::pkey::Private>::generate(p_bits.try_into()?)?; Ok((key.p().into(), key.q().into(), key.g().into(), key.pub_key().into(), key.priv_key().into())) } } impl TryFrom<&ProtectedMPI> for BigNum { type Error = anyhow::Error; fn try_from(mpi: &ProtectedMPI) -> std::result::Result<BigNum, anyhow::Error> { let mut bn = BigNum::new_secure()?; bn.copy_from_slice(mpi.value())?; Ok(bn) } } impl From<&BigNumRef> for ProtectedMPI { fn from(bn: &BigNumRef) -> Self { bn.to_vec().into() } } impl From<BigNum> for ProtectedMPI { fn from(bn: BigNum) -> Self { bn.to_vec().into() } } impl From<BigNum> for MPI { fn from(bn: BigNum) -> Self { bn.to_vec().into() } } impl TryFrom<&MPI> for BigNum { type Error = anyhow::Error; fn try_from(mpi: &MPI) -> std::result::Result<BigNum, anyhow::Error> { Ok(BigNum::from_slice(mpi.value())?) } } impl From<&BigNumRef> for MPI { fn from(bn: &BigNumRef) -> Self { bn.to_vec().into() } } impl TryFrom<&Curve> for Nid { type Error = crate::Error; fn try_from(curve: &Curve) -> std::result::Result<Nid, crate::Error> { Ok(match curve { Curve::NistP256 => Nid::X9_62_PRIME256V1, Curve::NistP384 => Nid::SECP384R1, Curve::NistP521 => Nid::SECP521R1, Curve::BrainpoolP256 => Nid::BRAINPOOL_P256R1, Curve::BrainpoolP384 => Nid::BRAINPOOL_P384R1, Curve::BrainpoolP512 => Nid::BRAINPOOL_P512R1, Curve::Ed25519 | // Handled differently. Curve::Cv25519 | // Handled differently. Curve::Unknown(_) => return Err(crate::Error::UnsupportedEllipticCurve(curve.clone()).into()), }) } } impl KeyPair { pub(crate) fn sign_backend(&self, secret: &mpi::SecretKeyMaterial, hash_algo: HashAlgorithm, digest: &[u8]) -> Result<mpi::Signature> { use crate::PublicKeyAlgorithm::*; #[allow(deprecated)] match (self.public().pk_algo(), self.public().mpis(), secret) { ( RSAEncryptSign, mpi::PublicKey::RSA { e, n }, mpi::SecretKeyMaterial::RSA { p, q, d, .. }, ) | ( RSASign, mpi::PublicKey::RSA { e, n }, mpi::SecretKeyMaterial::RSA { p, q, d, .. }, ) => { let key = RsaPrivateKeyBuilder::new(n.try_into()?, e.try_into()?, d.try_into()?)? .set_factors(p.try_into()?, q.try_into()?)? .build(); let key = PKey::from_rsa(key)?; let mut signature: Vec<u8> = vec![]; const MAX_OID_SIZE: usize = 20; let mut v = Vec::with_capacity(MAX_OID_SIZE + digest.len()); v.extend(hash_algo.oid()?); v.extend(digest); let mut ctx = PkeyCtx::new(&key)?; ctx.sign_init()?; ctx.sign_to_vec(&v, &mut signature)?; Ok(mpi::Signature::RSA { s: signature.into(), }) } ( PublicKeyAlgorithm::DSA, mpi::PublicKey::DSA { p, q, g, y }, mpi::SecretKeyMaterial::DSA { x }, ) => { use openssl::dsa::{Dsa, DsaSig}; let dsa = Dsa::from_private_components( p.try_into()?, q.try_into()?, g.try_into()?, x.try_into()?, y.try_into()?, )?; let key: PKey<_> = dsa.try_into()?; let mut ctx = PkeyCtx::new(&key)?; ctx.sign_init()?; let mut signature = vec![]; ctx.sign_to_vec(&digest, &mut signature)?; let signature = DsaSig::from_der(&signature)?; Ok(mpi::Signature::DSA { r: signature.r().to_vec().into(), s: signature.s().to_vec().into(), }) } ( PublicKeyAlgorithm::ECDSA, mpi::PublicKey::ECDSA { curve, q }, mpi::SecretKeyMaterial::ECDSA { scalar }, ) => { let nid = curve.try_into()?; let group = EcGroup::from_curve_name(nid)?; let mut ctx = BigNumContext::new()?; let point = EcPoint::from_bytes(&group, q.value(), &mut ctx)?; let mut private = BigNum::new_secure()?; private.copy_from_slice(scalar.value())?; let key = EcKey::from_private_components(&group, &private, &point)?; let sig = EcdsaSig::sign(digest, &key)?; Ok(mpi::Signature::ECDSA { r: sig.r().into(), s: sig.s().into(), }) } (pk_algo, _, _) => Err(crate::Error::InvalidOperation(format!( "unsupported combination of algorithm {:?}, key {:?}, \ and secret key {:?} by OpenSSL backend", pk_algo, self.public(), self.secret() )) .into()), } } } impl KeyPair { pub(crate) fn decrypt_backend( &self, secret: &mpi::SecretKeyMaterial, ciphertext: &mpi::Ciphertext, plaintext_len: Option<usize>, ) -> Result<SessionKey> { use crate::crypto::mpi::PublicKey; Ok(match (self.public().mpis(), secret, ciphertext) { ( PublicKey::RSA { ref e, ref n }, mpi::SecretKeyMaterial::RSA { ref p, ref q, ref d, .. }, mpi::Ciphertext::RSA { ref c }, ) => { let key = RsaPrivateKeyBuilder::new(n.try_into()?, e.try_into()?, d.try_into()?)? .set_factors(p.try_into()?, q.try_into()?)? .build(); let mut buf: Protected = vec![0; key.size().try_into()?].into(); let encrypted_len = key.private_decrypt(c.value(), &mut buf, Padding::PKCS1)?; buf[..encrypted_len].into() } ( PublicKey::ECDH { .. }, mpi::SecretKeyMaterial::ECDH { .. }, mpi::Ciphertext::ECDH { .. }, ) => crate::crypto::ecdh::decrypt(self.public(), secret, ciphertext, plaintext_len)?, (public, secret, ciphertext) => { return Err(crate::Error::InvalidOperation(format!( "unsupported combination of key pair {:?}/{:?} \ and ciphertext {:?}", public, secret, ciphertext )) .into()) } }) } } impl<P: key::KeyParts, R: key::KeyRole> Key<P, R> { /// Encrypts the given data with this key. pub(crate) fn encrypt_backend(&self, data: &SessionKey) -> Result<mpi::Ciphertext> { use PublicKeyAlgorithm::*; #[allow(deprecated)] match self.pk_algo() { RSAEncryptSign | RSAEncrypt => match self.mpis() { mpi::PublicKey::RSA { e, n } => { // The ciphertext has the length of the modulus. let ciphertext_len = n.value().len(); if data.len() + 11 > ciphertext_len { return Err(crate::Error::InvalidArgument( "Plaintext data too large".into(), ) .into()); } let e = BigNum::from_slice(e.value())?; let n = BigNum::from_slice(n.value())?; let rsa = Rsa::<openssl::pkey::Public>::from_public_components(n, e)?; // The ciphertext has the length of the modulus. let mut buf = vec![0; rsa.size().try_into()?]; rsa.public_encrypt(data, &mut buf, Padding::PKCS1)?; Ok(mpi::Ciphertext::RSA { c: buf.into(), }) } pk => Err(crate::Error::MalformedPacket(format!( "Key: Expected RSA public key, got {:?}", pk )) .into()), }, ECDH => crate::crypto::ecdh::encrypt(self.parts_as_public(), data), RSASign | DSA | ECDSA | EdDSA | Ed25519 | Ed448 => Err(Error::InvalidOperation( format!("{} is not an encryption algorithm", self.pk_algo()) ).into()), ElGamalEncrypt | ElGamalEncryptSign | X25519 | X448 | Private(_) | Unknown(_) => Err(Error::UnsupportedPublicKeyAlgorithm(self.pk_algo()).into()), } } /// Verifies the given signature. pub(crate) fn verify_backend( &self, sig: &mpi::Signature, hash_algo: HashAlgorithm, digest: &[u8], ) -> Result<()> { let ok = match (self.mpis(), sig) { (mpi::PublicKey::RSA { e, n }, mpi::Signature::RSA { s }) => { let e = BigNum::from_slice(e.value())?; let n = BigNum::from_slice(n.value())?; let keypair = Rsa::<openssl::pkey::Public>::from_public_components(n, e)?; let keypair = PKey::from_rsa(keypair)?; let signature = s.value(); let mut v = vec![]; v.extend(hash_algo.oid()?); v.extend(digest); let mut ctx = PkeyCtx::new(&keypair)?; ctx.verify_init()?; ctx.verify(&v, signature)? } (mpi::PublicKey::DSA { p, q, g, y }, mpi::Signature::DSA { r, s }) => { use openssl::dsa::{Dsa, DsaSig}; let dsa = Dsa::from_public_components( p.try_into()?, q.try_into()?, g.try_into()?, y.try_into()?, )?; let key: PKey<_> = dsa.try_into()?; let r = r.try_into()?; let s = s.try_into()?; let signature = DsaSig::from_private_components(r, s)?; let mut ctx = PkeyCtx::new(&key)?; ctx.verify_init()?; ctx.verify(&digest, &signature.to_der()?)? } (mpi::PublicKey::ECDSA { curve, q }, mpi::Signature::ECDSA { s, r }) => { let nid = curve.try_into()?; let group = EcGroup::from_curve_name(nid)?; let mut ctx = BigNumContext::new()?; let point = EcPoint::from_bytes(&group, q.value(), &mut ctx)?; let key = EcKey::from_public_key(&group, &point)?; let sig = EcdsaSig::from_private_components( r.try_into()?, s.try_into()?, )?; sig.verify(digest, &key)? } _ => { return Err(crate::Error::MalformedPacket(format!( "unsupported combination of key {} and signature {:?}.", self.pk_algo(), sig )) .into()) } }; if ok { Ok(()) } else { Err(crate::Error::ManipulatedMessage.into()) } } } impl<R> Key4<SecretParts, R> where R: key::KeyRole, { /// Creates a new OpenPGP public key packet for an existing RSA key. /// /// The RSA key will use public exponent `e` and modulo `n`. The key will /// have its creation date set to `ctime` or the current time if `None` /// is given. pub fn import_secret_rsa<T>(d: &[u8], p: &[u8], q: &[u8], ctime: T) -> Result<Self> where T: Into<Option<SystemTime>>, { // RFC 4880: `p < q` let (p, q) = crate::crypto::rsa_sort_raw_pq(p, q); let mut big_p = BigNum::new_secure()?; big_p.copy_from_slice(p)?; let mut big_q = BigNum::new_secure()?; big_q.copy_from_slice(q)?; let n = &big_p * &big_q; let mut one = BigNum::new_secure()?; one.copy_from_slice(&[1])?; let big_phi = &(&big_p - &one) * &(&big_q - &one); let mut ctx = BigNumContext::new_secure()?; let mut e = BigNum::new_secure()?; let mut d_bn = BigNum::new_secure()?; d_bn.copy_from_slice(d)?; e.mod_inverse(&d_bn, &big_phi, &mut ctx)?; // e ≡ d⁻¹ (mod 𝜙) let mut u = BigNum::new_secure()?; u.mod_inverse(&big_p, &big_q, &mut ctx)?; // RFC 4880: u ≡ p⁻¹ (mod q) Self::with_secret( ctime.into().unwrap_or_else(crate::now), PublicKeyAlgorithm::RSAEncryptSign, mpi::PublicKey::RSA { e: e.into(), n: n.into(), }, mpi::SecretKeyMaterial::RSA { d: d_bn.into(), p: p.into(), q: q.into(), u: u.into(), } .into(), ) } /// Generates a new RSA key with a public modulus of size `bits`. pub fn generate_rsa(bits: usize) -> Result<Self> { let key = Rsa::generate(bits.try_into()?)?; let e = key.e(); let n = key.n(); let d = key.d(); let p = key .p() .ok_or_else(|| crate::Error::InvalidOperation("p".into()))?; let q = key .q() .ok_or_else(|| crate::Error::InvalidOperation("q".into()))?; // RFC 4880: `p < q` let (p, q) = rsa_sort_pq(p, q); let mut ctx = BigNumContext::new_secure()?; let mut u = BigNum::new_secure()?; u.mod_inverse(p, q, &mut ctx)?; Self::with_secret( crate::now(), PublicKeyAlgorithm::RSAEncryptSign, mpi::PublicKey::RSA { e: e.into(), n: n.into(), }, mpi::SecretKeyMaterial::RSA { d: d.into(), p: p.into(), q: q.into(), u: u.into(), } .into(), ) } /// Generates a new ECC key over `curve`. /// /// If `for_signing` is false a ECDH key, if it's true either a /// EdDSA or ECDSA key is generated. Giving `for_signing == true` and /// `curve == Cv25519` will produce an error. Likewise /// `for_signing == false` and `curve == Ed25519` will produce an error. pub(crate) fn generate_ecc_backend(for_signing: bool, curve: Curve) -> Result<(PublicKeyAlgorithm, mpi::PublicKey, mpi::SecretKeyMaterial)> { let nid = (&curve).try_into()?; let group = EcGroup::from_curve_name(nid)?; let key = EcKey::generate(&group)?; let hash = crate::crypto::ecdh::default_ecdh_kdf_hash(&curve); let sym = crate::crypto::ecdh::default_ecdh_kek_cipher(&curve); let mut ctx = BigNumContext::new()?; let q = MPI::new(&key.public_key().to_bytes( &group, PointConversionForm::UNCOMPRESSED, &mut ctx, )?); let scalar = key.private_key().to_vec().into(); if for_signing { Ok(( PublicKeyAlgorithm::ECDSA, mpi::PublicKey::ECDSA { curve, q }, mpi::SecretKeyMaterial::ECDSA { scalar }, )) } else { Ok(( PublicKeyAlgorithm::ECDH, mpi::PublicKey::ECDH { curve, q, hash, sym, }, mpi::SecretKeyMaterial::ECDH { scalar }, )) } } } /// Given the secret prime values `p` and `q`, returns the pair of /// primes so that the smaller one comes first. /// /// Section 5.5.3 of RFC4880 demands that `p < q`. This function can /// be used to order `p` and `q` accordingly. /// /// Note: even though this function seems trivial, we introduce it as /// explicit abstraction. The reason is that the function's /// expression also "works" (as in it compiles) for byte slices, but /// does the wrong thing, see [`crate::crypto::rsa_sort_raw_pq`]. fn rsa_sort_pq<'a>(p: &'a BigNumRef, q: &'a BigNumRef) -> (&'a BigNumRef, &'a BigNumRef) { if p < q { (p, q) } else { (q, p) } } ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/src/crypto/backend/openssl/ecdh.rs��������������������������������������������0000644�0000000�0000000�00000006465�10461020230�0021404�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������//! Elliptic Curve Diffie-Hellman. use std::convert::{TryFrom, TryInto}; use crate::crypto::ecdh::{decrypt_unwrap, encrypt_wrap}; use crate::crypto::mpi; use crate::crypto::mpi::{Ciphertext, SecretKeyMaterial}; use crate::crypto::SessionKey; use crate::packet::{key, Key}; use crate::types::Curve; use crate::{Error, Result}; use openssl::bn::{BigNum, BigNumContext}; use openssl::derive::Deriver; use openssl::ec::{EcGroup, EcKey, EcPoint, PointConversionForm}; use openssl::pkey::PKey; /// Wraps a session key using Elliptic Curve Diffie-Hellman. pub fn encrypt<R>( recipient: &Key<key::PublicParts, R>, session_key: &SessionKey, ) -> Result<Ciphertext> where R: key::KeyRole, { let (curve, q) = match recipient.mpis() { mpi::PublicKey::ECDH { curve, q, .. } => (curve, q), _ => return Err(Error::InvalidArgument("Expected an ECDHPublicKey".into()).into()), }; if curve == &Curve::Cv25519 { return Err(Error::InvalidArgument("implemented elsewhere".into()).into()); } let nid = curve.try_into()?; let group = EcGroup::from_curve_name(nid)?; let mut ctx = BigNumContext::new()?; let point = EcPoint::from_bytes(&group, q.value(), &mut ctx)?; let recipient_key = EcKey::from_public_key(&group, &point)?; let recipient_key = PKey::<_>::try_from(recipient_key)?; let key = EcKey::generate(&group)?; let q = mpi::MPI::new(&key.public_key().to_bytes( &group, PointConversionForm::UNCOMPRESSED, &mut ctx, )?); let key = PKey::<_>::try_from(key)?; let mut deriver = Deriver::new(&key)?; deriver.set_peer(&recipient_key)?; let secret = deriver.derive_to_vec()?.into(); encrypt_wrap(recipient, session_key, q, &secret) } /// Unwraps a session key using Elliptic Curve Diffie-Hellman. pub fn decrypt<R>( recipient: &Key<key::PublicParts, R>, recipient_sec: &SecretKeyMaterial, ciphertext: &Ciphertext, plaintext_len: Option<usize>, ) -> Result<SessionKey> where R: key::KeyRole, { let (curve, scalar, e, q) = match (recipient.mpis(), recipient_sec, ciphertext) { ( mpi::PublicKey::ECDH { ref curve, ref q, .. }, SecretKeyMaterial::ECDH { ref scalar }, Ciphertext::ECDH { ref e, .. }, ) => (curve, scalar, e, q), _ => return Err(Error::InvalidArgument("Expected an ECDHPublicKey".into()).into()), }; if curve == &Curve::Cv25519 { return Err(Error::InvalidArgument("implemented elsewhere".into()).into()); } let nid = curve.try_into()?; let group = EcGroup::from_curve_name(nid)?; let mut ctx = BigNumContext::new()?; let point = EcPoint::from_bytes(&group, e.value(), &mut ctx)?; let public_point = EcPoint::from_bytes(&group, q.value(), &mut ctx)?; let scalar = BigNum::from_slice(scalar.value())?; let key = EcKey::from_private_components(&group, &scalar, &public_point)?; let recipient_key = EcKey::from_public_key(&group, &point)?; let recipient_key = PKey::<_>::try_from(recipient_key)?; let key = PKey::<_>::try_from(key)?; let mut deriver = Deriver::new(&key)?; deriver.set_peer(&recipient_key)?; let secret = deriver.derive_to_vec()?.into(); decrypt_unwrap(recipient.role_as_unspecified(), &secret, ciphertext, plaintext_len) } �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/src/crypto/backend/openssl/hash.rs��������������������������������������������0000644�0000000�0000000�00000005127�10461020230�0021416�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������use crate::crypto::hash::Digest; use crate::types::HashAlgorithm; use crate::Result; use openssl::error::ErrorStack; use openssl::hash::{Hasher, MessageDigest}; use openssl::nid::Nid; #[derive(Clone)] struct OpenSslDigest { hasher: Hasher, update_result: std::result::Result<(), ErrorStack>, } impl OpenSslDigest { fn new(algo: HashAlgorithm) -> Result<Self> { if let Some(md) = get_md(algo) { Ok(Self { update_result: Ok(()), hasher: Hasher::new(md)?, }) } else { Err(crate::Error::UnsupportedHashAlgorithm(algo).into()) } } } impl Digest for OpenSslDigest { fn update(&mut self, data: &[u8]) { if self.update_result.is_ok() { self.update_result = self.hasher.update(data); } } fn digest(&mut self, digest: &mut [u8]) -> Result<()> { self.update_result.clone()?; let result = self.hasher.finish()?; digest.copy_from_slice(&result[..digest.len()]); Ok(()) } } impl std::io::Write for OpenSslDigest { fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> { self.update(buf); Ok(buf.len()) } fn flush(&mut self) -> std::io::Result<()> { // Do nothing. Ok(()) } } fn get_md(algo: HashAlgorithm) -> Option<MessageDigest> { use HashAlgorithm::*; let nid = match algo { MD5 => Nid::MD5, RipeMD => Nid::RIPEMD160, SHA1 => Nid::SHA1, SHA256 => Nid::SHA256, SHA384 => Nid::SHA384, SHA512 => Nid::SHA512, SHA224 => Nid::SHA224, SHA3_256 => Nid::SHA3_256, SHA3_512 => Nid::SHA3_512, HashAlgorithm::Private(_) | HashAlgorithm::Unknown(_) => return None, }; MessageDigest::from_nid(nid) } impl HashAlgorithm { /// Whether Sequoia supports this algorithm. pub fn is_supported(self) -> bool { // Try to construct a digest. This indirectly looks up // digest's Nid and tries to initialize OpenSSL hasher. If // all of that succeeds the algorithm is supported by the // OpenSSL backend. OpenSslDigest::new(self).is_ok() } /// Creates a new hash context for this algorithm. /// /// # Errors /// /// Fails with `Error::UnsupportedHashAlgorithm` if Sequoia does /// not support this algorithm. See /// [`HashAlgorithm::is_supported`]. /// /// [`HashAlgorithm::is_supported`]: HashAlgorithm::is_supported() pub(crate) fn new_hasher(self) -> Result<Box<dyn Digest>> { Ok(Box::new(OpenSslDigest::new(self)?)) } } �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/src/crypto/backend/openssl/kdf.rs���������������������������������������������0000644�0000000�0000000�00000002212�10461020230�0021227�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������use openssl::{ md::Md, pkey::Id, pkey_ctx::PkeyCtx, }; use crate::{ Result, crypto::{ SessionKey, backend::interface::Kdf, }, }; impl Kdf for super::Backend { fn hkdf_sha256(ikm: &SessionKey, salt: Option<&[u8]>, info: &[u8], okm: &mut SessionKey) -> Result<()> { let mut pkey = PkeyCtx::new_id(Id::HKDF)?; pkey.derive_init()?; pkey.set_hkdf_md(Md::sha256())?; pkey.set_hkdf_key(&ikm)?; if let Some(salt) = salt { pkey.set_hkdf_salt(salt)?; } pkey.add_hkdf_info(info)?; pkey.derive(Some(okm))?; Ok(()) } fn hkdf_sha512(ikm: &SessionKey, salt: Option<&[u8]>, info: &[u8], okm: &mut SessionKey) -> Result<()> { let mut pkey = PkeyCtx::new_id(Id::HKDF)?; pkey.derive_init()?; pkey.set_hkdf_md(Md::sha512())?; pkey.set_hkdf_key(&ikm)?; if let Some(salt) = salt { pkey.set_hkdf_salt(salt)?; } pkey.add_hkdf_info(info)?; pkey.derive(Some(okm))?; Ok(()) } } ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/src/crypto/backend/openssl/symmetric.rs���������������������������������������0000644�0000000�0000000�00000015457�10461020230�0022516�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������use crate::crypto::symmetric::Mode; use crate::types::SymmetricAlgorithm; use crate::{Error, Result}; use openssl::cipher::{Cipher, CipherRef}; use openssl::cipher_ctx::CipherCtx; struct OpenSslMode { ctx: CipherCtx, } impl OpenSslMode { fn new(ctx: CipherCtx) -> Self { Self { ctx } } } impl Mode for OpenSslMode { fn block_size(&self) -> usize { self.ctx.block_size() } fn encrypt(&mut self, dst: &mut [u8], src: &[u8]) -> Result<()> { // Note that for cipher constructions that OpenSSL considers // "streaming" (such as CFB mode) the block size will be // always "1" instead of the real block size of the underlying // cipher. let block_size = self.ctx.block_size(); // SAFETY: If this is a block cipher we require the source length // to be exactly one block long not to populate OpenSSL's // cipher cache. if block_size > 1 && src.len() != block_size { return Err(Error::InvalidArgument("src need to be one block".into()).into()); } // SAFETY: `dst` must be big enough to hold decrypted data. if dst.len() < src.len() { return Err(Error::InvalidArgument( "dst need to be big enough to hold decrypted data".into(), ) .into()); } // SAFETY: This call is safe because either: this is a streaming cipher // (block_size == 1) or block cipher (block_size > 1) and `src` is // exactly one block and `dst` is big enough to hold the decrypted // data. unsafe { self.ctx.cipher_update_unchecked(src, Some(dst))?; } Ok(()) } fn decrypt(&mut self, dst: &mut [u8], src: &[u8]) -> Result<()> { self.encrypt(dst, src) } } impl SymmetricAlgorithm { /// Returns whether this algorithm is supported by the crypto backend. pub(crate) fn is_supported_by_backend(&self) -> bool { let cipher: &CipherRef = if let Ok(cipher) = (*self).make_cfb_cipher() { cipher } else { return false; }; let mut ctx = if let Ok(ctx) = CipherCtx::new() { ctx } else { return false; }; ctx.encrypt_init(Some(cipher), None, None).is_ok() } /// Creates a OpenSSL context for encrypting in CFB mode. pub(crate) fn make_encrypt_cfb(self, key: &[u8], iv: Vec<u8>) -> Result<Box<dyn Mode>> { let cipher = self.make_cfb_cipher()?; let mut ctx = CipherCtx::new()?; ctx.encrypt_init(Some(cipher), Some(key), Some(&iv))?; Ok(Box::new(OpenSslMode::new(ctx))) } /// Creates a OpenSSL context for decrypting in CFB mode. pub(crate) fn make_decrypt_cfb(self, key: &[u8], iv: Vec<u8>) -> Result<Box<dyn Mode>> { let cipher = self.make_cfb_cipher()?; let mut ctx = CipherCtx::new()?; ctx.decrypt_init(Some(cipher), Some(key), Some(&iv))?; Ok(Box::new(OpenSslMode::new(ctx))) } /// Creates a OpenSSL context for encrypting in ECB mode. pub(crate) fn make_encrypt_ecb(self, key: &[u8]) -> Result<Box<dyn Mode>> { let cipher = self.make_ecb_cipher()?; let mut ctx = CipherCtx::new()?; ctx.encrypt_init(Some(cipher), Some(key), None)?; ctx.set_padding(false); Ok(Box::new(OpenSslMode::new(ctx))) } /// Creates a OpenSSL context for decrypting in ECB mode. pub(crate) fn make_decrypt_ecb(self, key: &[u8]) -> Result<Box<dyn Mode>> { let cipher = self.make_ecb_cipher()?; let mut ctx = CipherCtx::new()?; ctx.decrypt_init(Some(cipher), Some(key), None)?; ctx.set_padding(false); Ok(Box::new(OpenSslMode::new(ctx))) } fn make_cfb_cipher(self) -> Result<&'static CipherRef> { #[allow(deprecated)] Ok(match self { #[cfg(not(osslconf = "OPENSSL_NO_IDEA"))] SymmetricAlgorithm::IDEA => Cipher::idea_cfb64(), SymmetricAlgorithm::AES128 => Cipher::aes_128_cfb128(), SymmetricAlgorithm::AES192 => Cipher::aes_192_cfb128(), SymmetricAlgorithm::AES256 => Cipher::aes_256_cfb128(), SymmetricAlgorithm::TripleDES => Cipher::des_ede3_cfb64(), #[cfg(not(osslconf = "OPENSSL_NO_CAMELLIA"))] SymmetricAlgorithm::Camellia128 => Cipher::camellia128_cfb128(), #[cfg(not(osslconf = "OPENSSL_NO_CAMELLIA"))] SymmetricAlgorithm::Camellia192 => Cipher::camellia192_cfb128(), #[cfg(not(osslconf = "OPENSSL_NO_CAMELLIA"))] SymmetricAlgorithm::Camellia256 => Cipher::camellia256_cfb128(), #[cfg(not(osslconf = "OPENSSL_NO_BF"))] SymmetricAlgorithm::Blowfish => Cipher::bf_cfb64(), #[cfg(not(osslconf = "OPENSSL_NO_CAST"))] SymmetricAlgorithm::CAST5 => Cipher::cast5_cfb64(), _ => return Err(Error::UnsupportedSymmetricAlgorithm(self))?, }) } fn make_ecb_cipher(self) -> Result<&'static CipherRef> { #[allow(deprecated)] Ok(match self { #[cfg(not(osslconf = "OPENSSL_NO_IDEA"))] SymmetricAlgorithm::IDEA => Cipher::idea_ecb(), SymmetricAlgorithm::AES128 => Cipher::aes_128_ecb(), SymmetricAlgorithm::AES192 => Cipher::aes_192_ecb(), SymmetricAlgorithm::AES256 => Cipher::aes_256_ecb(), SymmetricAlgorithm::TripleDES => Cipher::des_ecb(), #[cfg(not(osslconf = "OPENSSL_NO_CAMELLIA"))] SymmetricAlgorithm::Camellia128 => Cipher::camellia128_ecb(), #[cfg(not(osslconf = "OPENSSL_NO_CAMELLIA"))] SymmetricAlgorithm::Camellia192 => Cipher::camellia192_ecb(), #[cfg(not(osslconf = "OPENSSL_NO_CAMELLIA"))] SymmetricAlgorithm::Camellia256 => Cipher::camellia256_ecb(), #[cfg(not(osslconf = "OPENSSL_NO_BF"))] SymmetricAlgorithm::Blowfish => Cipher::bf_ecb(), #[cfg(not(osslconf = "OPENSSL_NO_CAST"))] SymmetricAlgorithm::CAST5 => Cipher::cast5_ecb(), _ => Err(Error::UnsupportedSymmetricAlgorithm(self))?, }) } } #[cfg(test)] mod tests { use super::*; /// Anchors the constants used in Sequoia with the ones from /// OpenSSL. #[test] fn key_size() -> Result<()> { for a in SymmetricAlgorithm::variants() { if let Ok(cipher) = a.make_cfb_cipher() { assert_eq!(a.key_size()?, cipher.key_length()); } } Ok(()) } /// Anchors the constants used in Sequoia with the ones from /// OpenSSL. #[test] fn block_size() -> Result<()> { for a in SymmetricAlgorithm::variants() { if let Ok(cipher) = a.make_ecb_cipher() { assert_eq!(a.block_size()?, cipher.block_size()); } } Ok(()) } } �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/src/crypto/backend/openssl.rs�������������������������������������������������0000644�0000000�0000000�00000004412�10461020230�0020467�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������//! Implementation of Sequoia crypto API using the OpenSSL cryptographic library. use crate::types::*; pub mod aead; pub mod asymmetric; pub mod ecdh; pub mod hash; pub mod kdf; pub mod symmetric; pub struct Backend(()); impl super::interface::Backend for Backend { fn backend() -> String { "OpenSSL".to_string() } fn random(buf: &mut [u8]) -> crate::Result<()> { openssl::rand::rand_bytes(buf)?; Ok(()) } } impl AEADAlgorithm { /// Returns the best AEAD mode supported by the backend. /// /// This SHOULD return OCB, which is the mandatory-to-implement /// algorithm and the most performing one, but fall back to any /// supported algorithm. pub(crate) const fn const_default() -> AEADAlgorithm { if cfg!(not(osslconf = "OPENSSL_NO_OCB")) { AEADAlgorithm::OCB } else { AEADAlgorithm::GCM } } pub(crate) fn is_supported_by_backend(&self) -> bool { match self { AEADAlgorithm::EAX => false, AEADAlgorithm::OCB => cfg!(not(osslconf = "OPENSSL_NO_OCB")), AEADAlgorithm::GCM => true, AEADAlgorithm::Private(_) | AEADAlgorithm::Unknown(_) => false, } } #[cfg(test)] pub(crate) fn supports_symmetric_algo(&self, algo: &SymmetricAlgorithm) -> bool { match &self { AEADAlgorithm::EAX => false, AEADAlgorithm::OCB => match algo { // OpenSSL supports OCB only with AES // see: https://wiki.openssl.org/index.php/OCB SymmetricAlgorithm::AES128 | SymmetricAlgorithm::AES192 | SymmetricAlgorithm::AES256 => true, _ => false, }, AEADAlgorithm::GCM => match algo { // OpenSSL supports GCM only with AES // see: https://wiki.openssl.org/index.php/GCM SymmetricAlgorithm::AES128 | SymmetricAlgorithm::AES192 | SymmetricAlgorithm::AES256 => true, _ => false, }, AEADAlgorithm::Private(_) | AEADAlgorithm::Unknown(_) => false, } } } ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/src/crypto/backend/rust/aead.rs�����������������������������������������������0000644�0000000�0000000�00000042035�10461020230�0020676�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������//! Implementation of AEAD using pure Rust cryptographic libraries. use std::cmp; use std::cmp::Ordering; use cipher::{BlockCipher, BlockDecrypt, BlockEncrypt, BlockSizeUser, KeyInit, Unsigned}; use cipher::consts::{U12, U15, U16}; use eax::online::{Eax, Encrypt, Decrypt}; use cipher::generic_array::GenericArray; use crate::{Error, Result}; use crate::crypto::aead::{Aead, CipherOp}; use crate::crypto::mem::secure_cmp; use crate::seal; use crate::types::{AEADAlgorithm, SymmetricAlgorithm}; use super::GenericArrayExt; /// Disables authentication checks. /// /// This is DANGEROUS, and is only useful for debugging problems with /// malformed AEAD-encrypted messages. const DANGER_DISABLE_AUTHENTICATION: bool = false; type TagLen = U16; impl<Cipher> Aead for Eax<Cipher, Encrypt, TagLen> where Cipher: BlockCipher<BlockSize = U16> + BlockEncrypt + Clone + KeyInit, { fn digest_size(&self) -> usize { TagLen::USIZE } fn encrypt_seal(&mut self, dst: &mut [u8], src: &[u8]) -> Result<()> { debug_assert_eq!(dst.len(), src.len() + self.digest_size()); let len = cmp::min(dst.len(), src.len()); dst[..len].copy_from_slice(&src[..len]); Self::encrypt(self, &mut dst[..len]); let tag = self.tag_clone(); dst[src.len()..].copy_from_slice(&tag[..]); Ok(()) } fn decrypt_verify(&mut self, _dst: &mut [u8], _src: &[u8]) -> Result<()> { panic!("AEAD decryption called in the encryption context") } } impl<Cipher> Aead for Eax<Cipher, Decrypt, TagLen> where Cipher: BlockCipher<BlockSize = U16> + BlockEncrypt + Clone + KeyInit, { fn digest_size(&self) -> usize { TagLen::USIZE } fn encrypt_seal(&mut self, _dst: &mut [u8], _src: &[u8]) -> Result<()> { panic!("AEAD encryption called in the decryption context") } fn decrypt_verify(&mut self, dst: &mut [u8], src: &[u8]) -> Result<()> { debug_assert_eq!(dst.len() + self.digest_size(), src.len()); // Split src into ciphertext and digest. let l = self.digest_size(); let digest = &src[src.len().saturating_sub(l)..]; let src = &src[..src.len().saturating_sub(l)]; let len = core::cmp::min(dst.len(), src.len()); dst[..len].copy_from_slice(&src[..len]); self.decrypt_unauthenticated_hazmat(&mut dst[..len]); let chunk_digest = self.tag_clone(); if secure_cmp(&chunk_digest[..], digest) != Ordering::Equal && ! DANGER_DISABLE_AUTHENTICATION { return Err(Error::ManipulatedMessage.into()); } Ok(()) } } impl<Cipher, Op> seal::Sealed for Eax<Cipher, Op, TagLen> where Cipher: BlockCipher<BlockSize = U16> + BlockEncrypt + Clone + KeyInit, Op: eax::online::CipherOp, {} struct Gcm<Cipher: BlockCipher + BlockSizeUser<BlockSize = U16> + BlockEncrypt> { cipher: aes_gcm::AesGcm<Cipher, U12>, nonce: GenericArray<u8, U12>, aad: Vec<u8>, } impl<Cipher> Aead for Gcm<Cipher> where Cipher: BlockCipher + BlockSizeUser<BlockSize = U16> + BlockEncrypt, { fn digest_size(&self) -> usize { TagLen::USIZE } fn encrypt_seal(&mut self, dst: &mut [u8], src: &[u8]) -> Result<()> { debug_assert_eq!(dst.len(), src.len() + self.digest_size()); use aes_gcm::AeadInPlace; let len = cmp::min(dst.len(), src.len()); dst[..len].copy_from_slice(&src[..len]); let tag = self.cipher.encrypt_in_place_detached(&self.nonce, &self.aad, &mut dst[..len])?; debug_assert_eq!(dst[len..].len(), tag.len()); let tag_len = cmp::min(dst[len..].len(), tag.len()); dst[len..len + tag_len].copy_from_slice(&tag[..tag_len]); Ok(()) } fn decrypt_verify(&mut self, dst: &mut [u8], src: &[u8]) -> Result<()> { debug_assert_eq!(dst.len() + self.digest_size(), src.len()); use aes_gcm::AeadInPlace; // Split src into ciphertext and digest. let len = src.len().saturating_sub(self.digest_size()); let digest = &src[len..]; let src = &src[..len]; debug_assert_eq!(dst.len(), src.len()); let len = core::cmp::min(dst.len(), src.len()); dst[..len].copy_from_slice(&src[..len]); self.cipher.decrypt_in_place_detached(&self.nonce, &self.aad, dst, digest.try_into()?)?; Ok(()) } } impl<'a, Cipher> seal::Sealed for Gcm<Cipher> where Cipher: BlockCipher + BlockSizeUser<BlockSize = U16> + BlockEncrypt, {} struct Ocb<Cipher: BlockCipher + BlockSizeUser<BlockSize = U16> + BlockDecrypt + BlockEncrypt> { cipher: ocb3::Ocb3<Cipher, U15>, nonce: GenericArray<u8, U15>, aad: Vec<u8>, } impl<Cipher> Aead for Ocb<Cipher> where Cipher: BlockCipher + BlockSizeUser<BlockSize = U16> + BlockDecrypt + BlockEncrypt, { fn digest_size(&self) -> usize { TagLen::USIZE } fn encrypt_seal(&mut self, dst: &mut [u8], src: &[u8]) -> Result<()> { debug_assert_eq!(dst.len(), src.len() + self.digest_size()); use ocb3::AeadInPlace; let len = cmp::min(dst.len(), src.len()); dst[..len].copy_from_slice(&src[..len]); let tag = self.cipher.encrypt_in_place_detached(&self.nonce, &self.aad, &mut dst[..len])?; debug_assert_eq!(dst[len..].len(), tag.len()); let tag_len = cmp::min(dst[len..].len(), tag.len()); dst[len..len + tag_len].copy_from_slice(&tag[..tag_len]); Ok(()) } fn decrypt_verify(&mut self, dst: &mut [u8], src: &[u8]) -> Result<()> { debug_assert_eq!(dst.len() + self.digest_size(), src.len()); use ocb3::AeadInPlace; // Split src into ciphertext and digest. let len = src.len().saturating_sub(self.digest_size()); let digest = &src[len..]; let src = &src[..len]; debug_assert_eq!(dst.len(), src.len()); let len = core::cmp::min(dst.len(), src.len()); dst[..len].copy_from_slice(&src[..len]); self.cipher.decrypt_in_place_detached(&self.nonce, &self.aad, dst, digest.try_into()?)?; Ok(()) } } impl<'a, Cipher> seal::Sealed for Ocb<Cipher> where Cipher: BlockCipher + BlockSizeUser<BlockSize = U16> + BlockDecrypt + BlockEncrypt, {} impl AEADAlgorithm { pub(crate) fn context( &self, sym_algo: SymmetricAlgorithm, key: &[u8], aad: &[u8], nonce: &[u8], op: CipherOp, ) -> Result<Box<dyn Aead>> { #[allow(deprecated)] match self { AEADAlgorithm::EAX => match sym_algo { SymmetricAlgorithm::AES128 => match op { CipherOp::Encrypt => { let mut ctx = Eax::<aes::Aes128, Encrypt>::with_key_and_nonce( GenericArray::try_from_slice(key)?, GenericArray::try_from_slice(nonce)?); ctx.update_assoc(aad); Ok(Box::new(ctx)) }, CipherOp::Decrypt => { let mut ctx = Eax::<aes::Aes128, Decrypt>::with_key_and_nonce( GenericArray::try_from_slice(key)?, GenericArray::try_from_slice(nonce)?); ctx.update_assoc(aad); Ok(Box::new(ctx)) }, }, SymmetricAlgorithm::AES192 => match op { CipherOp::Encrypt => { let mut ctx = Eax::<aes::Aes192, Encrypt>::with_key_and_nonce( GenericArray::try_from_slice(key)?, GenericArray::try_from_slice(nonce)?); ctx.update_assoc(aad); Ok(Box::new(ctx)) }, CipherOp::Decrypt => { let mut ctx = Eax::<aes::Aes192, Decrypt>::with_key_and_nonce( GenericArray::try_from_slice(key)?, GenericArray::try_from_slice(nonce)?); ctx.update_assoc(aad); Ok(Box::new(ctx)) }, }, SymmetricAlgorithm::AES256 => match op { CipherOp::Encrypt => { let mut ctx = Eax::<aes::Aes256, Encrypt>::with_key_and_nonce( GenericArray::try_from_slice(key)?, GenericArray::try_from_slice(nonce)?); ctx.update_assoc(aad); Ok(Box::new(ctx)) }, CipherOp::Decrypt => { let mut ctx = Eax::<aes::Aes256, Decrypt>::with_key_and_nonce( GenericArray::try_from_slice(key)?, GenericArray::try_from_slice(nonce)?); ctx.update_assoc(aad); Ok(Box::new(ctx)) }, }, SymmetricAlgorithm::Camellia128 => match op { CipherOp::Encrypt => { let mut ctx = Eax::<camellia::Camellia128, Encrypt>::with_key_and_nonce( GenericArray::try_from_slice(key)?, GenericArray::try_from_slice(nonce)?); ctx.update_assoc(aad); Ok(Box::new(ctx)) }, CipherOp::Decrypt => { let mut ctx = Eax::<camellia::Camellia128, Decrypt>::with_key_and_nonce( GenericArray::try_from_slice(key)?, GenericArray::try_from_slice(nonce)?); ctx.update_assoc(aad); Ok(Box::new(ctx)) }, }, SymmetricAlgorithm::Camellia192 => match op { CipherOp::Encrypt => { let mut ctx = Eax::<camellia::Camellia192, Encrypt>::with_key_and_nonce( GenericArray::try_from_slice(key)?, GenericArray::try_from_slice(nonce)?); ctx.update_assoc(aad); Ok(Box::new(ctx)) }, CipherOp::Decrypt => { let mut ctx = Eax::<camellia::Camellia192, Decrypt>::with_key_and_nonce( GenericArray::try_from_slice(key)?, GenericArray::try_from_slice(nonce)?); ctx.update_assoc(aad); Ok(Box::new(ctx)) }, }, SymmetricAlgorithm::Camellia256 => match op { CipherOp::Encrypt => { let mut ctx = Eax::<camellia::Camellia256, Encrypt>::with_key_and_nonce( GenericArray::try_from_slice(key)?, GenericArray::try_from_slice(nonce)?); ctx.update_assoc(aad); Ok(Box::new(ctx)) }, CipherOp::Decrypt => { let mut ctx = Eax::<camellia::Camellia256, Decrypt>::with_key_and_nonce( GenericArray::try_from_slice(key)?, GenericArray::try_from_slice(nonce)?); ctx.update_assoc(aad); Ok(Box::new(ctx)) }, }, | SymmetricAlgorithm::IDEA | SymmetricAlgorithm::TripleDES | SymmetricAlgorithm::CAST5 | SymmetricAlgorithm::Blowfish | SymmetricAlgorithm::Twofish | SymmetricAlgorithm::Private(_) | SymmetricAlgorithm::Unknown(_) | SymmetricAlgorithm::Unencrypted => Err(Error::UnsupportedSymmetricAlgorithm(sym_algo).into()), }, AEADAlgorithm::OCB => { use ocb3::{Ocb3, Nonce}; match sym_algo { SymmetricAlgorithm::AES128 => { let nonce = Nonce::try_from_slice(nonce)?; let cipher = Ocb3::<aes::Aes128, U15>::new_from_slice(key)?; Ok(Box::new(Ocb { cipher, nonce: *nonce, aad: aad.to_vec() })) }, SymmetricAlgorithm::AES192 => { let nonce = Nonce::try_from_slice(nonce)?; let cipher = Ocb3::<aes::Aes192, U15>::new_from_slice(key)?; Ok(Box::new(Ocb { cipher, nonce: *nonce, aad: aad.to_vec() })) }, SymmetricAlgorithm::AES256 => { let nonce = Nonce::try_from_slice(nonce)?; let cipher = Ocb3::<aes::Aes256, U15>::new_from_slice(key)?; Ok(Box::new(Ocb { cipher, nonce: *nonce, aad: aad.to_vec() })) }, SymmetricAlgorithm::Camellia128 => { let nonce = Nonce::try_from_slice(nonce)?; let cipher = Ocb3::<camellia::Camellia128, U15>::new_from_slice(key)?; Ok(Box::new(Ocb { cipher, nonce: *nonce, aad: aad.to_vec() })) }, SymmetricAlgorithm::Camellia192 => { let nonce = Nonce::try_from_slice(nonce)?; let cipher = Ocb3::<camellia::Camellia192, U15>::new_from_slice(key)?; Ok(Box::new(Ocb { cipher, nonce: *nonce, aad: aad.to_vec() })) }, SymmetricAlgorithm::Camellia256 => { let nonce = Nonce::try_from_slice(nonce)?; let cipher = Ocb3::<camellia::Camellia256, U15>::new_from_slice(key)?; Ok(Box::new(Ocb { cipher, nonce: *nonce, aad: aad.to_vec() })) }, | SymmetricAlgorithm::IDEA | SymmetricAlgorithm::TripleDES | SymmetricAlgorithm::CAST5 | SymmetricAlgorithm::Blowfish | SymmetricAlgorithm::Twofish | SymmetricAlgorithm::Private(_) | SymmetricAlgorithm::Unknown(_) | SymmetricAlgorithm::Unencrypted => Err(Error::UnsupportedSymmetricAlgorithm(sym_algo).into()), } }, AEADAlgorithm::GCM => { use aes_gcm::{AesGcm, Nonce}; match sym_algo { SymmetricAlgorithm::AES128 => { let nonce = Nonce::try_from_slice(nonce)?; let cipher = AesGcm::<aes::Aes128, U12>::new_from_slice(key)?; Ok(Box::new(Gcm { cipher, nonce: *nonce, aad: aad.to_vec() })) }, SymmetricAlgorithm::AES192 => { let nonce = Nonce::try_from_slice(nonce)?; let cipher = AesGcm::<aes::Aes192, U12>::new_from_slice(key)?; Ok(Box::new(Gcm { cipher, nonce: *nonce, aad: aad.to_vec() })) }, SymmetricAlgorithm::AES256 => { let nonce = Nonce::try_from_slice(nonce)?; let cipher = AesGcm::<aes::Aes256, U12>::new_from_slice(key)?; Ok(Box::new(Gcm { cipher, nonce: *nonce, aad: aad.to_vec() })) }, | SymmetricAlgorithm::IDEA | SymmetricAlgorithm::TripleDES | SymmetricAlgorithm::CAST5 | SymmetricAlgorithm::Blowfish | SymmetricAlgorithm::Twofish | SymmetricAlgorithm::Camellia128 | SymmetricAlgorithm::Camellia192 | SymmetricAlgorithm::Camellia256 | SymmetricAlgorithm::Private(_) | SymmetricAlgorithm::Unknown(_) | SymmetricAlgorithm::Unencrypted => Err(Error::UnsupportedSymmetricAlgorithm(sym_algo).into()), } }, AEADAlgorithm::Private(_) | AEADAlgorithm::Unknown(_) => Err(Error::UnsupportedAEADAlgorithm(*self).into()), } } } ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/src/crypto/backend/rust/asymmetric.rs�����������������������������������������0000644�0000000�0000000�00000075176�10461020230�0022175�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������//! Holds the implementation of [`Signer`] and [`Decryptor`] for [`KeyPair`]. //! //! [`Signer`]: ../../asymmetric/trait.Signer.html //! [`Decryptor`]: ../../asymmetric/trait.Decryptor.html //! [`KeyPair`]: ../../asymmetric/struct.KeyPair.html use std::convert::TryFrom; use std::time::SystemTime; use num_bigint_dig::{traits::ModInverse, BigUint}; use rsa::traits::{PrivateKeyParts, PublicKeyParts}; use rsa::{Pkcs1v15Encrypt, RsaPublicKey, RsaPrivateKey, Pkcs1v15Sign}; use ecdsa::{ EncodedPoint, hazmat::{SignPrimitive, VerifyPrimitive}, }; use p256::elliptic_curve::{ generic_array::GenericArray as GA, ops::Reduce, sec1::FromEncodedPoint, }; use crate::{Error, Result}; use crate::crypto::asymmetric::KeyPair; use crate::crypto::backend::interface::Asymmetric; use crate::crypto::mem::Protected; use crate::crypto::mpi::{self, MPI, ProtectedMPI}; use crate::crypto::SessionKey; use crate::crypto::pad_truncating; use crate::packet::{key, Key}; use crate::packet::key::{Key4, SecretParts}; use crate::types::{Curve, HashAlgorithm, PublicKeyAlgorithm}; use super::GenericArrayExt; impl TryFrom<&Protected> for Box<ed25519_dalek::SigningKey> { type Error = anyhow::Error; fn try_from(value: &Protected) -> Result<Self> { if value.len() != ed25519_dalek::SECRET_KEY_LENGTH { return Err(crate::Error::InvalidArgument( "Bad Ed25519 secret length".into()).into()); } Ok(Box::new(ed25519_dalek::SigningKey::from_bytes( value.as_ref().try_into().map_err( |e: std::array::TryFromSliceError| { Error::InvalidKey(e.to_string()) })?))) } } impl Asymmetric for super::Backend { fn supports_algo(algo: PublicKeyAlgorithm) -> bool { use PublicKeyAlgorithm::*; #[allow(deprecated)] match algo { X25519 | Ed25519 | RSAEncryptSign | RSAEncrypt | RSASign | ECDH | EdDSA | ECDSA => true, DSA => true, X448 | Ed448 | ElGamalEncrypt | ElGamalEncryptSign | Private(_) | Unknown(_) => false, } } fn supports_curve(curve: &Curve) -> bool { use self::Curve::*; match curve { NistP256 | NistP384 | NistP521 => true, Ed25519 | Cv25519 => true, BrainpoolP256 | BrainpoolP384 | BrainpoolP512 | Unknown(_) => false, } } fn x25519_generate_key() -> Result<(Protected, [u8; 32])> { use x25519_dalek::{StaticSecret, PublicKey}; // x25519_dalek v1.1 doesn't reexport OsRng. It // depends on rand 0.8. use rand::rngs::OsRng; let secret = StaticSecret::random_from_rng(&mut OsRng); let public = PublicKey::from(&secret); let mut secret_bytes = secret.to_bytes(); let secret: Protected = secret_bytes.as_ref().into(); unsafe { memsec::memzero(secret_bytes.as_mut_ptr(), secret_bytes.len()); } Ok((secret, public.to_bytes())) } fn x25519_derive_public(secret: &Protected) -> Result<[u8; 32]> { use x25519_dalek::{PublicKey, StaticSecret}; let secret = StaticSecret::from(<[u8; 32]>::try_from(&secret[..])?); Ok(*PublicKey::from(&secret).as_bytes()) } fn x25519_shared_point(secret: &Protected, public: &[u8; 32]) -> Result<Protected> { use x25519_dalek::{StaticSecret, PublicKey}; let secret = StaticSecret::from(<[u8; 32]>::try_from(&secret[..])?); let public = PublicKey::from(public.clone()); Ok((&secret.diffie_hellman(&public).as_bytes()[..]).into()) } fn ed25519_generate_key() -> Result<(Protected, [u8; 32])> { use rand::rngs::OsRng as OsRng; let pair = ed25519_dalek::SigningKey::generate(&mut OsRng); Ok((pair.to_bytes().into(), pair.verifying_key().to_bytes())) } fn ed25519_derive_public(secret: &Protected) -> Result<[u8; 32]> { use ed25519_dalek::SigningKey; let secret: Box<SigningKey> = secret.try_into()?; let public = secret.verifying_key(); Ok(public.to_bytes()) } fn ed25519_sign(secret: &Protected, _public: &[u8; 32], digest: &[u8]) -> Result<[u8; 64]> { use ed25519_dalek::{SigningKey, Signer}; let pair: Box<SigningKey> = secret.try_into()?; Ok(pair.sign(digest).to_bytes().try_into()?) } fn ed25519_verify(public: &[u8; 32], digest: &[u8], signature: &[u8; 64]) -> Result<bool> { use ed25519_dalek::{VerifyingKey, Verifier, Signature}; let public = VerifyingKey::from_bytes(public).map_err(|e| { Error::InvalidKey(e.to_string()) })?; let signature = signature.as_ref().try_into().map_err(|e: std::array::TryFromSliceError| { Error::InvalidArgument(e.to_string()) })?; let signature = Signature::from_bytes(signature); Ok(public.verify(digest, &signature).is_ok()) } fn dsa_generate_key(p_bits: usize) -> Result<(MPI, MPI, MPI, MPI, ProtectedMPI)> { #[allow(deprecated)] let size = match p_bits { 1024 => dsa::KeySize::DSA_1024_160, 2048 => dsa::KeySize::DSA_2048_256, 3072 => dsa::KeySize::DSA_3072_256, n => return Err(Error::InvalidArgument( format!("Key size {} is not supported", n)).into()), }; let mut rng = rand_core::OsRng; let components = dsa::Components::generate(&mut rng, size); let p = components.p().into(); let q = components.q().into(); let g = components.g().into(); let secret = dsa::SigningKey::generate(&mut rng, components); let public = secret.verifying_key(); Ok((p, q, g, public.y().into(), secret.x().into())) } } impl From<&BigUint> for ProtectedMPI { fn from(v: &BigUint) -> Self { v.to_bytes_be().into() } } impl From<&ProtectedMPI> for BigUint { fn from(v: &ProtectedMPI) -> Self { BigUint::from_bytes_be(v.value()).into() } } impl From<&BigUint> for MPI { fn from(v: &BigUint) -> Self { v.to_bytes_be().into() } } impl From<&MPI> for BigUint { fn from(v: &MPI) -> Self { BigUint::from_bytes_be(v.value()).into() } } fn pkcs1_padding(hash_algo: HashAlgorithm) -> Result<Pkcs1v15Sign> { let hash = match hash_algo { HashAlgorithm::MD5 => Pkcs1v15Sign::new::<md5::Md5>(), HashAlgorithm::SHA1 => Pkcs1v15Sign::new::<sha1collisiondetection::Sha1CD>(), HashAlgorithm::SHA224 => Pkcs1v15Sign::new::<sha2::Sha224>(), HashAlgorithm::SHA256 => Pkcs1v15Sign::new::<sha2::Sha256>(), HashAlgorithm::SHA384 => Pkcs1v15Sign::new::<sha2::Sha384>(), HashAlgorithm::SHA512 => Pkcs1v15Sign::new::<sha2::Sha512>(), HashAlgorithm::RipeMD => Pkcs1v15Sign::new::<ripemd::Ripemd160>(), _ => return Err(Error::InvalidArgument(format!( "Algorithm {:?} not representable", hash_algo)).into()), }; Ok(hash) } fn rsa_public_key(e: &MPI, n: &MPI) -> Result<RsaPublicKey> { let n = BigUint::from_bytes_be(n.value()); let e = BigUint::from_bytes_be(e.value()); Ok(RsaPublicKey::new(n, e)?) } fn rsa_private_key(e: &MPI, n: &MPI, p: &ProtectedMPI, q: &ProtectedMPI, d: &ProtectedMPI) -> Result<RsaPrivateKey> { let n = BigUint::from_bytes_be(n.value()); let e = BigUint::from_bytes_be(e.value()); let p = BigUint::from_bytes_be(p.value()); let q = BigUint::from_bytes_be(q.value()); let d = BigUint::from_bytes_be(d.value()); Ok(RsaPrivateKey::from_components(n, e, d, vec![p, q])?) } impl KeyPair { pub(crate) fn sign_backend(&self, secret: &mpi::SecretKeyMaterial, hash_algo: HashAlgorithm, digest: &[u8]) -> Result<mpi::Signature> { use crate::PublicKeyAlgorithm::*; #[allow(deprecated)] match (self.public().pk_algo(), self.public().mpis(), secret) { (RSAEncryptSign, mpi::PublicKey::RSA { e, n }, mpi::SecretKeyMaterial::RSA { p, q, d, .. }) | (RSASign, mpi::PublicKey::RSA { e, n }, mpi::SecretKeyMaterial::RSA { p, q, d, .. }) => { let key = rsa_private_key(e, n, p, q, d)?; let padding = pkcs1_padding(hash_algo)?; let sig = key.sign(padding, digest)?; Ok(mpi::Signature::RSA { s: mpi::MPI::new(&sig), }) }, (PublicKeyAlgorithm::DSA, mpi::PublicKey::DSA { p, q, g, y }, mpi::SecretKeyMaterial::DSA { x }) => { use dsa::signature::hazmat::PrehashSigner; let c = dsa::Components::from_components( p.into(), q.into(), g.into())?; let public = dsa::VerifyingKey::from_components(c, y.into())?; let secret = dsa::SigningKey::from_components(public, x.into())?; let sig = secret.sign_prehash(digest)?; Ok(mpi::Signature::DSA { r: sig.r().into(), s: sig.s().into(), }) }, (PublicKeyAlgorithm::ECDSA, mpi::PublicKey::ECDSA { curve, .. }, mpi::SecretKeyMaterial::ECDSA { scalar }) => match curve { Curve::NistP256 => { use p256::Scalar; const LEN: usize = 32; let key = scalar.value_padded(LEN); let key = Scalar::reduce_bytes(GA::try_from_slice(&key)?); let dig = pad_truncating(digest, LEN); let dig = GA::try_from_slice(&dig)?; let sig = loop { let mut k: Protected = vec![0; LEN].into(); crate::crypto::random(&mut k)?; let k = Scalar::reduce_bytes( GA::try_from_slice(&k)?); if let Ok(s) = key.try_sign_prehashed(k, &dig) { break s.0; } }; Ok(mpi::Signature::ECDSA { r: MPI::new(&sig.r().to_bytes()), s: MPI::new(&sig.s().to_bytes()), }) }, Curve::NistP384 => { use p384::Scalar; const LEN: usize = 48; let key = scalar.value_padded(LEN); let key = Scalar::reduce_bytes(GA::try_from_slice(&key)?); let dig = pad_truncating(digest, LEN); let dig = GA::try_from_slice(&dig)?; let sig = loop { let mut k: Protected = vec![0; LEN].into(); crate::crypto::random(&mut k)?; let k = Scalar::reduce_bytes( GA::try_from_slice(&k)?); if let Ok(s) = key.try_sign_prehashed(k, &dig) { break s.0; } }; Ok(mpi::Signature::ECDSA { r: MPI::new(&sig.r().to_bytes()), s: MPI::new(&sig.s().to_bytes()), }) }, Curve::NistP521 => { use p521::Scalar; const LEN: usize = 66; let key = scalar.value_padded(LEN); let key = Scalar::reduce_bytes(GA::try_from_slice(&key)?); let dig = pad_truncating(digest, LEN); let dig = GA::try_from_slice(&dig)?; let sig = loop { let mut k: Protected = vec![0; LEN].into(); crate::crypto::random(&mut k)?; let k = Scalar::reduce_bytes( GA::try_from_slice(&k)?); if let Ok(s) = key.try_sign_prehashed(k, &dig) { break s.0; } }; Ok(mpi::Signature::ECDSA { r: MPI::new(&sig.r().to_bytes()), s: MPI::new(&sig.s().to_bytes()), }) }, _ => Err(Error::UnsupportedEllipticCurve(curve.clone()).into()), }, (pk_algo, _, _) => { Err(Error::InvalidOperation(format!( "unsupported combination of algorithm {:?}, key {:?}, \ and secret key {:?}", pk_algo, self.public(), self.secret() )).into()) } } } } impl KeyPair { pub(crate) fn decrypt_backend(&self, secret: &mpi::SecretKeyMaterial, ciphertext: &mpi::Ciphertext, plaintext_len: Option<usize>) -> Result<SessionKey> { use crate::PublicKeyAlgorithm::*; #[allow(deprecated)] match (self.public().mpis(), secret, ciphertext) { (mpi::PublicKey::RSA { e, n }, mpi::SecretKeyMaterial::RSA { p, q, d, .. }, mpi::Ciphertext::RSA { c }) => { let key = rsa_private_key(e, n, p, q, d)?; let decrypted = key.decrypt(Pkcs1v15Encrypt, c.value())?; Ok(SessionKey::from(decrypted)) } (mpi::PublicKey::ElGamal { .. }, mpi::SecretKeyMaterial::ElGamal { .. }, mpi::Ciphertext::ElGamal { .. }) => Err(Error::UnsupportedPublicKeyAlgorithm(ElGamalEncrypt).into()), (mpi::PublicKey::ECDH { .. }, mpi::SecretKeyMaterial::ECDH { .. }, mpi::Ciphertext::ECDH { .. }) => crate::crypto::ecdh::decrypt(self.public(), secret, ciphertext, plaintext_len), (public, secret, ciphertext) => Err(Error::InvalidOperation(format!( "unsupported combination of key pair {:?}/{:?} \ and ciphertext {:?}", public, secret, ciphertext)).into()), } } } impl<P: key::KeyParts, R: key::KeyRole> Key<P, R> { /// Encrypts the given data with this key. pub(crate) fn encrypt_backend(&self, data: &SessionKey) -> Result<mpi::Ciphertext> { use PublicKeyAlgorithm::*; #[allow(deprecated)] match self.pk_algo() { RSAEncryptSign | RSAEncrypt => match self.mpis() { mpi::PublicKey::RSA { e, n } => { // The ciphertext has the length of the modulus. let ciphertext_len = n.value().len(); if data.len() + 11 > ciphertext_len { return Err(Error::InvalidArgument( "Plaintext data too large".into()).into()); } let key = rsa_public_key(e, n)?; let ciphertext = key.encrypt( &mut rsa::rand_core::OsRng, Pkcs1v15Encrypt, data.as_ref())?; Ok(mpi::Ciphertext::RSA { c: mpi::MPI::new(&ciphertext) }) } pk => Err(Error::MalformedPacket(format!( "Key: Expected RSA public key, got {:?}", pk)).into()) } ECDH => crate::crypto::ecdh::encrypt(self.parts_as_public(), data), RSASign | DSA | ECDSA | EdDSA | Ed25519 | Ed448 => Err(Error::InvalidOperation( format!("{} is not an encryption algorithm", self.pk_algo()) ).into()), ElGamalEncrypt | ElGamalEncryptSign | X25519 | X448 | Private(_) | Unknown(_) => Err(Error::UnsupportedPublicKeyAlgorithm(self.pk_algo()).into()), } } /// Verifies the given signature. pub(crate) fn verify_backend(&self, sig: &mpi::Signature, hash_algo: HashAlgorithm, digest: &[u8]) -> Result<()> { fn bad(e: impl ToString) -> anyhow::Error { Error::BadSignature(e.to_string()).into() } match (self.mpis(), sig) { (mpi::PublicKey::RSA { e, n }, mpi::Signature::RSA { s }) => { let key = rsa_public_key(e, n)?; let padding = pkcs1_padding(hash_algo)?; // Originally, we had: // // key.verify(padding, digest, s.value())?; // // Since version 0.9.0 of the rsa crate, this no // longer works, because the verify function checks // that the signature length in bytes is the same as // the key length. No other crypto backend appears // care (including older version of the rsa crate), // but would happily left pad it with zeros. We now // do that manually: // // See // https://docs.rs/rsa/0.9.0/src/rsa/pkcs1v15.rs.html#212 // and https://github.com/RustCrypto/RSA/issues/322. key.verify(padding, digest, &s.value_padded(key.size())?)?; Ok(()) } (mpi::PublicKey::DSA { p, q, g, y }, mpi::Signature::DSA { r, s }) => { use dsa::signature::hazmat::PrehashVerifier; let c = dsa::Components::from_components( p.into(), q.into(), g.into())?; let public = dsa::VerifyingKey::from_components(c, y.into())?; let sig = dsa::Signature::from_components(r.into(), s.into())?; public.verify_prehash(digest, &sig)?; Ok(()) }, (mpi::PublicKey::ECDSA { curve, q }, mpi::Signature::ECDSA { r, s }) => match curve { Curve::NistP256 => { use p256::{AffinePoint, ecdsa::Signature}; const LEN: usize = 32; let key = AffinePoint::from_encoded_point( &EncodedPoint::<p256::NistP256>::from_bytes(q.value())?); let key = if key.is_some().into() { key.unwrap() } else { return Err(Error::InvalidKey( "Point is not on the curve".into()).into()); }; let sig = Signature::from_scalars( GA::try_clone_from_slice( &r.value_padded(LEN).map_err(bad)?)?, GA::try_clone_from_slice( &s.value_padded(LEN).map_err(bad)?)?) .map_err(bad)?; let dig = pad_truncating(digest, LEN); let dig = GA::try_from_slice(&dig)?; key.verify_prehashed(&dig, &sig).map_err(bad) }, Curve::NistP384 => { use p384::{AffinePoint, ecdsa::Signature}; const LEN: usize = 48; let key = AffinePoint::from_encoded_point( &EncodedPoint::<p384::NistP384>::from_bytes(q.value())?); let key = if key.is_some().into() { key.unwrap() } else { return Err(Error::InvalidKey( "Point is not on the curve".into()).into()); }; let sig = Signature::from_scalars( GA::try_clone_from_slice( &r.value_padded(LEN).map_err(bad)?)?, GA::try_clone_from_slice( &s.value_padded(LEN).map_err(bad)?)?) .map_err(bad)?; let dig = pad_truncating(digest, LEN); let dig = GA::try_from_slice(&dig)?; key.verify_prehashed(&dig, &sig).map_err(bad) }, Curve::NistP521 => { use p521::{AffinePoint, ecdsa::Signature}; const LEN: usize = 66; let key = AffinePoint::from_encoded_point( &EncodedPoint::<p521::NistP521>::from_bytes(q.value())?); let key = if key.is_some().into() { key.unwrap() } else { return Err(Error::InvalidKey( "Point is not on the curve".into()).into()); }; let sig = Signature::from_scalars( GA::try_clone_from_slice( &r.value_padded(LEN).map_err(bad)?)?, GA::try_clone_from_slice( &s.value_padded(LEN).map_err(bad)?)?) .map_err(bad)?; let dig = pad_truncating(digest, LEN); let dig = GA::try_from_slice(&dig)?; key.verify_prehashed(&dig, &sig).map_err(bad) }, _ => Err(Error::UnsupportedEllipticCurve(curve.clone()).into()), }, _ => Err(Error::MalformedPacket(format!( "unsupported combination of key {} and signature {:?}.", self.pk_algo(), sig)).into()), } } } impl<R> Key4<SecretParts, R> where R: key::KeyRole, { /// Creates a new OpenPGP public key packet for an existing RSA key. /// /// The RSA key will use public exponent `e` and modulo `n`. The key will /// have its creation date set to `ctime` or the current time if `None` /// is given. pub fn import_secret_rsa<T>(d: &[u8], p: &[u8], q: &[u8], ctime: T) -> Result<Self> where T: Into<Option<SystemTime>> { // RFC 4880: `p < q` let (p, q) = crate::crypto::rsa_sort_raw_pq(p, q); // RustCrypto can't compute the public key from the private one, so do it ourselves let big_p = BigUint::from_bytes_be(p); let big_q = BigUint::from_bytes_be(q); let n = big_p.clone() * big_q.clone(); let big_d = BigUint::from_bytes_be(d); let big_phi = (big_p.clone() - 1u32) * (big_q.clone() - 1u32); let e = big_d.mod_inverse(big_phi) // e ≡ d⁻¹ (mod 𝜙) .and_then(|x| x.to_biguint()) .ok_or_else(|| Error::MalformedMPI("RSA: `d` and `(p-1)(q-1)` aren't coprime".into()))?; let u: BigUint = big_p.mod_inverse(big_q) // RFC 4880: u ≡ p⁻¹ (mod q) .and_then(|x| x.to_biguint()) .ok_or_else(|| Error::MalformedMPI("RSA: `p` and `q` aren't coprime".into()))?; Self::with_secret( ctime.into().unwrap_or_else(crate::now), PublicKeyAlgorithm::RSAEncryptSign, mpi::PublicKey::RSA { e: mpi::MPI::new(&e.to_bytes_be()), n: mpi::MPI::new(&n.to_bytes_be()), }, mpi::SecretKeyMaterial::RSA { d: d.into(), p: p.into(), q: q.into(), u: u.to_bytes_be().into(), }.into() ) } /// Generates a new RSA key with a public modulus of size `bits`. pub fn generate_rsa(bits: usize) -> Result<Self> { let key = RsaPrivateKey::new(&mut rsa::rand_core::OsRng, bits)?; let (p, q) = match key.primes() { [p, q] => (p, q), _ => panic!("RSA key generation resulted in wrong number of primes"), }; // RFC 4880: `p < q` let (p, q) = rsa_sort_pq(p, q); let u = p.mod_inverse(q) // RFC 4880: u ≡ p⁻¹ (mod q) .and_then(|x| x.to_biguint()) .expect("rsa crate did not generate coprime p and q"); let public = mpi::PublicKey::RSA { e: mpi::MPI::new(&key.to_public_key().e().to_bytes_be()), n: mpi::MPI::new(&key.to_public_key().n().to_bytes_be()), }; let private = mpi::SecretKeyMaterial::RSA { p: p.to_bytes_be().into(), q: q.to_bytes_be().into(), d: key.d().to_bytes_be().into(), u: u.to_bytes_be().into(), }; Self::with_secret( crate::now(), PublicKeyAlgorithm::RSAEncryptSign, public, private.into(), ) } /// Generates a new ECC key over `curve`. /// /// If `for_signing` is false a ECDH key, if it's true either a /// EdDSA or ECDSA key is generated. Giving `for_signing == true` and /// `curve == Cv25519` will produce an error. Likewise /// `for_signing == false` and `curve == Ed25519` will produce an error. pub(crate) fn generate_ecc_backend(for_signing: bool, curve: Curve) -> Result<(PublicKeyAlgorithm, mpi::PublicKey, mpi::SecretKeyMaterial)> { match (&curve, for_signing) { (Curve::Ed25519, true) => unreachable!("handled in Key4::generate_ecc"), (Curve::Cv25519, false) => unreachable!("handled in Key4::generate_ecc"), (Curve::NistP256, true) => { use p256::{EncodedPoint, SecretKey}; let secret = SecretKey::random( &mut p256::elliptic_curve::rand_core::OsRng); let public = EncodedPoint::from(secret.public_key()); let public_mpis = mpi::PublicKey::ECDSA { curve, q: MPI::new(public.as_bytes()), }; let private_mpis = mpi::SecretKeyMaterial::ECDSA { scalar: Vec::from(secret.to_bytes().as_slice()).into(), }; Ok((PublicKeyAlgorithm::ECDSA, public_mpis, private_mpis)) }, (Curve::NistP256, false) => { use p256::{EncodedPoint, SecretKey}; let secret = SecretKey::random( &mut p256::elliptic_curve::rand_core::OsRng); let public = EncodedPoint::from(secret.public_key()); let public_mpis = mpi::PublicKey::ECDH { q: MPI::new(public.as_bytes()), hash: crate::crypto::ecdh::default_ecdh_kdf_hash(&curve), sym: crate::crypto::ecdh::default_ecdh_kek_cipher(&curve), curve, }; let private_mpis = mpi::SecretKeyMaterial::ECDH { scalar: Vec::from(secret.to_bytes().as_slice()).into(), }; Ok((PublicKeyAlgorithm::ECDH, public_mpis, private_mpis)) }, (Curve::NistP384, true) => { use p384::{EncodedPoint, SecretKey}; let secret = SecretKey::random( &mut p384::elliptic_curve::rand_core::OsRng); let public = EncodedPoint::from(secret.public_key()); let public_mpis = mpi::PublicKey::ECDSA { curve, q: MPI::new(public.as_bytes()), }; let private_mpis = mpi::SecretKeyMaterial::ECDSA { scalar: Vec::from(secret.to_bytes().as_slice()).into(), }; Ok((PublicKeyAlgorithm::ECDSA, public_mpis, private_mpis)) }, (Curve::NistP384, false) => { use p384::{EncodedPoint, SecretKey}; let secret = SecretKey::random( &mut p384::elliptic_curve::rand_core::OsRng); let public = EncodedPoint::from(secret.public_key()); let public_mpis = mpi::PublicKey::ECDH { q: MPI::new(public.as_bytes()), hash: crate::crypto::ecdh::default_ecdh_kdf_hash(&curve), sym: crate::crypto::ecdh::default_ecdh_kek_cipher(&curve), curve, }; let private_mpis = mpi::SecretKeyMaterial::ECDH { scalar: Vec::from(secret.to_bytes().as_slice()).into(), }; Ok((PublicKeyAlgorithm::ECDH, public_mpis, private_mpis)) }, (Curve::NistP521, true) => { use p521::{EncodedPoint, SecretKey}; let secret = SecretKey::random( &mut p521::elliptic_curve::rand_core::OsRng); let public = EncodedPoint::from(secret.public_key()); let public_mpis = mpi::PublicKey::ECDSA { curve, q: MPI::new(public.as_bytes()), }; let private_mpis = mpi::SecretKeyMaterial::ECDSA { scalar: Vec::from(secret.to_bytes().as_slice()).into(), }; Ok((PublicKeyAlgorithm::ECDSA, public_mpis, private_mpis)) }, (Curve::NistP521, false) => { use p521::{EncodedPoint, SecretKey}; let secret = SecretKey::random( &mut p521::elliptic_curve::rand_core::OsRng); let public = EncodedPoint::from(secret.public_key()); let public_mpis = mpi::PublicKey::ECDH { q: MPI::new(public.as_bytes()), hash: crate::crypto::ecdh::default_ecdh_kdf_hash(&curve), sym: crate::crypto::ecdh::default_ecdh_kek_cipher(&curve), curve, }; let private_mpis = mpi::SecretKeyMaterial::ECDH { scalar: Vec::from(secret.to_bytes().as_slice()).into(), }; Ok((PublicKeyAlgorithm::ECDH, public_mpis, private_mpis)) }, _ => Err(Error::UnsupportedEllipticCurve(curve).into()), } } } /// Given the secret prime values `p` and `q`, returns the pair of /// primes so that the smaller one comes first. /// /// Section 5.5.3 of RFC4880 demands that `p < q`. This function can /// be used to order `p` and `q` accordingly. /// /// Note: even though this function seems trivial, we introduce it as /// explicit abstraction. The reason is that the function's /// expression also "works" (as in it compiles) for byte slices, but /// does the wrong thing, see [`crate::crypto::rsa_sort_raw_pq`]. fn rsa_sort_pq<'a>(p: &'a BigUint, q: &'a BigUint) -> (&'a BigUint, &'a BigUint) { if p < q { (p, q) } else { (q, p) } } ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/src/crypto/backend/rust/ecdh.rs�����������������������������������������������0000644�0000000�0000000�00000014744�10461020230�0020715�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������//! Elliptic Curve Diffie-Hellman. use std::convert::TryInto; use p256::elliptic_curve::{ ecdh::diffie_hellman, generic_array::GenericArray as GA, }; use crate::{Error, Result}; use crate::crypto::SessionKey; use crate::crypto::mem::Protected; use crate::crypto::ecdh::{encrypt_wrap, decrypt_unwrap}; use crate::crypto::mpi::{self, Ciphertext, SecretKeyMaterial, MPI}; use crate::packet::{key, Key}; use crate::types::Curve; use super::GenericArrayExt; const CURVE25519_SIZE: usize = 32; /// Wraps a session key using Elliptic Curve Diffie-Hellman. #[allow(non_snake_case)] pub fn encrypt<R>(recipient: &Key<key::PublicParts, R>, session_key: &SessionKey) -> Result<Ciphertext> where R: key::KeyRole { let (curve, q) = match recipient.mpis() { mpi::PublicKey::ECDH { curve, q, .. } => (curve, q), _ => return Err(Error::InvalidArgument("Expected an ECDHPublicKey".into()).into()), }; let (VB, shared) = match curve { Curve::Cv25519 => return Err(Error::InvalidArgument("implemented elsewhere".into()).into()), Curve::NistP256 => { use p256::{EncodedPoint, PublicKey, ecdh::EphemeralSecret}; // Decode the recipient's public key. let recipient_key = PublicKey::from_sec1_bytes(q.value())?; // Generate a keypair and perform Diffie-Hellman. let secret = EphemeralSecret::random( &mut p256::elliptic_curve::rand_core::OsRng); let public = EncodedPoint::from(PublicKey::from(&secret)); let shared = secret.diffie_hellman(&recipient_key); // Encode our public key. let VB = MPI::new(public.as_bytes()); // Encode the shared secret. let shared: &[u8] = shared.raw_secret_bytes(); let shared = Protected::from(shared); (VB, shared) }, Curve::NistP384 => { use p384::{EncodedPoint, PublicKey, ecdh::EphemeralSecret}; // Decode the recipient's public key. let recipient_key = PublicKey::from_sec1_bytes(q.value())?; // Generate a keypair and perform Diffie-Hellman. let secret = EphemeralSecret::random( &mut p384::elliptic_curve::rand_core::OsRng); let public = EncodedPoint::from(PublicKey::from(&secret)); let shared = secret.diffie_hellman(&recipient_key); // Encode our public key. let VB = MPI::new(public.as_bytes()); // Encode the shared secret. let shared: &[u8] = shared.raw_secret_bytes(); let shared = Protected::from(shared); (VB, shared) }, Curve::NistP521 => { use p521::{EncodedPoint, PublicKey, ecdh::EphemeralSecret}; // Decode the recipient's public key. let recipient_key = PublicKey::from_sec1_bytes(q.value())?; // Generate a keypair and perform Diffie-Hellman. let secret = EphemeralSecret::random( &mut p521::elliptic_curve::rand_core::OsRng); let public = EncodedPoint::from(PublicKey::from(&secret)); let shared = secret.diffie_hellman(&recipient_key); // Encode our public key. let VB = MPI::new(public.as_bytes()); // Encode the shared secret. let shared: &[u8] = shared.raw_secret_bytes(); let shared = Protected::from(shared); (VB, shared) }, _ => return Err(Error::UnsupportedEllipticCurve(curve.clone()).into()), }; encrypt_wrap(recipient, session_key, VB, &shared) } /// Unwraps a session key using Elliptic Curve Diffie-Hellman. #[allow(non_snake_case)] pub fn decrypt<R>(recipient: &Key<key::PublicParts, R>, recipient_sec: &SecretKeyMaterial, ciphertext: &Ciphertext, plaintext_len: Option<usize>) -> Result<SessionKey> where R: key::KeyRole { let (curve, scalar, e) = match (recipient.mpis(), recipient_sec, ciphertext) { (mpi::PublicKey::ECDH { ref curve, ..}, SecretKeyMaterial::ECDH { ref scalar, }, Ciphertext::ECDH { ref e, .. }) => (curve, scalar, e), _ => return Err(Error::InvalidArgument("Expected an ECDHPublicKey".into()).into()), }; let S: Protected = match curve { Curve::Cv25519 => return Err(Error::InvalidArgument("implemented elsewhere".into()).into()), Curve::NistP256 => { use p256::{SecretKey, PublicKey}; const NISTP256_SIZE: usize = 32; // Get the public part V of the ephemeral key. let V = PublicKey::from_sec1_bytes(e.value())?; let scalar: [u8; NISTP256_SIZE] = scalar.value_padded(NISTP256_SIZE).as_ref().try_into()?; let scalar = GA::try_from_slice(&scalar)?; let r = SecretKey::from_bytes(&scalar)?; let secret = diffie_hellman(r.to_nonzero_scalar(), V.as_affine()); Vec::from(secret.raw_secret_bytes().as_slice()).into() }, Curve::NistP384 => { use p384::{SecretKey, PublicKey}; const NISTP384_SIZE: usize = 48; // Get the public part V of the ephemeral key. let V = PublicKey::from_sec1_bytes(e.value())?; let scalar: [u8; NISTP384_SIZE] = scalar.value_padded(NISTP384_SIZE).as_ref().try_into()?; let scalar = GA::try_from_slice(&scalar)?; let r = SecretKey::from_bytes(&scalar)?; let secret = diffie_hellman(r.to_nonzero_scalar(), V.as_affine()); Vec::from(secret.raw_secret_bytes().as_slice()).into() }, Curve::NistP521 => { use p521::{SecretKey, PublicKey}; const NISTP521_SIZE: usize = 66; // Get the public part V of the ephemeral key. let V = PublicKey::from_sec1_bytes(e.value())?; let scalar: [u8; NISTP521_SIZE] = scalar.value_padded(NISTP521_SIZE).as_ref().try_into()?; let scalar = GA::try_from_slice(&scalar)?; let r = SecretKey::from_bytes(&scalar)?; let secret = diffie_hellman(r.to_nonzero_scalar(), V.as_affine()); Vec::from(secret.raw_secret_bytes().as_slice()).into() }, _ => { return Err(Error::UnsupportedEllipticCurve(curve.clone()).into()); }, }; decrypt_unwrap(recipient.role_as_unspecified(), &S, ciphertext, plaintext_len) } ����������������������������sequoia-openpgp-2.0.0/src/crypto/backend/rust/hash.rs�����������������������������������������������0000644�0000000�0000000�00000005563�10461020230�0020734�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������use std::cmp; use digest::Digest as _; use crate::{Error, Result}; use crate::crypto::hash::Digest; use crate::types::HashAlgorithm; macro_rules! impl_digest_for { ($ty:ty, $algo:ident) => { impl Digest for $ty { fn update(&mut self, data: &[u8]) { digest::Digest::update(self, data) } fn digest(&mut self, digest: &mut [u8]) -> Result<()> { let buf = self.finalize_reset(); let n = cmp::min(buf.len(), digest.len()); digest[..n].copy_from_slice(&buf[..n]); Ok(()) } } } } impl_digest_for!(md5::Md5, MD5); impl_digest_for!(ripemd::Ripemd160, RipeMD); impl_digest_for!(sha2::Sha224, SHA224); impl_digest_for!(sha2::Sha256, SHA256); impl_digest_for!(sha2::Sha384, SHA384); impl_digest_for!(sha2::Sha512, SHA512); impl_digest_for!(sha3::Sha3_256, SHA3_256); impl_digest_for!(sha3::Sha3_512, SHA3_512); impl HashAlgorithm { /// Whether Sequoia supports this algorithm. pub fn is_supported(self) -> bool { match self { HashAlgorithm::SHA1 => true, HashAlgorithm::SHA224 => true, HashAlgorithm::SHA256 => true, HashAlgorithm::SHA384 => true, HashAlgorithm::SHA512 => true, HashAlgorithm::SHA3_256 => true, HashAlgorithm::SHA3_512 => true, HashAlgorithm::RipeMD => true, HashAlgorithm::MD5 => true, HashAlgorithm::Private(_) => false, HashAlgorithm::Unknown(_) => false, } } /// Creates a new hash context for this algorithm. /// /// # Errors /// /// Fails with `Error::UnsupportedHashAlgorithm` if Sequoia does /// not support this algorithm. See /// [`HashAlgorithm::is_supported`]. /// /// [`HashAlgorithm::is_supported`]: #method.is_supported pub(crate) fn new_hasher(self) -> Result<Box<dyn Digest>> { match self { // Note: SHA1 is implemented using SHA1CD, this is handled // in HashAlgorithm::context. HashAlgorithm::SHA1 => Err(Error::UnsupportedHashAlgorithm(self).into()), HashAlgorithm::SHA224 => Ok(Box::new(sha2::Sha224::new())), HashAlgorithm::SHA256 => Ok(Box::new(sha2::Sha256::new())), HashAlgorithm::SHA384 => Ok(Box::new(sha2::Sha384::new())), HashAlgorithm::SHA512 => Ok(Box::new(sha2::Sha512::new())), HashAlgorithm::SHA3_256 => Ok(Box::new(sha3::Sha3_256::new())), HashAlgorithm::SHA3_512 => Ok(Box::new(sha3::Sha3_512::new())), HashAlgorithm::RipeMD => Ok(Box::new(ripemd::Ripemd160::new())), HashAlgorithm::MD5 => Ok(Box::new(md5::Md5::new())), HashAlgorithm::Private(_) | HashAlgorithm::Unknown(_) => Err(Error::UnsupportedHashAlgorithm(self).into()), } } } ���������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/src/crypto/backend/rust/kdf.rs������������������������������������������������0000644�0000000�0000000�00000001373�10461020230�0020550�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������use hkdf::Hkdf; use sha2::{Sha256, Sha512}; use crate::{ Result, crypto::{ SessionKey, backend::interface::Kdf, }, }; impl Kdf for super::Backend { fn hkdf_sha256(ikm: &SessionKey, salt: Option<&[u8]>, info: &[u8], okm: &mut SessionKey) -> Result<()> { Ok(Hkdf::<Sha256>::new(salt, &ikm).expand(info, okm) .map_err(|e| crate::Error::InvalidOperation(e.to_string()))?) } fn hkdf_sha512(ikm: &SessionKey, salt: Option<&[u8]>, info: &[u8], okm: &mut SessionKey) -> Result<()> { Ok(Hkdf::<Sha512>::new(salt, &ikm).expand(info, okm) .map_err(|e| crate::Error::InvalidOperation(e.to_string()))?) } } ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/src/crypto/backend/rust/symmetric.rs������������������������������������������0000644�0000000�0000000�00000057226�10461020230�0022030�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������use std::slice; use cipher::BlockDecryptMut; use cipher::BlockEncryptMut; use cipher::KeyInit; use cipher::KeyIvInit; use cipher::generic_array::{ArrayLength, GenericArray}; use crate::{Error, Result}; use crate::crypto::symmetric::Mode; use crate::types::SymmetricAlgorithm; use super::GenericArrayExt; enum CfbEncrypt { Idea(cfb_mode::Encryptor<idea::Idea>), TripleDES(cfb_mode::Encryptor<des::TdesEde3>), Cast5(cfb_mode::Encryptor<cast5::Cast5>), Blowfish(cfb_mode::Encryptor<blowfish::Blowfish>), Aes128(cfb_mode::Encryptor<aes::Aes128>), Aes192(cfb_mode::Encryptor<aes::Aes192>), Aes256(cfb_mode::Encryptor<aes::Aes256>), Twofish(cfb_mode::Encryptor<twofish::Twofish>), Camellia128(cfb_mode::Encryptor<camellia::Camellia128>), Camellia192(cfb_mode::Encryptor<camellia::Camellia192>), Camellia256(cfb_mode::Encryptor<camellia::Camellia256>), } enum CfbDecrypt { Idea(cfb_mode::Decryptor<idea::Idea>), TripleDES(cfb_mode::Decryptor<des::TdesEde3>), Cast5(cfb_mode::Decryptor<cast5::Cast5>), Blowfish(cfb_mode::Decryptor<blowfish::Blowfish>), Aes128(cfb_mode::Decryptor<aes::Aes128>), Aes192(cfb_mode::Decryptor<aes::Aes192>), Aes256(cfb_mode::Decryptor<aes::Aes256>), Twofish(cfb_mode::Decryptor<twofish::Twofish>), Camellia128(cfb_mode::Decryptor<camellia::Camellia128>), Camellia192(cfb_mode::Decryptor<camellia::Camellia192>), Camellia256(cfb_mode::Decryptor<camellia::Camellia256>), } enum EcbEncrypt { Idea(ecb::Encryptor<idea::Idea>), TripleDES(ecb::Encryptor<des::TdesEde3>), Cast5(ecb::Encryptor<cast5::Cast5>), Blowfish(ecb::Encryptor<blowfish::Blowfish>), Aes128(ecb::Encryptor<aes::Aes128>), Aes192(ecb::Encryptor<aes::Aes192>), Aes256(ecb::Encryptor<aes::Aes256>), Twofish(ecb::Encryptor<twofish::Twofish>), Camellia128(ecb::Encryptor<camellia::Camellia128>), Camellia192(ecb::Encryptor<camellia::Camellia192>), Camellia256(ecb::Encryptor<camellia::Camellia256>), } enum EcbDecrypt { Idea(ecb::Decryptor<idea::Idea>), TripleDES(ecb::Decryptor<des::TdesEde3>), Cast5(ecb::Decryptor<cast5::Cast5>), Blowfish(ecb::Decryptor<blowfish::Blowfish>), Aes128(ecb::Decryptor<aes::Aes128>), Aes192(ecb::Decryptor<aes::Aes192>), Aes256(ecb::Decryptor<aes::Aes256>), Twofish(ecb::Decryptor<twofish::Twofish>), Camellia128(ecb::Decryptor<camellia::Camellia128>), Camellia192(ecb::Decryptor<camellia::Camellia192>), Camellia256(ecb::Decryptor<camellia::Camellia256>), } macro_rules! impl_block_size { ($mode:ident) => { fn block_size(&self) -> usize { #[allow(deprecated)] match self { $mode::Idea(_) => <idea::Idea as cipher::BlockSizeUser>::block_size(), $mode::TripleDES(_) => <des::TdesEde3 as cipher::BlockSizeUser>::block_size(), $mode::Cast5(_) => <cast5::Cast5 as cipher::BlockSizeUser>::block_size(), $mode::Blowfish(_) => <blowfish::Blowfish as cipher::BlockSizeUser>::block_size(), $mode::Aes128(_) => <aes::Aes128 as cipher::BlockSizeUser>::block_size(), $mode::Aes192(_) => <aes::Aes192 as cipher::BlockSizeUser>::block_size(), $mode::Aes256(_) => <aes::Aes256 as cipher::BlockSizeUser>::block_size(), $mode::Twofish(_) => <twofish::Twofish as cipher::BlockSizeUser>::block_size(), $mode::Camellia128(_) => <camellia::Camellia128 as cipher::BlockSizeUser>::block_size(), $mode::Camellia192(_) => <camellia::Camellia192 as cipher::BlockSizeUser>::block_size(), $mode::Camellia256(_) => <camellia::Camellia256 as cipher::BlockSizeUser>::block_size(), } } } } macro_rules! impl_enc_mode { ($mode:ident) => { impl Mode for $mode { impl_block_size!($mode); fn encrypt( &mut self, dst: &mut [u8], src: &[u8], ) -> Result<()> { zero_stack!(4096 bytes after running { debug_assert_eq!(dst.len(), src.len()); let bs = self.block_size(); let missing = (bs - (dst.len() % bs)) % bs; if missing > 0 { let mut buf = vec![0u8; src.len() + missing]; buf[..src.len()].copy_from_slice(src); #[allow(deprecated)] match self { $mode::Idea(m) => { let blocks = to_blocks(&mut buf); m.encrypt_blocks_mut(blocks) } $mode::TripleDES(m) => { let blocks = to_blocks(&mut buf); m.encrypt_blocks_mut(blocks) } $mode::Cast5(m) => { let blocks = to_blocks(&mut buf); m.encrypt_blocks_mut(blocks) } $mode::Blowfish(m) => { let blocks = to_blocks(&mut buf); m.encrypt_blocks_mut(blocks) } $mode::Aes128(m) => { let blocks = to_blocks(&mut buf); m.encrypt_blocks_mut(blocks) } $mode::Aes192(m) => { let blocks = to_blocks(&mut buf); m.encrypt_blocks_mut(blocks) } $mode::Aes256(m) => { let blocks = to_blocks(&mut buf); m.encrypt_blocks_mut(blocks) } $mode::Twofish(m) => { let blocks = to_blocks(&mut buf); m.encrypt_blocks_mut(blocks) } $mode::Camellia128(m) => { let blocks = to_blocks(&mut buf); m.encrypt_blocks_mut(blocks) } $mode::Camellia192(m) => { let blocks = to_blocks(&mut buf); m.encrypt_blocks_mut(blocks) } $mode::Camellia256(m) => { let blocks = to_blocks(&mut buf); m.encrypt_blocks_mut(blocks) } } dst.copy_from_slice(&buf[..dst.len()]); } else { dst.copy_from_slice(src); #[allow(deprecated)] match self { $mode::Idea(m) => { let blocks = to_blocks(dst); m.encrypt_blocks_mut(blocks) } $mode::TripleDES(m) => { let blocks = to_blocks(dst); m.encrypt_blocks_mut(blocks) } $mode::Cast5(m) => { let blocks = to_blocks(dst); m.encrypt_blocks_mut(blocks) } $mode::Blowfish(m) => { let blocks = to_blocks(dst); m.encrypt_blocks_mut(blocks) } $mode::Aes128(m) => { let blocks = to_blocks(dst); m.encrypt_blocks_mut(blocks) } $mode::Aes192(m) => { let blocks = to_blocks(dst); m.encrypt_blocks_mut(blocks) } $mode::Aes256(m) => { let blocks = to_blocks(dst); m.encrypt_blocks_mut(blocks) } $mode::Twofish(m) => { let blocks = to_blocks(dst); m.encrypt_blocks_mut(blocks) } $mode::Camellia128(m) => { let blocks = to_blocks(dst); m.encrypt_blocks_mut(blocks) } $mode::Camellia192(m) => { let blocks = to_blocks(dst); m.encrypt_blocks_mut(blocks) } $mode::Camellia256(m) => { let blocks = to_blocks(dst); m.encrypt_blocks_mut(blocks) } } } Ok(()) }) } fn decrypt( &mut self, _dst: &mut [u8], _src: &[u8], ) -> Result<()> { Err(Error::InvalidOperation( "decryption not supported in encryption mode".into()) .into()) } } } } macro_rules! impl_dec_mode { ($mode:ident) => { impl Mode for $mode { impl_block_size!($mode); fn encrypt( &mut self, _dst: &mut [u8], _src: &[u8], ) -> Result<()> { Err(Error::InvalidOperation( "encryption not supported in decryption mode".into()) .into()) } fn decrypt( &mut self, dst: &mut [u8], src: &[u8], ) -> Result<()> { zero_stack!(4096 bytes after running { debug_assert_eq!(dst.len(), src.len()); let bs = self.block_size(); let missing = (bs - (dst.len() % bs)) % bs; if missing > 0 { let mut buf = vec![0u8; src.len() + missing]; buf[..src.len()].copy_from_slice(src); #[allow(deprecated)] match self { $mode::Idea(m) => { let blocks = to_blocks(&mut buf); m.decrypt_blocks_mut(blocks) } $mode::TripleDES(m) => { let blocks = to_blocks(&mut buf); m.decrypt_blocks_mut(blocks) } $mode::Cast5(m) => { let blocks = to_blocks(&mut buf); m.decrypt_blocks_mut(blocks) } $mode::Blowfish(m) => { let blocks = to_blocks(&mut buf); m.decrypt_blocks_mut(blocks) } $mode::Aes128(m) => { let blocks = to_blocks(&mut buf); m.decrypt_blocks_mut(blocks) } $mode::Aes192(m) => { let blocks = to_blocks(&mut buf); m.decrypt_blocks_mut(blocks) } $mode::Aes256(m) => { let blocks = to_blocks(&mut buf); m.decrypt_blocks_mut(blocks) } $mode::Twofish(m) => { let blocks = to_blocks(&mut buf); m.decrypt_blocks_mut(blocks) } $mode::Camellia128(m) => { let blocks = to_blocks(&mut buf); m.decrypt_blocks_mut(blocks) } $mode::Camellia192(m) => { let blocks = to_blocks(&mut buf); m.decrypt_blocks_mut(blocks) } $mode::Camellia256(m) => { let blocks = to_blocks(&mut buf); m.decrypt_blocks_mut(blocks) } } dst.copy_from_slice(&buf[..dst.len()]); } else { dst.copy_from_slice(src); #[allow(deprecated)] match self { $mode::Idea(m) => { let blocks = to_blocks(dst); m.decrypt_blocks_mut(blocks) } $mode::TripleDES(m) => { let blocks = to_blocks(dst); m.decrypt_blocks_mut(blocks) } $mode::Cast5(m) => { let blocks = to_blocks(dst); m.decrypt_blocks_mut(blocks) } $mode::Blowfish(m) => { let blocks = to_blocks(dst); m.decrypt_blocks_mut(blocks) } $mode::Aes128(m) => { let blocks = to_blocks(dst); m.decrypt_blocks_mut(blocks) } $mode::Aes192(m) => { let blocks = to_blocks(dst); m.decrypt_blocks_mut(blocks) } $mode::Aes256(m) => { let blocks = to_blocks(dst); m.decrypt_blocks_mut(blocks) } $mode::Twofish(m) => { let blocks = to_blocks(dst); m.decrypt_blocks_mut(blocks) } $mode::Camellia128(m) => { let blocks = to_blocks(dst); m.decrypt_blocks_mut(blocks) } $mode::Camellia192(m) => { let blocks = to_blocks(dst); m.decrypt_blocks_mut(blocks) } $mode::Camellia256(m) => { let blocks = to_blocks(dst); m.decrypt_blocks_mut(blocks) } } } Ok(()) }) } } } } impl_enc_mode!(CfbEncrypt); impl_dec_mode!(CfbDecrypt); impl_enc_mode!(EcbEncrypt); impl_dec_mode!(EcbDecrypt); fn to_blocks<N>(data: &mut [u8]) -> &mut [GenericArray<u8, N>] where N: ArrayLength<u8>, { let n = N::to_usize(); debug_assert!(data.len() % n == 0); unsafe { slice::from_raw_parts_mut(data.as_ptr() as *mut GenericArray<u8, N>, data.len() / n) } } /// Creates a context for encrypting/decrypting in CFB/ECB mode. macro_rules! make_mode { ($fn:ident, $enum:ident, $mode:ident::$mode2:ident $(, $iv:ident:$ivt:ty)?) => { pub(crate) fn $fn(self, key: &[u8], $($iv: $ivt)?) -> Result<Box<dyn Mode>> { zero_stack!(8192 bytes after running || -> Result<Box<dyn Mode>> { use cipher::generic_array::GenericArray as GA; use SymmetricAlgorithm::*; #[allow(deprecated)] match self { IDEA => { let key = GA::try_from_slice(&key)?; $( let $iv = &GA::try_from_slice(&$iv)?; )? Ok(Box::new($enum::Idea( $mode::$mode2::<idea::Idea>::new(key $(, $iv)?)))) }, TripleDES => { let key = GA::try_from_slice(&key)?; $( let $iv = &GA::try_from_slice(&$iv)?; )? Ok(Box::new($enum::TripleDES( $mode::$mode2::<des::TdesEde3>::new(key $(, $iv)?)))) }, CAST5 => { let key = GA::try_from_slice(&key)?; $( let $iv = &GA::try_from_slice(&$iv)?; )? Ok(Box::new($enum::Cast5( $mode::$mode2::<cast5::Cast5>::new(key $(, $iv)?)))) }, Blowfish => { // Right... the blowfish constructor expects a 56 // byte key, but in OpenPGP the key is only 16 // bytes. assert_eq!(key.len(), 16); let mut key = key.to_vec(); while key.len() < 56 { key.push(0); } let key = GA::try_from_slice(&key)?; $( let $iv = &GA::try_from_slice(&$iv)?; )? Ok(Box::new($enum::Blowfish( $mode::$mode2::<blowfish::Blowfish>::new(key $(, $iv)?)))) }, AES128 => { let key = GA::try_from_slice(&key)?; $( let $iv = &GA::try_from_slice(&$iv)?; )? Ok(Box::new($enum::Aes128( $mode::$mode2::<aes::Aes128>::new(key $(, $iv)?)))) }, AES192 => { let key = GA::try_from_slice(&key)?; $( let $iv = &GA::try_from_slice(&$iv)?; )? Ok(Box::new($enum::Aes192( $mode::$mode2::<aes::Aes192>::new(key $(, $iv)?)))) }, AES256 => { let key = GA::try_from_slice(&key)?; $( let $iv = &GA::try_from_slice(&$iv)?; )? Ok(Box::new($enum::Aes256( $mode::$mode2::<aes::Aes256>::new(key $(, $iv)?)))) }, Twofish => { let key = GA::try_from_slice(&key)?; $( let $iv = &GA::try_from_slice(&$iv)?; )? Ok(Box::new($enum::Twofish( $mode::$mode2::<twofish::Twofish>::new(key $(, $iv)?)))) }, Camellia128 => { let key = GA::try_from_slice(&key)?; $( let $iv = &GA::try_from_slice(&$iv)?; )? Ok(Box::new($enum::Camellia128( $mode::$mode2::<camellia::Camellia128>::new(key $(, $iv)?)))) }, Camellia192 => { let key = GA::try_from_slice(&key)?; $( let $iv = &GA::try_from_slice(&$iv)?; )? Ok(Box::new($enum::Camellia192( $mode::$mode2::<camellia::Camellia192>::new(key $(, $iv)?)))) }, Camellia256 => { let key = GA::try_from_slice(&key)?; $( let $iv = &GA::try_from_slice(&$iv)?; )? Ok(Box::new($enum::Camellia256( $mode::$mode2::<camellia::Camellia256>::new(key $(, $iv)?)))) }, Private(_) | Unknown(_) | Unencrypted => { Err(Error::UnsupportedSymmetricAlgorithm(self).into()) } } }) } } } impl SymmetricAlgorithm { /// Returns whether this algorithm is supported by the crypto backend. pub(crate) fn is_supported_by_backend(&self) -> bool { use SymmetricAlgorithm::*; #[allow(deprecated)] match self { IDEA => true, TripleDES => true, CAST5 => true, Blowfish => true, AES128 => true, AES192 => true, AES256 => true, Twofish => true, Camellia128 => true, Camellia192 => true, Camellia256 => true, Private(_) => false, Unknown(_) => false, Unencrypted => false, } } make_mode!(make_encrypt_cfb, CfbEncrypt, cfb_mode::Encryptor, iv: Vec<u8>); make_mode!(make_decrypt_cfb, CfbDecrypt, cfb_mode::Decryptor, iv: Vec<u8>); make_mode!(make_encrypt_ecb, EcbEncrypt, ecb::Encryptor); make_mode!(make_decrypt_ecb, EcbDecrypt, ecb::Decryptor); } #[cfg(test)] #[allow(deprecated)] mod tests { use super::*; /// Anchors the constants used in Sequoia with the ones from /// RustCrypto. #[test] fn key_size() -> Result<()> { assert_eq!(SymmetricAlgorithm::IDEA.key_size()?, <idea::Idea as cipher::KeySizeUser>::key_size()); assert_eq!(SymmetricAlgorithm::TripleDES.key_size()?, <des::TdesEde3 as cipher::KeySizeUser>::key_size()); assert_eq!(SymmetricAlgorithm::CAST5.key_size()?, <cast5::Cast5 as cipher::KeySizeUser>::key_size()); // RFC4880, Section 9.2: Blowfish (128 bit key, 16 rounds) assert_eq!(SymmetricAlgorithm::Blowfish.key_size()?, 16); assert_eq!(SymmetricAlgorithm::AES128.key_size()?, <aes::Aes128 as cipher::KeySizeUser>::key_size()); assert_eq!(SymmetricAlgorithm::AES192.key_size()?, <aes::Aes192 as cipher::KeySizeUser>::key_size()); assert_eq!(SymmetricAlgorithm::AES256.key_size()?, <aes::Aes256 as cipher::KeySizeUser>::key_size()); assert_eq!(SymmetricAlgorithm::Twofish.key_size()?, <twofish::Twofish as cipher::KeySizeUser>::key_size()); assert_eq!(SymmetricAlgorithm::Camellia128.key_size()?, <camellia::Camellia128 as cipher::KeySizeUser>::key_size()); assert_eq!(SymmetricAlgorithm::Camellia192.key_size()?, <camellia::Camellia192 as cipher::KeySizeUser>::key_size()); assert_eq!(SymmetricAlgorithm::Camellia256.key_size()?, <camellia::Camellia256 as cipher::KeySizeUser>::key_size()); Ok(()) } /// Anchors the constants used in Sequoia with the ones from /// RustCrypto. #[test] fn block_size() -> Result<()> { assert_eq!(SymmetricAlgorithm::IDEA.block_size()?, <idea::Idea as cipher::BlockSizeUser>::block_size()); assert_eq!(SymmetricAlgorithm::TripleDES.block_size()?, <des::TdesEde3 as cipher::BlockSizeUser>::block_size()); assert_eq!(SymmetricAlgorithm::CAST5.block_size()?, <cast5::Cast5 as cipher::BlockSizeUser>::block_size()); assert_eq!(SymmetricAlgorithm::Blowfish.block_size()?, <blowfish::Blowfish as cipher::BlockSizeUser>::block_size()); assert_eq!(SymmetricAlgorithm::AES128.block_size()?, <aes::Aes128 as cipher::BlockSizeUser>::block_size()); assert_eq!(SymmetricAlgorithm::AES192.block_size()?, <aes::Aes192 as cipher::BlockSizeUser>::block_size()); assert_eq!(SymmetricAlgorithm::AES256.block_size()?, <aes::Aes256 as cipher::BlockSizeUser>::block_size()); assert_eq!(SymmetricAlgorithm::Twofish.block_size()?, <twofish::Twofish as cipher::BlockSizeUser>::block_size()); assert_eq!(SymmetricAlgorithm::Camellia128.block_size()?, <camellia::Camellia128 as cipher::BlockSizeUser>::block_size()); assert_eq!(SymmetricAlgorithm::Camellia192.block_size()?, <camellia::Camellia192 as cipher::BlockSizeUser>::block_size()); assert_eq!(SymmetricAlgorithm::Camellia256.block_size()?, <camellia::Camellia256 as cipher::BlockSizeUser>::block_size()); Ok(()) } } ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/src/crypto/backend/rust.rs����������������������������������������������������0000644�0000000�0000000�00000007421�10461020230�0020004�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������//! Implementation of Sequoia crypto API using pure Rust cryptographic //! libraries. use cipher::generic_array::{ArrayLength, GenericArray}; use crate::{Error, Result}; use crate::types::*; pub mod aead; pub mod asymmetric; pub mod ecdh; pub mod hash; pub mod kdf; pub mod symmetric; pub struct Backend(()); impl super::interface::Backend for Backend { fn backend() -> String { // XXX: can we include features and the version? "RustCrypto".to_string() } fn random(buf: &mut [u8]) -> Result<()> { use rand::rngs::OsRng; use rand::RngCore; OsRng.fill_bytes(buf); Ok(()) } } trait GenericArrayExt<T, N: ArrayLength<T>> { const LEN: usize; /// Like [`GenericArray::from_slice`], but fallible. fn try_from_slice(slice: &[T]) -> Result<&GenericArray<T, N>> { if slice.len() == Self::LEN { Ok(GenericArray::from_slice(slice)) } else { Err(Error::InvalidArgument( format!("Invalid slice length, want {}, got {}", Self::LEN, slice.len())).into()) } } /// Like [`GenericArray::clone_from_slice`], but fallible. fn try_clone_from_slice(slice: &[T]) -> Result<GenericArray<T, N>> where T: Clone { if slice.len() == Self::LEN { Ok(GenericArray::clone_from_slice(slice)) } else { Err(Error::InvalidArgument( format!("Invalid slice length, want {}, got {}", Self::LEN, slice.len())).into()) } } } impl<T, N: ArrayLength<T>> GenericArrayExt<T, N> for GenericArray<T, N> { const LEN: usize = N::USIZE; } impl AEADAlgorithm { /// Returns the best AEAD mode supported by the backend. /// /// This SHOULD return OCB, which is the mandatory-to-implement /// algorithm and the most performing one, but fall back to any /// supported algorithm. pub(crate) const fn const_default() -> AEADAlgorithm { AEADAlgorithm::EAX } pub(crate) fn is_supported_by_backend(&self) -> bool { use self::AEADAlgorithm::*; match &self { EAX => true, OCB => false, GCM => true, Private(_) | Unknown(_) => false, } } #[cfg(test)] pub(crate) fn supports_symmetric_algo(&self, algo: &SymmetricAlgorithm) -> bool { match &self { AEADAlgorithm::EAX => match algo { SymmetricAlgorithm::AES128 | SymmetricAlgorithm::AES192 | SymmetricAlgorithm::AES256 | // XXX: Skipping Twofish until Twofish implements Clone // SymmetricAlgorithm::Twofish | SymmetricAlgorithm::Camellia128 | SymmetricAlgorithm::Camellia192 | SymmetricAlgorithm::Camellia256 => true, _ => false, }, AEADAlgorithm::OCB => match algo { SymmetricAlgorithm::AES128 | SymmetricAlgorithm::AES192 | SymmetricAlgorithm::AES256 | // XXX: Skipping Twofish until Twofish implements Clone // SymmetricAlgorithm::Twofish | SymmetricAlgorithm::Camellia128 | SymmetricAlgorithm::Camellia192 | SymmetricAlgorithm::Camellia256 => true, _ => false, }, AEADAlgorithm::GCM => match algo { SymmetricAlgorithm::AES128 | SymmetricAlgorithm::AES192 | SymmetricAlgorithm::AES256 => true, _ => false, }, _ => false } } } �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/src/crypto/backend/sha1cd.rs��������������������������������������������������0000644�0000000�0000000�00000011731�10461020230�0020151�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������use crate::crypto::hash::Digest; use crate::Result; pub(crate) fn build() -> sha1collisiondetection::Sha1CD { sha1collisiondetection::Builder::default() .detect_collisions(true) .use_ubc(true) .safe_hash(true) .build() } impl Digest for sha1collisiondetection::Sha1CD { fn update(&mut self, data: &[u8]) { sha1collisiondetection::Sha1CD::update(self, data); } fn digest(&mut self, digest: &mut [u8]) -> Result<()> { let mut d = sha1collisiondetection::Output::default(); let r = self.finalize_into_dirty_cd(&mut d); self.reset(); let l = digest.len().min(d.len()); digest[..l].copy_from_slice(&d[..l]); r.map_err(Into::into) } } #[cfg(test)] mod test { use crate::*; use crate::parse::{Parse, stream::*}; use crate::policy::StandardPolicy; /// Test vector from the "SHA-1 is a Shambles" paper. /// /// The scenario is the following. Bob obtains a certification /// from a CA, and transfers it to a key claiming to belong to /// Alice. Now the CA certifies an illegitimate binding to /// Alice's userid. #[test] fn shambles() -> Result<()> { let alice = PacketPile::from_bytes(crate::tests::key("sha-mbles.alice.asc"))?; let bob = PacketPile::from_bytes(crate::tests::key("sha-mbles.bob.asc"))?; let ca_keyid: KeyID = "AFBB 1FED 6951 A956".parse()?; assert_eq!(alice.children().count(), 4); assert_eq!(bob.children().count(), 7); let alice_sha1_fingerprint: Fingerprint = "43CD 5C5B 04FF 5742 FA14 1ABC A9D7 55A9 6354 8C78".parse()?; let bob_sha1_fingerprint: Fingerprint = "C6BF E2FC BBE5 1A89 2BEB 7798 1233 D4CC 61DB D9C4".parse()?; let alice_sha1cd_fingerprint: Fingerprint = "4D84 B08A A181 21DB D79E EA05 9CD0 8D5B 1680 87E2".parse()?; let bob_sha1cd_fingerprint: Fingerprint = "6434 B04B 4648 BA41 15BD C5C2 B67A DB26 6F74 DF89".parse()?; // The illegitimate certification is on Bob's user attribute. assert_eq!(bob.path_ref(&[6]).unwrap(), alice.path_ref(&[3]).unwrap()); match bob.path_ref(&[6]).unwrap() { Packet::Signature(s) => { assert_eq!(s.issuers().next().unwrap(), &ca_keyid); }, o => panic!("unexpected packet: {:?}", o), } let alice = Cert::from_packets(alice.into_children())?; let bob = Cert::from_packets(bob.into_children())?; // Check mitigations. First, the illegitimate certification // should be discarded. assert_eq!(alice.bad_signatures().count(), 1); // Bob's userid also got certified, hence there are two bad // signatures. assert_eq!(bob.bad_signatures().count(), 2); // The mitigation also changes the identities of the keys // containing the collision attack. This is a good thing, // because we cannot trust SHA-1 to discriminate keys // containing attacks. assert!(alice.fingerprint() != alice_sha1_fingerprint); assert_eq!(alice.fingerprint(), alice_sha1cd_fingerprint); assert!(bob.fingerprint() != bob_sha1_fingerprint); assert_eq!(bob.fingerprint(), bob_sha1cd_fingerprint); Ok(()) } /// Test vector from the paper "The first collision for full SHA-1". #[test] fn shattered() -> Result<()> { let cert = Cert::from_bytes(crate::tests::key("testy-new.pgp"))?; let shattered_1 = crate::tests::message("shattered-1.pdf"); let shattered_1_sig = crate::tests::message("shattered-1.pdf.sig"); let shattered_2 = crate::tests::message("shattered-2.pdf"); let shattered_2_sig = crate::tests::message("shattered-2.pdf.sig"); let mut p = StandardPolicy::new(); p.accept_hash(types::HashAlgorithm::SHA1); // This fetches keys and computes the validity of the verification. struct Helper(Cert); impl VerificationHelper for Helper { fn get_certs(&mut self, _ids: &[KeyHandle]) -> Result<Vec<Cert>> { Ok(vec![self.0.clone()]) } fn check(&mut self, structure: MessageStructure) -> Result<()> { if let MessageLayer::SignatureGroup { results } = structure.into_iter().next().unwrap() { assert_eq!(results.len(), 1); assert!(results[0].is_err()); } else { unreachable!() } Ok(()) } } let h = Helper(cert.clone()); let mut v = DetachedVerifierBuilder::from_bytes(shattered_1_sig)? .with_policy(&p, None, h)?; v.verify_bytes(shattered_1)?; let h = Helper(cert); let mut v = DetachedVerifierBuilder::from_bytes(shattered_2_sig)? .with_policy(&p, None, h)?; v.verify_bytes(shattered_2)?; Ok(()) } } ���������������������������������������sequoia-openpgp-2.0.0/src/crypto/backend.rs���������������������������������������������������������0000644�0000000�0000000�00000010067�10461020230�0017007�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������//! Concrete implementation of the crypto primitives used by the rest of the //! crypto API. pub(crate) mod interface; pub(crate) mod sha1cd; // Nettle is the default backend, but on Windows targets we instead // enable CNG for running the tests in non-leaf crates that depend on // sequoia-openpgp. This creates a conflict, and makes `cargo test` // fail. To mitigate this, only enable the Nettle backend if we are // not compiling the tests and have a different backend selected. // // Note: If you add a new crypto backend, add it to the expression, // and also synchronize the expression to `build.rs`. #[cfg(all(feature = "crypto-nettle", not(all(feature = "__implicit-crypto-backend-for-tests", any(feature = "crypto-openssl", feature = "crypto-botan", feature = "crypto-botan2", feature = "crypto-fuzzing", feature = "crypto-rust")))))] mod nettle; #[cfg(all(feature = "crypto-nettle", not(all(feature = "__implicit-crypto-backend-for-tests", any(feature = "crypto-openssl", feature = "crypto-botan", feature = "crypto-botan2", feature = "crypto-fuzzing", feature = "crypto-rust")))))] pub use self::nettle::*; #[cfg(all(feature = "crypto-nettle", not(all(feature = "__implicit-crypto-backend-for-tests", any(feature = "crypto-openssl", feature = "crypto-botan", feature = "crypto-botan2", feature = "crypto-rust")))))] pub use self::nettle::Backend; // Nettle is the default backend, but on Windows targets we instead // enable CNG for running the tests in non-leaf crates that depend on // sequoia-openpgp. This creates a conflict, and makes `cargo test` // fail. To mitigate this, only enable the CNG backend if we are // not compiling the tests and have a different backend selected. // // Note: If you add a new crypto backend, add it to the expression, // and also synchronize the expression to `build.rs`. #[cfg(all(feature = "crypto-cng", not(all(feature = "__implicit-crypto-backend-for-tests", any(feature = "crypto-nettle", feature = "crypto-openssl", feature = "crypto-botan", feature = "crypto-botan2", feature = "crypto-fuzzing", feature = "crypto-rust")))))] mod cng; #[cfg(all(feature = "crypto-cng", not(all(feature = "__implicit-crypto-backend-for-tests", any(feature = "crypto-nettle", feature = "crypto-openssl", feature = "crypto-botan", feature = "crypto-botan2", feature = "crypto-fuzzing", feature = "crypto-rust")))))] pub use self::cng::*; #[cfg(all(feature = "crypto-cng", not(all(feature = "__implicit-crypto-backend-for-tests", any(feature = "crypto-nettle", feature = "crypto-openssl", feature = "crypto-botan", feature = "crypto-botan2", feature = "crypto-fuzzing", feature = "crypto-rust")))))] pub use self::cng::Backend; #[cfg(feature = "crypto-rust")] mod rust; #[cfg(feature = "crypto-rust")] pub use self::rust::*; #[cfg(feature = "crypto-rust")] pub use self::rust::Backend; #[cfg(feature = "crypto-openssl")] mod openssl; #[cfg(feature = "crypto-openssl")] pub use self::openssl::*; #[cfg(feature = "crypto-openssl")] pub use self::openssl::Backend; #[cfg(any(feature = "crypto-botan", feature = "crypto-botan2"))] mod botan; #[cfg(any(feature = "crypto-botan", feature = "crypto-botan2"))] pub use self::botan::*; #[cfg(feature = "crypto-botan")] pub use self::botan::Backend; #[cfg(feature = "crypto-fuzzing")] mod fuzzing; #[cfg(feature = "crypto-fuzzing")] pub use self::fuzzing::*; #[cfg(feature = "crypto-fuzzing")] pub use self::fuzzing::Backend; �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/src/crypto/ecdh.rs������������������������������������������������������������0000644�0000000�0000000�00000052640�10461020230�0016326�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������//! Elliptic-curve Diffie-Hellman. //! //! Sequoia implements the Elliptic-curve Diffie-Hellman key agreement //! protocol for use in OpenPGP as described by [RFC 6637]. In short, //! a shared secret is derived using Elliptic-curve Diffie-Hellman, a //! wrapping key is derived from that shared secret, and the message's //! session key is wrapped using that wrapping key. //! //! [RFC 6637]: https://tools.ietf.org/html/rfc6637 use crate::vec_truncate; use crate::{Error, Result}; use crate::crypto::SessionKey; use crate::crypto::mem::Protected; use crate::crypto::mpi::{self, MPI}; use crate::key; use crate::packet::Key; use crate::types::{Curve, HashAlgorithm, PublicKeyAlgorithm, SymmetricAlgorithm}; use crate::utils::{read_be_u64, write_be_u64}; #[allow(unused_imports)] pub(crate) use crate::crypto::backend::ecdh::{encrypt, decrypt}; /// Returns the default ECDH KDF hash function. pub(crate) fn default_ecdh_kdf_hash(curve: &Curve) -> HashAlgorithm { match curve { Curve::Cv25519 => HashAlgorithm::SHA256, // From RFC6637: Curve::NistP256 => HashAlgorithm::SHA256, Curve::NistP384 => HashAlgorithm::SHA384, Curve::NistP521 => HashAlgorithm::SHA512, // Extrapolated from RFC6637: Curve::BrainpoolP256 => HashAlgorithm::SHA256, Curve::BrainpoolP384 => HashAlgorithm::SHA384, Curve::BrainpoolP512 => HashAlgorithm::SHA512, // Conservative default. Curve::Ed25519 // Odd: Not an encryption algo. | Curve::Unknown(_) => HashAlgorithm::SHA512, } } /// Returns the default ECDH KEK cipher. pub(crate) fn default_ecdh_kek_cipher(curve: &Curve) -> SymmetricAlgorithm { match curve { Curve::Cv25519 => SymmetricAlgorithm::AES128, // From RFC6637: Curve::NistP256 => SymmetricAlgorithm::AES128, Curve::NistP384 => SymmetricAlgorithm::AES192, Curve::NistP521 => SymmetricAlgorithm::AES256, // Extrapolated from RFC6637: Curve::BrainpoolP256 => SymmetricAlgorithm::AES128, Curve::BrainpoolP384 => SymmetricAlgorithm::AES192, Curve::BrainpoolP512 => SymmetricAlgorithm::AES256, // Conservative default. Curve::Ed25519 // Odd: Not an encryption algo. | Curve::Unknown(_) => SymmetricAlgorithm::AES256, } } /// Wraps a session key. /// /// After using Elliptic-curve Diffie-Hellman to compute the shared /// secret, this function deterministically derives the wrapping key /// from the shared secret, and uses it to wrap (i.e. encrypt) the /// given session key. /// /// `VB` is the ephemeral public key encoded appropriately as MPI /// (i.e. with the 0x40 prefix for X25519, or 0x04 for the NIST /// curves), `S` is the shared Diffie-Hellman secret. #[allow(non_snake_case, dead_code)] pub(crate) fn encrypt_wrap<R>(recipient: &Key<key::PublicParts, R>, session_key: &SessionKey, VB: MPI, S: &Protected) -> Result<mpi::Ciphertext> where R: key::KeyRole { match recipient.mpis() { mpi::PublicKey::ECDH { ref curve, ref hash, ref sym,.. } => { // m = sym_alg_ID || session key || checksum || pkcs5_padding; let mut m = Vec::with_capacity(40); m.extend_from_slice(session_key); let m = pkcs5_pad(m.into(), 40)?; // Note: We always pad up to 40 bytes to obfuscate the // length of the symmetric key. // Compute KDF input. let param = make_param(recipient, curve, hash, sym); // Z_len = the key size for the KEK_alg_ID used with AESKeyWrap // Compute Z = KDF( S, Z_len, Param ); #[allow(non_snake_case)] let Z = kdf(S, sym.key_size()?, *hash, &param)?; // Compute C = AESKeyWrap( Z, m ) as per [RFC3394] #[allow(non_snake_case)] let C = aes_key_wrap(*sym, &Z, &m)?; // Output (MPI(VB) || len(C) || C). Ok(mpi::Ciphertext::ECDH { e: VB, key: C.into_boxed_slice(), }) } _ => Err(Error::InvalidArgument("Expected an ECDHPublicKey".into()).into()), } } /// Unwraps a session key. /// /// After using Elliptic-curve Diffie-Hellman to compute the shared /// secret, this function deterministically derives the wrapping key /// from the shared secret, and uses it to unwrap (i.e. decrypt) the /// session key. /// /// `recipient` is the message receiver's public key, `S` is the /// shared Diffie-Hellman secret used to encrypt `ciphertext`. #[allow(non_snake_case)] pub fn decrypt_unwrap(recipient: &Key<key::PublicParts, key::UnspecifiedRole>, S: &Protected, ciphertext: &mpi::Ciphertext, _plaintext_len: Option<usize>) -> Result<SessionKey> { match (recipient.mpis(), ciphertext) { (mpi::PublicKey::ECDH { ref curve, ref hash, ref sym, ..}, mpi::Ciphertext::ECDH { ref key, .. }) => { // Compute KDF input. let param = make_param(recipient, curve, hash, sym); // Z_len = the key size for the KEK_alg_ID used with AESKeyWrap // Compute Z = KDF( S, Z_len, Param ); #[allow(non_snake_case)] let Z = kdf(S, sym.key_size()?, *hash, &param)?; // Compute m = AESKeyUnwrap( Z, C ) as per [RFC3394] let m = aes_key_unwrap(*sym, &Z, key)?; let m = pkcs5_unpad(m)?; Ok(m.into()) }, _ => Err(Error::InvalidArgument( "Expected an ECDH key and ciphertext".into()).into()), } } /// Derives a secret key for session key wrapping. /// /// See [Section 7 of RFC 6637]. /// /// [Section 7 of RFC 6637]: https://tools.ietf.org/html/rfc6637#section-7 fn kdf(x: &Protected, obits: usize, hash: HashAlgorithm, param: &[u8]) -> Result<Protected> { let mut hash = hash.context()?.for_digest(); if obits > hash.digest_size() { return Err( Error::InvalidArgument("Hash digest too short".into()).into()); } hash.update(&[0, 0, 0, 1]); hash.update(x); hash.update(param); // Providing a smaller buffer will truncate the digest. let mut key: Protected = vec![0; obits].into(); hash.digest(&mut key)?; Ok(key) } /// Pads a session key using PKCS5. /// /// See [Section 8 of RFC 6637]. /// /// [Section 8 of RFC 6637]: https://tools.ietf.org/html/rfc6637#section-8 #[allow(dead_code)] fn pkcs5_pad(sk: Protected, target_len: usize) -> Result<Protected> { if sk.len() > target_len { return Err(Error::InvalidArgument( "Plaintext data too large".into()).into()); } // !!! THIS FUNCTION MUST NOT FAIL FROM THIS POINT ON !!! let mut buf: Vec<u8> = sk.expose_into_unprotected_vec(); let missing = target_len - buf.len(); assert!(missing <= 0xff); for _ in 0..missing { buf.push(missing as u8); } assert_eq!(buf.len(), target_len); Ok(buf.into()) } /// Removes PKCS5 padding from a session key. /// /// See [Section 8 of RFC 6637]. /// /// [Section 8 of RFC 6637]: https://tools.ietf.org/html/rfc6637#section-8 fn pkcs5_unpad(sk: Protected) -> Result<Protected> { if sk.len() > 0xff { return Err(Error::InvalidArgument("message too large".into()).into()); } let mut buf: Vec<u8> = sk.expose_into_unprotected_vec(); let mut good = true; let padding = buf[buf.len() - 1]; let target_len = buf.len() - padding as usize; for &b in &buf[target_len..] { good = b == padding && good; } if good { vec_truncate(&mut buf, target_len); Ok(buf.into()) } else { let sk: Protected = buf.into(); drop(sk); Err(Error::InvalidArgument("bad padding".into()).into()) } } /// Wraps a key using the AES Key Wrap Algorithm. /// /// See [RFC 3394]. /// /// [RFC 3394]: https://tools.ietf.org/html/rfc3394 pub fn aes_key_wrap(algo: SymmetricAlgorithm, key: &Protected, plaintext: &Protected) -> Result<Vec<u8>> { if plaintext.len() % 8 != 0 { return Err(Error::InvalidArgument( "Plaintext must be a multiple of 8".into()).into()); } if key.len() != algo.key_size()? { return Err(Error::InvalidArgument("Bad key size".into()).into()); } let mut cipher = algo.make_encrypt_ecb(key)?; // Inputs: Plaintext, n 64-bit values {P1, P2, ..., Pn}, and // Key, K (the KEK). // Outputs: Ciphertext, (n+1) 64-bit values {C0, C1, ..., Cn}. let n = plaintext.len() / 8; let mut ciphertext = vec![0; 8 + plaintext.len()]; // 1) Initialize variables. // // Set A = IV, an initial value (see 2.2.3) let mut a = AES_KEY_WRAP_IV; { // For i = 1 to n // R[i] = P[i] let r = &mut ciphertext[8..]; r.copy_from_slice(plaintext); let mut b = [0; 16]; let mut tmp = [0; 16]; // 2) Calculate intermediate values. // For j = 0 to 5 for j in 0..6 { // For i=1 to n for i in 0..n { // B = AES(K, A | R[i]) write_be_u64(&mut tmp[..8], a); tmp[8..].copy_from_slice(&r[8 * i..8 * (i + 1)]); cipher.encrypt(&mut b, &tmp)?; // A = MSB(64, B) ^ t where t = (n*j)+i a = read_be_u64(&b[..8]) ^ ((n * j) + i + 1) as u64; // Note that our i runs from 0 to n-1 instead of 1 to // n, hence the index shift. // R[i] = LSB(64, B) r[8 * i..8 * (i + 1)].copy_from_slice(&b[8..]); } } } // 3) Output the results. // // Set C[0] = A // For i = 1 to n // C[i] = R[i] write_be_u64(&mut ciphertext[..8], a); Ok(ciphertext) } /// Unwraps an encrypted key using the AES Key Wrap Algorithm. /// /// See [RFC 3394]. /// /// [RFC 3394]: https://tools.ietf.org/html/rfc3394 pub fn aes_key_unwrap(algo: SymmetricAlgorithm, key: &Protected, ciphertext: &[u8]) -> Result<Protected> { if ciphertext.len() % 8 != 0 { return Err(Error::InvalidArgument( "Ciphertext must be a multiple of 8".into()).into()); } if key.len() != algo.key_size()? { return Err(Error::InvalidArgument("Bad key size".into()).into()); } let mut cipher = algo.make_decrypt_ecb(key)?; // Inputs: Ciphertext, (n+1) 64-bit values {C0, C1, ..., Cn}, and // Key, K (the KEK). // Outputs: Plaintext, n 64-bit values {P1, P2, ..., Pn}. let n = ciphertext.len() / 8 - 1; let mut plaintext = Vec::with_capacity(ciphertext.len() - 8); // 1) Initialize variables. // // Set A = C[0] // For i = 1 to n // R[i] = C[i] let mut a = read_be_u64(&ciphertext[..8]); plaintext.extend_from_slice(&ciphertext[8..]); let mut plaintext: Protected = plaintext.into(); // 2) Calculate intermediate values. { let r = &mut plaintext; let mut b = [0; 16]; let mut tmp = [0; 16]; // For j = 5 to 0 for j in (0..=5).rev() { // For i = n to 1 for i in (0..=n-1).rev() { // B = AES-1(K, (A ^ t) | R[i]) where t = n*j+i write_be_u64(&mut tmp[..8], a ^ ((n * j) + i + 1) as u64); tmp[8..].copy_from_slice(&r[8 * i..8 * (i + 1)]); // Note that our i runs from n-1 to 0 instead of n to // 1, hence the index shift. cipher.decrypt(&mut b, &tmp)?; // A = MSB(64, B) a = read_be_u64(&b[..8]); // R[i] = LSB(64, B) r[8 * i..8 * (i + 1)].copy_from_slice(&b[8..]); } } } // 3) Output results. // // If A is an appropriate initial value (see 2.2.3), // Then // For i = 1 to n // P[i] = R[i] // Else // Return an error if a == AES_KEY_WRAP_IV { Ok(plaintext) } else { Err(Error::InvalidArgument("Bad key".into()).into()) } } fn make_param<P, R>(recipient: &Key<P, R>, curve: &Curve, hash: &HashAlgorithm, sym: &SymmetricAlgorithm) -> Vec<u8> where P: key::KeyParts, R: key::KeyRole { // Param = curve_OID_len || curve_OID || // public_key_alg_ID || 03 || 01 || KDF_hash_ID || // KEK_alg_ID for AESKeyWrap || "Anonymous Sender " || // recipient_fingerprint; let fp = recipient.fingerprint(); let mut param = Vec::with_capacity( 1 + curve.oid().len() // Length and Curve OID, + 1 // Public key algorithm ID, + 4 // KDF parameters, + 20 // "Anonymous Sender ", + fp.as_bytes().len()); // Recipients key fingerprint. param.push(curve.oid().len() as u8); param.extend_from_slice(curve.oid()); param.push(PublicKeyAlgorithm::ECDH.into()); // KDF parameters. param.push(3); // Octet count of the following parameters. param.push(1); // 1-octet value 0x01, reserved for future extensions. param.push((*hash).into()); param.push((*sym).into()); param.extend_from_slice(b"Anonymous Sender "); param.extend_from_slice(fp.as_bytes()); assert_eq!(param.len(), 1 + curve.oid().len() // Length and Curve OID, + 1 // Public key algorithm ID, + 4 // KDF parameters, + 20 // "Anonymous Sender ", + fp.as_bytes().len()); // Recipients key fingerprint. param } const AES_KEY_WRAP_IV: u64 = 0xa6a6a6a6a6a6a6a6; #[cfg(test)] mod tests { use super::*; #[test] fn pkcs5_padding() { let v = pkcs5_pad(vec![0, 0, 0].into(), 8).unwrap(); assert_eq!(&v, &Protected::from(&[0, 0, 0, 5, 5, 5, 5, 5][..])); let v = pkcs5_unpad(v).unwrap(); assert_eq!(&v, &Protected::from(&[0, 0, 0][..])); let v = pkcs5_pad(vec![].into(), 8).unwrap(); assert_eq!(&v, &Protected::from(&[8, 8, 8, 8, 8, 8, 8, 8][..])); let v = pkcs5_unpad(v).unwrap(); assert_eq!(&v, &Protected::from(&[][..])); } #[test] fn aes_wrapping() { struct Test { algo: SymmetricAlgorithm, kek: &'static [u8], key_data: &'static [u8], ciphertext: &'static [u8], } // These are the test vectors from RFC3394. const TESTS: &[Test] = &[ Test { algo: SymmetricAlgorithm::AES128, kek: &[0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F], key_data: &[0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF], ciphertext: &[0x1F, 0xA6, 0x8B, 0x0A, 0x81, 0x12, 0xB4, 0x47, 0xAE, 0xF3, 0x4B, 0xD8, 0xFB, 0x5A, 0x7B, 0x82, 0x9D, 0x3E, 0x86, 0x23, 0x71, 0xD2, 0xCF, 0xE5], }, Test { algo: SymmetricAlgorithm::AES192, kek: &[0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17], key_data: &[0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF], ciphertext: &[0x96, 0x77, 0x8B, 0x25, 0xAE, 0x6C, 0xA4, 0x35, 0xF9, 0x2B, 0x5B, 0x97, 0xC0, 0x50, 0xAE, 0xD2, 0x46, 0x8A, 0xB8, 0xA1, 0x7A, 0xD8, 0x4E, 0x5D], }, Test { algo: SymmetricAlgorithm::AES256, kek: &[0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F], key_data: &[0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF], ciphertext: &[0x64, 0xE8, 0xC3, 0xF9, 0xCE, 0x0F, 0x5B, 0xA2, 0x63, 0xE9, 0x77, 0x79, 0x05, 0x81, 0x8A, 0x2A, 0x93, 0xC8, 0x19, 0x1E, 0x7D, 0x6E, 0x8A, 0xE7], }, Test { algo: SymmetricAlgorithm::AES192, kek: &[0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17], key_data: &[0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07], ciphertext: &[0x03, 0x1D, 0x33, 0x26, 0x4E, 0x15, 0xD3, 0x32, 0x68, 0xF2, 0x4E, 0xC2, 0x60, 0x74, 0x3E, 0xDC, 0xE1, 0xC6, 0xC7, 0xDD, 0xEE, 0x72, 0x5A, 0x93, 0x6B, 0xA8, 0x14, 0x91, 0x5C, 0x67, 0x62, 0xD2], }, Test { algo: SymmetricAlgorithm::AES256, kek: &[0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F], key_data: &[0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07], ciphertext: &[0xA8, 0xF9, 0xBC, 0x16, 0x12, 0xC6, 0x8B, 0x3F, 0xF6, 0xE6, 0xF4, 0xFB, 0xE3, 0x0E, 0x71, 0xE4, 0x76, 0x9C, 0x8B, 0x80, 0xA3, 0x2C, 0xB8, 0x95, 0x8C, 0xD5, 0xD1, 0x7D, 0x6B, 0x25, 0x4D, 0xA1], }, Test { algo: SymmetricAlgorithm::AES256, kek: &[0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F], key_data: &[0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F], ciphertext: &[0x28, 0xC9, 0xF4, 0x04, 0xC4, 0xB8, 0x10, 0xF4, 0xCB, 0xCC, 0xB3, 0x5C, 0xFB, 0x87, 0xF8, 0x26, 0x3F, 0x57, 0x86, 0xE2, 0xD8, 0x0E, 0xD3, 0x26, 0xCB, 0xC7, 0xF0, 0xE7, 0x1A, 0x99, 0xF4, 0x3B, 0xFB, 0x98, 0x8B, 0x9B, 0x7A, 0x02, 0xDD, 0x21], }, ]; for test in TESTS { let ciphertext = aes_key_wrap(test.algo, &test.kek.into(), &test.key_data.into()) .unwrap(); assert_eq!(test.ciphertext, &ciphertext[..]); let key_data = aes_key_unwrap(test.algo, &test.kek.into(), &ciphertext[..]) .unwrap(); assert_eq!(&Protected::from(test.key_data), &key_data); } } #[test] fn cv25519_generation() -> Result<()> { const CURVE25519_SIZE: usize = 32; fn check_clamping<S: AsRef<[u8]>>(s: S) { // Curve25519 Paper, Sec. 3: A user can, for example, // generate 32 uniform random bytes, clear bits 0, 1, 2 of // the first byte, clear bit 7 of the last byte, and set // bit 6 of the last byte. // OpenPGP stores the secret in reverse order. const FIRST: usize = CURVE25519_SIZE - 1; const LAST: usize = 0; let s = s.as_ref(); assert_eq!(s[FIRST] & ! 0b1111_1000, 0, "bits 0, 1 and 2 of the first byte should be cleared"); assert_eq!(s[LAST] & 0b1100_0000, 0b0100_0000, "bits 7 should be cleared and bit 6 should be set in the last byte"); } for _ in 0..5 { let k: key::Key4<_, key::SubordinateRole> = key::Key4::generate_ecc(false, Curve::Cv25519)?; match k.secret() { key::SecretKeyMaterial::Unencrypted(m) => m.map(|mpis| { match mpis { mpi::SecretKeyMaterial::ECDH { scalar } => check_clamping(scalar.value()), o => panic!("unexpected key material: {:?}", o), } }), o => panic!("expected unencrypted material: {:?}", o), } } Ok(()) } } ������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/src/crypto/hash.rs������������������������������������������������������������0000644�0000000�0000000�00000124526�10461020230�0016351�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������//! Cryptographic hash functions and hashing of OpenPGP data //! structures. //! //! This module provides struct [`Context`] representing a hash //! function context independent of the cryptographic backend, as well //! as trait [`Hash`] that handles hashing of OpenPGP data structures. //! //! //! # Examples //! //! ```rust //! # fn main() -> sequoia_openpgp::Result<()> { //! use sequoia_openpgp::types::HashAlgorithm; //! //! // Create a context and feed data to it. //! let mut ctx = HashAlgorithm::SHA512.context()?.for_digest(); //! ctx.update(&b"The quick brown fox jumps over the lazy dog."[..]); //! //! // Extract the digest. //! let mut digest = vec![0; ctx.digest_size()]; //! ctx.digest(&mut digest); //! //! use sequoia_openpgp::fmt::hex; //! assert_eq!(&hex::encode(digest), //! "91EA1245F20D46AE9A037A989F54F1F7\ //! 90F0A47607EEB8A14D12890CEA77A1BB\ //! C6C7ED9CF205E67B7F2B8FD4C7DFD3A7\ //! A8617E45F3C463D481C7E586C39AC1ED"); //! # Ok(()) } //! ``` use std::{ convert::TryFrom, sync::OnceLock, }; use dyn_clone::DynClone; use crate::HashAlgorithm; use crate::packet::Key; use crate::packet::UserID; use crate::packet::UserAttribute; use crate::packet::key; use crate::packet::key::{Key4, Key6}; use crate::packet::Signature; use crate::packet::signature::{self, Signature3, Signature4, Signature6}; use crate::Error; use crate::Result; use crate::types::{SignatureType, Timestamp}; use std::fs::{File, OpenOptions}; use std::io::{self, Write}; // If set to e.g. Some("/tmp/hash"), we will dump everything that is // hashed to files /tmp/hash-N, where N is a number. const DUMP_HASHED_VALUES: Option<&str> = None; // ASN.1 OID values copied from the nettle-rs crate: // https://gitlab.com/sequoia-pgp/nettle-rs/-/blob/main/src/rsa/pkcs1.rs#L22 /// ASN.1 OID for MD5 const ASN1_OID_MD5: &[u8] = &[ 0x30, 0x20, 0x30, 0x0c, 0x06, 0x08, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x02, 0x05, 0x05, 0x00, 0x04, 0x10, ]; /// ASN.1 OID for RipeMD160 const ASN1_OID_RIPEMD160: &[u8] = &[ 0x30, 0x21, 0x30, 0x09, 0x06, 0x05, 0x2B, 0x24, 0x03, 0x02, 0x01, 0x05, 0x00, 0x04, 0x14, ]; /// ASN.1 OID for SHA1 const ASN1_OID_SHA1: &[u8] = &[ 0x30, 0x21, 0x30, 0x09, 0x06, 0x05, 0x2b, 0x0e, 0x03, 0x02, 0x1a, 0x05, 0x00, 0x04, 0x14, ]; /// ASN.1 OID for SHA224 const ASN1_OID_SHA224: &[u8] = &[ 0x30, 0x2D, 0x30, 0x0D, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x04, 0x05, 0x00, 0x04, 0x1C, ]; /// ASN.1 OID for SHA256 const ASN1_OID_SHA256: &[u8] = &[ 0x30, 0x31, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01, 0x05, 0x00, 0x04, 0x20, ]; /// ASN.1 OID for SHA384 const ASN1_OID_SHA384: &[u8] = &[ 0x30, 0x41, 0x30, 0x0D, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x02, 0x05, 0x00, 0x04, 0x30, ]; /// ASN.1 OID for SHA512 const ASN1_OID_SHA512: &[u8] = &[ 0x30, 0x51, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x03, 0x05, 0x00, 0x04, 0x40, ]; /// ASN.1 OID for SHA3-256 const ASN1_OID_SHA3_256: &[u8] = &[ 0x30, 0x31, 0x30, 0x0D, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x08, 0x05, 0x00, 0x04, 0x20 ]; /// ASN.1 OID for SHA3-512. const ASN1_OID_SHA3_512: &[u8] = &[ 0x30, 0x51, 0x30, 0x0D, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x0a, 0x05, 0x00, 0x04, 0x40 ]; /// List of hashes that the signer may produce. /// /// This list is ordered by the preference so that the most preferred /// hash algorithm is first. pub(crate) fn default_hashes() -> &'static [HashAlgorithm] { static DEFAULT_HASHES: OnceLock<Vec<HashAlgorithm>> = OnceLock::new(); DEFAULT_HASHES.get_or_init(|| vec![ HashAlgorithm::default(), HashAlgorithm::SHA512, HashAlgorithm::SHA384, HashAlgorithm::SHA256, HashAlgorithm::SHA224, HashAlgorithm::SHA1, HashAlgorithm::RipeMD, HashAlgorithm::MD5, ]) } /// List of hashes that the signer may produce. /// /// This list is sorted. pub(crate) fn default_hashes_sorted() -> &'static [HashAlgorithm] { static DEFAULT_HASHES: OnceLock<Vec<HashAlgorithm>> = OnceLock::new(); DEFAULT_HASHES.get_or_init(|| { let mut hashes = default_hashes().to_vec(); hashes.sort(); hashes }) } /// Hasher capable of calculating a digest for the input byte stream. /// /// This provides an abstract interface to the hash functions used in /// OpenPGP. It is used by the crypto backends to provide a uniform /// interface to hash functions. pub(crate) trait Digest: DynClone + Write + Send + Sync { /// Writes data into the hash function. fn update(&mut self, data: &[u8]); /// Finalizes the hash function and writes the digest into the /// provided slice. /// /// Resets the hash function contexts. /// /// `digest` must be at least `self.digest_size()` bytes large, /// otherwise the digest will be truncated. fn digest(&mut self, digest: &mut [u8]) -> Result<()>; } dyn_clone::clone_trait_object!(Digest); impl Digest for Box<dyn Digest> { fn update(&mut self, data: &[u8]) { self.as_mut().update(data) } fn digest(&mut self, digest: &mut [u8]) -> Result<()>{ self.as_mut().digest(digest) } } /// A hash algorithm context. /// /// Provides additional metadata for the hashing contexts. This is /// implemented here once, so that the backends don't have to provide /// it. #[derive(Clone)] pub struct Context { /// The hash algorithm. algo: HashAlgorithm, /// Whether we are hashing for a signature, and if so, which /// version. for_signature: Option<u8>, /// The underlying bare hash context. ctx: Box<dyn Digest>, } impl Context { /// Returns the algorithm. pub fn algo(&self) -> HashAlgorithm { self.algo } /// Size of the digest in bytes. pub fn digest_size(&self) -> usize { self.algo.digest_size() .expect("we only create Contexts for known hash algos") } /// Writes data into the hash function. pub fn update(&mut self, data: &[u8]) { self.ctx.update(data) } /// Finalizes the hash function and writes the digest into the /// provided slice. /// /// Resets the hash function contexts. /// /// `digest` must be at least `self.digest_size()` bytes large, /// otherwise the digest will be truncated. pub fn digest(&mut self, digest: &mut [u8]) -> Result<()>{ self.ctx.digest(digest) } /// Finalizes the hash function and computes the digest. pub fn into_digest(mut self) -> Result<Vec<u8>> where Self: std::marker::Sized { let mut digest = vec![0u8; self.digest_size()]; self.digest(&mut digest)?; Ok(digest) } /// Returns whether we are hashing for a signature, and if so, /// which version. fn for_signature(&self) -> Option<u8> { self.for_signature.clone() } } impl io::Write for Context { fn write(&mut self, buf: &[u8]) -> io::Result<usize> { self.ctx.write(buf) } fn flush(&mut self) -> io::Result<()> { self.ctx.flush() } } /// Builds hash contexts. pub struct Builder(Context); impl Builder { /// Returns a hash context for signing and verification of OpenPGP /// signatures. pub fn for_signature(self, version: u8) -> Context { let mut ctx = self.0; ctx.for_signature = Some(version); ctx } /// Returns a hash context for general hashing, i.e. not for the /// purpose of signing and verification of OpenPGP signatures pub fn for_digest(self) -> Context { self.0 } } impl HashAlgorithm { /// Creates a new hash context for this algorithm. /// /// # Errors /// /// Fails with `Error::UnsupportedHashAlgorithm` if Sequoia does /// not support this algorithm. See /// [`HashAlgorithm::is_supported`]. /// /// [`HashAlgorithm::is_supported`]: HashAlgorithm::is_supported() pub fn context(self) -> Result<Builder> { // Create contexts only for known hashes. self.digest_size()?; let mut hasher: Box<dyn Digest> = match self { HashAlgorithm::SHA1 if ! cfg!(feature = "crypto-fuzzing") => Box::new(crate::crypto::backend::sha1cd::build()), _ => self.new_hasher()?, }; if let Some(prefix) = DUMP_HASHED_VALUES { hasher = Box::new(HashDumper::new(hasher, prefix)) } Ok(Builder(Context { algo: self, for_signature: None, ctx: hasher, })) } /// Returns the prefix of a serialized `DigestInfo` structure /// that contains the ASN.1 OID of this hash algorithm. /// /// The prefix is used for encoding RSA signatures according to /// the `EMSA-PKCS1-v1_5` algorithm as specified in [RFC 8017]. /// /// [RFC 8017]: https://www.rfc-editor.org/rfc/rfc8017.html#section-9.2 /// /// ``` /// # use sequoia_openpgp::types::HashAlgorithm; /// # fn main() -> sequoia_openpgp::Result<()> { /// let algo = HashAlgorithm::SHA512; /// let digest = // raw bytes of the digest /// # Vec::<u8>::new(); /// let digest_info = Vec::from(algo.oid()?).extend(digest); /// # Ok(()) } /// ``` /// /// # Errors /// /// Fails with `Error::UnsupportedHashAlgorithm` for unknown or /// private hash algorithms. pub fn oid(self) -> Result<&'static [u8]> { match self { HashAlgorithm::SHA1 => Ok(ASN1_OID_SHA1), HashAlgorithm::SHA224 => Ok(ASN1_OID_SHA224), HashAlgorithm::SHA256 => Ok(ASN1_OID_SHA256), HashAlgorithm::SHA384 => Ok(ASN1_OID_SHA384), HashAlgorithm::SHA512 => Ok(ASN1_OID_SHA512), HashAlgorithm::SHA3_256 => Ok(ASN1_OID_SHA3_256), HashAlgorithm::SHA3_512 => Ok(ASN1_OID_SHA3_512), HashAlgorithm::MD5 => Ok(ASN1_OID_MD5), HashAlgorithm::RipeMD => Ok(ASN1_OID_RIPEMD160), HashAlgorithm::Private(_) | HashAlgorithm::Unknown(_) => Err(crate::Error::UnsupportedHashAlgorithm(self).into()), } } } struct HashDumper { hasher: Box<dyn Digest>, sink: File, filename: String, written: usize, } impl HashDumper { fn new(hasher: Box<dyn Digest>, prefix: &str) -> Self { let mut n = 0; let mut filename; let sink = loop { filename = format!("{}-{}", prefix, n); match OpenOptions::new().write(true).create_new(true) .open(&filename) { Ok(f) => break f, Err(_) => n += 1, } }; eprintln!("HashDumper: Writing to {}...", &filename); HashDumper { hasher, sink, filename, written: 0, } } } impl Clone for HashDumper { fn clone(&self) -> HashDumper { // We only ever create instances of HashDumper when debugging. // Whenever we're cloning an instance, just open another file for // inspection. let prefix = DUMP_HASHED_VALUES .expect("cloning a HashDumper but DUMP_HASHED_VALUES wasn't specified"); HashDumper::new(self.hasher.clone(), prefix) } } impl Drop for HashDumper { fn drop(&mut self) { eprintln!("HashDumper: Wrote {} bytes to {}...", self.written, self.filename); } } impl Digest for HashDumper { fn update(&mut self, data: &[u8]) { self.hasher.update(data); self.sink.write_all(data).unwrap(); self.written += data.len(); } fn digest(&mut self, digest: &mut [u8]) -> Result<()> { self.hasher.digest(digest) } } impl io::Write for HashDumper { fn write(&mut self, buf: &[u8]) -> io::Result<usize> { self.update(buf); Ok(buf.len()) } fn flush(&mut self) -> io::Result<()> { self.hasher.flush() } } /// Hashes OpenPGP packets and related types. /// /// Some OpenPGP data structures need to be hashed to be covered by /// OpenPGP signatures. Hashing is often based on the serialized /// form, with some aspects fixed to ensure consistent results. This /// trait implements hashing as specified by OpenPGP. /// /// Most of the time it is not necessary to manually compute hashes. /// Instead, higher level functionality, like the streaming /// [`Verifier`], [`DetachedVerifier`], or [`Signature`'s verification /// functions] should be used, which handle the hashing internally. /// /// [`Verifier`]: crate::parse::stream::Verifier /// [`DetachedVerifier`]: crate::parse::stream::DetachedVerifier /// [`Signature`'s verification functions]: crate::packet::Signature#verification-functions /// /// This is a low-level mechanism. See [`Signature`'s hashing /// functions] for how to hash compounds like (Key,UserID)-bindings. /// /// [`Signature`'s hashing functions]: crate::packet::Signature#hashing-functions pub trait Hash { /// Updates the given hash with this object. fn hash(&self, hash: &mut Context) -> Result<()>; } impl Hash for UserID { fn hash(&self, hash: &mut Context) -> Result<()> { let len = self.value().len() as u32; let mut header = [0; 5]; header[0] = 0xB4; header[1..5].copy_from_slice(&len.to_be_bytes()); hash.update(&header); hash.update(self.value()); Ok(()) } } impl Hash for UserAttribute { fn hash(&self, hash: &mut Context) -> Result<()> { let len = self.value().len() as u32; let mut header = [0; 5]; header[0] = 0xD1; header[1..5].copy_from_slice(&len.to_be_bytes()); hash.update(&header); hash.update(self.value()); Ok(()) } } impl<P, R> Hash for Key<P, R> where P: key::KeyParts, R: key::KeyRole, { fn hash(&self, hash: &mut Context) -> Result<()> { match self { Key::V4(k) => k.hash(hash), Key::V6(k) => k.hash(hash), } } } /// Writes the appropriate hash prefix for keys. /// /// In RFC9580, the way key packets are hashed depends not on the /// version of the key packet, but on the version of the signature /// that is being verified or generated. /// /// See [Computing Signatures]. /// /// [Computing Signatures]: https://www.rfc-editor.org/rfc/rfc9580.html#name-computing-signatures fn write_key_hash_header(header: &mut Vec<u8>, public_len: usize, ctx: &Context) -> Result<()> { match ctx.for_signature() { None => Err(crate::Error::InvalidOperation( "cannot hash key without knowing the signature version" .into()).into()), Some(3) | Some(4) => { // When a version 4 signature is made over a key, the hash // data starts with the octet 0x99, followed by a 2-octet // length of the key, followed by the body of the key // packet. // Note: Reading RFC2440, this is also how keys should be // hashed for version 3 signatures. // Tag. header.push(0x99); // Length (2 bytes, big endian). header.extend_from_slice( &u16::try_from(public_len)?.to_be_bytes()); Ok(()) }, Some(6) => { // When a version 6 signature is made over a key, the hash // data starts with the [..] octet 0x9B, followed by a // 4-octet length of the key, followed by the body of the // key packet. // Tag. header.push(0x9b); // Length (4 bytes, big endian). header.extend_from_slice( &u32::try_from(public_len)?.to_be_bytes()); Ok(()) }, Some(n) => Err(crate::Error::InvalidOperation(format!( "don't know how to hash key for v{} signatures", n) ).into()), } } impl<P, R> Hash for Key4<P, R> where P: key::KeyParts, R: key::KeyRole, { fn hash(&self, hash: &mut Context) -> Result<()> { use crate::serialize::MarshalInto; // We hash 9 bytes plus the MPIs. But, the len doesn't // include the tag (1 byte) or the length (2 bytes). let len = (9 - 3) + self.mpis().serialized_len(); // Note: When making a v6 signature over the key, we hash a // four octet length instead of a two octet length. Reserve // two extra bytes. // // XXX: Use SmallVec to avoid heap allocations. let mut header: Vec<u8> = Vec::with_capacity(9 + 2); // Write the appropriate header. This depends on the version // of the signature we hash the data for. write_key_hash_header(&mut header, len, hash)?; // Version. header.push(4); // Creation time. let creation_time: u32 = self.creation_time_raw().into(); header.extend_from_slice(&creation_time.to_be_bytes()); // Algorithm. header.push(self.pk_algo().into()); // Hash the header. hash.update(&header[..]); // MPIs. self.mpis().hash(hash)?; Ok(()) } } impl<P, R> Hash for Key6<P, R> where P: key::KeyParts, R: key::KeyRole, { fn hash(&self, hash: &mut Context) -> Result<()> { use crate::serialize::MarshalInto; // We hash 15 bytes plus the MPIs. But, the len doesn't // include the tag (1 byte) or the length (4 bytes). let len = (15 - 5) + self.mpis().serialized_len(); // XXX: Use SmallVec to avoid heap allocations. let mut header: Vec<u8> = Vec::with_capacity(15); // Write the appropriate header. This depends on the version // of the signature we hash the data for. write_key_hash_header(&mut header, len, hash)?; // Version. header.push(6); // Creation time. let creation_time: u32 = self.creation_time_raw().into(); header.extend_from_slice(&creation_time.to_be_bytes()); // Algorithm. header.push(self.pk_algo().into()); // Length of all MPIs. header.extend_from_slice( &(self.mpis().serialized_len() as u32).to_be_bytes()); // Hash the header. hash.update(&header[..]); // MPIs. self.mpis().hash(hash)?; Ok(()) } } impl Hash for Signature { fn hash(&self, hash: &mut Context) -> Result<()> { match self { Signature::V3(sig) => sig.hash(hash), Signature::V4(sig) => sig.hash(hash), Signature::V6(sig) => sig.hash(hash), } } } impl Hash for Signature3 { fn hash(&self, hash: &mut Context) -> Result<()> { Self::hash_fields(hash, self) } } impl Signature3 { /// Hashes this signature. /// /// Because we need to call this from SignatureFields::hash, we /// provide this as associated method. fn hash_fields(hash: &mut Context, f: &signature::SignatureFields) -> Result<()> { let mut buffer = [0u8; 5]; // Signature type. buffer[0] = u8::from(f.typ()); // Creation time. let creation_time: u32 = Timestamp::try_from( f.signature_creation_time() .unwrap_or(std::time::UNIX_EPOCH)) .unwrap_or_else(|_| Timestamp::from(0)) .into(); buffer[1] = (creation_time >> 24) as u8; buffer[2] = (creation_time >> 16) as u8; buffer[3] = (creation_time >> 8) as u8; buffer[4] = (creation_time ) as u8; hash.update(&buffer[..]); Ok(()) } } impl Hash for Signature4 { fn hash(&self, hash: &mut Context) -> Result<()> { Self::hash_fields(hash, &self.fields) } } impl Signature4 { /// Hashes this signature. /// /// Because we need to call this from SignatureFields::hash, we /// provide this as associated method. fn hash_fields(mut hash: &mut Context, f: &signature::SignatureFields) -> Result<()> { use crate::serialize::{Marshal, MarshalInto}; // A version 4 signature packet is laid out as follows: // // version - 1 byte \ // type - 1 byte \ // pk_algo - 1 byte \ // hash_algo - 1 byte Included in the hash // hashed_area_len - 2 bytes (big endian)/ // hashed_area _/ // ... <- Not included in the hash let mut header = [0u8; 6]; // Version. header[0] = 4; header[1] = f.typ().into(); header[2] = f.pk_algo().into(); header[3] = f.hash_algo().into(); // The length of the hashed area, as a 16-bit big endian number. let hashed_area_len = f.hashed_area().serialized_len(); header[4..6].copy_from_slice(&(hashed_area_len as u16).to_be_bytes()); hash.update(&header[..]); f.hashed_area().serialize(&mut hash as &mut dyn Write)?; // A version 4 signature trailer is: // // version - 1 byte // 0xFF (constant) - 1 byte // amount - 4 bytes (big endian) // // The amount field is the amount of hashed from this // packet (this excludes the message content, and this // trailer). // // See https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.4 let mut trailer = [0u8; 6]; trailer[0] = 4; trailer[1] = 0xff; // The signature packet's length, not including the previous // two bytes and the length. let len = (header.len() + hashed_area_len) as u32; trailer[2..6].copy_from_slice(&len.to_be_bytes()); hash.update(&trailer[..]); Ok(()) } } impl Hash for Signature6 { fn hash(&self, hash: &mut Context) -> Result<()> { Self::hash_fields(hash, &self.fields) } } impl Signature6 { fn hash_fields(mut hash: &mut Context, sig: &signature::SignatureFields) -> Result<()> { use crate::serialize::{Marshal, MarshalInto}; // A version 6 signature packet is laid out as follows: // // version - 1 byte \ // type - 1 byte \ // pk_algo - 1 byte \ // hash_algo - 1 byte Included in the hash // hashed_area_len - 4 bytes (big endian)/ // hashed_area _/ // ... <- Not included in the hash let mut header = [0u8; 8]; // Version. header[0] = 6; header[1] = sig.typ().into(); header[2] = sig.pk_algo().into(); header[3] = sig.hash_algo().into(); // The length of the hashed area, as a 32-bit big endian number. let hashed_area_len = sig.hashed_area().serialized_len(); header[4..8].copy_from_slice(&(hashed_area_len as u32).to_be_bytes()); hash.update(&header[..]); sig.hashed_area().serialize(&mut hash as &mut dyn Write)?; // A version 6 signature trailer is: // // version - 1 byte // 0xFF (constant) - 1 byte // amount - 4 bytes (big endian) // // The amount field is the amount of hashed from this // packet (this excludes the message content, and this // trailer) modulo 2**32. // // See https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.4 let mut trailer = [0u8; 6]; trailer[0] = 6; trailer[1] = 0xff; // The signature packet's length, not including the previous // two bytes and the length modulo 2**32. let len = (header.len() + hashed_area_len) as u32; trailer[2..6].copy_from_slice(&len.to_be_bytes()); hash.update(&trailer[..]); Ok(()) } } impl Hash for signature::SignatureFields { fn hash(&self, hash: &mut Context) -> Result<()> { match self.version() { 3 => Signature3::hash_fields(hash, self), 4 => Signature4::hash_fields(hash, self), 6 => Signature6::hash_fields(hash, self), n => Err(Error::InvalidOperation(format!( "cannot hash a version {} signature packet", n) ).into()), } } } impl Hash for signature::SignatureBuilder { fn hash(&self, hash: &mut Context) -> Result<()> { match self.sb_version { signature::SBVersion::V4 {} => Signature4::hash_fields(hash, &self.fields), signature::SBVersion::V6 { .. } => Signature6::hash_fields(hash, &self.fields), } } } /// Hashing-related functionality. /// /// <a id="hashing-functions"></a> impl signature::SignatureBuilder { /// Hashes this standalone signature. pub fn hash_standalone(&self, hash: &mut Context) -> Result<()> { match self.typ() { SignatureType::Standalone => (), _ => return Err(Error::UnsupportedSignatureType(self.typ()).into()), } if let Some(salt) = self.prefix_salt() { hash.update(salt); } self.hash(hash)?; Ok(()) } /// Hashes this timestamp signature. pub fn hash_timestamp(&self, hash: &mut Context) -> Result<()> { match self.typ() { SignatureType::Timestamp => (), _ => return Err(Error::UnsupportedSignatureType(self.typ()).into()), } if let Some(salt) = self.prefix_salt() { hash.update(salt); } self.hash(hash)?; Ok(()) } /// Hashes this direct key signature over the specified primary /// key, and the primary key. pub fn hash_direct_key<P>(&self, hash: &mut Context, key: &Key<P, key::PrimaryRole>) -> Result<()> where P: key::KeyParts, { match self.typ() { SignatureType::DirectKey => (), SignatureType::KeyRevocation => (), _ => return Err(Error::UnsupportedSignatureType(self.typ()).into()), } if let Some(salt) = self.prefix_salt() { hash.update(salt); } key.hash(hash)?; self.hash(hash)?; Ok(()) } /// Hashes this subkey binding over the specified primary key and /// subkey, the primary key, and the subkey. pub fn hash_subkey_binding<P, Q>(&self, hash: &mut Context, key: &Key<P, key::PrimaryRole>, subkey: &Key<Q, key::SubordinateRole>) -> Result<()> where P: key::KeyParts, Q: key::KeyParts, { match self.typ() { SignatureType::SubkeyBinding => (), SignatureType::SubkeyRevocation => (), _ => return Err(Error::UnsupportedSignatureType(self.typ()).into()), } if let Some(salt) = self.prefix_salt() { hash.update(salt); } key.hash(hash)?; subkey.hash(hash)?; self.hash(hash)?; Ok(()) } /// Hashes this primary key binding over the specified primary key /// and subkey, the primary key, and the subkey. pub fn hash_primary_key_binding<P, Q>(&self, hash: &mut Context, key: &Key<P, key::PrimaryRole>, subkey: &Key<Q, key::SubordinateRole>) -> Result<()> where P: key::KeyParts, Q: key::KeyParts, { match self.typ() { SignatureType::PrimaryKeyBinding => (), _ => return Err(Error::UnsupportedSignatureType(self.typ()).into()), } if let Some(salt) = self.prefix_salt() { hash.update(salt); } key.hash(hash)?; subkey.hash(hash)?; self.hash(hash)?; Ok(()) } /// Hashes this user ID binding over the specified primary key and /// user ID, the primary key, and the userid. pub fn hash_userid_binding<P>(&self, hash: &mut Context, key: &Key<P, key::PrimaryRole>, userid: &UserID) -> Result<()> where P: key::KeyParts, { match self.typ() { SignatureType::GenericCertification => (), SignatureType::PersonaCertification => (), SignatureType::CasualCertification => (), SignatureType::PositiveCertification => (), SignatureType::CertificationRevocation => (), _ => return Err(Error::UnsupportedSignatureType(self.typ()).into()), } if let Some(salt) = self.prefix_salt() { hash.update(salt); } key.hash(hash)?; userid.hash(hash)?; self.hash(hash)?; Ok(()) } /// Hashes this user attribute binding over the specified primary /// key and user attribute, the primary key, and the user /// attribute. pub fn hash_user_attribute_binding<P>( &self, hash: &mut Context, key: &Key<P, key::PrimaryRole>, ua: &UserAttribute) -> Result<()> where P: key::KeyParts, { match self.typ() { SignatureType::GenericCertification => (), SignatureType::PersonaCertification => (), SignatureType::CasualCertification => (), SignatureType::PositiveCertification => (), SignatureType::CertificationRevocation => (), _ => return Err(Error::UnsupportedSignatureType(self.typ()).into()), } if let Some(salt) = self.prefix_salt() { hash.update(salt); } key.hash(hash)?; ua.hash(hash)?; self.hash(hash)?; Ok(()) } } /// Hashing-related functionality. /// /// <a id="hashing-functions"></a> impl Signature { /// Hashes this standalone signature. pub fn hash_standalone(&self, hash: &mut Context) -> Result<()> { match self.typ() { SignatureType::Standalone => (), _ => return Err(Error::UnsupportedSignatureType(self.typ()).into()), } if let Some(salt) = self.salt() { hash.update(salt); } self.hash(hash)?; Ok(()) } /// Hashes this timestamp signature. pub fn hash_timestamp(&self, hash: &mut Context) -> Result<()> { match self.typ() { SignatureType::Timestamp => (), _ => return Err(Error::UnsupportedSignatureType(self.typ()).into()), } if let Some(salt) = self.salt() { hash.update(salt); } self.hash(hash)?; Ok(()) } /// Hashes this direct key signature over the specified primary /// key, and the primary key. pub fn hash_direct_key<P>(&self, hash: &mut Context, key: &Key<P, key::PrimaryRole>) -> Result<()> where P: key::KeyParts, { match self.typ() { SignatureType::DirectKey => (), SignatureType::KeyRevocation => (), _ => return Err(Error::UnsupportedSignatureType(self.typ()).into()), } if let Some(salt) = self.salt() { hash.update(salt); } key.hash(hash)?; self.hash(hash)?; Ok(()) } /// Hashes this subkey binding over the specified primary key and /// subkey, the primary key, and the subkey. pub fn hash_subkey_binding<P, Q>(&self, hash: &mut Context, key: &Key<P, key::PrimaryRole>, subkey: &Key<Q, key::SubordinateRole>) -> Result<()> where P: key::KeyParts, Q: key::KeyParts, { match self.typ() { SignatureType::SubkeyBinding => (), SignatureType::SubkeyRevocation => (), _ => return Err(Error::UnsupportedSignatureType(self.typ()).into()), } if let Some(salt) = self.salt() { hash.update(salt); } key.hash(hash)?; subkey.hash(hash)?; self.hash(hash)?; Ok(()) } /// Hashes this primary key binding over the specified primary key /// and subkey, the primary key, and the subkey. pub fn hash_primary_key_binding<P, Q>(&self, hash: &mut Context, key: &Key<P, key::PrimaryRole>, subkey: &Key<Q, key::SubordinateRole>) -> Result<()> where P: key::KeyParts, Q: key::KeyParts, { match self.typ() { SignatureType::PrimaryKeyBinding => (), _ => return Err(Error::UnsupportedSignatureType(self.typ()).into()), } if let Some(salt) = self.salt() { hash.update(salt); } key.hash(hash)?; subkey.hash(hash)?; self.hash(hash)?; Ok(()) } /// Hashes this user ID binding over the specified primary key and /// user ID, the primary key, and the userid. pub fn hash_userid_binding<P>(&self, hash: &mut Context, key: &Key<P, key::PrimaryRole>, userid: &UserID) -> Result<()> where P: key::KeyParts, { match self.typ() { SignatureType::GenericCertification => (), SignatureType::PersonaCertification => (), SignatureType::CasualCertification => (), SignatureType::PositiveCertification => (), SignatureType::CertificationRevocation => (), _ => return Err(Error::UnsupportedSignatureType(self.typ()).into()), } if let Some(salt) = self.salt() { hash.update(salt); } key.hash(hash)?; userid.hash(hash)?; self.hash(hash)?; Ok(()) } /// Hashes this user attribute binding over the specified primary /// key and user attribute, the primary key, and the user /// attribute. pub fn hash_user_attribute_binding<P>( &self, hash: &mut Context, key: &Key<P, key::PrimaryRole>, ua: &UserAttribute) -> Result<()> where P: key::KeyParts, { match self.typ() { SignatureType::GenericCertification => (), SignatureType::PersonaCertification => (), SignatureType::CasualCertification => (), SignatureType::PositiveCertification => (), SignatureType::CertificationRevocation => (), _ => return Err(Error::UnsupportedSignatureType(self.typ()).into()), } if let Some(salt) = self.salt() { hash.update(salt); } key.hash(hash)?; ua.hash(hash)?; self.hash(hash)?; Ok(()) } /// Hashes this user ID approval over the specified primary key /// and user ID. pub fn hash_userid_approval<P>(&self, hash: &mut Context, key: &Key<P, key::PrimaryRole>, userid: &UserID) -> Result<()> where P: key::KeyParts, { match self.typ() { SignatureType::CertificationApproval => (), _ => return Err(Error::UnsupportedSignatureType(self.typ()).into()), } if let Some(salt) = self.salt() { hash.update(salt); } key.hash(hash)?; userid.hash(hash)?; self.hash(hash)?; Ok(()) } /// Hashes this user attribute approval over the specified primary /// key and user attribute. pub fn hash_user_attribute_approval<P>( &self, hash: &mut Context, key: &Key<P, key::PrimaryRole>, ua: &UserAttribute) -> Result<()> where P: key::KeyParts, { match self.typ() { SignatureType::CertificationApproval => (), _ => return Err(Error::UnsupportedSignatureType(self.typ()).into()), } if let Some(salt) = self.salt() { hash.update(salt); } key.hash(hash)?; ua.hash(hash)?; self.hash(hash)?; Ok(()) } /// Hashes this signature for use in a Third-Party Confirmation /// signature. pub fn hash_for_confirmation(&self, hash: &mut Context) -> Result<()> { match self { Signature::V3(s) => s.hash_for_confirmation(hash), Signature::V4(s) => s.hash_for_confirmation(hash), Signature::V6(s) => s.hash_for_confirmation(hash), } } } /// Hashing-related functionality. /// /// <a id="hashing-functions"></a> impl Signature4 { /// Hashes this signature for use in a Third-Party Confirmation /// signature. pub fn hash_for_confirmation(&self, hash: &mut Context) -> Result<()> { use crate::serialize::{Marshal, MarshalInto}; // Section 5.2.4 of RFC4880: // // > When a signature is made over a Signature packet (type // > 0x50), the hash data starts with the octet 0x88, followed // > by the four-octet length of the signature, and then the // > body of the Signature packet. (Note that this is an // > old-style packet header for a Signature packet with the // > length-of-length set to zero.) The unhashed subpacket // > data of the Signature packet being hashed is not included // > in the hash, and the unhashed subpacket data length value // > is set to zero. // This code assumes that the signature has been verified // prior to being confirmed, so it is well-formed. let mut body = vec![ self.version(), self.typ().into(), self.pk_algo().into(), self.hash_algo().into(), ]; // The hashed area. let l = self.hashed_area().serialized_len() // Assumes well-formedness. .min(std::u16::MAX as usize); body.extend(&(l as u16).to_be_bytes()); // Assumes well-formedness. self.hashed_area().serialize(&mut body)?; // The unhashed area. body.extend(&[0, 0]); // Size replaced by zero. // Unhashed packets omitted. body.extend(self.digest_prefix()); self.mpis().serialize(&mut body)?; hash.update(&[0x88]); hash.update(&(body.len() as u32).to_be_bytes()); hash.update(&body); Ok(()) } } #[cfg(test)] mod test { use crate::Cert; use crate::parse::Parse; #[test] fn hash_verification() { fn check(cert: Cert) -> (usize, usize, usize) { let mut userid_sigs = 0; for (i, binding) in cert.userids().enumerate() { for selfsig in binding.self_signatures() { let mut hash = selfsig.hash_algo().context().unwrap() .for_signature(selfsig.version()); selfsig.hash_userid_binding( &mut hash, cert.primary_key().key(), binding.userid()).unwrap(); let h = hash.into_digest().unwrap(); if &h[..2] != selfsig.digest_prefix() { eprintln!("{:?}: {:?} / {:?}", i, binding.userid(), selfsig); eprintln!(" Hash: {:?}", h); } assert_eq!(&h[..2], selfsig.digest_prefix()); userid_sigs += 1; } } let mut ua_sigs = 0; for (i, a) in cert.user_attributes().enumerate() { for selfsig in a.self_signatures() { let mut hash = selfsig.hash_algo().context().unwrap() .for_signature(selfsig.version()); selfsig.hash_user_attribute_binding( &mut hash, cert.primary_key().key(), a.user_attribute()).unwrap(); let h = hash.into_digest().unwrap(); if &h[..2] != selfsig.digest_prefix() { eprintln!("{:?}: {:?} / {:?}", i, a.user_attribute(), selfsig); eprintln!(" Hash: {:?}", h); } assert_eq!(&h[..2], selfsig.digest_prefix()); ua_sigs += 1; } } let mut subkey_sigs = 0; for (i, binding) in cert.subkeys().enumerate() { for selfsig in binding.self_signatures() { let mut hash = selfsig.hash_algo().context().unwrap() .for_signature(selfsig.version()); selfsig.hash_subkey_binding( &mut hash, cert.primary_key().key(), binding.key()).unwrap(); let h = hash.into_digest().unwrap(); if &h[..2] != selfsig.digest_prefix() { eprintln!("{:?}: {:?}", i, binding); eprintln!(" Hash: {:?}", h); } assert_eq!(h[0], selfsig.digest_prefix()[0]); assert_eq!(h[1], selfsig.digest_prefix()[1]); subkey_sigs += 1; } } (userid_sigs, ua_sigs, subkey_sigs) } check(Cert::from_bytes(crate::tests::key("hash-algos/MD5.gpg")).unwrap()); check(Cert::from_bytes(crate::tests::key("hash-algos/RipeMD160.gpg")).unwrap()); check(Cert::from_bytes(crate::tests::key("hash-algos/SHA1.gpg")).unwrap()); check(Cert::from_bytes(crate::tests::key("hash-algos/SHA224.gpg")).unwrap()); check(Cert::from_bytes(crate::tests::key("hash-algos/SHA256.gpg")).unwrap()); check(Cert::from_bytes(crate::tests::key("hash-algos/SHA384.gpg")).unwrap()); check(Cert::from_bytes(crate::tests::key("hash-algos/SHA512.gpg")).unwrap()); check(Cert::from_bytes(crate::tests::key("bannon-all-uids-subkeys.gpg")).unwrap()); let (_userid_sigs, ua_sigs, _subkey_sigs) = check(Cert::from_bytes(crate::tests::key("dkg.gpg")).unwrap()); assert!(ua_sigs > 0); } } ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/src/crypto/key.rs�������������������������������������������������������������0000644�0000000�0000000�00000004214�10461020230�0016205�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������//! Common secret key related operations. use std::time::SystemTime; use crate::{ Result, packet::key::{self, Key4, Key6, SecretParts}, types::Curve, }; impl<R> Key6<SecretParts, R> where R: key::KeyRole, { /// Generates a new X25519 key. pub fn generate_x25519() -> Result<Self> { Key4::generate_x25519().map(Key6::from_common) } /// Generates a new X448 key. pub fn generate_x448() -> Result<Self> { Key4::generate_x448().map(Key6::from_common) } /// Generates a new Ed25519 key. pub fn generate_ed25519() -> Result<Self> { Key4::generate_ed25519().map(Key6::from_common) } /// Generates a new Ed448 key. pub fn generate_ed448() -> Result<Self> { Key4::generate_ed448().map(Key6::from_common) } /// Generates a new RSA key with a public modulos of size `bits`. pub fn generate_rsa(bits: usize) -> Result<Self> { Key4::generate_rsa(bits) .map(Key6::from_common) } /// Creates a new OpenPGP public key packet for an existing RSA key. /// /// The RSA key will use public exponent `e` and modulo `n`. The key will /// have its creation date set to `ctime` or the current time if `None` /// is given. #[allow(clippy::many_single_char_names)] pub fn import_secret_rsa<T>(d: &[u8], p: &[u8], q: &[u8], ctime: T) -> Result<Self> where T: Into<Option<SystemTime>> { Key4::import_secret_rsa(d, p, q, ctime) .map(Key6::from_common) } /// Generates a new ECC key over `curve`. /// /// If `for_signing` is false a ECDH key, if it's true either a /// EdDSA or ECDSA key is generated. Giving `for_signing == true` and /// `curve == Cv25519` will produce an error. Likewise /// `for_signing == false` and `curve == Ed25519` will produce an error. pub fn generate_ecc(for_signing: bool, curve: Curve) -> Result<Self> { match (for_signing, curve) { (true, Curve::Ed25519) => Self::generate_ed25519(), (false, Curve::Cv25519) => Self::generate_x25519(), (s, c) => Key4::generate_ecc(s, c).map(Key6::from_common), } } } ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/src/crypto/mem.rs�������������������������������������������������������������0000644�0000000�0000000�00000040034�10461020230�0016173�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������//! Memory protection and encryption. //! //! Sequoia makes an effort to protect secrets stored in memory. Even //! though a process's memory should be protected from being read by an //! adversary, there may be bugs in the program or the architecture //! the program is running on that allow (partial) recovery of data. //! Or, the process may be serialized to persistent storage, and its //! memory may be inspected while it is not running. //! //! To reduce the window for these kind of exfiltrations, we use //! [`Protected`] to clear the memory once it is no longer in use, and //! [`Encrypted`] to protect long-term secrets like passwords and //! secret keys. //! //! //! Furthermore, operations involving secrets must be carried out in a //! way that avoids leaking information. For example, comparison //! must be done in constant time with [`secure_cmp`]. //! //! [`secure_cmp`]: secure_cmp() use std::cmp::{min, Ordering}; use std::fmt; use std::hash::{Hash, Hasher}; use std::ops::{Deref, DerefMut}; /// Whether to trace execution by default (on stderr). const TRACE: bool = false; /// Protected memory. /// /// The memory is guaranteed not to be copied around, and is cleared /// when the object is dropped. /// /// # Examples /// /// ```rust /// use sequoia_openpgp::crypto::mem::Protected; /// /// { /// let p: Protected = vec![0, 1, 2].into(); /// assert_eq!(p.as_ref(), &[0, 1, 2]); /// } /// /// // p is cleared once it goes out of scope. /// ``` // # Note on the implementation // // We use a boxed slice, then Box::leak the Box. This takes the // knowledge about the shape of the heap allocation away from Rust, // preventing any optimization based on that. // // For example, Rust could conceivably compact the heap: The borrow // checker knows when no references exist, and this is an excellent // opportunity to move the object on the heap because only one pointer // needs to be updated. pub struct Protected(*mut [u8]); // Safety: Box<[u8]> is Send and Sync, we do not expose any // functionality that was not possible before, hence Protected may // still be Send and Sync. unsafe impl Send for Protected {} unsafe impl Sync for Protected {} impl Clone for Protected { fn clone(&self) -> Self { // Make a vector with the correct size to avoid potential // reallocations when turning it into a `Protected`. let mut p = Vec::with_capacity(self.len()); p.extend_from_slice(self); p.into_boxed_slice().into() } } impl PartialEq for Protected { fn eq(&self, other: &Self) -> bool { secure_cmp(self, other) == Ordering::Equal } } impl Eq for Protected {} impl Hash for Protected { fn hash<H: Hasher>(&self, state: &mut H) { self.as_ref().hash(state); } } impl Protected { /// Allocates a chunk of protected memory. /// /// Effective protection of sensitive values requires avoiding any /// copying and reallocations. Therefore, it is required to /// provide the size upfront at allocation time, then copying the /// secrets into this protected memory region. pub fn new(size: usize) -> Protected { vec![0; size].into_boxed_slice().into() } /// Converts to a buffer for modification. /// /// Don't expose `Protected` values unless you know what you're doing. pub(crate) fn expose_into_unprotected_vec(self) -> Vec<u8> { let mut p = Vec::with_capacity(self.len()); p.extend_from_slice(&self); p } } impl Deref for Protected { type Target = [u8]; fn deref(&self) -> &Self::Target { self.as_ref() } } impl AsRef<[u8]> for Protected { fn as_ref(&self) -> &[u8] { unsafe { &*self.0 } } } impl AsMut<[u8]> for Protected { fn as_mut(&mut self) -> &mut [u8] { unsafe { &mut *self.0 } } } impl DerefMut for Protected { fn deref_mut(&mut self) -> &mut [u8] { self.as_mut() } } impl From<Vec<u8>> for Protected { fn from(mut v: Vec<u8>) -> Self { // Make a careful copy of the data. We do this instead of // reusing v's allocation so that our allocation has the exact // size. let p = Protected::from(&v[..]); // Now clear the previous allocation. Just to be safe, we // clear the whole allocation. let capacity = v.capacity(); unsafe { // Safety: New size is equal to the capacity, and we // initialize all elements. v.set_len(capacity); memsec::memzero(v.as_mut_ptr(), capacity); } p } } /// Zeros N bytes on the stack after running the given closure. /// /// Note: In general, don't use this function directly, use the more /// convenient and robust macro zero_stack! instead, like so: /// /// ```ignore /// zero_stack!(128 bytes after running { /// let mut a = [0; 6]; /// a.copy_from_slice(b"secret"); /// }) /// ``` /// /// Or, if you need to specify the type of the expression: /// /// ```ignore /// zero_stack!(128 bytes after running || -> () { /// let mut a = [0; 6]; /// a.copy_from_slice(b"secret"); /// }) /// ``` /// /// If you must use this function directly, make sure to declare `fun` /// as `#[inline(never)]`. #[allow(dead_code)] #[inline(never)] pub(crate) fn zero_stack_after<const N: usize, T>(fun: impl FnOnce() -> T) -> T { zero_stack::<N, T>(fun()) } /// Zeros N bytes on the stack, returning the given value. /// /// Note: In general, don't use this function directly. This is only /// effective if `v` has been computed by a function that has been /// marked as `#[inline(never)]`. However, since the inline attribute /// is only a hint that may be freely ignored by the compiler, it is /// sometimes necessary to use this function directly. #[allow(dead_code)] #[inline(never)] pub(crate) fn zero_stack<const N: usize, T>(v: T) -> T { tracer!(TRACE, "zero_stack"); let mut a = [0xffu8; N]; t!("zeroing {:?}..{:?}", a.as_ptr(), unsafe { a.as_ptr().offset(N as _) }); unsafe { memsec::memzero(a.as_mut_ptr(), a.len()); } std::hint::black_box(a); v } /// Very carefully copies the slice. /// /// The obvious `to.copy_from_slice(from);` indeed leaks secrets. pub(crate) fn careful_memcpy(from: &[u8], to: &mut [u8]) { from.iter().zip(to.iter_mut()).for_each(|(f, t)| *t = *f); } impl From<Box<[u8]>> for Protected { fn from(v: Box<[u8]>) -> Self { Protected(Box::leak(v)) } } impl From<&[u8]> for Protected { fn from(v: &[u8]) -> Self { let mut p = Protected::new(v.len()); careful_memcpy(v, &mut p); p } } impl<const N: usize> From<[u8; N]> for Protected { fn from(mut v: [u8; N]) -> Self { let mut p = Protected::new(v.len()); careful_memcpy(&v, &mut p); unsafe { memsec::memzero(v.as_mut_ptr(), v.len()); } p } } impl Drop for Protected { fn drop(&mut self) { unsafe { let len = self.len(); memsec::memzero(self.as_mut().as_mut_ptr(), len); drop(Box::from_raw(self.0)); } } } impl fmt::Debug for Protected { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { if cfg!(debug_assertions) { write!(f, "{:?}", self.0) } else { f.write_str("[<Redacted>]") } } } /// Encrypted memory. /// /// This type encrypts sensitive data, such as secret keys, in memory /// while they are unused, and decrypts them on demand. This protects /// against cross-protection-boundary readout via microarchitectural /// flaws like Spectre or Meltdown, via attacks on physical layout /// like Rowbleed, and even via coldboot attacks. /// /// The key insight is that these kinds of attacks are imperfect, /// i.e. the recovered data contains bitflips, or the attack only /// provides a probability for any given bit. Applied to /// cryptographic keys, these kind of imperfect attacks are enough to /// recover the actual key. /// /// This implementation on the other hand, derives a sealing key from /// a large area of memory, the "pre-key", using a key derivation /// function. Now, any single bitflip in the readout of the pre-key /// will avalanche through all the bits in the sealing key, rendering /// it unusable with no indication of where the error occurred. /// /// This kind of protection was pioneered by OpenSSH. The commit /// adding it can be found /// [here](https://marc.info/?l=openbsd-cvs&m=156109087822676). /// /// # Examples /// /// ```rust /// # fn main() -> sequoia_openpgp::Result<()> { /// use sequoia_openpgp::crypto::mem::Encrypted; /// /// let e = Encrypted::new(vec![0, 1, 2].into())?; /// e.map(|p| { /// // e is temporarily decrypted and made available to the closure. /// assert_eq!(p.as_ref(), &[0, 1, 2]); /// // p is cleared once the function returns. /// }); /// # Ok(()) } /// ``` #[derive(Clone, Debug)] pub struct Encrypted { ciphertext: Protected, salt: [u8; 32], plaintext_len: usize, } assert_send_and_sync!(Encrypted); impl PartialEq for Encrypted { fn eq(&self, other: &Self) -> bool { // Protected::eq is time-constant. self.map(|a| other.map(|b| a == b)) } } impl Eq for Encrypted {} impl Hash for Encrypted { fn hash<H: Hasher>(&self, state: &mut H) { self.map(|k| Hash::hash(k, state)); } } /// Opt out of memory encryption. const DANGER_DISABLE_ENCRYPTED_MEMORY: bool = false; /// The number of pages containing random bytes to derive the prekey /// from. const ENCRYPTED_MEMORY_PREKEY_PAGES: usize = 4; /// Page size. const ENCRYPTED_MEMORY_PAGE_SIZE: usize = 4096; /// This module contains the code that needs to access the prekey. /// /// Code outside of it cannot access it, because `PREKEY` is private. mod has_access_to_prekey { use std::io::{self, Read, Write}; use buffered_reader::Memory; use crate::Result; use crate::types::{AEADAlgorithm, HashAlgorithm, SymmetricAlgorithm}; use crate::crypto::{aead, SessionKey}; use super::*; /// Returns the pre-key. /// /// Access to this function is restricted to this module and its /// descendants. fn prekey() -> Result<&'static Box<[Box<[u8]>]>> { use std::sync::OnceLock; static PREKEY: OnceLock<Result<Box<[Box<[u8]>]>>> = OnceLock::new(); PREKEY.get_or_init(|| -> Result<Box<[Box<[u8]>]>> { let mut pages = Vec::new(); for _ in 0..ENCRYPTED_MEMORY_PREKEY_PAGES { let mut page = vec![0; ENCRYPTED_MEMORY_PAGE_SIZE]; crate::crypto::random(&mut page)?; pages.push(page.into()); } Ok(pages.into()) }).as_ref().map_err(|e| anyhow::anyhow!("{}", e)) } // Algorithms used for the memory encryption. // // The digest of the hash algorithm must be at least as large as // the size of the key used by the symmetric algorithm. All // algorithms MUST be supported by the cryptographic library. const HASH_ALGO: HashAlgorithm = HashAlgorithm::SHA256; const SYMMETRIC_ALGO: SymmetricAlgorithm = SymmetricAlgorithm::AES256; const AEAD_ALGO: AEADAlgorithm = AEADAlgorithm::const_default(); impl Encrypted { /// Computes the sealing key used to encrypt the memory. fn sealing_key(salt: &[u8; 32]) -> Result<SessionKey> { let mut ctx = HASH_ALGO.context() .expect("Mandatory algorithm unsupported") .for_digest(); ctx.update(salt); prekey()? .iter().for_each(|page| ctx.update(page)); let mut sk: SessionKey = Protected::new(256/8).into(); let _ = ctx.digest(&mut sk); Ok(sk) } /// Encrypts the given chunk of memory. pub fn new(p: Protected) -> Result<Self> { if DANGER_DISABLE_ENCRYPTED_MEMORY { return Ok(Encrypted { plaintext_len: p.len(), ciphertext: p, salt: Default::default(), }); } let mut salt = [0; 32]; crate::crypto::random(&mut salt)?; let mut ciphertext = Protected::new( p.len() + 2 * AEAD_ALGO.digest_size().expect("supported")); { let mut encryptor = aead::Encryptor::new(SYMMETRIC_ALGO, AEAD_ALGO, p.len(), CounterSchedule::default(), Self::sealing_key(&salt)?, io::Cursor::new(&mut ciphertext[..])) .expect("Mandatory algorithm unsupported"); encryptor.write_all(&p).unwrap(); encryptor.finish().unwrap(); } Ok(Encrypted { plaintext_len: p.len(), ciphertext, salt, }) } /// Maps the given function over the temporarily decrypted /// memory. pub fn map<F, T>(&self, mut fun: F) -> T where F: FnMut(&Protected) -> T { if DANGER_DISABLE_ENCRYPTED_MEMORY { return fun(&self.ciphertext); } let ciphertext = Memory::with_cookie(&self.ciphertext, Default::default()); let mut plaintext = Protected::new(self.plaintext_len); let mut decryptor = aead::Decryptor::from_cookie_reader( SYMMETRIC_ALGO, AEAD_ALGO, self.plaintext_len, CounterSchedule::default(), Self::sealing_key(&self.salt) .expect("was fine during encryption"), Box::new(ciphertext)) .expect("Mandatory algorithm unsupported"); // Be careful not to leak partially decrypted plain text. let r = decryptor.read_exact(&mut plaintext); if r.is_err() { drop(plaintext); // Securely erase partial plaintext. panic!("Encrypted memory modified or corrupted"); } fun(&plaintext) } } #[derive(Default)] struct CounterSchedule {} impl aead::Schedule for CounterSchedule { fn next_chunk<F, R>(&self, index: u64, mut fun: F) -> R where F: FnMut(&[u8], &[u8]) -> R, { // The nonce is a simple counter. let mut nonce_store = [0u8; aead::MAX_NONCE_LEN]; let nonce_len = AEAD_ALGO.nonce_size() .expect("Mandatory algorithm unsupported"); assert!(nonce_len >= 8); let nonce = &mut nonce_store[..nonce_len]; let index_be: [u8; 8] = index.to_be_bytes(); nonce[nonce_len - 8..].copy_from_slice(&index_be); // No AAD. fun(nonce, &[]) } fn final_chunk<F, R>(&self, index: u64, length: u64, mut fun: F) -> R where F: FnMut(&[u8], &[u8]) -> R { // The nonce is a simple counter. let mut nonce_store = [0u8; aead::MAX_NONCE_LEN]; let nonce_len = AEAD_ALGO.nonce_size() .expect("Mandatory algorithm unsupported"); assert!(nonce_len >= 8); let nonce = &mut nonce_store[..nonce_len]; let index_be: [u8; 8] = index.to_be_bytes(); nonce[nonce_len - 8..].copy_from_slice(&index_be); // Plaintext bytes as AAD to prevent truncation. let aad: [u8; 8] = length.to_be_bytes(); fun(nonce, &aad) } } } /// Time-constant comparison. pub fn secure_cmp(a: &[u8], b: &[u8]) -> Ordering { let ord1 = a.len().cmp(&b.len()); let ord2 = unsafe { memsec::memcmp(a.as_ptr(), b.as_ptr(), min(a.len(), b.len())) }; let ord2 = match ord2 { 1..=std::i32::MAX => Ordering::Greater, 0 => Ordering::Equal, std::i32::MIN..=-1 => Ordering::Less, }; if ord1 == Ordering::Equal { ord2 } else { ord1 } } ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/src/crypto/mod.rs�������������������������������������������������������������0000644�0000000�0000000�00000023657�10461020230�0016210�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������//! Cryptographic primitives. //! //! This module contains cryptographic primitives as defined and used //! by OpenPGP. It abstracts over the cryptographic library chosen at //! compile time. Most of the time, it will not be necessary to //! explicitly use types from this module directly, but they are used //! in the API (e.g. [`Password`]). Advanced users may use these //! primitives to provide custom extensions to OpenPGP. //! //! //! # Common Operations //! //! - *Converting a string to a [`Password`]*: Use [`Password::from`]. //! - *Create a session key*: Use [`SessionKey::new`]. //! - *Use secret keys*: See the [`KeyPair` example]. //! //! [`Password::from`]: std::convert::From //! [`SessionKey::new`]: SessionKey::new() //! [`KeyPair` example]: KeyPair#examples use std::cmp::Ordering; use std::ops::{Deref, DerefMut}; use std::fmt; use std::borrow::Cow; use crate::{ Error, Result, }; pub(crate) mod aead; mod asymmetric; pub use self::asymmetric::{Signer, Decryptor, KeyPair}; pub(crate) mod backend; pub mod ecdh; pub mod hash; mod key; pub mod mem; pub mod mpi; mod s2k; pub use s2k::S2K; pub(crate) mod symmetric; mod types; pub use types::{ AEADAlgorithm, Curve, HashAlgorithm, PublicKeyAlgorithm, SymmetricAlgorithm, }; #[cfg(test)] mod tests; /// Returns a short, human-readable description of the backend. /// /// This starts with the name of the backend, possibly a version, and /// any optional features that are available. This is meant for /// inclusion in version strings to improve bug reports. pub fn backend() -> String { use backend::interface::Backend; backend::Backend::backend() } /// Fills the given buffer with random data. /// /// Fills the given buffer with random data produced by a /// cryptographically secure pseudorandom number generator (CSPRNG). /// The output may be used as session keys or to derive long-term /// cryptographic keys from. However, to create session keys, /// consider using [`SessionKey::new`]. /// /// [`SessionKey::new`]: crate::crypto::SessionKey::new() pub fn random<B: AsMut<[u8]>>(mut buf: B) -> Result<()> { use backend::interface::Backend; backend::Backend::random(buf.as_mut()) } /// Holds a session key. /// /// The session key is cleared when dropped. Sequoia uses this type /// to ensure that session keys are not left in memory returned to the /// allocator. /// /// Session keys can be generated using [`SessionKey::new`], or /// converted from various types using [`From`]. /// /// [`SessionKey::new`]: SessionKey::new() /// [`From`]: std::convert::From #[derive(Clone, PartialEq, Eq)] pub struct SessionKey(mem::Protected); assert_send_and_sync!(SessionKey); impl SessionKey { /// Creates a new session key. /// /// Creates a new session key `size` bytes in length initialized /// using a strong cryptographic number generator. /// /// # Examples /// /// This creates a session key and encrypts it for a given /// recipient key producing a [`PKESK`] packet. /// /// [`PKESK`]: crate::packet::PKESK /// /// ``` /// # fn main() -> sequoia_openpgp::Result<()> { /// use sequoia_openpgp as openpgp; /// use openpgp::types::{Curve, SymmetricAlgorithm}; /// use openpgp::crypto::SessionKey; /// use openpgp::packet::prelude::*; /// /// let cipher = SymmetricAlgorithm::AES256; /// let sk = SessionKey::new(cipher.key_size()?)?; /// /// let key: Key<key::SecretParts, key::UnspecifiedRole> = /// Key6::generate_ecc(false, Curve::Cv25519)?.into(); /// /// let pkesk: PKESK = /// PKESK3::for_recipient(cipher, &sk, &key)?.into(); /// # Ok(()) } /// ``` pub fn new(size: usize) -> Result<Self> { let mut sk: mem::Protected = vec![0; size].into(); random(&mut sk)?; Ok(Self(sk)) } /// Returns a reference to the inner [`mem::Protected`]. pub fn as_protected(&self) -> &mem::Protected { &self.0 } } impl Deref for SessionKey { type Target = [u8]; fn deref(&self) -> &Self::Target { &self.0 } } impl AsRef<[u8]> for SessionKey { fn as_ref(&self) -> &[u8] { &self.0 } } impl DerefMut for SessionKey { fn deref_mut(&mut self) -> &mut [u8] { &mut self.0 } } impl AsMut<[u8]> for SessionKey { fn as_mut(&mut self) -> &mut [u8] { &mut self.0 } } impl From<mem::Protected> for SessionKey { fn from(v: mem::Protected) -> Self { SessionKey(v) } } impl From<Vec<u8>> for SessionKey { fn from(v: Vec<u8>) -> Self { SessionKey(v.into()) } } impl From<Box<[u8]>> for SessionKey { fn from(v: Box<[u8]>) -> Self { SessionKey(v.into()) } } impl From<&[u8]> for SessionKey { fn from(v: &[u8]) -> Self { Vec::from(v).into() } } impl fmt::Debug for SessionKey { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "SessionKey ({:?})", self.0) } } /// Holds a password. /// /// `Password`s can be converted from various types using [`From`]. /// The password is encrypted in memory and only decrypted on demand. /// See [`mem::Encrypted`] for details. /// /// [`From`]: std::convert::From /// /// # Examples /// /// ``` /// use sequoia_openpgp as openpgp; /// use openpgp::crypto::Password; /// /// // Convert from a &str. /// let p: Password = "hunter2".into(); /// /// // Convert from a &[u8]. /// let p: Password = b"hunter2"[..].into(); /// /// // Convert from a String. /// let p: Password = String::from("hunter2").into(); /// /// // ... /// ``` #[derive(Clone, PartialEq, Eq)] pub struct Password(mem::Encrypted); assert_send_and_sync!(Password); impl From<Vec<u8>> for Password { fn from(v: Vec<u8>) -> Self { Password(mem::Encrypted::new(v.into()) .expect("encrypting memory failed")) } } impl From<Box<[u8]>> for Password { fn from(v: Box<[u8]>) -> Self { Password(mem::Encrypted::new(v.into()) .expect("encrypting memory failed")) } } impl From<String> for Password { fn from(v: String) -> Self { v.into_bytes().into() } } impl<'a> From<&'a str> for Password { fn from(v: &'a str) -> Self { v.to_owned().into() } } impl From<&[u8]> for Password { fn from(v: &[u8]) -> Self { Vec::from(v).into() } } impl fmt::Debug for Password { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { if cfg!(debug_assertions) { self.map(|p| write!(f, "Password({:?})", p)) } else { f.write_str("Password(<Encrypted>)") } } } impl Password { /// Maps the given function over the password. /// /// The password is stored encrypted in memory. This function /// temporarily decrypts it for the given function to use. /// /// # Examples /// /// ``` /// use sequoia_openpgp as openpgp; /// use openpgp::crypto::Password; /// /// let p: Password = "hunter2".into(); /// p.map(|p| assert_eq!(p.as_ref(), &b"hunter2"[..])); /// ``` pub fn map<F, T>(&self, fun: F) -> T where F: FnMut(&mem::Protected) -> T { self.0.map(fun) } } /// Returns the value zero-padded to the given length. /// /// Some encodings strip leading zero-bytes. This function adds them /// back, if necessary. If the size exceeds `to`, an error is /// returned. pub(crate) fn pad(value: &[u8], to: usize) -> Result<Cow<[u8]>> { match value.len().cmp(&to) { Ordering::Equal => Ok(Cow::Borrowed(value)), Ordering::Less => { let missing = to - value.len(); let mut v = vec![0; to]; v[missing..].copy_from_slice(value); Ok(Cow::Owned(v)) } Ordering::Greater => { Err(Error::InvalidOperation( format!("Input value is longer than expected: {} > {}", value.len(), to)).into()) } } } /// Returns the value zero-padded to the given length. /// /// Some encodings strip leading zero-bytes. This function adds them /// back, if necessary. If the size exceeds `to`, the value is /// returned as-is. #[allow(dead_code)] pub(crate) fn pad_at_least(value: &[u8], to: usize) -> Cow<[u8]> { pad(value, to).unwrap_or(Cow::Borrowed(value)) } /// Returns the value zero-padded or truncated to the given length. /// /// Some encodings strip leading zero-bytes. This function adds them /// back, if necessary. If the size exceeds `to`, the value is /// silently truncated. #[allow(dead_code)] pub(crate) fn pad_truncating(value: &[u8], to: usize) -> Cow<[u8]> { if value.len() == to { Cow::Borrowed(value) } else { let missing = to.saturating_sub(value.len()); let limit = value.len().min(to); let mut v = vec![0; to]; v[missing..].copy_from_slice(&value[..limit]); Cow::Owned(v) } } /// Compares two arbitrary-sized big-endian integers. /// /// Note that the tempting `a < b` doesn't work: it computes the /// lexicographical order, so that `[2] > [1, 2]`, whereas we want /// `[2] < [1, 2]`. pub(crate) fn raw_bigint_cmp(mut a: &[u8], mut b: &[u8]) -> Ordering { // First, trim leading zeros. while a.get(0) == Some(&0) { a = &a[1..]; } while b.get(0) == Some(&0) { b = &b[1..]; } // Then, compare their length. Shorter integers are also smaller. a.len().cmp(&b.len()) // Finally, if their length is equal, do a lexicographical // comparison. .then_with(|| a.cmp(b)) } /// Given the secret prime values `p` and `q`, returns the pair of /// primes so that the smaller one comes first. /// /// Section 5.5.3 of RFC4880 demands that `p < q`. This function can /// be used to order `p` and `q` accordingly. #[allow(dead_code)] pub(crate) fn rsa_sort_raw_pq<'a>(p: &'a [u8], q: &'a [u8]) -> (&'a [u8], &'a [u8]) { match raw_bigint_cmp(p, q) { Ordering::Less => (p, q), Ordering::Equal => (p, q), Ordering::Greater => (q, p), } } ���������������������������������������������������������������������������������sequoia-openpgp-2.0.0/src/crypto/mpi.rs�������������������������������������������������������������0000644�0000000�0000000�00000135715�10461020230�0016215�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������//! Multiprecision Integers. //! //! Cryptographic objects like [public keys], [secret keys], //! [ciphertexts], and [signatures] are scalar numbers of arbitrary //! precision. OpenPGP specifies that these are stored encoded as //! big-endian integers with leading zeros stripped (See [Section 3.2 //! of RFC 9580]). Multiprecision integers in OpenPGP are extended by //! [Section 3.2.1 of RFC 9580] to store curves and coordinates used //! in elliptic curve cryptography (ECC). //! //! [public keys]: PublicKey //! [secret keys]: SecretKeyMaterial //! [ciphertexts]: Ciphertext //! [signatures]: Signature //! [Section 3.2 of RFC 9580]: https://www.rfc-editor.org/rfc/rfc9580.html#section-3.2 //! [Section 3.2.1 of RFC 9580]: https://www.rfc-editor.org/rfc/rfc9580.html#section-3.2.1 use std::fmt; use std::cmp::Ordering; use std::io::Write; use std::borrow::Cow; #[cfg(test)] use quickcheck::{Arbitrary, Gen}; use crate::types::{ Curve, HashAlgorithm, PublicKeyAlgorithm, SymmetricAlgorithm, }; use crate::crypto::hash::{self, Hash}; use crate::crypto::mem::{secure_cmp, Protected}; use crate::serialize::Marshal; use crate::Error; use crate::Result; /// A Multiprecision Integer. #[derive(Clone)] pub struct MPI { /// Integer value as big-endian with leading zeros stripped. value: Box<[u8]>, } assert_send_and_sync!(MPI); impl From<Vec<u8>> for MPI { fn from(v: Vec<u8>) -> Self { // XXX: This will leak secrets in v into the heap. But, // eagerly clearing the memory may have a very high overhead, // after all, most MPIs that we encounter will not contain // secrets. I think it is better to avoid creating MPIs that // contain secrets in the first place. In 2.0, we can remove // the impl From<MPI> for ProtectedMPI. Self::new(&v) } } impl From<Box<[u8]>> for MPI { fn from(v: Box<[u8]>) -> Self { // XXX: This will leak secrets in v into the heap. But, // eagerly clearing the memory may have a very high overhead, // after all, most MPIs that we encounter will not contain // secrets. I think it is better to avoid creating MPIs that // contain secrets in the first place. In 2.0, we can remove // the impl From<MPI> for ProtectedMPI. Self::new(&v) } } impl MPI { /// Trims leading zero octets. fn trim_leading_zeros(v: &[u8]) -> &[u8] { let offset = v.iter().take_while(|&&o| o == 0).count(); &v[offset..] } /// Creates a new MPI. /// /// This function takes care of removing leading zeros. pub fn new(value: &[u8]) -> Self { let value = Self::trim_leading_zeros(value).to_vec().into_boxed_slice(); MPI { value, } } /// Creates new MPI encoding an uncompressed EC point. /// /// Encodes the given point on an elliptic curve (see [Section 6 of /// RFC 6637] for details). This is used to encode public keys /// and ciphertexts for the NIST curves (`NistP256`, `NistP384`, /// and `NistP521`). /// /// [Section 6 of RFC 6637]: https://tools.ietf.org/html/rfc6637#section-6 pub fn new_point(x: &[u8], y: &[u8], field_bits: usize) -> Self { Self::new_point_common(x, y, field_bits).into() } /// Common implementation shared between MPI and ProtectedMPI. fn new_point_common(x: &[u8], y: &[u8], field_bits: usize) -> Vec<u8> { let field_sz = if field_bits % 8 > 0 { 1 } else { 0 } + field_bits / 8; let mut val = vec![0x0u8; 1 + 2 * field_sz]; let x_missing = field_sz - x.len(); let y_missing = field_sz - y.len(); val[0] = 0x4; val[1 + x_missing..1 + field_sz].copy_from_slice(x); val[1 + field_sz + y_missing..].copy_from_slice(y); val } /// Creates new MPI encoding a compressed EC point using native /// encoding. /// /// Encodes the given point on an elliptic curve (see [Section 13.2 /// of RFC4880bis] for details). This is used to encode public /// keys and ciphertexts for the Bernstein curves (currently /// `X25519`). /// /// [Section 13.2 of RFC4880bis]: https://tools.ietf.org/html/draft-ietf-openpgp-rfc4880bis-09#section-13.2 pub fn new_compressed_point(x: &[u8]) -> Self { Self::new_compressed_point_common(x).into() } /// Common implementation shared between MPI and ProtectedMPI. fn new_compressed_point_common(x: &[u8]) -> Vec<u8> { let mut val = vec![0; 1 + x.len()]; val[0] = 0x40; val[1..].copy_from_slice(x); val } /// Creates a new MPI representing zero. pub fn zero() -> Self { Self::new(&[]) } /// Tests whether the MPI represents zero. pub fn is_zero(&self) -> bool { self.value().is_empty() } /// Returns the length of the MPI in bits. /// /// Leading zero-bits are not included in the returned size. pub fn bits(&self) -> usize { self.value.len() * 8 - self.value.get(0).map(|&b| b.leading_zeros() as usize) .unwrap_or(0) } /// Returns the value of this MPI. /// /// Note that due to stripping of zero-bytes, the returned value /// may be shorter than expected. pub fn value(&self) -> &[u8] { &self.value } /// Returns the value of this MPI zero-padded to the given length. /// /// MPI-encoding strips leading zero-bytes. This function adds /// them back, if necessary. If the size exceeds `to`, an error /// is returned. pub fn value_padded(&self, to: usize) -> Result<Cow<[u8]>> { crate::crypto::pad(self.value(), to) } /// Decodes an EC point encoded as MPI. /// /// Decodes the MPI into a point on an elliptic curve (see /// [Section 6 of RFC 6637] and [Section 13.2 of RFC4880bis] for /// details). If the point is not compressed, the function /// returns `(x, y)`. If it is compressed, `y` will be empty. /// /// [Section 6 of RFC 6637]: https://tools.ietf.org/html/rfc6637#section-6 /// [Section 13.2 of RFC4880bis]: https://tools.ietf.org/html/draft-ietf-openpgp-rfc4880bis-09#section-13.2 /// /// # Errors /// /// Returns `Error::UnsupportedEllipticCurve` if the curve is not /// supported, `Error::MalformedMPI` if the point is formatted /// incorrectly, `Error::InvalidOperation` if the given curve is /// operating on native octet strings. pub fn decode_point(&self, curve: &Curve) -> Result<(&[u8], &[u8])> { Self::decode_point_common(self.value(), curve) } /// Common implementation shared between MPI and ProtectedMPI. fn decode_point_common<'a>(value: &'a [u8], curve: &Curve) -> Result<(&'a [u8], &'a [u8])> { const ED25519_KEY_SIZE: usize = 32; const CURVE25519_SIZE: usize = 32; use self::Curve::*; match &curve { Ed25519 | Cv25519 => { assert_eq!(CURVE25519_SIZE, ED25519_KEY_SIZE); // This curve uses a custom compression format which // only contains the X coordinate. if value.len() != 1 + CURVE25519_SIZE { return Err(Error::MalformedMPI( format!("Bad size of Curve25519 key: {} expected: {}", value.len(), 1 + CURVE25519_SIZE ) ).into()); } if value.get(0).map(|&b| b != 0x40).unwrap_or(true) { return Err(Error::MalformedMPI( "Bad encoding of Curve25519 key".into()).into()); } Ok((&value[1..], &[])) }, NistP256 | NistP384 | NistP521 | BrainpoolP256 | BrainpoolP384 | BrainpoolP512 => { // Length of one coordinate in bytes, rounded up. let coordinate_length = curve.field_size()?; // Check length of Q. let expected_length = 1 // 0x04. + (2 // (x, y) * coordinate_length); if value.len() != expected_length { return Err(Error::MalformedMPI( format!("Invalid length of MPI: {} (expected {})", value.len(), expected_length)).into()); } if value.get(0).map(|&b| b != 0x04).unwrap_or(true) { return Err(Error::MalformedMPI( format!("Bad prefix: {:?} (expected Some(0x04))", value.get(0))).into()); } Ok((&value[1..1 + coordinate_length], &value[1 + coordinate_length..])) }, Unknown(_) => Err(Error::UnsupportedEllipticCurve(curve.clone()).into()), } } /// Securely compares two MPIs in constant time. fn secure_memcmp(&self, other: &Self) -> Ordering { let cmp = unsafe { if self.value.len() == other.value.len() { ::memsec::memcmp(self.value.as_ptr(), other.value.as_ptr(), other.value.len()) } else { self.value.len() as i32 - other.value.len() as i32 } }; match cmp { 0 => Ordering::Equal, x if x < 0 => Ordering::Less, _ => Ordering::Greater, } } } impl fmt::Debug for MPI { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.write_fmt(format_args!( "{} bits: {}", self.bits(), crate::fmt::to_hex(&*self.value, true))) } } impl Hash for MPI { fn hash(&self, hash: &mut hash::Context) -> Result<()> { let len = self.bits() as u16; hash.update(&len.to_be_bytes()); hash.update(&self.value); Ok(()) } } #[cfg(test)] impl Arbitrary for MPI { fn arbitrary(g: &mut Gen) -> Self { loop { let buf = <Vec<u8>>::arbitrary(g); if !buf.is_empty() && buf[0] != 0 { break MPI::new(&buf); } } } } impl PartialOrd for MPI { fn partial_cmp(&self, other: &MPI) -> Option<Ordering> { Some(self.cmp(other)) } } impl Ord for MPI { fn cmp(&self, other: &MPI) -> Ordering { self.secure_memcmp(other) } } impl PartialEq for MPI { fn eq(&self, other: &MPI) -> bool { self.cmp(other) == Ordering::Equal } } impl Eq for MPI {} impl std::hash::Hash for MPI { fn hash<H: std::hash::Hasher>(&self, state: &mut H) { self.value.hash(state); } } /// Holds a single MPI containing secrets. /// /// The memory will be cleared when the object is dropped. Used by /// [`SecretKeyMaterial`] to protect secret keys. /// #[derive(Clone)] pub struct ProtectedMPI { /// Integer value as big-endian. value: Protected, } assert_send_and_sync!(ProtectedMPI); impl From<&[u8]> for ProtectedMPI { fn from(m: &[u8]) -> Self { let value = Protected::from(MPI::trim_leading_zeros(m)); ProtectedMPI { value, } } } impl From<Vec<u8>> for ProtectedMPI { fn from(m: Vec<u8>) -> Self { let value = Protected::from(MPI::trim_leading_zeros(&m)); drop(Protected::from(m)); // Erase source. ProtectedMPI { value, } } } impl From<Box<[u8]>> for ProtectedMPI { fn from(m: Box<[u8]>) -> Self { let value = Protected::from(MPI::trim_leading_zeros(&m)); drop(Protected::from(m)); // Erase source. ProtectedMPI { value, } } } impl From<Protected> for ProtectedMPI { fn from(m: Protected) -> Self { let value = Protected::from(MPI::trim_leading_zeros(&m)); drop(m); // Erase source. ProtectedMPI { value, } } } impl PartialOrd for ProtectedMPI { fn partial_cmp(&self, other: &ProtectedMPI) -> Option<Ordering> { Some(self.cmp(other)) } } impl Ord for ProtectedMPI { fn cmp(&self, other: &ProtectedMPI) -> Ordering { self.secure_memcmp(other) } } impl PartialEq for ProtectedMPI { fn eq(&self, other: &ProtectedMPI) -> bool { self.cmp(other) == Ordering::Equal } } impl Eq for ProtectedMPI {} impl std::hash::Hash for ProtectedMPI { fn hash<H: std::hash::Hasher>(&self, state: &mut H) { self.value.hash(state); } } #[cfg(test)] impl Arbitrary for ProtectedMPI { fn arbitrary(g: &mut Gen) -> Self { loop { let buf = <Vec<u8>>::arbitrary(g); if ! buf.is_empty() && buf[0] != 0 { break ProtectedMPI::from(buf); } } } } impl ProtectedMPI { /// Creates new MPI encoding an uncompressed EC point. /// /// Encodes the given point on an elliptic curve (see [Section 6 of /// RFC 6637] for details). This is used to encode public keys /// and ciphertexts for the NIST curves (`NistP256`, `NistP384`, /// and `NistP521`). /// /// [Section 6 of RFC 6637]: https://tools.ietf.org/html/rfc6637#section-6 pub fn new_point(x: &[u8], y: &[u8], field_bits: usize) -> Self { MPI::new_point_common(x, y, field_bits).into() } /// Creates new MPI encoding a compressed EC point using native /// encoding. /// /// Encodes the given point on an elliptic curve (see [Section 13.2 /// of RFC4880bis] for details). This is used to encode public /// keys and ciphertexts for the Bernstein curves (currently /// `X25519`). /// /// [Section 13.2 of RFC4880bis]: https://tools.ietf.org/html/draft-ietf-openpgp-rfc4880bis-09#section-13.2 pub fn new_compressed_point(x: &[u8]) -> Self { MPI::new_compressed_point_common(x).into() } /// Returns the length of the MPI in bits. /// /// Leading zero-bits are not included in the returned size. pub fn bits(&self) -> usize { self.value.len() * 8 - self.value.get(0).map(|&b| b.leading_zeros() as usize) .unwrap_or(0) } /// Returns the value of this MPI. /// /// Note that due to stripping of zero-bytes, the returned value /// may be shorter than expected. pub fn value(&self) -> &[u8] { &self.value } /// Returns the value of this MPI zero-padded to the given length. /// /// MPI-encoding strips leading zero-bytes. This function adds /// them back. This operation is done unconditionally to avoid /// timing differences. If the size exceeds `to`, the result is /// silently truncated to avoid timing differences. pub fn value_padded(&self, to: usize) -> Protected { let missing = to.saturating_sub(self.value.len()); let limit = self.value.len().min(to); let mut v: Protected = vec![0; to].into(); v[missing..].copy_from_slice(&self.value()[..limit]); v } /// Decodes an EC point encoded as MPI. /// /// Decodes the MPI into a point on an elliptic curve (see /// [Section 6 of RFC 6637] and [Section 13.2 of RFC4880bis] for /// details). If the point is not compressed, the function /// returns `(x, y)`. If it is compressed, `y` will be empty. /// /// [Section 6 of RFC 6637]: https://tools.ietf.org/html/rfc6637#section-6 /// [Section 13.2 of RFC4880bis]: https://tools.ietf.org/html/draft-ietf-openpgp-rfc4880bis-09#section-13.2 /// /// # Errors /// /// Returns `Error::UnsupportedEllipticCurve` if the curve is not /// supported, `Error::MalformedMPI` if the point is formatted /// incorrectly, `Error::InvalidOperation` if the given curve is /// operating on native octet strings. pub fn decode_point(&self, curve: &Curve) -> Result<(&[u8], &[u8])> { MPI::decode_point_common(self.value(), curve) } /// Securely compares two MPIs in constant time. fn secure_memcmp(&self, other: &Self) -> Ordering { (self.value.len() as i32).cmp(&(other.value.len() as i32)) .then( // Protected compares in constant time. self.value.cmp(&other.value)) } } impl fmt::Debug for ProtectedMPI { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { if cfg!(debug_assertions) { f.write_fmt(format_args!( "{} bits: {}", self.bits(), crate::fmt::to_hex(&*self.value, true))) } else { f.write_str("<Redacted>") } } } /// A public key. /// /// Provides a typed and structured way of storing multiple MPIs (and /// the occasional elliptic curve) in [`Key`] packets. /// /// [`Key`]: crate::packet::Key #[non_exhaustive] #[derive(Clone, PartialEq, Eq, Hash, Debug, PartialOrd, Ord)] pub enum PublicKey { /// RSA public key. RSA { /// Public exponent e: MPI, /// Public modulo N = pq. n: MPI, }, /// NIST DSA public key. DSA { /// Prime of the ring Zp. p: MPI, /// Order of `g` in Zp. q: MPI, /// Public generator of Zp. g: MPI, /// Public key g^x mod p. y: MPI, }, /// ElGamal public key. ElGamal { /// Prime of the ring Zp. p: MPI, /// Generator of Zp. g: MPI, /// Public key g^x mod p. y: MPI, }, /// DJB's "Twisted" Edwards curve DSA public key. EdDSA { /// Curve we're using. Must be curve 25519. curve: Curve, /// Public point. q: MPI, }, /// NIST's Elliptic Curve DSA public key. ECDSA { /// Curve we're using. curve: Curve, /// Public point. q: MPI, }, /// Elliptic Curve Diffie-Hellman public key. ECDH { /// Curve we're using. curve: Curve, /// Public point. q: MPI, /// Algorithm used to derive the Key Encapsulation Key. hash: HashAlgorithm, /// Algorithm used to encapsulate the session key. sym: SymmetricAlgorithm, }, /// X25519 public key. X25519 { /// The public key, an opaque string. u: [u8; 32], }, /// X448 public key. X448 { /// The public key, an opaque string. u: Box<[u8; 56]>, }, /// Ed25519 public key. Ed25519 { /// The public key, an opaque string. a: [u8; 32], }, /// Ed448 public key. Ed448 { /// The public key, an opaque string. a: Box<[u8; 57]>, }, /// Unknown number of MPIs for an unknown algorithm. Unknown { /// The successfully parsed MPIs. mpis: Box<[MPI]>, /// Any data that failed to parse. rest: Box<[u8]>, }, } assert_send_and_sync!(PublicKey); impl PublicKey { /// Returns the length of the public key in bits. /// /// For finite field crypto this returns the size of the field we /// operate in, for ECC it returns `Curve::bits()`. /// /// Note: This information is useless and should not be used to /// gauge the security of a particular key. This function exists /// only because some legacy PGP application like HKP need it. /// /// Returns `None` for unknown keys and curves. pub fn bits(&self) -> Option<usize> { use self::PublicKey::*; match self { RSA { ref n,.. } => Some(n.bits()), DSA { ref p,.. } => Some(p.bits()), ElGamal { ref p,.. } => Some(p.bits()), EdDSA { ref curve,.. } => curve.bits().ok(), ECDSA { ref curve,.. } => curve.bits().ok(), ECDH { ref curve,.. } => curve.bits().ok(), X25519 { .. } => Some(256), X448 { .. } => Some(448), Ed25519 { .. } => Some(256), Ed448 { .. } => Some(456), Unknown { .. } => None, } } /// Returns, if known, the public-key algorithm for this public /// key. pub fn algo(&self) -> Option<PublicKeyAlgorithm> { use self::PublicKey::*; #[allow(deprecated)] match self { RSA { .. } => Some(PublicKeyAlgorithm::RSAEncryptSign), DSA { .. } => Some(PublicKeyAlgorithm::DSA), ElGamal { .. } => Some(PublicKeyAlgorithm::ElGamalEncrypt), EdDSA { .. } => Some(PublicKeyAlgorithm::EdDSA), ECDSA { .. } => Some(PublicKeyAlgorithm::ECDSA), ECDH { .. } => Some(PublicKeyAlgorithm::ECDH), X25519 { .. } => Some(PublicKeyAlgorithm::X25519), X448 { .. } => Some(PublicKeyAlgorithm::X448), Ed25519 { .. } => Some(PublicKeyAlgorithm::Ed25519), Ed448 { .. } => Some(PublicKeyAlgorithm::Ed448), Unknown { .. } => None, } } } impl Hash for PublicKey { fn hash(&self, mut hash: &mut hash::Context) -> Result<()> { self.serialize(&mut hash as &mut dyn Write) } } #[cfg(test)] impl Arbitrary for PublicKey { fn arbitrary(g: &mut Gen) -> Self { use self::PublicKey::*; use crate::arbitrary_helper::gen_arbitrary_from_range; match gen_arbitrary_from_range(0..10, g) { 0 => RSA { e: MPI::arbitrary(g), n: MPI::arbitrary(g), }, 1 => DSA { p: MPI::arbitrary(g), q: MPI::arbitrary(g), g: MPI::arbitrary(g), y: MPI::arbitrary(g), }, 2 => ElGamal { p: MPI::arbitrary(g), g: MPI::arbitrary(g), y: MPI::arbitrary(g), }, 3 => EdDSA { curve: Curve::arbitrary(g), q: MPI::arbitrary(g), }, 4 => ECDSA { curve: Curve::arbitrary(g), q: MPI::arbitrary(g), }, 5 => ECDH { curve: Curve::arbitrary(g), q: MPI::arbitrary(g), hash: HashAlgorithm::arbitrary(g), sym: SymmetricAlgorithm::arbitrary(g), }, 6 => X25519 { u: arbitrary(g) }, 7 => X448 { u: Box::new(arbitrarize(g, [0; 56])) }, 8 => Ed25519 { a: arbitrary(g) }, 9 => Ed448 { a: Box::new(arbitrarize(g, [0; 57])) }, _ => unreachable!(), } } } #[cfg(test)] pub(crate) fn arbitrarize<T: AsMut<[u8]>>(g: &mut Gen, mut a: T) -> T { a.as_mut().iter_mut().for_each(|p| *p = Arbitrary::arbitrary(g)); a } #[cfg(test)] pub(crate) fn arbitrary<T: Default + AsMut<[u8]>>(g: &mut Gen) -> T { arbitrarize(g, Default::default()) } /// A secret key. /// /// Provides a typed and structured way of storing multiple MPIs in /// [`Key`] packets. Secret key components are protected by storing /// them using [`ProtectedMPI`]. /// /// [`Key`]: crate::packet::Key // Deriving Hash here is okay: PartialEq is manually implemented to // ensure that secrets are compared in constant-time. #[non_exhaustive] #[allow(clippy::derived_hash_with_manual_eq)] #[derive(Clone, Hash)] pub enum SecretKeyMaterial { /// RSA secret key. RSA { /// Secret exponent, inverse of e in Phi(N). d: ProtectedMPI, /// Smaller secret prime. p: ProtectedMPI, /// Larger secret prime. q: ProtectedMPI, /// Inverse of p mod q. u: ProtectedMPI, }, /// NIST DSA secret key. DSA { /// Secret key log_g(y) in Zp. x: ProtectedMPI, }, /// ElGamal secret key. ElGamal { /// Secret key log_g(y) in Zp. x: ProtectedMPI, }, /// DJB's "Twisted" Edwards curve DSA secret key. EdDSA { /// Secret scalar. scalar: ProtectedMPI, }, /// NIST's Elliptic Curve DSA secret key. ECDSA { /// Secret scalar. scalar: ProtectedMPI, }, /// Elliptic Curve Diffie-Hellman secret key. ECDH { /// Secret scalar. scalar: ProtectedMPI, }, /// X25519 secret key. X25519 { /// The secret key, an opaque string. x: Protected, }, /// X448 secret key. X448 { /// The secret key, an opaque string. x: Protected, }, /// Ed25519 secret key. Ed25519 { /// The secret key, an opaque string. x: Protected, }, /// Ed448 secret key. Ed448 { /// The secret key, an opaque string. x: Protected, }, /// Unknown number of MPIs for an unknown algorithm. Unknown { /// The successfully parsed MPIs. mpis: Box<[ProtectedMPI]>, /// Any data that failed to parse. rest: Protected, }, } assert_send_and_sync!(SecretKeyMaterial); impl fmt::Debug for SecretKeyMaterial { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { if cfg!(debug_assertions) { match self { SecretKeyMaterial::RSA{ ref d, ref p, ref q, ref u } => write!(f, "RSA {{ d: {:?}, p: {:?}, q: {:?}, u: {:?} }}", d, p, q, u), SecretKeyMaterial::DSA{ ref x } => write!(f, "DSA {{ x: {:?} }}", x), SecretKeyMaterial::ElGamal{ ref x } => write!(f, "ElGamal {{ x: {:?} }}", x), SecretKeyMaterial::EdDSA{ ref scalar } => write!(f, "EdDSA {{ scalar: {:?} }}", scalar), SecretKeyMaterial::ECDSA{ ref scalar } => write!(f, "ECDSA {{ scalar: {:?} }}", scalar), SecretKeyMaterial::ECDH{ ref scalar } => write!(f, "ECDH {{ scalar: {:?} }}", scalar), SecretKeyMaterial::X25519 { x } => write!(f, "X25519 {{ x: {:?} }}", x), SecretKeyMaterial::X448 { x } => write!(f, "X448 {{ x: {:?} }}", x), SecretKeyMaterial::Ed25519 { x } => write!(f, "Ed25519 {{ x: {:?} }}", x), SecretKeyMaterial::Ed448 { x } => write!(f, "Ed448 {{ x: {:?} }}", x), SecretKeyMaterial::Unknown{ ref mpis, ref rest } => write!(f, "Unknown {{ mips: {:?}, rest: {:?} }}", mpis, rest), } } else { match self { SecretKeyMaterial::RSA{ .. } => f.write_str("RSA { <Redacted> }"), SecretKeyMaterial::DSA{ .. } => f.write_str("DSA { <Redacted> }"), SecretKeyMaterial::ElGamal{ .. } => f.write_str("ElGamal { <Redacted> }"), SecretKeyMaterial::EdDSA{ .. } => f.write_str("EdDSA { <Redacted> }"), SecretKeyMaterial::ECDSA{ .. } => f.write_str("ECDSA { <Redacted> }"), SecretKeyMaterial::ECDH{ .. } => f.write_str("ECDH { <Redacted> }"), SecretKeyMaterial::X25519 { .. } => f.write_str("X25519 { <Redacted> }"), SecretKeyMaterial::X448 { .. } => f.write_str("X448 { <Redacted> }"), SecretKeyMaterial::Ed25519 { .. } => f.write_str("Ed25519 { <Redacted> }"), SecretKeyMaterial::Ed448 { .. } => f.write_str("Ed448 { <Redacted> }"), SecretKeyMaterial::Unknown{ .. } => f.write_str("Unknown { <Redacted> }"), } } } } impl PartialOrd for SecretKeyMaterial { fn partial_cmp(&self, other: &SecretKeyMaterial) -> Option<Ordering> { Some(self.cmp(other)) } } impl Ord for SecretKeyMaterial { fn cmp(&self, other: &Self) -> Ordering { use std::iter; fn discriminant(sk: &SecretKeyMaterial) -> usize { match sk { SecretKeyMaterial::RSA{ .. } => 0, SecretKeyMaterial::DSA{ .. } => 1, SecretKeyMaterial::ElGamal{ .. } => 2, SecretKeyMaterial::EdDSA{ .. } => 3, SecretKeyMaterial::ECDSA{ .. } => 4, SecretKeyMaterial::ECDH{ .. } => 5, SecretKeyMaterial::X25519 { .. } => 6, SecretKeyMaterial::X448 { .. } => 7, SecretKeyMaterial::Ed25519 { .. } => 8, SecretKeyMaterial::Ed448 { .. } => 9, SecretKeyMaterial::Unknown { .. } => 10, } } let ret = match (self, other) { (&SecretKeyMaterial::RSA{ d: ref d1, p: ref p1, q: ref q1, u: ref u1 } ,&SecretKeyMaterial::RSA{ d: ref d2, p: ref p2, q: ref q2, u: ref u2 }) => { let o1 = d1.cmp(d2); let o2 = p1.cmp(p2); let o3 = q1.cmp(q2); let o4 = u1.cmp(u2); if o1 != Ordering::Equal { return o1; } if o2 != Ordering::Equal { return o2; } if o3 != Ordering::Equal { return o3; } o4 } (&SecretKeyMaterial::DSA{ x: ref x1 } ,&SecretKeyMaterial::DSA{ x: ref x2 }) => { x1.cmp(x2) } (&SecretKeyMaterial::ElGamal{ x: ref x1 } ,&SecretKeyMaterial::ElGamal{ x: ref x2 }) => { x1.cmp(x2) } (&SecretKeyMaterial::EdDSA{ scalar: ref scalar1 } ,&SecretKeyMaterial::EdDSA{ scalar: ref scalar2 }) => { scalar1.cmp(scalar2) } (&SecretKeyMaterial::ECDSA{ scalar: ref scalar1 } ,&SecretKeyMaterial::ECDSA{ scalar: ref scalar2 }) => { scalar1.cmp(scalar2) } (&SecretKeyMaterial::ECDH{ scalar: ref scalar1 } ,&SecretKeyMaterial::ECDH{ scalar: ref scalar2 }) => { scalar1.cmp(scalar2) } (SecretKeyMaterial::X25519 { x: x0 }, SecretKeyMaterial::X25519 { x: x1 }) => x0.cmp(x1), (SecretKeyMaterial::X448 { x: x0 }, SecretKeyMaterial::X448 { x: x1 }) => x0.cmp(x1), (SecretKeyMaterial::Ed25519 { x: x0 }, SecretKeyMaterial::Ed25519 { x: x1 }) => x0.cmp(x1), (SecretKeyMaterial::Ed448 { x: x0 }, SecretKeyMaterial::Ed448 { x: x1 }) => x0.cmp(x1), (&SecretKeyMaterial::Unknown{ mpis: ref mpis1, rest: ref rest1 } ,&SecretKeyMaterial::Unknown{ mpis: ref mpis2, rest: ref rest2 }) => { let o1 = secure_cmp(rest1, rest2); let o2 = mpis1.len().cmp(&mpis2.len()); let on = mpis1.iter().zip(mpis2.iter()).map(|(a,b)| { a.cmp(b) }).collect::<Vec<_>>(); iter::once(o1) .chain(iter::once(o2)) .chain(on.iter().cloned()) .fold(Ordering::Equal, |acc, x| acc.then(x)) } (a, b) => { let ret = discriminant(a).cmp(&discriminant(b)); assert!(ret != Ordering::Equal); ret } }; ret } } impl PartialEq for SecretKeyMaterial { fn eq(&self, other: &Self) -> bool { self.cmp(other) == Ordering::Equal } } impl Eq for SecretKeyMaterial {} impl SecretKeyMaterial { /// Returns, if known, the public-key algorithm for this secret /// key. pub fn algo(&self) -> Option<PublicKeyAlgorithm> { use self::SecretKeyMaterial::*; #[allow(deprecated)] match self { RSA { .. } => Some(PublicKeyAlgorithm::RSAEncryptSign), DSA { .. } => Some(PublicKeyAlgorithm::DSA), ElGamal { .. } => Some(PublicKeyAlgorithm::ElGamalEncrypt), EdDSA { .. } => Some(PublicKeyAlgorithm::EdDSA), ECDSA { .. } => Some(PublicKeyAlgorithm::ECDSA), ECDH { .. } => Some(PublicKeyAlgorithm::ECDH), X25519 { .. } => Some(PublicKeyAlgorithm::X25519), X448 { .. } => Some(PublicKeyAlgorithm::X448), Ed25519 { .. } => Some(PublicKeyAlgorithm::Ed25519), Ed448 { .. } => Some(PublicKeyAlgorithm::Ed448), Unknown { .. } => None, } } } impl Hash for SecretKeyMaterial { fn hash(&self, mut hash: &mut hash::Context) -> Result<()> { self.serialize(&mut hash as &mut dyn Write) } } #[cfg(test)] impl SecretKeyMaterial { pub(crate) fn arbitrary_for(g: &mut Gen, pk: PublicKeyAlgorithm) -> Result<Self> { use self::PublicKeyAlgorithm::*; #[allow(deprecated)] match pk { RSAEncryptSign | RSASign | RSAEncrypt => Ok(SecretKeyMaterial::RSA { d: ProtectedMPI::arbitrary(g), p: ProtectedMPI::arbitrary(g), q: ProtectedMPI::arbitrary(g), u: ProtectedMPI::arbitrary(g), }), DSA => Ok(SecretKeyMaterial::DSA { x: ProtectedMPI::arbitrary(g), }), ElGamalEncryptSign | ElGamalEncrypt => Ok(SecretKeyMaterial::ElGamal { x: ProtectedMPI::arbitrary(g), }), EdDSA => Ok(SecretKeyMaterial::EdDSA { scalar: ProtectedMPI::arbitrary(g), }), ECDSA => Ok(SecretKeyMaterial::ECDSA { scalar: ProtectedMPI::arbitrary(g), }), ECDH => Ok(SecretKeyMaterial::ECDH { scalar: ProtectedMPI::arbitrary(g), }), X25519 => Ok(SecretKeyMaterial::X25519 { x: arbitrarize(g, vec![0; 32]).into(), }), X448 => Ok(SecretKeyMaterial::X448 { x: arbitrarize(g, vec![0; 56]).into(), }), Ed25519 => Ok(SecretKeyMaterial::Ed25519 { x: arbitrarize(g, vec![0; 32]).into(), }), Ed448 => Ok(SecretKeyMaterial::Ed448 { x: arbitrarize(g, vec![0; 57]).into(), }), Private(_) | Unknown(_) => Err(Error::UnsupportedPublicKeyAlgorithm(pk).into()), } } } #[cfg(test)] impl Arbitrary for SecretKeyMaterial { fn arbitrary(g: &mut Gen) -> Self { let pk = *g.choose( &crate::crypto::types::public_key_algorithm::PUBLIC_KEY_ALGORITHM_VARIANTS) .expect("not empty"); Self::arbitrary_for(g, pk).expect("only known variants") } } /// Checksum method for secret key material. /// /// Secret key material may be protected by a checksum. See [Section /// 5.5.3 of RFC 9580] for details. /// /// [Section 5.5.3 of RFC 9580]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.5.3 #[derive(PartialEq, Eq, Hash, Clone, Copy, Debug)] pub enum SecretKeyChecksum { /// SHA1 over the decrypted secret key. SHA1, /// Sum of the decrypted secret key octets modulo 65536. Sum16, } assert_send_and_sync!(SecretKeyChecksum); impl Default for SecretKeyChecksum { fn default() -> Self { SecretKeyChecksum::SHA1 } } impl SecretKeyChecksum { /// Returns the on-wire length of the checksum. pub(crate) fn len(&self) -> usize { match self { SecretKeyChecksum::SHA1 => 20, SecretKeyChecksum::Sum16 => 2, } } } /// An encrypted session key. /// /// Provides a typed and structured way of storing multiple MPIs in /// [`PKESK`] packets. /// /// [`PKESK`]: crate::packet::PKESK #[non_exhaustive] #[derive(Clone, PartialEq, Eq, Hash, Debug, PartialOrd, Ord)] pub enum Ciphertext { /// RSA ciphertext. RSA { /// m^e mod N. c: MPI, }, /// ElGamal ciphertext. ElGamal { /// Ephemeral key. e: MPI, /// Ciphertext. c: MPI, }, /// Elliptic curve ElGamal public key. ECDH { /// Ephemeral key. e: MPI, /// Symmetrically encrypted session key. key: Box<[u8]>, }, /// X25519 ciphertext. X25519 { /// Ephermeral key. e: Box<[u8; 32]>, /// Symmetrically encrypted session key. key: Box<[u8]>, }, /// X448 ciphertext. X448 { /// Ephermeral key. e: Box<[u8; 56]>, /// Symmetrically encrypted session key. key: Box<[u8]>, }, /// Unknown number of MPIs for an unknown algorithm. Unknown { /// The successfully parsed MPIs. mpis: Box<[MPI]>, /// Any data that failed to parse. rest: Box<[u8]>, }, } assert_send_and_sync!(Ciphertext); impl Ciphertext { /// Returns, if known, the public-key algorithm for this /// ciphertext. pub fn pk_algo(&self) -> Option<PublicKeyAlgorithm> { use self::Ciphertext::*; // Fields are mostly MPIs that consist of two octets length // plus the big endian value itself. All other field types are // commented. #[allow(deprecated)] match self { RSA { .. } => Some(PublicKeyAlgorithm::RSAEncryptSign), ElGamal { .. } => Some(PublicKeyAlgorithm::ElGamalEncrypt), ECDH { .. } => Some(PublicKeyAlgorithm::ECDH), X25519 { .. } => Some(PublicKeyAlgorithm::X25519), X448 { .. } => Some(PublicKeyAlgorithm::X448), Unknown { .. } => None, } } } impl Hash for Ciphertext { fn hash(&self, mut hash: &mut hash::Context) -> Result<()> { self.serialize(&mut hash as &mut dyn Write) } } #[cfg(test)] impl Arbitrary for Ciphertext { fn arbitrary(g: &mut Gen) -> Self { use crate::arbitrary_helper::gen_arbitrary_from_range; match gen_arbitrary_from_range(0..5, g) { 0 => Ciphertext::RSA { c: MPI::arbitrary(g), }, 1 => Ciphertext::ElGamal { e: MPI::arbitrary(g), c: MPI::arbitrary(g) }, 2 => Ciphertext::ECDH { e: MPI::arbitrary(g), key: { let mut k = <Vec<u8>>::arbitrary(g); k.truncate(255); k.into_boxed_slice() }, }, 3 => Ciphertext::X25519 { e: Box::new(arbitrary(g)), key: { let mut k = <Vec<u8>>::arbitrary(g); k.truncate(255); k.into_boxed_slice() }, }, 4 => Ciphertext::X448 { e: Box::new(arbitrarize(g, [0; 56])), key: { let mut k = <Vec<u8>>::arbitrary(g); k.truncate(255); k.into_boxed_slice() }, }, _ => unreachable!(), } } } /// A cryptographic signature. /// /// Provides a typed and structured way of storing multiple MPIs in /// [`Signature`] packets. /// /// [`Signature`]: crate::packet::Signature #[non_exhaustive] #[derive(Clone, PartialEq, Eq, Hash, Debug, PartialOrd, Ord)] pub enum Signature { /// RSA signature. RSA { /// Signature m^d mod N. s: MPI, }, /// NIST's DSA signature. DSA { /// `r` value. r: MPI, /// `s` value. s: MPI, }, /// ElGamal signature. ElGamal { /// `r` value. r: MPI, /// `s` value. s: MPI, }, /// DJB's "Twisted" Edwards curve DSA signature. EdDSA { /// `r` value. r: MPI, /// `s` value. s: MPI, }, /// NIST's Elliptic curve DSA signature. ECDSA { /// `r` value. r: MPI, /// `s` value. s: MPI, }, /// Ed25519 signature. Ed25519 { /// The signature. s: Box<[u8; 64]>, }, /// Ed448 signature. Ed448 { /// The signature. s: Box<[u8; 114]>, }, /// Unknown number of MPIs for an unknown algorithm. Unknown { /// The successfully parsed MPIs. mpis: Box<[MPI]>, /// Any data that failed to parse. rest: Box<[u8]>, }, } assert_send_and_sync!(Signature); impl Hash for Signature { fn hash(&self, mut hash: &mut hash::Context) -> Result<()> { self.serialize(&mut hash as &mut dyn Write) } } #[cfg(test)] impl Arbitrary for Signature { fn arbitrary(g: &mut Gen) -> Self { use crate::arbitrary_helper::gen_arbitrary_from_range; match gen_arbitrary_from_range(0..6, g) { 0 => Signature::RSA { s: MPI::arbitrary(g), }, 1 => Signature::DSA { r: MPI::arbitrary(g), s: MPI::arbitrary(g), }, 2 => Signature::EdDSA { r: MPI::arbitrary(g), s: MPI::arbitrary(g), }, 3 => Signature::ECDSA { r: MPI::arbitrary(g), s: MPI::arbitrary(g), }, 4 => Signature::Ed25519 { s: Box::new(arbitrarize(g, [0; 64])), }, 5 => Signature::Ed448 { s: Box::new(arbitrarize(g, [0; 114])), }, _ => unreachable!(), } } } #[cfg(test)] mod tests { use super::*; use crate::parse::Parse; quickcheck! { fn mpi_roundtrip(mpi: MPI) -> bool { let mut buf = Vec::new(); mpi.serialize(&mut buf).unwrap(); MPI::from_bytes(&buf).unwrap() == mpi } } quickcheck! { fn pk_roundtrip(pk: PublicKey) -> bool { use std::io::Cursor; use crate::PublicKeyAlgorithm::*; let mut buf = Vec::new(); pk.serialize(&mut buf).unwrap(); let cur = Cursor::new(buf); #[allow(deprecated)] let pk_ = match &pk { PublicKey::RSA { .. } => PublicKey::parse(RSAEncryptSign, cur).unwrap(), PublicKey::DSA { .. } => PublicKey::parse(DSA, cur).unwrap(), PublicKey::ElGamal { .. } => PublicKey::parse(ElGamalEncrypt, cur).unwrap(), PublicKey::EdDSA { .. } => PublicKey::parse(EdDSA, cur).unwrap(), PublicKey::ECDSA { .. } => PublicKey::parse(ECDSA, cur).unwrap(), PublicKey::ECDH { .. } => PublicKey::parse(ECDH, cur).unwrap(), PublicKey::X25519 { .. } => PublicKey::parse(X25519, cur).unwrap(), PublicKey::X448 { .. } => PublicKey::parse(X448, cur).unwrap(), PublicKey::Ed25519 { .. } => PublicKey::parse(Ed25519, cur).unwrap(), PublicKey::Ed448 { .. } => PublicKey::parse(Ed448, cur).unwrap(), PublicKey::Unknown { .. } => unreachable!(), }; pk == pk_ } } #[test] fn pk_bits() { for (name, key_no, bits) in &[ ("testy.pgp", 0, 2048), ("testy-new.pgp", 1, 256), ("dennis-simon-anton.pgp", 0, 2048), ("dsa2048-elgamal3072.pgp", 1, 3072), ("emmelie-dorothea-dina-samantha-awina-ed25519.pgp", 0, 256), ("erika-corinna-daniela-simone-antonia-nistp256.pgp", 0, 256), ("erika-corinna-daniela-simone-antonia-nistp384.pgp", 0, 384), ("erika-corinna-daniela-simone-antonia-nistp521.pgp", 0, 521), ] { let cert = crate::Cert::from_bytes(crate::tests::key(name)).unwrap(); let ka = cert.keys().nth(*key_no).unwrap(); assert_eq!(ka.key().mpis().bits().unwrap(), *bits, "Cert {}, key no {}", name, *key_no); } } quickcheck! { fn sk_roundtrip(sk: SecretKeyMaterial) -> bool { use std::io::Cursor; use crate::PublicKeyAlgorithm::*; let mut buf = Vec::new(); sk.serialize(&mut buf).unwrap(); let cur = Cursor::new(buf); #[allow(deprecated)] let sk_ = match &sk { SecretKeyMaterial::RSA { .. } => SecretKeyMaterial::parse(RSAEncryptSign, cur).unwrap(), SecretKeyMaterial::DSA { .. } => SecretKeyMaterial::parse(DSA, cur).unwrap(), SecretKeyMaterial::EdDSA { .. } => SecretKeyMaterial::parse(EdDSA, cur).unwrap(), SecretKeyMaterial::ECDSA { .. } => SecretKeyMaterial::parse(ECDSA, cur).unwrap(), SecretKeyMaterial::ECDH { .. } => SecretKeyMaterial::parse(ECDH, cur).unwrap(), SecretKeyMaterial::ElGamal { .. } => SecretKeyMaterial::parse(ElGamalEncrypt, cur).unwrap(), SecretKeyMaterial::X25519 { .. } => SecretKeyMaterial::parse(X25519, cur).unwrap(), SecretKeyMaterial::X448 { .. } => SecretKeyMaterial::parse(X448, cur).unwrap(), SecretKeyMaterial::Ed25519 { .. } => SecretKeyMaterial::parse(Ed25519, cur).unwrap(), SecretKeyMaterial::Ed448 { .. } => SecretKeyMaterial::parse(Ed448, cur).unwrap(), SecretKeyMaterial::Unknown { .. } => unreachable!(), }; sk == sk_ } } quickcheck! { fn ct_roundtrip(ct: Ciphertext) -> bool { use std::io::Cursor; use crate::PublicKeyAlgorithm::*; let mut buf = Vec::new(); ct.serialize(&mut buf).unwrap(); let cur = Cursor::new(buf); #[allow(deprecated)] let ct_ = match &ct { Ciphertext::RSA { .. } => Ciphertext::parse(RSAEncryptSign, cur).unwrap(), Ciphertext::ElGamal { .. } => Ciphertext::parse(ElGamalEncrypt, cur).unwrap(), Ciphertext::ECDH { .. } => Ciphertext::parse(ECDH, cur).unwrap(), Ciphertext::X25519 { .. } => Ciphertext::parse(X25519, cur).unwrap(), Ciphertext::X448 { .. } => Ciphertext::parse(X448, cur).unwrap(), Ciphertext::Unknown { .. } => unreachable!(), }; ct == ct_ } } quickcheck! { fn signature_roundtrip(sig: Signature) -> bool { use std::io::Cursor; use crate::PublicKeyAlgorithm::*; let mut buf = Vec::new(); sig.serialize(&mut buf).unwrap(); let cur = Cursor::new(buf); #[allow(deprecated)] let sig_ = match &sig { Signature::RSA { .. } => Signature::parse(RSAEncryptSign, cur).unwrap(), Signature::DSA { .. } => Signature::parse(DSA, cur).unwrap(), Signature::ElGamal { .. } => Signature::parse(ElGamalEncryptSign, cur).unwrap(), Signature::EdDSA { .. } => Signature::parse(EdDSA, cur).unwrap(), Signature::ECDSA { .. } => Signature::parse(ECDSA, cur).unwrap(), Signature::Ed25519 { .. } => Signature::parse(Ed25519, cur).unwrap(), Signature::Ed448 { .. } => Signature::parse(Ed448, cur).unwrap(), Signature::Unknown { .. } => unreachable!(), }; sig == sig_ } } } ���������������������������������������������������sequoia-openpgp-2.0.0/src/crypto/s2k.rs�������������������������������������������������������������0000644�0000000�0000000�00000065571�10461020230�0016131�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������//! String-to-Key (S2K) specifiers. //! //! String-to-key (S2K) specifiers are used to convert password //! strings into symmetric-key encryption/decryption keys. See //! [Section 3.7 of RFC 9580]. //! //! [Section 3.7 of RFC 9580]: https://www.rfc-editor.org/rfc/rfc9580.html#section-3.7 use std::convert::TryInto; use crate::Error; use crate::Result; use crate::HashAlgorithm; use crate::crypto::Password; use crate::crypto::SessionKey; use std::fmt; #[cfg(test)] use quickcheck::{Arbitrary, Gen}; /// String-to-Key (S2K) specifiers. /// /// String-to-key (S2K) specifiers are used to convert password /// strings into symmetric-key encryption/decryption keys. See /// [Section 3.7 of RFC 9580]. This is used to encrypt messages with /// a password (see [`SKESK`]), and to protect secret keys (see /// [`key::Encrypted`]). /// /// [Section 3.7 of RFC 9580]: https://www.rfc-editor.org/rfc/rfc9580.html#section-3.7 /// [`SKESK`]: crate::packet::SKESK /// [`key::Encrypted`]: crate::packet::key::Encrypted #[non_exhaustive] #[derive(Clone, PartialEq, Eq, Hash, Debug)] pub enum S2K { /// Argon2 Memory-Hard Password Hashing Function. Argon2 { /// The salt. salt: [u8; 16], /// Number of passes. t: u8, /// Degree of parallelism. p: u8, /// Exponent of memory size. m: u8, }, /// Repeatently hashes the password with a public `salt` value. Iterated { /// Hash used for key derivation. hash: HashAlgorithm, /// Public salt value mixed into the password. salt: [u8; 8], /// Number of bytes to hash. /// /// This parameter increases the workload for an attacker /// doing a dictionary attack. Note that not all values are /// representable. See [`S2K::new_iterated`]. /// /// [`S2K::new_iterated`]: S2K::new_iterated() hash_bytes: u32, }, /// Hashes the password with a public `salt` value. /// /// This mechanism does not use iteration to increase the time it /// takes to derive the key from the password. This makes /// dictionary attacks more feasible. Do not use this variant. #[deprecated(note = "Use `S2K::Iterated`.")] Salted { /// Hash used for key derivation. hash: HashAlgorithm, /// Public salt value mixed into the password. salt: [u8; 8], }, /// Simply hashes the password. /// /// This mechanism uses neither iteration to increase the time it /// takes to derive the key from the password nor does it salt the /// password. This makes dictionary attacks more feasible. /// /// This mechanism has been deprecated in RFC 4880. Do not use this /// variant. #[deprecated(note = "Use `S2K::Iterated`.")] Simple { /// Hash used for key derivation. hash: HashAlgorithm }, /// Simply hashes the password using MD5 /// /// This mechanism uses neither iteration to increase the time it /// takes to derive the key from the password nor does it salt the /// password, as well as using a very weak and fast hash /// algorithm. This makes dictionary attacks more feasible. /// /// This mechanism has been deprecated in RFC 2440. Do not use /// this variant. #[deprecated(note = "Use `S2K::Iterated`.")] Implicit, /// Private S2K algorithm. Private { /// Tag identifying the private algorithm. /// /// Tags 100 to 110 are reserved for private use. tag: u8, /// The parameters for the private algorithm. /// /// This is optional, because when we parse a packet /// containing an unknown S2K algorithm, we do not know how /// many octets to attribute to the S2K's parameters. In this /// case, `parameters` is set to `None`. Note that the /// information is not lost, but stored in the packet. If the /// packet is serialized again, it is written out. parameters: Option<Box<[u8]>>, }, /// Unknown S2K algorithm. Unknown { /// Tag identifying the unknown algorithm. tag: u8, /// The parameters for the unknown algorithm. /// /// This is optional, because when we parse a packet /// containing an unknown S2K algorithm, we do not know how /// many octets to attribute to the S2K's parameters. In this /// case, `parameters` is set to `None`. Note that the /// information is not lost, but stored in the packet. If the /// packet is serialized again, it is written out. parameters: Option<Box<[u8]>>, }, } assert_send_and_sync!(S2K); impl Default for S2K { fn default() -> Self { S2K::new_iterated( // SHA2-256, being optimized for implementations on // architectures with a word size of 32 bit, has a more // consistent runtime across different architectures than // SHA2-512. Furthermore, the digest size is large enough // for every cipher algorithm currently in use. HashAlgorithm::SHA256, // This is the largest count that OpenPGP can represent. // On moderate machines, like my Intel(R) Core(TM) i5-2400 // CPU @ 3.10GHz, it takes ~354ms to derive a key. 0x3e00000, ).expect("0x3e00000 is representable") } } impl S2K { /// Creates a new iterated `S2K` object. /// /// Usually, you should use `S2K`s [`Default`] implementation to /// create `S2K` objects with sane default parameters. The /// parameters are chosen with contemporary machines in mind, and /// should also be usable on lower-end devices like smartphones. /// /// [`Default`]: std::default::Default /// /// Using this method, you can tune the parameters for embedded /// devices. Note, however, that this also decreases the work /// factor for attackers doing dictionary attacks. pub fn new_iterated(hash: HashAlgorithm, approx_hash_bytes: u32) -> Result<Self> { if approx_hash_bytes > 0x3e00000 { Err(Error::InvalidArgument(format!( "Number of bytes to hash not representable: {}", approx_hash_bytes)).into()) } else { let mut salt = [0u8; 8]; crate::crypto::random(&mut salt)?; Ok(S2K::Iterated { hash, salt, hash_bytes: Self::nearest_hash_count(approx_hash_bytes as usize), }) } } /// Derives a key of the given size from a password. pub fn derive_key(&self, password: &Password, key_size: usize) -> Result<SessionKey> { #[allow(deprecated)] match self { &S2K::Argon2 { salt, t, p, m, } => { let mut config = argon2::ParamsBuilder::new(); config.t_cost(t.into()); config.p_cost(p.into()); config.m_cost( 2u32.checked_pow(m.into()) .ok_or_else(|| Error::InvalidArgument( format!("Argon2 memory parameter out of bounds: {}", m)))?); config.output_len( key_size.try_into() .map_err(|_| Error::InvalidArgument( format!("key size parameter out of bounds: {}", key_size)))?); let params = config.build() .map_err(|e| Error::InvalidOperation(e.to_string()))?; // Allocate the blocks for the Argon2 computation. let mut blocks = Blocks::new(&params)?; let argon2 = argon2::Argon2::new( argon2::Algorithm::Argon2id, argon2::Version::V0x13, params); let mut sk: SessionKey = vec![0; key_size].into(); password.map(|password| { argon2.hash_password_into_with_memory( password, &salt, &mut sk, blocks.as_mut()) }).map_err(|e| Error::InvalidOperation(e.to_string()))?; Ok(sk) }, &S2K::Simple { hash } | &S2K::Salted { hash, .. } | &S2K::Iterated { hash, .. } => password.map(|string| { let mut hash = hash.context()?.for_digest(); // If the digest length is shorter than the key length, // then we need to concatenate multiple hashes, each // preloaded with i 0s. let hash_sz = hash.digest_size(); let num_contexts = (key_size + hash_sz - 1) / hash_sz; let mut zeros = Vec::with_capacity(num_contexts + 1); let mut ret = vec![0u8; key_size]; for data in ret.chunks_mut(hash_sz) { hash.update(&zeros[..]); match self { &S2K::Argon2 { .. } => unreachable!("handled above"), &S2K::Simple { .. } => { hash.update(string); } &S2K::Salted { ref salt, .. } => { hash.update(salt); hash.update(string); } &S2K::Iterated { ref salt, hash_bytes, .. } if (hash_bytes as usize) < salt.len() + string.len() => { // Independent of what the hash count is, we // always hash the whole salt and password once. hash.update(&salt[..]); hash.update(string); }, &S2K::Iterated { ref salt, hash_bytes, .. } => { // Unroll the processing loop N times. const N: usize = 16; let data_len = salt.len() + string.len(); let octs_per_iter = N * data_len; let mut data: SessionKey = vec![0u8; octs_per_iter].into(); let full = hash_bytes as usize / octs_per_iter; let tail = hash_bytes as usize - (full * octs_per_iter); for i in 0..N { let o = data_len * i; data[o..o + salt.len()] .clone_from_slice(salt); data[o + salt.len()..o + data_len] .clone_from_slice(string); } for _ in 0..full { hash.update(&data); } if tail != 0 { hash.update(&data[0..tail]); } } S2K::Implicit | S2K::Unknown { .. } | &S2K::Private { .. } => unreachable!(), } let _ = hash.digest(data); zeros.push(0); } Ok(ret.into()) }), S2K::Implicit => S2K::Simple { hash: HashAlgorithm::MD5, }.derive_key(password, key_size), S2K::Unknown { tag, .. } | S2K::Private { tag, .. } => Err(Error::MalformedPacket( format!("Unknown S2K type {:#x}", tag)).into()), } } /// Returns whether this S2K mechanism is supported. pub fn is_supported(&self) -> bool { use self::S2K::*; #[allow(deprecated)] match self { Simple { .. } | Salted { .. } | Iterated { .. } | Implicit | Argon2 { .. } => true, S2K::Private { .. } | S2K::Unknown { .. } => false, } } /// This function returns an encodable iteration count. /// /// Not all iteration counts are encodable as *Iterated and Salted /// S2K*. The largest encodable hash count is `0x3e00000`. /// /// The returned value is larger or equal `hash_bytes`, or /// `0x3e00000` if `hash_bytes` is larger than or equal /// `0x3e00000`. fn nearest_hash_count(hash_bytes: usize) -> u32 { use std::usize; match hash_bytes { 0..=1024 => 1024, 0x3e00001..=usize::MAX => 0x3e00000, hash_bytes => { for i in 0..256 { let n = Self::decode_count(i as u8); if n as usize >= hash_bytes { return n; } } 0x3e00000 } } } /// Decodes the OpenPGP encoding of the number of bytes to hash. pub(crate) fn decode_count(coded: u8) -> u32 { use std::cmp; let mantissa = 16 + (coded as u32 & 15); let exp = (coded as u32 >> 4) + 6; mantissa << cmp::min(32 - 5, exp) } /// Converts `hash_bytes` into coded count representation. /// /// # Errors /// /// Fails with `Error::InvalidArgument` if `hash_bytes` cannot be /// encoded. See also [`S2K::nearest_hash_count()`]. /// pub(crate) fn encode_count(hash_bytes: u32) -> Result<u8> { // eeee.mmmm -> (16 + mmmm) * 2^(6 + e) let msb = 32 - hash_bytes.leading_zeros(); let (mantissa_mask, tail_mask) = match msb { 0..=10 => { return Err(Error::InvalidArgument( format!("S2K: cannot encode iteration count of {}", hash_bytes)).into()); } 11..=32 => { let m = 0b11_1100_0000 << (msb - 11); let t = 1 << (msb - 11); (m, t - 1) } _ => unreachable!() }; let exp = if msb < 11 { 0 } else { msb - 11 }; let mantissa = (hash_bytes & mantissa_mask) >> (msb - 5); if tail_mask & hash_bytes != 0 { return Err(Error::InvalidArgument( format!("S2K: cannot encode iteration count of {}", hash_bytes)).into()); } Ok(mantissa as u8 | (exp as u8) << 4) } } impl fmt::Display for S2K { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { #[allow(deprecated)] match self { S2K::Simple{ hash } => f.write_fmt(format_args!("Simple S2K with {}", hash)), S2K::Salted{ hash, salt } => { f.write_fmt( format_args!("Salted S2K with {} and salt\ {:x}{:x}{:x}{:x}{:x}{:x}{:x}{:x}", hash, salt[0], salt[1], salt[2], salt[3], salt[4], salt[5], salt[6], salt[7])) } S2K::Iterated{ hash, salt, hash_bytes, } => { f.write_fmt( format_args!("Iterated and Salted S2K with {}, \ salt {:x}{:x}{:x}{:x}{:x}{:x}{:x}{:x} and \ {} bytes to hash", hash, salt[0], salt[1], salt[2], salt[3], salt[4], salt[5], salt[6], salt[7], hash_bytes)) } S2K::Implicit => f.write_str("Implicit S2K"), S2K::Argon2 { salt, t, p, m, } => { write!(f, "Argon2id with t: {}, p: {}, m: 2^{}, salt: {}", t, p, m, crate::fmt::hex::encode(salt)) }, S2K::Private { tag, parameters } => if let Some(p) = parameters.as_ref() { write!(f, "Private/Experimental S2K {}:{:?}", tag, p) } else { write!(f, "Private/Experimental S2K {}", tag) }, S2K::Unknown { tag, parameters } => if let Some(p) = parameters.as_ref() { write!(f, "Unknown S2K {}:{:?}", tag, p) } else { write!(f, "Unknown S2K {}", tag) }, } } } #[cfg(test)] impl Arbitrary for S2K { fn arbitrary(g: &mut Gen) -> Self { use crate::arbitrary_helper::*; #[allow(deprecated)] match gen_arbitrary_from_range(0..8, g) { 0 => S2K::Simple{ hash: HashAlgorithm::arbitrary(g) }, 1 => S2K::Salted{ hash: HashAlgorithm::arbitrary(g), salt: { let mut salt = [0u8; 8]; arbitrary_slice(g, &mut salt); salt }, }, 2 => S2K::Iterated{ hash: HashAlgorithm::arbitrary(g), salt: { let mut salt = [0u8; 8]; arbitrary_slice(g, &mut salt); salt }, hash_bytes: S2K::nearest_hash_count(Arbitrary::arbitrary(g)), }, 7 => S2K::Argon2 { salt: { let mut salt = [0u8; 16]; arbitrary_slice(g, &mut salt); salt }, t: Arbitrary::arbitrary(g), p: Arbitrary::arbitrary(g), m: Arbitrary::arbitrary(g), }, 3 => S2K::Private { tag: gen_arbitrary_from_range(100..111, g), parameters: Some(arbitrary_bounded_vec(g, 200).into()), }, 4 => S2K::Unknown { tag: 2, parameters: Some(arbitrary_bounded_vec(g, 200).into()), }, 5 => S2K::Unknown { tag: gen_arbitrary_from_range(5..100, g), parameters: Some(arbitrary_bounded_vec(g, 200).into()), }, 6 => S2K::Unknown { tag: gen_arbitrary_from_range(111..256, g) as u8, parameters: Some(arbitrary_bounded_vec(g, 200).into()), }, _ => unreachable!(), } } } /// Memory for the Argon2 computation. /// /// We use fallible allocation to gracefully fail if we cannot /// allocate the required space. struct Blocks { blocks: *mut argon2::Block, count: usize, } impl Blocks { fn new(p: &argon2::Params) -> Result<Self> { use std::alloc::Layout; let error = || anyhow::Error::from( Error::InvalidOperation( "failed to allocate memory for key derivation" .into())); let count = p.block_count(); let l = Layout::array::<argon2::Block>(count) .map_err(|_| error())?; let blocks = unsafe { std::alloc::alloc_zeroed(l) as *mut argon2::Block }; if blocks.is_null() { Err(error()) } else { Ok(Blocks { blocks, count, }) } } } impl Drop for Blocks { fn drop(&mut self) { use std::alloc::Layout; let l = Layout::array::<argon2::Block>(self.count) .expect("was valid before"); unsafe { std::alloc::dealloc(self.blocks as *mut _, l) }; } } impl AsMut<[argon2::Block]> for Blocks { fn as_mut(&mut self) -> &mut [argon2::Block] { unsafe { std::slice::from_raw_parts_mut( self.blocks, self.count) } } } #[cfg(test)] mod tests { use super::*; use crate::fmt::to_hex; use crate::SymmetricAlgorithm; use crate::Packet; use crate::parse::{Parse, PacketParser}; #[test] fn s2k_parser_test() { use crate::packet::SKESK; struct Test<'a> { filename: &'a str, s2k: S2K, cipher_algo: SymmetricAlgorithm, password: Password, key_hex: &'a str, } // Note: this test only works with SK-ESK packets that don't // contain an encrypted session key, i.e., the session key is // the result of the s2k function. gpg generates this type of // SK-ESK packet when invoked with -c, but not -e. (When // invoked with -c and -e, it generates SK-ESK packets that // include an encrypted session key.) #[allow(deprecated)] let tests = [ Test { filename: "mode-0-password-1234.gpg", cipher_algo: SymmetricAlgorithm::AES256, s2k: S2K::Simple{ hash: HashAlgorithm::SHA1, }, password: "1234".into(), key_hex: "7110EDA4D09E062AA5E4A390B0A572AC0D2C0220F352B0D292B65164C2A67301", }, Test { filename: "mode-1-password-123456-1.gpg", cipher_algo: SymmetricAlgorithm::AES256, s2k: S2K::Salted{ hash: HashAlgorithm::SHA1, salt: [0xa8, 0x42, 0xa7, 0xa9, 0x59, 0xfa, 0x42, 0x2a], }, password: "123456".into(), key_hex: "8B79077CA448F6FB3D3AD2A264D3B938D357C9FB3E41219FD962DF960A9AFA08", }, Test { filename: "mode-1-password-foobar-2.gpg", cipher_algo: SymmetricAlgorithm::AES256, s2k: S2K::Salted{ hash: HashAlgorithm::SHA1, salt: [0xbc, 0x95, 0x58, 0x45, 0x81, 0x3c, 0x7c, 0x37], }, password: "foobar".into(), key_hex: "B7D48AAE9B943B22A4D390083E8460B5EDFA118FE1688BF0C473B8094D1A8D10", }, Test { filename: "mode-3-password-qwerty-1.gpg", cipher_algo: SymmetricAlgorithm::AES256, s2k: S2K::Iterated { hash: HashAlgorithm::SHA1, salt: [0x78, 0x45, 0xf0, 0x5b, 0x55, 0xf7, 0xb4, 0x9e], hash_bytes: S2K::decode_count(241), }, password: "qwerty".into(), key_hex: "575AD156187A3F8CEC11108309236EB499F1E682F0D1AFADFAC4ECF97613108A", }, Test { filename: "mode-3-password-9876-2.gpg", cipher_algo: SymmetricAlgorithm::AES256, s2k: S2K::Iterated { hash: HashAlgorithm::SHA1, salt: [0xb9, 0x67, 0xea, 0x96, 0x53, 0xdb, 0x6a, 0xc8], hash_bytes: S2K::decode_count(43), }, password: "9876".into(), key_hex: "736C226B8C64E4E6D0325C6C552EF7C0738F98F48FED65FD8C93265103EFA23A", }, Test { filename: "mode-3-aes192-password-123.gpg", cipher_algo: SymmetricAlgorithm::AES192, s2k: S2K::Iterated { hash: HashAlgorithm::SHA1, salt: [0x8f, 0x81, 0x74, 0xc5, 0xd9, 0x61, 0xc7, 0x79], hash_bytes: S2K::decode_count(238), }, password: "123".into(), key_hex: "915E96FC694E7F90A6850B740125EA005199C725F3BD27E3", }, Test { filename: "mode-3-twofish-password-13-times-0123456789.gpg", cipher_algo: SymmetricAlgorithm::Twofish, s2k: S2K::Iterated { hash: HashAlgorithm::SHA1, salt: [0x51, 0xed, 0xfc, 0x15, 0x45, 0x40, 0x65, 0xac], hash_bytes: S2K::decode_count(238), }, password: "0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789".into(), key_hex: "EA264FADA5A859C40D88A159B344ECF1F51FF327FDB3C558B0A7DC299777173E", }, Test { filename: "mode-3-aes128-password-13-times-0123456789.gpg", cipher_algo: SymmetricAlgorithm::AES128, s2k: S2K::Iterated { hash: HashAlgorithm::SHA1, salt: [0x06, 0xe4, 0x61, 0x5c, 0xa4, 0x48, 0xf9, 0xdd], hash_bytes: S2K::decode_count(238), }, password: "0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789".into(), key_hex: "F3D0CE52ED6143637443E3399437FD0F", }, ]; for test in tests.iter().filter(|t| t.cipher_algo.is_supported()) { let path = crate::tests::message(&format!("s2k/{}", test.filename)); let pp = PacketParser::from_bytes(path).unwrap().unwrap(); if let Packet::SKESK(SKESK::V4(ref skesk)) = pp.packet { assert_eq!(skesk.symmetric_algo(), test.cipher_algo); assert_eq!(skesk.s2k(), &test.s2k); let key = skesk.s2k().derive_key( &test.password, skesk.symmetric_algo().key_size().unwrap()); if let Ok(key) = key { let key = to_hex(&key[..], false); assert_eq!(key, test.key_hex); } else { panic!("Session key: None!"); } } else { panic!("Wrong packet!"); } // Get the next packet. let (_, ppr) = pp.next().unwrap(); assert!(ppr.is_eof()); } } quickcheck! { fn s2k_display(s2k: S2K) -> bool { let s = format!("{}", s2k); !s.is_empty() } } quickcheck! { fn s2k_parse(s2k: S2K) -> bool { match s2k { S2K::Unknown { tag, .. } => (tag > 3 && tag < 100) || tag == 2 || tag > 110, S2K::Private { tag, .. } => (100..=110).contains(&tag), _ => true } } } #[test] fn s2k_coded_count_roundtrip() { for cc in 0..0x100usize { let hash_bytes = S2K::decode_count(cc as u8); assert!(hash_bytes >= 1024 && S2K::encode_count(hash_bytes).unwrap() == cc as u8); } } quickcheck!{ fn s2k_coded_count_approx(i: u32) -> bool { let approx = S2K::nearest_hash_count(i as usize); let cc = S2K::encode_count(approx).unwrap(); (approx >= i || i > 0x3e00000) && S2K::decode_count(cc) == approx } } #[test] fn s2k_coded_count_approx_1025() { let i = 1025; let approx = S2K::nearest_hash_count(i); let cc = S2K::encode_count(approx).unwrap(); assert!(approx as usize >= i || i > 0x3e00000); assert_eq!(S2K::decode_count(cc), approx); } #[test] fn s2k_coded_count_approx_0x3e00000() { let i = 0x3e00000; let approx = S2K::nearest_hash_count(i); let cc = S2K::encode_count(approx).unwrap(); assert!(approx as usize >= i || i > 0x3e00000); assert_eq!(S2K::decode_count(cc), approx); } } ���������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/src/crypto/symmetric.rs�������������������������������������������������������0000644�0000000�0000000�00000045715�10461020230�0017444�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������//! Symmetric encryption. use std::io; use std::cmp; use std::fmt; use crate::Result; use crate::SymmetricAlgorithm; use crate::vec_resize; use crate::{ parse::Cookie, }; use buffered_reader::BufferedReader; /// Block cipher mode of operation. /// /// Block modes govern how a block cipher processes data spanning multiple blocks. pub(crate) trait Mode: Send + Sync { /// Block size of the underlying cipher in bytes. #[allow(dead_code)] // Used in some backends. fn block_size(&self) -> usize; /// Encrypt a single block `src` to a ciphertext block `dst`. /// The `dst` and `src` buffers are expected to be at least as large as /// the block size of the underlying cipher. fn encrypt( &mut self, dst: &mut [u8], src: &[u8], ) -> Result<()>; /// Decrypt a single ciphertext block `src` to a plaintext block `dst`. /// The `dst` and `src` buffers are expected to be at least as large as /// the block size of the underlying cipher. fn decrypt( &mut self, dst: &mut [u8], src: &[u8], ) -> Result<()>; } /// A `Read`er for decrypting symmetrically encrypted data. pub struct Decryptor<'a> { // The encrypted data. source: Box<dyn BufferedReader<Cookie> + 'a>, dec: Box<dyn Mode>, block_size: usize, // Up to a block of unread data. buffer: Vec<u8>, } assert_send_and_sync!(Decryptor<'_>); impl<'a> Decryptor<'a> { /// Instantiate a new symmetric decryptor. /// /// `reader` is the source to wrap. pub fn new<R>(algo: SymmetricAlgorithm, key: &[u8], source: R) -> Result<Self> where R: io::Read + Send + Sync + 'a, { Self::from_cookie_reader( algo, key, Box::new(buffered_reader::Generic::with_cookie( source, None, Default::default()))) } /// Instantiate a new symmetric decryptor. fn from_cookie_reader(algo: SymmetricAlgorithm, key: &[u8], source: Box<dyn BufferedReader<Cookie> + 'a>) -> Result<Self> { let block_size = algo.block_size()?; let iv = vec![0; block_size]; let dec = algo.make_decrypt_cfb(key, iv)?; Ok(Decryptor { source, dec, block_size, buffer: Vec::with_capacity(block_size), }) } } // Note: this implementation tries *very* hard to make sure we don't // gratuitiously do a short read. Specifically, if the return value // is less than `plaintext.len()`, then it is either because we // reached the end of the input or an error occurred. impl<'a> io::Read for Decryptor<'a> { fn read(&mut self, plaintext: &mut [u8]) -> io::Result<usize> { let mut pos = 0; // 1. Copy any buffered data. if !self.buffer.is_empty() { let to_copy = cmp::min(self.buffer.len(), plaintext.len()); plaintext[..to_copy].copy_from_slice(&self.buffer[..to_copy]); crate::vec_drain_prefix(&mut self.buffer, to_copy); pos = to_copy; } if pos == plaintext.len() { return Ok(pos); } // 2. Decrypt as many whole blocks as `plaintext` can hold. let mut to_copy = ((plaintext.len() - pos) / self.block_size) * self.block_size; let result = self.source.data_consume(to_copy); let short_read; let ciphertext = match result { Ok(data) => { short_read = data.len() < to_copy; to_copy = data.len().min(to_copy); &data[..to_copy] }, // We encountered an error, but we did read some. Err(_) if pos > 0 => return Ok(pos), Err(e) => return Err(e), }; self.dec.decrypt(&mut plaintext[pos..pos + to_copy], ciphertext) .map_err(|e| io::Error::new(io::ErrorKind::InvalidInput, format!("{}", e)))?; pos += to_copy; if short_read || pos == plaintext.len() { return Ok(pos); } // 3. The last bit is a partial block. Buffer it. let mut to_copy = plaintext.len() - pos; assert!(0 < to_copy); assert!(to_copy < self.block_size); let to_read = self.block_size; let result = self.source.data_consume(to_read); let ciphertext = match result { Ok(data) => { // Make sure we don't read more than is available. to_copy = cmp::min(to_copy, data.len()); &data[..data.len().min(to_read)] }, // We encountered an error, but we did read some. Err(_) if pos > 0 => return Ok(pos), Err(e) => return Err(e), }; assert!(ciphertext.len() <= self.block_size); vec_resize(&mut self.buffer, ciphertext.len()); self.dec.decrypt(&mut self.buffer, ciphertext) .map_err(|e| io::Error::new(io::ErrorKind::InvalidInput, format!("{}", e)))?; plaintext[pos..pos + to_copy].copy_from_slice(&self.buffer[..to_copy]); crate::vec_drain_prefix(&mut self.buffer, to_copy); pos += to_copy; Ok(pos) } } /// A `BufferedReader` that decrypts symmetrically-encrypted data as /// it is read. pub(crate) struct BufferedReaderDecryptor<'a> { reader: buffered_reader::Generic<Decryptor<'a>, Cookie>, } impl<'a> BufferedReaderDecryptor<'a> { /// Like `new()`, but sets a cookie, which can be retrieved using /// the `cookie_ref` and `cookie_mut` methods, and set using /// the `cookie_set` method. pub fn with_cookie(algo: SymmetricAlgorithm, key: &[u8], reader: Box<dyn BufferedReader<Cookie> + 'a>, cookie: Cookie) -> Result<Self> { Ok(BufferedReaderDecryptor { reader: buffered_reader::Generic::with_cookie( Decryptor::from_cookie_reader(algo, key, reader)?, None, cookie), }) } } impl<'a> io::Read for BufferedReaderDecryptor<'a> { fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> { self.reader.read(buf) } } impl<'a> fmt::Display for BufferedReaderDecryptor<'a> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "BufferedReaderDecryptor") } } impl<'a> fmt::Debug for BufferedReaderDecryptor<'a> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.debug_struct("BufferedReaderDecryptor") .field("reader", &self.get_ref().unwrap()) .finish() } } impl<'a> BufferedReader<Cookie> for BufferedReaderDecryptor<'a> { fn buffer(&self) -> &[u8] { self.reader.buffer() } fn data(&mut self, amount: usize) -> io::Result<&[u8]> { self.reader.data(amount) } fn data_hard(&mut self, amount: usize) -> io::Result<&[u8]> { self.reader.data_hard(amount) } fn data_eof(&mut self) -> io::Result<&[u8]> { self.reader.data_eof() } fn consume(&mut self, amount: usize) -> &[u8] { self.reader.consume(amount) } fn data_consume(&mut self, amount: usize) -> io::Result<&[u8]> { self.reader.data_consume(amount) } fn data_consume_hard(&mut self, amount: usize) -> io::Result<&[u8]> { self.reader.data_consume_hard(amount) } fn read_be_u16(&mut self) -> io::Result<u16> { self.reader.read_be_u16() } fn read_be_u32(&mut self) -> io::Result<u32> { self.reader.read_be_u32() } fn steal(&mut self, amount: usize) -> io::Result<Vec<u8>> { self.reader.steal(amount) } fn steal_eof(&mut self) -> io::Result<Vec<u8>> { self.reader.steal_eof() } fn get_mut(&mut self) -> Option<&mut dyn BufferedReader<Cookie>> { Some(&mut self.reader.reader_mut().source) } fn get_ref(&self) -> Option<&dyn BufferedReader<Cookie>> { Some(&self.reader.reader_ref().source) } fn into_inner<'b>(self: Box<Self>) -> Option<Box<dyn BufferedReader<Cookie> + 'b>> where Self: 'b { Some(self.reader.into_reader().source.into_boxed()) } fn cookie_set(&mut self, cookie: Cookie) -> Cookie { self.reader.cookie_set(cookie) } fn cookie_ref(&self) -> &Cookie { self.reader.cookie_ref() } fn cookie_mut(&mut self) -> &mut Cookie { self.reader.cookie_mut() } } /// A `Write`r for symmetrically encrypting data. pub struct Encryptor<W: io::Write> { inner: Option<W>, cipher: Box<dyn Mode>, block_size: usize, // Up to a block of unencrypted data. buffer: Vec<u8>, // A place to write encrypted data into. scratch: Vec<u8>, } assert_send_and_sync!(Encryptor<W> where W: io::Write); impl<W: io::Write> Encryptor<W> { /// Instantiate a new symmetric encryptor. pub fn new(algo: SymmetricAlgorithm, key: &[u8], sink: W) -> Result<Self> { let block_size = algo.block_size()?; let iv = vec![0; block_size]; let cipher = algo.make_encrypt_cfb(key, iv)?; Ok(Encryptor { inner: Some(sink), cipher, block_size, buffer: Vec::with_capacity(block_size), scratch: vec![0; 4096], }) } /// Finish encryption and write last partial block. pub fn finish(&mut self) -> Result<W> { if let Some(mut inner) = self.inner.take() { if !self.buffer.is_empty() { let n = self.buffer.len(); assert!(n <= self.block_size); self.cipher.encrypt(&mut self.scratch[..n], &self.buffer)?; crate::vec_truncate(&mut self.buffer, 0); inner.write_all(&self.scratch[..n])?; crate::vec_truncate(&mut self.scratch, 0); } Ok(inner) } else { Err(io::Error::new(io::ErrorKind::BrokenPipe, "Inner writer was taken").into()) } } /// Acquires a reference to the underlying writer. pub fn get_ref(&self) -> Option<&W> { self.inner.as_ref() } /// Acquires a mutable reference to the underlying writer. #[allow(dead_code)] pub fn get_mut(&mut self) -> Option<&mut W> { self.inner.as_mut() } } impl<W: io::Write> io::Write for Encryptor<W> { fn write(&mut self, mut buf: &[u8]) -> io::Result<usize> { if self.inner.is_none() { return Err(io::Error::new(io::ErrorKind::BrokenPipe, "Inner writer was taken")); } let inner = self.inner.as_mut().unwrap(); let amount = buf.len(); // First, fill the buffer if there is something in it. if !self.buffer.is_empty() { let n = cmp::min(buf.len(), self.block_size - self.buffer.len()); self.buffer.extend_from_slice(&buf[..n]); assert!(self.buffer.len() <= self.block_size); buf = &buf[n..]; // And possibly encrypt the block. if self.buffer.len() == self.block_size { self.cipher.encrypt(&mut self.scratch[..self.block_size], &self.buffer) .map_err(|e| io::Error::new(io::ErrorKind::InvalidInput, format!("{}", e)))?; crate::vec_truncate(&mut self.buffer, 0); inner.write_all(&self.scratch[..self.block_size])?; } } // Then, encrypt all whole blocks. let whole_blocks = (buf.len() / self.block_size) * self.block_size; if whole_blocks > 0 { // Encrypt whole blocks. if self.scratch.len() < whole_blocks { vec_resize(&mut self.scratch, whole_blocks); } self.cipher.encrypt(&mut self.scratch[..whole_blocks], &buf[..whole_blocks]) .map_err(|e| io::Error::new(io::ErrorKind::InvalidInput, format!("{}", e)))?; inner.write_all(&self.scratch[..whole_blocks])?; } // Stash rest for later. assert!(buf.is_empty() || self.buffer.is_empty()); self.buffer.extend_from_slice(&buf[whole_blocks..]); Ok(amount) } fn flush(&mut self) -> io::Result<()> { // It is not clear how we can implement this, because we can // only operate on block sizes. We will, however, ask our // inner writer to flush. if let Some(ref mut inner) = self.inner { inner.flush() } else { Err(io::Error::new(io::ErrorKind::BrokenPipe, "Inner writer was taken")) } } } impl<W: io::Write> Drop for Encryptor<W> { fn drop(&mut self) { // Unfortunately, we cannot handle errors here. If error // handling is a concern, call finish() and properly handle // errors there. let _ = self.finish(); } } #[cfg(test)] mod tests { use super::*; use std::io::{Cursor, Read, Write}; #[test] fn smoke_test() { use crate::fmt::hex; let algo = SymmetricAlgorithm::AES128; let key = &hex::decode("2b7e151628aed2a6abf7158809cf4f3c").unwrap(); assert_eq!(key.len(), 16); // Ensure we use CFB128 by default let iv = hex::decode("000102030405060708090A0B0C0D0E0F").unwrap(); let mut cfb = algo.make_encrypt_cfb(key, iv).unwrap(); let msg = hex::decode("6bc1bee22e409f96e93d7e117393172a").unwrap(); let mut dst = vec![0; msg.len()]; cfb.encrypt(&mut dst, &*msg).unwrap(); assert_eq!(&dst[..16], &*hex::decode("3b3fd92eb72dad20333449f8e83cfb4a").unwrap()); // 32-byte long message let iv = hex::decode("000102030405060708090A0B0C0D0E0F").unwrap(); let mut cfb = algo.make_encrypt_cfb(key, iv).unwrap(); let msg = b"This is a very important message"; let mut dst = vec![0; msg.len()]; cfb.encrypt(&mut dst, &*msg).unwrap(); assert_eq!(&dst, &hex::decode( "04960ebfb9044196bb29418ce9d6cc0939d5ccb1d0712fa8e45fe5673456fded" ).unwrap()); // 33-byte (uneven) long message let iv = hex::decode("000102030405060708090A0B0C0D0E0F").unwrap(); let mut cfb = algo.make_encrypt_cfb(key, iv).unwrap(); let msg = b"This is a very important message!"; let mut dst = vec![0; msg.len()]; cfb.encrypt(&mut dst, &*msg).unwrap(); assert_eq!(&dst, &hex::decode( "04960ebfb9044196bb29418ce9d6cc0939d5ccb1d0712fa8e45fe5673456fded0b" ).unwrap()); // 33-byte (uneven) long message, chunked let iv = hex::decode("000102030405060708090A0B0C0D0E0F").unwrap(); let mut cfb = algo.make_encrypt_cfb(key, iv).unwrap(); let mut dst = vec![0; msg.len()]; for (mut dst, msg) in dst.chunks_mut(16).zip(msg.chunks(16)) { cfb.encrypt(&mut dst, msg).unwrap(); } assert_eq!(&dst, &hex::decode( "04960ebfb9044196bb29418ce9d6cc0939d5ccb1d0712fa8e45fe5673456fded0b" ).unwrap()); } /// This test is designed to test the buffering logic in Decryptor /// by reading directly from it (i.e. without any buffering /// introduced by the BufferedReaderDecryptor or any other source /// of buffering). #[test] fn decryptor() { for algo in [SymmetricAlgorithm::AES128, SymmetricAlgorithm::AES192, SymmetricAlgorithm::AES256].iter() { // The keys are [0, 1, 2, ...]. let mut key = vec![0u8; algo.key_size().unwrap()]; for i in 0..key.len() { key[0] = i as u8; } let filename = &format!( "raw/a-cypherpunks-manifesto.aes{}.key_ascending_from_0", algo.key_size().unwrap() * 8); let ciphertext = Cursor::new(crate::tests::file(filename)); let decryptor = Decryptor::new(*algo, &key, ciphertext).unwrap(); // Read bytewise to test the buffer logic. let mut plaintext = Vec::new(); for b in decryptor.bytes() { plaintext.push(b.unwrap()); } assert_eq!(crate::tests::manifesto(), &plaintext[..]); } } /// This test is designed to test the buffering logic in Encryptor /// by writing directly to it. #[test] fn encryptor() { for algo in [SymmetricAlgorithm::AES128, SymmetricAlgorithm::AES192, SymmetricAlgorithm::AES256].iter() { // The keys are [0, 1, 2, ...]. let mut key = vec![0u8; algo.key_size().unwrap()]; for i in 0..key.len() { key[0] = i as u8; } let mut ciphertext = Vec::new(); { let mut encryptor = Encryptor::new(*algo, &key, &mut ciphertext) .unwrap(); // Write bytewise to test the buffer logic. for b in crate::tests::manifesto().chunks(1) { encryptor.write_all(b).unwrap(); } } let filename = format!( "raw/a-cypherpunks-manifesto.aes{}.key_ascending_from_0", algo.key_size().unwrap() * 8); let mut cipherfile = Cursor::new(crate::tests::file(&filename)); let mut reference = Vec::new(); cipherfile.read_to_end(&mut reference).unwrap(); assert_eq!(&reference[..], &ciphertext[..]); } } /// This test tries to encrypt, then decrypt some data. #[test] fn roundtrip() { use std::io::Cursor; #[allow(deprecated)] for algo in [SymmetricAlgorithm::TripleDES, SymmetricAlgorithm::CAST5, SymmetricAlgorithm::Blowfish, SymmetricAlgorithm::AES128, SymmetricAlgorithm::AES192, SymmetricAlgorithm::AES256, SymmetricAlgorithm::Twofish, SymmetricAlgorithm::Camellia128, SymmetricAlgorithm::Camellia192, SymmetricAlgorithm::Camellia256] .iter() .filter(|x| x.is_supported()) { let mut key = vec![0; algo.key_size().unwrap()]; crate::crypto::random(&mut key).unwrap(); let mut ciphertext = Vec::new(); { let mut encryptor = Encryptor::new(*algo, &key, &mut ciphertext) .unwrap(); encryptor.write_all(crate::tests::manifesto()).unwrap(); } let mut plaintext = Vec::new(); { let mut decryptor = Decryptor::new(*algo, &key, Cursor::new(&mut ciphertext)) .unwrap(); decryptor.read_to_end(&mut plaintext).unwrap(); } assert_eq!(&plaintext[..], crate::tests::manifesto()); } } } ���������������������������������������������������sequoia-openpgp-2.0.0/src/crypto/tests/dsa.rs�������������������������������������������������������0000644�0000000�0000000�00002261737�10461020230�0017347�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������//! Low-level DSA tests. use crate::Result; use crate::crypto::mpi; use crate::packet::{prelude::*, signature::subpacket::*}; use crate::types::*; #[test] #[allow(deprecated)] fn fips_186_3() -> Result<()> { if ! PublicKeyAlgorithm::DSA.is_supported() { eprintln!("Skipping because DSA is not supported."); return Ok(()); } fn test(hash: HashAlgorithm, msg: &[u8], p: &[u8], q: &[u8], g: &[u8], y: &[u8], r: &[u8], s: &[u8]) -> Result<()> { if ! hash.is_supported() { eprintln!("Skipping because {} is not supported.", hash); return Ok(()); } if cfg!(feature = "crypto-cng") && q.len() == 28 { eprintln!("Skipping DSA key with N = 224 because \ CNG doesn't support this."); return Ok(()); } let now = Timestamp::now(); let key: Key<key::PublicParts, key::PrimaryRole> = Key4::new(now, PublicKeyAlgorithm::DSA, mpi::PublicKey::DSA { p: mpi::MPI::new(p), q: mpi::MPI::new(q), g: mpi::MPI::new(g), y: mpi::MPI::new(y), })?.into(); let mut h = hash.context()?.for_digest(); h.update(msg); let mut d = h.into_digest()?; let sig: Signature = Signature4::new(SignatureType::Binary, PublicKeyAlgorithm::DSA, hash, SubpacketArea::new(vec![ Subpacket::new( SubpacketValue::SignatureCreationTime(now), false)?, ])?, SubpacketArea::default(), [d[0], d[1]], mpi::Signature::DSA { r: mpi::MPI::new(r), s: mpi::MPI::new(s), }).into(); sig.verify_digest(&key, &d)?; // Sanity check: Change the digest and retry. d[0] ^= 1; sig.verify_digest(&key, &d).unwrap_err(); Ok(()) } // [mod = L=1024, N=160, SHA-1] let p = b"\xa8\xf9\xcd\x20\x1e\x5e\x35\xd8\x92\xf8\x5f\x80\xe4\xdb\x25\x99\xa5\x67\x6a\x3b\x1d\x4f\x19\x03\x30\xed\x32\x56\xb2\x6d\x0e\x80\xa0\xe4\x9a\x8f\xff\xaa\xad\x2a\x24\xf4\x72\xd2\x57\x32\x41\xd4\xd6\xd6\xc7\x48\x0c\x80\xb4\xc6\x7b\xb4\x47\x9c\x15\xad\xa7\xea\x84\x24\xd2\x50\x2f\xa0\x14\x72\xe7\x60\x24\x17\x13\xda\xb0\x25\xae\x1b\x02\xe1\x70\x3a\x14\x35\xf6\x2d\xdf\x4e\xe4\xc1\xb6\x64\x06\x6e\xb2\x2f\x2e\x3b\xf2\x8b\xb7\x0a\x2a\x76\xe4\xfd\x5e\xbe\x2d\x12\x29\x68\x1b\x5b\x06\x43\x9a\xc9\xc7\xe9\xd8\xbd\xe2\x83"; let q = b"\xf8\x5f\x0f\x83\xac\x4d\xf7\xea\x0c\xdf\x8f\x46\x9b\xfe\xea\xea\x14\x15\x64\x95"; let g = b"\x2b\x31\x52\xff\x6c\x62\xf1\x46\x22\xb8\xf4\x8e\x59\xf8\xaf\x46\x88\x3b\x38\xe7\x9b\x8c\x74\xde\xea\xe9\xdf\x13\x1f\x8b\x85\x6e\x3a\xd6\xc8\x45\x5d\xab\x87\xcc\x0d\xa8\xac\x97\x34\x17\xce\x4f\x78\x78\x55\x7d\x6c\xdf\x40\xb3\x5b\x4a\x0c\xa3\xeb\x31\x0c\x6a\x95\xd6\x8c\xe2\x84\xad\x4e\x25\xea\x28\x59\x16\x11\xee\x08\xb8\x44\x4b\xd6\x4b\x25\xf3\xf7\xc5\x72\x41\x0d\xdf\xb3\x9c\xc7\x28\xb9\xc9\x36\xf8\x5f\x41\x91\x29\x86\x99\x29\xcd\xb9\x09\xa6\xa3\xa9\x9b\xbe\x08\x92\x16\x36\x81\x71\xbd\x0b\xa8\x1d\xe4\xfe\x33"; test( HashAlgorithm::SHA1, b"\x3b\x46\x73\x6d\x55\x9b\xd4\xe0\xc2\xc1\xb2\x55\x3a\x33\xad\x3c\x6c\xf2\x3c\xac\x99\x8d\x3d\x0c\x0e\x8f\xa4\xb1\x9b\xca\x06\xf2\xf3\x86\xdb\x2d\xcf\xf9\xdc\xa4\xf4\x0a\xd8\xf5\x61\xff\xc3\x08\xb4\x6c\x5f\x31\xa7\x73\x5b\x5f\xa7\xe0\xf9\xe6\xcb\x51\x2e\x63\xd7\xee\xa0\x55\x38\xd6\x6a\x75\xcd\x0d\x42\x34\xb5\xcc\xf6\xc1\x71\x5c\xca\xaf\x9c\xdc\x0a\x22\x28\x13\x5f\x71\x6e\xe9\xbd\xee\x7f\xc1\x3e\xc2\x7a\x03\xa6\xd1\x1c\x5c\x5b\x36\x85\xf5\x19\x00\xb1\x33\x71\x53\xbc\x6c\x4e\x8f\x52\x92\x0c\x33\xfa\x37\xf4\xe7", p, q, g, b"\x31\x3f\xd9\xeb\xca\x91\x57\x4e\x1c\x2e\xeb\xe1\x51\x7c\x57\xe0\xc2\x1b\x02\x09\x87\x21\x40\xc5\x32\x87\x61\xbb\xb2\x45\x0b\x33\xf1\xb1\x8b\x40\x9c\xe9\xab\x7c\x4c\xd8\xfd\xa3\x39\x1e\x8e\x34\x86\x83\x57\xc1\x99\xe1\x6a\x6b\x2e\xba\x06\xd6\x74\x9d\xef\x79\x1d\x79\xe9\x5d\x3a\x4d\x09\xb2\x4c\x39\x2a\xd8\x9d\xbf\x10\x09\x95\xae\x19\xc0\x10\x62\x05\x6b\xb1\x4b\xce\x00\x5e\x87\x31\xef\xde\x17\x5f\x95\xb9\x75\x08\x9b\xdc\xda\xea\x56\x2b\x32\x78\x6d\x96\xf5\xa3\x1a\xed\xf7\x53\x64\x00\x8a\xd4\xff\xfe\xbb\x97\x0b", b"\x50\xed\x0e\x81\x0e\x3f\x1c\x7c\xb6\xac\x62\x33\x20\x58\x44\x8b\xd8\xb2\x84\xc0", b"\xc6\xad\xed\x17\x21\x6b\x46\xb7\xe4\xb6\xf2\xa9\x7c\x1a\xd7\xcc\x3d\xa8\x3f\xde", )?; test( HashAlgorithm::SHA1, b"\xd2\xbc\xb5\x3b\x04\x4b\x3e\x2e\x4b\x61\xba\x2f\x91\xc0\x99\x5f\xb8\x3a\x6a\x97\x52\x5e\x66\x44\x1a\x3b\x48\x9d\x95\x94\x23\x8b\xc7\x40\xbd\xee\xa0\xf7\x18\xa7\x69\xc9\x77\xe2\xde\x00\x38\x77\xb5\xd7\xdc\x25\xb1\x82\xae\x53\x3d\xb3\x3e\x78\xf2\xc3\xff\x06\x45\xf2\x13\x7a\xbc\x13\x7d\x4e\x7d\x93\xcc\xf2\x4f\x60\xb1\x8a\x82\x0b\xc0\x7c\x7b\x4b\x5f\xe0\x8b\x4f\x9e\x7d\x21\xb2\x56\xc1\x8f\x3b\x9d\x49\xac\xc4\xf9\x3e\x2c\xe6\xf3\x75\x4c\x78\x07\x75\x7d\x2e\x11\x76\x04\x26\x12\xcb\x32\xfc\x3f\x4f\x70\x70\x0e\x25", p, q, g, b"\x29\xbd\xd7\x59\xaa\xa6\x2d\x4b\xf1\x6b\x48\x61\xc8\x1c\xf4\x2e\xac\x2e\x16\x37\xb9\xec\xba\x51\x2b\xdb\xc1\x3a\xc1\x2a\x80\xae\x8d\xe2\x52\x6b\x89\x9a\xe5\xe4\xa2\x31\xae\xf8\x84\x19\x7c\x94\x4c\x73\x26\x93\xa6\x34\xd7\x65\x9a\xbc\x69\x75\xa7\x73\xf8\xd3\xcd\x5a\x36\x1f\xe2\x49\x23\x86\xa3\xc0\x9a\xae\xf1\x2e\x4a\x7e\x73\xad\x7d\xfc\x36\x37\xf7\xb0\x93\xf2\xc4\x0d\x62\x23\xa1\x95\xc1\x36\xad\xf2\xea\x3f\xbf\x87\x04\xa6\x75\xaa\x78\x17\xaa\x7e\xc7\xf9\xad\xfb\x28\x54\xd4\xe0\x5c\x3c\xe7\xf7\x65\x60\x31\x3b", b"\xa2\x6c\x00\xb5\x75\x0a\x2d\x27\xfe\x74\x35\xb9\x34\x76\xb3\x54\x38\xb4\xd8\xab", b"\x61\xc9\xbf\xcb\x29\x38\x75\x5a\xfa\x7d\xad\x1d\x1e\x07\xc6\x28\x86\x17\xbf\x70", )?; test( HashAlgorithm::SHA1, b"\xd5\x43\x1e\x6b\x16\xfd\xae\x31\x48\x17\x42\xbd\x39\x47\x58\xbe\xb8\xe2\x4f\x31\x94\x7e\x19\xb7\xea\x7b\x45\x85\x21\x88\x22\x70\xc1\xf4\x31\x92\xaa\x05\x0f\x44\x85\x14\x5a\xf8\xf3\xf9\xc5\x14\x2d\x68\xb8\x50\x18\xd2\xec\x9c\xb7\xa3\x7b\xa1\x2e\xd2\x3e\x73\xb9\x5f\xd6\x80\xfb\xa3\xc6\x12\x65\xe9\xf5\xa0\xa0\x27\xd7\x0f\xad\x0c\x8a\xa0\x8a\x3c\xbf\xbe\x99\x01\x8d\x00\x45\x38\x61\x73\xe5\xfa\xe2\x25\xfa\xeb\xe0\xce\xf5\xdd\x45\x91\x0f\x40\x0a\x86\xc2\xbe\x4e\x15\x25\x2a\x16\xde\x41\x20\xa2\x67\xbe\x2b\x59\x4d", p, q, g, b"\x23\xb4\xf4\x04\xaa\x3c\x57\x5e\x55\x0b\xb3\x20\xfd\xb1\xa0\x85\xcd\x39\x6a\x10\xe5\xeb\xc6\x77\x1d\xa6\x2f\x03\x7c\xab\x19\xea\xcd\x67\xd8\x22\x2b\x63\x44\x03\x8c\x4f\x7a\xf4\x5f\x5e\x62\xb5\x54\x80\xcb\xe2\x11\x11\x54\xca\x96\x97\xca\x76\xd8\x7b\x56\x94\x41\x38\x08\x4e\x74\xc6\xf9\x0a\x05\xcf\x43\x66\x0d\xff\x8b\x8b\x3f\xab\xfc\xab\x3f\x0e\x44\x16\x77\x5f\xdf\x40\x05\x58\x64\xbe\x10\x2b\x45\x87\x39\x2e\x77\x75\x2e\xd2\xae\xb1\x82\xee\x4f\x70\xbe\x4a\x29\x1d\xbe\x77\xb8\x4a\x44\xee\x34\x00\x79\x57\xb1\xe0", b"\x3f\x0a\x4a\xd3\x2f\x08\x16\x82\x1b\x8a\xff\xb5\x18\xe9\xb5\x99\xf3\x5d\x57\xc2", b"\xea\x06\x63\x8f\x2b\x2f\xc9\xd1\xdf\xe9\x9c\x2a\x49\x28\x06\xb4\x97\xe2\xb0\xea", )?; test( HashAlgorithm::SHA1, b"\x85\x66\x2b\x69\x75\x50\xe4\x91\x5c\x29\xe3\x38\xb6\x24\xb9\x12\x84\x5d\x6d\x1a\x92\x0d\x9e\x4c\x16\x04\xdd\x47\xd6\x92\xbc\x7c\x0f\xfb\x95\xae\x61\x4e\x85\x2b\xeb\xaf\x15\x73\x75\x8a\xd0\x1c\x71\x3c\xac\x0b\x47\x6e\x2f\x12\x17\x45\xa3\xcf\xee\xff\xb2\x44\x1f\xf6\xab\xfb\x9b\xbe\xb9\x8a\xa6\x34\xca\x6f\xf5\x41\x94\x7d\xcc\x99\x27\x65\x9d\x44\xf9\x5c\x5f\xf9\x17\x0f\xdc\x3c\x86\x47\x3c\xb6\x01\xba\x31\xb4\x87\xfe\x59\x36\xba\xc5\xd9\xc6\x32\xcb\xcc\x3d\xb0\x62\x46\xba\x01\xc5\x5a\x03\x8d\x79\x7f\xe3\xf6\xc3", p, q, g, b"\x6b\xc3\x6c\xb3\xfa\x61\xce\xcc\x15\x7b\xe0\x86\x39\xa7\xca\x9e\x3d\xe0\x73\xb8\xa0\xff\x23\x57\x4c\xe5\xab\x0a\x86\x7d\xfd\x60\x66\x9a\x56\xe6\x0d\x1c\x98\x9b\x3a\xf8\xc8\xa4\x3f\x56\x95\xd5\x03\xe3\x09\x89\x63\x99\x0e\x12\xb6\x35\x66\x78\x41\x71\x05\x8e\xac\xe8\x5c\x72\x8c\xd4\xc0\x82\x24\xc7\xa6\xef\xea\x75\xdc\xa2\x0d\xf4\x61\x01\x3c\x75\xf4\x0a\xcb\xc2\x37\x99\xeb\xee\x7f\x33\x61\x33\x6d\xad\xc4\xa5\x6f\x30\x57\x08\x66\x7b\xfe\x60\x2b\x8e\xa7\x5a\x49\x1a\x5c\xf0\xc0\x6e\xbd\x6f\xdc\x71\x61\xe1\x04\x97", b"\x3b\xc2\x9d\xee\x96\x95\x70\x50\xba\x43\x8d\x1b\x3e\x17\xb0\x2c\x17\x25\xd2\x29", b"\x0a\xf8\x79\xcf\x84\x6c\x43\x4e\x08\xfb\x6c\x63\x78\x2f\x4d\x03\xe0\xd8\x88\x65", )?; test( HashAlgorithm::SHA1, b"\x87\xb6\xe7\x5b\x9f\x8e\x99\xc4\xdd\x62\xad\xb6\x93\xdd\x58\x90\xed\xff\x1b\xd0\x02\x8f\x4e\xf8\x49\xdf\x0f\x1d\x2c\xe6\xb1\x81\xfc\x3a\x55\xae\xa6\xd0\xa1\xf0\xae\xca\xb8\xed\x9e\x24\x8a\x00\xe9\x6b\xe7\x94\xa7\xcf\xba\x12\x46\xef\xb7\x10\xef\x4b\x37\x47\x1c\xef\x0a\x1b\xcf\x55\xce\xbc\x8d\x5a\xd0\x71\x61\x2b\xd2\x37\xef\xed\xd5\x10\x23\x62\xdb\x07\xa1\xe2\xc7\xa6\xf1\x5e\x09\xfe\x64\xba\x42\xb6\x0a\x26\x28\xd8\x69\xae\x05\xef\x61\x1f\xe3\x8d\x9c\xe1\x5e\xee\xc9\xbb\x3d\xec\xc8\xdc\x17\x80\x9f\x3b\x6e\x95", p, q, g, b"\x01\x4a\xc7\x46\xd3\x60\x5e\xfc\xb8\xa2\xc7\xda\xe1\xf5\x46\x82\xa2\x62\xe2\x76\x62\xb2\x52\xc0\x94\x78\xce\x87\xd0\xaa\xa5\x22\xd7\xc2\x00\x04\x34\x06\x01\x6c\x0c\x42\x89\x6d\x21\x75\x0b\x15\xdb\xd5\x7f\x97\x07\xec\x37\xdc\xea\x56\x51\x78\x1b\x67\xad\x8d\x01\xf5\x09\x9f\xe7\x58\x4b\x35\x3b\x64\x1b\xb1\x59\xcc\x71\x7d\x8c\xeb\x18\xb6\x67\x05\xe6\x56\xf3\x36\xf1\x21\x4b\x34\xf0\x35\x7e\x57\x7a\xb8\x36\x41\x96\x9e\x31\x1b\xf4\x0b\xdc\xb3\xff\xd5\xe0\xbb\x59\x41\x9f\x22\x95\x08\xd2\xf4\x32\xcc\x28\x59\xff\x75", b"\x63\x7e\x07\xa5\x77\x0f\x3d\xc6\x5e\x45\x06\xc6\x8c\x77\x0e\x5e\xf6\xb8\xce\xd3", b"\x7d\xfc\x6f\x83\xe2\x4f\x09\x74\x5e\x01\xd3\xf7\xae\x0e\xd1\x47\x4e\x81\x1d\x47", )?; test( HashAlgorithm::SHA1, b"\x22\x59\xee\xad\x2d\x6b\xbc\x76\xd4\x92\x13\xea\x0d\xc8\xb7\x35\x0a\x97\x69\x9f\x22\x34\x10\x44\xc3\x94\x07\x82\x36\x4a\xc9\xea\x68\x31\x79\xa4\x38\xa5\xea\x45\x99\x8d\xf9\x7c\x29\x72\xda\xe0\x38\x51\xf5\xbe\x23\xfa\x9f\x04\x18\x2e\x79\xdd\xb2\xb5\x6d\xc8\x65\x23\x93\xec\xb2\x7f\x3f\x3b\x7c\x8a\x8d\x76\x1a\x86\xb3\xb8\xf4\xd4\x1a\x07\xb4\xbe\x7d\x02\xfd\xde\xfc\x42\xb9\x28\x12\x4a\x5a\x45\xb9\xf4\x60\x90\x42\x20\x9b\x3a\x7f\x58\x5b\xd5\x14\xcc\x39\xc0\x0e\xff\xcc\x42\xc7\xfe\x70\xfa\x83\xed\xf8\xa3\x2b\xf4", p, q, g, b"\x0f\xe7\x40\x45\xd7\xb0\xd4\x72\x41\x12\x02\x83\x1d\x49\x32\x39\x6f\x24\x2a\x97\x65\xe9\x2b\xe3\x87\xfd\x81\xbb\xe3\x8d\x84\x50\x54\x52\x8b\x34\x8c\x03\x98\x41\x79\xb8\xe5\x05\x67\x4c\xb7\x9d\x88\xcc\x0d\x8d\x3e\x8d\x73\x92\xf9\xaa\x77\x3b\x29\xc2\x9e\x54\xa9\xe3\x26\x40\x60\x75\xd7\x55\xc2\x91\xfc\xed\xbc\xc5\x77\x93\x4c\x82\x4a\xf9\x88\x25\x0f\x64\xed\x56\x85\xfc\xe7\x26\xcf\xf6\x5e\x92\xd7\x08\xae\x11\xcb\xfa\xa9\x58\xab\x8d\x8b\x15\x34\x0a\x29\xa1\x37\xb5\xb4\x35\x7f\x7e\xd1\xc7\xa5\x19\x0c\xbf\x98\xa4", b"\x83\x36\x6b\xa3\xfe\xd9\x3d\xfb\x38\xd5\x41\x20\x3e\xcb\xf8\x1c\x36\x39\x98\xe2", b"\x1f\xe2\x99\xc3\x6a\x13\x32\xf2\x3b\xf2\xe1\x0a\x6c\x6a\x4e\x0d\x3c\xdd\x2b\xf4", )?; test( HashAlgorithm::SHA1, b"\x21\x9e\x8d\xf5\xbf\x88\x15\x90\x43\x0e\xce\x60\x82\x50\xf7\x67\x0d\xc5\x65\x37\x24\x93\x02\x42\x9e\x28\xec\xfe\xb9\xce\xaa\xa5\x49\x10\xa6\x94\x90\xf7\x65\xf3\xdf\x82\xe8\xb0\x1c\xd7\xd7\x6e\x56\x1d\x0f\x6c\xe2\x26\xef\x3c\xf7\x52\xca\xda\x6f\xeb\xdc\x5b\xf0\x0d\x67\x94\x7f\x92\xd4\x20\x51\x6b\x9e\x37\xc9\x6c\x8f\x1f\x2d\xa0\xb0\x75\x09\x7c\x3b\xda\x75\x8a\x8d\x91\xbd\x2e\xbe\x9c\x75\xcf\x14\x7f\x25\x4c\x25\x69\x63\xb3\x3b\x67\xd0\x2b\x6a\xa0\x9e\x7d\x74\x65\xd0\x38\xe5\x01\x95\xec\xe4\x18\x9b\x41\xe7\x68", p, q, g, b"\x3a\x41\xb0\x67\x8f\xf3\xc4\xdd\xe2\x0f\xa3\x97\x72\xba\xc3\x1a\x2f\x18\xba\xe4\xbe\xde\xc9\xe1\x2e\xe8\xe0\x2e\x30\xe5\x56\xb1\xa1\x36\x01\x3b\xef\x96\xb0\xd3\x0b\x56\x82\x33\xdc\xec\xc7\x1e\x48\x5e\xd7\x5c\x92\x2a\xfb\x4d\x06\x54\xe7\x09\xbe\xe8\x49\x93\x79\x21\x30\x22\x0e\x30\x05\xfd\xb0\x6e\xbd\xfc\x0e\x2d\xf1\x63\xb5\xec\x42\x4e\x83\x64\x65\xac\xd6\xd9\x2e\x24\x3c\x86\xf2\xb9\x4b\x26\xb8\xd7\x3b\xd9\xcf\x72\x2c\x75\x7e\x0b\x80\xb0\xaf\x16\xf1\x85\xde\x70\xe8\xca\x85\x0b\x14\x02\xd1\x26\xea\x60\xf3\x09", b"\x57\x97\x61\x03\x9a\xe0\xdd\xb8\x11\x06\xbf\x49\x68\xe3\x20\x08\x3b\xbc\xb9\x47", b"\x50\x3e\xa1\x5d\xba\xc9\xde\xde\xba\x91\x7f\xa8\xe9\xf3\x86\xb9\x3a\xa3\x03\x53", )?; test( HashAlgorithm::SHA1, b"\x2d\xa7\x9d\x06\x78\x85\xeb\x3c\xcf\x5e\x29\x3a\xe3\xb1\xd8\x22\x53\x22\x20\x3a\xbb\x5a\xdf\xde\x3b\x0f\x53\xbb\xe2\x4c\x4f\xe0\x01\x54\x1e\x11\x83\xd8\x70\xa9\x97\xf1\xf9\x46\x01\x00\xb5\xd7\x11\x92\x31\x80\x15\x43\x45\x28\x7a\x02\x14\xcf\x1c\xac\x37\xb7\xa4\x7d\xfb\xb2\xa0\xe8\xce\x49\x16\xf9\x4e\xbd\x6f\xa5\x4e\x31\x5b\x7a\x8e\xb5\xb6\x3c\xd9\x54\xc5\xba\x05\xc1\xbf\x7e\x33\xa4\xe8\xa1\x51\xf3\x2d\x28\x77\xb0\x17\x29\xc1\xad\x0e\x7c\x01\xbb\x8a\xe7\x23\xc9\x95\x18\x38\x03\xe4\x56\x36\x52\x0e\xa3\x8c\xa1", p, q, g, b"\x56\xf7\x27\x22\x10\xf3\x16\xc5\x1a\xf8\xbf\xc4\x5a\x42\x1f\xd4\xe9\xb1\x04\x38\x53\x27\x1b\x7e\x79\xf4\x09\x36\xf0\xad\xcf\x26\x2a\x86\x09\x7a\xa8\x6e\x19\xe6\xcb\x53\x07\x68\x5d\x86\x3d\xba\x76\x13\x42\xdb\x6c\x97\x3b\x38\x49\xb1\xe0\x60\xac\xa9\x26\xf4\x1f\xe0\x73\x23\x60\x10\x62\x51\x5a\xe8\x5f\x31\x72\xb8\xf3\x48\x99\xc6\x21\xd5\x9f\xa2\x1f\x73\xd5\xae\x97\xa3\xde\xb5\xe8\x40\xb2\x5a\x18\xfd\x58\x08\x62\xfd\x7b\x1c\xf4\x16\xc7\xae\x9f\xc5\x84\x2a\x01\x97\xfd\xb0\xc5\x17\x3f\xf4\xa4\xf1\x02\xa8\xcf\x89", b"\x5d\xd9\x0d\x69\xad\xd6\x7a\x5f\xae\x13\x8e\xec\x1a\xaf\xf0\x22\x9a\xa4\xaf\xc4", b"\x47\xf3\x9c\x4d\xb2\x38\x7f\x10\x76\x2f\x45\xb8\x0d\xfd\x02\x79\x06\xd7\xef\x04", )?; test( HashAlgorithm::SHA1, b"\xba\x30\xd8\x5b\xe3\x57\xe7\xfb\x29\xf8\xa0\x7e\x1f\x12\x7b\xaa\xa2\x4b\x2e\xe0\x27\xf6\x4c\xb5\xef\xee\xc6\xaa\xea\xbc\xc7\x34\x5c\x5d\x55\x6e\xbf\x4b\xdc\x7a\x61\xc7\x7c\x7b\x7e\xa4\x3c\x73\xba\xbc\x18\xf7\xb4\x80\x77\x22\xda\x23\x9e\x45\xdd\xf2\x49\x84\x9c\xbb\xfe\x35\x07\x11\x2e\xbf\x87\xd7\xef\x56\x0c\x2e\x7d\x39\x1e\xd8\x42\x4f\x87\x10\xce\xa4\x16\x85\x14\x3e\x30\x06\xf8\x1b\x68\xfb\xb4\xd5\xf9\x64\x4c\x7c\xd1\x0f\x70\x92\xef\x24\x39\xb8\xd1\x8c\x0d\xf6\x55\xe0\x02\x89\x37\x2a\x41\x66\x38\x5d\x64\x0c", p, q, g, b"\x09\x42\xa5\xb7\xa7\x2a\xb1\x16\xea\xd2\x93\x08\xcf\x65\x8d\xfe\x3d\x55\xd5\xd6\x1a\xfe\xd9\xe3\x83\x6e\x64\x23\x7f\x9d\x68\x84\xfd\xd8\x27\xd2\xd5\x89\x0c\x9a\x41\xae\x88\xe7\xa6\x9f\xc9\xf3\x45\xad\xe9\xc4\x80\xc6\xf0\x8c\xff\x06\x7c\x18\x32\x14\xc2\x27\x23\x6c\xed\xb6\xdd\x12\x83\xca\x2a\x60\x25\x74\xe8\x32\x75\x10\x22\x1d\x4c\x27\xb1\x62\x14\x3b\x70\x02\xd8\xc7\x26\x91\x68\x26\x26\x59\x37\xb8\x7b\xe9\xd5\xec\x6d\x7b\xd2\x8f\xb0\x15\xf8\x4e\x0a\xb7\x30\xda\x7a\x4e\xaf\x4e\xf3\x17\x4b\xf0\xa2\x2a\x63\x92", b"\x44\x84\x34\xb2\x14\xee\xe3\x8b\xde\x08\x0f\x8e\xc4\x33\xe8\xd1\x9b\x3d\xdf\x0d", b"\x0c\x02\xe8\x81\xb7\x77\x92\x3f\xe0\xea\x67\x4f\x26\x21\x29\x8e\x00\x19\x9d\x5f", )?; test( HashAlgorithm::SHA1, b"\x83\x49\x9e\xfb\x06\xbb\x7f\xf0\x2f\xfb\x46\xc2\x78\xa5\xe9\x26\x30\xac\x5b\xc3\xf9\xe5\x3d\xd2\xe7\x8f\xf1\x5e\x36\x8c\x7e\x31\xaa\xd7\x7c\xf7\x71\xf3\x5f\xa0\x2d\x0b\x5f\x13\x52\x08\xa4\xaf\xdd\x86\x7b\xb2\xec\x26\xea\x2e\x7d\xd6\x4c\xde\xf2\x37\x50\x8a\x38\xb2\x7f\x39\xd8\xb2\x2d\x45\xca\xc5\xa6\x8a\x90\xb6\xea\x76\x05\x86\x45\xf6\x35\x6a\x93\x44\xd3\x6f\x00\xec\x66\x52\xea\xa4\xe9\xba\xe7\xb6\x94\xf9\xf1\xfc\x8c\x6c\x5e\x86\xfa\xdc\x7b\x27\xa2\x19\xb5\xc1\xb2\xae\x80\xa7\x25\xe5\xf6\x11\x65\xfe\x2e\xdc", p, q, g, b"\xa0\x15\x42\xc3\xda\x41\x0d\xd5\x79\x30\xca\x72\x4f\x0f\x50\x7c\x4d\xf4\x3d\x55\x3c\x7f\x69\x45\x99\x39\x68\x59\x41\xce\xb9\x5c\x7d\xcc\x3f\x17\x5a\x40\x3b\x35\x96\x21\xc0\xd4\x32\x8e\x98\xf1\x5f\x33\x0a\x63\x86\x5b\xaf\x3e\x7e\xb1\x60\x4a\x07\x15\xe1\x6e\xed\x64\xfd\x14\xb3\x5d\x3a\x53\x42\x59\xa6\xa7\xdd\xf8\x88\xc4\xdb\xb5\xf5\x1b\xbc\x6e\xd3\x39\xe5\xbb\x2a\x23\x9d\x5c\xfe\x21\x00\xac\x8e\x2f\x9c\x16\xe5\x36\xf2\x51\x19\xab\x43\x58\x43\xaf\x27\xdc\x33\x41\x4a\x9e\x46\x02\xf9\x6d\x7c\x94\xd6\x02\x1c\xec", b"\x8c\x2f\xab\x48\x9c\x34\x67\x21\x40\x41\x5d\x41\xa6\x5c\xef\x1e\x70\x19\x2e\x23", b"\x3d\xf8\x6a\x9e\x2e\xfe\x94\x4a\x1c\x7e\xa9\xc3\x0c\xac\x33\x1d\x00\x59\x9a\x0e", )?; test( HashAlgorithm::SHA1, b"\xf2\x3e\xe7\x9e\xb4\xfc\xe5\xcb\xf3\xb0\x8d\x65\xa1\x80\x3d\x2e\x3e\x19\x1d\x35\x80\xa4\x4d\x17\x7d\x8f\xf0\x69\xf9\x07\x84\xd0\x12\xca\x57\x46\xe6\xdd\x66\x38\xdf\xe8\x41\x3f\x1d\xb3\xd8\xfe\x28\x2c\x21\x60\xf5\xdd\x96\x60\x7d\xd6\x3d\x61\x0f\x79\x1d\xfc\x10\xab\xad\x18\x72\x15\x87\x10\x1c\xec\x8a\x2a\x12\x91\x3c\xfb\xad\xa3\xa5\xb7\x59\x39\x58\xb9\xbf\xa6\xe9\xaf\x3a\xf5\xd7\x1f\xf1\x7e\xc7\x2a\xaa\xee\xca\xaf\xfc\x5d\x17\x4e\x62\x9a\x09\x02\x97\xe9\x4c\xdf\xe9\x88\xd9\xbf\x6c\x80\x82\x7c\x23\xdf\x51\x37", p, q, g, b"\x22\x9a\x26\xdc\xaf\xf2\x9e\xd1\xa7\x26\x4e\xd0\xf7\x7d\x67\x62\x39\xb9\xba\x1e\xf4\x77\x8e\x7d\xd6\x40\xe8\xaa\x6f\xab\xdc\x1f\x1b\xd3\xf5\x82\xe2\x11\xbd\x01\xc2\x6b\x3d\x9d\x3b\xff\xe7\x19\x9f\x9e\xd4\x5d\x76\x4c\xd9\xd0\xe8\x44\xb3\x85\xcb\x34\xe6\xde\x22\x37\x0e\xbc\x6b\xa4\x1d\xb4\x09\xd6\x3f\x50\xc1\xac\x09\xbe\xd0\x0c\xdc\x2b\x7c\x55\x22\x3c\x59\x6b\x7e\x13\x3b\xa2\x5b\xa9\xe7\x8f\x33\x50\x2f\x8d\xd5\x2f\x32\xa6\x67\xa7\x68\x3e\x50\x40\x47\x81\x79\x63\x23\x8d\x96\x29\xa9\x18\xa0\xce\xeb\xaa\xd5\x18", b"\x8d\x38\x8e\xc7\xf2\x86\x3d\xd5\xb7\xc9\x9a\xc9\x35\x05\xd1\x58\x0b\xf2\xe0\xc7", b"\x76\xae\x93\x17\x69\x6d\x37\xf2\xd8\xbd\x61\xc4\x77\x33\xe9\x45\x5b\x61\xd3\x47", )?; test( HashAlgorithm::SHA1, b"\x68\x36\x25\x5e\x6e\x65\x9d\xe4\xff\xb5\x35\x89\x2d\x46\x6a\x3b\xea\x09\x69\x3e\x58\x7e\xb5\xbd\x50\xf4\x4f\x8a\x22\xf1\x16\x97\x05\x7d\x68\x66\x0b\xc6\x56\x24\x00\xd5\x87\xba\xac\x1c\x19\xd3\x30\xff\x79\x4a\x70\xdf\x53\x00\xa5\x21\x1c\x72\x54\x1a\x56\xd0\xff\x2a\xf0\x2a\x27\x8e\xd2\xdb\x1d\xf9\x4c\xcb\x20\x26\xd3\x13\x8b\x2d\x92\x42\x45\x02\x1e\xe8\x35\xd3\xc1\x7b\x0b\x3b\x76\x77\xde\xf8\x56\x11\x22\x7f\x6c\xe2\x91\x3e\x7c\xb4\x46\xa4\x79\xb9\x5a\xcf\xd0\x10\x5c\x25\xe4\x65\x6f\xbc\x56\xc2\xa1\x0a\x22\xb3", p, q, g, b"\xa7\xbb\xc3\x54\x23\x51\x0e\xdf\xeb\xf7\x9c\x4e\x2e\x56\x98\x6f\x28\x06\xd1\x11\x16\xbc\xae\x90\xa7\x16\xf0\x5d\xcb\xfc\x46\xdc\xbf\xeb\xe2\xec\x94\x6c\x40\xf9\xcc\x8c\x1a\x74\x39\xcd\xd0\x4e\x27\x01\x22\xec\x1c\x3b\xac\xa8\x38\x11\xa9\xf1\xbd\xae\xd9\xb1\x17\x21\x50\xaf\x1c\x8c\xe1\xc5\xd5\x02\xdf\xe5\xf4\xe8\x46\x7e\x50\x60\x50\x87\xa8\xf8\xc5\xf4\x5c\xa6\x72\xfd\x04\x9e\xec\x0a\x06\xf5\xe0\x1f\x78\x2d\x51\xf3\xba\x56\x40\x4b\xf1\x38\x80\x65\x55\x2f\xc8\x7a\xd2\x1a\xc0\xfa\x40\x27\xa1\x45\xd0\xb0\xd9\xe6", b"\xc0\xab\x43\xd3\x09\xa5\xe9\x4b\x6e\xf4\xdb\x99\x43\x30\x6e\x6d\x96\x6f\xc9\xb5", b"\x07\xec\x5a\xa1\x92\x8f\x19\xfc\x3a\x42\x0f\x29\xb9\x35\xba\xc4\x61\x24\xc0\xe2", )?; test( HashAlgorithm::SHA1, b"\x4b\x08\x45\xc9\x9d\xb3\x48\x29\x4f\x1d\x83\x16\x6b\x27\xf4\x48\xec\x29\xab\x79\x65\x46\x44\x77\xf4\x54\x44\xf4\x46\x72\xa4\x09\xdd\xca\xfa\xf3\x5e\x91\xfa\xf4\x01\xec\xa7\x49\x8e\x32\x68\xca\xa2\xd9\x6b\xf1\xaa\x84\x0c\x0e\x1e\xd4\x3a\x5a\xb6\x08\x88\xfc\xf0\x2b\x2f\x8a\x2c\x89\xda\xa5\x98\xad\xf0\xb7\xd2\xda\xce\x92\x10\xef\xd4\x1a\xb4\x96\xa1\xe7\x3a\x18\x2d\xa4\x30\xc1\xd0\x43\xe2\x49\xa1\x28\x9c\x91\x80\x9c\x8c\x72\x98\xcf\xdb\xb0\xae\x43\x8b\x00\x93\x6c\x28\x3a\x0e\xc2\xd7\x9c\xdc\x81\xc9\xce\x3c\x2d", p, q, g, b"\x54\x1a\x9c\x45\xe1\x65\xd3\xd9\x1e\x71\xbb\x13\x70\xd7\xc0\x32\xc3\x60\x32\x2a\xa1\x5e\x99\xd8\xc1\xc1\x6e\xa3\x5c\x8c\x19\x32\x24\xa0\x64\x67\xab\x77\xa6\x54\x78\xc4\x67\xb3\xf2\x0c\xb0\xc5\xfd\xb8\xc8\x4c\xef\xa6\x95\x66\xa5\x94\xa2\xaa\x54\xc3\xa9\x48\xeb\xc1\xea\x7e\x6c\x3d\x28\xd3\x80\xcb\xd0\x17\x40\x63\x4c\x96\xb7\x6d\x6a\x03\xcc\x6e\xba\x0a\xfa\x72\x26\xf2\x3f\xc1\x0a\x18\xb0\xb6\xf9\x72\x70\xdf\xa0\x38\x16\x09\x60\xb5\xb8\x39\xba\x66\xaf\x50\xfd\xa0\x72\x45\x81\x0e\x80\xd3\x8b\x66\x93\xe8\xa9\xce", b"\x44\x28\x60\x19\xc1\xd5\x31\x03\x98\x06\x16\x94\x0c\x02\x8b\xad\x32\x17\xf7\x8d", b"\x4b\x37\x2b\xf5\x27\xc5\x15\xf5\x80\x25\x69\x9a\x45\xf2\x02\x1e\xf1\x8e\x11\xb9", )?; test( HashAlgorithm::SHA1, b"\x45\x97\xc1\xca\x0b\x07\x64\xbe\x31\xfa\x73\xcc\xc5\x89\x11\x6c\xc8\xd0\xa3\x16\x05\xf2\x55\x0e\xb3\x7f\xa5\x69\xb2\x49\x6c\x4f\x34\x32\x1d\x61\xbb\x8e\x49\xf8\x58\xc8\x67\x1b\x74\x37\xfc\x15\xf2\x69\xdd\x2d\x41\x46\x47\x0b\x81\x7d\xfe\x30\x69\x22\x5d\xdd\x3c\xd4\xa6\xc9\x77\xfb\x6c\xfc\x0d\x43\x26\x4a\x7b\xf6\x65\x92\x83\xe1\x40\xe4\xc8\x9a\xb2\xe8\xa4\xd0\xed\xe6\x27\x49\x61\xd6\x55\xbd\x79\xc7\xe4\x78\x80\xa7\x41\xfb\x01\x80\xc3\x25\xb5\xb7\xd2\xf7\xb8\xa5\x7a\xed\x52\xd0\x20\x6a\x83\xbb\x69\xa9\xd7\xa4", p, q, g, b"\x53\x15\xad\xf9\x0e\x19\x69\x46\xbe\x6b\x04\xc5\x41\x4d\xa1\xfa\xfd\x98\xb0\xd1\x7c\x3a\x39\x00\x0a\x00\x09\x1b\x7b\x56\x57\x4b\x1e\xcd\x02\x6e\xab\xb2\x5b\xe9\xec\xd0\xad\x69\x1d\xf2\xb7\xbf\x7e\xec\xd6\xad\x95\xbb\x4d\x7d\x17\xac\x74\x70\x60\xee\x7e\x3e\xb5\xc6\xfb\x75\x57\xcf\x7e\x80\x03\xa6\x20\xe4\x3e\x58\x7d\x06\x12\x85\x44\x72\xc3\xad\x85\x18\x39\xf7\x44\x15\x94\x11\xa3\x38\x76\xae\xc3\x65\xeb\x04\x91\xde\xc8\x0b\xa1\x4c\xba\x2d\x11\xde\xc4\x2a\xf4\xa4\xbf\x9c\x99\x31\x2a\x2a\xe7\xe5\x46\x2a\x2a\xdf", b"\x90\xd5\x47\x71\x2b\xc0\xce\xbb\xd3\xeb\xd1\x8a\x63\xd9\xb9\x2a\x03\x95\x30\x50", b"\x34\xea\x61\x76\xb4\xc6\x30\x43\x29\x5f\x12\x9a\x48\x95\xe1\x4e\xe5\x81\x65\x63", )?; test( HashAlgorithm::SHA1, b"\x18\xc6\x2a\x40\xb5\x23\x47\xa4\x73\xf5\x7a\xa6\x68\xee\xbb\x44\x84\xbe\xb5\xf1\x0f\xdc\x51\x77\x9e\x67\x70\x10\x6c\x0d\x12\x2e\xb6\x35\x6a\xe5\x3a\x33\x79\xe2\x70\xed\xca\x39\x01\x5d\xa3\x00\x57\x70\xc7\xb2\xa5\xaf\xd1\x12\x17\x99\x31\x53\xff\x43\xa0\xb2\x6d\xb0\x1a\xa2\xa4\x93\xde\x06\x14\x92\xa0\xaa\x3f\x22\x9b\x5a\xbd\x1a\xff\x29\x39\x5e\x31\xb0\x63\x50\x4e\xb3\x56\x20\x21\x9b\xa2\x99\x97\xf9\x2a\x52\xe1\xb2\xe6\xff\x20\x74\x80\xfd\x13\xd5\x8f\xf0\x29\x0e\xec\x5a\xab\xf2\x3b\x84\x94\x3e\xea\x20\xa4\x3c", p, q, g, b"\x3b\x73\x82\x46\xf9\xe3\x8c\xeb\xf4\x54\x2c\xed\x3f\xc0\xc0\x09\x6a\xeb\x9e\x9a\x3a\xd9\x28\xf4\xdd\x47\x45\xd8\x75\xfe\x6e\x20\xfb\x65\x55\x6d\x06\x69\x64\x32\xec\xff\xd5\x5b\x33\x49\x40\xc6\xe2\x3c\x90\x3f\x0a\xa4\xa1\x33\x5f\x73\x94\xc5\x50\x70\x58\x6b\xaa\xc8\x6c\x38\xcc\x19\x8e\xba\xf1\x54\x01\x25\x95\x28\xc5\x51\x92\xe9\x29\x8d\x2a\x0c\x89\x14\xda\xf2\xad\x00\x25\x9f\xe7\x25\x55\xc3\xc0\x44\x2e\x38\xc1\xe6\xe5\x02\x09\x28\xc6\xe6\x57\x1a\x0a\x98\xf6\xf4\x85\xe4\x37\x91\xae\x8a\xaa\xb1\x80\x46\x1f\xa4", b"\x29\xb7\xc0\xf9\x0d\x62\x4f\x8d\x58\x7e\xfd\x3f\x49\xf9\x7d\xa7\x0f\x6e\x63\xe7", b"\x22\x2a\x3d\x9f\xfc\xa0\xdc\xf5\x79\x37\xe8\x9c\x92\x53\x8e\x32\xe7\xa8\x68\x0f", )?; // [mod = L=1024, N=160, SHA-224] let p = b"\x8b\x9b\x32\xf5\xba\x38\xfa\xad\x5e\x0d\x50\x6e\xb5\x55\x54\x0d\x0d\x79\x63\x19\x55\x58\xca\x30\x8b\x74\x66\x22\x8d\x92\xa1\x7b\x3b\x14\xb8\xe0\xab\x77\xa9\xf3\xb2\x95\x9a\x09\x84\x8a\xa6\x9f\x8d\xf9\x2c\xd9\xe9\xed\xef\x0a\xdf\x79\x2c\xe7\x7b\xfc\xec\xca\xdd\x93\x52\x70\x0c\xa5\xfa\xec\xf1\x81\xfa\x0c\x32\x6d\xb1\xd6\xe5\xd3\x52\x45\x80\x11\xe5\x1b\xd3\x24\x8f\x4e\x3b\xd7\xc8\x20\xd7\xe0\xa8\x19\x32\xac\xa1\xeb\xa3\x90\x17\x5e\x53\xea\xda\x19\x72\x23\x67\x4e\x39\x00\x26\x3e\x90\xf7\x2d\x94\xe7\x44\x7b\xff"; let q = b"\xbc\x55\x0e\x96\x56\x47\xfb\x3a\x20\xf2\x45\xec\x84\x75\x62\x4a\xbb\xb2\x6e\xdd"; let g = b"\x11\x33\x3a\x93\x1f\xba\x50\x34\x87\x77\x73\x76\x85\x9f\xdc\x12\xf7\xc6\x87\xb0\x94\x8a\xe8\x89\xd2\x87\xf1\xb7\xa7\x12\xad\x22\x0a\xe4\xf1\xce\x37\x9d\x0d\xbb\x5c\x9a\xbf\x41\x96\x21\xf0\x05\xfc\x12\x3c\x32\x7e\x50\x55\xd1\x85\x06\x34\xc3\x6d\x39\x7e\x68\x9e\x11\x1d\x59\x8c\x1c\x36\x36\xb9\x40\xc8\x4f\x42\xf4\x36\x84\x6e\x8e\x7f\xca\xd9\x01\x2c\xed\xa3\x98\x72\x0f\x32\xff\xfd\x1a\x45\xab\x61\x36\xce\x41\x70\x69\x20\x7a\xc1\x40\x67\x5b\x8f\x86\xdd\x06\x39\x15\xae\x6f\x62\xb0\xce\xc7\x29\xfb\xd5\x09\xac\x17"; test( HashAlgorithm::SHA224, b"\xfb\x21\x28\x05\x25\x09\x48\x8c\xad\x07\x45\xed\x3e\x63\x12\x85\x0d\xd9\x6d\xda\xf7\x91\xf1\xe6\x24\xe2\x2a\x6b\x9b\xea\xa6\x53\x19\xc3\x25\xc7\x8e\xf5\x9c\xac\xba\x0c\xcf\xa7\x22\x25\x9f\x24\xf9\x2c\x17\xb7\x7a\x8f\x6d\x8e\x97\xc9\x3d\x88\x0d\x2d\x8d\xbb\xbe\xdc\xf6\xac\xef\xa0\x6b\x0e\x47\x6c\xa2\x01\x3d\x03\x94\xbd\x90\xd5\x6c\x10\x62\x6e\xf4\x3c\xea\x79\xd1\xef\x0b\xc7\xac\x45\x2b\xf9\xb9\xac\xae\xf7\x03\x25\xe0\x55\xac\x00\x6d\x34\x02\x4b\x32\x20\x4a\xbe\xa4\xbe\x5f\xaa\xe0\xa6\xd4\x6d\x36\x5e\xd0\xd9", p, q, g, b"\x7e\x33\x9f\x37\x57\x45\x03\x90\x16\x0e\x02\x29\x15\x59\xf3\x0b\xed\x0b\x2d\x75\x8c\x5c\xcc\x2d\x8d\x45\x62\x32\xbb\x43\x5a\xe4\x9d\xe7\xe7\x95\x7e\x3a\xad\x9b\xfd\xcf\x6f\xd5\xd9\xb6\xee\x3b\x52\x1b\xc2\x22\x9a\x84\x21\xdc\x2a\xa5\x9b\x99\x52\x34\x5a\x8f\xc1\xde\x49\xb3\x48\x00\x3a\x9b\x18\xda\x64\x2d\x7f\x6f\x56\xe3\xbc\x66\x51\x31\xae\x97\x62\x08\x8a\x93\x78\x6f\x7b\x4b\x72\xa4\xbc\xc3\x08\xc6\x7e\x25\x32\xa3\xa5\xbf\x09\x65\x20\x55\xcc\x26\xbf\x3b\x18\x83\x35\x98\xcf\xfd\x70\x11\xf2\x28\x5f\x79\x45\x57", b"\xaf\xee\x71\x9e\x7f\x84\x8b\x54\x34\x9c\xcc\x3b\x4f\xb2\x60\x65\x83\x3a\x4d\x8e", b"\x73\x4e\xfe\x99\x22\x56\xf3\x13\x25\xe7\x49\xbc\x32\xa2\x4a\x1f\x95\x7b\x3a\x1b", )?; test( HashAlgorithm::SHA224, b"\x02\x97\x1e\x0c\xdd\x48\xae\x23\x31\xdb\x9c\x62\x85\xe9\x88\x0e\x96\x10\x4f\xa7\xa9\xf3\x78\xdf\xea\x71\x8e\x63\xef\xe9\x83\x52\xfe\x4d\x35\xa2\xbc\x94\xb3\xa8\x88\xcf\xb8\x8b\x8b\x7d\x9f\x6c\x8c\x54\xe4\x86\x13\xf3\x2c\x99\x46\xff\xe6\xe9\xa4\xb7\x10\x8e\xce\xcd\xda\x41\xbc\x15\x1b\x3d\x87\x24\xb6\x1f\x5b\x83\xa4\xe2\x74\x76\x91\x43\x87\xb0\x48\x8e\x41\xbe\x54\xf6\x3a\xa7\x73\x17\x5e\xb3\x73\xa3\x64\x1e\x6e\x79\x50\xee\xe8\xfa\xf0\x48\xa8\x41\xf1\x07\xd3\x0c\xf9\xbe\x26\x84\x93\x23\x15\x45\xd8\x98\x46\x94", p, q, g, b"\x63\x3b\xb7\x57\xb3\xc0\xe3\xb7\x86\x7b\xf8\x45\x30\x1e\xa4\xe3\x9f\x75\xc9\x75\x9c\x22\x3f\x46\xce\x26\x6d\x40\x6b\x9d\xf5\xdb\x50\x1f\xb8\x26\xb6\xe6\x1c\xba\x61\x04\xc6\x04\x45\x8c\x90\x79\x9f\x2a\x36\xab\x51\x16\x6d\x0e\x83\xb7\x70\x84\x06\x24\xfe\xdc\x35\xeb\xfb\x98\x53\x41\x9e\x7e\x09\xb3\x2b\x4b\xd6\x52\xda\x4f\x1c\xe9\x73\xac\x26\x20\xc9\x66\xb6\x1e\x35\xa3\xf2\x16\x43\x9a\x8d\xe1\xa1\x04\xf1\x72\xe1\xb6\xe2\x87\x81\x12\xa6\x6c\x34\xd1\x6a\x9a\xef\x3a\xc2\x4a\x34\xaf\x5e\xdb\xf3\x98\x18\xa3\xe2\xef", b"\x92\xc6\x5e\x07\x46\x2d\x66\x8b\x06\xdd\x45\xb6\x08\x78\x49\x65\x89\x78\x38\xbc", b"\x2e\x40\xad\xf4\x1c\xaf\xb8\x04\x8c\x79\x3c\x70\x92\xa7\xe8\x23\x51\x5b\x6c\xfa", )?; test( HashAlgorithm::SHA224, b"\x06\x2e\x82\xfb\x43\x23\x6e\xe1\x7e\xbf\xaa\x3d\x36\x3b\x9b\x87\x3d\x0f\xe4\x14\x44\xc7\x4c\xef\x7f\x7e\x3b\xd8\x1f\x72\x3f\xd9\x0f\xd1\x48\xa2\x8e\x99\x75\x85\x41\x36\x95\x11\x37\x57\x75\x8a\xa4\xdd\x27\x5f\x70\xb3\x75\xf8\x90\x3c\x7b\xe4\x6e\x3a\x3a\xd3\x19\x0c\xd0\x49\x71\xab\xd2\xf1\xdb\x19\x2e\xf0\xd2\xb9\x8b\xbb\x80\x18\x1a\x72\x1a\x58\x09\x92\x8b\x5b\xca\x5c\x11\x8a\x29\x11\x13\x2a\xd2\x33\xcd\x27\xc7\xe4\x1a\xdf\xcc\xfe\xb4\xe9\x52\x87\x4b\xfa\x81\x96\x61\x18\x29\x75\xe4\x4d\x37\xc6\x17\x34\x75\x9c", p, q, g, b"\x3b\x0a\x09\x1d\xfc\xa0\x5d\xce\x61\xe9\xf0\x5b\x15\xb0\x74\x87\xd2\xe3\xea\x4f\x56\x8d\xc9\xac\x75\x2d\x42\xc0\xaa\x77\x1a\xe0\xcc\xc3\x72\xce\x97\x74\xfb\x9f\xd1\x6a\x30\xcb\x37\x59\xbb\x19\x89\x48\x8c\xe8\x5d\xb7\xcd\xfa\x50\x64\x76\xac\xec\x64\x4c\x21\x16\x8f\x2d\xb1\xf3\x6e\xfe\x02\x30\xc6\xfb\x8f\x1f\x2a\xe4\xea\xf1\x79\x9d\x5e\x29\xe2\x12\x46\x7b\x11\xbf\xbc\x1e\xeb\xed\x14\x2d\x7a\x01\x72\x62\xcd\x87\x35\xe3\xe2\x9d\x8e\x0c\x4a\x6e\x76\x6c\x07\xd7\xaa\x9f\x8d\x17\x6f\x53\x60\x87\xbf\xec\xf4\xc4\x14", b"\xba\x55\x41\x24\x87\x4d\x06\xa6\xce\xf6\x27\x40\xe1\x58\x21\xcc\xdd\xbf\xe6\xf3", b"\x59\x62\xbe\x75\x7d\x75\xb0\xf1\x7d\x15\x48\x2e\xbb\x59\x5c\xa4\xe9\xfb\xfe\x22", )?; test( HashAlgorithm::SHA224, b"\x4f\xca\x07\x48\x44\xea\xe2\x47\xd1\x9c\x06\xe9\x20\x32\xae\x8e\x77\x30\x43\xe2\xe1\xf4\x5d\x40\x0e\x9d\xce\xbb\xde\x5d\x65\xe7\xc1\x42\x3b\x03\x90\x16\x19\x91\xc0\x26\xf3\x8a\x0e\x2b\xfe\xef\x40\xda\xe1\x87\x41\x73\x7b\x1d\x53\x5a\xb4\x6b\x56\x6a\x1b\x67\x2f\xc2\x2d\xec\x86\x74\x7a\x7c\x76\x38\xfa\x65\x04\x7f\x1e\xde\x36\xad\x43\xf6\xae\xdf\x51\xb5\xbf\x29\x79\xad\xf4\xd9\xa9\x4e\xd8\x02\xa2\x9d\xe5\x60\x3b\x70\x47\x70\xb3\x2c\x8b\x94\x6a\x32\xe1\xb6\x05\x4c\xd7\x0c\x3a\xdd\x02\x5c\xc9\x37\x1d\x1e\x40\x4d", p, q, g, b"\x40\xb6\x38\xc9\x4b\x1e\x71\x9a\x33\x7d\x83\x35\x86\x99\xc7\x0c\xd8\x67\xff\x88\x8c\x65\x5a\x5f\x5a\x1d\xe8\x73\x2d\x05\x8b\xf0\x27\xd4\x74\x7e\xfe\x3b\x8d\xed\xca\x32\x76\xde\x5a\x58\xf1\x36\xed\x35\xcf\xf0\x30\x30\xf6\x72\xda\x65\xc7\x1f\x18\xe5\x82\x78\xdd\xfc\x7b\x9b\x50\xa2\x48\xef\xf9\x23\x68\x74\xee\x3c\xb0\xd0\xa3\x5b\x7b\x2e\xe1\x85\xb1\x39\xea\x84\xee\xd7\xbf\xfc\x50\x94\xab\x87\x43\xa7\x53\x74\xbc\x36\xc7\xd6\x9d\x5f\x3e\x6f\xe5\xf3\xef\x1f\x93\x58\xf0\x0a\x3c\x58\x92\xff\xf4\x1e\xd6\xaf\xee\x3b", b"\x65\x1a\x38\x9d\x8c\xa5\x0d\x6e\x32\x73\xca\xbb\xe7\x1c\xd8\x4c\xcc\xd0\x23\x61", b"\x34\x01\xfe\x47\xb3\x81\x2d\xaa\x8c\x02\x0c\x9b\xd4\x26\x09\xcb\xeb\xdf\xa7\x28", )?; test( HashAlgorithm::SHA224, b"\x4d\x96\x30\xfe\x05\x89\x98\xca\x5b\x80\xae\x62\xf3\xf7\x3d\xc8\x5b\xee\x29\x15\x09\x84\x3a\xc0\x02\x40\xd1\x3d\x55\x25\x1a\xe5\x3b\x37\x79\x47\x83\xb9\x7d\x53\xe0\x42\xca\xb2\x6f\x8c\x84\xde\x0a\x70\xf5\xb4\x30\x51\xfb\xef\xb3\xe4\x3f\x08\xf5\xd2\xe8\xaa\xd9\xe2\xde\x27\x17\x41\x2d\xbb\x90\x2a\xcc\x88\x49\xad\xc0\x4d\x06\xfe\xd8\xc1\x42\x1c\x4c\xfe\x8b\x81\xee\x7f\x5a\xc5\xd4\xf0\xc0\xb6\x8e\x80\xb6\xf8\x8f\xd3\xc7\xd5\xb3\x20\x22\x57\x2b\x0a\x68\x1b\xd2\xd4\xdf\x2d\x04\x7b\x0b\x23\xb6\x88\x71\x45\xaf\xe1", p, q, g, b"\x72\x7b\x65\x28\x35\x7d\x67\x05\xc2\x0d\x31\x35\x8f\x64\x19\x34\xfd\xec\x63\xcc\x66\xdf\x98\x83\x7d\x2f\x37\x81\x64\xe1\x5f\xa0\x84\x22\x07\xac\xf3\x22\x0c\x80\x23\xa9\xf4\xf8\xd2\x05\x71\x65\xb3\xc8\x49\xea\xeb\x53\x76\xe3\xfa\xd1\x17\x85\xf1\xd0\x26\x17\x79\xaa\xed\xd5\x3b\x1e\x52\x79\x80\x07\xeb\x4c\x8e\x83\xb1\xff\x32\x1b\x62\x0d\x88\x33\x91\xa1\x4f\xa4\x7f\xec\x49\x01\xd9\x6e\xc2\x32\xea\xbb\x4a\x0b\xb4\x45\x33\x68\xfe\xf5\x17\x6c\x67\x13\x56\x49\x97\x9d\x32\x14\xd3\xfb\x67\xa1\x31\x9a\xc5\x4f\xeb\x01", b"\x9c\xa3\xe4\x33\x50\x4c\x55\x7b\xa1\xaa\xc6\x64\x69\x78\x11\x75\xcd\xfb\x4a\xd5", b"\x72\x14\x5d\xfa\x52\x79\xdd\x82\xae\x99\x60\x4d\x16\xa2\xb8\xdf\x71\xb9\x53\x20", )?; test( HashAlgorithm::SHA224, b"\x62\xb9\xd6\x01\xe3\x0b\x42\xa2\x79\xc7\xe0\x4d\xf3\xca\x8d\x81\x40\xa5\x5c\xd5\x87\x6c\x7e\x91\x81\xc7\x35\x75\xe4\xc4\xf9\x21\xa9\x4e\x4e\x2d\x0b\xdd\x7b\xa9\x86\x00\xd6\x52\xe5\xdf\x5b\xe9\x46\x4e\x7a\x90\x11\xab\x48\x69\x60\xf6\x9d\x57\xec\xe1\xd2\xc4\xaf\x93\x24\x45\x7c\x1e\x3d\x83\xfb\xa4\x26\x5b\xeb\x47\x40\x7e\x47\x61\xdb\xc9\x49\xd5\xbd\x67\xfe\xe4\xa4\x76\xa4\xd5\xa9\x3d\x77\xac\xda\x96\xa2\x21\xa0\xa3\x1e\x0f\x02\x4b\x3f\x0b\x82\x34\xc0\x15\x23\x8f\x32\x58\xda\xa0\x85\xae\x9f\x4e\x1a\xa7\xb1\xcc", p, q, g, b"\x5f\x6d\xfb\x06\x4c\xad\xdf\x64\x4a\xf3\x99\xe3\x3a\x67\x25\x65\x76\x67\x61\xd5\x5a\xc0\xb8\x4b\xea\xd4\x2c\x39\x80\xe7\xe3\x96\x04\x37\x44\x36\x17\x78\xf0\x4d\xcb\x69\x8e\x45\x63\x85\x34\x20\xfe\xca\xcd\x59\x4a\xf8\x28\xf5\x7d\xf5\x41\xd9\xe4\xde\x89\x9d\x61\xf0\x4f\x63\x79\xc1\xc9\x62\x46\xd1\x52\x36\x93\x95\x24\x2a\x1c\x2e\x70\xee\xf8\xf3\x54\x17\xa0\xff\xdb\x03\x92\x82\x51\x6c\xe2\x1b\x85\x68\x79\x04\xc5\x11\x08\x7f\x11\x3e\x51\x42\xf0\x27\xf1\x17\x97\x12\xed\xcb\xce\x27\x93\x9a\xb1\x5e\xc4\x9c\x08\x5f", b"\x33\x19\x20\xa7\xb7\x9e\x3c\xfa\x76\x38\xe4\x09\xd9\x70\x2a\xaf\xd0\x8f\xbe\xc6", b"\x07\x1d\x06\xe6\xcd\x30\x15\x15\xf3\x7b\x60\x69\x0a\xfa\x21\x9f\xe5\x08\x3d\x96", )?; test( HashAlgorithm::SHA224, b"\x00\x06\xe0\x9c\x20\x37\x64\x42\xe6\x89\xbf\x2d\x34\x26\x8f\xd6\x91\x09\xc1\x30\x1e\xa6\x6c\xbe\x90\x39\x4c\xc0\xf4\x1f\x94\x82\x2c\x28\x84\x58\x19\xb9\xa9\x87\x64\xd2\xf7\x26\x2e\x98\x89\x14\x87\xff\x55\xb0\x5b\xd6\x9e\x18\xb7\xca\xd4\x1b\xd9\x8e\x13\x75\x66\xb6\x04\x1c\x73\x9d\xb1\x1f\x78\xe5\x67\xca\xc0\x2f\x33\xf1\x40\xd1\x9a\x48\x05\x00\x25\x45\x37\x5d\xae\xbf\xd7\xdc\xbe\xa3\x32\x42\xe7\x3c\x8e\x26\x91\x49\xd7\xeb\x9d\xb9\xf9\x00\x6e\x17\xac\xb7\x36\xb5\xe9\x77\x64\x5a\xb6\x51\xb8\x12\x25\xc5\xe5\x43", p, q, g, b"\x1b\x1f\x72\x56\x64\xd7\x5b\xdc\xb2\xa5\xa4\xc6\x53\x06\x1c\x46\x07\x99\xdd\x48\xbf\x1e\x6b\x03\xe1\x3c\x71\xd8\x3e\x3f\xdb\x50\x6f\xa9\x4e\x6c\xaf\xb5\xdb\xde\xad\x88\xa3\x3d\x23\xd4\xe9\x28\x7b\x47\x07\xe1\xfb\xa8\x71\xb9\x7c\x9a\x48\xf9\x30\xcd\xcc\xba\x0d\xc0\x6a\x4f\x0a\x8b\xfb\xb4\xe1\x4d\x0b\x4d\x5a\x08\x71\xfa\x13\x41\xca\xec\x7b\xc0\x81\x38\x71\x31\x21\xd4\x19\x76\x9f\x31\x20\x35\x08\xdf\x71\x94\x72\x65\x64\x4f\xdc\x61\x37\xd8\xe4\x66\xc8\xcb\x0c\xe9\x85\x34\x0c\xb2\xe2\x79\xb4\xce\x93\x15\xa7\x72", b"\xb6\xaa\x83\x3b\x82\x51\x84\x72\x9a\xf3\x08\xf8\x1b\xf5\xe5\x8e\x2d\x7e\x92\x84", b"\x54\x53\xb4\xb2\xe3\xfc\x80\x2b\x2f\x97\x7d\x0c\xf6\xeb\x7f\x5c\x16\x67\x3f\xa3", )?; test( HashAlgorithm::SHA224, b"\xe0\x4a\x71\xf2\xb5\xc1\x76\xa0\xdb\x17\xa9\x83\xa1\x7d\xec\x58\x8c\x00\xf4\x2c\x9a\xa3\x02\x6b\x5e\xb4\x40\xf0\x7a\x21\x40\xc2\xed\x84\x02\x4e\x05\x31\xea\x77\x88\xdf\xea\xa9\x18\x83\xfb\x6a\x98\x41\xc1\x7d\xcf\xd3\x12\x96\x8a\xdb\x00\xe5\x56\xbc\x7e\xb3\x02\x1f\x57\xb7\xa1\x68\x94\xfa\x4f\xe1\x2e\xc9\x3d\xfd\x49\x4a\x0a\x1c\x69\x3d\x6a\xde\x15\x4e\xf6\x48\xc0\x55\x52\xda\x41\x22\x4d\x49\x22\xd1\x86\x1d\x9f\x76\x71\xb8\xce\x6c\xe4\x48\xe8\x95\xea\x0e\xed\x25\x80\x2e\x33\x50\xec\x08\xae\x79\xf2\xd6\x1e\x0f", p, q, g, b"\x68\x7e\x16\x30\x9b\x06\x81\x7b\x93\x23\x6d\xd9\x90\xfa\xef\x9e\x23\x2e\xb8\x1c\xb9\xc7\xd6\xda\xe4\xfd\xd4\xf8\xe7\xca\x93\x3e\x18\x5b\x1d\xa6\x22\xd7\xc7\xfa\x35\xe0\xa3\x90\x6f\x91\x5d\xed\x14\xba\x96\xd6\x03\x5b\x46\xbd\x6c\xa5\xfe\x17\x2a\xf9\x4e\x08\x1f\xb0\xb9\xa9\x58\x3a\x45\x8b\xd2\x06\xc1\xe8\x7f\x97\xa5\x7d\x00\xd6\xea\xde\x19\xec\x56\xac\x2e\x9b\xbd\x8c\x15\xdf\x35\x6e\xe7\xb1\x2c\x91\x31\x1a\x38\xfc\x33\x15\xcf\xde\x9f\xf4\x62\xca\x6a\xdf\xf2\x80\x8b\x3f\x8e\x80\x5e\xe9\x15\xae\x88\x5c\xa9\x57", b"\x14\x89\x2b\x1e\xc7\xfc\x71\x6c\x75\xa1\x7f\x7a\xd2\xe4\x1e\xc6\xfa\xa7\x88\x36", b"\x72\xcc\x56\xa9\x89\x0e\x8b\xdf\x1a\x53\xd3\xac\xc6\xf8\x91\x37\x26\x4f\x9f\xf8", )?; test( HashAlgorithm::SHA224, b"\x5e\x8e\xb9\x6b\x5c\x6a\xd7\x5d\x3d\xab\x1e\x28\xbb\x2c\xe7\x51\xec\xc3\x16\x11\xa0\x19\xe8\xd4\xb5\x61\xc7\xe4\x53\x3c\xc7\xab\x73\xbd\x9d\xe9\x31\xe8\xc5\x4c\x51\xc5\x71\x1e\x6c\x27\x6a\x8e\xd9\x2f\x4b\xb4\x57\xdd\xf2\x82\x33\xda\x2c\xa3\xe3\x01\x3c\x56\xe3\xcd\x2b\xc6\x1d\x4d\x4e\x0e\x22\xcf\x63\x61\x30\x4e\x56\xd6\x8b\x31\x5c\xa5\xd3\xfc\xc4\x72\xa7\xee\xf8\xcc\xa5\x75\x20\x4d\xd0\x84\xa2\x1a\x99\xba\x67\xfd\xdb\xf9\x0d\xf7\xc6\xc6\x58\x76\x17\x34\xbc\xe1\x3c\x3d\x22\xd8\x0b\x6f\xb9\xbe\xce\x55\x14\x92", p, q, g, b"\x50\xb0\xf7\x60\x59\x11\xbc\xe6\xed\x5e\xcf\xf1\xe3\xc1\x81\x6f\xbb\xf0\x3a\x14\x79\xa0\x82\x06\x03\xff\xa7\x15\xae\xf9\xff\xbc\xcb\xd0\x67\x57\x9c\xbb\xc8\xc8\x7c\x39\x2e\x85\xbb\xe9\x29\xa0\xb5\xe1\x05\x9f\xaa\xe6\xf9\x12\x1d\xf4\x9c\x66\xa0\x49\xa9\x8a\x90\xd8\x4c\x70\xa2\x13\x12\xbf\x83\x7f\x47\x23\x99\x3d\x0e\xc0\xac\x4c\x2a\x7f\xfb\x9d\x40\x09\x57\xb3\x9f\xb8\x3e\x95\x1e\xf4\x13\x62\x45\x2c\xf4\x58\xd7\x84\xc4\x3f\xe8\x22\xea\x7a\x7a\xbb\xea\x0a\x69\x98\x32\x1a\x93\x81\x9d\x2d\x28\x2c\x78\x84\xf5\xc2", b"\x73\x99\xb1\x20\xd4\xbf\xbd\x6d\xc4\x06\x4d\x2f\x3f\x8f\x0c\xa5\xc3\x62\xb2\xd8", b"\x23\x02\xd8\x1d\x7e\xbb\x24\x17\xee\xf4\x5d\x88\x94\x1b\x07\x0e\xca\xb1\x1c\xab", )?; test( HashAlgorithm::SHA224, b"\xda\x91\xc6\x92\xcd\xb0\xa5\x95\x62\xe2\xb6\x64\xdc\xfe\x75\x54\xac\x58\x9d\x57\xf8\x22\x46\xc4\xa8\xa3\xf9\x57\x3b\xf4\x7b\x25\x7e\xb8\xf9\x34\x47\xc1\xeb\xab\x13\xdc\xe5\x3d\x6f\x44\x16\xfb\x2c\x6c\x36\x30\x3e\xd9\x78\x85\xcf\x7a\x6c\xae\xf0\x55\xf7\xe3\x14\x5e\xf3\x83\x8c\x31\x87\x7f\xad\x7a\x88\x83\xff\xc8\x4e\xbd\x97\x3f\x8c\x06\xd1\x7c\xdd\x33\x9b\xb3\x37\x1f\x9d\x3d\x4f\x2d\x9f\x0b\x80\xae\x2b\xcc\x87\x8b\x4a\xf7\x8f\x84\x5e\xac\x4f\x2a\xac\xee\x6a\x94\x51\xda\xf8\x14\xa4\x4e\x92\x7b\xb5\x42\x88\x20", p, q, g, b"\x67\x8b\x39\x58\xed\x24\xfc\x84\x94\x20\x54\xf4\x9d\x9e\x6f\x27\xbb\xac\x7d\xe3\xa4\xa5\x2a\xf9\xff\xcb\x9c\xe6\xc1\xfb\x8b\xdd\x99\xdb\x0e\x80\xc8\x68\xac\x54\x7c\x4c\xfc\x78\x2d\xe7\xeb\xcf\x69\x43\xb2\xe4\x64\x33\xc6\x70\x17\x8d\xe0\x10\x4b\xd6\xfc\x25\xdc\x30\x54\xdb\x9c\x48\xc1\x27\x06\xe1\xde\xa3\x5e\x16\x3b\xe3\x6a\x4a\xb7\x21\x95\x0c\x02\x8b\x05\x46\xf1\x71\x9f\xf2\xed\xd8\x1b\x2b\x79\x74\xfb\x9b\x12\x12\x24\xcc\xfa\xab\xc4\x7e\x9e\x62\x9a\x97\xbc\x6b\xa4\x26\x91\xca\x3f\x64\x9c\xca\xc4\x7d\x0f\x1e", b"\x6f\x15\x79\xed\xcf\x43\x75\x84\xd3\xe9\x39\xfa\x5b\x00\x2e\xee\x83\xe3\xb6\x14", b"\x71\x20\x8a\x87\xa4\xcf\x2b\x3a\x9b\x65\x47\x77\x73\xb0\x09\x6d\x45\x2d\xae\x60", )?; test( HashAlgorithm::SHA224, b"\x0f\x2e\xdc\x87\xf4\xd2\x94\x2c\x46\x93\xb0\x64\xa5\x11\xb9\x3f\x79\x0c\x60\xdc\x14\x9a\x1b\x0b\x70\x41\xaf\x51\x83\xbc\x0f\x42\x23\x41\x34\xb2\x84\x27\x0e\x4c\x7e\x53\x61\x4f\x7e\xcf\xe7\x11\xde\x0e\xfb\x28\x33\x6d\x0b\xb3\x59\xc8\x6e\x8b\xe8\x83\x9f\x58\x32\x11\xe9\x17\x48\x32\xb3\xd4\x1e\xe6\xd2\x18\x64\xac\x61\x86\xfd\x1d\xb9\x20\xdd\xa6\x5b\x25\x96\x6c\x59\x51\xab\x8a\x20\x50\xdd\xa8\x7d\x1d\x72\xe3\x03\x28\x52\xad\x43\xda\x9f\xb4\x30\xe8\x50\x02\x2b\x4b\xb6\xcc\x9c\xb9\x0e\x42\x8f\x3a\x5c\xa3\x2a\x62", p, q, g, b"\x3a\x97\x8e\x90\x22\xa8\xf7\xa0\xca\xa9\x1f\x27\x5b\xf9\xcf\x76\x48\xe1\xb9\xa3\x1a\x07\x02\xd8\xac\xdb\xf5\x9a\xff\xb5\x46\x7f\xb0\x7a\x8f\x7e\x5b\x4c\x86\x77\x5a\xc4\xef\xb6\x09\xb9\x46\xf0\x5a\x3f\x13\x03\x4d\xb9\x4a\xcc\x64\x05\x7f\x90\x6d\x18\x54\x91\x0d\xe5\x38\xf8\x43\x67\x18\x1c\x61\x8e\x96\xc3\xf9\x22\x54\x7d\x40\x8e\xe6\x40\x8b\x7a\x70\xac\xed\xc7\x5d\xe8\xae\x44\x5c\x5d\x4d\xd5\xde\xf4\xa3\x52\xd2\x52\x82\x34\x07\x0c\xc7\x20\x70\x0c\x14\xce\x12\xd2\xf3\x69\x90\xd3\x6b\x29\xd7\xb0\x05\x96\xe3\x4b", b"\xb6\xea\x9c\xdb\x21\x1c\x45\x60\xb3\xd5\x92\xe9\x3a\xf6\xd5\xf1\x33\xb6\x4b\x9b", b"\x62\x42\xe4\x5a\x47\x2f\xa8\x14\x7c\xb5\x25\x3d\xbd\xde\xba\xe3\x1e\xf3\x1e\x4e", )?; test( HashAlgorithm::SHA224, b"\xd1\x2f\xc1\x98\x3e\x00\x95\xe9\xe2\xb6\xb8\x74\x3f\xb3\x43\x86\xcc\x48\x21\x54\x0e\x3e\xfe\x1a\x29\xf8\x4c\xf7\xe6\x3e\x2a\x06\x68\xd5\x51\xf9\x12\xad\x22\x21\xb5\xa3\xd6\xb9\xeb\xd1\x21\x36\xde\xf5\xe6\x69\x0e\x1d\x32\xaa\xe9\x19\xf9\xf1\xcf\x5d\x24\xd6\x2a\x46\xa9\xa9\xa6\x04\xba\xe1\x1b\x9c\x08\x66\x35\x03\x67\x20\x4a\x92\x0b\x58\x9a\x31\x7d\xdf\xbb\x87\x7f\x9f\xad\x6b\x0d\x36\x29\xaf\x96\x35\xda\x46\x93\x31\x51\xc0\xd9\xa2\x0a\xaa\xbd\xdd\x3d\xf5\xd0\x49\x65\x9b\x28\x60\xdd\xb8\xb2\x09\x63\x26\x1e\xa0", p, q, g, b"\x10\xb7\xb1\x4a\xd2\x9f\xb3\x4d\x7a\x39\xf3\xe9\x53\x05\x1f\x45\x6a\x0c\xd1\x23\x3e\xf5\x4d\x90\xa4\xad\xc8\x2d\xfb\xd9\xfa\x7a\x85\x62\x8f\x11\x03\x96\x32\xb4\x7b\xa9\xda\xec\xa6\xe4\x63\xec\x46\x44\xf5\xe2\xa2\xa4\xbf\x95\xd3\x92\xe8\xc9\xc9\xf2\x87\xa2\x0b\xa4\x5a\x19\x88\x15\xca\x0e\x9b\xa8\x54\xd7\xf3\xc7\x9d\x90\x37\xfa\x14\x17\x72\x4f\xb7\xf0\x27\x99\xb1\xc2\xb2\xbc\xc7\x9d\x64\x36\x7b\x90\xc0\x6d\x17\x89\xdc\xc6\xde\x57\xca\x19\xfc\xef\xaf\xc0\x4f\xcc\xe2\x9c\x8f\x49\x5e\xd5\x64\xf5\xd9\xa1\x12\xca", b"\x36\x06\x17\x96\x5f\x65\xa6\x8a\xbc\xb8\x3d\xbf\x2d\x88\x6a\x1a\x10\xca\x05\xde", b"\x71\xab\xb6\xac\xbf\x7e\x65\x3d\x2e\xbc\x3c\xb7\x14\x9b\x51\xcc\x0c\x92\xfb\xa8", )?; test( HashAlgorithm::SHA224, b"\x87\xa6\xdf\xb8\x48\x7f\x16\xf6\xfe\xf1\xd6\x8b\xc3\x14\x69\xac\x21\x0e\xa5\x53\x87\x96\x5b\xb4\x45\x8c\xa0\xd0\x0d\x6c\x46\x85\x8b\xe2\x8a\x01\x9c\xe9\x14\xc3\x9c\x24\x79\xf3\x21\xf0\x25\x2c\xa4\xa8\xbd\x68\x1a\x5b\x35\x8a\x09\x3f\xc8\x34\x1c\x31\xbc\x47\xc6\x18\x40\x3f\x93\x32\x2b\x44\x30\x84\xce\x58\x18\x49\x0b\x74\xe8\x3c\x38\x66\xb8\x16\x4b\xbc\xf7\x9b\xf8\x25\x39\xf4\x28\xc9\x35\x1c\x40\xb1\x0d\x77\x3c\xbe\x1c\xba\xa8\xc9\x80\x0a\x6d\xcf\x38\xd8\x55\x15\xe2\xdf\xf5\xd4\xf8\xa9\x65\xec\xae\xf3\x7e\x38", p, q, g, b"\x75\xef\x5d\x5f\x67\x02\x24\x26\xf5\x31\xe9\xb8\xca\x91\x15\x92\x1d\x5a\x5c\x44\x6b\xcd\xf1\xaf\x70\x1b\x60\x5b\xae\x68\x7d\xff\x8d\x1e\x7b\x3c\x4f\x8b\x28\x95\x37\xeb\x09\xa7\x46\x1d\x66\x88\xa3\x71\x19\x74\x37\x1a\x5b\x73\xa2\x08\x2e\x99\x14\x10\x11\x86\x66\xcc\xd9\x4f\x44\x49\x77\xd0\xc8\x9b\xa3\x61\x62\xde\x02\x3a\xa5\x19\x03\x7a\x6b\xa6\x30\x54\x17\xda\xd3\xf2\xdc\x38\x75\x6a\x40\x04\x64\x91\xe8\xee\x80\xc4\xf1\x47\x82\x5b\x8c\x02\x1b\x5d\x09\xa2\x42\x2d\x39\xd7\xc4\xab\xc3\x95\xf6\xc2\xd7\x90\x3c\x66", b"\x54\x09\xcd\x62\xf5\x53\x93\x06\xae\x8c\x93\x60\x82\xee\xf9\x32\xc6\x50\x5c\x39", b"\x07\xc0\xcc\xb3\x0e\xc9\x0b\x14\x81\x40\x9c\xbf\xa2\xf5\xde\x6c\xfa\xf1\xef\xc5", )?; test( HashAlgorithm::SHA224, b"\xa3\x32\xb3\x8e\x64\x2b\xca\xd8\xbd\x27\x1f\x77\x6f\xff\x24\xa7\x31\x72\x4a\x43\x40\x0c\x16\x14\xf5\xe2\x12\x96\xdb\x04\xf7\x25\xee\xba\xd2\x8d\x62\xe2\x0c\xa3\xf7\xf1\x83\x28\xa7\x6b\x80\x92\xd9\x7b\x63\x2b\xb7\x87\x18\xf0\xf2\xf9\xec\xc7\xc1\x2c\xc3\x6b\x50\x59\x59\x91\x7b\x5c\x54\x31\x2a\xd4\x71\x7b\xe8\x4f\xa8\x40\xb9\xf0\x6d\xe0\x05\xc7\x92\xaf\x3e\x9e\xa7\x2b\x7a\xe2\xe3\x42\x3d\x07\xc7\x81\xc9\xc2\x55\x3f\x89\x95\x54\xa0\xd8\xde\xc9\xa2\x85\xc1\xee\x25\x16\x0f\xa2\x78\x48\x94\x74\xa0\xe4\x37\x95\x16", p, q, g, b"\x41\xcc\x1d\x6d\x9e\x0c\xf5\xf1\x58\xda\xb5\x99\x11\x4f\x3e\xe4\x73\x8f\x19\x7c\xf2\xc9\x56\xb6\xbb\x0d\xdd\x6d\xfd\xcf\x5e\x4d\xb3\x99\xaa\xcc\x16\xc5\x38\x94\x8c\x4b\x50\xde\x85\xba\xd6\xd9\x16\xdb\xc4\x15\xba\xd2\xf6\x73\x70\x23\xfc\x70\x63\xc1\x33\xbd\x0c\x42\x31\xd6\xb3\x3c\xe8\x13\xc0\xd6\x02\x4d\x13\x15\x26\x95\x71\xb2\x55\x4b\xbb\x2e\xdf\x2a\x99\x10\x8a\x43\x59\xe8\xe2\x3b\xf8\xa1\x43\xbf\xc5\x38\xab\x9f\x88\x42\xcd\x4e\x92\x59\x68\xf4\x9a\xc5\x6a\x02\xe3\xf0\x67\xe2\x60\x01\xe5\x20\x7b\xcb\x56\xd4", b"\xa1\x6a\x73\x08\xa6\x82\x4d\x92\x9b\x6a\x9a\x3b\xdb\x28\x0d\x15\x1a\x6e\xed\x81", b"\x7a\x42\xad\xda\xb7\xdd\xb9\x80\x00\x28\x60\x44\xd9\x99\x3d\x5c\xf8\x18\xf2\xb1", )?; test( HashAlgorithm::SHA224, b"\x79\xb1\x44\xd5\x0e\x00\x47\x59\x6c\xf0\x6b\xfc\xb3\xe9\xce\x39\x59\xec\x4b\x8c\xc9\xba\x01\x43\x4f\xc3\xf6\x8f\x47\xc8\x68\xce\xa0\x48\xb9\x90\xe6\x2c\xd7\xa5\x0e\xee\x28\x8b\x35\xae\x62\xaa\x79\x79\x24\xc9\xdc\xab\x76\x40\x9b\x86\x9b\x33\xde\x28\x88\x5e\x62\xf1\x7d\xb7\xa7\x75\x89\x73\x48\x29\x68\xb9\xf9\x60\xeb\x2d\xba\x84\xae\x85\x10\x1a\xa6\xc6\x14\x1b\x3f\x08\x39\xa4\x18\x5a\x4c\x49\x6e\xae\x87\x6e\xcd\xc4\x56\x27\x33\x0d\x36\xf0\x1a\x67\xcb\xb7\xfa\xef\x83\x43\x57\x33\x0a\xac\x36\xc7\xc6\xf4\x7a\xc9", p, q, g, b"\x74\xdb\x74\x60\xc5\x19\x19\xa9\xe8\x7b\x43\x0d\x10\x5d\x86\x36\x2e\xe4\xac\xd9\x68\x2b\xf6\xc9\xfe\x87\xd9\x95\x6c\x2f\x5f\xf1\x7d\x95\x93\x0c\xcc\x12\xf7\xe9\x2d\x8b\xcb\x6a\xf5\xf7\xef\x18\x48\xda\x8d\x15\xc9\x15\x20\x82\x47\x7d\xe9\x95\x94\x78\x1b\x99\x8d\xaa\xfb\xf8\xae\x4a\xf2\x37\x72\x12\x5c\x19\xe1\x66\x42\x1f\x80\x6b\xd0\xfb\xea\xc3\x65\x07\x6e\xcd\x9e\x15\x43\x2a\xd4\xac\x25\x23\x41\x8f\x6e\x41\x0c\xbf\xcb\xc5\xa7\x1a\x0e\xdf\x22\xe6\x94\xa6\x7d\x14\xb9\xcf\xc9\x72\x2b\xc4\xbd\x8c\x43\xe2\x2a\x91", b"\x02\x1a\x3d\xe9\x8c\x3d\xa6\x98\xb4\x77\xb4\xc3\xd5\x0b\x21\x69\xe6\x5f\x5e\x91", b"\xaf\xd7\x64\x31\x8d\xd0\xfe\xe0\x4f\xd6\xb0\x7f\x55\x03\x20\x78\x9c\xd9\xbf\xa5", )?; // [mod = L=1024, N=160, SHA-256] let p = b"\xcb\xa1\x3e\x53\x36\x37\xc3\x7c\x0e\x80\xd9\xfc\xd0\x52\xc1\xe4\x1a\x88\xac\x32\x5c\x4e\xbe\x13\xb7\x17\x00\x88\xd5\x4e\xef\x48\x81\xf3\xd3\x5e\xae\x47\xc2\x10\x38\x5a\x84\x85\xd2\x42\x3a\x64\xda\x3f\xfd\xa6\x3a\x26\xf9\x2c\xf5\xa3\x04\xf3\x92\x60\x38\x4a\x9b\x77\x59\xd8\xac\x1a\xdc\x81\xd3\xf8\xbf\xc5\xe6\xcb\x10\xef\xb4\xe0\xf7\x58\x67\xf4\xe8\x48\xd1\xa3\x38\x58\x6d\xd0\x64\x8f\xee\xb1\x63\x64\x7f\xfe\x71\x76\x17\x43\x70\x54\x0e\xe8\xa8\xf5\x88\xda\x8c\xc1\x43\xd9\x39\xf7\x0b\x11\x4a\x7f\x98\x1b\x84\x83"; let q = b"\x95\x03\x1b\x8a\xa7\x1f\x29\xd5\x25\xb7\x73\xef\x8b\x7c\x67\x01\xad\x8a\x5d\x99"; let g = b"\x45\xbc\xaa\x44\x3d\x4c\xd1\x60\x2d\x27\xaa\xf8\x41\x26\xed\xc7\x3b\xd7\x73\xde\x6e\xce\x15\xe9\x7e\x7f\xef\x46\xf1\x30\x72\xb7\xad\xca\xf7\xb0\x05\x3c\xf4\x70\x69\x44\xdf\x8c\x45\x68\xf2\x6c\x99\x7e\xe7\x75\x30\x00\xfb\xe4\x77\xa3\x77\x66\xa4\xe9\x70\xff\x40\x00\x8e\xb9\x00\xb9\xde\x4b\x5f\x9a\xe0\x6e\x06\xdb\x61\x06\xe7\x87\x11\xf3\xa6\x7f\xec\xa7\x4d\xd5\xbd\xdc\xdf\x67\x5a\xe4\x01\x4e\xe9\x48\x9a\x42\x91\x7f\xbe\xe3\xbb\x9f\x2a\x24\xdf\x67\x51\x2c\x1c\x35\xc9\x7b\xfb\xf2\x30\x8e\xaa\xcd\x28\x36\x8c\x5c"; test( HashAlgorithm::SHA256, b"\x81\x21\x72\xf0\x9c\xba\xe6\x25\x17\x80\x48\x85\x75\x41\x25\xfc\x60\x66\xe9\xa9\x02\xf9\xdb\x20\x41\xee\xdd\xd7\xe8\xda\x67\xe4\xa2\xe6\x5d\x00\x29\xc4\x5e\xca\xce\xa6\x00\x2f\x95\x40\xeb\x10\x04\xc8\x83\xa8\xf9\x00\xfd\x84\xa9\x8b\x5c\x44\x9a\xc4\x9c\x56\xf3\xa9\x1d\x8b\xed\x3f\x08\xf4\x27\x93\x5f\xbe\x43\x7c\xe4\x6f\x75\xcd\x66\x6a\x07\x07\x26\x5c\x61\xa0\x96\x69\x8d\xc2\xf3\x6b\x28\xc6\x5e\xc7\xb6\xe4\x75\xc8\xb6\x7d\xdf\xb4\x44\xb2\xee\x6a\x98\x4e\x9d\x6d\x15\x23\x3e\x25\xe4\x4b\xd8\xd7\x92\x4d\x12\x9d", p, q, g, b"\x4c\xd6\x17\x86\x37\xd0\xf0\xde\x14\x88\x51\x5c\x3b\x12\xe2\x03\xa3\xc0\xca\x65\x2f\x2f\xe3\x0d\x08\x8d\xc7\x27\x8a\x87\xaf\xfa\x63\x4a\x72\x7a\x72\x19\x32\xd6\x71\x99\x4a\x95\x8a\x0f\x89\x22\x3c\x28\x6c\x3a\x9b\x10\xa9\x65\x60\x54\x2e\x26\x26\xb7\x2e\x0c\xd2\x8e\x51\x33\xfb\x57\xdc\x23\x8b\x7f\xab\x2d\xe2\xa4\x98\x63\xec\xf9\x98\x75\x18\x61\xae\x66\x8b\xf7\xca\xd1\x36\xe6\x93\x3f\x57\xdf\xdb\xa5\x44\xe3\x14\x7c\xe0\xe7\x37\x0f\xa6\xe8\xff\x1d\xe6\x90\xc5\x1b\x4a\xee\xdf\x04\x85\x18\x38\x89\x20\x55\x91\xe8", b"\x76\x68\x3a\x08\x5d\x67\x42\xea\xdf\x95\xa6\x1a\xf7\x5f\x88\x12\x76\xcf\xd2\x6a", b"\x3b\x9d\xa7\xf9\x92\x6e\xaa\xad\x0b\xeb\xd4\x84\x5c\x67\xfc\xdb\x64\xd1\x24\x53", )?; test( HashAlgorithm::SHA256, b"\xc1\xb1\xf1\x47\x2f\x08\xdf\x38\xa5\x2a\x55\xba\x55\x82\x7b\xa3\xb7\xcd\xd6\xbe\xde\xd9\x04\xfc\xd5\x26\x10\xc8\x99\xed\xa3\xc6\x16\x82\x65\x68\x73\xbb\xfa\xab\x0d\x90\x74\x95\xda\xcf\x45\x8e\xa3\x45\x0a\xfd\x93\xbe\x96\x7a\x37\x43\x4d\x41\x2b\x63\x25\x66\x9a\xd8\x4b\x4e\xaa\x27\x8a\x24\x87\x0e\xcc\x2d\xf0\xda\x13\xad\x52\x6a\x9e\x66\x69\x95\x8d\x4e\x52\xdb\xfb\xa2\x80\x3a\xe9\xae\x13\x5d\x0c\x0a\xcc\xa8\x6a\x04\xc4\x2b\xa9\xca\xfb\x09\xb7\xaf\x96\x34\x71\x88\x88\x0b\x08\x61\x69\xeb\xdf\x9f\x1f\x5f\x31\x73", p, q, g, b"\x99\x18\x74\x98\x53\x4f\x31\x3d\xc7\xcd\x7f\x3a\x48\xd6\x2b\x23\x35\xbc\xdc\x36\xf0\xdc\x98\xdb\xf8\x45\xdc\x60\x85\xc2\x67\x47\x4c\x36\xfd\xfc\xa3\x88\x54\x21\x98\x30\xe6\x14\xbb\xca\xb2\xbb\x9d\xec\xb8\x1e\x86\x12\x4b\xd7\x8f\x86\xd4\x71\xbd\x84\xbe\x06\xac\x1f\x0f\x41\xfe\x5b\x4b\x37\x40\xb2\x10\x7e\x0c\x9c\x48\xf8\x1e\x31\xe9\xbf\x55\x0d\x96\x56\x4d\xd3\x80\xca\x47\xa1\x1d\x72\xf0\xd0\xa3\x27\x5f\x07\x5f\x95\xbb\xd5\x98\x69\xc1\x4d\xc9\x12\xa1\xcb\xcf\x01\xdb\x9f\xb7\xf7\x10\x15\xcc\x14\x99\x86\x82\x5e", b"\x54\xed\x4e\xfa\xec\xdf\xc7\x8d\x02\x64\x71\xb6\x5c\xfe\xfc\x65\x29\x94\x5b\xbf", b"\x6d\x6d\xac\x29\x6e\xbd\xe3\xf8\x73\xb7\x51\xc6\xb1\x48\x43\xf0\xb7\xbe\xfd\xff", )?; test( HashAlgorithm::SHA256, b"\xb8\x0a\x47\x07\x1d\x13\x76\xfe\x61\x7e\x59\xfd\xc0\x05\xa8\x90\x36\x9a\x4c\xa5\xe6\x78\xff\x46\xeb\x9b\x20\x5d\x6e\xc0\x9c\xbd\x49\x37\x3b\xb3\x41\xfe\x78\x13\xee\x44\x2a\x6e\xce\x17\xe7\x20\xbf\x71\xa7\x45\x57\xac\x9a\x37\x5c\x05\x9e\x55\x35\xe7\x73\xa4\x5e\x79\xe1\xbf\xf3\x46\x5a\x38\x86\xc8\x6e\x2a\x2b\xc8\x82\xf0\xbe\xce\xef\xff\xb2\xae\x1a\x52\x2f\x13\xc8\x2d\xef\x4c\xfd\x0c\xfc\xa6\xfc\xee\xb4\xce\xce\x71\x86\x9e\x90\xcd\x10\xd0\xaf\xf2\x7a\x84\xb5\x60\x1d\xaa\xe0\x61\xcb\xeb\x3a\xa6\x2b\x37\xfd\x3a", p, q, g, b"\x91\xf5\x02\x70\xa7\x54\x05\x5e\x5d\xa6\x11\xc7\x20\xa4\x26\x2f\x3c\xb8\xbd\x41\x61\xf7\x7d\x07\x40\x16\x04\xd3\xd1\x16\x5e\x45\x51\x8f\x7e\x19\x01\xad\xef\x66\x28\xf2\x3d\xc4\x82\x71\xd3\x5f\xf4\x92\xaf\x8d\x62\xaa\x53\x8c\x0e\x77\xe0\x42\xf2\x3a\x52\x2f\x22\x14\xe6\x21\x14\xbf\xee\xa4\x6a\xe8\x88\x8b\xda\xda\xcd\xaa\x0a\x9a\x5b\x50\x3d\x79\xc2\x3e\x4c\x20\xc9\x8b\xd4\xeb\xb3\x6f\x95\xbf\x44\x51\xcc\xb0\xb5\xbb\x44\xdf\xd0\x11\x34\x1c\xfa\x29\xa9\xe1\x56\xa3\xcd\x82\x8e\x12\x6e\x68\xcb\x91\x1e\x8f\x9d\xc0", b"\x1f\xc2\xd1\xcb\x80\xbf\x6e\x0e\x78\xb2\x5f\xac\x29\x3b\x75\x2c\xbf\xf2\xb5\xac", b"\x75\xbc\xc7\x72\xf7\x73\xd5\xfd\x98\xdd\xe1\xf9\x07\xe7\xec\x2c\xba\x20\x1d\xfb", )?; test( HashAlgorithm::SHA256, b"\xa9\x60\x30\x54\x46\x58\x87\xdf\x15\xdb\x07\xc0\x70\x9a\x8c\x87\x8d\x2f\x1a\xbd\xcf\xc6\x19\x5e\xab\xf3\xe9\xb3\xad\x07\xe8\x55\x8b\x99\xcc\x4a\x7a\xa0\x76\xda\xf6\x7e\x9b\x7d\x84\x80\xf1\x1e\x8a\xfb\x18\xe2\xac\x56\xa9\x54\x7b\x48\x45\x3f\xed\xca\x32\xda\x9e\xb0\xc2\x92\x71\xeb\x60\xf0\xa1\xd9\x5c\x18\xf4\x2d\x99\x23\x94\xb3\x26\x4f\xf3\xe2\x1e\x60\x6e\x0b\xea\xc0\x8a\x7b\xa7\x1b\x8e\x57\x95\xa8\xda\x98\x51\x18\xe4\x32\xcf\x5b\x30\xb6\xcd\x3a\x60\x3d\x8b\x0d\x58\x0f\x06\xc6\x26\xee\x93\x7c\x6c\xd0\x5f\x40", p, q, g, b"\xa2\xc4\x56\x9a\x30\x14\x73\xae\x4f\x16\x4d\x68\xb9\xa3\xc6\xeb\x70\x5a\xe8\x1f\x75\xd6\xe5\xcc\x30\x70\xa5\x59\xcc\xcb\x8b\x1a\x2d\x8c\x21\x09\x0e\xd7\x0e\x61\x67\x0c\x7e\x9d\xbf\x5f\x75\x5a\x37\xd5\x8d\x3a\xbb\x34\xc2\xdf\xd4\x0d\xb9\xf2\x6f\x68\x68\xd0\xdd\x91\xbe\x98\xf3\x95\xac\x0e\xbd\xc3\x7e\x1b\x54\x23\x80\x2b\xea\x7a\x6c\xb1\x96\xd7\xe0\xf9\x3d\xb9\x2f\x66\x3b\x6c\x9c\x72\x6e\x80\xfe\xb2\xe9\x22\x71\x54\xce\x1c\x15\xf8\xe8\xdf\x93\xec\x0d\x37\xfa\x47\xe5\xfa\x11\x2b\xb0\xa4\x8f\x4a\x23\x9d\x60\x52", b"\x48\x53\x95\x23\x81\x5b\xd8\xd7\x3c\xe7\x02\x36\x7c\x77\x12\xb9\xb1\x38\x67\xf2", b"\x20\xff\x4c\xfe\xf8\xa6\x68\x82\x9f\xea\xe7\x3b\x52\x0e\x8a\xa4\xd0\x2c\x81\x68", )?; test( HashAlgorithm::SHA256, b"\x19\xeb\x08\x8c\x32\x29\xa4\x4f\x95\x86\xf0\x04\x21\xcf\xe7\x42\x3a\x48\x6d\x5f\x7e\x28\xad\x2c\x91\x19\xdd\x2e\x13\x95\xdf\x1a\xcc\x06\xcb\x28\xe9\x06\x9c\xee\x62\xf0\x9f\x48\xe4\xca\x29\x26\x9d\xd8\x9d\xf9\xfe\xc1\xff\xdf\x64\xb1\xfe\x27\x17\xfe\x52\xb1\x42\x1f\xcf\x6c\x70\x5c\x0c\xf3\x99\x30\xf9\x0e\xcb\x33\x9b\x51\xef\x95\xb2\xef\x38\xa6\xd9\x6a\x57\x5f\x7b\x36\xf5\xed\xf4\xf2\xcb\xd6\xd2\x61\xe1\xfd\xd7\x7d\x44\x59\x28\x8c\x02\xe6\x8c\x82\xa3\x91\x0f\xf8\xca\x17\x47\xc8\x6b\xb1\x87\xd5\x20\x5f\x51\xa8", p, q, g, b"\x49\xe6\x06\xaa\xd5\xd2\xe5\x4e\x1b\xae\x25\x17\x91\x5c\x66\x0b\xa3\x0e\xc4\xfd\x28\xd7\x18\x61\x3a\x7c\x84\x46\x4b\x0f\x44\xbc\x6d\x54\x6e\x5a\x9b\xc1\xdc\x60\x42\x3b\x45\xdd\x01\xec\x29\x55\x64\xec\x08\xf2\x9d\x68\x87\xe6\x9f\x68\x9d\x6b\x34\x88\xf9\xda\x5d\x5a\x60\xf3\x9c\xdd\x5a\x15\x8d\x51\xa3\xd0\x73\xb2\x22\x5f\xea\x55\x9e\x58\xbb\x22\x2e\x29\xa8\x7b\x5f\x0f\x5a\xb3\x1d\xd7\xc0\xce\xaa\xd8\x87\x07\x0d\xac\x95\x5d\x28\x97\x36\x07\xa9\x9e\x46\xdd\xd7\x73\x7b\xea\xb6\x51\x99\xf2\x50\xd7\xf0\x3b\x65\x83", b"\x6b\xf4\xf5\xd3\x25\x12\x01\x05\x9e\xe8\x5e\xdb\x99\xa6\x7a\x70\x6f\x37\x19\x7d", b"\x31\x25\xc5\xaf\x39\x77\x59\x99\x6b\x87\x6c\xb5\x85\x7b\xe2\x63\x2a\xaa\xf3\xb6", )?; test( HashAlgorithm::SHA256, b"\xad\xdb\x5a\x04\x5c\x9f\x4f\x4f\xb9\xeb\x5e\x5d\xb4\x4d\x65\x15\x98\x0c\x9e\x08\x80\x15\xb6\x85\x93\xd8\xbc\xbf\xfc\x6f\xf5\x7f\x18\x86\x5a\xb8\x24\xd3\xd1\x58\x64\x25\xcb\x50\x81\x19\x7e\x9e\x01\xcb\x72\x97\xb0\x6b\x64\x10\x3c\xea\x43\x7e\xee\xec\x9c\x50\x79\x86\x79\xfb\x86\x9e\xc3\x06\xa7\x25\x75\x05\x7f\xd3\x68\xae\xb0\xf6\x74\xa2\x9c\x3a\xc2\x48\xb6\xa0\x8f\x91\x33\x1d\x84\x56\xd0\x62\x02\x53\x47\xc1\x2a\x0a\x61\xc6\x1f\x76\xe5\x20\x6f\xe6\xca\x43\x77\x35\xaf\x43\x0d\xea\x7c\xc8\xf3\x9f\x1a\x5b\x75\x05", p, q, g, b"\x22\x1c\xed\x57\xa9\x13\x25\xb1\x0f\x8d\xcd\x12\x20\xb1\xaf\x68\xf8\xda\xf3\x97\xf4\x19\xa4\x3b\xbd\x8f\xbe\x64\x43\x11\x75\x5b\x11\x1a\xae\x52\x57\xc6\x42\xfa\xfd\x83\xb0\x47\xa1\xf5\x6f\x2a\x82\x9f\xcd\xf4\xdf\x3e\x5d\xcc\xb2\x36\x45\xb2\x8c\x0a\x34\xc6\xe8\xa6\x50\xef\xcd\xfa\xdd\x48\xfe\xa4\x67\xcc\x94\x3c\xa4\xe7\x37\x88\x29\x30\x07\x13\x83\x8b\x6c\x71\x09\x62\xba\x72\xe7\x90\xc1\x0a\xb8\x79\xa0\x1f\xe1\x45\x7e\xa3\xdd\x4b\x7c\x3c\x3a\x54\x2e\x35\x22\xa7\x5d\x0d\xb2\x61\xe5\x76\xcd\x3f\x22\xc9\x98\xe4", b"\x7c\xc6\x62\xe3\x52\xe0\xee\xde\x85\x14\x01\x07\xa7\x77\x3a\xd8\x66\x3e\x70\xbd", b"\x15\xc1\x7b\x9d\x24\x58\x72\x84\x4e\xaa\xc3\xd4\x6b\xb0\x8c\x3e\x08\x59\x74\x23", )?; test( HashAlgorithm::SHA256, b"\x02\x70\x9d\x2b\xe0\xd9\xdc\x1d\xc0\xeb\xc5\x5f\x63\x0d\x91\xfa\x23\x60\x9f\x61\xb5\x13\xc2\x27\x57\x66\x03\x4d\x8f\x40\xe8\x19\xaa\xf9\x32\x6c\x8d\xb3\x7c\x35\xc5\xa1\x7e\x96\xbc\x95\x6d\xf6\xd1\x1b\x55\x8d\x16\xd9\x18\x71\xaf\xc0\x10\xb3\x11\x9c\x57\x98\xc2\xe2\x94\x11\xff\x4f\x0d\x71\x96\xe7\xe4\x76\xbf\x0a\xd0\x3b\xf7\x2e\x89\x7f\xed\x87\x3c\x10\x61\x3d\xd2\x55\xd1\x52\x43\x87\x0b\x81\xcd\x87\xd0\xab\xc1\x6e\x14\x0d\x03\x2f\xe5\xbd\x1c\x8e\xeb\x2f\x66\xe0\x4d\x13\xd4\x92\x69\xfc\x7d\xa6\xb6\x5a\x7c\x1c", p, q, g, b"\x9e\x93\xbc\x03\xe6\xe8\x15\x30\x87\x34\xe3\xb8\xf1\xd1\x06\x96\x1b\xeb\xdf\xf1\x0a\x52\x53\x03\x25\x7a\x05\x3d\xea\x4d\xa6\xdc\xf5\x04\xc7\x83\x9b\x54\xd5\x75\x22\xf2\xac\xb3\xaa\xc9\x59\xff\x4a\xe8\x61\x00\x22\xca\x5a\x1e\x68\x82\x32\x33\x6c\xa1\xee\x8f\xd7\x02\x8b\xf7\xb6\xe9\xee\xdf\x8a\x4b\x0d\x09\x89\x69\xf5\xe5\xfd\x3d\x93\x00\xc3\x40\xe7\xf1\x9f\xd4\x71\xa4\x51\xaf\xb9\x2e\xd4\x82\x9f\xa4\xd9\x02\x49\x14\x4a\xa3\x63\xdc\x18\x80\x7b\x3e\x29\xd2\x7e\x6e\xc3\xda\x73\x6c\x33\xb1\x85\x51\x1b\xb3\xaa\xa0", b"\x72\xb0\xbc\xc6\xde\xfa\x66\xfa\x8b\xab\x02\x96\x76\xa1\xc7\x70\x3f\x96\x08\xf2", b"\x69\xd9\x11\xe0\x5a\xcd\x7b\xe5\x2f\x28\x34\xc0\xaa\x00\x51\x28\xe7\xfa\x85\xb8", )?; test( HashAlgorithm::SHA256, b"\xcc\x06\x1e\xdb\x31\xc3\x4d\x39\x81\x51\x7f\x4d\x89\xaf\xbe\x98\x0f\x74\x18\x52\x60\xcf\x48\xb3\x04\x3b\xc1\x3a\x14\x49\x44\xad\x43\xe0\xe5\x76\xd2\xa5\x8b\xf5\x89\xcc\x02\x1d\xc1\xc1\xd3\x32\xc4\xd7\x68\x96\xea\x77\xdd\xa1\x97\xf6\x83\xe5\x1e\xed\x71\xb4\xd6\xdf\x46\x66\x6a\x1b\x14\x2e\x67\x9b\x02\x83\xcf\x33\x9e\x5b\xca\x90\xe2\xff\x9c\x34\xdd\x5f\xd7\xcc\x49\x17\xd6\x67\x04\xfe\xe4\x36\x4f\x76\x93\x10\x1d\xc7\x66\x70\x71\x04\xef\xb2\xb9\x33\xc4\x84\x8b\x93\xe1\x3f\x94\x85\x5f\x75\xe4\xfd\x75\x6c\xb6\xe3", p, q, g, b"\x5d\x7d\x2e\x34\x21\x54\x98\x3e\xbc\x20\x15\xbc\x67\x50\xf9\x87\x6f\x56\x89\xca\x0a\xda\x85\x29\x90\x8e\xd4\xfd\xbc\x59\x6b\x97\x2c\x5c\xc6\xd5\x3e\x80\xa8\xad\x8a\x8c\xed\xf3\xce\x64\xb6\x2a\x75\xdb\x44\x1c\x96\x20\x7f\xc7\x47\x7e\x3f\x7b\x9f\x10\xdf\x28\xe0\xcc\x2f\xb7\x73\x83\xe7\xca\x4c\x51\x50\xf7\x12\xdd\x82\x3c\x23\x09\xf0\x16\x1b\xe0\xbd\x5e\xed\xd6\x0c\xf6\xba\x23\x08\x61\xa0\x8b\x9d\x9a\x74\x68\x43\x8b\x4d\x6e\xc6\x73\xd2\x8a\x54\xd8\x3c\x70\x10\xd5\x06\x31\xe5\x5f\x0a\x02\x83\x2a\xbc\x5a\x0a\x46", b"\x21\xf5\x12\x42\x56\x70\x94\x34\x77\x53\x4e\x90\x75\xce\xb5\xb7\xd6\x3f\x20\xdf", b"\x73\xc6\xf6\xf8\xde\x3a\xae\xa5\x20\xa0\x83\xb2\x26\x42\x99\xe8\x1c\xfc\x91\xc5", )?; test( HashAlgorithm::SHA256, b"\x79\xd5\x29\xe4\x0c\x2b\xa4\xe5\xb9\xc7\xd7\x7d\x72\x07\x6f\x1f\xd9\x49\x09\x28\xff\x44\x19\xc8\x24\xe6\x4d\xb8\xfb\x9a\x05\x1e\x01\xe8\xe1\x73\xc6\xf2\x14\xe0\xe9\xe6\x45\xed\x25\x0b\x6d\xaa\xa6\xf8\xc1\xa5\xcc\x90\x0d\x52\xcf\x3e\x1e\xfb\xfe\xa2\x57\x48\xe8\x9a\x1a\x54\x8c\x73\xe2\xd1\x10\xb2\x5f\x53\x08\xbc\xf7\x57\xb2\x13\x52\x16\xc9\x1d\xca\x27\x83\x33\x2c\x0d\x79\x03\xeb\x21\xc2\x26\xdb\xd3\x3a\x69\xee\xf5\x75\xaa\x8a\x41\xcb\xbd\xcd\x1b\x3d\x94\x92\x8a\xa8\xf8\xba\x58\xc5\xce\x0d\x31\x77\x86\xe8\x7b", p, q, g, b"\x28\x2d\xec\xc0\xe3\x79\x94\xc2\x85\x6e\x61\xf3\x6b\x83\x1b\x61\xbd\xc0\x2b\x7c\xa6\x75\xdb\xc3\xc2\x03\x28\x00\xb7\xef\xd3\xb7\x11\xac\xf1\x4c\x88\x69\x96\x88\x31\xe1\x45\x36\x1b\xf2\x18\x2b\x06\x0e\x48\x38\xf0\x7d\xc6\x1f\x76\x58\x4c\xf1\x02\xa9\x13\xbb\x28\xa5\x2c\x73\x17\xaf\x5f\x9d\x23\x22\x92\x7c\x96\x66\xe5\xe8\x7c\x2f\x2b\xfd\x2f\x18\x1d\xd3\x26\x12\xd7\xb2\xb2\xa6\x45\xbf\x1a\x47\xc0\xeb\xfd\x79\xa9\x40\xf6\x27\xa6\x68\xa8\xf2\xeb\x72\x9f\xd0\x51\xaa\x2c\x65\x9a\xbc\x91\x8e\x55\x71\x99\x4e\x65\x93", b"\x92\x9a\x48\x51\xbe\x0a\xe4\xba\x91\xda\x0e\x6c\x73\x76\xd7\x1d\xf7\x59\x2d\xbb", b"\x7e\x6b\x65\x04\xb7\x48\xef\x00\x24\xd9\xd2\xa2\xe6\xf3\xbc\xd7\xcf\x13\x5a\xc7", )?; test( HashAlgorithm::SHA256, b"\xf5\x51\x64\x10\x70\x63\x23\x54\x9b\x20\xc5\x2d\xaf\xa2\xf2\xf9\x07\x99\x78\x6c\x0d\xdb\x85\x04\x88\x92\xcc\xc1\x87\x20\xdc\xe5\xc1\x29\xa1\x0e\xb4\x38\x87\x88\xa3\xd9\x7a\x03\xb0\x00\x17\x99\xcb\x65\xa7\x9c\x88\x08\x36\xbc\x9f\x32\x04\xea\x75\xa5\x77\x20\x4d\xc1\xe2\x89\x4c\x57\x2a\x25\x8f\x9e\x51\x7c\xa3\x7c\x5b\x79\x1e\x48\xb2\x7c\x8d\xc1\xc8\x21\xb3\x4e\xbb\x1f\x29\x85\x8c\x4a\x72\xa0\xd5\x17\x2c\x56\x5e\x9d\xbe\x1b\xdd\xdf\x6e\x02\x48\x91\xcd\x62\x91\xfa\xa8\x1e\xd5\x65\x74\x6c\x61\xc2\xed\xa2\x01\x1f", p, q, g, b"\x74\xcc\xc6\xeb\x83\xad\xbc\xba\xd0\xfc\x37\x14\x4d\x9b\xfb\x85\xfd\xcc\x85\xab\x92\xc9\xf8\x87\x7c\x9c\xda\x66\x25\x1d\x1a\xff\x2f\xb2\x24\x88\x8d\xdd\xb7\xd7\x72\xa8\xb7\x38\xc5\x3e\x03\xec\xad\x99\x03\x79\x6f\xa3\xc9\xc6\x02\x4d\x06\x36\x7e\x08\x70\xad\x79\x76\x94\xf5\x98\x70\x8d\x08\x91\x2c\x0f\xe0\x98\x81\x76\x3a\x0a\x72\x2d\xda\x95\xd9\x4e\xee\x88\x24\x92\x7c\xbf\xa6\x76\x1a\x79\xa0\x38\xaa\x6d\x33\x1d\xa3\x4d\x9b\xd5\xc5\x83\x3c\x94\xc5\x26\xa8\x6a\xf1\xcd\xfb\x2d\x40\x79\xd2\xdb\x6d\x0b\x9a\x12\x38", b"\x27\xb3\xf2\x35\xe4\xaf\xc1\x8c\x66\x13\xb4\xfa\x7f\x27\xd7\xa8\x26\x2b\xa4\xc0", b"\x8b\x22\x63\x4e\x4d\x45\xb7\x1a\x84\xea\xba\xa1\xe5\xa4\xbf\x1e\x37\x33\x7a\x59", )?; test( HashAlgorithm::SHA256, b"\x55\xbd\x15\x26\xe0\x8f\x64\x43\xb2\x55\xac\xd3\x2c\x28\x68\x07\x54\x2d\x34\xc0\xf3\xd7\x98\x92\x71\x3f\x9d\x6d\x6d\x6b\x3b\xe7\x07\xe4\xaf\x6e\x71\xf7\xda\xb4\xa2\xc5\xf6\xbd\x25\xf5\xae\x1f\x51\x4b\x26\x44\xa4\xcd\xaf\xce\xce\x1e\x58\xf7\x57\x6f\x82\xe2\xab\x0a\xf2\x32\x6c\x71\x27\x9e\x9b\xce\xf1\xe1\xc5\x4a\x76\xfa\x77\xec\x2b\x2d\x05\x67\x17\x64\x57\x64\xe7\x99\x1b\x52\x0b\x0e\x5a\x1b\x04\x91\x09\x51\x9b\x22\xaa\x52\x04\xe3\xed\x53\xb1\xe0\x95\x7d\xab\x5e\xc3\x24\x79\xd0\x6a\xc3\xe1\x1a\x5d\x1c\xbd\x03", p, q, g, b"\x5d\x6e\xdf\x6d\xb6\xe6\xc2\x7e\x80\xa7\xf0\x25\x97\x23\x79\x19\x17\x0b\x49\x36\x48\x9d\x6f\x15\xf5\x98\xb8\x20\xcd\x91\x7e\x17\x25\x09\xb7\xe2\x87\xb8\x8b\x0c\xc1\x4e\x1a\x01\x86\x79\x38\x86\x80\x9a\xb4\x17\x02\x09\x98\x70\x95\x09\x22\x34\xb4\xfd\xc4\x4b\x3d\x1f\xc1\x6e\xeb\x2e\xfa\xf8\x52\xed\x39\x16\x69\x8c\xf9\xec\xa4\x61\x2b\x49\x61\xbb\x6e\x20\xc3\x2e\x18\x84\x69\x88\x3f\x97\xf4\x9e\x29\xa8\x19\x7c\x30\xd0\x72\x3b\xab\xb0\x6d\xea\x70\x4f\x77\x04\xb2\x78\x8e\x57\xd7\x6d\x6d\x9a\x3c\xfa\x68\xf6\xc7\x83", b"\x62\x1a\x29\x09\x30\xac\x43\x67\x37\xa7\x2f\xb4\xc6\x2b\xf5\xc4\xb6\x74\x81\xaf", b"\x62\xdb\x20\xf8\x2a\x57\x54\xf1\x09\xf7\xa2\xce\x58\x1d\x4c\x8d\x71\xc6\x8d\x29", )?; test( HashAlgorithm::SHA256, b"\x62\x78\x9a\x89\xf0\xd7\x08\xe2\x1a\x12\x1f\xc3\x40\x09\xaf\x88\x41\x33\x68\x1b\x9d\x4a\x66\xcc\x36\xc0\x36\x5c\x34\xbe\x72\xa4\x98\x2e\xb0\x96\x1c\xe2\x57\xf3\x5e\x6e\x71\x83\xf0\x20\x4a\x96\xa5\x45\x19\x30\x01\x02\x3d\x33\x09\xa8\x99\x7e\x7c\x4b\x76\x2a\xb4\xf4\xc4\x0e\x03\xe1\x3f\x4e\xdb\x32\x8b\x23\xcf\x00\xc0\x91\x19\xde\xb4\x0a\xdd\xf6\x56\x7b\x3b\x74\xac\xef\x5c\xef\xf0\x45\x30\x4d\x61\x84\x21\xe8\x73\xc4\x1a\x72\xd3\x1e\x45\x1d\x21\x3b\x06\x08\x29\xb2\x86\xf6\x40\x13\xd4\xd9\x34\x2a\xe7\xab\x80\x64", p, q, g, b"\xad\x59\x05\x90\xa8\x2e\x89\x29\xca\x86\xf4\x05\x51\x6c\x32\x91\x3b\xf5\x28\x2f\x70\x30\x9c\x6d\x4a\x88\xcc\xf1\x65\xce\x15\xfc\xf1\x1e\x14\x0c\x36\x6b\xb2\x73\x83\x9a\x71\x1c\xb6\xae\x52\xbb\x71\x78\x59\x57\x0f\xdb\xf9\xfc\x26\x72\x67\x28\x59\x6e\x6f\xc7\x19\x23\xde\xad\xb3\x5a\x9d\x57\xcd\xb2\x13\xc0\xf2\x9c\x13\x75\xf8\xb1\xd3\xc6\xb5\x26\x90\xc4\x28\xf7\x48\x1c\x14\xaa\xd8\x2f\xba\x7f\x24\xee\xa8\xcd\x8d\xa7\xf0\xef\x7a\xe7\x81\xc1\xa9\x26\x67\x1a\x61\xd4\xe5\xff\xc8\xdd\xf1\xbc\x10\xd6\x88\xa9\x04\xc6", b"\x89\xdc\xbc\xa7\xc8\xcd\x6b\x90\xaa\x90\x6a\x4c\x54\x71\x53\x76\x2f\xcf\xff\xd6", b"\x23\xe8\x92\x6b\x18\xcf\xd4\xb6\x7c\x53\xfa\xc4\xa2\xd5\x32\x1e\x5c\x3d\x88\x0c", )?; test( HashAlgorithm::SHA256, b"\x4e\xaf\xcc\x68\x74\xae\x2a\x6d\x52\x57\x38\x96\x7a\xfb\x30\x54\x35\x7a\x39\x67\x0d\x1e\x55\x55\xd7\xdc\x55\xbe\x24\xdd\x5a\x32\xa0\xc7\xca\x3f\x1b\x5c\x6d\x94\x8c\x9c\xe3\x91\x01\x3a\xbe\xb4\x7f\x7e\x24\xcd\x2c\x54\xe1\xfc\x7c\x0e\x92\xc4\xab\x77\xf5\x97\x3a\x70\x54\xbd\x1c\x6c\x84\x5b\x80\x2b\x79\x37\xd6\x52\x05\x08\xae\x01\x8a\xe1\x4b\x27\xff\x4b\x1e\x34\x0a\x4b\x9f\x6f\x6b\x48\x14\xd0\x7e\x90\xcb\x8f\x19\xb1\x5e\x91\x5d\x6a\xd1\x83\x4c\x0f\x7a\x3c\x3e\x1e\x45\x20\x67\x72\xa0\xee\xc2\xd3\xf9\x16\x08\x97", p, q, g, b"\xb9\x3d\x79\x47\x2f\x04\x98\x93\x77\x9a\x3a\x0e\x83\xb3\x85\x3f\x78\xb3\xcf\x69\xb7\x59\x61\xa6\x0e\x95\x0f\x0c\x00\xf4\x98\xf3\xea\xa2\x38\x43\x25\xf7\x4d\xdd\x38\x29\x2f\xbd\xbd\xb1\x99\x21\x2e\x90\xb1\x4e\xc9\xe5\x54\x72\x7d\xf8\x1e\x06\xeb\x77\x83\xad\xda\x38\x69\x1c\x63\xa7\xcb\x00\xcd\x76\xd8\xe1\x8e\x3d\x29\xc7\x93\xe9\xf1\xfe\x83\x37\xf1\x59\x8b\x89\x65\x1f\x63\x4c\xb7\x03\xf2\x18\xe1\x90\x63\x19\xf8\x2a\xc6\xd5\x8e\x67\x86\xda\x7a\xec\xfb\xca\x59\x39\xf0\x3a\x13\xe7\xb4\xd5\xa8\xac\x81\x2d\x78\x29", b"\x63\x3e\x98\x12\xa0\x65\x7c\xec\x33\x26\xaa\x54\x15\x34\x0c\x46\x36\x2f\xcd\x4b", b"\x6b\x20\x1f\x0c\x3f\xd4\x42\x47\xf6\xc2\x8c\x01\xd1\x21\x7e\xb9\x91\x46\xc0\x40", )?; test( HashAlgorithm::SHA256, b"\x86\xd9\x89\x2b\x48\xf5\x95\x41\x01\x48\x27\x42\xc0\xda\xfb\x68\xdc\x97\x12\x24\x83\xb9\xe4\x59\xf9\x74\x95\xcc\x97\x0e\x05\x6d\x21\x62\xc7\xc7\x1d\xb1\x67\x22\x9f\xb7\xf4\x52\x09\xe0\xc0\x1e\xb0\x6f\xf9\x24\xb8\x23\xed\xa5\x1a\x7e\x99\x0f\x3c\x98\x6e\xb9\xaf\x2a\x7a\x07\x3f\x75\x4c\xb8\x4d\xb4\x53\xa9\xe8\xc0\xae\x7f\xa5\xc0\x5a\x26\x55\xd2\x61\xad\x7e\xc5\x61\x28\x76\xfa\x7d\xf0\x95\x22\xe0\xb6\x9a\xe9\x24\x77\xf6\x3d\xef\x19\x92\xc9\x6c\xe9\x5e\xe7\xbd\x63\x0e\xc1\x61\x46\x21\xda\x6a\x51\x2a\xb5\x3d\xd7", p, q, g, b"\xae\x26\x4e\xa9\x6b\xf0\x93\xef\x2d\xe2\x73\x81\x73\x82\x19\xe3\xbf\xdb\x08\x61\x69\x67\xcd\x13\xe9\x41\x5f\x47\x5c\x4a\x79\x4c\x19\xf1\x2a\x60\x7b\x89\x8d\xb1\xe3\xe6\xbc\x54\x02\x32\x75\x85\xd3\x28\x41\xae\x15\xe3\x46\x28\x80\x85\x0e\x9e\x41\x36\xa4\x75\x1b\x64\xa7\x29\xea\x27\xb7\x2c\xe3\x61\x28\xa4\x4f\xa5\x37\x52\xa0\x8d\x73\x58\x4f\xaa\x44\xfb\x14\x12\x0f\x47\xa0\x4c\x47\xe9\x89\xea\xda\xbc\x7e\x5c\xdb\x15\xd2\x7c\x2b\x0e\xa4\x25\x7c\xec\x22\x9a\x2c\x7b\xf7\xc9\x3c\x57\x1e\x8d\x22\xae\xaa\x2e\x38\xbe", b"\x77\xb4\x80\x88\x5c\x70\xc1\xfe\xe2\x05\x62\x37\xd1\xb7\x9c\xfd\x9f\xb5\x4a\x1f", b"\x22\x83\xf4\xc0\x64\x0f\xf6\xda\xac\xbd\xfb\xbe\xf7\x22\x4a\xfa\x59\xca\x39\x59", )?; test( HashAlgorithm::SHA256, b"\x8b\x60\xb9\xb6\xba\x37\x54\x48\xde\x4f\x00\xde\x51\xd1\x87\x06\xef\x8c\x4f\x97\xba\x34\xc9\xcc\xe2\xb0\xab\xb0\x69\x84\x36\x00\x9d\x1d\x2b\xaf\xcb\xef\x73\xa8\xb5\xdf\xf6\xa3\xcd\x5d\xb5\x25\x8a\xc8\x4e\xf7\x24\xb2\x8d\x8a\x62\xd7\x15\xda\x6e\x11\x19\x39\x73\x53\x66\xa7\xc6\x64\x70\x36\x45\x57\xf5\x46\x37\x7d\x5c\x0e\x7e\xa9\x06\x47\x31\xcb\x71\x49\xe1\x05\x1d\x66\xa7\xbe\xd1\x4a\xa2\x05\xbd\xc5\xd4\xb9\xca\x02\x9a\x1e\x68\xa6\xfa\x2c\x1d\xb2\x2d\x27\xfb\x79\xd8\x38\x77\xcf\xaa\x67\x42\x11\x92\x29\xa4\x93", p, q, g, b"\x87\x03\x2f\x26\x3d\xe2\xbf\x2f\x26\x8a\x09\x3f\x33\xc3\x66\xd6\xbc\xda\x77\x2c\xa9\x59\xfa\x17\xcf\xe9\x48\xf1\xdc\xa3\xe7\x5e\xc9\x42\x76\xde\x91\xd9\xbc\x60\xfc\x6a\xb9\x22\x48\x61\xc5\x5d\xc9\xcc\xc5\xf7\x15\xc2\x51\xdd\x50\x8b\xd4\x38\x68\x1c\xab\x20\x50\x59\x05\x0f\x8e\x11\xe8\xa5\x46\x8d\xa4\x2d\x20\xae\xfa\xc5\x3d\x7a\x9f\xb7\x1f\x64\x24\xd7\xbd\xc6\x5d\xb8\x73\xee\x4f\x9d\xcd\x91\x80\x91\xaa\x72\x4b\x26\x1b\x60\x56\xf3\x20\xca\x77\x24\x51\x8e\x14\xcb\x8d\xba\x0b\x71\x3f\x54\xa0\xfe\x44\xff\x15\x97", b"\x5d\x15\x9f\x89\x4d\x25\x0d\xb9\x0d\x7f\xcc\xd4\x93\x29\xe4\x4d\x11\x12\xdb\x47", b"\x37\x23\x1b\xc1\x51\x95\xec\xb6\xba\xdb\x7c\x3f\xe8\x03\x80\xff\x91\x2b\xae\xda", )?; // [mod = L=1024, N=160, SHA-384] let p = b"\xf2\x4a\x4a\xfc\x72\xc7\xe3\x73\xa3\xc3\x09\x62\x33\x2f\xe5\x40\x5c\x45\x93\x09\x63\x90\x94\x18\xc3\x07\x92\xaa\xf1\x35\xdd\xea\x56\x1e\x94\xf2\x47\x26\x71\x6b\x75\xa1\x88\x28\x98\x2e\x4c\xe4\x4c\x1f\xdd\xcb\x74\x64\x87\xb6\xb7\x7a\x9a\x5a\x17\xf8\x68\xab\x50\xcd\x62\x1b\x5b\xc9\xda\x47\x08\x80\xb2\x87\xd7\x39\x81\x90\xa4\x2a\x5e\xe2\x2e\xd8\xd1\xff\x14\x7e\x20\x19\x81\x0c\x82\x98\xed\x68\xe1\xca\x69\xd4\x1d\x55\x5f\x24\x9e\x64\x9f\xb1\x72\x5d\xdb\x07\x5c\x17\xb3\x7b\xef\xf4\x67\xfd\xd1\x60\x92\x43\x37\x3f"; let q = b"\xda\x06\x5a\x07\x8d\xdb\x56\xee\x5d\x2a\xd0\x6c\xaf\xab\x20\x82\x0d\x2c\x47\x55"; let g = b"\x47\xb5\x59\x1b\x79\x04\x3e\x4e\x03\xca\x78\xa0\xe2\x77\xc9\xa2\x1e\x2a\x6b\x54\x3b\xf4\xf0\x44\x10\x4c\xd9\xac\x93\xef\xf8\xe1\x01\xbb\x60\x31\xef\xc8\xc5\x96\xd5\xd2\xf9\x2e\x3a\x3d\x0f\x1f\x74\x70\x2d\xd5\x4f\x77\xd3\xcd\x46\xc0\x4d\xee\x7a\x5d\xe9\xf0\x0a\xd3\x17\x69\x1f\xdd\xce\xfe\x4a\x22\x0a\x26\x51\xac\xae\x7f\xce\xdd\xa9\x2b\xfc\xca\x85\x5d\xb6\x70\x5e\x8d\x86\x4f\x81\x92\xbf\x6b\xf8\x60\xc0\x0f\x08\xad\x64\x93\xec\xc1\x87\x2e\x00\x28\xd5\xc8\x6d\x44\x50\x5d\xb5\x74\x22\x51\x5c\x38\x25\xa6\xf7\x8a"; test( HashAlgorithm::SHA384, b"\xb0\xdb\xbf\x4a\x42\x1b\xa5\xc5\xb0\xe5\x2f\x09\x62\x98\x01\xc1\x13\x25\x8c\x25\x2f\x29\x89\x8c\x33\x54\x70\x6e\x39\xec\x58\x24\xbe\x52\x3d\x0e\x2f\x8c\xfe\x02\x2c\xd6\x11\x65\x30\x12\x74\xd5\xd6\x21\xa5\x97\x55\xf5\x04\x04\xd8\xb8\x02\x37\x1c\xe6\x16\xde\xfa\x96\x2e\x36\x36\xae\x93\x4e\xc3\x4e\x4b\xcf\x77\xa1\x6c\x7e\xff\x8c\xf4\xcc\x08\xa0\xf4\x84\x9d\x6a\xd4\x30\x7e\x9f\x8d\xf8\x3f\x24\xad\x16\xab\x46\xd1\xa6\x1d\x2d\x7d\x4e\x21\x68\x1e\xb2\xae\x28\x1a\x1a\x5f\x9b\xca\x85\x73\xa3\xf5\x28\x1d\x30\x8a\x5a", p, q, g, b"\x43\xa2\x7b\x74\x0f\x42\x2c\xb2\xdc\x3e\xaa\x23\x23\x15\x88\x3a\x2f\x6a\x22\x92\x7f\x99\x7d\x02\x4f\x5a\x63\x8b\x50\x7b\x17\xd3\xb1\xcb\xd3\xec\x69\x1c\xc6\x74\x47\x09\x60\xa0\x14\x6e\xfd\xec\xb9\x5b\xb5\xfe\x24\x97\x49\xe3\xc8\x06\xcd\x5c\xc3\xe7\xf7\xba\xb8\x45\xda\xdb\xe1\xf5\x0b\x33\x66\xfb\x82\x7a\x94\x2c\xe6\x24\x6d\xda\x7b\xd2\xc1\x3e\x1b\x4a\x92\x6c\x0c\x82\xc8\x84\x63\x95\x52\xd9\xd4\x60\x36\xf9\xa4\xbc\x2a\x9e\x51\xc2\xd7\x6e\x30\x74\xd1\xf5\x3a\x63\x22\x4c\x42\x79\xe0\xfa\x46\x04\x74\xd4\xff\xde", b"\x77\xc4\xd9\x9f\x62\xb3\xad\x7d\xd1\xfe\x64\x98\xdb\x45\xa5\xda\x73\xce\x7b\xde", b"\x23\x87\x1a\x00\x2a\xe5\x03\xfd\xab\xaa\x6a\x84\xdc\xc8\xf3\x87\x69\x73\x7f\x01", )?; test( HashAlgorithm::SHA384, b"\xec\x84\xbe\xd0\x9e\xcb\x4a\x6f\xee\xec\x3a\x70\x71\xb6\x5a\x4c\x12\x67\xa0\x3c\xac\x8b\x5a\x05\x00\xc2\x37\xb2\x0d\xc0\x58\x51\x4d\xa7\x98\x33\x5a\x21\xb2\x3d\x7e\x8c\xbb\x15\xef\xcf\x92\xe6\x06\x0a\x13\xfb\x77\xf4\x99\x81\x47\xde\xc1\xd0\xfa\x0e\xdd\x41\x8b\x0a\xae\x8e\xb0\x05\x6f\xc7\xd4\x00\x8b\x19\x8b\xd4\x0b\x96\x9d\xc1\x0d\x79\xe1\x5b\x23\x00\x82\x03\x23\xbd\x5e\x1b\x7d\x89\x4c\xe8\xe7\xbc\x8f\x7c\xec\xa1\x29\xb5\xe5\x11\xee\x1c\x8c\xae\xc2\x55\x14\xf5\x37\x35\x3a\x91\x2a\x97\x1b\x80\x70\xe3\xf1\x41", p, q, g, b"\xd7\xa0\x95\x0d\x0e\x63\x62\xb0\xc9\x42\xad\x8a\xf6\x71\x61\xdf\x07\xde\xbc\xa5\x9a\x4c\xfa\x72\x8f\x93\xd4\x9b\x6e\x29\x6a\x23\x96\x9a\x65\xa9\x2b\x2e\x05\x39\x8a\x11\x4d\x73\xd5\xa5\x2b\x73\xb7\x1e\xbb\x28\x57\x1c\xf6\xb6\x00\x2f\x85\x3a\x8f\x59\x4b\x5c\x93\xb9\xa8\x42\x33\xf3\xc5\x52\x82\x36\x19\xe0\xaa\x84\x7d\x60\x20\x3d\xb1\x5d\x2a\x91\x6a\xd0\x22\x28\x32\x5e\x15\x78\x39\x88\xf4\x15\x9e\x05\xc8\xca\x08\x83\x60\xe6\xea\x7a\xce\x51\xb0\x55\x10\x21\x53\xc0\x0a\xdf\x33\x5f\xf6\xaf\xfd\x17\x54\xf2\xa8\xaa", b"\xb2\x57\x0e\x0e\x19\x93\x54\x38\xd3\x26\x86\xc4\x78\x47\x3a\x0e\x45\xda\xd0\x23", b"\x39\xa0\x2e\x98\x03\x62\x4f\x7e\x90\xfe\xab\x87\x14\xcd\xdc\x41\xe0\x1f\x8f\xce", )?; test( HashAlgorithm::SHA384, b"\x80\xf7\x57\xfc\x06\x40\x9b\x70\xd7\x33\xef\xdb\x68\xb5\x20\xf3\xf9\x07\x8a\xb9\x36\xc4\x47\x9f\xb9\x8d\x0b\xeb\x16\x31\xd8\x30\x33\x24\x47\x08\x24\x86\x22\x24\xb4\x39\xbc\x85\xde\xcf\xcc\xb8\xde\x8f\xbf\x36\xa2\xbc\x4c\xe3\xa0\x92\x68\x82\x49\xab\x4e\xb9\xfe\xbf\xad\x26\x82\x45\xfb\xd7\xe7\x2e\x0f\x24\x05\x00\xaf\x71\x29\x2e\xa2\x3c\x8a\xd4\xb7\x1e\x03\x21\x06\xf5\x87\xf4\x61\x16\x63\x13\x76\x90\xcb\x25\x24\x19\x12\x76\x3c\x5e\x18\x79\xb3\xab\x67\xe2\x18\x7f\x92\xd8\x21\xfc\x81\xf5\x52\xe2\xc3\x55\xbd\x73", p, q, g, b"\x1f\x03\x01\x3e\x66\xfd\x1e\x63\x3f\xf7\x43\x89\x4c\x37\xf6\x96\x48\x39\xa5\x2c\xfb\xb6\xe8\x49\xcf\xb4\xea\xc9\xa3\xc9\xcd\xb5\x5c\x28\xe1\x47\x88\x86\x5c\x21\x2b\xe6\x20\x47\xcb\x39\xc6\x36\x57\x80\xbb\x2e\x62\x79\x57\xd3\x4e\x99\x23\x2f\x69\x17\x0a\x8e\xfb\x89\x4d\x80\x29\xd1\xb8\xbe\xa8\xb9\x11\xce\xbc\xd4\x3b\x86\xbd\x53\x66\x93\xf1\x8b\xfe\x50\xc8\x4b\x99\x91\x11\x81\xac\xe1\x4c\x3f\xab\x9f\xb6\xac\xd9\x87\x86\xf9\xd2\xad\x12\x9c\x5e\xfe\xb8\xcd\x09\x41\xa3\xd8\x90\x98\xd5\x72\x1d\x43\x53\x43\xcb\x76", b"\xc7\xdb\x4a\x9f\x54\xd8\x82\xec\x5f\x56\x17\x05\x39\x6c\x94\x83\x4d\xd5\x3c\x5a", b"\x67\x52\xcb\x6b\xe9\xb8\x72\x65\xd7\x6d\x69\xb3\x82\x29\x96\x78\xf9\x6a\x5f\xaf", )?; test( HashAlgorithm::SHA384, b"\x36\xa2\x56\x59\xa7\xf1\xde\x66\xb4\x72\x1b\x48\x85\x5c\xde\xbe\x98\xfe\x61\x13\x24\x1b\x7b\xed\xdc\x26\x91\x49\x3e\xd0\xad\xd0\xb6\xa9\xfb\xbf\x9f\xb8\x70\xa1\xbc\x68\xa9\x01\xb9\x32\xf4\x7d\xed\x53\x2f\x93\x49\x3b\x1c\x08\x14\x08\x16\x58\x07\xb3\x8e\xfc\xe7\xac\xc7\xdb\xc2\x16\xbe\xf7\x4e\xd5\x9e\x20\x97\x33\x26\x55\x3c\xc8\x37\x79\xf7\x42\xe3\xf4\x69\xa7\x27\x8e\xeb\x15\x37\xdd\x71\xcd\x8f\x15\x11\x4d\x84\x69\x3c\x2e\x6b\xbf\x62\x81\x4a\x08\xe8\x2b\xa7\x15\x39\xf4\xcb\x4b\xf0\x8c\x86\x9d\x7d\xb9\xde\xa9", p, q, g, b"\xc9\x00\x39\x95\xb0\x14\xaf\xad\x66\xde\x25\xfc\x0a\x22\x10\xb1\xf1\xb2\x2d\x27\x5d\xa5\x1a\x27\xfa\xac\xda\x04\x2f\xd7\x64\x56\x86\xec\x8b\x1b\x62\xd5\x8d\x8a\xf2\xe1\x06\x3a\xb8\xe1\x46\xd1\x1e\x3a\x07\x71\x0b\xc4\x52\x12\x28\xf3\x5f\x51\x73\x44\x3b\xbf\xd0\x89\xf6\x42\xcd\x16\x64\x1c\x57\x19\x9c\x9a\xb6\xe0\xd9\xb0\xc0\x19\x31\xc2\xd1\x62\xf5\xe2\x0d\xbe\x73\x65\xc9\x3a\xdc\x62\xfd\x5a\x46\x1b\xea\x59\x56\xd7\xc1\x1a\xc6\x76\x47\xbe\xdc\xea\xd5\xbb\x31\x12\x24\xa4\x96\xaa\x15\x59\x92\xae\xe7\x4e\x45\xad", b"\x17\xcc\x53\xb5\xb9\x55\x8c\xc4\x1d\xf9\x46\x05\x5b\x8d\x7e\x19\x71\xbe\x86\xd7", b"\x00\x3c\x21\x50\x39\x71\xc0\x3b\x5e\xf4\xed\xc8\x04\xd2\xf7\xd3\x3f\x9e\xa9\xcc", )?; test( HashAlgorithm::SHA384, b"\x65\xa3\xc9\x24\x53\xf9\x61\xde\x7f\x57\x6d\x5a\x1e\x31\x06\xc3\x8b\x7f\x20\x81\x39\x94\xb5\xdd\x20\x15\x46\xdc\x45\x50\x65\xdd\xe5\x9e\xdc\xd8\x4d\x0f\xa1\x7a\x85\xc0\xf9\xf9\x91\x71\xd6\x7a\x34\x47\x5c\xef\x4f\x31\x19\x51\xf2\xee\xf7\xf6\xb6\x4a\x5b\xbc\x6d\xa6\xd1\xb6\x22\x48\x0c\xde\x56\xa0\x7a\x77\xaa\x60\x40\xeb\xc1\xfc\xb2\x65\xb3\xb6\x24\x88\x1f\xd2\x72\x03\xdc\xfe\x8a\x12\x49\x21\x98\x47\x4a\x99\x0c\xb9\xf3\x4a\x19\x43\x35\x6f\xde\x5b\xce\x3f\xd8\x35\x16\xda\x8b\xf7\x80\xf8\xcb\x18\x51\xb3\xb9\x54", p, q, g, b"\x0f\xc5\x14\xca\x16\x0f\x34\xf2\xf6\xed\xe1\xba\x59\x14\xd5\x84\x4c\x9d\xe5\x14\x20\x8c\x72\x56\x9a\x0b\x36\xec\x92\xc8\xb2\xc8\xfd\xfb\x7d\x68\x12\x74\x86\xe5\x8a\x04\xa3\x2d\x0d\x15\x0e\x51\xbb\x05\xe6\x66\x24\xcb\x62\x2e\xda\xe1\x9a\x6b\x4b\x1d\x83\x17\x68\x9b\xaa\xfa\x30\x16\x8e\xf3\x75\x9e\xe8\x2e\x61\x4e\x47\x61\x90\x01\x82\xdf\x90\xe9\xcd\x2d\x93\x11\x53\x77\x1b\x8b\xe3\x0d\x89\xc2\xfb\xb9\x5b\xe7\xe0\x5a\x4b\x29\xda\x96\x8f\xfe\xbb\xda\x5c\x0c\x98\x39\x35\x4b\xb5\x9d\xc6\x97\xa2\x69\x06\x3f\x2f\x50", b"\x77\xff\xaf\x42\x90\xc4\x1e\xb0\x89\xc1\xd7\xbe\x5c\x8d\x38\x33\x02\x77\x02\xef", b"\xcb\x75\x3a\x2d\x4c\xe0\xe5\x98\x51\xf8\x14\x77\x9f\x34\x3b\xeb\x61\x5f\x27\x70", )?; test( HashAlgorithm::SHA384, b"\x15\x26\xb6\x4c\xe4\x1c\xc8\xe2\xce\xf2\x6f\x37\x06\xbe\x53\x0a\x36\xac\x9c\xd1\x6f\xf6\x9f\x05\x77\x3e\x94\x47\xed\x94\x52\x06\x4b\x77\x51\xf3\xa6\x49\x19\xbf\xa3\xa7\xe1\x02\x0d\xfc\x17\x5a\x10\xac\xfd\xf0\x96\xfd\x41\xc0\x33\x72\xe4\xd2\xab\xd7\xba\x88\x7e\x00\x76\x71\x6c\xe9\x55\x2f\x2c\x7c\x8e\xdd\xb1\xb3\xfc\xa1\xbd\xcd\x23\x30\x0c\xe2\xb1\x67\x7d\x4a\x2d\xeb\xea\xa7\x05\x34\x66\xe5\x9b\x09\x87\x71\xbf\xb9\x21\x8e\x0f\xb4\xab\x6b\x74\x18\xab\xeb\xcc\x34\xd6\x81\xe1\x4c\x4a\x89\x75\x00\x0d\x83\xbb\x44", p, q, g, b"\xd3\x0e\xed\x73\x9f\x46\x47\x93\x64\xd4\xc2\xbe\xc1\x8c\xf4\xc7\x5c\x32\x4f\x8d\xb8\x18\x4d\x9c\x3c\x17\x55\x56\xa0\x0a\xcf\xb0\xa6\x81\x38\x87\xb6\x87\x06\xe7\x0c\x16\x7f\x40\x63\xbc\x00\x46\x39\x6b\xa1\xbb\x32\x26\xc2\x92\x21\xbd\x64\xec\x4c\xeb\xc9\x90\xa7\xb4\x04\xe2\x6e\x2c\xf0\x42\x30\x4a\x7c\x57\xab\x7d\xe4\x18\xba\x67\x1e\x17\xf7\xf5\x02\xb9\xe1\xbb\x59\x84\x46\x9b\x30\x4e\xbc\x0c\x3c\x3a\x5a\x69\xcf\xf7\xab\xff\x41\x10\x13\x03\x16\x65\x1e\x0f\x93\xeb\xd2\x83\x4d\xd0\x44\xea\xe1\xfd\x6f\x04\x51\x02", b"\x31\xab\xe8\xe7\x45\x8c\xe3\x63\xa5\xf3\x98\x51\x11\xb2\x39\xbc\x8d\xf8\xdc\xb9", b"\x1d\x96\x7b\xe0\x11\x61\x28\x69\x9d\x16\x7f\xc1\x6e\x5e\x92\x0a\x41\x31\x16\x69", )?; test( HashAlgorithm::SHA384, b"\xd7\x85\x2e\xe9\x0b\x3f\x11\x20\xbb\x11\x24\x98\x08\xc7\xe7\xbe\x14\xfe\x57\x7b\xff\x18\x86\xbe\x3c\x42\x58\x9a\x6e\xeb\x06\xa1\x83\x41\x10\x86\x2b\x65\xd2\x6c\xc5\xa2\xe5\xd9\x03\xed\x24\x32\x8d\x68\x4c\x96\xe3\xba\xbb\x37\xae\x31\xf9\x6d\x32\xf5\x76\x57\xa3\xbd\x77\x98\xaa\xfa\xe8\x6f\x44\xad\x89\x81\xe7\xcd\x47\xd7\xf3\x1b\xb4\x56\x4a\x75\x7c\x92\x5c\x64\xda\x98\x20\x96\x3c\x1c\x51\x48\xf5\x89\xd6\x39\x30\x04\xa6\xa5\x8a\xa2\xc8\xa5\x78\xf4\xdb\x75\x95\xf8\x86\x17\x0e\x79\xe9\xd5\x7b\xf7\xff\x8f\xd0\xa7", p, q, g, b"\x0d\xd3\x79\x85\x16\x3f\x93\x61\x8f\xde\xa8\xe3\x97\x54\x19\xfc\xf7\x44\x6f\xf9\x80\x85\x1e\x18\x93\x2d\x74\x94\xf8\x09\xc0\xae\x9c\x03\xcc\x39\x77\x9f\xf0\x42\x2c\xb2\x24\x8a\xe1\x98\x6f\x9a\xad\x2a\x43\xd6\xfa\x68\x78\xd2\x44\xb4\x29\xaa\xc5\xea\x80\x15\x79\x80\x57\x7e\x5b\xa0\xd1\x1b\x1f\xa3\x40\xa2\x83\xfa\x0a\x2d\x65\x1e\x02\x43\x31\xe6\xbb\xe7\xd0\x1a\xc0\x34\xdb\x37\xb0\x08\xb9\x1f\x9f\x88\xd1\x35\xfa\xd2\x3a\xf8\xc2\x27\x65\xd8\x33\xa9\xc9\xef\xf7\xac\xcf\x66\x8e\x17\xf9\xa8\xbd\xf5\x93\x17\xc2\x02", b"\x44\xc2\xd6\x50\x98\x74\xac\xe7\x1a\xcd\x1d\xcc\x32\x33\x5b\x39\x4c\x4e\x41\xe0", b"\x37\xe7\x8f\x13\xae\xc0\x52\xeb\x7b\x07\xa8\xb9\xf6\xd5\x4d\xbc\x77\x82\x90\x06", )?; test( HashAlgorithm::SHA384, b"\x9a\xb9\x14\x48\xa0\xdc\x96\x94\xbe\x17\x3c\xe6\xd9\xb5\x22\xce\x0e\x2f\x75\xfc\xb5\x77\x20\xfc\x5e\xb8\xf9\x2d\x8f\xb0\xe1\x95\x03\x00\x63\x96\x89\x25\xa5\x68\x63\x6f\x4a\xea\x1e\xdf\x6c\x5f\xcb\x86\xdc\xed\xd2\x04\x53\x9d\x8c\x29\x17\x57\xfb\x8a\x51\x62\x0a\xbd\xa5\x9a\xa8\xf8\x50\x2e\x69\x04\xbc\xe0\x66\x7d\x92\xc8\xcb\x3f\xcf\x1a\x61\xb1\xfb\x0b\xb4\xe9\x38\x3b\x37\xeb\x46\x9b\xd5\xc2\xf5\xa7\x76\x80\xda\x62\xf9\x07\xc2\xe2\x63\xcb\x48\x40\x2b\x4b\x12\x98\x5e\xaa\xb9\x04\x51\x88\x5e\x81\x9b\x3e\x8c\x3a", p, q, g, b"\x49\xd7\xf0\x8f\xde\x0a\x83\xcf\xb8\x11\x6c\x9b\x7c\xdc\xab\x29\x75\x1f\xca\x5f\xfe\x31\x07\x60\xfe\xa7\x13\xc3\x0e\x95\xe7\x75\x5e\x65\xce\x60\x92\x88\x93\xc6\x50\x20\xee\x9b\x61\xf6\xc9\xc8\x9c\x07\xe0\xfc\x50\x3b\x7b\x03\x13\x68\xf0\x69\x57\x8a\x9e\x6b\x45\x1f\xef\x36\x9e\xf9\x0c\x26\xdd\x66\x0e\xe1\xa6\xb8\xb7\x14\xd1\xcc\x28\x24\x5e\x9f\x13\xf1\x87\x12\x2d\xe2\x6a\xc2\xfb\xf5\xbc\xcb\x7c\xaf\xf5\x9f\x1d\xe9\x10\x55\x11\x04\xd3\xa0\xe8\xfa\x9f\xe6\xb7\xea\xcc\x9a\x5f\xd5\x56\xb7\xbf\x71\x39\xd6\xed\xf9", b"\x95\xda\x25\xd0\x6f\xf9\xc0\x2b\xc8\x93\xfb\x03\x25\x08\x30\x4c\x17\xeb\xcf\x08", b"\x61\x7a\xdb\x8d\xe1\x0d\xa1\xa8\x74\x13\xd6\x44\x66\xb4\x82\x40\x9d\x27\xbc\xe7", )?; test( HashAlgorithm::SHA384, b"\xc9\xc0\xe6\x9f\x84\x0c\xb6\xde\xb9\x84\xc2\x57\x5d\x7f\x68\x16\xfa\x35\xaf\x03\xb4\x42\x9c\x70\x3a\x5a\xec\x90\xe7\xcb\x26\xe5\x24\x13\x58\x7f\x3b\xc5\xa0\x77\x2b\xe7\xb5\xe5\x89\xc9\xa7\x60\x71\xc1\x73\x98\x33\xf4\x61\x1f\xa9\x51\xd3\x75\x82\x0b\x48\xd7\x40\x62\x6c\x66\x55\x34\xd6\x04\x87\xbf\x3e\x0a\x84\xeb\x63\x89\xe0\x99\xfe\x62\x1f\x26\x94\x91\xc3\xb8\x94\x2e\x03\xbb\xad\x2a\x52\x20\xca\xf5\x1e\x7b\x4a\x26\x50\xe4\xb3\x00\x02\x4a\x0a\x96\xf0\x86\x1b\x32\x06\xff\xfc\xa8\x3d\x08\x50\xf2\xa3\xe2\xa0\x6c", p, q, g, b"\x26\xf7\x32\x19\xd0\xe7\xdd\x3a\x80\xe7\xfb\xc0\x79\xd9\xba\xad\x45\x12\x89\x1a\xad\xfd\x24\x16\xb1\x85\x9f\x41\xad\xac\x31\x17\x1e\xc6\x24\xd8\xa4\xd6\xa1\x0d\x5d\xe1\xb9\x39\x59\xbc\x49\x95\x3f\x23\x49\x2f\x18\xab\x76\x5f\x96\x3a\x98\x58\x48\x07\xd6\x66\x29\xe5\xa1\xe0\x57\xd7\x7d\x42\xe3\x36\x34\x58\x64\x1a\x04\x69\x16\x6a\x0d\x85\x3b\x27\x79\x8b\xd8\x48\xaa\x0d\x3c\xcd\xbb\x40\xfa\x21\xb9\xfe\x62\x82\x4c\xb2\xc7\xcc\x62\x42\x59\x78\xe6\x72\xaf\xf0\xbb\xd8\xc8\xcd\x08\xe4\x63\x85\xb0\xd6\x21\x9d\xc5\x6e", b"\xb6\xb2\x5a\x9d\xa1\x10\xb5\xd5\x76\x75\x88\x9e\xae\x75\xab\x58\xa4\xd8\xe2\x81", b"\x5a\x60\xc2\xb0\xad\xbe\xa4\xc5\xbe\x06\x5b\xbd\x0f\xd0\xe3\xce\x4b\xf2\x92\x00", )?; test( HashAlgorithm::SHA384, b"\x40\x02\xde\x82\x5b\xb8\x7a\xc3\x46\xbd\x84\x87\xcf\x6b\xe0\x53\xcb\x30\xee\x67\xc6\x64\x34\x21\x71\x07\xa8\xb0\xb5\x2e\x57\x26\x90\x06\x15\xed\xd2\xfd\x0a\xcd\xf8\x8a\x7e\x65\xe7\xdd\x3b\xa6\xab\xbb\xb3\x71\xa1\xc8\x40\x25\x0d\x9c\xe8\x09\xe7\xb1\x11\x1f\x16\xda\xf5\x19\x42\x11\x71\x5f\xf5\xfe\x63\x1e\x37\x84\x08\x74\x98\x48\xa0\xc8\x1a\x28\x9b\x43\x38\xbc\xcd\x8d\x10\x53\xf8\x63\x19\x7a\xd0\x29\x20\xfc\xbc\xa5\x14\xe2\xdf\xd9\x4a\x8b\x00\xf9\x0c\xf0\x34\xad\xfd\x77\x6f\x4d\xca\xef\x2c\x8d\xce\x3b\x05\x39", p, q, g, b"\x14\x9b\xcb\xb4\xf5\x98\x3d\xb5\x6f\xbe\x99\x8f\xcd\x02\xd7\x36\xe6\xd2\xf1\x8f\xcf\x96\x46\x8c\xd7\xe9\x9b\xc6\x47\x43\x6f\xbd\x74\xfd\x7a\x2c\xc2\xf0\xd8\x86\x69\x52\xb9\x7b\x44\xff\x64\x4b\x56\x65\xcd\x10\x65\xb0\x7a\x2c\x33\xd9\x15\x1d\xeb\x33\x5e\x35\x22\xc1\xb7\x7d\xa1\x44\x3a\x13\x73\xc9\x3b\xfa\x04\x0d\xa5\xa1\x35\x3b\x88\xa7\x8e\x3a\x5a\x08\x4e\x6c\x44\x2d\xb0\x3f\x7f\xbb\x4b\xdb\xd3\x0b\x1a\xf3\x96\x3f\x8c\x5d\x3e\x83\x45\x32\x94\xe3\xa0\x7d\xda\xcf\xd4\x3d\xc8\xf9\xe8\x30\x32\xfe\xf7\x84\x20\xc4", b"\xd3\xcd\xe1\x70\xd8\x21\x54\xec\x1b\xbd\x90\x77\xc4\x86\x97\x11\x20\x60\x03\x76", b"\xb0\x08\xfc\xd0\x1b\x5e\x49\xa8\x5a\x92\x1b\xee\x1d\xdd\x70\x62\x12\x79\x90\x86", )?; test( HashAlgorithm::SHA384, b"\xf7\x01\x8f\xf0\xaf\x67\x76\xed\x42\x34\xc1\xfb\x9c\xca\x1f\x8c\xff\x31\x29\x5c\xb9\xf7\x6d\x8b\x73\x89\x84\x30\x09\x7c\x49\xa4\x00\x28\x44\x17\x71\xea\x1d\xe0\x8f\xfd\x5c\xec\x7e\xaa\x59\xe3\x2b\x3a\x17\x03\x29\x13\x92\x27\xba\x86\xe0\xc5\xef\xca\xee\x38\x2b\xff\xf9\x62\x24\x9d\xa8\x53\xde\xe4\x18\x41\x3f\x20\x1a\x28\xfe\x45\xb8\x29\x3c\x26\x20\x89\xd2\xce\xeb\x9a\xf6\x75\x29\xab\x01\x1f\x04\xf5\xee\xaf\x82\xba\x32\xdc\xe9\xa9\x82\x17\x62\xc3\x35\x1b\x00\x20\x65\x91\xa3\xf8\x7c\x52\x60\xa4\x26\x36\x59\xf0", p, q, g, b"\x6c\x20\x6e\x71\xfe\xd8\xb3\x63\xfc\xf5\x71\x78\x6c\xe1\xb4\xe5\x2a\x40\x4d\xe7\xed\xa7\x03\x1e\x5d\x93\xa4\x7e\xa6\x68\xde\x43\xdc\x70\x73\xe3\x1d\x3b\x6b\x12\x5a\xe3\xe8\xee\x45\xae\xd2\x73\xbc\x87\x8c\x73\x42\x3b\x22\x5a\x15\x26\xbb\xb1\x49\xa0\xce\x5e\x9a\x2d\x29\x62\xbd\x6d\x33\x23\x75\x86\x0f\x84\xce\x0e\x78\x7a\x0a\xf9\x3f\x44\xe6\x4e\xda\xa2\xdc\xe6\xca\x22\xbc\xc6\xd4\x8b\x84\xb0\xaf\xfb\xa3\x42\x75\x3b\x18\x42\x94\x10\x67\xd5\xb8\x41\x4c\x35\x61\x38\xe6\x25\xbb\x50\x65\x66\xa2\x7b\x33\x50\x94\xb0", b"\x07\x9b\x08\xbc\x01\x6c\x54\x3d\x09\xd6\xb2\x76\xc0\x23\x34\x7a\x3a\xac\xe9\xae", b"\x16\x4c\x3c\x38\x0f\x20\x9f\xea\xf8\xff\xcf\x53\x69\x1e\xe3\x03\x1c\x3b\x3f\xff", )?; test( HashAlgorithm::SHA384, b"\x4a\x18\xbd\xcc\xcd\x46\xbb\x89\x56\x7c\xeb\x9c\x1e\x2e\x50\x0a\x3b\xae\xd2\x4f\xf2\xc5\xfc\x7f\x83\xcb\x3c\xf6\xa6\xf3\x88\x59\xa1\xa9\x27\xfa\xb5\xe2\xfd\x7e\xa1\xe1\xa4\x15\x47\x39\x30\x1c\xb1\x95\x77\x09\x10\x3a\xf8\x86\xc9\x29\xcf\x88\xd2\x5c\xed\x5c\xd6\xf8\xcf\x3f\xfe\xe7\xb0\x88\xed\xc2\xf6\xab\xd1\x11\x43\x98\xa3\xab\x00\xfc\x21\xbe\xc0\x2e\x8e\x53\x9b\xa1\x2d\xf7\x0a\x58\x7f\xbf\xba\x63\x19\x5c\x64\x49\xb2\xb8\x49\x54\x7c\x42\x27\x78\x34\xe1\xec\x08\x6b\x5e\x53\xd9\x49\x84\x67\x69\xe8\x97\x15\xbf", p, q, g, b"\x8e\x66\x8d\xd1\x52\x7b\x5d\x1e\x56\xaa\xe4\xa6\xca\x22\x5e\x67\x73\x68\x41\x23\x24\xa7\x9d\x98\xbf\xda\xd9\xa8\x4d\x9f\x87\xc1\x35\x75\x18\xc9\xa5\x05\x6e\xa6\xa0\x88\x2e\x94\xd4\xff\xad\xd9\x7d\x89\xbc\xf2\xf3\x2f\xf4\x42\xb2\x5d\xd2\xaf\x2a\x78\xdd\xad\xe4\x6b\x75\xaa\x8a\x5b\x1a\x14\x71\x76\x4a\xb6\x99\xd7\x00\xcb\x2a\x28\xb9\x59\xa3\x84\x8e\xdb\xbd\x6c\x95\x14\xee\x84\x9f\x83\x3c\x43\x00\x85\x31\x36\x5a\x01\x54\x1f\x9c\x0b\x65\xd5\xe7\xd3\xc2\x1d\xc8\xbe\xf1\x36\x9a\x41\xc0\x40\x5f\x37\x23\xf6\x79\x10", b"\xb2\x2c\x00\xfe\x0b\xc2\xfa\xe7\xa4\xab\x74\xed\xcd\x49\x6c\x64\xa9\x99\xc7\xd3", b"\x85\xba\x8d\xbb\xc9\x3a\xb9\x4a\x76\x13\x3d\x47\x9e\x3f\x79\x57\x69\x44\xe6\xca", )?; test( HashAlgorithm::SHA384, b"\x75\x47\x47\x11\x82\x17\x66\xb0\x65\xe2\x44\x86\x01\xe8\x2b\x88\x15\x3a\x41\xbf\xb5\xc6\xb6\xa9\xdd\xcf\x73\x17\x0e\xe3\x74\xa6\x62\x5d\xe1\x9c\x56\x0b\xcb\xd2\x02\x0b\xfe\xab\x5c\xbf\xad\x8f\xc6\x0c\xcf\xc9\x5a\x1b\x94\xfb\xef\xdf\x81\x5d\x9b\xfc\x43\xfa\x59\x31\x5e\x70\x93\xd5\x68\x52\x74\xb8\xaf\xc3\x13\x9b\x92\x5e\xbf\x69\x7f\xe2\x69\x9b\x0f\xeb\x1e\x42\xbc\xa6\x5e\x5d\x4e\xb0\xb4\x51\x4a\xf9\x2d\xfa\xb8\x5e\x7f\x26\x66\xc8\x7e\x97\x89\x39\x5f\x35\x4c\xe3\x39\x38\xe9\x62\x30\x61\x11\x34\x65\xa4\xe2\xb9", p, q, g, b"\x74\xa9\x3c\x73\xd7\x55\x00\xca\x43\x05\xca\x31\x84\x47\x5b\x53\xd9\x6c\x6f\xdd\x41\x7e\xf2\x3d\x9d\xc6\x1b\x80\xbb\xc1\x10\x82\x28\xd2\x54\x3c\x1c\x3a\x9f\x2e\x77\x83\xca\x69\xb0\x19\xc0\xcd\x9a\x6d\x2b\x62\xb0\xed\x93\xd4\x22\x9d\xa8\x7b\xfc\x21\xf9\xe4\xbd\x0d\xea\x2c\x4e\x6d\x4d\x2f\x88\x20\x1a\xb0\x50\x4b\x31\xf4\xef\x15\x58\xad\xf4\x93\xe4\x70\xad\xfc\x57\x2c\xa6\x8d\xeb\xc4\x61\x23\x58\x9a\xe9\x13\xb9\x67\x98\x3d\xbc\xac\x6b\xf3\xbd\x86\x11\x13\x7e\x39\xd5\x87\x00\x57\xae\x18\xcb\x84\xa7\x6a\xae\x30", b"\x1e\xd1\x31\xc9\x6a\x2c\x31\x0e\x1f\x79\x76\xd3\x08\x2a\x69\xa5\xaf\x45\xbd\xd0", b"\x70\x66\x3e\x9a\xd7\x11\x3a\xe5\x7d\x4a\xf6\x90\x77\x12\xe0\xaa\xf8\x8b\xc0\x7a", )?; test( HashAlgorithm::SHA384, b"\x34\x0d\xf7\x08\xd4\x57\xdf\x94\x13\xef\x2b\xda\x22\x5c\x5f\x55\x8b\x90\x96\x6c\xdd\x53\x1a\x0b\x5a\xa7\x45\xd5\xc3\xea\x79\x0d\xeb\xea\x22\x48\x61\xef\x12\xfb\x16\x38\xbf\xf0\x12\x1f\xf2\x6d\xbd\xcf\xfc\x29\x9b\xf9\xf3\xa9\xc1\xfe\x60\x27\x40\x0f\xf1\x4c\x34\xfb\x06\xf6\x7d\xb9\xc3\x0a\x1d\xcb\xfd\x99\x69\x03\x52\x3d\x85\x04\x63\x82\xff\x28\x04\x18\xd9\x74\xa3\xec\xe6\xb5\xfa\xfe\x30\x5e\x2e\x79\xb1\xd0\x7a\x7c\x1e\xeb\x7a\x12\x77\xa8\x22\x82\xbe\x62\x83\x1d\xf7\xfe\xe3\x88\x41\x46\x26\x02\x98\x6a\x8e\x9b", p, q, g, b"\x61\x50\xa6\x8d\x64\xad\xda\x3d\x3f\xb5\xa9\x73\xc6\x2b\x99\x2a\xd3\xfe\x53\x8a\xf7\x16\x1b\xae\x41\xea\x2f\x17\x99\x30\x4f\xe5\xb8\xc8\x64\xe0\x61\xd1\x33\xd9\x4c\x16\xa4\xc6\xb0\xed\x8d\xff\xdf\x2c\xef\xa7\x39\x40\x15\xe7\x5c\x57\xb1\x81\x41\x9d\xcf\xef\xe3\x40\x9d\x3b\x53\xd8\x69\x11\xc7\x49\xf9\xf2\x8f\x7c\x1d\xe9\x9f\x7e\x4b\x2e\xa2\x2a\x48\x81\x7a\xce\x4f\xd9\x97\x4f\xa5\x3b\x8d\x4f\x05\xf5\x73\x14\x88\x81\x38\x03\xd7\xf3\xaa\xf1\xcf\xa1\x38\xbc\x73\xc4\xd2\x7c\xa1\x62\x1e\x92\x26\x66\x18\x83\xe9\xdd", b"\x4f\x18\x2a\xd4\x2c\xb5\x67\x1d\x31\x62\xbb\x9d\x04\xa0\x6c\xd2\x0e\xdb\xc5\x58", b"\xa6\xc5\x41\x79\x47\x44\x77\x18\xed\x1c\xb8\x9a\x6e\xfc\xe2\xd3\x11\x6e\x50\xd1", )?; test( HashAlgorithm::SHA384, b"\x9f\x23\xc8\x25\x63\xab\x7c\x0b\xa8\x6b\xbb\x98\x93\x35\x00\x0a\x49\x3b\x29\x1e\x5d\xc1\x7c\xe7\x29\x49\x49\x58\x90\x36\x23\xed\x99\xdf\x34\x42\x30\xff\xb6\x26\xb1\xdb\xef\xcc\xe0\x59\xae\x16\xc2\xee\x7e\xe6\xfd\x2a\x78\x07\x33\x6c\xb7\x1b\x88\x53\xe2\xed\x3b\x74\xb2\xfa\xac\x82\xa8\x31\xd5\x3e\x03\xd7\xbb\xb9\x6d\x38\xdf\x98\xfd\x19\xbd\x4c\x1a\x62\x48\xcd\x50\x7c\x89\xf7\x99\x5f\x59\x57\x9a\xfe\x53\x19\x73\x1b\x44\x3d\x68\x71\xe5\x58\xf5\xb7\x7f\x2f\x9a\x4d\xd9\x9e\xfb\x30\x5e\x27\x91\x65\x94\x52\x4e\x02", p, q, g, b"\x96\xd7\x45\x11\x81\xfb\x25\x3f\xbc\x3f\x44\x14\x09\xbe\x5e\x5e\x01\x44\x97\x26\x10\xe3\x7f\xa8\x2b\xc2\xaf\x24\x66\x37\xa4\xc9\x18\x02\x30\x97\x87\x52\x55\xa2\x17\xea\x89\x5d\xad\xdf\x46\xbf\xbb\x17\x47\x49\xb0\x4c\x59\xfe\xfa\x62\x89\x68\x4f\x2f\x9a\xea\xdf\x5c\xe7\xca\x47\xf0\x03\x2e\x38\x4b\x7d\x50\x59\x79\x01\x18\x15\x01\xcb\x59\x15\xfb\x46\x86\xa6\xad\x7b\xcd\x5b\x46\x86\x24\x11\xa4\xdf\x22\xb1\xed\x2a\x56\x90\x5e\x07\xc0\xa9\x36\xc9\x94\x42\x13\x19\x4e\xbe\xfd\x4e\xc6\x85\x97\xcc\xa0\x36\x33\x8b\x3c", b"\xb6\x59\x9f\xbd\xdb\x48\x56\x27\x6d\xf4\x48\xcf\x09\xd6\x2f\xd7\x65\x7d\xe6\xc3", b"\x4b\x49\x58\x90\x99\xbe\x55\x78\x32\x2d\x82\x9b\x87\xb4\x3a\xc0\x7f\x62\xe3\x5d", )?; // [mod = L=1024, N=160, SHA-512] let p = b"\x88\xd9\x68\xe9\x60\x2e\xcb\xda\x6d\x86\xf7\xc9\x70\xa3\xff\xbe\xb1\xda\x96\x2f\x28\xc0\xaf\xb9\x27\x0e\xf0\x5b\xc3\x30\xca\x98\xc3\xad\xf8\x3c\x07\x2f\xeb\x05\xfb\x2e\x29\x3b\x50\x65\xbb\xb0\xcb\xcc\x93\x0c\x24\xd8\xd0\x78\x69\xde\xae\xcd\x92\xa2\x60\x4c\x0f\x5d\xd3\x5c\x5b\x43\x1f\xda\x6a\x22\x2c\x52\xc3\x56\x2b\xf7\x57\x1c\x71\x02\x09\xbe\x8b\x3b\x85\x88\x18\x78\x87\x25\xfe\x81\x12\xb7\xd6\xbc\x82\xe0\xff\x1c\xbb\xf5\xd6\xfe\x94\x69\x0a\xf2\xb5\x10\xe4\x1a\xd8\x20\x7d\xc2\xc0\x2f\xb9\xfa\x5c\xef\xaa\xb5"; let q = b"\xa6\x65\x68\x9b\x9e\x5b\x9c\xe8\x2f\xd1\x67\x60\x06\xcf\x4c\xf6\x7e\xcc\x56\xb7"; let g = b"\x26\x7e\x28\x28\x57\x41\x77\x52\x11\x3f\xba\x3f\xca\x71\x55\xb5\xce\x89\xe7\xc8\xa3\x3c\x1a\x29\x12\x2e\x2b\x72\x09\x65\xfc\x04\x24\x52\x67\xff\x87\xfc\x67\xa5\x73\x0f\xe5\xb3\x08\x01\x3a\xa3\x26\x69\x90\xfb\xb3\x98\x18\x5a\x87\xe0\x55\xb4\x43\xa8\x68\xce\x0c\xe1\x3a\xe6\xae\xe3\x30\xb9\xd2\x5d\x3b\xbb\x36\x26\x65\xc5\x88\x1d\xaf\x0c\x5a\xa7\x5e\x9d\x4a\x82\xe8\xf0\x4c\x91\xa9\xad\x29\x48\x22\xe3\x39\x78\xab\x0c\x13\xfa\xdc\x45\x83\x1f\x9d\x37\xda\x4e\xfa\x0f\xc2\xc5\xeb\x01\x37\x1f\xa8\x5b\x7d\xdb\x1f\x82"; test( HashAlgorithm::SHA512, b"\x3a\x84\xa5\x31\x4e\x90\xfd\x33\xbb\x7c\xd6\xca\x68\x72\x0c\x69\x05\x8d\xa1\xda\x1b\x35\x90\x46\xae\x89\x22\xca\xc8\xaf\xc5\xe0\x25\x77\x16\x35\xfb\x47\x35\x49\x15\x21\xa7\x28\x44\x1b\x5c\xb0\x87\xd6\x07\x76\xee\x0e\xcc\x21\x74\xa4\x19\x85\xa8\x2c\xf4\x6d\x8f\x8d\x8b\x27\x4a\x0c\xc4\x39\xb0\x09\x71\x07\x7c\x74\x5f\x8c\xf7\x01\xcf\x56\xbf\x99\x14\xcc\x57\x20\x9b\x55\x5d\xc8\x7c\xa8\xc1\x3d\xa0\x63\x27\x0c\x60\xfc\x2c\x98\x8e\x69\x2b\x75\xa7\xf2\xa6\x69\x90\x3b\x93\xd2\xe1\x4e\x8e\xfb\x6f\xb9\xf8\x69\x4a\x78", p, q, g, b"\x60\xf5\x34\x1e\x48\xca\x7a\x3b\xc5\xde\xce\xe6\x12\x11\xdd\x27\x27\xcd\x8e\x2f\xc7\x63\x5f\x3a\xab\xea\x26\x23\x66\xe4\x58\xf5\xc5\x1c\x31\x1a\xfd\xa9\x16\xcb\x0d\xcd\xc5\xd5\xa5\x72\x9f\x57\x3a\x53\x2b\x59\x47\x43\x19\x9b\xcf\xa7\x45\x49\x03\xe7\x4b\x33\xdd\xfe\x65\x89\x63\x06\xce\xc2\x0e\xbd\x84\x27\x68\x2f\xa5\x01\xee\x06\xbc\x4c\x5d\x14\x25\xcb\xe3\x18\x28\xba\x00\x8b\x19\xc9\xda\x68\x13\x6c\xf7\x18\x40\xb2\x05\x91\x9e\x78\x3a\x62\x8a\x5a\x57\xcf\x91\xcf\x56\x9b\x28\x54\xff\xef\x7a\x09\x6e\xda\x96\xc9", b"\xa5\x3f\x1f\x8f\x20\xb8\xd3\xd4\x72\x0f\x14\xa8\xba\xb5\x22\x6b\x07\x9d\x99\x53", b"\x11\xf5\x3f\x6a\x4e\x56\xb5\x1f\x60\xe2\x0d\x49\x57\xae\x89\xe1\x62\xae\xa6\x16", )?; test( HashAlgorithm::SHA512, b"\x6f\x39\x97\x3f\xd2\x25\x16\x7a\x76\x73\xcd\x71\xab\x35\x34\xd2\x68\x66\x87\xc3\x32\xf9\x3f\xd6\x6d\xb5\xf1\xca\x99\x67\x8e\xfd\x28\x25\xa8\x4c\xd7\xa7\x10\x7a\xdf\x96\x50\x1d\xd1\xd0\x5e\x7b\xbc\x8d\x11\x3e\x08\x7b\xba\x77\xb2\x34\x6b\x43\x64\x13\x21\x25\x24\x5e\x9a\xac\xe3\xa1\x46\xb5\x76\xf6\x54\xc8\x6e\x07\xfc\x19\x14\xca\xfa\x20\x9d\xd6\xd0\x48\x45\x57\x5d\xbb\x27\x9c\xd1\xb2\x32\x96\xd0\x1e\xf5\x05\xb5\xe1\xce\x7f\x21\x94\xf1\x89\x88\xf3\x55\xc9\xb3\x4f\x92\x0a\xb3\x51\x52\xe0\x3b\xcf\x79\x2a\xc5\x29", p, q, g, b"\x11\x0e\x39\x8e\x36\xc9\xd2\x72\x6e\x77\xde\xb4\x65\xdd\x23\x30\x3f\x9e\x38\x77\x78\xb5\x49\x70\x0a\x52\xb9\xa5\x46\x85\x12\xee\x37\x7c\xe3\xd7\xdc\xbf\xc6\xb6\x4e\xe3\x53\xea\xc6\xa4\x38\x98\xb2\x64\x84\x05\x8b\xa1\xb2\x4b\x22\x9c\xd6\x9c\x99\x4d\x97\x6d\x43\x34\x4c\x18\x1e\xa6\xc4\x7d\xf0\x06\x2c\x09\xa1\x6b\x23\xab\x60\x75\xc0\x4a\x08\x99\xba\xc3\xe4\xf0\x34\xa9\x83\xbf\x90\x43\x8f\x6a\xc2\x68\x55\xd8\xa5\xfd\xed\x90\x17\x2e\x7e\x8c\x19\x6a\x2c\xe7\xe1\xfc\x0d\xac\x94\x27\x8a\xff\x16\x53\xc3\xae\x09\xf5", b"\x1b\x9e\xd3\x9b\xcc\x4b\x46\xed\x00\x07\x67\x9c\xe9\xc3\xf6\xdc\x7c\x41\x57\xb9", b"\x25\x8d\x41\x36\xad\x95\xb7\x04\xa7\x95\x9d\x04\x09\x6d\xcd\x78\x1e\xb5\x4b\xde", )?; test( HashAlgorithm::SHA512, b"\x7f\x59\x74\x4c\x79\x0c\x0f\x98\x5a\x9a\xe1\x01\xd9\xfa\x00\xda\x3b\x95\xd2\x47\x3d\x79\x28\x05\xec\x1d\x6d\x1e\x95\x22\x2a\x6f\x30\xee\x6a\xb8\xfc\x5a\x63\x20\x57\x15\x3f\x23\x7a\xd3\xaa\x2f\xae\x8f\x1e\x51\xea\xe7\x59\x06\xd0\x7e\x57\x6d\xd0\x02\x1a\xc1\x71\x1b\x1c\x88\x53\xe6\x2d\x27\xfe\x6b\x09\x87\x66\xb8\xce\x3e\x76\xd3\x47\xc8\xe4\x9b\xe0\xab\x05\xd0\xd1\x2f\xd7\x77\xa8\x5c\xff\xc7\xad\x12\x07\xa9\xaa\x75\x64\x3d\x7b\x41\x5b\xa4\xb1\xb9\x7d\xc0\xee\x19\xd0\x5a\x60\x7b\xa0\x63\xa0\x34\x1f\x17\x61\x04", p, q, g, b"\x3e\xad\x9c\xf2\x11\xf3\x85\x9d\x5b\xaa\x51\x55\xfb\x62\x33\x1b\xca\x3f\xff\x9e\xcb\xe1\x82\xeb\xf8\xb0\x4d\xb0\xeb\xb1\x9e\xda\x54\x8c\x86\xdb\x4c\xbb\x5e\xca\x98\xce\x44\x9c\xfd\x51\xf1\xc4\x60\xd7\x84\x83\x26\xee\xe2\x2f\xca\xc7\x24\x7f\xb8\x89\xee\x41\x5c\x49\x33\xa9\x09\xc7\x8c\xe9\xbc\x50\xee\x19\x01\x16\xda\x9a\xe2\x54\x7a\xe6\x24\x2a\x34\x0d\xdb\xb9\xa1\x5a\xc8\x18\xc4\x67\x7f\x29\x19\xc6\x45\x09\xd0\x3c\x49\xd1\x30\x7b\xb2\xcd\x78\xe0\x1c\xe5\xb2\x5a\x9f\x47\xd8\x28\xfc\x75\x84\xeb\xce\x36\x6c\x2f", b"\x38\xf5\x2d\xf7\x8b\x0e\x45\x4d\x35\x83\x20\x8a\x0f\xce\x03\xb9\x04\xee\xc8\x16", b"\x5c\xdc\x57\xa9\x43\xab\x1f\x26\x9c\xa1\x1c\x63\xbc\xb1\x05\x9e\xe7\x6f\x9c\x2e", )?; test( HashAlgorithm::SHA512, b"\x16\x25\x0c\x74\xcc\xb4\x04\x43\x62\x5a\x37\xc4\xb7\xe2\xb3\x61\x52\x55\x76\x82\x41\xf2\x54\xa5\x06\xfa\x81\x9e\xfb\xb8\x69\x8a\xde\x38\xfc\x75\x94\x6b\x3a\xf0\x90\x55\x57\x8f\x28\xa1\x81\x82\x7d\xda\x31\x1b\xd4\x03\x8f\xd4\x7f\x6d\x86\xcc\xeb\x1b\xbb\xef\x2d\xf2\x0b\xf5\x95\xa0\xad\x77\xaf\xd3\x9c\x84\x87\x74\x34\xad\xe3\x81\x2f\x05\xec\x54\x1e\x04\x03\xab\xad\xc7\x78\xd1\x16\xfd\x07\x7c\x95\xc6\xec\x0f\x47\x24\x1f\x4d\xb8\x13\xf3\x19\x86\xb7\x50\x4c\x1c\xd9\xdd\xb4\x96\xac\x6e\xd2\x2b\x45\xe7\xdf\x72\xcc", p, q, g, b"\x6e\x8c\x85\x15\x0c\x5c\x9c\xa6\xdc\xb0\x48\x06\x67\x1d\xb1\xb6\x72\xfc\x10\x87\xc9\x95\x31\x1d\x70\x87\xad\x12\xab\x18\xf2\xc1\x4b\x61\x2c\xea\x13\xbf\x79\x51\x8d\x2b\x57\x0b\x8b\x69\x6b\x3e\x4e\xfc\xd0\xfd\xa5\x22\xa2\x53\xbb\xcb\x7d\xbb\x71\x1d\x98\x4c\x59\x8f\xa2\x01\xc2\x1a\x8a\x9e\x27\x74\xbc\x15\x02\x09\x20\xcd\x8c\x27\xc2\x87\x5c\x77\x9b\x08\xef\x95\x09\x3c\xaa\xc2\xc9\xce\xa3\x7e\xc4\x98\xc2\x3d\xd2\x4b\x68\x4a\xbc\xb4\x67\xec\x95\x2a\x20\x2c\xbd\x2d\xf7\x96\x0c\x1e\xf9\x29\xcc\x2b\x61\x1c\xa6\xc8", b"\x00\x01\x8f\x0f\xdc\x16\xd9\x14\x97\x1c\x8f\x31\x0f\x1a\xf7\x79\x6c\x6f\x66\x2a", b"\x62\xb7\xae\xcc\x75\xcb\xc6\xdb\x00\xdd\x0c\x24\x33\x9f\x7b\xdb\x5a\xe9\x66\xa5", )?; test( HashAlgorithm::SHA512, b"\xa2\xce\x90\xb5\x1a\x48\x0c\x06\x68\xb5\x59\x36\xbb\xee\xbe\x36\x79\xf8\xd4\x06\xa0\xb6\x94\xb9\x03\x45\x74\x9e\x3b\x9c\x67\x77\x6c\xae\x9a\x62\xc2\x5c\xc0\x11\xcd\xb3\x18\x02\x63\xdd\xd7\x3a\xa2\x09\x0e\xc7\xa7\x49\x09\x2f\x6c\x78\x16\xc2\x67\x44\xc5\x39\x3a\xcb\x08\xc6\xb7\xb3\x59\xbb\x3a\x3c\x75\x43\x68\x4f\x80\x50\xec\xc6\x42\x22\x34\xff\x24\x97\x8a\xe0\x6b\x91\xd6\xa2\x4c\x08\x6d\x71\xeb\x17\x61\xca\xf1\x41\x76\xd8\xba\xcd\xca\xd5\x3b\x78\x95\xbd\xb0\xe0\x53\xc6\x16\xb1\x47\xff\x73\xd2\xd8\x6b\xa3\xbc", p, q, g, b"\x0e\x6b\x41\x9d\xa8\xb4\xdb\x80\x2d\x93\x88\x73\xe3\xb1\x05\xab\x3e\xff\x43\x2d\x8a\x13\x76\x60\x20\x59\xcf\x2e\x51\x0f\x69\x6a\x2a\x4e\x42\x02\x56\x70\xdb\x00\x11\xe9\xbe\x31\xe8\xb1\x40\x36\x15\xb9\xa3\x39\xce\x65\x4a\x89\xa2\xd4\x62\xee\x20\xc0\x80\xc4\x47\x96\x48\xc5\xc0\x0e\x17\x2e\xcd\x53\x7c\x93\x4e\x75\x34\xaf\x70\x02\xbd\x6f\xda\xfa\xb5\x65\x06\x68\x0c\x01\x9c\xed\x38\x77\x9d\x95\x40\x91\x64\x5f\xed\xf5\xd0\x05\x7a\x23\xff\x63\x49\x19\xfc\x56\xa9\x67\x71\xce\x21\xfa\x99\xec\xd9\xaa\x7f\x79\x85\xf1", b"\x5b\x13\xf1\x33\x7a\xc7\x2e\x41\x98\x67\xc9\x2f\x93\x87\xf9\xdf\x62\x88\x3a\xa5", b"\x90\xab\x5b\x68\xfd\x82\x53\xb6\xbb\x64\xc6\x17\x59\x16\x4a\x97\x83\x4c\x39\xe1", )?; test( HashAlgorithm::SHA512, b"\x3b\x6e\xea\xed\xc5\xfb\x38\xce\x86\x91\x68\x6c\x89\x99\x3c\xaf\x17\xc9\xe2\x4f\xa5\x65\xa9\xe8\xd4\x84\x36\xb8\x7d\xb6\x2f\xab\x83\x9c\x42\xd8\x1f\xb1\xf8\xb8\x96\x8c\x82\x6e\x78\xd3\x33\xb1\xd9\x9d\x5c\x36\xe0\x8a\x9a\x0e\xc7\x55\x4c\x2b\xde\x07\xfd\x8e\xc4\x22\xaf\x12\x82\x46\xba\x3b\xea\xe1\x8e\xf2\xbe\x75\x5d\xb2\x2a\x86\x92\x02\x95\x1c\xd9\x57\x96\xfc\x2f\xf7\xba\x2a\x69\x67\xd1\x9e\x5c\xa2\x30\x46\x55\xbf\xdf\x87\x9b\x77\x47\xf8\x0a\x59\xb1\xda\xc0\x46\x1c\xf6\xe4\x90\x37\x8e\x56\xab\x37\x85\x84\xf2", p, q, g, b"\x4a\x7f\xf6\x67\xf7\xab\x28\x91\xa8\xa6\x9a\xb5\xd1\x5d\x93\xd1\xfd\x83\x39\x06\xc9\xb6\x29\xfc\xb9\xb4\x9e\x84\xd8\xec\xb3\x5b\x39\x7d\x93\x83\x9d\x79\x85\x59\x03\x26\xcf\xfb\x55\xa7\x0e\x4a\x51\xa2\x82\x9e\x38\x72\x90\xf6\xfa\xfb\x7d\x22\x61\x51\xc2\x82\x47\x02\x24\xfd\x71\x7f\x3d\x52\x65\x89\xc6\xee\xd9\x61\x1c\x5b\xdf\x4b\xde\x63\xfc\xc9\x20\x4c\x80\x07\xb0\xb1\x43\xc4\x9d\x19\x81\x83\x56\x58\xbc\xf8\x00\xa2\x15\x7c\x5c\x14\x3d\x76\x36\x9f\xd6\xe7\x8d\x0a\x3f\x80\x0b\x8a\x3a\x8f\x0e\x11\xc9\x05\x9d\xcd", b"\x61\x38\x0c\xa8\x67\x98\xfc\x5f\xb6\x1c\x35\x67\x5a\xf0\x8d\x5c\xc1\x3c\x25\xaa", b"\x54\xdd\xf6\x8f\x47\x68\x84\xaf\x3e\x0e\x65\x36\xf3\xa8\x09\x25\xee\x63\xa4\x02", )?; test( HashAlgorithm::SHA512, b"\x01\x19\x7a\xe9\x60\xde\x90\xa9\x3d\x97\x36\x89\x6f\xe1\x36\xbc\x56\x1f\x05\x50\xc6\xb1\xcc\x36\x31\xb3\x1d\xf6\x83\x01\x7c\x2a\xb8\xc6\xf4\x1d\x27\x45\xf1\xa7\x97\xe0\xe8\x9d\xc3\xd5\x87\x88\x66\xc3\x69\x4a\x08\x03\x66\x75\x7e\x6f\xd8\x92\xd2\x66\x68\xfd\x2d\x86\x0e\xa2\xa2\xb6\x7f\xda\xca\x96\xe3\x22\x97\x75\x87\x87\xec\xc0\xa7\xe1\xd3\x04\xcc\x71\x98\x03\x27\x2e\x72\xe3\x39\xb3\xf3\x4c\x34\x7e\x47\xb9\x1a\x1e\xd6\x9c\xa8\x06\x2c\xd3\x50\xdc\xcc\x9c\x22\x64\x73\x2b\x9f\xdd\x84\x62\xd9\xf6\xfc\x76\x85\x0c", p, q, g, b"\x37\x30\x81\x77\x0a\x9f\x4d\xae\x8d\xf5\xdf\xa7\x05\x03\xe1\x49\xd7\x59\xca\x37\x40\x85\x49\xaa\x34\xd1\xb4\x9b\x31\x76\xa3\x9d\x7c\x46\x61\xe4\x21\xa1\xf5\xd6\x1e\x3f\x77\xb3\xc4\xb3\x9b\xb2\xe6\x3c\xd2\x49\x19\xa2\x91\x0a\x8b\x15\x5e\x17\x58\xf5\xa3\x75\xda\x60\xf1\x9d\x2b\xf4\x02\x0e\x82\x8f\x42\x37\xeb\x3e\x2a\x36\x12\x4a\x6a\x39\x14\x46\x9d\x68\x33\x69\x5b\x83\xb9\x37\x7f\xb2\x85\xb2\x7b\xd2\x68\x99\x33\xc1\x89\xc7\xfd\xe4\x2e\x1e\x0e\x20\x30\x83\x31\xfd\x56\xed\x0d\xb2\xef\xbc\x77\xea\x3a\xc7\x12\x1f", b"\x41\xed\x17\x0c\x8b\xf6\xf2\x0f\xd1\xce\x18\xfa\xac\x97\x56\x5f\xdb\x4f\xe6\xf4", b"\x7c\x8c\x6f\xea\xce\x68\xc9\x7c\xa4\x37\x80\x74\x1f\xae\x58\xf2\xf6\x1b\xf7\x65", )?; test( HashAlgorithm::SHA512, b"\x0d\x5a\xb2\x7b\x2b\x7e\x18\xcf\xce\x4c\xcd\xa1\x3a\xa1\xa5\xa8\xc1\x8b\xaa\xf3\x9b\x14\xe6\x42\xb8\xf8\x1b\x30\xcd\x54\x18\xa1\xdd\x05\xdf\x22\x59\x9f\xbb\xb3\xba\xe4\xfe\xe1\xe4\xb2\xc1\x50\xa2\x3e\x21\x6c\x13\x3f\xe2\xd8\x23\x54\x85\xe3\x4f\x80\x68\x5c\x66\xbc\x0c\x19\x0a\xf6\x7a\x0a\x49\x93\x0b\x47\x6b\x28\x03\xe1\x22\x74\xcd\x43\x09\x09\x21\xbf\x66\x8f\xdf\xef\x15\x50\x72\xa3\xcd\xf1\x79\x01\x42\x7a\xfa\x51\x31\x8a\xfd\xda\x93\x7e\x28\x3e\x2c\x60\xd8\x5e\x3b\xfe\x07\xf3\xda\x5f\x99\x2c\x1f\xca\x4b\x98", p, q, g, b"\x1c\xa3\x6e\x35\x05\xee\x70\xa5\x6a\xfd\x5d\xc4\x0a\x48\xe9\x79\x79\xe9\x84\xdd\x2d\x89\x6a\xbc\x7a\x49\x1d\x34\x61\xc6\x93\x16\x68\xa0\xce\xf1\x1e\x45\xbb\x66\xc6\x11\x13\x79\x99\x90\x7a\xd7\xe1\xf7\xcf\xea\x7f\x7e\xd4\x9a\xae\x93\x5b\xfc\x41\x44\x32\x93\xe7\x1d\xd2\xfe\xc2\x9f\x37\xa9\x54\x46\x72\xab\x92\x50\xca\xa2\x81\x88\xf3\x90\xb5\xd4\xaf\x13\xbb\x05\xe9\x69\x2c\x1c\x6a\x4d\x6a\xaf\xeb\xdd\xaf\x7e\xef\x18\x34\xff\xfe\x0f\x53\x91\xbc\xe2\x43\x78\x9a\x2d\x55\xd2\x9e\x2b\x90\xce\x12\x04\x29\xf2\xa0\x75", b"\x66\x01\x5e\x5f\xb3\xab\xe9\xd7\x85\x23\x77\x0f\x7b\xa0\x99\x00\x31\x06\x5a\xd7", b"\x4b\x8b\x15\x3d\x5b\x01\xdd\xfa\x91\xf2\xde\xc6\xf0\xfa\xff\x02\xe6\xe8\x72\x18", )?; test( HashAlgorithm::SHA512, b"\x90\x6a\x93\x3b\xc8\x23\xa3\x07\xe2\xab\x29\xa4\xa8\xf7\xf1\x51\x0d\x5d\x30\x35\x04\xfd\xe3\x81\x69\xde\xd1\x68\x91\x3e\x3b\xf8\x1d\x53\xa4\x38\x9a\x3e\x73\xa3\xef\xeb\xd5\xe4\x2c\xf4\x02\xbf\x9f\xdc\x5d\xa5\xef\x46\x87\x81\x65\xad\xa6\xd2\xe0\x72\x99\x03\x5a\x39\x87\xed\x6c\x2c\x6c\x8b\xec\xc4\x4e\xa1\x31\xa9\x49\x3e\x72\xae\xe2\x82\x42\xcf\x7c\xfa\xc3\x8e\xe8\x70\xe5\x4e\xb9\x5a\x6e\xfa\x9f\xad\x74\x35\x4b\x28\x1c\xb6\x3e\xa7\x16\x52\xeb\xa1\xad\x73\xf8\x41\xdb\xa7\x77\x8f\x3a\x03\xd3\xe0\x01\x90\xed\x68", p, q, g, b"\x4f\x3a\xde\x3e\xa4\xf7\x06\x61\x07\x32\x1e\x8b\xfb\x12\xee\xaf\x9b\x3c\x7b\xdc\xc6\x14\x79\x08\x75\x42\x31\x15\x6b\x46\xe0\x63\x9c\x9d\xb9\xd5\x44\x7a\xbd\x2d\x0a\x92\x23\xc8\x5d\x63\xd8\xb1\xdf\xa6\x97\x42\xeb\xf7\xe0\x41\x9e\x60\x8c\x4b\x18\xc3\xad\x9f\x55\xf5\xd2\x84\x8e\xdb\xec\x4e\x71\x80\xe3\x4b\xfb\xb1\xf6\xb6\xeb\xbb\x68\x64\x97\x14\xb5\xfb\xfa\x6c\xfa\xb4\xa0\x1f\x65\x50\x08\xa9\x5a\x7e\xed\xcd\xc2\xf7\x17\x10\x94\x56\x3a\x3c\x18\x31\xe8\x1f\x5c\xa0\x75\xc6\xe8\x53\xbe\xea\xe8\x7a\x67\xff\x90\x22", b"\x99\xc9\x1e\x07\x94\x72\x3b\xcd\xe3\x45\x94\xdd\x22\x68\x41\x8d\xfb\x35\x34\x43", b"\x42\xe9\xc4\x9d\x60\xad\x8f\x9b\x41\xf2\x90\xae\x6b\x77\x2f\x44\xbe\x62\xce\xa9", )?; test( HashAlgorithm::SHA512, b"\x1d\x6b\xa4\x3a\x0f\xf6\x77\xcf\x8c\xf6\x8d\x6a\x1d\x33\x04\xd9\x94\x90\xa7\xca\xe5\x6f\xe3\x53\x18\xf3\x8e\xd0\xf5\x87\x9f\xe2\x54\x70\x3f\xa7\x74\x58\xc4\x5e\x8a\x69\x84\x69\xb8\x99\xa2\x15\xc2\x5e\x86\x9f\xd2\x87\x41\x10\x1d\x27\xdc\x11\x1f\xfa\xd6\x98\x0f\x8e\xbd\x74\x8f\x69\x77\xd5\xd6\x04\x38\xe6\xed\xec\x37\xa4\x9d\x30\x11\xf8\xf0\xf0\x85\x25\x15\x6a\xe6\x0b\xc9\x1a\xbe\x66\x16\x38\xf4\xb9\xc6\xc3\x65\xc3\xaf\x17\x13\xbf\x7f\x72\x25\xd4\xaf\xad\x7a\x1b\x53\x1a\x33\x11\x33\xd8\xb8\xfd\x23\x85\x98\xa4", p, q, g, b"\x08\xad\x77\xf2\x33\x33\x4c\x04\xca\x22\x82\xf6\xda\xc0\xb0\xd8\xa0\x0d\x59\x6e\x02\xe8\x36\xa7\x67\xa1\x46\xef\x80\x62\x4b\x33\xfd\xba\x8b\x35\x20\x4b\x20\xbe\xe8\xff\x2b\xe9\xa8\x2b\xd8\x01\x31\xc0\xaa\x89\x8b\x17\xee\xab\x5a\xf2\x4c\x20\x55\x1d\x5d\x63\x6a\x11\x54\x8f\xdd\x2e\x6c\x61\x6b\x09\xdf\x86\xb0\x57\xe5\x70\x21\x46\xec\xc4\xfa\x2d\x44\xd8\x5b\xb1\x42\x7e\x7e\x35\x76\xf6\x98\xb4\xf2\x16\x45\xa1\xe0\x04\x79\xd0\x89\x82\xb0\x57\x3d\xd1\x98\x1b\xbd\x40\x5c\x2a\x45\xd7\xde\x92\x42\xaf\xae\x8f\x95\xc9", b"\xa2\xb4\x2c\xca\x55\xbc\x1b\xa3\x3f\x82\x52\xd1\xa8\x9c\x8d\x89\xb0\x0b\x39\x50", b"\x2e\xc5\x16\x6e\x35\xe6\x3f\x0f\xa1\x16\xb3\xdb\x1b\xd1\x86\x81\xa4\x39\x9c\x04", )?; test( HashAlgorithm::SHA512, b"\x3b\xd0\xc5\xb7\x59\xcb\x71\x0c\x52\xb8\x1f\xba\x48\xb6\x77\x1c\xab\x17\xbf\x1b\x67\xea\xfd\x08\xf4\xee\x17\x77\xdd\x47\x30\x64\xdd\x0b\xec\x98\xd3\x58\x2e\xe1\xe9\x91\xab\x9a\x91\xa6\xfe\x55\x8a\x41\xdb\x9a\xe6\xb2\x1a\x05\x79\x32\x81\x14\x40\xd6\x4c\x78\x6b\x22\xd1\x50\xe3\xd3\x8c\x71\x90\x0a\xd5\xb6\x1e\x05\x30\x74\x4e\x76\x5b\x5c\x2e\xf3\x0b\xcb\x96\xe7\x26\xe3\x07\x9e\x44\x00\x86\xef\x30\x0b\xae\x90\x00\xdf\x34\x03\xc3\x3a\x79\x84\x9f\x8f\x83\xd6\xc0\x3f\x77\xea\xe9\x80\x52\x57\x8d\x82\xd6\x28\xe6\x5c", p, q, g, b"\x3a\x1e\xd9\x76\xb7\x93\x4b\xee\x3e\x80\xd6\x9f\xbc\xdd\x35\xf8\x20\x51\xcc\xc2\x14\xbd\xe6\xfa\x75\x6b\xe6\x70\x17\xff\x60\xac\x68\x47\xcf\x8d\x1f\x82\x3f\x89\x0d\x26\xaf\x8c\xd3\x51\x71\x6a\xd2\xd4\xee\xfd\x7f\x06\xc1\x95\x1e\xa4\xa7\xdb\x5c\xaf\x25\x0f\x40\x7b\x78\xf2\x1f\xff\x42\x5d\x0c\xba\x1b\x5f\xb3\x5a\x5b\x5d\xcf\x06\x2a\x1c\xdf\x25\x07\xaf\x74\x78\x93\x26\x71\x0e\x33\x4f\xaf\x3c\x50\x1b\xd8\xc8\x34\x72\x25\xf9\x4f\x89\x73\xad\xb7\xa8\xb5\xde\xf9\x89\x61\x09\xd1\xef\xe5\x50\x32\x5d\xd8\x9f\x31\xd6", b"\x77\xd6\x2e\xc2\xa9\x5b\xeb\xa6\xc6\x72\xd8\x42\x2e\xe6\x63\xd1\xd1\x80\x49\xd0", b"\x2a\x33\x9c\xc8\xf5\x67\xc1\x21\x49\xa8\x91\x73\x75\xec\x6c\xa4\xb4\x72\x54\xa1", )?; test( HashAlgorithm::SHA512, b"\x8d\xc5\x82\xa2\xb5\xaf\x65\xe6\x6e\xbd\xf5\xb5\x33\xd8\xe2\x2b\x38\xb5\xc1\x97\x7e\x57\x8d\x32\x13\xa1\x10\xe9\xd4\x83\x7a\x74\xd4\xb7\xbf\x7d\x71\x87\x56\x90\xb5\xb7\x1c\x8e\x8a\xfa\xb8\x94\x34\xc4\x6a\x86\x41\xcc\xed\x1a\x13\x0e\x21\xcd\x0c\x80\x5e\xe4\x5c\x13\x4c\x7c\x0d\xf7\x5d\x5c\xd3\x0c\x41\x81\x8f\x7a\xe4\x75\xdd\x60\x22\x87\x7c\x74\x3d\x09\xd5\x4f\x0c\x94\x58\x1a\xe7\xbd\x4b\x42\x3f\x02\xe1\x93\x97\xbe\x7b\xd4\xa9\x04\xb8\x8c\xbd\x2f\x81\x4b\x1d\xff\x1e\x79\x6d\x9f\x2d\x1c\x84\x70\xb7\x96\xc6\x9a", p, q, g, b"\x5d\x6d\xc1\x74\x9f\x28\xcb\x8f\x7c\x01\x4d\x5c\x55\x16\xcf\x5b\xc2\x22\xc6\xd9\x33\x7a\xc0\x08\x9b\x19\xb9\x0b\x32\x19\x56\xcf\x61\x92\xf3\x25\x5d\x0e\xec\x45\x84\x08\x10\xc2\x1f\xe9\x1c\xf5\x30\x89\x48\x85\x2a\x57\xcd\x01\x89\xf1\x5b\xd9\x6a\xf8\x38\x0d\x19\xcb\x82\x1b\x1c\x56\xaf\xdc\x38\xa9\x4b\x2c\x32\xfe\xb1\x82\x13\x93\x96\x93\xb6\x9f\x2b\xcb\xae\x7e\x70\xab\x09\xea\xd3\xb6\xa8\xb7\xda\xd3\xc4\xf5\x21\xad\x04\x55\xdd\x4e\x87\x2b\x36\x27\xd4\xfe\xd2\x0d\x5e\xfc\x78\xf6\xae\x46\x7f\xb9\x26\x7a\xb1\xd4", b"\x05\x36\x3b\xcc\xa1\x93\xd7\x26\xcd\x20\xe0\x34\x89\xe1\xb1\x3b\x7d\xf3\xbc\x98", b"\x31\xbd\xac\xcb\x29\xe4\xa6\x00\x23\x92\x9f\x18\x21\x99\xc0\x70\xb7\x1a\xc5\x75", )?; test( HashAlgorithm::SHA512, b"\x47\x7a\xf8\xc0\x25\x18\x1b\x55\x77\x32\xb9\x56\x86\x34\xb1\x32\x4e\x66\x69\xb4\xc2\x8a\x0b\xcd\x4c\x65\x3d\x4c\x81\xed\x68\xb2\xa2\x04\x3a\x80\x0a\x31\x4b\xa9\x5e\x50\xde\xea\xcc\x5e\xe9\xc2\xba\x6f\x6f\x62\xfd\xba\x0e\x86\xac\xa2\x27\xd7\x27\x37\x75\x52\xa3\xab\xdb\xab\x60\x1c\x26\x01\x84\x6e\xc2\x7a\x19\x2a\x3f\x33\xe7\xff\xdb\xe4\xa4\xaa\x7b\xeb\x2b\x3f\xf6\xc9\x1b\xd5\xcd\x5c\x89\x0b\xcb\x6f\x4c\x90\x8f\xf5\xb9\xb5\x55\xe2\xa0\xa7\xdf\x8c\x3e\xf6\x77\x01\x36\xbb\xf0\x09\x75\x5b\xf6\xc3\xe6\x30\x73\x10", p, q, g, b"\x2c\xce\xdd\xc9\xe2\xce\xbb\xc1\xe9\x9b\x83\xb0\x30\x53\xbb\x14\xa9\xcf\xaf\x07\x2b\x45\xe4\x74\x6d\x18\xcb\x39\x01\xf6\xa2\xc3\xcf\x72\xda\x66\xb0\xb9\xb3\xe1\x05\xbd\x4c\xd0\xe5\x42\x7d\x7e\x9b\x65\x7e\xd7\x18\x84\xcd\x49\xf5\x1f\xe8\xfa\x18\xa3\x66\x01\x8a\x3e\xaf\xac\x33\x81\xe0\x7a\x5b\x19\xf6\xd3\x86\x2e\xd2\x91\x60\x94\x90\x6e\x75\x28\x6e\xaf\x1d\x13\xc4\x85\x74\x4b\x27\x04\x04\xff\x9a\xdc\x8e\x17\x78\x33\x04\x3b\xdc\x34\xc3\x07\xe6\xfc\x9c\x55\xc5\x3d\x8f\xf8\x4a\x6e\x25\x10\x38\xdb\xeb\x5e\xf7\x74", b"\x35\x91\xc5\x21\xb2\xa5\x6c\xf4\x60\x51\xc0\xcb\x3d\x44\x4b\x9a\x22\xff\xf6\x3f", b"\x7a\xc7\x8e\xe2\x52\x44\x0c\xf9\xe8\x51\x04\x94\xd1\xfa\xd8\xb5\x18\xf1\xe1\x28", )?; test( HashAlgorithm::SHA512, b"\xbb\x65\x93\xff\x21\x9c\x9f\x20\xaa\x47\xe1\xe1\x57\xe8\x8e\xd5\x9a\xe2\x9c\x89\x40\xa5\x27\xc8\x2e\x0e\x0f\x2e\x85\x5f\xa9\x8e\x94\xe0\x7b\xe1\xf6\xbc\xe3\x83\x2b\x7e\xa1\xe6\x0a\x5c\x9e\xf5\x83\xf2\xec\x7b\x17\x92\x27\xe4\xaf\xdc\xf8\x29\xd6\x73\xe1\x37\x7f\x83\x2a\xe3\x8e\x7c\xad\xed\xe4\x15\x96\x4f\x12\xba\xf7\x75\xd3\x8c\xe3\x8e\x94\x55\x63\xe7\x28\x61\x51\x91\x97\xc2\xd0\x8f\x28\xd8\xb6\x46\x65\x62\xe0\x59\xec\x41\x74\x1d\xe3\x49\xed\x5d\xe2\xc7\xd6\xcc\x75\x18\xa8\x77\x20\xa2\x48\xb3\x01\x73\x3a\x47", p, q, g, b"\x15\xd9\xe2\x0c\x3f\x39\xcc\x9e\x3b\x8f\xb6\x5f\xeb\x64\xfb\x15\x68\xf6\xef\xde\xf6\x45\x7d\x23\x1c\x49\x1e\xd5\x17\x31\xd5\x8f\x06\xe4\x5e\xa5\xd6\x65\xd0\x49\x69\x82\x3d\xa4\xe6\x75\x0a\x2c\x3d\x16\xc5\xff\x60\x80\xec\xd0\x9a\xa3\x9c\x00\x6e\xed\xce\xb4\xdb\xc1\x6e\xbd\x64\xbc\x5b\x1e\x44\xb8\x31\xa6\xbb\x25\xaf\xbc\xb3\x00\x0f\xa6\xb6\xc2\x00\x08\x60\x01\x40\x11\x18\x9c\x22\x54\x2c\x14\x5e\x40\x7e\x7b\x59\xf6\xd3\xfb\x1e\x13\x62\x95\xec\x85\x0b\x14\xff\x2f\x49\x94\xea\x37\x48\x1e\x80\x19\x99\x10\xbe\x8e", b"\x61\xe7\x27\x71\x6c\xc9\x69\x14\x50\x97\x40\xa7\xcb\xa6\xe7\x4a\x9d\xec\x64\x06", b"\x2e\x77\xc1\x4f\x01\xf2\x21\x80\xbc\xda\x57\x25\xcf\x0e\xaa\xc9\xad\x13\xa7\xd1", )?; test( HashAlgorithm::SHA512, b"\x56\x5f\x19\x24\x44\x68\x51\x5e\x84\x63\xd0\x7b\x42\x5b\x4d\x5f\x81\xff\x2e\xfa\xb5\x15\x6b\xa1\x9a\x63\x73\x42\x19\xc2\x26\xcc\xca\x59\x03\xbf\x9c\x35\xdb\xca\x09\x61\xdb\x7c\x2e\x3f\x69\x44\xd0\x57\xed\xfa\x6c\x23\x94\xc3\x9a\x00\xf1\xc4\x25\x96\xe7\xee\x72\xed\x64\x4c\x6a\x18\x21\x15\xbd\xc4\x4b\x90\x10\xc8\x6e\x7b\x0e\xc2\xe3\xbd\xf7\x01\x6c\x5e\x04\xf4\x55\xb4\xcb\x69\x3e\x32\x49\x0b\x8f\x49\x4b\xb4\x10\x3b\x3b\x5e\xa6\x80\x82\x22\x45\x28\x41\xb7\x33\xfa\xf7\x35\xf1\x0a\x95\xfb\x28\x3d\xd8\x6c\xe5\x93", p, q, g, b"\x66\x42\x45\xaa\xeb\xcf\x5c\x05\x5c\x32\x10\x9b\x21\x59\xa1\x74\x73\x04\x30\x87\x91\x5f\x14\xe9\x59\xdd\xdc\x0c\x9b\x20\xc7\x26\xf0\x12\x4f\x1e\xcb\xaf\x20\x2f\xe2\x67\x6a\xfd\xab\xd3\x46\xa7\xb5\xbe\xf7\x69\xa2\x5c\x6f\x73\x36\x12\xd7\x37\x8d\xf1\xb2\xd4\xc5\x18\xa2\xda\x5b\x3a\x4c\xd0\x25\x2b\xb8\x18\x08\x38\xa4\x63\x89\xa8\x46\x93\xbe\x8c\xc2\x4f\xbd\xc6\x39\xb6\x2c\xb2\x1d\x8a\xbe\x12\x72\xb5\xaa\x06\x22\x2f\xe2\x13\x3f\xc5\x55\x6d\x24\xe7\x54\x96\xa5\x3e\x19\x34\xd3\xb5\x84\x8e\x51\x0b\x69\xda\x04\xa4", b"\x5c\xa0\x7b\xc7\xcd\x9f\x7a\x60\xcf\x79\x39\x1d\x87\x3b\x6f\xdd\xf5\xa4\x8c\xca", b"\x97\x99\xc7\x4a\x80\x6f\xc1\x96\xe0\x22\x3f\xb1\xa6\x13\xfd\x17\x8c\xaf\xbd\x99", )?; // [mod = L=2048, N=224, SHA-1] let p = b"\xf2\xd3\x9e\xd3\x06\x2b\x13\xc9\x16\x27\x36\x00\xa0\xf2\xa0\x29\xe8\x6d\x7a\x4b\x92\x17\xb4\xf1\x81\x5b\xf2\xb2\x4d\x97\x10\xa5\x7a\xb3\x3f\x99\x72\x94\xb0\x14\x58\x5b\x8d\x01\x98\xdf\xdc\xcb\xcd\x75\x31\x4d\xa5\xff\x85\xaa\x34\x4b\x45\xad\xae\xaa\x97\x9b\x51\xa3\x12\xa7\xbf\xa9\x44\x72\xfb\x63\x3f\x1a\x6f\x15\x6b\xb4\x45\x88\x67\xdf\xd3\x84\x03\xf0\x6b\x85\x1f\x00\xfe\x2d\x34\x84\x07\x7b\xde\xd7\x1a\xb7\x51\x3d\x04\xa1\x40\x22\x05\x75\xfb\x69\x33\x95\x48\x0e\x4c\x84\x02\xb7\xa4\x6c\xec\x2d\x37\xa7\x78\xc3\x05\xac\xcd\x1f\x13\xe9\xf6\x2e\x86\x53\x15\xf4\xb2\x2c\xc4\x67\xc8\x98\x6e\xc8\xe4\x96\x1d\xdf\x81\x05\x66\xb0\xc4\xee\x36\x9a\xc6\xaa\x15\xe4\x3f\x47\x44\x00\x58\x26\xf5\xbd\xe8\x07\x1a\x19\xe3\x0b\x69\x09\xaa\xc4\xb3\xd1\x74\x23\x72\x70\xda\xd0\x27\x99\xd0\x9b\x8a\x2c\xc5\xf2\x2e\x66\x89\x4b\x54\x22\x22\x8b\x2c\x23\x4f\x11\xf5\xa7\x71\xc5\xb8\x9c\xf4\x65\xa2\xac\xec\xbb\xee\xaa\x17\x25\xfe\x8f\x9b\x59\x42\x2b\xe8\x99\x10\x52\xcb\x55\x6d\xdf\x2c\x8c\xe8\xfa\x92\x06\xdb\xf3\x9f\xea\xdc\x19\x4e\x00\xf8\xe5"; let q = b"\x80\x00\x00\x00\x00\x00\x00\x00\xc1\x18\xf4\x98\x35\xe4\xef\x73\x3c\x4d\x15\x80\x0f\xcf\x05\x9e\x88\x4d\x31\xb1"; let g = b"\xe3\xa9\x3c\x09\xda\x6f\x56\x0e\x4d\x48\x3a\x38\x2a\x4c\x54\x6f\x23\x35\xc3\x6a\x4c\x35\xac\x14\x63\xc0\x8a\x3e\x6d\xd4\x15\xdf\x56\xfd\xc5\x37\xf2\x5f\xd5\x37\x2b\xe6\x3e\x4f\x53\x00\x78\x0b\x78\x2f\x1a\xcd\x01\xc8\xb4\xeb\x33\x41\x46\x15\xfd\x0e\xa8\x25\x73\xac\xba\x7e\xf8\x3f\x5a\x94\x38\x54\x15\x1a\xfc\x2d\x7d\xfe\x12\x1f\xb8\xcd\x03\x33\x5b\x06\x5b\x54\x9c\x5d\xcc\x60\x6b\xe9\x05\x24\x83\xbc\x28\x4e\x12\xac\x3c\x8d\xba\x09\xb4\x26\xe0\x84\x02\x03\x0e\x70\xbc\x1c\xc2\xbf\x89\x57\xc4\xba\x06\x30\xf3\xf3\x2a\xd6\x89\x38\x9a\xc4\x74\x43\x17\x60\x63\xf2\x47\xd9\xe2\x29\x6b\x3e\xa5\xb5\xbc\x23\x35\x82\x8e\xa1\xa0\x80\xed\x35\x91\x8d\xee\x21\x2f\xd0\x31\x27\x9d\x1b\x89\x4f\x01\xaf\xec\x52\x38\x33\x66\x9e\xac\x03\x1a\x42\x0e\x54\x0b\xa1\x32\x0a\x59\xc4\x24\xa3\xe5\x84\x9a\x46\x0a\x56\xbc\xb0\x01\x64\x78\x85\xb1\x43\x3c\x4f\x99\x29\x71\x74\x6b\xfe\x29\x77\xce\x72\x59\xc5\x50\xb5\x51\xa6\xc3\x57\x61\xe4\xa4\x1a\xf7\x64\xe8\xd9\x21\x32\xfc\xc0\xa5\x9d\x16\x84\xea\xb9\x0d\x86\x3f\x29\xf4\x1c\xf7\x57\x8f\xaa\x90\x8c"; test( HashAlgorithm::SHA1, b"\xed\xc6\xfd\x9b\x6c\x6e\x8a\x59\xf2\x83\x01\x6f\x7f\x29\xee\x16\xde\xea\xa6\x09\xb5\x73\x79\x27\x16\x2a\xef\x34\xfe\xd9\x85\xd0\xbc\xb5\x50\x27\x56\x37\xba\x67\x83\x1a\x2d\x4e\xfc\xcb\x35\x29\x6d\xfe\x73\x0f\x4a\x0b\x4f\x47\x28\xd1\xd7\xd1\xbb\x8f\x4a\x36\x23\x8a\x5c\x94\x31\x1f\xa1\x13\x4a\x93\xa6\xb4\xde\x39\xc0\x85\xe9\xf6\x0a\xe4\xe2\x37\xc0\x41\x6d\x58\x04\x2b\xb3\x6b\xaa\x38\xcb\xa8\xc8\x96\x29\x5b\x74\x5d\x53\x76\xfd\x8c\xe4\x2e\xb6\xee\x5a\x1b\x38\xf8\x77\x16\xb2\x65\xb7\x6e\x58\xcf\xb2\x4a\x91\x70", p, q, g, b"\x28\x9f\xf1\x8c\x32\xa5\x6b\xb0\xb8\x83\x93\x70\x64\x76\x83\xa3\x8a\x5a\x7e\x29\x14\x10\xb9\x32\x07\x21\x2a\xdc\x80\x88\xd3\x0f\x93\xe9\xe4\xab\xc5\x23\xf3\xd4\x69\x36\xe7\xd5\xc9\x0d\x88\x74\x2b\x36\xaf\xd3\x75\x63\x40\x8f\x15\xc8\xc1\xa4\xf7\xac\x24\xbf\x05\xf0\x10\x08\xff\xee\x70\xc8\x82\x5d\x57\xc3\xa9\x30\x8b\xad\x8a\x09\x5a\xf2\xb5\x3b\x2d\xda\x3c\xbe\xd8\x46\xd9\x5e\x30\x1e\xb9\xb8\x47\x66\x41\x5d\x11\xf6\xc3\x32\x09\xa0\xd2\x85\x71\x09\x6a\xb0\x4a\x79\xaa\x0d\xc4\x65\x99\x75\x29\x68\x6b\x68\xe8\x87\xcd\x8a\x20\x5c\x2d\xc8\x19\x5a\xef\x04\x22\xeb\xa9\x97\x9f\x54\x9a\xc8\x55\x48\xe4\x19\x41\x36\x43\xb7\x24\x43\x61\x15\x3a\xda\x14\x80\xd2\x38\xcd\x00\xdc\x16\x52\x79\x38\x95\x55\x48\xdd\x5d\x02\x7d\xed\x10\x29\xee\xeb\x8e\xd6\xc6\x1b\x4c\xd5\x93\x41\xd8\xb1\x54\x66\xe9\xda\x89\x0a\x98\x99\x96\xf4\xd7\x69\x1e\x60\x72\xde\x13\x6a\xf2\x8b\x58\x74\xbf\x08\xbd\x1f\x8a\x60\xcf\xb1\xc0\x08\x88\x13\x29\x09\xf5\x15\xe0\x4b\xce\x81\xb0\x29\x51\xaa\x41\xba\xac\x68\xff\xdb\x8c\x5d\xc7\x7a\x1d\x32\xd8\xf2\xc1\x0d\xd7", b"\x45\xdf\x2f\x42\x3e\x94\xbf\x15\x5d\xd4\xe1\xd9\xe6\x3f\x31\x5e\xa6\x06\xdd\x38\x52\x7d\x4c\xf6\x32\x87\x38\xc8", b"\x59\xb3\xe8\xef\xa5\xbc\x0c\xcb\xf4\xa3\xcb\xb6\x51\x5c\x4b\x9b\xf7\x84\xcf\xac\xdc\xc1\x01\xdc\x9f\x81\xd3\x1f", )?; test( HashAlgorithm::SHA1, b"\x3b\xd2\xab\x08\x21\x78\x78\xe6\x77\x4e\xc7\x79\x7d\xeb\x75\xd5\xc9\x4c\x40\xe2\x4d\xdf\x1f\xac\x8d\xde\x3a\x29\xc8\x6b\x26\xf5\x71\x57\xd3\x29\xaa\xc3\x1a\x66\x22\xe1\xd6\xda\xc9\x7e\x22\x69\x5d\x7d\x1f\x8e\x20\xaa\x26\xb0\x67\x95\xc2\xf8\x78\xba\x5d\x2b\x9c\xc4\xb1\x6d\x5f\xa6\x0a\x5f\xa5\xc2\x4c\x09\x03\x1d\xe2\xf9\x70\xa9\xb5\x7e\xa2\x4a\xf1\x71\x92\xec\xe2\x1a\x4d\x12\x0f\xdb\x52\xe6\x2b\x82\x38\xf7\x78\xff\x85\x52\xfa\x45\x3c\x0a\x88\x91\x24\x3f\xc8\x75\x71\x88\xe9\xc4\xe0\xe7\x49\xf7\xe9\xcd\xf1\xc1", p, q, g, b"\xb9\xb0\xe1\xcd\x37\xba\xfb\xed\xee\xd1\x73\xfd\x70\x99\x83\xf5\x3c\x2c\x42\x7f\x9f\x61\xc8\x95\xfa\xc9\xeb\x54\x9b\xd6\x20\x1d\x05\xef\xd5\x51\xae\xcb\x98\xb2\xdf\x80\x14\x2d\xea\x7a\x35\x49\x1d\x47\x4a\x3a\xdc\x83\xf0\xda\x8d\xc4\xea\xcd\x7f\x6d\x72\x01\xc6\xfc\x0a\xb7\x98\xab\xe8\x9d\xcd\x7d\x03\x10\xd5\xf0\x0f\xa1\x0d\x21\x1f\x18\xea\x85\x35\x79\xe2\xfe\x31\xee\x55\x37\x1d\x1c\x9f\xc4\xcf\xb0\x50\x78\x65\x86\x65\x9b\xdc\x0f\x1a\xac\x4c\x10\x9b\x9e\x4f\x94\x16\xd2\x2c\x42\xb3\x9a\x47\x13\x11\xe2\x8a\x8e\xd6\x2f\x1f\x41\xbc\xfe\x06\xe0\x74\xbb\x2f\x1a\xcd\x29\x59\x79\x53\xc3\xb6\x9d\x3a\x78\x83\x1f\xb2\xf8\x36\x65\xd0\x4a\x13\x95\x77\x5e\xa3\xa2\xa6\xea\x14\x2e\xc0\x05\x07\xba\xdd\x4d\xe0\xd9\xc1\x02\xea\xc7\xbb\x89\x4f\x74\x53\xe6\xa8\xe0\xdd\x3f\x14\x97\x83\x77\xd1\xdd\xb1\xfd\xf1\xc5\x58\x35\xb9\x92\x4f\x42\xad\x45\xc8\x47\xc7\x9b\x3f\x83\xfb\xf9\x24\xf8\x0b\x78\xf5\x03\x29\x73\x10\x16\x76\x3e\x01\xba\x8e\xf6\x9e\x81\x52\x3e\x18\x15\x84\xf4\x5c\x21\xe3\xc8\xed\xfe\xd4\xe2\xec\x56\xfb\x7b\x02\xaa\x4e\xe9", b"\x6d\x19\xfe\x3c\x41\x5d\x6b\x07\xd6\xa1\x03\x9a\x1f\xe3\x4b\x10\x6d\xaa\x2e\xea\x4c\xbc\xa9\x71\xcb\x66\x9e\xac", b"\x14\xd7\xde\xcc\x2c\xc0\x5a\x17\x00\xfa\x25\x6e\x4d\x29\x94\xbc\x4b\xd9\x57\xbe\xd0\xba\xf9\xa1\x8b\xda\x70\x90", )?; test( HashAlgorithm::SHA1, b"\xc6\x7f\xa7\x7c\xd7\x35\x1d\x10\x0c\x76\x24\xe2\x54\x18\x48\x1f\x8f\xa4\x99\xd7\x5f\x59\x49\xa5\xca\xe6\x0f\x96\xa0\xf7\xbf\xcd\xda\x7d\xba\x37\x3f\x9f\x75\x12\xa5\xf1\x46\x0a\x95\x21\x30\x77\xce\xbd\x91\x2e\x26\x62\xc4\x3a\xc6\xbb\xe3\x8c\x44\x79\xb0\x41\x51\xa5\xe2\xd2\x88\x09\x02\xd0\x31\xaa\x0d\xff\x3f\x41\x12\x6d\xd0\x9f\xba\x5c\x05\x07\x63\x4e\xd1\x6c\x39\x38\xfb\xd3\xa9\x64\x73\xa8\xb1\xeb\xdc\x37\xd3\x2c\x76\x7f\xd5\x93\x2e\xfa\x23\x55\x55\xf3\x82\x5a\x15\x95\x36\x92\x38\x67\x54\x53\x60\x4d\x27\x8e", p, q, g, b"\x31\x93\x9c\xcd\xd3\x93\xf7\x47\x54\x1a\x5c\x69\xf8\xe5\x09\x76\x1d\xd6\x7e\xdd\xb4\x2e\x0b\xdf\xc4\x12\xd4\xcc\x30\xd3\x68\xd8\x78\xd2\x6d\x85\x6c\x52\x90\xec\x74\x6b\x59\xc5\xe5\xaf\x65\xef\x3f\xd6\x2c\x9a\x2d\xcc\xfc\x15\x58\xdf\xbf\xb6\x2c\xfe\xcd\x78\x38\xe6\xd5\x94\x95\xb2\x0d\xb5\xad\x9d\x78\xe8\x2f\x04\x6f\x9f\x15\x98\x11\x3a\xae\x0a\x79\x60\x1d\x6b\x94\xa3\x2e\x05\xf6\xec\xfd\xf2\xb9\xc4\xcf\xa7\x20\xde\xbf\xc2\x12\x22\x1b\x14\xb0\xdd\x6f\x70\x78\x20\x5a\x4f\x21\x8c\xd4\xb8\xf1\x0b\xea\x8f\xa4\x81\xec\xa5\x25\x4f\x36\x5d\x01\xf3\xc8\x65\x20\xbf\x25\x43\x23\xd5\x63\x4b\x96\x92\x0a\x13\xb8\xf2\x9d\x73\x4e\x07\xfd\xe8\x06\x4e\xb0\xc9\xf8\xeb\xb6\xae\x0b\x40\xb4\xaa\x7d\x26\xbb\x8d\x80\x86\x82\x31\xd4\x55\x8a\x27\x80\x45\xcb\x5f\x29\x51\xcb\xfe\x0d\xc9\x7b\xbd\xce\xe7\xaf\x8c\x9b\x1e\x3b\x63\xcb\x49\xdc\x29\xf3\x00\x77\x5c\xdb\xe4\xd2\xd2\x78\x94\xe2\x7e\x0e\x7c\x9e\xad\xa1\x3a\x35\x9f\x0b\x92\xb4\x49\xe9\xd0\x69\xb9\x5b\xdc\x2a\xa7\xc8\x5e\x56\x81\x1c\x07\x20\x7a\x15\x0e\x59\x87\x35\x99\x6a\x6e\x53\x49", b"\x79\x24\xb7\x6e\xe7\x6a\xd7\xff\x2a\xb3\x27\xda\xbb\xbd\x31\x33\x67\x50\xfc\x76\x63\xdf\x4b\x5b\x94\xee\xb6\x2d", b"\x59\x14\xcf\x96\x54\x90\xb0\xbf\x81\x92\xfc\x6e\x16\x97\x54\xbd\xfd\x31\xc4\x8d\x71\x63\x61\xdd\x15\xf4\x5b\xf7", )?; test( HashAlgorithm::SHA1, b"\xfd\xe7\x43\x4c\x41\x66\x60\x22\xd6\xd7\xda\xbc\x7a\x67\x31\x5b\x1f\xf4\x9a\x2a\x85\xa6\x16\x8f\x2b\x60\x63\xe3\x03\x6a\x4f\x35\xe6\x6d\x28\x72\xaf\x3d\x97\xe5\xbe\xba\x23\x96\x98\xd8\x8e\x13\xbd\x03\x6e\xf0\x8c\xf0\xe8\x3a\x41\x66\x4c\x3d\x0d\x21\x86\x3c\x24\x12\x9a\x6a\x9b\x27\xb8\xe9\x6c\x80\x29\xec\x67\x3e\x07\xaf\x72\x46\xab\x77\xa5\x6c\x21\xca\x20\x8d\xf4\xb1\x81\x8d\xed\xa9\x06\xb5\x53\xb2\xb2\x3a\x37\xb5\xa0\x5e\x29\x82\x5e\xbe\xb4\x7f\x53\x98\x6c\x2b\xf2\x6d\x73\x1a\x5b\x73\x1f\xff\xc3\x53\x25\x8c", p, q, g, b"\x59\xa1\x4e\x36\xc9\xed\xed\xdc\xe8\x00\x0f\x04\xc6\xf7\x40\x1a\xd9\x87\xf1\xc7\xa5\xa0\x70\xb8\x0e\x0a\xae\xd7\x75\x1d\x1d\x50\xd9\x9a\x58\x0c\xf2\x05\xdb\xcc\x37\x97\xa0\xa0\x40\x6b\x04\x77\x6d\x80\xf2\xf2\xdf\x41\x8c\xee\x24\x9b\x98\x67\x2d\xe7\xe6\x1c\xda\x85\xcf\xbe\x90\x36\x90\xe5\x46\x42\xdc\x2a\x12\xa9\x0e\xcf\x88\xc5\x92\x56\xa4\xd7\x7c\x4c\x0c\xb5\x4e\x13\xfa\x36\x47\xb1\x14\x31\xe1\x73\x4f\x3c\xee\xea\x04\xfb\xf3\x45\x96\x65\xe9\x99\xfc\x0f\x7a\x75\x46\x83\xe4\x8c\xef\xeb\x4a\x95\xfe\x47\x39\x11\xff\xe0\xde\x0f\x73\x89\x60\x75\x3d\xac\x33\x66\x6c\x53\xed\x28\x93\xbc\x63\xdd\x41\x62\xd7\xa6\x32\x87\x39\xa2\x52\xcd\xae\xa7\xa9\x48\xc9\x7d\x02\x41\x53\xb5\x5d\x14\xfd\x53\x04\xe3\x57\x50\x48\x41\x88\x08\xa9\x52\x67\x5f\xaf\xb9\x5f\xad\x84\xb1\x15\x6b\x24\xe9\x8e\x04\x8a\xa7\x77\xa7\x45\x32\x4e\xc1\x3b\xa3\x78\xe8\x3b\x23\x84\xbc\x2e\x96\xc6\x09\x5a\xa7\x86\xbd\x28\xfc\x3b\xe6\xbf\xa4\xdb\x0c\x3c\x44\xfe\xd4\xc3\x51\xbd\x88\xa1\x9e\x17\x9a\x6a\x7b\xc1\x2f\xc0\x14\xf1\x7d\xe4\x6f\xd1\x2e\xf1\x28\x7f\x72", b"\x49\xea\x82\x71\x3a\xaa\xd7\x99\xe2\x63\x80\x9e\x16\x1b\x06\x55\xf1\xe7\x43\x23\xa0\x60\x41\x83\x6f\x67\x69\x80", b"\x76\xb3\xf6\xc1\x64\x7f\x8d\x17\x71\x8f\xfb\x92\xd6\xe1\x42\x46\x06\xba\x97\x24\xe5\x29\x0d\xaa\x4e\xe9\x5e\xfb", )?; test( HashAlgorithm::SHA1, b"\x66\x76\xa3\xa1\x31\xce\xf7\xe5\x64\x7e\xa7\x59\x0d\xa3\xc7\x04\xa0\xf5\xdc\x3f\x37\xf2\x69\x13\xa7\x0d\x43\x06\x09\xcc\x24\x97\xc4\x5e\x68\xb7\xbd\x6f\x58\x93\xdb\xa2\x62\x87\xff\x0d\x24\x0b\xab\x8a\x07\x61\x93\x6a\xa7\x09\xa2\x16\x2e\xbf\x1c\x20\xa6\x13\x6a\x74\x83\x52\xdc\x39\xba\x44\x03\xcb\xe4\xb0\xa5\xa5\x4a\x72\x92\x86\xdd\x19\x3e\xac\x1a\x2e\x6b\xdc\x15\x0f\xb0\x63\x69\xbe\x44\x43\xa6\x0e\x75\xe5\x33\x00\x83\xff\x00\x9e\xab\xb0\x52\x32\xc5\x23\x68\xa2\x6f\xd2\x37\xc7\xc3\x18\x5c\x1c\x7e\x7d\x59\x55", p, q, g, b"\xdd\xcd\xf4\xc6\x16\xfd\x6e\x40\x16\x09\x9f\xb3\x4e\xbc\x4e\xc5\x07\x29\x07\x62\xc5\xee\x68\x76\xf1\x0c\x6a\x2d\xed\xec\x97\xba\x86\xa6\x06\x3a\xa8\xff\x06\x9f\x3f\x3d\xb4\x0c\x94\x64\xaf\xb1\xba\x7e\xd6\x91\x77\x3a\xfd\x60\x83\x58\x6b\x14\xe3\x56\x94\xa9\xdd\xc3\x76\xdd\xc3\x9d\xac\x57\x13\x2a\x05\xbf\x88\xa0\xa6\x08\x5c\x72\xa8\x0a\x21\xc1\x3e\x59\x0c\x68\xc4\xe9\x8e\xed\xb6\x7f\x1e\x16\xc8\xcc\x7e\x9e\x25\xff\x37\xc8\x7e\xe3\xbe\x9a\xdf\x1a\xd0\xb8\x38\x65\x1b\x0f\xdd\xf8\xd0\x26\x96\x9d\x4a\x16\xbb\xb8\x28\xfc\xaf\x00\xef\xa3\x06\xfc\xed\xd5\xae\x19\xca\x1a\x1a\xbf\x44\xa2\xbd\xf6\xf9\x94\x12\x3c\xe9\x41\xfd\x35\x23\xbc\x13\x35\xf5\x1f\xa8\xdc\x5d\x52\x53\x58\xbd\xdf\x0c\x55\xfe\x2c\xe0\x7c\xe9\x74\x40\x8d\x90\x90\x48\x88\x37\x97\x6f\x16\x84\x5e\xb7\xa8\x2d\x04\xc4\x3a\x70\x4b\xe2\xde\xe1\xbe\x2c\x86\x83\xb2\xd9\xe5\xc4\x4f\x18\x33\xf5\xc4\x6c\x65\xb6\xe6\x2c\x2a\x72\x04\x21\xbb\x35\x84\x3f\xea\xd7\xb9\xe0\xb3\xfc\x04\xc6\x46\xbe\x39\xe8\x90\xe3\x70\xb9\x82\xbd\xe9\x1f\x2f\xc1\x84\x42\xb6\x50\xae\x60\x2f", b"\x16\x58\xa7\xef\x2f\x44\x4b\x01\x4a\x18\x85\xb1\xed\xa8\xda\xd3\x60\x5b\x96\xc3\x94\x8e\x54\x4e\x4c\x88\x25\xeb", b"\x60\x21\x50\xf6\x7b\x19\xa5\xe3\xe3\x9f\xc5\x3a\xbe\xa0\x2d\xd8\xf3\xb3\x0d\x25\xc0\xb4\xea\x0b\xcd\xdc\xbd\xb0", )?; test( HashAlgorithm::SHA1, b"\x07\x1f\x06\xa1\x15\x88\x58\x4d\xa5\x57\x60\x13\x02\x9b\x5a\x14\x71\x25\x81\xa4\x84\x08\xbb\xfd\xbe\x34\xe1\x75\x68\xc0\xa0\xe4\xd1\x2c\x1e\x9c\x3f\xb2\x27\x10\x14\x40\xdd\x8d\xcd\xc4\x15\xe3\xb4\x9f\x68\xa2\x6a\x0e\xc7\x61\x2a\x10\xbb\xc6\x4d\xdb\x8f\x7e\xc9\xe9\x75\x0d\x1e\xfc\x9c\x05\x74\x70\x08\x75\xfc\xf5\x2d\x00\xd3\x7b\x9d\xd7\x44\xca\x84\x1e\xcf\x75\x66\x97\x7c\x1b\x57\x99\xdc\x41\x05\xd0\xb7\xa9\x25\x51\xc5\xb3\x3a\x50\x13\x3f\xa3\x00\xa5\x90\x8b\x18\xf4\xc0\x19\x36\x34\x7c\x60\x49\x44\x7a\xbf\x29", p, q, g, b"\xb1\xf4\xdf\xc9\xc8\x34\x55\xf2\x79\xa3\xe7\x52\x27\x34\xd6\xb1\x20\xab\x8e\xd3\x6c\xcb\xa7\x85\x4e\x26\x50\x4c\x70\x79\x83\xd2\xa9\xc0\x75\x04\x57\x23\xf4\x41\xab\xfc\x7b\x36\xfb\xb5\xd4\xbf\x04\x47\x67\x8f\x5b\x70\x9c\xa5\x12\x9b\x74\x88\x8a\x07\x23\xe9\x05\x76\x98\x36\xb9\xda\xc1\x30\x3f\x1b\x9a\xce\x26\x55\x43\x42\xb6\xe6\xd0\x32\xac\x4b\x47\x7e\x01\x1a\x4d\xdd\x3e\x29\x78\xfc\x0c\x44\x9c\x64\xa6\x6e\xfe\x7d\x4e\x22\xe5\xa5\xfa\x2b\x01\xbb\x17\xfc\xdb\xec\x71\x85\xdd\x41\x15\xa1\x9d\x97\x2f\xb0\x1a\x06\xb3\x3b\xb1\x8b\x93\x49\xff\x95\xfb\x10\xdb\xbf\x2d\xcf\x89\x9b\x18\x17\xd3\x0a\xd4\x8a\x99\xa6\x14\xd5\x77\x70\xba\x76\x4a\x11\xa8\x4a\x8d\xb3\xaf\x30\x41\xec\x36\x26\x14\xf8\x07\x19\x6e\xa3\xb9\x0d\x05\xb0\x14\x05\x4f\xf4\xe1\x85\x24\xc7\x95\xe6\x72\x2c\x0f\xa1\xf6\xd1\x20\x5d\x53\x2d\x94\x34\x76\x33\xeb\x13\x2e\x6c\xbb\x59\x6d\x8b\x34\x1e\x65\xf2\xb2\xf9\x55\x87\x2e\xbd\x4d\x30\x06\xc4\x5a\xc3\x3d\xa1\x11\x67\xfa\x46\x86\x9c\x7e\xe7\x0e\x9c\xf1\x47\xb2\x33\x68\xb3\xaa\xcd\x9c\x18\x80\xb0\x9a\xc8\x6a\x8d", b"\x07\xbd\x3f\x67\x18\xe3\x98\x39\x30\x4e\xf5\x4a\xc4\x8b\xda\x8d\x9a\xc8\xee\x05\x1a\x49\xbb\x91\x31\xdc\xc9\x18", b"\x64\x96\xb2\x46\x9b\xfb\x58\x45\x48\x50\x04\x70\x2b\x0c\x79\x94\x1b\xc3\xc3\x00\x70\x07\xba\x16\x9d\x83\x07\xce", )?; test( HashAlgorithm::SHA1, b"\x71\x27\x9b\x84\x8c\x00\x20\x8f\xb4\xe4\xd8\x79\x79\xcf\x97\x3b\x32\x1b\x20\xd0\x98\xde\xa9\x12\xa3\xb4\xb5\x78\x9c\xdd\x3b\x7c\xcd\x8f\x39\x93\xa9\xc9\x2c\x34\xb7\x0e\x9b\x0b\xd5\x75\x20\xdb\x56\xf2\xde\xd3\xa6\x12\xa6\x16\x9d\x2a\x1c\xc6\x35\x09\x05\xed\x02\x02\xa2\x5c\x11\x3b\x7b\xf8\xfa\xec\x4e\xdd\x2e\xa3\xb8\xf4\x47\xca\x75\xd1\x5a\x71\x2b\x4b\x43\x94\xc2\x2d\xe0\xc2\x55\x4b\x9a\xa0\x7e\xc8\x46\x67\x27\xe7\xef\x6f\x1f\x04\xac\x45\x68\xd7\x72\x6d\x9d\x77\xf5\x0a\x2f\xd5\x51\xac\x29\xe4\x2f\x8d\xda\x23", p, q, g, b"\x7c\x8d\x63\xb9\xd5\x5f\x59\x29\x0b\x02\xa0\xfc\xea\x6d\x98\xc6\xc5\x45\xe2\xc0\xd4\xb1\x09\xa0\x69\x69\x4d\x80\xcb\x03\x4d\xbd\xbd\x9e\xdb\x6b\x4d\x9b\x15\x28\x49\xca\xbd\x65\x5f\xc7\x70\x71\x64\x4b\xbf\x4a\x0c\x7e\xa4\xed\xfe\x86\x4a\x43\xc4\x4f\xde\xd1\x63\xdd\x89\x9c\x21\xcc\x2f\x9c\x33\xcb\xf5\x6e\x8c\xaf\x84\x39\x4b\x24\xf8\xc1\x4e\x84\xf2\x2c\x3b\x0f\x74\x71\x29\xd9\xae\xf4\x1b\x6f\x1b\x1f\xa8\xff\x5a\x8f\x68\x0b\x49\x65\x95\xdb\xc7\xb7\xb6\x3a\x77\x90\xe3\x62\x87\x47\x01\x1b\x32\x77\xb0\x6e\x80\xde\x0b\x67\x94\x2f\x60\x2e\xad\xa6\x0b\x51\x8f\x28\x2c\xde\x69\xcd\x71\x7a\x5f\x6a\x19\xc8\xe1\x69\x44\x9e\x0d\x32\xa9\xd8\xce\x8f\x09\xa5\xad\xa2\x3c\x12\xa0\x2d\xcc\xfc\xdc\x02\x90\xa8\xbd\x46\xe8\xb7\xeb\x39\x74\x94\xf3\x2a\x0e\xcb\x49\xfa\x5a\x8e\xdd\x41\x84\x5e\xb4\x17\xfb\xb8\xcd\xb8\x9a\x9f\x18\xb9\xad\x1b\x41\xdd\x41\x22\xab\x34\x9b\xb3\xc4\x49\x51\xe4\xf9\x60\x43\x60\xfc\xb1\xb7\x95\x31\x15\x45\xa6\x1c\xfd\x67\xc2\x87\xa7\xc9\xd4\xd3\x53\x02\x14\x98\x8e\x76\x16\x97\x9e\x2c\xe9\x07\xd5\xc7\xf3\xe9\xad", b"\x4c\xf5\xc2\x6c\x4c\x2c\xd4\x8c\x05\x50\x8e\x52\xd7\x43\xef\x48\x68\x5f\x63\x24\x14\x1a\xde\xf2\x3d\x79\xa3\x96", b"\x59\xf6\x47\x55\xa0\x4c\x90\xa1\x4b\x18\x7a\xe1\x42\xec\x48\x3c\x46\x00\xb6\xfb\xbe\x19\xf0\x4a\x49\xe9\xff\x88", )?; test( HashAlgorithm::SHA1, b"\x3e\xa0\x3e\x9b\x00\x5e\xc1\x95\x4f\xee\x0c\x73\x32\x6d\x8a\xca\x1a\x4f\x63\x64\x8e\xb4\xcc\x59\x26\x55\x28\xee\x8e\x96\x9e\xce\xfe\xcf\x27\x97\xa0\x14\x4c\x83\x36\x50\x0e\x26\xa1\xc7\xcb\x1a\x64\x2b\x1e\xc6\x52\x01\x41\x6e\x5d\xeb\x35\x52\x01\xde\x2b\xda\x69\x5d\x1b\xeb\xa8\xde\xe6\x27\x72\xf4\xd5\x91\x4a\x24\x5b\xe9\xff\xec\xf3\x94\x08\xae\x7b\xf1\xbf\xf7\xc2\x45\x10\x29\xc4\xba\x0c\x52\x25\x16\xe8\x99\x55\xad\x3b\xd6\x99\xcc\xe9\x4c\x74\x40\x81\xa9\xf2\xd6\x0f\x5c\x51\x27\xec\x72\x2f\xa5\x73\x16\xce\xde", p, q, g, b"\x12\x39\xc3\x47\xbe\x4c\xe6\xf1\xda\xa7\x21\xfb\xbb\x14\x1e\xe6\xe2\xf7\xc7\x30\x98\xef\xfe\x8e\x71\xbe\xb9\xf1\xab\x72\xd1\xb5\xbd\x3e\x78\xdf\x77\x0f\x7f\xbd\x4b\x3a\x95\x05\x70\x2d\xac\xf1\x02\xee\xb8\xa1\x6f\x11\xb4\xf8\x09\xca\x00\x2a\xe3\x77\x4a\xc0\x40\x7e\x25\x72\xae\x3e\xe1\x71\x64\x58\xe5\xf4\x5c\x49\x3f\x4b\x92\x11\x44\xe8\x58\xd8\x7d\x63\x77\x3d\x02\x37\x45\x51\x2b\x0c\xc0\x2b\x31\xeb\xfe\x5c\x24\xad\x37\xef\xe5\x39\xcd\x39\x3c\xfc\x2b\x95\x1f\xe1\xb6\xff\xad\x2a\x28\x24\xc0\xf5\x4b\xd7\x76\xaa\x0a\xfc\xf9\xc1\xef\x42\x7a\xfc\x6c\xf4\xc4\xb1\x7f\x66\x35\x5d\x68\x57\x41\x32\xe1\xd8\x8a\xde\x37\x22\x51\x3e\x39\x5f\xc6\x2d\x65\xe9\x48\x51\x57\xc8\x20\x64\xc9\x08\x03\xa1\xa9\x1f\x9e\x6b\x10\xaf\x2f\x80\x69\x9d\x91\x7d\xaa\x6b\x81\x41\x5e\x50\x81\x93\x15\x2b\x4c\xcd\xed\x59\x3d\xde\x35\xf6\x45\xe5\x4b\x7c\xba\x44\x57\x75\xeb\x16\xc5\xe1\x90\x73\xf0\xa9\xeb\x53\x69\xbf\x25\x13\xb9\x21\x58\x16\x5b\x94\xde\xa5\x11\xe9\x38\xfb\x6a\x87\x98\xe0\x40\xa0\x5d\xa9\x4f\xdb\x5a\x4d\x44\xbe\xe9\x43\xb9\x5b\x39\xd9", b"\x5c\xa2\xe9\x71\xf2\x1b\x70\x12\x7a\x70\xc6\x55\xeb\x87\xe2\x0b\x25\x17\x97\x62\x28\xa2\xc4\xe6\x48\xd5\x49\xb2", b"\x44\x03\x6b\x34\x66\x71\x36\xa5\x14\x0d\xd1\x94\x8d\xdc\x2f\xb2\xbf\x67\x9e\xfe\xe2\x1f\x29\xb7\xad\x87\xaf\x7c", )?; test( HashAlgorithm::SHA1, b"\xa3\xf7\x03\x39\x58\xc5\xb7\x79\x07\x2b\x05\x48\xba\xed\xf4\xf8\x8d\x14\xf1\x1a\x6d\xd6\xee\xc0\x18\x1b\x39\x99\x43\xd7\x24\x6a\x45\xd5\x0c\x4f\x7b\x52\x95\xda\xe4\xcd\x3b\xa7\xc4\xc1\x81\xfa\x20\x15\x81\xad\x5c\x4b\x38\x79\x3b\xcf\x45\x4f\x17\x68\x68\xe9\xcb\xe0\x99\x7a\xa4\x19\x87\xb1\xaa\x3d\x5d\xdc\x04\x6b\xe7\xb0\x22\xfb\x51\x30\x59\x4c\x8a\x9d\xf0\x3c\xfa\xa7\xac\xef\x81\x7e\x3b\xa5\xe1\x92\xc6\x9a\x12\x02\x99\x49\x2b\xaa\x52\xa9\xbe\x83\xb8\xe8\x71\xab\xe3\x18\xb4\xa1\xf5\x88\xf9\xed\xcd\xda\xfc\x17", p, q, g, b"\x62\xde\x24\x65\xed\xbc\x1e\xf8\x45\x8b\xea\xa2\x05\xf4\x5f\x9d\xc0\xfc\x3d\xb7\x7b\xae\x0f\x2b\x13\xbe\xf6\xd8\x03\xdb\x68\x9b\x8f\x5c\x74\x7e\x3a\x04\x1c\x08\xd3\x26\xcd\x7e\x18\x91\x67\x5b\x02\x2a\x9d\xa3\xbb\xae\xf8\x00\x77\x84\xc5\x6c\x86\xc4\x17\x6c\x0a\xc8\x76\x35\x1d\x10\x62\xd9\xc2\x70\xd5\x48\xc8\xf4\xec\x39\xa4\x55\x6c\x66\xe7\x6e\x50\x7f\xc5\xf2\x54\x0a\xbf\xa7\x7c\x17\x8a\x9b\xae\x15\x34\x35\xa7\xca\xaa\x00\x8f\x36\xb9\xca\xb2\x13\xec\xf5\xe1\x9a\x0f\x7b\x1e\x62\xfb\x9a\x9c\x82\x23\xbb\x68\x9e\x85\x47\xb5\xec\x91\x5b\x04\xa8\x5b\x2f\x53\xcc\xc7\x92\xdc\x0a\x7a\x41\xd1\x72\xe6\xf5\x9f\x5b\x5e\x7c\x44\x03\x50\xac\x6a\x72\xca\x9c\x06\x56\x2d\x4c\xf8\xc6\x0e\x70\x87\x0a\x97\x83\x12\xe1\x9b\xf5\x4c\x24\x81\xc5\x82\x29\x6b\x64\x55\x4b\xd8\x71\xac\xcc\x8b\x25\x1a\x76\x17\xca\x5e\x5d\x2a\xad\xc1\x9d\x48\x4d\x76\xbc\x38\x26\x84\x1f\x88\xfa\xd1\x49\x1d\x80\x67\x92\x43\xe1\x52\x71\x97\xd0\x2a\x40\x63\x48\xb2\x47\xae\x78\x61\x08\xe5\x40\x09\x75\xa3\x8f\x39\x61\x75\x8a\xdc\x07\xce\x74\x0d\x8d\x44\x2f\x15\x2f", b"\x18\x23\xf0\xa8\x07\xfb\x9e\x71\xad\x69\xb8\xe9\xfc\x67\x4c\xf7\x6f\x67\xc4\x2c\xad\xbe\xa6\xd3\x4c\xf1\xf1\xcc", b"\x66\x7f\xc5\x7a\x44\xb2\x89\xfc\x34\xa1\x98\x55\x61\x17\xaf\xd6\x96\xdc\xbd\x96\xbf\x1b\xaa\xcb\x40\xd3\xf8\xb2", )?; test( HashAlgorithm::SHA1, b"\x68\x0d\x87\x8c\xa6\xee\xb8\x7e\x4a\xe1\x58\xdd\xdc\x37\x32\x78\x40\x13\xeb\xb1\xda\x89\x40\x1a\xcd\xd6\x10\x90\x89\xe5\x60\x1d\x69\x5f\x9e\x4e\x6e\xbf\x16\x02\x6a\xa7\x46\xde\xe8\x0a\x01\x23\x50\x33\xf2\x42\x07\x9a\xf1\xb7\xfa\x69\x65\xc8\x7e\xae\x8b\x32\x91\xa0\x09\xe4\xf1\x9d\x5b\x8f\x13\x94\xd8\x66\xe7\xc9\xb7\x20\x73\xa9\x56\x52\xc0\xee\xd9\x8e\x94\x84\xa1\x5c\x92\x44\x76\x4d\x8c\xba\xab\xd4\x9d\x24\xc2\x07\xc7\x05\x70\x3c\xc3\x5e\xbf\xc7\x68\x3f\x4a\x0e\x6a\xbf\x23\xfa\x07\x67\x83\x50\xa6\xa0\x0c\xde", p, q, g, b"\x51\x1a\x36\x08\xc4\xbd\xa7\xc8\x2d\x7d\x68\xa5\xd3\x0b\xd4\xc7\x1e\x45\x7b\x0f\x32\x30\x27\xd6\x01\xd6\x32\x4a\x94\x89\x3a\xb3\xd6\x28\x78\xb1\x2d\x98\xc4\x4d\xcf\x30\xad\xab\x43\x52\xb7\x0f\x4d\xaa\x77\x2d\xf6\xae\xd3\xc0\x75\x87\xe9\x6c\x68\xf8\xa8\x47\xa3\x35\x05\x14\x81\xd5\x39\x03\xd1\xd1\xae\x0c\xf9\x9a\x54\x38\x7b\x00\x16\x9a\x1c\x97\x04\xbb\x62\xf1\xd9\x80\x47\xdb\xa8\xa0\xfd\xda\x73\x4c\xd4\x16\x11\x58\x4d\x50\x55\x4a\xd7\x78\x90\x72\x0c\x8a\xc2\x99\x32\x09\x7c\xf2\xbb\x1a\x8d\x0d\xaf\x86\x63\x24\x1e\x23\x64\x0c\xc3\x96\xf9\xe6\x87\x73\x48\xf0\x14\x07\x3f\x4f\xdc\x5b\xeb\xd1\x15\xa0\xd7\x4c\x2c\xe8\x57\xe1\x00\xae\x3d\xc0\x70\x7b\x95\xef\xfa\x4a\x2c\xd8\x62\x9f\xdd\x9b\xce\x72\x09\x1c\x0e\x26\x12\xd2\xb3\x03\x20\x42\x0f\x42\xec\xb0\x98\x6a\xc3\x28\x92\x51\xb4\xae\x54\xe5\x1e\xd8\x3d\x01\x95\xde\xda\x9d\x4e\x5b\x39\x8b\x03\x72\x13\xd2\xf8\xf0\xff\xdb\xf7\x27\x21\x40\x85\x53\x4a\x32\x4d\x4f\xef\xc1\x65\x36\x42\x03\x5e\xbd\xbe\x81\x67\xb1\x50\xbd\x92\xb7\xcd\xf2\x76\xfc\xf5\xe0\xbf\xfc\xe9\x56\xa4\x7e", b"\x7c\xeb\x71\x48\x0b\x5a\x71\x33\x40\x1b\x52\x27\xfa\x22\x53\x33\x2e\x04\xf7\x8e\xa5\xd0\xfe\x23\x7c\x85\x25\xd1", b"\x48\x48\x00\xe8\x1f\x7b\x56\x92\xb7\x9e\xb2\x1a\xc2\xff\xf8\x3c\x49\xc9\xf0\xd4\x09\xc7\x56\xc7\x3f\xbd\xd2\xf0", )?; test( HashAlgorithm::SHA1, b"\x69\x7f\x9e\xfc\x86\x53\xfe\xdb\x89\x8c\x77\xf9\x0f\x12\x4b\xea\x5c\x3b\x89\x3c\x49\xd7\xf1\xb1\x16\x47\x9e\x83\xd3\x5c\xb6\xc3\x94\x07\x97\x50\x1e\x7f\x52\x88\x7d\x18\xae\x9f\x40\x55\xe1\xbd\xd1\x24\xb5\x72\xf7\xa6\xfa\xd1\x01\xf5\x8b\x52\xb3\x0c\xa3\x0d\x97\x43\xa9\x01\x6a\xf8\x91\x89\x6d\x25\x35\x6e\x44\xf9\x82\xd4\x06\xea\x26\xa9\xb2\x5f\xc4\xf9\x03\x09\x2d\x7e\x8e\x87\x13\x77\x4a\x8b\xe7\xaa\xac\x93\xa6\x94\x2c\x1f\x2c\x48\xe9\xde\xa6\x49\x84\xae\x54\xf7\xef\x99\x96\x1b\xfd\x9b\x8d\x93\x22\x6a\xf7\x76", p, q, g, b"\x64\xb5\x88\x49\x9c\x9d\xb3\xe5\x86\x41\x92\x46\x4d\x32\xfa\x35\x47\xf6\x48\xfe\x67\x6c\x15\x0a\x8f\x9e\x15\x3c\x5a\xf5\x79\x25\xc7\x6d\xda\x4b\x41\x9d\x60\xb2\x2f\xa5\xcd\xea\x0f\xb6\xf0\xb8\x47\x9c\x98\x8a\x32\x4d\x27\x5b\xd4\x2e\xf1\x0d\x89\x98\xc3\x60\x39\xeb\x40\x21\xfc\x0d\x27\x88\xb5\x9a\x75\xcf\x25\xed\x6e\xe4\xd4\x48\x82\xb0\xc5\xc5\xcb\x8d\xcc\x10\x02\xc0\xba\xa4\x79\x81\x07\xe0\xb5\x7c\xd2\x6d\xeb\xbc\xd0\xba\x41\xd1\xb1\xb8\x60\xb8\xeb\x90\xf6\xf3\x05\x00\xb2\xe4\xbe\x7a\x00\xb6\x7d\x93\xc8\x7d\x3f\xf7\xa6\xce\x53\xb9\x77\xa9\x30\x99\x98\x07\xfc\xbe\xf5\x7d\x8d\xc6\x7a\x8f\x36\x61\x24\x99\x13\x89\x32\x8c\xe7\xe7\x0f\x9e\x5c\x22\xff\xde\xdb\x28\x49\x82\x82\xb4\xa9\xa9\xc6\x85\x34\xa2\x38\x32\x2e\x0d\xb6\x08\x8e\xd0\xaf\xa8\xbc\x77\xce\x99\x8c\x81\x44\x71\xab\x56\x76\x7b\x35\xd0\x7b\x94\x29\x0e\xa1\x06\xff\x0c\x99\x8b\x51\xf0\x22\x22\x73\x8e\xf9\x30\x1f\x29\x0c\x6b\x48\x5d\xbc\x4f\x12\xb4\x72\xa1\x19\x2f\xd9\x3f\x2d\x23\x52\x7a\x02\xd9\x5a\xf0\xb4\x22\xbe\x76\x40\xa9\x70\x2e\xca\xac\x26\xc9\xe0\x04", b"\x62\x05\x4d\x11\x52\x9b\x99\x3a\x6f\x19\xa0\xd5\x48\x1b\x99\xb4\xb4\x46\x1a\x49\x86\x6c\x29\x53\x4a\x36\x1a\x8b", b"\x7a\x7f\xd0\x98\x2e\x4e\x21\x18\xd1\xa0\x69\x78\x7a\x80\xb9\x02\x49\x34\x65\xf6\x62\x0a\x35\x5c\x86\xa9\x48\x67", )?; test( HashAlgorithm::SHA1, b"\xd0\x80\xa7\xdf\xf1\xef\x20\xe3\x38\x32\xb9\x9c\xf8\x3c\x6c\x91\x9c\x07\x62\x0b\xf6\x08\xe0\x80\xaa\x30\x18\x31\xca\x61\x78\xe4\x4e\xf7\xa4\xc1\x15\xe9\x3a\xb6\xd8\x77\xe9\x66\x52\x17\x16\x10\xa5\x1d\x92\x7d\x20\x34\xf4\x2f\x28\x0f\xe8\x7d\x7c\x17\x47\xc4\x80\xeb\xcc\xbf\x56\x5a\x15\x0f\x32\x40\xf6\xd4\xce\x5d\x6e\xb0\xb2\xe9\x64\x41\x67\x91\x37\x6e\xd2\x2b\x35\x59\xcf\x93\xa0\x19\x67\x6e\x9e\x0b\xe3\xc8\xd3\x4f\x0e\x0d\x11\x52\xec\x6c\x32\x6d\x3d\xbf\x1d\x33\x03\xbe\xad\xd1\x88\xc3\xaa\x0d\x77\xe8\xa1\x17", p, q, g, b"\x41\x76\x7c\xe2\x6c\x78\x0e\x3f\x20\x19\xf5\xa4\x9a\x70\x15\x70\x14\x8e\x9f\xf3\x38\x22\x03\x83\x3d\x1b\x18\xe9\xd8\xd6\xa0\x0c\x0b\x22\x58\xf2\xe5\x67\xdb\x31\xad\x4e\x8c\xfb\x26\x21\x79\x4b\xac\x87\xd9\xb3\xb5\x3b\x79\x19\x9a\x77\x50\x58\xfe\xbc\x19\x0d\x00\xad\xed\xae\x0f\xd3\x02\x12\x91\xbc\x2d\x1f\xf0\x50\x8b\xf0\x19\xec\xa0\xc5\x73\xfd\x86\x37\x22\xf3\x67\xd5\xd0\x2b\xd9\xfa\x0d\x07\xf7\x54\x06\xac\x20\x4f\xd3\xa5\xca\x16\x32\x5c\x66\x1f\xe8\x54\xfd\x00\xfb\x26\x65\x47\x52\xfe\xbb\xe4\x39\x09\x6d\xd2\x28\x4d\x5a\xb1\x3d\xe9\xeb\x00\x48\x47\xd1\xd8\x59\x9f\xee\x68\x7c\xb2\xec\xd0\xe5\xb7\x61\xd9\x1a\x7e\x9c\x58\xe6\x92\x1f\x10\x30\x24\x21\x5e\x74\xf3\xde\x3c\xc1\x2f\x5e\xd7\x70\x3d\xef\x04\x1d\xd3\x26\x7f\x1c\xde\x0d\x4f\xda\x8d\xd5\xcc\xc9\xc0\x7b\x65\xde\x59\x48\x2c\x47\x84\xb4\xf6\xfa\x85\x66\x71\x86\xe2\xdf\x6c\x5d\xc8\xb4\x95\xbe\x8e\xc6\x13\x79\xf2\x09\x23\x57\x6f\x17\x68\x0c\x4d\xab\x99\x31\x2d\x0b\x64\x41\x30\x6a\xe7\x17\xc9\x5d\x3f\x35\x2b\xa4\xc0\x96\xf0\x1d\x14\xa7\xdc\x05\xb2\x8b\xa9\xa3\xca", b"\x44\xe7\x0d\x2e\xad\x3c\x51\xdd\x0c\x54\x61\xdd\x41\x86\x82\x5e\x23\xb4\xe7\x51\xd8\xab\x17\xd0\xb7\xed\xfa\xac", b"\x48\xff\xad\xe2\x75\x31\xdb\x47\x8f\x22\xfa\x0e\xc9\x2b\xcf\xd2\xff\xeb\x6d\xb6\x77\x15\xdc\xdc\x79\xbc\xb0\x28", )?; test( HashAlgorithm::SHA1, b"\xf6\xa9\xaf\xe2\x41\xdd\x98\x4e\x3b\xc2\x65\x78\x7d\xcc\x49\x49\x1b\x3b\xca\x67\xfe\xef\x32\xfc\x1e\x07\xfd\xaf\x0a\xf6\xc5\xd0\x6d\xcc\xb4\x7c\xdb\x69\x07\x51\x1c\xb3\x0c\x10\x9f\x62\xe6\x67\x18\xc5\xc4\xbb\x43\xd4\xb0\x0b\x51\x23\x5d\xf4\x32\x23\xd6\x0c\xe1\xf9\xbe\x34\x93\xa4\xdc\xb0\x2e\x25\xed\x3d\xda\xe1\x0d\x13\x1b\x48\x1a\x61\xae\xf3\x34\xb6\x90\xc7\xa1\xec\x74\x86\x59\x54\xb3\x9c\xcf\xa7\xa5\x1a\x9a\x1e\x62\xe6\x54\xbb\x89\x27\x0c\x77\x4f\x08\x2a\xdf\x09\xb5\x79\xc8\x35\x8d\xac\xb9\xdb\x7c\xa1\xc2", p, q, g, b"\xb4\x13\x8f\xa4\xe1\xdc\x67\x72\xb4\x7e\x5a\x3e\xd1\x30\xa1\x3b\x82\x23\x94\xc3\xce\x8a\x01\x93\xd1\xdd\xe4\xc9\x0e\x7d\xa1\x17\x8e\x11\x26\xdd\x29\x62\x52\xfa\x7d\x2f\x13\x9a\x14\x8a\xc4\x4d\xc0\x6a\x05\x8b\x84\xec\xb0\x3a\xd8\x27\xe6\x68\x92\xe8\x55\x29\xc3\x62\xce\xac\x2e\x71\x04\xb7\x97\xb2\xe9\x82\x60\x54\xde\x35\x05\x96\xab\x58\x17\x65\xe9\xa5\xc9\xff\x51\x43\x33\x2c\x2f\x3b\xfd\x24\x9a\x87\xfe\x1e\x30\xef\xd6\xfc\x05\x7e\x23\x4a\x1c\xd4\xc1\x9e\x07\x2b\xd7\x1b\x32\xd5\x5e\xf1\x22\xea\x93\x09\x11\x08\x1e\x26\xd9\x98\x49\x03\x76\xe3\xb7\x21\xcc\x32\xfe\xd9\x2b\x82\xd5\x45\xa7\xe6\xba\x6e\x4e\xb4\x34\x06\x3c\x87\xdb\x84\x8d\xf4\xef\x02\xed\xa3\xfd\xf4\xf9\xd2\x90\x5b\x78\xf7\xb1\x6b\x5e\xa0\xb5\x99\x8f\x1f\xbb\x0a\xaf\x62\xa1\x73\x55\x91\x60\x0f\x98\x01\x97\x7b\x1b\x94\x7f\x61\xa9\x1f\xf2\xaf\xb8\x72\x7c\x55\x26\x89\x72\xc8\x72\x16\xaa\xe9\x00\x61\x7a\x56\xf5\x35\xed\x18\xc4\xc5\xdd\xf8\xd7\xa5\x44\x63\x25\x6d\x09\x14\x4d\x88\x9c\x14\x9e\x5b\x09\xbd\xd9\xd8\x50\x93\x14\xb1\x03\xb8\x46\xf3\xe6\xfa\x1b\xb2", b"\x55\x5a\x45\x48\x80\x08\x4f\x6c\xb2\x52\x2d\xaf\x33\x99\xfb\x4a\x50\x1a\x94\x3a\x9b\x6a\xac\xd5\x8e\x2c\x7d\x37", b"\x73\x0f\xed\xb3\xa5\x91\x18\x44\x14\x60\x98\xac\x56\x03\xe2\xba\xaa\xe7\x69\x62\xb3\x3a\x32\x7b\x50\x42\x0a\x50", )?; test( HashAlgorithm::SHA1, b"\x2d\x1c\x57\x3b\xf3\x24\x02\x8d\xc2\xfe\x00\x92\x8f\x55\xf7\xfa\xc7\x90\x37\xd4\xd9\x9e\xb1\x85\xf3\xb9\x97\xe0\x42\xcd\xf8\x08\xb5\x38\x2d\x50\xa6\xaa\x80\x85\xc5\xd1\x95\x8e\x67\x28\x3d\xf6\x69\x86\xb9\x34\x71\xc1\x2e\x30\x45\xba\x14\x6e\xd5\x96\x5c\x8a\xc5\xb4\x46\x68\xf6\x19\x84\xd2\x17\x36\xcf\x1c\x27\x67\x54\xb8\x48\xe9\xfa\x63\x6b\x63\x15\xb2\x27\x2c\x19\xe6\x56\x26\xbf\x8b\x12\x14\xd7\x09\x89\xa6\x23\xb5\xff\xf7\x80\x3d\x28\xa6\x63\xbb\xbb\xeb\xb8\x4c\x83\x9b\x42\x72\x0f\xd0\xe6\x22\x46\xb3\xb0\x34", p, q, g, b"\x5c\xcd\xca\x35\x55\x1c\xf3\x55\xec\x85\xdb\x8d\x68\x01\x0d\xed\x63\x58\x32\x55\xb1\xd5\xfd\x2a\x52\x2e\x29\x51\x3a\xd3\xce\x61\x57\xbe\x30\xea\x1c\x30\x5d\x87\xde\x6c\x27\xfb\xe3\xa3\xfa\x50\x07\x12\x82\x75\xd6\xe6\x18\x3a\x65\xce\xc5\xb6\x94\xbc\x6c\x02\x73\x35\x06\x6e\x01\x27\x3f\xd6\x98\x1c\xc5\xf6\x0c\x3e\x33\x75\x13\x86\xce\x79\x2c\xcb\x6e\x6a\x6d\xb5\xd7\xf0\x73\x80\x03\x29\xf9\xcc\x46\xd1\x9f\x42\x29\x23\xb9\x74\x8d\xcc\xa4\x97\x1e\x43\xa9\xd1\xf5\x9d\x1c\x74\x97\x88\xa8\x52\x7a\xd5\x24\xdf\x74\x15\x0b\x39\xea\xfa\x7f\x4d\x56\x08\xd1\xc9\x72\x55\x65\x44\x56\xea\xdd\x4d\x38\x2a\xc5\x4f\xdd\x12\x53\x8b\x2f\x2e\xf7\x5a\x50\x98\x01\x71\xa0\x4d\x40\x54\xb4\xcd\x79\xc7\x1e\x1c\x4d\xeb\x3b\xc6\xaf\x4c\x87\x4f\x5c\xf0\x27\x38\x96\xd4\xfd\xc5\x84\x7f\xef\xdc\xc9\x7f\x54\x02\xc7\xe7\x64\x84\xd3\xd2\xd7\x0a\xc1\x6b\xda\x41\x99\x6c\xad\xcd\x83\xad\x92\xcb\x37\xc0\xc1\xe9\xd6\x4f\xa1\xab\xd9\xa2\xcf\x00\x5c\x2c\x29\xa1\x73\x7c\xdd\x6d\x63\xaa\x2f\xda\xa5\x60\x79\x9b\x9f\x07\xd4\x48\x76\x06\x78\x47\x76\x29\xf2\x2f", b"\x7b\xf3\xc0\xc5\x47\xe2\x18\x46\x21\x2b\xf4\xcf\x3e\x38\x36\x2d\xd4\xd3\x59\xb7\xaf\x64\x20\xf9\x0d\xa5\x79\x07", b"\x5e\xbd\x5d\x2d\x88\xca\xe4\x0b\x37\xa9\xa5\xa8\x4e\x62\x18\xd2\x45\x3a\xfa\x14\x6c\x79\xa5\xd5\xf5\xdf\x44\xf4", )?; test( HashAlgorithm::SHA1, b"\xba\xb4\xdb\x55\xbf\x6d\x3a\xbe\xfd\x1b\xb4\xe0\xf7\xbc\xec\x65\xee\x6c\x6d\x8e\xb0\x4b\x7c\x48\x0d\xf4\xe9\xe3\x91\x50\xf1\x0c\x38\xf1\xab\xb6\x3d\xfe\x1b\xb9\x75\x5c\x41\xb3\x89\x55\xba\x38\xba\x93\x8b\x6c\xee\xdf\xec\x02\x00\x1f\xa8\x70\x07\x0c\x59\xdf\x1f\xd2\xd7\x2a\x81\x41\x04\xc5\x14\x33\x76\xa3\x13\x6b\x81\x18\xf7\xb4\x7b\xd1\xff\xab\x53\x35\x9e\x53\xf9\x5c\x66\xee\x12\x70\x5e\x31\xa4\x62\xa8\xca\xae\x48\x15\x56\xce\xff\x60\x7c\xcc\x8b\xf1\x45\x07\x72\xcd\x68\x08\x1d\x3f\x15\xa7\x10\xe6\x56\xae\x56", p, q, g, b"\x53\xc0\xb0\xb0\x26\x9f\xcf\x29\x48\x66\x7e\x28\xb1\x1c\xcd\xa9\xcb\xb9\x27\x54\x63\xf2\x1e\xe3\x0d\xa3\x3c\x45\x75\xbe\x5e\x11\x1a\x18\x2a\x6f\x38\xb8\x90\xf2\x0b\x8f\x2d\x22\x4f\x59\x81\x89\x53\x10\xdb\x7c\x47\x03\xc1\xce\xc2\xb2\x57\xf4\x52\xd9\x64\xbe\x50\xc0\x14\xb7\x52\x36\x0e\xe2\x4f\x2f\xe1\xbc\xc0\x23\x47\x7a\x2d\x70\x85\xf5\x82\x14\xdf\x86\x6b\x13\xa8\xd8\xaf\x91\x31\x46\xdc\x0b\xee\x07\x8a\xea\x1c\xe6\x45\x99\x9b\x57\x94\x98\xea\xe9\x27\x7e\xd7\xe8\xb2\xc7\x5f\x49\x4e\xfa\xa7\x3a\x97\x3f\x32\x23\x2f\x08\xce\x7f\x0a\xfc\xba\x31\x66\x23\xb9\x41\x58\xde\x39\xbd\x4c\x0d\x51\x32\x34\xee\x1a\x48\x1d\x5b\x72\xf4\xee\xa3\x77\x49\xb4\x0f\xff\x12\xab\x62\x0f\x11\xaa\xa0\x1e\x35\x58\xe7\xa4\xc5\x50\x70\x7b\x71\xc1\x6c\xb8\xcd\xa9\x8f\x46\xbf\x71\x76\x9a\x47\x6c\x33\x85\xa8\xca\xf7\xc8\x86\xae\x47\xd2\x28\xb1\x77\x1a\x8b\xd4\xb7\xf1\x9e\x6f\x53\x04\x7f\x62\xf0\x29\xc3\x39\xfe\x75\x75\xbe\x93\x08\x0a\xc7\x48\x28\x91\x49\xa5\x7a\x0d\xdc\xed\x54\xd7\x2f\x6d\x4d\x34\x4f\xb8\x74\xcc\xc8\x5e\xa7\xf3\xdd\x21\x64\xdf", b"\x11\x8d\x22\x27\xbe\x4b\xd9\x1e\x98\xa2\xef\xde\x15\x60\x9b\x2b\x91\x24\xb2\xe8\x3c\x27\x4b\x63\x23\x00\x43\x2b", b"\x3a\x44\x74\x61\x94\x4b\x2a\x59\x27\x8a\x8e\x11\x18\xb4\x06\xbd\x3f\xf4\x16\x77\x5d\x65\x53\x0e\x54\xf9\xe6\x23", )?; // [mod = L=2048, N=224, SHA-224] let p = b"\xaa\x81\x5c\x9d\xb1\xc4\xd3\xd2\x77\x3c\x7d\x0d\x4d\x1d\xa7\x5e\xcf\xc4\xa3\x9e\x97\xd5\xfa\x19\x1f\xfe\xc8\xb1\x49\x0a\x29\x0c\xe3\x35\xe5\xce\x87\xea\x62\x0a\x8a\x17\xde\x0b\xb6\x47\x14\xe2\xec\x84\x0b\xf0\x0e\x6e\xbd\xb4\xff\xb4\xe3\x24\xca\x07\xc3\xc8\x71\x73\x09\xaf\x14\x10\x36\x2a\x77\x2c\x9a\xdd\x83\x8b\x2b\x0c\xae\x1e\x90\xab\x44\x8a\xda\xbd\xac\xd2\xe5\xdf\x59\xc4\x18\x7a\x32\xa2\x37\x19\xd6\xc5\x7e\x94\x00\x88\x53\x83\xbf\x8f\x06\x6f\x23\xb9\x41\x92\x0d\x54\xc3\x5b\x4f\x7c\xc5\x04\x4f\x3b\x40\xf1\x70\x46\x95\x63\x07\xb7\x48\xe8\x40\x73\x28\x44\xd0\x0a\x9c\xe6\xec\x57\x14\x29\x3b\x62\x65\x14\x7f\x15\xc6\x7f\x4b\xe3\x8b\x08\x2b\x55\xfd\xea\xdb\x61\x24\x68\x9f\xb7\x6f\x9d\x25\xcc\x28\xb8\xea\xa9\x8b\x56\x2d\x5c\x10\x11\xe0\xdc\xf9\xb3\x99\x23\x24\x0d\x33\x2d\x89\xdc\x96\x03\xb7\xbd\xdd\x0c\x70\xb8\x3c\xaa\x29\x05\x63\x1b\x1c\x83\xca\xbb\xae\x6c\x0c\x0c\x2e\xfe\x8f\x58\x13\x1e\xd8\x35\x1b\xf9\x3e\x87\x5f\x6a\x73\xa9\x3c\xba\xd4\x70\x14\x1a\x26\x87\xfb\xac\xf2\xd7\x1c\x8d\xde\xe9\x71\xad\x66\x07\x29\xad"; let q = b"\xea\x34\x7e\x90\xbe\x7c\x28\x75\xd1\xfe\x1d\xb6\x22\xb4\x76\x38\x37\xc5\xe2\x7a\x60\x37\x31\x03\x48\xc1\xaa\x11"; let g = b"\x20\x42\x09\x4c\xcb\xc8\xb8\x72\x3f\xc9\x28\xc1\x2f\xda\x67\x1b\x83\x29\x5e\x99\xc7\x43\x57\x6f\x44\x50\x4b\xe1\x18\x63\x23\x31\x9b\x50\x02\xd2\x4f\x17\x3d\xf9\x09\xea\x24\x1d\x6e\xa5\x28\x99\x04\xee\x46\x36\x20\x4b\x2f\xbe\x94\xb0\x68\xfe\x09\x3f\x79\x62\x57\x95\x49\x55\x1d\x3a\xf2\x19\xad\x8e\xd1\x99\x39\xef\xf8\x6b\xce\xc8\x34\xde\x2f\x2f\x78\x59\x6e\x89\xe7\xcb\x52\xc5\x24\xe1\x77\x09\x8a\x56\xc2\x32\xeb\x1f\x56\x3a\xa8\x4b\xc6\xb0\x26\xde\xee\x6f\xf5\x1c\xb4\x41\xe0\x80\xf2\xda\xfa\xea\x1c\xed\x86\x42\x7d\x1c\x34\x6b\xe5\x5c\x66\x80\x3d\x4b\x76\xd1\x33\xcd\x44\x5b\x4c\x34\x82\xfa\x41\x50\x23\x46\x3c\x9b\xf3\x0f\x2f\x78\x42\x23\xe2\x60\x57\xd3\xaa\x0d\x7f\xbb\x66\x06\x30\xc5\x2e\x49\xd4\xa0\x32\x5c\x73\x89\xe0\x72\xaa\x34\x9f\x13\xc9\x66\xe1\x59\x75\x2f\xbb\x71\xe9\x33\x68\x90\xf9\x32\x43\xfa\x6e\x72\xd2\x99\x36\x5e\xe5\xb3\xfe\x26\x6e\xbf\x11\x10\x56\x8f\xee\x44\x25\xc8\x47\xb5\x02\x10\xbd\x48\x4b\x97\x43\x1a\x42\x85\x6a\xdc\xa3\xe7\xd1\xa9\xc9\xc6\x75\xc7\xe2\x66\x91\x83\x20\xdd\x5a\x78\xa4\x8c\x48\xa9"; test( HashAlgorithm::SHA224, b"\xe9\x20\xfc\x16\x10\x71\x8f\x2b\x02\x13\xd3\x01\xc0\x09\x2a\x51\xf3\xc6\xb0\x10\x7b\xbb\xd8\x24\x3a\x96\x89\xc0\x44\xe2\xd1\x42\xf2\x02\xd9\xd1\x95\xa5\xfa\xef\x4b\xe5\xac\xad\xc9\xff\x6f\x7d\x22\x61\xe5\x8b\x51\x71\x39\xbc\xb9\x48\x9b\x11\x04\x23\xc2\xe5\x9e\xb1\x81\x29\x4f\xfd\xae\x8a\xad\x0e\x62\x4f\xab\x97\x4c\x97\xf9\xf5\xe7\xdc\x19\xd6\x78\xa9\xcb\x34\x29\xcf\x05\xec\x50\x90\x72\x85\x6f\x5a\xdf\xec\x6e\x29\xba\xfe\x8e\x5b\xa9\x55\x93\xe6\x12\x84\x3e\x34\x31\x11\xd8\x8a\x1e\xaf\xf7\xdc\x0a\x2e\x27\x7f", p, q, g, b"\x1a\xe1\x0c\x78\x6a\xd0\x90\x2c\x5c\x68\x5d\xae\x5c\x71\x21\x41\x8a\x37\x7b\x88\x8b\x5f\x2f\x2b\xc7\x66\x23\x57\x0f\xd6\x2b\xcb\x19\x0b\x47\x1a\xd5\x35\x9c\x5f\x06\x2f\x88\x19\x28\x9e\x95\x6d\x8a\xa6\xf9\x0d\x1f\x8c\xf1\xee\x72\xd3\xa1\xbd\xfd\x56\xc4\x78\xdc\x29\xa1\x9c\x45\x69\xb5\xa6\x0e\x3a\x8f\x34\xf6\x06\x56\xea\xc5\xb2\x5d\xde\x55\x14\xa5\xc6\x7b\x67\x54\x23\x20\x4f\x6c\xca\xf0\x99\x06\x17\xcc\x73\x55\xb9\xd3\xed\x86\x89\x78\xa2\x52\x02\x0a\x76\x9e\xd5\x9a\x6e\xda\xa6\xef\xe3\x37\x7e\xef\x45\xf3\xf6\xf3\xe6\x41\x79\xcc\x7d\xb8\xb1\x43\xfb\x83\x5c\x5d\x71\xbf\xcf\xa1\xe2\xa9\x04\x9b\xcc\xf7\xfe\x9a\xb5\x75\x46\x22\x0f\xe3\xf4\xb7\x52\x1c\x86\x17\x39\xd1\x38\x50\x7e\x81\xa4\x6a\x69\x93\x60\x54\x41\xdc\xb9\x0d\x6e\xe4\xaf\xbc\x42\xca\xbe\x90\xa2\x54\x44\x49\x68\x10\x9d\x7e\xdd\x96\x94\xa0\x23\x23\x9f\x1d\x56\x17\x5d\xd1\xfa\xc1\x15\x91\x5e\x24\xfa\xb5\x63\xf4\xfc\x3f\x26\x9b\xed\x2f\x30\x08\x32\xd1\x12\x59\x64\x85\xa7\x11\x41\x7a\xa7\x3b\xb4\xac\x72\xa6\x51\xa1\xfa\x5b\xae\xd3\x63\x6c\x72\x0d\x39\x70\x08", b"\x65\x10\x2e\x8f\x64\xec\xb1\x1f\x06\x01\x7b\x1a\x0c\x0d\xef\x3c\x29\x89\x7c\x27\x7c\x4a\x94\x8b\x1f\x4d\xa6\xb9", b"\x21\xad\x0a\xbb\x27\xbd\x3c\x21\x16\x6c\xb9\x6a\xef\x70\xc0\xdb\xd5\xf3\x07\x9c\xab\x0d\xd5\x43\xd4\x12\x5b\xd1", )?; test( HashAlgorithm::SHA224, b"\xda\x5e\x7b\x05\x1c\x18\x59\xd2\x2f\x2a\x31\x63\x33\x5d\x27\x79\x51\x97\x3c\x17\x2e\x06\x69\x7c\x04\x90\xff\x15\xb5\x92\xc1\xeb\xd0\xfa\x5e\xfa\x24\x63\x11\x98\x04\xa3\xfe\xa2\x24\xb9\x6b\x46\x3e\x30\x08\x3e\x00\x29\x49\xa2\x4e\x92\x20\x31\x76\x4b\xb3\xda\xff\x81\x01\xfa\x08\x8a\xf5\x45\x7a\xf3\x66\x54\xc6\x68\xf2\x34\xa0\x0c\xd8\x28\xcc\x74\x0a\x89\x8c\x0c\xd3\xdf\x09\x31\x5d\xa9\xb3\x46\xb3\x25\xb2\xfb\xec\x47\x52\x10\xb7\x54\x82\xaf\xfa\x61\xa3\xef\xf5\x0c\x83\xc3\xa0\x39\xfa\xe5\xcf\xa8\xd9\x71\xfd\xdd", p, q, g, b"\x5e\x27\x69\x87\xb8\x47\xb8\x52\xcc\x37\x2e\x98\x6e\x8a\xba\x06\x33\xdd\x46\xc4\x61\xba\xb5\x8a\xca\xe0\x56\xd4\xd1\xa9\xdf\x03\xa1\x9d\xf1\x14\xf6\x48\xb2\x8e\x03\x85\x06\xfd\x09\xad\x0d\x95\x44\x9d\x9d\x80\x58\xaa\x1b\x24\x1b\x2a\xcd\x3b\xad\xbf\x98\x82\x69\x73\x31\xde\x45\xb4\x52\x34\x5c\x05\x1c\x2c\xd8\x30\xf7\xcd\xd7\x48\x6b\x11\x66\xb9\x38\x91\xa7\x2a\x8b\x7d\xc6\x22\x8b\xad\x70\x87\x20\xef\x33\x23\x58\x01\xc4\xd4\xc3\xc4\xf2\x80\x36\xdf\x60\x29\xa1\x95\xd0\x01\x91\x24\xd1\x6f\xe8\xf7\x6c\x52\x5b\x7e\x8f\x04\xbf\x4b\x8d\x8b\xa6\xef\x60\x8e\x62\x32\x24\xfa\x8d\x98\x84\x20\xf4\x05\x26\xc2\x5a\xe3\xe4\xc7\x9d\x5a\xe7\xfe\xe6\x97\x93\xe0\x2b\xad\x96\x51\xea\x0f\xef\xd3\xea\xdc\x5f\xf1\xca\x2d\x14\x29\x30\x35\x5b\x1f\x3a\xea\x10\x22\x21\xfa\x17\xb7\x35\xa1\x8a\xf3\xb8\x33\x27\xc8\xf3\x3e\xfb\x9a\x49\xb7\x02\x11\x01\x4e\xba\x43\xfa\x65\xee\xaf\x25\xeb\xf4\x52\xbc\x4b\x7d\xc1\xf4\x07\xd0\xcf\x1b\x83\x46\x19\xb5\xf7\x3c\x6c\xab\x70\x51\xc9\x20\x70\xaa\x06\xf7\xf9\x40\x6c\x50\x7d\x1a\x15\xd1\x2c\x11\xbc\x83\x9a", b"\x31\x36\x15\x83\x6f\x0d\x33\x8d\x81\xb6\x70\xf1\x16\xa5\x41\x4d\x2c\xe9\x0e\xa5\xca\x53\x08\xba\x4f\x0c\x8a\x7d", b"\xdc\x1d\x4c\x3c\x06\x20\x3f\xd5\x98\xa4\x76\xc8\x91\xdf\xe5\x93\x41\x62\xd0\xd3\x5f\x37\xf1\xc0\x9d\xd6\x39\x5d", )?; test( HashAlgorithm::SHA224, b"\xf4\x98\x95\xb3\x29\x0d\x9a\xae\xb4\xaf\x61\x1c\x5e\x30\xaf\xc0\x04\x7d\xd4\x2c\x07\x21\x62\x11\xd5\x49\x77\xd1\x49\x7f\xa4\xee\x6a\xbe\x11\x00\x0d\x6a\xc0\x4d\x24\xb4\xc5\x0f\x31\xe0\x6e\xe8\xa7\x47\x74\xd3\xd3\x04\x13\x7c\xc6\xb1\x14\xd1\x45\x25\x0e\xe7\xe9\x4a\x12\xa1\xab\x59\x2a\xe3\x07\xef\x5d\x93\x0c\xf3\x91\x70\xe9\x75\x6a\xdc\x5e\x7b\xa6\x2a\x54\xab\xb6\xf0\x47\xb4\x50\x0b\x61\x21\xe1\xf4\xa9\x5d\x3c\x6a\x96\xf7\xf8\x33\x3c\xbb\x1e\xbe\xed\x8b\x4d\xb1\xa7\xfe\x75\xf4\x07\x1c\xeb\xfb\xbd\xfd\xab\x90", p, q, g, b"\x6d\x62\x25\x25\xec\xf5\x4d\xbe\xca\xa8\x11\x93\x9e\xe0\x7e\xf2\x97\x5d\x9d\xa9\xf7\xa3\xc5\x8b\xbb\x89\x3c\xe3\x88\x06\x77\x40\x4f\x2c\x6e\x59\x63\xb8\xc0\xb4\x49\x26\x01\xf1\x5b\xc6\xfd\xfd\x74\x7a\x00\xab\x83\x34\xe9\x05\x32\x01\xe1\xc9\xfb\xa5\x5f\xbf\xde\x36\xec\x54\x23\x75\x01\xb8\x74\x16\x99\x27\x71\xcb\x5a\xb8\x78\x1d\x0a\x96\x7b\x7f\x14\xf3\xd5\xde\x6b\x16\x65\xf6\x62\x88\x58\x78\xe5\x0a\xd3\x78\x27\xb9\x5c\x8f\x0e\x21\xd6\xbb\xeb\xc9\xdf\xd4\x7b\x29\x57\xd2\xfc\xdd\x1a\x2b\x25\xa6\x16\xe6\x98\x12\x9b\x45\x99\x8b\x6b\x6a\xa2\xa9\x9c\x1e\xbf\x42\x75\x49\x3e\x28\xee\xf1\xae\x34\xe9\xca\x63\xcd\x88\x86\xda\x58\x57\x29\x07\xaa\x9b\x71\x4e\x89\xbd\x36\x44\xa7\xea\x02\x9f\xa3\xa4\xae\x9c\x26\xe6\x65\xc8\x52\x96\x20\x4f\xdf\x86\xb7\xb1\xdd\x78\x66\xbc\x8e\x93\x85\xe9\x51\x8a\x27\x02\x48\x29\x25\x94\xc5\x4a\x4a\x03\xdc\x14\x92\x66\x4d\xda\xe5\x32\x77\xc6\xfb\xb9\xdd\x0c\xdd\x99\xbf\x11\xea\xf6\xae\x31\x92\x3e\x4f\x97\x9a\x7f\x58\x17\x99\xdc\x43\x2b\x19\x40\xf6\x13\xa7\xa7\xea\x68\x55\x23\x7f\x77\x6e\x91\xd4", b"\x79\xd5\x44\xcd\xec\xfd\x1e\xc1\xb7\xd1\xba\x63\x22\xa5\xe0\xeb\x85\x8a\xeb\x4b\x76\xd5\xb3\x20\x2c\xea\x23\x3a", b"\x0e\xa5\x3d\xea\x4c\xcb\x25\x97\x8a\x0a\xf5\x52\x95\x98\x91\x1b\x47\xc2\x5e\x0b\xa3\xb2\xa0\x50\x5f\xd1\xd7\xfc", )?; test( HashAlgorithm::SHA224, b"\x31\xd7\x39\x56\x69\x14\x54\x9e\xb2\x57\x26\xbf\x6d\x4b\x6c\x67\x4f\x47\x9b\xa7\xa4\x06\xac\xd1\x08\xa1\x06\xf3\x6c\x7f\x52\x14\x97\x6d\xcf\x3a\xdf\x2c\x83\xfd\x26\xb3\x7d\x52\xc0\xb5\xff\x51\xe6\xb3\x81\x1a\x8d\xcb\x02\x6a\x1f\xbb\x52\xf9\x50\x27\xea\x60\x34\xd9\x11\x49\xb3\x0a\xb4\x92\x8e\xde\x26\xdd\xd6\x92\xdd\xb8\xdd\xd9\x29\xfb\xff\x83\xfc\x67\x37\x88\xfa\xa0\xba\x5d\x96\x7f\xd1\x33\x92\x99\xe5\x5b\xe5\x1c\xea\x80\x60\x9d\x2b\x3c\x34\x33\xcf\x71\x3a\x96\x86\xe2\x29\x33\x6c\xfa\x7e\x72\x0f\xd5\x30\x3d", p, q, g, b"\x38\x6c\xbb\x8f\x7e\x72\x87\x51\xd4\xf6\xa7\x5f\x89\x05\x02\x98\x9b\x51\x22\x8d\x30\x39\xdd\x1a\xf7\xf2\xdd\x01\x86\xbf\x97\xa9\xff\x76\x3b\x40\x32\x3b\x30\xab\x0d\xc8\x1b\xf0\x9e\xf4\x8d\xb7\x2c\x0c\xfb\xe7\x72\xb3\xd3\x14\x92\x7e\xd1\x9b\xad\xee\x7b\x88\xb4\x9e\xe2\x94\x92\x37\x14\xad\xae\x30\xc9\x55\xd3\x7b\x99\xc1\xda\xdc\x4a\x29\xf0\xf8\xc2\xb9\xd1\x03\x8d\x17\x05\x9c\x58\x6a\x21\x2a\x97\x48\x72\x0f\xde\xc9\x5b\x42\x89\x71\xdf\x19\x23\xf0\x8a\x01\xd3\x58\x93\xd1\x2e\xd1\x7e\x0b\x14\x2e\xd8\xe9\xef\x77\xd4\x40\xa0\x1d\x77\x90\x5b\x92\xc5\x1d\xac\xe1\xb3\x45\xcd\x19\xf9\x16\x23\xa6\x96\x42\x88\xdd\xee\x6e\x99\x08\x19\x7f\x91\xda\x9a\x26\xf8\x06\xbb\x14\xe2\x37\x17\x42\xf8\x49\xcd\xc6\xce\x7a\x04\x5a\x70\x4a\x79\x2e\x57\x60\xd6\x64\x4e\xad\xb7\xcf\xfa\xba\x80\x6b\x05\x45\xfa\xe3\xb9\xfa\xda\xe4\xe3\x6b\xdf\x3b\x69\xc6\xdb\xbf\x0d\x8b\x05\x3d\xa3\x8b\x90\x4e\x9c\x4b\x94\x93\x25\xb2\xa0\x05\xb2\x49\x27\x6a\xc3\x69\x27\xb3\x17\x93\xf8\x01\x15\xb5\xe2\xf2\x10\x7f\x98\x77\x10\x38\x07\x08\xe2\xc3\x22\x89\x4f\xa8", b"\xc8\xb8\xa9\x2e\x8c\x10\x15\x05\xa1\x99\x1b\xcb\x02\xfb\x6e\x38\x2a\x3e\xcb\xae\xc8\xf4\x37\x45\x01\xb6\x57\xbe", b"\x20\xd1\x61\xce\xfd\x58\x49\x79\x22\x43\x79\xf2\x8d\x82\x7a\xa2\x19\xc5\x72\xf9\x60\x01\x47\xf4\x04\x8b\xa7\xcf", )?; test( HashAlgorithm::SHA224, b"\xd0\xa8\xa1\xca\x0f\xf2\xb4\x4b\x37\xff\x86\x00\x07\x33\x4b\x23\xbe\x49\x34\xff\x89\x05\x1d\x78\x7c\xe6\x9d\x3d\x7f\xa7\x34\xb9\x77\x9e\x2f\x0b\x38\xc2\x35\x39\x1a\x89\x7f\xb8\x51\x4b\x85\x7b\x99\x1d\x10\xe3\x4a\x00\xdc\x25\xb0\xc4\x38\x2d\xfb\x6d\x53\xaa\x87\xec\x17\x84\xf1\xca\xe2\x59\x92\x59\x40\x6d\x47\x56\x53\x98\x67\x67\x9d\x30\x88\x91\x3a\x13\x88\x71\xe2\xa4\x34\x74\x72\x22\xfc\xfa\xb0\x79\xd9\xe6\x55\xba\x25\x44\x63\xcb\x0c\x57\x86\xb9\x85\x8d\xc4\x29\xff\xda\xdf\x4c\x3b\x6a\x25\x3f\x90\xee\xba\x24", p, q, g, b"\x72\x47\xd4\xe1\x25\x3f\x0b\x52\xa1\x38\x8b\x79\x48\x15\xdb\x61\xc1\xa3\x54\xcb\x0f\x73\xfd\x19\xfe\xde\x61\x5c\x1c\x30\x25\x84\x0f\xff\x20\x4b\x0c\x6e\x61\x0e\xbe\xf1\x11\x3d\xf5\x6f\x67\x40\x6b\xad\xeb\x99\x44\x58\x91\xdc\xaf\xe1\x8d\x28\xf5\x97\x12\x60\x64\xdd\xf7\xaa\xf2\x03\xb2\xfb\x0d\x35\xd2\xf4\x58\xbb\x74\x34\x1a\xd9\x37\x21\x1e\xdc\x39\x4e\xc1\xa3\xf7\x90\x9a\x3f\x97\x2d\xb2\x7a\xa1\x35\xd3\x1b\xbd\x7e\x36\xc2\xbb\xc3\x60\x58\x5e\x7b\xb6\xe8\x32\x76\x40\x6b\x95\x25\xf6\x88\xee\x59\x95\xe7\xaa\x8e\xf7\xa7\x2c\x27\xe9\x90\xd6\x40\x16\xb9\x9a\x0a\xe4\xd0\x4b\x2f\x1b\x7d\x23\x8a\xf8\x8a\xc4\xc2\xe4\xe0\xf3\x29\x4c\xfe\xe9\xbe\x24\x57\xe4\x89\x55\x94\x8c\xf4\xbb\x3a\x44\x5a\x1d\x77\x8c\xed\xfa\x4b\x86\xf5\x9f\x15\x61\x18\x03\x4b\x2b\x83\x4a\x9a\xa1\x21\xe9\xd4\x82\xd6\x92\x22\x92\x82\x3b\xe2\x99\x1b\x3b\x5b\x42\xc2\x39\x25\xda\x29\x4d\x5e\xa3\x74\x06\xea\xf7\x8b\x7d\xc7\x25\x19\xd8\xf2\x61\x48\x2d\x6a\xff\xf0\xe5\x67\xbf\x6e\x67\x3d\xd8\x99\x60\xce\x73\x4f\x09\x2d\x98\x95\x63\x52\x42\x9a\x91\x84\x56\x94", b"\x9d\xab\xff\x22\xa4\x30\x12\xdb\xf4\x7d\x56\xb9\xae\x5a\x09\xf4\xd7\x39\xdd\x69\xfe\x90\x77\x25\xaf\xcd\x84\xf4", b"\xb6\x0c\x44\x72\x8e\x4b\x13\x90\xf3\x02\x38\xfb\xa1\xdc\x10\x03\xfd\xd3\x95\x07\xff\x5d\x6b\xa7\xe6\x09\xf2\xae", )?; test( HashAlgorithm::SHA224, b"\xe4\xff\xe7\x2c\x77\xc3\xa4\x3a\xf8\xa6\x1f\x58\xf9\x24\x0e\x1a\x07\xb5\xc2\x89\x4d\x5b\xdb\x65\x4b\x2b\x99\x4d\xc0\xc9\x87\xba\xd9\xb7\x04\x07\x5d\x3d\x0a\x96\x9c\xec\xfc\x98\xb1\xdc\x20\xe7\x6c\xd8\xe0\x12\x28\x58\x19\x46\x22\x26\xa8\x4d\xcd\xd6\x78\x95\xf6\xea\x27\x82\x66\xf1\x57\x5e\xa7\x85\xa2\xc3\x59\xf8\xf4\x59\x3b\xef\x31\xa5\x80\x91\xb6\x4a\xfb\x84\xcd\xfd\x23\xe4\xaa\xff\x29\xd9\x62\x6f\x0c\x82\x3d\x93\x42\x83\xa4\xfa\xaf\xc9\xc6\xcc\x18\x62\x23\x28\xca\xd9\x6f\x77\xd7\x9b\x9b\xa3\x5a\x43\xd8\x25", p, q, g, b"\x71\x46\x00\x9d\x12\xb0\x3b\x2f\x32\x30\x5f\x49\x5f\xaf\xcc\x4d\x45\x2e\xfb\x85\xcc\x80\xd6\x71\xff\x42\x49\x49\x2c\x66\x99\xfb\x26\xa8\x9c\xa4\xb2\x24\xd5\x6f\x6b\x8e\x74\x5d\xf9\xfb\xc7\x35\x2c\xa5\x83\x22\x2f\x4d\xea\xb1\x18\xf9\xfe\xc0\xb3\x4e\x33\x40\x60\xbd\xc2\x8d\xb8\x72\xe0\x09\x06\x49\x14\x94\x99\xe7\xa1\xc1\x97\x87\x8d\x3c\x72\x62\x43\x93\x03\xb9\x02\x01\xd0\xb7\xf5\xbe\x94\xd0\xa7\xc4\xeb\x15\x18\x29\x35\x29\x6c\x3e\x3f\xa2\xd7\x7d\x74\xd7\x8f\x41\xca\xda\xa4\x0e\xaf\xd4\x0d\x01\x78\x88\xca\xa0\x2a\x47\x48\x68\xe4\x0f\x49\x6b\x7b\xc1\xce\x36\x7f\x50\x34\x35\xe0\xd9\xa6\x37\x5a\xab\x03\xc2\x31\xd9\xcd\xaa\x15\xde\x23\xc4\x8a\xc0\x87\x8e\xf6\x49\xeb\x14\x4c\xe6\xbe\x4d\x2d\xe1\x1d\xa2\x02\xfa\xe8\x20\x90\x67\x3c\x83\xb3\x28\x40\xa3\x2d\xf6\x17\x6e\x1d\x55\x02\x7d\x7a\x1c\x1c\x56\xe6\x42\xf5\x1a\xae\xcc\xb3\xc9\x90\x89\x80\x61\xbf\xa1\x6b\x3d\xc1\x46\x10\x73\xc3\x33\x33\x7f\xd7\x6a\x31\x03\xf3\xfd\xe8\x21\xbc\x99\x4e\xbe\xdd\x6f\xfd\x79\x74\xd0\xca\x1b\x54\x96\x1d\x7d\xf5\xb9\xee\xbb\xfa\x26\xc3\xd6", b"\x4a\x2a\xbc\x68\x9d\x2a\x63\xe8\xb2\x32\x14\xa3\x21\x2a\x5d\x20\xa7\x38\x68\x82\xd5\xe1\x1c\x5d\x5d\xaa\x66\xbc", b"\x08\xe0\xc6\x54\x70\x87\xb5\x8b\xc9\x4f\xae\x24\x7e\x96\x2d\xa1\xa2\x89\x78\x88\xd1\xbc\x9c\x8c\xbf\x3a\xd6\xaf", )?; test( HashAlgorithm::SHA224, b"\xf8\xfe\xc1\x92\x88\xf3\xa8\xbd\x1d\x0d\x57\x3b\xbb\xc1\x80\x10\x60\x65\x69\x74\x81\xbe\xd9\x12\xf8\x75\x27\x50\xd3\x31\xe3\xa0\x97\x77\x5a\x12\x27\x6b\xc4\x29\x3a\x78\xa8\x07\x48\xb2\xb2\xc3\x7d\x20\xb8\x00\x33\x5c\x1d\x1b\x43\x0a\x71\xbb\xdf\xd8\xf7\xaf\xee\xec\x82\xce\xff\x2f\xd3\x3f\x26\x24\xe4\x9d\x37\x45\x7f\x26\x2c\xf5\xde\xde\xf9\x02\x5c\xe9\x6e\x0b\x7d\x49\x9f\xcc\x7a\x7f\xf0\x6c\x02\x59\x0e\xa8\x21\xdd\x8e\xd0\x60\xca\xbc\xf4\xfe\xec\x95\x92\xac\xed\xdf\xd3\x2b\x4c\x09\xe4\xd4\x49\x38\x43\x5b\x82", p, q, g, b"\x7e\x50\x01\x1d\x42\x29\x86\xea\xe0\x1a\xe6\x89\x43\xdc\xa0\xc8\x7a\xf4\x4f\x7b\x87\x9b\xd1\x25\x6d\x4c\xaf\xfa\x0e\xb1\x92\x50\x29\xc0\x63\x3a\x7a\xc6\x74\x87\xa7\xb6\xf9\x8a\xd7\x7e\xe7\xe1\x44\x2d\x12\x9d\x06\xdb\x47\x5a\x4f\x78\x04\xfd\x8c\x6a\x03\x81\x51\x91\x1f\x81\x39\x7e\x96\x35\x94\xb9\xc9\x1e\x3b\xfe\x94\x32\x8f\x05\x6e\x9b\xdb\xb9\xb1\x1f\x54\x93\x9d\x7e\x23\x7a\xaf\xb0\xc9\x50\xe0\x58\x1c\xab\xfe\x94\xbc\x26\xf0\xe0\xd5\x56\x09\x97\xbf\xb0\xf6\x35\x7b\xbf\x2c\xad\xb0\x10\x8e\xc0\x09\x56\x46\xe4\xca\xa2\x2f\x71\xe1\xf1\x7a\x9f\x34\xe8\xa8\xc4\xb7\x1c\xf0\xb1\x26\x5e\x00\x15\x54\xfa\x91\xf1\x8a\x17\x56\x2b\xc0\x94\x8c\x43\x1f\x25\x94\x59\x62\xba\x7f\xaf\x7d\xcb\x64\xff\x0b\x8b\xdd\xe7\x01\xe1\xdf\x62\x0a\x11\xaa\xd0\x71\x96\xd6\x7a\x95\x6e\xbe\x49\x8a\xe6\xf8\x23\x24\xf7\x5c\xaf\xbe\x80\xed\xaa\xbe\xf0\x03\x7b\x79\xc3\xed\x65\x8d\x9b\xa1\xb5\x42\x2c\x4a\xc0\x53\xba\x69\xbb\xaf\x7f\xa9\xdb\x99\x0e\x8b\x5e\x7f\x9a\xf5\x7a\x79\xf3\xe3\x1c\x07\x61\x1f\x50\x2b\x30\x15\x96\x2b\x02\xb6\xb4\x25\x70\x6e\x0a", b"\x2f\x38\xc5\xcf\x86\xaa\x0e\x53\xd1\xfe\xa0\xe6\x5d\xd0\x38\x13\x64\x04\x04\xb8\xd9\xa8\xcd\x6d\x26\x4d\x92\x85", b"\x47\x60\x38\x80\xf3\xd6\x7b\xa1\xa6\xea\xbc\x20\x13\x7d\xc4\x88\x2e\x41\x73\x04\xcb\x95\xd6\x22\x17\x7d\xf5\x11", )?; test( HashAlgorithm::SHA224, b"\x75\x59\x46\x5a\xf5\xca\x04\xc1\xe7\x4d\xeb\x9f\x8e\x46\xb0\xef\x17\xde\x4d\x7a\x2a\xe0\xfa\xf4\xe9\x03\xa2\x99\x8b\xca\xa0\x9b\x7f\x17\x30\x39\x33\x20\xeb\xc5\x7d\x05\x2d\x2e\x98\xf5\x48\x6e\x8e\x92\xbd\x1e\xe6\xbb\x0f\xfd\x02\xd6\x9e\x5d\x45\x91\xe2\xfa\x12\xe4\xeb\xff\x8b\x6b\x9d\x32\x70\xfc\x75\x27\x4f\x8f\x82\xe1\xc6\x0e\xdb\x2a\x21\xf8\xd5\x53\x1a\x23\x80\xcb\xeb\xb2\x4f\x64\x57\x17\x6e\x54\x76\x9a\x13\x66\x01\xa9\xb8\x1d\xa6\x8f\xf1\x96\xff\x8c\xc7\x8c\xf0\x59\xc0\x4a\xe2\x24\x59\xce\xc7\xda\x89\xb6", p, q, g, b"\x5b\xcd\x42\xe5\x86\xca\x18\x0f\x74\x33\x95\xfc\x39\xe2\xbd\x39\x38\x20\xf5\xb4\xc4\x9c\x7c\xb7\x69\x21\xec\x38\xbb\x53\xe8\x64\xfb\xe8\x09\xa0\x33\x77\x5f\x16\xc7\xf5\xc6\x48\x72\xfe\xdd\xe6\xab\xc5\x60\x48\x8e\x57\x29\x55\xed\xd3\xf9\x56\x90\x92\x07\x1e\x56\xdf\x21\x15\x64\xf3\x31\x85\xdb\xff\x18\x0e\x7a\xb2\x29\x77\x00\xc6\x4d\xb6\xe2\x20\x70\x1c\xb8\xa2\x1e\xad\x2e\xa8\x09\xf0\x6a\x16\x55\x43\x19\xb2\x73\x9d\xe2\xac\xa8\x05\x7a\x62\xd4\xca\xa7\x95\x7a\x2b\x9f\x03\x9b\x3c\x7d\x4f\xb0\x76\x1a\x73\x30\x2a\x6f\xbb\x58\x31\x00\xb2\x39\xd7\x27\x15\x8b\x4c\xdc\x97\x65\xfe\x04\x85\xaf\xb6\xa1\xb0\xac\x0d\xb5\x04\xa9\x47\xf3\xd8\x7f\xaa\x55\x42\xc6\xee\xf7\xa6\x81\xc5\xfc\xd2\x8f\x46\x36\x36\x0f\x55\x93\xbf\xf7\xe4\x33\xb6\xa3\x38\xd7\x7e\x3d\x63\xf6\xce\xff\x69\x53\x6e\x2a\x3f\xf7\x7a\xce\x74\x5b\x65\xa5\x16\x0d\x7f\xbf\x91\x05\xa9\x0f\x46\xce\x1c\x54\xfa\x35\x3c\x8a\xee\xbe\x16\xfb\x23\x8c\x8e\xd9\x98\x61\x7b\x63\x28\x75\x11\x20\x8d\x9d\xb3\xf6\x6d\x50\x33\x74\xbb\xda\x48\xa5\x52\xd0\x4b\x2c\x30\x4a\x15\xba\xc0", b"\xc5\xd3\x3f\x5a\x4f\xe2\x28\x0a\x9b\x96\xd7\xa9\xb5\x53\x0d\xc1\x7c\xd1\x05\x4b\xf1\xe8\xcf\x6f\x4a\xa3\xe2\xac", b"\xc9\xbf\x1c\x06\x2b\xd1\xe8\x6f\x3b\xd3\xc1\xff\x58\x2c\x33\x27\x05\x37\xfa\x77\x69\xb9\x59\x2a\xef\x12\xe1\x04", )?; test( HashAlgorithm::SHA224, b"\x16\x74\x82\x38\x96\xc5\xa7\x64\xc6\x1f\xd1\x9b\x12\x5a\x7d\x6c\xd5\x8c\x88\x3d\x86\x79\x43\x91\x47\x73\x49\xf0\x36\x16\xd7\x5b\x69\x25\xe9\xdc\xc5\x53\xde\xa3\x70\x47\xf0\xcd\x15\x31\x68\xeb\x26\xe5\xad\x4b\x8f\xe7\xcc\x65\xe4\xfa\x27\x55\x14\xc8\x42\xaf\x63\x50\x7f\x90\x1f\xd1\x10\xb9\x82\x49\x13\x3d\x3d\x12\x66\xd2\xf9\x67\xc8\x5b\x7f\x88\xdd\x76\xc7\xf7\x6b\x78\x6b\x55\x72\xdc\xae\x68\xcc\x64\x6e\x45\x8b\x82\x78\xdb\x34\x6b\x2e\x97\x0c\x78\x70\xcf\xfd\x84\x57\xfb\xec\x06\xbb\xb5\x14\x15\x75\xf4\x0f\xde", p, q, g, b"\x5c\x34\x13\x5c\x90\xf9\x7e\xbc\x9b\xf1\xed\x98\x6e\xba\x56\x3e\x32\xce\x8c\x25\xae\x71\x41\xdf\xef\xca\x86\x00\xad\x2f\x3c\xbe\x8e\x45\xb4\xa0\x10\xae\x49\x97\x82\x0a\x38\xb4\x88\x81\x87\xbf\x20\x7b\xde\x43\x8a\x1e\xc7\xbe\xff\xf8\x1a\x64\x26\x5a\x4c\xe9\x90\x0b\x37\xa3\x8e\x4f\xc2\x36\x13\x88\x7b\x63\x8a\x11\x3e\xf4\x16\x65\xad\x2b\x1f\x15\x76\x4c\xb5\x36\x07\xd0\xee\xc3\x03\xac\x48\xc0\x55\xf5\xaa\xda\xbc\xfb\xe2\xc5\xfa\xa8\x5e\x02\x9c\x43\xe1\x60\x7a\x3a\x29\xf6\x58\x02\x95\x9b\x68\x6b\x46\x8e\x81\x07\xc4\x66\xa7\x31\x7b\x50\x63\xe0\x38\x02\x19\x75\xb2\xf0\x17\xf1\xf3\xba\xd0\x7c\xd0\xeb\xb4\x87\x96\x41\x51\xe4\xf8\x2b\xb5\x27\x7c\x35\xa2\x18\xec\x57\x0c\xb5\x68\xad\x04\x04\xa3\x71\x3a\xb7\xfc\xc1\x29\x7b\x1e\xa9\x74\x3f\x85\xac\x5d\x5a\x7e\xc8\x18\xe5\xf9\x0a\x4a\x58\xf2\xc2\x19\x2b\xba\x6d\xff\xec\xbc\xd3\x9f\x24\x5c\xc9\x32\x95\x31\x90\xee\x35\x3a\x0c\xa9\x9d\xc6\x1e\xac\x4b\x4f\x83\x46\x18\x14\x0c\x9a\x32\xec\xa3\x1d\x71\x8c\x95\xee\x03\xb2\x99\x2c\x63\xa6\x83\xb0\x62\x88\x83\xa5\xc2\x22\xfd\xde\xf0", b"\xb1\xa9\x46\xfa\x42\xa3\x6d\x83\x6d\xaa\xb5\x6f\xe0\x15\xc9\xf2\x9c\x45\x44\xa4\xa4\x7d\x48\x2e\xa2\xd9\xcc\x5b", b"\xe2\x90\x5e\xe7\x0a\x5d\xc0\x99\xb7\xe0\xba\xec\x55\x66\xb2\x29\xe9\xca\x8e\x7e\x00\x84\x09\x66\xcf\x56\xc4\xd5", )?; test( HashAlgorithm::SHA224, b"\x28\x1f\xd1\x4a\xe2\xe7\x02\xdb\xd2\x5f\x77\xd8\xba\x8a\xf0\x9f\xdd\x77\xb1\x83\x96\x48\xab\x9c\x88\x0b\xd1\x19\xd4\x47\x53\x78\xfc\xd0\xd1\x24\x15\xab\xb9\xf2\x6b\xfb\x8e\x26\xf1\x08\xb1\x29\x88\x59\x23\x5e\xd1\x2e\x7f\x9e\x91\x56\x28\xe3\xca\x36\xc5\x98\x6d\x18\x81\x1a\x59\x05\xae\xf7\x87\x8c\x63\x00\xa9\x5e\xa8\x71\x82\x01\x6e\xc5\x95\xd3\x2e\x4d\xfc\x27\x4a\xdb\x47\xc3\xed\x0f\x6c\x38\xec\x89\x3b\x33\x1f\x70\x92\xf1\x9b\x72\x4b\x9f\xe4\x3f\x0e\xf8\xde\xc1\x4f\xb7\xbf\x8b\x90\x41\xb5\x39\x0b\xeb\x44\x08", p, q, g, b"\x48\xed\x8f\xa8\x9d\x07\xde\xb5\xf8\xee\x6d\x38\x74\x8a\x4e\x66\x00\x20\x20\xf7\x9f\xf2\x2d\x66\xfa\x53\xad\x91\x3d\x59\x68\x60\xd4\xdb\xcb\x7c\x3a\x66\x33\xcd\x42\x24\xa8\x0e\x5e\x95\x90\x8f\x87\xb1\x8a\xcc\x2e\x36\x4c\x14\xb5\x1d\xe6\xbd\xda\x7a\xd8\x96\x1d\xfd\xa4\x54\xef\x47\x98\xd0\xf7\xa3\x0e\xf1\x0e\xae\x87\xde\x40\x86\x77\x64\xb8\x4b\xc5\x5d\x7c\x02\x83\xf9\xc7\xcd\x2b\xe0\x8e\x18\x52\x48\x75\x12\xff\x43\xa8\xd1\xe6\x8a\x95\x11\x97\xc7\x71\xf9\xe6\xc2\xff\xdf\x2c\x00\xed\x21\x63\xf8\x6d\xff\x52\x41\xf9\xe2\xff\x1c\xdb\x05\xa0\xb3\xe6\x47\xe6\xfd\x23\xcc\xad\xa8\x3b\x9c\x59\x61\xe6\xe2\xfe\xf3\x29\x74\x93\xdd\xb0\xe9\x90\x29\x5d\x38\x40\x5a\x24\x44\x8e\x24\x96\x27\xc0\xa7\x99\x8c\xc4\x07\x2d\xd2\x91\x39\xc5\x33\x6d\x98\x56\x01\x66\x42\x99\x2c\xd2\x45\xc7\x58\xa3\x03\x1e\xc2\x80\x7b\x17\x1a\xba\xee\xf1\x4c\x82\xa3\xda\xb2\x01\x75\x23\x51\xde\x2b\xff\xa5\x08\x5c\x13\x76\x56\xe4\x69\x58\x1f\x63\xf8\x63\x79\xd6\x28\x68\xac\x3e\x3a\xa2\x4d\xf9\x82\x6a\x83\x33\x14\xbd\x41\xe0\xd9\xa0\xae\x56\x80\xe6\xa4\xd2", b"\x5a\x77\x63\x96\x63\x66\x4e\x3f\x0b\x19\xfd\x58\x3b\xab\x6e\x68\x06\x88\xcd\x89\xd5\xe0\x12\xdd\xcb\x1e\x06\xbc", b"\xd4\x1c\x78\x4b\x58\x3c\xbc\x52\x5b\xce\x87\xc6\xca\xa4\x40\x62\xea\xc8\x47\xbc\xa8\xb0\x05\xc1\x2a\xb5\xe5\x54", )?; test( HashAlgorithm::SHA224, b"\x50\x3f\x20\x42\x35\x8f\x7e\x41\x42\x96\xab\x2d\x41\xf3\xa1\xf3\xf1\x11\x82\xec\xa6\xc8\x2b\x2a\xe6\xee\x83\x3d\xd7\x37\xbc\xb3\x46\x91\x79\x3e\x30\x11\x00\x36\xae\x54\xd4\x03\xa5\xea\x45\xcb\xf3\xe5\x51\x5b\xbf\x80\xb1\xaf\x13\x98\x53\xf5\x06\x79\x2d\xf7\xff\x52\x35\x99\x5e\x08\x0f\x82\xb5\x62\x32\x6a\xda\xf3\x21\x15\x9a\xde\xef\x20\x38\x80\x24\x50\x9f\x22\x5e\x8c\x52\x35\x36\x8a\x7b\x04\x5d\x69\xe4\x72\xe6\xb2\xad\x7d\x47\x0a\x11\xf6\xaa\x8d\x4c\xa6\xc6\xcd\xb0\xf3\xed\x4e\x06\xfb\x9a\x95\xe2\xcf\x20\x0c", p, q, g, b"\x7e\x51\x4a\x04\xbb\x57\x5a\xb9\x3e\x71\xb3\x55\x5c\xdb\xac\x63\x4d\x47\x5c\x58\xc1\xd9\xb4\x80\x2e\x15\x3a\x85\x8d\x02\x78\x04\xea\x74\x8c\x29\x07\xeb\x99\x87\xf7\x8e\x41\xc6\x75\x7e\xd5\xcb\xf1\x02\x54\x4a\x71\x46\x99\xa0\x2a\x9e\xf1\x47\x68\xf9\x6d\xbb\xdf\x48\xf3\xb2\xb3\x79\x2e\xfb\x97\x3a\x7f\x91\xf2\x60\xe0\xde\xa2\x80\x34\xc9\x15\xd9\xd5\xa8\x7a\x8f\x98\x6a\x15\xf5\xd6\xf9\x8d\x7d\x6d\x35\xbe\xe7\xe0\x59\xae\xdb\x59\xfe\x59\x5b\xa7\xda\x17\xce\x0d\xb8\x95\xf3\x41\x1b\x83\x2a\x1e\x22\x1a\x83\x1f\x70\x65\x87\x84\x1d\x93\x23\xe0\xc7\xf4\x43\x57\x03\x12\x70\x84\xb2\x0e\xda\x9c\x6a\x24\x97\x28\x01\x90\xa2\xb5\x27\x3b\x23\x1b\x44\x48\x2c\x92\x53\x50\x1c\x66\xef\x11\x22\x25\x3b\xe4\xea\x34\x77\xff\x61\x86\xaf\x87\x18\x69\xaf\x1b\xa1\x0f\x6a\x15\xd1\xc4\x32\x94\x03\x17\xd1\x19\xdd\x76\x1c\xa0\x34\x2a\xb6\x06\xd5\x32\xc4\x71\x78\x3a\x4d\xcd\x6f\xac\x9b\x8a\x67\xa6\xba\xe1\x87\xc7\xdc\x64\xc7\x61\x1d\xed\x72\x73\xdc\x34\x8c\xd7\x61\x3a\x52\xd0\x26\x70\xe8\x77\xe1\x8d\x0b\x60\xc8\xbb\xdd\x1a\xdb\x04\xef\xf2\x13", b"\x84\x86\xab\x31\xc8\x27\x8f\xad\x06\x91\xfd\xd6\xca\xc2\xf5\xfd\x79\x0b\x2f\x3f\xed\x52\xb0\x99\x86\x76\x60\x42", b"\xb6\x96\x7b\x9e\xac\xde\x5f\x48\x83\x71\x0e\xba\x38\x7b\x3c\x6f\xed\xfc\x91\x94\x4e\xa5\x1f\x6f\xfa\xb7\x25\x31", )?; test( HashAlgorithm::SHA224, b"\x65\x0c\x3c\x40\x9a\x88\x5f\xa6\xd1\xac\x1f\xf4\x1e\x15\xf9\x00\x1f\x6c\xd6\xa1\x52\xc3\x76\xfd\x22\xe2\x85\x1c\x9c\xba\xa5\x35\x0d\x8a\x92\xb7\x40\x10\x30\x80\x93\x95\xcf\x0b\x1a\x0c\xb0\x3a\x24\xdc\x3b\x43\x47\x05\x0e\x85\x53\xda\x0e\x61\xd8\x1d\xee\x44\x02\xb1\xce\xc9\x7d\x89\x8d\xc6\x88\x66\x01\x02\x4f\x6b\xfb\xc4\x8d\x2f\x2c\x40\xbf\x96\xde\x9b\xc0\xe0\x78\xe4\x40\xc7\x71\xf7\x4e\x71\x15\xad\x22\xba\x99\x4a\xe2\xf8\x57\xc7\xfb\x86\x5e\xa7\x50\xb1\x8c\x79\xe7\xb0\x48\x56\x3b\xec\xef\x88\x98\xce\xd3\xdd", p, q, g, b"\x55\x18\x6d\xe3\x9e\x6a\x01\x31\xad\xb7\xd8\x41\x70\xa8\xd3\x6a\xc4\xbf\x31\x36\x16\xe7\x50\x22\x0d\xe3\x56\xfb\xb1\x89\x9d\xba\xaa\x65\x0d\x8d\xe9\xa7\xaf\xab\xf3\xc4\xdd\x6a\x3c\x8b\xac\x24\x19\x22\xac\xbc\xc4\xbb\x7f\xa4\xce\x5f\xcd\xb5\xf2\x31\xcb\x17\xa8\xc0\x97\x8c\x8e\x69\xfb\x82\xd4\x46\x83\xeb\xb9\xfb\x17\x89\x8e\x0b\xa4\x93\x91\x96\xed\x99\x80\xeb\xec\xab\xba\xad\x7b\x5b\x34\xcd\x9e\xc0\xea\x6d\xf9\x62\x43\x82\x3b\x1d\x17\x0e\xfc\xcb\x4d\x59\xbc\xba\x24\xce\x5f\xaa\xd3\x2d\x59\x1a\xd6\xec\xe0\x44\x0d\x2b\x62\xa2\x12\x05\x9e\x00\x0f\xb5\x00\x5a\xbf\xec\x12\x7c\x1e\x9f\xa7\xd3\x46\x9c\x72\xb8\x9a\x96\x97\x6e\xb4\x70\x2f\x09\xf9\xc0\xa0\x97\x1b\x30\xdf\xc3\x39\x07\x2b\x5e\x3a\x6c\xe4\x0b\xfe\xa2\xd5\x2f\x2c\x93\x0a\x11\xdd\x65\x5d\xd3\x6a\xc9\xfa\xd8\x6f\xc3\x98\x6b\x48\x71\xe7\xc9\x04\x59\xa2\xea\xa3\xb3\xd2\x2d\xd0\x4c\xb8\x24\x17\x3c\xcc\x08\x7d\x42\x9b\xb2\xa1\x88\xe0\x5d\x8a\xf0\xac\x29\x11\xc9\x07\xfd\x95\x7b\x2b\xb3\x30\xa6\xf3\x98\x7a\x59\x59\x30\xb3\x12\x05\x3c\x4b\xdf\x85\x6d\xe7\x29\x38\x58", b"\xa0\xc4\x9d\x3c\x47\x24\x0d\x30\xd2\x6f\x0c\x20\xe4\x50\x8b\x36\x0a\x84\x12\x85\xde\x3f\xc1\x98\x6f\x1e\xf9\xf6", b"\x97\xca\xa2\xb7\x6d\x15\xb1\xf9\xf1\x77\xe2\x09\x00\x4a\x2b\x1f\xdd\x23\xa3\x94\x50\x34\x58\x4c\x2c\x15\xbf\xa2", )?; test( HashAlgorithm::SHA224, b"\x64\x12\x91\x53\xeb\x9c\xcc\x74\xcc\x3a\xae\x1d\x59\x99\xc6\xe9\x0d\x98\x6b\xe6\xfa\x40\xc6\xc4\xbc\x00\xb1\xc3\xf8\x07\x2d\x10\xa9\xd8\xe6\xc3\x14\xd8\x2a\x76\x41\xf8\xa3\xae\x29\xd3\xe7\xdd\x19\x42\xdb\xf0\xdc\x52\xb4\xb4\xb3\x5b\xb6\x7a\x99\x49\x42\xaf\xf0\x29\xca\x6f\xa1\x87\x09\x91\x5f\xf7\x20\xab\x8f\x65\xf2\x31\x15\x5c\xb1\xd0\xdb\xcb\xa0\x4f\xc5\x19\x3a\xfc\x71\xa5\xed\xdb\x4a\x03\x86\x7e\x5c\x4b\xb9\x2d\x37\xb7\xef\x77\x1d\xa9\x54\xec\x67\x54\xd5\xfb\xe2\xe3\x72\xb9\x2d\xf6\xa3\xea\x8c\x3a\x4a\xff", p, q, g, b"\x23\xf5\x38\xd4\xec\x34\x5f\xaa\x90\x6e\xff\x12\xf6\xc5\x94\x2a\xc1\x66\x91\x4b\xaf\x8e\x73\x7d\xaf\xc7\x1e\x47\x28\x55\x12\xeb\xc5\x7e\xbf\x3e\xc6\x66\x34\x2a\xbc\x05\x9b\x0e\xbd\xdb\x02\x1c\xea\xff\x6e\xf7\x58\x28\xc7\xbe\x37\x66\x25\x7f\x72\x47\xa6\x7e\x14\x08\x23\x9f\xa4\xdd\x1c\xaa\xc2\xb7\x22\x9e\x8c\x1b\xcf\xd5\x7a\xee\xa4\xc0\x4e\x15\x86\x76\x4e\x28\x66\x9c\x36\x12\xd8\xa0\x06\x58\x2c\xf8\xf8\x29\x10\x48\x26\x91\xc1\x0e\x41\x13\x21\x6f\xc2\x4f\xeb\x29\x9f\x84\xba\x58\x70\x0a\x3b\xb6\xfd\xef\xa1\x7a\x7f\xac\x9a\xa9\xbb\x41\x0f\xe4\x11\xfb\x29\x4d\x62\x94\x39\x6f\x7f\x62\x7d\xca\x04\x52\xef\x59\x5d\xc2\x41\x70\xc1\x47\xd3\x86\x3f\xc1\x6e\x23\x64\x50\x19\xac\xa6\x3f\xcc\x11\x52\xb0\xf7\x66\xf5\xf6\x51\xc9\xbb\x69\x9e\x2f\x50\x47\xfa\x1e\x96\x03\x97\x2d\x2c\x75\x51\xb1\x8f\x3b\x16\xc1\x06\xdd\xd6\xcc\x2e\x24\xd2\xd0\x5e\x79\x68\x7e\xfe\x65\x51\x02\xe6\xbc\x15\xbc\x3a\x57\xf6\x0c\x1a\x6a\xd2\x0b\xf1\xcb\xe6\x20\x52\xad\x09\x47\x43\x7b\x92\xb2\xc9\x32\xaf\x5d\x72\x77\x5d\x43\x18\x3b\xbc\x6f\x35\x9a\x4d\xf6", b"\x3d\x72\x89\x62\xae\xc3\x58\x22\xff\xf9\x9e\x1b\x52\x17\xd8\xa6\x26\x4a\x7c\x60\x8d\x80\x66\xf4\xfc\xc9\x00\x8a", b"\xca\x5c\x8e\x17\x8a\x14\xba\x00\x6e\x93\xcf\x4a\xd1\x19\xf0\x45\xbb\xf8\x2b\x82\x87\x67\xd3\xe5\x83\xd0\xbd\x15", )?; test( HashAlgorithm::SHA224, b"\x9f\xd2\x79\x1c\x41\xa2\xff\xa6\xdf\x26\x10\x98\x04\xea\xf0\x70\x12\x2e\x20\xbb\xb6\x2e\xcd\x98\x11\x55\x11\x36\xaa\x95\x6d\xc1\xc3\x21\x32\x78\x93\xa0\xdd\xe6\xdd\x1d\x5b\x3a\x0d\x2a\x5a\xa9\x7e\xd7\x54\xe5\xbc\x06\x67\x53\x33\x8d\xdd\xfc\x68\xeb\xa2\x17\xd2\x48\x35\x05\xb0\xd7\xc0\xa4\x37\x73\x2f\x80\x46\xcf\x3b\xf5\x93\x0a\x11\xef\xd3\xf6\x59\x9c\x0f\x8d\x46\x5f\xca\x76\x76\xce\x1f\x39\x10\x2c\xc0\xcd\xf1\x32\x81\xb2\xc7\xb9\xcf\x7a\x7a\xfc\xde\x68\x10\x05\xe5\xa2\xe4\xe3\x8c\xf8\x2e\x42\x13\x57\xa4\x1f", p, q, g, b"\x14\x7a\xa8\xd9\xe4\xcc\xac\x90\x6d\x6a\x5a\x0b\x65\xbf\xeb\x59\xd4\xd6\x60\x37\xad\x40\xd2\x88\xd7\x53\x4f\xc9\xae\x33\xc5\xaa\x70\x1c\xa1\x8e\x60\xf0\xb6\x89\x08\x28\x05\x62\x11\x0a\xf7\xd1\xd1\xbf\xb5\x38\xc5\x9d\x91\x00\x98\x03\x84\xae\x93\xb7\x7b\xe0\x33\x2a\x03\xcc\x56\x7d\x4d\x63\x4f\x76\x48\xa1\xb9\xfd\x25\xda\xf2\x50\xb2\x86\x96\x83\xe9\x42\x6d\x75\x56\x1a\x5e\x17\x87\xc2\xba\xb7\x11\x32\x75\x7d\xff\xc4\xb7\x66\x51\x43\xe7\xd8\x7d\x50\xf1\x2d\x01\x07\x5b\xef\x5f\x4b\x0f\x14\xcb\x3f\x10\x9d\x15\x99\xe5\xbf\x94\xde\x01\x11\xa0\x1a\xf5\x7e\x8c\x13\xf5\x83\xbe\x4d\xc9\x00\x89\x61\x9c\x72\xd2\x2a\x49\x5c\x45\x25\x6e\xc7\x87\xa5\x83\x2d\x2e\x4c\x4a\x42\xf0\x00\x18\x37\xa9\x75\xac\x8f\xbb\x8c\x56\x5f\x77\xb2\x53\x30\x3b\x1a\x87\x33\x06\xfa\x5c\xf6\xa5\xda\xb6\x2d\x7b\x1b\xa3\xd7\x0d\xc1\x1b\x4e\x4f\x87\x5e\x3e\xda\xe5\x0e\xe8\xe5\x17\x8d\xd0\x9a\x33\x4c\xf9\x26\x0c\x3e\x0a\x10\x91\x1d\x38\x1d\x7f\x56\x01\xc0\xb3\xf2\x69\x46\x68\x20\x18\x62\x99\x22\x94\x6d\xd7\x3f\x81\x24\x08\x16\xae\x96\x06\x91\x1c\xbf\xd6", b"\xa7\xcc\x74\x86\xf4\x7f\xe6\x2f\xe3\x25\x4e\xd6\x55\xe1\xc9\x94\x90\x2d\x79\x7f\x0d\x7c\xa9\x3f\xb9\x7d\xf9\xc1", b"\x91\x4b\xf7\xd1\x5c\xe2\xc9\xec\xc5\xae\x15\x0d\x63\x08\xfc\x55\x7d\x94\xe1\xef\x18\xc0\x86\x0a\xa6\x8a\xd4\x8e", )?; test( HashAlgorithm::SHA224, b"\x6b\x78\xb4\xde\x5f\x75\x26\xdb\xed\x08\xee\x0f\xf4\xe4\x33\x35\xb6\x0c\xd3\xbc\x37\x1b\x70\xcd\x4f\xd9\xce\x45\xbf\x06\x50\x83\x91\x08\x5d\x14\x2c\xc3\x89\x1b\x17\x91\x67\xc7\x6a\x13\x50\xca\x8e\xf8\xce\x75\x4a\xb1\xd6\x24\x57\x2e\x43\x71\x95\x66\x0f\x00\x4c\xb7\xbe\xd2\xff\x3b\x0f\x7c\x7e\x53\xf8\x53\x30\x5a\x38\x21\xdf\xba\xec\x33\xe2\x20\xdf\x3c\x3e\xf7\xa7\x9f\x34\xe8\x2c\xc8\xff\xf8\x41\x5f\x10\x8c\x00\x0f\x21\xc3\xbb\x21\xa4\xc3\x32\x67\xa2\x13\xcb\x4a\x55\x8e\x3b\x37\x0d\x17\xc6\x39\x24\x7b\xff\xeb", p, q, g, b"\x91\x47\x67\x0f\x64\xae\xdf\xa2\x46\x93\x8b\xa7\x7f\xb9\xc1\xac\x27\x1c\xa1\x09\x1d\x86\x3f\x32\xf0\x0d\x5c\xcd\xeb\xe7\x02\x2d\x26\x8b\xa9\x05\x1d\x80\xfe\x55\xdf\xc5\xf6\x4b\x07\x16\xc4\xbb\x8d\xa4\xb1\x1e\x9e\x28\x34\x48\xed\x8b\xe4\x27\x8e\x93\xb5\x2d\x67\x56\x49\xab\xb4\x59\x56\x52\x2f\x92\x63\x4c\x92\xa0\x9a\xc5\xa5\xd6\x03\xaa\xe2\xa6\xd0\x4a\x43\x52\x39\x53\x8d\xe3\x03\xfc\x05\xb9\xed\x5f\xcb\x84\x3f\x05\x36\xa8\xab\x94\x2d\x9c\x3b\xdc\x90\xfe\xed\x97\x44\x9c\xe3\x09\xbe\x8a\xb1\x19\x67\x6a\x96\xc2\xa6\x0a\x06\x69\x2e\x8c\xd5\x9e\x55\xe6\xff\x8d\x91\xfa\x46\x29\x66\x55\x55\x26\xc9\x87\xfc\x44\xba\x42\x0b\xbf\xf7\x68\xf7\xa7\xfd\x36\x36\x38\xd5\xce\x4d\x9e\xa1\xed\xd7\xfd\x39\x9d\x6c\x65\x62\x7b\xbc\x33\x7f\x13\x1c\x73\x45\xb3\xd7\x9b\x4d\xb7\x41\x25\x62\x54\x7c\xa2\xa7\xc8\xea\x55\xeb\xdd\xdd\x05\xa4\xb4\x20\x0c\x72\xab\x2b\x83\x31\x11\x52\xb7\x1c\x99\x30\x6c\x1d\x3b\x3d\x44\x66\x57\xbe\x65\xe5\x8d\x7c\xf8\xa0\x62\xb2\x25\xce\x93\x78\x02\x59\x05\x46\x85\x3f\x19\x2a\x6a\x8c\x8b\x3f\xf7\xa6\x2f\xcf\x80", b"\x2f\x85\xee\x5c\x32\xd5\x46\xc6\x8f\x0a\xa2\x69\x8b\xea\xe5\x3e\x28\x48\xc3\x75\x51\x7a\x57\x0e\x0f\x1b\x55\x46", b"\x54\x76\x67\xe8\xb1\x3f\x21\x63\x5a\x0b\x10\x6d\x32\x4d\x06\xc8\x5b\x74\xa6\x4c\xe9\x22\x5c\xc5\xe0\x84\x35\x81", )?; // [mod = L=2048, N=224, SHA-256] let p = b"\xa4\xc7\xea\xab\x42\xc4\xc7\x3b\x75\x77\x70\x91\x64\x89\xf1\x7c\xd5\x07\x25\xcd\x0a\x4b\xc4\xe1\xcf\x67\xf7\x63\xb8\xc1\xde\x2d\x6d\xab\x98\x56\xba\xaf\xb0\x08\xf3\x65\xb1\x8a\x42\xe1\x4d\xc5\x1f\x35\x0b\x88\xec\xa0\x20\x9c\x5a\xa4\xfd\x71\xa7\xa9\x6c\x76\x5f\x59\x01\xc2\x1e\x72\x05\x70\xd7\x83\x7b\xec\x7c\x76\xd2\xe4\x93\x44\x73\x1c\xa3\x94\x05\xd0\xa8\x79\xb9\xe0\xdc\xd1\xa8\x12\x5f\xd1\x30\xec\x1e\x78\x3e\x65\x4b\x94\xe3\x00\x2e\x6b\x62\x9e\x90\x4a\xb3\x87\x78\x67\x72\x0c\xbd\x54\xb4\x27\x0a\x9e\x15\xcd\x02\x8c\x7c\xc7\x96\xf0\x6c\x27\x2a\x66\x09\x51\x92\x8f\xdb\xeb\x2d\xca\x06\x1b\x41\xe9\x32\x25\x73\x05\x74\x2f\xf1\x6e\x2f\x42\x91\x91\xd5\xe5\xf1\xa6\xdd\xf6\xe7\x8c\x5d\x77\x22\xcf\xf8\x0a\x9c\x0b\xd5\xc8\xd7\xae\xba\x8c\x04\x43\x89\x92\xb0\x75\xe3\x07\xc1\x53\x4c\x49\xad\x38\x0f\x47\x7f\x5f\x79\x87\xdc\x17\x2c\x16\x1d\xca\x38\xdc\xaf\x3f\xb3\x84\x6c\x72\xc9\x11\x9a\x52\x99\xad\xc7\x48\x95\x1b\x3d\xce\x0d\x00\xd4\xa9\x01\x38\x00\xb2\x00\x82\x03\xb7\x24\x65\xbc\x6a\x84\xae\x05\x9a\x30\xc4\x52\x2d\xea\x57"; let q = b"\xce\x89\xfe\x33\x2b\x8e\x4e\xb3\xd1\xe8\xdd\xce\xa5\xd1\x63\xa5\xbc\x13\xb6\x3f\x16\x99\x37\x55\x42\x7a\xef\x43"; let g = b"\x8c\x46\x5e\xdf\x5a\x18\x07\x30\x29\x1e\x08\x0d\xfc\x53\x85\x39\x7a\x50\x06\x45\x0d\xba\x2e\xfe\x01\x29\x26\x4f\xbd\x89\x7b\xb5\x57\x9c\xa0\xea\xb1\x9a\xa2\x78\x22\x04\x24\x72\x4b\x4f\x2a\x6f\x6e\xe6\x32\x84\x32\xab\xf6\x61\x38\x06\x46\x09\x72\x33\x50\x53\x39\xc5\x51\x9d\x35\x7d\x71\x12\xb6\xee\xc9\x38\xb8\x5d\x5a\xa7\x5c\xc2\xe3\x80\x92\xf0\xa5\x30\xac\xb5\x4e\x50\xfe\x82\xc4\xd5\x62\xfb\x0f\x30\x36\xb8\x0b\x30\x33\x40\x23\xeb\xbe\x66\x37\xa0\x01\x0b\x00\xc7\xdb\x86\x37\x11\x68\x56\x36\x71\xe1\xe0\xf0\x28\xae\xdb\xd4\x5d\x2d\x57\x26\x21\xa6\x09\x98\x2a\x07\x3e\x51\xaa\xe2\x77\x07\xaf\xbe\xef\x29\xe2\xec\xee\x84\xd7\xa6\xd5\xda\x38\x2b\xe3\xa3\x5f\x42\xb6\xc6\x68\x49\x20\x2a\xb1\x9d\x02\x5b\x86\x9d\x08\x77\x64\x76\xd1\xab\x98\x14\x75\xad\x2a\xd2\xf3\xe6\xfd\x07\xe3\x06\x96\xd9\x0a\x62\x68\x16\xdf\x60\xd6\xca\x7a\xfd\x7b\x48\x2f\x94\x2f\x83\xb4\x5c\xc8\x29\x33\x73\x1f\x87\xfa\xee\x32\x09\x00\xf2\xaa\x3e\x70\xb1\x86\x7e\x14\x30\xe4\x0b\xe6\x7c\x07\xf9\x29\x02\x99\xef\x06\x7b\x8b\x24\xa7\x51\x5b\x3f\x99\x2c\x07"; test( HashAlgorithm::SHA256, b"\xce\xc8\xd2\x84\x3d\xee\x7c\xb5\xf9\x11\x9b\x75\x56\x25\x85\xe0\x5c\x5c\xe2\xf4\xe6\x45\x7e\x9b\xcc\x3c\x1c\x78\x1c\xcd\x2c\x04\x42\xb6\x28\x2a\xea\x61\x0f\x71\x61\xdc\xed\xe1\x76\xe7\x74\x86\x1f\x7d\x26\x91\xbe\x6c\x89\x4a\xc3\xeb\xf8\x0c\x0f\xab\x21\xe5\x2a\x3e\x63\xae\x0b\x35\x02\x57\x62\xcc\xd6\xc9\xe1\xfe\xcc\x7f\x9f\xe0\x0a\xa5\x5c\x0c\x3a\xe3\x3a\xe8\x8f\x66\x18\x7f\x95\x98\xeb\xa9\xf8\x63\x17\x1f\x3f\x56\x48\x46\x25\xbf\x39\xd8\x83\x42\x73\x49\xb8\x67\x1d\x9b\xb7\xd3\x96\x18\x06\x94\xe5\xb5\x46\xae", p, q, g, b"\x74\x8a\x40\x23\x72\x11\xa2\xd9\x85\x25\x96\xe7\xa8\x91\xf4\x3d\x4e\xb0\xee\x48\x82\x6c\x9c\xfb\x33\x6b\xbb\x68\xdb\xe5\xa5\xe1\x6b\x2e\x12\x71\xd4\xd1\x3d\xe0\x36\x44\xbb\x85\xef\x6b\xe5\x23\xa4\xd4\xd8\x84\x15\xbc\xd5\x96\xba\x8e\x0a\x3c\x4f\x64\x39\xe9\x81\xed\x01\x3d\x7d\x9c\x70\x33\x6f\xeb\xf7\xd4\x20\xcf\xed\x02\xc2\x67\x45\x7b\xb3\xf3\xe7\xc8\x21\x45\xd2\xaf\x54\x83\x0b\x94\x2e\xc7\x4a\x5d\x50\x3e\x42\x26\xcd\x25\xdd\x75\xde\xcd\x3f\x50\xf0\xa8\x58\x15\x5d\x7b\xe7\x99\x41\x08\x36\xdd\xc5\x59\xce\x99\xe1\xae\x51\x38\x08\xfd\xae\xac\x34\x84\x3d\xd7\x25\x8f\x16\xf6\x7f\x19\x20\x5f\x6f\x13\x92\x51\xa4\x18\x6d\xa8\x49\x6d\x5e\x90\xd3\xfe\xcf\x8e\xd1\x0b\xe6\xc2\x5f\xf5\xeb\x33\xd9\x60\xc9\xa8\xf4\xc5\x81\xc8\xc7\x24\xca\x43\xb7\x61\xe9\xfd\xb5\xaf\x66\xbf\xfb\x9d\x2e\xbb\x11\xa6\xb5\x04\xa1\xfb\xe4\xf8\x34\xec\xb6\xac\x25\x4c\xab\x51\x3e\x94\x3b\x9a\x95\x3a\x70\x84\xb3\x30\x5c\x66\x1b\xfa\xd4\x34\xf6\xa8\x35\x50\x3c\x9a\xde\x7f\x4a\x57\xf5\xc9\x65\xec\x30\x1e\xcd\xe9\x38\xee\x31\xb4\xde\xb0\x38\xaf\x97\xb3", b"\x9c\x5f\xa4\x68\x79\xdd\xaf\x5c\x14\xf0\x7d\xfb\x53\x20\x71\x5f\x67\xa6\xfe\xc1\x79\xe3\xad\x53\x34\x2f\xb6\xd1", b"\xc3\xe1\x7e\x7b\x3c\x4d\x0a\xc8\xd4\x9f\x4d\xd0\xf0\x4c\x16\xa0\x94\xf4\x2d\xa0\xaf\xcc\x6c\x90\xf5\xf1\xbb\xc8", )?; test( HashAlgorithm::SHA256, b"\xf3\xbb\x27\xbf\x9d\x41\x2f\x13\x22\x9a\x56\xd2\xd1\x53\x3e\xae\x63\xf4\x00\x04\xc1\x43\xc6\xb9\x2f\x6e\x60\x6d\x26\x3d\xd2\xda\x75\x81\xe5\xeb\x20\xb6\xcd\x02\x1e\x3a\xb6\x3b\x49\x8a\xba\xfc\xe0\x1b\x4a\xd7\xac\x86\x28\xf7\xa1\x84\x9c\x4e\x45\x4f\x11\x68\xae\x97\xad\xfa\xb1\xfa\xdb\xd3\x13\xfc\xa7\x38\x17\x26\xf5\x04\x57\x52\xda\xba\xad\x6e\xa3\x25\x0d\x30\x3a\x54\x96\xbb\xa2\xfa\x48\x95\xae\x49\xf0\x6a\x9a\xa6\x45\x1a\xe7\x0c\xf3\x3b\x5f\x06\xfa\x17\xca\xc0\x14\x4f\x28\xbd\x19\xfb\x2a\xc0\x41\xa5\x78\xed", p, q, g, b"\x00\xc7\xaa\xbe\x30\xfa\x4c\x3d\x1b\xa8\x5e\x7a\xe0\xaa\xe7\x93\x60\xe5\xea\xb3\x04\x1b\xca\xaa\x5d\x32\x1c\x92\xf3\x47\x1e\x41\x94\xc1\x04\x84\xcf\xf1\x52\xba\xde\x6b\x7d\x61\x9c\xf2\x86\x77\x34\x75\x29\x8f\x88\x3e\xfd\xf6\x4c\x08\xb6\x92\x58\x3d\xe3\x1b\xe0\xa4\xe2\xb8\xe8\xd5\x08\xec\x14\x5c\x65\xa3\x69\xce\x61\x95\x44\x6c\x52\xd0\x23\x72\xeb\xa5\x62\xf9\xa9\xd7\xcb\x24\xd2\xec\x3b\x0a\x1a\xb8\x33\xe4\xd7\x62\x3b\x04\x55\xa4\x1e\xec\x75\x9d\x07\xa3\xc8\xa2\x0d\x88\xa9\x26\x40\x8c\x20\xf1\x67\x56\x01\xbe\x53\xcf\xfd\x65\x61\x7b\x66\xfd\x4e\xb3\x53\xa1\xf2\xdb\x31\xf6\x63\x43\xb0\x7f\xaf\x60\xde\x0b\x6a\x68\x08\x09\xc6\x16\x6a\xdb\xf5\xe5\x04\xc5\xc6\x1b\xab\xb8\x4b\xe7\x2c\x02\xd3\xeb\xee\xe0\x66\xd9\xea\xb0\xd0\xec\xdf\xe0\x1b\x8c\xcd\x67\x28\xee\x91\x23\xb9\xd2\x11\x54\xb2\xbc\x9a\x13\x43\x63\x56\x64\x02\x29\x1a\xc8\xa4\x84\xee\x32\xeb\x88\x40\x46\xd4\x0f\xde\x7c\xab\xbf\x51\xd1\xd1\x20\x6d\xf1\xc5\xec\xf2\x90\xab\x7e\xa7\x2a\xbb\x5b\xd3\xbe\x8d\x91\xc0\x2b\xb6\x3f\x80\x97\x18\xba\x1d\x38\x0a\xf8\x83\x31", b"\x79\xa6\xae\xd7\x3c\xe1\x77\xed\x35\x81\xf5\xd1\x81\xa7\x7f\x00\x0d\x63\x58\x51\x4e\xa9\x5c\xb0\x38\x8a\x6a\xdd", b"\x2b\x85\x97\xa6\x94\x56\x4e\x26\x7b\x6f\x25\x0a\x4c\x76\x36\x1f\x8c\xdf\x49\x86\x3a\x79\x02\xaf\xa4\x8f\xd6\xd8", )?; test( HashAlgorithm::SHA256, b"\xe7\x14\xc0\x16\x31\x70\x4e\x94\x47\x39\x0f\x5c\x31\x5c\x96\x15\xa7\xa5\x28\x63\xb1\x43\x70\x65\x83\xf6\x61\x59\x5c\x50\x5a\xec\x47\x7e\xeb\x5a\xd6\xd6\x40\xca\x81\x2c\xe1\x17\x50\xb6\x7b\xc8\xbe\xde\x2e\x4f\x96\x18\xdb\xe7\x37\x6c\xab\x62\x31\xb2\x12\x48\xec\x91\x4a\xe1\x82\xdf\x87\x53\x36\x2d\x21\x18\xa6\x5e\x66\xf6\x40\x18\x81\x08\x04\xad\x97\xfc\xc1\xa8\x7b\x8c\x9f\x34\x9d\x10\x01\xe4\xb0\x9b\x04\x69\x91\xe6\xab\xe6\x33\x8f\xbe\xf7\xbe\x48\xf1\xc8\x0c\x35\x0d\x29\x62\xeb\x6b\x8f\xce\x25\xb6\x9f\x8d\xc9", p, q, g, b"\x04\xd3\x01\xf0\x01\x82\x1b\x03\xc9\x13\x94\xc5\x20\x83\x9a\xb6\xaa\xa9\x53\x25\xc1\x08\xa0\x2d\xad\x9d\xb4\x8b\x3c\x80\x33\xd6\x44\x3b\xcb\xf0\x50\x45\x23\x0c\xa8\x8a\xaf\x98\xa8\xc4\xcb\x6b\x09\x5b\x35\x2d\x91\xb4\xc4\x16\xf6\x32\xfa\xb4\x9d\x45\xac\x90\x69\x9a\x5a\x41\x96\x30\xa8\x1d\x47\x3b\xc8\x91\x22\xeb\x5b\xac\xb9\x1c\x40\xca\xa4\xe4\xbc\xc4\x76\xf3\xca\x77\xbf\x6a\x21\x03\x7a\x06\xbe\x24\xf1\x1c\x64\x5b\x0c\x21\xb8\x57\xfd\xc5\xc0\x4f\xbb\xf0\xa2\x6e\xfc\x56\x9c\xdb\xb0\xea\x98\x9b\xa0\xe0\x37\xc2\x3f\x22\xb0\xc5\xf1\x64\x3d\x77\xd9\x8f\x2d\xe2\x48\xcc\xc3\x66\x72\xd3\x97\xd3\x0c\x1c\x5e\x13\x19\xfc\x7e\x58\x42\xae\x1a\x9f\xcd\x9e\x96\xfe\x89\x0a\x74\xdd\xee\x91\xa3\x9c\xe7\x32\xe4\xc0\xea\xf7\x09\x4b\x53\xb7\xb4\x09\x30\x38\x60\xb0\xb4\x94\x4c\xc8\x1b\x4a\x42\xd4\x05\x38\xcf\xe5\x12\xb9\x68\x0e\x0a\x28\x1b\x1f\xbb\xf6\x39\x13\x9e\x80\x66\xad\x63\x8c\xf8\x46\xc9\xea\x51\xfb\x4c\x4e\xf8\x49\x21\xf1\x6a\x6c\xa3\xf2\xbd\x15\x81\x57\xc5\x51\x73\x9c\x9d\x02\x3e\x27\x0b\x3d\xe7\xc2\xf1\xd7\x68\x3c\xf8\x09", b"\x79\x0b\x4d\xca\xe3\x1f\xe4\x5c\xd3\xa7\xbb\x6f\xa1\x0d\xcf\x9e\xde\x1f\x06\x71\x23\xf9\x3b\xaa\xd7\xed\xb4\x89", b"\x71\xe3\xe4\x6d\xfe\x04\x04\x96\xce\x4c\x5e\x49\x0f\x69\x44\xa2\x3c\xd5\xe6\x6c\xe9\xb4\xd9\xac\xbe\x41\x30\xce", )?; test( HashAlgorithm::SHA256, b"\x3f\x6e\x48\x2f\xd4\x84\xed\x3d\x07\xf1\xd0\x76\x1f\x2d\x60\xfc\x96\xd4\x6e\xb0\xec\xd1\x0a\x59\xdd\x4f\x39\x2e\x3d\x3b\x2c\xbe\x18\x40\x10\xe1\x32\x68\x55\x78\xb1\xf6\x30\x32\x39\x79\x8a\x53\x03\xa8\x11\x69\xd4\xf5\x2f\xba\x0d\x20\xa4\x28\x34\xde\x29\x3e\x3a\x7b\x32\x84\x8b\x65\xdd\x30\x8e\xef\x53\x50\xd6\x33\x29\x74\x65\x42\x5b\x7b\x15\x95\xff\xc8\xea\x7b\x12\x58\x96\xf8\x9e\x28\x44\x56\x16\x35\xf5\x2e\xc6\x2f\xab\x2e\xcf\xea\x28\x8d\x23\xf0\xa7\x71\xcd\x63\x11\x80\x61\x03\x13\x51\x72\xcf\x9f\xef\x14\x55", p, q, g, b"\x9f\xc1\xb2\x92\xeb\xe1\x55\x31\x57\x9f\x35\xdd\xa8\xd7\x06\xbe\xe0\xda\x85\x7c\xd6\x96\xa1\x0a\xf7\x70\xdc\x35\x62\x32\x73\x6c\xf8\x93\xf7\x41\x1a\x9d\x27\x18\xb3\x9f\x38\x81\x18\xd1\x77\xcd\x8d\x0f\xd7\xca\x3b\x3c\x22\x0f\x3a\xa7\x43\xd8\xb1\x67\x21\x9d\x3c\x2c\x78\x3e\x1f\x09\xd8\xb8\xdf\x8e\xc7\xe1\x75\x78\xc5\x32\x94\x88\xc8\x7a\x89\x67\x8d\x28\x18\xa9\x93\x66\xb7\x85\xd5\x3f\x6c\xa6\x99\x5e\x19\x3b\xa5\xca\x26\xc0\x0b\x84\x9f\x90\x27\xca\x5d\xf5\xbb\x7e\xc8\x7f\xe7\x87\x35\xae\x88\x0f\x1a\x97\xda\xbc\x3c\xa7\x98\x5d\x8c\xbc\x81\xbe\x82\x4c\x1f\xfb\x95\x3f\x10\x96\xbf\x92\x62\x26\xfb\x5e\x9d\x4a\xd4\x3e\x93\x63\xda\x5e\x6b\x73\x8c\x9a\x2f\x95\x1a\xb3\x29\x4e\x2b\x28\x22\xcf\x52\x82\xbb\x41\x34\x15\x8a\xa9\x0a\xb9\xc8\xf0\xf6\x4d\x05\xa0\xd6\x25\xa7\x5b\xc2\xd6\xa4\xae\x3d\xd1\x1f\xc0\x5e\xde\x7b\x66\x47\xae\x7c\x07\x50\xdd\xb2\x73\xfe\x5f\x88\x28\x31\x8a\x91\xdb\x31\x72\xad\x59\x16\x6a\xac\xf2\xda\x4f\x37\x04\xd1\x69\xeb\xc8\x60\xd9\xe1\xc6\x46\x4a\xbc\x2b\x65\x30\x13\x77\x4d\x29\x37\x5b\x77\xba\xc1\xec", b"\x3b\x5d\x80\x34\xc4\xb8\xad\x97\x01\xbf\x29\xb1\x00\x06\xdb\x69\xd0\x17\xfd\xe8\x63\x80\x79\xdd\x7b\xbf\xac\xe7", b"\xcd\xe0\x1d\xf5\x4a\x66\xce\xf3\xc0\x53\x86\x48\x52\x5b\x25\x0c\xb1\xf0\x87\x07\xf5\xff\x11\x4b\xde\xbf\xf8\xf7", )?; test( HashAlgorithm::SHA256, b"\x31\xa2\x78\xf8\x81\xfd\xd3\x75\x56\x5c\x0f\x28\xff\x75\x75\xf2\x16\x11\x04\x86\xd6\xfe\x08\xda\xe8\xfd\x07\x29\x50\x97\x8b\xdf\xf6\x01\xde\xd1\xef\x22\x6b\x5d\x90\x4c\x47\xf7\x14\x2a\x8f\x46\x65\xe0\x3e\xfe\x58\x70\xda\x2d\xd1\xab\x80\xe4\x49\xf5\xc7\x57\xb3\xb6\x99\x6a\x9d\xc0\xb5\xb2\x75\x0b\x97\xbb\xad\x2f\x55\x3f\xba\xff\x2a\xed\xec\xfc\x9f\xf6\xa9\x70\xd1\x56\xe4\xfe\x38\x52\x97\x9d\xc9\x13\xbd\xb2\x96\xa3\x21\xf7\x66\x36\x72\x39\xde\x45\xe4\x7c\xbe\xf4\xd7\x9b\xfa\x3d\x57\x68\x87\xc6\x5f\x7f\x8a\x60", p, q, g, b"\x7e\xc0\xa5\x41\x88\x28\x15\x9e\x3e\xc8\x29\xf7\x93\xb9\x6e\xa3\x46\x03\x28\xde\xa2\x1b\xa1\x57\xd7\x1a\xc3\x06\xf9\xbc\xde\x61\x7d\xb6\x73\x68\xd5\x92\xbf\x46\xd4\x69\x18\xb1\x30\xfc\x7e\x3d\x11\x89\xee\xb7\x99\x6d\x5f\x66\x0a\xce\x30\xbe\x50\x9a\x26\xb2\x18\xd8\x65\xd9\xe5\x6b\xa7\xf6\x19\x42\xe5\x67\xd8\xcd\xab\x96\xa7\x8c\xa3\x03\xc6\xb0\x1d\x98\x9b\x1e\x78\xae\x95\x64\x23\xe3\x5b\x5a\x46\x6c\x16\x07\x4e\x0b\xc9\xe8\x37\x23\x40\xd2\xc1\x25\x16\xc2\x2d\x5e\x1f\xf6\x5a\xbd\x5d\x44\x82\x15\xb6\xba\xf5\x65\xde\x18\x20\x1c\x1f\xd5\xba\x3d\xe8\x7e\x5d\x5b\x43\x7d\x2f\x48\xde\xee\x72\xa1\x2e\x65\x5f\x8c\x7f\xa3\x13\xd2\x4b\xd0\xc8\xc2\x0e\x59\xc9\x0e\xdf\xbf\x5d\xfc\x05\x7c\x6b\x67\x98\x50\xae\x41\x82\x61\x78\xf2\xf3\x04\xca\x3b\x92\xa9\xba\xc3\x1a\xb3\xcf\x74\xdf\xb8\xee\x5b\x64\x3b\x4a\x34\x1e\xbb\xdb\x5d\xbd\x24\xd0\xb7\x82\xc5\xb4\x50\x59\x6a\xbf\xc3\xdf\x9e\xe0\x5f\x45\xd0\xea\x2e\x8f\xf4\x35\x7c\xd3\x60\x5f\x35\x06\xce\x58\xa5\x39\x4f\x1f\x24\x44\xc2\x63\x59\x29\x9a\xf1\x53\x53\x2b\xc9\x0d\xaa\xf9\x54\xae", b"\x2b\x47\xe2\x57\xbf\x72\xad\xf3\x4d\x61\x8d\x3a\x6c\x46\x14\x28\x81\xbd\xd0\x68\x9a\x46\xf1\xcb\x31\x99\xee\x6c", b"\xcc\x1f\xf2\xfa\x37\x55\xa0\xe8\x1e\xdf\xc7\x53\xbc\xf1\x4e\x63\x74\x13\xea\xee\x0f\x22\xd7\x88\x6b\x05\x8d\xcc", )?; test( HashAlgorithm::SHA256, b"\xa6\xd7\x60\x47\xbd\x18\xde\xef\xe7\x0d\xc0\xa4\xbd\x08\x2a\x10\xfa\x52\x1d\xff\xda\x78\x2a\x93\x64\xb9\xe2\xb1\x1e\x14\x7e\x1a\x36\xa1\x1c\x43\x00\x67\x21\x44\xd9\xb9\x74\x13\x2b\x49\x75\xf2\x7e\xa6\xe8\xe4\x6b\x55\xae\xdd\x67\x23\xe5\x3e\x7b\xc9\xb4\x0d\xce\x24\x49\x28\x5a\x69\x08\x85\xc3\x22\x3b\x63\x6c\xb5\xc4\x87\x3c\x5d\xda\xeb\xb0\xb6\xdc\x5b\x69\x43\x8d\x88\x1a\x52\x59\x05\xa5\x1b\xdb\x97\xb0\x51\xdb\xfe\xc6\xdd\x4a\x7b\x58\x02\x97\xb0\x8f\x2b\xa6\x0f\x2e\xad\x3a\x07\x53\x1c\xf2\x99\x97\x74\x13\xaf", p, q, g, b"\x8b\x26\x62\x77\x5b\xb7\xf1\x92\x52\x20\x45\x94\xa8\x4b\x46\x9f\x3d\xc8\xd6\x6e\xb7\x99\x3b\xed\x12\x2d\x8a\x06\x5f\x59\xea\x81\xd4\xc4\x84\xce\xe5\xbd\x76\x6a\x5c\x13\x7d\xd5\x7e\x43\xe9\x41\x33\x98\x52\x15\x05\x09\xac\xbd\xe6\xf7\x95\x7a\x1b\x04\xec\xe7\x18\x56\x5c\xe8\xb6\x37\xea\x03\x1b\xfa\x34\x10\xa5\x80\x74\x4b\x3d\x49\x59\xa5\xe7\x5e\x31\x5d\xd3\x3c\x02\xb5\x2c\x7c\x56\x21\x8b\x7c\xdf\xdc\x24\xf5\x1d\xdb\x4e\x78\x49\xfa\xf2\x89\xcf\x80\x6c\x4d\x3c\x6b\x87\x7c\x63\xdb\xfa\xb5\x69\x92\x0a\x2b\x21\x9c\x39\x21\x5c\x5e\x3e\x63\x8a\x3e\xbe\xeb\xfb\x52\xc8\xb3\x8e\x82\x85\xa8\x5d\x62\x5f\xc1\xb4\x2f\xbf\x0e\x51\x8c\x58\xeb\x8f\x45\xfa\x54\x67\x6e\xd8\xb0\x09\x41\x5d\x26\x96\xee\x9b\x51\x53\xdd\xdc\x5e\xeb\xef\x49\xcc\x76\x59\x81\x0a\x98\xd4\xb5\xe8\xb9\x69\x5f\xb2\xd9\xe4\xbf\x19\x20\x93\x74\x7c\x87\x8a\x95\x65\xb4\x7c\xba\x05\x3c\x48\xba\x7c\x0b\x9b\x1c\xe7\x7f\x8a\x3e\x10\x43\xe8\x7f\xcc\x61\x32\xcb\xe8\xfa\xd7\xc7\x38\xe9\xbf\x79\xbc\xcb\x41\x4e\xf2\x49\x07\x67\x5b\xa7\xcb\x05\x9a\x83\x89\xee\xe7\xeb\xbe", b"\xb8\x67\x4d\x1b\xa6\xf1\x33\x98\xf5\xe8\x94\x4b\x82\x15\x0d\x9e\x9b\xc9\xb2\x10\xa8\x14\x95\xb3\x35\x94\x7e\x64", b"\x75\xfc\xfe\x96\x92\x61\x86\xef\xa1\x2c\x00\x7c\x09\x85\x20\x51\x47\xcf\x65\xab\xd1\x08\x36\x3d\x8b\x89\x11\x90", )?; test( HashAlgorithm::SHA256, b"\xf0\xd5\xb3\x33\x27\x69\x55\x36\xe3\x51\xb3\x7c\xd3\xfe\xea\x69\x3f\x10\x37\x7a\x5f\x8b\xdd\x91\x34\x02\xc2\xed\x67\xa0\xfc\x1e\x7b\xca\xab\x00\x2f\xa7\x79\x93\x59\x50\xc7\x6e\x42\xa4\x91\xa6\x8f\xa6\xfe\x44\x5c\xd3\x55\x75\xcf\xce\x5f\x37\x6c\x29\xc4\xe8\xc0\xfe\xd5\xa5\x48\x7e\xf4\x18\xb9\x6f\xa5\x75\x2a\x03\x3a\xd0\x79\x59\x65\x3d\x1b\x8a\xf6\x70\x2d\xcc\xe4\x0e\xfe\xf2\x1b\x2d\x64\xcf\x06\xbd\x8b\x03\xda\xdb\x2f\xda\xaa\x73\xfb\x2d\x3d\x75\xb0\x98\x5e\x9a\xef\xa1\xf9\x44\x42\xa5\x49\x1a\xe4\x6d\x7c\x51", p, q, g, b"\xa4\x48\xb0\xd4\x48\x24\x9a\x0e\x54\xa9\x45\x86\x88\x29\x85\xa0\x8e\x19\x97\x22\x81\xd1\x0d\x9e\x7f\xb5\x7f\x95\xdf\xee\xbf\x97\x1f\x6d\x9d\xfe\x88\xdb\xd0\xa4\x95\x0f\x52\x82\x00\xbe\x7b\x60\x58\x65\xee\xfd\x8e\xc2\x74\xac\x53\xe4\xed\x5b\x28\x8c\x6a\x00\x72\x1e\x02\x88\x81\xb9\x72\x5f\xb0\xa9\xce\x41\x53\xdc\xc1\xfe\x7b\x5c\xe7\x25\x9f\x16\xea\x8b\x32\x45\x6c\xb0\x3b\xae\x81\xbe\x77\xf3\xf6\xe8\xb3\x9f\x52\x58\x7b\xc9\xdd\x47\xa2\x64\x27\x8d\x5d\x8e\xcb\xe1\xba\x57\x42\x69\x69\x6a\x7b\xb1\xe1\x67\xa3\xae\x71\x10\xec\x05\x7f\x42\x91\xa1\xba\xe8\x25\x7d\x69\xc1\x0a\xe0\x95\xf3\x27\x16\x21\xc6\xd6\xb5\x60\x79\x25\xc3\x49\x81\x89\xd7\x51\xb7\xc9\xbf\x30\xe6\x56\x83\xcb\x39\xfb\x51\xbd\x59\x2f\x1f\x98\x27\x9f\x2e\x7b\x2b\x53\xad\x54\x68\x16\xa8\x50\x8c\x93\xf0\x34\x96\xde\x7c\x47\x16\x5f\x5c\xf2\x97\x68\x7a\xd7\xd6\x0f\x01\x0a\xb9\xfa\xad\x01\x53\x43\x2e\xc1\xcc\xdf\x26\xd4\xf4\x41\xdf\x62\x53\x94\xe2\x10\x42\x08\xbb\x67\x5e\x7f\x97\x2b\x6c\x66\xed\x70\x28\xa1\xe3\xf4\x5a\x67\x1a\xb2\x71\x6c\x60\xfe\xab\xcc\x22", b"\x01\xa4\xf4\xbc\x63\x3e\xbf\x84\x2a\x28\xd0\x45\x18\x4d\x25\x05\x29\x92\x0d\xf2\x80\x54\x5c\xba\x00\x50\x1c\xad", b"\x09\xfc\xeb\x2d\xf2\x00\xb7\xc0\xa5\x6a\xe7\x96\x9f\x54\x73\xb7\xa1\xf6\xb7\x03\xf7\x43\xf9\x54\xa4\xfb\xdb\xe3", )?; test( HashAlgorithm::SHA256, b"\xf5\x8e\x03\x9d\x66\x6e\xf0\x64\xcc\xcc\x7e\xd0\x15\x01\x7c\x68\x39\x3d\x14\x55\x30\x0d\x0c\x4f\xd4\xf0\xd3\x02\xc4\x3a\x00\x22\x36\x3a\x7c\xb0\x1b\xf0\x67\x3d\x32\x52\x93\xbd\x50\xb2\x7f\x81\x87\xd8\x8e\xe2\xb5\x53\xb1\x59\xa9\x7d\x15\xac\x54\x34\x21\x44\x6c\x2a\xec\x39\x56\x63\x15\x21\x1b\x9b\x41\x08\xca\xcf\x90\x85\xda\xcd\xb4\xde\x94\xbc\xe8\x40\x97\xc0\x89\x2b\x1c\xc6\x5f\x2e\x10\xd7\x4e\x52\x93\xa0\x4a\x83\x7b\x61\x6d\x41\x81\xf3\xfe\x4c\xaa\x4c\xc2\xe7\x44\x91\x6e\x77\x0f\xf0\xab\x13\x68\xc8\x6c\xfc", p, q, g, b"\x40\x52\x53\x4a\x77\x26\xcb\xe1\x7e\x34\x55\x56\x48\xe5\xf2\x97\xb9\x63\xf2\x2d\x3a\xca\x24\x97\x85\xad\x93\x2f\x6e\xa1\xfb\x5d\xf3\x1d\x37\x9b\x68\x52\x2f\x8e\xeb\xed\xfc\x9b\x5c\x52\x77\xe9\x15\x74\xfa\x79\xec\xf0\x37\x80\xcc\x44\x35\x1f\x3e\x3b\xfa\x1a\x05\x87\xc8\x8d\x0e\x04\xe0\xa0\x2c\xd1\xee\x9a\xe2\x10\xb3\xc9\xaa\xcc\x65\xc7\x1c\xf1\xb8\x64\x63\x36\x7e\x2b\xe2\x5c\xca\xdd\x9d\x5a\x4d\x1f\xcb\xd5\x87\x72\xf7\xa1\x17\xf3\x67\x3c\x76\xee\x2a\x8d\x93\x44\x6f\xfd\x7c\xda\x7f\x84\x30\x49\x05\x02\xc1\x6b\x1a\x50\x22\xe1\x2a\x3a\x95\xa7\xa9\xf2\x0e\x98\xd3\xb2\x85\xab\xe3\x0e\x8d\xe4\x2a\x11\xc5\x17\xc1\x4e\xf3\xb6\xe5\xb6\xc4\x71\x14\xa9\x61\xd8\x58\xc6\x87\x55\x61\xc7\xd5\xd2\x1b\x7c\x93\xf3\x73\xcb\x33\x08\x00\x72\x8e\xa1\x88\xb2\x57\x8a\x6d\xf3\x47\x72\xa7\xac\xdd\xb8\x29\xc0\x9b\x3a\xcf\x9b\xc5\xb0\x61\x40\xb9\xb0\x35\x26\x7a\x40\xe8\x6c\x1a\xf5\x57\x7b\x3d\x02\xa8\x9b\x20\xa4\x65\x73\xc8\x75\x00\xa2\xeb\xed\x4b\x00\xb1\xfb\x13\xa8\x6f\x14\x3e\x35\x67\x02\xd7\x91\x37\x9a\x90\xdf\xcc\x26\xb8\x07\x19\xad", b"\x31\xfd\xe5\xf2\x2e\xbb\x42\x6f\x25\x6b\x17\x50\x57\xa7\x61\x25\xc4\x01\x36\x97\x4a\xd5\x8e\x68\x1e\xc2\xc4\xa9", b"\x77\xb0\x61\x4d\xd9\x9a\xcb\xbf\x4c\x43\xaa\x92\x6b\x3f\x0b\xe1\xcd\x52\xd5\x27\x75\xf2\x2a\x40\x8c\x4e\x03\x04", )?; test( HashAlgorithm::SHA256, b"\x14\x77\xaa\x0b\x9f\x1b\x19\x9b\x6a\xa0\x93\x1d\x4d\x3f\x76\x6d\x80\xa3\xaf\x10\xc9\xff\x73\x15\x39\x1f\x15\xed\xc4\xe9\x26\x32\xf9\xd4\xd2\x1a\x80\x33\x21\x5d\x5e\x99\xcf\xf1\x70\xd9\x88\x8f\x02\x0b\x0d\xb0\xe5\xb9\x7e\x12\x3a\x28\x89\x89\x8c\x5b\x0e\xf7\xc8\x32\xd0\x28\xaf\xd5\xe3\x85\x00\x45\x31\xff\x99\x89\x79\x7c\x3b\xd9\x54\xb1\xac\x72\x90\x66\x57\x76\x67\x56\x78\x84\xcd\x4b\xc5\xd0\x55\xa3\xf6\x45\x58\x3d\x29\xcf\x47\x58\x50\x7c\x88\x3c\x5b\xbf\xa7\x44\x44\xb9\xc5\xb9\xb4\x95\x07\x2c\x32\x61\xb6\xec", p, q, g, b"\x46\x75\xf1\x9b\x00\x95\xfa\xf8\xec\x96\x88\x8e\x48\x3f\x3a\x0a\xa6\x75\xf5\xb4\x25\x91\x07\x65\x06\x9a\xb5\x7c\x97\xa1\x2b\x7c\x50\x64\x37\xc8\x75\x7f\xef\x54\xec\xc6\xd3\x10\x92\x1d\x71\x59\xff\x39\xf2\xf1\xcd\x95\x35\xb6\x4f\x27\xf1\x36\x91\x37\x15\x77\x5a\x23\x8f\xbe\x01\x23\x7e\x18\x1a\xde\xbe\x55\x1f\xfe\x5d\x21\xe3\xc3\x57\x74\xe7\xad\xe8\xc7\x9d\xf7\x41\xc5\x2d\xab\xd8\xbe\x47\x82\xee\x5a\x3b\x60\x7a\x39\xd1\xb4\x55\xdc\x84\x83\x01\x84\x73\x12\x98\x05\x66\xf5\x5e\xba\x08\x06\x21\xe3\xc1\x23\x14\x2a\x1a\x20\x74\xe2\xe3\x9f\x6c\x06\x30\xb3\x68\x31\xf0\x74\x86\x9d\x46\xa6\x84\x29\xf6\x25\x73\xcd\x2c\x67\x17\x26\x13\x1f\xbf\xd5\x66\xa6\xd0\x71\x93\xdb\x4f\x36\x78\x02\xd7\xde\x8f\x4e\x83\x0a\xa8\x78\xee\x2c\xdf\xb8\x6d\x85\x37\x74\x6b\x71\xc7\x0f\xbc\xb6\xa1\xfa\xd6\x62\x13\xd6\xfb\xea\x68\x24\x1e\xb9\xf6\x17\x47\x8a\xdc\xc9\xfa\xaa\xb2\x6c\xf8\x1b\x91\x20\x89\xda\x0c\x4b\x18\x7b\x49\x6a\x17\xd8\x86\xce\xf5\x71\xe3\x93\xd6\xf1\xf8\x57\xeb\xf5\x17\xc8\x01\xf9\x23\x1e\x95\xdb\x66\x1e\x8c\xb2\x09\x54\x56\xa3", b"\xa2\x38\x0b\x5e\xce\x76\x67\x26\x69\xe2\x61\x87\xa1\x7d\xa4\x5a\xd8\x9d\xe1\x72\x6c\x82\x6e\x57\x37\x8a\xf7\x07", b"\x9c\xc2\x6c\x34\x56\xc0\xa4\x09\xf4\xcc\x98\xc8\x3e\xa5\x17\x6e\xb2\x93\xec\x71\x57\xe5\x13\x70\x72\x64\x29\xce", )?; test( HashAlgorithm::SHA256, b"\xfc\x82\x37\x25\x66\xef\x2c\x62\x6b\x21\x45\x54\x9a\x5d\xb9\x73\x11\x8d\xff\x4c\x6d\x1d\x7c\x4a\x2e\x16\xec\xc3\x1b\x43\xc1\x4a\xd3\x68\x31\x73\x53\x5b\x0b\x82\x33\x1f\x15\xa1\x83\xe6\xa5\x02\x00\xfd\x1e\x88\xff\x90\x3e\xcf\xc5\x0b\xdd\x4f\x58\x75\xe2\x64\xa4\x49\x9e\xad\xbd\xaf\x80\x7f\x97\x4f\x8d\x81\x04\x47\x7a\x0e\x4d\x30\x46\x3d\xfc\x61\xcd\xac\x5b\xf4\x4e\xab\x96\xc7\x70\xa7\xdb\x91\x2e\xee\x2d\xb2\x48\xcd\xd2\xb9\xb3\x62\x11\xf9\x38\x70\xbe\xae\x6b\xdf\x8e\x0a\xed\x00\x97\x51\x9e\xcd\xe3\x47\x0c\xdd", p, q, g, b"\x38\x84\xab\x23\xab\x93\xd9\xd1\xb7\x16\x71\x2c\x8d\xaa\x08\x0b\x26\xaf\x01\x65\x7f\x0d\xab\x71\x5e\xbe\x6b\xd7\x66\xde\xca\x76\x12\xbe\xa6\xa4\xcf\x1f\xf7\xd0\x8a\xbb\x2d\x44\x42\xac\x0e\xaa\xb0\x1e\x68\x57\x0b\xdc\xc2\x22\xf8\x4b\xc3\xdd\x6d\x8c\x54\x90\x13\x2d\x1c\x36\xe2\x39\x13\xf0\x0d\x11\xc8\x03\xb7\x03\xa6\x9a\x51\xa1\xb4\x75\xf5\x6d\xb0\x0f\xca\x47\xd2\x34\xaa\xc3\x07\xb9\xe7\x98\xe9\xfd\x89\x1d\xff\x9c\x12\x57\xbe\xe5\x56\x31\x4b\x02\x1f\xbf\x93\xf7\x5e\xd8\xc4\x34\x33\xaf\xa7\x15\xb8\x2d\x5e\xc6\xaf\x8e\xf9\x47\x1e\x9b\x02\xf9\x55\x4e\xd7\x95\x7c\x1f\x46\xd8\xdb\x35\xa5\x92\x1f\x4a\x83\x72\x7f\x75\x4e\x82\xb6\xff\xa6\xd1\xb8\x25\x95\x22\x08\x76\xd2\x2e\x18\xfb\xaf\xa5\x33\x3b\x26\xc2\xcf\xd4\x7d\x89\x4a\xaa\x71\x64\xa2\x63\x02\x94\xd0\xa3\x85\xfc\x8a\x8c\xf5\x7d\x10\xed\x0f\xc5\x3f\x21\xf1\xfd\x6b\x4c\x27\xe9\xc6\x9e\x65\xa2\x88\x44\x46\x19\xa3\xc2\x48\xbc\xc4\x4e\xc2\x56\x05\x02\x83\x25\x24\x32\x74\xd7\x21\x00\xed\xf5\x60\xcd\x38\x2b\xab\xee\x1c\xa5\x32\xb7\xf0\x6a\x43\x88\xf1\x81\xdb\xbb\x5d\xb5", b"\x54\x61\xb2\x07\x04\x45\x3b\x6c\x51\x83\x7f\x7b\x9e\xf5\x83\x61\x31\xb5\x01\xf2\x53\x91\x45\xca\x34\x81\xe6\xaf", b"\xb6\x5f\x69\xd2\x91\xff\xae\x2d\x16\xe3\x10\x8d\x69\xae\xb0\x1b\x4f\x92\x02\xaf\xa0\x13\x82\xe5\x3d\xea\x4d\x54", )?; test( HashAlgorithm::SHA256, b"\xe6\x6a\xad\x54\x04\x8b\xec\xec\xa5\x68\x26\x44\xd5\x27\x4c\x18\x06\x83\x63\xe9\x68\xe3\x7e\x6c\x11\xc1\xf8\xa0\xd7\xe3\x20\x57\x85\x14\xe1\x87\x4e\x9d\x4e\xaf\x1b\xd0\x2d\xa6\xb7\x22\xed\x22\xac\xfc\xa4\x8c\x3a\xcb\x67\x0a\x6f\x9e\xe6\x2e\x3a\xa7\x1d\xeb\x18\x09\x75\x08\xf4\x31\xb0\x52\x14\xc1\x99\xc1\x66\xfa\x42\xcd\x6a\x07\x97\xbc\x7b\x4d\x1a\x2f\x33\x0c\xb6\x2c\x2c\x95\x18\x2f\xef\x0d\x06\x86\x25\x42\x84\x5e\x43\x0d\x77\x8c\x82\x07\x63\x87\xad\xad\x43\x55\xc2\x58\xe6\xc5\x43\xcd\x65\x6f\xe3\xcd\x23\x32", p, q, g, b"\x06\x24\x5b\xc5\x09\xb4\x95\x54\x40\xb0\xe4\x01\x71\x0d\xdb\x2c\x4e\xa2\xe5\x59\x59\x83\x61\xa3\x66\x6c\x4a\xb1\x2e\x76\x6b\x43\x9f\x21\xb9\x53\x96\x2f\x6e\xf5\xa1\x1d\xbe\xe5\x67\x7a\xb7\xf8\x90\x6d\x8b\x32\x51\x80\xef\x4e\x45\xd0\x5c\x12\x94\xfc\xe5\xdc\xaf\x63\x60\xf7\x1b\x10\xb7\x05\x56\xf3\x06\x99\x3d\x29\x5b\x69\x5f\xfe\x57\x29\xc5\xc5\xbb\xb6\xcb\x48\x34\xad\x03\x7b\xd8\x36\x4a\x12\xc9\x92\xc2\x59\x8e\x8e\xe6\xbe\xb1\x60\x6e\xbc\x0a\xc0\xff\x00\xc0\xea\x2e\xb8\xae\xd7\x5d\xca\x01\xa8\x90\x08\x5a\x40\x0e\xbf\x99\x3e\x58\x79\x38\x2f\xf9\x1a\xbf\x1b\xe2\xce\xed\xd1\xfc\x4a\x87\x43\x42\xb7\x7b\x6c\x55\xff\xe7\xf6\x76\xa1\xc9\x5e\xe4\xec\xc3\x23\x58\xa0\x80\xc9\x23\x61\xcf\xcd\x2e\x34\x26\xf7\x8c\x21\x7a\xe2\x95\x56\x70\x9e\xd0\x29\xb2\x87\xe7\x1f\xea\xe0\x60\x8c\xf3\x93\x88\x57\x04\x0d\x7f\x06\xb0\xf9\x1b\x3b\x4d\xa8\x92\x9d\xf4\xb5\x69\x8e\x73\x4a\x37\x31\x68\x79\xc3\x08\xa8\x1c\x09\x6b\x72\x3b\xf2\x08\x99\x10\xd5\xab\x30\xb8\xef\xf3\x88\x58\xaf\xf6\xec\xf7\x64\xe2\x68\xed\x69\x8b\x70\xe8\xfb\x7f\x3c\x66", b"\x86\xd5\xba\xc3\xae\xee\x9b\x50\x1f\x91\xf2\xfa\x71\xb1\x06\x67\x60\xdf\x2e\x0e\xe1\x47\x38\x3f\x14\x5b\xb0\xd3", b"\x8d\x6a\x20\x78\x02\xd6\xfd\x6e\x53\x4e\x1b\x8a\x1e\xdb\x99\x7b\x7c\xc9\xa2\x5a\x97\xa9\xe4\xb6\xee\xbd\x0e\x23", )?; test( HashAlgorithm::SHA256, b"\xc8\x57\x47\xcd\xd2\xac\x9d\xa0\x99\x9b\x7e\x5d\x7f\x64\xd1\x1d\xce\x76\x73\xdf\x5b\xc6\x05\x05\x13\x16\xb4\xb9\x4b\xc7\xfc\x77\x6f\xb1\xd3\xda\x5a\x43\x95\xa6\x74\xaa\x8a\x07\x98\xa3\x41\xb3\x1b\x11\xe6\x3c\xdf\xac\x5f\x85\x43\x46\xf6\xa4\xb7\x4b\x49\xf2\xd0\x89\xcb\xb8\x6f\xae\x54\xeb\xfd\x95\xeb\x9f\x05\xa1\xb5\xe8\x43\x06\xe9\x30\x46\x1a\xd7\xf8\x27\xcf\xb9\x10\x01\x4a\x3a\xf4\xda\xe0\xd4\x6e\xce\x91\x2b\xc2\x68\x70\xa4\x33\xf7\x0f\x0a\x38\xbf\x23\xb1\x5d\x98\xcc\x65\x88\x48\xf4\xba\xd9\xc8\x4e\x89\xf0", p, q, g, b"\x29\x72\x78\x7d\xcb\xd6\x7e\x5b\xdd\xaa\xf1\xbd\x3f\x05\xeb\xd6\x69\x49\x60\x1d\xda\x44\x23\x7e\xc9\x36\x15\x91\xce\x9b\x80\x9f\x87\x22\xfb\x39\x9e\x6b\x9b\x81\x09\xa7\x9e\xa7\xb8\x3f\xe9\x83\x59\xa0\x7a\x27\xe2\x32\xcd\xea\x8f\x65\x33\xe3\x4e\x37\xdb\x3a\xe5\x33\x09\xf6\x2f\x10\x8b\x2e\xe7\xb4\x89\xa9\x33\xe4\xef\x58\xdd\x4d\xb8\xc0\x10\x8a\x36\x70\xc6\x75\xb9\x8b\x75\x79\x8a\xc0\x88\x4c\xf5\xa4\x61\xaf\x28\x1f\x6d\xd8\xe7\xea\x3d\x41\x39\x6f\x04\x96\x01\xa9\xaf\x2e\x39\x08\x8a\xe0\xa1\xec\x0d\x2d\x10\xfa\xe1\xdc\x1d\xe9\x62\xd8\x4d\x8c\xf0\x42\x15\xfc\x6d\x62\x62\xac\x43\x25\x41\xaf\x2c\x48\xc0\x9c\xd4\xe1\x5b\xd9\x46\x0e\x9a\x7b\xae\x17\xe0\x03\x5a\xf0\xb1\x3d\x8d\xe7\x07\x87\x0c\x54\xbc\x85\x11\x12\xf4\xae\x1d\x69\x07\x47\x12\xc2\x12\xbc\x7e\x13\xf1\x99\xff\xc8\xf3\x77\x23\xcd\x6d\xcf\x53\x9f\x8d\xf8\xcf\x0c\xf1\xed\x4c\x10\xee\xaf\x0f\x44\x48\x04\xf1\xeb\x9d\x9c\x32\x9d\x6f\x19\x97\x3e\xec\x27\x32\x22\xfa\x04\xb5\xf1\xf0\xe1\x79\x71\xce\x39\x98\x69\x58\x20\x27\xb1\xc4\x54\xdc\x1a\xdd\xd4\x84\x90\x2c\xb0", b"\x28\xe3\xdd\x71\x09\x8f\xf0\x4d\x1c\xa8\x85\xc2\x77\x4f\x78\xec\xb3\xec\xea\x70\x8f\xab\x2e\x16\xbd\x5c\xec\xe1", b"\xac\x8b\x6e\xe4\x98\xee\x38\x3e\x28\x40\x4b\xa4\xb5\x3e\x64\xac\xa0\xfc\xd2\x67\x90\x71\x32\x64\xfe\x3c\xf6\xa1", )?; test( HashAlgorithm::SHA256, b"\xa7\xa5\x9d\xa6\x2a\x93\x91\xcf\xe6\x28\x69\x75\x48\xb0\x5f\x8a\xf3\x9e\xa9\x82\x1d\x76\xc3\x14\x47\x8e\x21\x0f\xbc\xd2\x7f\xbf\x6b\x0b\xf4\x60\xa6\x5d\xbc\xba\xdc\xdd\xfc\x01\x78\xec\xe1\x35\x26\x4a\x7d\x7c\x5b\x70\x53\x20\x8b\xfb\xde\x54\xe3\x33\x8d\x90\x19\x27\xe9\x5e\x1d\xc8\xee\xb7\x3d\x29\x9e\x6f\xa6\x58\x45\x55\xcf\xea\xfd\x19\x25\xe9\x5e\x0b\x35\x58\xdd\xec\x64\x11\x75\xfc\x72\x93\xc0\x31\x02\x66\xac\xe1\x8b\xbb\x16\xf9\x08\x4f\xd4\xac\x22\xad\x2d\xc8\x52\x8c\x3f\x3f\x33\x26\x84\x03\x9e\x74\xb3\x90", p, q, g, b"\x0a\xa0\x40\xbb\xb2\x3c\x33\x7d\x58\x87\x4d\x95\xef\xe9\x27\x70\x80\x86\x2e\xa0\x88\x8d\x92\x09\xec\xc2\xf5\xd7\xe0\xd5\x6b\x3e\x84\x44\xca\x93\x38\x00\x45\x0f\x10\xb8\x12\x4f\xf8\x81\x2f\x87\xe1\xbe\xcf\x1a\x31\x7a\xce\x0c\x3a\x13\x76\xd6\x24\x93\x8c\xab\x61\x7b\xb5\x46\xd0\xaa\xd4\xf1\xd0\xaa\x23\xc6\x67\x0c\xfa\xe0\xda\x28\x66\x03\x93\xa9\x09\x11\xb3\xdb\xe3\x84\x7e\xab\x4e\xbb\x7d\xd0\x50\x4a\xeb\x02\x69\x12\x66\x55\xd1\x35\xd2\xe9\x14\x9c\xd8\xac\x52\x21\x15\x16\x40\x91\x4d\x48\x05\x69\xb3\x83\xe9\x83\x64\xcc\x41\xce\xc5\x6e\xa1\x57\xce\x8d\x7e\x73\xa9\x49\xb3\x48\xe5\xff\xd3\xce\xef\xea\x7f\x76\x25\xf5\x99\xaa\x9a\xfe\x2d\xb4\xcf\x3b\x0d\x59\xf2\x70\x0f\x6c\xec\xc5\x4f\x8b\xf7\x85\x38\x92\xf0\x73\x37\xdb\xe7\x6b\xe7\x81\x99\x4e\xf4\xe1\x4d\xf2\xf0\xcf\x7c\xb3\x42\xee\x1c\x8b\x18\x8a\x7d\xcc\x31\x7a\x09\x7c\x9f\x9e\x33\xff\x89\x46\x2c\x26\x46\x5b\xb5\x3e\xec\x05\xd1\x08\x5f\xc6\x15\x6c\xad\x0f\x7c\x9b\x80\xd2\xa6\x89\x53\x50\x1a\x97\xac\xb7\x46\xac\x3a\x2b\x9b\xdc\xf1\x8d\xfc\xea\xa1\x96\x71\x6e\xc7\x73", b"\x84\x93\x4f\x3f\x56\xd6\x48\x15\xfc\x66\xb0\xdb\xf3\xb1\xfa\x56\xd1\x38\x7b\xe7\x61\x1a\x1e\x57\x1c\x40\x51\x00", b"\x43\x1f\x11\x34\x69\x50\xe7\x7c\x9e\x9e\xd0\x12\x7c\x50\xbf\x62\x0f\x6f\x69\xa6\x99\xcd\x01\x7c\x7d\x87\x36\x8a", )?; test( HashAlgorithm::SHA256, b"\xd4\xc5\xb4\x39\xa1\xcc\xf5\xd9\x8c\xf0\xb9\x31\xf2\x53\xf7\x33\x03\x79\x21\xd4\xef\xb0\x2c\xf8\x7b\x25\x09\xe7\x32\xa5\x6c\xcb\x49\xe0\xc8\x3b\x14\x09\xcc\x00\x9f\x1d\x2d\x1c\xb4\xc0\xc7\xab\x00\xc4\x02\xee\x01\x8e\xc5\x09\x80\x31\xac\x9e\x71\x97\xd4\x39\x5d\x49\x17\x21\x70\x8a\x41\xff\x5c\xda\x5a\x03\xbe\x6a\x11\x69\xbf\x45\x94\x70\xb1\xaa\xf5\x3c\x8a\x96\x68\xac\xae\x13\x85\xb9\x21\xf5\xa2\x6c\x73\x36\x54\x44\x51\x5c\x3c\x12\x6c\x69\x40\xb4\xbf\x57\x59\x1a\x0b\xfd\x6c\x2c\x74\xc7\x24\x42\x6c\xb2\xad\x3f", p, q, g, b"\x37\xc5\xf0\x29\x81\x63\x22\xda\x51\x61\xc4\xe2\x0d\xc4\xf5\xab\xde\x9f\x04\xf5\xf9\xdf\xf5\xd5\x81\xb2\x53\x10\x91\x91\xb3\x84\x24\xdd\xe7\x5f\xeb\xac\x32\xd6\xce\x31\xb1\x16\x06\x34\x94\xa7\x0c\x5c\x1d\x9d\x8b\x73\x51\x25\x2e\xd3\x77\xea\x38\xfb\xe8\x5b\x9f\x61\x4e\xca\x13\x46\xbf\xf6\x53\x45\xd5\x7e\x64\x6b\xfb\x03\x2e\x9b\xef\xa9\xe6\xe5\xa8\x9c\x16\xd7\x15\x42\x0e\x24\x12\x9b\x6f\x70\xe4\xf6\x81\xbc\x1d\x38\xad\x17\x37\xdb\x79\x65\x5d\x24\x4b\x4d\x67\xad\x3d\x2b\xd8\x0f\xd9\xd8\x0c\x2e\x15\x24\x02\x14\x85\x9f\xdc\x0b\x6c\x43\xdd\x1e\x80\x5d\xcd\xd2\xa5\xb9\x78\x13\x97\xbd\x4a\x4e\x8b\xc4\xd6\xf9\xa1\x66\x40\x36\xe9\x0c\xac\x55\x0e\x83\xd6\x64\x13\x67\x61\x37\x07\xd0\xde\x4f\x2d\xee\x55\xe9\xa5\xbe\x6d\x3d\xe8\x93\xd6\x15\x61\xf4\xba\x90\xd3\x87\xb7\xab\x48\x80\x10\x86\x01\x6c\x84\x2f\x3e\x0c\xe6\x0e\x6b\x46\xaa\x98\x01\x91\xcb\xa1\x47\x40\x7a\xa4\xcc\xbe\x19\xb0\x0b\x0a\xc7\x16\x48\xd5\x29\x6d\x13\xe4\x8c\x75\xd5\x28\x48\xbb\xd3\x9f\x1d\xed\x98\x8c\x36\x16\xfa\xaf\x64\xf9\x1a\x30\x74\x25\x06\x31\x68\x93", b"\x1b\x51\xb8\xd2\xd3\xee\xb3\xd6\x21\x8d\xa3\x49\x47\x14\xd0\xe8\x8c\xd7\x36\x6f\x38\x7e\x6e\xde\x00\xf6\x53\xe0", b"\x84\x42\x03\xa8\x1f\xb3\x8f\x57\x50\x5b\xf8\x3b\xc8\xc1\xda\x00\x2a\x39\xe8\x1a\xbb\xdd\x2f\x99\xab\x6a\x4d\x65", )?; test( HashAlgorithm::SHA256, b"\x40\xd4\xd9\x73\x6b\x54\x99\x3c\x1b\xce\xe7\x07\x1c\x68\x23\x90\xd3\x4d\x47\xc3\x5f\x17\x79\x39\xca\x5b\x70\xf4\x57\xb3\x45\x8f\xd5\xec\xa4\xcb\x03\xf0\xef\xe1\xae\xc1\x0b\xf7\x94\xb8\x41\x21\x60\x56\xa1\x55\xda\xb5\x8a\x3d\xbf\xc1\x9d\xdf\x05\xd4\x58\x61\xba\xe6\xee\xa2\xbd\x7f\xfb\x87\xa6\xfd\x0f\xd2\x39\x4e\x84\x7d\xc3\x6c\x94\xc8\x15\x61\xde\xe1\x20\x77\x9b\xbe\xcb\xc3\x22\x06\x32\x7f\xeb\xaa\x17\xc9\x65\x05\xec\xb9\x7d\x56\x0c\x93\x4c\x38\x6f\x6f\x76\x6a\x2f\x51\x54\xf5\x45\xf2\x21\x81\xc1\x9f\xc6\x98", p, q, g, b"\x24\xaa\x1c\x7c\x6a\x04\x1f\x6d\x2c\x53\x30\x06\xce\xbc\xc2\xad\x04\x8b\x3d\xc0\x8f\xa8\x62\x82\xf5\x87\x9a\x23\x72\x31\xd2\x30\xcd\x85\x4a\xa1\x01\x58\xce\xbb\x45\xf3\x87\x92\x3f\xad\xa8\xc5\xf4\xb9\x1a\x7b\xc2\xdc\x3e\x2c\x39\x46\x37\x97\xe6\xeb\x19\x58\xab\xc9\xb9\xe7\x48\xbb\xfe\x80\xe3\x60\x23\x3e\x96\x95\x22\x79\x95\x9a\x6b\x80\x61\x91\x00\xf6\xf1\x87\x6f\xad\xeb\x79\x04\x91\x46\x2f\x59\x17\xda\x36\xce\xa3\x79\x3c\x44\xdb\x90\x90\x8c\xb9\xda\x18\xf6\x96\xce\xd9\x0f\x2a\xcb\x82\x63\x55\x10\x4c\x4c\x8f\x06\xc7\x37\xd4\x8a\xcf\x98\x5d\x6b\x8c\x2a\xbf\x31\x80\x72\x82\xb6\xe6\x51\xd2\x96\x7a\x16\x90\x7b\xe3\xd8\xe4\xb7\xf3\x2e\xd3\x4e\xba\x8c\x26\x2d\x6c\x0e\xcb\x13\x19\x46\xd2\x54\x63\x62\xc2\x17\xae\x19\x5d\x05\x65\x6a\x4f\xcf\xac\x73\x71\x7a\xe8\x5a\x57\x1d\x81\x1c\xbc\x99\xe0\xb3\x12\x4b\xba\x76\x7f\xea\xd6\x05\x26\x6d\x99\x02\x1c\xdd\x8c\xb4\xc0\x81\xbe\xf1\x02\x43\x10\x07\xee\x12\x52\x3b\x48\xbb\x83\x86\x98\xa5\x97\x1e\x51\x72\x52\xd6\xd9\x3e\x1c\x7f\xe9\xfb\xe0\x7b\xf4\x34\x16\x4b\xaa\xa1\x02\x6d\xa4", b"\x32\x5a\xa7\xb1\x73\xca\xc9\x6d\x58\x65\xaa\x50\xea\x54\xe5\xdf\x45\xa1\x0e\x72\xfd\x5d\xd1\xfb\x26\x5a\xae\x09", b"\x0a\x72\x03\xf6\xb8\xfb\xf6\x68\xb8\xf6\x43\x5e\x92\x9f\xd5\x2f\x52\xe2\x3a\xd4\xb8\xa1\x56\xae\x5f\x3c\x9c\x47", )?; // [mod = L=2048, N=224, SHA-384] let p = b"\xa6\xbb\x53\x33\xce\x34\x3c\x31\xc9\xb2\xc8\x78\xab\x91\xee\xf2\xfd\xea\x35\xc6\xdb\x0e\x71\x67\x62\xbf\xc0\xd4\x36\xd8\x75\x06\xe8\x65\xa4\xd2\xc8\xcf\xbb\xd6\x26\xce\x8b\xfe\x64\x56\x3c\xa5\x68\x6c\xd8\xcf\x08\x14\x90\xf0\x24\x45\xb2\x89\x08\x79\x82\x49\x5f\xb6\x99\x76\xb1\x02\x42\xd6\xd5\x0f\xc2\x3b\x4d\xbd\xb0\xbe\xf7\x83\x05\xd9\xa4\xd0\x5d\x9e\xae\x65\xd8\x7a\x89\x3e\xaf\x39\x7e\x04\xe3\x9b\xaa\x85\xa2\x6c\x8f\xfb\xde\xf1\x23\x32\x87\xb5\xf5\xb6\xef\x6a\x90\xf2\x7a\x69\x48\x1a\x93\x2e\xe4\x7b\x18\xd5\xd2\x7e\xb1\x07\xff\xb0\x50\x25\xe6\x46\xe8\x87\x6b\x5c\xb5\x67\xfe\xc1\xdd\x35\x83\x5d\x42\x08\x21\x98\x53\x1f\xaf\xbe\x5a\xe2\x80\xc5\x75\xa1\xfb\x0e\x62\xe9\xb3\xca\x37\xe1\x97\xad\x96\xd9\xdd\xe1\xf3\x3f\x2c\xec\x7d\x27\xde\xae\x26\x1c\x83\xee\x8e\x20\x02\xaf\x7e\xb6\xe8\x2f\x6a\x14\x79\x6a\xf0\x37\x57\x7a\x10\x32\xbb\xc7\x09\x12\x9c\xaa\xbd\x8a\xdd\xf8\x70\xae\x2d\x05\x95\xc8\xfd\xb3\x71\x55\x74\x8f\x0d\xea\x34\xb4\x4d\x4f\x82\xed\x58\xc2\xf5\xb1\xb8\x48\x16\x62\xac\x53\x47\x3c\x69\x34\x10\x08\x2f\xbd"; let q = b"\x8c\x3e\xe5\xbd\x9a\x2a\xaf\x06\x8b\xd5\x84\x5b\xd5\x5e\xcf\x27\x41\x70\x55\x30\x75\x77\xbb\xc3\x77\x0e\xc6\x8b"; let g = b"\x43\xb5\xa6\xb6\xd0\xbb\x96\x2e\xc9\x76\x6a\x37\x7c\x32\xcc\x41\x24\xf1\x31\x11\x88\xc2\xec\xf9\x5c\x0c\xd4\xa4\xfa\x09\x72\x25\xb7\x61\x8c\xb1\x27\x6c\x47\x45\x78\xd3\xbf\x56\x4c\x14\x51\x99\xc0\x92\xa1\xb1\x4b\xaa\x92\x9c\x2f\x3f\x0f\x36\xe0\xc2\xda\xe9\x1e\xba\x08\xbe\x30\x99\x2a\x88\x9f\x29\x52\xe0\x44\x2c\x37\xaf\x48\x4a\x4e\xcd\xc3\x24\x3c\xcf\xcb\x9e\x34\x13\xcf\x5c\xdd\x66\x30\xb0\x9f\xe1\x7e\xfb\xfd\xe1\x4d\x87\x25\x49\x30\x19\xb7\xb7\x3d\x1f\x78\x2b\x48\xef\x30\xbe\xc3\x6e\x00\xe0\x2b\xa3\x36\xd2\x25\x4f\xc2\x02\xa6\x96\x12\xcd\x94\x46\xf9\x1d\x76\xb7\x39\xff\xa6\xd8\xb8\x60\x52\xf8\xdc\x5f\x11\x45\x80\x1c\x56\x24\x1a\xf5\xba\x90\x37\x24\x1b\xd8\x9e\x63\x38\xb5\x8e\x01\x31\x06\x71\xc2\x68\xeb\x5e\x33\xac\xb5\x7d\x1f\x99\xf1\x64\x40\xa6\x75\x82\x7d\x40\x17\x75\x4d\x60\x1a\x17\xad\xa2\xfb\xed\xf9\x04\x55\x4a\x90\xb0\x15\x30\xda\x8c\x93\xcd\x14\xce\x29\x3c\xb2\xbd\x3e\x79\x37\xe9\x34\xb7\x9e\x31\x0f\xe4\xd8\x0c\x13\xf9\x2f\x63\x38\x13\x55\xbd\x80\xa1\xab\xee\x1a\x73\xfd\xfb\x6d\xa2\x4e\xf2\x80\x02\xa3"; test( HashAlgorithm::SHA384, b"\xdf\x5d\x56\x4d\xb8\x35\x92\xc1\x12\x8b\xe5\xd2\x9b\x70\x36\x88\x0d\x55\xe8\x34\xa2\x91\xa7\x45\xed\x8d\xcd\x43\x8c\x4d\xa6\xb1\xb9\xf3\x94\x12\xb2\xc5\x11\x07\x30\xdb\x83\xc1\xcc\xdf\xe9\x05\x9d\xd9\x6e\xc7\xea\x2b\xbc\xb3\x4e\x3e\xba\x72\xef\x0a\x1d\x47\x21\xc7\xc0\x22\x1e\x29\x27\x9f\x01\x4d\x63\xfa\xcc\x5b\xc8\xf1\x8c\x53\x9b\x92\xff\x2a\xf8\x9e\x56\x82\x25\xd6\xb4\xcf\x59\x9c\xb3\xdf\xf5\xe3\xc6\xdd\xfa\xc0\xa2\x7f\x10\xf6\x36\xec\x22\x0a\xbb\x72\x63\x0b\xae\x9a\x39\xc1\x8f\xd3\x66\x3e\x46\x51\xcc\xac", p, q, g, b"\x64\x79\x79\xb7\x96\x0c\xe7\xb9\x71\xff\x0e\x5f\x64\x35\xf4\x2a\x41\xb1\x8c\x9d\xe0\x9a\x30\x11\x14\xa0\x13\xa7\xcd\x01\x18\x3f\x17\x6f\x88\x83\x83\x79\xdc\xb4\xef\xb6\x7d\xae\xa7\x9d\xef\x3f\x04\x2c\xbc\xf9\xcc\x50\x3b\x4c\x21\x51\xa2\x36\x4f\x7c\x94\x37\xb1\x96\x43\xe6\x7e\x24\xa3\x6b\xac\x4a\x4c\xfa\x29\x3d\xee\xdf\x8e\xc6\xb1\x54\xa3\x2a\xa7\x29\x85\xf7\xd8\xde\x23\x53\x34\xb5\x46\xc2\x9d\xef\x45\x8c\x55\xd0\xc5\xc0\xac\x5d\x74\xe2\x02\x4e\xc7\xd4\xab\xc2\xfd\xa5\x16\xa2\xa0\xb1\xa4\xd8\x86\xad\x92\xc2\x04\x70\x78\x28\xa4\xfc\x77\x94\xf6\x0e\xe8\xa4\xbe\x11\x01\xc9\xe5\x51\x8f\x7e\x19\xee\xbd\x47\x5f\x2d\xe6\xf6\xba\x89\xc2\x8b\xd1\x29\xf1\x39\x93\xbe\xfe\x58\x18\x44\x03\x19\xa7\x95\x49\x83\x31\x96\x34\x2a\x31\xdb\xaf\x7d\x79\x49\x7d\xec\x65\xee\x7d\xbe\xf7\x0e\x58\xf9\x9d\x05\x95\xf6\xa7\x11\x40\x9a\xde\x31\x51\xd4\x55\x63\xd5\x3c\x1c\xd0\xa8\xab\x1a\x18\xbe\xff\x65\x02\xcb\xb0\xc0\x69\xb1\x14\xea\x7b\xe7\x78\x98\xd0\xf4\xe5\x49\x99\x1b\xa0\xb3\x68\x97\x1b\x10\x72\xec\xe4\xaf\xc3\x80\xe9\xae\x32\x9a\x50", b"\x5a\xb4\x3e\xde\x66\xa1\x56\x88\x14\x6d\x1f\x4c\xd7\x16\x47\x02\xc0\xc4\x45\x7b\xd4\xfd\xde\xba\xc0\x48\x29\x53", b"\x6c\x58\xe8\xab\x27\xd2\x85\x12\xc4\x60\x63\xc9\x6b\xf5\xbc\xeb\x8f\xba\xd2\x32\xd8\xf5\xb3\x9c\x47\x55\xd0\xb1", )?; test( HashAlgorithm::SHA384, b"\xeb\xeb\x9e\x2b\x69\x2e\xc6\xc9\xaf\xad\x2a\x0c\x2b\x90\x89\x39\x94\x3f\xdf\x4b\xb7\x43\x8e\x3b\xd9\x28\x8e\x76\x81\x98\x40\x87\xff\xdc\xf8\x65\x02\x07\x9c\x29\x12\x36\xd7\xf1\xad\xb5\x04\xe6\x7e\x0f\x88\xbe\xe6\x1b\x61\x71\x70\x14\xcf\x06\xb5\xfa\xd5\xcb\x36\xf1\xb2\x23\xb6\x39\x12\xcd\xcd\x2b\x94\x16\x52\x4d\x37\xf5\xd7\xb0\x5c\x37\xd1\x78\x96\x69\xe1\x41\xaf\xf6\x67\x0d\xb2\xe0\xde\x31\x67\x3b\x20\x55\xf6\x79\x9a\xc8\x87\x93\x7e\x56\x64\xa6\x59\xea\x02\x54\xa8\xd4\xba\x6f\x20\x4d\xf2\xa3\x8c\x2a\x77\xe4", p, q, g, b"\x31\xd3\x1a\x5b\xb8\x28\x74\xbd\xc7\x6c\xab\xae\x3e\xc8\x56\x90\xaa\x51\x03\xca\xcb\xe5\x23\x4e\x0d\x5e\xf6\x45\xee\xf3\x80\xd3\xae\x2f\x62\x39\x14\x4b\x82\xb1\x01\xa7\xef\x47\x44\xaa\xdb\x8f\xc9\x8e\x82\xb4\x13\x72\xe9\x9d\x6c\x90\x5c\xa9\x74\xb8\x1c\x9f\xa5\x21\xf9\x20\xa1\xdf\xfa\xb4\xe2\xee\x15\xf6\x1e\x03\xb7\x42\xf4\x24\x70\xdc\x2f\xa9\xab\x25\x7f\x11\x36\xf9\xfe\x4b\x5a\xa2\xec\xe5\x20\x72\x30\xc4\x90\x6d\x67\xa1\x56\xa3\xff\xef\x47\x0c\xbf\x3a\x65\xe3\x18\x9b\x38\x9d\xdc\x66\xc6\x04\x0a\x79\x95\xc6\x8a\xe1\xdf\x20\x85\x94\x1b\x5b\x1d\xf7\xd9\x57\xfb\xcf\x36\x68\x24\xe0\x29\x1d\xf8\x8e\xae\x55\xd8\xd3\x04\x0d\x8d\x09\xf4\xf6\xff\xee\x34\xcc\xbd\x19\x61\x85\x2a\x5a\x62\xb2\x6c\x8d\xaa\xaa\x56\xa8\xff\x7f\xa8\x63\xb6\x3c\x6d\x60\x4f\xd3\x37\x82\x62\xe8\x15\xf5\x51\x71\xdc\xa3\x5d\x04\x76\x1f\xe3\xd9\xed\xdc\x6d\x32\x65\x7a\x96\xd6\x43\xd4\x60\x8e\xf2\x14\x3b\x19\xf1\xc9\xd8\xc0\x0e\xd2\x65\x47\x1b\x24\x5b\x60\xf3\x1f\x8c\x7e\xd4\x8d\xd6\xb1\x8b\x5b\xec\x1a\x6e\xde\x14\x5d\xea\x40\x28\x32\x30\x72\x4e\xc8", b"\x82\xc3\x74\x7a\x06\x58\xdf\x00\x6a\x7a\x20\x5a\x6a\xe2\xae\xdd\x5d\x29\x48\x48\x85\x59\xfc\x3c\xfd\x64\x3a\x64", b"\x86\x36\x79\x6d\xf6\x22\xd1\x3f\x07\x0f\xbe\xd4\x18\x4c\x81\x38\x35\x8c\x21\xdb\x30\xc6\x06\xb8\xf9\xbe\x52\x1a", )?; test( HashAlgorithm::SHA384, b"\xdb\xd2\x51\x6b\x03\xfd\xc5\x8b\x32\xc0\x23\x30\x80\xff\xee\xa4\x1c\x0d\x9c\x15\x6b\x30\x33\x2e\xc4\x2b\xe5\xe1\x05\x84\xbe\x3e\x3d\xb8\x5f\xfd\x5b\x5b\xae\x16\xfc\x87\x6a\x0c\x92\x17\x62\x7d\x84\x01\x12\x23\xfa\xb5\x7d\x17\x6d\xef\x61\xe4\x0d\x91\x2e\x7e\xeb\x2b\xf8\x68\x73\x4a\xe8\xf2\x76\xa9\x6a\xb1\x3d\xe5\x58\xec\x42\x61\x41\x67\xc5\xaa\x4c\x60\x35\x7f\x71\xfa\xc5\x89\x80\xe5\x79\x44\x0f\x69\x96\x8d\x22\x80\xbc\x97\x0d\x00\x66\xb5\xbd\x6a\x6f\x50\x02\x48\x15\x10\x25\x6b\x3e\xb2\x1b\xbb\x92\xef\x2c\xdd", p, q, g, b"\x6e\x6e\xe0\x31\x9a\xf8\xfa\xfd\x7a\xe0\x20\x13\xf4\x22\x7e\x26\x62\x44\xae\x5d\x87\xfe\x15\x6c\xef\xd4\x51\x8b\xcd\x71\xaa\x73\xf9\x36\x4b\xff\x35\xd4\xd2\x3d\x45\xb0\xf4\x7d\xfe\x93\xa6\x07\xd9\xf8\xb3\x99\xb4\x24\xba\x75\x07\x2f\xdc\xed\x6c\x3e\xd2\x11\x06\x06\xfa\x48\xed\x63\x3f\xae\xf2\x06\x4f\xb3\x36\x06\x9e\xec\x7e\xbd\x8a\xe4\x75\x97\x83\x89\xe6\xe4\x33\xd5\xa4\x35\xd6\x52\x9a\x66\xc4\x89\xce\x15\x39\x40\xd2\xb1\xb8\xc8\x86\xc8\x11\x0d\x8b\x0a\xeb\x64\x1a\x40\xe2\x85\xd6\x75\x1c\xe7\x10\x27\xc3\x0e\xc6\x2f\x4b\x1f\xc1\x4f\x4d\xa2\x0b\x1d\x50\x57\x42\xca\xda\x20\x1c\xea\x81\x93\x0c\x38\x1f\x8a\x6f\x13\xdd\x0a\x42\xaa\xc1\xe0\xbd\x7f\xcd\x19\xc6\xbd\xd1\x70\xfa\xc6\xa4\x23\x76\x7b\x83\x1c\x1e\x28\x9e\x0a\x29\xef\x85\xd8\x17\xad\x23\x8d\x91\xac\x3a\xce\x2f\x40\xa1\x63\xb0\xa9\xbb\xdd\xc6\xf0\x5d\x0b\xdc\xd8\xcc\x27\x4a\x74\xd0\x74\x3c\x9f\xb5\x65\x56\xec\x1c\xb8\xe9\xcb\xa9\x82\xc1\x5a\x9a\x66\xfa\x6b\x69\x99\xb8\x48\x5d\xb1\xa8\x6e\xe1\x8b\xe1\x6e\x06\x8e\x12\xa8\xa1\x65\xe3\x59\x9d\xf9\x66\x69\xa1\xb7", b"\x04\x04\x05\x13\x6a\x12\x20\xad\xbb\x64\xab\x75\x1d\xb3\x30\x7f\xaf\xad\x54\x47\xab\x2d\x9b\xcc\x52\xf7\x9b\xe3", b"\x1d\x35\xf3\x26\x9c\x77\xc5\x77\x24\x3f\x1d\xb8\xdf\xdb\xc4\xcc\x45\x31\x57\x42\x76\xf0\xda\x1f\x7a\x44\xac\xd4", )?; test( HashAlgorithm::SHA384, b"\x34\xc4\x54\x35\xd0\xcc\x29\x26\x92\x72\xa9\x3d\x43\x32\x06\x98\xe4\x54\xa7\xc2\x87\xdb\x9d\x06\x20\x92\xac\xac\xd7\xca\x08\x64\x55\xe5\x83\xba\xee\x12\x76\xca\xba\x06\x8f\xde\xeb\x52\x18\x33\x96\xd5\x44\x4c\x5a\x14\xad\x52\xa5\xc2\xbc\x08\x2c\xd8\x74\x52\xaa\x8f\x9b\x23\x05\x6b\x5f\x8a\xf2\x63\x8d\x96\x5e\xf4\xfe\x6e\x4e\x68\xe8\x8b\x0f\x50\xe0\x12\x48\xfe\x6a\x6a\x1d\x9d\x6d\x93\xb0\x3c\xd5\x5d\x16\xfd\x83\xcd\x4e\x06\x76\x3d\x92\x6f\x7c\x50\xf2\x0f\x0e\xd6\x73\x06\x13\xf0\xf4\xdb\x57\x1e\x22\xd2\x88\xe4", p, q, g, b"\x5e\xbd\x81\x52\x93\x5f\xf2\xa3\xf9\xa6\x1b\x27\x5e\x98\x08\xa0\x41\xaa\xd5\x65\x0f\x59\x3f\x61\x2a\xf3\x3b\xc4\x62\xb8\xc9\x94\x16\x93\x72\xe8\xf8\x0f\x51\xb1\x5f\x5c\xe9\x66\xea\x3e\x76\xa9\x12\xc6\x53\x97\x83\x37\xe9\x62\x21\x9e\x32\x3b\x6e\x92\x2d\xea\x4b\xcc\x23\xc6\x46\xa2\x2e\xec\xde\x02\x43\x31\x26\xfb\xac\xe0\xe3\xa0\x1f\xa6\xd0\xb9\xfd\xea\x92\x45\xd6\x78\x99\xa7\xb7\x45\xb8\x84\x7c\x80\x87\xfa\x7f\x6c\x0f\x3e\xda\xfa\xb4\xc3\xb4\x72\x20\x82\x1f\xe4\x6f\x1b\xcb\x00\xa3\x23\xdf\xf3\xde\xe4\x7e\xe1\xde\x2e\xce\x44\xe1\xfd\xf3\xe6\x4a\xa2\x0c\x9e\x6b\x58\xe5\x34\x48\x2e\x73\x13\xda\xce\x1c\x61\x7d\x8e\xa9\xa6\x5d\xd5\x1f\xd3\x30\x24\xf7\x35\xc3\x84\x4c\x5c\x6b\x4a\x3f\x44\x7e\x71\x4a\xb0\xc1\x7d\xc8\x8e\x33\xf0\x8b\x14\x2b\x72\xe8\x11\xe6\xda\x00\x29\x9c\x82\x89\x8a\xaf\x2b\xed\x5a\xe5\x17\x0c\x1d\xd0\x05\x67\x8d\x2b\x57\x6b\x9c\xe3\xe6\xbc\x6b\x2a\xeb\x04\xc9\xf0\x4e\x44\x4e\x2a\x98\x08\x40\x5f\xf5\x92\x65\x48\xb5\x93\x04\xdd\xdc\xa8\x97\x26\x31\xf7\xfb\x13\x68\x08\xe2\x13\xec\xd9\x3a\xf9\x8e\x2e\x54", b"\x66\x48\x1f\x24\x1f\x6b\x44\x31\x48\xf0\xb1\xf2\x45\x9b\xe5\xca\x16\x41\x3d\x94\x7d\x09\x81\x62\x87\x17\xc1\x08", b"\x2c\xda\xa7\x35\x00\xd0\xad\x29\x12\x52\xd0\x7c\xef\xf9\xcf\xea\xb8\x7a\x73\x97\x52\x29\x1e\xb5\xdc\xef\xea\x87", )?; test( HashAlgorithm::SHA384, b"\xd7\xac\x5c\xc8\xa4\xc3\xf3\x8c\xfe\x5c\x0e\x10\x68\xea\x28\xf0\xf9\x5d\x32\x50\xd1\xae\xae\x5f\x66\xbd\xc4\xd2\x2e\x23\xe2\x46\xff\x30\x42\x9c\xbc\xba\xd3\xb0\x2a\x62\xa0\xa1\x79\xd4\xd1\x07\x13\x0f\xa3\xa7\x80\xc0\x09\x2c\x32\x9c\x2b\x02\x6e\x12\xe6\x73\x5a\x75\xc4\x95\xb0\x97\xaa\x69\xeb\xe9\x8a\x96\xff\x89\x12\x34\xff\x37\x95\x11\x14\x9e\x07\xc6\xe2\x41\x1e\x58\x97\x6e\xe9\x3f\xba\x7d\x3d\x57\x0c\x91\x1f\x6f\x20\x83\x75\x78\x3f\xf5\xd9\x47\xa3\xaf\x0c\x83\x9d\x21\x0a\x8e\x4a\x8c\x8f\xa4\x1e\xfb\xc5\x7e", p, q, g, b"\x7b\x5f\xb0\x22\xb5\x5f\xb6\x1f\x8e\xf8\xcd\xbf\xee\x46\xc0\xfc\x61\xe5\x9f\xc6\x2d\xee\x5c\x14\xd0\xc3\x13\x4b\x4f\x26\x59\x11\x2e\x3f\x4e\x70\x17\xf9\x57\x4a\x27\x24\x18\x8b\xa6\xa1\xce\x77\x7a\x89\x15\xbc\x11\x71\xd7\x38\x75\x4b\x5a\xc1\xdf\x92\x31\x03\xad\x7b\x19\x85\x11\xed\x36\x27\x26\x68\xae\x0c\x2e\x31\x42\xba\x01\x1c\xb4\x5f\x89\x3d\xdb\xf7\xb3\x86\x25\x81\x8c\xba\x9a\x9b\x78\xae\xf8\xd0\x60\x07\xed\x50\x5e\x6d\xd6\xe2\x0c\x92\xd2\x50\x02\x34\xf1\x04\xc1\x28\x3f\x7c\x00\xcf\x2a\x3a\x32\x45\x8d\x97\xf7\xbd\x17\x09\x0f\x76\x23\x5c\x6c\x4f\x8a\xe1\x94\xd5\x2d\x67\xc7\x4a\x85\x49\x73\xfd\x12\x47\x51\xf7\xf5\x80\x4b\x67\x87\x9b\x02\x3b\xb6\xee\xac\x76\xe9\x6f\xe6\x76\xda\xeb\xbc\xb1\xbc\x94\xd5\xd8\x51\xd7\xbc\x56\xbf\xb3\xd2\xa0\xa6\xd9\x92\x31\x37\x86\xd9\xfb\x38\xad\x29\xb7\x62\x34\x94\x51\xd1\x49\xd0\xe5\xfd\xe6\xad\x49\x71\x83\xe3\x52\x82\x8e\x25\x1b\xcc\x7c\x3a\x91\x8b\xe4\xd0\x3b\x17\xaf\x60\xf3\xf3\xef\x6d\x9f\xb2\x45\x5d\xf7\xe8\xb6\xb1\x69\x47\x5e\x5f\x89\xdb\x99\x08\x54\x1b\x56\x7d\x0f\x29\x9b", b"\x5b\x6b\xe6\xba\xd7\x25\xaf\xa4\x42\xf2\x9a\xb7\xd3\x43\xd2\xf8\xb4\xb4\x94\x1c\xbd\x23\xd6\x91\x64\xb3\xc5\xfd", b"\x3a\x1b\x94\x63\x4e\x31\x3f\xc4\xdf\x82\x92\xe0\x38\xc6\xe8\x76\x33\x6c\xef\x88\xd6\x91\xb8\x94\xc0\xec\xcd\x3f", )?; test( HashAlgorithm::SHA384, b"\x7a\x96\x87\x3f\x07\x77\xe8\xad\xa9\x86\x75\x32\xae\x5f\x51\x93\x8b\xae\x2d\x56\xfb\x47\x1e\x0f\xef\xa6\x93\xb7\x1a\x2a\xea\x25\x71\xc0\x10\x8b\xa5\x9e\x63\x44\x01\xbb\xaf\x20\xa8\x48\xad\x8c\x30\x58\x48\x42\x0c\xee\x65\x4a\x30\x40\x00\x7f\x05\x5d\x4e\x97\x58\x07\x89\x4b\x56\x18\xb9\x39\x23\x63\xbc\x7f\x8c\x88\xd5\x26\xbc\x49\x1a\xdb\xd8\x92\xa9\x37\x51\xa2\x1d\x13\x7c\xee\xde\x8a\x04\x42\x3a\x4d\x0c\xa1\x55\x7b\xcf\x33\x4e\x4f\x85\x5b\x04\x47\x45\x44\x21\x29\x29\xa8\x1d\xc7\x1f\xb3\xfc\x41\xf7\x0d\x6b\x18", p, q, g, b"\x05\x31\x51\x81\x77\x08\x7d\xff\x8d\x04\xa0\x66\x6c\x13\x01\xa9\xb3\x84\x27\xc2\xea\x1b\x16\x2e\x6f\xca\x52\x01\x81\xef\x22\xa2\xd2\x05\xce\xff\xff\xb1\x54\x9c\x97\x07\x80\x55\x60\xc6\xc4\xb3\x19\x43\xd5\x25\x56\xbf\x30\x1c\x5e\x0e\x75\x92\x4f\xbe\x6b\x5c\x36\x2f\xc9\x80\x17\x53\xe6\x30\x43\x3a\x9a\x34\x8f\x53\xe6\x2c\x07\x46\xb2\x6e\x34\x8d\xfb\x85\x85\x3d\x1e\xf6\xec\xa0\x2c\xf3\xf3\x43\xe7\x7c\x17\x69\xff\xc1\xc1\x09\xb8\x8e\xce\xa1\x6a\xb6\xcf\x47\x6e\x54\x31\x25\x00\x98\x36\x22\xdf\x41\xe6\x95\xec\x27\xa4\x1c\xa7\xa6\x31\x21\xba\x97\xbe\xe7\xb0\xe9\xd5\x47\xbf\x42\x0f\x64\x7d\x0f\x86\x71\xbf\x41\x07\xa7\x12\xa7\xdb\xc1\xaf\x3a\xa8\xd1\x5b\x98\x54\x8d\x39\x09\xf7\x2b\x9a\x27\xf8\x1c\x46\xe3\xde\xfa\x95\xea\xff\x75\x90\xc6\x26\xb9\xba\x10\x97\x4a\xe8\xb9\xf5\x85\x35\xd0\x9c\xa3\x0f\x9f\x52\x35\x39\xcf\x58\x4f\x9b\xc6\xc7\x41\x85\xc2\xff\x12\x50\x4f\x55\x98\xff\xde\x6f\x86\x02\x1a\xe5\x14\x56\x2f\xed\x38\x81\x19\x7f\xca\x22\xdb\x55\x90\xfc\xf9\x52\x2e\xf7\x60\xed\x0e\x36\x31\xa6\xbd\x79\xf2\x90\x00\xb4\x2b", b"\x76\xbd\x6f\xf4\xcd\xc4\xfe\x37\xf6\x70\x5e\x77\xef\xdc\xac\x6f\xbb\x9d\x54\xfc\x0b\x22\x06\x43\xc6\x62\xac\xbf", b"\x8a\x12\x4a\x36\x40\xad\x73\x28\x0f\x30\x5a\xfc\x2b\xc3\xe5\x7f\x7a\x2e\x07\x40\x81\xbe\x7b\xc9\x0b\x5b\x1f\xaa", )?; test( HashAlgorithm::SHA384, b"\xd6\x96\x94\xbf\x9a\x93\xac\x0c\xc3\x91\x59\x73\xd4\x0e\x35\x12\x47\xc3\xbc\xac\xa9\x80\x69\xcd\x9c\x1e\x7a\x3c\x58\x50\x63\x6a\x59\x2e\xa7\x5f\xae\x7b\xfd\x38\xb1\x29\x0e\x3f\x4d\x0a\xae\x8e\xe6\x89\xce\x41\x37\xea\x86\x8a\xae\xbb\x17\xda\xfb\x25\x5c\x4a\x20\xe0\xfa\xc1\xf4\x66\x66\x12\xf9\x0c\x46\x32\x0a\x62\x00\x2e\xde\x31\x67\xa3\x4d\xff\x74\xa3\x06\xa0\x84\x24\x27\xcb\x9d\x2c\x61\x59\x9b\x05\xc6\x7b\x67\x31\x44\xf6\xc0\x82\x32\xd7\x71\xf2\xe0\xaf\x38\x25\x3f\x36\xe1\x22\x87\x0e\x04\xeb\xc5\x4a\x51\x2f", p, q, g, b"\x9c\x58\x8b\x76\x26\x9b\x2f\x08\x7f\x7e\x7a\xf4\xec\x4c\x0e\xf2\x63\xe9\x63\x6f\x45\xe7\x3e\x60\x45\x02\xd6\x2f\xae\x90\xa2\x51\x01\xbc\x2b\xad\x2a\x00\x21\x27\xd4\xb6\x0f\x5c\x4a\x13\x88\x88\x0c\xad\xe9\x46\x3a\xb5\xf7\x99\x7d\x54\xa0\x2c\x24\xe7\xd5\x1a\x4b\x8a\x7d\x91\xcd\xf6\xaf\xca\x2b\x43\x37\x68\x09\x45\x33\xa0\xde\x08\xde\xc1\xf1\x9e\xcc\xb4\x6d\xf1\x80\x0f\x53\xd3\xdf\xee\xfb\xfb\x76\x9a\x80\xe1\x68\x6e\x8d\x53\xc6\x0e\x8c\x15\x11\xa6\xdd\x4f\x42\xa1\x55\xbd\x85\xf7\x57\x40\xbc\xbb\x7b\x11\x27\x59\x18\x22\x92\x6d\x16\x82\x98\x23\x75\xea\x5e\xc2\x9f\xd1\xef\x4f\x28\x3b\x94\xe0\x24\x23\xa8\x30\xb3\x5e\x97\x3c\xaf\x12\x37\x7e\xe1\x8d\x2c\x6e\xe7\x77\x11\x84\xd7\xa9\x4e\x7a\x0c\x4a\x01\x04\x4a\xfc\x4e\xfb\x2f\xfe\xcb\x69\x5e\x23\x3a\xeb\x80\xc5\x16\xc7\x7d\x1c\x73\x0d\x30\xd1\xaa\x4f\x39\xda\x51\xbc\xc4\x8f\x44\xd0\x7a\xbf\xbe\x75\xf2\x28\xab\xec\x2e\x72\x73\x59\x3c\x98\xf3\x23\xa9\xb0\x03\x56\x2a\x16\x87\x52\xe8\x37\xa1\x23\x2f\x46\x2a\x23\xd3\xb1\x85\xea\x8a\x05\x36\x15\x70\x45\x5a\xad\xd1\x03\x70\x63", b"\x10\x8a\x08\x2d\x2b\xf6\x35\x8a\x73\x74\x65\x62\x43\x20\xc4\xfa\x9d\x37\x19\x74\x4c\x2d\xb6\x9d\x18\x96\x3d\x75", b"\x42\x0f\x35\x37\xfa\x68\x58\x65\x7d\xb7\xa2\x1e\x72\xe1\x1e\xc0\xec\x8c\xc8\x5a\x09\xa0\xd1\xa4\x45\x94\x49\x80", )?; test( HashAlgorithm::SHA384, b"\x17\x45\x5b\xfb\xb1\x28\xdf\x0f\x96\x54\x4b\xbf\x83\xca\x0f\xf3\x74\xbc\x08\x6b\x2d\xe1\x8f\x74\xf5\x90\x49\xf7\x3e\xff\x3c\x8e\xf3\x2a\x48\x42\x9a\x40\x38\x25\x63\x04\x63\x6f\x30\x32\x19\x27\x95\xba\x28\x07\x40\x7e\xf5\x2b\x8d\x59\xb4\x0b\xfd\x51\x75\x83\xf9\x98\x81\x02\x79\xc0\x21\x17\x71\xd9\xe5\x4f\x2b\x84\xe8\x98\xf9\x89\x2e\xf7\x7b\xeb\xa3\x3f\xf3\x1a\x28\x68\x69\x3f\x1f\x09\x78\xb8\x98\x95\xe3\x50\xd5\xde\xd2\x59\xfb\x13\x97\xe9\xc6\x98\x99\x86\x45\x2a\x0d\x77\xdf\x99\x04\x8f\xff\x84\xb6\xeb\x15\x0e", p, q, g, b"\x85\x0c\x0f\xca\xc0\x73\xc5\x63\x18\xa9\x21\x04\x65\x4e\x6a\x8a\xe7\x67\x8f\xc4\x01\x47\x28\x30\x46\x49\xbf\x10\x70\x27\x77\x06\xfb\xd3\x2e\xa4\xd4\x1f\x77\xf8\x0a\x80\xc8\x8f\x27\x01\xe3\x66\x5b\xe7\x3f\x59\xf9\x14\xa9\x15\xd6\x6b\x41\x1b\xb0\x5a\xe5\xc1\x8b\x00\xbc\x21\x62\x51\x39\x97\x32\xfd\xc2\xa6\x8b\xe6\xa2\x1b\x3b\x08\x87\x97\x41\x6a\xe0\x5c\xe8\x76\xb6\x80\x2e\x4f\x94\x1a\x21\xb1\xc6\x61\xe3\xf0\x6d\x50\x1e\xf2\xa1\x76\x59\xf0\x88\xd2\x19\x5d\xd1\x61\xf0\x64\x04\x48\x7a\x27\xb7\x9d\xf1\xec\x57\x4a\xc3\xab\xc3\x0e\xce\x2a\x14\x28\xc5\xe0\xc1\xd4\xc4\x98\x03\x39\x8d\x07\x14\xca\xcd\x98\x53\x85\x4b\x08\x74\x6f\xa4\x53\x56\x15\x45\xe6\xf0\xd9\x6c\xd2\xc7\xce\x1b\x89\xbc\xac\xe1\xc6\x97\xec\x4d\x61\x6b\xf1\x4d\x18\x89\xa7\x9a\x80\x6a\x36\x99\xf8\x4f\x19\xef\xe6\x90\xfa\x13\xa3\xb4\x38\x3e\xbf\x77\x26\x14\x00\xfc\xbe\x30\x9c\x2e\x5e\xab\x0b\x24\xb1\x97\xcb\x85\x6a\xa2\x7d\x7d\x71\xd9\x2d\x32\xaa\xb6\x56\xfa\xec\x5f\xf7\x92\xec\xe5\x38\x74\xc4\x06\x9f\x54\x0d\x94\x8f\x8b\x2e\x55\x99\x08\x2e\x21\xf0\x2d\x72", b"\x42\x3d\xe9\xe1\x12\xec\x38\xe3\xa0\x34\xf5\xd9\x67\x5c\x76\xf9\xdc\x85\x36\xb3\x0d\x05\x67\x8a\x29\x63\xec\x16", b"\x74\x05\x1e\x79\x69\x9f\xa4\x4d\xe1\x8e\x36\xab\x11\x68\x73\x59\x3a\x31\x0e\x4e\x09\xdc\xe1\x8b\x83\x3f\xc2\xf5", )?; test( HashAlgorithm::SHA384, b"\xde\x1f\x96\x06\x26\x1f\xf8\x22\x18\xc8\xc1\x45\xaa\x4d\x58\x47\x67\x3b\x45\x9e\xb5\x5f\xe7\xe6\x45\x4c\x04\x43\x26\x6b\xbf\x80\x0c\x1d\x09\x05\x1f\x5e\x31\x41\xc4\x37\x0d\x1b\x99\x0c\xf5\xfe\xa9\xd2\x68\x39\x86\xc3\xbd\xd2\x82\x31\x07\x82\x9a\xce\x6e\xd7\x03\x4c\xae\xb2\xf6\x57\xa0\x7b\x25\xb7\xd6\x02\x40\xa0\x20\x50\x26\xc2\xe3\x01\x81\x41\xd4\x79\xc0\x77\x87\xa1\x4e\x70\x26\x22\xf8\xe6\xdf\x70\x9b\x63\x6c\x6d\x3d\x0b\x5f\xd5\x4f\x55\x16\xdb\xad\x97\x03\x8e\x5c\x0e\xb3\x1f\x54\xdb\x12\x64\xd6\x00\xef\xc6", p, q, g, b"\x4d\x6e\x89\xb0\x22\xc2\x78\xf3\xbf\x89\x32\xe7\x06\xe4\x18\xec\xb2\x0c\x1b\xba\xb1\x3e\xa8\xc9\x0b\x6b\xd8\x43\x84\xf3\x8b\x31\x1e\x8f\xb2\xc4\xc0\xa9\x4b\xa7\xd3\xaf\xca\x1b\xa9\x42\x52\xa4\xc1\xac\x11\x87\x62\x2c\xd9\xc1\x6a\xa7\x3b\xb1\xb4\xa5\xcf\x55\xb5\xaa\x34\xbd\x93\x52\x6f\x18\x7b\xee\xb1\x17\x00\xe4\xaf\xb8\x8c\x81\x6e\xda\x50\xa5\x0e\x81\x86\x0c\x87\xfa\x66\xa1\xb6\x3f\x5f\xfe\xc3\xc3\xae\x39\xbd\xc0\x09\xd3\x8f\xa1\x3d\xa8\x63\xca\x5e\xc1\x34\xa7\xff\xcf\x5d\xc3\xca\x85\xcc\x34\xd6\x1c\x5d\xf8\xf9\xd9\xbd\xbe\x6a\x54\x10\x45\xb4\x5c\xb5\x12\xef\x64\xd1\xad\x3d\xb7\xb3\x7d\xba\x33\xc6\xe3\xc9\x61\x80\xcf\xb2\x6f\x48\xc6\x33\x73\xa0\xf0\x00\x3a\xe6\x58\x26\x79\xda\x48\x50\xad\x2a\x0b\x89\x9e\x0e\x8a\x18\x47\xdf\x07\xfe\xf3\xa4\x33\x0a\x72\xf8\xa8\x02\xc0\x6e\x8e\x95\x70\x7e\x0c\x7d\xc1\x91\x5f\x6e\x17\x31\xfe\x65\x0f\x1a\xe3\x52\xe7\x82\xd2\xdd\x77\xf5\x4e\x5d\xac\x52\x53\x9a\x10\xa2\x2b\xbc\x2e\xea\x31\xef\xb9\x44\x38\xa0\x30\xc4\xb2\x45\x1b\xbf\xf6\x90\x1b\x5f\xb3\x01\x6c\xd1\x62\xaf\x6b\xf0\xfb", b"\x5f\x38\x39\xeb\x66\x3f\x02\x6f\x79\x29\x12\xd1\xcb\x0b\x44\x8f\x5e\x2e\x59\x31\x39\x00\x1e\x83\x9f\x71\xc9\x42", b"\x6b\x07\xed\xb6\xa0\x34\xd0\x84\xa6\x1b\xf3\xc0\xa3\x6e\x7e\xe6\x91\x19\x48\xad\x8f\x6e\x50\xac\x68\x44\xb1\xf3", )?; test( HashAlgorithm::SHA384, b"\xc1\xed\xd8\x61\x51\xaf\x66\xc6\x22\x3e\x41\x3f\x17\xe7\x34\xb2\xbc\x02\x4f\xf0\x66\x57\x8c\x55\x30\x8f\x13\x88\xa9\x1a\xb8\x72\x70\xcd\x25\xca\x2e\xfb\xc2\x86\x7e\xb7\x15\xeb\xed\x6d\x10\x01\x2b\x6f\x48\x08\xf2\xde\x19\x86\xff\x7f\x4c\x36\x9d\xaf\x46\xc8\x0a\x61\x87\x07\x88\x8a\xe3\xf8\x6e\x38\xe7\xf2\x5d\x6c\xaa\x50\x91\x04\xd4\x85\x1c\xbe\xef\xbb\x75\x69\x2a\xad\x49\x9a\x33\xaa\x35\xb1\x14\x09\x30\x0e\x49\x5f\xe0\x07\x52\x4b\x4a\xf2\xc2\x0d\x33\xf1\xc8\xc0\x45\x16\xb6\x97\x3a\xc1\xe0\x7d\xf3\xf1\x60\xdd", p, q, g, b"\x90\xdb\xbe\x47\x41\xa7\x6a\x5f\xf2\x22\xdd\xc8\x33\xc0\xe2\xdd\x44\x5a\xd0\x17\x26\xbb\xea\x25\xca\xc2\x47\xf9\xef\x9d\xa6\x43\x93\x27\x36\xdb\x07\xcd\x9a\xef\xfe\xb4\x51\x19\x35\x1e\x00\x33\x2d\x9d\xfc\x89\xf5\x90\x3a\x54\x1e\x74\xe2\xe9\x70\x9d\x0f\x85\x2a\xd6\x52\x40\xd0\x61\x59\xfe\x54\x43\x6d\xd8\x20\x1f\x8c\x56\x92\x6e\x8d\x23\xc2\xec\xad\xeb\x8c\xbc\x9a\xeb\xf1\x2d\x52\xbe\x64\x89\xe0\xac\xb0\xe7\x52\x6f\xba\x37\x54\xb7\xec\x16\x3d\xc7\xe2\xfa\x91\x93\x31\x91\x24\xf0\xcb\xb6\x1c\x2a\xb7\xab\x1a\x28\xc1\x4e\x7d\x58\x1d\xfb\x8d\xe2\x3f\x53\x36\x4d\x20\x41\x90\xa5\x8f\xcb\x9e\xa5\xb6\xf6\x1a\x79\x79\xb8\x6b\xb7\xa7\xa4\x26\x3a\x10\x66\xf0\x51\x6e\x58\x70\xde\x42\x3a\x7e\x3b\x90\x6d\x90\x31\x3d\x1f\xf9\x32\x24\x50\xf7\x2d\xdd\xa4\x73\x3a\xc7\x4f\xca\x5d\x4a\xd2\xbe\x22\xc2\x66\x7b\x92\x21\x20\x69\x44\x6b\x42\xa3\x91\x23\x3d\x85\x21\x6a\x88\xc2\x5b\x76\xc9\x47\xd8\xd5\x65\x91\x00\x3d\xf2\x53\x2f\xcd\x7b\x18\xf9\x23\xed\x48\x2d\x46\x4f\xb7\x6f\x2c\x85\x61\x78\x40\xd3\x70\xab\x99\xe3\x20\xe8\x8c\xf9\xef\x8d", b"\x83\x6d\x84\xd8\x62\x71\xe1\x64\x84\x66\xd1\x95\x5c\x2b\x60\xb2\xa0\x4c\xc0\x21\x40\x50\x83\x62\x63\x47\xae\xf9", b"\x63\xc7\xee\xb5\xe0\x6e\x81\xd8\x92\x33\x56\xf7\x99\x81\x0a\x26\xaf\x67\xc0\xfa\xa1\x8b\x39\x22\x58\xe4\xa9\xa0", )?; test( HashAlgorithm::SHA384, b"\x2b\x5f\xb6\x13\x59\x8c\x02\x91\x6b\xf6\xb4\xb0\xfd\x7a\x6b\x54\x26\xac\x5b\x56\x95\x43\x92\xfb\xa3\x2d\xe0\x0b\xdf\x4b\x70\x95\x3b\xe1\x96\xad\x51\xff\x2c\x09\x7a\x81\xe6\xce\x1d\x17\xcf\x83\x7d\x24\x44\x75\x2b\xe9\x2b\xd4\xa9\xd1\xa8\xb4\x13\x27\x52\x7f\xf6\xbd\xc0\xe5\xc3\xe0\xcf\x46\xf7\xe3\x79\x66\xaa\xe1\x8a\x29\xce\x19\x81\xf2\x12\xd7\x14\xdd\x6c\x0c\xbb\x41\x0d\x3a\x5f\x3d\x00\x6b\xa9\xb5\x93\xda\x15\x0c\xe4\x22\xb5\xcc\x42\x0f\x3b\x56\x1b\xfd\xf1\x1d\xcb\x99\x10\x00\x57\x09\xee\xb1\x29\xe2\x06\x65", p, q, g, b"\x95\x94\x7f\xbc\x50\xd5\xa8\x02\x99\xc9\x0d\xd2\x7c\xf3\x91\x00\x91\x42\x0d\x8a\xf8\x49\x24\x0e\xbb\x54\x1a\x21\xb4\x9e\x52\x8b\x0f\x33\x17\xac\xc1\x04\x93\xd5\x0e\x6b\xce\x67\x6c\x43\x3c\x31\x14\x7f\x81\x28\x67\x89\xe6\xa4\x1f\x4b\x26\x03\xba\xc0\xf6\xe5\xee\x7a\xff\xdb\x44\xcc\xeb\x42\x86\x43\x58\x60\x7d\x45\xf4\x65\x5a\x70\x9d\x7d\x67\xf7\x16\xd7\x36\x7b\xb5\xea\xb3\x34\xf6\x1c\xef\x37\x20\xc0\x80\xca\xb1\x75\x12\x32\x9e\x6d\x99\x92\x5b\x47\xe4\x96\x0c\x85\x03\x1b\xfd\xdb\x13\xf0\xc6\x1a\xf8\x0e\xa4\x6b\x7b\x87\x02\xf8\xad\x34\x8d\x57\xd4\x81\xef\xe8\x21\x05\x4f\xc8\x3b\x52\x66\x78\x27\x56\xa4\x2d\xd4\x31\x88\x1e\xa6\xcf\xeb\x7f\x79\x00\xd8\xf7\x47\xaa\xc9\x97\x6b\xe8\x94\x59\x52\xaf\xb8\xa2\x74\xda\xd0\x34\x28\x08\x83\x10\xa2\x45\x6e\xc2\x54\xd1\xcc\xfb\x63\xee\xde\xa5\xd3\x74\xed\x8c\xc6\x37\xa7\xba\xab\xf8\xf4\x22\xe1\xa1\x2d\x5f\xf3\x16\xdf\xf8\xa0\x82\x06\x89\x31\x49\x0a\x47\x06\x50\x3d\x19\xf9\x35\x54\xf2\x52\x43\x75\x1d\xfe\x62\xcd\x87\xcb\x85\x6f\x64\x4f\xbb\x6f\xc4\x6f\xb9\xcf\x89\xaf\x5a\xea\x1a", b"\x7b\x45\x5f\xae\x10\x02\xfa\x87\xf3\x6c\xf6\xf3\x45\x71\x62\x25\xd4\xaa\x14\x07\x80\x2a\xf4\x08\x2b\xfb\xb1\x4a", b"\x23\x5d\x8b\xe4\xce\xb0\x17\x6f\x5d\x0c\x47\xc1\x19\x9a\xfc\x7e\x30\x41\xc7\xd7\x50\x8b\x9f\xed\xdc\xaa\x0d\x74", )?; test( HashAlgorithm::SHA384, b"\xbd\x7d\x69\xbc\xc2\xe4\xf8\xa4\x2e\x62\x7f\xa2\x1c\x7f\xa9\xfd\xd3\xe5\x74\xb6\xdc\x5a\xd2\x02\x17\xe8\x0b\xcc\x99\x97\xb4\xc5\xef\xb3\x1c\x7b\x65\xdb\xe8\xa0\xa3\x94\xf0\xaf\x58\x03\x87\xb9\x91\x78\x88\x15\x2d\xc4\xf6\x3c\xe5\x2d\x3e\xc4\xb7\x23\xbf\xea\x81\x14\x82\x5f\x9f\x1e\x25\x9f\x67\xb5\xd1\x3b\xca\xa6\x6c\x97\xde\x72\x5f\xae\x4a\xd2\x47\xbb\x92\x24\x97\xeb\xed\x0f\x09\x2b\xba\xc1\x2f\x2c\xbd\x9b\x71\xb2\x29\x08\x73\x78\xe8\xbe\x06\x26\xb8\xd5\xe8\x95\x0b\x0a\x6e\x69\xe0\x51\x29\xf0\xd3\x84\x2d\x27", p, q, g, b"\x6f\xa6\xde\xdc\x84\xa1\x47\x9b\xe4\x39\x06\xf2\xf6\x8d\xf0\xe9\x32\x34\xca\x22\x30\xc8\x32\xdb\x07\x9d\x9c\xbd\x93\x42\xb2\xdf\x13\xde\x4b\xff\x10\xbd\xd8\x31\x31\x34\x53\xb3\x3b\x72\x5c\xd6\x16\xac\xf1\xfe\x2f\x79\x27\xea\x32\xd4\x6f\xf1\x0e\xf1\x15\x4e\x50\x3f\x71\x16\x5a\xde\xaf\xfd\xd5\x00\xa8\x3b\xf1\x00\x1e\xd3\x6c\xa6\x5b\xb6\x97\x4d\x03\x72\xcb\x0f\x21\x18\x27\x84\x66\xfe\x12\x86\xad\xff\x3c\x7e\xf7\x19\xc2\xa0\x2c\xff\x9e\xd9\x37\x4f\xbb\xe6\x05\x18\x14\xd2\x68\x48\xb7\xd9\x70\xfb\xec\xfb\xbf\xfe\xdf\x40\xa0\x30\x83\xfe\x33\xd3\x06\x78\x38\xac\xe2\x28\x54\xa8\xe8\x8b\xfc\xb0\x2e\xcd\x76\xc3\x78\xbb\x5c\x8b\xab\xd2\x2d\xfb\xe0\x90\x75\x3a\xbf\x9e\x97\xcb\x6b\xa7\x08\xce\x00\xff\xea\x5c\x55\x0b\x09\xf2\x49\x30\x69\x8d\xf1\x15\xc0\x20\xb1\x30\x1d\x57\x1a\x47\x0e\x5a\x8a\x6c\xcf\xc7\x4a\xd1\x89\x49\xa5\x7f\x61\x4f\xcb\x0f\x7e\x8b\xf7\x53\x0a\x73\x1b\xb6\x09\x1a\x73\x01\xaf\x42\x89\x9d\x9e\xe9\xe4\x5a\xa6\x2c\xa4\x90\x3e\x66\x73\x3e\x47\xd0\x1e\x26\xb2\x99\x74\x6d\xa7\x5c\x7a\x57\xdc\x00\xbc\xeb\x4d\x6c", b"\x16\xa2\xa4\x85\x78\xa0\xb5\xb5\x75\x53\xcd\x20\x00\x5b\x7e\x84\x00\xe1\x06\x1c\x4f\xef\x20\xd0\x33\xf7\x2f\x8a", b"\x6c\x34\xd1\x76\xe9\x5d\xd4\x92\x71\xee\x48\xa3\x80\x2e\xdf\x42\x38\x40\x10\x84\xbc\x39\x30\x20\x14\x05\x69\x3a", )?; test( HashAlgorithm::SHA384, b"\x77\x66\xe1\xab\x76\x38\xbc\xda\x3e\x6f\xdb\xd4\xc8\x5b\x36\x61\xac\xb2\x76\x3d\x41\x13\x76\xb2\xee\xdb\x4b\x2c\x6b\xff\x5d\x8f\xa2\x0c\x0a\xe5\xb3\xcb\xed\x20\x79\x6a\x6d\x8b\x81\xa1\x09\x6d\xc3\x6a\x39\x82\x6a\x18\xff\xb8\x97\xd3\x6b\xfb\x16\x36\x3c\xca\x76\x32\xec\xb7\x1d\x2f\x99\x6c\xf7\xca\xc6\x66\x69\xbf\x4c\x83\x11\x4b\xd5\x3b\xe3\xbe\x33\x05\xef\xc9\x9d\x22\x76\x91\x88\xf8\x42\x89\xcb\x1d\x11\x50\x1f\x04\x0b\x85\xd1\x58\x90\xd2\x9a\xf2\xc8\xea\xe6\x14\xf7\x4b\xee\xee\xb5\xfc\x91\x5a\xfa\x43\x22\xc2", p, q, g, b"\x4c\x2b\x55\x90\x24\xf1\xb3\xff\x5c\x71\x67\x27\x0c\xd1\xf3\x3b\xbf\x0f\x40\xb9\xef\xa2\x5e\x13\x74\x41\xab\x46\x98\x15\x4e\x74\xda\x3c\xad\x34\x23\x6d\xa4\xbd\x1c\x57\xd7\x63\x8e\x42\x77\x27\x8b\x50\x8e\x85\xe3\xa9\x8d\x30\x38\x8a\xb8\x63\x8f\x55\x3e\x2a\x70\x00\x11\x92\x3e\x5d\x15\x4f\x8c\x14\x07\x45\x2d\xc4\xf8\x07\x70\xc9\xc3\x1c\x36\x8a\x21\xe4\x99\xd5\xdf\xb6\xf0\x5f\xd6\x77\x91\xe7\x61\xa4\x94\x20\x07\x10\xaf\x8c\x21\x88\x89\x2c\x2d\x1c\x31\x95\xbe\x4a\x0a\x1d\x67\x55\x1a\xd4\x66\xfe\xe8\x0d\x7e\xdc\x43\x53\x79\xa7\x2c\x3b\xff\xad\x27\x1d\xe3\x1a\xd2\xed\x10\x7d\x78\x4f\x40\xe2\x4c\x5a\x6e\x8d\x5a\xae\x8f\x24\x05\x96\x4f\xe3\xc2\x8c\xc3\x65\x2d\xc3\xc9\x52\x3b\x39\xd4\xb0\x83\xee\x65\xe9\xa0\x7c\xe8\x97\xa1\x7b\x02\xb3\x54\x76\x6f\x1b\x19\xc2\xb1\x22\x9a\xb4\x68\xb0\x14\x8c\xa8\xfe\x89\x48\x4b\x7b\x36\x00\x24\x21\x80\x86\xaf\x56\x40\x37\x07\xbe\xc6\x5c\x52\x28\x1c\xb8\xaa\x53\x46\xcb\x6f\x64\x81\x43\x0e\x8e\x05\x71\x46\xf3\x90\x60\x7c\x57\x2b\x5b\xd8\x42\x6b\x90\xef\x3a\x82\x7c\xb0\xd5\x8b\xd4\x38\xd1", b"\x09\xc6\x18\x78\xa9\x91\x71\x77\x05\x8e\x9d\xff\x27\x10\x6b\xdc\xa7\xd0\x6c\x50\x0e\x09\x09\x93\x06\x66\x8c\xbf", b"\x7b\x8b\x6c\x4c\x56\x15\x97\x6d\x7a\x73\x5a\xc3\xe1\x84\xcd\xe9\x61\x54\xff\xc8\x7b\x45\x89\x24\xd4\x60\x28\x95", )?; test( HashAlgorithm::SHA384, b"\x84\x09\x52\x78\xf7\xf1\xd5\x78\xe7\x98\x39\x9a\xf0\xbc\x9f\x46\x95\xf9\x30\x2e\xa5\x97\x24\x79\xad\xf9\x0c\x95\xfc\x25\xd5\x9e\x57\x6d\x97\xb8\x9b\x73\xde\xc6\x29\xce\xf0\x5d\x61\x73\xb5\x5d\x01\x5a\x3f\xb1\xd8\x19\x1a\xe5\x40\xd5\x52\x40\x9b\x03\xa7\xa8\xdb\x51\x1b\xad\x09\x51\x89\x6d\xb9\x49\xfc\xc2\x88\x70\xf9\xd1\x73\x14\x73\x4c\xa6\xa3\x47\x26\x83\xd0\x2f\xdc\x8d\xef\xa7\xb9\xd3\x76\x2a\xe9\x35\x7c\xa2\xa6\xab\x62\x3b\x04\x63\x50\xfa\x21\x1d\x52\x13\x78\x71\x27\xd2\x71\x1c\xbd\x91\x40\x5a\xbb\xe5\x0d", p, q, g, b"\x4b\x52\xc5\x6f\xc6\x49\x22\xac\x04\xee\x7a\x80\xfc\x5c\x22\x40\x13\xe2\xff\xda\xa1\x67\x38\x12\x57\xe0\x0c\x59\x7b\x43\x36\x41\xce\xad\xbc\x9b\x16\x56\x8b\xbc\x9c\x6d\x31\xd0\x2c\x8e\x36\xdb\x2e\x39\x87\x52\x0c\xe8\x59\x08\x56\xbd\x4a\x84\x1b\x72\x5e\xc9\x5a\x46\x59\xa6\x1a\x00\x86\xf6\x6a\x6b\xfd\xbf\x1e\x4b\xf9\x2b\x44\x19\x28\xcf\x31\x9f\x92\x9a\x64\x28\xf5\xe3\xba\x7c\x89\x12\x3d\xbb\x0c\xac\xc1\x6b\xb0\xe2\xb8\x08\x54\xb0\xf6\x0d\xfa\xa9\x9f\x9c\x4c\xaa\x41\x2c\x44\x3a\x07\x3b\x7a\x51\x25\x91\x25\xf0\x12\xd9\x8f\x0f\x66\x99\xd7\x0a\xde\x66\xdf\x9c\x5e\x18\x18\x56\x72\xe0\xe2\x83\x0e\x05\x85\x41\x3d\xa2\x95\x6c\x89\xd2\x32\x0f\xaa\xc0\x3a\xaa\x83\xfe\x71\x8a\x0d\x6c\xf7\xfe\xb3\x8a\x19\x4e\x43\x62\xd7\xc8\x9e\x4a\x13\x96\x7e\x3a\x2d\x44\x93\xf4\xec\x09\xac\x2f\xc8\x9d\x56\xa5\x95\x47\x2e\x60\x33\x24\x48\x54\x8d\x91\xcd\x6a\xac\x84\xa2\xf9\xb4\xd7\xa8\x04\x62\xdc\x15\x47\x79\xbe\x5f\x9e\x1f\x70\x9b\x9d\x9a\x15\x62\x73\x03\x3f\xe6\xe4\x84\x2e\xc4\x75\x21\x96\x4d\x2e\x2f\xe2\x62\x28\x0f\xdd\xec\x64\x03\xe8", b"\x0c\x4d\x45\x27\x81\x5a\x94\xbc\x2d\x77\x06\x3e\xa6\x90\x49\xbe\x6a\x2b\x3b\x3a\x3a\x0b\xad\xd5\xe6\x2a\x8f\x9a", b"\x57\x87\xce\xd7\x08\x1f\xad\x3f\xe1\x9a\xb5\xb9\x02\x8e\x9e\x8d\xf1\x86\x39\xe4\x99\x1a\xb6\xe1\xe2\x43\x41\x6e", )?; test( HashAlgorithm::SHA384, b"\x30\xee\xdc\x9d\x63\x0b\x63\x20\x82\xc1\x96\xb9\x69\xd2\x4f\x6e\xb9\xcf\x1b\x1e\x2c\x53\xd2\x44\xe8\xd8\xb5\x0a\x40\x98\x2a\xb5\x3c\x4d\x57\xff\x99\x5f\xa8\x45\x89\x08\xa7\x43\x89\x03\x82\xda\x65\x13\xcf\xe9\xc1\x99\x18\x24\x87\x36\x15\xa8\xa1\x63\x74\xa5\xe5\xdc\x2f\xab\x3f\x5c\xd2\x56\x52\xec\x8a\xa3\x93\x9f\x48\x84\xf7\x4a\xc7\x37\x98\x9b\x6a\xc2\xe4\x3f\x45\xb8\x85\x20\x6a\x31\xe7\x97\xfd\x85\x76\x35\x7e\x4b\x4b\xaa\x56\x62\x91\x81\x5d\xac\x2f\x54\x6f\x4a\xbf\x8b\xa1\xde\x11\x20\xfd\x80\x42\x84\xe9\x59", p, q, g, b"\x89\x20\xf6\xab\x95\xb1\xdc\x6b\x93\xe0\x8e\xad\x6b\x08\x14\x1c\xc2\xa8\xf1\xff\xbb\x71\xd5\xec\x59\x64\xf6\xb2\xc3\xd7\x2f\xf3\xad\xad\xe5\x22\x54\x37\x0f\x13\x09\x90\xb4\x34\x87\x77\x5c\x2f\xe0\x17\xa8\x20\x0d\x81\x19\x81\x8a\x15\xed\x7e\x56\x36\xbf\xbf\x31\x64\x04\x2f\x27\xbb\x1e\xa4\x18\x69\x8b\x67\x56\xf7\x5a\x8f\xda\xeb\xf0\xf6\xe5\x42\x3e\x46\x02\x87\xf4\xfd\xd2\xa0\xef\x30\x5e\x65\x87\x41\x37\x3d\x3b\xae\xcc\xe7\x90\x63\x96\x2f\x88\x33\x98\xc3\x14\xe3\x62\x30\xba\x8c\x57\x0e\x66\x7c\x30\xca\xc8\xfb\xaa\x4e\x70\x20\x2a\x91\x57\xd2\x27\x08\xca\x60\x54\x03\x06\x6d\x0f\xc8\x48\x45\xbc\xe9\xb8\xc3\xb4\x1e\xc3\x2f\x40\xc8\x45\xa5\x32\xfd\xff\x4d\xd1\x0c\xf6\x2a\x71\x41\x21\xea\x8a\x61\x88\x50\x06\x45\xaf\xa9\x31\x6f\xb3\xe1\x16\x28\xb1\x63\xd3\x5d\x8c\xfc\xc5\x52\x72\xb6\x50\xe8\x07\x2c\x23\x76\x45\x60\x01\x50\xbb\xb6\x6d\x39\x3c\x1c\x97\x34\x5d\x58\x20\xf1\x78\xdd\x40\x5b\x5d\x46\xfc\x4a\xc8\xa5\xf3\x92\x9e\x6b\x16\x27\x94\x40\x93\x17\x8a\x8d\x65\x10\x10\x59\xfb\xbb\xb7\x08\x11\x74\xf2\x30\x8b\x26\x53\xce", b"\x45\x21\x2d\x1c\x8c\x12\x80\x02\xfc\xb3\xce\x35\x58\x3f\xf8\xd0\x83\x63\x71\x1c\x15\x98\x30\x7d\x9e\xc6\xa1\x08", b"\x48\x58\x10\x56\x49\xdb\x59\x92\x76\x4d\xd3\x2b\x10\x2d\x9b\x9d\x2b\xc6\xaf\x64\xc6\xa8\x15\x95\x61\x1e\x3e\x20", )?; // [mod = L=2048, N=224, SHA-512] let p = b"\xbf\xeb\xd0\x00\xb2\xd6\xcd\x4a\xb3\x8e\xfb\xa3\x5d\xf3\x34\xdf\x72\x1d\x6c\x2f\x2b\x3d\x95\x66\x79\xcb\xad\x00\x9f\x3d\xfb\xd0\x02\x95\x2c\xc8\x99\xcc\x23\x56\xec\x87\x69\xbd\x3d\x1b\xa5\xa7\x30\x23\x72\x98\x88\xda\x92\xca\x48\xa5\xee\x94\xc9\x7f\x4f\x04\xa2\xe3\xac\xb4\xf3\x3a\x2f\x0f\xb3\x78\x3c\x31\xf2\xc7\x0f\xa7\xc7\x0f\x38\x21\x4a\x27\xda\xde\xc8\xb1\x2e\x67\x99\x6a\x9e\x85\xee\x3b\xb1\x48\x80\x31\x30\x14\x73\x92\xdc\x52\x53\xc0\x4d\x70\x63\x53\x5e\x6c\xd6\x46\xbf\xb1\x86\x98\x4e\x08\xb5\x8b\x74\xa7\xbe\x5b\x33\x3b\xf3\x2b\x0a\xbf\xd5\x66\x53\x60\xe9\xa9\x23\xa0\xc5\x28\xff\x1c\x62\xc7\x25\x34\x58\xf5\x67\x85\x28\x71\x9d\x43\x6e\x50\x14\x87\x41\xf4\x5d\xc7\xdd\x2c\x6c\xac\x71\xc5\x52\x31\xf1\x2a\x83\xfe\xfd\x2e\xd0\xa3\x3e\xde\x1b\x8a\x51\xf5\x66\xfc\xf7\x89\x06\x82\xcd\xc1\x93\x1d\xc2\x07\xc9\x2b\xf2\xef\x4e\x28\xab\x31\x66\x1e\xeb\x77\xf1\x60\x1e\xea\x94\x1c\x95\x91\xf0\x38\xd3\xf0\x0d\x91\x28\x57\xdb\x05\xe6\x4b\x2a\xd5\x69\x32\x00\x61\xc6\xf8\x63\xff\x33\x54\xd8\x42\xe7\xe7\xea\x71\x5a\xfe\xf8\xd1"; let q = b"\xaa\x98\x6d\xf8\xa0\x64\x27\x8e\x93\x63\x31\x6a\x98\x30\xbc\xfa\x49\x06\x56\xfa\xa6\xd5\xda\xa8\x17\xd8\x79\x49"; let g = b"\x81\x95\xad\x9a\x47\x8f\xd9\x85\x21\x6e\xe5\x83\x68\x36\x6d\x2e\xdd\x13\xc1\x2b\x3d\x62\x23\x91\x69\xfa\x04\x2d\x91\x15\x64\x08\xb4\x83\x12\x2f\x44\xed\x62\x36\xb8\x30\x8a\x6c\xdb\x52\xf9\xaf\x3d\xe8\x8e\xc8\x9e\x03\x9a\xfa\xd7\xda\x3a\xa6\x6c\x19\x76\x04\x9a\x8e\x0a\x7d\x18\xd5\x67\xba\xf9\x9f\xce\xfe\x31\x5c\xad\xa0\x15\x48\x38\x6b\x10\xb2\x5e\x52\xf5\x2e\xd7\x8e\xb4\xd2\x80\x82\xe5\xe1\xff\xee\x94\x80\xc4\xfe\x2c\xc4\xaa\xfd\x1e\xfc\x9d\x4f\xd2\xcc\x6d\x15\x59\x68\x93\x12\x71\xef\x15\xb3\x24\x0e\x7f\xb0\x43\xa8\x0c\x8f\x62\x8b\xef\xe0\x9d\x64\x50\x77\xc1\x02\x9d\x21\xe0\xac\x8b\xf0\xba\x9c\x27\x71\x4d\x1b\x58\x0e\xde\x59\x4a\xa0\x1b\x3b\x76\xf6\xe7\x45\xfc\x1e\xc0\x7d\xb3\x7e\x2f\xd7\xe9\x8c\x6c\x8c\x69\x15\x22\x8e\x42\x2c\x30\x9d\xe9\xf5\xdb\x16\x8f\x50\x24\x9d\x1b\xe1\xed\x32\x98\x09\x08\x08\xe2\xeb\xb8\x96\xbb\x79\xb8\xc4\xcb\xf9\x4d\x4c\x20\x64\xe3\x7e\x61\x2b\xa4\x44\x9d\x7a\xc2\x10\xed\xde\x21\x14\x16\xd6\x4b\x05\x1d\xd8\x04\x6a\xb0\x41\x73\x26\x65\x41\x1a\x7f\x15\x4d\x31\xb3\xe1\x1a\x51\xda\x7f\xc0"; test( HashAlgorithm::SHA512, b"\xe9\xf5\x9c\x6a\x5c\xbe\x8f\x5b\x0c\xf7\x50\x08\xd0\x6a\x07\x6a\x67\x39\xbd\xdd\xb3\x9b\x82\x14\x3c\xd0\x39\x39\xaa\x47\x38\xa2\x87\xc2\xa6\xf3\x18\x29\xbb\xe1\x5f\x02\xcc\x2e\xe7\xd7\x12\x2d\xbd\x13\x28\x25\x97\x0d\xad\xdd\x8a\x4d\x85\x1d\xa8\x6e\x7e\xdc\x89\x40\xcb\x11\x88\x31\x92\x18\xb8\xe0\x24\x8a\x10\x3e\xae\x34\xbc\x68\xd8\x5f\x5a\x32\x83\x0d\x7e\x5d\xc7\x71\x8f\x74\xdb\x5e\x42\x24\xc0\xde\xbe\x1e\x84\x1e\x1e\xea\x1a\x88\xfe\xe0\xf8\x5d\x9f\xb0\x87\xcb\xce\xe5\x5f\x86\x03\x7a\x64\x6e\x38\x34\x6d\x2b", p, q, g, b"\xaf\x67\x21\xbf\x75\xde\xc6\xa1\xb7\x6a\xd3\x5c\xa3\x75\x0d\xef\x31\x11\x7c\x5b\x44\x1c\x15\xa3\x06\x83\x5a\x1d\xb7\x4c\x00\x3b\x86\xae\x90\x99\xeb\xfb\x74\x5b\x0a\xa9\xcb\x00\x0c\xf4\x3f\xb0\x21\x51\x3b\x8f\x19\x7b\xc8\x65\xb2\x2b\xf9\x49\xb4\x91\x80\x9a\xd7\x52\xff\xc1\xca\x8e\x54\xbe\xa1\x6d\xc7\xf5\x39\xe4\xc5\x5f\xb7\x0a\x77\x43\xdd\x28\xf2\x62\xf6\x0e\xf0\xf2\xfc\xaa\xc2\x9e\x80\x21\xa7\x93\x8c\x18\xff\xe0\x30\x75\xd0\xb7\xe0\xa2\xb4\xdc\xab\xe4\x6e\xd1\x95\x3d\x33\xe3\x7f\x11\x3a\xf5\x19\xab\x0b\xf0\xb6\x18\x6c\x12\xb5\xf6\x48\x84\x37\xf5\x19\x30\x96\xe2\xfd\x6a\x6a\x18\x35\x60\x47\x94\xc6\x6b\x42\xae\x52\x65\xc1\xcf\x1c\xb5\x3a\xe8\x49\x97\x97\x5e\x03\x18\xa9\x3c\xe4\x1e\x39\x02\xe4\xef\x54\xde\x3c\x56\x55\x5b\xd1\x94\x91\xac\xd5\x3f\x3e\x57\x46\x4e\x1f\x46\x03\x89\xdb\xc5\xfa\x80\x64\x8f\xa5\xa5\xa0\xf2\x95\x6e\x9e\xc3\xb8\xdc\x44\x1b\x53\x5c\x64\x1c\x36\x2e\xed\x77\x0d\xa8\x28\x64\x9b\xfd\x14\x64\x72\xb0\xf4\x6a\x4c\x06\x4e\x45\x9f\x88\xbf\xf9\x0d\xed\xe7\xec\x56\x17\x7a\x9a\x71\xd1\x67\x94\x87\x12", b"\x9d\xa9\x96\x65\x00\xde\x9d\x3b\x6b\x7f\x44\x1c\xa5\x50\x23\x3f\xc4\x50\x94\x4b\xc5\x07\xe0\x1c\xd4\xac\xb0\x30", b"\x2d\x72\xf1\xf6\x68\x1e\x86\x7f\x7d\x8b\xea\xeb\xeb\xa4\xbc\x5b\x23\x28\x76\x04\xa6\x4c\xfe\xe1\xc1\x64\x59\x5a", )?; test( HashAlgorithm::SHA512, b"\x97\x1d\x16\xd1\x11\xc9\x6d\xe0\xf7\x09\x8b\x25\x6a\xf2\x13\xf4\x47\x5a\xef\x31\x00\x7e\x12\xe2\x97\x4c\x5f\x64\xb2\xf3\x35\xe0\x18\x3c\x19\x6c\x33\xd5\x0f\x64\x45\xc5\xf6\x14\x64\x95\x49\x77\x0b\x18\x74\xdd\x07\x56\xa9\xa8\xe3\x99\x71\xdf\xec\xc3\xf2\x67\xeb\xcc\x1f\x53\x01\x70\x3f\x88\x74\x3b\x0f\x37\x64\x82\xcf\xc0\x6d\x59\x48\xbd\x79\x26\xd9\x6e\xc4\xd7\x31\xa4\x4b\x0c\x0e\xee\x5e\x85\xda\x26\x68\x72\x65\xde\x5a\x66\xcb\x1a\x73\xa7\xe4\xf3\x23\x6f\x60\x64\x7b\xee\x5c\x16\x33\x40\xe1\x95\x05\x57\x7c\xf6", p, q, g, b"\x29\x05\x17\x29\x7e\x42\x49\xfc\x32\x12\xba\xd6\x72\x69\xe0\x32\x81\x8d\x76\x0b\x0e\xe0\x52\x5d\xc5\xa1\x7c\x97\x11\x6e\xe2\x9e\xb3\xb4\x50\xb4\x1d\x15\xce\xa4\x05\xd5\xe9\x83\xa8\x55\x81\x84\x06\x7f\x42\x4a\xcc\x49\x86\x76\x41\x5e\x17\x50\x6a\x35\x1c\x12\x4b\x54\x04\xf1\xd1\x71\x53\x27\x26\x19\xdf\x71\x3c\xe3\x4d\x03\xf1\xf9\xee\x28\x59\x2f\x22\xf8\x29\xa3\x19\x93\xb1\x06\xc7\x85\xfa\x6d\xbe\x57\xd0\x04\x9c\x81\x5d\xb5\xee\x2d\xfe\x94\x8d\xde\xdd\x1a\x5e\x2c\xd2\x34\x6c\xf2\xf6\x6f\x04\xfb\xad\x61\x9c\xd9\x83\xa1\xb0\x69\xb4\x71\xef\x9a\xdb\x4d\xf6\xce\xae\xa2\x3d\x09\xf0\xa5\x48\xc3\xc7\x20\x96\x34\xc8\xa0\x5e\x58\x97\x44\x59\x06\xde\xa0\x8a\x52\xe4\x07\x4b\xe2\x2d\x84\x85\xf2\x0e\xaa\xea\xdb\xaa\xb3\x97\x19\x9b\x06\x7a\xa8\x60\x05\x69\x91\xee\x08\x84\x80\xb4\x92\x12\x67\xa6\x98\xa8\xf7\xa0\x37\x77\xf5\x6b\xac\x84\xe5\x09\x03\xe8\x8d\x07\x26\x1f\x24\xd0\xa4\xf3\x17\x12\x8e\x01\xfe\x8a\x92\x24\xf1\x22\x93\x94\x9c\xb6\xc3\xf0\x95\xaf\xd1\x9a\xec\xb1\x6b\x20\x9a\x99\x48\x7d\xcc\x2a\x1b\x83\xc4\x9d\x75\xe3\x51", b"\x1f\x44\xf6\xea\xc2\x18\x23\x6a\x1d\x99\xcf\x76\x25\xab\xcf\x5c\x96\x4b\x0a\x0c\x5d\x88\xb8\xd0\x5d\x74\xa3\xc0", b"\x71\x01\x5c\xbe\x86\x22\xd2\xa3\x4f\xbb\x5e\x7c\xca\x8c\x59\xe8\x28\xad\xee\x71\xf5\x05\x24\x48\x2d\x9e\x79\x04", )?; test( HashAlgorithm::SHA512, b"\x08\xea\x09\xfa\x5e\xfd\xe2\x15\xbd\x8b\x3c\x4d\x6a\x9c\x90\xee\x93\x87\xff\xb7\xbd\x65\xbe\xcd\xb8\x8b\x40\x13\x2c\x63\x84\x10\x6a\xa6\x19\xb7\xc6\x6c\xa9\x20\x34\xd2\x84\x60\x85\x93\x86\x4c\xe6\xb9\x28\x77\x11\x2a\xa1\x39\x24\x0c\xb4\x4b\x38\x8f\xe6\x8a\x8f\xe0\x50\x1c\xa5\x84\xf6\xa2\xde\x27\xc0\xfb\x65\x8e\x72\xbb\x13\xfd\xdb\x8d\x03\x9a\x6b\xf8\x5d\x63\xa6\xc0\x73\xb2\x66\x80\x13\xce\x8f\xe5\x89\xa0\x15\x0e\x46\xd5\xb1\xd9\xb0\xcb\xb5\xa1\x4c\x10\x0a\xe4\xb2\x0d\x6c\xe8\x1a\x98\x7a\x50\xa9\x49\xf4\x34", p, q, g, b"\xb3\xe2\xb7\xe0\x64\x17\x21\xd6\x96\x16\x67\x95\x96\xcc\x75\x09\x1f\xad\xe2\xda\x05\x58\xe3\x10\xb8\xd1\x4d\xb0\xf4\x68\x6f\x1f\xed\x48\xd0\xfb\x7f\x0b\x3b\x27\xbf\x6e\x19\x81\xea\xfa\x77\x37\xa3\xe6\x51\x82\x8d\x1f\xcb\xf8\x83\x87\xd0\x6f\x78\x40\x4a\x7a\xfa\xea\xaf\x8f\xae\x18\x93\xbe\xa3\xa0\x9a\x11\x88\x93\x93\x7a\xe2\xa8\xfd\xef\x33\x20\x94\x2a\x15\x84\x63\xde\x4f\xdd\xc1\x19\x87\xf2\x3f\xee\x96\x33\xe0\x6a\xc2\x39\xc0\x66\x10\xbc\x45\x31\x9a\xba\xfe\x51\x7c\xe4\xae\xae\x62\x47\xea\x78\x9d\x7d\xa6\x0d\x3e\xed\xdf\xdc\x4b\x23\x2b\x4d\x7a\x06\x9b\xcc\x0e\xac\x7b\x99\xfc\x08\x8f\xb7\xec\x19\x46\x03\x4a\x98\xd7\xe6\x9c\xab\x0c\xb2\xb0\x6b\x3d\x9d\xea\xcd\x1b\x43\x3e\xbe\x94\xf5\x47\xa3\x22\x89\x5c\xca\x9b\x0e\xd3\x19\xb1\xd4\x58\xc3\xbf\xb2\x60\xbe\xb6\x41\xa5\x34\x5d\xbe\x3d\x01\xce\x80\x0e\xc2\xc6\xbd\x43\x0c\xe3\xe3\xf5\xf7\x8f\xca\xbf\x91\xa2\x96\x58\x66\x1c\x57\x3b\x9f\x6f\xd3\x81\x2e\x56\x0d\x88\x8b\x6c\xdf\x3d\x57\x67\x3c\x16\x30\xe0\x0c\xa8\x41\xee\x99\x49\x58\xb2\x50\xda\xfb\xc3\xe8\x3b\xcb\x8b\xe5", b"\x6c\x03\x63\x7d\x25\x3a\x8d\xcd\x09\x07\xd6\xde\x93\x92\x6b\xdb\x3e\x1e\xa3\x13\x5a\x70\x9d\xa2\x30\x9a\x8d\xa6", b"\x23\x6e\x51\x63\xf2\xc2\xeb\xe0\xec\xcd\xbd\x33\x51\xe4\x28\x55\x31\xa4\xf5\x3e\x45\x28\x4e\x41\xdb\x37\xe2\x66", )?; test( HashAlgorithm::SHA512, b"\x95\x7c\xef\x16\x3b\x16\xd8\x07\x3d\x5d\x3f\xe1\x58\xfa\x0c\x73\x38\xbd\x10\x7c\x6a\x65\x3c\xb0\xf1\x1e\xbe\x41\x40\x26\x07\xb8\x22\xab\xe3\x0e\x36\xca\x9e\xe4\xc9\xde\x00\xcf\x72\xdb\x97\xf5\x7d\x78\xf3\xdb\x49\xa8\xe1\x09\x32\x85\x56\x3c\x68\xb0\xf4\xe1\x24\x83\x0b\x9f\xeb\xfa\x3e\x75\xce\x2e\xa5\x9c\xba\x2c\xc6\xd7\x1e\x90\x8b\x5e\x6d\x8f\x46\x39\x54\x92\x2b\x82\xbb\x55\xa6\x9f\xb2\xff\x14\x3f\xfc\xae\x6b\x56\x56\x14\x3c\x8b\x6c\xc2\x4f\x57\xb1\x7c\xfb\x02\x0f\x6e\x15\xbd\xc5\xf2\x54\x36\xd0\x7b\x7f\x8a", p, q, g, b"\x3f\xcb\x8e\x44\xd6\x88\x0f\x9e\xeb\xae\xdf\xb7\x59\x94\x60\x5c\x9e\xc0\x01\xf0\x59\x5a\xeb\x5f\x2b\xca\xf6\xb3\x98\x7b\xc2\x8a\x7c\xa9\x05\xe1\xfe\xd7\xe3\xc7\x15\x40\x1b\x5c\x60\x8d\x12\x07\x69\x38\xa1\x80\x13\x47\x3d\x8a\x43\x32\x77\xfd\x9c\xe5\xa5\xca\xe0\x38\x28\x1e\x76\x8f\xf9\x09\xae\xbe\x4d\x25\x7d\xcb\x5d\x93\x48\x80\x22\xd0\x7d\x4c\x28\x62\xaf\xb2\xbf\x8a\x2b\x1e\x97\x4a\x8e\x7b\x6e\x17\x6b\x1b\x0b\x7a\xd6\xf6\x3b\xda\x1b\x71\x42\xe4\x6f\x50\x4d\xcc\xcc\xa7\xd1\xe2\xe7\x66\x27\x58\xf7\x60\xe6\x24\xe5\x95\x28\xc5\xa0\xc9\x56\x3e\xd5\x17\xc6\x91\xfb\xa2\xab\xf6\x68\x99\x24\x11\x78\x22\x3b\xa2\x00\x13\xed\x0a\xb2\x1f\x91\xf3\xe6\xbe\xf7\x55\xc8\x10\x0c\x51\xee\x94\x7b\x7a\x9b\xa3\x85\x70\xf8\x80\xb5\xe4\x2f\x24\xb7\x2d\x53\x21\x13\x2e\x03\x1b\x98\x5a\x0d\xb8\x25\xbf\x3b\xb0\x0a\x77\x71\xa0\x30\x07\x38\x7e\x03\xce\x02\x0f\xc3\x58\xe6\x5e\xd3\xde\x8d\x84\x7f\x5b\xe6\x07\x20\x91\x7c\x06\x16\xa4\x50\xaa\x34\x1a\xe0\x0a\xbe\x0a\x80\x9c\x38\xe9\x73\x14\xf3\x03\xfe\x9b\x0c\x6c\xde\x44\x6d\x02\x17\xcc\x4e\xab", b"\x15\x03\x62\xda\x79\x27\x01\x69\x4e\x23\xf0\xb0\xa9\xb7\x03\x54\x37\xcc\x8f\x4f\xaa\x45\xc6\xdf\x8f\x79\x82\xfb", b"\x6d\xf4\x32\x1c\x61\x73\x87\x43\xa9\xfe\x78\xec\x76\xb4\x95\x26\x92\xaa\xa3\x72\xd1\xc8\x53\x0f\xba\x0f\xcd\xec", )?; test( HashAlgorithm::SHA512, b"\x20\x4d\x9c\xde\x24\xa2\xf0\xde\x02\xaf\xf0\x20\xf6\x36\x3f\xd6\x8f\x70\x42\x0d\xc1\xa9\xb5\x13\x82\x16\x20\x13\x63\xf8\x32\xda\x0a\xa8\x01\x86\x5a\x75\xa2\x43\x42\x7d\x9d\x6c\x78\xdc\x5e\x60\x41\xb2\x7d\x03\x36\x60\xe1\xe4\x05\xab\xe1\xbe\x27\xc9\x09\x99\x4b\xd6\xfb\x57\x18\x0c\x3d\x6b\x49\x8c\xe8\x79\x3b\xee\x8e\xcf\x51\xe0\x6b\x96\x41\x1d\x00\x99\x62\x09\xf4\x4a\x38\x09\x26\xc7\xb1\x95\xe8\x4e\x78\xf0\x1f\xe0\x2e\x0b\xc7\x03\x2c\xa4\x62\xa5\x18\x26\x83\x47\x52\x22\xf9\xdd\x8f\x3a\xde\x1a\xb8\xfe\xa3\x18", p, q, g, b"\x99\xb8\xfc\x6e\x64\xcc\xe2\x62\xed\x74\x1c\x30\xcd\x58\x69\x86\xaa\x2e\x8f\x63\x71\xb8\x48\xa2\x61\x7c\x61\x98\x97\xde\x23\x72\x6b\xb5\x45\x36\xec\xe4\xb4\x60\xcc\x7f\x1f\x39\xe0\xc1\x84\xeb\x19\x29\x1e\x93\x0d\xc9\x14\x0e\x4b\x77\x35\x54\x1e\xee\xf8\xca\x8e\xbc\x81\x79\x0f\xed\x37\xa5\xf0\x8e\x9d\xa9\xab\xc6\x6a\x3a\x2e\x90\x99\x02\xa4\x21\x21\x06\x92\x7d\x08\xab\xec\x01\xf2\x7c\x60\x56\xb6\xe0\x38\x11\x50\xbd\x74\x2d\x40\x9f\x68\x10\xfa\x58\x18\xff\xcb\x3f\x18\x2a\xdf\x89\x4b\xa7\xf8\x06\x78\xce\x88\x3c\x10\x89\xa6\xae\x71\xdb\x3a\x11\x5c\x38\x6d\xd9\x15\x3f\x41\x91\xfc\x36\x54\x61\xac\x86\x83\x8e\xcf\x2f\x3f\x81\xcc\xf2\x83\x29\x7a\x6f\xbc\x64\x4f\x52\xaa\xe6\x64\x90\x1a\xe3\x0c\x96\xfe\x4d\xf9\x30\xcf\x1a\x41\x75\x72\x41\xcc\x4d\x9a\xdf\xcc\xdd\x9a\x6b\xd5\x00\x4b\x05\x75\x74\x43\x59\x88\x56\x40\x0d\xd7\x71\xdc\x08\x90\x95\xc7\xdc\xde\x82\xf7\x21\xf9\x86\xaf\x63\x66\x38\xee\xa2\xc7\x17\x70\x85\x6c\x2b\xa8\x03\x15\xe8\x69\x61\x42\xa1\x1e\x51\xeb\xd7\x55\x9e\x9d\xa6\xa0\x0b\xe3\xf9\xf3\x8c\x61\x4e\xf2\x07", b"\x9c\x02\x33\x31\x75\x1c\x79\xd5\xda\x35\x5b\xb5\x8e\x2b\xbe\x2e\x97\x3e\x3e\x4b\x4f\x52\x74\x3c\xe1\xf1\xee\xc2", b"\x96\xad\x0e\x8c\xa9\x06\x27\xfb\x7a\xc4\x54\x0c\x9b\x58\xa0\x16\xee\x6c\x4e\x0a\x6f\x0a\xa1\xe7\xde\xf8\x1a\x51", )?; test( HashAlgorithm::SHA512, b"\x1e\x4e\x58\xaf\xb3\x4c\x5d\x6f\x64\x5a\x82\x64\x5b\xe3\x58\xa2\xe2\x28\xcc\x7b\x9c\x23\xdd\x7f\x3a\xa7\x95\x95\x81\x4d\x05\x4b\x92\x3b\x9c\xbc\x6c\x9e\x6c\x6f\x94\x84\x8c\x1a\x4d\x21\x56\x79\x02\x3a\x96\x97\x6a\x44\xe9\xb5\x91\x36\x24\x1f\xdf\x26\xf8\xf7\x1f\xe5\xa9\xbf\x36\x6e\x49\x12\xb5\x93\x1e\x1c\x8f\x63\xc3\x7f\xae\x2b\xf1\xd5\x5b\xa3\x94\x3a\x65\x0b\xb4\x63\xcd\xed\x9a\x7b\x06\x2a\xe5\x5a\xa5\x7d\x9c\x5c\xee\xd3\x23\xfd\x9a\x75\x55\xe4\x8b\x83\x4d\x3a\xd4\x44\x1c\x35\xd9\xe0\x7c\x7c\x6e\x4d\x5d\x0f", p, q, g, b"\xb4\xde\xa0\xd5\xb6\x71\xcc\x81\x53\x82\xd0\xec\x6d\xce\x66\x1c\x30\xff\x93\x71\x9d\xc7\xf5\x6e\x7e\x61\xdf\x6e\xb6\xa3\x20\x7a\x05\x61\x79\x38\xc8\x74\xbc\x3a\xb0\x93\xbc\xdb\xbc\x98\x3a\x4b\x0b\x58\x7d\x60\xfd\xeb\x7b\x87\xf7\xb0\xbe\x4a\x65\x68\x83\xf5\x44\x3c\xa7\x86\x45\x41\xcc\xbf\xe0\xd0\x83\x56\x36\xef\x08\xa9\x36\xb2\x32\x1a\x51\x50\x3b\xe1\xee\xc5\xf7\xbc\xcd\x0c\x73\xc9\xcd\x52\x39\x7c\xc2\x14\x31\x8b\x30\xe8\xbe\x1e\xab\x57\x20\x0a\x4d\x4d\xf7\x8a\xf9\x91\xbd\xe1\x83\xe0\x16\x4e\x69\x4d\x83\x08\xb7\xd2\x0d\x06\x7b\xfc\xab\xdc\xb5\x0f\x7a\x2c\x19\x0c\x66\xce\x3d\xd0\xe1\x89\x60\x93\x9c\xb5\x7f\xc3\xa2\xe5\xa6\x04\xf3\xd9\xbd\x6f\xa4\x40\xd5\x4e\x9c\xc0\x38\x39\x58\xa0\xd6\xaa\x2a\xb6\x70\x97\x0f\x9b\x2c\xaf\x86\x6e\xe5\x07\x06\x73\x43\xf7\x51\x3e\x0a\x98\x1f\x3a\x34\x4f\x2f\x75\x3a\xf4\x4f\xda\x26\xd6\x61\x79\x60\x32\xbd\xa0\xf6\xcc\x30\xa9\xa7\x89\xdb\x8d\x3d\x54\x6f\x02\xf8\x98\x11\x68\x05\x18\x0c\x6f\x0d\x2f\x53\x88\xab\x51\x10\xa5\x21\x07\x7d\x88\xd2\x14\xfb\xb3\x2e\xed\x26\x64\x40\x6c\xde\x9b", b"\x04\x34\xef\x1c\x12\x72\x07\xd0\xc8\x84\x70\x1e\x75\xd8\x01\x72\x5c\x45\x1c\xe6\x7d\x2e\x71\x53\x46\x38\xb2\x31", b"\x0c\x62\x5e\x4a\x33\x4d\xb0\x78\x25\xa4\x6b\x55\xda\x9c\x2e\x8a\x5f\x60\x0a\x36\xb7\x16\x06\x83\x40\x97\xe7\x77", )?; test( HashAlgorithm::SHA512, b"\x5a\x47\x0a\x38\xb2\xeb\xbe\xad\x08\xe0\x10\xef\xef\x74\x61\xf6\xf8\x59\x25\x7d\x91\xa6\x1e\x2f\x0b\xa8\x09\xe2\x8c\x0e\xa3\xd4\x10\xe4\xf4\x14\x77\xa3\x98\xd5\x93\xdf\x58\x03\x9c\x43\x36\x26\x0e\xa7\xd8\xe9\x8c\x9d\x7d\xaa\xd0\xc3\x1e\xcd\x15\x67\xc7\xdb\x73\x01\x79\xe2\xa9\xa6\x20\x07\xbd\x56\xf9\xd9\xda\x48\xde\xaa\x65\x7a\xc9\x22\x93\xe5\xbf\xaf\xbd\xeb\xad\x1a\xfe\x25\xc4\x1e\x1a\xa0\x9d\xb6\x1f\xcc\x19\x19\x71\xc3\x75\x49\x15\x5b\x3e\x67\x95\x69\x13\xaa\xe3\xa5\xf6\x24\x5c\xfc\xb9\xaa\xd5\xdc\x1e\x15", p, q, g, b"\x06\xa2\x0d\x55\x71\x29\x6e\xeb\x87\xe7\x9e\xb2\x74\x03\x6d\x81\x9e\x86\x23\xb1\x5d\xe4\x4c\x26\x97\xda\xde\xca\xb2\x99\x6f\x51\xa7\x5a\xa0\x88\x49\x0e\x68\x3f\x34\xd5\xe0\xe7\x1d\x9f\xb8\x73\x4b\xcf\xb7\x1e\x9d\x19\xcb\xda\x3c\xac\xa5\xce\xc4\x17\xfa\x37\xa0\x61\x42\xbf\xc0\x68\x2d\xe5\x6f\x0d\xce\x6e\x82\x6e\xe9\xf3\x0d\x01\x27\x98\x59\xd3\xff\xbd\x44\x33\xbf\x4a\x10\x57\xba\x0a\xd7\x50\x60\xd4\x1f\x96\x8f\x6d\xa8\x22\xc3\x3c\xbd\xa9\xf7\x72\xc2\xb7\x7b\xc1\xb2\x93\x05\xcb\x69\x71\x82\xc0\xd3\x9b\x13\x28\x68\x93\x2c\x64\x01\x6b\xc9\x07\x1b\x30\x92\x0e\xb3\x85\xc5\xae\x41\xc5\xd4\xf6\x31\xbf\x5f\x54\xb1\xeb\x4b\x37\x3b\xb3\xe0\xbf\x6e\x44\x8a\xd8\xc9\x88\xfe\xa1\x6e\x64\x37\x90\x30\x7b\x8b\x85\xf0\x09\xfb\x67\x31\x72\x17\xd9\x14\x8c\x6c\xd7\xa4\x61\x36\xee\xce\x19\x50\xa1\x19\xe5\xa4\x16\xa1\x97\xe0\x0d\x0e\x92\x9b\x04\xa5\xbb\xf6\xc9\x88\xd8\x59\x5a\x0b\x2a\x5c\xa7\x19\x26\xba\x35\x1a\x5f\x76\x74\xaf\x41\x83\xb5\xa6\x89\x79\xbe\xdd\x64\x91\x29\x5b\x0f\x17\x2e\x73\x73\xec\xa7\xe6\x2d\x78\xd7\x44\xfd\xcc\xec", b"\x74\xdd\xdf\xa3\x5b\x25\xd0\xc0\xb2\x85\xa5\xd2\x17\x19\xee\x39\xd6\xe3\xf4\x43\x44\x5c\xeb\x90\x55\x6b\x01\x86", b"\x47\x48\x65\xd3\xef\x07\xf5\xdf\x49\xe0\xa6\xeb\xfb\x5a\xb5\xc2\xed\xe4\x7c\x4c\x63\x14\xbe\x4c\xcf\x45\x5e\x21", )?; test( HashAlgorithm::SHA512, b"\x08\x49\xd6\x7e\xad\x3e\x8c\x44\xad\x3b\x2f\x94\x9b\xe1\xcd\x9f\x9a\x4b\xf8\xb5\x78\x5b\xd0\x0c\xa6\x60\x38\xe9\xa8\xb9\x37\x27\xa6\x52\xa4\x15\xc1\xd8\xa1\xec\xfc\xad\x77\x78\x2d\x87\xd9\x12\x62\x3c\x2f\xef\x45\xb2\x08\x3e\xc0\xf7\x9a\x26\x4e\xf7\xc5\xbf\xb7\x6f\xde\x5b\x22\xb9\x84\x53\x92\xe7\x59\xa1\xec\x05\xfa\x63\x87\xcc\xd2\x94\x3e\xf1\x27\x7c\x2e\x06\x03\x37\xf8\x2a\xa5\x62\xce\xe5\xbd\x7c\x15\x82\x58\xf2\xe7\x79\xd5\x1e\x47\xe0\x00\xa7\xb0\x70\x60\x77\x49\x09\x76\xa0\x77\x63\xe2\xef\xb2\x75\xb5\xbf", p, q, g, b"\xb1\xc6\x14\x42\xd8\xae\xda\xe0\xa0\x4d\xae\xf7\xb6\xf8\xa4\x9c\x6d\x07\xbd\x95\x8e\x8e\xc5\x61\x90\x6d\xdf\x31\xf3\xb4\xff\xd4\x81\xda\x54\x43\xfe\x87\x88\x05\x6c\x4e\xa7\xb5\xdf\xa2\xce\xe6\x47\x4e\x3f\xdc\x83\xfc\x04\x3a\x2b\xba\x33\x3d\x50\x3a\x2a\x93\x88\x65\xec\x3f\x11\x86\x40\xe8\x45\x7c\x7d\x97\x4e\x2a\x65\x65\x9c\xef\x5b\x7a\xe4\xf4\x9a\x05\x4d\x94\xae\x5e\x2e\xb6\x34\x5f\x5b\xda\xf9\x21\x48\xbe\xec\xc1\x09\xc5\x50\x31\xfc\xcd\x90\xce\xf8\x82\x13\xb6\x9d\xdb\x75\x4b\x40\xca\x8d\x8f\x0a\x4b\xfc\x81\xa2\x87\x63\x7a\x38\xc2\x18\x07\xf7\x27\xa6\x70\x25\xff\x67\xb7\xfc\xc5\x44\x18\xad\xad\x40\x8a\x5c\x7d\x1c\xe0\x5a\x1d\xe7\xe3\x09\x88\xd5\x60\xe7\x79\xfd\xea\x1b\x78\x75\x33\x14\xb0\xb8\x0f\xda\xcb\x62\x46\xfa\xa4\xb4\xc4\xee\x8a\xcc\x5a\xe2\x4b\x82\x31\x20\x40\x13\x4c\xd8\xcc\x2f\xd4\xfc\xb1\x91\xfe\x43\xf6\x4d\x14\x06\x24\xa8\xc6\xc2\xac\x5f\xa4\xbf\xdb\xa5\xd6\x25\xd7\xd2\x1e\x3c\x3f\x6a\xcd\x8a\x15\x3a\x04\xfb\x22\xf8\xd3\xb2\x44\xae\x8c\x6a\x1d\xd0\xe6\xe3\xb2\xf7\x3c\x06\x4f\xfa\xbf\xad\x6c\xc4\x61", b"\x2a\xeb\x7f\xce\x1b\x77\x64\xd3\x2c\xfb\x7d\x85\x25\x4c\xee\xd9\xf3\xa6\x33\x7e\xe8\xda\xb4\x2c\x8a\xb7\xa4\x15", b"\x17\xcc\xe1\x3b\xcb\x91\x7c\xdb\xef\xe0\xc5\x66\x31\x8f\xc9\x74\x20\x4b\x70\x0c\x5c\xdd\xc5\xb2\xb4\x99\xa7\x8e", )?; test( HashAlgorithm::SHA512, b"\xe7\x46\x39\xf2\xba\xd4\x2f\xd6\x39\x3f\x9b\x35\x0d\x6e\x19\xcd\x4c\x1c\xe0\xf4\x1e\x8c\x90\x26\x84\xef\x6f\x86\x79\x0f\xfc\x83\x11\xac\xd9\xb5\x7d\x65\x21\xe8\x03\x39\xb3\x24\x3f\x6e\xc6\xb0\x1a\x06\xea\x89\x9f\xd7\x5d\xa9\x1e\x10\x80\xfd\xf0\x61\x29\xdd\x85\x1a\x89\x5d\x74\xb1\xef\xb9\x83\x72\x89\xc1\x1d\x68\xe1\x30\x8c\x47\xbb\x8c\x59\xd5\xeb\x89\x5d\xb5\x3b\xba\x29\x10\x2a\x5b\x48\xb1\xe7\x5c\x73\x38\x7f\xf2\x2e\x6c\x04\x61\x19\x6a\x7d\x48\x61\x5f\xfd\xb9\xc8\xff\x4e\xc6\x58\x7b\x4f\x68\xd2\x60\xad\x86", p, q, g, b"\x9e\x1b\x77\x24\x3a\xba\x08\x86\xf9\xba\xec\xa6\xc1\x1b\xd2\xc5\xc5\x55\x47\xcc\x50\x2e\x73\x1d\x9c\x47\x25\xda\x87\x77\xab\x60\x50\xe3\x39\x9e\x25\x57\x77\x04\xcf\xc6\x61\x63\xf6\xdf\x8d\x74\x91\x42\xa7\xe9\x74\xe4\x9b\x73\x15\xab\x7c\x8b\x85\xad\x5d\x5c\xb2\x71\xcf\x20\x7e\xb7\x2e\x1c\x34\x76\xb0\xd8\x63\x72\x1c\x96\x7b\xe1\x5e\xcb\xfb\xf0\x6e\xad\xc2\x7d\xe3\x38\xea\xa3\xca\xc1\xdd\xe6\x42\xd5\x2a\xa5\x35\x91\x98\xd8\x90\x9d\x23\xd8\x7d\x82\x70\x90\xa8\xad\xa7\xb7\xa5\x55\x36\x42\xd5\x86\x60\x3e\xa2\x46\x4d\xab\xd2\xef\x5e\x18\xdb\x3a\x62\x3b\xe6\x5b\xe7\xb5\xa4\x69\x89\x0f\x9d\xde\x54\xa2\x7c\xa7\x23\xb4\xe0\x5d\x56\xb7\x18\x1b\x28\xd5\xc1\xf6\x54\x15\x68\x8e\xe4\x1d\x53\x37\xa9\x95\x2d\x92\xed\xe4\xd1\x92\xb9\x09\x16\x39\xca\xaa\x60\x33\xe4\x74\x94\x18\xdd\xe1\x5a\xbe\x4b\xad\x62\xc3\x7f\xab\x05\xe3\xbe\xf4\xcd\x73\x98\xa4\x97\x7e\x07\xe1\x21\xfe\xf2\xaa\xc5\x6b\xe7\xe0\x54\x6e\x40\xfc\xa8\x85\x69\x6a\x38\x50\xc9\xa2\x87\x09\xe6\x99\xd5\x26\x11\xc9\xb7\x92\x6e\x7a\xd1\x81\x49\x04\x05\x82\xc9\x97\xdb\x71", b"\x51\x5b\x9c\xe5\x3e\xb1\x0c\x3e\x47\x89\x05\x56\xe0\xf0\xfd\x19\xad\xb2\x07\xb9\xc0\x1f\x12\xef\x5c\x6c\xaa\xad", b"\x09\x00\xe3\xac\xc4\xc3\x78\xbd\xfe\x9c\xda\x4d\xb8\xf8\xab\x54\x43\x69\x31\xc7\x3d\x8d\x31\x71\xc6\xdc\x8b\xb8", )?; test( HashAlgorithm::SHA512, b"\x4a\x14\x5d\xd5\xcc\x4a\x12\xea\x43\x61\x7e\xc9\x79\x0f\x10\x38\x19\x0e\xd3\xd8\xaf\x24\xbb\xec\x14\xda\x3e\xcf\x5f\x38\x7c\xa9\x76\x4a\x8b\x9c\xbc\x5f\x62\x92\xa5\x3a\x9d\xa9\x53\x3c\x75\x11\x40\xf8\xda\x5f\xb6\xf3\xd4\x8e\xba\x1e\x7b\x98\x66\x27\x34\xd9\xa8\xb1\x20\xdd\x51\x54\x08\xba\x75\x6f\x75\xa5\x75\x52\x12\x76\x4a\xd9\x2c\x3f\x22\x63\x83\x52\x11\xad\xd5\xb4\xcc\x0e\xca\x8d\x4f\xc7\xa8\x43\xf4\x9c\x38\xce\x80\x86\x8f\xaf\x8b\x49\x8f\xb4\x14\xd3\x08\x0e\xd4\x1e\x36\x74\xe2\x85\xd3\xe4\x0d\x62\xf3\x05", p, q, g, b"\xb3\xf6\xd4\x4d\xa8\x6a\x51\x5d\x71\x85\xb7\x0c\x5a\xda\xa3\xf6\x05\x9c\x0b\xb7\x99\x5a\x53\x91\x07\x61\xfe\xa3\x62\xd9\x84\x3f\x92\xf2\x27\x1d\xdb\x0b\xca\x0d\x45\x19\xe3\x3f\xdb\x13\xaf\x49\xd8\x55\xcd\x0b\x9a\xb0\xb9\x70\x26\x72\x43\xe4\x68\xd3\xc4\x16\x77\xac\x58\x8f\xdf\xcb\x1c\xb9\xaa\x4d\x23\x3f\x7a\xe0\x17\xe6\x70\x94\xf4\xf4\xd9\x04\xe1\x57\x5e\x76\xbd\xc6\xbd\x82\x99\xb4\x2a\x2f\x39\xad\xef\x63\xce\x04\x78\x62\xaa\xa0\xbb\x8b\xa3\x2e\xc2\x73\x34\x93\x64\x84\x06\xf5\x4f\x5d\x8e\x2e\xb1\x9e\xea\x83\x7f\x4d\x59\x63\xad\x31\x92\x91\x7f\x5f\xe3\xb6\xd0\x27\xb2\x2b\xc1\xbf\x0d\xce\x84\x01\xd6\x22\xca\x72\xb1\xd7\x3a\x89\xe8\x88\xde\x1e\x62\xbe\xad\x2e\x4e\x1d\xa6\xb5\xd0\x4b\x2a\x36\x94\xc7\x6f\xe0\x7a\xd3\xc6\x64\x26\x34\x3d\x67\xbe\x12\xb2\xa7\x2c\x3f\x76\x22\x55\x73\xfc\x05\x4f\x3b\x7d\x73\x59\x15\x23\x8d\x7b\xdb\xcb\x03\xba\x6d\xde\x3e\xdc\x00\xf8\xc9\x83\xb0\xb5\x01\x29\xfa\xb4\x26\x00\x4a\x27\xa0\x38\x13\x9f\x2d\x32\x95\xb5\xb0\x32\x70\x1f\xac\xe3\x4a\x75\x23\x55\x94\x85\xfa\x63\x1c\x21\x92\x37\xf6", b"\x5c\xfd\x4b\x9f\x92\xca\x72\x7d\x51\x3a\xc1\x41\x43\xb1\x25\x14\x86\x55\xf1\x64\x2c\x53\xb7\x3c\xc2\x51\x31\xc9", b"\x2a\xde\xf9\x4a\xae\x37\x2d\x57\x9c\x99\x62\x9c\xa0\x78\x63\x62\xcb\x02\x47\xaa\x6d\x99\x95\x70\x74\xcd\x7d\x43", )?; test( HashAlgorithm::SHA512, b"\x42\x8a\x20\x79\x0c\xad\x1c\x7b\xa8\x21\x18\xae\x58\x41\xbd\x53\x80\xee\x50\xbe\x5b\x64\xb8\x04\x09\x35\xef\x3d\x6d\xa3\x7a\x26\xe6\xf0\x20\x35\xfb\x19\x37\xc7\xa6\xbc\xd8\x8c\x89\x4f\xad\x7d\x8a\xa4\x8a\xbb\x89\xe0\xc6\x42\x87\xcd\xc6\x37\x45\x4d\xb8\x9e\xaf\x0a\x7e\x69\x27\x34\xc8\xa2\x43\x85\x6d\xd7\x56\x90\xbd\xce\xfe\x55\x4e\x39\xa0\xdf\x84\xe6\xe0\xc9\x6b\x2c\x57\x74\xa3\xe4\xe2\xaf\xed\x02\x8f\xb4\x3d\x79\x98\xd3\xcd\xc9\xa6\x40\x93\x22\xcf\x3b\xfa\x4d\x1e\x36\xf5\xe7\x07\x20\x3b\x59\xc4\x9a\x75\x3e", p, q, g, b"\x47\xa9\x34\x0a\xc5\x13\x58\x5c\x83\xbb\x20\xa2\xfb\xa9\x46\x97\x18\x11\x18\x4f\xd2\x00\x65\xfb\x95\xcb\xb2\x06\x25\xb4\x7b\x21\x6f\x75\xe1\xf3\xd8\x97\x97\xf5\x40\xa0\x48\x5c\xfb\xf0\x7b\x17\x16\xa3\xec\xe7\x02\x7d\x86\xf4\x94\x0a\xb9\x0b\xbf\xdd\x8e\xbf\x15\x13\x7b\xcf\x88\x05\xf9\x3c\xea\x25\x9c\x4b\xea\x5a\x2d\x3b\xb3\xdd\xdf\x83\xaa\x29\x0d\x35\x73\xe9\x1a\xa3\x00\xbb\xf1\xaf\xb9\xb5\x25\x54\x2d\x67\xa8\xd8\x60\x51\xae\xd8\xff\x8a\x2c\xfc\x22\x5a\x9e\x51\xeb\x37\x4c\x31\xfe\x10\x3a\xe8\xf4\xa0\xc8\x91\x14\x21\xd2\x25\xc0\x19\xe1\xb5\xc0\x7d\xc1\x49\xba\xbc\x26\xb7\x08\xfc\x0f\xc0\xc1\x3c\x3b\x35\x39\x03\x17\xc4\x09\xfa\xae\x81\xaa\xc9\xab\x5d\x01\xce\x85\xad\xd2\x49\x17\xd9\x4c\xd1\xb2\x14\x1b\x63\x8d\xe3\xa2\x53\xbf\xca\x6b\x7f\x1a\x81\x04\x51\x8d\x15\x72\x21\x1b\xa5\x2d\xd1\x75\x63\x2c\x8f\x3f\x67\x48\x26\x5a\x4b\xf6\xc2\xb8\x36\x3d\x98\x10\xba\x1f\x1e\x58\x47\x94\xf6\x23\x19\xf0\x45\x1d\xa8\x31\xd4\x57\xb5\x26\x9b\xbe\x67\x78\x4c\x47\x4f\xff\xf6\x92\xbb\xe2\xba\xac\xa3\x2d\x3f\x85\xf4\xfe\x39\xe0\x3f", b"\x8a\x96\xc4\x19\xe0\xf3\x91\xda\xa2\x9f\xb1\x62\xa1\xb9\x57\x0f\x48\xa0\x08\x10\xaa\x48\x0c\xde\x0f\x27\xcf\xb0", b"\x02\x8e\xd9\x16\x55\x22\xfc\x59\xae\xeb\x79\xc4\x91\xa9\x5e\xd8\x42\x7f\xd1\xb6\x95\xf3\xde\xdf\x42\x28\xa3\x28", )?; test( HashAlgorithm::SHA512, b"\x2a\x07\xe2\x8f\xc1\x02\xdf\xe1\x7c\x79\xb9\x36\x8e\x0b\xa9\x24\x14\xd2\xfc\xb4\x07\xd3\x4e\x90\x3a\x0a\x53\x37\x0f\x7d\x2d\x33\xaa\x13\xc0\x2e\x52\x75\x87\x71\x8c\x3b\x39\x66\x61\x25\xec\xa2\xe8\xfd\x4c\x94\xb9\x86\x7f\xb6\xef\x16\xd5\x55\x54\x9d\x8d\xd0\xf6\xe1\x04\x17\xeb\xec\xf4\x8f\x99\x2a\xd8\x4b\x5d\x97\x74\x54\x07\x85\xdd\xcd\x26\x4c\x55\x79\x6b\xc2\x16\x28\x98\xec\xef\x40\x27\xc3\x41\x87\xf8\xc0\xb1\xc2\x0d\x4d\xaa\x10\x8b\x70\xd7\x6c\x40\xdd\xbe\xbc\x1e\x0f\x50\xf4\xdc\x90\x4d\xbf\xbe\x6b\xeb\x9d", p, q, g, b"\x05\xf2\x7e\xc0\x35\x62\x78\x60\xc3\x1a\xa5\x97\xc9\x68\x37\x08\x46\x05\xf2\x70\xd1\x5a\x3f\xbb\xdd\xa1\xc3\x85\x3d\xb2\xea\x6f\x6c\x9d\xe4\xe1\x1a\x6f\xbd\x77\x3c\x30\x0e\xba\xd0\xf9\xdb\xc3\x36\x08\xf9\xc4\xc5\xce\xde\xde\x0c\x26\x79\x1c\xbe\xa3\x5a\xf0\x32\x2a\x60\x77\x39\xe9\x7c\x32\x42\xf0\xae\x7d\x36\xaf\xe2\x69\xaa\xe6\x4b\x5f\xb2\xdb\x26\x5c\xd7\x56\xce\xd4\x5d\x88\x8e\xaa\xb0\x46\x5e\x50\x9a\xb7\xf8\x3d\x62\x3f\x69\xe7\x3c\xdc\x0c\x76\x70\x67\x5c\xe0\xc2\x9f\x49\xa1\x9d\x70\x38\x62\x3b\xde\x36\xe2\x9f\xb8\x54\xe6\xfe\x6f\xfd\xb9\x16\xab\xb7\xd6\x1f\xab\x4b\x62\x0d\xc7\x39\xa5\xcb\xd9\x60\x8a\x45\xe8\x6c\x2b\xbf\xb4\x1b\x86\x99\x16\x68\x22\xe8\x32\xbb\x6c\xac\x66\xe0\x04\xe9\x3d\x19\x0b\x95\x14\x24\xed\xaf\x34\xbf\x6b\xd3\x43\xbf\x60\x15\x4f\x73\x9c\x43\x56\x2b\x03\xae\xb4\xd2\x3d\xe1\xf7\x6c\x18\xf7\x4b\x5f\x7a\x73\xc8\x05\xb2\x2a\xf8\xcc\x6b\xdc\x9b\x55\x77\x9c\xcf\x6d\x44\x1c\xfd\x31\x54\x61\x6c\xda\x18\x80\x7a\x9f\x5e\x2d\x76\x59\xe9\xe2\x13\x29\x75\x51\x57\xda\xbc\x62\x2b\xd1\xae\x2d\x50\x97\xc6", b"\x91\xa4\x57\x69\x31\xed\x62\x1a\x03\x42\xf1\x4e\xe2\xba\x8f\xa8\xe1\xbb\xdf\x89\x4c\x12\x51\xaf\xdf\x72\x14\x6f", b"\x56\x75\x5c\xa1\x63\xf7\xdc\x89\x45\x8a\x7a\x75\xd4\xdd\x3c\xe3\xad\xec\x42\xb4\xaa\x7d\x04\xb2\x85\x8c\x47\xf6", )?; test( HashAlgorithm::SHA512, b"\x7e\x96\x38\x58\x16\xc9\x7b\xd9\xde\x81\xde\x30\xe6\x7d\xb7\x24\x36\xfb\x42\xfa\xa9\xb6\xcc\xfe\xab\x1f\xa5\x28\xc6\x9e\x63\x51\xb2\x01\x2a\x10\x97\xfb\x86\xd8\xc5\xcc\x60\x25\x6e\xf1\x1b\xe1\x8f\x16\x13\x76\x17\xf8\xcd\xd2\x9e\x3b\xab\x94\x68\xc1\x2a\xe3\x43\x36\xba\x0e\x0e\xb6\xc8\x28\x17\x7d\x1d\x55\xb0\x66\x98\xdd\xf7\x53\x75\x6a\xf8\x30\xa1\x0c\xe9\xc9\x9f\x1d\x13\x68\x26\x68\xe3\xeb\x33\x6a\x80\x61\x8e\x66\x62\x80\x09\x64\x17\xc1\xe2\xb0\x05\xb9\x35\x1f\x5e\xa3\x06\xb8\xc6\x3f\xd1\x84\xa5\x91\x32\xb5", p, q, g, b"\x2b\x69\xbf\x21\xbf\x68\x9a\x1f\x5e\xd7\x09\x6b\x27\xe4\x47\xc1\xd5\x2f\xc2\x47\x3e\x9e\x43\x53\xdb\xf1\x85\x63\x20\x22\xfc\x60\x5c\xef\xe5\x48\x91\x02\xf7\xcb\xe9\x84\xf0\x0c\x1a\xb3\x2f\x2d\xef\x1a\x84\xf1\xbe\xdd\xdb\xc1\x5f\x87\xae\xd0\xa2\xb1\xe9\x12\xe9\xed\xd7\x4e\xdb\xe2\xc1\x5a\x4c\x37\x53\x30\x14\xb9\xd3\x2b\x05\xf5\xa4\x4d\x32\x3d\xef\x1c\xeb\xae\x0e\x21\x6b\xc3\x5a\x1c\xa8\xa4\x26\x5c\x3d\xb5\x57\x4e\xb2\x3e\x17\xf1\x83\x8e\x22\x5e\x46\x7a\x94\x26\xe8\x79\x8c\x5a\x2e\x89\x65\x36\xc4\x8c\x4e\x24\xcd\x2e\xe9\xda\x1b\x61\xae\xd2\xe2\x5b\x98\xe4\xc1\xf4\xee\x55\xe0\xb4\x70\x5f\xeb\x2b\xb1\x69\x4c\xb1\x8a\x64\x14\xbc\xdc\x1a\x74\x89\xb4\xbf\x89\x67\x98\x54\x89\x31\x6b\x3e\x57\xea\x28\x12\x04\xce\xd3\xed\x88\xad\x1b\x20\x7b\xe7\xd2\x94\x12\x7b\xca\x86\xa9\xb8\x61\xcc\xca\x19\x2c\x15\xc8\x15\xe2\x32\x8c\xbd\xaa\x58\x99\xc9\xdd\x27\x1f\xcd\x6e\xea\x0d\x2a\xb0\x09\xa8\xba\x00\x1e\x67\x25\x13\x9b\xe2\x6c\x51\x51\x87\x5c\xdc\xa7\xf9\x14\x34\x44\x3b\x9e\x5e\x47\xa4\x5c\xdc\x8b\x73\x99\xbc\x5e\x8b\xed\x93\x00", b"\x1b\xca\xa2\xca\xf4\x83\xab\xc8\x0b\x75\xf6\x70\x25\x2f\xaa\x2a\x8e\x18\xc3\x23\x01\xba\x6f\xc0\x6f\x37\xc0\x8e", b"\x90\x9a\x78\x52\xb8\xd5\xc8\x81\x3e\x17\xc0\x40\x77\x9a\xd0\xdc\x5e\x9e\x05\x56\x61\x20\x56\x83\x5e\x68\xd2\xb8", )?; test( HashAlgorithm::SHA512, b"\x24\xed\x7a\x16\x78\x2b\x5c\x34\xbe\xb5\x8b\xab\x6a\x7d\x20\x28\x71\x9f\x97\x38\xe5\xd1\xba\x69\x78\xef\xac\x4b\x53\xb3\x7c\x88\xe7\xea\x02\xe0\xcf\x0f\xd8\x2a\x3e\x50\x04\x60\x52\xa9\x04\x95\x41\xd1\x29\x93\x25\x4a\x46\xfe\x40\x1f\x40\x2d\x38\x94\x3e\x94\x91\x8b\xf7\xa6\xfe\xcb\x08\xed\x13\x09\xb7\xb0\xf2\x18\x59\x67\xef\x28\x9a\x2e\xfa\x6c\x2e\x37\xa7\x4d\x65\x92\xa2\xeb\x74\x01\xca\x5e\x98\xbb\x86\x45\xa9\x4e\x57\x49\x9d\x36\x2e\x0f\x31\x33\xef\x33\x6e\x11\x95\x61\xce\xe1\xb5\x58\xc1\x55\x08\x78\x18\x68", p, q, g, b"\x9d\xcc\xc1\x37\x19\x7b\xb2\x98\x24\xb1\xc1\x0e\x9e\x8d\xed\xd7\x14\xef\xc9\x36\xcf\xf8\x3f\x42\x63\x4d\x64\x39\x1f\x9b\x7f\x4f\xc3\xa2\x31\x95\x4a\x8c\x3b\xfe\x4a\xe0\xf8\x22\x25\xfd\x52\xb5\xdd\xe6\xdc\xd1\x4c\x0c\xe5\x08\x59\x71\xc5\x15\xda\x38\x18\x34\x27\xc7\xe2\xa8\xd7\x6e\x40\xef\xb6\x71\xaf\x79\x7e\x0c\x57\x6e\x38\x81\xd4\x34\xca\x80\x9d\xd5\x53\xcc\xb0\xf7\xcd\x9f\x73\xc7\xae\xa2\x26\x8f\x36\xc8\x41\x70\xab\x0a\xe0\x3b\x2b\x46\xa2\x19\x54\x75\x64\xfd\x21\xc5\x40\xb1\x60\x3a\xd7\x30\x6d\x22\xa9\xeb\x8e\xf3\x7c\xa0\x8c\x2b\x28\xd1\x6c\x5b\x9c\x54\xa3\x28\xeb\xb3\xc0\xf9\x50\x50\x95\xc6\x12\x27\x0d\x52\x63\x7c\xb5\x58\x4e\xd0\x8b\xad\x71\x38\xd3\x38\x8c\x63\x4b\x65\x02\xfa\x64\x73\xa2\xf5\x94\x04\x0b\x9a\xcc\x14\x80\xb3\x43\xd2\x28\x7f\xdc\x70\xd1\x6b\xa1\x4b\x1c\x21\x17\x61\x2d\xcc\x58\x60\xdb\xef\x83\x87\xaf\x9a\xa5\xe1\x62\x1d\x37\xa3\x8f\x6c\xbe\x59\x35\x67\x3e\xa3\xcb\xcd\xe4\xf3\x2a\x24\x9e\xb6\xa5\xee\xd4\x1c\xfd\xca\xa4\xc8\x7e\x8b\xca\xba\xa6\xbd\x1f\xe5\xa8\x79\xd1\x7e\x9a\xe3\x58\x37\xce\x0f", b"\x5f\x4f\x54\x49\xb8\xd0\xdd\xa3\xac\x59\x0b\xa1\x64\x0d\xf9\x77\x2f\xf0\x8c\xec\x08\x52\x8b\xc2\xd7\x0d\x7a\xc9", b"\x5b\xea\x04\xbf\xd3\x32\x48\xf2\x6a\xee\x98\xca\x85\x96\x77\x4e\x95\xce\x68\x54\x65\x17\x4d\x1c\xae\xd7\xd9\x20", )?; test( HashAlgorithm::SHA512, b"\x49\x06\xdb\xdd\x9d\xa6\xdd\xff\xa1\x52\xfa\x2e\x25\x0e\xea\xd3\xc6\xef\x70\x83\x87\xa3\xad\x64\xd3\x4a\x0e\x05\x74\x59\x47\x1f\x48\x75\x2f\xde\x07\x86\xdb\x28\xa4\xbb\xf5\x81\x14\xd8\xdc\x91\xb6\x9e\x56\xbe\x3c\x49\xec\x1b\x98\x80\xd9\x91\x7c\x73\xab\xc8\x95\x75\x4a\x60\x77\x9b\x18\xbc\x95\x15\x50\xb9\x57\xa7\x7c\x8c\xef\xa1\x59\x90\x81\x26\xcc\x80\x1c\x66\x5d\x1b\x01\x10\x9b\xa6\x04\xbb\x9e\x79\x7c\x7a\x37\x66\x0b\xfc\x05\x93\xba\xb0\x92\x4d\xf5\x80\x6c\xa8\x03\x38\x1b\x24\xb0\x3d\xe3\xd0\x3b\x48\x4d\x49", p, q, g, b"\x07\x2c\xb5\x61\x25\x96\xaa\x71\x61\x42\xf5\xf7\x56\xc9\x54\x20\x13\xf3\xf1\x62\x8c\xfc\x54\x97\xeb\x1b\xa0\xaa\x51\xbd\x5a\xdb\x8e\xb8\xad\xfe\x05\x9c\x0e\x08\x82\xe3\xc0\x9a\x17\xd1\xf5\x1a\xcc\xb6\x87\xb2\x43\xfd\x30\x52\xbb\xcb\x81\xb0\x63\xc1\xe7\xd5\xbe\x06\x65\x87\xeb\xca\x07\x80\x06\xf6\xd6\xee\x71\xa6\x9e\xf5\x9b\x63\x65\xcb\xcf\x64\xd4\xcf\x1b\x92\x99\xe7\x40\x30\x09\x27\x20\x26\xfc\x16\x65\xed\x40\x3a\xb8\xde\xe4\x0e\xea\x4e\xe7\xd5\x62\xaf\x00\x19\x51\x92\x6d\xc8\xbf\x0c\x78\x39\x84\x66\x4f\xfe\xf6\x29\xcb\x59\xd7\x09\xb3\xd9\xaa\x06\x80\x5d\x62\xaf\xd7\x94\x54\x1a\x2b\x4c\xe0\xc5\x90\x43\xac\xf7\x3e\x18\xe7\x44\x53\xe8\x6a\x08\x2f\x17\x91\x4b\xa6\xb2\xb0\xfa\x80\xda\x83\x53\xc7\xed\x91\x62\x60\x95\x75\xed\x41\xf8\xeb\x78\xdb\xaf\xaa\x7b\x51\x8d\xe0\xc8\x5b\x17\x20\xe7\xf4\x93\xb9\x14\xd5\xa3\xd2\xd0\x74\x82\x73\xd1\x69\xd5\x5c\x45\x55\x6b\xca\xe6\x70\x57\x5c\x96\xa4\x44\xfc\x1d\x78\x9f\x5b\xac\xfc\x8b\x24\x13\x2b\xfb\xd7\x5b\x30\x61\xfb\xac\xf2\x93\x5a\x21\x9b\x0f\x2a\xc5\xdc\xad\x71\x85\x16\xa9", b"\x77\x47\x59\x00\xfc\x7f\x3e\x0b\x80\xf3\x88\x4a\xf8\x60\x4e\xef\x60\xff\xe4\x84\xbc\x6c\xd3\xde\x12\x3f\x79\x59", b"\x26\xca\x92\x7d\xa0\xd1\x0b\x43\xdc\x15\x21\xbf\xeb\x58\xff\x34\x7e\xe1\x43\xfc\x38\xdb\x45\x1c\x11\xa0\x35\x10", )?; // [mod = L=2048, N=256, SHA-1] let p = b"\xc1\xa5\x9d\x21\x55\x73\x94\x9e\x0b\x20\xa9\x74\xc2\xed\xf2\xe3\x13\x7f\xf2\x46\x30\x62\xf7\x5f\x1d\x13\xdf\x12\xab\xa1\x07\x6b\xb2\xd0\x13\x40\x2b\x60\xaf\x6c\x18\x7f\xb0\xfa\x36\x21\x67\xc9\x76\xc2\x61\x7c\x72\x6f\x90\x77\xf0\x9e\x18\xc1\x1b\x60\xf6\x50\x08\x82\x5b\xd6\xc0\x2a\x1f\x57\xd3\xeb\x0a\xd4\x1c\xd5\x47\xde\x43\xd8\x7f\x25\x25\xf9\x71\xd4\x2b\x30\x65\x06\xe7\xca\x03\xbe\x63\xb3\x5f\x4a\xda\x17\x2d\x0a\x06\x92\x44\x40\xa1\x42\x50\xd7\x82\x2a\xc2\xd5\xae\xaf\xed\x46\x19\xe7\x9d\x41\x58\xa7\xd5\xeb\x2d\x9f\x02\x3d\xb1\x81\xa8\xf0\x94\xb2\xc6\xcb\x87\xcb\x85\x35\x41\x6a\xc1\x98\x13\xf0\x71\x44\x66\x0c\x55\x77\x45\xf4\x4a\x01\xc6\xb1\x02\x90\x92\xc1\x29\xb0\xd2\x71\x83\xe8\x2c\x5a\x21\xa8\x01\x77\xee\x74\x76\xeb\x95\xc4\x66\xfb\x47\x2b\xd3\xd2\xdc\x28\x6c\xe2\x58\x47\xe9\x3c\xbf\xa9\xad\x39\xcc\x57\x03\x5d\x0c\x7b\x64\xb9\x26\xa9\xc7\xf5\xa7\xb2\xbc\x5a\xbc\xbf\xbd\xc0\xb0\xe3\xfe\xde\x3c\x1e\x02\xc4\x4a\xfc\x8a\xef\xc7\x95\x7d\xa0\x7a\x0e\x5f\xd1\x23\x39\xdb\x86\x67\x61\x6f\x62\x28\x6d\xf8\x0d\x58\xab"; let q = b"\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x1b\xd6\x2c\x65\xe8\xb8\x7c\x89\x79\x7f\x8f\x0c\xbf\xa5\x5e\x4a\x68\x10\xe2\xc7"; let g = b"\xae\xa5\x87\x87\x40\xf1\x42\x4d\x3c\x6e\xa9\xc6\xb4\x79\x96\x15\xd2\x74\x92\x98\xa1\x7e\x26\x20\x7f\x76\xce\xf3\x40\xdd\xd3\x90\xe1\xb1\xad\x6b\x6c\x00\x10\xad\x01\x5a\x10\x33\x42\xdd\xd4\x52\xca\xc0\x24\xb3\x6e\x42\xd9\xb8\xed\x52\xfa\xfa\xe7\xa1\xd3\xce\x9e\x4b\x21\xf9\x10\xd1\x35\x6e\xb1\x63\xa3\xe5\xa8\x18\x4c\x78\x1b\xf1\x44\x92\xaf\xa2\xe4\xb0\xa5\x6d\x88\x84\xfd\x01\xa6\x28\xb9\x66\x27\x39\xc4\x2e\x5c\x57\x95\xad\xe2\xf5\xf2\x7e\x6d\xe1\xd9\x63\x91\x7c\xe8\x80\x6f\xc4\x0d\x02\x1c\xd8\x7a\xa3\xaa\x3a\x9e\x4f\x0c\x2c\x4c\x45\xd2\x95\x9b\x25\x78\xb2\xfb\x1a\x22\x29\xc3\x7e\x18\x10\x59\xb9\xd5\xe7\xb7\x86\x2f\xa8\x2e\x23\x77\xa4\x9e\xd0\xf9\xdc\xa8\x20\xa5\x81\x40\x79\xdd\x66\x10\x71\x4e\xfa\xf8\xb0\xcc\x68\x3d\x8e\x72\xe4\xc8\x84\xe6\xf9\xd4\x94\x6b\x3e\x8d\x4c\xbb\x92\xad\xbb\xe7\xd4\xc4\x7c\xc3\x0b\xe7\xf8\xc3\x7c\xa8\x18\x83\xa1\xaa\xc6\x86\x00\x59\xff\x46\x40\xa2\x9c\xca\xe7\x3d\xe2\x0b\x12\xe6\x3b\x00\xa8\x8b\x2e\xe9\xba\x94\xb7\x5e\xb4\x0a\x65\x6e\x15\xd9\xec\x83\x73\x1c\x85\xd0\xef\xfc\xb9\xef\x9f"; test( HashAlgorithm::SHA1, b"\xde\x36\x05\xdb\xef\xde\x35\x3c\xbe\x05\xe0\xd6\x09\x86\x47\xb6\xd0\x41\x46\x0d\xfd\x4c\x00\x03\x12\xbe\x1a\xfe\x75\x51\xfd\x3b\x93\xfe\xd7\x6a\x97\x63\xc3\x4e\x00\x45\x64\xb8\xf7\xdc\xac\xbd\x99\xe8\x50\x30\x63\x2c\x94\xe9\xb0\xa0\x32\x04\x65\x23\xb7\xaa\xcd\xf9\x34\xa2\xdb\xbd\xcf\xce\xef\xe6\x6b\x4e\x3d\x1c\xb2\x9e\x99\x4f\xf3\xa4\x64\x8a\x8e\xdd\x9d\x58\xed\x71\xf1\x23\x99\xd9\x06\x24\x78\x9c\x4e\x0e\xeb\xb0\xfb\xd5\x08\x0f\x7d\x73\x0f\x87\x5a\x1f\x29\x07\x49\x33\x4c\xb4\x05\xe9\xfd\x2a\xe1\xb4\xed\x65", p, q, g, b"\x88\x0e\x17\xc4\xae\x81\x41\x75\x06\x09\xd8\x25\x1c\x0b\xbd\x7a\xcf\x6d\x0b\x46\x0e\xd3\x68\x8e\x9a\x5f\x99\x0e\x6c\x4b\x5b\x00\x87\x5d\xa7\x50\xe0\x22\x8a\x04\x10\x2a\x35\xf5\x7e\x74\xb8\xd2\xf9\xb6\x95\x0f\x0d\x1d\xb8\xd3\x02\xc5\xc9\x0a\x5b\x87\x86\xa8\x2c\x68\xff\x5b\x17\xa5\x7a\x75\x84\x96\xc5\xf8\x05\x3e\x44\x84\xa2\x53\xd9\x94\x22\x04\xd9\xa1\x10\x9f\x4b\xd2\xa3\xec\x31\x1a\x60\xcf\x69\xc6\x85\xb5\x86\xd9\x86\xf5\x65\xd3\x3d\xbf\x5a\xab\x70\x91\xe3\x1a\xa4\x10\x2c\x4f\x4b\x53\xfb\xf8\x72\xd7\x00\x15\x64\x65\xb6\xc0\x75\xe7\xf7\x78\x47\x1a\x23\x50\x2d\xc0\xfe\xe4\x1b\x27\x1c\x83\x7a\x1c\x26\x69\x16\x99\xf3\x55\x0d\x06\x0a\x33\x10\x99\xf6\x48\x37\xcd\xde\xc6\x9c\xae\xbf\x51\xbf\x4e\xc9\xf3\x6f\x2a\x22\x0f\xe7\x73\xcb\x4d\x3c\x02\xd0\x44\x6d\xdd\x46\x13\x35\x32\xef\x1c\x3c\x69\xd4\x32\xe3\x03\x50\x2b\xd0\x5a\x75\x27\x9a\x78\x09\xa7\x42\xac\x4a\x78\x72\xb0\x7f\x19\x08\x65\x40\x49\x41\x93\x50\xe3\x7a\x95\xf2\xef\x33\x36\x1d\x8d\x87\x36\xd4\x08\x3d\xc1\x4c\x0b\xb9\x72\xe1\x4d\x4c\x7b\x97\xf3\xdd\xfc\xca\xef", b"\x36\x3e\x01\xc5\x64\xf3\x80\xa2\x7d\x7d\x23\xb2\x07\xaf\x3f\x96\x1d\x48\xfc\x09\x95\x48\x7f\x60\x05\x27\x75\xd7\x24\xab\x3d\x10", b"\x49\x16\xd9\x1b\x29\x27\x29\x4e\x42\x9d\x53\x7c\x06\xdd\x24\x63\xd1\x84\x50\x18\xcc\xa2\x87\x3e\x90\xa6\xc8\x37\xb4\x45\xfd\xde", )?; test( HashAlgorithm::SHA1, b"\x49\x70\x7b\x65\x5b\x6d\x16\x8c\x70\xba\xed\xe0\x38\x66\xb0\xfb\xa6\x02\x39\xad\x4c\xf8\x2f\x53\xb4\x6e\x11\xb2\x6f\xa8\xf6\x27\x6f\xf6\x68\x7d\x09\xe8\xed\x1e\x5d\x96\x3c\x11\xe4\x76\x3b\x2e\x59\xa0\x92\x7f\x01\xe8\xff\xfd\x18\x94\xa6\x26\x23\x27\xc8\x4b\xbb\x42\x98\xd7\xd7\xfb\xca\x66\x06\x73\x12\x8b\xb7\xde\xa4\x61\x78\x14\x64\x85\x53\x9f\x9a\x8f\x88\xda\xc7\x61\xd0\xd5\xd4\x5c\xb5\x57\xcd\xac\x96\x0b\xe2\x3d\xd9\x19\x9a\xcd\x99\xcb\x64\xd1\xfe\xe2\xca\x68\xe4\x23\x46\x1a\x02\xab\xb3\x4c\x1d\xc4\x50\x11", p, q, g, b"\x38\x53\x49\xec\xf9\x9c\xe7\x83\xd4\xe7\xa8\x0a\x7d\xd2\xc5\x33\xa3\x62\x3c\x38\x26\x02\x43\xac\x39\x2d\x4e\xab\x6d\xed\xa5\xb7\x9b\x8f\x91\x67\x92\x2e\x8b\x60\x46\x86\x23\xe4\x60\x3f\xa7\x68\x1f\x53\x5e\x20\xde\x67\x35\x31\x25\x5e\x10\x8f\x54\x2a\x26\xd5\xc8\x7f\x19\xe0\x63\x37\x2d\x14\x28\x69\xc5\xee\xf1\x32\x52\x81\xfe\xe7\xf1\xc7\x4d\x2a\x96\x25\x5d\x42\x0f\x27\x13\x86\x4d\x55\xd3\x6f\x81\x39\x19\x4f\x64\x3a\x6e\x98\xb5\xbf\x97\x32\xc8\x59\x74\x45\xaf\x5a\x71\xe2\x3e\x2a\xc5\xca\xe3\x60\x43\x23\xf7\xbf\x09\x44\x97\x86\x97\x4e\xd5\x3a\x57\x17\xf9\xae\xc1\x4d\xd0\x1b\xd1\xcf\x27\x6b\xf3\xc6\x3d\xec\x43\xc3\xec\x8e\xa6\x55\x7d\xe4\x69\x91\x64\x12\xf0\x45\x6c\x90\xf0\x12\x91\xbb\x71\x25\xe9\xf8\x55\xf4\x55\xb3\x60\xc0\x3d\x4a\x7b\x4a\x8d\x40\x90\xe4\x7a\xaf\x11\x11\xf3\x82\xdd\x26\x05\x73\x4f\xb5\x4f\x4b\x8f\xfe\x23\xc9\xde\xd2\x90\x0b\x31\x21\xb4\x97\xbd\x46\xd0\x45\x8a\x09\xa5\xdf\x4a\xa9\xcf\x1b\xe9\x06\xf5\x54\x23\x13\x38\x4f\x93\xd3\x77\xba\x9e\x0a\x76\x2b\x47\x93\x40\x3b\x91\x4e\x52\x86\x5a\xfa\xbb\x67", b"\x0f\xdc\x5a\x5a\x4a\x2c\x2f\x3d\xf5\x0c\x86\x83\x83\xba\x80\x03\x96\xae\x25\x26\x5b\xe1\xa1\x47\x62\xd3\x11\x0c\xbe\xb3\x48\x19", b"\x4b\x41\x84\x1c\xad\x45\xfe\xde\xa5\xaa\xd0\xa1\x6b\x05\x3e\x88\x35\x3b\x6f\x01\x02\xdf\x74\xc9\xfc\xe0\x9e\x38\xf5\xe6\xc2\x77", )?; test( HashAlgorithm::SHA1, b"\x76\x3c\x1f\x15\xc5\xdd\x8a\x93\xaa\xc4\xe0\x48\x65\x1c\x4e\xa8\x4a\xf1\x8a\xee\x25\x5b\x56\x95\x9e\xae\xb1\x87\x66\x99\xbe\x75\x27\x1a\xf0\xda\x6c\x3c\xa9\x36\xe9\x9b\xe4\xff\x44\x36\x41\x0f\x69\xae\x70\x18\xb6\xc8\x43\xdc\xe9\xd8\xb7\x1a\x91\xef\xa5\x3c\x39\xbe\x55\xf2\x85\xfb\x8a\xd8\x54\x39\x52\xfd\x3c\xa8\x92\x71\xec\x23\xd3\x42\xcf\xd5\x57\xbf\xb7\x2d\xb4\x3b\x43\x4d\x0e\xd5\xb3\x07\x63\x03\x77\x54\xbb\x0f\x78\x2a\xb0\x82\x35\xa6\x4a\xbb\x7f\x0a\x82\x8f\x89\x2c\xde\x7e\x05\xe3\x01\xda\x7c\x21\xc0\x96", p, q, g, b"\x0b\xec\xd9\x17\xee\xd0\xbe\x9c\xb5\x8f\xf9\xd2\x59\xa8\xfa\x41\x5b\x81\x6d\xa4\xa2\x5d\x3f\x56\x9d\x7b\x9f\x31\x7b\x3f\x47\xe4\x24\x4c\xde\xf3\x57\x96\xfb\x45\x5c\x05\xc1\x56\x45\x2f\x1c\x86\x60\xf5\x34\x6f\xba\x16\x92\x76\x22\x14\x46\xf8\x2b\xbb\x20\x27\xb0\x56\xb5\x37\xcf\xd5\x9c\x57\x29\x91\x66\xa6\xf2\x08\x71\xc7\x4e\x6c\x1d\x3f\x5a\x37\xb7\x5e\x8d\xad\x6c\xad\xcf\x12\xc9\x09\x58\x6a\x32\xf1\x50\xc6\x8e\x33\x23\x06\xab\xef\x8b\xe1\xab\xd5\x6c\x42\xd3\xc3\x69\x36\xcf\x8f\x2a\xca\xce\xb7\x07\x99\x4a\x3d\x4c\x05\x55\xa0\x15\xde\x89\x20\x37\xaa\xc6\x8e\x33\x81\x3b\xf3\x05\x0f\x0f\x3a\x8d\xf5\xe8\x14\x65\x85\x2f\x6a\x19\x5e\xa6\x88\xac\x5d\x25\x8e\xee\x20\x76\xa6\xb2\x36\x36\x2e\x3d\x79\x2e\x7f\x35\x8c\x6b\xa9\x94\xda\x7a\x64\xb1\x82\x63\x96\x96\x55\x47\x3a\xaa\x37\xcb\x3c\xfb\x00\xa2\x7f\x8f\xb2\x4a\x4b\x73\xb0\x25\xc9\x63\x35\x43\x84\x84\xe9\x58\xad\x08\x48\x27\x7d\xf9\x50\x84\x7d\x46\xa9\x87\x4f\x10\x39\xfb\xea\x7e\x08\xbc\x79\x67\x5e\xf1\xdf\x6e\xf2\x12\x30\xa7\x9a\x3b\x16\x13\x08\xa0\xa4\x60\x0b\x53\x47", b"\x76\xe9\xb6\xef\x7e\x8d\x48\xfb\xfc\x43\xbf\x46\x52\x81\x59\x22\x23\xfa\x7e\x0d\x99\x78\x39\x2d\x35\x58\x68\xc8\xa2\x02\x09\xbb", b"\x7f\x9c\x8d\xea\xb5\x1c\x60\xbb\x6f\x86\x6c\x76\x45\x01\x38\xe0\xd2\x94\x6a\xca\x6c\x5f\x88\xdf\xe3\x5a\x0c\x1b\xa4\x93\xee\x47", )?; test( HashAlgorithm::SHA1, b"\x67\x85\x1d\xe9\x82\xfc\x70\xf9\x69\xd8\x2f\x65\xd8\x5b\x03\x32\xd6\x67\x11\x4f\x27\xb5\x8b\xb9\xe5\x65\xd2\xe4\x0a\xd0\x11\x98\x3d\x93\x60\x49\xcc\x97\xa2\x16\x26\x0f\xa2\xe4\x10\xad\x6d\x6c\x98\xa5\x48\x75\x9a\xa8\xe2\xd0\x22\xc1\xfb\xc1\xb1\x6b\x10\xd8\x3f\xbb\xbd\x12\x6e\xc4\x3d\x5f\xed\xc4\x07\xc8\x31\x46\x1c\x7f\x33\xed\x94\x74\x00\x31\xec\xd0\xf7\x01\xc7\xb1\xdf\x88\xa2\x49\x26\x5b\x3f\x60\xc3\x8f\x42\x85\xbb\xc9\xba\xe1\x64\xbc\x38\xe1\x62\xc2\x35\xc9\xa9\xdf\xc1\xb1\x50\xea\xeb\x14\x82\xeb\xed\x48", p, q, g, b"\xab\x9a\x99\xff\x87\x89\x9b\xd6\x65\x7b\x3a\x9e\x9b\x72\x06\x99\x6b\xbc\x77\x99\xdd\xe5\x7d\xcf\xff\x80\x98\x87\x5d\xc4\x65\x0d\x79\x1e\x90\xbc\x4c\xee\x10\x98\x9b\xf4\x9e\xb5\xe6\x23\x08\x57\xf9\x68\x41\xae\x83\x62\xe4\xee\x5c\xc8\x60\x2f\x6a\x1a\x2c\x6f\x8f\x2f\x68\x0a\xd3\xa7\x2b\x0e\x07\x51\x1e\xad\x30\x1f\x57\x52\x78\xa0\x74\x13\x8a\xa4\xea\xa5\x39\x19\xe3\x4f\x00\x1c\xbe\x2d\xcb\xc3\x45\xc7\x7f\x56\x87\xd0\x71\x98\x1a\x4d\xca\x29\xd0\x26\xbb\x53\xec\x9c\xf0\x3a\x88\xd6\x3c\x52\x20\x6d\x35\x1f\x8f\xca\x10\x23\x9e\x84\xf4\x91\x5c\xe3\x47\xf4\x8d\x65\x0a\xaa\xa6\xb0\x2d\x31\x64\x97\x3f\x82\xfc\x0e\x0f\x83\xa2\xd4\x58\xaf\x65\x73\x6d\x7e\x0d\xbb\x26\x4f\xd7\x79\xff\xd5\xa3\xf0\x66\x58\x44\x94\x59\x85\x26\xcd\x67\xe1\x2d\x6c\x67\x96\x5a\x70\xec\x3f\x09\xe2\xcc\x44\x7f\x17\x7e\xc8\x76\x04\xb5\x31\x48\x66\x83\x02\x5e\x3b\x52\x0a\x26\xe6\x9c\x95\x8c\xf8\x43\x5f\x7c\x6c\xe5\x64\xf0\xa7\x2d\x1f\xc4\x72\x05\xa5\x0b\x39\xd5\x16\xb1\x4a\x47\x6f\x6c\x2d\xca\xce\x50\x33\x9c\xae\x20\xcd\x34\x21\xa7\x5f\x6d\x37\x7b\x8b", b"\x76\x3e\x89\xfc\x8b\x2a\x09\x0b\x75\x81\x2a\xef\xa5\x5d\xe7\xb7\xcd\x61\xec\x3f\xdf\x87\x30\xce\x16\xb0\x5a\x7b\x94\x56\xfd\x2d", b"\x4a\x97\x08\x6b\x67\x17\xa7\x3a\x6b\xe6\xd4\xa9\x5b\x83\x43\xbd\x20\xb0\xd7\xb5\x1c\x3d\xa1\xd8\x6c\x58\x52\x35\x08\x71\x37\x9b", )?; test( HashAlgorithm::SHA1, b"\x61\x6d\xe9\xdd\x23\xeb\xed\xe4\x28\xe0\x32\xdb\x78\x38\x10\x8a\x22\x4f\x7a\xca\x57\xb1\xdf\x87\xf0\x31\xfe\x1d\x86\x08\x3d\x68\x8c\x5c\x3e\xf0\x78\xe6\x4d\x8d\x5a\x9e\x61\x2d\x39\x83\x46\x0c\xa1\xf8\x16\xf7\x87\xc0\x3c\xa4\x3a\x1f\xd8\xce\x13\x86\x55\xdf\x67\x70\x56\x36\x4c\x0e\xab\x8e\x04\x93\xc0\x7b\xd4\xb2\xb0\x50\x22\x19\x09\x32\xde\x79\x4f\x19\x5d\xbe\xf2\x97\x09\x3e\x7d\xa1\xc4\x30\x4d\xb4\x0b\x63\xca\x53\xe1\xb8\xbc\xda\xd9\x13\xd7\xa9\x02\xaf\x02\x5c\x36\x7c\x48\xde\x38\x7f\x1a\x9b\xcd\x7c\xa4\x2e", p, q, g, b"\x58\x4e\xae\xed\x2d\xc7\x85\xd8\xe2\xb8\xc8\x5f\xd0\xe5\xec\x25\x1f\x13\x49\x58\xbd\x9e\xea\xe4\xf7\x9f\x86\x2b\x62\xcf\x60\x2a\xb1\x0d\x22\xec\xa4\x99\x04\x2f\x2c\x87\x5f\x27\x08\xba\x0d\x69\x7a\xf3\x9f\x23\xf5\xe0\xb7\xde\x4f\xf7\x96\x4b\xab\x12\x79\xef\xa2\xaa\x79\x7a\x2d\x21\xe7\x88\xd2\x49\xf4\x26\x93\xcd\xbf\xd7\x1f\xdc\xb1\xaa\x93\xb7\x9b\xac\x0d\xbc\xb5\x87\xbb\xff\x4e\xf1\x5a\x37\x99\xa5\xfc\xa8\xb1\x58\x98\x38\xe3\x00\x96\x06\x9c\xa7\x93\x1f\x74\x08\x81\x5b\x58\x5d\x14\x0a\x74\x7d\xe4\x3b\xd9\x2c\xac\x3f\x9a\x9b\x18\x62\xfd\x70\x46\x73\xe1\xe5\x87\x10\xc1\x6d\xdb\xe7\xe5\x2d\x31\xa7\xdf\x15\x97\x49\x58\xb1\x28\x81\x16\xed\x98\xff\x24\x7f\x50\x28\xce\xc8\x6d\x9e\xb9\x7b\x12\x6a\x48\xad\xc9\x52\xe9\x0d\xc5\x2f\x2b\xd7\x81\x03\x55\xaa\x90\x75\x05\x1f\x26\x12\x9c\x2d\x2f\xb0\xba\x80\x66\xe4\x14\x98\x9d\x92\xe2\x9e\x68\x99\x60\xe3\x3e\xe5\x6c\xa6\x2d\x71\x4a\x42\xcb\x74\x87\xf7\x0c\x0c\x0b\xa6\x43\xfa\x9d\xd5\xf8\x52\x59\xfd\xec\xd4\x9f\xa9\x70\xc8\x32\x26\x82\xb1\x14\xf2\x64\x78\x37\x63\x7a\xbc\x0e\xd2", b"\x74\x8f\x46\x6b\x7f\xdc\xdf\xa7\x70\x17\xc8\x65\xa3\x3b\x1d\xad\x4d\xb9\x9d\xbd\x63\xef\xa1\xc8\x73\x45\xc4\x83\x3b\x06\x32\xac", b"\x0b\xf9\x93\x8e\x79\x72\xeb\xb0\x0f\xb0\xa3\xc0\xc2\x47\x6d\x25\x09\xdb\x23\xaf\xca\xec\xb1\x7d\xc5\x71\x90\x53\x17\xeb\x8c\xa7", )?; test( HashAlgorithm::SHA1, b"\x11\x5f\x0a\x8b\xe3\x4e\x84\xd0\x9b\xdc\xca\x69\xd1\x9c\xe1\x7d\xd6\x7d\xf7\x39\xaa\x4f\xc6\xe8\x07\x70\x76\x53\x5f\x39\xaf\x83\x02\x88\x14\x71\xa5\xfb\x0e\x18\x39\xa3\xaa\x76\xdf\xda\x4b\xde\x2f\x9f\xa2\x5f\xa5\x82\xb7\x56\xa4\x96\x6d\x75\x32\x0a\xc1\x99\x54\x72\x27\x16\x66\x15\x6e\xa8\x6c\x19\xa2\x39\x89\x5e\x55\x78\xa3\xc3\x9b\x0b\xa3\x25\x88\x27\xa0\x1d\xf1\xf3\x0d\xb2\x2d\xdb\xc2\x67\xc9\xe2\x90\xd5\xd4\x57\xd0\xa9\x4d\x8a\xa7\x3f\x8e\x79\xf3\xac\xd3\x1b\xde\xee\x7a\xa3\x2c\x79\x2c\x22\xac\xb8\x07\xba", p, q, g, b"\x2e\x06\x07\x3f\x59\x19\x6d\x3e\x29\xba\x71\x8e\x84\x48\x9b\x6f\x44\x7f\xd6\xf6\x7a\x9e\xe6\x35\x7c\x5e\x8a\x58\xfa\x3c\x4f\xb6\xac\x83\x14\xeb\xdc\x3b\x4d\x61\x27\xf2\xb4\xd2\x11\x2c\x27\x79\x9f\x0c\x1a\xc5\xf7\x94\x6b\x56\x07\x21\x2d\x79\x67\x41\xcc\x3b\xe1\x27\x21\x2a\x12\x5e\xdc\x3a\x7a\x91\xa5\x25\xcd\x62\x15\x21\x99\xb1\x8b\x4f\x1d\xc3\x32\x21\x5d\x65\xd6\x4a\xd0\x60\x98\xff\x21\x80\xab\x47\xbb\x57\x28\x72\x0c\x93\x7e\x12\x07\x64\x9e\xd1\x9c\x88\x33\x31\xea\x41\x5f\xaa\x51\xc5\x56\xd1\x26\x49\x66\x5f\x1e\xce\x88\x0d\x05\x5a\x2a\x79\x3a\xdc\x74\xb3\x8f\x15\xf5\x0a\xa9\xb4\x67\x86\xd9\x07\x01\x7b\x1d\x62\x35\xc4\x3b\x37\xc2\x03\x6a\x16\x40\xf6\xbf\xe3\xbe\xc2\xb9\x5b\x43\x00\xa3\xbd\x78\xf4\x71\xf6\xaa\x56\xe5\xe6\x34\x75\x71\x99\x6f\x77\x86\x70\xad\x94\xef\xaf\x20\x99\x1c\x55\x59\x24\xfd\x55\xcd\x51\x8d\xf0\xbd\x55\x8f\xaa\xc3\xf9\x82\x6a\x86\x5a\x3c\xed\x0f\x59\xcb\xea\x45\xc6\x54\x12\xbd\xdf\x8f\x2a\x8a\xab\x3d\xfc\xa1\xdf\xf5\x03\x74\x16\x3f\xa8\x99\xcc\x7f\x7f\x10\x8b\x19\x4f\xc9\x55\xca\xbe\x9c\xa4", b"\x5c\x8d\x76\x44\x07\x35\x05\x5c\x1b\x36\x69\x8d\xa7\x39\x03\xb3\x32\xd6\x4c\xa5\x60\x30\x46\x14\x4f\xb7\x66\x8b\x1a\xca\xc3\x37", b"\x11\xc5\x4e\xfb\xd4\x92\xa7\x14\x7a\x1c\x50\xb2\x87\x37\x7b\x52\xd2\x19\x39\x07\xd5\xbb\x63\x61\x59\xc1\x53\x18\xa4\x80\xca\x6a", )?; test( HashAlgorithm::SHA1, b"\x3c\x1f\x2b\x92\xdb\x1b\x43\x15\x83\x7b\xaa\x86\x30\x43\xa9\xb4\x49\x6a\x78\x14\x3c\xa7\x4f\x6e\x67\x18\x1f\xac\xf5\x0a\x6e\x08\xd2\x79\x45\xd0\x0e\x7b\x06\xf9\xc5\x7c\x0e\x2f\x15\x27\xc9\x4b\xce\xce\xa6\x99\x31\x75\xd0\xf0\x9b\xab\x4f\x15\xaf\x55\xab\x7a\xa9\xb1\x6b\x48\xc9\x4a\x6a\x99\xc2\xd7\xe4\x77\xb7\x44\xcd\x27\xcd\xb9\xb0\xbb\xf8\x10\x75\x6b\xc6\x37\x6f\xa1\x5b\xfb\xea\x3c\x93\x76\xca\x69\x79\x75\x2f\xdb\x3a\x65\x5a\xff\xd6\xc0\x18\x6d\x1a\x34\x35\x5d\xae\xa8\xcc\x75\xac\xf9\x6b\x88\x47\xdb\xdb\x8d", p, q, g, b"\xa4\x74\x2d\x3c\x7e\x76\x81\xb0\x1c\xd6\xaa\xe1\x74\x23\xcc\x78\x04\x91\xd0\x8d\xf7\x3b\x4a\x71\xed\xf7\xbd\x2e\xe2\x9c\x69\x8c\xd6\x6d\xba\x04\x91\x68\x8f\xc7\xee\xfb\x4d\x70\x91\x47\xbf\xd4\xc8\xc4\xb7\x97\xab\x91\x97\x57\x3b\x5d\x36\x59\x9c\x4a\x59\x2c\x46\x69\x55\xe8\x0a\xe5\xd2\x12\x2b\xca\xa5\xd0\xe1\xd9\x4b\x4e\xd2\xa9\x9b\x1a\xf5\xd0\x8e\xec\x86\xc3\x77\x53\xa3\xc3\x65\x6c\x0f\xef\x0d\x2c\x47\x1e\x4f\xfa\x0f\xb1\x63\x17\x4a\x4d\xf1\x70\x78\x79\xfe\x08\x36\x55\x29\x11\x27\xa3\xbb\xb0\x59\x7e\x23\x80\x2e\x42\x4e\xfe\x40\x16\x36\x03\x64\x50\x6c\x8a\xb4\x08\x1f\x0a\x95\x69\x2c\x26\x29\x53\x7f\x05\x30\x61\x81\xdb\x66\x9b\xcf\xaf\x01\xc1\x53\x95\x61\x42\x38\xa2\x30\x94\x29\x19\x95\x55\x14\x26\x39\xb3\x44\x3e\xf8\x5a\xf7\x4b\x5e\x88\xb7\xc7\x0a\x81\x67\x33\x4f\x27\x29\x4a\x8b\xa1\x26\x66\x95\xa3\x69\x37\x2b\xad\xcb\xa7\x62\x3a\xa5\x8c\xbc\xf2\x5b\x4b\xbe\x66\x3d\x4e\xce\xd1\xa1\x8e\x77\x53\x39\x1d\x6c\x53\x85\x4c\x4a\x8d\x0e\xe1\xa7\x90\xa1\xa2\x10\x71\xf1\x38\x6c\x23\x5a\xc2\x61\x82\xd0\x1a\x1e\x81\xec\xf8", b"\x31\xc1\xc6\xae\xe7\xed\x54\x1a\x28\x1f\x37\x63\x2b\x27\xba\x88\x53\x6f\x36\xbc\xd9\x2f\xcc\x36\x0d\xa0\x41\xf4\x19\x7f\x7f\x95", b"\x45\xe1\x01\x9b\x2a\x17\x02\xb5\xdf\x1e\xef\x4f\xb7\xdf\x6a\x53\xaa\xa6\x6e\xcb\x8b\xe5\xcd\x2e\x28\xb3\x53\xc8\x70\xe0\x1f\x41", )?; test( HashAlgorithm::SHA1, b"\xad\x38\x9f\x53\x23\x5d\xeb\x06\x8f\x70\x97\x78\x03\x30\x74\x64\x93\x60\x7f\xdb\x7e\x11\x70\xbd\x1f\xe0\xda\x01\x27\x14\xb8\xf1\xb1\x28\xc6\x9a\x53\xd7\xdd\x26\x46\xb0\x97\x20\x88\x3e\x23\x87\xdd\x15\xd4\x65\x64\xad\xff\x66\x42\x37\x2c\x83\x82\x87\xba\xfa\x5f\x43\x43\xa2\x7e\xc8\x06\x97\x70\xe5\xc3\x67\x54\x88\x33\xfd\xdc\xc5\xf8\x61\x7a\xaf\x41\x28\x9d\x96\xdd\x40\xf1\x09\x8d\xed\x9f\xbb\x11\x0a\xeb\x14\xd6\x92\x72\xdf\xb2\xdd\x7d\x75\xe7\xa8\x8d\xc4\x14\x7f\x27\xc6\x4e\xb1\xbf\x0a\xa0\x56\x9b\xbd\xa3\x20", p, q, g, b"\xbf\x4a\xa2\xd8\x67\xb4\x33\xf9\x34\xd1\xd5\x67\x01\x0d\xbe\x06\x79\x05\xf4\xe3\x5d\x7c\xe5\x68\xb5\x5a\xba\x69\x4d\x12\xdf\xba\x95\xc2\x35\x07\x84\x61\xaa\xab\x81\xf1\xe4\xdf\x32\x31\x9e\x57\x59\xc5\x26\x3e\xbf\xbe\xbf\x79\x60\xc5\x7a\xed\x79\xbf\x2d\xe3\x89\x48\xf8\xff\x79\xef\x26\xd6\x6a\x7f\x98\x38\x41\x17\xdc\xe1\xf3\x86\xae\xcc\x43\x69\xaf\xb2\xe0\xde\x77\xcc\xd2\xe7\xde\xc3\x28\x61\x42\x43\xef\xfa\xc6\x07\xc8\xd5\xfc\x5c\x7c\x0b\x11\x43\x96\x35\x73\xd9\xf1\x06\xfc\xec\xf2\xe1\x5c\x67\xa3\xbf\xf6\x90\x8b\x28\x6d\x0e\x41\x31\xfb\x81\x62\x2f\xff\x9e\x10\xf5\x77\x1a\xfe\xde\x22\x76\xe8\x34\x4d\x9a\xe2\xf4\x93\xfb\x48\x56\xd1\xba\x57\x60\xdd\xae\x38\xaf\x7d\xdc\xa4\x09\xe7\x90\x72\x68\x69\x1b\xaa\x33\xdf\xcb\xfd\x69\xe9\xaa\x9f\xaa\x79\xcf\x30\x3a\xc8\xb1\xfa\x07\xc1\xd4\x0d\x1c\xea\x01\xe8\xba\x0d\x65\x26\x5f\x4c\x6a\xab\xb1\x6e\xbe\x2f\x6e\xf5\xaa\xac\x25\xc0\xc2\x73\x0c\xbe\xed\xc1\x77\x66\x7e\xe0\x2b\xf4\x52\x34\x18\xa9\x86\xd5\xb8\x7a\x9b\x75\xec\x20\x1a\xf0\xf1\x96\x1c\xd5\x1b\x85\x87\x91\x47\xe6\x07", b"\x2f\x94\x84\xaa\xed\xa9\xdc\xb8\x8d\x2d\x36\x44\xdb\x2c\x58\xee\xfe\x2e\x76\x95\xa6\xc8\xbe\x9a\xbe\x97\x17\x3e\xfc\x9c\x0b\xc3", b"\x01\x66\xa7\xbf\x4e\x8b\xda\x6b\x86\x39\x69\x43\xa7\x4a\x8e\xbf\xc6\x03\xa8\x5e\xd2\x87\xbf\x3f\x5a\x30\xdd\x0b\xbe\x49\xcd\x8b", )?; test( HashAlgorithm::SHA1, b"\x12\xf9\x58\x2e\x3a\x1a\x76\xf2\x99\xd7\x2d\x9b\x15\x02\xb9\x90\x60\x80\x26\x60\x22\x6b\xc4\x7b\x71\xe5\x4e\xc9\x38\x8e\xac\x32\x59\x02\xac\xbe\x2b\xd7\x10\x9e\x19\xf3\x77\xc9\xd2\xb4\xd2\x80\xcd\xfa\xa4\x88\x88\xb9\xcf\x4e\xd0\x6c\xcf\x5a\xd8\x66\xd6\x93\x2d\x40\x25\x92\xf6\xbe\x6e\x68\x76\xdb\x5a\x62\xbe\xea\xf3\x73\xb6\x02\x38\xab\x96\x82\x92\x43\x75\x9b\xdb\x58\x6f\x45\xec\x4a\xe2\xcb\x22\x24\x8a\xb0\xb6\xaa\x7a\x75\x83\xa6\x1d\xd3\xb8\xf1\x19\xcd\x84\x04\x79\xa4\xa9\xaf\x8a\x43\x9d\xb9\x04\xac\x14\xec", p, q, g, b"\x72\xd8\x10\x06\x92\xe1\xa3\x0a\x32\xe3\x7c\x90\x9e\xb6\xc7\xba\xea\x72\x58\xb0\xb7\x86\x68\xe7\x59\x15\x07\x00\x37\x47\x9b\x88\x4f\xa9\xf1\x80\x66\xdf\x89\xb4\x90\xf9\xa2\x69\x6a\x85\x05\x03\x69\x77\x60\x4d\xad\x26\x8e\x90\x55\x28\x35\xfd\xca\x33\x39\xb3\x23\x60\xc9\x43\x58\xff\xcd\x0b\x1e\xa1\x10\x66\x12\x2e\xfd\x01\x7c\xd6\xfe\x1e\xcd\x0d\xd6\x67\x80\x81\xb8\x4c\xb6\xe1\x44\x47\x1d\xae\x76\x36\xb4\xa0\x92\x9c\xa7\x1a\xa4\x7b\x40\x86\x66\x5d\x66\xd4\x03\x4c\x18\x8d\x64\xd3\x8b\x69\xf0\xca\x17\x1c\x85\x92\x5c\xad\x28\x40\x27\x7d\x28\x87\xa7\xf7\xb8\x1e\x6b\x12\x87\x0c\xc3\xc6\x9e\x18\xca\x9c\x22\xc3\xd3\xa3\x9e\xe2\x86\xca\x65\xd2\x3f\x3e\x81\x11\xaa\x7c\x6e\xa9\xa0\xd1\x4c\x84\xdd\xf7\x6a\xbd\x44\xdb\x3b\x98\x33\xd6\x9c\xb9\x9b\x52\x4c\x98\xfd\xb9\xd0\xff\x20\xc9\xd2\x68\xe8\xe7\x17\x5f\x13\xc1\x1c\x57\x95\xd0\xfe\x0b\x38\x99\xb7\x4c\x0d\xca\x91\x47\x6f\xeb\xcb\x50\x9f\x7f\xd5\x07\x02\x39\x88\x14\x52\x42\xdf\xc8\x09\xce\x95\xc6\xf1\xb3\x1f\x67\xe0\x16\x50\xdd\x45\x87\x8e\xfc\x7e\xa8\x9c\xf6\xe3\x17\x1e\x43", b"\x38\xcf\x6b\x8c\xba\xe8\x2e\x62\x95\xf8\x33\x16\xa9\xc4\x9d\x2d\xc7\xc9\x2c\xb9\x0b\x19\xa2\xc2\xd4\x56\x49\x94\x93\x54\xd9\x30", b"\x35\x6a\x58\x50\xd0\x7a\xec\x6e\x9d\x4a\x4d\x7f\x79\xd9\xb0\x35\x2b\x08\x7d\x7e\xf4\x83\x94\x12\x8c\x5a\xe4\x99\x3e\x82\x59\xb8", )?; test( HashAlgorithm::SHA1, b"\xb6\xac\x84\xc4\x9f\x6b\xd6\x01\xd5\x86\x8b\xa0\x6d\x49\xb8\xcb\xa8\x7a\x9d\x6e\x79\x05\x24\x75\x41\xfd\x33\x2c\x2b\x03\x74\xcf\x57\xd4\xa0\xdc\x0b\x5a\x6c\x3f\x8f\x7e\x24\xbe\x3a\x1e\xed\xc4\xa8\xc5\x75\x84\x7c\x02\xe4\xed\xd4\x74\x50\x40\x68\x56\x70\x05\x89\x96\x25\x0f\x73\xe2\x98\xa4\x3b\x39\x1a\x4a\xd5\x67\xf0\xc9\xbc\x4b\x6a\xbf\x6d\x1e\x5c\x56\xb2\x2f\x4e\xab\x36\xaa\x1a\x81\x2a\x1d\xae\x8d\x28\x73\xcb\x2c\x2a\x52\x1d\x32\x00\x19\xc7\xca\xb1\xef\xb1\x1f\xa4\x59\x5c\x53\x4c\xe5\x27\xd4\x3b\xa6\x05\xf7", p, q, g, b"\x06\xda\xb4\x8a\x07\x6e\x8c\xec\x27\xd4\xc4\xfb\x98\xe7\xc0\x0f\x36\xbe\xd7\x3f\x11\xe4\x91\xd9\x13\x86\x4c\xae\x0f\xdf\x88\x34\x68\xd7\x35\xde\xee\x52\x51\xdd\x38\xa1\xf8\xb1\xd2\xbc\x19\xd3\x7f\x31\x87\xa4\xef\x69\xc3\x3d\xc9\x52\x88\x01\xa2\x3a\x98\xd9\x6f\xd3\xf1\x29\xb8\xca\x29\x41\x42\x1b\xa1\x82\x8e\x0c\x4f\x8d\x88\xc5\x31\x93\x93\x02\x92\xa0\xdf\x11\x47\xb0\x7c\x20\xaa\x72\x6c\x71\x77\xef\x66\x0d\xdd\x4e\xcd\xd7\x33\x15\xd4\xb9\x35\x60\x13\xe1\x15\xf0\x67\xe8\x43\xc8\x96\xc1\xa5\x4c\x81\xff\xab\x1b\xfe\x7c\x78\x5e\xde\xc3\x2f\xba\x65\x2b\xab\xfd\xaa\xa0\x39\xb0\x56\x8c\x6b\xeb\x7d\x13\xfb\x4e\x45\x88\x14\x0e\xd6\x26\xb1\x87\x49\xb0\xf7\x9f\x66\x9f\x6e\x70\x45\x73\x8c\xf5\x0a\x6d\x00\x28\xba\x11\xfe\x18\x45\xa2\xdc\xbd\x9c\x1b\x02\x33\x6f\xb3\x0e\xaa\xa3\x97\x41\x8f\xe1\x7e\x14\x98\x29\xca\xb1\x3d\x2c\x2e\x6b\x90\xe5\xcc\x81\x83\x4e\x32\xfc\xa8\xa1\x73\x63\x4e\x01\xf9\xa9\x73\xe0\x29\x64\x4f\x01\x65\xb3\x03\x3d\xfb\x05\x4d\xd2\x1d\x65\xe0\xc0\xe1\x37\xb4\x8c\x34\xd4\x21\x34\xc4\x7b\x97\x24\x33\xcc\xde", b"\x1d\x60\x0a\x74\x5a\x1d\xec\x93\x38\x68\xdc\x53\x5a\x19\xee\x9f\x1a\xf8\xbf\x09\xb5\xab\xee\x15\xdc\x4f\x7c\xbc\xb9\x5a\xc8\xc5", b"\x23\xb8\x10\x97\xd5\x83\x34\x2e\xbe\x4a\xed\x36\x4a\x7a\xf9\x88\x2f\x74\xe6\x45\x18\xaa\xed\xce\x34\x6c\x91\xd6\xd7\xac\x47\x0b", )?; test( HashAlgorithm::SHA1, b"\xa9\x2e\x2d\xdb\xfd\x18\xcd\x30\x73\x73\xfc\xb3\x9d\xff\xc3\x3e\x0b\x91\xa4\x8c\x62\x07\x1f\x2f\x7a\x8e\x50\xdb\xf2\xc2\x90\x88\x93\x07\x97\x5b\x6a\xcd\x64\x2c\x8e\x3d\x34\x44\xac\xac\x98\xc2\x2e\xd0\x65\x51\xfe\xc5\xdc\x7c\x9f\x22\x43\xb6\x81\xcc\x9f\xa4\xfc\xc1\x2c\x31\x82\x37\xe9\xa5\xdf\x0a\x77\xac\x22\x40\x20\x39\xce\xf3\x1b\x1e\x62\x3a\xf5\x82\x12\xa2\x2e\x7e\x60\x41\x9b\xb3\x6b\x77\x7c\xf6\xce\x65\xdd\x1f\x56\x96\x3e\xb2\x8b\x77\x06\xf1\x37\xc0\xf7\x36\x3a\x00\x2d\x82\x7e\x45\xba\xdc\x20\x23\x3c\x16", p, q, g, b"\x51\x41\x22\x3f\x46\x97\xde\x27\x22\x69\xf3\xd9\x94\x37\xc4\x8d\xba\x5a\xb7\xf1\x37\x3f\xc6\xba\xd8\x16\x10\x18\xc5\xd6\xfc\xe2\xbc\xcc\x40\xca\x78\xe4\xd7\x3b\x6e\xeb\x09\x6f\x17\x5c\x4c\xd0\xc8\xe9\xf3\xe9\x31\x19\x51\xd5\x1e\xa2\x44\xfd\x33\xd9\xe4\x7d\xe7\x5f\x10\x00\x24\x8f\xdc\x00\x3b\xc0\x7b\x50\x1c\xe5\x8f\x6e\xc1\xae\xd1\x75\x4c\x36\x82\x6c\xd9\x19\x76\xb4\x08\xeb\x7a\xa9\xbc\x42\x44\x80\x58\xff\xd3\xb4\xe5\x13\xc6\x58\x9f\x8e\x1b\xc1\x45\xa4\x7b\x24\x70\xe7\x24\x1e\x23\x25\xe5\x43\x02\x25\x5c\x3d\x6d\x97\xab\xc5\xc6\x05\x62\x66\xa9\x52\x3d\x46\x1f\xc7\x44\x14\x6d\xa3\x5c\x04\xa4\xfc\x0b\x09\x58\x81\xcb\x94\xfc\x4c\x03\xbb\x86\x23\x95\x39\x28\x49\x0d\xbe\x7f\x84\xef\x68\x66\x7f\x23\xd4\xcb\x3e\xd8\x87\x44\x9f\x77\xae\xb1\x58\xa2\x6d\x1b\x39\xb4\xe6\x29\x7f\x23\xd4\x9f\x5b\x41\xf1\x70\xe7\x2f\x72\x13\xee\x40\x36\x4c\x1c\x9a\x63\x98\x5f\x69\xe4\x4e\xac\xdf\xdc\xb5\x8c\x35\xda\xce\x8b\x93\x5d\x07\x89\xa8\xc0\x66\x9a\x23\xd6\x73\x92\x9b\x2a\x58\x2d\x6d\x3b\x2f\x9e\x67\xbe\x89\x18\x90\xda\x12\x36\xc6\xf0", b"\x34\xa6\x5e\x99\xbf\x01\x69\x8b\x5a\x68\xf2\x15\xb9\xc2\x92\x11\x5d\x17\xb3\xc2\x02\xea\x1f\xda\x17\xfc\xd8\xa0\xcd\x74\xb6\x36", b"\x7e\x67\xd4\x42\xb8\xf9\xac\x29\x74\xe8\x4b\xa6\x5a\xef\xf0\xdf\x5f\x83\xc2\x71\xec\xe7\x92\xa8\xda\xb9\xc4\xae\xe8\x7b\xfe\xa8", )?; test( HashAlgorithm::SHA1, b"\xb5\xaa\x1c\xfe\x23\x48\xd5\x7f\x0e\x53\x33\xfc\x70\x27\x6d\x24\x18\xdd\xda\x49\x12\x2f\x4a\x88\xe8\x01\x0f\x6f\x78\xdc\x82\x9b\xa5\xc7\xcc\x68\xdb\x66\x40\x80\x94\x5c\x43\xee\xb7\x05\xc2\xef\x13\xde\x6e\x4b\x8f\x4d\xe1\xd0\x4f\xb3\x3d\x5b\xcd\x78\x93\xd8\xca\x8b\xfd\xe3\x8c\x9f\xec\xa6\xc4\xec\x03\xb2\xce\x7b\x35\xed\x60\xa6\xa4\x3f\x7f\xc9\xed\x08\x06\x1a\x09\x9b\x3e\xee\xae\x7f\x0f\x15\x16\x14\x9d\x17\x5a\x95\x3f\x52\xc8\xc5\x18\xf3\xad\x24\x7c\x9f\xba\x23\xf1\xf8\x29\xd5\xca\xe6\x26\x73\xee\x20\x1a\xda", p, q, g, b"\x0b\x66\xef\x2c\x7a\x34\x20\x5d\x70\xfc\x36\x40\x49\x57\x04\x3c\xf4\x6b\x28\xac\x4f\x08\x3e\xba\xc3\x78\x7f\x55\xe8\xdd\x1f\x75\xd9\x19\x3a\x84\x27\x59\x37\x6f\x05\x08\xc9\x4c\xc7\x52\x8d\x66\x11\xb5\x0a\x73\x26\x1a\x4a\x5c\xff\x73\x0d\x99\x85\xbb\x34\x1d\xfd\x73\x9a\x4e\x96\x3d\x1c\x40\xf1\x14\xd7\xa7\xac\xe8\x9e\x81\xdd\x70\x86\x1e\xfe\xf2\xba\x9d\x1c\x64\x25\xd5\xf8\x58\x09\x05\x9e\x8e\xf3\x1f\x45\x3c\x97\x74\x3f\xcc\x94\xd3\xb1\xbd\x62\x08\x4e\x97\x57\x90\xb3\x71\x93\xeb\x40\x58\x45\x4a\xb2\x83\xfe\x2b\xaf\xaa\xe8\x03\xde\x89\x28\x79\x55\x4a\x34\x0b\x9a\x3e\x25\x32\x93\x1e\xb9\x5d\x3a\xc5\xeb\x3f\x29\x0a\x3f\x56\x93\x69\x51\x28\x8e\x1c\x05\xbd\xa1\xfa\x74\xdc\x78\xd6\x31\xc2\xe7\xa5\x63\x67\xec\x57\x81\x01\x9d\xfe\xe7\x14\x53\xea\x6b\xbd\x90\x77\x8e\x92\xfe\xa8\xc2\x6b\xd6\xa8\x23\xfb\xca\x71\x57\x7b\x63\x35\xf3\xbd\xf4\x0a\x30\x83\x6e\x94\x8d\xb0\x32\xdb\x5a\x46\x03\xdd\x31\xb8\x51\xec\xbb\xdf\x76\xb4\xa6\xc9\x95\x1d\x21\x92\xb9\x7f\xf0\x1d\xaa\x5c\xb0\x30\xe1\x5a\xd1\xd4\xcf\xf3\x67\xf7\x00\xe7\x9f\xfb", b"\x51\x7f\x7d\xf4\x83\x1f\xbd\x01\x90\x8b\x92\x18\xb1\x7a\xe1\xc4\x0e\x00\xc5\x34\x04\xb3\xbd\x72\xb6\x4f\x67\xce\xe7\x52\x15\xf2", b"\x19\x03\x43\x4a\x72\x7c\x8e\xf0\xe8\x0a\x43\xdc\xe2\x83\x4b\x80\x78\x39\xef\x43\xc2\x2a\xfb\x50\x2b\x35\xa3\x81\x78\x2b\xb6\x39", )?; test( HashAlgorithm::SHA1, b"\x27\xaa\x81\xd2\xbc\x49\x60\x1c\x3f\x6b\xce\xb0\x87\x0b\xb5\x5d\xd1\x0e\x7b\xa6\xd1\xf8\xac\xad\xa7\x0b\x5f\x90\x2a\x0f\x40\x62\xeb\x93\xae\x72\xcd\xfd\x3f\x94\x30\x99\xcc\x2a\x10\xa3\xda\x7b\xdc\x9f\x24\xb0\x0b\xf3\x6a\x29\xd7\x51\x36\xaf\x10\xbb\x71\xec\x9c\x19\x32\x05\x8e\x22\xec\x9c\x06\x00\xd1\x73\xd3\x79\x70\xd5\x8a\xe1\xf6\x6c\xef\xd2\x7e\x29\x05\xaf\xdd\xe4\x22\x39\x79\xb4\x04\x1f\xd7\xd7\x16\x6e\xa3\x26\xbe\xfd\x5d\xd8\x96\xef\x47\xab\xc6\xd0\x45\xc1\xca\x23\xc1\x95\x3a\x6e\x12\xcc\x3c\x54\xb4\xf6", p, q, g, b"\x93\x2b\x9c\x0f\x2d\x31\x0b\x6b\xfe\xe8\x00\xc0\x74\xa0\x96\x9e\xfa\x24\x62\x44\xfb\x06\x2a\x74\x5a\x9a\x3c\xfe\x6f\x53\x36\xa3\x13\x19\x2e\x92\xa2\x02\x7e\x1d\x2c\x3c\xfa\x93\xaa\xc5\x3d\xfe\x05\xcb\x8f\x83\x21\xac\x88\x2a\x63\xbd\x37\x5a\xf0\xf3\xd9\xec\xc7\x3a\xee\xbe\x12\x67\xf4\x73\xa9\xf9\x0b\x94\xf5\xb6\xde\x43\x57\xb7\x4e\xb3\x0c\xd4\x1a\xea\xfc\x25\x9e\x85\xca\xc7\xd3\x65\xee\x33\x38\x2a\x58\x4e\xec\x63\x71\x9e\xa3\x25\xa2\x41\x4e\x11\x6f\x84\xd2\xaf\x96\x54\x26\x8e\xc4\x4d\x6e\xa2\xe9\x81\x58\x1d\x45\xd8\x05\xb3\x83\xd8\x5c\x13\x0d\x2d\xcd\x1c\x71\xfa\x68\xd9\xc7\x6d\x79\xaa\x81\x96\x15\x2c\x1d\x94\x40\xc3\x3d\x99\xde\x45\x1a\x35\x9e\x0d\x2c\x51\xd6\xaa\xec\xb2\x67\x95\x40\x6e\x52\x8f\x5d\xe3\xe0\x09\x47\xd3\xda\xcc\x69\x5c\x08\xa9\x60\x88\x9a\x2e\x94\xec\xf0\xa4\x61\xc0\x2a\xfc\x58\xb5\x1e\x00\x36\x9c\x73\xc8\x14\x0e\x8b\x92\x38\x8c\xaa\xbd\x1f\x37\xa6\x2d\x1b\x21\x0e\x0f\x31\x41\x27\xf4\x6b\x57\x6a\x4b\x8e\xde\xb3\x47\x13\xaa\x41\x36\xb8\xa1\x87\x5b\xba\x8a\x59\x37\x06\x65\x44\xe3\x4c\x20\x6a\xa4", b"\x05\x05\x7a\x98\x2a\xb4\xa2\xe3\x22\x38\xef\x2e\x3e\xdb\xa0\x7f\xd1\x93\xd9\x0c\x5f\x05\x3c\x83\xa9\xf1\x76\xe2\x1a\x9d\x52\x08", b"\x03\xc2\xb2\x6c\xf4\x6b\x7f\x72\x69\x1a\x72\xd7\xcb\xf3\x36\x53\xdf\x34\x7f\x02\xb0\x68\x3e\xbc\x6c\xb7\xea\x7e\x72\xdc\x8a\x0a", )?; test( HashAlgorithm::SHA1, b"\x75\x27\x53\x3f\x2d\x10\xc1\x80\x78\xf5\xa8\xde\xc3\x50\xcd\xfa\xd0\x6d\x31\x57\x87\x1e\x4f\xf7\xd7\xc2\xb7\xab\x11\xdf\xf2\x32\xd3\x4f\x07\x69\x92\x78\xf0\x75\x44\x2e\x1d\x4e\xe0\x0c\xd6\xe8\x7c\x19\x31\x33\x38\x41\xc3\x99\x57\x6f\x4e\x58\x7a\x25\x16\x84\xe7\x31\xf7\xc8\x36\x9f\x71\x26\x56\xbc\x1e\x6c\x2d\x20\x9f\x51\x11\x79\xda\x09\x36\x8d\x93\x29\x0e\x05\x8e\x0c\xe9\xb6\x53\x0a\xc6\xc5\xe4\xcf\x0a\x1b\x22\xd5\x88\xd9\x8f\x32\xb3\x4e\x85\x20\x6e\x09\xaa\xc0\x4a\x0e\x1f\x2a\xe2\xa5\xcf\xda\xc4\xe6\xe2\xb3", p, q, g, b"\x72\xc4\x65\x05\xe4\xb0\x71\xf4\x6e\xd6\xb6\xd5\x30\x80\x16\x64\xa4\xfd\x51\x8e\x4c\x6b\xe8\x46\x8a\x38\xc2\x2b\xf7\x4e\xd9\x66\xfd\xc7\xbf\xd7\xc5\x72\x21\x89\x98\xfc\x4c\x14\x4b\x59\x46\x2a\xf7\xe2\x94\xbd\xf5\x79\x7e\xce\xa5\xcb\x2e\xdf\x8c\x8d\x2d\xab\xba\x88\xd0\xb8\x4c\xf2\x85\x24\x36\x9c\x50\x40\xb5\x8f\x09\x07\x72\xda\xc0\xfe\x45\x3c\x32\x90\x7e\x9b\x6c\x74\x0f\xb2\x4e\xd4\xda\xcb\x8f\xdd\x25\xe0\x66\x1b\xc0\xd7\x9d\x41\xf1\x03\xfb\xc8\xf9\x6b\x3e\x3a\x47\x08\xa5\xa7\xf5\xdb\xff\xc9\x8f\x34\x4b\xb7\xcc\xf0\xd5\xed\x07\xaf\x2c\x2f\x0d\x5f\x40\x7b\xcf\xef\xb5\x4d\x9b\x94\x76\x04\xe7\xa7\x83\x56\x87\x4c\x01\xb8\xc1\xfd\xd7\x49\xf6\xa3\xd6\x19\xd1\x09\x0c\x83\x72\x5e\x72\x57\x06\x84\x6c\x16\xbf\x9d\xfd\xf3\x9f\x21\x80\x62\x3f\x4f\x58\x54\x02\xcc\x7d\x6e\x2c\x10\xb5\x7c\x83\x00\x54\x36\x86\xa3\x86\x05\x6a\x93\x1b\xe6\x33\x6b\xb6\x17\x3d\x9f\xda\x8b\x10\x2c\xf3\x29\x89\xcf\x09\x78\xf9\x56\xd9\xae\x0d\x8f\x30\x75\x2f\x15\x6f\x9f\x92\xd2\x95\x4e\xf1\x31\x00\xa7\x5d\x9f\x7f\xf9\x6f\xe1\x5d\xf0\x7e\x79\x93\xe3", b"\x6a\xa6\xc4\xd7\xaf\xda\x30\xff\x2d\x71\x78\xb5\x2a\x3e\x43\x7e\xd5\xb0\x74\x5a\x24\x7c\x9c\x9e\x12\x0b\xd3\xe8\x33\xa1\xdf\xac", b"\x26\xe0\x88\x79\x11\xbb\x5e\xdb\x6a\x56\x6a\x2a\x12\x76\x35\x33\x91\xb1\xe4\xab\x8a\xe0\xb2\x59\xc1\xbb\xb3\xaf\x3d\x85\xb4\x39", )?; test( HashAlgorithm::SHA1, b"\x99\x4a\x49\xe5\xe8\xa5\x69\x8f\xda\xc9\xa7\xfa\xac\x01\xfb\x09\xb2\xc6\x11\x3a\x18\x66\x77\x67\x6d\x11\xe6\x04\x9d\xc9\x8c\x93\xc5\x1e\xb5\x14\x4a\xf1\x81\xe1\xef\xbf\x44\x43\x9a\x13\xd2\x95\x65\x38\x54\x81\x36\x71\xf0\x32\xaa\x62\x25\x8c\x14\x19\x5c\x48\x64\xaf\xae\x0b\x5d\x15\x4f\x97\x56\x5c\xef\x07\x5b\xbb\x6d\x97\xe3\x41\x81\x41\x03\x09\xff\xe9\x8b\x45\xc1\xf8\x74\x32\x63\x43\xc3\x6c\x14\xf5\x5f\xa0\x58\x48\x9d\xff\x3b\x49\xdc\x78\x88\xf4\x5a\x09\x9c\x3c\x91\x9b\x25\xed\xac\x17\x06\xbb\x90\xf1\x64\xca", p, q, g, b"\x05\xe2\x33\xac\x49\xc1\xfd\xa2\xa0\xc3\xc7\x8b\x0b\xc7\x2f\xa3\x96\x74\x05\x5d\x18\x8a\x12\x4a\x58\xab\x38\x50\xd9\xa8\x88\x86\x1c\x2f\xe4\xd0\x46\xc3\xe7\xc7\x5e\xe2\x54\xde\x70\xcd\xb1\xc3\x15\x02\x01\xc2\xe0\x47\x33\xeb\xcc\x25\xb8\x87\x70\xfc\x2a\xa8\x2f\x60\x52\x6b\xc6\x64\x04\x7a\x02\x6c\x22\x90\xfa\xd8\xe9\xf8\x1c\xed\xdd\xde\x7f\xe3\xba\x40\x65\x35\xbf\x27\x10\xd7\x9d\xa0\x1b\xd2\xd4\x2b\xb5\xf4\x09\x9c\x3f\x8b\xc2\xac\x86\x4b\xe7\x89\x2a\xeb\x6a\x1f\x34\x02\xc8\x14\x74\xda\x23\xe0\x79\x5c\xd6\xc2\x13\x67\x50\x9a\x54\x15\x91\xee\x1e\x63\x64\xf7\xe7\x55\xb1\x41\x9e\x90\xaf\x86\x99\x30\x15\x2f\x34\xde\x51\xf0\xf0\x6c\xa3\x07\x6e\x68\xc3\xe3\xea\x7f\x4f\x1b\xf1\xd3\xcd\xe3\xa0\xdf\xf0\xcf\xfa\x1b\x58\x42\x75\x23\x47\x08\x2d\xda\x34\x75\x99\x2f\x15\xa7\x4d\x29\x85\x24\xe6\x36\x22\x0b\xc9\xfa\xed\x08\xaf\x7a\xa5\xe4\x81\xba\x78\xd2\xd2\xfd\x8e\x51\x94\x2c\xfd\x08\x4e\xfe\x0e\xbd\xdd\x75\x00\xef\xc9\x5a\x6c\xad\x37\xfc\x49\x23\xf9\xbf\x65\x29\x78\x05\x84\x08\x76\xc6\x89\xee\x07\x9b\x7f\xa6\x16\x97\x68\xfa", b"\x3c\xc2\x69\xbc\x7b\x89\x58\x64\xa0\x32\x31\x31\x8c\xf3\x93\x79\xae\x33\xc7\x18\x0a\x18\xc0\x8b\x5a\xef\x74\x14\xfd\xac\x05\x8f", b"\x6a\x6e\xb8\x3c\x5f\xab\x10\xe3\x4f\x04\x16\x62\x8c\x82\x1a\x6d\xe0\xad\x0c\x20\x24\x43\xc6\xdf\x03\x2c\xc9\xd8\xe4\x94\x8a\xc6", )?; // [mod = L=2048, N=256, SHA-224] let p = b"\xd0\x22\x76\xeb\xf3\xc2\x2f\xfd\x66\x69\x83\x18\x3a\x47\xae\x94\xc9\xbc\xcb\xcb\xf9\x5d\xdc\xb4\x91\xd1\xf7\xce\x64\x35\x49\x19\x99\x92\xd3\x7c\x79\xe7\xb0\x32\xd2\x6e\xd0\x31\xb6\xba\x44\x89\xf3\x12\x58\x26\xfa\xfb\x27\x26\xa9\x83\x33\xeb\xd9\xab\xdd\xe5\x92\xd8\x69\x3d\x98\x59\x53\x6d\x9c\xc3\x84\x1a\x1d\x24\xe0\x44\xd3\x5a\xce\xd6\x13\x62\x56\xfc\x6d\x6b\x61\x5c\xf4\xf4\x16\x3a\xa3\x81\xeb\x2b\x4c\x48\x08\x25\xa8\xec\xcc\x56\xd8\xdd\xcf\x5f\xe6\x37\xe3\x8a\xd9\xb2\x97\x4b\xd2\xcf\x68\xbf\x27\x1e\x0d\x06\x7d\x24\x65\xa8\xb6\xb6\x60\x52\x4f\x00\x82\x59\x89\x45\xad\xa5\x8e\xa6\x49\xb9\x80\x4e\xb4\x75\x34\x08\xc2\xc5\x97\x68\xc4\x6a\xbb\x82\xe3\x29\x5f\x3d\x9c\xa4\x69\xf8\x4c\xc1\x87\xf5\x72\xdc\x4b\x5a\x3b\x39\x34\x6e\xc8\x39\xdf\xad\x6f\x07\xd6\xd1\xf0\xe2\x15\x20\x9b\xb0\xec\xc0\x5c\x76\x7c\xf2\xe7\x94\x3a\xc9\xcf\xb0\x2e\xee\x1e\x9e\xf5\x94\x6e\x8c\xe8\x83\x16\xb5\xe1\x5f\xdc\xf9\x5a\x13\x2e\xf2\xe4\xbb\x08\x17\x13\x65\x28\xcf\xa5\xdd\x96\x53\x2f\x9c\x3a\xbe\x5c\x42\x16\x20\xed\xb6\xbc\xbd\x52\x23\x4c\xa9"; let q = b"\x80\x00\x00\x00\x12\x99\x7e\x82\x85\xe4\x08\x97\x08\xf5\x28\x07\x0c\x6d\x7a\xf8\xa0\xbd\x01\x40\x9e\x7a\x07\x9c\xdb\x6f\xc5\xbb"; let g = b"\x77\x84\x53\x04\x9e\xf2\x62\x14\x7f\xed\x7b\x59\xb0\xee\x67\x64\x60\x7c\x51\xe7\xb5\xb5\xfc\x6f\xea\x7a\x7a\x7b\x1d\xd6\xbb\x28\x3f\x4a\x9a\xe9\x8e\xfd\x39\x64\xb1\x55\x67\x58\xcb\x15\xb2\xa5\x3a\xf8\x61\x9e\x74\xd8\x58\x98\xbe\xc7\x7d\x3b\x3f\x38\x24\x94\xae\x59\x61\xa1\x3f\xfc\x74\x5d\xa3\x86\x18\x22\x91\x51\x98\x00\xf9\x9d\xd7\x10\xe0\x0a\xeb\x15\xad\xee\x08\x8e\x27\x98\xee\x2e\x46\xf5\x98\x52\x6c\xf0\xf4\x66\x70\x55\xd1\xba\x00\x97\x50\x04\x1d\xc5\xcd\xd2\x72\x5f\xf1\xd9\x7d\xd3\x40\xc8\x51\x8a\xf7\x67\x1b\x87\xd3\x9d\x67\xae\xce\xd8\x4b\x66\xf8\x4e\x07\x01\xef\xc8\x2a\x5c\x9e\xf9\x54\xee\x57\x6d\x24\xc3\x85\xb1\x4d\x63\x03\x7f\x0d\x86\x6f\xd4\x24\xb4\x97\x5b\xdd\x54\x85\xed\x74\x0c\xb9\x32\xe8\x43\xf9\x06\x68\x3f\x7c\x7b\x2c\x74\x77\x5d\x90\x1c\x36\x1b\x84\x7b\x51\x9c\x0d\xa6\x99\x63\x8d\xa4\x0b\xd7\x36\xb7\x83\xd2\x71\x0b\x2c\x2c\xc2\x6e\xf9\x12\x71\xbf\x4e\x2c\x19\x29\xf8\x76\xe9\x02\xe2\x05\x71\x64\x22\x3b\xc7\x8d\x6a\x2b\x9f\x6c\x0c\x7a\x7c\xb8\x59\x22\xf7\xd6\xc4\x28\x7a\xe2\x38\x61\xf8\x12\x88\x48"; test( HashAlgorithm::SHA224, b"\x39\xf2\xd8\xd5\x03\xaa\xe8\xcd\x17\x85\x44\x56\xec\xfa\xd4\x9a\x18\x90\x0d\x43\x75\x41\x2b\xc6\x89\x18\x1e\xd9\xc2\xcc\xaf\xea\x98\xdc\xa6\x89\xa7\x2d\xc7\x5e\x53\x67\xd3\xd3\xab\xfc\x21\x69\x70\x0d\x58\x91\xcf\xf7\x0f\x69\xd9\xac\xa0\x93\xb0\x61\xb9\xf5\x05\x7f\x94\x63\x6b\xc2\x78\x31\x15\x25\x43\x44\xfb\x12\xe3\x3b\x16\x72\x72\xe1\x98\x83\x8a\x87\x28\xe7\x74\x4e\xa9\xa2\xe8\x24\x8e\x34\xd5\x90\x6e\x29\x83\x02\x47\x26\x37\xb8\x79\xde\x91\xc1\xa6\xf9\xf3\x31\xa5\xcf\x98\xa5\xaf\x29\x13\x29\x90\xd2\x74\x16", p, q, g, b"\x7b\xb3\x1e\x98\xc7\xa0\x43\x7f\x97\x8a\x73\xd5\xdc\xfb\xdf\xbb\x09\xcc\x24\x99\xdf\xaf\x1e\xb5\x25\x6b\xcc\xd6\x35\x8c\xab\xb5\xf6\x7d\x04\xa4\x28\x23\x46\x3b\x7e\x95\x7f\x2b\x92\x13\xf1\xfa\x8e\x5a\x98\xd6\x14\x48\x47\x01\xab\xb8\xc7\xd6\x76\x41\xfe\x6e\xd0\x6f\xa4\x52\x7b\x49\x3d\xda\xb2\xe7\x46\x40\xfd\xe3\xde\x70\xda\x69\x3f\x1d\xb2\xb8\xe2\x64\x17\x04\x0a\xf0\xee\xa6\xca\xb4\x51\xa7\x95\xa5\x2e\x18\x7d\x2e\xe2\x41\xb9\x3f\x65\xc8\x6c\x6d\x66\xf4\x58\x34\xcc\xe1\x65\xac\x5e\xb6\x70\xd4\xf0\x09\x5c\x23\xce\x97\x57\xe3\xbd\xc6\x36\xf9\x91\xee\x00\x73\xd9\x0a\x09\x20\x2e\xdb\x35\xcc\x3e\xa1\xcf\x9a\xdc\xa1\x61\x7f\xa0\xbf\xfd\x9c\x12\x62\x29\xa6\x04\xa1\xd3\xbf\x49\x31\xdd\xf0\xb9\x94\x2d\xfc\x8a\x2f\x8c\x09\xfc\xc9\x70\x32\x56\x4a\x79\xae\x1e\xbe\x1e\x2c\xe4\x9f\xf5\x78\x39\xe7\xc4\x3f\xa6\x0b\x16\x03\xd1\x5a\x45\x08\x98\xaa\x4e\x4a\x1e\xe8\x06\x57\x94\x12\x6d\x64\xf0\x13\x36\x70\x96\xa8\x36\x86\xb9\xf1\x58\xc3\x3b\x10\xf5\xf3\xb3\x6c\xf1\xf6\x35\x8b\x3f\x34\xf8\x4b\x10\x1d\xc2\x6d\x3d\xb6\x8b\xcc\x95\xc8", b"\x05\x9b\xee\x9e\x70\x8b\x7f\x20\xc3\xf7\x91\xa6\x40\xed\xee\x96\x4e\x0a\xa6\x72\x89\x3c\x48\x47\x99\x71\x58\x17\xb3\xa8\xf6\xd4", b"\x4b\xd4\x1c\x84\xa7\x24\xcc\x86\xe4\xf0\x19\x4e\xc0\xfb\xf3\x79\xe6\x54\xd0\xd7\xf6\xa1\xf0\x8b\xd4\x68\x13\x94\x22\xa5\xc3\x53", )?; test( HashAlgorithm::SHA224, b"\x05\x77\xee\x4a\x9b\x8d\xbe\x3c\x6f\xb9\x72\x51\x74\xe8\x99\x40\xb2\x7e\x8a\x98\x92\x17\xb6\x44\x17\xe6\x6f\x39\x6a\x35\xe5\x82\x4f\x21\xe5\x82\x36\xb2\x79\x10\xa3\xbe\x6b\x57\xd3\x11\xaa\x77\x8b\xef\x63\xdd\x02\x5d\x94\x35\x30\x1a\xef\xc9\x22\x23\xc1\xaa\xbb\x03\xd3\xd5\xd3\x85\xb1\xa3\xd1\xf9\x37\xf0\xf1\xf7\xf8\xba\xba\x91\xa0\x11\x20\x74\x80\xb5\xc2\x3a\x78\xeb\xae\xa6\x9a\xe8\xad\x43\x73\xb2\xb0\x52\xd6\x0c\x54\x61\x11\x14\x79\x59\x1f\x83\x30\x12\x3b\xf7\x43\x70\xfb\xa6\x6b\xc7\xe2\xb4\x00\x19\x2c\x47", p, q, g, b"\xc5\x4a\x57\xb0\x8f\x25\x5d\xb1\xc7\x76\xbb\x21\x26\xea\x3c\x1e\x60\x22\x9f\x1e\x19\x81\xe4\x3f\x1d\x6b\x91\x10\xf9\x50\xed\xd8\x24\x5e\xec\xa7\xd5\x5b\xa0\x64\x68\x04\x08\x55\xb7\x36\xdb\x50\x2f\x01\xd6\xb3\xcb\x2d\x9d\x62\x1c\x4d\xb4\x4c\xf8\xcb\x39\x0a\xb2\xae\x33\x2b\xca\x21\x9e\x09\xbb\xbb\xc2\x25\x54\x1d\x4a\x0e\xc0\xb4\xf1\x1a\x59\x1c\x07\x7f\x23\x82\xf0\x4b\xd9\x3b\x36\x4c\x94\xfb\x1c\x61\x47\xff\x77\x84\xe8\x25\x58\xe5\xfb\x68\x42\x74\x59\xfa\x9a\x69\xd7\x8a\x9f\x60\x51\xbd\x94\x31\x88\x7a\xce\x46\xfa\x49\x70\xf0\xe2\x2d\x75\xd2\xbe\xfa\x5a\x22\x8e\x48\x9e\x00\x9a\xf9\x7c\xe9\x21\x14\x08\xb4\xe5\xbf\xe3\x7d\x3e\x07\x00\xb2\x58\xb5\x41\x74\xa5\x12\x5e\xb6\xbb\xec\xa3\x88\x05\xda\x53\xb1\xf5\x82\x9d\xfd\xec\x8c\x4c\x93\x76\xbf\x23\x5b\x7b\x0e\xb7\x11\x9d\x3d\x69\x76\x8b\x80\xee\x02\x23\x45\x89\xb8\xd9\x5f\xaf\x80\x62\xa8\xe1\xe9\xc3\xa6\x86\xb6\x35\x0e\x30\xfa\x53\x5e\xaa\xe7\x1d\x75\x3b\x7c\x3b\x04\x8f\x8e\x97\x22\x25\x4d\xed\xbc\x22\x0a\xc9\xc9\xaf\x07\x84\x53\x20\x32\xab\x65\xe4\x8c\xcf\xcf\xd6\x23", b"\x33\xc1\x98\xea\x68\xbe\xc4\xa7\xfe\xda\xf0\x30\x9c\x31\x7d\x33\x6b\x97\xd1\xeb\x1f\x1d\xc4\x4e\xba\xf5\xc8\x5c\x5a\x3a\xfa\x98", b"\x5c\x9b\x23\xc1\x3b\xb6\x07\xbe\x54\x73\xb3\x2a\xe2\xb5\xe8\xf2\xa1\xe1\x8f\x59\xdf\x8c\xa7\xfd\x93\x03\xf7\x6e\xd8\xe6\x80\xe3", )?; test( HashAlgorithm::SHA224, b"\xc6\x43\x69\x5d\x29\xb2\x82\x10\x01\x7a\xa5\xa7\xd1\x6e\xbe\xd8\x1b\xa0\x0a\x86\x9d\x66\x81\xd1\xc0\xfe\x90\xa5\xe8\xbe\x9d\x59\x73\x29\xea\x15\xd2\x4b\xa1\x2d\x77\xe4\xc3\xf2\x16\x0b\xcb\xe8\x08\x84\x0c\x6e\x77\xb0\x52\x8b\xf9\xae\x58\x87\x38\xe2\x2f\x41\x91\x0a\x80\xa7\xc6\xe3\x34\x0c\x12\x7b\x9d\xe1\x79\x45\xe7\xf9\x22\x99\x53\xe2\x85\x02\x17\xb6\xd4\x86\xf7\xcc\x80\x4e\x72\x0d\xe2\x14\xce\xf0\x2d\xf4\xa8\x92\xf7\xe4\x28\x98\xf1\x5c\xaa\xd2\x6b\xb3\x0b\xfa\xf4\xb0\x55\x1a\xee\xa1\x40\x35\xcb\x75\x6b\x11", p, q, g, b"\x17\xff\x2a\x5e\xff\x39\x26\xee\x15\x20\xd5\xa6\x3a\x13\xb4\xf7\x01\xdc\xee\xd2\x5a\x65\x39\x66\xf5\x25\x45\x0b\x3a\x63\xb0\x32\x29\xd6\x15\xec\x54\xcf\x4f\x6d\xdb\x86\x8b\x54\xdf\x36\x3f\xee\xcc\x95\xeb\x8a\x3a\xb2\x58\x7f\xc4\xde\x9c\x93\xdc\x8f\x8d\x7f\x38\xf9\x90\x82\xd2\x86\x7b\x23\xd0\x73\x58\x4c\x83\x1b\xaa\x09\x61\x65\x1e\x07\x1b\x43\xf9\xd5\xda\x97\xb6\x0e\x7b\x5b\x7a\x93\x5f\x6c\x1d\xc8\x82\x79\x60\x8e\x2b\xec\x5c\xac\x61\x62\x48\x80\x85\xd0\x92\xa9\x7c\x6b\x6f\x24\x53\x65\x89\xb8\x01\xb6\xb4\x8d\x47\x87\x96\xb5\x2c\x05\x56\x4e\x90\x4b\xc5\x8a\xc1\x50\x50\x74\xdb\x37\x34\xfc\xf3\x57\x5f\x79\x95\x2b\xa0\xa2\xa0\x69\x7e\x55\xe5\x79\xd5\x08\xa4\x00\xeb\xfb\x2d\x46\x94\xb7\x20\x80\x4a\x9d\x00\xf8\x84\x5e\xf0\xa8\xe6\x90\xe6\x75\xb4\xc1\xce\x07\x99\x6d\x64\xe6\x66\xb0\xd6\xa1\xd6\xfc\x6b\xbc\x3c\xd9\xb5\xcc\x38\x64\xe5\xe8\x88\xe3\xc3\x35\xe0\x5e\x83\xc6\x7c\x00\x33\xba\x5e\xfc\x3d\xcd\xec\x04\x46\xd3\xb4\x07\x93\x23\x6c\xa0\x74\xc5\x4d\x2a\x74\xda\xd2\x96\xd7\xc6\x39\xde\xc9\x38\xe3\xbf\x1c\xa0\x85\xdc", b"\x4d\xdd\x2a\x1f\x41\x1b\x57\x0f\xef\x6d\x91\x84\x40\x9b\x4f\xd5\x5d\x12\xc5\xe4\xbd\xdc\x2a\xc7\x21\x12\x35\x87\x33\x22\x15\x5d", b"\x40\x43\x95\x2c\x10\x8e\xf8\x4a\x25\xa1\x68\xea\x5b\x64\xa4\x38\x6f\x7a\x48\x33\x66\x05\x4c\x5d\xfb\xfc\x5f\xa9\x85\x79\x43\x2a", )?; test( HashAlgorithm::SHA224, b"\x2f\x64\xd1\x1e\x29\x02\x75\x98\x7b\x7d\x74\x30\x24\x22\x89\xaf\xd5\x4f\x1b\xe0\x28\xcf\x36\xf8\xf5\x5d\xb5\x4b\xe7\x0b\x8d\xd5\xad\x74\xae\x26\xe0\x79\xd0\xed\x31\xa3\x61\xc1\x16\x95\x1b\xde\x94\xd6\x86\xab\xf1\x5a\xc5\xed\x14\x70\xc3\xe9\x02\x46\x1c\xea\x8e\x5d\x58\xf4\x07\xd2\xe0\xc0\x72\xee\x61\x56\x7d\xa7\xb3\x53\xf6\xc4\x7e\x69\x4c\xd6\x07\xf3\xae\x89\x4a\x97\x05\xe8\xea\x2b\xf9\xce\xec\x3a\xcf\xa6\xd2\x0b\x23\x8b\xf0\xa7\xa7\xea\xc7\x6c\x44\x62\xb7\xe4\xe4\xe8\x68\x17\x4a\x88\xa6\xa6\xc9\x47\x6c\xdf", p, q, g, b"\x41\xcd\xb2\xc1\xbd\xfa\x36\x52\xee\x49\x69\x5d\x5e\x5e\xee\xc0\x0f\x64\xb5\x4b\x56\x76\xee\x27\xf0\x43\xb4\x3f\x24\x13\x3f\x61\x42\x5b\x0c\xeb\xaa\x1f\x88\xda\x07\x2c\xc6\x88\x65\xc1\x27\x90\xc4\x32\x85\xb7\xe1\x9c\x38\x44\xfc\x7d\x81\xd0\x64\x42\x3f\xf1\xe1\x92\x66\xf6\x9f\x7d\xcb\x3d\x02\x03\x73\x9f\x84\xd7\x3b\xf0\x0c\x52\xd6\x0b\x28\x75\x17\x12\x16\x67\x8d\x59\xfb\x55\x75\x53\xed\xc9\xeb\xa6\xb8\x41\x27\x16\x9f\xe5\xdd\x2f\x81\xfc\x90\x2c\x97\x0d\x1d\x8d\x9c\x47\x79\xdf\xa1\xb1\x43\x09\xf8\x10\x06\xee\x64\x17\x76\xa6\xfa\x36\x33\x9e\x96\x31\x17\x44\x7a\xce\xb8\x23\xc9\xca\x33\x67\x17\x2e\xdd\xaf\x6e\x36\x18\x29\xda\xe4\x3c\x40\x38\xcd\xb9\x0e\xbb\x68\xb5\x3c\x0a\x22\xd4\x10\xb6\xf1\xbf\xa7\xc4\x74\x96\xea\x3a\xed\xdc\x36\xbf\x24\xf2\x19\xb8\x59\x17\xa2\x4d\x30\x84\x7c\x77\xd8\x7d\x22\xa7\xf7\x48\x6c\x66\x84\x75\x5e\x04\x5d\xdf\x72\xd4\x16\x50\xe9\x7b\x64\xa6\x4b\xec\xad\xfc\x47\xd5\x35\x55\x12\x7f\x8b\x7a\xb7\x8d\x48\x05\x29\x57\x19\x96\xee\xde\x46\x18\x88\x2d\x83\x8b\xd6\x95\xef\xc8\x7e\x74\xd6\x8c\xa5", b"\x4f\xe6\xe2\xa7\x5d\x9c\x72\xe8\x1a\xc6\x0d\xd3\x3d\x31\x18\x0d\xf8\x29\xb3\x1a\x0d\xbd\x5f\xd2\x0b\x7e\x28\xc4\xfe\xe2\x7d\x5b", b"\x3c\xe4\xa0\x6b\xfa\xf7\x0c\xb6\xcc\x93\xf3\x3f\x95\xa4\x3a\xd7\x7e\xd7\xad\x7c\x77\xa1\x67\x4b\xf8\x49\xe9\xeb\xbc\x5e\xda\x29", )?; test( HashAlgorithm::SHA224, b"\x17\x3c\x4a\x23\x62\x1c\x32\xc3\xe4\xb1\x57\xef\x96\xb0\x2f\xc1\xbb\x46\x6a\x25\x37\xd3\xf6\xe5\x1a\x58\xe5\x10\xc4\xae\xf3\xaa\xe4\xbc\xe4\xc0\xb4\xd5\x9b\xb1\xc0\x0e\x7a\x35\xf9\x89\x45\xca\x9d\x7f\xdf\x1f\x0b\xac\x73\x2d\x42\x50\x43\x06\x2b\xc6\xd3\x20\x15\x23\x3d\xfb\x29\x5a\xe0\x8a\x32\x4a\xc7\xc1\xe0\x2a\x11\x7c\xe4\x36\xd7\x7d\x4e\x46\xd0\xb7\x94\xaf\x04\xb1\xdb\x82\xa2\x70\x9d\xa1\xc4\x44\x9c\x29\xcc\xba\x93\xdb\x8e\xc4\x8e\xb1\x79\x21\xcb\x38\x9f\x6e\x0a\xe3\x29\x95\xd7\xfe\xe1\xfa\x07\x17\x7a\x7a", p, q, g, b"\x67\x3e\x34\x9c\xf6\xd0\x5c\xaa\x16\x75\x1d\x97\xba\x6e\x34\x4e\x40\xe1\x58\xe6\xa7\xfc\x53\xea\x2d\xb8\x78\x91\x34\x1e\x64\x99\x82\x5b\x5b\x9e\xdb\xce\x91\x90\xbd\x87\xc3\xea\xdf\x7c\x6d\x5b\xf0\xa7\x93\xaf\x2c\x3a\x1c\x8d\xed\x79\x0b\xc3\x19\x44\x93\x94\xc6\x43\x84\x30\x58\x64\x72\x3a\x8a\x7b\xfe\xf2\x6c\x08\x20\x30\xab\x36\x0b\xf9\xab\xb1\x11\x17\xe6\x1b\x00\x54\x97\x26\xd7\x72\x22\x1f\x6f\x67\xc4\xa6\xa1\x10\xcd\x9a\x96\x58\x78\x1e\xa8\xf7\xef\x2f\x17\x6c\x6e\x88\x16\xa8\x65\xaf\x39\x6d\xb9\x5d\x84\x15\xb5\x41\xcf\x0f\x83\xe4\x5a\x41\x73\x74\xcf\x3a\xcf\x5c\x6b\x4a\x98\x39\x05\x22\xe7\x14\x0c\xc8\xaa\x3f\x9d\x2d\xd2\x63\x41\xd4\xeb\x79\xe4\xd9\x31\xa1\x78\xe3\xd5\x7d\xc5\x2b\xfd\xf9\x01\x15\xe0\x1b\x76\x09\x4a\xd0\x29\x49\x79\xd3\x5d\x92\xb5\x74\xce\x7b\x0c\x62\x7f\x08\xbe\x66\xf9\x9e\xff\xad\xc3\x3a\xed\x0f\x63\x4f\x6a\x89\x50\x74\x55\xd7\x34\x1e\xe6\x41\x83\xaa\x61\x0d\x8b\xb3\x23\x71\x47\xbd\x90\xdc\xd9\xc1\xa0\x3d\x89\xb2\x6e\xe3\x1d\xbe\xf5\xae\x7e\x76\x4b\xa9\xf7\x7b\x6a\x74\x34\xad\x2a\x8f\x96\x6c", b"\x39\x3d\x68\x1c\x3e\xdb\xa2\x8f\x7c\xb0\xf3\x05\x93\xb9\x4f\xc1\x5c\xca\x65\x9a\x80\xcf\xbc\xb3\xb2\x36\x45\x37\x22\xd5\xb4\x02", b"\x44\xf7\x42\x1b\xce\x1e\x52\x73\xa3\x0e\xc0\x16\xbb\x99\x69\xb7\x57\x19\x79\x87\x54\x8e\x43\x4e\x39\x5a\xb3\xde\x1b\x0e\x7b\xa2", )?; test( HashAlgorithm::SHA224, b"\x7d\x6f\x2a\x97\xe1\xeb\x08\x5c\xb9\xe8\x3a\xa2\x40\x47\xaf\x9b\xa3\x0a\x05\xd7\xba\xb5\x64\xa1\x49\xb9\xcd\x23\x66\x51\x8e\x8f\x19\x91\x34\xfc\x2c\xa4\x03\x94\x7f\x2a\x61\x4c\x03\x63\xed\x4b\xc1\x34\x9d\xc4\x96\xa8\xec\x74\xd8\x80\x57\x84\x75\xe4\x74\x27\x62\x8b\xb0\x23\xf0\x27\x22\x08\x87\x6a\x3a\x73\x33\x30\x7a\x59\x6c\x15\x8e\xba\x64\xce\x42\xa3\xc7\x90\xe7\x16\x7b\xa4\xa3\x27\xac\x71\xaa\xba\xd2\xf3\x63\x41\xed\xea\x12\xce\x5b\x2b\x73\x58\x07\xb3\x4b\x71\x4a\x49\xa0\xaa\x47\x68\x93\x57\x8f\x06\x45\xdb", p, q, g, b"\x77\x7c\x25\x10\x67\xc8\xab\x16\xcc\xe2\xc4\xa4\xd7\x84\xc7\xe8\x06\xfd\x29\x6c\xbb\xba\xb0\x13\x2e\x2a\xb9\x16\x23\xac\xec\xd8\x30\xe7\xcc\x7c\xde\x03\xe5\x44\xb5\x1f\xb1\xd8\xf0\xb2\xee\xc0\x9f\x55\x95\x39\xaa\x9d\x63\xeb\xc0\xc1\xe3\x25\x79\xf0\x95\x47\x3d\x12\x71\x7c\xe8\x8f\x66\x71\xec\x7e\x3d\x25\x81\xf6\x1b\xfd\xe6\x6c\xf9\xbe\x21\x6d\x6a\x20\x80\x86\xcd\x7b\xea\x77\x01\x50\xa9\xbb\x0a\x5a\x7a\x0d\xac\xe8\x2b\x46\x41\x80\x24\x12\x02\xa3\x0b\x26\xad\x5f\xb9\x33\xc8\x23\x5a\xc2\x91\x8e\x29\xbc\x53\xa5\xc0\x1e\xbc\x1e\x30\xb1\xb4\x6e\x37\x12\x4a\xec\x59\x6f\x8d\x1a\x73\xba\xea\xe5\x88\xce\x7d\x4a\xef\x1a\xe8\x4e\x9a\x97\x66\xc2\x43\x67\x32\x1c\x04\x7c\x3c\xab\xa6\x29\xf5\xd9\x18\x5f\x0f\xfb\x3a\xf7\xe5\x0e\xeb\xd1\xba\x0e\xb7\x7e\xb1\x21\xb9\x80\x73\x79\x4c\xbc\x66\x22\xb6\x78\x26\x2e\xd3\xe2\x29\xc6\xce\xeb\x60\x72\x74\xce\x34\x96\xf3\x70\xb4\x82\xbf\x8f\x68\xc2\x73\x66\x81\x84\x86\xb7\x2a\xdf\xc8\x10\xb2\xf5\x79\x77\x9a\xdc\x9c\x25\x00\x2e\x27\x76\x41\xdd\x9f\xfb\xc5\xdb\x52\x39\xf6\x77\xba\x1a\x9c\x1d", b"\x46\x3b\x1f\xd6\xef\x29\x86\xf7\x5f\x96\x20\x77\x9b\xb6\xf4\x7e\x0b\xea\xfa\x93\x40\xe3\xe5\xee\x58\x9d\x92\x42\x8a\xcd\x4f\x2c", b"\x27\xed\xd3\x39\x17\xe4\x9b\xf7\x71\xf3\xfa\x13\x55\xcd\x39\x28\xd0\xbd\x40\x1a\xa7\xbf\x05\x41\xf3\xaf\x16\x43\xef\xd7\xb6\x77", )?; test( HashAlgorithm::SHA224, b"\x7f\x87\x85\xe1\xc4\xf8\x2b\xc0\xbb\x75\xf7\x8d\x8c\x41\x13\xe0\x88\x7e\x76\x1a\x86\xb4\x8d\xfa\x43\xa3\x68\x3b\x2b\xb8\x86\xba\x53\xf5\x60\x3c\x8d\x94\xa0\x52\xaf\x36\x71\xc5\xc1\xe7\xc2\x32\x90\x8e\x10\xfa\xa6\xcd\x54\xef\xc7\x9c\xcf\xd6\x48\x11\x13\x1a\xcd\x7d\x60\xa9\x30\x97\x29\x45\x5a\xa7\x04\x43\xae\x8f\x32\xa3\x45\x80\xf9\xa1\xaa\x7d\x89\xe5\xfa\x8c\xd4\xe9\x58\x09\xa5\x73\xec\x6d\xfe\x9f\xe3\x5b\x11\x30\x57\x19\x82\xa0\xdd\x46\xee\xeb\xb6\xa1\x6f\x85\xee\x63\x14\x93\x18\x39\xe3\xa4\xc2\x9d\xc7\x00", p, q, g, b"\x28\xc0\x6e\x5a\xb3\xc8\x60\xbe\x8c\x13\xf7\x4f\x28\xb5\x79\x2b\x39\x48\x7b\x79\x54\x7f\x4a\xfa\xf6\xf7\x7a\x5c\x3a\x43\xe8\x81\x32\xed\xf9\x44\xee\x00\x15\x0a\x78\xb5\x8a\x78\xcf\x92\xed\x94\x15\x78\xec\x67\x9e\x10\x67\x67\x01\x4e\x5b\x27\x9c\x0e\xae\x9c\x40\x8e\x6e\xd6\x06\x87\xee\x14\x64\x98\x8e\xa5\x45\xf5\x5b\xe3\x67\x3e\xcd\xa1\x0c\x63\xfb\x0b\x19\x08\xe7\x96\xd6\x71\x5a\xbd\x54\x51\x84\x3d\xa6\xe6\x3b\xf8\x80\x2c\xca\xda\x32\xc7\xc5\x34\x23\x74\xab\x26\xee\x70\x1f\x9d\xb3\xd3\x4f\xc9\x6d\xe9\xd2\x30\x21\xb9\x8a\x93\xdf\x68\x77\xf8\x4f\xad\x67\x41\x16\x40\x55\x69\x6f\x3b\x72\x05\x03\x43\xea\x3e\x5c\xca\x01\xa3\xd5\x7e\x29\x72\x7e\xbc\xf8\x58\x31\x18\x14\x6c\x27\xf4\x2a\xda\xf6\x23\x65\xb9\x69\x7c\xf0\x3b\xdd\xc6\x9d\x0b\xd1\x51\xf7\x15\xb2\x3b\xfa\xaa\x27\xa3\x68\x11\x4b\x3d\xfb\x54\xc0\x84\xe0\x6d\x43\x43\xff\xde\x1c\xd2\x20\x58\xe9\x62\x3a\x70\xe9\x94\x2e\x09\x0e\xdc\x73\xdb\x06\xdd\x31\x80\xbb\x96\x0f\x0d\x7f\xed\x00\x5b\x14\x9b\x69\xd6\xd4\x5f\x40\x36\x8f\xc2\x5a\xe0\x43\x21\xed\xa4\x6d\x52\xa5\x92", b"\x31\x65\xb1\xcf\x3c\xa9\xbb\x89\x15\x4a\xd6\x84\xe0\x89\x36\x4f\x91\xb6\xe5\xd5\x94\x52\x60\x72\xf7\xb9\xdb\x3b\x23\x58\xe7\x11", b"\x49\xe1\xc8\xc3\x47\x24\xac\x55\x32\xff\xf1\xc7\xd2\x43\xb4\x86\xa2\xcd\xc0\x87\x2a\xb8\x4f\xda\x6c\xf2\xba\x96\xf9\x58\xf4\x6a", )?; test( HashAlgorithm::SHA224, b"\x3e\x17\xea\x8b\x9f\xeb\x2f\x4e\x55\xc1\x03\xe5\x8c\x4e\xad\x96\xb5\xcb\x89\x2d\x09\x82\xab\x2b\x0c\xb1\xee\xb9\xe1\xdd\xde\x99\x90\x23\x3a\x22\x58\x84\x73\x42\x1a\xad\xf5\x27\x67\xa8\xdf\x52\x4b\xc6\xe6\xed\x85\x7a\x9f\xd5\x94\x2e\xf9\x76\xb1\xfd\x8b\xca\xd3\x1e\x40\x3b\x1f\xeb\xb8\x65\xd2\x87\x2a\x7b\x34\xec\xdb\xab\x8b\x24\x5a\xda\x45\x24\x3a\x49\xc7\xbe\x67\xaa\x09\x78\x80\x29\x77\x9d\x61\x9d\xe3\x0d\xea\xd9\xf7\xd8\xc9\xc4\x21\x53\xb8\x65\xb1\xa9\xe8\x11\x80\x38\x0e\x27\xa3\x05\xa6\x39\x2f\x4b\x2a\x0b", p, q, g, b"\xb7\x1d\x0a\xb2\xd4\x05\xa5\x01\x2d\x69\x4e\x0a\x4a\x82\x76\x92\x56\xcb\xdb\x49\xc1\x81\x12\xef\xee\x81\x53\xc8\xe8\x16\x31\x04\x86\xa1\x7b\xce\x19\x74\x8b\x11\xf3\xd5\xd1\x8c\xb4\x49\x98\xeb\x32\x9b\x95\x1c\x23\xa5\x7c\xac\x47\xec\x99\x73\x83\x9b\x13\x0f\x3a\x98\x0e\x62\x70\x5c\x07\x02\xe4\xd6\x84\x25\x84\x5d\x54\xe1\x52\xe2\xe8\x36\x46\xb5\x6a\x67\x57\xcd\xe0\x6f\x85\xba\x37\x79\xee\xa5\x85\xdf\xe8\x30\x2f\x12\xae\x77\xfa\x58\xcb\xc6\xdc\xca\x70\xb4\x61\x02\x4b\x7d\x17\x65\x10\xa3\x93\xec\x02\x7c\x76\x9c\xfe\x49\xb6\x98\xe5\x75\xfc\xf9\x9c\x60\x29\x3a\xf2\xad\xe3\xdc\x4d\xf2\x3f\xf3\x38\x6f\x13\x77\x73\x06\xc5\x2d\xe9\x7e\xd1\xa8\x86\xb8\x24\x78\x88\x63\xff\x72\x63\xbb\xbb\x5b\x5f\xa0\xd4\x68\x1c\x16\x94\x22\x72\xf5\xe4\x41\xbd\xf4\x9e\xec\x75\x56\xc1\xfd\x40\x9c\x78\xe3\xaa\xff\xeb\x95\xc1\x26\x7d\xee\x12\xc2\x4c\x04\x5e\xf6\x7a\xa7\x0e\x9a\x3d\x92\x44\xf2\xcf\x1a\xc6\x8c\xd9\x18\xdf\x5f\x62\xa3\xdd\x3d\xe7\xbc\xde\xaa\x3f\x61\xde\x51\xcc\x01\xaf\x63\x6b\xd6\x65\xc0\x09\x9d\x13\x93\x8e\xb4\xfc\x28\x9b\x42", b"\x11\xb7\xec\xfe\xb3\x39\xd6\x01\x49\x48\xde\x5a\xd4\xc9\x6f\x4b\xa5\x17\xa2\xcd\xdc\xa6\x11\xc8\x88\x7f\xc4\x4f\x14\xac\x9a\x63", b"\x13\x28\x7a\x22\xcf\xfd\x82\x53\x02\xb0\xfd\xc0\x95\x54\x58\xd9\x18\x72\x70\x92\xc7\xbf\xb3\xec\x4c\x3d\x7a\x83\x8e\xa6\xc4\x91", )?; test( HashAlgorithm::SHA224, b"\xc3\xe1\x90\x3c\xec\xcb\x2a\xf5\xb0\xdc\x6b\x1f\xba\xaf\x1b\x2e\x96\x47\x7e\x00\x1c\x43\xee\xe3\x04\x6e\xed\x06\x12\x8c\x4c\x81\xeb\x2b\xc9\x17\xaa\x8a\xc3\x0d\x07\xe6\x6c\x9a\x94\x69\x51\x8e\x3c\xab\xc2\x64\xd6\x93\x6e\x5d\x72\x4a\x61\x3b\xf9\xa4\x4d\x60\x79\x7b\x89\x0c\xc5\xce\x0d\x04\x62\x9e\x5f\xaa\x1d\xd5\x3e\x7a\x12\x5a\x14\xa2\x6d\xf3\xcd\xd9\x87\x8d\x9c\x67\xe7\xe1\x8a\x46\x55\xa1\x88\x88\x53\x63\xdd\xab\xd7\x3a\x17\x65\x9d\x19\x1e\x51\xfa\xfb\x6d\x41\x71\xff\x6c\x4b\x65\x11\x68\xce\x16\x7a\xda\x01", p, q, g, b"\x42\x9e\x6b\xa2\x0b\x02\xcd\x69\xa2\x9b\x4a\x97\xa6\xea\x56\x4e\x5b\x88\x74\xad\xa1\x95\xa4\x9c\x3a\x52\x93\xc9\xbc\x8d\x19\xe0\xa3\xa3\xc4\xac\x85\x47\xbf\xdc\x7a\x20\x9b\xf3\xa6\x03\x7e\x5b\x0b\xb7\xaa\x29\x1d\x59\x40\xd2\x35\xc7\x87\xa2\xaf\x79\xa9\xcd\x7f\x83\x08\x4b\xa7\xdf\x85\xc0\x36\xad\x8e\xa2\x3c\x4f\xdb\xf9\x1d\x28\x5c\x7c\xaa\x64\x97\xaf\x38\x80\x17\xbd\x58\x1f\xf3\x08\xd9\xb5\x67\x99\x02\x9e\x21\x40\x0c\x0c\x99\xd1\x03\xa2\xca\xec\x19\x5e\x40\xc9\x0d\x24\x4d\xac\x89\x7b\xd4\x18\xae\x01\x6d\x25\xf7\x1e\x98\x9a\xf5\x16\xd5\xe2\x49\x1e\x1e\x4b\xc2\x59\x14\xec\x3a\xd0\xa9\xf8\x59\x68\xa6\x77\x7f\xbe\xbd\xc7\x3b\x1a\xc6\x81\x44\x96\xd9\x42\x1d\x2b\x7c\xdf\x17\xd5\x3f\x00\x62\x40\x10\xed\x66\x18\xf1\x25\x8d\xa1\x94\xf7\x7c\x28\x28\x62\x25\xd1\xb1\x6d\xa3\xfa\xb7\x6c\x9b\x70\xdb\x1f\x7d\xbc\xba\xcf\x4e\x60\xb6\xb9\x1a\x1f\x47\x50\x07\xee\x4d\x2c\x5e\x37\xfc\x31\xe8\x9a\x0f\xa8\x08\xf8\x9e\x8a\x4e\x54\x6b\xc9\x0e\x69\x6f\x45\x47\x21\xbe\x71\xc0\x73\x1f\x99\xee\x36\x8a\xfc\x69\x98\x76\x1a\xf9\xdd\x9d\x6d", b"\x77\x47\x0f\x0d\x39\x23\xff\x40\x7e\x71\xa8\x6f\x03\x36\x81\x1b\xdd\x63\xe1\x79\x89\x1f\xd3\x0e\x34\x52\xda\xc1\xe5\x17\x50\x81", b"\x4b\x96\x9f\x77\xc7\x0b\x5e\x6f\xf9\x35\x0c\xa2\x5e\x7d\x95\x1a\xca\xae\xe9\x07\xfa\x7b\x83\x0a\x32\xdc\xe4\xf9\x1a\x89\xaf\xa4", )?; test( HashAlgorithm::SHA224, b"\x4b\x7c\x08\x28\xb7\x15\xec\x2d\xa1\xe0\x92\x20\x4f\x55\xdd\xd6\x5d\x13\xf1\xcd\xd6\x4c\x10\x94\x78\xd3\x84\x74\x87\xbc\x48\xa8\xcb\x02\x99\x22\x2a\x74\x95\xef\xff\xa6\x3e\xa1\x58\x25\x3f\xae\xdc\xb5\x31\x48\x81\xab\x41\xb5\xe7\x73\x33\x76\x62\xcc\x2f\x50\xdb\xcc\xc7\x36\x97\x4e\x31\xb3\xd0\x80\x46\x75\x89\x95\x1d\x51\x10\x32\xe4\xcb\xa6\x64\x7f\x94\xc6\x79\xaa\x26\x9f\xca\x6d\xb9\x27\x15\xa4\xda\x28\xff\x98\x03\xa1\xdc\x61\x67\x5f\xa5\xac\x11\x4e\x37\x6f\xa4\xda\xdb\x37\xc1\xb0\x9e\xd5\xc3\x1b\xc5\xae\xe8", p, q, g, b"\x09\xa1\x6e\x0a\x60\x03\xf4\x5a\xaa\xa3\xc6\x31\x1a\xa9\x86\x62\x17\xd4\xa7\xc8\xcb\x50\x93\x51\x49\x76\xf6\xa3\x41\x26\x0e\x5a\xba\x7c\xb0\x0a\xb2\xad\xb7\x46\x2a\x47\xa8\xcf\xee\x4f\xdc\xae\x5a\xcc\xda\x6d\x42\xa3\x14\x47\x92\xa1\x46\x31\xbb\xe8\x55\x34\xc1\x11\xd2\xff\xcd\xbc\x15\xb6\xdb\x9d\xbf\xc4\xbc\x71\xd3\x00\x32\x4f\xd3\x10\xc4\x65\x44\x3c\xb2\xa6\xf2\xae\x33\x70\x1f\x39\x66\x8b\x11\x8c\x38\xef\x56\x2e\x85\x54\xfe\xa6\x61\xa3\xef\x80\x45\x56\x99\xc2\x34\x30\xd2\x8b\xa6\xdc\xf0\x42\xfc\x92\x0a\x67\x7c\x29\x71\xb2\xdf\x8c\x67\x29\xc5\xb3\xb1\xbe\x6c\x5a\x04\x7a\xc1\xbc\xc8\xcd\x8d\xc5\x19\xad\xa2\x21\xbd\x92\xca\x68\x93\xc1\xcc\x1d\xc1\x58\xf9\xd4\x72\xf8\x9a\x8e\x02\x64\x94\x40\xdd\xed\x0f\x72\x34\x85\x55\x8e\xff\xe8\xcf\x9d\xf1\x21\xc9\x69\xa2\xd1\xb7\x6a\x37\xdc\xbf\xfb\x17\xed\xf3\x12\x1d\x43\x38\xd4\xab\x68\xb1\x54\x22\x6c\x00\x72\xd8\xbd\x51\xf2\x3e\x56\x59\xa2\xaf\xe5\x20\xdd\x5e\x91\x00\x5a\x6f\xc1\x15\x7f\x07\x97\x36\x10\xc5\x57\x78\x24\xbf\x16\x66\xcc\xf8\x51\xd6\x9e\xfd\xe3\x47\xf0\xb9\x96", b"\x1b\x8b\x8d\x67\xb6\x40\xaf\xda\x26\xfb\xe6\x7c\xfd\x4b\xea\x52\x13\x75\x52\x6a\xd5\x8a\x22\xd4\xd9\x7d\x7a\xf1\x34\x38\x4f\x4a", b"\x66\xd6\xc2\x40\x99\x22\x56\xee\xbe\x07\x82\x65\xc3\x02\x9a\x88\xc3\x40\x95\x14\x21\x34\xdf\xc3\x1f\xf0\xa2\xd8\xbb\xd6\x09\xb5", )?; test( HashAlgorithm::SHA224, b"\xba\xea\x89\xdc\xc1\x02\xcd\x64\x91\x35\xd6\x3a\x5f\x52\xdf\x43\x7a\xf7\x84\x0d\x69\x9a\x9d\xaf\x13\x1e\xaa\xc3\x81\x34\x8d\x45\xb4\xe6\x04\x77\xfe\xa8\x88\x03\xfc\xa3\x1b\x54\x82\x9c\x58\x06\xc7\x03\xeb\x8f\xdf\x41\x23\x06\xff\x7a\x79\xb5\x5a\xab\x90\x64\xbc\x37\xcb\x26\xbf\xfa\xa6\x71\xde\xbb\x74\xc2\x28\xba\x2d\x2a\x06\xda\x36\x2f\x61\x3b\x78\xe5\xb1\xf0\xa0\xb5\xc5\xfe\xbf\x6b\xc3\x26\xb0\x21\xbd\x7f\xc7\x04\x71\xb2\x5e\x15\x3e\xa5\x1d\xe1\x01\x0b\x87\x11\x0e\x01\x49\x7a\x7f\x1a\xc3\x9c\xf4\xd4\x24\xc3", p, q, g, b"\xcb\xd4\x65\xce\x9c\x3d\x0a\x13\x7e\xe3\xd5\x82\xa5\x17\x21\x83\xb8\xa6\x3c\xfe\x41\x40\x70\xb2\x47\xda\x36\x74\x56\x20\x3f\x98\x6e\x67\x86\xff\xb8\x3a\xd7\x64\xab\xa3\x09\xc2\xef\x74\x42\xce\x38\x73\x5f\x49\x2c\x0c\xe6\xd9\x2e\xaf\x9a\xe6\xb1\xcc\x87\x3a\xb6\xff\x58\x31\x7c\xd1\x66\xa5\x10\xc3\xff\xd8\xd4\xe6\x00\x88\x25\xb5\x8c\xae\x21\x7f\xa3\x5c\x94\xc9\xbb\xd1\x2a\x4d\x63\x8c\x20\x11\x63\x98\xb2\x1b\x59\x29\xdc\xa1\xd4\x9a\x7b\x74\x89\x70\xe4\x5d\xe0\xd4\x32\xfc\x91\x2f\x76\x19\x91\x37\xf1\xbb\x0c\x0d\x2c\x95\xbd\xcb\xa0\xd3\x03\xec\xdb\xf4\x89\x84\x9b\xe8\xe6\x30\xff\xff\x06\x03\x94\x8c\x87\xa7\xe5\x81\x31\x65\x5c\x9f\x40\x77\x08\xe8\xa9\xd6\x75\xe2\x8e\x9b\x57\x72\x9f\x03\x46\xc0\x28\x7f\x43\xed\x67\xf9\xc0\xc0\xce\x15\x42\x98\x48\x51\xcc\x3b\x52\x1a\xfa\x5b\x9b\x8f\xa5\x36\x80\xbd\xb2\xd7\x3c\x2b\x6b\x09\x0e\xf0\x85\xa7\xe7\xc6\xf7\x6a\x2e\x50\x10\x64\xc8\x52\x59\x1d\xf6\x04\x39\xa9\x6d\xd8\xd6\x63\xb5\x64\xc9\xe5\xc2\x53\xee\x8d\x8e\xe5\x8a\xb2\x7d\x83\x32\x11\x3b\xdd\x51\xd8\xb4\x1a\xc7\x3c\x14\x3a", b"\x76\x89\xb5\x24\x9f\x19\x43\xe6\x85\x09\x51\x06\xd3\xf6\x83\x59\xcd\xb7\x6b\xe5\xd9\xa5\x0e\xbf\xdf\x36\xe7\x31\x57\x5f\x8b\xda", b"\x04\x9d\xa4\x2d\xe5\x1e\x61\x7c\xdc\xde\xf1\x7c\xdf\x60\x59\x34\x5b\x8e\x18\x1b\xac\x64\xc4\x71\x23\xd4\x7b\x5e\xfe\x10\x5e\xbb", )?; test( HashAlgorithm::SHA224, b"\xb1\x30\x37\x68\xbe\x17\x4d\x83\x57\x84\x07\xdd\xe1\xab\x91\xcf\x02\x11\x24\xa3\x4c\x4a\x35\xea\xfa\x45\x12\x70\x7a\x36\x60\xd1\xf8\x84\xfa\x6c\x3d\x7d\xf2\x99\x59\x80\x18\xdc\xa2\x2f\x27\x3f\x60\x2b\xab\x37\x15\x92\xb1\x1f\x45\x74\x88\x57\x41\xab\x3f\xe2\xaf\x5b\x71\x23\x7d\x00\x57\xae\x59\xf3\x7b\x61\xdf\xd1\xad\x5e\xa2\x7c\xf8\xf0\x5f\x5b\x69\xf2\x93\x6e\xc7\x9d\x10\x4f\x4a\x46\xc9\x02\xfb\x67\x90\xdf\xdc\x75\xb9\x76\x8c\xc7\xdf\xba\xe0\x11\xc7\x95\xe6\x46\xf9\xa2\x34\x72\x87\x07\xfb\x11\x2c\x46\x10\x07", p, q, g, b"\x56\x02\xdd\x57\x9f\xbe\x37\xf1\x87\xd4\x9d\x76\xfd\x59\x36\xfc\xde\xf2\x36\x9f\x7a\xf2\x9d\xa4\x3c\x64\x56\xa6\xac\x83\x17\xb3\x9e\x4c\xd6\x79\x14\x3a\x4d\x97\x75\x1b\x80\xce\x1c\xb4\x51\x86\xda\x7b\xee\x99\x1e\x25\xeb\x9a\x1a\xed\x14\x90\xfd\x74\xf6\xab\x50\x79\x40\x82\x1a\x1a\xdf\xbc\x30\xe1\x9a\x93\x3c\xc4\xd2\x17\x69\xcc\xdf\xc5\x7c\x96\xf0\xd2\x19\x44\xf8\xa0\xf1\x31\x62\x6e\xd0\x13\xb3\xe5\xc0\x13\x13\xa1\x75\x6b\x67\xb7\xd2\xa2\x1e\xda\xc4\x86\xcb\xc3\xcd\x1d\x2b\x6f\xcf\x20\xc8\x2d\xd7\x0b\x4f\x72\x92\x9c\x14\x99\xad\x79\x6d\xe8\x94\xdb\x8a\xf1\x03\xd9\xb9\x1c\x25\x73\x70\x73\xd9\xdf\x62\xe6\xb6\x24\xb9\x0f\xb3\x52\xdb\x78\x1c\x7f\x2f\xf8\xd3\xa2\x0a\x70\x63\xfb\x51\x27\x23\x95\xcc\x7d\x35\xef\x79\xc2\x7b\x76\x34\xe3\x9f\x74\xeb\x15\x29\x75\xfd\xf3\xb9\x03\xc2\x39\x90\xee\xde\x8a\xa5\x8d\xf9\xa2\x99\x54\x33\x3a\x3f\x52\x5d\x5b\xaa\xfd\x37\x9d\xd5\x7f\xe3\x96\xa5\x18\x76\xf2\x5d\x9e\x82\x65\xcf\x69\x71\xed\xc6\x27\x8c\xe9\x96\xbd\xee\x20\x68\x83\x44\x8a\xf1\x84\xfa\xe2\x3a\xf2\xa6\x95\x72\xb2\x00\x90", b"\x18\x00\xb6\xbd\x5c\x94\xa0\x31\xd9\x77\xb9\xd0\x17\x54\x17\x90\xa9\xfe\x7e\x41\x4c\x90\xfa\x4d\x38\x03\xd5\x6e\xf1\x6a\x64\x79", b"\x07\xec\xe1\xb6\x47\x11\xc9\xb3\xec\xa4\x89\xe7\x5f\x2e\x63\x43\x8e\x09\x74\x98\xe2\x89\x0d\xd0\x27\x37\x29\xa5\x5d\xf0\xd2\xdf", )?; test( HashAlgorithm::SHA224, b"\x25\xca\x3d\xc8\xe6\xea\x4e\xbb\x93\x6f\xa0\x1b\x1c\xcc\x08\xbb\x1d\xe9\x23\xbe\x62\x92\x42\x1f\xf9\xf7\x73\xaf\x9c\xc7\x39\x35\x10\xdf\x2f\xcb\x6e\xc1\x88\xb2\x7c\x26\x88\xc7\x2f\xdc\x2f\xf6\xc9\x0f\x0a\xb0\xed\x59\xc9\xc3\xa6\x50\x3f\x53\xe3\x27\x78\xb9\x54\xea\xe5\x82\xc9\x58\x03\xc5\x11\xff\x39\x18\xad\xda\x02\xe6\x8e\x2c\x3e\x73\xf8\xa6\xad\x60\x7a\x89\xd8\xeb\xa0\x05\x9e\xb8\x7f\x4d\x9b\x00\x81\xf2\x96\x96\x1e\xc6\xea\x78\x85\x3a\xa5\x3d\x24\xa4\x70\xa7\x4a\xcf\x16\xa2\xf8\x67\x48\xa8\xda\x34\xfb\x90", p, q, g, b"\xbf\x2e\x14\x0f\x8b\x8d\x99\xd2\xdf\x10\x52\xe9\x81\xfa\x0a\xc5\x33\xc0\xd4\xea\x9f\x26\x6f\x92\x67\xcd\xe7\xba\x03\xcf\x10\x01\x5d\xa1\xcc\x13\x61\x2d\xcf\xc9\x20\x30\xb7\xc7\xd1\xc0\x57\xe2\x8a\x6f\xb4\x57\x48\xee\xb9\xc4\xbd\x2e\x6e\x79\xb2\x17\xf4\xb6\x8e\xf0\x3f\x96\x59\xc8\xe8\x4a\x20\xee\x92\x0d\x29\x71\x13\x81\xce\x39\xfe\x0a\xfc\x9a\x7f\xe2\xfb\xdf\xce\x63\x24\x96\x51\x23\x0f\x3e\x72\xee\xd5\x79\xf0\xd3\x65\x9c\x2b\xff\xc7\x0f\xb5\xd8\xbe\x88\x9a\x34\xbb\x67\xf1\xa9\x04\xc3\x18\x56\x83\x94\xb9\x46\xfd\x40\x38\x37\x82\xcb\x5e\x48\x09\xd0\xc6\x01\x9d\x20\xaf\xad\x09\xf2\x9f\xbb\xc9\x94\xd2\x8f\x4e\x41\xda\xf4\x66\x62\x98\xf3\x51\x89\x8d\x8d\xef\x40\x47\x12\xc4\x09\x74\x5a\x88\x96\x2e\x4a\x61\x8c\x23\x49\x76\x64\x55\x59\xc9\x0c\x54\xfe\x76\x4e\xea\x46\xfa\x03\x54\x3e\x4c\x4f\x25\xc8\xd2\xc3\xc1\x97\x9f\x95\x24\x58\x17\x7d\xc6\x96\x3e\x3f\x34\x6a\x7f\xdd\xbe\x0c\xdf\x23\xdd\xc7\xd2\xfa\x8a\x34\x55\xcd\x5b\x54\x6e\x47\x16\x99\x12\xce\x7f\x33\x3a\xc6\xf0\x1e\x64\xae\xc5\x96\x08\x0b\x5d\x3e\x0f\x25\xad\xb9", b"\x7b\x1d\xfc\xf3\x9b\x62\x4d\x64\xdb\x08\xa3\x97\x4c\x8e\x14\x17\x31\x05\x01\x0f\x2b\xd5\x13\x5e\x92\x6f\x28\x84\xe3\x0b\x46\xfa", b"\x69\x7e\xea\xb6\x69\x67\x74\x69\xf6\x2c\xca\x46\xd3\xe6\x8c\x84\x9f\x44\x78\x81\xe2\xc9\xf7\x42\x94\xf4\xe8\xad\xa4\x42\x6c\x7d", )?; test( HashAlgorithm::SHA224, b"\xd5\x8a\x8f\x5a\xb4\x4f\x9d\xf9\xed\x93\x6a\x13\x18\x65\x7c\x32\x4f\xb1\x39\x9c\x25\x10\x54\x98\x6d\x19\x21\x4c\x15\xce\x95\x1f\x87\xcc\xb3\x51\x0a\xed\x90\x85\x41\x1d\x9c\x5a\x67\x40\xdf\x51\x60\xf3\xe5\x7e\xa8\xc9\x42\xd3\x35\x47\x31\x7c\x7a\x38\x7c\x60\xc7\xac\x2f\x0e\x14\x17\x1f\x0b\x77\x19\xab\xa7\x6a\xc4\x18\xd1\x57\xa4\xe3\xbe\xc6\xb7\x99\xb5\xda\x10\xbd\x3e\xcd\xda\xe0\x85\x7a\x29\x67\x0c\x99\xd3\x78\x10\x34\x9b\x82\xb7\xbb\x37\xc0\x93\x7b\x0d\xd2\x73\x4d\xa0\x8b\x8b\x1c\xb7\xbe\xec\xd4\x3c\xb6\x15", p, q, g, b"\xba\xa1\x36\x52\x64\x2d\x95\x0d\x8b\xce\xc1\x6c\x62\x4a\x07\x99\x9f\xb5\x57\xfb\x40\xa2\x66\x29\x7c\x15\x65\x97\x55\xfd\x61\x5c\xc7\xe2\x12\x5d\x4e\x8c\x8a\xf8\xc4\x33\x35\x53\x90\x05\xe9\xe2\xf2\xd4\x04\x28\xe7\xc8\xcc\x05\x5f\xf3\xf6\xfe\x3b\x3d\xf6\x04\xac\x12\x8d\x99\x5c\xfb\x9c\x86\x7e\x2a\x96\x07\xaa\x3b\x77\xcf\x0f\x69\x17\x38\xb7\x84\xd4\xbe\x2f\xea\x47\x39\xfd\xa1\xf0\x67\x42\x60\xf2\x1f\x66\x6a\xce\xf5\xbd\x56\xa7\x80\x0b\xbe\x95\x07\x92\xba\x05\xee\xe4\x2e\x80\xa2\x57\x8d\x2c\x50\xec\x28\xd4\x4a\xfb\x6b\x68\x76\x52\xbb\x94\x52\x40\x8f\xca\xf2\x57\xc4\xb5\xcd\x56\x4d\xdc\x4e\x63\xce\x9c\xa1\x3d\x4c\x74\x73\xf5\x1b\x01\xac\x8e\x4c\x3f\x79\x9a\xfc\x90\x8e\xae\xac\xca\xd0\x62\xb0\xf9\x7d\x95\x8a\x30\x08\xca\xe2\x20\x62\xbb\x16\x6c\x73\x00\xdf\x0b\x43\x86\xba\xec\xd5\x99\xfa\x8b\x08\x3f\xba\x6e\x7e\x4e\x5b\xa1\x19\x86\x02\x68\x51\x7d\x79\xeb\xdc\xbe\x02\x43\x7b\xf4\xeb\x5d\x91\xa8\x43\x72\x5d\xb0\xed\xa6\x6e\xed\xd4\x6d\x66\xb7\x81\xac\xed\x0d\xcc\x23\x15\x4e\x4b\x8a\x8f\x04\x53\xb2\xf4\x66\x03\x3b\xd9", b"\x18\x76\xb2\x09\x26\xd8\xed\xe7\x8d\x28\x17\x4e\xeb\x4c\xb0\xc1\xaf\x8e\xe2\x06\xfc\x8d\xb4\xa8\xcd\xeb\xb5\xdb\xfb\x0c\x15\xcf", b"\x23\x1a\xf0\x7a\xeb\xa9\x9f\xfd\x00\x65\x93\x94\xab\x6e\xd1\x9a\x5e\x9f\x9e\x60\xe2\x19\x7f\x65\xfc\x88\xc8\x15\xbe\xae\x7f\xe0", )?; test( HashAlgorithm::SHA224, b"\xaa\x13\x4e\x9d\xb7\x39\x82\xe7\xa3\x7a\x10\x34\xaa\xb8\x2b\x50\xd5\xe5\x8e\x03\x4a\x56\x37\x08\x1d\xc8\x80\xa6\xe2\x65\xeb\xc7\xb3\x53\xdf\x21\x03\x04\xba\x00\x77\x1c\x5b\xab\x44\x5d\xc6\xc2\x49\x99\xfe\x8e\xaf\xde\xfa\xbc\xdd\x46\xf7\xa9\x1f\x30\x72\x1a\x68\x96\x33\x3c\x3f\x30\x1e\x19\x7f\x96\x19\x44\xf5\x45\xe4\xfe\x07\x30\xcd\x96\x77\x90\x50\x4c\x49\xb0\xab\x5b\x89\x08\x09\xbe\x5c\x7c\x1c\x3f\x8a\x2e\x52\xd9\x2a\x2c\x19\x9b\x98\x1b\x64\x8f\xdd\x52\x8e\x76\x8e\x6a\xb3\x92\x57\x9b\x54\xc7\x2c\x41\x61\x7d", p, q, g, b"\x69\x1d\xfe\xa1\x44\xe5\x1b\x9e\x0f\xf7\x53\x65\x57\xb5\x8a\xce\x87\x16\x26\x3a\x70\x55\x4e\x2f\x46\x76\xd1\x72\x33\x2a\xed\xaa\x67\x73\x6d\x72\x66\x7d\x32\x81\x70\xac\xa0\x70\xe1\xbb\x89\x86\x8b\xf4\xcc\x98\x96\x2d\x87\xeb\x05\x99\xf1\x08\x28\xc6\xea\x24\xcf\xfe\xde\x8e\xd7\xb3\x9a\xbb\xa6\x66\xbd\x6d\x0d\x35\x02\x4a\xde\x6a\xaa\x06\xfe\x6a\xe4\x5d\xc4\xb3\xa9\x1c\x21\x9d\x47\x2d\xb0\xef\xed\x46\x9d\x69\xcb\x5f\x11\xd4\x01\x58\xea\x81\x67\x2b\x1a\xe1\x16\xff\x2c\x30\x16\xf2\x45\x25\x4e\x98\x4a\x59\x94\x5e\x4e\x3b\x3d\x37\xad\x12\x05\x8d\x84\x08\x29\x55\xc7\x68\x64\x3e\x7d\x80\xc0\x55\xc1\x70\x3a\x88\x3f\x2a\xbb\x07\x5a\x24\xc2\xe9\x30\x56\x69\x73\x40\x93\x1c\x25\x89\x4d\x1d\x2f\xfa\xc4\xb1\x02\x20\x12\xc1\x5c\xb7\x07\xfb\x35\x96\x83\xad\x04\x08\xb6\x68\x77\x9e\x9d\x9b\xa2\x19\x89\xba\xa6\xa6\xb0\xb2\x56\xa3\x4e\xfb\x47\x51\xbc\xaf\x42\x85\xb1\x56\x35\xd4\x09\xfd\xa9\x93\xc0\x43\x8a\xcd\xdc\x9d\xa0\x06\xc3\x90\x36\x03\x04\xab\x12\xda\x76\xb4\x44\xd6\x4e\x11\xcc\xf0\x5d\x96\x3f\xfb\x7f\x38\x9b\xee\x83\x1d\xc7", b"\x04\x1d\x22\x93\x49\xce\xc7\x5f\xb2\xbd\x8c\x35\xc2\x49\xf9\x19\x6a\x18\x96\x2c\xa7\x5e\xbd\xb4\x2d\xca\x61\xd2\x1c\xb0\xe9\x10", b"\x77\xbb\x79\x75\xa5\x44\xc5\x1b\xf2\x49\xde\xe2\x35\x95\x23\x07\x28\x63\x93\x44\x97\xd1\xa4\x79\xd6\xe4\xb2\x45\xd4\x56\xeb\x2a", )?; // [mod = L=2048, N=256, SHA-256] let p = b"\xa8\xad\xb6\xc0\xb4\xcf\x95\x88\x01\x2e\x5d\xef\xf1\xa8\x71\xd3\x83\xe0\xe2\xa8\x5b\x5e\x8e\x03\xd8\x14\xfe\x13\xa0\x59\x70\x5e\x66\x32\x30\xa3\x77\xbf\x73\x23\xa8\xfa\x11\x71\x00\x20\x0b\xfd\x5a\xdf\x85\x73\x93\xb0\xbb\xd6\x79\x06\xc0\x81\xe5\x85\x41\x0e\x38\x48\x0e\xad\x51\x68\x4d\xac\x3a\x38\xf7\xb6\x4c\x9e\xb1\x09\xf1\x97\x39\xa4\x51\x7c\xd7\xd5\xd6\x29\x1e\x8a\xf2\x0a\x3f\xbf\x17\x33\x6c\x7b\xf8\x0e\xe7\x18\xee\x08\x7e\x32\x2e\xe4\x10\x47\xda\xbe\xfb\xcc\x34\xd1\x0b\x66\xb6\x44\xdd\xb3\x16\x0a\x28\xc0\x63\x95\x63\xd7\x19\x93\xa2\x65\x43\xea\xdb\x77\x18\xf3\x17\xbf\x5d\x95\x77\xa6\x15\x65\x61\xb0\x82\xa1\x00\x29\xcd\x44\x01\x2b\x18\xde\x68\x44\x50\x9f\xe0\x58\xba\x87\x98\x07\x92\x28\x5f\x27\x50\x96\x9f\xe8\x9c\x2c\xd6\x49\x8d\xb3\x54\x56\x38\xd5\x37\x9d\x12\x5d\xcc\xf6\x4e\x06\xc1\xaf\x33\xa6\x19\x08\x41\xd2\x23\xda\x15\x13\x33\x3a\x7c\x9d\x78\x46\x2a\xba\xab\x31\xb9\xf9\x6d\x5f\x34\x44\x5c\xeb\x63\x09\xf2\xf6\xd2\xc8\xdd\xe0\x64\x41\xe8\x79\x80\xd3\x03\xef\x9a\x1f\xf0\x07\xe8\xbe\x2f\x0b\xe0\x6c\xc1\x5f"; let q = b"\xe7\x1f\x85\x67\x44\x7f\x42\xe7\x5f\x5e\xf8\x5c\xa2\x0f\xe5\x57\xab\x03\x43\xd3\x7e\xd0\x9e\xdc\x3f\x6e\x68\x60\x4d\x6b\x9d\xfb"; let g = b"\x5b\xa2\x4d\xe9\x60\x7b\x89\x98\xe6\x6c\xe6\xc4\xf8\x12\xa3\x14\xc6\x93\x58\x42\xf7\xab\x54\xcd\x82\xb1\x9f\xa1\x04\xab\xfb\x5d\x84\x57\x9a\x62\x3b\x25\x74\xb3\x7d\x22\xcc\xae\x9b\x3e\x41\x5e\x48\xf5\xc0\xf9\xbc\xbd\xff\x80\x71\xd6\x3b\x9b\xb9\x56\xe5\x47\xaf\x3a\x8d\xf9\x9e\x5d\x30\x61\x97\x96\x52\xff\x96\xb7\x65\xcb\x3e\xe4\x93\x64\x35\x44\xc7\x5d\xbe\x5b\xb3\x98\x34\x53\x19\x52\xa0\xfb\x4b\x03\x78\xb3\xfc\xbb\x4c\x8b\x58\x00\xa5\x33\x03\x92\xa2\xa0\x4e\x70\x0b\xb6\xed\x7e\x0b\x85\x79\x5e\xa3\x8b\x1b\x96\x27\x41\xb3\xf3\x3b\x9d\xde\x2f\x4e\xc1\x35\x4f\x09\xe2\xeb\x78\xe9\x5f\x03\x7a\x58\x04\xb6\x17\x16\x59\xf8\x87\x15\xce\x1a\x9b\x0c\xc9\x0c\x27\xf3\x5e\xf2\xf1\x0f\xf0\xc7\xc7\xa2\xbb\x01\x54\xd9\xb8\xeb\xe7\x6a\x3d\x76\x4a\xa8\x79\xaf\x37\x2f\x42\x40\xde\x83\x47\x93\x7e\x5a\x90\xce\xc9\xf4\x1f\xf2\xf2\x6b\x8d\xa9\xa9\x4a\x22\x5d\x1a\x91\x37\x17\xd7\x3f\x10\x39\x7d\x21\x83\xf1\xba\x3b\x7b\x45\xa6\x8f\x1f\xf1\x89\x3c\xaf\x69\xa8\x27\x80\x2f\x7b\x6a\x48\xd5\x1d\xa6\xfb\xef\xb6\x4f\xd9\xa6\xc5\xb7\x5c\x45\x61"; test( HashAlgorithm::SHA256, b"\x4e\x3a\x28\xbc\xf9\x0d\x1d\x2e\x75\xf0\x75\xd9\xfb\xe5\x5b\x36\xc5\x52\x9b\x17\xbc\x3a\x9c\xca\xba\x69\x35\xc9\xe2\x05\x48\x25\x5b\x3d\xfa\xe0\xf9\x1d\xb0\x30\xc1\x2f\x2c\x34\x4b\x3a\x29\xc4\x15\x1c\x5b\x20\x9f\x5e\x31\x9f\xdf\x1c\x23\xb1\x90\xf6\x4f\x1f\xe5\xb3\x30\xcb\x7c\x8f\xa9\x52\xf9\xd9\x0f\x13\xaf\xf1\xcb\x11\xd6\x31\x81\xda\x9e\xfc\x6f\x7e\x15\xbf\xed\x48\x62\xd1\xa6\x2c\x7d\xcf\x3b\xa8\xbf\x1f\xf3\x04\xb1\x02\xb1\xec\x3f\x14\x97\xdd\xdf\x09\x71\x2c\xf3\x23\xf5\x61\x0a\x9d\x10\xc3\xd9\x13\x26\x59", p, q, g, b"\x5a\x55\xdc\xed\xdd\x11\x34\xee\x5f\x11\xed\x85\xde\xb4\xd6\x34\xa3\x64\x3f\x5f\x36\xdc\x3a\x70\x68\x92\x56\x46\x9a\x0b\x65\x1a\xd2\x28\x80\xf1\x4a\xb8\x57\x19\x43\x4f\x9c\x0e\x40\x7e\x60\xea\x42\x0e\x2a\x0c\xd2\x94\x22\xc4\x89\x9c\x41\x63\x59\xdb\xb1\xe5\x92\x45\x6f\x2b\x3c\xce\x23\x32\x59\xc1\x17\x54\x2f\xd0\x5f\x31\xea\x25\xb0\x15\xd9\x12\x1c\x89\x0b\x90\xe0\xba\xd0\x33\xbe\x13\x68\xd2\x29\x98\x5a\xac\x72\x26\xd1\xc8\xc2\xea\xb3\x25\xef\x3b\x2c\xd5\x9d\x3b\x9f\x7d\xe7\xdb\xc9\x4a\xf1\xa9\x33\x9e\xb4\x30\xca\x36\xc2\x6c\x46\xec\xfa\x6c\x54\x81\x71\x14\x96\xf6\x24\xe1\x88\xad\x75\x40\xef\x5d\xf2\x6f\x8e\xfa\xcb\x82\x0b\xd1\x7a\x1f\x61\x8a\xcb\x50\xc9\xbc\x19\x7d\x4c\xb7\xcc\xac\x45\xd8\x24\xa3\xbf\x79\x5c\x23\x4b\x55\x6b\x06\xae\xb9\x29\x17\x34\x53\x25\x20\x84\x00\x3f\x69\xfe\x98\x04\x5f\xe7\x40\x02\xba\x65\x8f\x93\x47\x56\x22\xf7\x67\x91\xd9\xb2\x62\x3d\x1b\x5f\xff\x2c\xc1\x68\x44\x74\x6e\xfd\x2d\x30\xa6\xa8\x13\x4b\xfc\x4c\x8c\xc8\x0a\x46\x10\x79\x01\xfb\x97\x3c\x28\xfc\x55\x31\x30\xf3\x28\x6c\x14\x89\xda", b"\x63\x30\x55\xe0\x55\xf2\x37\xc3\x89\x99\xd8\x1c\x39\x78\x48\xc3\x8c\xce\x80\xa5\x5b\x64\x9d\x9e\x79\x05\xc2\x98\xe2\xa5\x14\x47", b"\x2b\xbf\x68\x31\x76\x60\xec\x1e\x4b\x15\x49\x15\x02\x7b\x0b\xc0\x0e\xe1\x9c\xfc\x0b\xf7\x5d\x01\x93\x05\x04\xf2\xce\x10\xa8\xb0", )?; test( HashAlgorithm::SHA256, b"\xa7\x33\xb3\xf5\x88\xd5\xac\x9b\x9d\x4f\xe2\xf8\x04\xdf\x8c\x25\x64\x03\xa9\xf8\xee\xf6\xf1\x91\xfc\x48\xe1\x26\x7f\xb5\xb4\xd5\x46\xba\x11\xe7\x7b\x66\x78\x44\xe4\x89\xbf\x0d\x5f\x72\x99\x0a\xeb\x06\x1d\x01\xcc\xd7\x94\x9a\x23\xde\xf7\x4a\x80\x3b\x7d\x92\xd5\x1a\xbf\xad\xeb\x48\x85\xff\xd8\xff\xd5\x8a\xb8\x75\x48\xa1\x5c\x08\x7a\x39\xb8\x99\x3b\x2f\xa6\x4c\x9d\x31\xa5\x94\xee\xb7\x51\x2d\xa1\x69\x55\x83\x43\x36\xa2\x34\x43\x5c\x5a\x9d\x0d\xd9\xb1\x5a\x94\xe1\x16\x15\x4d\xea\x63\xfd\xc8\xdd\x7a\x51\x21\x81", p, q, g, b"\x35\x6e\xd4\x75\x37\xfb\xf0\x2c\xb3\x0a\x8c\xee\x05\x37\xf3\x00\xdf\xf1\xd0\xc4\x67\x39\x9c\xe7\x0b\x87\xa8\x75\x8d\x5e\xc9\xdd\x25\x62\x46\xfc\xca\xeb\x9d\xfe\x10\x9f\x2a\x98\x4f\x2d\xda\xa8\x7a\xad\x54\xce\x0d\x31\xf9\x07\xe5\x04\x52\x1b\xaf\x42\x07\xd7\x07\x3b\x0a\x4a\x9f\xc6\x7d\x8d\xdd\xa9\x9f\x87\xae\xd6\xe0\x36\x7c\xec\x27\xf9\xc6\x08\xaf\x74\x3b\xf1\xee\x6e\x11\xd5\x5a\x18\x2d\x43\xb0\x24\xac\xe5\x34\x02\x9b\x86\x6f\x64\x22\x82\x8b\xb8\x1a\x39\xaa\xe9\x60\x1e\xe8\x1c\x7f\x81\xdd\x35\x8e\x69\xf4\xe2\xed\xfa\x46\x54\xd8\xa6\x5b\xc6\x43\x11\xdc\x86\xaa\xc4\xab\xc1\xfc\x7a\x3f\x65\x15\x96\x61\xa0\xd8\xe2\x88\xeb\x8d\x66\x5c\xb0\xad\xf5\xac\x3d\x6b\xa8\xe9\x45\x3f\xac\xf7\x54\x23\x93\xae\x24\xfd\x50\x45\x1d\x38\x28\x08\x65\x58\xf7\xec\x52\x8e\x28\x49\x35\xa5\x3f\x67\xa1\xaa\x8e\x25\xd8\xad\x5c\x4a\xd5\x5d\x83\xae\xf8\x83\xa4\xd9\xee\xb6\x29\x7e\x6a\x53\xf6\x50\x49\xba\x9e\x2c\x6b\x79\x53\xa7\x60\xbc\x1d\xc4\x6f\x78\xce\xaa\xa2\xc0\x2f\x53\x75\xdd\x82\xe7\x08\x74\x4a\xa4\x0b\x15\x79\x9e\xb8\x1d\x7e\x5b\x1a", b"\xbc\xd4\x90\x56\x8c\x0a\x89\xba\x31\x1b\xef\x88\xea\x4f\x4b\x03\xd2\x73\xe7\x93\x72\x27\x22\x32\x70\x95\xa3\x78\xdd\x6f\x35\x22", b"\x74\x49\x8f\xc4\x30\x91\xfc\xdd\x2d\x1e\xf0\x77\x5f\x82\x86\x94\x5a\x01\xcd\x72\xb8\x05\x25\x6b\x04\x51\xf9\xcb\xd9\x43\xcf\x82", )?; test( HashAlgorithm::SHA256, b"\xac\x30\xfb\x15\x51\x04\x95\x4b\x9d\x71\x39\xde\x93\x46\xd5\x4c\xa0\x51\x78\x95\x40\x53\xfd\x36\x1c\x97\x19\xce\xa5\x30\xd2\xd2\xe1\x73\x7f\xc4\x6b\x0e\xe2\x73\x57\xce\xcb\xd4\x7e\x0f\xd4\x7a\xda\x0d\x52\x36\xa9\xd7\x7d\xd6\x1a\x1b\x0d\xb5\x2e\x62\x8b\x14\x58\x8f\xdb\xa8\x77\x48\x82\x86\x6b\x04\xb4\x9c\xf5\x20\x5d\xb4\x94\x45\xa8\xa2\x02\xa5\xfc\x3f\xcc\x36\xef\xe0\xbd\x0c\x1e\x51\xeb\x08\x61\x6c\x4a\x7a\xfe\x12\x00\x77\xea\x08\xca\xf1\x67\xe9\x04\x46\x86\x22\x98\x01\x1a\xd9\xa1\xf1\x1c\xef\xb5\xf7\x43\x35", p, q, g, b"\x84\x74\x1b\xef\x3d\x9f\x9d\xab\x0e\x3f\xae\x78\x39\xd3\x9c\x1a\x19\x66\xab\x82\x79\x8d\x71\xaa\x46\xb7\xde\xf4\x65\xe3\x9e\xa5\xe7\xad\xae\xed\x2d\xfc\x92\xc9\xbe\xa7\x2d\x65\x26\x8b\x8d\xf9\x55\xf9\xb7\xe7\xb6\x92\x3d\x2b\xf0\x0e\x7e\x43\xf8\x3a\x0e\x54\xca\x94\x42\x75\xdc\x39\xc0\xfb\x0c\x8a\x00\xcc\xd0\xb2\x9b\x79\x0d\x9d\x8f\x34\x96\x05\x43\x90\x41\x0b\x4a\xe5\xc6\xea\xf2\xe2\x1b\xdb\x52\x42\x11\x79\x97\x0f\xa1\x3e\x09\x48\x28\x0a\x06\xa5\x76\xcd\xff\xae\x6f\xdb\x23\x9e\xbd\x48\x6b\xf4\x69\x92\x70\xe2\xbc\x08\x79\xbe\x25\xa6\xa0\xc2\xf7\x28\x0e\xa3\x3e\xeb\x32\xc5\xd2\xea\x60\x93\x38\x1f\xc4\xc8\x3c\x8f\x9a\x59\x1b\x0b\x0e\x72\xfc\xc1\x49\xc6\x85\xb0\x13\x81\xa7\x4a\xf4\xcc\xb9\x02\xc0\x05\x0e\x05\xba\xf7\x32\xba\xcd\x16\x06\x53\x3e\x2a\xcc\x63\x08\xc7\x77\x20\x1e\xec\xdc\xdc\xbe\x93\x51\x49\xc4\xe5\x72\xa1\x5a\x20\x5d\x2b\x80\xe7\x5e\xf2\x47\x31\x60\xf8\x5e\x64\x2d\x28\x37\x0c\x0f\x19\x46\x41\x25\xc6\x87\xc9\x69\x66\x5b\x13\xb0\x95\xaa\x87\xba\x47\x68\x02\xd7\x2c\x35\x4e\xbc\xbc\xd8\x9f\x28\xef\x00\x1c", b"\x28\xc6\xbf\xca\xdb\x5f\x52\x32\x4e\x39\x90\x3b\xf7\xa0\x4f\xae\xfb\x89\x38\x3f\x47\x3d\xaa\x43\x2c\xab\x91\x78\xf2\x47\x0d\x3c", b"\x4e\x88\xf6\x5f\xf7\x76\x94\x0b\xaf\xbb\xfb\x35\x64\x3b\xcd\xae\xb4\x3b\x25\xb4\x5d\xe2\xde\x3c\x01\x1f\xf1\x44\x9c\x8b\x8b\x32", )?; test( HashAlgorithm::SHA256, b"\x22\x25\x03\x1f\xd2\x6a\x6b\xb4\xfd\x99\x90\x34\x7b\xc2\xc8\xea\x4b\xa4\x5b\xd7\x5d\xf6\x84\x76\xf9\x83\xdf\xfb\x55\x31\x89\x9f\x13\x17\xd9\x5f\x7c\xbb\x49\x3d\xe4\x5c\xd2\xf1\x19\x04\xcd\x5c\x5d\x5a\x74\x8b\x4a\xa1\x27\xca\x73\x0f\x89\xa9\x28\xdd\xcd\x25\x0a\x65\x51\xc2\xf7\xcc\xe1\x09\xe6\x4d\x3a\xb7\x4a\xfb\x2d\x4f\x4f\x7e\x34\x94\xeb\x7d\x55\x70\x60\xa1\xf2\x9e\xcb\x5b\x75\xf6\x48\x48\x37\x09\x02\xbd\x6a\xe2\xfb\xf6\x80\x2b\x2f\x9c\x37\xf3\x48\x36\xad\x71\xdd\x2e\x2a\xbf\x6a\x0a\x47\xdf\x4f\xd5\x57\x3d", p, q, g, b"\x04\x96\x4c\x09\x3f\xdb\x85\x2c\x97\xb1\x65\xe1\x79\xf7\xef\x3b\x39\x35\x0c\x25\x88\xe6\x0a\x01\x77\xbc\x2e\x89\x0a\xb0\x8f\xfd\x73\xd8\xa5\xa6\x69\x2c\xfe\xbd\x0c\x91\x2d\xe2\xd5\x0b\xf0\x21\x39\xbf\x01\x7e\xc7\x15\xc2\xdd\x7b\xe1\xaa\xd9\xd0\xb9\x6c\x47\xd6\x46\x5d\x4e\xb0\xea\x02\x47\xff\x65\x59\x59\xd9\x4a\x34\x09\xe9\xf9\x26\x2d\x87\x70\x75\xf6\xf0\xc7\x78\x3a\x8d\xf3\xcc\x11\x5c\x52\x87\xc6\x9b\xdb\xf0\xff\xe0\xed\x37\x19\xe4\x18\xff\x99\xb5\xdc\xd5\xf0\xcf\xc1\x06\x5e\x40\x4a\x21\x6e\x09\x50\x86\xa6\xe2\x19\x7a\x69\xc4\x77\x74\x37\x72\x03\xd9\x9a\x23\x4e\x7b\xe6\x1c\xc4\xa9\x5a\x80\x9f\x9b\x9d\xd0\xa5\x50\xb7\x12\xbc\xe5\xd1\xcf\xda\xfd\xa2\x32\xd7\xc8\x31\xec\x52\x88\x47\x01\x15\x5a\x3d\xf2\xb0\x86\xbe\x87\x0a\xf8\xe8\x75\x55\x75\x18\xb0\x35\xc8\x49\x57\xc1\x74\x2b\x8c\x02\xb0\xd4\x6b\x64\xa7\x73\x01\x28\x09\xbf\xa4\xc5\x40\x7c\x3f\xbf\xed\x3b\x96\x08\x16\x60\x4c\xf4\x2b\x2d\xef\xb4\xfe\xea\xbc\x17\x2a\xfb\xfc\xbc\x82\x83\x6b\x44\xb9\x27\xe0\xcd\x4c\xa6\x3a\x1d\xae\xb3\xee\xb3\x0d\x1d\xe6\x08\x12\x7b", b"\x55\x68\xd8\x10\xba\x66\x4a\x08\xb3\x01\x26\x6d\x08\xc6\x9e\xac\xcc\xec\x5a\xae\x87\x0a\x6d\x57\x9e\xda\x51\xa3\x1b\x18\x46\x55", b"\x9e\x81\x88\x68\xe0\x67\x87\xfb\x95\x19\xb5\x05\x46\xee\x21\xd0\x54\x6e\x16\xbb\x1b\x59\x20\x31\x1b\xa4\x47\x69\xdc\x69\xc7\xa6", )?; test( HashAlgorithm::SHA256, b"\x4b\x1f\x93\x35\xfd\xfe\x88\xc0\x86\x6b\xb6\x48\xc0\x58\x57\xb7\x9c\x2f\xda\x92\xa9\x87\xb3\x59\x28\x2b\xbf\x08\x22\xdb\x74\x7a\x39\x40\xfe\xe0\x5a\xeb\x3c\xc0\x81\x23\x1e\x29\xb9\xd4\x60\xef\x30\xa5\x5f\x0f\x88\x70\x2a\x4e\xcd\xcb\x84\x2b\xee\xb3\x6a\x97\x61\x36\xc9\x24\x1f\x2e\xb5\xc2\xd9\x3f\xe3\x8a\x15\x80\xcd\x58\xfb\x93\xed\x13\x7a\x7d\x05\xea\x22\xd5\xe8\x73\x45\x63\x3a\x0e\x39\x3f\xee\xa6\x16\xea\xf8\x36\x84\xc3\xba\xca\x4f\xc5\xbf\x80\xa8\x7d\xbe\xc3\xa9\x78\x7d\xac\xce\xc4\x79\x66\x1a\xf0\xb9\x68", p, q, g, b"\x57\x76\x7c\x34\x8a\xb0\xc6\x1e\xab\x4f\x2e\x08\x94\xbb\x62\x23\x64\x5a\x33\x1c\x5b\xe2\x49\x0d\x76\x48\x39\xfa\x4d\xac\x81\x4e\x05\xe7\x09\x25\xd7\x20\xd0\xe0\xab\x5f\xaa\x3d\xb6\xdc\x58\xba\x57\x3b\x4e\x0b\x7b\xc1\x3e\x4c\x04\x4b\x96\x25\x93\x85\xfc\xd1\xea\xde\x0d\x7c\x51\x74\x49\x8c\x70\xba\x8f\xb8\x66\x1e\xd5\x24\xfa\x81\x71\x57\x0f\xd5\x2f\xaa\xc9\x91\x5d\x94\x7b\x51\xf6\xcf\x5b\x74\xe3\xed\xfa\x06\x4a\x51\x61\xc7\x62\x3e\xc6\xe8\x0d\x29\x96\x0b\x57\x3f\xb9\x8d\xe9\xe7\x10\xc5\x6e\xe4\x5a\xab\xc4\x02\x23\x57\xf6\xc3\x71\x29\x62\xad\x19\xe4\x3a\x41\x48\x95\x7c\xc6\xb9\xc8\xf6\x91\x87\x7a\x59\xf4\x31\x62\xd8\xf9\x8f\x24\x72\x69\x9e\xa5\x10\x10\x93\x05\xf8\xf9\x8a\xa3\xf3\xf3\x1e\x43\x02\xeb\x05\xe5\xf1\xa4\x62\xd0\xf3\xbf\xdc\xd0\xc8\x4e\x76\xbf\xdd\x14\xb7\xc9\x0b\x98\x2b\x8c\x0e\xc7\xc7\x8c\xf3\xe6\xc2\x16\xed\x1d\x20\xb5\x2a\x13\x2f\x53\xc9\x74\x7c\x7f\xbe\x39\x09\x2d\x5c\xcf\xcc\x01\xa1\x19\xc9\x2f\xaa\x3f\x13\xd4\x64\x3e\x5d\xb2\x2c\xa1\x68\x1d\x65\x36\xbc\x7b\x70\x4b\xb0\x9b\xf6\xc6\x21\xc2\xff\x06", b"\x7a\xc9\x5d\x3e\x09\x36\xcd\xe4\x41\xe4\xa2\x90\x71\x1c\xc0\x44\xe6\xe9\x8e\x8a\x8d\xe6\x82\x98\xbf\x7f\xb9\x0e\xef\x58\x9e\xb2", b"\x14\x0e\x9d\xe3\x7e\xc5\xae\xb3\xfb\x79\x5b\x01\x6f\x51\xea\x3e\x92\xd6\xf1\x98\xc5\xa0\xe5\xa5\xd2\x36\x67\x1c\x91\x04\x2c\x94", )?; test( HashAlgorithm::SHA256, b"\x3b\x87\x10\x9b\xf2\x15\x71\xfc\xfa\xe9\x2b\x85\x96\x49\xbf\x37\xdd\x23\xd5\x9f\x76\xd5\x0c\xf2\x6f\x4b\x2e\xbf\x7c\x5f\x4a\xe0\xb3\x77\xbf\x3b\xf2\xc7\xe0\x15\xa7\x4e\xfc\x80\x84\x33\x04\x7a\x71\xbf\x1e\xd4\xba\x90\x25\xf4\x56\x1d\xcb\x94\xbe\xf2\xc2\xa2\xc9\x4b\x3f\x55\xed\x61\x1c\x43\x2f\x98\xa6\x83\xab\xad\xc2\xc3\x1d\x00\x2e\xac\xa9\xb0\x70\xf2\xb2\x13\x19\xd0\x72\xdf\x75\xc6\x23\x85\xd7\xd0\x28\x97\xa0\x0f\x86\x3c\x28\x82\xb2\x89\x7a\x33\x13\x32\xbb\x95\x68\xb2\xfd\xfa\xcc\xf5\x0b\x3d\xe4\xb4\x2e\x8a", p, q, g, b"\x7c\x16\xa9\x64\x4c\x18\x25\x79\x11\xb8\x26\xda\x10\xb5\xb1\x01\x15\xff\x77\x67\x5b\xdc\x3c\x9f\x77\x09\x71\x62\xfc\x05\x9e\x86\xb0\x4c\x1f\xae\xed\x3c\x66\x30\x6c\x7e\x5f\xe2\xd5\xc6\x3e\x8f\xa5\xfa\x2b\x82\x56\x5a\xc6\x06\x54\x45\xde\x58\x19\xa2\xe4\xa5\x69\x25\xbd\xcc\xe1\x38\x65\x4d\xfb\x49\x0a\xc6\x24\xa3\x8a\xd6\x58\x49\xbe\x4b\xa7\x4d\x14\xc8\x29\xef\x10\x22\x48\xa1\x81\x93\x93\x33\x35\xea\xf0\xc7\x3b\x7b\xfe\x77\xd6\x69\xf8\x57\xef\x3a\xdd\xb1\xf4\xca\x42\x4d\xbf\xde\xdb\x9e\x2d\xe1\xfc\x0c\xc2\xd9\x77\x7e\xe8\x34\xa0\xac\x7d\x0c\xac\x1b\x2a\x61\x38\x90\x07\x14\x90\xef\xe5\xcb\x20\x97\xac\x83\x0f\xbc\x27\x88\x1f\x9f\xa5\x1d\x3b\x02\x47\xc5\xe1\xb7\xf6\xbe\x13\xc3\x0d\xd3\x1c\x2c\x59\xb7\x68\x3c\xe6\x0a\x0e\xbd\x66\x63\xde\x97\x87\x0a\xf2\xdd\x17\xd9\x14\x31\x32\x3a\x46\x86\xbf\x32\xe1\xe3\x97\x32\xda\xe1\x30\x0c\x57\xbd\x60\x0b\xe7\x90\x59\x3b\x2e\xfa\x04\x5b\xbf\xca\x95\x67\x68\x15\x7b\x47\x24\xca\x0a\x14\x72\xfe\x6c\x8d\xcd\x82\xa3\x80\x24\x76\x63\x41\xd1\xf5\x48\xad\x8f\x36\xdc\x67\x66\x76\xfb\xe3", b"\x1e\x21\x9e\xef\xd6\x16\xca\xac\x54\x9a\x85\x9d\x45\x18\x6b\x5c\x52\x86\x27\x57\x39\x58\xfe\x55\xcf\x57\xfb\xbd\x16\x61\xf7\xb8", b"\xb0\x95\x45\x84\x3d\xc0\xf6\x29\x9b\x48\xf1\x43\x11\x50\x36\x05\x50\x28\x68\x85\x9e\x8c\x43\x86\x7f\x80\xdf\x3c\x23\x91\xc7\x62", )?; test( HashAlgorithm::SHA256, b"\x04\x23\x65\xb1\x25\x69\x31\xa1\x11\xfa\xcc\x6c\x40\xf6\x18\xc4\x28\x80\x1b\x03\xe4\xf2\x22\xa1\xe1\xb7\x76\x3c\x3b\x02\xa6\x21\x4e\x4c\x51\x7b\xeb\x58\x7a\x4e\xa6\x9f\xdb\xd4\xea\x2d\x5d\x5f\x45\xaf\xde\xd9\x6d\xda\xc8\x7d\xc8\x99\x55\x61\x3a\xef\xf7\x64\x4f\xc6\xa5\x8b\xb8\x59\xa8\x52\x21\x31\x8f\xbc\x5e\x17\x5c\x69\x85\xb1\x9a\x1d\x16\xab\x6a\xd3\xca\x8f\xa1\x90\x3a\xcc\xa4\x2b\xc6\xd9\xef\xbe\x88\xfd\x6f\x2a\x86\x50\x42\x5b\xe9\x7b\xab\x9c\xb6\x70\xb2\xe3\x9f\x36\xd5\x26\x27\x8e\x0b\xcf\xcb\xff\xc3\xc6", p, q, g, b"\x81\x54\x11\xac\x6a\xa1\xb4\x95\xc4\xba\xc8\x02\x80\x6a\x1a\x35\x92\x92\x4f\xd9\xc0\xa3\xcc\xa4\x1e\x07\x6d\xb2\x93\xd8\x15\xc2\xf2\xb0\xa5\x3e\x97\xcf\x65\x7c\x89\x51\xb8\x56\xcc\xa1\x16\x6a\xd4\x33\xbe\x58\x29\xb0\xb6\x36\xca\x9d\xe4\x91\x11\xce\x5c\xec\xce\xde\xdf\x36\xd7\x95\xed\xef\xef\xee\x1d\x55\x32\x50\xfb\xcd\x5b\xd0\x5b\x4d\x99\xde\x55\xf1\x47\x77\x3a\xb3\xa0\xf7\x54\xd0\x90\xca\x7b\x6f\xf7\x5c\x16\x0e\xef\xd1\x70\x9a\x5d\xf3\xcd\x8a\x0c\xae\x3e\x34\x1f\x22\x75\xfa\xae\xe3\xe3\xe3\x17\x37\xe7\xe9\xc7\xe7\x48\x45\x65\x1f\x4f\x83\x9c\x9d\x08\xda\x6b\xfd\x00\xf2\xc2\xb9\xc6\xed\x9a\xcb\x78\xd1\x11\x75\xfa\x6d\xed\x7a\xb9\x5d\xbb\x2b\xfe\xf1\x8f\xeb\x14\x9b\xc9\x4f\x6d\xe0\x5a\x20\x52\x21\xba\x04\x06\xc9\x6f\x63\x97\x2a\xef\xec\x1b\xee\xf0\x30\x13\x70\x11\xe6\x79\x6a\xf2\xe4\xeb\xaa\x10\x01\x50\xd5\x8c\xaf\x40\x82\x17\xac\xb1\x18\x3a\x1a\x46\xe0\x63\x68\xcf\xf6\xfd\x74\x4d\xa7\x01\x9e\x7c\xa1\x09\xac\xf1\x24\x4a\x76\x3c\xc2\xb2\x18\x6f\x49\x27\x2b\xa3\xae\x04\x25\xf2\xeb\xcd\x30\xe7\x7e\x9f\x7c\x95\x7a", b"\xe7\x14\x5c\x70\xe0\x03\x8a\xe7\xe7\xd9\x01\xb4\x88\x28\xb0\xb8\xbc\x96\x0c\xc4\xfa\x29\xa5\x2e\x11\xff\xc9\xab\x08\xee\xe7\x26", b"\xb9\xc5\x4e\xf6\xcb\x3e\x1b\x04\x98\x95\x22\x99\xd1\x46\x5e\xd2\xc5\xd4\xe6\x70\xcd\xfd\x25\x06\x46\x24\x66\xc3\xb0\xfc\xc5\x38", )?; test( HashAlgorithm::SHA256, b"\x98\xff\xb2\x89\x9f\x17\xc8\x0a\x83\xe8\x2c\xa6\x26\x5e\x6f\x36\x17\x33\xa6\xbb\xc6\x3c\xdf\x88\x80\xdc\x75\x6b\xc7\x68\xb3\x5b\x90\xdb\x73\x90\xcf\xff\x74\x5e\xc1\xb5\x6f\x16\x55\xd8\xd9\xa2\x9a\x6e\x8a\x63\xbe\x0b\x1b\x2f\x9a\xa7\x43\x62\x09\xa1\xfa\x06\x1a\x7a\xec\x28\x62\x2c\x47\x2b\x3d\x02\x85\xa7\x01\x65\x5a\x49\x65\x46\xe8\x91\xa8\xab\x29\xd9\xf4\x0d\x2e\x74\x8d\x0a\xa2\xba\xbc\x06\xcf\xca\x64\x1b\x30\x0b\x7a\x21\x9c\xaa\x9e\x5b\xae\x3b\xf6\x89\xf6\x05\x67\xf9\x22\xe7\x79\x6f\xe4\x7b\xb7\x2f\xfb\x64", p, q, g, b"\x14\x11\x1d\xca\x30\xc0\x13\x87\x61\xfd\x2f\x55\x97\x2b\x98\x46\x04\x1e\x5c\xa8\xb9\xbc\x6b\x2d\xc8\x20\xf2\xa2\xf5\x10\x0a\xba\xab\x33\x7c\x7e\x0d\x1b\xc5\x9d\xe5\xae\x58\x6b\xbd\xcf\x4d\x4b\x14\xaa\x23\xbe\x40\x09\x52\x93\x12\x3b\xad\xbb\x11\x91\x9b\x78\xcd\x64\x12\x54\x8d\x9f\x9d\x15\xf6\x14\xb6\x92\x87\x13\x34\x41\x48\xfd\x7d\x30\x98\x5f\xd2\xc5\x09\xb4\x4d\x39\x6c\x56\x72\xa0\x82\xde\x41\x83\xfe\xe0\x3e\x45\xa9\x0e\xef\x6a\x08\xb0\xd9\xd4\x71\x32\xc8\x2a\x2c\xcf\xef\x05\xe2\xad\x0f\x34\x0d\xcc\x06\xd9\xe2\xe9\x79\xec\xc4\x38\x44\xc6\x05\x4e\x4f\xa5\xfb\x8a\x73\xa1\xe3\x87\x3f\x21\x45\xb0\xfd\x40\xf3\xec\x79\x46\xf1\xf4\x3d\xe8\xb8\x05\x7c\x1b\xe5\xbf\x04\x63\x0a\x12\x45\x3d\x62\x3c\x9b\x8d\x9f\x0e\x30\xc8\x8c\x30\x43\x42\x15\xd4\x8f\x77\x34\x8e\x6b\x04\x7f\x16\x93\x4e\xa0\x97\x43\xdd\x3b\x00\x9c\xeb\xc4\x9d\xbc\x3a\x3d\x35\x67\xc3\x32\x15\x55\xec\x96\xb2\x16\x0c\xaf\x78\x70\x97\x0a\xc3\xcd\x82\x94\x47\x7a\x06\x43\xad\x52\xc2\x3d\x9d\x98\x7d\xbf\xff\x64\xae\xd1\xa8\x83\xc3\x0a\x49\xf1\x4f\xf0\x62\x00\x95", b"\x45\x51\xb0\x96\x44\x6d\xb6\x76\x1b\x70\x8f\x35\x20\x9e\xdb\x91\xcc\x51\xee\x4e\xf9\x6a\x74\x95\x40\x7a\xb4\x16\x7a\x05\xc7\x91", b"\xcf\xe4\xc5\x8b\xdb\xf6\x1c\xaf\x09\xa4\x2a\xdb\x1a\xa5\xd9\x8b\x4c\x45\x9c\x01\x12\xc5\x78\x23\xbc\x15\xb5\xb9\x90\xd9\x2f\xf1", )?; test( HashAlgorithm::SHA256, b"\x58\x98\xcc\x0b\x42\x2b\xb8\x9f\x06\x6d\xab\xbd\x30\xf5\x9e\x9a\x35\xa3\x92\xbd\xd7\xad\x31\x5e\xc8\xad\x32\xb8\xf0\xf3\xd0\x28\x64\xe7\x0e\xa3\x6e\x90\x76\xc3\x95\xf0\xba\x9d\xe1\xab\x60\x80\xdf\x3c\xf4\xa1\x47\x0e\x2b\x99\x90\xb8\xe7\x61\x4b\xb8\x31\x2b\x07\x5c\x0b\x2a\x13\x2d\x7e\x47\xde\xd9\xe4\xc0\xa1\x36\x84\x55\xb9\xd1\xa6\x7b\xc4\x4a\xf2\xf3\x74\x28\xf4\x8f\x7e\x08\x9a\xb4\x1d\x04\x63\x78\xb6\xd4\x8d\x9c\xb1\x35\xee\xe4\x57\x40\x72\xab\xea\x93\xbd\xa7\xeb\x4f\x15\xa2\x06\xcd\xaf\x3b\xbb\xeb\xd3\x18", p, q, g, b"\x76\x6d\x7e\x4f\x8b\xc3\x25\x4d\x92\xcf\x6a\x64\xab\xd5\x04\xd0\x1c\xdc\xf6\xc2\x39\x17\x8b\x0a\xeb\x3f\x69\xc9\xbf\x20\x2b\xff\x75\x66\xec\xa0\x9f\x29\xcf\x5d\x6f\xa4\x73\x6d\x57\xc0\x82\x05\x50\x0d\x64\x83\x36\x40\x9d\xf0\x6e\x7f\x2c\xf9\x91\x78\xb2\x0a\x7e\xc2\xb5\x12\x4b\xcf\xfc\x61\xad\xb6\x6f\x6f\xaf\xc5\x1e\x32\x52\x1d\xea\x21\x24\xe5\x78\x1c\x38\x3b\x11\x6d\x06\xa6\xa6\xe8\x9d\xec\x46\xb5\xe4\xad\x69\xf5\xa1\xe8\xdd\x7a\xc5\xe1\x60\xda\x33\x6c\x11\x86\x0b\x60\x1e\x7e\x6d\x58\x89\x5e\x67\x97\xdb\x5a\xa9\x2d\xeb\x7b\x94\x2f\x2e\xdf\x58\xd4\x3d\x3d\xac\x92\x09\x55\x7a\x6a\xa0\x7b\x22\x8e\x73\xa8\x0f\xf0\xe9\x2e\x4e\xc4\x60\x3d\x36\x2e\x1c\xca\x7e\x92\x8d\x94\x59\xc2\x14\x05\xaa\x0f\x65\x48\x73\x2c\x0f\xc5\x01\xce\x50\xf0\x89\x6f\x07\x63\xf6\x33\xc8\xc1\xa8\x53\x13\x21\xe1\xa0\xf4\x71\x34\xa0\xd2\xd8\x67\x6f\x45\xf1\x3e\xa5\x76\xe6\x4c\x78\x70\x02\x80\x33\xa4\x26\x1b\xdf\xce\xc9\x48\xeb\xb1\xaa\x25\xb0\x21\x34\xd0\x25\x9d\x73\x02\x4a\x01\xda\x0c\xad\x1c\xe6\x75\x71\xe3\x69\x63\xdc\x13\x04\x96\x16\x0e\xbf", b"\xa2\x37\xd2\xc3\xd2\x37\x06\xca\xf0\x04\xa2\xe9\x4d\xe2\x9f\x04\xc7\x48\x93\x6b\x62\xab\x54\x31\xfe\x73\xc7\x24\x85\x81\x42\x65", b"\xb4\x8b\x9e\xf9\xcb\xd8\xbd\xf7\x99\xb7\x06\x05\xf0\x05\x50\xb8\x1b\x30\x9c\x15\x73\x32\x15\x3b\xe9\x70\x7a\x39\x9f\xbd\xd6\x7f", )?; test( HashAlgorithm::SHA256, b"\x04\x18\xe0\x12\x36\xca\xed\x0f\x80\x24\x1c\xe8\xc6\x30\x7d\x02\x6f\x5e\x25\xf4\xa9\x22\xbb\xdb\x4a\xaf\xb8\xd9\xdb\x95\xa1\x81\x75\xf9\xdc\xea\x9a\xcb\x4d\x37\x6f\x36\xff\x7b\x7c\xb5\x98\xe0\x73\xde\x95\xad\x20\x12\xeb\x9d\x11\xe1\x5c\xb3\x94\x1c\x6d\xd0\xdd\x69\x42\x2e\x78\x51\x2e\xbf\xfb\x19\xcc\x8a\x40\x3a\x9a\x7d\x1f\x17\x20\xab\x0f\x2d\x25\x62\x75\x80\x36\x60\x93\xe2\x1a\xc1\x53\x7f\x93\xde\x90\xa9\x45\x08\xf1\xd7\xa7\xa1\xdb\x5a\x7b\x13\xc9\xfd\x00\xb8\x2b\xe0\x44\xc3\xa3\x5e\xc0\x45\x1c\x30\x9b\x82", p, q, g, b"\x4c\xf4\xce\xe4\xd5\xab\xc2\xc9\x2d\xb5\x22\x92\x8b\x6d\x7e\x43\x6e\xa0\x08\x84\x00\x94\x97\xed\x58\x8e\x93\x28\x1c\xf0\x5b\x37\x47\xca\x00\x48\xb9\x17\x70\x82\x79\xcd\x02\x77\xce\x85\x60\xc2\x27\x75\xd2\xaa\x0e\x7e\xed\x1b\xba\x77\xbe\x45\x41\x7f\xa7\xaf\xd7\x76\xb8\xe5\x60\x67\x9c\x49\x3a\x52\x0a\x0e\x62\x6a\xcd\xc8\x3d\xf0\x21\x35\x16\x69\xbd\xf9\xda\x19\xb1\x2b\xef\x29\x26\xb5\x25\xfa\x4c\x8e\x3d\x1f\x20\x83\xea\x6b\xbb\x48\x98\x80\xf5\x94\xe6\x79\x34\xd1\xf3\x55\x81\xad\x18\xe0\xdb\x46\x2a\x1a\xc9\x44\x06\x6c\x65\xdd\x74\x3f\x35\x74\x1c\x6c\xf5\x88\x91\x8d\x83\x36\x70\x23\x29\xc6\x21\x13\xe9\x48\x6b\xfa\x49\xca\x54\x25\x91\x45\x26\xa9\x65\xe3\xc1\x97\x58\x24\xf4\xb3\x9f\xa5\xfe\xf8\x9c\xf6\xf9\xea\x51\x2f\x7f\xfc\x91\x38\xe7\x2d\xbd\x0f\x71\xb0\x1a\x70\x97\x53\x12\xea\xca\xb1\x11\x18\x47\x11\x15\xee\x3f\xc8\x10\x52\x29\x36\xc9\xdf\x35\x97\x75\x09\xb1\x96\xd8\x67\xfa\x11\xf6\x07\xb7\xef\x9a\xb7\x8c\xb7\x48\x21\x3a\x67\x63\x43\x9c\xe5\xe7\x64\x1b\x05\x35\x96\x70\x61\x22\x03\xa4\x7d\x4d\xe9\xc5\x38\x84\x05", b"\x83\xee\x96\x0e\x6f\x90\x26\xfe\x24\x54\xd8\x59\x46\x2a\xc3\x34\xa1\x38\x96\xe7\x51\x79\x85\x8e\xf4\x0e\x2e\x9a\x06\x5c\x53\x6a", b"\x7c\xe8\x69\x9c\x6c\xcb\x18\x4d\x42\x40\xb8\x70\x9d\xa1\x14\x51\x32\x8c\xf1\xa7\xe0\xca\xfe\x6e\x1c\x8a\xb5\x3d\x7d\xe6\x7d\x9e", )?; test( HashAlgorithm::SHA256, b"\x92\xc9\x49\xfe\x23\x42\xf9\x1a\x38\x7b\x67\xc1\xb1\x2b\x1d\x04\xd0\x72\x12\x03\xca\xed\x59\x3c\x9c\x46\x4e\x5f\xda\x09\xfd\xcc\x91\xd3\x32\x1d\x29\x85\xee\xc0\x8a\xb2\x02\x6d\x1e\xc3\xfc\xfa\x83\x8c\xb6\xaf\x45\x29\x0c\x08\xdc\x30\xb9\xc1\x4c\x44\x45\xd7\x83\xb6\xf4\x84\x09\xa0\x04\x90\xf4\xe3\x08\xdb\xc8\x7f\xd1\xb2\xf8\x78\x38\x52\x12\xe1\xf4\xc3\xe1\xcf\x81\xc5\x6d\x71\xe7\x3f\xd7\xa0\x95\xb5\x6b\x4a\xbe\xc1\x5c\x57\x10\x74\x20\xfb\xdf\xa4\x44\x77\x07\x8c\xcf\x45\x19\xf9\xf6\x04\x4f\x07\x44\x05\x20\x35", p, q, g, b"\x25\x6d\x23\x1a\xc2\xba\xe6\x50\xd2\x59\x99\xb2\x70\x6d\x4c\xb6\x3a\x89\xb1\x46\x8e\x0d\xf3\x6d\x67\x75\x35\xfa\x7a\x0e\xa8\x90\x59\x0d\x32\x90\xd4\xb5\x0b\xdb\x39\x9f\x33\xdc\x41\x5e\x44\x69\xc9\x7c\x6c\x0c\xee\x82\x05\xee\xc9\x62\xd7\x15\x3c\x4c\x85\xab\x88\xf7\xcf\x80\x97\x9d\x4a\x1f\xfd\x8c\x74\xe6\x81\xc1\xd2\x8d\xa0\x77\x32\x11\x6c\x32\x10\xee\x4b\x69\x33\x09\x33\x36\x86\x24\x6d\x66\x70\x74\xc7\x17\x20\x35\xfd\x60\x91\xb2\x84\x0b\x11\x39\x70\xb4\x59\x83\xd4\x74\xf5\x4b\x95\xd2\x63\x94\xb7\xa4\x3e\x81\xb4\x49\xa2\xee\x94\x23\xaa\x1c\x27\xf4\x59\x2b\x51\x6c\x12\xd5\x43\x3e\x2b\xa7\x24\xf5\x46\x3b\x41\x69\xa2\xb0\x94\x0e\x1b\xcc\xd6\x0c\xca\xb9\xb5\xa3\x82\x48\xac\xb6\x05\x82\xab\x8b\xbc\x01\xc5\xe7\x5f\x9e\xf7\x47\x42\x73\xfb\x51\xaa\x63\x16\xe6\x49\xf4\xf2\x24\x52\xdc\x70\xbf\xd4\xc3\xda\x07\x2c\x03\xea\x82\xee\x00\x9d\x42\x72\xa8\x49\x61\xc9\x8e\x51\x7a\xb9\x47\x74\x1d\x81\x21\x16\x01\x1d\xec\x03\x73\xca\x8f\xba\xc5\x57\x6c\x20\x69\xb0\x67\xf8\xb0\x05\xd6\x0a\x36\xec\xa4\x4f\x56\x01\x9a\x64\x83\x5d\x76", b"\x84\xca\xce\x71\xa8\x0e\xd4\x74\x94\x57\x0f\xc8\x48\x39\xf2\xe3\x50\x19\x1b\x74\xf0\xee\xff\xf2\xd7\xab\x2c\x68\x9d\xb7\x7b\xae", b"\x9c\xac\x33\x59\x4e\x19\x34\xb6\x8f\x62\xac\xa0\x5c\xa0\x40\xf3\xc8\x21\x10\xc1\x0b\x73\x79\x87\x8b\x78\x94\xb0\x91\x9a\x0f\x2f", )?; test( HashAlgorithm::SHA256, b"\xdf\x6a\x4e\xb7\xca\xd4\xff\x9b\xdd\x83\x56\xd3\x56\x8f\xcf\x02\x85\xc1\xa4\xe3\xc3\x10\x9f\xaa\x09\x1b\x58\xa9\xbd\x90\x7c\x62\x9d\x54\xaa\x7a\x23\xa7\x48\x70\x54\x5a\x09\x42\xa2\xd2\x39\x14\xf2\xf1\x67\xd9\x65\x73\xf0\x6f\x35\xea\x05\xef\x70\x4c\xac\x80\x14\xdd\x21\xb9\x61\xd3\xda\xcf\x7b\x93\x0b\xbd\x7e\x35\x55\x0f\x72\x10\x94\xc8\x63\x33\xe0\x3e\xd4\xda\xb7\xbc\x1b\x64\x16\xad\xd9\x57\x8d\x27\x9e\xda\xee\x37\x50\x4f\xd2\x5e\xc0\xc5\xe8\xa3\x7a\xc9\xec\x19\xbf\xb1\xe3\x77\x8e\xd6\xd9\xc6\xb6\xe3\x5e\xc7", p, q, g, b"\x2f\x4b\x0c\x01\xe4\xb1\x5e\xb5\xee\x7a\xfa\x98\x24\x09\x33\x30\x73\x8b\xe2\xf3\xf0\x6c\x42\xb2\xb7\xc6\x96\x8f\xa5\x4b\x98\x7c\x18\x4e\x7f\xa8\x9e\xff\x16\xda\x02\xb9\x3f\xf6\x1b\x9c\xe4\x8e\xeb\xe7\xea\xb0\xf7\xe2\x03\xad\x11\xc7\x1e\x7b\x29\x7d\x23\xf2\xd5\xa5\x99\x82\x72\xc3\x0c\x2e\x17\x24\xb5\xe9\x63\xbf\xd6\xf8\x32\x39\xf8\x74\xd8\x8e\xa0\x89\x43\x5b\x89\x6d\xd2\x10\x9b\x6a\x14\xb2\xd8\x48\xf9\xed\x7e\x92\x14\x3c\x06\x49\xf9\x7f\x4f\x2e\xb0\x5b\x8c\x5a\x07\xe9\x9e\x49\x7d\xbc\x75\x2d\x44\x3e\xba\x93\xd7\xf3\xdc\xdc\x32\x40\xa2\x71\x4e\xa0\xe3\xe7\x62\x7f\x21\x6e\x47\x01\x14\x8d\xd2\x11\x92\xf2\x74\xf1\xed\x5d\xf0\x5c\x60\xb1\x57\x6d\x3a\x0b\x7f\x69\xa7\x76\xb5\x01\x04\x04\xac\xd5\xaf\xaf\xd3\xd7\x0f\x57\x76\x3f\x2b\x77\x8d\x0c\x36\x1e\x5f\x7f\x0b\xbe\x17\xaa\xfa\xa5\xcd\x39\x33\x29\x17\x1d\x06\xec\x03\x20\x39\xa9\xff\xb3\x7c\x3a\xb8\xcd\x85\x8e\xa7\x88\xa7\xb9\xf5\x01\x99\x6b\xaf\x95\x9c\xa8\x5c\x7d\xaf\xe0\xcd\x3e\x30\x95\x76\x40\xef\xf1\x05\x89\x4c\x43\xf8\x66\xbc\xc4\x22\x69\x8d\x12\x8d\xca\x08\x87", b"\x1d\xd2\xda\xea\xf3\xe8\x9f\xd6\x44\xc6\xcc\x94\x23\x11\xea\x50\x56\x41\x3d\x8a\x24\x08\x77\x87\x67\x5c\xef\xfd\x3d\x6c\x15\xe4", b"\x3e\x12\x78\x13\x96\x55\x85\x60\x45\x5c\x4e\x70\xf6\x10\x52\x2a\xb2\xb1\x0f\xc2\x53\x43\x29\x68\x18\xef\x7f\xfb\x03\x78\xfa\x47", )?; test( HashAlgorithm::SHA256, b"\x4f\x16\x68\x1e\xaa\x5d\x97\x67\x3a\x7c\xca\x02\xee\x8a\x73\x74\xb7\x54\x11\xe0\xb5\x70\x4a\x94\x7f\x04\xd1\xa5\xb1\x4b\xe0\xb5\x06\xf3\x1c\x2f\xa3\x29\xe3\xca\x51\x6f\xa4\xf1\x62\x6a\x9b\x5e\x08\x0b\xda\x7f\x35\x3f\x85\x03\x65\xea\xc7\xc3\xd2\x59\x6f\x50\x2a\x5d\x70\xb1\x54\x22\x76\xc1\x2d\x4e\xa4\xa2\x2b\x53\x25\xb9\xeb\x3e\x94\x2e\x55\x67\x69\xb7\x96\xc4\xf5\x24\x59\x5f\x1c\xc6\xce\x17\xf9\x9f\x9d\xbf\x51\x33\x14\x53\x22\x8e\xad\x32\x7b\x61\x4f\x44\x38\xd3\x5d\x61\x42\x84\x29\xf7\x8c\x8c\x93\x77\xaa\xaa", p, q, g, b"\x2e\x33\x60\x4e\xd9\xe6\xc0\xf1\xba\x40\x3a\x8c\x3c\x3f\xe8\xe8\xf4\x88\x59\x18\x13\xaa\x3d\x2f\xcc\xcd\xf8\x8f\xe8\x08\xf7\x0a\xdf\x17\x3f\x0f\x14\x3a\xbd\xaa\xd4\x3b\x80\x76\x9e\x30\xff\xc5\x74\x9e\x8a\xd3\x59\x99\x95\x3d\xef\xf4\xf6\x1f\x4c\xa0\x73\x13\x60\x9e\x23\xac\xae\x7b\x35\xf7\x79\x34\xfd\xbb\xe1\xc3\x80\xb2\x72\x7b\x1c\x38\x99\x25\x0a\xf5\xb4\x39\x9b\x65\x8b\x79\x08\x67\x6d\x64\xd1\x17\x63\x78\x53\x73\xb2\x16\x98\x36\x61\x1d\x72\xa9\x57\x31\x99\x36\xc8\x4e\xfd\x72\xb7\x2f\x92\xbd\xd2\xdb\xe0\x00\x0d\x88\x41\xab\x6d\x8d\x0d\x66\x6e\x79\x36\x1a\xbb\x23\xb6\x00\x73\x48\xdb\xbe\x7a\x94\x93\x6d\xc6\xb0\x26\xf3\xb7\x10\x00\x81\xf5\x47\xb9\x94\xe0\xe0\x77\x8c\xb7\x61\xeb\xd4\x3a\x29\xd8\x76\x4c\x7f\x96\x2a\x74\x7e\xcc\x92\xe4\xa2\xa6\x28\xf5\x2d\x8a\xbf\x43\xf6\xe3\x27\x8a\x0d\x32\xea\x67\xc2\xd7\x9d\x04\xc8\x33\x87\xdd\xc7\x09\x36\x5c\x0a\x0b\xac\xc8\x3d\x75\xc9\x46\xe2\x83\xe0\x73\x92\x33\x58\x14\x41\xae\xdd\xb0\xd7\xd7\x65\x03\xd6\x21\x40\x5d\x27\xef\x66\xfa\x8b\x53\x79\xd1\x78\x61\x7d\x4b\xb5\xad\x59", b"\xe2\xff\x3f\xc4\x41\xdb\x45\x40\x19\x4a\x7f\x5d\xa1\xea\xd8\x49\xc2\xc3\xc4\x8d\xcc\xf8\xb2\xc1\xb3\xb3\x59\xa7\xb1\x6e\x16\xab", b"\x52\xfb\xdc\xd5\xc6\x2a\x99\x9a\xab\x46\x14\x7f\xef\x9e\x18\xcb\xfc\x7d\xaf\x68\x0a\x7d\xdb\x89\x2e\xdf\xa4\x4d\x28\x5e\x21\x58", )?; test( HashAlgorithm::SHA256, b"\x09\xe4\x8a\x36\x52\x3b\x52\x89\xec\x41\x85\x9f\xaa\x14\x1e\x2a\x29\xb3\xe8\x8a\xb2\xd6\x35\x1e\x20\xde\x00\x1e\x64\x24\xb8\x53\x37\x67\x5f\x0c\xe2\x6b\xe2\x24\xfa\x4f\x8d\xf0\xef\x97\x10\xea\x28\x56\x35\xb2\x7b\x29\x7d\x68\x8e\x33\x8b\x54\x61\x82\x0b\x57\xbe\x4b\xee\x21\x64\x5b\x04\x95\x7c\xa2\xf6\xcd\x7a\xf9\xa6\xa5\x2b\x3c\x97\xc5\xb9\xdb\x1c\x2f\x7e\xa8\x17\xcd\x6d\x3c\x85\x22\xd4\xe6\xa9\xde\x86\x9a\xef\x26\xec\x0d\xbd\xd2\x69\xc7\x9b\x38\x80\x69\x27\xbd\x3a\x51\x00\x73\x5e\x6f\x9f\x65\x5c\xa9\x4d\xae", p, q, g, b"\x7e\x38\xcb\x66\x8d\x64\x7e\xe1\x5f\x71\xac\x5d\x2b\x55\xc1\x1f\xd4\x73\x1e\x1a\x6c\x03\x1d\xd7\x59\x4d\x61\x4f\x2f\x1e\xd2\x56\x23\xff\xfd\xc5\x95\x6f\x52\x56\xe6\x35\xc9\x14\x20\x5a\x29\x37\xa6\x07\x4c\xfe\x1f\x3e\x44\x3b\xbe\xb3\x23\xa2\x3b\x0f\x0f\xbc\xcf\x8c\x17\x70\xad\x18\xba\x97\xd0\xac\xbe\xa1\xe8\x46\xe1\x2c\xf1\x2c\x37\x06\x25\xb1\x55\x5d\x71\x09\x05\xee\xe9\x43\x53\x9f\x22\x41\xb8\xfb\x49\x0c\x9d\x6b\x44\xf3\x61\x39\x22\x6b\x4c\x1f\x00\xe9\x5f\xfe\x59\x50\x14\xf6\x1b\xf5\x79\x83\x6a\x14\x21\x2c\x07\x23\x1a\x5e\x9e\x87\xde\x4a\x9a\xaf\x0f\x46\xf3\x4c\x92\x29\xf2\xea\xbb\x71\xd4\x0d\xe2\x6a\x1c\xbe\x10\xdb\x06\x45\xce\xc3\x7d\x48\x57\x5a\x11\x54\xbb\x5a\xcc\x94\x7b\xec\xb2\xa7\x4b\x07\xe2\xa0\xe4\x5b\x90\x3b\xe3\x75\x02\xf9\x1b\x07\xfb\x4e\xcd\x7f\x21\xfb\x13\x0c\x6d\x63\x9e\xf0\xfd\x84\x44\xfa\x12\xde\x85\x9a\xbe\x95\x54\x88\x01\xf6\xa3\xc4\x0e\x7a\x65\xfd\x15\x18\x22\x1a\x27\x4d\x7b\x65\xed\x41\x75\xf6\x6c\x04\xd9\x19\xc8\x6d\x2a\xe8\xc3\x74\xb1\x47\x09\xe9\xc8\xa3\x9e\x1d\x0c\x4e\x99\x35\x54\x0b", b"\xe5\x50\xdc\x65\xaf\x27\x5e\x47\xbe\x48\x0f\xd6\x47\x36\x6e\x2b\x05\x5c\x79\xea\x33\xde\xd4\xf5\xa9\x55\x71\x21\xe0\x82\xaf\x26", b"\xe2\x6b\x1a\x5f\x27\xcc\x6c\x87\x86\x3e\x31\xef\x7f\x1e\x61\xbe\xa4\x76\xfc\x5d\x7c\x25\xfd\xf2\x2f\xe7\x40\xf2\x3a\xa9\xa7\x52", )?; test( HashAlgorithm::SHA256, b"\x88\x37\xbb\xce\xef\x57\x75\x11\xf2\xd0\xc0\x8f\x79\x0d\x5d\x2e\x85\x62\xd9\x3d\xf3\xd8\x2d\xd4\xc2\x82\x7c\xd9\xa9\x11\x53\x08\x11\x4a\x18\xc4\x52\xdb\x27\x85\x56\x10\x81\xeb\x52\x36\x85\xae\x2b\x3c\x8b\x09\x0e\x0d\x44\xdd\x40\xd2\xfc\x0c\xdf\xc8\x8d\x6f\x90\x63\xa7\x70\x7d\xf6\x09\xed\xf0\xa8\xc5\x50\x34\x81\x5e\xa9\xf1\xd8\xb0\xbc\xbc\x92\xfb\xa5\x13\xba\x81\xee\x64\x6b\xf9\x8a\xd4\xeb\x22\xbe\x26\xa4\x58\x2b\x1b\xe2\x89\x9c\x91\xee\xbc\xbc\x9f\xba\x58\x25\xe0\x21\xe9\x9b\xe0\xc9\xd2\x86\x42\xd1\x3f\xa4", p, q, g, b"\x77\xd7\xa4\x0a\x7b\xab\x3f\x57\x78\xf8\x5d\x4f\xc4\x8b\x3e\x28\xce\x28\xb2\xdf\x9e\xb8\x7c\xc9\xcf\x39\x4e\xf2\x8e\x80\x64\xf3\x9a\x96\x90\x10\x39\x80\xa6\x6d\xa2\x19\xcb\x50\x22\xc1\x01\xf2\x20\x11\xa8\x15\x7a\x75\x68\xc5\xff\x2e\x97\x8b\xa2\x20\x13\x67\xd1\x7c\x22\xa8\x67\x86\x5d\x00\xc2\xa4\x37\x38\x56\x27\xbd\x08\x8b\xfc\xf7\x21\x92\x51\xbf\x6a\xe1\x58\x26\x9f\x4e\xf3\x5d\xa7\x09\x5a\x53\xc2\x4f\x37\xd6\x1b\xcf\xb7\xc0\x43\xfe\xb6\xe9\x38\x32\x34\x3f\x9e\x90\xee\x71\x04\xc8\x04\x86\xec\xd0\x87\xbe\x1b\x67\xf1\x8c\xda\xaa\x37\x5e\x03\x9c\xb7\xad\x60\x3c\xb0\xcd\x85\x56\x23\xe9\xfb\x48\xe4\xee\xde\x14\xea\x3c\x76\xa0\x36\x4a\xac\x00\x66\x50\xd3\xb5\xcd\x9b\x47\x4b\x56\xf8\x58\x4b\xe5\x8a\x72\x1b\xf3\x4d\xd0\x80\x8d\x33\x4c\xd8\x63\x2e\x80\x85\x36\x79\x1f\xcb\xea\x96\x1f\x71\x63\xda\xd2\x83\x53\xc1\x15\xeb\x3e\x85\x67\x37\xdb\xbe\xe0\x34\x36\x72\x16\x37\xa4\x77\x54\xa8\xa1\xfe\x0f\xed\xf5\x47\xb3\x58\xa7\x3d\x05\xb7\x69\xa9\x5b\xde\x34\x40\x00\x7c\x07\x73\xa3\xc7\xc8\xdc\x97\x14\xe1\x1c\x3a\x10\xee\x01\xd7", b"\x7b\x6b\x3e\xae\xf6\xcd\x5f\xe6\xda\xed\xe8\x6d\x63\x94\x34\x78\xc7\x71\x58\x24\x83\xbe\x0b\x92\x6e\xe3\x02\x2d\x22\xef\x91\x2e", b"\x39\xd9\x28\xb5\x9a\x69\x04\x50\xd1\x33\x59\xa2\x9e\xfe\x20\xcb\x98\xbf\xd3\xfc\x97\x26\xf8\x0e\x51\x48\xf0\x59\x66\x3f\xfd\x08", )?; // [mod = L=2048, N=256, SHA-384] let p = b"\xa6\x16\x7c\x16\xff\xf7\x4e\x29\x34\x2b\x85\x86\xae\xd3\xcd\x89\x6f\x7b\x16\x35\xa2\x28\x6f\xf1\x6f\xdf\xf4\x1a\x06\x31\x7c\xa6\xb0\x5c\xa2\xba\x7c\x06\x0a\xd6\xdb\x15\x61\x62\x1c\xcb\x0c\x40\xb8\x6a\x03\x61\x9b\xff\xf3\x2e\x20\x4c\xbd\x90\xb7\x9d\xcb\x5f\x86\xeb\xb4\x93\xe3\xbd\x19\x88\xd8\x09\x7f\xa2\x3f\xa4\xd7\x8f\xb3\xcd\xdc\xb0\x0c\x46\x64\x23\xd8\xfa\x71\x98\x73\xc3\x76\x45\xfe\x4e\xec\xc5\x71\x71\xbb\xed\xfe\x56\xfa\x94\x74\xc9\x63\x85\xb8\xba\x37\x8c\x79\x97\x2d\x7a\xaa\xe6\x9a\x2b\xa6\x4c\xde\x8e\x56\x54\xf0\xf7\xb7\x45\x50\xcd\x34\x47\xe7\xa4\x72\xa3\x3b\x40\x37\xdb\x46\x8d\xde\x31\xc3\x48\xaa\x25\xe8\x2b\x7f\xc4\x1b\x83\x7f\x7f\xc2\x26\xa6\x10\x39\x66\xec\xd8\xf9\xd1\x4c\x2d\x31\x49\x55\x6d\x43\x82\x9f\x13\x74\x51\xb8\xd2\x0f\x85\x20\xb0\xce\x8e\x3d\x70\x5f\x74\xd0\xa5\x7e\xa8\x72\xc2\xbd\xee\x97\x14\xe0\xb6\x39\x06\xcd\xdf\xdc\x28\xb6\x77\x7d\x19\x32\x50\x00\xf8\xed\x52\x78\xec\x5d\x91\x2d\x10\x21\x09\x31\x9c\xba\x3b\x64\x69\xd4\x67\x29\x09\xb4\xf0\xdb\xee\xc0\xbb\xb6\x34\xb5\x51\xba\x0c\xf2\x13"; let q = b"\x84\x27\x52\x90\x44\xd2\x14\xc0\x75\x74\xf7\xb3\x59\xc2\xe0\x1c\x23\xfd\x97\x70\x1b\x32\x8a\xc8\xc1\x38\x5b\x81\xc5\x37\x38\x95"; let g = b"\x6f\xc2\x32\x41\x5c\x31\x20\x0c\xf5\x23\xaf\x34\x83\xf8\xe2\x6a\xce\x80\x8d\x2f\x1c\x6a\x8b\x86\x3a\xb0\x42\xcc\x7f\x6b\x71\x44\xb2\xd3\x94\x72\xc3\xcb\x4c\x76\x81\xd0\x73\x28\x43\x50\x3d\x8f\x85\x8c\xbe\x47\x6e\x67\x40\x32\x4a\xaa\x29\x59\x50\x10\x59\x78\xc3\x35\x06\x9b\x91\x9f\xf9\xa6\xff\x4b\x41\x05\x81\xb8\x07\x12\xfe\x5d\x3e\x04\xdd\xb4\xdf\xd2\x6d\x5e\x7f\xbc\xa2\xb0\xc5\x2d\x8d\x40\x43\x43\xd5\x7b\x2f\x9b\x2a\x26\xda\xa7\xec\xe3\x0c\xea\xb9\xe1\x78\x9f\x97\x51\xaa\xa9\x38\x70\x49\x96\x5a\xf3\x26\x50\xc6\xca\x5b\x37\x4a\x5a\xe7\x0b\x3f\x98\xe0\x53\xf5\x18\x57\xd6\xbb\xb1\x7a\x67\x0e\x6e\xaa\xf8\x98\x44\xd6\x41\xe1\xe1\x3d\x5a\x1b\x24\xd0\x53\xdc\x6b\x8f\xd1\x01\xc6\x24\x78\x69\x51\x92\x7e\x42\x63\x10\xab\xa9\x49\x8a\x00\x42\xb3\xdc\x7b\xbc\x59\xd7\x05\xf8\x0d\x9b\x80\x7d\xe4\x15\xf7\xe9\x4c\x5c\xf9\xd7\x89\x99\x2d\x3b\xb8\x33\x6d\x1d\x80\x8c\xb8\x6b\x56\xdd\xe0\x9d\x93\x4b\xb5\x27\x03\x39\x22\xde\x14\xbf\x30\x73\x76\xab\x7d\x22\xfb\xcd\x61\x6f\x9e\xda\x47\x9a\xb2\x14\xa1\x78\x50\xbd\xd0\x80\x2a\x87\x1c"; test( HashAlgorithm::SHA384, b"\x8c\x78\xcf\xfd\xcf\x25\xd8\x23\x0b\x83\x5b\x30\x51\x26\x84\xc9\xb2\x52\x11\x58\x70\xb6\x03\xd1\xb4\xba\x2e\xb5\xd3\x5b\x33\xf2\x6d\x96\xb6\x84\x12\x6e\xc3\x4f\xff\x67\xdf\xe5\xc8\xc8\x56\xac\xfe\x3a\x9f\xf4\x5a\xe1\x1d\x41\x5f\x30\x44\x9b\xcd\xc3\xbf\x9a\x9f\xb5\xa7\xe4\x8a\xfe\xab\xa6\xd0\xb0\xfc\x9b\xce\x01\x97\xeb\x2b\xf7\xa8\x40\x24\x9d\x4e\x55\x0c\x5a\x25\xdc\x1c\x71\x37\x0e\x67\x93\x3e\xda\xd2\x36\x2f\xae\x6f\xad\x1e\xfb\xa5\xc0\x8d\xc1\x93\x1c\xa2\x84\x1b\x44\xb7\x8c\x0c\x63\xa1\x66\x5f\xfa\xc8\x60", p, q, g, b"\x5c\xa7\x15\x1b\xca\x0e\x45\x7b\xbc\x46\xf5\x9f\x71\xd8\x1a\xb1\x66\x88\xdc\x0e\xb7\xe4\xd1\x7b\x16\x6c\x33\x26\xc5\xb1\x2c\x5b\xde\xbb\x36\x13\x22\x4d\x1a\x75\x40\x23\xc5\x0b\x83\xcb\x5e\xcc\x13\x90\x96\xce\xf2\x89\x33\xb3\xb1\x2c\xa3\x10\x38\xe4\x08\x93\x83\x59\x7c\x59\xcc\x27\xb9\x02\xbe\x5d\xa6\x2c\xae\x7d\xa5\xf4\xaf\x90\xe9\x41\x0e\xd1\x60\x40\x82\xe2\xe3\x8e\x25\xeb\x0b\x78\xdf\xac\x0a\xeb\x2a\xd3\xb1\x9d\xc2\x35\x39\xd2\xbc\xd7\x55\xdb\x1c\xc6\xc9\x80\x5a\x7d\xd1\x09\xe1\xc9\x86\x67\xa5\xb9\xd5\x2b\x21\xc2\x77\x21\x21\xb8\xd0\xd2\xb2\x46\xe5\xfd\x3d\xa8\x07\x28\xe8\x5b\xbf\x0d\x70\x67\xd1\xc6\xba\xa6\x43\x94\xa2\x9e\x7f\xcb\xf8\x08\x42\xbd\x4a\xb0\x2b\x35\xd8\x3f\x59\x80\x5a\x10\x4e\x0b\xd6\x9d\x00\x79\xa0\x65\xf5\x9e\x3e\x6f\x21\x57\x3a\x00\xda\x99\x0b\x72\xea\x53\x7f\xa9\x8c\xaa\xa0\xa5\x88\x00\xa7\xe7\xa0\x62\x3e\x26\x3d\x4f\xca\x65\xeb\xb8\xed\xed\x46\xef\xdf\xe7\xdb\x92\xc9\xeb\xd3\x80\x62\xd8\xf1\x25\x34\xf0\x15\xb1\x86\x18\x6e\xe2\x36\x1d\x62\xc2\x4e\x4f\x22\xb3\xe9\x5d\xa0\xf9\x06\x2c\xe0\x4d", b"\x4f\xd8\xf2\x5c\x05\x90\x30\x02\x73\x81\xd4\x16\x7c\x31\x74\xb6\xbe\x00\x88\xc1\x5f\x0a\x57\x3d\x7e\xbd\x05\x96\x0f\x5a\x1e\xb2", b"\x5f\x56\x86\x9c\xee\x7b\xf6\x4f\xec\x5d\x5d\x6e\xa1\x5b\xb1\xfa\x11\x69\x00\x3a\x87\xec\xcc\x16\x21\xb9\x0a\x1b\x89\x22\x26\xf2", )?; test( HashAlgorithm::SHA384, b"\x02\xbb\x64\xd2\xd5\x03\x2f\x54\xf1\xac\x9e\x9e\xe1\x64\xdb\x83\xaf\x0c\xb0\x36\xd8\x8d\x41\xe9\xb2\x11\x8c\xfc\x39\xd1\xb4\xb4\xdc\x2c\x49\x75\x49\xc7\x98\x2c\xca\xcf\x66\x5d\x1b\x00\x11\x26\x82\x46\xc7\xc1\x7f\x56\x2e\xcb\xa2\x5e\x26\x54\x89\x87\x3e\x0d\xd9\x26\x8e\x9b\x06\x88\x0b\xa7\x4e\x74\xb5\x6f\x50\xc7\x32\x4d\x29\x37\x38\x53\xe3\xa0\xf3\xff\x78\x7e\xba\x4e\x5e\x7f\x94\x37\xf8\xec\x8a\x5e\x86\x83\x24\xe9\xc1\x7f\xb3\xd0\xe1\x2d\xe2\xd3\x1d\x43\x8c\x5b\xf3\x8b\x27\x16\x7d\x43\xae\x43\x11\xb1\x10\x62", p, q, g, b"\x11\xf3\xa7\x16\xfb\xda\x7a\xf3\x5b\xdb\x62\xd1\x28\xaf\x6f\x21\xec\x2e\xd4\x89\x6a\xa8\x1e\x87\x69\xc6\xee\xa9\xc2\x1c\x81\xae\xf2\x3a\xe0\xf5\x25\x26\x9d\xc4\x05\xac\xce\xf0\x98\x37\x7f\x65\x27\x30\x96\x8a\x33\xb5\x0f\x0a\x4c\x77\x84\x34\x52\x80\x65\x1c\xaa\x03\x4d\xf8\x73\x42\xca\x89\x73\xad\x86\xff\x7f\x0f\x87\x73\xa9\x4f\x95\xdd\x2b\xfa\x80\x2d\x26\x8d\xbf\x3a\x21\x03\xb1\x27\x6e\x06\xdb\x2d\x73\x43\x99\xf2\xab\x7b\xdc\xca\x09\x76\x16\xfc\x46\xed\x24\x78\xe5\x2c\xef\x04\x9d\x19\x44\x45\x86\xe7\xb7\x5d\x6a\x56\x74\x1d\xa2\x27\x0f\x54\xd2\xc7\x39\xec\x8d\xb9\x96\xc7\x1f\x06\xa3\x9a\xf2\x38\x3c\x61\x14\x99\xbe\x0f\xb3\x48\x09\xb1\x71\x25\x4e\xf2\x73\x51\x6c\x33\xe1\x7e\x14\x04\x8e\xf2\xd2\x1d\x60\x0a\xa1\x53\xbc\xf7\x37\x7f\xba\x94\x05\xc6\xb2\xe5\xf2\xaa\xf0\xf2\xf3\x46\x7d\x74\x61\xf6\x2e\x81\x4a\x2c\x46\x1e\x8a\xc9\xdb\x0d\xf3\x70\xe1\x8e\xc6\xee\xd8\x21\x2a\xca\xec\xf1\xe7\x24\x1b\xcb\xcb\xca\x67\x10\x60\xe5\x0c\x29\xf9\x66\xf1\xea\x1e\x92\xaf\x69\x03\xf8\x1c\x7a\xb9\xee\x09\xf6\x05\x77\xbf\x30\xc1\x86", b"\x7a\x5d\x20\x16\xaf\xe8\x78\x83\x49\x1b\xd6\xcd\x16\x6e\xdd\xdf\x13\x8c\x1c\x89\x96\x1e\x4a\xf6\x87\x6b\xe0\x8b\x0e\x06\xad\x74", b"\x34\xef\xbd\xa1\x84\x9d\xed\xd0\xd1\xaa\x77\x5d\xab\x2a\xa2\xb1\x4c\x9b\xa0\x20\x65\x92\xfb\xc3\x4e\xb4\x7b\x84\x46\x46\xad\xc2", )?; test( HashAlgorithm::SHA384, b"\x4f\x1c\x00\x53\x98\x4a\xb5\x5a\x49\x1f\x36\x18\xdb\x1b\xe2\x37\x91\x74\xa4\x38\x59\x74\x82\x5f\xcb\xe5\x84\xe2\xb6\xd0\x70\x2a\xbb\x82\x98\xdd\x91\x84\xee\xf1\x74\x0b\x90\xa5\xea\xe8\x50\xe9\x45\x2b\x4e\x4a\xb2\x19\xe1\x87\x86\x0f\x0f\xb4\xad\x2b\xe3\x90\xef\x2b\xa7\xd7\x6c\xde\xdc\xaf\x10\xae\xaf\x4f\x25\xe4\x97\xb4\xda\x95\x13\x75\xb6\x87\xa8\xd6\x70\x12\xd3\xf9\x9c\x7b\x5c\xa8\x2e\x9b\xd0\x63\x0d\xff\xcd\x63\x5e\xcd\x82\x09\xcd\xdb\x87\x2d\xa5\xbf\x47\x36\x30\x97\x83\x34\x5a\x35\x37\x6b\x4f\xce\x4b\x91", p, q, g, b"\x10\xe6\xf5\x0f\xd6\xdb\xb1\xca\x16\xf2\xdf\x51\x32\xa4\xa4\xea\xbc\x51\xda\x4a\x58\xfe\x61\x9b\x22\x25\xd7\xad\xab\x0c\xea\x3a\xfc\x2d\xb9\x0b\x15\x8b\x62\x31\xc8\xb0\x77\x4e\x0f\x0d\x90\x74\x51\x7f\x33\x6c\xa0\x53\xae\x11\x56\x71\xae\xe3\xc1\xde\x0f\x85\x72\x8c\xff\x99\xde\xeb\xc0\x7f\xfc\x9a\x63\x63\x19\x89\xa9\x27\x7e\x64\xc5\x4d\x9c\x25\xa7\xe7\x39\xae\x92\xf7\x06\xee\x23\x7b\x98\xb8\x70\x0a\x9d\xf0\xde\x12\xd2\x12\x4e\x2c\xfd\x81\xd9\xec\x7b\x04\x69\xee\x3a\x71\x8a\xb1\x53\x05\xde\x09\x9d\x9a\x2f\x8c\xec\xb7\x95\x27\xd0\x16\x44\x7c\x8f\x6f\xe4\x90\x5c\x37\x18\xce\x52\x34\xd1\x3b\xf4\xed\xd7\x16\x9b\x9d\x0d\xb9\xa6\xb0\xfc\x77\xb7\xd5\x3b\xdd\x32\xb0\x7d\xc1\x5b\xc8\x29\x62\x0d\xb0\x85\x11\x45\x81\x60\x8a\xc9\xe0\x93\x77\x52\x09\x59\x51\xd2\x89\x85\x5d\x0b\xcc\x9d\x42\x1b\x94\x5c\xc4\xf3\x7f\x80\xb0\xcb\x25\xf1\xff\xee\x9c\x61\xe5\x67\xf4\x9d\x21\xf8\x89\xec\xbc\x3f\x4e\xd3\x37\xbc\xa6\x66\xba\x3b\xa6\x84\x87\x4c\x88\x3f\xe2\x28\xac\x44\x95\x2a\x85\x13\xe1\x2d\x9f\x0c\x4e\xd4\x3c\x9b\x60\xf3\x52\x25\xb2", b"\x00\x6b\x75\x9f\xb7\x18\xc3\x4f\x1a\x6e\x51\x8f\x83\x40\x53\xb9\xf1\x82\x5d\xd3\xeb\x8d\x71\x94\x65\xc7\xbc\xc8\x30\x32\x2f\x4b", b"\x47\xfa\x59\x85\x2c\x9a\xe5\xe1\x81\x38\x1e\x34\x57\xa3\x3b\x25\x42\x00\x11\xd6\xf9\x11\xef\xa9\x0f\x3e\xac\xed\x1d\xee\x13\x29", )?; test( HashAlgorithm::SHA384, b"\x42\x19\x91\x86\x43\x4d\x6c\x55\xbc\xef\x26\x9b\xee\x68\x5c\x4e\x15\x80\xe2\x43\x02\x7e\xd1\x28\xca\x99\x49\x20\x33\xa5\x29\x54\xbd\x1c\xa8\xec\xc5\x04\x38\x20\x72\x5a\x3c\x0d\x71\xa1\x81\xa0\x5a\xab\xcb\x4e\xcd\xa7\x18\x0d\x86\x85\x5e\x7b\x4d\xfa\x9a\x44\xc7\xaf\x4c\x98\xfb\xf1\xf0\x62\x40\x58\x80\x4f\xd8\xea\xae\x49\x90\xd4\xd7\xbb\x75\xf0\x17\x41\xce\x36\xcf\xc9\xc1\x37\x25\x4c\xab\x06\x5a\x46\x17\xd0\xd0\xcd\x5f\x58\xea\x56\x86\x8a\x40\xf3\xe0\xba\xf7\xdb\x5d\x25\x57\xf4\xb9\x77\x5c\x18\x20\xdc\x1d\x41", p, q, g, b"\x63\x64\xa3\x5a\xe9\x94\xf2\x77\x03\x31\x9c\x36\xd9\x07\x93\xc8\xf2\x65\x11\x84\x6b\xa0\x60\x38\x99\x5b\x65\x56\xe4\x44\x3a\xa6\x1e\xb0\xf8\xef\xcc\x3d\x47\xf7\xc5\xf8\x52\x76\xea\x92\x1d\xa0\x78\x4a\x67\x99\x82\x53\xc9\x92\x97\x5f\x9e\x13\x84\x7c\xca\xd0\x99\xd9\xc1\xe5\xc9\x4c\xfb\x19\x54\x88\xe1\x29\x3e\x23\xb7\x4d\xb0\x06\x03\xe8\xbd\x68\x14\xc9\x46\x90\xbf\x0c\xcc\xc1\xc0\xe4\x7f\x0c\x66\x09\xa4\x8e\x14\x45\x87\xec\xe1\x78\xf7\x2c\x85\x14\xa4\x35\x90\xbc\x4c\x21\x9d\xa9\x5c\xbe\x89\x66\xf4\x40\x4f\xe9\xc2\x88\xf2\x3c\xd0\xf9\x73\xe7\x7e\xc8\x4b\x4b\x0f\x16\x3b\x50\xa3\xc5\x56\xcd\x1d\x39\x51\xfa\xeb\xd9\x82\xaf\x44\x44\x7e\x60\xd7\x83\x4b\x93\xb6\xd9\xc3\xff\x09\x61\xfc\xcb\x90\x83\x12\xa2\x43\x76\xee\xdc\x50\x8f\x80\x66\x68\xd6\x61\x7b\x77\x49\x1a\x01\xd5\xd0\x69\xd6\xcc\xd5\xf2\x1b\x5e\xb3\xc3\xa3\xd4\xa0\x47\x95\x93\x84\x5c\x72\xf7\x20\x15\x7b\x18\x8d\x2d\xfa\xe4\x40\x1c\x57\xa6\x00\xb1\x42\xb6\xbd\xe2\xa6\x9f\x1a\x0a\xfb\xa2\xf5\x07\xa6\x3c\xd6\xdf\x05\x6b\xb5\xb3\x4f\xdf\xce\xe0\x12\xd3\x41\xb3\xf1", b"\x25\x51\xd4\xf8\x55\x17\x4f\x7b\x28\xa7\x82\xb8\x96\x97\xd4\x8f\xbc\x31\x4c\xfe\xb1\x7e\xc4\xc9\x90\x2a\x8e\x55\x7c\xc6\xf6\xb9", b"\x27\x8b\x78\x6f\x9e\x28\xee\xcc\xd0\x05\x86\xb4\x45\xe7\x5f\x48\xcf\x26\x49\xf3\xf1\xb7\xbf\xf7\x2b\x0e\x76\x7f\x34\x43\xdc\x58", )?; test( HashAlgorithm::SHA384, b"\x4f\xdd\x88\x87\x56\xac\x68\xf4\xc2\x9c\xd5\xb1\xde\x42\x75\x67\x94\x57\x0c\xa8\xf1\x8f\xf7\x95\xf6\xf0\xfc\x85\x67\x72\xb6\xa2\x18\x9b\x5e\xd4\xa9\xb7\x54\x73\x28\x07\x5b\x56\xc2\x8d\xdf\x50\xb8\x4c\x27\x20\x5c\xee\x57\xb2\x9d\x0b\x38\x79\x70\xe8\x9a\x6a\x22\x36\x29\x3b\xbc\x9e\x39\x90\x13\xd1\xdd\x3b\xd5\xa1\x0a\xb0\xd2\x59\xf7\xfd\xa7\x04\xf7\x1c\xbe\x3b\x8b\x87\x52\x80\x6a\x0c\x84\x66\x8d\x85\xe4\xd7\x39\xce\xc6\x28\xdf\xf6\x33\x71\xd2\x4a\x4b\x14\x13\x73\x82\x75\x9b\xa4\x00\xdf\x0e\x2c\x25\x94\x7d\x18", p, q, g, b"\x5b\x61\x98\x45\xba\x96\x9f\x1c\xa5\x96\x3f\xcf\x04\xc0\x3a\xa4\x0e\x98\x92\x22\x77\x4e\x95\x7a\x54\x19\x1a\xcf\x9d\xdc\x40\x7a\x54\xa1\x61\xe2\x2a\x5a\xc5\x0c\xa5\xd6\x1e\x66\x01\xcc\x79\x95\xbf\x0d\xb3\x8f\xf0\xfa\x1f\x77\xb2\x44\xfe\x98\x14\x8c\x81\xf2\x08\xdc\xa2\x9f\xfa\x30\xf1\x13\x1c\x76\xdb\xbe\x43\x03\x42\x5e\x91\x80\xb4\xa4\x8f\x22\xc7\x57\xed\x8e\x38\x8b\x61\xbd\xc6\xd5\x55\x19\x52\x3d\x00\xc3\x1a\x5f\x83\x76\x64\x0d\x46\x88\xe6\x0d\xcc\x17\x2d\xee\xce\x73\xde\x28\x43\x7e\x90\x0c\xb1\x9a\x53\x11\xa0\xc9\xca\x9a\xf6\xcc\x6e\xeb\x68\x44\xe9\xb8\x35\x9e\x3e\xf1\xcb\xe0\x37\x84\x10\x7d\x2d\x0a\xeb\xec\x7c\x1d\x70\xd9\x38\x5a\x4d\x2b\x80\x33\x85\x1f\x5d\x5b\x7a\xa1\x8e\xf5\x70\xaa\x03\x7f\xcb\xd3\xe3\x0f\x2f\xc2\x01\x3f\xfb\xfa\x07\x87\xbe\x6d\x59\xff\xa1\x61\x6e\xed\x5e\x12\x1e\xe4\xdb\xee\x04\xa9\xed\xe0\x04\x95\x60\x75\x46\x5a\x76\x88\x70\x1e\x04\xec\x9b\x21\x53\xf5\x2c\xaf\xbf\xf7\xff\x92\x26\xe6\x93\x97\xc7\x08\x3c\x3a\xa5\x36\xd7\x10\x9e\xe4\x30\xa6\x54\x48\xb1\x0c\x18\x18\xc7\x05\x10\xa3\x39\xc1", b"\x4b\x90\x99\x3d\x70\x7f\x33\x71\xd0\xa0\xcc\x87\x25\x5e\x99\xa8\xfb\xa1\x8c\x3b\x58\xdd\xdd\xc1\x06\x7c\xd3\x94\x17\x23\x66\xcc", b"\x4b\x26\x12\xd5\x06\xfb\x85\xe5\xaf\xf9\xfc\xd5\x6c\x09\xbd\x12\xbf\x60\xf7\x8a\xb7\xdf\xd0\x21\xa7\x42\xff\x85\xdc\x50\x7a\xe2", )?; test( HashAlgorithm::SHA384, b"\x85\x07\xdb\x5f\x1d\xf9\xd2\x2f\x44\x7c\x20\xe4\x32\x0f\x90\xd9\xb3\x07\x22\x19\x71\x96\xd1\xa2\x41\x8d\x06\xdc\xa4\x1b\x33\x05\xf6\xfb\xe5\x2a\xb5\x8c\xc0\xb6\x0e\xf1\xa1\xd2\x57\xfc\x2f\xb2\x06\x2f\xe6\xc5\xf2\xa2\x5f\x02\x93\xca\x39\xd0\xc0\x83\xcf\xd5\xe4\xbd\xad\xf2\x16\x9a\xd4\xed\x17\x8c\x88\xec\xb5\x55\x4f\xfa\x2b\x53\xaa\x43\x98\x11\x5c\xde\x62\x7d\x30\x14\x4a\xce\x93\x25\xb2\xd7\x9d\x7d\xce\x95\x15\x09\xd7\x34\xaf\xb0\xff\x6d\x92\x65\xb9\x02\x67\x2e\xb5\x88\x4e\x9d\x8a\xcf\xf0\xea\x22\xc7\x69\x38", p, q, g, b"\x43\x88\x31\xcb\x0e\xb0\x9a\xab\x24\x27\x54\x54\x35\x4c\xe4\x2b\x9a\x2e\xed\xb3\x1f\x42\x12\x19\xde\xf7\x46\x87\xe6\xf9\xc9\x2f\x0b\x19\x82\x35\x5c\xad\xb2\x6e\x09\x5b\x7c\xa2\x5d\xe5\x30\xaa\xba\x63\xe6\x4f\xc2\x3a\xcc\x3d\x1d\x1f\x1b\x70\xcb\x72\x61\x56\xca\x0a\x79\x9b\x59\x09\x4b\xcc\x3b\x89\x98\xa4\xae\x77\x44\xd2\x15\xd6\x3b\x88\x70\x82\xf4\xc8\x41\x28\xe7\x4b\x9b\x99\x99\xc6\x0c\xad\x3b\xc6\xbb\x6f\x72\x72\x84\xb4\x31\x1a\x92\x9b\xbd\x96\x4c\x9a\x70\x74\xe8\x60\x62\x22\x4d\xce\xdb\x58\xb9\xb5\x98\x54\x6a\xc9\x5b\x3b\x43\x4e\xa1\x14\xab\x0d\x67\x85\x41\xd6\xca\xec\x0c\x56\x00\x9b\xc3\x47\xa4\x25\xf1\x67\xcd\x32\xa3\x4e\xec\xb7\x19\x24\x24\xd5\x7b\x0e\x54\xb4\xa9\xe8\x2f\x42\x51\x38\x70\x3c\xe8\x9b\x18\x90\x39\xe9\x2a\x77\x0b\x51\x49\x7f\x8f\x10\xea\xe9\xc3\x45\x9e\xd8\x7e\x51\x01\xf5\xab\x1b\x62\x71\x48\x5f\xdb\x2d\xd3\xdb\xc4\x21\x7f\xcf\x67\xc7\xe9\x2d\x00\x96\xdc\x7d\xa9\x72\x7f\x5a\x43\x4b\x75\x45\x28\x4c\xd8\xa2\x83\x07\x0b\x5a\x49\xd7\x11\xdf\xfa\x85\x90\x43\x11\xe0\x34\x5a\x99\x14\x7a\x16\x8e\xa0", b"\x1d\x27\x81\xf5\xf9\xd0\x8a\xb2\xfe\xb1\x68\x39\x42\xc2\xc2\x9a\x66\x31\x88\x39\xa7\xdf\xef\x9a\xee\x9c\xd7\xa8\x9e\xfe\x2a\xb0", b"\x3a\xdc\x7b\xe9\x68\x50\x2e\xad\x10\xfe\xec\x19\x1e\x21\x2e\xa0\xe0\x7d\x44\x90\x06\xe7\xf2\x2d\xdf\x86\x9a\x9f\xae\x71\x18\x34", )?; test( HashAlgorithm::SHA384, b"\xc7\x84\x49\x60\x96\x65\x84\xc8\xe3\xa5\x9d\xc3\x7d\xf3\x7b\x7e\xb3\xad\x33\x31\x48\xa3\x2b\x86\xc1\xec\x18\x07\x2f\x3b\x31\x6c\x59\xcd\xef\x98\xba\x4d\xc4\x6f\x53\x2a\x42\x80\x20\x0c\x22\x5f\xac\x6c\xd1\xad\xf0\xa4\x53\x82\xc2\xd8\x80\x54\xe4\x47\x74\x04\x54\x97\x6e\x52\x72\x33\x0c\x74\x87\xeb\x42\xa0\x95\xf7\x31\x41\x39\x93\x8c\x74\x19\x19\x3b\x1c\x12\x80\x54\xc1\xbb\xf1\x0d\x06\x34\xe2\x2c\x6e\x02\xd8\xe1\x22\x79\xca\xc0\xbf\xa0\x1d\x30\x58\xe0\xf8\xd5\x54\x7b\xa0\xf7\x15\x29\xc2\x7e\x00\x84\xd4\xbd\xe7", p, q, g, b"\x2d\xe9\xd2\x7f\x1a\x03\x01\x99\xff\xbb\xa7\x70\xe0\x8a\xeb\x1f\xf3\x70\x8e\xdf\x8e\xbb\x3a\x8e\x66\x4e\x3b\xd1\x51\x1d\xb1\x26\xed\x87\xbc\x44\xc2\xd2\xaf\x40\xb9\xd5\x12\xc5\x0a\x4d\x6c\x10\xb2\x3e\x3c\xa6\x18\x19\xf5\x84\x1c\xbf\x5d\x0b\xd6\xc8\x8d\x46\xf1\xac\x64\x74\xec\x20\xb9\x10\x0b\x32\x8c\xc1\x55\x87\x91\x66\xf4\x6b\x6d\x71\x14\x0b\x0c\xfb\x2b\x07\x25\xb6\x4a\x38\xd7\x0a\x91\xca\x8f\x0e\x3b\xae\xec\x61\x25\x26\x2c\x52\xa9\x5d\x5c\xa5\xd5\xff\x6f\x44\x82\xb1\x82\x50\x06\xcd\x46\x9f\x9e\x7f\x31\x76\x9a\x73\xed\xdb\x5f\x70\x17\xf1\x8b\xc7\x47\xae\x4f\xce\x45\x0c\x42\x74\xf4\xab\xb9\x60\x57\x7d\x13\xb6\xa7\x7d\xd9\x9e\x67\xd1\x1e\xdb\x41\x3e\x42\x8e\x50\x72\x6f\x70\x52\xe5\x35\x65\xfa\x1d\x6f\xde\x91\x85\x95\x73\xc9\x28\x92\x89\xff\xef\x05\x98\x80\x28\x08\xec\xc5\x50\x1c\xb3\x00\xe0\x64\x05\xed\x0f\xeb\xc3\xdf\x23\xf4\x0a\x1f\x65\x32\x41\x0f\x7d\x90\x49\xb9\x20\x21\x6f\x7d\x5c\x7a\x72\x8c\x8d\xd6\x3a\x8d\x00\x60\xfb\x53\xb3\x54\x3d\x62\xa6\x36\x66\x17\x50\xfd\x43\x77\x5e\x80\xb5\x09\x00\x43\x51\x47\x5f", b"\x09\xe6\x54\xb1\x7a\xb7\x75\x95\x96\x28\xe7\xca\xd0\xe2\x70\x53\xee\x49\x5b\xcc\x29\xcc\x2a\x5e\x3b\x02\x96\x60\xa7\x7b\x13\x30", b"\x26\x1a\xd4\x1d\x6b\xce\x6d\x04\xd8\x91\xa4\x3c\x16\xec\x2a\x81\x14\xe5\x1f\x0e\x47\xb4\x8b\x1d\xd1\xf3\xd6\x26\x15\x03\x38\xfb", )?; test( HashAlgorithm::SHA384, b"\x6f\x3f\x74\x38\x8c\xc9\x0b\x29\xc1\x09\xec\xbd\xa0\x8c\x79\x34\x9d\xff\xde\xb9\x07\x22\x97\x4d\x79\xd6\x40\x62\x09\x49\x44\x8f\x66\xae\x67\x3e\xaf\x4d\x4a\xf8\xc4\x3d\xa6\x73\xa4\x5e\xd1\x52\xea\x66\xfc\x97\x16\x6b\xaa\x7c\xe8\xbe\xb6\x66\xbd\x57\xca\x43\xda\x68\x01\xc0\xee\x5a\x5a\x9b\x50\xc5\x04\x79\x35\xd7\xa8\x55\x2c\x38\x1d\x93\xea\xf0\x3c\xbb\xbb\x88\xed\x0d\x3b\x5a\x25\x21\xb6\x76\x12\xa4\x40\x51\x20\xef\x02\x05\xe8\x9a\xeb\x48\xd5\x77\xbc\xda\x3a\xd2\x0e\x0a\x7c\xd0\x7f\x8c\x9b\x21\x5c\x84\x5d\xd8", p, q, g, b"\x08\x0c\xa4\x12\xbd\x19\x7c\x5a\xaf\xa2\xc6\xdf\x59\x33\xa6\x21\x0f\xa5\x40\x89\x82\x68\x28\xd5\x49\x6b\x45\x36\x09\xa5\x6b\x7d\x55\xd2\x32\xfb\xe6\x50\xdd\x9f\x62\xc0\x5c\x05\x0c\x02\x6a\x87\x17\xa7\x8b\x5d\xb0\x16\x14\xa1\x93\x01\xc6\x10\xd2\xb9\x96\x4a\x7e\x33\x57\xc7\x22\xa4\xc5\x53\x27\x3b\xf2\x7f\x87\x1b\x4b\x92\x41\x67\x8c\x33\x4e\x20\x82\x7a\x5f\x51\x1f\xe9\x31\x9a\x07\x5d\x12\x75\x3a\xc0\x96\x0d\xf6\x08\x70\xa0\x8a\x12\xf0\x9b\x9d\x35\x93\x78\x17\x81\xa0\xcd\x75\xe9\xd8\x1c\xc6\xb9\xb0\xd5\x06\xd1\x00\xfe\x97\x21\x65\xb6\x82\x97\xe6\x07\x0d\xb2\xd8\xb6\xea\x32\x17\x6d\x15\x62\x08\x4f\x6a\x06\xe0\x8e\x29\x29\x15\x5b\x25\x5d\x33\x85\x3d\xe6\x54\x9e\x79\xf8\xb5\x60\x49\xa1\xd0\x2f\x29\x16\x6d\x5f\x91\xcf\xbd\xe5\xaa\xf6\xbc\xae\x56\xf5\xd2\xd9\x0a\x9b\x4e\x8f\x6f\x45\x00\x80\xca\xe8\x25\x6c\x66\x19\xe9\x15\x55\x23\xc2\xb2\x05\x22\x55\xa8\xf6\xd9\xf5\x3d\x8a\x89\x7b\xe5\xb0\x47\x60\x02\x41\x0b\xf7\x98\x25\x6f\x62\xbb\x1a\x81\x82\x7c\x2c\x3f\xc4\xec\xf9\xab\xfd\x77\xe7\x41\x74\x78\x73\x70\x86\x4f\x05\xf9", b"\x43\x99\x3b\x68\xe8\x47\xf6\xba\x61\xd5\xad\x4d\xc8\xf5\xad\x70\xda\xbc\x31\x7a\x7b\x68\x11\xc2\x3e\x7f\x21\x5f\x95\x41\x5e\xd5", b"\x1e\xa7\x27\xaf\xdb\x90\x7d\x1d\x5b\x23\x37\xc1\xec\xea\x46\xc7\x1e\xb0\xfc\x83\x63\xaf\x23\x86\x5a\x34\x52\x02\xa7\x62\xa7\xc5", )?; test( HashAlgorithm::SHA384, b"\x74\xa4\x33\xc2\xd3\x13\xf6\x62\x32\x32\x4d\xf8\x75\xb8\x25\x63\x80\x5d\x7e\xd6\x82\xb2\x66\xea\xf9\x62\x37\x5e\x42\x2b\x3a\xbb\xfe\x3d\xce\x7f\x3c\x19\x60\xa1\xe4\x10\x0f\x33\x3e\x16\x8d\x02\x19\x68\xb4\x83\x97\xe8\xcc\xe9\x00\x5e\x95\x1f\xdc\xb0\x96\xa9\xab\xea\x34\x2c\xb5\xb0\x8b\xab\x79\xef\x0c\x43\x1d\xd3\xa4\x3d\xe7\xd5\xbd\x6b\x86\xbe\xa8\x87\x2b\xa0\x38\xb4\x3a\x23\x6a\x73\x56\xb0\x3f\x89\xb0\x90\x04\xba\x2d\xef\x66\x3e\x6d\x29\x97\x63\xb6\xca\xfc\xb6\xb1\x50\xa5\x7f\x82\xb8\x90\xff\x6d\x56\xf8\x32", p, q, g, b"\x44\x4f\xaf\xab\x58\xdb\x4d\x6f\x52\x83\xc3\x44\x3d\x64\x78\xb5\xb7\x8d\xaa\x63\x1b\xd4\xc3\xd9\xa2\x8e\xd1\x72\x81\xda\x4c\x1c\x2e\xf4\xd5\xed\x57\x6d\x66\xbf\xe5\x31\x4e\x11\xfe\x68\xab\xff\xe4\xdf\x40\x6f\x60\x33\xed\xb8\x4f\x36\xa3\x8a\x3c\xe6\x14\x60\x1b\xc2\x58\x41\xf9\x41\x9a\xfb\x28\x67\xd9\x91\xe8\x7b\x44\xc4\xb7\x44\xe3\x9b\x64\x07\x9d\x9a\xad\x4b\x58\x5d\x79\xc8\xe2\x1c\x8f\x90\x99\x05\x40\xfe\xc8\xae\x98\x1f\x74\x83\xdc\x55\x23\xd2\x16\x08\x8a\x55\xcf\x23\x80\xea\x8e\xb5\x24\x67\x81\x29\x05\x59\xea\x1b\x20\x8a\xd4\xd0\xf5\x87\x1c\xb4\xd1\x3c\xdc\xa6\xef\x34\xfd\xf2\xde\x63\xe2\x09\xaa\x32\x0c\xdf\x14\x18\x5b\x8f\x5f\x60\xcc\xf9\x3f\x39\x8c\x1a\x6c\xf8\xb3\xce\x3d\x98\xda\xf0\x5e\x4c\xf9\x0c\x39\x80\x1c\xe3\x5f\x01\xec\x76\xa9\xf6\x03\x5c\xe1\xb5\xba\x10\x7a\x5f\x66\xcf\x25\x3b\x71\xfb\xa3\x83\x3e\x99\x69\xc3\x14\xeb\x6d\x50\x00\x05\x74\x92\x31\xf7\x99\xb0\xc7\x9a\x55\x5a\x10\xcd\xd6\x9f\x8e\xec\x4c\x11\x7d\x7c\x8b\x4e\xc6\xf6\x0a\x1e\xe5\x57\xb7\x0c\x0d\xea\x38\x0a\xf5\x3b\x92\xfd\xde\x88\x23\xca", b"\x3b\xda\x5b\x0c\x9e\x3d\xa2\x2f\x0b\x3e\x29\x35\x6a\x2f\x7d\xda\xce\x6e\x9b\x24\xa0\x63\xeb\x3f\x5a\x7d\x75\x5f\x2e\xea\xff\xb5", b"\x4c\xbb\x81\x53\x20\x31\x4a\x06\x53\x8d\x2a\x67\x40\xe6\xbf\x9d\x02\x2e\xac\x9a\xa2\x5c\x75\x08\xf6\x59\xf0\xf7\xc1\xf5\x9c\x45", )?; test( HashAlgorithm::SHA384, b"\xf4\xea\xdf\xea\x11\x7f\xd3\xd6\x70\xce\xa2\x8a\xa9\xd2\x60\x2c\x95\x1e\xd8\x43\xe2\xe8\xcb\x28\x64\x07\x4c\x8c\x9b\xcc\xb0\x60\x6c\xed\x83\xae\x29\x80\x59\x8c\xc3\xe1\xb0\x47\xfc\xa8\x65\x91\x27\x40\x6d\x8f\x59\xf5\xb7\xbb\xfe\x8e\xce\x6d\x3e\x42\xf8\x7f\x4e\x42\xeb\xe9\x2a\xda\xa1\xe6\xe9\x2c\xed\x3d\xca\xcc\x2e\x0b\x2c\x98\xea\xde\x7c\x9c\x99\xda\x88\x7e\x74\xdb\x5a\x59\x13\x2c\x1d\x7d\xf7\xcd\xe8\x66\xcb\x2f\x3c\xa7\x50\x85\x2b\xa5\x3e\x26\x5e\x62\xbf\x7a\x93\xfd\x69\x3e\x4a\x13\x75\x1e\x18\x6e\x9d\x6b", p, q, g, b"\x10\x4f\x44\xfd\x76\x69\x60\x76\x44\xec\x55\xe6\xca\x40\x96\xc9\xa2\x79\x47\x27\x52\xa1\x75\x3d\xbb\x9f\x2a\x69\x41\xb8\x12\x22\x74\xc8\x7d\x16\xf6\x3d\x75\xdd\xa9\xeb\xcf\xd6\x58\x4b\x0c\xb3\x74\xfd\x17\x58\x13\x53\xd2\xa2\x46\xec\x0b\x37\x8d\xe6\x0e\x96\x13\x13\x16\x83\xc0\x56\x8b\xb5\x4d\x74\x45\x7a\xd7\x3d\xe8\x59\xa4\xf0\x24\x45\x34\x4d\x13\xee\x92\x8f\x3c\xda\x51\x34\x20\x2a\x93\x88\xe6\x4c\xf0\x5f\x81\x90\x04\x9d\xf4\xe7\x77\x70\x98\x38\xd0\xc9\xd3\xbc\xb3\x7e\xec\xdc\x38\xc1\xa5\xd2\xb4\x71\xc4\xb9\x10\xcf\xaa\x9a\x9b\xa8\x1f\x69\xb4\xb4\x5c\x40\x34\x40\x29\x95\x8f\xa4\x00\x00\xe5\x68\x81\xbc\x6a\x14\x86\x43\x30\xd5\xb3\x51\xc1\x61\x20\x86\x76\xcb\x85\x2b\xf4\x79\x70\x26\x8d\x37\xd4\xbf\xe9\x7b\x3b\x26\xef\x5b\x78\x5f\x50\xeb\xc8\xc4\x79\x49\xdc\x9b\xd0\xb2\xe6\x73\xfb\x04\x0e\x26\x78\x9f\x3f\x5c\xdb\xce\x8e\x4b\x78\x38\x99\x92\xbb\x83\xee\xb2\xb0\x63\xe9\xe1\xdb\x06\xa9\xed\xe9\x33\xfa\xef\x7e\x63\x5e\xff\xe5\xe1\xb1\xe2\x11\x53\xdc\x69\x34\x19\x7e\xfa\x1f\xd6\x8f\x18\xa4\x0e\xd5\x69\x74\x6c\x83\x74", b"\x36\xc0\x86\x07\x03\x68\x26\x5f\x73\x6e\x7b\xba\xd5\x4a\xaf\x24\x82\xd2\x61\x61\xf8\x05\x7a\x97\xa4\xb8\xcd\x2b\x4d\xdd\x78\x55", b"\x31\xd9\x9d\x73\x6e\xa6\x70\x14\xfe\x59\xcb\x22\x12\xc4\x7e\xb9\x20\xf2\xaf\x44\xe3\x2b\x65\xdb\x15\xaf\x83\xcb\xe8\xe6\xaa\x70", )?; test( HashAlgorithm::SHA384, b"\xcb\xc3\x7a\xfc\x75\x17\x7a\x83\x86\xdc\xe2\xc4\x0c\x33\xb8\xf5\xde\xdc\x23\x11\x3b\x45\x12\xcb\x96\x79\x0f\x2d\xd7\x40\x66\x10\x3e\x0c\x45\xa9\xc6\x17\x6f\xf9\x6b\x7d\x71\x91\x62\x00\x3c\xee\x10\xfa\xd6\xcc\xc1\x98\x55\x0a\x38\x92\x75\xd2\x1e\x70\x8b\x69\x61\x52\x32\x72\xec\xd5\xef\xab\x56\x80\xed\x74\x1c\x2d\xe0\x25\xb0\x2b\xbd\xc5\x63\x15\xa4\x42\xe4\x37\xc4\x3e\x3b\x37\x8e\x6d\x62\xea\x88\x78\xfd\x97\x89\x85\x8a\x8c\x68\xa5\x04\xbf\xf4\x95\x16\xe7\x62\xa2\x2a\xe5\x13\xa2\xdc\xeb\xa9\x25\x3b\x36\xf5\x53", p, q, g, b"\x35\x6c\xc7\x37\x0c\x84\x0f\xa2\x6b\x0d\x10\x6c\x47\xa6\x26\xe0\x28\xa0\xc9\x67\xc0\x93\x81\x0b\x52\x06\x39\xbd\xda\x0d\x33\x9b\x7f\xc2\x9a\xdc\x0d\x90\x36\xb9\x71\x03\x58\xef\x9f\x8c\x6c\x05\x25\x2b\x27\x82\x81\xb2\xaf\xe7\x95\x38\x86\x42\x9e\x85\xd2\x28\xfb\x54\x74\xac\xfd\x65\x21\x31\x51\xe9\xda\x0a\xef\x86\xa6\x6f\x9f\x9c\x59\xfa\x88\xfd\x48\xcc\x3a\xdd\xc8\x3d\x7a\xdf\x4a\xfb\x16\x65\x04\x9e\xd0\x94\x02\x02\x19\xc0\x19\x58\xb6\x97\xf2\x2e\x65\x21\x52\xe5\x3b\xf4\xe8\xf6\x8f\x47\x6a\x58\x18\x1d\xdd\x3f\x64\x34\x4e\x9b\x87\xa0\x8c\x5d\x0d\xe4\x9e\x7b\x3c\x29\x95\x84\x0c\x20\x00\x84\xe9\x0a\x76\xd2\xc0\x5f\x8b\x5c\x68\xe7\x71\x92\xd0\x67\x6b\x42\x19\xd4\x57\x9c\xb2\xde\x0f\x2a\x93\xa9\x16\xb4\xf9\xcf\xe0\xd8\x11\x3d\xc4\xbb\xd9\x7e\xd1\x2d\x8c\xe0\x44\x7f\xcf\x9d\xf1\x2e\x92\x2c\x63\x83\xca\x69\xc9\xde\x9a\xd3\x20\xf9\xc5\x33\x1a\xdb\x6e\xb1\xd2\x23\x07\x91\x96\xa2\x93\x9c\xc0\xa7\x25\x9c\x51\x2c\x47\x8c\x94\x3f\xe0\x57\x36\x71\x0e\x27\x3e\x4b\x58\x67\x17\x4d\xe7\x2e\x70\x3b\x5e\x7b\xf7\xaf\xdb\xc0\x64\x27", b"\x56\x45\xef\x65\xe8\xe9\x23\x6d\x87\x4d\x45\x9e\x7a\x58\x09\x92\x3c\x05\xd6\x4b\x22\x75\x7b\xfc\x5b\x56\x21\x07\x9e\x84\x81\x9c", b"\x65\xf4\xc8\xfe\xba\xf3\xe9\xd4\x65\x81\xb1\x76\x85\xc4\xf2\xec\x9b\x95\x64\x21\xd0\x34\xa2\xc1\xaa\xab\xee\x94\xb7\x87\xa4\xf1", )?; test( HashAlgorithm::SHA384, b"\x8e\xb3\x68\x5c\x3f\x40\x6c\x56\x15\xe8\x8a\xcc\xf4\xc0\xc7\xd2\x07\x1b\x6c\x7b\xde\x52\x44\x99\x4f\x73\xdc\x04\xf3\xcc\x0a\xb7\xe2\xb6\x66\x4a\x19\x94\xe6\xee\xc5\x2b\x62\x79\x0a\x04\x32\x8e\x43\x6a\x2b\x4a\xf3\xcb\xe3\xba\x6e\x4c\x8f\x36\x3a\x39\xb2\x52\x9e\xf5\x54\xc0\xc6\x27\xf9\xf6\xb2\x55\x92\x8a\x39\xa4\x65\xe6\x0a\xc5\x0c\xcf\x01\xf3\x2c\x7b\xa4\x83\x64\x03\x44\xb6\xa8\xf5\x83\xc9\x08\x76\xb8\x4d\x19\x55\x4b\x0a\x4b\xaa\xbc\x2c\x24\x0e\x29\x6b\x12\xc8\x19\x41\x0c\xac\xff\xe7\xa7\x46\x44\x19\xbe\xe0", p, q, g, b"\x94\xba\x48\x69\x77\xf5\x98\x2f\x2a\xe7\x5e\x98\x6b\x7e\x19\x44\x61\xcc\x3d\x65\xcd\xbf\x26\xf9\x36\x80\x5d\x12\xd7\xf8\x50\xaa\xd7\x58\x02\x06\xd7\xdc\x54\x4c\xd1\x2c\xa1\x89\x1c\x9d\xc4\x06\xc9\x49\xe5\x2b\x9f\xeb\xfa\x88\x83\x6f\x15\x66\xd5\x21\xa1\x10\xbb\x54\x5e\x07\xba\x28\xca\xf0\x7e\x1b\xbf\xa3\xb1\x76\xcc\x91\x7c\xc4\xbb\x45\xda\xe7\xf8\x73\xb7\x2d\xfa\x90\x00\xe9\xab\x60\x83\xe7\x05\xc0\x16\x7d\x85\x3d\xda\x11\x4c\x42\x9f\xd8\x12\xa0\x59\x61\xfc\x2e\x78\xba\x9e\x68\xcc\xdb\x9d\xc6\x7b\x11\x6f\x10\x53\x20\x34\xd9\xf0\xf7\xd3\x99\x01\xdc\x64\x31\x27\xc4\x30\x90\x58\xf8\xeb\xf4\x3b\x28\xa5\xce\x53\x4e\x29\xd6\x22\x7c\x4e\xc2\x7c\xcf\x77\x7b\x00\x08\xdf\x5c\xe8\xb8\xa1\x9b\x57\x71\x72\x5c\xb0\xf9\xf2\xa6\x2b\xb4\x1f\x01\x06\xc3\x90\x80\x3a\x30\x7c\x60\xac\xbe\xd6\xc2\xe1\xe0\xdb\x50\x36\xe0\xe7\x9d\xdc\xc3\xf7\x18\xb2\x9c\xa5\xaa\x02\x2f\x2f\x0b\xbe\x81\x5f\x9c\x0e\xb5\x04\xfc\x9f\xf8\xd1\x8a\x2d\xa9\x99\x02\x3a\xf8\x10\x5c\xdd\xfc\x67\x94\xdf\xdc\xc4\x13\x33\xbc\xcd\x44\x6a\xd7\xb8\x2a\x0a\x7b\xfe\x38", b"\x27\xb4\xe3\xc3\xa4\x5e\xfa\x61\x31\xc3\xd0\x05\xca\x92\x4d\xff\x11\xfd\xcc\xf4\x09\xc2\xa6\x99\x3f\xcb\x50\x54\x77\xb6\xe4\x00", b"\x68\xa0\x85\xbd\x13\x0c\x4e\xc0\x8a\xa9\x67\x3c\x49\x5b\xa5\xaf\xd4\x6c\x9d\xda\xd2\x05\x2b\xa7\xab\x39\x63\x29\xd9\x00\xd8\x6c", )?; test( HashAlgorithm::SHA384, b"\xf2\xb0\x2a\xc6\x27\xb3\xf6\x6b\xaf\x4e\xba\xa5\x2b\x89\x9a\xdf\xd7\x07\x1a\xf5\x3e\x78\x92\x31\x82\xd8\xb4\xd5\xf3\xa9\x47\x42\x51\x30\x8b\x4d\xbd\x15\xfb\x6b\x65\x7b\xe6\x50\x28\xa1\x89\x35\x39\x12\xd7\xc1\x6d\x6d\x49\x89\x98\x5c\x15\xce\xdc\x43\x43\xf0\xce\xb6\x80\x61\x7b\xc7\x27\x85\x11\xf9\x06\x8a\xbd\x61\x37\x18\xa8\x62\x51\x3e\xe5\x14\xfd\xf8\x0c\xd2\x5b\x6f\x84\xc4\x88\x51\xe6\xa7\x85\x0f\xea\xea\x57\xea\x20\xde\xb1\x12\x3c\xa4\x20\x6b\xde\x8a\x93\xff\x99\x9e\xf7\x89\x58\x3e\x2c\x85\x0d\x9e\x06\x35", p, q, g, b"\x4e\x16\x0d\x69\x70\x68\x3f\x4d\x84\xeb\x88\xc5\x5b\xa2\xda\x58\xd7\x7f\x63\x74\xfc\x51\x27\x27\x3d\x65\xe8\xef\x96\xcc\xff\xf5\x1d\xf6\x9b\x0e\x2f\xdf\x3e\x98\xf6\xd3\x5e\x6a\x3d\xd9\xf7\xed\xd9\x0b\xba\xe4\xc6\x58\x1c\xd0\x2a\xd0\x13\x36\xc0\x08\x6d\x42\x48\xeb\x13\x73\x48\x07\x89\xf7\xd8\x33\x3b\x83\x1d\xb3\xba\xe0\xbd\xb4\x97\x89\xaa\xb9\x3c\xde\x1f\xaf\x1c\xe8\x8d\xcd\xc7\xa1\xa4\xf8\x61\x43\xce\x44\xf8\x51\xac\xe4\x59\xa5\x52\x8c\x96\x19\x5f\x44\x38\xee\x7c\x18\x56\xac\x61\xfd\x50\x35\xd8\x39\xd6\x2e\x48\xa1\xab\x6b\xd2\x3a\xd5\x2f\x1f\x6f\xfe\xd1\x98\x26\xb6\xd7\xf6\x49\x1c\xfb\x05\x00\x31\x76\xf2\x90\x79\x45\x54\x43\xf0\xab\x48\x21\x50\xfa\xc8\xe3\x2a\x39\x02\xa4\x09\x67\x75\xf3\x42\xed\xee\x2d\xaf\x4c\x4f\x33\x8d\x45\x5b\x4e\xa3\x5d\x39\x75\xf7\x2b\xe8\x5e\x98\xe8\x71\x58\x48\x6b\x4c\x3d\x6e\xc3\x7a\x37\x03\xf6\x3a\x3e\x19\x27\x2b\xa5\x25\x50\x89\xaa\xcd\x30\xfa\x39\x79\xb4\x58\xdf\x61\x6f\x57\xb7\x50\x2b\x42\x91\x38\x45\x62\x04\x1f\x61\x88\xdb\x50\x3f\x3d\xf7\xf5\x98\x1d\xa5\x70\x5e\xb0\xf1\xd2\x42", b"\x64\x33\xbd\x33\xdb\x0a\xc8\x26\x1c\x69\x1a\xf3\xa2\x7f\x52\xcd\xd4\xa6\x5d\x79\x99\x39\xfa\xf2\x79\xac\x41\x78\x8e\x75\x28\xa6", b"\x04\xcf\xdc\xb9\x93\x38\x2e\x8f\xd2\xdb\x8d\x90\xdc\xa8\x0e\x94\xb1\x7b\x43\x20\x09\x85\x2c\xd3\xf8\x66\x25\x15\x9e\x83\x7c\x19", )?; test( HashAlgorithm::SHA384, b"\x2b\x43\x65\xa4\xac\x68\x54\xc9\x72\xda\x73\x47\xaf\x1c\xec\xc6\xed\xcb\xae\x9d\x53\x3b\x74\xfb\xe6\xdb\x57\x12\x16\x3a\x6c\xe9\x84\xf9\xd7\xa4\xc5\x4b\x44\xdd\x75\x55\xe5\xc2\xd2\xf3\xd0\x98\xf3\x1d\x51\x7f\x8e\xbd\x33\x01\x99\xa5\x4b\x15\x29\x7e\x5a\xde\xe1\xbd\xf3\x91\x58\x1f\x10\x19\xb1\xad\x72\xdc\xcc\xd5\x48\x4b\x51\xd2\x75\xa3\x68\xc6\x9a\x76\x62\xe7\x9f\x9b\x29\xc9\xa3\x08\x4c\x94\xae\x76\xda\x04\xf9\x58\xc7\xd3\x6c\xec\xc5\xd4\x1d\x77\xf2\x30\x2f\xf2\x8f\x2e\xd9\xc6\x6a\x06\x62\xca\xbf\x51\xc8\x42", p, q, g, b"\x58\xe6\x35\xee\xc8\x0b\xde\x1e\xb7\xbf\x2d\xa2\x06\x00\x61\x7a\xf2\x9f\x0a\x19\x17\x05\x67\x6b\xc1\x0f\x75\x53\xf7\x61\x11\x26\xe4\xc4\xd4\x4b\xcf\x14\xf7\xa9\xf4\x8d\xa6\xe1\xb1\xe5\x4d\x0a\x71\x57\x24\xaf\x5b\xca\x93\x86\x70\x90\xf9\xbf\xc9\x27\x41\xdf\xe1\xdd\x4f\x06\x07\x5e\xc2\xa9\x26\x2d\xa8\x1e\x0d\xca\xbf\xca\xb9\xe6\x94\xdd\xca\x86\xd0\xe1\xcf\xaa\x32\x1e\x2b\x58\x18\x18\x2e\xb6\x20\xbd\x5d\x16\xbc\x27\xa2\xda\x03\x5d\x4b\xc1\x78\x07\xcf\xe8\xae\x30\x38\xc5\xbb\xb8\xa0\x23\xfb\x23\x28\x14\xb9\x1b\x99\x74\x9f\x51\x9d\xe3\x9a\xa0\xf4\x34\x31\x33\x23\xb1\xb5\x82\x02\xc5\x91\x19\xb0\xbe\x21\x76\x17\x04\x7c\x9e\x2e\xa4\x53\xd6\x08\x56\x2c\xb9\x6c\x4f\x08\x51\xa7\x96\x5b\x16\x4f\x9b\xbe\x15\x1f\x9c\x50\x8c\xa2\x09\xf1\xaf\x65\x9e\x36\x38\x04\xc8\xd8\xfa\x1a\xd7\x00\xe2\x08\x66\xec\x9a\x1e\x50\x5b\x74\xbb\xab\x70\xcb\x47\x23\x08\x43\x1a\x3e\x87\x27\x2f\xeb\xf7\xcc\xe2\xc2\x0e\xc3\x7f\x5d\x68\xb4\xe4\x7b\xf3\x74\x10\x13\x72\x39\x36\xdb\x7c\x9b\x0f\x3d\xed\x96\x4a\xcb\x7f\x8a\xc9\xc5\xa6\xb4\xf2\x8d\xe1\x98", b"\x00\xa7\xc6\x64\xc5\x44\xcd\x7b\x61\x74\x94\x10\xdd\xa3\x3b\xb3\xa4\x7c\x3e\xb5\xa9\xa7\xbe\x5f\xba\x20\x1a\x39\x0c\xec\xfa\xef", b"\x6f\xbb\xda\x96\x7b\x58\x4b\xd9\xec\x6a\x0a\xe7\x6e\x0c\x55\x2b\x3d\x42\xbf\x0e\x9c\xf2\x93\x9c\xaf\x61\x23\xf6\xe8\x60\x46\xf6", )?; test( HashAlgorithm::SHA384, b"\xca\xb1\xd1\x76\x66\xb0\xc9\x65\x8c\xc7\x8c\xfc\xba\x17\xa0\x8e\x29\x89\xd3\xc2\x02\xc8\xb5\x08\x55\x31\x40\x4d\x92\x8c\x61\x8b\x6e\x23\x0b\x25\xc4\x6a\x5b\x58\x43\x7e\x43\x35\xfc\x04\x00\x20\xba\x00\xc8\x63\x18\x23\x25\x94\x0f\x00\xaa\xd3\x30\x14\x5e\x66\x6d\x07\xe9\xe9\xd8\x76\x13\x70\x10\x93\x2a\xe5\x20\xd9\x18\x8c\xa3\xd7\x99\x3c\x90\x53\x95\x21\x9c\x55\x84\x6d\x19\xb8\xfc\xdb\x1d\x0c\x15\x86\xb9\xb5\x10\x97\xaf\xd6\x97\x2a\xe1\x47\x2b\x0e\x20\x45\x3f\x8f\xbd\x5d\x6a\xa9\xe4\xa9\xa9\xb3\xdc\x37\xdd\x8f", p, q, g, b"\x50\x22\xc8\xa6\xfa\x79\xb7\xaa\x11\xa3\xd7\xaf\x5a\xce\xbb\x2e\xf8\xc5\x0b\x28\xd8\xf0\xe3\xa5\x56\x19\x65\x62\xd3\x41\x31\xfb\x44\xf2\x2c\x3b\xe3\xf9\x89\x5e\x35\xee\xe7\x0a\xa5\x3b\x6c\x67\x92\x0c\x54\x0b\xa6\xc1\x08\x5b\x0e\xa8\x18\xb1\x2a\xea\x81\x1f\x2d\xfa\xeb\x6d\xae\xd9\x76\xe3\x62\x43\x07\x98\xfd\xcc\xa3\x91\x2a\x08\x91\xe7\xd1\xc8\x3b\x74\x8a\xf1\xe7\x68\x9e\x03\x8b\x49\x0e\xb7\x3f\x7f\xe6\xe0\x61\x2e\x8f\x23\x85\x80\xe7\x88\x33\xb2\x07\x27\xa6\x02\x76\x8a\xb2\xd5\x9d\xda\x36\xe7\x51\x46\xfa\x4d\x36\x64\xf7\xb0\xce\xf7\xbe\x87\x7a\xfd\xcd\xba\x23\x00\x4e\xe3\x13\xa6\x9f\xd6\x1c\x32\x67\x59\xe7\xe7\x79\xad\x75\x0f\x7a\x5c\xad\x9f\xb2\xdd\x80\xa8\xee\xa6\xdc\xbd\xa0\x19\x5d\xcc\x17\xb3\x8a\xd6\xf0\xe2\xab\x68\xcf\xc6\x9b\x15\xc5\x72\xf8\x5f\x20\xc3\x67\x9c\x15\xa8\x30\x99\xcf\x08\xa3\x79\x05\x5f\x8f\xbd\xd8\xf5\x90\xd4\x3b\xd1\x2f\x75\xba\xf0\xec\xcd\x6c\x07\x7a\xc7\x58\x9a\xab\x81\x71\xe8\x87\x5d\xb0\x12\x2e\x6c\x78\x61\x7c\x13\x58\x61\x43\xa7\xeb\xe9\x04\xa7\x82\x2b\xac\xf4\x8a\x75\x27\xf7\xfa\x4e", b"\x7b\x8b\x75\xac\x85\x14\xc6\x8d\xe0\xca\xa9\x8e\x9d\xe0\xb9\x60\x72\x53\xd8\x08\x8d\x3f\xea\xdf\x92\xb8\x3f\xfc\x26\xe0\x88\xce", b"\x4b\x10\xe1\x7f\xf6\x4a\x0e\xb7\x2f\x70\xa8\x63\xd0\x0a\x9b\xf3\x31\xbb\xb5\x15\xba\x3a\x9f\xef\x72\x75\x3a\xd7\xf0\xdf\x0b\xe5", )?; // [mod = L=2048, N=256, SHA-512] let p = b"\xf6\x3d\xa3\xbe\x9a\x96\x16\x19\x6c\x65\x56\xf3\xce\x6f\xd8\xb9\x8b\xdd\xa9\x13\x74\x73\xda\x46\xfe\xd9\x70\xe2\xb8\xd1\x47\x38\x7a\x81\x92\x20\x65\xd5\x28\xa7\xd6\x43\x3e\xbc\x5e\x35\xb1\x5c\x67\xea\x35\xa5\xa5\xbf\xf5\xb9\xce\xf1\xcd\x1e\x6f\xe3\x1d\xda\x52\x83\x8d\xa3\xaa\x89\xb9\xb4\xe8\xd9\xd3\xc0\x73\x2c\xcc\x4f\x23\x8c\xe1\xb4\x16\xc4\xca\x93\xf2\xc6\x80\x0e\x5f\x4e\xd4\x1c\x4f\x76\x15\xce\xc5\x53\x1b\x98\x68\x0b\x20\xdc\x63\xf7\x3e\x70\xd8\x03\xaa\xcf\xae\xce\x33\xd4\x5f\xa0\xe3\x9d\x77\xc8\x50\x82\x09\x52\x8b\x90\x46\xb5\x91\x70\x10\x79\x12\x34\x39\x7e\x41\x2d\x22\xbc\x0b\x8d\x67\xcb\xd1\xcd\x28\xa3\x2c\x24\x60\xa0\xbd\x86\xaa\xba\x0e\xea\x80\xe1\x6e\x32\x45\x64\x31\x71\xe3\x42\x21\x76\x0c\x20\x3a\x56\xb8\x20\x7a\x10\x09\xe6\xc1\xa2\xf6\xcd\xa8\x5f\x85\xc4\xf9\xe4\x10\xb9\x49\x92\x33\xc0\xee\x07\x2e\x46\x5a\xf4\xfb\x4f\xb9\x28\x2c\x5c\x10\xe8\x23\x4f\xd6\x30\xea\x92\xf0\xaa\xe6\xb9\x7a\x52\x0d\xb3\x44\x75\x70\x7b\x79\xa4\xc1\x75\x26\x5c\x03\x56\xcc\xbc\xa8\x27\xe3\x83\x7d\xf3\xd6\xd0\x57\x6d\x90\x79"; let q = b"\x9b\x74\x63\xf8\x26\x9f\x0b\x90\x9a\xbe\xd1\x09\x91\x68\x4f\x36\xa6\x4a\xc8\x64\xe0\xd6\xd7\x17\xc0\xef\x21\x57\x7a\x4c\x39\x07"; let g = b"\x97\x2a\x75\xf6\x06\xe8\xaa\x3a\x91\xff\x08\xfd\x13\x1a\x20\xf5\x96\x32\x51\x30\x4e\x3d\x14\x31\xb7\x12\xfa\x08\x03\xd5\x27\xfd\x71\x0f\xb7\xeb\x27\xe5\x29\x04\x97\x1c\xd4\x3c\xa9\x77\x19\x9a\x24\xdb\xee\xb4\xb7\xbc\x2b\xa0\x75\xd3\xb7\x2e\xb6\xb2\xc5\xad\x8f\x0e\x8b\x8f\x48\xc5\x0b\x55\x4c\x7e\x07\x11\xf4\xc7\x41\x63\x30\x80\x66\x72\x49\x8f\x43\x02\x92\x72\x4b\xf9\x8a\x8e\xa4\x8c\x7f\x53\xd7\xb3\x1d\x8b\x75\x28\xb1\xa6\xf0\x87\xd2\xc2\x7c\x33\x52\x02\x83\x5b\x1e\x31\x42\x25\xb3\x7a\xef\x8b\xfc\xec\x7d\x80\x92\x0c\x4a\x46\x0a\x3d\x68\x34\x4d\xed\x75\xed\x9e\xe8\x67\xfa\x2a\x69\x45\x06\x38\x94\xf5\x63\xb6\x86\x33\xb8\xb3\x9f\x83\xa1\xaa\xaf\x5a\x96\xc7\xf4\x22\x68\x7e\x7c\x84\xcf\x8f\xb8\xcc\x5f\x45\x04\xdf\xf0\x87\xbc\xb2\x6a\x95\xbb\xf8\x58\x3f\x03\xb3\xa0\xe4\x3a\x35\x6b\x2b\xd7\xe2\x5c\xdd\xdf\x7a\x01\x53\x00\xfa\xec\xc6\x79\x3c\x5e\xe9\x9b\x63\x27\xcb\x84\x56\xe3\x2d\x91\x15\x33\x9d\x5a\x6b\x71\x2b\x7f\x9d\x03\x01\xac\xb0\x51\x33\xe3\x11\x5e\x45\x4d\x3a\x6d\xd2\x4a\x16\x93\xc9\x4a\xab\x54\x06\x50\x4b\xf7"; test( HashAlgorithm::SHA512, b"\x8a\xb0\x15\x10\xcf\xa3\x3c\xfa\x5b\xcf\xf0\x03\xbb\xa3\x99\x96\xfa\x72\x76\x93\xab\xf6\xac\x01\x0b\xb9\x59\xb0\xb5\x9a\x15\x30\x6c\x0c\x3a\x19\x21\xaf\x2a\x76\x71\x7a\xa5\x5b\x39\xfa\x37\x23\xf4\xc3\x22\x9c\xa9\xac\xf6\xb7\x41\x61\x4b\xb5\x51\xcd\xe8\xa7\x22\x0a\xb9\x7d\x4b\x45\x3b\xec\x1e\x05\xa0\xea\xa4\x2e\x38\x2b\xbc\x7b\x9b\x84\xf8\x23\x7d\xc8\x96\x4e\xe5\xb6\x6e\x9b\x2a\x4c\xa6\x1c\xf6\x75\x14\x0e\xfe\xf5\x4f\xb3\x27\xa6\x65\xde\xf8\xd5\x7a\xb0\x97\xe8\xc5\x3c\x64\x3f\xcb\x58\x20\x9c\x42\x15\xb6\x08", p, q, g, b"\x41\x19\x7c\xe2\x23\x3d\x7e\x48\xc8\x03\xcd\x64\xc7\x8f\x65\x79\x23\xb9\xe3\x6b\x87\x14\x01\xf8\x66\x1c\x21\xd8\xba\x38\xc6\xb9\xb3\x23\x9d\xb7\x67\xb1\x1d\x1d\x40\x1e\x5f\xae\xcb\xf7\xa4\x58\x60\xcc\x5f\x1a\x54\xd6\x02\x86\xb7\xd6\xe1\xc9\x9f\xd5\xb8\xc8\x4e\xd8\x51\xc5\x35\x7d\x41\xad\x60\x16\x3f\x22\x4d\x78\xc9\x96\x14\x3f\xff\x89\xdd\x3a\x8f\xe1\x23\xda\xe1\xf6\x21\x42\x7f\xd8\xcc\xe7\x6e\xd1\x38\xd6\x8f\xa2\x48\xf3\x74\xae\x23\x32\x49\x62\x5b\x93\xf3\xdd\x59\x37\xd1\x5e\x54\x1b\x7e\xff\xa4\xdf\x4f\xea\x7d\x52\xfa\xce\xd6\x15\xbf\xe0\x34\x84\x18\xff\x93\xe6\x9a\x20\xa5\x2e\x55\xc7\x6c\xc3\x0f\x30\x7f\x84\xe7\x1e\x4a\xab\xc0\x82\x5e\xca\x3a\x95\xb4\xbd\x58\xeb\xfb\x00\x29\xd2\x3a\x16\x9e\x9d\x80\xba\x7d\x1c\x5f\xd3\x53\x95\xe6\x60\x2e\x08\x9a\xa9\x91\x8f\x08\xba\xe3\x5a\xe1\xca\xc7\xaf\x33\x69\x41\x29\xe9\x8f\x0d\xad\xad\xd9\x0e\xae\xb6\xee\xd2\x50\x24\x39\x0b\x1a\x60\xaf\x79\x47\x34\xc3\x97\xb0\xf5\x09\x86\x5b\x13\x4b\x28\x67\xc1\x15\xd6\xf4\x89\xb6\xdd\x7e\x3c\x82\x99\x4b\x45\xdc\xe2\xa2\x3c\x6b\xc9\x02", b"\x6a\x47\xea\x57\xce\xae\xcc\x11\x6d\x71\x90\xff\x6c\x6d\xd9\x83\x1a\xb7\x5b\x4b\xf6\xcb\x29\x10\x83\xe4\x26\x8b\x48\x6e\xd2\x45", b"\x01\x73\x55\xf6\x98\xa3\x2a\xbe\x9a\x4d\x4a\x7d\xda\x7c\x85\x95\x0c\xdd\xc3\x48\xab\x8a\x67\x51\xe7\x2f\xdd\xc0\x1a\xa5\xd1\xf0", )?; test( HashAlgorithm::SHA512, b"\xb2\xf5\x69\x48\xa3\x36\x98\x2a\x5b\xcb\x4b\xb5\xd7\x9e\x3f\xe5\xc3\x60\x81\xbd\x28\x6e\x6e\x02\x1a\xb2\x9b\x52\x2f\x0b\xe5\xff\x5e\x81\xe6\x38\xf2\x3d\x07\x81\xc2\x68\xa8\x9b\x09\x33\x25\x75\xcb\x31\xc0\x80\x4b\xbd\x34\xc8\x05\x89\xfb\x11\x57\x0f\xc6\x5b\x3f\x67\x61\x26\x05\xa9\x41\x1c\xda\xb3\xac\x00\xff\x3f\xce\x33\xab\x22\xc4\x6d\x26\xbf\x9c\x3f\xc5\xad\x2d\x90\x18\xde\xb9\xb6\x69\xb5\x0f\xbf\xba\xf8\xbe\xd6\x23\x0c\x7b\xd6\x21\xd5\x64\xfb\x1a\xf9\x53\xf0\xe8\x2c\x5b\x55\x20\xab\x97\xba\xcc\xf5\x8d\x6e", p, q, g, b"\x72\xb8\x4e\xb6\xa6\x0c\x68\x6f\x74\xf3\x76\xe2\x6b\x2e\x47\xe4\x4a\x6d\x5d\xd9\x2c\x06\xfd\xe4\x9f\xaa\xd0\xaf\x9b\x11\xe4\x31\x47\xce\x93\x08\xef\x35\x01\xa7\x52\xe7\xbf\x18\xe9\xe6\xdf\x3c\x0a\x49\xc4\x4c\xd2\x51\x5a\x05\x50\x8f\x80\x60\xa6\x1e\x6e\x6f\x1b\x2e\xcf\x14\xb3\x38\xcf\x0f\xd8\xb7\xcc\xbe\x67\x8d\x52\xdb\xdf\x20\x35\x2c\x15\x5a\x2b\xd5\x17\xd8\x27\xd6\xce\xfb\xf4\x8c\x56\x79\xc9\x98\x29\x8e\x21\x86\xef\x10\x98\x16\x0d\xfb\x65\x91\x45\x06\xa1\x77\x94\x3a\x4a\x05\x82\x82\x38\x2d\x32\x7a\xd3\x6f\x88\x30\x1b\xe6\x93\xc0\x20\x00\xc7\x24\x63\xe6\x82\x42\x1a\x02\x37\x80\x4d\xbb\x27\x33\x5c\x78\xe8\x49\x5f\xac\x78\x42\xd2\xaa\xfe\xbf\x90\xf3\xc3\x60\x5f\x75\x86\x15\xdf\x98\x9f\xdb\xd0\x6e\x23\xe4\xad\x69\x74\xb6\x23\x84\xf0\xaa\x01\x02\x7d\xb8\x9a\xc3\xdc\xb0\x1c\xb5\x25\x8c\xdb\xd9\xc1\x93\x72\xa6\xc4\xaa\xdf\x27\x29\x80\x62\xac\x9a\x16\xde\x2e\xb0\x76\xe1\x67\xad\x7c\x65\xd0\x50\x5c\x8f\xce\xcf\x35\x9b\xb5\xd0\x5c\xd2\x2e\x7d\x48\x62\x9a\xf5\x39\xfe\x7f\x60\xe2\x3e\x95\x7c\x84\xc7\xa6\x1a\xc9\x2b\xf8", b"\x43\x70\x4e\x96\xcc\x8d\x63\xe6\xf5\xb7\xe1\x18\xcb\x7c\x03\x0d\x0b\xd5\x63\xb8\xf7\xa1\xa3\x04\xb3\x68\xa6\xc6\x6d\x7e\x7f\xa8", b"\x49\x0d\xa4\x3f\xd0\xf1\x9f\xec\x4e\xe0\x81\xcc\xe2\x5d\xf6\xb2\x72\x0b\x1a\x76\xb0\x23\xc1\x57\x04\xdd\x03\xef\x1c\x3e\x48\xa7", )?; test( HashAlgorithm::SHA512, b"\x9a\xe8\x47\x93\x27\xb8\xb8\xa5\x7f\x57\x0f\x6e\xc7\x6a\x1a\xc6\xf0\x2b\x19\x8c\x60\x48\xa1\xf0\x96\xe6\xce\x56\x30\xb6\xca\xf3\x63\x17\x64\x13\xd8\x80\x33\xb1\xcd\x07\xf4\xd3\x96\x0a\x12\xdb\xae\x8a\x65\x91\x74\xbb\x87\xc3\x7a\xca\x6e\xc5\x6e\xd5\xa6\x61\x9b\x8b\xa6\x76\xb6\x50\xd9\x7c\x6a\x21\xaf\x02\x39\x85\xdc\x36\x1f\xa2\x34\xb2\xb3\xc1\x7e\x77\x70\x3b\xa9\x9a\xe3\x21\x12\x60\xda\x10\xa6\x0f\x24\x0e\xee\xf4\x78\xf2\x64\x11\x84\xa2\x81\x71\x6a\xe5\x78\x88\x11\x7d\xba\x99\x28\x53\xf4\x94\xac\x3c\xaa\x45", p, q, g, b"\xce\x34\x8b\x5c\xb3\xd3\x68\x08\x42\x2a\x50\x16\xdd\x58\x73\xdf\x79\xf3\xcb\xb5\xe1\xb4\x58\xe8\xc1\x11\x02\x26\x04\x75\x43\xd9\x65\x76\x9a\x11\x2a\xdb\x4f\xce\xd0\xd1\x46\x23\x09\x62\xa8\xd4\x13\x22\x5c\xc7\x0d\x81\x0d\x40\xe6\xa7\x2e\x6d\xc8\x0d\xb5\x09\x40\x0c\x09\xd2\x63\xd6\x62\x06\x96\x6e\xd5\x1a\xb6\x59\x30\xa2\xaa\xc9\x9f\xcc\xe3\xa3\x98\xb6\x4d\x59\x09\x76\x83\xd2\xba\xa5\x76\x82\x70\x5a\xbc\x32\xeb\x8c\x32\xd6\xf1\xe7\xd9\x4c\xa1\x7e\xd7\x06\x78\x22\xcd\x20\xfb\xa3\x79\x5e\xd1\x84\x3c\x01\xb0\xd7\x55\x1c\x7c\x4c\x75\x9d\x53\xa4\x19\x14\x83\xbd\xc6\xe3\x12\x1c\x2b\xc1\x26\x07\x70\x1f\x43\xe3\xba\x38\x2c\x67\x66\x81\x9d\xb0\x7e\xf9\xc5\x95\x86\x93\x75\x14\x77\x2c\x2e\xcc\xde\x4c\x54\xd9\x25\x75\x73\x4c\x45\xa8\xe8\x32\xc4\x41\x7b\x43\xa9\x2c\x9a\xbd\x15\x22\x59\xcc\x0a\x96\x9b\xac\x64\xb2\x37\xbb\x3a\x08\x26\xae\x72\x91\x9d\x7c\x2d\xd2\xef\xdf\x03\xe8\x37\x01\x98\x0c\x2a\x8f\x50\xce\x6e\x44\xd7\xcc\x88\x48\x64\x5b\xf4\x0a\xef\xdf\x24\xfa\x7a\x6d\xce\x5a\x3b\x9a\xca\x6f\x01\x76\x18\xa6\x4d\x91\xce\x4b", b"\x00\x91\xd7\x50\xad\x9a\x4f\x29\x57\x3f\xd4\x57\xa5\x89\x1b\x68\xd4\xb6\xc1\x57\x03\xa2\xbc\x19\x2c\x7c\x62\x0c\x4e\x4c\x45\x29", b"\x92\xc4\x09\xc8\x97\x79\x75\xa4\x17\xd9\xf5\xe0\xe2\xdc\x70\x68\x3a\x53\xa9\x56\x62\xad\x27\x0a\xe3\x5d\x49\x65\x67\xa9\xa2\xfc", )?; test( HashAlgorithm::SHA512, b"\xe5\xa1\xa3\x44\xc2\x5b\xa0\xcb\xbc\xff\xe6\x80\x01\x35\xf2\xed\xe8\x10\x49\x18\x0f\xb2\x75\x9f\xd9\xe1\xaf\x3b\x81\x6a\xd5\x43\x6a\x24\xfa\xf2\x9c\xf3\xad\x91\xcf\x41\x33\x32\xf4\x54\xf7\x4a\x9d\x4f\x5e\xfe\x76\xcf\x02\x51\x2c\x27\x3c\xd5\x25\xf0\x4a\xfd\xb5\xc2\x4b\x05\x88\xd6\x11\xd7\x21\x53\x68\x0d\x1e\x39\x95\xe0\xaa\x75\x0e\x90\x77\xb0\x75\x2b\xd4\x44\x2b\xf7\xbf\xa8\xdb\xa3\x8e\x1c\x5e\x7d\xdd\x68\x7f\x55\xaa\x54\xc1\x38\xc7\xe6\xd5\xf0\x64\xf3\xec\x55\x94\x2d\xc1\x92\xdd\x99\x6e\x55\x36\x33\xaf\xd6", p, q, g, b"\x38\x59\xd4\x73\x5c\x14\xba\xee\xc1\x4b\x79\xcc\x26\x93\xff\xca\xc9\x00\xa2\xc2\x6e\xc6\x34\xa8\xe9\x77\xd2\x06\xad\x6e\xc7\xb1\x3f\x2d\x45\x0e\xf0\x47\x82\xec\x0a\xbb\x0d\xa4\x8f\x00\x06\x28\xce\xc1\xf6\xe9\xa7\x27\xbb\x59\xd7\xc0\xf0\xd7\x43\xf5\x13\xac\x09\x25\xbe\xb6\x1b\xf3\xad\x75\x82\x4f\xff\xae\x1e\xb7\x83\xeb\x1b\x68\xfc\x40\xd2\x87\x70\xe2\x80\xfd\xe2\x38\x44\xa1\x44\xd4\xb1\xa9\x54\x09\xb7\x55\xc7\xff\x2e\x5c\x67\x81\x1f\x3b\x1c\x2e\xb9\x6c\xb1\x59\xa6\x42\xd8\x4d\xd7\xb5\xdc\xcc\x2c\x0a\xef\x06\xd1\xcd\x54\xea\xc9\x4a\x11\x27\x3f\x94\x98\xf1\xe7\xa7\xcd\x79\xc1\x08\xe4\x96\xdc\xf5\x73\xef\x3a\x66\x10\xb7\x73\x1a\xb1\x4c\x16\x2c\xe8\x37\x7c\xb9\xb9\x07\x88\xe3\x56\xf5\x1f\x4b\x51\xa1\xec\x8b\xd8\x6b\xd8\x8f\xd4\xc3\x8e\x62\xca\xd6\x19\xab\x89\x41\xbc\xb9\x8a\x2f\x35\xee\x51\x2f\x4f\x8f\xfd\xd5\xee\x70\xca\xed\x84\x67\x15\x6b\x89\x3b\x35\x32\xa0\xa2\xaa\x51\x99\xce\xae\xcc\x5b\x19\x4b\xc0\x57\x96\x4c\xf4\x50\x66\x8c\x44\xf2\x7e\xc8\x0d\xe2\x1e\xa1\xa4\x15\xee\x6a\x65\x69\x83\x23\x94\xf6\xb4\x05\xd1", b"\x44\x36\x44\xe1\x27\xe3\x81\xb1\x7b\xb6\x6c\x53\x50\x97\x18\xa5\x8a\x30\xf9\x27\x42\x58\x06\xa6\x28\x40\x11\x9e\x78\xc2\x93\xb7", b"\x3f\x01\xe5\xd1\xe9\xfd\xb1\xcf\xda\x25\xef\xf3\xca\xcc\xf4\xed\xf5\x99\xfe\xa2\x77\x20\x1c\xf2\xb0\x1f\xfd\x7c\xb1\xa9\xa7\x27", )?; test( HashAlgorithm::SHA512, b"\xb8\x8c\x21\x20\x70\xbe\x39\x8a\x1f\x81\xe8\x5d\xfd\x71\xdc\x24\x24\xa3\x8a\xe3\x8a\x9d\x61\x08\x51\x86\x50\x4f\x4c\x2c\xbf\xa4\x92\xb7\x6d\xbc\xc0\x51\xce\xfd\xe0\x61\x6a\x7e\x33\x10\xb4\xbf\x17\x24\x4d\xe7\xd1\x0f\x84\x7c\xe2\xa9\xf6\x65\x94\x8e\x76\x72\x4d\x8f\x1f\x4b\xb3\xa6\x19\x19\xb2\xec\x7d\xc4\x7a\xd8\xa7\x2c\xb5\x99\x8b\x79\xfe\x3a\x15\x63\x95\xe4\xae\x88\xe6\x82\xb1\xdd\x16\xc5\x2d\x64\xcb\x4b\x31\xc3\x9d\x4a\x42\xa2\x1e\x62\x42\xdc\x0c\xdb\xb0\xac\xf3\xd4\x71\x82\x63\x8c\x5f\x21\x6d\xc6\xe8\xb1", p, q, g, b"\x54\x1c\x69\x0f\x4c\xa0\xc4\x2e\x52\x67\x64\x6f\x78\xef\x42\xfd\x68\xc3\x63\x37\x5b\x2e\x98\x3b\xe4\x44\xe4\x81\x9e\x63\xcd\xc1\x29\x01\x8b\xd3\xb8\xc6\xda\x8b\x70\x7c\x19\x6c\x35\xc9\x3e\xab\xee\x10\xe8\x75\xc4\x1f\xd9\x25\xbb\x3c\xe8\x06\x96\x93\x5d\x16\x31\x3f\xd3\xa2\x68\x58\xec\xcf\x2d\x50\x7f\xc2\xa1\x09\x50\x52\x5c\x67\x0d\xad\xc8\x83\xdc\x67\x79\xac\x1c\xe8\x66\xd8\x82\x03\x95\xf3\x54\x1c\x86\x30\x18\x33\x7a\x6b\xe9\x44\xdd\xc6\x44\xaa\xa6\xc0\x07\x19\x7d\x7a\x5f\x9a\xa5\x3a\x5e\x11\x80\xad\x51\xc9\x8b\xe9\xd5\x61\xa8\x5f\xe9\x73\x41\x60\xca\x35\xe4\xfa\xdb\x02\x52\x7b\xa0\xfa\x58\x04\x1b\x4d\x96\x38\x5f\x7f\x8f\xf6\xae\x75\x6a\xdd\x49\x68\xc0\xc2\x79\x9c\x0d\x68\x0f\x66\xc8\xce\x96\xf4\x98\x22\x87\x38\xe3\xe8\x7b\x7c\x86\x63\x44\xdb\x7d\x5a\x4e\xc3\x28\x24\x31\xae\xe5\x95\x1d\x9b\x4c\x83\xec\x2a\x0c\xda\x36\xcb\x2e\x2c\x43\x73\x63\xce\xba\x4e\x8e\x9f\x61\x28\x43\x9d\x12\xc5\x18\x68\xd0\xcb\x1f\x61\xe5\x3a\x68\xd4\xe7\x1c\x5a\x9e\x7d\xe4\x3c\x6d\xfc\xa2\x6f\x17\x41\xac\xa9\x16\xe4\x28\x26\x53\xbf\xc1", b"\x07\x8a\x71\x46\xa2\xc5\x09\xb9\x7a\x6a\x8c\x96\x3b\xaf\x1f\xbf\xbd\x1a\x2a\x5a\xa2\x14\xa1\x5e\xa4\x57\x63\xf0\xe7\x93\x0b\xeb", b"\x29\x79\xcb\xf5\x9a\xdb\x70\xf2\x8a\xc4\xfc\xb6\x92\x97\x49\x8f\x81\x63\x76\x4c\x62\xb3\x19\x63\xda\x9c\x8f\x9c\x0c\x43\xe0\x75", )?; test( HashAlgorithm::SHA512, b"\x4a\xdf\x1e\xd4\xfb\xb5\xb8\x2d\x7a\x2b\x1a\x29\x38\x43\x07\x53\xa6\x20\x7d\xa1\xcc\x04\x95\x74\xf0\xa1\x93\x14\x27\x2f\x9a\x80\xc6\xa5\x34\x98\xb7\x8e\x5c\x0b\x74\x01\xce\x48\x5f\xd4\xba\xeb\xc9\x66\xda\x6c\x1f\xcb\x02\x58\x16\xcf\xae\x32\xb5\x8a\xa8\x7f\x5e\x88\x85\x05\x47\x35\xf9\x3d\xf1\x9e\xd3\x2c\x81\x97\x86\xd4\x10\x9d\xbd\xa0\x47\xd6\x8c\x05\x89\x33\x07\x15\xe1\x05\x22\x64\x3b\xbe\x27\xe3\x2c\x0d\xc9\xc5\x83\x36\xbe\x30\x5b\x4c\x0c\x98\x1b\x40\xe0\xee\xda\x0d\xe4\x61\xd8\x44\x1c\x02\xc1\x8c\xea\xc5", p, q, g, b"\x8b\x69\x27\xfe\x29\x3a\xc9\x11\x1b\xa4\x06\x12\x5d\x6e\xbf\xbc\x30\xf9\x6c\xbf\xd6\x96\xfc\xac\x7d\xde\xd4\x23\x05\xc6\x10\x54\x53\xac\xcb\x1b\x0c\xa6\xf0\xf3\x16\x01\xf8\xc3\x4f\x96\xbb\x8e\xe4\xcc\xf1\x49\x92\x3a\x12\x82\x1d\xfa\xa2\xa3\x85\x9a\x39\xcf\x82\x56\x76\x09\xb2\x06\x0f\xf6\x09\x23\x2e\x90\x26\x1d\x66\xcf\x31\xfb\x92\x64\x67\x1f\x3f\x1b\xff\x6c\x8a\x95\x8e\x5c\xd0\x15\xdc\xc0\x2d\xfd\x2f\x02\xfb\x6a\x44\x3c\x2b\xf4\x5a\xbf\x13\x86\x20\x59\xdf\x98\x06\x6e\x00\x31\x1b\xb6\x43\x8b\x7f\xe2\xd9\x1e\x28\x75\x53\xd2\x54\x11\xf0\xfb\xa4\x74\x17\xc2\x90\x2f\x97\x8c\x57\x25\x7a\xe4\xea\xa3\xf9\x93\x17\xd5\xad\xee\x0f\x9a\xdf\x4d\x41\xe4\x10\x72\x55\x2b\x3f\x51\xeb\x99\x36\xa7\xf6\x3c\xc2\x8b\x46\x6f\xab\x64\x29\xd0\x68\x68\xd1\x8c\xa0\x9a\xba\x63\x40\x93\x76\x71\x92\x04\x9b\x02\xbc\xb7\x52\xeb\x67\x4c\x98\xa8\x68\x69\xd6\x72\x6f\x74\x2e\x57\xef\x8c\x3d\x45\x31\x17\x1c\x64\xf0\x3e\x10\xa4\xe4\x40\x39\xa4\x4d\x40\x7e\xbf\xc6\xb5\x6a\x7c\xdf\x6b\x17\x39\x4b\x53\xb5\x60\x43\x47\xc5\x1c\xf3\x75\x55\x1b\x73\x06", b"\x5b\x20\x11\x16\xd8\xbb\xc8\x7d\xb9\x90\x01\x70\x7b\x56\x7e\x7c\x34\x51\xd8\x02\xfa\x6c\x67\x9b\xf3\xdb\x34\x56\x71\x1a\x19\x13", b"\x5b\xe7\xe4\xc4\x93\xfd\x5d\x19\xb7\x71\x37\x31\x41\x29\x4d\xaa\xd9\x76\x56\xa3\xdb\xe3\xfd\x2a\xbb\xd3\xb6\xc6\x2c\x16\x61\x26", )?; test( HashAlgorithm::SHA512, b"\xbd\x49\x1c\xf6\x8b\x34\xf7\xba\x9a\xfe\x0c\x6e\xf5\xf2\xb7\x95\x6e\xf9\x64\x46\x5f\x28\xb2\x79\x7b\xc1\xd6\xe6\x70\xa6\xd8\x17\x30\xee\x29\x93\xd0\xb4\xaa\x96\x90\x51\x57\x02\x5d\x77\x5b\xa1\x04\xe7\xc1\x9b\x3b\x37\x2e\x85\x20\x26\xb1\x28\x6c\xbc\x6a\x48\xa1\x0c\xb9\x37\x8e\x97\xad\x96\x6f\x9c\xf0\x39\x17\xee\x8d\xb7\x5b\x62\x64\xe9\xb0\xa4\x8a\x0a\xe1\x0c\x2f\x46\x44\x47\x10\xd4\x23\x41\x26\xce\x45\x6b\x9f\xd1\x1a\xb7\xa3\x50\x49\x48\xd0\x46\xd5\xf4\x38\xd8\x93\xd9\xb1\x05\x2b\x8f\xac\x95\x47\x41\x54\x72", p, q, g, b"\xa9\x2e\x44\x65\x10\x76\x4e\xe1\xcf\x81\xc6\xb5\x9b\x51\x60\xa7\x60\x8f\xf8\x95\x2d\x04\x5d\xd6\x9f\x03\x4f\xdf\xef\x93\xf6\x33\x60\x7e\xc2\x09\xb1\x06\xc6\xac\x8f\x0c\xc6\xff\xa6\x4b\xb9\xa4\x48\x45\x60\xb8\x38\xd6\xf2\x4c\x99\x3a\x95\x4e\xfc\x9d\x5e\xe1\x66\x56\xaa\xba\x2a\x0d\x5a\x94\xe7\xa3\x46\xc7\xe5\x01\xaf\x83\xf1\x31\xdb\x9e\x0c\xab\x87\x89\xfa\xb1\x9b\xd5\x91\xec\x22\x7f\x39\xb3\x49\xbe\x7f\x8d\x0d\xf5\x8c\xa0\x39\x6e\xfb\x1e\x76\x54\x93\x35\x90\x4b\x88\xec\x21\xcd\x32\x65\xc5\x43\xc4\xe8\x0e\x9d\xde\x7c\xb5\xc9\xea\x8c\xdd\xa2\x3d\x96\xef\x1c\x38\x39\xad\xe8\xed\x4a\x5c\xd5\xfd\x98\xb7\x9b\xce\xee\xd9\xc6\x41\xc5\xa7\x75\x8d\x05\x29\xac\xea\xf2\x7b\x50\x14\xf1\x3d\xfc\xaa\x26\x7a\x14\xa0\x84\x1b\x36\x89\x7b\x6e\x1e\x89\x17\xb7\xf7\xcb\xf7\xcf\xf1\xd1\x95\x3a\xc4\x3c\xc0\x4a\xb0\x6c\xf1\x11\xe0\x06\x49\x7e\xb4\x2f\x28\xcb\xc9\x05\xd6\xf1\xcd\x5d\x83\x94\x85\x79\x83\xe1\xc9\xe5\x52\x01\x5a\x45\x1d\x0c\x13\xa6\x84\x8a\x8f\xc5\x6b\x79\xde\xc1\x72\x3a\x80\x67\xff\x18\x93\x1c\x85\x2c\xeb\x81\xaf\xfe\xc1", b"\x0c\xf5\x26\xd8\xa0\xf9\xc9\x12\xd1\x43\xf3\xf8\xaf\xde\xd4\x59\x8b\x2a\x5a\xaf\x20\x0e\x07\x49\xea\x27\xde\xfe\xb7\xf2\x8f\x3a", b"\x87\x7a\x90\x66\xf6\xc5\xae\x78\x25\x1d\x9d\x14\x0b\xcf\x39\xae\x91\x2d\x18\xbf\x13\x1b\xdc\x7e\x9d\x61\x01\x2d\xaa\xa4\x29\x2c", )?; test( HashAlgorithm::SHA512, b"\xc0\x0a\x8a\x2f\xff\xd1\x0b\xc2\xea\xb6\x3b\x8e\x37\x5d\x0c\x10\xf9\xdf\xae\x28\x48\xba\x42\xaf\xe6\x08\x5a\xee\xc2\x6e\x21\xaf\x3e\xaa\x49\x3c\xe4\xb3\xd9\x5a\x31\xfa\x50\x2a\x60\xab\x88\xe8\x05\xf4\xfd\xf8\x89\xed\x91\xc1\x54\x21\x71\x80\x84\xcd\x0d\x64\x47\x95\x74\x9b\x1a\x6b\x18\x3d\x74\x78\x2d\x52\xc7\xba\xbf\x74\x00\x39\x3c\xee\x69\x8a\xf5\xdc\x01\x0c\x0f\xf7\xf5\xac\xdf\x02\x08\xf9\x3e\xe7\xe4\xef\x58\xda\x12\x3d\xfd\xe7\xf0\xa3\x4e\x20\x9b\xba\xec\x61\x00\x72\x93\xfd\x11\xaf\xa6\x0b\x65\x22\xc4\x5d", p, q, g, b"\x75\x60\x10\x5b\x85\x86\xc4\x53\x2b\xf1\xb5\x1e\x0d\x2c\xf9\xa7\x13\xa5\xea\x5d\x40\xe2\x62\xce\x01\xeb\xda\xf1\xee\x53\xd8\x57\x12\x9e\x15\x29\xa0\xf8\xdf\xf6\x3e\x86\x20\x2c\x11\x1c\x6e\xb2\x89\x43\x9c\xb1\x5c\xd5\x9f\xc2\x18\xab\xe6\x19\xc9\x51\x62\x50\xf1\x27\xfa\xfe\x9a\x53\x07\x62\x74\xf3\x06\xf0\xb7\x87\x1c\xff\xbd\x15\x6b\x1a\x88\x19\x79\x5f\x0a\x99\x55\x86\x47\x56\x65\x02\x74\xb8\x3e\x67\xca\xa4\xe2\x15\xf8\x33\xaf\xd5\xa7\x7d\x05\x94\xb2\x1b\x4b\x54\x35\x6a\x98\xa5\x6a\x0b\xf6\x17\x7f\xaf\xfb\x9f\xdf\xd8\x88\xd6\x53\x8a\x1c\xe7\x60\x59\x85\x4b\xd1\xf0\xa2\x07\x61\x28\x1d\x7b\x75\x17\x57\xc6\xec\xc9\xb1\xe8\x13\x11\x96\xd0\x66\x95\x97\x21\x3a\xe7\x3e\xdb\x99\x65\xda\x9f\xf3\x72\x42\x08\x51\x15\x50\x11\xf6\x91\xa0\x3a\x7f\x1e\x20\x40\x29\x15\x75\xb8\x6f\x59\x59\x98\xa0\x6e\xf7\x9f\x4e\xad\xba\xe2\xbd\x9e\x2e\x47\x7d\xd7\x26\x84\xd8\xef\xdc\x1e\x83\x5f\x7f\x0f\x5c\x93\x63\x5c\x18\x1b\x96\xcc\x7c\x0e\xaa\x27\xee\x62\xc9\x22\x7e\xd9\x48\x5a\x8c\x82\x2b\x32\x24\xe9\xe2\xb7\xac\xc1\x09\x56\xf3\xd4\x9a\x6f", b"\x98\xfe\xe1\x0c\x85\xab\x46\xd3\x34\x75\x87\x34\x81\x9e\x68\xb5\x04\x64\x39\xcd\x0b\x66\xbe\x26\xd4\x37\x60\x61\x3a\xc7\x7b\x8c", b"\x66\x5f\xab\x98\xdd\x43\x7e\x06\xa4\xf8\x77\xee\x21\x89\x86\xe3\x7c\x2c\xb2\xd2\x37\xe5\x98\xd9\x8f\x1b\x7d\x4e\x82\x9a\x84\x6b", )?; test( HashAlgorithm::SHA512, b"\x27\xf0\x1b\x47\xd1\x5f\x7d\x19\x6f\x26\x67\xb7\x5e\xd1\x5b\x89\xd7\x44\x3f\xb4\xfa\xb0\x68\xf4\xad\xb6\x71\x75\xca\x70\x07\x1d\x52\xe2\x70\xf6\x89\x64\xf9\xfb\x0e\x0e\x14\xed\x5d\x29\x54\xa3\x3d\x93\x80\x7a\xcf\x3c\x82\x50\x0e\x8b\x9f\x5f\xc5\x51\x0c\xc3\xbd\x6a\xaa\x1d\xaa\xc8\x30\x91\x28\xef\x4c\x0b\x4c\xac\x02\x64\x25\xae\xfd\xd7\xe6\x9c\x22\xc3\x2e\x5f\x8d\x2a\x6e\x8f\x2e\xa2\x91\xac\x33\xda\x6c\x71\xa1\x95\x3e\x44\x3c\x0e\xa2\x06\x56\x8a\xad\xef\x2b\x96\x46\x6c\xbf\x76\xbf\x14\x9d\x89\xd8\x6f\x52\x9f", p, q, g, b"\x38\xfa\x99\x4a\x1f\x61\xab\x79\xee\x7a\x7e\x6f\x68\x9c\x38\xf6\xc2\x82\x6f\x06\x64\x7b\x16\x63\xcd\x81\x2a\xdb\x36\xd7\xfd\x7c\xcc\x50\xe9\xa9\x0d\x02\xbf\x7c\x3f\x12\xa2\x28\xc6\x92\xc0\x56\xfb\x3b\xd6\x08\xf5\x1a\xa4\x01\x02\x2c\x83\x97\x91\xe6\xa6\x78\x18\x5c\xd3\x1d\x88\xcc\x66\x1a\xf2\x9e\x5d\x23\x81\x42\x18\x1d\xd3\xf6\xe7\xc8\xb0\x57\x85\x22\x1e\x62\xfd\xb3\x6c\x71\xe0\x7f\x51\xd7\x32\xe7\xe0\xca\xb5\x20\xa7\xf2\xfc\x5b\x18\x31\xb0\xa6\xba\x28\x0e\x00\x32\x1c\xb9\xa0\x25\xdb\x65\x38\xab\xd6\x72\x46\x3d\xbf\xf5\xca\x81\x99\x36\x76\xbc\xba\xf0\xf6\xe9\xc7\x54\xf2\x4d\x65\x4e\xe7\x87\x9b\xc0\x3d\x7d\x4b\xc8\xe8\xca\x58\xfb\x9b\x39\x29\xa3\xc3\x83\x65\xcd\x2e\x20\x57\x29\xe9\xde\xf0\xa0\x01\x08\xdf\xfe\x94\x07\x27\x1e\x17\xd3\x55\xec\x4b\x29\x00\x3e\x0c\xaf\x0c\x5b\x2a\xcb\x9b\xd8\xe5\x2d\x44\x10\xba\xa9\xb9\x7a\x49\x87\x4c\x14\xbe\xeb\xf0\x3a\xbf\x28\xa9\xec\x59\xbc\x17\x38\xb8\xdd\x42\x23\xd4\x7a\xa3\x36\xac\xbc\xa7\x66\x2f\xc6\x9a\x6f\xef\xee\xcf\xfd\x47\xf6\x73\x7e\xcd\xa3\x31\xd1\xba\x5c\xdf\x02\x3d", b"\x8d\xad\x02\xc0\x2a\xd3\x4f\xe4\xe7\x58\xff\x5c\x81\xd5\x38\x4c\x40\xd2\xc4\x9d\x0a\xc7\x77\xba\xd1\xcd\xeb\xc5\x8e\xc0\x1c\xfd", b"\x0f\xe4\xe1\xf6\x87\x5c\x11\x3f\x1c\x17\xa0\xf0\xed\x22\x8d\x44\x21\x3f\x8d\x7e\x2f\x15\x56\x7e\x57\xce\xb2\xe8\xb1\x09\x8f\x7d", )?; test( HashAlgorithm::SHA512, b"\x73\xcc\x5e\x4a\x18\x8d\x28\x14\x46\x69\x41\x38\x90\x14\xea\x45\xa1\xa0\x65\x25\xd2\x06\x9c\xf4\x88\x3e\xbc\xb5\xf2\x2a\xb1\x28\xc0\x0f\x04\x1c\xf6\x9f\xd9\x4b\x33\xfd\xad\xe7\x85\x48\xf6\x52\x3c\x83\x8b\x87\xcc\xd8\x68\xf3\xd3\xd0\xa9\xa0\x00\xf2\x78\xba\x54\x04\x8b\x9c\xad\xac\x7a\x99\xd9\x8d\xef\x51\x71\x31\x91\xad\x83\xe5\x23\x2e\x3e\x86\x49\x72\x45\xc8\x0b\xc7\x10\xfd\xd7\xfa\xaa\xd8\x8c\xe9\x2c\x89\x4f\x8c\xad\x3d\xe0\x07\x5c\xab\xa3\x37\xa2\x22\xcb\x7a\x3d\x7c\x2d\x93\x7b\xcf\xe4\xb6\xe6\x9d\x38\x8d", p, q, g, b"\x52\x66\x42\x7a\xd4\xc1\xcf\x3e\xa2\x29\x37\x7a\xd3\x97\xc7\xd5\x61\x35\x12\xfc\x27\xf2\xce\x37\x40\x7d\x2c\xea\x8e\x19\x99\xae\xbb\x8f\x37\x67\xee\x96\xcb\x92\x7e\xbd\xd4\x3b\x8d\xbc\x10\xba\x2c\x47\x84\x3d\x3f\x43\x36\x8d\x9e\x44\x2b\xf5\x1e\xbc\xf2\x0b\x48\xb5\x43\xa4\xc3\x88\xbb\x3a\xe3\xe4\x02\x7a\xcb\x65\x7d\x1b\xf7\x4a\xbe\xb8\xb9\x98\x42\x13\x08\x77\x0f\x70\xb3\xf7\xb1\xd9\x10\x21\x9a\x12\x10\x26\x03\x40\x12\x3b\x95\xdb\xa1\x87\xe0\x0c\xb0\x67\xf7\xe3\x77\x92\x34\x12\x02\x55\x4b\xfc\x8a\x23\x5f\xc0\x1e\xcb\x09\x9e\xc3\x61\x5a\x67\xa3\x61\x0d\x4d\x8c\x2d\xad\x16\x08\x70\x24\xf5\x97\x3e\xb1\x84\x00\xc2\x9c\x05\xd6\x98\x4d\x1c\x15\xc1\x59\x42\x28\x27\xc0\xdb\xb2\xbf\x45\x09\xd7\x10\xc4\x97\x2e\xe9\x3b\xe7\x28\x3a\xad\xd9\x91\xae\x8e\xf0\xe9\x73\x12\x11\x8f\x19\x5d\x30\x4f\xbe\x96\xd5\xae\xbf\xb2\x12\x03\xea\xe6\x11\x78\x31\xf9\xbe\x90\x99\xd3\xd4\x76\xb8\x3f\x65\xab\x22\x5f\x8b\xe4\x93\xa8\xad\x21\x62\x0f\x25\x9d\x8a\x44\x20\x08\x10\xc8\xe5\x62\xae\xa8\xe7\xa6\xbc\x23\x8c\x12\x9b\x19\xf2\x53\x1a\x6a\xf0", b"\x77\x23\x6b\x33\xb0\x42\x85\x42\x57\x75\xee\x3f\x65\x8b\x37\x61\x29\x5c\xbf\xf8\xe4\xbc\x05\xab\xdd\x22\xe3\xd7\x8b\x1b\x6d\xa2", b"\x43\xfd\xbd\x93\x6a\xb4\x04\x59\xf6\x84\x30\x56\xca\x77\xe1\x25\xb6\xec\x5a\xd9\x45\x04\x1c\x1f\x6a\x27\x70\xbe\x9d\xfc\xc6\x82", )?; test( HashAlgorithm::SHA512, b"\xc0\x74\x6b\xef\xd2\xaf\xc6\xca\x15\xcd\xb1\x45\xc1\x84\x62\xc5\x15\xbd\x42\x79\x4c\x4c\x7e\xe5\x13\xcd\x9a\xeb\x0f\xc6\xfc\x30\x48\xb6\xc7\x23\x16\x34\x98\x4a\x1b\xe8\x24\xc7\x75\xf9\xc9\xb0\x28\x25\x5f\x5b\x3c\x3d\x8f\xa0\x8d\x47\xab\xa0\x77\x55\xb5\xf1\xb5\xb0\x08\x93\x3e\xff\x35\x83\x8f\x15\xa0\x2b\xa9\x36\x6c\x10\x36\xd3\xff\x19\xe8\x81\x99\xef\x86\xa8\x82\x27\x27\x2c\xf4\xe4\xe0\x0f\xfa\xd9\xc3\x6b\xeb\xac\x30\x57\x8b\x00\x21\x4f\xb2\x9b\xae\x43\xcf\x55\x5e\xd4\x31\xa2\xf2\x49\x22\x43\x0b\x14\x96\xfb", p, q, g, b"\x43\x1e\xee\x49\x09\x0a\xd5\x8f\x4a\x87\x4c\x2e\xb5\x89\x79\x69\xfa\xfe\x32\x74\xbd\x74\x86\xb6\x5e\x35\x19\xe4\x30\x9d\x63\x6a\xce\x68\x64\xd5\xca\x4d\x84\x48\xa3\x57\xca\xfa\xc1\x5a\xc3\xcb\x3b\xd7\xb2\x75\x5b\x3c\xb6\xdb\x0a\xf1\xa4\xe9\x1b\x2d\x1f\xcb\x28\x56\x1b\x17\x0f\xaf\x2e\x06\x90\x07\x1b\xc0\xf6\xe4\x2b\x2d\x82\xab\xe5\x64\x6d\xdb\x8f\x9b\x99\xee\x1d\xaf\x59\x06\x03\x6f\x39\x5d\x82\x4d\x08\x0b\xfa\xea\x10\x30\x48\xb3\xf4\x4d\x06\x36\xbc\x7a\x6a\x88\xe9\xb0\x04\xa3\x63\xb9\x9d\x24\xa8\x9b\x6e\x97\x37\x9b\x20\xba\xcf\x48\xc7\xae\x2e\x9b\xf7\xe2\x81\xfe\x3b\x4d\x7e\xb9\x47\xa1\x02\x39\x6d\x52\x3a\x1e\x85\xce\x17\xfd\x25\xf2\x71\xf3\xc2\x21\xa5\x68\x1e\x9f\xb7\x7d\x64\xd6\x24\x10\x39\xac\x8a\x85\xda\x32\x74\x1b\xac\xf0\x06\x60\xe4\x21\xfe\x85\x0a\x0f\xe7\x3a\x08\xee\x3a\x9b\x06\x9c\x6d\x91\x14\xc1\x97\x52\x72\x12\x74\x68\xf9\x00\x85\x52\xea\x4c\xdf\x9d\x96\x56\x1e\xa6\x9a\x64\x66\x95\x24\x25\x00\xf2\x31\x8b\xda\x82\xda\x63\x3e\xf1\xae\x04\x97\x01\x4a\x63\x7b\x15\xa5\x72\xdd\xdd\xec\x07\x0d\x19\xd8\x84", b"\x79\x6d\x7d\xba\x32\x2d\x92\xa0\x83\xda\x7a\x58\x8f\xb6\x23\x8d\xc8\x6b\x1f\xc5\x10\x4e\xd6\x00\xc9\xb4\xc6\x88\xed\xf8\x05\xe9", b"\x01\x2c\x1f\xf4\xde\x8e\xe3\x86\xb9\x51\x27\x5e\x25\x05\x81\xd6\x61\xd0\x30\xa4\xd8\xfe\x11\x54\x32\x28\x8a\xb0\xa4\xbd\x46\xcb", )?; test( HashAlgorithm::SHA512, b"\xb8\xb9\x15\xcf\x4e\xa3\xb0\xc4\xcd\xcd\x8b\x2a\x06\x47\x9e\x71\xbb\x47\x97\x29\x4b\x6c\x41\xca\x87\x0d\x3c\xb2\xec\x2c\xb5\xa4\x9f\x6b\xfe\x5b\xcd\x10\xbe\x60\x9e\xd3\xe1\x88\x2a\x31\x23\x95\xfc\x99\x13\x45\xab\xa5\xb5\x66\xe6\x79\x60\xb4\x29\x13\xdb\x66\x90\x41\xea\x30\xc2\x99\x47\xed\xde\x7b\xdc\xfc\x08\x96\xb9\x76\x60\x74\x0d\x6c\x79\xf0\x08\x86\x65\xf5\x1d\xad\xcf\xa0\x7f\x7b\xe4\x48\x21\xd6\x0a\x8f\xfd\xe4\xe5\xcb\x1f\x98\x13\x9f\xf9\x1c\x9c\x6f\x31\x26\x59\x63\x44\xc5\xf7\xef\xf4\x00\x49\xd3\xf9\xae", p, q, g, b"\x1b\x37\x22\x76\x42\x64\xe1\x79\x94\xf3\x34\x3b\xf2\x60\xc7\x35\x75\xd1\x06\xf6\x30\x7f\x2e\xaa\x3f\x7d\xcd\x5a\xf8\x04\x46\x3d\xdb\x6b\xbe\x38\xa3\x8f\x5a\xb5\xa8\xae\x67\x01\x31\x7c\xf6\xc2\x67\x04\x9f\xc9\xb8\x40\x78\x24\x1f\x82\xd3\xc6\xb7\xe5\xbe\xba\x5c\x14\x27\x03\x02\x97\xf1\xdf\x25\x81\x48\xe5\xf9\xeb\x41\xeb\x20\xa8\x68\x77\xfc\xc0\x6e\x53\x73\xcd\x50\x56\x26\x13\xd3\x07\x64\x95\x39\xd2\x8c\xb5\x24\x18\xd4\x2f\xd5\x97\x58\xb6\x11\x85\xe7\x92\x99\x2b\x5a\x58\x12\x29\xb4\x34\x03\xd7\x93\xb0\x4d\x87\x8e\xb9\xb9\xd1\x2e\xa1\x0d\x2e\x64\xd1\x53\xd3\xfa\x41\x88\x1f\xe7\x9a\x67\xac\x40\x8a\x53\x48\xd7\x92\x39\x56\x7d\xca\x96\xe1\xea\xd3\xc6\xac\x22\xdb\xcd\xbc\xb5\x18\x5b\xf8\xac\xe5\x76\x60\xa4\x25\x21\x04\xe5\x04\x7c\xac\x87\x85\x1d\x28\x15\xb1\x2a\xe8\xae\x96\xab\x2f\x33\x34\x5e\xa1\xcf\x5f\x2e\x58\xa4\xdd\xcb\xa2\x62\x65\xc0\x6d\xf6\x5a\xfc\xc6\xe8\x52\xb3\xf9\x10\xc8\x77\x8d\xe2\x8a\x9f\x09\x81\x58\xed\x0e\xca\x65\x2d\xda\x2f\x9f\x4a\xc8\xa1\x7a\x9b\x25\x24\x10\xec\x59\x73\xa6\x06\x3b\x64\x25\x68\xf1", b"\x6b\x4b\x0e\x1e\x7c\xbd\xef\xed\xb1\xdf\x1f\x52\x9e\xce\x47\x89\x1f\x7b\x9e\x95\x9a\x3f\x85\x56\xba\x4b\xef\x7b\xb9\x85\x65\x60", b"\x7e\x93\x3b\x44\xed\xe6\xb2\xe9\x41\xb6\x0c\x37\xdc\xd1\x56\x82\x84\xde\xf2\x29\xc0\xa2\xbb\x90\x93\xf4\x82\x90\x00\xc4\x40\x9a", )?; test( HashAlgorithm::SHA512, b"\xdf\xfd\x45\x8a\x80\x8f\x18\x89\xd7\xf3\xd6\x19\x7f\x0e\x41\x92\x0a\xd7\x31\x12\x4c\xee\x30\x8c\xb9\x0d\x23\x61\xb2\x3f\xee\x96\x9c\x0e\x10\x58\x35\x54\x9e\x5d\x0a\x3f\x76\x90\xd5\x86\x2d\x4c\xd6\xcc\xb3\x3a\xd1\x80\x94\xc8\x5c\x96\x50\xd7\x5b\x24\x84\x96\x39\x0a\x0b\x89\xe7\xdc\x7d\xc0\xd3\xa6\x13\x0d\xd9\x77\x89\xeb\xf1\x05\xf8\xe5\x5d\x8f\x0a\x11\x62\xfb\x3c\x6b\x52\x9e\x2a\x80\xdd\x51\xe9\x04\x5e\xf8\xec\x42\xca\x4b\xc4\x6a\xbb\x65\x39\x58\x8b\x53\x1c\x97\x99\x56\x0c\xf4\xea\x80\x6c\x3d\x93\xd0\x43\xe5", p, q, g, b"\xea\x43\x7a\xd0\xee\x92\x64\xde\x87\x92\xb6\x77\x20\x7e\x54\x70\x90\xb3\x2d\x6a\xb4\x60\xb4\xd5\x89\xd8\x42\xed\x0a\x0b\x4f\xb4\xc6\x35\xe4\x44\x3b\xf6\x0e\x46\xcb\xa8\xd2\x26\xf6\x59\xc7\x6d\x2c\xa0\x1c\x69\x70\x7b\xa6\xd9\x77\x25\x5c\x45\x84\xb7\x47\x40\xa7\xcd\xec\x4c\x97\x3e\x3d\x16\xab\x6a\xf6\x0c\xd3\x12\x3c\xa1\x2e\xd5\x97\x1e\x69\xea\xff\xa3\xda\x07\x70\xd8\xe1\x22\x88\x89\xcd\x68\x25\xe1\xb8\x58\x46\xf4\xf7\xec\xdb\x33\xf1\xe5\xc7\xac\xd6\xb2\xad\xd1\x30\x8c\x5c\xec\x43\x97\x28\xd0\xcc\x62\x5e\xb8\x9d\xf3\x4f\xb9\xc0\xdd\x45\x68\xf9\x79\xde\xea\xd2\x86\xc5\x01\x45\x90\x3a\x0d\xcc\xca\x72\x39\x87\x4b\x46\x83\xd3\x67\xed\x31\x69\x6e\xec\xad\xa9\x0d\xce\xd8\xa9\xb1\xe0\x13\x64\xb8\x79\x46\x60\xc6\x0f\x40\x59\x07\x94\xc9\x5a\x61\x4c\x04\x56\x3c\x92\xd4\x44\xb5\xec\xf0\x12\x86\xb1\xbf\xfe\x9e\xd9\xef\x91\x5b\x4d\xb8\x20\xea\x5c\x9a\x5b\x3d\xed\xcf\x89\xa3\xe2\xc3\x78\x71\xd2\x1b\x76\x39\x90\xc7\xbb\xf4\x44\x18\xf9\x1c\xdb\xce\x43\x61\xee\xb2\x27\x51\x6c\xb3\x44\x40\x9d\x2c\x65\x1f\x0d\xc2\x9e\xc8\x26\x23", b"\x31\x52\xfc\x28\x6f\xed\x44\xf2\x8b\x1a\xf2\xd5\x37\x59\x2c\x56\x91\xd6\x79\x8c\xae\xd9\x05\x91\xb5\x88\x8b\x0d\x6f\xe6\xbb\x07", b"\x7b\xff\x61\xa8\x67\x6f\x0d\xf1\x89\x65\x4f\x25\xc5\x81\x2b\x34\x1d\xd1\x7f\x4f\x44\x66\x77\x89\xcc\x88\x7c\x19\x1b\xf4\x72\x02", )?; test( HashAlgorithm::SHA512, b"\xa6\x51\x60\x19\x72\x7d\x95\x63\x9d\xb0\x38\xf9\x03\x06\xa8\xd9\x4f\xac\x52\x43\xdc\x7b\x67\xc3\x56\x8d\x63\xd8\x5d\xea\xd1\xcf\xdd\xbb\x2b\x33\x0b\x61\x95\x89\xbd\x58\x2a\xf1\x5f\x08\x11\x17\x75\x04\xfd\x5b\x7a\xad\x7b\x29\x86\x47\xa3\xf6\x47\x97\xe3\xda\x5f\xe5\xbf\x87\xb6\x5c\x2d\xde\xc5\x76\xa8\xf4\x06\x60\x68\x6b\x80\x8b\xa4\x2e\x54\xbf\xd0\xe9\xe4\x80\x82\xd6\x90\x4f\x8e\x19\x05\x0e\x54\xea\x47\x97\xa2\xf4\x01\xff\x7c\x9f\x3d\x21\x7b\x52\x6c\x03\xbe\x92\x01\xc0\xdc\x1b\x0e\x8e\x05\x4b\xbb\x32\xc3\x82", p, q, g, b"\x97\x79\xeb\x53\x38\xdc\xae\x73\x77\xb1\x84\x70\x18\xce\x72\xc1\xed\x4c\x55\x29\x2a\x96\x3f\x81\x60\x8e\xf3\x32\x05\x0f\x0a\x48\x45\x19\xaa\x96\xb1\x8b\xcc\xe8\xe1\xb4\x9c\x11\xa2\x00\xc1\xab\x4a\x75\x72\x6b\xcc\x84\x24\x85\xdf\x63\x14\xe5\xc3\x9f\xec\x62\x2d\x81\x94\x34\x29\x4d\xbe\x1e\xb6\x47\x88\x5c\xe8\x41\x52\x7c\x03\x48\x1b\x7f\x22\xee\x58\x6d\x8c\x2b\x1a\x84\x71\xa2\x75\x7b\xff\xbd\xd9\xc2\x6f\x12\x50\x65\x68\x55\x09\xff\x0e\x4c\x8b\x82\x6d\x73\xc6\xe1\x2f\x6d\x4b\x93\x19\xcd\xfa\x72\xc0\x69\xe0\x7b\x2d\x2c\x25\x4b\x33\x0c\x06\xf4\x88\xd6\x59\x8c\x74\x76\xce\x0f\x33\x30\xc9\x7e\xc3\x6b\x7c\x10\x87\x13\x88\x47\x24\x51\xa3\x4a\xfb\x7b\x4d\x4e\x25\x1f\x9f\x72\xa4\xa3\x8a\x68\x51\xaa\xb8\x65\x07\xb2\x83\xe8\x90\xc3\x1b\xa9\x6d\x0a\x1e\x55\x72\x63\x7b\x2d\x84\x67\x06\x0c\x07\x36\xd1\x1d\x07\x44\xe3\x32\xa1\x9f\x59\xae\x29\x20\x89\x4e\x9c\xff\xac\xfe\xda\x64\xae\x1f\xf4\x86\x98\x82\xdf\x3b\x69\x0c\x7c\xe8\xfe\x0e\xb8\x81\x71\xe4\xf2\xab\x86\x24\xe6\xac\x77\xdc\x89\x07\x61\x32\x35\x16\x3e\x0a\x2c\x7d\x9f\xd6", b"\x72\x22\xd5\xeb\x39\x24\x60\xde\xfe\x8f\xe3\xdf\x18\xfa\x53\x4f\x30\x60\x23\x5f\x1e\x8d\xce\x53\x70\x76\x2e\xc6\xfc\x11\xe6\x90", b"\x43\x51\xc4\x28\x03\x1c\xd9\xaf\x56\x7b\x11\x63\x03\x7a\x4e\x37\x69\x62\x62\x0c\x4e\xc2\x3c\x43\xb7\x10\x58\x79\xf9\x5b\xf6\x14", )?; test( HashAlgorithm::SHA512, b"\x1f\xfa\x7c\xf5\x5f\x92\xf2\x34\xa2\x4b\xd3\x29\x67\x44\xd5\x43\xa4\x33\xc9\x07\xc1\xf7\x7d\x8b\x70\x6f\x4b\x62\x62\xd0\x96\xe2\xdf\xe7\x13\xfa\x9c\xa8\x0e\x68\x57\x93\x96\xfc\x11\xa1\x2c\x03\x31\xcf\xb7\x74\x5d\x96\xb0\x05\x20\x4e\x48\x3f\xbf\x8f\x9f\xdc\x45\x8e\x2c\xa8\x61\x34\x06\x06\x9d\xf5\xf4\x49\x18\xef\xf8\xc5\xf5\x4b\x8b\x4d\x97\x2e\x07\xa4\xb8\xe0\x6d\x84\x26\xa7\x08\x74\xce\xfe\x6e\x93\x40\x4c\x1e\xb3\x81\xc2\xd0\x70\x1c\x37\xf8\x5a\xfb\x16\x01\xa0\x9f\xff\x8e\xcf\xda\xf6\xcb\x64\xad\x9b\xd8\xb7", p, q, g, b"\x18\xe2\x98\xe6\x30\x13\x89\xd4\x86\x44\x67\x4f\x83\x39\x48\x7a\x86\x51\xb0\x76\x8d\xee\x42\x59\x05\xe8\x03\xab\x35\x7c\x7f\x9f\xa0\x5d\xd5\xe2\xee\x84\xbf\xe1\x05\xa0\x92\x71\x62\x74\x55\x7e\x06\x3d\x08\x6e\x78\xb7\x81\xa4\x3c\x56\xa4\xe0\xea\x11\x5c\x5c\xfe\xac\x57\xa4\xc9\xb7\xe1\xef\xfb\x89\x41\x36\x89\x92\x8f\x15\x46\xfe\xb3\x07\x38\x58\x6d\x36\xff\xe3\x38\x08\x3e\xe2\xbf\x5c\x5b\xd3\x44\xbc\x3d\xb2\xa7\x97\x7d\xe2\xb1\xab\x5b\xa0\x06\xd9\xee\x93\xef\x86\x88\xa7\xd1\x0c\xaf\xe2\x7a\xf3\xe6\x71\x01\x3a\x81\x69\x84\x19\x6b\xfa\xcf\x00\x23\x35\xfe\x74\x14\x42\x3e\xd8\xbd\xc8\x03\x27\x37\x2b\x0d\x46\x08\x66\x48\x0b\xdf\x07\x3c\x9d\xef\x79\x77\x13\x1b\x06\xe2\x8d\x14\xae\x1a\x81\x6d\x32\x22\xeb\xaa\xdc\xc8\xd7\xc3\x00\xaa\x82\x0e\x03\x28\xaf\x66\xf7\x42\x06\x1a\xff\x5d\x4b\x71\x76\xa9\x94\xad\x69\xb3\x90\xbb\xdd\x61\x9f\xce\x04\x7d\xc7\xd1\x5a\x48\xea\x71\xaf\xa7\x20\x40\xbb\x14\xee\xaf\x4a\x2b\x23\xd9\x9b\x4d\x97\x7b\xeb\x6d\x80\x61\x01\x02\x1e\xb0\xc3\xa0\xe3\x1e\x54\x57\x9e\x58\xc9\x53\xb5\x5b\x6e\x32\x45", b"\x21\xd7\x0e\xd9\x55\xb0\x9e\xa3\x02\xfb\x79\x29\x78\xd1\x25\x01\x07\x1a\x2e\x8e\x2c\xc8\xf6\x59\xde\xcd\x3d\xf2\x4e\x37\xc4\x66", b"\x2c\xda\xae\xe2\xa5\xa3\xdd\x74\xa6\x77\x95\xf9\x3a\xc1\xd8\x41\x62\x23\x83\x6c\x76\xf7\xfe\x31\xc7\x2e\xc6\x17\x09\x25\xfd\x73", )?; // [mod = L=3072, N=256, SHA-1] let p = b"\xfd\x5a\x6c\x56\xdd\x29\x0f\x7d\xd8\x4a\x29\xde\x17\x12\x6e\xb4\xe4\x48\x7b\x3e\xff\x0a\x44\xab\xe5\xc5\x97\x92\xd2\xe1\x20\x0b\x9c\x3d\xb4\x4d\x52\x8b\x9f\x7d\x22\x48\x03\x2e\x4b\xa0\xf7\xbf\xc4\xfa\xfc\x70\x6b\xe5\x11\xdb\x22\x76\xc0\xb7\xec\xff\xd3\x8d\xa2\xe1\xc2\xf2\x37\xa7\x53\x90\xc1\xe4\xd3\x23\x9c\xba\x8e\x20\xe5\x58\x40\xec\xb0\x5d\xf5\xf0\x1a\x1b\x69\x77\xad\x19\x06\xf2\xcb\x54\x4c\xcf\xb9\x3b\x90\x1a\xd0\x96\x6b\x18\x32\xad\x2d\xab\x52\x62\x44\xa3\x15\x6c\x90\x5c\x01\xac\x51\xcb\x73\xb9\xdc\xd9\x86\x0d\x56\x17\x5a\x42\x5d\x84\x64\x85\xd9\xb1\xf4\x4a\x8a\x0c\x25\x78\xe6\xcf\x61\x94\x7b\xc1\xa1\x39\x2f\xdd\x32\x0b\x16\xa9\xd7\x04\x55\xfe\x43\x6f\x2d\x47\xde\xd8\xe8\xe6\x05\xf7\x48\x6e\xb5\x78\xea\x7f\xc4\xff\xd1\x3c\x07\xf9\x99\x6a\xf1\x59\xfd\x41\x1e\x94\x51\x40\x32\x78\xdd\x11\x41\xa8\xc9\x26\xb3\x5c\x96\x38\x4b\xbd\x6b\xee\x09\xc4\x6f\x44\xc3\x6b\x1f\xfc\x71\x97\xf5\xe9\x25\xdb\xe0\x54\x4a\x68\xe6\xab\x8c\x18\xe4\x26\xa4\x66\xb3\x92\xf9\xc2\x7d\xd7\x9f\xef\xa9\xca\x16\x3c\xc5\xa3\x75\x53\x9a\x85\x59\xf2\x77\xf6\x57\xa5\x35\xd1\x96\x4c\x6a\x5e\x91\x68\x3e\xf5\x69\x8e\xba\xa0\x1e\xf8\x18\xdb\xf7\x2c\xb0\x4c\x3f\xf0\x92\xd1\x88\x86\x6f\x25\xcd\x40\x51\x08\xf5\x66\xb0\x87\xf7\x3d\x2d\x5b\xeb\x51\xfa\xc6\xde\x84\xae\x51\x61\xa6\x6a\xf9\x60\x2c\x7e\x4b\xfc\x14\x6f\x48\x20\xbd\xfc\x09\x2f\xae\xac\x69\x13\x3e\x4a\x08\xa5\xb2\x02\xa1\x24\x98\xa2\x2e\x57\xba\xd5\x46\x74\xed\x4b\x51\x01\x09\xd5\x2b\x5f\x74\xe7\x0e\x1f\x6f\x82\x16\x17\x18\xcd\x4c\xf0\x0c\xc9\xf1\x95\x8a\xcc\x8b\xdd\xcd\xfb\xd1\xfb\xe4\x6c\xd1"; let q = b"\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x33\x4a\x26\xdd\x8f\x49\xc6\x81\x1c\xe8\x1b\xb1\x34\x2b\x06\xe9\x80\xf6\x4b\x75"; let g = b"\x99\xab\x03\x0a\x21\xa5\xc9\x81\x81\x74\x87\x21\x67\x64\x1c\x81\xc1\xe0\x3c\x9b\x27\x4c\xfb\xc2\x7b\xc4\x72\x54\x29\x27\x76\x6d\xe5\xfa\x05\x39\xb3\xb7\x3f\x3f\x16\xac\x86\x6a\x9a\xec\x8b\x44\x5d\xed\x97\xfb\xff\x08\x83\x4e\xd9\x8c\x77\xe7\xfc\x89\xe5\xdc\x65\x7b\xef\x76\x6f\xf7\xfb\xf8\xe7\x68\x73\xe1\x7b\xee\x41\x27\x62\xd5\x6f\xe1\x14\x17\x60\xab\x4d\x25\xba\xfd\x4b\x6e\xf2\x5b\x49\xa3\x50\x66\x32\xd1\xf8\xe1\x07\x70\x93\x07\x60\xec\x13\x25\x93\x2c\x5a\x4b\xaf\x9e\x90\x15\x42\x64\xdd\xf4\x42\xec\x5c\x41\xfe\xd9\x5d\x11\x52\x51\x51\xdb\xcf\xb3\x75\x81\x49\xba\xd8\x1c\x62\xb9\xcf\xf7\x81\x6b\x8f\x95\x3b\x8b\x7c\x02\x25\x90\xd1\x58\x4e\x92\x1d\xc9\x55\xf5\x32\x8a\xc7\x29\x83\xed\x5c\xf0\xd0\x40\x56\xfe\x0d\x53\x1e\x62\xf8\xf6\xc9\xab\x3c\x0f\xcd\x44\xe1\x48\x60\xb7\x31\x1d\x25\x61\xc7\x7c\x1d\x32\xf6\xc6\x9d\xc8\xf7\x79\x68\xc9\xd8\x81\xad\x9d\xb5\xe0\xc1\x14\xfd\xa8\x62\x8b\xca\x03\x35\xeb\x7f\xb9\xe1\x5e\x62\x5a\xab\xab\x58\xfc\x01\x19\x4c\x81\xbf\x6f\xb2\xce\x54\x07\x7b\x82\x25\x0e\x57\xc6\xa7\xb2\x5d\xeb\x6e\xe3\x9d\x4b\x68\x6a\x5c\x30\x7a\x76\x12\xb2\xd8\x5e\xe9\x25\x12\x41\x3d\xea\x29\x7e\x44\xf3\x17\xbe\x7c\xeb\x70\xa3\x32\x8a\xf0\xb4\x01\x00\x1a\x41\x85\x62\xb8\xff\xe4\xe9\x77\x1b\x4b\x4a\x8e\x0b\x40\xc7\x91\x34\x9d\x5d\x4e\x45\x9f\xe6\x20\xa1\xa2\xfc\x72\xe2\xf6\xca\x28\x56\x7d\x4c\x26\x32\xbb\xde\x1b\x49\x86\x4c\x06\xbb\x12\x61\x9f\x13\x2c\x1d\xa8\xf5\x71\xef\x61\x3e\xac\x73\x9f\x66\xab\x39\x14\xcb\x3f\xa1\xab\x86\xe0\x5e\x50\x82\xeb\xaa\x24\xeb\xee\xa4\xcf\x51\xbe\xef\xc2\x7d\xf5\x12\xfe\x3f\xee\x7d"; test( HashAlgorithm::SHA1, b"\xca\x84\xaf\x5c\x9a\xdb\xc0\x04\x4d\xb0\x0d\x7a\xcf\xb1\xb4\x93\xaa\xb0\x38\x8f\xfb\xad\x47\xb3\x8c\xd3\xe9\xe3\x11\x1c\xfe\x2c\xda\x2a\x45\xf7\x51\xc4\x68\x62\xf0\x5b\xdc\xec\x4b\x69\x8a\xdf\xd2\xe1\x60\x6e\x48\x4c\x3b\xe4\xac\x0c\x37\x9d\x4f\xbc\x7c\x2c\xda\x43\xe9\x22\x81\x1d\x7f\x6c\x33\x04\x0e\x8e\x65\xd5\xf3\x17\x68\x4b\x90\xe2\x63\x87\xcf\x93\x1f\xe7\xc2\xf5\x15\x05\x8d\x75\x3b\x08\x13\x7f\xf2\xc6\xb7\x9c\x91\x0d\xe8\x28\x31\x49\xe6\x87\x2c\xb6\x6f\x7e\x02\xe6\x6f\x23\x71\x78\x51\x29\x56\x93\x62\xf1", p, q, g, b"\xe7\xc2\xee\x18\xc3\xaa\x36\x2c\x01\x82\xc6\xa5\x6c\x25\x84\x62\x80\x83\xc7\x3e\x04\x5b\xed\xa8\xd6\x53\x69\x0c\x9c\x2f\x65\x44\xed\xf9\x70\x2c\x57\xc4\x55\x27\x39\x05\x33\x6a\x5f\x51\x71\x10\x7a\x31\x3c\xd7\xd0\xb0\xf5\x0f\x8d\x33\x42\xc6\x02\x19\xf2\x2a\x90\x23\x39\x40\x59\xd0\x5f\x46\x4c\x44\x96\xd5\x5d\xab\x6e\xb0\x89\x85\x27\xff\x4c\xf5\x67\x8e\x7b\x5b\xfb\x5e\x18\xd9\x2c\x4a\x9d\x73\x28\x8c\xce\x14\x53\x0f\xc4\x70\x2f\x6d\x03\x97\xec\x39\xa8\x80\xc4\xa7\x2d\x35\x87\x30\xc5\x66\x33\x38\x6e\xde\x02\x80\x23\xc1\x79\x1f\x31\x64\xd1\x57\x4e\x78\x23\xc7\x9b\x8a\x3c\xa1\x34\x3e\xa1\x66\xba\x6f\x02\xb7\xff\x7e\x9e\xf2\x19\x8d\xb1\x07\xf7\xcc\x15\x9f\x3b\x6a\x1c\x00\xa7\x8c\x35\x5c\x56\x6d\xeb\x0a\xc6\xfd\xe3\xf6\x33\xcb\x91\x77\xa1\xfb\xc6\xc1\x76\x6c\xa0\x21\xd5\xfe\xc4\x70\x10\x1a\xbb\x44\x0d\x2f\x06\x98\x21\x81\xa8\xc9\x2b\x7c\xdd\x76\x53\x36\xb9\xa1\xe1\xab\x70\x28\x3d\x6d\xb0\xa9\x63\xfb\x64\x8c\x37\xc4\xe2\x9a\x74\xc3\x75\x77\x29\x10\x49\xab\x47\xcd\xbc\x10\x4c\x04\xdb\x96\x66\x81\xea\x8e\xbb\x9f\x00\xcf\x4c\x4a\x54\x62\x11\x73\x79\x57\x5f\xbd\xa4\xb8\x01\x97\x94\x51\xfa\x94\xb1\x9b\x4e\x93\x65\x67\x05\xc0\xf7\x34\xf3\xe0\x91\x4b\xb9\x6c\x1e\x2b\x8a\x0f\xb6\x8f\xaf\x14\x29\x6e\xfd\xf3\x30\x0a\xd9\x5b\xcd\xe8\xb6\x7c\xc4\xb2\x6e\x64\x88\xee\xf9\x25\xcf\xae\xac\x6f\x0d\x65\x67\xe8\xb4\x13\x55\xf8\x9d\x1c\x2b\x8f\xe6\x87\xbf\xa2\xdf\x5e\x28\x7e\x13\x05\xb8\x9b\x8c\x38\x8c\x26\x19\x60\x90\xac\x03\x51\xab\xc5\x61\xaa\xdc\x79\x7d\xa8\xcc\xea\x41\x46\xc3\xe9\x60\x95\xeb\xce\x35\x3e\x0d\xa4\xc5\x50\x19\x05\x2c\xaa", b"\x21\xca\x14\x8c\xdf\x44\xbe\x4a\xe9\x3b\x2f\x35\x3b\x8e\x51\x2d\x03\xad\x96\xda\xfa\x80\x62\x3f\xde\x49\x22\xa9\x5f\x03\x27\x32", b"\x73\xe4\x8b\x77\xa3\xaa\x44\x30\x74\x83\xc2\xdd\x89\x5c\xb5\x1d\xb2\x11\x21\x77\xc1\x85\xc5\x9c\xb1\xdc\xff\x32\xfd\xa0\x2a\x4f", )?; test( HashAlgorithm::SHA1, b"\x3c\xca\xd0\x01\x85\x19\xa8\x98\xf8\x7d\x8c\xe5\xf2\x8c\x0d\x93\xab\x16\xc5\x1a\xdd\xf4\x17\x33\x22\xcb\xc4\x9d\x48\xca\x9e\xa3\x7e\xbe\x8b\xc9\xd8\xc1\xb3\xf7\x83\xf8\xcf\x59\xcf\x3f\xcb\xa1\x0a\x39\x3e\xb2\xdd\xd9\x89\xce\x25\x8e\x73\x78\x8c\xe7\x4b\x0c\xe8\x22\x3d\x24\xe9\x93\xcf\xea\xfa\x49\xcc\x8e\xc1\xb8\xec\xee\x01\x7d\x83\xa1\x1b\xb7\x03\x4c\x77\x92\x06\xc3\x64\xac\x46\x3c\xfe\xd3\x04\x7e\x1a\x2b\xf5\xc5\x91\x77\x3b\x1d\x88\x2b\x31\x0b\xfb\xa2\xdb\x87\x89\x3c\x89\xa5\x44\x2c\x08\x45\xbf\x64\x4e\x21", p, q, g, b"\x37\x50\xd3\x63\x53\xbf\xd2\xe9\x97\x3e\x26\xa5\x55\xbc\xf0\xd8\x34\xd3\xd6\x62\x0c\xb6\x65\x79\x19\x9e\x04\x0c\xe8\xec\xcf\xae\xe6\x60\x04\x6e\x78\xdf\x66\xe8\xff\x64\x15\x23\x04\x6a\xdc\xf4\x25\xb8\x31\x9d\xb2\x44\x76\x80\x19\x4c\x3a\x38\x6b\x52\x01\xdd\x1a\xc6\xbf\x3e\x66\x39\x4e\x93\x9e\xaa\xac\xa4\xfd\x3f\x38\x6f\xcf\xe1\xd5\xef\x45\x24\xb0\x6c\x5e\xd9\xa1\x57\x46\xf2\x4b\xae\xf1\xee\xc4\x1e\x68\x3b\xf3\x53\x71\x08\x44\x95\xd4\xda\x8e\x72\x7a\xeb\xa3\x07\xfb\xa0\x00\xa7\x69\xa2\x34\xe3\xc4\x60\x97\x04\xb3\xba\x4d\xfd\x6a\x86\x44\xfb\xa5\x60\x83\xda\xc8\x48\x75\x1b\x52\xa8\xc2\xcd\xc4\x79\x46\xcd\x21\xea\x24\x38\x3c\xc6\x24\x4f\x00\x09\x18\xe9\xa2\x32\x76\xb6\x06\xc5\x68\x85\x65\xc4\x4d\xdf\x77\x88\x18\x1b\x78\x95\x65\xa6\xbe\xcd\x25\x71\x23\xbb\x81\xa2\xcb\xf9\xdb\x7f\xa3\x84\xe0\xca\x41\x80\x4e\xd7\xcd\x3c\x9c\xa0\xe1\xf8\xbb\x39\x0b\xff\x50\x21\x3b\x06\x29\x68\x24\x09\x93\x37\x70\xf6\xe0\x3a\x5c\x4e\x7e\x89\xad\xe9\x02\x55\x60\x97\x86\xf6\xb2\xfc\x5a\x7a\xa7\x56\x6b\xcf\x7f\x72\x5a\xea\xd4\xcf\x45\x6c\x5f\x5e\xd7\xdc\x3e\x91\xe2\x0d\x94\xd1\xaa\x2f\x65\x68\xc9\x7a\xbd\xf2\x1e\x0b\xa8\xcb\xfb\x65\x61\x30\x5c\xb4\x51\x75\xb1\xab\xd7\xf3\x9b\x9a\x11\xc7\x97\x92\x6b\x94\x4f\x5d\x13\xc3\xd7\x0e\x0b\x2a\x8c\xa1\x8e\x1f\x5c\xda\x8c\xe6\xac\x43\xec\xbc\x1f\xef\x88\x1f\x5e\xef\x5a\x84\x2f\xd5\x98\x4a\xd1\xe3\x21\xa3\x17\x00\x5a\xd4\x78\xcb\x47\xc9\xcf\xf6\x12\x67\xf1\xd4\x96\xfd\xed\x0a\x48\x32\x8d\x62\x9b\x7b\x20\x0c\x44\x16\x34\xee\x90\x88\x79\x01\x17\x45\xbc\xab\x66\x60\xe1\x55\x83\x74\x80\x14\xd6\xde\x2f\xe2", b"\x1e\xf7\x72\x33\x45\xb2\x01\x3b\x71\x10\x4c\xee\xdb\xe7\xa9\xca\xd4\x30\x01\x89\x68\xbb\x29\x5b\x67\x2c\x2b\x57\xb9\xa1\x08\xb9", b"\x72\x85\x2d\xa4\x85\xc0\x83\x6a\x8e\xbd\xbc\x4c\x99\x6f\x7f\x6c\xb6\x5e\x99\x39\x1c\xe0\x6b\x19\xa7\x18\x76\x18\xe9\xa9\x55\x84", )?; test( HashAlgorithm::SHA1, b"\x1f\xc9\x82\x88\x85\x7f\xb3\xa8\x3a\xb5\x07\x46\x5a\x53\xc0\x79\xed\x66\x67\x9c\xaf\xdf\xb8\x65\x3b\xfd\xeb\xb0\x30\x20\xfe\x86\xa9\x43\x18\x2d\x4f\x13\x77\xd5\x8e\xca\x3c\x77\x10\xd3\x2e\x21\x0d\x8d\x03\x72\x8b\xc6\x9e\x1b\x80\x03\x94\x4f\xfe\xda\xa1\xb6\x9a\xe6\xcc\x50\x63\x02\xbd\x69\x17\x01\x9f\x58\x8c\xc2\x95\x01\xcc\x82\x63\x57\x2e\xbc\x0f\xeb\x15\x38\x77\x17\x4b\xcf\xdb\xad\x4a\x58\x65\x91\x75\xd2\xde\x71\xd5\xf5\x01\x9c\x46\xd1\x12\xb6\x63\x1c\xf0\xc3\xf9\x12\xaa\xc8\x31\x40\xcd\x56\xcd\xf9\x03\xee", p, q, g, b"\x33\x53\x72\x77\x0c\x0e\x8e\x59\x1a\x36\x7d\xe9\x98\x33\xbd\xe6\xf0\x12\x40\xbc\x6e\x23\x6a\x5b\x4e\x36\x23\x3e\x12\x0b\x8e\xe6\xd1\xc1\x9c\x77\xf4\xcd\xbc\x29\x4d\x32\x78\xc3\xd4\xcf\x73\xed\x9e\x8e\xa5\x03\x2b\x05\x24\xa3\x91\xcf\x29\x3b\x35\xee\x7e\x02\x34\x30\x22\x22\x16\xd9\xf1\x8b\x45\x02\x2f\x4d\x5f\x93\x85\xf6\x38\x4d\x9f\xaf\x1a\x0f\xfa\x4a\x80\x0d\xa2\x3b\x93\x76\x51\xa0\x9e\x82\xc2\x22\x85\xb9\xde\x6a\x40\x8e\x23\x38\x6f\xfa\x67\xab\xb9\xd1\xc7\x1c\xda\x7b\xc0\xc9\x35\x25\xfc\xd7\x9e\x83\x15\x3e\x74\x60\x70\x78\x24\x67\x85\x8b\x69\x7a\xd1\x49\x14\x67\x30\x33\xfe\xdb\x2d\x7a\x10\x5a\xd2\xd4\x38\xda\xaa\x35\xb5\x03\xb5\x18\x31\x4a\xc3\x70\xfc\x5b\x11\x12\xd4\xfe\x51\x4e\x58\x35\xd9\xa8\x6d\xe2\x5e\x6b\x35\x69\x13\x92\xd1\xcd\x04\x83\x6d\x41\x26\xb2\x95\xb8\xa8\x9f\x21\x7d\x58\x12\x58\xaf\x95\x27\x7b\x8b\x91\xc3\x1e\x6b\x0d\x23\xa7\xc5\x2b\x0c\xe2\x64\x1c\xf1\xa2\x52\x83\x8b\x6e\x28\xe2\x26\xcf\xc4\xfa\x9d\xc9\x14\xc5\xf6\x75\xfc\x90\x0e\xd6\x80\xdc\x1a\xa9\xe1\xd1\x71\x93\xc4\x32\xaf\x40\x32\xeb\xab\x95\x41\x91\x32\x70\x83\xc5\x9a\x5f\x64\xc1\xea\x18\x10\x7c\xe4\xd7\x21\x1d\x1c\x22\xf0\x48\x05\xed\x54\x8f\xc2\x2d\xf4\xb1\x62\xf3\x0b\x6f\xf3\xa7\xf7\xc3\x8a\x5a\x95\xfe\x82\x4d\x29\x61\x18\x0e\x98\xb3\x02\x08\xdc\x7e\xa7\x07\x1f\x79\x22\x61\xd4\x5c\x7b\xb7\xb9\x11\xf3\xb1\x9c\x3e\xe0\x17\x1a\x32\x6c\x03\x3c\xf5\xfc\x2b\xf7\x9d\xe7\xd5\x11\x5a\xc5\x68\xe0\x47\x89\xcb\x44\xe0\x8f\x3a\x86\x27\xa1\xb1\xf3\x76\x23\x42\xb4\x9b\x76\x79\xbd\x7e\xdb\xe4\x7b\xc3\xee\x9c\x3f\x02\xdb\x15\xd5\x32\x56\xa5\xea\x28\x47", b"\x5d\xd0\xc7\xe8\xa3\x99\x3b\x9d\xe0\x67\x6a\x57\x9c\x89\x7e\xa3\x99\x43\xa4\x3d\xbe\xc5\x99\x6e\x58\xc1\x98\x5b\x54\x1d\x7c\x1a", b"\x67\x97\x10\x01\x82\x2a\x08\xa2\x14\x8a\x6b\x1a\xdb\x50\x27\x4a\x57\xda\xfe\x89\x6f\xb0\x4a\x12\xa6\xf9\x97\x07\x55\x53\x06\xac", )?; test( HashAlgorithm::SHA1, b"\xfd\xa9\x76\x5c\xc9\x1a\x9d\xb9\x22\xae\xc7\xb1\x3f\xc3\x2a\xc4\xec\x4e\x3b\x85\x34\xf9\xe9\x5a\xf9\x6e\x8e\xbe\xab\x89\xd8\x47\xdc\xd1\x50\x44\x48\x68\xcf\xaf\x42\x13\xf8\xd8\xba\xa6\xb1\xd0\x88\x62\x24\xe2\xaf\xd0\xae\xb9\x3d\x59\xb8\x86\x57\x20\x88\xd0\x5b\xf7\x21\xc7\xad\xfb\x54\xda\x47\xc6\xc4\x85\x12\x04\xa7\xa9\x2a\x11\xde\xb3\x9b\xa1\x7c\xf6\xc0\x7f\xb7\xce\x8b\xa3\x50\xa9\x9d\x01\x8d\x4e\xa6\x4b\xd5\x6d\x1d\x9f\x8f\x7d\x88\x15\x7f\x19\x0f\xcb\x37\x2a\xcf\x6f\x8d\x31\xcf\x7b\x79\x5b\x36\xc1\x0f\x5e", p, q, g, b"\x05\x39\xcc\x99\x2c\xa7\x0f\x91\x35\x37\xb1\x21\x1d\xd3\x26\xd8\x5f\x75\x31\xba\xa6\xbe\x05\x83\xba\x45\xb9\x57\x1b\xaa\x81\xcd\x58\x28\x05\x0d\xcd\x9a\xb7\xa2\x03\xbc\x4f\xe1\xd8\x74\xf7\x6e\xc1\xf3\x4d\x93\x55\x79\xed\x21\x32\x25\x57\x89\xd7\xe6\x01\x0c\xf5\x04\xb4\xc7\xf5\x86\xd4\x4a\x71\x66\x00\xac\xf8\xa0\x4a\xd3\x0c\xb7\xca\x05\x5d\x72\x23\xf9\x76\x1c\xda\xeb\xfd\xf7\xef\x72\xbd\xea\x3d\xfc\xd0\x20\x06\x9a\x96\x9c\x56\x01\x60\xf0\x53\x46\x76\xbe\xff\x5e\xa6\x11\xfc\xbc\x0f\xd4\x7c\x86\x7f\x31\x63\xe1\x37\x1e\x1d\xe6\x7a\x1a\x3c\x3e\x37\x16\x8b\xf0\xbe\x79\xc0\x9f\x45\xf2\xbc\x43\x51\x7a\xee\xa0\x10\x0a\x2a\x25\xd1\x48\xff\x19\x90\xc0\x61\x43\xfa\x25\x3d\x83\x06\xf4\x8d\x77\x36\x22\x24\xbc\x3e\xfe\x93\x38\x9e\x92\x2d\xef\x0f\xd1\x1d\x19\x92\xf5\x50\xed\x82\x94\xb6\x13\x65\x47\xff\xd6\x12\xb0\xbc\x8e\x4e\xe9\x0b\x31\x00\xbb\x89\x92\x21\x77\x14\x7b\xe0\x08\x32\x81\xbf\x66\x3f\x83\x70\x41\x7f\xa7\x90\xd4\x10\x5e\xb9\x8c\xc1\x26\xf5\x00\x5b\x7c\x08\xbc\x21\x1d\xea\x28\x98\xaa\x65\x3c\x3d\x2b\x51\xfc\x67\x73\x2b\xff\x56\x44\xe8\x04\xaa\xe6\x92\x00\xc4\x16\x03\x5a\xa0\xba\x5a\x14\xcc\x43\x9b\x56\x9f\x46\x21\x17\xb7\xdf\xcf\x3f\x2c\xc1\x3e\x72\x3a\x93\xff\x95\x33\x20\x8f\x20\x24\x1d\xaf\x36\xcd\x16\x06\x6b\xe3\xdb\xa2\x01\x17\xcb\x14\x5d\x75\x6f\x5a\x6f\x79\xce\x23\x56\xa0\x51\x64\x7a\xed\xd6\x45\xbf\xa6\xfa\xf8\xf8\x0a\x6f\xdf\x3e\xec\x42\xdd\xd4\x2b\xb2\xe7\xb7\x73\x82\x96\xe2\x39\x78\xc8\xbd\x63\xb8\x04\x59\x53\xe0\x6c\xef\x12\x63\xbf\xe0\x3b\xe2\xf6\x1b\x16\x00\x7d\xf1\xeb\x19\x85\x67\xa7\xbc\x6b\xff\x27\x4e", b"\x55\xc2\xb2\x7e\x76\x9f\xac\x99\xb4\x7b\xc0\xa5\x4f\xf1\x82\x1c\x7a\x46\xbe\x60\x01\xab\x66\x4f\xb6\x8f\xb1\xba\xfc\x04\x44\x6f", b"\x30\x59\xdb\x42\xa3\x99\xc4\x28\xf3\xcf\xbb\x10\x2d\x6c\x04\x09\xb0\x6f\x20\x06\x8d\x1c\xa8\xcb\xea\x48\x58\xac\x6e\x5d\xe1\xd3", )?; test( HashAlgorithm::SHA1, b"\xe4\x9a\x12\xb8\xd7\x61\xef\x7a\xfb\xcb\x1c\x37\x7e\xed\xf6\x29\xd0\x8c\xc5\x09\xa8\x75\x3a\x5b\x92\xe2\x6a\x23\x97\x36\x51\x56\xe7\xc0\x81\xbc\xb4\x68\x66\x95\x57\x5c\x6a\x64\xf5\xd7\x7d\xfd\x55\x0b\x04\xdf\x39\x0a\xa5\x5e\x0d\x05\x1c\x75\x9f\x19\x7a\x75\x1a\x60\x41\xe2\xdd\x09\x59\xf9\x02\xf2\xe3\x59\xa1\x67\xd8\x80\xc4\x9c\xfa\x81\xe7\x19\x6f\xa1\x60\x4a\xd3\x2a\x80\x17\x07\x1f\x09\x8d\x4c\xb3\x46\xb3\x92\x66\xfb\xe7\x56\x59\xdf\xc6\x60\x7b\xf0\xd8\x29\x64\x07\x82\xcf\x3e\x12\xe3\x83\x76\xc5\xa9\x92\x82", p, q, g, b"\xee\x7f\xff\x18\x82\x2f\xf4\x54\xa2\x07\xf9\xdb\x54\x2d\x24\x29\x8b\xb5\xed\xb1\x1d\x80\xdd\xc6\xdd\xb9\xbf\xae\x0c\x95\x2d\x4f\xe8\xd9\xdb\x0f\x1a\x86\xe8\xa0\xf2\x19\x3a\xf7\xca\xae\xe7\x26\x4d\x74\x10\x6d\xe5\xaf\x0a\x6c\x14\xf7\x10\xbb\x86\x3e\xb7\xdc\x16\x7a\x1e\x43\x78\xb6\xcd\xb7\xab\x68\x41\xc6\x64\xe9\x82\x45\x29\x11\x97\x73\x57\x8e\xf5\x5b\x7c\x35\xed\x22\x1e\xf0\x70\xdd\x46\x90\xb4\xc1\x2f\x27\x67\x3e\x5d\x1f\xe9\x64\xff\xe2\x9d\xa5\x7e\x2d\x1a\xcd\x21\xef\x13\xe0\x66\x9f\xa9\x76\x68\xbb\x19\x9b\x56\xa3\xa5\x3e\x10\x46\x91\x33\x02\x20\x81\xcd\xf6\x26\x48\x10\x0d\xca\x26\x7c\x4f\x6a\x3c\xa3\xa7\x5b\x57\x3b\xb1\xb3\x9c\x8a\x4e\x1f\xcf\x81\x26\x9e\x9e\x1b\x10\xc6\x3f\x5b\xa4\xfe\x75\xcf\x71\x39\xd0\x38\xd0\x2f\x5f\x53\x4a\xa0\x81\xfc\xe7\x32\xcd\x50\x51\x60\x9b\xc0\x6f\x18\x87\x4d\xd0\x11\x21\xd3\xc1\x79\xf0\xc3\xf0\x39\x9c\x18\x5e\xeb\xdc\x34\x63\x5b\x31\x39\xf1\xca\x50\xfb\xff\xb3\xb0\xad\x12\xe4\x81\xc1\xa6\x46\x82\x14\x37\x93\xf0\x72\xc7\xdb\x8b\x5b\x9e\xef\x41\xcc\xdd\x66\xb9\x04\x13\x9d\x64\x44\x42\xa9\x2f\x62\x55\xed\xb9\xbc\x12\x34\xe2\x7d\x07\xa6\xba\x32\xb1\xf1\x4c\xdf\x98\xa2\x2c\x6a\x12\x30\x0d\xff\x50\xac\x1b\x65\x56\x8b\x6e\x91\x55\x41\xbb\x38\x6e\xc7\x25\xda\x44\x44\x67\xca\x25\xe8\x14\x48\xcb\x78\x37\x51\x46\xad\x20\x78\xa8\x30\xe7\xd9\x05\xde\x9a\xd7\xd8\x95\x59\xc9\xd4\x30\xcf\x5f\x41\x9c\xe9\x45\x70\x4a\x42\x6a\xb2\x64\x01\x6e\xd8\x7c\x90\xd9\x7f\x51\xa7\xd6\xe1\xee\x2f\x51\xbb\xb3\xa8\xde\x81\x39\x16\x97\xb0\xe4\x22\xdf\x9e\x5d\x35\x51\xe9\x33\x74\xe5\xf3\x80\x16\xb2\x96\xd5\x3b\xc2", b"\x21\xd3\x4d\xf6\x05\x23\x79\x75\xdb\x31\xb8\x64\xf9\x8c\x9a\xb6\xe4\x65\xdb\xf0\xb3\xfc\x58\x68\xd6\x7c\xd6\xcb\x3a\x13\x96\x3b", b"\x70\xc4\x88\x07\xd6\x2d\x1f\xe7\x4d\x58\x95\x93\x47\xab\x12\xc9\x7b\x50\x0d\x20\x60\x7e\xd2\xa9\x5d\x8a\x38\x8f\xee\x26\x58\x12", )?; test( HashAlgorithm::SHA1, b"\x28\xf7\xa0\x67\xa0\xea\x7f\x0a\x4d\x79\x7c\xea\x39\x39\xf6\x6b\x28\x1e\xd1\x9c\xc9\x8b\x85\x63\xef\x37\x57\x98\xb4\x06\x14\xf4\xdd\x85\xac\x2f\xcf\xcc\xbc\x5e\xbf\x0a\xc9\x32\x28\xc0\xb7\x29\x37\xa4\x81\xca\x4f\x9d\xf7\xa7\xe5\xd2\xe5\xda\x9a\xf0\x48\x74\xdc\xec\x35\x03\x5f\x6a\x7d\xb4\x93\x79\x3a\xa2\x36\x1f\xb6\x6e\xf2\xee\xdb\x75\x74\xd0\x4e\x21\x47\xc3\x57\x29\x8a\x2a\xdf\x99\xac\xa1\xee\xbe\x00\xce\xfa\x44\xb3\x91\x57\xeb\x1e\x94\xaa\x8a\xa9\x8d\x54\x51\x51\xfb\xb4\xde\x67\x07\x0b\x39\x04\xcc\xe9\x30", p, q, g, b"\xcd\xb9\x92\x2d\x69\xe9\x9c\x7f\x34\xa9\x21\x0e\x2a\xfc\x5b\xe0\x11\x5d\xa4\xaa\xf6\x82\xd9\xea\x37\x78\x8e\x0b\x6c\xaa\x6f\xde\x13\xc8\x8e\x51\xf5\x58\x82\x06\x68\xb5\x9d\x14\xc0\x6d\x2c\xbe\x65\x49\xd3\xf0\x6d\x10\xdb\xee\xe4\x6f\x59\x15\x4c\xd4\x67\xae\x19\xe1\x6b\xe2\x5e\x6f\x6c\xd2\x38\xcc\xd1\x94\x7f\xc5\x81\x56\x2d\x30\xca\x32\x9b\xb3\x27\x25\x8c\xa4\xae\xb9\x01\xf8\x14\x41\x40\x58\xb6\xf1\x69\xa4\x5f\xf5\x5e\x40\x23\x2d\x78\x70\x49\x9a\xe7\x8c\x05\x13\x77\x71\x40\x75\x2d\x55\xf0\xa4\x70\x76\x1b\xdc\xff\x5a\x66\x09\xcc\xa2\xd1\x80\x9f\x18\x4b\x29\x87\x18\x07\x1d\x21\x6a\x14\xad\x01\xf5\x6c\xcc\xce\xd2\x39\x69\x60\x7b\x62\xd4\xd1\x40\xc9\xef\x28\x50\x76\x74\xf5\x9f\xec\xc7\xe7\xce\x8a\xd2\x63\x6a\x5c\x53\xf0\x70\xad\x31\x7c\x8c\xd0\x23\x1f\x50\x0a\x79\x0e\xf6\x9a\xc7\x86\x00\x0f\xaf\x68\xe7\xb7\x85\x4d\x6e\xb2\x64\x99\xa9\xd5\x24\xcb\xf8\xf3\x73\xca\x41\xdd\x6a\x2f\xa5\x19\x8e\xba\x2a\x8e\x22\x8f\x5a\xb2\x9b\xe9\xf6\xd4\x50\xf7\xf5\xa1\x49\xae\xb2\x0d\x8a\x27\x79\x71\xfa\x6e\x64\xa0\xde\x36\xc8\x75\x0a\xfc\x38\x19\x61\x75\x69\x75\x62\x1f\x28\x7a\x39\x50\xf8\x84\x02\xc5\x08\x1f\xe0\xc5\x4f\x44\xf9\xfa\x7c\x50\xdf\x90\x6b\x26\x40\x98\x85\x36\x46\xb3\xd0\x5a\x4f\x04\xc6\xf1\xbb\xc6\xc4\x40\xf7\xe7\x35\x8d\x3a\x72\xb2\x9f\x76\x43\xf4\x40\x6b\x7d\xb1\x73\x69\x0d\x40\xaa\x29\x38\xea\xf0\x18\x74\xd2\xba\x80\x94\xcc\x5b\xe1\x14\x5b\x2b\x2e\xe9\xe7\xcf\x15\xbf\x39\x8e\x50\x83\x2d\x95\x01\x74\x30\xb1\x86\x99\x38\x73\x2c\xdd\x1d\xf5\x93\xf5\xdb\x2b\x2b\xd7\x13\x08\xd8\xc2\x53\xd2\x54\xef\x39\xb4\x75\xe2\x49\xd8\x90", b"\x12\xde\x25\x2d\xa2\x59\x3c\x59\x69\xa6\x49\x6a\xe8\x08\xd8\x51\xca\xd1\xde\xd2\x95\x9e\xa8\x90\x57\xa9\x2e\x5e\xc9\x1c\x5f\x95", b"\x16\x53\x38\x07\x5e\x6a\x4f\xea\x0b\x23\x8f\x9f\xac\x90\x4b\x7b\x33\xdb\xee\x5a\x55\x26\x46\xdf\xbe\xd8\x27\xf6\xd2\x8d\x64\x92", )?; test( HashAlgorithm::SHA1, b"\x0e\x15\x6b\x0b\xd8\x45\x95\x15\x5e\xf4\xfc\x21\x3d\xfc\x7e\x46\xbf\x27\xa8\x9c\x27\x57\x23\xe0\x98\x40\x76\xb0\x27\xc4\x9c\xb2\xee\xe6\xac\x86\x6d\x75\x33\x35\x81\xcc\xa6\xf8\x97\xe1\x14\x18\xfb\x37\xba\x5c\xab\x13\x91\xcd\x23\x7e\x2c\x6a\xb3\xf1\x1a\x05\x5d\x3b\xd0\x3f\x42\x5b\xaa\xab\xe5\xa6\xa3\x4e\xba\x4b\x11\x8a\xf7\x3e\xdd\x61\x07\x87\xcb\x8e\xaf\x47\x6b\xd2\x17\x04\x82\x08\xea\x4c\x1d\x05\x91\x37\x29\x47\xa1\xc0\xef\x94\x69\x65\x68\x98\x34\x24\xfd\x1d\x80\x2f\xc9\x11\xe7\xbf\x71\x22\x4a\xfd\xbd\xd9", p, q, g, b"\xd0\x97\x36\x41\xd5\x6d\x8b\xaa\xa2\xc2\xd4\x30\x50\x1d\xff\x44\xea\xf9\xa3\x65\x74\x78\x79\x91\x34\xb0\xf3\x35\xae\x94\xff\x27\x91\xdf\xd4\x94\x40\x13\x32\x48\x6b\xa6\x37\x68\x3e\x70\x4b\xd9\x85\xf5\x26\x91\x9e\x66\x1a\x22\x80\xd9\x9b\xd4\x82\x62\xb6\xc9\x30\x5e\x0c\x8f\xd8\x79\xcd\x0a\x83\x61\x28\xd8\x8e\xdd\xae\x51\xfc\xfb\x51\xf7\x44\xb2\x3d\x2d\x2d\x27\xa2\xcc\x1e\xa5\xa9\xd5\xe0\xa7\xfa\xf4\x22\x7a\x2a\xdb\xfb\x7e\xd4\x5d\x6a\xa9\xc3\x37\x98\xab\xf0\x7b\xc6\x9e\xfc\x5f\xdd\xe5\xdc\x5c\x78\x01\x96\x25\x70\x93\xec\xa7\x54\x68\xb1\x61\xcb\xa4\x4b\xcd\x14\x2b\x21\xfa\xe9\xed\xc6\xab\x32\x78\x30\xc2\x8e\x1b\x3d\x2d\x7c\x81\x2d\x8a\xec\x3a\x19\x52\x62\x7a\x04\x01\x10\x87\x2e\x14\x8e\x15\xde\x5c\x7b\x4c\xa2\x4f\x08\x63\x36\xda\xec\xbb\xf9\x81\x6c\xdb\x9d\xc7\x30\xdb\x8a\x66\xa1\x92\x9a\xbe\xcd\x4b\x09\xa0\x39\xa1\x9b\xff\xa4\x5f\xfc\x85\xdd\xf0\xbe\x32\x77\xbf\x07\x5b\xbb\x46\xf0\x7b\xf0\xda\xea\x24\x89\x7e\x07\x04\x4b\x5e\xe3\x7f\x9f\x44\xfe\xe7\x57\x18\x81\x70\xda\x22\x92\x4f\xa1\x5e\xd9\xc0\x7f\x11\x3c\xdf\x37\xa8\xc4\x86\x48\xe5\x86\xfb\x55\xa0\xc3\x5f\x3b\x63\xa6\x96\x67\x24\x41\x93\xc7\x0d\x94\xbb\xe3\x6d\x04\x3b\x25\xa0\x41\xfb\xa9\x2a\x20\x42\xe2\xee\xf7\x67\xe7\xcd\x18\xdd\x1c\x1b\x5c\xa4\x87\x8f\xe7\x74\xc8\x33\xcb\x5c\x5a\xff\x9f\x67\xbf\xd6\xcf\xbf\x2d\xfc\x63\xb8\x84\x2a\xd2\xd4\x9c\xeb\xca\xff\x4c\x39\x27\xf3\x19\x9c\x10\x6d\x0c\x14\x9a\x9b\x1b\x49\xbe\xf1\xd6\xf8\x14\x3d\x93\xd2\x5d\xf9\xdb\x1b\x5b\x37\xd5\x22\xe7\x23\xff\x64\xd9\xee\x52\xe4\x76\x20\x67\x12\xa3\x82\x46\xdd\x92\x62\x71\xf5\x59\x0e", b"\x5e\xf5\xd7\x8c\x42\x1a\xe5\xa6\x39\x78\xbc\xbf\x7d\x20\x37\xb5\x02\x2b\xc4\x7b\xe7\xb2\x93\x80\x65\x80\xad\x5b\x4d\xe2\x7a\x4e", b"\x67\xcc\xb2\x83\x3c\x1d\x32\xc6\x8e\x91\xae\x38\x90\xb4\xc9\xa6\xe5\x22\x9b\x22\xa5\x79\x91\x68\xc0\x04\x6e\xad\x92\x57\x3c\x85", )?; test( HashAlgorithm::SHA1, b"\x84\x9c\x53\x37\xd8\x8b\x3b\x24\x7d\xf5\x73\xeb\x0d\x66\x55\x48\xb6\x42\x37\x63\xd5\x57\x1f\x8a\xcb\x5e\x61\xe3\x16\xd7\xcd\xc2\x08\xcd\xa5\xb3\x9a\x19\x44\xa7\x17\x58\x7e\x58\xe2\x1b\x86\xed\x22\x2b\x8e\xe2\x65\x10\x5a\x32\xba\xff\x36\x92\xdc\xf7\xb8\x71\x3d\x0b\x53\x92\x62\xa5\xbd\x9a\x95\x4c\xb7\x14\x3e\xe6\x6f\x87\x64\xdb\x62\x36\x13\x6c\xb1\xcb\x3b\x34\xa8\x7c\xbd\x3f\xee\x3b\x11\x28\x8b\xc9\x4a\xc9\x91\x79\xc6\x81\xa4\x69\xd6\x2d\x9b\xcd\x91\xd4\x03\x32\xa6\x50\xa5\xbc\xe3\x3b\x60\x26\x88\x4e\xf9\x4a", p, q, g, b"\x1e\x8d\x4d\x6f\xef\x99\x05\xd6\x39\xe2\x56\x4d\x87\xdc\xbe\x0d\x8f\x99\xbd\xe3\x80\x82\xff\x09\x1a\x97\x7f\x2a\xff\xca\xb8\x65\x05\xae\xff\xe6\xef\x1d\xdb\xac\xf1\x5d\x91\x65\xb0\x06\xac\x05\x17\x43\x4a\xaa\x65\xdb\x21\x04\x52\xfb\x2f\xf4\xc9\x90\xb8\x7f\x25\xfe\xe7\xad\x5b\x26\xad\x87\x74\x95\x75\x19\x00\x89\xa5\x6c\xdb\xce\xee\x67\x82\xce\xaa\xf5\x69\x81\x4b\xb9\xe6\x58\xff\x50\xae\xbf\x6f\x3c\x97\x91\x89\x3e\x5d\x6a\xda\x5f\xdf\x8c\x47\x20\xfa\xfa\x18\x4c\xc8\x4a\x84\xf5\xfc\xa7\x9d\x89\x96\x36\xe0\x07\xbd\x0e\x1a\x89\xda\x09\x4a\x37\x8e\xdb\x6d\x72\x24\x0c\xc2\xd1\xd7\x09\x8b\x53\xba\x48\x37\xa5\xd0\xd7\xd0\x20\x19\xb9\x52\x71\x2e\x4f\x14\x20\xe5\x8a\xf2\x3d\x13\x77\xcd\x6d\x5f\x39\x89\xb3\xd6\x0b\x5f\xc5\x72\x04\x3b\x96\xc4\xf7\xbe\xb7\x13\x7c\x08\x94\xfa\x99\xd7\x27\xa5\xa8\x8a\x5d\x5d\xcb\xf2\xda\x7b\x0b\x2d\x83\xdb\x88\x74\x7f\xb0\xcc\xaa\x89\x91\xd2\x4f\xcc\xde\xf4\x21\x11\xff\x40\x2e\xd0\xd9\xbd\xb8\xa4\xad\x13\xf8\xfc\xff\x6a\x1d\xf5\x6c\x82\xa5\xf8\x8f\x57\x5f\x49\xa0\x62\x75\xa9\xe6\x60\x67\xf1\x5d\xae\xc4\x02\xed\x87\x70\x48\x49\x99\x09\xb9\xe7\x6e\x5f\xde\x52\xfe\xac\x94\x4e\x1d\xe7\x89\x4c\xf1\x3c\x51\x52\x99\xac\xc6\x44\x2d\x90\xf0\x27\x31\x7b\x07\x13\x80\x5a\x95\x12\x25\x6b\xca\xa7\x96\x3b\x94\x29\xa5\x10\xc5\x86\x97\x92\xc1\xe2\x90\x82\x92\x1d\x0e\x7d\x0c\xef\xff\xc3\x4d\x30\x76\x2f\xb8\x3e\x2a\xbb\x78\x21\xfa\xb4\xca\x89\xd0\x8b\x49\x7f\x75\xe3\x14\x9a\x5c\xd3\xd2\x3b\x29\xbc\x52\x13\x7d\x8b\xe9\xc4\xa9\x5c\x63\x76\xf6\x2e\xd6\x4f\xdc\x15\x9b\x1b\xb6\xc8\x42\xbd\x07\xf8\xcf\x03\xf7\xf2\xeb", b"\x11\xb1\x63\x51\xf8\xf7\x20\x31\xba\x2a\x77\x20\x00\xac\x87\x26\xa4\x79\xe1\xbe\x45\x23\xa9\xee\xfa\xbe\x23\x94\x7a\x1d\xf0\xd9", b"\x26\x60\xfb\xb4\x4e\x29\xe7\x68\x7c\x10\xe2\x9d\xe9\x6f\xa1\xab\x03\xc0\x87\xcc\xce\x08\x6c\xdd\xab\x48\xec\x63\x77\x41\x41\xc1", )?; test( HashAlgorithm::SHA1, b"\x4c\x37\xa4\xc8\xb4\x11\x09\x24\x0c\x4f\x53\xd8\x72\x77\xd3\xc7\x90\xb2\xf0\x71\x10\x5d\x15\xaa\x10\xbd\x0f\x77\x09\xda\x27\x4c\xce\xa1\x96\x1e\x0b\x99\x63\x5b\x31\xac\xd2\xc8\x05\x30\xd2\xb4\x03\xd7\x11\x0a\xd7\xcd\x0e\x35\x72\x51\x89\x09\xc1\x36\xe7\x3e\x57\xd3\x8c\x1c\x74\x43\xe5\x8a\x25\x7f\x07\x36\xb9\xf6\xf5\x1d\xa8\xfd\x1a\xe9\x21\x3e\x81\x93\x00\x3d\x69\x58\x33\x81\xf0\x20\xcc\xe7\xfc\x59\xba\x1b\x1e\xd5\x54\x1d\xbe\xf6\xb5\x99\x25\x75\x0d\x50\xb6\x51\x5a\x97\x7a\xa4\x32\x5d\x5f\xad\xe4\x2f\x82\x87", p, q, g, b"\x88\xa4\xdd\x59\x3b\x64\xa4\xeb\xcb\x27\xff\xea\x3d\xe9\xa7\xed\x78\x01\xf9\x67\x2b\x5c\x8d\xc2\x7b\x38\x3d\x6c\xba\x58\xb4\xf0\x01\x81\x63\x4d\x05\xeb\x49\x02\x82\xce\x4e\x57\xf0\x94\x03\x73\xd3\xa7\xbd\x7e\x9c\xca\xa9\xbb\x29\x65\x32\x2a\xb5\xfb\x21\xb4\x32\x7b\x47\xef\x4e\x2b\x42\x42\x4c\x13\x83\xbb\xd8\x55\x8b\x50\x6a\x7b\xf5\x53\x7b\x04\x9f\xff\x35\xc5\x58\xbc\xc7\x39\xb7\x60\x44\x37\x28\xc0\x90\xc3\x4d\x6d\x4e\xba\x81\xe2\x4e\x42\x39\x4f\x8f\xb8\x26\xf7\xc9\x2c\xa7\x1a\x9d\xab\x16\xe9\x99\x27\x47\x26\xb0\xc5\xd8\xf7\x2f\xb9\x14\x18\x70\xda\xc0\xbb\x9e\xc0\x42\x98\x02\xb6\x29\xad\x71\xae\x05\x60\xe5\x86\x2e\xcf\x3e\xab\xa9\xc2\xa5\x84\x88\x5b\x32\xc6\x84\xf6\xd5\x5f\xd1\xb0\x90\xd9\x3d\x03\x6a\x4e\x98\x58\xa4\xd8\x9b\x9b\x57\x50\x84\x9d\x92\x6c\x51\x91\x20\x13\x1d\x45\x6f\xce\x9d\x24\x73\x41\xeb\x17\x33\x6c\xa9\x72\x9a\x90\x80\xac\x5b\x12\x72\xfb\xf7\x07\x52\x6a\xfc\x8a\xe6\xa8\xc6\x61\xef\x3c\x15\x18\x45\xf6\xee\x09\x02\xde\x9a\xbb\x43\x22\xaf\xe5\x85\xe5\x9d\x6d\x41\x8e\x87\xd7\xcd\xce\x48\x97\xcc\xac\x81\xd0\x13\xfd\x72\xda\xe1\xa5\x55\x77\x62\x52\x73\x12\x58\x7c\xa6\x76\xf0\xe0\x67\x60\x00\xfc\x0c\x76\xb8\x26\x58\x42\xf2\xdb\x7e\x18\xe6\x21\xc0\xe3\xc2\xca\x92\x95\xe9\xe3\x6e\xc8\xce\x1c\x85\x09\x7c\xa5\xff\xfa\x62\xe7\xb8\x96\xbb\x16\x83\x6d\x06\x33\x86\xb1\xe6\x63\xef\x29\xec\x17\x02\x96\x5a\x7e\x05\x62\xd2\xd2\x82\xf8\x09\x52\xd7\x47\x6b\x32\x2f\xfa\x79\x29\xa4\x53\xa6\x38\xea\x3b\xed\xe8\x02\xff\x5f\x8f\x56\x60\x85\xa6\xe2\xa2\x41\x4e\xf7\xa6\xf1\x17\xac\x86\x28\x48\x6b\x23\x60\x3b\x14\x08\xfa\xae", b"\x23\xe1\x3a\x35\x77\x7c\x18\x9a\xe5\x65\x09\xc7\xaf\xb4\x11\xb3\x13\x07\x73\x7e\x2f\xfc\x8d\xb3\xf2\x08\x94\x0c\x5e\x76\xed\xb3", b"\x05\x44\x75\x83\x62\xcb\xb6\x1d\x66\xb6\x68\x26\x95\x8a\xca\x63\xaf\x1b\x8a\xd6\x15\xa4\x9b\xa5\x57\x92\x39\x59\xb6\x8f\x82\x28", )?; test( HashAlgorithm::SHA1, b"\x44\x34\x73\xd6\x15\xbe\xdc\xba\x2c\x8d\x9a\x9a\x45\xa2\x8c\x42\x8d\x7f\x1a\x26\xab\x14\x70\x56\x27\xd9\xad\x13\xf5\x3b\x76\x7c\xbb\x60\xbe\x52\x3f\xc2\x1a\x99\xc3\x73\xbd\x77\x61\x81\x7b\x31\x42\x90\xf2\xf6\xa8\x0e\x06\xe1\x2c\xce\x23\x89\x54\xc6\x48\xac\xe5\x0f\x3b\x0d\xfd\xf7\x1d\xc3\x08\xe1\xa8\xee\x11\x59\xfc\x1f\x19\xb7\x3a\xb6\x01\x5d\x18\x6d\x9b\x6b\xad\x96\x5a\x9a\xd6\x2e\x44\x0a\x9c\xed\x13\x55\x0a\x44\x4b\x5f\x04\x00\xb9\x6e\x2d\x23\x8e\x9e\x3d\xc6\xe6\xde\x12\xf4\x42\x05\xd4\xfd\x57\xf6\x0e\x9d", p, q, g, b"\x0f\x4e\xc6\xe2\xba\xae\xa9\xc8\x1e\x90\x70\x05\x19\xf2\xf0\x5f\x54\x5d\xdc\x0a\xe9\xbd\x3a\x09\x1e\x8b\x6b\xa5\x25\x5c\x15\xfc\xe5\xef\x3c\x04\x67\x71\xc5\xf3\x1b\xb0\x1d\xe4\x37\x7e\x14\x28\x31\xac\x17\x49\x90\x3f\x93\x17\xc7\xb0\x1a\x99\x07\x14\x98\x5f\x92\x51\x19\x8c\x82\x90\x73\x20\x59\x24\xc5\x68\x05\x0a\xcd\x6d\xcc\xa7\x57\x61\x8c\xd2\x80\x9b\xb7\xaa\xb6\x4d\xb1\xe8\x6c\xa9\x2e\xeb\x85\x41\x20\xc9\xd8\x9f\xb9\x36\x35\x96\xbe\x9c\xbb\xaf\x8e\xac\xae\x2f\x18\xf3\xed\x48\x35\x89\xeb\x46\x6a\x51\x44\x82\x4f\xeb\x1f\x88\xc3\x0c\xfc\xbb\x76\x28\xf7\xcb\x41\x59\xce\x32\xe7\xc2\xed\x04\xd0\xff\x04\x81\xc9\x58\xe5\xff\x74\x45\x22\x94\x4c\xf3\x20\x20\x38\x9b\x32\x95\x9b\x5e\x12\xf8\x0f\x08\x06\x49\x08\xa2\x70\xf8\x69\x5a\x3f\x99\xe7\x5e\x7e\x85\xba\x3b\x3c\x77\x3f\x04\xef\x9e\x09\xe7\x6b\x6c\x47\x30\x2e\x41\xd5\x0e\xad\x04\x54\x1e\x0f\xca\x4a\x42\x50\x27\x22\x26\x5f\x82\xff\x60\xef\x46\xaa\x75\x47\xf9\xde\x24\x91\x35\xdd\x07\x7f\x24\xa4\xe7\xe0\x3b\xe2\xe3\x09\x47\x72\x76\x7a\x97\x60\x88\x3c\x52\x08\x16\xfa\xe6\x37\xc0\x30\x95\x6e\xa2\x5f\x0a\x86\x9e\x4a\x00\xa4\xa8\x01\x7b\xcb\x72\xb2\xf2\xfd\x83\x64\x3b\xdc\x01\xd8\xff\x28\x68\xd3\xca\xf1\x00\xae\x8b\x81\x8b\x92\x6c\x96\xa8\x50\xbd\x69\xd8\x93\x1d\xbf\xdc\xff\x31\xc6\x7c\x53\x7c\x4f\x59\x59\xd0\x4b\x74\x4a\x34\x66\x47\x06\x6d\xcc\x61\xf6\x3b\xe6\x25\x1b\x59\x0d\x68\x8a\xe3\xc9\xb5\x3f\x39\x20\x07\xd8\x58\x4e\x46\x24\xff\xd2\x94\x16\x50\xa3\x1d\xcd\x5a\xbf\xae\x7c\xa1\x20\xb1\x1c\x8d\x01\x94\xbe\x96\xe8\xdd\x09\xb6\x43\xd5\x68\x5d\x10\x65\xd9\x8f\x39\xb6\xed\x7c", b"\x76\xbb\x0e\xcb\x9f\xae\xc7\xc9\x71\x13\x7e\xa6\xfe\xac\xf1\x79\x20\x73\xae\x80\xbe\x1c\xa8\xed\x9c\xec\x2a\x5c\xa6\xcd\x51\x0f", b"\x34\x92\x02\x46\x73\x0e\x09\x74\xfb\x0f\xaa\x57\xe7\x7f\xc5\x0a\xb7\x87\x26\xc8\xe5\x15\x79\xa0\xef\x5e\xbe\x3f\xce\x3b\xa7\xcb", )?; test( HashAlgorithm::SHA1, b"\xce\xe0\x6f\x79\x23\x32\x08\x0d\x6e\x73\xb3\xf0\x2f\x5e\xc1\x69\x96\xb6\x69\x95\xbe\xab\x4a\x2b\xa0\x92\xf4\x0d\x85\xc8\xac\x1a\xcc\xf5\x4f\xba\x06\x81\x28\xc8\xcd\xba\xda\x20\x93\x60\x77\x6a\x77\x06\x45\x50\x15\xe7\x3e\x92\xc6\x24\xad\xa1\xdf\xa6\x2e\xc7\x94\xcf\x2a\x1a\x92\x94\xf3\xfb\x55\x99\x4b\xc5\x21\x1a\xdd\x1c\x68\x5d\x9a\x54\xac\xd5\xbc\xd8\x30\xd9\xa4\xfc\xff\x29\xae\xc5\x00\x1c\x3b\x2b\x2a\x97\x06\x04\x6f\x38\xbf\xe4\x8e\x85\x22\x76\x8f\x1c\x6f\x08\xa8\xe2\x40\xe1\x23\xed\x30\xe2\x0f\xc4\x6c\x19", p, q, g, b"\x5c\x92\x05\xfb\x64\x9d\x3b\x4b\xa2\xd4\x4c\x80\xa9\x25\xe3\x0d\x27\xb0\x5b\xd3\x39\xf1\xce\x35\xe0\xd0\x41\x9a\x91\xed\x31\xfd\x10\x8c\x51\xa2\xa6\x2c\xf9\xd0\xad\xfd\x87\x7d\x27\xcf\x55\x75\xe4\x3a\xc7\xbf\xcf\xce\xec\x56\x73\x73\x6c\xae\x08\x95\x16\xdf\x8e\xb1\xea\x6b\x56\x31\x98\xb2\x4a\x6e\x25\x22\xf3\x20\xb1\x23\xbf\xb2\x50\xd4\x3b\x60\x0d\xf9\x29\x8e\x12\x1b\x6c\x5d\x2e\x63\x7a\x98\x92\x15\xe0\x95\xe6\x03\xee\x6d\x4e\x8a\x2d\xcd\x17\xb9\x08\x91\x8a\xa5\x14\xc8\x6a\x33\xd8\xc7\x17\x57\x8d\x86\x12\x61\xda\x43\xf7\x32\x50\xff\x2b\xe7\x46\xc6\x91\x6f\xc7\x28\x71\xfb\x42\xa2\x79\xd2\x25\x95\x05\x1b\x8a\xc0\x4a\xfb\xf2\x01\x30\x63\xe3\x16\x61\xb1\x17\xc5\xd0\x94\xb4\xc2\x32\xb2\x2f\x21\xd2\xc6\x5d\x63\x61\x29\x0c\x08\xf1\x2b\xef\xd1\xf5\xa2\xb9\xb5\x25\x9a\xf0\x43\x5b\x97\xb4\x32\x82\x97\xc2\x52\xd8\x13\x49\x9f\x52\x09\xdf\xa3\x5e\x91\x98\xde\x68\x50\x1a\xf4\xca\x86\x58\x94\x2d\x05\x9b\xb6\x2b\x8e\x55\xa3\xce\x61\x20\xa7\x8e\xe0\x98\x13\x2e\x8d\x2d\xc3\x75\x7f\x7e\x60\xf8\xc0\x8c\x4e\x43\xfe\xac\x67\xab\xcd\xdd\x1e\xa2\xf0\x16\x83\x9f\xb1\xa0\xf7\x97\xb8\xb1\x37\xab\x43\xb6\x45\x08\xef\x69\xf6\xae\x0f\x3a\xbc\x4e\xd6\x82\xaa\x7e\x38\xfa\x51\x46\xfe\xc6\x2e\x01\xe0\x95\x1a\x6e\x81\x15\x2d\xe4\x31\x71\xca\x88\x69\xfa\x1a\x42\xa4\xfb\x2d\x8a\xe5\x12\xc0\x05\xfd\x97\xd1\x2b\xb1\x3f\x29\x9a\xb9\xf5\x32\x1e\xe2\xfc\x39\xb2\x8e\x61\xc9\xeb\xcb\x91\xec\xd2\xb6\x10\xfd\x82\x91\xf5\x38\xa0\x0d\x06\xd0\x57\xc3\xe7\x94\x22\xa9\x31\x27\x9f\xed\x9d\x93\xb0\xb6\x53\x3f\xae\x44\x1e\x98\x41\x30\x25\xfb\x4f\xa7\x3c\xde\xfa\x80", b"\x6d\x02\x53\x6d\xb5\x46\xf2\xbb\x1f\x65\xff\x0b\x91\xb9\x64\x80\x2b\x38\xd1\x71\xe6\x78\x05\x4e\xe4\x1f\x2b\x85\x63\x80\x9c\xfa", b"\x6b\xc5\x11\x20\xe3\x5c\x95\x5a\xb8\xf7\x17\xf8\x93\x0d\x8c\xc8\xde\xf8\x50\x54\x15\xcf\x15\x9d\x25\x16\xf9\x65\x78\x84\x2f\x31", )?; test( HashAlgorithm::SHA1, b"\x58\xab\xa2\x4e\x94\x81\xd1\x15\x1b\x57\x4b\x14\x6a\xc2\x1b\x17\x11\x0e\xd0\xb9\xbf\xaa\x55\xa4\xe2\xe0\x6d\xcd\xc1\x8b\xd1\x0c\xdf\xaf\xac\x04\x71\x89\xf5\xba\x9f\x10\x37\x7a\xff\xb4\x0a\x51\x4d\x52\x8a\x34\x83\xfe\x8e\x64\xb8\x31\xea\x0c\xd0\x76\xce\x58\x39\x42\xb9\x38\xa4\xb2\x57\xd0\xb5\xa9\x24\x12\xe0\x1d\xfd\xa8\x21\x7d\x5f\x80\x54\x59\x6a\x61\xd5\x73\x7d\x8a\xd8\x11\x2a\xe2\x28\x22\x0e\x3b\xff\x60\xe2\xe8\x91\xd0\x3d\x53\xfb\x14\xf1\x4d\xd9\x19\x75\xdc\x15\xd6\xb7\xbd\x62\xe9\x9d\x74\xef\x38\x39\xfd", p, q, g, b"\x2a\xdb\xc0\x7d\x8a\xee\x28\x4e\xc9\x82\xc4\xb9\x5e\x1e\xc3\xec\x3f\x5f\xd5\x17\x23\x68\xdd\xf8\x3f\x9a\x3c\x69\x65\x52\x91\xde\xe6\xb9\x9e\xd7\x13\xe5\xa1\xfe\xc3\x38\x23\x9b\x81\x99\xc5\xa3\xbb\x2b\x5e\x2e\x7f\x23\xfc\x79\x50\x58\xa9\xac\x70\xeb\xdf\xf2\xb3\xda\xaf\xfa\x38\x9e\x97\xfe\xe3\x5f\x17\x49\x61\xf1\x2d\x63\x4e\x8b\x82\x50\xb8\xb7\x70\xb8\xd7\x11\x3d\x0f\xbc\x02\x0b\x7b\x10\x8f\x8d\x6b\x2d\x7c\xb6\xc5\x9e\x2e\x15\x10\x15\x14\x5a\x8e\x37\x4f\x9b\x73\x96\xe9\x70\xd9\x1e\x3c\x1f\x85\xce\x23\xdc\xae\x12\xb2\xf5\x37\x41\xcf\xc2\x35\x0b\x58\x2c\xa8\x7f\x0f\xf9\xab\x50\xad\x0c\xa2\x87\x9e\x21\x6e\x61\xa5\xc3\x58\x97\x0a\x3c\x35\x28\xdc\xd9\xec\xe6\xb8\x3d\x52\x5b\x31\xfe\x68\x76\x96\xa2\xa2\xc6\x5e\x34\xf2\x85\x4f\xea\x6f\xf9\x22\x44\xd2\x75\x00\xf7\xda\x94\x6c\x37\x16\x97\x56\xf4\xa4\x66\x4b\x29\x09\x61\x15\x49\xad\x2b\x93\xeb\xac\xeb\xfc\x27\x0e\xcf\x42\x04\xe6\x64\x1d\xbc\xe0\x5d\xa2\xc0\x00\xa4\xca\x5a\xc8\x85\x40\x6b\xa1\x55\x80\x74\x94\x70\x61\x80\xd5\x4c\xc0\x12\xce\x06\xe7\x34\x02\x4f\x4c\xcd\x88\x2b\xdd\xd2\x25\x7a\xfb\x5c\x28\x7b\xc3\xa8\x57\x0e\xdf\x21\xa2\x0a\xfe\xda\x0c\x76\x2a\xd6\x96\xfb\xa1\x77\xa5\xf2\xf9\xd6\x09\x35\x5c\xb9\x1d\x72\xcc\xac\x8b\xb9\xe7\xc3\xcf\xf1\x83\x4d\x86\xb0\x77\x2a\xec\x74\x1d\x7b\x4b\x3c\x3e\x43\xbb\x26\xec\x9f\x5e\x86\xb8\x68\x5e\xa5\xc6\x25\xb6\xae\xa4\x50\xa4\x6e\x85\xe3\x80\xb1\x58\xde\x6a\xaa\x27\x01\xff\xad\x0c\x7d\x1e\xd0\xdf\x35\x5d\x09\xd0\x6f\xe1\x75\x8b\x2f\x27\xa5\xd0\x2a\xa2\x83\xae\xc9\xfd\x12\xd3\xb6\x2d\x50\x4d\xca\x0b\x66\x32\xe8\x9f\xc5\x5f\xb0\x83", b"\x2c\x46\x2d\x49\x34\x4f\x3a\xd0\x3b\x67\x98\xf9\x64\x52\xf7\xd6\x63\x51\xce\xad\x91\x9e\x82\x01\xb7\x66\x5c\x87\x7f\x82\x55\xbb", b"\x50\xe8\x90\x8a\x1c\x66\x84\xa2\xca\xa8\xaa\xfb\x43\x2c\xda\x4b\x76\x99\x00\x8c\x72\xd8\xd6\x22\xc3\xda\x41\x71\xe5\x1c\xfd\xbf", )?; test( HashAlgorithm::SHA1, b"\xe1\x06\x04\xca\x00\x72\x8e\x53\x36\x21\xdb\xb6\x61\x8b\x0c\x87\x7c\x49\x02\xa2\xed\x79\xaa\xf4\x0a\x4d\xaa\x34\xd6\xcc\x21\x6a\xd4\x64\x8d\xaa\xb6\xcc\x1e\x18\x45\x1b\xb9\x4e\x6a\x1c\x0c\x6f\x9d\x0d\x88\x39\x62\xee\xbd\x50\x7d\xa0\x99\x78\x80\x08\xda\x23\x20\x5e\x3b\x4e\x90\xfa\xd9\xae\x85\x70\x74\xff\xea\xc6\x34\x30\xc0\xfa\xcb\xae\x48\x9c\x54\xc9\x57\xdb\x09\xd5\x3e\x12\xb6\x56\xcc\x27\x86\x15\xa3\xa5\x61\x2a\xf4\xc2\xf1\x68\xbd\xeb\x11\x8a\x42\xa2\xa6\x71\x03\xfa\xc3\x21\xad\xf5\x68\x8b\x05\x84\x8f\x7c", p, q, g, b"\xb0\x44\x8d\x43\xc5\x20\x37\x7b\x7d\xf2\x14\x96\x9f\x59\xff\xd4\xe0\x01\x0c\x12\xd7\xe5\xfa\x8f\x24\x1e\x9c\xe1\xc6\x34\x43\x9c\x94\x70\x0e\xd5\x74\x2a\x83\x22\xd4\x05\xdd\x05\xde\x99\x53\x44\x78\x31\xc7\x67\x4e\x5a\xe1\xb8\x90\x41\xfb\x8f\x2e\xc1\x05\x4b\x92\x8c\x64\xab\x86\x2f\x02\x1a\x55\xeb\xce\x83\x8d\x2a\x3d\x2c\x76\x45\xec\x7c\x0a\x1a\x46\x03\x61\x7e\x4f\x50\x89\x29\x14\x4c\x1e\xf2\xb0\x39\xbc\x78\xb5\x93\x62\xd5\xba\x95\x37\x90\x6e\x66\xc8\xe9\xc9\xa3\xc6\x8e\x71\xb3\x5d\x88\xb8\xba\xc8\x6c\xac\xbe\xbd\x96\x2c\x66\xe1\x81\x29\x63\x7f\xad\x2d\x98\xd2\x1e\x45\xa3\x26\x72\x64\x94\x92\xf1\x31\xba\xe8\x8c\x99\x89\xbd\x63\x72\xe1\x74\x92\xbe\xdf\xf4\xd9\xb0\x91\xb3\xdd\x00\xeb\xca\x6b\xcc\x49\x14\x84\x80\x58\x9f\x95\x93\xe3\x27\x95\x29\x9f\x3d\xe7\xe0\x9d\x88\xbc\x0e\xd2\x7b\x7e\xf2\x2e\xf7\xd2\x02\x20\x7f\xb5\xce\x8c\x91\x71\x2c\x3b\xd5\xe7\x58\xd2\x82\x28\x09\xea\x5d\x2c\xb2\x88\x33\x2a\xa0\x76\x03\x68\x25\x92\x81\xa3\x44\x47\xff\x5a\x98\xc9\xc9\x7c\x1d\x58\x38\x3c\xd1\x4f\x6d\x59\xbb\x5e\x57\x63\x21\x7b\x23\x37\xec\x23\x21\x26\x81\x97\xf0\x2c\xec\xd0\xd9\xfd\x93\xdb\x39\xf8\x05\x9a\x38\xbb\xb3\x57\x92\xba\x0d\x4e\xd1\xba\xd9\x5a\x05\xb4\x81\xc3\x9f\x6a\xdc\x90\x17\xde\xc1\xd6\x62\xb0\x80\x3f\x2e\xcf\x08\x45\x93\x57\x65\xf9\x35\x6d\xb5\x36\xc8\xc1\x18\x87\xd9\xe4\x4b\x73\xb6\x99\x6a\xe7\xac\x24\xfc\xad\x9c\x23\x01\x7e\x5c\x2a\xca\x88\xb5\xa1\x36\xb6\x30\x72\x98\xb8\x5f\xf0\x10\xf9\x64\xb7\x47\x7a\x4f\x98\x08\x00\xe6\x9d\x3c\xc0\xf4\x38\xaf\xf7\xf2\xdf\x8a\xc6\x1d\x64\x43\x5f\xfa\xf5\xe4\x66\x33\x60\x9e\x87", b"\x56\xab\x99\x47\xac\x94\xfe\x3d\xf7\xe3\x58\x01\x66\x0f\x68\x75\x3b\x0b\x62\x0a\x26\x59\x4c\xb8\xfd\x37\x5b\xe3\xea\x4d\xbf\x05", b"\x60\x8e\xd1\x83\x51\x39\xaf\x29\xa2\xe3\xd8\x74\xdf\x46\x5e\xdd\x8d\x64\x28\xf4\x03\x57\xd9\xae\x49\x04\xef\xe8\xbc\xcb\xd0\x35", )?; test( HashAlgorithm::SHA1, b"\x8a\xf3\x1f\x66\x77\x2f\xb0\xc3\x1a\x8c\x5b\x28\xe5\x68\xe6\x36\x8c\xb6\x6b\x59\x1e\xdf\xb0\xdb\x86\x7f\xd9\x9e\x83\xfe\xb3\x63\x8b\xc8\x0f\x0b\x14\x48\x3d\x06\x9e\x8f\x2e\x16\x7c\x8b\x0f\x10\xcd\x6b\x45\xd0\x39\xb7\xd6\xf8\x33\xbd\x58\xd9\x9b\x00\x59\x7a\xee\xf8\x2f\xa3\xaa\xe2\xe5\x5d\xed\x62\xab\x66\x08\x10\xde\x0f\xe1\xc9\x2d\x53\xad\xf9\x8c\x83\x8c\x18\xfd\x76\xa2\x73\xea\x12\x11\x9d\x67\x5a\xf7\x27\x01\x18\x69\x94\x3d\x76\x5b\x96\xef\x26\x62\x70\xb4\xf8\x9a\xc7\x2e\xda\xdc\xf7\x07\xa4\xa2\x1b\x75\x33", p, q, g, b"\x22\xf3\xdb\x9e\xa3\x69\x93\x8e\xd7\x50\xd5\xed\x37\x81\x36\x8d\x59\x4e\x62\x63\x5c\x6b\x6e\x10\x3d\x6d\xb4\x89\xa9\x97\x2f\x39\x82\x03\xab\xb9\x73\xd5\xad\x9c\x0d\xc1\x10\x58\x69\x78\xd2\x06\x14\x83\xc0\x20\x27\x38\xce\xb0\x1a\x66\x5d\xd2\x2f\xc5\x68\xcb\xdf\xf2\x14\x8a\xe6\x64\xdf\xfb\xf8\x88\xe4\xdd\xa5\xa0\x4f\xd3\xe8\x93\x98\xb4\xf1\xff\xc3\xa3\x81\x3a\xe9\x4d\xa1\xf8\x96\x5e\xfb\xe7\xf3\x00\x94\x87\x49\xe9\x75\x7c\xc7\xc0\x5f\x6e\x53\xfd\xbf\xf9\x94\xc2\x23\xab\xa2\xc1\x37\x15\x1b\x6a\x32\x0f\x5b\x7f\x8c\xdd\x60\x03\xba\xa6\x60\x20\x16\x29\x90\x62\x40\x99\xf3\xcf\x56\xd6\x8b\x74\xe9\x6e\xe0\x92\x40\xf2\xcf\x11\xe3\x95\x4e\x75\xb2\x61\xef\x9e\x8e\x35\x51\xc6\xc0\x0f\x41\xe9\xeb\x17\x42\x12\x03\xa4\x56\x53\x88\xc3\x21\xc1\x32\x5f\x72\xeb\x10\xc2\x8a\x9d\xee\xdd\xcb\x48\x06\xf6\x25\x38\x2b\x37\xf0\xbe\xcf\x77\x93\x6b\x7f\x83\xd2\x6b\xf1\xee\x1f\xe0\x5e\x8a\x00\x05\xa4\x05\x8c\x67\x8e\xb5\x69\xe3\x39\x42\x3e\x7c\x84\x43\x05\xf4\xa1\x8b\x11\x60\xa0\xc4\x30\x51\x3f\xad\x71\x58\x96\xb6\x2b\x9d\x6e\x24\x68\x23\x2a\xe3\x75\xf5\xf3\xc0\x05\x62\x45\xeb\x46\x16\xba\x11\xa6\x02\x94\x10\xa9\x55\xaf\x09\xf0\x75\x95\xfe\xfa\x03\xe5\x51\x6c\x95\xa4\xcf\xcd\x66\x04\x6b\xe2\xa4\xf7\xb3\xab\x27\x4b\x21\xc0\xa4\xf1\x26\xc4\x82\xc9\x34\xc7\x9d\xcb\xbd\x69\x16\xf3\xb8\x87\xb2\x60\x04\x72\x49\x5c\x83\x35\xde\x12\x1c\x77\x20\xf2\x9a\xe5\x6f\x5c\xcf\x9b\x99\xc9\xce\x56\x55\xc5\xe1\xd1\x5d\x67\x89\x5a\xf0\xde\xe5\x86\xbc\x49\x1a\x97\x24\x1f\x7e\xff\x43\x4b\xb7\x9a\xad\x83\x1d\xb0\x69\x57\x81\xe6\xb5\x12\xe8\x70\x24\x07\xa7\xd7\x48", b"\x0f\xda\x7a\x8a\x3e\x5d\x32\x4f\xc0\xa1\xc2\x84\x1c\xd2\x2f\x98\x75\x7a\x0c\x6a\x2a\x46\x5b\x0d\x9d\x65\xbd\xa9\xb2\x3b\x3c\x1a", b"\x40\x86\x02\x65\x22\x90\x85\x45\x3f\xe5\x84\x87\xa9\x33\xed\xf3\xc2\x84\x33\x69\x4c\x7b\x85\xf6\x37\x0d\x9a\x47\x83\x16\x82\x37", )?; test( HashAlgorithm::SHA1, b"\xe2\x45\x6e\xf5\xd4\x65\x73\x1b\x97\x6f\x2a\xd1\xfc\x94\x63\x4c\x05\x69\xa0\xff\x75\x66\xa4\x9d\x47\xd6\x9e\x60\xb3\xb6\xd7\xeb\x2a\xb2\x5c\xd4\x9c\x93\x12\x99\x79\x6b\xff\x7e\x97\x74\x07\x5e\xa2\x0a\x97\x2e\x39\x49\xa2\x9d\xfb\x50\xb2\xb5\x61\x3b\x45\xc5\x96\xca\x5d\xab\x28\x2f\xf1\x83\xf5\x64\xa0\x63\x11\xa4\x96\x01\xa1\xe8\x56\x0d\x43\xc6\xa4\x81\xce\x71\x3f\x46\xc6\xea\x85\xbf\x4c\x16\x48\x9f\xbd\x72\xcf\x55\x2b\x26\x51\x62\x98\xbc\x66\x94\x2a\x05\xd5\xa8\xe6\xd0\xf6\xa8\x8f\x3e\x67\x8d\x31\x0e\x29\x7b", p, q, g, b"\xdc\x9d\x68\xb5\x3f\x35\xc2\x9f\x7c\xa0\x03\xa2\x58\x3e\xc8\xf8\xef\x5d\x78\xa0\xe4\x5d\xb3\xc8\x84\xd3\x5d\xf4\xfb\x53\x1a\x08\x0e\xe3\x83\x1b\xff\xd3\xc7\x56\xea\x50\x42\xc7\x61\x45\x70\xfb\xa2\xf6\xca\x48\x70\xdb\x4a\x45\x3d\x0f\x79\x3f\xb4\xd0\x22\x5d\x94\xf2\x74\x12\xdb\xdf\x43\x43\x2f\x52\xcb\x8f\x86\x7f\xe5\xf4\x92\xa8\x87\x6d\x7b\xd8\x50\xd8\x99\xba\x2f\x0a\x53\x82\x0c\x44\x08\x41\xfe\x0c\xb7\x6f\xe0\x44\x4b\xd6\xc3\x23\x57\x85\xa3\xda\x30\x81\xfe\xf9\x9f\x53\xa1\x95\x31\x4a\xef\xe9\x55\xf2\x96\x4c\x56\x50\x6f\xcc\x96\x9b\x67\xb3\x23\x76\x6d\x29\x9c\x0b\x02\x98\x1c\x72\xa2\xce\x3d\x75\x24\xae\x6f\x08\x45\x87\x95\xfd\x32\xe3\x1b\x47\xaa\x1f\x97\x4e\x35\x60\x81\x16\x3c\xb2\x3e\xfd\x73\xa9\xe6\x55\xde\xef\xe5\xe7\x34\xce\xb5\x8e\x88\xa9\xdb\xb5\x24\xef\xf7\xe1\x1c\x3e\x30\x68\x07\x02\xd8\x56\x0d\xd8\xb6\xad\x9f\x61\xe7\x24\x6c\x6d\xde\x16\x4e\x91\x49\x51\xd6\xa0\x57\x31\x52\xec\x8b\xde\xa6\x79\xdc\xa1\x98\x5b\xcf\x26\x73\x04\xd5\xf1\xbc\xe2\xf3\x2b\xb9\x94\x6a\x05\x68\x57\x35\x9a\xfb\xaf\xfa\x59\xbe\xe6\x1a\xd9\x60\xc5\x67\xef\xe3\xf1\x14\x5a\x8a\x87\xc2\x49\x1f\xa6\xb3\x3f\x7e\x71\xfc\xdd\x8f\x1f\xfb\xcd\x2b\x89\x92\x09\x07\xd1\x14\x4a\x8c\xf0\x57\x3f\x5b\x89\x21\x7b\xc0\x59\x8c\x6e\x17\x54\xf1\xae\x7d\x9d\x42\xa6\x08\xa0\x51\x62\x14\x19\xda\x91\xd1\x1b\xda\x9b\xb9\xdf\xa7\x11\x8e\x4b\x66\x3e\x7b\xff\xe6\xe9\x94\x6c\x77\xce\x9f\x80\x86\xdf\xc8\x22\xa7\xef\x72\x88\x88\xb3\x16\x54\xa1\x9b\x6d\xeb\xd2\xca\x62\xf5\xe3\xb4\xe2\x89\x81\x04\x35\xb3\x63\xec\xab\x51\x1f\x47\xe9\xe1\x57\xf0\xf4\x19\x88\x62\xca\x13", b"\x77\x86\x40\xce\x75\xda\x58\x4a\x6a\x83\xf9\x79\x4c\x4f\xfd\xbe\x30\x41\x1b\xe4\x30\x27\x75\x8c\x74\xf8\x9f\x7c\xcc\x7f\x39\x83", b"\x61\x25\x48\x1e\x10\x3f\x78\x03\xb2\xf1\x6d\x9a\x4d\x00\xf8\x81\xe0\xb3\x67\x02\x4d\xf5\x82\x2f\x7c\xbe\xb5\x71\x1e\x0e\x44\x01", )?; // [mod = L=3072, N=256, SHA-224] let p = b"\xf6\x3b\x3c\xdd\x64\x6d\x8e\x7d\xdb\x57\x21\x6a\xa6\xee\xc2\x13\x4d\x70\x74\x88\xa1\xf2\x9c\xfa\x99\x70\x64\x5f\x12\x27\xea\x5d\xb2\xe3\x18\xee\xa5\xda\x16\x87\xc7\xed\x90\x50\x96\x69\x34\x5e\xd6\x13\x4c\xff\x32\x20\x3a\xb7\x2a\xec\xbf\xa6\x93\xd2\x16\xae\xb5\x5d\x8d\x28\xa9\x81\xf4\xab\xff\x07\xd1\x31\x9a\x79\x9b\xe5\xdd\x74\x6f\x84\x84\x28\x17\x92\x9c\x30\x5b\x40\x85\x98\xaf\x12\x04\x5d\xaa\x2f\x1c\xcc\x8b\xe4\xd8\x1b\x51\x3c\x63\x0f\x01\x7f\xec\x16\x58\xac\xa1\x08\xa1\xaf\x61\x20\xec\x05\xe3\x01\x8c\x42\x53\xc9\xdd\x35\xbc\xe0\x62\xb7\x3d\x0f\x2a\x93\xd4\x1c\x48\x1a\x5c\x43\xbb\x97\x90\x96\x82\xd3\x9a\x9a\x60\xdc\x3c\x35\xe3\x63\x75\xde\xc6\xce\xd0\xd2\xdb\x3b\xa0\xd1\x11\xbe\xde\xa7\x01\xa0\xe4\x75\x36\x24\x97\x7a\x9e\x75\xb7\x0a\x74\xe2\xb8\x1e\x38\xa5\x2a\xb2\x2d\xa1\x31\xb3\x54\x16\xd3\xce\xc9\x66\x30\x79\x74\x6a\x76\x34\x76\xe5\x75\x98\x14\x2e\x39\x86\x15\x45\xda\xaf\x8d\x38\xa1\x76\xf2\x6c\x71\xf5\xaf\xeb\xd9\xc5\x62\x0d\xa8\x0c\xf3\x45\x2b\x55\xc3\x7c\x66\x1b\x4a\x1e\xc0\x35\x17\x10\xb9\xde\x4a\x3c\xbe\x0b\x98\xb4\xd9\xec\x89\x12\x8d\x97\xaa\x7e\xfb\x19\xdb\x8b\xa4\x3c\xc0\xbe\x25\xc2\x00\xf9\x0e\x15\x06\xcb\x78\xec\x0c\x33\x6d\x7a\x95\x61\x3d\x42\x04\xe8\xed\x68\xd0\xf0\xa6\xc7\x84\x20\x10\x5a\x8d\x2d\x43\x8f\xbd\x25\x51\xa6\x4a\x1a\x0b\x03\xff\xb8\x78\x74\x2f\x8c\x99\x79\xcf\xa8\x73\x94\x15\x02\x81\x99\x8d\x51\x70\x1d\x5f\xcf\xa9\x69\x6a\x49\x89\xfd\x25\xf4\x00\x95\x5e\x62\x6b\x1a\xbe\x92\x6c\x0a\xfa\x69\xaa\x69\x81\x90\x0e\xff\xcd\xd0\x30\x59\x2f\x82\xb2\x04\x2a\x47\xa9\xa5\xa8\xcb\x02\x83\xdc\x4d"; let q = b"\x80\x00\x00\x00\xba\x46\x34\xb5\xfa\x4d\xa0\x54\xbd\x0c\xa4\x8a\xe4\x90\xe5\x77\x11\xf3\x81\x19\x38\x42\x42\x91\x59\xba\x7c\xa1"; let g = b"\x8a\xd4\x55\x3c\x4e\x49\xaa\x24\x72\x8a\xb5\x02\x44\x17\xb1\x32\xd2\xca\x53\xa5\x5d\x95\x94\x58\xf2\xf7\x59\xad\xb0\x43\x5b\xee\xef\xa3\xa2\xcf\xcd\x00\x38\xe2\x42\x06\x43\xfc\x4a\x4d\xee\xb5\xd9\xfe\xaa\x1e\xdf\x21\x19\x3b\x40\xe1\x4b\x42\x98\x2a\x94\xf3\x5c\x58\xb8\x11\x47\xd7\x18\x9d\x26\x3c\x9b\x12\xfe\x63\xab\x9f\xa5\xf6\xf0\x3a\x28\x60\xc1\x86\x43\x2e\x3a\xb0\x4f\x2a\xb0\xf2\xfb\x61\x47\xbd\x9b\xf7\xed\x5d\x20\x71\x3b\x9d\xa2\x13\x83\xe2\xc3\xa1\x68\xe7\xd0\x9d\x3d\x8a\x5a\x05\x8f\xd2\x30\x95\xb5\xac\xfe\xb8\x64\xa3\x30\x6b\xe2\x42\x5f\xa1\xad\x32\xad\x6d\x93\x82\xe6\x03\xb0\x3c\x68\xaf\x4a\xf0\x24\x63\x97\x10\x2c\x41\x55\xcb\xa8\x11\xab\xf9\x9d\xa7\x83\x9e\x77\xb2\xea\xc9\x97\x05\x88\xca\x1d\x0a\x23\x61\x72\x3a\x16\x4a\xc9\x22\x9c\x2e\x80\xdc\xfa\x8d\xb4\xf9\xe2\x98\x03\xef\xfb\x31\x68\xc7\xfe\xd7\xa3\xa6\xde\x40\xdd\xa1\x9a\x05\x36\xaf\x9b\x5b\x7a\xfa\xef\xb9\xc7\x0d\x6a\xe8\xdf\x12\xda\x65\x8f\x62\x36\x04\x3a\xea\x87\x3d\xb2\x9c\xeb\x6f\x07\xd1\x08\xf5\x22\x56\x87\xbd\x0c\x30\xe3\x08\x4e\x20\x90\xb4\x5a\xe2\xf9\x2a\x97\xb8\xec\xb7\xa9\x70\x5c\x49\x56\xb8\xb3\x1c\x4a\x3d\x61\x10\x7c\x84\xe4\x7a\xdd\xa6\xc8\x0d\x5d\x22\xda\xb3\xd8\x59\x22\x0f\x9d\x5a\xab\x13\x67\x7a\xe3\xdf\x16\x8f\x0c\x17\x6d\x17\x6b\x54\x50\x6c\x63\x98\x53\xf0\x4d\xde\xf2\x72\x2f\x39\xc1\x8e\x5c\xe4\x26\xe1\x45\x62\xad\x8f\xf2\x62\x47\xaf\x88\x87\x0e\xfb\x72\xc0\xcc\xe8\x36\xde\x8f\xee\x67\xa6\x62\x37\x82\x45\xb5\x02\xbf\x1f\x83\x09\x99\x88\xa0\x93\xce\x7c\xdc\x81\x36\x4c\x78\xb1\xf4\xa5\x1b\x80\x0d\xf6\x13\x7c\x71\xd6\x5e\x6b\x08\x9a"; test( HashAlgorithm::SHA224, b"\x95\x79\x73\xfc\x3f\x3f\xe3\xf5\x59\x06\x5b\xe5\xd4\xa0\xc2\x81\xcf\x17\x95\x90\x18\xb9\xa6\x70\xd2\xb3\x70\x6d\x41\xd5\x81\x2e\x37\x30\x10\x05\xf8\xb7\x0e\xbd\x2f\xba\x3c\x40\xa3\xf3\x77\xa7\x51\xb6\xcb\x96\x93\xe3\xcb\x00\xd9\x28\x88\x24\x7d\x07\x92\x1d\x3c\x1e\x92\x57\xce\x08\x73\x3b\x89\x26\xe0\xdf\x7b\xdb\x6e\x85\x5f\x1f\x85\x10\x75\xd4\xe6\x28\xd1\x10\xd4\x2b\x64\x3b\x54\x87\x6e\x5f\xaa\x36\x11\x47\x7e\xe6\x83\x71\x56\x25\x55\x26\x9e\xd6\x2a\x92\x71\xba\xd5\x0c\xc4\xd4\x60\x38\xde\x2d\xd4\x19\x20\xc2", p, q, g, b"\x42\x24\x35\x39\xe4\x9d\xb9\xea\x19\xd9\x8d\x97\xf6\xf2\xa9\x4b\x23\x52\x98\x12\xdf\x88\x9e\xaa\xbc\xfe\xda\x01\xce\x4c\x75\x94\x87\xfb\x89\xbc\x82\xda\x75\xfe\x1c\x91\x34\x36\x1f\x86\xde\x47\xd1\x6d\x8e\xee\x80\xe5\x6a\xc5\x02\x17\x8e\x8e\xd8\x12\x94\x77\xaf\x8b\xfb\xd8\x26\x2c\x5e\xdd\x93\x7e\x1a\x86\xc0\xf0\xe7\xb2\xaf\xe7\xbc\xbd\xdf\xcb\x58\x14\xce\xd0\xb7\x56\xa7\x6c\xa1\x78\x42\x3b\xb4\xd5\x78\xc5\xda\x18\x37\x12\xd9\x68\x58\x26\x40\xaa\x0e\xc7\xe9\xfb\x56\xbf\xd9\x60\xd7\xa5\x75\x49\x74\x7d\x8f\xb7\xad\xe4\x7c\xfe\x81\x6c\x1e\x57\xda\x66\x33\xda\xcc\x53\x7d\xe0\x60\x81\x39\x64\xbb\x5b\x27\x57\xa3\x12\xf9\xda\x3d\x84\xe6\x0a\xff\x98\x17\x00\x51\xd3\xd9\x0e\x38\x0b\x8b\xcc\x19\x86\xc5\x8f\xf9\xdc\x91\xe8\x82\x7d\x4f\x9f\x5f\xc4\xb2\xb2\xe7\x43\xcf\x93\x89\xff\x02\xde\xc0\x1f\x5d\x43\x4b\x43\x0d\x16\x2e\x89\x1c\x33\x55\xf9\x18\x55\x33\x9f\x8d\xf5\x83\x00\xe4\xc9\x93\xae\x4d\xf8\xc4\x31\x8b\x5c\x4b\xd0\x52\x83\xca\x4b\x46\xb7\xd2\xfb\x0f\x64\x76\xbf\x15\x90\x7f\x50\xdd\x41\x41\xaa\x7a\xca\xc9\xda\xa6\x2e\xcc\xd3\xa6\x73\x57\x12\x20\x60\xb6\xce\xce\x04\x46\xa9\x3e\xb2\x30\xad\x93\xbc\x9a\x4d\x1b\x1e\xfe\xec\xa1\xe3\xfc\x83\xc1\x19\x78\x50\x35\xb4\x39\x50\x9f\xfb\x79\x68\xb1\xa4\x48\xb7\xbd\x83\x15\x75\x3f\xdf\x04\xa2\x56\xec\xa1\x56\x2a\x11\xb0\x96\xc9\x0a\x36\xb3\x53\x65\x9c\xbd\xe4\x42\x0e\x17\xe9\x0b\x94\xc4\x3c\x75\x19\xc6\x06\x41\xce\xec\x05\x6f\x89\x7b\x97\xd6\xbb\x18\x61\x26\x8e\x0d\xc7\x9b\x7c\x3b\x6b\x76\x39\xc2\x55\xbf\x06\x86\x57\x37\x45\x91\x26\xcb\x46\x5b\xc1\xda\x4a\x04\x3a\x19\x63\xda\x7d\x63", b"\x2e\x59\xd5\xf3\x0f\x73\x78\x1d\x38\x25\x5b\x70\xde\xde\xeb\x38\xae\x78\xdf\x4f\x00\x2c\x1f\x74\x7c\x08\xde\xad\xc6\x53\x01\x55", b"\x61\x5c\x55\xb2\xdf\x0c\xa2\x8c\x60\xa6\xb3\x85\xc5\x8f\xa0\x36\xdf\x8c\x4b\x2f\x4f\x19\x35\x73\x0b\xf8\xf4\xf0\xbe\xd1\x36\x10", )?; test( HashAlgorithm::SHA224, b"\x54\x07\x1a\xca\x28\x96\x97\x49\xce\x2e\x2d\xc8\x55\x05\x20\x19\xbe\xc2\x7d\x0d\xd6\xa3\x10\x21\x93\x11\xb4\xb6\xd8\x22\x46\x7b\x22\xb3\xf0\x2f\xb8\x31\x39\x93\xfc\x77\xc4\xaf\x1d\x76\xab\x9d\xb9\x9b\x0b\x2b\x78\x20\x4a\xa4\x5f\x40\x32\xa7\xd9\x45\xf9\x3d\x55\xbc\xb8\xa6\xbb\xd4\x7f\x98\x29\x9a\x09\x29\x71\x04\x61\x41\x9e\xdb\xe1\x13\x2d\xc2\x25\x75\xf5\xaf\xbe\x70\x78\xcf\x5f\x05\xb2\x31\x00\x0f\x4a\x0f\x9f\x36\x7d\x90\x25\xed\x3a\xe1\x78\x6e\x01\x83\xea\xc9\x3e\xa9\x6b\x55\x30\x4a\x8c\x2d\xbf\x69\x08\x21", p, q, g, b"\xef\x78\x15\x2e\xfd\x88\x13\x0a\x4f\xec\xfe\x23\x50\x37\xde\x23\x09\xb1\xe2\xf3\x22\xd4\xf4\x15\x47\x56\xca\xa8\xf0\xb3\xe4\x1b\xe4\x5c\x80\xd8\x95\xde\x56\x38\x92\x57\xc3\x91\x30\x72\x86\xbe\x8e\x87\x09\xb8\x01\x86\xe2\x72\x41\x72\xb0\xf2\x97\x4b\xe5\x91\x58\x49\x16\xfc\x0e\x75\x0c\x0c\xaf\x83\xd8\x39\xb5\xc2\x48\xf5\xde\x65\x86\x68\x66\x5f\x00\x4b\xab\x8a\xd3\x10\x11\x88\x35\x95\x7c\x02\xda\x6a\xe9\xa2\xa7\x9d\xa0\x39\xad\xc8\x84\xf9\xeb\x8b\x62\xe3\x79\xe2\x7f\x54\x9e\x7f\x8a\xff\x8a\xd2\xfc\x27\x6e\xce\x15\xf0\x42\x35\x28\xa0\x9e\x31\xb2\x64\x21\xdf\x93\x57\x3b\xec\x7a\x4d\x6c\x2c\xbf\xbe\x5c\xe0\xfc\xe0\x70\x20\x88\xfb\x38\x4a\xd1\xdc\x35\xbb\x2c\x1c\x74\x2d\x43\xd7\x9a\xd1\x36\xe7\x10\x57\xcb\x9f\x22\xca\x04\x2e\x61\xd2\xc5\xcc\x4c\xcf\x5b\x75\xa7\x37\x99\x22\xbc\x4f\xd8\x83\x72\xd2\xa8\xf6\xa2\x75\x08\x65\xf9\x1c\x14\x34\x12\xa3\xfc\x61\xe4\xad\x4a\xbd\x03\xdc\x1c\xa0\xfc\x42\x97\xab\x10\x7a\x19\x63\x53\x3a\x3d\x80\xa2\x4a\xe2\xec\x41\x46\xe8\x26\x5a\xcf\xd4\x44\x6f\xc2\x81\x03\xc5\x04\x7c\x17\x79\x6c\x41\x48\xb8\xe6\x58\xe4\x4e\x9b\x1c\x25\x9d\x63\xc9\x7f\x0e\x76\x6f\xba\x8d\x9a\x73\x94\xcd\xb7\x34\x50\x8b\xfa\x09\xae\x42\xd2\xda\x30\x68\xe2\xc8\x5a\xf2\x06\x5f\x61\x8e\xc3\xf3\xc7\x3d\x73\xa7\x50\xc1\x36\x44\xc9\x6e\x3d\xbb\xb7\x47\x43\x25\xaf\x48\xd1\xd1\x45\xc2\x8d\x69\xf2\x2c\xbb\x4a\x90\x73\x05\x9a\x9c\x40\x89\x18\x04\xc7\x3a\x22\x9f\x01\xce\xf0\x67\x8c\xf4\x85\x5d\x18\xf9\x00\xf0\x25\x3a\xcd\x6b\x3e\xe5\x3d\xd9\x6c\x4c\x92\xaf\xff\x1f\x30\x87\xee\xb4\xfb\xa8\x6d\x2e\x94\x95\xc5\xf7\x34\xa4\x6c\xa2", b"\x0d\xeb\xcf\x6c\x88\x50\x4a\x88\x2a\x01\x91\xe6\xfa\x4c\x77\x4c\x10\x85\x83\x62\x62\x94\x28\xaf\xf2\x4c\x22\xe3\x36\x4b\xaa\x15", b"\x53\xd8\xc1\xdb\xb3\xa2\xc1\x02\x35\x21\xb7\x05\x00\x5c\xe6\x35\x0b\xcf\x66\xc0\x93\x58\x8c\x35\xd7\x68\xfc\xa2\x95\xa4\xa9\xce", )?; test( HashAlgorithm::SHA224, b"\x49\xd5\xf2\x0a\xcf\x1e\x9d\x59\xa6\x56\xbd\x16\x3f\xe4\x6f\xc8\x68\x47\x6c\xcd\x92\x63\x77\xa4\x0e\xd3\xd7\x47\x6e\x9e\xb7\xa8\xa7\x0c\x4b\x88\xb1\x6e\x79\x91\x48\xd2\x5f\xa2\x3b\xd0\xc9\x16\x11\xb7\x6c\x96\x65\xf5\x72\x2f\x40\x4f\xd9\x0e\xfd\xb8\xad\x14\xb7\x59\xc3\x49\xff\x6c\x83\x06\x42\xd5\x10\x76\xcc\xbd\xc5\x7f\x15\x2f\xba\x41\xc6\xa7\xf3\xcd\x39\x05\xfa\x7c\x85\x72\x65\xff\xc7\x59\x6a\x64\xdc\x69\x49\x0a\x93\x2b\x95\xad\xbc\x79\xa3\xb4\xf2\x1b\x2c\x6f\xb5\xd5\x83\x5d\x8b\xca\xe5\xd4\x4d\x91\x2a\x0a", p, q, g, b"\x8f\xf1\x30\x22\x08\x03\x16\xbe\xa4\x9b\x89\xa0\x6d\xd5\xa9\x71\xd8\x6e\x0c\x9a\x3a\xf4\x14\x25\x8a\x8f\x48\x50\x88\xb6\x6c\xc3\x8c\xde\xa0\x2c\xdd\x62\x09\x6c\x00\xeb\x0d\x1c\x2e\xe6\x62\xcf\xf1\x6f\x6d\x2d\x30\x44\x0b\x2a\xd9\xe8\x97\xb9\xeb\x93\x9b\x12\x99\xff\x87\x95\x57\xf1\x63\xf1\x7c\x8a\xc6\x0d\x0c\x6e\x99\x8b\x3a\x04\x4b\x43\xfb\xfa\xc7\xb0\xcc\x30\xa5\x79\xa6\xbd\xa1\xb4\xff\x59\x8a\x53\x1f\x9e\x37\xcc\x19\x01\xa7\xb0\x8e\x79\x4a\x74\x01\xd0\xf8\xca\x4b\xe5\x5b\xff\x7b\x17\x63\x21\x82\x85\x75\xa4\x77\x68\x6a\x98\xb4\xb1\x72\x66\xe1\x01\x60\x1f\x43\x6e\x55\x4b\x9e\x42\x88\x05\x79\x70\xfa\x34\x63\x34\x3e\x7e\x52\xa5\x8c\xa1\x45\xec\x9b\xef\xd7\xbe\x31\xea\x76\x6e\xd7\x4a\xc1\x78\xbc\xcd\xfe\xe9\xd2\x95\x65\xe7\x93\x5e\x8d\x70\xc3\xeb\x09\x1e\x3e\x3b\x3e\x6e\x77\x71\x69\x31\xed\x72\x9c\x49\xb9\x64\x43\x60\x60\x98\xbd\x08\x10\x98\x9e\x0e\x6f\x25\x3c\xf3\xec\x38\x29\x42\x31\xb7\x11\xb0\x9a\x94\x16\x09\xac\xc8\x97\x68\x19\x07\x65\x43\x92\x6e\xc4\xe0\x6f\x3e\x4d\x7f\x12\x3c\x2b\x87\x71\xe5\x45\x89\xe0\x45\x24\xe3\xb4\xf9\x50\xda\x56\x0a\x25\xd1\x21\x72\xd4\xeb\xda\xdc\x17\x19\x40\x0d\x91\xcf\x02\x64\x70\x87\x14\x47\x92\x00\xc5\x0e\xf0\x0e\xc0\xe6\x04\x90\x9a\x54\x6c\x95\xeb\x2f\xa5\x3c\x65\xee\x72\xad\x53\xf1\x49\xc9\x38\xdc\x21\x93\x49\x6d\xb0\x7a\xf3\xb3\x0a\x1f\x43\x97\x08\xaa\x11\x5c\x8d\xd4\x7c\x81\xc1\xbc\x68\xea\x3a\xbd\x90\x26\x11\x3c\x01\xeb\x05\x55\x8b\x8a\x2b\xe9\x09\x34\x76\xf0\x12\x47\xbf\xbe\xb3\xf2\x85\x8b\x13\xe6\x22\x8b\x98\x20\x5f\xa7\x10\xb6\xaf\x1c\x5f\x71\x48\x0d\xee\x40\x1d\x74\x72\xd7", b"\x19\xa7\x3b\x04\x9b\x16\x4d\xbf\x7f\xb2\x82\x6f\x42\x53\x61\x7c\xf1\xc5\xbb\x46\xff\xc5\x20\x4e\xfa\x00\x00\x2a\x79\xe2\x3c\x0b", b"\x7b\xe1\x37\xc1\x09\xe6\x8f\x33\x7b\x5a\x21\xcb\x59\x1a\x87\xaf\x1c\xb8\x68\x14\x19\xf8\x75\xff\x8f\x04\x1e\x82\x99\x91\xfe\x28", )?; test( HashAlgorithm::SHA224, b"\x11\x90\x85\x3e\xfb\x7e\x04\xcd\x49\x47\xc1\xea\x5b\x1b\x5d\x9e\x0a\xc5\xe6\xdf\x1d\xd0\x50\x87\x73\x08\xf1\xb2\xc7\xe0\xa4\x91\x7e\x58\x81\x03\xd2\x8c\x0f\x6e\x8b\x72\xd9\x67\xaa\x06\xac\xa6\x8a\x98\x6d\x80\x77\x40\xf2\xdd\xdd\xe7\x28\x1e\x55\x0a\xf4\xf6\x37\xea\xdf\x61\xf8\x80\xc7\x35\x1b\x48\x66\x15\x09\x6f\x6b\xa5\x0d\x87\x54\xbb\xf9\xba\x1c\x49\xa3\x48\x58\x15\xef\x06\xb3\xcd\x76\x1b\x55\x86\xc3\xfc\x2b\x46\x4c\x6f\xe1\x2c\x16\x0a\xb0\xf6\xf4\x46\xfa\xbf\x74\x21\x24\x30\xce\xc1\x5e\x75\xa5\x7b\x10\x2e", p, q, g, b"\x9b\xb8\x1c\x80\xd2\xb8\xa6\x01\xa0\x9e\x22\x47\x5d\x70\xd1\xdc\x55\x13\x40\x9f\xb4\x66\x8b\x17\x6c\x76\xb3\xaa\x1a\xf8\x63\x0a\xc7\x79\x0a\x44\x44\xab\x82\x37\x87\xf6\xf5\x69\xbd\xf0\x2b\x9e\xef\x5e\x7b\xb2\x1a\x88\xe3\xd3\x29\x68\x57\xe9\x19\x19\xf3\xc4\x73\xad\xd1\x6b\xcd\x76\x3f\x31\xa2\xf9\x84\x4d\x7c\xbd\x8d\x48\x06\x72\xa0\x36\xc4\xb1\x04\xbe\x66\xac\xd6\x6e\x6e\xf0\xe8\xa7\x44\xb3\xd8\x78\x09\x0d\x1d\xe9\xf1\x05\x56\x02\x47\xc6\x21\x53\xe1\x17\xef\xa5\x5e\xc6\x1c\x17\x7c\xd8\x2f\x8d\x72\xc5\x1d\x25\x3f\x4d\xc7\x33\x6f\x79\x82\x60\x25\x61\x9f\xb2\x10\x3f\x91\x14\x4f\x90\xf6\xa6\x89\xab\xcc\x51\xc6\x8a\xff\xd2\x84\x62\x57\x8b\x18\x3e\xec\x94\x20\x58\xf4\x8a\xbf\x54\x6f\x73\x89\x40\xa6\xc2\x6d\x30\x1c\x4b\x90\xca\x40\xea\x49\xc1\x17\xd6\x11\x47\xe8\x68\x39\x89\xba\xed\x7a\x22\x1c\x4f\x22\x09\x2f\x72\xb1\xed\x60\x4b\x6a\xa9\x4f\xf6\xa5\x74\xb4\x21\x5b\xd6\xf8\xe9\xd7\xb6\x38\xaf\xa4\x35\xa3\x34\x65\x89\xa6\x1b\x1d\x1d\xb2\x98\x9d\x7b\x45\xf3\x23\x45\x45\xe8\xa2\x2d\x60\x5a\xd6\xcb\x03\x6e\xf7\x91\xf6\x25\xd2\xc6\xa9\x95\xed\xa3\xe0\xca\xfc\xe7\x04\xa2\xbf\x15\xab\x5d\xfa\xd0\x16\x21\x04\x59\x2d\x23\xf5\x2a\xa0\xfe\xa1\xf4\x32\xf0\xa3\x08\xd1\x6a\x45\xe1\xf4\x1f\x82\x32\x62\x07\x4e\x91\x73\x75\x4c\xeb\xa7\x0c\xd8\xa3\x70\xdb\xab\x1a\x14\xf8\x41\x59\x11\x6d\xa7\x3d\x3a\x9c\xf8\x25\x94\xcb\x3a\xf9\x57\x97\xcf\x44\x42\x72\x85\x05\x89\xac\xc6\xbc\xa4\x71\xd0\x76\x33\x5d\x67\xc4\x61\xdb\x60\x23\x95\xbf\xb1\x7c\x39\xbf\xa2\x4d\xf1\x40\xc0\xac\x43\x88\xdb\x05\x34\xa5\x0d\xfd\x26\x13\x74\xf8\x1b\x31\x0f\x75\x1d\x16", b"\x58\x7d\x7f\x44\x54\xd5\x94\x18\xa7\x52\x75\x70\xf2\x8f\x1b\x07\x45\x1f\x3b\xaf\x28\xf5\xca\xbe\x03\x10\xc4\xd7\x9e\x42\x53\xa5", b"\x18\x83\x94\x04\xaa\xad\x59\xff\x24\xd6\xac\xce\xc3\xb7\xcc\x6a\xc7\x00\x3d\xd4\xad\xf9\x6b\x77\xba\xb0\x68\xae\x72\xf2\x5f\x61", )?; test( HashAlgorithm::SHA224, b"\xb1\xcb\x43\x0c\x5a\x1d\x72\x78\x8c\x79\x5a\xb5\x67\xa8\x4c\x7f\x59\x77\x96\x59\x33\xa5\xbf\x23\x80\x58\xf2\xfc\x81\x88\x80\xd2\x5b\x4d\xde\xf9\x63\x54\x81\xfd\x9f\xdd\x45\x98\xae\xce\xc3\x76\x4f\xa7\x30\x93\xa2\x25\xd4\xe4\xeb\xcf\x01\xe4\xb7\x5b\xdc\x18\x41\xdc\x01\x65\x2c\x4d\x99\x16\xaf\xa2\x4b\x89\xc2\xd6\x85\x4b\x72\xea\xa7\xb1\xf3\x08\x9d\x1a\x91\x92\x10\x83\x1a\xc8\x0f\x99\x83\x57\x90\xce\x64\xab\xc3\x42\x70\xcd\x45\x51\xd3\x1b\x8f\x53\x48\xce\x8a\x70\xdf\x60\xb8\x8e\x08\x5a\x98\x4a\xca\xc6\x65\xa7", p, q, g, b"\xa8\x1a\x54\xbe\x06\x85\xf3\x35\x05\xae\xd9\x59\x1f\x33\x3a\x74\xa8\x42\x99\x5d\xa5\x13\x5f\xa4\x8f\x50\x53\xfa\xc2\x9f\xff\x08\xaf\xd9\xb9\x01\xc3\xdf\x13\x47\x20\x4a\x3f\x13\x3a\x7d\xff\x6b\x1a\xdb\xab\x07\x75\x26\xb6\x38\xa6\x38\x37\xd7\x84\x43\x39\xd4\x8f\xe1\x07\xaf\x08\xed\x62\xe8\x7d\xe5\x47\xce\xd8\x4d\xf9\xa2\xcc\xc4\x58\x76\xb2\x9b\xc5\x36\x1c\xe8\xa9\xa2\x1b\x81\xd4\xf8\x5d\x3b\x67\x1c\x9b\x44\xb5\x48\x3f\x26\x10\xef\xa0\x17\x51\xd3\xa0\x7f\xd6\x94\xe4\x66\x53\xac\x47\xac\x64\xa9\x10\xb7\xfc\x42\x1f\x07\xe5\xde\x54\xe8\x98\x78\x99\x89\x09\x1e\x9e\xd5\x8b\x7c\x04\xe9\xe1\xdc\xed\x60\x47\x5d\xc6\x93\xa0\xeb\x40\x15\xed\x65\x81\x10\xb8\x2f\x8e\x72\x0d\xc7\xaf\xff\x69\xce\xa7\xb8\xe5\x6b\x8a\x97\x55\xbf\x1e\x29\x33\xd0\x83\x60\x83\x77\x50\x4c\xab\x52\xd3\x8c\xce\x1b\xa8\x2f\x84\xc2\x62\x65\xe6\x93\xf1\x8c\xf5\x2e\x93\x0d\xc0\xd8\xbc\x9d\x41\xf4\xd2\x8b\x32\xb7\x40\x5c\xb1\xfc\xe8\x8a\x55\xbe\x40\xdc\xa1\xb1\xa3\x51\xaa\x7d\x77\xfa\x6e\xf8\x4c\x77\x6f\xa3\x01\xdb\xa2\xe2\x36\x93\x3d\x89\xc8\xb9\x44\xf5\x34\x03\x41\x4d\xf0\xd4\x34\xdb\x72\xca\xa7\x49\xfb\xcd\x56\x6d\x76\xf4\xf6\xf0\xbc\x40\xe4\x2a\x29\xae\xbe\x62\x10\xe8\x9f\xa0\xca\x8b\x6a\xc0\x8a\x4c\xac\x65\xc5\x90\x50\x35\x33\xc3\xe4\xf1\xb3\xc5\xbd\xe8\x68\xe7\x9d\x9d\xa9\x18\xb7\x2d\x1b\x09\x8a\x72\x78\x76\x95\x46\xb7\x84\x50\xe0\x0e\x46\xdd\x40\x0e\xfe\x97\xc8\x84\xdb\x96\x12\xba\xaa\xee\xe2\x48\x6f\x64\xcd\x83\x02\xa4\xc3\x2d\x8f\xdb\x87\x3f\xe0\xaf\xff\xd7\xbb\x74\x81\x12\x20\xb0\x13\x39\xdf\xc5\xe5\x67\xc7\x66\xaf\x28\x05\xec\x1c\x30\x12\x63\x99", b"\x60\xd2\x76\x3f\x01\x38\x07\x6e\x9e\x0e\x20\xf8\x3e\x4a\xa2\xe9\xaa\x35\x2c\x19\xca\x79\xe3\x72\x63\x03\xfe\x89\xb1\x2e\x27\xf2", b"\x07\xe0\x8d\x91\x6c\x8a\x10\xba\x26\x9d\xc4\x60\xee\x9d\x83\xf8\x6a\x7b\x3d\x98\x62\x1b\xb7\x32\x4a\x6a\x7e\x60\x72\x38\xba\xa3", )?; test( HashAlgorithm::SHA224, b"\x3b\xb9\x43\x0e\xea\x69\x79\x12\x9b\xe7\x45\xd5\xae\x6b\xab\xd4\x96\x6e\x3a\xbf\x7d\x9e\xe5\x85\x6f\x2c\xaa\xe6\x01\x4c\xb3\x40\xee\xbd\x28\xbd\x9f\x39\x1e\xb4\x6b\x3a\x2b\x8a\x4c\xdc\x22\x4e\x55\x08\x53\x2c\xa0\x8c\xb1\x04\xaf\xf6\x77\x13\x3c\xf4\x39\x3a\x20\xfe\x44\x99\x96\x7d\xfa\x64\x51\x54\x55\x93\x0c\x65\x9d\x43\xbb\xee\x23\x40\xb1\x4a\x3b\x33\x42\xd4\xb9\xa4\x66\xb8\x89\xe8\x50\xdf\xf4\xb2\xa5\x1d\x38\x9c\xa3\x2f\xb6\xa5\xf4\x33\xed\x93\x03\x2b\xe4\xe5\x63\x69\x57\x97\xb8\xc1\xe1\xe0\x19\x18\x41\x72", p, q, g, b"\x75\xb7\x65\xec\xa4\xeb\xde\x0b\x65\x64\xc3\x13\x7f\x16\xcd\xae\x00\xee\xad\xd2\xd0\xb2\xcb\x83\xcd\x15\x00\xcd\x05\xed\x0d\xd1\x67\x30\xc9\x50\x1c\x8a\x35\x3a\x64\x63\x4d\x06\x5f\x61\x37\xff\xcf\x95\x63\xd9\x61\x27\x90\x6f\xb1\x7d\x5a\x79\xad\x29\x10\x24\xa4\xa6\xfb\x7e\x7d\x08\x02\x19\xa6\x23\x1c\xa1\x58\xb6\x5f\x52\x02\x91\x2d\xdc\xb8\xdd\x1f\x01\x8c\x9b\x0e\x76\xb3\xa4\x76\x33\x6c\x50\x41\xbc\x50\x2f\x8a\xcb\x74\x8f\x13\x6c\x3d\x78\xcb\x2c\x42\x9c\x8f\x1a\xc1\x7b\x63\xdd\x7e\x9e\x57\xb6\x07\xf9\xde\xbe\x57\x14\x59\xdf\x36\x88\xcf\x4c\x11\xfa\x1e\x84\x53\x3a\xec\xda\x2d\xfe\xce\x05\xf4\xbd\xb2\x68\xcc\x7b\x0c\x8f\xe7\xaf\x5a\x63\x3a\x83\x51\x5a\xda\x95\xf3\x18\x24\xd6\xa3\xc7\x12\x2f\xdc\xd1\x2f\x54\x99\x2c\xbe\x64\xd1\xd6\xbd\xbd\x0a\xb5\xae\x4d\x19\xaa\x52\x60\x97\x50\xa1\xde\x18\x6a\xfa\xb5\xa1\x63\x98\xda\x47\x3d\x12\x88\x82\xb0\x65\xe8\x73\x80\x9f\xae\x0b\xbd\xc0\x1a\x9c\x73\xb5\xc6\xee\x65\x85\x7f\xa7\x94\xa1\x50\x58\xdd\xfb\x24\xa9\xa1\x7a\x04\x08\x64\x6f\x20\x09\xdd\xa6\x10\xc8\x29\x1a\xe1\x48\xa1\x8c\x17\x3f\x83\x6b\x19\x7c\x78\xed\xe5\x65\x48\x95\xb4\x5a\x34\x19\xe9\xc3\x17\x7f\x25\x03\xa9\x3c\xe5\x26\xbe\x14\xad\x91\x99\x39\xeb\xe3\xf2\xd0\x7f\x00\x6a\x0b\x02\x2d\x6a\x62\x3c\x60\x17\xf0\xc7\x66\x19\xf0\x78\x05\x31\xd5\x39\x0d\x42\x39\xb2\xf9\x00\xef\xb4\x4c\x95\x30\xc7\xd9\xb3\xe8\x4a\x70\xc9\x04\xb1\x79\xad\x0c\x4f\x90\x92\x50\xf7\xcc\xf8\x3c\x5f\x42\xd6\x43\x7c\xbc\x9f\x03\xfb\xae\x81\x31\xa1\x2d\x33\xe0\x17\x21\xe6\x50\xae\xe9\x1e\x1c\x89\x3f\x5e\x7e\x03\x9e\x0d\x58\x5c\xd7\xcd\x74\x95\xc4\x0d", b"\x74\x16\x72\x9a\x1f\x60\x20\x8b\x7f\x83\x74\x80\xfb\xa8\x18\x40\xe4\x5b\x33\x8a\xb9\x84\x6e\x9b\xbb\x91\x68\x22\x9f\x64\xbc\xea", b"\x58\xeb\x90\x40\x76\xa3\xac\x69\x07\xd7\x50\xff\x6c\xdf\xaa\x46\x54\x35\xe9\x98\x2e\xcb\xdf\x72\x19\x7b\x09\xbb\x6d\xf1\x37\x3a", )?; test( HashAlgorithm::SHA224, b"\x55\xa6\x9f\xc1\x6f\x6b\x75\x3d\x0b\xf6\x5e\x84\x4d\x06\x78\x59\xf5\x1d\xd3\x29\x27\x99\x80\x19\x60\x63\xfb\x59\xf8\x9b\xd7\x78\xa9\x24\x4f\x93\x2c\x2a\xdb\x68\x11\x18\x36\x12\x10\x5d\x1c\x52\x7e\x83\x02\xdf\xee\x50\x42\xcf\xce\x5d\xbe\xab\x16\x5a\x39\x6f\x5a\x4c\x21\x33\x9b\xe1\x02\x1b\x7e\xce\xc6\x6f\x21\x77\xf9\x42\x43\xef\x62\x61\x60\x8c\x56\x91\x96\x79\xd4\x48\x63\xcf\x9d\x2a\xfc\x60\x10\xfc\x2b\xf8\x21\xb9\x31\xca\x39\x70\xd6\x9b\x1e\x62\x2a\x90\x83\x89\xdb\x50\x49\xd7\x18\xe3\x57\x07\x10\x63\xae\xf8", p, q, g, b"\x61\x46\xa5\x1d\xeb\x79\x95\x7a\x83\xb2\xc7\xa3\x20\x4b\x5c\x34\xae\x4f\x8e\x0d\xb6\x0f\x0c\x07\xe7\x08\x03\xf2\x2b\xf9\x9a\x39\x64\x72\x63\xdb\x9e\x28\x5d\x72\xf6\x27\x0e\xe1\x0f\x18\x58\x4c\x39\x08\x1d\x25\x44\xd4\x05\x02\xc5\x0d\xf1\xe3\x5a\x45\x76\x00\xb5\x56\x9d\x61\xe8\x12\x6c\x05\x5f\x7b\x96\x45\x72\xe9\xf3\x28\x2e\x4d\x97\x45\x00\x69\x55\xc2\x42\x61\xc6\x8d\x7c\x0c\xb3\xf0\x8b\x0b\x0d\x8e\xaa\x97\x1e\x1a\x63\x1c\x68\xa3\xa9\x14\xd3\x5e\xfe\x89\xf7\x6b\x9c\x21\x16\xaf\xb7\xbd\x19\x89\xe2\x02\xe0\x92\xb5\xb5\x70\xea\xef\xcc\x93\x35\x42\xe6\x50\xd9\x2c\x03\x3b\x59\x73\x82\x1d\x6d\x77\xcf\xc2\x43\xf7\x44\xda\x80\xb5\x6e\xae\xa7\x65\x0b\xf5\x08\x02\x51\x62\x28\xad\x6d\x5b\x0d\x4e\x88\x9c\x57\x5e\x36\x78\xff\xdb\x1c\x28\x9e\x59\xd9\xff\x7f\x84\xa3\xd6\x3d\x39\xd6\x88\x8d\xbe\x21\x3e\x2c\x3b\x31\x14\x08\x5e\x00\x6a\xd7\x45\x05\x73\x9f\xce\x82\x6f\x96\x32\x84\xdc\x4e\x2b\x01\xec\x2f\x92\x33\xd3\x47\x0e\x82\xd8\x72\xed\x94\x4e\x62\x96\x1f\x64\x13\x4e\x80\x80\xda\xf2\xdf\x49\x4a\x76\x24\x0a\xc0\xcd\x22\xf9\xaf\xae\x7e\x80\xd3\xcf\x3e\xfb\xe0\x55\x14\x7f\x62\xff\x8c\x61\x92\xe3\x88\xb4\x9e\x47\xd9\xfe\xaf\x19\xec\xcd\x65\xdc\xa9\x99\x16\x38\xeb\xd7\xb0\x48\x07\x77\x07\xad\xab\x1c\xb2\xa4\x35\x8e\xef\xc4\xaa\xb8\x25\x1f\xb0\xf9\xd5\xf0\xb0\x9f\x29\x9c\x72\x0d\x3a\x8c\x00\xa5\xa4\xd8\x4f\xee\xc0\x40\x05\x70\x40\xb7\x09\xcc\x0e\xd1\x85\xa8\x32\x53\x7b\xc4\xb2\xdf\x0e\xc1\xf7\x71\x69\xac\x96\xe9\x12\x82\xde\x21\xf3\x42\xd5\x42\x9e\xc3\xd6\x6a\xd9\xd3\x36\xc4\x40\x94\x9a\x12\x11\x21\x7b\xf5\x4a\xad\x93\xbb\x4b\x0a\x43", b"\x13\x6f\x93\xdc\xc7\xd3\x3e\x55\x9b\x8d\xb0\xaf\x13\xe0\x0c\x71\x90\x92\x8b\xff\x50\x86\xee\xdf\xd1\x17\x06\xe6\xf2\x34\x9a\xd0", b"\x32\xb9\x5b\x9b\x14\x7c\x7d\x1a\xc2\xa2\xf0\x05\x7f\xc0\x53\x8a\x4b\x7c\x9c\xd4\x65\x2e\x67\x83\xe5\xd7\xe3\x53\x46\x55\x63\x1a", )?; test( HashAlgorithm::SHA224, b"\x15\x67\x89\x0c\x69\xe5\x78\xa2\x7d\x62\x08\x91\x3d\xfb\xc2\x0e\xdd\xc6\x1f\x5f\xee\xd4\x57\x40\x06\x93\xdd\x17\x0f\x80\x67\xbf\x29\x0b\x11\x15\x07\x80\x68\x4c\x20\xd5\xcf\xd2\xbf\x1d\x53\x6d\xd3\xb7\x00\x25\x88\x3f\xb4\x17\x03\x43\x6f\xd0\x9c\x0a\x14\x11\x25\x78\x4f\x90\x91\x15\x13\x03\xef\x80\xcd\x34\x5e\x5a\x7d\x28\x54\x33\x5c\x29\x84\x53\x8c\x5c\xd7\x39\xb0\x07\x24\x8c\xd9\x9f\x1d\xbc\xd3\x14\x8c\xb0\xff\x0d\xb6\x33\xf8\xca\xfc\x7a\x0b\x99\xc6\x1e\x78\x4d\x03\x03\xa5\x12\x03\x07\xd3\xfb\x3c\x4c\x21\x9e", p, q, g, b"\x5c\x53\xd1\x3a\x1b\xee\x17\xa2\x87\x20\xb7\x08\x96\x46\xd0\x7a\x3f\xd5\x8b\x9b\x2b\x23\xec\x94\xaf\x31\x44\x83\x07\x46\x17\x7b\x0d\x20\x73\x70\x7b\x6b\x84\x90\x1f\xfa\xa7\xa4\x16\x5c\xef\xf2\x42\x56\x40\xfc\xfe\x5d\x17\x65\x0a\x44\xa1\x68\xeb\xd7\x69\xc8\x33\x44\x5f\x1b\x2d\x26\x43\x4c\x22\x8c\x1e\x2e\xdf\x17\x04\xd7\x11\xa8\x62\x57\xbe\x25\x23\x5a\x7c\xea\x1e\x5c\xba\xc4\x12\x23\x5b\x75\x96\xd1\xdf\xa0\x39\x80\x81\xa4\xf1\x81\x51\xcb\xb5\x1d\xc6\x2c\x22\x6a\x2a\xbc\xaf\x33\x35\xe8\x6a\xb5\x46\x08\x04\x0e\xe8\x14\xe4\x43\xb6\x43\x98\x21\x3b\xa6\x0d\x7b\x5a\x3c\x8e\xa7\x8e\xc6\xb9\x89\x34\xc8\x9a\xca\x05\xb9\x7d\xf5\xf6\x5b\xc5\x74\xa3\x0a\xcd\xdd\x09\xf7\x3c\xec\x14\x52\x8b\xe4\x9a\x2f\xbe\xca\x70\x29\x1b\x1b\x29\xf7\x04\x2c\x59\x49\x94\xda\x12\x8f\xda\x22\xb3\xed\x3a\x93\x5a\x1a\x00\x57\x5f\xf1\xff\xd1\x93\xc4\xca\xc5\x3a\x2a\x2d\x4b\x0c\x51\x02\x28\xa7\x6a\x74\x33\x36\x07\xd1\x5b\x56\x86\x14\x42\x71\x44\xb4\x17\x4d\xa3\x58\xe3\x83\xf6\x58\xc6\x0b\x45\x71\x00\x36\xf5\x4f\x93\xf1\x7b\xc8\x08\xb3\x02\x67\x4e\x83\x8c\x1d\xfd\x7f\x81\x6f\x7e\xa4\x4b\x0d\x97\x38\x6e\x4e\x16\x34\xc9\x53\x95\x68\xdd\x6a\xe1\xc2\x8f\x25\xb2\x7a\xa9\x44\x99\xae\x38\x9a\x09\x26\xc8\xfa\x62\x95\x6c\x6e\x24\xdc\xed\x0a\xfb\x04\x91\xdd\x9f\xac\x05\x16\xd2\x7f\xd4\xd2\xdd\x01\x50\xee\x6b\x4c\xff\x7b\xfd\x57\x50\x43\xd7\x01\xda\xad\x0f\x1b\x94\x2a\x0e\x4c\x61\x95\x6b\x32\xa6\x8c\x90\x78\xf6\x07\x7f\xa9\x94\x51\x98\xd4\x47\xa5\xbf\x3c\x47\xb7\x28\x84\x27\xed\xc6\xf9\x96\x55\xae\xad\xf8\xde\x18\x51\x57\x14\xc6\xb9\xc0\xd4\xce\x5a\xb0\x92\xc2", b"\x49\x47\xd3\x6e\x74\x26\xf1\x44\x1b\xe5\xa7\x5d\xc9\xcd\x84\x54\x50\xc6\x11\x04\xf1\x9e\xd4\x0c\xe3\x3e\x25\x2f\xa2\xc2\x62\x68", b"\x35\x68\x79\xde\xb1\xda\xef\x01\xda\x04\x75\x0d\x58\xe5\x98\xdb\x47\xaa\xaf\xf5\x0b\x1c\xf4\x2d\x87\x33\x4a\x61\x57\x80\xff\x8c", )?; test( HashAlgorithm::SHA224, b"\x4f\x7d\x89\x4d\xfb\x7d\x82\x04\x0a\x9f\xed\x6c\x26\xa7\xd2\x7a\x9a\x15\x11\x38\x8c\x11\x3c\x64\x71\x5a\x06\xdc\x46\xfc\xf4\xf9\x04\x07\x0a\x6e\xd9\x5b\xdd\x8d\xc1\x73\x0a\x27\x64\x5d\x37\xeb\x3b\x02\x84\x7c\xb1\xc6\x31\xec\x0c\x67\xb2\xee\x07\xb8\x80\x5b\x34\xdd\x9b\x84\xe6\xab\x3f\x9a\xfb\x92\x46\x99\x4e\xa5\x79\x56\x7a\x8f\x4a\xf7\xfe\xb8\x68\x98\xcc\x9c\xb5\x34\xc3\x87\x99\x3c\x6e\xc1\x65\x84\xac\x85\xbe\xd3\x6b\xbc\x2c\x30\x57\x70\xf2\x11\x63\x68\x61\x67\xdd\x53\xfe\x56\x23\x62\xff\x54\x9d\x90\x35\x39", p, q, g, b"\x00\x96\x74\x78\x35\x8d\x7c\x16\x96\xce\xb9\x2b\xe8\x50\xf5\x53\x8a\xd8\x54\x3e\x15\x1a\xad\xd8\x4c\xab\xa1\xb7\x2f\x36\x36\xa2\x09\x2a\x86\xb6\x46\x28\x73\x90\x3d\x5b\xf1\x7f\x61\x2b\x45\xb5\x13\x3e\xac\x16\x30\xbf\x07\xc0\x37\x14\x23\xd2\xe5\xd7\x14\x7c\xea\xcc\x9b\xaa\x8c\xb3\xb0\x4c\xbc\x3c\xbd\xa4\x29\xab\x40\xd7\xe5\x92\x73\x0d\xc4\x77\xb0\xa9\x5f\x1f\xb5\xed\x5d\x91\xe1\x4b\x9d\x5a\x1a\xc8\xd4\x03\xa5\x5a\x65\x8d\x1c\x38\x3b\xb5\x98\x05\x3b\xe2\x38\xcd\x82\x38\x69\x68\xae\xdb\x81\x15\x86\xfa\x2a\x14\x11\x93\x24\x89\x6f\x21\x11\xb9\xbc\x7c\xff\x66\x6d\x37\xaf\xfe\x76\x04\x1d\x98\xf3\x62\xda\xa0\x9f\xf6\x5e\x82\xe8\x65\xeb\x29\xc5\xd4\x71\x0c\xa7\x80\x08\x86\x88\x7d\x38\x3d\xa0\xcb\x59\x9b\x22\x5f\xdd\x21\x0a\x3d\x70\x92\x9d\x35\xfb\x9c\xa8\x07\xe5\x6c\x91\xc0\x85\x12\x52\xb9\x5c\x07\xb6\xb1\x20\xb3\xb6\x50\x41\x8e\x0f\x54\xf4\x57\x36\xf8\x20\x18\xd0\x92\x94\x46\x2d\xde\x6e\xea\xfc\xb1\x5a\x2a\x72\x85\x77\xfa\xf3\xef\x3e\xb1\x3d\xb0\x44\x96\x5e\xa3\x89\x2f\x7e\xb0\x88\x4e\x47\x76\x60\x89\xd2\xa4\x3a\xbc\x62\xa3\xc3\x75\x83\x1c\x20\x84\x8d\xfd\xe8\xf8\x3c\x24\x9a\x8e\x27\xf2\x89\x7c\xaf\xcf\x5a\x06\xb7\xc3\x59\x1e\x09\xb4\x2f\x82\x84\x9d\x49\x86\x64\xf4\x85\xde\x26\xc7\x88\xe5\x59\xad\x5b\x15\xf9\x99\xdb\x92\x7f\x81\xf5\x4b\x96\xe9\x97\xb9\x09\x6b\x2a\x7e\x3e\x75\x6f\x5a\x9a\xab\x54\xc1\x60\xcf\xc2\xe6\x44\x92\x17\x94\x87\xc9\x8d\x0a\xa3\x83\x08\xd6\x74\x28\xf3\xa1\x13\x22\x8b\xc6\xdc\xdf\x7a\xb9\x3c\xbb\x1d\xa2\x25\xc7\x2c\x63\x6f\x49\xd2\x74\x42\xcf\x3c\xf2\xf9\xc4\x9b\x90\xac\x8b\xaf\xe7\x40\xdb\xbf\xd5", b"\x09\x40\x72\x48\x55\xa0\x67\x1d\x60\x14\x7d\xc6\x1f\xd2\x83\x19\x01\x34\xa6\x8c\x17\x81\x14\xd5\x9a\xb5\x8d\xa7\x3a\x1c\x81\x82", b"\x43\xf1\x94\xb9\x70\x78\xdc\x9b\x84\xc8\xe8\xe8\x67\xa7\x4b\xaf\xdc\x22\x11\x70\x6a\xe1\x10\xb5\xae\xc0\xb9\x9e\xde\x1f\xfe\xd8", )?; test( HashAlgorithm::SHA224, b"\x9b\x62\xa7\x4b\xc4\x9e\xf4\xff\x5c\x62\x16\x5e\x7d\x25\x52\x1f\x13\x5c\x83\x6b\xc4\xef\x02\x3f\xb4\xbb\x1d\x6b\x42\xc6\x29\x10\x71\xea\xe0\xb4\x65\xc5\x92\x31\xcb\x29\x7c\xac\x6d\x14\x58\x75\xfd\x84\xf5\x72\x9f\x79\xf9\x22\x18\x52\x2b\x9e\x55\xcb\x70\xd4\x71\x03\x0d\x36\x29\x1a\x24\x92\x5a\xb7\x31\xa2\xd4\x45\x8c\xff\x67\x70\x79\xd2\x07\xce\x86\x5b\x3d\x55\x26\x00\x92\x38\x86\x1d\x64\x50\x6a\x92\xb7\x6b\xaf\xf5\x9b\x37\xb8\x63\x08\x75\x58\xd5\x96\x5d\x76\x68\x5f\x0f\xbd\x1f\xab\x1b\x1f\x95\x61\xf8\xf6\x9c", p, q, g, b"\x75\xd7\xd9\xa5\xdb\xdd\x47\xce\xcd\x12\xf6\x9a\xb2\x12\xdf\xe0\x8a\x96\x56\xe2\xbc\xa9\x2c\x81\xdb\x2d\x26\x8a\x29\x3a\x32\x5e\x51\x1c\xd5\xaa\x1b\xa5\x9d\xee\xf2\xab\x63\x11\x66\x5d\xda\x58\x23\x0d\x48\xf1\x41\x63\x71\xde\x1a\x83\x64\xb3\x8f\x5a\xd5\x99\xc4\x72\xd3\x63\xa1\x8a\x2c\x13\xd5\x72\xcf\x84\x9b\xe2\xfe\xf9\xa1\x66\xe8\x38\xaa\x58\xb7\x21\xec\xfc\x4b\x36\x1f\xda\xb1\xd0\x87\x6b\x78\xe2\xe8\xf2\x3e\xf1\xc8\x2c\xc0\xe1\x70\x0f\xa0\x15\xa4\x00\x7b\x1d\x7b\x53\x5c\x82\xd2\x3c\x12\x9d\x1d\x1c\x9c\x4a\xfe\x87\x5a\x06\xc0\x5f\x71\xf0\x78\xcb\x8d\x90\x60\xf4\xd9\x36\x67\x1f\xae\xe2\x17\xd4\x04\x55\x25\xd5\x70\xb0\xc8\xca\x0c\x4e\x8b\x55\xdf\xe9\xb7\x80\xba\x69\xc9\xd8\xcd\xa1\x0c\x50\xfd\x61\xc4\xe7\x21\x4b\x94\x3c\x1c\x29\x79\x7b\x09\x9f\x57\xa4\xc6\x48\x59\x7c\xed\xd9\xd9\x09\xbc\x58\x4a\x9b\x75\x4b\x20\x95\x15\xdb\xfa\x0f\xec\xce\x2a\xd0\x5c\x84\x8e\x99\xdc\xa2\x1a\x6d\x0d\x5f\x2d\xac\x23\x61\xe4\xc0\xea\xf9\x6d\xf1\x99\xad\x28\x88\xd6\x71\x97\x4e\xf0\x5d\x65\xc9\x27\x88\x43\x4a\xb4\x2f\x1f\x1f\x79\xed\xc4\x9e\xc1\xfa\x92\x13\x95\xbd\x0f\xeb\x6a\x9e\x6a\x06\x22\xe8\x25\x5b\x0e\xf6\x93\x7b\x89\xd0\xcc\xcd\x58\x52\x87\x2d\x2b\x0a\xb5\xd7\x9c\x2f\x19\x8b\xff\x6b\x8a\xa3\x8a\xce\xe2\x1d\x6c\x3a\xdd\x55\x62\xd8\x4d\x96\x87\x58\xd9\x3e\x8c\x1d\x61\x1f\x7d\x61\x82\xb6\x2e\x44\xf5\x7d\xf3\x42\x89\x9b\xb5\x64\xa7\x94\xd1\x39\x15\x88\x21\x43\xd9\xdf\x45\xf8\xf2\x1c\xc0\x30\xaf\x33\x97\xe9\xe9\x49\x68\x3d\xdd\x8d\x8d\xa9\x90\x9c\xc1\x13\x96\x19\xe4\xb7\xb2\x52\xaa\xdd\x02\xc6\x6a\x5e\x20\x10\x5a\xdf\x26\xf2\xf0\x21", b"\x01\xe3\xde\x39\x8b\x01\x8a\x69\x47\x80\xdd\xc6\xca\x12\xb7\x8d\xc5\x5e\x7a\xd9\xfd\xdd\xb5\xa3\xf5\xb2\xca\xd0\x10\x32\x53\xdd", b"\x03\xc9\x82\x80\xab\xe3\x05\x0a\x67\xf8\x8e\xf2\x9f\xb2\x14\xa8\x01\x24\xf4\x73\x21\xc6\x2e\x41\xe3\x90\x5b\x85\x32\xf4\x93\x6c", )?; test( HashAlgorithm::SHA224, b"\x6c\x66\x05\x1e\x04\xc2\xe6\xaa\xa4\x3d\xe9\xaa\x42\xcd\x9f\x61\xe8\x32\x9c\x12\x4e\xd3\x03\x1b\x67\x45\x2d\xb4\xc4\x35\xdb\x29\x1d\x75\x6b\xa6\xef\x90\xab\x06\x30\x7c\xb8\xd7\x0f\x34\x96\x79\x2e\x63\x3b\xf5\xac\x98\x5c\x37\xc4\x3b\xdb\x4e\x45\x5c\x7f\x76\x1a\x5e\xe4\x50\x56\x7f\x85\xcc\x97\x7e\x96\x8e\x7f\xa4\x2a\x42\x8c\x1a\x7e\x91\x5c\x82\x87\x48\x65\x63\x1d\x80\x78\x89\x93\x77\x25\x59\x47\xc3\x44\x61\x82\x97\xb8\x3c\x96\x11\x4d\x11\xd7\x4d\x8c\xd5\x79\xb5\x53\x66\x7c\xac\x1d\x97\xae\xa4\xd1\x68\x49\x87", p, q, g, b"\xed\x2e\x10\xa4\x43\x16\xd6\x77\x46\x7d\x79\x94\x7b\xec\x9e\x40\x5d\x30\xf3\x2d\x86\x0a\x1c\xe4\x6b\x36\x68\x45\xdf\x9a\xd2\x22\xb0\xf9\x92\xf5\x84\x45\x71\xb1\x96\xa3\x10\xd5\x87\xff\xfa\x74\xbd\x51\x02\x15\xf3\xbd\xaf\xa1\xc9\x3d\x1b\x13\x15\x24\x6f\xd2\xf7\x94\xc4\xda\x07\xbd\x72\x2a\x98\xdd\xa9\xa0\x2a\xd4\x25\x5b\x6d\x52\x67\x73\x82\x56\xcb\x86\x39\xa1\x45\xc2\x84\x04\x56\x2a\xdd\x2b\xc7\x69\x1d\xac\x12\x60\x0b\xa9\xf8\xeb\xe0\x06\x14\xee\x3f\xc6\xe6\xb2\x48\x4d\x9c\x5c\x70\x90\xb3\xf3\xb1\x34\xba\x19\x90\x98\x64\x56\x30\x40\xfe\x87\x52\xd6\xc6\xab\x95\x11\x1f\xe1\x01\x4b\xf7\xbb\xe4\xe6\x74\xc9\xd0\x3b\xb8\xd2\x29\xe4\xb5\xf6\xa6\xe4\x71\xc6\x78\xb0\x26\x5e\x88\xcc\xad\x79\x60\xff\xfa\xe7\x00\xf3\xa7\x5e\x61\xa2\x4e\xa8\x82\xb9\x70\x53\x5e\xb7\x01\x7e\x16\xc4\x8c\xe9\xe2\xbc\x83\x57\xf7\xf0\x88\x9c\x87\x1d\x0b\x4c\xe2\x9d\x27\x9a\xfd\x1d\x11\x49\x98\xd1\xeb\x6f\xe4\xa5\x66\x1e\x42\x9b\x13\x27\xf0\xa3\x9e\x9e\xf0\x0a\x41\xa7\x4f\xe4\x79\xb9\x0f\xdd\xa2\x1d\x93\x15\x55\x5a\xfe\x22\x72\x74\xc1\x1a\x71\xc0\xd1\x0c\x9e\x5d\xfc\x89\x75\x0e\xda\x53\xc6\xa8\xb5\x2a\x52\x72\xc7\x55\x26\x37\x5e\x5f\xb9\x1f\xf7\x5d\x02\x8d\xf7\xaa\x2b\xce\xb5\xfd\xf6\xf8\xe3\xbc\x1e\xc3\xf1\xe2\x26\xd0\x4d\xf1\xd8\x42\xe4\xc8\xf4\x58\x98\x8c\xb7\x41\x5f\x0d\x2c\xa4\x49\x8b\x0c\xd6\x7e\x8b\x08\x5b\x00\x8f\xc4\xca\x06\x43\x93\xa0\xdf\x51\x7f\x0b\x48\x33\xea\x40\x51\xac\x3f\x1d\xe5\x68\x6d\xcc\xb7\xbb\xa8\xbd\x93\x90\x92\xd6\xd7\x8f\xa0\x8f\x5b\xf9\xbf\x6f\x13\xd7\xae\xf7\x2f\x04\x7f\xcc\x47\xa8\x82\x23\xdf\x6e\x1a\x62\xd2\x18\x16\x9f", b"\x31\xf2\xc8\x62\x87\xe5\x72\xff\x4d\x07\x42\x1a\x58\xdc\x7b\x3d\x72\x7d\xe1\x13\x76\x99\x52\xb6\xd8\xd7\x36\x08\x8b\x36\xa8\x25", b"\x30\xac\xbd\x1c\x4c\xd6\xaa\x66\x6e\xe5\x2b\x0b\xdc\x41\xfc\x3b\x23\x9b\x60\xd5\x7e\x27\x9b\x3f\x54\x83\xc4\xd5\x4b\xdd\x97\xa6", )?; test( HashAlgorithm::SHA224, b"\x5f\x8d\x7f\x28\x3a\xf0\x03\x84\xa5\x19\x76\x90\x29\xd2\x08\xb6\x1e\xee\x0e\x1c\xb2\x1c\xe9\xfb\x80\xe9\xd8\x59\x6b\x89\x45\x80\xda\x7a\xb3\x45\x74\x29\xe7\x2d\xfa\x64\xe7\xcb\x83\x94\x14\xde\x34\x4d\xa2\x1c\xff\x55\xb1\xb3\xa8\x31\x89\xd2\x08\xad\x20\x89\xb3\x5a\xbd\x78\xe2\x41\x6b\xce\xb6\x64\x66\x76\x2f\xd7\xab\x9c\x23\x4c\x4a\xec\x38\x72\xcb\xc8\x44\x3c\x92\xb8\xce\x4e\xe4\x59\x54\x25\xe7\x46\xe4\xb6\xf7\x97\x2e\xbd\x5d\x06\x5f\xb3\xfd\xc5\xe3\x29\xe8\xa8\x7e\xd3\xcd\xdb\xe2\x79\xd5\x72\x27\xae\x4b\x13", p, q, g, b"\xaa\x4d\x06\x52\x70\xc3\x8b\xdf\x99\x6b\x1f\x5f\x1e\xe4\xb6\x7a\x76\xef\x1e\x7b\x13\x4e\xa2\x1f\xd0\xa6\x13\x75\x21\x24\x50\x52\xe7\x49\x54\xb9\x65\x44\xc7\x00\xd4\x0f\x36\x24\x8f\xf2\x9a\x71\x2a\x09\x8d\x80\xca\x12\xe2\x8f\xdd\x79\x01\xa6\x22\xdd\x09\x88\xe1\xc4\xd6\x7d\xe4\xc4\x97\xa9\x57\x88\x2c\xe9\x92\xfc\xb0\x8c\x5b\x85\xc6\x85\x84\x47\xed\x6f\xcb\xad\x26\xd8\xc4\x04\x85\xf0\xa8\x9d\x9d\x02\x0f\xe2\x33\xe8\x93\x19\x03\x84\x55\x64\x4c\x82\x8d\x60\x8d\xf9\x70\x7c\x63\x17\x0d\xd0\x61\x8c\x0b\xae\xf3\xec\xa8\xd1\x45\x54\x60\xa2\xeb\x25\xfa\xff\x44\x4f\x80\x3b\xca\x29\x7b\xb6\x80\xe5\xf0\xfd\x06\xe8\x87\xed\x50\xc8\x06\x0f\x55\xd0\x16\x0e\xc6\x45\x17\x08\x6f\x4e\x1d\x62\x4a\xb7\xd1\x2d\xf1\xb5\x94\x70\x17\xe6\x22\xeb\xbc\xd6\xf4\xed\xdb\x0a\x41\xdc\xba\x82\x74\x3e\xfd\xc5\x80\x42\x88\xd2\x86\x3f\x54\x00\x3e\xea\x12\x75\x32\x46\xe6\xe0\x35\x7d\xf0\x55\x01\xb1\x95\xfd\xf3\xa7\x76\x1c\x4c\x3a\xcf\x26\x53\x7b\xf9\x8b\x32\xf2\xe7\x2f\xf1\xe0\x15\x9d\x04\x6b\xbc\x05\x31\x71\xe3\xd5\x18\x34\x4f\x05\x37\xf2\xe7\x20\x0b\xcd\xd9\x57\xec\x96\x36\x5c\xaf\x55\xfc\xd2\x46\xaf\xe7\x71\x70\x9e\xce\xc2\x83\x48\xa3\x56\xa1\xd4\xeb\x13\x6a\x17\x6a\xdb\x5f\xa1\x02\xf5\xfa\x5c\x96\x9f\x90\x89\x64\x62\xe0\x67\x7a\xfc\x60\x6a\x94\x8b\x25\x58\x7c\x10\x31\x6d\x22\xe1\x26\x9f\xc6\x4f\x91\x5a\x79\x6c\x96\x5b\x8b\xe9\x7e\x5b\xea\xb0\x47\xca\x51\x98\xbf\x2f\xf8\x56\xdf\x74\x0a\xfb\xbc\x1a\xef\xaf\xef\xb1\xed\x47\x27\x8b\x15\x0e\x6a\x72\x22\x41\x7d\x3a\x86\x49\x4b\xdb\x51\xed\xd0\x61\x68\x99\x52\x6c\x27\xac\xc2\xa8\x18\xe8\x3b\xaf\x57\x9b", b"\x71\x87\xca\xe8\x36\x82\x36\x18\xf9\xa6\xe8\x47\x05\x5c\xa2\xbc\x38\xc8\x6e\x72\x6d\x02\xd3\x8f\x49\x50\xeb\x6b\x71\xb3\x6b\xcb", b"\x21\xf6\xff\x41\x75\x76\x54\x30\xe2\xdb\xed\x34\x2a\x85\xd3\x00\x56\xb2\x89\x05\x74\x4e\xce\x5d\xad\x79\x75\x5e\xe3\xd7\xbb\xbd", )?; test( HashAlgorithm::SHA224, b"\xb2\x16\xa0\x35\xb0\xff\x29\xfe\xaf\x7d\x4c\x34\xee\xb1\x60\x41\x55\xc9\x03\x38\x00\x67\x53\xee\x2b\x36\x06\x2d\x72\xf6\x2b\x52\x45\x04\x65\x9f\x70\xb9\x76\xc6\x89\x52\xa6\x2c\x2b\x9a\x2a\x00\xcf\x00\x66\xa5\xe5\x09\x8a\x63\x2d\xf2\xee\x56\xdd\x1a\x14\x0a\x98\xf7\xb3\xac\x12\xdb\x35\x76\xb6\x10\xd7\x65\x63\xe4\x62\x16\x37\xda\x10\x98\xaa\x20\xf3\xc8\x32\x47\xb7\x27\x88\x60\x41\x7c\xec\xf7\xe1\x37\x19\x4c\xf1\xba\xe1\x2b\xbc\x63\xa7\xba\xe0\x2c\x90\x6d\x50\x3f\x69\x4d\xea\x3b\xd5\x34\x71\x8e\x37\x70\x49\x62", p, q, g, b"\x7d\x6b\x3b\x71\xb1\x41\x58\x07\xd1\x59\x01\x42\x7e\x6a\xb0\x02\xee\x98\x5c\xe7\xc8\xd8\x44\x96\x9c\x6e\x72\x94\xa2\x16\x7b\x4c\x26\x17\x1b\xcd\x64\x6f\x0d\x1b\xce\x14\xdf\x05\xe4\xce\x58\xa3\xae\x50\xb2\xab\xa5\xfb\x74\x45\x52\x33\xfa\x6d\x17\x9a\x07\x94\xcb\x26\xe9\x2c\xa9\x10\xcd\x1c\x16\xe5\x46\x4e\x8f\xa7\xba\x93\x63\x41\xd3\xac\x21\x1a\xc1\xf8\xa2\xf2\xa1\x9c\x14\x8a\x1c\x3d\x6b\x00\xac\x44\xc3\x5e\xa3\x45\xa3\xff\x73\xae\x9d\x5a\xbc\xc6\xab\x65\x16\x2a\x53\xda\xab\xdf\x6d\xa2\x5f\x96\x95\x8e\xaf\x89\xf5\x59\x89\x5c\xbe\xc5\x23\x51\x39\x4f\x91\x32\xc9\x56\x4d\x61\xaa\xc7\x92\x64\x0f\x11\xe0\x9a\xa6\xf6\xcd\xe9\xee\x9c\xa5\xe0\x5f\xd9\x02\x91\x11\x63\x81\x71\x77\xbf\x05\x4c\xf2\xea\xbf\x7c\xe8\xf3\x4b\xb1\xc4\xad\xed\x8d\xad\x93\x41\x1f\xb2\x76\xd2\xd0\xa2\x96\x79\x96\x61\x30\x7d\xe5\x79\x64\x1e\x60\x7f\xda\xd0\x58\xd9\xa3\xf1\x94\x57\x4e\xa7\x6f\x4b\xec\x46\xbe\xf8\xad\xc5\xd6\x2c\x73\x90\xda\x1c\x45\xf6\xfc\x5d\x9a\x78\x4f\x69\x6f\x24\xae\x7e\x6b\x27\xa8\x09\x02\x94\x18\xdd\x18\xa4\x20\x45\x5c\x2c\xc9\x69\x5e\x7c\x0f\xe0\x02\x19\xa1\x71\x14\x68\xe2\x86\x6b\x71\xf3\xf9\xc5\x38\x78\x9e\xd2\x84\x3f\x44\xf2\xa8\x21\x77\x3c\x52\xd2\x11\xdd\x13\x33\xb5\xf1\x64\xec\xdf\x6c\x3f\xfd\x71\xde\x66\x78\xb0\xc2\x72\xf9\x23\x55\xd5\x97\x4e\xb2\x1c\x3c\x8f\xbd\x0b\xca\x75\x38\xbb\xd9\x89\x47\x50\xb1\xdd\x01\x42\xbe\xa8\x51\x04\x35\x6f\x9a\x51\x5e\xf1\xab\x69\xda\xed\x98\xd9\x48\x03\xac\x91\x2c\x77\x0e\x26\xef\xa2\xfa\x0b\x04\xe1\x10\x51\xce\xd2\xf7\x0f\x06\xf2\xf0\x5e\xac\x80\x29\xd6\x8e\x12\x26\x16\x57\xcf\x4d\xbc\xc1", b"\x67\xdf\x1d\x51\x0d\x06\x3c\x90\x67\xe9\x75\x91\x80\xbe\x47\x0c\x71\xfe\x09\xc4\xf1\x33\xac\xa1\x81\xbd\xb4\x7b\xb8\x7b\x20\x97", b"\x73\x28\xb8\x87\xbf\x0d\x52\x0a\xbe\x6f\x24\xaf\xf2\x15\x3f\x40\xde\x00\x9e\x27\x06\xae\x04\x3d\xd3\xaa\x55\x52\x1d\x95\x72\xd6", )?; test( HashAlgorithm::SHA224, b"\x6c\x67\x11\x6f\xbd\x21\xa0\xe3\xed\x16\xb3\xc4\xca\x58\xac\x49\x66\x19\x18\xbf\xc6\xa7\xc3\xa6\xac\xdb\xcd\x53\xdd\x40\x87\x03\x4f\xca\x16\x4d\xf8\xd3\x8f\x7e\xf7\xdb\x03\x36\x37\x01\x40\x92\x46\x38\x2e\xe0\x53\xc6\x9c\x84\xfa\xfa\x3c\x77\xad\x2c\xe0\x8d\xc7\xf4\x1c\x34\xa3\x1d\xa4\x96\xd0\x70\xa9\x94\x35\x79\x9f\x26\x9d\xc8\xef\xfd\x06\xd3\x1f\x85\x87\x9c\x29\x9c\xf7\x24\x1b\x37\xb9\xa4\xcf\xd5\x45\x08\x63\x93\x15\x67\x37\xcd\x9d\xa2\xd2\x82\xe7\xd5\x69\xfc\xfa\x5c\xbd\xe4\xbb\xa5\x1b\xd8\x9f\xdc\xc9\x13", p, q, g, b"\x6a\x50\xd1\x12\x5f\x9f\x3f\xc2\xf7\xe0\x23\xc0\x93\xb3\x60\x8e\x69\x72\xac\xef\xe2\x9c\x0c\x6b\xa0\x7a\x2f\x61\xed\x74\x71\x53\xad\xa4\xa9\xb6\x80\x62\x2a\x84\x2b\x9a\x82\x01\x19\x67\x56\x20\xc1\x16\x88\x70\x0b\x85\x5d\x4b\x8d\x13\xbf\x72\x6c\x36\xac\xf9\x23\x25\x6f\xef\x1b\x53\x09\x36\x22\xd1\xbc\xbc\xf0\x23\x84\x8b\x8b\x8f\x4a\xbf\x43\xbb\x6e\x87\xb8\x4d\x06\x1d\xeb\x75\x23\x62\x24\xce\xda\x91\x4b\x18\xf7\xce\xb7\x27\x08\x78\x9d\xfb\x94\x07\x04\x13\xb0\xe6\x5c\x12\x31\xad\x02\xdb\x42\xde\xcb\xe0\xe5\x58\xae\xa0\x6c\x31\x0a\xa1\xa8\xd1\x13\xbe\x1f\x07\x14\x82\xfc\x61\x91\x32\x25\xf0\x07\xb5\x69\xb6\xe8\x67\xcf\xb3\x92\x72\x57\x76\xad\x71\xf5\x0d\xc9\x7b\x83\x4a\x71\x37\x5b\xac\x18\xfa\xbf\x78\x11\x26\xd0\x6d\xf6\x21\x24\x06\x4e\x6a\x72\x3b\x48\x63\x5e\x67\x54\xfc\x76\x7a\x50\x94\xd0\x64\x59\x74\x04\x15\x91\xd0\xad\x48\x28\xf6\x37\x83\x35\x66\x96\xaf\x7f\xf7\x7c\xd0\x01\x07\x94\x9f\xbf\xf4\x70\x9d\xff\x8a\x66\x0a\x41\x3f\x5b\x6c\x0d\xf3\x7a\xde\x84\xfc\xbc\x1d\x32\x53\xba\x61\x72\x65\xa1\x0c\xc0\x87\x60\x61\x30\x29\x09\x09\xa4\xf8\x13\x34\x1e\xfd\xb6\x11\x69\x6f\xeb\x5b\xea\x3d\x7d\x00\xa5\x3a\x81\xf3\xa2\x04\x3b\x88\x7a\x77\x60\x75\xd2\x50\xc1\xa0\x10\xec\x47\x66\x00\x87\xf3\xef\x05\x78\x2d\xd2\x1d\x29\x8d\x6d\x37\x55\x9c\xd4\x73\x00\x8f\x47\x4d\x8d\xec\xa6\x81\x7c\x13\x90\x18\x02\x76\x09\x7a\x81\xf4\x62\xc0\x52\x79\x28\xf9\x3a\x46\x1f\x4a\xc2\xd6\xed\x8c\x9d\x6d\x10\x1a\x2a\x9a\x29\x20\x1a\x83\xd0\x58\x9f\x57\xbe\x28\xa7\x27\x48\x45\x18\xc7\x42\x5c\xf5\x74\x4d\xf3\x96\xa0\xe1\x4a\x4d\x26\x0a\x5c\x8d\x29\xbf", b"\x7d\x48\x9a\xb0\xd4\x4b\xc7\x32\x71\xef\x42\xe2\x8a\x60\xe1\xb7\xef\x7d\xd2\x7a\xf4\x04\x55\x46\x04\x70\x85\xda\x40\x8b\xcc\xc7", b"\x31\x01\x51\xd9\x43\xf0\x88\xbb\x7d\xfd\xcd\x52\xd8\x28\x84\xa7\xf1\xee\x64\xd4\x6f\x9d\x60\x0d\x23\xf5\x2f\x4c\xea\x4d\x28\x62", )?; test( HashAlgorithm::SHA224, b"\xc8\xd4\x16\xc1\xef\xe6\x86\x63\x70\x78\x12\x2f\x79\x8d\x88\x04\xf6\x4a\x6e\x85\xe0\x5f\x7e\x8e\x07\x63\x4a\x30\x9a\x98\xe9\x2a\xbd\x54\x06\x1c\xcc\xc3\x19\xf1\xac\xd4\xa0\x87\xb1\xd7\xdb\xf0\xb6\xbf\x2a\x09\xc5\xdc\x50\x8e\xd1\x4d\xcd\x54\x42\x05\x6e\xad\xe7\x69\x1b\x7f\xb6\x5b\x67\x8e\xc2\xe1\x37\xb5\xfb\xe8\x75\x20\x8a\x42\x7c\x2a\x7a\xd9\x06\x65\x42\x6f\xbc\xbc\x76\x55\xe4\x8a\x89\x65\xd2\x3f\xde\xf1\x1c\xa8\x09\x2f\x51\x12\x07\xa6\x07\x35\x9f\x94\xe9\x1b\x19\x7f\xcc\x99\x3e\xe6\xce\x3c\x37\xad\x3b\x71", p, q, g, b"\xcc\x9b\x9d\x02\x92\x91\x5d\x63\x1a\xa0\xd9\xeb\x61\x61\xf9\x24\x70\x5c\x56\x6e\xe0\x9e\x74\xe4\x18\xd8\x8e\x6b\x67\xb7\xf5\x7a\xff\x51\x70\xf6\xc4\x2a\x83\x9b\xa8\x39\x40\x2b\xfe\x51\x7c\x28\x77\x81\xdc\x97\xdf\x2e\x05\x50\xb3\x86\x24\x84\xd2\x53\x15\x2f\x6c\xff\x89\x5f\x09\x23\x58\xb5\xc4\x45\x90\x48\x58\x13\x09\xef\xf2\xf6\x89\x23\x0b\x4c\x49\x51\xdb\x84\x13\x57\x3b\x6e\xae\x85\xc2\xdc\x50\xfd\x61\x34\x46\x13\x28\xe5\xb6\x43\x9f\x41\x44\x2b\x91\xe3\xa3\x42\x04\x42\x8d\x1e\x2c\x22\x41\x2b\x01\x22\x42\xb1\x4f\x92\xe2\xd1\xba\xd6\x26\xaf\x95\x05\x1b\xf0\x6c\x74\xda\x40\x81\xb0\xd6\x19\xe1\x36\xa9\x9c\x8d\xa3\xa9\x1a\xdb\x3b\x8c\xf8\xbc\x59\x64\xff\x65\x5d\x45\xc7\x5a\xda\x25\x3a\xba\x91\xc6\x40\x95\x39\x4c\x70\x1c\x53\xdd\xc1\x1f\x38\x8d\x61\x98\x4c\x32\xd4\x32\x6a\x8c\x62\x7d\xf8\x45\xb4\x10\x0f\x17\x1b\xbd\xb2\x52\xd3\xe2\x84\x94\xac\x17\x34\x32\xdd\x55\x31\xe0\x30\x40\x30\x2a\xac\x8c\x07\xc9\xea\x92\xa9\xab\x67\xfa\xf0\xc7\x8b\x3a\xd8\xd4\x54\xdc\xd4\x28\xf9\x42\xd8\xce\x6e\x29\x87\x30\x49\xfd\xbf\xa1\xdf\x0e\x6e\xc2\x24\xc9\xdd\x06\x6b\x98\x1a\x40\x0b\x1f\x51\x94\xfe\xe1\x3c\xc5\xca\x7f\xfb\xec\xa9\x8e\xd0\xa0\x22\x13\x77\xa1\xae\x61\x27\x40\xfc\xe7\x74\xee\xed\x68\x38\x2b\x32\xb6\x86\xa2\x5f\xfc\x01\x66\x82\x18\x64\x48\x20\x7c\x4d\x97\x83\xe8\x3d\xa2\x0a\x5e\x8b\x22\x8a\x13\x4d\xc3\xf4\x4e\xcc\x56\x5a\xb9\xae\x16\x2b\x85\x5e\xcd\x37\xe6\x40\x7e\x71\x40\x45\xf4\xe8\x3b\x97\x1a\x5f\x4e\x30\x4c\xd7\x78\xf3\xd3\x41\x37\x74\x5f\xc6\xea\x15\xb4\xb7\x4d\x60\x17\x6e\xf8\x07\x41\x0b\x1b\x26\xf6\x8e\xa1\x4f\x8f\x91", b"\x7f\xa5\x12\x31\xbc\x84\x5f\xa8\xb6\x68\x39\x3b\x78\xa7\xb0\x40\x81\x13\xfb\x77\xc1\xe3\x6f\x3c\x78\xc6\x7d\x65\x71\x5a\x8b\x58", b"\x73\x0c\x9e\x34\x83\x81\x1c\x52\xcf\x29\x5b\xad\x04\x2a\xcb\x5d\xd6\xee\x90\x08\x38\x57\xbe\xe9\x5b\x63\x92\xb0\x80\xb5\x04\x1d", )?; // [mod = L=3072, N=256, SHA-256] let p = b"\xc7\xb8\x6d\x70\x44\x21\x8e\x36\x74\x53\xd2\x10\xe7\x64\x33\xe4\xe2\x7a\x98\x3d\xb1\xc5\x60\xbb\x97\x55\xa8\xfb\x7d\x81\x99\x12\xc5\x6c\xfe\x00\x2a\xb1\xff\x3f\x72\x16\x5b\x94\x3c\x0b\x28\xed\x46\x03\x9a\x07\xde\x50\x7d\x7a\x29\xf7\x38\x60\x3d\xec\xd1\x27\x03\x80\xa4\x1f\x97\x1f\x25\x92\x66\x1a\x64\xba\x2f\x35\x1d\x9a\x69\xe5\x1a\x88\x8a\x05\x15\x6b\x7f\xe1\x56\x3c\x4b\x77\xee\x93\xa4\x49\x49\x13\x84\x38\xa2\xab\x8b\xdc\xfc\x49\xb4\xe7\x8d\x1c\xde\x76\x6e\x54\x98\x47\x60\x05\x7d\x76\xcd\x74\x0c\x94\xa4\xdd\x25\xa4\x6a\xa7\x7b\x18\xe9\xd7\x07\xd6\x73\x84\x97\xd4\xea\xc3\x64\xf4\x79\x2d\x97\x66\xa1\x6a\x0e\x23\x48\x07\xe9\x6b\x8c\x64\xd4\x04\xbb\xdb\x87\x6e\x39\xb5\x79\x9e\xf5\x3f\xe6\xcb\x9b\xab\x62\xef\x19\xfd\xcc\x2b\xdd\x90\x5b\xed\xa1\x3b\x9e\xf7\xac\x35\xf1\xf5\x57\xcb\x0d\xc4\x58\xc0\x19\xe2\xbc\x19\xa9\xf5\xdf\xc1\xe4\xec\xa9\xe6\xd4\x66\x56\x41\x24\x30\x4a\x31\xf0\x38\x60\x5a\x3e\x34\x2d\xa0\x1b\xe1\xc2\xb5\x45\x61\x0e\xdd\x2c\x13\x97\xa3\xc8\x39\x65\x88\xc6\x32\x9e\xfe\xb4\xe1\x65\xaf\x5b\x36\x8a\x39\xa8\x8e\x48\x88\xe3\x9f\x40\xbb\x3d\xe4\xeb\x14\x16\x67\x2f\x99\x9f\xea\xd3\x7a\xef\x1c\xa9\x64\x3f\xf3\x2c\xdb\xc0\xfc\xeb\xe6\x28\xd7\xe4\x6d\x28\x1a\x98\x9d\x43\xdd\x21\x43\x21\x51\xaf\x68\xbe\x3f\x6d\x56\xac\xfb\xdb\x6c\x97\xd8\x7f\xcb\x5e\x62\x91\xbf\x8b\x4e\xe1\x27\x5a\xe0\xeb\x43\x83\xcc\x75\x39\x03\xc8\xd2\x9f\x4a\xdb\x6a\x54\x7e\x40\x5d\xec\xdf\xf2\x88\xc5\xf6\xc7\xaa\x30\xdc\xb1\x2f\x84\xd3\x92\x49\x3a\x70\x93\x33\x17\xc0\xf5\xe6\x55\x26\x01\xfa\xe1\x8f\x17\xe6\xe5\xbb\x6b\xf3\x96\xd3\x2d\x8a\xb9"; let q = b"\x87\x6f\xa0\x9e\x1d\xc6\x2b\x23\x6c\xe1\xc3\x15\x5b\xa4\x8b\x0c\xcf\xda\x29\xf3\xac\x5a\x97\xf7\xff\xa1\xbd\x87\xb6\x8d\x2a\x4b"; let g = b"\x11\x0a\xfe\xbb\x12\xc7\xf8\x62\xb6\xde\x03\xd4\x7f\xdb\xc3\x32\x6e\x0d\x4d\x31\xb1\x2a\x8c\xa9\x5b\x2d\xee\x21\x23\xbc\xc6\x67\xd4\xf7\x2c\x1e\x72\x09\x76\x7d\x27\x21\xf9\x5f\xbd\x9a\x4d\x03\x23\x6d\x54\x17\x4f\xbf\xaf\xf2\xc4\xff\x7d\xea\xe4\x73\x8b\x20\xd9\xf3\x7b\xf0\xa1\x13\x4c\x28\x8b\x42\x0a\xf0\xb5\x79\x2e\x47\xa9\x25\x13\xc0\x41\x3f\x34\x6a\x4e\xdb\xab\x2c\x45\xbd\xca\x13\xf5\x34\x1c\x2b\x55\xb8\xba\x54\x93\x2b\x92\x17\xb5\xa8\x59\xe5\x53\xf1\x4b\xb8\xc1\x20\xfb\xb9\xd9\x99\x09\xdf\xf5\xea\x68\xe1\x4b\x37\x99\x64\xfd\x3f\x38\x61\xe5\xba\x5c\xc9\x70\xc4\xa1\x80\xee\xf5\x44\x28\x70\x39\x61\x02\x1e\x7b\xd6\x8c\xb6\x37\x92\x7b\x8c\xbe\xe6\x80\x5f\xa2\x72\x85\xbf\xee\x4d\x1e\xf7\x0e\x02\xc1\xa1\x8a\x7c\xd7\x8b\xef\x1d\xd9\xcd\xad\x45\xdd\xe9\xcd\x69\x07\x55\x05\x0f\xc4\x66\x29\x37\xee\x1d\x6f\x4d\xb1\x28\x07\xcc\xc9\x5b\xc4\x35\xf1\x1b\x71\xe7\x08\x60\x48\xb1\xda\xb5\x91\x3c\x60\x55\x01\x2d\xe8\x2e\x43\xa4\xe5\x0c\xf9\x3f\xef\xf5\xdc\xab\x81\x4a\xbc\x22\x4c\x5e\x00\x25\xbd\x86\x8c\x3f\xc5\x92\x04\x1b\xba\x04\x74\x7c\x10\xaf\x51\x3f\xc3\x6e\x4d\x91\xc6\x3e\xe5\x25\x34\x22\xcf\x40\x63\x39\x8d\x77\xc5\x2f\xcb\x01\x14\x27\xcb\xfc\xfa\x67\xb1\xb2\xc2\xd1\xaa\x4a\x3d\xa7\x26\x45\xcb\x1c\x76\x70\x36\x05\x4e\x2f\x31\xf8\x86\x65\xa5\x44\x61\xc8\x85\xfb\x32\x19\xd5\xad\x87\x48\xa0\x11\x58\xf6\xc7\xc0\xdf\x5a\x8c\x90\x8b\xa8\xc3\xe5\x36\x82\x24\x28\x88\x6c\x7b\x50\x0b\xbc\x15\xb4\x9d\xf7\x46\xb9\xde\x5a\x78\xfe\x3b\x4f\x69\x91\xd0\x11\x0c\x3c\xbf\xf4\x58\x03\x9d\xc3\x62\x61\xcf\x46\xaf\x4b\xc2\x51\x53\x68\xf4\xab\xb7"; test( HashAlgorithm::SHA256, b"\xcb\x06\xe0\x22\x34\x26\x3c\x22\xb8\x0e\x83\x2d\x6d\xc5\xa1\xbe\xe5\xea\x8a\xf3\xbc\x2d\xa7\x52\x44\x1c\x04\x02\x7f\x17\x61\x58\xbf\xe6\x83\x72\xbd\x67\xf8\x4d\x48\x9c\x0d\x49\xb0\x7d\x40\x25\x96\x29\x76\xbe\x60\x43\x7b\xe1\xa2\xd0\x1d\x3b\xe0\x99\x2a\xfa\x5a\xbe\x09\x80\xe2\x6a\x9d\xa4\xae\x72\xf8\x27\xb4\x23\x66\x51\x95\xcc\x4e\xed\x6f\xe8\x5c\x33\x5b\x32\xd9\xc0\x3c\x94\x5a\x86\xe7\xfa\x99\x37\x3f\x0a\x30\xc6\xec\xa9\x38\xb3\xaf\xb6\xdf\xf6\x7a\xdb\x8b\xec\xe6\xf8\xcf\xec\x4b\x6a\x12\xea\x28\x1e\x23\x23", p, q, g, b"\x45\x6a\x10\x5c\x71\x35\x66\x23\x48\x38\xbc\x07\x0b\x8a\x75\x1a\x0b\x57\x76\x7c\xb7\x5e\x99\x11\x4a\x1a\x46\x64\x1e\x11\xda\x1f\xa9\xf2\x29\x14\xd8\x08\xad\x71\x48\x61\x2c\x1e\xa5\x5d\x25\x30\x17\x81\xe9\xae\x0c\x9a\xe3\x6a\x69\xd8\x7b\xa0\x39\xec\x7c\xd8\x64\xc3\xad\x09\x48\x73\xe6\xe5\x67\x09\xfd\x10\xd9\x66\x85\x3d\x61\x1b\x1c\xff\x15\xd3\x7f\xde\xe4\x24\x50\x6c\x18\x4d\x62\xc7\x03\x33\x58\xbe\x78\xc2\x25\x09\x43\xb6\xf6\xd0\x43\xd6\x3b\x31\x7d\xe5\x6e\x5a\xd8\xd1\xfd\x97\xdd\x35\x5a\xbe\x96\x45\x2f\x8e\x43\x54\x85\xfb\x3b\x90\x7b\x51\x90\x0a\xa3\xf2\x44\x18\xdf\x50\xb4\xfc\xda\xfb\xf6\x13\x75\x48\xc3\x93\x73\xb8\xbc\x4b\xa3\xda\xbb\x47\x46\xeb\xd1\x7b\x87\xfc\xd6\xa2\xf1\x97\xc1\x07\xb1\x8e\xc5\xb4\x65\xe6\xe4\xcb\x43\x0d\x9c\x0c\xe7\x8d\xa5\x98\x84\x41\x05\x4a\x37\x07\x92\xb7\x30\xda\x9a\xba\x41\xa3\x16\x9a\xf2\x61\x76\xf7\x4e\x6f\x7c\x0c\x9c\x9b\x55\xb6\x2b\xbe\x7c\xe3\x8d\x46\x95\xd4\x81\x57\xe6\x60\xc2\xac\xb6\x3f\x48\x2f\x55\x41\x81\x50\xe5\xfe\xe4\x3a\xce\x84\xc5\x40\xc3\xba\x76\x62\xae\x80\x83\x5c\x1a\x2d\x51\x89\x0e\xa9\x6b\xa2\x06\x42\x7c\x41\xef\x8c\x38\xaa\x07\xd2\xa3\x65\xe7\xe5\x83\x80\xd8\xf4\x78\x2e\x22\xac\x21\x01\xaf\x73\x2e\xe2\x27\x58\x33\x7b\x25\x36\x37\x83\x8e\x16\xf5\x0f\x56\xd3\x13\xd0\x79\x81\x88\x0d\x68\x55\x57\xf7\xd7\x9a\x6d\xb8\x23\xc6\x1f\x1b\xb3\xdb\xc5\xd5\x04\x21\xa4\x84\x3a\x6f\x29\x69\x0e\x78\xaa\x0f\x0c\xff\x30\x42\x31\x81\x8b\x81\xfc\x4a\x24\x3f\xc0\x0f\x09\xa5\x4c\x46\x6d\x6a\x8c\x73\xd3\x2a\x55\xe1\xab\xd5\xec\x8b\x4e\x1a\xfa\x32\xa7\x9b\x01\xdf\x85\xa8\x1f\x3f\x5c\xfe", b"\x53\xba\xe6\xc6\xf3\x36\xe2\xeb\x31\x1c\x1e\x92\xd9\x5f\xc4\x49\xa9\x29\x44\x4e\xf8\x1e\xc4\x27\x96\x60\xb2\x00\xd5\x94\x33\xde", b"\x49\xf3\xa7\x4e\x95\x3e\x77\xa7\x94\x1a\xf3\xae\xfe\xef\x4e\xd4\x99\xbe\x20\x99\x76\xa0\xed\xb3\xfa\x5e\x7c\xb9\x61\xb0\xc1\x12", )?; test( HashAlgorithm::SHA256, b"\x06\x61\xc1\xbf\x79\xee\xd7\x8a\xd4\x87\x9e\x24\x0a\x46\xb9\x5a\x0d\xb2\xb2\x9b\xf8\x12\x63\xb9\xb1\x67\x6d\xaa\x25\x54\xaa\xd7\x22\x2c\x9e\xb7\xa8\x93\x04\x8e\x46\xfb\xd2\x82\x6a\xb6\xe8\xcf\x42\xab\x0c\xd6\x31\xc4\xc4\xa1\xa8\x19\x56\x0f\x73\xcc\x86\x1a\x5b\x64\x65\xcf\x28\x80\xa7\x30\x63\x5e\xd7\xf4\x9e\x28\xf7\xb5\x65\x76\x8f\x02\x9d\xb2\xa4\x43\xba\x0a\x1b\xd1\x07\x73\xf2\x6f\x75\x2c\x83\xda\x40\xfc\xd3\x3f\x32\xf7\x8d\x24\xac\x98\x20\xd0\xbf\x70\xda\xe5\x68\xa1\x25\x38\xaf\xfa\x86\x71\x60\xc8\x1e\x39", p, q, g, b"\x54\xb6\x81\x80\x54\xcc\x00\x0c\x3a\xf6\x1b\x62\xef\x41\x89\xba\x35\xe0\x48\x45\xde\xe0\x01\x5b\xe6\x27\x33\x92\xc6\x73\x32\xe2\xe0\x45\x10\xcd\x5b\x2b\xbf\x47\x23\xcd\x81\x96\xe0\x25\x51\x1f\x66\x23\xf0\x36\x07\xe5\x66\x48\x4c\x33\x07\x51\xd0\x3c\x71\x30\x68\xa7\x7e\x08\xbd\xe9\x07\xfc\x57\xb3\xc0\x21\xe3\x73\x03\x37\x3d\x9d\x81\x1e\x38\xf1\x4b\x54\x7d\x2b\xd8\x7d\x98\x12\x69\xc6\x77\xda\xc6\xad\xe6\xac\xbb\xae\x30\x14\xeb\xd3\x81\xb4\x00\x86\x37\x03\x1c\x9b\x6d\x49\xca\x90\x87\x65\x47\x2b\x05\x96\x2f\x55\xaa\x36\x1f\x7d\xd5\xa4\x26\x07\x05\xff\x5e\xcf\x7b\x31\x7d\xb1\xfe\x5d\x33\xfd\xbf\x48\xe6\xa3\x3b\x3c\x78\xb1\x4e\x62\x0d\x93\x80\x6b\x52\xe8\x6e\x08\x2f\xe4\xf5\x4d\x52\x65\xe8\xdf\x62\x3b\x0c\x9a\x25\x9f\x61\xb7\xfa\x2c\x04\x55\xfa\xdf\x39\x69\x3e\xf3\x97\x74\x40\xf3\x02\x06\x7c\x3a\xff\xbc\x45\x74\x22\x4d\x5a\x22\x04\x4e\x9b\xfe\x11\xd0\xd6\xed\xe2\x73\x9c\x7f\xfe\x92\x77\xc8\x64\x4d\x46\xbe\xec\xb9\x46\xf8\x17\x75\xc1\x16\x38\x8f\xd6\xc2\x4a\xf0\x2e\xc5\x9f\x62\x12\x33\xef\xe8\x79\x2d\x6d\x0c\xd2\xc8\x43\x33\xb1\x1f\x07\x65\x73\x33\xda\x4e\x27\x4b\x8c\xd3\x91\x4d\x97\x77\x06\xe7\x86\xf3\x25\xe1\x8a\x33\x9b\x80\x5c\x51\xb4\x5e\xac\xb3\xce\x24\x18\x45\x97\x0a\xcb\x9f\xd1\xa4\x82\xa5\x64\xb2\xae\xec\xda\xeb\x0a\x0d\xb3\x9f\x33\xad\x29\x91\xf2\x5c\xf6\x22\xbf\x22\xf0\xc4\x43\x0c\xf9\x4d\xf1\xdb\x59\xaa\x2d\x7c\x20\x04\xb5\x17\x7b\x9e\xa6\x9f\xf5\x56\xdd\x4c\x07\xed\xec\x62\x59\xee\x13\x9b\x42\x15\x73\xa1\x1c\xf8\x5d\x11\xe2\x45\xe2\x51\x19\x0b\xa8\x69\xc9\xcb\x4d\xaf\x9f\x49\x45\x1a\x85\xf3\x8b\x9b\x90\x3e", b"\x51\x9f\xe4\xc5\xf9\xb7\x70\x7a\xe4\xb3\x62\x17\xea\x17\x07\xa1\x87\x1d\x8f\xce\x98\xee\xe9\xe6\x43\xc4\x5c\xd3\xeb\x50\xc5\xd3", b"\x1d\xf2\x24\xaf\x0b\x51\x51\x9e\x11\xd8\x42\x29\x99\xb1\xd3\xab\x09\x72\x06\x41\x80\xff\xc3\xf1\x11\x4c\x9f\x87\x6a\x1d\xe3\xb1", )?; test( HashAlgorithm::SHA256, b"\x15\x97\x35\x3f\x24\xaa\xf5\x15\xfd\x7c\x0b\x0a\x74\x53\x44\x4d\x5f\x32\x9d\x6c\x3f\x09\x91\x13\xbb\x3a\x13\x30\x9b\x05\x3e\x6c\x12\x3a\x56\x22\x7a\x81\xe8\xb1\xa0\xc8\xab\x4b\x46\x16\x0c\xc5\x38\x0d\xf5\x91\xb1\x9d\x8a\x38\x6d\x29\xa8\xe4\x3c\xca\xb5\xd8\xc0\xe5\x47\xfb\xa2\x1b\xff\xcf\x5e\xf4\x2e\xfb\x9f\xb2\xe9\xbe\x62\x97\xc0\x3d\x57\xda\x0b\x58\x89\xb3\xb9\x74\x2d\xdc\x2c\x54\xb8\x37\x3f\xed\x1f\x21\x95\xf5\xbb\x23\x29\xa8\xf1\xf3\xf8\xaf\xce\xc2\x5e\xb1\x52\xe7\xfa\x81\x9e\x5d\x36\xcf\xd3\x62\x52\x39", p, q, g, b"\x50\x62\xaa\x1f\xdc\x67\x29\x4c\xd5\x76\x23\xce\xdd\x28\x08\x30\x3c\xeb\x43\x53\x7e\x3a\xbf\xa1\xbd\xbc\x49\x2b\x1a\xee\xce\xe6\x1b\x1f\xd9\x6c\xc0\x55\xd1\x45\x9a\xb5\x2d\xdc\x3f\x23\x44\x38\x9e\x5f\x21\x44\x8a\x90\xcb\x36\xe4\x48\xe6\x07\x87\xb1\xff\x5a\xb6\xe5\x54\x9a\x39\x21\x49\x6e\x83\x54\x64\x6b\xc1\xfd\x6c\xd5\xf2\x35\x9a\xe2\x99\xc0\xa0\x47\xfa\xc3\x92\x05\x12\xa1\xf4\x11\xc4\x38\xba\xfd\x03\xe9\x5e\x53\x8c\x6e\x21\xd1\xdd\x1f\x15\xa8\x9d\x38\xd4\x8f\x26\x30\x5c\x25\x34\xfa\x8e\x31\xd0\x54\xdc\xb0\x07\x74\x13\x8f\xb8\xfc\x61\xc6\xa8\xd4\xae\x1c\xa4\x64\x30\xd0\xe3\x1b\x4b\x92\xdf\xb1\x5b\xd6\xb8\x73\x9f\xd5\x37\x10\x1e\x77\x33\x4e\x6f\x3c\xe5\x46\x9e\x82\xa8\xdb\xc5\x8b\x3b\xe5\xca\x37\x03\x59\xf4\xa6\x13\x2f\xe0\x33\x60\xb8\xf6\xbe\x24\x8c\x34\x22\x0a\x80\x03\x77\x26\x48\x66\x40\x59\xf1\xf6\xa3\x22\xe0\xc1\x22\xf4\x27\xef\xdb\x7d\x64\x0e\xb5\xbb\x7f\x3d\xb2\xd9\x67\xa2\x15\x90\x92\xd8\xf8\xdf\x33\x3f\xf5\xba\x13\x56\x02\xb9\xee\x7e\x9d\xb6\xae\x0b\x95\x88\x6d\xf3\x8d\x4b\x4a\x26\xa4\xb2\xd7\x90\xc2\x4f\xa2\x14\xcd\x68\xd0\xa7\xed\xe6\x3e\x7d\xfa\xca\xea\xe1\x4d\x97\x85\xbe\x69\x3a\xd7\x8d\x88\x24\x2d\xfa\xd9\x88\xb7\x12\x2a\xdf\x5a\xfa\x9e\xfd\xd0\xc2\x04\x74\x70\xc6\x07\xd4\x7b\x30\x08\x9f\xf8\xbf\xc4\xcf\x5d\x7a\x8b\xa6\x9a\x7d\x0a\xb6\xc5\x4c\x05\x28\x0d\x66\xaa\x40\x19\xf6\x36\x2e\xa2\x4a\x1d\x3f\x8f\xcd\x80\xc3\xeb\x20\x83\x1b\x6e\x0d\xb0\x10\xfa\xf8\x26\x48\x8f\x01\x5f\x63\xf0\xb9\xac\x6d\xf7\x28\x83\xef\xd2\x86\xf0\x53\x2b\x5b\xef\xf1\xb9\xe8\x10\xff\x6a\x2b\x2d\x32\x8a\xf6\x75\xea\xfc\x2f\x56", b"\x05\x02\xa6\xe1\xd8\xc8\xdf\xdf\x56\xeb\x67\xf9\xa6\xf6\x60\x57\x35\xe4\xd1\xb0\x07\x6c\x8b\x08\xb6\x1d\xaf\x8e\x7c\x2b\xf2\xd6", b"\x7c\x67\xab\xdc\xf4\xe5\x80\x81\x2b\x13\xd0\xa4\xed\xba\xe8\xa2\x78\x6d\x66\x12\xbc\x86\x6e\x3c\x13\xbc\x09\xf3\xe9\x66\x16\xe0", )?; test( HashAlgorithm::SHA256, b"\x71\x5f\x29\x69\x30\x31\x23\x68\xa2\xa9\x8d\x3f\x42\x81\x0d\xa5\x71\x15\xf0\x0f\xfc\x4a\x12\x02\x9c\x27\x6b\x10\x62\x9e\x6b\xdd\xd6\x0b\xca\x2c\x53\x5b\x79\xa5\xf4\xa0\x06\x81\x77\x91\xf7\xf3\xad\x2e\x01\xa0\x02\x16\x67\x2e\xe5\xad\xec\x57\x9d\xeb\x07\xe9\xd2\xb0\xdb\x22\x2c\x4e\x01\xe1\xf8\x19\xc1\xa5\x2d\x10\x1b\x1e\xf6\x78\xcf\xca\x85\x65\x5d\xd6\xb2\x42\x6f\x1a\xc3\x79\xa9\x2a\x9c\x69\xb0\xf8\x98\x74\x32\xd1\x09\xcd\x9a\x7b\xc0\x4e\xf2\x87\xc2\xaf\xb6\x63\x44\x46\x88\x60\x1c\xe3\xc5\x5f\xd9\x0d\x0f\xa3", p, q, g, b"\xb6\x33\x40\xd6\xa1\x95\x57\x31\x28\x30\x64\xf6\xf2\x2a\xd7\xf0\xe2\x81\x99\xf6\xa5\x8c\x57\xdd\xcb\x44\xa0\x26\xc6\x1e\x44\x13\x18\xc4\xf8\x75\x5d\xfd\x71\xb2\x95\xe9\xe7\xba\xbe\x00\x08\x49\xc9\x72\xf6\x8d\x4b\xe0\x09\x54\xa3\xc2\x9c\xd4\xb4\xe8\x3a\xd5\x18\x30\x08\x0e\x29\xe7\x61\x9e\x45\xd3\xab\xbf\x9d\x82\xfd\x87\xe9\x75\x81\xfe\x90\x9d\x3d\xa1\xe3\xe9\x6c\xb3\xf0\xc8\x93\xaf\x9d\x07\xf4\x18\xdf\x90\x2e\x76\xb0\xbb\xc1\xc9\x71\x39\xcb\xd5\x12\x26\xac\x44\x2b\x3d\x0b\x05\x25\xc7\x84\xba\x13\x81\x31\x42\x1c\x60\x54\x3e\x6e\x29\x60\x69\xf6\x11\xb9\xc3\x7c\xf6\x03\x06\x36\xee\xca\xf4\x1c\x3b\x48\x38\xf5\x06\xc0\x2c\xc8\x4c\xde\x6b\x99\xca\xbd\x2c\xa5\x78\x44\x9c\xc1\x71\x8a\xa4\x18\xca\x12\xa2\xb7\x6f\x78\x25\x9c\x16\x91\xe0\xb4\x9f\x09\xdb\xda\xf5\x85\xf6\x26\xcf\x74\xd7\x32\x12\xb3\x42\x78\x45\xc6\x6f\x22\x83\xb6\x07\x03\xad\xf1\xa2\x62\xbb\x8b\x10\xac\x7a\xc5\xd1\xec\x73\x61\x4f\xdd\x37\xee\x51\xb7\x1c\xd1\xfb\x4e\x6d\xb8\x93\x83\x82\x64\x3c\x72\x1f\xbc\x4c\xfc\x98\x7b\xc5\xef\xbc\x81\x29\x9b\x37\x5a\x56\x0c\xde\x5a\xda\xe6\x28\x31\xca\x41\x38\xc3\x99\xd8\x2f\x1f\x8b\xc6\x80\xf9\xc6\xb4\x7e\xb4\x64\xa1\xe0\xaa\xc4\x48\xfe\x3b\x5c\x25\xbd\x8c\x0b\x7a\xfb\x70\x1b\x06\x80\xdb\x87\xab\x51\x73\x8f\x19\xf5\xb9\x65\x37\x5d\xd4\x8d\xac\xa0\x7b\xff\x38\x85\x63\x21\x75\x70\x0c\x67\x86\x19\xf1\x94\xe4\xee\x5f\x55\xaa\x44\x8a\xec\xa7\xf7\xb3\x32\x2f\x64\xa5\x47\x31\x5c\x5c\xee\x04\x51\x22\x54\x9f\xb3\x8b\x8a\xcc\x95\xda\x5e\x83\x30\x22\xb3\xb8\x94\xf0\x3c\xcb\x7f\x73\xb9\x1c\x1f\xe8\x2c\xe4\x14\xe1\x21\x94\x11\x78\x0e", b"\x4c\x5e\x99\x0a\x6e\x24\xfe\xdd\xab\x48\xd0\xaf\x4a\x08\xb4\x5a\xe8\x09\x25\x94\xbf\xb3\xc0\x12\xfa\x1c\x32\x5c\x97\x7a\x3c\xc0", b"\x82\x0b\x6c\xaf\xa8\x9b\x41\xc4\xcc\xbe\xc8\x42\xd7\xc4\x08\xc6\x5d\x49\x98\xab\x1a\xc6\xb6\xbc\xe8\xd4\xd5\x69\xcd\xf0\x47\x26", )?; test( HashAlgorithm::SHA256, b"\x1f\xe5\xad\x49\xe1\x1c\x20\x7d\x3d\x5e\x19\x23\x06\x08\x32\xaf\xbf\xc0\xaa\x0c\xb2\x9f\xc0\xb2\x2b\x3b\xe5\x9a\x59\x8f\x8c\x70\x3b\x9b\xf2\xc7\x34\x7f\x8a\xbd\xe2\x56\x77\xea\x9c\xc6\x0a\xf9\x30\x7d\x21\xd3\x01\xfd\xd2\x3c\x28\x27\x7f\xce\x11\x40\x03\x10\x03\x39\x62\xc0\x4e\xcd\x37\x7f\xd4\x46\x35\x8a\x34\x49\xef\xd6\xbc\x05\x72\x1b\x78\x4d\xdf\x0e\x23\x8f\x28\x60\x8e\x86\xbd\x4c\x3d\x7a\xc6\x31\xff\xf8\xbe\x06\x78\xd3\x7b\xfb\xac\x16\xb7\x5b\xc1\x5a\x50\xce\x13\x97\xdd\x4b\xa3\xbf\xfc\xf9\x4d\x34\x12\x74", p, q, g, b"\x53\xfc\xd0\x73\x99\xe4\xd3\x1b\x09\xab\xef\xf2\xf0\x96\xa7\xb2\xcc\x5c\xf4\x17\xde\xe1\x20\x7d\x8a\x5a\xab\xf9\xe8\xf9\xfb\x0f\x66\xbe\x48\x82\x6a\x3d\xc1\x1e\x39\xbe\xba\x2f\xf4\x7b\x76\x54\x4b\xcf\x55\x48\x5a\xcf\x1e\x3d\x49\xe1\x90\x57\x01\x5e\x49\xed\x01\x2a\x48\x77\xbe\x74\x16\x07\x74\x9b\x6f\x4b\xf9\x5c\x44\xec\x3c\x9e\x8b\x89\x3a\xae\x8d\x80\xe3\x69\x97\x8a\x35\x80\x37\x1c\xc1\x3d\xe8\xe7\x14\x09\x2b\xb8\x92\xe4\xa9\x56\xad\x36\x54\x03\x2f\x77\x58\xfb\x94\x54\xa1\xcb\x56\x40\x6e\x1b\xf4\x58\x55\x10\x8e\xe9\x60\x10\x7a\x65\xd4\x54\x53\xcb\x48\x2d\xc1\x90\x49\xb6\xc8\x3b\xac\x11\x17\x56\xca\xf6\x5b\xdb\xe5\xe6\xb2\x70\xd5\x87\x5b\x99\x7a\x17\x22\xee\x9d\x58\x38\x49\x41\xaa\x40\xe8\x10\xb6\x0b\x83\x41\x2e\xaf\xd0\xa7\x42\x8a\x0a\xbb\x55\xdf\x45\x68\x0c\xf2\x26\x56\x71\x1d\xb6\xbf\xce\x8b\xdc\xbb\x4c\x08\x3a\x40\x1c\xdb\x68\x28\x4e\x0c\x7e\xc0\x0f\x7d\xe7\x4e\x57\x14\x6a\xda\xe2\x21\xe5\x4c\xc4\xa5\x66\xb0\x5a\x11\x3d\xdb\x22\xcb\xc1\x9d\x88\x1a\x41\xcd\x75\xde\x8c\xf6\xc7\xb8\x9a\x5f\xae\x65\x0d\xf5\x85\xaa\x70\xc0\x45\xb8\x4b\x2c\xbb\xcc\xd0\xe7\xab\x72\x0c\x58\x96\xab\xfd\x35\x6a\x66\xf3\xdc\xbb\xb5\x38\x6b\xe6\xd0\x2e\xa9\xb3\x19\x1c\xa2\x75\xd2\x22\x48\xae\xdc\x36\x0e\xcd\x40\x57\xae\x06\xab\x2c\x2a\xaf\xb5\x06\x57\xa9\x1c\x62\xe0\x38\xea\xc9\xf5\xc4\xd8\x81\x06\xdb\x4c\x69\x26\xfb\x5d\xd2\xde\x1e\xc7\xe4\xe0\x05\xce\x18\x45\x70\xe7\xe9\x7d\x76\x42\x2f\xa0\x37\x62\x1a\x6f\x6d\x46\xcb\x83\xab\x6f\x4d\x43\x4b\x6a\x8f\x07\x39\x00\xcb\x03\xa7\x81\x04\x55\xd1\x9e\x77\xd4\xdf\x62\x4d\x08\xe7\x82\x09\x0f\xfa", b"\x41\xa2\xc9\x55\xf4\x14\x13\xa7\xab\x06\x7b\x4f\x50\xc6\x1e\x39\x6f\x9f\xeb\xff\x61\xc1\x50\x0b\x1a\x4b\xc6\x9e\x50\xa5\x19\x35", b"\x79\xed\xd7\x51\xa9\xdc\x23\x72\xb4\x05\x80\xfa\x4d\x53\x8f\xbe\x2c\xda\x41\x49\xf6\xb1\x19\x39\xdd\xad\x92\xc5\x74\x74\x08\x83", )?; test( HashAlgorithm::SHA256, b"\xa3\x26\x97\x30\x93\xce\x50\x2c\x16\x47\x3d\x89\xba\x19\x65\x07\xd9\x22\x81\x50\x47\x59\xcb\x34\xc6\xcc\x35\x3d\x45\x19\x7f\x91\x5b\x5e\x73\x6b\x8f\xf8\x57\xa8\xb2\xec\x99\x64\x9a\x32\x24\xf8\x57\x40\x18\x98\xc9\xea\x60\x7e\x6a\x2c\x1d\x32\x0f\x27\x56\x4c\xcf\xf5\xdb\xda\xcf\xd8\x7a\x14\x5f\x1a\x02\x94\x25\xd7\x65\x02\xc0\x81\xac\x0f\x6a\x14\xde\x5b\x2c\xad\x1c\x23\xa6\x1d\x4e\x9e\xc6\xa0\x4e\x1a\x45\x5f\xd7\x10\xc3\xc7\x8c\x09\x67\x53\xc0\xb7\xf1\x51\x1e\x8b\xa5\xf5\xf1\xaf\x4f\x07\x41\xfe\xe8\x8b\x77\xeb", p, q, g, b"\x92\x91\x5d\xb2\x1c\x2c\x3e\x57\xfc\xcc\xb7\xdf\xdc\xe2\x8a\x12\xaa\xf6\xdd\x10\x58\x11\x93\xb9\x8b\x7d\x51\xa7\x28\xc3\x85\x16\xe3\x9e\xf5\xcf\xb1\xff\x9f\xa1\x65\x9c\x9b\xee\x56\xd4\xeb\xc1\xcd\x69\x64\x6c\x3c\xc3\xf7\xca\xae\x0c\x42\xd9\xcc\xa9\x21\x91\x48\xe4\x99\x8c\x2d\xdc\x89\xeb\x9a\x3e\xdc\xfa\x6f\x45\x71\x29\x00\x7a\x93\x44\x01\x3d\xd1\x23\xaf\xf1\x97\xbf\xcd\x3d\xb1\xd9\xe2\x19\x9b\xce\xa1\x61\x65\xa4\xc3\x4e\xd2\xac\x32\x16\x7a\xbd\x16\x77\x04\xea\xd3\x1d\x5f\xc2\x86\x0b\x83\x4d\x44\xf8\x6c\xb5\x30\xda\xd9\xe8\x87\x01\x3c\xa4\xd6\xe8\x83\x00\x8c\x28\x6d\x20\x6b\x6c\x7c\xb2\x52\xd1\x32\x8b\x50\x3a\xe0\x67\x9b\x50\x2e\xc1\x64\x6f\x69\xf2\x60\x2d\x5e\x3d\x63\x1d\x4a\x5a\x63\xfc\x7a\x5d\x06\xf2\x79\x26\xa4\xd6\xb1\xef\x2f\x77\xdd\xff\x3d\x85\x0d\x3d\x9f\x58\xa9\x58\xc3\xf4\xf1\x2c\xf0\x29\xf1\x48\x38\x6c\x5b\x8a\x71\xba\xe9\x09\x4d\xec\x85\x27\x9b\x1e\x38\x77\x99\xd2\x6b\x2a\x6a\x0e\x0d\xbf\x06\x49\x73\x66\xe4\x90\x3e\x55\x9e\x70\x97\x5d\xed\xc7\xd4\x93\x4d\x4e\x2d\x3d\x2c\xd3\x05\xab\x82\x64\x02\xea\x8f\x27\x78\xe2\x66\x25\x11\x9e\x7b\x0c\x24\xc4\x5d\xd9\xc0\x5a\x38\x90\xdd\x1d\x9d\x93\x0b\xd0\xbb\x40\x93\x66\xb0\x7a\x47\xce\x57\x2e\xd5\xbc\xd5\xf6\x3c\x46\x7d\x49\xc5\x68\x11\xfc\x3e\x40\x13\x41\xb9\xa4\x53\x1f\x77\x6d\xeb\xde\xa5\x40\xa3\x4c\x7c\xca\x3c\x3f\xb2\xea\x99\xc5\xfa\x9f\x9f\xdf\xde\x91\x8a\x94\xf7\x4e\x08\x0d\x19\x86\xb6\x8f\xc1\xe3\xfb\x97\x80\x54\x87\x2c\xed\x97\xba\xfd\x96\x73\x1e\x6d\x4f\x1c\x4a\x91\x27\x8c\x38\x3d\x47\x61\xc9\x74\x10\x09\x74\x52\x2f\x7b\x6e\x8a\x28\x84\xd5\xb3\xbb\xf6", b"\x73\xf1\x92\x2e\x26\xd9\xb8\x06\x8b\x68\xf8\x3c\x2b\xd5\xdb\xbb\x59\x60\x40\x3b\x49\x22\x3c\x02\xa4\x2c\xe6\xcf\x38\x10\xdb\x66", b"\x3a\xd3\x0b\xe9\xa6\x0f\x6d\x42\x27\x03\x94\x56\xc9\x82\x7d\x54\x24\x85\x8a\x02\xa8\xe6\xd3\x89\x17\x72\xcf\x80\xa5\xe4\xee\x21", )?; test( HashAlgorithm::SHA256, b"\x75\x04\x38\x2f\xb7\xfb\xa1\xda\xb3\xc9\x3b\xd3\x1b\x16\xe7\x3d\x9a\xe1\xd0\x27\xdd\x23\x16\x6b\x3b\x94\xc7\x12\x41\x83\xfa\xf3\x96\x3c\x42\x0b\xe5\x20\x5a\x1f\x44\xa9\xa9\x02\x6c\x6e\xf7\x7e\x7c\x4e\xf1\xec\x48\x45\xfe\xf6\xe5\xea\x24\x87\xce\x01\x2f\xf5\x3f\x94\x50\xfc\xeb\x0d\x3a\xc6\x2f\x21\x02\xd7\x17\xe3\x28\x7d\xb3\x71\x47\x17\xa2\x8c\xd8\xb7\xfc\x64\x55\x6a\x86\x17\x3e\x6e\x7f\x47\x9f\x8a\x8d\xcd\x89\x54\x29\xcd\x7f\x0f\x53\x04\xef\x6a\xaf\x27\x5d\x94\xa7\xf4\xb3\x0a\xcc\x10\x71\x78\x7c\xa5\xf0\x62", p, q, g, b"\x20\x55\xbb\xe8\x9d\xa0\xa0\xc4\x88\xc3\xdb\xf2\x95\x31\xf1\xf7\xcd\x3f\xb5\x5a\x26\xef\xc5\x40\xc2\xed\xdc\xcc\xea\x16\x15\xdd\x92\x3f\xea\x4c\x8d\x0c\x95\xa5\xaf\x7e\x1e\x78\x16\x04\x8f\x2a\xe8\x53\x23\xa9\x64\x11\xe7\xd1\xad\x62\xc4\xca\x67\x5b\x63\xdf\x9d\xba\x31\xc1\xc7\x68\x03\xfb\x1c\x82\x92\x46\x5a\xd0\xa7\xe4\x9b\xa3\x75\x6a\x8a\xd4\xc6\xce\x86\xfd\x30\xb8\xb2\x8e\x08\xc4\xb4\x77\x7e\x07\x9f\xaf\xf1\x0f\xf8\x52\xf7\xd8\x91\xa9\x84\x19\x8d\xd0\x49\x77\x97\x21\x08\xc5\x2c\xe8\xbd\xb1\x15\x64\x62\x24\xa7\x93\x37\x74\x6e\x36\x47\x21\x31\x98\xf1\x12\x74\x30\xf5\x60\x87\x33\xd8\x82\x04\xa6\x2b\xe6\xea\xee\x84\x62\x9f\xc7\x28\x2a\xce\xf4\xc4\xf5\xd3\xad\xbe\x72\x41\x0b\x1e\xdf\xb7\x4b\xe1\x6b\x2d\x67\x5c\xca\x89\x1b\xd8\xce\xf2\x05\x17\x89\x02\xb9\x92\x71\xb4\x80\x41\xab\xe3\x3a\xc1\x19\xad\x6b\x75\x6a\x47\x7a\x63\x06\x3a\xae\x8a\x17\xcc\xfb\xe2\xac\xae\x3c\x0a\x3c\x63\x0c\x13\xad\xe1\x97\xcf\x3d\x05\xa9\xfa\x9d\x68\x99\xc0\xa3\xf9\x48\x7e\x61\x48\x73\x2d\xc6\x3e\x90\x7e\xf7\x94\x88\xdf\x33\x73\xb8\xa2\x13\x70\x5d\x69\xdc\xce\x6e\xd9\xa2\x20\x9f\x59\xeb\xc5\x8b\xbb\xeb\x08\x05\x45\x10\xb5\xa6\x51\x69\xd0\xfc\x1d\x4d\x10\xbd\xa6\x8a\xa7\xec\xea\xe2\xe7\x2f\x03\x39\xa2\xea\xae\xa0\x83\x03\x06\x4d\xd6\x58\x84\x14\xee\x77\x05\xdf\x3a\xb9\x74\xde\xbe\xf5\x88\xf4\xe3\x1f\xd6\xa8\xf2\x59\x79\xc9\xf5\x21\xd2\x34\x31\x20\xe4\x07\x94\xf4\x1a\x46\x01\xbe\x57\x91\x83\xb8\x77\xe6\xa8\xf6\xc0\xab\x7c\xe8\x48\x0e\x7f\xbf\xf4\x67\xa5\x81\xdf\x57\x0a\xf8\x99\x29\xbc\x4b\x56\x39\x7b\x78\x7d\xf4\xd7\x29\xe6\x5f\x9b\x98\xee\x7e", b"\x56\xe2\x1a\x7a\xb6\x1f\x9e\xab\xbf\xf4\x7c\x75\xe5\xf6\x8c\x31\x87\x3a\x9e\x1f\x2e\x1d\xb6\x62\x73\x11\x82\xf9\xa0\x29\xb8\xf6", b"\x2f\x24\xc5\x2f\x7b\xaa\xe2\x9c\x0b\x46\x33\xa3\x85\x52\x33\x18\x0e\xba\x80\x61\x1d\xbc\x7e\x88\xe2\x35\x48\xa5\x20\xb6\x0f\x66", )?; test( HashAlgorithm::SHA256, b"\x0c\x0f\x7b\x0f\x99\x55\xbb\x54\xf1\x6e\x4e\x39\xad\x9b\xfd\x1d\xeb\x04\xb8\xe8\xb3\x8e\x67\x4d\xa4\x55\x69\x6b\xdf\x7c\xf2\x8e\x24\x11\x4a\xd0\x05\x13\xd8\xdd\x4e\x5c\x89\x5d\x35\x1e\xa9\x13\xfe\xe5\x16\xb6\x46\x82\x00\x87\x72\x1d\x9a\x0b\x5e\xcd\x76\x9b\x38\x25\x73\x91\x23\x54\x4e\x70\x58\xb6\x6d\x23\x42\xb0\x44\x62\xd5\xd1\x73\xcd\xb0\x0e\xf6\xac\xa6\x04\xaa\xa4\x38\xb8\x86\x8d\x15\xdd\x66\x24\xab\xb8\xd1\x93\x84\xdb\x48\xbd\xaa\x66\x47\x14\x13\xa8\x94\xd3\x61\x0b\xc9\x7d\x84\x8a\x59\xe2\xc6\x9c\x0c\x0a", p, q, g, b"\x45\xef\x38\x4e\xd8\x17\x38\x66\x68\xe1\xb9\x0b\x42\xf1\xd4\x23\xad\x9b\x17\xea\x87\x01\x19\xc0\x93\x2a\xc2\xf5\x15\xf5\x46\xa3\xb6\xb8\x0a\x61\x2e\xe6\x6d\xfc\x00\xcc\x4d\x9e\x3b\x5d\xd1\x53\x03\xd5\xeb\xc0\xaa\x40\xcb\xcd\x77\x46\xf5\x4a\x3f\xfe\xa2\x3a\xea\x07\x04\xae\x9c\xf5\xad\x61\x45\x62\x9c\x61\xd1\x58\xdb\x6e\xe3\x9a\xc8\x99\xbb\xda\x59\x79\x4b\x17\x69\xa9\x29\x82\x08\x2b\x77\xa1\xd4\x88\x56\x42\x7b\x78\xbb\x6e\x07\x7e\x27\x33\x5f\x11\x5b\xb8\x42\xe5\x32\x51\xf6\x99\xf0\x44\x88\xbe\xaf\x83\xa6\xc4\xaa\x6a\x4b\x76\x37\x0c\xef\xc9\x09\x9c\x0a\x45\xbc\xf9\x73\x24\x2d\xf2\xa0\x1e\xf6\x8e\x66\xc8\x7e\xff\xd7\xf9\x8f\x44\x1e\x94\xa0\x9a\x28\x30\x07\x6c\x28\x95\xf9\x97\xaf\xd0\xa9\x09\xb4\x5b\x3c\x05\x91\x77\x00\x02\x36\xc5\x01\xbf\xaa\x56\xda\x80\x0e\xcf\x08\x70\x1d\x21\x20\x16\xb5\x25\xf3\x0d\x63\xcc\xf3\xaf\xea\x09\xdf\x39\xe1\xcf\xab\x7b\xf4\x5d\xe1\xa3\x9a\xc7\xf2\x8d\xe0\x03\x7e\xc5\x52\xe2\xea\x10\xc6\xb5\x6a\x5d\xb8\xc1\x3f\xcb\xf7\x3d\x2e\x50\xd5\x8b\x4f\x3c\xf2\x78\x50\x6f\x1e\xaf\x08\x73\xe9\xee\x94\x65\xcb\xaf\xf4\xae\x62\x6f\x3a\xa1\x09\xfc\xe4\x9e\x55\xd5\x7f\xe8\x81\xc5\x0f\x72\x79\x26\x26\x21\x28\x2a\xdc\xf3\x79\x14\x1c\x9b\x2c\x39\x81\x3f\xaf\x82\x3a\x7e\xc0\x77\xc6\xe6\xbf\x95\x3f\x13\x0a\xca\x58\xf3\x6e\x7a\x87\xab\x1a\xae\xea\x5e\xeb\x44\x02\xfa\x9e\x26\xef\x89\x38\xc8\xf3\x8a\x6c\x04\x08\x09\xf4\xd0\x4c\x81\xe2\x94\x83\x87\xd7\xbe\x81\x3a\x97\x3a\x9c\x95\x17\x67\x00\x11\x7d\xe2\xf3\x3e\x61\x94\x03\x87\xf8\x51\xa7\x3d\xfa\x4a\xc5\xc9\x84\xec\x97\x91\x8c\x96\x7b\xfe\xdd\x88\x6d\x1b\xb7\x05", b"\x77\x56\x3b\x3b\x48\xfc\x9e\xe0\xdb\xea\x79\xfc\x74\xdd\x6c\x69\xb7\x2c\x42\x70\x91\x8e\x6a\x1b\xe2\xc9\x98\x17\x70\x23\xb4\x0f", b"\x09\x9c\xdd\x62\xdc\x04\x4a\x57\xea\x25\xd1\xb5\xc1\xf6\xed\x84\xd1\x1b\xac\xbb\x09\x75\x97\x6d\x58\x21\xc4\x14\xb5\x41\x6b\xde", )?; test( HashAlgorithm::SHA256, b"\xc6\x77\x35\x69\x8a\xe7\xbb\xae\xb6\xf3\x21\xa1\x08\x86\x17\x38\x2a\x5c\x92\x09\x21\x51\xec\x36\x45\x82\x96\x2c\x9c\x0e\xd9\xed\x8f\xc7\x90\xcd\xe0\xd9\x74\x4d\x4e\x38\x97\x0a\x84\x82\x40\x1c\x0f\x61\xe9\x18\x05\xf4\x98\x4b\x8c\xfd\xf9\xdc\x80\x93\xa5\xc6\x68\x1d\xac\x13\x80\x9b\xc4\x1d\x16\x7d\x3e\x11\xbc\x99\x69\x8a\x4b\xc0\x7f\xd2\x48\xa6\x74\x91\xe8\x64\x10\x81\xff\x1e\x97\x87\x17\x45\x15\x7c\xf9\x30\x19\x5a\x35\xa1\x4d\x08\x83\xa2\x6d\xb4\x42\xe4\xed\xb9\x62\xaa\x61\x87\xb8\xd1\xc7\x79\x1d\x61\xbd\x25", p, q, g, b"\x0d\x3b\x3c\x3d\xf0\x72\xb5\xf5\x12\x91\x18\x13\x2b\xb7\xbc\xa3\xc5\x2f\x51\xdf\x36\x76\x7f\x11\x52\x38\x7e\xc0\x0d\xf6\x5c\x72\x8f\x0c\xff\xc1\xcb\x6f\x22\x42\x58\xcb\x6d\x3e\x90\xf7\x9d\xd9\x76\xb5\xa1\x80\xb8\x39\x03\xd2\x10\xf0\xc4\xda\xb8\x2e\xb7\x2a\x1f\x89\x97\xbf\x09\x30\x1d\x0f\x7c\x89\x07\x5d\x55\x2c\x81\xfd\x95\x85\xb0\xb1\xb1\x29\x17\x44\xd2\x1b\xd1\xed\xcb\x51\x12\x17\xc2\x96\x2e\x1a\x6d\xe9\xbb\x01\xc2\xb9\x69\x8f\xf5\x5e\xa7\x5d\xcf\xe4\x56\xbe\x48\x1c\xb6\xf0\x64\xfe\xd4\xbf\xf8\x74\xeb\x1c\x9b\x74\x51\x97\x9f\x7d\xe7\x01\x1b\xaf\x5a\x47\xc9\x76\xa1\x79\xae\xe9\x09\xd2\x5c\xa8\x7f\xd5\xe3\xc7\x5d\xf7\x78\xe2\x12\x72\x93\x7c\x5b\xa7\x80\x6a\xef\xa7\x06\x47\x22\x1e\x5f\x7c\xc3\x2a\xb8\x01\x59\x21\xa5\xa9\x5e\xcb\xb3\xca\x4b\x66\x72\x49\xd0\xf3\x4d\xd2\xd8\xba\x86\xdc\x15\x8f\x9e\x84\x25\x17\x6e\x98\x80\x48\xef\xd9\xf7\xb7\xcc\x53\xe9\xfc\xdb\x29\xad\x24\x12\xab\x4c\xa6\xeb\xbd\xe6\xf4\xef\xca\x59\x45\xb5\x3b\x27\x53\xbf\xc4\xea\xbe\x62\x80\x23\x56\x20\xc4\x46\x4f\x69\x40\xac\xca\x1a\x94\x65\x9a\x52\x7a\xa1\x4c\xc7\xc5\x46\x73\x82\xa5\x4f\xe4\x79\x65\x6d\xfb\xc1\x19\x23\x09\x4f\xe8\x01\x9a\x08\xc3\xce\x7e\x99\xa2\x8f\x08\x6b\xda\xaf\x0f\xaa\xc6\xee\x16\x19\x0d\xca\x8e\x94\xbf\x87\x65\x70\x58\x49\x5a\xd0\x79\x31\xc8\x90\x08\xca\x1e\x56\x50\x76\x25\x6a\x93\xcb\x24\x68\xaa\x71\x22\x75\x8b\x8e\x17\x4f\x6a\x80\xf4\x1a\x90\xfc\x92\xf0\x5b\xf1\xf1\xf4\x7d\xa1\x85\xb2\xf2\x5a\x1a\xbf\x5e\x0c\xcc\x66\x13\xe3\xae\xf8\x71\x93\x40\x0d\x75\x1b\x4c\x87\xb4\x4d\x9b\xdf\x5c\x0e\x20\x7f\x0f\x6a\x7d\xc2\x11\x37\x99", b"\x42\xc9\x02\xc5\x82\x68\x74\x77\x45\x50\x46\x4c\x4b\xb7\x36\xf2\xaf\x7f\xd2\xa3\x47\xf2\x7c\x65\xba\xe1\x18\x20\xee\xb7\x52\xaa", b"\x64\x11\xb4\x59\x47\xa4\x3c\x5b\x01\xc2\xf6\xce\xfc\xd4\x1c\xab\x73\xfc\xb6\xea\x0f\x2a\x35\xa2\x14\x75\x56\x30\x55\x31\x6e\x3e", )?; test( HashAlgorithm::SHA256, b"\xeb\x6a\x03\x59\xc6\xe4\x6e\x09\xa4\x2c\x55\x47\x05\xbc\xfc\x5c\x0c\x02\x26\x70\xb2\xf6\xc1\xa5\xbf\xe1\x4e\xa8\x05\x75\x9c\xa2\x25\x61\x53\xfd\xf8\x15\x05\x7c\xa9\xbd\x5f\x4c\xf8\x37\xe1\x4f\xdb\xa3\xad\x17\x61\x2c\xcd\x19\xfd\xe0\x07\x64\xba\x2e\x8e\xcd\x8f\x5a\x18\x5c\xb2\x65\x12\xf7\x45\x72\x59\xc2\xf0\x67\x08\x52\x74\x1e\x73\x93\xb4\x0c\x8b\xab\x67\x3b\xe2\xfa\x51\x9b\x48\xa9\x5d\xee\x65\x52\x36\x5f\xdb\x7d\xdb\x63\x2b\x1b\x33\xf1\xa5\x29\x0b\x82\x8d\xa5\x96\x5e\x82\xd8\x74\xf7\x9c\xdb\x92\x88\x14\xfb", p, q, g, b"\x6d\xb8\x3b\x06\xc6\x98\xed\x80\x12\x2e\xc4\xa2\x18\x33\x70\xed\x7d\xbd\x6e\xa4\x4d\xbb\x45\x42\x14\x95\x68\x57\x0c\x53\x52\x1d\x33\x99\xab\x44\xfe\x2b\xab\xd4\x90\x68\xe1\x19\x53\xc5\xd3\x8f\x7f\xfe\x3b\xcb\xe4\xcb\xce\xb9\x1c\x15\x5a\xc8\x74\x1d\xcf\x22\x6a\x59\xed\xe1\x0b\x05\x0b\x9f\x37\x43\xf2\x96\x89\x26\x6c\xe6\xee\x02\x0c\xa1\x7f\x9f\xa0\xe7\x5b\x3f\x71\x58\xa6\x5c\xef\x9f\xac\x76\xc8\x87\x86\xb5\xe3\x77\xaf\xea\xcb\x9b\x3d\xda\x55\xbe\x92\x2d\xa0\xef\x95\x8a\xa5\x56\xab\xfb\x43\x06\x7a\x41\x4e\x91\x5e\x31\xaf\x5f\x53\x70\x88\x1e\xd9\x7b\x25\xb4\xbf\xec\xbe\x08\x2a\x14\x5d\x02\x71\x7a\xf8\x00\xe7\x7e\x28\x96\x3c\xc0\xa6\xa1\xc1\x1b\x02\x83\x5e\x14\xbd\xba\x1a\x8c\x9c\xe4\xbf\xeb\x06\xaa\xeb\xd7\x60\xd7\xc4\x3c\xf5\x6b\xa2\x12\xd0\xc7\x5d\xa0\x26\x17\x65\x35\xf9\x82\xe8\xd7\x49\xf2\x0c\x2a\x8d\x5f\x53\x87\x5d\x89\x33\x74\xd8\x59\xb7\xce\xe5\x8b\x0e\xb3\x19\xd3\x31\x3c\xb8\xd1\x76\x02\xf4\x7e\x12\x0d\x1a\x24\xa0\xf8\xa6\x3c\xfe\x45\xa5\x02\x8c\xc0\x93\x7b\xbe\x89\xf6\xb3\xb7\xcd\xca\xa7\xdc\xd5\xec\x5f\x3e\xd2\xaa\x9f\x3a\xa8\xe9\x1a\x49\x6a\x8b\xad\x78\x74\xdd\x34\xbd\x8f\x2a\x95\x91\x99\x7d\x54\xf9\x2d\x58\x64\x21\x6c\x95\x36\x46\x84\x0b\x37\x8c\x7a\x05\x21\x5e\xcd\x97\xb6\xba\x94\x4c\xa1\x85\x97\xb7\xa5\x48\x32\xec\x98\xc1\xca\xc0\x00\x3d\x50\xd5\xa0\x53\x12\xcb\xc8\x52\xd5\x07\xcc\x97\x3e\xcb\x56\xf4\x24\xe8\xa1\xc1\x98\xbc\xdb\xaf\xaa\x6f\x92\x8f\xd2\x7a\x7c\x91\xf8\x4b\xc2\x34\xf2\x53\x26\x39\xa8\xaa\x21\x96\xf8\xfc\x2b\x71\x11\xb3\xd0\xb1\x15\x31\x65\xa0\xe0\x52\x5d\x4e\xa5\x95\xf8\x9a\xec\x33\xb6", b"\x04\x93\xdb\x0c\x18\xa3\x88\x27\x09\xb3\xcc\x9f\x8d\xbe\x05\x45\x45\x06\xc0\x4c\x3a\x12\xa4\x1d\x59\x9d\x20\x1d\x76\x15\xb6\xd8", b"\x74\x94\xb4\xd1\xb2\xf3\xae\x22\x79\x72\x55\xa1\xd0\x66\x27\x46\x35\x2a\x3d\x05\x32\x29\x04\x02\x06\x85\x94\xcf\xe4\x8c\x23\xa3", )?; test( HashAlgorithm::SHA256, b"\x5c\x59\xb2\x09\xbb\xc0\xa1\xe0\x10\xcb\x10\x8d\xb4\x10\x1b\x8e\x2d\x04\xce\xd9\x12\x99\xa8\x74\x23\x22\x10\x2e\x0d\x57\x8c\x36\x98\x42\x2b\x43\xd1\x9d\x33\x16\x08\x18\x8b\xed\x4c\x7e\xdc\x03\xa4\x42\xf8\x9a\xae\x60\xf4\xe7\xee\x9b\x63\x25\xde\x3a\x8b\xb7\x02\x91\x8c\x21\x34\x3b\xc9\xb2\x66\xf2\xeb\xcf\x5a\x62\x03\x36\xa7\xbc\x99\xae\x36\x85\xf1\x90\x80\xdb\x46\xf2\x4a\x50\x12\x28\xc5\xbb\xfd\x9c\x0b\x4b\x0a\xbe\xcb\xfb\xd6\x76\xc3\x59\x60\x7c\xe2\x92\xcf\xfd\x52\xd2\x6a\xf8\x0b\x22\xe3\xc4\xd5\x16\xba\x0f", p, q, g, b"\xbe\x31\xfd\x5d\x62\xdb\x69\x0b\xcd\xbc\x09\xe4\x53\xd4\x41\x7f\x82\xe8\x62\x1a\xd7\x17\xca\xb9\x46\x48\x20\x1a\x74\xf6\xff\xdf\xab\x96\x53\x11\xe8\xff\x35\xc4\xa0\xb5\xdd\xa3\x39\xb4\x35\xf1\x73\x17\x17\x5a\xc6\x42\xf7\x85\x12\x9e\x15\x16\x94\xea\x8b\x24\x46\x27\xe3\x00\xce\xb0\xf3\xbe\x08\xf9\x1c\x0f\x52\x7f\x2e\x0d\xf7\xc9\xf5\x54\x92\xd1\x32\x9b\x7d\x96\x89\x63\x4c\x8a\x4f\x52\x10\x15\x7e\x24\x19\xe6\x15\xd9\x43\x17\x36\xf8\x04\xb1\x64\x11\x03\x37\x1e\x7f\xfe\x72\x00\xe7\x42\x96\x12\x7d\x59\xa8\xf9\x7d\x41\xaf\x11\xd7\x0c\x3f\xd0\x25\x31\xf7\xb8\x11\xda\xa7\x51\x6a\xa2\xf2\xa9\xba\x70\xdc\xb7\x04\xf3\xfe\xe4\x7f\x2c\xbe\xd6\x5c\x1e\x3d\x06\xc8\x81\x4e\x1b\x28\xab\xe2\x9f\x3d\x05\x67\x92\xef\xdf\x9a\xc9\x30\x7e\xd0\x10\x6c\x5a\x32\x87\x21\xaf\x0e\x20\x2b\x6d\xf7\x37\xec\x4d\x82\x14\x3d\xd2\x50\x5e\x10\x3a\xd8\x45\x86\x3c\x45\x86\x9e\x69\xab\xd9\xe0\x2c\x7b\x6e\xaa\xff\x9e\x2e\x12\xbc\x18\x81\x38\x68\x8c\x0b\xe3\xe6\x94\x1c\x37\xc7\xdd\xc9\xb6\xd2\x89\xf7\xcc\x8f\xde\x42\xbc\x3c\x14\xe3\xee\x52\x16\x35\xf3\x2f\x54\x28\x0d\x11\x9c\xce\xdf\xc5\x10\x90\xa0\xad\x00\x6b\x24\x27\x60\x40\x14\xea\x4d\x0e\x0c\xd1\xef\xbc\xe0\x9c\x7f\x8e\x99\x81\xf9\x69\xae\xd6\xd4\x81\xca\xfb\x32\x9f\x99\x53\x43\x54\x1d\x36\x68\x6d\xe6\xcb\x8e\x4b\x1e\x7e\x37\x27\xab\xd5\xc1\xe3\xff\xa6\x93\x6a\xd4\x4b\x92\x60\x63\x56\x15\x12\xc0\xe9\xac\x78\x7f\x8e\xb7\x91\xf9\x63\xf7\x90\xba\x1b\x21\xdf\xe1\xb8\xd3\x1d\x4c\x16\xb1\x52\xa6\xde\x65\xbf\x54\xab\x0f\x0d\x1e\x3d\x45\x03\x17\xb1\xcf\x0c\x4e\x33\x1d\x18\x58\x7a\xcc\xb6\x96\x0c\xcd\x04\xdd", b"\x7f\xc9\xba\xb3\x50\x5a\xdc\xd1\xb1\xc8\x12\x7e\x2d\x1f\xbc\xd0\xe1\x5e\xaa\xc3\x14\x25\x0d\xc1\xc6\x84\xfc\xc4\x7f\xda\x29\x93", b"\x70\xf2\x00\x7e\xdd\x68\xfb\x9d\xfe\x19\xa6\x3e\xee\x4d\x5a\x97\x72\x91\xab\xd2\x35\xed\x26\xe4\x29\x14\x76\xca\x5d\x0c\x81\x71", )?; test( HashAlgorithm::SHA256, b"\xc8\x05\xd1\x8c\x0b\xb5\x3d\x32\xb5\x7c\xb6\x52\xf5\xb0\xe5\x29\x3b\xe4\x92\xa1\xc8\x8d\xfb\xec\x5b\xaf\x47\xee\x09\x3e\x2d\xf0\x69\x18\x99\x4e\x5c\xac\xbc\x3d\xff\xf2\x29\xab\xd3\x1f\xab\x7a\x95\xad\xe2\xfb\x53\xad\xaa\x7d\xff\x51\xf6\xc8\x58\x1c\x69\xeb\x5b\x09\x0b\xae\xc3\x86\x07\xee\x94\x35\x44\x7a\xd8\x13\x74\x55\xb6\xba\x17\x9f\xc5\x3a\xc0\x94\xf9\x7e\x3e\x29\xd0\x72\x4c\xd1\x08\x11\xf1\x42\xd6\x7d\x1c\xfc\xd5\xc3\xd1\xe9\xb4\x11\xda\xc3\x8f\x6e\x1c\x0c\x14\xdc\x9a\x50\xd8\x4b\xcf\x00\xec\xe8\xa6\x03", p, q, g, b"\x2b\x6e\x1a\x8d\x44\x82\xb4\x16\x97\xbb\xbe\x50\xb5\x5b\x3d\xcd\xec\xea\x8d\x2e\x2e\xb5\xcf\x27\xb8\x92\xbc\xbc\xab\xfb\x25\x3c\x19\x48\x6f\xa7\x7c\x98\xc1\x5a\xdd\x41\x49\x92\x5b\x55\x01\xe5\xa5\xef\x45\xb3\x2a\xd0\x9a\x87\x24\x62\xa0\xf4\x1d\x04\x8a\xf4\xe5\x30\x66\x0a\x38\x64\x93\x7b\xa6\xa9\xeb\x07\x34\xe9\x0f\xda\x3c\x9b\x6f\xcd\x30\xc9\x07\x87\x71\x29\x5a\x93\x80\x2d\x9e\x19\x92\xa4\xee\xe9\xaf\x7a\x04\x13\x88\x0f\x33\xbc\x0b\x62\x03\x62\x03\x28\x68\x44\xbc\x38\x41\x87\xec\x51\xa3\x3d\x39\x0e\xaa\xc0\xcc\x33\x28\x09\x8a\x84\x75\x09\x12\x9b\xda\x73\x59\x09\xfc\x7a\x11\x89\x3a\xd0\xec\x61\x27\x6b\x7a\x5d\xcd\x4e\x62\x6d\x9b\xa6\x76\x10\xea\xf0\xaf\x87\x6a\xfc\x04\x19\xfa\x4f\x00\x9a\xa5\xf9\x13\xa1\xc7\x37\x98\xc2\x70\x7e\xeb\x8f\xa7\x7f\x4e\xe0\x58\x22\x9a\x0a\xd3\x7e\x84\x57\x39\x66\x8d\x95\xde\x22\x67\x60\x89\x8c\x02\xd0\x6f\x15\x5f\x82\xdc\x16\x36\x0c\x3a\xbc\xa3\x78\x0b\xcd\xb7\x94\x46\xc8\x34\x35\x83\xdc\x0f\x69\x25\x43\x4b\x0d\xae\x7b\x59\xcb\x26\xb1\x00\x08\xf8\x65\x70\xca\x03\x50\xde\x34\x0b\x27\x55\x24\xf0\x05\x51\x31\x0f\x1d\x09\x5d\xb8\x48\x0b\x4a\xcc\x48\x9c\xf5\xe2\x94\x7e\xb9\x29\x04\xeb\xfd\x0d\x97\x8b\xbf\xb5\xd0\xc6\xa1\xa9\xdb\x50\xcc\x69\x17\x94\x9c\x71\x85\x46\x32\xb4\x40\x8b\xad\xe5\x19\x5d\x40\xdc\xaf\x61\xfe\x95\x0e\xff\x0c\x89\x97\xc3\x74\xf1\xd4\x65\xc8\x0b\xc6\x5a\xdd\xa6\x36\x43\x3e\x94\xf2\x2c\x5f\xbc\xf0\x9e\x99\x66\x6a\x53\x59\x19\xee\x6f\x88\x15\x49\x34\xf1\x13\x77\xa9\xa9\xe0\x21\xf2\xd7\xec\xab\xa3\x25\x10\xe9\x2b\xf5\xad\x67\xfa\x8b\x3d\x70\xdd\x20\x92\xb1\x38\x9e\x31\x93", b"\x38\x20\x8c\x09\x85\x62\x4b\xb9\xd6\x27\x13\xbc\x71\x50\x94\x2c\xbc\x92\xb8\xe8\xa3\x6e\xf6\xd1\xec\x4d\x08\xd1\xd9\xa5\x71\x5f", b"\x65\xd2\xba\x78\x7e\xd4\xc0\x8b\xea\xbf\x24\x34\x3d\x06\xed\x61\x87\x2d\x6d\x68\x4a\x3b\xc7\x03\x07\xfc\xb7\xe2\x0d\xf9\x31\xda", )?; test( HashAlgorithm::SHA256, b"\x9e\x0c\x66\xa4\xf1\x20\xe8\x5a\xea\x06\x4e\x7a\x8b\xa1\x32\xcf\x30\xa4\x5d\xe2\x88\x9f\x35\x47\x38\x4e\x4e\x84\xf4\x5b\x35\x72\xbb\x04\x23\xb8\x34\xde\x9f\x2c\x96\x36\xfa\xff\xdb\x63\x31\x92\x4f\x0d\x2f\x5b\x68\x76\x14\x5d\x9c\xae\x11\x0a\xb0\xcf\x6f\xc9\x0c\x2e\xef\xf9\x8c\x61\xfa\x18\x6c\xc3\x95\x2b\x57\x29\x9a\x73\x67\x8f\x45\x85\xbb\x18\xfb\xb8\x4e\xf4\x16\x67\x79\xff\x10\xee\xd1\x4d\x47\xae\x52\x8e\x03\x29\x8d\xbb\x97\xcf\x4f\x88\xb7\xe6\xd0\x95\x9b\x58\x94\x55\x0a\x3e\x2e\x35\x69\x47\xd2\x5f\xfe\x73", p, q, g, b"\xa6\x2a\xdb\xda\xa5\xa5\x5a\x2d\x1e\x43\x9b\x54\x89\xcd\x6c\x8f\xcb\x23\xe9\xc6\x4f\xbf\xae\x7c\x83\xe9\xd5\x59\x93\x19\xbf\x3f\x06\xc3\xc2\x90\xb9\x89\xa6\x38\x94\x0b\x1d\x0b\x7e\x8b\xf6\x74\x13\x19\xab\x4c\x38\xd4\x6e\x77\xeb\xd4\x94\x5e\x25\xcb\x89\xcb\xb6\x4e\x44\xb9\x47\x4b\xc7\xc9\xd9\xf6\x1a\x36\xe5\x7e\xb6\xaf\xab\x6c\x7a\x14\x9a\xfe\x02\xc1\xcd\x68\x54\x83\x20\x8c\x55\xfe\xec\xb0\xd0\xbd\x96\x69\x7b\x43\x79\x91\x05\x92\x67\xd7\x6a\x48\x84\x65\xfa\xab\x4a\x7e\x17\x59\x23\x29\x56\x70\x05\xfa\xa4\x21\xe0\x11\xd6\x7f\x4d\xa7\x5a\xcc\xb6\x27\x53\x7e\x93\x3e\x9e\xf0\xbe\x3c\x70\xf2\x1e\xd3\xf8\xc3\xb3\xd7\xd7\x69\xbb\x61\x1f\x82\xf2\xba\xa1\x0f\xbc\x73\x13\xad\x08\x19\x04\x8d\x35\x3d\x67\x97\x36\xc4\xd1\x4b\xca\x99\x85\xec\xd3\x70\x41\xaf\xff\xb2\x91\xa7\xd9\x09\xc7\x45\x81\x81\xd0\x15\x92\xe6\xc9\x0c\x0e\x34\xb4\x94\x61\xed\xe6\x6c\x5a\xc0\x02\x67\x1a\x49\x85\x54\x6a\x60\x75\xdf\x95\xb5\x23\xf1\x66\xd2\xe0\xd1\xf5\xda\x77\xba\xff\x5a\x24\xdf\x77\x5c\xc9\xd3\x67\xf2\xa0\x72\x8c\x48\x02\xd7\x97\x04\x17\x88\xc5\x6c\xb8\x71\x29\x03\x32\xc1\x36\x1f\x8d\xa8\x89\x7b\x5b\x8e\x25\xd4\xa9\x35\x94\xac\x64\x8b\xc5\x3c\x9d\x85\xb4\xfc\xdd\x7a\xb0\xf5\xa3\xee\x9c\x25\xcc\x14\xba\x65\x43\xb0\x78\x85\x95\x24\xec\x7f\x0b\x61\xcd\xb2\x09\xcc\x51\xc4\x0a\xa9\xaf\x08\x2e\xa9\xc1\xd4\xb9\x1b\x2c\x1f\x6d\xc1\x1c\xd8\x79\xfb\x38\x65\xd8\x79\xfe\x00\x0f\x0e\x0b\x4b\x23\x3d\xbd\x01\xc9\xc9\x8d\x01\xa6\x64\x74\x65\x77\xa6\x4b\xf2\x8d\x88\x25\x6b\x76\xde\x2b\xab\xf1\x49\x61\x11\x37\x33\xb1\xbb\x55\x53\x25\xc0\x9d\x8e\xc9\x18\x9f\xca", b"\x4e\x35\xf5\x86\xfa\xd4\xf5\x12\x86\x3c\x48\x5e\xc6\x1e\xd0\x16\x29\xaa\x13\x99\xb1\x6f\xef\x4d\x80\xcb\x33\x27\x52\xb1\xda\x92", b"\x26\x2d\xfe\x6a\xc7\x2a\x2f\x60\x44\xf6\x26\x98\xe4\x2d\xd2\xf9\x2b\x1f\x9a\x91\xbe\x42\xb5\xfd\xd2\x93\xb1\xbf\x9a\x14\x5f\x00", )?; test( HashAlgorithm::SHA256, b"\xed\x88\xd7\x07\x6c\x5f\x6a\x5e\x0f\x94\x75\x43\xd5\xfe\x74\x6a\xfc\xa9\xb2\xc4\xd0\x66\x55\xda\x46\x07\x68\x5c\x79\x9c\x21\x0b\xe4\xaa\xee\x0e\x6e\xd1\x97\x13\x81\x41\x82\xc7\xf7\xd5\x84\xdd\xbe\xd4\x88\xc8\xe3\x23\x9d\xdd\x81\x05\x55\xad\x63\x16\xd1\xdb\x37\xfd\x95\x53\xad\x74\xe3\xce\xef\x9e\xee\xfa\xf5\x45\x63\x60\x2f\x55\x47\xaa\xd4\x16\x1e\x93\x84\xed\xab\x65\x5a\x89\x84\x16\xdb\x53\xf7\x12\x37\xac\x5a\x14\x85\x71\x11\x82\xbc\x5b\xff\xf7\x24\x60\x25\x27\x84\xab\x1b\xba\x23\x63\x4a\x36\xbe\x77\x53\x3f", p, q, g, b"\x3e\x1c\xe8\x78\x0f\x39\x44\x4c\x21\x30\xdb\xf9\xd8\x0c\xa4\xb2\x58\x17\xdc\x16\xd0\x8e\x2c\xda\xca\x0b\x56\xcd\x2a\xbd\xb9\xef\x5a\xdb\x74\x1c\xcc\x1a\xbe\xcf\x62\x80\x6a\xd7\xe8\x76\x36\xf5\x28\x31\xc6\xde\xa4\x8e\x07\x29\xb9\x04\xe5\xa0\x61\x5d\x7a\xb4\x45\x01\x04\x20\x8a\x5d\xdf\xdb\x2f\x25\x69\x14\x6e\xe8\x3a\xc9\xaa\x27\xb4\xd0\x66\x35\x5f\xc5\x3d\xc1\xa3\x68\x32\x11\xad\x3e\xfa\xd1\xae\x69\xb8\xa7\x73\x7b\xbd\x89\xf5\xff\x48\x48\x2e\x2c\x56\xed\xaa\x77\x6e\x43\xb2\xa0\xba\x62\xe5\x13\x86\x2d\xa2\x90\x28\x8f\x07\xf8\x4c\xa5\xa0\x68\x37\xd1\x9e\x9b\x18\x6d\xc8\xd3\x69\x52\x96\x6e\x08\xf7\x21\x33\x40\x18\x6d\x31\xfd\x41\xa2\xd1\x45\x5a\x08\x3a\xee\x62\x12\x7a\x28\xdf\xe4\xda\x6c\x87\x6a\x5a\x6f\x36\xc4\x52\x45\xde\xe6\xf6\x56\x6b\x83\x18\xd3\xd0\x19\x43\xb2\xad\xf8\xce\x94\xea\x01\xa0\x1b\xa4\x1a\x6e\x28\x68\x20\xa9\x67\x07\xcb\xd4\x00\x28\x75\xb7\x9d\x9f\xe2\xdb\x6c\xc3\xf8\x08\xef\x0f\x71\x38\x0e\xa9\xa7\x3f\xc7\xe3\x68\x50\xd0\x22\xff\xac\x13\x16\x36\x36\x78\x86\xa6\xe9\x96\x57\x59\xd7\x3f\x03\xac\xe6\x97\x04\xb5\x21\x44\xf6\x7b\x67\x8e\x2f\xa2\x01\xc1\x9b\xb3\x7b\x00\x37\x7d\xaa\xbc\x93\x77\xad\xcb\xdd\xea\x28\x16\xcb\xb5\x0b\x26\xad\x2e\x42\x9e\xa0\x57\x6e\x77\x21\xb3\xb7\x5c\x4f\xed\xb3\x1f\xdf\x1f\x0c\x6c\x2e\xaa\x13\x5f\x52\xc9\xa9\x7f\x0d\xf5\xfb\x25\xef\x28\x84\x8b\xdd\x73\x90\xcd\x05\x40\x03\x72\x25\x82\xd9\x4e\x90\xa3\xbb\xe8\x5b\xeb\x34\x70\x12\x71\xb4\xbb\x48\xbd\xf9\xb3\xd0\xe1\xbb\x56\x23\x44\x5c\x78\x28\xc9\x37\xa4\x23\xbe\x51\x2c\x11\x77\xc9\xc0\xb5\xb0\xb6\xb0\xe1\xf6\x39\xd3\x30\xe0\x51", b"\x2e\x7c\xb4\x04\xa6\xda\xaa\x8e\x00\x76\x0d\xaf\xc9\x5b\x4e\xb5\x54\x56\x83\x22\x4a\x61\xa1\xbc\xd6\x12\x8b\xc4\xe7\xac\x53\x5e", b"\x3a\x70\xb3\xa9\x7e\x06\xe6\x3b\x89\xd5\x6e\xd5\x23\x23\x46\x46\x1c\x1a\x3b\x6b\x14\x5d\x89\x04\x3a\x48\xd6\x66\xde\x02\x56\xd5", )?; test( HashAlgorithm::SHA256, b"\x9e\x44\x00\x52\xed\x92\x73\x21\x94\x83\x88\x77\x6d\x37\x19\xbe\x06\x87\x39\xdc\x2d\x6c\x64\xc5\x93\x71\x76\xb2\x00\x5c\x2d\x70\xa9\x38\x9e\x6a\x65\x56\x63\x36\x6c\x09\x70\xa8\xe2\xe3\x11\x7e\xce\xf2\x57\xe9\x51\xac\x81\xc0\x73\x1d\xfc\xd4\xfb\xdb\x12\x41\xbc\x24\x9a\xdd\xe9\xcb\x39\x8c\x7d\x15\xe3\x81\x36\x8a\xd3\xd2\x4e\xde\xe2\x33\x97\xc1\x5a\x5a\x35\x6e\x78\x7d\x8f\x2f\xe9\xbe\x76\x26\x0b\xd3\x63\xe1\x70\x06\x28\x1c\x19\x9f\xe5\xb7\x10\xf9\xdf\xca\xc5\x28\x95\xe3\x92\xf7\x38\x4d\x71\xbb\x83\x05\x3f\xfc", p, q, g, b"\x89\xe8\x59\xfc\x63\xa2\x63\xbc\xc0\x51\xbc\x2e\xf5\x8c\xc9\x19\xee\x53\x73\x85\xcb\x36\x36\xd8\x3a\x62\x4a\x42\x30\xd4\xb0\x02\x4e\xc5\xe2\x8b\xcb\x88\x46\x67\xcd\x2b\xf8\xc2\x84\x51\xb6\x4d\xe0\x97\xf2\x19\x4c\xbb\x8c\x6e\x1c\xec\xbd\x6f\x9f\xbd\x57\x64\x81\x55\x5d\x0f\x0e\x8f\x13\x75\x2f\x24\x72\xf7\x61\x9d\x05\x23\x18\x42\x43\x10\xf6\x9d\x50\xde\x78\xad\x6c\x45\x7b\x98\xc6\x11\xf8\x48\x1d\x45\x43\x03\x1a\x73\xf8\x3d\x1e\x85\x2c\x1f\x20\x38\xa6\x43\x5e\x57\x1f\x77\x6b\xbb\x5c\xf9\x78\xa9\xb2\xc8\x8f\x05\xd1\x34\xfd\x5f\xf4\x65\x6a\x69\xd6\xfe\x6b\x66\x7d\xa6\xda\x54\xbe\x48\x38\x62\x50\x39\x4c\x75\xb4\x95\x68\x9f\xd4\x62\x8f\x66\x64\x24\xeb\x08\x00\x94\x44\x8d\x41\xb7\x06\x29\x2e\x51\xe7\x53\x86\x54\x3e\x5f\xcc\xe6\xa6\xf3\xaa\xc0\x3a\x7d\x6d\x5c\x25\x51\xca\x6b\x5b\x85\xfa\xdc\x86\xbf\xf1\x4c\x79\xa1\x60\x2f\xb0\xc1\xd4\x3d\x88\xd5\x67\x90\x21\xe8\x26\x06\x2e\xcf\x18\x6a\xaa\xae\xfc\x31\x2e\xab\x9f\x9e\x2d\xa1\x20\xa8\xd7\xd0\x8b\xa0\x9a\xa9\xab\xf4\xe3\x4f\x6d\x88\xc4\xc3\x14\xc5\x9c\x36\xba\x57\xf9\x28\xd8\x8d\x5d\x70\xfe\x48\xac\x67\x00\xf5\xcf\x60\x7a\x55\xe3\x64\x6d\xd0\x3d\x47\xe9\x6a\xd8\x69\xf7\xba\x2b\xcc\x7d\x65\xa9\x9c\x32\x21\xd4\x90\x9d\x1f\x22\xe4\xcc\xba\x81\x5f\xa5\xb7\x20\x57\x0e\x42\xf8\x62\x6c\x31\xd9\x9f\x60\xcd\x6a\x01\x53\x91\xfa\xb3\x53\x74\x46\xf7\x47\xc0\x11\x12\x93\xc5\xbd\x6b\x5d\xab\x2b\xc3\xd5\x13\x7d\x21\x24\x02\x9e\xed\x12\xdb\x71\xbd\xf7\x94\xde\x1a\x2e\xc5\x07\x0d\x83\xf8\x71\x95\x26\x4f\xf0\x9c\xb4\x8c\xdd\xb5\xe8\x52\xb2\x33\x57\x0f\x1b\x70\xcd\x45\x7c\xf8\x64\xe2\xef\x3b", b"\x37\xc3\x4f\x9c\xce\x91\x6d\xf3\xde\xff\x26\xbe\x08\xa4\xe6\xbb\xae\x06\x61\xfb\xbb\x5d\x81\xd6\x03\x9f\x00\xb1\xe5\x63\x2b\x67", b"\x3f\x4a\x29\x32\x91\x7e\x6b\xb0\x88\x59\x9a\x26\x9d\x7b\x59\x07\x69\xac\xf9\x80\x7d\xc5\xa9\x42\x0a\x95\xe1\x2c\x73\x64\xc5\xfa", )?; // [mod = L=3072, N=256, SHA-384] let p = b"\xa4\x10\xd2\x3e\xd9\xad\x99\x64\xd3\xe4\x01\xcb\x93\x17\xa2\x52\x13\xf7\x57\x12\xac\xbc\x5c\x12\x19\x1a\xbf\x3f\x1c\x0e\x72\x3e\x23\x33\xb4\x9e\xb1\xf9\x5b\x0f\x97\x48\xd9\x52\xf0\x4a\x5a\xe3\x58\x85\x9d\x38\x44\x03\xce\x36\x4a\xa3\xf5\x8d\xd9\x76\x99\x09\xb4\x50\x48\x54\x8c\x55\x87\x2a\x6a\xfb\xb3\xb1\x5c\x54\x88\x2f\x96\xc2\x0d\xf1\xb2\xdf\x16\x4f\x0b\xac\x84\x9c\xa1\x7a\xd2\xdf\x63\xab\xd7\x5c\x88\x19\x22\xe7\x9a\x50\x09\xf0\x0b\x7d\x63\x16\x22\xe9\x0e\x7f\xa4\xe9\x80\x61\x85\x75\xe1\xd6\xbd\x1a\x72\xd5\xb6\xa5\x0f\x4f\x6a\x68\xb7\x93\x93\x7c\x4a\xf9\x5f\xc1\x15\x41\x75\x9a\x17\x36\x57\x7d\x94\x48\xb8\x77\x92\xdf\xf0\x72\x32\x41\x55\x12\xe9\x33\x75\x5e\x12\x25\x0d\x46\x6e\x9c\xc8\xdf\x15\x07\x27\xd7\x47\xe5\x1f\xea\x79\x64\x15\x83\x26\xb1\x36\x5d\x58\x0c\xb1\x90\xf4\x51\x82\x91\x59\x82\x21\xfd\xf3\x6c\x63\x05\xc8\xb8\xa8\xed\x05\x66\x3d\xd7\xb0\x06\xe9\x45\xf5\x92\xab\xbe\xca\xe4\x60\xf7\x7c\x71\xb6\xec\x64\x9d\x3f\xd5\x39\x42\x02\xed\x7b\xbb\xd0\x40\xf7\xb8\xfd\x57\xcb\x06\xa9\x9b\xe2\x54\xfa\x25\xd7\x1a\x37\x60\x73\x40\x46\xc2\xa0\xdb\x38\x3e\x02\x39\x79\x13\xae\x67\xce\x65\x87\x0d\x9f\x6c\x6f\x67\xa9\xd0\x04\x97\xbe\x1d\x76\x3b\x21\x93\x7c\xf9\xcb\xf9\xa2\x4e\xf9\x7b\xbc\xaa\x07\x91\x6f\x88\x94\xe5\xb7\xfb\x03\x25\x88\x21\xac\x46\x14\x09\x65\xb2\x3c\x54\x09\xca\x49\x02\x6e\xfb\x2b\xf9\x5b\xce\x02\x5c\x41\x83\xa5\xf6\x59\xbf\x6a\xae\xef\x56\xd7\x93\x3b\xb2\x96\x97\xd7\xd5\x41\x34\x8c\x87\x1f\xa0\x1f\x86\x96\x78\xb2\xe3\x45\x06\xf6\xdc\x0a\x4c\x13\x2b\x68\x9a\x0e\xd2\x7d\xc3\xc8\xd5\x37\x02\xaa\x58\x48\x77"; let q = b"\xab\xc6\x74\x17\x72\x5c\xf2\x8f\xc7\x64\x0d\x5d\xe4\x38\x25\xf4\x16\xeb\xfa\x80\xe1\x91\xc4\x2e\xe8\x86\x30\x33\x38\xf5\x60\x45"; let g = b"\x86\x7d\x5f\xb7\x2f\x59\x36\xd1\xa1\x4e\xd3\xb6\x04\x99\x66\x2f\x31\x24\x68\x6e\xf1\x08\xc5\xb3\xda\x66\x63\xa0\xe8\x61\x97\xec\x2c\xc4\xc9\x46\x01\x93\xa7\x4f\xf1\x60\x28\xac\x94\x41\xb0\xc7\xd2\x7c\x22\x72\xd4\x83\xac\x7c\xd7\x94\xd5\x98\x41\x6c\x4f\xf9\x09\x9a\x61\x67\x9d\x41\x7d\x47\x8c\xe5\xdd\x97\x4b\xf3\x49\xa1\x45\x75\xaf\xe7\x4a\x88\xb1\x2d\xd5\xf6\xd1\xcb\xd3\xf9\x1d\xdd\x59\x7e\xd6\x8e\x79\xeb\xa4\x02\x61\x31\x30\xc2\x24\xb9\x4a\xc2\x87\x14\xa1\xf1\xc5\x52\x47\x5a\x5d\x29\xcf\xcd\xd8\xe0\x8a\x6b\x1d\x65\x66\x1e\x28\xef\x31\x35\x14\xd1\x40\x8f\x5a\xbd\x3e\x06\xeb\xe3\xa7\xd8\x14\xd1\xed\xe3\x16\xbf\x49\x52\x73\xca\x1d\x57\x4f\x42\xb4\x82\xee\xa3\x0d\xb5\x34\x66\xf4\x54\xb5\x1a\x17\x5a\x0b\x89\xb3\xc0\x5d\xda\x00\x6e\x71\x9a\x2e\x63\x71\x66\x90\x80\xd7\x68\xcc\x03\x8c\xdf\xb8\x09\x8e\x9a\xad\x9b\x8d\x83\xd4\xb7\x59\xf4\x3a\xc9\xd2\x2b\x35\x3e\xd8\x8a\x33\x72\x35\x50\x15\x0d\xe0\x36\x1b\x7a\x37\x6f\x37\xb4\x5d\x43\x7f\x71\xcb\x71\x1f\x28\x47\xde\x67\x1a\xd1\x05\x95\x16\xa1\xd4\x57\x55\x22\x4a\x15\xd3\x7b\x4a\xea\xda\x3f\x58\xc6\x9a\x13\x6d\xae\xf0\x63\x6f\xe3\x8e\x37\x52\x06\x4a\xfe\x59\x84\x33\xe8\x00\x89\xfd\xa2\x4b\x14\x4a\x46\x27\x34\xbe\xf8\xf7\x76\x38\x84\x5b\x00\xe5\x9c\xe7\xfa\x4f\x1d\xaf\x48\x7a\x2c\xad\xa1\x1e\xab\xa7\x2b\xb2\x3e\x1d\xf6\xb6\x6a\x18\x3e\xdd\x22\x6c\x44\x02\x72\xdd\x9b\x06\xbe\xc0\xe5\x7f\x1a\x08\x22\xd2\xe0\x02\x12\x06\x4b\x6d\xba\x64\x56\x20\x85\xf5\xa7\x59\x29\xaf\xa5\xfe\x50\x9e\x0b\x78\xe6\x30\xaa\xf1\x2f\x91\xe4\x98\x0c\x9b\x0d\x6f\x7e\x05\x9a\x2e\xa3\xe2\x34\x79\xd9\x30"; test( HashAlgorithm::SHA384, b"\xed\x9a\x64\xd3\x10\x9e\xf8\xa9\x29\x29\x56\xb9\x46\x87\x3c\xa4\xbd\x88\x7c\xe6\x24\xb8\x1b\xe8\x1b\x82\xc6\x9c\x67\xaa\xdd\xf5\x65\x5f\x70\xfe\x47\x68\x11\x4d\xb2\x83\x4c\x71\x78\x7f\x85\x8e\x51\x65\xda\x1a\x7f\xa9\x61\xd8\x55\xad\x7e\x5b\xc4\xb7\xbe\x31\xb9\x7d\xbe\x77\x07\x98\xef\x79\x66\x15\x2b\x14\xb8\x6a\xe3\x56\x25\xa2\x8a\xee\x56\x63\xb9\xef\x30\x67\xcb\xdf\xba\xbd\x87\x19\x7e\x5c\x84\x2d\x30\x92\xeb\x88\xdc\xa5\x7c\x6c\x8a\xd4\xc0\x0a\x19\xdd\xf2\xe1\x96\x7b\x59\xbd\x06\xcc\xae\xf9\x33\xbc\x28\xe7", p, q, g, b"\x1f\x0a\x5c\x75\xe7\x98\x5d\x6e\x70\xe4\xfb\xfd\xa5\x1a\x10\xb9\x25\xf6\xac\xcb\x60\x0d\x7c\x65\x10\xdb\x90\xec\x36\x7b\x93\xbb\x06\x9b\xd2\x86\xe8\xf9\x79\xb2\x2e\xf0\x70\x2f\x71\x7a\x87\x55\xc1\x83\x09\xc8\x7d\xae\x3f\xe8\x2c\xc3\xdc\x8f\x4b\x7a\xa3\xd5\xf3\x87\x6f\x4d\x4b\x3e\xb6\x8b\xfe\x91\x0c\x43\x07\x6d\x6c\xd0\xd3\x9f\xc8\x8d\xde\x78\xf0\x94\x80\xdb\x55\x23\x4e\x6c\x8c\xa5\x9f\xe2\x70\x0e\xfe\xc0\x4f\xee\xe6\xb4\xe8\xee\x24\x13\x72\x18\x58\xbe\x71\x90\xdb\xe9\x05\xf4\x56\xed\xca\xb5\x5b\x2d\xc2\x91\x6d\xc1\xe8\x73\x19\x88\xd9\xef\x8b\x61\x9a\xbc\xf8\x95\x5a\xa9\x60\xef\x02\xb3\xf0\x2a\x8d\xc6\x49\x36\x92\x22\xaf\x50\xf1\x33\x8e\xd2\x8d\x66\x7f\x3f\x10\xca\xe2\xa3\xc2\x8a\x3c\x1d\x08\xdf\x63\x9c\x81\xad\xa1\x3c\x8f\xd1\x98\xc6\xda\xe3\xd6\x2a\x3f\xe9\xf0\x4c\x98\x5c\x65\xf6\x10\xc0\x6c\xb8\xfa\xea\x68\xed\xb8\x0d\xe6\xcf\x07\xa8\xe8\x9c\x00\x21\x81\x85\xa9\x52\xb2\x35\x72\xe3\x4d\xf0\x7c\xe5\xb4\x26\x1e\x5d\xe4\x27\xeb\x50\x3e\xe1\xba\xf5\x99\x2d\xb6\xd4\x38\xb4\x74\x34\xc4\x0c\x22\x65\x7b\xc1\x63\xe7\x95\x3f\xa3\x3e\xff\x39\xdc\x27\x34\x60\x70\x39\xaa\xdd\x6a\xc2\x7e\x43\x67\x13\x10\x41\xf8\x45\xff\xa1\xa1\x3f\x55\x6b\xfb\xa2\x30\x7a\x5c\x78\xf2\xcc\xf1\x12\x98\xc7\x62\xe0\x88\x71\x96\x8e\x48\xdc\x3d\x15\x69\xd0\x99\x65\xcd\x09\xda\x43\xcf\x03\x09\xa1\x6a\xf1\xe2\x0f\xee\x7d\xa3\xdc\x21\xb3\x64\xc4\x61\x5c\xd5\x12\x3f\xa5\xf9\xb2\x3c\xfc\x4f\xfd\x9c\xfd\xce\xa6\x70\x62\x38\x40\xb0\x62\xd4\x64\x8d\x2e\xba\x78\x6a\xd3\xf7\xae\x33\x7a\x42\x84\x32\x4a\xce\x23\x6f\x9f\x71\x74\xfb\xf4\x42\xb9\x90\x43\x00\x2f", b"\x76\x95\x69\x8a\x14\x75\x5d\xb4\x20\x6e\x85\x0b\x4f\x5f\x19\xc5\x40\xb0\x7d\x07\xe0\x8a\xac\x59\x1e\x20\x08\x16\x46\xe6\xee\xdc", b"\x3d\xae\x01\x15\x4e\xcf\xf7\xb1\x90\x07\xa9\x53\xf1\x85\xf0\x66\x3e\xf7\xf2\x53\x7f\x0b\x15\xe0\x4f\xb3\x43\xc9\x61\xf3\x6d\xe2", )?; test( HashAlgorithm::SHA384, b"\x4b\xfd\x28\xa0\xa7\x9c\x94\xdb\xd6\x67\xc2\x75\xef\x77\xa2\x35\xd8\xea\xd7\xc6\x98\xd4\x2f\xb7\xf7\xc1\xfd\x3c\x8c\x2d\xc4\x8d\x0d\xda\x24\x08\xde\xa5\x63\x25\xd6\x92\x83\x69\x2a\x52\x3d\x28\x1f\xfe\xa8\x56\xff\xd9\xf8\x41\x7e\xaf\xbe\xa6\x06\xd8\x62\xdc\x58\x97\xbd\xf2\x41\xf3\xe8\xe4\x9a\xde\xd5\xea\xdc\x72\x95\xe5\xaf\xbf\x96\xb3\x97\x5d\x0e\x25\xda\xa2\x43\x36\x12\xe1\x20\xf6\x59\x03\x6b\x80\x7c\x18\x53\xc0\x3c\x90\xfa\xde\x2c\x19\xdc\xd9\x23\x49\x2e\xcc\x90\x6c\xaf\xc5\x7a\x95\xda\x6f\x20\xdd\x59\xd6", p, q, g, b"\x6c\x77\x8b\xcb\x14\x65\x82\x27\x76\x33\x93\x1b\xfd\x02\x9e\x69\xc9\xe8\xc0\xae\x9e\x24\x91\x3f\xa7\x34\x55\x4f\x24\xf6\x4a\xa6\x4f\xd9\xbc\x60\x8e\xf6\x77\xa1\xd4\x82\x9a\xa8\xa8\x56\x4c\x2f\xf0\xff\xa2\xfa\x6a\x0c\x1a\x2c\xcb\x60\x6d\xda\x01\x8b\xf0\x95\xf8\xc8\x97\xd7\xa4\x33\x49\xbe\xb9\x80\x7b\x7b\x11\x8f\x8d\xe8\x85\x6b\x16\x4b\x8d\x8b\xab\xdc\x17\xb4\x8f\x3a\x2b\x97\x2c\xe5\x37\xab\x4e\x7a\x7d\x9b\xa5\xd7\xe6\xfa\x36\x98\xac\xa9\x19\x73\xcd\x17\x87\xef\x7b\x6b\x4d\x04\x10\xde\x59\xcd\x31\x43\xe0\xf3\xac\xfd\xaa\xbe\x56\xb3\x71\xb4\x35\x4d\x0d\x32\xdb\xd1\xb5\xca\x6a\x87\x20\x54\xf3\xe6\x56\x63\x19\xd5\xd5\x0b\x2c\xf5\x4c\x12\x3f\xfc\x92\x90\x07\xad\x18\x57\xba\x13\xb7\xc4\x03\xf5\x51\xc2\xfa\x41\x09\xc4\x4e\x19\xef\x97\xaf\xb6\x2a\x61\x03\x35\x6f\xcc\x2e\xf4\x51\xe7\x36\x26\x10\x10\xb0\xef\x58\xae\x07\xa0\xc8\x01\xff\x75\xeb\xaf\x6c\xdd\x76\x3f\x8d\xf2\xf8\x3f\x0e\xbb\xda\x40\x84\x5b\x2f\x42\xd3\xfe\xea\xc0\x71\xfc\x62\x6e\xe5\xb5\x1f\x9b\xc1\xa1\x30\x51\x4f\x22\x04\x97\x1b\x4b\x72\x61\xb4\xbd\x78\x3f\xf7\x57\x75\xaa\x73\xa6\x3d\x7e\xbe\x99\x0b\x93\x9b\x0f\x44\xa9\x09\xec\x39\x00\x36\xf2\x97\xc3\x56\x3f\x64\xd1\x42\xc1\x4e\xa4\x3c\x5d\x3c\x6d\xef\x4a\x3a\x9c\xcf\x62\x74\x18\x2b\x93\x9b\x88\x65\x01\xae\xb4\xef\xb2\x3d\x00\x73\x43\x4c\xec\x6a\x91\x5a\x67\xe2\x4c\xbb\x23\x54\xc9\xbb\x10\x89\xaf\x48\x7e\xab\x5d\x8e\x49\x9a\x63\x2e\x6c\x61\x49\x2e\xa1\x5d\x2c\x44\x4c\x26\x9d\xe3\x32\x71\xa9\x00\x42\x46\x8d\xe2\x76\x7f\x0d\xcf\x7a\x66\x42\x4a\x3a\x40\xa6\x3e\xeb\xd1\x9c\xb8\x9c\x8d\x74\xc5\x85\x04\xc4\xe1\x03", b"\x37\xc3\xf7\x55\x6d\x6e\x5a\xcf\x79\x89\xf0\xba\xa7\x70\xc2\x45\x0d\xee\xbd\x4d\x5f\x58\xb6\x1e\x17\xb4\xb2\xb9\x26\xb5\x80\x31", b"\xa6\x1d\x86\x36\x5f\x10\xca\x5e\x1e\xe2\xc4\xbf\x27\x6f\x23\x74\xe8\x8b\x5a\x2d\x1a\xcd\x8e\xcc\x11\xe9\x77\x85\xb4\xfd\x99\x31", )?; test( HashAlgorithm::SHA384, b"\xe3\xfc\x75\x1b\x69\x78\xfc\xf4\x0f\x09\x60\x6e\xe4\x26\x3e\x16\x60\xff\x20\xe9\xc6\x3a\x71\x38\xf0\x78\xae\x3e\x3e\x60\x3d\xfc\xad\x17\x2f\x3c\x7c\xb3\xf3\x54\x5f\xc2\x3b\xc3\x0c\x37\xc8\x43\x9c\x7b\x23\x83\x41\xf2\x91\x48\x27\x6e\xa2\x12\x2e\xa8\xed\x0f\xea\xcb\x14\x9d\xe1\x7c\xfd\x33\xb8\xc9\x40\x8a\xee\x8a\xb0\xea\x8b\xa4\xa2\xb2\xea\x23\x74\x18\xbc\x31\x65\x36\x9c\x8c\xd4\x20\x24\x2f\x8d\x32\xbc\xab\xe0\xc3\x52\xe2\x1f\x65\xde\x80\xd5\x87\xba\x27\x13\xce\xa6\xe5\x3c\xa5\x24\xae\xc3\x65\xbd\xf2\x1a\xdc", p, q, g, b"\x13\x49\xbb\xf1\x6d\x37\x5c\x39\x2a\x9a\xcd\x5b\xdc\xe6\x55\xf1\x4d\x61\x62\x74\x38\x8a\x45\xcd\x37\x29\x25\xc5\x07\xac\x12\x9f\xe6\x1b\x99\x8e\x25\x12\x7f\x21\x09\x26\xad\x11\x91\x58\x3e\xee\x8c\x41\x90\x02\x6b\xa0\xa9\x58\x94\xbe\x3f\x0a\xd5\xd0\x58\x86\xc5\x9a\x3c\x7a\x00\x44\xf7\xe2\xbd\x9b\xbe\x28\xbf\x93\x66\xd0\x34\xdb\x42\x4f\x34\x96\x0e\x30\xa8\xe7\x88\x8f\x92\x7d\x0b\xf9\x84\xb0\xff\x99\xea\x27\x18\x71\x12\x4a\xa1\x2e\x0c\x0e\x19\x62\x4e\x53\x3c\xb4\x14\x9c\xed\xb3\xe1\x1d\x32\x16\x00\xdc\x07\xb3\x2e\x53\x1a\x61\x5c\x8f\x7f\xd7\xf3\x3a\x07\x1c\xaa\xa7\x64\x33\xd1\xaa\xb0\xb7\x10\xfa\x7b\xa3\xdd\xb0\x17\x5c\xed\x4e\x55\x8d\x51\x17\xaf\xc7\x54\x2b\x9b\x07\xa8\xfe\x8e\x4b\x08\xa1\xde\x45\x64\x43\x55\x3f\xe8\x7a\x4c\x24\x55\xde\xd7\x2f\x98\x54\x4d\x6c\x41\xd6\xef\x66\xb7\x14\x2a\x4a\xa9\xaa\x1d\x3d\x20\xf7\x00\x01\x03\x89\xe4\x17\x84\x07\x82\xfa\xd6\x82\x15\x3d\x56\x9f\x94\x4d\x3d\x3a\xd1\xd8\x8d\xb5\xbf\xba\x34\x99\xe4\xc3\x66\x0b\x76\xb4\x4d\xa4\xb0\xe6\x72\x7e\xbc\x3f\x22\xb2\xa0\xaa\xf6\x2d\xc2\xa2\x9d\xb8\xba\xbc\xac\xc2\x16\x9c\x2b\x86\x74\x05\x4c\x89\xfd\x77\x0d\xb9\x8b\x12\xaf\x2d\x93\x3b\xec\xbe\xca\x9f\x22\x44\x4b\x52\x7a\xa8\x94\xb3\x76\x52\x92\xdc\xff\xaf\x34\x08\xe6\x99\x49\x5d\xf7\x9b\x98\xd9\x57\xfd\xba\x7e\x4c\x8e\x7a\xce\x3f\x98\x7a\x95\xdc\xb2\xe7\x77\xfa\x2d\x13\x04\x47\x9a\x6d\x13\x7e\xfc\xb0\xc4\x04\xe6\xd8\xed\x39\xd6\xaf\xba\x25\x49\xf3\xee\x2b\x9a\x45\xf3\x24\x56\x7c\x02\x27\x31\x9d\xc5\x9b\xca\xdf\xcf\xdf\x15\x66\xf3\x56\xf7\xc2\xba\x6d\xb2\x1c\xca\x2a\x8f\xb2\xfb\xea\xf3\x1c\xb7", b"\x2d\x3f\x3c\x60\x5e\xca\x8f\xec\x37\xa7\x6d\x60\x6d\x20\xfd\xe8\x9c\xb6\xf9\x71\xa4\x47\x96\x09\x5a\x01\xdc\xf8\xe9\x00\xf5\xb2", b"\x6a\x43\x16\x83\x34\xe5\xb0\xea\x07\xcf\xa5\x97\x86\x09\xe8\x6f\x96\x9d\x10\x05\x52\x8e\xbb\x3e\xe9\x07\x3d\x56\x55\xd5\x4b\x44", )?; test( HashAlgorithm::SHA384, b"\x45\xf6\x56\xa1\xef\x0e\x61\xde\x46\xdf\x2c\xa2\xd8\xea\x26\x64\x0a\x99\x4c\x30\x38\x0c\x0c\xfd\x66\xbe\x39\x98\xd8\x98\x49\x16\x1b\xbc\xf3\xbe\xe7\x7a\xd3\x0e\x76\x9f\x10\xe2\x3a\xad\x5b\x4d\xf4\xed\xc1\x9a\x86\xfb\xb5\xab\xde\xec\x87\x79\xb7\x6b\xe2\x79\x53\x2d\x76\x92\xbc\x58\x6c\x62\x69\x2f\xa1\xe3\xdb\xcc\xe3\x3f\xfd\xdc\x9f\x97\x58\x91\x72\xf6\x4a\x48\x53\x56\x93\xde\xd6\xbc\x73\xb2\xca\x32\x46\x9d\x0e\xaf\x67\x06\xd2\xa5\xf5\x8f\x8d\x28\xa7\x45\xdc\x32\x8b\xcc\x75\xb3\x41\x5c\xa9\x3e\x29\xea\xbb\x1e", p, q, g, b"\x31\xa9\x89\x60\x1f\x32\xb2\x05\x94\x3a\x84\x18\x87\xdf\x3c\x68\x14\xcf\xb2\x25\x8e\x52\x04\xd0\x4d\x39\x28\xdd\xfa\xba\x0d\xff\xad\x43\x15\x1e\x27\xd6\x66\xd2\x92\x8b\xed\xc6\x72\x75\x44\x0f\xb5\x02\xed\x3e\xaf\xc3\xad\xc1\x10\x09\xee\x70\x3f\x01\xea\xa0\x34\xaa\x72\x4f\xcc\x63\xc5\x9a\x8a\x59\x63\xf3\x35\x2f\x72\x93\xea\x24\x25\xea\x89\xbb\xf1\xe4\x17\x24\xb6\x9f\x38\x3b\xf1\x0a\x97\x31\x46\xed\x02\xf5\x52\x08\xb0\x48\x33\xd1\xbb\x53\x99\xa6\x7f\x04\x08\x15\x90\xac\xfc\xfb\xb1\x21\x05\x42\x3e\x26\x09\x1d\x09\x07\x8c\x45\x00\x7d\x43\x6e\xb1\x9f\x95\x2f\x87\x98\xb0\x01\xa3\xc6\x4a\x3b\xaa\x54\x96\xc9\xdb\xe6\x58\x07\x81\xd4\x02\x0b\xb7\xe4\xe7\xae\x23\x80\xce\x79\x65\x8c\x10\xa2\xe5\x7b\xbb\x8c\xac\x12\x08\x77\x28\xce\x43\xba\x2b\x9f\x38\x0e\x3a\xbc\x2d\xd1\x2a\x68\x24\x88\xc6\xb4\xfb\x2f\x8d\xd7\xf3\x84\x6b\x6a\x26\xf9\x13\xac\x15\x68\x79\xee\x6a\x1a\xe0\xad\xa9\x56\x85\x21\xa4\x42\x8e\xd9\xf7\x41\xe0\xe7\x9a\x84\x28\x80\x01\x9c\x01\xb3\x4e\x98\x8a\x7c\xf7\xe6\x35\x24\xe8\xcd\x02\x54\x53\x22\x3a\x26\x60\x27\x3e\x49\x19\x68\xaf\x7f\x4b\x1d\xc2\x12\x39\x61\xde\x37\x53\xab\x16\xec\xa5\xb1\x85\x9a\x4f\x71\x17\x25\x38\xf0\x5a\x2a\x82\xa3\x4f\x98\xba\x07\xc1\xe5\x31\xd8\x2e\xf5\x92\xe5\x49\x35\x33\x41\x6b\xd6\xc6\xa4\xc7\xca\x3b\x0d\x2a\x2f\xff\x88\xa8\xf0\x73\xa7\x6c\x69\x18\x02\xaa\xae\xce\x4e\x85\x2d\x66\x50\x87\x1a\x17\xcc\xa0\xf5\x25\x1e\xf2\x2d\xfc\x8e\x3b\x26\x1b\xfc\xbd\x5a\x22\xb2\x73\x2a\xa1\x7d\x7d\xf1\xf7\xb8\x2f\x6b\x22\x2e\x5f\x60\x65\xbf\x80\xd0\x4c\x2e\x57\x74\x09\x40\x84\xe4\xd5\xce\x0d\x3e\x89\x17", b"\x3c\xed\x0e\xa5\xf7\xfd\x58\x86\x68\xa4\x1e\xfe\x0e\x90\x95\x4c\x09\x30\xaf\xb6\xbe\x18\xd9\x07\x52\x83\x1f\x68\x3c\xd9\x2a\x9c", b"\x9e\x46\xca\x12\x94\x17\x45\xea\x1a\x12\xc5\xa2\xd6\x09\x88\x4c\xb5\x79\x2f\x46\xaf\xaa\xcf\xf0\x72\x37\x13\x74\x00\x36\x68\x68", )?; test( HashAlgorithm::SHA384, b"\xc7\x37\xd5\xae\x24\x8a\x96\x06\x2d\x6a\xfa\x8d\xca\xcc\x03\x84\xc5\xfb\xfb\x9d\x8b\x60\x52\xb5\x24\x93\xc6\x0d\x3e\xdf\xc5\x24\xb5\x67\xb1\xf8\x96\xe7\x44\x7d\x0e\x24\x01\x94\x03\xed\x83\xe4\x88\x9c\x0c\x4d\xe5\x7c\x70\xfa\xda\x6c\x8b\x5a\x09\x90\x43\x50\xa4\x4d\xfa\xf7\x7d\x60\xaf\x62\xde\x3e\xdf\xd8\x76\x0d\x07\x74\x73\xf2\x6d\xf2\x83\x7c\xfc\x20\x15\xf2\x27\xdd\x7d\x35\x1a\x53\x50\xf1\x42\x8f\x26\x99\xfd\x3f\x51\x83\x26\xfe\xa8\xae\xf9\x8f\xc4\xea\x67\x31\x30\xc8\x07\x9f\xac\x38\x95\xfe\x85\x6c\x77\xf8", p, q, g, b"\x61\x12\xd3\xcd\x31\x91\xd1\x7d\xee\x77\x88\xf5\x68\x81\x5a\x0a\xab\x50\x00\x60\x02\xc9\xde\x2b\xd1\xa9\xbb\xa2\x45\xba\x02\x89\x4b\x02\xe9\x24\x75\x17\xac\xe6\x98\xae\x0a\x05\x17\x6b\x62\xb3\xa0\x25\xa5\x63\xdd\xa8\xde\xb7\xf2\xfc\x3e\x17\x7a\xe3\x47\x74\x48\xd3\x9a\xe4\xeb\xe7\xae\x8e\xc6\x5a\x44\x21\xf7\x54\x66\x7f\xd6\xd7\xc2\xeb\x93\xf1\xa1\x8d\x3d\x1a\x62\x35\x73\x6b\xcd\xb7\x47\x46\xf4\x6d\x88\xe6\x5d\xc0\x7c\x25\x91\xe1\xf9\x5d\xda\x5e\x5e\x20\xe1\x05\xee\x8b\x4d\xdc\xaa\xf3\x60\x21\x29\x0d\x6b\x64\x93\x67\x1d\x8a\xaf\xae\x14\x5d\x9b\x90\xbe\xc3\xcc\x60\x17\x9b\xb8\xfc\x30\xf1\x43\xc5\x75\xd5\xd8\x61\x62\x37\x21\xb6\x54\x7d\x3a\xaa\xad\xe4\x55\xf0\x5f\xef\x93\x18\xab\xcd\x29\xbd\x19\xb1\x2c\x35\xca\x75\x6d\xe5\x10\x8c\x18\x5e\xce\x4a\xa1\xbf\x1a\x8e\x38\x80\x97\x97\x06\x7b\xd1\xf5\x2b\x6c\xf2\xc4\x15\xe7\x3f\x92\x46\xbd\x5b\xfa\xdd\x7b\x9a\x9d\x2b\x53\x69\x70\x1e\x72\x14\x7e\x22\xda\x7e\x09\x2d\x9b\x57\x8f\xb0\xc0\x44\xa3\x6e\xff\xcb\xd7\x09\x25\x85\x00\xa0\x0c\xff\x23\x09\x62\xc4\x42\x25\x71\x2f\xc4\x3f\x9e\x80\x2b\xae\xad\x7f\x9c\xb4\x6a\xb4\x93\x1f\x66\x3c\x6e\x3e\xd4\x08\x2d\x59\x61\x0f\x01\x74\x1b\x5f\x24\x56\x6b\x01\xb3\xe3\x93\x3b\x29\xe0\x28\xc5\x4b\xd2\xfc\x75\xb5\x49\xfd\x05\xe6\x4c\x58\xc9\xae\x0b\xa4\x17\xa9\xe9\x85\x81\xdb\x77\xbe\x75\x23\x3a\x42\xf7\x71\xc9\x9f\x0a\x49\xb4\x94\xf0\x95\x52\x02\xb1\x9d\x6c\x74\x0e\x86\x60\x66\x10\x4e\x46\x3e\x65\xe4\xba\xd9\xa0\x81\x63\x6d\x05\x36\x74\x26\x15\x3f\x04\xbc\xb2\x71\x21\x86\xdc\xa6\x83\x43\x88\xe8\x25\x20\xd3\x4e\xfd\x8a\x89\x31\x3b\x2c\x7e\x60", b"\x00\x41\xb1\xc7\x56\xdd\x2e\x42\x71\x4f\x9e\xe7\xed\xce\x21\xea\x33\xef\x49\xdb\xf4\x52\xcc\xd9\x35\x7d\x5f\x45\xff\xab\x08\xf9", b"\x10\x2c\x6e\xaa\xd3\x8d\x39\xc0\xd0\x36\x33\x5a\xe1\x9d\xd0\xd7\x5e\x8d\xca\xba\xe5\x9b\x12\x0f\x69\xcb\xd2\xb5\xcf\x48\xab\xdb", )?; test( HashAlgorithm::SHA384, b"\xa6\xfc\x89\xa2\x23\x02\x2e\xe9\xe5\x08\x72\x52\x78\x58\x2f\x56\xdb\x9c\xd2\x4c\x0d\x75\xd0\x72\xa5\x28\xd0\xc6\x0f\x27\x17\x1e\xa3\x76\xe2\xdc\x28\xa9\xdc\x0b\x12\xe6\x68\xaf\x77\xdc\xbb\x38\x17\x37\xe1\xba\x7d\x9e\x80\xb9\xbe\xc8\x0b\xf9\x06\x1b\x8f\xa1\x0e\x43\xa7\x40\x3a\x29\x16\x24\xa6\x00\xdd\x4f\x5c\x2b\x50\xc5\x2d\x5c\x61\x55\xd5\x2b\xe5\xa3\x25\xf6\xad\x81\x3f\xb3\xec\xaf\x6d\x1f\x92\xe9\x8c\xc8\x7c\x26\xc6\x8c\xbd\x15\xd5\x48\xa3\x78\x2b\xff\xdd\x11\x16\xc7\xc1\x1f\xca\xbd\xe4\x02\x5f\xec\x51\x54", p, q, g, b"\x6c\x1d\x4d\x6b\x52\xaa\x4b\xff\x35\xf4\x30\x23\x30\x05\x27\x77\xf5\x1f\x6a\x08\x49\x16\x1f\x90\x6e\xf2\x17\xb0\x4b\x18\x54\x5c\xe5\x2a\xe4\xae\x42\x3a\xd1\xb4\xf8\xb1\x73\x5a\xe0\x0a\xb0\xc0\x44\xa5\x6f\x94\x5d\xa8\x4d\x1c\xdc\x26\xe0\x82\xd7\xac\xd7\x72\xdf\xab\xcd\x18\xb5\xe1\x3c\x05\xc2\x79\x1a\x8d\xc1\x61\x46\xe1\x51\x32\x3e\x4e\xf2\xce\x5d\x64\x38\x9f\x69\xd9\x34\x7a\xa2\xa5\xbd\x01\x14\xde\x0e\xec\xdf\x99\x0a\x44\x0d\x1b\xf9\x89\x0d\xd9\x5f\xd6\x40\xd2\xfb\x17\x89\xca\x6a\x6d\xbe\xe1\x83\x6a\xd7\xcb\x47\x37\x0b\x74\x56\xe4\x9f\x3b\xac\x03\x31\x0f\x8c\xbe\x61\xdd\x1c\xc0\x6d\x78\xc7\x6f\xec\x63\x97\xe6\x08\xa4\xca\xc4\xe2\xc3\x89\x83\xce\x5a\xa9\xdc\xba\x07\x4a\x20\x6f\xa6\x08\xdb\x35\xf2\xad\x3d\x63\xd9\x5b\x2c\xb7\xa0\x1c\x33\xd4\x98\x76\x7e\x8e\x68\x57\x8e\x4e\x99\x53\x8b\xf3\xd7\x03\xe6\x38\x63\xa2\x50\x91\x45\x2e\x73\xb9\x6a\x37\x16\xe9\xcc\x10\x9b\x66\x00\x8f\xa5\xca\xfd\xbf\x96\xb7\xfc\x10\xc3\xbb\x89\xd7\x9d\x45\xff\xef\xc0\x19\x08\xd2\x47\xef\x1d\x4f\xcb\x90\x3b\xf5\xe7\x91\x7a\xf8\x86\x18\xa5\x2a\x12\x00\x47\x98\x89\x05\x40\xa5\xa7\x5c\x65\xfb\xc0\x57\xd8\x60\xf4\xb6\x5d\x8b\x08\xb8\xd2\x15\xf0\x56\xd8\xe5\xe3\x8b\xf0\xb3\x19\xe2\x94\xdb\x24\x2a\x4f\xc7\x9b\x2e\x10\x6f\xec\xa2\x55\x6d\x14\x6f\x52\x03\xfd\x72\xad\xc7\x3a\x48\xe3\xa5\xaa\xdb\xb2\x93\xa2\xef\x58\x62\x65\x4c\x31\x53\x9a\xd8\x56\xa1\x6e\x57\x16\xc4\x37\xb4\x74\xf3\x33\x9c\xd8\x4f\x0a\xc9\x2b\xc2\xca\x6f\xac\x10\xc7\x51\xd0\x99\xa9\x04\x08\xde\xf6\x10\x6c\xa8\x38\x93\xd8\x7e\x32\x81\x8d\x76\x34\x53\x7a\x4e\xf6\x67\xce\x7f\x26\xa5\xcb", b"\x48\xbd\x01\x0c\x1a\xf7\x7b\x3c\x40\xdb\x50\x34\x97\x06\xd6\x4d\x16\xcb\xb7\x2d\xb5\x19\x43\xd3\x45\x15\x1d\xea\xcd\x4a\x41\x33", b"\x0f\x1c\x4b\xdb\x47\x58\xab\x3b\x55\x18\xd4\x60\x5b\x98\x64\x80\x57\x23\xd3\x3a\x36\x11\x6e\xa6\x50\x54\x6f\xee\xf1\x1c\x4a\x5e", )?; test( HashAlgorithm::SHA384, b"\x2a\xe4\xac\x7c\xe2\x9a\xe7\xd3\x24\x90\xd3\xa5\x4b\x71\x5d\xb3\xf4\x73\x06\xf8\x4b\x59\xb3\x3b\x21\x62\x2a\x18\xaa\x2c\x06\x0a\x44\x34\xad\xfa\x01\xff\x16\x86\xb5\xd1\xdd\x30\x35\x30\x8e\x92\xf7\xac\xc7\x6d\xea\x96\x9d\xee\xfb\x98\xc2\x97\x2b\x42\xa5\x96\xe1\x05\x5a\x5a\xa2\xc6\x61\xf0\xb7\x34\xba\x4f\x0b\x34\x1c\x77\x82\x7d\x88\x91\x5a\x5e\x89\xf9\x5a\x98\xd6\x3d\x77\x29\x87\x4f\xce\x4f\xf7\x5d\x7a\xdd\x74\xf4\x31\x3d\xff\x78\x4e\x41\x7b\x2e\xe1\xfc\xd2\x70\xc0\x38\xdb\xbb\xb9\x6a\x77\x68\x48\x4b\x88\x54", p, q, g, b"\x0a\x84\x29\x8f\x47\x68\xe9\xd7\xbf\x79\x6d\x06\x58\x5e\x8b\x75\xfb\xde\x65\x83\x98\xa2\x24\xa8\xac\x3a\x49\xfb\x91\x23\x5e\xaa\xa1\x83\xaa\x88\x27\xcc\x2a\xf7\x9e\xa3\x34\xdc\x8b\xe4\xcc\x72\x90\x29\xab\x5f\x81\x61\xf7\x18\xf7\xbf\xbe\x90\xad\x2a\x15\x98\x88\x52\x39\x82\xb6\xd4\x93\x2d\x81\x59\x49\x5b\xa8\x4d\x0a\xb3\x5d\x7e\x39\x5d\x14\xdb\xa9\x06\xa1\x67\x9a\xe3\xcb\xb7\x2c\x10\xed\x6f\xa1\x4d\xa4\xd6\x00\x77\xb0\xbf\xb5\x91\xa3\xde\xc6\x43\x99\x6c\x39\x63\x38\xa5\x1d\x44\x6b\xde\x62\x24\xae\xa1\x6a\xef\x41\xf3\x54\xe0\x9a\x9d\xce\x9f\x3a\x00\xcb\x44\x5a\x5c\x9c\xae\x4a\x6c\x3c\x19\x19\xc9\xe0\xc5\x30\x82\x17\x3d\x0e\xc0\x0a\xe5\xe1\x5a\xa7\x26\x07\x50\xb6\xa0\x3e\xf0\x5a\x51\x8a\x48\x61\x53\x40\xac\x20\x98\x40\x73\xce\xa5\xfc\x99\x0d\x48\x98\x58\x94\x9a\xaf\x6e\x9e\x34\x7b\x48\x02\xaf\xbe\x25\xa0\x66\x94\x72\xbd\x93\x16\xba\x2c\x23\xa6\x1c\xc3\xaa\xdf\x1b\x70\xd9\xfd\x97\x61\xbb\x03\x5f\x0c\xa5\x1e\xdb\x2b\x12\xfc\xfd\x65\x1c\xb9\x23\x63\xef\x48\x00\x5a\x26\x83\xfd\x2e\xd8\x66\x5d\x70\x58\x8f\xd9\xa1\xbe\x3a\xa5\x1c\x95\x8b\x81\xf1\x3e\x4a\xcf\xaf\x0d\x2a\x90\xaa\xae\xf2\x1b\x2c\xc9\xef\x2e\xd3\x7b\xce\x3c\x47\xc8\xbc\xbf\xc1\xfb\x9f\x94\xe4\x9b\xd2\xf1\xa3\x0a\x88\xdf\x22\x73\x5a\x0f\xdf\x0a\xc6\x02\x8a\x00\x8b\x06\x2c\x95\x60\xc4\x2a\x47\x69\x97\xdd\x21\x10\x06\x92\xef\x63\x96\xd5\xf3\xfb\x2c\x15\x53\x28\x25\x7e\x7b\x7d\x2b\xc0\x5f\xab\xd5\x4a\x81\xa2\x27\x29\x93\xd3\x42\xbe\xc8\x57\x7c\x64\xd5\x1b\x4c\xdb\xe3\x65\x4d\xae\x56\x8c\x4d\xa0\x18\x61\x8c\x30\x47\xae\xe0\x6b\xf2\x62\x1e\x05\x6b\x33\x5d\x04\x4b", b"\x6b\x7e\xd3\xa4\xc2\xa4\xf7\x85\x00\xc7\xe9\x47\xe6\x17\x5c\x5c\xa8\x57\xc9\xd6\x13\xe7\x79\x0b\x9b\xe0\xd1\x4e\xc8\x40\x3e\x5f", b"\xa1\x16\xf3\xde\x16\x62\x60\xd1\x10\xe2\x0e\x84\xeb\x8c\x97\xc3\xf0\x18\x17\x86\x08\xa2\xea\x3e\x3e\x2f\x5e\xd9\x1d\x43\xde\x11", )?; test( HashAlgorithm::SHA384, b"\x3e\xad\xe9\xa1\x0f\xb5\x9a\xf3\x6a\x54\x01\x70\x73\x7f\xbc\x53\x6e\x4c\x52\x30\xb8\xf6\xc4\xb2\x16\xed\xdd\x3e\xa9\x23\x42\x12\x3a\x33\x74\xd0\xc7\x51\xb2\x4b\x62\x7f\x9e\xad\x4d\xe2\x6e\x9a\x78\x97\xd9\xbc\x5d\x58\xa6\xa3\xac\x74\xcd\x45\x75\xb3\x28\x6e\xc1\x5f\x84\x53\x22\x4f\x37\x17\x9e\x51\xd9\xc4\xad\x8a\x60\xbf\x37\xd7\x1c\x62\xad\x7f\xc5\x3e\x5c\x7b\x12\xf4\xaa\xa2\xd4\x28\xe5\xc8\x89\xfd\x7f\x06\x2c\x91\x3d\x9b\x57\x4f\x4b\x5d\xb5\x16\xc9\x76\xba\xd5\x88\x30\x2f\x21\x9f\xd8\x3e\x18\xbe\xe8\xe6\x8e", p, q, g, b"\x08\xa1\x5b\x23\x84\xdf\xf4\xf3\x03\x3c\x87\x16\x86\x73\xc5\x67\x05\x98\x70\xc8\xe7\x8d\x2f\xdd\xc7\x54\x0a\xfd\xa8\x05\x8d\xf3\x84\xd3\x18\x2a\x42\x61\x54\x32\xff\x93\x77\x7d\x3f\xce\x49\xc1\x17\xc7\xbb\xe8\x21\xe6\x78\x9b\x51\x37\xdd\xf0\x84\x65\x60\x98\xaa\x7b\x05\x16\xfd\x30\xa4\x2c\x8c\x86\xd9\x4e\x6b\x26\x8b\x6e\x13\x01\x1d\x25\xeb\xa0\x18\xca\x40\xcf\x8a\x35\xe1\x96\x31\x35\xd5\xcd\x65\xa5\x7a\xca\x8b\x00\x79\x88\xa5\xea\x75\xad\xb4\xd0\x1c\xc0\xf0\x83\x8a\xb4\x2d\x3d\xf6\x43\xa7\xd2\x56\x1c\xfd\x1f\xde\xbe\x3a\xd8\x6a\xd0\x3d\xe3\x17\x02\x75\x33\xd5\x23\x35\x1b\xe5\x32\xbc\x73\x1a\xaf\x43\xb8\x64\x2a\x7d\xa8\x08\x73\xb8\x0d\xc6\x1b\x7a\x24\x9e\x58\x60\xfd\x1a\x3e\xae\x0f\x8f\x0c\xf2\x1e\x20\x5d\x6f\x40\x3c\xb0\xa1\x03\x29\x0c\x9e\x69\xd3\x8c\xbe\xd9\xe0\x92\xb6\x9f\x71\xf9\x17\x2b\x36\x76\xf2\x9a\x97\x13\x3f\xc3\xe1\x87\x46\xfe\xdc\x65\x3f\xbf\xb6\x2c\x5e\x0a\xfe\x89\xa8\xe1\xb8\x72\x4b\x1a\x33\x14\xc4\xca\xcc\x4b\xb8\xf3\x90\x43\x97\x01\xa6\x14\xae\x9b\xcd\xaf\xd4\x72\xb0\xab\x13\x16\x67\xdb\xbf\x1c\x79\x0f\x73\xab\x90\x46\xa5\x89\x32\x69\x1a\x93\x0b\x3c\x42\xe9\x08\xb4\xd1\xf4\x7e\xd6\xe2\xff\x18\xd6\xb7\x0b\xb1\x6d\x1a\xf7\x99\x3b\xdb\x2c\xa3\xcb\x35\x9a\x0b\x43\xf8\xdc\x84\x4d\xea\x6a\xeb\xaa\x34\xb8\xd2\xb6\xfc\x28\x84\x19\x78\x0f\xf9\x80\x90\x89\x26\xc4\x6c\x3b\x0e\x59\x5f\xa3\x08\xf4\xe8\x94\xec\xb6\x83\xc8\x04\xc9\x31\x40\xd9\x17\x69\x13\x2d\x37\xe9\x37\x91\xb9\xf8\x9d\x59\x5e\x69\x8f\x04\x9b\x3a\x95\x02\xab\xc4\x88\xbd\xd9\x47\x2f\x11\x31\xa7\x57\xf3\xd5\x4b\x14\x90\x67\x50\x7d\x1b\x04\xa9\x76", b"\x9e\x83\x3e\xc3\xde\xd9\xd8\x1e\xa7\x42\x2b\xda\xc7\x84\x22\x27\x4f\xa3\x53\x48\xe3\xfc\xe3\xbb\xc9\x3b\x3c\x10\xd7\x0b\x4f\x1e", b"\x65\x37\x56\x59\x4e\xac\x68\x1d\x48\xa2\x35\x8a\x0f\x82\xa1\x0f\xaa\x79\x29\xb0\x0f\xd9\xcd\x43\x94\xc3\x26\x79\x06\x0f\x96\xe3", )?; test( HashAlgorithm::SHA384, b"\x33\xde\xcf\xc1\xe0\x6b\x92\xed\x81\xcd\x30\xee\x37\x71\x47\x0b\x59\xe2\x2c\x15\x64\x64\x7f\x1a\xae\x85\x10\x72\x97\x15\xa8\xce\x94\x62\x4a\x11\x55\x4a\xc9\x09\xc9\x24\xae\xc8\x53\xdf\x64\x32\x75\x46\xdb\x85\xd3\xdf\x59\x79\x16\xa3\x93\x53\x38\x8a\x8b\x33\x63\x76\x52\x81\xa4\x35\x27\x01\xff\x1a\xf4\x3f\xba\x6d\x03\x66\x41\x27\xc1\x5d\xa7\xb8\x4c\x04\xd5\x40\x9c\x36\x40\x94\xdc\x62\xe3\x79\x83\xa8\xeb\x06\x68\x80\xde\x81\x36\x70\x14\x06\xe6\x72\x50\x67\x93\x00\xd2\xb9\x7d\x22\x83\x27\xc1\x51\x4c\x0b\xc1\xea", p, q, g, b"\x16\xea\x2e\x79\x5c\x63\x6c\x9d\x31\x21\x59\xa5\x79\xb8\xdf\x32\x9f\xfc\x28\xfe\xcc\x4a\x4c\x13\xb1\x6a\x29\x0b\xd1\x52\x5a\x53\xa9\x7d\x72\x31\x5b\xe2\x51\xd1\x1d\x23\xca\x78\xbb\xec\x45\xc0\xe2\x43\x27\x9b\x1e\xb6\xe2\x06\xa9\x27\x3c\x1e\x76\x6e\x21\x36\x48\xbd\xf9\x0c\x40\x47\x9d\xf4\x8a\xcf\xd9\xc2\x09\xa5\x23\xc8\xb4\xa9\x9a\x48\x1c\xa8\xdf\x47\x74\xb3\xbb\x29\xf8\x25\x26\x52\x0c\x2d\xc2\x8a\xb3\x14\xfe\x14\x14\x0f\x2b\xe1\x79\x2e\x1a\xc3\xc7\x59\xad\x44\xf7\x84\x5a\x20\x12\xf6\x4e\xca\xb0\xb1\xfe\xc0\xed\x16\x6b\xd1\x75\x95\x57\x04\xf6\x2d\x94\x01\x11\x1f\xfc\x04\xf8\x04\xe4\x8f\xe7\x74\xdf\xd3\x46\xbb\x41\xf4\xbe\xca\x2b\x34\xa8\x31\x34\xa3\x88\x4a\x01\x72\x9c\xce\x1a\xbc\x5b\x8d\x0d\xe3\xfe\x26\x54\xc3\x74\xde\xb2\x46\xd9\x6f\xfa\xff\xc7\xaa\x20\x55\xb7\x4e\x81\x9b\xbe\xec\x13\x7e\xb3\xca\xed\x1f\xc7\x1f\x12\x9c\x8e\xa8\xb7\x63\xf2\xf5\x7e\x88\xde\x08\x45\xf7\x6c\xeb\x18\x41\x55\x90\x19\x87\x2a\x5b\x5a\x96\x9c\x9c\xf3\x85\xd6\x57\x8b\x4f\x27\xb5\xb7\x6b\xe3\xef\x0a\x8f\xd3\xee\x47\xee\xd6\x95\xe1\x6f\x14\xe2\xa3\xb7\x91\xf2\xa0\x16\xd6\xb8\x6f\xf8\xec\x23\x43\xc6\xa5\xc8\x0a\xb6\x22\x4b\x65\x02\xeb\x37\x4c\x8f\xa6\x51\x0b\xce\x99\x0d\x70\xef\xdf\xa9\xa0\xb7\x02\x58\x55\x95\x18\x45\x14\xc7\x8f\x7e\x90\x5b\x6f\xd6\xc2\x37\x33\x3d\x56\x0f\xcc\x06\x30\x36\x37\xac\x0b\x2c\x7f\x7c\x4d\xa5\x59\xe3\x1f\x53\x1d\xf2\xe5\xd6\xc6\x51\x59\x17\x71\xd7\xea\x45\x75\x88\x8a\xfc\x40\x11\xfa\x11\x24\xfb\xd1\xa2\x82\xa4\x1d\x93\x39\x89\xef\xf9\x1a\x51\xcd\x39\xbc\xe7\xfb\x0d\x56\x9f\xed\xcc\x42\xde\x48\xbf\x18\xee\x75\x5f", b"\x6f\x77\xa5\x21\x69\xa2\xe8\x80\xa3\xb5\x5a\xa2\x78\xf6\x46\x30\x32\xdc\x5f\x81\xc3\x84\x68\x22\x4d\x55\x32\xf6\xa6\x01\xf2\xd9", b"\x96\xb7\x53\xef\xb4\xab\xbc\x8c\x17\x9d\x03\xcc\x2a\x1a\x0c\x12\x56\xe2\x3d\x1f\xa2\xe9\x7c\xfb\xf5\x5d\x2b\xb6\x98\x12\xd1\x00", )?; test( HashAlgorithm::SHA384, b"\x6a\xe5\xa6\xda\x79\x4f\x92\x3f\x6d\x80\x32\x54\x9b\x81\xd0\x4a\xe7\xaa\x35\xc2\x09\x9d\xff\xbd\xd8\x3b\xb9\x4d\xb5\x74\xfa\xf8\xf9\x5c\x71\x26\xdb\x2d\xb6\x0f\xed\x50\xf7\x40\xe8\x7c\x35\x95\x44\xdc\x2e\xbf\xbc\xaf\xb0\x94\xdd\xca\x69\xc9\x14\xd2\x7e\x5f\x3d\x10\xfa\x0c\xe3\x2d\x2a\x13\x55\xbc\xf6\x1a\x25\x74\xc7\x55\xd7\xc3\x24\xa2\xe0\xed\x6f\x77\x19\xba\x2f\x2c\x9f\x11\x3d\xf8\xd0\x40\x25\xf4\xab\xd2\xe1\xc4\xb7\xbc\x18\xd8\xac\xec\x9f\x6d\x8d\x79\x7c\xd7\xb0\x42\xf5\x03\x48\xee\xb3\xf7\xa2\x92\x2d\xa7", p, q, g, b"\x93\x10\x6f\xb0\x00\xc6\x7f\x11\x11\xc6\xfd\x3d\xa0\xf4\x4b\x4a\xe4\xcb\x36\x95\xde\x2e\x35\xb2\x41\xdf\xe8\x8d\x32\x69\xb8\xfd\xa2\x5b\xf3\x48\x00\x87\x25\xfd\x61\x3c\xd6\x1a\xa8\x26\xbd\x8f\x1a\xaa\xee\x22\xb4\xdc\x0a\x02\x84\x22\x90\xbb\x7d\xad\x91\xaf\x0b\x28\x54\xff\xab\x16\x93\x22\x08\xd2\x72\xf2\xc0\x81\xc1\x38\x89\xdb\x3e\xd0\xb2\x46\x46\xc6\x65\xaf\x9f\x4b\x72\x38\x98\xeb\x1a\xc0\x05\x3f\x2a\x0f\x4c\xf2\x2f\xd4\xe1\x29\x40\xb5\xb5\x22\x69\x48\x4e\xbb\x8a\xbc\x48\x4c\x06\xed\xdb\xd9\xb1\xa4\x26\x13\x2f\x40\x2e\xfd\xcd\x88\xab\x29\xe7\xe5\x10\x96\x1a\xf8\xec\x83\xa6\x42\xe3\x40\x15\x85\x8a\xc3\xf3\x21\x97\x60\x1a\x88\x8e\x16\xc7\x59\xc9\x4e\xc5\xb8\xde\xc0\xda\x30\x64\x3b\x9d\x9d\xb2\x57\x4a\xf2\x9e\x78\xf9\xd3\xf6\xa7\xb4\xc7\x6f\x45\xcd\x0b\x2a\xb5\xe8\x52\x49\x35\xb8\x86\x91\x8b\x5d\x9e\x9c\xcb\x5a\x68\x53\xe6\x2e\xfa\xd2\xdf\xf8\x3a\x85\x20\x98\x5e\xe8\x44\x2f\x2b\xdd\x1c\x5f\x9d\x48\x06\x2a\xde\x6b\x28\x8c\x8a\xd8\x2a\x41\xdb\x6c\x34\xe2\xde\xba\x54\x1a\xaa\xc3\xcd\x31\x56\xc9\x75\xef\xbb\xc7\x18\xeb\xd4\x96\x19\x96\xb3\xed\x1c\xc5\xc2\x98\x7a\xb7\x79\x05\x2c\xdb\xec\xf5\x1d\x17\x66\x1b\x49\x8e\x84\x37\x1f\xf8\x59\xf8\x99\x06\xf4\x26\xf5\x63\x57\x2f\x66\xc2\x79\xef\x3d\x03\x6a\x42\x77\x78\x46\x3f\x67\xf8\xd4\xde\x62\x3f\xb4\xb2\x80\x30\x07\x87\x1d\x0a\x34\x9e\xc2\x02\xa9\xaa\x1c\xff\xef\x70\x13\x7e\x00\x93\x03\x49\x72\x14\xad\xa7\x86\x35\x7a\x4d\x80\x46\x25\x5e\x40\xf8\x9e\xa5\x88\x00\x06\x34\xe7\xf0\xaa\xf6\x4d\x92\xaa\x21\xff\xf8\xfb\xe0\x78\xba\xa9\x69\x61\x69\x97\x38\xb2\x68\x23\x7e\xab\x60\x6c", b"\x86\x36\xd4\xd3\x20\x3a\xa0\x91\x2f\xbf\xc9\x38\xbe\x43\x70\x07\x7e\xa9\xc7\x51\x95\xcd\x2f\x67\xe6\xee\x42\x7c\xde\x53\x1c\x40", b"\x93\x02\x3d\x97\xef\xb4\x32\x7e\x9e\x88\x6e\x7b\x78\x37\x41\xe9\xd2\xc3\x97\xaf\x9c\x67\xb9\x1c\xdb\x8a\xa2\x7f\x83\xbb\x02\x5d", )?; test( HashAlgorithm::SHA384, b"\x86\xe0\x3b\xc3\xf4\xdd\xea\x6a\x93\x88\x8e\xe3\x89\xb1\x5e\xb6\x90\x82\x2c\x71\xf9\xb8\x5e\xfa\xaf\xfc\x52\xe4\x86\xb1\x14\x4a\xd7\xfc\xff\x3f\x53\xbf\x97\xda\x24\x81\xe8\x5e\x09\x83\xee\x1d\x52\x79\xe2\x7a\x36\x4d\x0e\x69\x0f\x58\x7a\x31\x53\x5f\xb9\x4e\xec\xe7\x47\xf8\xb6\x05\x72\x4a\xdf\xb2\x58\xc9\x98\x3c\x90\x02\xe0\xc1\x1b\x79\x76\x62\x76\x90\xd5\x82\x81\x30\x5e\xa9\x30\x8d\xb7\x4c\x49\x1a\x28\x19\x2e\x35\x4b\x60\x0e\x83\x76\x81\x1c\xce\xfb\x75\x1b\xb1\x0c\x7d\x97\xb4\x2f\xfe\x30\x4b\xee\x97\xec\xaf", p, q, g, b"\x23\xed\x54\x45\x39\x1a\x5b\xb9\x4e\x00\xc7\x6e\xc8\x0d\x83\x72\x8d\x5d\x46\x1b\xe4\x25\xda\x79\xf9\x21\xbc\xa2\x7d\x62\x5c\xb4\x2b\x32\x39\x71\x02\x2a\xd4\xc3\xf0\x5b\xca\x10\x99\x10\xfd\x06\xba\x39\xe9\x5b\xeb\xe7\x94\xed\x10\x8d\x2e\xad\x29\x7a\xd7\x94\xf9\x9c\x32\xc2\x19\xe6\x5f\xb7\x26\x53\x27\x15\xb1\xbc\x20\x75\xdd\x4b\x69\x49\x29\x77\x12\xf9\x1d\x5b\xa0\x61\x19\x6f\xb2\x57\x54\xc3\x43\x77\xbb\xbe\x6a\x37\xf6\x17\x87\xea\x84\x4d\x35\x92\x85\xc7\x8e\x73\x3e\xb6\x5f\x66\x5a\x6b\x15\x7f\x83\x2b\x56\x38\xd7\x4e\xbe\x1d\x5d\xce\x66\xd5\x28\x92\x5e\x44\xee\xf1\x3b\xf2\x3f\x80\x7d\xa3\x5f\x34\xd1\x69\xa6\x87\x75\x82\x29\xb9\x9a\x31\x3a\xce\xcf\xb2\x0b\x14\x2b\x53\x49\x26\xd5\x9a\xaa\x76\x43\xa7\x90\x30\xe9\x33\x5e\xf2\x8a\xbe\xdd\xac\x8a\xc9\x47\x1d\xa4\x99\x7e\x33\xf3\xe4\x91\xdb\x86\x68\xa2\xc3\x92\x0a\x3b\x3a\x37\x22\x51\x79\x36\x1d\x55\x39\xbe\xb3\x3f\x32\x52\x24\x42\x67\x46\x5e\x48\xfa\xf5\x75\xcd\xac\x93\x81\x33\xef\xfe\x9d\x1f\x69\xf1\x9f\x1b\x44\xb2\x45\xa4\x47\xb1\xfc\x2b\x85\x92\x44\xe2\xe3\x90\x53\x59\x5c\xf7\x97\x89\x33\xc3\xd4\x68\xc6\x5c\x23\x16\x63\x07\x0a\xea\xf2\xec\x23\x13\x8d\x16\x60\x08\x1a\x55\xbd\xc3\xdd\x3f\x24\x46\x17\x6b\x1d\x6d\x99\x77\xa1\x4e\xbd\x0e\xd4\xd8\xdf\xcd\xfc\x4a\x43\x31\x18\x40\x1f\x2c\x26\x32\x09\x5c\xe7\xae\x62\x00\xc7\x4b\xda\x5d\x2f\xd3\x85\x45\x24\xc3\x08\x17\x41\x97\x5a\x07\x6a\x1b\x4f\x93\x3e\xc3\x2a\x2b\xac\x91\x71\xbe\xbf\xdf\x3b\x35\x5e\xdd\xb1\xf4\x55\xec\xaf\x73\x39\x6e\x85\xfb\x04\x79\x75\x58\xba\x4f\x2b\xbc\x49\xd9\xf2\x32\x9a\x23\xb3\x93\x30\x1a\xe0\xdb\x92", b"\x68\xef\xaa\x05\xeb\x90\xc4\x8c\x6a\x7a\x45\x33\x7c\x29\x17\x5f\x8e\xe5\xb1\x9b\x53\xdb\x4e\xbd\x83\xa0\x2f\x53\xc5\xb2\x10\x4b", b"\x14\x5f\x13\xf1\xae\x36\x75\xc5\x21\xb3\x34\xce\x6a\x49\xfc\x6f\x50\x2e\x3a\xc6\xb2\xb5\x14\x3b\xe0\x64\x1d\x0d\x57\xb3\xc7\x22", )?; test( HashAlgorithm::SHA384, b"\x1d\x09\x54\xee\x0d\xe1\xe9\xce\xee\x05\x32\x59\x7e\xe4\x34\xc7\x3f\xe4\xf6\x66\x35\xf7\xe7\x2d\x38\xb6\x77\x63\xc6\x68\x17\xf5\x3c\xf3\x6c\xa0\xf6\x13\xe0\x18\x96\xce\xbc\x9f\x77\xa7\x72\x60\x7f\x4a\xee\xdd\x38\x56\xc7\x3f\xc2\xf1\x91\x00\xaa\x7b\x54\x0c\xcd\x05\x7f\x26\xcd\x95\x64\xd6\x73\x22\x8c\x68\x08\x8e\x5f\x1a\xbf\x12\x54\xa9\x7e\xd1\x45\x3e\xe5\x58\xe0\x62\x71\x1c\xeb\x76\x43\xb3\x45\xad\x33\xb6\x49\xaf\xfb\xe8\xa6\x20\x67\xf9\xd8\x4e\xd4\xc8\x50\x6f\xcf\xf5\x78\xd2\xeb\xa5\x96\xa2\x05\x26\x73\x87", p, q, g, b"\x2f\x0d\x89\xac\x78\xa6\x1f\xb7\x4f\x81\x14\x2b\x17\x76\x66\x56\xd1\x78\x89\x40\x07\x78\x08\xe3\xd8\x80\xce\x10\xec\x60\xe2\xbb\xb1\x58\xd5\x4e\x02\x0d\xbc\x5f\x67\x86\xc0\xb4\x3c\xca\x2c\xb0\x02\xc8\xce\x13\xb2\x91\xb2\x50\xf3\x99\xe8\xe0\x2f\x19\x59\x26\x97\x8f\x6c\x5b\x00\x7d\x4f\x0a\x66\x04\x89\x96\xa9\x93\x2a\x91\x8b\x23\x63\xc4\x00\x8f\x54\x7a\xdc\xaa\x7d\x12\x69\x4b\xae\xe4\xfb\xca\x34\xbc\x6d\x7e\x29\xc5\x04\x9c\xda\x13\x69\x8f\xcc\xe6\x1b\xd3\xb3\xdb\x05\xd2\x15\x81\x32\xdd\x38\x0c\xf6\x53\xcc\xcd\xf2\x79\xaa\x16\x41\x34\xbf\xbd\xdd\x7e\xa3\x47\x76\x00\x41\xf9\x2c\x3a\x4c\xfd\xe0\x09\x2d\x5c\xb9\x6b\xb8\xc2\x4e\x98\x25\x94\x75\x59\x6f\x33\x77\xd5\x9f\x11\x66\x1b\xcc\x0d\x47\xe8\x3c\xb3\x1a\xae\x9d\xcb\x4a\x6f\x25\x61\x9a\x29\x05\x4b\x62\xaa\x8b\x42\x1e\x52\x9e\x61\xac\x95\xa0\xde\x01\xc5\x0b\x09\xe1\x19\x51\x6c\x2c\x5b\x35\x63\xd4\x7e\xed\x67\x9a\x1c\xf8\x0b\xa7\x0a\x50\x25\x4d\x85\x1a\x13\xa7\x78\xe1\xa0\x8d\xa8\x66\x7e\x46\xe3\x59\x79\xc1\x5d\xf4\x5c\xf7\x88\x6d\xde\x5a\xf9\xd7\x44\x62\x4b\x98\x1a\xcd\x25\x2e\xc5\xba\x46\x87\x0b\x8e\xe4\xb3\x2b\x1b\xe1\xb9\x44\x80\x2d\x91\xd8\x14\x8d\x38\xf5\x43\x15\xa7\xad\x4e\x38\x07\x9e\xa2\xbe\xd9\xdf\x8f\xa5\x94\x14\xdd\xde\xd3\xa1\xd2\x30\x8b\xa7\x69\xae\x2a\x65\x2f\x10\xc2\xd9\x69\x17\xed\xfe\x58\x74\x88\x5f\x3c\x99\xd6\x91\x2f\x69\xae\x3f\xc3\xb4\xde\x82\xde\xcc\x30\xed\xc9\x31\x4f\x7e\xc9\xe5\x67\xb7\xe0\x0d\xe2\x19\x59\x48\x6a\x88\x7d\x74\xa5\xb2\x18\x02\x93\xdf\x5d\xbe\xae\x1e\x35\xa6\xe9\x37\xb2\x50\x6d\x20\x50\x92\xcc\x4c\x35\x95\xdb\x92\xfc\x25\x5a\xf5", b"\xa6\x72\x10\x34\x1a\x04\xcd\x3a\x4b\x63\xeb\xc7\xe6\x20\x8f\x37\xe4\x87\xa8\xc6\xf1\x13\x4c\xd2\x60\x1b\x84\x4d\x69\x03\x20\x3f", b"\x6b\x97\x2c\x62\x2c\xab\x48\xd8\x5a\x2d\xde\x35\x5f\x94\x7a\x81\x51\xa1\x7a\x0a\xcf\x06\xb7\xf3\x65\x9f\x86\x8d\x5e\xce\x92\xd9", )?; test( HashAlgorithm::SHA384, b"\x14\xf5\x66\xc5\xfe\x44\xaa\xad\x6e\x8b\x3c\x62\x75\x70\xaa\xbd\xd4\xef\xb7\xfc\xfa\x1a\xb1\xbb\x74\xf2\xc6\xd8\x79\x5e\x88\x23\x3d\xac\x4e\x7d\x24\x0a\xbd\x5e\x9b\xbd\x8e\x1f\xb0\x3a\x3b\xf5\x0c\x0c\xa9\x2c\x9a\xef\x18\x94\xf2\xae\xd6\x00\xfc\x58\x73\xd2\x34\x51\xd3\x20\x4d\x75\xab\x95\x81\xcb\xcf\x82\xae\x8c\x0d\xf0\xdf\xbd\x3a\x1f\x14\x9f\x70\x66\x08\x65\x72\x6c\xdc\x73\xc0\x15\xd5\xdd\xbf\x75\x13\xee\xdc\xd1\xef\x17\x57\x8d\x27\x19\xfe\xa1\xe5\xba\x39\xae\xf3\xfa\x6f\x00\x84\x6f\x0f\xb8\xd9\xa1\xa4\x36", p, q, g, b"\xa3\x6a\x33\x39\x00\x03\x5d\x34\x53\x13\x9b\x28\x35\x6b\xf0\x12\x4e\x57\x1f\x55\xa5\xe4\x25\x9b\x8b\x2e\xe1\x45\x7c\xc3\x58\x80\x56\xd6\xc6\xa6\x45\xd4\x22\xca\xc7\x24\x74\xc5\x90\x1d\x0a\x7f\x41\x0d\xf7\xf9\xb4\xe2\x2f\x86\x84\x86\x7d\x93\x32\xe2\xd4\x26\x6a\x6e\x59\x5e\x51\x5b\xec\xff\x7f\xb9\x4d\x21\xa8\xa9\xad\x72\x11\x57\x2e\x44\xce\x84\x48\x31\x7b\x34\xc3\xc0\xb8\x9b\x30\x97\xab\x2e\xc1\x34\xec\x7c\x17\x8c\x22\x78\x30\x9c\xf9\x15\x2b\x22\x3b\xb9\x37\xe6\x86\x82\xf1\xf6\x80\xc1\x7e\xe5\x9e\xcd\x06\x98\xa0\x5c\x24\xc1\x35\xd2\xb0\x23\x8e\x71\xf8\x07\xe0\x79\xf1\x75\xe1\x16\x71\x30\x8f\x5b\xd9\xe5\xa6\x97\x12\xa9\xc5\x08\xb3\xb5\x09\x25\xd1\x27\x6d\x55\x2b\xda\x51\xce\xf3\xbd\x0f\xbd\x00\xa9\xd2\xdd\xdf\x0e\x5e\xcb\x6b\x32\x83\x78\xea\x63\x7b\x49\x38\x46\x48\x0e\xd7\x5a\x31\x52\xd9\xe6\xa4\x88\x4e\xeb\xad\x12\xb0\x7c\xad\x8d\x10\x1b\x3d\x00\x1b\xc9\x9f\xb1\xee\xe4\xe9\x8f\xd6\xfc\x92\x0c\xb5\x76\x5e\xc2\x4e\x62\xab\xd3\x2f\x97\x5a\x47\xd5\x0f\x61\x55\x3e\x1c\x14\x77\x51\x93\xb5\x3b\x05\xb7\xd0\x20\x24\xaa\xce\x81\x8a\xb6\x59\xd7\x17\xd1\x1d\xea\xcc\x98\x77\xb8\x18\xa5\x16\x89\xd2\x39\xb6\x0f\x7f\x9e\xd4\xca\xf7\x32\x5a\xc0\xb3\x1b\x31\x6c\x03\x65\x99\xea\x66\x95\x9d\x52\x5f\xd1\x6f\x5c\x1a\x2a\x80\x9f\x28\x66\xee\x9e\x99\xf6\xd8\xa3\xc4\x2b\x58\xd3\x3d\x0e\x5d\x38\x05\x5c\x55\xc7\xbc\xcd\xef\x31\x0c\xcd\x34\x26\x20\x7d\xbb\xc6\x0f\xaf\x9f\x2a\x21\x9a\xb3\x67\xce\x84\x62\x3b\x81\x10\x48\x22\xe2\xc7\x7e\xc5\xb1\x33\xce\x70\x50\xca\xed\x09\x09\x46\xc1\xf1\x35\x5d\x87\x8a\x13\x17\xde\x69\x4e\x68\x6c\x62\xff\xdf", b"\x12\xb4\x0b\xd1\xc8\x66\xce\x38\xe7\xda\x07\x64\xd8\x07\xae\x82\x51\x2b\x33\xb5\x1d\xc9\x08\xe5\xa5\xb3\xd7\xc1\x6f\x0d\x08\xa5", b"\x5c\xac\xce\xe2\xbc\x85\xe2\x8d\x50\x6a\x9b\xc6\xd2\x60\xdb\xd0\x82\x05\xb7\x5d\x20\x69\x0e\x26\xaa\x6b\xed\x30\xd7\x32\x70\x99", )?; test( HashAlgorithm::SHA384, b"\x60\xc2\x9d\x99\x75\x3d\x08\x47\xbb\x52\xe9\x06\xc8\x62\xa1\xb0\x62\x84\x96\x41\x6c\x14\xdf\x5d\xcf\xbb\x5e\x28\x04\xf5\x02\xcb\x0a\x2d\x16\x3e\x9b\xc2\xd8\x41\x22\xc0\xb3\xf5\xd0\x60\x9b\x82\xac\x16\xaa\x15\xef\xd5\x5f\x55\xc8\xca\xa3\xd1\x11\x4a\xc0\xcb\x83\xe5\xff\x3d\xb1\x2a\x24\xb8\x9a\xca\x5f\x05\x14\xd2\xce\xb0\x9b\x14\xfa\x91\x60\x00\xc0\xf4\xde\xb0\x16\xdb\x75\x5e\x88\xb3\x26\x17\x21\x44\xe4\xf1\xa7\x05\xa8\x00\x55\x9b\x3d\xa3\xc2\x7a\xf5\x5c\xb3\x2b\x11\x47\x46\x0c\x31\x18\x6d\x99\xdc\x1c\xf2\xe5", p, q, g, b"\xa3\x73\x97\xe6\xea\xfb\xdc\xf1\xe0\x15\x8f\x1f\x4e\xa1\xcb\x3a\x1e\xbd\x73\x9c\x85\x59\xa5\x00\xde\xf3\xb7\x55\x17\x99\xd6\x52\xb3\x27\x10\x1c\xfe\xa0\xb8\x70\x16\xdb\x59\x15\x22\xb9\xb3\x4e\xd2\x67\x13\x2c\x52\x55\xe7\x76\x53\xc4\xeb\x93\x5c\xe0\xc8\x22\xb4\xb1\x0a\x5e\x8f\x3c\xce\x39\xad\x1b\x96\x06\xde\x5b\xe2\xb2\xd3\x6e\x1c\x54\x11\xf0\x6a\xba\x04\x61\xea\x8d\xc4\x8b\x64\x9f\x10\x8e\xba\x88\xde\xf4\x4d\xaa\x2a\x5c\x65\x3d\xcc\xf1\xd8\xae\x29\x20\x5d\xd5\xc3\x40\xe3\x4b\x7b\xd6\x98\xec\xcd\xcd\x34\x5b\xd4\xaa\x5e\xee\x3c\x08\xb9\x16\x2c\xa1\x80\x48\x72\xde\x3c\x57\x5d\x57\x2f\x34\xdd\x48\xb4\x1f\x82\x35\xd0\xf5\x11\xc8\xdc\x65\xda\xeb\x07\x09\x5c\x3b\x5d\xbd\x3a\x07\x6f\x8e\xb2\x44\x12\xf3\x62\x1f\x49\x21\x26\x73\x7a\x9d\x73\x01\x4d\xef\xa5\xf5\xd5\x7b\xdc\x6f\xaf\x53\x14\x2e\xb1\x91\x60\x6f\x2f\xd3\xdc\x03\x5f\x4b\x8a\xe8\x4d\x65\x5c\xb6\xda\xaa\xf8\x89\x00\x5c\x3c\x33\x4f\xfd\x7e\x3b\x04\x98\xfa\xe2\xa6\xf8\xdc\x1b\xc6\x2f\x37\x04\xc8\xf8\xc0\x05\xc8\x01\x9e\x0b\xf4\x5b\x7a\xa8\xe0\x80\x3b\x93\xa9\x92\x67\x5e\x38\x1f\x61\xa8\x98\x58\x29\x50\xb9\xce\x40\xe7\xcd\xb0\x30\x0f\x4b\x26\xf9\xb4\x44\x84\xe8\x9c\x92\x34\x17\x9b\x60\xa3\x72\xfe\x94\x76\xf8\x4d\xe0\xed\x4b\x93\x49\x72\x16\xfb\x96\xba\xe4\x32\x97\xdc\xdc\x84\x96\xc6\x34\x10\x0c\xf0\x66\x40\x2c\x7d\x29\x0a\x7c\xd2\x8c\xbc\xf8\xb0\x8a\xd4\xc1\x36\xdb\x2f\xe9\x92\xff\xa0\x45\xbf\x8c\xb2\x49\x23\x4f\x29\xa6\x74\x76\x2a\x56\xd2\x08\x97\xea\x55\x38\xc6\x74\xa1\x43\x53\xdb\x64\xba\x60\xfe\x40\x52\xa0\x52\x8e\xb0\xb2\x58\x87\xe3\xc5\xea\x69\xb4\x1f\x68\xb3", b"\x72\xcf\x0e\x18\xe4\xbc\x37\x49\x64\x7c\xdf\xa6\x2d\xcb\xd2\x51\x3c\x7c\x2b\x1d\x39\x7c\x1f\xcb\xc7\xf6\xa4\x25\xeb\xb8\x97\xce", b"\x7b\x7d\x0a\x9e\x93\x34\x09\x41\xbb\x55\xf6\xaf\xa6\xcd\x63\xf7\x36\x49\x63\x67\x10\x08\xed\xe4\x57\xd0\x5b\x65\x45\xfa\xb1\xf1", )?; test( HashAlgorithm::SHA384, b"\xb3\xde\xa6\x2a\x20\xa9\xed\x9d\xa9\x90\x46\x5b\xeb\xe1\x8a\xa7\x1f\x08\xf9\x3f\xba\xee\x4f\xe5\xd5\x81\xff\xaa\x6f\xd5\x5c\xbe\x27\x2a\x11\x5d\x7f\xa1\x8f\xb9\xcf\x56\x62\xf5\x95\xb7\xcb\x9b\xdb\x97\xa8\x1b\xdc\x07\x8e\xe3\xbd\xce\xb2\xc0\x37\x22\x61\x01\x34\xc3\xbb\xfd\x7a\x6f\x8b\x79\xec\xc6\xa9\xa7\x70\x92\x65\x68\x7f\x9c\x23\x6f\xc6\x8b\x02\x20\x3b\xa6\x66\xe9\xec\xed\x51\x61\xde\x90\xc1\x10\xee\x7a\xf9\xbf\x54\xd8\x4a\x22\x18\x1f\x17\xb4\x32\x93\x48\xbd\xee\xfb\xb3\x24\x96\x2e\x63\x56\x9f\x07\xc2\x85", p, q, g, b"\x45\x01\x33\x18\xb9\x41\xa7\x10\xb8\xab\x10\x10\xd8\x18\xc3\x10\x36\x34\x65\x8d\x2e\x3e\x2f\x41\x31\x65\x86\x08\x05\xe0\x8d\x5c\x1e\x80\xad\xd9\x96\x9a\x3d\x3a\x0d\x23\x43\x2c\x8a\x48\xcc\xe8\x36\xb2\x4a\x41\x08\x92\x09\x9b\xbf\x53\xcc\x5a\x84\xa9\x5e\x1e\xb3\xb6\x82\xd2\x75\x4e\x72\x1e\xfc\x86\xd3\xf4\x24\x8b\xaa\x33\x7d\x6f\x6e\x5d\xac\x47\x59\xb2\x96\x16\x59\x18\xa7\x1b\x31\xce\xd2\x5b\xf1\xb0\x5d\x67\x5b\xfa\x22\x29\x80\x60\x8f\xda\x8f\x9d\x0e\xba\x9a\xa0\x84\x75\x51\x2d\x04\xc6\x12\x13\x3c\x88\x25\x3b\xf3\xe2\x7e\x9f\xfe\x3a\x85\x70\xbe\x20\x4f\x54\xbf\x8f\xf1\xc7\xfe\x42\xae\xce\x83\x20\x50\xaa\xbd\xd9\x41\x57\x64\xb8\xc8\x72\x69\x7f\x9c\x8e\x78\xe2\xf5\x6b\xd2\x35\xeb\xbb\xb4\xb9\xcf\x8f\x05\x4b\x60\x29\x29\x63\x76\x45\x36\xd6\xfd\x4c\x6c\xfa\xa1\xba\xea\x53\x54\x6c\x6f\xfb\x56\xa0\x4f\xbf\xae\xe0\x01\x22\x82\x80\xae\xc4\x0e\x66\xd9\xdc\x19\x2f\x9b\xa7\x43\xbd\x3f\xfc\x0e\xaf\x27\x7b\x6b\xa3\xd3\x3c\x36\x97\x02\x48\x92\xb0\xb3\x54\x19\x53\x48\x73\xfb\x7a\x3d\x59\x4d\xd6\xae\x07\x51\xa2\xfa\x43\x0b\xa4\x62\x37\xf4\xa5\x5e\x4a\x67\x80\x72\xc6\x51\xfe\x6a\xd3\x14\xa0\x10\xfd\xfe\x8f\x8b\x53\x42\xbd\xab\xe9\xae\x59\x10\xc6\xf4\x4a\x51\xf4\x75\x24\xa6\xfe\x82\x16\x83\x0c\xca\xed\xed\x26\xce\x1f\x13\xf7\xf2\x16\xe0\xb7\x80\x9e\x92\x72\x56\x3c\xab\x33\x52\xb8\xed\x76\x66\x50\x22\x7b\xfe\x16\xe9\x81\xb5\x05\x60\x9c\x41\xf0\x3d\xca\x98\xe2\x19\xd0\x2a\xa7\xd9\x19\x21\xed\xb3\xa8\x92\x29\xe7\x8c\x30\x16\x1c\xc1\x39\x73\xb3\x5d\xe3\xc8\x77\x79\x37\x8b\x8d\x60\x7a\x19\x32\x04\x05\x66\x13\x12\x43\x2d\xd8\xd0\x7a\xf2", b"\x3e\xc6\x77\xe9\x1c\x63\xe6\x5a\xaa\x17\x4a\xee\x27\x91\xdc\x40\x92\x44\xcb\x80\xc0\x22\x09\x91\xdc\xb4\x97\x39\x7a\x3c\x5e\x9b", b"\x1d\xe0\xec\x46\x6b\x2a\xd4\xed\x1a\xdc\xe3\xbc\x38\xee\x52\x18\x03\xdc\x87\x08\x5e\x2f\xbf\xc5\x61\xd6\x38\x44\xc1\xa9\xa2\xe6", )?; // [mod = L=3072, N=256, SHA-512] let p = b"\xc1\xd0\xa6\xd0\xb5\xed\x61\x5d\xee\x76\xac\x5a\x60\xdd\x35\xec\xb0\x00\xa2\x02\x06\x30\x18\xb1\xba\x0a\x06\xfe\x7a\x00\xf7\x65\xdb\x1c\x59\xa6\x80\xce\xcf\xe3\xad\x41\x47\x5b\xad\xb5\xad\x50\xb6\x14\x7e\x25\x96\xb8\x8d\x34\x65\x60\x52\xac\xa7\x94\x86\xea\x6f\x6e\xc9\x0b\x23\xe3\x63\xf3\xab\x8c\xdc\x8b\x93\xb6\x2a\x07\x0e\x02\x68\x8e\xa8\x77\x84\x3a\x46\x85\xc2\xba\x6d\xb1\x11\xe9\xad\xdb\xd7\xca\x4b\xce\x65\xbb\x10\xc9\xce\xb6\x9b\xf8\x06\xe2\xeb\xd7\xe5\x4e\xde\xb7\xf9\x96\xa6\x5c\x90\x7b\x50\xef\xdf\x8e\x57\x5b\xae\x46\x2a\x21\x9c\x30\x2f\xef\x2a\xe8\x1d\x73\xce\xe7\x52\x74\x62\x5b\x5f\xc2\x9c\x6d\x60\xc0\x57\xed\x9e\x7b\x0d\x46\xad\x2f\x57\xfe\x01\xf8\x23\x23\x0f\x31\x42\x27\x22\x31\x9c\xe0\xab\xf1\xf1\x41\xf3\x26\xc0\x0f\xbc\x2b\xe4\xcd\xb8\x94\x4b\x6f\xd0\x50\xbd\x30\x0b\xdb\x1c\x5f\x4d\xa7\x25\x37\xe5\x53\xe0\x1d\x51\x23\x9c\x4d\x46\x18\x60\xf1\xfb\x4f\xd8\xfa\x79\xf5\xd5\x26\x3f\xf6\x2f\xed\x70\x08\xe2\xe0\xa2\xd3\x6b\xf7\xb9\x06\x2d\x0d\x75\xdb\x22\x6c\x34\x64\xb6\x7b\xa2\x41\x01\xb0\x85\xf2\xc6\x70\xc0\xf8\x7a\xe5\x30\xd9\x8e\xe6\x0c\x54\x72\xf4\xaa\x15\xfb\x25\x04\x1e\x19\x10\x63\x54\xda\x06\xbc\x2b\x1d\x32\x2d\x40\xed\x97\xb2\x1f\xd1\xcd\xad\x30\x25\xc6\x9d\xa6\xce\x9c\x7d\xdf\x3d\xcf\x1e\xa4\xd5\x65\x77\xbf\xde\xc2\x30\x71\xc1\xf0\x5e\xe4\x07\x7b\x53\x91\xe9\xa4\x04\xea\xff\xe1\x2d\x1e\xa6\x2d\x06\xac\xd6\xbf\x19\xe9\x1a\x15\x8d\x20\x66\xb4\xcd\x20\xe4\xc4\xe5\x2f\xfb\x1d\x52\x04\xcd\x02\x2b\xc7\x10\x8f\x2c\x79\x9f\xb4\x68\x86\x6e\xf1\xcb\x09\xbc\xe0\x9d\xfd\x49\xe4\x74\x0f\xf8\x14\x04\x97\xbe\x61"; let q = b"\xbf\x65\x44\x1c\x98\x7b\x77\x37\x38\x5e\xad\xec\x15\x8d\xd0\x16\x14\xda\x6f\x15\x38\x62\x48\xe5\x9f\x3c\xdd\xbe\xfc\x8e\x9d\xd1"; let g = b"\xc0\x2a\xc8\x53\x75\xfa\xb8\x0b\xa2\xa7\x84\xb9\x4e\x4d\x14\x5b\x3b\xe0\xf9\x20\x90\xeb\xa1\x7b\xd1\x23\x58\xcf\x3e\x03\xf4\x37\x95\x84\xf8\x74\x22\x52\xf7\x6b\x1e\xde\x3f\xc3\x72\x81\x42\x0e\x74\xa9\x63\xe4\xc0\x88\x79\x6f\xf2\xba\xb8\xdb\x6e\x9a\x45\x30\xfc\x67\xd5\x1f\x88\xb9\x05\xab\x43\x99\x5a\xab\x46\x36\x4c\xb4\x0c\x12\x56\xf0\x46\x6f\x3d\xbc\xe3\x62\x03\xef\x22\x8b\x35\xe9\x02\x47\xe9\x5e\x51\x15\xe8\x31\xb1\x26\xb6\x28\xee\x98\x4f\x34\x99\x11\xd3\x0f\xfb\x9d\x61\x3b\x50\xa8\x4d\xfa\x1f\x04\x2b\xa5\x36\xb8\x2d\x51\x01\xe7\x11\xc6\x29\xf9\xf2\x09\x6d\xc8\x34\xde\xec\x63\xb7\x0f\x2a\x23\x15\xa6\xd2\x73\x23\xb9\x95\xaa\x20\xd3\xd0\x73\x70\x75\x18\x6f\x50\x49\xaf\x6f\x51\x2a\x0c\x38\xa9\xda\x06\x81\x7f\x4b\x61\x9b\x94\x52\x0e\xdf\xac\x85\xc4\xa6\xe2\xe1\x86\x22\x5c\x95\xa0\x4e\xc3\xc3\x42\x2b\x8d\xeb\x28\x4e\x98\xd2\x4b\x31\x46\x58\x02\x00\x8a\x09\x7c\x25\x96\x9e\x82\x6c\x2b\xaa\x59\xd2\xcb\xa3\x3d\x6c\x1d\x9f\x39\x62\x33\x0c\x1f\xcd\xa7\xcf\xb1\x85\x08\xfe\xa7\xd0\x55\x5e\x3a\x16\x9d\xae\xd3\x53\xf3\xee\x6f\x4b\xb3\x02\x44\x31\x91\x61\xdf\xf6\x43\x8a\x37\xca\x79\x3b\x24\xbb\xb1\xb1\xbc\x21\x94\xfc\x6e\x6e\xf6\x02\x78\x15\x78\x99\xcb\x03\xc5\xdd\x6f\xc9\x1a\x83\x6e\xb2\x0a\x25\xc0\x99\x45\x64\x3d\x95\xf7\xbd\x50\xd2\x06\x68\x4d\x6f\xfc\x14\xd1\x6d\x82\xd5\xf7\x81\x22\x5b\xff\x90\x83\x92\xa5\x79\x3b\x80\x3f\x9b\x70\xb4\xdf\xcb\x39\x4f\x9e\xd8\x1c\x18\xe3\x91\xa0\x9e\xb3\xf9\x3a\x03\x2d\x81\xba\x67\x0c\xab\xfd\x6f\x64\xaa\x5e\x33\x74\xcb\x7c\x20\x29\xf4\x52\x00\xe4\xf0\xbf\xd8\x20\xc8\xbd\x58\xdc\x5e\xeb\x34"; test( HashAlgorithm::SHA512, b"\x49\x41\x80\xee\xd0\x95\x13\x71\xbb\xaf\x0a\x85\x0e\xf1\x36\x79\xdf\x49\xc1\xf1\x3f\xe3\x77\x0b\x6c\x13\x28\x5b\xf3\xad\x93\xdc\x4a\xb0\x18\xaa\xb9\x13\x9d\x74\x20\x08\x08\xe9\xc5\x5b\xf8\x83\x00\x32\x4c\xc6\x97\xef\xea\xa6\x41\xd3\x7f\x3a\xcf\x72\xd8\xc9\x7b\xff\x01\x82\xa3\x5b\x94\x01\x50\xc9\x8a\x03\xef\x41\xa3\xe1\x48\x74\x40\xc9\x23\xa9\x88\xe5\x3c\xa3\xce\x88\x3a\x2f\xb5\x32\xbb\x74\x41\xc1\x22\xf1\xdc\x2f\x9d\x0b\x0b\xc0\x7f\x26\xba\x29\xa3\x5c\xdf\x0d\xa8\x46\xa9\xd8\xea\xb4\x05\xcb\xf8\xc8\xe7\x7f", p, q, g, b"\x6d\xa5\x4f\x2b\x0d\xdb\x4d\xcc\xe2\xda\x1e\xdf\xa1\x6b\xa8\x49\x53\xd8\x42\x9c\xe6\x0c\xd1\x11\xa5\xc6\x5e\xdc\xf7\xba\x5b\x8d\x93\x87\xab\x68\x81\xc2\x48\x80\xb2\xaf\xbd\xb4\x37\xe9\xed\x7f\xfb\x8e\x96\xbe\xca\x7e\xa8\x0d\x1d\x90\xf2\x4d\x54\x61\x12\x62\x9d\xf5\xc9\xe9\x66\x17\x42\xcc\x87\x2f\xdb\x3d\x40\x9b\xc7\x7b\x75\xb1\x7c\x7e\x6c\xff\xf8\x62\x61\x07\x1c\x4b\x5c\x9f\x98\x98\xbe\x1e\x9e\x27\x34\x9b\x93\x3c\x34\xfb\x34\x56\x85\xf8\xfc\x6c\x12\x47\x0d\x12\x4c\xec\xf5\x1b\x5d\x5a\xdb\xf5\xe7\xa2\x49\x0f\x8d\x67\xaa\xc5\x3a\x82\xed\x6a\x21\x10\x68\x6c\xf6\x31\xc3\x48\xbc\xbc\x4c\xf1\x56\xf3\xa6\x98\x01\x63\xe2\xfe\xca\x72\xa4\x5f\x6b\x3d\x68\xc1\x0e\x5a\x22\x83\xb4\x70\xb7\x29\x26\x74\x49\x03\x83\xf7\x5f\xa2\x6c\xcf\x93\xc0\xe1\xc8\xd0\x62\x8c\xa3\x5f\x2f\x3d\x9b\x68\x76\x50\x5d\x11\x89\x88\x95\x72\x37\xa2\xfc\x80\x51\xcb\x47\xb4\x10\xe8\xb7\xa6\x19\xe7\x3b\x13\x50\xa9\xf6\xa2\x60\xc5\xf1\x68\x41\xe7\xc4\xdb\x53\xd8\xea\xa0\xb4\x70\x8d\x62\xf9\x5b\x2a\x72\xe2\xf0\x4c\xa1\x46\x47\xbc\xa6\xb5\xe3\xee\x70\x7f\xcd\xf7\x58\xb9\x25\xeb\x8d\x4e\x6a\xce\x4f\xc7\x44\x3c\x9b\xc5\x81\x9f\xf9\xe5\x55\xbe\x09\x8a\xa0\x55\x06\x68\x28\xe2\x1b\x81\x8f\xed\xc3\xaa\xc5\x17\xa0\xee\x8f\x90\x60\xbd\x86\xe0\xd4\xcc\xe2\x12\xab\x6a\x3a\x24\x3c\x5e\xc0\x27\x45\x63\x35\x3c\xa7\x10\x3a\xf0\x85\xe8\xf4\x1b\xe5\x24\xfb\xb7\x5c\xda\x88\x90\x39\x07\xdf\x94\xbf\xd6\x93\x73\xe2\x88\x94\x9b\xd0\x62\x6d\x85\xc1\x39\x8b\x30\x73\xa1\x39\xd5\xc7\x47\xd2\x4a\xfd\xae\x7a\x3e\x74\x54\x37\x33\x5d\x0e\xe9\x93\xee\xf3\x6a\x30\x41\xc9\x12\xf7\xeb\x58", b"\xa4\x0a\x6c\x90\x56\x54\xc5\x5f\xc5\x8e\x99\xc7\xd1\xa3\xfe\xea\x2c\x5b\xe6\x48\x23\xd4\x08\x6c\xe8\x11\xf3\x34\xcf\xdc\x44\x8d", b"\x64\x78\x05\x09\x77\xec\x58\x59\x80\x45\x4e\x0a\x2f\x26\xa0\x30\x37\xb9\x21\xca\x58\x8a\x78\xa4\xda\xff\x7e\x84\xd4\x9a\x8a\x6c", )?; test( HashAlgorithm::SHA512, b"\xc0\x1c\x47\xbf\xa2\x08\xe2\xf1\x9d\xdd\xa5\xcd\xe5\x83\x33\x25\xd1\x6a\x83\xfb\xda\x29\xe6\x66\xfe\x67\xff\x34\x89\x80\x3a\x64\x78\xa5\xac\x17\xff\x01\xed\xc7\x97\x3d\x15\xfe\x49\x98\xf6\x3b\xbc\x09\x5f\xc1\xac\x07\x53\x42\x41\xc6\x43\xa4\x44\x44\xdc\x9a\x35\x6f\xa8\x12\xd5\xca\x19\x1a\x2f\x6e\xd1\x62\xa2\xd5\xfd\x6d\x0a\xa8\x98\xa2\x05\x63\xd9\x93\x83\x02\x54\xdb\x8a\x4b\xf6\x5b\xa8\x60\x99\xcc\x6b\x58\xa1\xbf\x6e\xbb\x01\xa1\x9c\x79\x30\x43\x08\xac\xeb\xe1\xda\x09\xf1\x75\x3a\x19\x5e\x9e\xf5\x86\xc7\xe1", p, q, g, b"\x97\x0d\x38\xcd\x8b\x3f\x16\x65\x9e\xc4\x2a\x46\xa1\x9f\xf0\x6c\xe8\x49\x5b\x9f\x47\x7d\x9b\x7e\x35\xae\x10\x35\xb0\x8b\x0e\xe1\x7a\x0c\x3c\xee\xdf\x02\x98\x46\xe3\xae\xb9\x12\xf8\x50\x88\x1c\x22\x77\xf8\x22\x81\xe7\xc0\x74\x1d\x2f\x87\xe9\xfa\x5c\x30\x67\x7f\xe7\x26\x8c\xc5\xfd\x9a\xed\x29\xf3\x08\xd9\xbe\x8d\xe9\x2b\x96\x1e\x39\xc1\xdb\xc4\x67\x90\xc9\x9b\x7e\x29\x57\x9d\xaf\x88\x81\x76\xd5\xce\x16\xdb\x5c\xab\xfc\xbe\x42\x09\xac\x47\x53\xb0\xe9\x6b\x15\xd0\xb8\x2c\x7e\xef\xb4\x2a\x10\xde\x88\xf8\xa7\x72\x34\x92\xa2\xbe\x54\x51\xc1\xc6\xec\x68\xca\x75\x9d\x8b\x4e\xe4\x18\x82\x6e\x71\xf3\x9c\xd0\x76\x54\xd0\x0d\x0e\x0f\x88\xd0\x92\x4b\xdb\x97\xaa\xca\x5a\x63\x46\xad\x69\xfc\x22\x3c\xd5\x7f\x5b\xb0\x30\x04\x77\xb5\x94\xaa\x44\x5e\x5e\xa8\x89\x6c\xdf\x3b\xc8\x82\xe8\xfa\x55\x23\xb8\xa3\x32\xfd\x98\xe9\xd0\xa9\x24\x57\x89\x44\xd2\x4a\x41\xcb\xea\xe3\xed\x7b\x37\xdf\xfb\x2f\x60\xc0\x08\x4e\xaf\x00\x5c\x12\x51\x82\x3d\xa4\x1d\x2a\x5d\x97\x7d\x8e\x48\x3d\xdb\x33\xf7\x3f\xbc\x27\x25\x4a\x81\x4b\x61\x6d\x6a\x39\x05\x13\xf0\x56\x7a\x56\x3a\xc0\x53\xa7\x66\x67\x19\x7b\x45\x58\xf8\x71\xb6\x9c\xbf\x2c\x11\x6c\xe4\x57\x51\x3f\x60\xb4\xf5\x28\xe2\xdc\xda\xa7\x1a\x9a\x3a\x4c\xcc\xb3\x73\x8a\x22\x93\x7b\xca\x2a\x04\x2b\xef\x8a\x74\xa6\x00\xac\xd2\x69\x75\xc8\x91\x46\x6d\x7e\x57\xcc\x93\x09\x84\x21\x2e\xe0\xea\xf1\x74\xeb\xcb\xaf\xbe\xb8\xcc\x12\xbc\x43\xbf\xdb\x00\xfd\x11\x57\x6c\x43\x95\x13\xef\x5b\x59\xa8\x8f\xa5\xa9\xae\x96\x3d\x94\xda\xfd\x78\xf8\x1e\xe7\xb0\xd7\xfa\xb5\x3e\x41\xbb\xf6\x5f\x84\x49\xa4\xf5\x8b\x44\xf9\xe3", b"\x5b\xb5\x0e\x4f\x53\x8a\x6e\x46\x38\x20\x6b\xe1\x19\xdb\xf7\x12\x77\x61\x54\xac\xfb\x4c\x06\xd6\x5d\x66\xc8\x02\x12\x34\x17\x39", b"\x7b\x7e\x64\x0c\xd7\x60\x86\xd3\xf6\x40\xd1\x8c\xeb\x26\xbb\x53\xe3\x02\x82\xaf\xb1\x74\x01\xe7\xb4\x8a\xa6\x81\x89\x34\xdc\x5c", )?; test( HashAlgorithm::SHA512, b"\x47\xe7\xaf\x22\xc9\x29\x8a\xd3\xbf\xef\x9b\xee\x50\x86\xbe\xdb\xdc\x51\x3d\x67\x41\x6d\x5f\x4e\x79\x81\xcd\xdb\x10\x02\xcb\xa2\x47\x00\xc4\x5d\xd6\xd4\xdc\xef\x4f\x81\xd0\x03\xf0\x51\x3d\xab\x4e\x04\xeb\x4c\x70\xd9\x44\x04\x2e\x1b\x72\x6d\x8a\x33\x05\x0d\x0e\x4f\x70\xc0\xa8\x34\x1b\x75\xfd\x4e\x27\xc7\x94\x87\x54\xe4\x41\x20\x8e\xb9\x3f\xc7\xb7\xc3\x73\x54\x25\x2f\x73\xb8\x38\xfd\x02\xd0\x78\xc6\xa1\xae\x07\x3e\xf1\x23\x3a\xa1\xc8\xaa\x27\x81\xe1\x93\xba\x28\x97\xcc\xdd\x8c\xf6\x17\xca\x23\x54\x1c\xe1\xc5", p, q, g, b"\x75\x16\x3a\xf1\x5c\xd6\xb2\x28\x25\x15\x04\xba\x02\x4d\xf5\x1d\xf3\x2f\x63\x8e\x37\xf0\xf2\xf9\xd0\x88\x37\xf8\xc6\xec\xfb\xa4\x3e\xb5\x15\xcc\xba\xbe\xa1\x1b\x01\xe1\xe1\xfd\x3c\xfe\x7e\x40\x5f\xc7\xf8\x14\x2b\x07\x31\x5e\x1d\xc3\x7b\x08\xc7\x86\x68\x42\x1e\x2a\x21\xfc\x5d\x81\x1d\x26\x55\x8c\x50\x4a\xbc\x4e\x6f\xdd\xf0\x37\x40\xb8\xa2\x7f\xa2\xeb\xcd\xa5\x46\x0a\xd7\x85\x70\x6c\x53\xcd\x2d\x14\x09\x3d\x92\x3d\xf9\x42\x05\x1c\xbb\xa2\x58\x6b\x4d\x54\x70\x9d\x24\xba\xbe\x2f\x7c\x61\xa5\x0d\xa8\x45\x18\x95\x99\x91\x66\xe8\x0c\x0f\xab\x89\x2a\x37\xeb\x67\x82\x74\x55\x96\xb4\x9f\x96\xe1\x1e\x9a\x95\x7c\x8e\xc6\x50\xd2\xd9\xa4\x0a\xa4\xb0\x14\xd2\xe9\xa4\xc0\x8b\x9d\x7b\xfe\xaf\x1e\xcd\x42\x78\x5b\x95\xc0\x17\x2a\xe2\x1c\xf2\x5c\x4d\x36\x8b\xb5\x10\x0b\x6e\x6d\x92\x31\x0b\x28\xb7\xb1\xaf\xe6\x4d\x49\x6b\x9c\x60\xb7\x63\xca\xc0\x8a\xc4\x6a\x6b\xce\x1b\xbd\x3a\xc8\xbb\x76\xbb\x55\xb6\x49\xb7\x59\x48\x20\xab\x6e\xf7\xdd\x1b\x09\xbb\x12\x85\x28\x16\xb6\x1e\x6d\xbe\xfa\xb7\x42\xe0\xea\x2c\xda\x47\xea\xc7\xd9\xd9\x13\xdd\xd4\xbf\xd8\xb2\xeb\x5f\x01\x95\x1c\xaa\x4f\x41\x3e\xb5\xe7\xa4\x1a\x06\x85\x69\x5f\x83\x31\xa3\x94\xe0\x6b\x14\x95\xc1\x70\xf3\x0a\xc2\x94\x66\x0e\x89\x09\x84\x3f\x9f\x11\xc4\xbf\xa6\x4e\x87\x92\xdf\x67\x7d\xa0\xa0\x8a\xae\x32\xa8\xa4\xe7\x06\x7f\xc3\x5e\xee\x03\x96\x4e\x8a\xfb\xdb\x6a\x42\x1b\x82\x48\xad\xd2\x84\x78\x9e\x4e\xd3\xca\xce\x71\x06\xc2\x3f\xe6\x66\x6c\x4b\x12\xb8\x36\xe7\x30\x7a\x55\xab\x24\xd9\x2d\x58\xac\x84\xe7\x1f\x81\xdc\x9b\x0b\x74\x36\xad\x07\xf7\x49\x94\xaf\x7d\x0b\x04\x9b\xd0\x9a", b"\x61\x75\x47\x3d\x7a\xa7\xd5\xce\x55\x59\x0c\x95\x2a\x19\x89\x72\x06\x08\x68\x87\xfd\x84\xbf\x2b\x56\x69\x26\xe4\x79\x81\xc2\xa3", b"\x71\xd7\x85\x7b\x6f\xf0\x6c\xa6\x78\x85\xfa\x9c\x9c\x71\xb8\xcc\x24\x6d\x03\x39\xb6\xc2\x72\x52\x47\x17\x2a\x29\x7e\x26\xa7\xb5", )?; test( HashAlgorithm::SHA512, b"\x93\x11\xd8\xf9\x51\x14\x17\x13\xf4\x59\xeb\x65\xf0\x18\x80\xb9\x61\xc0\xa5\x90\xb3\x6f\x78\x5f\x1a\xeb\x88\x0e\xe7\x13\x00\xc0\xcb\xc6\x01\xb3\xa6\x07\x21\x93\xda\xd6\xdd\xf2\x02\x8e\xca\x4c\x8b\xd7\xb8\x57\x51\x87\x92\x8f\x84\xbd\x69\xc5\xdc\xfb\x0b\x9d\x32\x00\x03\xc3\xa8\x63\xc0\x9e\xe5\x03\xe3\x8a\xbe\x07\xce\x2e\x0d\x46\xb3\xce\xc9\x26\x23\x1a\x57\xde\xfa\x0a\xeb\xd1\xa6\xe0\x1e\xef\x4f\x9b\x53\x7a\xe1\xfc\xdf\x64\xe0\x14\x34\xd4\x0a\xb5\x01\x9f\x39\x65\xc7\x35\x41\x1a\x5c\x19\x94\x1f\x41\xfe\xbf\x4f", p, q, g, b"\x28\x7d\xdc\x19\x69\x15\x6c\x18\x42\x07\x43\xad\xe0\xfa\x12\x71\xea\x34\x6c\x33\x29\xf9\xca\x9b\x5d\x54\xeb\xfa\x21\xf6\x76\xf9\xe0\x13\x61\x62\x39\xf4\xbb\xe6\x0e\xaf\x8e\x19\x02\xed\x9a\xc7\x42\xd8\xdf\x91\x88\x76\x77\x08\x94\xb5\x12\xaa\xa2\x5c\x06\x8b\xde\x96\x1f\x56\xc9\xb5\xb8\x78\x06\xd7\xd0\xa9\xde\x78\x43\xd3\xcb\x07\x97\x90\x31\x26\xa4\x7b\xd9\x42\x23\x37\xe3\xb4\x6b\xb1\xf4\xf4\xa7\x9f\xdf\x9c\xf6\x76\x21\x57\x11\x8a\xee\xe1\xe7\x11\x16\xf3\x4d\xaf\xce\x00\x47\xf0\x5d\x43\xc7\xf2\xcb\xd4\xcd\x52\xd6\x14\xb7\xa9\x45\xd4\x8b\xe4\x4c\xfe\xbf\x78\x43\x32\xfe\x99\xc1\xee\x1a\xa8\x31\x08\x67\xdf\x20\xb2\x80\xda\x85\x5b\x19\x02\x9f\xa7\x9e\xcd\x6d\xd6\x91\x9a\x4d\x22\xb5\xa1\x40\x0c\x30\xe6\x2c\xe7\xac\xc4\xb2\x8e\xfb\xdb\x94\xea\x23\xaf\xbb\x64\xd6\xe5\xf7\xb3\x97\x5d\x2a\xc6\x3b\x1d\x04\x8f\xee\xa8\x35\xc7\xf5\x0b\x42\x5c\xe3\xcb\x41\x8a\xfd\xf4\xdc\x84\x00\x84\x73\x60\x65\x74\xe2\x0d\xb5\xeb\xf8\x6c\xb1\xad\x27\x73\x7d\x46\x49\x4b\x2e\x48\x5b\x26\xb8\xc9\x5d\x82\x9c\xf6\x56\xf8\x0f\x96\xb1\xa6\x2e\x7c\x03\xc8\xf2\x0f\x18\xdc\x58\xbf\x59\x91\x66\x82\xe6\xdc\xc6\x8d\x34\xc8\x9c\x1b\x1b\xd6\xe6\xb1\xe1\x5a\x7d\xc3\x25\xe2\x3f\xd7\xa3\x50\x99\x83\x1d\xbd\x75\x98\x9c\x73\x80\x20\xbf\x4d\xc4\x07\x9c\xcb\x0b\xf1\x2f\xaf\x3b\x9d\x64\x94\xa3\x79\xaa\xcb\x1b\x66\xd0\x7c\xbc\xeb\xbf\x77\xa6\xe2\x9a\xef\x22\xf4\xba\xa3\xdf\x40\xd2\x70\xb4\x57\xdd\xe6\x4f\x00\xb5\x37\x59\xae\x57\x81\x1b\x64\xe0\x40\xcb\xd4\x2e\xa9\x0f\x4e\x28\x08\xbc\x81\xdf\xd6\x63\xb2\x85\x84\xcd\xb8\x19\x9d\xa9\x6d\x3e\x03\xd0\x3f\xb4\x13\x3e\x2f", b"\x66\xf7\x29\x71\x64\x56\xa2\x78\x1b\xdb\x85\x78\xfa\x18\xd1\xe6\x4a\xf0\xed\xf8\xec\x1d\xee\x0a\x50\xd2\x59\x81\x91\x2f\xc4\x5a", b"\x8c\x3c\xcc\xfe\x6f\x0c\xfd\xc0\xac\x3a\x54\x2c\x8e\x8c\x85\x21\x0b\xbd\x7f\x95\x13\x4c\x8f\x03\x5d\x1c\xe1\x6f\x44\xab\x7a\x06", )?; test( HashAlgorithm::SHA512, b"\x80\x86\x03\xf7\xf8\x43\x94\x41\x27\x79\x13\xb2\x1b\xef\x4e\x01\xc8\x9e\x41\x13\xe0\x7c\xac\xc3\x3f\x65\xac\x98\x49\xdb\x1a\xd1\xa1\xcb\x7d\xd2\xfe\xcd\x88\xee\x41\x39\xb1\x63\x83\x55\xc6\x23\x82\x13\x09\xf3\x26\xc1\x6b\xc6\x58\xbb\x48\x21\x51\x82\x38\x98\x2e\x52\x51\xf7\xcd\x37\x80\x72\x92\x15\x3d\x2b\x07\xdd\xdc\x06\x6e\x00\x3c\x60\x69\xc3\x71\x15\x5d\x2d\x19\x1f\x15\x11\x1f\x20\x89\xce\x42\x3f\x5c\x2a\x1f\x85\x34\xe3\x01\x31\x3c\x69\x62\x3f\x62\xba\x63\x5a\xdc\xe8\x55\x17\x33\xa8\x2a\x8f\xac\x1a\x66\xb1", p, q, g, b"\x38\x96\x72\xec\x6d\xe0\xb8\x66\x55\xcb\x10\xf1\x19\x9f\x85\x70\x13\xb6\x32\x0d\x52\xc8\x72\x8f\xbb\xb5\x36\x0a\x97\x01\xb1\xd6\xca\x4f\x9e\xec\xb8\x48\x7f\xb8\x79\x69\x0f\x85\x43\x0c\x58\x2d\x3d\x91\xef\x18\x4c\x82\x47\xd1\x62\xb9\x4d\x6d\xfd\xfe\x7c\x4a\xe8\x67\xac\x16\x72\x82\x79\x70\x41\x5a\xa6\x7a\x14\x06\xac\x1a\x6e\x2c\x6c\x13\x16\x77\x19\xe1\xd1\xa5\x36\xd1\x00\x78\x42\x7c\x21\x1c\xf6\x82\x05\x1a\x75\xee\x83\x22\xc1\x40\x8b\x89\xd9\x63\xbd\x8e\x85\xf9\xef\xf7\xbb\x8c\xe0\x5c\xa4\x22\x25\xb4\xbd\xfe\xad\x6b\x89\x7b\x0f\xea\xb7\x6c\x22\x72\xb4\x87\xd2\x7d\x4e\x8d\xcd\xe0\xf1\x9e\x46\x15\xf7\xe1\x11\x45\x41\xf6\x1d\x43\x53\x3c\xe7\x88\xcc\x45\x05\x60\x0b\x83\x26\x6b\x1b\xea\x66\x59\x12\x19\x6c\x2c\x84\xc3\x6a\xa9\x3b\xaf\x5b\x74\x64\xa6\xdd\xf5\x47\x18\x3e\x2c\xd0\x58\xbb\x50\xa1\x27\x65\x53\x6f\x0a\x4d\x35\x24\xaf\x4f\x31\xac\xc6\x09\xfc\x44\x7e\x17\x29\xaa\xb9\x7b\x5a\x36\xb0\x17\x64\xb8\x4b\xc5\xf7\x7f\x6c\xc5\x84\x86\x6d\x1a\x6c\xfb\x3a\xa8\x43\x78\x95\xf7\x77\xf2\xdc\x68\x97\x49\x9f\x6c\x5f\x02\xfa\x1e\x6c\x1e\xad\x68\xf3\x38\x5b\x73\x33\x87\xc6\xb5\x8f\x2d\x11\x28\x4a\x63\xae\x7c\x7c\xfe\xe4\x2c\x3f\x44\xa3\xc9\x26\xad\xad\x81\x07\xcc\xa1\xc3\xf9\x44\xf9\xb9\xe2\x37\xd9\xab\x35\xc8\x13\x91\xd7\xc5\xf5\x29\x2d\x1a\x32\x2f\x7a\x12\xce\x10\x8a\x86\x23\x7b\xa4\xde\x3c\x61\x2f\xa7\x38\xf5\x31\x94\xba\x67\xbe\xd8\x43\xcd\x2d\x43\x30\xa5\xd1\x94\xd6\x7c\xf4\x5f\xa0\x51\x83\xe0\xcb\x46\xc2\xd2\x3a\x1b\xae\x76\x75\x5c\x30\x9f\xa1\xc3\x16\x05\xc8\x8a\x92\x14\x22\x7c\xe0\x2f\xe9\x15\xbc\xf0\xd3\x4b\xce\x8c\x8e", b"\x98\xfe\x58\x7e\x43\xaa\x96\xf9\xa9\xbb\xe8\xaf\x40\x4a\x08\xb0\x23\x07\xb3\x60\x53\xdb\x87\xf6\xdb\x25\xa3\xaa\x36\xfc\xc3\xdb", b"\x5c\x94\xea\x70\xf9\x9f\x9f\xf1\x4b\x8e\x5d\xd4\xa6\x68\x83\x98\x26\x09\x07\x17\x6e\xa8\x0e\x19\xc3\x9b\x14\x62\x11\x49\xf0\xd6", )?; test( HashAlgorithm::SHA512, b"\xce\x2a\xa3\xed\x12\xc1\xb8\x84\x3a\x3e\x11\xb0\x6b\x5f\x0e\x5e\x63\xfe\x8e\x19\xc1\xa3\x8a\xc4\x46\xa4\x8e\xec\xa8\xda\xc6\xd8\xb7\x69\xd7\x80\x94\x42\xc3\x2a\xc8\x2e\x93\xf6\x86\xec\x64\x34\x7e\x94\x44\xc3\xf4\x52\x82\x3c\x84\x0e\x8d\x0c\xd3\x34\xb4\x15\x20\x02\x14\x8d\xa1\x6a\xc8\x85\x9d\x18\x9d\x87\xd6\x71\x64\xc5\xdb\x16\x19\x5c\x08\x1d\x2e\xdd\x7d\x81\x57\xe2\xbf\x3b\x97\xa9\x0b\x4b\x47\x84\x32\x4e\xb8\xce\xac\x42\x61\x80\x9f\x67\x42\x56\xda\xf0\x07\xc4\xab\x1f\x22\x2f\x5f\xd2\x83\x98\xa5\xb8\x24\xde", p, q, g, b"\x58\x4f\xe0\xeb\x31\x4a\xfb\x86\x6c\x04\x66\xc3\x98\x0a\x2d\xf5\x45\x98\xd8\xa7\x05\xdc\x2d\x1b\xf5\x10\x2e\xac\x86\x31\x27\x84\xee\xbd\x01\x9b\x81\xa7\x64\x2d\x4a\x3c\x4c\xc6\x5d\xbe\xdd\x81\x87\xe3\x59\x3f\x0a\x9b\xcc\x06\xea\x36\x70\x09\xb7\xeb\x4d\x29\xb0\x45\x00\x61\x37\x8e\xdb\xe1\x63\xef\xd3\xf3\x44\xbb\x36\x23\x4f\xc8\x6f\xe1\xc3\x2f\x2c\x99\x95\xa0\x7c\x6e\x95\x7d\x19\x5e\x81\x05\xf5\x17\x9c\x2b\xd9\x76\xb3\x12\x70\x67\xc8\x0c\xa9\x34\x56\xc1\x6b\x98\xdf\xcc\x7d\xe3\x55\x79\x0f\x0b\x15\xcf\xd2\xff\x91\xdb\x09\x34\x55\x32\xd4\x60\x96\xc0\x6b\x40\xa2\x30\x46\x81\xd6\x28\x57\x67\x5a\xc5\x0e\x22\xc7\xd1\xab\x47\x58\x92\x35\x41\x9c\xbe\xdd\x4b\x7d\x24\xb9\x05\x31\xe5\xbf\xd8\x53\xe8\x8a\x28\x83\x6a\xc4\x6b\x6d\xf2\x67\x60\x98\x5b\x96\x2c\x6a\x24\x45\x80\x98\x66\xb4\x61\x26\x21\x2a\xa2\x63\xab\x2a\x46\x03\xff\x41\xa8\x52\xc7\x98\x8c\x2d\x43\x86\x24\x16\x55\xa7\x22\x2f\xa4\xe9\xf6\xea\xc6\xa1\x44\xa1\x6b\x05\x9e\xa2\x5b\x71\xa2\x13\x84\x91\xd5\x4e\xe9\x5a\x9d\x68\x19\x97\x7f\x90\xfe\x6a\x59\xe0\xca\xd8\x1b\x32\x9e\xba\x3e\x68\x27\x7d\xf0\x4f\x98\x28\xef\x6f\x08\x16\x10\xb4\x59\x5a\x92\x11\x3e\xc6\xd0\x69\xff\xe9\x71\x96\xd9\x56\x19\x1d\xaa\xbe\x98\x77\x37\x7a\xd0\x41\x6b\x0e\xe0\x65\x86\x63\x37\x7e\x07\xad\xb2\x46\x44\xe8\xa0\xe3\xce\x5f\xc1\x78\xf1\x52\xbe\x0c\xd9\xb0\x40\x71\x89\x04\x27\xc6\xb0\x01\xd5\x92\x62\xf3\x8f\xe8\x97\xce\x32\x04\x0d\xaa\x78\x07\x82\x1c\x40\xac\x8c\x63\x50\x5b\xed\x0a\xf0\x70\x44\x33\x37\xc9\xe9\xa6\x4e\x44\x20\x3c\x36\xa8\xca\x50\x64\xd8\x7a\xa0\xd3\xcd\x1d\x40\x3a\xa6\xa2\x4e\xcc", b"\x54\xc9\x9b\x21\xf2\x8f\xee\xe2\x7f\x0e\x99\x9a\xac\x6b\x49\xb6\xb0\x76\x33\xe1\xdb\x18\xa4\x59\x52\xfc\xf7\xe7\x3b\x16\x6b\xdb", b"\x7a\x18\x58\x8e\xa1\x45\x6f\x67\x56\x2d\x67\x78\x78\x34\x6f\xb3\x4b\x68\x4b\x9a\x8a\x61\xa7\x21\xb3\xdb\x0e\x95\x69\x5a\xb4\x3a", )?; test( HashAlgorithm::SHA512, b"\x17\xb9\x25\xe2\xa1\xa5\x1c\x20\x36\xe2\x25\x71\x5f\x2f\x77\x1d\x8f\x0a\x6d\x98\xc7\xed\x9c\xac\xf5\xaa\x4c\xd3\x0a\xb1\x6a\xfb\x94\xe2\x1a\x7c\x95\x3e\x01\xca\x21\x1c\x28\x78\x2a\x06\x07\x3f\xda\xd2\x77\x13\xaa\x8c\x26\xae\x9e\xc4\x49\xaa\xaa\x8c\xcf\xda\x8c\x94\x71\x72\xde\x94\xb3\xf2\x0b\x54\xaf\x98\xdf\x15\x2d\x5d\x3a\x63\x6c\x73\x6f\xf0\x1b\xfa\x69\x9d\x62\x14\x00\x2d\xc7\x6d\xbb\x3f\x38\x60\xd9\x4e\x0e\x34\xed\xab\xa5\xf2\xbf\xd6\xb2\xbf\x66\x00\x86\xbe\x87\x64\x51\xa5\x0f\x6a\x2d\xc7\xc2\xb0\x98\xb7", p, q, g, b"\x42\xa9\x3b\xf4\x4e\xc7\xd2\xfb\xd6\x51\xcc\x1d\x1a\xc3\x91\xd6\x3c\xab\x00\x97\x1a\x7f\xf7\xa5\x61\x66\x76\x8b\x22\xe6\x11\xdc\x4d\x72\x9f\xaf\x8c\x94\xe7\xed\x4d\x6f\x82\xb7\x02\x0b\x7b\x4d\x2f\xb3\x59\x1c\xf2\x29\x5c\xc6\xe1\xb4\xbe\x2c\x25\x6c\x2f\xdd\xa4\x3e\x00\x05\x11\x14\x64\x5d\xa9\x1c\xbe\xd5\xcc\x08\x70\x85\xf7\xce\xcd\x8b\xac\xe6\x78\x89\x10\x0b\xcc\xe7\x92\x82\x20\x26\x6f\xd3\xfa\xf2\xea\xd9\xc2\x1e\x42\x3c\x99\x48\xec\x70\xc2\xd3\x1b\x66\x8c\xdc\x36\x0d\xdc\xeb\xdf\x42\x97\x20\x60\x7f\x96\xd8\x51\x23\x55\x15\xd6\xdb\xdf\x16\x3f\x7e\xa5\xdd\xf3\x51\xba\xa7\x6f\x38\x66\x3f\xdb\xfb\xd5\x87\x1b\xb2\x15\x7d\xf0\xa4\x34\x20\x64\x8c\x10\xe4\x82\x7f\x54\x06\x56\x14\x62\x3e\xd3\xab\xad\x10\xd3\x17\xbe\x9d\x49\xa4\xc6\x65\x64\xf2\x0d\xca\xc1\x76\xb6\x60\x5a\x2e\x3c\x3c\x01\xc3\x62\x22\x0f\x35\x2e\x47\x74\x19\xf2\xb4\xb2\x38\xaf\xfb\xc3\x92\x0e\x5b\xb5\x7c\xeb\xb9\xa7\x47\x46\xd6\x2c\xdd\x07\x0f\x4a\x13\xaf\x00\x1d\x26\x2d\xef\x01\x4f\x29\xb7\xf7\x54\xfa\xc8\x4e\x02\xd2\x92\x85\xb7\x3b\xb2\x0a\xc0\xc8\x62\x41\x23\xa5\x77\xbe\x8d\x6a\x6b\x97\x39\x18\x5e\x44\x58\x09\x0d\xdb\x42\xb0\x05\xea\x4f\xa8\xb5\x10\x07\xbd\x9c\xa5\xb4\xcf\x2a\x3d\xca\x44\x6a\x87\xec\x83\xc9\x54\x8d\xab\x46\xcf\x3d\xaf\x86\xdb\x3b\xc6\x9a\x99\xba\xed\x45\x9d\x6a\x19\x7f\x9b\xf5\x03\x2c\x1d\xc3\xa8\x77\xdd\x7e\x5c\x11\x61\x12\x4a\x6d\x70\x13\x24\xe9\xa9\x71\x2b\x82\x4a\x4f\xc3\xb1\xb3\x53\x25\x9a\xf2\x25\x81\x3c\x27\xe8\x20\xb0\xba\x72\xfb\x4e\x78\xf5\xc7\x86\x73\x92\x4e\x7f\xa2\xf4\x86\x03\x02\x84\xf2\x6c\xb6\xfa\x31\xda\x56\xf4\x9d\x3f", b"\x72\x6e\x4d\x3b\xaf\x00\xb2\x59\xf4\xbd\xca\x8b\x0a\x5e\x1c\xbf\xd3\x78\x27\xc4\x83\x73\xef\x50\x29\xf7\x60\x1a\x77\x69\x47\x8c", b"\x90\x30\x79\x43\x9e\xbd\xe1\xf7\x66\xd1\xa8\xff\x33\xe0\xf7\x78\xd7\x7b\x5e\x8b\x7b\x0d\x68\x74\x43\xc2\x71\xe8\xa6\x3b\x59\x75", )?; test( HashAlgorithm::SHA512, b"\x1c\x11\x69\xf0\xe7\x90\x05\x3c\xd7\xdf\x78\x0b\x5c\x83\x2c\x64\x14\x76\x94\xb4\xa6\x44\x8b\xa1\x4a\x42\x6d\x9d\xef\x7d\xdc\x78\xe3\xed\x36\xa1\x2d\xa8\x1c\xf9\xc3\xf2\x45\xd6\x4c\x85\x9b\x6b\x4d\x81\x21\xd1\x12\x85\x19\x74\xdf\x17\x8d\xef\xc9\x77\xdb\x69\x12\x34\xd1\x42\xdf\xf9\x9b\xea\x19\x57\x89\x1b\x5d\x6f\xe8\xa7\x87\xe9\x63\x69\xd9\x3c\x24\x68\x2d\xeb\xd1\xcf\x3f\xdb\x64\x37\x9b\x8c\x1b\x3b\x73\xe1\xbc\x24\x67\xdc\xb0\x8b\x86\xcb\xd4\x94\xc0\x14\x77\xbe\x24\xd7\x90\x0f\x5a\x57\x89\x30\xf4\xbd\xdc\xb6", p, q, g, b"\x7f\xca\x22\x68\xfb\xa3\x3b\xf9\x4e\x76\x41\x6a\x9e\x38\x69\xf8\xa9\x0c\x3b\x0d\x2d\x37\xaa\xce\xcd\x3f\x67\x85\xb9\xa9\x5a\xee\xfe\x93\x24\xc3\xab\x09\xce\x61\xff\xde\x37\xb5\x0f\x82\xb6\x99\x41\x3f\x3b\x54\xf2\x4d\x6c\x52\xec\xa6\x23\x25\x02\x95\x23\xde\xb0\x5d\xb1\x38\x77\x84\x47\xbc\x3d\x0d\x05\xaf\xf7\xd8\x5b\x55\x25\xf2\xb8\x63\xd2\x64\x86\xe8\x4c\xde\x13\xe2\xe2\x11\x7d\x3f\xa3\x8a\x38\xd1\x07\x3a\xaa\x79\x4e\xd8\xea\xa7\xb3\xd1\xda\xa4\xac\x3e\x80\x8c\x37\x38\xa9\xcb\xef\x35\x46\xcd\x79\xec\xcb\x4f\xaa\x28\xb5\x0f\xce\x57\xcd\xc2\x40\x15\xfe\xc3\x90\xf0\xe7\xa7\xdc\x9f\x9c\x47\x1d\x22\xb3\x0c\x3e\x41\x74\x35\x8f\x1a\xd0\x73\x4c\xf7\x9a\x09\xa6\x39\xbd\xf3\xf3\xea\xbd\xa2\xb4\x7b\x81\xf9\x2e\x2a\x4f\x90\x04\xdd\x64\x13\x70\x33\x8c\x02\x02\x9b\xbf\x49\x71\xaa\x67\x48\x3e\xea\x7a\x4b\xf7\xdf\xf3\x88\x9f\x84\xfa\xa5\x76\x56\x17\xcc\xab\x37\xd1\x90\xa9\x4c\x57\xf9\x9d\x79\x28\x07\xa6\x96\x5e\x21\x13\x58\x6c\x6c\x5d\x1a\x81\xab\xfd\x37\x2e\x1c\x79\x54\xe2\xe0\x90\x64\xdf\x4d\x2d\x82\x88\xf5\xcd\xd8\x10\x6e\xd8\x4f\xfa\x79\x88\x19\xa0\x9a\x73\x2b\xc2\x04\xa8\x12\xc0\x35\x2e\x4e\x39\xd2\xce\xb8\x8f\x8e\x7d\x36\x24\xa5\xa5\xf3\xdc\x56\xea\x0f\x9c\x52\x90\x78\x8e\x12\xdc\x46\x31\x61\x60\x1f\xf3\xab\x68\x1b\xd0\x40\x3e\xe0\x3a\xf4\x5d\x5e\x58\x6d\x84\xd9\xc9\x01\x98\x67\x18\x19\x3e\x66\x12\x56\xf4\x02\xde\x73\x5d\x2c\xa6\x96\xef\x6b\x59\x48\x68\x95\x0a\xe1\x73\xf2\x2d\x95\x85\x66\x56\xa9\xd0\x06\x10\xfe\x8c\x2b\xd7\x25\xae\x55\xd7\x91\x27\x7b\x13\x17\x08\x5b\x67\x18\x8d\xa0\x06\x45\xce\x91\xbb\xe6\x2e\x32\x43\x11", b"\x10\x26\xec\xee\x0a\xc3\x1b\xdc\xdb\xd6\x10\x3b\x13\x43\xf8\x4b\x44\x1f\xc3\x26\xe1\xd8\x6a\xd0\x90\x3d\x0b\x17\xcf\xb2\xff\x9c", b"\xa5\xd3\xcb\x2e\x7c\x39\xd8\x76\x40\xc4\x54\x7a\xc6\xc3\x3a\xfc\xcb\xfc\x18\x20\x90\x5b\xa1\xe5\xbe\x5b\x26\x23\x13\x27\x7c\xb9", )?; test( HashAlgorithm::SHA512, b"\x80\x5b\xaa\xbd\xd0\x18\xd9\xe5\xeb\xb4\xdc\x51\x43\x5b\xe6\x32\xd2\x38\x78\x69\x75\x6d\x74\x37\x88\x44\x27\x90\xd5\x5b\xb1\x83\xe2\x66\x55\xae\x3a\xac\x86\xdc\x16\xa4\x8d\xdd\x26\x8d\xd1\x5e\x18\xd8\x32\x0d\xf9\xa1\xa0\xa6\xcb\x2b\x49\xbc\x70\x1d\x7a\x15\xe3\xfe\x8d\xdd\x58\x4a\x75\xc8\xc9\xaa\xae\xcd\x1e\xfe\x17\x32\x4d\x62\x61\x88\x1f\x3d\x34\x68\x5b\x04\xf6\x2e\x96\x85\x05\x96\x6c\x9a\x5f\xeb\x0c\x39\xb5\x09\x5e\x55\x68\xe4\x0f\x20\xaa\x21\xcb\x25\x05\x35\x6d\xc9\x04\x9c\xe5\x61\x82\xd9\x4a\x2d\x94\xa9", p, q, g, b"\x43\x4d\x06\x12\xb2\xa8\x33\x2b\x0a\xb1\x56\x14\xe3\xee\x9f\xa2\x45\x13\x17\x12\xfb\x2b\xa8\x4f\x71\x39\x6f\xff\x94\x88\xdc\xa3\x40\xa3\x7e\x82\x0f\x44\xc1\x3a\xa8\x7f\xc9\xdf\x0b\x7a\xab\xea\xe2\xed\x85\xa9\x62\x2b\x8d\xef\xad\x47\x4a\xc3\x62\xa7\x03\x9a\xbd\xe3\x3d\x1d\xf7\x32\xa0\x52\x44\x6a\xff\x78\x57\xbc\x24\xd8\xf6\x1d\x25\x80\x15\xed\x2a\x30\x60\xa8\xbf\x9d\x44\x7e\x7d\x83\xd7\xb4\x97\xa8\xe6\x54\x73\x19\x69\xe4\x37\xb3\xf4\x6f\x83\xeb\x58\xf7\x88\x4f\xf2\xa2\x39\x0f\x5d\x82\x1e\xca\xa7\xfd\x09\xa1\x46\xc5\x5f\xc1\x18\x00\x73\xcc\x5a\xaa\x60\x7c\xab\xb9\x44\xf6\x07\x8a\x44\x86\xcf\x20\x6d\xdc\x56\x35\x24\x2d\xef\x2d\x3e\x2e\xdc\xbc\x02\x6b\xb8\x4e\x84\x95\x18\xf1\x97\x39\x9c\x22\xa9\x00\x9d\xde\x9a\xfc\xd8\x76\x9b\x24\x1c\x75\xd4\xcc\xce\x7f\x93\x90\x0b\x5f\x48\x83\x33\xdf\x47\xc0\x26\xc4\xf2\xb2\x76\x7e\x70\xd2\xd9\xdd\xe7\x84\x05\xe2\x26\xc9\x95\x2f\x6d\xb1\xa2\xe5\x58\x29\xbc\x8a\x76\xc7\xde\x5c\x2b\x58\x8f\x3f\x3e\x93\xce\x72\xfa\xda\xba\xcb\x75\xc7\xc1\x46\x69\x70\x1e\x0a\x2b\xa1\x27\xba\xc5\x68\x63\xc8\xc4\xe7\x20\x5c\xc0\xa7\x3c\x42\x9a\x80\x1e\x97\x97\xda\x4f\x26\xe8\x48\x98\x23\x06\xcc\x3c\x34\x39\xf9\xe3\x94\xdd\xc8\x0b\x0f\x13\xe0\xd5\x28\x19\x06\x38\xd8\xb9\x6b\xba\x3a\xf8\x89\xde\x37\x3b\x35\x49\xfc\x90\xa6\x82\x29\x64\xc2\x21\x71\xe7\x60\x1f\xde\xfb\xe5\x70\x89\x88\xb8\x4f\x3e\xa5\x54\xd6\x21\x60\x0a\x87\x64\x15\xd5\xbc\x1e\x55\x7e\x94\x8c\xaa\xce\x56\x3b\x37\x02\xf0\x91\x5a\x90\xa1\x3a\xad\xa7\x77\x09\xee\xba\x8c\x50\xa8\x62\x93\x51\xa4\x78\x7d\x0d\x58\x80\x8f\xfb\x8b\x21\x7c\x1d\x16\x4f", b"\x2d\x63\xe6\xd2\x56\x85\x71\xac\xfe\x4a\x93\x15\x80\xa0\x4b\x97\x4c\x7a\xae\x4c\xa9\xaa\x96\x10\xd8\x7b\xe1\xa9\x1c\x65\x7c\x31", b"\x57\x4b\x10\xd1\x4d\xcb\x8f\x07\x94\x61\xb2\x9a\xe1\xb9\x1e\xd6\xc5\xef\x32\xf9\x3c\xba\xd3\x06\x69\x75\x52\xc1\x17\x48\xfe\x0c", )?; test( HashAlgorithm::SHA512, b"\xbe\x8c\xa5\xed\x4c\x22\xa0\x50\xd8\x30\x9c\x7a\x31\xac\xf6\x67\xf0\xc0\xfb\xaa\xdc\x64\xa3\x4d\x2b\x63\x07\x4a\x76\x3a\x2b\x8d\xb7\x3b\x24\xcd\xba\xad\x26\xcc\x6f\x2c\x3e\x90\xdf\x4b\x25\xbf\xa7\x24\xfc\xe5\x87\xfa\xa0\xfd\x65\xff\xb7\x19\xf0\xa0\x35\x16\x48\x23\x0d\x53\x54\xd7\x21\xd8\xfa\x6d\x0d\x68\x6c\x37\xf2\x57\xd7\xd9\xdb\xd1\x5f\x55\x5d\x50\x73\xf8\xbc\x71\xc9\x21\x39\xd1\xf6\x27\xd7\x43\xf7\xd6\x58\x6d\x51\x0d\x19\xd0\xd8\xa5\x55\xd0\xbf\x79\xec\x70\x59\x6e\x71\x21\x83\x88\x0c\x89\xca\xf6\x9d\x6f", p, q, g, b"\xaf\xaf\xf7\xa4\xd4\x38\xb4\x64\xf2\x74\x15\xd2\xe0\x3e\xd9\xc4\x16\xdb\x2b\xeb\xfb\xe0\xab\x34\xf1\x4e\xe1\x06\x44\x88\x5b\x5a\x45\x88\x87\x71\x50\xf4\x63\x27\xc2\xc7\xa6\xf7\x12\x67\x0b\xfd\x62\x37\xa2\x94\x52\x49\x48\x59\x94\x8f\x5e\x37\xc0\xe5\x86\x65\x6b\x11\x9a\x0e\x01\xc8\x1a\xce\xe5\x7c\x17\x75\xa3\xa1\x46\xe8\xfb\xaf\xc9\x9c\xd2\x03\xfc\x98\x19\x56\x87\xfb\x94\xa8\x8a\x4f\x44\x28\x0b\x03\xf0\x89\x5e\x0e\xca\x84\xdb\x08\x7c\x1b\xf7\xc4\x84\x3c\x85\x59\x73\x68\xe8\x39\x84\x11\x31\xe0\x27\x10\x9d\xaa\x7b\x81\x72\xa2\x5e\x11\x35\x5f\xa9\xa9\x20\x5a\xc3\x24\x94\x1a\x9f\xe4\x92\xc4\x84\x21\xf0\x68\x1a\x47\xe2\x80\x80\x3e\x8b\xd9\x1b\x11\x3e\x0f\xa1\x59\x76\x07\x43\x0b\xcb\x0a\xd5\x0b\x94\x08\xde\x00\x66\xd6\xa2\x32\x4d\x09\xcf\x6e\x99\x13\x36\x54\xdd\x64\xe8\xc8\xf7\x0c\xd6\x44\x53\x43\x75\x8b\x5c\xd5\xa0\xe7\x7e\x2d\x3f\xa1\xcb\x3f\x7e\xfe\xd7\x61\x24\xb2\x88\x1d\xfd\x20\x28\xab\x59\x18\xc3\x89\xb9\xc3\x97\x82\x71\xdb\x54\xa5\x17\x15\x15\xab\x2e\x85\xee\xb1\x0a\xb3\x07\x13\x01\x59\xbc\xa5\xfe\x13\xcc\x4a\x95\x9e\x88\xe9\x26\x72\x21\xac\x8d\x14\xee\x69\x38\xe1\x49\xf5\x2e\xc5\x91\x25\xb4\x49\xcb\x55\xc5\xa0\x02\x9f\x01\x87\x70\xb3\x1f\x08\x44\x0c\xe6\x87\x6e\x66\x00\xa3\x24\x11\x72\x2f\x58\xe6\x26\x33\x39\xbd\x9d\x34\xe1\x7a\xa5\x74\xb9\x21\x22\x89\x26\xff\x66\x8c\xe9\x03\x62\xc4\x39\x1e\xcd\x0c\x03\x74\x54\xe1\x2f\xdf\x80\xc9\x6b\xb7\xa8\x40\xcd\x86\x6e\x85\x70\xbb\x7d\x65\x86\xfb\xe3\xd1\xea\xe5\x33\x29\x31\x19\x8b\xa1\xd5\xd9\x02\xd6\xb7\xa1\x22\xdf\xa7\x70\x18\x55\x3a\x2d\xd3\x68\x0a\x80\x9b\xb0\x60\x53", b"\x9c\x7d\x40\xe2\x14\x08\x2b\xd5\xe7\x1f\x3b\xf4\xbe\x99\x78\x93\x03\xf3\x8e\x85\x1a\x76\xf8\x8c\xb9\x0a\xff\x71\x30\x80\xc5\x87", b"\x24\xca\x23\xbe\x94\xc6\x24\xb9\xd7\x36\x32\x8b\x53\x78\x2b\x5f\xeb\x38\x4d\xc9\xfe\x63\x70\x01\x6c\xc3\xf9\x7d\x8f\x48\xb6\xd0", )?; test( HashAlgorithm::SHA512, b"\x62\xf0\xcb\x1b\xb0\x7f\x64\x97\xa1\xdc\x7a\x66\x95\x57\x65\xa9\xcc\x40\x3b\xde\x03\xfe\xf4\xe1\x6b\x09\xd7\xec\x54\x5b\x4c\x75\xd0\x8b\x6e\x9c\x4c\x5a\xf7\x23\x25\x48\xd4\x54\x45\x63\x8d\x71\x94\xa1\x99\xef\x15\x34\xe8\x12\x41\xea\xa9\xc7\xe7\x67\xfd\x54\xe2\xca\xce\xea\x4d\x2f\x72\x15\xd3\x7b\xaa\xd6\xb0\x5e\x28\xea\x09\x34\x97\xe2\xe8\xe1\xdb\x6e\x41\xa5\xeb\x13\xff\xa4\xca\xa2\x71\x08\xf2\x26\x3a\x74\xcf\x54\xbd\x5b\x6a\x6b\x62\x28\x4b\xac\x99\xfd\x79\x77\xaa\xa8\xff\xff\x18\xfa\x8a\x70\xab\x0d\xeb\xdf", p, q, g, b"\x73\x55\x4a\x69\xe1\xa0\x9f\x61\x91\xf0\xad\xed\x54\x2a\x07\x7e\xe8\xc8\x14\x26\x5d\x74\x5d\x9a\xe5\xc7\x92\xf4\x42\xc5\xfa\x47\xb3\x46\x43\xd3\xba\x1d\x51\x47\x16\x18\x98\xde\x51\x88\xa8\x07\x14\xee\x36\x51\x2a\x61\x8a\x33\xe4\x03\x00\xff\x11\x87\xe5\x53\xf5\x44\x33\xe1\x74\x66\xaf\x48\x64\x72\xbc\x07\x78\xaf\x55\xba\x73\x46\xc9\x61\xd7\xf1\x3a\xc6\xd8\xd6\xac\x9a\x42\x09\x2c\x01\x57\x9e\xe2\x17\x05\x90\xcb\xc3\xb4\x5e\xef\x79\x5b\x5d\x9e\x5d\x0a\x84\x49\x43\x9a\xb3\x07\xc1\x4c\x56\x74\xc4\xa7\xa3\xea\xf8\xb2\x40\xef\x36\xdd\x21\xf4\x3c\xce\xd5\x8c\x2d\xcf\x23\xc3\x14\x36\x4e\x8e\x31\x4e\x96\x71\xe8\x08\x13\xd1\x85\x80\x13\x58\xd5\xdf\x61\xd7\xe7\xec\x0d\xd6\x9e\x90\xc2\xcc\x75\xc1\xc3\x54\x3e\xfe\xca\x82\xb2\xec\x6e\xc5\x9e\x6c\x99\xbc\xd1\xa8\x63\x1c\x62\x28\xe2\x16\x88\x40\x82\xda\x11\x91\x25\xcb\x0a\x80\xc8\xfe\x34\x4a\xfe\x66\xe0\xf2\x06\x46\x43\x24\x65\xf3\xe0\x09\x6a\x17\x72\x5a\x88\x67\xb3\xbd\xba\x3c\x69\xa1\xaa\xcb\xb8\xd6\x47\x55\xb7\xf2\xa3\xdf\x0a\x49\xba\x0b\x21\x14\xe1\x12\xd4\xca\xe0\xad\x6d\x8d\x0f\xd6\x18\xe5\x4d\x53\xf0\x7b\xa1\x09\xb7\x5a\x54\xa9\x89\x61\x8b\x28\x63\xe4\x41\x5e\x17\x6e\x0b\xfd\x88\xdb\xf3\x65\x53\xca\x85\x3b\xb3\x63\x16\xc6\x6e\xb9\x3d\xa3\x4f\xf3\xae\x74\xcd\x5f\x18\x7f\x49\xbf\x38\xaf\x0f\x39\x3b\x2d\x7f\x85\x4d\xf1\x92\xad\xe2\xdf\x6b\x39\xa1\x76\xd2\x15\x2c\x91\x2b\xba\x24\x8d\x84\xa5\xb0\xaa\x40\x84\xa1\x8b\xb6\x4f\xd1\x36\x97\x3f\x73\xb4\x13\xd7\x7d\xb2\x75\xea\x5e\xce\x93\xce\x2f\xa0\x0d\x7c\x88\x87\xb7\xe5\x0b\x00\x64\x9d\x03\x53\xa7\xf5\x8c\xc6\x3f\x6b\x5f\xbd\xfc", b"\x54\xff\x5d\x3d\xc8\x76\x78\x56\xa1\x0f\x54\x08\x88\x82\xe2\x8c\x11\x09\x80\xef\x9b\x20\x4e\xb5\xf1\x62\xdb\xef\x73\xa3\x7c\x73", b"\x57\xed\x07\x48\x42\x7c\x08\x9d\x63\x95\x52\x8b\x2b\x45\x55\xc0\x1b\x4c\x13\x41\xab\x5f\xb9\x9c\x64\xd1\xcc\x24\x7a\x41\xc3\xa8", )?; test( HashAlgorithm::SHA512, b"\xba\xeb\x12\xa1\xeb\xd8\x05\x7a\x99\xa0\x13\x7e\xe6\x0f\x60\xee\xd1\x0d\x26\xf1\xea\xb2\x2a\xe2\xd9\xad\xbc\x3e\x5f\xfc\x32\x52\xab\xf6\x2b\x61\x47\x07\xad\x25\x46\x14\x1b\xed\x77\x9f\x0c\xfa\xd9\x54\x4a\x74\xe5\x62\xda\x54\x9e\x2f\x7b\x28\x6e\xfb\x61\x54\x49\xb0\x94\x6d\xc7\xc4\x98\xd8\xf1\x21\x50\xb2\xea\xcb\xd2\x71\x57\x96\x6f\x59\x2a\xd5\xf3\xe4\x3a\x24\xc6\x0b\x7e\x06\x63\x0b\x82\xa4\xfd\xb6\x99\x11\x9d\xbd\x87\x8b\x13\xa9\x8b\xf2\x2a\x7b\x3d\xc7\xef\xdd\x99\x2c\xe6\xb8\xa9\x50\xe6\x12\x99\xc5\x66\x3b", p, q, g, b"\x00\x72\x8e\x23\xe7\x4b\xb8\x2d\xe0\xe1\x31\x5d\x58\x16\x4a\x5c\xec\xc8\x95\x1d\x89\xe8\x8d\xa7\x02\xf5\xb8\x78\x02\x0f\xd8\xd2\xa1\x79\x1b\x3e\x8a\xb7\x70\xe0\x84\xac\x23\x97\xd2\x97\x97\x1c\xa8\x70\x8a\x30\xa4\x09\x7d\x86\x74\x01\x53\xee\x2d\xb6\xab\x63\x43\xc5\xb6\xcc\x2c\x8a\x7f\xa5\x90\x82\xa8\xd6\x59\x93\x1c\xc4\x8a\x04\x33\xa0\x33\xdb\xb2\xff\xf3\xaa\x54\x56\x86\xf9\x22\xc7\x06\x3d\xa1\xd5\x2d\x96\x88\x14\x2e\xc6\x4a\x10\x02\x94\x8e\x5d\xa8\x91\x65\xd9\xdf\x8e\xed\x9a\xa4\x69\xb6\x1e\xe0\x21\x0b\x40\x33\x56\x23\x33\x09\x7b\xa8\x65\x99\x44\xe5\xf7\x92\x4e\x04\xa2\x1b\xc3\xed\xc6\xd5\x51\xe2\x02\xe4\xc5\x43\xe9\x75\x18\xf9\x1e\x0c\xab\x49\x11\x10\x29\xb2\x9c\x3a\xa1\xbe\xd5\xf3\x5e\x5c\x90\xfe\xb9\xd3\xc7\x45\x95\x3d\xbf\x85\x9d\xef\xce\x45\x37\xb4\xa0\x98\x01\xfd\xc8\xfe\x69\x99\xfb\xde\x39\x90\x80\x79\x81\x1b\x4b\x99\x2c\x2e\x83\x33\xb9\xf8\x00\xea\x0d\x9f\x0a\x5f\x53\x60\x7e\x30\x89\x42\xe6\x8e\xfe\xf0\x1e\x03\xd7\xcc\xa6\xf1\x96\x87\x2b\xf0\x1f\x43\x6d\x4a\x8e\x05\xfc\x59\xd8\xfb\xc6\xb8\x8a\x16\x6f\x57\xa4\xe9\x9d\x67\xdd\xae\xce\x84\x46\x53\xbe\x77\x81\x97\x47\xdd\x2e\x07\xd5\x81\xc5\x18\xcb\x97\x79\xe9\xf7\x96\x0c\x17\xff\x0b\xae\x71\x0e\xcf\x57\x5b\x09\x59\x1b\x01\x3b\x48\x05\xc8\x8b\x23\x5d\xf2\x62\xe6\x1a\x4c\x94\xf4\x6b\xf9\xa0\x82\x84\x61\x1d\xf4\x4e\xad\xd9\x4f\x44\xce\xf6\x22\x5a\x80\x8e\x21\x1e\x4d\x3a\xf5\xe9\x6b\xce\x64\xa9\x0f\x80\x13\x87\x4f\x10\x74\x9a\x83\x82\xa6\x02\x6a\x85\x5d\x90\x85\x34\x40\xbf\xce\x31\xf2\x58\xb3\xa2\x58\xf7\xb5\xe6\x59\xb4\x3e\x70\x2d\xee\x7c\x24\xc0\x2d\x22\x84", b"\x8d\x35\x7b\x0b\x95\x6f\xb9\x0e\x8e\x0b\x9f\xf2\x84\xce\xdc\x88\xa0\x4d\x17\x1a\x90\xc5\x99\x7d\x8e\xe1\xe9\xbc\x4d\x0b\x35\xff", b"\xab\x37\x32\x9c\x50\x14\x5d\x14\x65\x05\x01\x57\x04\xfd\xc4\xfb\x0f\xd7\x20\x7e\x0b\x11\xd8\xbe\xcb\xad\x93\x4e\x62\x55\xc3\x0c", )?; test( HashAlgorithm::SHA512, b"\x18\x4e\x59\x9a\x4c\x1d\xe8\x6c\x41\x51\x20\x57\x54\xdf\x0b\x19\x12\xc2\xb3\xc5\x32\x55\x2c\x51\xa6\x1c\x64\x59\xdb\x98\xc8\x3e\x59\xd4\xa4\x08\x06\xc6\xa2\xc6\xb3\xfe\x74\xe3\xbb\x9e\x72\x0d\x7d\x0a\x3c\xc1\x1e\xf8\x89\x59\xa8\x99\x0c\x0f\xa0\x57\xa3\x91\x5f\xe0\xdd\x9a\x13\x8a\xa0\xec\x1c\xb1\xab\x69\xd9\x39\x10\xd8\xd6\xf9\xe1\x4f\x3b\x8a\x13\x5d\x3f\x03\x1a\x56\xc7\x6a\x9d\xc3\xae\xd1\x96\x2b\xdf\x05\x81\x5c\x24\x92\xd1\x4f\x23\x24\xd2\xda\x49\x18\x10\xd1\x67\x2b\x63\x3f\x24\x19\xda\x4e\x7e\xbd\xef\x24", p, q, g, b"\x60\x15\x97\x20\x02\x1f\xd2\xd5\xa2\xf5\x75\xb3\x22\x09\x05\x78\x8d\x32\x8d\x0c\x46\x89\x5a\x46\xbb\x98\x59\x42\x46\x72\x09\xec\x28\xd8\xdd\xfd\xc9\x7e\xc3\x4d\xa6\x5b\x16\x4c\xf4\x86\x52\xac\x47\x5d\x89\x78\x95\x9c\xfc\x43\x30\x74\x3e\xd9\x81\x37\x55\x93\x91\xb1\x20\x4d\xa6\xb2\x6b\x45\x12\x11\x40\x7e\x8f\xc7\x7d\x81\x99\x34\xc4\x87\x09\xc8\xea\xdc\x62\x0f\x6d\xb2\x59\x2b\x65\x48\x32\x65\x14\x9a\x32\x44\x67\xd9\x3c\x37\x5d\x97\x23\x0f\x2b\x1a\x68\x28\x97\xcf\x6d\x28\x0d\xf6\x1a\x34\xf2\x0f\x0c\x7c\x72\x9a\x40\x14\x19\x58\x04\x48\x76\xc4\x4e\x59\x5d\x23\x78\xa7\xd2\x2c\x6c\xda\x9a\xb8\x16\x48\x6c\x29\x4e\x4e\xdd\xea\x7a\xda\x88\xb1\x5e\xca\x53\x71\xda\x16\x44\x71\xed\xaf\xcd\xef\xc6\x54\xe6\x4a\x1f\x99\x50\x68\xfa\x85\xdb\xbb\x55\x16\x13\x7b\xc4\x42\xf6\x07\x17\xfe\x59\xc6\x29\x08\x1c\x23\x4f\x27\x19\x5d\x5f\x9c\x2b\xf8\x5c\xdc\x1e\xa4\xca\xe5\x7a\xa9\x08\xcb\xff\x9b\x2a\x53\x35\x3b\x13\xe9\xf6\xfe\x45\xda\xa5\x17\x4c\xd9\x56\x23\x6d\x44\x7b\x52\x01\x1d\x68\x8c\xd2\x2f\x23\x01\x84\x09\xb3\x9a\x36\x07\x9c\xb5\x3e\x03\xb6\xd3\xa7\x52\x73\x32\x97\xfe\xa4\xca\x27\xc6\x39\x5b\xec\xef\x40\x81\xd2\x01\xf4\x1d\x4a\x00\xe9\x9d\x95\xf4\x22\x81\xdc\xf4\x4b\x9e\xf6\x75\x49\x98\xd9\x42\x31\x93\x7c\x82\x59\x42\x18\xa7\x84\x63\xcc\x83\x71\x93\xde\x6b\xf1\xd3\xc3\xec\x31\xd8\xdc\x54\x68\xcb\x56\xde\xfc\x9c\x70\xd0\x8b\x95\xb0\x29\xd9\x7a\xa0\x43\xd5\x57\xf6\x28\x6b\x87\xee\x40\x98\x44\x2d\xf4\x95\xc0\xad\x8a\xe4\xd4\xae\x03\x73\x12\xc5\xf7\x23\x90\x32\xc0\x3b\x08\x8c\x10\x36\xfa\xd7\x77\x4b\x15\x19\x70\x92\x42\xc9\x51\x1e\x6e", b"\x07\x9d\x4d\xf1\x4a\xd7\x03\xa4\x35\xb2\x1b\xc7\x0a\x03\x45\x6c\xa8\x22\xb8\x76\xc9\xac\xcb\x01\x8b\xdd\xd6\x74\xbd\x63\x92\xd7", b"\x6c\x77\x65\xe1\xf1\xed\xdf\x91\x5a\x56\xa5\x73\x90\xdb\x45\x63\x6e\x52\xf0\x83\xce\x44\x07\x66\xad\x4f\x32\x58\x0f\x72\x24\x83", )?; test( HashAlgorithm::SHA512, b"\xb1\x89\xdd\x34\xf5\x8f\x3e\xfa\x85\xb6\xf9\x76\x77\xed\xfb\x82\x66\x4c\xbe\x43\xa2\x55\x0c\x33\x6f\xfa\x08\x70\x5b\xbd\xa2\x54\x5e\xf2\x44\xa2\x75\x01\x4c\x6a\x26\x59\x71\xf4\xc3\x65\x8e\x5e\x8d\x6a\x3f\xaf\xc8\x89\xf3\xc4\xed\xa6\xb5\x61\x60\x92\x95\x4b\x15\xc6\x04\x35\xef\xd7\x68\x06\xe2\x85\x57\xc0\x5f\xaa\xaa\x8a\x05\xc2\x62\x65\x78\x40\x86\x5f\xf6\x9c\x51\x1a\x68\xd1\x30\x22\xa7\x12\xd3\x5b\xde\x13\x8e\xb7\xa2\xf8\xf1\xa8\x7b\x34\x2c\x7c\xaf\x38\x8c\x1a\x8b\x95\x07\x9b\xc4\xa8\x00\x3e\xef\x84\xb8\x99", p, q, g, b"\x05\xe2\x80\x31\x08\x10\x71\x5d\x29\xea\x1c\xa0\x0a\x70\x03\x78\xbd\x59\x79\x49\x3b\x98\x03\x17\x4c\x93\x2b\x7d\xad\xb7\x02\x9a\x9a\x9f\x9c\x91\xcf\x8f\x93\x8a\xf2\xbc\xea\xa0\x52\xf2\x27\x3f\x0d\xe3\x93\xb0\xf7\x54\x44\x90\xd6\x93\xf5\x29\xa6\x8b\x81\x2e\x2e\x58\x9c\xc0\x92\xb8\x3e\xf8\x47\xc5\x30\x60\x39\xaa\x8e\xaf\x22\x51\x28\x92\x61\x45\x89\x3a\x51\x55\x1d\xb3\x82\xfd\xa4\xb6\x3e\x5a\xbc\x10\xfd\x07\x61\x00\x68\x4d\x4c\xa6\x57\xc8\x9b\x22\x65\xde\x6e\x0f\x04\x73\xf0\x1b\xb2\x22\xb2\xbc\x50\xec\x1c\x5f\xcd\xe9\x16\x18\x31\x01\x8a\xab\x30\x14\xa9\x56\x03\x3b\xb0\xa8\x38\x66\xdf\x11\x91\x58\x08\xf9\xe7\x46\x16\x45\xc8\x9c\x6e\x17\xab\x65\xdb\xf9\x7c\xbf\x4a\xc1\x16\x4d\x67\x1a\x15\x16\xca\x81\x64\x5b\xc3\xe0\x99\x13\xa0\x3f\x30\x64\x1b\xd0\x92\x00\x83\x57\x8c\xa8\x4d\xf7\x1f\x62\xeb\x75\x6b\xa4\x45\xa0\xdc\x44\xf8\x5a\x9e\x4f\x72\xce\x5f\x6b\xf8\x2c\xcb\xd6\x74\xd2\xce\x3c\x4a\xfc\x30\x05\x62\xa7\xdb\xd3\xe8\xab\x83\x89\x93\xf9\xde\xcc\x99\x33\xdc\x07\xdc\x01\xb5\x02\xfe\xe5\xb3\x90\x46\x1a\x8c\x82\xc4\xe6\x96\x15\xf1\x21\xb3\xf9\xfd\x4f\x0c\x8b\x76\x20\xa2\x59\x96\xdf\x43\xd7\xcf\x35\x5f\x15\xbe\x09\xe2\xc8\x21\x78\xc6\xf8\x83\x6c\x36\xc1\xd3\xef\x26\xad\x05\x21\x9f\xb5\x7e\x85\xef\x16\x2c\x8d\xd8\xf0\xe5\x50\x14\x76\x9d\x53\xcb\xa4\x78\xa2\xaa\x66\xd9\x0d\x8a\xcd\x6c\xb0\x48\x9d\x1e\xea\x46\xc2\xc4\x1b\xd5\x49\x5a\xb8\xde\xf4\x3b\x2c\xd5\xbb\x26\x73\x94\x5c\x21\xc8\x0a\x48\x33\xfd\x75\xd8\x84\xc7\x67\x5c\x09\xe7\x19\x1f\xb2\x6e\x92\xc5\x4c\x7c\x82\x08\xd0\xa0\xe8\xde\xe7\x5c\x29\x68\xe9\x62\xde\x44\x93\xe8", b"\x9f\xd1\x05\xc7\x4a\x0d\x36\x97\x37\x40\x86\x7c\xcc\x1c\x73\x1c\xf1\xc5\x0c\x79\x35\xd5\xc0\x9e\x92\xf5\x74\xd7\xa5\x69\x15\x7e", b"\x50\x1f\x50\xc3\x2b\x02\x88\x67\x2e\x02\xac\xa7\x8f\x90\xf4\x46\xac\xf9\x26\x26\x36\x59\x57\xa3\x75\x55\x0c\x77\x98\x0c\x3c\x17", )?; test( HashAlgorithm::SHA512, b"\x42\xc0\x65\xfa\xdd\x56\xd6\xa1\xfe\x68\xdd\x4e\x86\xc1\x7e\xfd\x76\xd0\xf9\xdb\x87\x03\x6b\xd7\xb6\x09\x15\x9d\x66\x84\x7f\x46\xde\x01\xb8\xae\x43\x59\x03\x60\xfa\x32\x45\x59\xa2\xd7\x09\xd4\x5c\xf0\x10\x34\xf5\xfa\xcb\x7f\x52\x32\x4e\x60\xdd\x46\x4a\x58\x3d\x42\xe4\x12\x65\x9d\x84\x20\xf7\x26\x5e\x30\xcf\x82\xbb\xbc\xb2\xc9\x9b\x0f\x00\xca\x6a\x46\xd2\x85\x56\x42\x87\x89\xf4\x15\x00\x0d\xc3\x1b\xab\xbd\x67\xcc\xc8\xfb\xaa\x84\xa8\x80\x46\x6b\xca\x47\x83\xea\xf0\x0b\x7f\x78\x23\x1c\x66\x71\x26\x43\x3e\x6a", p, q, g, b"\xb2\x65\xed\xfe\xd7\x7b\x3a\xd5\x11\xe5\x6d\x58\x31\x29\xb1\x2e\x57\x96\xd6\x59\xd4\x84\xa2\xfc\xe3\x50\x66\x1f\x79\xe5\x45\xdd\x0a\x06\xc2\x37\x74\xc8\xba\x2f\xb5\x10\x1a\x28\x48\xc4\x13\xdf\xc5\xb3\x74\xa7\xc5\xff\x3a\xcc\x73\x32\xf0\xff\x8b\xd6\xf5\xfa\x88\x2c\x0a\x67\x68\x93\x08\xbe\x71\x54\xc4\xef\xc5\x18\x35\xf3\x49\x52\x54\x19\xed\x72\x2a\x90\xbf\x26\xdd\xde\xd6\x5b\xc8\x96\x2b\xa1\x1d\xe9\xe7\x34\x44\x25\x71\xaf\xfc\x2d\x42\xb9\xf3\xf5\x4a\x46\x53\x5a\xe9\xeb\x01\x36\x1a\xdf\x03\xfc\x28\x41\x0a\xbf\x41\xdb\x3a\xe4\x11\x3d\xa4\xc4\x0e\x9a\x36\x8f\x9c\xd0\x29\xbe\x4d\x98\xc6\x6d\x83\x5d\x03\x4e\x3c\x86\x54\x4b\x60\xbc\xb0\x1f\xeb\x38\x3b\x2a\xdd\x9a\xfe\x7b\x62\x51\xa1\x7a\xd4\xe5\x43\x9a\x9c\xd2\xd1\xbf\x62\xb6\xcf\x53\x77\xc0\x97\xb7\x26\x8b\xd7\x36\xcc\xa9\xce\xb8\x22\xe5\xd1\x84\x4a\x09\xfa\x69\xc7\x82\x17\xc3\xd6\x73\x7f\x0b\xf4\x5e\x32\x36\x50\x8b\x5a\x3f\x5c\x46\x6d\xd0\xd7\x5a\xce\x95\xd4\x47\xf9\xbd\x7a\xa9\xee\x57\xbd\x10\xee\x3c\x5e\x83\x89\xa0\x6c\x00\x85\x7e\x69\x97\x94\xf5\xca\xcc\x7d\xc5\xbb\x15\x04\x42\x1d\xc9\x20\x56\x56\x18\xbe\xf0\x5d\xc1\x71\x3b\x6f\x08\xbc\x00\x68\x1c\x5a\x1c\x06\x85\x35\x97\x29\xfe\x4b\x54\x40\x90\xcc\xce\xaa\x82\xf4\xfe\xfa\x9f\x11\x17\xbf\x1e\x37\x1b\x99\xfe\x4e\xd7\x16\x35\xda\xd4\x15\x01\x7a\x62\x34\x1d\x70\x42\x27\xee\x7c\xfb\x64\xa8\xde\xae\x90\xd8\x6c\x0c\xfd\x37\xed\x36\x3d\x91\xa4\xa0\x6f\xd0\x6f\x64\xdb\xd8\x14\x2c\x12\x50\x3f\x49\xee\xb1\xb9\xa9\x71\xae\xb3\x43\xf1\x5c\xd2\x7d\x27\x9b\x99\xd4\xcf\xa5\x1f\x12\x12\x59\xb3\xc1\xb5\x5d\x28\xd9\x94\xbb\x32\x99", b"\x6e\xd8\x2a\xf8\xe8\x9e\x38\xc4\x9a\x58\x01\x0f\x05\x64\x16\x5a\x16\xa7\x6a\x2b\xfb\x34\x84\x66\xd9\xb4\xa9\x1e\x5c\xe5\x3a\xb2", b"\x8c\x46\x6a\x8b\x3e\x4c\x90\x88\x6f\x29\x98\x6a\x4d\x51\x39\x04\xf3\x1d\xb4\x3a\x68\xce\x88\x03\x11\x40\x3c\xc7\x55\x46\x66\x04", )?; Ok(()) } ���������������������������������sequoia-openpgp-2.0.0/src/crypto/tests/ecdsa.rs�����������������������������������������������������0000644�0000000�0000000�00000045636�10461020230�0017653�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������//! Low-level ECDSA tests. use crate::Result; use crate::crypto::mpi; use crate::packet::{prelude::*, signature::subpacket::*}; use crate::types::*; #[test] fn fips_186_4() -> Result<()> { if ! PublicKeyAlgorithm::ECDSA.is_supported() { eprintln!("Skipping because ECDSA is not supported."); return Ok(()); } fn test(curve: Curve, hash: HashAlgorithm, msg: &[u8], x: &[u8], y: &[u8], r: &[u8], s: &[u8]) -> Result<()> { if ! curve.is_supported() { eprintln!("Skipping because {} is not supported.", curve); return Ok(()); } if ! hash.is_supported() { eprintln!("Skipping because {} is not supported.", hash); return Ok(()); } let now = Timestamp::now(); let key: Key<key::PublicParts, key::PrimaryRole> = Key4::new(now, PublicKeyAlgorithm::ECDSA, mpi::PublicKey::ECDSA { curve: curve.clone(), q: mpi::MPI::new_point(x, y, curve.bits().unwrap()), })?.into(); let mut h = hash.context()?.for_digest(); h.update(msg); let mut d = h.into_digest()?; let sig: Signature = Signature4::new(SignatureType::Binary, PublicKeyAlgorithm::ECDSA, hash, SubpacketArea::new(vec![ Subpacket::new( SubpacketValue::SignatureCreationTime(now), false)?, ])?, SubpacketArea::default(), [d[0], d[1]], mpi::Signature::ECDSA { r: mpi::MPI::new(r), s: mpi::MPI::new(s), }).into(); sig.verify_digest(&key, &d)?; // Sanity check: Change the digest and retry. d[0] ^= 1; sig.verify_digest(&key, &d).unwrap_err(); Ok(()) } test( Curve::NistP256, HashAlgorithm::SHA224, b"\xfc\x3b\x82\x91\xc1\x72\xda\xe6\x35\xa6\x85\x9f\x52\x5b\xea\xf0\x1c\xf6\x83\x76\x5d\x7c\x86\xf1\xa4\xd7\x68\xdf\x7c\xae\x05\x5f\x63\x9e\xcc\xc0\x8d\x7a\x02\x72\x39\x4d\x94\x9f\x82\xd5\xe1\x2d\x69\xc0\x8e\x24\x83\xe1\x1a\x1d\x28\xa4\xc6\x1f\x18\x19\x31\x06\xe1\x2e\x5d\xe4\xa9\xd0\xb4\xbf\x34\x1e\x2a\xcd\x6b\x71\x5d\xc8\x3a\xe5\xff\x63\x32\x8f\x83\x46\xf3\x55\x21\xca\x37\x8b\x31\x12\x99\x94\x7f\x63\xec\x59\x3a\x5e\x32\xe6\xbd\x11\xec\x4e\xdb\x0e\x75\x30\x2a\x9f\x54\xd2\x12\x26\xd2\x33\x14\x72\x9e\x06\x10\x16", b"\xf0\x4e\x9f\x28\x31\xd9\x69\x7a\xe1\x46\xc7\xd4\x55\x2e\x5f\x91\x08\x5c\xc4\x67\x78\x40\x0b\x75\xb7\x6f\x00\x20\x52\x52\x94\x1d", b"\xbd\x26\x71\x48\x17\x4c\xd0\xc2\xb0\x19\xcd\x0a\x52\x56\xe2\xf3\xf8\x89\xd1\xe5\x97\x16\x03\x72\xb5\xa1\x33\x9c\x8d\x78\x7f\x10", b"\x5d\x95\xc3\x85\xee\xba\x0f\x15\xdb\x0b\x80\xae\x15\x19\x12\x40\x91\x28\xc9\xc8\x0e\x55\x42\x46\x06\x7b\x8f\x6a\x36\xd8\x5e\xa5", b"\xdb\x5d\x8a\x1e\x34\x5f\x88\x3e\x4f\xcb\x38\x71\x27\x6f\x17\x0b\x78\x3c\x1a\x1e\x9d\xa6\xb6\x61\x59\x13\x36\x8a\x85\x26\xf1\xc3", )?; test( Curve::NistP256, HashAlgorithm::SHA256, b"\x21\x18\x8c\x3e\xdd\x5d\xe0\x88\xda\xcc\x10\x76\xb9\xe1\xbc\xec\xd7\x9d\xe1\x00\x3c\x24\x14\xc3\x86\x61\x73\x05\x4d\xc8\x2d\xde\x85\x16\x9b\xaa\x77\x99\x3a\xdb\x20\xc2\x69\xf6\x0a\x52\x26\x11\x18\x28\x57\x8b\xcc\x7c\x29\xe6\xe8\xd2\xda\xe8\x18\x06\x15\x2c\x8b\xa0\xc6\xad\xa1\x98\x6a\x19\x83\xeb\xee\xc1\x47\x3a\x73\xa0\x47\x95\xb6\x31\x9d\x48\x66\x2d\x40\x88\x1c\x17\x23\xa7\x06\xf5\x16\xfe\x75\x30\x0f\x92\x40\x8a\xa1\xdc\x6a\xe4\x28\x8d\x20\x46\xf2\x3c\x1a\xa2\xe5\x4b\x7f\xb6\x44\x8a\x0d\xa9\x22\xbd\x7f\x34", b"\x10\x5d\x22\xd9\xc6\x26\x52\x0f\xac\xa1\x3e\x7c\xed\x38\x2d\xcb\xe9\x34\x98\x31\x5f\x00\xcc\x0a\xc3\x9c\x48\x21\xd0\xd7\x37\x37", b"\x6c\x47\xf3\xcb\xbf\xa9\x7d\xfc\xeb\xe1\x62\x70\xb8\xc7\xd5\xd3\xa5\x90\x0b\x88\x8c\x42\x52\x0d\x75\x1e\x8f\xaf\x3b\x40\x1e\xf4", b"\x54\x2c\x40\xa1\x81\x40\xa6\x26\x6d\x6f\x02\x86\xe2\x4e\x9a\x7b\xad\x76\x50\xe7\x2e\xf0\xe2\x13\x1e\x62\x9c\x07\x6d\x96\x26\x63", b"\x4f\x7f\x65\x30\x5e\x24\xa6\xbb\xb5\xcf\xf7\x14\xba\x8f\x5a\x2c\xee\x5b\xdc\x89\xba\x8d\x75\xdc\xbf\x21\x96\x6c\xe3\x8e\xb6\x6f", )?; test( Curve::NistP256, HashAlgorithm::SHA384, b"\x78\x43\xf1\x57\xef\x85\x66\x72\x2a\x7d\x69\xda\x67\xde\x75\x99\xee\x65\xcb\x39\x75\x50\x8f\x70\xc6\x12\xb3\x28\x91\x90\xe3\x64\x14\x17\x81\xe0\xb8\x32\xf2\xd9\x62\x71\x22\x74\x2f\x4b\x58\x71\xce\xea\xfc\xd0\x9b\xa5\xec\x90\xca\xe6\xbc\xc0\x1a\xe3\x2b\x50\xf1\x3f\x63\x91\x8d\xfb\x51\x77\xdf\x97\x97\xc6\x27\x3b\x92\xd1\x03\xc3\xf7\xa3\xfc\x20\x50\xd2\xb1\x96\xcc\x87\x2c\x57\xb7\x7f\x9b\xdb\x17\x82\xd4\x19\x54\x45\xfc\xc6\x23\x6d\xd8\xbd\x14\xc8\xbc\xbc\x82\x23\xa6\x73\x9f\x6a\x17\xc9\xa8\x61\xe8\xc8\x21\xa6", b"\x76\x0b\x56\x24\xbd\x64\xd1\x9c\x86\x6e\x54\xcc\xd7\x4a\xd7\xf9\x88\x51\xaf\xdb\xc3\xdd\xea\xe3\xec\x2c\x52\xa1\x35\xbe\x9c\xfa", b"\xfe\xca\x15\xce\x93\x50\x87\x71\x02\xee\xe0\xf5\xaf\x18\xb2\xfe\xd8\x9d\xc8\x6b\x7d\xf0\xbf\x7b\xc2\x96\x3c\x16\x38\xe3\x6f\xe8", b"\xbd\xff\x14\xe4\x60\x03\x09\xc2\xc7\x7f\x79\xa2\x59\x63\xa9\x55\xb5\xb5\x00\xa7\xb2\xd3\x4c\xb1\x72\xcd\x6a\xcd\x52\x90\x5c\x7b", b"\xb0\x47\x9c\xdb\x3d\xf7\x99\x23\xec\x36\xa1\x04\xa1\x29\x53\x4c\x5d\x59\xf6\x22\xbe\x7d\x61\x3a\xa0\x45\x30\xad\x25\x07\xd3\xa2", )?; test( Curve::NistP256, HashAlgorithm::SHA512, b"\xea\x95\x85\x9c\xc1\x3c\xcc\xb3\x71\x98\xd9\x19\x80\x3b\xe8\x9c\x2e\xe1\x0b\xef\xdc\xaf\x5d\x5a\xfa\x09\xdc\xc5\x29\xd3\x33\xae\x1e\x4f\xfd\x3b\xd8\xba\x86\x42\x20\x3b\xad\xd7\xa8\x0a\x3f\x77\xee\xee\x94\x02\xee\xd3\x65\xd5\x3f\x05\xc1\xa9\x95\xc5\x36\xf8\x23\x6b\xa6\xb6\xff\x88\x97\x39\x35\x06\x66\x0c\xc8\xea\x82\xb2\x16\x3a\xa6\xa1\x85\x52\x51\xc8\x7d\x93\x5e\x23\x85\x7f\xe3\x5b\x88\x94\x27\xb4\x49\xde\x72\x74\xd7\x75\x4b\xde\xac\xe9\x60\xb4\x30\x3c\x5d\xd5\xf7\x45\xa5\xcf\xd5\x80\x29\x3d\x65\x48\xc8\x32", b"\xc6\x2c\xc4\xa3\x9a\xce\x01\x00\x6a\xd4\x8c\xf4\x9a\x3e\x71\x46\x69\x55\xbb\xee\xca\x5d\x31\x8d\x67\x26\x95\xdf\x92\x6b\x3a\xa4", b"\xc8\x5c\xcf\x51\x7b\xf2\xeb\xd9\xad\x6a\x9e\x99\x25\x4d\xef\x0d\x74\xd1\xd2\xfd\x61\x1e\x32\x8b\x4a\x39\x88\xd4\xf0\x45\xfe\x6f", b"\x6e\x7f\xf8\xec\x7a\x5c\x48\xe0\x87\x72\x24\xa9\xfa\x84\x81\x28\x3d\xe4\x5f\xcb\xee\x23\xb4\xc2\x52\xb0\xc6\x22\x44\x2c\x26\xad", b"\x3d\xfa\xc3\x20\xb9\xc8\x73\x31\x81\x17\xda\x6b\xd8\x56\x00\x0a\x39\x2b\x81\x56\x59\xe5\xaa\x2a\x6a\x18\x52\xcc\xb2\x50\x1d\xf3", )?; test( Curve::NistP384, HashAlgorithm::SHA224, b"\x94\xf8\xbf\xbb\x9d\xd6\xc9\xb6\x19\x3e\x84\xc2\x02\x3a\x27\xde\xa0\x0f\xd4\x83\x56\x90\x9f\xae\xc2\x16\x19\x72\x43\x96\x86\xc1\x46\x18\x4f\x80\x68\x6b\xc0\x9e\x1a\x69\x8a\xf7\xdf\x9d\xea\x3d\x24\xd9\xe9\xfd\x6d\x73\x48\xa1\x46\x33\x9c\x83\x92\x82\xcf\x89\x84\x34\x5d\xc6\xa5\x10\x96\xd7\x4a\xd2\x38\xc3\x52\x33\x01\x2a\xd7\x29\xf2\x62\x48\x1e\xc7\xcd\x64\x88\xf1\x3a\x6e\xba\xc3\xf3\xd2\x34\x38\xc7\xcc\xb5\xa6\x6e\x2b\xf8\x20\xe9\x2b\x71\xc7\x30\xbb\x12\xfd\x64\xea\x17\x70\xd1\xf8\x92\xe5\xb1\xe1\x4a\x9e\x5c", b"\x3a\x65\xb2\x6c\x08\x10\x2b\x44\x83\x8f\x8c\x23\x27\xea\x08\x0d\xaf\x1e\x4f\xc4\x5b\xb2\x79\xce\x03\xaf\x13\xa2\xf9\x57\x5f\x0f\xff\x9e\x2e\x44\x23\xa5\x85\x94\xce\x95\xd1\xe7\x10\xb5\x90\xce", b"\xfe\x9d\xcb\xcb\x2e\xc6\xe8\xbd\x8e\xd3\xaf\x3f\xf0\xaa\x61\x9e\x90\x0c\xc8\xba\xb3\xf5\x0f\x6e\x5f\x79\xfa\xc0\x91\x64\xfb\x6a\x20\x77\xcc\x4f\x1f\xed\x3e\x9e\xc6\x89\x9e\x91\xdb\x32\x9b\xf3", b"\x67\x70\xee\xa9\x36\x9d\x67\x18\xe6\x0d\xd0\xb9\x1a\xee\x84\x5f\xf7\xed\x7e\x0f\xcc\x91\x67\x5f\x56\xd3\x2e\x52\x27\xfd\x3a\x46\x12\xbb\xcb\x15\x56\xfe\x94\xa9\x89\xb9\xe3\xbc\xc2\x5b\xb2\x0e", b"\xc4\x30\x72\xf7\x06\xc9\x81\x26\xd0\x6a\x82\xb0\x42\x51\xe3\xec\xb0\xba\x66\xc4\xbb\x6c\xd7\xc0\x25\x91\x9b\x9c\xc6\x01\x9c\xdc\x63\x52\x56\xd2\xa7\xfa\x01\x7b\x80\x6b\x1e\x88\x64\x9d\x2c\x0d", )?; test( Curve::NistP384, HashAlgorithm::SHA256, b"\x64\xf9\xf0\x5c\x28\x05\xac\xf5\x9c\x04\x7b\x5f\x5d\x2e\x20\xc3\x92\x77\xb6\xd6\x38\x0f\x70\xf8\x7b\x72\x32\x7a\x76\x17\x0b\x87\x2b\xfe\x4b\x25\xc4\x51\x60\x2a\xcf\xb6\xa6\x31\xbb\x88\x5e\x26\x55\xae\xe8\xab\xe4\x4f\x69\xc9\x0f\xb2\x1f\xfd\xe0\x3c\xef\x2a\x45\x2c\x46\x8c\x63\x69\x86\x7d\xfd\x8a\xa2\x6a\xc2\x4e\x16\xaa\x53\xb2\x92\x37\x5a\x8d\x8f\xbf\x98\x8e\x30\x2b\xf0\x00\x88\xe4\xc0\x61\xaa\x12\xc4\x21\xd8\xfe\x3c\xbd\x72\x73\xb0\xe8\x99\x37\x01\xdf\x1c\x59\x43\x1f\x43\x6a\x08\xb8\xe1\x5b\xd1\x23\xd1\x33", b"\x16\x6e\x6d\x96\xcb\x60\xd9\x16\xfd\x19\x88\x8a\x2d\xd9\x45\xa3\x30\x6f\xf0\xd7\xb0\xa5\xe3\x07\x29\xf4\x7d\x3d\xac\x3d\xe2\xbe\x3f\xd5\xcd\x74\x37\xe9\xa8\x0d\x6c\x48\xcf\x96\x0d\x2d\x36\xf8", b"\xe6\xb2\xb7\x0f\x13\x10\x92\xae\x21\x0f\x29\xcc\x6b\xad\x70\x13\x18\xbd\xdb\x31\xbd\xdf\x92\x16\x95\x85\x5c\x62\x08\x94\x11\x00\xd0\xce\xe5\xd1\x07\x99\xf8\xb8\x35\xaf\xe3\xea\x51\x0e\x82\x29", b"\xd9\x12\x4c\x42\x85\x80\x80\xc6\x24\x00\xe4\xd4\xd8\x13\x63\x04\xe0\x3d\x91\x0c\xbe\x9b\x9b\x34\x87\xf4\xd2\x7c\x7e\x05\x40\xa3\x14\xd3\x4b\xef\x8c\x85\x00\x45\xc8\x74\x6c\xa6\x31\xc1\x1c\x42", b"\xbb\xf6\x42\x4a\x3b\x70\x16\x6f\xa7\x99\xf4\x9e\x91\x84\x39\xd5\x15\x32\x70\x39\x25\x8e\xf9\xbd\x88\x43\x5a\x59\xc9\xc1\x96\x59\xf8\xec\x3c\x86\x60\x72\x0b\x0c\x08\x35\x4f\xf6\x0e\x0f\x5a\x76", )?; test( Curve::NistP384, HashAlgorithm::SHA384, b"\x0e\x64\x6c\x6c\x3c\xc0\xf9\xfd\xed\xef\x93\x4b\x71\x95\xfe\x38\x37\x83\x6a\x9f\x6f\x26\x39\x68\xaf\x95\xef\x84\xcd\x03\x57\x50\xf3\xcd\xb6\x49\xde\x74\x5c\x87\x4a\x6e\xf6\x6b\x3d\xd8\x3b\x66\x06\x8b\x43\x35\xbc\x0a\x97\x18\x41\x82\xe3\x96\x5c\x72\x2b\x3b\x1a\xee\x48\x8c\x36\x20\xad\xb8\x35\xa8\x14\x0e\x19\x9f\x4f\xc8\x3a\x88\xb0\x28\x81\x81\x6b\x36\x6a\x09\x31\x6e\x25\x68\x52\x17\xf9\x22\x11\x57\xfc\x05\xb2\xd8\xd2\xbc\x85\x53\x72\x18\x3d\xa7\xaf\x3f\x0a\x14\x14\x8a\x09\xde\xf3\x7a\x33\x2f\x8e\xb4\x0d\xc9", b"\xa3\x9a\xc3\x53\xca\x78\x79\x82\xc5\x77\xaf\xf1\xe8\x60\x1c\xe1\x92\xaa\x90\xfd\x0d\xe4\xc0\xed\x62\x7f\x66\xa8\xb6\xf0\x2a\xe5\x13\x15\x54\x3f\x72\xff\xc1\xc4\x8a\x72\x69\xb2\x5e\x7c\x28\x9a", b"\x90\x64\xa5\x07\xb6\x6b\x34\x0b\x6e\x0e\x0d\x5f\xfa\xa6\x7d\xd2\x0e\x6d\xaf\xc0\xea\x6a\x6f\xae\xe1\x63\x51\x77\xaf\x25\x6f\x91\x08\xa2\x2e\x9e\xdf\x73\x6a\xb4\xae\x8e\x96\xdc\x20\x7b\x1f\xa9", b"\xee\x82\xc0\xf9\x05\x01\x13\x6e\xb0\xdc\x0e\x45\x9a\xd1\x7b\xf3\xbe\x1b\x1c\x8b\x8d\x05\xc6\x00\x68\xa9\x30\x6a\x34\x63\x26\xff\x73\x44\x77\x6a\x95\xf1\xf7\xe2\xe2\xcf\x94\x77\x13\x0e\x73\x5c", b"\xaf\x10\xb9\x0f\x20\x3a\xf2\x3b\x75\x00\xe0\x70\x53\x6e\x64\x62\x9b\xa1\x92\x45\xd6\xef\x39\xaa\xb5\x7f\xcd\xb1\xb7\x3c\x4c\x6b\xf7\x07\x0c\x62\x63\x54\x46\x33\xd3\xd3\x58\xc1\x2a\x17\x81\x38", )?; test( Curve::NistP384, HashAlgorithm::SHA512, b"\xdb\xd8\xdd\xc0\x27\x71\xa5\xff\x73\x59\xd5\x21\x65\x36\xb2\xe5\x24\xa2\xd0\xb6\xff\x18\x0f\xa2\x9a\x41\xa8\x84\x7b\x6f\x45\xf1\xb1\xd5\x23\x44\xd3\x2a\xea\x62\xa2\x3e\xa3\xd8\x58\x4d\xea\xae\xa3\x8e\xe9\x2d\x13\x14\xfd\xb4\xfb\xbe\xcd\xad\x27\xac\x81\x0f\x02\xde\x04\x52\x33\x29\x39\xf6\x44\xaa\x9f\xe5\x26\xd3\x13\xce\xa8\x1b\x9c\x3f\x6a\x8d\xbb\xea\xfc\x89\x9d\x0c\xda\xeb\x1d\xca\x05\x16\x0a\x8a\x03\x96\x62\xc4\xc8\x45\xa3\xdb\xb0\x7b\xe2\xbc\x8c\x91\x50\xe3\x44\x10\x3e\x40\x44\x11\x66\x8c\x48\xaa\x77\x92", b"\x54\xc7\x9d\xa7\xf8\xfa\xee\xee\x6f\x3a\x1f\xdc\x66\x4e\x40\x5d\x5c\x0f\xb3\xb9\x04\x71\x5f\x3a\x9d\x89\xd6\xfd\xa7\xea\xbe\x6c\xee\x86\xef\x82\xc1\x9f\xca\x0d\x1a\x29\xe0\x9c\x1a\xcf\xcf\x18", b"\x92\x6c\x17\xd6\x87\x78\xeb\x06\x6c\x20\x78\xcd\xb6\x88\xb1\x73\x99\xe5\x4b\xde\x5a\x79\xef\x18\x52\x35\x2a\x58\x96\x7d\xff\x02\xc1\x7a\x79\x2d\x39\xf9\x5c\x76\xd1\x46\xfd\xc0\x86\xfe\x26\xb0", b"\x9d\xbf\xa1\x47\x37\x57\x67\xdd\xe8\x1b\x01\x4f\x1e\x3b\xf5\x79\xc4\x4d\xd2\x24\x86\x99\x8a\x9b\x6f\x9e\x09\x20\xe5\x3f\xaa\x11\xee\xd2\x9a\x4e\x23\x56\xe3\x93\xaf\xd1\xf5\xc1\xb0\x60\xa9\x58", b"\xe4\xd3\x18\x39\x1f\x7c\xbf\xe7\x0d\xa7\x89\x08\xd4\x2d\xb8\x52\x25\xc8\x5f\x4f\x2f\xf4\x13\xec\xad\x50\xaa\xd5\x83\x3a\xbe\x91\xbd\xd5\xf6\xd6\x4b\x0c\xd2\x81\x39\x8e\xab\x19\x45\x20\x87\xdd", )?; test( Curve::NistP521, HashAlgorithm::SHA224, b"\xc6\x43\x19\xc8\xaa\x1c\x1a\xe6\x76\x63\x00\x45\xae\x48\x8a\xed\xeb\xca\x19\xd7\x53\x70\x41\x82\xc4\xbf\x3b\x30\x6b\x75\xdb\x98\xe9\xbe\x43\x82\x34\x23\x3c\x2f\x14\xe3\xb9\x7c\x2f\x55\x23\x69\x50\x62\x98\x85\xac\x1e\x0b\xd0\x15\xdb\x0f\x91\x29\x13\xff\xb6\xf1\x36\x1c\x4c\xc2\x5c\x3c\xd4\x34\x58\x3b\x0f\x7a\x5a\x9e\x1a\x54\x9a\xa5\x23\x61\x42\x68\x03\x79\x73\xb6\x5e\xb5\x9c\x0c\x16\xa1\x9a\x49\xbf\xaa\x13\xd5\x07\xb2\x9d\x5c\x7a\x14\x6c\xd8\xda\x29\x17\x66\x51\x00\xac\x9d\xe2\xd7\x5f\xa4\x8c\xb7\x08\xac\x79", b"\x00\x01\x88\x36\x6b\x94\x19\xa9\x00\xab\x0e\xd9\x63\x34\x26\xd5\x1e\x25\xe8\xdc\x03\xf4\xf0\xe7\x54\x99\x04\x24\x39\x81\xec\x46\x9c\x8d\x6d\x93\x8f\x67\x14\xee\x62\x0e\x63\xbb\x0e\xc5\x36\x37\x6a\x73\xd2\x4d\x40\xe5\x8a\xd9\xeb\x44\xd1\xe6\x06\x3f\x2e\xb4\xc5\x1d", b"\x00\x98\x89\xb9\x20\x3d\x52\xb9\x24\x3f\xd5\x15\x29\x4a\x67\x4a\xfd\x6b\x81\xdf\x46\x37\xff\xdd\xdc\x43\xa7\x41\x47\x41\xed\xa7\x8d\x8a\xa8\x62\xc9\xcb\xbb\x61\x8a\xce\xc5\x5b\xb9\xa2\x9a\xac\x59\x61\x6f\xc8\x04\xa5\x2a\x97\xa9\xfc\x4d\x03\x25\x4f\x44\x69\xef\xfe", b"\x01\xd5\x94\x01\xb8\xac\x43\x88\x55\xd5\x45\xa6\x99\x99\x11\x42\x68\x50\x77\xa4\x09\xde\x24\x18\xc7\xcc\xfe\x01\xa4\x77\x1b\x38\x70\xe7\x62\x87\xa9\x65\x4c\x20\x9b\x58\xa1\x2b\x0f\x51\xe8\xdc\x56\x8e\x33\x14\x0a\x6b\x63\x03\x24\xf7\xef\x17\xca\xa6\x4b\xf4\xc1\x39", b"\x01\x43\xaf\x36\x0b\x79\x71\x09\x5b\x3b\x50\x67\x9a\x13\xcd\x49\x21\x71\x89\xea\xee\x47\x13\xf4\x20\x17\x20\x17\x52\x16\x57\x3c\x68\xf7\xac\x6f\x68\x8b\xfe\x6e\xb9\x40\xa2\xd9\x71\x80\x9b\xf3\x6c\x0a\x77\xde\xcc\x55\x3b\x02\x5e\xd4\x19\x35\xa3\x89\x86\x85\x18\x3b", )?; test( Curve::NistP521, HashAlgorithm::SHA256, b"\x91\xf1\xca\x8c\xe6\x68\x1f\x4e\x1f\x11\x7b\x91\x8a\xe7\x87\xa8\x88\x79\x8a\x9d\xf3\xaf\xc9\xd0\xe9\x22\xf5\x1c\xdd\x6e\x7f\x7e\x55\xda\x99\x6f\x7e\x36\x15\xf1\xd4\x1e\x42\x92\x47\x98\x59\xa4\x4f\xa1\x8a\x5a\x00\x66\x62\x61\x0f\x1a\xaa\x28\x84\xf8\x43\xc2\xe7\x3d\x44\x17\x53\xe0\xea\xd5\x1d\xff\xc3\x66\x25\x06\x16\xc7\x06\xf0\x71\x28\x94\x0d\xd6\x31\x2f\xf3\xed\xa6\xf0\xe2\xb4\xe4\x41\xb3\xd7\x4c\x59\x2b\x97\xd9\xcd\x91\x0f\x97\x9d\x7f\x39\x76\x7b\x37\x9e\x7f\x36\xa7\x51\x9f\x2a\x4a\x25\x1e\xf5\xe8\xaa\xe1", b"\x01\x67\xd8\xb8\x30\x82\x59\xc7\x30\x93\x1d\xb8\x28\xa5\xf6\x96\x97\xec\x07\x73\xa7\x9b\xde\xdb\xaa\xf1\x51\x14\xa4\x93\x70\x11\xc5\xae\x36\xab\x05\x03\x95\x73\x73\xfe\xe6\xb1\xc4\x65\x0f\x91\xa3\xb0\xc9\x2c\x2d\x60\x4a\x35\x59\xdd\x2e\x85\x6a\x9a\x84\xf5\x51\xd9", b"\x01\x9d\x2c\x13\x46\xaa\xda\xa3\x09\x0b\x59\x81\xf5\x35\x32\x43\x30\x0a\x4f\xf0\xab\x96\x1c\x4e\xe5\x30\xf4\x13\x3f\xe8\x5e\x6a\xab\x5b\xad\x42\xe7\x47\xee\xe0\x29\x8c\x2b\x80\x51\xc8\xbe\x70\x49\x10\x9a\xd3\xe1\xb5\x72\xdd\xa1\xca\xc4\xa0\x30\x10\xf9\x9f\x20\x6e", b"\x01\xff\x09\x74\x85\xfa\xf3\x2c\xe9\xe0\xc5\x57\xee\x06\x45\x87\xc1\x2c\x48\x34\xe7\xf0\x98\x8c\xf1\x81\xd0\x7b\xa9\xee\x15\xae\x85\xa8\x20\x8b\x61\x85\x00\x80\xfc\x4b\xbe\xdb\xd8\x25\x36\x18\x1d\x43\x97\x34\x59\xf0\xd6\x96\xac\x5e\x6b\x8f\x23\x30\xb1\x79\xd1\x80", b"\x00\x30\x6d\xc3\xc3\x82\xaf\x13\xc9\x9d\x44\xdb\x7a\x84\xed\x81\x3c\x87\x19\xc6\xed\x3b\xbe\x75\x1e\xad\x0d\x48\x7b\x5a\x4a\xa0\x18\x12\x98\x62\xb7\xd2\x82\xcc\xe0\xbc\x20\x59\xa5\x6d\x77\x22\xf4\xb2\x26\xf9\xde\xb8\x5d\xa1\x2d\x5b\x40\x64\x8b\xf6\xec\x56\x81\x28", )?; test( Curve::NistP521, HashAlgorithm::SHA384, b"\x4b\xe8\x1d\xcf\xab\x39\xa6\x4d\x6f\x00\xc0\xd7\xff\xf9\x4d\xab\xdf\x34\x73\xdc\x49\xf0\xe1\x29\x00\xdf\x32\x8d\x65\x84\xb8\x54\xfb\xae\xba\xf3\x19\x4c\x43\x3e\x9e\x21\x74\x33\x42\xe2\xdd\x05\x6b\x44\x5c\x8a\xa7\xd3\x0a\x38\x50\x4b\x36\x6a\x8f\xa8\x89\xdc\x8e\xce\xc3\x5b\x31\x30\x07\x07\x87\xe7\xbf\x0f\x22\xfa\xb5\xbe\xa5\x4a\x07\xd3\xa7\x53\x68\x60\x53\x97\xba\x74\xdb\xf2\x92\x3e\xf2\x0c\x37\xa0\xd9\xc6\x4c\xae\xbc\xc9\x31\x57\x45\x6b\x57\xb9\x8d\x4b\xec\xb1\x3f\xec\xb7\xcc\x7f\x37\x40\xa6\x05\x7a\xf2\x87", b"\x00\xcf\xa5\xa8\xa3\xf1\x5e\xb8\xc4\x19\x09\x56\x73\xf1\xd0\xbd\x63\xb3\x96\xff\x98\x13\xc1\x8d\xfe\x5a\xa3\x1f\x40\xb5\x0b\x82\x48\x1f\x9e\xd2\xed\xd4\x7a\xe5\xea\x6a\x48\xea\x01\xf7\xe0\xad\x00\x00\xed\xf7\xb6\x6f\x89\x09\xee\x94\xf1\x41\xd5\xa0\x7e\xfe\x31\x5c", b"\x01\x8a\xf7\x28\xf7\x31\x8b\x96\xd5\x7f\x19\xc1\x10\x44\x15\xc8\xd5\x98\x95\x65\x46\x5e\x42\x9b\xc3\x0c\xf6\x5c\xed\x12\xa1\xc5\x85\x6a\xc8\x6f\xca\x02\x38\x8b\xc1\x51\xcf\x89\x95\x9a\x4f\x04\x85\x97\xa9\xe7\x28\xf3\x03\x4a\xa3\x92\x59\xb5\x98\x70\x94\x61\x87\xbf", b"\x01\x9c\xf9\x1a\x38\xcc\x20\xb9\x26\x9e\x74\x67\x85\x7b\x1f\xc7\xea\xbb\x8c\xea\x91\x5a\x31\x35\xf7\x27\xd4\x71\xe5\xbf\xcf\xb6\x6d\x32\x1f\xab\xe2\x83\xa2\xcf\x38\xd4\xc5\xa6\xec\xb6\xe8\xcb\xee\x10\x30\x47\x43\x73\xbb\x87\xfc\xdf\xcc\x95\xcf\x85\x7a\x8d\x25\xd0", b"\x01\xcf\x9a\xcd\x94\x49\xc5\x75\x89\xc9\x50\xf2\x87\x84\x2f\x9e\x24\x87\xc5\x61\x09\x55\xb2\xb5\x03\x5f\x6a\xac\xfd\x24\x02\xf5\x11\x99\x8a\x1a\x94\x2b\x39\xc3\x07\xfc\x2b\xca\xb2\xc8\xd0\xda\xe9\x4b\x55\x47\xdd\xcc\xfb\x10\x12\xca\x98\x5b\x3e\xdf\x42\xbb\xba\x8b", )?; test( Curve::NistP521, HashAlgorithm::SHA512, b"\x54\x3c\x37\x4a\xf9\x0c\x34\xf5\x0e\xe1\x95\x00\x6d\x5f\x9d\x8d\xd9\x86\xd0\x9a\xd1\x82\xfc\xbe\xfa\x08\x55\x67\x27\x5e\xee\x1e\x74\x2b\xfe\x0a\xf3\xd0\x58\x67\x5a\xde\xb5\xb9\xf8\x7f\x24\x8b\x00\xa9\xfb\xd2\xaa\x77\x91\x29\x12\x3a\x5b\x98\x3f\x2f\x26\xfc\x3c\xaf\x2e\xa3\x42\x77\x55\x0c\x22\xfe\x8c\x81\x4c\x73\x9b\x46\x97\x2d\x50\x23\x29\x93\xcd\xdd\x63\xa3\xc9\x9e\x20\xf5\xc5\x06\x7d\x9b\x57\xe2\xd5\xdb\x94\x31\x7a\x5a\x16\xb5\xc1\x2b\x5c\x4c\xaf\xbc\x79\xcb\xc2\xf9\x94\x0f\x07\x4b\xbc\x7d\x0d\xc7\x1e\x90", b"\x00\x9e\xc1\xa3\x76\x1f\xe3\x95\x80\x73\xb9\x64\x7f\x34\x20\x2c\x5e\x8c\xa2\x42\x8d\x05\x6f\xac\xc4\xf3\xfe\xdc\x70\x77\xfa\x87\xf1\xd1\xeb\x30\xcc\x74\xf6\xe3\xff\x3d\x3f\x82\xdf\x26\x41\xce\xa1\xeb\x3f\xf1\x52\x9e\x8a\x38\x66\xae\x20\x55\xaa\xce\xc0\xbf\x68\xc4", b"\x00\xbe\xd0\x26\x1b\x91\xf6\x64\xc3\xff\x53\xe3\x37\xd8\x32\x1c\xb9\x88\xc3\xed\xc0\x3b\x46\x75\x46\x80\x09\x7e\x5a\x85\x85\x24\x5d\x80\xd0\xb7\x04\x5c\x75\xa9\xc5\xbe\x7f\x59\x9d\x3b\x5e\xea\x08\xd8\x28\xac\xb6\x29\x4a\xe5\x15\xa3\xdf\x57\xa3\x7f\x90\x3e\xf6\x2e", b"\x00\xce\xf3\xf4\xba\xbe\x6f\x98\x75\xe5\xdb\x28\xc2\x7d\x6a\x19\x7d\x60\x7c\x36\x41\xa9\x0f\x10\xc2\xcc\x2c\xb3\x02\xba\x65\x8a\xa1\x51\xdc\x76\xc5\x07\x48\x8b\x99\xf4\xb3\xc8\xbb\x40\x4f\xb5\xc8\x52\xf9\x59\x27\x3f\x41\x2c\xbd\xd5\xe7\x13\xc5\xe3\xf0\xe6\x7f\x94", b"\x00\x09\x7e\xd9\xe0\x05\x41\x6f\xc9\x44\xe2\x6b\xcc\x36\x61\xa0\x9b\x35\xc1\x28\xfc\xcc\xdc\x27\x42\x73\x9c\x8a\x30\x1a\x33\x8d\xd7\x7d\x9d\x13\x57\x16\x12\xa3\xb9\x52\x4a\x61\x64\xb0\x9f\xe7\x36\x43\xbb\xc3\x14\x47\xee\x31\xef\x44\xa4\x90\x84\x3e\x4e\x7d\xb2\x3f", )?; Ok(()) } ��������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/src/crypto/tests/rsa.rs�������������������������������������������������������0000644�0000000�0000000�00000467741�10461020230�0017366�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������//! Low-level RSA tests. use crate::Result; use crate::crypto::mpi; use crate::packet::{prelude::*, signature::subpacket::*}; use crate::types::*; #[test] fn p_less_than_q() -> Result<()> { use std::cmp::Ordering; use crate::crypto::raw_bigint_cmp; // Repeat four times to increase the sensitivity. for _ in 0..4 { // Check that p < q holds when generating an RSA key. let key0 = Key4::generate_rsa(1024)?; let t = key0.creation_time(); let (d0, p, q, u0) = extract(&key0); assert_eq!(raw_bigint_cmp(&p, &q), Ordering::Less); // Check that p < q holds when importing an RSA key. // // Note: p and q are swapped, i.e. p < q doesn't hold in the // arguments of import_secret_rsa: let key1 = Key4::import_secret_rsa(&d0, &q, &p, t)?; let (d1, p, q, u1) = extract(&key1); assert_eq!(raw_bigint_cmp(&p, &q), Ordering::Less); assert_eq!(d0, d1); assert_eq!(u0, u1); } fn extract(key: &Key4<key::SecretParts, key::PrimaryRole>) -> (Vec<u8>, Vec<u8>, Vec<u8>, Vec<u8>) { match key.secret() { SecretKeyMaterial::Unencrypted(s) => s.map(|s| match s { mpi::SecretKeyMaterial::RSA { d, p, q, u } => (d.value().into(), p.value().into(), q.value().into(), u.value().into()), _ => unreachable!(), }), _ => unreachable!(), } } Ok(()) } #[test] fn fips_186_3_verification() -> Result<()> { if ! PublicKeyAlgorithm::RSAEncryptSign.is_supported() { eprintln!("Skipping because RSA is not supported."); return Ok(()); } fn test(hash: HashAlgorithm, msg: &[u8], n: &[u8], e: &[u8], s: &[u8]) -> Result<()> { if ! hash.is_supported() { eprintln!("Skipping because {} is not supported.", hash); return Ok(()); } let now = Timestamp::now(); let key: Key<key::PublicParts, key::PrimaryRole> = Key4::new(now, PublicKeyAlgorithm::RSAEncryptSign, mpi::PublicKey::RSA { e: mpi::MPI::new(e), n: mpi::MPI::new(n), })?.into(); let mut h = hash.context()?.for_digest(); h.update(msg); let mut d = h.into_digest()?; let sig: Signature = Signature4::new(SignatureType::Binary, PublicKeyAlgorithm::RSAEncryptSign, hash, SubpacketArea::new(vec![ Subpacket::new( SubpacketValue::SignatureCreationTime(now), false)?, ])?, SubpacketArea::default(), [d[0], d[1]], mpi::Signature::RSA { s: mpi::MPI::new(s), }).into(); sig.verify_digest(&key, &d)?; // Sanity check: Change the digest and retry. d[0] ^= 1; sig.verify_digest(&key, &d).unwrap_err(); Ok(()) } // [mod = 2048] let n = b"\xce\xa8\x04\x75\x32\x4c\x1d\xc8\x34\x78\x27\x81\x8d\xa5\x8b\xac\x06\x9d\x34\x19\xc6\x14\xa6\xea\x1a\xc6\xa3\xb5\x10\xdc\xd7\x2c\xc5\x16\x95\x49\x05\xe9\xfe\xf9\x08\xd4\x5e\x13\x00\x6a\xdf\x27\xd4\x67\xa7\xd8\x3c\x11\x1d\x1a\x5d\xf1\x5e\xf2\x93\x77\x1a\xef\xb9\x20\x03\x2a\x5b\xb9\x89\xf8\xe4\xf5\xe1\xb0\x50\x93\xd3\xf1\x30\xf9\x84\xc0\x7a\x77\x2a\x36\x83\xf4\xdc\x6f\xb2\x8a\x96\x81\x5b\x32\x12\x3c\xcd\xd1\x39\x54\xf1\x9d\x5b\x8b\x24\xa1\x03\xe7\x71\xa3\x4c\x32\x87\x55\xc6\x5e\xd6\x4e\x19\x24\xff\xd0\x4d\x30\xb2\x14\x2c\xc2\x62\xf6\xe0\x04\x8f\xef\x6d\xbc\x65\x2f\x21\x47\x9e\xa1\xc4\xb1\xd6\x6d\x28\xf4\xd4\x6e\xf7\x18\x5e\x39\x0c\xbf\xa2\xe0\x23\x80\x58\x2f\x31\x88\xbb\x94\xeb\xbf\x05\xd3\x14\x87\xa0\x9a\xff\x01\xfc\xbb\x4c\xd4\xbf\xd1\xf0\xa8\x33\xb3\x8c\x11\x81\x3c\x84\x36\x0b\xb5\x3c\x7d\x44\x81\x03\x1c\x40\xba\xd8\x71\x3b\xb6\xb8\x35\xcb\x08\x09\x8e\xd1\x5b\xa3\x1e\xe4\xba\x72\x8a\x8c\x8e\x10\xf7\x29\x4e\x1b\x41\x63\xb7\xae\xe5\x72\x77\xbf\xd8\x81\xa6\xf9\xd4\x3e\x02\xc6\x92\x5a\xa3\xa0\x43\xfb\x7f\xb7\x8d"; let e = b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x26\x04\x45"; test( HashAlgorithm::SHA224, b"\x74\x23\x04\x47\xbc\xd4\x92\xf2\xf8\xa8\xc5\x94\xa0\x43\x79\x27\x16\x90\xbf\x0c\x8a\x13\xdd\xfc\x1b\x7b\x96\x41\x3e\x77\xab\x26\x64\xcb\xa1\xac\xd7\xa3\xc5\x7e\xe5\x27\x6e\x27\x41\x4f\x82\x83\xa6\xf9\x3b\x73\xbd\x39\x2b\xd5\x41\xf0\x7e\xb4\x61\xa0\x80\xbb\x66\x7e\x5f\xf0\x95\xc9\x31\x9f\x57\x5b\x38\x93\x97\x7e\x65\x8c\x6c\x00\x1c\xee\xf8\x8a\x37\xb7\x90\x2d\x4d\xb3\x1c\x3e\x34\xf3\xc1\x64\xc4\x7b\xbe\xef\xde\x3b\x94\x6b\xad\x41\x6a\x75\x2c\x2c\xaf\xce\xe9\xe4\x01\xae\x08\x88\x4e\x5b\x8a\xa8\x39\xf9\xd0\xb5", n, e, b"\x27\xda\x41\x04\xea\xce\x19\x91\xe0\x8b\xd8\xe7\xcf\xcc\xd9\x7e\xc4\x8b\x89\x6a\x0e\x15\x6c\xe7\xbd\xc2\x3f\xd5\x70\xaa\xa9\xa0\x0e\xd0\x15\x10\x1f\x0c\x62\x61\xc7\x37\x1c\xec\xa3\x27\xa7\x3c\x3c\xec\xfc\xf6\xb2\xd9\xed\x92\x0c\x96\x98\x04\x6e\x25\xc8\x9a\xdb\x23\x60\x88\x7d\x99\x98\x3b\xf6\x32\xf9\xe6\xeb\x0e\x5d\xf6\x07\x15\x90\x2b\x9a\xea\xa7\x4b\xf5\x02\x7a\xa2\x46\x51\x08\x91\xc7\x4a\xe3\x66\xa1\x6f\x39\x7e\x2c\x8c\xcd\xc8\xbd\x56\xaa\x10\xe0\xd0\x15\x85\xe6\x9f\x8c\x48\x56\xe7\x6b\x53\xac\xfd\x3d\x78\x2b\x81\x71\x52\x90\x08\xfa\x5e\xff\x03\x0f\x46\x95\x67\x04\xa3\xf5\xd9\x16\x73\x48\xf3\x70\x21\xfc\x27\x7c\x6c\x0a\x8f\x93\xb8\xa2\x3c\xfb\xf9\x18\x99\x0f\x98\x2a\x56\xd0\xed\x2a\xa0\x81\x61\x56\x07\x55\xad\xc0\xce\x2c\x3e\x2a\xb2\x92\x9f\x79\xbf\xc0\xb2\x4f\xf3\xe0\xff\x35\x2e\x64\x45\xd8\xa6\x17\xf1\x78\x5d\x66\xc3\x22\x95\xbb\x36\x5d\x61\xcf\xb1\x07\xe9\x99\x3b\xbd\x93\x42\x1f\x2d\x34\x4a\x86\xe4\x12\x78\x27\xfa\x0d\x0b\x25\x35\xf9\xb1\xd5\x47\xde\x12\xba\x28\x68\xac\xde\xcf\x2c\xb5\xf9\x2a\x6a\x15\x9a", )?; test( HashAlgorithm::SHA224, b"\x9a\xf2\xc5\xa9\x19\xe5\xda\xdc\x66\x87\x99\xf3\x65\xfc\x23\xda\x62\x31\x43\x7e\xa5\x1c\xa5\x31\x46\x45\x42\x50\x43\x85\x1f\x23\xd0\x0d\x37\x04\xee\xab\xb5\xc4\x3f\x49\x67\x4a\x19\xb7\x70\x7d\xd9\xaa\x3d\x65\x7a\x04\xba\x8c\x66\x55\xc5\xab\x8b\xa2\xe3\x82\xb2\x66\x31\x08\x0c\xd7\x9e\xc4\x0e\x6a\x58\x7b\x7f\x99\x84\x0b\xd0\xe4\x32\x97\xab\x16\x90\xe4\xce\xc9\x5d\x03\x1a\x2c\xa1\x31\xe7\x04\x9c\xfb\x9b\xf1\xfc\xa6\x7b\xf3\x53\xcd\xc1\x2c\xc7\x4c\xee\xe8\x0c\x5d\x61\xda\x8f\x01\x29\xa8\xf4\xa2\x18\xab\xc3\xf6", n, e, b"\xc5\xdf\xbe\xfd\x35\xce\xc8\x46\xe2\xc7\xb2\x43\x4d\xc9\xc4\x6a\x5a\x9b\x1b\x6c\xe6\x5b\x2b\x18\x66\x5a\xed\xb1\x40\x4d\xe1\xf4\x66\xe0\x24\xf8\x49\xee\xc3\x08\xc2\xd2\xf2\xf0\x19\x3d\xf1\x89\x8a\x58\x1c\x9e\xa3\x25\x81\x18\x55\x53\xb1\x71\xb6\x50\x70\x82\x61\x7c\x5c\x01\x8a\xfe\x0c\x3a\xf6\x4d\x2e\xc5\xa5\x63\x79\x5a\xa5\x85\xe7\x77\x53\xcd\x18\x83\x6f\x6f\x0c\x29\x53\x5f\x62\x00\xca\x89\x99\x28\xfe\x78\xe9\x49\xb0\xa2\x16\xec\x47\xa6\xad\xf2\x22\x3e\x17\x23\x6c\xfc\x16\x7c\xf0\x0e\xd6\x13\x6f\x03\xcf\x6f\xfd\x4f\x3f\x77\x87\xae\xb0\x05\x84\x09\x78\xd8\xd6\xba\x59\x3d\x4f\x4c\xfe\x69\x20\xbe\x10\x2b\x98\x47\xd1\x01\x40\xdf\xf8\x6b\x0d\xb1\x4f\xfc\xcc\x9a\x96\xe6\x73\xc6\x72\xc1\x12\x8a\xe4\x54\x89\xd2\xcb\xfe\x6e\x19\x5c\xa5\x20\x6e\xda\x51\x9c\xad\x3d\x6e\x0a\xbf\x46\x53\xe3\x6b\x5a\x26\x4e\x87\x49\x4a\x4d\x63\xee\x91\xff\x7c\x35\xa6\xab\x12\xad\xfa\x3b\xb5\x37\xf6\x19\x8b\x06\xf5\xde\x07\x17\x07\x6b\x0e\xc8\x3a\xe0\xda\x9e\xa4\x19\xcc\x0c\x96\x66\x9d\x1d\x7c\x9e\x52\x92\x71\x42\x84\x01\xe0\x9e\x04\x88\x8a", )?; test( HashAlgorithm::SHA224, b"\x59\xb5\xb8\x5b\x9d\xc2\x46\xd3\x0a\x3f\xc8\xa2\xde\x3c\x9d\xfa\x97\x16\x43\xb0\xc1\xf7\xc9\xe4\x0c\x9c\x87\xe4\xa1\x5b\x0c\x4e\xb6\x64\x58\x75\x60\x47\x4c\x06\xa9\xb6\x5e\xec\xe3\x8c\x91\x70\x3c\x0f\xa5\xa5\x92\x72\x8a\x03\x88\x9f\x1b\x52\xd9\x33\x09\xca\xec\xc9\x15\x78\xa9\x7b\x83\xe3\x8c\xa6\xcb\xf0\xf7\xee\x91\x03\xcd\x82\xd7\x67\x3c\xa1\x72\xf0\xda\x5e\xba\xde\xf4\xa0\x86\x05\x22\x6c\x58\x2b\x1f\x67\xd4\xb2\xd8\x96\x77\x77\xc3\x69\x85\xf9\x72\xf8\x43\xbe\x68\x8c\x67\xf2\x2b\x61\xcd\x52\x9b\xaa\x6b\x48", n, e, b"\x29\xb5\xac\x41\x72\x26\x44\x4b\xc8\x57\x0a\x27\x9e\x0e\x56\x1a\x4c\x39\x70\x7b\xdb\xea\x93\x60\x64\xed\x60\x3b\xa9\x68\x89\xeb\x3d\x78\x6b\x19\x99\xb5\x18\x0c\xd5\xd0\x61\x17\x88\x83\x7a\x9d\xf1\x49\x6b\xac\xea\x31\xcb\xf8\xf2\x4a\x1a\x22\x32\xd4\x15\x89\x13\xc9\x63\xf5\x06\x6a\xad\x4b\x65\xe6\x17\xd0\x90\x33\x59\x69\x6d\x75\x9d\x84\xc1\x39\x2e\x22\xc2\x46\xd5\xf5\xbe\xd4\xb8\x06\xf4\x09\x1d\x5e\x8f\x71\xa5\x13\xf1\x31\x9b\xb4\xe5\x69\x71\xcd\x3e\x16\x8c\x9a\x7e\x27\x89\x83\x22\x93\x99\x1a\x73\xd3\x02\x70\x72\xec\xee\x68\x63\x51\x45\x49\x02\x9f\xb3\x55\x34\x78\xc8\xf4\x10\x3b\xf6\x2d\x7d\xe1\xfb\x53\xfe\x76\xce\x97\x78\xad\xa3\xbb\x9e\xfa\x62\xda\x44\xcd\x00\xd0\x2b\xb0\xeb\x74\x88\xac\x24\xda\x38\x14\xc6\x53\xcb\xa6\x12\x30\x13\x73\x83\x7a\x0c\x3f\x11\x88\x54\x93\xcb\xf3\x02\x4c\x35\x72\xea\xed\x39\x6d\x0e\xbb\x80\x39\xdd\xf8\x43\xc2\x18\xd8\xbc\x77\x83\x54\x90\x46\xc3\x35\x86\xfb\x34\x28\x56\x2c\xb8\x04\x60\x90\x04\x0c\x0e\x4e\xea\x50\xa1\x9a\x42\x8b\xde\x34\x62\x62\x77\xff\x48\xa8\x4f\xaa\x18\x9b\x54\x40", )?; test( HashAlgorithm::SHA224, b"\x49\xa5\xf3\x93\x0a\xd4\x5a\xca\x5e\x22\xca\xac\x66\x46\xf0\xbe\xde\x12\x28\x83\x8d\x49\xf8\xf2\xe0\xb2\xdd\x27\xd2\x6a\x4b\x59\x0e\x7e\xef\x0c\x58\xb9\x37\x88\x29\xbb\x14\x89\x99\x4b\xff\x38\x82\xef\x3a\x5a\xe3\xb9\x58\xc8\x82\x63\xff\x1f\xd6\x9f\xed\xb8\x23\xa8\x39\xdb\xe7\x1d\xdb\x2f\x75\x0f\x6f\x75\xe0\x59\x36\x76\x1a\x2f\x5e\x3a\x5d\xfa\x83\x7b\xca\x63\x75\x59\x51\xae\x3c\x50\xd0\x4a\x59\x66\x7f\xa6\x4f\xa9\x8b\x46\x62\xd8\x01\x15\x9f\x61\xee\xfd\x1c\x8b\xc5\xb5\x81\xf5\x00\xda\xc7\x3f\x0a\x42\x40\x07", n, e, b"\x60\x4e\xb6\x37\xca\x54\xbe\xa5\xad\x1f\xd3\x16\x59\x11\xf3\xba\xa2\xe0\x6c\x85\x9d\xc7\x39\x45\xa3\x8b\xca\x7f\xf9\xbf\xa9\xed\x39\x43\x53\x48\x62\x3d\x3e\x60\xf1\xce\x48\x74\x43\x84\x0c\x6b\x2c\x00\x0f\x15\x82\xe8\x52\x60\x67\xa5\xe8\x92\x3f\x1a\x1b\xda\xab\xb1\xa4\x0c\x0f\x49\xee\x69\x06\xa4\xc8\xfc\x9b\x8c\xfa\x6d\x07\xc2\xcc\x5b\xdf\x2a\xda\x65\xc5\x3d\x79\x54\x80\x89\xc5\x24\xfa\x36\x43\x19\xa9\x0d\x46\x21\x3f\xeb\xdc\xe6\xdb\x79\x59\x14\xcb\xda\x04\xd7\xbb\xbf\x26\xbb\xb2\x99\xfc\x7d\x14\x49\xdc\xc8\x1d\x13\x9e\x3c\x33\xd4\xc1\xde\x96\x47\x39\x94\x73\x0a\x4b\x63\x96\x33\xd6\x77\xdb\x25\x69\x5f\xfd\x15\x7e\x59\x1b\xdd\xea\xd0\x3d\xd2\xf1\xc1\xb8\xf5\xc8\xa2\x13\xb7\x85\x87\x9b\xf7\xc9\xa9\x92\xbb\x11\xdd\x5e\x91\xdf\x3a\xff\x09\x31\xca\x76\xc4\x06\x23\x0a\x19\xe3\x07\xf3\x34\x19\xc9\xd9\xd3\xf6\xf6\x4b\xf8\x88\x1c\x0d\xdf\x74\xa5\x71\x6c\xbc\x43\x33\x29\x36\x8d\x6e\x55\xf1\xf7\x51\xd7\xb9\xf9\xb0\xa2\x6e\xb5\x81\x17\x72\xf5\xf6\x98\x53\x0e\xfc\x1e\xac\xee\xe6\xe1\xdc\x68\x39\xb2\x13\x3c\x2f\xcc\xfa\x8c", )?; test( HashAlgorithm::SHA224, b"\x9b\xfc\x4d\xac\x8c\x22\x32\x38\x72\x16\xa5\x32\xce\x62\xd9\x8c\x1a\xaf\xa3\x5c\x65\xdc\x38\x8e\x3d\x4d\x37\xd6\xd1\x86\xea\xe9\x57\xf8\xc9\xed\xac\x1a\x3f\x2e\x3a\xbc\xb1\x12\x1f\x99\xbd\x4f\x8c\x2b\xbf\x5b\x6a\xc3\x9a\x25\x44\xd8\xb5\x02\x61\x9f\x43\xea\x30\xdd\xc8\xe4\xea\xfa\xd8\xbf\x72\x56\x22\x03\x80\xe0\xae\x27\xfe\xe4\x63\x04\xb2\x24\xcc\x8a\x1e\x2b\x1c\xb2\xa4\xde\x6f\xb3\xee\x54\x52\x79\x8d\xe7\x86\x53\xe0\x8b\x01\xec\x38\x5f\x36\x7c\x39\x82\x96\x3f\x84\x28\x57\x27\x93\xed\x74\xce\xe3\x69\xf5\xae", n, e, b"\x44\x4f\x7e\xfb\xfe\xf5\x86\xfa\xd4\x31\xe1\x7f\xea\x1a\x2d\x59\xf1\x9b\x3d\x61\x9b\xb6\xfa\x36\x64\x30\x18\x33\xa4\xdb\x12\x43\x45\x9e\x31\xaa\x6a\x70\x3b\x22\x57\x2f\x09\x12\x75\x4e\x56\xf7\x23\x1a\x55\xac\x7a\xbc\xa5\x14\xc7\x9d\x9f\xb3\x56\x42\x14\xb4\xaf\x83\x5d\x7d\x1e\xaf\x2b\x58\xce\xb6\xa3\x44\xf1\xc3\x68\x90\xf5\xe8\x3b\x50\x18\x8c\x01\x47\xd6\xd1\x15\x6d\xa2\x89\xcc\xf4\xbd\xb0\xb9\xa6\x6f\x1e\x4a\x1f\x26\x43\x59\x1d\x5f\xfb\x53\x70\x2c\xf7\x0d\xdf\x35\x15\x92\x57\x54\x88\xf1\x92\x90\x10\xac\xa3\x77\x14\xb2\x34\xee\xb5\xb9\x52\xb9\x32\x3a\xe2\x65\x33\xe9\xec\xd5\x16\xdf\x26\x39\x2d\x12\x54\x22\x8b\xd9\xca\x21\xa3\x69\xbb\x6a\xb0\xa3\x3d\x5e\xb4\x4c\xee\x92\xb0\xea\x74\x71\xff\xe5\xfa\x43\xc2\x1d\xe2\xa8\x97\x5d\x4c\x5c\x8e\x18\x5f\xcb\x7a\xab\x33\xd8\x8a\x83\x65\xdd\xf0\x11\x9c\x10\x88\x03\xc5\x62\x88\x64\x3a\x05\x6e\x78\x1a\xbd\x4a\x02\x42\xa9\x2e\x25\x29\xd4\x05\xef\xcf\xd4\x24\x86\x62\xcf\xbb\x33\x2d\x6e\x6f\xad\x6a\xce\xb9\x0b\x5b\x58\xa5\x54\x1a\xbe\x07\xbe\xf2\x5d\x9d\x89\x21\x5e\x39\x84\x26", )?; test( HashAlgorithm::SHA224, b"\xbf\x5f\xf1\x96\x8a\x39\xf8\x09\xde\x73\xe6\xa8\x01\x4f\xc6\xe8\xdf\x15\x93\x67\xf4\x63\x40\xda\x6c\xc5\xfb\x46\x89\x85\xb3\x74\x46\xc5\xd8\x9f\x3a\xca\x62\x6f\xbe\x9b\x14\x2b\x52\xcb\x02\x2a\x3d\x93\x51\x8a\x74\x24\x3e\x25\xbd\x3a\x61\xc1\x14\xf5\x33\x87\x4e\xe5\xcf\xb7\xfc\x63\xf5\x99\x92\x28\x54\xb7\xc9\x18\x09\x49\x41\x5f\x63\xf1\x6b\xbf\xe9\xa8\xa6\x28\x9e\xf8\xa8\x8a\x83\x6d\x20\xe7\x5e\x46\x99\xac\xba\x6f\xa2\x41\x2f\xb4\x2c\xdf\xe3\x2f\x33\xa2\x51\x02\xa1\xdf\x49\x4c\x6f\xb7\x38\x55\x0d\xec\xaa\x0c", n, e, b"\x01\x7e\x05\x3d\x1e\xf8\x5c\x43\x19\x3a\x00\x09\xa9\x03\x95\x2a\xaf\x40\x0f\xbc\xfe\xe9\xc0\x28\x97\x57\x77\xab\x54\x0d\x2d\x22\xab\x5c\x25\xf4\xcf\x1d\x37\x94\xaf\xac\x66\x97\xe1\xf2\x43\x82\x90\x52\xa8\x4e\x28\x43\xcc\x0e\x25\x4d\xba\xc1\x02\x15\x72\x99\x9f\x2d\xca\xfa\xb5\x8b\x9d\xfe\xf2\xfc\xaf\x70\x1e\x43\x1b\xdc\xd1\x6d\xbe\xf1\x10\x09\x5b\xcf\xba\x50\x10\x59\xd7\x99\x4d\xad\x5b\x0b\x54\xd0\x81\x2a\x43\x80\xa1\xf0\xba\x8e\xc2\xbc\xba\x76\x8b\xf5\xb5\x44\x69\x56\x26\xa5\xf3\x95\xe7\x84\xd4\xb2\x96\x2f\xb7\x53\x38\x18\xde\x1d\x6e\xc6\x86\xed\xc9\xf6\x68\x68\xad\x03\xee\x64\x36\x1a\x6c\xb9\x1f\xd8\xef\x53\x6c\xa6\x45\x4d\x16\xc5\x37\xc0\x7a\xa4\x29\x23\xe6\x20\x57\xdf\x9d\xd9\xe7\xfa\x4a\xd0\x38\x4f\x35\x72\x1f\x6e\xb3\xb8\x16\xd3\x52\xa0\x95\xc6\x05\xd5\xc1\x0e\x0a\x7a\x2e\x86\x40\xe2\x73\x07\xcd\x44\xb9\xd7\x1a\xc5\x0c\x00\x43\xca\xca\x28\xae\x8d\x6f\x8f\xa5\xbb\x48\x31\x58\xa4\xe4\x15\xef\x6c\xfa\xd4\x7f\x34\xc0\x04\x2a\x2d\x58\x8a\xce\x0f\x13\x71\xd9\x38\x65\x39\x7b\xd2\x15\x16\xda\x2c\xc1\x5e\x90\x9c", )?; test( HashAlgorithm::SHA224, b"\x2f\xf4\xfc\xd0\xbe\x26\x0b\xf4\xa0\xd7\x31\x12\xd0\xe5\x64\x9c\x0b\xef\x5b\xbc\xdf\x15\x42\x3a\x05\xff\xb2\xa1\xf0\x21\xe0\x9d\xa6\x3d\x15\xa8\xcf\x29\x5e\xe5\x0b\xd2\x84\x4c\x89\x81\x3e\x08\xd6\x5d\xa6\x1d\xf2\x32\xea\x4e\xa9\x70\x44\x3e\x20\x77\x2c\xd5\xaf\x11\xcc\xe5\xee\x40\xb4\x0e\x13\x3b\xcf\xdf\x7b\xb3\x95\x3d\x86\x5a\x83\x09\xa8\xa6\xc8\xfd\xbd\xd2\x42\xd7\x9d\x27\xa8\xba\xf1\x79\x09\xd1\x45\xf4\x75\x35\x5e\x19\xfa\x11\xcd\x03\xd2\x04\xc4\xef\xda\xc6\x29\xfb\x46\x0f\xe9\x2e\x93\xb4\x8f\xb9\xbe\x13", n, e, b"\xab\xee\x5c\x86\x8f\x85\x0c\x17\x79\x4f\x02\x1e\xe9\x70\x9c\xc2\x30\x13\x20\xdd\x24\x6f\xb3\xea\xdb\x78\x02\xa3\x00\xa9\x8a\x67\x08\x3a\x2e\x4e\x25\x0d\xf1\x33\x14\xc2\x54\x53\xb8\x98\x11\x08\x01\xf7\xe7\xac\xb9\xb6\x94\x64\x4e\x5c\x4a\x26\x23\xdf\xf1\x88\x49\x13\xc0\x5e\x63\x6f\xe7\x7e\xd5\x15\x5d\x95\x4e\xe3\x8f\x12\x62\xc6\xc2\xe3\x8d\x11\x14\xcf\x6c\xc5\x14\x3c\x72\x77\xc8\x64\x9f\x5a\x42\x3f\x83\xdf\xd5\xf8\x29\xd9\xdc\x74\xaa\x4b\x2f\xcd\xc8\x96\x0c\xde\x5c\xe1\x46\xb2\x89\x13\x60\x64\xb1\x3b\xd0\xd3\x6a\x1e\x64\xa2\x61\xd6\x80\xfb\x7e\x23\xd2\xae\x92\xef\xb7\x43\xc3\xdb\x54\x60\x9e\xca\x7a\x1b\xe0\xe4\x7e\x6f\x72\x4d\xc5\xcf\x61\xcb\x2a\x36\x9c\x2b\xb1\x73\xf2\xc6\xcf\xec\xb9\xa8\x87\xd5\x83\xd2\x77\xb8\xe3\x0b\x24\xec\x85\x49\xc4\xd5\x3b\xa3\x98\x86\x42\xa6\x1f\x1f\x93\x9f\x0f\x38\x98\x00\x5c\x5d\x13\xaa\xaa\x54\xbc\xb8\xae\x83\xb7\x2b\x3c\xb6\x44\xb9\x43\x9d\x1d\x2a\xcc\xc8\x00\x27\x1d\x23\xe5\x2f\x98\x48\x0d\x27\x0f\xad\x6a\xce\xd5\x12\x25\x2e\xe9\x83\x32\xaf\x90\x35\x63\xd9\x82\xd8\xcb\xde\xfb\x7d", )?; test( HashAlgorithm::SHA224, b"\xb5\xdc\xa1\x53\x2d\xff\xda\x08\x31\xcb\x2d\x21\xeb\xd1\xbd\xca\x23\xc9\x31\x9c\x64\x27\xfd\xcc\x5a\xef\xe3\xa2\x7f\xc9\xb9\x2d\xf7\x58\x6c\x36\xb7\xc8\x45\x72\xed\xa6\x6b\xfb\x9c\xf5\xaa\x01\x87\x7e\x72\xbd\x51\x67\x23\xa7\xe2\x07\x87\xe9\x0d\xf9\xa0\x13\x6f\x6f\xa5\x10\x9a\xc9\x47\x59\x73\x67\x38\x68\xd8\xbb\xee\x70\x86\xa2\xa5\x4b\x3a\xf4\xa3\xb4\x17\x59\xbf\xb6\x48\x5f\x24\x64\xe6\xca\x53\xcb\x1c\x2c\x67\x25\x89\xb5\x9d\x50\xe5\x4b\x13\x7e\xe8\xdd\xd0\x2d\x67\xf5\x05\x5a\xc1\x8d\x92\xf1\x79\x24\xcc\x89", n, e, b"\x9a\xe5\xb9\x63\x3f\x9a\xdc\x7f\xf9\x23\xd8\x87\x57\x48\xbc\x62\x20\xdd\x8f\x67\x81\xb3\xd4\x6d\x60\x08\xae\x69\xfd\xa0\x72\xd2\x05\xf8\x7a\x12\xd5\x4c\x3c\x7e\xcc\x85\xb8\x8b\x6e\xf4\x77\x0e\xeb\x4b\x71\xde\xbe\xff\x84\x01\xe3\x29\xf6\xb3\xe8\xdc\x8a\x9a\xf1\x3a\x53\x3b\x60\xb9\x62\x93\x0b\xc0\xce\x3d\x65\xd0\xb5\xa2\x76\xe8\x5a\x0c\x74\xf4\x59\xfb\x07\x29\x92\x99\x1b\xa9\x68\x49\x02\x34\x78\xab\x28\xd3\x81\xaa\x67\xd2\x2c\x9c\x3b\x09\x2a\x02\x3f\x06\xc9\x6e\x11\xfd\x2f\x1b\x4d\x9d\xaf\x0f\x34\x49\xde\x17\x97\x61\x2a\x81\x13\xd6\xe6\x26\xcc\x3f\x99\x5e\x1c\x11\x0e\x65\xd1\x7c\x63\x6c\x92\x92\x9f\x91\x36\x39\xa9\x7c\xd0\x49\x15\x58\x30\xdc\x0f\x76\x04\x91\x23\xbe\x3d\x3d\x79\x15\x9f\xc2\xb4\x25\x8e\x94\xb8\xbf\x80\x8d\x7c\x46\xbe\xef\xe6\xdf\x0a\x83\x03\x7d\x15\xa7\x2a\x58\x1d\x8a\xde\xdd\x8f\x01\x3b\x38\xf5\x50\x2d\x73\x6d\x1d\x2f\x04\xb0\xe5\xdc\x22\xeb\x1a\x41\x4e\x52\xb1\xa9\xe8\x73\x5e\x05\x92\x28\x8c\x9e\x5a\x0a\x78\x53\x1e\x95\x97\x4a\x5d\x48\x86\x0f\x8e\x5b\x04\xeb\xd3\xeb\x56\xad\x12\xad\xc4\x6e\xc7", )?; test( HashAlgorithm::SHA224, b"\x1e\x56\x3f\xc3\xad\x02\x7a\x9c\xc6\x06\xbe\x19\xb2\x58\xbf\x70\xdd\x8b\x52\x73\xe2\x96\x23\x6e\xe8\xd7\xa6\x53\x31\x58\x50\x14\xf0\x50\x06\x51\x5b\xed\xd6\x33\x02\x50\xe5\x98\x5f\xda\xa8\x70\xae\xa6\x57\x66\xff\x56\x9f\xc4\x89\x13\x98\x90\x41\xcf\xf6\xfb\xab\xcd\x83\xfd\xf0\x64\xcd\x39\x32\x00\x1b\x26\x1c\x69\xa6\x70\xbd\x48\x06\x9c\x96\xe7\xeb\xec\xf1\x38\x0d\x82\x75\x19\x66\xc7\xf8\xd6\x9e\x0e\x94\xef\xc7\x75\xfd\x1c\x4a\x0c\x11\x8f\x21\x3a\xb1\x79\x47\x5c\xd0\xcf\x6d\xae\xc9\x4e\xef\x6f\xf6\xbd\x06\x40", n, e, b"\x80\xd3\xff\x1f\x74\xa8\x10\x95\xd0\xba\xa2\xe9\xde\x24\x8c\x03\x12\xca\x5a\x81\x7b\xc9\xf5\x15\x6a\x29\x3d\x80\x89\x6a\xde\xc5\x50\x7e\xe8\xf2\xdf\x41\x7a\xfe\x87\x79\x66\x8e\x25\xb4\x6f\x49\xe4\x35\x7a\x71\x70\x53\x1e\xd3\x07\x61\x10\x3d\xbb\x99\x41\x35\xb5\x10\xd9\x1d\xb9\xfe\x1f\x12\x68\xf4\x37\xe0\xf3\xa7\xa4\xba\x6a\x4d\x0b\x9e\xb7\x0d\xfc\x09\xfe\xd4\xb4\x4b\x35\x60\x85\x01\xc2\xdf\xd7\xa2\x30\xa2\x8d\xad\x14\x92\x6d\xa4\x60\x0b\xa7\x85\xe4\x96\x21\x2e\x57\x73\x8d\xd5\x75\xb4\x0c\x23\x34\x7b\x16\x35\xec\xdf\x2b\x91\x94\xd9\x6b\x14\x50\xa6\x87\x6a\xa7\x6d\x04\xaa\x59\x47\xcc\xe7\x1d\x85\x12\x1e\x0b\xf5\x78\xe8\x1c\xf7\x8c\x6a\x04\x7e\x30\xfc\x1d\x87\xcf\xd3\x01\x9d\xe4\xbb\x48\x29\x4c\x25\x86\x0b\x45\x03\x55\xbc\x26\x62\xaa\x36\xd6\xe3\x3f\x00\xad\x79\x25\x7d\x2d\x8b\x91\xf7\x3f\x27\xc3\x2a\x9a\xfc\xb1\xe1\xf0\x15\xf7\x7c\xb6\xb0\xdf\x51\xfb\x39\xee\x1b\xd7\x6a\xc4\x2c\x20\x79\x1d\x79\xcf\x3f\x36\x3f\xb3\x24\xdb\x30\xee\x82\xbc\xc1\xdf\x1a\x95\x64\x33\x0c\x12\xa5\x49\x65\x9b\xd3\x01\x00\x01\x57\x31\x33", )?; test( HashAlgorithm::SHA224, b"\x90\x0a\xe7\xe2\xe7\xe5\xf6\x15\x75\x0c\x4e\xe4\xc1\x3c\xca\x8f\x9f\x45\x07\x14\xa6\xb2\x73\xf2\xe4\xac\xa6\x32\xd1\x1c\xf6\xa8\x82\x10\x45\x77\x1f\x60\x1e\xd3\x97\x91\x01\x0b\x92\xf9\xfa\xc6\xa8\x24\x78\x8c\xd0\x77\x5d\x89\x1b\x13\x52\x8e\xa2\xfd\x5d\x59\xbc\x7b\xb5\x16\x75\xc1\xd5\x26\x3c\xcc\xcf\x1e\xdc\x8f\xe3\x13\xae\x4d\x50\x15\x0c\x46\x6a\xf9\x08\x95\xed\x5c\x5e\x59\x91\xe4\xa8\x13\xde\xc9\xd1\x4f\x42\x94\xcc\x87\x61\x27\x86\x44\xac\xfe\x19\x86\x35\xb4\x42\x66\xc1\xc9\x15\xfa\x1f\xa2\xef\x79\xb9\xd1", n, e, b"\x39\xc6\x48\x91\xd9\xac\x47\x41\xa5\x7d\xd8\xae\xc7\xf7\x24\x36\x13\xd1\x55\xdf\x44\x92\x81\x4b\x40\xce\xab\xee\x79\xea\xdb\x8d\x8b\xc5\xfa\x61\x1b\xde\xbe\x0e\x0d\x97\x14\xc4\x3d\x6d\x29\xef\x30\x9f\x78\x2b\xc8\xe6\x8a\x4d\x31\x7c\xe1\xec\xe4\x68\x55\x23\x05\xa7\x3d\xb9\xd0\xd2\x89\x1e\x28\x04\xf4\x20\x1b\x1b\xf8\xa3\x24\x6f\xa0\x82\xad\xde\x1f\xc9\xb3\xd2\x99\xf8\x8c\xb9\x3b\x7b\x47\xfe\x9f\x73\x13\x70\x96\xc2\xb8\xc5\x9e\xc0\x61\x2a\x08\x53\x63\xc0\x4c\xc3\x74\x76\x9a\x96\x4f\xea\xf1\xf8\xe4\x91\x38\x1e\x16\xd7\xae\x2a\x0c\x67\x2e\x69\xa3\x66\x73\x10\xfe\xed\x01\x21\x56\xdc\xa6\x30\xa6\x8d\x33\x9e\xc8\x04\x96\xc6\xb5\x94\xfe\xd1\x70\x91\xd3\xa1\xc6\xac\x3e\x4d\xa1\x41\x9b\x05\xd5\x89\xcb\x32\x46\x82\x88\xf7\xdf\x4d\xaa\xce\xff\x5a\x39\xbc\xf2\x97\xdc\x50\x8c\xe9\x54\x9f\x60\x2e\x97\x3e\xdb\xc2\xaa\x44\x33\x2e\xc3\x66\x1b\x19\xc8\xc5\x8c\x56\x16\x92\x4b\xeb\x89\x2f\x77\xb5\xe2\x00\xd6\xfb\x3f\xc7\x59\x26\x3a\x74\x9d\x15\x7e\xff\x9f\x73\x67\x98\xd2\x81\xb2\x5b\x71\xfb\x47\x0b\xdb\x70\x0f\x21\x1f\x84\x1d\xb7", )?; test( HashAlgorithm::SHA256, b"\x5a\xf2\x83\xb1\xb7\x6a\xb2\xa6\x95\xd7\x94\xc2\x3b\x35\xca\x73\x71\xfc\x77\x9e\x92\xeb\xf5\x89\xe3\x04\xc7\xf9\x23\xd8\xcf\x97\x63\x04\xc1\x98\x18\xfc\xd8\x9d\x6f\x07\xc8\xd8\xe0\x8b\xf3\x71\x06\x8b\xdf\x28\xae\x6e\xe8\x3b\x2e\x02\x32\x8a\xf8\xc0\xe2\xf9\x6e\x52\x8e\x16\xf8\x52\xf1\xfc\x54\x55\xe4\x77\x2e\x28\x8a\x68\xf1\x59\xca\x6b\xdc\xf9\x02\xb8\x58\xa1\xf9\x47\x89\xb3\x16\x38\x23\xe2\xd0\x71\x7f\xf5\x66\x89\xee\xc7\xd0\xe5\x4d\x93\xf5\x20\xd9\x6e\x1e\xb0\x45\x15\xab\xc7\x0a\xe9\x05\x78\xff\x38\xd3\x1b", n, e, b"\x6b\x8b\xe9\x7d\x9e\x51\x8a\x2e\xde\x74\x6f\xf4\xa7\xd9\x1a\x84\xa1\xfc\x66\x5b\x52\xf1\x54\xa9\x27\x65\x0d\xb6\xe7\x34\x8c\x69\xf8\xc8\x88\x1f\x7b\xcf\x9b\x1a\x6d\x33\x66\xee\xd3\x0c\x3a\xed\x4e\x93\xc2\x03\xc4\x3f\x55\x28\xa4\x5d\xe7\x91\x89\x57\x47\xad\xe9\xc5\xfa\x5e\xee\x81\x42\x7e\xde\xe0\x20\x82\x14\x7a\xa3\x11\x71\x2a\x6a\xd5\xfb\x17\x32\xe9\x3b\x3d\x6c\xd2\x3f\xfd\x46\xa0\xb3\xca\xf6\x2a\x8b\x69\x95\x7c\xc6\x8a\xe3\x9f\x99\x93\xc1\xa7\x79\x59\x9c\xdd\xa9\x49\xbd\xaa\xba\xbb\x77\xf2\x48\xfc\xfe\xaa\x44\x05\x9b\xe5\x45\x9f\xb9\xb8\x99\x27\x8e\x92\x95\x28\xee\x13\x0f\xac\xd5\x33\x72\xec\xbc\x42\xf3\xe8\xde\x29\x98\x42\x58\x60\x40\x64\x40\xf2\x48\xd8\x17\x43\x2d\xe6\x87\x11\x2e\x50\x4d\x73\x40\x28\xe6\xc5\x62\x0f\xa2\x82\xca\x07\x64\x70\x06\xcf\x0a\x2f\xf8\x3e\x19\xa9\x16\x55\x4c\xc6\x18\x10\xc2\xe8\x55\x30\x5d\xb4\xe5\xcf\x89\x3a\x6a\x96\x76\x73\x65\x79\x45\x56\xff\x03\x33\x59\x08\x4d\x7e\x38\xa8\x45\x6e\x68\xe2\x11\x55\xb7\x61\x51\x31\x4a\x29\x87\x5f\xee\xe0\x95\x57\x16\x1c\xbc\x65\x45\x41\xe8\x9e\x42", )?; test( HashAlgorithm::SHA256, b"\xc4\x30\x11\xf3\xee\x88\xc9\xc9\xad\xca\xc8\xbf\x37\x22\x1a\xfa\x31\x76\x9d\x34\x7d\xec\x70\x5e\x53\xac\xa9\x89\x93\xe7\x46\x06\x59\x18\x67\xcc\xd2\x89\xba\x1b\x4f\x19\x36\x5f\x98\x3e\x0c\x57\x83\x46\xda\x76\xc5\xe2\x22\x8a\x07\xe4\xfc\x9b\x3d\x48\x07\x16\x33\x71\xa5\x2b\x68\xb6\x68\x73\x20\x1d\xc7\xd6\xb5\x66\x16\xac\x2e\x4c\xb5\x22\x12\x07\x87\xdf\x7f\x15\xa5\xe8\x76\x3a\x54\xc1\x79\xc6\x35\xd6\x58\x16\xbc\x19\x48\x5d\xe3\xeb\x35\xa5\x20\x40\x59\x10\x94\xfe\x0e\x64\x85\xa7\xe0\xc6\x0e\x38\xe7\xc6\x15\x51", n, e, b"\xaa\x3a\x4e\x12\xeb\x87\x59\x6c\x71\x1c\x9a\x22\xbc\xab\xcb\x9d\xad\xff\xca\xbc\xec\xbd\x16\x22\x88\x89\xe9\xbb\x45\x7d\x5d\x22\x57\x1a\x72\xf0\x34\xbe\x47\x83\x38\x4f\x43\xce\x6f\xff\xc6\x05\x34\xb8\x33\x1c\xdd\x5d\x7c\x77\xf4\x91\x80\xbf\xd1\x94\xb5\xfd\x43\xa5\x08\xc6\x6d\x78\x6c\x55\x88\x76\x73\x58\x94\xe6\xa9\x30\x09\x52\xde\x79\x2f\x74\x70\x45\xe7\x4d\x87\xfd\x50\x98\x02\x30\x70\x7a\x34\xa4\xdf\x01\x3c\xe0\x50\xbb\xff\x0d\x6f\x57\x08\x85\xc9\xc7\xbf\x8d\xc4\x99\x13\x2c\xae\xe0\x71\xb4\x1d\x81\xff\x91\xb8\xce\x21\xaa\x2f\x28\x2c\xbf\x52\x38\x9f\x23\x9a\xfe\x14\x90\x89\x0b\xe2\x1f\x9d\x80\x8b\x3d\x70\xb9\x7e\xfd\x59\xc0\xb6\x0e\x46\x60\x88\xbb\x42\x71\x4f\x21\x2b\xc9\x0d\xb7\xe9\x42\xeb\xce\xe6\x0e\x7b\x10\x7f\xff\x44\xfb\x35\x64\xff\x07\xd6\xd0\x28\x50\x21\x5f\xd3\x57\xd8\x97\xc4\xd3\x2b\xef\x86\x61\x68\x9f\x2d\x84\xff\x89\x76\x37\xfb\x6d\x55\x68\xa7\x27\x0e\x78\x34\x26\xb7\x4b\x70\x37\x49\x3e\x51\x55\xfd\x7c\xb3\xdd\xdd\xfd\x36\xbd\x8a\x9c\x87\x7d\x71\xd2\xa9\x66\x05\x7c\x08\x26\x3d\x29\x39\xc8\x49\x87", )?; test( HashAlgorithm::SHA256, b"\x61\xd7\xb3\x15\x01\x31\x35\x1e\x7b\x4c\x8e\x56\x45\xd3\x8b\xe9\x33\x5b\x40\x28\x9a\xf3\x4c\xc6\xb6\xfc\x5e\x48\x49\x3b\xf8\xb7\x85\x2c\x73\x98\x2c\x99\x44\x1e\xf6\x6c\x7d\x9d\x33\xc2\x97\x42\xb1\x40\x6e\x02\xe0\xaa\x8d\xd0\x34\xb1\xac\x13\xcb\x0d\x77\x57\x50\xcc\x91\x42\x1f\xea\xd9\xca\xa9\x21\xec\xa6\x1a\x02\xeb\x02\x3a\x45\x7e\x77\x91\x5e\x18\x3a\xcf\x51\x7d\x94\x6b\xc6\x82\x92\x89\x60\x14\xfd\x21\x4b\x7c\x8c\x5e\x14\xe1\x59\x44\xbe\x0f\x92\x96\x12\x77\x71\xf7\x36\x76\x6e\x4f\x81\xda\xb3\x70\x8e\xa2\xd0", n, e, b"\x84\xe9\x2a\x14\x5a\xe6\xbe\x1f\xf9\x24\x2d\x9e\xd2\xd6\x8d\xe6\x68\xe8\x02\x52\x4e\x8a\xc0\xa7\x9d\xe6\x2f\xe7\x40\x48\xc3\x54\x91\xfd\x2f\xfd\xb1\x85\x05\x7e\x66\x6d\xbf\xaa\xc8\x4c\x34\xfd\xe7\x89\x12\x63\xf8\xb2\xbc\x74\x74\x62\x30\x32\x0f\x67\xa7\xbd\x73\x19\xc9\xb9\xde\x41\x90\x54\x70\x14\xe2\xd7\xa2\xa5\x06\x0d\x62\x00\xaa\xdc\x3a\x44\xba\xc0\x29\xff\x39\x92\xed\xd3\x0e\xc5\x3a\xb0\xd9\x12\x3e\xaa\x6b\x14\x73\x52\xa0\x73\xa9\x81\x61\xe6\x4f\x39\x4b\xb9\x94\x92\xc6\x97\x7e\x24\xf4\x45\xc7\x12\x5b\xfb\x90\xf8\x7f\xaf\x26\x22\x72\x13\x4a\xcb\x18\x82\x3a\x99\xa5\x22\x8d\x14\x95\x46\x32\x97\xfd\x77\x48\x77\xfb\x63\xd4\x91\x81\x06\x34\x7e\x6f\x29\x31\x5e\x48\x36\x3f\x39\xb3\x32\x99\xea\xa3\x2d\x8d\xa7\x1b\x22\x9d\x8f\xfe\xe5\xf6\x6f\x72\x2a\xd3\xaa\x41\x75\xd3\xf8\x4e\xce\x9c\xc8\xec\xa8\xd6\xf2\xf3\x56\xa8\x5c\x15\x24\x89\x6c\x18\xf7\xb5\xc8\xf9\xbc\xde\xf4\x5c\x49\x6d\x53\x91\x79\x89\x1d\xdc\x76\xe5\x20\x8a\xd8\x35\x3d\x48\xc6\x24\x05\x4f\x34\x40\xee\xba\x44\x32\xa1\x06\x54\xa1\x1e\xf5\x37\x83\xbd\x11\x6f", )?; test( HashAlgorithm::SHA256, b"\xb6\x77\x1a\xb0\xe1\x28\xb4\x1b\x32\xb8\xb0\x5e\x05\xad\xd2\x3c\xe0\xfb\x87\x7b\x40\xbf\xcc\x3b\x99\x2f\x4c\x86\x98\xd1\xc8\x28\xab\xec\xbc\xc1\xc3\x3d\x40\x18\x59\xea\x2c\xb2\xaf\xbc\x7f\xa4\x58\x88\x02\xa5\xfa\xee\x28\x67\x53\x46\x39\x28\x7a\xd8\xaf\x84\x67\x4b\xe1\x8d\xb6\x61\xde\x1d\xa8\xe1\x9c\x6b\x6b\xd4\x52\xdd\x9b\xf3\x22\x1d\x08\x61\xfb\x6f\xba\x96\xbe\x42\x32\x9b\x9f\x04\xf3\x7d\xcf\x3b\x41\xfc\x58\xd2\x29\x83\x48\xb0\xc1\x5d\x11\x90\xb1\x25\x30\x0c\xf2\x7e\x0d\xfa\xd6\x05\x22\xfc\x49\x84\x60\x53", n, e, b"\x62\x76\x92\x55\x68\x62\x6f\x0c\xbe\x6f\x51\x50\xb0\x50\xe1\x70\x25\x82\xf8\xda\xf9\x9a\x6f\x88\x0e\xf7\x5c\xd9\x6c\x2d\x42\x08\xfb\x6e\x91\xb0\x1b\xa6\xab\xa2\xa8\x16\xb2\xd3\xcb\x97\x5d\xf8\x50\xb1\xd2\x68\xc4\x66\x2d\xd1\xea\x3a\x30\x0c\x1d\x71\x71\xc6\x33\xdd\x2e\xfb\xac\x30\x00\xc5\x6a\xb8\x0f\x98\x9d\xbc\x18\x24\x3e\x63\x6b\xa5\xd4\xd2\x6a\x7d\x3f\x19\x65\xad\x3c\xb0\xf1\xa8\x51\x3f\x99\x80\x03\xf7\xb6\x7e\x2a\xc5\xc7\x18\xcb\x68\x8b\x32\x01\xd5\x6e\x68\xf0\xb9\xf8\x62\x57\xb8\x47\x94\xcd\xff\xbc\x1f\xe3\xea\x24\xb7\xbb\x6e\x9e\xf0\x53\x9b\xd4\xfb\xc1\xaf\xb5\x5b\xc1\xdc\xa3\x99\x96\xea\x8a\x63\x76\x9f\x6e\x22\x57\x07\xf6\x90\x47\x55\x5e\x1a\x4e\xf3\xc6\x39\xc5\xf2\xa4\x97\xb8\x89\x42\x4a\x90\x14\x86\x39\xbb\x64\xdf\x0a\x06\xe0\xb7\xf0\xe8\xed\x46\x6a\x97\x7b\xac\xa3\x2f\x48\x23\x37\xb2\xab\xe3\x98\x3e\xae\xc3\xfe\x10\x75\x01\x6e\x58\x67\x52\x17\x60\xfd\x06\x07\xd7\x99\xf1\x76\x6b\x3f\xf6\xe2\xae\x15\x5d\x69\x25\x0f\x8b\xf0\x8c\x8e\xdc\xa0\xb4\xf3\x1d\x0f\x83\x8c\xfd\x29\x8c\xb7\x31\x2d\xf9\x3f\x09\x97", )?; test( HashAlgorithm::SHA256, b"\x6a\x81\xcb\x6c\x7b\x26\x8f\x4b\x9f\xb9\x17\x2a\xdb\xbb\x36\xa2\x37\xa0\xdc\xf1\xc3\xc8\x3a\x95\xdc\xb0\x27\x1a\xac\x6a\xc3\x30\xf0\x4a\x5a\x00\xfe\xe3\x8b\xc0\x06\x31\xa9\x85\x98\x18\x61\x59\x66\x0d\x9d\x8e\x4c\x14\xa9\x52\x8d\xea\x94\x83\x60\x83\xda\xc4\xab\xb7\x3f\xd0\x0e\x38\xfe\x0e\x23\xc7\x23\x66\x04\xa7\x36\x54\x0e\x52\x19\x3a\xe5\x6c\x33\xfb\xb8\xf5\xcf\xc5\xc7\xc2\xbe\x2e\x22\x2e\x44\x83\xb3\x0d\x32\x5c\x7e\xe1\x4f\x74\x28\x51\xfc\xb8\xb6\xd6\x18\x9e\x98\xb8\x22\xb8\xe6\x39\x9d\x89\xe9\x0f\xb9\x97", n, e, b"\xb6\x79\x91\x05\x0c\x08\x3e\x64\x50\x97\xdb\x03\xff\xf3\x47\x58\x86\x8b\xeb\x19\xe9\xc0\xc4\x84\x75\xf0\xf9\x13\x36\x1e\x71\xd3\xd6\xf2\x7a\x8c\x4f\x0b\x26\x9b\x49\xe8\x53\x40\x39\xe5\x3a\xd3\xba\xb9\xa3\xe6\x2a\xbe\x07\x8e\xe7\x5e\x7f\xb5\x95\x90\x06\xfb\xfb\x01\x4c\xa7\xb8\x1b\x3d\x5a\xfe\x0e\xe5\xf6\xfc\x2d\xfb\xc4\x50\xf2\x83\x95\x43\x00\x2f\x33\xf4\xf3\x54\xf8\x27\x27\x8c\x76\xc0\x41\x68\x6e\xea\x78\x86\xeb\xb2\xa7\xaf\xa5\x99\x5c\x6c\xdd\xb1\xc0\xb5\x80\x66\xdd\xb8\xdc\x54\xa6\x92\x7c\x14\x6c\x3b\x2a\x0f\xa7\xce\xf2\x89\x03\xc6\xc6\x72\xbc\x20\xef\x68\xff\xbf\xab\x24\x7e\xb6\x88\xab\x4b\xde\x71\x06\xd9\xc5\x9d\x21\x53\x09\x6d\xc9\xe5\x20\x72\x67\x03\x8d\x88\xe2\x17\x4e\x76\xad\xc1\x50\x8a\xe2\x4e\xb6\x02\x33\x2e\x53\xc0\xc2\xe3\x31\x54\xa6\x6a\x97\xa0\xf1\x2f\x66\xc6\x12\x58\xc7\xbf\x6b\xbf\x3f\x1d\xcb\xe9\xca\xf2\xfd\x30\xec\x68\xc0\xa9\xd0\x9f\x4f\xd7\x76\x30\x4b\x54\x0e\x62\xfc\x85\x12\xbe\xaa\xbc\x4b\xe2\x10\x7a\x1e\xc1\x8e\x87\xf6\x1f\x9d\xb2\x5e\x87\x1d\xc0\x69\x3c\xef\x17\xc2\xa6\x87\xfc\x85\x4f", )?; test( HashAlgorithm::SHA256, b"\x05\x6c\x1e\x46\x44\x59\x9e\x31\x83\xdd\x8d\x2f\x64\xe4\xbb\x23\x52\xff\x00\xd0\x12\xab\x76\x3f\x9a\xd6\xe5\x60\x27\x9f\x7f\xf3\x8a\x5e\xce\xa9\xc2\xe4\xea\x87\xd0\x04\xef\x8c\xc7\x52\xae\x93\x23\x2a\xa3\x7b\x5b\xf4\x28\x84\xba\xa7\xe7\xfc\x6a\x8c\x95\x1c\xd2\x45\xde\x2d\x22\x0d\x9b\xee\x2b\x41\x4b\x3a\x75\x20\xc1\xe6\x8b\xcf\x1a\xe9\x9a\x9f\xf2\xbf\x3a\x93\xd8\x0f\x8c\x1d\xfe\x8b\x85\x29\x35\x17\x89\x5c\x19\x2e\x3c\x9e\x89\x82\x95\xd6\x5b\xe3\x34\xf4\x4d\x62\xf5\x35\x3e\xb6\xc5\xa2\x9e\xdf\xb4\xdb\x23\x09", n, e, b"\xae\x05\x20\x4e\x40\x9d\x72\x7e\xb9\xe4\xdc\x24\xbe\x8f\x86\x33\x28\xc2\x81\x3d\xa4\xfc\xef\x28\x86\x6e\x21\xa5\xda\xb2\x1a\x48\x53\x21\xb7\x35\x27\x4a\xf0\x6b\xf1\x7e\x27\x15\x18\xe1\x11\x64\xd7\x22\xab\x07\x35\x48\xf0\x2e\x1b\x44\x19\x23\xdb\x6f\x1c\xee\x65\xa0\x17\xed\xfb\xaf\x33\x61\xc6\x7f\xbc\x2b\x39\xfe\x03\x8c\xb5\xcb\x65\xa6\x40\xf9\x58\x87\x38\x9c\xe8\xa5\xad\x2e\xc6\xe6\x9d\x3d\x60\x35\x05\xb0\x25\xf6\xd6\x33\x0c\x8b\x64\x88\x02\xca\xf7\xe6\xfa\x3f\xe7\xb3\x81\x41\x65\x99\x86\xcb\x89\xe6\x23\x2f\x10\x62\x22\x56\x4d\x5e\x51\x95\xed\xa6\xa2\x5f\x99\x06\x85\x72\xc2\xfa\xfe\x97\xf1\x47\xf7\xf2\xf4\x11\x9f\x21\x38\x5a\xf1\xfc\xed\x97\xf7\x86\x32\xd8\xbf\x4f\xd9\xa9\x05\x4d\x8b\x9a\xa2\xa9\xf4\xde\xd5\x87\x84\x7a\x91\xd4\x2c\x63\x91\x12\x5f\x10\x3a\xe2\x88\x54\x7e\x84\x89\x69\x3a\xe8\x68\x6b\x84\x89\x1b\x77\x2b\x10\xc4\x79\x68\x83\xf6\x6c\xd4\x59\xa8\xc1\xa6\xa4\x18\x7b\xd6\xb3\x87\xd3\x49\xe9\x2d\x7b\x60\x49\x53\x72\x7c\x9e\x9f\xdc\x44\x9e\x73\x45\xe7\xca\x6b\x33\x9e\x26\xb0\x86\xf5\x54\x88\x98\xcb\xe9", )?; test( HashAlgorithm::SHA256, b"\xce\xc5\xc9\xb6\xf8\x44\x97\xac\x32\x7f\x68\xef\x88\x66\x41\xfe\xc9\x95\x17\x8b\x30\x71\x92\x30\x43\x74\x11\x5e\xfc\xc5\xee\x96\x27\x0c\x03\xdb\x0b\x84\x6d\x67\x4c\x52\x8f\x9d\x10\x15\x5a\x3f\x61\xbe\xcc\xe1\xd3\xa2\xb7\x9d\x66\xcd\xc4\x09\xad\x99\xb7\x66\x30\x80\xf5\x1a\x10\x2f\x43\x61\xe9\xdb\xd0\x3f\xfc\xd8\x76\xb9\x8e\x68\x3d\x44\x8b\xd1\x21\x7e\x6f\xb2\x15\x1c\x66\x96\x47\x23\xb2\xca\xa6\x5c\x4e\x6c\xa2\x01\xd1\xc5\x32\xbd\x94\xd9\x1c\xd4\x17\x3b\x71\x9d\xa1\x26\x56\x39\x27\xca\x0a\x7f\x6f\xe4\x25\x36", n, e, b"\xc4\x8a\x8e\x01\xd4\xbb\xfe\x0f\x2f\x05\x65\x93\x37\xea\x71\xd2\x1f\x38\xd7\xf7\xa1\x0b\x00\xb0\x6e\x1f\x89\x9e\xaf\x40\xa8\xe9\x7e\xad\x64\xbc\xa3\x7f\x13\xa5\x5e\xf1\xcf\x3f\xb5\x2c\xee\x27\x9c\xdc\xb0\x96\x08\x5a\x46\x7a\xfa\x97\xb0\x3d\x78\xd6\x07\x6e\x47\x2b\x12\xd6\xbe\x96\x47\xce\xc3\x2d\x8d\x91\xa2\x62\x47\x69\x37\x71\x68\x74\x60\xba\x52\x69\xde\x18\xe1\xed\xef\x60\x22\x53\x3a\x95\x79\xf9\x1d\x58\x4f\x9e\x0c\xee\x11\x00\xc4\x47\xb7\x75\x76\xb1\xb4\xee\x16\x3e\xd4\x70\x01\x47\xa9\xaa\x61\xbd\xc4\xe2\x31\x6d\x2d\x81\x8c\x10\x28\xed\x1c\x3e\x37\x2c\x9f\x6a\x17\x45\x57\x24\x44\x63\x72\x48\x09\x1b\x83\xf7\xb5\x39\xf9\xbd\x58\xb7\x67\x56\x76\x03\x4c\x20\xe4\xca\x11\x9b\x91\xc4\xca\x5d\xc7\x6a\xcb\xff\x3d\x04\x62\x89\x83\x52\xc5\x91\xc2\xca\x6f\x2d\x8b\x09\xe2\xe6\x33\x8a\x84\x33\x6e\x06\xf0\xcc\x02\x0e\x9e\xb8\xda\x78\x58\x89\xb4\x97\xf3\xb9\x8e\x82\x7e\xe7\xa7\xd3\xf1\xb0\xb7\x3c\x19\x58\xe1\x6a\xa9\x78\x61\xe6\x67\x59\x70\xce\x31\xd9\xd1\x19\xbb\x34\x0b\xe8\x0f\xd0\xf4\x3c\x3d\xbe\x64\xf2\xa5\x9d\x62\x9d", )?; test( HashAlgorithm::SHA256, b"\x91\x93\xf8\xb9\x14\xdf\xe0\xe6\x25\x21\xf3\x5a\xfa\x4f\xa5\xd4\x28\x35\xe1\x98\xaf\x67\x38\x09\x37\x7a\x3e\x7a\x99\x73\x31\x42\xa1\x80\xdc\x0e\x13\xe6\xbb\x7c\xeb\x3b\x60\xe5\xe9\xd5\x15\x79\x4d\x82\xc3\x92\xe0\x79\x13\x42\x33\x91\xd2\x2e\x2b\xb1\x9a\xa0\xbd\x88\xaf\xd7\xf7\x7e\x27\xa2\x40\xea\x4e\x2d\xe0\x85\x48\x1a\xc3\x1f\xf8\xd3\x79\x90\x21\x1f\x82\xf2\xcb\xf4\xc9\x0d\xe9\x8d\x6e\x13\x38\xbb\xc8\x8e\x6a\x80\xab\x96\x84\xda\xe6\x47\x85\xdd\x10\x72\x48\x04\x85\x93\xab\xc9\xab\x03\xf1\x73\x7a\x6f\x65\x30", n, e, b"\x5c\x2f\xe4\x53\xa8\xb0\x8c\x90\xb0\x2e\xb2\xc9\x99\x42\x42\xd5\x18\xf3\xf2\x1b\x36\x88\x95\xcf\xfd\x62\x40\x50\xe4\x8a\xa7\x14\x00\x5a\xe6\x75\xfe\x79\xaa\x3c\xad\xd4\xdf\x55\xbd\xf1\x2b\xec\x5b\xe8\xa4\x1d\x87\x53\x8f\x7e\x03\x1b\x78\x2e\x34\xd3\x92\x46\x8e\x5f\x14\xbc\x61\x3b\x8f\x4d\x28\xc8\xfb\x79\xa2\x53\x7e\x1e\x60\x10\x31\xda\x72\x0a\xcd\x7b\x2c\x8d\xcb\xe9\x85\x86\x24\xa7\xa9\xa9\x2a\x06\xf9\x18\x45\xf7\x32\x37\x0d\x67\x36\x5c\x64\x64\xf7\xb6\x8f\x22\xeb\x3e\xdf\xee\xc9\x7e\x32\x85\x02\x4d\x7f\x69\x43\xb6\xd5\x0a\x16\xcc\x96\xd6\x0f\x68\x03\x51\xde\xaa\x25\xf0\xbc\x86\x89\x48\x60\x7a\x6b\xa7\xf1\x94\x9b\x85\x94\x3c\x6a\x92\xbd\x61\x72\xe8\x1b\xcc\x05\x50\x14\xb7\x8a\x73\x39\x72\xe3\xf3\x9d\x14\x09\x9d\x16\x07\xa2\x0f\xf8\x68\x1c\x29\xae\x1e\xf9\x9e\xf1\x15\xed\x6a\x10\x84\xb5\x14\xb8\x1a\x69\xd4\xa1\x5c\xe1\xe2\x57\x6f\xdc\xf2\xb2\xaf\x61\x5b\x52\xfe\xc7\x01\x32\x11\x2d\xcc\x5b\xc1\x9e\xc1\x7f\x32\x28\x14\x60\x62\x34\x20\x31\x73\x53\xe8\xa2\x55\xfd\xa5\x02\xbd\x1f\xb1\x1a\x58\x83\x2a\xe2\xc0\x4f\x9a", )?; test( HashAlgorithm::SHA256, b"\x0e\x57\xef\x40\xb0\x21\xbf\x87\xf6\x42\xc5\x75\x6b\x65\x15\xa0\xe0\x6c\x15\xa0\x18\x56\xd7\x16\xc5\x66\xa6\xed\xb3\x81\xdf\xdf\x44\xd9\x03\x3b\x1c\xc8\x09\xe6\x1d\xfe\xf9\xa0\x96\xdf\xb6\x89\xb7\x27\x1b\xe4\x49\xd0\x4a\x1a\x9c\x35\x41\x02\xc0\x77\xaf\x5f\xf7\x20\x05\xab\x6b\x06\xcf\x13\x1d\x73\x45\xc2\x1e\x82\x1d\x62\x01\xcc\xa4\xe0\x90\x44\x0d\x70\xbe\x60\x09\xd2\xdd\x7a\x98\xd3\x11\x75\x1e\x16\x05\xa3\xb9\x14\xdc\xe6\xd2\x62\x6b\x16\xf2\x33\xa5\xa3\xd7\x1d\x56\x7c\xc8\x20\x15\x2f\x25\xe4\x73\x51\x42\x42", n, e, b"\x76\x43\xaa\x3f\xe6\x3e\x66\xf7\x9d\x6b\x40\x9d\x14\x5e\xa8\x20\xc9\xf7\x35\x6f\x71\xb4\xac\xdc\xbd\x43\xfe\x1e\x99\xf8\x80\x2c\xd1\x66\x2b\x16\x24\x0f\x5c\xfd\x94\xa7\x69\xb0\xb3\xf2\xcb\x0b\x11\x88\x7e\x88\x6e\x5b\xa4\x37\x33\x36\x74\x90\xb3\xfc\x18\x8f\x2f\xb3\xa0\xc0\xc8\xa6\x8b\x5d\x27\x26\xc8\xf7\xa3\x19\x02\xb6\xb8\x6c\xd4\x02\x28\x7d\x38\x5c\x3e\x3c\x06\x50\x3c\xe1\x7f\xd6\xe5\x4e\x58\x2f\x4a\x90\x7a\x91\xf9\x52\xd2\xa3\x60\xe2\xfb\xa0\x00\x28\xe4\xd3\xb0\x2a\xab\xf7\xd2\x20\xb3\x1d\x1f\x8e\xe7\xfa\xa0\x70\x14\x76\x82\xcc\xc8\xbc\xc7\x56\xca\x6a\x68\xfc\x20\x95\x45\x50\xc3\x17\xe8\x79\x18\x78\x1a\x3d\x1f\x19\x23\x50\x30\x91\x09\x0c\x3c\x60\xca\x1c\x0b\x1c\x69\x99\x06\xfb\xf8\x5a\xa7\x0a\xd9\xae\x48\x70\x9f\xf7\x43\xb8\x2d\xcc\x31\x07\x4c\xfc\xea\x62\x3e\xa4\x5e\x48\x64\x4b\x19\xa2\x17\x72\xca\x10\x7e\xd6\x42\x39\xc5\x65\x74\xa0\x87\xf1\xa6\xaa\xdf\x0f\x4b\x00\xff\xe5\x81\xc1\x41\x02\x74\xc8\x75\xe4\x59\x90\x63\xe4\x6e\x51\x68\x80\x3f\x0d\x28\xd2\x1f\xcd\x35\x09\xb4\xc6\x22\x29\x95\xad\xd7\x75\x3b\xf3", )?; test( HashAlgorithm::SHA256, b"\x0c\x84\x91\xfc\x34\x8d\x34\x1f\xe8\x5c\x46\xa5\x61\x15\xf2\x60\x35\xc5\x9e\x6a\x2b\xe7\x65\xc4\x4e\x2e\xc8\x3d\x40\x7e\xa0\x96\xd1\x3b\x57\xe3\xd0\xc7\x58\x34\x22\x46\xc4\x75\x10\xa5\x67\x93\xe5\xda\xea\xe1\xb9\x6d\x4a\xb9\x88\x37\x89\x66\x87\x6a\xa3\x41\xb7\xd1\xc3\x1b\xba\x59\xb7\xdb\xe6\xd1\xa1\x68\x98\xee\xf0\xca\xca\x92\x8f\x8c\xe8\x4d\x5c\x64\xe0\x25\xdc\x16\x79\x92\x2d\x95\xe5\xcd\x3c\x6b\x99\x4a\x38\x5c\x5c\x83\x46\x46\x9e\xf8\x76\x4c\x0c\x74\xf5\x33\x61\x91\x85\x0c\x7f\x7e\x2b\x14\xbe\x00\x27\xd8", n, e, b"\xca\xcc\x8d\x9f\x5e\xcd\x34\xc1\x43\x48\x84\x61\x13\x5c\x49\x51\x67\x61\x45\xc6\xe4\x72\xb9\x2f\x12\xf7\x58\x04\x6f\x17\x21\x42\xfa\x38\x8f\x28\x5f\x3f\xff\x06\x82\x42\x02\x88\x29\x04\x7e\x24\x80\x59\xed\x4f\xd3\x9d\x2c\x5a\xde\x46\x9d\xc7\xc3\x93\x45\xe5\x11\x49\x50\xd2\x03\x1c\xc7\x46\x5f\xe7\x12\xc4\x04\x1d\x05\xc7\x56\xd3\xf2\xd8\x8a\x46\xce\xb9\x9f\x2e\x24\xa5\x2e\x95\x8a\x03\xcd\x25\x19\xa9\xb1\x37\xe6\x2d\x5c\xa2\xb3\x53\xf7\xb0\x47\xb6\x25\xc3\x60\x23\x13\xfd\xb5\x3c\x8d\xb2\x3d\x83\x95\x1a\x59\x9d\xb3\x28\xfe\xdc\x4a\xe0\x6d\xa8\x9c\xe7\xf5\x62\x59\xb5\xc8\x22\x2f\x7b\xd3\xd9\x74\x04\x78\xfd\x28\xe5\x81\x0d\xb7\x8a\xee\x86\x23\xfd\xd3\x9f\x60\x3f\x8d\xdf\x98\x08\x1d\x78\x73\x98\x0c\x4e\xb0\xe2\x2a\x9c\xd4\x08\xf7\xc4\x13\x4c\x12\xd2\x04\x9a\x2d\x12\x0f\x4b\x62\xe6\xb3\x82\xb9\x97\xfc\x37\x5e\xf7\xac\x95\x5f\xcf\x80\xb0\x45\xc3\xd6\x38\x5f\xf4\x22\xda\xd3\x50\xc6\x88\x70\x53\x90\x68\xa1\x62\xa2\xed\xbb\x93\xce\xef\xed\x96\x77\x93\x9b\x90\xbd\x3d\xfa\x0d\xc0\x53\x46\x0b\x4e\x23\x32\xef\xa6\x92\x17\x9a", )?; test( HashAlgorithm::SHA384, b"\x6c\xd5\x9f\xdd\x3e\xfd\x89\x3d\x09\x1a\xfd\xc3\x15\x5d\x35\x4f\x10\xd6\xd8\x81\x67\x42\x7a\x2c\xf7\x24\x62\x07\xe5\x17\x91\xa6\xca\x62\x00\xa9\x14\xcd\x28\x34\xa9\xb3\xc7\x9f\xcd\x59\xe2\x6e\x45\x7e\x06\x83\xbc\x33\xd4\x92\x67\xed\xbd\xd6\xe5\xd9\x09\x02\x69\x6f\x1e\x7b\x1a\x4a\xff\xc4\xba\x37\x13\x39\x86\x8c\x28\x01\x5e\xbb\xb7\x3e\x26\x26\x69\x86\x6c\x35\xdb\x97\x4b\xa6\x9e\x46\x8f\x25\x83\xb9\x19\x1d\x15\xd6\x86\xcd\x66\xfb\x0b\x9e\x0f\xf0\xa3\xb4\x72\x1a\x6d\xc3\x42\xf1\x4f\x24\x46\xb4\xe0\x28\x59\x5b", n, e, b"\x39\x74\x90\x0b\xec\x3f\xcb\x08\x1f\x0e\x5a\x29\x9a\xdf\x30\xd0\x87\xaa\xba\xa6\x33\x91\x14\x10\xe8\x7a\x49\x79\xbb\xe3\xfa\x80\xc3\xab\xcf\x22\x16\x86\x39\x9a\x49\xbc\x2f\x1e\x5a\xc4\x0c\x35\xdf\x17\x00\xe4\xb9\xcb\x7c\x80\x5a\x89\x66\x46\x57\x3f\x4a\x57\x0a\x97\x04\xd2\xa2\xe6\xba\xee\x4b\x43\xd9\x16\x90\x68\x84\xad\x3c\xf2\x83\x52\x9e\xa2\x65\xe8\xfc\xb5\xcc\x1b\xdf\x7b\x7d\xee\x85\x94\x1e\x4b\x4f\xb2\x5c\x1f\xc7\xb9\x51\xfb\x12\x9a\xb3\x93\xcb\x06\x9b\xe2\x71\xc1\xd9\x54\xda\x3c\x43\x67\x43\x09\xf1\xd2\x12\x82\x6f\xab\xb8\xe8\x12\xde\x2d\x53\xd1\x25\x97\xde\x04\x0d\x32\xcb\x28\xc9\xf8\x13\x15\x9c\xb1\x8c\x1b\x51\xf7\xa8\x74\xcb\xf2\x29\xcc\x22\x2c\xae\xb9\x8e\x35\xec\x5e\x4b\xf5\xc5\xe2\x2c\xc8\x52\x86\x31\xf1\x51\x17\xe8\xc2\xbe\x6e\xac\x91\xf4\x07\x0e\xec\xdd\x07\xec\xc6\xdb\x6c\x46\xea\xa6\x5f\x47\x2f\x20\x06\x98\x8e\xfe\xf0\xb5\x1c\x53\x8c\x6e\x04\xd7\x51\x9c\x8e\x3d\xa4\xb1\x72\xb1\xe2\x76\x10\x89\xed\x3a\xd1\x19\x79\x92\xef\x37\xc1\x68\xdc\x88\x1c\x8b\x5f\x8b\xbf\xee\x91\x9f\x7c\x7a\xfd\x25\xb8\xfc", )?; test( HashAlgorithm::SHA384, b"\xac\xb3\x0b\xe9\x09\x2b\x2f\x18\xf2\x59\x34\xa0\xd6\x78\xb6\xbc\xd6\xb6\x7c\x2b\x88\xe7\x58\x84\xf4\x7b\x4f\xca\xe3\xad\xfa\x40\x5a\xfe\x2c\x7e\x61\xe2\xd6\xc5\x08\xb9\x27\x90\xac\x00\xf7\x6b\x77\xc9\x65\x08\x26\x68\xbf\x90\x0f\x70\xa3\x37\x62\xde\x64\x13\xaf\x93\xaf\x2e\xa8\x08\x6f\xda\x29\x3d\xed\x44\x75\xf2\x3c\x4c\xc3\x1a\xd4\x94\xf9\x8d\x7d\xd7\xb7\xfd\x6f\x7d\x97\x2b\xb7\x6c\xb3\x5a\xdc\x20\x68\x04\xc3\xfe\x5a\xcd\xd0\xe5\xb8\xb5\x4e\x07\xc2\x91\x11\xf7\x88\xbc\x59\x02\xf4\x0a\xfa\xc3\x0a\xfd\xba\xf2", n, e, b"\xb5\xc6\x0d\x8d\xa9\xb3\x94\x38\x78\xcb\x23\x59\xcf\x65\xe4\x81\x7c\x07\x94\xf9\x50\x45\x3c\xa7\x7c\x81\xa5\xa1\xc1\x58\x55\x91\xaa\x50\xa6\x74\x68\xe3\xb3\x99\xe4\xfa\xf1\xd6\x06\xbe\xa0\xd9\xe6\xcc\x1d\x2d\x70\xdb\x80\x63\x73\x9e\x0c\x27\xd3\xdc\x9f\x9a\xfe\x88\xde\xa5\x2e\x73\x29\x8a\x07\xd0\x5c\x7d\x97\x07\x00\x2e\xfa\x53\x7c\x38\x9e\x38\xbd\x37\xbc\xa7\x4e\xb0\xaf\x62\x61\xa5\xda\x06\x13\x62\x02\xc8\xad\x48\x7e\xeb\xd5\x0b\xef\x74\x76\x70\x89\xc7\x08\x70\xbe\x1d\x8f\xab\x91\x56\xf9\xfd\xbc\x2f\x2e\x9c\xc3\x30\xa9\x50\x18\xce\x79\x43\x98\x4b\xec\xc2\x56\x21\xbf\xa6\x60\x18\xef\x83\x20\xb6\x00\x59\xf9\x41\x15\x6e\x9c\xdd\x87\xff\x0d\x82\xcf\x7b\xe7\x74\x65\xe0\x20\x3e\x71\x20\xaa\xec\xed\x84\xab\xd8\x18\x69\x47\xd4\xac\x3d\xaf\x3f\x99\x39\x02\xae\xc4\x7c\x30\x90\x47\x5c\x85\x7b\x5d\x35\x9f\x0a\x55\x72\xd4\x68\x8e\x5a\x76\xa4\x65\x38\x68\xff\x54\xce\x9f\x99\x9e\x6b\xb5\x59\xd1\xc1\x1c\x67\xc1\x5b\xe9\xd7\xfe\x5f\x8c\x17\x04\x30\x1d\x05\x5f\x3d\x29\x07\x72\x27\x79\xd6\x01\x20\x36\x08\x4e\x95\x0d\xe3\x6f\x4f", )?; test( HashAlgorithm::SHA384, b"\x60\x1a\x6a\xad\x3f\xaa\x79\x88\xd5\xae\x52\x8a\x69\x69\x03\x1b\x10\xa6\xf3\x92\x16\x94\x6a\xa8\x9f\xd4\x53\x2c\x8e\xd1\x41\xf9\xa6\x50\xb1\x26\xef\x48\x8f\x7c\x5c\xf3\xfb\x2d\xaa\x25\x4c\xc2\x8b\xdd\x55\x56\x04\x19\xe8\x02\x14\xef\x99\x98\x96\xda\xc4\x94\x68\x52\xd2\x4f\xcd\x9f\xb7\x76\x10\xee\xbf\xbb\x6b\xa5\x8b\xca\x26\xf4\x56\x7f\x03\xac\x7e\x56\xda\x55\x3f\x23\x81\x7b\xc1\x03\xee\x48\x55\x92\xa0\x58\xfb\x5e\x3b\xc8\x29\x9c\x72\x90\xc7\x1a\x29\x13\x7e\x75\xdb\xf5\x32\x8c\x3a\x2d\xcd\x34\x16\x5b\x3f\x2e", n, e, b"\x30\x1d\x60\xd5\x65\x76\xf3\x66\x3a\x7f\xbe\x80\x36\xbb\xe4\xfb\xc0\xfb\xd8\x2c\xd6\xa4\x2e\x36\xd7\xbb\xc8\xb2\x06\x54\x3d\xc2\xd5\x6d\x31\x98\xe7\x91\x1a\xd1\x38\xca\xd2\x22\xdd\x99\x05\x0d\xd1\xf8\x5f\xe1\x9c\x8a\x88\xbf\x67\x13\x5e\x7f\x8f\x11\xb5\xf5\xe4\x85\xc9\x1f\xc7\xd4\x78\x06\x9b\x72\xf4\x6e\xbc\xdc\xf2\xd2\xae\x7d\xe6\xac\x8f\xe5\x3b\xb6\xc0\x49\x11\xd1\x22\xcc\x23\x1d\xc2\x10\xb2\x14\x7e\xbe\x8b\x05\x2e\x8b\x2c\xcc\x09\xf3\x38\xb3\x49\xde\x20\x25\xcc\x87\xb2\x61\x9a\x7b\x16\x33\x47\xca\x66\xa3\x47\x91\xa2\xe4\x6b\x4e\x2a\xc5\x7e\xb9\xf6\x02\x9c\xdb\xe0\x24\xe8\x96\xd5\x7f\x7d\x04\x91\xf7\x78\x33\x12\xf8\xf0\x6c\x79\x07\x70\x15\x0c\xd1\x39\xf6\x1f\xd2\xb3\xe7\x04\x1b\x37\x26\x1c\x6e\x7e\xa8\x6d\x4e\x06\xd9\x30\x0b\x1a\x56\x67\xcb\x02\x88\xc5\x50\xb2\xaf\xb3\x55\x94\x48\x34\xb4\x61\xce\xad\x13\x79\x42\x76\xbb\x46\xe5\xe2\x0a\xec\x7b\x63\xaa\xca\x4d\x49\x1a\x50\x0f\xac\xd5\x9a\x37\xc5\x27\x79\xcf\x46\x7d\x74\xaf\x1e\x62\xb1\xeb\xe0\xfd\x0b\xe1\xca\xcb\x7c\xe6\xd0\x50\xd8\x6e\x4e\xb7\x6c\xde\x06\x93", )?; test( HashAlgorithm::SHA384, b"\x44\xd3\xe0\xfc\x90\x10\x0a\x1c\x93\x16\x06\x3f\x26\xb1\x80\x32\x6c\xc2\xe3\x83\x4c\xe5\x6e\x43\x24\x52\x8a\x0b\xbb\x01\x5b\x3d\x78\x12\x95\x8c\xd2\x6b\x91\xbf\x08\xa3\xa0\xb1\x12\x1f\x9f\x9d\xd7\x7a\xcb\x98\xa0\x2a\xd7\x5f\xcd\x61\x3c\x53\xc7\x32\xd1\xc2\x35\xf5\x9b\x68\x73\xec\xe6\x36\x3f\x27\x94\x52\xb6\xa4\xb6\x5e\x80\xbb\x59\xfd\x47\xb9\xa2\x93\x6d\xcc\x1e\x4d\xfe\x1f\x53\x62\xe3\x45\x9b\x98\x59\xdb\x32\x09\xa2\x69\x8d\x27\xfa\x8a\xed\xfe\xcd\x4d\x35\xb9\x27\xda\xf8\x68\x6c\x59\xd7\x00\x49\x0f\x0a\xa3", n, e, b"\xaf\x22\x29\xe9\x4a\x85\x7b\x89\xe0\xe8\x90\xda\xca\x3a\x8f\xe1\x2e\xbd\xba\x04\x94\x8d\x18\x83\xa7\xd7\x81\x6a\x3b\x68\x2f\x7d\xa3\x03\x25\x40\xa8\x76\x9f\x9c\xca\xc9\x58\x6c\xf2\x4e\x8c\x20\x4b\x45\xb8\x5d\x1b\xdc\xc5\xa5\x45\x0a\x21\x5b\x40\x48\xea\x42\x98\x3b\x34\x56\xfa\x8c\x76\xc6\x78\x6e\x02\x4f\x70\x5e\x08\x8d\x69\x45\x59\xd6\x68\xca\xa8\x68\x4c\xad\x0f\xc5\x78\x50\xfc\xaf\x34\xe4\x58\xae\xe8\xfa\xd4\xe0\x9e\x6f\x19\x65\x57\xd4\xe8\x86\x02\x84\xd9\x82\xc0\x10\x5d\x98\xce\x49\x12\xe9\x6c\x35\x50\xe2\xa0\xc7\xe8\xba\xd5\xab\xc2\x9a\x9a\x54\x2f\x57\xa8\xc6\x05\x79\x03\x80\x67\xb3\xd5\x39\x1a\xbc\x21\xb4\xf9\xde\xb0\x24\xca\x58\xf9\xb0\xc3\x8c\x0d\x1f\x82\x37\x3f\x52\x8e\x93\x9b\xd7\x3a\x24\xd5\x01\xc5\x91\x16\x88\x14\xc8\x72\xc5\x25\xdb\x0e\x56\xca\xe4\x7d\xf0\x0f\xa3\x72\x8d\xc3\xa0\x97\x69\x65\x32\x3c\xe8\xd2\xde\xe2\xb1\x38\xb5\x0a\xb7\xaf\xd4\x84\x95\x11\x46\x73\xe9\x1b\xb3\xed\x22\x05\xe2\x6a\x84\x55\x47\x4c\x3d\x4e\xc8\x73\x9b\xbf\xf6\xdf\x39\xb2\xb7\x2e\xe0\x50\x41\x09\x30\x42\x3b\x14\x72\xb6\xed", )?; test( HashAlgorithm::SHA384, b"\x5a\xf0\x90\x77\xa1\xf5\x34\xb8\x98\x22\xb2\x6c\x32\x72\xad\xf8\x50\x0d\x3c\x6b\xd9\x0f\x9b\x5e\x0d\x8b\x21\x1f\x16\xd0\x72\x0e\xe0\xea\xf6\x46\x2b\x6c\x8a\x80\xdf\x6d\x75\x35\x9f\xd1\x9d\x03\xa0\xca\xfb\x52\xbc\x9d\x4c\x37\xc2\xaa\x09\x99\x11\xa7\x9a\x92\x65\x2c\xc7\x17\xf0\x74\x6f\xdc\xad\x62\x7c\x72\xf1\xc2\x16\xb2\x43\xd2\x17\x5f\x6d\x00\xbf\x07\xd3\xf6\xaa\x2a\x04\xd4\xfe\x9f\x8f\xbc\xe9\x32\x18\x94\x4b\x92\xaa\x07\xaf\x6b\x4f\xcd\x80\xcf\xde\x2d\x7a\xda\x15\xc0\x5e\x96\xe7\x77\xea\x1c\x17\xdf\x08\xfc", n, e, b"\xa5\x68\x23\xfa\x57\x7e\x89\x46\xf1\xd2\xf6\xe3\x51\xb7\x38\xb5\x35\x92\x54\x43\x58\x52\x8a\xf8\x88\x07\xea\x4f\x19\x01\x7d\xfe\x81\xa3\xd6\x9f\x62\xfb\xff\x64\x95\x50\xd9\xb3\x10\xfa\xf2\x7a\x04\x1f\xe6\x24\xf0\xa0\x2b\xdc\xdd\xb7\x9b\xfb\x0a\x46\x57\x39\xec\x8b\x64\xb7\x48\xcc\x29\xe5\xa0\x2c\x77\x7e\x18\x26\xd3\xe2\xf1\xee\xe6\xfe\x2e\xde\xe4\xa8\xbc\xac\x51\x9c\x7c\x7c\xa5\xc0\x39\xe7\x6d\x63\x06\x68\x94\x5a\x1e\x5e\x86\x18\xe2\x35\x86\x45\x61\xa4\x40\xe7\x3e\x39\xf6\xd6\x84\x2a\xd7\xda\x64\xef\x5b\x0c\xe1\xc4\xab\x88\xdb\x15\x7b\x68\x10\x71\x74\xad\x7d\x5c\x9a\x60\x65\x06\x87\x68\xc1\x1c\x4c\x96\xff\x67\x05\x0b\x5d\x07\xb8\xcd\x02\x7f\xcd\x0d\x34\x7e\xc7\x9a\x19\x7c\xf4\x34\x35\x98\x5b\xc1\xae\xb4\x79\xdb\x00\x22\x28\x9e\x8d\xd3\xb3\x1b\xb7\xc6\x2d\x88\x31\xcf\xe6\x95\x2f\x41\xd2\x4f\x89\xd7\x53\x78\x95\x35\xf9\x18\xff\x68\xb3\x69\x50\xaf\x6f\xd3\x1d\xee\x1a\xc4\x76\xa0\xcf\x93\xaf\xe9\xf4\xa7\x66\xf3\xc4\xd2\xc0\xc3\xf9\x28\x25\xd5\x57\x2e\xb2\xeb\x8a\x2b\x64\x4e\x32\x9e\xea\x16\x83\xf9\x08\x10\xed\x77", )?; test( HashAlgorithm::SHA384, b"\xf6\x0a\x3a\x54\x37\x68\xfa\xbe\x37\xf0\x03\x00\x9a\x8c\x26\xf7\xdc\x91\xf1\x42\x2d\x44\x29\xed\x7f\x9d\x74\x4c\xdd\x4b\x55\x2a\xfe\xf7\x5d\x24\x1a\xcd\xa0\x4f\xfc\x39\x67\x21\x59\xee\x24\x8e\x60\x2d\xab\x71\x92\x44\x9e\x2e\xd4\x55\x29\x95\xc2\x58\xf0\x0a\x47\x63\x46\xe3\x6a\x29\xa0\x12\x6b\xc2\x49\x04\x0f\xaa\x57\xc9\x38\x0b\xdd\x74\xb8\x3f\x62\xc5\x67\x90\x92\x05\x74\x43\x34\x32\xf8\xd6\x5c\x5c\xd1\x85\xe2\x4f\xad\x13\x12\x72\x65\xc6\xa5\xef\x8d\xb4\xf1\x14\x49\x3d\x5c\xfa\x61\xd9\x16\x64\x98\x14\x08\xe9", n, e, b"\x08\xd3\x96\x48\x1d\xee\xf1\x8c\xb0\xbe\xf7\xc3\xe8\x26\xfe\x6e\x5c\x9e\xcc\x85\xe5\x23\x0d\x35\xd6\x67\x72\xb8\xd2\xd0\x15\xd4\xe5\xf5\x79\x4f\xbe\x05\x50\xdf\x2f\x74\x57\x30\xd6\xf8\xd1\xd3\xb8\x50\xd1\x64\xfc\xe4\x63\x08\x05\xe7\x11\xb5\x93\x08\xf8\x60\x85\x06\xb7\xe0\x1e\x8e\x92\x94\xed\x8b\x7e\x75\x82\x16\x56\x77\xf1\x80\xe9\x65\x16\x9d\xca\x81\xb3\xda\xf2\x4d\x7b\x92\xfe\x32\xd6\xa9\xac\x63\x82\x1d\x48\xb1\xa0\xa1\x44\xfc\x7a\x04\xb0\xbf\xc6\x3a\x3b\xc1\x6a\x0f\xd8\x37\xb0\x20\x37\xed\x76\xe5\x0d\x46\xcb\xfa\x38\x57\xe6\x58\xe3\x70\xc5\x86\xab\x1e\xed\x82\x50\x76\x32\x1a\xc8\xe8\x2b\xe3\x74\xba\xcb\x29\x5e\x4d\x34\x08\xf0\xcc\x1f\xc4\xc3\x00\xb8\x42\x75\xa5\x1c\x35\x73\xe9\xca\xbf\xdb\xe3\xdc\x51\xe4\xa6\xf5\x81\x1d\x86\x0d\x72\x5a\xaf\x8f\xd0\xaf\x19\xa2\x43\x7b\x0f\x1c\x80\xf5\xac\x22\x2f\x6b\x25\xf1\xfa\x09\xe9\x33\x99\xa6\x97\x6b\x1b\x3c\xa7\x6a\xfe\x60\x86\xe9\xb2\x32\xaa\xe6\xc7\xb8\x18\x25\x5b\xf9\x63\xf3\x1c\x04\xae\x3f\xa2\x13\x6c\x0a\x44\x29\x97\xd4\xcf\x12\xf3\x95\xfb\x80\x4a\x47\x55\xb5\x6b", )?; test( HashAlgorithm::SHA384, b"\x2c\x07\xa8\x1d\xe5\x89\x55\xb6\x76\xfe\xc0\x57\x2d\x48\xd1\x95\x5b\x48\x75\xff\x62\xa4\x4b\x00\x10\xc7\xa1\x07\x2b\x29\x9e\xe4\x4d\xd0\xc0\x76\xf2\x17\x8a\x83\xd0\xae\x76\xe7\x67\xe2\x31\xf1\xd8\x1e\x07\x0a\xfa\xb2\x9c\x97\xab\xd4\xde\x21\x64\xe4\x37\xb3\x11\xf5\x07\x84\x1f\x88\x51\xd6\xd6\x9a\xb5\x1e\xe9\xe2\x9e\x65\x4b\x54\xbc\xee\x45\xe9\xb5\x19\xc6\xa2\x17\x87\xfa\xcb\x92\x7f\x1d\x7d\x64\x91\x92\x66\x14\x79\x2f\xcc\x63\x46\xdc\xd0\x80\xbb\x5c\xf0\x7b\xf5\x6a\xd0\xfc\x4e\x08\x3a\x35\x82\x14\x63\x15\x10", n, e, b"\x9a\xa3\x91\xe7\xc2\xf0\xe9\x20\xaa\xc2\x7e\xd9\xfc\x20\x81\xd3\xc9\xca\xa3\x73\x58\x83\xd0\x1a\xd7\xa7\xe3\xb1\x18\x67\xd0\xad\x62\x41\x56\x47\x7b\xbb\xdd\xe6\x59\xf4\x74\x68\x2d\x0d\x77\x44\x89\xe2\xb5\xb0\x39\xd1\xeb\x35\x45\x4c\x9e\x3e\xed\x78\xcf\xf9\xc4\x26\x2e\x3a\xec\xfc\xa1\xd8\x17\x54\x2b\x48\x60\x96\x59\x8e\x11\x14\xbf\xc0\x3f\x20\xa4\x5d\xe3\x6f\x6d\xf7\x0d\x14\x4d\x01\xdc\x48\x66\xa0\xf8\x33\x19\xe7\xc2\xb8\x53\x0f\x8c\x27\xa4\x1b\x7a\xdd\x9f\x69\x2d\x8a\x8e\x64\x64\x55\xb6\x7c\x9e\xc4\x7a\x4d\x2c\xe3\xdf\xe3\x5d\x6a\x2e\x89\xd9\xbe\x50\xc5\xb6\xda\x39\xbb\x02\x54\xbd\x23\xa8\x09\xab\x97\xb2\xb4\x8a\x06\x8a\x87\xab\xde\x6b\x6a\x6e\x35\x95\x5f\xc9\x2a\x96\x26\xf9\x60\x7d\x5b\x3f\x40\x15\x17\x27\x15\x94\xbe\xf7\x38\x59\x81\x2b\x6a\x62\x1e\xd6\xbd\xaf\x3c\x5f\x2a\x90\xb1\xe1\x68\x0f\x68\xdc\xfc\xca\xcb\x65\xe0\x08\x1f\x1c\xcb\x6a\x20\x73\x70\x9d\x1b\xa0\x67\x06\x50\x16\xed\x73\xeb\xd7\xeb\xe9\xe7\xa7\xb6\x0c\x8c\x9d\xd0\x4a\x56\xfa\xb3\x07\x02\xc8\xa6\xdf\x6a\x35\x3a\x30\x10\x47\xdf\x4c\x7a\xff\x62", )?; test( HashAlgorithm::SHA384, b"\x35\xec\x92\xaf\xdb\xc2\xfc\xef\xe4\x8f\x1e\x2f\x6e\x48\x29\xae\x53\xb3\xda\x04\x59\xcc\x4e\xa8\xa9\x68\x18\xb5\x83\x18\x91\xee\x2f\x50\x6f\xff\x37\xc8\x99\x06\xd3\x23\x3a\x51\xa5\xcf\x14\x69\xa6\x2c\x18\x50\x61\xf0\x33\x08\x5f\xca\x6a\x54\xe2\x45\x29\xc3\xd6\xf0\xd8\xe9\x04\xbc\xb0\xf0\x89\xa5\xcd\x50\x86\x94\x84\xda\x1a\x84\xf6\xfb\x8d\xe4\xe5\x3f\xce\x3d\xc7\x14\x20\x15\x19\xd1\x10\x13\xf6\xf6\xaa\x64\xe8\xb5\xec\x5c\xfe\xb2\x7b\x61\x1f\x08\x95\x05\x9d\x8c\x47\x72\x0d\x55\xe0\x0b\x57\x7c\xa5\x50\x09\x20", n, e, b"\x6b\x0f\x5b\x50\xe6\x78\xda\x08\x3e\xd0\xf1\xb6\x4e\x94\x3e\x8c\x62\x79\xc7\x24\x6a\xf5\xad\x07\x9c\xdb\xf2\x23\xe4\x2a\x0d\x47\x1e\x56\x31\x4b\xc0\xd5\x8f\x20\x2a\xa6\xc5\xe1\xe5\x25\x59\x85\xb0\x79\x5d\x48\xeb\x3d\x4b\x8e\x3f\xc9\x22\x40\xae\x02\xb4\x08\x8c\x6c\xe8\xab\x0e\x8c\x79\xc6\x8d\xfd\xc4\x86\x57\xd6\xa2\x82\x95\x39\x1b\x9a\x5a\x5f\x35\x25\x51\x26\xbf\x8c\xa5\x3c\xbc\xc0\x08\x2e\xab\x52\xec\x10\x9d\x22\xa1\x18\x5f\x6d\xc7\x92\xfc\x29\x0a\xa8\xdb\xae\xbb\x2f\xbe\x40\x4f\x1d\x03\x9a\xa6\x34\x3c\xd7\xaf\x9f\xcb\x2d\x1e\x05\xde\xf4\x80\x96\xc2\x37\xe1\x0d\xaa\x7c\xfa\xc5\xae\x9b\x3b\x30\x22\x00\x5d\x0d\x2d\x5c\x9c\x5c\x50\x2b\x2f\x23\x59\x4e\x80\xd1\x60\x4b\xbb\x8f\x5d\xec\x07\xcd\x3a\xfe\x1f\x77\x77\x43\xb0\xb5\x8a\x4e\x0e\x4e\x5c\xaa\x14\x88\x30\xee\xe0\x47\x96\x8e\x7f\x40\x66\x1f\x9f\x1a\x02\xe1\xa7\xfd\x2b\x6c\xaf\x19\x32\x6a\x75\xe9\x56\x5e\xfd\xc0\x11\x4b\xce\xcb\x14\xdd\xa0\x6c\x32\x9c\xf3\x22\xa5\xbd\x3e\x6a\xb4\x8d\x95\xf2\xd2\xa9\xc1\xc1\x23\x3a\x0a\xa0\x15\xa7\x38\xf9\x01\xf1\x31\x48\xb4\x54", )?; test( HashAlgorithm::SHA384, b"\x80\xc9\xde\xbd\xf9\x31\x74\xd7\x57\x50\xa6\xcf\x09\xaf\x71\xfc\x18\xfd\x51\x3b\xff\x9c\xb4\x91\xbe\x60\xaf\x11\x2a\x93\xf0\x00\x87\x3c\xf4\x38\x58\xa0\x7a\xca\x76\x0a\x37\xe7\x60\xc8\xcb\x01\xd2\x76\xf4\x2d\x99\x7f\x01\xcc\xa5\xe0\x8a\x6a\x60\x2f\x5f\xe6\x3e\xdc\xbe\xd3\x95\xb8\xc9\x1f\xb0\xb3\x36\xf2\x1f\xea\x49\xd9\x50\xe1\xff\x24\x64\x0c\x8d\x8d\x3b\x95\x08\x1a\xd1\x59\x66\x44\xce\x34\xa5\x58\x58\x7e\x4a\x1e\x2c\xd5\x0d\xb9\xed\x1d\xd3\xce\xbb\xc6\xdc\xe8\x08\x4d\x3e\x1b\xa7\x06\x92\xe8\x26\x18\xed\x61", n, e, b"\x4a\x15\xa7\x83\xad\xbf\x27\x46\x22\xd5\xa6\x10\xbb\x6f\xc7\x33\x37\x99\x9e\x44\x5d\xc2\x13\x3a\xcc\xb7\x88\xd6\x20\x3d\x70\xf3\xcd\xc6\x3e\x67\xda\xa4\x17\x1a\x79\x52\xa4\x98\x64\x56\xfa\xb3\xc0\x77\xa8\x94\x1f\xb2\x59\xe3\x7a\x5c\x0c\xbb\x20\xc4\x08\xfa\x24\xad\x0e\xc8\x50\xe9\xbf\x02\x8c\x36\x04\x60\x99\x41\xf5\xae\x2f\x18\xbf\x1a\xc3\x7a\x24\xf7\x55\xab\xb9\xc8\x5d\xdc\xd0\xbf\x4a\x12\xfa\xbd\x9d\x25\x30\x29\xe0\x81\xf6\x28\xe2\xbb\xe9\xf9\xaf\xe9\x22\x49\x54\xd8\x31\x5d\xb8\x6c\x21\x25\x51\x2b\xb9\x8c\xe9\xb3\x69\x30\x99\x4b\x09\x1a\x8a\x1d\x7d\x4e\x2f\x4a\x0e\x58\xd0\xa3\x58\x76\xad\xad\x14\x30\x05\x30\xb3\x9c\x8d\xc1\x1d\xed\x3e\xf2\xfa\x95\xd5\xf2\x2e\x67\xca\xe3\x4c\xc2\x1a\xd5\xe2\x3f\x91\x22\xb5\x3d\xfb\x79\xf1\xa2\xac\x63\xc1\x84\x4e\x9e\xf0\x69\xa2\xe4\x1f\x17\x8d\x6d\xce\xdc\x51\x8a\xaf\xcf\x81\xe0\xeb\xd8\x82\x55\x6e\x73\x1c\xb0\xab\x41\xd9\x57\x27\x4a\x3f\xbb\xb7\xce\xf2\x60\x87\x91\x00\x0c\x6b\x86\x08\x68\xcb\x73\x93\xe7\xd0\x3d\x94\x56\x89\xff\xb7\x75\x55\xef\xe0\x8f\x46\x14\x51\xd3\x3c\x11", )?; test( HashAlgorithm::SHA384, b"\x31\x39\x5c\xef\x34\x95\x51\x34\x3a\x49\x27\x1a\x8d\x81\x2b\x4c\x7b\x65\xb4\x55\xb7\xed\xa8\x11\xfc\xf7\x41\x61\xf3\x97\x11\x23\x57\xae\x44\x62\x57\xbe\x26\xc9\x3c\xfc\xe5\x5e\x4b\xa7\x97\x6d\xed\x99\x7e\xc1\x0d\x1c\x8b\x1a\xc2\xfe\x22\xdc\x2e\xe8\x1d\x05\xa6\xeb\x13\x61\x12\x5c\xda\x01\x97\xe2\x4a\xe9\x74\xcd\x44\x09\x2a\xa9\xf3\x6f\xe0\x13\x52\xba\x05\xcc\xef\xd2\x37\x0c\xee\xd6\x64\x19\x50\x56\x2f\x17\x76\xc3\x95\x22\xe0\x23\xd0\x9a\x3b\x09\x7b\xbe\x9b\xc5\xf8\x7d\x05\xd8\x0f\x88\x30\xab\xd7\xac\x8c\x80", n, e, b"\x16\x2f\x38\x76\x95\xcf\x9d\x82\xdd\xa8\x9c\x74\x93\x18\xe4\x6c\x9b\xe8\x95\xec\x36\x4e\xa4\xae\xce\x97\xcc\xfa\x63\x92\x5a\xf3\x71\x08\x94\xda\x2b\x7b\x59\x67\xe4\x6f\x4e\xfa\x80\xca\x25\xd2\xa9\x65\xa7\xe1\x5f\x75\xe0\xaa\x1b\xd4\x25\x0f\x8f\x41\x09\x9e\x6e\x97\x14\xc3\xfc\x43\x11\x07\x7a\xe9\xbd\xdf\xe3\x5b\xa4\x72\x75\x31\x52\x9c\x23\x9d\x54\x6a\xb1\xc2\x98\x18\x7f\x16\x5f\x70\x8c\xcc\x0a\xe3\x97\x9a\x8d\xa1\x93\xe3\x48\x59\xa5\x9c\x2c\x3b\xc4\x22\x53\xc8\x34\x66\x88\xe6\xbb\xa6\xfb\x1b\x01\xb1\x0c\x1e\xc2\xc6\x49\x3d\xed\xcc\x26\x96\x26\x9d\x85\x1b\xde\x63\xe2\x7e\x37\xbe\xd3\x57\x45\x5c\x8f\xee\x56\x29\xf9\x4a\xfa\x7a\x98\x66\x95\xcf\xd5\xb9\x92\x12\x65\x7a\x6c\x88\x46\x44\x59\x60\x86\xb8\x9e\x0c\x7c\x05\xe8\x19\xfa\xeb\xeb\xef\x74\x5f\xd2\x95\xaf\x88\x66\xe0\x75\x0f\x54\x79\xba\xed\x50\xcb\xb3\xd0\x59\xf8\xa5\xeb\x7e\x0e\x61\xe2\x73\x3a\xe5\x0f\x0c\x1e\xc4\x2b\xe7\x1f\x5d\xff\x32\x41\x95\xcb\x4f\x0e\x94\x1a\x21\x56\x15\x13\xc3\x03\x7d\xb9\x2f\xec\x95\x56\xb7\x72\xcc\xab\x23\x9e\x34\xb1\x87\x6c\x56\xb1", )?; test( HashAlgorithm::SHA512, b"\xa7\xc3\x09\xd4\x4a\x57\x18\x8b\xbd\x7b\x72\x6b\x98\xb9\x8c\xe1\x25\x82\x22\x8e\x14\x15\x86\x48\x70\xa2\x39\x61\xd2\xaf\xb8\x2c\xd5\xbc\x98\xbe\xc9\x22\xd5\xf2\xac\x41\x68\xb0\x56\xda\x17\x6e\xf3\xba\x91\xf6\xb6\x99\xba\x6a\xcc\x41\x44\x86\x8f\xf3\x7f\x26\xfd\x06\x72\x08\x68\xd1\x2a\xd2\x6e\xcb\x52\x57\x2c\xf1\x04\x16\xaf\x68\xdf\x03\xab\x64\x5a\x8b\x70\x48\x57\xd2\x19\x0f\xfc\x3f\x07\xea\xbe\x3a\x8e\x2a\xbe\x34\xed\x61\x59\xe8\x84\xc4\xfa\xe1\x41\xd4\x33\x3d\x5c\x3e\x0d\xb0\x44\xff\x9c\xcc\xd9\xcb\xd6\x7f", n, e, b"\x14\x8a\xf6\x1e\xd5\xea\x8a\x87\xa0\x8b\x3f\x40\x39\x29\xbf\x80\x31\xdb\x4f\xd3\x99\x9b\x64\x40\x9b\xa4\x89\xf9\x7a\x3e\xe5\x20\x8e\xa4\x20\x2d\x2e\xc1\x87\x34\xf6\x15\x00\x3a\x51\xf7\x74\x41\x08\x5b\xe6\xac\x0f\x11\x81\x0f\xfa\x2d\xad\x58\xf0\xe1\x86\xd5\x52\x0a\xc2\xb8\xa5\xd3\x96\x6e\x8d\x2a\xbb\x80\x74\xe1\x3b\x50\xa4\xe7\xde\x83\xbe\x10\xa6\x6f\xdc\x7c\xa1\x81\x18\xc5\x77\x4f\x78\x12\x12\xde\x9e\xfe\xbc\x63\x76\xfc\xdd\xdc\x65\xa3\xb1\xb8\xf1\xab\x31\x49\x2f\xe4\x78\x25\x9c\xe7\x19\xb3\xdb\x58\x74\x98\xd8\x79\xa0\x1d\xec\x96\xe8\xea\xbe\xb0\x7f\xf7\x07\x3f\x3f\x3e\xb4\x46\x08\x49\x55\xca\x26\x32\x9a\x79\x13\x15\xa2\xc2\x59\xd2\x25\xe2\x6b\x21\x54\xb2\x04\x7b\x21\xfa\xba\x68\x11\x5b\xfd\x96\x2e\x5e\x24\xec\x52\xd7\xc5\xd2\x31\xe3\x04\x4c\xbc\xd8\xc8\x80\x48\x55\x70\x3c\xba\xa6\x22\xb1\x5b\x6e\xf7\x8c\x74\x21\xa3\x67\x16\x6f\x1b\x02\x57\x6c\x87\x36\x05\x93\xda\x75\xb7\x18\x9e\xfa\xfd\x10\x82\xbd\x59\xf6\x85\x7f\x17\x01\xf6\x46\xc2\x4d\x70\xc9\x52\x73\xc4\x9d\x5b\x11\xe6\xaf\xe2\x58\x82\x1b\x55\xc1\x68\x0c", )?; test( HashAlgorithm::SHA512, b"\xca\x50\x5d\x45\x91\x12\x16\x64\x99\x07\x47\xd9\x5d\x95\x55\xcc\x75\xbf\xc3\xfd\xae\xec\xee\xaa\x60\xea\xfa\xb3\xfc\x32\x0c\xfc\xe5\x6e\xb9\x13\x81\x38\xbf\x13\x8f\x25\xf3\xc8\xbb\x02\x7b\x13\x6f\x5d\x3d\x90\xed\x48\x97\x77\x9b\x59\x51\xc0\x9d\xf5\xd0\x8b\xa9\xce\x8c\xbe\x17\xab\xc4\xf0\x38\x68\x70\x86\xe9\x3d\x77\x1b\x68\x43\x22\x26\x66\x33\xd0\xd6\x5d\x71\xec\x41\x23\x4a\x1d\xbe\xc0\x7a\xbc\x8f\x7d\xf2\x8b\xc4\x3d\xd8\xa4\x5b\x10\xce\xaf\xac\x06\x77\x58\x05\x41\x37\x01\x91\x4e\x3b\xb3\x7e\xb6\xba\x5b\x5e", n, e, b"\x58\x9c\xcd\x4e\xbf\x97\x64\xf8\x7e\x6a\xfa\x7f\x13\xc4\x06\x25\x79\xb0\x22\x28\x11\x7b\x15\xa8\x73\x8a\xb3\x9c\xd6\x44\x77\x06\x9c\xb4\xf5\x2c\xd8\xd5\xf4\x57\x4c\x65\x7b\x45\x38\x35\xca\x3c\xed\xb8\x24\xf0\x3b\x92\xa5\x73\xd6\xd3\xd9\x13\x61\x31\x3f\x11\xbd\xcb\x34\xd2\x05\x9f\xe2\xe6\xce\x2b\x85\x44\x61\xaf\x58\xa9\x29\x4c\x88\xcb\xfb\x2a\x63\x99\x76\xb5\x6e\x47\x48\x02\x6f\x30\x40\xe2\xfd\x71\x12\xd6\xad\x44\x50\x06\x89\xac\x77\x7c\x07\x1d\x17\x39\x19\x69\x76\x2e\x18\x64\x17\xc4\x40\x0a\xbd\xda\x5c\x16\xdc\xe0\x07\x76\x42\xf1\xfc\x13\x54\xe0\xe8\xc1\x4e\x55\x8c\x92\x3c\x1b\xfb\x85\x48\x8b\x83\x50\xf4\x15\x86\x6a\x60\x87\x1e\xd7\x15\x1f\x5f\xbc\x5b\x88\x05\x00\x01\x19\x77\xc7\x78\xe1\x7f\xe8\x91\x8c\x5d\x34\x3f\x70\xb0\x0d\x58\xf7\x18\x95\x61\x25\xfe\x28\xb3\xa5\xe2\xd0\x76\x04\xa2\xb8\xa8\x77\x20\x44\x34\xce\x90\x3b\x35\xa0\x30\x93\x6b\xc7\x19\x51\xca\x59\x3d\xf9\x7d\x24\xe8\xe8\xad\x8f\x2d\xc9\xb7\x8f\x76\xef\x13\xa1\xd3\x86\xca\x85\x7c\xed\x48\xf1\x9f\x3e\xbe\x39\x10\x8f\x9b\x33\xff\x59\xeb\x05\x56\xb1", )?; test( HashAlgorithm::SHA512, b"\x23\x7a\x7e\x44\xb0\xa6\xc2\x68\xbb\x63\x36\x4b\x95\x8a\xe0\x2b\x95\xe7\xee\xd3\x6b\x3e\xa5\xbf\xb1\x8b\x9b\x81\xc3\x8e\x26\x63\xd1\x87\x14\x4e\x32\x3f\x9c\xea\xfb\x47\x95\x07\xd1\x84\xe6\x3c\xfb\xec\x3e\xcd\xbb\x8a\x05\xd2\xdf\xc8\x92\x96\x93\xed\x9e\x3e\x79\xe5\xf8\xab\xfc\x41\x7b\xa1\xe1\x7e\x3e\x28\x1e\x8a\x0a\x32\xf0\x84\x11\x7f\x28\xc3\xdc\xbe\xc5\x1b\x86\xf5\xc8\x5b\x28\x22\x44\x1a\x94\x23\xb5\xb4\x46\xd3\x92\x8f\x97\x76\x26\xa3\x34\x57\x9b\x39\xcf\xaf\x58\xf2\x14\xc9\x8d\x0c\xdf\x64\x0b\xe1\xac\x59", n, e, b"\xaf\x07\x6b\xc2\x13\xca\xf7\x56\x19\xf4\xbd\x1d\x78\x7c\xc1\x98\xf7\xdf\x33\x24\xa0\xdd\x87\xa8\x84\x16\xe0\xa4\xb8\x1c\x2f\xb9\xa9\xdb\x5f\x98\xae\xd4\x3b\xc1\x5f\xe2\x35\x71\x43\xa6\xe4\xff\x70\x1d\x9c\x48\xf5\x1d\xe9\xeb\x80\x36\x70\xbb\xc4\xb0\xae\xa7\x22\x0b\xe2\xf8\x4b\x83\x00\x31\x8c\x77\xa9\xf6\x15\x98\x6c\x49\x80\xab\xda\x85\xe3\xad\x00\x89\x56\x4d\xba\xf7\xf4\x4d\x81\xb6\x66\x4e\xec\x03\x11\xad\xb1\x94\xd4\x6d\xe9\x6b\xb1\x7d\x5a\x5d\x47\x42\x68\x45\x80\x2c\xa0\xf4\x9a\x16\x9e\xb8\x2b\x75\xaf\xa1\x91\x02\x7a\x0c\xc8\xfc\xe9\xdd\x16\x05\x53\x50\xdf\x97\x45\xfc\x72\x00\xff\x9f\x4e\xa3\xcf\xbf\xc6\x6c\x42\x84\x81\x13\xe3\xbe\x32\x93\xd5\x10\x38\x2d\x09\x99\xf0\x32\x51\x55\x27\xbd\x99\xf6\x6e\xfa\x2a\x75\x5e\x01\x12\x47\xb2\x23\xa6\x8e\x51\x25\x8b\x6b\xc3\x19\xa7\xcd\xef\x4a\xec\x53\x3e\x9d\xcd\x8a\xe2\x6e\x34\x9e\x5b\x33\xc7\x91\x21\x90\x7d\xe5\x09\xa1\xcb\x83\xc2\xe5\x9a\x47\xc1\xa8\x84\xbf\x68\xe7\x22\x93\x16\xa6\x2e\x3c\x49\xd1\xf5\x42\xeb\xe7\x10\x5c\xfc\x27\x09\x92\x68\x12\x0a\x77\x43\x90\x84\x71", )?; test( HashAlgorithm::SHA512, b"\xab\x18\x93\x92\x30\xb0\x96\x64\x6a\x37\xa7\x81\x62\x9f\xbd\x92\x70\xf3\x89\x1a\x5c\xea\xb4\xa8\xc3\xbc\x68\x51\xbc\x34\x11\x5d\xbc\x06\x65\x41\xb7\x64\xa2\xce\x88\xcc\x16\xa7\x93\x24\xe5\xf8\xa9\x08\x07\x65\x2c\x63\x90\x41\x73\x3c\x34\x01\x6f\xd3\x0a\xf0\x8f\xed\x90\x24\xe2\x6c\xf0\xb0\x7c\x22\x81\x1b\x1a\xe7\x91\x11\x09\xe9\x62\x59\x43\x44\x72\x07\xdc\xd3\xff\xf3\x9c\x45\xcb\x69\xee\x73\x1d\x22\xf8\xf0\x08\x73\x0c\xe2\xef\xc5\x3f\x11\x49\x45\x57\x3e\xa2\xdd\xeb\xb6\xe2\x62\xc5\x27\xd2\x0f\x8b\xb1\xdc\x32", n, e, b"\x95\xbd\x0b\xf2\x36\x2f\x34\xb2\xe0\x40\x75\xb2\x93\x4f\x40\x47\x98\x70\x3e\xa4\x72\xb8\x1a\xc3\xcc\x22\x3a\xec\x48\x6e\x4c\x3d\x9c\x5d\x1c\x2f\x9e\xe2\x24\x17\x13\x29\x64\xed\x58\xe4\x99\x37\xf5\xb2\x57\xd3\x16\xca\x7f\xff\xe2\x90\xb1\x9f\x5b\x58\x10\x38\x36\x81\x2b\xef\x30\xca\x03\x27\x03\x9d\x8b\x9e\xa9\x12\x95\x39\x2f\xc3\x94\xb8\x81\xe2\xd2\xac\x9e\x30\xc5\xa4\x42\x56\x70\x0f\xc9\xde\x0d\xba\x29\x82\x73\xae\xc3\x0c\x4f\x77\x8d\x2e\x71\x27\xe8\xb8\xa8\x8b\x02\x74\xfc\xe0\x40\x81\xcc\x13\xad\xbe\xfe\x55\x50\x14\xe1\xb5\xd5\xdc\xf6\x22\x4c\x5a\xe2\x77\x54\x23\xa6\x6c\x81\x81\x8e\xec\x01\x4a\x3f\xaf\x9e\xe7\x5a\x3f\x6c\x3e\x51\xc5\x56\xb0\xa2\x88\xe8\xc2\x62\x94\x66\x84\xeb\x62\x8b\x88\xe3\xf8\x75\xe6\x2e\xf6\xe8\x01\xca\xe7\x5f\x61\xce\xe4\x04\x97\x1c\x39\xd2\x4a\x97\x12\xeb\x34\x2d\xdc\x66\x35\x15\xde\xc1\x03\xb1\x8d\x97\xd7\x8e\xd6\x82\x12\xf2\x79\x00\xe7\x7c\x04\x9b\x60\xc8\x53\x00\x2b\x08\x02\x2d\xf5\x6f\x70\x7e\xfa\x71\x02\x75\x89\xe1\xa3\xca\x6e\x41\x5b\xa5\xf4\x43\x7e\x97\x8b\x07\xaf\x3b\x73\xba\x0d", )?; test( HashAlgorithm::SHA512, b"\xa2\x80\xe8\x9c\xeb\x2c\x8c\xf2\x62\x97\x19\x1b\xaf\x9a\x95\x5d\x0d\x52\x37\x5d\xa0\x23\x63\x3e\x0a\xfc\xdb\x0d\x39\xdc\x33\x5d\x82\x95\x85\x2e\xf4\xd0\x67\x14\xe6\x51\x1a\x95\xd3\x7c\x04\xd2\x68\x18\x60\x6a\xda\x54\x35\x9b\x7d\x07\x84\xaa\x93\x3c\xc6\x85\x61\xee\x96\xa8\x89\x10\xaa\x3d\x93\xd1\x07\x87\xcd\x1d\x75\x80\x55\x67\x31\xc1\x74\xa6\xe3\xa3\x2d\x9d\xcf\xa4\x16\x60\x4f\x0c\x67\x14\x81\xd0\x51\xf6\x3d\xb6\x91\x9f\x4a\xba\x44\x86\xd1\xb0\xfd\xc6\x11\x2c\x15\x21\x55\x9f\x42\x45\x23\xc2\x6b\x4f\xb7\x38", n, e, b"\xcd\x60\xde\x3b\x4a\x12\x89\xa8\x4c\xa7\x61\xf9\x0f\xa6\x3f\x4d\x56\x88\xbd\x88\x5f\x4b\x53\x1c\x85\x15\xad\xd2\xde\x12\x51\xf9\x93\xff\x7f\x98\x6b\xef\x3f\xba\x69\x2e\xcd\xeb\xc8\x19\x42\xd7\x42\x9c\x7a\x59\xc5\xd3\xf1\xfb\x87\x2f\xc1\xda\x19\x15\xe9\x45\x86\xa5\xc3\xd9\x63\x60\x36\x19\x00\x8f\x7e\xfe\xde\xd1\xd7\x0b\x0a\x11\xce\x2c\xd8\x1b\x5b\x0d\x86\xb3\x76\x0c\x94\x83\x67\x4f\x55\xe9\xfa\x47\xf2\xf3\x10\xd5\x88\xfb\x21\x60\xe8\xb5\xc3\x2b\xe4\xe7\xa9\x68\xd5\xa8\xd4\xac\x65\x76\xb7\x1a\x2b\x91\xcd\x6a\xf0\x01\x6c\xbc\x81\x6d\x4a\xae\x8c\x70\x64\x9e\x08\xdc\xe9\x0b\x3c\xe5\x2a\xb4\x9c\xe2\xcb\x5b\x0e\xd8\xa4\x5e\x33\xd9\x4c\xf2\xd4\xcf\xde\xe1\x15\x12\x70\xb2\x07\x3a\xef\xfe\xaf\x71\x7d\x39\xe0\x41\x92\xb8\xb6\x93\xc5\x3f\x21\xa6\x12\x38\x13\x28\x08\x06\x92\x0b\x7d\xc5\x82\x20\x1c\x9d\x11\x70\x50\x32\x06\x71\xe8\x61\x39\xa0\x27\x97\x6b\x7e\xcf\x41\x33\x69\xa9\xfc\x28\xe0\xbd\x71\x9c\xeb\x5e\x10\x7d\xe7\x99\xf1\xbc\x2e\x25\x5a\x9f\x29\x47\x6d\x45\x74\xd1\x33\x2f\x66\x46\x8a\xfb\x90\x04\xff\x7b\x53\x53\x02", )?; test( HashAlgorithm::SHA512, b"\x85\xed\x1e\x3d\xfc\xd5\xbc\xa2\x4c\xad\x1d\x01\xeb\xe1\x92\xb7\xd0\x59\xec\x9b\x88\x44\x36\xe1\x87\x14\xa4\x3f\xbc\xc9\xc6\x4f\x68\x73\x01\x35\x2f\xf2\x40\x81\x70\x01\xe7\x57\xd2\x73\x09\xcd\x1f\xbb\xda\x94\x56\xb2\x67\xdb\xfb\x95\x84\x70\xb2\x4d\x06\x28\x0c\xf4\x33\x82\xa1\x94\x77\x87\x5f\x32\x59\xf4\x21\x0b\xac\x9b\x83\x1d\x0a\x07\xf5\xe9\x7e\x5f\x0f\x78\x81\x8c\x25\x9c\x28\x9e\x1a\x78\x9b\x6c\x79\x42\xc9\x7b\xc1\x48\x5a\x22\x01\x31\xe5\xeb\xa5\x86\x64\x3b\x90\x71\xe5\x36\x6b\xc4\x82\xdd\x3c\x3c\x92\x79", n, e, b"\x13\x81\x34\xbb\xec\xef\xaf\xc7\xca\x8b\x10\x2c\xbe\x87\xb0\x12\xf8\xaa\xda\x88\x78\x99\x50\x02\xcf\x18\x87\x69\x4b\x5b\xe3\xb8\xf0\xbb\x61\x6b\xc6\xe0\x79\x62\xd5\x48\x2d\x3a\x52\xc5\x2a\xb9\x1b\x3e\xe0\x06\x4d\x24\x55\x8e\x13\xc7\x5c\x80\xf6\xa9\x5b\x7d\xc4\x98\x44\x28\x79\xd5\xba\xf8\xff\xa7\xe2\xf6\x38\x80\x8b\x97\xff\x70\x13\x6b\xb6\x45\xe3\x09\x44\xdd\x97\xa9\x97\xa0\x20\x51\x69\x55\x3a\x5b\x9e\x87\x4c\x5a\x94\x41\xe1\x8c\x15\xeb\xed\x76\x04\x3b\x63\x9d\xfd\x64\xdb\x79\xe1\x74\x84\x7a\x10\x27\x24\xa2\xa0\x5c\x64\x94\x73\xcc\x7d\xac\xd3\x9e\x2e\x1d\x56\x66\xbb\xb5\xf0\x12\x46\x74\x70\x48\xff\xfc\xdf\xcd\xdf\x78\x2d\xa2\x4a\x6d\xcc\x02\x2b\x26\x95\xf7\x07\x81\xbd\x9f\x8f\xf7\xd0\x3b\xe2\x2e\xb8\xfc\x79\x3f\x5c\x07\x1a\x66\xd9\xa6\xea\x46\xc6\xa2\xcf\x05\x56\x52\x6b\xa8\xb0\x85\x07\x35\x46\x44\x80\x81\x73\x2a\xc1\x5f\x12\x83\x3c\x1d\xb1\x70\x1f\xf7\xf6\x83\x44\xca\x65\xdf\xf8\x62\x11\xa0\x03\xad\xbf\x51\x89\xcf\xae\x79\xea\xa8\xc8\xb7\x14\x1e\xa3\x78\xe4\x4c\xc9\xc5\xbf\x02\x4d\x2c\x71\x0f\xf5\xcd\x68\xaf", )?; test( HashAlgorithm::SHA512, b"\x0b\xdb\xa3\x4e\x35\xfc\xa6\x5a\x17\x81\xd4\xd7\xc9\x33\xa5\xf2\x10\xd3\xa5\x94\x83\xae\xbc\x95\xec\x71\xb3\x2d\xf1\x3f\xf4\xab\xf4\x01\x91\x69\x37\xfd\x88\xff\x44\xab\x46\xb7\x8c\xc3\x69\x41\x4e\x9b\xca\xa8\xba\xb0\xbb\x85\x57\x82\x8d\x73\xa2\xa6\x56\xc2\xf8\x16\xf0\x70\xb5\xcb\x45\x54\x9e\x8e\xca\x9d\x7c\x0b\x4a\x7b\x0a\x27\xe5\x1c\x11\x93\x58\xda\xd2\xa1\x7f\xb3\xa4\x57\x18\xf9\xde\xc3\xc9\x4a\xf7\x8d\x65\xc3\xec\xd3\x6b\x71\xe2\x30\xcf\x08\x0d\x1e\xfd\xd8\xd0\x7f\x1c\xfc\x26\x76\x8f\xd5\x40\x7b\xc2\xb7", n, e, b"\x9f\x48\xde\xb9\x6b\xec\x0b\x72\xfb\xc4\xf1\x2f\x08\xaf\xb4\x6b\xcc\xf1\x9d\x9e\x0c\xd0\x36\x8e\xbe\xb3\x12\xd8\x38\x72\x62\x63\x80\xac\x92\x8b\x61\x2c\x5c\xd7\x74\x38\xd4\x7a\xa9\xce\xea\x90\x5a\x9d\xe7\x18\x2c\x8e\xf7\x6e\x8a\x7a\x03\xd6\xef\xec\x84\x00\xb6\x49\x63\x62\xbf\x6a\x30\xce\xb1\xce\xd2\x18\x5f\xc7\xc2\x11\x7b\x6a\x6d\x88\x8a\xc2\x0c\x16\x87\xb0\xf2\xaa\x9b\x76\x70\x5f\xd3\x15\x48\x89\xb6\xac\xaf\x4e\x63\xbe\x25\x88\x0c\x71\xe6\xc2\x39\xec\xfb\x96\x50\x04\xcd\x63\x21\x25\x7f\x84\x6a\xfd\x2a\x65\x90\xc7\x2a\xd8\x31\x46\xee\xfc\x7b\x0d\xc4\x79\x63\x39\xa7\xf6\x4d\xa0\xfb\xe3\x59\xf9\x4a\xce\x1f\xd1\x51\xc5\xac\x7b\xb5\x70\x7b\x32\xea\xcf\x56\x4f\xe1\x62\x2e\x66\xe1\x84\x4e\x63\x96\x02\xca\x36\x27\x4a\xe0\x1f\x93\xe6\xb2\xbd\x1e\xff\xd3\x4a\xb6\x3d\x85\x2c\xc9\xca\xf3\xce\x84\x46\xc2\x9c\x8a\xe3\xc6\x11\x0f\xb7\x53\x8c\xc8\x37\x1c\x2a\x39\x81\x24\x9c\xdc\x1b\xe2\xb2\x4b\x6a\x0c\x95\x17\x64\xd0\xb7\xef\xa9\x2a\x22\xcd\x8e\xd1\x65\xe1\x82\x86\x35\x79\x37\x79\x97\xa9\xee\x50\xc8\xac\x3a\xa4\xdf\x1a\xca", )?; test( HashAlgorithm::SHA512, b"\x9a\xee\xd8\x5b\x40\xba\x7f\x86\xa2\x28\xb5\xa1\x51\x5b\xa1\x90\xb2\xef\xff\x66\x99\x3a\x5e\xce\x19\xd1\x8b\xaa\x9b\x4e\x4d\xf9\x2e\x51\x52\xfe\x1e\xc5\x6a\x9f\xc8\x65\xf3\x0b\xac\x7e\x94\x9f\xc4\xf6\x2f\x0b\x15\x8d\x10\xb0\x83\x63\x6b\x4d\xe9\xbb\x05\xdb\x69\xfe\x31\xb5\x01\x03\xfe\xfc\x5f\x8d\xaf\x3a\xf7\x15\x6b\x45\x52\xca\x36\x67\xa9\xd7\x20\xbb\xb2\xe4\xbc\xda\xba\xdf\xd4\xb7\xf4\xfc\x5b\xc8\x11\xfa\xa3\x67\x10\xa9\xd1\x77\x58\xa9\x8d\x4a\x04\x74\xfe\xc2\x7e\x9e\xf5\xb7\x4f\x5c\x68\x99\x35\x44\x23\x57", n, e, b"\x9e\xec\xdb\xd7\xfb\xf6\x18\xdd\xdd\xfb\x6e\x75\xd6\x44\x40\xf6\x04\x45\xb8\x53\xc5\x42\xfe\x0f\xba\xaa\x6a\x43\x12\x94\xe6\xcb\x66\x83\xae\x1a\x71\xea\x05\x5e\xb4\x9c\xd2\xa3\xcb\x51\x54\xdc\x93\xd9\xaa\x16\x63\x99\xf4\xe6\x29\x4f\x0e\xb0\x65\x28\x00\xd7\x1e\x04\x1c\x1c\xe1\xad\x84\x9c\x03\xc9\x63\xbc\x09\x29\xdc\xdd\x11\xbe\x5d\x67\xa0\x50\xd0\x2b\x64\xb2\x9e\xab\xa6\x55\x64\x2b\x64\x36\xfb\xfb\x16\x36\x90\xbf\x43\x2f\xdc\xee\xdd\x10\x6c\x2f\x49\x72\xec\xbf\x30\x77\xed\x8b\x75\x3b\xb6\x05\xec\x1e\xa0\x30\x20\x83\x9a\x31\x8a\x24\xf8\xd4\xc1\xd7\xd8\xdf\x99\xa7\xf0\x01\x0a\xe4\x1a\x8b\x06\x8e\x28\x88\x53\x10\x56\xa7\xda\xbb\xe9\x21\x87\x8d\xcd\x3c\x7d\x69\x41\x68\x67\xf4\x01\x2a\x60\x6a\xe8\x68\x55\xf1\x5a\xed\x0d\xa1\x25\x0e\x59\x68\x77\x06\xe8\x9c\x94\x94\xba\xf3\x7f\x61\xfb\x17\x03\xb7\x99\x28\x79\x5f\x90\xcc\xbe\x29\x3a\x1e\x94\x72\xf6\xe0\xf4\xb8\x90\xfd\xda\x3e\xa2\x52\x2e\x3d\x11\xd5\xab\xdf\x00\x69\x51\x94\x24\xd1\x47\xb5\x64\x6a\x5a\x60\x1f\x19\xec\x89\x72\x9a\x8b\x48\x46\x1e\x71\xc0\x8b\xbe\x9c\xda", )?; test( HashAlgorithm::SHA512, b"\x65\x4e\x18\x9f\x06\xc7\xd4\x2d\x55\x39\xa5\x87\x21\x84\xf8\x33\x6c\xf1\x00\x69\x1f\x19\x08\x18\xfd\x02\x08\x2a\xd6\x8a\x76\x09\xfd\x09\x5e\x62\xfc\x32\xb5\x29\x85\x3a\xeb\xdd\xac\x3d\xbf\x0d\x54\xdd\x57\x1b\xe7\x2c\x90\x40\x4b\xcc\x93\xd0\x11\x54\xa9\xbf\xef\xf6\x50\x65\x70\x5f\x8e\x7e\xea\xdf\x85\x75\xb1\xca\x48\xe2\x8a\x1e\xed\x51\x62\x65\xe3\x45\x40\xdd\x86\x7c\x79\xd7\xf1\x75\x23\x5d\x13\x30\xcb\x17\x06\x35\x6b\x70\x9b\xd7\x96\xf4\x3a\xba\xf6\xfc\xe9\x93\xf8\x8e\xaa\x2f\xc6\x7f\x0a\xb7\x76\xda\xf7\x32", n, e, b"\xaf\x90\x29\x8b\xce\xf6\x15\x30\x9f\x23\x5d\x5c\x33\x60\xf0\xdf\x11\xf5\xfb\x98\x87\x89\xf2\x13\xd4\xc4\x61\x34\xfe\xe5\xeb\x10\x4a\xa1\xfa\xbb\x13\x07\xc9\xa9\x04\x70\x9d\xe8\x86\x73\xed\x99\x51\xcb\xa9\x31\x67\xc6\x7c\x09\xd8\x27\x02\x1b\x08\xa2\x2c\x05\x05\x82\x8a\xb4\xbe\xb4\x2e\x59\xa3\x88\x32\xcb\x4d\xa2\x4e\xcf\x91\xf4\x70\xa3\xb4\x12\xc0\x71\x2a\x8a\x59\xf6\xf2\x73\x9d\x4e\x9e\xb4\xcc\x58\xd2\xc5\x25\x92\xf1\x45\x2d\xc6\x57\x59\xab\xe4\x3e\x8d\x2b\xc8\x04\xe2\xef\xb3\xef\xc9\xb2\x3c\xc1\x73\x4f\xf7\xca\xef\xa4\x6b\x03\xba\x4b\x39\x7d\x07\x14\xcd\xb8\x50\x1a\x81\x2c\x1b\x9f\x47\x41\x1c\x91\xcb\xa5\x3a\x3d\x3b\x13\x9e\xdb\xd7\xcb\xb5\x43\xf5\xbf\x38\x29\xba\x7f\x5f\xaf\xd8\xa7\x12\xc0\xb1\x11\x94\x3f\x53\x20\x93\x53\xaf\xab\xa1\x76\xb3\xf5\xdc\x06\x03\x39\xd0\x9b\x1f\xb3\xc2\x13\xda\xe5\xd0\xf0\x04\xd3\x02\x82\x85\x60\xfb\x5d\xeb\xf9\xfe\x49\x1e\xaa\x66\xf5\x97\xaa\x4d\xe2\x3e\xee\xf9\x17\x63\x58\x75\x5c\x95\x2e\xf9\x6e\x36\x72\x58\x3b\x6e\xcd\x95\xa0\x2e\x8c\xa7\xb2\x1d\x7c\x20\xcb\xb7\xa7\x57\xaf\x71", )?; test( HashAlgorithm::SHA512, b"\x12\x1f\x80\xb4\x3f\x97\x57\xb3\xfa\x80\x90\x6a\xea\xb2\x32\x19\x5f\x0e\x2c\x41\xe5\xbf\x8c\x09\x1a\xc0\xf1\xe0\xbc\x9e\x43\x64\x06\x80\xa1\x82\x3d\x64\x9b\xdf\x86\xab\xa2\x77\xfa\xd8\xbc\x85\xfc\x95\x7d\xa2\xca\xf7\x32\x30\x53\x02\x5f\xf9\x49\x70\x6c\x14\x76\xae\x9b\x09\x53\x28\x3d\x34\xd7\xc6\x26\x6f\x8d\xb6\x5e\xeb\xe9\x6d\x19\x5f\xdc\xe8\xe9\x65\xa6\x38\x33\x20\xec\x3d\xe0\x23\x0a\xb2\x54\x8e\xaa\x69\xa4\x7a\x96\xd8\x03\x98\xca\xd5\x7e\x14\xce\x9e\xea\xc0\x42\x1c\x1a\x6e\xba\x69\x55\x9d\xcd\x8f\x06\x59", n, e, b"\x06\xa2\xd7\x45\x85\xf1\x2e\xa7\xa8\x05\x27\xb8\xc6\x35\xa2\x1c\xc1\x1b\x45\xdb\xb0\x88\x5a\x12\x72\x21\x26\x81\x1d\xd2\x5d\x65\x7b\xfa\x9f\xda\x77\x43\x01\xca\x34\x98\xd0\x5d\xfd\xfb\x78\xa6\xaa\x16\xa9\xf8\xa9\x5f\x40\xf1\xf0\x4b\xd3\x54\xa5\x22\xf6\xa2\xd6\x2b\x32\x4e\xfa\x3c\x00\x6c\x22\xc2\x31\x4b\x01\xfa\x0e\x91\xa3\xdb\xa4\x9a\xa3\x5b\x46\xb1\x98\x04\xb0\x7a\xd9\x8f\xe4\xbc\x99\x03\x93\xa4\xa2\x73\xce\x8f\x1c\x85\xfc\x19\xcd\x5e\xae\x9a\xf0\xb7\xd1\x95\x7b\xb2\x34\x09\x77\x8a\x01\x0b\x00\xc6\x95\x9e\x1b\x67\x06\x6f\xdb\x9f\x84\x95\xb4\xde\x4d\xcb\xb9\x87\x35\x81\x45\xb1\xff\x6a\x39\xef\x6f\xc5\x88\xcd\xa1\x74\x4e\x0a\xb9\xe7\xeb\x00\x2c\x29\xa7\x85\x31\xd2\x51\x57\xc5\xc2\xcd\x64\x70\x55\x15\x60\xa0\x28\x45\xdb\x6d\xbe\xe2\x42\xf9\x65\xa2\x55\x40\x6f\x6e\xf4\x7b\x32\x21\xa5\x11\x0e\xdb\x44\xd3\x8b\x94\x19\x1a\xea\xf4\x33\xc0\xec\xe3\x48\x0b\x9d\x1b\x06\xd8\xb8\xb6\xc0\xa2\x32\xa0\x4c\x56\x78\x88\xe6\x37\x2f\x2e\x94\xbc\x2b\xe6\xb8\x27\xf8\x71\x2a\xf4\x8c\x6f\x1e\x4f\x22\x3f\x55\x28\xfc\xf3\x48\x79\x9d", )?; // [mod = 3072] let n = b"\xdc\xa9\x83\x04\xb7\x29\xe8\x19\xb3\x40\xe2\x6c\xec\xb7\x30\xae\xcb\xd8\x93\x0e\x33\x4c\x73\x14\x93\xb1\x80\xde\x97\x0e\x6d\x3b\xc5\x79\xf8\x6c\x8d\x5d\x03\x2f\x8c\xd3\x3c\x43\x97\xee\x7f\xfd\x01\x9d\x51\xb0\xa7\xdb\xe4\xf5\x25\x05\xa1\xa3\x4a\xe3\x5d\x23\xcf\xaa\xf5\x94\x41\x9d\x50\x9f\x46\x9b\x13\x69\x58\x9f\x9c\x86\x16\xa7\xd6\x98\x51\x3b\xc1\xd4\x23\xd7\x00\x70\xd3\xd7\x2b\x99\x6c\x23\xab\xe6\x8b\x22\xcc\xc3\x9a\xab\xd1\x65\x07\x12\x40\x42\xc8\x8d\x4d\xa6\xa7\x45\x12\x88\xec\x87\xc9\x24\x4b\xe2\x26\xaa\xc0\x2d\x18\x17\x68\x2f\x80\xcc\x34\xc6\xea\xf3\x7e\xc8\x4d\x24\x7a\xae\xde\xbb\x56\xc3\xbb\xca\xff\xb5\xcf\x42\xf6\x1f\xe1\xb7\xf3\xfc\x89\x74\x8e\x21\x39\x73\xbf\x5f\x67\x9d\x8b\x8b\x42\xa4\x7a\xc4\xaf\xd9\xe5\x1e\x1d\x12\x14\xdf\xe1\xa7\xe1\x16\x90\x80\xbd\x9a\xd9\x17\x58\xf6\xc0\xf9\xb2\x2a\xe4\x0a\xf6\xb4\x14\x03\xd8\xf2\xd9\x6d\xb5\xa0\x88\xda\xa5\xef\x86\x83\xf8\x6f\x50\x1f\x7a\xd3\xf3\x58\xb6\x33\x7d\xa5\x5c\x6c\xfc\x00\x31\x97\x42\x0c\x1c\x75\xab\xdb\x7b\xe1\x40\x3e\xa4\xf3\xe6\x42\x59\xf5\xc6\xda\x33\x25\xbb\x87\xd6\x05\xb6\xe1\x4b\x53\x50\xe6\xe1\x45\x5c\x9d\x49\x7d\x81\x04\x66\x08\xe3\x87\x95\xdc\x85\xab\xa4\x06\xc9\xde\x1f\x4f\x99\x90\xd5\x15\x3b\x98\xbb\xab\xbd\xcb\xd6\xbb\x18\x85\x43\x12\xb2\xda\x48\xb4\x11\xe8\x38\xf2\x6a\xe3\x10\x9f\x10\x4d\xfd\x16\x19\xf9\x91\x82\x4e\xc8\x19\x86\x1e\x51\x99\xf2\x6b\xb9\xb3\xb2\x99\xbf\xa9\xec\x2f\xd6\x91\x27\x1b\x58\xa8\xad\xec\xbf\x0f\xf6\x27\xb5\x43\x36\xf3\xdf\x70\x03\xd7\x0e\x37\xd1\x1d\xdb\xd9\x30\xd9\xab\xa7\xe8\x8e\xd4\x01\xac\xb4\x40\x92\xfd\x53\xd5"; let e = b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xea\xf0\x5d"; test( HashAlgorithm::SHA224, b"\x25\x4c\xe3\x6e\x8e\xd6\x2e\x08\x87\xd4\xbe\x00\xee\xfa\x82\x51\x5a\xce\xf9\x56\x54\x0c\xff\x45\xc4\x48\xe7\xf9\xa9\xd5\xc9\xf4\x0d\xe6\x1d\xa4\x39\xf3\x89\xe5\x25\x5e\xf8\xc8\x32\x57\xec\x92\x1b\xfd\x15\x08\x29\xc5\x22\xea\xa7\x20\xd7\xbe\x96\x58\x60\xce\xa2\xbb\xe5\x74\x54\xfc\x5e\x95\x88\xd6\xa9\x6c\x22\xf2\xd9\x89\xfd\x0b\xd2\x19\x24\x50\x13\x67\x45\x0a\xd2\xa3\x62\x7e\x4e\xe3\xca\x15\x61\x67\x48\xba\x54\x21\x9a\x84\xf8\x74\x24\x95\xf2\x3d\xe6\x42\x57\x10\xac\x74\x79\xc4\x84\x4d\x00\x31\x75\x0f\x3c\x38", n, e, b"\x9d\xfd\x3f\x32\x09\x1b\x91\x6a\x56\xf4\xf3\x57\xa9\x61\xa5\x25\xa5\x27\xfd\x29\xb1\x14\xb3\xf0\x38\x29\xdf\x1b\x25\xc0\xc5\x97\x3b\x1c\x51\xaf\x36\x63\x31\x16\xf4\xc7\x7a\xa2\xf6\x77\xa3\xb0\xf8\x23\x68\xa5\x38\xbd\xb3\x3e\x49\xdb\x9f\xa7\x04\xbd\x51\x23\xed\xef\xbd\x3a\x86\xdc\xc3\x92\x83\xc2\xa0\x3c\x96\xc6\x90\x30\xf0\x4c\x64\x84\x17\xf5\x44\xf0\x8a\x9b\x4e\x01\xae\x4d\x42\x9e\xf2\x14\x22\xdd\xfe\x76\x83\x4f\x92\x5c\x56\x53\xb1\x22\x78\x35\xa9\xa4\x41\x3d\xa7\x94\x2b\x0a\x01\x51\x96\xfa\xec\x31\x50\x41\x11\xc9\xf0\x84\xd6\xdd\x6d\x5a\x69\x56\xc5\x54\x12\xc1\xbd\x14\xaa\xf9\x5d\x82\x8e\x84\x49\x61\xfd\xd6\x0c\xc0\x78\xf1\x69\xf6\xe1\x18\x6c\xb0\xcb\x6b\xa3\xd2\x11\x68\xc5\xbf\xd0\x67\xdc\x6e\x46\x07\x84\xe7\xc6\xa7\x6e\xe3\xd8\xb3\x32\xac\xfa\x97\xe5\xd4\xb6\x56\xec\x8c\x61\x1e\xbd\x89\x6f\xe9\x0e\x61\x9b\x58\x8d\x0c\x61\x54\x92\x46\x4a\x1b\x3d\x05\xd3\xa9\x63\xf4\x51\x05\x1c\x65\xd8\xf8\x1f\xee\xa9\x25\xbc\xbe\xe9\xce\x7a\x39\xba\x3c\x91\x5a\x18\xa2\x4a\x45\x1e\x47\x0e\x76\x1d\x09\x85\x5a\x96\x5e\x83\xed\xae\x3f\xca\x41\x67\x8c\xc9\xd0\x98\xba\x99\x28\xb5\x25\xb5\x0e\x48\xcb\x03\x0c\x51\x0c\x4c\xe7\x27\xc6\xb9\x3b\xd0\x91\xb7\xd2\x0b\x4b\x96\x11\x65\xae\x0e\x28\x48\xaa\x99\x5b\xb7\x3a\xbe\x9a\x26\x34\x37\x8d\x22\x41\x28\x54\x1a\xb0\x56\xa3\x1b\x78\x48\x85\xae\xf8\x03\x4d\xed\xac\x13\x16\x74\x02\xf9\xf6\x2b\x55\x74\x12\x20\xdf\x8a\xff\x5d\xef\xb6\x9c\x03\x5d\x9a\x31\xe2\xa5\xb8\x81\x70\x57\x24\x1b\xcf\x85\x49\x32\xf5\xed\xee\x7e\xe6\x6e\x89\x17\xaa\x4a\x71\x8b\x6c\x44\x6b\xdd\xf0\x84\xf5\xcd\x76\x9c\xae\xff", )?; test( HashAlgorithm::SHA224, b"\x35\xad\xcd\x3f\x24\xb6\x72\x55\x18\x81\x5c\xf4\x60\x6f\x7b\x1d\x94\x0c\x39\x63\x84\x37\x0a\x37\x6e\x84\x45\x69\x74\xde\x32\xec\x4c\x71\x64\xda\x3a\xc7\x49\xb7\x3b\x30\xff\xfa\x83\x68\x69\xc9\x2a\x74\x83\x05\x23\xcd\xf2\x86\x6d\xc0\xe0\xa8\x8d\x15\x06\x06\x3b\xef\x0a\x85\x5c\xf3\x0c\x95\x30\xac\x7c\xb3\xcd\x2e\x2e\x32\xcc\xfa\xb0\x3c\x42\x22\xdb\x14\xf2\xae\xa8\x9d\xc0\x3d\x45\x20\x69\xf0\x68\x4a\x72\x83\xe4\x74\x5e\xbd\x7a\x28\x24\x0b\xf1\xe0\xe0\x68\x68\x10\xc9\x7f\xec\x67\x63\x14\x46\x52\xf6\xa0\x16\xc3", n, e, b"\xb5\x12\x0b\xe9\x8b\xcd\xfd\xc1\xe1\xe3\x31\x2d\xd7\xb5\x91\x0f\x07\x31\x32\xa4\x27\x76\xc4\xda\x75\x69\x0c\x64\x1f\x32\xd2\x89\x91\x87\xd9\xb3\x9b\x55\xf9\x9e\xbe\x6c\xa0\xa0\x80\x36\x37\x27\x49\x81\x67\x06\x66\x4f\x39\xb2\x73\x12\x13\x5c\x50\x33\x9f\x2b\xb1\x7b\x2c\xee\xe2\x57\x68\xc2\xbc\x0a\xc3\x7d\x6c\xa6\xee\x90\x3c\x84\xe8\x2e\x2f\x4d\x00\x5d\x73\xbd\xc3\x35\xf1\x35\x39\x9c\x49\x12\x36\x62\xe8\x90\x81\x19\x91\x84\x37\xed\xb6\x15\xb1\x4e\x90\x6c\x9f\x8b\xa1\xb8\x5d\x5b\x45\x90\x9f\x43\x9c\xc8\x99\x29\x51\xbe\x16\x84\xa9\x9e\xba\x04\xec\xb0\xf6\xdf\x92\x33\x53\x51\x69\x77\x77\x4f\x69\xe8\x26\x65\x11\x90\xaf\xfa\x86\xa4\x0b\xe7\x5b\x06\xa4\x12\x8e\x55\x09\xc5\x15\x57\xae\x4f\xb4\x10\xc7\xe5\x84\x1a\xc9\xfd\xc4\xbc\x1f\x97\xe2\x86\x24\x29\xf3\x71\xaa\xaf\x99\x82\x4d\xac\xfe\xe0\xbc\x39\x61\xfb\x98\xb3\xff\xc0\x91\xf7\x79\x56\x22\x3e\xbf\x5b\xb5\x46\x55\x23\x58\x20\x8a\x32\xef\x9c\x37\x82\x5e\x81\x66\x8f\xd2\xc2\x30\xf7\x88\xca\x16\xff\xbc\xc0\xf1\xd8\x84\xb3\x0f\xe8\xef\xe6\x49\x82\x95\x00\x4c\xa7\xc7\xf2\xb1\x73\xe5\x66\x6b\x8b\x0f\xdf\x9d\x32\x75\x65\x59\xf9\x9d\x10\x5c\x1e\x80\x42\xa7\xae\xd7\x26\x2c\xa9\xa1\x70\x25\xaa\x09\x60\x75\xfe\x44\x33\xf3\x4d\xb6\xb0\xf1\x97\x77\x6c\x21\xfb\xe0\x0e\x83\x2e\xba\x02\x8e\x66\x52\x65\x30\x18\x07\x9f\xee\x04\xeb\x3e\x3c\x12\x80\x3c\x39\x83\x0d\x07\x2a\xb4\x97\x1b\xca\xb4\xb7\x97\x58\x69\x4b\x5d\x3d\x8a\xb2\x1c\xe8\x74\xb7\xc4\x2b\xed\xd5\x26\x52\x21\x9f\xf5\x16\xfd\x69\x4c\x3d\x7c\xb0\xbe\xf0\x18\x1b\xb8\x5e\xb4\xb1\x31\x84\xea\x3a\xef\xe3\xcc\xee\xa5\xc5\x75\x96\xf7", )?; test( HashAlgorithm::SHA224, b"\x0b\xa5\x73\xa9\xdb\xb7\xf6\x2e\x5a\x4d\x3d\x84\x1b\xfd\x92\x98\xe8\xbb\x29\x9e\xb4\xfd\xb2\x56\xd1\x1d\x2f\x8d\x64\xfe\x03\xe6\x15\xf2\x4c\xda\x0b\xdb\x73\xfe\x17\x91\x02\x84\x2f\x84\xb5\x05\x1f\xa3\xd3\x7e\x7b\x7c\xbe\x98\xd8\xa4\xc9\x2c\x3b\x59\x4b\x06\xd2\x66\xf2\xe9\xe2\x47\x59\xd4\x01\x8e\xdc\x84\x85\x85\xab\x3a\x3c\x15\x1d\xbe\x5e\xe6\x47\xa4\xbf\xc8\xce\xce\x49\x52\xf9\x32\xaa\xc8\x0a\xdd\x4a\x42\xcf\x38\x80\x0b\x74\x8b\x05\x48\x9b\xbf\xa9\xda\xae\x68\x44\x85\x74\x03\xf0\x51\xe3\x7b\x75\x30\x36\xf3", n, e, b"\x36\xfd\x68\x13\xab\x41\x1c\x4d\xcb\x2d\x7b\xe1\xed\x61\x6c\x1e\x40\xde\x29\x1a\x00\xac\xd8\x7d\x2b\x4d\x9d\x4b\x73\xc8\x86\x4a\x44\x41\x3c\x51\xf0\x9b\x37\x84\x4a\x98\x04\xf8\x23\xb2\x7a\x90\x94\x62\x7a\xaa\xf0\x0a\x6b\xe9\x42\xd7\x55\x8b\xe1\x1b\x84\xa7\x3d\x98\x02\x9c\x2e\x26\xeb\x8f\x65\x05\x80\xec\xb1\x1b\x4e\xc2\x36\x35\x97\x33\x34\x44\x56\x96\x34\x35\x16\x00\x21\x29\x62\xfe\xf5\x35\x2b\xdb\xa3\x67\x83\x28\x99\xd3\x18\x8a\x74\x72\x36\xf0\x85\x28\xf0\x86\xd9\x3c\xa3\x3a\x06\xb1\x03\x92\xbb\xbd\x62\x5c\x86\x7d\xdb\xa7\x4b\xb1\x51\xdc\xc6\xaf\xdd\x4c\xe4\x10\x16\xdc\x2e\xf0\xce\xea\x2c\xa2\x09\x17\xfb\xdb\x07\x77\xe2\x35\x03\x46\x4d\x0b\xb5\x9c\xd4\xe1\x2c\x10\x94\x52\x50\x88\x9b\xae\x2e\xd8\x39\xb7\x09\x64\xb2\xc9\xd9\x57\xea\xc6\x22\x2a\x49\xb3\x37\x73\x04\x11\x98\x44\x48\xe5\x8c\x02\x73\x71\xbc\xf9\xc2\xc7\xd6\x86\xde\x3b\xda\xe1\x67\x38\xdb\x52\x76\xe0\xf5\x38\xd1\x5b\x35\x41\xc0\xed\x86\xd3\x18\xb4\x23\xd8\xc7\xf1\x85\x96\x02\x10\x8a\x4b\x11\xc2\x77\x29\x41\x39\x6a\x14\xa2\xa8\x8e\xc7\x97\x12\x97\xc1\x86\x33\x02\x09\x98\xee\x02\xb3\x11\x4d\x19\x01\x2a\x09\xa1\x81\xd0\x1f\x11\xcb\x8f\x8c\xb5\xf4\x38\xe8\x2f\xb4\x5e\x76\x78\xbc\x8d\xf9\xa2\x6f\x1a\x34\x95\x43\x9a\x7a\xc1\xf1\xbd\xa6\xfb\x86\xc9\xb3\xed\x6c\xb5\xf7\x88\x63\x49\x46\x34\x8b\x7e\x24\xb0\x89\x4c\x39\xc5\x06\xce\xd2\xda\x65\x7a\x33\x5e\x54\xe8\xf9\x97\x38\x4e\x40\xc5\x6a\x17\xa2\x8a\x9b\xb6\x48\x75\xa1\x59\xca\xda\x5a\x64\x4a\xb3\xbd\x6e\xa7\xbc\x4c\xca\xed\x43\xdd\x09\x55\xf6\xbe\x6e\x45\x9e\x2e\x6a\x7b\xa6\x52\xf1\xe9\xa3\xf8\xa8\x3e\x47\x95", )?; test( HashAlgorithm::SHA224, b"\x89\x53\x0f\x81\x6a\x5e\x2a\xbd\x4b\x42\x2f\xdf\x96\x8f\xfd\x96\x4e\x0c\xcf\x82\xa4\xfc\x6d\x9a\xc5\xa1\xa4\xcb\xf7\xff\xf3\xe1\xe4\xe2\x87\xab\x35\x22\x6a\x5a\x63\x26\xf7\x2b\xca\xa7\x91\x46\x00\xb6\x94\xe5\x64\x01\x8c\xb8\xfa\x52\xa5\x89\x76\x58\x63\x1c\x96\xaa\x93\x59\xb5\x09\x82\xac\x9e\xe5\x6c\xad\x9e\x23\x37\xfc\xdd\x1e\x61\x6f\xed\xec\x38\x70\xa4\xe2\x49\xa0\x27\x5a\x1a\xc1\x48\xb3\x1c\xd2\x12\x9a\xdb\x7b\xa1\x88\x78\xac\x38\x8c\x59\x82\x8d\x4b\x1f\x6a\x67\x45\xd8\x88\x6b\x5a\x76\x5a\x33\x8c\x81\x98", n, e, b"\x27\xc7\x96\xca\xee\xe6\xb4\xbc\xd7\x50\xd8\xdf\x13\xcb\xe5\x16\x4f\xd7\x26\xf9\x1b\xaa\x57\x5f\x70\x2f\xe2\x96\x67\x44\xcf\x2b\xef\x38\xc9\x3e\xfa\x11\x11\xc9\x27\x7d\x77\xf3\xec\xf6\x97\xd0\x20\x30\xf0\x1e\x3d\x96\x4c\x31\x25\x53\x3d\x40\x88\x34\xb7\xce\x65\x28\x24\x30\x3e\xb2\x78\xdc\xa6\x10\x23\xa2\xf9\x28\x03\x52\xf8\x9b\x5d\x03\xd0\x08\xc1\x03\x03\x2b\x2b\x5c\x6b\x8c\xf7\xbe\xfc\x1f\xff\xfa\x9b\x55\x9a\x99\x57\x59\xa8\xd3\x3c\x6f\x49\xae\x57\x4a\x2d\x31\x80\x5a\xb0\x55\xe6\x46\xab\xed\x71\xb3\x0e\xcf\x73\x67\x03\x0b\xf2\x6b\x96\x2d\x41\xa2\xc7\xd7\x73\x5d\xdc\x0e\x5f\x1e\xda\x30\xb1\xae\x6e\xfe\xaa\xe9\xa4\xcf\x50\xb6\x85\x06\xc2\x1b\x12\xe3\xdf\x2b\x99\x3f\xea\xee\x44\x8a\x64\x43\xe6\x13\xcf\x53\x6e\x2a\x71\x1a\xa5\x26\x48\x71\x87\xb4\xfc\xd1\xfa\x68\x4e\x99\x47\x8c\x28\xb8\x4d\x9a\xf0\xeb\x6a\x49\x56\xc0\x37\x7d\x08\xee\x26\xeb\xd2\xd8\xd2\xf4\xce\x7e\x66\x04\x8d\xa3\xc0\x9c\x05\x38\xff\x8e\xfa\x17\x86\x90\xd4\x2f\x03\x41\xb2\x8a\x8f\xcb\x64\x9b\x53\x1a\x07\xaf\x1f\x21\xc4\x24\x32\x42\xe0\x45\xb1\x94\xa0\x4a\xd0\xf9\x2e\xdc\xe4\x82\xf3\x55\xf6\x69\x69\xcd\x90\x25\x4a\xb1\x59\xff\x9d\x9c\x0c\x66\x80\xf7\x8c\x99\x6d\x70\x48\xe2\xc5\xf0\x07\xad\x36\x21\x9d\x67\x2a\x0e\x76\xf1\xbf\x8b\xc8\x90\xfa\xa5\x6e\x49\x3f\x0c\x52\xd0\x9f\xa1\x26\x5c\xe5\x38\xe1\x66\x70\x9a\x00\xa2\xcd\x64\xe4\x5b\x9e\x5a\xca\xe2\xb9\x5d\xcb\x22\xbc\xfe\x96\x30\xe3\x2f\x37\xd0\xbb\x52\x9e\xfc\x8d\x29\x8c\x0b\xa7\xb8\xd6\x5e\x16\xde\xe9\x9a\xd7\x44\x6a\x39\x39\x46\x25\x87\x24\xd0\x8d\x84\x76\xe7\xf1\x6c\xcb\xc0\xe4\x26\x38\x38\x1a\x58", )?; test( HashAlgorithm::SHA224, b"\xe3\x76\x56\xde\xfd\xee\xdf\xb4\x6b\x14\x62\x8d\xff\x3f\x69\x17\xb8\x42\x0e\x5a\x97\xef\x6c\x54\xaf\xda\x55\xe0\x7c\x60\x43\xdd\x75\xe7\x90\x8b\xe4\x66\xe9\x38\xf6\x29\x00\x1d\x0e\xce\x81\x83\x5f\x94\x48\x2a\xba\xd5\xd1\xea\xa4\xd0\xef\x9b\xac\xac\xc1\x33\xfc\xba\xe2\x2e\x2d\xfb\xe1\x33\x60\xe2\xf1\xf4\x8a\x5a\xe1\x56\x0f\x0b\x4e\xd2\x93\xd9\x17\x1a\x0c\xae\x11\x00\x1c\x7a\xfc\x94\x9f\x78\xb6\x8d\x80\xb2\xaf\xeb\xd0\xc7\x9d\xda\x19\xec\x71\xd8\xef\x31\x89\x1a\xc9\x06\x27\x2c\x0f\xfd\x22\xd9\x74\xd1\xdb\x4a", n, e, b"\xa9\x27\xec\x4c\xeb\x2e\xc1\x47\xcc\x45\x7e\x66\xc1\x2a\x64\x6f\xdc\x41\x2d\x9e\xeb\x1d\x51\xf3\xb5\xa3\xe5\xa8\xf4\xb0\xd3\x6d\xeb\xa3\xa7\x19\x14\xcc\x6f\x23\x21\xc3\x9d\x83\x4a\xdd\xb4\x85\x7c\x82\xab\xe9\x28\x0c\x7c\x82\x31\x89\x39\x04\xbd\x27\x47\x4c\xb2\xcc\xe1\x01\x2b\x92\x1f\x0a\x4d\x63\x80\xaa\xed\x61\x43\x56\xd6\x53\x65\x33\x88\xce\x86\xac\x71\xa2\x7c\x97\x67\x47\xc9\x21\x3c\xf2\x97\xe7\x59\xfc\x3e\x2d\x7b\x1a\xd5\xba\x8c\xb3\x10\x6c\x0a\x67\x62\x44\x79\xce\x55\xd0\xcd\x67\xc2\x4b\x5a\x45\xc1\x80\xef\xb5\x83\x0f\xc2\x0d\x87\xad\x3b\x15\x15\xe9\x0b\x77\xaf\x87\xf0\x6c\x6b\x0e\x71\x29\x71\x8a\x2f\x93\xae\xfb\xd1\x02\x8b\x1a\xc6\x3f\x6b\xd7\xec\xa0\xa0\x02\x69\xc0\x47\x3e\xaa\xc5\x57\x97\x51\x19\x50\xb1\x15\x25\xc2\x41\x41\xcb\x5a\xc4\xcf\xe2\xd9\xfd\xbf\xfc\xbd\xdf\x84\x12\xa7\x0e\xb1\xb8\xf4\x56\x48\x55\x3b\x70\x67\x58\x1b\xc8\xee\x2d\x6a\xa0\x89\xb9\x7e\x40\xdf\xe6\x1c\x33\xfa\xf9\xfc\xd5\x65\x0f\x61\x07\x85\x71\xf0\x3c\x6d\xf9\x4e\x01\xdd\x7f\x90\xf1\xdb\xea\xf0\x42\xd9\xbb\xc8\xb3\x63\x5c\x4c\x89\x93\x28\x52\xb3\x11\xf6\x3f\xf6\x19\x55\x0a\xab\xa0\x0f\x06\x14\x18\x88\x62\x24\xf8\x47\x87\x08\xf9\xec\xdb\xd9\x6f\x0f\x25\x15\x35\x31\x92\xad\x93\xd4\x6c\xfa\x8a\x4b\x3a\xc3\xea\xf7\xab\x9d\x1a\x3c\x4d\xfc\x62\x74\x6c\xeb\x08\x9e\xd3\xab\x40\x51\xae\x09\x27\x4f\x54\xf2\xa9\xc3\x79\xff\xe8\xc8\xc0\x10\x94\x87\xb6\x88\x3a\x48\x49\x41\x5c\x6a\x0c\xcc\xc6\x8b\x30\x96\x93\x8d\x6e\x54\x66\x9e\xda\xf7\xb8\x2e\xc9\x01\xc0\x53\x33\xe6\xc3\x10\x55\x41\xf0\x31\xab\x59\x04\x61\xe7\xf1\xf7\x76\xa2\x93\xe5\x93\xd0\x0d", )?; test( HashAlgorithm::SHA224, b"\x99\xea\x30\xdf\xbb\x1e\xff\x6f\x56\xad\x6e\x0b\x05\x59\x89\xa2\xcb\xa1\x1f\xd3\x9e\x38\x6b\x00\x26\xb5\xf3\xa4\xc2\x8c\xb1\xe6\xcc\x3d\x71\x6e\x1e\xcb\x7a\x77\xd4\x70\x70\x25\x54\x8f\x79\x19\x8c\xea\x9f\x44\x7b\x15\x76\xf8\xf3\x2b\xfe\x45\x9d\xbf\xca\x82\x3d\x15\x62\x2a\x37\x92\xe7\xea\x53\x72\xf5\xf7\xbd\xb9\xcd\xa5\x50\x6c\xb4\x36\x13\x00\x96\xef\x04\x13\xef\x72\x15\x5a\xec\x47\x75\xdb\xcd\xbc\x10\x5c\x8d\xef\x59\x1b\xc5\x29\x47\xbf\xce\x6d\x9d\x8f\x25\x51\x6f\xe2\x14\x0d\xe2\xd6\x8f\xd2\x33\x45\x5d\x5d", n, e, b"\x69\x21\x0e\xe2\x7a\x00\xdf\xbf\xcd\x50\xaa\xf2\xeb\x50\x2c\x57\x06\xdd\xff\x6d\x9d\x23\xfb\x38\xd1\x11\x2f\x25\xc0\x47\xea\xac\x57\xdc\x90\xa6\xda\x67\x38\x76\x31\x9d\x5c\x04\x49\x4e\xce\x80\x37\xc2\xfb\x60\x20\x3c\x9f\x23\x32\x2e\x2c\x20\x63\xfa\x7d\x19\x16\x5e\xdd\xd8\x9e\x1b\x91\x93\x5a\x2b\x50\x02\x1e\x62\x68\x25\xbf\x19\xcc\x46\xaa\xeb\xfa\xb0\x9b\x49\x04\xde\xde\xf8\xc4\x63\x2a\xae\xdb\x42\x9f\xeb\x68\x7b\xba\xc2\xb4\x06\xf9\x23\xff\x1e\x84\x49\x41\xb0\xc0\x2b\x08\xdc\x2d\x8b\x42\x65\xfc\xeb\x61\xa8\x2f\xce\xf0\x62\x4f\x28\xee\xf3\xa9\x19\x3b\x86\xf1\x5f\x7a\xc4\x70\xdf\x59\x0a\xe8\x55\xa7\xaa\x75\x40\x49\x9d\xd4\x6a\x67\x85\x5a\x5b\xae\x6e\xc5\xdc\xa8\xb0\xc1\x6b\xcc\x69\xc0\xa1\xf9\x21\x8e\xc7\xcc\xae\x21\x7a\xc9\xb4\x7e\x8f\x7c\xae\xfc\x1e\x10\x2e\x3b\xdb\x42\xa6\x77\xfa\xbe\x18\x27\x4a\x5e\x69\x44\x7b\x33\x41\x4d\xf5\xbb\x29\xcc\xeb\x2a\xbd\x35\xc9\x4d\x36\x9e\xed\x25\x63\x02\xd7\x58\xdf\x99\x48\xbe\xe4\xef\xbd\xcc\x4a\xe3\x56\xe7\x8b\xe7\x35\xf7\x42\x5b\x64\x43\xcb\xff\x7e\x85\xc6\x53\xa6\x66\xde\xd2\xe7\x4e\xc7\xf6\x11\x03\xd6\xe8\xba\xc1\x10\xb1\x57\xae\xbf\x61\xce\x32\xf8\xb6\xf5\x67\xac\xbe\x92\xf6\xe3\xe2\x6e\xfd\xd3\x94\x2a\xf6\xc2\x79\xc2\xc7\xb4\xf1\x83\x98\xcc\x0a\xb4\xe2\x76\x88\x1b\x60\x46\xcc\x55\x25\x94\xcd\x96\x56\xf2\x2c\x3e\xe4\x98\x07\xcc\xe0\xf0\x9f\x2b\xfa\x7a\xbb\x87\x97\x27\xb7\x34\xdc\x19\xc4\x68\xf4\xaf\x4d\x72\x0d\xa8\xff\xd6\x50\xcd\xd6\x93\x82\x49\xb6\xa4\xc8\x47\xa5\x13\x83\x88\x8d\x12\x92\xa6\x16\x32\x22\x12\x6d\x5a\x42\xdc\xa6\xfb\x22\x83\xe7\xbb\xb6\xc2\x0d\x7b\x60\xb1", )?; test( HashAlgorithm::SHA224, b"\x1e\xe4\x3d\xe2\xd8\x79\x7e\x65\xbf\xaf\x52\xc2\x5a\x0b\x00\xb6\xc0\x4e\x0e\x40\x46\x92\x05\x56\x5a\x96\x74\xd6\xaf\x57\x37\xbf\x9b\xe0\xf9\xa1\xbd\x62\xf8\x52\xe2\x87\x08\xe3\x29\x04\xdb\xd2\x66\x6e\x7f\x81\xc5\x4e\x28\xe7\xb0\x77\x30\x86\xf2\x97\x5c\x9d\x1c\x0d\x61\x9e\x27\xfa\xa7\xe2\x5c\x9b\x4c\x9c\x71\xd6\x6c\x0c\xf5\x12\xa0\xf5\xee\x4c\xc4\x50\xc0\x67\xfe\x42\x50\xc4\xfb\x4c\x6a\x13\x7c\xc2\x60\x69\x12\x7e\xf4\x92\x25\xd5\x78\xa8\x3b\xca\x34\xe4\x77\x82\x08\xb5\x60\xf8\x53\x0f\xe5\xf2\x13\x06\x9d\x34", n, e, b"\x3d\xd7\x22\xd2\xf0\x54\x3e\x66\x74\x3f\x8c\xdb\x60\x34\x1d\x61\xfd\x7b\x6e\xf8\xcb\x23\xa9\xe9\xf3\x40\x57\xd7\xa0\xaf\x49\xe3\x08\x26\xaa\x0a\xaf\x1f\xd3\x4e\xfe\xbd\xbf\xc9\x3a\xe5\x21\x27\x11\xa1\x60\xf2\xb8\x78\x6f\x4f\x5b\xec\xc4\x92\x09\xbd\x05\xdd\xf8\xde\x9f\xec\xd0\x0a\xf5\x30\x4d\x66\x15\x27\x2f\x2e\x49\x40\xbc\x8c\x39\xc2\xfb\xc6\x36\xf8\xc1\x05\x56\x5e\xc0\xf1\x57\x00\xcd\xb0\x66\xc5\xca\x1f\xd0\xe3\xe3\xf4\x94\x52\xe4\xf6\x71\x5a\x58\x22\x27\xd5\x9e\xc1\x04\x57\x5c\x17\x4f\x8c\xd1\x3e\xca\xbc\x4d\x58\x99\xe0\x2e\xbd\x3e\x81\xbd\x2c\x00\x32\x42\x73\x8b\x3b\x95\xb0\xe0\xcf\x0e\xf0\x2f\x8e\xe0\x28\x96\xdf\x64\x60\x68\xae\x23\x3f\xfc\x44\x36\xf1\xe9\x7d\x37\xd4\x5d\x49\x7e\x1a\x54\xa0\xd6\xfc\x5a\xaf\x27\x5e\xc5\x0c\xbf\x0b\x40\x20\x52\x20\x0f\x6b\xc3\x53\x73\x82\x8b\xcd\xb4\x8a\x17\x8c\x96\x88\x65\x8a\x23\x63\xa8\x68\x3a\xb9\xea\xfa\x97\x90\xee\xf2\xc7\x9d\xa1\x48\xa9\xd9\x95\x39\x5d\x9f\x6a\x7b\x31\x0f\x6f\x71\x41\xd3\xcb\x0f\x20\x6e\x8b\xaa\x82\xa3\x38\xd5\x19\xee\x88\x1c\xf6\x1d\x5e\x1f\x90\x6d\x42\xc2\xe8\x5f\x25\xcd\x19\xd9\x86\x4a\xb5\x4a\x32\x96\x9c\x8e\xdf\x29\xe5\xac\x52\xf6\x20\x06\xd9\x21\x9c\x21\x14\x00\x07\xb0\x5c\x63\xe3\xba\x4c\x04\xec\xe5\xd8\x80\x50\x26\xdb\xe8\xff\x66\x52\x52\xd5\x37\xd0\x13\xf7\x09\xd8\x49\x99\xf8\x4b\x43\x82\xa8\x94\xc1\xba\x03\x18\x49\x37\x83\xa5\x98\xf6\x37\xbc\x2d\x8d\x56\x78\xcf\x65\xd0\x38\x33\x80\xad\xa0\xdb\x5a\x51\x07\x37\xa8\xb7\x0c\x3b\xae\xee\xe4\x70\x85\x08\x8e\x96\xd9\x94\x38\xba\x5e\x98\x87\x88\xf2\x88\x6a\xa7\xe2\x95\xd8\x57\x8e\xb2\x7f\x1d\x68\x38", )?; test( HashAlgorithm::SHA224, b"\x74\x03\x22\x95\x3b\xfc\x8e\x84\x0c\xec\xd9\x96\x3f\x58\xbe\xa7\x0d\x2b\xd2\x0a\x50\x63\x66\xd7\x8a\x0b\xad\x86\x29\x69\x22\xbd\x09\x78\x24\x67\x3b\x99\xe3\x06\x05\x85\x80\x4a\x29\x86\x70\xe2\x6a\xe7\x22\x92\x4d\xa8\xe1\x86\x1d\x77\xfb\xe6\x31\xdc\x23\xaa\x72\xb4\x14\xb0\x17\xe0\x77\x0b\xb3\x3e\x07\x9f\x72\xd8\xf3\xeb\x9f\x5c\x83\x25\x6a\xcd\xff\x8c\xe9\x77\xcd\xfa\x5c\x28\xd9\xb8\xb5\x9d\x3a\x97\x58\x3b\x12\x3c\x1f\x00\xb5\xbc\xa1\xb8\x0e\x69\xb4\x74\x3f\xeb\x30\x38\x88\x92\xf6\xf4\x6a\xea\x31\xb5\x0c\x90", n, e, b"\x7c\x41\x48\x40\x91\x0c\xa0\x8f\xec\xd2\x3f\xf1\x2c\xee\xbc\xd4\x8b\x7a\xfa\x4e\x6a\x87\xa4\x06\x54\xba\xae\xc6\xc9\x05\x00\x87\xb1\xf0\xb6\xfa\x04\xe3\x6c\xd5\x95\xad\x29\x3d\x08\x27\xe9\xe1\xc9\x4f\xe0\x33\xec\x42\xbb\xd0\x21\xf7\xce\x2e\x75\xda\x6d\xd2\x06\xb9\x91\x51\x76\x8d\x6d\x5a\xe7\xb1\xf0\x44\x16\x80\x4c\x2a\xd7\xc6\x74\x4c\x73\x43\xc8\xf0\x1b\xe2\x59\x36\x1c\x11\x68\x10\xf0\xad\xa1\xc6\x43\x48\x05\x5b\x25\x94\xa0\x6b\xdc\x08\xdb\x39\x0d\x75\x0e\x4a\xee\xa5\x43\x59\x32\xa0\x4d\x0e\x69\xd5\x90\x63\x04\xc8\x4e\x19\xd5\xfb\x86\x88\xca\x25\x98\xb6\xfa\xe6\xd1\x69\x59\x3f\xac\x29\x09\x23\x8c\x55\x3c\x66\x4d\xe9\x2c\xba\x6d\x89\x15\xe0\x1a\x6e\x99\xd8\xd9\x2f\xec\xbc\x6e\xae\xfd\x93\x15\x1c\x61\xfb\xbd\xe2\xea\xcf\x26\x34\xe7\xb6\x11\x6a\xd2\xfe\x88\x59\xb6\x5a\x70\x66\xd7\xb5\xb7\x76\x38\x65\x0b\x60\xa4\x3d\x82\x77\xda\xb0\xac\xa1\x45\x06\x5b\x3c\xf0\x0d\x96\x3b\x7f\x81\x8d\xda\xdd\x7c\x54\xbe\x5b\x4b\xa7\x69\xae\x01\x34\x46\xa5\x74\xdb\xbb\x8f\x7c\x22\xb2\xd1\x54\x3e\x7b\x5e\xc0\x8d\xfd\xe3\x8e\xf9\xad\x84\x3c\x1b\xb6\xd9\x55\x8a\xef\xcd\x45\xd3\xb1\x2c\x82\x06\xb7\x92\xca\x72\xbf\x49\x50\xbe\xfb\xee\xc0\x4f\xc1\xa2\x8c\x37\x20\x58\x85\x13\xa2\x9a\xf9\x69\x1d\x2f\x31\xdd\x7d\x39\xa5\x6b\xcb\x5f\x49\x9f\xb1\x4c\xa4\x7f\xa5\x41\xe2\xea\x67\x84\x33\x99\xe0\xc8\xab\x89\xc8\x1e\x58\x93\x41\x59\x42\xbf\xe4\xe4\x70\xa6\x78\xc0\xe5\x61\xed\x64\x55\x47\x11\xb1\x6b\xe3\x35\x0c\x98\x5b\x61\xf2\x92\x19\xc5\x27\x4d\x87\x93\x08\xdd\x25\xfc\x03\x3f\x81\x9c\x38\x59\x04\x65\x43\x99\xe5\x43\x8f\xd9\xc8\xcf\x1e\xc7\x6e\xcc", )?; test( HashAlgorithm::SHA224, b"\xf7\xe3\x78\x20\xa1\x9d\x5f\x6a\x05\xeb\x47\x79\xc2\x40\xe7\xfb\x58\x6a\xe8\xc3\xdf\x71\x3b\xcd\xf9\xc2\xaf\x7c\x05\x8c\xc3\x27\x95\x6b\xb8\xd4\x22\x44\xeb\x43\xff\x70\x62\x2f\x8c\x1c\xa5\xd0\xac\xef\xcf\xa4\x79\xee\xe4\x6f\x36\x9d\x65\x81\x84\x67\x22\x37\xd9\x40\x50\xc4\x2f\x89\xdb\x31\xf9\x34\xfe\xa3\x5b\x28\x10\xdd\x9a\xe7\xa1\x05\xd2\x6e\xc5\xab\xe7\x5d\xb0\x07\xbd\x57\x83\x82\xac\xac\x66\x79\x2e\x35\xd7\x3d\xdb\x80\x41\x5e\x98\x2d\xd1\x29\x0b\x98\x85\x6f\x52\xb9\x86\x88\xf4\x48\xb7\x98\x17\x24\x8e\x11", n, e, b"\x56\x3e\x22\x61\x7d\xd8\x89\xe7\xbe\x8d\xd2\x6a\x17\x6e\xe9\xf6\x7b\x9b\x3e\xb0\x40\xad\x7a\x7f\xab\xc0\x89\xb2\x7e\xd4\xe7\xa7\x82\xf1\x52\x2b\x44\x6f\x42\xa5\x67\x49\x21\x37\x77\x0c\x61\x2d\xc5\xe4\x28\xec\x28\xa3\xc5\x02\xaa\x25\x08\xfb\x46\xb7\x03\xd7\x9d\x1f\xde\x8e\x1a\x50\x7d\x70\x62\xe2\x64\x40\xb3\xa3\xff\x16\xbc\x82\xfc\xc9\xb3\x01\xf2\xb5\x8f\xa8\x18\x52\xb5\x7f\x95\x1d\x92\x51\x64\xbe\x0c\x70\xbd\x28\x1d\x72\x6c\x9a\x71\xb9\x84\x28\x03\x52\x28\x9f\x8c\x1b\x39\x4a\x85\xdf\x9e\x17\x32\xa4\x53\x9a\x30\xa7\x59\xe8\xf1\x26\x09\x6b\xf7\x3f\x7b\x25\xa5\xed\x34\xc3\x2a\xf3\x45\xbc\x32\xe4\x12\xe0\x8b\x6c\xa9\xb6\x56\xa6\x92\x85\x19\x65\x5e\xc9\x76\x9c\xf1\xda\xe7\xc9\x85\x50\x5a\x81\x2e\xe4\x4b\xb3\xb4\x2e\xcb\xec\x91\x1b\xec\xed\x8f\xe8\x73\x65\xf1\x13\xaa\xc0\x0a\x65\x9c\x0e\xb3\x7b\xfe\x75\x36\xf9\x17\x6a\xfe\x9c\x45\x9a\x08\xae\x23\x60\x0d\x4c\x85\x43\xef\x3c\x3a\xf4\xcd\x10\x11\xe0\x8f\xdc\xf1\x99\xba\x49\x02\x4f\x08\x80\x8c\x47\x59\x86\x87\x05\x61\xd6\xa0\x88\xb7\x9c\x38\xae\x8c\xe0\xe6\xec\x40\x26\x8b\xc9\xfb\x7a\x3b\x61\x85\x87\xf5\x5f\xbc\xd3\x1c\xea\x93\x70\x24\x38\x65\x49\x2e\x5f\x13\xc9\xfd\xad\x61\xf4\x0b\x32\xd3\xa9\x15\x19\x42\x44\x94\x9a\xdd\x15\x02\x6c\x0a\xe1\x9f\x52\xad\x5b\x70\x36\x5e\x77\xf2\xcf\x53\x29\x8c\x9e\x2b\xad\x06\x17\x1b\x09\x08\xdf\x26\xb2\x2e\xf1\xc7\x37\xc3\xb3\x21\x39\x5f\xfc\xdb\x71\xc8\x22\x8f\xe9\xde\x02\x7f\x0d\x31\x06\x86\xb1\x68\x3a\x67\x41\x9e\xa0\x89\x71\xcf\x0b\xf1\xa3\xe5\xa1\x07\x27\x24\x83\x46\x01\xd5\xf9\x44\xfa\x23\xf7\x7d\x8e\x77\xe8\x87\xf8\x8d\xdb\xee\xb1", )?; test( HashAlgorithm::SHA224, b"\x87\x10\xa8\x77\xb7\xa4\xc2\xe5\x78\x79\x3b\xd3\xe4\xd1\x9c\xb5\x6d\xe9\x7f\xcd\x1f\x2a\xf5\xfb\x25\xa3\x26\xd6\x8f\xb7\x37\xfb\x52\x13\x71\xa6\x90\xe4\x9f\x7f\x1a\x46\xb7\xb6\x34\xff\xbd\x51\x98\x6d\xe5\xc5\xbd\xbd\xf8\xc4\x58\x5e\xf8\x57\x24\xb5\x07\x2c\xde\x13\x85\x31\x94\xe4\x79\x62\x20\x29\x32\xde\xf0\x28\x2e\x41\x08\x61\x3a\x2e\x49\xc5\xdb\x2b\xf3\x23\xed\xb2\x69\xe3\x8a\x84\x34\xf6\x2d\x41\x4b\x0d\x17\x36\x91\x09\xf2\x76\xa0\xb3\xb5\x2c\xc5\xae\xc7\x2f\x4b\xaa\x67\xd7\xfd\xd9\x4b\x10\xe6\xa7\x87\xac", n, e, b"\xa7\x83\x58\xef\x28\x30\x3d\xeb\xa1\xbf\x1b\xc3\xca\xe5\x9a\xb0\xff\x66\x14\xc5\x20\xee\xb7\xd8\xc8\xfd\x5c\xed\x34\xda\x74\x54\xad\x14\x0b\x53\x9e\xf7\x5e\x2d\x65\xdd\x89\x1e\xbf\x89\x9a\x88\xad\xa2\x5b\xcc\x35\x72\x60\x53\xda\x68\xe2\xe0\x2b\x6a\xcd\x2e\x7e\x21\xcb\x8b\x37\x35\x5d\x19\xbd\x4c\x3e\x36\xa8\xc1\x64\x7e\x1a\x38\x4c\x8a\xd2\xab\x39\xbd\x22\xf3\xd3\x0f\x0f\x9d\xd6\x85\xfe\x4d\xd7\xf8\x36\xec\x46\xbb\xce\xf0\x80\x5d\x08\xa7\x84\xa6\x96\x4c\xd5\x0f\x58\x07\x1e\xd7\x9f\x88\x24\x91\xa3\x31\xb4\x45\x39\x0b\x43\xf2\xa2\x95\xa1\x3a\x28\xce\x0f\x44\xbb\x6d\x63\xf3\x19\xd8\xde\x90\xe3\x90\x17\xf4\xcb\xc1\x45\x33\xda\x33\x38\x0f\x55\x3f\x09\x7e\x79\x6a\x67\x1b\xa2\x9c\x94\x58\x2c\xd5\x19\xf1\xf6\x4d\xb3\xbe\x89\x4b\x66\x15\xf6\x84\x4f\xf2\xfc\x62\x10\x13\x82\xb0\x44\xf5\x85\x6b\x9b\xb9\x78\x71\xcf\x13\x7c\x4e\x9e\x48\x4e\x84\xa3\xcd\x2d\xae\xa8\xe1\xc6\x35\x8d\x66\xcd\x83\x26\xc1\x92\x5c\xe1\xf7\xd2\xd2\xe9\x04\x57\xad\xaa\x65\xec\x3a\x67\xf4\x86\x5b\xf6\x12\x0e\xff\xa0\x6a\x79\xde\xb6\xb6\xca\x9f\x85\xc9\xdd\x96\x7f\x2f\x31\xa2\x2d\x5d\xb2\x5b\x15\x53\x0a\x9e\x85\x0a\xca\x48\x6b\xc0\xca\xc2\xbe\x6b\x0a\xf6\x6e\xcb\x56\x8c\x09\x55\xa3\x04\x95\xbd\xd5\xd0\x5a\x22\x0c\xd0\x6c\xb0\x6f\x04\xf2\x16\x07\x6a\xaa\xd4\x38\x2a\x94\x04\x0d\xcc\xda\x68\xa1\x9d\x55\xb4\x93\x38\xc9\x31\x5a\xa8\x02\x91\x06\x55\xfe\x93\x94\xaa\x73\x59\x0a\x6b\x2a\x04\x39\xbb\xef\x5e\xc7\xcc\xb5\x20\xf2\xc5\xcb\x71\xd3\x93\xa6\xcc\xe2\x5b\xf7\x7d\x80\x33\x44\x4f\xb3\xda\x8a\xc8\x61\xc6\x3d\xc2\x56\x1f\xfd\xcc\xe8\xc2\x06\x5b\x35\xb5\xc8\x3b", )?; test( HashAlgorithm::SHA256, b"\xbc\xf6\x07\x43\x33\xa7\xed\xe5\x92\xff\xc9\xec\xf1\xc5\x11\x81\x28\x7e\x0a\x69\x36\x3f\x46\x7d\xe4\xbf\x6b\x5a\xa5\xb0\x37\x59\xc1\x50\xc1\xc2\xb2\x3b\x02\x3c\xce\x83\x93\x88\x27\x02\xb8\x6f\xb0\xef\x9e\xf9\xa1\xb0\xe1\xe0\x1c\xef\x51\x44\x10\xf0\xf6\xa0\x5e\x22\x52\xfd\x3a\xf4\xe5\x66\xd4\xe9\xf7\x9b\x38\xef\x91\x0a\x73\xed\xcd\xfa\xf8\x9b\x4f\x0a\x42\x96\x14\xda\xba\xb4\x6b\x08\xda\x94\x40\x5e\x93\x7a\xa0\x49\xec\x5a\x7a\x8d\xed\x33\xa3\x38\xbb\x9f\x1d\xd4\x04\xa7\x99\xe1\x9d\xdb\x3a\x83\x6a\xa3\x9c\x77", n, e, b"\xd1\xd2\x1b\x8d\xfa\x55\xf0\x68\x1e\x8f\xa8\x61\x35\xcf\x29\x2d\x71\xb7\x66\x97\x13\xc2\x91\xd8\xf8\xdc\x24\x64\x64\xde\x3b\xbb\x96\x1b\x59\x6d\xfc\x8f\xda\x6c\x82\x3c\x38\x40\x08\xd0\x5b\xcb\x3d\xcc\xc3\x6a\xcc\xf1\xb2\xbe\xde\x1a\x95\xe5\x22\x58\xd7\xd1\xbd\xf1\xfc\x44\xe1\x80\x72\xab\xd4\x5c\x13\x92\x01\x5e\xe7\x16\x92\x69\x0e\xf8\xcd\xaa\xed\x33\x7d\xd8\x54\x67\x83\xf9\x61\xbb\x96\x20\xeb\x5c\x7b\x8b\x67\x16\xe8\xc6\x00\x35\x1f\xab\x77\x65\xee\x38\xa1\x5d\x32\xd8\xa2\xc0\x94\x98\x25\xc4\x9a\x7f\x25\xee\xdd\x9b\xe7\xb8\x07\xbb\xfd\x51\x79\x13\x78\x66\x20\xd2\x49\x82\x3d\xae\x6f\xe2\xfd\x39\xac\x63\x9d\xd7\x48\x21\xb0\xc1\x20\xb4\x2f\x31\xc2\xc6\x39\xd2\xc6\x1b\x39\x5f\x09\xf8\x68\x51\xbc\x80\x9b\x34\xc4\x98\x1a\xc6\x5c\xf2\x5b\x2e\x8a\xdc\xbc\xe1\x90\xef\x2e\xf6\x7a\x01\x89\x03\x9c\x91\x10\xf2\x67\x01\xc3\xee\xd7\x31\xc8\xd9\xea\xd1\x78\x22\x0f\xfc\xac\x7f\x0f\x67\x8a\xa2\x22\x68\xe1\xd0\x19\x42\xec\x51\xe8\x0e\xef\x06\xe2\x11\x28\x30\x85\x5e\x87\xba\xfe\x8c\xc9\xc2\x2f\xd7\x37\xc7\xab\xbc\xa5\xeb\x7a\x22\x1d\x38\x35\xa8\x66\x10\xd2\x4b\x50\x7b\x5d\xcb\x46\x18\xaa\x42\x1f\x63\xa5\x60\x9e\xf5\xd6\x8f\x57\x60\xfd\xdf\x97\x01\x35\x60\x2e\xfa\xd0\x85\x1b\xbf\xf9\x8f\xe8\x7f\xa5\x8b\xc3\x65\xf3\x8e\xe7\xec\x8e\xf5\xaa\xb1\x7f\xd1\x1d\x89\xd9\x1e\xf4\xc6\x04\xe0\xd1\xf0\x01\xd0\xe0\x88\x69\xdf\x92\x25\xe3\xb4\xce\xf5\x2f\xf8\x68\x15\xe1\x3b\x3e\xfd\xf4\x57\x76\xf9\x35\x37\x69\xa8\xa5\x1f\xe7\xd8\x91\xa7\xef\x70\x35\xee\xcf\xa2\x59\x84\x87\x38\x37\x68\x86\xed\xc9\x1c\xc7\x8f\x6d\xa3\x1c\x2f\x07\xee\x36\x2c\x3d\x82", )?; test( HashAlgorithm::SHA256, b"\x2b\xca\xd6\xe7\x44\xf2\x49\x0b\xa6\xa6\xe0\x72\x28\x32\x41\x7e\xbd\x91\x0f\x91\x46\xeb\x62\xba\xaa\x5c\x74\x95\x29\xf7\x9d\x6c\xed\x0b\x81\xa2\xe2\xa4\x88\x52\xc8\x55\x8e\x33\x87\x35\xdc\xbf\xc2\x28\x57\x94\xae\x60\xf8\x1a\x25\x23\x7c\x66\xf6\xce\x5d\x5e\x80\x1a\x00\x1e\x7f\x9e\x30\x9b\x25\x95\xcb\x86\x6d\xe2\xbb\x74\xac\x51\x28\x3b\x68\x20\xec\x9f\x6e\xbe\x48\x2e\x1f\xd2\xd5\x68\x0b\x7f\xbd\x23\xc1\xe6\x2a\x2e\xe4\xed\xff\x35\x82\x3f\xc7\xe4\xa2\x95\xea\x4f\x1c\x33\x27\x92\xae\xb5\x3e\xb4\x4b\x0b\xed\xd2", n, e, b"\x37\xd9\x60\xfe\x39\x12\x98\xbb\xdc\x22\x3f\xa1\xeb\x1d\x3c\xd9\xa4\x6b\xa8\xc6\x2e\x1d\xa8\xc5\x63\xc8\x9a\x8f\x0e\x67\xb8\x64\xfc\x89\x83\x7f\xfc\x08\xaa\xb7\x12\x2b\x84\xc4\x35\xc7\xf9\x40\x6e\x16\x5a\x10\x29\x85\x7c\x1e\x4d\xea\x65\x35\x69\x27\x72\x73\xb1\xd9\xb0\xa9\xf5\xb0\xdc\x24\xaf\xdd\x21\x44\x76\xd4\x72\x08\xad\x52\x21\xa7\xd7\x93\xca\xb8\x06\x71\xfb\x49\x87\xc8\x6b\xd6\x14\x48\x80\xc5\x9d\x24\x87\x14\x00\xf6\x4b\xdc\x6d\x49\x6d\xbd\x49\x7f\x3d\xbf\x64\x28\x64\xfe\x49\xaf\x3e\x21\x51\x5e\x62\xd6\x0f\x00\x71\xdb\x48\x84\xf4\x96\x70\xea\xa9\xe4\xe4\x98\x2f\x26\x9a\xbe\x72\x42\x44\x28\x88\x59\xc2\xad\xf6\x0a\x09\xfa\xaa\xbb\x07\x99\x0e\x09\xe5\x6d\xe2\x54\xba\xbb\xee\x14\xbe\x7e\xb6\xed\xa0\xcd\xb2\x2f\x3d\x0d\xe8\x72\x48\x04\x67\x3f\xb9\x9f\x86\xef\xb4\x26\x3d\xcc\x50\x17\xab\xc9\x1b\xd9\xcd\x83\x36\x79\x47\x5b\xfa\xc5\x0a\x2b\xe8\xdb\x86\x29\x6b\xbf\x80\x17\x88\x93\x57\x37\x13\x14\x60\x4e\x83\xd6\x8b\x6e\xfe\xcd\x4b\x79\xf0\xa8\xaf\xa0\xdf\xfa\x44\x8f\xb7\xfc\xe6\xd3\x44\x70\x9a\x67\x0e\x0c\xff\x43\x2c\x3e\x18\x7b\xcf\xf7\xfd\xc4\xf4\xe9\xab\xe1\x09\x5c\x46\xb0\x1d\x88\xb6\x04\x4b\xb9\x50\xe9\x28\x59\x01\x0d\x9a\x0e\x3b\x2d\x1f\x27\xa0\x96\xea\xca\xa2\x42\x63\xa2\xa0\x52\x3d\x6e\x0d\xa1\xfb\xa8\xaf\x76\x81\x96\xf7\xa5\x1f\x92\xfd\xf1\x52\xbe\xf0\x62\xdd\x1f\x83\x27\xce\xe1\xd3\x44\xc2\x00\xc2\x11\x5a\xc6\xec\x1d\xd8\x51\x4c\xef\x9e\x36\xd0\xce\x8c\x32\xe5\x87\x83\xc4\xfc\xba\x90\x1a\xa7\x0c\x2b\x42\x96\x64\x88\x00\x2f\xf1\x71\xd3\x64\x14\xa1\x44\xbf\x46\x77\x51\x83\xa8\x81\x5d\xe9\xee\x3e\x81\xf3\x1b", )?; test( HashAlgorithm::SHA256, b"\xc3\x97\x8b\xd0\x50\xd4\x6d\xa4\xa7\x92\x27\xd8\x27\x0a\x22\x02\x95\x34\x82\x87\x59\x30\xfb\x1a\xea\xe4\xe6\x7f\x87\xe7\x94\x95\x28\x9d\xe2\x93\xb4\xa4\x0d\x92\x74\x6f\xc8\x4c\xc8\x31\x8c\x23\x18\xfd\x30\x65\x0e\x2b\xb9\xce\x02\xfd\x73\x4e\xb6\x83\x41\x0d\x44\xbb\x31\xad\x54\xfd\x53\xcf\x92\x96\xcc\xd8\x60\xb4\x26\xf5\xc7\x82\xea\x5c\xb4\x93\x71\xd5\x61\x84\xf7\x79\x11\xdd\xf1\xba\x00\x39\xa0\xa4\x9a\xa7\xe7\x63\xeb\x4f\x5a\x04\x57\x59\x97\x80\x8b\x0a\xd9\xf6\xb3\x30\xca\x38\xed\xc1\x99\x89\xfe\xbf\x4d\xa5", n, e, b"\x9a\xed\x20\xa8\xbd\xaf\x26\xf1\xf1\x19\x02\x0d\x8f\x3e\xa6\xce\x91\x51\x38\xd4\xc8\x7d\xce\x02\x5e\x7f\x4e\x49\x53\x6c\x8e\xc0\x79\xed\xc6\xca\xf0\xd6\x03\xbf\x42\xbd\x6a\x45\x4a\x6d\x52\xd0\xd9\x9f\xd0\xf5\x9f\xfb\x3b\x22\xe9\xe6\x7b\x3d\x0b\xb2\xd2\x75\xd9\xae\xdc\x6d\xa9\x6a\x72\xcb\xff\x35\xc4\x3e\x7f\x39\xa9\x96\xfa\x8a\x6d\x33\x8a\x07\x25\xf7\x85\x25\x4f\xe9\x1a\x20\x83\x4b\xa5\x57\xfe\xdf\xe7\x15\x2b\x99\x56\xfe\xdd\xfd\x94\x17\x41\xef\xf9\x17\x7c\x2f\xbb\x55\xe2\x00\xbb\xe4\x21\x62\xb3\x2a\x94\x0c\xc3\x00\xab\x37\x55\x57\xdf\xfd\x48\xdf\xa5\x39\xf5\x0e\xdd\x52\xdf\x15\x8d\x90\x72\xd1\x49\x82\xe9\x63\x03\xbc\x61\x2c\x2c\x25\x06\xdb\xca\x3a\x93\x9d\x62\x6d\x2e\x7f\xb4\x44\xc6\xad\x7d\x8d\x9f\x3b\xba\x82\x10\xb2\xac\x2f\x69\x67\x83\xc3\x49\xfc\x52\x80\xc1\x05\x40\x2a\x4b\x3d\x86\xbe\xf5\x02\x6c\x3d\xd9\x99\xe3\xb2\x23\x80\xf9\xdc\xce\x40\xe3\xa9\xcc\x9f\x1d\x7b\xc3\x8e\xf3\xdd\x7e\x94\x13\xbb\x57\x98\x00\xc0\xe6\xc3\xe9\xab\x91\x2d\xa8\xfe\xc1\xa4\xab\x21\x39\x8e\x96\x80\xba\x0d\x04\xf3\xb4\xc8\xd5\x3c\x02\xf0\x5c\x7a\xe4\x9b\x70\xa5\x61\x1c\xf8\x2e\x38\xde\x84\xaa\x8c\x24\x26\xf0\xb6\x3e\xa0\x1b\x28\x9f\x20\x1d\x3a\xf4\x0d\xad\x5d\x6e\x5b\xcc\xc7\x5b\x99\x59\xe5\xc9\x75\x8e\x79\x10\x5a\xf7\xa9\xaf\xb1\x2a\xee\x57\x7c\xb3\x99\x18\x79\xdb\x0f\xd8\x66\x2c\x5b\xc4\x90\x22\x75\x24\x98\xa3\x01\xd9\x5f\x4b\x1d\x08\xc0\x1e\xbc\x31\x3f\x89\xc0\x0b\x1e\xc2\x73\x5a\x07\x98\x3f\xd5\x28\xe6\x38\x82\x45\x03\x6f\x0e\xd4\xa2\xdb\xb6\x5d\xd3\x3a\xb7\xf1\x24\xc0\x14\xec\x16\x79\xf1\xc2\xf1\x1e\xdf\xfb\x93\xfa\x2d\x1d\x73", )?; test( HashAlgorithm::SHA256, b"\x0c\x11\x95\x02\xc2\xa0\x19\x20\xa0\x90\xe4\x33\x57\xe7\xb2\x8e\x33\xc7\xee\x85\x8b\x43\x30\xe0\x5c\x71\x04\x89\x31\xc0\xed\x88\x46\x8c\xa9\x31\xec\xf0\xb7\x9c\x2f\xdc\x17\x56\xb7\x67\x51\x56\xec\x66\xb8\x33\x5e\x3d\xf0\x94\x63\xf5\xae\xe7\x02\x8f\xbf\x56\x0f\x98\x4c\xf6\x98\xfe\x5c\x42\x80\x22\x9a\xc9\x6a\x2e\x59\x23\xd8\xa9\xd5\x29\x94\x49\xbb\x66\x50\x08\xec\xc8\x89\x79\x7e\x9b\xb1\x5d\x04\xb8\x8c\x72\x10\xfa\xdb\x8b\xf6\xf2\x38\xe5\xd2\xdc\x41\xb9\xcc\xd1\xf8\x0e\x9a\x3e\x6a\xd1\x47\x94\x8f\x27\x33\x41", n, e, b"\x8a\xbf\x2a\x30\x77\x4e\x6e\x73\x38\xec\xa0\x9c\xcc\xac\xa3\x68\x43\x99\x94\x04\x92\xfb\x94\xb2\x3b\x5a\xd6\x2c\xe3\xe1\x1d\x2d\xbe\xf8\x96\x6b\xa5\x26\x99\x79\xeb\x96\x53\xba\xad\x71\x95\x16\xd3\xe8\x39\x90\x79\xa2\xf6\x70\x27\x5a\x2e\xd4\x2c\x82\x0a\x9a\x31\xfc\xd7\x03\xa7\x66\x37\xe0\xd7\x13\xf3\x2d\x79\x2b\x9a\xe3\x6d\x72\x88\xf6\x0c\x2d\x1a\xe5\x26\x83\xbb\x15\x94\x1b\x1c\xd8\x90\xd2\xcd\x64\x99\x8b\x77\x25\x85\xe7\x60\x32\xa1\x70\x2e\x06\x52\xcb\xf2\x59\xa1\xce\xae\x69\x5d\x40\xcf\x2f\x4f\x6d\x81\x34\x1c\x8b\xc9\x08\x2c\xb9\x6c\x75\x2c\x35\x5d\xfb\xe2\x96\xdd\x21\xd6\x98\x46\xfa\x37\x61\x3e\x73\x81\x7b\x2a\x07\x04\x66\x58\xc9\xe3\xfc\x6d\x09\x1e\x17\x59\x1b\xb1\xa4\xfb\x6e\x2a\xc0\x0a\x31\x94\xc1\x48\x8e\x16\xa9\xd2\x90\x37\x86\xdb\x86\xae\x90\xe9\x6a\xcb\x4d\xe9\x90\x1a\xaf\x1b\x06\x51\xfb\x76\xa5\x8d\xcb\x3d\xb4\x73\xef\xbf\xb8\x31\xef\x8e\x30\xf8\x99\x67\xdd\xd3\xa6\xc2\xf1\x89\x79\xa0\x45\x06\x57\xcd\xae\xef\x6e\x59\x37\x7c\x6d\xb1\xec\x46\x06\x5f\x61\x40\x24\xa6\x9c\x51\x8a\x55\x99\x42\x59\x4a\x46\x26\x6e\x0d\x3c\xa1\x33\x42\x96\xb9\x68\xa2\x3a\x4b\x11\xc6\x3a\x97\xe2\x9e\xb1\x6b\x24\xc0\x2d\x54\x5d\x5b\x42\x7e\x6a\xa5\x85\x33\x33\x18\xe6\x3a\x20\x45\x24\xe0\xe4\x2a\xc1\xed\xb7\x0d\x34\x56\x78\x0d\xbe\xad\x31\xf7\x85\xf0\xb2\xa7\x7f\xfe\xb0\xd3\x73\x84\xcb\x5f\x65\xb4\xe3\x6c\xa2\x41\xf3\xb2\xb0\x59\x10\x5f\xaa\xa3\x22\x2d\x6c\x13\x5e\xa5\xa3\x66\x51\xae\xa3\x96\xd2\x2f\xc4\xea\x1b\x40\x4d\x7e\x83\x4b\x6d\xf1\xfb\x83\x8b\xb5\xba\x0d\x78\x4a\x96\xe2\xae\x28\x43\xdb\x3e\xee\xa4\x96\xc7\xad\x2b\x42\x41", )?; test( HashAlgorithm::SHA256, b"\xdd\xbd\x84\x68\xbd\xb0\x36\xf4\x79\x9f\x42\x8b\xc8\xb4\x37\x4e\xd9\xb7\xcd\xe5\x41\x33\x7a\xc4\x39\xd4\x41\xac\x06\x14\xcb\x75\xb8\x16\xb8\x0c\x17\xd2\x37\xb8\xdb\x73\xd4\xa1\x1b\xfd\x92\x92\x08\x33\x3a\xfe\xdb\xb8\xf2\x41\x0c\x74\x11\x29\xc5\x39\x32\xb5\x96\xa7\x88\x1c\x6a\x4d\x71\x11\xba\x10\x4d\x46\x00\xd1\x90\x2f\x6f\x4a\x16\x08\xe1\x39\xb7\x19\x11\xc1\x1c\x39\x0a\x0d\xd0\x91\xdf\x36\x9a\xa2\x9d\x67\x0b\x8a\x7e\x3f\x53\x82\x5f\x76\x59\xac\x74\xc4\x0a\x0c\x3b\xfe\xf0\xd3\xae\x83\x07\xe4\xbd\xd6\xcd\x91", n, e, b"\x4e\x37\x7e\x24\x59\x81\x5d\x5b\x33\x91\x5f\xa6\x3c\xd4\x77\xb5\xbe\x7c\x6b\x7f\x78\x14\xd1\x35\x00\x34\xce\x71\x0b\xe6\x7e\xd6\x91\x39\xdb\x62\x2e\xf6\x0e\xc6\xb7\x63\x8e\x94\xb2\x02\x36\x8b\xac\x63\x1e\x05\x77\x02\xb0\xe6\x48\x7b\x32\x4a\x6b\x98\xed\x7e\x03\xd1\xf3\xf2\x0a\x98\x14\xb0\x0e\x21\x7a\x46\x48\xe4\xbb\xc4\x49\xa2\xaf\x40\x5c\xa4\xb5\x9f\x84\x38\xdd\xfd\x75\xd3\x4d\x10\x64\xe5\x8b\xfb\x32\x5c\x55\xbd\x54\xea\x6c\xdf\x77\x12\xba\x80\x7c\x3e\x4c\x66\x5d\x62\x0c\xd5\x95\x13\xd7\xbc\x08\x55\x24\x7e\xb6\x70\xec\xc2\x92\x50\x96\x61\x81\x27\x02\x70\x32\x75\xd9\xb2\xf8\x7e\xf2\x79\xd7\x70\x0e\x69\xd9\x95\xdb\x98\x14\x4a\x14\xc8\x17\x74\xa4\xcd\x89\x0e\xc0\x3d\x13\xf8\x58\xf3\x76\x9e\x50\x48\xed\x55\xca\xa8\x12\x01\xe8\x78\x5d\x37\x71\xce\x6d\xa5\x11\x75\xd0\x17\xd2\x11\xfa\x70\x37\x94\x41\x6f\x46\x9b\x11\x29\xd7\x31\xab\xde\x74\x4d\xa5\xb2\xfa\xcd\x7a\x9b\x09\x3d\x6c\x97\x43\x50\x9b\x01\x03\xba\xb9\xc8\x1c\x6e\x5f\x38\xbc\x97\x18\xe3\xe4\xfa\xa8\x64\x75\xd1\x37\x25\xa8\x29\xac\x61\xdf\x8d\x15\xf0\xb2\x7c\xb4\x0d\x0e\xba\x0b\x24\x6b\x9c\x36\x0b\x56\x9b\x81\xb3\xab\xf3\x80\xee\xc2\x74\x92\x31\x6b\xc2\x92\xe5\x15\x0e\xe0\x60\x72\x19\xa2\xbd\x80\xba\x98\x4c\x7e\x3f\x19\x89\xbc\x51\xe4\xc5\xda\x3a\xe5\x07\x06\x76\xe0\xc1\x50\xd0\x37\xa8\x6a\x0f\x91\xbf\xc0\x7c\xde\x64\xc1\x9f\x9c\x7a\x7a\xf4\x4d\x69\x29\x97\x00\x41\x44\x8d\x3b\x17\xc2\x49\xd5\xe0\xb5\x86\x2e\x9a\x25\x20\x9e\x8f\x97\xd7\xa0\xf0\x30\x18\x15\x04\xfe\xad\x22\x66\xc8\x73\xfd\x23\x59\x83\xdf\x3d\x06\x57\xb9\x20\x96\xe2\xb4\x90\xdf\x33\xca\x11\x57\x33", )?; test( HashAlgorithm::SHA256, b"\xf9\x96\xf3\xad\xc2\xab\xa5\x05\xad\x4a\xe5\x2b\xc5\xa4\x33\x71\xa3\x3d\x0f\x28\xe1\x95\x0b\x66\xd2\x08\x24\x06\x70\xf3\x52\xef\x96\x18\x5e\x9a\x70\x44\xf4\xce\x2f\x2f\xf9\xae\x01\xa3\x1e\xf6\x40\xe0\xb6\x82\xe9\x40\xc5\x10\x51\x17\x59\x46\x13\xdd\x1d\xf7\x4d\x8f\x2b\xa2\x0c\x52\x22\x3b\x04\x5a\x78\x2e\x85\x0a\x12\xa2\xaa\x5c\x12\xfa\xd4\x84\xf1\xa2\x56\xd0\xcd\x08\x72\xd3\x04\xe8\x85\xc2\x01\xcd\x7e\x1e\x56\xd5\x94\x93\x0b\xb4\x39\x21\x36\xfb\x49\x79\xcc\x9b\x88\xaa\xb7\xa4\x4b\xfc\x29\x53\x75\x1c\x2f\x4c", n, e, b"\x30\xb3\x48\x62\x4f\xaa\x99\x85\xfc\xd9\x5f\x9c\x7e\xad\x3a\xfe\x64\x56\xba\xdf\x8c\x0f\xed\xbd\xad\xb3\xa9\x00\x3a\x67\x02\x97\x3a\xcd\xb4\xe8\x66\x52\x36\x7d\xb2\x3e\x0a\x81\x41\x88\x0d\x66\x31\x83\x4f\x9f\x17\x1c\x94\xa8\xfe\x9c\x31\x5b\xcb\x86\x80\xec\xfb\x5a\x4f\x59\xb4\x5d\x4e\x4c\x3c\x05\x82\x8b\x7f\xaa\xa8\xe4\x23\x4a\xad\xa4\xe7\x66\x64\x6c\xc5\x10\xd0\x7b\x42\xbd\x38\x83\xa8\x3b\x5b\xcb\x92\xd9\xe7\xcc\x1d\xdf\x59\x0a\x69\x01\x11\xbf\xc6\x2a\x51\xaf\x7e\x55\x54\x3e\xa5\x18\x8c\x92\x45\x3d\x41\xd3\xe8\xfd\xab\xee\x3e\x1d\xef\xa9\xd0\xaf\xdb\x85\xc8\x15\x3a\x50\x19\xae\x45\x56\x3e\xa3\x08\x0a\x30\x22\x66\x81\x68\xf0\xc2\x73\xa6\xdb\x1a\xfa\xdc\xd5\xed\xbc\xa5\x02\x1c\x2e\x53\xf4\xd9\x51\xc6\x04\x20\x6a\xe1\x0f\x28\x7f\x45\x18\x67\x27\x1d\x37\x04\x82\x79\x1c\xdf\xdc\xb6\xa4\x01\x0f\x6b\x3d\x9b\x92\x85\x63\xd1\x68\xda\x19\xf1\xc1\xe5\x70\xf8\xc1\x58\xf3\xd4\x90\xb2\x9a\xa2\x3a\xbd\x1f\xfd\xf2\x08\x66\xc3\x4c\x6e\x63\xb9\xe8\xa9\xa0\x2d\x7a\x1b\x19\x6d\x05\x5f\x4c\x53\xce\x82\xb4\x00\xe4\xab\x9e\x1b\x9d\x70\xd0\x04\x9d\x6d\x57\xcf\x0a\x49\x49\xcf\xc6\x8d\x63\x38\x82\x88\x2d\xcf\xdf\xc5\x0c\xf4\x49\xdf\x10\xac\xf2\x03\x05\xc2\xaa\x43\xbd\xa1\x0f\xd8\xa1\x0b\x4e\xca\xa2\x31\x00\xaa\x47\xe9\x29\x36\xdc\xe1\xbf\xb8\xd6\x59\x52\x35\xbb\xfe\x2c\x85\x85\xcb\x16\x47\xb2\xbe\xac\xb1\xe1\xd4\xb6\xce\xf7\x58\x81\x1a\x68\x33\x0f\xa9\xc3\xa8\x25\x73\xc0\x8f\xa2\xcd\xa5\xa0\x3f\x34\x25\x55\x4e\x45\xd9\x8c\x16\x45\xc5\xbd\x27\xd1\x2e\x6c\x20\xb2\xc4\x62\xa7\x46\xe8\x82\xa3\x42\x1a\x7b\x1b\x1e\x25\xb4\xc3\x6c\x8b\x16\xa1", )?; test( HashAlgorithm::SHA256, b"\x6a\xce\x05\x2d\x7e\x99\xcd\x97\x3b\xb5\xc9\xf6\x67\x9b\x1c\x30\x5e\x07\x20\x89\x65\xfe\x58\xc6\x3b\x10\xa6\x92\xf1\xdb\xbe\x22\xfc\xd0\xdb\x15\x89\x3a\xb1\x9e\x10\x7b\xa2\xe4\x2c\x99\x34\xa9\xaa\xfa\xc3\x2a\xdf\x6c\x73\x47\x3f\x69\x69\xe4\x2c\x98\x3b\x8f\x0c\x96\xa4\x63\x9e\xf7\x7d\x2c\x8e\x88\xe8\xcc\x47\xd7\xcf\xdd\x08\xf6\x8d\x97\x3a\x7b\xea\xf4\x01\xcb\x4d\x13\x11\x99\x2d\xda\xc3\xa9\xc9\xe0\x67\xda\x19\x8a\xdc\x63\x04\x74\x5f\x5d\xd3\x12\xa1\x82\xe6\x97\x1c\x34\xa5\x15\xa6\xc1\xba\xe6\x47\xe5\x7e\x4c", n, e, b"\x5f\x0e\x74\xf4\x54\x75\x4a\x30\x74\xfa\xaf\xc6\x05\xf3\xc9\xaf\x47\x60\x4a\x89\x83\x65\x0a\x9b\x62\x11\xfb\x19\x1d\x9a\xfa\x53\x15\xdf\x4d\xb4\x50\x1f\xd4\xf0\x4c\x74\x1d\x76\x46\x56\xd4\xa5\xd0\x06\x38\x8a\xd8\xfd\xb2\x19\xec\x6b\x75\x69\x08\xe2\x3b\x30\xcb\x63\x9f\xfa\x7b\xbf\x28\x74\x71\x3b\xfd\x5a\x10\x62\xc1\x9d\x04\xe0\xe4\xa7\x4b\x14\x44\x6a\x7f\xdf\x5c\xb8\x12\xe9\xac\x7b\x60\x12\xd9\xae\x99\x1c\x47\x65\x6d\x2a\xde\xd2\x40\x74\xbb\x8a\x38\xb1\xa8\x8b\x1c\x2b\x13\x1e\x5b\x09\xc9\x37\x57\xfd\xb2\xd6\xb6\x9a\xa8\x26\x5a\x43\x5f\xba\x00\xae\xb3\x6a\x1f\x62\x9b\xc3\x4b\x87\x60\x89\xd2\x8a\x94\x8d\xd6\xab\x4c\x89\x94\x30\xda\x60\xa2\x6f\x6c\x13\x60\x3f\xc8\x89\xc7\xb2\x93\x6c\xa3\xc5\x15\x6b\xd7\xfa\x6e\x34\xea\xc9\xe0\x48\x00\x83\x3e\xf0\xcb\x9b\x6e\xef\x78\x8c\x0e\xf0\x02\x1a\x45\x36\xfb\x83\x71\xfa\x3e\x2c\x8b\xb8\xbe\xfa\xc1\x6e\x80\x92\xd6\x9c\x57\x1c\x1e\x15\xfd\x25\x5e\xc0\xa0\x7a\xcf\x9a\xe9\x95\x38\x31\xef\xd3\xdc\xbe\xf4\x4e\x0f\xcc\xeb\xb1\xaf\x95\x9d\x71\xf5\x01\x30\xe8\xac\xb4\xfa\x23\x19\x26\x1f\xba\x12\xf2\x71\x5d\xef\x82\xbf\xaf\xbf\x40\xe3\x45\xec\x5d\xcd\xab\x5c\x1b\xf5\xf6\x6b\x1d\x0e\x9f\x7a\x9c\x62\xc9\x37\x57\x46\xe1\xae\x0c\x8f\x14\xa4\x89\x18\x43\x83\xe8\x1d\xce\x20\x70\xad\x4b\x52\x5d\xf7\x6b\x44\x6b\x1f\x22\x92\x1d\x42\x4d\x9b\xa3\xce\x21\x57\x75\x01\xdf\x62\x80\xfd\xc6\x9f\x02\x39\xae\x11\x27\xb6\x99\x50\x75\x9d\x5f\x0b\x69\x3f\x54\xe8\x7e\x07\x63\x62\x3b\xf5\xd3\xff\x69\x43\x00\x81\xb9\xc9\xe2\x44\x5a\x05\xe1\x15\x67\x5e\x09\x0b\xca\xb2\xaa\x1d\x75\xce\xee\x2a\xd6\x19\xec\x8b\x80", )?; test( HashAlgorithm::SHA256, b"\x0e\x49\x74\x0f\xdc\xca\x6b\xfc\xe2\x94\xc1\x1f\x45\x40\x78\x05\xb3\xda\x41\x2b\x01\xef\x3f\xb5\x13\xe7\x0e\x62\xfd\x95\x04\xc0\x67\x0d\xb6\x9c\x36\xb6\xbe\xbd\x69\xa0\xbc\xd2\x40\x17\x9b\xa8\xa4\x78\x16\xa0\xc3\x43\x7a\x61\xfb\x72\xad\xca\xf9\x09\x6f\x2a\x22\xef\xe0\xb4\x31\xfc\x42\x2d\x22\x53\x01\xe8\x50\xf2\xf0\xf4\xda\x87\xd6\x94\x4a\x85\x29\xef\x79\x78\x19\x09\xad\x96\xd1\xf2\x05\x96\xf9\x3e\x17\xc5\x7f\xb4\xd7\x56\x97\x4b\xbb\xf9\x00\x52\x1c\xb0\x89\xee\xe0\xde\xd5\xc9\x56\xa1\x5b\x09\x61\x62\xb0\x7f", n, e, b"\x7b\xbb\x3d\xdd\x17\xa4\x2b\xe7\xcc\x4e\x7e\xaf\x45\x65\x09\xa4\xba\x58\xd4\x0c\x49\xa3\xd9\x95\x73\xb7\x33\xe1\x94\x2f\x9f\xca\x20\xba\x8b\x91\x07\x08\xd6\xe7\x50\x36\x7e\x84\x73\x02\xfc\x60\x3b\x80\x63\xc1\x9a\xf8\x83\xe7\x50\x7f\xb0\xd9\xcc\x2b\xe3\x74\x79\xa3\x7c\xca\x25\xb8\xc7\xc4\x6f\x6b\xf6\x61\xdc\x6a\x32\x32\xf8\x8b\x48\x3f\x1b\x8f\x41\xb4\x6d\x49\xba\x3f\x17\x95\xd6\x8e\xaa\xd4\xa2\x55\x6f\xb5\xd7\x87\x3b\xbb\x65\x01\xec\xf0\x6a\xc5\x58\x23\x5e\xd1\x39\x90\xb0\xe1\x6f\x67\x96\x5b\x09\x36\x6b\xcb\x36\x2c\xfc\x6f\xb9\x78\xf4\xf6\x8d\x81\x46\xdc\x8b\x81\x98\x04\xdf\x42\x4e\x8c\xa5\xb6\x3c\xf1\xfc\xf9\x7b\xbf\x30\x0d\x0b\x99\x88\x60\x79\x8a\x63\x42\x43\x83\xfc\xd8\x1d\x37\x77\x3d\x59\xbb\x13\xb4\xfa\x5d\x46\x8c\xd1\x28\xbb\xab\x18\xa8\xce\x51\x73\xbe\x5d\x9d\x54\xd3\x17\x7f\x02\x45\x78\x84\x09\x97\x3d\xf4\xa9\x01\x6b\x94\x4b\xae\xfb\xf3\xbf\x11\x46\xa9\x39\x3d\x22\xe3\x5e\xc2\xbe\x0a\xe6\xf4\xc3\x1d\xc4\x98\x1f\x40\xfc\x1b\xaf\x38\x26\x00\x69\x9e\xaf\xce\xa9\x2c\xbe\x24\xe2\x6e\xe8\x46\xfa\x23\xbc\x19\x3b\x6e\x72\x14\x01\xb7\xac\x3f\x5f\x4e\xbe\xb6\x33\x97\x9f\x8e\xf3\x5f\x4a\xb1\x11\x7a\x86\x9d\x5b\x9d\xbb\x74\x82\xf0\xd5\xa5\x9e\x41\x63\x54\x8d\x25\x12\xae\x06\x72\x05\xb5\x7d\x03\x0c\x48\x3f\x72\x0d\x2c\x44\x35\x04\x28\xf5\x26\x89\x43\xfc\x5f\x6e\xa1\xc8\x8e\x2e\xc1\x3a\xb3\xdc\x14\x56\xe9\x6a\x3b\x8e\x7c\x12\x1a\xf4\xd6\xa5\xfe\x4e\xe5\x5e\x99\xfb\xc3\x59\x2a\x48\x7c\x19\x4b\xc2\xf2\xbf\x6e\x79\xfb\x79\xc2\x87\x6c\xf3\x36\x5e\x07\x5b\xee\xac\xc7\xdb\x4d\xb7\xee\x69\xe7\xf1\xfe\x12\xa3\x27\xe6\xcb\x0f", )?; test( HashAlgorithm::SHA256, b"\x0e\x67\x5d\xac\x9a\xec\x91\x01\x06\xa6\xab\x21\x9b\x4c\xce\xb5\x2d\xed\x25\x49\xe8\x99\xc9\xa2\x4d\x5e\xe5\x51\x77\x76\x18\x88\xa3\xbe\x1a\x2d\xef\x6a\xa3\x2d\x62\xf7\x88\x13\x2d\x62\x27\xd9\x30\x98\x06\xfd\xc0\x2d\xb7\xd8\xa8\x50\xff\x2c\x6d\xff\x37\xfc\xd7\x77\xf1\xa0\xac\xef\xdf\x18\xbf\x85\xf1\xa1\x29\x79\xbe\x86\xd7\x99\x25\x39\x45\xfc\x34\xa2\x88\xf3\x48\xb7\x92\x3d\x76\x4d\xb2\x7a\x2a\x2d\x5a\xe2\x0e\x6b\x25\x37\x2e\xf3\x18\xf8\x59\x65\x29\xd8\xca\x23\xfd\x6f\x08\xa8\xf6\x2e\x0a\x1b\x6d\x98\x9f\x23", n, e, b"\x80\x52\xd9\x5f\x12\xce\x0e\x6e\x53\xa5\xa3\x56\xa0\xeb\x35\x3b\xdc\xc1\xa6\x65\x14\xd6\xcf\xb3\xa3\xd9\x61\x55\x31\x0b\xdd\xa0\xa0\xd1\x79\x5f\x97\x64\x3f\x3a\x44\x96\x63\x4f\x2d\xd9\xb9\x5a\x21\x38\xee\x39\x0e\x1e\x74\xbe\x31\x34\xf3\xf4\x7a\x91\x9e\xe7\xb5\x9f\x8e\xcd\x27\x2a\xb8\x8c\x82\xcb\xce\x7c\x21\x7e\x5f\x92\xd0\x57\xa5\xb0\x0f\xbf\x05\x75\xcd\xae\xcd\x7d\xc2\x85\xa4\x21\x8c\x8a\x95\x52\x16\x59\x8f\x07\x42\x67\x1e\x01\x8e\x8e\x4e\x76\x83\x9a\x57\x5f\x50\xb2\x10\x2a\x8b\x77\xd1\xb8\x4f\x6d\xce\x98\xd7\x8e\x57\x58\xe0\xa6\xf9\x2b\xf3\x5d\x6a\x2f\x18\xad\x40\x09\x25\xd7\x88\x0f\x9e\xfc\x77\x4a\x8c\x7e\xbf\x64\x88\x5c\xd2\xf6\xf6\x29\xb5\x4a\x7c\x12\xec\x91\xd3\x9b\x3c\x25\x18\x24\x1f\xdc\x32\x2d\x9b\x23\x5a\x8e\xa4\x4f\x77\xe8\x2f\x3d\xc4\xf7\x28\xf6\x20\xc0\x7d\x1e\x7f\xf4\x09\x4f\x29\xc6\x74\xab\x0f\x08\x02\xef\xa1\xc9\xe6\x48\x1e\xbb\x84\xe0\xbf\x13\xef\x46\x8d\x8c\xca\x11\x45\x70\xb9\xed\xcd\xdf\x98\xac\x4a\x83\x4f\xe7\xa0\xd5\xc6\xfa\xe8\xa6\x0a\x48\x39\x9f\x3c\x8a\xf4\x2f\xf4\x02\x6e\x42\xa8\x1a\xac\x36\x11\x4f\xfc\x05\x3f\x3f\x72\x9b\x7c\xf9\xa9\x7a\x56\x84\x8e\xbe\xa0\x11\x5a\xa8\x29\x83\x41\xaa\x22\x69\x63\xeb\xdf\x57\xab\x2d\x8e\x4b\x90\x00\xdd\x05\x1a\x6c\x5d\x69\xf6\x0e\x1d\xc1\xb3\x3f\x20\x94\xfd\xbf\x8e\x5b\x62\x7b\xc0\x76\x4d\xb9\x52\x2c\xbb\xc0\x81\xdb\xf3\x8c\x21\xb1\x3f\x98\x08\x13\xbd\x2b\x00\xc7\x57\xeb\xb8\xc0\xb2\x12\x13\x15\x2e\x69\x40\x39\xf3\x06\xf7\x34\x28\x57\x65\x1f\x72\x2b\xdd\xa0\x12\x12\xa8\x55\x27\x99\xbd\xa6\xef\x07\xc5\x20\x7d\xc7\x44\xef\x79\x69\xaf\xd5\xaf\x2e\x6f\x12", )?; test( HashAlgorithm::SHA256, b"\xf6\xa7\xa6\xe5\x26\x59\x12\x5f\xbb\xc8\x72\x74\x17\x28\x3b\x9a\x64\x44\x1f\x87\x12\x1e\x27\xf3\x86\xd5\x01\x9f\x10\xcc\x9b\x96\x1e\x09\xf1\xb3\xb0\xdb\x23\x63\x0c\xc0\xca\xac\xb3\x85\x8c\x6f\x93\xaf\xee\xea\x7e\x1a\x6a\x80\xdb\xe0\xc2\xbd\x9c\x7c\x93\x95\x70\x30\x2d\xec\x39\xa4\xa2\x5c\xc0\xcf\x1d\x32\xa7\x1a\x75\xb9\xa0\xc3\x02\xbc\xdd\x80\xb0\x46\xc8\x66\x51\xac\xf3\x08\x38\xcd\x52\xe3\x03\x99\xa8\xfa\xb8\xd0\x3f\xbd\x14\x0c\xdc\x2f\x1f\x02\xf2\x48\x04\x05\x16\x98\x20\xcc\xb3\x2e\x59\x74\xff\xb8\xb1\xc8", n, e, b"\x84\x60\x3a\xcb\xfe\x1f\x2f\x76\x9f\x1a\x62\xb0\xf2\x87\xf3\x06\x94\x0b\x22\x54\x76\x71\x4a\x4b\x68\x27\xc0\x2d\x7b\xd0\x52\xf3\x03\xf3\x0a\x5f\xa6\xda\x83\xe6\x06\x15\x30\x56\x69\xca\x9e\xc1\x77\xc5\xb3\x2b\x14\x15\xee\xbe\xf7\x86\x20\x29\x6e\xba\xd6\xdb\xbd\x52\x08\x39\xd3\xaa\xcc\x97\x81\xac\x86\x02\xdd\xce\x07\x36\xdc\xfa\x72\x90\xb4\x5f\x15\x5b\x8e\x92\x4d\x0a\xfd\xf7\xdf\xc8\xd1\x99\xbf\x09\x50\x9d\x01\x76\xa6\x8b\x14\x57\x56\xee\xf5\x3d\xe4\x56\xe1\x70\x78\x85\x98\x49\xa3\x52\xa5\xbb\x65\x42\x39\xd8\xeb\xaf\x88\x00\xca\x82\x63\xd3\x4a\x86\x8d\x52\xbf\x8f\x22\x64\x4d\xd9\xf3\xc0\x5b\xd8\x91\xcd\x92\xf2\x63\x53\x0c\x58\x96\x02\x3c\x6b\x21\x3d\xdb\x64\xed\xe1\x77\x0f\xf1\x68\x6c\x34\x03\x6e\x28\x1e\x91\x1d\x9d\xc9\x60\x35\x4f\xd8\x44\xcb\x7b\x22\xdc\x0c\xd8\x1a\x96\x20\x3b\xa8\x18\x40\x1c\xcc\x22\x5f\x85\x7e\x59\xa5\xcb\x7b\xa6\xdf\xc7\xf5\x13\x5e\xa3\x27\x81\xe6\x3d\xaa\x14\xfb\xda\x1b\xac\xc1\x8e\xbc\x50\x82\x4d\x40\x28\xb8\xfd\xec\xda\x49\xe8\x10\xba\xe5\xac\xc8\xad\xc0\xdc\xa2\xe2\x36\xfc\x83\x2a\x97\x33\x0a\x12\x14\xfa\x0a\xed\x15\xcd\x10\xc0\x49\xef\xb6\x5c\xe8\x55\xc0\x60\xf0\x5b\xef\xb3\x17\xb8\x06\x58\x43\xc4\xeb\x5a\x03\x71\xfc\x6f\x20\x9f\x6f\xfb\x94\x8c\x88\x1f\x2f\x20\x91\xca\xf0\xf5\x9f\x60\xb7\x2c\x5f\x67\x27\x1b\xae\x96\xb9\x13\xfd\x21\xfa\x1d\xfa\x97\x5d\x5e\xcd\x62\xb0\xd5\x08\x73\xb6\x86\xd2\x9c\x88\x0d\x36\xed\xca\xd3\x3e\xc3\xe2\x21\x6c\x9c\xfc\xfb\x4f\x98\x4c\x23\xfd\xe8\x15\xe2\x80\xa8\x02\x42\x86\x08\xbe\xd3\x73\x9a\xf9\x20\x0d\xe1\xf8\x5e\xde\xe2\x83\x4c\x04\x94\x2c\x06\x8a\xac\xd2", )?; test( HashAlgorithm::SHA384, b"\xbb\x29\x4b\x95\xd9\x13\x00\x5b\x11\x09\x87\xcd\xe4\x58\x87\x48\x4a\xe6\xdf\x79\x48\x73\xdf\xc5\xc4\x1f\xb7\xe8\x99\x2c\x2f\xdc\xe7\x06\x99\xfc\xac\x80\x04\x69\x99\x61\xb3\xad\x1e\x1f\xce\x9e\xc8\xea\x56\x85\xcc\xec\x5e\x80\xe4\xd0\x79\x25\x59\x81\x6f\x68\x61\x34\x34\xbf\xac\xa8\x1a\x84\x3a\xac\x45\x9a\x6f\xe3\x5f\x53\x69\xc4\x8e\x91\x91\xe4\xa3\x2c\x70\x78\x95\x94\xc5\x15\x2d\xb8\xd4\xbb\x02\x26\x00\x12\xa8\x73\x9c\xf3\x25\xdd\xff\x2a\xa4\x2f\xd6\x7b\x6e\xe5\xbf\xe3\x15\x91\x13\x1f\xf2\x7d\x02\x73\xd2\x92", n, e, b"\x32\x63\x7c\x60\x79\x8b\x45\x0b\xff\x10\x0b\xff\x12\x83\x83\x57\xde\xff\x28\x1d\x5b\x31\xe4\xf4\xc2\xcf\xc9\x6e\xb7\x79\xce\x6d\x31\xb1\xce\x8b\xd7\xaa\x7f\xa8\x8d\xdc\x42\x79\xc8\xc3\x28\x06\x04\xb0\x18\xcc\xf4\x52\x00\x4a\x14\x88\xed\x47\x50\x18\x1c\x50\x25\x63\x65\x11\xac\x67\x24\xfe\x51\x76\x1c\x27\xd7\xcf\x9a\x0c\x87\x82\xea\x22\x31\x26\x88\x53\xc4\xb1\xf7\xac\xb0\x00\x5e\x56\x87\xc8\xf3\xdf\x16\xc9\x62\xf0\x2c\xe5\x6b\x23\xd3\x87\xa2\xba\xad\xc8\xbe\xc9\x42\x29\xc3\x55\x75\x26\xe6\x17\x07\xa8\xb5\x92\x93\xa9\x76\xe3\x2c\x7f\xa1\x33\x28\x50\x88\xf3\xce\x3e\x67\x77\x88\xaa\xa9\x47\xe7\x62\x2c\x75\x7e\x84\x4b\x11\x75\x92\xbe\x99\xfe\x45\x37\x6f\x8b\x30\x13\xe8\x77\x2e\xc9\x2c\x5b\xb0\xb9\xfa\x30\x1b\x95\x54\x45\x99\x69\x0a\xd9\x36\x68\xd8\x3b\x2d\xaa\x7d\xf0\x5c\x66\x21\x4e\x27\x50\x14\x78\x0a\x91\x2d\x8b\x19\x32\xd7\xa6\x55\x05\x8e\x74\x3f\x50\xb0\x74\xb1\xd9\x69\x1c\xa2\x3a\x2f\x95\xf6\xaf\xfb\xd5\x16\xd6\x4c\xcb\x2a\xa4\x3c\x23\x6e\xb9\x5d\x36\xd2\x72\x54\x5e\x3b\xeb\x8f\xf5\xaa\xcd\x95\xb3\x0f\x7f\x1d\x64\x18\xaf\x04\x2c\xd9\xa0\xcf\x01\x89\x84\x62\x62\x32\x2a\x18\x87\x5a\xe4\xc3\xe6\x8e\x4e\x8f\xfa\xa0\x27\x6c\xdd\x99\xa0\x04\x7c\x86\xc0\xf7\x1d\x2d\xee\xfd\x50\x64\x2d\x29\xc1\x95\xe6\xd1\x4f\xb4\x6f\xba\xc3\x3a\x50\x8c\x1f\x03\xa2\x32\xde\x08\xaa\xe0\x9f\xaf\x1d\xa8\xed\x2b\xa2\xae\x84\xbc\xca\x88\xb7\x8d\xcc\xbd\xe9\xaf\xde\x08\xa3\xbe\xb3\x22\xdc\x79\x35\x6b\x29\xc8\x48\x41\x69\x89\x14\xb0\x50\xbe\xb7\x5a\x7b\x2f\x67\x01\xaa\x81\x01\xa5\xa4\x95\x5e\xe2\x7b\xaf\xe8\x1b\x21\xd0\x3b\x43\xe3\xc7\x73\x98", )?; test( HashAlgorithm::SHA384, b"\xf9\x46\xc6\xbd\x5e\x1d\x6b\x89\x09\x2f\x3c\x48\x7c\x05\x68\xfa\x07\xc3\x56\xfa\xe9\xb8\xe8\x31\xb8\x32\x02\x89\x03\x97\x46\xa4\x35\xb1\x22\xcf\xbc\x4a\x0d\x31\x6b\xf9\x0d\x48\x1d\x3b\x7d\x97\x9c\xc5\x0d\x98\xc1\x19\x0a\xf8\xdc\x58\xe0\x03\x55\x57\xdd\x5e\x94\xf4\x37\xf4\x1f\xab\x51\x32\x02\x64\x3a\x77\x74\x8f\x76\xc6\xb7\x73\x02\xbf\x40\xc3\x92\xcd\x18\x73\x1d\xa0\x82\xc9\x9b\xde\xde\xb7\x0e\x15\xcd\x68\xbf\xf5\x96\x19\xca\xbc\xc9\x2a\xdc\xf1\x22\x75\x3c\x55\xaf\xde\x08\x17\x35\x2b\xc2\x47\xd1\x17\x0b\x8d", n, e, b"\x50\x70\x6b\xa4\x9d\x9a\x31\x66\x88\xa3\xee\x80\xa0\xbd\x98\x67\x57\xd4\x3e\xc8\x32\x85\xaf\x9e\x78\x19\x6b\xd5\x2c\x90\x0d\x40\xb2\x80\xfa\x0d\xe5\x4e\x35\xac\xe7\xd6\x66\x00\x12\xf1\xa6\x62\x04\x09\x2f\x0e\x63\x4b\x97\xe0\xe5\x16\x65\xb4\x07\x5e\x36\xf1\x42\x22\x66\xc7\xca\xd7\xb2\xd9\x98\x1b\x91\x3d\xf3\xfa\x3e\x6a\x5a\x1c\xad\xfc\x63\x78\xa8\x54\x0e\x0f\xaa\x26\xf1\xcc\x6f\xb2\xfb\x49\x2a\x80\xd0\xa6\x94\x5b\xce\x5b\xbc\x23\xdd\xb3\xb1\x07\x01\xf0\x24\x9b\x27\x40\x7a\x67\x00\x80\x2e\x88\x42\xef\x3c\xc7\x61\xc4\x82\x3a\xcb\x5d\x14\x53\x50\x8d\xcd\xbb\x97\x9e\x7b\xd8\xd0\x01\x28\xe6\x0a\x9b\x37\x89\x16\x7c\x91\x41\x7d\x93\xf0\xe9\xfb\xb0\x0c\x9a\xf1\x49\x8e\x09\xeb\x64\x85\xeb\x94\xce\xa4\x88\x3f\x6a\x25\x6e\xab\x2c\xaa\x82\x6d\xe4\xfd\xac\x01\xba\xca\x3a\x21\x6e\x3d\x20\x4a\x3d\x83\x7f\xfd\x4d\x0b\xe2\xb2\xce\xf7\x11\x90\x90\x54\xc4\xda\x1d\x5b\x93\xa8\xf9\x84\x51\xc7\x00\x2a\xe8\x4a\x5e\x70\x80\xd9\x86\x71\xc5\x0e\x3c\x91\xc4\x08\x7d\x04\x77\xb1\x04\xf9\x16\x01\x0e\x74\x2f\x2d\x20\x7f\xb4\x0d\x12\x2d\x8f\x21\x1a\xf6\xd7\xc5\xec\xa4\x95\x42\xd9\xac\xb0\xf1\x66\xe3\x6a\xbc\x37\x15\x50\x70\xc1\x2e\x9f\x28\xb9\x07\xd6\x7a\x2c\xa7\x0b\xfc\xe5\x54\xe1\xc4\x4c\x91\x52\x0e\x98\xfc\x9a\xd0\xc0\xee\x47\x7f\x75\x05\x16\x47\x6a\x94\x16\x80\x66\xce\x47\x00\x00\x30\xa9\x9c\x23\xe2\xc3\x87\x55\xde\x94\x6d\x5e\xdf\x0d\x6a\xa9\x42\x12\xf9\x92\x31\x5b\x24\x8c\x1f\x82\x72\x3b\x29\xc4\x22\x16\xc7\x8c\xdc\xb6\x68\xf1\x12\x78\x26\x1c\xee\x92\x52\xc8\xfd\x0e\xd3\x7d\x0a\x85\x80\xca\x9b\x9f\xde\x75\x05\x61\x59\x43\x71\x2d\xa1\x9a", )?; test( HashAlgorithm::SHA384, b"\x9a\x33\x7d\x4c\x0b\xb9\xa0\x05\xb4\x7f\x47\x65\xd6\x96\xd1\x9d\xec\x58\xbc\x84\x82\xf2\x17\x3a\x4a\x20\x3a\x0b\x6d\x38\xb4\x96\x1f\x6a\x85\x2e\x76\x46\x8e\x80\x7c\x7e\x45\x76\x83\xee\xad\x5c\xb8\xd9\x86\x42\xfb\x76\xc0\xa1\xee\xab\x36\x41\x4c\x18\x99\x59\x7d\x57\xaa\xf9\x67\x82\xad\xa5\x86\xf6\x1a\x42\x3f\x57\x95\x37\x71\xd5\x20\xcc\x4e\xad\x90\xd5\x69\xf2\x3d\x95\x0f\x8d\xfe\xdd\xdb\x83\x55\x74\x85\x76\xe6\xbb\xfb\x6f\x2e\x91\xb3\xda\x71\x75\x3f\xd2\xf4\xea\x22\x9f\x6d\x20\xe2\x7d\xb8\xd0\x5e\x9f\xcb\x68", n, e, b"\xcf\xf7\xaa\x7f\x87\x56\x42\xfb\x93\x43\xe0\x7e\xf5\xe7\x30\x3b\xbf\x5f\x06\x9b\x44\xc1\x9f\xbf\x83\xe5\x9d\x42\x2e\x25\x26\x7e\xf9\x30\x74\x14\xb6\xb1\xef\x61\x71\x1e\xd0\x01\x32\x76\xd1\xa2\xad\x98\x39\x04\x74\x02\x7a\x0a\x70\x3b\xfe\x8a\x6e\x87\x70\x60\x59\xd8\x9c\x06\x09\x80\xc9\xc9\xe6\x0d\xc7\xe1\xfb\x9f\x77\x7a\x41\x78\x5a\xb4\xd2\xb6\x63\xba\x0e\x3c\x19\x21\x54\x5c\x47\x9c\x2a\x38\x3a\x50\xda\x8e\x48\x9c\xb2\x2b\x71\x10\x1d\x0e\xc1\x48\xac\x70\x92\x87\x32\xa7\x72\x19\x5a\x14\x0d\x08\x01\x52\x76\x2a\x9c\x40\x80\x3a\x39\xfa\x2a\x69\x78\xc2\xa7\x5a\xc4\xd8\xbd\x1b\xcc\xaa\x1f\x42\x04\xba\x65\xed\xdd\xf3\x2f\xed\xf2\xd9\xd0\xa3\xae\xd9\xb0\x6c\x47\xe7\x17\x73\x3c\x57\x78\x12\xd7\x23\xdb\xa7\x4a\x85\x2b\x29\x05\x23\x5c\x81\x2d\xc5\xf1\xd0\xdf\x0f\x0d\xe7\x3d\xfb\x86\x22\x1c\x6f\xfd\xd1\xed\xa1\x19\xbb\xe9\x8d\x14\x8a\xdd\x36\xa4\xfe\x50\x48\x9b\x06\xaa\xee\xfc\xb5\xc2\x06\x6d\x90\xfa\x79\x73\x87\x06\xcd\x18\xe4\x74\xd6\x96\x09\xff\x12\x10\xc7\x7d\xe7\xcd\x23\xba\x2a\x77\x5a\x43\x29\xcb\x27\x1a\x82\x6d\x60\x2c\x40\x1a\x71\x43\x90\x19\xce\xc1\x0c\xd9\xf1\x84\xc4\xd0\x45\x84\x21\x18\x27\xb1\x9e\xad\xac\x32\x58\xd8\xa0\xf2\x63\x16\x13\xf0\x51\xaa\xe0\xc6\x13\x05\x0c\xb2\x44\x42\xf1\x5e\xd4\xfe\x0d\xbd\x29\x0e\x42\x62\x91\x41\xbd\x2c\xd5\x6d\x20\x58\x4a\x1d\x10\xe1\xf2\xc2\xa9\xec\x73\x14\x33\xd5\xbc\xd1\xd3\x18\xbe\xd5\x24\x3b\x4b\x7d\x0f\x9a\x79\x82\x06\x1c\x55\xdf\xaa\x86\xb2\xc0\x18\x45\xc0\x21\xfd\xd2\xa9\x78\xd4\x20\x34\x21\x2f\x43\xb3\x35\x1b\x6a\xde\xb0\x3b\xdd\x6c\xaf\x7d\xe0\x59\x50\x2f\x16\xd7\x73\x48", )?; test( HashAlgorithm::SHA384, b"\x32\xfd\x45\xe7\x3f\x6f\x69\x49\xf2\x0c\xab\x78\xc0\xcc\x31\xd8\x14\xba\xea\x63\x89\x54\x6a\x36\x5d\x35\xf5\x4f\x23\xf1\xd9\x95\xb7\x41\x01\x18\x77\x60\xc8\x9b\xb0\xb4\x0b\x50\x57\xb1\x82\xe2\xfa\xfb\x50\xb8\xf5\xca\xd8\x79\xe9\x93\xd3\xcb\x6a\xe5\x9f\x61\xf8\x91\xda\x34\x31\x0d\x30\x10\x44\x1a\x71\x53\xa9\xa5\xe7\xf2\x10\xeb\xe6\xbc\x97\xe1\xa4\xe3\x3f\xd3\x4b\xb8\xa1\x4b\x4d\xb6\xdd\x34\xf8\xc2\xd4\x3f\x4a\xb1\x97\x86\x06\x0b\x1e\x70\x07\x0e\x3e\xd4\xd5\xf6\xd5\x61\x76\x7c\x48\x3d\x87\x9d\x2f\xec\x8b\x9c", n, e, b"\xc3\x89\x61\x37\x17\xec\x74\x76\xec\xda\x21\x44\xd0\xe8\xc8\xf9\xd6\x6f\xb4\x69\xc1\x67\xc4\x20\x9e\xc0\xbd\xee\xbf\xb4\x71\x66\x5d\x33\xda\xd4\x7b\x8f\x3c\x31\x9a\x76\xfe\x8a\x8a\x9f\x66\x2b\x6c\x69\x0b\x74\x90\x3d\x17\xf6\x1e\x23\x14\xe5\xea\x8d\x26\x67\x0e\xe4\xdb\x4d\xad\x29\x5b\x27\x7c\xa0\x8a\xde\x88\x0d\xe2\xe4\x2d\x12\xb9\x29\x52\x76\x4c\x1d\xc8\x08\xc2\x66\xdb\xbe\xdb\x67\x01\x58\xee\xf3\x6e\x89\x6f\x55\xa2\x03\xfb\x99\x55\x6d\xed\x05\x97\x41\x0b\xa3\x74\x86\xb1\xd8\x41\xf3\xd6\xd5\xc0\xb3\x9f\x2f\x49\xf0\xc5\x79\x48\x24\xfb\xa9\x4a\x8e\xc7\xc2\xb2\xc9\x1e\xad\xd5\xc8\xcb\xe4\x48\x95\xfe\x3b\xe3\xbc\x17\x27\xd6\xfc\x0e\x53\x64\xf5\x35\x78\x63\x9d\x3b\x3a\xf6\x96\xb7\x50\xa0\x78\x53\x69\x4f\xfe\x14\x5a\x28\xc0\x36\x20\xc7\x8d\xd7\x37\x7d\x09\x4d\x92\xc3\xe0\x95\x46\x88\x3d\x47\x03\xe6\x2a\x98\xdd\xf8\x1f\xd0\x1f\xcd\xf3\xc4\xb2\x15\x22\x4f\xe2\xb1\xb4\x99\x2a\xbf\x31\xf2\x0d\x12\xaf\xa8\x68\x20\x23\x90\xde\x33\x4a\x84\x6b\x2d\x58\xb2\x53\xea\x8a\xb3\xc5\x26\x5d\x84\x77\x3a\x65\x9e\x8b\xac\x7a\xf4\x41\x23\xd9\xea\x15\x06\x2e\x65\xd4\xd4\x19\xcf\x2d\x97\x07\x7d\x06\x24\xf8\xe5\xc3\x6f\x2c\x7b\x35\xcc\xf9\x54\x35\xd5\xc3\x68\x86\xff\x91\x05\xa6\xc1\xea\x22\x5e\x15\xea\x8c\xbc\x7b\x6b\xf6\x85\x61\x51\xcd\x76\xfb\xb7\x5b\x5b\x98\xf0\xe3\xdb\x51\x6a\x8e\x21\x81\x89\xfc\xb1\xcd\x5d\xe3\xca\xfe\xaa\x33\xef\x13\x5c\x5d\x8b\x8a\xa5\xf8\x81\xaf\xaa\xca\xf4\xc0\x8b\xd7\x28\x12\x55\xbc\x2a\x33\xb7\x6d\x4a\x36\xe0\xb1\x70\xc4\x55\x88\x23\x9e\x5b\x38\xc6\x79\xb0\x8c\xf8\x02\xaf\x73\xb6\xd7\x9b\x39\x35\x94\x94\x61\xe7", )?; test( HashAlgorithm::SHA384, b"\xab\x66\xcc\x48\x7e\xc9\x51\xf2\x11\x9d\x6e\x0f\xa1\x7a\x6d\x8f\xeb\x7d\x07\x14\x9b\xec\x7d\xb2\x07\x18\xe4\xf3\x1d\x88\xc0\x1f\x9a\x53\xd5\xba\x7e\xce\x3a\x4d\xbc\x67\xaf\x6a\x35\xd1\x30\xea\xe7\x62\xcb\x79\x62\xb9\xae\x55\x7c\xa3\x84\x52\x46\x40\x02\x22\x3f\x61\xbc\xd3\xc7\x35\x3e\x99\xd6\x25\x58\xce\xed\xfc\xb9\x37\x4d\x4b\xbf\x89\x68\x0c\x8e\x2b\x95\x85\x60\x3e\x07\x6f\x1c\xdb\x00\x58\x29\x9b\x42\x46\x84\x5d\xc7\x9d\x10\x43\xb1\x42\x2e\xfe\x84\x01\x8e\x4c\x93\x2c\x45\xbe\xb8\x85\x1f\xbf\x48\x5e\x36\xd2", n, e, b"\xb5\x13\x31\x55\x2b\x08\xbe\x35\xa1\x69\x8a\xa6\x20\x3d\x84\xdb\xff\xf9\x00\x1e\xd5\xdd\x77\x6f\x2b\xe4\xdd\xfc\x07\xdd\x46\x20\xe9\x65\x4e\x82\xa3\x34\x65\xbd\x20\xf1\x18\x63\xc0\xed\x02\xa0\xae\xa2\x7a\x44\xd4\x14\xc3\x28\xa9\x38\xbf\x87\x7e\x15\x83\x8a\xb9\x9d\x67\x0d\x01\x41\x42\x62\xe8\x86\x5d\xc1\xd9\xfc\x30\xfd\x08\x12\x69\x9f\xa6\x90\xc3\x4f\x30\x2f\x63\x7e\xc8\x02\xcd\x40\xac\x85\x91\xe9\x76\xc0\xb8\xbc\xcb\x1b\x01\x37\xaf\x64\xa2\x87\x02\x10\xe8\xfa\x3d\xc4\x31\xfe\x09\x56\xb8\xad\xdf\xf1\xe4\xb1\x8c\xf0\x7e\x07\x8a\xa9\x3a\xf8\x1b\xb3\x02\x3c\x9e\x59\x4e\x66\x59\x5f\xd9\x2b\x10\x22\x6e\xa1\x26\x00\x5f\x47\x24\x42\x73\x52\xc3\x8e\x9e\x85\xfc\x2e\x07\x23\xf8\x0a\xf1\xf6\x15\x99\x55\x0b\x5e\xf5\x4c\x5b\x38\xca\x40\x57\x38\x01\x7b\x89\xcb\x94\x68\xd9\x74\x1c\xd6\xbd\xf7\x11\x21\x62\x25\x1b\xa1\xd0\x83\xcc\x37\x0a\x4a\x82\x61\xc3\x9b\x6b\x94\xbf\x21\xa5\x3b\x75\x64\x53\x1a\xe9\xeb\xc4\xcc\xea\x7e\xbb\x8b\xd3\x14\xb2\xe1\x3b\x58\xed\x10\x18\xae\x5b\x41\x5e\x0f\x9e\x3e\x19\xa5\xea\xd3\xa4\x46\x03\xf9\x06\x74\xa1\x90\xfe\xbd\xe2\x5f\x8a\xd8\x77\x8a\xee\xad\x4d\x0f\x64\xfb\xae\x37\x16\x6a\x54\xe3\xa7\x63\xe3\x55\x59\xbf\x8c\x3f\x17\x3f\x19\xff\x7b\xab\x98\xf3\xef\x80\x3d\xd5\x6c\x07\x62\x83\x99\xaf\xf8\x74\x85\xee\x73\xdb\xc3\xdb\x34\xec\xc7\xbf\xf3\xa5\x32\x26\xcf\x87\xbc\x81\xd2\x56\xe8\x0c\x09\x52\x0c\x8f\x38\xe9\xbc\xda\x09\x5e\x36\x35\x12\x8e\x1b\xed\xd9\x97\x06\x00\x54\x6a\x75\x1e\xb1\x1d\xab\x42\xe2\x89\xd6\xfd\xfe\xa0\x4b\xd5\x8d\x45\x71\xa7\x9d\x24\xbc\xe4\x50\x8c\x54\xe1\xec\x4c\xf7\x5b\x98\x5f\xd3", )?; test( HashAlgorithm::SHA384, b"\xfe\xf7\xfe\x89\xb9\xa5\x99\x02\xa7\x0a\x1d\x9c\xaa\xd0\x9c\xed\x8b\xee\x41\x45\xed\xcb\xe3\xef\x7f\xa6\xda\xb3\x76\x35\x12\x9f\x3b\x8c\x5e\x08\x60\x41\x0e\xcb\xd9\xce\xc3\xd8\x69\x36\x82\xf2\x5a\xec\x08\xb0\x71\xf0\x5d\xc8\x21\x3b\xac\x8c\xff\x5d\x52\xb5\x76\x65\x35\x60\xbc\x01\x57\x56\x04\xe6\xab\x90\xf6\x72\x27\xfb\x5c\x90\x1a\x78\x1e\xdd\xc0\x27\x70\x09\x13\xe5\x4a\x7f\xe5\x13\x18\x48\x2c\x9a\xb4\x2c\x9d\x2b\x91\x1b\x7c\xcc\x39\xcc\xb2\x90\xf9\xa4\x20\xa5\xda\xd9\x33\x94\xd4\xd7\xb8\xc5\x3f\xe3\xf2\x42", n, e, b"\x45\x06\x8c\xa6\xd8\x2f\x2c\x12\x39\x25\xcd\xe1\x19\x71\x21\x5d\x8f\xa4\xa4\xdf\x68\x48\xbb\x76\x54\x86\x87\x00\x97\x87\x64\x85\x46\x38\x92\x1b\xea\x58\x69\x28\x0d\xc6\xad\x95\x81\xab\x43\xff\x70\x12\x96\x99\x48\xa5\x67\x7f\xa0\xa6\x61\x36\xa3\x16\xa4\xbf\xec\xb8\x9a\xdf\x41\x31\xb5\xbe\xdf\x3d\x46\x93\xb7\x80\xd1\x33\xaf\x9b\xf9\xc1\x33\x30\x5b\xe7\x83\x74\xaf\xda\x3b\xa3\x85\x42\x03\x32\x44\x81\xa9\xd1\x0b\x9c\xa9\xb9\x2d\xc7\xd7\x4d\xf5\x31\x87\x2d\xdf\xc7\x6c\xaa\x82\xde\x02\x0e\x2c\x41\x56\x43\xcb\xcc\x42\x80\xe6\xd2\xf4\x37\x1f\xda\x7d\x92\x49\x31\x4a\x8f\x43\x76\x48\x99\x1a\x9b\x03\xd7\x1b\x58\x39\xad\x38\xa1\x55\x5a\xd3\x45\x26\x99\x4b\xa5\x68\x70\xb6\xea\x18\x01\x12\x95\xf2\xca\x2b\x07\x13\xb2\xe9\x2a\xd7\x76\x80\xc0\xdc\x5b\xed\x8d\x3b\x9b\x31\xac\x14\xdf\x76\x99\x49\xc4\xa4\x3e\xa6\x7f\x6d\xee\xb3\xdc\x9e\xd5\x89\xea\x4e\x8a\x2c\xf6\x69\x5d\xf4\x6f\x94\x6f\x14\x67\xb2\x8e\x87\x54\x77\xae\x4e\x64\x50\x80\xfa\xfd\xa6\xdd\x55\x1d\x2c\x02\xfd\x6b\x2b\x19\x4f\xc0\xbd\xb0\x50\xe0\x6d\x4c\x78\x41\x05\xf5\xa3\x3b\x53\xe7\x30\x98\x05\x59\x63\x07\x1e\xfc\x1b\xf3\x97\xfd\x32\x5f\x3a\x6f\x4e\x10\xd7\x6f\x04\x11\xa0\x01\xe6\x2e\xc7\x37\x29\x01\x83\x16\xf5\x63\x10\xf8\x93\xa5\x93\x63\xd1\xf6\xfe\x5c\x17\x44\x4b\x6c\x72\x8a\x49\x33\xb7\x52\x12\xfd\xfa\x25\x8e\x40\x18\xb7\x76\x39\x51\xab\x4e\x50\x96\x41\x1d\xf9\xe5\xbc\x16\xdf\x38\x96\xe4\x6c\x97\x3d\x32\xac\x92\x76\xa4\xe2\xb5\xb8\x0e\x3d\x8d\x79\x8d\xc0\x47\x0b\x45\x09\x6b\x4d\x73\x86\x69\xce\x05\x2e\xd8\x18\xe5\x60\xaf\x1e\x92\xc9\x15\x18\x7d\x66\xcc\x30\x8b\x70", )?; test( HashAlgorithm::SHA384, b"\x82\xb3\x84\x0e\xeb\x95\xc9\xc5\x77\x24\xc7\x0f\x11\x2b\x6c\x2d\xc6\x17\xc3\x17\x85\xac\xd0\xc8\x23\xf8\xbc\xdd\xa2\x85\x32\x5e\xb3\xd3\x08\xdc\x79\x05\x22\xbc\x90\xdb\x93\xd2\x4e\xe0\x06\x32\x49\xe5\x5d\x42\x19\xad\x97\x14\x5f\xea\xf7\xf3\x06\x68\x62\x3c\xc8\x89\x0a\x70\xf4\xf1\x49\x86\x6f\x82\xcf\x86\xf9\x8b\x00\x53\xb2\x3c\x98\xc8\xdd\x5e\x91\x07\xe3\x41\x46\x0e\x9b\xf5\xd8\x8c\xc8\xbc\xd1\xf2\xe4\xc0\x07\xcc\x1c\x02\xc4\x52\x9b\x93\x23\x3a\x0b\x06\xbd\xd1\x59\x25\x85\x4a\xb9\xe3\xf1\x56\xeb\x92\x5b\xf5", n, e, b"\x05\x93\xb9\xfd\x44\x21\x45\x23\x76\xd2\x7b\xc7\xa2\x80\x10\x1c\xfd\x6e\x88\xa6\x72\x7d\x7d\x77\xcf\x65\xce\xb7\x23\xec\xd2\x57\xf3\x2f\xe1\x02\x77\xe8\x57\x98\xe0\xda\x75\x91\x77\x36\xda\x1a\x3b\xfc\x22\xad\xc7\x65\x8f\xbb\x84\xda\x6e\xbe\xa0\xb0\x7d\x1c\xc4\x05\x73\x2f\xb0\x40\xb5\x85\xc1\xb6\x3c\x80\x34\x06\x9b\xff\xb8\x22\x06\x56\xf1\xac\x54\xce\x69\x37\x20\xd6\xfb\x1b\x5a\xec\x67\xb0\x3c\x88\x7c\x80\x77\xda\x14\x8d\x10\xf4\x8a\xf7\xc0\x28\xf9\x92\xb1\x8f\x13\xc0\xe5\x75\x30\xc0\x86\xd7\x75\x48\x3d\xa5\xf6\x6f\x3a\x6a\x19\x18\x78\x68\x34\x0a\xc6\x3c\x62\x12\xbc\xbd\x6c\xbb\x7b\xed\xa8\x62\x0a\xfd\x9b\x66\xde\x47\x47\x3e\xf2\x4d\x1b\x6a\x36\xf4\xec\xe9\xad\xd4\x95\x14\xfd\xf1\xd8\x4c\x7a\x78\x5b\x7f\x0e\x00\xf3\x82\x23\x58\x99\x79\x0f\x47\x2d\x13\xf4\x85\x58\xa4\x31\x47\x42\xf3\x76\x80\x8d\xec\x96\xed\xd2\xe2\x29\xe9\x43\xf7\xb9\x83\xbe\xa5\xec\x6e\xdf\xa5\xe9\xbb\x37\xf5\x88\xe5\x5e\xf6\x2e\xbc\x92\x14\xbe\xaf\x9d\xa5\x02\x43\x4e\x10\x88\xdf\x27\x2c\x6c\x77\xc1\xe1\xd8\x97\xc4\x7b\xea\xb7\x7e\x3b\xbe\x31\x7f\x8d\x43\xd2\x1f\xd7\xe9\x43\x37\xc7\xe2\x63\xe2\x86\x7b\xf5\x80\xa2\xa8\xec\xb9\xe3\x6a\xb7\xd3\xe1\xd5\xcf\x9a\x23\x23\x09\x53\xd5\x9d\xf0\xd7\xe2\x35\x58\xfb\x61\x2b\x79\x18\xab\xba\x31\xb1\x64\xce\x17\x88\x18\xa1\xa9\xe6\xb6\x68\x7f\x4d\xe6\x85\xd7\x0e\x16\xbe\xf6\xe1\x92\xfa\xed\xfe\x0b\x2b\x95\x47\x7d\x37\xb0\xa3\xa2\xd0\x02\xf3\x3e\xf4\x32\x1c\xb9\x05\x04\x0c\xe0\x6f\xda\x1c\x98\xa0\x08\x76\x7f\xbc\x78\x1a\x1e\xaf\x33\x75\xda\xb8\x66\x4b\x59\x03\x36\xb9\x9e\x15\x7b\x86\x87\xa6\x60\x2f\xef\x6a\x3b", )?; test( HashAlgorithm::SHA384, b"\xe1\x53\xcc\xa4\x43\x1e\xd9\x71\x3f\x47\x44\xba\x05\x4f\x5f\x19\x1c\xb3\x7b\x28\x01\x08\xae\x3a\x11\x4a\xd3\x49\xa8\x72\xd1\x30\x8b\x46\x21\x1a\x83\x75\x8a\x3b\x4b\xe3\x2f\xbe\xac\x42\xcc\xfe\xe7\xe2\x3d\xf8\x53\xca\x40\x01\x47\x07\x7b\xb4\x3a\x44\xc1\x2f\x29\x9b\x91\x7f\x3a\xab\xdf\x58\x9e\xeb\x17\x09\xbb\x3d\x60\xb0\x8b\xc7\x1e\xaa\x3f\xfe\xba\x4e\x29\x03\xa5\xdb\xd8\x33\x9a\xae\x85\xfa\x24\xb9\xae\xe7\x61\x30\x00\x06\x05\x85\x7a\x6a\xa1\x97\xd0\x09\x26\x27\x0d\xcd\xa5\x8b\x7d\xe7\x58\xa6\xca\x67\xe6\x17", n, e, b"\xa8\x35\xcd\x41\x46\xbe\xf4\x65\x64\x2d\x49\x49\x36\x26\x8a\x31\x1a\x54\x90\xd2\xc9\xf9\x16\x6c\x6c\xe9\x82\x16\xa9\xa2\x3a\x64\x35\x97\x30\x0a\x00\x50\xe6\x44\x5a\xbd\x5a\x9b\xfc\x7a\x2d\x9b\x70\x72\x6c\x82\x4c\x38\x3b\xf5\xac\xad\xdd\xdc\x34\xd4\x34\xa3\x1e\x53\x14\xd2\x5f\xb5\x8e\x25\x8f\x51\x88\x66\xc1\x36\xe5\x28\x55\xc1\x6f\xe6\x4f\xf8\xf1\xc4\xd6\x6c\x4e\x9e\x39\xb8\xcb\x11\x96\xd8\x09\x44\xd0\x74\x6c\x0a\x3e\x17\x69\xcd\x41\x67\xdf\x72\xab\x5e\x4c\x9d\xba\xe9\xcb\x35\xf4\x82\x8e\x12\x09\x9f\x9b\x36\xa5\xa7\x0c\x48\xd4\xae\xc9\x87\x2d\x7b\x19\xe1\x29\x1b\x33\xcb\xdf\x08\xa2\x26\x3d\x50\x0c\x0a\x83\xb5\x23\x7e\xf6\xce\x92\xde\x34\x4b\x3b\x41\xd0\xd0\x74\x04\xfc\xd5\x46\x7b\x04\x6b\x52\xb8\xf8\x5f\xc6\xb5\xd7\xaf\xc4\x37\xf1\xee\x9e\x78\x39\x0c\xa9\xbb\x6c\xec\x61\x88\x85\xec\xe2\x97\x58\xf2\xfd\x6f\x4e\x5f\x4f\x89\x69\x35\xde\x5f\x67\xcc\x04\x05\x5a\x4c\x4c\x0f\xba\x5d\xef\x8d\x2c\xaa\x17\x93\x31\xa8\x55\x01\xed\x25\x82\x2a\xe7\x9d\xa9\xbc\x81\x5c\xc3\x9c\x6a\x97\x92\x11\x08\x3e\x86\x83\x13\x6c\x94\x2e\x1e\x17\xe9\xeb\x8f\x84\xaa\xcf\x09\x1a\xa1\xe5\x16\x65\xfa\xe4\x46\xbc\x48\xc3\x04\xaf\x65\x39\x1f\x27\x9a\xfb\x98\xb9\x2e\x04\xc2\xb7\x3d\x9d\x94\xe9\x91\x19\x8f\xe7\x78\x1f\x0f\x96\x96\xfc\xba\x2c\x03\x48\x5f\x76\xe6\xde\x30\xb9\x53\x5c\xf3\x90\x3d\xb2\xf3\xaf\xa8\x51\xa4\x7b\xcd\xe7\x2d\x4e\xd2\xe8\xfa\xbf\x9b\xb7\xd4\x69\x6c\xb4\xab\x8c\x28\x9b\x0c\x21\xe1\xf9\x79\xeb\xc5\x32\xe2\x80\xcd\x90\x10\xdf\x4e\xe7\x2f\x84\xbb\x9e\x82\x75\x28\x28\xf1\x67\x03\x0c\x0f\xe3\x48\xeb\xc3\x1e\xc1\x7b\x8f\x07\xd9\x4b", )?; test( HashAlgorithm::SHA384, b"\x9c\x63\x89\x9d\xfc\x7b\xdc\x0d\xb3\x84\x72\x72\x44\xca\xf7\x1e\xcf\xb9\xb8\x79\x2b\x9f\x57\xe9\x36\xb3\xc2\xf5\x69\x55\x65\xa9\xb0\x97\x9f\x3c\x78\xfd\x73\xf0\x09\x81\x81\x3a\x16\xda\x34\x23\x92\xfe\x3c\xee\xc6\xe6\x3f\xfb\xa1\x91\xcb\xeb\x4f\x4b\x90\x05\x0d\x2f\xcc\xd8\x3b\xeb\x06\x22\xb2\xc3\xff\xf1\x59\xd9\xe6\x08\xf3\xab\xcb\x84\x3b\xdd\x56\xc0\x33\x39\xb9\x75\xb9\xf4\xe3\x26\x5b\x32\xf6\xbb\x6c\xcd\xfc\x6c\x57\x52\xd6\xe0\x34\x4d\x74\x96\x99\xc7\x4c\x85\xb3\x0c\x04\xff\x95\xb2\x72\xdb\xcf\xd6\xc7\xd3", n, e, b"\x4d\x38\xa2\x97\x30\x2a\xd0\x77\x0d\x97\x29\xce\x5b\x72\x12\xee\xf2\x87\xce\x02\x50\xf4\x03\xe3\x2b\x4a\xcc\x36\x17\xdc\x0d\x2e\xdc\xcc\xc2\xd5\x80\xdd\xbd\xbc\xa5\x72\x2b\x70\x70\x40\x58\xa3\xb8\x07\xf5\x92\xe4\x00\xbd\x56\x3f\xca\xa8\xb0\x66\xa6\x14\xb4\x90\x6f\x14\x33\x96\x8e\xd2\xf5\x20\xa2\xf6\xb0\x34\xd4\xb2\xd6\x89\x0a\x24\x1a\xfd\x1a\xdb\x86\x39\xa6\xca\xd9\xdb\xfd\x2e\x27\x8d\xfe\xbf\x79\x74\x0d\x75\xf2\x95\x75\x9d\x29\x13\x0b\x19\xab\x19\x98\x3d\xd6\x8f\x77\x9d\xe4\x1f\xfe\xfd\x4e\x82\xb5\xe6\x2f\x72\xf9\x0e\xfb\x73\x43\x7f\x08\xa2\x50\x3d\xd9\x81\x9d\xae\x20\xba\x97\x06\xc1\x99\xde\x9c\xf8\x84\x43\x3e\xeb\x75\x62\x86\xa8\x5e\xae\x14\xbf\x9f\x6d\xbe\xb7\x05\x46\x1d\x91\x82\x22\x82\xf1\x8e\xfb\xb1\x05\x89\xa5\x78\xf2\xc9\xc3\x45\xb0\x79\xa7\xe9\xdd\x07\xfd\x4b\x34\x05\x1b\x27\x11\x97\x29\x90\x6c\x77\xdf\xb7\xd2\xf8\xfa\x6b\xdd\x5f\xaa\x1e\x13\x2b\xfb\xa9\xd3\x91\xe6\x63\x95\xe6\x7f\x01\x35\x3f\xa2\x75\xea\xce\x8b\x53\xaa\x91\xcb\x6f\xb6\x93\xe1\x91\x91\xd4\x2a\x4c\x1a\x85\xa0\xc5\x04\xb1\xc8\x5f\x49\xa4\xd6\x09\x36\xde\xe4\x64\x6a\xca\x62\xa9\x4a\xa4\xbc\x78\x28\xc1\xff\xaf\xde\x8b\xe6\x56\x31\x7d\x50\x6a\xbe\xc1\x79\xcc\x90\x19\x1d\x12\x35\x6f\xf5\x06\x44\xd3\xe0\x1a\xa5\xbc\xfd\xd7\x1d\x3c\x82\x8d\xc3\x53\x9d\xc0\xcf\x3f\xe8\xb9\xb9\x1e\x0c\x25\x24\xf6\xa3\x71\x03\x79\xc9\x0a\xff\xd0\xd0\xa5\x0d\x74\x38\x7f\x9c\xa8\x8b\x46\x46\x3e\xf1\xbd\xba\x58\xcc\x9a\x36\xe5\xc2\xc4\x35\xa2\x0d\x96\x83\x50\xd1\x5d\x94\x1c\x32\x12\xcd\xce\x81\x55\x92\xb3\x10\xd2\x59\x86\x0d\xe1\xdc\x1a\x3d\x70\xac\x22\x30\x2a\x51", )?; test( HashAlgorithm::SHA384, b"\x04\x84\x6c\x2e\x67\x6a\xc7\x31\x60\xbf\x4e\x45\x65\x2b\xdc\x6c\xc4\xd4\xc9\x28\x45\x77\xb4\x32\x0a\xb7\x7f\x6e\xbb\xb5\x9a\x1f\xe0\xe0\x85\x58\x8e\x0f\x90\xb3\x46\xcd\xe6\x44\x1a\xf3\xc9\xd0\x11\x7d\x1f\x3b\xcd\x96\x2e\x40\x6b\xf5\xa4\x65\xab\x6c\xda\x2d\x51\xbe\x59\x8f\xcb\xb2\x9e\xa7\x13\x65\x1a\xac\xd7\xe4\x7d\x22\xd8\xfa\x34\x50\x90\x47\x30\xf5\x17\x92\xea\x37\x47\x61\xa4\xdc\x1f\xc6\xf1\xbc\x65\x7b\x77\x76\x8f\x31\xf4\x63\xe4\x26\x7f\xc8\xdf\xf6\x11\x50\xd4\xb3\x43\xb9\xd5\x37\x59\xcd\xd7\xb9\x80\x94", n, e, b"\x10\x3b\xee\x57\xe2\x5b\xe8\xc3\xa2\xf7\x74\xe7\x39\xb4\x7f\x93\x43\x5e\x41\x49\x32\xc0\x49\x4b\x6b\x6a\xa2\x47\x5b\xf7\xc9\x30\x5c\x73\x74\x7e\x0a\xdf\x82\xc2\x03\x20\x07\xb3\xf7\x5a\x69\xc9\x31\x12\x61\x7a\x62\x56\x6c\x5a\x2d\xea\xa2\x5f\xb9\x52\x09\xda\x49\xfe\x9c\x16\x1c\xb2\xff\xa4\x0f\xd9\xd7\x7f\x1f\xf6\x60\xc8\xb6\xcd\x3b\x54\xe3\xe7\x9a\x75\x9c\x57\xc5\x71\x98\x02\xc9\x31\x1d\xb7\x04\xba\x3c\x67\xb4\xa3\x11\x37\x54\xa4\x1b\x8d\xa5\x9c\x64\x5b\xe3\x90\x9e\x7d\xb7\xe7\xcf\x72\x94\xda\xb4\x4f\x74\x24\x0f\x81\xa2\x81\xee\xcd\x6e\xf3\x1c\x7c\xf1\x8b\x1a\x19\xc7\xd0\x2a\x31\x2b\x91\xd6\xed\xfa\xa9\x54\x46\x2d\x34\x74\x0a\xf5\xab\x70\x8d\xb5\xa1\x0b\x00\xc5\x42\xbe\x82\xfa\x2b\x20\x26\xb0\x9e\xf3\x8a\x40\x01\x45\x7e\x27\xa6\x02\x37\x70\xe4\xb4\xd5\x00\x32\x67\xc8\x5c\x9e\xea\x1d\x5f\x8d\x77\x0b\xd4\x0b\x55\x4d\x5b\x4d\xaf\x14\x6d\xcc\xab\xac\x3e\xa8\xa1\x3a\x05\xc3\xbd\xdf\xc9\x71\xc5\x15\x8f\xac\x02\x7c\xa1\x9b\x72\x32\x62\x1e\x9d\x2e\x37\xb6\xa6\x55\xaf\x54\x5e\x44\xa2\x98\xbe\x78\xcd\x47\x5c\x22\xa4\x8b\xff\x7c\x34\x94\xa5\xf8\xa6\xab\xdf\x1a\x46\xf9\xde\x08\x2e\x37\x4f\xd5\x98\x86\x7d\x61\xe4\xd5\x1d\xae\xd8\x41\x52\xe4\x3c\xc6\xa2\xaf\xfa\xe2\x05\xed\xc5\x26\x13\x48\x0d\x41\x1a\xba\x84\xfc\xc9\xb6\x9d\x1c\x28\xf1\x6f\x76\x83\x69\x01\xa7\xc5\xb3\xeb\x2f\x2c\x94\x0d\x0a\x3f\xad\x38\xa8\xef\xab\x96\x8a\x0c\x85\xeb\x22\xe1\x1d\x3d\x08\x61\x13\x6c\xed\x5f\x06\x73\x4f\xdf\x8d\x4f\x15\x1d\x23\x86\x1b\x1c\xba\x9b\x9c\x58\x0d\x33\x50\xc7\x6d\x4d\xc8\x08\x46\x1d\x5f\x87\x2e\xc5\x48\xb2\xb4\x27\xdf\xf7\x4b\x1d\x1a", )?; test( HashAlgorithm::SHA512, b"\xdb\x6c\x9d\x4b\xad\xb1\xd9\xb7\x4d\x68\x34\x64\x48\xb4\xd5\x34\x06\x31\x78\x3b\x5a\x35\xac\x24\x58\x56\x3e\xd0\x67\x2c\xf5\x41\x97\x58\x7f\xb7\x34\xc4\xac\x18\x9b\x2d\xda\x95\x4c\xdf\xb1\x8b\x41\xc0\x10\xa7\x7e\x90\x46\x4e\xea\x6f\x86\x3c\x5d\xa0\x95\x6b\xfa\x8c\xc6\x36\xbf\x0a\x28\xbe\x5a\xdd\xfe\x8d\x3e\x7e\x6f\x79\xf7\x1d\x7f\xcb\xba\xe2\x3e\xa1\x41\x78\x3f\x91\xd6\xcc\x4c\x8f\xad\x12\x58\x11\x76\x0a\xb5\x71\x33\x81\x88\x92\x47\x1a\x79\xc6\xd0\x4e\xaf\xef\x37\xb2\xfb\xe5\x06\x78\x53\x18\xf9\x39\x83\x77", n, e, b"\xd4\x80\xd5\xa9\x79\xad\x1a\x0c\x4c\xa3\x29\xeb\xd8\x8a\x4a\xa6\x94\x8a\x8c\xf6\x6a\x3c\x0b\xfe\xe2\x25\x44\x09\xc5\x30\x54\xd6\xff\xf5\x9f\x72\xa4\x6f\x02\xc6\x68\x14\x6a\x14\x4f\x8f\x2b\xa7\xc4\xe6\xb4\xde\x31\x40\x0e\xba\x00\xae\x3e\xe8\x75\x89\xdc\xb6\xea\x13\x9e\x70\xf7\x70\x4f\x69\x1b\xc3\x7d\x72\x2f\x62\xbb\x3b\x2c\xd3\x03\xa3\x4d\x92\xfd\xe4\xde\xb5\x4a\x64\xdd\x39\x18\x43\x82\xd5\x9c\xca\xf0\xc0\x7a\x7e\xa4\x10\x7d\x08\x08\x26\x0e\xd8\xd4\x21\xcb\x8b\x14\x07\xcd\xf9\xe9\x15\x15\x92\x82\xb9\xf7\xbf\xfd\xbf\x40\xd8\x77\x88\x5d\xa7\x39\x9e\xde\xbd\x30\x0a\x7e\x77\xa9\x08\xf7\x56\x65\x9a\x18\x24\xf9\x5c\x8a\x81\x2a\xa5\x40\xeb\xaa\x64\xab\x54\xa2\x33\x72\x3d\xb5\x5c\xaa\x8b\x44\x66\xea\x9a\xe6\x61\x4a\xd1\xbb\x86\x9e\x9d\x8e\x0d\x03\x2f\x39\x01\x67\x1e\x94\xc0\xb6\x73\xbe\x65\x37\xcd\x54\x27\x8e\xd3\xda\x2e\x1e\xdb\xc0\x4e\xe3\xa9\xe8\x07\x0d\x73\xba\x0f\xfb\x93\xe6\x0f\x30\xb8\x7f\xf3\x86\x2e\x9c\x53\x90\x8f\x2c\x8e\x99\x91\x56\x68\xc1\xf4\x66\x35\xe0\x5b\xf7\x16\x30\x51\xff\x9d\x92\xbc\x71\xa6\x26\x55\x3c\x69\xdf\xdd\x06\xa4\x9f\x7f\xf1\xed\x51\xe9\x18\xf3\xed\x80\x1d\xae\x62\xca\x27\x6d\x70\x63\xd7\x2a\x6e\xbc\x13\x6b\xa0\x6c\xfe\xdf\x5a\xa2\x32\x77\xe8\x10\x08\xc6\x3b\x2e\x00\x83\xd0\xfd\x68\x14\xf6\xd4\xb4\xb4\x0a\x42\xe8\xc0\x20\x6f\x3c\x35\x6a\x5e\xc7\x09\xb7\xc8\xa4\xb7\x4b\x7b\x48\xd5\x3c\x9d\x86\x94\xd2\x73\x59\xc2\xc7\x70\x19\x38\xd2\xf0\x16\x17\x21\xa5\x73\x13\xbb\x1a\x2e\x11\xda\x21\x58\x72\x49\x81\x82\x49\x3d\x85\x17\x04\x3b\x4c\x03\xf9\x34\x46\xaa\xc9\x38\x30\x27\x65\x42\x02\x6c\xe8\x30\x55", )?; test( HashAlgorithm::SHA512, b"\xd5\xdd\x3b\x6c\xe9\x77\x2d\x9a\x97\xfe\x21\x64\x84\x97\x78\x3b\xac\x5b\xb5\x25\x4a\xad\x82\xb6\xf7\xcb\xf4\x3b\x15\xa4\x0f\x38\x6e\xea\x8d\x15\x19\x67\xdb\x14\x9e\x94\x65\x86\x59\x68\x13\x3f\x24\x6e\x13\x47\x30\x1a\xda\xd2\x34\x5d\x65\x72\xca\x77\xc5\x8c\x15\x0d\xda\x09\xa8\x7b\x5f\x4d\xa3\x6b\x26\x6d\x1f\xa7\xa5\x9c\xcd\x2b\xb2\xe7\xd9\x7f\x8b\x23\x15\x43\x19\x23\x53\x0b\x76\x2e\x12\x6e\xac\xaf\x5e\x5a\xc0\x2f\xf1\xaa\xef\x81\x9e\xfb\x37\x3c\xf0\xbb\x19\x6f\x0e\x82\x9e\x8f\xe1\xa6\x98\xb4\x79\x0a\x2a\x05", n, e, b"\xbf\x9e\x8b\x4f\x2a\xe5\x13\xf7\x3d\x78\x89\x58\x00\x37\x33\xdb\xe2\x09\x57\xb1\x47\xb1\x7c\x3f\x4f\xd6\xd0\x24\xe8\xe8\x3f\x07\xb6\x5d\x9f\x3d\xbc\x3b\x1f\xe8\x4d\xa0\x21\xce\xab\xfc\xcd\x8c\x57\xa0\x14\xfb\xe5\xa2\xbc\xe3\xe4\x05\x1b\x7d\x03\xe0\x9f\xc0\x35\x0b\x6a\x21\xfa\xd2\x14\xae\x7a\x07\x32\x77\xc7\x7a\x40\xdc\x44\xa5\xae\xea\x51\x94\xa7\x56\xb6\x9c\x93\x97\x7b\x69\xee\x92\x94\x36\x0e\xaa\x73\xa5\x74\x54\x8f\xa6\xa9\x74\xa7\xcd\x5a\x6a\xdc\xf0\x9e\x80\x63\x11\x56\xaf\x85\xa8\xe5\xc5\x31\x7e\x18\x9e\xea\xd4\x7e\x2e\xad\x65\xc3\x81\x39\x6b\x5c\xac\xde\x26\x0e\x93\x72\x84\xa8\xe9\x0e\xff\x2c\xbc\xb9\xde\xe2\x29\x25\xf2\xf7\x25\x6f\x74\xc6\x7c\xf3\xff\xc7\xb8\xce\x65\x7e\x8d\x13\x5f\x0f\x37\x6d\x9d\x93\x6a\x79\x79\x2c\x98\x16\x14\xd9\x8e\x3f\x7d\x66\x2a\x4f\xd4\x6d\xcd\xa9\x69\x16\xb3\x2f\x36\x6e\xd2\x7d\xab\x18\x8f\x18\x4b\x98\x4d\xf0\xb5\x59\x71\x0d\x8f\xf2\x04\x0b\xe4\x62\xf9\x19\x43\x50\x1b\xda\x48\x40\xfd\xd5\xc8\xec\x15\xd1\x89\x06\x4d\xef\x75\x6e\x54\x5d\xb3\x19\xe0\x07\xc4\x33\xf0\x46\x8a\x67\x23\x35\x7b\xa4\x7d\x15\x6a\xb7\x65\x2b\x06\xae\x2b\x18\x87\x4f\x07\x71\xc6\x26\x46\x6d\xbd\x64\x23\xe6\xcb\xc5\x18\xb5\xe4\xae\x7b\x8f\x15\xe0\xf2\xd0\x47\x1a\x95\x16\xdf\xa9\x59\x16\x97\xf7\x42\x86\x23\x24\xd8\xd1\x03\xfb\x63\x1d\x6c\x20\x73\xd4\x06\xb6\x5c\xde\xe7\xbd\xa5\x43\xe2\xe9\xeb\xff\x99\x06\x98\x5d\x1c\xb3\x65\x17\x2e\xa6\x23\xed\x7a\xa4\xc7\xa3\x22\xf0\x98\x46\x80\xe3\x4e\x99\xbc\x62\x31\xb0\x2e\x3d\x14\x58\x16\x08\xbc\x55\xbc\xa7\xfb\xe2\x2d\x7f\x03\xe9\x04\xda\x45\x52\xe0\x09\xe5\x60\x7f\x04\x18", )?; test( HashAlgorithm::SHA512, b"\x59\x16\x52\xb6\xeb\x1b\x52\xc9\xbe\xbd\x58\x32\x56\xc2\x22\x86\x80\x11\x0b\x87\x89\x17\xde\xa5\xad\x69\xe8\xc5\xd2\xab\x51\x42\x77\xb0\xac\x31\xe7\xe2\xcc\xea\xb2\xe5\xd9\xc4\x5d\x77\xa4\x1f\x59\x9b\x38\xa8\x32\xf6\xb2\xd8\x09\x79\x52\xbe\x44\x40\xd1\xff\x84\xba\xf5\x1b\xd7\x0b\x64\xf1\x30\xae\xb6\x86\x14\x5f\xcd\x02\x95\x38\x69\xfb\x84\x1a\xf7\xf6\xe3\x4e\xaa\x2b\x99\x6c\xcd\x89\x69\x7c\x58\xfa\x25\x5c\xc1\xe8\x1f\x62\x14\x00\xe1\x41\x46\x36\x1e\x31\xc7\x09\xe8\x4a\x56\x08\x22\x31\x19\x95\x39\xf7\xed\xe9", n, e, b"\x1d\xe7\x9d\x72\x16\xdd\xe1\x25\xde\xb7\x7c\x34\xd9\x0a\xb3\x21\xa4\xde\x5f\xb1\x1c\x29\x66\x56\xad\x9b\xf9\xa2\x46\x53\x59\x11\x17\xac\xe4\x15\xe1\x8e\xad\xce\x92\x82\x3f\x31\xaf\xe5\x6f\xc8\xe2\x94\x94\xe3\x7c\xf2\xba\x85\xab\xc3\xba\xc6\x6e\x01\x95\x84\x79\x9a\xee\x23\x4a\xd5\x55\x9e\x21\xc7\xfd\x4f\xfd\x24\xd8\x26\x49\xf6\x79\xb4\xc0\x5d\x8c\x15\xd3\xd4\x57\x4a\x2e\x76\xb1\xf3\xee\x9f\x8d\xec\x0a\xf6\x0b\x0c\xed\x1b\xe8\xa1\x9c\x2f\xa7\x1b\xcb\xc1\xfb\x19\x08\x99\xec\x85\x56\x95\x8e\x07\x82\xac\xe7\x19\x6b\x36\x65\x86\x56\xcf\x36\x4d\x37\x73\xde\x86\x26\x0f\xd8\x98\x76\x04\xef\x35\xea\xe8\xf3\x8e\xc2\xcb\x0d\xa8\x64\xcc\xa7\x19\x21\x9c\x2a\xd7\x1c\x08\x50\x6c\x41\x2e\xc7\x79\x95\xf3\x74\x39\xc8\x56\x97\x7b\x71\xdf\xb9\x64\x79\x90\xef\x70\xfa\xf4\x32\x73\xae\x60\x83\x9c\xd0\x67\x9e\xc9\xaa\x42\xbf\x91\x4e\x42\x1b\x79\x7c\xba\x21\x8a\x40\x0f\xf9\xdb\xaa\x20\x6c\xb9\xc2\xb0\x59\x6c\x70\x9a\x32\x2b\x73\xcb\x82\x72\x1d\x79\xf9\xdb\x24\x21\x1b\xf0\x75\xa1\xce\xf7\x4e\x8f\x6d\x2b\xa0\x7f\xe0\xdc\x8a\x60\xf4\x8a\xf5\x11\xad\x46\x9d\xcd\x06\xe0\x7a\x4c\xe6\x80\x72\x13\x9c\x46\xd8\xbe\x5e\x72\x12\x53\xc3\xb1\x8b\x3c\x94\x48\x5c\xe5\x5c\x0e\x7c\x1c\xbc\x39\xb7\x7b\xc6\xbb\x7e\x5e\x9f\x42\xb1\x53\x9e\x44\x2d\xa8\x57\x65\x8c\x9e\x77\x1c\xcb\x86\xbe\x73\x97\x64\x7e\xfb\xc0\xcc\xb2\xc3\xad\x31\xac\x4e\x32\xbf\x24\x8c\xc0\xce\xd3\xa4\xf0\x94\x52\x6b\x25\x63\x1c\xb5\x02\x47\x09\x61\x29\xb0\x8a\x9c\x2c\xdf\xb7\x75\x97\x8b\x0f\xee\xe2\x65\xa6\xc4\x19\x91\xc1\xdc\x44\x52\x61\x5b\x78\xc9\x06\xc7\xed\x1b\xd2\x07\x96\x9d\x98\xd0", )?; test( HashAlgorithm::SHA512, b"\x8d\xff\xaa\x91\x51\x27\x1a\xd2\x26\x22\xf2\x28\xc8\x92\xe1\xd9\x74\x8b\x3c\x39\x43\x97\xf2\xcb\xb6\xfe\xbe\xaa\x92\x44\xa0\x27\xee\xf2\x8d\xb4\x8a\x9a\x66\x01\x62\x15\x27\x64\x83\x0f\x61\x7e\x1e\xc6\xea\x1c\xdb\x0e\xd2\x5b\x6f\x99\x9a\x10\x71\x75\xa1\x66\x69\xd6\xdf\xc9\x2b\x16\xd5\x03\x63\xfa\xc4\xa5\x70\x37\x1e\xa9\x76\x34\x3a\x55\xae\x12\x4b\x63\x01\xea\x93\x5e\xd6\x55\xd4\x4f\x28\x32\x08\x99\xdb\xa3\x51\x22\x50\x59\x33\xb3\x37\x12\x01\xa2\xa4\x5f\x95\xae\x65\xab\x44\x2a\x94\x79\x12\x5e\x68\xed\x21\x2a", n, e, b"\xb3\x29\xae\xf8\x3a\x56\xdd\xc5\x7c\xd9\xa0\xe1\x5e\xb0\xb0\xb7\xae\xa7\xd7\x8d\x5e\x8c\xa3\x98\x2b\xd3\x1c\xc8\x25\xa0\xcd\x1c\x44\x4d\x9f\x7b\xea\x9e\x7a\x27\xf3\xbb\xb3\x76\x10\x60\xff\x95\xfe\xe1\xa3\xe8\x64\xd2\x10\x8f\xc4\x0b\x64\x78\x6a\x96\xa6\xd6\x2d\x20\x12\x17\xe0\x3a\x8b\xa2\xc0\x7e\xe9\x4c\x26\x71\x49\xd1\xe7\x2c\xc5\x77\x9b\x73\x7e\x85\x47\xac\xd6\xaa\x4b\xba\x3f\xf3\x8b\xf9\x68\x7e\x9e\x82\xf5\x11\xb5\x97\xad\x7e\xc1\xd7\x95\xc3\x6a\x98\xbf\x83\xa9\x0f\xc8\x6b\x0c\xad\x41\x95\x33\x60\x73\x89\x21\x93\x6a\x45\x86\x74\xb2\xe9\xa7\x01\x2a\xc3\x02\x9f\xdb\x0a\x9d\x12\x31\x82\x02\xd2\x54\x4a\x0d\x97\x6e\xe5\x36\xe0\x3b\x7e\x8d\x89\x4b\x3b\x9c\x76\x2d\xab\x01\x10\x84\x9c\xc1\xea\xad\x74\x7e\x3d\x88\xd7\xdc\xf4\x9f\x82\x4d\xf0\x27\xe6\x45\xc0\xb9\x29\x4e\x65\x5d\x9f\xc9\xe1\xef\x95\xeb\x53\xaa\xff\x57\x75\xc3\x49\x48\x6d\x4b\x5d\x67\xdb\xa2\x9b\x62\x17\xf8\xb9\x97\x66\x12\xb5\x7e\x16\xfc\x1f\x99\x98\x3f\x2a\xf0\x45\x79\x93\x86\x06\x87\x9b\x7c\x72\x53\xe8\x70\x71\x4b\x4f\x0f\x24\xe2\x6d\xc8\xc7\xa6\xfc\xef\xfb\x5f\x98\xe3\xb2\xfb\x5d\xb9\x49\xd2\xf9\x8c\xd1\xae\x1a\xa5\x52\x69\x6b\x48\xc3\x9f\x67\x8e\x15\x43\x51\xcc\x75\x6d\x3e\x9a\x97\xf7\x92\x79\x85\x3e\xbd\x0d\xb9\xae\x68\x59\xfb\x2d\x57\x21\x38\x5d\x06\xf5\x56\x5a\x3a\x8f\xf0\x99\x2d\x51\x7a\xcd\xa1\xaf\x69\xa9\x28\x54\xa1\xb3\x2a\x79\xcb\x9e\x44\x2a\x90\xb0\x55\xbb\x2e\xc3\xaf\x8d\x99\x26\xa0\xd8\x57\xe3\xcb\x1e\x7e\x4a\x73\x00\xd1\xac\xcb\x94\x92\xec\x78\x32\xaf\x45\x35\x29\xff\x0f\x4a\x6a\xd3\x25\x97\x57\xf7\x07\xf7\x13\xaa\xa5\xdf\x23\x1f\x74\x87", )?; test( HashAlgorithm::SHA512, b"\x71\xd4\x16\x3e\x70\x8c\x12\x1e\x93\x1b\xb9\x69\x2b\x21\x7d\xdd\xd3\x5c\x73\x46\xf6\x1c\xfc\x95\x91\xf7\xa4\x31\x3a\xbd\x4a\x92\x62\xaf\x82\x0b\xd7\xeb\x37\xe7\x8c\x2b\x95\xb8\x9d\xaf\x25\xec\x8e\x78\x3a\xa1\xd4\xb7\x8d\xbb\x96\x85\x24\x33\xb4\xd4\x78\xb1\x09\xa6\xd6\x5e\xed\x7d\x06\xf3\xfe\x12\x2b\x17\x21\x49\xea\xe7\xc3\x65\xce\xd6\x65\x78\xeb\xb7\x57\x1e\xc2\x18\xc3\x6b\x65\xd2\xee\x22\xdc\xde\xbb\x28\xc6\x6a\x71\x38\x43\x2c\xbd\xd7\x12\xf7\xfb\x8b\xf7\x8c\xb1\x48\x60\xb2\x5c\x2b\x47\x89\x70\x6b\x5a\x1b", n, e, b"\x25\x22\xee\x3b\xda\x30\xc0\x43\x4e\x54\xb1\x99\xda\x8c\x97\x33\x96\x4f\xd4\x02\xb7\x07\xf5\xb3\x30\xf4\xf7\x54\xa0\x50\x2c\x7a\x71\x3c\x78\x14\xf0\xe8\x51\xa4\xa4\xdb\x72\x69\x0d\xb9\x6e\xa8\xb8\x81\x3b\xd8\x62\x9a\x94\x8b\xb3\x0c\x1b\x82\x72\xa8\x16\xb3\x0a\x75\x5f\xc6\xfb\x17\x54\x16\x7c\x3e\xb1\xf1\x94\x39\x59\x07\xa5\x6c\xf5\xa7\x3b\x41\x54\x38\x3a\x05\xb7\x8b\x73\x1f\xed\xd9\x07\x7f\x3c\x22\x67\xa5\xcf\x92\x66\x97\x87\x1f\xe0\xa4\xbe\xd9\xc2\x19\x55\x2d\xd1\xc8\x7a\xff\x50\x61\x30\x94\xbc\xaa\x2d\xec\x42\xa3\x53\x80\xa6\xba\xc6\x73\xda\x25\x94\xf8\x24\xa8\xf3\x2f\x21\xd7\x59\x3a\x3e\x49\xc7\x8e\xe2\x80\x19\x3a\x47\x86\x21\xd3\xb0\x95\xc1\x6d\xce\x72\x93\x53\x14\xd4\xa2\x32\x3e\xeb\xe7\x85\x5c\xa4\x73\x8a\x19\xb5\xa3\x1a\x5f\x95\xab\x91\xfb\xe1\x28\x9c\x02\xfe\xa7\xa6\x5b\x91\x32\x7b\x7b\x97\x90\x55\x62\x89\xe1\xb9\x88\xe4\x5d\x50\xeb\x8c\xea\x15\x81\xde\x5d\x5d\xfd\x21\x00\x1c\x73\xb4\x39\x21\xd8\xb2\x1b\x96\x44\xb0\xf2\xb9\x6e\xe6\xb0\x9d\x73\x70\x9c\x33\x33\x81\x43\xd6\xa2\xfe\xc5\x59\xa4\x36\xc5\xec\x86\x5d\x3a\xcc\xa5\xfe\xe6\x54\xf1\x32\x5a\xe5\x72\x55\xdf\xd4\x21\x88\xc8\x4d\xcb\x1f\x7c\x1e\x86\x02\x8a\x74\xe3\x1d\x73\x60\x78\x74\x1e\xe9\x7c\x39\xa5\x6e\x4d\xe0\x0f\xc1\x2b\x80\x51\x83\x5b\xbd\x0d\x8f\xca\xe7\x37\x32\x20\x99\xad\xc1\x01\x71\x07\x02\x2d\xd1\x5c\x11\x4d\xa5\x7e\x78\xb9\x56\x81\xba\x99\x45\x61\x5b\x59\xda\x90\xf5\xa2\xa9\x9a\x25\x2e\xb4\x2b\x20\x06\xee\xdd\x6e\x78\x47\x6c\x29\x05\x47\x3e\xe6\xb4\xf2\x3c\x1c\x5c\xf0\xb8\x04\x51\xc5\x42\x6e\xa0\x09\x14\x1c\xb3\xfc\xb0\xdf\x2d\xed\x92\xbe", )?; test( HashAlgorithm::SHA512, b"\xd0\x0e\x15\x29\x22\x8c\x79\xa2\x0a\x1c\x36\x68\xff\xa4\xa5\x41\x40\xbb\x17\x0b\xc5\xc6\x69\xfd\x75\x60\xd9\x30\x99\x00\x17\x5e\x91\xd5\xa0\xe9\xc5\xf5\x47\x1f\xdf\xb7\x14\xbc\x38\x5d\x52\xb0\x8f\xf7\xe4\x23\x01\x84\xd8\xb7\x35\x59\x3f\x0d\xd8\xc7\x3b\x8a\x49\xf8\x59\x5b\x95\x1a\x21\xb6\xa5\xbf\xec\x63\xb6\x84\xf6\x7c\x0a\xf1\xb4\x71\xdd\xa1\x68\x4e\x9b\xa3\xf2\x41\x50\x1f\xe9\x57\x60\x3d\xea\x86\x78\x42\x30\xf0\xc4\xfd\x65\x66\x63\x61\xb8\x2b\x18\x73\x30\xfb\x42\x67\x40\x4c\x0e\x05\x9b\xd4\xeb\x52\x49\x4b", n, e, b"\x18\x35\xdd\x97\xe5\x09\x3a\x33\xce\x1e\x62\xd6\x83\x86\x3f\x6b\x35\x07\xf3\x58\xa6\x2f\xc8\x79\xb5\x24\x35\x0f\xbc\x73\x30\x68\x1c\xb0\xc6\x82\xee\xf4\x33\x04\x19\xca\xf8\x54\x3b\xd9\x26\x9b\x6d\x91\xd8\xe1\x07\xec\x38\xb6\xe9\xc6\xea\xab\xf9\x06\x45\x72\x05\xd5\x2a\x90\x0e\x05\x57\x9a\xa1\x1f\xc5\x81\x37\x52\x64\xe6\x9a\x92\x57\x98\xe5\xa3\x48\xe5\xa1\x6f\x15\x67\xd5\xd0\xe4\x08\x53\x38\x0b\x34\xde\xac\x93\xad\x73\x77\xaa\xe8\xa2\x7b\x09\x0d\x0d\x3a\x92\xbf\x7a\x82\x4d\x92\x6e\x2e\x35\xa0\xc3\xbd\x0e\x99\x0b\x59\x11\x20\xd7\x4d\xd9\xb0\x52\xa7\x35\x68\xe3\xc3\xf2\x9c\x5a\x77\xfb\x1c\x92\x1b\xce\x9c\x1e\x7f\x76\x4a\xa6\x7b\xac\x11\x9f\x58\x39\xa5\x30\x38\x60\xed\xeb\x63\x48\x14\xc2\x38\x6c\x83\x1f\xee\x62\x00\xcf\x55\xb6\xbf\xea\x05\x8b\x79\x5a\x0f\xcf\x26\xeb\x72\x16\xae\x1b\x75\x87\xc8\x2e\x56\x85\xe5\x84\x17\x0c\xbd\xdc\x89\xa7\x7e\x09\x89\xd4\xce\x5c\x3c\x7f\xdb\x66\x4a\xae\xaa\xdb\xce\x1f\x23\x1e\x64\x79\x8f\x6f\x9a\x85\x45\x6b\x5a\x93\xa5\x02\x12\x6a\x80\xe2\xd2\x1f\x46\x92\x1c\xc3\x60\x1f\x5e\xcd\xbd\x56\x99\x8a\x63\xb8\x65\xfc\xe7\xeb\x29\x9f\x76\xaf\x40\xe9\x12\x81\xbf\xc0\x19\xf4\x0e\x0d\x46\x81\x1e\x38\x36\x91\xe4\x02\x4c\x94\x56\x6f\x18\x02\x4f\xf2\xb2\x2a\xa7\xe1\x27\x02\x33\xff\x16\xe9\x2f\x89\xc6\x85\x09\xea\x0b\xe2\xd3\x45\x11\x58\x1d\x47\x22\x07\xd1\xb6\x5f\x7e\xde\x45\x13\x3d\xe8\x7a\x5f\xfb\x92\x62\xc1\xff\x84\x08\x8f\xf0\x4c\x01\x83\xf4\x84\x67\x99\x6a\x94\xd8\x2b\xa7\x51\x0c\xb0\xb3\x6c\xf2\x54\x82\x09\xa5\x06\x03\x37\x5c\xb8\x2e\x67\x8f\x51\x49\x33\x45\xca\x33\xf9\x34\x5f\xfd\xf5\x4b\xe9", )?; test( HashAlgorithm::SHA512, b"\xa3\x59\x26\x68\x55\x61\xf0\x9f\x30\x92\x5e\x94\xd7\x4e\x56\x61\x89\x2a\x2d\xdd\x52\x4f\x75\x1f\x83\x21\x16\x3d\x61\x1e\xa1\x59\x1a\x08\xe0\xdf\xfd\x46\xb2\x08\xe9\x88\x15\xa3\x06\xaa\x85\x14\xb4\xdb\x85\x9d\xc1\xfe\x7b\xdc\xdf\x50\xc0\x95\x55\x4b\xf8\xb2\xf4\xcb\x9f\x88\x4d\x70\xe5\x5c\x21\x43\xbc\x26\x19\x9c\x2f\x94\xb7\x43\xf5\x52\x8d\xd5\x46\x89\xad\x69\xed\xa6\x60\x74\x9f\x5c\x1b\xea\x8b\xec\xae\xa6\x32\xa4\xbf\x0c\x79\xa5\x77\xed\xfc\xea\x7b\xaa\xa6\x86\x1e\x9d\x7f\x2d\xd5\xb4\xc4\xf6\xeb\x5f\x3d\x5f", n, e, b"\xb1\xa9\xc4\x5a\x26\x4d\x2c\x9a\xf4\x41\xa7\xb2\xd3\x30\xdd\x78\x80\x89\xcc\xef\x20\x5d\x5d\x66\x6b\xfe\x86\x43\x67\xbe\x97\x38\x12\x4e\x9d\x74\x64\x8a\xd9\x91\x60\xbd\x3a\xf8\x1a\x81\x85\x8b\xab\xe6\x67\xa5\xd9\x5c\x98\x0f\xe2\xf6\xac\x34\x86\x1e\xb2\xec\x9b\x4b\x4e\x8b\x64\x2e\xf3\x82\x0f\x56\xca\x38\x8a\x55\x65\x30\xd4\x27\x54\xc4\x72\x12\xe9\xb2\xf2\x52\x38\xa1\xef\x5a\xfe\x29\xbe\x63\x40\x8c\xf3\x8c\xaa\x2d\x23\xa7\x88\x24\xae\x0b\x92\x59\x75\xd3\xe9\x83\x55\x8d\xf6\xd2\xe9\xb1\xd3\x4a\x18\xb1\xd9\x73\xff\xac\xcc\x74\x5e\x52\x7c\xe7\x6c\x66\x3e\x90\x37\x19\x35\x5e\x45\xcd\x6d\x11\x8e\xd0\xb8\x5b\x70\xcb\xb8\xe4\x96\x41\x13\x53\xf8\x4f\x88\x66\xa0\x1f\xad\xc8\x19\xca\x0f\xf9\x5b\xbe\x2c\xc6\x8c\x8c\xf7\x8d\xa5\x58\x1b\xec\xc9\x62\x47\xb9\x11\xd1\x85\xed\x1f\xae\x36\xc4\xca\xd2\x62\x08\xeb\x80\x88\x3f\x42\xa0\x81\x23\xda\xc6\x8d\x88\xf2\xf9\x89\x3c\xde\x02\xef\x5a\x57\x66\x1d\xb2\xb3\xe1\xe9\x26\x9c\xbb\x0e\x15\xc4\x07\xbc\xf5\x5d\x92\xe6\x79\x38\x3c\x90\x80\x2c\xd0\xbf\xfd\x46\x96\x46\xdc\xb6\x0c\xa0\x1a\x1d\xea\xd4\x32\x28\x93\x40\x18\x39\x1d\xd8\x1f\x8b\x7e\x79\x7e\x52\x7f\xbe\x18\x15\xb9\x1b\xf3\xcd\x6a\x1f\x2f\xfb\xf5\xdd\x16\x6a\xcd\x55\x26\x76\x1c\xa8\xba\xb5\xd4\x63\xfb\x9f\xb8\x20\x65\x9f\x5c\xd5\x0f\x81\x50\xf1\x2f\x7e\x8d\x52\xe7\x77\x73\xc1\xe6\x48\x0c\x2c\xc1\x84\xd4\x11\xd6\x41\xf7\x1a\x9d\xed\xc2\xc5\xfc\x2e\xc3\x7a\x27\x70\xa9\x38\x3b\xfb\xf6\xa4\x89\xcf\x32\xb5\x6a\x12\xcf\x99\x37\x8e\x39\xb5\x0b\xda\xdb\x9f\x05\x91\xb2\x06\x5f\x9d\x44\xe5\x11\xc9\xdf\xb6\x15\x8f\xdd\xdd\xd1\xbc\x2c\xec\xe6", )?; test( HashAlgorithm::SHA512, b"\x12\x71\xa0\xdd\xb9\x9a\x0e\x1e\x9a\x50\x1c\xa3\x3c\x13\x1b\x0a\x1c\x78\x20\xa3\x97\x79\x08\x69\x09\x0f\xba\x37\x37\x03\xac\x38\xea\x00\xa9\xa0\xdd\xee\xd1\x99\xd9\x7b\xe1\x80\x1f\xfa\xb4\x52\x06\x71\x0a\x61\xe5\xed\x89\x4c\x33\x19\x01\x2d\xed\x0f\xf4\x14\x38\x6e\x56\xb5\x48\xad\x91\x5d\x80\xaf\xcc\x2b\xdb\x97\x6d\x7c\x8a\xdd\xdc\xa7\xdf\xa2\x8a\xeb\x69\x40\x33\xa5\x61\x26\x60\xc6\x44\xe3\x2f\x85\xc2\x80\x56\x51\xd7\x13\x66\x0a\x38\x91\x4d\x70\xf0\xe4\x1f\xdc\x4b\x3d\x16\x2e\xf3\xac\xd7\x06\x59\xee\xf6\x37", n, e, b"\xbf\xfd\x01\x0b\x2e\xc4\xe4\xa3\x27\x77\xb7\x76\x19\xb8\x76\x22\xf8\x92\x1d\xab\x56\xe1\x02\xc8\xd8\x24\xfe\x52\xb5\xdf\x7a\x20\x3f\xe7\x17\x99\xee\xaf\xdc\xc0\xc8\x87\x2d\xba\x6a\x37\x44\x07\xb5\x63\x9a\xeb\x5a\x30\xa9\x04\x71\x2f\x15\x09\x7d\xba\x0f\x2d\x62\xe8\x45\x41\x23\x95\xcf\x09\x54\x0a\xbd\x6e\x10\xc1\xa2\xe2\x3d\xbf\x2f\xe1\xdf\xd2\xb0\x2a\xf4\xee\xa4\x75\x15\x95\x7f\xa3\x73\x8b\x06\x41\x1a\x55\x1f\x8f\x8d\xc4\xb8\x5e\xa7\xf5\xa3\xa1\xe2\x6c\xcc\x44\x98\xbd\x64\xaf\x80\x38\xc1\xda\x5c\xbd\x8e\x80\xb3\xcb\xac\xde\xf1\xa4\x1e\xc5\xaf\x20\x55\x66\xc8\xdd\x80\xb2\xea\xda\xf9\x7d\xd0\xaa\x98\x33\xba\x3f\xd0\xe4\xb6\x73\xe2\xf8\x96\x0b\x04\xed\xa7\x61\x61\x64\x39\x14\x24\x2b\x96\x1e\x74\xde\xae\x49\x7c\xaf\x00\x5b\x00\x51\x5d\x78\x49\x2e\xc2\xc2\xde\xb6\x0a\x57\xb9\xdc\xe3\x6e\x68\xdd\x82\x00\x7d\x94\x2a\xe7\xc0\x23\xe1\x21\x0f\x0b\xe8\xa3\xeb\x3f\x00\x48\x24\x07\x4b\x8f\x72\x5e\xaf\x8a\xc7\x73\xe6\x0f\xbb\xb7\xcb\xa9\x63\x0e\x88\xb6\x9c\x8b\xcb\x2d\x74\xdb\xdb\x29\xbf\xff\x8b\x22\x54\x5b\x80\xbb\x63\x4e\x4c\x05\xf7\x3e\x00\x2a\x92\x8e\xfd\x5a\x6a\xa4\x56\x21\xce\x1b\x03\x2a\x22\x44\xde\x48\xf4\xdf\x43\x58\x15\x66\x78\xcb\xe0\x39\xc9\xeb\xe4\xce\xe9\x45\xa2\x5b\x90\x38\x46\x9f\xe0\x0c\x30\x92\x93\x6a\x8c\xff\x93\x69\x04\x5f\x90\x67\x33\xa9\xd2\xab\x36\x60\x18\x20\x69\xb1\x57\xca\x8f\x9b\x99\xa7\x1f\xc1\x53\xc6\x83\x01\xe9\x7a\x38\xfc\x3a\x87\xae\x2b\x6f\x03\x75\x4e\x6d\xa8\x2d\x0b\x07\x26\xe0\x70\x39\x79\xc9\x32\x02\x89\xfe\xef\xbc\xdd\xcd\x9d\x70\x6b\x71\xb5\x1e\x9a\x1b\x9d\xc1\x41\x2e\x6e\xd4\xb5\x66\x76", )?; test( HashAlgorithm::SHA512, b"\xf3\x0c\x78\x3b\x4e\xae\xb4\x65\x76\x7f\xa1\xb9\x6d\x0a\xf5\x24\x35\xd8\x5f\xab\x91\x2b\x6a\xba\x10\xef\xa5\xb9\x46\xed\x01\xe1\x5d\x42\x7a\x4e\xcd\x0f\xf9\x55\x67\x73\x79\x17\x98\xb6\x69\x56\xec\xc7\x52\x88\xd1\xe9\xba\x2a\x9e\xa9\x48\x57\xd3\x13\x29\x99\xa2\x25\xb1\xff\xaf\x84\x46\x70\x15\x6e\x7a\x3e\xa9\xf0\x77\xfe\x82\x59\xa0\x98\xb9\xee\x75\x9a\x6d\xdf\xb7\xd2\x0a\x7a\xcd\x1b\xcb\x9f\x67\x77\x7e\x74\x61\x5e\x88\x59\xea\x56\x28\x1f\xe5\xc4\x00\x74\x8f\x02\xd1\xa2\x63\xb1\x86\x7a\x3b\x51\x74\x8a\xb7\x0f", n, e, b"\x34\x5e\x2f\x60\xf7\xc8\x2c\x89\xef\x7d\xfd\x7d\xff\x2b\xc2\x34\x8b\xab\x02\x04\x79\x33\x08\x99\xd4\x41\x02\x13\xb3\x5e\x98\xd9\xba\xc9\x2f\xd8\xae\x80\x6b\x5b\xce\x8a\x6c\x4b\xd8\x27\x5b\x0f\xac\xb4\xdd\x13\xf9\xd6\x8b\xa6\x71\x41\xfa\x50\x85\x26\x4d\xa6\xdd\x68\x5a\x6d\x21\x21\x70\xa2\xc9\xcb\xf2\xcf\x59\x30\x18\x0e\xff\xc2\x50\x86\x8c\x98\x4b\xf5\x0f\xf6\x9d\x60\x69\xea\x28\xf5\xbc\x1b\x63\x70\x5d\x07\x32\x41\x6f\xd8\x29\xa5\xf5\xd6\x21\x74\x62\xc2\x2a\x33\xfd\x46\x52\xf7\xc1\xd1\x98\x79\x46\x46\xc0\x84\x06\x02\x4e\x81\x63\xa7\xeb\xe3\x9c\xfb\x51\x4c\x54\x43\x89\x7b\x58\x94\xdd\x19\xa2\x13\xe0\x37\xf2\x7e\x0f\xfb\xd6\xc5\x44\x7a\x80\x5a\x54\xdf\xdf\x4f\x65\x81\x9d\x4e\x0f\xbe\xe2\x5e\x3d\xac\x47\xfb\x6b\x63\x6e\x8d\xe6\x19\x0a\xdc\xcb\xce\xe9\x37\xd0\x97\x7b\x35\xb9\x73\x60\x6b\x0c\xa3\x48\x75\x8b\x50\xcd\xbb\xa0\x28\xb7\x3d\x0e\xf0\x1c\x56\x01\x4c\x03\x1c\x59\x8f\xe8\xdb\x87\xd2\xca\x46\x44\x77\x0a\xaa\x04\x51\xc3\x76\xde\xd8\x2f\xf5\xc6\xb8\xe7\xd2\xed\x9d\x1c\x8a\x17\xc3\x12\x2c\x12\x82\x73\xc6\x0f\xd1\xb0\x08\x8d\xfb\xc9\xc9\x27\xf1\x62\xe4\x38\x79\x40\x59\x64\xcb\x11\xef\x78\x99\x12\x3f\xeb\x8f\x88\xdd\x27\x34\xdf\x98\xaa\x69\x6d\x93\x6a\x8d\xf0\x70\x00\xe8\x4a\xf9\x01\x01\xf7\x00\x6a\x9b\xd2\x54\x9f\xdd\x0a\xd3\xf9\xde\x09\x30\x12\xd3\x2d\x2a\xfa\xa8\x28\x01\x7e\xe9\xc6\x07\xcb\xf5\xb5\x4f\x22\x36\x66\xd4\xb5\xf3\xe2\x6e\x0d\xfe\xc0\x03\x96\x1b\x83\xd8\x3d\xe3\x9f\xf6\xa0\xe8\x1e\x18\x83\xc1\xdb\x4a\xaa\xf0\x82\xfe\xc5\xaa\x30\xa7\xe5\x78\x55\x3d\x89\x77\x4c\x67\x90\x77\x90\xc9\x6d\xc4\xf5\xbe\x4c\x8c", )?; test( HashAlgorithm::SHA512, b"\x13\x2c\xf5\x0c\x66\xac\x4c\xc5\x43\x39\x75\x1a\x0e\xbb\x86\x5e\x1d\x3d\x32\x05\x62\xfc\x90\x5c\x4a\xbd\x1e\x78\xe4\x64\x06\x6c\x46\xc3\xa0\xc0\x2d\xb0\x37\x1e\xe3\x5a\x10\x4d\x66\xdd\xa8\x64\xc6\x13\x3e\x37\xcf\xad\x91\x16\xe8\x83\xeb\xb7\x3b\x29\x5e\x70\x16\xc3\x4e\xa9\x91\x1a\x30\x92\x72\xef\x90\x11\x4d\x8f\x59\xff\xf0\xa7\x51\x93\xfe\x5a\xe3\x1e\xd9\x91\x21\xf9\xc5\x92\x09\xbc\x4b\xd5\x07\xb1\xdc\x12\xbc\x89\xb7\x9f\xfe\x4d\x0d\xf9\x20\x97\x62\xa1\x73\x01\x36\x29\x0c\xde\xe5\x8e\xc8\x28\xcc\xc8\x8e\xba", n, e, b"\xb1\x25\x03\xb7\xb2\xf7\x83\x61\x88\x84\x17\x4b\xcb\x9b\xe1\x08\x77\x96\x04\x31\xed\x63\x63\xc8\x07\xe1\x2d\xb7\x1b\x8b\x6b\xd9\xd6\x40\x1d\x06\x4e\x25\x37\x40\x15\x8e\x8b\x90\x01\x52\xd3\x7f\xaf\x20\x33\x3a\x7d\x80\xb3\xd4\x7c\x7c\x7a\x3f\xa1\x20\x91\xce\x31\xcd\x8a\xae\x27\x2a\x4d\xa1\x5f\xe2\xcb\x5c\xfd\xea\x54\x11\x95\xa4\x69\xc9\x6b\xcf\x69\x5e\x0b\x52\x6d\xfa\x48\xa5\x90\x03\xc6\x76\x3a\xf8\x13\x63\x92\xc4\xb8\xd2\x4d\xb3\x14\x74\x6f\x42\xac\xa5\x50\xac\xc6\x5e\x07\x49\x13\xab\x82\x23\x2e\xb8\x59\x35\x09\x15\x8a\x8b\xa3\x4b\xc0\xf0\xe3\x12\x5a\x83\x4a\x3e\xd2\xd6\xa8\xcb\x1d\x08\x5f\x23\x4a\xe8\x68\xb8\x6a\xea\x8d\x6f\x82\xe1\x3a\x08\x84\x24\x85\x06\x6e\x48\xaa\xe4\x83\x78\x73\x15\x0f\x44\x47\x5e\x12\x60\x2b\x55\x2d\xcb\x34\xd1\xf9\xfd\xaa\xdb\xc6\xbf\xf5\x13\x4c\x6f\xc7\x62\x63\x88\x8b\xe6\x7e\xfe\x63\xee\x18\x40\xfa\x08\xc4\x99\x38\x85\x8a\x9d\x48\xb1\x05\x8d\x18\x97\x6b\xf2\xe3\xbf\xc6\x25\x55\x2f\x75\xb3\xea\x44\xeb\x91\xdd\x36\x68\x65\xf2\x40\xa0\xc3\x36\xa0\x11\x0e\x0f\xa0\x9d\x09\xcd\x94\xc7\x0c\xbc\x88\x95\xae\x3d\x44\xae\x3d\xff\x54\x5f\x0e\x8c\x8c\xc6\x62\xec\xd4\x0f\x90\x99\xa9\x52\x49\x43\x96\xc6\xb4\x23\xeb\xb4\x63\x40\x99\x69\x28\x1c\xdd\x54\xad\x87\xa3\x08\xe4\x87\xce\x19\x74\x5b\x30\xd5\xda\x76\xb9\x8d\x2a\xa9\xa0\x07\xa5\x57\x83\xb3\x03\x7e\x5b\x86\x62\x32\x28\x10\xbd\xd1\x1d\x86\xdc\x3f\x61\x45\x11\x49\x39\x1f\xb2\xf1\x4e\xd9\xc1\x7c\x75\x16\x23\xa4\x04\x2c\xe7\xed\xb8\x75\xee\x27\xbc\xd1\xf1\x9d\x6d\xc9\x28\x3a\xd0\x6d\x15\xe0\x97\xe2\xb0\xb1\x5a\x7e\xb7\x12\x8a\xdb\xca\x0a\xa6\xad\xcc", )?; Ok(()) } �������������������������������sequoia-openpgp-2.0.0/src/crypto/tests.rs�����������������������������������������������������������0000644�0000000�0000000�00000001730�10461020230�0016557�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������//! Low-level crypto tests. mod rsa; mod dsa; mod ecdsa; #[test] fn raw_bigint_cmp() { use std::cmp::Ordering; use crate::crypto::raw_bigint_cmp as cmp; assert_eq!(cmp(&[], &[]), Ordering::Equal); assert_eq!(cmp(&[], &[1]), Ordering::Less); assert_eq!(cmp(&[1], &[]), Ordering::Greater); assert_eq!(cmp(&[1], &[1]), Ordering::Equal); assert_eq!(cmp(&[1], &[2]), Ordering::Less); assert_eq!(cmp(&[2], &[1]), Ordering::Greater); assert_eq!(cmp(&[1], &[1, 2]), Ordering::Less); assert_eq!(cmp(&[1, 2], &[1]), Ordering::Greater); assert_eq!(cmp(&[1], &[2, 1]), Ordering::Less); assert_eq!(cmp(&[2, 1], &[1]), Ordering::Greater); assert_eq!(cmp(&[0], &[]), Ordering::Equal); assert_eq!(cmp(&[0], &[1]), Ordering::Less); assert_eq!(cmp(&[0, 1], &[]), Ordering::Greater); assert_eq!(cmp(&[], &[0]), Ordering::Equal); assert_eq!(cmp(&[], &[0, 1]), Ordering::Less); assert_eq!(cmp(&[1], &[0]), Ordering::Greater); } ����������������������������������������sequoia-openpgp-2.0.0/src/crypto/types/aead_algorithm.rs��������������������������������������������0000644�0000000�0000000�00000014575�10461020230�0021534�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������use std::{ fmt, }; #[cfg(test)] use quickcheck::{Arbitrary, Gen}; /// AEAD modes. /// /// See [AEAD Algorithms] for details. /// /// [AEAD Algorithms]: https://www.rfc-editor.org/rfc/rfc9580.html#name-aead-algorithms /// /// The values can be converted into and from their corresponding values of the serialized format. /// /// Use [`AEADAlgorithm::from`] to translate a numeric value to a /// symbolic one. /// /// [`AEADAlgorithm::from`]: std::convert::From /// /// # Examples /// /// Use `AEADAlgorithm` to set the preferred AEAD algorithms on a signature: /// /// ```rust /// use sequoia_openpgp as openpgp; /// use openpgp::packet::signature::SignatureBuilder; /// use openpgp::types::{AEADAlgorithm, Features, SignatureType, SymmetricAlgorithm}; /// /// # fn main() -> openpgp::Result<()> { /// let features = Features::empty().set_seipdv2(); /// let mut builder = SignatureBuilder::new(SignatureType::DirectKey) /// .set_features(features)? /// .set_preferred_aead_ciphersuites(vec![ /// (SymmetricAlgorithm::Camellia128, AEADAlgorithm::EAX), /// ])?; /// # Ok(()) } #[non_exhaustive] #[derive(Clone, Copy, Hash, PartialEq, Eq, Debug, PartialOrd, Ord)] pub enum AEADAlgorithm { /// EAX mode. EAX, /// OCB mode. OCB, /// Galois/Counter mode. GCM, /// Private algorithm identifier. Private(u8), /// Unknown algorithm identifier. Unknown(u8), } assert_send_and_sync!(AEADAlgorithm); const AEAD_ALGORITHM_VARIANTS: [AEADAlgorithm; 3] = [ AEADAlgorithm::EAX, AEADAlgorithm::OCB, AEADAlgorithm::GCM, ]; impl Default for AEADAlgorithm { fn default() -> Self { Self::const_default() } } impl AEADAlgorithm { /// Returns whether this algorithm is supported. /// /// # Examples /// /// ```rust /// use sequoia_openpgp as openpgp; /// use openpgp::types::AEADAlgorithm; /// /// assert!(! AEADAlgorithm::Private(100).is_supported()); /// ``` pub fn is_supported(&self) -> bool { self.is_supported_by_backend() } /// Returns an iterator over all valid variants. /// /// Returns an iterator over all known variants. This does not /// include the [`AEADAlgorithm::Private`], or /// [`AEADAlgorithm::Unknown`] variants. pub fn variants() -> impl Iterator<Item=Self> { AEAD_ALGORITHM_VARIANTS.iter().cloned() } } impl From<u8> for AEADAlgorithm { fn from(u: u8) -> Self { match u { 1 => AEADAlgorithm::EAX, 2 => AEADAlgorithm::OCB, 3 => AEADAlgorithm::GCM, 100..=110 => AEADAlgorithm::Private(u), u => AEADAlgorithm::Unknown(u), } } } impl From<AEADAlgorithm> for u8 { fn from(s: AEADAlgorithm) -> u8 { match s { AEADAlgorithm::EAX => 1, AEADAlgorithm::OCB => 2, AEADAlgorithm::GCM => 3, AEADAlgorithm::Private(u) => u, AEADAlgorithm::Unknown(u) => u, } } } /// Formats the AEAD algorithm name. /// /// There are two ways the AEAD algorithm name can be formatted. By /// default the short name is used. The alternate format uses the /// full algorithm name. /// /// # Examples /// /// ``` /// use sequoia_openpgp as openpgp; /// use openpgp::types::AEADAlgorithm; /// /// // default, short format /// assert_eq!("EAX", format!("{}", AEADAlgorithm::EAX)); /// /// // alternate, long format /// assert_eq!("EAX mode", format!("{:#}", AEADAlgorithm::EAX)); /// ``` impl fmt::Display for AEADAlgorithm { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { if f.alternate() { match *self { AEADAlgorithm::EAX => f.write_str("EAX mode"), AEADAlgorithm::OCB => f.write_str("OCB mode"), AEADAlgorithm::GCM => f.write_str("GCM mode"), AEADAlgorithm::Private(u) => f.write_fmt(format_args!("Private/Experimental AEAD algorithm {}", u)), AEADAlgorithm::Unknown(u) => f.write_fmt(format_args!("Unknown AEAD algorithm {}", u)), } } else { match *self { AEADAlgorithm::EAX => f.write_str("EAX"), AEADAlgorithm::OCB => f.write_str("OCB"), AEADAlgorithm::GCM => f.write_str("GCM"), AEADAlgorithm::Private(u) => f.write_fmt(format_args!("Private AEAD algo {}", u)), AEADAlgorithm::Unknown(u) => f.write_fmt(format_args!("Unknown AEAD algo {}", u)), } } } } #[cfg(test)] impl Arbitrary for AEADAlgorithm { fn arbitrary(g: &mut Gen) -> Self { u8::arbitrary(g).into() } } #[cfg(test)] mod tests { use super::*; quickcheck! { fn aead_roundtrip(aead: AEADAlgorithm) -> bool { let val: u8 = aead.into(); aead == AEADAlgorithm::from(val) } } quickcheck! { fn aead_display(aead: AEADAlgorithm) -> bool { let s = format!("{}", aead); !s.is_empty() } } quickcheck! { fn aead_parse(aead: AEADAlgorithm) -> bool { match aead { AEADAlgorithm::Unknown(u) => u == 0 || u > 110 || (u > 2 && u < 100), AEADAlgorithm::Private(u) => (100..=110).contains(&u), _ => true } } } #[test] fn aead_algorithms_variants() { use std::collections::HashSet; use std::iter::FromIterator; // AEAD_ALGORITHM_VARIANTS is a list. Derive it in a // different way to double-check that nothing is missing. let derived_variants = (0..=u8::MAX) .map(AEADAlgorithm::from) .filter(|t| { match t { AEADAlgorithm::Private(_) => false, AEADAlgorithm::Unknown(_) => false, _ => true, } }) .collect::<HashSet<_>>(); let known_variants = HashSet::from_iter(AEAD_ALGORITHM_VARIANTS .iter().cloned()); let missing = known_variants .symmetric_difference(&derived_variants) .collect::<Vec<_>>(); assert!(missing.is_empty(), "{:?}", missing); } } �����������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/src/crypto/types/curve.rs�����������������������������������������������������0000644�0000000�0000000�00000024131�10461020230�0017705�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������use std::{ fmt, }; use crate::{Error, Result}; #[cfg(test)] use quickcheck::{Arbitrary, Gen}; /// Elliptic curves used in OpenPGP. /// /// `PublicKeyAlgorithm` does not differentiate between elliptic /// curves. Instead, the curve is specified using an OID prepended to /// the key material. We provide this type to be able to match on the /// curves. #[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)] #[non_exhaustive] pub enum Curve { /// NIST curve P-256. NistP256, /// NIST curve P-384. NistP384, /// NIST curve P-521. NistP521, /// brainpoolP256r1. BrainpoolP256, /// brainpoolP384r1. BrainpoolP384, /// brainpoolP512r1. BrainpoolP512, /// D.J. Bernstein's "Twisted" Edwards curve Ed25519. Ed25519, /// Elliptic curve Diffie-Hellman using D.J. Bernstein's Curve25519. Cv25519, /// Unknown curve. Unknown(Box<[u8]>), } assert_send_and_sync!(Curve); const CURVE_VARIANTS: [Curve; 8] = [ Curve::NistP256, Curve::NistP384, Curve::NistP521, Curve::BrainpoolP256, Curve::BrainpoolP384, Curve::BrainpoolP512, Curve::Ed25519, Curve::Cv25519, ]; impl Curve { /// Returns the length of public keys over this curve in bits. /// /// For the Kobliz curves this is the size of the underlying /// finite field. For X25519 it is 256. /// /// This value is also equal to the length of a coordinate in bits. /// /// Note: This information is useless and should not be used to /// gauge the security of a particular curve. This function exists /// only because some legacy PGP application like HKP need it. /// /// # Examples /// /// ```rust /// # fn main() -> sequoia_openpgp::Result<()> { /// use sequoia_openpgp as openpgp; /// use openpgp::types::Curve; /// /// assert_eq!(Curve::NistP256.bits()?, 256); /// assert_eq!(Curve::NistP384.bits()?, 384); /// assert_eq!(Curve::Ed25519.bits()?, 256); /// assert!(Curve::Unknown(Box::new([0x2B, 0x11])).bits().is_err()); /// # Ok(()) } /// ``` pub fn bits(&self) -> Result<usize> { use self::Curve::*; match self { NistP256 => Ok(256), NistP384 => Ok(384), NistP521 => Ok(521), BrainpoolP256 => Ok(256), BrainpoolP384 => Ok(384), BrainpoolP512 => Ok(512), Ed25519 => Ok(256), Cv25519 => Ok(256), Unknown(_) => Err(Error::UnsupportedEllipticCurve(self.clone()).into()), } } /// Returns the curve's field size in bytes. /// /// # Examples /// /// ```rust /// # fn main() -> sequoia_openpgp::Result<()> { /// use sequoia_openpgp as openpgp; /// use openpgp::types::Curve; /// /// assert_eq!(Curve::NistP256.field_size()?, 32); /// assert_eq!(Curve::NistP384.field_size()?, 48); /// assert_eq!(Curve::NistP521.field_size()?, 66); /// assert_eq!(Curve::Ed25519.field_size()?, 32); /// assert!(Curve::Unknown(Box::new([0x2B, 0x11])).field_size().is_err()); /// # Ok(()) } /// ``` pub fn field_size(&self) -> Result<usize> { self.bits() .map(|bits| (bits + 7) / 8) } } /// Formats the elliptic curve name. /// /// There are two ways the elliptic curve name can be formatted. By /// default the short name is used. The alternate format uses the /// full curve name. /// /// # Examples /// /// ``` /// use sequoia_openpgp as openpgp; /// use openpgp::types::Curve; /// /// // default, short format /// assert_eq!("NIST P-256", format!("{}", Curve::NistP256)); /// /// // alternate, long format /// assert_eq!("NIST curve P-256", format!("{:#}", Curve::NistP256)); /// ``` impl fmt::Display for Curve { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { use self::Curve::*; struct DotEncoded<'o>(&'o [u8]); impl fmt::Display for DotEncoded<'_> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let mut oid = self.0; if oid.is_empty() { write!(f, "[invalid]")?; return Ok(()); } // The first octet encodes two values. let first = oid[0] / 40; let second = oid[0] % 40; oid = &oid[1..]; write!(f, "{}.{}", first, second)?; let mut acc: usize = 0; for b in oid { if b & 0x80 > 0 { acc *= 0x80; acc += (b & 0x7f) as usize; } else { acc *= 0x80; acc += (b & 0x7f) as usize; write!(f, ".{}", acc)?; acc = 0; } } Ok(()) } } if f.alternate() { match *self { NistP256 => f.write_str("NIST curve P-256"), NistP384 => f.write_str("NIST curve P-384"), NistP521 => f.write_str("NIST curve P-521"), BrainpoolP256 => f.write_str("brainpoolP256r1"), BrainpoolP384 => f.write_str("brainpoolP384r1"), BrainpoolP512 => f.write_str("brainpoolP512r1"), Ed25519 => f.write_str("D.J. Bernstein's \"Twisted\" Edwards curve Ed25519"), Cv25519 => f.write_str("Elliptic curve Diffie-Hellman using D.J. Bernstein's Curve25519"), Unknown(ref oid) => write!(f, "Unknown curve (OID: {})", DotEncoded(oid)), } } else { match *self { NistP256 => f.write_str("NIST P-256"), NistP384 => f.write_str("NIST P-384"), NistP521 => f.write_str("NIST P-521"), BrainpoolP256 => f.write_str("brainpoolP256r1"), BrainpoolP384 => f.write_str("brainpoolP384r1"), BrainpoolP512 => f.write_str("brainpoolP512r1"), Ed25519 => f.write_str("Ed25519"), Cv25519 => f.write_str("Curve25519"), Unknown(ref oid) => write!(f, "Unknown curve {}", DotEncoded(oid)), } } } } const NIST_P256_OID: &[u8] = &[0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x03, 0x01, 0x07]; const NIST_P384_OID: &[u8] = &[0x2B, 0x81, 0x04, 0x00, 0x22]; const NIST_P521_OID: &[u8] = &[0x2B, 0x81, 0x04, 0x00, 0x23]; const BRAINPOOL_P256_OID: &[u8] = &[0x2B, 0x24, 0x03, 0x03, 0x02, 0x08, 0x01, 0x01, 0x07]; const BRAINPOOL_P384_OID: &[u8] = &[0x2B, 0x24, 0x03, 0x03, 0x02, 0x08, 0x01, 0x01, 0x0B]; const BRAINPOOL_P512_OID: &[u8] = &[0x2B, 0x24, 0x03, 0x03, 0x02, 0x08, 0x01, 0x01, 0x0D]; const ED25519_OID: &[u8] = &[0x2B, 0x06, 0x01, 0x04, 0x01, 0xDA, 0x47, 0x0F, 0x01]; const CV25519_OID: &[u8] = &[0x2B, 0x06, 0x01, 0x04, 0x01, 0x97, 0x55, 0x01, 0x05, 0x01]; impl Curve { /// Parses the given OID. /// /// # Examples /// /// ```rust /// use sequoia_openpgp as openpgp; /// use openpgp::types::Curve; /// /// assert_eq!(Curve::from_oid(&[0x2B, 0x81, 0x04, 0x00, 0x22]), Curve::NistP384); /// assert_eq!(Curve::from_oid(&[0x2B, 0x11]), Curve::Unknown(Box::new([0x2B, 0x11]))); /// ``` pub fn from_oid(oid: &[u8]) -> Curve { // Match on OIDs, see section 11 of RFC6637. match oid { NIST_P256_OID => Curve::NistP256, NIST_P384_OID => Curve::NistP384, NIST_P521_OID => Curve::NistP521, BRAINPOOL_P256_OID => Curve::BrainpoolP256, BRAINPOOL_P384_OID => Curve::BrainpoolP384, BRAINPOOL_P512_OID => Curve::BrainpoolP512, ED25519_OID => Curve::Ed25519, CV25519_OID => Curve::Cv25519, oid => Curve::Unknown(Vec::from(oid).into_boxed_slice()), } } /// Returns this curve's OID. /// /// # Examples /// /// ```rust /// use sequoia_openpgp as openpgp; /// use openpgp::types::Curve; /// /// assert_eq!(Curve::NistP384.oid(), &[0x2B, 0x81, 0x04, 0x00, 0x22]); /// assert_eq!(Curve::Unknown(Box::new([0x2B, 0x11])).oid(), &[0x2B, 0x11]); /// ``` pub fn oid(&self) -> &[u8] { match self { Curve::NistP256 => NIST_P256_OID, Curve::NistP384 => NIST_P384_OID, Curve::NistP521 => NIST_P521_OID, Curve::BrainpoolP256 => BRAINPOOL_P256_OID, Curve::BrainpoolP384 => BRAINPOOL_P384_OID, Curve::BrainpoolP512 => BRAINPOOL_P512_OID, Curve::Ed25519 => ED25519_OID, Curve::Cv25519 => CV25519_OID, Curve::Unknown(ref oid) => oid, } } /// Returns whether this algorithm is supported. /// /// # Examples /// /// ```rust /// use sequoia_openpgp as openpgp; /// use openpgp::types::Curve; /// /// assert!(Curve::Ed25519.is_supported()); /// assert!(!Curve::Unknown(Box::new([0x2B, 0x11])).is_supported()); /// ``` pub fn is_supported(&self) -> bool { use crate::crypto::backend::{Backend, interface::Asymmetric}; Backend::supports_curve(self) } /// Returns an iterator over all valid variants. pub fn variants() -> impl Iterator<Item=Self> { CURVE_VARIANTS.iter().cloned() } } #[cfg(test)] impl Arbitrary for Curve { fn arbitrary(g: &mut Gen) -> Self { match u8::arbitrary(g) % 9 { 0 => Curve::NistP256, 1 => Curve::NistP384, 2 => Curve::NistP521, 3 => Curve::BrainpoolP256, 4 => Curve::BrainpoolP384, 5 => Curve::BrainpoolP512, 6 => Curve::Ed25519, 7 => Curve::Cv25519, 8 => Curve::Unknown({ let mut k = <Vec<u8>>::arbitrary(g); k.truncate(255); k.into_boxed_slice() }), _ => unreachable!(), } } } #[cfg(test)] mod tests { use super::*; quickcheck! { fn curve_roundtrip(curve: Curve) -> bool { curve == Curve::from_oid(curve.oid()) } } } ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/src/crypto/types/hash_algorithm.rs��������������������������������������������0000644�0000000�0000000�00000025145�10461020230�0021560�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������use std::{ fmt, }; use crate::{Error, Result}; #[cfg(test)] use quickcheck::{Arbitrary, Gen}; /// The OpenPGP hash algorithms as defined in [Section 9.5 of RFC 9580]. /// /// # Examples /// /// Use `HashAlgorithm` to set the preferred hash algorithms on a signature: /// /// ```rust /// use sequoia_openpgp as openpgp; /// use openpgp::packet::signature::SignatureBuilder; /// use openpgp::types::{HashAlgorithm, SignatureType}; /// /// # fn main() -> openpgp::Result<()> { /// let mut builder = SignatureBuilder::new(SignatureType::DirectKey) /// .set_hash_algo(HashAlgorithm::SHA512); /// # Ok(()) } /// ``` /// /// [Section 9.5 of RFC 9580]: https://www.rfc-editor.org/rfc/rfc9580.html#section-9.5 #[non_exhaustive] #[derive(Clone, Copy, Hash, PartialEq, Eq, Debug, PartialOrd, Ord)] pub enum HashAlgorithm { /// Rivest et.al. message digest 5. MD5, /// NIST Secure Hash Algorithm (deprecated) SHA1, /// RIPEMD-160 RipeMD, /// 256-bit version of SHA2 SHA256, /// 384-bit version of SHA2 SHA384, /// 512-bit version of SHA2 SHA512, /// 224-bit version of SHA2 SHA224, /// 256-bit version of SHA3 SHA3_256, /// 512-bit version of SHA3 SHA3_512, /// Private hash algorithm identifier. Private(u8), /// Unknown hash algorithm identifier. Unknown(u8), } assert_send_and_sync!(HashAlgorithm); const HASH_ALGORITHM_VARIANTS: [HashAlgorithm; 9] = [ HashAlgorithm::MD5, HashAlgorithm::SHA1, HashAlgorithm::RipeMD, HashAlgorithm::SHA256, HashAlgorithm::SHA384, HashAlgorithm::SHA512, HashAlgorithm::SHA224, HashAlgorithm::SHA3_256, HashAlgorithm::SHA3_512, ]; impl Default for HashAlgorithm { fn default() -> Self { // SHA512 is almost twice as fast as SHA256 on 64-bit // architectures because it operates on 64-bit words. HashAlgorithm::SHA512 } } impl From<u8> for HashAlgorithm { fn from(u: u8) -> Self { match u { 1 => HashAlgorithm::MD5, 2 => HashAlgorithm::SHA1, 3 => HashAlgorithm::RipeMD, 8 => HashAlgorithm::SHA256, 9 => HashAlgorithm::SHA384, 10 => HashAlgorithm::SHA512, 11 => HashAlgorithm::SHA224, 12 => HashAlgorithm::SHA3_256, 14 => HashAlgorithm::SHA3_512, 100..=110 => HashAlgorithm::Private(u), u => HashAlgorithm::Unknown(u), } } } impl From<HashAlgorithm> for u8 { fn from(h: HashAlgorithm) -> u8 { match h { HashAlgorithm::MD5 => 1, HashAlgorithm::SHA1 => 2, HashAlgorithm::RipeMD => 3, HashAlgorithm::SHA256 => 8, HashAlgorithm::SHA384 => 9, HashAlgorithm::SHA512 => 10, HashAlgorithm::SHA224 => 11, HashAlgorithm::SHA3_256 => 12, HashAlgorithm::SHA3_512 => 14, HashAlgorithm::Private(u) => u, HashAlgorithm::Unknown(u) => u, } } } impl std::str::FromStr for HashAlgorithm { type Err = Error; fn from_str(s: &str) -> std::result::Result<Self, Error> { if s.eq_ignore_ascii_case("MD5") { Ok(HashAlgorithm::MD5) } else if s.eq_ignore_ascii_case("SHA1") { Ok(HashAlgorithm::SHA1) } else if s.eq_ignore_ascii_case("RipeMD160") { Ok(HashAlgorithm::RipeMD) } else if s.eq_ignore_ascii_case("SHA256") { Ok(HashAlgorithm::SHA256) } else if s.eq_ignore_ascii_case("SHA384") { Ok(HashAlgorithm::SHA384) } else if s.eq_ignore_ascii_case("SHA512") { Ok(HashAlgorithm::SHA512) } else if s.eq_ignore_ascii_case("SHA224") { Ok(HashAlgorithm::SHA224) } else if s.eq_ignore_ascii_case("SHA3-256") { Ok(HashAlgorithm::SHA3_256) } else if s.eq_ignore_ascii_case("SHA3-512") { Ok(HashAlgorithm::SHA3_512) } else { Err(Error::InvalidArgument(format!( "Unknown hash algorithm {:?}", s))) } } } impl fmt::Display for HashAlgorithm { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match *self { HashAlgorithm::MD5 => f.write_str("MD5"), HashAlgorithm::SHA1 => f.write_str("SHA1"), HashAlgorithm::RipeMD => f.write_str("RipeMD160"), HashAlgorithm::SHA256 => f.write_str("SHA256"), HashAlgorithm::SHA384 => f.write_str("SHA384"), HashAlgorithm::SHA512 => f.write_str("SHA512"), HashAlgorithm::SHA224 => f.write_str("SHA224"), HashAlgorithm::SHA3_256 => f.write_str("SHA3-256"), HashAlgorithm::SHA3_512 => f.write_str("SHA3-512"), HashAlgorithm::Private(u) => f.write_fmt(format_args!("Private/Experimental hash algorithm {}", u)), HashAlgorithm::Unknown(u) => f.write_fmt(format_args!("Unknown hash algorithm {}", u)), } } } impl HashAlgorithm { /// Returns the text name of this algorithm. /// /// [Section 9.5 of RFC 9580] defines a textual representation of /// hash algorithms. This is used in cleartext signed messages /// (see [Section 7 of RFC 9580]). /// /// [Section 9.5 of RFC 9580]: https://www.rfc-editor.org/rfc/rfc9580.html#section-9.5 /// [Section 7 of RFC 9580]: https://www.rfc-editor.org/rfc/rfc9580.html#section-7 /// /// # Examples /// /// ```rust /// # use sequoia_openpgp as openpgp; /// # use openpgp::types::HashAlgorithm; /// # fn main() -> openpgp::Result<()> { /// assert_eq!(HashAlgorithm::RipeMD.text_name()?, "RIPEMD160"); /// # Ok(()) } /// ``` pub fn text_name(&self) -> Result<&str> { match self { HashAlgorithm::MD5 => Ok("MD5"), HashAlgorithm::SHA1 => Ok("SHA1"), HashAlgorithm::RipeMD => Ok("RIPEMD160"), HashAlgorithm::SHA256 => Ok("SHA256"), HashAlgorithm::SHA384 => Ok("SHA384"), HashAlgorithm::SHA512 => Ok("SHA512"), HashAlgorithm::SHA224 => Ok("SHA224"), HashAlgorithm::SHA3_256 => Ok("SHA3-256"), HashAlgorithm::SHA3_512 => Ok("SHA3-512"), HashAlgorithm::Private(_) => Err(Error::UnsupportedHashAlgorithm(*self).into()), HashAlgorithm::Unknown(_) => Err(Error::UnsupportedHashAlgorithm(*self).into()), } } /// Returns the digest size for this algorithm. pub fn digest_size(&self) -> Result<usize> { match self { HashAlgorithm::MD5 => Ok(16), HashAlgorithm::SHA1 => Ok(20), HashAlgorithm::RipeMD => Ok(20), HashAlgorithm::SHA256 => Ok(32), HashAlgorithm::SHA384 => Ok(48), HashAlgorithm::SHA512 => Ok(64), HashAlgorithm::SHA224 => Ok(28), HashAlgorithm::SHA3_256 => Ok(32), HashAlgorithm::SHA3_512 => Ok(64), HashAlgorithm::Private(_) => Err(Error::UnsupportedHashAlgorithm(*self).into()), HashAlgorithm::Unknown(_) => Err(Error::UnsupportedHashAlgorithm(*self).into()), } } /// Returns the salt size for this algorithm. /// /// Version 6 signatures salt the hash, and the size of the hash /// is dependent on the hash algorithm. pub fn salt_size(&self) -> Result<usize> { match self { HashAlgorithm::SHA256 => Ok(16), HashAlgorithm::SHA384 => Ok(24), HashAlgorithm::SHA512 => Ok(32), HashAlgorithm::SHA224 => Ok(16), HashAlgorithm::SHA3_256 => Ok(16), HashAlgorithm::SHA3_512 => Ok(32), _ => Err(Error::UnsupportedHashAlgorithm(*self).into()), } } /// Returns an iterator over all valid variants. /// /// Returns an iterator over all known variants. This does not /// include the [`HashAlgorithm::Private`], or /// [`HashAlgorithm::Unknown`] variants. pub fn variants() -> impl Iterator<Item=Self> { HASH_ALGORITHM_VARIANTS.iter().cloned() } } #[cfg(test)] impl Arbitrary for HashAlgorithm { fn arbitrary(g: &mut Gen) -> Self { u8::arbitrary(g).into() } } #[cfg(test)] mod tests { use super::*; quickcheck! { fn hash_roundtrip(hash: HashAlgorithm) -> bool { let val: u8 = hash.into(); hash == HashAlgorithm::from(val) } } quickcheck! { fn hash_roundtrip_str(hash: HashAlgorithm) -> bool { use std::str::FromStr; match hash { HashAlgorithm::Private(_) | HashAlgorithm::Unknown(_) => true, hash => { let s = format!("{}", hash); hash == HashAlgorithm::from_str(&s).unwrap() } } } } quickcheck! { fn hash_roundtrip_text_name(hash: HashAlgorithm) -> bool { use std::str::FromStr; match hash { HashAlgorithm::Private(_) | HashAlgorithm::Unknown(_) => true, hash => { let s = hash.text_name().unwrap(); hash == HashAlgorithm::from_str(s).unwrap() } } } } quickcheck! { fn hash_display(hash: HashAlgorithm) -> bool { let s = format!("{}", hash); !s.is_empty() } } quickcheck! { fn hash_parse(hash: HashAlgorithm) -> bool { match hash { HashAlgorithm::Unknown(u) => u == 0 || (u > 11 && u < 100) || u > 110 || (4..=7).contains(&u) || u == 0, HashAlgorithm::Private(u) => (100..=110).contains(&u), _ => true } } } #[test] fn hash_algorithms_variants() { use std::collections::HashSet; use std::iter::FromIterator; // HASH_ALGORITHM_VARIANTS is a list. Derive it in a // different way to double-check that nothing is missing. let derived_variants = (0..=u8::MAX) .map(HashAlgorithm::from) .filter(|t| { match t { HashAlgorithm::Private(_) => false, HashAlgorithm::Unknown(_) => false, _ => true, } }) .collect::<HashSet<_>>(); let known_variants = HashSet::from_iter(HASH_ALGORITHM_VARIANTS .iter().cloned()); let missing = known_variants .symmetric_difference(&derived_variants) .collect::<Vec<_>>(); assert!(missing.is_empty(), "{:?}", missing); } } ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/src/crypto/types/public_key_algorithm.rs��������������������������������������0000644�0000000�0000000�00000026144�10461020230�0022763�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������use std::{ fmt, }; #[cfg(test)] use quickcheck::{Arbitrary, Gen}; /// The OpenPGP public key algorithms as defined in [Section 9.1 of /// RFC 9580]. /// /// # Examples /// /// ```rust /// # fn main() -> sequoia_openpgp::Result<()> { /// use sequoia_openpgp as openpgp; /// use openpgp::cert::prelude::*; /// use openpgp::types::PublicKeyAlgorithm; /// /// let (cert, _) = CertBuilder::new() /// .set_cipher_suite(CipherSuite::Cv25519) /// .generate()?; /// /// assert_eq!(cert.primary_key().key().pk_algo(), PublicKeyAlgorithm::EdDSA); /// # Ok(()) } /// ``` /// /// [Section 9.1 of RFC 9580]: https://www.rfc-editor.org/rfc/rfc9580.html#section-9.1 #[non_exhaustive] #[derive(Clone, Copy, Hash, PartialEq, Eq, Debug, PartialOrd, Ord)] pub enum PublicKeyAlgorithm { /// RSA (Encrypt or Sign) RSAEncryptSign, /// RSA Encrypt-Only, deprecated in RFC 4880. #[deprecated(note = "Use `PublicKeyAlgorithm::RSAEncryptSign`.")] RSAEncrypt, /// RSA Sign-Only, deprecated in RFC 4880. #[deprecated(note = "Use `PublicKeyAlgorithm::RSAEncryptSign`.")] RSASign, /// ElGamal (Encrypt-Only), deprecated in RFC 9580. #[deprecated(note = "Use a newer public key algorithm instead.")] ElGamalEncrypt, /// DSA (Digital Signature Algorithm) #[deprecated(note = "Use a newer public key algorithm instead.")] DSA, /// Elliptic curve DH ECDH, /// Elliptic curve DSA ECDSA, /// ElGamal (Encrypt or Sign), deprecated in RFC 4880. #[deprecated(note = "Use a newer public key algorithm instead.")] ElGamalEncryptSign, /// "Twisted" Edwards curve DSA EdDSA, /// X25519 (RFC 7748). X25519, /// X448 (RFC 7748). X448, /// Ed25519 (RFC 8032). Ed25519, /// Ed448 (RFC 8032). Ed448, /// Private algorithm identifier. Private(u8), /// Unknown algorithm identifier. Unknown(u8), } assert_send_and_sync!(PublicKeyAlgorithm); #[allow(deprecated)] pub(crate) const PUBLIC_KEY_ALGORITHM_VARIANTS: [PublicKeyAlgorithm; 13] = [ PublicKeyAlgorithm::RSAEncryptSign, PublicKeyAlgorithm::RSAEncrypt, PublicKeyAlgorithm::RSASign, PublicKeyAlgorithm::ElGamalEncrypt, PublicKeyAlgorithm::DSA, PublicKeyAlgorithm::ECDH, PublicKeyAlgorithm::ECDSA, PublicKeyAlgorithm::ElGamalEncryptSign, PublicKeyAlgorithm::EdDSA, PublicKeyAlgorithm::X25519, PublicKeyAlgorithm::X448, PublicKeyAlgorithm::Ed25519, PublicKeyAlgorithm::Ed448, ]; impl PublicKeyAlgorithm { /// Returns true if the algorithm can sign data. /// /// # Examples /// /// ```rust /// use sequoia_openpgp as openpgp; /// use openpgp::types::PublicKeyAlgorithm; /// /// assert!(PublicKeyAlgorithm::EdDSA.for_signing()); /// assert!(PublicKeyAlgorithm::RSAEncryptSign.for_signing()); /// assert!(!PublicKeyAlgorithm::ElGamalEncrypt.for_signing()); /// ``` pub fn for_signing(&self) -> bool { use self::PublicKeyAlgorithm::*; #[allow(deprecated)] { matches!(self, RSAEncryptSign | RSASign | DSA | ECDSA | ElGamalEncryptSign | EdDSA | Ed25519 | Ed448 | Private(_) | Unknown(_) ) } } /// Returns true if the algorithm can encrypt data. /// /// # Examples /// /// ```rust /// use sequoia_openpgp as openpgp; /// use openpgp::types::PublicKeyAlgorithm; /// /// assert!(!PublicKeyAlgorithm::EdDSA.for_encryption()); /// assert!(PublicKeyAlgorithm::RSAEncryptSign.for_encryption()); /// assert!(PublicKeyAlgorithm::ElGamalEncrypt.for_encryption()); /// ``` pub fn for_encryption(&self) -> bool { use self::PublicKeyAlgorithm::*; #[allow(deprecated)] { matches!(self, RSAEncryptSign | RSAEncrypt | ElGamalEncrypt | ECDH | ElGamalEncryptSign | X25519 | X448 | Private(_) | Unknown(_) ) } } /// Returns whether this algorithm is supported. /// /// # Examples /// /// ```rust /// use sequoia_openpgp as openpgp; /// use openpgp::types::PublicKeyAlgorithm; /// /// assert!(PublicKeyAlgorithm::EdDSA.is_supported()); /// assert!(PublicKeyAlgorithm::RSAEncryptSign.is_supported()); /// assert!(!PublicKeyAlgorithm::Private(101).is_supported()); /// ``` pub fn is_supported(&self) -> bool { use crate::crypto::backend::{Backend, interface::Asymmetric}; Backend::supports_algo(*self) } /// Returns an iterator over all valid variants. /// /// Returns an iterator over all known variants. This does not /// include the [`PublicKeyAlgorithm::Private`], or /// [`PublicKeyAlgorithm::Unknown`] variants. pub fn variants() -> impl Iterator<Item=Self> { PUBLIC_KEY_ALGORITHM_VARIANTS.iter().cloned() } } impl From<u8> for PublicKeyAlgorithm { fn from(u: u8) -> Self { use crate::PublicKeyAlgorithm::*; #[allow(deprecated)] match u { 1 => RSAEncryptSign, 2 => RSAEncrypt, 3 => RSASign, 16 => ElGamalEncrypt, 17 => DSA, 18 => ECDH, 19 => ECDSA, 20 => ElGamalEncryptSign, 22 => EdDSA, 25 => X25519, 26 => X448, 27 => Ed25519, 28 => Ed448, 100..=110 => Private(u), u => Unknown(u), } } } impl From<PublicKeyAlgorithm> for u8 { fn from(p: PublicKeyAlgorithm) -> u8 { use crate::PublicKeyAlgorithm::*; #[allow(deprecated)] match p { RSAEncryptSign => 1, RSAEncrypt => 2, RSASign => 3, ElGamalEncrypt => 16, DSA => 17, ECDH => 18, ECDSA => 19, ElGamalEncryptSign => 20, EdDSA => 22, X25519 => 25, X448 => 26, Ed25519 => 27, Ed448 => 28, Private(u) => u, Unknown(u) => u, } } } /// Formats the public key algorithm name. /// /// There are two ways the public key algorithm name can be formatted. /// By default the short name is used. The alternate format uses the /// full public key algorithm name. /// /// # Examples /// /// ``` /// use sequoia_openpgp as openpgp; /// use openpgp::types::PublicKeyAlgorithm; /// /// // default, short format /// assert_eq!("ECDH", format!("{}", PublicKeyAlgorithm::ECDH)); /// /// // alternate, long format /// assert_eq!("ECDH public key algorithm", format!("{:#}", PublicKeyAlgorithm::ECDH)); /// ``` impl fmt::Display for PublicKeyAlgorithm { #[allow(deprecated)] fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { use crate::PublicKeyAlgorithm::*; if f.alternate() { match *self { RSAEncryptSign => f.write_str("RSA (Encrypt or Sign)"), RSAEncrypt => f.write_str("RSA Encrypt-Only"), RSASign => f.write_str("RSA Sign-Only"), ElGamalEncrypt => f.write_str("ElGamal (Encrypt-Only)"), DSA => f.write_str("DSA (Digital Signature Algorithm)"), ECDSA => f.write_str("ECDSA public key algorithm"), ElGamalEncryptSign => f.write_str("ElGamal (Encrypt or Sign)"), ECDH => f.write_str("ECDH public key algorithm"), EdDSA => f.write_str("EdDSA Edwards-curve Digital Signature Algorithm"), X25519 => f.write_str("X25519"), X448 => f.write_str("X448"), Ed25519 => f.write_str("Ed25519"), Ed448 => f.write_str("Ed448"), Private(u) => f.write_fmt(format_args!("Private/Experimental public key algorithm {}", u)), Unknown(u) => f.write_fmt(format_args!("Unknown public key algorithm {}", u)), } } else { match *self { RSAEncryptSign => f.write_str("RSA"), RSAEncrypt => f.write_str("RSA"), RSASign => f.write_str("RSA"), ElGamalEncrypt => f.write_str("ElGamal"), DSA => f.write_str("DSA"), ECDSA => f.write_str("ECDSA"), ElGamalEncryptSign => f.write_str("ElGamal"), ECDH => f.write_str("ECDH"), EdDSA => f.write_str("EdDSA"), X25519 => f.write_str("X25519"), X448 => f.write_str("X448"), Ed25519 => f.write_str("Ed25519"), Ed448 => f.write_str("Ed448"), Private(u) => f.write_fmt(format_args!("Private algo {}", u)), Unknown(u) => f.write_fmt(format_args!("Unknown algo {}", u)), } } } } #[cfg(test)] impl Arbitrary for PublicKeyAlgorithm { fn arbitrary(g: &mut Gen) -> Self { u8::arbitrary(g).into() } } #[cfg(test)] impl PublicKeyAlgorithm { pub(crate) fn arbitrary_for_signing(g: &mut Gen) -> Self { use self::PublicKeyAlgorithm::*; #[allow(deprecated)] let a = g.choose(&[ RSAEncryptSign, RSASign, DSA, ECDSA, EdDSA, Ed25519, Ed448, ]).unwrap(); assert!(a.for_signing()); *a } } #[cfg(test)] mod tests { use super::*; quickcheck! { fn pk_roundtrip(pk: PublicKeyAlgorithm) -> bool { let val: u8 = pk.into(); pk == PublicKeyAlgorithm::from(val) } } quickcheck! { fn pk_display(pk: PublicKeyAlgorithm) -> bool { let s = format!("{}", pk); !s.is_empty() } } quickcheck! { fn pk_parse(pk: PublicKeyAlgorithm) -> bool { match pk { PublicKeyAlgorithm::Unknown(u) => u == 0 || u > 110 || (4..=15).contains(&u) || (18..100).contains(&u), PublicKeyAlgorithm::Private(u) => (100..=110).contains(&u), _ => true } } } #[test] fn public_key_algorithms_variants() { use std::collections::HashSet; use std::iter::FromIterator; // PUBLIC_KEY_ALGORITHM_VARIANTS is a list. Derive it in a // different way to double-check that nothing is missing. let derived_variants = (0..=u8::MAX) .map(PublicKeyAlgorithm::from) .filter(|t| { match t { PublicKeyAlgorithm::Private(_) => false, PublicKeyAlgorithm::Unknown(_) => false, _ => true, } }) .collect::<HashSet<_>>(); let known_variants = HashSet::from_iter(PUBLIC_KEY_ALGORITHM_VARIANTS.iter().cloned()); let missing = known_variants .symmetric_difference(&derived_variants) .collect::<Vec<_>>(); assert!(missing.is_empty(), "{:?}", missing); } } ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/src/crypto/types/symmetric_algorithm.rs���������������������������������������0000644�0000000�0000000�00000031324�10461020230�0022645�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������use std::{ fmt, }; use crate::{Error, Result}; #[cfg(test)] use quickcheck::{Arbitrary, Gen}; /// The symmetric-key algorithms as defined in [Section 9.3 of RFC 9580]. /// /// [Section 9.3 of RFC 9580]: https://www.rfc-editor.org/rfc/rfc9580.html#section-9.3 /// /// The values can be converted into and from their corresponding values of the serialized format. /// /// Use [`SymmetricAlgorithm::from`] to translate a numeric value to a /// symbolic one. /// /// [`SymmetricAlgorithm::from`]: std::convert::From /// /// # Examples /// /// Use `SymmetricAlgorithm` to set the preferred symmetric algorithms on a signature: /// /// ```rust /// use sequoia_openpgp as openpgp; /// use openpgp::packet::signature::SignatureBuilder; /// use openpgp::types::{HashAlgorithm, SymmetricAlgorithm, SignatureType}; /// /// # fn main() -> openpgp::Result<()> { /// let mut builder = SignatureBuilder::new(SignatureType::DirectKey) /// .set_hash_algo(HashAlgorithm::SHA512) /// .set_preferred_symmetric_algorithms(vec![ /// SymmetricAlgorithm::AES256, /// ])?; /// # Ok(()) } /// ``` #[non_exhaustive] #[derive(Clone, Copy, Hash, PartialEq, Eq, Debug, PartialOrd, Ord)] pub enum SymmetricAlgorithm { /// Null encryption. Unencrypted, /// IDEA block cipher, deprecated in RFC 9580. #[deprecated(note = "Use a newer symmetric algorithm instead.")] IDEA, /// 3-DES in EDE configuration, deprecated in RFC 9580. #[deprecated(note = "Use a newer symmetric algorithm instead.")] TripleDES, /// CAST5/CAST128 block cipher, deprecated in RFC 9580. #[deprecated(note = "Use a newer symmetric algorithm instead.")] CAST5, /// Schneier et.al. Blowfish block cipher. Blowfish, /// 10-round AES. AES128, /// 12-round AES. AES192, /// 14-round AES. AES256, /// Twofish block cipher. Twofish, /// 18 rounds of NESSIEs Camellia. Camellia128, /// 24 rounds of NESSIEs Camellia w/192 bit keys. Camellia192, /// 24 rounds of NESSIEs Camellia w/256 bit keys. Camellia256, /// Private algorithm identifier. Private(u8), /// Unknown algorithm identifier. Unknown(u8), } assert_send_and_sync!(SymmetricAlgorithm); #[allow(deprecated)] const SYMMETRIC_ALGORITHM_VARIANTS: [ SymmetricAlgorithm; 11 ] = [ SymmetricAlgorithm::IDEA, SymmetricAlgorithm::TripleDES, SymmetricAlgorithm::CAST5, SymmetricAlgorithm::Blowfish, SymmetricAlgorithm::AES128, SymmetricAlgorithm::AES192, SymmetricAlgorithm::AES256, SymmetricAlgorithm::Twofish, SymmetricAlgorithm::Camellia128, SymmetricAlgorithm::Camellia192, SymmetricAlgorithm::Camellia256, ]; impl Default for SymmetricAlgorithm { fn default() -> Self { SymmetricAlgorithm::AES256 } } impl From<u8> for SymmetricAlgorithm { fn from(u: u8) -> Self { #[allow(deprecated)] match u { 0 => SymmetricAlgorithm::Unencrypted, 1 => SymmetricAlgorithm::IDEA, 2 => SymmetricAlgorithm::TripleDES, 3 => SymmetricAlgorithm::CAST5, 4 => SymmetricAlgorithm::Blowfish, 7 => SymmetricAlgorithm::AES128, 8 => SymmetricAlgorithm::AES192, 9 => SymmetricAlgorithm::AES256, 10 => SymmetricAlgorithm::Twofish, 11 => SymmetricAlgorithm::Camellia128, 12 => SymmetricAlgorithm::Camellia192, 13 => SymmetricAlgorithm::Camellia256, 100..=110 => SymmetricAlgorithm::Private(u), u => SymmetricAlgorithm::Unknown(u), } } } impl From<SymmetricAlgorithm> for u8 { fn from(s: SymmetricAlgorithm) -> u8 { #[allow(deprecated)] match s { SymmetricAlgorithm::Unencrypted => 0, SymmetricAlgorithm::IDEA => 1, SymmetricAlgorithm::TripleDES => 2, SymmetricAlgorithm::CAST5 => 3, SymmetricAlgorithm::Blowfish => 4, SymmetricAlgorithm::AES128 => 7, SymmetricAlgorithm::AES192 => 8, SymmetricAlgorithm::AES256 => 9, SymmetricAlgorithm::Twofish => 10, SymmetricAlgorithm::Camellia128 => 11, SymmetricAlgorithm::Camellia192 => 12, SymmetricAlgorithm::Camellia256 => 13, SymmetricAlgorithm::Private(u) => u, SymmetricAlgorithm::Unknown(u) => u, } } } /// Formats the symmetric algorithm name. /// /// There are two ways the symmetric algorithm name can be formatted. /// By default the short name is used. The alternate format uses the /// full algorithm name. /// /// # Examples /// /// ``` /// use sequoia_openpgp as openpgp; /// use openpgp::types::SymmetricAlgorithm; /// /// // default, short format /// assert_eq!("AES-128", format!("{}", SymmetricAlgorithm::AES128)); /// /// // alternate, long format /// assert_eq!("AES with 128-bit key", format!("{:#}", SymmetricAlgorithm::AES128)); /// ``` impl fmt::Display for SymmetricAlgorithm { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { #[allow(deprecated)] if f.alternate() { match *self { SymmetricAlgorithm::Unencrypted => f.write_str("Unencrypted"), SymmetricAlgorithm::IDEA => f.write_str("IDEA"), SymmetricAlgorithm::TripleDES => f.write_str("TripleDES (EDE-DES, 168 bit key derived from 192))"), SymmetricAlgorithm::CAST5 => f.write_str("CAST5 (128 bit key, 16 rounds)"), SymmetricAlgorithm::Blowfish => f.write_str("Blowfish (128 bit key, 16 rounds)"), SymmetricAlgorithm::AES128 => f.write_str("AES with 128-bit key"), SymmetricAlgorithm::AES192 => f.write_str("AES with 192-bit key"), SymmetricAlgorithm::AES256 => f.write_str("AES with 256-bit key"), SymmetricAlgorithm::Twofish => f.write_str("Twofish with 256-bit key"), SymmetricAlgorithm::Camellia128 => f.write_str("Camellia with 128-bit key"), SymmetricAlgorithm::Camellia192 => f.write_str("Camellia with 192-bit key"), SymmetricAlgorithm::Camellia256 => f.write_str("Camellia with 256-bit key"), SymmetricAlgorithm::Private(u) => f.write_fmt(format_args!("Private/Experimental symmetric key algorithm {}", u)), SymmetricAlgorithm::Unknown(u) => f.write_fmt(format_args!("Unknown symmetric key algorithm {}", u)), } } else { match *self { SymmetricAlgorithm::Unencrypted => f.write_str("Unencrypted"), SymmetricAlgorithm::IDEA => f.write_str("IDEA"), SymmetricAlgorithm::TripleDES => f.write_str("3DES"), SymmetricAlgorithm::CAST5 => f.write_str("CAST5"), SymmetricAlgorithm::Blowfish => f.write_str("Blowfish"), SymmetricAlgorithm::AES128 => f.write_str("AES-128"), SymmetricAlgorithm::AES192 => f.write_str("AES-192"), SymmetricAlgorithm::AES256 => f.write_str("AES-256"), SymmetricAlgorithm::Twofish => f.write_str("Twofish"), SymmetricAlgorithm::Camellia128 => f.write_str("Camellia-128"), SymmetricAlgorithm::Camellia192 => f.write_str("Camellia-192"), SymmetricAlgorithm::Camellia256 => f.write_str("Camellia-256"), SymmetricAlgorithm::Private(u) => f.write_fmt(format_args!("Private symmetric key algo {}", u)), SymmetricAlgorithm::Unknown(u) => f.write_fmt(format_args!("Unknown symmetric key algo {}", u)), } } } } #[cfg(test)] impl Arbitrary for SymmetricAlgorithm { fn arbitrary(g: &mut Gen) -> Self { u8::arbitrary(g).into() } } impl SymmetricAlgorithm { /// Returns an iterator over all valid variants. /// /// Returns an iterator over all known variants. This does not /// include the [`SymmetricAlgorithm::Unencrypted`], /// [`SymmetricAlgorithm::Private`], or /// [`SymmetricAlgorithm::Unknown`] variants. pub fn variants() -> impl Iterator<Item=Self> { SYMMETRIC_ALGORITHM_VARIANTS.iter().cloned() } /// Returns whether this algorithm is supported by the crypto backend. /// /// All backends support all the AES variants. /// /// # Examples /// /// ```rust /// use sequoia_openpgp as openpgp; /// use openpgp::types::SymmetricAlgorithm; /// /// assert!(SymmetricAlgorithm::AES256.is_supported()); /// assert!(SymmetricAlgorithm::TripleDES.is_supported()); /// /// assert!(!SymmetricAlgorithm::Unencrypted.is_supported()); /// assert!(!SymmetricAlgorithm::Private(101).is_supported()); /// ``` pub fn is_supported(&self) -> bool { self.is_supported_by_backend() } /// Length of a key for this algorithm in bytes. /// /// Fails if the algorithm isn't known to Sequoia. pub fn key_size(self) -> Result<usize> { #[allow(deprecated)] match self { SymmetricAlgorithm::IDEA => Ok(16), SymmetricAlgorithm::TripleDES => Ok(24), SymmetricAlgorithm::CAST5 => Ok(16), // RFC4880, Section 9.2: Blowfish (128 bit key, 16 rounds) SymmetricAlgorithm::Blowfish => Ok(16), SymmetricAlgorithm::AES128 => Ok(16), SymmetricAlgorithm::AES192 => Ok(24), SymmetricAlgorithm::AES256 => Ok(32), SymmetricAlgorithm::Twofish => Ok(32), SymmetricAlgorithm::Camellia128 => Ok(16), SymmetricAlgorithm::Camellia192 => Ok(24), SymmetricAlgorithm::Camellia256 => Ok(32), _ => Err(Error::UnsupportedSymmetricAlgorithm(self).into()), } } /// Length of a block for this algorithm in bytes. /// /// Fails if the algorithm isn't known to Sequoia. pub fn block_size(self) -> Result<usize> { #[allow(deprecated)] match self { SymmetricAlgorithm::IDEA => Ok(8), SymmetricAlgorithm::TripleDES => Ok(8), SymmetricAlgorithm::CAST5 => Ok(8), SymmetricAlgorithm::Blowfish => Ok(8), SymmetricAlgorithm::AES128 => Ok(16), SymmetricAlgorithm::AES192 => Ok(16), SymmetricAlgorithm::AES256 => Ok(16), SymmetricAlgorithm::Twofish => Ok(16), SymmetricAlgorithm::Camellia128 => Ok(16), SymmetricAlgorithm::Camellia192 => Ok(16), SymmetricAlgorithm::Camellia256 => Ok(16), _ => Err(Error::UnsupportedSymmetricAlgorithm(self).into()), } } } #[cfg(test)] mod tests { use super::*; quickcheck! { fn sym_roundtrip(sym: SymmetricAlgorithm) -> bool { let val: u8 = sym.into(); sym == SymmetricAlgorithm::from(val) } } quickcheck! { fn sym_display(sym: SymmetricAlgorithm) -> bool { let s = format!("{}", sym); !s.is_empty() } } quickcheck! { fn sym_parse(sym: SymmetricAlgorithm) -> bool { match sym { SymmetricAlgorithm::Unknown(u) => u == 5 || u == 6 || u > 110 || (u > 10 && u < 100), SymmetricAlgorithm::Private(u) => (100..=110).contains(&u), _ => true } } } #[test] fn symmetric_algorithms_variants() { use std::collections::HashSet; use std::iter::FromIterator; // SYMMETRIC_ALGORITHM_VARIANTS is a list. Derive it in a // different way to double-check that nothing is missing. let derived_variants = (0..=u8::MAX) .map(SymmetricAlgorithm::from) .filter(|t| { match t { SymmetricAlgorithm::Unencrypted => false, SymmetricAlgorithm::Private(_) => false, SymmetricAlgorithm::Unknown(_) => false, _ => true, } }) .collect::<HashSet<_>>(); let known_variants = HashSet::from_iter(SYMMETRIC_ALGORITHM_VARIANTS .iter().cloned()); let missing = known_variants .symmetric_difference(&derived_variants) .collect::<Vec<_>>(); assert!(missing.is_empty(), "{:?}", missing); } } ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/src/crypto/types.rs�����������������������������������������������������������0000644�0000000�0000000�00000000543�10461020230�0016562�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������//! Types for the crypto module. pub mod aead_algorithm; pub use aead_algorithm::AEADAlgorithm; pub mod curve; pub use curve::Curve; pub mod hash_algorithm; pub use hash_algorithm::HashAlgorithm; pub mod public_key_algorithm; pub use public_key_algorithm::PublicKeyAlgorithm; pub mod symmetric_algorithm; pub use symmetric_algorithm::SymmetricAlgorithm; �������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/src/fingerprint.rs������������������������������������������������������������0000644�0000000�0000000�00000047206�10461020230�0016434�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������use std::borrow::Borrow; use std::fmt; #[cfg(test)] use quickcheck::{Arbitrary, Gen}; use crate::Error; use crate::KeyHandle; use crate::KeyID; use crate::Result; /// A long identifier for certificates and keys. /// /// A `Fingerprint` uniquely identifies a public key. /// /// Currently, Sequoia supports *version 6* fingerprints and Key IDs, /// and *version 4* fingerprints and Key IDs. *Version 3* /// fingerprints and Key IDs were deprecated by [RFC 4880] in 2007. /// /// Essentially, a fingerprint is a hash over the key's public key /// packet. For details, see [Section 5.5.4 of RFC 9580]. /// /// Fingerprints are used, for example, to reference the issuing key /// of a signature in its [`IssuerFingerprint`] subpacket. As a /// general rule of thumb, you should prefer using fingerprints over /// KeyIDs because the latter are vulnerable to [birthday attack]s. /// /// See also [`KeyID`] and [`KeyHandle`]. /// /// [RFC 4880]: https://tools.ietf.org/html/rfc4880 /// [Section 5.5.4 of RFC 9580]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.5.4 /// [`IssuerFingerprint`]: crate::packet::signature::subpacket::SubpacketValue::IssuerFingerprint /// [birthday attack]: https://nullprogram.com/blog/2019/07/22/ /// [`KeyID`]: crate::KeyID /// [`KeyHandle`]: crate::KeyHandle /// /// # Examples /// /// ```rust /// # fn main() -> sequoia_openpgp::Result<()> { /// # use sequoia_openpgp as openpgp; /// use openpgp::Fingerprint; /// /// let fp: Fingerprint = /// "0123 4567 89AB CDEF 0123 4567 89AB CDEF 0123 4567".parse()?; /// /// assert_eq!("0123456789ABCDEF0123456789ABCDEF01234567", fp.to_hex()); /// # Ok(()) } /// ``` #[non_exhaustive] #[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Hash)] pub enum Fingerprint { /// Fingerprint of v6 certificates and keys. V6([u8; 32]), /// Fingerprint of v4 certificates and keys. V4([u8; 20]), /// Fingerprint of unknown version or shape. Unknown { /// Version of the fingerprint, if known. version: Option<u8>, /// Raw bytes of the fingerprint. bytes: Box<[u8]>, }, } assert_send_and_sync!(Fingerprint); impl fmt::Display for Fingerprint { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{:X}", self) } } impl fmt::Debug for Fingerprint { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { Fingerprint::V4(_) => write!(f, "Fingerprint::V4({})", self), Fingerprint::V6(_) => write!(f, "Fingerprint::V6({})", self), Fingerprint::Unknown { version, .. } => write!(f, "Fingerprint::Unknown {{ {:?}, {} }}", version, self), } } } impl fmt::UpperHex for Fingerprint { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { self.write_to_fmt(f, true) } } impl fmt::LowerHex for Fingerprint { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { self.write_to_fmt(f, false) } } impl std::str::FromStr for Fingerprint { type Err = anyhow::Error; fn from_str(s: &str) -> std::result::Result<Self, Self::Err> { if s.chars().filter(|c| ! c.is_whitespace()).count() % 2 == 1 { return Err(crate::Error::InvalidArgument( "Odd number of nibbles".into()).into()); } Self::from_bytes_intern(None, &crate::fmt::hex::decode_pretty(s)?) } } impl Fingerprint { /// Creates a `Fingerprint` from a byte slice in big endian /// representation. /// /// # Examples /// /// ```rust /// # fn main() -> sequoia_openpgp::Result<()> { /// # use sequoia_openpgp as openpgp; /// use openpgp::Fingerprint; /// /// let fp: Fingerprint = /// "0123 4567 89AB CDEF 0123 4567 89AB CDEF 0123 4567".parse()?; /// let bytes = /// [0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF, 0x01, 0x23, /// 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF, 0x01, 0x23, 0x45, 0x67]; /// /// assert_eq!(Fingerprint::from_bytes(4, &bytes)?, fp); /// # Ok(()) } /// ``` pub fn from_bytes(version: u8, raw: &[u8]) -> Result<Fingerprint> { Self::from_bytes_intern(Some(version), raw) } /// Like [`Fingerprint::from_bytes`], but with optional version. pub(crate) fn from_bytes_intern(mut version: Option<u8>, raw: &[u8]) -> Result<Fingerprint> { // Apply some heuristics if no explicit version is known. if version.is_none() && raw.len() == 32 { version = Some(6); } else if version.is_none() && raw.len() == 20 { version = Some(4); } match version { Some(6) => if raw.len() == 32 { let mut fp: [u8; 32] = Default::default(); fp.copy_from_slice(raw); Ok(Fingerprint::V6(fp)) } else { Err(Error::InvalidArgument(format!( "a v6 fingerprint consists of 32 bytes, got {}", raw.len())).into()) }, Some(4) => if raw.len() == 20 { let mut fp : [u8; 20] = Default::default(); fp.copy_from_slice(raw); Ok(Fingerprint::V4(fp)) } else { Err(Error::InvalidArgument(format!( "a v4 fingerprint consists of 20 bytes, got {}", raw.len())).into()) }, _ => Ok(Fingerprint::Unknown { version, bytes: raw.to_vec().into_boxed_slice(), }), } } /// Returns the raw fingerprint as a byte slice in big endian /// representation. /// /// # Examples /// /// ```rust /// # fn main() -> sequoia_openpgp::Result<()> { /// # use sequoia_openpgp as openpgp; /// use openpgp::Fingerprint; /// /// let fp: Fingerprint = /// "0123 4567 89AB CDEF 0123 4567 89AB CDEF 0123 4567".parse()?; /// /// assert_eq!(fp.as_bytes(), /// [0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF, 0x01, 0x23, /// 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF, 0x01, 0x23, 0x45, 0x67]); /// # Ok(()) } /// ``` pub fn as_bytes(&self) -> &[u8] { match self { Fingerprint::V4(ref fp) => fp, Fingerprint::V6(fp) => fp, Fingerprint::Unknown { bytes, .. } => bytes, } } /// Converts this fingerprint to its canonical hexadecimal /// representation. /// /// This representation is always uppercase and without spaces and /// is suitable for stable key identifiers. /// /// The output of this function is exactly the same as formatting /// this object with the `:X` format specifier. /// /// ```rust /// # fn main() -> sequoia_openpgp::Result<()> { /// # use sequoia_openpgp as openpgp; /// use openpgp::Fingerprint; /// /// let fp: Fingerprint = /// "0123 4567 89AB CDEF 0123 4567 89AB CDEF 0123 4567".parse()?; /// /// assert_eq!("0123456789ABCDEF0123456789ABCDEF01234567", fp.to_hex()); /// assert_eq!(format!("{:X}", fp), fp.to_hex()); /// # Ok(()) } /// ``` pub fn to_hex(&self) -> String { use std::fmt::Write; let mut output = String::with_capacity( // Each byte results in two hex characters. self.as_bytes().len() * 2); // We write to String that never fails but the Write API // returns Results. write!(output, "{:X}", self).unwrap(); output } /// Converts this fingerprint to its hexadecimal representation /// with spaces. /// /// This representation is always uppercase and with spaces /// grouping the hexadecimal digits into groups of four with a /// double space in the middle. It is only suitable for manual /// comparison of fingerprints. /// /// Note: The spaces will hinder other kind of use cases. For /// example, it is harder to select the whole fingerprint for /// copying, and it has to be quoted when used as a command line /// argument. Only use this form for displaying a fingerprint /// with the intent of manual comparisons. /// /// See also [`Fingerprint::to_icao`]. /// /// [`Fingerprint::to_icao`]: Fingerprint::to_icao() /// /// ```rust /// # fn main() -> sequoia_openpgp::Result<()> { /// # use sequoia_openpgp as openpgp; /// let fp: openpgp::Fingerprint = /// "0123 4567 89AB CDEF 0123 4567 89AB CDEF 0123 4567".parse()?; /// /// assert_eq!("0123 4567 89AB CDEF 0123 4567 89AB CDEF 0123 4567", /// fp.to_spaced_hex()); /// # Ok(()) } /// ``` pub fn to_spaced_hex(&self) -> String { use std::fmt::Write; let raw_len = self.as_bytes().len(); let mut output = String::with_capacity( // Each byte results in two hex characters. raw_len * 2 + // Every 2 bytes of output, we insert a space. raw_len / 2 // After half of the groups, there is another space. + 1); // We write to String that never fails but the Write API // returns Results. write!(output, "{:#X}", self).unwrap(); output } /// Parses the hexadecimal representation of an OpenPGP /// fingerprint. /// /// This function is the reverse of `to_hex`. It also accepts /// other variants of the fingerprint notation including /// lower-case letters, spaces and optional leading `0x`. /// /// ```rust /// # fn main() -> sequoia_openpgp::Result<()> { /// # use sequoia_openpgp as openpgp; /// use openpgp::Fingerprint; /// /// let fp = /// Fingerprint::from_hex("0123456789ABCDEF0123456789ABCDEF01234567")?; /// /// assert_eq!("0123456789ABCDEF0123456789ABCDEF01234567", fp.to_hex()); /// /// let fp = /// Fingerprint::from_hex("0123 4567 89ab cdef 0123 4567 89ab cdef 0123 4567")?; /// /// assert_eq!("0123456789ABCDEF0123456789ABCDEF01234567", fp.to_hex()); /// # Ok(()) } /// ``` pub fn from_hex(s: &str) -> std::result::Result<Self, anyhow::Error> { std::str::FromStr::from_str(s) } /// Common code for the above functions. fn write_to_fmt(&self, f: &mut fmt::Formatter, upper_case: bool) -> fmt::Result { use std::fmt::Write; let raw = self.as_bytes(); // We currently only handle V4 fingerprints, which look like: // // 8F17 7771 18A3 3DDA 9BA4 8E62 AACB 3243 6300 52D9 // // Since we have no idea how to format an invalid fingerprint, // just format it like a V4 fingerprint and hope for the best. // XXX: v5 fingerprints have no human-readable formatting by // choice. let a_letter = if upper_case { b'A' } else { b'a' }; let pretty = f.alternate(); for (i, b) in raw.iter().enumerate() { if pretty && i > 0 && i % 2 == 0 { f.write_char(' ')?; } if pretty && i > 0 && i * 2 == raw.len() { f.write_char(' ')?; } let top = b >> 4; let bottom = b & 0xFu8; if top < 10u8 { f.write_char((b'0' + top) as char)?; } else { f.write_char((a_letter + (top - 10u8)) as char)?; } if bottom < 10u8 { f.write_char((b'0' + bottom) as char)?; } else { f.write_char((a_letter + (bottom - 10u8)) as char)?; } } Ok(()) } /// Converts the hex representation of the `Fingerprint` to a /// phrase in the [ICAO spelling alphabet]. /// /// [ICAO spelling alphabet]: https://en.wikipedia.org/wiki/ICAO_spelling_alphabet /// /// # Examples /// /// ```rust /// # fn main() -> sequoia_openpgp::Result<()> { /// # use sequoia_openpgp as openpgp; /// use openpgp::Fingerprint; /// /// let fp: Fingerprint = /// "01AB 4567 89AB CDEF 0123 4567 89AB CDEF 0123 4567".parse()?; /// /// assert!(fp.to_icao().starts_with("Zero One Alfa Bravo")); /// /// # let expected = "\ /// # Zero One Alfa Bravo Four Five Six Seven Eight Niner Alfa Bravo \ /// # Charlie Delta Echo Foxtrot Zero One Two Three Four Five Six Seven \ /// # Eight Niner Alfa Bravo Charlie Delta Echo Foxtrot Zero One Two \ /// # Three Four Five Six Seven"; /// # assert_eq!(fp.to_icao(), expected); /// # /// # Ok(()) } /// ``` pub fn to_icao(&self) -> String { let mut ret = String::default(); for ch in self.to_hex().chars() { let word = match ch { '0' => "Zero", '1' => "One", '2' => "Two", '3' => "Three", '4' => "Four", '5' => "Five", '6' => "Six", '7' => "Seven", '8' => "Eight", '9' => "Niner", 'A' => "Alfa", 'B' => "Bravo", 'C' => "Charlie", 'D' => "Delta", 'E' => "Echo", 'F' => "Foxtrot", _ => { continue; } }; if !ret.is_empty() { ret.push(' '); } ret.push_str(word); } ret } /// Returns whether `self` and `other` could be aliases of each /// other. /// /// `KeyHandle`'s `PartialEq` implementation cannot assert that a /// `Fingerprint` and a `KeyID` are equal, because distinct /// fingerprints may have the same `KeyID`, and `PartialEq` must /// be [transitive], i.e., /// /// ```text /// a == b and b == c implies a == c. /// ``` /// /// [transitive]: std::cmp::PartialEq /// /// That is, if `fpr1` and `fpr2` are distinct fingerprints with the /// same key ID then: /// /// ```text /// fpr1 == keyid and fpr2 == keyid, but fpr1 != fpr2. /// ``` /// /// This definition of equality makes searching for a given /// `KeyHandle` using `PartialEq` awkward. This function fills /// that gap. It answers the question: given a `KeyHandle` and a /// `Fingerprint`, could they be aliases? That is, it implements /// the desired, non-transitive equality relation: /// /// ``` /// # fn main() -> sequoia_openpgp::Result<()> { /// # use sequoia_openpgp as openpgp; /// # use openpgp::Fingerprint; /// # use openpgp::KeyID; /// # use openpgp::KeyHandle; /// # /// # let fpr1: Fingerprint /// # = "8F17 7771 18A3 3DDA 9BA4 8E62 AACB 3243 6300 52D9" /// # .parse::<Fingerprint>()?; /// # /// # let fpr2: Fingerprint /// # = "0123 4567 8901 2345 6789 0123 AACB 3243 6300 52D9" /// # .parse::<Fingerprint>()?; /// # /// # let keyid: KeyID = "AACB 3243 6300 52D9".parse::<KeyID>()?; /// # /// // fpr1 and fpr2 are different fingerprints with the same KeyID. /// assert_ne!(fpr1, fpr2); /// assert!(fpr1.aliases(KeyHandle::from(&keyid))); /// assert!(fpr2.aliases(KeyHandle::from(&keyid))); /// assert!(! fpr1.aliases(KeyHandle::from(&fpr2))); /// # Ok(()) } /// ``` pub fn aliases<H>(&self, other: H) -> bool where H: Borrow<KeyHandle> { let other = other.borrow(); match (self, other) { (f, KeyHandle::Fingerprint(o)) => { f == o }, (Fingerprint::V4(f), KeyHandle::KeyID(KeyID::Long(o))) => { // Avoid a heap allocation by embedding our // knowledge of how a v4 key ID is derived from a // v4 fingerprint: // // A v4 key ID are the 8 right-most octets of a v4 // fingerprint. &f[12..] == o }, (Fingerprint::V6(f), KeyHandle::KeyID(KeyID::Long(o))) => { // A v6 key ID are the 8 left-most octets of a v6 // fingerprint. &f[..8] == o }, (f, KeyHandle::KeyID(o)) => { &KeyID::from(f) == o }, } } } #[cfg(test)] impl Fingerprint { pub(crate) fn arbitrary_v4(g: &mut Gen) -> Self { let mut fp = [0; 20]; fp.iter_mut().for_each(|p| *p = Arbitrary::arbitrary(g)); Fingerprint::V4(fp) } pub(crate) fn arbitrary_v6(g: &mut Gen) -> Self { let mut fp = [0; 32]; fp.iter_mut().for_each(|p| *p = Arbitrary::arbitrary(g)); Fingerprint::V6(fp) } } #[cfg(test)] impl Arbitrary for Fingerprint { fn arbitrary(g: &mut Gen) -> Self { if Arbitrary::arbitrary(g) { Self::arbitrary_v4(g) } else { Self::arbitrary_v6(g) } } } #[cfg(test)] mod tests { use super::*; #[test] fn v4_hex_formatting() { let fp = "0123 4567 89AB CDEF 0123 4567 89AB CDEF 0123 4567" .parse::<Fingerprint>().unwrap(); assert!(matches!(&fp, Fingerprint::V4(_))); assert_eq!(format!("{:X}", fp), "0123456789ABCDEF0123456789ABCDEF01234567"); assert_eq!(format!("{:x}", fp), "0123456789abcdef0123456789abcdef01234567"); } #[test] fn v5_hex_formatting() -> crate::Result<()> { let fp = "0123 4567 89AB CDEF 0123 4567 89AB CDEF \ 0123 4567 89AB CDEF 0123 4567 89AB CDEF" .parse::<Fingerprint>()?; assert!(matches!(&fp, Fingerprint::V6(_))); assert_eq!(format!("{:X}", fp), "0123456789ABCDEF0123456789ABCDEF\ 0123456789ABCDEF0123456789ABCDEF"); assert_eq!(format!("{:x}", fp), "0123456789abcdef0123456789abcdef\ 0123456789abcdef0123456789abcdef"); Ok(()) } #[test] fn aliases() -> crate::Result<()> { // fp1 and fp15 have the same key ID, but are different // fingerprints. let fp1 = "280C0AB0B94D1302CAAEB71DA299CDCD3884EBEA" .parse::<Fingerprint>()?; let fp15 = "1234567890ABCDEF12345678A299CDCD3884EBEA" .parse::<Fingerprint>()?; let fp2 = "F8D921C01EE93B65D4C6FEB7B456A7DB5E4274D0" .parse::<Fingerprint>()?; let keyid1 = KeyID::from(&fp1); let keyid15 = KeyID::from(&fp15); let keyid2 = KeyID::from(&fp2); eprintln!("fp1: {:?}", fp1); eprintln!("keyid1: {:?}", keyid1); eprintln!("fp15: {:?}", fp15); eprintln!("keyid15: {:?}", keyid15); eprintln!("fp2: {:?}", fp2); eprintln!("keyid2: {:?}", keyid2); assert_ne!(fp1, fp15); assert_eq!(keyid1, keyid15); // Compare fingerprints to fingerprints. assert!(fp1.aliases(KeyHandle::from(&fp1))); assert!(! fp1.aliases(KeyHandle::from(&fp15))); assert!(! fp1.aliases(KeyHandle::from(&fp2))); assert!(! fp15.aliases(KeyHandle::from(&fp1))); assert!(fp15.aliases(KeyHandle::from(&fp15))); assert!(! fp15.aliases(KeyHandle::from(&fp2))); assert!(! fp2.aliases(KeyHandle::from(&fp1))); assert!(! fp2.aliases(KeyHandle::from(&fp15))); assert!(fp2.aliases(KeyHandle::from(&fp2))); // Compare fingerprints to key IDs. assert!(fp1.aliases(KeyHandle::from(&keyid1))); assert!(fp1.aliases(KeyHandle::from(&keyid15))); assert!(! fp1.aliases(KeyHandle::from(&keyid2))); assert!(fp15.aliases(KeyHandle::from(&keyid1))); assert!(fp15.aliases(KeyHandle::from(&keyid15))); assert!(! fp15.aliases(KeyHandle::from(&keyid2))); assert!(! fp2.aliases(KeyHandle::from(&keyid1))); assert!(! fp2.aliases(KeyHandle::from(&keyid15))); assert!(fp2.aliases(KeyHandle::from(&keyid2))); Ok(()) } } ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/src/fmt.rs��������������������������������������������������������������������0000644�0000000�0000000�00000040153�10461020230�0014665�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������//! Utilities for formatting, printing, and user communication. use crate::Error; use crate::Result; /// Converts buffers to and from hexadecimal numbers. pub mod hex { use std::io; use crate::Result; /// Encodes the given buffer as hexadecimal number. pub fn encode<B: AsRef<[u8]>>(buffer: B) -> String { super::to_hex(buffer.as_ref(), false) } /// Encodes the given buffer as hexadecimal number with spaces. pub fn encode_pretty<B: AsRef<[u8]>>(buffer: B) -> String { super::to_hex(buffer.as_ref(), true) } /// Decodes the given hexadecimal number. pub fn decode<H: AsRef<str>>(hex: H) -> Result<Vec<u8>> { super::from_hex(hex.as_ref(), false) } /// Decodes the given hexadecimal number, ignoring whitespace. pub fn decode_pretty<H: AsRef<str>>(hex: H) -> Result<Vec<u8>> { super::from_hex(hex.as_ref(), true) } /// Dumps binary data, like `hd(1)`. pub fn dump<W: io::Write, B: AsRef<[u8]>>(sink: W, data: B) -> io::Result<()> { Dumper::new(sink, "").write_ascii(data) } /// Writes annotated hex dumps, like hd(1). /// /// # Examples /// /// ```rust /// # fn main() -> sequoia_openpgp::Result<()> { /// use sequoia_openpgp::fmt::hex; /// /// let mut dumper = hex::Dumper::new(Vec::new(), ""); /// dumper.write(&[0x89, 0x01, 0x33], "frame")?; /// dumper.write(&[0x04], "version")?; /// dumper.write(&[0x00], "type")?; /// /// let buf = dumper.into_inner(); /// assert_eq!( /// ::std::str::from_utf8(&buf[..])?, /// "00000000 89 01 33 frame\n\ /// 00000003 04 version\n\ /// 00000004 00 type\n\ /// "); /// # Ok(()) } /// ``` pub struct Dumper<W: io::Write> { inner: W, indent: String, offset: usize, } assert_send_and_sync!(Dumper<W> where W: io::Write); impl<W: io::Write> Dumper<W> { /// Creates a new dumper. /// /// The dump is written to `inner`. Every line is indented with /// `indent`. pub fn new<I: AsRef<str>>(inner: W, indent: I) -> Self { Self::with_offset(inner, indent, 0) } /// Creates a new dumper starting at the given offset. /// /// The dump is written to `inner`. Every line is indented with /// `indent`. pub fn with_offset<I>(inner: W, indent: I, offset: usize) -> Self where I: AsRef<str>, { Dumper { inner, indent: indent.as_ref().into(), offset, } } /// Returns the inner writer. pub fn into_inner(self) -> W { self.inner } /// Writes a chunk of data. /// /// The `msg` is printed at the end of the first line. pub fn write<B, M>(&mut self, buf: B, msg: M) -> io::Result<()> where B: AsRef<[u8]>, M: AsRef<str>, { let mut first = true; self.write_labeled(buf.as_ref(), move |_, _| { if first { first = false; Some(msg.as_ref().into()) } else { None } }) } /// Writes a chunk of data with ASCII-representation. /// /// This produces output similar to `hd(1)`. pub fn write_ascii<B>(&mut self, buf: B) -> io::Result<()> where B: AsRef<[u8]>, { self.write_labeled(buf, |offset, data| { let mut l = String::new(); for _ in 0..offset { l.push(' '); } for &c in data { l.push(if c < 32 { '.' } else if c < 128 { c.into() } else { '.' }) } Some(l) }) } /// Writes a chunk of data. /// /// For each line, the given function is called to compute a /// label that printed at the end of the first line. The /// functions first argument is the offset in the current line /// (0..16), the second the slice of the displayed data. pub fn write_labeled<B, L>(&mut self, buf: B, mut labeler: L) -> io::Result<()> where B: AsRef<[u8]>, L: FnMut(usize, &[u8]) -> Option<String>, { let buf = buf.as_ref(); let mut first_label_offset = self.offset % 16; write!(self.inner, "{}{:08x} ", self.indent, self.offset)?; for i in 0 .. self.offset % 16 { if i != 7 { write!(self.inner, " ")?; } else { write!(self.inner, " ")?; } } let mut offset_printed = true; let mut data_start = 0; for (i, c) in buf.iter().enumerate() { if ! offset_printed { write!(self.inner, "\n{}{:08x} ", self.indent, self.offset)?; offset_printed = true; } write!(self.inner, " {:02x}", c)?; self.offset += 1; match self.offset % 16 { 0 => { if let Some(msg) = Some(&buf[data_start..i + 1]) .filter(|b| ! b.is_empty()) .and_then(|b| labeler(first_label_offset, b)) { write!(self.inner, " {}", msg)?; // Only the first label is offset. first_label_offset = 0; } data_start = i + 1; offset_printed = false; }, 8 => write!(self.inner, " ")?, _ => (), } } if let Some(msg) = Some(&buf[data_start..]) .filter(|b| ! b.is_empty()) .and_then(|b| labeler(first_label_offset, b)) { for i in self.offset % 16 .. 16 { if i != 7 { write!(self.inner, " ")?; } else { write!(self.inner, " ")?; } } write!(self.inner, " {}", msg)?; } writeln!(self.inner)?; Ok(()) } } } /// A helpful debugging function. #[allow(dead_code)] pub(crate) fn to_hex(s: &[u8], pretty: bool) -> String { use std::fmt::Write; let mut result = String::new(); for (i, b) in s.iter().enumerate() { // Add spaces every four digits to make the output more // readable. if pretty && i > 0 && i % 2 == 0 { write!(&mut result, " ").unwrap(); } write!(&mut result, "{:02X}", b).unwrap(); } result } /// A helpful function for converting a hexadecimal string to binary. /// This function skips whitespace if `pretty` is set. pub(crate) fn from_hex(hex: &str, pretty: bool) -> Result<Vec<u8>> { const BAD: u8 = 255u8; const X: u8 = b'x'; let mut nibbles = hex.chars().filter_map(|x| { match x { '0' => Some(0u8), '1' => Some(1u8), '2' => Some(2u8), '3' => Some(3u8), '4' => Some(4u8), '5' => Some(5u8), '6' => Some(6u8), '7' => Some(7u8), '8' => Some(8u8), '9' => Some(9u8), 'a' | 'A' => Some(10u8), 'b' | 'B' => Some(11u8), 'c' | 'C' => Some(12u8), 'd' | 'D' => Some(13u8), 'e' | 'E' => Some(14u8), 'f' | 'F' => Some(15u8), 'x' | 'X' if pretty => Some(X), _ if pretty && x.is_whitespace() => None, _ => Some(BAD), } }).collect::<Vec<u8>>(); if pretty && nibbles.len() >= 2 && nibbles[0] == 0 && nibbles[1] == X { // Drop '0x' prefix. nibbles.remove(0); nibbles.remove(0); } if nibbles.iter().any(|&b| b == BAD || b == X) { // Not a hex character. return Err(Error::InvalidArgument("Invalid characters".into()).into()); } // We need an even number of nibbles. if nibbles.len() % 2 != 0 { nibbles.insert(0, 0); } let bytes = nibbles.chunks(2).map(|nibbles| { (nibbles[0] << 4) | nibbles[1] }).collect::<Vec<u8>>(); Ok(bytes) } /// Formats the given time using ISO 8601. /// /// This is a no-dependency, best-effort mechanism. If the given time /// is not representable using unsigned UNIX time, we return the debug /// formatting. pub(crate) fn time(t: &std::time::SystemTime) -> String { // Actually use a chrono dependency for WASM since there's no strftime // (except for WASI). #[cfg(all(target_arch = "wasm32", target_os = "unknown"))] { chrono::DateTime::<chrono::Utc>::from(t.clone()) .format("%Y-%m-%dT%H:%M:%SZ") .to_string() } #[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))] { extern "C" { fn strftime( s: *mut libc::c_char, max: libc::size_t, format: *const libc::c_char, tm: *const libc::tm, ) -> usize; } let t = match t.duration_since(std::time::UNIX_EPOCH) { Ok(t) => t.as_secs() as libc::time_t, Err(_) => return format!("{:?}", t), }; let fmt = b"%Y-%m-%dT%H:%M:%SZ\x00"; assert_eq!(b"2020-03-26T10:08:10Z\x00".len(), 21); let mut s = [0u8; 21]; unsafe { let mut tm: libc::tm = std::mem::zeroed(); #[cfg(unix)] libc::gmtime_r(&t, &mut tm); #[cfg(windows)] libc::gmtime_s(&mut tm, &t); strftime(s.as_mut_ptr() as *mut libc::c_char, s.len(), fmt.as_ptr() as *const libc::c_char, &tm); } std::ffi::CStr::from_bytes_with_nul(&s) .expect("strftime nul terminates string") .to_string_lossy().into() } } #[cfg(test)] mod test { #[test] fn from_hex() { use super::from_hex as fh; assert_eq!(fh("", false).ok(), Some(vec![])); assert_eq!(fh("0", false).ok(), Some(vec![0x00])); assert_eq!(fh("00", false).ok(), Some(vec![0x00])); assert_eq!(fh("09", false).ok(), Some(vec![0x09])); assert_eq!(fh("0f", false).ok(), Some(vec![0x0f])); assert_eq!(fh("99", false).ok(), Some(vec![0x99])); assert_eq!(fh("ff", false).ok(), Some(vec![0xff])); assert_eq!(fh("000", false).ok(), Some(vec![0x00, 0x00])); assert_eq!(fh("0000", false).ok(), Some(vec![0x00, 0x00])); assert_eq!(fh("0009", false).ok(), Some(vec![0x00, 0x09])); assert_eq!(fh("000f", false).ok(), Some(vec![0x00, 0x0f])); assert_eq!(fh("0099", false).ok(), Some(vec![0x00, 0x99])); assert_eq!(fh("00ff", false).ok(), Some(vec![0x00, 0xff])); assert_eq!(fh("\t\n\x0c\r ", false).ok(), None); assert_eq!(fh("a", false).ok(), Some(vec![0x0a])); assert_eq!(fh("0x", false).ok(), None); assert_eq!(fh("0x0", false).ok(), None); assert_eq!(fh("0x00", false).ok(), None); } #[test] fn from_pretty_hex() { use super::from_hex as fh; assert_eq!(fh(" ", true).ok(), Some(vec![])); assert_eq!(fh(" 0", true).ok(), Some(vec![0x00])); assert_eq!(fh(" 00", true).ok(), Some(vec![0x00])); assert_eq!(fh(" 09", true).ok(), Some(vec![0x09])); assert_eq!(fh(" 0f", true).ok(), Some(vec![0x0f])); assert_eq!(fh(" 99", true).ok(), Some(vec![0x99])); assert_eq!(fh(" ff", true).ok(), Some(vec![0xff])); assert_eq!(fh(" 00 0", true).ok(), Some(vec![0x00, 0x00])); assert_eq!(fh(" 00 00", true).ok(), Some(vec![0x00, 0x00])); assert_eq!(fh(" 00 09", true).ok(), Some(vec![0x00, 0x09])); assert_eq!(fh(" 00 0f", true).ok(), Some(vec![0x00, 0x0f])); assert_eq!(fh(" 00 99", true).ok(), Some(vec![0x00, 0x99])); assert_eq!(fh(" 00 ff", true).ok(), Some(vec![0x00, 0xff])); assert_eq!(fh("\t\n\x0c\r ", true).ok(), Some(vec![])); // Fancy Unicode spaces are ok too: assert_eq!(fh("     23", true).ok(), Some(vec![0x23])); assert_eq!(fh("a", true).ok(), Some(vec![0x0a])); assert_eq!(fh(" 0x", true).ok(), Some(vec![])); assert_eq!(fh(" 0x0", true).ok(), Some(vec![0x00])); assert_eq!(fh(" 0x00", true).ok(), Some(vec![0x00])); } quickcheck! { fn hex_roundtrip(data: Vec<u8>) -> bool { let hex = super::to_hex(&data, false); data == super::from_hex(&hex, false).unwrap() } } quickcheck! { fn pretty_hex_roundtrip(data: Vec<u8>) -> bool { let hex = super::to_hex(&data, true); data == super::from_hex(&hex, true).unwrap() } } #[test] fn hex_dumper() { use super::hex::Dumper; let mut dumper = Dumper::new(Vec::new(), "III"); dumper.write(&[0x89, 0x01, 0x33], "frame").unwrap(); let buf = dumper.into_inner(); assert_eq!( ::std::str::from_utf8(&buf[..]).unwrap(), "III00000000 \ 89 01 33 \ frame\n"); let mut dumper = Dumper::new(Vec::new(), "III"); dumper.write(&[0x89, 0x01, 0x33, 0x89, 0x01, 0x33, 0x89, 0x01], "frame") .unwrap(); let buf = dumper.into_inner(); assert_eq!( ::std::str::from_utf8(&buf[..]).unwrap(), "III00000000 \ 89 01 33 89 01 33 89 01 \ frame\n"); let mut dumper = Dumper::new(Vec::new(), "III"); dumper.write(&[0x89, 0x01, 0x33, 0x89, 0x01, 0x33, 0x89, 0x01, 0x89, 0x01, 0x33, 0x89, 0x01, 0x33, 0x89, 0x01], "frame") .unwrap(); let buf = dumper.into_inner(); assert_eq!( ::std::str::from_utf8(&buf[..]).unwrap(), "III00000000 \ 89 01 33 89 01 33 89 01 89 01 33 89 01 33 89 01 \ frame\n"); let mut dumper = Dumper::new(Vec::new(), "III"); dumper.write(&[0x89, 0x01, 0x33, 0x89, 0x01, 0x33, 0x89, 0x01, 0x89, 0x01, 0x33, 0x89, 0x01, 0x33, 0x89, 0x01, 0x89, 0x01, 0x33, 0x89, 0x01, 0x33, 0x89, 0x01, 0x89, 0x01, 0x33, 0x89, 0x01, 0x33, 0x89, 0x01], "frame") .unwrap(); let buf = dumper.into_inner(); assert_eq!( ::std::str::from_utf8(&buf[..]).unwrap(), "III00000000 \ 89 01 33 89 01 33 89 01 89 01 33 89 01 33 89 01 \ frame\n\ III00000010 \ 89 01 33 89 01 33 89 01 89 01 33 89 01 33 89 01\n"); let mut dumper = Dumper::new(Vec::new(), ""); dumper.write(&[0x89, 0x01, 0x33], "frame").unwrap(); dumper.write(&[0x04], "version").unwrap(); dumper.write(&[0x00], "type").unwrap(); let buf = dumper.into_inner(); assert_eq!( ::std::str::from_utf8(&buf[..]).unwrap(), "00000000 89 01 33 \ frame\n\ 00000003 04 \ version\n\ 00000004 00 \ type\n\ "); } #[test] fn time() { use super::time; use crate::types::Timestamp; let t = |epoch| -> std::time::SystemTime { Timestamp::from(epoch).into() }; assert_eq!(&time(&t(1585217290)), "2020-03-26T10:08:10Z"); } } ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/src/keyhandle.rs��������������������������������������������������������������0000644�0000000�0000000�00000045302�10461020230�0016044�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������use std::convert::TryFrom; use std::cmp::Ordering; use std::borrow::Borrow; use crate::{ Error, Fingerprint, KeyID, Result, }; /// Enum representing an identifier for certificates and keys. /// /// A `KeyHandle` contains either a [`Fingerprint`] or a [`KeyID`]. /// This is needed because signatures can reference their issuer /// either by `Fingerprint` or by `KeyID`. /// /// Currently, Sequoia supports *version 6* fingerprints and Key IDs, /// and *version 4* fingerprints and Key IDs. *Version 3* /// fingerprints and Key IDs were deprecated by [RFC 4880] in 2007. /// /// Essentially, a fingerprint is a hash over the key's public key /// packet. *Version 6* and *version 4* [`KeyID`]s are a truncated /// version of the key's fingerprint. For details, see [Section 5.5.4 /// of RFC 9580]. /// /// Both fingerprint and Key ID are used to identify a key, e.g., the /// issuer of a signature. /// /// [RFC 4880]: https://tools.ietf.org/html/rfc4880 /// [Section 5.5.4 of RFC 9580]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.5.4 /// /// # A Note on Equality /// /// Like other data types, two `KeyHandle`s are considered equal if /// their serialized forms are the same. That is, if you compare a /// key handle that contains a `Fingerprint`, and a key handle that /// contains a `KeyID`, they will not be considered equal **even if /// the key ID aliases the fingerprint**. If you want to check for /// aliasing, you should use [`KeyHandle::aliases`]. /// /// # Examples /// /// ```rust /// # fn main() -> sequoia_openpgp::Result<()> { /// # use sequoia_openpgp as openpgp; /// use openpgp::KeyHandle; /// use openpgp::KeyID; /// use openpgp::Packet; /// use openpgp::parse::Parse; /// /// let p = Packet::from_bytes( /// "-----BEGIN PGP SIGNATURE----- /// # /// # wsBzBAABCgAdFiEEwD+mQRsDrhJXZGEYciO1ZnjgJSgFAlnclx8ACgkQciO1Znjg /// # JShldAf+NBvUTVPnVPhYM4KihWOUlup8lbD6g1IduSM5rpsGvOVb+uKF6ik+GOBB /// # RlMT4s183r3teFxiTkDx2pRhUz0MnOMPfbXovjF6Y93fKCOxCQWLBa0ukjNmE+ax /// # gu9nZ3XXDGXZW22iGE52uVjPGSfuLfqvdMy5bKHn8xow/kepuGHZwy8yn7uFv7sl /// # LnOBUz1FKA7iRl457XKPUhw5K7BnfRW/I2BRlnrwTDkjfXaJZC+bUTIJvm682Bvt /// # ZNn8zc0JucyEkuL9WXYNuZg0znDE3T7D/6+tzfEdSf706unsXFXWHf83vL2eHCcw /// # qhImm1lmcC+agFtWQ6/qD923LR9xmg== /// # =htNu /// # -----END PGP SIGNATURE-----" /* docstring trickery ahead: /// // ... /// -----END PGP SIGNATURE-----")?; /// # */)?; /// if let Packet::Signature(sig) = p { /// let issuers = sig.get_issuers(); /// assert_eq!(issuers.len(), 2); /// let kh: KeyHandle /// = "C03F A641 1B03 AE12 5764 6118 7223 B566 78E0 2528".parse()?; /// assert!(&issuers[0].aliases(&kh)); /// assert!(&issuers[1].aliases(&kh)); /// } else { /// unreachable!("It's a signature!"); /// } /// # Ok(()) } /// ``` #[derive(Debug, Clone)] pub enum KeyHandle { /// A Fingerprint. Fingerprint(Fingerprint), /// A KeyID. KeyID(KeyID), } assert_send_and_sync!(KeyHandle); impl std::fmt::Display for KeyHandle { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { match self { KeyHandle::Fingerprint(v) => v.fmt(f), KeyHandle::KeyID(v) => v.fmt(f), } } } impl std::fmt::UpperHex for KeyHandle { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { match &self { KeyHandle::Fingerprint(ref fpr) => write!(f, "{:X}", fpr), KeyHandle::KeyID(ref keyid) => write!(f, "{:X}", keyid), } } } impl std::fmt::LowerHex for KeyHandle { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { match &self { KeyHandle::Fingerprint(ref fpr) => write!(f, "{:x}", fpr), KeyHandle::KeyID(ref keyid) => write!(f, "{:x}", keyid), } } } impl From<KeyID> for KeyHandle { fn from(i: KeyID) -> Self { KeyHandle::KeyID(i) } } impl From<&KeyID> for KeyHandle { fn from(i: &KeyID) -> Self { KeyHandle::KeyID(i.clone()) } } impl From<KeyHandle> for KeyID { fn from(i: KeyHandle) -> Self { match i { KeyHandle::Fingerprint(i) => i.into(), KeyHandle::KeyID(i) => i, } } } impl From<Option<KeyHandle>> for KeyID { fn from(i: Option<KeyHandle>) -> Self { match i { Some(KeyHandle::Fingerprint(i)) => i.into(), Some(KeyHandle::KeyID(i)) => i, None => KeyID::wildcard(), } } } impl From<&KeyHandle> for KeyID { fn from(i: &KeyHandle) -> Self { match i { KeyHandle::Fingerprint(i) => i.clone().into(), KeyHandle::KeyID(i) => i.clone(), } } } impl From<Fingerprint> for KeyHandle { fn from(i: Fingerprint) -> Self { KeyHandle::Fingerprint(i) } } impl From<&Fingerprint> for KeyHandle { fn from(i: &Fingerprint) -> Self { KeyHandle::Fingerprint(i.clone()) } } impl TryFrom<KeyHandle> for Fingerprint { type Error = anyhow::Error; fn try_from(i: KeyHandle) -> Result<Self> { match i { KeyHandle::Fingerprint(i) => Ok(i), KeyHandle::KeyID(i) => Err(Error::InvalidOperation( format!("Cannot convert keyid {} to fingerprint", i)).into()), } } } impl TryFrom<&KeyHandle> for Fingerprint { type Error = anyhow::Error; fn try_from(i: &KeyHandle) -> Result<Self> { match i { KeyHandle::Fingerprint(i) => Ok(i.clone()), KeyHandle::KeyID(i) => Err(Error::InvalidOperation( format!("Cannot convert keyid {} to fingerprint", i)).into()), } } } impl PartialOrd for KeyHandle { fn partial_cmp(&self, other: &KeyHandle) -> Option<Ordering> { let a = self.as_bytes(); let b = other.as_bytes(); Some(a.cmp(b)) } } impl PartialEq for KeyHandle { fn eq(&self, other: &Self) -> bool { self.partial_cmp(other) == Some(Ordering::Equal) } } impl std::str::FromStr for KeyHandle { type Err = anyhow::Error; fn from_str(s: &str) -> std::result::Result<Self, Self::Err> { let bytes = &crate::fmt::hex::decode_pretty(s)?[..]; match Fingerprint::from_bytes_intern(None, bytes)? { fpr @ Fingerprint::Unknown { .. } => { match KeyID::from_bytes(bytes) { // If it can't be parsed as either a Fingerprint or a // KeyID, return Fingerprint::Invalid. KeyID::Invalid(_) => Ok(fpr.into()), kid => Ok(kid.into()), } } fpr => Ok(fpr.into()), } } } impl KeyHandle { /// Returns the raw identifier as a byte slice. pub fn as_bytes(&self) -> &[u8] { match self { KeyHandle::Fingerprint(i) => i.as_bytes(), KeyHandle::KeyID(i) => i.as_bytes(), } } /// Returns whether `self` and `other` could be aliases of each /// other. /// /// `KeyHandle`'s `PartialEq` implementation cannot assert that a /// `Fingerprint` and a `KeyID` are equal, because distinct /// fingerprints may have the same `KeyID`, and `PartialEq` must /// be [transitive], i.e., /// /// ```text /// a == b and b == c implies a == c. /// ``` /// /// [transitive]: std::cmp::PartialEq /// /// That is, if `fpr1` and `fpr2` are distinct fingerprints with the /// same key ID then: /// /// ```text /// fpr1 == keyid and fpr2 == keyid, but fpr1 != fpr2. /// ``` /// /// This definition of equality makes searching for a given /// `KeyHandle` using `PartialEq` awkward. This function fills /// that gap. It answers the question: given two `KeyHandles`, /// could they be aliases? That is, it implements the desired, /// non-transitive equality relation: /// /// ``` /// # fn main() -> sequoia_openpgp::Result<()> { /// # use sequoia_openpgp as openpgp; /// # use openpgp::Fingerprint; /// # use openpgp::KeyID; /// # use openpgp::KeyHandle; /// # /// # let fpr1: KeyHandle /// # = "8F17 7771 18A3 3DDA 9BA4 8E62 AACB 3243 6300 52D9" /// # .parse::<Fingerprint>()?.into(); /// # /// # let fpr2: KeyHandle /// # = "0123 4567 8901 2345 6789 0123 AACB 3243 6300 52D9" /// # .parse::<Fingerprint>()?.into(); /// # /// # let keyid: KeyHandle = "AACB 3243 6300 52D9".parse::<KeyID>()? /// # .into(); /// # /// // fpr1 and fpr2 are different fingerprints with the same KeyID. /// assert_ne!(fpr1, fpr2); /// assert_eq!(KeyID::from(&fpr1), KeyID::from(&fpr2)); /// /// # let v6_fpr1 : KeyHandle /// # = "AACB3243630052D9AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" /// # .parse::<Fingerprint>()?.into(); /// # /// # let v6_fpr2 : KeyHandle /// # = "AACB3243630052D9BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB" /// # .parse::<Fingerprint>()?.into(); /// # /// # let keyid : KeyHandle = "AACB 3243 6300 52D9".parse::<KeyID>()? /// # .into(); /// # /// // fpr1 and fpr2 are different v4 fingerprints with the same KeyID. /// assert!(! fpr1.eq(&fpr2)); /// assert!(fpr1.aliases(&keyid)); /// assert!(fpr2.aliases(&keyid)); /// assert!(! fpr1.aliases(&fpr2)); /// /// // v6_fpr1 and v6_fpr2 are different v6 fingerprints with the same KeyID. /// assert!(! v6_fpr1.eq(&v6_fpr2)); /// assert!(v6_fpr1.aliases(&keyid)); /// assert!(v6_fpr2.aliases(&keyid)); /// assert!(! v6_fpr1.aliases(&v6_fpr2)); /// /// // And of course, v4 and v6 don't alias. /// assert!(! fpr1.aliases(&v6_fpr1)); /// # Ok(()) } /// ``` pub fn aliases<H>(&self, other: H) -> bool where H: Borrow<KeyHandle> { match self { KeyHandle::Fingerprint(fpr) => fpr.aliases(other), KeyHandle::KeyID(keyid) => keyid.aliases(other), } } /// Returns whether the KeyHandle is invalid. /// /// A KeyHandle is invalid if the `Fingerprint` or `KeyID` that it /// contains is invalid. /// /// ``` /// use sequoia_openpgp as openpgp; /// use openpgp::Fingerprint; /// use openpgp::KeyID; /// use openpgp::KeyHandle; /// /// # fn main() -> sequoia_openpgp::Result<()> { /// // A perfectly valid fingerprint: /// let kh : KeyHandle = "8F17 7771 18A3 3DDA 9BA4 8E62 AACB 3243 6300 52D9" /// .parse()?; /// assert!(! kh.is_invalid()); /// /// // But, V3 fingerprints are invalid. /// let kh : KeyHandle = "9E 94 45 13 39 83 5F 70 7B E7 D8 ED C4 BE 5A A6" /// .parse()?; /// assert!(kh.is_invalid()); /// /// // A perfectly valid Key ID: /// let kh : KeyHandle = "AACB 3243 6300 52D9" /// .parse()?; /// assert!(! kh.is_invalid()); /// /// // But, short Key IDs are invalid: /// let kh : KeyHandle = "6300 52D9" /// .parse()?; /// assert!(kh.is_invalid()); /// # Ok(()) } /// ``` pub fn is_invalid(&self) -> bool { matches!(self, KeyHandle::Fingerprint(Fingerprint::Unknown { .. }) | KeyHandle::KeyID(KeyID::Invalid(_))) } /// Returns whether the KeyHandle contains a fingerprint. /// /// # Examples /// /// ``` /// # use sequoia_openpgp as openpgp; /// # use openpgp::Fingerprint; /// # use openpgp::KeyID; /// # use openpgp::KeyHandle; /// # /// # fn main() -> sequoia_openpgp::Result<()> { /// let fpr: KeyHandle = "8F17 7771 18A3 3DDA 9BA4 8E62 AACB 3243 6300 52D9" /// .parse()?; /// let keyid: KeyHandle = KeyHandle::from(KeyID::from(&fpr)); /// /// assert!(fpr.is_fingerprint()); /// assert!(! keyid.is_fingerprint()); /// # Ok(()) } /// ``` pub fn is_fingerprint(&self) -> bool { match self { KeyHandle::Fingerprint(_) => true, KeyHandle::KeyID(_) => false, } } /// Returns whether the KeyHandle contains a key ID. /// /// # Examples /// /// ``` /// # use sequoia_openpgp as openpgp; /// # use openpgp::Fingerprint; /// # use openpgp::KeyID; /// # use openpgp::KeyHandle; /// # /// # fn main() -> sequoia_openpgp::Result<()> { /// let fpr: KeyHandle = "8F17 7771 18A3 3DDA 9BA4 8E62 AACB 3243 6300 52D9" /// .parse()?; /// let keyid: KeyHandle = KeyHandle::from(KeyID::from(&fpr)); /// /// assert!(! fpr.is_keyid()); /// assert!(keyid.is_keyid()); /// # Ok(()) } /// ``` pub fn is_keyid(&self) -> bool { match self { KeyHandle::Fingerprint(_) => false, KeyHandle::KeyID(_) => true, } } /// Converts this `KeyHandle` to its canonical hexadecimal /// representation. /// /// This representation is always uppercase and without spaces and /// is suitable for stable key identifiers. /// /// The output of this function is exactly the same as formatting /// this object with the `:X` format specifier. /// /// ```rust /// # fn main() -> sequoia_openpgp::Result<()> { /// # use sequoia_openpgp as openpgp; /// use openpgp::KeyHandle; /// /// let h: KeyHandle = /// "0123 4567 89AB CDEF 0123 4567 89AB CDEF 0123 4567".parse()?; /// /// assert_eq!("0123456789ABCDEF0123456789ABCDEF01234567", h.to_hex()); /// assert_eq!(format!("{:X}", h), h.to_hex()); /// # Ok(()) } /// ``` pub fn to_hex(&self) -> String { format!("{:X}", self) } /// Converts this `KeyHandle` to its hexadecimal representation /// with spaces. /// /// This representation is always uppercase and with spaces /// grouping the hexadecimal digits into groups of four. It is /// only suitable for manual comparison of key handles. /// /// Note: The spaces will hinder other kind of use cases. For /// example, it is harder to select the whole key handle for /// copying, and it has to be quoted when used as a command line /// argument. Only use this form for displaying a key handle with /// the intent of manual comparisons. /// /// ```rust /// # fn main() -> sequoia_openpgp::Result<()> { /// # use sequoia_openpgp as openpgp; /// use openpgp::KeyHandle; /// /// let h: KeyHandle = /// "0123 4567 89AB CDEF 0123 4567 89AB CDEF 0123 4567".parse()?; /// /// assert_eq!("0123 4567 89AB CDEF 0123 4567 89AB CDEF 0123 4567", /// h.to_spaced_hex()); /// # Ok(()) } /// ``` pub fn to_spaced_hex(&self) -> String { match self { KeyHandle::Fingerprint(v) => v.to_spaced_hex(), KeyHandle::KeyID(v) => v.to_spaced_hex(), } } } #[cfg(test)] mod tests { use quickcheck::{Arbitrary, Gen}; use super::*; impl Arbitrary for KeyHandle { fn arbitrary(g: &mut Gen) -> Self { if bool::arbitrary(g) { Fingerprint::arbitrary(g).into() } else { KeyID::arbitrary(g).into() } } } #[test] fn upper_hex_formatting() { let handle = KeyHandle::Fingerprint(Fingerprint::V4([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20])); assert_eq!(format!("{:X}", handle), "0102030405060708090A0B0C0D0E0F1011121314"); let handle = KeyHandle::Fingerprint(Fingerprint::Unknown { version: None, bytes: Box::new([10, 2, 3, 4]), }); assert_eq!(format!("{:X}", handle), "0A020304"); let handle = KeyHandle::KeyID(KeyID::Long([10, 2, 3, 4, 5, 6, 7, 8])); assert_eq!(format!("{:X}", handle), "0A02030405060708"); let handle = KeyHandle::KeyID(KeyID::Invalid(Box::new([10, 2]))); assert_eq!(format!("{:X}", handle), "0A02"); } #[test] fn lower_hex_formatting() { let handle = KeyHandle::Fingerprint(Fingerprint::V4([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20])); assert_eq!(format!("{:x}", handle), "0102030405060708090a0b0c0d0e0f1011121314"); let handle = KeyHandle::Fingerprint(Fingerprint::Unknown { version: None, bytes: Box::new([10, 2, 3, 4]), }); assert_eq!(format!("{:x}", handle), "0a020304"); let handle = KeyHandle::KeyID(KeyID::Long([10, 2, 3, 4, 5, 6, 7, 8])); assert_eq!(format!("{:x}", handle), "0a02030405060708"); let handle = KeyHandle::KeyID(KeyID::Invalid(Box::new([10, 2]))); assert_eq!(format!("{:x}", handle), "0a02"); } #[test] fn parse() -> Result<()> { let handle: KeyHandle = "0123 4567 89AB CDEF 0123 4567 89AB CDEF 0123 4567".parse()?; assert_match!(&KeyHandle::Fingerprint(Fingerprint::V4(_)) = &handle); assert_eq!(handle.as_bytes(), [0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF, 0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF, 0x01, 0x23, 0x45, 0x67]); let handle: KeyHandle = "89AB CDEF 0123 4567".parse()?; assert_match!(&KeyHandle::KeyID(KeyID::Long(_)) = &handle); assert_eq!(handle.as_bytes(), [0x89, 0xAB, 0xCD, 0xEF, 0x01, 0x23, 0x45, 0x67]); // Invalid handles are parsed as invalid Fingerprints, not // invalid KeyIDs. let handle: KeyHandle = "4567 89AB CDEF 0123 4567".parse()?; assert_match!(&KeyHandle::Fingerprint(Fingerprint::Unknown { .. }) = &handle); assert_eq!(handle.as_bytes(), [0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF, 0x01, 0x23, 0x45, 0x67]); let handle: Result<KeyHandle> = "INVALID CHARACTERS".parse(); assert!(handle.is_err()); Ok(()) } quickcheck! { fn partial_cmp_is_asymmetric(a: KeyHandle, b: KeyHandle) -> bool { use Ordering::*; true && (! (a.partial_cmp(&b) == Some(Less)) || ! (a.partial_cmp(&b) == Some(Greater))) && (! (a.partial_cmp(&b) == Some(Greater)) || ! (a.partial_cmp(&b) == Some(Less))) } } quickcheck! { fn partial_cmp_is_transitive(a: KeyHandle, b: KeyHandle, c: KeyHandle) -> bool { use Ordering::*; true && (! (a.partial_cmp(&b) == Some(Less) && b.partial_cmp(&c) == Some(Less)) || a.partial_cmp(&c) == Some(Less)) && (! (a.partial_cmp(&b) == Some(Equal) && b.partial_cmp(&c) == Some(Equal)) || a.partial_cmp(&c) == Some(Equal)) && (! (a.partial_cmp(&b) == Some(Greater) && b.partial_cmp(&c) == Some(Greater)) || a.partial_cmp(&c) == Some(Greater)) } } } ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/src/keyid.rs������������������������������������������������������������������0000644�0000000�0000000�00000045137�10461020230�0015213�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������use std::borrow::Borrow; use std::fmt; #[cfg(test)] use quickcheck::{Arbitrary, Gen}; use crate::Error; use crate::Fingerprint; use crate::KeyHandle; use crate::Result; /// A short identifier for certificates and keys. /// /// A `KeyID` identifies a public key. It was used in [RFC 4880]: for /// example to reference the issuing key of a signature in its /// [`Issuer`] subpacket. You should prefer [`Fingerprint`] over /// [`KeyID`] in data structures, interfaces, and wire formats, unless /// space is of the utmost concern. /// /// Currently, Sequoia supports *version 6* fingerprints and Key IDs, /// and *version 4* fingerprints and Key IDs. *Version 3* /// fingerprints and Key IDs were deprecated by [RFC 4880] in 2007. /// /// *Version 6* and *version 4* [`KeyID`]s are a truncated version of /// the key's fingerprint, which in turn is hash of the public key /// packet. As a general rule of thumb, you should prefer the /// fingerprint as it is possible to create keys with a colliding /// KeyID using a [birthday attack]. /// /// For more details about how a `KeyID` is generated, see [Section /// 5.5.4 of RFC 9580]. /// /// In previous versions of OpenPGP, the Key ID used to be called /// "long Key ID", as there even was a "short Key ID". At only 4 bytes /// length, short Key IDs vulnerable to preimage attacks. That is, an /// attacker can create a key with any given short Key ID in short /// amount of time. /// /// See also [`Fingerprint`] and [`KeyHandle`]. /// /// [RFC 4880]: https://tools.ietf.org/html/rfc4880 /// [Section 5.5.4 of RFC 9580]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.5.4 /// [birthday attack]: https://nullprogram.com/blog/2019/07/22/ /// [`Issuer`]: crate::packet::signature::subpacket::SubpacketValue::Issuer /// [`Fingerprint`]: crate::Fingerprint /// [`KeyHandle`]: crate::KeyHandle /// /// # Examples /// /// ```rust /// # fn main() -> sequoia_openpgp::Result<()> { /// # use sequoia_openpgp as openpgp; /// use openpgp::KeyID; /// /// let id: KeyID = "0123 4567 89AB CDEF".parse()?; /// /// assert_eq!("0123456789ABCDEF", id.to_hex()); /// # Ok(()) } /// ``` #[non_exhaustive] #[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Hash)] pub enum KeyID { /// A long (8 bytes) key ID. /// /// For v4, this is the right-most 8 bytes of the v4 fingerprint. /// For v6, this is the left-most 8 bytes of the v6 fingerprint. Long([u8; 8]), /// Used for holding invalid keyids encountered during parsing /// e.g. wrong number of bytes. Invalid(Box<[u8]>), } assert_send_and_sync!(KeyID); impl fmt::Display for KeyID { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{:X}", self) } } impl fmt::Debug for KeyID { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.debug_tuple("KeyID") .field(&self.to_string()) .finish() } } impl fmt::UpperHex for KeyID { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { self.write_to_fmt(f, true) } } impl fmt::LowerHex for KeyID { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { self.write_to_fmt(f, false) } } impl std::str::FromStr for KeyID { type Err = anyhow::Error; fn from_str(s: &str) -> std::result::Result<Self, Self::Err> { if s.chars().filter(|c| ! c.is_whitespace()).count() % 2 == 1 { return Err(Error::InvalidArgument( "Odd number of nibbles".into()).into()); } let bytes = crate::fmt::hex::decode_pretty(s)?; // A KeyID is exactly 8 bytes long. if bytes.len() == 8 { Ok(KeyID::from_bytes(&bytes[..])) } else if bytes.len() == 4 { Err(Error::ShortKeyID(s.to_string()).into()) } else { // Maybe a fingerprint was given. Try to parse it and // convert it to a KeyID. Ok(s.parse::<Fingerprint>()?.into()) } } } impl From<KeyID> for Vec<u8> { fn from(id: KeyID) -> Self { let mut r = Vec::with_capacity(8); match id { KeyID::Long(ref b) => r.extend_from_slice(b), KeyID::Invalid(ref b) => r.extend_from_slice(b), } r } } impl From<u64> for KeyID { fn from(id: u64) -> Self { Self::new(id) } } impl From<[u8; 8]> for KeyID { fn from(id: [u8; 8]) -> Self { KeyID::from_bytes(&id[..]) } } impl From<&Fingerprint> for KeyID { fn from(fp: &Fingerprint) -> Self { match fp { Fingerprint::V4(fp) => KeyID::from_bytes(&fp[fp.len() - 8..]), Fingerprint::V6(fp) => KeyID::from_bytes(&fp[..8]), Fingerprint::Unknown { bytes, .. } => { KeyID::Invalid(bytes.clone()) } } } } impl From<Fingerprint> for KeyID { fn from(fp: Fingerprint) -> Self { match fp { Fingerprint::V4(fp) => KeyID::from_bytes(&fp[fp.len() - 8..]), Fingerprint::V6(fp) => KeyID::from_bytes(&fp[..8]), Fingerprint::Unknown { bytes, .. } => { KeyID::Invalid(bytes) } } } } impl KeyID { /// Converts a `u64` to a `KeyID`. /// /// # Examples /// /// ```rust /// # extern crate sequoia_openpgp as openpgp; /// use openpgp::KeyID; /// /// let keyid = KeyID::new(0x0123456789ABCDEF); /// ``` pub fn new(data: u64) -> KeyID { let bytes = data.to_be_bytes(); Self::from_bytes(&bytes[..]) } /// Converts the `KeyID` to a `u64` if possible. /// /// # Examples /// /// ```rust /// # fn main() -> sequoia_openpgp::Result<()> { /// # extern crate sequoia_openpgp as openpgp; /// use openpgp::KeyID; /// /// let keyid = KeyID::new(0x0123456789ABCDEF); /// /// assert_eq!(keyid.as_u64()?, 0x0123456789ABCDEF); /// # Ok(()) } /// ``` pub fn as_u64(&self) -> Result<u64> { match &self { KeyID::Long(ref b) => Ok(u64::from_be_bytes(*b)), KeyID::Invalid(_) => Err(Error::InvalidArgument("Invalid KeyID".into()).into()), } } /// Creates a `KeyID` from a big endian byte slice. /// /// # Examples /// /// ```rust /// # fn main() -> sequoia_openpgp::Result<()> { /// # extern crate sequoia_openpgp as openpgp; /// use openpgp::KeyID; /// /// let keyid: KeyID = "0123 4567 89AB CDEF".parse()?; /// /// let bytes = [0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF]; /// assert_eq!(KeyID::from_bytes(&bytes), keyid); /// # Ok(()) } /// ``` pub fn from_bytes(raw: &[u8]) -> KeyID { if raw.len() == 8 { let mut keyid : [u8; 8] = Default::default(); keyid.copy_from_slice(raw); KeyID::Long(keyid) } else { KeyID::Invalid(raw.to_vec().into_boxed_slice()) } } /// Returns a reference to the raw `KeyID` as a byte slice in big /// endian representation. /// /// # Examples /// /// ```rust /// # use sequoia_openpgp as openpgp; /// use openpgp::KeyID; /// /// # fn main() -> sequoia_openpgp::Result<()> { /// let keyid: KeyID = "0123 4567 89AB CDEF".parse()?; /// /// assert_eq!(keyid.as_bytes(), [0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF]); /// # Ok(()) } /// ``` pub fn as_bytes(&self) -> &[u8] { match self { KeyID::Long(ref id) => id, KeyID::Invalid(ref id) => id, } } /// Creates a wildcard `KeyID`. /// /// Refer to [Section 5.1 of RFC 9580] for details. /// /// [Section 5.1 of RFC 9580]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.1 /// /// # Examples /// /// ```rust /// # use sequoia_openpgp as openpgp; /// use openpgp::KeyID; /// /// assert_eq!(KeyID::wildcard(), KeyID::new(0x0000000000000000)); /// ``` pub fn wildcard() -> Self { Self::from_bytes(&[0u8; 8][..]) } /// Returns `true` if this is the wildcard `KeyID`. /// /// Refer to [Section 5.1 of RFC 9580] for details. /// /// [Section 5.1 of RFC 9580]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.1 /// /// # Examples /// /// ```rust /// # use sequoia_openpgp as openpgp; /// use openpgp::KeyID; /// /// assert!(KeyID::new(0x0000000000000000).is_wildcard()); /// ``` pub fn is_wildcard(&self) -> bool { self.as_bytes().iter().all(|b| *b == 0) } /// Converts this `KeyID` to its canonical hexadecimal /// representation. /// /// This representation is always uppercase and without spaces and /// is suitable for stable key identifiers. /// /// The output of this function is exactly the same as formatting /// this object with the `:X` format specifier. /// /// ```rust /// # fn main() -> sequoia_openpgp::Result<()> { /// # use sequoia_openpgp as openpgp; /// use openpgp::KeyID; /// /// let keyid: KeyID = "fb3751f1587daef1".parse()?; /// /// assert_eq!("FB3751F1587DAEF1", keyid.to_hex()); /// assert_eq!(format!("{:X}", keyid), keyid.to_hex()); /// # Ok(()) } /// ``` pub fn to_hex(&self) -> String { use std::fmt::Write; let raw_len = self.as_bytes().len(); let mut output = String::with_capacity( // Each byte results in two hex characters. raw_len * 2); // We write to String that never fails but the Write API // returns Results. write!(output, "{:X}", self).unwrap(); output } /// Converts this `KeyID` to its hexadecimal representation with /// spaces. /// /// This representation is always uppercase and with spaces /// grouping the hexadecimal digits into groups of four. It is /// suitable for manual comparison of Key IDs. /// /// Note: The spaces will hinder other kind of use cases. For /// example, it is harder to select the whole Key ID for copying, /// and it has to be quoted when used as a command line argument. /// Only use this form for displaying a Key ID with the intent of /// manual comparisons. /// /// ```rust /// # fn main() -> sequoia_openpgp::Result<()> { /// # use sequoia_openpgp as openpgp; /// let keyid: openpgp::KeyID = "fb3751f1587daef1".parse()?; /// /// assert_eq!("FB37 51F1 587D AEF1", keyid.to_spaced_hex()); /// # Ok(()) } /// ``` pub fn to_spaced_hex(&self) -> String { use std::fmt::Write; let raw_len = self.as_bytes().len(); let mut output = String::with_capacity( // Each byte results in two hex characters. raw_len * 2 + // Every 2 bytes of output, we insert a space. raw_len / 2); // We write to String that never fails but the Write API // returns Results. write!(output, "{:#X}", self).unwrap(); output } /// Parses the hexadecimal representation of an OpenPGP `KeyID`. /// /// This function is the reverse of `to_hex`. It also accepts /// other variants of the `keyID` notation including lower-case /// letters, spaces and optional leading `0x`. /// /// ```rust /// # fn main() -> sequoia_openpgp::Result<()> { /// # use sequoia_openpgp as openpgp; /// use openpgp::KeyID; /// /// let keyid = KeyID::from_hex("0xfb3751f1587daef1")?; /// /// assert_eq!("FB3751F1587DAEF1", keyid.to_hex()); /// # Ok(()) } /// ``` pub fn from_hex(s: &str) -> std::result::Result<Self, anyhow::Error> { std::str::FromStr::from_str(s) } /// Common code for the above functions. fn write_to_fmt(&self, f: &mut fmt::Formatter, upper_case: bool) -> fmt::Result { use std::fmt::Write; let a_letter = if upper_case { b'A' } else { b'a' }; let pretty = f.alternate(); let raw = match self { KeyID::Long(ref fp) => &fp[..], KeyID::Invalid(ref fp) => &fp[..], }; // We currently only handle long Key IDs, which look like: // // AACB 3243 6300 52D9 // // Since we have no idea how to format an invalid Key ID, just // format it like a V4 fingerprint and hope for the best. for (i, b) in raw.iter().enumerate() { if pretty && i > 0 && i % 2 == 0 { f.write_char(' ')?; } let top = b >> 4; let bottom = b & 0xFu8; if top < 10u8 { f.write_char((b'0' + top) as char)?; } else { f.write_char((a_letter + (top - 10u8)) as char)?; } if bottom < 10u8 { f.write_char((b'0' + bottom) as char)?; } else { f.write_char((a_letter + (bottom - 10u8)) as char)?; } } Ok(()) } /// Returns whether `self` and `other` could be aliases of each /// other. /// /// `KeyHandle`'s `PartialEq` implementation cannot assert that a /// `Fingerprint` and a `KeyID` are equal, because distinct /// fingerprints may have the same `KeyID`, and `PartialEq` must /// be [transitive], i.e., /// /// ```text /// a == b and b == c implies a == c. /// ``` /// /// [transitive]: std::cmp::PartialEq /// /// That is, if `fpr1` and `fpr2` are distinct fingerprints with the /// same key ID then: /// /// ```text /// fpr1 == keyid and fpr2 == keyid, but fpr1 != fpr2. /// ``` /// /// This definition of equality makes searching for a given /// `KeyHandle` using `PartialEq` awkward. This function fills /// that gap. It answers the question: given a `KeyHandle` and a /// `KeyID`, could they be aliases? That is, it implements the /// desired, non-transitive equality relation: /// /// ``` /// # fn main() -> sequoia_openpgp::Result<()> { /// # use sequoia_openpgp as openpgp; /// # use openpgp::Fingerprint; /// # use openpgp::KeyID; /// # use openpgp::KeyHandle; /// # /// # let fpr1: Fingerprint /// # = "8F17 7771 18A3 3DDA 9BA4 8E62 AACB 3243 6300 52D9" /// # .parse::<Fingerprint>()?; /// # /// # let fpr2: Fingerprint /// # = "0123 4567 8901 2345 6789 0123 AACB 3243 6300 52D9" /// # .parse::<Fingerprint>()?; /// # /// # let keyid: KeyID = "AACB 3243 6300 52D9".parse::<KeyID>()?; /// # /// // fpr1 and fpr2 are different fingerprints with the same KeyID. /// assert_ne!(fpr1, fpr2); /// assert_eq!(KeyID::from(&fpr1), KeyID::from(&fpr2)); /// assert!(keyid.aliases(KeyHandle::from(&fpr1))); /// assert!(keyid.aliases(KeyHandle::from(&fpr2))); /// # Ok(()) } /// ``` pub fn aliases<H>(&self, other: H) -> bool where H: Borrow<KeyHandle> { let other = other.borrow(); match (self, other) { (k, KeyHandle::KeyID(o)) => { k == o }, (KeyID::Long(k), KeyHandle::Fingerprint(Fingerprint::V4(o))) => { // Avoid a heap allocation by embedding our // knowledge of how a v4 key ID is derived from a // v4 fingerprint: // // A v4 key ID are the 8 right-most octets of a v4 // fingerprint. &o[12..] == k }, (KeyID::Long(k), KeyHandle::Fingerprint(Fingerprint::V6(f))) => { // A v6 key ID are the 8 left-most octets of a v6 // fingerprint. k == &f[..8] }, (k, o) => { k == &KeyID::from(o) }, } } } #[cfg(test)] impl Arbitrary for KeyID { fn arbitrary(g: &mut Gen) -> Self { KeyID::new(u64::arbitrary(g)) } } #[cfg(test)] mod test { use super::*; quickcheck! { fn u64_roundtrip(id: u64) -> bool { KeyID::new(id).as_u64().unwrap() == id } } #[test] fn from_hex() { "FB3751F1587DAEF1".parse::<KeyID>().unwrap(); "39D100AB67D5BD8C04010205FB3751F1587DAEF1".parse::<KeyID>() .unwrap(); "0xFB3751F1587DAEF1".parse::<KeyID>().unwrap(); "0x39D100AB67D5BD8C04010205FB3751F1587DAEF1".parse::<KeyID>() .unwrap(); "FB37 51F1 587D AEF1".parse::<KeyID>().unwrap(); "39D1 00AB 67D5 BD8C 0401 0205 FB37 51F1 587D AEF1".parse::<KeyID>() .unwrap(); "GB3751F1587DAEF1".parse::<KeyID>().unwrap_err(); "EFB3751F1587DAEF1".parse::<KeyID>().unwrap_err(); "%FB3751F1587DAEF1".parse::<KeyID>().unwrap_err(); } #[test] fn from_hex_short_keyid() { for s in &[ "FB3751F1", "0xFB3751F1", "fb3751f1", "0xfb3751f1" ] { match s.parse::<KeyID>() { Ok(_) => panic!("Failed to reject short Key ID."), Err(err) => { let err = err.downcast_ref::<Error>().unwrap(); assert!(matches!(err, Error::ShortKeyID(_))); } } } } #[test] fn hex_formatting() { let keyid = "FB3751F1587DAEF1".parse::<KeyID>().unwrap(); assert_eq!(format!("{:X}", keyid), "FB3751F1587DAEF1"); assert_eq!(format!("{:x}", keyid), "fb3751f1587daef1"); } #[test] fn aliases() -> crate::Result<()> { // fp1 and fp15 have the same key ID, but are different // fingerprints. let fp1 = "280C0AB0B94D1302CAAEB71DA299CDCD3884EBEA" .parse::<Fingerprint>()?; let fp15 = "1234567890ABCDEF12345678A299CDCD3884EBEA" .parse::<Fingerprint>()?; let fp2 = "F8D921C01EE93B65D4C6FEB7B456A7DB5E4274D0" .parse::<Fingerprint>()?; let keyid1 = KeyID::from(&fp1); let keyid15 = KeyID::from(&fp15); let keyid2 = KeyID::from(&fp2); eprintln!("fp1: {:?}", fp1); eprintln!("keyid1: {:?}", keyid1); eprintln!("fp15: {:?}", fp15); eprintln!("keyid15: {:?}", keyid15); eprintln!("fp2: {:?}", fp2); eprintln!("keyid2: {:?}", keyid2); assert_ne!(fp1, fp15); assert_eq!(keyid1, keyid15); assert!(keyid1.aliases(KeyHandle::from(&fp1))); assert!(keyid1.aliases(KeyHandle::from(&fp15))); assert!(! keyid1.aliases(KeyHandle::from(&fp2))); assert!(keyid15.aliases(KeyHandle::from(&fp1))); assert!(keyid15.aliases(KeyHandle::from(&fp15))); assert!(! keyid15.aliases(KeyHandle::from(&fp2))); assert!(! keyid2.aliases(KeyHandle::from(&fp1))); assert!(! keyid2.aliases(KeyHandle::from(&fp15))); assert!(keyid2.aliases(KeyHandle::from(&fp2))); Ok(()) } } ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/src/lib.rs��������������������������������������������������������������������0000644�0000000�0000000�00000032732�10461020230�0014651�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������//! OpenPGP data types and associated machinery. //! //! This crate aims to provide a complete implementation of OpenPGP as //! defined by [RFC 9580] as well as the deprecated OpenPGP as defined //! by [RFC 4880]. OpenPGP is a standard by the IETF. It was derived //! from the PGP software, which was created by Phil Zimmermann in //! 1991. //! //! This crate also includes support for unbuffered message //! processing. //! //! A few features that the OpenPGP community considers to be //! deprecated (e.g., version 3 compatibility) have been left out. We //! have also updated some OpenPGP defaults to avoid foot guns (e.g., //! we selected modern algorithm defaults). If some functionality is //! missing, please file a bug report. //! //! A non-goal of this crate is support for any sort of high-level, //! bolted-on functionality. For instance, [RFC 9580] does not define //! trust models, such as the web of trust, direct trust, or TOFU. //! Neither does this crate. [RFC 9580] does provide some mechanisms //! for creating trust models (specifically, UserID certifications), //! and this crate does expose those mechanisms. //! //! We also try hard to avoid dictating how OpenPGP should be used. //! This doesn't mean that we don't have opinions about how OpenPGP //! should be used in a number of common scenarios (for instance, //! message validation). But, in this crate, we refrain from //! expressing those opinions; we will expose an opinionated, //! high-level interface in the future. In order to figure out the //! most appropriate high-level interfaces, we look at existing users. //! If you are using Sequoia, please get in contact so that we can //! learn from your use cases, discuss your opinions, and develop a //! high-level interface based on these experiences in the future. //! //! Despite —or maybe because of— its unopinionated nature we found //! it easy to develop opinionated OpenPGP software based on Sequoia. //! //! [RFC 9580]: https://www.rfc-editor.org/rfc/rfc9580.html //! [RFC 4880]: https://tools.ietf.org/html/rfc4880 //! //! # Experimental Features //! //! This crate may implement extensions where the standardization //! effort is still ongoing. These experimental features are marked //! as such in the documentation. We invite you to experiment with //! them, but please do expect the semantics and possibly even the //! wire format to evolve. #![doc(html_favicon_url = "https://docs.sequoia-pgp.org/favicon.png")] #![doc(html_logo_url = "https://docs.sequoia-pgp.org/logo.svg")] #![warn(missing_docs)] // Public re-exports. // // We should provide public re-exports for any crate defining types // that we use in our public API. This allows downstream consumers to // name the types without explicitly depending on the third-party // crates, and provides the correct version of the crates. pub use anyhow; #[cfg(test)] #[macro_use] extern crate quickcheck; #[macro_use] mod macros; // On debug builds, Vec<u8>::truncate is very, very slow. For // instance, running the decrypt_test_stream test takes 51 seconds on // my (Neal's) computer using Vec<u8>::truncate and <0.1 seconds using // `unsafe { v.set_len(len); }`. // // The issue is that the compiler calls drop on every element that is // dropped, even though a u8 doesn't have a drop implementation. The // compiler optimizes this away at high optimization levels, but those // levels make debugging harder. fn vec_truncate(v: &mut Vec<u8>, len: usize) { if cfg!(debug_assertions) { if len < v.len() { unsafe { v.set_len(len); } } } else { v.truncate(len); } } /// Like `Vec<u8>::resize`, but fast in debug builds. fn vec_resize(v: &mut Vec<u8>, new_size: usize) { if v.len() < new_size { v.resize(new_size, 0); } else { vec_truncate(v, new_size); } } /// Like `drop(Vec<u8>::drain(..prefix_len))`, but fast in debug /// builds. fn vec_drain_prefix(v: &mut Vec<u8>, prefix_len: usize) { if cfg!(debug_assertions) { // Panic like v.drain(..prefix_len). assert!(prefix_len <= v.len(), "prefix len {} > vector len {}", prefix_len, v.len()); let new_len = v.len() - prefix_len; unsafe { std::ptr::copy(v[prefix_len..].as_ptr(), v[..].as_mut_ptr(), new_len); } vec_truncate(v, new_len); } else { v.drain(..prefix_len); } } /// Like std::time::SystemTime::now, but works on WASM. fn now() -> std::time::SystemTime { #[cfg(all(target_arch = "wasm32", target_os = "unknown"))] { chrono::Utc::now().into() } #[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))] { std::time::SystemTime::now() } } // Like assert!, but checks a pattern. // // assert_match!(Some(_) = x); // // Note: For modules to see this macro, we need to define it before we // declare the modules. #[allow(unused_macros)] macro_rules! assert_match { ( $error: pat = $expr:expr, $fmt:expr, $($pargs:expr),* ) => {{ let x = $expr; if let $error = x { /* Pass. */ } else { let extra = format!($fmt, $($pargs),*); panic!("Expected {}, got {:?}{}{}", stringify!($error), x, if $fmt.len() > 0 { ": " } else { "." }, extra); } }}; ( $error: pat = $expr: expr, $fmt:expr ) => { assert_match!($error = $expr, $fmt, ) }; ( $error: pat = $expr: expr ) => { assert_match!($error = $expr, "") }; } #[macro_use] pub mod armor; pub mod fmt; pub mod crypto; pub mod packet; #[doc(inline)] pub use packet::Packet; use crate::packet::key; pub mod parse; pub mod cert; #[doc(inline)] pub use cert::Cert; pub mod serialize; mod packet_pile; pub use packet_pile::PacketPile; pub mod message; #[doc(inline)] pub use message::Message; pub mod types; use crate::types::{ PublicKeyAlgorithm, SymmetricAlgorithm, HashAlgorithm, SignatureType, }; mod fingerprint; pub use fingerprint::Fingerprint; mod keyid; pub use keyid::KeyID; mod keyhandle; pub use keyhandle::KeyHandle; pub mod regex; pub mod policy; pub(crate) mod seal; pub(crate) mod utils; #[cfg(test)] mod tests; /// Returns a timestamp for the tests. /// /// The time is chosen to that the subkeys in /// openpgp/tests/data/keys/neal.pgp are not expired. #[cfg(test)] fn frozen_time() -> std::time::SystemTime { crate::types::Timestamp::from(1554542220 - 1).into() } /// The version of this crate. pub const VERSION: &str = env!("CARGO_PKG_VERSION"); /// Profiles select versions of the OpenPGP standard. /// /// While this type implements [`Default`], please consider what a /// good default for your use case is. /// /// If you are doing a greenfield implementation where you know that /// every client understands RFC9580, you can just explicitly pick /// that. /// /// Otherwise, you have to consider the state of the ecosystem your /// client will interact with. Maybe it is better to stick to /// generating RFC4880 certificates for the time being, while rolling /// out RFC9580 support. Consider adding a configuration option or /// command line switch like `--profile`, but pick a sensible default, /// and remember to don't overwhelm your users. /// /// For now, our default is RFC4880. This is a safe default for every /// downstream consumer that has existing legacy deployments /// (including their own previous versions using a legacy version of /// Sequoia). We will update this default once RFC9580 is more widely /// deployed. #[derive(Default, Debug, Clone, Copy, PartialEq, Eq, Hash)] #[non_exhaustive] pub enum Profile { /// RFC9580, published in 2024, defines "v6" OpenPGP. RFC9580, /// RFC4880, published in 2007, defines "v4" OpenPGP. #[default] RFC4880, } /// Crate result specialization. pub type Result<T> = ::std::result::Result<T, anyhow::Error>; /// Errors used in this crate. #[non_exhaustive] #[derive(thiserror::Error, Debug, Clone, PartialEq, Eq)] pub enum Error { /// Invalid argument. #[error("Invalid argument: {0}")] InvalidArgument(String), /// Invalid operation. #[error("Invalid operation: {0}")] InvalidOperation(String), /// A malformed packet. #[error("Malformed packet: {0}")] MalformedPacket(String), /// Packet size exceeds the configured limit. #[error("{} Packet ({} bytes) exceeds limit of {} bytes", _0, _1, _2)] PacketTooLarge(packet::Tag, u32, u32), /// Unsupported packet type. #[error("Unsupported packet type. Tag: {0}")] UnsupportedPacketType(packet::Tag), /// Unsupported hash algorithm identifier. #[error("Unsupported hash algorithm: {0}")] UnsupportedHashAlgorithm(HashAlgorithm), /// Unsupported public key algorithm identifier. #[error("Unsupported public key algorithm: {0}")] UnsupportedPublicKeyAlgorithm(PublicKeyAlgorithm), /// Unsupported elliptic curve ASN.1 OID. #[error("Unsupported elliptic curve: {0}")] UnsupportedEllipticCurve(types::Curve), /// Unsupported symmetric key algorithm. #[error("Unsupported symmetric algorithm: {0}")] UnsupportedSymmetricAlgorithm(SymmetricAlgorithm), /// Unsupported AEAD algorithm. #[error("Unsupported AEAD algorithm: {0}")] UnsupportedAEADAlgorithm(types::AEADAlgorithm), /// Unsupported Compression algorithm. #[error("Unsupported Compression algorithm: {0}")] UnsupportedCompressionAlgorithm(types::CompressionAlgorithm), /// Unsupported signature type. #[error("Unsupported signature type: {0}")] UnsupportedSignatureType(SignatureType), /// Invalid password. #[error("Invalid password")] InvalidPassword, /// Invalid session key. #[error("Invalid session key: {0}")] InvalidSessionKey(String), /// Missing session key. #[error("Missing session key: {0}")] MissingSessionKey(String), /// Malformed MPI. #[error("Malformed MPI: {0}")] MalformedMPI(String), /// Bad signature. #[error("Bad signature: {0}")] BadSignature(String), /// Message has been manipulated. #[error("Message has been manipulated")] ManipulatedMessage, /// Malformed message. #[error("Malformed Message: {0}")] MalformedMessage(String), /// Malformed certificate. #[error("Malformed Cert: {0}")] MalformedCert(String), /// Unsupported Cert. /// /// This usually occurs, because the primary key is in an /// unsupported format. In particular, Sequoia does not support /// version 3 keys. #[error("Unsupported Cert: {0}")] UnsupportedCert(String, Vec<Packet>), /// Index out of range. #[error("Index out of range")] IndexOutOfRange, /// Expired. #[error("Expired on {}", crate::fmt::time(.0))] Expired(std::time::SystemTime), /// Not yet live. #[error("Not live until {}", crate::fmt::time(.0))] NotYetLive(std::time::SystemTime), /// No binding signature. #[error("No binding signature at time {}", crate::fmt::time(.0))] NoBindingSignature(std::time::SystemTime), /// Invalid key. #[error("Invalid key: {0:?}")] InvalidKey(String), /// No hash algorithm found that would be accepted by all signers. #[error("No acceptable hash")] NoAcceptableHash, /// The operation is not allowed, because it violates the policy. /// /// The optional time is the time at which the operation was /// determined to no longer be secure. #[error("{} is not considered secure{}", .0, .1.as_ref().map(|t| { if *t == std::time::UNIX_EPOCH { "".to_string() } else { format!(" since {}", crate::fmt::time(t)) } }) .unwrap_or_else(|| "".into()))] PolicyViolation(String, Option<std::time::SystemTime>), /// Short key IDs are insecure, and not supported. #[error("Short key IDs are insecure, and not supported: {0}")] ShortKeyID(String), } assert_send_and_sync!(Error); /// Provide a helper function that generates an arbitrary value from a given /// range. Quickcheck > 1 does not re-export rand so we need to implement this /// ourselves. #[cfg(test)] mod arbitrary_helper { use quickcheck::{Arbitrary, Gen}; pub(crate) fn gen_arbitrary_from_range<T>( range: std::ops::Range<T>, g: &mut Gen, ) -> T where T: Arbitrary + std::cmp::PartialOrd + std::ops::Sub<Output = T> + std::ops::Rem<Output = T> + std::ops::Add<Output = T> + Copy, { if !range.is_empty() { let m = range.end - range.start; // The % operator calculates the remainder, which is negative for // negative inputs, not the modulus. This actually calculates the // modulus by making sure the result is positive. The primitive // integer types implement .rem_euclid for that, but there is no way // to constrain this function to primitive types. range.start + (T::arbitrary(g) % m + m) % m } else { panic!() } } pub(crate) fn arbitrary_slice<T>(g: &mut Gen, s: &mut [T]) where T: Arbitrary, { s.iter_mut().for_each(|p| *p = Arbitrary::arbitrary(g)); } pub(crate) fn arbitrary_bounded_vec<T>(g: &mut Gen, limit: usize) -> Vec<T> where T: Arbitrary + Default, { let mut v = vec![Default::default(); gen_arbitrary_from_range(0..limit, g)]; arbitrary_slice(g, &mut v[..]); v } } ��������������������������������������sequoia-openpgp-2.0.0/src/macros.rs�����������������������������������������������������������������0000644�0000000�0000000�00000024643�10461020230�0015371�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������use std::cmp; macro_rules! trace { ( $TRACE:expr, $fmt:expr, $($pargs:expr),* ) => { if $TRACE { eprintln!($fmt, $($pargs),*); } }; ( $TRACE:expr, $fmt:expr ) => { trace!($TRACE, $fmt, ); }; } // Converts an indentation level to whitespace. pub(crate) fn indent(i: isize) -> &'static str { let s = " "; &s[0..cmp::min(usize::try_from(i).unwrap_or(0), s.len())] } macro_rules! tracer { ( $TRACE:expr, $func:expr ) => { tracer!($TRACE, $func, 0) }; ( $TRACE:expr, $func:expr, $indent:expr ) => { // Currently, Rust doesn't support $( ... ) in a nested // macro's definition. See: // https://users.rust-lang.org/t/nested-macros-issue/8348/2 #[allow(unused_macros)] macro_rules! t { ( $fmt:expr ) => { trace!($TRACE, "{}{}: {}", crate::macros::indent($indent), $func, $fmt) }; ( $fmt:expr, $a:expr ) => { trace!($TRACE, "{}{}: {}", crate::macros::indent($indent), $func, format!($fmt, $a)) }; ( $fmt:expr, $a:expr, $b:expr ) => { trace!($TRACE, "{}{}: {}", crate::macros::indent($indent), $func, format!($fmt, $a, $b)) }; ( $fmt:expr, $a:expr, $b:expr, $c:expr ) => { trace!($TRACE, "{}{}: {}", crate::macros::indent($indent), $func, format!($fmt, $a, $b, $c)) }; ( $fmt:expr, $a:expr, $b:expr, $c:expr, $d:expr ) => { trace!($TRACE, "{}{}: {}", crate::macros::indent($indent), $func, format!($fmt, $a, $b, $c, $d)) }; ( $fmt:expr, $a:expr, $b:expr, $c:expr, $d:expr, $e:expr ) => { trace!($TRACE, "{}{}: {}", crate::macros::indent($indent), $func, format!($fmt, $a, $b, $c, $d, $e)) }; ( $fmt:expr, $a:expr, $b:expr, $c:expr, $d:expr, $e:expr, $f:expr ) => { trace!($TRACE, "{}{}: {}", crate::macros::indent($indent), $func, format!($fmt, $a, $b, $c, $d, $e, $f)) }; ( $fmt:expr, $a:expr, $b:expr, $c:expr, $d:expr, $e:expr, $f:expr, $g:expr ) => { trace!($TRACE, "{}{}: {}", crate::macros::indent($indent), $func, format!($fmt, $a, $b, $c, $d, $e, $f, $g)) }; ( $fmt:expr, $a:expr, $b:expr, $c:expr, $d:expr, $e:expr, $f:expr, $g:expr, $h:expr ) => { trace!($TRACE, "{}{}: {}", crate::macros::indent($indent), $func, format!($fmt, $a, $b, $c, $d, $e, $f, $g, $h)) }; ( $fmt:expr, $a:expr, $b:expr, $c:expr, $d:expr, $e:expr, $f:expr, $g:expr, $h:expr, $i:expr ) => { trace!($TRACE, "{}{}: {}", crate::macros::indent($indent), $func, format!($fmt, $a, $b, $c, $d, $e, $f, $g, $h, $i)) }; ( $fmt:expr, $a:expr, $b:expr, $c:expr, $d:expr, $e:expr, $f:expr, $g:expr, $h:expr, $i:expr, $j:expr ) => { trace!($TRACE, "{}{}: {}", crate::macros::indent($indent), $func, format!($fmt, $a, $b, $c, $d, $e, $f, $g, $h, $i, $j)) }; ( $fmt:expr, $a:expr, $b:expr, $c:expr, $d:expr, $e:expr, $f:expr, $g:expr, $h:expr, $i:expr, $j:expr, $k:expr ) => { trace!($TRACE, "{}{}: {}", crate::macros::indent($indent), $func, format!($fmt, $a, $b, $c, $d, $e, $f, $g, $h, $i, $j, $k)) }; } } } /// A very simple profiling tool. /// /// Note: don't ever profile code that has not been compiled in /// release mode. There can be orders of magnitude difference in /// execution time between it and debug mode! /// /// This macro measures the wall time it takes to execute the block. /// If the time is at least $ms_threshold (in milliseconds), then it /// displays the output on stderr. The output is prefixed with label, /// if it is provided. /// /// ```ignore /// let result = time_it!("Some code", 10, { /// // Some code. /// 5 /// }); /// assert_eq!(result, 5); /// ``` // Note: We cannot test the macro in doctests, because the macro is // not public. We test the cases in the test module below, instead. // If you change the examples here, propagate the changes to the // module below. #[allow(unused_macros)] macro_rules! time_it { ( $label:expr, $ms_threshold:expr, $body:expr ) => {{ use std::time::{SystemTime, Duration}; // We use drop so that code that uses non-local exits (e.g., // using break 'label) still works. struct Timer { start: SystemTime, } impl Drop for Timer { fn drop(&mut self) { let elapsed = self.start.elapsed(); if elapsed.clone().unwrap_or(Duration::from_millis($ms_threshold)) >= Duration::from_millis($ms_threshold) { if $label.len() > 0 { eprint!("{}:", $label); } eprintln!("{}:{}: {:?}", file!(), line!(), elapsed); } } } let _start = Timer { start: SystemTime::now() }; $body }}; ( $label:expr, $body:expr ) => { time_it!($label, 0, $body) }; ( $body:expr ) => { time_it!("", $body) }; } /// We cannot test the macro in doctests, because the macro is not /// public. We test the cases here, instead. If you change the /// examples here, propagate the changes to the docstring above. #[cfg(test)] mod test_time_it { /// This macro measures the wall time it takes to execute the /// block. If the time is at least $ms_threshold (in /// milliseconds), then it displays the output on stderr. The /// output is prefixed with label, if it is provided. #[test] fn time_it() { let result = time_it!("Some code", 10, { // Some code. 5 }); assert_eq!(result, 5); } } /// A simple shortcut for ensuring a type is send and sync. /// /// For most types just call it after defining the type: /// /// ```ignore /// pub struct MyStruct {} /// assert_send_and_sync!(MyStruct); /// ``` /// /// For types with lifetimes, use the anonymous lifetime: /// /// ```ignore /// pub struct WithLifetime<'a> { _p: std::marker::PhantomData<&'a ()> } /// assert_send_and_sync!(WithLifetime<'_>); /// ``` /// /// For a type generic over another type `W`, /// pass the type `W` as a where clause /// including a trait bound when needed: /// /// ```ignore /// pub struct MyWriter<W: std::io::Write> { _p: std::marker::PhantomData<W> } /// assert_send_and_sync!(MyWriter<W> where W: std::io::Write); /// ``` /// /// This will assert that `MyWriterStruct<W>` is `Send` and `Sync` /// if `W` is `Send` and `Sync`. /// /// You can also combine the two and be generic over multiple types. /// Just make sure to list all the types - even those without additional /// trait bounds: /// /// ```ignore /// pub struct MyWriterWithLifetime<'a, C, W: std::io::Write> { /// _p: std::marker::PhantomData<&'a (C, W)>, /// } /// assert_send_and_sync!(MyWriterWithLifetime<'_, C, W> where C, W: std::io::Write); /// ``` /// /// If you need multiple additional trait bounds on a single type /// you can add them separated by `+` like in normal where clauses. /// However you have to make sure they are `Identifiers` like `Write`. /// In macro patterns `Paths` (like `std::io::Write`) may not be followed /// by `+` characters. // Note: We cannot test the macro in doctests, because the macro is // not public. We test the cases in the test module below, instead. // If you change the examples here, propagate the changes to the // module below. macro_rules! assert_send_and_sync { ( $x:ty where $( $g:ident$( : $a:path )? $(,)?)*) => { impl<$( $g ),*> crate::types::Sendable for $x where $( $g: Send + Sync $( + $a )? ),* {} impl<$( $g ),*> crate::types::Syncable for $x where $( $g: Send + Sync $( + $a )? ),* {} }; ( $x:ty where $( $g:ident$( : $a:ident $( + $b:ident )* )? $(,)?)*) => { impl<$( $g ),*> crate::types::Sendable for $x where $( $g: Send + Sync $( + $a $( + $b )* )? ),* {} impl<$( $g ),*> crate::types::Syncable for $x where $( $g: Send + Sync $( + $a $( + $b )* )? ),* {} }; ( $x:ty ) => { impl crate::types::Sendable for $x {} impl crate::types::Syncable for $x {} }; } /// We cannot test the macro in doctests, because the macro is not /// public. We test the cases here, instead. If you change the /// examples here, propagate the changes to the docstring above. #[cfg(test)] #[allow(dead_code)] mod test_assert_send_and_sync { /// For most types just call it after defining the type: pub struct MyStruct {} assert_send_and_sync!(MyStruct); /// For types with lifetimes, use the anonymous lifetime: pub struct WithLifetime<'a> { _p: std::marker::PhantomData<&'a ()> } assert_send_and_sync!(WithLifetime<'_>); /// For a type generic over another type `W`, pass the type `W` as /// a where clause including a trait bound when needed: pub struct MyWriter<W: std::io::Write> { _p: std::marker::PhantomData<W> } assert_send_and_sync!(MyWriter<W> where W: std::io::Write); /// This will assert that `MyWriterStruct<W>` is `Send` and `Sync` /// if `W` is `Send` and `Sync`. /// /// You can also combine the two and be generic over multiple /// types. Just make sure to list all the types - even those /// without additional trait bounds: pub struct MyWriterWithLifetime<'a, C, W: std::io::Write> { _p: std::marker::PhantomData<&'a (C, W)>, } assert_send_and_sync!(MyWriterWithLifetime<'_, C, W> where C, W: std::io::Write); } /// Zeros the stack after executing a block of code. /// /// These are more convenient and more robust ways of using /// crypto::mem::zero_stack and crypto::mem::zero_stack_after. You /// should prefer this macro over using the functions directly. /// /// # Examples /// /// ```ignore /// zero_stack!(128 bytes after running { /// let mut a = [0; 6]; /// a.copy_from_slice(b"secret"); /// }) /// ``` /// /// Or, if you need to specify the type of the expression: /// /// ```ignore /// zero_stack!(128 bytes after running || -> () { /// let mut a = [0; 6]; /// a.copy_from_slice(b"secret"); /// }) /// ``` #[allow(unused_macros)] macro_rules! zero_stack { ($n:literal bytes after running || -> $t:ty $code:block) => { crate::crypto::mem::zero_stack_after::<$n, _>( #[inline(never)] || -> $t { $code }) }; ($n:literal bytes after running $code:block) => { crate::crypto::mem::zero_stack_after::<$n, _>( #[inline(never)] || $code) }; } ���������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/src/message/grammar.lalrpop���������������������������������������������������0000644�0000000�0000000�00000003033�10461020230�0020172�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������// -*- mode: Rust; -*- use crate::message::lexer; grammar; pub Message: () = { LITERAL, CompressedData, EncryptedPart, SignedPart, OPAQUE_CONTENT, }; CompressedData: () = { COMPRESSED_DATA Message POP }; Seipv1Part: () = { SEIPv1 Message MDC POP, SEIPv1 OPAQUE_CONTENT POP, } Seipv2Part: () = { SEIPv2 Message POP, } AedPart: () = { AED Message POP, } // An encrypted part is 0 or more ESKs followed by an encryption container. EncryptedPart: () = { EncryptionContainer, ESKS EncryptionContainer, }; EncryptionContainer: () = { Seipv1Part, Seipv2Part, AedPart, }; ESKS: () = { ESK, ESKS ESK, }; ESK: () = { PKESK, SKESK, }; // Signatures bracket a message like so: // // OPS OPS Message SIG SIG // // or, there are 1 or more signatures preceding a Message (this is an // artifact of old PGP versions): // // SIG SIG Message SignedPart: () = { SIG Message, OPS Message SIG, } extern { type Location = usize; type Error = lexer::LexicalError; enum lexer::Token { LITERAL => lexer::Token::Literal, COMPRESSED_DATA => lexer::Token::CompressedData, SKESK => lexer::Token::SKESK, PKESK => lexer::Token::PKESK, SEIPv1 => lexer::Token::SEIPv1, SEIPv2 => lexer::Token::SEIPv2, MDC => lexer::Token::MDC, AED => lexer::Token::AED, OPS => lexer::Token::OPS, SIG => lexer::Token::SIG, POP => lexer::Token::Pop, OPAQUE_CONTENT => lexer::Token::OpaqueContent, } } �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/src/message/lexer.rs����������������������������������������������������������0000644�0000000�0000000�00000004026�10461020230�0016641�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������use std::fmt; // The type of the parser's input. // // The parser iterators over tuples consisting of the token's starting // position, the token itself, and the token's ending position. pub(crate) type LexerItem<Tok, Loc, Error> = ::std::result::Result<(Loc, Tok, Loc), Error>; /// The components of an OpenPGP Message. #[non_exhaustive] #[derive(Debug, Clone, Copy, PartialEq)] pub enum Token { /// A Literal data packet. Literal, /// A Compressed Data packet. CompressedData, /// An SK-ESK packet. SKESK, /// An PK-ESK packet. PKESK, /// A version 1 SEIP packet. SEIPv1, /// A version 2 SEIP packet. SEIPv2, /// An MDC packet. MDC, /// An AED packet. AED, /// A OnePassSig packet. OPS, /// A Signature packet. SIG, /// The end of a container (either a Compressed Data packet or a /// SEIP packet). Pop, /// A container's unparsed content. OpaqueContent, } assert_send_and_sync!(Token); impl fmt::Display for Token { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{:?}", self) } } #[derive(Debug, Clone)] pub enum LexicalError { // There are no lexing errors. } assert_send_and_sync!(LexicalError); impl fmt::Display for LexicalError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{:?}", self) } } pub(crate) struct Lexer<'input> { iter: Box<dyn Iterator<Item=(usize, &'input Token)> + 'input>, } impl<'input> Iterator for Lexer<'input> { type Item = LexerItem<Token, usize, LexicalError>; fn next(&mut self) -> Option<Self::Item> { let n = self.iter.next().map(|(pos, tok)| (pos, *tok)); if let Some((pos, tok)) = n { Some(Ok((pos, tok, pos))) } else { None } } } impl<'input> Lexer<'input> { /// Uses a raw sequence of tokens as input to the parser. pub(crate) fn from_tokens(raw: &'input [Token]) -> Self { Lexer { iter: Box::new(raw.iter().enumerate()) } } } ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/src/message/mod.rs������������������������������������������������������������0000644�0000000�0000000�00000116603�10461020230�0016306�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������//! Message support. //! //! An OpenPGP message is a sequence of OpenPGP packets that //! corresponds to an optionally signed, optionally encrypted, //! optionally compressed literal data packet. The exact format of an //! OpenPGP message is described in [Section 10.3 of RFC 9580]. //! //! This module provides support for validating and working with //! OpenPGP messages. //! //! Example of ASCII-armored OpenPGP message: //! //! ```txt //! -----BEGIN PGP MESSAGE----- //! //! yDgBO22WxBHv7O8X7O/jygAEzol56iUKiXmV+XmpCtmpqQUKiQrFqclFqUDBovzS //! vBSFjNSiVHsuAA== //! =njUN //! -----END PGP MESSAGE----- //! ``` //! //! [Section 10.3 of RFC 9580]: https://www.rfc-editor.org/rfc/rfc9580.html#section-10.3 use std::convert::TryFrom; use std::fmt; use buffered_reader::BufferedReader; use crate::Result; use crate::Error; use crate::Packet; use crate::PacketPile; use crate::packet::Literal; use crate::packet::Tag; use crate::parse::{Cookie, Parse}; mod lexer; lalrpop_util::lalrpop_mod!(#[allow(clippy::all)] grammar, "/message/grammar.rs"); use self::lexer::{Lexer, LexicalError}; pub(crate) use self::lexer::Token; use lalrpop_util::ParseError; use self::grammar::MessageParser; /// Errors that MessageValidator::check may return. #[non_exhaustive] #[derive(Debug, Clone)] pub (crate) enum MessageParserError { /// A parser error. Parser(ParseError<usize, Token, LexicalError>), /// An OpenPGP error. OpenPGP(Error), } assert_send_and_sync!(MessageParserError); impl From<MessageParserError> for anyhow::Error { fn from(err: MessageParserError) -> Self { match err { MessageParserError::Parser(p) => p.into(), MessageParserError::OpenPGP(p) => p.into(), } } } /// Represents the status of a parsed message. #[derive(Debug)] pub(crate) enum MessageValidity { /// The packet sequence is a valid OpenPGP message. Message, /// The packet sequence appears to be a valid OpenPGP message that /// has been truncated, i.e., the packet sequence is a valid /// prefix of an OpenPGP message. MessagePrefix, /// The message is definitely not valid. Error(anyhow::Error), } #[allow(unused)] impl MessageValidity { /// Returns whether the packet sequence is a valid message. /// /// Note: a `MessageValidator` will only return this after /// `MessageValidator::finish` has been called. pub fn is_message(&self) -> bool { matches!(self, MessageValidity::Message) } /// Returns whether the packet sequence forms a valid message /// prefix. /// /// Note: a `MessageValidator` will only return this before /// `MessageValidator::finish` has been called. pub fn is_message_prefix(&self) -> bool { matches!(self, MessageValidity::MessagePrefix) } /// Returns whether the packet sequence is definitely not a valid /// OpenPGP Message. pub fn is_err(&self) -> bool { matches!(self, MessageValidity::Error(_)) } } /// Used to help validate a packet sequence is a valid OpenPGP message. #[derive(Debug)] pub(crate) struct MessageValidator { tokens: Vec<Token>, finished: bool, // Once a raw token is pushed, this is set to None and pushing // packet Tags is no longer supported. depth: Option<isize>, // If we know that the packet sequence is invalid. error: Option<MessageParserError>, } impl Default for MessageValidator { fn default() -> Self { MessageValidator::new() } } impl MessageValidator { /// Instantiates a new `MessageValidator`. pub fn new() -> Self { MessageValidator { tokens: vec![], finished: false, depth: Some(0), error: None, } } /// Adds a token to the token stream. #[cfg(test)] pub(crate) fn push_raw(&mut self, token: Token) { assert!(!self.finished); if self.error.is_some() { return; } self.depth = None; self.tokens.push(token); } /// Add the token `token` at position `path` to the token stream. /// /// Note: top-level packets are at `[n]`, their immediate /// children are at `[n, m]`, etc. /// /// This function pushes any required `Token::Pop` tokens based on /// changes in the `path`. /// /// Note: the token *must* correspond to a packet; this function /// will panic if `token` is `Token::Pop`. pub fn push_token(&mut self, token: Token, path: &[usize]) { assert!(!self.finished); assert!(self.depth.is_some()); assert!(token != Token::Pop); assert!(!path.is_empty()); if self.error.is_some() { return; } // We popped one or more containers. let depth = path.len() as isize - 1; if self.depth.unwrap() > depth { for _ in 1..self.depth.unwrap() - depth + 1 { self.tokens.push(Token::Pop); } } self.depth = Some(depth); self.tokens.push(token); } /// Add a packet of type `tag` at position `path` to the token /// stream. /// /// Note: top-level packets are at `[n]`, their immediate /// children are at `[n, m]`, etc. /// /// Unlike `push_token`, this function does not automatically /// account for changes in the depth. If you use this function /// directly, you must push any required `Token::Pop` tokens. pub fn push(&mut self, tag: Tag, version: Option<u8>, path: &[usize]) { if self.error.is_some() { return; } let token = match tag { Tag::Literal => Token::Literal, Tag::CompressedData => Token::CompressedData, Tag::SKESK => Token::SKESK, Tag::PKESK => Token::PKESK, Tag::SEIP if version == Some(1) => Token::SEIPv1, Tag::SEIP if version == Some(2) => Token::SEIPv2, Tag::MDC => Token::MDC, Tag::AED => Token::AED, Tag::OnePassSig => Token::OPS, Tag::Signature => Token::SIG, Tag::Marker => { // "[Marker packets] MUST be ignored when received.", // section 5.8 of RFC4880. return; }, Tag::Padding => { // "[Padding packets] MUST be ignored when received.", // section 5.14 of RFC 9580. return; }, t if ! t.is_critical() => { // "Unknown, non-critical packets MUST be ignored when // received.", section 4.3 of RFC 9580. return; }, _ => { // Unknown token. self.error = Some(MessageParserError::OpenPGP( Error::MalformedMessage( format!("Invalid OpenPGP message: \ {:?} packet (at {:?}) not expected", tag, path)))); self.tokens.clear(); return; } }; self.push_token(token, path) } /// Note that the entire message has been seen. pub fn finish(&mut self) { assert!(!self.finished); if let Some(depth) = self.depth { // Pop any containers. for _ in 0..depth { self.tokens.push(Token::Pop); } } self.finished = true; } /// Returns whether the token stream corresponds to a valid /// OpenPGP message. /// /// This returns a tri-state: if the message is valid, it returns /// MessageValidity::Message, if the message is invalid, then it /// returns MessageValidity::Error. If the message could be /// valid, then it returns MessageValidity::MessagePrefix. /// /// Note: if MessageValidator::finish() *hasn't* been called, then /// this function will only ever return either /// MessageValidity::MessagePrefix or MessageValidity::Error. Once /// MessageValidity::finish() has been called, then only /// MessageValidity::Message or MessageValidity::Error will be returned. pub fn check(&self) -> MessageValidity { if let Some(ref err) = self.error { return MessageValidity::Error((*err).clone().into()); } let r = MessageParser::new().parse( Lexer::from_tokens(&self.tokens[..])); if self.finished { match r { Ok(_) => MessageValidity::Message, Err(ref err) => MessageValidity::Error( MessageParserError::Parser((*err).clone()).into()), } } else { match r { Ok(_) => MessageValidity::MessagePrefix, Err(ParseError::UnrecognizedEof { .. }) => MessageValidity::MessagePrefix, Err(ref err) => MessageValidity::Error( MessageParserError::Parser((*err).clone()).into()), } } } } /// A message. /// /// An OpenPGP message is a structured sequence of OpenPGP packets. /// Basically, it's an optionally encrypted, optionally signed literal /// data packet. The exact structure is defined in [Section 10.3 of RFC /// 9580]. /// /// [Section 10.3 of RFC 9580]: https://www.rfc-editor.org/rfc/rfc9580.html#section-10.3 /// /// [ASCII Armored Messages] are wrapped in `-----BEGIN PGP MESSAGE-----` header /// and `-----END PGP MESSAGE-----` tail lines: /// /// ```txt /// -----BEGIN PGP MESSAGE----- /// /// xA0DAAoW5saJekzviSQByxBiAAAAAADYtdiv2KfZgtipwnUEABYKACcFglwJHYoW /// IQRnpIdTo4Cms7fffcXmxol6TO+JJAkQ5saJekzviSQAAIJ6APwK6FxtHXn8txDl /// tBFsIXlOSLOs4BvArlZzZSMomIyFLAEAwCLJUChMICDxWXRlHxORqU5x6hlO3DdW /// sl/1DAbnRgI= /// =AqoO /// -----END PGP MESSAGE----- /// ``` /// /// [ASCII Armored Messages]: https://www.rfc-editor.org/rfc/rfc9580.html#section-! /// /// # Examples /// /// Creating a Message encrypted with a password. /// /// ``` /// # use sequoia_openpgp as openpgp; /// # fn main() -> sequoia_openpgp::Result<()> { /// use std::io::Write; /// use openpgp::serialize::stream::{Message, Encryptor, LiteralWriter}; /// /// let mut sink = vec![]; /// let message = Encryptor::with_passwords( /// Message::new(&mut sink), Some("ściśle tajne")).build()?; /// let mut w = LiteralWriter::new(message).build()?; /// w.write_all(b"Hello world.")?; /// w.finalize()?; /// # Ok(()) } /// ``` #[derive(PartialEq)] pub struct Message { /// A message is just a validated packet pile. pile: PacketPile, } assert_send_and_sync!(Message); impl fmt::Debug for Message { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.debug_struct("Message") .field("pile", &self.pile) .finish() } } impl<'a> Parse<'a, Message> for Message { /// Reads a `Message` from the specified reader. /// /// See [`Message::try_from`] for more details. /// /// [`Message::try_from`]: Message::try_from() fn from_buffered_reader<R>(reader: R) -> Result<Message> where R: BufferedReader<Cookie> + 'a, { Self::try_from(PacketPile::from_buffered_reader(reader.into_boxed())?) } } impl std::str::FromStr for Message { type Err = anyhow::Error; fn from_str(s: &str) -> std::result::Result<Self, Self::Err> { Self::from_bytes(s.as_bytes()) } } impl Message { /// Returns the body of the message. /// /// Returns `None` if no literal data packet is found. This /// happens if a SEIP container has not been decrypted. /// /// # Examples /// /// ``` /// # use sequoia_openpgp as openpgp; /// # fn main() -> openpgp::Result<()> { /// use std::io; /// use std::io::Read; /// use openpgp::Message; /// use openpgp::armor::{Reader, ReaderMode}; /// use openpgp::parse::Parse; /// /// let data = "yxJiAAAAAABIZWxsbyB3b3JsZCE="; // base64 over literal data packet /// /// let mut cursor = io::Cursor::new(&data); /// let mut reader = Reader::from_reader(&mut cursor, ReaderMode::VeryTolerant); /// /// let mut buf = Vec::new(); /// reader.read_to_end(&mut buf)?; /// /// let message = Message::from_bytes(&buf)?; /// assert_eq!(message.body().unwrap().body(), b"Hello world!"); /// # Ok(()) /// # } /// ``` pub fn body(&self) -> Option<&Literal> { for packet in self.pile.descendants() { if let Packet::Literal(ref l) = packet { return Some(l); } } // No literal data packet found. None } /// Returns a reference to the message's packets. pub fn packets(&self) -> &PacketPile { &self.pile } } impl TryFrom<PacketPile> for Message { type Error = anyhow::Error; /// Converts the `PacketPile` to a `Message`. /// /// Converting a `PacketPile` to a `Message` doesn't change the /// packets; it asserts that the packet sequence is an optionally /// encrypted, optionally signed, optionally compressed literal /// data packet. The exact grammar is defined in [Section 10.3 of /// RFC 9580]. /// /// Caveats: this function assumes that any still encrypted parts /// or still compressed parts are valid messages. /// /// [Section 10.3 of RFC 9580]: https://www.rfc-editor.org/rfc/rfc9580.html#section-10.3 fn try_from(pile: PacketPile) -> Result<Self> { let mut v = MessageValidator::new(); for (mut path, packet) in pile.descendants().paths() { match packet { Packet::Unknown(ref u) => return Err(MessageParserError::OpenPGP( Error::MalformedMessage( format!("Invalid OpenPGP message: \ {:?} packet (at {:?}) not expected: {}", u.tag(), path, u.error()))) .into()), _ => v.push(packet.tag(), packet.version(), &path), } match packet { Packet::CompressedData(_) | Packet::SEIP(_) => { // If a container's content is not unpacked, then // we treat the content as an opaque message. path.push(0); if packet.children().is_none() { v.push_token(Token::OpaqueContent, &path); } } _ => {} } } v.finish(); match v.check() { MessageValidity::Message => Ok(Message { pile }), MessageValidity::MessagePrefix => unreachable!(), // We really want to squash the lexer's error: it is an // internal detail that may change, and meaningless even // to an immediate user of this crate. MessageValidity::Error(e) => Err(e), } } } impl TryFrom<Vec<Packet>> for Message { type Error = anyhow::Error; /// Converts the vector of `Packets` to a `Message`. /// /// See [`Message::try_from`] for more details. /// /// [`Message::try_from`]: Message::try_from() fn try_from(packets: Vec<Packet>) -> Result<Self> { Self::try_from(PacketPile::from(packets)) } } impl From<Message> for PacketPile { fn from(m: Message) -> Self { m.pile } } #[cfg(test)] mod tests { use super::*; use crate::types::DataFormat::Unicode; use crate::HashAlgorithm; use crate::types::CompressionAlgorithm; use crate::SymmetricAlgorithm; use crate::PublicKeyAlgorithm; use crate::SignatureType; use crate::crypto::S2K; use crate::crypto::mpi::{Ciphertext, MPI}; use crate::packet::prelude::*; #[test] fn tokens() { use self::lexer::{Token, Lexer}; use self::lexer::Token::*; use self::grammar::MessageParser; struct TestVector<'a> { s: &'a [Token], result: bool, } let test_vectors = [ TestVector { s: &[Literal][..], result: true, }, TestVector { s: &[CompressedData, Literal, Pop], result: true, }, TestVector { s: &[CompressedData, CompressedData, Literal, Pop, Pop], result: true, }, TestVector { s: &[SEIPv1, Literal, MDC, Pop], result: true, }, TestVector { s: &[CompressedData, SEIPv1, Literal, MDC, Pop, Pop], result: true, }, TestVector { s: &[CompressedData, SEIPv1, CompressedData, Literal, Pop, MDC, Pop, Pop], result: true, }, TestVector { s: &[SEIPv1, MDC, Pop], result: false, }, TestVector { s: &[SKESK, SEIPv1, Literal, MDC, Pop], result: true, }, TestVector { s: &[PKESK, SEIPv1, Literal, MDC, Pop], result: true, }, TestVector { s: &[SKESK, SKESK, SEIPv1, Literal, MDC, Pop], result: true, }, TestVector { s: &[SEIPv2, Literal, Pop], result: true, }, TestVector { s: &[CompressedData, SEIPv2, Literal, Pop, Pop], result: true, }, TestVector { s: &[CompressedData, SEIPv2, CompressedData, Literal, Pop, Pop, Pop], result: true, }, TestVector { s: &[SEIPv2, Pop], result: false, }, TestVector { s: &[SKESK, SEIPv2, Literal, Pop], result: true, }, TestVector { s: &[PKESK, SEIPv2, Literal, Pop], result: true, }, TestVector { s: &[SKESK, SKESK, SEIPv2, Literal, Pop], result: true, }, TestVector { s: &[OPS, Literal, SIG], result: true, }, TestVector { s: &[OPS, OPS, Literal, SIG, SIG], result: true, }, TestVector { s: &[OPS, OPS, Literal, SIG], result: false, }, TestVector { s: &[OPS, OPS, SEIPv1, OPS, SEIPv1, Literal, MDC, Pop, SIG, MDC, Pop, SIG, SIG], result: true, }, TestVector { s: &[CompressedData, OpaqueContent], result: false, }, TestVector { s: &[CompressedData, OpaqueContent, Pop], result: true, }, TestVector { s: &[CompressedData, CompressedData, OpaqueContent, Pop, Pop], result: true, }, TestVector { s: &[SEIPv1, CompressedData, OpaqueContent, Pop, MDC, Pop], result: true, }, TestVector { s: &[SEIPv1, OpaqueContent, Pop], result: true, }, ]; for v in &test_vectors { if v.result { let mut l = MessageValidator::new(); for token in v.s.iter() { l.push_raw(*token); assert_match!(MessageValidity::MessagePrefix = l.check()); } l.finish(); assert_match!(MessageValidity::Message = l.check()); } match MessageParser::new().parse(Lexer::from_tokens(v.s)) { Ok(r) => assert!(v.result, "Parsing: {:?} => {:?}", v.s, r), Err(e) => assert!(! v.result, "Parsing: {:?} => {:?}", v.s, e), } } } #[test] fn tags() { use crate::packet::Tag::*; struct TestVector<'a> { s: &'a [(Tag, Option<u8>, isize)], result: bool, } let test_vectors = [ TestVector { s: &[(Literal, None, 0)][..], result: true, }, TestVector { s: &[(CompressedData, None, 0), (Literal, None, 1)], result: true, }, TestVector { s: &[(CompressedData, None, 0), (CompressedData, None, 1), (Literal, None, 2)], result: true, }, TestVector { s: &[(SEIP, Some(1), 0), (Literal, None, 1), (MDC, None, 1)], result: true, }, TestVector { s: &[(CompressedData, None, 0), (SEIP, Some(1), 1), (Literal, None, 2), (MDC, None, 2)], result: true, }, TestVector { s: &[(CompressedData, None, 0), (SEIP, Some(1), 1), (CompressedData, None, 2), (Literal, None, 3), (MDC, None, 2)], result: true, }, TestVector { s: &[(CompressedData, None, 0), (SEIP, Some(1), 1), (CompressedData, None, 2), (Literal, None, 3), (MDC, None, 3)], result: false, }, TestVector { s: &[(SEIP, Some(1), 0), (MDC, None, 0)], result: false, }, TestVector { s: &[(SKESK, None, 0), (SEIP, Some(1), 0), (Literal, None, 1), (MDC, None, 1)], result: true, }, TestVector { s: &[(PKESK, None, 0), (SEIP, Some(1), 0), (Literal, None, 1), (MDC, None, 1)], result: true, }, TestVector { s: &[(PKESK, None, 0), (SEIP, Some(1), 0), (CompressedData, None, 1), (Literal, None, 2), (MDC, None, 1)], result: true, }, TestVector { s: &[(SKESK, None, 0), (SKESK, None, 0), (SEIP, Some(1), 0), (Literal, None, 1), (MDC, None, 1)], result: true, }, TestVector { s: &[(OnePassSig, None, 0), (Literal, None, 0), (Signature, None, 0)], result: true, }, TestVector { s: &[(OnePassSig, None, 0), (CompressedData, None, 0), (Literal, None, 1), (Signature, None, 0)], result: true, }, TestVector { s: &[(OnePassSig, None, 0), (OnePassSig, None, 0), (Literal, None, 0), (Signature, None, 0), (Signature, None, 0)], result: true, }, TestVector { s: &[(OnePassSig, None, 0), (OnePassSig, None, 0), (Literal, None, 0), (Signature, None, 0)], result: false, }, TestVector { s: &[(OnePassSig, None, 0), (OnePassSig, None, 0), (SEIP, Some(1), 0), (OnePassSig, None, 1), (SEIP, Some(1), 1), (Literal, None, 2), (MDC, None, 2), (Signature, None, 1), (MDC, None, 1), (Signature, None, 0), (Signature, None, 0)], result: true, }, // "[A Marker packet] MUST be ignored when received. It // may be placed at the beginning of a message that uses // features not available in PGP 2.6.x in order to cause // that version to report that newer software is necessary // to process the message.", section 5.8 of RFC4880. TestVector { s: &[(Marker, None, 0), (OnePassSig, None, 0), (Literal, None, 0), (Signature, None, 0)], result: true, }, TestVector { s: &[(OnePassSig, None, 0), (Literal, None, 0), (Signature, None, 0), (Marker, None, 0)], result: true, }, ]; for v in &test_vectors { let mut l = MessageValidator::new(); for (token, version, depth) in v.s.iter() { l.push(*token, *version, &(0..1 + *depth) .map(|x| x as usize) .collect::<Vec<_>>()[..]); if v.result { assert_match!(MessageValidity::MessagePrefix = l.check()); } } l.finish(); if v.result { assert_match!(MessageValidity::Message = l.check()); } else { assert_match!(MessageValidity::Error(_) = l.check()); } } } #[test] fn basic() { // Empty. // => bad. let message = Message::try_from(vec![]); assert!(message.is_err(), "{:?}", message); // 0: Literal // => good. let mut packets = Vec::new(); let mut lit = Literal::new(Unicode); lit.set_body(b"data".to_vec()); packets.push(lit.into()); let message = Message::try_from(packets); assert!(message.is_ok(), "{:?}", message); } #[test] fn compressed_part() { let mut lit = Literal::new(Unicode); lit.set_body(b"data".to_vec()); // 0: CompressedData // 0: Literal // => good. let mut packets = Vec::new(); packets.push( CompressedData::new(CompressionAlgorithm::Uncompressed) .push(lit.clone().into()) .into()); let message = Message::try_from(packets); assert!(message.is_ok(), "{:?}", message); // 0: CompressedData // 0: Literal // 1: Literal // => bad. let mut packets = Vec::new(); packets.push( CompressedData::new(CompressionAlgorithm::Uncompressed) .push(lit.clone().into()) .push(lit.clone().into()) .into()); let message = Message::try_from(packets); assert!(message.is_err(), "{:?}", message); // 0: CompressedData // 0: Literal // 1: Literal // => bad. let mut packets = Vec::new(); packets.push( CompressedData::new(CompressionAlgorithm::Uncompressed) .push(lit.clone().into()) .into()); packets.push(lit.clone().into()); let message = Message::try_from(packets); assert!(message.is_err(), "{:?}", message); // 0: CompressedData // 0: CompressedData // 0: Literal // => good. let mut packets = Vec::new(); packets.push( CompressedData::new(CompressionAlgorithm::Uncompressed) .push(CompressedData::new(CompressionAlgorithm::Uncompressed) .push(lit.clone() .into()) .into()) .into()); let message = Message::try_from(packets); assert!(message.is_ok(), "{:?}", message); } #[test] fn one_pass_sig_part() { let mut lit = Literal::new(Unicode); lit.set_body(b"data".to_vec()); let hash = crate::types::HashAlgorithm::SHA512; let key: key::SecretKey = crate::packet::key::Key6::generate_ecc(true, crate::types::Curve::Ed25519) .unwrap().into(); let mut pair = key.clone().into_keypair().unwrap(); let ctx = hash.context().unwrap().for_signature(pair.public().version()); let sig = crate::packet::signature::SignatureBuilder::new(SignatureType::Binary) .sign_hash(&mut pair, ctx).unwrap(); // 0: OnePassSig // => bad. let mut packets : Vec<Packet> = Vec::new(); packets.push(OnePassSig3::new(SignatureType::Binary).into()); let message = Message::try_from(packets); assert!(message.is_err(), "{:?}", message); // 0: OnePassSig // 1: Literal // => bad. let mut packets : Vec<Packet> = Vec::new(); packets.push(OnePassSig3::new(SignatureType::Binary).into()); packets.push(lit.clone().into()); let message = Message::try_from(packets); assert!(message.is_err(), "{:?}", message); // 0: OnePassSig // 1: Literal // 2: Signature // => good. let mut packets : Vec<Packet> = Vec::new(); packets.push(OnePassSig3::new(SignatureType::Binary).into()); packets.push(lit.clone().into()); packets.push(sig.clone().into()); let message = Message::try_from(packets); assert!(message.is_ok(), "{:?}", message); // 0: OnePassSig // 1: Literal // 2: Signature // 3: Signature // => bad. let mut packets : Vec<Packet> = Vec::new(); packets.push(OnePassSig3::new(SignatureType::Binary).into()); packets.push(lit.clone().into()); packets.push(sig.clone().into()); packets.push(sig.clone().into()); let message = Message::try_from(packets); assert!(message.is_err(), "{:?}", message); // 0: OnePassSig // 1: OnePassSig // 2: Literal // 3: Signature // 4: Signature // => good. let mut packets : Vec<Packet> = Vec::new(); packets.push(OnePassSig3::new(SignatureType::Binary).into()); packets.push(OnePassSig3::new(SignatureType::Binary).into()); packets.push(lit.clone().into()); packets.push(sig.clone().into()); packets.push(sig.clone().into()); let message = Message::try_from(packets); assert!(message.is_ok(), "{:?}", message); // 0: OnePassSig // 1: OnePassSig // 2: Literal // 3: Literal // 4: Signature // 5: Signature // => bad. let mut packets : Vec<Packet> = Vec::new(); packets.push(OnePassSig3::new(SignatureType::Binary).into()); packets.push(OnePassSig3::new(SignatureType::Binary).into()); packets.push(lit.clone().into()); packets.push(lit.clone().into()); packets.push(sig.clone().into()); packets.push(sig.clone().into()); let message = Message::try_from(packets); assert!(message.is_err(), "{:?}", message); // 0: OnePassSig // 1: OnePassSig // 2: CompressedData // 0: Literal // 3: Signature // 4: Signature // => good. let mut packets : Vec<Packet> = Vec::new(); packets.push(OnePassSig3::new(SignatureType::Binary).into()); packets.push(OnePassSig3::new(SignatureType::Binary).into()); packets.push( CompressedData::new(CompressionAlgorithm::Uncompressed) .push(lit.clone().into()) .into()); packets.push(sig.clone().into()); packets.push(sig.clone().into()); let message = Message::try_from(packets); assert!(message.is_ok(), "{:?}", message); } #[test] fn signature_part() { let mut lit = Literal::new(Unicode); lit.set_body(b"data".to_vec()); let hash = crate::types::HashAlgorithm::SHA512; let key: key::SecretKey = crate::packet::key::Key6::generate_ecc(true, crate::types::Curve::Ed25519) .unwrap().into(); let mut pair = key.clone().into_keypair().unwrap(); let ctx = hash.context().unwrap().for_signature(pair.public().version()); let sig = crate::packet::signature::SignatureBuilder::new(SignatureType::Binary) .sign_hash(&mut pair, ctx).unwrap(); // 0: Signature // => bad. let mut packets : Vec<Packet> = Vec::new(); packets.push(sig.clone().into()); let message = Message::try_from(packets); assert!(message.is_err(), "{:?}", message); // 0: Signature // 1: Literal // => good. let mut packets : Vec<Packet> = Vec::new(); packets.push(sig.clone().into()); packets.push(lit.clone().into()); let message = Message::try_from(packets); assert!(message.is_ok(), "{:?}", message); // 0: Signature // 1: Signature // 2: Literal // => good. let mut packets : Vec<Packet> = Vec::new(); packets.push(sig.clone().into()); packets.push(sig.clone().into()); packets.push(lit.clone().into()); let message = Message::try_from(packets); assert!(message.is_ok(), "{:?}", message); } #[test] fn encrypted_part() { // There are no simple constructors for SEIP packets: they are // interleaved with SK-ESK and PK-ESK packets. And, the // session key needs to be managed. Instead, we use some // internal interfaces to progressively build up more // complicated messages. let mut lit = Literal::new(Unicode); lit.set_body(b"data".to_vec()); // 0: SK-ESK // => bad. let mut packets : Vec<Packet> = Vec::new(); let cipher = SymmetricAlgorithm::AES256; let sk = crate::crypto::SessionKey::new(cipher.key_size().unwrap()).unwrap(); #[allow(deprecated)] packets.push(SKESK4::with_password( cipher, cipher, S2K::Simple { hash: HashAlgorithm::SHA256 }, &sk, &"12345678".into()).unwrap().into()); let message = Message::try_from(packets.clone()); assert!(message.is_err(), "{:?}", message); // 0: SK-ESK // 1: Literal // => bad. packets.push(lit.clone().into()); assert!(packets.iter().map(|p| p.tag()).collect::<Vec<Tag>>() == [ Tag::SKESK, Tag::Literal ]); let message = Message::try_from(packets.clone()); assert!(message.is_err(), "{:?}", message); // 0: SK-ESK // 1: SEIP // 0: Literal // 1: MDC // => good. let mut seip = SEIP1::new(); seip.container_mut().children_mut().unwrap().push( lit.clone().into()); seip.container_mut().children_mut().unwrap().push( MDC::from([0u8; 20]).into()); packets[1] = seip.into(); assert!(packets.iter().map(|p| p.tag()).collect::<Vec<Tag>>() == [ Tag::SKESK, Tag::SEIP ]); let message = Message::try_from(packets.clone()); assert!(message.is_ok(), "{:#?}", message); // 0: SK-ESK // 1: SEIP // 0: Literal // 1: MDC // 2: SK-ESK // => bad. let skesk = packets[0].clone(); packets.push(skesk); assert!(packets.iter().map(|p| p.tag()).collect::<Vec<Tag>>() == [ Tag::SKESK, Tag::SEIP, Tag::SKESK ]); let message = Message::try_from(packets.clone()); assert!(message.is_err(), "{:#?}", message); // 0: SK-ESK // 1: SK-ESK // 2: SEIP // 0: Literal // 1: MDC // => good. packets.swap(1, 2); assert!(packets.iter().map(|p| p.tag()).collect::<Vec<Tag>>() == [ Tag::SKESK, Tag::SKESK, Tag::SEIP ]); let message = Message::try_from(packets.clone()); assert!(message.is_ok(), "{:#?}", message); // 0: SK-ESK // 1: SK-ESK // 2: SEIP // 0: Literal // 1: MDC // 3: SEIP // 0: Literal // 1: MDC // => bad. let seip = packets[2].clone(); packets.push(seip); assert!(packets.iter().map(|p| p.tag()).collect::<Vec<Tag>>() == [ Tag::SKESK, Tag::SKESK, Tag::SEIP, Tag::SEIP ]); let message = Message::try_from(packets.clone()); assert!(message.is_err(), "{:#?}", message); // 0: SK-ESK // 1: SK-ESK // 2: SEIP // 0: Literal // 1: MDC // 3: Literal // => bad. packets[3] = lit.clone().into(); assert!(packets.iter().map(|p| p.tag()).collect::<Vec<Tag>>() == [ Tag::SKESK, Tag::SKESK, Tag::SEIP, Tag::Literal ]); let message = Message::try_from(packets.clone()); assert!(message.is_err(), "{:#?}", message); // 0: SK-ESK // 1: SK-ESK // 2: SEIP // 0: Literal // 1: MDC // 2: Literal // => bad. packets.remove(3); packets[2].container_mut().unwrap() .children_mut().unwrap().push(lit.clone().into()); assert!(packets.iter().map(|p| p.tag()).collect::<Vec<Tag>>() == [ Tag::SKESK, Tag::SKESK, Tag::SEIP ]); let message = Message::try_from(packets.clone()); assert!(message.is_err(), "{:#?}", message); // 0: SK-ESK // 2: PK-ESK // 1: SK-ESK // 2: SEIP // 0: Literal // => good. packets[2].container_mut().unwrap() .children_mut().unwrap().pop().unwrap(); #[allow(deprecated)] packets.insert( 1, PKESK3::new( Some("0000111122223333".parse().unwrap()), PublicKeyAlgorithm::RSAEncrypt, Ciphertext::RSA { c: MPI::new(&[]) }).unwrap().into()); assert!(packets.iter().map(|p| p.tag()).collect::<Vec<Tag>>() == [ Tag::SKESK, Tag::PKESK, Tag::SKESK, Tag::SEIP ]); let message = Message::try_from(packets.clone()); assert!(message.is_ok(), "{:#?}", message); } #[test] fn basic_message_validator() { use crate::message::{MessageValidator, MessageValidity, Token}; let mut l = MessageValidator::new(); l.push_token(Token::Literal, &[0]); l.finish(); assert!(matches!(l.check(), MessageValidity::Message)); } #[test] fn message_validator_push_token() { use crate::message::{MessageValidator, MessageValidity, Token}; let mut l = MessageValidator::new(); l.push_token(Token::Literal, &[0]); l.finish(); assert!(matches!(l.check(), MessageValidity::Message)); } #[test] fn message_validator_push() { use crate::message::{MessageValidator, MessageValidity}; use crate::packet::Tag; let mut l = MessageValidator::new(); l.push(Tag::Literal, None, &[0]); l.finish(); assert!(matches!(l.check(), MessageValidity::Message)); } #[test] fn message_validator_finish() { use crate::message::{MessageValidator, MessageValidity}; let mut l = MessageValidator::new(); l.finish(); assert!(matches!(l.check(), MessageValidity::Error(_))); } #[test] fn message_validator_check() { use crate::message::{MessageValidator, MessageValidity}; use crate::packet::Tag; // No packets will return an error. let mut l = MessageValidator::new(); assert!(matches!(l.check(), MessageValidity::MessagePrefix)); l.finish(); assert!(matches!(l.check(), MessageValidity::Error(_))); // Simple one-literal message. let mut l = MessageValidator::new(); l.push(Tag::Literal, None, &[0]); assert!(matches!(l.check(), MessageValidity::MessagePrefix)); l.finish(); assert!(matches!(l.check(), MessageValidity::Message)); } } �����������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/src/packet/any.rs�������������������������������������������������������������0000644�0000000�0000000�00000012466�10461020230�0016143�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������//! Convenient downcasting from Packets to Packet Bodies. use crate::packet::{ Packet, Unknown, Signature, OnePassSig, Key, key, Marker, Trust, UserID, UserAttribute, Literal, CompressedData, PKESK, SKESK, SEIP, MDC, Padding, }; /// Convenient downcasting from Packets to Packet Bodies. /// /// This trait offers functionality similar to [`std::any::Any`], /// hence the name. /// /// # Sealed trait /// /// This trait is [sealed] and cannot be implemented for types outside /// this crate. Therefore it can be extended in a non-breaking way. /// /// [sealed]: https://rust-lang.github.io/api-guidelines/future-proofing.html#sealed-traits-protect-against-downstream-implementations-c-sealed pub trait Any<T>: crate::seal::Sealed { /// Attempts to downcast to `T`, returning the packet if it fails. /// /// # Examples /// /// ```rust /// # use sequoia_openpgp::packet::prelude::*; /// let p: Packet = Marker::default().into(); /// let m: Marker = p.downcast().unwrap(); /// # let _ = m; /// ``` fn downcast(self) -> std::result::Result<T, Packet>; /// Attempts to downcast to `&T`, returning `None` if it fails. /// /// # Examples /// /// ```rust /// # use sequoia_openpgp::packet::prelude::*; /// let p: Packet = Marker::default().into(); /// let m: &Marker = p.downcast_ref().unwrap(); /// # let _ = m; /// ``` fn downcast_ref(&self) -> Option<&T>; /// Attempts to downcast to `&mut T`, returning `None` if it fails. /// /// # Examples /// /// ```rust /// # use sequoia_openpgp::packet::prelude::*; /// let mut p: Packet = Marker::default().into(); /// let m: &mut Marker = p.downcast_mut().unwrap(); /// # let _ = m; /// ``` fn downcast_mut(&mut self) -> Option<&mut T>; } macro_rules! impl_downcast_for { ($typ: tt) => { impl_downcast_for!($typ => $typ); }; ($($typ: tt)|* => $subtyp: ty) => { #[allow(deprecated)] impl Any<$subtyp> for Packet { fn downcast(self) -> std::result::Result<$subtyp, Packet> { match self { $(Packet::$typ(v) => Ok(v.into()),)* p => Err(p), } } fn downcast_ref(&self) -> Option<&$subtyp> { match self { $(Packet::$typ(v) => Some(v.into()),)* _ => None, } } fn downcast_mut(&mut self) -> Option<&mut $subtyp> { match self { $(Packet::$typ(v) => Some(v.into()),)* _ => None, } } } }; } macro_rules! impl_downcasts { ($($typ:ident, )*) => { $(impl_downcast_for!($typ);)* /// Checks that all packet types have implementations of `Any`. /// /// Not visible outside this module, isn't supposed to be /// called, this is a compile-time check. #[allow(unused, deprecated)] fn check_exhaustion(p: Packet) { match p { $(Packet::$typ(_) => (),)* // The downcasts to Key<P, R> are handled below. Packet::PublicKey(_) => (), Packet::PublicSubkey(_) => (), Packet::SecretKey(_) => (), Packet::SecretSubkey(_) => (), } } } } impl_downcasts!( Unknown, Signature, OnePassSig, Marker, Trust, UserID, UserAttribute, Literal, CompressedData, PKESK, SKESK, SEIP, MDC, Padding, ); // We ow selectively implement downcasts for the key types that alias // with the packet type. // 1. PublicParts, any role. impl_downcast_for!(PublicKey | SecretKey => Key<key::PublicParts, key::PrimaryRole>); impl_downcast_for!(PublicSubkey | SecretSubkey => Key<key::PublicParts, key::SubordinateRole>); impl_downcast_for!(PublicKey | PublicSubkey | SecretKey | SecretSubkey => Key<key::PublicParts, key::UnspecifiedRole>); // 2. SecretParts, any role. impl_downcast_for!(SecretKey => Key<key::SecretParts, key::PrimaryRole>); impl_downcast_for!(SecretSubkey => Key<key::SecretParts, key::SubordinateRole>); impl_downcast_for!(SecretKey | SecretSubkey => Key<key::SecretParts, key::UnspecifiedRole>); // 3. UnspecifiedParts, any role. impl_downcast_for!(PublicKey | SecretKey => Key<key::UnspecifiedParts, key::PrimaryRole>); impl_downcast_for!(PublicSubkey | SecretSubkey => Key<key::UnspecifiedParts, key::SubordinateRole>); impl_downcast_for!(PublicKey | PublicSubkey | SecretKey | SecretSubkey => Key<key::UnspecifiedParts, key::UnspecifiedRole>); #[cfg(test)] mod test { use super::*; #[test] fn downcast() { let p: Packet = Marker::default().into(); let mut p = Any::<UserID>::downcast(p).unwrap_err(); let r: Option<&UserID> = p.downcast_ref(); assert!(r.is_none()); let r: Option<&mut UserID> = p.downcast_mut(); assert!(r.is_none()); let _: &Marker = p.downcast_ref().unwrap(); let _: &mut Marker = p.downcast_mut().unwrap(); let _: Marker = p.downcast().unwrap(); } } ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/src/packet/compressed_data.rs�������������������������������������������������0000644�0000000�0000000�00000006402�10461020230�0020502�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������use std::fmt; #[cfg(test)] use quickcheck::{Arbitrary, Gen}; use crate::packet; use crate::Packet; use crate::types::CompressionAlgorithm; /// Holds a compressed data packet. /// /// A compressed data packet is a container. See [Section 5.6 of RFC /// 9580] for details. /// /// When the parser encounters a compressed data packet with an /// unknown compress algorithm, it returns an `Unknown` packet instead /// of a `CompressedData` packet. /// /// [Section 5.6 of RFC 9580]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.6 // IMPORTANT: If you add fields to this struct, you need to explicitly // IMPORTANT: implement PartialEq, Eq, and Hash. #[derive(Clone, PartialEq, Eq, Hash)] pub struct CompressedData { /// CTB packet header fields. pub(crate) common: packet::Common, /// Algorithm used to compress the payload. algo: CompressionAlgorithm, /// This is a container packet. container: packet::Container, } assert_send_and_sync!(CompressedData); impl fmt::Debug for CompressedData { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.debug_struct("CompressedData") .field("algo", &self.algo) .field("container", &self.container) .finish() } } impl CompressedData { /// Returns a new `CompressedData` packet. pub fn new(algo: CompressionAlgorithm) -> Self { CompressedData { common: Default::default(), algo, container: Default::default(), } } /// Gets the compression algorithm. pub fn algo(&self) -> CompressionAlgorithm { self.algo } /// Sets the compression algorithm. pub fn set_algo(&mut self, algo: CompressionAlgorithm) -> CompressionAlgorithm { ::std::mem::replace(&mut self.algo, algo) } /// Adds a new packet to the container. #[cfg(test)] pub fn push(mut self, packet: Packet) -> Self { self.container.children_mut().unwrap().push(packet); self } /// Inserts a new packet to the container at a particular index. /// If `i` is 0, the new packet is insert at the front of the /// container. If `i` is one, it is inserted after the first /// packet, etc. #[cfg(test)] pub fn insert(mut self, i: usize, packet: Packet) -> Self { self.container.children_mut().unwrap().insert(i, packet); self } } impl_processed_body_forwards!(CompressedData); impl From<CompressedData> for Packet { fn from(s: CompressedData) -> Self { Packet::CompressedData(s) } } #[cfg(test)] impl Arbitrary for CompressedData { fn arbitrary(g: &mut Gen) -> Self { use crate::serialize::SerializeInto; use crate::arbitrary_helper::gen_arbitrary_from_range; loop { let a = CompressionAlgorithm::from(gen_arbitrary_from_range(0..4, g)); if a.is_supported() { let mut c = CompressedData::new(a); // We arbitrarily chose to create packets with // processed bodies, so that // Packet::from_bytes(c.to_vec()) will roundtrip them. c.set_body(packet::Body::Processed( Packet::arbitrary(g).to_vec().unwrap() )); return c; } } } } ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/src/packet/container.rs�������������������������������������������������������0000644�0000000�0000000�00000036554�10461020230�0017342�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������//! Packet container support. //! //! Some packets contain other packets. This creates a tree //! structure. use std::fmt; use std::hash::{Hash, Hasher}; use std::slice; use std::vec; use xxhash_rust::xxh3::Xxh3; use crate::{ Packet, packet::{ Iter, SEIP, }, }; /// A packet's body holds either unprocessed bytes, processed bytes, /// or packets. /// /// We conceptually divide packets into two parts: the header and the /// body. Whereas the header is read eagerly when the packet is /// deserialized, the body is only read on demand. /// /// A packet's body is stored here either when configured via /// [`PacketParserBuilder::buffer_unread_content`], when one of the /// [`PacketPile`] deserialization routines is used, or on demand for /// a particular packet using the /// [`PacketParser::buffer_unread_content`] method. /// /// [`PacketParserBuilder::buffer_unread_content`]: crate::parse::PacketParserBuilder::buffer_unread_content() /// [`PacketPile`]: crate::PacketPile /// [`PacketParser::buffer_unread_content`]: crate::parse::PacketParser::buffer_unread_content() /// /// There are three different types of packets: /// /// - Most packets, like the [`UserID`] and [`Signature`] packets, don't /// actually have a body. /// /// [`UserID`]: crate::packet::UserID /// [`Signature`]: crate::packet::Signature /// /// - Some packets have an unprocessed body. The [`Literal`] data /// packet wraps unstructured plaintext, and the [`Unknown`] /// packet contains data that we failed to process, say because we /// didn't support the packet's version. /// /// [`Literal`]: crate::packet::Literal /// [`Unknown`]: crate::packet::Unknown /// /// - Some packets are containers. If the parser does not parse the /// packet's child, either because the caller used /// [`PacketParser::next`] to get the next packet, or the maximum /// recursion depth was reached, then the packets can be stored /// here as an unstructured byte stream. (If the caller so /// chooses, the content can be parsed later using the regular /// deserialization routines, since the content is just an OpenPGP /// message.) /// /// [`PacketParser::next`]: crate::parse::PacketParser::next() #[derive(Clone, Debug)] pub enum Body { /// Unprocessed packet body. /// /// The body has not been processed. This happens in the /// following cases: /// /// - The packet is a [`Literal`] packet. /// /// - The packet is an [`Unknown`] packet, i.e. it contains data /// that we failed to process, say because we didn't support /// the packet's version. /// /// - The packet is an encryption container ([`SEIP`]) and the /// body is encrypted. /// /// Note: if some of a packet's data is streamed, and the /// `PacketParser` is configured to buffer unread content, then /// this is not the packet's entire content; it is just the unread /// content. /// /// [`Literal`]: crate::packet::Literal /// [`Unknown`]: crate::packet::Unknown /// [`SEIP`]: crate::packet::SEIP Unprocessed(Vec<u8>), /// Processed packed body. /// /// The body has been processed, i.e. decompressed or decrypted, /// but not parsed into packets. /// /// Note: if some of a packet's data is streamed, and the /// `PacketParser` is configured to buffer unread content, then /// this is not the packet's entire content; it is just the unread /// content. Processed(Vec<u8>), /// Parsed packet body. /// /// Used by container packets (such as the encryption and /// compression packets) to reference their immediate children. /// This results in a tree structure. /// /// This is automatically populated when using the [`PacketPile`] /// deserialization routines, e.g., [`PacketPile::from_file`]. By /// default, it is *not* automatically filled in by the /// [`PacketParser`] deserialization routines; this needs to be /// done manually. /// /// [`PacketPile`]: crate::PacketPile /// [`PacketPile::from_file`]: crate::PacketPile#method.from_file /// [`PacketParser`]: crate::parse::PacketParser Structured(Vec<Packet>), } assert_send_and_sync!(Body); /// Holds packet bodies. /// /// This is used by OpenPGP container packets, like the compressed /// data packet, to store the containing packets. #[derive(Clone)] pub struct Container { /// Holds a packet's body. body: Body, /// We compute a digest over the body to implement comparison. body_digest: u64, } assert_send_and_sync!(Container); impl PartialEq for Container { fn eq(&self, other: &Container) -> bool { use Body::*; match (&self.body, &other.body) { (Unprocessed(_), Unprocessed(_)) => self.body_digest == other.body_digest, (Processed(_), Processed(_)) => self.body_digest == other.body_digest, (Structured(a), Structured(b)) => a == b, _ => false, } } } impl Eq for Container {} impl Hash for Container { fn hash<H: Hasher>(&self, state: &mut H) { if let Body::Structured(packets) = &self.body { packets.hash(state); } else { self.body_digest.hash(state); } } } impl Default for Container { fn default() -> Self { Self { body: Body::Structured(Vec::with_capacity(0)), body_digest: 0, } } } impl From<Vec<Packet>> for Container { fn from(packets: Vec<Packet>) -> Self { Self { body: Body::Structured(packets), body_digest: 0, } } } impl fmt::Debug for Container { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt_bytes(f: &mut fmt::Formatter, tag: &str, bytes: &[u8], digest: String) -> fmt::Result { let threshold = 16; let prefix = &bytes[..std::cmp::min(threshold, bytes.len())]; let mut prefix_fmt = crate::fmt::hex::encode(prefix); if bytes.len() > threshold { prefix_fmt.push_str("..."); } prefix_fmt.push_str(&format!(" ({} bytes)", bytes.len())[..]); f.debug_struct("Container") .field(tag, &prefix_fmt) .field("digest", &digest) .finish() } use Body::*; match &self.body { Unprocessed(bytes) => fmt_bytes(f, "unprocessed", bytes, self.body_digest()), Processed(bytes) => fmt_bytes(f, "processed", bytes, self.body_digest()), Structured(packets) => f.debug_struct("Container").field("packets", packets).finish(), } } } impl Container { pub(crate) fn default_unprocessed() -> Self { Self { body: Body::Unprocessed(Vec::with_capacity(0)), body_digest: Self::empty_body_digest(), } } /// Returns a reference to this Packet's children. /// /// Returns `None` if the body is not structured. pub fn children_ref(&self) -> Option<&[Packet]> { if let Body::Structured(packets) = &self.body { Some(&packets[..]) } else { None } } /// Returns a mutable reference to this Packet's children. /// /// Returns `None` if the body is not structured. pub fn children_mut(&mut self) -> Option<&mut Vec<Packet>> { if let Body::Structured(packets) = &mut self.body { Some(packets) } else { None } } /// Returns an iterator over the packet's descendants. The /// descendants are visited in depth-first order. /// /// Returns `None` if the body is not structured. pub fn descendants(&self) -> Option<Iter> { Some(Iter { // Iterate over each packet in the message. children: self.children()?, child: None, grandchildren: None, depth: 0, }) } /// Returns an iterator over the packet's immediate children. /// /// Returns `None` if the body is not structured. pub fn children(&self) -> Option<slice::Iter<Packet>> { Some(self.children_ref()?.iter()) } /// Returns an `IntoIter` over the packet's immediate children. /// /// Returns `None` if the body is not structured. pub fn into_children(self) -> Option<vec::IntoIter<Packet>> { if let Body::Structured(packets) = self.body { Some(packets.into_iter()) } else { None } } /// Gets the packet's body. pub fn body(&self) -> &Body { &self.body } /// Sets the packet's body. pub fn set_body(&mut self, body: Body) -> Body { use Body::*; let mut h = Self::make_body_hash(); match &body { Unprocessed(bytes) => h.update(bytes), Processed(bytes) => h.update(bytes), Structured(_) => (), } self.set_body_hash(h); std::mem::replace(&mut self.body, body) } /// Returns the hash for the empty body. fn empty_body_digest() -> u64 { use std::sync::OnceLock; static DIGEST: OnceLock<u64> = OnceLock::new(); *DIGEST.get_or_init(|| Container::make_body_hash().digest()) } /// Creates a hash context for hashing the body. pub(crate) // For parse.rs fn make_body_hash() -> Box<Xxh3> { Box::new(Xxh3::new()) } /// Hashes content that has been streamed. pub(crate) // For parse.rs fn set_body_hash(&mut self, h: Box<Xxh3>) { self.body_digest = h.digest(); } pub(crate) fn body_digest(&self) -> String { format!("{:08X}", self.body_digest) } /// Converts an indentation level to whitespace. #[cfg(test)] fn indent(depth: usize) -> &'static str { use std::cmp; let s = " "; &s[0..cmp::min(depth, s.len())] } /// Pretty prints the container to stderr. /// /// This function is primarily intended for debugging purposes. /// /// `indent` is the number of spaces to indent the output. #[cfg(test)] pub(crate) fn pretty_print(&self, indent: usize) { for (i, p) in self.children_ref().iter().enumerate() { eprintln!("{}{}: {:?}", Self::indent(indent), i + 1, p); if let Some(children) = self.children_ref() .and_then(|c| c.get(i)).and_then(|p| p.container_ref()) { children.pretty_print(indent + 1); } } } } macro_rules! impl_unprocessed_body_forwards { ($typ:ident) => { /// This packet implements the unprocessed container /// interface. /// /// Container packets like this one can contain unprocessed /// data. impl $typ { /// Returns a reference to the container. pub(crate) fn container_ref(&self) -> &packet::Container { &self.container } /// Returns a mutable reference to the container. pub(crate) fn container_mut(&mut self) -> &mut packet::Container { &mut self.container } /// Gets a reference to the this packet's body. pub fn body(&self) -> &[u8] { use crate::packet::Body::*; match self.container.body() { Unprocessed(bytes) => bytes, Processed(_) => unreachable!( "Unprocessed container has processed body"), Structured(_) => unreachable!( "Unprocessed container has structured body"), } } /// Sets the this packet's body. pub fn set_body(&mut self, data: Vec<u8>) -> Vec<u8> { use crate::packet::{Body, Body::*}; match self.container.set_body(Body::Unprocessed(data)) { Unprocessed(bytes) => bytes, Processed(_) => unreachable!( "Unprocessed container has processed body"), Structured(_) => unreachable!( "Unprocessed container has structured body"), } } } }; } macro_rules! impl_processed_body_forwards { ($typ:ident) => { /// This packet implements the processed container /// interface. /// /// Container packets like this one can contain either /// unprocessed or processed, structured data. impl $typ { /// Returns a reference to the container. pub fn container_ref(&self) -> &packet::Container { &self.container } /// Returns a mutable reference to the container. pub fn container_mut(&mut self) -> &mut packet::Container { &mut self.container } /// Gets a reference to the this packet's body. pub fn body(&self) -> &crate::packet::Body { self.container_ref().body() } /// Sets the this packet's body. pub fn set_body(&mut self, body: crate::packet::Body) -> crate::packet::Body { self.container_mut().set_body(body) } } }; } impl Packet { pub(crate) // for packet_pile.rs fn container_ref(&self) -> Option<&Container> { match self { Packet::CompressedData(p) => Some(p.container_ref()), Packet::SEIP(SEIP::V1(p)) => Some(p.container_ref()), Packet::SEIP(SEIP::V2(p)) => Some(p.container_ref()), Packet::Literal(p) => Some(p.container_ref()), Packet::Unknown(p) => Some(p.container_ref()), _ => None, } } pub(crate) // for packet_pile.rs, packet_pile_parser.rs, parse.rs fn container_mut(&mut self) -> Option<&mut Container> { match self { Packet::CompressedData(p) => Some(p.container_mut()), Packet::SEIP(SEIP::V1(p)) => Some(p.container_mut()), Packet::SEIP(SEIP::V2(p)) => Some(p.container_mut()), Packet::Literal(p) => Some(p.container_mut()), Packet::Unknown(p) => Some(p.container_mut()), _ => None, } } /// Returns an iterator over the packet's immediate children. pub(crate) fn children(& self) -> Option<impl Iterator<Item = &Packet>> { self.container_ref().and_then(|c| c.children()) } /// Returns an iterator over all the packet's descendants, in /// depth-first order. pub(crate) fn descendants(&self) -> Option<Iter> { self.container_ref().and_then(|c| c.descendants()) } /// Retrieves the packet's unprocessed body. #[cfg(test)] #[allow(dead_code)] // Not used if no compression feature is enabled. pub(crate) fn unprocessed_body(&self) -> Option<&[u8]> { self.container_ref().and_then(|c| match c.body() { Body::Unprocessed(bytes) => Some(&bytes[..]), _ => None, }) } /// Retrieves the packet's processed body. #[cfg(test)] pub(crate) fn processed_body(&self) -> Option<&[u8]> { self.container_ref().and_then(|c| match c.body() { Body::Processed(bytes) => Some(&bytes[..]), _ => None, }) } } ����������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/src/packet/header/ctb.rs������������������������������������������������������0000644�0000000�0000000�00000020636�10461020230�0017352�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������//! Cipher Type Byte (CTB). //! //! The CTB encodes the packet's type and some length information. It //! has two variants: the so-called old format and the so-called new //! format. See [Section 4.2 of RFC 9580] for more details. //! //! [Section 4.2 of RFC 9580]: https://www.rfc-editor.org/rfc/rfc9580.html#section-4.2 use std::convert::TryFrom; use crate::{ packet::Tag, Error, Result }; use crate::packet::header::BodyLength; /// Data common to all CTB formats. /// /// OpenPGP defines two packet formats: an old format and a new /// format. They both include the packet's so-called tag. /// /// See [Section 4.2 of RFC 9580] for more details. /// /// [Section 4.2 of RFC 9580]: https://www.rfc-editor.org/rfc/rfc9580.html#section-4.2 #[derive(Clone, Debug)] struct CTBCommon { /// RFC4880 Packet tag tag: Tag, } /// A CTB using the new format encoding. /// /// See [Section 4.2 of RFC 9580] for more details. /// /// [Section 4.2 of RFC 9580]: https://www.rfc-editor.org/rfc/rfc9580.html#section-4.2 #[derive(Clone, Debug)] pub struct CTBNew { /// Packet CTB fields common: CTBCommon, } assert_send_and_sync!(CTBNew); impl CTBNew { /// Constructs a new-style CTB. pub fn new(tag: Tag) -> Self { CTBNew { common: CTBCommon { tag, }, } } /// Returns the packet's tag. pub fn tag(&self) -> Tag { self.common.tag } } /// The length encoded for an old style CTB. /// /// The `PacketLengthType` is only part of the [old CTB], and is /// partially used to determine the packet's size. /// /// See [Section 4.2.2 of RFC 9580] for more details. /// /// [old CTB]: CTBOld /// [Section 4.2.2 of RFC 9580]: https://www.rfc-editor.org/rfc/rfc9580.html#section-4.2.2 #[derive(Debug)] #[derive(Clone, Copy, PartialEq)] pub enum PacketLengthType { /// A one-octet Body Length header encodes a length of 0 to 191 octets. /// /// The header is 2 octets long. It contains the one byte CTB /// followed by the one octet length. OneOctet, /// A two-octet Body Length header encodes a length of 192 to 8383 octets. /// /// The header is 3 octets long. It contains the one byte CTB /// followed by the two octet length. TwoOctets, /// A four-octet Body Length. /// /// The header is 5 octets long. It contains the one byte CTB /// followed by the four octet length. FourOctets, /// The packet is of indeterminate length. /// /// Neither the packet header nor the packet itself contain any /// information about the length. The end of the packet is clear /// from the context, e.g., EOF. Indeterminate, } assert_send_and_sync!(PacketLengthType); impl TryFrom<u8> for PacketLengthType { type Error = anyhow::Error; fn try_from(u: u8) -> Result<Self> { match u { 0 => Ok(PacketLengthType::OneOctet), 1 => Ok(PacketLengthType::TwoOctets), 2 => Ok(PacketLengthType::FourOctets), 3 => Ok(PacketLengthType::Indeterminate), _ => Err(Error::InvalidArgument( format!("Invalid packet length: {}", u)).into()), } } } impl From<PacketLengthType> for u8 { fn from(l: PacketLengthType) -> Self { match l { PacketLengthType::OneOctet => 0, PacketLengthType::TwoOctets => 1, PacketLengthType::FourOctets => 2, PacketLengthType::Indeterminate => 3, } } } /// A CTB using the old format encoding. /// /// See [Section 4.2 of RFC 9580] for more details. /// /// [Section 4.2 of RFC 9580]: https://www.rfc-editor.org/rfc/rfc9580.html#section-4.2 #[derive(Clone, Debug)] pub struct CTBOld { /// Common CTB fields. common: CTBCommon, /// Type of length specifier. length_type: PacketLengthType, } assert_send_and_sync!(CTBOld); impl CTBOld { /// Constructs an old-style CTB. /// /// # Errors /// /// Returns [`crate::Error::InvalidArgument`] if the tag or the /// body length cannot be expressed using an old-style CTB. pub fn new(tag: Tag, length: BodyLength) -> Result<Self> { let n: u8 = tag.into(); // Only tags 0-15 are supported. if n > 15 { return Err(Error::InvalidArgument( format!("Only tags 0-15 are supported, got: {:?} ({})", tag, n)).into()); } let length_type = match length { // Assume an optimal encoding. BodyLength::Full(l) => { match l { // One octet length. 0 ..= 0xFF => PacketLengthType::OneOctet, // Two octet length. 0x1_00 ..= 0xFF_FF => PacketLengthType::TwoOctets, // Four octet length, _ => PacketLengthType::FourOctets, } }, BodyLength::Partial(_) => return Err(Error::InvalidArgument( "Partial body lengths are not support for old format packets". into()).into()), BodyLength::Indeterminate => PacketLengthType::Indeterminate, }; Ok(CTBOld { common: CTBCommon { tag, }, length_type, }) } /// Returns the packet's tag. pub fn tag(&self) -> Tag { self.common.tag } /// Returns the packet's length type. pub fn length_type(&self) -> PacketLengthType { self.length_type } } /// The CTB variants. /// /// There are two CTB variants: the [old CTB format] and the [new CTB /// format]. /// /// [old CTB format]: CTBOld /// [new CTB format]: CTBNew /// /// Note: CTB stands for Cipher Type Byte. #[derive(Clone, Debug)] pub enum CTB { /// New (current) packet header format. New(CTBNew), /// Old PGP 2.6 header format. Old(CTBOld), } assert_send_and_sync!(CTB); impl CTB { /// Constructs a new-style CTB. pub fn new(tag: Tag) -> Self { CTB::New(CTBNew::new(tag)) } /// Returns the packet's tag. pub fn tag(&self) -> Tag { match self { CTB::New(c) => c.tag(), CTB::Old(c) => c.tag(), } } } impl TryFrom<u8> for CTB { type Error = anyhow::Error; /// Parses a CTB as described in [Section 4.2 of RFC 9580]. This /// function parses both new and old format CTBs. /// /// [Section 4.2 of RFC 9580]: https://www.rfc-editor.org/rfc/rfc9580.html#section-4.2 fn try_from(ptag: u8) -> Result<CTB> { // The top bit of the ptag must be set. if ptag & 0b1000_0000 == 0 { return Err( Error::MalformedPacket( format!("Malformed CTB: MSB of ptag ({:#010b}) not set{}.", ptag, if ptag == b'-' { " (ptag is a dash, perhaps this is an \ ASCII-armor encoded message)" } else { "" })).into()); } let new_format = ptag & 0b0100_0000 != 0; let ctb = if new_format { let tag = ptag & 0b0011_1111; CTB::New(CTBNew { common: CTBCommon { tag: tag.into() }}) } else { let tag = (ptag & 0b0011_1100) >> 2; let length_type = ptag & 0b0000_0011; CTB::Old(CTBOld { common: CTBCommon { tag: tag.into(), }, length_type: PacketLengthType::try_from(length_type)?, }) }; Ok(ctb) } } #[test] fn ctb() { // 0x99 = public key packet if let CTB::Old(ctb) = CTB::try_from(0x99).unwrap() { assert_eq!(ctb.tag(), Tag::PublicKey); assert_eq!(ctb.length_type, PacketLengthType::TwoOctets); } else { panic!("Expected an old format packet."); } // 0xa3 = old compressed packet if let CTB::Old(ctb) = CTB::try_from(0xa3).unwrap() { assert_eq!(ctb.tag(), Tag::CompressedData); assert_eq!(ctb.length_type, PacketLengthType::Indeterminate); } else { panic!("Expected an old format packet."); } // 0xcb: new literal if let CTB::New(ctb) = CTB::try_from(0xcb).unwrap() { assert_eq!(ctb.tag(), Tag::Literal); } else { panic!("Expected a new format packet."); } } ��������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/src/packet/header/mod.rs������������������������������������������������������0000644�0000000�0000000�00000030351�10461020230�0017354�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������//! OpenPGP packet headers. //! //! An OpenPGP packet header contains packet meta-data. Specifically, //! it includes the [packet's type] (its so-called *tag*), and the //! [packet's length]. //! //! Decades ago, when OpenPGP was conceived, saving even a few bits //! was considered important. As such, OpenPGP uses very compact //! encodings. The encoding schemes have evolved so that there are //! now two families: the so-called old format, and new format //! encodings. //! //! [packet's type]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5 //! [packet's length]: https://www.rfc-editor.org/rfc/rfc9580.html#section-4.2.2 use crate::{ Error, Result, }; use crate::packet::tag::Tag; mod ctb; pub use self::ctb::{ CTB, CTBOld, CTBNew, PacketLengthType, }; /// A packet's header. /// /// See [Section 4.2 of RFC 9580] for details. /// /// [Section 4.2 of RFC 9580]: https://www.rfc-editor.org/rfc/rfc9580.html#section-4.2 #[derive(Clone, Debug)] pub struct Header { /// The packet's CTB. ctb: CTB, /// The packet's length. length: BodyLength, } assert_send_and_sync!(Header); impl Header { /// Creates a new header. pub fn new(ctb: CTB, length: BodyLength) -> Self { Header { ctb, length } } /// Returns the header's CTB. pub fn ctb(&self) -> &CTB { &self.ctb } /// Returns the header's length. pub fn length(&self) -> &BodyLength { &self.length } /// Checks the header for validity. /// /// A header is considered invalid if: /// /// - The tag is [`Tag::Reserved`]. /// - The tag is [`Tag::Unknown`] or [`Tag::Private`] and /// `future_compatible` is false. /// - The [length encoding] is invalid for the packet (e.g., /// partial body encoding may not be used for [`PKESK`] packets) /// - The lengths are unreasonable for a packet (e.g., a /// `PKESK` or [`SKESK`] larger than 10 KB). /// /// [`Tag::Reserved`]: super::Tag::Reserved /// [`Tag::Unknown`]: super::Tag::Unknown /// [`Tag::Private`]: super::Tag::Private /// [length encoding]: https://www.rfc-editor.org/rfc/rfc9580.html#section-4.2.1.4 /// [`PKESK`]: super::PKESK /// [`SKESK`]: super::SKESK // Note: To check the packet's content, use // `PacketParser::plausible`. pub fn valid(&self, future_compatible: bool) -> Result<()> { let tag = self.ctb.tag(); match tag { // Reserved packets are never valid. Tag::Reserved => return Err(Error::UnsupportedPacketType(tag).into()), // Unknown packets are not valid unless we want future compatibility. Tag::Unknown(_) | Tag::Private(_) if !future_compatible => return Err(Error::UnsupportedPacketType(tag).into()), _ => (), } // An implementation MAY use Partial Body Lengths for data // packets, be they literal, compressed, or encrypted. The // first partial length MUST be at least 512 octets long. // Partial Body Lengths MUST NOT be used for any other packet // types. // // https://www.rfc-editor.org/rfc/rfc9580.html#section-4.2.1.4 if tag == Tag::Literal || tag == Tag::CompressedData || tag == Tag::SED || tag == Tag::SEIP || tag == Tag::AED { // Data packet. match self.length { BodyLength::Indeterminate => (), BodyLength::Partial(l) => { if l < 512 { return Err(Error::MalformedPacket( format!("Partial body length must be \ at least 512 (got: {})", l)).into()); } } BodyLength::Full(l) => { // In the following block cipher length checks, we // conservatively assume a block size of 8 bytes, // because Twofish, TripleDES, IDEA, and CAST-5 // have a block size of 64 bits. if tag == Tag::SED && (l < (8 // Random block. + 2 // Quickcheck bytes. + 6)) { // Smallest literal. return Err(Error::MalformedPacket( format!("{} packet's length must be \ at least 16 bytes in length (got: {})", tag, l)).into()); } else if tag == Tag::SEIP && (l < (1 // Version. + 8 // Random block. + 2 // Quickcheck bytes. + 6 // Smallest literal. + 20)) // MDC packet. { return Err(Error::MalformedPacket( format!("{} packet's length minus 1 must be \ at least 37 bytes in length (got: {})", tag, l)).into()); } else if tag == Tag::CompressedData && l == 0 { // One byte header. return Err(Error::MalformedPacket( format!("{} packet's length must be \ at least 1 byte (got ({})", tag, l)).into()); } else if tag == Tag::Literal && l < 6 { // Smallest literal packet consists of 6 octets. return Err(Error::MalformedPacket( format!("{} packet's length must be \ at least 6 bytes (got: ({})", tag, l)).into()); } } } } else { // Non-data packet. match self.length { BodyLength::Indeterminate => return Err(Error::MalformedPacket( format!("Indeterminite length encoding \ not allowed for {} packets", tag)).into()), BodyLength::Partial(_) => return Err(Error::MalformedPacket( format!("Partial Body Chunking not allowed \ for {} packets", tag)).into()), BodyLength::Full(l) => { let valid = match tag { Tag::Signature => // A V3 signature is 19 bytes plus the // MPIs. A V4 is 10 bytes plus the hash // areas and the MPIs. (10..(10 // Header, fixed sized fields. + 2 * 64 * 1024 // Hashed & Unhashed areas. + 64 * 1024 // MPIs. )).contains(&l), Tag::SKESK => // 2 bytes of fixed header. A s2k // specification (at least 1 byte), an // optional encryption session key. (3..10 * 1024).contains(&l), Tag::PKESK => // For v3, 10 bytes of fixed header, plus // the encrypted session key. (10 < l && l < 10 * 1024) // For v6, 5 bytes of fixed header, plus // the encrypted session key. || (5 <= l && l < 10 * 1024), Tag::OnePassSig if ! future_compatible => l == 13 // v3 || (6 + 32..6 + 32 + 256).contains(&l), // v6 Tag::OnePassSig => l < 1024, Tag::PublicKey | Tag::PublicSubkey | Tag::SecretKey | Tag::SecretSubkey => // A V3 key is 8 bytes of fixed header // plus MPIs. A V4 key is 6 bytes of // fixed headers plus MPIs. 6 < l && l < 1024 * 1024, Tag::Trust => true, Tag::UserID => // Avoid insane user ids. l < 32 * 1024, Tag::UserAttribute => // The header is at least 2 bytes. 2 <= l, Tag::MDC => l == 20, Tag::Literal | Tag::CompressedData | Tag::SED | Tag::SEIP | Tag::AED => unreachable!("handled in the data-packet branch"), Tag::Unknown(_) | Tag::Private(_) => true, Tag::Marker => l == 3, Tag::Reserved => true, Tag::Padding => true, }; if ! valid { return Err(Error::MalformedPacket( format!("Invalid size ({} bytes) for a {} packet", l, tag)).into()) } } } } Ok(()) } } /// A packet's size. /// /// A packet's size can be expressed in three different ways. Either /// the size of the packet is fully known (`Full`), the packet is /// chunked using OpenPGP's partial body encoding (`Partial`), or the /// packet extends to the end of the file (`Indeterminate`). See /// [Section 4.2 of RFC 9580] for more details. /// /// [Section 4.2 of RFC 9580]: https://www.rfc-editor.org/rfc/rfc9580.html#section-4.2 #[derive(Debug)] // We need PartialEq so that assert_eq! works. #[derive(PartialEq)] #[derive(Clone, Copy)] pub enum BodyLength { /// The packet's size is known. Full(u32), /// The parameter is the number of bytes in the current chunk. /// /// This type is only used with new format packets. Partial(u32), /// The packet extends until an EOF is encountered. /// /// This type is only used with old format packets. Indeterminate, } assert_send_and_sync!(BodyLength); #[cfg(test)] mod tests { use super::*; use crate::packet::Packet; use crate::parse::{ Cookie, Dearmor, PacketParserBuilder, PacketParserResult, Parse, }; use crate::serialize::SerializeInto; quickcheck! { /// Checks alignment of Header::parse-then-Header::valid, the /// PacketParser, and Arbitrary::arbitrary. fn parser_alignment(p: Packet) -> bool { let verbose = false; let buf = p.to_vec().expect("Failed to serialize packet"); // First, check Header::parse and Header::valid. let mut reader = buffered_reader::Memory::with_cookie( &buf, Cookie::default()); let header = Header::parse(&mut reader).unwrap(); if verbose { eprintln!("header parsed: {:?}", header); } header.valid(true).unwrap(); header.valid(false).unwrap(); // Now check the packet parser. Be careful to disable the // armor detection because that in turn relies on // Header::valid, and we want to test the behavior of the // packet parser. let ppr = PacketParserBuilder::from_bytes(&buf).unwrap() .dearmor(Dearmor::Disabled) .buffer_unread_content() .build().unwrap(); let (p, ppr) = match ppr { PacketParserResult::Some(pp) => { pp.next().unwrap() }, PacketParserResult::EOF(eof) => panic!("no packet found: {:?}", eof), }; if verbose { eprintln!("packet parser parsed: {:?}", p); } if let PacketParserResult::Some(pp) = ppr { panic!("Excess data after packet: {:?}", pp) } true } } } ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/src/packet/key/conversions.rs�������������������������������������������������0000644�0000000�0000000�00000062227�10461020230�0020514�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������//! Conversion functions for `Key` and associated types. use std::convert::TryFrom; use crate::Error; use crate::cert::prelude::*; use crate::packet::prelude::*; use crate::packet::key::{ KeyParts, KeyRole, PrimaryRole, PublicParts, SubordinateRole, SecretParts, UnspecifiedParts, UnspecifiedRole }; use crate::Result; macro_rules! convert { ( $x:ident ) => { // XXX: This is ugly, but how can we do better? { // XXX: Appease rustc 1.80.1 which doesn't like annotations on expressions. #[allow(clippy::missing_transmute_annotations)] unsafe { std::mem::transmute($x) } } } } macro_rules! convert_ref { ( $x:ident ) => { // XXX: This is ugly, but how can we do better? #[allow(clippy::missing_transmute_annotations)] unsafe { std::mem::transmute($x) } } } // Make it possible to go from an arbitrary Key<P, R> to an // arbitrary Key<P', R'> (or &Key<P, R> to &Key<P', R'>) in a // single .into(). // // To allow the programmer to make the intent clearer, also // provide explicit conversion function. // In principle, this is as easy as the following: // // impl<P, P2, R, R2> From<Key<P, R>> for Key<P2, R2> // where P: KeyParts, P2: KeyParts, R: KeyRole, R2: KeyRole // { // fn from(p: Key<P, R>) -> Self { // unimplemented!() // } // } // // But that results in: // // error[E0119]: conflicting implementations of trait `std::convert::From<packet::Key<_, _>>` for type `packet::Key<_, _>`: // = note: conflicting implementation in crate `core`: // - impl<T> std::convert::From<T> for T; // // Unfortunately, it's not enough to make one type variable // concrete, as the following errors demonstrate: // // error[E0119]: conflicting implementations of trait `std::convert::From<packet::Key<packet::key::PublicParts, _>>` for type `packet::Key<packet::key::PublicParts, _>`: // ... // = note: conflicting implementation in crate `core`: // - impl<T> std::convert::From<T> for T; // // impl<P, R, R2> From<Key<P, R>> for Key<PublicParts, R2> // where P: KeyParts, R: KeyRole, R2: KeyRole // { // fn from(p: Key<P, R>) -> Self { // unimplemented!() // } // } // // error[E0119]: conflicting implementations of trait `std::convert::From<packet::Key<packet::key::PublicParts, _>>` for type `packet::Key<packet::key::PublicParts, _>`: // --> openpgp/src/packet/key.rs:186:5 // ... // = note: conflicting implementation in crate `core`: // - impl<T> std::convert::From<T> for T; // impl<P2, R, R2> From<Key<PublicParts, R>> for Key<P2, R2> // where P2: KeyParts, R: KeyRole, R2: KeyRole // { // fn from(p: Key<PublicParts, R>) -> Self { // unimplemented!() // } // } // // To solve this, we need at least one generic variable to be // concrete on both sides of the `From`. macro_rules! create_part_conversions { ( $Key:ident<$( $l:lifetime ),*; $( $g:ident ),*>) => { create_part_conversions!($Key<$($l),*; $($g),*> where ); }; ( $Key:ident<$( $l:lifetime ),*; $( $g:ident ),*> where $( $w:ident: $c:path ),* ) => { // Convert between two KeyParts for a constant KeyRole. // Unfortunately, we can't let the KeyRole vary as otherwise we // get conflicting types when we do the same to convert between // two KeyRoles for a constant KeyParts. :( macro_rules! p { ( <$from_parts:ty> -> <$to_parts:ty> ) => { impl<$($l, )* $($g, )* > From<$Key<$($l, )* $from_parts, $($g, )* >> for $Key<$($l, )* $to_parts, $($g, )* > where $($w: $c ),* { fn from(p: $Key<$($l, )* $from_parts, $($g, )* >) -> Self { convert!(p) } } impl<$($l, )* $($g, )* > From<&$($l)* $Key<$($l, )* $from_parts, $($g, )* >> for &$($l)* $Key<$($l, )* $to_parts, $($g, )* > where $($w: $c ),* { fn from(p: &$($l)* $Key<$($l, )* $from_parts, $($g, )* >) -> Self { convert_ref!(p) } } impl<$($l, )* $($g, )* > From<&$($l)* mut $Key<$($l, )* $from_parts, $($g, )* >> for &$($l)* mut $Key<$($l, )* $to_parts, $($g, )* > where $($w: $c ),* { fn from(p: &$($l)* mut $Key<$($l, )* $from_parts, $($g, )* >) -> Self { convert_ref!(p) } } } } // Likewise, but using TryFrom. macro_rules! p_try { ( <$from_parts:ty> -> <$to_parts:ty>) => { impl<$($l, )* $($g, )* > TryFrom<$Key<$($l, )* $from_parts, $($g, )* >> for $Key<$($l, )* $to_parts, $($g, )* > where $($w: $c ),* { type Error = anyhow::Error; fn try_from(p: $Key<$($l, )* $from_parts, $($g, )* >) -> Result<Self> { p.parts_into_secret() } } impl<$($l, )* $($g, )* > TryFrom<&$($l)* $Key<$($l, )* $from_parts, $($g, )* >> for &$($l)* $Key<$($l, )* $to_parts, $($g, )* > where $($w: $c ),* { type Error = anyhow::Error; fn try_from(p: &$($l)* $Key<$($l, )* $from_parts, $($g, )* >) -> Result<Self> { if p.has_secret() { Ok(convert_ref!(p)) } else { Err(Error::InvalidArgument("No secret key".into()) .into()) } } } impl<$($l, )* $($g, )* > TryFrom<&$($l)* mut $Key<$($l, )* $from_parts, $($g, )* >> for &$($l)* mut $Key<$($l, )* $to_parts, $($g, )* > where $($w: $c ),* { type Error = anyhow::Error; fn try_from(p: &$($l)* mut $Key<$($l, )* $from_parts, $($g, )* >) -> Result<Self> { if p.has_secret() { Ok(convert_ref!(p)) } else { Err(Error::InvalidArgument("No secret key".into()) .into()) } } } } } p_try!(<PublicParts> -> <SecretParts>); p!(<PublicParts> -> <UnspecifiedParts>); p!(<SecretParts> -> <PublicParts>); p!(<SecretParts> -> <UnspecifiedParts>); p!(<UnspecifiedParts> -> <PublicParts>); p_try!(<UnspecifiedParts> -> <SecretParts>); impl<$($l, )* P, $($g, )*> $Key<$($l, )* P, $($g, )*> where P: KeyParts, $($w: $c ),* { /// Changes the key's parts tag to `PublicParts`. pub fn parts_into_public(self) -> $Key<$($l, )* PublicParts, $($g, )*> { // Ideally, we'd use self.into() to do the actually // conversion. But, because P is not concrete, we get the // following error: // // error[E0277]: the trait bound `packet::Key<packet::key::PublicParts, R>: std::convert::From<packet::Key<P, R>>` is not satisfied // --> openpgp/src/packet/key.rs:401:18 // | // 401 | self.into() // | ^^^^ the trait `std::convert::From<packet::Key<P, R>>` is not implemented for `packet::Key<packet::key::PublicParts, R>` // | // = help: consider adding a `where packet::Key<packet::key::PublicParts, R>: std::convert::From<packet::Key<P, R>>` bound // = note: required because of the requirements on the impl of `std::convert::Into<packet::Key<packet::key::PublicParts, R>>` for `packet::Key<P, R>` // // But we can't implement implement `From<Key<P, R>>` for // `Key<PublicParts, R>`, because that conflicts with a // standard conversion! (See the comment for the `p` // macro above.) // // Adding the trait bound is annoying, because then we'd // have to add it everywhere that we use into. convert!(self) } /// Changes the key's parts tag to `PublicParts`. pub fn parts_as_public(&$($l)* self) -> &$($l)* $Key<$($l, )* PublicParts, $($g, )*> { convert_ref!(self) } /// Changes the key's parts tag to `PublicParts`. pub fn parts_as_public_mut(&$($l)* mut self) -> &$($l)* mut $Key<$($l, )* PublicParts, $($g, )*> { convert_ref!(self) } /// Changes the key's parts tag to `SecretParts`. pub fn parts_into_secret(self) -> Result<$Key<$($l, )* SecretParts, $($g, )*>> { if self.has_secret() { Ok(convert!(self)) } else { Err(Error::InvalidArgument("No secret key".into()).into()) } } /// Changes the key's parts tag to `SecretParts`. pub fn parts_as_secret(&$($l)* self) -> Result<&$($l)* $Key<$($l, )* SecretParts, $($g, )*>> { if self.has_secret() { Ok(convert_ref!(self)) } else { Err(Error::InvalidArgument("No secret key".into()).into()) } } /// Changes the key's parts tag to `SecretParts`. pub fn parts_as_secret_mut(&$($l)* mut self) -> Result<&$($l)* mut $Key<$($l, )* SecretParts, $($g, )*>> { if self.has_secret() { Ok(convert_ref!(self)) } else { Err(Error::InvalidArgument("No secret key".into()).into()) } } /// Changes the key's parts tag to `UnspecifiedParts`. pub fn parts_into_unspecified(self) -> $Key<$($l, )* UnspecifiedParts, $($g, )*> { convert!(self) } /// Changes the key's parts tag to `UnspecifiedParts`. pub fn parts_as_unspecified(&$($l)* self) -> &$($l)* $Key<$($l, )* UnspecifiedParts, $($g, )*> { convert_ref!(self) } /// Changes the key's parts tag to `UnspecifiedParts`. pub fn parts_as_unspecified_mut(&$($l)* mut self) -> &$($l)* mut $Key<$($l, )* UnspecifiedParts, $($g, )*> { convert_ref!(self) } } } } macro_rules! create_role_conversions { ( $Key:ident<$( $l:lifetime ),*> ) => { // Convert between two KeyRoles for a constant KeyParts. See // the comment for the p macro above. macro_rules! r { ( <$from_role:ty> -> <$to_role:ty>) => { impl<$($l, )* P> From<$Key<$($l, )* P, $from_role>> for $Key<$($l, )* P, $to_role> where P: KeyParts { fn from(p: $Key<$($l, )* P, $from_role>) -> Self { let mut k: Self = convert!(p); k.set_role(<$to_role>::role()); k } } impl<$($l, )* P> From<&$($l)* $Key<$($l, )* P, $from_role>> for &$($l)* $Key<$($l, )* P, $to_role> where P: KeyParts { fn from(p: &$($l)* $Key<$($l, )* P, $from_role>) -> Self { convert_ref!(p) } } impl<$($l, )* P> From<&$($l)* mut $Key<$($l, )* P, $from_role>> for &$($l)* mut $Key<$($l, )* P, $to_role> where P: KeyParts { fn from(p: &$($l)* mut $Key<$($l, )* P, $from_role>) -> Self { convert_ref!(p) } } } } r!(<PrimaryRole> -> <SubordinateRole>); r!(<PrimaryRole> -> <UnspecifiedRole>); r!(<SubordinateRole> -> <PrimaryRole>); r!(<SubordinateRole> -> <UnspecifiedRole>); r!(<UnspecifiedRole> -> <PrimaryRole>); r!(<UnspecifiedRole> -> <SubordinateRole>); } } macro_rules! create_conversions { ( $Key:ident<$( $l:lifetime ),*> ) => { create_part_conversions!($Key<$($l ),* ; R> where R: KeyRole); create_role_conversions!($Key<$($l ),* >); // We now handle converting both the part and the role at the same // time. macro_rules! f { ( <$from_parts:ty, $from_role:ty> -> <$to_parts:ty, $to_role:ty> ) => { impl<$($l ),*> From<$Key<$($l, )* $from_parts, $from_role>> for $Key<$($l, )* $to_parts, $to_role> { fn from(p: $Key<$($l, )* $from_parts, $from_role>) -> Self { let mut k: Self = convert!(p); k.set_role(<$to_role>::role()); k } } impl<$($l ),*> From<&$($l)* $Key<$($l, )* $from_parts, $from_role>> for &$($l)* $Key<$($l, )* $to_parts, $to_role> { fn from(p: &$($l)* $Key<$from_parts, $from_role>) -> Self { convert_ref!(p) } } impl<$($l ),*> From<&$($l)* mut $Key<$($l, )* $from_parts, $from_role>> for &$($l)* mut $Key<$($l, )* $to_parts, $to_role> { fn from(p: &$($l)* mut $Key<$from_parts, $from_role>) -> Self { convert_ref!(p) } } } } macro_rules! f_try { ( <$from_parts:ty, $from_role:ty> -> <SecretParts, $to_role:ty> ) => { impl<$($l ),*> TryFrom<$Key<$($l, )* $from_parts, $from_role>> for $Key<$($l, )* SecretParts, $to_role> { type Error = anyhow::Error; fn try_from(p: $Key<$($l, )* $from_parts, $from_role>) -> Result<Self> { // First, just change the role. let mut k: $Key<$($l, )* $from_parts, $to_role> = p.into(); k.set_role(<$to_role>::role()); // Now change the parts. k.try_into() } } impl<$($l ),*> TryFrom<&$($l)* $Key<$($l, )* $from_parts, $from_role>> for &$($l)* $Key<$($l, )* SecretParts, $to_role> { type Error = anyhow::Error; fn try_from(p: &$($l)* $Key<$($l, )* $from_parts, $from_role>) -> Result<Self> { // First, just change the role. let k: &$($l)* $Key<$($l, )* $from_parts, $to_role> = p.into(); // Now change the parts. k.try_into() } } impl<$($l ),*> TryFrom<&$($l)* mut $Key<$($l, )* $from_parts, $from_role>> for &$($l)* mut $Key<$($l, )* SecretParts, $to_role> { type Error = anyhow::Error; fn try_from(p: &$($l)* mut $Key<$($l, )* $from_parts, $from_role>) -> Result<Self> { // First, just change the role. let k: &$($l)* mut $Key<$($l, )* $from_parts, $to_role> = p.into(); // Now change the parts. k.try_into() } } } } // The calls that are comment out are the calls for the // combinations where either the KeyParts or the KeyRole does not // change. //f!(<PublicParts, PrimaryRole> -> <PublicParts, PrimaryRole>); //f!(<PublicParts, PrimaryRole> -> <PublicParts, SubordinateRole>); //f!(<PublicParts, PrimaryRole> -> <PublicParts, UnspecifiedRole>); //f!(<PublicParts, PrimaryRole> -> <SecretParts, PrimaryRole>); f_try!(<PublicParts, PrimaryRole> -> <SecretParts, SubordinateRole>); f_try!(<PublicParts, PrimaryRole> -> <SecretParts, UnspecifiedRole>); //f!(<PublicParts, PrimaryRole> -> <UnspecifiedParts, PrimaryRole>); f!(<PublicParts, PrimaryRole> -> <UnspecifiedParts, SubordinateRole>); f!(<PublicParts, PrimaryRole> -> <UnspecifiedParts, UnspecifiedRole>); //f!(<PublicParts, SubordinateRole> -> <PublicParts, PrimaryRole>); //f!(<PublicParts, SubordinateRole> -> <PublicParts, SubordinateRole>); //f!(<PublicParts, SubordinateRole> -> <PublicParts, UnspecifiedRole>); f_try!(<PublicParts, SubordinateRole> -> <SecretParts, PrimaryRole>); //f!(<PublicParts, SubordinateRole> -> <SecretParts, SubordinateRole>); f_try!(<PublicParts, SubordinateRole> -> <SecretParts, UnspecifiedRole>); f!(<PublicParts, SubordinateRole> -> <UnspecifiedParts, PrimaryRole>); //f!(<PublicParts, SubordinateRole> -> <UnspecifiedParts, SubordinateRole>); f!(<PublicParts, SubordinateRole> -> <UnspecifiedParts, UnspecifiedRole>); //f!(<PublicParts, UnspecifiedRole> -> <PublicParts, PrimaryRole>); //f!(<PublicParts, UnspecifiedRole> -> <PublicParts, SubordinateRole>); //f!(<PublicParts, UnspecifiedRole> -> <PublicParts, UnspecifiedRole>); f_try!(<PublicParts, UnspecifiedRole> -> <SecretParts, PrimaryRole>); f_try!(<PublicParts, UnspecifiedRole> -> <SecretParts, SubordinateRole>); //f!(<PublicParts, UnspecifiedRole> -> <SecretParts, UnspecifiedRole>); f!(<PublicParts, UnspecifiedRole> -> <UnspecifiedParts, PrimaryRole>); f!(<PublicParts, UnspecifiedRole> -> <UnspecifiedParts, SubordinateRole>); //f!(<PublicParts, UnspecifiedRole> -> <UnspecifiedParts, UnspecifiedRole>); //f!(<SecretParts, PrimaryRole> -> <PublicParts, PrimaryRole>); f!(<SecretParts, PrimaryRole> -> <PublicParts, SubordinateRole>); f!(<SecretParts, PrimaryRole> -> <PublicParts, UnspecifiedRole>); //f!(<SecretParts, PrimaryRole> -> <SecretParts, PrimaryRole>); //f!(<SecretParts, PrimaryRole> -> <SecretParts, SubordinateRole>); //f!(<SecretParts, PrimaryRole> -> <SecretParts, UnspecifiedRole>); //f!(<SecretParts, PrimaryRole> -> <UnspecifiedParts, PrimaryRole>); f!(<SecretParts, PrimaryRole> -> <UnspecifiedParts, SubordinateRole>); f!(<SecretParts, PrimaryRole> -> <UnspecifiedParts, UnspecifiedRole>); f!(<SecretParts, SubordinateRole> -> <PublicParts, PrimaryRole>); //f!(<SecretParts, SubordinateRole> -> <PublicParts, SubordinateRole>); f!(<SecretParts, SubordinateRole> -> <PublicParts, UnspecifiedRole>); //f!(<SecretParts, SubordinateRole> -> <SecretParts, PrimaryRole>); //f!(<SecretParts, SubordinateRole> -> <SecretParts, SubordinateRole>); //f!(<SecretParts, SubordinateRole> -> <SecretParts, UnspecifiedRole>); f!(<SecretParts, SubordinateRole> -> <UnspecifiedParts, PrimaryRole>); //f!(<SecretParts, SubordinateRole> -> <UnspecifiedParts, SubordinateRole>); f!(<SecretParts, SubordinateRole> -> <UnspecifiedParts, UnspecifiedRole>); f!(<SecretParts, UnspecifiedRole> -> <PublicParts, PrimaryRole>); f!(<SecretParts, UnspecifiedRole> -> <PublicParts, SubordinateRole>); //f!(<SecretParts, UnspecifiedRole> -> <PublicParts, UnspecifiedRole>); //f!(<SecretParts, UnspecifiedRole> -> <SecretParts, PrimaryRole>); //f!(<SecretParts, UnspecifiedRole> -> <SecretParts, SubordinateRole>); //f!(<SecretParts, UnspecifiedRole> -> <SecretParts, UnspecifiedRole>); f!(<SecretParts, UnspecifiedRole> -> <UnspecifiedParts, PrimaryRole>); f!(<SecretParts, UnspecifiedRole> -> <UnspecifiedParts, SubordinateRole>); //f!(<SecretParts, UnspecifiedRole> -> <UnspecifiedParts, UnspecifiedRole>); //f!(<UnspecifiedParts, PrimaryRole> -> <PublicParts, PrimaryRole>); f!(<UnspecifiedParts, PrimaryRole> -> <PublicParts, SubordinateRole>); f!(<UnspecifiedParts, PrimaryRole> -> <PublicParts, UnspecifiedRole>); //f!(<UnspecifiedParts, PrimaryRole> -> <SecretParts, PrimaryRole>); f_try!(<UnspecifiedParts, PrimaryRole> -> <SecretParts, SubordinateRole>); f_try!(<UnspecifiedParts, PrimaryRole> -> <SecretParts, UnspecifiedRole>); //f!(<UnspecifiedParts, PrimaryRole> -> <UnspecifiedParts, PrimaryRole>); //f!(<UnspecifiedParts, PrimaryRole> -> <UnspecifiedParts, SubordinateRole>); //f!(<UnspecifiedParts, PrimaryRole> -> <UnspecifiedParts, UnspecifiedRole>); f!(<UnspecifiedParts, SubordinateRole> -> <PublicParts, PrimaryRole>); //f!(<UnspecifiedParts, SubordinateRole> -> <PublicParts, SubordinateRole>); f!(<UnspecifiedParts, SubordinateRole> -> <PublicParts, UnspecifiedRole>); f_try!(<UnspecifiedParts, SubordinateRole> -> <SecretParts, PrimaryRole>); //f!(<UnspecifiedParts, SubordinateRole> -> <SecretParts, SubordinateRole>); f_try!(<UnspecifiedParts, SubordinateRole> -> <SecretParts, UnspecifiedRole>); //f!(<UnspecifiedParts, SubordinateRole> -> <UnspecifiedParts, PrimaryRole>); //f!(<UnspecifiedParts, SubordinateRole> -> <UnspecifiedParts, SubordinateRole>); //f!(<UnspecifiedParts, SubordinateRole> -> <UnspecifiedParts, UnspecifiedRole>); f!(<UnspecifiedParts, UnspecifiedRole> -> <PublicParts, PrimaryRole>); f!(<UnspecifiedParts, UnspecifiedRole> -> <PublicParts, SubordinateRole>); //f!(<UnspecifiedParts, UnspecifiedRole> -> <PublicParts, UnspecifiedRole>); f_try!(<UnspecifiedParts, UnspecifiedRole> -> <SecretParts, PrimaryRole>); f_try!(<UnspecifiedParts, UnspecifiedRole> -> <SecretParts, SubordinateRole>); //f!(<UnspecifiedParts, UnspecifiedRole> -> <SecretParts, UnspecifiedRole>); //f!(<UnspecifiedParts, UnspecifiedRole> -> <UnspecifiedParts, PrimaryRole>); //f!(<UnspecifiedParts, UnspecifiedRole> -> <UnspecifiedParts, SubordinateRole>); //f!(<UnspecifiedParts, UnspecifiedRole> -> <UnspecifiedParts, UnspecifiedRole>); impl<$($l, )* P, R> $Key<$($l, )* P, R> where P: KeyParts, R: KeyRole { /// Changes the key's role tag to `PrimaryRole`. pub fn role_into_primary(self) -> $Key<$($l, )* P, PrimaryRole> { let mut k: $Key<$($l, )* P, PrimaryRole> = convert!(self); k.set_role(PrimaryRole::role()); k } /// Changes the key's role tag to `PrimaryRole`. pub fn role_as_primary(&$($l)* self) -> &$($l)* $Key<$($l, )* P, PrimaryRole> { convert_ref!(self) } /// Changes the key's role tag to `PrimaryRole`. pub fn role_as_primary_mut(&$($l)* mut self) -> &$($l)* mut $Key<$($l, )* P, PrimaryRole> { convert_ref!(self) } /// Changes the key's role tag to `SubordinateRole`. pub fn role_into_subordinate(self) -> $Key<$($l, )* P, SubordinateRole> { let mut k: $Key<$($l, )* P, SubordinateRole> = convert!(self); k.set_role(SubordinateRole::role()); k } /// Changes the key's role tag to `SubordinateRole`. pub fn role_as_subordinate(&$($l)* self) -> &$($l)* $Key<$($l, )* P, SubordinateRole> { convert_ref!(self) } /// Changes the key's role tag to `SubordinateRole`. pub fn role_as_subordinate_mut(&$($l)* mut self) -> &$($l)* mut $Key<$($l, )* P, SubordinateRole> { convert_ref!(self) } /// Changes the key's role tag to `UnspecifiedRole`. pub fn role_into_unspecified(self) -> $Key<$($l, )* P, UnspecifiedRole> { let mut k: $Key<$($l, )* P, UnspecifiedRole> = convert!(self); k.set_role(UnspecifiedRole::role()); k } /// Changes the key's role tag to `UnspecifiedRole`. pub fn role_as_unspecified(&$($l)* self) -> &$($l)* $Key<$($l, )* P, UnspecifiedRole> { convert_ref!(self) } /// Changes the key's role tag to `UnspecifiedRole`. pub fn role_as_unspecified_mut(&$($l)* mut self) -> &$($l)* mut $Key<$($l, )* P, UnspecifiedRole> { convert_ref!(self) } } } } create_conversions!(Key<>); create_conversions!(Key4<>); create_conversions!(Key6<>); create_conversions!(KeyBundle<>); // A hack, since the type has to be an ident, which means that we // can't use <>. type KeyComponentAmalgamation<'a, P, R> = ComponentAmalgamation<'a, Key<P, R>>; create_conversions!(KeyComponentAmalgamation<'a>); create_part_conversions!(PrimaryKeyAmalgamation<'a;>); create_part_conversions!(SubordinateKeyAmalgamation<'a;>); create_part_conversions!(ErasedKeyAmalgamation<'a;>); create_part_conversions!(ValidPrimaryKeyAmalgamation<'a;>); create_part_conversions!(ValidSubordinateKeyAmalgamation<'a;>); create_part_conversions!(ValidErasedKeyAmalgamation<'a;>); �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/src/packet/key/v4.rs����������������������������������������������������������0000644�0000000�0000000�00000177452�10461020230�0016504�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������//! OpenPGP v4 key packet. use std::fmt; use std::cmp::Ordering; use std::convert::TryInto; use std::hash::Hasher; use std::time; #[cfg(test)] use quickcheck::{Arbitrary, Gen}; use crate::Error; use crate::crypto::{mem::Protected, mpi, hash::Hash, KeyPair}; use crate::packet; use crate::packet::prelude::*; use crate::PublicKeyAlgorithm; use crate::SymmetricAlgorithm; use crate::HashAlgorithm; use crate::types::{ Curve, Timestamp, }; use crate::Result; use crate::crypto::Password; use crate::KeyID; use crate::Fingerprint; use crate::KeyHandle; use crate::packet::key::{ self, KeyParts, KeyRole, KeyRoleRT, PublicParts, SecretParts, UnspecifiedParts, }; use crate::policy::HashAlgoSecurity; /// Holds a public key, public subkey, private key or private subkey /// packet. /// /// Use [`Key4::generate_rsa`] or [`Key4::generate_ecc`] to create a /// new key. /// /// Existing key material can be turned into an OpenPGP key using /// [`Key4::new`], [`Key4::with_secret`], [`Key4::import_public_cv25519`], /// [`Key4::import_public_ed25519`], [`Key4::import_public_rsa`], /// [`Key4::import_secret_cv25519`], [`Key4::import_secret_ed25519`], /// and [`Key4::import_secret_rsa`]. /// /// Whether you create a new key or import existing key material, you /// still need to create a binding signature, and, for signing keys, a /// back signature before integrating the key into a certificate. /// /// Normally, you won't directly use `Key4`, but [`Key`], which is a /// relatively thin wrapper around `Key4`. /// /// See [Section 5.5 of RFC 9580] and [the documentation for `Key`] /// for more details. /// /// [Section 5.5 of RFC 9580]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.5 /// [the documentation for `Key`]: super::Key /// [`Key`]: super::Key pub struct Key4<P, R> where P: KeyParts, R: KeyRole { /// CTB packet header fields. pub(crate) common: packet::Common, /// When the key was created. pub(crate) creation_time: Timestamp, /// Public key algorithm of this signature. pk_algo: PublicKeyAlgorithm, /// Public key MPIs. mpis: mpi::PublicKey, /// Optional secret part of the key. pub(crate) secret: Option<SecretKeyMaterial>, pub(crate) fingerprint: std::sync::OnceLock<Fingerprint>, /// The key role tracked at run time. role: KeyRoleRT, p: std::marker::PhantomData<P>, r: std::marker::PhantomData<R>, } // derive(Clone) doesn't work as expected with generic type parameters // that don't implement clone: it adds a trait bound on Clone to P and // R in the Clone implementation. Happily, we don't need P or R to // implement Clone: they are just marker traits, which we can clone // manually. // // See: https://github.com/rust-lang/rust/issues/26925 impl<P, R> Clone for Key4<P, R> where P: KeyParts, R: KeyRole { fn clone(&self) -> Self { Key4 { common: self.common.clone(), creation_time: self.creation_time.clone(), pk_algo: self.pk_algo.clone(), mpis: self.mpis.clone(), secret: self.secret.clone(), fingerprint: self.fingerprint.get() .map(|fp| fp.clone().into()) .unwrap_or_default(), role: self.role, p: std::marker::PhantomData, r: std::marker::PhantomData, } } } assert_send_and_sync!(Key4<P, R> where P: KeyParts, R: KeyRole); impl<P: KeyParts, R: KeyRole> PartialEq for Key4<P, R> { fn eq(&self, other: &Key4<P, R>) -> bool { self.creation_time == other.creation_time && self.pk_algo == other.pk_algo && self.mpis == other.mpis && (! P::significant_secrets() || self.secret == other.secret) } } impl<P: KeyParts, R: KeyRole> Eq for Key4<P, R> {} impl<P: KeyParts, R: KeyRole> std::hash::Hash for Key4<P, R> { fn hash<H: std::hash::Hasher>(&self, state: &mut H) { std::hash::Hash::hash(&self.creation_time, state); std::hash::Hash::hash(&self.pk_algo, state); std::hash::Hash::hash(&self.mpis, state); if P::significant_secrets() { std::hash::Hash::hash(&self.secret, state); } } } impl<P, R> fmt::Debug for Key4<P, R> where P: key::KeyParts, R: key::KeyRole, { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.debug_struct("Key4") .field("fingerprint", &self.fingerprint()) .field("creation_time", &self.creation_time) .field("pk_algo", &self.pk_algo) .field("mpis", &self.mpis) .field("secret", &self.secret) .finish() } } impl<P, R> fmt::Display for Key4<P, R> where P: key::KeyParts, R: key::KeyRole, { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{}", self.fingerprint()) } } impl<P, R> Key4<P, R> where P: key::KeyParts, R: key::KeyRole, { /// The security requirements of the hash algorithm for /// self-signatures. /// /// A cryptographic hash algorithm usually has [three security /// properties]: pre-image resistance, second pre-image /// resistance, and collision resistance. If an attacker can /// influence the signed data, then the hash algorithm needs to /// have both second pre-image resistance, and collision /// resistance. If not, second pre-image resistance is /// sufficient. /// /// [three security properties]: https://en.wikipedia.org/wiki/Cryptographic_hash_function#Properties /// /// In general, an attacker may be able to influence third-party /// signatures. But direct key signatures, and binding signatures /// are only over data fully determined by signer. And, an /// attacker's control over self signatures over User IDs is /// limited due to their structure. /// /// These observations can be used to extend the life of a hash /// algorithm after its collision resistance has been partially /// compromised, but not completely broken. For more details, /// please refer to the documentation for [HashAlgoSecurity]. /// /// [HashAlgoSecurity]: crate::policy::HashAlgoSecurity pub fn hash_algo_security(&self) -> HashAlgoSecurity { HashAlgoSecurity::SecondPreImageResistance } /// Compares the public bits of two keys. /// /// This returns `Ordering::Equal` if the public MPIs, creation /// time, and algorithm of the two `Key4`s match. This does not /// consider the packets' encodings, packets' tags or their secret /// key material. pub fn public_cmp<PB, RB>(&self, b: &Key4<PB, RB>) -> Ordering where PB: key::KeyParts, RB: key::KeyRole, { self.mpis.cmp(&b.mpis) .then_with(|| self.creation_time.cmp(&b.creation_time)) .then_with(|| self.pk_algo.cmp(&b.pk_algo)) } /// Tests whether two keys are equal modulo their secret key /// material. /// /// This returns true if the public MPIs, creation time and /// algorithm of the two `Key4`s match. This does not consider /// the packets' encodings, packets' tags or their secret key /// material. pub fn public_eq<PB, RB>(&self, b: &Key4<PB, RB>) -> bool where PB: key::KeyParts, RB: key::KeyRole, { self.public_cmp(b) == Ordering::Equal } /// Hashes everything but any secret key material into state. /// /// This is an alternate implementation of [`Hash`], which never /// hashes the secret key material. /// /// [`Hash`]: std::hash::Hash pub fn public_hash<H>(&self, state: &mut H) where H: Hasher { use std::hash::Hash; self.common.hash(state); self.creation_time.hash(state); self.pk_algo.hash(state); Hash::hash(&self.mpis(), state); } } impl<P, R> Key4<P, R> where P: key::KeyParts, R: key::KeyRole, { /// Gets the `Key`'s creation time. pub fn creation_time(&self) -> time::SystemTime { self.creation_time.into() } /// Gets the `Key`'s creation time without converting it to a /// system time. /// /// This conversion may truncate the time to signed 32-bit time_t. pub(crate) fn creation_time_raw(&self) -> Timestamp { self.creation_time } /// Sets the `Key`'s creation time. /// /// `timestamp` is converted to OpenPGP's internal format, /// [`Timestamp`]: a 32-bit quantity containing the number of /// seconds since the Unix epoch. /// /// `timestamp` is silently rounded to match the internal /// resolution. An error is returned if `timestamp` is out of /// range. /// /// [`Timestamp`]: crate::types::Timestamp pub fn set_creation_time<T>(&mut self, timestamp: T) -> Result<time::SystemTime> where T: Into<time::SystemTime> { // Clear the cache. self.fingerprint = Default::default(); Ok(std::mem::replace(&mut self.creation_time, timestamp.into().try_into()?) .into()) } /// Gets the public key algorithm. pub fn pk_algo(&self) -> PublicKeyAlgorithm { self.pk_algo } /// Sets the public key algorithm. /// /// Returns the old public key algorithm. pub fn set_pk_algo(&mut self, pk_algo: PublicKeyAlgorithm) -> PublicKeyAlgorithm { // Clear the cache. self.fingerprint = Default::default(); ::std::mem::replace(&mut self.pk_algo, pk_algo) } /// Returns a reference to the `Key`'s MPIs. pub fn mpis(&self) -> &mpi::PublicKey { &self.mpis } /// Returns a mutable reference to the `Key`'s MPIs. pub fn mpis_mut(&mut self) -> &mut mpi::PublicKey { // Clear the cache. self.fingerprint = Default::default(); &mut self.mpis } /// Sets the `Key`'s MPIs. /// /// This function returns the old MPIs, if any. pub fn set_mpis(&mut self, mpis: mpi::PublicKey) -> mpi::PublicKey { // Clear the cache. self.fingerprint = Default::default(); ::std::mem::replace(&mut self.mpis, mpis) } /// Returns whether the `Key` contains secret key material. pub fn has_secret(&self) -> bool { self.secret.is_some() } /// Returns whether the `Key` contains unencrypted secret key /// material. /// /// This returns false if the `Key` doesn't contain any secret key /// material. pub fn has_unencrypted_secret(&self) -> bool { matches!(self.secret, Some(SecretKeyMaterial::Unencrypted { .. })) } /// Returns `Key`'s secret key material, if any. pub fn optional_secret(&self) -> Option<&SecretKeyMaterial> { self.secret.as_ref() } /// Computes and returns the `Key`'s `Fingerprint` and returns it as /// a `KeyHandle`. /// /// See [Section 5.5.4 of RFC 9580]. /// /// [Section 5.5.4 of RFC 9580]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.5.4 pub fn key_handle(&self) -> KeyHandle { self.fingerprint().into() } /// Computes and returns the `Key`'s `Fingerprint`. /// /// See [Key IDs and Fingerprints]. /// /// [Key IDs and Fingerprints]: https://www.rfc-editor.org/rfc/rfc9580.html#key-ids-fingerprints pub fn fingerprint(&self) -> Fingerprint { self.fingerprint.get_or_init(|| { let mut h = HashAlgorithm::SHA1.context() .expect("SHA1 is MTI for RFC4880") // v4 fingerprints are computed the same way a key is // hashed for v4 signatures. .for_signature(4); self.hash(&mut h).expect("v4 key hashing is infallible"); let mut digest = [0u8; 20]; let _ = h.digest(&mut digest); Fingerprint::V4(digest) }).clone() } /// Computes and returns the `Key`'s `Key ID`. /// /// See [Section 5.5.4 of RFC 9580]. /// /// [Section 5.5.4 of RFC 9580]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.5.4 pub fn keyid(&self) -> KeyID { self.fingerprint().into() } /// Creates an OpenPGP public key from the specified key material. /// /// This is an internal version for parse.rs that avoids going /// through SystemTime. pub(crate) fn make<T>(creation_time: T, pk_algo: PublicKeyAlgorithm, mpis: mpi::PublicKey, secret: Option<SecretKeyMaterial>) -> Result<Self> where T: Into<Timestamp>, { Ok(Key4 { common: Default::default(), creation_time: creation_time.into(), pk_algo, mpis, secret, fingerprint: Default::default(), role: R::role(), p: std::marker::PhantomData, r: std::marker::PhantomData, }) } pub(crate) fn role(&self) -> KeyRoleRT { self.role } pub(crate) fn set_role(&mut self, role: KeyRoleRT) { self.role = role; } } impl<R> Key4<key::PublicParts, R> where R: key::KeyRole, { /// Creates an OpenPGP public key from the specified key material. pub fn new<T>(creation_time: T, pk_algo: PublicKeyAlgorithm, mpis: mpi::PublicKey) -> Result<Self> where T: Into<time::SystemTime> { Ok(Key4 { common: Default::default(), creation_time: creation_time.into().try_into()?, pk_algo, mpis, secret: None, fingerprint: Default::default(), role: R::role(), p: std::marker::PhantomData, r: std::marker::PhantomData, }) } /// Creates an OpenPGP public key packet from existing X25519 key /// material. /// /// The ECDH key will use hash algorithm `hash` and symmetric /// algorithm `sym`. If one or both are `None` secure defaults /// will be used. The key will have its creation date set to /// `ctime` or the current time if `None` is given. pub fn import_public_cv25519<H, S, T>(public_key: &[u8], hash: H, sym: S, ctime: T) -> Result<Self> where H: Into<Option<HashAlgorithm>>, S: Into<Option<SymmetricAlgorithm>>, T: Into<Option<time::SystemTime>> { let mut point = Vec::from(public_key); point.insert(0, 0x40); use crate::crypto::ecdh; Self::new( ctime.into().unwrap_or_else(crate::now), PublicKeyAlgorithm::ECDH, mpi::PublicKey::ECDH { curve: Curve::Cv25519, hash: hash.into().unwrap_or_else( || ecdh::default_ecdh_kdf_hash(&Curve::Cv25519)), sym: sym.into().unwrap_or_else( || ecdh::default_ecdh_kek_cipher(&Curve::Cv25519)), q: mpi::MPI::new(&point), }) } /// Creates an OpenPGP public key packet from existing Ed25519 key /// material. /// /// The key will have its creation date set to `ctime` or the /// current time if `None` is given. pub fn import_public_ed25519<T>(public_key: &[u8], ctime: T) -> Result<Self> where T: Into<Option<time::SystemTime>> { let mut point = Vec::from(public_key); point.insert(0, 0x40); Self::new( ctime.into().unwrap_or_else(crate::now), PublicKeyAlgorithm::EdDSA, mpi::PublicKey::EdDSA { curve: Curve::Ed25519, q: mpi::MPI::new(&point), }) } /// Creates an OpenPGP public key packet from existing RSA key /// material. /// /// The RSA key will use the public exponent `e` and the modulo /// `n`. The key will have its creation date set to `ctime` or the /// current time if `None` is given. pub fn import_public_rsa<T>(e: &[u8], n: &[u8], ctime: T) -> Result<Self> where T: Into<Option<time::SystemTime>> { Self::new( ctime.into().unwrap_or_else(crate::now), PublicKeyAlgorithm::RSAEncryptSign, mpi::PublicKey::RSA { e: mpi::MPI::new(e), n: mpi::MPI::new(n), }) } } impl<R> Key4<SecretParts, R> where R: key::KeyRole, { /// Creates an OpenPGP key packet from the specified secret key /// material. pub fn with_secret<T>(creation_time: T, pk_algo: PublicKeyAlgorithm, mpis: mpi::PublicKey, secret: SecretKeyMaterial) -> Result<Self> where T: Into<time::SystemTime> { Ok(Key4 { common: Default::default(), creation_time: creation_time.into().try_into()?, pk_algo, mpis, secret: Some(secret), fingerprint: Default::default(), role: R::role(), p: std::marker::PhantomData, r: std::marker::PhantomData, }) } /// Creates a new OpenPGP secret key packet for an existing X25519 key. /// /// The ECDH key will use hash algorithm `hash` and symmetric /// algorithm `sym`. If one or both are `None` secure defaults /// will be used. The key will have its creation date set to /// `ctime` or the current time if `None` is given. /// /// The given `private_key` is expected to be in the native X25519 /// representation, i.e. as opaque byte string of length 32. It /// is transformed into OpenPGP's representation during import. pub fn import_secret_cv25519<H, S, T>(private_key: &[u8], hash: H, sym: S, ctime: T) -> Result<Self> where H: Into<Option<HashAlgorithm>>, S: Into<Option<SymmetricAlgorithm>>, T: Into<Option<std::time::SystemTime>> { use crate::crypto::backend::{Backend, interface::Asymmetric}; let mut private_key = Protected::from(private_key); let public_key = Backend::x25519_derive_public(&private_key)?; // Clamp the X25519 secret key scalar. // // X25519 does the clamping implicitly, but OpenPGP's ECDH // over Curve25519 requires the secret to be clamped. To // increase compatibility with OpenPGP implementations that do // not implicitly clamp the secrets before use, we do that // before we store the secrets in OpenPGP data structures. Backend::x25519_clamp_secret(&mut private_key); // Reverse the scalar. // // X25519 stores the secret as opaque byte string representing // a little-endian scalar. OpenPGP's ECDH over Curve25519 on // the other hand stores it as big-endian scalar, as was // customary in OpenPGP. See // https://lists.gnupg.org/pipermail/gnupg-devel/2018-February/033437.html. private_key.reverse(); use crate::crypto::ecdh; Self::with_secret( ctime.into().unwrap_or_else(crate::now), PublicKeyAlgorithm::ECDH, mpi::PublicKey::ECDH { curve: Curve::Cv25519, hash: hash.into().unwrap_or_else( || ecdh::default_ecdh_kdf_hash(&Curve::Cv25519)), sym: sym.into().unwrap_or_else( || ecdh::default_ecdh_kek_cipher(&Curve::Cv25519)), q: mpi::MPI::new_compressed_point(&public_key), }, mpi::SecretKeyMaterial::ECDH { scalar: private_key.into(), }.into()) } /// Creates a new OpenPGP secret key packet for an existing Ed25519 key. /// /// The key will have its creation date set to `ctime` or the current /// time if `None` is given. pub fn import_secret_ed25519<T>(private_key: &[u8], ctime: T) -> Result<Self> where T: Into<Option<time::SystemTime>> { use crate::crypto::backend::{Backend, interface::Asymmetric}; let private_key = Protected::from(private_key); let public_key = Backend::ed25519_derive_public(&private_key)?; Self::with_secret( ctime.into().unwrap_or_else(crate::now), PublicKeyAlgorithm::EdDSA, mpi::PublicKey::EdDSA { curve: Curve::Ed25519, q: mpi::MPI::new_compressed_point(&public_key), }, mpi::SecretKeyMaterial::EdDSA { scalar: private_key.into(), }.into()) } /// Generates a new ECC key over `curve`. /// /// If `for_signing` is false a ECDH key, if it's true either a /// EdDSA or ECDSA key is generated. Giving `for_signing == true` and /// `curve == Cv25519` will produce an error. Likewise /// `for_signing == false` and `curve == Ed25519` will produce an error. pub fn generate_ecc(for_signing: bool, curve: Curve) -> Result<Self> { use crate::crypto::backend::{Backend, interface::Asymmetric}; let (pk_algo, public, secret) = match (curve, for_signing) { (Curve::Ed25519, true) => { let (secret, public) = Backend::ed25519_generate_key()?; ( PublicKeyAlgorithm::EdDSA, mpi::PublicKey::EdDSA { curve: Curve::Ed25519, q: mpi::MPI::new_compressed_point(&public), }, mpi::SecretKeyMaterial::EdDSA { scalar: secret.into(), }, ) }, (Curve::Cv25519, false) => { let (mut secret, public) = Backend::x25519_generate_key()?; // Clamp the X25519 secret key scalar. // // X25519 does the clamping implicitly, but OpenPGP's ECDH over // Curve25519 requires the secret to be clamped. To increase // compatibility with OpenPGP implementations that do not // implicitly clamp the secrets before use, we do that before we // store the secrets in OpenPGP data structures. Backend::x25519_clamp_secret(&mut secret); // Reverse the scalar. // // X25519 stores the secret as opaque byte string // representing a little-endian scalar. OpenPGP's // ECDH over Curve25519 on the other hand stores it as // big-endian scalar, as was customary in OpenPGP. // See // https://lists.gnupg.org/pipermail/gnupg-devel/2018-February/033437.html. secret.reverse(); ( PublicKeyAlgorithm::ECDH, mpi::PublicKey::ECDH { curve: Curve::Cv25519, q: mpi::MPI::new_compressed_point(&public), hash: crate::crypto::ecdh::default_ecdh_kdf_hash( &Curve::Cv25519), sym: crate::crypto::ecdh::default_ecdh_kek_cipher( &Curve::Cv25519), }, mpi::SecretKeyMaterial::ECDH { scalar: secret.into(), }, ) }, (curve, for_signing) => Self::generate_ecc_backend(for_signing, curve)?, }; Self::with_secret(crate::now(), pk_algo, public, secret.into()) } /// Generates a new DSA key with a public modulus of size `p_bits`. /// /// Note: In order to comply with FIPS 186-4, and to increase /// compatibility with implementations, you SHOULD only generate /// keys with moduli of size `2048` or `3072` bits. pub fn generate_dsa(p_bits: usize) -> Result<Self> { use crate::crypto::backend::{Backend, interface::Asymmetric}; let (p, q, g, y, x) = Backend::dsa_generate_key(p_bits)?; let public_mpis = mpi::PublicKey::DSA { p, q, g, y }; let private_mpis = mpi::SecretKeyMaterial::DSA { x }; Self::with_secret( crate::now(), #[allow(deprecated)] PublicKeyAlgorithm::DSA, public_mpis, private_mpis.into()) } /// Generates a new ElGamal key with a public modulus of size `p_bits`. /// /// Note: ElGamal is no longer well-supported in cryptographic /// libraries and should be avoided. pub fn generate_elgamal(p_bits: usize) -> Result<Self> { use crate::crypto::backend::{Backend, interface::Asymmetric}; let (p, g, y, x) = Backend::elgamal_generate_key(p_bits)?; let public_mpis = mpi::PublicKey::ElGamal { p, g, y }; let private_mpis = mpi::SecretKeyMaterial::ElGamal { x }; Self::with_secret( crate::now(), #[allow(deprecated)] PublicKeyAlgorithm::ElGamalEncrypt, public_mpis, private_mpis.into()) } /// Generates a new X25519 key. pub fn generate_x25519() -> Result<Self> { use crate::crypto::backend::{Backend, interface::Asymmetric}; let (private, public) = Backend::x25519_generate_key()?; Self::with_secret( crate::now(), PublicKeyAlgorithm::X25519, mpi::PublicKey::X25519 { u: public, }, mpi::SecretKeyMaterial::X25519 { x: private, }.into()) } /// Generates a new X448 key. pub fn generate_x448() -> Result<Self> { use crate::crypto::backend::{Backend, interface::Asymmetric}; let (private, public) = Backend::x448_generate_key()?; Self::with_secret( crate::now(), PublicKeyAlgorithm::X448, mpi::PublicKey::X448 { u: Box::new(public), }, mpi::SecretKeyMaterial::X448 { x: private, }.into()) } /// Generates a new Ed25519 key. pub fn generate_ed25519() -> Result<Self> { use crate::crypto::backend::{Backend, interface::Asymmetric}; let (private, public) = Backend::ed25519_generate_key()?; Self::with_secret( crate::now(), PublicKeyAlgorithm::Ed25519, mpi::PublicKey::Ed25519 { a: public, }, mpi::SecretKeyMaterial::Ed25519 { x: private, }.into()) } /// Generates a new Ed448 key. pub fn generate_ed448() -> Result<Self> { use crate::crypto::backend::{Backend, interface::Asymmetric}; let (private, public) = Backend::ed448_generate_key()?; Self::with_secret( crate::now(), PublicKeyAlgorithm::Ed448, mpi::PublicKey::Ed448 { a: Box::new(public), }, mpi::SecretKeyMaterial::Ed448 { x: private, }.into()) } /// Creates a new key pair from a secret `Key` with an unencrypted /// secret key. /// /// # Errors /// /// Fails if the secret key is encrypted. You can use /// [`Key::decrypt_secret`] to decrypt a key. pub fn into_keypair(self) -> Result<KeyPair> { let (key, secret) = self.take_secret(); let secret = match secret { SecretKeyMaterial::Unencrypted(secret) => secret, SecretKeyMaterial::Encrypted(_) => return Err(Error::InvalidArgument( "secret key material is encrypted".into()).into()), }; KeyPair::new(key.role_into_unspecified().into(), secret) } } macro_rules! impl_common_secret_functions { ($t: ident) => { /// Secret key material handling. impl<R> Key4<$t, R> where R: key::KeyRole, { /// Takes the `Key`'s `SecretKeyMaterial`, if any. pub fn take_secret(mut self) -> (Key4<PublicParts, R>, Option<SecretKeyMaterial>) { let old = std::mem::replace(&mut self.secret, None); (self.parts_into_public(), old) } /// Adds the secret key material to the `Key`, returning /// the old secret key material, if any. pub fn add_secret(mut self, secret: SecretKeyMaterial) -> (Key4<SecretParts, R>, Option<SecretKeyMaterial>) { let old = std::mem::replace(&mut self.secret, Some(secret)); (self.parts_into_secret().expect("secret just set"), old) } /// Takes the `Key`'s `SecretKeyMaterial`, if any. pub fn steal_secret(&mut self) -> Option<SecretKeyMaterial> { std::mem::replace(&mut self.secret, None) } } } } impl_common_secret_functions!(PublicParts); impl_common_secret_functions!(UnspecifiedParts); /// Secret key handling. impl<R> Key4<SecretParts, R> where R: key::KeyRole, { /// Gets the `Key`'s `SecretKeyMaterial`. pub fn secret(&self) -> &SecretKeyMaterial { self.secret.as_ref().expect("has secret") } /// Gets a mutable reference to the `Key`'s `SecretKeyMaterial`. pub fn secret_mut(&mut self) -> &mut SecretKeyMaterial { self.secret.as_mut().expect("has secret") } /// Takes the `Key`'s `SecretKeyMaterial`. pub fn take_secret(mut self) -> (Key4<PublicParts, R>, SecretKeyMaterial) { let old = std::mem::replace(&mut self.secret, None); (self.parts_into_public(), old.expect("Key<SecretParts, _> has a secret key material")) } /// Adds `SecretKeyMaterial` to the `Key`. /// /// This function returns the old secret key material, if any. pub fn add_secret(mut self, secret: SecretKeyMaterial) -> (Key4<SecretParts, R>, SecretKeyMaterial) { let old = std::mem::replace(&mut self.secret, Some(secret)); (self.parts_into_secret().expect("secret just set"), old.expect("Key<SecretParts, _> has a secret key material")) } /// Decrypts the secret key material using `password`. /// /// In OpenPGP, secret key material can be [protected with a /// password]. The password is usually hardened using a [KDF]. /// /// Refer to the documentation of [`Key::decrypt_secret`] for /// details. /// /// This function returns an error if the secret key material is /// not encrypted or the password is incorrect. /// /// [protected with a password]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.5.3 /// [KDF]: https://www.rfc-editor.org/rfc/rfc9580.html#section-3.7 /// [`Key::decrypt_secret`]: super::Key::decrypt_secret() pub fn decrypt_secret(self, password: &Password) -> Result<Self> { let (key, mut secret) = self.take_secret(); let key = Key::V4(key); secret.decrypt_in_place(&key, password)?; let key = if let Key::V4(k) = key { k } else { unreachable!() }; Ok(key.add_secret(secret).0) } /// Encrypts the secret key material using `password`. /// /// In OpenPGP, secret key material can be [protected with a /// password]. The password is usually hardened using a [KDF]. /// /// Refer to the documentation of [`Key::encrypt_secret`] for /// details. /// /// This returns an error if the secret key material is already /// encrypted. /// /// [protected with a password]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.5.3 /// [KDF]: https://www.rfc-editor.org/rfc/rfc9580.html#section-3.7 /// [`Key::encrypt_secret`]: super::Key::encrypt_secret() pub fn encrypt_secret(self, password: &Password) -> Result<Key4<SecretParts, R>> { let (key, mut secret) = self.take_secret(); let key = Key::V4(key); secret.encrypt_in_place(&key, password)?; let key = if let Key::V4(k) = key { k } else { unreachable!() }; Ok(key.add_secret(secret).0) } } impl<P, R> From<Key4<P, R>> for super::Key<P, R> where P: key::KeyParts, R: key::KeyRole, { fn from(p: Key4<P, R>) -> Self { super::Key::V4(p) } } #[cfg(test)] use crate::packet::key::{ PrimaryRole, SubordinateRole, UnspecifiedRole, }; #[cfg(test)] impl Arbitrary for Key4<PublicParts, PrimaryRole> { fn arbitrary(g: &mut Gen) -> Self { Key4::<PublicParts, UnspecifiedRole>::arbitrary(g).into() } } #[cfg(test)] impl Arbitrary for Key4<PublicParts, SubordinateRole> { fn arbitrary(g: &mut Gen) -> Self { Key4::<PublicParts, UnspecifiedRole>::arbitrary(g).into() } } #[cfg(test)] impl Arbitrary for Key4<PublicParts, UnspecifiedRole> { fn arbitrary(g: &mut Gen) -> Self { let mpis = mpi::PublicKey::arbitrary(g); Key4 { common: Arbitrary::arbitrary(g), creation_time: Arbitrary::arbitrary(g), pk_algo: mpis.algo() .expect("mpi::PublicKey::arbitrary only uses known algos"), mpis, secret: None, fingerprint: Default::default(), role: UnspecifiedRole::role(), p: std::marker::PhantomData, r: std::marker::PhantomData, } } } #[cfg(test)] impl Arbitrary for Key4<SecretParts, PrimaryRole> { fn arbitrary(g: &mut Gen) -> Self { Key4::<SecretParts, PrimaryRole>::arbitrary_secret_key(g) } } #[cfg(test)] impl Arbitrary for Key4<SecretParts, SubordinateRole> { fn arbitrary(g: &mut Gen) -> Self { Key4::<SecretParts, SubordinateRole>::arbitrary_secret_key(g) } } #[cfg(test)] impl<R> Key4<SecretParts, R> where R: KeyRole, Key4::<PublicParts, R>: Arbitrary, { fn arbitrary_secret_key(g: &mut Gen) -> Self { let key = Key::V4(Key4::<PublicParts, R>::arbitrary(g)); let mut secret: SecretKeyMaterial = mpi::SecretKeyMaterial::arbitrary_for(g, key.pk_algo()) .expect("only known algos used") .into(); if <bool>::arbitrary(g) { secret.encrypt_in_place(&key, &Password::from(Vec::arbitrary(g))) .unwrap(); } let key = if let Key::V4(k) = key { k } else { unreachable!() }; Key4::<PublicParts, R>::add_secret(key, secret).0 } } #[cfg(test)] mod tests { use std::time::Duration; use std::time::UNIX_EPOCH; use crate::crypto::S2K; use crate::packet::Key; use crate::Cert; use crate::packet::pkesk::PKESK3; use crate::packet::key; use crate::packet::key::SecretKeyMaterial; use crate::packet::Packet; use super::*; use crate::PacketPile; use crate::serialize::Serialize; use crate::parse::Parse; #[test] fn encrypted_rsa_key() { let cert = Cert::from_bytes( crate::tests::key("testy-new-encrypted-with-123.pgp")).unwrap(); let key = cert.primary_key().key().clone(); let (key, secret) = key.take_secret(); let mut secret = secret.unwrap(); assert!(secret.is_encrypted()); secret.decrypt_in_place(&key, &"123".into()).unwrap(); assert!(!secret.is_encrypted()); let (pair, _) = key.add_secret(secret); assert!(pair.has_unencrypted_secret()); match pair.secret() { SecretKeyMaterial::Unencrypted(ref u) => u.map(|mpis| match mpis { mpi::SecretKeyMaterial::RSA { .. } => (), _ => panic!(), }), _ => panic!(), } } #[test] fn primary_key_encrypt_decrypt() -> Result<()> { key_encrypt_decrypt::<PrimaryRole>() } #[test] fn subkey_encrypt_decrypt() -> Result<()> { key_encrypt_decrypt::<SubordinateRole>() } fn key_encrypt_decrypt<R>() -> Result<()> where R: KeyRole + PartialEq, { let mut g = quickcheck::Gen::new(256); let p: Password = Vec::<u8>::arbitrary(&mut g).into(); let check = |key: Key4<SecretParts, R>| -> Result<()> { let key: Key<_, _> = key.into(); let encrypted = key.clone().encrypt_secret(&p)?; let decrypted = encrypted.decrypt_secret(&p)?; assert_eq!(key, decrypted); Ok(()) }; use crate::types::Curve::*; for curve in vec![NistP256, NistP384, NistP521, Ed25519] { if ! curve.is_supported() { eprintln!("Skipping unsupported {}", curve); continue; } let key: Key4<_, R> = Key4::generate_ecc(true, curve.clone())?; check(key)?; } for bits in vec![2048, 3072] { if ! PublicKeyAlgorithm::RSAEncryptSign.is_supported() { eprintln!("Skipping unsupported RSA"); continue; } let key: Key4<_, R> = Key4::generate_rsa(bits)?; check(key)?; } Ok(()) } #[test] fn eq() { use crate::types::Curve::*; for curve in vec![NistP256, NistP384, NistP521] { if ! curve.is_supported() { eprintln!("Skipping unsupported {}", curve); continue; } let sign_key : Key4<_, key::UnspecifiedRole> = Key4::generate_ecc(true, curve.clone()).unwrap(); let enc_key : Key4<_, key::UnspecifiedRole> = Key4::generate_ecc(false, curve).unwrap(); let sign_clone = sign_key.clone(); let enc_clone = enc_key.clone(); assert_eq!(sign_key, sign_clone); assert_eq!(enc_key, enc_clone); } for bits in vec![1024, 2048, 3072, 4096] { if ! PublicKeyAlgorithm::RSAEncryptSign.is_supported() { eprintln!("Skipping unsupported RSA"); continue; } let key : Key4<_, key::UnspecifiedRole> = Key4::generate_rsa(bits).unwrap(); let clone = key.clone(); assert_eq!(key, clone); } } #[test] fn generate_roundtrip() { use crate::types::Curve::*; let keys = vec![NistP256, NistP384, NistP521].into_iter().flat_map(|cv| { if ! cv.is_supported() { eprintln!("Skipping unsupported {}", cv); return Vec::new(); } let sign_key : Key4<key::SecretParts, key::PrimaryRole> = Key4::generate_ecc(true, cv.clone()).unwrap(); let enc_key = Key4::generate_ecc(false, cv).unwrap(); vec![sign_key, enc_key] }).chain(vec![1024, 2048, 3072, 4096].into_iter().filter_map(|b| { Key4::generate_rsa(b).ok() })); for key in keys { let mut b = Vec::new(); Packet::SecretKey(key.clone().into()).serialize(&mut b).unwrap(); let pp = PacketPile::from_bytes(&b).unwrap(); if let Some(Packet::SecretKey(Key::V4(ref parsed_key))) = pp.path_ref(&[0]) { assert_eq!(key.creation_time(), parsed_key.creation_time()); assert_eq!(key.pk_algo(), parsed_key.pk_algo()); assert_eq!(key.mpis(), parsed_key.mpis()); assert_eq!(key.secret(), parsed_key.secret()); assert_eq!(&key, parsed_key); } else { panic!("bad packet: {:?}", pp.path_ref(&[0])); } let mut b = Vec::new(); let pk4 : Key4<PublicParts, PrimaryRole> = key.clone().into(); Packet::PublicKey(pk4.into()).serialize(&mut b).unwrap(); let pp = PacketPile::from_bytes(&b).unwrap(); if let Some(Packet::PublicKey(Key::V4(ref parsed_key))) = pp.path_ref(&[0]) { assert!(! parsed_key.has_secret()); let key = key.take_secret().0; assert_eq!(&key, parsed_key); } else { panic!("bad packet: {:?}", pp.path_ref(&[0])); } } } #[test] fn encryption_roundtrip() { use crate::crypto::SessionKey; use crate::types::Curve::*; let keys = vec![NistP256, NistP384, NistP521].into_iter() .filter_map(|cv| { Key4::generate_ecc(false, cv).ok() }).chain(vec![1024, 2048, 3072, 4096].into_iter().filter_map(|b| { Key4::generate_rsa(b).ok() })).chain(vec![1024, 2048, 3072, 4096].into_iter().filter_map(|b| { Key4::generate_elgamal(b).ok() })); for key in keys.into_iter() { let key: Key<key::SecretParts, key::UnspecifiedRole> = key.into(); let mut keypair = key.clone().into_keypair().unwrap(); let cipher = SymmetricAlgorithm::AES256; let sk = SessionKey::new(cipher.key_size().unwrap()).unwrap(); let pkesk = PKESK3::for_recipient(cipher, &sk, &key).unwrap(); let (cipher_, sk_) = pkesk.decrypt(&mut keypair, None) .expect("keypair should be able to decrypt PKESK"); assert_eq!(cipher, cipher_); assert_eq!(sk, sk_); let (cipher_, sk_) = pkesk.decrypt(&mut keypair, Some(cipher)).unwrap(); assert_eq!(cipher, cipher_); assert_eq!(sk, sk_); } } #[test] fn signature_roundtrip() { use crate::types::{Curve::*, SignatureType}; let keys = vec![NistP256, NistP384, NistP521].into_iter() .filter_map(|cv| { Key4::generate_ecc(true, cv).ok() }).chain(vec![1024, 2048, 3072, 4096].into_iter().filter_map(|b| { Key4::generate_rsa(b).ok() })).chain(vec![1024, 2048, 3072].into_iter().filter_map(|b| { Key4::generate_dsa(b).ok() })); for key in keys.into_iter() { let key: Key<key::SecretParts, key::UnspecifiedRole> = key.into(); let mut keypair = key.clone().into_keypair().unwrap(); let hash = HashAlgorithm::default(); // Sign. let ctx = hash.context().unwrap().for_signature(key.version()); let sig = SignatureBuilder::new(SignatureType::Binary) .sign_hash(&mut keypair, ctx).unwrap(); // Verify. let ctx = hash.context().unwrap().for_signature(key.version()); sig.verify_hash(&key, ctx).unwrap(); } } #[test] fn secret_encryption_roundtrip() { use crate::types::Curve::*; use crate::types::SymmetricAlgorithm::*; use crate::types::AEADAlgorithm::*; let keys = vec![NistP256, NistP384, NistP521].into_iter() .filter_map(|cv| -> Option<Key<key::SecretParts, key::PrimaryRole>> { Key4::generate_ecc(false, cv).map(Into::into).ok() }).chain(vec![1024, 2048, 3072, 4096].into_iter().filter_map(|b| { Key4::generate_rsa(b).map(Into::into).ok() })); for key in keys { for (symm, aead) in [(AES128, None), (AES128, Some(OCB)), (AES256, Some(EAX))] { if ! aead.map(|a| a.is_supported()).unwrap_or(true) { continue; } assert!(! key.secret().is_encrypted()); let password = Password::from("foobarbaz"); let mut encrypted_key = key.clone(); encrypted_key.secret_mut() .encrypt_in_place_with(&key, S2K::default(), symm, aead, &password).unwrap(); assert!(encrypted_key.secret().is_encrypted()); encrypted_key.secret_mut() .decrypt_in_place(&key, &password).unwrap(); assert!(! key.secret().is_encrypted()); assert_eq!(key, encrypted_key); assert_eq!(key.secret(), encrypted_key.secret()); } } } #[test] fn import_cv25519() { use crate::crypto::{ecdh, mem, SessionKey}; use self::mpi::{MPI, Ciphertext}; // X25519 key let ctime = time::UNIX_EPOCH + time::Duration::new(0x5c487129, 0); let public = b"\xed\x59\x0a\x15\x08\x95\xe9\x92\xd2\x2c\x14\x01\xb3\xe9\x3b\x7f\xff\xe6\x6f\x22\x65\xec\x69\xd9\xb8\xda\x24\x2c\x64\x84\x44\x11"; let key : Key<_, key::UnspecifiedRole> = Key4::import_public_cv25519(&public[..], HashAlgorithm::SHA256, SymmetricAlgorithm::AES128, ctime).unwrap().into(); // PKESK let eph_pubkey = MPI::new(&b"\x40\xda\x1c\x69\xc4\xe3\xb6\x9c\x6e\xd4\xc6\x69\x6c\x89\xc7\x09\xe9\xf8\x6a\xf1\xe3\x8d\xb6\xaa\xb5\xf7\x29\xae\xa6\xe7\xdd\xfe\x38"[..]); let ciphertext = Ciphertext::ECDH{ e: eph_pubkey.clone(), key: Vec::from(&b"\x45\x8b\xd8\x4d\x88\xb3\xd2\x16\xb6\xc2\x3b\x99\x33\xd1\x23\x4b\x10\x15\x8e\x04\x16\xc5\x7c\x94\x88\xf6\x63\xf2\x68\x37\x08\x66\xfd\x5a\x7b\x40\x58\x21\x6b\x2c\xc0\xf4\xdc\x91\xd3\x48\xed\xc1"[..]).into_boxed_slice() }; let shared_sec: mem::Protected = b"\x44\x0C\x99\x27\xF7\xD6\x1E\xAD\xD1\x1E\x9E\xC8\x22\x2C\x5D\x43\xCE\xB0\xE5\x45\x94\xEC\xAF\x67\xD9\x35\x1D\xA1\xA3\xA8\x10\x0B"[..].into(); // Session key let dek = b"\x09\x0D\xDC\x40\xC5\x71\x51\x88\xAC\xBD\x45\x56\xD4\x2A\xDF\x77\xCD\xF4\x82\xA2\x1B\x8F\x2E\x48\x3B\xCA\xBF\xD3\xE8\x6D\x0A\x7C\xDF\x10\xe6"; let sk = SessionKey::from(Vec::from(&dek[..])); // Expected let got_enc = ecdh::encrypt_wrap(&key.parts_into_public(), &sk, eph_pubkey, &shared_sec) .unwrap(); assert_eq!(ciphertext, got_enc); } #[test] fn import_cv25519_sec() -> Result<()> { use self::mpi::{MPI, Ciphertext}; // X25519 key let ctime = time::UNIX_EPOCH + time::Duration::new(0x5c487129, 0); let public = b"\xed\x59\x0a\x15\x08\x95\xe9\x92\xd2\x2c\x14\x01\xb3\xe9\x3b\x7f\xff\xe6\x6f\x22\x65\xec\x69\xd9\xb8\xda\x24\x2c\x64\x84\x44\x11"; let secret = b"\xa0\x27\x13\x99\xc9\xe3\x2e\xd2\x47\xf6\xd6\x63\x9d\xe6\xec\xcb\x57\x0b\x92\xbb\x17\xfe\xb8\xf1\xc4\x1f\x06\x7c\x55\xfc\xdd\x58"; let key: Key<_, UnspecifiedRole> = Key4::import_secret_cv25519(&secret[..], HashAlgorithm::SHA256, SymmetricAlgorithm::AES128, ctime).unwrap().into(); match key.mpis() { self::mpi::PublicKey::ECDH{ ref q,.. } => assert_eq!(&q.value()[1..], &public[..]), _ => unreachable!(), } // PKESK let eph_pubkey: &[u8; 33] = b"\x40\xda\x1c\x69\xc4\xe3\xb6\x9c\x6e\xd4\xc6\x69\x6c\x89\xc7\x09\xe9\xf8\x6a\xf1\xe3\x8d\xb6\xaa\xb5\xf7\x29\xae\xa6\xe7\xdd\xfe\x38"; let ciphertext = Ciphertext::ECDH{ e: MPI::new(&eph_pubkey[..]), key: Vec::from(&b"\x45\x8b\xd8\x4d\x88\xb3\xd2\x16\xb6\xc2\x3b\x99\x33\xd1\x23\x4b\x10\x15\x8e\x04\x16\xc5\x7c\x94\x88\xf6\x63\xf2\x68\x37\x08\x66\xfd\x5a\x7b\x40\x58\x21\x6b\x2c\xc0\xf4\xdc\x91\xd3\x48\xed\xc1"[..]).into_boxed_slice() }; let pkesk = PKESK3::new(None, PublicKeyAlgorithm::ECDH, ciphertext)?; // Session key let dek = b"\x0D\xDC\x40\xC5\x71\x51\x88\xAC\xBD\x45\x56\xD4\x2A\xDF\x77\xCD\xF4\x82\xA2\x1B\x8F\x2E\x48\x3B\xCA\xBF\xD3\xE8\x6D\x0A\x7C\xDF"; let key = key.parts_into_secret().unwrap(); let mut keypair = key.into_keypair()?; let (sym, got_dek) = pkesk.decrypt(&mut keypair, None).unwrap(); assert_eq!(sym, SymmetricAlgorithm::AES256); assert_eq!(&dek[..], &got_dek[..]); Ok(()) } #[test] fn import_rsa() { use crate::crypto::SessionKey; use self::mpi::{MPI, Ciphertext}; // RSA key let ctime = time::UNIX_EPOCH + time::Duration::new(1548950502, 0); let d = b"\x14\xC4\x3A\x0C\x3A\x79\xA4\xF7\x63\x0D\x89\x93\x63\x8B\x56\x9C\x29\x2E\xCD\xCF\xBF\xB0\xEC\x66\x52\xC3\x70\x1B\x19\x21\x73\xDE\x8B\xAC\x0E\xF2\xE1\x28\x42\x66\x56\x55\x00\x3B\xFD\x50\xC4\x7C\xBC\x9D\xEB\x7D\xF4\x81\xFC\xC3\xBF\xF7\xFF\xD0\x41\x3E\x50\x3B\x5F\x5D\x5F\x56\x67\x5E\x00\xCE\xA4\x53\xB8\x59\xA0\x40\xC8\x96\x6D\x12\x09\x27\xBE\x1D\xF1\xC2\x68\xFC\xF0\x14\xD6\x52\x77\x07\xC8\x12\x36\x9C\x9A\x5C\xAF\x43\xCC\x95\x20\xBB\x0A\x44\x94\xDD\xB4\x4F\x45\x4E\x3A\x1A\x30\x0D\x66\x40\xAC\x68\xE8\xB0\xFD\xCD\x6C\x6B\x6C\xB5\xF7\xE4\x36\x95\xC2\x96\x98\xFD\xCA\x39\x6C\x1A\x2E\x55\xAD\xB6\xE0\xF8\x2C\xFF\xBC\xD3\x32\x15\x52\x39\xB3\x92\x35\xDB\x8B\x68\xAF\x2D\x4A\x6E\x64\xB8\x28\x63\xC4\x24\x94\x2D\xA9\xDB\x93\x56\xE3\xBC\xD0\xB6\x38\x84\x04\xA4\xC6\x18\x48\xFE\xB2\xF8\xE1\x60\x37\x52\x96\x41\xA5\x79\xF6\x3D\xB7\x2A\x71\x5B\x7A\x75\xBF\x7F\xA2\x5A\xC8\xA1\x38\xF2\x5A\xBD\x14\xFC\xAF\xB4\x54\x83\xA4\xBD\x49\xA2\x8B\x91\xB0\xE0\x4A\x1B\x21\x54\x07\x19\x70\x64\x7C\x3E\x9F\x8D\x8B\xE4\x70\xD1\xE7\xBE\x4E\x5C\xCE\xF1"; let p = b"\xC8\x32\xD1\x17\x41\x4D\x8F\x37\x09\x18\x32\x4C\x4C\xF4\xA2\x15\x27\x43\x3D\xBB\xB5\xF6\x1F\xCF\xD2\xE4\x43\x61\x07\x0E\x9E\x35\x1F\x0A\x5D\xFB\x3A\x45\x74\x61\x73\x73\x7B\x5F\x1F\x87\xFB\x54\x8D\xA8\x85\x3E\xB0\xB7\xC7\xF5\xC9\x13\x99\x8D\x40\xE6\xA6\xD0\x71\x3A\xE3\x2D\x4A\xC3\xA3\xFF\xF7\x72\x82\x14\x52\xA4\xBA\x63\x0E\x17\xCA\xCA\x18\xC4\x3A\x40\x79\xF1\x86\xB3\x10\x4B\x9F\xB2\xAE\x2E\x13\x38\x8D\x2C\xF9\x88\x4C\x25\x53\xEF\xF9\xD1\x8B\x1A\x7C\xE7\xF6\x4B\x73\x51\x31\xFA\x44\x1D\x36\x65\x71\xDA\xFC\x6F"; let q = b"\xCC\x30\xE9\xCC\xCB\x31\x28\xB5\x90\xFF\x06\x62\x42\x5B\x24\x0E\x00\xFE\xE2\x37\xC4\xAC\xBB\x3B\x8F\xF2\x0E\x3F\x78\xCF\x6B\x7C\xE8\x75\x57\x7C\x15\x9D\x1A\x66\xF2\x0A\xE5\xD3\x0B\xE7\x40\xF7\xE7\x00\xB6\x86\xB5\xD9\x20\x67\xE0\x4A\xC0\x90\xA4\x13\x4D\xC9\xB0\x12\xC5\xCD\x4C\xEB\xA1\x91\x2D\x43\x58\x6E\xB6\x75\xA0\x93\xF0\x5B\xC5\x31\xCA\xB7\xC6\x22\x0C\xD3\xEC\x84\xC5\x91\xA1\x5F\x2C\x8E\x07\x5D\xA1\x98\x67\xC5\x7A\x58\x16\x71\x3D\xED\x91\x03\x0D\xD4\x25\x07\x89\x9B\x33\x98\xA3\x70\xD9\xE7\xC8\x17\xA3\xD9"; let key: key::SecretKey = Key4::import_secret_rsa(&d[..], &p[..], &q[..], ctime) .unwrap().into(); // PKESK let c = b"\x8A\x1A\xD4\x82\x91\x6B\xBF\xA1\x65\xD3\x82\x8C\x97\xAB\xD0\x91\xE4\xB4\xC4\x9D\x08\xD8\x8B\xB7\xE6\x13\x3F\x6F\x52\x14\xED\xC4\x77\xB7\x31\x00\xC1\x43\xF9\x62\x53\xBF\x21\x21\x52\x74\x35\xD8\xC7\xA2\x11\x89\xA5\xD5\x21\x98\x6D\x3C\x9F\xF0\xED\xDB\xD7\x0F\xAC\x3C\x15\x25\x34\x52\xC7\x7C\x82\x07\x5A\x99\xC1\xC6\xF6\xF2\x6D\x46\xC8\x56\x59\xE7\xC6\x34\x0C\xCA\x37\x70\xB4\x97\xDA\x18\x14\xC4\x03\x0A\xCB\xE5\x0C\x41\x43\x61\xBA\x32\xB6\x9A\xF3\xDF\x0C\xB0\xCE\xBD\xFE\x72\x6C\xCC\xC1\xE8\xF0\x05\x97\x61\xEA\x30\x10\xB9\x43\xC4\x9A\x41\xED\x72\x27\xA4\xD5\xE7\x08\x41\x6C\x57\x80\xF3\x64\xF0\x45\x70\x27\x36\xBD\x64\x59\x74\xCF\xCD\x39\xE6\xEB\x7C\x62\xC8\x38\x23\xF8\x4C\xB7\x30\x9F\xF1\x40\x4A\xE9\x72\x66\x99\xF7\x2A\x47\x1C\xE7\x12\x20\x58\xBA\x87\x00\xB8\xFC\x54\xBC\xA5\x1D\x7D\x8B\x50\xA4\x4B\xB3\xD7\x44\xC7\x68\x5E\x2D\xBB\xE9\x6E\xC4\xD0\x31\xB0\xD0\xB6\x02\xD1\x74\x6B\xC9\x3D\x19\x32\x3B\xF1\x0E\x74\xF6\x12\x13\xE6\x40\x8F\xA6\x97\xAD\x83\xB0\x84\xD6\xD9\xE5\x25\x8E\x57\x0B\x7A\x7B\xD0\x5C\x29\x96\xED\x29\xED"; let ciphertext = Ciphertext::RSA{ c: MPI::new(&c[..]), }; let pkesk = PKESK3::new(Some(key.keyid()), PublicKeyAlgorithm::RSAEncryptSign, ciphertext).unwrap(); // Session key let dek = b"\xA5\x58\x3A\x04\x35\x8B\xC7\x3F\x4A\xEF\x0C\x5A\xEB\xED\x59\xCA\xFD\x96\xB5\x32\x23\x26\x0C\x91\x78\xD1\x31\x12\xF0\x41\x42\x9D"; let sk = SessionKey::from(Vec::from(&dek[..])); // Expected let mut decryptor = key.into_keypair().unwrap(); let got_sk = pkesk.decrypt(&mut decryptor, None).unwrap(); assert_eq!(got_sk.1, sk); } #[test] fn import_ed25519() { use crate::types::SignatureType; use crate::packet::signature::Signature4; use crate::packet::signature::subpacket::{ Subpacket, SubpacketValue, SubpacketArea}; // Ed25519 key let ctime = time::UNIX_EPOCH + time::Duration::new(1548249630, 0); let q = b"\x57\x15\x45\x1B\x68\xA5\x13\xA2\x20\x0F\x71\x9D\xE3\x05\x3B\xED\xA2\x21\xDE\x61\x5A\xF5\x67\x45\xBB\x97\x99\x43\x53\x59\x7C\x3F"; let key: key::PublicKey = Key4::import_public_ed25519(q, ctime).unwrap().into(); let mut hashed = SubpacketArea::default(); let mut unhashed = SubpacketArea::default(); let fpr = "D81A 5DC0 DEBF EE5F 9AC8 20EB 6769 5DB9 920D 4FAC" .parse().unwrap(); let kid = "6769 5DB9 920D 4FAC".parse().unwrap(); let ctime = 1549460479.into(); let r = b"\x5A\xF9\xC7\x42\x70\x24\x73\xFF\x7F\x27\xF9\x20\x9D\x20\x0F\xE3\x8F\x71\x3C\x5F\x97\xFD\x60\x80\x39\x29\xC2\x14\xFD\xC2\x4D\x70"; let s = b"\x6E\x68\x74\x11\x72\xF4\x9C\xE1\x99\x99\x1F\x67\xFC\x3A\x68\x33\xF9\x3F\x3A\xB9\x1A\xA5\x72\x4E\x78\xD4\x81\xCB\x7B\xA5\xE5\x0A"; hashed.add(Subpacket::new(SubpacketValue::IssuerFingerprint(fpr), false).unwrap()).unwrap(); hashed.add(Subpacket::new(SubpacketValue::SignatureCreationTime(ctime), false).unwrap()).unwrap(); unhashed.add(Subpacket::new(SubpacketValue::Issuer(kid), false).unwrap()).unwrap(); eprintln!("fpr: {}", key.fingerprint()); let sig = Signature4::new(SignatureType::Binary, PublicKeyAlgorithm::EdDSA, HashAlgorithm::SHA256, hashed, unhashed, [0xa7,0x19], mpi::Signature::EdDSA{ r: mpi::MPI::new(r), s: mpi::MPI::new(s) }); let sig: Signature = sig.into(); sig.verify_message(&key, b"Hello, World\n").unwrap(); } #[test] fn fingerprint_test() { let pile = PacketPile::from_bytes(crate::tests::key("public-key.gpg")).unwrap(); // The blob contains a public key and three subkeys. let mut pki = 0; let mut ski = 0; let pks = [ "8F17777118A33DDA9BA48E62AACB3243630052D9" ]; let sks = [ "C03FA6411B03AE12576461187223B56678E02528", "50E6D924308DBF223CFB510AC2B819056C652598", "2DC50AB55BE2F3B04C2D2CF8A3506AFB820ABD08"]; for p in pile.descendants() { if let &Packet::PublicKey(ref p) = p { let fp = p.fingerprint().to_hex(); // eprintln!("PK: {:?}", fp); assert!(pki < pks.len()); assert_eq!(fp, pks[pki]); pki += 1; } if let &Packet::PublicSubkey(ref p) = p { let fp = p.fingerprint().to_hex(); // eprintln!("SK: {:?}", fp); assert!(ski < sks.len()); assert_eq!(fp, sks[ski]); ski += 1; } } assert!(pki == pks.len() && ski == sks.len()); } #[test] fn issue_617() -> Result<()> { use crate::serialize::MarshalInto; let p = Packet::from_bytes(&b"-----BEGIN PGP ARMORED FILE----- xcClBAAAAMUWBSuBBAAjAPDbS+Z6Ti+PouOV6c5Ypr3jn1w1Ih5GqikN5E29PGz+ CQMIoYc7R4YRiLr/ZJB/MW5M0kuuWyUirUKRkYCotB5omVE8fGtqW5wGCGf79Tzb rKVmPl25CJdEabIfAOl0WwciipDx1tqNOOYEci/JWSbTEymEyCH9oQPObt2sdDxh wLcBgsd/CVl3kuqiXFHNYDvWVBmUHeltS/J22Kfy/n1qD3CCBFooHGdc13KwtMLk UPb5LTTqCk2ihQ7e+5u7EmueLUp1431HJiYa+olaPZ7caRNfQfggtHcfQOJdnWRJ FN2nTDgLHX0cEOiMboZrS4S9xtjyVRLcRZcCIyeQF0Q889rq0lmxHG38XUeIj/3y SJJNnZxmJtHNo+SZQ/gXhO9TzeeA6yQm2myQlRkXBtdQEz6mtznphWeWMkWApZpa FwPoSAbbsLkNS/iNN2MDGAVYvezYn2QZ =0cxs -----END PGP ARMORED FILE-----"[..])?; let i: usize = 360; let mut buf = p.to_vec().unwrap(); // Avoid first two bytes so that we don't change the // type and reduce the chance of changing the length. let bit = i.saturating_add(2 * 8) % (buf.len() * 8); buf[bit / 8] ^= 1 << (bit % 8); match Packet::from_bytes(&buf) { Ok(q) => { eprintln!("{:?}", p); eprintln!("{:?}", q); assert!(p != q); }, Err(_) => unreachable!(), }; Ok(()) } #[test] fn encrypt_huge_plaintext() -> Result<()> { let sk = crate::crypto::SessionKey::new(256).unwrap(); if PublicKeyAlgorithm::RSAEncryptSign.is_supported() { let rsa2k: Key<SecretParts, UnspecifiedRole> = Key4::generate_rsa(2048)?.into(); assert!(matches!( rsa2k.encrypt(&sk).unwrap_err().downcast().unwrap(), crate::Error::InvalidArgument(_) )); } if PublicKeyAlgorithm::ECDH.is_supported() && Curve::Cv25519.is_supported() { let cv25519: Key<SecretParts, UnspecifiedRole> = Key4::generate_ecc(false, Curve::Cv25519)?.into(); assert!(matches!( cv25519.encrypt(&sk).unwrap_err().downcast().unwrap(), crate::Error::InvalidArgument(_) )); } Ok(()) } #[test] fn cv25519_secret_is_reversed() -> Result<()> { use crate::crypto::backend::{Backend, interface::Asymmetric}; let (mut private_key, _) = Backend::x25519_generate_key()?; Backend::x25519_clamp_secret(&mut private_key); let key: Key4<_, UnspecifiedRole> = Key4::import_secret_cv25519(&private_key, None, None, None)?; if let crate::packet::key::SecretKeyMaterial::Unencrypted(key) = key.secret() { key.map(|secret| { if let mpi::SecretKeyMaterial::ECDH { scalar } = secret { let scalar_reversed = private_key.iter().copied().rev().collect::<Vec<u8>>(); let scalar_actual = &*scalar.value_padded(32); assert_eq!(scalar_actual, scalar_reversed); } else { unreachable!(); } }) } else { unreachable!(); } Ok(()) } #[test] fn ed25519_secret_is_not_reversed() { let private_key: &[u8] = &crate::crypto::SessionKey::new(32).unwrap(); let key: Key4<_, UnspecifiedRole> = Key4::import_secret_ed25519(private_key, None).unwrap(); if let crate::packet::key::SecretKeyMaterial::Unencrypted(key) = key.secret() { key.map(|secret| { if let mpi::SecretKeyMaterial::EdDSA { scalar } = secret { assert_eq!(&*scalar.value_padded(32), private_key); } else { unreachable!(); } }) } else { unreachable!(); } } #[test] fn issue_1016() { // The fingerprint is a function of the creation time, // algorithm, and public MPIs. When we change them make sure // the fingerprint also changes. let mut g = quickcheck::Gen::new(256); let mut key = Key4::<PublicParts, UnspecifiedRole>::arbitrary(&mut g); let fpr1 = key.fingerprint(); if key.creation_time() == UNIX_EPOCH { key.set_creation_time(UNIX_EPOCH + Duration::new(1, 0)).expect("ok"); } else { key.set_creation_time(UNIX_EPOCH).expect("ok"); } assert_ne!(fpr1, key.fingerprint()); let mut key = Key4::<PublicParts, UnspecifiedRole>::arbitrary(&mut g); let fpr1 = key.fingerprint(); key.set_pk_algo(PublicKeyAlgorithm::from(u8::from(key.pk_algo()) + 1)); assert_ne!(fpr1, key.fingerprint()); let mut key = Key4::<PublicParts, UnspecifiedRole>::arbitrary(&mut g); let fpr1 = key.fingerprint(); loop { let mpis2 = mpi::PublicKey::arbitrary(&mut g); if key.mpis() != &mpis2 { *key.mpis_mut() = mpis2; break; } } assert_ne!(fpr1, key.fingerprint()); let mut key = Key4::<PublicParts, UnspecifiedRole>::arbitrary(&mut g); let fpr1 = key.fingerprint(); loop { let mpis2 = mpi::PublicKey::arbitrary(&mut g); if key.mpis() != &mpis2 { key.set_mpis(mpis2); break; } } assert_ne!(fpr1, key.fingerprint()); } /// Smoke test for ECC key creation, signing and verification, and /// encryption and decryption. #[test] fn ecc_support() -> Result<()> { for for_signing in [true, false] { for curve in Curve::variants() .filter(Curve::is_supported) { match curve { Curve::Cv25519 if for_signing => continue, Curve::Ed25519 if ! for_signing => continue, _ => (), } eprintln!("curve {}, for signing {:?}", curve, for_signing); let key: Key<SecretParts, UnspecifiedRole> = Key4::generate_ecc(for_signing, curve.clone())?.into(); let mut pair = key.into_keypair()?; if for_signing { use crate::crypto::Signer; let hash = HashAlgorithm::default(); let digest = hash.context()? .for_signature(pair.public().version()) .into_digest()?; let sig = pair.sign(hash, &digest)?; pair.public().verify(&sig, hash, &digest)?; } else { use crate::crypto::{SessionKey, Decryptor}; let sk = SessionKey::new(32).unwrap(); let ciphertext = pair.public().encrypt(&sk)?; assert_eq!(pair.decrypt(&ciphertext, Some(sk.len()))?, sk); } } } Ok(()) } #[test] fn ecc_encoding() -> Result<()> { for for_signing in [true, false] { for curve in Curve::variants() .filter(Curve::is_supported) { match curve { Curve::Cv25519 if for_signing => continue, Curve::Ed25519 if ! for_signing => continue, _ => (), } use crate::crypto::mpi::{Ciphertext, MPI, PublicKey}; eprintln!("curve {}, for signing {:?}", curve, for_signing); let key: Key<SecretParts, UnspecifiedRole> = Key4::generate_ecc(for_signing, curve.clone())?.into(); let compressed = |mpi: &MPI| mpi.value()[0] == 0x40; let uncompressed = |mpi: &MPI| mpi.value()[0] == 0x04; match key.mpis() { PublicKey::ECDSA { curve: c, q } if for_signing => { assert!(c == &curve); assert!(uncompressed(q)); }, PublicKey::EdDSA { curve: c, q } if for_signing => { assert!(c == &curve); assert!(compressed(q)); }, PublicKey::ECDH { curve: c, q, .. } if ! for_signing => { assert!(c == &curve); if curve == Curve::Cv25519 { assert!(compressed(q)); } else { assert!(uncompressed(q)); } use crate::crypto::SessionKey; let sk = SessionKey::new(32).unwrap(); let ciphertext = key.encrypt(&sk)?; if let Ciphertext::ECDH { e, .. } = &ciphertext { if curve == Curve::Cv25519 { assert!(compressed(e)); } else { assert!(uncompressed(e)); } } else { panic!("unexpected ciphertext: {:?}", ciphertext); } }, mpi => unreachable!( "curve {}, mpi {:?}, for signing {:?}", curve, mpi, for_signing), } } } Ok(()) } } ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/src/packet/key/v6.rs����������������������������������������������������������0000644�0000000�0000000�00000117066�10461020230�0016501�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������//! OpenPGP v6 key packet. use std::fmt; use std::cmp::Ordering; use std::hash::Hasher; use std::time; #[cfg(test)] use quickcheck::{Arbitrary, Gen}; use crate::Error; use crate::crypto::{mpi, hash::Hash, mem::Protected, KeyPair}; use crate::packet::key::{ KeyParts, KeyRole, KeyRoleRT, PublicParts, SecretParts, UnspecifiedParts, }; use crate::packet::prelude::*; use crate::PublicKeyAlgorithm; use crate::HashAlgorithm; use crate::types::Timestamp; use crate::Result; use crate::crypto::Password; use crate::KeyID; use crate::Fingerprint; use crate::KeyHandle; use crate::policy::HashAlgoSecurity; /// Holds a public key, public subkey, private key or private subkey /// packet. /// /// Use [`Key6::generate_rsa`] or [`Key6::generate_ecc`] to create a /// new key. /// /// Existing key material can be turned into an OpenPGP key using /// [`Key6::new`], [`Key6::with_secret`], [`Key6::import_public_x25519`], /// [`Key6::import_public_ed25519`], [`Key6::import_public_rsa`], /// [`Key6::import_secret_x25519`], [`Key6::import_secret_ed25519`], /// and [`Key6::import_secret_rsa`]. /// /// Whether you create a new key or import existing key material, you /// still need to create a binding signature, and, for signing keys, a /// back signature before integrating the key into a certificate. /// /// Normally, you won't directly use `Key6`, but [`Key`], which is a /// relatively thin wrapper around `Key6`. /// /// See [Section 5.5 of RFC 9580] and [the documentation for `Key`] /// for more details. /// /// [Section 5.5 of RFC 9580]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.5 /// [the documentation for `Key`]: super::Key /// [`Key`]: super::Key #[derive(PartialEq, Eq, Hash)] pub struct Key6<P: KeyParts, R: KeyRole> { pub(crate) common: Key4<P, R>, } // derive(Clone) doesn't work as expected with generic type parameters // that don't implement clone: it adds a trait bound on Clone to P and // R in the Clone implementation. Happily, we don't need P or R to // implement Clone: they are just marker traits, which we can clone // manually. // // See: https://github.com/rust-lang/rust/issues/26925 impl<P, R> Clone for Key6<P, R> where P: KeyParts, R: KeyRole { fn clone(&self) -> Self { Key6 { common: self.common.clone(), } } } impl<P, R> fmt::Debug for Key6<P, R> where P: KeyParts, R: KeyRole, { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.debug_struct("Key6") .field("fingerprint", &self.fingerprint()) .field("creation_time", &self.creation_time()) .field("pk_algo", &self.pk_algo()) .field("mpis", &self.mpis()) .field("secret", &self.optional_secret()) .finish() } } impl<P, R> fmt::Display for Key6<P, R> where P: KeyParts, R: KeyRole, { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{}", self.fingerprint()) } } impl<P, R> Key6<P, R> where P: KeyParts, R: KeyRole, { /// The security requirements of the hash algorithm for /// self-signatures. /// /// A cryptographic hash algorithm usually has [three security /// properties]: pre-image resistance, second pre-image /// resistance, and collision resistance. If an attacker can /// influence the signed data, then the hash algorithm needs to /// have both second pre-image resistance, and collision /// resistance. If not, second pre-image resistance is /// sufficient. /// /// [three security properties]: https://en.wikipedia.org/wiki/Cryptographic_hash_function#Properties /// /// In general, an attacker may be able to influence third-party /// signatures. But direct key signatures, and binding signatures /// are only over data fully determined by signer. And, an /// attacker's control over self signatures over User IDs is /// limited due to their structure. /// /// These observations can be used to extend the life of a hash /// algorithm after its collision resistance has been partially /// compromised, but not completely broken. For more details, /// please refer to the documentation for [HashAlgoSecurity]. /// /// [HashAlgoSecurity]: crate::policy::HashAlgoSecurity pub fn hash_algo_security(&self) -> HashAlgoSecurity { HashAlgoSecurity::SecondPreImageResistance } /// Compares the public bits of two keys. /// /// This returns `Ordering::Equal` if the public MPIs, creation /// time, and algorithm of the two `Key6`s match. This does not /// consider the packets' encodings, packets' tags or their secret /// key material. pub fn public_cmp<PB, RB>(&self, b: &Key6<PB, RB>) -> Ordering where PB: KeyParts, RB: KeyRole, { self.mpis().cmp(b.mpis()) .then_with(|| self.creation_time().cmp(&b.creation_time())) .then_with(|| self.pk_algo().cmp(&b.pk_algo())) } /// Tests whether two keys are equal modulo their secret key /// material. /// /// This returns true if the public MPIs, creation time and /// algorithm of the two `Key6`s match. This does not consider /// the packets' encodings, packets' tags or their secret key /// material. pub fn public_eq<PB, RB>(&self, b: &Key6<PB, RB>) -> bool where PB: KeyParts, RB: KeyRole, { self.public_cmp(b) == Ordering::Equal } /// Hashes everything but any secret key material into state. /// /// This is an alternate implementation of [`Hash`], which never /// hashes the secret key material. /// /// [`Hash`]: std::hash::Hash pub fn public_hash<H>(&self, state: &mut H) where H: Hasher { self.common.public_hash(state); } } impl<P, R> Key6<P, R> where P: KeyParts, R: KeyRole, { /// Gets the `Key`'s creation time. pub fn creation_time(&self) -> time::SystemTime { self.common.creation_time() } /// Gets the `Key`'s creation time without converting it to a /// system time. /// /// This conversion may truncate the time to signed 32-bit time_t. pub(crate) fn creation_time_raw(&self) -> Timestamp { self.common.creation_time_raw() } /// Sets the `Key`'s creation time. /// /// `timestamp` is converted to OpenPGP's internal format, /// [`Timestamp`]: a 32-bit quantity containing the number of /// seconds since the Unix epoch. /// /// `timestamp` is silently rounded to match the internal /// resolution. An error is returned if `timestamp` is out of /// range. /// /// [`Timestamp`]: crate::types::Timestamp pub fn set_creation_time<T>(&mut self, timestamp: T) -> Result<time::SystemTime> where T: Into<time::SystemTime> { self.common.set_creation_time(timestamp) } /// Gets the public key algorithm. pub fn pk_algo(&self) -> PublicKeyAlgorithm { self.common.pk_algo() } /// Sets the public key algorithm. /// /// Returns the old public key algorithm. pub fn set_pk_algo(&mut self, pk_algo: PublicKeyAlgorithm) -> PublicKeyAlgorithm { self.common.set_pk_algo(pk_algo) } /// Returns a reference to the `Key`'s MPIs. pub fn mpis(&self) -> &mpi::PublicKey { self.common.mpis() } /// Returns a mutable reference to the `Key`'s MPIs. pub fn mpis_mut(&mut self) -> &mut mpi::PublicKey { self.common.mpis_mut() } /// Sets the `Key`'s MPIs. /// /// This function returns the old MPIs, if any. pub fn set_mpis(&mut self, mpis: mpi::PublicKey) -> mpi::PublicKey { self.common.set_mpis(mpis) } /// Returns whether the `Key` contains secret key material. pub fn has_secret(&self) -> bool { self.common.has_secret() } /// Returns whether the `Key` contains unencrypted secret key /// material. /// /// This returns false if the `Key` doesn't contain any secret key /// material. pub fn has_unencrypted_secret(&self) -> bool { self.common.has_unencrypted_secret() } /// Returns `Key`'s secret key material, if any. pub fn optional_secret(&self) -> Option<&SecretKeyMaterial> { self.common.optional_secret() } /// Computes and returns the `Key`'s `Fingerprint` and returns it as /// a `KeyHandle`. /// /// See [Section 5.5.4 of RFC 9580]. /// /// [Section 5.5.4 of RFC 9580]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.5.4 pub fn key_handle(&self) -> KeyHandle { self.fingerprint().into() } /// Computes and returns the `Key`'s `Fingerprint`. /// /// See [Key IDs and Fingerprints]. /// /// [Key IDs and Fingerprints]: https://www.rfc-editor.org/rfc/rfc9580.html#key-ids-fingerprints pub fn fingerprint(&self) -> Fingerprint { let fp = self.common.fingerprint.get_or_init(|| { let mut h = HashAlgorithm::SHA256.context() .expect("SHA256 is MTI for RFC9580") // v6 fingerprints are computed the same way a key is // hashed for v6 signatures. .for_signature(6); self.hash(&mut h).expect("v6 key hashing is infallible"); let mut digest = [0u8; 32]; let _ = h.digest(&mut digest); Fingerprint::V6(digest) }); // Currently, it could happen that a Key4 has its fingerprint // computed, and is then converted to a Key6. That is only // possible within this crate, and should not happen. Assert // that. The better way to handle this is to have a CommonKey // struct which both Key4 and Key6 use, so that a Key6 does // not start out as a Key4, preventing this issue. debug_assert!(matches!(fp, Fingerprint::V6(_))); fp.clone() } /// Computes and returns the `Key`'s `Key ID`. /// /// See [Section 5.5.4 of RFC 9580]. /// /// [Section 5.5.4 of RFC 9580]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.5.4 pub fn keyid(&self) -> KeyID { self.fingerprint().into() } /// Creates a v6 key from a v4 key. Used internally in /// constructors. pub(crate) fn from_common(common: Key4<P, R>) -> Self { Key6 { common } } /// Creates an OpenPGP public key from the specified key material. /// /// This is an internal version for parse.rs that avoids going /// through SystemTime. pub(crate) fn make<T>(creation_time: T, pk_algo: PublicKeyAlgorithm, mpis: mpi::PublicKey, secret: Option<SecretKeyMaterial>) -> Result<Self> where T: Into<Timestamp>, { Ok(Key6 { common: Key4::make(creation_time, pk_algo, mpis, secret)?, }) } pub(crate) fn role(&self) -> KeyRoleRT { self.common.role() } pub(crate) fn set_role(&mut self, role: KeyRoleRT) { self.common.set_role(role); } } impl<R> Key6<key::PublicParts, R> where R: KeyRole, { /// Creates an OpenPGP public key from the specified key material. pub fn new<T>(creation_time: T, pk_algo: PublicKeyAlgorithm, mpis: mpi::PublicKey) -> Result<Self> where T: Into<time::SystemTime> { Ok(Key6 { common: Key4::new(creation_time, pk_algo, mpis)?, }) } /// Creates an OpenPGP public key packet from existing X25519 key /// material. /// /// The key will have its creation date set to `ctime` or the /// current time if `None` is given. pub fn import_public_x25519<T>(public_key: &[u8], ctime: T) -> Result<Self> where T: Into<Option<time::SystemTime>>, { Ok(Key6 { common: Key4::new(ctime.into().unwrap_or_else(crate::now), PublicKeyAlgorithm::X25519, mpi::PublicKey::X25519 { u: public_key.try_into()?, })?, }) } /// Creates an OpenPGP public key packet from existing X448 key /// material. /// /// The key will have its creation date set to `ctime` or the /// current time if `None` is given. pub fn import_public_x448<T>(public_key: &[u8], ctime: T) -> Result<Self> where T: Into<Option<time::SystemTime>>, { Ok(Key6 { common: Key4::new(ctime.into().unwrap_or_else(crate::now), PublicKeyAlgorithm::X448, mpi::PublicKey::X448 { u: Box::new(public_key.try_into()?), })?, }) } /// Creates an OpenPGP public key packet from existing Ed25519 key /// material. /// /// The key will have its creation date set to `ctime` or the /// current time if `None` is given. pub fn import_public_ed25519<T>(public_key: &[u8], ctime: T) -> Result<Self> where T: Into<Option<time::SystemTime>>, { Ok(Key6 { common: Key4::new(ctime.into().unwrap_or_else(crate::now), PublicKeyAlgorithm::Ed25519, mpi::PublicKey::Ed25519 { a: public_key.try_into()?, })?, }) } /// Creates an OpenPGP public key packet from existing Ed448 key /// material. /// /// The key will have its creation date set to `ctime` or the /// current time if `None` is given. pub fn import_public_ed448<T>(public_key: &[u8], ctime: T) -> Result<Self> where T: Into<Option<time::SystemTime>>, { Ok(Key6 { common: Key4::new(ctime.into().unwrap_or_else(crate::now), PublicKeyAlgorithm::Ed448, mpi::PublicKey::Ed448 { a: Box::new(public_key.try_into()?), })?, }) } /// Creates an OpenPGP public key packet from existing RSA key /// material. /// /// The RSA key will use the public exponent `e` and the modulo /// `n`. The key will have its creation date set to `ctime` or the /// current time if `None` is given. pub fn import_public_rsa<T>(e: &[u8], n: &[u8], ctime: T) -> Result<Self> where T: Into<Option<time::SystemTime>> { Ok(Key6 { common: Key4::import_public_rsa(e, n, ctime)?, }) } } impl<R> Key6<SecretParts, R> where R: KeyRole, { /// Creates an OpenPGP key packet from the specified secret key /// material. pub fn with_secret<T>(creation_time: T, pk_algo: PublicKeyAlgorithm, mpis: mpi::PublicKey, secret: SecretKeyMaterial) -> Result<Self> where T: Into<time::SystemTime> { Ok(Key6 { common: Key4::with_secret(creation_time, pk_algo, mpis, secret)?, }) } /// Creates a new OpenPGP secret key packet for an existing X25519 /// key. /// /// The given `private_key` is expected to be in the native X25519 /// representation, i.e. as opaque byte string of length 32. /// /// The key will have its creation date set to `ctime` or the /// current time if `None` is given. pub fn import_secret_x25519<T>(private_key: &[u8], ctime: T) -> Result<Self> where T: Into<Option<std::time::SystemTime>>, { use crate::crypto::backend::{Backend, interface::Asymmetric}; let private_key = Protected::from(private_key); let public_key = Backend::x25519_derive_public(&private_key)?; Self::with_secret( ctime.into().unwrap_or_else(crate::now), PublicKeyAlgorithm::X25519, mpi::PublicKey::X25519 { u: public_key, }, mpi::SecretKeyMaterial::X25519 { x: private_key.into(), }.into()) } /// Creates a new OpenPGP secret key packet for an existing X448 /// key. /// /// The given `private_key` is expected to be in the native X448 /// representation, i.e. as opaque byte string of length 32. /// /// The key will have its creation date set to `ctime` or the /// current time if `None` is given. pub fn import_secret_x448<T>(private_key: &[u8], ctime: T) -> Result<Self> where T: Into<Option<std::time::SystemTime>>, { use crate::crypto::backend::{Backend, interface::Asymmetric}; let private_key = Protected::from(private_key); let public_key = Backend::x448_derive_public(&private_key)?; Self::with_secret( ctime.into().unwrap_or_else(crate::now), PublicKeyAlgorithm::X448, mpi::PublicKey::X448 { u: Box::new(public_key), }, mpi::SecretKeyMaterial::X448 { x: private_key.into(), }.into()) } /// Creates a new OpenPGP secret key packet for an existing /// Ed25519 key. /// /// The key will have its creation date set to `ctime` or the /// current time if `None` is given. pub fn import_secret_ed25519<T>(private_key: &[u8], ctime: T) -> Result<Self> where T: Into<Option<time::SystemTime>>, { use crate::crypto::backend::{Backend, interface::Asymmetric}; let private_key = Protected::from(private_key); let public_key = Backend::ed25519_derive_public(&private_key)?; Self::with_secret( ctime.into().unwrap_or_else(crate::now), PublicKeyAlgorithm::Ed25519, mpi::PublicKey::Ed25519 { a: public_key, }, mpi::SecretKeyMaterial::Ed25519 { x: private_key.into(), }.into()) } /// Creates a new OpenPGP secret key packet for an existing /// Ed448 key. /// /// The key will have its creation date set to `ctime` or the /// current time if `None` is given. pub fn import_secret_ed448<T>(private_key: &[u8], ctime: T) -> Result<Self> where T: Into<Option<time::SystemTime>>, { use crate::crypto::backend::{Backend, interface::Asymmetric}; let private_key = Protected::from(private_key); let public_key = Backend::ed448_derive_public(&private_key)?; Self::with_secret( ctime.into().unwrap_or_else(crate::now), PublicKeyAlgorithm::Ed448, mpi::PublicKey::Ed448 { a: Box::new(public_key), }, mpi::SecretKeyMaterial::Ed448 { x: private_key.into(), }.into()) } /// Creates a new key pair from a secret `Key` with an unencrypted /// secret key. /// /// # Errors /// /// Fails if the secret key is encrypted. You can use /// [`Key::decrypt_secret`] to decrypt a key. pub fn into_keypair(self) -> Result<KeyPair> { let (key, secret) = self.take_secret(); let secret = match secret { SecretKeyMaterial::Unencrypted(secret) => secret, SecretKeyMaterial::Encrypted(_) => return Err(Error::InvalidArgument( "secret key material is encrypted".into()).into()), }; KeyPair::new(key.role_into_unspecified().into(), secret) } } macro_rules! impl_common_secret_functions_v6 { ($t: ident) => { /// Secret key material handling. impl<R> Key6<$t, R> where R: KeyRole, { /// Takes the `Key`'s `SecretKeyMaterial`, if any. pub fn take_secret(mut self) -> (Key6<PublicParts, R>, Option<SecretKeyMaterial>) { let old = std::mem::replace(&mut self.common.secret, None); (self.parts_into_public(), old) } /// Adds the secret key material to the `Key`, returning /// the old secret key material, if any. pub fn add_secret(mut self, secret: SecretKeyMaterial) -> (Key6<SecretParts, R>, Option<SecretKeyMaterial>) { let old = std::mem::replace(&mut self.common.secret, Some(secret)); (self.parts_into_secret().expect("secret just set"), old) } /// Takes the `Key`'s `SecretKeyMaterial`, if any. pub fn steal_secret(&mut self) -> Option<SecretKeyMaterial> { std::mem::replace(&mut self.common.secret, None) } } } } impl_common_secret_functions_v6!(PublicParts); impl_common_secret_functions_v6!(UnspecifiedParts); /// Secret key handling. impl<R> Key6<SecretParts, R> where R: KeyRole, { /// Gets the `Key`'s `SecretKeyMaterial`. pub fn secret(&self) -> &SecretKeyMaterial { self.common.secret() } /// Gets a mutable reference to the `Key`'s `SecretKeyMaterial`. pub fn secret_mut(&mut self) -> &mut SecretKeyMaterial { self.common.secret_mut() } /// Takes the `Key`'s `SecretKeyMaterial`. pub fn take_secret(mut self) -> (Key6<PublicParts, R>, SecretKeyMaterial) { let old = std::mem::replace(&mut self.common.secret, None); (self.parts_into_public(), old.expect("Key<SecretParts, _> has a secret key material")) } /// Adds `SecretKeyMaterial` to the `Key`. /// /// This function returns the old secret key material, if any. pub fn add_secret(mut self, secret: SecretKeyMaterial) -> (Key6<SecretParts, R>, SecretKeyMaterial) { let old = std::mem::replace(&mut self.common.secret, Some(secret)); (self.parts_into_secret().expect("secret just set"), old.expect("Key<SecretParts, _> has a secret key material")) } /// Decrypts the secret key material using `password`. /// /// In OpenPGP, secret key material can be [protected with a /// password]. The password is usually hardened using a [KDF]. /// /// Refer to the documentation of [`Key::decrypt_secret`] for /// details. /// /// This function returns an error if the secret key material is /// not encrypted or the password is incorrect. /// /// [protected with a password]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.5.3 /// [KDF]: https://www.rfc-editor.org/rfc/rfc9580.html#section-3.7 /// [`Key::decrypt_secret`]: super::Key::decrypt_secret() pub fn decrypt_secret(self, password: &Password) -> Result<Self> { let (key, mut secret) = self.take_secret(); // Note: Key version is authenticated. let key = Key::V6(key); secret.decrypt_in_place(&key, password)?; let key = if let Key::V6(k) = key { k } else { unreachable!() }; Ok(key.add_secret(secret).0) } /// Encrypts the secret key material using `password`. /// /// In OpenPGP, secret key material can be [protected with a /// password]. The password is usually hardened using a [KDF]. /// /// Refer to the documentation of [`Key::encrypt_secret`] for /// details. /// /// This returns an error if the secret key material is already /// encrypted. /// /// [protected with a password]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.5.3 /// [KDF]: https://www.rfc-editor.org/rfc/rfc9580.html#section-3.7 /// [`Key::encrypt_secret`]: super::Key::encrypt_secret() pub fn encrypt_secret(self, password: &Password) -> Result<Key6<SecretParts, R>> { let (key, mut secret) = self.take_secret(); // Note: Key version is authenticated. let key = Key::V6(key); secret.encrypt_in_place(&key, password)?; let key = if let Key::V6(k) = key { k } else { unreachable!() }; Ok(key.add_secret(secret).0) } } impl<P, R> From<Key6<P, R>> for super::Key<P, R> where P: KeyParts, R: KeyRole, { fn from(p: Key6<P, R>) -> Self { super::Key::V6(p) } } #[cfg(test)] use crate::packet::key::{ PrimaryRole, SubordinateRole, UnspecifiedRole, }; #[cfg(test)] impl Arbitrary for Key6<PublicParts, PrimaryRole> { fn arbitrary(g: &mut Gen) -> Self { Key6::from_common(Key4::arbitrary(g)) } } #[cfg(test)] impl Arbitrary for Key6<PublicParts, SubordinateRole> { fn arbitrary(g: &mut Gen) -> Self { Key6::from_common(Key4::arbitrary(g)) } } #[cfg(test)] impl Arbitrary for Key6<PublicParts, UnspecifiedRole> { fn arbitrary(g: &mut Gen) -> Self { Key6::from_common(Key4::arbitrary(g)) } } #[cfg(test)] impl Arbitrary for Key6<SecretParts, PrimaryRole> { fn arbitrary(g: &mut Gen) -> Self { Key6::from_common(Key4::arbitrary(g)) } } #[cfg(test)] impl Arbitrary for Key6<SecretParts, SubordinateRole> { fn arbitrary(g: &mut Gen) -> Self { Key6::from_common(Key4::arbitrary(g)) } } #[cfg(test)] mod tests { use std::time::Duration; use std::time::UNIX_EPOCH; use crate::crypto::S2K; use crate::packet::Key; use crate::packet::key; use crate::packet::Packet; use super::*; use crate::PacketPile; use crate::serialize::Serialize; use crate::types::*; use crate::parse::Parse; #[test] fn primary_key_encrypt_decrypt() -> Result<()> { key_encrypt_decrypt::<PrimaryRole>() } #[test] fn subkey_encrypt_decrypt() -> Result<()> { key_encrypt_decrypt::<SubordinateRole>() } fn key_encrypt_decrypt<R>() -> Result<()> where R: KeyRole + PartialEq, { let mut g = quickcheck::Gen::new(256); let p: Password = Vec::<u8>::arbitrary(&mut g).into(); let check = |key: Key6<SecretParts, R>| -> Result<()> { let key: Key<_, _> = key.into(); let encrypted = key.clone().encrypt_secret(&p)?; let decrypted = encrypted.decrypt_secret(&p)?; assert_eq!(key, decrypted); Ok(()) }; use crate::types::Curve::*; for curve in vec![NistP256, NistP384, NistP521, Ed25519] { if ! curve.is_supported() { eprintln!("Skipping unsupported {}", curve); continue; } let key: Key6<_, R> = Key6::generate_ecc(true, curve.clone())?; check(key)?; } for bits in vec![2048, 3072] { if ! PublicKeyAlgorithm::RSAEncryptSign.is_supported() { eprintln!("Skipping unsupported RSA"); continue; } let key: Key6<_, R> = Key6::generate_rsa(bits)?; check(key)?; } Ok(()) } #[test] fn eq() { use crate::types::Curve::*; for curve in vec![NistP256, NistP384, NistP521] { if ! curve.is_supported() { eprintln!("Skipping unsupported {}", curve); continue; } let sign_key : Key6<_, key::UnspecifiedRole> = Key6::generate_ecc(true, curve.clone()).unwrap(); let enc_key : Key6<_, key::UnspecifiedRole> = Key6::generate_ecc(false, curve).unwrap(); let sign_clone = sign_key.clone(); let enc_clone = enc_key.clone(); assert_eq!(sign_key, sign_clone); assert_eq!(enc_key, enc_clone); } for bits in vec![1024, 2048, 3072, 4096] { if ! PublicKeyAlgorithm::RSAEncryptSign.is_supported() { eprintln!("Skipping unsupported RSA"); continue; } let key : Key6<_, key::UnspecifiedRole> = Key6::generate_rsa(bits).unwrap(); let clone = key.clone(); assert_eq!(key, clone); } } #[test] fn generate_roundtrip() { use crate::types::Curve::*; let keys = vec![NistP256, NistP384, NistP521].into_iter().flat_map(|cv| { if ! cv.is_supported() { eprintln!("Skipping unsupported {}", cv); return Vec::new(); } let sign_key : Key6<key::SecretParts, key::PrimaryRole> = Key6::generate_ecc(true, cv.clone()).unwrap(); let enc_key = Key6::generate_ecc(false, cv).unwrap(); vec![sign_key, enc_key] }).chain(vec![1024, 2048, 3072, 4096].into_iter().filter_map(|b| { Key6::generate_rsa(b).ok() })); for key in keys { let mut b = Vec::new(); Packet::SecretKey(key.clone().into()).serialize(&mut b).unwrap(); let pp = PacketPile::from_bytes(&b).unwrap(); if let Some(Packet::SecretKey(Key::V6(ref parsed_key))) = pp.path_ref(&[0]) { assert_eq!(key.creation_time(), parsed_key.creation_time()); assert_eq!(key.pk_algo(), parsed_key.pk_algo()); assert_eq!(key.mpis(), parsed_key.mpis()); assert_eq!(key.secret(), parsed_key.secret()); assert_eq!(&key, parsed_key); } else { panic!("bad packet: {:?}", pp.path_ref(&[0])); } let mut b = Vec::new(); let pk4 : Key6<PublicParts, PrimaryRole> = key.clone().into(); Packet::PublicKey(pk4.into()).serialize(&mut b).unwrap(); let pp = PacketPile::from_bytes(&b).unwrap(); if let Some(Packet::PublicKey(Key::V6(ref parsed_key))) = pp.path_ref(&[0]) { assert!(! parsed_key.has_secret()); let key = key.take_secret().0; assert_eq!(&key, parsed_key); } else { panic!("bad packet: {:?}", pp.path_ref(&[0])); } } } #[test] fn encryption_roundtrip() { use crate::crypto::SessionKey; use crate::types::Curve::*; let keys = vec![NistP256, NistP384, NistP521].into_iter() .filter_map(|cv| { Key6::generate_ecc(false, cv).ok() }).chain(vec![1024, 2048, 3072, 4096].into_iter().filter_map(|b| { Key6::generate_rsa(b).ok() })); for key in keys.into_iter() { let key: Key<key::SecretParts, key::UnspecifiedRole> = key.into(); let mut keypair = key.clone().into_keypair().unwrap(); let cipher = SymmetricAlgorithm::AES256; let sk = SessionKey::new(cipher.key_size().unwrap()).unwrap(); let pkesk = PKESK6::for_recipient(&sk, &key).unwrap(); let sk_ = pkesk.decrypt(&mut keypair, None) .expect("keypair should be able to decrypt PKESK"); assert_eq!(sk, sk_); let sk_ = pkesk.decrypt(&mut keypair, Some(cipher)).unwrap(); assert_eq!(sk, sk_); } } #[test] fn signature_roundtrip() { use crate::types::{Curve::*, SignatureType}; let keys = vec![NistP256, NistP384, NistP521].into_iter() .filter_map(|cv| { Key6::generate_ecc(true, cv).ok() }).chain(vec![1024, 2048, 3072, 4096].into_iter().filter_map(|b| { Key6::generate_rsa(b).ok() })); for key in keys.into_iter() { let key: Key<key::SecretParts, key::UnspecifiedRole> = key.into(); let mut keypair = key.clone().into_keypair().unwrap(); let hash = HashAlgorithm::default(); // Sign. let ctx = hash.context().unwrap().for_signature(key.version()); let sig = SignatureBuilder::new(SignatureType::Binary) .sign_hash(&mut keypair, ctx).unwrap(); // Verify. let ctx = hash.context().unwrap().for_signature(key.version()); sig.verify_hash(&key, ctx).unwrap(); } } #[test] fn secret_encryption_roundtrip() { use crate::types::Curve::*; use crate::types::SymmetricAlgorithm::*; use crate::types::AEADAlgorithm::*; let keys = vec![NistP256, NistP384, NistP521].into_iter() .filter_map(|cv| -> Option<Key<key::SecretParts, key::PrimaryRole>> { Key6::generate_ecc(false, cv).map(Into::into).ok() }).chain(vec![1024, 2048, 3072, 4096].into_iter().filter_map(|b| { Key6::generate_rsa(b).map(Into::into).ok() })); for key in keys { for (symm, aead) in [(AES128, None), (AES128, Some(OCB)), (AES256, Some(EAX))] { if ! aead.map(|a| a.is_supported()).unwrap_or(true) { continue; } assert!(! key.secret().is_encrypted()); let password = Password::from("foobarbaz"); let mut encrypted_key = key.clone(); encrypted_key.secret_mut() .encrypt_in_place_with(&key, S2K::default(), symm, aead, &password).unwrap(); assert!(encrypted_key.secret().is_encrypted()); encrypted_key.secret_mut() .decrypt_in_place(&key, &password).unwrap(); assert!(! key.secret().is_encrypted()); assert_eq!(key, encrypted_key); assert_eq!(key.secret(), encrypted_key.secret()); } } } #[test] fn encrypt_huge_plaintext() -> Result<()> { let sk = crate::crypto::SessionKey::new(256).unwrap(); if PublicKeyAlgorithm::RSAEncryptSign.is_supported() { let rsa2k: Key<SecretParts, UnspecifiedRole> = Key6::generate_rsa(2048)?.into(); assert!(matches!( rsa2k.encrypt(&sk).unwrap_err().downcast().unwrap(), crate::Error::InvalidArgument(_) )); } Ok(()) } #[test] fn issue_1016() { // The fingerprint is a function of the creation time, // algorithm, and public MPIs. When we change them make sure // the fingerprint also changes. let mut g = quickcheck::Gen::new(256); let mut key = Key6::<PublicParts, UnspecifiedRole>::arbitrary(&mut g); let fpr1 = key.fingerprint(); if key.creation_time() == UNIX_EPOCH { key.set_creation_time(UNIX_EPOCH + Duration::new(1, 0)).expect("ok"); } else { key.set_creation_time(UNIX_EPOCH).expect("ok"); } assert_ne!(fpr1, key.fingerprint()); let mut key = Key6::<PublicParts, UnspecifiedRole>::arbitrary(&mut g); let fpr1 = key.fingerprint(); key.set_pk_algo(PublicKeyAlgorithm::from(u8::from(key.pk_algo()) + 1)); assert_ne!(fpr1, key.fingerprint()); let mut key = Key6::<PublicParts, UnspecifiedRole>::arbitrary(&mut g); let fpr1 = key.fingerprint(); loop { let mpis2 = mpi::PublicKey::arbitrary(&mut g); if key.mpis() != &mpis2 { *key.mpis_mut() = mpis2; break; } } assert_ne!(fpr1, key.fingerprint()); let mut key = Key6::<PublicParts, UnspecifiedRole>::arbitrary(&mut g); let fpr1 = key.fingerprint(); loop { let mpis2 = mpi::PublicKey::arbitrary(&mut g); if key.mpis() != &mpis2 { key.set_mpis(mpis2); break; } } assert_ne!(fpr1, key.fingerprint()); } /// Smoke test for ECC key creation, signing and verification, and /// encryption and decryption. #[test] fn ecc_support() -> Result<()> { for for_signing in [true, false] { for curve in Curve::variants() .filter(Curve::is_supported) { match curve { Curve::Cv25519 if for_signing => continue, Curve::Ed25519 if ! for_signing => continue, _ => (), } eprintln!("curve {}, for signing {:?}", curve, for_signing); let key: Key<SecretParts, UnspecifiedRole> = Key6::generate_ecc(for_signing, curve.clone())?.into(); let mut pair = key.into_keypair()?; if for_signing { use crate::crypto::Signer; let hash = HashAlgorithm::default(); let digest = hash.context()? .for_signature(pair.public().version()) .into_digest()?; let sig = pair.sign(hash, &digest)?; pair.public().verify(&sig, hash, &digest)?; } else { use crate::crypto::{SessionKey, Decryptor}; let sk = SessionKey::new(32).unwrap(); let ciphertext = pair.public().encrypt(&sk)?; assert_eq!(pair.decrypt(&ciphertext, Some(sk.len()))?, sk); } } } Ok(()) } #[test] fn ecc_encoding() -> Result<()> { for for_signing in [true, false] { for curve in Curve::variants() .filter(Curve::is_supported) { match curve { Curve::Cv25519 if for_signing => continue, Curve::Ed25519 if ! for_signing => continue, _ => (), } use crate::crypto::mpi::{Ciphertext, MPI, PublicKey}; eprintln!("curve {}, for signing {:?}", curve, for_signing); let key: Key<SecretParts, UnspecifiedRole> = Key6::generate_ecc(for_signing, curve.clone())?.into(); let uncompressed = |mpi: &MPI| mpi.value()[0] == 0x04; match key.mpis() { PublicKey::X25519 { .. } if ! for_signing => (), PublicKey::X448 { .. } if ! for_signing => (), PublicKey::Ed25519 { .. } if for_signing => (), PublicKey::Ed448 { .. } if for_signing => (), PublicKey::ECDSA { curve: c, q } if for_signing => { assert!(c == &curve); assert!(c != &Curve::Ed25519); assert!(uncompressed(q)); }, PublicKey::ECDH { curve: c, q, .. } if ! for_signing => { assert!(c == &curve); assert!(c != &Curve::Cv25519); assert!(uncompressed(q)); use crate::crypto::SessionKey; let sk = SessionKey::new(32).unwrap(); let ciphertext = key.encrypt(&sk)?; if let Ciphertext::ECDH { e, .. } = &ciphertext { assert!(uncompressed(e)); } else { panic!("unexpected ciphertext: {:?}", ciphertext); } }, mpi => unreachable!( "curve {}, mpi {:?}, for signing {:?}", curve, mpi, for_signing), } } } Ok(()) } #[test] fn v6_key_fingerprint() -> Result<()> { let p = Packet::from_bytes("-----BEGIN PGP ARMORED FILE----- xjcGY4d/4xYAAAAtCSsGAQQB2kcPAQEHQPlNp7tI1gph5WdwamWH0DMZmbudiRoI JC6thFQ9+JWj =SgmS -----END PGP ARMORED FILE-----")?; let k: &Key<PublicParts, PrimaryRole> = p.downcast_ref().unwrap(); assert_eq!(k.fingerprint().to_string(), "4EADF309C6BC874AE04702451548F93F\ 96FA7A01D0A33B5AF7D4E379E0F9F8EE".to_string()); Ok(()) } } ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/src/packet/key.rs�������������������������������������������������������������0000644�0000000�0000000�00000301652�10461020230�0016142�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������//! Key-related functionality. //! //! # Data Types //! //! The main data type is the [`Key`] enum. This enum abstracts away //! the differences between the key formats (the current [version 6], //! the deprecated [version 4], and the legacy [version 3]). //! Nevertheless, some functionality remains format specific. For //! instance, the `Key` enum doesn't provide a mechanism to generate //! keys. This functionality depends on the format. //! //! This version of Sequoia only supports version 6 and version 4 keys //! ([`Key6`], and [`Key4`]). However, future versions may include //! limited support for version 3 keys to allow working with archived //! messages. //! //! OpenPGP specifies four different types of keys: [public keys], //! [secret keys], [public subkeys], and [secret subkeys]. These are //! all represented by the `Key` enum and the `Key4` struct using //! marker types. We use marker types rather than an enum, to better //! exploit the type checking. For instance, type-specific methods //! like [`Key4::secret`] are only exposed for those types that //! actually support them. See the documentation for [`Key`] for an //! explanation of how the markers work. //! //! The [`SecretKeyMaterial`] data type allows working with secret key //! material directly. This enum has two variants: [`Unencrypted`], //! and [`Encrypted`]. It is not normally necessary to use this data //! structure directly. The primary functionality that is of interest //! to most users is decrypting secret key material. This is usually //! more conveniently done using [`Key::decrypt_secret`]. //! //! [`Key`]: super::Key //! [version 3]: https://tools.ietf.org/html/rfc1991#section-6.6 //! [version 4]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.5.2 //! [version 6]: https://www.rfc-editor.org/rfc/rfc9580.html#name-version-6-public-keys //! [public keys]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.5.1.1 //! [secret keys]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.5.1.3 //! [public subkeys]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.5.1.2 //! [secret subkeys]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.5.1.5 //! [`Key::decrypt_secret`]: super::Key::decrypt_secret() //! //! # Key Creation //! //! Use [`Key6::generate_x25519`], [`Key6::generate_ed25519`], //! [`Key6::generate_x448`], [`Key6::generate_ed448`], //! [`Key6::generate_ecc`], or [`Key6::generate_rsa`] to create a new //! key. //! //! Existing key material can be turned into an OpenPGP key using //! [`Key6::import_public_x25519`], [`Key6::import_public_ed25519`], //! [`Key6::import_public_x448`], [`Key6::import_public_ed448`], //! [`Key6::import_public_rsa`], [`Key6::import_secret_x25519`], //! [`Key6::import_secret_ed25519`], [`Key6::import_secret_x448`], //! [`Key6::import_secret_ed448`], and [`Key6::import_secret_rsa`]. //! //! Whether you create a new key or import existing key material, you //! still need to create a binding signature, and, for signing keys, a //! back signature for the key to be usable. //! //! # In-Memory Protection of Secret Key Material //! //! Whether the secret key material is protected on disk or not, //! Sequoia encrypts unencrypted secret key material ([`Unencrypted`]) //! while it is memory. This helps protect against [heartbleed]-style //! attacks where a buffer over-read allows an attacker to read from //! the process's address space. This protection is less important //! for Rust programs, which are memory safe. However, it is //! essential when Sequoia is used via its FFI. //! //! See [`crypto::mem::Encrypted`] for details. //! //! [heartbleed]: https://en.wikipedia.org/wiki/Heartbleed //! [`crypto::mem::Encrypted`]: super::super::crypto::mem::Encrypted use std::fmt; use std::convert::TryInto; use std::hash::Hasher; #[cfg(test)] use quickcheck::{Arbitrary, Gen}; use crate::Error; use crate::cert::prelude::*; use crate::crypto::{self, mem, mpi, KeyPair}; use crate::packet::prelude::*; use crate::policy::HashAlgoSecurity; use crate::PublicKeyAlgorithm; use crate::seal; use crate::SymmetricAlgorithm; use crate::HashAlgorithm; use crate::types::{ AEADAlgorithm, Curve, }; use crate::crypto::S2K; use crate::Result; use crate::crypto::Password; use crate::crypto::SessionKey; mod conversions; mod v6; pub use v6::Key6; mod v4; pub use v4::Key4; /// Holds a public key, public subkey, private key or private subkey packet. /// /// The different `Key` packets are described in [Section 5.5 of RFC 9580]. /// /// [Section 5.5 of RFC 9580]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.5 /// /// # Key Variants /// /// There are four different types of keys in OpenPGP: [public keys], /// [secret keys], [public subkeys], and [secret subkeys]. Although /// the semantics of each type of key are slightly different, the /// underlying representation is identical (even a public key and a /// secret key are the same: the public key variant just contains 0 /// bits of secret key material). /// /// In Sequoia, we use a single type, `Key`, for all four variants. /// To improve type safety, we use marker traits rather than an `enum` /// to distinguish them. Specifically, we `Key` is generic over two /// type variables, `P` and `R`. /// /// `P` and `R` take marker traits, which describe how any secret key /// material should be treated, and the key's role (primary or /// subordinate). The markers also determine the `Key`'s behavior and /// the exposed functionality. `P` can be [`key::PublicParts`], /// [`key::SecretParts`], or [`key::UnspecifiedParts`]. And, `R` can /// be [`key::PrimaryRole`], [`key::SubordinateRole`], or /// [`key::UnspecifiedRole`]. /// /// If `P` is `key::PublicParts`, any secret key material that is /// present is ignored. For instance, when serializing a key with /// this marker, any secret key material will be skipped. This is /// illutrated in the following example. If `P` is /// `key::SecretParts`, then the key definitely contains secret key /// material (although it is not guaranteed that the secret key /// material is valid), and methods that require secret key material /// are available. /// /// Unlike `P`, `R` does not say anything about the `Key`'s content. /// But, a key's role does influence's the key's semantics. For /// instance, some of a primary key's meta-data is located on the /// primary User ID whereas a subordinate key's meta-data is located /// on its binding signature. /// /// The unspecified variants [`key::UnspecifiedParts`] and /// [`key::UnspecifiedRole`] exist to simplify type erasure, which is /// needed to mix different types of keys in a single collection. For /// instance, [`Cert::keys`] returns an iterator over the keys in a /// certificate. Since the keys have different roles (a primary key /// and zero or more subkeys), but the `Iterator` has to be over a /// single, fixed type, the returned keys use the /// `key::UnspecifiedRole` marker. /// /// [public keys]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.5.1.1 /// [secret keys]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.5.1.3 /// [public subkeys]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.5.1.2 /// [secret subkeys]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.5.1.5 /// [`Cert::keys`]: crate::Cert::keys /// /// ## Examples /// /// Serializing a public key with secret key material drops the secret /// key material: /// /// ``` /// use sequoia_openpgp as openpgp; /// use openpgp::cert::prelude::*; /// use openpgp::packet::prelude::*; /// use sequoia_openpgp::parse::Parse; /// use openpgp::serialize::Serialize; /// /// # fn main() -> openpgp::Result<()> { /// // Generate a new certificate. It has secret key material. /// let (cert, _) = CertBuilder::new() /// .generate()?; /// /// let pk = cert.primary_key().key(); /// assert!(pk.has_secret()); /// /// // Serializing a `Key<key::PublicParts, _>` drops the secret key /// // material. /// let mut bytes = Vec::new(); /// Packet::from(pk.clone()).serialize(&mut bytes); /// let p : Packet = Packet::from_bytes(&bytes)?; /// /// if let Packet::PublicKey(key) = p { /// assert!(! key.has_secret()); /// } else { /// unreachable!(); /// } /// # Ok(()) /// # } /// ``` /// /// # Conversions /// /// Sometimes it is necessary to change a marker. For instance, to /// help prevent a user from inadvertently leaking secret key /// material, the [`Cert`] data structure never returns keys with the /// [`key::SecretParts`] marker. This means, to use any secret key /// material, e.g., when creating a [`Signer`], the user needs to /// explicitly opt-in by changing the marker using /// [`Key::parts_into_secret`] or [`Key::parts_as_secret`]. /// /// For `P`, the conversion functions are: [`Key::parts_into_public`], /// [`Key::parts_as_public`], [`Key::parts_into_secret`], /// [`Key::parts_as_secret`], [`Key::parts_into_unspecified`], and /// [`Key::parts_as_unspecified`]. With the exception of converting /// `P` to `key::SecretParts`, these functions are infallible. /// Converting `P` to `key::SecretParts` may fail if the key doesn't /// have any secret key material. (Note: although the secret key /// material is required, it is not checked for validity.) /// /// For `R`, the conversion functions are [`Key::role_into_primary`], /// [`Key::role_as_primary`], [`Key::role_into_subordinate`], /// [`Key::role_as_subordinate`], [`Key::role_into_unspecified`], and /// [`Key::role_as_unspecified`]. /// /// It is also possible to use `From`. /// /// [`Signer`]: crate::crypto::Signer /// [`Key::parts_as_secret`]: Key::parts_as_secret() /// [`Key::parts_into_public`]: Key::parts_into_public() /// [`Key::parts_as_public`]: Key::parts_as_public() /// [`Key::parts_into_secret`]: Key::parts_into_secret() /// [`Key::parts_as_secret`]: Key::parts_as_secret() /// [`Key::parts_into_unspecified`]: Key::parts_into_unspecified() /// [`Key::parts_as_unspecified`]: Key::parts_as_unspecified() /// [`Key::role_into_primary`]: Key::role_into_primary() /// [`Key::role_as_primary`]: Key::role_as_primary() /// [`Key::role_into_subordinate`]: Key::role_into_subordinate() /// [`Key::role_as_subordinate`]: Key::role_as_subordinate() /// [`Key::role_into_unspecified`]: Key::role_into_unspecified() /// [`Key::role_as_unspecified`]: Key::role_as_unspecified() /// /// ## Examples /// /// Changing a marker: /// /// ``` /// use sequoia_openpgp as openpgp; /// use openpgp::cert::prelude::*; /// use openpgp::packet::prelude::*; /// /// # fn main() -> openpgp::Result<()> { /// // Generate a new certificate. It has secret key material. /// let (cert, _) = CertBuilder::new() /// .generate()?; /// /// let pk: &Key<key::PublicParts, key::PrimaryRole> /// = cert.primary_key().key(); /// // `has_secret`s is one of the few methods that ignores the /// // parts type. /// assert!(pk.has_secret()); /// /// // Treat it like a secret key. This only works if `pk` really /// // has secret key material (which it does in this case, see above). /// let sk = pk.parts_as_secret()?; /// assert!(sk.has_secret()); /// /// // And back. /// let pk = sk.parts_as_public(); /// // Yes, the secret key material is still there. /// assert!(pk.has_secret()); /// # Ok(()) /// # } /// ``` /// /// The [`Cert`] data structure only returns public keys. To work /// with any secret key material, the `Key` first needs to be /// converted to a secret key. This is necessary, for instance, when /// creating a [`Signer`]: /// /// [`Cert`]: crate::Cert /// /// ```rust /// use std::time; /// use sequoia_openpgp as openpgp; /// # use openpgp::Result; /// use openpgp::cert::prelude::*; /// use openpgp::crypto::KeyPair; /// use openpgp::policy::StandardPolicy; /// /// # fn main() -> Result<()> { /// let p = &StandardPolicy::new(); /// /// let the_past = time::SystemTime::now() - time::Duration::from_secs(1); /// let (cert, _) = CertBuilder::new() /// .set_creation_time(the_past) /// .generate()?; /// /// // Set the certificate to expire now. To do this, we need /// // to create a new self-signature, and sign it using a /// // certification-capable key. The primary key is always /// // certification capable. /// let mut keypair = cert.primary_key() /// .key().clone().parts_into_secret()?.into_keypair()?; /// let sigs = cert.set_expiration_time(p, None, &mut keypair, /// Some(time::SystemTime::now()))?; /// /// let cert = cert.insert_packets(sigs)?.0; /// // It's expired now. /// assert!(cert.with_policy(p, None)?.alive().is_err()); /// # Ok(()) /// # } /// ``` /// /// # Key Generation /// /// `Key` is a wrapper around [the different key formats]. /// (Currently, Sequoia only supports version 6 and version 4 keys, /// however, future versions may add limited support for version 3 /// keys to facilitate working with achieved messages.) As such, it /// doesn't provide a mechanism to generate keys or import existing /// key material. Instead, use the format-specific functions (e.g., /// [`Key6::generate_ecc`]) and then convert the result into a `Key` /// packet, as the following example demonstrates. /// /// [the different key formats]: https://www.rfc-editor.org/rfc/rfc9580.html#name-public-key-packet-formats /// /// ## Examples /// /// ``` /// use sequoia_openpgp as openpgp; /// use openpgp::packet::prelude::*; /// use openpgp::types::Curve; /// /// # fn main() -> openpgp::Result<()> { /// let key: Key<key::SecretParts, key::PrimaryRole> /// = Key::from(Key6::generate_ecc(true, Curve::Ed25519)?); /// # Ok(()) /// # } /// ``` /// /// # Password Protection /// /// OpenPGP provides a mechanism to [password protect keys]. If a key /// is password protected, you need to decrypt the password using /// [`Key::decrypt_secret`] before using its secret key material /// (e.g., to decrypt a message, or to generate a signature). /// /// [password protect keys]: https://www.rfc-editor.org/rfc/rfc9580.html#section-3.7 /// [`Key::decrypt_secret`]: Key::decrypt_secret() /// /// # A note on equality /// /// The implementation of `Eq` for `Key` compares the serialized form /// of `Key`s. Comparing or serializing values of `Key<PublicParts, /// _>` ignore secret key material, whereas the secret key material is /// considered and serialized for `Key<SecretParts, _>`, and for /// `Key<UnspecifiedParts, _>` if present. To explicitly exclude the /// secret key material from the comparison, use [`Key::public_cmp`] /// or [`Key::public_eq`]. /// /// When merging in secret key material from untrusted sources, you /// need to be very careful: secret key material is not /// cryptographically protected by the key's self signature. Thus, an /// attacker can provide a valid key with a valid self signature, but /// invalid secret key material. If naively merged, this could /// overwrite valid secret key material, and thereby render the key /// useless. Unfortunately, the only way to find out that the secret /// key material is bad is to actually try using it. But, because the /// secret key material is usually encrypted, this can't always be /// done automatically. /// /// [`Key::public_cmp`]: Key::public_cmp() /// [`Key::public_eq`]: Key::public_eq() /// /// Compare: /// /// ``` /// use sequoia_openpgp as openpgp; /// use openpgp::cert::prelude::*; /// use openpgp::packet::prelude::*; /// use openpgp::packet::key::*; /// /// # fn main() -> openpgp::Result<()> { /// // Generate a new certificate. It has secret key material. /// let (cert, _) = CertBuilder::new() /// .generate()?; /// /// let sk: &Key<PublicParts, _> = cert.primary_key().key(); /// assert!(sk.has_secret()); /// /// // Strip the secret key material. /// let cert = cert.clone().strip_secret_key_material(); /// let pk: &Key<PublicParts, _> = cert.primary_key().key(); /// assert!(! pk.has_secret()); /// /// // Eq on Key<PublicParts, _> compares only the public bits, so it /// // considers pk and sk to be equal. /// assert_eq!(pk, sk); /// /// // Convert to Key<UnspecifiedParts, _>. /// let sk: &Key<UnspecifiedParts, _> = sk.parts_as_unspecified(); /// let pk: &Key<UnspecifiedParts, _> = pk.parts_as_unspecified(); /// /// // Eq on Key<UnspecifiedParts, _> compares both the public and the /// // secret bits, so it considers pk and sk to be different. /// assert_ne!(pk, sk); /// /// // In any case, Key::public_eq only compares the public bits, /// // so it considers them to be equal. /// assert!(Key::public_eq(pk, sk)); /// # Ok(()) /// # } /// ``` #[non_exhaustive] #[derive(PartialEq, Eq, Hash, Debug)] pub enum Key<P: key::KeyParts, R: key::KeyRole> { /// A version 4 `Key` packet. V4(Key4<P, R>), /// A version 6 `Key` packet. V6(Key6<P, R>), } assert_send_and_sync!(Key<P, R> where P: key::KeyParts, R: key::KeyRole); // derive(Clone) doesn't work as expected with generic type parameters // that don't implement clone: it adds a trait bound on Clone to P and // R in the Clone implementation. Happily, we don't need P or R to // implement Clone: they are just marker traits, which we can clone // manually. // // See: https://github.com/rust-lang/rust/issues/26925 impl<P, R> Clone for Key<P, R> where P: key::KeyParts, R: key::KeyRole { fn clone(&self) -> Self { match self { Key::V4(key) => Key::V4(key.clone()), Key::V6(key) => Key::V6(key.clone()), } } } impl<P: key::KeyParts, R: key::KeyRole> fmt::Display for Key<P, R> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { Key::V4(k) => k.fmt(f), Key::V6(k) => k.fmt(f), } } } impl From<Key<key::PublicParts, key::PrimaryRole>> for Packet { /// Convert the `Key` struct to a `Packet`. fn from(k: Key<key::PublicParts, key::PrimaryRole>) -> Self { Packet::PublicKey(k) } } impl From<Key<key::PublicParts, key::SubordinateRole>> for Packet { /// Convert the `Key` struct to a `Packet`. fn from(k: Key<key::PublicParts, key::SubordinateRole>) -> Self { Packet::PublicSubkey(k) } } impl From<Key<key::SecretParts, key::PrimaryRole>> for Packet { /// Convert the `Key` struct to a `Packet`. fn from(k: Key<key::SecretParts, key::PrimaryRole>) -> Self { Packet::SecretKey(k) } } impl From<Key<key::SecretParts, key::SubordinateRole>> for Packet { /// Convert the `Key` struct to a `Packet`. fn from(k: Key<key::SecretParts, key::SubordinateRole>) -> Self { Packet::SecretSubkey(k) } } impl<R: key::KeyRole> Key<key::SecretParts, R> { /// Gets the `Key`'s `SecretKeyMaterial`. pub fn secret(&self) -> &SecretKeyMaterial { match self { Key::V4(k) => k.secret(), Key::V6(k) => k.secret(), } } /// Gets a mutable reference to the `Key`'s `SecretKeyMaterial`. pub fn secret_mut(&mut self) -> &mut SecretKeyMaterial { match self { Key::V4(k) => k.secret_mut(), Key::V6(k) => k.secret_mut(), } } /// Creates a new key pair from a `Key` with an unencrypted /// secret key. /// /// If the `Key` is password protected, you first need to decrypt /// it using [`Key::decrypt_secret`]. /// /// [`Key::decrypt_secret`]: Key::decrypt_secret() /// /// # Errors /// /// Fails if the secret key is encrypted. /// /// # Examples /// /// Revoke a certificate by signing a new revocation certificate: /// /// ```rust /// use std::time; /// use sequoia_openpgp as openpgp; /// # use openpgp::Result; /// use openpgp::cert::prelude::*; /// use openpgp::crypto::KeyPair; /// use openpgp::types::ReasonForRevocation; /// /// # fn main() -> Result<()> { /// // Generate a certificate. /// let (cert, _) = /// CertBuilder::general_purpose(Some("Alice Lovelace <alice@example.org>")) /// .generate()?; /// /// // Use the secret key material to sign a revocation certificate. /// let mut keypair = cert.primary_key() /// .key().clone().parts_into_secret()? /// .into_keypair()?; /// let rev = cert.revoke(&mut keypair, /// ReasonForRevocation::KeyCompromised, /// b"It was the maid :/")?; /// # Ok(()) /// # } /// ``` pub fn into_keypair(self) -> Result<KeyPair> { match self { Key::V4(k) => k.into_keypair(), Key::V6(k) => k.into_keypair(), } } /// Decrypts the secret key material. /// /// In OpenPGP, secret key material can be [protected with a /// password]. The password is usually hardened using a [KDF]. /// /// [protected with a password]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.5.3 /// [KDF]: https://www.rfc-editor.org/rfc/rfc9580.html#section-3.7 /// /// This function takes ownership of the `Key`, decrypts the /// secret key material using the password, and returns a new key /// whose secret key material is not password protected. /// /// If the secret key material is not password protected or if the /// password is wrong, this function returns an error. /// /// # Examples /// /// Sign a new revocation certificate using a password-protected /// key: /// /// ```rust /// use sequoia_openpgp as openpgp; /// # use openpgp::Result; /// use openpgp::cert::prelude::*; /// use openpgp::types::ReasonForRevocation; /// /// # fn main() -> Result<()> { /// // Generate a certificate whose secret key material is /// // password protected. /// let (cert, _) = /// CertBuilder::general_purpose(Some("Alice Lovelace <alice@example.org>")) /// .set_password(Some("1234".into())) /// .generate()?; /// /// // Use the secret key material to sign a revocation certificate. /// let key = cert.primary_key().key().clone().parts_into_secret()?; /// /// // We can't turn it into a keypair without decrypting it. /// assert!(key.clone().into_keypair().is_err()); /// /// // And, we need to use the right password. /// assert!(key.clone() /// .decrypt_secret(&"correct horse battery staple".into()) /// .is_err()); /// /// // Let's do it right: /// let mut keypair = key.decrypt_secret(&"1234".into())?.into_keypair()?; /// let rev = cert.revoke(&mut keypair, /// ReasonForRevocation::KeyCompromised, /// b"It was the maid :/")?; /// # Ok(()) /// # } /// ``` pub fn decrypt_secret(self, password: &Password) -> Result<Self> { match self { Key::V4(k) => Ok(Key::V4(k.decrypt_secret(password)?)), Key::V6(k) => Ok(Key::V6(k.decrypt_secret(password)?)), } } /// Encrypts the secret key material. /// /// In OpenPGP, secret key material can be [protected with a /// password]. The password is usually hardened using a [KDF]. /// /// [protected with a password]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.5.3 /// [KDF]: https://www.rfc-editor.org/rfc/rfc9580.html#section-3.7 /// /// This function takes ownership of the `Key`, encrypts the /// secret key material using the password, and returns a new key /// whose secret key material is protected with the password. /// /// If the secret key material is already password protected, this /// function returns an error. /// /// # Examples /// /// This example demonstrates how to encrypt the secret key /// material of every key in a certificate. Decryption can be /// done the same way with [`Key::decrypt_secret`]. /// /// ```rust /// use sequoia_openpgp as openpgp; /// # use openpgp::Result; /// use openpgp::cert::prelude::*; /// use openpgp::packet::Packet; /// /// # fn main() -> Result<()> { /// // Generate a certificate whose secret key material is /// // not password protected. /// let (cert, _) = /// CertBuilder::general_purpose(Some("Alice Lovelace <alice@example.org>")) /// .generate()?; /// /// // Encrypt every key. /// let mut encrypted_keys: Vec<Packet> = Vec::new(); /// for ka in cert.keys().secret() { /// assert!(ka.key().has_unencrypted_secret()); /// /// // Encrypt the key's secret key material. /// let key = ka.key().clone().encrypt_secret(&"1234".into())?; /// assert!(! key.has_unencrypted_secret()); /// /// // We cannot merge it right now, because `cert` is borrowed. /// encrypted_keys.push(if ka.primary() { /// key.role_into_primary().into() /// } else { /// key.role_into_subordinate().into() /// }); /// } /// /// // Merge the keys into the certificate. Note: `Cert::insert_packets` /// // prefers added versions of keys. So, the encrypted version /// // will override the decrypted version. /// let cert = cert.insert_packets(encrypted_keys)?.0; /// /// // Now the every key's secret key material is encrypted. We'll /// // demonstrate this using the primary key: /// let key = cert.primary_key().key().parts_as_secret()?; /// assert!(! key.has_unencrypted_secret()); /// /// // We can't turn it into a keypair without decrypting it. /// assert!(key.clone().into_keypair().is_err()); /// /// // And, we need to use the right password. /// assert!(key.clone() /// .decrypt_secret(&"correct horse battery staple".into()) /// .is_err()); /// /// // Let's do it right: /// let mut keypair = key.clone() /// .decrypt_secret(&"1234".into())?.into_keypair()?; /// # Ok(()) /// # } /// ``` pub fn encrypt_secret(self, password: &Password) -> Result<Self> { match self { Key::V4(k) => Ok(Key::V4(k.encrypt_secret(password)?)), Key::V6(k) => Ok(Key::V6(k.encrypt_secret(password)?)), } } } macro_rules! impl_common_secret_functions { ($t: path) => { /// Secret key handling. impl<R: key::KeyRole> Key<$t, R> { /// Takes the key packet's `SecretKeyMaterial`, if any. pub fn take_secret(self) -> (Key<key::PublicParts, R>, Option<key::SecretKeyMaterial>) { match self { Key::V4(k) => { let (k, s) = k.take_secret(); (k.into(), s) }, Key::V6(k) => { let (k, s) = k.take_secret(); (k.into(), s) }, } } /// Adds `SecretKeyMaterial` to the packet, returning the old if /// any. pub fn add_secret(self, secret: key::SecretKeyMaterial) -> (Key<key::SecretParts, R>, Option<key::SecretKeyMaterial>) { match self { Key::V4(k) => { let (k, s) = k.add_secret(secret); (k.into(), s) }, Key::V6(k) => { let (k, s) = k.add_secret(secret); (k.into(), s) }, } } /// Takes the key packet's `SecretKeyMaterial`, if any. pub fn steal_secret(&mut self) -> Option<key::SecretKeyMaterial> { match self { Key::V4(k) => k.steal_secret(), Key::V6(k) => k.steal_secret(), } } } } } impl_common_secret_functions!(key::PublicParts); impl_common_secret_functions!(key::UnspecifiedParts); /// Secret key handling. impl<R: key::KeyRole> Key<key::SecretParts, R> { /// Takes the key packet's `SecretKeyMaterial`. pub fn take_secret(self) -> (Key<key::PublicParts, R>, key::SecretKeyMaterial) { match self { Key::V4(k) => { let (k, s) = k.take_secret(); (k.into(), s) }, Key::V6(k) => { let (k, s) = k.take_secret(); (k.into(), s) }, } } /// Adds `SecretKeyMaterial` to the packet, returning the old. pub fn add_secret(self, secret: key::SecretKeyMaterial) -> (Key<key::SecretParts, R>, key::SecretKeyMaterial) { match self { Key::V4(k) => { let (k, s) = k.add_secret(secret); (k.into(), s) }, Key::V6(k) => { let (k, s) = k.add_secret(secret); (k.into(), s) }, } } } /// Ordering, equality, and hashing on the public parts only. impl<P: key::KeyParts, R: key::KeyRole> Key<P, R> { /// Compares the public bits of two keys. /// /// This returns `Ordering::Equal` if the public MPIs, creation /// time, and algorithm of the two `Key4`s match. This does not /// consider the packets' encodings, packets' tags or their secret /// key material. pub fn public_cmp<PB, RB>(&self, b: &Key<PB, RB>) -> std::cmp::Ordering where PB: key::KeyParts, RB: key::KeyRole, { match (self, b) { (Key::V4(a), Key::V4(b)) => a.public_cmp(b), (Key::V6(a), Key::V6(b)) => a.public_cmp(b), // XXX: is that okay? (Key::V4(_), Key::V6(_)) => std::cmp::Ordering::Less, (Key::V6(_), Key::V4(_)) => std::cmp::Ordering::Greater, } } /// Tests whether two keys are equal modulo their secret key /// material. /// /// This returns true if the public MPIs, creation time and /// algorithm of the two `Key4`s match. This does not consider /// the packets' encodings, packets' tags or their secret key /// material. pub fn public_eq<PB, RB>(&self, b: &Key<PB, RB>) -> bool where PB: key::KeyParts, RB: key::KeyRole, { self.public_cmp(b) == std::cmp::Ordering::Equal } /// Hashes everything but any secret key material into state. /// /// This is an alternate implementation of [`Hash`], which never /// hashes the secret key material. /// /// [`Hash`]: std::hash::Hash pub fn public_hash<H>(&self, state: &mut H) where H: Hasher, { use std::hash::Hash; match self { Key::V4(k) => k.common.hash(state), Key::V6(k) => k.common.common.hash(state), } self.creation_time().hash(state); self.pk_algo().hash(state); Hash::hash(&self.mpis(), state); } } /// Immutable key interface. impl<P: key::KeyParts, R: key::KeyRole> Key<P, R> { /// Gets the version. pub fn version(&self) -> u8 { match self { Key::V4(_) => 4, Key::V6(_) => 6, } } /// Gets the `Key`'s creation time. pub fn creation_time(&self) -> std::time::SystemTime { match self { Key::V4(k) => k.creation_time(), Key::V6(k) => k.creation_time(), } } /// Sets the `Key`'s creation time. /// /// `timestamp` is converted to OpenPGP's internal format, /// [`Timestamp`]: a 32-bit quantity containing the number of /// seconds since the Unix epoch. /// /// `timestamp` is silently rounded to match the internal /// resolution. An error is returned if `timestamp` is out of /// range. /// /// [`Timestamp`]: crate::types::Timestamp pub fn set_creation_time<T>(&mut self, timestamp: T) -> Result<std::time::SystemTime> where T: Into<std::time::SystemTime>, { match self { Key::V4(k) => k.set_creation_time(timestamp.into()), Key::V6(k) => k.set_creation_time(timestamp.into()), } } /// Gets the public key algorithm. pub fn pk_algo(&self) -> PublicKeyAlgorithm { match self { Key::V4(k) => k.pk_algo(), Key::V6(k) => k.pk_algo(), } } /// Sets the public key algorithm. /// /// Returns the old public key algorithm. pub fn set_pk_algo(&mut self, pk_algo: PublicKeyAlgorithm) -> PublicKeyAlgorithm { match self { Key::V4(k) => k.set_pk_algo(pk_algo), Key::V6(k) => k.set_pk_algo(pk_algo), } } /// Returns a reference to the `Key`'s MPIs. pub fn mpis(&self) -> &mpi::PublicKey { match self { Key::V4(k) => k.mpis(), Key::V6(k) => k.mpis(), } } /// Returns a mutable reference to the `Key`'s MPIs. pub fn mpis_mut(&mut self) -> &mut mpi::PublicKey { match self { Key::V4(k) => k.mpis_mut(), Key::V6(k) => k.mpis_mut(), } } /// Sets the `Key`'s MPIs. /// /// This function returns the old MPIs, if any. pub fn set_mpis(&mut self, mpis: mpi::PublicKey) -> mpi::PublicKey { match self { Key::V4(k) => k.set_mpis(mpis), Key::V6(k) => k.set_mpis(mpis), } } /// Returns whether the `Key` contains secret key material. pub fn has_secret(&self) -> bool { match self { Key::V4(k) => k.has_secret(), Key::V6(k) => k.has_secret(), } } /// Returns whether the `Key` contains unencrypted secret key /// material. /// /// This returns false if the `Key` doesn't contain any secret key /// material. pub fn has_unencrypted_secret(&self) -> bool { match self { Key::V4(k) => k.has_unencrypted_secret(), Key::V6(k) => k.has_unencrypted_secret(), } } /// Returns `Key`'s secret key material, if any. pub fn optional_secret(&self) -> Option<&SecretKeyMaterial> { match self { Key::V4(k) => k.optional_secret(), Key::V6(k) => k.optional_secret(), } } /// Computes and returns the `Key`'s `Fingerprint` and returns it as /// a `KeyHandle`. /// /// See [Section 5.5.4 of RFC 9580]. /// /// [Section 5.5.4 of RFC 9580]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.5.4 pub fn key_handle(&self) -> crate::KeyHandle { match self { Key::V4(k) => k.key_handle(), Key::V6(k) => k.key_handle(), } } /// Computes and returns the `Key`'s `Fingerprint`. /// /// See [Section 5.5.4 of RFC 9580]. /// /// [Section 5.5.4 of RFC 9580]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.5.4 pub fn fingerprint(&self) -> crate::Fingerprint { match self { Key::V4(k) => k.fingerprint(), Key::V6(k) => k.fingerprint(), } } /// Computes and returns the `Key`'s `Key ID`. /// /// See [Section 5.5.4 of RFC 9580]. /// /// [Section 5.5.4 of RFC 9580]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.5.4 pub fn keyid(&self) -> crate::KeyID { match self { Key::V4(k) => k.keyid(), Key::V6(k) => k.keyid(), } } /// The security requirements of the hash algorithm for /// self-signatures. /// /// A cryptographic hash algorithm usually has [three security /// properties]: pre-image resistance, second pre-image /// resistance, and collision resistance. If an attacker can /// influence the signed data, then the hash algorithm needs to /// have both second pre-image resistance, and collision /// resistance. If not, second pre-image resistance is /// sufficient. /// /// [three security properties]: https://en.wikipedia.org/wiki/Cryptographic_hash_function#Properties /// /// In general, an attacker may be able to influence third-party /// signatures. But direct key signatures, and binding signatures /// are only over data fully determined by signer. And, an /// attacker's control over self signatures over User IDs is /// limited due to their structure. /// /// These observations can be used to extend the life of a hash /// algorithm after its collision resistance has been partially /// compromised, but not completely broken. For more details, /// please refer to the documentation for [HashAlgoSecurity]. /// /// [HashAlgoSecurity]: crate::policy::HashAlgoSecurity pub fn hash_algo_security(&self) -> HashAlgoSecurity { HashAlgoSecurity::SecondPreImageResistance } pub(crate) fn role(&self) -> key::KeyRoleRT { match self { Key::V4(k) => k.role(), Key::V6(k) => k.role(), } } pub(crate) fn set_role(&mut self, role: key::KeyRoleRT) { match self { Key::V4(k) => k.set_role(role), Key::V6(k) => k.set_role(role), } } } #[cfg(test)] impl<P, R> Arbitrary for Key<P, R> where P: KeyParts, R: KeyRole, Key4<P, R>: Arbitrary, Key6<P, R>: Arbitrary, { fn arbitrary(g: &mut Gen) -> Self { if <bool>::arbitrary(g) { Key4::arbitrary(g).into() } else { Key6::arbitrary(g).into() } } } /// A marker trait that captures whether a `Key` definitely contains /// secret key material. /// /// A [`Key`] can be treated as if it only has public key material /// ([`key::PublicParts`]) or also has secret key material /// ([`key::SecretParts`]). For those cases where the type /// information needs to be erased (e.g., interfaces like /// [`Cert::keys`]), we provide the [`key::UnspecifiedParts`] marker. /// /// Even if a `Key` does not have the `SecretKey` marker, it may still /// have secret key material. But, it will generally act as if it /// didn't. In particular, when serializing a `Key` without the /// `SecretKey` marker, secret key material will be ignored. See the /// documentation for [`Key`] for a demonstration of this behavior. /// /// [`Cert::keys`]: crate::cert::Cert::keys() /// [`Key`]: super::Key /// [`key::PublicParts`]: PublicParts /// [`key::SecretParts`]: SecretParts /// [`key::UnspecifiedParts`]: UnspecifiedParts /// /// # Sealed trait /// /// This trait is [sealed] and cannot be implemented for types outside this crate. /// Therefore it can be extended in a non-breaking way. /// If you want to implement the trait inside the crate /// you also need to implement the `seal::Sealed` marker trait. /// /// [sealed]: https://rust-lang.github.io/api-guidelines/future-proofing.html#sealed-traits-protect-against-downstream-implementations-c-sealed pub trait KeyParts: fmt::Debug + seal::Sealed { /// Converts a key with unspecified parts into this kind of key. /// /// This function is helpful when you need to convert a concrete /// type into a generic type. Using `From` works, but requires /// adding a type bound to the generic type, which is ugly and /// invasive. /// /// Converting a key with [`key::PublicParts`] or /// [`key::UnspecifiedParts`] will always succeed. However, /// converting a key to one with [`key::SecretParts`] only /// succeeds if the key actually contains secret key material. /// /// [`key::PublicParts`]: PublicParts /// [`key::UnspecifiedParts`]: UnspecifiedParts /// [`key::SecretParts`]: SecretParts /// /// # Examples /// /// For a less construed example, refer to the [source code]: /// /// [source code]: https://gitlab.com/search?search=convert_key&project_id=4469613&search_code=true&repository_ref=master /// /// ``` /// use sequoia_openpgp as openpgp; /// use openpgp::Result; /// # use openpgp::cert::prelude::*; /// use openpgp::packet::prelude::*; /// /// fn f<P>(cert: &Cert, mut key: Key<P, key::UnspecifiedRole>) /// -> Result<Key<P, key::UnspecifiedRole>> /// where P: key::KeyParts /// { /// // ... /// /// # let criterium = true; /// if criterium { /// // Cert::primary_key's return type is concrete /// // (Key<key::PublicParts, key::PrimaryRole>). We need to /// // convert it to the generic type Key<P, key::UnspecifiedRole>. /// // First, we "downcast" it to have unspecified parts and an /// // unspecified role, then we use a method defined by the /// // generic type to perform the conversion to the generic /// // type P. /// key = P::convert_key( /// cert.primary_key().key().clone() /// .parts_into_unspecified() /// .role_into_unspecified())?; /// } /// # else { unreachable!() } /// /// // ... /// /// Ok(key) /// } /// # fn main() -> openpgp::Result<()> { /// # let (cert, _) = /// # CertBuilder::general_purpose(Some("alice@example.org")) /// # .generate()?; /// # f(&cert, cert.primary_key().key().clone().role_into_unspecified())?; /// # Ok(()) /// # } /// ``` fn convert_key<R: KeyRole>(key: Key<UnspecifiedParts, R>) -> Result<Key<Self, R>> where Self: Sized; /// Converts a key reference with unspecified parts into this kind /// of key reference. /// /// This function is helpful when you need to convert a concrete /// type into a generic type. Using `From` works, but requires /// adding a type bound to the generic type, which is ugly and /// invasive. /// /// Converting a key with [`key::PublicParts`] or /// [`key::UnspecifiedParts`] will always succeed. However, /// converting a key to one with [`key::SecretParts`] only /// succeeds if the key actually contains secret key material. /// /// [`key::PublicParts`]: PublicParts /// [`key::UnspecifiedParts`]: UnspecifiedParts /// [`key::SecretParts`]: SecretParts fn convert_key_ref<R: KeyRole>(key: &Key<UnspecifiedParts, R>) -> Result<&Key<Self, R>> where Self: Sized; /// Converts a key bundle with unspecified parts into this kind of /// key bundle. /// /// This function is helpful when you need to convert a concrete /// type into a generic type. Using `From` works, but requires /// adding a type bound to the generic type, which is ugly and /// invasive. /// /// Converting a key bundle with [`key::PublicParts`] or /// [`key::UnspecifiedParts`] will always succeed. However, /// converting a key bundle to one with [`key::SecretParts`] only /// succeeds if the key bundle actually contains secret key /// material. /// /// [`key::PublicParts`]: PublicParts /// [`key::UnspecifiedParts`]: UnspecifiedParts /// [`key::SecretParts`]: SecretParts fn convert_bundle<R: KeyRole>(bundle: KeyBundle<UnspecifiedParts, R>) -> Result<KeyBundle<Self, R>> where Self: Sized; /// Converts a key bundle reference with unspecified parts into /// this kind of key bundle reference. /// /// This function is helpful when you need to convert a concrete /// type into a generic type. Using `From` works, but requires /// adding a type bound to the generic type, which is ugly and /// invasive. /// /// Converting a key bundle with [`key::PublicParts`] or /// [`key::UnspecifiedParts`] will always succeed. However, /// converting a key bundle to one with [`key::SecretParts`] only /// succeeds if the key bundle actually contains secret key /// material. /// /// [`key::PublicParts`]: PublicParts /// [`key::UnspecifiedParts`]: UnspecifiedParts /// [`key::SecretParts`]: SecretParts fn convert_bundle_ref<R: KeyRole>(bundle: &KeyBundle<UnspecifiedParts, R>) -> Result<&KeyBundle<Self, R>> where Self: Sized; /// Converts a key amalgamation with unspecified parts into this /// kind of key amalgamation. /// /// This function is helpful when you need to convert a concrete /// type into a generic type. Using `From` works, but requires /// adding a type bound to the generic type, which is ugly and /// invasive. /// /// Converting a key amalgamation with [`key::PublicParts`] or /// [`key::UnspecifiedParts`] will always succeed. However, /// converting a key amalgamation to one with [`key::SecretParts`] /// only succeeds if the key amalgamation actually contains secret /// key material. /// /// [`key::PublicParts`]: PublicParts /// [`key::UnspecifiedParts`]: UnspecifiedParts /// [`key::SecretParts`]: SecretParts fn convert_key_amalgamation<R: KeyRole>( ka: ComponentAmalgamation<Key<UnspecifiedParts, R>>) -> Result<ComponentAmalgamation<Key<Self, R>>> where Self: Sized; /// Converts a key amalgamation reference with unspecified parts /// into this kind of key amalgamation reference. /// /// This function is helpful when you need to convert a concrete /// type into a generic type. Using `From` works, but requires /// adding a type bound to the generic type, which is ugly and /// invasive. /// /// Converting a key amalgamation with [`key::PublicParts`] or /// [`key::UnspecifiedParts`] will always succeed. However, /// converting a key amalgamation to one with [`key::SecretParts`] /// only succeeds if the key amalgamation actually contains secret /// key material. /// /// [`key::PublicParts`]: PublicParts /// [`key::UnspecifiedParts`]: UnspecifiedParts /// [`key::SecretParts`]: SecretParts fn convert_key_amalgamation_ref<'a, R: KeyRole>( ka: &'a ComponentAmalgamation<'a, Key<UnspecifiedParts, R>>) -> Result<&'a ComponentAmalgamation<'a, Key<Self, R>>> where Self: Sized; /// Indicates that secret key material should be considered when /// comparing or hashing this key. fn significant_secrets() -> bool; } /// A marker trait that captures a `Key`'s role. /// /// A [`Key`] can either be a primary key ([`key::PrimaryRole`]) or a /// subordinate key ([`key::SubordinateRole`]). For those cases where /// the type information needs to be erased (e.g., interfaces like /// [`Cert::keys`]), we provide the [`key::UnspecifiedRole`] marker. /// /// [`Key`]: super::Key /// [`key::PrimaryRole`]: PrimaryRole /// [`key::SubordinateRole`]: SubordinateRole /// [`Cert::keys`]: crate::cert::Cert::keys() /// [`key::UnspecifiedRole`]: UnspecifiedRole /// /// # Sealed trait /// /// This trait is [sealed] and cannot be implemented for types outside this crate. /// Therefore it can be extended in a non-breaking way. /// If you want to implement the trait inside the crate /// you also need to implement the `seal::Sealed` marker trait. /// /// [sealed]: https://rust-lang.github.io/api-guidelines/future-proofing.html#sealed-traits-protect-against-downstream-implementations-c-sealed pub trait KeyRole: fmt::Debug + seal::Sealed { /// Converts a key with an unspecified role into this kind of key. /// /// This function is helpful when you need to convert a concrete /// type into a generic type. Using `From` works, but requires /// adding a type bound to the generic type, which is ugly and /// invasive. /// /// # Examples /// /// ``` /// use sequoia_openpgp as openpgp; /// use openpgp::Result; /// # use openpgp::cert::prelude::*; /// use openpgp::packet::prelude::*; /// /// fn f<R>(cert: &Cert, mut key: Key<key::UnspecifiedParts, R>) /// -> Result<Key<key::UnspecifiedParts, R>> /// where R: key::KeyRole /// { /// // ... /// /// # let criterium = true; /// if criterium { /// // Cert::primary_key's return type is concrete /// // (Key<key::PublicParts, key::PrimaryRole>). We need to /// // convert it to the generic type Key<key::UnspecifiedParts, R>. /// // First, we "downcast" it to have unspecified parts and an /// // unspecified role, then we use a method defined by the /// // generic type to perform the conversion to the generic /// // type R. /// key = R::convert_key( /// cert.primary_key().key().clone() /// .parts_into_unspecified() /// .role_into_unspecified()); /// } /// # else { unreachable!() } /// /// // ... /// /// Ok(key) /// } /// # fn main() -> openpgp::Result<()> { /// # let (cert, _) = /// # CertBuilder::general_purpose(Some("alice@example.org")) /// # .generate()?; /// # f(&cert, cert.primary_key().key().clone().parts_into_unspecified())?; /// # Ok(()) /// # } /// ``` fn convert_key<P: KeyParts>(key: Key<P, UnspecifiedRole>) -> Key<P, Self> where Self: Sized; /// Converts a key reference with an unspecified role into this /// kind of key reference. /// /// This function is helpful when you need to convert a concrete /// type into a generic type. Using `From` works, but requires /// adding a type bound to the generic type, which is ugly and /// invasive. fn convert_key_ref<P: KeyParts>(key: &Key<P, UnspecifiedRole>) -> &Key<P, Self> where Self: Sized; /// Converts a key bundle with an unspecified role into this kind /// of key bundle. /// /// This function is helpful when you need to convert a concrete /// type into a generic type. Using `From` works, but requires /// adding a type bound to the generic type, which is ugly and /// invasive. fn convert_bundle<P: KeyParts>(bundle: KeyBundle<P, UnspecifiedRole>) -> KeyBundle<P, Self> where Self: Sized; /// Converts a key bundle reference with an unspecified role into /// this kind of key bundle reference. /// /// This function is helpful when you need to convert a concrete /// type into a generic type. Using `From` works, but requires /// adding a type bound to the generic type, which is ugly and /// invasive. fn convert_bundle_ref<P: KeyParts>(bundle: &KeyBundle<P, UnspecifiedRole>) -> &KeyBundle<P, Self> where Self: Sized; /// Returns the role as a runtime value. fn role() -> KeyRoleRT; } /// A marker that indicates that a `Key` should be treated like a /// public key. /// /// Note: this doesn't indicate whether the data structure contains /// secret key material; it indicates whether any secret key material /// should be ignored. For instance, when exporting a key with the /// `PublicParts` marker, secret key material will *not* be exported. /// See the documentation for [`Key`] for a demonstration. /// /// Refer to [`KeyParts`] for details. /// /// [`Key`]: super::Key #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] pub struct PublicParts; assert_send_and_sync!(PublicParts); impl seal::Sealed for PublicParts {} impl KeyParts for PublicParts { fn convert_key<R: KeyRole>(key: Key<UnspecifiedParts, R>) -> Result<Key<Self, R>> { Ok(key.into()) } fn convert_key_ref<R: KeyRole>(key: &Key<UnspecifiedParts, R>) -> Result<&Key<Self, R>> { Ok(key.into()) } fn convert_bundle<R: KeyRole>(bundle: KeyBundle<UnspecifiedParts, R>) -> Result<KeyBundle<Self, R>> { Ok(bundle.into()) } fn convert_bundle_ref<R: KeyRole>(bundle: &KeyBundle<UnspecifiedParts, R>) -> Result<&KeyBundle<Self, R>> { Ok(bundle.into()) } fn convert_key_amalgamation<R: KeyRole>( ka: ComponentAmalgamation<Key<UnspecifiedParts, R>>) -> Result<ComponentAmalgamation<Key<Self, R>>> { Ok(ka.into()) } fn convert_key_amalgamation_ref<'a, R: KeyRole>( ka: &'a ComponentAmalgamation<'a, Key<UnspecifiedParts, R>>) -> Result<&'a ComponentAmalgamation<'a, Key<Self, R>>> { Ok(ka.into()) } fn significant_secrets() -> bool { false } } /// A marker that indicates that a `Key` should be treated like a /// secret key. /// /// Unlike the [`key::PublicParts`] marker, this marker asserts that /// the [`Key`] contains secret key material. Because secret key /// material is not protected by the self-signature, there is no /// indication that the secret key material is actually valid. /// /// Refer to [`KeyParts`] for details. /// /// [`key::PublicParts`]: PublicParts /// [`Key`]: super::Key #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] pub struct SecretParts; assert_send_and_sync!(SecretParts); impl seal::Sealed for SecretParts {} impl KeyParts for SecretParts { fn convert_key<R: KeyRole>(key: Key<UnspecifiedParts, R>) -> Result<Key<Self, R>>{ key.try_into() } fn convert_key_ref<R: KeyRole>(key: &Key<UnspecifiedParts, R>) -> Result<&Key<Self, R>> { key.try_into() } fn convert_bundle<R: KeyRole>(bundle: KeyBundle<UnspecifiedParts, R>) -> Result<KeyBundle<Self, R>> { bundle.try_into() } fn convert_bundle_ref<R: KeyRole>(bundle: &KeyBundle<UnspecifiedParts, R>) -> Result<&KeyBundle<Self, R>> { bundle.try_into() } fn convert_key_amalgamation<R: KeyRole>( ka: ComponentAmalgamation<Key<UnspecifiedParts, R>>) -> Result<ComponentAmalgamation<Key<Self, R>>> { ka.try_into() } fn convert_key_amalgamation_ref<'a, R: KeyRole>( ka: &'a ComponentAmalgamation<'a, Key<UnspecifiedParts, R>>) -> Result<&'a ComponentAmalgamation<'a, Key<Self, R>>> { ka.try_into() } fn significant_secrets() -> bool { true } } /// A marker that indicates that a `Key`'s parts are unspecified. /// /// Neither public key-specific nor secret key-specific operations are /// allowed on these types of keys. For instance, it is not possible /// to export a key with the `UnspecifiedParts` marker, because it is /// unclear how to treat any secret key material. To export such a /// key, you need to first change the marker to [`key::PublicParts`] /// or [`key::SecretParts`]. /// /// This marker is used when it is necessary to erase the type. For /// instance, we need to do this when mixing [`Key`]s with different /// markers in the same collection. See [`Cert::keys`] for an /// example. /// /// Refer to [`KeyParts`] for details. /// /// [`key::PublicParts`]: PublicParts /// [`key::SecretParts`]: SecretParts /// [`Key`]: super::Key /// [`Cert::keys`]: super::super::Cert::keys() #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] pub struct UnspecifiedParts; assert_send_and_sync!(UnspecifiedParts); impl seal::Sealed for UnspecifiedParts {} impl KeyParts for UnspecifiedParts { fn convert_key<R: KeyRole>(key: Key<UnspecifiedParts, R>) -> Result<Key<Self, R>> { Ok(key) } fn convert_key_ref<R: KeyRole>(key: &Key<UnspecifiedParts, R>) -> Result<&Key<Self, R>> { Ok(key) } fn convert_bundle<R: KeyRole>(bundle: KeyBundle<UnspecifiedParts, R>) -> Result<KeyBundle<Self, R>> { Ok(bundle) } fn convert_bundle_ref<R: KeyRole>(bundle: &KeyBundle<UnspecifiedParts, R>) -> Result<&KeyBundle<Self, R>> { Ok(bundle) } fn convert_key_amalgamation<R: KeyRole>( ka: ComponentAmalgamation<Key<UnspecifiedParts, R>>) -> Result<ComponentAmalgamation<Key<UnspecifiedParts, R>>> { Ok(ka) } fn convert_key_amalgamation_ref<'a, R: KeyRole>( ka: &'a ComponentAmalgamation<'a, Key<UnspecifiedParts, R>>) -> Result<&'a ComponentAmalgamation<'a, Key<Self, R>>> { Ok(ka) } fn significant_secrets() -> bool { true } } /// A marker that indicates the `Key` should be treated like a primary key. /// /// Refer to [`KeyRole`] for details. /// #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] pub struct PrimaryRole; assert_send_and_sync!(PrimaryRole); impl seal::Sealed for PrimaryRole {} impl KeyRole for PrimaryRole { fn convert_key<P: KeyParts>(key: Key<P, UnspecifiedRole>) -> Key<P, Self> { key.into() } fn convert_key_ref<P: KeyParts>(key: &Key<P, UnspecifiedRole>) -> &Key<P, Self> { key.into() } fn convert_bundle<P: KeyParts>(bundle: KeyBundle<P, UnspecifiedRole>) -> KeyBundle<P, Self> { bundle.into() } fn convert_bundle_ref<P: KeyParts>(bundle: &KeyBundle<P, UnspecifiedRole>) -> &KeyBundle<P, Self> { bundle.into() } fn role() -> KeyRoleRT { KeyRoleRT::Primary } } /// A marker that indicates the `Key` should be treated like a subkey. /// /// Refer to [`KeyRole`] for details. /// #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] pub struct SubordinateRole; assert_send_and_sync!(SubordinateRole); impl seal::Sealed for SubordinateRole {} impl KeyRole for SubordinateRole { fn convert_key<P: KeyParts>(key: Key<P, UnspecifiedRole>) -> Key<P, Self> { key.into() } fn convert_key_ref<P: KeyParts>(key: &Key<P, UnspecifiedRole>) -> &Key<P, Self> { key.into() } fn convert_bundle<P: KeyParts>(bundle: KeyBundle<P, UnspecifiedRole>) -> KeyBundle<P, Self> { bundle.into() } fn convert_bundle_ref<P: KeyParts>(bundle: &KeyBundle<P, UnspecifiedRole>) -> &KeyBundle<P, Self> { bundle.into() } fn role() -> KeyRoleRT { KeyRoleRT::Subordinate } } /// A marker that indicates the `Key`'s role is unspecified. /// /// Neither primary key-specific nor subkey-specific operations are /// allowed. To perform those operations, the marker first has to be /// changed to either [`key::PrimaryRole`] or /// [`key::SubordinateRole`], as appropriate. /// /// Refer to [`KeyRole`] for details. /// /// [`key::PrimaryRole`]: PrimaryRole /// [`key::SubordinateRole`]: SubordinateRole #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] pub struct UnspecifiedRole; assert_send_and_sync!(UnspecifiedRole); impl seal::Sealed for UnspecifiedRole {} impl KeyRole for UnspecifiedRole { fn convert_key<P: KeyParts>(key: Key<P, UnspecifiedRole>) -> Key<P, Self> { key } fn convert_key_ref<P: KeyParts>(key: &Key<P, UnspecifiedRole>) -> &Key<P, Self> { key } fn convert_bundle<P: KeyParts>(bundle: KeyBundle<P, UnspecifiedRole>) -> KeyBundle<P, Self> { bundle } fn convert_bundle_ref<P: KeyParts>(bundle: &KeyBundle<P, UnspecifiedRole>) -> &KeyBundle<P, Self> { bundle } fn role() -> KeyRoleRT { KeyRoleRT::Unspecified } } /// Encodes the key role at run time. /// /// While `KeyRole` tracks the key's role in the type system, /// `KeyRoleRT` tracks the key role at run time. /// /// When we are doing a reference conversion (e.g. by using /// [`Key::role_as_primary`]), we do not change the key's role. But, /// when we are doing an owned conversion (e.g. by using /// [`Key::role_into_primary`]), we do change the key's role. The /// rationale here is that the former conversion is done to allow a /// reference to be given to a function expecting a certain shape of /// key (e.g. to prevent excessive monomorphization), while the latter /// conversion signals intent (e.g. to put a key into a /// `Packet::PublicKey`). /// /// This is similar to how we have `KeyParts` that track the presence /// or absence of secret key material in the type system, yet at run /// time a key may or may not actually have secret key material (with /// the constraint that a key with `SecretParts` MUST have secret key /// material). #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum KeyRoleRT { /// The key is a primary key. Primary, /// The key is a subkey. Subordinate, /// The key's role is unspecified. Unspecified, } /// A Public Key. pub(crate) type PublicKey = Key<PublicParts, PrimaryRole>; /// A Public Subkey. pub(crate) type PublicSubkey = Key<PublicParts, SubordinateRole>; /// A Secret Key. pub(crate) type SecretKey = Key<SecretParts, PrimaryRole>; /// A Secret Subkey. pub(crate) type SecretSubkey = Key<SecretParts, SubordinateRole>; /// A key with public parts, and an unspecified role /// (`UnspecifiedRole`). #[allow(dead_code)] pub(crate) type UnspecifiedPublic = Key<PublicParts, UnspecifiedRole>; /// A key with secret parts, and an unspecified role /// (`UnspecifiedRole`). pub(crate) type UnspecifiedSecret = Key<SecretParts, UnspecifiedRole>; /// A primary key with unspecified parts (`UnspecifiedParts`). #[allow(dead_code)] pub(crate) type UnspecifiedPrimary = Key<UnspecifiedParts, PrimaryRole>; /// A subkey key with unspecified parts (`UnspecifiedParts`). #[allow(dead_code)] pub(crate) type UnspecifiedSecondary = Key<UnspecifiedParts, SubordinateRole>; /// A key whose parts and role are unspecified /// (`UnspecifiedParts`, `UnspecifiedRole`). #[allow(dead_code)] pub(crate) type UnspecifiedKey = Key<UnspecifiedParts, UnspecifiedRole>; /// Cryptographic operations using the key material. impl<P, R> Key<P, R> where P: key::KeyParts, R: key::KeyRole, { /// Encrypts the given data with this key. pub fn encrypt(&self, data: &SessionKey) -> Result<mpi::Ciphertext> { use crate::crypto::ecdh::aes_key_wrap; use crate::crypto::backend::{Backend, interface::{Asymmetric, Kdf}}; use crate::crypto::mpi::PublicKey; use PublicKeyAlgorithm::*; #[allow(deprecated, non_snake_case)] #[allow(clippy::erasing_op, clippy::identity_op)] match self.pk_algo() { X25519 => if let mpi::PublicKey::X25519 { u: U } = self.mpis() { // Generate an ephemeral key pair {v, V=vG} let (v, V) = Backend::x25519_generate_key()?; // Compute the shared point S = vU; let S = Backend::x25519_shared_point(&v, U)?; // Compute the wrap key. let wrap_algo = SymmetricAlgorithm::AES128; let mut ikm: SessionKey = vec![0; 32 + 32 + 32].into(); ikm[0 * 32..1 * 32].copy_from_slice(&V[..]); ikm[1 * 32..2 * 32].copy_from_slice(&U[..]); ikm[2 * 32..3 * 32].copy_from_slice(&S[..]); let mut kek = vec![0; wrap_algo.key_size()?].into(); Backend::hkdf_sha256(&ikm, None, b"OpenPGP X25519", &mut kek)?; let esk = aes_key_wrap(wrap_algo, kek.as_protected(), data.as_protected())?; Ok(mpi::Ciphertext::X25519 { e: Box::new(V), key: esk.into(), }) } else { Err(Error::MalformedPacket(format!( "Key: Expected X25519 public key, got {:?}", self.mpis())).into()) }, X448 => if let mpi::PublicKey::X448 { u: U } = self.mpis() { let (v, V) = Backend::x448_generate_key()?; // Compute the shared point S = vU; let S = Backend::x448_shared_point(&v, U)?; // Compute the wrap key. let wrap_algo = SymmetricAlgorithm::AES256; let mut ikm: SessionKey = vec![0; 56 + 56 + 56].into(); ikm[0 * 56..1 * 56].copy_from_slice(&V[..]); ikm[1 * 56..2 * 56].copy_from_slice(&U[..]); ikm[2 * 56..3 * 56].copy_from_slice(&S[..]); let mut kek = vec![0; wrap_algo.key_size()?].into(); Backend::hkdf_sha512(&ikm, None, b"OpenPGP X448", &mut kek)?; let esk = aes_key_wrap(wrap_algo, kek.as_protected(), data.as_protected())?; Ok(mpi::Ciphertext::X448 { e: Box::new(V), key: esk.into(), }) } else { Err(Error::MalformedPacket(format!( "Key: Expected X448 public key, got {:?}", self.mpis())).into()) }, RSASign | DSA | ECDSA | EdDSA | Ed25519 | Ed448 => Err(Error::InvalidOperation( format!("{} is not an encryption algorithm", self.pk_algo()) ).into()), ECDH if matches!(self.mpis(), PublicKey::ECDH { curve: Curve::Cv25519, ..}) => { let q = match self.mpis() { PublicKey::ECDH { q, .. } => q, _ => unreachable!(), }; // Obtain the authenticated recipient public key R let R = q.decode_point(&Curve::Cv25519)?.0; // Generate an ephemeral key pair {v, V=vG} // Compute the public key. let (v, VB) = Backend::x25519_generate_key()?; let VB = mpi::MPI::new_compressed_point(&VB); // Compute the shared point S = vR; let S = Backend::x25519_shared_point(&v, R.try_into()?)?; crate::crypto::ecdh::encrypt_wrap( self.parts_as_public(), data, VB, &S) }, RSAEncryptSign | RSAEncrypt | ElGamalEncrypt | ElGamalEncryptSign | ECDH | Private(_) | Unknown(_) => self.encrypt_backend(data), } } /// Verifies the given signature. pub fn verify(&self, sig: &mpi::Signature, hash_algo: HashAlgorithm, digest: &[u8]) -> Result<()> { use crate::crypto::backend::{Backend, interface::Asymmetric}; use crate::crypto::mpi::{PublicKey, Signature}; fn bad(e: impl ToString) -> anyhow::Error { Error::BadSignature(e.to_string()).into() } let ok = match (self.mpis(), sig) { (PublicKey::Ed25519 { a }, Signature::Ed25519 { s }) => Backend::ed25519_verify(a, digest, s)?, (PublicKey::Ed448 { a }, Signature::Ed448 { s }) => Backend::ed448_verify(a, digest, s)?, (PublicKey::EdDSA { curve, q }, Signature::EdDSA { r, s }) => match curve { Curve::Ed25519 => { let (public, ..) = q.decode_point(&Curve::Ed25519)?; assert_eq!(public.len(), 32); // OpenPGP encodes R and S separately, but our // cryptographic backends expect them to be // concatenated. let mut signature = Vec::with_capacity(64); // We need to zero-pad them at the front, because // the MPI encoding drops leading zero bytes. signature.extend_from_slice( &r.value_padded(32).map_err(bad)?); signature.extend_from_slice( &s.value_padded(32).map_err(bad)?); // Let's see if we got it right. debug_assert_eq!(signature.len(), 64); Backend::ed25519_verify(public.try_into()?, digest, &signature.as_slice().try_into()?)? }, _ => return Err(Error::UnsupportedEllipticCurve(curve.clone()).into()), }, (PublicKey::RSA { .. }, Signature::RSA { .. }) | (PublicKey::DSA { .. }, Signature::DSA { .. }) | (PublicKey::ECDSA { .. }, Signature::ECDSA { .. }) => return self.verify_backend(sig, hash_algo, digest), _ => return Err(Error::MalformedPacket(format!( "unsupported combination of key {} and signature {:?}.", self.pk_algo(), sig)).into()), }; if ok { Ok(()) } else { Err(Error::ManipulatedMessage.into()) } } } /// Holds secret key material. /// /// This type allows postponing the decryption of the secret key /// material until it is actually needed. /// /// If the secret key material is not encrypted with a password, then /// we encrypt it in memory. This helps protect against /// [heartbleed]-style attacks where a buffer over-read allows an /// attacker to read from the process's address space. This /// protection is less important for Rust programs, which are memory /// safe. However, it is essential when Sequoia is used via its FFI. /// /// See [`crypto::mem::Encrypted`] for details. /// /// [heartbleed]: https://en.wikipedia.org/wiki/Heartbleed /// [`crypto::mem::Encrypted`]: super::super::crypto::mem::Encrypted #[derive(PartialEq, Eq, Hash, Clone, Debug)] pub enum SecretKeyMaterial { /// Unencrypted secret key. Can be used as-is. Unencrypted(Unencrypted), /// The secret key is encrypted with a password. Encrypted(Encrypted), } assert_send_and_sync!(SecretKeyMaterial); impl From<mpi::SecretKeyMaterial> for SecretKeyMaterial { fn from(mpis: mpi::SecretKeyMaterial) -> Self { SecretKeyMaterial::Unencrypted(mpis.into()) } } impl From<Unencrypted> for SecretKeyMaterial { fn from(key: Unencrypted) -> Self { SecretKeyMaterial::Unencrypted(key) } } impl From<Encrypted> for SecretKeyMaterial { fn from(key: Encrypted) -> Self { SecretKeyMaterial::Encrypted(key) } } impl SecretKeyMaterial { /// Decrypts the secret key material using `password`. /// /// The `SecretKeyMaterial` type does not know what kind of key it /// contains. So, in order to know how many MPIs to parse, the /// public key algorithm needs to be provided explicitly. /// /// This returns an error if the secret key material is not /// encrypted or the password is incorrect. pub fn decrypt<P, R>(mut self, key: &Key<P, R>, password: &Password) -> Result<Self> where P: KeyParts, R: KeyRole, { self.decrypt_in_place(key, password)?; Ok(self) } /// Decrypts the secret key material using `password`. /// /// The `SecretKeyMaterial` type does not know what kind of key it /// contains. So, in order to know how many MPIs to parse, the /// public key algorithm needs to be provided explicitly. /// /// This returns an error if the secret key material is not /// encrypted or the password is incorrect. pub fn decrypt_in_place<P, R>(&mut self, key: &Key<P, R>, password: &Password) -> Result<()> where P: KeyParts, R: KeyRole, { match self { SecretKeyMaterial::Encrypted(e) => { *self = e.decrypt(key, password)?.into(); Ok(()) } SecretKeyMaterial::Unencrypted(_) => Err(Error::InvalidArgument( "secret key is not encrypted".into()).into()), } } /// Encrypts the secret key material using `password`. /// /// This returns an error if the secret key material is encrypted. /// /// See [`Unencrypted::encrypt`] for details. pub fn encrypt<P, R>(mut self, key: &Key<P, R>, password: &Password) -> Result<Self> where P: KeyParts, R: KeyRole, { self.encrypt_in_place(key, password)?; Ok(self) } /// Encrypts the secret key material using `password` with the /// given parameters. /// /// This returns an error if the secret key material is encrypted. /// /// See [`Unencrypted::encrypt_with`] for details. pub fn encrypt_with<P, R>(mut self, key: &Key<P, R>, s2k: S2K, symm: SymmetricAlgorithm, aead: Option<AEADAlgorithm>, password: &Password) -> Result<Self> where P: KeyParts, R: KeyRole, { self.encrypt_in_place_with(key, s2k, symm, aead, password)?; Ok(self) } /// Encrypts the secret key material using `password`. /// /// This returns an error if the secret key material is encrypted. /// /// See [`Unencrypted::encrypt`] for details. pub fn encrypt_in_place<P, R>(&mut self, key: &Key<P, R>, password: &Password) -> Result<()> where P: KeyParts, R: KeyRole, { match self { SecretKeyMaterial::Unencrypted(ref u) => { *self = SecretKeyMaterial::Encrypted( u.encrypt(key, password)?); Ok(()) } SecretKeyMaterial::Encrypted(_) => Err(Error::InvalidArgument( "secret key is encrypted".into()).into()), } } /// Encrypts the secret key material using `password` and the /// given parameters. /// /// This returns an error if the secret key material is encrypted. /// /// See [`Unencrypted::encrypt`] for details. pub fn encrypt_in_place_with<P, R>(&mut self, key: &Key<P, R>, s2k: S2K, symm: SymmetricAlgorithm, aead: Option<AEADAlgorithm>, password: &Password) -> Result<()> where P: KeyParts, R: KeyRole, { match self { SecretKeyMaterial::Unencrypted(ref u) => { *self = SecretKeyMaterial::Encrypted( u.encrypt_with(key, s2k, symm, aead, password)?); Ok(()) } SecretKeyMaterial::Encrypted(_) => Err(Error::InvalidArgument( "secret key is encrypted".into()).into()), } } /// Returns whether the secret key material is encrypted. pub fn is_encrypted(&self) -> bool { match self { SecretKeyMaterial::Encrypted(_) => true, SecretKeyMaterial::Unencrypted(_) => false, } } } /// Unencrypted secret key material. /// /// This data structure is used by the [`SecretKeyMaterial`] enum. /// /// Unlike an [`Encrypted`] key, this key can be used as-is. /// /// The secret key is encrypted in memory and only decrypted on /// demand. This helps protect against [heartbleed]-style /// attacks where a buffer over-read allows an attacker to read from /// the process's address space. This protection is less important /// for Rust programs, which are memory safe. However, it is /// essential when Sequoia is used via its FFI. /// /// See [`crypto::mem::Encrypted`] for details. /// /// [heartbleed]: https://en.wikipedia.org/wiki/Heartbleed /// [`crypto::mem::Encrypted`]: super::super::crypto::mem::Encrypted // Note: PartialEq, Eq, and Hash on mem::Encrypted does the right // thing. #[derive(Clone, Debug, PartialEq, Eq, Hash)] pub struct Unencrypted { /// MPIs of the secret key. mpis: mem::Encrypted, } assert_send_and_sync!(Unencrypted); impl From<mpi::SecretKeyMaterial> for Unencrypted { fn from(mpis: mpi::SecretKeyMaterial) -> Self { use crate::serialize::MarshalInto; // We need to store the type. let mut plaintext = mem::Protected::new(1 + mpis.serialized_len()); plaintext[0] = mpis.algo().unwrap_or(PublicKeyAlgorithm::Unknown(0)).into(); mpis.serialize_into(&mut plaintext[1..]) .expect("MPI serialization to vec failed"); Unencrypted { mpis: mem::Encrypted::new(plaintext) .expect("encrypting memory failed"), } } } impl Unencrypted { /// Maps the given function over the secret. pub fn map<F, T>(&self, mut fun: F) -> T where F: FnMut(&mpi::SecretKeyMaterial) -> T { self.mpis.map(|plaintext| { let algo: PublicKeyAlgorithm = plaintext[0].into(); let mpis = mpi::SecretKeyMaterial::from_bytes(algo, &plaintext[1..]) .expect("Decrypted secret key is malformed"); fun(&mpis) }) } /// Encrypts the secret key material using `password`. /// /// This encrypts the secret key material using AES-128/OCB and a /// key derived from the `password` using the default [`S2K`] /// scheme. pub fn encrypt<P, R>(&self, key: &Key<P, R>, password: &Password) -> Result<Encrypted> where P: KeyParts, R: KeyRole, { // Pick sensible parameters according to the key version. let (s2k, symm, aead) = match key.version() { 6 => ( S2K::default(), SymmetricAlgorithm::AES128, Some(AEADAlgorithm::OCB), ), _ => ( S2K::default(), SymmetricAlgorithm::default(), None, ), }; self.encrypt_with(key, s2k, symm, aead, password) } /// Encrypts the secret key material using `password` and the /// given parameters. pub fn encrypt_with<P, R>(&self, key: &Key<P, R>, s2k: S2K, symm: SymmetricAlgorithm, aead: Option<AEADAlgorithm>, password: &Password) -> Result<Encrypted> where P: KeyParts, R: KeyRole, { use std::io::Write; use crate::crypto::symmetric::Encryptor; let derived_key = s2k.derive_key(password, symm.key_size()?)?; let checksum = Default::default(); constrain_encryption_methods(key, &s2k, symm, aead, Some(checksum))?; if matches!(s2k, S2K::Argon2 { .. }) && aead.is_none() { return Err(Error::InvalidOperation( "Argon2 MUST be used with an AEAD mode".into()).into()); } if let Some(aead) = aead { use crate::serialize::MarshalInto; let mut iv = vec![0; aead.nonce_size()?]; crypto::random(&mut iv)?; let schedule = Key253Schedule::new( match key.role() { KeyRoleRT::Primary => Tag::SecretKey, KeyRoleRT::Subordinate => Tag::SecretSubkey, KeyRoleRT::Unspecified => return Err(Error::InvalidOperation( "cannot encrypt key with unspecified role".into()).into()), }, key.parts_as_public(), derived_key, symm, aead, &iv)?; let mut enc = schedule.encryptor()?; // Encrypt the secret key. let esk = self.map(|mpis| -> Result<Vec<u8>> { let mut esk = vec![0; mpis.serialized_len() + aead.digest_size()?]; let secret = mpis.to_vec()?; enc.encrypt_seal(&mut esk, &secret)?; Ok(esk) })?; Ok(Encrypted::new_aead(s2k, symm, aead, iv.into_boxed_slice(), esk.into_boxed_slice())) } else { // Ciphertext is preceded by a random block. let mut trash = vec![0u8; symm.block_size()?]; crypto::random(&mut trash)?; let mut esk = Vec::new(); let mut encryptor = Encryptor::new(symm, &derived_key, &mut esk)?; encryptor.write_all(&trash)?; self.map(|mpis| mpis.serialize_with_checksum(&mut encryptor, checksum))?; drop(encryptor); Ok(Encrypted::new(s2k, symm, Some(checksum), esk.into_boxed_slice())) } } } /// Secret key material encrypted with a password. /// /// This data structure is used by the [`SecretKeyMaterial`] enum. /// #[derive(Clone, Debug)] pub struct Encrypted { /// Key derivation mechanism to use. s2k: S2K, /// Symmetric algorithm used to encrypt the secret key material. algo: SymmetricAlgorithm, /// AEAD algorithm and IV used to encrypt the secret key material. aead: Option<(AEADAlgorithm, Box<[u8]>)>, /// Checksum method. checksum: Option<mpi::SecretKeyChecksum>, /// Encrypted MPIs prefixed with the IV. /// /// If we recognized the S2K object during parsing, we can /// successfully parse the data into S2K, IV, and ciphertext. /// However, if we do not recognize the S2K type, we do not know /// how large its parameters are, so we cannot cleanly parse it, /// and have to accept that the S2K's body bleeds into the rest of /// the data. ciphertext: std::result::Result<(usize, // IV length Box<[u8]>), // IV + ciphertext. Box<[u8]>>, // S2K body + IV + ciphertext. } assert_send_and_sync!(Encrypted); // Because the S2K and ciphertext cannot be cleanly separated at parse // time, we need to carefully compare and hash encrypted key packets. impl PartialEq for Encrypted { fn eq(&self, other: &Encrypted) -> bool { self.algo == other.algo && self.aead == other.aead && self.checksum == other.checksum && match (&self.ciphertext, &other.ciphertext) { (Ok(a), Ok(b)) => self.s2k == other.s2k && a == b, (Err(a_raw), Err(b_raw)) => { // Treat S2K and ciphertext as opaque blob. // XXX: This would be nicer without the allocations. use crate::serialize::MarshalInto; let mut a = self.s2k.to_vec().unwrap(); let mut b = other.s2k.to_vec().unwrap(); a.extend_from_slice(a_raw); b.extend_from_slice(b_raw); a == b }, _ => false, } } } impl Eq for Encrypted {} impl std::hash::Hash for Encrypted { fn hash<H: std::hash::Hasher>(&self, state: &mut H) { self.algo.hash(state); self.aead.hash(state); self.checksum.hash(state); match &self.ciphertext { Ok(c) => { self.s2k.hash(state); c.hash(state); }, Err(c) => { // Treat S2K and ciphertext as opaque blob. // XXX: This would be nicer without the allocations. use crate::serialize::MarshalInto; let mut a = self.s2k.to_vec().unwrap(); a.extend_from_slice(c); a.hash(state); }, } } } impl Encrypted { /// Creates a new encrypted key object. pub fn new(s2k: S2K, algo: SymmetricAlgorithm, checksum: Option<mpi::SecretKeyChecksum>, ciphertext: Box<[u8]>) -> Self { Self::new_raw(s2k, algo, checksum, Ok((0, ciphertext))) } /// Creates a new encrypted key object. pub fn new_aead(s2k: S2K, sym_algo: SymmetricAlgorithm, aead_algo: AEADAlgorithm, aead_iv: Box<[u8]>, ciphertext: Box<[u8]>) -> Self { Encrypted { s2k, algo: sym_algo, aead: Some((aead_algo, aead_iv)), checksum: None, ciphertext: Ok((0, ciphertext)), } } /// Creates a new encrypted key object. pub(crate) fn new_raw(s2k: S2K, algo: SymmetricAlgorithm, checksum: Option<mpi::SecretKeyChecksum>, ciphertext: std::result::Result<(usize, Box<[u8]>), Box<[u8]>>) -> Self { Encrypted { s2k, algo, aead: None, checksum, ciphertext } } /// Returns the key derivation mechanism. pub fn s2k(&self) -> &S2K { &self.s2k } /// Returns the symmetric algorithm used to encrypt the secret /// key material. pub fn algo(&self) -> SymmetricAlgorithm { self.algo } /// Returns the AEAD algorithm used to encrypt the secret key /// material. pub fn aead_algo(&self) -> Option<AEADAlgorithm> { self.aead.as_ref().map(|(a, _iv)| *a) } /// Returns the AEAD IV used to encrypt the secret key material. pub fn aead_iv(&self) -> Option<&[u8]> { self.aead.as_ref().map(|(_a, iv)| &iv[..]) } /// Returns the checksum method used to protect the encrypted /// secret key material, if any. pub fn checksum(&self) -> Option<mpi::SecretKeyChecksum> { self.checksum } /// Returns the encrypted secret key material. /// /// If the [`S2K`] mechanism is not supported by Sequoia, this /// function will fail. Note that the information is not lost, /// but stored in the packet. If the packet is serialized again, /// it is written out. /// /// [`S2K`]: super::super::crypto::S2K pub fn ciphertext(&self) -> Result<&[u8]> { self.ciphertext .as_ref() .map(|(_cfb_iv_len, ciphertext)| &ciphertext[..]) .map_err(|_| Error::MalformedPacket( format!("Unknown S2K: {:?}", self.s2k)).into()) } /// Returns the encrypted secret key material, possibly including /// the body of the S2K object. pub(crate) fn raw_ciphertext(&self) -> &[u8] { match self.ciphertext.as_ref() { Ok((_cfb_iv_len, ciphertext)) => &ciphertext[..], Err(s2k_ciphertext) => &s2k_ciphertext[..], } } /// Returns the length of the CFB IV, if used. /// /// In v6 key packets, we explicitly model the length of the IV, /// but in Sequoia we store the IV and the ciphertext as one /// block, due to how bad this was modeled in v4 key packets. /// However, now that our in-core representation is less precise /// to support v4, we need to track this length to uphold our /// equality guarantee. pub(crate) fn cfb_iv_len(&self) -> usize { self.ciphertext.as_ref().ok() .map(|(cfb_iv_len, _)| *cfb_iv_len) .unwrap_or(0) } /// Decrypts the secret key material using `password`. /// /// The `Encrypted` key does not know what kind of key it is, so /// the public key algorithm is needed to parse the correct number /// of MPIs. pub fn decrypt<P, R>(&self, key: &Key<P, R>, password: &Password) -> Result<Unencrypted> where P: KeyParts, R: KeyRole, { use std::io::{Cursor, Read}; use crate::crypto; constrain_encryption_methods( key, &self.s2k, self.algo,self.aead.as_ref().map(|(a, _)| *a), self.checksum)?; let derived_key = self.s2k.derive_key(password, self.algo.key_size()?)?; let ciphertext = self.ciphertext()?; if let Some((aead, iv)) = &self.aead { let schedule = Key253Schedule::new( match key.role() { KeyRoleRT::Primary => Tag::SecretKey, KeyRoleRT::Subordinate => Tag::SecretSubkey, KeyRoleRT::Unspecified => return Err(Error::InvalidOperation( "cannot decrypt key with unspecified role".into()).into()), }, key.parts_as_public(), derived_key, self.algo, *aead, iv)?; let mut dec = schedule.decryptor()?; // Read the secret key. let mut secret = mem::Protected::new( ciphertext.len().saturating_sub(aead.digest_size()?)); dec.decrypt_verify(&mut secret, ciphertext)?; mpi::SecretKeyMaterial::from_bytes( key.pk_algo(), &secret).map(|m| m.into()) } else { let cur = Cursor::new(ciphertext); let mut dec = crypto::symmetric::Decryptor::new(self.algo, &derived_key, cur)?; // Consume the first block. let block_size = self.algo.block_size()?; let mut trash = mem::Protected::new(block_size); dec.read_exact(&mut trash)?; // Read the secret key. let mut secret = mem::Protected::new(ciphertext.len() - block_size); dec.read_exact(&mut secret)?; mpi::SecretKeyMaterial::from_bytes_with_checksum( key.pk_algo(), &secret, self.checksum.unwrap_or_default()) .map(|m| m.into()) } } } /// Constrains the secret key material encryption methods according to /// [Section 3.7.2.1. of RFC 9580]. /// /// [Section 3.7.2.1. of RFC 9580]: https://www.rfc-editor.org/rfc/rfc9580.html#section-3.7.2.1 fn constrain_encryption_methods<P, R>(key: &Key<P, R>, s2k: &S2K, _symm: SymmetricAlgorithm, aead: Option<AEADAlgorithm>, checksum: Option<mpi::SecretKeyChecksum>) -> Result<()> where P: KeyParts, R: KeyRole, { #[allow(deprecated)] match s2k { S2K::Argon2 { .. } if aead.is_none() => Err(Error::InvalidOperation( "Argon2 MUST be used with an AEAD mode".into()).into()), S2K::Implicit if key.version() == 6 => Err(Error::InvalidOperation( "Implicit S2K MUST NOT be used with v6 keys".into()).into()), // Technically not forbidden, but this is a terrible idea and // I doubt that anyone depends on it. Let's see whether we // can get away with being strict here. S2K::Simple { .. } if key.version() == 6 => Err(Error::InvalidOperation( "Simple S2K SHOULD NOT be used with v6 keys".into()).into()), _ if key.version() == 6 && aead.is_none() && checksum != Some(mpi::SecretKeyChecksum::SHA1) => Err(Error::InvalidOperation( "Malleable CFB MUST NOT be used with v6 keys".into()).into()), _ => Ok(()), } } pub(crate) struct Key253Schedule<'a> { symm: SymmetricAlgorithm, aead: AEADAlgorithm, nonce: &'a [u8], kek: SessionKey, ad: Vec<u8> } impl<'a> Key253Schedule<'a> { fn new<R>(tag: Tag, key: &Key<PublicParts, R>, derived_key: SessionKey, symm: SymmetricAlgorithm, aead: AEADAlgorithm, nonce: &'a [u8]) -> Result<Self> where R: KeyRole, { use crate::serialize::{Marshal, MarshalInto}; use crate::crypto::backend::{Backend, interface::Kdf}; let info = [ 0b1100_0000 | u8::from(tag), // Canonicalized packet type. key.version(), symm.into(), aead.into(), ]; let mut kek = vec![0; symm.key_size()?].into(); Backend::hkdf_sha256(&derived_key, None, &info, &mut kek)?; let mut ad = Vec::with_capacity(key.serialized_len()); ad.push(0b1100_0000 | u8::from(tag)); // Canonicalized packet type. key.serialize(&mut ad)?; Ok(Self { symm, aead, nonce, kek, ad, }) } fn decryptor(&self) -> Result<Box<dyn crypto::aead::Aead>> { use crypto::aead::CipherOp; self.aead.context(self.symm, &self.kek, &self.ad, self.nonce, CipherOp::Decrypt) } fn encryptor(&self) -> Result<Box<dyn crypto::aead::Aead>> { use crypto::aead::CipherOp; self.aead.context(self.symm, &self.kek, &self.ad, self.nonce, CipherOp::Encrypt) } } #[cfg(test)] mod tests { use crate::packet::Key; use crate::Cert; use crate::packet::key::SecretKeyMaterial; use crate::packet::Packet; use super::*; use crate::parse::Parse; #[test] fn encrypted_rsa_key() { let cert = Cert::from_bytes( crate::tests::key("testy-new-encrypted-with-123.pgp")).unwrap(); let key = cert.primary_key().key().clone(); let (key, secret) = key.take_secret(); let mut secret = secret.unwrap(); assert!(secret.is_encrypted()); secret.decrypt_in_place(&key, &"123".into()).unwrap(); assert!(!secret.is_encrypted()); let (pair, _) = key.add_secret(secret); assert!(pair.has_unencrypted_secret()); match pair.secret() { SecretKeyMaterial::Unencrypted(ref u) => u.map(|mpis| match mpis { mpi::SecretKeyMaterial::RSA { .. } => (), _ => panic!(), }), _ => panic!(), } } #[test] fn primary_key_encrypt_decrypt() -> Result<()> { key_encrypt_decrypt::<PrimaryRole>() } #[test] fn subkey_encrypt_decrypt() -> Result<()> { key_encrypt_decrypt::<SubordinateRole>() } fn key_encrypt_decrypt<R>() -> Result<()> where R: KeyRole + PartialEq, { let mut g = quickcheck::Gen::new(256); let p: Password = Vec::<u8>::arbitrary(&mut g).into(); let check = |key: Key<SecretParts, R>| -> Result<()> { let encrypted = key.clone().encrypt_secret(&p)?; let decrypted = encrypted.decrypt_secret(&p)?; assert_eq!(key, decrypted); Ok(()) }; use crate::types::Curve::*; for curve in vec![NistP256, NistP384, NistP521, Ed25519] { if ! curve.is_supported() { eprintln!("Skipping unsupported {}", curve); continue; } let key: Key4<_, R> = Key4::generate_ecc(true, curve.clone())?; check(key.into())?; let key: Key6<_, R> = Key6::generate_ecc(true, curve.clone())?; check(key.into())?; } for bits in vec![2048, 3072] { if ! PublicKeyAlgorithm::RSAEncryptSign.is_supported() { eprintln!("Skipping unsupported RSA"); continue; } let key: Key4<_, R> = Key4::generate_rsa(bits)?; check(key.into())?; let key: Key6<_, R> = Key6::generate_rsa(bits)?; check(key.into())?; } Ok(()) } quickcheck! { fn roundtrip_public(p: Key<PublicParts, UnspecifiedRole>) -> bool { use crate::parse::Parse; use crate::serialize::MarshalInto; let buf = p.to_vec().expect("Failed to serialize key"); let q = Key::from_bytes(&buf).expect("Failed to parse key").into(); assert_eq!(p, q); true } } quickcheck! { fn roundtrip_secret(p: Key<SecretParts, PrimaryRole>) -> bool { use crate::parse::Parse; use crate::serialize::MarshalInto; let buf = p.to_vec().expect("Failed to serialize key"); let q = Key::from_bytes(&buf).expect("Failed to parse key") .parts_into_secret().expect("No secret material") .role_into_primary(); assert_eq!(p, q); true } } fn mutate_eq_discriminates_key<P, R>(key: Key<P, R>, i: usize) -> bool where P: KeyParts, R: KeyRole, Key<P, R>: Into<Packet>, { use crate::serialize::MarshalInto; let p: Packet = key.into(); let mut buf = p.to_vec().unwrap(); // Avoid first two bytes so that we don't change the // type and reduce the chance of changing the length. if buf.len() < 3 { return true; } let bit = i % ((buf.len() - 2) * 8) + 16; buf[bit / 8] ^= 1 << (bit % 8); let ok = match Packet::from_bytes(&buf) { Ok(q) => p != q, Err(_) => true, // Packet failed to parse. }; if ! ok { eprintln!("mutate_eq_discriminates_key for ({:?}, {})", p, i); } ok } // Given a packet and a position, induces a bit flip in the // serialized form, then checks that PartialEq detects that. // Recall that for packets, PartialEq is defined using the // serialized form. quickcheck! { fn mutate_eq_discriminates_pp(key: Key<PublicParts, PrimaryRole>, i: usize) -> bool { mutate_eq_discriminates_key(key, i) } } quickcheck! { fn mutate_eq_discriminates_ps(key: Key<PublicParts, SubordinateRole>, i: usize) -> bool { mutate_eq_discriminates_key(key, i) } } quickcheck! { fn mutate_eq_discriminates_sp(key: Key<SecretParts, PrimaryRole>, i: usize) -> bool { mutate_eq_discriminates_key(key, i) } } quickcheck! { fn mutate_eq_discriminates_ss(key: Key<SecretParts, SubordinateRole>, i: usize) -> bool { mutate_eq_discriminates_key(key, i) } } } ��������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/src/packet/literal.rs���������������������������������������������������������0000644�0000000�0000000�00000017157�10461020230�0017012�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������use std::fmt; use std::cmp; use std::convert::TryInto; use std::time; #[cfg(test)] use quickcheck::{Arbitrary, Gen}; use crate::types::{DataFormat, Timestamp}; use crate::Error; use crate::packet; use crate::Packet; use crate::Result; /// Holds a literal packet. /// /// A literal packet contains unstructured data. Since the size can /// be very large, it is advised to process messages containing such /// packets using a `PacketParser` or a `PacketPileParser` and process /// the data in a streaming manner rather than the using the /// `PacketPile::from_file` and related interfaces. /// /// See [Section 5.9 of RFC 9580] for details. /// /// [Section 5.9 of RFC 9580]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.9 // IMPORTANT: If you add fields to this struct, you need to explicitly // IMPORTANT: implement PartialEq, Eq, and Hash. #[derive(Clone, PartialEq, Eq, Hash)] pub struct Literal { /// CTB packet header fields. pub(crate) common: packet::Common, /// A one-octet field that describes how the data is formatted. format: DataFormat, /// filename is a string, but strings in Rust are valid UTF-8. /// There is no guarantee, however, that the filename is valid /// UTF-8. Thus, we leave filename as a byte array. It can be /// converted to a string using String::from_utf8() or /// String::from_utf8_lossy(). filename: Option<Vec<u8>>, /// A four-octet number that indicates a date associated with the /// literal data. date: Option<Timestamp>, /// The literal data packet is a container packet, but cannot /// store packets. /// /// This is written when serialized, and set by the packet parser /// if `buffer_unread_content` is used. container: packet::Container, } assert_send_and_sync!(Literal); impl fmt::Debug for Literal { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let filename = self .filename .as_ref() .map(|filename| String::from_utf8_lossy(filename)); let threshold = 36; let body = self.body(); let prefix = &body[..cmp::min(threshold, body.len())]; let mut prefix_fmt = String::from_utf8_lossy(prefix).into_owned(); if body.len() > threshold { prefix_fmt.push_str("..."); } prefix_fmt.push_str(&format!(" ({} bytes)", body.len())[..]); f.debug_struct("Literal") .field("format", &self.format) .field("filename", &filename) .field("date", &self.date) .field("body", &prefix_fmt) .field("body_digest", &self.container.body_digest()) .finish() } } impl Default for Literal { fn default() -> Self { Self::new(Default::default()) } } impl Literal { /// Returns a new `Literal` packet. pub fn new(format: DataFormat) -> Literal { Literal { common: Default::default(), format, filename: None, date: None, container: packet::Container::default_unprocessed(), } } /// Gets the Literal packet's content disposition. pub fn format(&self) -> DataFormat { self.format } /// Sets the Literal packet's content disposition. pub fn set_format(&mut self, format: DataFormat) -> DataFormat { ::std::mem::replace(&mut self.format, format) } /// Gets the literal packet's filename. /// /// Note: when a literal data packet is protected by a signature, /// only the literal data packet's body is protected, not the /// meta-data. As such, this field should normally be ignored. pub fn filename(&self) -> Option<&[u8]> { self.filename.as_deref() } /// Sets the literal packet's filename field. /// /// The standard does not specify the encoding. Filenames must /// not be longer than 255 bytes. /// /// Note: when a literal data packet is protected by a signature, /// only the literal data packet's body is protected, not the /// meta-data. As such, this field should not be used. pub fn set_filename<F>(&mut self, filename: F) -> Result<Option<Vec<u8>>> where F: AsRef<[u8]> { let filename = filename.as_ref(); Ok(::std::mem::replace(&mut self.filename, match filename.len() { 0 => None, 1..=255 => Some(filename.to_vec()), n => return Err(Error::InvalidArgument( format!("filename too long: {} bytes", n)).into()), })) } /// Gets the literal packet's date field. /// /// Note: when a literal data packet is protected by a signature, /// only the literal data packet's body is protected, not the /// meta-data. As such, this field should normally be ignored. pub fn date(&self) -> Option<time::SystemTime> { self.date.map(|d| d.into()) } /// Sets the literal packet's date field. /// /// Note: when a literal data packet is protected by a signature, /// only the literal data packet's body is protected, not the /// meta-data. As such, this field should not be used. pub fn set_date<T>(&mut self, timestamp: T) -> Result<Option<time::SystemTime>> where T: Into<Option<time::SystemTime>> { let date = if let Some(d) = timestamp.into() { let t = d.try_into()?; if u32::from(t) == 0 { None // RFC4880, section 5.9: 0 =^= "no specific time". } else { Some(t) } } else { None }; Ok(std::mem::replace(&mut self.date, date).map(|d| d.into())) } } impl_unprocessed_body_forwards!(Literal); impl From<Literal> for Packet { fn from(s: Literal) -> Self { Packet::Literal(s) } } #[cfg(test)] impl Arbitrary for Literal { fn arbitrary(g: &mut Gen) -> Self { let mut l = Literal::new(DataFormat::arbitrary(g)); l.set_body(Vec::<u8>::arbitrary(g)); while let Err(_) = l.set_filename(&Vec::<u8>::arbitrary(g)) { // Too long, try again. } l.set_date(Some(Timestamp::arbitrary(g).into())).unwrap(); l } } #[cfg(test)] mod tests { use super::*; use crate::parse::Parse; use crate::serialize::MarshalInto; quickcheck! { fn roundtrip(p: Literal) -> bool { let q = Literal::from_bytes(&p.to_vec().unwrap()).unwrap(); assert_eq!(p, q); true } } /// Checks that partially read packets are still considered equal. #[test] fn partial_read_eq() -> Result<()> { use buffered_reader::BufferedReader; use crate::parse::PacketParserBuilder; let mut l0 = Literal::new(Default::default()); l0.set_body(vec![0, 0]); let l0 = Packet::from(l0); let l0bin = l0.to_vec()?; // Sanity check. assert_eq!(l0, Packet::from_bytes(&l0bin)?); for &buffer_unread_content in &[false, true] { for read_n in 0..3 { eprintln!("buffer_unread_content: {:?}, read_n: {}", buffer_unread_content, read_n); let mut b = PacketParserBuilder::from_bytes(&l0bin)?; if buffer_unread_content { b = b.buffer_unread_content(); } let mut pp = b.build()?.unwrap(); let d = pp.steal(read_n)?; d.into_iter().for_each(|b| assert_eq!(b, 0)); let l = pp.finish()?; assert_eq!(&l0, l); let l = pp.next()?.0; assert_eq!(l0, l); } } Ok(()) } } �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/src/packet/marker.rs����������������������������������������������������������0000644�0000000�0000000�00000002156�10461020230�0016630�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#[cfg(test)] use quickcheck::{Arbitrary, Gen}; use crate::packet; use crate::Packet; /// Holds a Marker packet. /// /// See [Section 5.8 of RFC 9580] for details. /// /// [Section 5.8 of RFC 9580]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.8 // IMPORTANT: If you add fields to this struct, you need to explicitly // IMPORTANT: implement PartialEq, Eq, and Hash. #[derive(Default, Clone, Debug, PartialEq, Eq, Hash)] pub struct Marker { /// CTB packet header fields. pub(crate) common: packet::Common, } assert_send_and_sync!(Marker); impl Marker { pub(crate) const BODY: &'static [u8] = &[0x50, 0x47, 0x50]; } impl From<Marker> for Packet { fn from(p: Marker) -> Self { Packet::Marker(p) } } #[cfg(test)] impl Arbitrary for Marker { fn arbitrary(_: &mut Gen) -> Self { Self::default() } } #[cfg(test)] mod tests { use super::*; use crate::parse::Parse; use crate::serialize::MarshalInto; #[test] fn roundtrip() { let p = Marker::default(); let q = Marker::from_bytes(&p.to_vec().unwrap()).unwrap(); assert_eq!(p, q); } } ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/src/packet/mdc.rs�������������������������������������������������������������0000644�0000000�0000000�00000005046�10461020230�0016113�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������use std::cmp::Ordering; use crate::crypto; use crate::crypto::mem; use crate::packet; use crate::Packet; /// Holds an MDC packet. /// /// A modification detection code packet. This packet appears after a /// SEIP packet. See [Section 5.13.1 of RFC 9580] for details. /// /// [Section 5.13.1 of RFC 9580]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.13.1 /// /// # A note on equality /// /// Two `MDC` packets are considered equal if their serialized form is /// equal. This excludes the computed digest. #[derive(Clone, Debug)] pub struct MDC { /// CTB packet header fields. pub(crate) common: packet::Common, /// Our SHA-1 hash. computed_digest: [u8; 20], /// A 20-octet SHA-1 hash of the preceding plaintext data. digest: [u8; 20], } assert_send_and_sync!(MDC); impl PartialEq for MDC { fn eq(&self, other: &MDC) -> bool { self.common == other.common && self.digest == other.digest } } impl Eq for MDC {} impl std::hash::Hash for MDC { fn hash<H: std::hash::Hasher>(&self, state: &mut H) { std::hash::Hash::hash(&self.common, state); std::hash::Hash::hash(&self.digest, state); } } impl MDC { /// Creates an MDC packet. pub fn new(digest: [u8; 20], computed_digest: [u8; 20]) -> Self { MDC { common: Default::default(), computed_digest, digest, } } /// Gets the packet's hash value. pub fn digest(&self) -> &[u8] { &self.digest[..] } /// Gets the computed hash value. pub fn computed_digest(&self) -> &[u8] { &self.computed_digest[..] } /// Returns whether the data protected by the MDC is valid. pub fn valid(&self) -> bool { if self.digest == [ 0; 20 ] { // If the computed_digest and digest are uninitialized, then // return false. false } else { mem::secure_cmp(&self.computed_digest, &self.digest) == Ordering::Equal } } } impl From<MDC> for Packet { fn from(s: MDC) -> Self { #[allow(deprecated)] Packet::MDC(s) } } impl From<[u8; 20]> for MDC { fn from(digest: [u8; 20]) -> Self { MDC { common: Default::default(), // All 0s. computed_digest: Default::default(), digest, } } } impl From<crypto::hash::Context> for MDC { fn from(mut hash: crypto::hash::Context) -> Self { let mut value : [u8; 20] = Default::default(); let _ = hash.digest(&mut value[..]); value.into() } } ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/src/packet/mod.rs�������������������������������������������������������������0000644�0000000�0000000�00000121030�10461020230�0016117�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������//! Packet-related data types. //! //! OpenPGP data structures are [packet based]. This module defines //! the corresponding data structures. //! //! Most users of this library will not need to generate these packets //! themselves. Instead, the packets are instantiated as a side //! effect of [parsing a message], or [creating a message]. The main //! current exception are `Signature` packets. Working with //! `Signature` packets is, however, simplified by using the //! [`SignatureBuilder`]. //! //! # Data Types //! //! Many OpenPGP packets include a version field. Versioning is used //! to make it easier to change the standard. For instance, using //! versioning, it is possible to remove a field from a packet without //! introducing a new packet type, which would also require changing //! [the grammar]. Versioning also enables a degree of forward //! compatibility when a new version of a packet can be safely //! ignored. For instance, there are currently two versions of the //! [`Signature`] packet with completely different layouts: [v3] and //! [v4]. An implementation that does not understand the latest //! version of the packet can still parse and display a message using //! them; it will just be unable to verify that signature. //! //! In Sequoia, packets that have a version field are represented by //! `enum`s, and each supported version of the packet has a variant, //! and a corresponding `struct`. This is the case even when only one //! version of the packet is currently defined, as is the case with //! the [`OnePassSig`] packet. The `enum`s implement forwarders for //! common operations. As such, users of this library can often //! ignore that there are multiple versions of a given packet. //! //! # Unknown Packets //! //! Sequoia gracefully handles unsupported packets by storing them as //! [`Unknown`] packets. There are several types of unknown packets: //! //! - Packets that are known, but explicitly not supported. //! //! The two major examples are the [`SED`] packet type and v3 //! `Signature` packets, which have both been considered insecure //! for well over a decade. //! //! Note: future versions of Sequoia may add limited support for //! these packets to enable parsing archived messages. //! //! - Packets that are known about, but that use unsupported //! options, e.g., a [`Compressed Data`] packet using an unknown or //! unsupported algorithm. //! //! - Packets that are unknown, e.g., future or [private //! extensions]. //! //! When Sequoia [parses] a message containing these packets, it //! doesn't fail. Instead, Sequoia stores them in the [`Unknown`] //! data structure. This allows applications to not only continue to //! process such messages (albeit with degraded performance), but to //! losslessly reserialize the messages, should that be required. //! //! # Containers //! //! Packets can be divided into two categories: containers and //! non-containers. A container is a packet that contains other //! OpenPGP packets. For instance, by definition, a [`Compressed //! Data`] packet contains an [OpenPGP Message]. It is possible to //! iterate over a container's descendants using the //! [`Container::descendants`] method. (Note: `Container`s have a //! `.container_ref()` and a `.container_mut()` method that return a //! reference to [`Container`].) //! //! # Packet Headers and Bodies //! //! Conceptually, packets have zero or more headers and an optional //! body. The headers are small, and have a known upper bound. The //! version field is, for instance, 4 bytes, and although //! [`Signature`][] [`SubpacketArea`][] areas are variable in size, //! they are limited to 64 KB. In contrast the body, can be unbounded //! in size. //! //! To limit memory use, and enable streaming processing (i.e., //! ensuring that processing a message can be done using a fixed size //! buffer), Sequoia does not require that a packet's body be present //! in memory. For instance, the body of a literal data packet may be //! streamed. And, at the end, a [`Literal`] packet is still //! returned. This allows the caller to examine the message //! structure, and the message headers in *in toto* even when //! streaming. It is even possible to compare two streamed version of //! a packet: Sequoia stores a hash of the body. See the [`Body`] //! data structure for more details. //! //! # Equality //! //! There are several reasonable ways to define equality for //! `Packet`s. Unfortunately, none of them are appropriate in all //! situations. This makes choosing a general-purpose equality //! function for [`Eq`] difficult. //! //! Consider defining `Eq` as the equivalence of two `Packet`s' //! serialized forms. If an application naively deduplicates //! signatures, then an attacker can potentially perform a //! denial-of-service attack by causing the application to process many //! cryptographically-valid `Signature`s by varying the content of one //! cryptographically-valid `Signature`'s unhashed area. This attack //! can be prevented by only comparing data that is protected by the //! signature. But this means that naively deduplicating `Signature` //! packets will return in "a random" variant being used. So, again, //! an attacker could create variants of a cryptographically-valid //! `Signature` to get the implementation to incorrectly drop a useful //! one. //! //! These issues are also relevant when comparing [`Key`s]: should the //! secret key material be compared? Usually we want to merge the //! secret key material. But, again, if done naively, the incorrect //! secret key material may be retained or dropped completely. //! //! Instead of trying to come up with a definition of equality that is //! reasonable for all situations, we use a conservative definition: //! two packets are considered equal if the serialized forms of their //! packet bodies as defined by RFC 9580 are equal. That is, two //! packets are considered equal if and only if their serialized forms //! are equal modulo the OpenPGP framing ([`CTB`] and [length style], //! potential [partial body encoding]). This definition will avoid //! unintentionally dropping information when naively deduplicating //! packets, but it will result in potential redundancies. //! //! For some packets, we provide additional variants of equality. For //! instance, [`Key::public_cmp`] compares just the public parts of //! two keys. //! //! [packet based]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5 //! [the grammar]: https://www.rfc-editor.org/rfc/rfc9580.html#section-10 //! [v3]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.2 //! [v4]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3 //! [parsing a message]: crate::parse //! [creating a message]: crate::serialize::stream //! [`SignatureBuilder`]: signature::SignatureBuilder //! [`SED`]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.7 //! [private extensions]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5 //! [`Compressed Data`]: CompressedData //! [parses]: crate::parse //! [OpenPGP Message]: https://www.rfc-editor.org/rfc/rfc9580.html#section-10.3 //! [`Container::descendants`]: Container::descendants() //! [`Deref`]: std::ops::Deref //! [`SubpacketArea`]: signature::subpacket::SubpacketArea //! [`Eq`]: std::cmp::Eq //! [`Key`s]: Key //! [`CTB`]: header::CTB //! [length style]: https://www.rfc-editor.org/rfc/rfc9580.html#section-4.2 //! [partial body encoding]: https://www.rfc-editor.org/rfc/rfc9580.html#section-4.2.1.4 //! [`Key::public_cmp`]: Key::public_cmp() use std::fmt; use std::hash::Hasher; use std::ops::{Deref, DerefMut}; use std::slice; use std::iter::IntoIterator; #[cfg(test)] use quickcheck::{Arbitrary, Gen}; use crate::Result; #[macro_use] mod container; pub use container::Container; pub use container::Body; pub mod prelude; mod any; pub use self::any::Any; mod tag; pub use self::tag::Tag; pub mod header; pub use self::header::Header; mod unknown; pub use self::unknown::Unknown; pub mod signature; pub mod one_pass_sig; pub use one_pass_sig::OnePassSig; pub mod key; pub use key::Key; mod marker; pub use self::marker::Marker; mod trust; pub use self::trust::Trust; mod userid; pub use self::userid::UserID; pub mod user_attribute; pub use self::user_attribute::UserAttribute; mod literal; pub use self::literal::Literal; mod compressed_data; pub use self::compressed_data::CompressedData; pub mod seip; pub mod skesk; pub use skesk::SKESK; pub mod pkesk; pub use pkesk::PKESK; mod mdc; pub use self::mdc::MDC; mod padding; pub use self::padding::Padding; /// Enumeration of packet types. /// /// The different OpenPGP packets are detailed in [Section 5 of RFC 9580]. /// /// [Section 5 of RFC 9580]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5 /// /// The [`Unknown`] packet allows Sequoia to deal with packets that it /// doesn't understand. It is basically a binary blob that includes /// the packet's [tag]. See the [module-level documentation] for /// details. /// /// # A note on equality /// /// We define equality on `Packet` as the equality of the serialized /// form of their packet bodies as defined by RFC 9580. That is, two /// packets are considered equal if and only if their serialized forms /// are equal, modulo the OpenPGP framing ([`CTB`] and [length style], /// potential [partial body encoding]). /// /// [`Unknown`]: crate::packet::Unknown /// [tag]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5 /// [module-level documentation]: crate::packet#unknown-packets /// [`CTB`]: crate::packet::header::CTB /// [length style]: https://www.rfc-editor.org/rfc/rfc9580.html#section-4.2 /// [partial body encoding]: https://www.rfc-editor.org/rfc/rfc9580.html#section-4.2.1.4 #[non_exhaustive] #[derive(PartialEq, Eq, Hash, Clone)] pub enum Packet { /// Unknown packet. Unknown(Unknown), /// Signature packet. Signature(Signature), /// One pass signature packet. OnePassSig(OnePassSig), /// Public key packet. PublicKey(key::PublicKey), /// Public subkey packet. PublicSubkey(key::PublicSubkey), /// Public/Secret key pair. SecretKey(key::SecretKey), /// Public/Secret subkey pair. SecretSubkey(key::SecretSubkey), /// Marker packet. Marker(Marker), /// Trust packet. Trust(Trust), /// User ID packet. UserID(UserID), /// User attribute packet. UserAttribute(UserAttribute), /// Literal data packet. Literal(Literal), /// Compressed literal data packet. CompressedData(CompressedData), /// Public key encrypted data packet. PKESK(PKESK), /// Symmetric key encrypted data packet. SKESK(SKESK), /// Symmetric key encrypted, integrity protected data packet. SEIP(SEIP), /// Modification detection code packet. #[deprecated] MDC(MDC), /// Padding packet. Padding(Padding), } assert_send_and_sync!(Packet); macro_rules! impl_into_iterator { ($t:ty) => { impl_into_iterator!($t where); }; ($t:ty where $( $w:ident: $c:path ),*) => { /// Implement `IntoIterator` so that /// `cert::insert_packets(sig)` just works. impl<$($w),*> IntoIterator for $t where $($w: $c ),* { type Item = $t; type IntoIter = std::iter::Once<$t>; fn into_iter(self) -> Self::IntoIter { std::iter::once(self) } } } } impl_into_iterator!(Packet); impl_into_iterator!(Unknown); impl_into_iterator!(Signature); impl_into_iterator!(OnePassSig); impl_into_iterator!(Marker); impl_into_iterator!(Trust); impl_into_iterator!(UserID); impl_into_iterator!(UserAttribute); impl_into_iterator!(Literal); impl_into_iterator!(CompressedData); impl_into_iterator!(PKESK); impl_into_iterator!(SKESK); impl_into_iterator!(SEIP); impl_into_iterator!(MDC); impl_into_iterator!(Key<P, R> where P: key::KeyParts, R: key::KeyRole); // Make it easy to pass an iterator of Packets to something expecting // an iterator of Into<Result<Packet>> (specifically, // CertParser::into_iter). impl From<Packet> for Result<Packet> { fn from(p: Packet) -> Self { Ok(p) } } impl Packet { /// Returns the `Packet's` corresponding OpenPGP tag. /// /// Tags are explained in [Section 5 of RFC 9580]. /// /// [Section 5 of RFC 9580]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5 pub fn tag(&self) -> Tag { match self { Packet::Unknown(ref packet) => packet.tag(), Packet::Signature(_) => Tag::Signature, Packet::OnePassSig(_) => Tag::OnePassSig, Packet::PublicKey(_) => Tag::PublicKey, Packet::PublicSubkey(_) => Tag::PublicSubkey, Packet::SecretKey(_) => Tag::SecretKey, Packet::SecretSubkey(_) => Tag::SecretSubkey, Packet::Marker(_) => Tag::Marker, Packet::Trust(_) => Tag::Trust, Packet::UserID(_) => Tag::UserID, Packet::UserAttribute(_) => Tag::UserAttribute, Packet::Literal(_) => Tag::Literal, Packet::CompressedData(_) => Tag::CompressedData, Packet::PKESK(_) => Tag::PKESK, Packet::SKESK(_) => Tag::SKESK, Packet::SEIP(_) => Tag::SEIP, #[allow(deprecated)] Packet::MDC(_) => Tag::MDC, Packet::Padding(_) => Tag::Padding, } } /// Returns the parsed `Packet's` corresponding OpenPGP tag. /// /// Returns the packets tag, but only if it was successfully /// parsed into the corresponding packet type. If e.g. a /// Signature Packet uses some unsupported methods, it is parsed /// into an `Packet::Unknown`. `tag()` returns `Tag::Signature`, /// whereas `kind()` returns `None`. pub fn kind(&self) -> Option<Tag> { match self { Packet::Unknown(_) => None, _ => Some(self.tag()), } } /// Returns whether this is a critical packet. /// /// Upon encountering an unknown critical packet, implementations /// MUST reject the whole packet sequence. On the other hand, /// unknown non-critical packets MUST be ignored. See [Section /// 4.3 of RFC 9580]. /// /// [Section 4.3 of RFC 9580]: https://www.rfc-editor.org/rfc/rfc9580.html#section-4.3 pub fn is_critical(&self) -> bool { self.tag().is_critical() } /// Returns the `Packet's` version, if the packet is versioned and /// recognized. /// /// If the packet is not versioned, or we couldn't parse the /// packet, this function returns `None`. pub fn version(&self) -> Option<u8> { match self { Packet::Unknown(_) => None, Packet::Signature(p) => Some(p.version()), Packet::OnePassSig(p) => Some(p.version()), Packet::PublicKey(p) => Some(p.version()), Packet::PublicSubkey(p) => Some(p.version()), Packet::SecretKey(p) => Some(p.version()), Packet::SecretSubkey(p) => Some(p.version()), Packet::Marker(_) => None, Packet::Trust(_) => None, Packet::UserID(_) => None, Packet::UserAttribute(_) => None, Packet::Literal(_) => None, Packet::CompressedData(_) => None, Packet::PKESK(p) => Some(p.version()), Packet::SKESK(p) => Some(p.version()), Packet::SEIP(p) => Some(p.version()), #[allow(deprecated)] Packet::MDC(_) => None, Packet::Padding(_) => None, } } /// Hashes most everything into state. /// /// This is an alternate implementation of [`Hash`], which does /// not hash: /// /// - The unhashed subpacket area of Signature packets. /// - Secret key material. /// /// [`Hash`]: std::hash::Hash /// /// Unlike [`Signature::normalize`], this method ignores /// authenticated packets in the unhashed subpacket area. /// /// [`Signature::normalize`]: Signature::normalize() pub fn normalized_hash<H>(&self, state: &mut H) where H: Hasher { use std::hash::Hash; match self { Packet::Signature(sig) => sig.normalized_hash(state), Packet::OnePassSig(x) => Hash::hash(&x, state), Packet::PublicKey(k) => k.public_hash(state), Packet::PublicSubkey(k) => k.public_hash(state), Packet::SecretKey(k) => k.public_hash(state), Packet::SecretSubkey(k) => k.public_hash(state), Packet::Marker(x) => Hash::hash(&x, state), Packet::Trust(x) => Hash::hash(&x, state), Packet::UserID(x) => Hash::hash(&x, state), Packet::UserAttribute(x) => Hash::hash(&x, state), Packet::Literal(x) => Hash::hash(&x, state), Packet::CompressedData(x) => Hash::hash(&x, state), Packet::PKESK(x) => Hash::hash(&x, state), Packet::SKESK(x) => Hash::hash(&x, state), Packet::SEIP(x) => Hash::hash(&x, state), #[allow(deprecated)] Packet::MDC(x) => Hash::hash(&x, state), Packet::Unknown(x) => Hash::hash(&x, state), Packet::Padding(x) => Padding::hash(x, state), } } } // Allow transparent access of common fields. impl Packet { /// Returns a reference to the packet's `Common` struct. fn common(&self) -> &Common { match self { Packet::Unknown(ref packet) => &packet.common, Packet::Signature(ref packet) => &packet.common, Packet::OnePassSig(OnePassSig::V3(packet)) => &packet.common, Packet::OnePassSig(OnePassSig::V6(packet)) => &packet.common.common, Packet::PublicKey(Key::V4(packet)) => &packet.common, Packet::PublicKey(Key::V6(packet)) => &packet.common.common, Packet::PublicSubkey(Key::V4(packet)) => &packet.common, Packet::PublicSubkey(Key::V6(packet)) => &packet.common.common, Packet::SecretKey(Key::V4(packet)) => &packet.common, Packet::SecretKey(Key::V6(packet)) => &packet.common.common, Packet::SecretSubkey(Key::V4(packet)) => &packet.common, Packet::SecretSubkey(Key::V6(packet)) => &packet.common.common, Packet::Marker(ref packet) => &packet.common, Packet::Trust(ref packet) => &packet.common, Packet::UserID(ref packet) => &packet.common, Packet::UserAttribute(ref packet) => &packet.common, Packet::Literal(ref packet) => &packet.common, Packet::CompressedData(ref packet) => &packet.common, Packet::PKESK(PKESK::V3(packet)) => &packet.common, Packet::PKESK(PKESK::V6(packet)) => &packet.common, Packet::SKESK(SKESK::V4(ref packet)) => &packet.common, Packet::SKESK(SKESK::V6(ref packet)) => &packet.skesk4.common, Packet::SEIP(SEIP::V1(packet)) => &packet.common, Packet::SEIP(SEIP::V2(packet)) => &packet.common, #[allow(deprecated)] Packet::MDC(ref packet) => &packet.common, Packet::Padding(packet) => &packet.common, } } } impl fmt::Debug for Packet { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn debug_fmt(p: &Packet, f: &mut fmt::Formatter) -> fmt::Result { match p { Packet::Unknown(v) => write!(f, "Unknown({:?})", v), Packet::Signature(v) => write!(f, "Signature({:?})", v), Packet::OnePassSig(v) => write!(f, "OnePassSig({:?})", v), Packet::PublicKey(v) => write!(f, "PublicKey({:?})", v), Packet::PublicSubkey(v) => write!(f, "PublicSubkey({:?})", v), Packet::SecretKey(v) => write!(f, "SecretKey({:?})", v), Packet::SecretSubkey(v) => write!(f, "SecretSubkey({:?})", v), Packet::Marker(v) => write!(f, "Marker({:?})", v), Packet::Trust(v) => write!(f, "Trust({:?})", v), Packet::UserID(v) => write!(f, "UserID({:?})", v), Packet::UserAttribute(v) => write!(f, "UserAttribute({:?})", v), Packet::Literal(v) => write!(f, "Literal({:?})", v), Packet::CompressedData(v) => write!(f, "CompressedData({:?})", v), Packet::PKESK(v) => write!(f, "PKESK({:?})", v), Packet::SKESK(v) => write!(f, "SKESK({:?})", v), Packet::SEIP(v) => write!(f, "SEIP({:?})", v), #[allow(deprecated)] Packet::MDC(v) => write!(f, "MDC({:?})", v), Packet::Padding(v) => write!(f, "Padding({:?})", v), } } fn try_armor_fmt(p: &Packet, f: &mut fmt::Formatter) -> Result<fmt::Result> { use crate::armor::{Writer, Kind}; use crate::serialize::Serialize; let mut w = Writer::new(Vec::new(), Kind::File)?; p.serialize(&mut w)?; let buf = w.finalize()?; Ok(f.write_str(std::str::from_utf8(&buf).expect("clean"))) } if ! cfg!(test) { debug_fmt(self, f) } else { try_armor_fmt(self, f).unwrap_or_else(|_| debug_fmt(self, f)) } } } #[cfg(test)] impl Arbitrary for Packet { fn arbitrary(g: &mut Gen) -> Self { use crate::arbitrary_helper::gen_arbitrary_from_range; match gen_arbitrary_from_range(0..16, g) { 0 => Signature::arbitrary(g).into(), 1 => OnePassSig::arbitrary(g).into(), 2 => Key::<key::PublicParts, key::PrimaryRole>::arbitrary(g) .into(), 3 => Key::<key::PublicParts, key::SubordinateRole>::arbitrary(g) .into(), 4 => Key::<key::SecretParts, key::PrimaryRole>::arbitrary(g) .into(), 5 => Key::<key::SecretParts, key::SubordinateRole>::arbitrary(g) .into(), 6 => Marker::arbitrary(g).into(), 7 => Trust::arbitrary(g).into(), 8 => UserID::arbitrary(g).into(), 9 => UserAttribute::arbitrary(g).into(), 10 => Literal::arbitrary(g).into(), 11 => CompressedData::arbitrary(g).into(), 12 => PKESK::arbitrary(g).into(), 13 => SKESK::arbitrary(g).into(), 14 => Padding::arbitrary(g).into(), 15 => loop { let mut u = Unknown::new( Tag::arbitrary(g), anyhow::anyhow!("Arbitrary::arbitrary")); u.set_body(Arbitrary::arbitrary(g)); let u = Packet::Unknown(u); // Check that we didn't accidentally make a valid // packet. use crate::parse::Parse; use crate::serialize::SerializeInto; if let Ok(Packet::Unknown(_)) = Packet::from_bytes( &u.to_vec().unwrap()) { break u; } // Try again! }, _ => unreachable!(), } } } /// Fields used by multiple packet types. #[derive(Default, Debug, Clone)] pub(crate) struct Common { // In the future, this structure will hold the parsed CTB, packet // length, and lengths of chunks of partial body encoded packets. // This will allow for bit-perfect roundtripping of parsed // packets. Since we consider Packets to be equal if their // serialized form is equal modulo CTB, packet length encoding, // and chunk lengths, this structure has trivial implementations // for PartialEq, Eq, PartialOrd, Ord, and Hash, so that we can // derive PartialEq, Eq, PartialOrd, Ord, and Hash for most // packets. /// XXX: Prevents trivial matching on this structure. Remove once /// this structure actually gains some fields. dummy: std::marker::PhantomData<()>, } assert_send_and_sync!(Common); impl Common { /// Returns a default version of `Common`. /// /// This is equivalent to using `Common::from`, but the function /// is constant. pub(crate) const fn new() -> Self { Common { dummy: std::marker::PhantomData } } } #[cfg(test)] impl Arbitrary for Common { fn arbitrary(_: &mut Gen) -> Self { // XXX: Change if this gets interesting fields. Common::default() } } impl PartialEq for Common { fn eq(&self, _: &Common) -> bool { // Don't compare anything. true } } impl Eq for Common {} impl PartialOrd for Common { fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> { Some(self.cmp(other)) } } impl Ord for Common { fn cmp(&self, _: &Self) -> std::cmp::Ordering { std::cmp::Ordering::Equal } } impl std::hash::Hash for Common { fn hash<H: std::hash::Hasher>(&self, _: &mut H) { // Don't hash anything. } } /// An iterator over the *contents* of a packet in depth-first order. /// /// Given a [`Packet`], an `Iter` iterates over the `Packet` and any /// `Packet`s that it contains. For non-container `Packet`s, this /// just returns a reference to the `Packet` itself. For [container /// `Packet`s] like [`CompressedData`], and [`SEIP`], this /// walks the `Packet` hierarchy in depth-first order, and returns the /// `Packet`s the first time they are visited. (Thus, the packet /// itself is always returned first.) /// /// This is returned by [`PacketPile::descendants`] and /// [`Container::descendants`]. /// /// [container `Packet`s]: self#containers /// [`PacketPile::descendants`]: super::PacketPile::descendants() /// [`Container::descendants`]: Container::descendants() pub struct Iter<'a> { // An iterator over the current message's children. children: slice::Iter<'a, Packet>, // The current child (i.e., the last value returned by // children.next()). child: Option<&'a Packet>, // The iterator over the current child's children. grandchildren: Option<Box<Iter<'a>>>, // The depth of the last returned packet. This is used by the // `paths` iter. depth: usize, } assert_send_and_sync!(Iter<'_>); impl<'a> Default for Iter<'a> { fn default() -> Self { Iter { children: [].iter(), child: None, grandchildren: None, depth: 0, } } } impl<'a> Iterator for Iter<'a> { type Item = &'a Packet; fn next(&mut self) -> Option<Self::Item> { // If we don't have a grandchild iterator (self.grandchildren // is None), then we are just starting, and we need to get the // next child. if let Some(ref mut grandchildren) = self.grandchildren { let grandchild = grandchildren.next(); // If the grandchild iterator is exhausted (grandchild is // None), then we need the next child. if grandchild.is_some() { self.depth = grandchildren.depth + 1; return grandchild; } } // Get the next child and the iterator for its children. self.child = self.children.next(); if let Some(child) = self.child { self.grandchildren = child.descendants().map(Box::new); } // First return the child itself. Subsequent calls will // return its grandchildren. self.depth = 0; self.child } } impl<'a> Iter<'a> { /// Extends an `Iter` to also return each packet's `pathspec`. /// /// This is similar to `enumerate`, but instead of counting, this /// returns each packet's `pathspec` in addition to a reference to /// the packet. /// /// See [`PacketPile::path_ref`] for an explanation of /// `pathspec`s. /// /// [`PacketPile::path_ref`]: super::PacketPile::path_ref /// /// # Examples /// /// ```rust /// use sequoia_openpgp as openpgp; /// # use openpgp::Result; /// use openpgp::packet::prelude::*; /// use openpgp::PacketPile; /// /// # fn main() -> Result<()> { /// # let message = { /// # use openpgp::types::CompressionAlgorithm; /// # use openpgp::packet; /// # use openpgp::PacketPile; /// # use openpgp::serialize::Serialize; /// # use openpgp::parse::Parse; /// # use openpgp::types::DataFormat; /// # /// # let mut lit = Literal::new(DataFormat::Unicode); /// # lit.set_body(b"test".to_vec()); /// # let lit = Packet::from(lit); /// # /// # let mut cd = CompressedData::new( /// # CompressionAlgorithm::Uncompressed); /// # cd.set_body(packet::Body::Structured(vec![lit.clone()])); /// # let cd = Packet::from(cd); /// # /// # // Make sure we created the message correctly: serialize, /// # // parse it, and then check its form. /// # let mut bytes = Vec::new(); /// # cd.serialize(&mut bytes)?; /// # /// # let pp = PacketPile::from_bytes(&bytes[..])?; /// # /// # assert_eq!(pp.descendants().count(), 2); /// # assert_eq!(pp.path_ref(&[0]).unwrap().tag(), /// # packet::Tag::CompressedData); /// # assert_eq!(pp.path_ref(&[0, 0]), Some(&lit)); /// # /// # cd /// # }; /// # /// let pp = PacketPile::from(message); /// let tags: Vec<(Vec<usize>, Tag)> = pp.descendants().paths() /// .map(|(path, packet)| (path, packet.into())) /// .collect::<Vec<_>>(); /// assert_eq!(&tags, /// &[ /// // Root. /// ([0].to_vec(), Tag::CompressedData), /// // Root's first child. /// ([0, 0].to_vec(), Tag::Literal), /// ]); /// # Ok(()) } /// ``` pub fn paths(self) -> impl Iterator<Item = (Vec<usize>, &'a Packet)> + Send + Sync { PacketPathIter { iter: self, path: None, } } } /// Augments the packet returned by `Iter` with its `pathspec`. /// /// Like [`Iter::enumerate`]. /// /// [`Iter::enumerate`]: std::iter::Iterator::enumerate() struct PacketPathIter<'a> { iter: Iter<'a>, // The path to the most recently returned node relative to the // start of the iterator. path: Option<Vec<usize>>, } impl<'a> Iterator for PacketPathIter<'a> { type Item = (Vec<usize>, &'a Packet); fn next(&mut self) -> Option<Self::Item> { if let Some(packet) = self.iter.next() { if self.path.is_none() { // Init. let mut path = Vec::with_capacity(4); path.push(0); self.path = Some(path); } else { let mut path = self.path.take().unwrap(); let old_depth = path.len() - 1; let depth = self.iter.depth; if old_depth > depth { // We popped. path.truncate(depth + 1); path[depth] += 1; } else if old_depth == depth { // Sibling. path[old_depth] += 1; } else if old_depth + 1 == depth { // Recursion. path.push(0); } self.path = Some(path); } Some((self.path.as_ref().unwrap().clone(), packet)) } else { None } } } // Tests the `paths`() iter and `path_ref`(). #[test] fn packet_path_iter() { use crate::parse::Parse; use crate::PacketPile; fn paths<'a>(iter: impl Iterator<Item=&'a Packet>) -> Vec<Vec<usize>> { let mut lpaths : Vec<Vec<usize>> = Vec::new(); for (i, packet) in iter.enumerate() { let mut v = Vec::new(); v.push(i); lpaths.push(v); if let Some(container) = packet.container_ref() { if let Some(c) = container.children() { for mut path in paths(c).into_iter() { path.insert(0, i); lpaths.push(path); } } } } lpaths } for i in 1..5 { let pile = PacketPile::from_bytes( crate::tests::message(&format!("recursive-{}.gpg", i)[..])).unwrap(); let mut paths1 : Vec<Vec<usize>> = Vec::new(); for path in paths(pile.children()).iter() { paths1.push(path.clone()); } let mut paths2 : Vec<Vec<usize>> = Vec::new(); for (path, packet) in pile.descendants().paths() { assert_eq!(Some(packet), pile.path_ref(&path[..])); paths2.push(path); } if paths1 != paths2 { eprintln!("PacketPile:"); pile.pretty_print(); eprintln!("Expected paths:"); for p in paths1 { eprintln!(" {:?}", p); } eprintln!("Got paths:"); for p in paths2 { eprintln!(" {:?}", p); } panic!("Something is broken. Don't panic."); } } } /// Holds a signature packet. /// /// Signature packets are used to hold all kinds of signatures /// including certifications, and signatures over documents. See /// [Section 5.2 of RFC 9580] for details. /// /// [Section 5.2 of RFC 9580]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2 /// /// When signing a document, a `Signature` packet is typically created /// indirectly by the [streaming `Signer`]. Similarly, a `Signature` /// packet is created as a side effect of parsing a signed message /// using the [`PacketParser`]. /// /// `Signature` packets are also used for [self signatures on Keys], /// [self signatures on User IDs], [self signatures on User /// Attributes], [certifications of User IDs], and [certifications of /// User Attributes]. In these cases, you'll typically want to use /// the [`SignatureBuilder`] to create the `Signature` packet. See /// the linked documentation for details, and examples. /// /// [streaming `Signer`]: crate::serialize::stream::Signer /// [`PacketParser`]: crate::parse::PacketParser /// [self signatures on Keys]: Key::bind() /// [self signatures on User IDs]: UserID::bind() /// [self signatures on User Attributes]: user_attribute::UserAttribute::bind() /// [certifications of User IDs]: UserID::certify() /// [certifications of User Attributes]: user_attribute::UserAttribute::certify() /// [`SignatureBuilder`]: signature::SignatureBuilder /// /// # A note on equality /// /// Two `Signature` packets are considered equal if their serialized /// form is equal. Notably this includes the unhashed subpacket area /// and the order of subpackets and notations. This excludes the /// computed digest and signature level, which are not serialized. /// /// A consequence of considering packets in the unhashed subpacket /// area is that an adversary can take a valid signature and create /// many distinct but valid signatures by changing the unhashed /// subpacket area. This has the potential of creating a denial of /// service vector, if `Signature`s are naively deduplicated. To /// protect against this, consider using [`Signature::normalized_eq`]. /// /// [`Signature::normalized_eq`]: Signature::normalized_eq() /// /// # Examples /// /// Add a User ID to an existing certificate: /// /// ``` /// use std::time; /// use sequoia_openpgp as openpgp; /// use openpgp::cert::prelude::*; /// use openpgp::packet::prelude::*; /// use openpgp::policy::StandardPolicy; /// /// # fn main() -> openpgp::Result<()> { /// let p = &StandardPolicy::new(); /// /// let t1 = time::SystemTime::now(); /// let t2 = t1 + time::Duration::from_secs(1); /// /// let (cert, _) = CertBuilder::new() /// .set_creation_time(t1) /// .add_userid("Alice <alice@example.org>") /// .generate()?; /// /// // Add a new User ID. /// let mut signer = cert /// .primary_key().key().clone().parts_into_secret()?.into_keypair()?; /// /// // Use the existing User ID's signature as a template. This ensures that /// // we use the same /// let userid = UserID::from("Alice <alice@other.com>"); /// let template: signature::SignatureBuilder /// = cert.with_policy(p, t1)?.primary_userid().unwrap() /// .binding_signature().clone().into(); /// let sig = template.clone() /// .set_signature_creation_time(t2)?; /// let sig = userid.bind(&mut signer, &cert, sig)?; /// /// let cert = cert.insert_packets(vec![Packet::from(userid), sig.into()])?.0; /// # assert_eq!(cert.with_policy(p, t2)?.userids().count(), 2); /// # Ok(()) } /// ``` #[non_exhaustive] #[derive(PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Debug)] pub enum Signature { /// Signature packet version 3. V3(self::signature::Signature3), /// Signature packet version 4. V4(self::signature::Signature4), /// Signature packet version 6. V6(self::signature::Signature6), } assert_send_and_sync!(Signature); impl Signature { /// Gets the version. pub fn version(&self) -> u8 { match self { Signature::V3(_) => 3, Signature::V4(_) => 4, Signature::V6(_) => 6, } } } impl From<Signature> for Packet { fn from(s: Signature) -> Self { Packet::Signature(s) } } impl Signature { /// Gets the salt, if any. pub fn salt(&self) -> Option<&[u8]> { match self { Signature::V3(_) => None, Signature::V4(_) => None, Signature::V6(s) => Some(s.salt()), } } } // Trivial forwarder for singleton enum. impl Deref for Signature { type Target = signature::Signature4; fn deref(&self) -> &Self::Target { match self { Signature::V3(sig) => &sig.intern, Signature::V4(sig) => sig, Signature::V6(sig) => &sig.common, } } } // Trivial forwarder for singleton enum. impl DerefMut for Signature { fn deref_mut(&mut self) -> &mut Self::Target { match self { Signature::V3(ref mut sig) => &mut sig.intern, Signature::V4(ref mut sig) => sig, Signature::V6(ref mut sig) => &mut sig.common, } } } /// Holds a SEIP packet. /// /// A SEIP packet holds encrypted data. The data contains additional /// OpenPGP packets. See [Section 5.13 of RFC 9580] for details. /// /// A SEIP packet is not normally instantiated directly. In most /// cases, you'll create one as a side effect of encrypting a message /// using the [streaming serializer], or parsing an encrypted message /// using the [`PacketParser`]. /// /// [Section 5.13 of RFC 9580]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.13 /// [streaming serializer]: crate::serialize::stream /// [`PacketParser`]: crate::parse::PacketParser #[derive(PartialEq, Eq, Hash, Clone, Debug)] #[non_exhaustive] pub enum SEIP { /// SEIP packet version 1. V1(self::seip::SEIP1), /// SEIP packet version 2. V2(self::seip::SEIP2), } assert_send_and_sync!(SEIP); impl SEIP { /// Gets the version. pub fn version(&self) -> u8 { match self { SEIP::V1(_) => 1, SEIP::V2(_) => 2, } } } impl From<SEIP> for Packet { fn from(p: SEIP) -> Self { Packet::SEIP(p) } } #[cfg(test)] mod test { use super::*; use crate::serialize::SerializeInto; use crate::parse::Parse; quickcheck! { fn roundtrip(p: Packet) -> bool { let buf = p.to_vec().expect("Failed to serialize packet"); let q = Packet::from_bytes(&buf).unwrap(); assert_eq!(p, q); true } } quickcheck! { /// Given a packet and a position, induces a bit flip in the /// serialized form, then checks that PartialEq detects that. /// Recall that for packets, PartialEq is defined using the /// serialized form. fn mutate_eq_discriminates(p: Packet, i: usize) -> bool { if p.tag() == Tag::CompressedData { // Mutating compressed data streams is not that // trivial, because there are bits we can flip without // changing the decompressed data. return true; } let mut buf = p.to_vec().unwrap(); // Avoid first two bytes so that we don't change the // type and reduce the chance of changing the length. if buf.len() < 3 { return true; } let bit = i % ((buf.len() - 2) * 8) + 16; buf[bit / 8] ^= 1 << (bit % 8); match Packet::from_bytes(&buf) { Ok(q) => p != q, Err(_) => true, // Packet failed to parse. } } } /// Problem on systems with 32-bit time_t. #[test] fn issue_802() -> Result<()> { let pp = crate::PacketPile::from_bytes(b"-----BEGIN PGP ARMORED FILE----- xiEE/////xIJKyQDAwIIAQENAFYp8M2JngCfc04tIwMBCuU= -----END PGP ARMORED FILE----- ")?; let p = pp.path_ref(&[0]).unwrap(); let buf = p.to_vec().expect("Failed to serialize packet"); let q = Packet::from_bytes(&buf).unwrap(); assert_eq!(p, &q); Ok(()) } } ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/src/packet/one_pass_sig/v3.rs�������������������������������������������������0000644�0000000�0000000�00000012744�10461020230�0020354�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������//! A version 3 one-pass signature packet. //! //! See [Section 5.4 of RFC 9580] for details. //! //! [Section 5.4 of RFC 9580]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.4 use std::fmt; #[cfg(test)] use quickcheck::{Arbitrary, Gen}; use crate::Error; use crate::Packet; use crate::packet; use crate::packet::Signature; use crate::packet::OnePassSig; use crate::Result; use crate::KeyID; use crate::HashAlgorithm; use crate::PublicKeyAlgorithm; use crate::SignatureType; /// Holds a version 3 one-pass signature packet. /// /// This holds a [version 3 One-Pass Signature Packet]. Normally, you won't /// directly work with this data structure, but with the [`OnePassSig`] /// enum, which is version agnostic. An exception is when you need to /// do version-specific operations. /// /// [version 3 One-Pass Signature Packet]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.4 /// [`OnePassSig`]: crate::packet::OnePassSig /// /// # A note on equality /// /// The `last` flag is represented as a `u8` and is compared /// literally, not semantically. // IMPORTANT: If you add fields to this struct, you need to explicitly // IMPORTANT: implement PartialEq, Eq, and Hash. #[derive(Clone, PartialEq, Eq, Hash)] pub struct OnePassSig3 { /// CTB packet header fields. pub(crate) common: packet::Common, /// Type of the signature. typ: SignatureType, /// Hash algorithm used to compute the signature. hash_algo: HashAlgorithm, /// Public key algorithm of this signature. pk_algo: PublicKeyAlgorithm, /// Key ID of the signing key. issuer: KeyID, /// A one-octet number holding a flag showing whether the signature /// is nested. last: u8, } assert_send_and_sync!(OnePassSig3); impl fmt::Debug for OnePassSig3 { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.debug_struct("OnePassSig3") .field("typ", &self.typ) .field("hash_algo", &self.hash_algo) .field("pk_algo", &self.pk_algo) .field("issuer", &self.issuer) .field("last", &self.last) .finish() } } impl OnePassSig3 { /// Returns a new One-Pass Signature packet. pub fn new(typ: SignatureType) -> Self { OnePassSig3 { common: Default::default(), typ, hash_algo: HashAlgorithm::Unknown(0), pk_algo: PublicKeyAlgorithm::Unknown(0), issuer: KeyID::new(0), last: 1, } } /// Gets the signature type. pub fn typ(&self) -> SignatureType { self.typ } /// Sets the signature type. pub fn set_type(&mut self, t: SignatureType) -> SignatureType { ::std::mem::replace(&mut self.typ, t) } /// Gets the public key algorithm. pub fn pk_algo(&self) -> PublicKeyAlgorithm { self.pk_algo } /// Sets the public key algorithm. pub fn set_pk_algo(&mut self, algo: PublicKeyAlgorithm) -> PublicKeyAlgorithm { ::std::mem::replace(&mut self.pk_algo, algo) } /// Gets the hash algorithm. pub fn hash_algo(&self) -> HashAlgorithm { self.hash_algo } /// Sets the hash algorithm. pub fn set_hash_algo(&mut self, algo: HashAlgorithm) -> HashAlgorithm { ::std::mem::replace(&mut self.hash_algo, algo) } /// Gets the issuer. pub fn issuer(&self) -> &KeyID { &self.issuer } /// Sets the issuer. pub fn set_issuer(&mut self, issuer: KeyID) -> KeyID { ::std::mem::replace(&mut self.issuer, issuer) } /// Gets the last flag. pub fn last(&self) -> bool { self.last > 0 } /// Sets the last flag. pub fn set_last(&mut self, last: bool) -> bool { ::std::mem::replace(&mut self.last, if last { 1 } else { 0 }) > 0 } /// Gets the raw value of the last flag. pub fn last_raw(&self) -> u8 { self.last } /// Sets the raw value of the last flag. pub fn set_last_raw(&mut self, last: u8) -> u8 { ::std::mem::replace(&mut self.last, last) } } impl From<OnePassSig3> for OnePassSig { fn from(s: OnePassSig3) -> Self { OnePassSig::V3(s) } } impl From<OnePassSig3> for Packet { fn from(p: OnePassSig3) -> Self { OnePassSig::from(p).into() } } impl<'a> std::convert::TryFrom<&'a Signature> for OnePassSig3 { type Error = anyhow::Error; fn try_from(s: &'a Signature) -> Result<Self> { let issuer = match s.issuers().next() { Some(i) => i.clone(), None => return Err(Error::InvalidArgument( "Signature has no issuer".into()).into()), }; Ok(OnePassSig3 { common: Default::default(), typ: s.typ(), hash_algo: s.hash_algo(), pk_algo: s.pk_algo(), issuer, last: 0, }) } } #[cfg(test)] impl Arbitrary for OnePassSig3 { fn arbitrary(g: &mut Gen) -> Self { let mut ops = OnePassSig3::new(SignatureType::arbitrary(g)); ops.set_hash_algo(HashAlgorithm::arbitrary(g)); ops.set_pk_algo(PublicKeyAlgorithm::arbitrary(g)); ops.set_issuer(KeyID::arbitrary(g)); ops.set_last_raw(u8::arbitrary(g)); ops } } #[cfg(test)] mod tests { use super::*; use crate::parse::Parse; use crate::serialize::MarshalInto; quickcheck! { fn roundtrip(p: OnePassSig3) -> bool { let q = OnePassSig3::from_bytes(&p.to_vec().unwrap()).unwrap(); assert_eq!(p, q); true } } } ����������������������������sequoia-openpgp-2.0.0/src/packet/one_pass_sig/v6.rs�������������������������������������������������0000644�0000000�0000000�00000013737�10461020230�0020362�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������//! A version 6 one-pass signature packet. use std::convert::TryFrom; use std::fmt; use std::mem; #[cfg(test)] use quickcheck::{Arbitrary, Gen}; use crate::{ Error, Fingerprint, HashAlgorithm, Packet, PublicKeyAlgorithm, Result, SignatureType, packet::{ Signature, OnePassSig, one_pass_sig::{ OnePassSig3, }, }, }; /// Holds a version 6 one-pass signature packet. /// /// This holds a [version 6 One-Pass Signature Packet]. Normally, you won't /// directly work with this data structure, but with the [`OnePassSig`] /// enum, which is version agnostic. An exception is when you need to /// do version-specific operations. /// /// [version 6 One-Pass Signature Packet]: https://www.rfc-editor.org/rfc/rfc9580.html#name-one-pass-signature-packet-t /// [`OnePassSig`]: crate::packet::OnePassSig /// /// # A note on equality /// /// The `last` flag is represented as a `u8` and is compared /// literally, not semantically. #[derive(PartialEq, Eq, Hash, Clone)] pub struct OnePassSig6 { pub(crate) common: OnePassSig3, salt: Vec<u8>, issuer: Fingerprint, } assert_send_and_sync!(OnePassSig6); impl TryFrom<OnePassSig> for OnePassSig6 { type Error = anyhow::Error; fn try_from(ops: OnePassSig) -> Result<Self> { match ops { OnePassSig::V6(ops) => Ok(ops), ops => Err( Error::InvalidArgument( format!( "Got a v{}, require a v6 one-pass signature", ops.version())) .into()), } } } impl fmt::Debug for OnePassSig6 { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.debug_struct("OnePassSig6") .field("typ", &self.typ()) .field("hash_algo", &self.hash_algo()) .field("pk_algo", &self.pk_algo()) .field("salt", &crate::fmt::hex::encode(self.salt())) .field("issuer", &self.issuer()) .field("last", &self.last()) .finish() } } impl OnePassSig6 { /// Returns a new One-Pass Signature packet. pub fn new(typ: SignatureType, issuer: Fingerprint) -> Self { OnePassSig6 { common: OnePassSig3::new(typ), salt: vec![], issuer, } } /// Gets the signature type. pub fn typ(&self) -> SignatureType { self.common.typ() } /// Sets the signature type. pub fn set_type(&mut self, t: SignatureType) -> SignatureType { self.common.set_type(t) } /// Gets the public key algorithm. pub fn pk_algo(&self) -> PublicKeyAlgorithm { self.common.pk_algo() } /// Sets the public key algorithm. pub fn set_pk_algo(&mut self, algo: PublicKeyAlgorithm) -> PublicKeyAlgorithm { self.common.set_pk_algo(algo) } /// Gets the hash algorithm. pub fn hash_algo(&self) -> HashAlgorithm { self.common.hash_algo() } /// Sets the hash algorithm. pub fn set_hash_algo(&mut self, algo: HashAlgorithm) -> HashAlgorithm { self.common.set_hash_algo(algo) } /// Gets the salt. pub fn salt(&self) -> &[u8] { &self.salt } /// Sets the salt. pub fn set_salt(&mut self, salt: Vec<u8>) -> Vec<u8> { mem::replace(&mut self.salt, salt) } /// Gets the issuer. pub fn issuer(&self) -> &Fingerprint { &self.issuer } /// Sets the issuer. pub fn set_issuer(&mut self, issuer: Fingerprint) -> Fingerprint { mem::replace(&mut self.issuer, issuer) } /// Gets the last flag. pub fn last(&self) -> bool { self.common.last() } /// Sets the last flag. pub fn set_last(&mut self, last: bool) -> bool { self.common.set_last(last) } /// Gets the raw value of the last flag. pub fn last_raw(&self) -> u8 { self.common.last_raw() } /// Sets the raw value of the last flag. pub fn set_last_raw(&mut self, last: u8) -> u8 { self.common.set_last_raw(last) } } impl From<OnePassSig6> for OnePassSig { fn from(s: OnePassSig6) -> Self { OnePassSig::V6(s) } } impl From<OnePassSig6> for Packet { fn from(p: OnePassSig6) -> Self { OnePassSig::from(p).into() } } impl<'a> std::convert::TryFrom<&'a Signature> for OnePassSig6 { type Error = anyhow::Error; fn try_from(s: &'a Signature) -> Result<Self> { let s = if let Signature::V6(s) = s { s } else { return Err(Error::InvalidArgument(format!( "Can not derive a v6 OnePassSig from a v{} Signature", s.version())).into()); }; let issuer = match s.issuer_fingerprints().next() { Some(i) => i.clone(), None => return Err(Error::InvalidArgument( "Signature has no issuer fingerprints".into()).into()), }; let mut common = OnePassSig3::new(s.typ()); common.set_hash_algo(s.hash_algo()); common.set_pk_algo(s.pk_algo()); Ok(OnePassSig6 { common, salt: s.salt().to_vec(), issuer, }) } } #[cfg(test)] mod tests { use super::*; use crate::arbitrary_helper::arbitrary_bounded_vec; use crate::parse::Parse; use crate::serialize::MarshalInto; impl Arbitrary for OnePassSig6 { fn arbitrary(g: &mut Gen) -> Self { let mut ops = OnePassSig6::new(SignatureType::arbitrary(g), Fingerprint::arbitrary_v6(g)); ops.set_hash_algo(HashAlgorithm::arbitrary(g)); ops.set_pk_algo(PublicKeyAlgorithm::arbitrary(g)); ops.set_last_raw(u8::arbitrary(g)); ops.set_salt(arbitrary_bounded_vec(g, 256)); ops } } quickcheck! { fn roundtrip(p: OnePassSig6) -> bool { let q = OnePassSig6::from_bytes(&p.to_vec().unwrap()).unwrap(); assert_eq!(p, q); true } } } ���������������������������������sequoia-openpgp-2.0.0/src/packet/one_pass_sig.rs����������������������������������������������������0000644�0000000�0000000�00000007557�10461020230�0020032�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������//! One-pass signature packets. //! //! See [One-Pass Signature Packet] for details. //! //! [One-Pass Signature Packet]: https://www.rfc-editor.org/rfc/rfc9580.html#name-one-pass-signature-packet-t #[cfg(test)] use quickcheck::{Arbitrary, Gen}; use crate::{ Error, KeyHandle, Result, packet::{ Packet, Signature, }, types::{ SignatureType, PublicKeyAlgorithm, HashAlgorithm, }, }; mod v3; pub use v3::OnePassSig3; mod v6; pub use v6::OnePassSig6; /// Holds a one-pass signature packet. /// /// See [One-Pass Signature Packet] for details. /// /// A `OnePassSig` packet is not normally instantiated directly. In /// most cases, you'll create one as a side effect of signing a /// message using the [streaming serializer], or parsing a signed /// message using the [`PacketParser`]. /// /// [One-Pass Signature Packet]: https://www.rfc-editor.org/rfc/rfc9580.html#name-one-pass-signature-packet-t /// [`PacketParser`]: crate::parse::PacketParser /// [streaming serializer]: crate::serialize::stream #[non_exhaustive] #[derive(PartialEq, Eq, Hash, Clone, Debug)] pub enum OnePassSig { /// OnePassSig packet version 3. V3(OnePassSig3), /// OnePassSig packet version 6. V6(OnePassSig6), } assert_send_and_sync!(OnePassSig); impl OnePassSig { /// Gets the version. pub fn version(&self) -> u8 { match self { OnePassSig::V3(_) => 3, OnePassSig::V6(_) => 6, } } /// Gets the signature type. pub fn typ(&self) -> SignatureType { match self { OnePassSig::V3(p) => p.typ(), OnePassSig::V6(p) => p.typ(), } } /// Gets the public key algorithm. pub fn pk_algo(&self) -> PublicKeyAlgorithm { match self { OnePassSig::V3(p) => p.pk_algo(), OnePassSig::V6(p) => p.pk_algo(), } } /// Gets the hash algorithm. pub fn hash_algo(&self) -> HashAlgorithm { match self { OnePassSig::V3(p) => p.hash_algo(), OnePassSig::V6(p) => p.hash_algo(), } } /// Gets the salt, if any. pub fn salt(&self) -> Option<&[u8]> { match self { OnePassSig::V3(_) => None, OnePassSig::V6(p) => Some(p.salt()), } } /// Gets the issuer. pub fn issuer(&self) -> KeyHandle { match self { OnePassSig::V3(p) => p.issuer().into(), OnePassSig::V6(p) => p.issuer().into(), } } /// Gets the last flag. pub fn last(&self) -> bool { match self { OnePassSig::V3(p) => p.last(), OnePassSig::V6(p) => p.last(), } } /// Sets the last flag. pub fn set_last(&mut self, last: bool) -> bool { match self { OnePassSig::V3(p) => p.set_last(last), OnePassSig::V6(p) => p.set_last(last), } } /// Gets the raw value of the last flag. pub fn last_raw(&self) -> u8 { match self { OnePassSig::V3(p) => p.last_raw(), OnePassSig::V6(p) => p.last_raw(), } } } impl From<OnePassSig> for Packet { fn from(s: OnePassSig) -> Self { Packet::OnePassSig(s) } } impl<'a> std::convert::TryFrom<&'a Signature> for OnePassSig { type Error = anyhow::Error; fn try_from(s: &'a Signature) -> Result<Self> { match s.version() { 4 => OnePassSig3::try_from(s).map(Into::into), 6 => OnePassSig6::try_from(s).map(Into::into), n => Err(Error::InvalidOperation( format!("Unsupported signature version {}", n)).into()), } } } #[cfg(test)] impl Arbitrary for super::OnePassSig { fn arbitrary(g: &mut Gen) -> Self { if Arbitrary::arbitrary(g) { OnePassSig3::arbitrary(g).into() } else { OnePassSig6::arbitrary(g).into() } } } �������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/src/packet/padding.rs���������������������������������������������������������0000644�0000000�0000000�00000004153�10461020230�0016754�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������use std::fmt; #[cfg(test)] use quickcheck::{Arbitrary, Gen}; use crate::{ Packet, Result, packet, }; /// Holds a Padding packet. /// /// Padding packets are used to obscure the size of cryptographic /// artifacts. /// /// See [Padding Packet] for details. /// /// [Padding Packet]: https://www.rfc-editor.org/rfc/rfc9580.html#padding-packet // IMPORTANT: If you add fields to this struct, you need to explicitly // IMPORTANT: implement PartialEq, Eq, and Hash. #[derive(Clone, PartialEq, Eq, Hash)] pub struct Padding { pub(crate) common: packet::Common, value: Vec<u8>, } assert_send_and_sync!(Padding); impl From<Vec<u8>> for Padding { fn from(u: Vec<u8>) -> Self { Padding { common: Default::default(), value: u, } } } impl fmt::Display for Padding { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let padding = String::from_utf8_lossy(&self.value[..]); write!(f, "{}", padding) } } impl fmt::Debug for Padding { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "Padding {{ {} bytes }}", self.value.len()) } } impl Padding { /// Creates a new Padding packet of the given size. /// /// Note that this is the net size, packet framing (CTB and packet /// length) will come on top. pub fn new(size: usize) -> Result<Padding> { let mut v = vec![0; size]; crate::crypto::random(&mut v)?; Ok(v.into()) } /// Gets the padding packet's value. pub(crate) fn value(&self) -> &[u8] { self.value.as_slice() } } impl From<Padding> for Packet { fn from(s: Padding) -> Self { Packet::Padding(s) } } #[cfg(test)] impl Arbitrary for Padding { fn arbitrary(g: &mut Gen) -> Self { Vec::<u8>::arbitrary(g).into() } } #[cfg(test)] mod tests { use super::*; use crate::parse::Parse; use crate::serialize::MarshalInto; quickcheck! { fn roundtrip(p: Padding) -> bool { let q = Padding::from_bytes(&p.to_vec().unwrap()).unwrap(); assert_eq!(p, q); true } } } ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/src/packet/pkesk/v3.rs��������������������������������������������������������0000644�0000000�0000000�00000040211�10461020230�0017006�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������//! PublicKey-Encrypted Session Key packets. //! //! The session key is needed to decrypt the actual ciphertext. See //! [Section 5.1 of RFC 9580] for details. //! //! [Section 5.1 of RFC 9580]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.1 #[cfg(test)] use quickcheck::{Arbitrary, Gen}; use crate::packet::key; use crate::packet::Key; use crate::KeyID; use crate::crypto::Decryptor; use crate::crypto::mpi::Ciphertext; use crate::Packet; use crate::PublicKeyAlgorithm; use crate::Result; use crate::SymmetricAlgorithm; use crate::crypto::SessionKey; use crate::packet; /// Holds an asymmetrically encrypted session key. /// /// The session key is needed to decrypt the actual ciphertext. See /// [Section 5.1 of RFC 9580] for details. /// /// [Section 5.1 of RFC 9580]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.1 // IMPORTANT: If you add fields to this struct, you need to explicitly // IMPORTANT: implement PartialEq, Eq, and Hash. #[derive(Clone, Debug, PartialEq, Eq, Hash)] pub struct PKESK3 { /// CTB header fields. pub(crate) common: packet::Common, /// Key ID of the key this is encrypted to. recipient: Option<KeyID>, /// Public key algorithm used to encrypt the session key. pk_algo: PublicKeyAlgorithm, /// The encrypted session key. esk: Ciphertext, } assert_send_and_sync!(PKESK3); impl PKESK3 { /// Creates a new PKESK3 packet. pub fn new(recipient: Option<KeyID>, pk_algo: PublicKeyAlgorithm, encrypted_session_key: Ciphertext) -> Result<PKESK3> { // Normalize recipient. let recipient = if recipient.as_ref().map(KeyID::is_wildcard).unwrap_or(true) { None } else { recipient }; Ok(PKESK3 { common: Default::default(), recipient, pk_algo, esk: encrypted_session_key, }) } /// Creates a new PKESK3 packet for the given recipient. /// /// The given symmetric algorithm must match the algorithm that is /// used to encrypt the payload. pub fn for_recipient<P, R>(algo: SymmetricAlgorithm, session_key: &SessionKey, recipient: &Key<P, R>) -> Result<PKESK3> where P: key::KeyParts, R: key::KeyRole, { Ok(PKESK3{ common: Default::default(), recipient: Some(recipient.keyid()), pk_algo: recipient.pk_algo(), esk: packet::PKESK::encrypt_common( Some(algo), session_key, recipient.parts_as_unspecified().role_as_unspecified())?, }) } /// Gets the recipient. pub fn recipient(&self) -> Option<&KeyID> { self.recipient.as_ref() } /// Sets the recipient. pub fn set_recipient(&mut self, recipient: Option<KeyID>) -> Option<KeyID> { ::std::mem::replace(&mut self.recipient, recipient) } /// Gets the public key algorithm. pub fn pk_algo(&self) -> PublicKeyAlgorithm { self.pk_algo } /// Sets the public key algorithm. pub fn set_pk_algo(&mut self, algo: PublicKeyAlgorithm) -> PublicKeyAlgorithm { ::std::mem::replace(&mut self.pk_algo, algo) } /// Gets the encrypted session key. pub fn esk(&self) -> &Ciphertext { &self.esk } /// Sets the encrypted session key. pub fn set_esk(&mut self, esk: Ciphertext) -> Ciphertext { ::std::mem::replace(&mut self.esk, esk) } /// Decrypts the encrypted session key. /// /// If the symmetric algorithm used to encrypt the message is /// known in advance, it should be given as argument. This allows /// us to reduce the side-channel leakage of the decryption /// operation for RSA. /// /// Returns the session key and symmetric algorithm used to /// encrypt the following payload. /// /// Returns `None` on errors. This prevents leaking information /// to an attacker, which could lead to compromise of secret key /// material with certain algorithms (RSA). See [Section 13 of /// RFC 9580]. /// /// [Section 13 of RFC 9580]: https://www.rfc-editor.org/rfc/rfc9580.html#section-13 pub fn decrypt(&self, decryptor: &mut dyn Decryptor, sym_algo_hint: Option<SymmetricAlgorithm>) -> Option<(SymmetricAlgorithm, SessionKey)> { self.decrypt_insecure(decryptor, sym_algo_hint).ok() } fn decrypt_insecure(&self, decryptor: &mut dyn Decryptor, sym_algo_hint: Option<SymmetricAlgorithm>) -> Result<(SymmetricAlgorithm, SessionKey)> { packet::PKESK::decrypt_common(&self.esk, decryptor, sym_algo_hint, true) .map(|(sym_algo, sk)| (sym_algo.expect("known for v3 PKESK"), sk)) } } impl From<PKESK3> for crate::packet::PKESK { fn from(p: PKESK3) -> Self { crate::packet::PKESK::V3(p) } } impl From<PKESK3> for Packet { fn from(p: PKESK3) -> Self { Packet::PKESK(p.into()) } } #[cfg(test)] impl Arbitrary for PKESK3 { fn arbitrary(g: &mut Gen) -> Self { let (ciphertext, pk_algo) = loop { let ciphertext = Ciphertext::arbitrary(g); if let Some(pk_algo) = ciphertext.pk_algo() { break (ciphertext, pk_algo); } }; let keyid = bool::arbitrary(g).then_some(KeyID::arbitrary(g)); PKESK3::new(keyid, pk_algo, ciphertext).unwrap() } } #[cfg(test)] mod tests { use super::*; use crate::Cert; use crate::PacketPile; use crate::packet::*; use crate::parse::Parse; use crate::serialize::MarshalInto; use crate::types::Curve; quickcheck! { fn roundtrip(p: PKESK3) -> bool { let q = PKESK3::from_bytes(&p.to_vec().unwrap()).unwrap(); assert_eq!(p, q); true } } #[test] fn decrypt_rsa() { if ! PublicKeyAlgorithm::RSAEncryptSign.is_supported() { eprintln!("Skipping test, algorithm is not supported."); return; } let cert = Cert::from_bytes( crate::tests::key("testy-private.pgp")).unwrap(); let pile = PacketPile::from_bytes( crate::tests::message("encrypted-to-testy.gpg")).unwrap(); let mut keypair = cert.subkeys().next().unwrap() .key().clone().parts_into_secret().unwrap().into_keypair().unwrap(); let pkesk: &PKESK = pile.descendants().next().unwrap().downcast_ref().unwrap(); let plain = pkesk.decrypt(&mut keypair, None).unwrap(); let plain_ = pkesk.decrypt(&mut keypair, Some(SymmetricAlgorithm::AES256)) .unwrap(); assert_eq!(plain, plain_); eprintln!("plain: {:?}", plain); } #[test] fn decrypt_ecdh_cv25519() { if ! (PublicKeyAlgorithm::EdDSA.is_supported() && Curve::Ed25519.is_supported() && PublicKeyAlgorithm::ECDH.is_supported() && Curve::Cv25519.is_supported()) { eprintln!("Skipping test, algorithm is not supported."); return; } let cert = Cert::from_bytes( crate::tests::key("testy-new-private.pgp")).unwrap(); let pile = PacketPile::from_bytes( crate::tests::message("encrypted-to-testy-new.pgp")).unwrap(); let mut keypair = cert.subkeys().next().unwrap() .key().clone().parts_into_secret().unwrap().into_keypair().unwrap(); let pkesk: &PKESK = pile.descendants().next().unwrap().downcast_ref().unwrap(); let plain = pkesk.decrypt(&mut keypair, None).unwrap(); let plain_ = pkesk.decrypt(&mut keypair, Some(SymmetricAlgorithm::AES256)) .unwrap(); assert_eq!(plain, plain_); eprintln!("plain: {:?}", plain); } #[test] fn decrypt_ecdh_nistp256() { if ! (PublicKeyAlgorithm::ECDSA.is_supported() && PublicKeyAlgorithm::ECDH.is_supported() && Curve::NistP256.is_supported()) { eprintln!("Skipping test, algorithm is not supported."); return; } let cert = Cert::from_bytes( crate::tests::key("testy-nistp256-private.pgp")).unwrap(); let pile = PacketPile::from_bytes( crate::tests::message("encrypted-to-testy-nistp256.pgp")).unwrap(); let mut keypair = cert.subkeys().next().unwrap() .key().clone().parts_into_secret().unwrap().into_keypair().unwrap(); let pkesk: &PKESK = pile.descendants().next().unwrap().downcast_ref().unwrap(); let plain = pkesk.decrypt(&mut keypair, None) .expect("ECDH decryption using P-256 key should work"); let plain_ = pkesk.decrypt(&mut keypair, Some(SymmetricAlgorithm::AES256)) .unwrap(); assert_eq!(plain, plain_); eprintln!("plain: {:?}", plain); } #[test] fn decrypt_ecdh_nistp384() { if ! (PublicKeyAlgorithm::ECDSA.is_supported() && PublicKeyAlgorithm::ECDH.is_supported() && Curve::NistP384.is_supported()) { eprintln!("Skipping test, algorithm is not supported."); return; } let cert = Cert::from_bytes( crate::tests::key("testy-nistp384-private.pgp")).unwrap(); let pile = PacketPile::from_bytes( crate::tests::message("encrypted-to-testy-nistp384.pgp")).unwrap(); let mut keypair = cert.subkeys().next().unwrap() .key().clone().parts_into_secret().unwrap().into_keypair().unwrap(); let pkesk: &PKESK = pile.descendants().next().unwrap().downcast_ref().unwrap(); let plain = pkesk.decrypt(&mut keypair, None) .expect("ECDH decryption using P-384 key should work"); let plain_ = pkesk.decrypt(&mut keypair, Some(SymmetricAlgorithm::AES256)) .unwrap(); assert_eq!(plain, plain_); eprintln!("plain: {:?}", plain); } #[test] #[allow(deprecated)] fn decrypt_elgamal() -> Result<()> { if ! (PublicKeyAlgorithm::DSA.is_supported() && PublicKeyAlgorithm::ElGamalEncrypt.is_supported()) { eprintln!("Skipping test, algorithm is not supported."); return Ok(()); } let cert = Cert::from_bytes( crate::tests::key("dsa2048-elgamal3072-private.pgp"))?; let pile = PacketPile::from_bytes( crate::tests::message("encrypted-to-dsa2048-elgamal3072.pgp"))?; let mut keypair = cert.subkeys().next().unwrap() .key().clone().parts_into_secret()?.into_keypair()?; let pkesk: &PKESK = pile.descendants().next().unwrap().downcast_ref().unwrap(); let plain = pkesk.decrypt(&mut keypair, None).unwrap(); let plain_ = pkesk.decrypt(&mut keypair, Some(SymmetricAlgorithm::AES256)) .unwrap(); assert_eq!(plain, plain_); eprintln!("plain: {:?}", plain); Ok(()) } #[test] fn decrypt_ecdh_nistp521() { if ! (PublicKeyAlgorithm::ECDSA.is_supported() && PublicKeyAlgorithm::ECDH.is_supported() && Curve::NistP521.is_supported()) { eprintln!("Skipping test, algorithm is not supported."); return; } let cert = Cert::from_bytes( crate::tests::key("testy-nistp521-private.pgp")).unwrap(); let pile = PacketPile::from_bytes( crate::tests::message("encrypted-to-testy-nistp521.pgp")).unwrap(); let mut keypair = cert.subkeys().next().unwrap() .key().clone().parts_into_secret().unwrap().into_keypair().unwrap(); let pkesk: &PKESK = pile.descendants().next().unwrap().downcast_ref().unwrap(); let plain = pkesk.decrypt(&mut keypair, None) .expect("ECDH decryption using P-521 key should work"); let plain_ = pkesk.decrypt(&mut keypair, Some(SymmetricAlgorithm::AES256)) .unwrap(); assert_eq!(plain, plain_); eprintln!("plain: {:?}", plain); } #[test] fn decrypt_with_short_cv25519_secret_key() { if ! (PublicKeyAlgorithm::ECDH.is_supported() && Curve::Cv25519.is_supported()) { eprintln!("Skipping test, algorithm is not supported."); return; } use super::PKESK3; use crate::crypto::SessionKey; use crate::{HashAlgorithm, SymmetricAlgorithm}; use crate::packet::key::{Key4, UnspecifiedRole}; // 20 byte sec key let mut secret_key = [ 0x0,0x0, 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, 0x1,0x2,0x2,0x2,0x2,0x2,0x2,0x2,0x2,0x2, 0x1,0x2,0x2,0x2,0x2,0x2,0x2,0x2,0x0,0x0 ]; // Ensure that the key is at least somewhat valid, according to the // generation procedure specified in "Responsibilities of the user": // https://cr.yp.to/ecdh/curve25519-20060209.pdf#page=5 // Only perform the bit-twiddling on the last byte. This is done so that // we can still have somewhat defined multiplication while still testing // the "short" key logic. // secret_key[0] &= 0xf8; secret_key[31] &= 0x7f; secret_key[31] |= 0x40; let key: Key<_, UnspecifiedRole> = Key4::import_secret_cv25519( &secret_key, HashAlgorithm::SHA256, SymmetricAlgorithm::AES256, None, ).unwrap().into(); let sess_key = SessionKey::new(32).unwrap(); let pkesk = PKESK3::for_recipient(SymmetricAlgorithm::AES256, &sess_key, &key).unwrap(); let mut keypair = key.into_keypair().unwrap(); pkesk.decrypt(&mut keypair, None).unwrap(); } /// Insufficient validation of RSA ciphertexts crash Nettle. /// /// See CVE-2021-3580. #[test] fn cve_2021_3580_ciphertext_too_long() -> Result<()> { if ! PublicKeyAlgorithm::RSAEncryptSign.is_supported() { eprintln!("Skipping test, algorithm is not supported."); return Ok(()); } // Get (any) 2k RSA key. let cert = Cert::from_bytes( crate::tests::key("testy-private.pgp"))?; let mut keypair = cert.primary_key().key().clone() .parts_into_secret()?.into_keypair()?; let pile = PacketPile::from_bytes(b"-----BEGIN PGP ARMORED FILE----- wcDNAwAAAAAAAAAAAQwGI5SkpcRMjkiOKx332kxv+2Xh4y1QTefPilKOPOlHYFa0 rnnLaQVEACKJNQ38YuCFUvtpK4IN2grjlj71IP24+KDp3ZuVWnVTS6JcyE10Y9iq uGvKdS0C17XCze2LD4ouVOrUZHGXpeDT47w6DsHb/0UE85h56wpk2CzO1XFQzHxX HR2DDLqqeFVzTv0peYiQfLHl7kWXijTNEqmYhFCzxuICXzuClAAJM+fVIRfcm2tm 2R4AxOQGv9DlWfZwbkpKfj/uuo0CAe21n4NT+NzdVgPlff/hna3yGgPe1B+vjq4e jfxHg+pvo/HTLkV+c2HAGbM1bCb/5TedGd1nAMSAIOu/J/WQp/l3HtEv63HaVPZJ JInJ6L/KyPwjm/ieZx5EWOLJgFRWGCrBGnb8T81lkFey7uZR5Xiq+9KoUhHQFw8N joc0YUVyhUBVFf4B0zVZRUfqZyJtJ07Sl5xppI12U1HQCTjn7Fp8BHMPKuBotYzv 1Q4f00k6Txctw+LDRM17/w== =VtwB -----END PGP ARMORED FILE----- ")?; let pkesk: &PKESK = pile.descendants().next().unwrap().downcast_ref().unwrap(); // Boom goes the assertion. let _ = pkesk.decrypt(&mut keypair, None); Ok(()) } /// Insufficient validation of RSA ciphertexts crash Nettle. /// /// See CVE-2021-3580. #[test] fn cve_2021_3580_zero_ciphertext() -> Result<()> { if ! PublicKeyAlgorithm::RSAEncryptSign.is_supported() { eprintln!("Skipping test, algorithm is not supported."); return Ok(()); } // Get (any) 2k RSA key. let cert = Cert::from_bytes( crate::tests::key("testy-private.pgp"))?; let mut keypair = cert.primary_key().key().clone() .parts_into_secret()?.into_keypair()?; let pile = PacketPile::from_bytes(b"-----BEGIN PGP ARMORED FILE----- wQwDAAAAAAAAAAABAAA= =H/1T -----END PGP ARMORED FILE----- ")?; let pkesk: &PKESK = pile.descendants().next().unwrap().downcast_ref().unwrap(); // Boom goes the memory safety. let _ = pkesk.decrypt(&mut keypair, None); Ok(()) } } ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/src/packet/pkesk/v6.rs��������������������������������������������������������0000644�0000000�0000000�00000014214�10461020230�0017015�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������//! PublicKey-Encrypted Session Key packets version 6. //! //! The session key is needed to decrypt the actual ciphertext. See //! [Version 6 Public Key Encrypted Session Key Packet Format] for //! details. //! //! [Version 6 Public Key Encrypted Session Key Packet Format]: https://www.rfc-editor.org/rfc/rfc9580.html#name-version-6-public-key-encryp #[cfg(test)] use quickcheck::{Arbitrary, Gen}; use crate::packet::key; use crate::packet::Key; use crate::Fingerprint; use crate::crypto::Decryptor; use crate::crypto::mpi::Ciphertext; use crate::Packet; use crate::PublicKeyAlgorithm; use crate::Result; use crate::SymmetricAlgorithm; use crate::crypto::SessionKey; use crate::packet; /// Holds an asymmetrically encrypted session key. /// /// The session key is needed to decrypt the actual ciphertext. See /// [Version 6 Public Key Encrypted Session Key Packet Format] for /// details. /// /// [Version 6 Public Key Encrypted Session Key Packet Format]: https://www.rfc-editor.org/rfc/rfc9580.html#name-version-6-public-key-encryp #[derive(Clone, Debug, PartialEq, Eq, Hash)] pub struct PKESK6 { /// CTB header fields. pub(crate) common: packet::Common, /// Fingerprint of the key this is encrypted to. /// /// If the value is `None`, the recipient has not been specified /// by the sender to decrease metadata leakage. recipient: Option<Fingerprint>, /// Public key algorithm used to encrypt the session key. pk_algo: PublicKeyAlgorithm, /// The encrypted session key. esk: Ciphertext, } assert_send_and_sync!(PKESK6); impl PKESK6 { /// Creates a new PKESK6 packet. pub fn new(recipient: Option<Fingerprint>, pk_algo: PublicKeyAlgorithm, encrypted_session_key: Ciphertext) -> Result<PKESK6> { Ok(PKESK6 { common: Default::default(), recipient, pk_algo, esk: encrypted_session_key, }) } /// Creates a new PKESK6 packet for the given recipient. /// /// The given symmetric algorithm must match the algorithm that is /// used to encrypt the payload. pub fn for_recipient<P, R>(session_key: &SessionKey, recipient: &Key<P, R>) -> Result<PKESK6> where P: key::KeyParts, R: key::KeyRole, { // ElGamal is phased out in RFC 9580. #[allow(deprecated)] if recipient.pk_algo() == PublicKeyAlgorithm::ElGamalEncrypt || recipient.pk_algo() == PublicKeyAlgorithm::ElGamalEncryptSign { return Err(crate::Error::InvalidOperation( "MUST NOT encrypt with version 6 ElGamal keys".into()) .into()); } Ok(PKESK6 { common: Default::default(), recipient: Some(recipient.fingerprint()), pk_algo: recipient.pk_algo(), esk: packet::PKESK::encrypt_common( None, session_key, recipient.parts_as_unspecified().role_as_unspecified())?, }) } /// Gets the recipient. pub fn recipient(&self) -> Option<&Fingerprint> { self.recipient.as_ref() } /// Sets the recipient. pub fn set_recipient(&mut self, recipient: Option<Fingerprint>) -> Option<Fingerprint> { std::mem::replace(&mut self.recipient, recipient) } /// Gets the public key algorithm. pub fn pk_algo(&self) -> PublicKeyAlgorithm { self.pk_algo } /// Sets the public key algorithm. pub fn set_pk_algo(&mut self, algo: PublicKeyAlgorithm) -> PublicKeyAlgorithm { std::mem::replace(&mut self.pk_algo, algo) } /// Gets the encrypted session key. pub fn esk(&self) -> &Ciphertext { &self.esk } /// Sets the encrypted session key. pub fn set_esk(&mut self, esk: Ciphertext) -> Ciphertext { std::mem::replace(&mut self.esk, esk) } /// Decrypts the encrypted session key. /// /// If the symmetric algorithm used to encrypt the message is /// known in advance, it should be given as argument. This allows /// us to reduce the side-channel leakage of the decryption /// operation for RSA. /// /// Returns the session key and symmetric algorithm used to /// encrypt the following payload. /// /// Returns `None` on errors. This prevents leaking information /// to an attacker, which could lead to compromise of secret key /// material with certain algorithms (RSA). See [Avoiding Leaks /// from PKCS#1 Errors]. /// /// [Avoiding Leaks from PKCS#1 Errors]: https://www.rfc-editor.org/rfc/rfc9580.html#name-avoiding-leaks-from-pkcs1-e pub fn decrypt(&self, decryptor: &mut dyn Decryptor, sym_algo_hint: Option<SymmetricAlgorithm>) -> Option<SessionKey> { self.decrypt_insecure(decryptor, sym_algo_hint).ok() } fn decrypt_insecure(&self, decryptor: &mut dyn Decryptor, sym_algo_hint: Option<SymmetricAlgorithm>) -> Result<SessionKey> { packet::PKESK::decrypt_common(&self.esk, decryptor, sym_algo_hint, false) .map(|(_sym_algo, key)| key) } } impl From<PKESK6> for packet::PKESK { fn from(p: PKESK6) -> Self { packet::PKESK::V6(p) } } impl From<PKESK6> for Packet { fn from(p: PKESK6) -> Self { Packet::PKESK(p.into()) } } #[cfg(test)] impl Arbitrary for PKESK6 { fn arbitrary(g: &mut Gen) -> Self { let (ciphertext, pk_algo) = loop { let ciphertext = Ciphertext::arbitrary(g); if let Some(pk_algo) = ciphertext.pk_algo() { break (ciphertext, pk_algo); } }; PKESK6::new(bool::arbitrary(g).then(|| Fingerprint::arbitrary_v6(g)), pk_algo, ciphertext).unwrap() } } #[cfg(test)] mod tests { use super::*; use crate::parse::Parse; use crate::serialize::MarshalInto; quickcheck! { fn roundtrip(p: PKESK6) -> bool { let q = PKESK6::from_bytes(&p.to_vec().unwrap()).unwrap(); assert_eq!(p, q); true } } } ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/src/packet/pkesk.rs�����������������������������������������������������������0000644�0000000�0000000�00000025457�10461020230�0016475�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������//! PublicKey-Encrypted Session Key packets. //! //! The session key is needed to decrypt the actual ciphertext. See //! [Section 5.1 of RFC 9580] for details. //! //! [Section 5.1 of RFC 9580]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.1 #[cfg(test)] use quickcheck::{Arbitrary, Gen}; use crate::Error; use crate::KeyHandle; use crate::packet::key; use crate::packet::Key; use crate::packet::Packet; use crate::crypto::Decryptor; use crate::crypto::mpi::Ciphertext; use crate::PublicKeyAlgorithm; use crate::Result; use crate::SymmetricAlgorithm; use crate::crypto::SessionKey; use crate::packet; mod v3; pub use v3::PKESK3; mod v6; pub use v6::PKESK6; /// Holds an asymmetrically encrypted session key. /// /// The session key is used to decrypt the actual ciphertext, which is /// typically stored in a [`SEIP`] packet. See [Section 5.1 of /// RFC 9580] for details. /// /// A PKESK packet is not normally instantiated directly. In most /// cases, you'll create one as a side effect of encrypting a message /// using the [streaming serializer], or parsing an encrypted message /// using the [`PacketParser`]. /// /// [`SEIP`]: crate::packet::SEIP /// [Section 5.1 of RFC 9580]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.1 /// [streaming serializer]: crate::serialize::stream /// [`PacketParser`]: crate::parse::PacketParser #[non_exhaustive] #[derive(PartialEq, Eq, Hash, Clone, Debug)] pub enum PKESK { /// PKESK packet version 3. V3(PKESK3), /// PKESK packet version 6. V6(PKESK6), } assert_send_and_sync!(PKESK); impl PKESK { /// Gets the version. pub fn version(&self) -> u8 { match self { PKESK::V3(_) => 3, PKESK::V6(_) => 6, } } /// Gets the recipient. pub fn recipient(&self) -> Option<KeyHandle> { match self { PKESK::V3(p) => p.recipient().map(Into::into), PKESK::V6(p) => p.recipient().map(Into::into), } } /// Gets the public key algorithm. pub fn pk_algo(&self) -> PublicKeyAlgorithm { match self { PKESK::V3(p) => p.pk_algo(), PKESK::V6(p) => p.pk_algo(), } } /// Gets the encrypted session key. pub fn esk(&self) -> &crate::crypto::mpi::Ciphertext { match self { PKESK::V3(p) => p.esk(), PKESK::V6(p) => p.esk(), } } /// Decrypts the encrypted session key. /// /// If the symmetric algorithm used to encrypt the message is /// known in advance, it should be given as argument. This allows /// us to reduce the side-channel leakage of the decryption /// operation for RSA. /// /// Returns the session key and symmetric algorithm used to /// encrypt the following payload. /// /// Returns `None` on errors. This prevents leaking information /// to an attacker, which could lead to compromise of secret key /// material with certain algorithms (RSA). See [Section 13 of /// RFC 9580]. /// /// [Section 13 of RFC 9580]: https://www.rfc-editor.org/rfc/rfc9580.html#section-13 pub fn decrypt(&self, decryptor: &mut dyn Decryptor, sym_algo_hint: Option<SymmetricAlgorithm>) -> Option<(Option<SymmetricAlgorithm>, SessionKey)> { match self { PKESK::V3(p) => p.decrypt(decryptor, sym_algo_hint) .map(|(s, k)| (Some(s), k)), PKESK::V6(p) => p.decrypt(decryptor, sym_algo_hint) .map(|k| (None, k)), } } } impl From<PKESK> for Packet { fn from(p: PKESK) -> Self { Packet::PKESK(p) } } /// Returns whether the given `algo` requires checksumming, and /// whether the cipher octet is prepended to the encrypted session /// key, or it is prepended to the plain session key and then /// encrypted. fn classify_pk_algo(algo: PublicKeyAlgorithm, seipdv1: bool) -> Result<(bool, bool, bool)> { #[allow(deprecated)] match algo { // Classical encryption: plaintext includes the cipher // octet and is checksummed. PublicKeyAlgorithm::RSAEncryptSign | PublicKeyAlgorithm::RSAEncrypt | PublicKeyAlgorithm::ElGamalEncrypt | PublicKeyAlgorithm::ElGamalEncryptSign | PublicKeyAlgorithm::ECDH => Ok((true, false, seipdv1)), // Corner case: for X25519 and X448 we have to prepend // the cipher octet to the ciphertext instead of // encrypting it. PublicKeyAlgorithm::X25519 | PublicKeyAlgorithm::X448 => Ok((false, seipdv1, false)), a @ PublicKeyAlgorithm::RSASign | a @ PublicKeyAlgorithm::DSA | a @ PublicKeyAlgorithm::ECDSA | a @ PublicKeyAlgorithm::EdDSA | a @ PublicKeyAlgorithm::Ed25519 | a @ PublicKeyAlgorithm::Ed448 | a @ PublicKeyAlgorithm::Private(_) | a @ PublicKeyAlgorithm::Unknown(_) => Err(Error::UnsupportedPublicKeyAlgorithm(a).into()), } } impl packet::PKESK { fn encrypt_common(algo: Option<SymmetricAlgorithm>, session_key: &SessionKey, recipient: &Key<key::UnspecifiedParts, key::UnspecifiedRole>) -> Result<Ciphertext> { let (checksummed, unencrypted_cipher_octet, encrypted_cipher_octet) = classify_pk_algo(recipient.pk_algo(), algo.is_some())?; // We may need to prefix the cipher specifier to the session // key, and we may add a two-octet checksum. let mut psk = Vec::with_capacity( encrypted_cipher_octet.then(|| 1).unwrap_or(0) + session_key.len() + checksummed.then(|| 2).unwrap_or(0)); if let Some(algo) = algo { if encrypted_cipher_octet { psk.push(algo.into()); } } psk.extend_from_slice(session_key); if checksummed { // Compute the sum modulo 65536, i.e. as u16. let checksum = session_key .iter() .cloned() .map(u16::from) .fold(0u16, u16::wrapping_add); psk.extend_from_slice(&checksum.to_be_bytes()); } // Make sure it is cleaned up when dropped. let psk: SessionKey = psk.into(); let mut esk = recipient.encrypt(&psk)?; if let Some(algo) = algo { if unencrypted_cipher_octet { match esk { Ciphertext::X25519 { ref mut key, .. } | Ciphertext::X448 { ref mut key, .. } => { let mut new_key = Vec::with_capacity(1 + key.len()); new_key.push(algo.into()); new_key.extend_from_slice(key); *key = new_key.into(); }, _ => unreachable!("We only prepend the cipher octet \ for X25519 and X448"), }; } } Ok(esk) } fn decrypt_common(ciphertext: &Ciphertext, decryptor: &mut dyn Decryptor, sym_algo_hint: Option<SymmetricAlgorithm>, seipdv1: bool) -> Result<(Option<SymmetricAlgorithm>, SessionKey)> { let (checksummed, unencrypted_cipher_octet, encrypted_cipher_octet) = classify_pk_algo(decryptor.public().pk_algo(), seipdv1)?; //dbg!((checksummed, unencrypted_cipher_octet, encrypted_cipher_octet)); let mut sym_algo: Option<SymmetricAlgorithm> = None; let modified_ciphertext; let esk; if unencrypted_cipher_octet { match ciphertext { Ciphertext::X25519 { e, key, } => { sym_algo = Some((*key.get(0).ok_or_else( || Error::MalformedPacket("Short ESK".into()))?) .into()); modified_ciphertext = Ciphertext::X25519 { e: e.clone(), key: key[1..].into(), }; esk = &modified_ciphertext; }, Ciphertext::X448 { e, key, } => { sym_algo = Some((*key.get(0).ok_or_else( || Error::MalformedPacket("Short ESK".into()))?) .into()); modified_ciphertext = Ciphertext::X448 { e: e.clone(), key: key[1..].into(), }; esk = &modified_ciphertext; }, _ => { // We only prepend the cipher octet for X25519 and // X448, yet we're trying to decrypt a ciphertext // that uses a different algorithm, clearly // something has gone wrong and will fail when we // try to decrypt it downstream. esk = ciphertext; }, } } else { esk = ciphertext; } let plaintext_len = if let Some(s) = sym_algo_hint { Some(encrypted_cipher_octet.then(|| 1).unwrap_or(0) + s.key_size()? + checksummed.then(|| 2).unwrap_or(0)) } else { None }; let plain = decryptor.decrypt(esk, plaintext_len)?; let key_rgn = encrypted_cipher_octet.then(|| 1).unwrap_or(0) ..plain.len().saturating_sub(checksummed.then(|| 2).unwrap_or(0)); if encrypted_cipher_octet { sym_algo = Some(plain[0].into()); } let sym_algo = sym_algo.or(sym_algo_hint); if let Some(sym_algo) = sym_algo { if key_rgn.len() != sym_algo.key_size()? { return Err(Error::MalformedPacket( format!("session key has the wrong size (got: {}, expected: {})", key_rgn.len(), sym_algo.key_size()?)).into()) } } let mut key: SessionKey = vec![0u8; key_rgn.len()].into(); key.copy_from_slice(&plain[key_rgn]); if checksummed { let our_checksum = key.iter().map(|&x| x as usize).sum::<usize>() & 0xffff; let their_checksum = (plain[plain.len() - 2] as usize) << 8 | (plain[plain.len() - 1] as usize); if their_checksum != our_checksum { return Err(Error::MalformedPacket( "key checksum wrong".to_string()).into()); } } Ok((sym_algo, key)) } } #[cfg(test)] impl Arbitrary for super::PKESK { fn arbitrary(g: &mut Gen) -> Self { if bool::arbitrary(g) { PKESK3::arbitrary(g).into() } else { PKESK6::arbitrary(g).into() } } } �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/src/packet/prelude.rs���������������������������������������������������������0000644�0000000�0000000�00000002651�10461020230�0017007�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������//! Brings the most relevant types and traits into scope for working //! with packets. //! //! Less often used types and traits that are more likely to lead to a //! naming conflict are not brought into scope. For instance, the //! markers [`PublicParts`], etc. are not imported to avoid potential //! naming conflicts. Instead, they should be accessed as //! [`key::PublicParts`]. And, [`user_attribute::Subpacket`] is not //! imported, because it is rarely used. If required, it should be //! imported explicitly. //! //! [`PublicParts`]: key::PublicParts //! [`user_attribute::Subpacket`]: user_attribute::Subpacket //! //! # Examples //! //! ``` //! # #![allow(unused_imports)] //! # use sequoia_openpgp as openpgp; //! use openpgp::packet::prelude::*; //! ``` pub use crate::packet::{ Any, Body, CompressedData, Container, Header, Key, Literal, MDC, Marker, OnePassSig, PKESK, Packet, Padding, SEIP, SKESK, Signature, Tag, Trust, Unknown, UserAttribute, UserID, key, key::Key4, key::Key6, key::SecretKeyMaterial, one_pass_sig::OnePassSig3, one_pass_sig::OnePassSig6, pkesk::{ PKESK3, PKESK6, }, seip::{ SEIP1, SEIP2, }, signature, signature::Signature4, signature::Signature6, signature::SignatureBuilder, skesk::SKESK4, skesk::SKESK6, user_attribute, }; ���������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/src/packet/seip/v2.rs���������������������������������������������������������0000644�0000000�0000000�00000010473�10461020230�0016637�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������//! Symmetrically Encrypted Integrity Protected data packets version 2. //! //! An encrypted data packet is a container. See [Version 2 //! Symmetrically Encrypted and Integrity Protected Data Packet //! Format] for details. //! //! [Version 2 Symmetrically Encrypted and Integrity Protected Data Packet Format]: https://www.rfc-editor.org/rfc/rfc9580.html#name-version-2-symmetrically-enc use crate::{ Error, packet::{ self, Packet, SEIP, }, Result, types::{ AEADAlgorithm, SymmetricAlgorithm, }, }; /// Holds an encrypted data packet. /// /// An encrypted data packet is a container. See [Version 2 /// Symmetrically Encrypted and Integrity Protected Data Packet /// Format] for details. /// /// [Version 2 Symmetrically Encrypted and Integrity Protected Data Packet Format]: https://www.rfc-editor.org/rfc/rfc9580.html#name-version-2-symmetrically-enc /// /// # A note on equality /// /// An unprocessed (encrypted) `SEIP2` packet is never considered equal /// to a processed (decrypted) one. Likewise, a processed (decrypted) /// packet is never considered equal to a structured (parsed) one. // IMPORTANT: If you add fields to this struct, you need to explicitly // IMPORTANT: implement PartialEq, Eq, and Hash. #[derive(Clone, Debug, PartialEq, Eq, Hash)] pub struct SEIP2 { /// CTB packet header fields. pub(crate) common: packet::Common, /// Symmetric algorithm. sym_algo: SymmetricAlgorithm, /// AEAD algorithm. aead: AEADAlgorithm, /// Chunk size. chunk_size: u64, /// Salt. salt: [u8; 32], /// This is a container packet. container: packet::Container, } assert_send_and_sync!(SEIP2); impl SEIP2 { /// Creates a new SEIP2 packet. pub fn new(sym_algo: SymmetricAlgorithm, aead: AEADAlgorithm, chunk_size: u64, salt: [u8; 32]) -> Result<Self> { if chunk_size.count_ones() != 1 { return Err(Error::InvalidArgument( format!("chunk size is not a power of two: {}", chunk_size)) .into()); } if chunk_size < 64 { return Err(Error::InvalidArgument( format!("chunk size is too small: {}", chunk_size)) .into()); } Ok(SEIP2 { common: Default::default(), sym_algo, aead, chunk_size, salt, container: Default::default(), }) } /// Gets the symmetric algorithm. pub fn symmetric_algo(&self) -> SymmetricAlgorithm { self.sym_algo } /// Sets the symmetric algorithm. pub fn set_symmetric_algo(&mut self, sym_algo: SymmetricAlgorithm) -> SymmetricAlgorithm { std::mem::replace(&mut self.sym_algo, sym_algo) } /// Gets the AEAD algorithm. pub fn aead(&self) -> AEADAlgorithm { self.aead } /// Sets the AEAD algorithm. pub fn set_aead(&mut self, aead: AEADAlgorithm) -> AEADAlgorithm { std::mem::replace(&mut self.aead, aead) } /// Gets the chunk size. pub fn chunk_size(&self) -> u64 { self.chunk_size } /// Sets the chunk size. pub fn set_chunk_size(&mut self, chunk_size: u64) -> Result<()> { if chunk_size.count_ones() != 1 { return Err(Error::InvalidArgument( format!("chunk size is not a power of two: {}", chunk_size)) .into()); } if chunk_size < 64 { return Err(Error::InvalidArgument( format!("chunk size is too small: {}", chunk_size)) .into()); } self.chunk_size = chunk_size; Ok(()) } /// Gets the size of a chunk with a digest. pub fn chunk_digest_size(&self) -> Result<u64> { Ok(self.chunk_size + self.aead.digest_size()? as u64) } /// Gets the salt. pub fn salt(&self) -> &[u8; 32] { &self.salt } /// Sets the salt. pub fn set_salt(&mut self, salt: [u8; 32]) -> [u8; 32] { std::mem::replace(&mut self.salt, salt) } } impl_processed_body_forwards!(SEIP2); impl From<SEIP2> for SEIP { fn from(p: SEIP2) -> Self { SEIP::V2(p) } } impl From<SEIP2> for Packet { fn from(s: SEIP2) -> Self { Packet::SEIP(s.into()) } } �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/src/packet/seip.rs������������������������������������������������������������0000644�0000000�0000000�00000003060�10461020230�0016302�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������//! Symmetrically Encrypted Integrity Protected data packets. //! //! An encrypted data packet is a container. See [Section 5.13 of RFC //! 9580] for details. //! //! [Section 5.13 of RFC 9580]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.13 use crate::packet; use crate::Packet; mod v2; pub use v2::*; /// Holds an encrypted data packet. /// /// An encrypted data packet is a container. See [Section 5.13 of RFC /// 9580] for details. /// /// [Section 5.13 of RFC 9580]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.13 /// /// # A note on equality /// /// An unprocessed (encrypted) `SEIP` packet is never considered equal /// to a processed (decrypted) one. Likewise, a processed (decrypted) /// packet is never considered equal to a structured (parsed) one. // IMPORTANT: If you add fields to this struct, you need to explicitly // IMPORTANT: implement PartialEq, Eq, and Hash. #[derive(Clone, Debug, PartialEq, Eq, Hash)] pub struct SEIP1 { /// CTB packet header fields. pub(crate) common: packet::Common, /// This is a container packet. container: packet::Container, } assert_send_and_sync!(SEIP1); impl SEIP1 { /// Creates a new SEIP1 packet. pub fn new() -> Self { Self { common: Default::default(), container: Default::default(), } } } impl_processed_body_forwards!(SEIP1); impl From<SEIP1> for super::SEIP { fn from(p: SEIP1) -> Self { super::SEIP::V1(p) } } impl From<SEIP1> for Packet { fn from(s: SEIP1) -> Self { Packet::SEIP(s.into()) } } ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/src/packet/signature/cache.rs�������������������������������������������������0000644�0000000�0000000�00000051024�10461020230�0020411�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������//! A signature verification cache. //! //! Signature verification is expensive. To mitigate this, Sequoia //! includes a signature verification cache. This is keyed on the //! hash of the signature's context: the signature MPIs, the computed //! hash, and the key. Since this context is needed to use the cache, //! it's hard to misuse the cache. //! //! The signature cache also supports dumping and restoring the cache //! from disk (see [`SignatureVerificationCache::restore`] and //! [`SignatureVerificationCache::dump`]). This is particularly //! useful for one-shot programs, which don't have enough time to warm //! the cache up. //! //! The cache file needs to be managed carefully. In particular, you //! probably don't want to allow it to grow without bound. To help //! manage the cache, the cache keeps track of whether an entry was //! added ([`Entry::inserted`]), and whether it was accessed //! ([`Entry::accessed`]). use std::cmp; use std::collections::BTreeMap; use std::collections::btree_map; use std::sync::OnceLock; use std::sync::RwLock; use std::sync::atomic::AtomicBool; use std::sync::atomic::AtomicUsize; use std::sync::atomic::Ordering; use crate::HashAlgorithm; use crate::Result; use crate::packet::Key; use crate::packet::Signature; use crate::packet::key; const TRACE: bool = false; /// The cache singleton. static SIGNATURE_VERIFICATION_CACHE: SignatureVerificationCache = SignatureVerificationCache::empty(); /// The hash algorithm that we use. /// /// SHA-512 is faster than SHA-256 on 64-bit hardware. const HASH_ALGO: HashAlgorithm = HashAlgorithm::SHA512; /// We use SHA-512, which has 512 / 8 bytes = 64 bytes. We truncate /// it to the first 256 bits, i.e. we do SHA-512-256. We're only /// worried about second pre-image resistance, so this is enough even /// when the signature uses SHA-512. const HASH_BYTES_UNTRUNCATED: usize = 512 / 8; const HASH_BYTES_TRUNCATED: usize = HASH_BYTES_UNTRUNCATED / 2; // The value of a cache entry. const VALUE_BYTES: usize = HASH_BYTES_TRUNCATED; type Value = [u8; VALUE_BYTES]; const VALUE_NULL: Value = [0u8; VALUE_BYTES]; /// Information about a cache entry. #[derive(Debug)] pub struct Metadata { /// Whether the entry was inserted. /// /// Entries added by [`SignatureVerificationCache::restore`] have /// this cleared. Entries added as a side effect of a signature /// verification have this set. inserted: bool, /// Whether the entry is accessed. /// /// An entry added by [`SignatureVerificationCache::restore`] /// initially is not considered to have been accessed. This is /// set when an entry is used by the signature verification code. accessed: AtomicBool, } impl Clone for Metadata { fn clone(&self) -> Metadata { Self { inserted: self.inserted, accessed: AtomicBool::from(self.accessed.load(Ordering::Relaxed)), } } } impl Metadata { /// Instantiate a value. /// /// Entries added by [`SignatureVerificationCache::restore`] have /// this cleared. Entries added as a side effect of a signature /// verification have this set. fn new(inserted: bool) -> Self { Metadata { inserted, accessed: false.into(), } } /// Whether the entry was inserted since the program started. /// /// Entries added by [`SignatureVerificationCache::restore`] have /// this cleared. Entries added as a side effect of a signature /// verification have this set. pub fn inserted(&self) -> bool { self.inserted } /// Whether the entry was accessed. /// /// An entry added by [`SignatureVerificationCache::restore`] /// initially is not considered to have been accessed. This is /// set when an entry is used by the signature verification code. pub fn accessed(&self) -> bool { self.accessed.load(Ordering::Relaxed) } } /// An entry in the signature verification cache. /// /// You can iterate over the cache using /// [`SignatureVerificationCache::dump`]. /// /// Two entries are considered equal if their values are identical; /// the metadata is ignored. #[derive(Clone)] pub struct Entry { value: Value, metadata: Metadata, } impl PartialOrd for Entry { fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> { Some(self.cmp(other)) } } impl Ord for Entry { fn cmp(&self, other: &Self) -> cmp::Ordering { self.value.cmp(&other.value) } } impl PartialEq for Entry { fn eq(&self, other: &Self) -> bool { self.cmp(other) == cmp::Ordering::Equal } } impl Eq for Entry {} impl Entry { /// Computes the cache entry from the signature and its context. pub(super) fn new(sig: &Signature, computed_digest: &[u8], key: &Key<key::PublicParts, key::UnspecifiedRole>) -> Result<Self> { use crate::serialize::Marshal; use crate::serialize::MarshalInto; // Hash(Version || Signature MPIs Len || Signature MPIs || Hash Algorithm || Digest || Key MPIs) // // - Version: one byte, currently 0. // - Signature MPIs: 4 bytes, little endian // - Signature MPIs: variable number of bytes, the signature's MPIs // - Hash algorithm: one byte, the hash algorithm // - Digest: HashAlgorithm::len() bytes, the digest's length // - Key: variable number of bytes, the key's MPIs let mut context = HASH_ALGO.context()?.for_digest(); // Version. context.update(&[ 0u8 ]); // MPIs. let mpis_len = sig.mpis.serialized_len(); context.update(&[ (mpis_len & 0xFF) as u8, ((mpis_len >> 8) & 0xFF) as u8, ((mpis_len >> 16) & 0xFF) as u8, ((mpis_len >> 24) & 0xFF) as u8, ]); sig.mpis.export(&mut context)?; // Hash algorithm. context.update(&[ u8::from(sig.hash_algo()) ]); // Hash. context.update(computed_digest); // Keys. key.mpis().export(&mut context)?; let context_hash = context.into_digest()?; let mut value = VALUE_NULL; value.copy_from_slice(&context_hash[..VALUE_BYTES]); Ok(Entry { value, metadata: Metadata::new(true), }) } /// Returns the cache entry's value. /// /// This value is opaque and must not be interpreted. /// /// You can write this value to disk, and restore it using /// [`SignatureVerificationCache::restore`]. pub fn value(&self) -> &[u8] { &self.value } /// Returns whether the entry is in the cache. pub(super) fn present(&self) -> bool { SIGNATURE_VERIFICATION_CACHE.present(&self.value) } /// Inserts the entry in the cache. /// /// `verified` indicates whether the signature could be verified /// (`true`), or not (`false`). pub(super) fn insert(self, verified: bool) { // We don't cache negative results. if verified { SIGNATURE_VERIFICATION_CACHE.insert(self.value); } } /// Whether the entry was inserted since the program started. /// /// Entries added by [`SignatureVerificationCache::restore`] have /// this cleared. Entries added as a side effect of a signature /// verification have this set. pub fn inserted(&self) -> bool { self.metadata.inserted } /// Whether the entry was accessed. /// /// An entry added by [`SignatureVerificationCache::restore`] /// initially is not considered to have been accessed. This is /// set when an entry is used by the signature verification code. pub fn accessed(&self) -> bool { self.metadata.accessed.load(Ordering::Relaxed) } } /// We split on the `BUCKETS_BITS` most significant bits of the value /// to reduce locking contention. const BUCKETS_BITS: usize = 4; const BUCKETS: usize = 1 << BUCKETS_BITS; const BUCKETS_SHIFT: usize = 8 - BUCKETS_BITS; /// A signature verification cache. pub struct SignatureVerificationCache { /// A sorted list of entries. This is filled by /// `SignatureVerificationCache::restore`, and is much faster than /// filling the btrees. list: OnceLock<Vec<Entry>>, /// The buckets. buckets: [ RwLock<BTreeMap<Value, Metadata>>; BUCKETS ], /// The number of cache hits. hits: AtomicUsize, /// The number of cache misses. misses: AtomicUsize, /// The number of entries that were restored (i.e., not inserted). preloads: AtomicUsize, /// The number of entries that were inserted (i.e., not restored). insertions: AtomicUsize, } impl SignatureVerificationCache { const fn empty() -> Self { SignatureVerificationCache { list: OnceLock::new(), buckets: [ // 0 RwLock::new(BTreeMap::new()), RwLock::new(BTreeMap::new()), RwLock::new(BTreeMap::new()), RwLock::new(BTreeMap::new()), RwLock::new(BTreeMap::new()), RwLock::new(BTreeMap::new()), RwLock::new(BTreeMap::new()), RwLock::new(BTreeMap::new()), // 8 RwLock::new(BTreeMap::new()), RwLock::new(BTreeMap::new()), RwLock::new(BTreeMap::new()), RwLock::new(BTreeMap::new()), RwLock::new(BTreeMap::new()), RwLock::new(BTreeMap::new()), RwLock::new(BTreeMap::new()), RwLock::new(BTreeMap::new()), ], hits: AtomicUsize::new(0), misses: AtomicUsize::new(0), preloads: AtomicUsize::new(0), insertions: AtomicUsize::new(0), } } /// Returns the bucket that a cache entry goes into. fn bucket(value: &[u8]) -> usize { (value[0] >> BUCKETS_SHIFT) as usize } /// Returns whether the cache contains `value`. fn present(&self, value: &[u8]) -> bool { assert_eq!(value.len(), HASH_BYTES_TRUNCATED); // First search in our restored list. It's sorted so we can // use binary search. if let Some(list) = self.list.get() { if let Ok(i) = list.binary_search_by(|e| e.value[..].cmp(value)) { list[i].metadata.accessed.store(true, Ordering::Relaxed); self.hits.fetch_add(1, Ordering::Relaxed); return true; } } // Fallback to searching the buckets. let i = Self::bucket(value); let entries = self.buckets[i].read().unwrap(); if let Some(metadata) = entries.get(value) { metadata.accessed.store(true, Ordering::Relaxed); self.hits.fetch_add(1, Ordering::Relaxed); true } else { self.misses.fetch_add(1, Ordering::Relaxed); false } } /// Inserts a verified signature into the cache. fn insert(&self, value: [u8; HASH_BYTES_TRUNCATED]) { let i = Self::bucket(&value); let mut entries = self.buckets[i].write().unwrap(); match entries.entry(value) { btree_map::Entry::Vacant(e) => { // The entry is new. Note it. self.insertions.fetch_add(1, Ordering::Relaxed); // Add the entry. e.insert(Metadata::new(true)); } btree_map::Entry::Occupied(_e) => { // Nothing to do. } } } /// Returns the number of cache hits. pub fn cache_hits() -> usize { SIGNATURE_VERIFICATION_CACHE.hits.load(Ordering::Relaxed) } /// Returns the number of cache misses. pub fn cache_misses() -> usize { SIGNATURE_VERIFICATION_CACHE.misses.load(Ordering::Relaxed) } /// Returns the number of cache insertions. /// /// This returns the number of times an entry was added to the /// cache since the program started or the last time /// [`SignatureVerificationCache::clear_insertions`] was called. /// /// This does not include entries added via /// [`SignatureVerificationCache::restore`]. pub fn insertions() -> usize { SIGNATURE_VERIFICATION_CACHE.insertions.load(Ordering::Relaxed) } /// Resets the insertions counter. pub fn clear_insertions() { SIGNATURE_VERIFICATION_CACHE.insertions.store(0, Ordering::Relaxed); } /// Restores the signature verification cache. /// /// This merges the entries into the existing signature cache. /// /// The values are the values as returned by [`Entry::value`]. /// /// The iterator is `Send`, `Sync` and `'static`, because this /// function may spawn a thread to avoid blocking the main thread. /// /// When the restore is complete, `finished` is called. pub fn restore<'a, F>( entries: impl Iterator<Item=Vec<u8>> + Send + Sync + 'static, finished: F) where F: FnOnce() + Send + Sync + 'static { tracer!(TRACE, "SignatureVerificationCache::restore"); // Sanity check the constants here: this function is run O(1) // times. assert_eq!(HASH_ALGO.context().expect("have SHA-512") .for_digest().digest_size(), HASH_BYTES_UNTRUNCATED); assert!(HASH_BYTES_TRUNCATED <= HASH_BYTES_UNTRUNCATED); // Must fit in a byte. assert!(BUCKETS_BITS <= 8); // Consistency check. assert_eq!(BUCKETS, 1 << BUCKETS_BITS); std::thread::spawn(move || { let mut items: Vec<Entry> = Vec::with_capacity(32 * 1024); let mut bad = 0; let mut count = 0; for entry in entries { count += 1; if entry.len() != VALUE_BYTES { bad += 1; continue; } let mut value = VALUE_NULL; value.copy_from_slice(&entry[..VALUE_BYTES]); items.push(Entry { value, metadata: Metadata::new(false), }); } if bad > 0 { t!("Warning: {} of {} cache entries could not be read", bad, count); } t!("Restored {} entries", count); SIGNATURE_VERIFICATION_CACHE.preloads .fetch_add(items.len(), Ordering::Relaxed); items.sort(); // If this is the first restore, then we can store the // signatures in the list. if let Err(items) = SIGNATURE_VERIFICATION_CACHE.list.set(items) { // Hmm, another restore. This is unusual, but okay. // We add the signatures to the buckets, as we can't // change the list: it is behind a OnceLock. let mut bucket_i = 0; let mut bucket = SIGNATURE_VERIFICATION_CACHE .buckets[bucket_i].write().unwrap(); for item in items.into_iter() { let i = Self::bucket(&item.value); if i != bucket_i { // Items should be sorted so we should move // from one bucket to the next. assert!(i > bucket_i); bucket = SIGNATURE_VERIFICATION_CACHE .buckets[i].write().unwrap(); bucket_i = i; } bucket.insert(item.value, item.metadata); } } finished(); }); } /// Dumps the contents of the cache. /// /// This clones the cache to avoid holding locks too long. /// /// The values returned by [`Entry::value`] may be written to a /// file, and restored using /// [`SignatureVerificationCache::restore`]. /// /// Before saving them, you may want to check if there were any /// insertions using [`SignatureVerificationCache::insertions`]. /// /// Also, you may want to prune the entries to avoid having the /// cache grow without bound. pub fn dump<'a>() -> impl IntoIterator<Item=Entry> { tracer!(TRACE, "SignatureVerificationCache::dump"); if TRACE { let preloads = SIGNATURE_VERIFICATION_CACHE .preloads.load(Ordering::Relaxed); let insertions = SIGNATURE_VERIFICATION_CACHE .insertions.load(Ordering::Relaxed); t!("{} entries: {} restored, {} inserted", preloads + insertions, preloads, insertions); let hits = SIGNATURE_VERIFICATION_CACHE .hits.load(Ordering::Relaxed); let misses = SIGNATURE_VERIFICATION_CACHE .misses.load(Ordering::Relaxed); let lookups = hits + misses; if lookups > 0 { t!("{} cache lookups, {} hits ({}%), {} misses ({}%)", lookups, hits, (100 * hits) / lookups, misses, (100 * misses) / lookups); } else { t!("0 cache lookups"); } } DumpIter { bucket: 0, iter: None, list: SIGNATURE_VERIFICATION_CACHE.list.get() .map(|list| list.clone()) .unwrap_or(Vec::new()), } } } /// Iterates over all entries in the cache. /// /// Note: to reduce lock contention, this may return individual entries /// added after it was instantiated. struct DumpIter { iter: Option<std::vec::IntoIter<Entry>>, // The next bucket to dump. Once we get to `BUCKETS`, we dump // `list`. bucket: usize, // If we verify an entry before the cache is restored, the entry // could end in both SignatureVerificationCache.list and a bucket. // Avoid dumping an entry twice. list: Vec<Entry>, } impl Iterator for DumpIter { type Item = Entry; fn next(&mut self) -> Option<Self::Item> { tracer!(TRACE, "DumpIter::next"); loop { if let Some(ref mut iter) = self.iter { if let Some(item) = iter.next() { return Some(item); } } if self.bucket == BUCKETS { if self.list.is_empty() { return None; } let list = std::mem::take(&mut self.list); t!("Dumping {} restored entries", list.len()); self.iter = Some(list.into_iter()); } else { let bucket = &SIGNATURE_VERIFICATION_CACHE.buckets[self.bucket]; self.bucket += 1; let bucket = bucket.read().unwrap(); t!("Dumping {} entries from bucket {}", bucket.len(), self.bucket - 1); self.iter = Some( bucket.iter() .filter_map(|(v, m)| { // If the entry is also in list, then we // don't want to return it twice. if let Ok(_) = self.list.binary_search_by(|e| { e.value[..].cmp(v) }) { // This is a dupe. Skip it. None } else { Some(Entry { value: v.clone(), metadata: m.clone(), }) } }) .collect::<Vec<_>>() .into_iter()) } } } } #[cfg(test)] mod test { use super::*; #[test] fn bucket() { // Assert that all the buckets have the same number of items. let mut bucket = 0; let mut bucket_count = vec![0; BUCKETS]; for i in 0..=u8::MAX { let mut value = VALUE_NULL; value[0] = i; let b = SignatureVerificationCache::bucket(&value); if b != bucket { // Different bucket. Since we are using the most // significant bits, it must be the next bucket. assert_eq!(b, bucket + 1); bucket = bucket + 1; } bucket_count[b] += 1; } for (i, c) in bucket_count.iter().enumerate() { eprintln!("{}: {}", i, c); } assert!(bucket_count.iter().all(|c| *c == bucket_count[0])); assert_eq!(bucket_count.iter().map(|c| *c as usize).sum::<usize>(), u8::MAX as usize + 1); } } ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/src/packet/signature/subpacket.rs���������������������������������������������0000644�0000000�0000000�00001223225�10461020230�0021334�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������//! Signature subpackets. //! //! OpenPGP signature packets include a set of key-value attributes //! called subpackets. These subpackets are used to indicate when a //! signature was created, who created the signature, user & //! implementation preferences, etc. The full details are in [Section //! 5.2.3.7 of RFC 9580]. //! //! [Section 5.2.3.7 of RFC 9580]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.7 //! //! The standard assigns each subpacket a numeric id, and describes //! the format of its value. One subpacket is called Notation Data //! and is intended as a generic key-value store. The combined size //! of the subpackets (including notation data) is limited to 64 KB. //! //! Subpackets and notations can be marked as critical. If an OpenPGP //! implementation processes a packet that includes critical //! subpackets or notations that it does not understand, it is //! required to abort processing. This allows for forwards compatible //! changes by indicating whether it is safe to ignore an unknown //! subpacket or notation. //! //! A number of methods are defined on [`Signature`] for working with //! subpackets. //! //! [`Signature`]: super::super::Signature //! //! # Examples //! //! Print any Issuer Fingerprint subpackets: //! //! ```rust //! # use sequoia_openpgp as openpgp; //! # use openpgp::Result; //! # use openpgp::Packet; //! # use openpgp::parse::{Parse, PacketParserResult, PacketParser}; //! # //! # f(include_bytes!("../../../tests/data/messages/signed.gpg")); //! # //! # fn f(message_data: &[u8]) -> Result<()> { //! let mut ppr = PacketParser::from_bytes(message_data)?; //! while let PacketParserResult::Some(mut pp) = ppr { //! if let Packet::Signature(ref sig) = pp.packet { //! for fp in sig.issuer_fingerprints() { //! eprintln!("Signature allegedly issued by: {}", fp.to_string()); //! } //! } //! //! // Get the next packet. //! ppr = pp.recurse()?.1; //! } //! # Ok(()) //! # } //! ``` use std::cmp::Ordering; use std::convert::{TryInto, TryFrom}; use std::hash::{Hash, Hasher}; use std::ops::{Deref, DerefMut}; use std::fmt; use std::cmp; use std::sync::atomic; use std::time; #[cfg(test)] use quickcheck::{Arbitrary, Gen}; #[cfg(test)] use crate::packet::signature::ArbitraryBounded; use crate::{ Error, Result, packet::header::BodyLength, packet::Signature, packet::signature::{self, Signature4}, packet::key, packet::Key, Fingerprint, KeyID, serialize::MarshalInto, }; use crate::types::{ AEADAlgorithm, CompressionAlgorithm, Duration, Features, HashAlgorithm, KeyFlags, KeyServerPreferences, PublicKeyAlgorithm, ReasonForRevocation, RevocationKey, SymmetricAlgorithm, Timestamp, }; /// The default amount of tolerance to use when comparing /// some timestamps. /// /// Used by `Subpacket::signature_alive`. /// /// When determining whether a timestamp generated on another /// machine is valid *now*, we need to account for clock skew. /// (Note: you don't normally need to consider clock skew when /// evaluating a signature's validity at some time in the past.) /// /// We tolerate half an hour of skew based on the following /// anecdote: In 2019, a developer using Sequoia in a Windows VM /// running inside Virtual Box on Mac OS X reported that he /// typically observed a few minutes of clock skew and /// occasionally saw over 20 minutes of clock skew. /// /// Note: when new messages override older messages, and their /// signatures are evaluated at some arbitrary point in time, an /// application may not see a consistent state if it uses a /// tolerance. Consider an application that has two messages and /// wants to get the current message at time te: /// /// - t0: message 0 /// - te: "get current message" /// - t1: message 1 /// /// If te is close to t1, then t1 may be considered valid, which /// is probably not what you want. pub const CLOCK_SKEW_TOLERANCE: time::Duration = time::Duration::new(30 * 60, 0); /// The subpacket types. /// /// The `SubpacketTag` enum holds a [`Subpacket`]'s identifier, the /// so-called tag. #[non_exhaustive] #[derive(Debug)] #[derive(PartialEq, Eq, PartialOrd, Ord, Hash)] #[derive(Clone, Copy)] pub enum SubpacketTag { /// The time the signature was made. /// /// See [Section 5.2.3.11 of RFC 9580] for details. /// /// [Section 5.2.3.11 of RFC 9580]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.11 SignatureCreationTime, /// The validity period of the signature. /// /// The validity is relative to the time stored in the signature's /// Signature Creation Time subpacket. /// /// See [Section 5.2.3.18 of RFC 9580] for details. /// /// [Section 5.2.3.18 of RFC 9580]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.18 SignatureExpirationTime, /// Whether a signature should be published. /// /// See [Section 5.2.3.19 of RFC 9580] for details. /// /// [Section 5.2.3.19 of RFC 9580]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.19 ExportableCertification, /// Signer asserts that the key is not only valid but also trustworthy at /// the specified level. /// /// See [Section 5.2.3.21 of RFC 9580] for details. /// /// [Section 5.2.3.21 of RFC 9580]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.21 TrustSignature, /// Used in conjunction with Trust Signature packets (of level > 0) to /// limit the scope of trust that is extended. /// /// See [Section 5.2.3.22 of RFC 9580] for details. /// /// [Section 5.2.3.22 of RFC 9580]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.22 RegularExpression, /// Whether a signature can later be revoked. /// /// See [Section 5.2.3.20 of RFC 9580] for details. /// /// [Section 5.2.3.20 of RFC 9580]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.20 Revocable, /// The validity period of the key. /// /// The validity period is relative to the key's (not the signature's) creation time. /// /// See [Section 5.2.3.13 of RFC 9580] for details. /// /// [Section 5.2.3.13 of RFC 9580]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.13 KeyExpirationTime, /// Deprecated PlaceholderForBackwardCompatibility, /// The Symmetric algorithms that the certificate holder prefers. /// /// See [Section 5.2.3.14 of RFC 9580] for details. /// /// [Section 5.2.3.14 of RFC 9580]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.14 PreferredSymmetricAlgorithms, /// Authorizes the specified key to issue revocation signatures for this /// certificate. /// /// See [Section 5.2.3.23 of RFC 9580] for details. /// /// [Section 5.2.3.23 of RFC 9580]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.23 RevocationKey, /// The OpenPGP Key ID of the key issuing the signature. /// /// See [Section 5.2.3.12 of RFC 9580] for details. /// /// [Section 5.2.3.12 of RFC 9580]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.12 Issuer, /// A "notation" on the signature. /// /// See [Section 5.2.3.24 of RFC 9580] for details. /// /// [Section 5.2.3.24 of RFC 9580]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.24 NotationData, /// The Hash algorithms that the certificate holder prefers. /// /// See [Section 5.2.3.16 of RFC 9580] for details. /// /// [Section 5.2.3.16 of RFC 9580]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.16 PreferredHashAlgorithms, /// The compression algorithms that the certificate holder prefers. /// /// See [Section 5.2.3.17 of RFC 9580] for details. /// /// [Section 5.2.3.17 of RFC 9580]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.17 PreferredCompressionAlgorithms, /// A list of flags that indicate preferences that the certificate /// holder has about how the key is handled by a key server. /// /// See [Section 5.2.3.25 of RFC 9580] for details. /// /// [Section 5.2.3.25 of RFC 9580]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.25 KeyServerPreferences, /// The URI of a key server where the certificate holder keeps /// their certificate up to date. /// /// See [Section 5.2.3.26 of RFC 9580] for details. /// /// [Section 5.2.3.26 of RFC 9580]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.26 PreferredKeyServer, /// A flag in a User ID's self-signature that states whether this /// User ID is the primary User ID for this certificate. /// /// See [Section 5.2.3.27 of RFC 9580] for details. /// /// [Section 5.2.3.27 of RFC 9580]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.27 PrimaryUserID, /// The URI of a document that describes the policy under which /// the signature was issued. /// /// See [Section 5.2.3.28 of RFC 9580] for details. /// /// [Section 5.2.3.28 of RFC 9580]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.28 PolicyURI, /// A list of flags that hold information about a key. /// /// See [Section 5.2.3.29 of RFC 9580] for details. /// /// [Section 5.2.3.29 of RFC 9580]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.29 KeyFlags, /// The User ID that is responsible for the signature. /// /// See [Section 5.2.3.30 of RFC 9580] for details. /// /// [Section 5.2.3.30 of RFC 9580]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.30 SignersUserID, /// The reason for a revocation, used in key revocations and /// certification revocation signatures. /// /// See [Section 5.2.3.31 of RFC 9580] for details. /// /// [Section 5.2.3.31 of RFC 9580]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.31 ReasonForRevocation, /// The OpenPGP features a user's implementation supports. /// /// See [Section 5.2.3.32 of RFC 9580] for details. /// /// [Section 5.2.3.32 of RFC 9580]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.32 Features, /// A signature to which this signature refers. /// /// See [Section 5.2.3.33 of RFC 9580] for details. /// /// [Section 5.2.3.33 of RFC 9580]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.33 SignatureTarget, /// A complete Signature packet body. /// /// This is used to store a backsig in a subkey binding signature. /// /// See [Section 5.2.3.34 of RFC 9580] for details. /// /// [Section 5.2.3.34 of RFC 9580]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.34 EmbeddedSignature, /// The Fingerprint of the key that issued the signature. /// /// See [Section 5.2.3.35 of RFC 9580] for details. /// /// [Section 5.2.3.35 of RFC 9580]: https://www.rfc-editor.org/rfc/rfc9580.html#name-issuer-fingerprint IssuerFingerprint, /// Reserved (was: AEAD algorithms that the certificate holder prefers). #[deprecated(note = "Use PreferredAEADCiphersuites instead")] PreferredAEADAlgorithms, /// Who the signed message was intended for. /// /// See [Section 5.2.3.36 of RFC 9580] for details. /// /// [Section 5.2.3.36 of RFC 9580]: https://www.rfc-editor.org/rfc/rfc9580.html#name-intended-recipient-fingerpr IntendedRecipient, /// The Approved Certifications subpacket (experimental). /// /// Allows the certificate holder to attest to third party /// certifications, allowing them to be distributed with the /// certificate. This can be used to address certificate flooding /// concerns. /// /// See [Section 2.2 of draft-dkg-openpgp-1pa3pc-02] for details. /// /// [Section 2.2 of draft-dkg-openpgp-1pa3pc-02]: https://www.ietf.org/archive/id/draft-dkg-openpgp-1pa3pc-02.html#approved-certifications-subpacket ApprovedCertifications, /// The AEAD Ciphersuites that the certificate holder prefers. /// /// A series of paired algorithm identifiers indicating how the /// keyholder prefers to receive version 2 Symmetrically Encrypted /// Integrity Protected Data. Each pair of octets indicates a /// combination of a symmetric cipher and an AEAD mode that the /// key holder prefers to use. /// /// It is assumed that only the combinations of algorithms listed /// are supported by the recipient's software, with the exception /// of the mandatory-to-implement combination of AES-128 and OCB. /// If AES-128 and OCB are not found in the subpacket, it is /// implicitly listed at the end. /// /// See [Section 5.2.3.15 of RFC 9580] for details. /// /// [Section 5.2.3.15 of RFC 9580]: https://www.rfc-editor.org/rfc/rfc9580.html#name-preferred-aead-ciphersuites PreferredAEADCiphersuites, /// Reserved subpacket tag. Reserved(u8), /// Private subpacket tag. Private(u8), /// Unknown subpacket tag. Unknown(u8), // If you add a new variant, make sure to add it to the // conversions and to SUBPACKET_TAG_VARIANTS. } assert_send_and_sync!(SubpacketTag); impl fmt::Display for SubpacketTag { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{:?}", self) } } impl From<u8> for SubpacketTag { fn from(u: u8) -> Self { #[allow(deprecated)] match u { 2 => SubpacketTag::SignatureCreationTime, 3 => SubpacketTag::SignatureExpirationTime, 4 => SubpacketTag::ExportableCertification, 5 => SubpacketTag::TrustSignature, 6 => SubpacketTag::RegularExpression, 7 => SubpacketTag::Revocable, 9 => SubpacketTag::KeyExpirationTime, 10 => SubpacketTag::PlaceholderForBackwardCompatibility, 11 => SubpacketTag::PreferredSymmetricAlgorithms, 12 => SubpacketTag::RevocationKey, 16 => SubpacketTag::Issuer, 20 => SubpacketTag::NotationData, 21 => SubpacketTag::PreferredHashAlgorithms, 22 => SubpacketTag::PreferredCompressionAlgorithms, 23 => SubpacketTag::KeyServerPreferences, 24 => SubpacketTag::PreferredKeyServer, 25 => SubpacketTag::PrimaryUserID, 26 => SubpacketTag::PolicyURI, 27 => SubpacketTag::KeyFlags, 28 => SubpacketTag::SignersUserID, 29 => SubpacketTag::ReasonForRevocation, 30 => SubpacketTag::Features, 31 => SubpacketTag::SignatureTarget, 32 => SubpacketTag::EmbeddedSignature, 33 => SubpacketTag::IssuerFingerprint, 34 => SubpacketTag::PreferredAEADAlgorithms, 35 => SubpacketTag::IntendedRecipient, 37 => SubpacketTag::ApprovedCertifications, 39 => SubpacketTag::PreferredAEADCiphersuites, 0| 1| 8| 13| 14| 15| 17| 18| 19 | 38 => SubpacketTag::Reserved(u), 100..=110 => SubpacketTag::Private(u), _ => SubpacketTag::Unknown(u), } } } impl From<SubpacketTag> for u8 { fn from(t: SubpacketTag) -> Self { #[allow(deprecated)] match t { SubpacketTag::SignatureCreationTime => 2, SubpacketTag::SignatureExpirationTime => 3, SubpacketTag::ExportableCertification => 4, SubpacketTag::TrustSignature => 5, SubpacketTag::RegularExpression => 6, SubpacketTag::Revocable => 7, SubpacketTag::KeyExpirationTime => 9, SubpacketTag::PlaceholderForBackwardCompatibility => 10, SubpacketTag::PreferredSymmetricAlgorithms => 11, SubpacketTag::RevocationKey => 12, SubpacketTag::Issuer => 16, SubpacketTag::NotationData => 20, SubpacketTag::PreferredHashAlgorithms => 21, SubpacketTag::PreferredCompressionAlgorithms => 22, SubpacketTag::KeyServerPreferences => 23, SubpacketTag::PreferredKeyServer => 24, SubpacketTag::PrimaryUserID => 25, SubpacketTag::PolicyURI => 26, SubpacketTag::KeyFlags => 27, SubpacketTag::SignersUserID => 28, SubpacketTag::ReasonForRevocation => 29, SubpacketTag::Features => 30, SubpacketTag::SignatureTarget => 31, SubpacketTag::EmbeddedSignature => 32, SubpacketTag::IssuerFingerprint => 33, SubpacketTag::PreferredAEADAlgorithms => 34, SubpacketTag::IntendedRecipient => 35, SubpacketTag::ApprovedCertifications => 37, SubpacketTag::PreferredAEADCiphersuites => 39, SubpacketTag::Reserved(u) => u, SubpacketTag::Private(u) => u, SubpacketTag::Unknown(u) => u, } } } #[allow(deprecated)] const SUBPACKET_TAG_VARIANTS: [SubpacketTag; 29] = [ SubpacketTag::SignatureCreationTime, SubpacketTag::SignatureExpirationTime, SubpacketTag::ExportableCertification, SubpacketTag::TrustSignature, SubpacketTag::RegularExpression, SubpacketTag::Revocable, SubpacketTag::KeyExpirationTime, SubpacketTag::PlaceholderForBackwardCompatibility, SubpacketTag::PreferredSymmetricAlgorithms, SubpacketTag::RevocationKey, SubpacketTag::Issuer, SubpacketTag::NotationData, SubpacketTag::PreferredHashAlgorithms, SubpacketTag::PreferredCompressionAlgorithms, SubpacketTag::KeyServerPreferences, SubpacketTag::PreferredKeyServer, SubpacketTag::PrimaryUserID, SubpacketTag::PolicyURI, SubpacketTag::KeyFlags, SubpacketTag::SignersUserID, SubpacketTag::ReasonForRevocation, SubpacketTag::Features, SubpacketTag::SignatureTarget, SubpacketTag::EmbeddedSignature, SubpacketTag::IssuerFingerprint, SubpacketTag::PreferredAEADAlgorithms, SubpacketTag::IntendedRecipient, SubpacketTag::ApprovedCertifications, SubpacketTag::PreferredAEADCiphersuites, ]; impl SubpacketTag { /// Returns an iterator over all valid variants. /// /// Returns an iterator over all known variants. This does not /// include the [`SubpacketTag::Reserved`], /// [`SubpacketTag::Private`], or [`SubpacketTag::Unknown`] /// variants. pub fn variants() -> impl Iterator<Item=Self> { SUBPACKET_TAG_VARIANTS.iter().cloned() } } #[cfg(test)] impl Arbitrary for SubpacketTag { fn arbitrary(g: &mut Gen) -> Self { u8::arbitrary(g).into() } } #[cfg(test)] mod tests { use super::*; quickcheck! { fn roundtrip(tag: SubpacketTag) -> bool { let val: u8 = tag.into(); tag == SubpacketTag::from(val) } } quickcheck! { fn parse(tag: SubpacketTag) -> bool { match tag { SubpacketTag::Reserved(u) => (u == 0 || u == 1 || u == 8 || u == 13 || u == 14 || u == 15 || u == 17 || u == 18 || u == 19 || u == 38), SubpacketTag::Private(u) => (100..=110).contains(&u), SubpacketTag::Unknown(u) => (u > 33 && u < 100) || u > 110, _ => true } } } #[test] fn subpacket_tag_variants() { use std::collections::HashSet; use std::iter::FromIterator; // SUBPACKET_TAG_VARIANTS is a list. Derive it in a different way // to double-check that nothing is missing. let derived_variants = (0..=u8::MAX) .map(SubpacketTag::from) .filter(|t| { match t { SubpacketTag::Reserved(_) => false, SubpacketTag::Private(_) => false, SubpacketTag::Unknown(_) => false, _ => true, } }) .collect::<HashSet<_>>(); let known_variants = HashSet::from_iter(SUBPACKET_TAG_VARIANTS.iter().cloned()); let missing = known_variants .symmetric_difference(&derived_variants) .collect::<Vec<_>>(); assert!(missing.is_empty(), "{:?}", missing); } } /// Subpacket area. /// /// A version 4 Signature contains two areas that can stored /// [signature subpackets]: a so-called hashed subpacket area, and a /// so-called unhashed subpacket area. The hashed subpacket area is /// protected by the signature; the unhashed area is not. This makes /// the unhashed subpacket area only appropriate for /// self-authenticating data, like the [`Issuer`] subpacket. The /// [`SubpacketAreas`] data structure understands these nuances and /// routes lookups appropriately. As such, it is usually better to /// work with subpackets using that interface. /// /// [signature subpackets]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.7 /// [`Issuer`]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.12 /// /// # Examples /// /// ``` /// # use sequoia_openpgp as openpgp; /// # use openpgp::cert::prelude::*; /// # use openpgp::packet::prelude::*; /// # use openpgp::policy::StandardPolicy; /// # use openpgp::types::SignatureType; /// # /// # fn main() -> openpgp::Result<()> { /// # let p = &StandardPolicy::new(); /// # /// # let (cert, _) = CertBuilder::new().generate()?; /// # /// # let key : &Key<_, _> = cert.primary_key().key(); /// # let mut signer = key.clone().parts_into_secret()?.into_keypair()?; /// # /// # let msg = b"Hello, world!"; /// # let mut sig = SignatureBuilder::new(SignatureType::Binary) /// # .sign_message(&mut signer, msg)?; /// # /// # // Verify it. /// # sig.verify_message(signer.public(), msg)?; /// fn sig_stats(sig: &Signature) { /// eprintln!("Hashed subpacket area has {} subpackets", /// sig.hashed_area().iter().count()); /// eprintln!("Unhashed subpacket area has {} subpackets", /// sig.unhashed_area().iter().count()); /// } /// # sig_stats(&sig); /// # Ok(()) /// # } /// ``` #[derive(Clone)] pub struct SubpacketArea { /// The subpackets. packets: Vec<Subpacket>, // The subpacket area, but parsed so that the vector is indexed by // the subpacket tag, and the value is the index of the *last* // occurrence of that subpacket in the subpacket area. // // Since self-referential structs are a no-no, we use an index // to reference the content in the area. // // Note: A subpacket area is at most 2**16-1 bytes large. A // subpacket is at least two bytes long (one for the length, and // one for the subpacket type). Thus, a subpacket area can't have // more than 2**15 subpackets. This means that we need at most 15 // bits. Thus, instead of using an `Option<u16>`, which requires // 32 bits, we use an unused value to mean not present. parsed: std::sync::OnceLock<Vec<u16>>, } assert_send_and_sync!(SubpacketArea); // The value for an entry of `SubpacketArea::parsed` when the // subpacket is not present. // // We don't use an `Option<u16>`, as that would require 32 bits, and // this value is not used. See the comment for // `SubpacketArea::parsed`. const SUBPACKET_NOT_PRESENT: u16 = u16::MAX; #[cfg(test)] impl ArbitraryBounded for SubpacketArea { fn arbitrary_bounded(g: &mut Gen, depth: usize) -> Self { use crate::arbitrary_helper::gen_arbitrary_from_range; let mut a = Self::default(); for _ in 0..gen_arbitrary_from_range(0..32, g) { let _ = a.add(ArbitraryBounded::arbitrary_bounded(g, depth)); } a } } #[cfg(test)] impl_arbitrary_with_bound!(SubpacketArea); impl Default for SubpacketArea { fn default() -> Self { Self::new(Default::default()).unwrap() } } impl PartialEq for SubpacketArea { fn eq(&self, other: &SubpacketArea) -> bool { self.cmp(other) == Ordering::Equal } } impl Eq for SubpacketArea {} impl PartialOrd for SubpacketArea { fn partial_cmp(&self, other: &SubpacketArea) -> Option<Ordering> { Some(self.cmp(other)) } } impl Ord for SubpacketArea { fn cmp(&self, other: &SubpacketArea) -> Ordering { self.packets.cmp(&other.packets) } } impl Hash for SubpacketArea { fn hash<H: Hasher>(&self, state: &mut H) { // We hash only the data, the cache is a hashmap and does not // implement hash. self.packets.hash(state); } } impl fmt::Debug for SubpacketArea { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.debug_list() .entries(self.iter()) .finish() } } impl<'a> IntoIterator for &'a SubpacketArea { type Item = &'a Subpacket; type IntoIter = std::slice::Iter<'a, Subpacket>; fn into_iter(self) -> Self::IntoIter { self.packets.iter() } } impl SubpacketArea { /// The maximum size of a subpacket area. pub const MAX_SIZE: usize = (1 << 16) - 1; /// Returns a new subpacket area containing the given `packets`. pub fn new(packets: Vec<Subpacket>) -> Result<SubpacketArea> { let area = SubpacketArea { packets, parsed: std::sync::OnceLock::new(), }; Ok(area) } /// Initialize the cache mapping subpacket tags to positions in /// the subpacket area. /// /// If the cache is already initialized, this is a NOP. /// /// Returns the locked cache. fn cache_init(&self) -> &Vec<u16> { self.parsed.get_or_init(|| { // The largest defined subpacket in the crypto refresh is // 39. if let Some(max) = self.packets.iter().map(|sp| u8::from(sp.tag())).max() { let max = max as usize; let mut index = vec![ SUBPACKET_NOT_PRESENT; max + 1 ]; for (i, sp) in self.packets.iter().enumerate() { index[u8::from(sp.tag()) as usize] = i as u16; } index } else { Vec::new() } }) } /// Invalidates the cache. fn cache_invalidate(&mut self) { self.parsed = std::sync::OnceLock::new(); } /// Iterates over the subpackets. /// /// # Examples /// /// Print the number of different types of subpackets in a /// Signature's hashed subpacket area: /// /// ``` /// # use sequoia_openpgp as openpgp; /// # use openpgp::cert::prelude::*; /// # use openpgp::packet::prelude::*; /// # use openpgp::types::SignatureType; /// # /// # fn main() -> openpgp::Result<()> { /// # let (cert, _) = CertBuilder::new().generate()?; /// # /// # let key : &Key<_, _> = cert.primary_key().key(); /// # let mut signer = key.clone().parts_into_secret()?.into_keypair()?; /// # /// # let msg = b"Hello, world!"; /// # let mut sig = SignatureBuilder::new(SignatureType::Binary) /// # .sign_message(&mut signer, msg)?; /// # /// # // Verify it. /// # sig.verify_message(signer.public(), msg)?; /// # /// let mut tags: Vec<_> = sig.hashed_area().iter().map(|sb| { /// sb.tag() /// }).collect(); /// tags.sort(); /// tags.dedup(); /// /// eprintln!("The hashed area contains {} types of subpackets", /// tags.len()); /// # Ok(()) /// # } /// ``` pub fn iter(&self) -> impl Iterator<Item = &Subpacket> + Send + Sync { self.packets.iter() } pub(crate) fn iter_mut(&mut self) -> impl Iterator<Item = &mut Subpacket> + Send + Sync { self.packets.iter_mut() } /// Returns a reference to the *last* instance of the specified /// subpacket, if any. /// /// A given subpacket may occur multiple times. For some, like /// the [`Notation Data`] subpacket, this is reasonable. For /// others, like the [`Signature Creation Time`] subpacket, this /// results in an ambiguity. [Section 5.2.3.9 of RFC 9580] says: /// /// > a signature may contain multiple copies of a preference or /// > multiple expiration times. In most cases, an implementation /// > SHOULD use the last subpacket in the signature, but MAY use /// > any conflict resolution scheme that makes more sense. /// /// [`Notation Data`]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.24 /// [`Signature Creation Time`]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.11 /// [Section 5.2.3.9 of RFC 9580]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.9 /// /// This function implements the recommended strategy of returning /// the last subpacket. /// /// # Examples /// /// All signatures must have a `Signature Creation Time` subpacket /// in the hashed subpacket area: /// /// ``` /// use sequoia_openpgp as openpgp; /// # use openpgp::cert::prelude::*; /// # use openpgp::packet::prelude::*; /// use openpgp::packet::signature::subpacket::SubpacketTag; /// # use openpgp::types::SignatureType; /// /// # fn main() -> openpgp::Result<()> { /// # let (cert, _) = CertBuilder::new().generate()?; /// # /// # let key : &Key<_, _> = cert.primary_key().key(); /// # let mut signer = key.clone().parts_into_secret()?.into_keypair()?; /// # /// # let msg = b"Hello, world!"; /// # let mut sig = SignatureBuilder::new(SignatureType::Binary) /// # .sign_message(&mut signer, msg)?; /// # /// # // Verify it. /// # sig.verify_message(signer.public(), msg)?; /// # /// if sig.hashed_area().subpacket(SubpacketTag::SignatureCreationTime).is_none() { /// eprintln!("Invalid signature."); /// } /// # Ok(()) /// # } /// ``` pub fn subpacket(&self, tag: SubpacketTag) -> Option<&Subpacket> { match self.cache_init().get(u8::from(tag) as usize) { Some(&SUBPACKET_NOT_PRESENT) => None, Some(&n) => Some(&self.packets[n as usize]), _ => None, } } /// Returns a mutable reference to the *last* instance of the /// specified subpacket, if any. /// /// A given subpacket may occur multiple times. For some, like /// the [`Notation Data`] subpacket, this is reasonable. For /// others, like the [`Signature Creation Time`] subpacket, this /// results in an ambiguity. [Section 5.2.3.9 of RFC 9580] says: /// /// > a signature may contain multiple copies of a preference or /// > multiple expiration times. In most cases, an implementation /// > SHOULD use the last subpacket in the signature, but MAY use /// > any conflict resolution scheme that makes more sense. /// /// [`Notation Data`]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.24 /// [`Signature Creation Time`]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.11 /// [Section 5.2.3.9 of RFC 9580]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.9 /// /// This function implements the recommended strategy of returning /// the last subpacket. /// /// # Examples /// /// All signatures must have a `Signature Creation Time` subpacket /// in the hashed subpacket area: /// /// ``` /// use sequoia_openpgp as openpgp; /// # use openpgp::cert::prelude::*; /// # use openpgp::packet::prelude::*; /// use openpgp::packet::signature::subpacket::SubpacketTag; /// # use openpgp::types::SignatureType; /// /// # fn main() -> openpgp::Result<()> { /// # let (cert, _) = CertBuilder::new().generate()?; /// # /// # let key : &Key<_, _> = cert.primary_key().key(); /// # let mut signer = key.clone().parts_into_secret()?.into_keypair()?; /// # /// # let msg = b"Hello, world!"; /// # let mut sig = SignatureBuilder::new(SignatureType::Binary) /// # .sign_message(&mut signer, msg)?; /// # /// # // Verify it. /// # sig.verify_message(signer.public(), msg)?; /// # /// if sig.hashed_area().subpacket(SubpacketTag::SignatureCreationTime).is_none() { /// eprintln!("Invalid signature."); /// } /// # Ok(()) /// # } /// ``` pub fn subpacket_mut(&mut self, tag: SubpacketTag) -> Option<&mut Subpacket> { match self.cache_init().get(u8::from(tag) as usize) { Some(&SUBPACKET_NOT_PRESENT) => None, Some(&n) => Some(&mut self.packets[n as usize]), _ => None, } } /// Returns all instances of the specified subpacket. /// /// For most subpackets, only a single instance of the subpacket /// makes sense. [`SubpacketArea::subpacket`] resolves this /// ambiguity by returning the last instance of the request /// subpacket type. But, for some subpackets, like the [`Notation /// Data`] subpacket, multiple instances of the subpacket are /// reasonable. /// /// [`SubpacketArea::subpacket`]: Self::subpacket() /// [`Notation Data`]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.24 /// /// # Examples /// /// Count the number of `Notation Data` subpackets in the hashed /// subpacket area: /// /// ``` /// use sequoia_openpgp as openpgp; /// # use openpgp::cert::prelude::*; /// # use openpgp::packet::prelude::*; /// use openpgp::packet::signature::subpacket::SubpacketTag; /// # use openpgp::types::SignatureType; /// /// # fn main() -> openpgp::Result<()> { /// # let (cert, _) = CertBuilder::new().generate()?; /// # /// # let key : &Key<_, _> = cert.primary_key().key(); /// # let mut signer = key.clone().parts_into_secret()?.into_keypair()?; /// # /// # let msg = b"Hello, world!"; /// # let mut sig = SignatureBuilder::new(SignatureType::Binary) /// # .sign_message(&mut signer, msg)?; /// # /// # // Verify it. /// # sig.verify_message(signer.public(), msg)?; /// # /// eprintln!("Signature has {} notations.", /// sig.hashed_area().subpackets(SubpacketTag::NotationData).count()); /// # Ok(()) /// # } /// ``` pub fn subpackets(&self, target: SubpacketTag) -> impl Iterator<Item = &Subpacket> + Send + Sync { self.iter().filter(move |sp| sp.tag() == target) } pub(crate) fn subpackets_mut(&mut self, target: SubpacketTag) -> impl Iterator<Item = &mut Subpacket> + Send + Sync { self.iter_mut().filter(move |sp| sp.tag() == target) } /// Adds the given subpacket. /// /// Adds the given subpacket to the subpacket area. If the /// subpacket area already contains subpackets with the same tag, /// they are left in place. If you want to replace them, you /// should instead use the [`SubpacketArea::replace`] method. /// /// [`SubpacketArea::replace`]: Self::replace() /// /// # Errors /// /// Returns `Error::MalformedPacket` if adding the packet makes /// the subpacket area exceed the size limit. /// /// # Examples /// /// Adds an additional `Issuer` subpacket to the unhashed /// subpacket area. (This is useful if the key material is /// associated with multiple certificates, e.g., a v4 and a v5 /// certificate.) Because the subpacket is added to the unhashed /// area, the signature remains valid. /// /// ``` /// use sequoia_openpgp as openpgp; /// # use openpgp::cert::prelude::*; /// use openpgp::KeyID; /// # use openpgp::packet::prelude::*; /// use openpgp::packet::signature::subpacket::{ /// Subpacket, /// SubpacketTag, /// SubpacketValue, /// }; /// # use openpgp::types::SignatureType; /// /// # fn main() -> openpgp::Result<()> { /// # let (cert, _) = CertBuilder::new().generate()?; /// # /// # let key : &Key<_, _> = cert.primary_key().key(); /// # let mut signer = key.clone().parts_into_secret()?.into_keypair()?; /// # /// # let msg = b"Hello, world!"; /// # let mut sig = SignatureBuilder::new(SignatureType::Binary) /// # .sign_message(&mut signer, msg)?; /// # /// # // Verify it. /// # sig.verify_message(signer.public(), msg)?; /// # /// # assert_eq!(sig /// # .hashed_area() /// # .iter() /// # .filter(|sp| sp.tag() == SubpacketTag::Issuer) /// # .count(), /// # 1); /// let mut sig: Signature = sig; /// sig.unhashed_area_mut().add( /// Subpacket::new( /// SubpacketValue::Issuer(KeyID::from_hex("AAAA BBBB CCCC DDDD")?), /// false)?); /// /// sig.verify_message(signer.public(), msg)?; /// # assert_eq!(sig /// # .unhashed_area() /// # .iter() /// # .filter(|sp| sp.tag() == SubpacketTag::Issuer) /// # .count(), /// # 1); /// # Ok(()) /// # } /// ``` pub fn add(&mut self, packet: Subpacket) -> Result<()> { self.add_internal(packet, false) } /// Adds `packet`, setting its authenticated flag to `authenticated`. pub(super) fn add_internal(&mut self, packet: Subpacket, authenticated: bool) -> Result<()> { if self.serialized_len() + packet.serialized_len() > ::std::u16::MAX as usize { return Err(Error::MalformedPacket( "Subpacket area exceeds maximum size".into()).into()); } self.cache_invalidate(); packet.set_authenticated(authenticated); self.packets.push(packet); Ok(()) } /// Adds the given subpacket, replacing all other subpackets with /// the same tag. /// /// Adds the given subpacket to the subpacket area. If the /// subpacket area already contains subpackets with the same tag, /// they are first removed. If you want to preserve them, you /// should instead use the [`SubpacketArea::add`] method. /// /// [`SubpacketArea::add`]: Self::add() /// /// # Errors /// /// Returns `Error::MalformedPacket` if adding the packet makes /// the subpacket area exceed the size limit. /// /// # Examples /// /// Assuming we have a signature with an additional `Issuer` /// subpacket in the unhashed area (see the example for /// [`SubpacketArea::add`]), this replaces the `Issuer` subpacket /// in the unhashed area. Because the unhashed area is not /// protected by the signature, the signature remains valid: /// /// ``` /// use sequoia_openpgp as openpgp; /// # use openpgp::cert::prelude::*; /// use openpgp::KeyID; /// # use openpgp::packet::prelude::*; /// use openpgp::packet::signature::subpacket::{ /// Subpacket, /// SubpacketTag, /// SubpacketValue, /// }; /// # use openpgp::types::SignatureType; /// /// # fn main() -> openpgp::Result<()> { /// # let (cert, _) = CertBuilder::new().generate()?; /// # /// # let key : &Key<_, _> = cert.primary_key().key(); /// # let mut signer = key.clone().parts_into_secret()?.into_keypair()?; /// # /// # let msg = b"Hello, world!"; /// # let mut sig = SignatureBuilder::new(SignatureType::Binary) /// # .sign_message(&mut signer, msg)?; /// # /// # // Verify it. /// # sig.verify_message(signer.public(), msg)?; /// # /// # assert_eq!(sig /// # .hashed_area() /// # .iter() /// # .filter(|sp| sp.tag() == SubpacketTag::Issuer) /// # .count(), /// # 1); /// // First, add a subpacket to the unhashed area. /// let mut sig: Signature = sig; /// sig.unhashed_area_mut().add( /// Subpacket::new( /// SubpacketValue::Issuer(KeyID::from_hex("DDDD CCCC BBBB AAAA")?), /// false)?); /// /// // Now, replace it. /// sig.unhashed_area_mut().replace( /// Subpacket::new( /// SubpacketValue::Issuer(KeyID::from_hex("AAAA BBBB CCCC DDDD")?), /// false)?); /// /// sig.verify_message(signer.public(), msg)?; /// # assert_eq!(sig /// # .unhashed_area() /// # .iter() /// # .filter(|sp| sp.tag() == SubpacketTag::Issuer) /// # .count(), /// # 1); /// # Ok(()) /// # } /// ``` pub fn replace(&mut self, packet: Subpacket) -> Result<()> { if self.iter().filter_map(|sp| if sp.tag() != packet.tag() { Some(sp.serialized_len()) } else { None }).sum::<usize>() + packet.serialized_len() > std::u16::MAX as usize { return Err(Error::MalformedPacket( "Subpacket area exceeds maximum size".into()).into()); } self.remove_all(packet.tag()); packet.set_authenticated(false); self.packets.push(packet); Ok(()) } /// Removes all subpackets with the given tag. pub fn remove_all(&mut self, tag: SubpacketTag) { self.cache_invalidate(); self.packets.retain(|sp| sp.tag() != tag); } /// Removes all subpackets. pub fn clear(&mut self) { self.cache_invalidate(); self.packets.clear(); } /// Sorts the subpackets by subpacket tag. /// /// This normalizes the subpacket area, and accelerates lookups in /// implementations that sort the in-core representation and use /// binary search for lookups. /// /// The subpackets are sorted by the numeric value of their tag. /// The sort is stable. So, if there are multiple [`Notation Data`] /// subpackets, for instance, they will remain in the same order. /// /// The [`SignatureBuilder`] sorts the subpacket areas just before /// creating the signature. /// /// [`Notation Data`]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.24 /// [`SignatureBuilder`]: super::SignatureBuilder pub fn sort(&mut self) { self.cache_invalidate(); // slice::sort_by is stable. self.packets.sort_by(|a, b| u8::from(a.tag()).cmp(&b.tag().into())); } } /// Payload of a Notation Data subpacket. /// /// The [`Notation Data`] subpacket provides a mechanism for a /// message's signer to insert nearly arbitrary data into the /// signature. Because notations can be marked as critical, it is /// possible to add security relevant notations, which the receiving /// OpenPGP implementation will respect (in the sense that an /// implementation will reject signatures that include unknown, /// critical notations), even if they don't understand the notations /// themselves. /// /// [`Notation Data`]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.24 /// /// It is possible to control how Sequoia's higher-level functionality /// handles unknown, critical notations using a [`Policy`] object. /// Depending on the degree of control required, it may be sufficient /// to customize a [`StandardPolicy`] object using, for instance, the /// [`StandardPolicy::good_critical_notations`] method. /// /// [`Policy`]: crate::policy::Policy /// [`StandardPolicy`]: crate::policy::StandardPolicy /// [`StandardPolicy::good_critical_notations`]: crate::policy::StandardPolicy::good_critical_notations() /// /// Notation names are human-readable UTF-8 strings. There are two /// namespaces: The user namespace and the IETF namespace. Names in /// the user namespace have the form `name@example.org` and are /// managed by the owner of the domain. Names in the IETF namespace /// may not contain an `@` and are managed by IANA. See [Section /// 5.2.3.24 of RFC 9580] for details. /// /// [Section 5.2.3.24 of RFC 9580]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.24 #[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct NotationData { flags: NotationDataFlags, name: String, value: Vec<u8>, } assert_send_and_sync!(NotationData); impl fmt::Display for NotationData { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{}", self.name)?; let flags = format!("{:?}", self.flags); if ! flags.is_empty() { write!(f, " ({})", flags)?; } if self.flags.human_readable() { write!(f, ": {}", String::from_utf8_lossy(&self.value))?; } else { let hex = crate::fmt::hex::encode(&self.value); write!(f, ": {}", hex)?; } Ok(()) } } impl fmt::Debug for NotationData { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let mut dbg = f.debug_struct("NotationData"); dbg.field("name", &self.name); let flags = format!("{:?}", self.flags); if ! flags.is_empty() { dbg.field("flags", &flags); } if self.flags.human_readable() { match std::str::from_utf8(&self.value) { Ok(s) => { dbg.field("value", &s); }, Err(e) => { let s = format!("({}): {}", e, crate::fmt::hex::encode(&self.value)); dbg.field("value", &s); }, } } else { let hex = crate::fmt::hex::encode(&self.value); dbg.field("value", &hex); } dbg.finish() } } #[cfg(test)] impl Arbitrary for NotationData { fn arbitrary(g: &mut Gen) -> Self { NotationData { flags: Arbitrary::arbitrary(g), name: Arbitrary::arbitrary(g), value: Arbitrary::arbitrary(g), } } } impl NotationData { /// Creates a new Notation Data subpacket payload. pub fn new<N, V, F>(name: N, value: V, flags: F) -> Self where N: AsRef<str>, V: AsRef<[u8]>, F: Into<Option<NotationDataFlags>>, { Self { flags: flags.into().unwrap_or_else(NotationDataFlags::empty), name: name.as_ref().into(), value: value.as_ref().into(), } } /// Returns the flags. pub fn flags(&self) -> &NotationDataFlags { &self.flags } /// Returns the name. pub fn name(&self) -> &str { &self.name } /// Returns the value. pub fn value(&self) -> &[u8] { &self.value } } /// Flags for the Notation Data subpacket. #[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct NotationDataFlags(crate::types::Bitfield); assert_send_and_sync!(NotationDataFlags); #[cfg(test)] impl Arbitrary for NotationDataFlags { fn arbitrary(g: &mut Gen) -> Self { NotationDataFlags(vec![u8::arbitrary(g), u8::arbitrary(g), u8::arbitrary(g), u8::arbitrary(g)].into()) } } impl fmt::Debug for NotationDataFlags { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let mut need_comma = false; if self.human_readable() { f.write_str("human readable")?; need_comma = true; } for i in self.0.iter_set() { match i { NOTATION_DATA_FLAG_HUMAN_READABLE => (), i => { if need_comma { f.write_str(", ")?; } write!(f, "#{}", i)?; need_comma = true; }, } } // Don't mention padding, the bit field always has the same // size. Ok(()) } } const NOTATION_DATA_FLAG_HUMAN_READABLE: usize = 7; impl NotationDataFlags { /// Creates a new instance from `bits`. pub fn new<B: AsRef<[u8]>>(bits: B) -> Result<Self> { if bits.as_ref().len() == 4 { Ok(Self(bits.as_ref().to_vec().into())) } else { Err(Error::InvalidArgument( format!("Need four bytes of flags, got: {:?}", bits.as_ref())) .into()) } } /// Returns an empty key server preference set. pub fn empty() -> Self { Self::new(&[0, 0, 0, 0]).unwrap() } /// Returns a reference to the underlying /// [`Bitfield`](crate::types::Bitfield). pub fn as_bitfield(&self) -> &crate::types::Bitfield { &self.0 } /// Returns whether the specified notation data flag is set. /// /// # Examples /// /// ``` /// use sequoia_openpgp as openpgp; /// use openpgp::packet::signature::subpacket::NotationDataFlags; /// /// # fn main() -> openpgp::Result<()> { /// // Notation Data flags 0 and 2. /// let ndf = NotationDataFlags::new(&[5, 0, 0, 0])?; /// /// assert!(ndf.get(0)); /// assert!(! ndf.get(1)); /// assert!(ndf.get(2)); /// assert!(! ndf.get(3)); /// assert!(! ndf.get(8)); /// assert!(! ndf.get(80)); /// # assert!(! ndf.human_readable()); /// # Ok(()) } /// ``` pub fn get(&self, bit: usize) -> bool { self.0.get(bit) } /// Sets the specified notation data flag. /// /// # Examples /// /// ``` /// use sequoia_openpgp as openpgp; /// use openpgp::packet::signature::subpacket::NotationDataFlags; /// /// # fn main() -> openpgp::Result<()> { /// let ndf = NotationDataFlags::empty().set(0)?.set(2)?; /// /// assert!(ndf.get(0)); /// assert!(! ndf.get(1)); /// assert!(ndf.get(2)); /// assert!(! ndf.get(3)); /// # assert!(! ndf.human_readable()); /// # Ok(()) } /// ``` pub fn set(mut self, bit: usize) -> Result<Self> { assert_eq!(self.0.as_bytes().len(), 4); let byte = bit / 8; if byte < 4 { self.0.set(bit); Ok(self) } else { Err(Error::InvalidArgument( format!("flag index out of bounds: {}", bit)).into()) } } /// Clears the specified notation data flag. /// /// # Examples /// /// ``` /// use sequoia_openpgp as openpgp; /// use openpgp::packet::signature::subpacket::NotationDataFlags; /// /// # fn main() -> openpgp::Result<()> { /// let ndf = NotationDataFlags::empty().set(0)?.set(2)?.clear(2)?; /// /// assert!(ndf.get(0)); /// assert!(! ndf.get(1)); /// assert!(! ndf.get(2)); /// assert!(! ndf.get(3)); /// # assert!(! ndf.human_readable()); /// # Ok(()) } /// ``` pub fn clear(mut self, bit: usize) -> Result<Self> { assert_eq!(self.0.as_bytes().len(), 4); let byte = bit / 8; if byte < 4 { self.0.clear(bit); Ok(self) } else { Err(Error::InvalidArgument( format!("flag index out of bounds: {}", bit)).into()) } } /// Returns whether the value is human-readable. pub fn human_readable(&self) -> bool { self.get(NOTATION_DATA_FLAG_HUMAN_READABLE) } /// Asserts that the value is human-readable. pub fn set_human_readable(self) -> Self { self.set(NOTATION_DATA_FLAG_HUMAN_READABLE).unwrap() } /// Clear the assertion that the value is human-readable. pub fn clear_human_readable(self) -> Self { self.clear(NOTATION_DATA_FLAG_HUMAN_READABLE).unwrap() } } /// Holds an arbitrary, well-structured subpacket. /// /// The `SubpacketValue` enum holds a [`Subpacket`]'s value. The /// values are well-structured in the sense that they have been parsed /// into Sequoia's native data types rather than just holding the raw /// byte vector. For instance, the [`Issuer`] variant holds a /// [`KeyID`]. /// /// [`Issuer`]: SubpacketValue::Issuer /// [`KeyID`]: super::super::super::KeyID #[non_exhaustive] #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone)] pub enum SubpacketValue { /// An unknown subpacket. Unknown { /// The unknown subpacket's tag. tag: SubpacketTag, /// The unknown subpacket's uninterpreted body. body: Vec<u8> }, /// The time the signature was made. /// /// See [Section 5.2.3.11 of RFC 9580] for details. /// /// [Section 5.2.3.11 of RFC 9580]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.11 SignatureCreationTime(Timestamp), /// The validity period of the signature. /// /// The validity is relative to the time stored in the signature's /// Signature Creation Time subpacket. /// /// See [Section 5.2.3.18 of RFC 9580] for details. /// /// [Section 5.2.3.18 of RFC 9580]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.18 SignatureExpirationTime(Duration), /// Whether a signature should be published. /// /// See [Section 5.2.3.19 of RFC 9580] for details. /// /// [Section 5.2.3.19 of RFC 9580]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.19 ExportableCertification(bool), /// Signer asserts that the key is not only valid but also trustworthy at /// the specified level. /// /// See [Section 5.2.3.21 of RFC 9580] for details. /// /// [Section 5.2.3.21 of RFC 9580]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.21 TrustSignature { /// Trust level, or depth. /// /// Level 0 has the same meaning as an ordinary validity /// signature. Level 1 means that the signed key is asserted /// to be a valid trusted introducer, with the 2nd octet of /// the body specifying the degree of trust. Level 2 means /// that the signed key is asserted to be trusted to issue /// level 1 trust signatures, i.e., that it is a "meta /// introducer". level: u8, /// Trust amount. /// /// This is interpreted such that values less than 120 /// indicate partial trust and values of 120 or greater /// indicate complete trust. Implementations SHOULD emit /// values of 60 for partial trust and 120 for complete trust. trust: u8, }, /// Used in conjunction with Trust Signature packets (of level > 0) to /// limit the scope of trust that is extended. /// /// See [Section 5.2.3.22 of RFC 9580] for details. /// /// [Section 5.2.3.22 of RFC 9580]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.22 /// /// Note: The RFC requires that the serialized form includes a /// trailing NUL byte. When Sequoia parses the regular expression /// subpacket, it strips the trailing NUL. (If it doesn't include /// a NUL, then parsing fails.) Likewise, when it serializes a /// regular expression subpacket, it unconditionally adds a NUL. RegularExpression(Vec<u8>), /// Whether a signature can later be revoked. /// /// See [Section 5.2.3.20 of RFC 9580] for details. /// /// [Section 5.2.3.20 of RFC 9580]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.20 Revocable(bool), /// The validity period of the key. /// /// The validity period is relative to the key's (not the signature's) creation time. /// /// See [Section 5.2.3.13 of RFC 9580] for details. /// /// [Section 5.2.3.13 of RFC 9580]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.13 KeyExpirationTime(Duration), /// The Symmetric algorithms that the certificate holder prefers. /// /// See [Section 5.2.3.14 of RFC 9580] for details. /// /// [Section 5.2.3.14 of RFC 9580]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.14 PreferredSymmetricAlgorithms(Vec<SymmetricAlgorithm>), /// Authorizes the specified key to issue revocation signatures for this /// certificate. /// /// See [Section 5.2.3.23 of RFC 9580] for details. /// /// [Section 5.2.3.23 of RFC 9580]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.23 RevocationKey(RevocationKey), /// The OpenPGP Key ID of the key issuing the signature. /// /// See [Section 5.2.3.12 of RFC 9580] for details. /// /// [Section 5.2.3.12 of RFC 9580]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.12 Issuer(KeyID), /// A "notation" on the signature. /// /// See [Section 5.2.3.24 of RFC 9580] for details. /// /// [Section 5.2.3.24 of RFC 9580]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.24 NotationData(NotationData), /// The Hash algorithms that the certificate holder prefers. /// /// See [Section 5.2.3.16 of RFC 9580] for details. /// /// [Section 5.2.3.16 of RFC 9580]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.16 PreferredHashAlgorithms(Vec<HashAlgorithm>), /// The compression algorithms that the certificate holder prefers. /// /// See [Section 5.2.3.17 of RFC 9580] for details. /// /// [Section 5.2.3.17 of RFC 9580]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.17 PreferredCompressionAlgorithms(Vec<CompressionAlgorithm>), /// A list of flags that indicate preferences that the certificate /// holder has about how the key is handled by a key server. /// /// See [Section 5.2.3.25 of RFC 9580] for details. /// /// [Section 5.2.3.25 of RFC 9580]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.25 KeyServerPreferences(KeyServerPreferences), /// The URI of a key server where the certificate holder keeps /// their certificate up to date. /// /// See [Section 5.2.3.26 of RFC 9580] for details. /// /// [Section 5.2.3.26 of RFC 9580]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.26 PreferredKeyServer(Vec<u8>), /// A flag in a User ID's self-signature that states whether this /// User ID is the primary User ID for this certificate. /// /// See [Section 5.2.3.27 of RFC 9580] for details. /// /// [Section 5.2.3.27 of RFC 9580]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.27 PrimaryUserID(bool), /// The URI of a document that describes the policy under which /// the signature was issued. /// /// See [Section 5.2.3.28 of RFC 9580] for details. /// /// [Section 5.2.3.28 of RFC 9580]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.28 PolicyURI(Vec<u8>), /// A list of flags that hold information about a key. /// /// See [Section 5.2.3.29 of RFC 9580] for details. /// /// [Section 5.2.3.29 of RFC 9580]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.29 KeyFlags(KeyFlags), /// The User ID that is responsible for the signature. /// /// See [Section 5.2.3.30 of RFC 9580] for details. /// /// [Section 5.2.3.30 of RFC 9580]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.30 SignersUserID(Vec<u8>), /// The reason for a revocation, used in key revocations and /// certification revocation signatures. /// /// See [Section 5.2.3.31 of RFC 9580] for details. /// /// [Section 5.2.3.31 of RFC 9580]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.31 ReasonForRevocation { /// Machine-readable reason for revocation. code: ReasonForRevocation, /// Human-readable reason for revocation. reason: Vec<u8>, }, /// The OpenPGP features a user's implementation supports. /// /// See [Section 5.2.3.32 of RFC 9580] for details. /// /// [Section 5.2.3.32 of RFC 9580]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.32 Features(Features), /// A signature to which this signature refers. /// /// See [Section 5.2.3.33 of RFC 9580] for details. /// /// [Section 5.2.3.33 of RFC 9580]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.33 SignatureTarget { /// Public-key algorithm of the target signature. pk_algo: PublicKeyAlgorithm, /// Hash algorithm of the target signature. hash_algo: HashAlgorithm, /// Hash digest of the target signature. digest: Vec<u8>, }, /// A complete Signature packet body. /// /// This is used to store a backsig in a subkey binding signature. /// /// See [Section 5.2.3.34 of RFC 9580] for details. /// /// [Section 5.2.3.34 of RFC 9580]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.34 EmbeddedSignature(Signature), /// The Fingerprint of the key that issued the signature. /// /// See [Section 5.2.3.35 of RFC 9580] /// for details. /// /// [Section 5.2.3.35 of RFC 9580]: https://www.rfc-editor.org/rfc/rfc9580.html#name-issuer-fingerprint IssuerFingerprint(Fingerprint), /// Who the signed message was intended for. /// /// See [Section 5.2.3.36 of RFC 9580] for details. /// /// [Section 5.2.3.36 of RFC 9580]: https://www.rfc-editor.org/rfc/rfc9580.html#name-intended-recipient-fingerpr IntendedRecipient(Fingerprint), /// The Approved Certifications subpacket (experimental). /// /// Allows the certificate holder to attest to third party /// certifications, allowing them to be distributed with the /// certificate. This can be used to address certificate flooding /// concerns. /// /// See [Section 2.2 of draft-dkg-openpgp-1pa3pc-02] for details. /// /// [Section 2.2 of draft-dkg-openpgp-1pa3pc-02]: https://www.ietf.org/archive/id/draft-dkg-openpgp-1pa3pc-02.html#approved-certifications-subpacket ApprovedCertifications(Vec<Box<[u8]>>), /// The AEAD Ciphersuites that the certificate holder prefers. /// /// A series of paired algorithm identifiers indicating how the /// keyholder prefers to receive version 2 Symmetrically Encrypted /// Integrity Protected Data. Each pair of octets indicates a /// combination of a symmetric cipher and an AEAD mode that the /// key holder prefers to use. /// /// It is assumed that only the combinations of algorithms listed /// are supported by the recipient's software, with the exception /// of the mandatory-to-implement combination of AES-128 and OCB. /// If AES-128 and OCB are not found in the subpacket, it is /// implicitly listed at the end. /// /// See [Section 5.2.3.15 of RFC 9580] for details. /// /// [Section 5.2.3.15 of RFC 9580]: https://www.rfc-editor.org/rfc/rfc9580.html#name-preferred-aead-ciphersuites PreferredAEADCiphersuites(Vec<(SymmetricAlgorithm, AEADAlgorithm)>), } assert_send_and_sync!(SubpacketValue); #[cfg(test)] impl ArbitraryBounded for SubpacketValue { fn arbitrary_bounded(g: &mut Gen, depth: usize) -> Self { use self::SubpacketValue::*; use crate::arbitrary_helper::gen_arbitrary_from_range; #[allow(deprecated)] loop { #[allow(deprecated)] break match gen_arbitrary_from_range(0..27, g) { 0 => SignatureCreationTime(Arbitrary::arbitrary(g)), 1 => SignatureExpirationTime(Arbitrary::arbitrary(g)), 2 => ExportableCertification(Arbitrary::arbitrary(g)), 3 => TrustSignature { level: Arbitrary::arbitrary(g), trust: Arbitrary::arbitrary(g), }, 4 => RegularExpression(Arbitrary::arbitrary(g)), 5 => Revocable(Arbitrary::arbitrary(g)), 6 => KeyExpirationTime(Arbitrary::arbitrary(g)), 7 => PreferredSymmetricAlgorithms(Arbitrary::arbitrary(g)), 8 => RevocationKey(Arbitrary::arbitrary(g)), 9 => Issuer(Arbitrary::arbitrary(g)), 10 => NotationData(Arbitrary::arbitrary(g)), 11 => PreferredHashAlgorithms(Arbitrary::arbitrary(g)), 12 => PreferredCompressionAlgorithms(Arbitrary::arbitrary(g)), 13 => KeyServerPreferences(Arbitrary::arbitrary(g)), 14 => PreferredKeyServer(Arbitrary::arbitrary(g)), 15 => PrimaryUserID(Arbitrary::arbitrary(g)), 16 => PolicyURI(Arbitrary::arbitrary(g)), 17 => KeyFlags(Arbitrary::arbitrary(g)), 18 => SignersUserID(Arbitrary::arbitrary(g)), 19 => ReasonForRevocation { code: Arbitrary::arbitrary(g), reason: Arbitrary::arbitrary(g), }, 20 => Features(Arbitrary::arbitrary(g)), 21 => SignatureTarget { pk_algo: Arbitrary::arbitrary(g), hash_algo: Arbitrary::arbitrary(g), digest: Arbitrary::arbitrary(g), }, 22 if depth == 0 => continue, // Don't recurse, try again. 22 => EmbeddedSignature( ArbitraryBounded::arbitrary_bounded(g, depth - 1)), 23 => IssuerFingerprint(Arbitrary::arbitrary(g)), 24 => Unknown { tag: SubpacketTag::PreferredAEADAlgorithms, body: Arbitrary::arbitrary(g), }, 25 => IntendedRecipient(Arbitrary::arbitrary(g)), 26 => PreferredAEADCiphersuites(Arbitrary::arbitrary(g)), _ => unreachable!(), } } } } #[cfg(test)] impl_arbitrary_with_bound!(SubpacketValue); impl SubpacketValue { /// Returns the subpacket tag for this value. pub fn tag(&self) -> SubpacketTag { use self::SubpacketValue::*; #[allow(deprecated)] match &self { SignatureCreationTime(_) => SubpacketTag::SignatureCreationTime, SignatureExpirationTime(_) => SubpacketTag::SignatureExpirationTime, ExportableCertification(_) => SubpacketTag::ExportableCertification, TrustSignature { .. } => SubpacketTag::TrustSignature, RegularExpression(_) => SubpacketTag::RegularExpression, Revocable(_) => SubpacketTag::Revocable, KeyExpirationTime(_) => SubpacketTag::KeyExpirationTime, PreferredSymmetricAlgorithms(_) => SubpacketTag::PreferredSymmetricAlgorithms, RevocationKey { .. } => SubpacketTag::RevocationKey, Issuer(_) => SubpacketTag::Issuer, NotationData(_) => SubpacketTag::NotationData, PreferredHashAlgorithms(_) => SubpacketTag::PreferredHashAlgorithms, PreferredCompressionAlgorithms(_) => SubpacketTag::PreferredCompressionAlgorithms, KeyServerPreferences(_) => SubpacketTag::KeyServerPreferences, PreferredKeyServer(_) => SubpacketTag::PreferredKeyServer, PrimaryUserID(_) => SubpacketTag::PrimaryUserID, PolicyURI(_) => SubpacketTag::PolicyURI, KeyFlags(_) => SubpacketTag::KeyFlags, SignersUserID(_) => SubpacketTag::SignersUserID, ReasonForRevocation { .. } => SubpacketTag::ReasonForRevocation, Features(_) => SubpacketTag::Features, SignatureTarget { .. } => SubpacketTag::SignatureTarget, EmbeddedSignature(_) => SubpacketTag::EmbeddedSignature, IssuerFingerprint(_) => SubpacketTag::IssuerFingerprint, IntendedRecipient(_) => SubpacketTag::IntendedRecipient, ApprovedCertifications(_) => SubpacketTag::ApprovedCertifications, PreferredAEADCiphersuites(_) => SubpacketTag::PreferredAEADCiphersuites, Unknown { tag, .. } => *tag, } } } /// Signature subpackets. /// /// Most of a signature's attributes are not stored in fixed fields, /// but in so-called subpackets. These subpackets are stored in a /// [`Signature`]'s so-called subpacket areas, which are effectively /// small key-value stores. The keys are subpacket tags /// ([`SubpacketTag`]). The values are well-structured /// ([`SubpacketValue`]). /// /// [`Signature`]: super::super::Signature /// /// In addition to their key and value, subpackets also include a /// critical flag. When set, this flag indicates to the OpenPGP /// implementation that if it doesn't understand the subpacket, it /// must consider the signature to be invalid. (Likewise, if it isn't /// set, then it means that it is safe for the implementation to /// ignore the subpacket.) This enables forward compatibility with /// security-relevant extensions. /// /// It is possible to control how Sequoia's higher-level functionality /// handles unknown, critical subpackets using a [`Policy`] object. /// Depending on the degree of control required, it may be sufficient /// to customize a [`StandardPolicy`] object using, for instance, the /// [`StandardPolicy::accept_critical_subpacket`] method. /// /// [`Policy`]: crate::policy::Policy /// [`StandardPolicy`]: crate::policy::StandardPolicy /// [`StandardPolicy::accept_critical_subpacket`]: crate::policy::StandardPolicy::accept_critical_subpacket() /// /// The subpacket system is extensible in two ways. First, although /// limited, the subpacket name space is not exhausted. So, it is /// possible to introduce new packets. Second, one of the subpackets, /// the [`Notation Data`] subpacket ([`NotationData`]), is explicitly /// designed for adding arbitrary data to signatures. /// /// [`Notation Data`]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.24 /// /// Subpackets are described in [Section 5.2.3.7 of RFC 9580]. /// /// [Section 5.2.3.7 of RFC 9580]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.7 pub struct Subpacket { /// The length. /// /// In order not to break signatures, we need to be able to /// roundtrip the subpackets, perfectly reproducing all the bits. /// To allow for suboptimal encoding of lengths, we store the /// length when we parse subpackets. pub(crate) // For serialize/mod.rs, parse/parse.rs. length: SubpacketLength, /// Critical flag. critical: bool, /// Packet value, must match packet type. value: SubpacketValue, /// Whether the information in this subpacket are /// authenticated in the context of its signature. authenticated: atomic::AtomicBool, } assert_send_and_sync!(Subpacket); impl Clone for Subpacket { fn clone(&self) -> Self { Subpacket { length: self.length.clone(), critical: self.critical, value: self.value.clone(), authenticated: self.authenticated().into(), } } } impl PartialEq for Subpacket { fn eq(&self, other: &Subpacket) -> bool { self.cmp(other) == Ordering::Equal } } impl Eq for Subpacket {} impl PartialOrd for Subpacket { fn partial_cmp(&self, other: &Subpacket) -> Option<Ordering> { Some(self.cmp(other)) } } impl Ord for Subpacket { fn cmp(&self, other: &Subpacket) -> Ordering { self.length.cmp(&other.length) .then_with(|| self.critical.cmp(&other.critical)) .then_with(|| self.value.cmp(&other.value)) } } impl Hash for Subpacket { fn hash<H: Hasher>(&self, state: &mut H) { self.length.hash(state); self.critical.hash(state); self.value.hash(state); } } #[cfg(test)] impl ArbitraryBounded for Subpacket { fn arbitrary_bounded(g: &mut Gen, depth: usize) -> Self { use crate::arbitrary_helper::gen_arbitrary_from_range; fn encode_non_optimal(length: usize) -> SubpacketLength { // Calculate length the same way as Subpacket::new. let length = 1 /* Tag */ + length as u32; let mut len_vec = Vec::<u8>::with_capacity(5); len_vec.push(0xFF); len_vec.extend_from_slice(&length.to_be_bytes()); SubpacketLength::new(length, Some(len_vec)) } let critical = <bool>::arbitrary(g); let use_nonoptimal_encoding = <bool>::arbitrary(g); // We don't want to overrepresent large subpackets. let create_large_subpacket = gen_arbitrary_from_range(0..25, g) == 0; let value = if create_large_subpacket { // Choose a size which makes sure the subpacket length must be // encoded with 2 or 5 octets. let value_size = gen_arbitrary_from_range(7000..9000, g); let nd = NotationData { flags: Arbitrary::arbitrary(g), name: Arbitrary::arbitrary(g), value: (0..value_size) .map(|_| <u8>::arbitrary(g)) .collect::<Vec<u8>>(), }; SubpacketValue::NotationData(nd) } else { SubpacketValue::arbitrary_bounded(g, depth) }; if use_nonoptimal_encoding { let length = encode_non_optimal(value.serialized_len()); Subpacket::with_length(length, value, critical) } else { Subpacket::new(value, critical).unwrap() } } } #[cfg(test)] impl_arbitrary_with_bound!(Subpacket); impl fmt::Debug for Subpacket { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let mut s = f.debug_struct("Subpacket"); if self.length.raw.is_some() { s.field("length", &self.length); } if self.critical { s.field("critical", &self.critical); } s.field("value", &self.value); s.field("authenticated", &self.authenticated()); s.finish() } } impl Subpacket { /// Creates a new Subpacket. pub fn new(value: SubpacketValue, critical: bool) -> Result<Subpacket> { Ok(Self::with_length( SubpacketLength::from(1 /* Tag */ + value.serialized_len() as u32), value, critical)) } /// Creates a new subpacket with the given length and tag. pub(crate) fn with_length(length: SubpacketLength, value: SubpacketValue, critical: bool) -> Subpacket { Subpacket { length, critical, value, authenticated: false.into(), } } /// Returns whether the critical bit is set. pub fn critical(&self) -> bool { self.critical } /// Returns the Subpacket's tag. pub fn tag(&self) -> SubpacketTag { self.value.tag() } /// Returns the Subpacket's value. pub fn value(&self) -> &SubpacketValue { &self.value } /// Returns whether the information in this subpacket has been /// authenticated. /// /// There are three ways a subpacket can be authenticated: /// /// - It is in the hashed subpacket area and the signature has /// been verified. /// - It is in the unhashed subpacket area and the information /// is self-authenticating and has been authenticated by /// Sequoia. This can be done for issuer information and /// embedded Signatures. /// - The subpacket has been authenticated by the user and /// marked as such using [`Subpacket::set_authenticated`]. /// /// Note: The authentication is only valid in the context of the /// signature the subpacket is in. If the authenticated /// `Subpacket` is added to a [`SubpacketArea`], the flag is /// cleared. pub fn authenticated(&self) -> bool { self.authenticated.load(atomic::Ordering::Relaxed) } /// Marks the information in this subpacket as authenticated or /// not. /// /// See [`Subpacket::authenticated`] for more information. /// /// [`Subpacket::authenticated`]: Self::authenticated() pub fn set_authenticated(&self, authenticated: bool) -> bool { self.authenticated.swap(authenticated, atomic::Ordering::Relaxed) } } #[derive(Clone, Debug)] pub(crate) struct SubpacketLength { /// The length. pub(crate) len: u32, /// The length encoding used in the serialized form. /// If this is `None`, optimal encoding will be used. pub(crate) raw: Option<Vec<u8>>, } impl From<u32> for SubpacketLength { fn from(len: u32) -> Self { SubpacketLength { len, raw: None, } } } impl PartialEq for SubpacketLength { fn eq(&self, other: &Self) -> bool { self.cmp(other) == Ordering::Equal } } impl Eq for SubpacketLength {} impl Hash for SubpacketLength { fn hash<H: Hasher>(&self, state: &mut H) { match &self.raw { Some(raw) => raw.hash(state), None => { let l = self.serialized_len(); let mut raw = [0; 5]; self.serialize_into(&mut raw[..l]).unwrap(); raw[..l].hash(state); }, } } } impl PartialOrd for SubpacketLength { fn partial_cmp(&self, other: &SubpacketLength) -> Option<Ordering> { Some(self.cmp(other)) } } impl Ord for SubpacketLength { fn cmp(&self, other: &SubpacketLength) -> Ordering { match (&self.raw, &other.raw) { (None, None) => { self.len.cmp(&other.len) }, // Compare serialized representations if at least one is given (Some(self_raw), Some(other_raw)) => { self_raw.cmp(other_raw) }, (Some(self_raw), None) => { let mut other_raw = [0; 5]; other.serialize_into(&mut other_raw[..self.serialized_len()]) .unwrap(); self_raw[..].cmp(&other_raw[..self.serialized_len()]) }, (None, Some(other_raw)) => { let mut self_raw = [0; 5]; self.serialize_into(&mut self_raw[..self.serialized_len()]) .unwrap(); self_raw[..self.serialized_len()].cmp(&other_raw[..]) }, } } } impl SubpacketLength { pub(crate) fn new(len: u32, raw: Option<Vec<u8>>) -> Self { Self { len, raw } } /// Returns the length. pub(crate) fn len(&self) -> usize { self.len as usize } /// Returns the length of the optimal encoding of `len`. pub(crate) fn len_optimal_encoding(len: u32) -> usize { BodyLength::serialized_len(&BodyLength::Full(len)) } } /// Subpacket storage. /// /// Subpackets are stored either in a so-called hashed area or a /// so-called unhashed area. Packets stored in the hashed area are /// protected by the signature's hash whereas packets stored in the /// unhashed area are not. Generally, two types of information are /// stored in the unhashed area: self-authenticating data (the /// `Issuer` subpacket, the `Issuer Fingerprint` subpacket, and the /// `Embedded Signature` subpacket), and hints, like the features /// subpacket. /// /// When accessing subpackets directly via `SubpacketArea`s, the /// subpackets are only looked up in the hashed area unless the /// packets are self-authenticating in which case subpackets from the /// hash area are preferred. To return packets from a specific area, /// use the `hashed_area` and `unhashed_area` methods to get the /// specific methods and then use their accessors. #[derive(Clone, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct SubpacketAreas { /// Subpackets that are part of the signature. hashed_area: SubpacketArea, /// Subpackets that are _not_ part of the signature. unhashed_area: SubpacketArea, } assert_send_and_sync!(SubpacketAreas); #[cfg(test)] impl ArbitraryBounded for SubpacketAreas { fn arbitrary_bounded(g: &mut Gen, depth: usize) -> Self { SubpacketAreas::new(ArbitraryBounded::arbitrary_bounded(g, depth), ArbitraryBounded::arbitrary_bounded(g, depth)) } } #[cfg(test)] impl_arbitrary_with_bound!(SubpacketAreas); impl SubpacketAreas { /// Returns a new `SubpacketAreas` object. pub fn new(hashed_area: SubpacketArea, unhashed_area: SubpacketArea) -> Self { Self { hashed_area, unhashed_area, } } /// Gets a reference to the hashed area. pub fn hashed_area(&self) -> &SubpacketArea { &self.hashed_area } /// Gets a mutable reference to the hashed area. /// /// Note: if you modify the hashed area of a [`Signature4`], this /// will invalidate the signature. Instead, you should normally /// convert the [`Signature4`] into a [`signature::SignatureBuilder`], /// modify that, and then create a new signature. pub fn hashed_area_mut(&mut self) -> &mut SubpacketArea { &mut self.hashed_area } /// Gets a reference to the unhashed area. pub fn unhashed_area(&self) -> &SubpacketArea { &self.unhashed_area } /// Gets a mutable reference to the unhashed area. pub fn unhashed_area_mut(&mut self) -> &mut SubpacketArea { &mut self.unhashed_area } /// Sorts the subpacket areas. /// /// See [`SubpacketArea::sort()`]. /// pub fn sort(&mut self) { self.hashed_area.sort(); self.unhashed_area.sort(); } /// Returns a reference to the *last* instance of the specified /// subpacket, if any. /// /// This function returns the last instance of the specified /// subpacket in the subpacket areas in which it can occur. Thus, /// when looking for the `Signature Creation Time` subpacket, this /// function only considers the hashed subpacket area. But, when /// looking for the `Embedded Signature` subpacket, this function /// considers both subpacket areas. /// /// Unknown subpackets are assumed to only safely occur in the /// hashed subpacket area. Thus, any instances of them in the /// unhashed area are ignored. /// /// For subpackets that can safely occur in both subpacket areas, /// this function prefers instances in the hashed subpacket area. pub fn subpacket(&self, tag: SubpacketTag) -> Option<&Subpacket> { if let Some(sb) = self.hashed_area().subpacket(tag) { return Some(sb); } // There are a couple of subpackets that we are willing to // take from the unhashed area. The others we ignore // completely. if !(tag == SubpacketTag::Issuer || tag == SubpacketTag::IssuerFingerprint || tag == SubpacketTag::EmbeddedSignature) { return None; } self.unhashed_area().subpacket(tag) } /// Returns a mutable reference to the *last* instance of the /// specified subpacket, if any. /// /// This function returns the last instance of the specified /// subpacket in the subpacket areas in which it can occur. Thus, /// when looking for the `Signature Creation Time` subpacket, this /// function only considers the hashed subpacket area. But, when /// looking for the `Embedded Signature` subpacket, this function /// considers both subpacket areas. /// /// Unknown subpackets are assumed to only safely occur in the /// hashed subpacket area. Thus, any instances of them in the /// unhashed area are ignored. /// /// For subpackets that can safely occur in both subpacket areas, /// this function prefers instances in the hashed subpacket area. pub fn subpacket_mut(&mut self, tag: SubpacketTag) -> Option<&mut Subpacket> { if let Some(_) = self.hashed_area().subpacket(tag) { return self.hashed_area_mut().subpacket_mut(tag); } // There are a couple of subpackets that we are willing to // take from the unhashed area. The others we ignore // completely. if !(tag == SubpacketTag::Issuer || tag == SubpacketTag::IssuerFingerprint || tag == SubpacketTag::EmbeddedSignature) { return None; } self.unhashed_area_mut().subpacket_mut(tag) } /// Returns an iterator over all instances of the specified /// subpacket. /// /// This function returns an iterator over all instances of the /// specified subpacket in the subpacket areas in which it can /// occur. Thus, when looking for the `Issuer` subpacket, the /// iterator includes instances of the subpacket from both the /// hashed subpacket area and the unhashed subpacket area, but /// when looking for the `Signature Creation Time` subpacket, the /// iterator only includes instances of the subpacket from the /// hashed subpacket area; any instances of the subpacket in the /// unhashed subpacket area are ignored. /// /// Unknown subpackets are assumed to only safely occur in the /// hashed subpacket area. Thus, any instances of them in the /// unhashed area are ignored. pub fn subpackets(&self, tag: SubpacketTag) -> impl Iterator<Item = &Subpacket> + Send + Sync { // It would be nice to do: // // let iter = self.hashed_area().subpackets(tag); // if (subpacket allowed in unhashed area) { // iter.chain(self.unhashed_area().subpackets(tag)) // } else { // iter // } // // but then we have different types. Instead, we need to // inline SubpacketArea::subpackets, add the additional // constraint in the closure, and hope that the optimizer is // smart enough to not unnecessarily iterate over the unhashed // area. self.hashed_area().subpackets(tag).chain( self.unhashed_area() .iter() .filter(move |sp| { (tag == SubpacketTag::Issuer || tag == SubpacketTag::IssuerFingerprint || tag == SubpacketTag::EmbeddedSignature) && sp.tag() == tag })) } pub(crate) fn subpackets_mut(&mut self, tag: SubpacketTag) -> impl Iterator<Item = &mut Subpacket> + Send + Sync { self.hashed_area.subpackets_mut(tag).chain( self.unhashed_area .iter_mut() .filter(move |sp| { (tag == SubpacketTag::Issuer || tag == SubpacketTag::IssuerFingerprint || tag == SubpacketTag::EmbeddedSignature) && sp.tag() == tag })) } /// Returns the value of the Signature Creation Time subpacket. /// /// The [Signature Creation Time subpacket] specifies when the /// signature was created. According to the standard, all /// signatures must include a Signature Creation Time subpacket in /// the signature's hashed area. This doesn't mean that the time /// stamp is correct: the issuer can always forge it. /// /// [Signature Creation Time subpacket]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.11 /// /// If the subpacket is not present in the hashed subpacket area, /// this returns `None`. /// /// Note: if the signature contains multiple instances of this /// subpacket in the hashed subpacket area, the last one is /// returned. pub fn signature_creation_time(&self) -> Option<time::SystemTime> { // 4-octet time field if let Some(sb) = self.subpacket(SubpacketTag::SignatureCreationTime) { if let SubpacketValue::SignatureCreationTime(v) = sb.value { Some(v.into()) } else { None } } else { None } } /// Returns the value of the Signature Expiration Time subpacket. /// /// This function is called `signature_validity_period` and not /// `signature_expiration_time`, which would be more consistent /// with the subpacket's name, because the latter suggests an /// absolute time, but the time is actually relative to the /// signature's creation time, which is stored in the signature's /// [Signature Creation Time subpacket]. /// /// [Signature Creation Time subpacket]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.11 /// /// A [Signature Expiration Time subpacket] specifies when the /// signature expires. This is different from the [Key Expiration /// Time subpacket], which is accessed using /// [`SubpacketAreas::key_validity_period`], and used to /// specify when an associated key expires. The difference is /// that in the former case, the signature itself expires, but in /// the latter case, only the associated key expires. This /// difference is critical: if a binding signature expires, then /// an OpenPGP implementation will still consider the associated /// key to be valid if there is another valid binding signature, /// even if it is older than the expired signature; if the active /// binding signature indicates that the key has expired, then /// OpenPGP implementations will not fall back to an older binding /// signature. /// /// [Signature Expiration Time subpacket]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.18 /// [Key Expiration Time subpacket]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.13 /// [`SubpacketAreas::key_validity_period`]: SubpacketAreas::key_validity_period() /// /// There are several cases where having a signature expire is /// useful. Say Alice certifies Bob's certificate for /// `bob@example.org`. She can limit the lifetime of the /// certification to force her to reevaluate the certification /// shortly before it expires. For instance, is Bob still /// associated with `example.org`? Does she have reason to /// believe that his key has been compromised? Using an /// expiration is common in the X.509 ecosystem. For instance, /// [Let's Encrypt] issues certificates with 90-day lifetimes. /// /// [Let's Encrypt]: https://letsencrypt.org/2015/11/09/why-90-days.html /// /// Having signatures expire can also be useful when deploying /// software. For instance, you might have a service that /// installs an update if it has been signed by a trusted /// certificate. To prevent an adversary from coercing the /// service to install an older version, you could limit the /// signature's lifetime to just a few minutes. /// /// If the subpacket is not present in the hashed subpacket area, /// this returns `None`. If this function returns `None`, or the /// returned period is `0`, the signature does not expire. /// /// Note: if the signature contains multiple instances of this /// subpacket in the hashed subpacket area, the last one is /// returned. pub fn signature_validity_period(&self) -> Option<time::Duration> { // 4-octet time field if let Some(sb) = self.subpacket(SubpacketTag::SignatureExpirationTime) { if let SubpacketValue::SignatureExpirationTime(v) = sb.value { Some(v.into()) } else { None } } else { None } } /// Returns the value of the Signature Expiration Time subpacket /// as an absolute time. /// /// A [Signature Expiration Time subpacket] specifies when the /// signature expires. The value stored is not an absolute time, /// but a duration, which is relative to the Signature's creation /// time. To better reflect the subpacket's name, this method /// returns the absolute expiry time, and the /// [`SubpacketAreas::signature_validity_period`] method returns /// the subpacket's raw value. /// /// [Signature Expiration Time subpacket]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.18 /// [`SubpacketAreas::signature_validity_period`]: SubpacketAreas::signature_validity_period() /// /// The Signature Expiration Time subpacket is different from the /// [Key Expiration Time subpacket], which is accessed using /// [`SubpacketAreas::key_validity_period`], and used specifies /// when an associated key expires. The difference is that in the /// former case, the signature itself expires, but in the latter /// case, only the associated key expires. This difference is /// critical: if a binding signature expires, then an OpenPGP /// implementation will still consider the associated key to be /// valid if there is another valid binding signature, even if it /// is older than the expired signature; if the active binding /// signature indicates that the key has expired, then OpenPGP /// implementations will not fall back to an older binding /// signature. /// /// [Key Expiration Time subpacket]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.13 /// [`SubpacketAreas::key_validity_period`]: SubpacketAreas::key_validity_period() /// /// There are several cases where having a signature expire is /// useful. Say Alice certifies Bob's certificate for /// `bob@example.org`. She can limit the lifetime of the /// certification to force her to reevaluate the certification /// shortly before it expires. For instance, is Bob still /// associated with `example.org`? Does she have reason to /// believe that his key has been compromised? Using an /// expiration is common in the X.509 ecosystem. For instance, /// [Let's Encrypt] issues certificates with 90-day lifetimes. /// /// [Let's Encrypt]: https://letsencrypt.org/2015/11/09/why-90-days.html /// /// Having signatures expire can also be useful when deploying /// software. For instance, you might have a service that /// installs an update if it has been signed by a trusted /// certificate. To prevent an adversary from coercing the /// service to install an older version, you could limit the /// signature's lifetime to just a few minutes. /// /// If the subpacket is not present in the hashed subpacket area, /// this returns `None`. If this function returns `None`, the /// signature does not expire. /// /// Note: if the signature contains multiple instances of this /// subpacket in the hashed subpacket area, the last one is /// returned. // Note: If you update this function, also update // SignatureBuilder::signature_expiration_time. pub fn signature_expiration_time(&self) -> Option<time::SystemTime> { match (self.signature_creation_time(), self.signature_validity_period()) { (Some(ct), Some(vp)) if vp.as_secs() > 0 => Some(ct + vp), _ => None, } } /// Returns whether the signature is alive at the specified /// time. /// /// A signature is considered to be alive if `creation time - /// tolerance <= time` and `time < expiration time`. /// /// This function does not check whether the key is revoked. /// /// If `time` is `None`, then this function uses the current time /// for `time`. /// /// If `time` is `None`, and `clock_skew_tolerance` is `None`, /// then this function uses [`CLOCK_SKEW_TOLERANCE`] for the /// tolerance. If `time` is not `None `and `clock_skew_tolerance` /// is `None`, it uses no tolerance. The intuition here is that /// we only need a tolerance when checking if a signature is alive /// right now; if we are checking at a specific time, we don't /// want to use a tolerance. /// /// /// A small amount of tolerance for clock skew is necessary, /// because although most computers synchronize their clocks with /// a time server, up to a few seconds of clock skew are not /// unusual in practice. And, even worse, several minutes of /// clock skew appear to be not uncommon on virtual machines. /// /// Not accounting for clock skew can result in signatures being /// unexpectedly considered invalid. Consider: computer A sends a /// message to computer B at 9:00, but computer B, whose clock /// says the current time is 8:59, rejects it, because the /// signature appears to have been made in the future. This is /// particularly problematic for low-latency protocols built on /// top of OpenPGP, e.g., when two MUAs synchronize their state /// via a shared IMAP folder. /// /// Being tolerant to potential clock skew is not always /// appropriate. For instance, when determining a User ID's /// current self signature at time `t`, we don't ever want to /// consider a self-signature made after `t` to be valid, even if /// it was made just a few moments after `t`. This goes doubly so /// for soft revocation certificates: the user might send a /// message that she is retiring, and then immediately create a /// soft revocation. The soft revocation should not invalidate /// the message. /// /// Unfortunately, in many cases, whether we should account for /// clock skew or not depends on application-specific context. As /// a rule of thumb, if the time and the timestamp come from /// different clocks, you probably want to account for clock skew. /// /// # Errors /// /// [Section 5.2.3.11 of RFC 9580] states that a Signature Creation /// Time subpacket "MUST be present in the hashed area." /// Consequently, if such a packet does not exist, this function /// returns [`Error::MalformedPacket`]. /// /// [Section 5.2.3.11 of RFC 9580]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.11 /// [`Error::MalformedPacket`]: super::super::super::Error::MalformedPacket /// /// # Examples /// /// Alice's desktop computer and laptop exchange messages in real /// time via a shared IMAP folder. Unfortunately, the clocks are /// not perfectly synchronized: the desktop computer's clock is a /// few seconds ahead of the laptop's clock. When there is little /// or no propagation delay, this means that the laptop will /// consider the signatures to be invalid, because they appear to /// have been created in the future. Using a tolerance prevents /// this from happening. /// /// ``` /// use std::time::{SystemTime, Duration}; /// use sequoia_openpgp as openpgp; /// use openpgp::cert::prelude::*; /// use openpgp::packet::signature::SignatureBuilder; /// # use openpgp::packet::signature::subpacket::SubpacketTag; /// use openpgp::types::SignatureType; /// /// # fn main() -> openpgp::Result<()> { /// # /// let (alice, _) = /// CertBuilder::general_purpose(Some("alice@example.org")) /// .generate()?; /// /// // Alice's Desktop computer signs a message. Its clock is a /// // few seconds fast. /// let now = SystemTime::now() + Duration::new(5, 0); /// /// let mut alices_signer = alice.primary_key().key().clone() /// .parts_into_secret()?.into_keypair()?; /// let msg = "START PROTOCOL"; /// let mut sig = SignatureBuilder::new(SignatureType::Binary) /// .set_signature_creation_time(now)? /// .sign_message(&mut alices_signer, msg)?; /// # assert!(sig.verify_message(alices_signer.public(), msg).is_ok()); /// /// // The desktop computer transfers the message to the laptop /// // via the shared IMAP folder. Because the laptop receives a /// // push notification, it immediately processes it. /// // Unfortunately, it is considered to be invalid: the message /// // appears to be from the future! /// assert!(sig.signature_alive(None, Duration::new(0, 0)).is_err()); /// /// // But, using the small default tolerance causes the laptop /// // to consider the signature to be alive. /// assert!(sig.signature_alive(None, None).is_ok()); /// # Ok(()) } /// ``` pub fn signature_alive<T, U>(&self, time: T, clock_skew_tolerance: U) -> Result<()> where T: Into<Option<time::SystemTime>>, U: Into<Option<time::Duration>> { let (time, tolerance) = match (time.into(), clock_skew_tolerance.into()) { (None, None) => (crate::now(), CLOCK_SKEW_TOLERANCE), (None, Some(tolerance)) => (crate::now(), tolerance), (Some(time), None) => (time, time::Duration::new(0, 0)), (Some(time), Some(tolerance)) => (time, tolerance) }; match (self.signature_creation_time(), self.signature_validity_period()) { (None, _) => Err(Error::MalformedPacket("no signature creation time".into()) .into()), (Some(c), Some(e)) if e.as_secs() > 0 && (c + e) <= time => Err(Error::Expired(c + e).into()), // Be careful to avoid underflow. (Some(c), _) if cmp::max(c, time::UNIX_EPOCH + tolerance) - tolerance > time => Err(Error::NotYetLive(cmp::max(c, time::UNIX_EPOCH + tolerance) - tolerance).into()), _ => Ok(()), } } /// Returns the value of the Key Expiration Time subpacket. /// /// This function is called `key_validity_period` and not /// `key_expiration_time`, which would be more consistent with /// the subpacket's name, because the latter suggests an absolute /// time, but the time is actually relative to the associated /// key's (*not* the signature's) creation time, which is stored /// in the [Key]. /// /// [Key]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.5.2 /// /// A [Key Expiration Time subpacket] specifies when the /// associated key expires. This is different from the [Signature /// Expiration Time subpacket] (accessed using /// [`SubpacketAreas::signature_validity_period`]), which is /// used to specify when the signature expires. That is, in the /// former case, the associated key expires, but in the latter /// case, the signature itself expires. This difference is /// critical: if a binding signature expires, then an OpenPGP /// implementation will still consider the associated key to be /// valid if there is another valid binding signature, even if it /// is older than the expired signature; if the active binding /// signature indicates that the key has expired, then OpenPGP /// implementations will not fall back to an older binding /// signature. /// /// [Key Expiration Time subpacket]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.13 /// [Signature Expiration Time subpacket]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.13 /// [`SubpacketAreas::signature_validity_period`]: Self::signature_validity_period() /// /// If the subpacket is not present in the hashed subpacket area, /// this returns `None`. If this function returns `None`, or the /// returned period is `0`, the key does not expire. /// /// Note: if the signature contains multiple instances of this /// subpacket in the hashed subpacket area, the last one is /// returned. pub fn key_validity_period(&self) -> Option<time::Duration> { // 4-octet time field if let Some(sb) = self.subpacket(SubpacketTag::KeyExpirationTime) { if let SubpacketValue::KeyExpirationTime(v) = sb.value { Some(v.into()) } else { None } } else { None } } /// Returns the value of the Key Expiration Time subpacket /// as an absolute time. /// /// A [Key Expiration Time subpacket] specifies when a key /// expires. The value stored is not an absolute time, but a /// duration, which is relative to the associated [Key]'s creation /// time, which is stored in the Key packet, not the binding /// signature. As such, the Key Expiration Time subpacket is only /// meaningful on a key's binding signature. To better reflect /// the subpacket's name, this method returns the absolute expiry /// time, and the [`SubpacketAreas::key_validity_period`] method /// returns the subpacket's raw value. /// /// [Key Expiration Time subpacket]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.13 /// [Key]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.5.2 /// [`SubpacketAreas::key_validity_period`]: Self::key_validity_period() /// /// The Key Expiration Time subpacket is different from the /// [Signature Expiration Time subpacket], which is accessed using /// [`SubpacketAreas::signature_validity_period`], and specifies /// when a signature expires. The difference is that in the /// former case, only the associated key expires, but in the /// latter case, the signature itself expires. This difference is /// critical: if a binding signature expires, then an OpenPGP /// implementation will still consider the associated key to be /// valid if there is another valid binding signature, even if it /// is older than the expired signature; if the active binding /// signature indicates that the key has expired, then OpenPGP /// implementations will not fall back to an older binding /// signature. /// /// [Signature Expiration Time subpacket]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.18 /// [`SubpacketAreas::signature_validity_period`]: Self::signature_validity_period() /// /// Because the absolute time is relative to the key's creation /// time, which is stored in the key itself, this function needs /// the associated key. Since there is no way to get the /// associated key from a signature, the key must be passed to /// this function. This function does not check that the key is /// in fact associated with this signature. /// /// If the subpacket is not present in the hashed subpacket area, /// this returns `None`. If this function returns `None`, the /// signature does not expire. /// /// Note: if the signature contains multiple instances of this /// subpacket in the hashed subpacket area, the last one is /// returned. pub fn key_expiration_time<P, R>(&self, key: &Key<P, R>) -> Option<time::SystemTime> where P: key::KeyParts, R: key::KeyRole, { match self.key_validity_period() { Some(vp) if vp.as_secs() > 0 => Some(key.creation_time() + vp), _ => None, } } /// Returns whether a key is alive at the specified /// time. /// /// A [Key] is considered to be alive if `creation time - /// tolerance <= time` and `time < expiration time`. /// /// [Key]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.5.2 /// /// This function does not check whether the signature is alive /// (cf. [`SubpacketAreas::signature_alive`]), or whether the key /// is revoked (cf. [`ValidKeyAmalgamation::revoked`]). /// /// [`SubpacketAreas::signature_alive`]: Self::signature_alive() /// [`ValidKeyAmalgamation::revoked`]: crate::cert::amalgamation::key::ValidKeyAmalgamationIter::revoked() /// /// If `time` is `None`, then this function uses the current time /// for `time`. /// /// Whereas a Key's expiration time is stored in the Key's active /// binding signature in the [Key Expiration Time /// subpacket], its creation time is stored in the Key packet. As /// such, the associated Key must be passed to this function. /// This function, however, has no way to check that the signature /// is actually a binding signature for the specified Key. /// /// [Key Expiration Time subpacket]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.13 /// /// # Examples /// /// Even keys that don't expire may not be considered alive. This /// is the case if they were created after the specified time. /// /// ``` /// use std::time::{SystemTime, Duration}; /// use sequoia_openpgp as openpgp; /// use openpgp::cert::prelude::*; /// use openpgp::policy::StandardPolicy; /// /// # fn main() -> openpgp::Result<()> { /// # /// let p = &StandardPolicy::new(); /// /// let (cert, _) = CertBuilder::new().generate()?; /// /// let mut pk = cert.primary_key().key(); /// let sig = cert.primary_key().with_policy(p, None)?.binding_signature(); /// /// assert!(sig.key_alive(pk, None).is_ok()); /// // A key is not considered alive prior to its creation time. /// let the_past = SystemTime::now() - Duration::new(300, 0); /// assert!(sig.key_alive(pk, the_past).is_err()); /// # Ok(()) } /// ``` pub fn key_alive<P, R, T>(&self, key: &Key<P, R>, t: T) -> Result<()> where P: key::KeyParts, R: key::KeyRole, T: Into<Option<time::SystemTime>> { let t = t.into().unwrap_or_else(crate::now); match self.key_validity_period() { Some(e) if e.as_secs() > 0 && key.creation_time() + e <= t => Err(Error::Expired(key.creation_time() + e).into()), _ if key.creation_time() > t => Err(Error::NotYetLive(key.creation_time()).into()), _ => Ok(()), } } /// Returns the value of the Exportable Certification subpacket. /// /// The [Exportable Certification subpacket] indicates whether the /// signature should be exported (e.g., published on a public key /// server) or not. When using [`Serialize::export`] to export a /// certificate, signatures that have this subpacket present and /// set to false are not serialized. /// /// [Exportable Certification subpacket]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.19 /// [`Serialize::export`]: https://docs.sequoia-pgp.org/sequoia_openpgp/serialize/trait.Serialize.html#method.export /// /// Normally, you'll want to use [`Signature4::exportable`] to /// check if a signature should be exported. That function also /// checks whether the signature includes any sensitive /// [Revocation Key subpackets], which also shouldn't be exported. /// /// [`Signature4::exportable`]: super::Signature4::exportable() /// [Revocation Key subpackets]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.23 /// /// If the subpacket is not present in the hashed subpacket area, /// this returns `None`. /// /// Note: if the signature contains multiple instances of this /// subpacket in the hashed subpacket area, the last one is /// returned. pub fn exportable_certification(&self) -> Option<bool> { // 1 octet of exportability, 0 for not, 1 for exportable if let Some(sb) = self.subpacket(SubpacketTag::ExportableCertification) { if let SubpacketValue::ExportableCertification(v) = sb.value { Some(v) } else { None } } else { None } } /// Returns the value of the Trust Signature subpacket. /// /// The [Trust Signature subpacket] indicates the degree to which /// a certificate holder is trusted to certify other keys. /// /// [Trust Signature subpacket]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.21 /// /// A level of 0 means that the certificate holder is not trusted /// to certificate other keys, a level of 1 means that the /// certificate holder is a trusted introducer (a [certificate /// authority]) and any certifications that they make should be /// considered valid. A level of 2 means the certificate holder /// can designate level 1 trusted introducers, etc. /// /// [certificate authority]: https://en.wikipedia.org/wiki/Certificate_authority /// /// The trust indicates the degree of confidence. A value of 120 /// means that a certification should be considered valid. A /// value of 60 means that a certification should only be /// considered partially valid. In the latter case, typically /// three such certifications are required for a binding to be /// considered authenticated. /// /// If the subpacket is not present in the hashed subpacket area, /// this returns `None`. /// /// Note: if the signature contains multiple instances of this /// subpacket in the hashed subpacket area, the last one is /// returned. pub fn trust_signature(&self) -> Option<(u8, u8)> { // 1 octet "level" (depth), 1 octet of trust amount if let Some(sb) = self.subpacket(SubpacketTag::TrustSignature) { if let SubpacketValue::TrustSignature{ level, trust } = sb.value { Some((level, trust)) } else { None } } else { None } } /// Returns the values of all Regular Expression subpackets. /// /// The [Regular Expression subpacket] is used in conjunction with /// a [Trust Signature subpacket], which is accessed using /// [`SubpacketAreas::trust_signature`], to limit the scope /// of a trusted introducer. This is useful, for instance, when a /// company has a CA and you only want to trust them to certify /// their own employees. /// /// [Trust Signature subpacket]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.21 /// [Regular Expression subpacket]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.22 /// [`SubpacketAreas::trust_signature`]: Self::trust_signature() /// /// Note: The serialized form includes a trailing `NUL` byte. /// Sequoia strips the `NUL` when parsing the subpacket. /// /// This returns all instances of the Regular Expression subpacket /// in the hashed subpacket area. pub fn regular_expressions(&self) -> impl Iterator<Item=&[u8]> + Send + Sync { self.subpackets(SubpacketTag::RegularExpression).map(|sb| { match sb.value { SubpacketValue::RegularExpression(ref v) => &v[..], _ => unreachable!(), } }) } /// Returns the value of the Revocable subpacket. /// /// /// The [Revocable subpacket] indicates whether a certification /// may be later revoked by creating a [Certification revocation /// signature] (0x30) that targets the signature using the /// [Signature Target subpacket] (accessed using the /// [`SubpacketAreas::signature_target`] method). /// /// [Revocable subpacket]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.20 /// [Certification revocation signature]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.1 /// [Signature Target subpacket]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.33 /// [`SubpacketAreas::signature_target`]: Self::signature_target() /// /// If the subpacket is not present in the hashed subpacket area, /// this returns `None`. /// /// Note: if the signature contains multiple instances of this /// subpacket in the hashed subpacket area, the last one is /// returned. pub fn revocable(&self) -> Option<bool> { // 1 octet of revocability, 0 for not, 1 for revocable if let Some(sb) = self.subpacket(SubpacketTag::Revocable) { if let SubpacketValue::Revocable(v) = sb.value { Some(v) } else { None } } else { None } } /// Returns the values of all Revocation Key subpackets. /// /// A [Revocation Key subpacket] indicates certificates (so-called /// designated revokers) that are allowed to revoke the signer's /// certificate. For instance, if Alice trusts Bob, she can set /// him as a designated revoker. This is useful if Alice loses /// access to her key, and therefore is unable to generate a /// revocation certificate on her own. In this case, she can /// still Bob to generate one on her behalf. /// /// When getting a certificate's revocation keys, all valid /// self-signatures should be checked, not only the active /// self-signature. This prevents an attacker who has gained /// access to the private key material from invalidating a /// third-party revocation by publishing a new self signature that /// doesn't include any revocation keys. /// /// Due to the complexity of verifying such signatures, many /// OpenPGP implementations do not support this feature. /// /// [Revocation Key subpacket]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.23 /// /// This returns all instance of the Revocation Key subpacket in /// the hashed subpacket area. pub fn revocation_keys(&self) -> impl Iterator<Item=&RevocationKey> + Send + Sync { self.subpackets(SubpacketTag::RevocationKey) .map(|sb| { match sb.value { SubpacketValue::RevocationKey(ref rk) => rk, _ => unreachable!(), } }) } /// Returns the values of all Issuer subpackets. /// /// The [Issuer subpacket] is used when processing a signature to /// identify which certificate created the signature. Since this /// information is self-authenticating (the act of validating the /// signature authenticates the subpacket), it may be stored in the /// unhashed subpacket area. /// /// [Issuer subpacket]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.12 /// /// This returns all instances of the Issuer subpacket in both the /// hashed subpacket area and the unhashed subpacket area. pub fn issuers(&self) -> impl Iterator<Item=&KeyID> + Send + Sync { // 8-octet Key ID self.subpackets(SubpacketTag::Issuer) .map(|sb| { match sb.value { SubpacketValue::Issuer(ref keyid) => keyid, _ => unreachable!(), } }) } /// Returns the values of all Issuer Fingerprint subpackets. /// /// The [Issuer Fingerprint subpacket] is used when processing a /// signature to identify which certificate created the signature. /// Since this information is self-authenticating (the act of /// validating the signature authenticates the subpacket), it is /// normally stored in the unhashed subpacket area. /// /// [Issuer Fingerprint subpacket]: https://www.rfc-editor.org/rfc/rfc9580.html#name-issuer-fingerprint /// /// This returns all instances of the Issuer Fingerprint subpacket /// in both the hashed subpacket area and the unhashed subpacket /// area. pub fn issuer_fingerprints(&self) -> impl Iterator<Item=&Fingerprint> + Send + Sync { // 1 octet key version number, N octets of fingerprint self.subpackets(SubpacketTag::IssuerFingerprint) .map(|sb| { match sb.value { SubpacketValue::IssuerFingerprint(ref fpr) => fpr, _ => unreachable!(), } }) } /// Returns all Notation Data subpackets. /// /// [Notation Data subpackets] are key-value pairs. They can be /// used by applications to annotate signatures in a structured /// way. For instance, they can define additional, /// application-specific security requirements. Because they are /// functionally equivalent to subpackets, they can also be used /// for OpenPGP extensions. This is how the [Intended Recipient /// subpacket] started life. /// /// [Notation Data subpackets]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.24 /// [Intended Recipient subpacket]: https://www.rfc-editor.org/rfc/rfc9580.html#name-intended-recipient-fingerpr /// /// Notation names are structured, and are divided into two /// namespaces: the user namespace and the IETF namespace. Names /// in the user namespace have the form `name@example.org` and /// their meaning is defined by the owner of the domain. The /// meaning of the notation `name@example.org`, for instance, is /// defined by whoever controls `example.org`. Names in the IETF /// namespace do not contain an `@` and are managed by IANA. See /// [Section 5.2.3.24 of RFC 9580] for details. /// /// [Section 5.2.3.24 of RFC 9580]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.24 /// /// This returns all instances of the Notation Data subpacket in /// the hashed subpacket area. pub fn notation_data(&self) -> impl Iterator<Item=&NotationData> + Send + Sync { self.subpackets(SubpacketTag::NotationData) .map(|sb| { match sb.value { SubpacketValue::NotationData(ref v) => v, _ => unreachable!(), } }) } /// Returns the values of all Notation Data subpackets with the /// given name. /// /// [Notation Data subpackets] are key-value pairs. They can be /// used by applications to annotate signatures in a structured /// way. For instance, they can define additional, /// application-specific security requirements. Because they are /// functionally equivalent to subpackets, they can also be used /// for OpenPGP extensions. This is how the [Intended Recipient /// subpacket] started life. /// /// [Notation Data subpackets]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.24 /// [Intended Recipient subpacket]: https://www.rfc-editor.org/rfc/rfc9580.html#name-intended-recipient-fingerpr /// /// Notation names are structured, and are divided into two /// namespaces: the user namespace and the IETF namespace. Names /// in the user namespace have the form `name@example.org` and /// their meaning is defined by the owner of the domain. The /// meaning of the notation `name@example.org`, for instance, is /// defined by whoever controls `example.org`. Names in the IETF /// namespace do not contain an `@` and are managed by IANA. See /// [Section 5.2.3.24 of RFC 9580] for details. /// /// [Section 5.2.3.24 of RFC 9580]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.24 /// /// This returns the values of all instances of the Notation Data /// subpacket with the specified name in the hashed subpacket area. // name needs 'a, because the closure outlives the function call. pub fn notation<'a, N>(&'a self, name: N) -> impl Iterator<Item=&'a [u8]> + Send + Sync where N: 'a + AsRef<str> + Send + Sync { self.notation_data() .filter_map(move |n| { if n.name == name.as_ref() { Some(&n.value[..]) } else { None } }) } /// Returns the value of the Preferred Symmetric Algorithms /// subpacket. /// /// A [Preferred Symmetric Algorithms subpacket] lists what /// symmetric algorithms the user prefers. When encrypting a /// message for a recipient, the OpenPGP implementation should not /// use an algorithm that is not on this list. /// /// [Preferred Symmetric Algorithms subpacket]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.14 /// /// This subpacket is a type of preference. When looking up a /// preference, an OpenPGP implementation should first look for /// the subpacket on the binding signature of the User ID or the /// User Attribute used to locate the certificate (or the primary /// User ID, if it was addressed by Key ID or fingerprint). If /// the binding signature doesn't contain the subpacket, then the /// direct key signature should be checked. See the /// [`Preferences`] trait for details. /// /// Unless addressing different User IDs really should result in /// different behavior, it is best to only set this preference on /// the direct key signature. This guarantees that even if some /// or all User IDs are stripped, the behavior remains consistent. /// /// [`Preferences`]: crate::cert::Preferences /// /// If the subpacket is not present in the hashed subpacket area, /// this returns `None`. /// /// Note: if the signature contains multiple instances of this /// subpacket in the hashed subpacket area, the last one is /// returned. pub fn preferred_symmetric_algorithms(&self) -> Option<&[SymmetricAlgorithm]> { // array of one-octet values if let Some(sb) = self.subpacket( SubpacketTag::PreferredSymmetricAlgorithms) { if let SubpacketValue::PreferredSymmetricAlgorithms(v) = &sb.value { Some(v) } else { None } } else { None } } /// Returns the value of the Preferred Hash Algorithms subpacket. /// /// A [Preferred Hash Algorithms subpacket] lists what hash /// algorithms the user prefers. When signing a message that /// should be verified by a particular recipient, the OpenPGP /// implementation should not use an algorithm that is not on this /// list. /// /// [Preferred Hash Algorithms subpacket]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.16 /// /// This subpacket is a type of preference. When looking up a /// preference, an OpenPGP implementation should first look for /// the subpacket on the binding signature of the User ID or the /// User Attribute used to locate the certificate (or the primary /// User ID, if it was addressed by Key ID or fingerprint). If /// the binding signature doesn't contain the subpacket, then the /// direct key signature should be checked. See the /// [`Preferences`] trait for details. /// /// Unless addressing different User IDs really should result in /// different behavior, it is best to only set this preference on /// the direct key signature. This guarantees that even if some /// or all User IDs are stripped, the behavior remains consistent. /// /// [`Preferences`]: crate::cert::Preferences /// /// If the subpacket is not present in the hashed subpacket area, /// this returns `None`. /// /// Note: if the signature contains multiple instances of this /// subpacket in the hashed subpacket area, the last one is /// returned. pub fn preferred_hash_algorithms(&self) -> Option<&[HashAlgorithm]> { // array of one-octet values if let Some(sb) = self.subpacket( SubpacketTag::PreferredHashAlgorithms) { if let SubpacketValue::PreferredHashAlgorithms(v) = &sb.value { Some(v) } else { None } } else { None } } /// Returns the value of the Preferred Compression Algorithms /// subpacket. /// /// A [Preferred Compression Algorithms subpacket] lists what /// compression algorithms the user prefers. When compressing a /// message for a recipient, the OpenPGP implementation should not /// use an algorithm that is not on the list. /// /// [Preferred Compression Algorithms subpacket]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.17 /// /// This subpacket is a type of preference. When looking up a /// preference, an OpenPGP implementation should first for the /// subpacket on the binding signature of the User ID or the User /// Attribute used to locate the certificate (or the primary User /// ID, if it was addressed by Key ID or fingerprint). If the /// binding signature doesn't contain the subpacket, then the /// direct key signature should be checked. See the /// [`Preferences`] trait for details. /// /// Unless addressing different User IDs really should result in /// different behavior, it is best to only set this preference on /// the direct key signature. This guarantees that even if some /// or all User IDs are stripped, the behavior remains consistent. /// /// [`Preferences`]: crate::cert::Preferences /// /// If the subpacket is not present in the hashed subpacket area, /// this returns `None`. /// /// Note: if the signature contains multiple instances of this /// subpacket in the hashed subpacket area, the last one is /// returned. pub fn preferred_compression_algorithms(&self) -> Option<&[CompressionAlgorithm]> { // array of one-octet values if let Some(sb) = self.subpacket( SubpacketTag::PreferredCompressionAlgorithms) { if let SubpacketValue::PreferredCompressionAlgorithms(v) = &sb.value { Some(v) } else { None } } else { None } } /// Returns the value of the Preferred AEAD Ciphersuites subpacket. /// /// The [Preferred AEAD Ciphersuites subpacket] indicates what /// AEAD ciphersuites (i.e. pairs of symmetric algorithm and AEAD /// algorithm) the key holder prefers ordered by preference. If /// this is set, then the SEIPDv2 feature flag should in the /// [Features subpacket] should also be set. /// /// [Preferred AEAD Ciphersuites subpacket]: https://www.rfc-editor.org/rfc/rfc9580.html#name-preferred-aead-ciphersuites /// [Features subpacket]: https://www.rfc-editor.org/rfc/rfc9580.html#features-subpacket /// /// This subpacket is a type of preference. When looking up a /// preference, an OpenPGP implementation should first look for /// the subpacket on the binding signature of the User ID or the /// User Attribute used to locate the certificate (or the primary /// User ID, if it was addressed by Key ID or fingerprint). If /// the binding signature doesn't contain the subpacket, then the /// direct key signature should be checked. See the /// [`Preferences`] trait for details. /// /// Unless addressing different User IDs really should result in /// different behavior, it is best to only set this preference on /// the direct key signature. This guarantees that even if some /// or all User IDs are stripped, the behavior remains consistent. /// /// [`Preferences`]: crate::cert::Preferences /// /// If the subpacket is not present in the hashed subpacket area, /// this returns `None`. /// /// Note: if the signature contains multiple instances of this /// subpacket in the hashed subpacket area, the last one is /// returned. pub fn preferred_aead_ciphersuites(&self) -> Option<&[(SymmetricAlgorithm, AEADAlgorithm)]> { if let Some(sb) = self.subpacket( SubpacketTag::PreferredAEADCiphersuites) { if let SubpacketValue::PreferredAEADCiphersuites(v) = &sb.value { Some(v) } else { None } } else { None } } /// Returns the value of the Key Server Preferences subpacket. /// /// The [Key Server Preferences subpacket] indicates to key /// servers how they should handle the certificate. /// /// [Key Server Preferences subpacket]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.25 /// /// This subpacket is a type of preference. When looking up a /// preference, an OpenPGP implementation should first for the /// subpacket on the binding signature of the User ID or the User /// Attribute used to locate the certificate (or the primary User /// ID, if it was addressed by Key ID or fingerprint). If the /// binding signature doesn't contain the subpacket, then the /// direct key signature should be checked. See the /// [`Preferences`] trait for details. /// /// Unless addressing different User IDs really should result in /// different behavior, it is best to only set this preference on /// the direct key signature. This guarantees that even if some /// or all User IDs are stripped, the behavior remains consistent. /// /// [`Preferences`]: crate::cert::Preferences /// /// If the subpacket is not present in the hashed subpacket area, /// this returns `None`. /// /// Note: if the signature contains multiple instances of this /// subpacket in the hashed subpacket area, the last one is /// returned. pub fn key_server_preferences(&self) -> Option<KeyServerPreferences> { // N octets of flags if let Some(sb) = self.subpacket(SubpacketTag::KeyServerPreferences) { if let SubpacketValue::KeyServerPreferences(v) = &sb.value { Some(v.clone()) } else { None } } else { None } } /// Returns the value of the Preferred Key Server subpacket. /// /// The [Preferred Key Server subpacket] contains a link to a key /// server where the certificate holder plans to publish updates /// to their certificate (e.g., extensions to the expiration time, /// new subkeys, revocation certificates). /// /// The Preferred Key Server subpacket should be handled /// cautiously, because it can be used by a certificate holder to /// track communication partners. /// /// [Preferred Key Server subpacket]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.26 /// /// This subpacket is a type of preference. When looking up a /// preference, an OpenPGP implementation should first look for /// the subpacket on the binding signature of the User ID or the /// User Attribute used to locate the certificate (or the primary /// User ID, if it was addressed by Key ID or fingerprint). If /// the binding signature doesn't contain the subpacket, then the /// direct key signature should be checked. See the /// [`Preferences`] trait for details. /// /// Unless addressing different User IDs really should result in /// different behavior, it is best to only set this preference on /// the direct key signature. This guarantees that even if some /// or all User IDs are stripped, the behavior remains consistent. /// /// [`Preferences`]: crate::cert::Preferences /// /// If the subpacket is not present in the hashed subpacket area, /// this returns `None`. /// /// Note: if the signature contains multiple instances of this /// subpacket in the hashed subpacket area, the last one is /// returned. pub fn preferred_key_server(&self) -> Option<&[u8]> { // String if let Some(sb) = self.subpacket(SubpacketTag::PreferredKeyServer) { if let SubpacketValue::PreferredKeyServer(v) = &sb.value { Some(v) } else { None } } else { None } } /// Returns the value of the Policy URI subpacket. /// /// The [Policy URI subpacket] contains a link to a policy document, /// which contains information about the conditions under which /// the signature was made. /// /// [Policy URI subpacket]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.28 /// /// This subpacket is a type of preference. When looking up a /// preference, an OpenPGP implementation should first look for /// the subpacket on the binding signature of the User ID or the /// User Attribute used to locate the certificate (or the primary /// User ID, if it was addressed by Key ID or fingerprint). If /// the binding signature doesn't contain the subpacket, then the /// direct key signature should be checked. See the /// [`Preferences`] trait for details. /// /// Unless addressing different User IDs really should result in /// different behavior, it is best to only set this preference on /// the direct key signature. This guarantees that even if some /// or all User IDs are stripped, the behavior remains consistent. /// /// [`Preferences`]: crate::cert::Preferences /// /// If the subpacket is not present in the hashed subpacket area, /// this returns `None`. /// /// Note: if the signature contains multiple instances of this /// subpacket in the hashed subpacket area, the last one is /// returned. pub fn policy_uri(&self) -> Option<&[u8]> { // String if let Some(sb) = self.subpacket(SubpacketTag::PolicyURI) { if let SubpacketValue::PolicyURI(v) = &sb.value { Some(v) } else { None } } else { None } } /// Returns the value of the Primary UserID subpacket. /// /// The [Primary User ID subpacket] indicates whether the /// associated User ID or User Attribute should be considered the /// primary User ID. It is possible that this is set on multiple /// User IDs. See the documentation for /// [`ValidCert::primary_userid`] for an explanation of how /// Sequoia resolves this ambiguity. /// /// [Primary User ID subpacket]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.27 /// [`ValidCert::primary_userid`]: crate::cert::ValidCert::primary_userid() /// /// If the subpacket is not present in the hashed subpacket area, /// this returns `None`. /// /// Note: if the signature contains multiple instances of this /// subpacket in the hashed subpacket area, the last one is /// returned. pub fn primary_userid(&self) -> Option<bool> { // 1 octet, Boolean if let Some(sb) = self.subpacket(SubpacketTag::PrimaryUserID) { if let SubpacketValue::PrimaryUserID(v) = sb.value { Some(v) } else { None } } else { None } } /// Returns the value of the Key Flags subpacket. /// /// The [Key Flags subpacket] describes a key's capabilities /// (certification capable, signing capable, etc.). In the case /// of subkeys, the Key Flags are located on the subkey's binding /// signature. For primary keys, locating the correct Key Flags /// subpacket is more complex: First, the primary User ID is /// consulted. If the primary User ID contains a Key Flags /// subpacket, that is used. Otherwise, any direct key signature /// is considered. If that still doesn't contain a Key Flags /// packet, then the primary key should be assumed to be /// certification capable. /// /// [Key Flags subpacket]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.29 /// /// If the subpacket is not present in the hashed subpacket area, /// this returns `None`. /// /// Note: if the signature contains multiple instances of this /// subpacket in the hashed subpacket area, the last one is /// returned. pub fn key_flags(&self) -> Option<KeyFlags> { // N octets of flags if let Some(sb) = self.subpacket(SubpacketTag::KeyFlags) { if let SubpacketValue::KeyFlags(v) = &sb.value { Some(v.clone()) } else { None } } else { None } } /// Returns the value of the Signer's UserID subpacket. /// /// The [Signer's User ID subpacket] indicates, which User ID made /// the signature. This is useful when a key has multiple User /// IDs, which correspond to different roles. For instance, it is /// not uncommon to use the same certificate in private as well as /// for a club. /// /// [Signer's User ID subpacket]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.30 /// /// If the subpacket is not present in the hashed subpacket area, /// this returns `None`. /// /// Note: if the signature contains multiple instances of this /// subpacket in the hashed subpacket area, the last one is /// returned. pub fn signers_user_id(&self) -> Option<&[u8]> { // String if let Some(sb) = self.subpacket(SubpacketTag::SignersUserID) { if let SubpacketValue::SignersUserID(v) = &sb.value { Some(v) } else { None } } else { None } } /// Returns the value of the Reason for Revocation subpacket. /// /// The [Reason For Revocation subpacket] indicates why a key, /// User ID, or User Attribute is being revoked. It includes both /// a machine-readable code, and a human-readable string. The /// code is essential as it indicates to the OpenPGP /// implementation that reads the certificate whether the key was /// compromised (a hard revocation), or is no longer used (a soft /// revocation). In the former case, the OpenPGP implementation /// must conservatively consider all past signatures as suspect /// whereas in the latter case, past signatures can still be /// considered valid. /// /// [Reason For Revocation subpacket]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.31 /// /// If the subpacket is not present in the hashed subpacket area, /// this returns `None`. /// /// Note: if the signature contains multiple instances of this /// subpacket in the hashed subpacket area, the last one is /// returned. pub fn reason_for_revocation(&self) -> Option<(ReasonForRevocation, &[u8])> { // 1 octet of revocation code, N octets of reason string if let Some(sb) = self.subpacket(SubpacketTag::ReasonForRevocation) { if let SubpacketValue::ReasonForRevocation { code, reason, } = &sb.value { Some((*code, reason)) } else { None } } else { None } } /// Returns the value of the Features subpacket. /// /// A [Features subpacket] lists what OpenPGP features the user /// wants to use. When creating a message, features that the /// intended recipients do not support should not be used. /// However, because this information is rarely held up to date in /// practice, this information is only advisory, and /// implementations are allowed to infer what features the /// recipients support from contextual clues, e.g., their past /// behavior. /// /// [Features subpacket]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.32 /// [features]: crate::types::Features /// /// This subpacket is a type of preference. When looking up a /// preference, an OpenPGP implementation should first look for /// the subpacket on the binding signature of the User ID or the /// User Attribute used to locate the certificate (or the primary /// User ID, if it was addressed by Key ID or fingerprint). If /// the binding signature doesn't contain the subpacket, then the /// direct key signature should be checked. See the /// [`Preferences`] trait for details. /// /// Unless addressing different User IDs really should result in /// different behavior, it is best to only set this preference on /// the direct key signature. This guarantees that even if some /// or all User IDs are stripped, the behavior remains consistent. /// /// [`Preferences`]: crate::cert::Preferences /// /// If the subpacket is not present in the hashed subpacket area, /// this returns `None`. /// /// Note: if the signature contains multiple instances of this /// subpacket in the hashed subpacket area, the last one is /// returned. pub fn features(&self) -> Option<Features> { // N octets of flags if let Some(sb) = self.subpacket(SubpacketTag::Features) { if let SubpacketValue::Features(v) = &sb.value { Some(v.clone()) } else { None } } else { None } } /// Returns the value of the Signature Target subpacket. /// /// The [Signature Target subpacket] is used to identify the target /// of a signature. This is used when revoking a signature, and /// by timestamp signatures. It contains a hash of the target /// signature. /// /// [Signature Target subpacket]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.33 /// /// If the subpacket is not present in the hashed subpacket area, /// this returns `None`. /// /// Note: if the signature contains multiple instances of this /// subpacket in the hashed subpacket area, the last one is /// returned. pub fn signature_target(&self) -> Option<(PublicKeyAlgorithm, HashAlgorithm, &[u8])> { // 1 octet public-key algorithm, 1 octet hash algorithm, N // octets hash if let Some(sb) = self.subpacket(SubpacketTag::SignatureTarget) { if let SubpacketValue::SignatureTarget { pk_algo, hash_algo, digest, } = &sb.value { Some((*pk_algo, *hash_algo, digest)) } else { None } } else { None } } /// Returns references to all Embedded Signature subpackets. /// /// The [Embedded Signature subpacket] is normally used to hold a /// [Primary Key Binding signature], which binds a /// signing-capable, authentication-capable, or /// certification-capable subkey to the primary key. Since this /// information is self-authenticating, it is usually stored in /// the unhashed subpacket area. /// /// [Embedded Signature subpacket]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.34 /// [Primary Key Binding signature]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.1 /// /// If the subpacket is not present in the hashed subpacket area /// or in the unhashed subpacket area, this returns `None`. /// /// Note: if the signature contains multiple instances of this /// subpacket in the hashed subpacket area, the last one is /// returned. Otherwise, the last one is returned from the /// unhashed subpacket area. pub fn embedded_signatures(&self) -> impl Iterator<Item = &Signature> + Send + Sync { self.subpackets(SubpacketTag::EmbeddedSignature).map(|sb| { if let SubpacketValue::EmbeddedSignature(v) = &sb.value { v } else { unreachable!( "subpackets(EmbeddedSignature) returns EmbeddedSignatures" ); } }) } /// Returns mutable references to all Embedded Signature subpackets. /// /// The [Embedded Signature subpacket] is normally used to hold a /// [Primary Key Binding signature], which binds a /// signing-capable, authentication-capable, or /// certification-capable subkey to the primary key. Since this /// information is self-authenticating, it is usually stored in /// the unhashed subpacket area. /// /// [Embedded Signature subpacket]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.34 /// [Primary Key Binding signature]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.1 /// /// If the subpacket is not present in the hashed subpacket area /// or in the unhashed subpacket area, this returns `None`. /// /// Note: if the signature contains multiple instances of this /// subpacket in the hashed subpacket area, the last one is /// returned. Otherwise, the last one is returned from the /// unhashed subpacket area. pub fn embedded_signatures_mut(&mut self) -> impl Iterator<Item = &mut Signature> + Send + Sync { self.subpackets_mut(SubpacketTag::EmbeddedSignature).map(|sb| { if let SubpacketValue::EmbeddedSignature(v) = &mut sb.value { v } else { unreachable!( "subpackets_mut(EmbeddedSignature) returns EmbeddedSignatures" ); } }) } /// Returns the intended recipients. /// /// The [Intended Recipient subpacket] holds the fingerprint of a /// certificate. /// /// [Intended Recipient subpacket]: https://www.rfc-editor.org/rfc/rfc9580.html#name-intended-recipient-fingerpr /// /// When signing a message, the message should include one such /// subpacket for each intended recipient. Note: not all messages /// have intended recipients. For instance, when signing an open /// letter, or a software release, the message is intended for /// anyone. /// /// When processing a signature, the application should ensure /// that if there are any such subpackets, then one of the /// subpackets identifies the recipient's certificate (or user /// signed the message). If this is not the case, then an /// attacker may have taken the message out of its original /// context. For instance, if Alice sends a signed email to Bob, /// with the content: "I agree to the contract", and Bob forwards /// that message to Carol, then Carol may think that Alice agreed /// to a contract with her if the signature appears to be valid! /// By adding an intended recipient, it is possible for Carol's /// mail client to warn her that although Alice signed the /// message, the content was intended for Bob and not for her. /// /// This returns all instances of the Intended Recipient subpacket /// in the hashed subpacket area. pub fn intended_recipients(&self) -> impl Iterator<Item=&Fingerprint> + Send + Sync { self.subpackets(SubpacketTag::IntendedRecipient) .map(|sb| { match sb.value() { SubpacketValue::IntendedRecipient(ref fp) => fp, _ => unreachable!(), } }) } /// Returns the digests of approved certifications. /// /// This feature is [experimental](crate#experimental-features). /// /// Allows the certificate holder to attest to third party /// certifications, allowing them to be distributed with the /// certificate. This can be used to address certificate flooding /// concerns. /// /// Note: The maximum size of the hashed signature subpacket area /// constrains the number of attestations that can be stored in a /// signature. If the certificate holder approved of more /// certifications, the digests are split across multiple approved /// certification key signatures with the same creation time. /// /// The standard strongly suggests that the digests should be /// sorted. However, this function returns the digests in the /// order they are stored in the subpacket, which may not be /// sorted. /// /// To address both issues, collect all digests from all approved /// key signatures with the most recent creation time into a data /// structure that allows efficient lookups, such as [`HashSet`] /// or [`BTreeSet`]. /// /// See [Section 2.2 of draft-dkg-openpgp-1pa3pc-02] for details. /// /// [`HashSet`]: std::collections::HashSet /// [`BTreeSet`]: std::collections::BTreeSet /// [Section 2.2 of draft-dkg-openpgp-1pa3pc-02]: https://www.ietf.org/archive/id/draft-dkg-openpgp-1pa3pc-02.html#approved-certifications-subpacket pub fn approved_certifications(&self) -> Result<impl Iterator<Item=&[u8]> + Send + Sync> { if self.hashed_area() .subpackets(SubpacketTag::ApprovedCertifications).count() > 1 || self.unhashed_area() .subpackets(SubpacketTag::ApprovedCertifications).count() != 0 { return Err(Error::BadSignature( "Wrong number of approved certifications subpackets".into()) .into()); } Ok(self.subpackets(SubpacketTag::ApprovedCertifications) .flat_map(|sb| { match sb.value() { SubpacketValue::ApprovedCertifications(digests) => digests.iter().map(|d| d.as_ref()), _ => unreachable!(), } })) } } impl TryFrom<Signature> for Signature4 { type Error = anyhow::Error; fn try_from(sig: Signature) -> Result<Self> { match sig { Signature::V4(sig) => Ok(sig), sig => Err( Error::InvalidArgument( format!( "Got a v{}, require a v4 signature", sig.version())) .into()), } } } impl Deref for Signature4 { type Target = signature::SignatureFields; fn deref(&self) -> &Self::Target { &self.fields } } impl DerefMut for Signature4 { fn deref_mut(&mut self) -> &mut Self::Target { &mut self.fields } } impl signature::SignatureBuilder { /// Modifies the unhashed subpacket area. /// /// This method provides a builder-style interface for modifying /// the unhashed subpacket area. /// /// Normally, to modify a subpacket area in a non-standard way /// (that is, when there are no subpacket-specific function like /// [`SignatureBuilder::set_signature_validity_period`] that /// implement the required functionality), you need to do /// something like the following: /// /// [`SignatureBuilder::set_signature_validity_period`]: super::SignatureBuilder::set_signature_validity_period() /// /// ``` /// # use sequoia_openpgp as openpgp; /// # use openpgp::types::Curve; /// # use openpgp::cert::prelude::*; /// # use openpgp::packet::prelude::*; /// # use openpgp::packet::signature::subpacket::{ /// # Subpacket, /// # SubpacketTag, /// # SubpacketValue, /// # }; /// # use openpgp::types::SignatureType; /// # /// # fn main() -> openpgp::Result<()> { /// # /// # let key: Key<key::SecretParts, key::PrimaryRole> /// # = Key6::generate_ecc(true, Curve::Ed25519)?.into(); /// # let mut signer = key.into_keypair()?; /// # let msg = b"Hello, World"; /// # /// let mut builder = SignatureBuilder::new(SignatureType::Binary) /// // Build up the signature. /// ; /// builder.unhashed_area_mut().add(Subpacket::new( /// SubpacketValue::Unknown { /// tag: SubpacketTag::Private(61), /// body: [0x6D, 0x6F, 0x6F].to_vec(), /// }, /// true)?)?; /// let sig = builder.sign_message(&mut signer, msg)?; /// # let mut sig = sig; /// # sig.verify_message(signer.public(), msg)?; /// # Ok(()) } /// ``` /// /// This is necessary, because modifying the subpacket area /// doesn't follow the builder pattern like the surrounding code. /// Using this function, you can instead do: /// /// ``` /// # use sequoia_openpgp as openpgp; /// # use openpgp::cert::prelude::*; /// # use openpgp::packet::prelude::*; /// # use openpgp::packet::signature::subpacket::{ /// # Subpacket, /// # SubpacketTag, /// # SubpacketValue, /// # }; /// # use openpgp::types::Curve; /// # use openpgp::types::SignatureType; /// # /// # fn main() -> openpgp::Result<()> { /// # /// # let key: Key<key::SecretParts, key::PrimaryRole> /// # = Key6::generate_ecc(true, Curve::Ed25519)?.into(); /// # let mut signer = key.into_keypair()?; /// # let msg = b"Hello, World"; /// # /// let sig = SignatureBuilder::new(SignatureType::Binary) /// // Call some setters. /// .modify_unhashed_area(|mut a| { /// a.add(Subpacket::new( /// SubpacketValue::Unknown { /// tag: SubpacketTag::Private(61), /// body: [0x6D, 0x6F, 0x6F].to_vec(), /// }, /// true)?); /// Ok(a) /// })? /// .sign_message(&mut signer, msg)?; /// # let mut sig = sig; /// # sig.verify_message(signer.public(), msg)?; /// # Ok(()) } /// ``` /// /// If you are only interested in modifying an existing /// signature's unhashed area, it may be better to simply modify /// the signature in place using /// [`SignatureBuilder::modify_unhashed_area`] rather than to create a /// new signature, because modifying the unhashed area doesn't /// invalidate any existing signature. /// /// [`SignatureBuilder::modify_unhashed_area`]: super::SignatureBuilder::modify_unhashed_area /// /// # Examples /// /// Create a signature with a custom, non-critical subpacket in /// the unhashed area: /// /// ``` /// use sequoia_openpgp as openpgp; /// use openpgp::cert::prelude::*; /// use openpgp::packet::prelude::*; /// use openpgp::packet::signature::subpacket::{ /// Subpacket, /// SubpacketTag, /// SubpacketValue, /// }; /// use openpgp::types::SignatureType; /// # /// # fn main() -> openpgp::Result<()> { /// /// let (cert, _) = /// CertBuilder::general_purpose(Some("alice@example.org")) /// .generate()?; /// let mut signer = cert.primary_key().key().clone().parts_into_secret()?.into_keypair()?; /// /// let msg = b"Hello, World"; /// /// let sig = SignatureBuilder::new(SignatureType::Binary) /// // Call some setters. /// .modify_unhashed_area(|mut a| { /// a.add(Subpacket::new( /// SubpacketValue::Unknown { /// tag: SubpacketTag::Private(61), /// body: [0x6D, 0x6F, 0x6F].to_vec(), /// }, /// true)?); /// Ok(a) /// })? /// .sign_message(&mut signer, msg)?; /// # let mut sig = sig; /// # sig.verify_message(signer.public(), msg)?; /// # Ok(()) } /// ``` pub fn modify_unhashed_area<F>(mut self, f: F) -> Result<Self> where F: FnOnce(SubpacketArea) -> Result<SubpacketArea> { self.fields.subpackets.unhashed_area = f(self.fields.subpackets.unhashed_area)?; Ok(self) } /// Modifies the hashed subpacket area. /// /// This method provides a builder-style interface for modifying /// the hashed subpacket area. /// /// Normally, to modify a subpacket area in a non-standard way /// (that is, when there are no subpacket-specific function like /// [`SignatureBuilder::set_signature_validity_period`] that /// implement the required functionality), you need to do /// something like the following: /// /// [`SignatureBuilder::set_signature_validity_period`]: super::SignatureBuilder::set_signature_validity_period() /// /// ``` /// # use sequoia_openpgp as openpgp; /// # use openpgp::types::Curve; /// # use openpgp::cert::prelude::*; /// # use openpgp::packet::prelude::*; /// # use openpgp::packet::signature::subpacket::{ /// # Subpacket, /// # SubpacketTag, /// # SubpacketValue, /// # }; /// # use openpgp::types::SignatureType; /// # /// # fn main() -> openpgp::Result<()> { /// # /// # let key: Key<key::SecretParts, key::PrimaryRole> /// # = Key6::generate_ecc(true, Curve::Ed25519)?.into(); /// # let mut signer = key.into_keypair()?; /// # let msg = b"Hello, World"; /// # /// let mut builder = SignatureBuilder::new(SignatureType::Binary) /// // Build up the signature. /// ; /// builder.hashed_area_mut().add(Subpacket::new( /// SubpacketValue::Unknown { /// tag: SubpacketTag::Private(61), /// body: [0x6D, 0x6F, 0x6F].to_vec(), /// }, /// true)?)?; /// let sig = builder.sign_message(&mut signer, msg)?; /// # let mut sig = sig; /// # sig.verify_message(signer.public(), msg)?; /// # Ok(()) } /// ``` /// /// This is necessary, because modifying the subpacket area /// doesn't follow the builder pattern like the surrounding code. /// Using this function, you can instead do: /// /// ``` /// # use sequoia_openpgp as openpgp; /// # use openpgp::cert::prelude::*; /// # use openpgp::packet::prelude::*; /// # use openpgp::packet::signature::subpacket::{ /// # Subpacket, /// # SubpacketTag, /// # SubpacketValue, /// # }; /// # use openpgp::types::Curve; /// # use openpgp::types::SignatureType; /// # /// # fn main() -> openpgp::Result<()> { /// # /// # let key: Key<key::SecretParts, key::PrimaryRole> /// # = Key6::generate_ecc(true, Curve::Ed25519)?.into(); /// # let mut signer = key.into_keypair()?; /// # let msg = b"Hello, World"; /// # /// let sig = SignatureBuilder::new(SignatureType::Binary) /// // Call some setters. /// .modify_hashed_area(|mut a| { /// a.add(Subpacket::new( /// SubpacketValue::Unknown { /// tag: SubpacketTag::Private(61), /// body: [0x6D, 0x6F, 0x6F].to_vec(), /// }, /// true)?); /// Ok(a) /// })? /// .sign_message(&mut signer, msg)?; /// # let mut sig = sig; /// # sig.verify_message(signer.public(), msg)?; /// # Ok(()) } /// ``` /// /// # Examples /// /// Add a critical, custom subpacket to a certificate's direct key /// signature: /// /// ``` /// use sequoia_openpgp as openpgp; /// use openpgp::cert::prelude::*; /// use openpgp::packet::prelude::*; /// use openpgp::packet::signature::subpacket::{ /// Subpacket, /// SubpacketTag, /// SubpacketValue, /// }; /// use openpgp::policy::StandardPolicy; /// use openpgp::types::Features; /// /// # fn main() -> openpgp::Result<()> { /// let p = &StandardPolicy::new(); /// /// let (cert, _) = CertBuilder::new().add_userid("Alice").generate()?; /// /// // Derive a signer (the primary key is always certification capable). /// let pk = cert.primary_key().key(); /// let mut signer = pk.clone().parts_into_secret()?.into_keypair()?; /// /// let vc = cert.with_policy(p, None)?; /// /// let sig = vc.direct_key_signature().expect("direct key signature"); /// let sig = SignatureBuilder::from(sig.clone()) /// .modify_hashed_area(|mut a| { /// a.add(Subpacket::new( /// SubpacketValue::Unknown { /// tag: SubpacketTag::Private(61), /// body: [0x6D, 0x6F, 0x6F].to_vec(), /// }, /// true)?)?; /// Ok(a) /// })? /// .sign_direct_key(&mut signer, None)?; /// /// // Merge in the new signature. /// let cert = cert.insert_packets(sig)?.0; /// # assert_eq!(cert.bad_signatures().count(), 0); /// # Ok(()) /// # } /// ``` /// /// Update a certificate's feature set by updating the `Features` /// subpacket on any direct key signature, and any User ID binding /// signatures: /// /// ``` /// use sequoia_openpgp as openpgp; /// use openpgp::cert::prelude::*; /// use openpgp::packet::prelude::*; /// use openpgp::packet::signature::subpacket::{Subpacket, SubpacketValue}; /// use openpgp::policy::StandardPolicy; /// use openpgp::types::Features; /// /// # fn main() -> openpgp::Result<()> { /// let p = &StandardPolicy::new(); /// /// let (cert, _) = CertBuilder::new().add_userid("Alice").generate()?; /// /// // Derive a signer (the primary key is always certification capable). /// let pk = cert.primary_key().key(); /// let mut signer = pk.clone().parts_into_secret()?.into_keypair()?; /// /// let mut sigs = Vec::new(); /// /// let vc = cert.with_policy(p, None)?; /// /// if let Ok(sig) = vc.direct_key_signature() { /// sigs.push(SignatureBuilder::from(sig.clone()) /// .modify_hashed_area(|mut a| { /// a.replace(Subpacket::new( /// SubpacketValue::Features(Features::sequoia().set(10)), /// false)?)?; /// Ok(a) /// })? /// // Update the direct key signature. /// .sign_direct_key(&mut signer, Some(pk))?); /// } /// /// for ua in vc.userids() { /// sigs.push(SignatureBuilder::from(ua.binding_signature().clone()) /// .modify_hashed_area(|mut a| { /// a.replace(Subpacket::new( /// SubpacketValue::Features(Features::sequoia().set(10)), /// false)?)?; /// Ok(a) /// })? /// // Update the binding signature. /// .sign_userid_binding(&mut signer, pk, ua.userid())?); /// } /// /// // Merge in the new signatures. /// let cert = cert.insert_packets(sigs)?.0; /// # assert_eq!(cert.bad_signatures().count(), 0); /// # Ok(()) /// # } /// ``` pub fn modify_hashed_area<F>(mut self, f: F) -> Result<Self> where F: FnOnce(SubpacketArea) -> Result<SubpacketArea> { self.fields.subpackets.hashed_area = f(self.fields.subpackets.hashed_area)?; Ok(self) } /// Sets the Signature Creation Time subpacket. /// /// Adds a [Signature Creation Time subpacket] to the hashed /// subpacket area. This function first removes any Signature /// Creation Time subpacket from the hashed subpacket area. /// /// The Signature Creation Time subpacket specifies when the /// signature was created. According to the standard, all /// signatures must include a Signature Creation Time subpacket in /// the signature's hashed area. This doesn't mean that the time /// stamp is correct: the issuer can always forge it. /// /// When creating a signature using a SignatureBuilder or the /// [streaming `Signer`], it is not necessary to explicitly set /// this subpacket: those functions automatically set the /// signature creation time, if it has not been set explicitly. /// /// [Signature Creation Time subpacket]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.11 /// [streaming `Signer`]: crate::serialize::stream::Signer /// /// # Examples /// /// Create a backdated signature: /// /// ``` /// use sequoia_openpgp as openpgp; /// use openpgp::cert::prelude::*; /// use openpgp::packet::signature::SignatureBuilder; /// use openpgp::types::SignatureType; /// /// # fn main() -> openpgp::Result<()> { /// # /// # let (cert, _) = /// # CertBuilder::general_purpose(Some("alice@example.org")) /// # // We also need to backdate the certificate. /// # .set_creation_time( /// # std::time::SystemTime::now() /// # - std::time::Duration::new(2 * 24 * 60 * 60, 0)) /// # .generate()?; /// # let mut signer = cert.primary_key().key().clone() /// # .parts_into_secret()?.into_keypair()?; /// let msg = "hiermit kündige ich den mit Ihnen bestehenden Vertrag fristgerecht."; /// /// let mut sig = SignatureBuilder::new(SignatureType::Binary) /// .set_signature_creation_time( /// std::time::SystemTime::now() /// - std::time::Duration::new(24 * 60 * 60, 0))? /// .sign_message(&mut signer, msg)?; /// /// assert!(sig.verify_message(signer.public(), msg).is_ok()); /// # Ok(()) } /// ``` pub fn set_signature_creation_time<T>(mut self, creation_time: T) -> Result<Self> where T: Into<time::SystemTime> { self.hashed_area.replace(Subpacket::new( SubpacketValue::SignatureCreationTime( creation_time.into().try_into()?), true)?)?; self.overrode_creation_time = true; Ok(self) } /// Causes the builder to use an existing signature creation time /// subpacket. /// /// When converting a [`Signature`] to a `SignatureBuilder`, the /// [Signature Creation Time subpacket] is removed from the hashed /// area, and saved internally. When creating the signature, a /// Signature Creation Time subpacket with the current time is /// normally added to the hashed area. Calling this function /// instead causes the signature generation code to use the cached /// `Signature Creation Time` subpacket. /// /// [`Signature`]: super::Signature /// [Signature Creation Time subpacket]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.11 /// /// This function returns an error if there is no cached /// `Signature Creation Time` subpacket. /// /// # Examples /// /// Alice signs a message. Shortly thereafter, Bob signs the /// message using a nearly identical Signature packet: /// /// ``` /// use sequoia_openpgp as openpgp; /// # use openpgp::cert::prelude::*; /// use openpgp::packet::signature::SignatureBuilder; /// use openpgp::types::SignatureType; /// /// # fn main() -> openpgp::Result<()> { /// # /// # let (alice, _) = /// # CertBuilder::general_purpose(Some("alice@example.org")) /// # .generate()?; /// # let mut alices_signer = alice.primary_key().key().clone() /// # .parts_into_secret()?.into_keypair()?; /// # let (bob, _) = /// # CertBuilder::general_purpose(Some("bob@example.org")) /// # .generate()?; /// # let mut bobs_signer = bob.primary_key().key().clone() /// # .parts_into_secret()?.into_keypair()?; /// let msg = "Version 489 of Foo has the SHA256 sum e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"; /// /// let siga = SignatureBuilder::new(SignatureType::Binary) /// .sign_message(&mut alices_signer, msg)?; /// let sigb = SignatureBuilder::from(siga.clone()) /// .preserve_signature_creation_time()? /// .sign_message(&mut bobs_signer, msg)?; /// # /// # let mut siga = siga; /// # let mut sigb = sigb; /// # assert!(siga.verify_message(alices_signer.public(), msg).is_ok()); /// # assert!(sigb.verify_message(bobs_signer.public(), msg).is_ok()); /// # assert_eq!(siga.signature_creation_time(), /// # sigb.signature_creation_time()); /// # Ok(()) } /// ``` pub fn preserve_signature_creation_time(self) -> Result<Self> { if let Some(t) = self.original_creation_time { self.set_signature_creation_time(t) } else { Err(Error::InvalidOperation( "Signature does not contain a Signature Creation Time subpacket".into()) .into()) } } /// Causes the builder to not output a Signature Creation Time /// subpacket. /// /// When creating a signature, a [Signature Creation Time /// subpacket] is added to the hashed area if one hasn't been /// added already. This function suppresses that behavior. /// /// [Signature Creation Time subpacket]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.11 /// /// [Section 5.2.3.11 of RFC 9580] says that the `Signature /// Creation Time` subpacket must be present in the hashed area. /// This function clears any `Signature Creation Time` subpackets /// from both the hashed area and the unhashed area, and causes /// the various `SignatureBuilder` finalizers to not emit a /// `Signature Creation Time` subpacket. This function should /// only be used for generating test data. /// /// [Section 5.2.3.11 of RFC 9580]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.11 /// /// # Examples /// /// Create a signature without a Signature Creation Time /// subpacket. As per the specification, Sequoia considers such /// signatures to be invalid: /// /// ``` /// use sequoia_openpgp as openpgp; /// use openpgp::cert::prelude::*; /// use openpgp::packet::signature::SignatureBuilder; /// # use openpgp::packet::signature::subpacket::SubpacketTag; /// use openpgp::types::SignatureType; /// /// # fn main() -> openpgp::Result<()> { /// # /// # let (cert, _) = /// # CertBuilder::general_purpose(Some("alice@example.org")) /// # .generate()?; /// # let mut signer = cert.primary_key().key().clone() /// # .parts_into_secret()?.into_keypair()?; /// let msg = "Some things are timeless."; /// /// let mut sig = SignatureBuilder::new(SignatureType::Binary) /// .suppress_signature_creation_time()? /// .sign_message(&mut signer, msg)?; /// /// assert!(sig.verify_message(signer.public(), msg).is_err()); /// # assert_eq!(sig /// # .hashed_area() /// # .iter() /// # .filter(|sp| sp.tag() == SubpacketTag::SignatureCreationTime) /// # .count(), /// # 0); /// # Ok(()) } /// ``` pub fn suppress_signature_creation_time(mut self) -> Result<Self> { self.hashed_area.remove_all(SubpacketTag::SignatureCreationTime); self.unhashed_area.remove_all(SubpacketTag::SignatureCreationTime); self.overrode_creation_time = true; Ok(self) } /// Returns the value of the Signature Expiration Time subpacket /// as an absolute time. /// /// A [Signature Expiration Time subpacket] specifies when the /// signature expires. The value stored is not an absolute time, /// but a duration, which is relative to the Signature's creation /// time. To better reflect the subpacket's name, this method /// returns the absolute expiry time, and the /// [`SubpacketAreas::signature_validity_period`] method returns /// the subpacket's raw value. /// /// [Signature Expiration Time subpacket]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.18 /// [`SubpacketAreas::signature_validity_period`]: SubpacketAreas::signature_validity_period() /// /// The Signature Expiration Time subpacket is different from the /// [Key Expiration Time subpacket], which is accessed using /// [`SubpacketAreas::key_validity_period`], and used specifies /// when an associated key expires. The difference is that in the /// former case, the signature itself expires, but in the latter /// case, only the associated key expires. This difference is /// critical: if a binding signature expires, then an OpenPGP /// implementation will still consider the associated key to be /// valid if there is another valid binding signature, even if it /// is older than the expired signature; if the active binding /// signature indicates that the key has expired, then OpenPGP /// implementations will not fall back to an older binding /// signature. /// /// [Key Expiration Time subpacket]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.13 /// [`SubpacketAreas::key_validity_period`]: SubpacketAreas::key_validity_period() /// /// There are several cases where having a signature expire is /// useful. Say Alice certifies Bob's certificate for /// `bob@example.org`. She can limit the lifetime of the /// certification to force her to reevaluate the certification /// shortly before it expires. For instance, is Bob still /// associated with `example.org`? Does she have reason to /// believe that his key has been compromised? Using an /// expiration is common in the X.509 ecosystem. For instance, /// [Let's Encrypt] issues certificates with 90-day lifetimes. /// /// [Let's Encrypt]: https://letsencrypt.org/2015/11/09/why-90-days.html /// /// Having signatures expire can also be useful when deploying /// software. For instance, you might have a service that /// installs an update if it has been signed by a trusted /// certificate. To prevent an adversary from coercing the /// service to install an older version, you could limit the /// signature's lifetime to just a few minutes. /// /// If the subpacket is not present in the hashed subpacket area, /// this returns `None`. If this function returns `None`, the /// signature does not expire. /// /// Note: if the signature contains multiple instances of this /// subpacket in the hashed subpacket area, the last one is /// returned. // Note: This shadows SubpacketAreas::signature_expiration_time // (SignatureBuilder derefs to SubpacketAreas), because we need to // take SignatureBuilder::reference_time into account. // // If you update this function, also update // SubpacketAreas::signature_expiration_time. pub fn signature_expiration_time(&self) -> Option<time::SystemTime> { match (self.effective_signature_creation_time(), // This ^ is the difference to // SubpacketAreas::signature_expiration_time. self.signature_validity_period()) { (Ok(Some(ct)), Some(vp)) if vp.as_secs() > 0 => Some(ct + vp), _ => None, } } /// Sets the Signature Expiration Time subpacket. /// /// Adds a [Signature Expiration Time subpacket] to the hashed /// subpacket area. This function first removes any Signature /// Expiration Time subpacket from the hashed subpacket area. /// /// [Signature Expiration Time subpacket]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.18 /// /// This function is called `set_signature_validity_period` and /// not `set_signature_expiration_time`, which would be more /// consistent with the subpacket's name, because the latter /// suggests an absolute time, but the time is actually relative /// to the signature's creation time, which is stored in the /// signature's [Signature Creation Time subpacket] and set using /// [`SignatureBuilder::set_signature_creation_time`]. /// /// [Signature Creation Time subpacket]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.11 /// [`SignatureBuilder::set_signature_creation_time`]: super::SignatureBuilder::set_signature_creation_time() /// /// A Signature Expiration Time subpacket specifies when the /// signature expires. This is different from the [Key Expiration /// Time subpacket], which is set using /// [`SignatureBuilder::set_key_validity_period`], and used to /// specify when an associated key expires. The difference is /// that in the former case, the signature itself expires, but in /// the latter case, only the associated key expires. This /// difference is critical: if a binding signature expires, then /// an OpenPGP implementation will still consider the associated /// key to be valid if there is another valid binding signature, /// even if it is older than the expired signature; if the active /// binding signature indicates that the key has expired, then /// OpenPGP implementations will not fall back to an older binding /// signature. /// /// [Key Expiration Time subpacket]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.13 /// [`SignatureBuilder::set_key_validity_period`]: super::SignatureBuilder::set_key_validity_period() /// /// There are several cases where having a signature expire is /// useful. Say Alice certifies Bob's certificate for /// `bob@example.org`. She can limit the lifetime of the /// certification to force her to reevaluate the certification /// shortly before it expires. For instance, is Bob still /// associated with `example.org`? Does she have reason to /// believe that his key has been compromised? Using an /// expiration is common in the X.509 ecosystem. For instance, /// [Let's Encrypt] issues certificates with 90-day lifetimes. /// /// [Let's Encrypt]: https://letsencrypt.org/2015/11/09/why-90-days.html /// /// Having signatures expire can also be useful when deploying /// software. For instance, you might have a service that /// installs an update if it has been signed by a trusted /// certificate. To prevent an adversary from coercing the /// service to install an older version, you could limit the /// signature's lifetime to just a few minutes. /// /// # Examples /// /// Create a signature that expires in 10 minutes: /// /// ``` /// use sequoia_openpgp as openpgp; /// use openpgp::cert::prelude::*; /// use openpgp::packet::signature::SignatureBuilder; /// # use openpgp::packet::signature::subpacket::SubpacketTag; /// use openpgp::types::SignatureType; /// /// # fn main() -> openpgp::Result<()> { /// # /// let (cert, _) = /// CertBuilder::general_purpose(Some("alice@example.org")) /// .generate()?; /// let mut signer = cert.primary_key().key().clone() /// .parts_into_secret()?.into_keypair()?; /// /// let msg = "install e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"; /// /// let mut sig = SignatureBuilder::new(SignatureType::Binary) /// .set_signature_validity_period( /// std::time::Duration::new(10 * 60, 0))? /// .sign_message(&mut signer, msg)?; /// /// assert!(sig.verify_message(signer.public(), msg).is_ok()); /// # assert_eq!(sig /// # .hashed_area() /// # .iter() /// # .filter(|sp| sp.tag() == SubpacketTag::SignatureExpirationTime) /// # .count(), /// # 1); /// # Ok(()) } /// ``` /// /// Create a certification that expires at the end of the year /// (give or take a few seconds) unless the new year is in a /// month, then have it expire at the end of the following year: /// /// ``` /// use std::time::{SystemTime, UNIX_EPOCH, Duration}; /// use sequoia_openpgp as openpgp; /// use openpgp::cert::prelude::*; /// use openpgp::packet::signature::SignatureBuilder; /// # use openpgp::packet::signature::subpacket::SubpacketTag; /// use openpgp::types::SignatureType; /// /// # fn main() -> openpgp::Result<()> { /// let (cert, _) = /// CertBuilder::general_purpose(Some("alice@example.org")) /// .generate()?; /// let mut signer = cert.primary_key().key().clone() /// .parts_into_secret()?.into_keypair()?; /// /// let msg = "message."; /// /// // Average number of seconds in a year. See: /// // https://en.wikipedia.org/wiki/Year . /// const SECONDS_IN_YEAR: u64 = (365.2425 * 24. * 60. * 60.) as u64; /// /// let now = SystemTime::now(); /// let since_epoch = now.duration_since(UNIX_EPOCH)?.as_secs(); /// let next_year /// = (since_epoch + SECONDS_IN_YEAR) - (since_epoch % SECONDS_IN_YEAR); /// // Make sure the expiration is at least a month in the future. /// let next_year = if next_year - since_epoch < SECONDS_IN_YEAR / 12 { /// next_year + SECONDS_IN_YEAR /// } else { /// next_year /// }; /// let next_year = UNIX_EPOCH + Duration::new(next_year, 0); /// let next_year = next_year.duration_since(now)?; /// /// let sig = SignatureBuilder::new(SignatureType::Binary) /// .set_signature_creation_time(now)? /// .set_signature_validity_period(next_year)? /// .sign_message(&mut signer, msg)?; /// # /// # let mut sig = sig; /// # assert!(sig.verify_message(signer.public(), msg).is_ok()); /// # assert_eq!(sig /// # .hashed_area() /// # .iter() /// # .filter(|sp| sp.tag() == SubpacketTag::SignatureExpirationTime) /// # .count(), /// # 1); /// # Ok(()) } /// ``` pub fn set_signature_validity_period<D>(mut self, expires_in: D) -> Result<Self> where D: Into<time::Duration> { self.hashed_area.replace(Subpacket::new( SubpacketValue::SignatureExpirationTime( Duration::try_from(expires_in.into())?), true)?)?; Ok(self) } /// Sets the Exportable Certification subpacket. /// /// Adds an [Exportable Certification subpacket] to the hashed /// subpacket area. This function first removes any Exportable /// Certification subpacket from the hashed subpacket area. /// /// [Exportable Certification subpacket]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.19 /// /// The Exportable Certification subpacket indicates whether the /// signature should be exported (e.g., published on a public key /// server) or not. When using [`Serialize::export`] to export a /// certificate, signatures that have this subpacket present and /// set to false are not serialized. /// /// [`Serialize::export`]: https://docs.sequoia-pgp.org/sequoia_openpgp/serialize/trait.Serialize.html#method.export /// /// # Examples /// /// Alice certificates Bob's certificate, but because she doesn't /// want to publish it, she creates a so-called local signature by /// adding an Exportable Certification subpacket set to `false` to /// the signature: /// /// ``` /// use sequoia_openpgp as openpgp; /// use openpgp::cert::prelude::*; /// use openpgp::packet::prelude::*; /// # use openpgp::packet::signature::subpacket::SubpacketTag; /// use openpgp::policy::StandardPolicy; /// use openpgp::types::SignatureType; /// /// # fn main() -> openpgp::Result<()> { /// # /// let p = &StandardPolicy::new(); /// /// let (alice, _) /// = CertBuilder::general_purpose(Some("alice@example.org")) /// .generate()?; /// let mut alices_signer = alice.primary_key().key().clone() /// .parts_into_secret()?.into_keypair()?; /// /// let (bob, _) /// = CertBuilder::general_purpose(Some("bob@example.org")) /// .generate()?; /// let bobs_userid /// = bob.with_policy(p, None)?.userids().nth(0).expect("Added a User ID").userid(); /// /// let certification = SignatureBuilder::new(SignatureType::GenericCertification) /// .set_exportable_certification(false)? /// .sign_userid_binding( /// &mut alices_signer, bob.primary_key().key(), bobs_userid)?; /// # assert_eq!(certification /// # .hashed_area() /// # .iter() /// # .filter(|sp| sp.tag() == SubpacketTag::ExportableCertification) /// # .count(), /// # 1); /// /// // Merge in the new signature. /// let bob = bob.insert_packets(certification)?.0; /// # assert_eq!(bob.bad_signatures().count(), 0); /// # assert_eq!(bob.userids().nth(0).unwrap().certifications().count(), 1); /// # Ok(()) } /// ``` pub fn set_exportable_certification(mut self, exportable: bool) -> Result<Self> { self.hashed_area.replace(Subpacket::new( SubpacketValue::ExportableCertification(exportable), true)?)?; Ok(self) } /// Sets the Trust Signature subpacket. /// /// Adds a [Trust Signature subpacket] to the hashed subpacket /// area. This function first removes any Trust Signature /// subpacket from the hashed subpacket area. /// /// [Trust Signature subpacket]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.21 /// /// The Trust Signature subpacket indicates the degree to which a /// certificate holder is trusted to certify other keys. /// /// A level of 0 means that the certificate holder is not trusted /// to certificate other keys, a level of 1 means that the /// certificate holder is a trusted introducer (a [certificate /// authority]) and any certifications that they make should be /// considered valid. A level of 2 means the certificate holder /// can designate level 1 trusted introducers, etc. /// /// [certificate authority]: https://en.wikipedia.org/wiki/Certificate_authority /// /// The trust indicates the degree of confidence. A value of 120 /// means that a certification should be considered valid. A /// value of 60 means that a certification should only be /// considered partially valid. In the latter case, typically /// three such certifications are required for a binding to be /// considered authenticated. /// /// # Examples /// /// Alice designates Bob as a fully trusted, trusted introducer: /// /// ``` /// use sequoia_openpgp as openpgp; /// use openpgp::cert::prelude::*; /// use openpgp::packet::prelude::*; /// # use openpgp::packet::signature::subpacket::SubpacketTag; /// use openpgp::policy::StandardPolicy; /// use openpgp::types::SignatureType; /// /// # fn main() -> openpgp::Result<()> { /// # /// let p = &StandardPolicy::new(); /// /// let (alice, _) /// = CertBuilder::general_purpose(Some("alice@example.org")) /// .generate()?; /// let mut alices_signer = alice.primary_key().key().clone() /// .parts_into_secret()?.into_keypair()?; /// /// let (bob, _) /// = CertBuilder::general_purpose(Some("bob@example.org")) /// .generate()?; /// let bobs_userid /// = bob.with_policy(p, None)?.userids().nth(0).expect("Added a User ID").userid(); /// /// let certification = SignatureBuilder::new(SignatureType::GenericCertification) /// .set_trust_signature(1, 120)? /// .sign_userid_binding( /// &mut alices_signer, bob.primary_key().component(), bobs_userid)?; /// # assert_eq!(certification /// # .hashed_area() /// # .iter() /// # .filter(|sp| sp.tag() == SubpacketTag::TrustSignature) /// # .count(), /// # 1); /// /// // Merge in the new signature. /// let bob = bob.insert_packets(certification)?.0; /// # assert_eq!(bob.bad_signatures().count(), 0); /// # assert_eq!(bob.userids().nth(0).unwrap().certifications().count(), 1); /// # Ok(()) } /// ``` pub fn set_trust_signature(mut self, level: u8, trust: u8) -> Result<Self> { self.hashed_area.replace(Subpacket::new( SubpacketValue::TrustSignature { level, trust, }, true)?)?; Ok(self) } /// Sets the Regular Expression subpacket. /// /// Adds a [Regular Expression subpacket] to the hashed subpacket /// area. This function first removes any Regular Expression /// subpacket from the hashed subpacket area. /// /// [Regular Expression subpacket]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.22 /// /// The Regular Expression subpacket is used in conjunction with a /// [Trust Signature subpacket], which is set using /// [`SignatureBuilder::set_trust_signature`], to limit the scope /// of a trusted introducer. This is useful, for instance, when a /// company has a CA and you only want to trust them to certify /// their own employees. /// /// [Trust Signature subpacket]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.21 /// [`SignatureBuilder::set_trust_signature`]: super::SignatureBuilder::set_trust_signature() /// /// GnuPG only supports [a limited form of regular expressions]. /// /// [a limited form of regular expressions]: https://git.gnupg.org/cgi-bin/gitweb.cgi?p=gnupg.git;a=blob;f=g10/trustdb.c;h=c4b996a9685486b2095608f6685727022120505f;hb=refs/heads/master#l1537 /// /// Note: The serialized form includes a trailing `NUL` byte. /// Sequoia adds this `NUL` when serializing the signature. /// Adding it yourself will result in two trailing NUL bytes. /// /// # Examples /// /// Alice designates ``openpgp-ca@example.com`` as a fully /// trusted, trusted introducer, but only for users from the /// ``example.com`` domain: /// /// ``` /// use sequoia_openpgp as openpgp; /// use openpgp::cert::prelude::*; /// use openpgp::packet::prelude::*; /// # use openpgp::packet::signature::subpacket::SubpacketTag; /// use openpgp::policy::StandardPolicy; /// use openpgp::types::SignatureType; /// /// # fn main() -> openpgp::Result<()> { /// # /// let p = &StandardPolicy::new(); /// /// let (alice, _) /// = CertBuilder::general_purpose(Some("Alice <alice@example.org>")) /// .generate()?; /// let mut alices_signer = alice.primary_key().key().clone() /// .parts_into_secret()?.into_keypair()?; /// /// let (example_com, _) /// = CertBuilder::general_purpose(Some("OpenPGP CA <openpgp-ca@example.com>")) /// .generate()?; /// let example_com_userid = example_com.with_policy(p, None)? /// .userids().nth(0).expect("Added a User ID").userid(); /// /// let certification = SignatureBuilder::new(SignatureType::GenericCertification) /// .set_trust_signature(1, 120)? /// .set_regular_expression("<[^>]+[@.]example\\.com>$")? /// .sign_userid_binding( /// &mut alices_signer, /// example_com.primary_key().component(), /// example_com_userid)?; /// # assert_eq!(certification /// # .hashed_area() /// # .iter() /// # .filter(|sp| sp.tag() == SubpacketTag::TrustSignature) /// # .count(), /// # 1); /// # assert_eq!(certification /// # .hashed_area() /// # .iter() /// # .filter(|sp| sp.tag() == SubpacketTag::RegularExpression) /// # .count(), /// # 1); /// /// // Merge in the new signature. /// let example_com = example_com.insert_packets(certification)?.0; /// # assert_eq!(example_com.bad_signatures().count(), 0); /// # assert_eq!(example_com.userids().nth(0).unwrap().certifications().count(), 1); /// # Ok(()) } /// ``` pub fn set_regular_expression<R>(mut self, re: R) -> Result<Self> where R: AsRef<[u8]> { self.hashed_area.replace(Subpacket::new( SubpacketValue::RegularExpression(re.as_ref().to_vec()), true)?)?; Ok(self) } /// Sets a Regular Expression subpacket. /// /// Adds a [Regular Expression subpacket] to the hashed subpacket /// area. Unlike [`SignatureBuilder::set_regular_expression`], /// this function does not first remove any Regular Expression /// subpacket from the hashed subpacket area, but adds an /// additional Regular Expression subpacket to the hashed /// subpacket area. /// /// [`SignatureBuilder::set_regular_expression`]: super::SignatureBuilder::set_regular_expression() /// [Regular Expression subpacket]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.22 /// /// The Regular Expression subpacket is used in conjunction with a /// [Trust Signature subpacket], which is set using /// [`SignatureBuilder::set_trust_signature`], to limit the scope /// of a trusted introducer. This is useful, for instance, when a /// company has a CA and you only want to trust them to certify /// their own employees. /// /// [Trust Signature subpacket]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.21 /// [`SignatureBuilder::set_trust_signature`]: super::SignatureBuilder::set_trust_signature() /// /// GnuPG only supports [a limited form of regular expressions]. /// /// [a limited form of regular expressions]: https://git.gnupg.org/cgi-bin/gitweb.cgi?p=gnupg.git;a=blob;f=g10/trustdb.c;h=c4b996a9685486b2095608f6685727022120505f;hb=refs/heads/master#l1537 /// /// Note: The serialized form includes a trailing `NUL` byte. /// Sequoia adds this `NUL` when serializing the signature. /// Adding it yourself will result in two trailing NUL bytes. /// /// # Examples /// /// Alice designates ``openpgp-ca@example.com`` as a fully /// trusted, trusted introducer, but only for users from the /// ``example.com`` and ``example.net`` domains: /// /// ``` /// use sequoia_openpgp as openpgp; /// use openpgp::cert::prelude::*; /// use openpgp::packet::prelude::*; /// # use openpgp::packet::signature::subpacket::SubpacketTag; /// use openpgp::policy::StandardPolicy; /// use openpgp::types::SignatureType; /// /// # fn main() -> openpgp::Result<()> { /// # /// let p = &StandardPolicy::new(); /// /// let (alice, _) /// = CertBuilder::general_purpose(Some("Alice <alice@example.org>")) /// .generate()?; /// let mut alices_signer = alice.primary_key().key().clone() /// .parts_into_secret()?.into_keypair()?; /// /// let (example_com, _) /// = CertBuilder::general_purpose(Some("OpenPGP CA <openpgp-ca@example.com>")) /// .generate()?; /// let example_com_userid = example_com.with_policy(p, None)? /// .userids().nth(0).expect("Added a User ID").userid(); /// /// let certification = SignatureBuilder::new(SignatureType::GenericCertification) /// .set_trust_signature(1, 120)? /// .set_regular_expression("<[^>]+[@.]example\\.com>$")? /// .add_regular_expression("<[^>]+[@.]example\\.net>$")? /// .sign_userid_binding( /// &mut alices_signer, /// example_com.primary_key().component(), /// example_com_userid)?; /// # assert_eq!(certification /// # .hashed_area() /// # .iter() /// # .filter(|sp| sp.tag() == SubpacketTag::TrustSignature) /// # .count(), /// # 1); /// # assert_eq!(certification /// # .hashed_area() /// # .iter() /// # .filter(|sp| sp.tag() == SubpacketTag::RegularExpression) /// # .count(), /// # 2); /// /// // Merge in the new signature. /// let example_com = example_com.insert_packets(certification)?.0; /// # assert_eq!(example_com.bad_signatures().count(), 0); /// # assert_eq!(example_com.userids().nth(0).unwrap().certifications().count(), 1); /// # Ok(()) } /// ``` pub fn add_regular_expression<R>(mut self, re: R) -> Result<Self> where R: AsRef<[u8]> { self.hashed_area.add(Subpacket::new( SubpacketValue::RegularExpression(re.as_ref().to_vec()), true)?)?; Ok(self) } /// Sets the Revocable subpacket. /// /// Adds a [Revocable subpacket] to the hashed subpacket area. /// This function first removes any Revocable subpacket from the /// hashed subpacket area. /// /// [Revocable subpacket]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.20 /// /// The Revocable subpacket indicates whether a certification may /// be later revoked by creating a [Certification revocation /// signature] (0x30) that targets the signature using the /// [Signature Target subpacket] (set using the /// [`SignatureBuilder::set_signature_target`] method). /// /// [Certification revocation signature]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.1 /// [Signature Target subpacket]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.33 /// [`SignatureBuilder::set_signature_target`]: super::SignatureBuilder::set_signature_target() /// /// # Examples /// /// Alice certifies Bob's key and marks the certification as /// irrevocable. Since she can't revoke the signature, she limits /// the scope of misuse by setting the signature to expire in a /// year: /// /// ``` /// use sequoia_openpgp as openpgp; /// use openpgp::cert::prelude::*; /// use openpgp::packet::prelude::*; /// # use openpgp::packet::signature::subpacket::SubpacketTag; /// use openpgp::policy::StandardPolicy; /// use openpgp::types::SignatureType; /// /// # fn main() -> openpgp::Result<()> { /// # /// let p = &StandardPolicy::new(); /// /// let (alice, _) /// = CertBuilder::general_purpose(Some("alice@example.org")) /// .generate()?; /// let mut alices_signer = alice.primary_key().key().clone() /// .parts_into_secret()?.into_keypair()?; /// /// let (bob, _) /// = CertBuilder::general_purpose(Some("bob@example.org")) /// .generate()?; /// let bobs_userid /// = bob.with_policy(p, None)?.userids().nth(0).expect("Added a User ID").userid(); /// /// // Average number of seconds in a year. See: /// // https://en.wikipedia.org/wiki/Year . /// const SECONDS_IN_YEAR: u64 = (365.2425 * 24. * 60. * 60.) as u64; /// /// let certification = SignatureBuilder::new(SignatureType::GenericCertification) /// .set_revocable(false)? /// .set_signature_validity_period( /// std::time::Duration::new(SECONDS_IN_YEAR, 0))? /// .sign_userid_binding( /// &mut alices_signer, bob.primary_key().component(), bobs_userid)?; /// # assert_eq!(certification /// # .hashed_area() /// # .iter() /// # .filter(|sp| sp.tag() == SubpacketTag::Revocable) /// # .count(), /// # 1); /// /// // Merge in the new signature. /// let bob = bob.insert_packets(certification)?.0; /// # assert_eq!(bob.bad_signatures().count(), 0); /// # assert_eq!(bob.userids().nth(0).unwrap().certifications().count(), 1); /// # Ok(()) } /// ``` pub fn set_revocable(mut self, revocable: bool) -> Result<Self> { self.hashed_area.replace(Subpacket::new( SubpacketValue::Revocable(revocable), true)?)?; Ok(self) } /// Sets the Key Expiration Time subpacket. /// /// Adds a [Key Expiration Time subpacket] to the hashed subpacket /// area. This function first removes any Key Expiration Time /// subpacket from the hashed subpacket area. /// /// If `None` is given, any expiration subpacket is removed. /// /// [Key Expiration Time subpacket]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.13 /// /// This function is called `set_key_validity_period` and not /// `set_key_expiration_time`, which would be more consistent with /// the subpacket's name, because the latter suggests an absolute /// time, but the time is actually relative to the associated /// key's (*not* the signature's) creation time, which is stored /// in the [Key]. /// /// [Key]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.5.2 /// /// There is a more convenient function /// [`SignatureBuilder::set_key_expiration_time`] that takes an /// absolute expiration time. /// /// [`SignatureBuilder::set_key_expiration_time`]: super::SignatureBuilder::set_key_expiration_time() /// /// A Key Expiration Time subpacket specifies when the associated /// key expires. This is different from the [Signature Expiration /// Time subpacket] (set using /// [`SignatureBuilder::set_signature_validity_period`]), which is /// used to specify when the signature expires. That is, in the /// former case, the associated key expires, but in the latter /// case, the signature itself expires. This difference is /// critical: if a binding signature expires, then an OpenPGP /// implementation will still consider the associated key to be /// valid if there is another valid binding signature, even if it /// is older than the expired signature; if the active binding /// signature indicates that the key has expired, then OpenPGP /// implementations will not fall back to an older binding /// signature. /// /// [Signature Expiration Time subpacket]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.13 /// [`SignatureBuilder::set_signature_validity_period`]: super::SignatureBuilder::set_signature_validity_period() /// /// # Examples /// /// Change all subkeys to expire 10 minutes after their (not the /// new binding signature's) creation time. /// /// ``` /// use sequoia_openpgp as openpgp; /// use openpgp::cert::prelude::*; /// use openpgp::packet::prelude::*; /// use openpgp::policy::StandardPolicy; /// use openpgp::types::SignatureType; /// /// # fn main() -> openpgp::Result<()> { /// # /// let p = &StandardPolicy::new(); /// /// let (cert, _) = /// CertBuilder::general_purpose(Some("alice@example.org")) /// .generate()?; /// let pk = cert.primary_key().key(); /// let mut signer = pk.clone().parts_into_secret()?.into_keypair()?; /// /// // Create the binding signatures. /// let mut sigs = Vec::new(); /// /// for ka in cert.with_policy(p, None)?.keys().subkeys() { /// // This reuses any existing backsignature. /// let sig = SignatureBuilder::from(ka.binding_signature().clone()) /// .set_key_validity_period(std::time::Duration::new(10 * 60, 0))? /// .sign_subkey_binding(&mut signer, None, ka.key())?; /// sigs.push(sig); /// } /// /// let cert = cert.insert_packets(sigs)?.0; /// # assert_eq!(cert.bad_signatures().count(), 0); /// # /// # // "Before" /// # for key in cert.with_policy(p, None)?.keys().subkeys() { /// # assert_eq!(key.bundle().self_signatures().count(), 2); /// # assert!(key.alive().is_ok()); /// # } /// # /// # // "After" /// # for key in cert /// # .with_policy(p, std::time::SystemTime::now() /// # + std::time::Duration::new(20 * 60, 0))? /// # .keys().subkeys() /// # { /// # assert!(key.alive().is_err()); /// # } /// # Ok(()) } /// ``` pub fn set_key_validity_period<D>(mut self, expires_in: D) -> Result<Self> where D: Into<Option<time::Duration>> { if let Some(e) = expires_in.into() { self.hashed_area.replace(Subpacket::new( SubpacketValue::KeyExpirationTime(e.try_into()?), true)?)?; } else { self.hashed_area.remove_all(SubpacketTag::KeyExpirationTime); } Ok(self) } /// Sets the Key Expiration Time subpacket. /// /// Adds a [Key Expiration Time subpacket] to the hashed subpacket /// area. This function first removes any Key Expiration Time /// subpacket from the hashed subpacket area. /// /// If `None` is given, any expiration subpacket is removed. /// /// [Key Expiration Time subpacket]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.13 /// /// This function is called `set_key_expiration_time` similar to /// the subpacket's name, but it takes an absolute time, whereas /// the subpacket stores a time relative to the associated key's /// (*not* the signature's) creation time, which is stored in the /// [Key]. /// /// [Key]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.5.2 /// /// This is a more convenient function than /// [`SignatureBuilder::set_key_validity_period`] that takes a /// relative expiration time. /// /// [`SignatureBuilder::set_key_validity_period`]: super::SignatureBuilder::set_key_validity_period() /// /// A Key Expiration Time subpacket specifies when the associated /// key expires. This is different from the [Signature Expiration /// Time subpacket] (set using /// [`SignatureBuilder::set_signature_validity_period`]), which is /// used to specify when the signature expires. That is, in the /// former case, the associated key expires, but in the latter /// case, the signature itself expires. This difference is /// critical: if a binding signature expires, then an OpenPGP /// implementation will still consider the associated key to be /// valid if there is another valid binding signature, even if it /// is older than the expired signature; if the active binding /// signature indicates that the key has expired, then OpenPGP /// implementations will not fall back to an older binding /// signature. /// /// [Signature Expiration Time subpacket]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.13 /// [`SignatureBuilder::set_signature_validity_period`]: super::SignatureBuilder::set_signature_validity_period() /// /// # Examples /// /// Change all subkeys to expire 10 minutes after their (not the /// new binding signature's) creation time. /// /// ``` /// use std::time; /// use sequoia_openpgp as openpgp; /// use openpgp::cert::prelude::*; /// use openpgp::packet::prelude::*; /// use openpgp::policy::StandardPolicy; /// use openpgp::types::SignatureType; /// /// # fn main() -> openpgp::Result<()> { /// # /// let p = &StandardPolicy::new(); /// /// let (cert, _) = /// CertBuilder::general_purpose(Some("alice@example.org")) /// .generate()?; /// let pk = cert.primary_key().key(); /// let mut signer = pk.clone().parts_into_secret()?.into_keypair()?; /// /// // Create the binding signatures. /// let mut sigs = Vec::new(); /// /// for ka in cert.with_policy(p, None)?.keys().subkeys() { /// // This reuses any existing backsignature. /// let sig = SignatureBuilder::from(ka.binding_signature().clone()) /// .set_key_expiration_time(ka.key(), /// time::SystemTime::now() /// + time::Duration::new(10 * 60, 0))? /// .sign_subkey_binding(&mut signer, None, ka.key())?; /// sigs.push(sig); /// } /// /// let cert = cert.insert_packets(sigs)?.0; /// # assert_eq!(cert.bad_signatures().count(), 0); /// # /// # // "Before" /// # for key in cert.with_policy(p, None)?.keys().subkeys() { /// # assert_eq!(key.bundle().self_signatures().count(), 2); /// # assert!(key.alive().is_ok()); /// # } /// # /// # // "After" /// # for key in cert.with_policy(p, time::SystemTime::now() /// # + time::Duration::new(20 * 60, 0))? /// # .keys().subkeys() /// # { /// # assert!(key.alive().is_err()); /// # } /// # Ok(()) } /// ``` pub fn set_key_expiration_time<P, R, E>( self, key: &Key<P, R>, expiration: E) -> Result<Self> where P: key::KeyParts, R: key::KeyRole, E: Into<Option<time::SystemTime>>, { if let Some(e) = expiration.into() .map(crate::types::normalize_systemtime) { let ct = key.creation_time(); let vp = match e.duration_since(ct) { Ok(v) => v, Err(_) => return Err(Error::InvalidArgument( format!("Expiration time {:?} predates creation time \ {:?}", e, ct)).into()), }; self.set_key_validity_period(Some(vp)) } else { self.set_key_validity_period(None) } } /// Sets the Preferred Symmetric Algorithms subpacket. /// /// Replaces any [Preferred Symmetric Algorithms subpacket] in the /// hashed subpacket area with a new subpacket containing the /// specified value. That is, this function first removes any /// Preferred Symmetric Algorithms subpacket from the hashed /// subpacket area, and then adds a new one. /// /// A Preferred Symmetric Algorithms subpacket lists what /// symmetric algorithms the user prefers. When encrypting a /// message for a recipient, the OpenPGP implementation should not /// use an algorithm that is not on this list. /// /// [Preferred Symmetric Algorithms subpacket]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.14 /// /// This subpacket is a type of preference. When looking up a /// preference, an OpenPGP implementation should first look for /// the subpacket on the binding signature of the User ID or the /// User Attribute used to locate the certificate (or the primary /// User ID, if it was addressed by Key ID or fingerprint). If /// the binding signature doesn't contain the subpacket, then the /// direct key signature should be checked. See the /// [`Preferences`] trait for details. /// /// Unless addressing different User IDs really should result in /// different behavior, it is best to only set this preference on /// the direct key signature. This guarantees that even if some /// or all User IDs are stripped, the behavior remains consistent. /// /// [`Preferences`]: crate::cert::Preferences /// /// # Examples /// /// ``` /// use sequoia_openpgp as openpgp; /// use openpgp::cert::prelude::*; /// use openpgp::packet::prelude::*; /// # use openpgp::packet::signature::subpacket::SubpacketTag; /// use openpgp::policy::StandardPolicy; /// use openpgp::types::SymmetricAlgorithm; /// /// # fn main() -> openpgp::Result<()> { /// let p = &StandardPolicy::new(); /// /// let (cert, _) = CertBuilder::new().add_userid("Alice").generate()?; /// let mut signer = cert.primary_key().key() /// .clone().parts_into_secret()?.into_keypair()?; /// /// let vc = cert.with_policy(p, None)?; /// /// let template = vc.direct_key_signature() /// .expect("CertBuilder always includes a direct key signature"); /// let sig = SignatureBuilder::from(template.clone()) /// .set_preferred_symmetric_algorithms( /// vec![ SymmetricAlgorithm::AES256, /// SymmetricAlgorithm::AES128, /// ])? /// .sign_direct_key(&mut signer, None)?; /// # assert_eq!(sig /// # .hashed_area() /// # .iter() /// # .filter(|sp| sp.tag() == SubpacketTag::PreferredSymmetricAlgorithms) /// # .count(), /// # 1); /// /// // Merge in the new signature. /// let cert = cert.insert_packets(sig)?.0; /// # assert_eq!(cert.bad_signatures().count(), 0); /// # Ok(()) } /// ``` pub fn set_preferred_symmetric_algorithms(mut self, preferences: Vec<SymmetricAlgorithm>) -> Result<Self> { self.hashed_area.replace(Subpacket::new( SubpacketValue::PreferredSymmetricAlgorithms(preferences), false)?)?; Ok(self) } /// Sets the Revocation Key subpacket. /// /// Replaces any [Revocation Key subpacket] in the hashed /// subpacket area with one new subpacket for each of the /// specified values. That is, unlike /// [`super::SignatureBuilder::add_revocation_key`], this function /// first removes any Revocation Key subpackets from the hashed /// subpacket area, and then adds new ones. /// /// [Revocation Key subpacket]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.23 /// /// A Revocation Key subpacket indicates certificates (so-called /// designated revokers) that are allowed to revoke the signer's /// certificate. For instance, if Alice trusts Bob, she can set /// him as a designated revoker. This is useful if Alice loses /// access to her key, and therefore is unable to generate a /// revocation certificate on her own. In this case, she can /// still Bob to generate one on her behalf. /// /// Due to the complexity of verifying such signatures, many /// OpenPGP implementations do not support this feature. /// /// # Examples /// /// ``` /// use sequoia_openpgp as openpgp; /// use openpgp::cert::prelude::*; /// use openpgp::packet::prelude::*; /// # use openpgp::packet::signature::subpacket::SubpacketTag; /// use openpgp::policy::StandardPolicy; /// use openpgp::types::RevocationKey; /// /// # fn main() -> openpgp::Result<()> { /// let p = &StandardPolicy::new(); /// /// let (alice, _) = CertBuilder::new().add_userid("Alice").generate()?; /// let mut alices_signer = alice.primary_key().key() /// .clone().parts_into_secret()?.into_keypair()?; /// /// let (bob, _) = CertBuilder::new().add_userid("Bob").generate()?; /// /// let template = alice.with_policy(p, None)?.direct_key_signature() /// .expect("CertBuilder always includes a direct key signature"); /// let sig = SignatureBuilder::from(template.clone()) /// // Replace any revocation keys inherited from template. /// .set_revocation_key( /// RevocationKey::new(bob.primary_key().key().pk_algo(), bob.fingerprint(), false), /// )? /// .sign_direct_key(&mut alices_signer, None)?; /// # assert_eq!(sig /// # .hashed_area() /// # .iter() /// # .filter(|sp| sp.tag() == SubpacketTag::RevocationKey) /// # .count(), /// # 1); /// /// // Merge in the new signature. /// let alice = alice.insert_packets(sig)?.0; /// # assert_eq!(alice.bad_signatures().count(), 0); /// # assert_eq!(alice.primary_key().self_signatures().count(), 2); /// # Ok(()) } /// ``` pub fn set_revocation_key(mut self, rk: RevocationKey) -> Result<Self> { self.hashed_area.remove_all(SubpacketTag::RevocationKey); self.add_revocation_key(rk) } /// Adds a Revocation Key subpacket. /// /// Adds a [Revocation Key subpacket] to the hashed subpacket /// area. Unlike [`super::SignatureBuilder::set_revocation_key`], /// this function does not first remove any Revocation Key /// subpackets from the hashed subpacket area. /// /// [Revocation Key subpacket]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.23 /// /// A Revocation Key subpacket indicates certificates (so-called /// designated revokers) that are allowed to revoke the signer's /// certificate. For instance, if Alice trusts Bob, she can set /// him as a designated revoker. This is useful if Alice loses /// access to her key, and therefore is unable to generate a /// revocation certificate on her own. In this case, she can /// still Bob to generate one on her behalf. /// /// Due to the complexity of verifying such signatures, many /// OpenPGP implementations do not support this feature. /// /// # Examples /// /// ``` /// use sequoia_openpgp as openpgp; /// use openpgp::cert::prelude::*; /// use openpgp::packet::prelude::*; /// # use openpgp::packet::signature::subpacket::SubpacketTag; /// use openpgp::policy::StandardPolicy; /// use openpgp::types::RevocationKey; /// /// # fn main() -> openpgp::Result<()> { /// let p = &StandardPolicy::new(); /// /// let (alice, _) = CertBuilder::new().add_userid("Alice").generate()?; /// let mut alices_signer = alice.primary_key().key() /// .clone().parts_into_secret()?.into_keypair()?; /// /// let (bob, _) = CertBuilder::new().add_userid("Bob").generate()?; /// /// let template = alice.with_policy(p, None)?.direct_key_signature() /// .expect("CertBuilder always includes a direct key signature"); /// let sig = SignatureBuilder::from(template.clone()) /// // Add to any revocation keys inherited from template. /// .add_revocation_key( /// RevocationKey::new(bob.primary_key().key().pk_algo(), bob.fingerprint(), false), /// )? /// .sign_direct_key(&mut alices_signer, None)?; /// # assert_eq!(sig /// # .hashed_area() /// # .iter() /// # .filter(|sp| sp.tag() == SubpacketTag::RevocationKey) /// # .count(), /// # 1); /// /// // Merge in the new signature. /// let alice = alice.insert_packets(sig)?.0; /// # assert_eq!(alice.bad_signatures().count(), 0); /// # assert_eq!(alice.primary_key().self_signatures().count(), 2); /// # Ok(()) } /// ``` pub fn add_revocation_key(mut self, rk: RevocationKey) -> Result<Self> { self.hashed_area.add(Subpacket::new( SubpacketValue::RevocationKey(rk), true)?)?; Ok(self) } /// Sets the Issuer subpacket. /// /// Adds an [Issuer subpacket] to the hashed subpacket area. /// Unlike [`add_issuer`], this function first removes any /// existing Issuer subpackets from the hashed and unhashed /// subpacket area. /// /// [Issuer subpacket]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.12 /// [`add_issuer`]: super::SignatureBuilder::add_issuer() /// /// The Issuer subpacket is used when processing a signature to /// identify which certificate created the signature. Even though this /// information is self-authenticating (the act of validating the /// signature authenticates the subpacket), it is stored in the /// hashed subpacket area. This has the advantage that the signer /// authenticates the set of issuers. Furthermore, it makes /// handling of the resulting signatures more robust: If there are /// two signatures that are equal modulo the contents of the /// unhashed area, there is the question of how to merge the /// information in the unhashed areas. Storing issuer information /// in the hashed area avoids this problem. /// /// When creating a signature using a SignatureBuilder or the /// [streaming `Signer`], it is not necessary to explicitly set /// this subpacket: those functions automatically set both the /// [Issuer Fingerprint subpacket] (set using /// [`SignatureBuilder::set_issuer_fingerprint`]) and the Issuer /// subpacket, if they have not been set explicitly. /// /// [streaming `Signer`]: crate::serialize::stream::Signer /// [Issuer Fingerprint subpacket]: https://www.rfc-editor.org/rfc/rfc9580.html#name-issuer-fingerprint /// [`SignatureBuilder::set_issuer_fingerprint`]: super::SignatureBuilder::set_issuer_fingerprint() /// /// # Examples /// /// It is possible to use the same key material with different /// OpenPGP keys. This is useful when the OpenPGP format is /// upgraded, but not all deployed implementations support the new /// format. Here, Alice signs a message, and adds the fingerprint /// of her v4 key and her v5 key indicating that the recipient can /// use either key to verify the message: /// /// ``` /// use sequoia_openpgp as openpgp; /// # use openpgp::cert::prelude::*; /// use openpgp::packet::prelude::*; /// # use openpgp::packet::signature::subpacket::SubpacketTag; /// use openpgp::types::SignatureType; /// /// # fn main() -> openpgp::Result<()> { /// # /// # let (alicev4, _) = /// # CertBuilder::general_purpose(Some("alice@example.org")) /// # .generate()?; /// # let mut alices_signer = alicev4.primary_key().key().clone().parts_into_secret()?.into_keypair()?; /// # let (alicev5, _) = /// # CertBuilder::general_purpose(Some("alice@example.org")) /// # .generate()?; /// # /// let msg = b"Hi!"; /// /// let sig = SignatureBuilder::new(SignatureType::Binary) /// .set_issuer(alicev4.keyid())? /// .add_issuer(alicev5.keyid())? /// .sign_message(&mut alices_signer, msg)?; /// # let mut sig = sig; /// # assert!(sig.verify_message(alices_signer.public(), msg).is_ok()); /// # assert_eq!(sig /// # .hashed_area() /// # .iter() /// # .filter(|sp| sp.tag() == SubpacketTag::Issuer) /// # .count(), /// # 2); /// # assert_eq!(sig /// # .hashed_area() /// # .iter() /// # .filter(|sp| sp.tag() == SubpacketTag::IssuerFingerprint) /// # .count(), /// # 0); /// # Ok(()) } /// ``` pub fn set_issuer(mut self, id: KeyID) -> Result<Self> { self.hashed_area.replace(Subpacket::new( SubpacketValue::Issuer(id), false)?)?; self.unhashed_area.remove_all(SubpacketTag::Issuer); Ok(self) } /// Adds an Issuer subpacket. /// /// Adds an [Issuer subpacket] to the hashed subpacket area. /// Unlike [`set_issuer`], this function does not first remove any /// existing Issuer subpacket from neither the hashed nor the /// unhashed subpacket area. /// /// [Issuer subpacket]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.12 /// [`set_issuer`]: super::SignatureBuilder::set_issuer() /// /// The Issuer subpacket is used when processing a signature to /// identify which certificate created the signature. Even though this /// information is self-authenticating (the act of validating the /// signature authenticates the subpacket), it is stored in the /// hashed subpacket area. This has the advantage that the signer /// authenticates the set of issuers. Furthermore, it makes /// handling of the resulting signatures more robust: If there are /// two signatures that are equal modulo the contents of the /// unhashed area, there is the question of how to merge the /// information in the unhashed areas. Storing issuer information /// in the hashed area avoids this problem. /// /// When creating a signature using a SignatureBuilder or the /// [streaming `Signer`], it is not necessary to explicitly set /// this subpacket: those functions automatically set both the /// [Issuer Fingerprint subpacket] (set using /// [`SignatureBuilder::set_issuer_fingerprint`]) and the Issuer /// subpacket, if they have not been set explicitly. /// /// [streaming `Signer`]: crate::serialize::stream::Signer /// [Issuer Fingerprint subpacket]: https://www.rfc-editor.org/rfc/rfc9580.html#name-issuer-fingerprint /// [`SignatureBuilder::set_issuer_fingerprint`]: super::SignatureBuilder::set_issuer_fingerprint() /// /// # Examples /// /// It is possible to use the same key material with different /// OpenPGP keys. This is useful when the OpenPGP format is /// upgraded, but not all deployed implementations support the new /// format. Here, Alice signs a message, and adds the fingerprint /// of her v4 key and her v5 key indicating that the recipient can /// use either key to verify the message: /// /// ``` /// use sequoia_openpgp as openpgp; /// # use openpgp::cert::prelude::*; /// use openpgp::packet::prelude::*; /// # use openpgp::packet::signature::subpacket::SubpacketTag; /// use openpgp::types::SignatureType; /// /// # fn main() -> openpgp::Result<()> { /// # /// # let (alicev4, _) = /// # CertBuilder::general_purpose(Some("alice@example.org")) /// # .generate()?; /// # let mut alices_signer = alicev4.primary_key().key().clone().parts_into_secret()?.into_keypair()?; /// # let (alicev5, _) = /// # CertBuilder::general_purpose(Some("alice@example.org")) /// # .generate()?; /// # /// let msg = b"Hi!"; /// /// let sig = SignatureBuilder::new(SignatureType::Binary) /// .set_issuer(alicev4.keyid())? /// .add_issuer(alicev5.keyid())? /// .sign_message(&mut alices_signer, msg)?; /// # let mut sig = sig; /// # assert!(sig.verify_message(alices_signer.public(), msg).is_ok()); /// # assert_eq!(sig /// # .hashed_area() /// # .iter() /// # .filter(|sp| sp.tag() == SubpacketTag::Issuer) /// # .count(), /// # 2); /// # assert_eq!(sig /// # .hashed_area() /// # .iter() /// # .filter(|sp| sp.tag() == SubpacketTag::IssuerFingerprint) /// # .count(), /// # 0); /// # Ok(()) } /// ``` pub fn add_issuer(mut self, id: KeyID) -> Result<Self> { self.hashed_area.add(Subpacket::new( SubpacketValue::Issuer(id), false)?)?; Ok(self) } /// Sets a Notation Data subpacket. /// /// Adds a [Notation Data subpacket] to the hashed subpacket area. /// Unlike the [`SignatureBuilder::add_notation`] method, this /// function first removes any existing Notation Data subpacket /// with the specified name from the hashed subpacket area. /// /// [Notation Data subpacket]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.24 /// [`SignatureBuilder::add_notation`]: super::SignatureBuilder::add_notation() /// /// Notations are key-value pairs. They can be used by /// applications to annotate signatures in a structured way. For /// instance, they can define additional, application-specific /// security requirements. Because they are functionally /// equivalent to subpackets, they can also be used for OpenPGP /// extensions. This is how the [Intended Recipient subpacket] /// started life. /// /// [Intended Recipient subpacket]: https://www.rfc-editor.org/rfc/rfc9580.html#name-intended-recipient-fingerpr /// /// Notation names are structured, and are divided into two /// namespaces: the user namespace and the IETF namespace. Names /// in the user namespace have the form `name@example.org` and /// their meaning is defined by the owner of the domain. The /// meaning of the notation `name@example.org`, for instance, is /// defined by whoever controls `example.org`. Names in the IETF /// namespace do not contain an `@` and are managed by IANA. See /// [Section 5.2.3.24 of RFC 9580] for details. /// /// [Section 5.2.3.24 of RFC 9580]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.24 /// /// # Examples /// /// Adds two [social proofs] to a certificate's primary User ID. /// This first clears any social proofs. /// /// [social proofs]: https://metacode.biz/openpgp/proofs /// /// ``` /// use sequoia_openpgp as openpgp; /// use openpgp::cert::prelude::*; /// use openpgp::packet::prelude::*; /// use openpgp::packet::signature::subpacket::NotationDataFlags; /// # use openpgp::packet::signature::subpacket::SubpacketTag; /// use openpgp::policy::StandardPolicy; /// /// # fn main() -> openpgp::Result<()> { /// let p = &StandardPolicy::new(); /// /// let (cert, _) = CertBuilder::new().add_userid("Wiktor").generate()?; /// let mut signer = cert.primary_key().key() /// .clone().parts_into_secret()?.into_keypair()?; /// /// let vc = cert.with_policy(p, None)?; /// let ua = vc.primary_userid().expect("Added a User ID"); /// /// let template = ua.binding_signature(); /// let sig = SignatureBuilder::from(template.clone()) /// .set_notation("proof@metacode.biz", "https://metacode.biz/@wiktor", /// NotationDataFlags::empty().set_human_readable(), false)? /// .add_notation("proof@metacode.biz", "https://news.ycombinator.com/user?id=wiktor-k", /// NotationDataFlags::empty().set_human_readable(), false)? /// .sign_userid_binding(&mut signer, None, ua.userid())?; /// # assert_eq!(sig /// # .hashed_area() /// # .iter() /// # .filter(|sp| sp.tag() == SubpacketTag::NotationData) /// # .count(), /// # 3); /// /// // Merge in the new signature. /// let cert = cert.insert_packets(sig)?.0; /// # assert_eq!(cert.bad_signatures().count(), 0); /// # Ok(()) } /// ``` pub fn set_notation<N, V, F>(mut self, name: N, value: V, flags: F, critical: bool) -> Result<Self> where N: AsRef<str>, V: AsRef<[u8]>, F: Into<Option<NotationDataFlags>>, { self.hashed_area.packets.retain(|s| { ! matches!( s.value, SubpacketValue::NotationData(ref v) if v.name == name.as_ref()) }); self.add_notation(name.as_ref(), value.as_ref(), flags.into().unwrap_or_else(NotationDataFlags::empty), critical) } /// Adds a Notation Data subpacket. /// /// Adds a [Notation Data subpacket] to the hashed subpacket area. /// Unlike the [`SignatureBuilder::set_notation`] method, this /// function does not first remove any existing Notation Data /// subpacket with the specified name from the hashed subpacket /// area. /// /// [Notation Data subpacket]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.24 /// [`SignatureBuilder::set_notation`]: super::SignatureBuilder::set_notation() /// /// Notations are key-value pairs. They can be used by /// applications to annotate signatures in a structured way. For /// instance, they can define additional, application-specific /// security requirements. Because they are functionally /// equivalent to subpackets, they can also be used for OpenPGP /// extensions. This is how the [Intended Recipient subpacket] /// started life. /// /// [Intended Recipient subpacket]: https://www.rfc-editor.org/rfc/rfc9580.html#name-intended-recipient-fingerpr /// /// Notation names are structured, and are divided into two /// namespaces: the user namespace and the IETF namespace. Names /// in the user namespace have the form `name@example.org` and /// their meaning is defined by the owner of the domain. The /// meaning of the notation `name@example.org`, for instance, is /// defined by whoever controls `example.org`. Names in the IETF /// namespace do not contain an `@` and are managed by IANA. See /// [Section 5.2.3.24 of RFC 9580] for details. /// /// [Section 5.2.3.24 of RFC 9580]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.24 /// /// # Examples /// /// Adds two new [social proofs] to a certificate's primary User /// ID. A more sophisticated program will check that the new /// notations aren't already present. /// /// [social proofs]: https://metacode.biz/openpgp/proofs /// /// ``` /// use sequoia_openpgp as openpgp; /// use openpgp::cert::prelude::*; /// use openpgp::packet::prelude::*; /// use openpgp::packet::signature::subpacket::NotationDataFlags; /// # use openpgp::packet::signature::subpacket::SubpacketTag; /// use openpgp::policy::StandardPolicy; /// /// # fn main() -> openpgp::Result<()> { /// let p = &StandardPolicy::new(); /// /// let (cert, _) = CertBuilder::new().add_userid("Wiktor").generate()?; /// let mut signer = cert.primary_key().key() /// .clone().parts_into_secret()?.into_keypair()?; /// /// let vc = cert.with_policy(p, None)?; /// let ua = vc.primary_userid().expect("Added a User ID"); /// /// let template = ua.binding_signature(); /// let sig = SignatureBuilder::from(template.clone()) /// .add_notation("proof@metacode.biz", "https://metacode.biz/@wiktor", /// NotationDataFlags::empty().set_human_readable(), false)? /// .add_notation("proof@metacode.biz", "https://news.ycombinator.com/user?id=wiktor-k", /// NotationDataFlags::empty().set_human_readable(), false)? /// .sign_userid_binding(&mut signer, None, ua.userid())?; /// # assert_eq!(sig /// # .hashed_area() /// # .iter() /// # .filter(|sp| sp.tag() == SubpacketTag::NotationData) /// # .count(), /// # 3); /// /// // Merge in the new signature. /// let cert = cert.insert_packets(sig)?.0; /// # assert_eq!(cert.bad_signatures().count(), 0); /// # Ok(()) } /// ``` pub fn add_notation<N, V, F>(mut self, name: N, value: V, flags: F, critical: bool) -> Result<Self> where N: AsRef<str>, V: AsRef<[u8]>, F: Into<Option<NotationDataFlags>>, { self.hashed_area.add(Subpacket::new(SubpacketValue::NotationData( NotationData::new(name.as_ref(), value.as_ref(), flags.into().unwrap_or_else(NotationDataFlags::empty))), critical)?)?; Ok(self) } /// Sets the Preferred Hash Algorithms subpacket. /// /// Replaces any [Preferred Hash Algorithms subpacket] in the /// hashed subpacket area with a new subpacket containing the /// specified value. That is, this function first removes any /// Preferred Hash Algorithms subpacket from the hashed subpacket /// area, and then adds a new one. /// /// A Preferred Hash Algorithms subpacket lists what hash /// algorithms the user prefers. When signing a message that /// should be verified by a particular recipient, the OpenPGP /// implementation should not use an algorithm that is not on this /// list. /// /// [Preferred Hash Algorithms subpacket]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.16 /// /// This subpacket is a type of preference. When looking up a /// preference, an OpenPGP implementation should first look for /// the subpacket on the binding signature of the User ID or the /// User Attribute used to locate the certificate (or the primary /// User ID, if it was addressed by Key ID or fingerprint). If /// the binding signature doesn't contain the subpacket, then the /// direct key signature should be checked. See the /// [`Preferences`] trait for details. /// /// Unless addressing different User IDs really should result in /// different behavior, it is best to only set this preference on /// the direct key signature. This guarantees that even if some /// or all User IDs are stripped, the behavior remains consistent. /// /// [`Preferences`]: crate::cert::Preferences /// /// # Examples /// /// ``` /// use sequoia_openpgp as openpgp; /// use openpgp::cert::prelude::*; /// use openpgp::packet::prelude::*; /// # use openpgp::packet::signature::subpacket::SubpacketTag; /// use openpgp::policy::StandardPolicy; /// use openpgp::types::HashAlgorithm; /// /// # fn main() -> openpgp::Result<()> { /// let p = &StandardPolicy::new(); /// /// let (cert, _) = CertBuilder::new().add_userid("Alice").generate()?; /// let mut signer = cert.primary_key().key() /// .clone().parts_into_secret()?.into_keypair()?; /// /// let vc = cert.with_policy(p, None)?; /// /// let template = vc.direct_key_signature() /// .expect("CertBuilder always includes a direct key signature"); /// let sig = SignatureBuilder::from(template.clone()) /// .set_preferred_hash_algorithms( /// vec![ HashAlgorithm::SHA512, /// HashAlgorithm::SHA256, /// ])? /// .sign_direct_key(&mut signer, None)?; /// # assert_eq!(sig /// # .hashed_area() /// # .iter() /// # .filter(|sp| sp.tag() == SubpacketTag::PreferredHashAlgorithms) /// # .count(), /// # 1); /// /// // Merge in the new signature. /// let cert = cert.insert_packets(sig)?.0; /// # assert_eq!(cert.bad_signatures().count(), 0); /// # Ok(()) } /// ``` pub fn set_preferred_hash_algorithms(mut self, preferences: Vec<HashAlgorithm>) -> Result<Self> { self.hashed_area.replace(Subpacket::new( SubpacketValue::PreferredHashAlgorithms(preferences), false)?)?; Ok(self) } /// Sets the Preferred Compression Algorithms subpacket. /// /// Replaces any [Preferred Compression Algorithms subpacket] in /// the hashed subpacket area with a new subpacket containing the /// specified value. That is, this function first removes any /// Preferred Compression Algorithms subpacket from the hashed /// subpacket area, and then adds a new one. /// /// A Preferred Compression Algorithms subpacket lists what /// compression algorithms the user prefers. When compressing a /// message for a recipient, the OpenPGP implementation should not /// use an algorithm that is not on the list. /// /// [Preferred Compression Algorithms subpacket]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.17 /// /// This subpacket is a type of preference. When looking up a /// preference, an OpenPGP implementation should first look for /// the subpacket on the binding signature of the User ID or the /// User Attribute used to locate the certificate (or the primary /// User ID, if it was addressed by Key ID or fingerprint). If /// the binding signature doesn't contain the subpacket, then the /// direct key signature should be checked. See the /// [`Preferences`] trait for details. /// /// Unless addressing different User IDs really should result in /// different behavior, it is best to only set this preference on /// the direct key signature. This guarantees that even if some /// or all User IDs are stripped, the behavior remains consistent. /// /// [`Preferences`]: crate::cert::Preferences /// /// # Examples /// /// ``` /// use sequoia_openpgp as openpgp; /// use openpgp::cert::prelude::*; /// use openpgp::packet::prelude::*; /// # use openpgp::packet::signature::subpacket::SubpacketTag; /// use openpgp::policy::StandardPolicy; /// use openpgp::types::CompressionAlgorithm; /// /// # fn main() -> openpgp::Result<()> { /// let p = &StandardPolicy::new(); /// /// let (cert, _) = CertBuilder::new().add_userid("Alice").generate()?; /// let mut signer = cert.primary_key().key() /// .clone().parts_into_secret()?.into_keypair()?; /// /// let vc = cert.with_policy(p, None)?; /// /// let template = vc.direct_key_signature() /// .expect("CertBuilder always includes a direct key signature"); /// let sig = SignatureBuilder::from(template.clone()) /// .set_preferred_compression_algorithms( /// vec![ CompressionAlgorithm::Zlib, /// CompressionAlgorithm::Zip, /// CompressionAlgorithm::BZip2, /// ])? /// .sign_direct_key(&mut signer, None)?; /// # assert_eq!(sig /// # .hashed_area() /// # .iter() /// # .filter(|sp| sp.tag() == SubpacketTag::PreferredCompressionAlgorithms) /// # .count(), /// # 1); /// /// // Merge in the new signature. /// let cert = cert.insert_packets(sig)?.0; /// # assert_eq!(cert.bad_signatures().count(), 0); /// # Ok(()) } /// ``` pub fn set_preferred_compression_algorithms(mut self, preferences: Vec<CompressionAlgorithm>) -> Result<Self> { self.hashed_area.replace(Subpacket::new( SubpacketValue::PreferredCompressionAlgorithms(preferences), false)?)?; Ok(self) } /// Sets the Key Server Preferences subpacket. /// /// Replaces any [Key Server Preferences subpacket] in the hashed /// subpacket area with a new subpacket containing the specified /// value. That is, this function first removes any Key Server /// Preferences subpacket from the hashed subpacket area, and then /// adds a new one. /// /// The Key Server Preferences subpacket indicates to key servers /// how they should handle the certificate. /// /// [Key Server Preferences subpacket]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.25 /// /// This subpacket is a type of preference. When looking up a /// preference, an OpenPGP implementation should first look for /// the subpacket on the binding signature of the User ID or the /// User Attribute used to locate the certificate (or the primary /// User ID, if it was addressed by Key ID or fingerprint). If /// the binding signature doesn't contain the subpacket, then the /// direct key signature should be checked. See the /// [`Preferences`] trait for details. /// /// Unless addressing different User IDs really should result in /// different behavior, it is best to only set this preference on /// the direct key signature. This guarantees that even if some /// or all User IDs are stripped, the behavior remains consistent. /// /// [`Preferences`]: crate::cert::Preferences /// /// # Examples /// /// ``` /// use sequoia_openpgp as openpgp; /// use openpgp::cert::prelude::*; /// use openpgp::packet::prelude::*; /// # use openpgp::packet::signature::subpacket::SubpacketTag; /// use openpgp::policy::StandardPolicy; /// use openpgp::types::KeyServerPreferences; /// /// # fn main() -> openpgp::Result<()> { /// let p = &StandardPolicy::new(); /// /// let (cert, _) = CertBuilder::new().add_userid("Alice").generate()?; /// let mut signer = cert.primary_key().key() /// .clone().parts_into_secret()?.into_keypair()?; /// /// let vc = cert.with_policy(p, None)?; /// /// let sig = vc.direct_key_signature() /// .expect("CertBuilder always includes a direct key signature"); /// let sig = /// SignatureBuilder::from(sig.clone()) /// .set_key_server_preferences( /// KeyServerPreferences::empty().set_no_modify())? /// .sign_direct_key(&mut signer, None)?; /// # assert_eq!(sig /// # .hashed_area() /// # .iter() /// # .filter(|sp| sp.tag() == SubpacketTag::KeyServerPreferences) /// # .count(), /// # 1); /// /// // Merge in the new signature. /// let cert = cert.insert_packets(sig)?.0; /// # assert_eq!(cert.bad_signatures().count(), 0); /// # Ok(()) } /// ``` pub fn set_key_server_preferences(mut self, preferences: KeyServerPreferences) -> Result<Self> { self.hashed_area.replace(Subpacket::new( SubpacketValue::KeyServerPreferences(preferences), false)?)?; Ok(self) } /// Sets the Preferred Key Server subpacket. /// /// Adds a [Preferred Key Server subpacket] to the hashed /// subpacket area. This function first removes any Preferred Key /// Server subpacket from the hashed subpacket area. /// /// [Preferred Key Server subpacket]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.26 /// /// The Preferred Key Server subpacket contains a link to a key /// server where the certificate holder plans to publish updates /// to their certificate (e.g., extensions to the expiration time, /// new subkeys, revocation certificates). /// /// The Preferred Key Server subpacket should be handled /// cautiously, because it can be used by a certificate holder to /// track communication partners. /// /// This subpacket is a type of preference. When looking up a /// preference, an OpenPGP implementation should first look for /// the subpacket on the binding signature of the User ID or the /// User Attribute used to locate the certificate (or the primary /// User ID, if it was addressed by Key ID or fingerprint). If /// the binding signature doesn't contain the subpacket, then the /// direct key signature should be checked. See the /// [`Preferences`] trait for details. /// /// Unless addressing different User IDs really should result in /// different behavior, it is best to only set this preference on /// the direct key signature. This guarantees that even if some /// or all User IDs are stripped, the behavior remains consistent. /// /// [`Preferences`]: crate::cert::Preferences /// /// # Examples /// /// ``` /// use sequoia_openpgp as openpgp; /// use openpgp::cert::prelude::*; /// use openpgp::packet::prelude::*; /// # use openpgp::packet::signature::subpacket::SubpacketTag; /// use openpgp::policy::StandardPolicy; /// /// # fn main() -> openpgp::Result<()> { /// let p = &StandardPolicy::new(); /// /// let (cert, _) = CertBuilder::new().add_userid("Alice").generate()?; /// let mut signer = cert.primary_key().key() /// .clone().parts_into_secret()?.into_keypair()?; /// /// let vc = cert.with_policy(p, None)?; /// /// let sig = vc.direct_key_signature() /// .expect("CertBuilder always includes a direct key signature"); /// let sig = /// SignatureBuilder::from(sig.clone()) /// .set_preferred_key_server(&"https://keys.openpgp.org")? /// .sign_direct_key(&mut signer, None)?; /// # assert_eq!(sig /// # .hashed_area() /// # .iter() /// # .filter(|sp| sp.tag() == SubpacketTag::PreferredKeyServer) /// # .count(), /// # 1); /// /// // Merge in the new signature. /// let cert = cert.insert_packets(sig)?.0; /// # assert_eq!(cert.bad_signatures().count(), 0); /// # Ok(()) } /// ``` pub fn set_preferred_key_server<U>(mut self, uri: U) -> Result<Self> where U: AsRef<[u8]>, { self.hashed_area.replace(Subpacket::new( SubpacketValue::PreferredKeyServer(uri.as_ref().to_vec()), false)?)?; Ok(self) } /// Sets the Primary User ID subpacket. /// /// Adds a [Primary User ID subpacket] to the hashed subpacket /// area. This function first removes any Primary User ID /// subpacket from the hashed subpacket area. /// /// [Primary User ID subpacket]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.27 /// /// The Primary User ID subpacket indicates whether the associated /// User ID or User Attribute should be considered the primary /// User ID. It is possible that this is set on multiple User /// IDs. See the documentation for [`ValidCert::primary_userid`] for /// an explanation of how Sequoia resolves this ambiguity. /// /// [`ValidCert::primary_userid`]: crate::cert::ValidCert::primary_userid() /// /// # Examples /// /// Change the primary User ID: /// /// ``` /// use sequoia_openpgp as openpgp; /// use openpgp::cert::prelude::*; /// use openpgp::packet::prelude::*; /// # use openpgp::packet::signature::subpacket::SubpacketTag; /// use openpgp::policy::StandardPolicy; /// /// # fn main() -> openpgp::Result<()> { /// let p = &StandardPolicy::new(); /// /// let club = "Alice <alice@club.org>"; /// let home = "Alice <alice@home.org>"; /// /// // CertBuilder makes the first User ID (club) the primary User ID. /// let (cert, _) = CertBuilder::new() /// # // Create it in the past. /// # .set_creation_time(std::time::SystemTime::now() /// # - std::time::Duration::new(10, 0)) /// .add_userid(club) /// .add_userid(home) /// .generate()?; /// # assert_eq!(cert.userids().count(), 2); /// assert_eq!(cert.with_policy(p, None)?.primary_userid().unwrap().userid(), /// &UserID::from(club)); /// /// // Make the `home` User ID the primary User ID. /// /// // Derive a signer. /// let pk = cert.primary_key().key(); /// let mut signer = pk.clone().parts_into_secret()?.into_keypair()?; /// /// let mut sig = None; /// for ua in cert.with_policy(p, None)?.userids() { /// if ua.userid() == &UserID::from(home) { /// sig = Some(SignatureBuilder::from(ua.binding_signature().clone()) /// .set_primary_userid(true)? /// .sign_userid_binding(&mut signer, pk, ua.userid())?); /// # assert_eq!(sig.as_ref().unwrap() /// # .hashed_area() /// # .iter() /// # .filter(|sp| sp.tag() == SubpacketTag::PrimaryUserID) /// # .count(), /// # 1); /// break; /// } /// } /// assert!(sig.is_some()); /// /// let cert = cert.insert_packets(sig)?.0; /// /// assert_eq!(cert.with_policy(p, None)?.primary_userid().unwrap().userid(), /// &UserID::from(home)); /// # Ok(()) /// # } /// ``` pub fn set_primary_userid(mut self, primary: bool) -> Result<Self> { self.hashed_area.replace(Subpacket::new( SubpacketValue::PrimaryUserID(primary), true)?)?; Ok(self) } /// Sets the Policy URI subpacket. /// /// Adds a [Policy URI subpacket] to the hashed subpacket area. /// This function first removes any Policy URI subpacket from the /// hashed subpacket area. /// /// [Policy URI subpacket]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.28 /// /// The Policy URI subpacket contains a link to a policy document, /// which contains information about the conditions under which /// the signature was made. /// /// This subpacket is a type of preference. When looking up a /// preference, an OpenPGP implementation should first look for /// the subpacket on the binding signature of the User ID or the /// User Attribute used to locate the certificate (or the primary /// User ID, if it was addressed by Key ID or fingerprint). If /// the binding signature doesn't contain the subpacket, then the /// direct key signature should be checked. See the /// [`Preferences`] trait for details. /// /// Unless addressing different User IDs really should result in /// different behavior, it is best to only set this preference on /// the direct key signature. This guarantees that even if some /// or all User IDs are stripped, the behavior remains consistent. /// /// [`Preferences`]: crate::cert::Preferences /// /// # Examples /// /// Alice updates her direct key signature to include a Policy URI /// subpacket: /// /// ``` /// use sequoia_openpgp as openpgp; /// use openpgp::cert::prelude::*; /// use openpgp::packet::prelude::*; /// # use openpgp::packet::signature::subpacket::SubpacketTag; /// use openpgp::policy::StandardPolicy; /// /// # fn main() -> openpgp::Result<()> { /// let p = &StandardPolicy::new(); /// /// let (alice, _) = CertBuilder::new().add_userid("Alice").generate()?; /// let pk = alice.primary_key().key(); /// let mut signer = pk.clone().parts_into_secret()?.into_keypair()?; /// /// let sig = SignatureBuilder::from( /// alice /// .with_policy(p, None)? /// .direct_key_signature().expect("Direct key signature") /// .clone() /// ) /// .set_policy_uri("https://example.org/~alice/signing-policy.txt")? /// .sign_direct_key(&mut signer, None)?; /// # let mut sig = sig; /// # sig.verify_direct_key(signer.public(), pk)?; /// # assert_eq!(sig /// # .hashed_area() /// # .iter() /// # .filter(|sp| sp.tag() == SubpacketTag::PolicyURI) /// # .count(), /// # 1); /// /// // Merge it into the certificate. /// let alice = alice.insert_packets(sig)?.0; /// # /// # assert_eq!(alice.bad_signatures().count(), 0); /// # Ok(()) /// # } /// ``` pub fn set_policy_uri<U>(mut self, uri: U) -> Result<Self> where U: AsRef<[u8]>, { self.hashed_area.replace(Subpacket::new( SubpacketValue::PolicyURI(uri.as_ref().to_vec()), false)?)?; Ok(self) } /// Sets the Key Flags subpacket. /// /// Adds a [Key Flags subpacket] to the hashed subpacket area. /// This function first removes any Key Flags subpacket from the /// hashed subpacket area. /// /// [Key Flags subpacket]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.29 /// /// The Key Flags subpacket describes a key's capabilities /// (certification capable, signing capable, etc.). In the case /// of subkeys, the Key Flags are located on the subkey's binding /// signature. For primary keys, locating the correct Key Flags /// subpacket is more complex: First, the primary User ID is /// consulted. If the primary User ID contains a Key Flags /// subpacket, that is used. Otherwise, any direct key signature /// is considered. If that still doesn't contain a Key Flags /// packet, then the primary key should be assumed to be /// certification capable. /// /// # Examples /// /// Adds a new subkey, which is intended for encrypting data at /// rest, to a certificate: /// /// ``` /// use sequoia_openpgp as openpgp; /// use openpgp::cert::prelude::*; /// use openpgp::packet::prelude::*; /// use openpgp::policy::StandardPolicy; /// use openpgp::types::{ /// Curve, /// KeyFlags, /// SignatureType /// }; /// /// # fn main() -> openpgp::Result<()> { /// let p = &StandardPolicy::new(); /// /// // Generate a Cert, and create a keypair from the primary key. /// let (cert, _) = CertBuilder::new().generate()?; /// # assert_eq!(cert.keys().with_policy(p, None).alive().revoked(false) /// # .key_flags(&KeyFlags::empty().set_storage_encryption()).count(), /// # 0); /// let mut signer = cert.primary_key().key().clone() /// .parts_into_secret()?.into_keypair()?; /// /// // Generate a subkey and a binding signature. /// let subkey: Key<_, key::SubordinateRole> /// = Key6::generate_ecc(false, Curve::Cv25519)? /// .into(); /// let builder = signature::SignatureBuilder::new(SignatureType::SubkeyBinding) /// .set_key_flags(KeyFlags::empty().set_storage_encryption())?; /// let binding = subkey.bind(&mut signer, &cert, builder)?; /// /// // Now merge the key and binding signature into the Cert. /// let cert = cert.insert_packets(vec![Packet::from(subkey), /// binding.into()])?.0; /// /// # assert_eq!(cert.keys().with_policy(p, None).alive().revoked(false) /// # .key_flags(&KeyFlags::empty().set_storage_encryption()).count(), /// # 1); /// # Ok(()) } /// ``` pub fn set_key_flags(mut self, flags: KeyFlags) -> Result<Self> { self.hashed_area.replace(Subpacket::new( SubpacketValue::KeyFlags(flags), true)?)?; Ok(self) } /// Sets the Signer's User ID subpacket. /// /// Adds a [Signer's User ID subpacket] to the hashed subpacket /// area. This function first removes any Signer's User ID /// subpacket from the hashed subpacket area. /// /// [Signer's User ID subpacket]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.30 /// /// The Signer's User ID subpacket indicates, which User ID made /// the signature. This is useful when a key has multiple User /// IDs, which correspond to different roles. For instance, it is /// not uncommon to use the same certificate in private as well as /// for a club. /// /// # Examples /// /// Sign a message being careful to set the Signer's User ID /// subpacket to the user's private identity and not their club /// identity: /// /// ```rust /// use sequoia_openpgp as openpgp; /// use openpgp::cert::prelude::*; /// use openpgp::packet::prelude::*; /// # use openpgp::packet::signature::subpacket::SubpacketTag; /// use openpgp::types::SignatureType; /// /// # fn main() -> openpgp::Result<()> { /// let (cert, _) = CertBuilder::new() /// .add_userid("Alice <alice@home.org>") /// .add_userid("Alice (President) <alice@club.org>") /// .generate()?; /// let mut signer = cert.primary_key().key().clone() /// .parts_into_secret()?.into_keypair()?; /// /// let msg = "Speaking for myself, I agree."; /// /// let sig = SignatureBuilder::new(SignatureType::Binary) /// .set_signers_user_id(&b"Alice <alice@home.org>"[..])? /// .sign_message(&mut signer, msg)?; /// # let mut sig = sig; /// # assert!(sig.verify_message(signer.public(), msg).is_ok()); /// # assert_eq!(sig /// # .hashed_area() /// # .iter() /// # .filter(|sp| sp.tag() == SubpacketTag::SignersUserID) /// # .count(), /// # 1); /// # Ok(()) } /// ``` pub fn set_signers_user_id<U>(mut self, uid: U) -> Result<Self> where U: AsRef<[u8]>, { self.hashed_area.replace(Subpacket::new( SubpacketValue::SignersUserID(uid.as_ref().to_vec()), false)?)?; Ok(self) } /// Sets the value of the Reason for Revocation subpacket. /// /// Adds a [Reason For Revocation subpacket] to the hashed /// subpacket area. This function first removes any Reason For /// Revocation subpacket from the hashed subpacket /// area. /// /// [Reason For Revocation subpacket]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.31 /// /// The Reason For Revocation subpacket indicates why a key, User /// ID, or User Attribute is being revoked. It includes both a /// machine-readable code, and a human-readable string. The code /// is essential as it indicates to the OpenPGP implementation /// that reads the certificate whether the key was compromised (a /// hard revocation), or is no longer used (a soft revocation). /// In the former case, the OpenPGP implementation must /// conservatively consider all past signatures as suspect whereas /// in the latter case, past signatures can still be considered /// valid. /// /// # Examples /// /// Revoke a certificate whose private key material has been /// compromised: /// /// ```rust /// use sequoia_openpgp as openpgp; /// use openpgp::cert::prelude::*; /// # use openpgp::packet::signature::subpacket::SubpacketTag; /// use openpgp::policy::StandardPolicy; /// use openpgp::types::ReasonForRevocation; /// use openpgp::types::RevocationStatus; /// /// # fn main() -> openpgp::Result<()> { /// let p = &StandardPolicy::new(); /// /// let (cert, _) = CertBuilder::new().generate()?; /// assert_eq!(RevocationStatus::NotAsFarAsWeKnow, /// cert.revocation_status(p, None)); /// /// // Create and sign a revocation certificate. /// let mut signer = cert.primary_key().key().clone() /// .parts_into_secret()?.into_keypair()?; /// let sig = CertRevocationBuilder::new() /// .set_reason_for_revocation(ReasonForRevocation::KeyCompromised, /// b"It was the maid :/")? /// .build(&mut signer, &cert, None)?; /// /// // Merge it into the certificate. /// let cert = cert.insert_packets(sig.clone())?.0; /// /// // Now it's revoked. /// assert_eq!(RevocationStatus::Revoked(vec![ &sig ]), /// cert.revocation_status(p, None)); /// # assert_eq!(sig /// # .hashed_area() /// # .iter() /// # .filter(|sp| sp.tag() == SubpacketTag::ReasonForRevocation) /// # .count(), /// # 1); /// # Ok(()) /// # } /// ``` pub fn set_reason_for_revocation<R>(mut self, code: ReasonForRevocation, reason: R) -> Result<Self> where R: AsRef<[u8]>, { self.hashed_area.replace(Subpacket::new( SubpacketValue::ReasonForRevocation { code, reason: reason.as_ref().to_vec(), }, false)?)?; Ok(self) } /// Sets the Features subpacket. /// /// Adds a [Feature subpacket] to the hashed subpacket area. This /// function first removes any Feature subpacket from the hashed /// subpacket area. /// /// A Feature subpacket lists what OpenPGP features the user wants /// to use. When creating a message, features that the intended /// recipients do not support should not be used. However, /// because this information is rarely held up to date in /// practice, this information is only advisory, and /// implementations are allowed to infer what features the /// recipients support from contextual clues, e.g., their past /// behavior. /// /// [Feature subpacket]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.32 /// [features]: crate::types::Features /// /// This subpacket is a type of preference. When looking up a /// preference, an OpenPGP implementation should first look for /// the subpacket on the binding signature of the User ID or the /// User Attribute used to locate the certificate (or the primary /// User ID, if it was addressed by Key ID or fingerprint). If /// the binding signature doesn't contain the subpacket, then the /// direct key signature should be checked. See the /// [`Preferences`] trait for details. /// /// Unless addressing different User IDs really should result in /// different behavior, it is best to only set this preference on /// the direct key signature. This guarantees that even if some /// or all User IDs are stripped, the behavior remains consistent. /// /// [`Preferences`]: crate::cert::Preferences /// /// # Examples /// /// Update a certificate's binding signatures to indicate support for AEAD: /// /// ``` /// use sequoia_openpgp as openpgp; /// use openpgp::cert::prelude::*; /// use openpgp::packet::prelude::*; /// use openpgp::policy::StandardPolicy; /// use openpgp::types::{AEADAlgorithm, Features}; /// /// # fn main() -> openpgp::Result<()> { /// use sequoia_openpgp::types::SymmetricAlgorithm; /// let p = &StandardPolicy::new(); /// /// let (cert, _) = CertBuilder::new().add_userid("Alice").generate()?; /// /// // Derive a signer (the primary key is always certification capable). /// let pk = cert.primary_key().key(); /// let mut signer = pk.clone().parts_into_secret()?.into_keypair()?; /// /// let mut sigs = Vec::new(); /// /// let vc = cert.with_policy(p, None)?; /// /// if let Ok(sig) = vc.direct_key_signature() { /// sigs.push( /// SignatureBuilder::from(sig.clone()) /// .set_preferred_aead_ciphersuites(vec![(SymmetricAlgorithm::AES256, AEADAlgorithm::EAX) ])? /// .set_features( /// sig.features().unwrap_or_else(Features::sequoia) /// .set_seipdv2())? /// .sign_direct_key(&mut signer, None)?); /// } /// /// for ua in vc.userids() { /// let sig = ua.binding_signature(); /// sigs.push( /// SignatureBuilder::from(sig.clone()) /// .set_preferred_aead_ciphersuites(vec![(SymmetricAlgorithm::AES256, AEADAlgorithm::EAX) ])? /// .set_features( /// sig.features().unwrap_or_else(Features::sequoia) /// .set_seipdv2())? /// .sign_userid_binding(&mut signer, pk, ua.userid())?); /// } /// /// // Merge in the new signatures. /// let cert = cert.insert_packets(sigs)?.0; /// # assert_eq!(cert.bad_signatures().count(), 0); /// # Ok(()) /// # } /// ``` pub fn set_features(mut self, features: Features) -> Result<Self> { self.hashed_area.replace(Subpacket::new( SubpacketValue::Features(features), false)?)?; Ok(self) } /// Sets the Signature Target subpacket. /// /// Adds a [Signature Target subpacket] to the hashed subpacket /// area. This function first removes any Signature Target /// subpacket from the hashed subpacket area. /// /// The Signature Target subpacket is used to identify the target /// of a signature. This is used when revoking a signature, and /// by timestamp signatures. It contains a hash of the target /// signature. /// /// [Signature Target subpacket]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.33 pub fn set_signature_target<D>(mut self, pk_algo: PublicKeyAlgorithm, hash_algo: HashAlgorithm, digest: D) -> Result<Self> where D: AsRef<[u8]>, { self.hashed_area.replace(Subpacket::new( SubpacketValue::SignatureTarget { pk_algo, hash_algo, digest: digest.as_ref().to_vec(), }, true)?)?; Ok(self) } /// Sets the value of the Embedded Signature subpacket. /// /// Adds an [Embedded Signature subpacket] to the hashed /// subpacket area. This function first removes any Embedded /// Signature subpacket from both the hashed and the unhashed /// subpacket area. /// /// [Embedded Signature subpacket]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.34 /// /// The Embedded Signature subpacket is normally used to hold a /// [Primary Key Binding signature], which binds a /// signing-capable, authentication-capable, or /// certification-capable subkey to the primary key. Since this /// information is self-authenticating, it is usually stored in the /// unhashed subpacket area. /// /// [Primary Key Binding signature]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.1 /// /// # Examples /// /// Add a new signing-capable subkey to a certificate: /// /// ``` /// use sequoia_openpgp as openpgp; /// use openpgp::cert::prelude::*; /// use openpgp::packet::prelude::*; /// use openpgp::policy::StandardPolicy; /// use openpgp::types::KeyFlags; /// use openpgp::types::SignatureType; /// /// # fn main() -> openpgp::Result<()> { /// let p = &StandardPolicy::new(); /// /// let (cert, _) = CertBuilder::new().generate()?; /// # assert_eq!(cert.keys().count(), 1); /// /// let pk = cert.primary_key().key().clone().parts_into_secret()?; /// // Derive a signer. /// let mut pk_signer = pk.clone().into_keypair()?; /// /// // Generate a new signing subkey. /// let mut subkey: Key<_, _> = Key4::generate_rsa(3072)?.into(); /// // Derive a signer. /// let mut sk_signer = subkey.clone().into_keypair()?; /// /// // Create the binding signature. /// let sig = SignatureBuilder::new(SignatureType::SubkeyBinding) /// .set_key_flags(KeyFlags::empty().set_signing())? /// // And, the backsig. This is essential for subkeys that create signatures! /// .set_embedded_signature( /// SignatureBuilder::new(SignatureType::PrimaryKeyBinding) /// .sign_primary_key_binding(&mut sk_signer, &pk, &subkey)?)? /// .sign_subkey_binding(&mut pk_signer, None, &subkey)?; /// /// let cert = cert.insert_packets(vec![Packet::SecretSubkey(subkey), /// sig.into()])?.0; /// /// assert_eq!(cert.keys().count(), 2); /// # Ok(()) /// # } /// ``` pub fn set_embedded_signature(mut self, signature: Signature) -> Result<Self> { self.hashed_area.replace(Subpacket::new( SubpacketValue::EmbeddedSignature(signature), true)?)?; self.unhashed_area.remove_all(SubpacketTag::EmbeddedSignature); Ok(self) } /// Sets the Issuer Fingerprint subpacket. /// /// Adds an [Issuer Fingerprint subpacket] to the hashed /// subpacket area. Unlike [`add_issuer_fingerprint`], this /// function first removes any existing Issuer Fingerprint /// subpackets from the hashed and unhashed subpacket area. /// /// [Issuer Fingerprint subpacket]: https://www.rfc-editor.org/rfc/rfc9580.html#name-issuer-fingerprint /// [`add_issuer_fingerprint`]: super::SignatureBuilder::add_issuer_fingerprint() /// /// The Issuer Fingerprint subpacket is used when processing a /// signature to identify which certificate created the signature. /// Even though this information is self-authenticating (the act of /// validating the signature authenticates the subpacket), it is /// stored in the hashed subpacket area. This has the advantage /// that the signer authenticates the set of issuers. /// Furthermore, it makes handling of the resulting signatures /// more robust: If there are two signatures that are equal /// modulo the contents of the unhashed area, there is the /// question of how to merge the information in the unhashed /// areas. Storing issuer information in the hashed area avoids /// this problem. /// /// When creating a signature using a SignatureBuilder or the /// [streaming `Signer`], it is not necessary to explicitly set /// this subpacket: those functions automatically set both the /// Issuer Fingerprint subpacket, and the [Issuer subpacket] (set /// using [`SignatureBuilder::set_issuer`]), if they have not been /// set explicitly. /// /// [streaming `Signer`]: crate::serialize::stream::Signer /// [Issuer subpacket]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.12 /// [`SignatureBuilder::set_issuer`]: super::SignatureBuilder::set_issuer() /// /// # Examples /// /// It is possible to use the same key material with different /// OpenPGP keys. This is useful when the OpenPGP format is /// upgraded, but not all deployed implementations support the new /// format. Here, Alice signs a message, and adds the fingerprint /// of her v4 key and her v5 key indicating that the recipient can /// use either key to verify the message: /// /// ``` /// use sequoia_openpgp as openpgp; /// # use openpgp::cert::prelude::*; /// use openpgp::packet::prelude::*; /// # use openpgp::packet::signature::subpacket::SubpacketTag; /// use openpgp::types::SignatureType; /// /// # fn main() -> openpgp::Result<()> { /// # /// # let (alicev4, _) = /// # CertBuilder::general_purpose(Some("alice@example.org")) /// # .generate()?; /// # let mut alices_signer = alicev4.primary_key().key().clone().parts_into_secret()?.into_keypair()?; /// # let (alicev5, _) = /// # CertBuilder::general_purpose(Some("alice@example.org")) /// # .generate()?; /// # /// let msg = b"Hi!"; /// /// let sig = SignatureBuilder::new(SignatureType::Binary) /// .set_issuer_fingerprint(alicev4.fingerprint())? /// .add_issuer_fingerprint(alicev5.fingerprint())? /// .sign_message(&mut alices_signer, msg)?; /// # let mut sig = sig; /// # assert!(sig.verify_message(alices_signer.public(), msg).is_ok()); /// # assert_eq!(sig /// # .hashed_area() /// # .iter() /// # .filter(|sp| sp.tag() == SubpacketTag::Issuer) /// # .count(), /// # 0); /// # assert_eq!(sig /// # .hashed_area() /// # .iter() /// # .filter(|sp| sp.tag() == SubpacketTag::IssuerFingerprint) /// # .count(), /// # 2); /// # Ok(()) } /// ``` pub fn set_issuer_fingerprint(mut self, fp: Fingerprint) -> Result<Self> { self.hashed_area.replace(Subpacket::new( SubpacketValue::IssuerFingerprint(fp), false)?)?; self.unhashed_area.remove_all(SubpacketTag::IssuerFingerprint); Ok(self) } /// Adds an Issuer Fingerprint subpacket. /// /// Adds an [Issuer Fingerprint subpacket] to the hashed /// subpacket area. Unlike [`set_issuer_fingerprint`], this /// function does not first remove any existing Issuer Fingerprint /// subpacket from neither the hashed nor the unhashed subpacket /// area. /// /// [Issuer Fingerprint subpacket]: https://www.rfc-editor.org/rfc/rfc9580.html#name-issuer-fingerprint /// [`set_issuer_fingerprint`]: super::SignatureBuilder::set_issuer_fingerprint() /// /// The Issuer Fingerprint subpacket is used when processing a /// signature to identify which certificate created the signature. /// Even though this information is self-authenticating (the act of /// validating the signature authenticates the subpacket), it is /// stored in the hashed subpacket area. This has the advantage /// that the signer authenticates the set of issuers. /// Furthermore, it makes handling of the resulting signatures /// more robust: If there are two signatures that are equal /// modulo the contents of the unhashed area, there is the /// question of how to merge the information in the unhashed /// areas. Storing issuer information in the hashed area avoids /// this problem. /// /// When creating a signature using a SignatureBuilder or the /// [streaming `Signer`], it is not necessary to explicitly set /// this subpacket: those functions automatically set both the /// Issuer Fingerprint subpacket, and the [Issuer subpacket] (set /// using [`SignatureBuilder::set_issuer`]), if they have not been /// set explicitly. /// /// [streaming `Signer`]: crate::serialize::stream::Signer /// [Issuer subpacket]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.12 /// [`SignatureBuilder::set_issuer`]: super::SignatureBuilder::set_issuer() /// /// # Examples /// /// It is possible to use the same key material with different /// OpenPGP keys. This is useful when the OpenPGP format is /// upgraded, but not all deployed implementations support the new /// format. Here, Alice signs a message, and adds the fingerprint /// of her v4 key and her v5 key indicating that the recipient can /// use either key to verify the message: /// /// ``` /// use sequoia_openpgp as openpgp; /// # use openpgp::cert::prelude::*; /// use openpgp::packet::prelude::*; /// # use openpgp::packet::signature::subpacket::SubpacketTag; /// use openpgp::types::SignatureType; /// /// # fn main() -> openpgp::Result<()> { /// # /// # let (alicev4, _) = /// # CertBuilder::general_purpose(Some("alice@example.org")) /// # .generate()?; /// # let mut alices_signer = alicev4.primary_key().key().clone().parts_into_secret()?.into_keypair()?; /// # let (alicev5, _) = /// # CertBuilder::general_purpose(Some("alice@example.org")) /// # .generate()?; /// # /// let msg = b"Hi!"; /// /// let sig = SignatureBuilder::new(SignatureType::Binary) /// .set_issuer_fingerprint(alicev4.fingerprint())? /// .add_issuer_fingerprint(alicev5.fingerprint())? /// .sign_message(&mut alices_signer, msg)?; /// # let mut sig = sig; /// # assert!(sig.verify_message(alices_signer.public(), msg).is_ok()); /// # assert_eq!(sig /// # .hashed_area() /// # .iter() /// # .filter(|sp| sp.tag() == SubpacketTag::Issuer) /// # .count(), /// # 0); /// # assert_eq!(sig /// # .hashed_area() /// # .iter() /// # .filter(|sp| sp.tag() == SubpacketTag::IssuerFingerprint) /// # .count(), /// # 2); /// # Ok(()) } /// ``` pub fn add_issuer_fingerprint(mut self, fp: Fingerprint) -> Result<Self> { self.hashed_area.add(Subpacket::new( SubpacketValue::IssuerFingerprint(fp), false)?)?; Ok(self) } /// Sets the Preferred AEAD Ciphersuites subpacket. /// /// Replaces any [Preferred AEAD Ciphersuites subpacket] in the /// hashed subpacket area with a new subpacket containing the /// specified value. That is, this function first removes any /// Preferred AEAD Ciphersuites subpacket from the hashed /// subpacket area, and then adds a Preferred AEAD Ciphersuites /// subpacket. /// /// [Preferred AEAD Ciphersuites subpacket]: https://www.rfc-editor.org/rfc/rfc9580.html#name-preferred-aead-ciphersuites /// /// The Preferred AEAD Ciphersuites subpacket indicates what AEAD /// ciphersuites the key holder prefers ordered by preference. If /// this is set, then the `Version 2 Symmetrically Encrypted and /// Integrity Protected Data packet` feature flag should in the /// [Features subpacket] should also be set. /// /// [Features subpacket]: https://www.rfc-editor.org/rfc/rfc9580.html#name-features /// /// This subpacket is a type of preference. When looking up a /// preference, an OpenPGP implementation should first look for /// the subpacket on the binding signature of the User ID or the /// User Attribute used to locate the certificate (or the primary /// User ID, if it was addressed by Key ID or fingerprint). If /// the binding signature doesn't contain the subpacket, then the /// direct key signature should be checked. See the /// [`Preferences`] trait for details. /// /// Unless addressing different User IDs really should result in /// different behavior, it is best to only set this preference on /// the direct key signature. This guarantees that even if some /// or all User IDs are stripped, the behavior remains consistent. /// /// [`Preferences`]: crate::cert::Preferences /// /// # Examples /// /// Update a certificate's binding signatures to indicate support for AEAD: /// /// ``` /// use sequoia_openpgp as openpgp; /// use openpgp::cert::prelude::*; /// use openpgp::packet::prelude::*; /// use openpgp::policy::StandardPolicy; /// use openpgp::types::{AEADAlgorithm, Features, SymmetricAlgorithm}; /// /// # fn main() -> openpgp::Result<()> { /// let p = &StandardPolicy::new(); /// /// let (cert, _) = CertBuilder::new().add_userid("Alice").generate()?; /// /// // Derive a signer (the primary key is always certification capable). /// let pk = cert.primary_key().key(); /// let mut signer = pk.clone().parts_into_secret()?.into_keypair()?; /// /// // Update the cert to advocate support for SEIPDv2 with a /// // preferred AEAD cipher suite. /// let symm = SymmetricAlgorithm::default(); /// let aead = AEADAlgorithm::default(); /// /// let mut sigs = Vec::new(); /// /// let vc = cert.with_policy(p, None)?; /// /// // Update the direct key signature. /// if let Ok(sig) = vc.direct_key_signature() { /// sigs.push( /// SignatureBuilder::from(sig.clone()) /// .set_preferred_aead_ciphersuites(vec![(symm, aead)])? /// .set_features( /// sig.features().unwrap_or_else(Features::sequoia) /// .set_seipdv2())? /// .sign_direct_key(&mut signer, None)?); /// } /// /// // Update the user ID binding signatures. /// for ua in vc.userids() { /// let sig = ua.binding_signature(); /// sigs.push( /// SignatureBuilder::from(sig.clone()) /// .set_preferred_aead_ciphersuites(vec![(symm, aead)])? /// .set_features( /// sig.features().unwrap_or_else(Features::sequoia) /// .set_seipdv2())? /// .sign_userid_binding(&mut signer, pk, ua.userid())?); /// } /// /// // Merge in the new signatures. /// let cert = cert.insert_packets(sigs)?.0; /// # assert_eq!(cert.bad_signatures().count(), 0); /// # Ok(()) /// # } /// ``` pub fn set_preferred_aead_ciphersuites( mut self, preferences: Vec<(SymmetricAlgorithm, AEADAlgorithm)>) -> Result<Self> { self.hashed_area.replace(Subpacket::new( SubpacketValue::PreferredAEADCiphersuites(preferences), false)?)?; Ok(self) } /// Sets the Intended Recipient subpacket. /// /// Replaces any [Intended Recipient subpacket] in the hashed /// subpacket area with one new subpacket for each of the /// specified values. That is, unlike /// [`SignatureBuilder::add_intended_recipient`], this function /// first removes any Intended Recipient subpackets from the /// hashed subpacket area, and then adds new ones. /// /// [Intended Recipient subpacket]: https://www.rfc-editor.org/rfc/rfc9580.html#name-intended-recipient-fingerpr /// [`SignatureBuilder::add_intended_recipient`]: super::SignatureBuilder::add_intended_recipient() /// /// The Intended Recipient subpacket holds the fingerprint of a /// certificate. /// /// When signing a message, the message should include one such /// subpacket for each intended recipient. Note: not all messages /// have intended recipients. For instance, when signing an open /// letter, or a software release, the message is intended for /// anyone. /// /// When processing a signature, the application should ensure /// that if there are any such subpackets, then one of the /// subpackets identifies the recipient's certificate (or user /// signed the message). If this is not the case, then an /// attacker may have taken the message out of its original /// context. For instance, if Alice sends a signed email to Bob, /// with the content: "I agree to the contract", and Bob forwards /// that message to Carol, then Carol may think that Alice agreed /// to a contract with her if the signature appears to be valid! /// By adding an intended recipient, it is possible for Carol's /// mail client to warn her that although Alice signed the /// message, the content was intended for Bob and not for her. /// /// # Examples /// /// To create a signed message intended for both Bob and Carol, /// Alice adds an intended recipient subpacket for each of their /// certificates. Because this function first removes any /// existing Intended Recipient subpackets both recipients must be /// added at once (cf. [`SignatureBuilder::add_intended_recipient`]): /// /// ``` /// use sequoia_openpgp as openpgp; /// use openpgp::cert::prelude::*; /// use openpgp::packet::signature::SignatureBuilder; /// use openpgp::types::SignatureType; /// /// # fn main() -> openpgp::Result<()> { /// # /// # let (alice, _) = /// # CertBuilder::general_purpose(Some("alice@example.org")) /// # .generate()?; /// # let mut alices_signer = alice.primary_key().key().clone().parts_into_secret()?.into_keypair()?; /// # let (bob, _) = /// # CertBuilder::general_purpose(Some("bob@example.org")) /// # .generate()?; /// # let (carol, _) = /// # CertBuilder::general_purpose(Some("carol@example.org")) /// # .generate()?; /// # /// let msg = b"Let's do it!"; /// /// let sig = SignatureBuilder::new(SignatureType::Binary) /// .set_intended_recipients(&[ bob.fingerprint(), carol.fingerprint() ])? /// .sign_message(&mut alices_signer, msg)?; /// # let mut sig = sig; /// # assert!(sig.verify_message(alices_signer.public(), msg).is_ok()); /// # assert_eq!(sig.intended_recipients().count(), 2); /// # Ok(()) } /// ``` pub fn set_intended_recipients<T>(mut self, recipients: T) -> Result<Self> where T: AsRef<[Fingerprint]> { self.hashed_area.remove_all(SubpacketTag::IntendedRecipient); for fp in recipients.as_ref().iter() { self.hashed_area.add( Subpacket::new(SubpacketValue::IntendedRecipient(fp.clone()), false)?)?; } Ok(self) } /// Adds an Intended Recipient subpacket. /// /// Adds an [Intended Recipient subpacket] to the hashed subpacket /// area. Unlike [`SignatureBuilder::set_intended_recipients`], this function does /// not first remove any Intended Recipient subpackets from the /// hashed subpacket area. /// /// [Intended Recipient subpacket]: https://www.rfc-editor.org/rfc/rfc9580.html#name-intended-recipient-fingerpr /// [`SignatureBuilder::set_intended_recipients`]: super::SignatureBuilder::set_intended_recipients() /// /// The Intended Recipient subpacket holds the fingerprint of a /// certificate. /// /// When signing a message, the message should include one such /// subpacket for each intended recipient. Note: not all messages /// have intended recipients. For instance, when signing an open /// letter, or a software release, the message is intended for /// anyone. /// /// When processing a signature, the application should ensure /// that if there are any such subpackets, then one of the /// subpackets identifies the recipient's certificate (or user /// signed the message). If this is not the case, then an /// attacker may have taken the message out of its original /// context. For instance, if Alice sends a signed email to Bob, /// with the content: "I agree to the contract", and Bob forwards /// that message to Carol, then Carol may think that Alice agreed /// to a contract with her if the signature appears to be valid! /// By adding an intended recipient, it is possible for Carol's /// mail client to warn her that although Alice signed the /// message, the content was intended for Bob and not for her. /// /// # Examples /// /// To create a signed message intended for both Bob and Carol, /// Alice adds an Intended Recipient subpacket for each of their /// certificates. Unlike /// [`SignatureBuilder::set_intended_recipients`], which first /// removes any existing Intended Recipient subpackets, with this /// function we can add one recipient after the other: /// /// [`SignatureBuilder::set_intended_recipients`]: NotationDataFlags::set_intended_recipients() /// /// ``` /// use sequoia_openpgp as openpgp; /// use openpgp::cert::prelude::*; /// use openpgp::packet::signature::SignatureBuilder; /// use openpgp::types::SignatureType; /// /// # fn main() -> openpgp::Result<()> { /// # /// # let (alice, _) = /// # CertBuilder::general_purpose(Some("alice@example.org")) /// # .generate()?; /// # let mut alices_signer = alice.primary_key().key().clone().parts_into_secret()?.into_keypair()?; /// # let (bob, _) = /// # CertBuilder::general_purpose(Some("bob@example.org")) /// # .generate()?; /// # let (carol, _) = /// # CertBuilder::general_purpose(Some("carol@example.org")) /// # .generate()?; /// # /// let msg = b"Let's do it!"; /// /// let sig = SignatureBuilder::new(SignatureType::Binary) /// .add_intended_recipient(bob.fingerprint())? /// .add_intended_recipient(carol.fingerprint())? /// .sign_message(&mut alices_signer, msg)?; /// # let mut sig = sig; /// # assert!(sig.verify_message(alices_signer.public(), msg).is_ok()); /// # assert_eq!(sig.intended_recipients().count(), 2); /// # Ok(()) } /// ``` pub fn add_intended_recipient(mut self, recipient: Fingerprint) -> Result<Self> { self.hashed_area.add( Subpacket::new(SubpacketValue::IntendedRecipient(recipient), false)?)?; Ok(self) } /// Adds an approved certifications subpacket. /// /// This feature is [experimental](crate#experimental-features). /// /// Allows the certificate holder to attest to third party /// certifications, allowing them to be distributed with the /// certificate. This can be used to address certificate flooding /// concerns. /// /// Sorts the digests and adds an [Approved Certification /// subpacket] to the hashed subpacket area. The digests must be /// calculated using the same hash algorithm that is used in the /// resulting signature. To attest a signature, hash it with /// [`super::Signature::hash_for_confirmation`]. /// /// Note: The maximum size of the hashed signature subpacket area /// constrains the number of attestations that can be stored in a /// signature. If you need to attest to more certifications, /// split the digests into chunks and create multiple approved key /// signatures with the same creation time. /// /// See [Approved Certification subpacket] for details. /// /// [Approved Certification subpacket]: https://www.ietf.org/archive/id/draft-dkg-openpgp-1pa3pc-02.html#approved-certifications-subpacket pub fn set_approved_certifications<A, C>(mut self, certifications: C) -> Result<Self> where C: IntoIterator<Item = A>, A: AsRef<[u8]>, { let mut digests: Vec<_> = certifications.into_iter() .map(|d| d.as_ref().to_vec().into_boxed_slice()) .collect(); if let Some(first) = digests.get(0) { if digests.iter().any(|d| d.len() != first.len()) { return Err(Error::InvalidOperation( "Inconsistent digest algorithm used".into()).into()); } } // Hashes SHOULD be sorted. This optimizes lookups for the // consumer and provides a canonical form. digests.sort_unstable(); self.hashed_area_mut().replace( Subpacket::new( SubpacketValue::ApprovedCertifications(digests), true)?)?; Ok(self) } } #[test] fn accessors() { use crate::types::Curve; let pk_algo = PublicKeyAlgorithm::EdDSA; let hash_algo = HashAlgorithm::SHA512; let mut sig = signature::SignatureBuilder::new(crate::types::SignatureType::Binary); let mut key: crate::packet::key::SecretKey = crate::packet::key::Key6::generate_ecc(true, Curve::Ed25519).unwrap().into(); let mut keypair = key.clone().into_keypair().unwrap(); let hash = hash_algo.context().unwrap().for_signature(key.version()); // Cook up a timestamp without ns resolution. use std::convert::TryFrom; let now: time::SystemTime = Timestamp::try_from(crate::now()).unwrap().into(); sig = sig.set_signature_creation_time(now).unwrap(); let sig_ = sig.clone().sign_hash(&mut keypair, hash.clone()).unwrap(); assert_eq!(sig_.signature_creation_time(), Some(now)); let zero_s = time::Duration::new(0, 0); let minute = time::Duration::new(60, 0); let five_minutes = 5 * minute; let ten_minutes = 10 * minute; sig = sig.set_signature_validity_period(five_minutes).unwrap(); let sig_ = sig.clone().sign_hash(&mut keypair, hash.clone()).unwrap(); assert_eq!(sig_.signature_validity_period(), Some(five_minutes)); assert!(sig_.signature_alive(None, zero_s).is_ok()); assert!(sig_.signature_alive(now, zero_s).is_ok()); assert!(!sig_.signature_alive(now - five_minutes, zero_s).is_ok()); assert!(!sig_.signature_alive(now + ten_minutes, zero_s).is_ok()); sig = sig.modify_hashed_area(|mut a| { a.remove_all(SubpacketTag::SignatureExpirationTime); Ok(a) }).unwrap(); let sig_ = sig.clone().sign_hash(&mut keypair, hash.clone()).unwrap(); assert_eq!(sig_.signature_validity_period(), None); assert!(sig_.signature_alive(None, zero_s).is_ok()); assert!(sig_.signature_alive(now, zero_s).is_ok()); assert!(!sig_.signature_alive(now - five_minutes, zero_s).is_ok()); assert!(sig_.signature_alive(now + ten_minutes, zero_s).is_ok()); sig = sig.set_exportable_certification(true).unwrap(); let sig_ = sig.clone().sign_hash(&mut keypair, hash.clone()).unwrap(); assert_eq!(sig_.exportable_certification(), Some(true)); sig = sig.set_exportable_certification(false).unwrap(); let sig_ = sig.clone().sign_hash(&mut keypair, hash.clone()).unwrap(); assert_eq!(sig_.exportable_certification(), Some(false)); sig = sig.set_trust_signature(2, 3).unwrap(); let sig_ = sig.clone().sign_hash(&mut keypair, hash.clone()).unwrap(); assert_eq!(sig_.trust_signature(), Some((2, 3))); sig = sig.set_regular_expression(b"foobar").unwrap(); let sig_ = sig.clone().sign_hash(&mut keypair, hash.clone()).unwrap(); assert_eq!(sig_.regular_expressions().collect::<Vec<&[u8]>>(), vec![ &b"foobar"[..] ]); sig = sig.set_revocable(true).unwrap(); let sig_ = sig.clone().sign_hash(&mut keypair, hash.clone()).unwrap(); assert_eq!(sig_.revocable(), Some(true)); sig = sig.set_revocable(false).unwrap(); let sig_ = sig.clone().sign_hash(&mut keypair, hash.clone()).unwrap(); assert_eq!(sig_.revocable(), Some(false)); key.set_creation_time(now).unwrap(); sig = sig.set_key_validity_period(Some(five_minutes)).unwrap(); let sig_ = sig.clone().sign_hash(&mut keypair, hash.clone()).unwrap(); assert_eq!(sig_.key_validity_period(), Some(five_minutes)); assert!(sig_.key_alive(&key, None).is_ok()); assert!(sig_.key_alive(&key, now).is_ok()); assert!(!sig_.key_alive(&key, now - five_minutes).is_ok()); assert!(!sig_.key_alive(&key, now + ten_minutes).is_ok()); sig = sig.set_key_validity_period(None).unwrap(); let sig_ = sig.clone().sign_hash(&mut keypair, hash.clone()).unwrap(); assert_eq!(sig_.key_validity_period(), None); assert!(sig_.key_alive(&key, None).is_ok()); assert!(sig_.key_alive(&key, now).is_ok()); assert!(!sig_.key_alive(&key, now - five_minutes).is_ok()); assert!(sig_.key_alive(&key, now + ten_minutes).is_ok()); let pref = vec![SymmetricAlgorithm::AES256, SymmetricAlgorithm::AES192, SymmetricAlgorithm::AES128]; sig = sig.set_preferred_symmetric_algorithms(pref.clone()).unwrap(); let sig_ = sig.clone().sign_hash(&mut keypair, hash.clone()).unwrap(); assert_eq!(sig_.preferred_symmetric_algorithms(), Some(&pref[..])); let fp = Fingerprint::from_bytes(4, b"bbbbbbbbbbbbbbbbbbbb").unwrap(); let rk = RevocationKey::new(pk_algo, fp.clone(), true); sig = sig.set_revocation_key(rk.clone()).unwrap(); sig = sig.set_revocation_key(rk.clone()).unwrap(); let sig_ = sig.clone().sign_hash(&mut keypair, hash.clone()).unwrap(); assert_eq!(sig_.revocation_keys().next().unwrap(), &rk); assert_eq!(sig_.revocation_keys().count(), 1); sig = sig.add_revocation_key(rk.clone()).unwrap(); sig = sig.add_revocation_key(rk.clone()).unwrap(); let sig_ = sig.clone().sign_hash(&mut keypair, hash.clone()).unwrap(); assert_eq!(sig_.revocation_keys().count(), 3); sig = sig.set_issuer(fp.clone().into()).unwrap(); let sig_ = sig.clone().sign_hash(&mut keypair, hash.clone()).unwrap(); assert_eq!(sig_.issuers().collect::<Vec<_>>(), vec![ &fp.clone().into() ]); let pref = vec![HashAlgorithm::SHA512, HashAlgorithm::SHA384, HashAlgorithm::SHA256]; sig = sig.set_preferred_hash_algorithms(pref.clone()).unwrap(); let sig_ = sig.clone().sign_hash(&mut keypair, hash.clone()).unwrap(); assert_eq!(sig_.preferred_hash_algorithms(), Some(&pref[..])); let pref = vec![CompressionAlgorithm::BZip2, CompressionAlgorithm::Zlib, CompressionAlgorithm::Zip]; sig = sig.set_preferred_compression_algorithms(pref.clone()).unwrap(); let sig_ = sig.clone().sign_hash(&mut keypair, hash.clone()).unwrap(); assert_eq!(sig_.preferred_compression_algorithms(), Some(&pref[..])); let pref = KeyServerPreferences::empty() .set_no_modify(); sig = sig.set_key_server_preferences(pref.clone()).unwrap(); let sig_ = sig.clone().sign_hash(&mut keypair, hash.clone()).unwrap(); assert_eq!(sig_.key_server_preferences().unwrap(), pref); sig = sig.set_primary_userid(true).unwrap(); let sig_ = sig.clone().sign_hash(&mut keypair, hash.clone()).unwrap(); assert_eq!(sig_.primary_userid(), Some(true)); sig = sig.set_primary_userid(false).unwrap(); let sig_ = sig.clone().sign_hash(&mut keypair, hash.clone()).unwrap(); assert_eq!(sig_.primary_userid(), Some(false)); sig = sig.set_policy_uri(b"foobar").unwrap(); let sig_ = sig.clone().sign_hash(&mut keypair, hash.clone()).unwrap(); assert_eq!(sig_.policy_uri(), Some(&b"foobar"[..])); let key_flags = KeyFlags::empty() .set_certification() .set_signing(); sig = sig.set_key_flags(key_flags.clone()).unwrap(); let sig_ = sig.clone().sign_hash(&mut keypair, hash.clone()).unwrap(); assert_eq!(sig_.key_flags().unwrap(), key_flags); sig = sig.set_signers_user_id(b"foobar").unwrap(); let sig_ = sig.clone().sign_hash(&mut keypair, hash.clone()).unwrap(); assert_eq!(sig_.signers_user_id(), Some(&b"foobar"[..])); sig = sig.set_reason_for_revocation(ReasonForRevocation::KeyRetired, b"foobar").unwrap(); let sig_ = sig.clone().sign_hash(&mut keypair, hash.clone()).unwrap(); assert_eq!(sig_.reason_for_revocation(), Some((ReasonForRevocation::KeyRetired, &b"foobar"[..]))); let feats = Features::empty().set_seipdv1(); sig = sig.set_features(feats.clone()).unwrap(); let sig_ = sig.clone().sign_hash(&mut keypair, hash.clone()).unwrap(); assert_eq!(sig_.features().unwrap(), feats); let feats = Features::empty().set_seipdv2(); sig = sig.set_features(feats.clone()).unwrap(); let sig_ = sig.clone().sign_hash(&mut keypair, hash.clone()).unwrap(); assert_eq!(sig_.features().unwrap(), feats); let digest = vec![0; hash_algo.digest_size().unwrap()]; sig = sig.set_signature_target(pk_algo, hash_algo, &digest).unwrap(); let sig_ = sig.clone().sign_hash(&mut keypair, hash.clone()).unwrap(); assert_eq!(sig_.signature_target(), Some((pk_algo, hash_algo, &digest[..]))); let embedded_sig = sig_.clone(); sig = sig.set_embedded_signature(embedded_sig.clone()).unwrap(); let sig_ = sig.clone().sign_hash(&mut keypair, hash.clone()).unwrap(); assert_eq!(sig_.embedded_signatures().next(), Some(&embedded_sig)); sig = sig.set_issuer_fingerprint(fp.clone()).unwrap(); let sig_ = sig.clone().sign_hash(&mut keypair, hash.clone()).unwrap(); assert_eq!(sig_.issuer_fingerprints().collect::<Vec<_>>(), vec![ &fp ]); let pref = vec![(SymmetricAlgorithm::AES128, AEADAlgorithm::EAX), (SymmetricAlgorithm::AES128, AEADAlgorithm::OCB)]; sig = sig.set_preferred_aead_ciphersuites(pref.clone()).unwrap(); let sig_ = sig.clone().sign_hash(&mut keypair, hash.clone()).unwrap(); assert_eq!(sig_.preferred_aead_ciphersuites(), Some(&pref[..])); let fps = vec![ Fingerprint::from_bytes(4, b"aaaaaaaaaaaaaaaaaaaa").unwrap(), Fingerprint::from_bytes(4, b"bbbbbbbbbbbbbbbbbbbb").unwrap(), ]; sig = sig.set_intended_recipients(fps.clone()).unwrap(); let sig_ = sig.clone().sign_hash(&mut keypair, hash.clone()).unwrap(); assert_eq!(sig_.intended_recipients().collect::<Vec<&Fingerprint>>(), fps.iter().collect::<Vec<&Fingerprint>>()); sig = sig.set_notation("test@example.org", &[0, 1, 2], None, false) .unwrap(); let sig_ = sig.clone().sign_hash(&mut keypair, hash.clone()).unwrap(); assert_eq!(sig_.notation("test@example.org").collect::<Vec<&[u8]>>(), vec![&[0, 1, 2]]); sig = sig.add_notation("test@example.org", &[3, 4, 5], None, false) .unwrap(); let sig_ = sig.clone().sign_hash(&mut keypair, hash.clone()).unwrap(); assert_eq!(sig_.notation("test@example.org").collect::<Vec<&[u8]>>(), vec![&[0, 1, 2], &[3, 4, 5]]); sig = sig.set_notation("test@example.org", &[6, 7, 8], None, false) .unwrap(); let sig_ = sig.clone().sign_hash(&mut keypair, hash.clone()).unwrap(); assert_eq!(sig_.notation("test@example.org").collect::<Vec<&[u8]>>(), vec![&[6, 7, 8]]); } #[cfg(feature = "compression-deflate")] #[test] fn subpacket_test_1 () { use crate::Packet; use crate::PacketPile; use crate::parse::Parse; let pile = PacketPile::from_bytes(crate::tests::message("signed.gpg")).unwrap(); eprintln!("PacketPile has {} top-level packets.", pile.children().len()); eprintln!("PacketPile: {:?}", pile); let mut count = 0; for p in pile.descendants() { if let &Packet::Signature(ref sig) = p { count += 1; let mut got2 = false; let mut got16 = false; let mut got33 = false; for i in 0..255 { if let Some(sb) = sig.subpacket(i.into()) { if i == 2 { got2 = true; assert!(!sb.critical); } else if i == 16 { got16 = true; assert!(!sb.critical); } else if i == 33 { got33 = true; assert!(!sb.critical); } else { panic!("Unexpectedly found subpacket {}", i); } } } assert!(got2 && got16 && got33); let hex = format!("{:X}", sig.issuer_fingerprints().next().unwrap()); assert!( hex == "7FAF6ED7238143557BDF7ED26863C9AD5B4D22D3" || hex == "C03FA6411B03AE12576461187223B56678E02528"); } } // 2 packets have subpackets. assert_eq!(count, 2); } #[test] #[allow(deprecated)] fn subpacket_test_2() { use crate::Packet; use crate::parse::Parse; use crate::PacketPile; // Test # Subpacket // 1 2 3 4 5 6 SignatureCreationTime // * SignatureExpirationTime // 2 ExportableCertification // 6 TrustSignature // 6 RegularExpression // 3 Revocable // 1 7 KeyExpirationTime // 1 PreferredSymmetricAlgorithms // 3 RevocationKey // 1 3 7 Issuer // 1 3 5 NotationData // 1 PreferredHashAlgorithms // 1 PreferredCompressionAlgorithms // 1 KeyServerPreferences // * PreferredKeyServer // * PrimaryUserID // * PolicyURI // 1 KeyFlags // * SignersUserID // 4 ReasonForRevocation // 1 Features // * SignatureTarget // 7 EmbeddedSignature // 1 3 7 IssuerFingerprint // // XXX: The subpackets marked with * are not tested. let pile = PacketPile::from_bytes( crate::tests::key("subpackets/shaw.gpg")).unwrap(); // Test #1 if let (Some(&Packet::PublicKey(ref key)), Some(&Packet::Signature(ref sig))) = (pile.children().next(), pile.children().nth(2)) { // tag: 2, SignatureCreationTime(1515791508) } // tag: 9, KeyExpirationTime(63072000) } // tag: 11, PreferredSymmetricAlgorithms([9, 8, 7, 2]) } // tag: 16, Issuer(KeyID("F004 B9A4 5C58 6126")) } // tag: 20, NotationData(NotationData { flags: 2147483648, name: [114, 97, 110, 107, 64, 110, 97, 118, 121, 46, 109, 105, 108], value: [109, 105, 100, 115, 104, 105, 112, 109, 97, 110] }) } // tag: 21, PreferredHashAlgorithms([8, 9, 10, 11, 2]) } // tag: 22, PreferredCompressionAlgorithms([2, 3, 1]) } // tag: 23, KeyServerPreferences([128]) } // tag: 27, KeyFlags([3]) } // tag: 30, Features([1]) } // tag: 33, IssuerFingerprint(Fingerprint("361A 96BD E1A6 5B6D 6C25 AE9F F004 B9A4 5C58 6126")) } // for i in 0..256 { // if let Some(sb) = sig.subpacket(i as u8) { // eprintln!(" {:?}", sb); // } // } assert_eq!(sig.signature_creation_time(), Some(Timestamp::from(1515791508).into())); assert_eq!(sig.subpacket(SubpacketTag::SignatureCreationTime), Some(&Subpacket { length: 5.into(), critical: false, value: SubpacketValue::SignatureCreationTime( 1515791508.into()), authenticated: false.into(), })); // The signature does not expire. assert!(sig.signature_alive(None, None).is_ok()); assert_eq!(sig.key_validity_period(), Some(Duration::from(63072000).into())); assert_eq!(sig.subpacket(SubpacketTag::KeyExpirationTime), Some(&Subpacket { length: 5.into(), critical: false, value: SubpacketValue::KeyExpirationTime( 63072000.into()), authenticated: false.into(), })); // Check key expiration. assert!(sig.key_alive( key, key.creation_time() + time::Duration::new(63072000 - 1, 0)) .is_ok()); assert!(! sig.key_alive( key, key.creation_time() + time::Duration::new(63072000, 0)) .is_ok()); assert_eq!(sig.preferred_symmetric_algorithms(), Some(&[SymmetricAlgorithm::AES256, SymmetricAlgorithm::AES192, SymmetricAlgorithm::AES128, SymmetricAlgorithm::TripleDES][..])); assert_eq!(sig.subpacket(SubpacketTag::PreferredSymmetricAlgorithms), Some(&Subpacket { length: 5.into(), critical: false, value: SubpacketValue::PreferredSymmetricAlgorithms( vec![SymmetricAlgorithm::AES256, SymmetricAlgorithm::AES192, SymmetricAlgorithm::AES128, SymmetricAlgorithm::TripleDES] ), authenticated: false.into(), })); assert_eq!(sig.preferred_hash_algorithms(), Some(&[HashAlgorithm::SHA256, HashAlgorithm::SHA384, HashAlgorithm::SHA512, HashAlgorithm::SHA224, HashAlgorithm::SHA1][..])); assert_eq!(sig.subpacket(SubpacketTag::PreferredHashAlgorithms), Some(&Subpacket { length: 6.into(), critical: false, value: SubpacketValue::PreferredHashAlgorithms( vec![HashAlgorithm::SHA256, HashAlgorithm::SHA384, HashAlgorithm::SHA512, HashAlgorithm::SHA224, HashAlgorithm::SHA1] ), authenticated: false.into(), })); assert_eq!(sig.preferred_compression_algorithms(), Some(&[CompressionAlgorithm::Zlib, CompressionAlgorithm::BZip2, CompressionAlgorithm::Zip][..])); assert_eq!(sig.subpacket(SubpacketTag::PreferredCompressionAlgorithms), Some(&Subpacket { length: 4.into(), critical: false, value: SubpacketValue::PreferredCompressionAlgorithms( vec![CompressionAlgorithm::Zlib, CompressionAlgorithm::BZip2, CompressionAlgorithm::Zip] ), authenticated: false.into(), })); assert_eq!(sig.key_server_preferences().unwrap(), KeyServerPreferences::empty().set_no_modify()); assert_eq!(sig.subpacket(SubpacketTag::KeyServerPreferences), Some(&Subpacket { length: 2.into(), critical: false, value: SubpacketValue::KeyServerPreferences( KeyServerPreferences::empty().set_no_modify()), authenticated: false.into(), })); assert!(sig.key_flags().unwrap().for_certification()); assert!(sig.key_flags().unwrap().for_signing()); assert_eq!(sig.subpacket(SubpacketTag::KeyFlags), Some(&Subpacket { length: 2.into(), critical: false, value: SubpacketValue::KeyFlags( KeyFlags::empty().set_certification().set_signing()), authenticated: false.into(), })); assert_eq!(sig.features().unwrap(), Features::empty().set_seipdv1()); assert_eq!(sig.subpacket(SubpacketTag::Features), Some(&Subpacket { length: 2.into(), critical: false, value: SubpacketValue::Features( Features::empty().set_seipdv1()), authenticated: false.into(), })); let keyid = "F004 B9A4 5C58 6126".parse().unwrap(); assert_eq!(sig.issuers().collect::<Vec<_>>(), vec![ &keyid ]); assert_eq!(sig.subpacket(SubpacketTag::Issuer), Some(&Subpacket { length: 9.into(), critical: false, value: SubpacketValue::Issuer(keyid), authenticated: false.into(), })); let fp = "361A96BDE1A65B6D6C25AE9FF004B9A45C586126".parse().unwrap(); assert_eq!(sig.issuer_fingerprints().collect::<Vec<_>>(), vec![ &fp ]); assert_eq!(sig.subpacket(SubpacketTag::IssuerFingerprint), Some(&Subpacket { length: 22.into(), critical: false, value: SubpacketValue::IssuerFingerprint(fp), authenticated: false.into(), })); let n = NotationData { flags: NotationDataFlags::empty().set_human_readable(), name: "rank@navy.mil".into(), value: b"midshipman".to_vec() }; assert_eq!(sig.notation_data().collect::<Vec<&NotationData>>(), vec![&n]); assert_eq!(sig.subpacket(SubpacketTag::NotationData), Some(&Subpacket { length: 32.into(), critical: false, value: SubpacketValue::NotationData(n.clone()), authenticated: false.into(), })); assert_eq!(sig.hashed_area().subpackets(SubpacketTag::NotationData) .collect::<Vec<_>>(), vec![&Subpacket { length: 32.into(), critical: false, value: SubpacketValue::NotationData(n.clone()), authenticated: false.into(), }]); } else { panic!("Expected signature!"); } // Test #2 if let Some(&Packet::Signature(ref sig)) = pile.children().nth(3) { // tag: 2, SignatureCreationTime(1515791490) // tag: 4, ExportableCertification(false) // tag: 16, Issuer(KeyID("CEAD 0621 0934 7957")) // tag: 33, IssuerFingerprint(Fingerprint("B59B 8817 F519 DCE1 0AFD 85E4 CEAD 0621 0934 7957")) // for i in 0..256 { // if let Some(sb) = sig.subpacket(i as u8) { // eprintln!(" {:?}", sb); // } // } assert_eq!(sig.signature_creation_time(), Some(Timestamp::from(1515791490).into())); assert_eq!(sig.subpacket(SubpacketTag::SignatureCreationTime), Some(&Subpacket { length: 5.into(), critical: false, value: SubpacketValue::SignatureCreationTime( 1515791490.into()), authenticated: false.into(), })); assert_eq!(sig.exportable_certification(), Some(false)); assert_eq!(sig.subpacket(SubpacketTag::ExportableCertification), Some(&Subpacket { length: 2.into(), critical: false, value: SubpacketValue::ExportableCertification(false), authenticated: false.into(), })); } let pile = PacketPile::from_bytes( crate::tests::key("subpackets/marven.gpg")).unwrap(); // Test #3 if let Some(&Packet::Signature(ref sig)) = pile.children().nth(1) { // tag: 2, SignatureCreationTime(1515791376) // tag: 7, Revocable(false) // tag: 12, RevocationKey((128, 1, Fingerprint("361A 96BD E1A6 5B6D 6C25 AE9F F004 B9A4 5C58 6126"))) // tag: 16, Issuer(KeyID("CEAD 0621 0934 7957")) // tag: 33, IssuerFingerprint(Fingerprint("B59B 8817 F519 DCE1 0AFD 85E4 CEAD 0621 0934 7957")) // for i in 0..256 { // if let Some(sb) = sig.subpacket(i as u8) { // eprintln!(" {:?}", sb); // } // } assert_eq!(sig.signature_creation_time(), Some(Timestamp::from(1515791376).into())); assert_eq!(sig.subpacket(SubpacketTag::SignatureCreationTime), Some(&Subpacket { length: 5.into(), critical: false, value: SubpacketValue::SignatureCreationTime( 1515791376.into()), authenticated: false.into(), })); assert_eq!(sig.revocable(), Some(false)); assert_eq!(sig.subpacket(SubpacketTag::Revocable), Some(&Subpacket { length: 2.into(), critical: false, value: SubpacketValue::Revocable(false), authenticated: false.into(), })); let fp = "361A96BDE1A65B6D6C25AE9FF004B9A45C586126".parse().unwrap(); let rk = RevocationKey::new(PublicKeyAlgorithm::RSAEncryptSign, fp, false); assert_eq!(sig.revocation_keys().next().unwrap(), &rk); assert_eq!(sig.subpacket(SubpacketTag::RevocationKey), Some(&Subpacket { length: 23.into(), critical: false, value: SubpacketValue::RevocationKey(rk), authenticated: false.into(), })); let keyid = "CEAD 0621 0934 7957".parse().unwrap(); assert_eq!(sig.issuers().collect::<Vec<_>>(), vec![ &keyid ]); assert_eq!(sig.subpacket(SubpacketTag::Issuer), Some(&Subpacket { length: 9.into(), critical: false, value: SubpacketValue::Issuer(keyid), authenticated: false.into(), })); let fp = "B59B8817F519DCE10AFD85E4CEAD062109347957".parse().unwrap(); assert_eq!(sig.issuer_fingerprints().collect::<Vec<_>>(), vec![ &fp ]); assert_eq!(sig.subpacket(SubpacketTag::IssuerFingerprint), Some(&Subpacket { length: 22.into(), critical: false, value: SubpacketValue::IssuerFingerprint(fp), authenticated: false.into(), })); // This signature does not contain any notation data. assert_eq!(sig.notation_data().count(), 0); assert_eq!(sig.subpacket(SubpacketTag::NotationData), None); assert_eq!(sig.subpackets(SubpacketTag::NotationData).count(), 0); } else { panic!("Expected signature!"); } // Test #4 if let Some(&Packet::Signature(ref sig)) = pile.children().nth(6) { // for i in 0..256 { // if let Some(sb) = sig.subpacket(i as u8) { // eprintln!(" {:?}", sb); // } // } assert_eq!(sig.signature_creation_time(), Some(Timestamp::from(1515886658).into())); assert_eq!(sig.subpacket(SubpacketTag::SignatureCreationTime), Some(&Subpacket { length: 5.into(), critical: false, value: SubpacketValue::SignatureCreationTime( 1515886658.into()), authenticated: false.into(), })); assert_eq!(sig.reason_for_revocation(), Some((ReasonForRevocation::Unspecified, &b"Forgot to set a sig expiration."[..]))); assert_eq!(sig.subpacket(SubpacketTag::ReasonForRevocation), Some(&Subpacket { length: 33.into(), critical: false, value: SubpacketValue::ReasonForRevocation { code: ReasonForRevocation::Unspecified, reason: b"Forgot to set a sig expiration.".to_vec(), }, authenticated: false.into(), })); } // Test #5 if let Some(&Packet::Signature(ref sig)) = pile.children().nth(7) { // The only thing interesting about this signature is that it // has multiple notations. assert_eq!(sig.signature_creation_time(), Some(Timestamp::from(1515791467).into())); assert_eq!(sig.subpacket(SubpacketTag::SignatureCreationTime), Some(&Subpacket { length: 5.into(), critical: false, value: SubpacketValue::SignatureCreationTime( 1515791467.into()), authenticated: false.into(), })); let n1 = NotationData { flags: NotationDataFlags::empty().set_human_readable(), name: "rank@navy.mil".into(), value: b"third lieutenant".to_vec() }; let n2 = NotationData { flags: NotationDataFlags::empty().set_human_readable(), name: "foo@navy.mil".into(), value: b"bar".to_vec() }; let n3 = NotationData { flags: NotationDataFlags::empty().set_human_readable(), name: "whistleblower@navy.mil".into(), value: b"true".to_vec() }; // We expect all three notations, in order. assert_eq!(sig.notation_data().collect::<Vec<&NotationData>>(), vec![&n1, &n2, &n3]); // We expect only the last notation. assert_eq!(sig.subpacket(SubpacketTag::NotationData), Some(&Subpacket { length: 35.into(), critical: false, value: SubpacketValue::NotationData(n3.clone()), authenticated: false.into(), })); // We expect all three notations, in order. assert_eq!(sig.subpackets(SubpacketTag::NotationData) .collect::<Vec<_>>(), vec![ &Subpacket { length: 38.into(), critical: false, value: SubpacketValue::NotationData(n1), authenticated: false.into(), }, &Subpacket { length: 24.into(), critical: false, value: SubpacketValue::NotationData(n2), authenticated: false.into(), }, &Subpacket { length: 35.into(), critical: false, value: SubpacketValue::NotationData(n3), authenticated: false.into(), }, ]); } // # Test 6 if let Some(&Packet::Signature(ref sig)) = pile.children().nth(8) { // A trusted signature. // tag: 2, SignatureCreationTime(1515791223) // tag: 5, TrustSignature((2, 120)) // tag: 6, RegularExpression([60, 91, 94, 62, 93, 43, 91, 64, 46, 93, 110, 97, 118, 121, 92, 46, 109, 105, 108, 62, 36]) // tag: 16, Issuer(KeyID("F004 B9A4 5C58 6126")) // tag: 33, IssuerFingerprint(Fingerprint("361A 96BD E1A6 5B6D 6C25 AE9F F004 B9A4 5C58 6126")) // for i in 0..256 { // if let Some(sb) = sig.subpacket(i as u8) { // eprintln!(" {:?}", sb); // } // } assert_eq!(sig.signature_creation_time(), Some(Timestamp::from(1515791223).into())); assert_eq!(sig.subpacket(SubpacketTag::SignatureCreationTime), Some(&Subpacket { length: 5.into(), critical: false, value: SubpacketValue::SignatureCreationTime( 1515791223.into()), authenticated: false.into(), })); assert_eq!(sig.trust_signature(), Some((2, 120))); assert_eq!(sig.subpacket(SubpacketTag::TrustSignature), Some(&Subpacket { length: 3.into(), critical: false, value: SubpacketValue::TrustSignature { level: 2, trust: 120, }, authenticated: false.into(), })); // Note: our parser strips the trailing NUL. let regex = &b"<[^>]+[@.]navy\\.mil>$"[..]; assert_eq!(sig.regular_expressions().collect::<Vec<&[u8]>>(), vec![ regex ]); assert_eq!(sig.subpacket(SubpacketTag::RegularExpression), Some(&Subpacket { length: 23.into(), critical: true, value: SubpacketValue::RegularExpression(regex.to_vec()), authenticated: false.into(), })); } // Test #7 if let Some(&Packet::Signature(ref sig)) = pile.children().nth(11) { // A subkey self-sig, which contains an embedded signature. // tag: 2, SignatureCreationTime(1515798986) // tag: 9, KeyExpirationTime(63072000) // tag: 16, Issuer(KeyID("CEAD 0621 0934 7957")) // tag: 27, KeyFlags([2]) // tag: 32, EmbeddedSignature(Signature(Signature { // version: 4, sigtype: 25, timestamp: Some(1515798986), // issuer: "F682 42EA 9847 7034 5DEC 5F08 4688 10D3 D67F 6CA9", // pk_algo: 1, hash_algo: 8, hashed_area: "29 bytes", // unhashed_area: "10 bytes", hash_prefix: [162, 209], // mpis: "258 bytes")) // tag: 33, IssuerFingerprint(Fingerprint("B59B 8817 F519 DCE1 0AFD 85E4 CEAD 0621 0934 7957")) // for i in 0..256 { // if let Some(sb) = sig.subpacket(i as u8) { // eprintln!(" {:?}", sb); // } // } assert_eq!(sig.key_validity_period(), Some(Duration::from(63072000).into())); assert_eq!(sig.subpacket(SubpacketTag::KeyExpirationTime), Some(&Subpacket { length: 5.into(), critical: false, value: SubpacketValue::KeyExpirationTime( 63072000.into()), authenticated: false.into(), })); let keyid = "CEAD 0621 0934 7957".parse().unwrap(); assert_eq!(sig.issuers().collect::<Vec<_>>(), vec! [&keyid ]); assert_eq!(sig.subpacket(SubpacketTag::Issuer), Some(&Subpacket { length: 9.into(), critical: false, value: SubpacketValue::Issuer(keyid), authenticated: false.into(), })); let fp = "B59B8817F519DCE10AFD85E4CEAD062109347957".parse().unwrap(); assert_eq!(sig.issuer_fingerprints().collect::<Vec<_>>(), vec![ &fp ]); assert_eq!(sig.subpacket(SubpacketTag::IssuerFingerprint), Some(&Subpacket { length: 22.into(), critical: false, value: SubpacketValue::IssuerFingerprint(fp), authenticated: false.into(), })); assert_eq!(sig.embedded_signatures().count(), 1); assert!(sig.subpacket(SubpacketTag::EmbeddedSignature) .is_some()); } // for (i, p) in pile.children().enumerate() { // if let &Packet::Signature(ref sig) = p { // eprintln!("{:?}: {:?}", i, sig); // for j in 0..256 { // if let Some(sb) = sig.subpacket(j as u8) { // eprintln!(" {:?}", sb); // } // } // } // } () } #[test] fn issuer_default_v4() -> Result<()> { use crate::types::Curve; let hash_algo = HashAlgorithm::SHA512; let sig = signature::SignatureBuilder::new(crate::types::SignatureType::Binary); let key: crate::packet::key::SecretKey = crate::packet::key::Key4::generate_ecc(true, Curve::Ed25519)?.into(); let mut keypair = key.into_keypair()?; let hash = hash_algo.context()?.for_signature(keypair.public().version()); // no issuer or issuer_fingerprint present, use default let sig_ = sig.sign_hash(&mut keypair, hash.clone())?; assert_eq!(sig_.issuers().collect::<Vec<_>>(), vec![ &keypair.public().keyid() ]); assert_eq!(sig_.issuer_fingerprints().collect::<Vec<_>>(), vec![ &keypair.public().fingerprint() ]); let fp = Fingerprint::from_bytes(4, b"bbbbbbbbbbbbbbbbbbbb")?; // issuer subpacket present, do not override let mut sig = signature::SignatureBuilder::new(crate::types::SignatureType::Binary); sig = sig.set_issuer(fp.clone().into())?; let sig_ = sig.clone().sign_hash(&mut keypair, hash.clone())?; assert_eq!(sig_.issuers().collect::<Vec<_>>(), vec![ &fp.clone().into() ]); assert_eq!(sig_.issuer_fingerprints().count(), 0); // issuer_fingerprint subpacket present, do not override let mut sig = signature::SignatureBuilder::new(crate::types::SignatureType::Binary); sig = sig.set_issuer_fingerprint(fp.clone())?; let sig_ = sig.clone().sign_hash(&mut keypair, hash.clone())?; assert_eq!(sig_.issuer_fingerprints().collect::<Vec<_>>(), vec![ &fp ]); assert_eq!(sig_.issuers().count(), 0); Ok(()) } #[test] fn issuer_default_v6() -> Result<()> { use crate::types::Curve; let hash_algo = HashAlgorithm::SHA512; let sig = signature::SignatureBuilder::new(crate::types::SignatureType::Binary); let key: crate::packet::key::SecretKey = crate::packet::key::Key6::generate_ecc(true, Curve::Ed25519)?.into(); let mut keypair = key.into_keypair()?; let hash = hash_algo.context()?.for_signature(keypair.public().version()); // no issuer or issuer_fingerprint present, use default let sig_ = sig.sign_hash(&mut keypair, hash.clone())?; assert_eq!(sig_.issuers().collect::<Vec<_>>(), Vec::<&KeyID>::new()); assert_eq!(sig_.issuer_fingerprints().collect::<Vec<_>>(), vec![ &keypair.public().fingerprint() ]); let fp = Fingerprint::from_bytes(4, b"bbbbbbbbbbbbbbbbbbbb")?; // issuer subpacket present, ignore, subpacket is not used with v6 let mut sig = signature::SignatureBuilder::new(crate::types::SignatureType::Binary); sig = sig.set_issuer(fp.clone().into())?; let sig_ = sig.clone().sign_hash(&mut keypair, hash.clone())?; assert_eq!(sig_.issuers().collect::<Vec<_>>(), vec![ &fp.clone().into() ]); assert_eq!(sig_.issuer_fingerprints().collect::<Vec<_>>(), vec![ &keypair.public().fingerprint() ]); // issuer_fingerprint subpacket present, do not override let mut sig = signature::SignatureBuilder::new(crate::types::SignatureType::Binary); sig = sig.set_issuer_fingerprint(fp.clone())?; let sig_ = sig.clone().sign_hash(&mut keypair, hash.clone())?; assert_eq!(sig_.issuer_fingerprints().collect::<Vec<_>>(), vec![ &fp ]); assert_eq!(sig_.issuers().count(), 0); Ok(()) } ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/src/packet/signature/v6.rs����������������������������������������������������0000644�0000000�0000000�00000020703�10461020230�0017701�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������//! OpenPGP v6 signature implementation. use std::cmp::Ordering; use std::convert::TryFrom; use std::fmt; use std::ops::{Deref, DerefMut}; use crate::{ Error, HashAlgorithm, Packet, PublicKeyAlgorithm, Result, SignatureType, crypto::mpi, packet::{ Signature, signature::{ Signature4, subpacket::{ SubpacketArea, }, }, }, }; /// Holds a v6 Signature packet. /// /// This holds a [version 6] Signature packet. Normally, you won't /// directly work with this data structure, but with the [`Signature`] /// enum, which is version agnostic. An exception is when you need to /// do version-specific operations. But currently, there aren't any /// version-specific methods. /// /// [version 6]: https://www.rfc-editor.org/rfc/rfc9580.html#name-versions-4-and-6-signature- /// [`Signature`]: super::Signature #[derive(Clone)] pub struct Signature6 { pub(crate) common: Signature4, salt: Vec<u8>, } assert_send_and_sync!(Signature6); impl TryFrom<Signature> for Signature6 { type Error = anyhow::Error; fn try_from(sig: Signature) -> Result<Self> { match sig { Signature::V6(sig) => Ok(sig), sig => Err( Error::InvalidArgument( format!( "Got a v{}, require a v6 signature", sig.version())) .into()), } } } // Yes, Signature6 derefs to Signature4. This is because Signature // derefs to Signature4 so this is the only way to add support for v6 // sigs without breaking the semver. impl Deref for Signature6 { type Target = Signature4; fn deref(&self) -> &Self::Target { &self.common } } impl DerefMut for Signature6 { fn deref_mut(&mut self) -> &mut Self::Target { &mut self.common } } impl fmt::Debug for Signature6 { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.debug_struct("Signature6") .field("version", &self.version()) .field("typ", &self.typ()) .field("pk_algo", &self.pk_algo()) .field("hash_algo", &self.hash_algo()) .field("hashed_area", self.hashed_area()) .field("unhashed_area", self.unhashed_area()) .field("additional_issuers", &self.additional_issuers) .field("digest_prefix", &crate::fmt::to_hex(&self.digest_prefix, false)) .field("salt", &crate::fmt::hex::encode(&self.salt)) .field( "computed_digest", &self .computed_digest .get() .map(|hash| crate::fmt::to_hex(&hash[..], false)), ) .field("level", &self.level) .field("mpis", &self.mpis) .finish() } } impl PartialEq for Signature6 { /// This method tests for self and other values to be equal, and /// is used by ==. /// /// This method compares the serialized version of the two /// packets. Thus, the computed values are ignored ([`level`], /// [`computed_digest`]). /// /// [`level`]: Signature6::level() /// [`computed_digest`]: Signature6::computed_digest() fn eq(&self, other: &Signature6) -> bool { self.cmp(other) == Ordering::Equal } } impl Eq for Signature6 {} impl PartialOrd for Signature6 { fn partial_cmp(&self, other: &Signature6) -> Option<Ordering> { Some(self.cmp(other)) } } impl Ord for Signature6 { fn cmp(&self, other: &Signature6) -> Ordering { self.common.cmp(&other.common) .then_with(|| self.salt.cmp(&other.salt)) } } impl std::hash::Hash for Signature6 { fn hash<H: std::hash::Hasher>(&self, state: &mut H) { use std::hash::Hash as StdHash; StdHash::hash(&self.common, state); StdHash::hash(&self.salt, state); } } impl Signature6 { /// Creates a new signature packet from common fields and salt. pub(crate) fn from_common(mut common: Signature4, salt: Vec<u8>) -> Result<Self> { common.fields.version = 6; Ok(Signature6 { common, salt }) } /// Creates a new signature packet. /// /// If you want to sign something, consider using the /// [`SignatureBuilder`] interface. /// /// [`SignatureBuilder`]: crate::packet::signature::SignatureBuilder pub fn new(typ: SignatureType, pk_algo: PublicKeyAlgorithm, hash_algo: HashAlgorithm, hashed_area: SubpacketArea, unhashed_area: SubpacketArea, digest_prefix: [u8; 2], salt: Vec<u8>, mpis: mpi::Signature) -> Result<Self> { Signature6::from_common( Signature4::new(typ, pk_algo, hash_algo, hashed_area, unhashed_area, digest_prefix, mpis), salt) } /// Gets the public key algorithm. // SigantureFields::pk_algo is private, because we don't want it // available on SignatureBuilder, which also derefs to // &SignatureFields. pub fn pk_algo(&self) -> PublicKeyAlgorithm { self.fields.pk_algo() } /// Gets the hash prefix. pub fn digest_prefix(&self) -> &[u8; 2] { &self.digest_prefix } /// Sets the hash prefix. #[allow(dead_code)] pub(crate) fn set_digest_prefix(&mut self, prefix: [u8; 2]) -> [u8; 2] { ::std::mem::replace(&mut self.digest_prefix, prefix) } /// Gets the salt. pub fn salt(&self) -> &[u8] { &self.salt } /// Sets the salt. #[allow(dead_code)] pub(crate) fn set_salt(&mut self, salt: Vec<u8>) -> Vec<u8> { ::std::mem::replace(&mut self.salt, salt) } /// Gets the signature packet's MPIs. pub fn mpis(&self) -> &mpi::Signature { &self.mpis } /// Sets the signature packet's MPIs. #[allow(dead_code)] pub(crate) fn set_mpis(&mut self, mpis: mpi::Signature) -> mpi::Signature { ::std::mem::replace(&mut self.mpis, mpis) } /// Gets the computed hash value. /// /// This is set by the [`PacketParser`] when parsing the message. /// /// [`PacketParser`]: crate::parse::PacketParser pub fn computed_digest(&self) -> Option<&[u8]> { self.computed_digest.get().map(|d| &d[..]) } /// Gets the signature level. /// /// A level of 0 indicates that the signature is directly over the /// data, a level of 1 means that the signature is a notarization /// over all level 0 signatures and the data, and so on. pub fn level(&self) -> usize { self.level } /// Returns whether this signature should be exported. /// /// This checks whether the [`Exportable Certification`] subpacket /// is absent or present and 1, and that the signature does not /// include any sensitive [`Revocation Key`] (designated revokers) /// subpackets. /// /// [`Exportable Certification`]: https://www.rfc-editor.org/rfc/rfc9580.html#name-exportable-certification /// [`Revocation Key`]: https://www.rfc-editor.org/rfc/rfc9580.html#name-revocation-key-deprecated pub fn exportable(&self) -> Result<()> { if ! self.exportable_certification().unwrap_or(true) { return Err(Error::InvalidOperation( "Cannot export non-exportable certification".into()).into()); } if self.revocation_keys().any(|r| r.sensitive()) { return Err(Error::InvalidOperation( "Cannot export signature with sensitive designated revoker" .into()).into()); } Ok(()) } } impl From<Signature6> for Packet { fn from(s: Signature6) -> Self { Packet::Signature(s.into()) } } impl From<Signature6> for super::Signature { fn from(s: Signature6) -> Self { super::Signature::V6(s) } } #[cfg(test)] use quickcheck::{Arbitrary, Gen}; #[cfg(test)] use crate::packet::signature::ArbitraryBounded; #[cfg(test)] impl ArbitraryBounded for Signature6 { fn arbitrary_bounded(g: &mut Gen, depth: usize) -> Self { let common = Signature4::arbitrary_bounded(g, depth); let salt_size = common.hash_algo().salt_size().unwrap_or(16); let mut salt = vec![0u8; salt_size]; salt.iter_mut().for_each(|p| *p = u8::arbitrary(g)); Self::from_common(common, salt) .expect("salt has the right size") } } #[cfg(test)] impl_arbitrary_with_bound!(Signature6); �������������������������������������������������������������sequoia-openpgp-2.0.0/src/packet/signature.rs�������������������������������������������������������0000644�0000000�0000000�00000551725�10461020230�0017363�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������//! Signature-related functionality. //! //! Signatures are one of the central data structures in OpenPGP. //! They are used to protect the integrity of both structured //! documents (e.g., timestamps) and unstructured documents (arbitrary //! byte sequences) as well as cryptographic data structures. //! //! The use of signatures to protect cryptographic data structures is //! central to making it easy to change an OpenPGP certificate. //! Consider how a certificate is initially authenticated. A user, //! say Alice, securely communicates her certificate's fingerprint to //! another user, say Bob. Alice might do this by personally handing //! Bob a business card with her fingerprint on it. When Bob is in //! front of his computer, he may then record that Alice uses the //! specified key. Technically, the fingerprint that he used only //! identifies the primary key: a fingerprint is the hash of the //! primary key; it does not say anything about any of the rest of the //! certificate---the subkeys, the User IDs, and the User Attributes. //! But, because these components are signed by the primary key, we //! know that the controller of the key intended that they be //! associated with the certificate. This mechanism makes it not only //! possible to add and revoke components, but also to change //! meta-data, such as a key's expiration time. If the fingerprint //! were instead computed over the whole OpenPGP certificate, then //! changing the certificate would result in a new fingerprint. In //! that case, the fingerprint could not be used as a long-term, //! unique, and stable identifier. //! //! Signatures are described in [Section 5.2 of RFC 9580]. //! //! # Data Types //! //! The main signature-related data type is the [`Signature`] enum. //! This enum abstracts away the differences between the signature //! formats (the current [version 6], the deprecated [version 4], and //! the legacy [version 3] formats). Nevertheless some functionality //! remains format specific. For instance, [version 4] signatures //! introduced support for storing arbitrary key-value pairs //! (so-called [notations]). //! //! This version of Sequoia supports [version 6] and [version 4] //! signatures ([`Signature6`] and [`Signature4`]), and includes //! limited support for [version 3] signatures to allow working with //! archived messages. //! //! When signing a document, a `Signature` is typically created //! indirectly by the [streaming `Signer`]. Similarly, a `Signature` //! packet is created as a side effect of parsing a signed message //! using the [`PacketParser`]. //! //! The [`SignatureBuilder`] can be used to create a binding //! signature, a certification, etc. The motivation for having a //! separate data structure for creating signatures is that it //! decreases the chance that a half-constructed signature is //! accidentally exported. When modifying an existing signature, you //! can use, for instance, `SignatureBuilder::from` to convert a //! `Signature` into a `SignatureBuilder`: //! //! ``` //! use sequoia_openpgp as openpgp; //! use openpgp::policy::StandardPolicy; //! # use openpgp::cert::prelude::*; //! # use openpgp::packet::prelude::*; //! //! # fn main() -> openpgp::Result<()> { //! let p = &StandardPolicy::new(); //! //! # // Generate a new certificate. It has secret key material. //! # let (cert, _) = CertBuilder::new() //! # .generate()?; //! # //! // Create a new direct key signature using the current one as a template. //! let pk = cert.with_policy(p, None)?.primary_key(); //! let sig = pk.direct_key_signature()?; //! let builder: SignatureBuilder = sig.clone().into(); //! # Ok(()) //! # } //! ``` //! //! For [version 6] and [version 4] signatures, attributes are set //! using so-called subpackets. Subpackets can be stored in two //! places: either in the so-called hashed area or in the so-called //! unhashed area. Whereas the hashed area's integrity is protected //! by the signature, the unhashed area is not. Because an attacker //! can modify the unhashed area without detection, the unhashed area //! should only be used for storing self-authenticating data, e.g., //! the issuer, or a back signature. It is also sometimes used for //! [hints]. [`Signature::normalize`] removes unexpected subpackets //! from the unhashed area. However, due to a lack of context, it //! does not validate the remaining subpackets. //! //! In Sequoia, each subpacket area is represented by a //! [`SubpacketArea`] data structure. The two subpacket areas are //! unified by the [`SubpacketAreas`] data structure, which implements //! a reasonable policy for looking up subpackets. In particular, it //! prefers subpackets from the hashed subpacket area, and only //! consults the unhashed subpacket area for certain packets. See //! [its documentation] for details. //! //! [Section 5.2 of RFC 9580]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2 //! [`Signature`]: super::Signature //! [version 3]: https://tools.ietf.org/html/rfc1991#section-5.2.2 //! [version 4]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3 //! [version 6]: https://www.rfc-editor.org/rfc/rfc9580.html#name-versions-4-and-6-signature- //! [notations]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.24 //! [streaming `Signer`]: crate::serialize::stream::Signer //! [`PacketParser`]: crate::parse::PacketParser //! [hints]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.13 //! [`Signature::normalize`]: super::Signature::normalize() //! [`SubpacketArea`]: subpacket::SubpacketArea //! [`SubpacketAreas`]: subpacket::SubpacketAreas //! [its documentation]: subpacket::SubpacketAreas use std::borrow::Cow; use std::cmp::Ordering; use std::convert::TryFrom; use std::fmt; use std::hash::Hasher; use std::ops::{Deref, DerefMut}; use std::sync::OnceLock; use std::time::SystemTime; #[cfg(test)] use quickcheck::{Arbitrary, Gen}; use crate::Error; use crate::Result; use crate::crypto::{ mpi, hash::{self, Hash}, Signer, }; use crate::KeyID; use crate::KeyHandle; use crate::HashAlgorithm; use crate::PublicKeyAlgorithm; use crate::SignatureType; use crate::packet::Signature; use crate::packet::{ key, Key, }; use crate::packet::UserID; use crate::packet::UserAttribute; use crate::Packet; use crate::packet; use crate::packet::signature::subpacket::{ Subpacket, SubpacketArea, SubpacketAreas, SubpacketTag, SubpacketValue, }; use crate::types::Timestamp; #[cfg(test)] /// Like quickcheck::Arbitrary, but bounded. pub(crate) trait ArbitraryBounded { /// Generates an arbitrary value, but only recurses if `depth > /// 0`. fn arbitrary_bounded(g: &mut Gen, depth: usize) -> Self; } #[cfg(test)] /// Default depth when implementing Arbitrary using ArbitraryBounded. const DEFAULT_ARBITRARY_DEPTH: usize = 2; #[cfg(test)] macro_rules! impl_arbitrary_with_bound { ($typ:path) => { impl Arbitrary for $typ { fn arbitrary(g: &mut Gen) -> Self { Self::arbitrary_bounded( g, crate::packet::signature::DEFAULT_ARBITRARY_DEPTH) } } } } pub mod subpacket; pub mod cache; mod v6; pub use v6::Signature6; /// How many seconds to backdate signatures. /// /// When creating certificates (more specifically, binding /// signatures), and when updating binding signatures (creating /// signatures from templates), we backdate the signatures by this /// amount if no creation time is explicitly given. Backdating the /// certificate by a minute has the advantage that the certificate can /// immediately be customized: /// /// In order to reliably override a binding signature, the /// overriding binding signature must be newer than the existing /// signature. If, however, the existing signature is created /// `now`, any newer signature must have a future creation time, /// and is considered invalid by Sequoia. To avoid this, we /// backdate certificate creation times (and hence binding /// signature creation times), so that there is "space" between /// the creation time and now for signature updates. pub(crate) const SIG_BACKDATE_BY: u64 = 60; /// The data stored in a `Signature` packet. /// /// This data structure contains exactly those fields that appear in a /// [`Signature` packet]. It is used by both the [`Signature4`] and /// the [`SignatureBuilder`] data structures, which include other /// auxiliary information. This data structure is public so that /// `Signature4` and `SignatureBuilder` can deref to it. /// /// A `SignatureField` derefs to a [`SubpacketAreas`]. /// /// [`Signature`]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2 /// [`SubpacketAreas`]: subpacket::SubpacketAreas #[derive(Clone, Hash, PartialEq, Eq, PartialOrd, Ord)] pub struct SignatureFields { /// Version of the signature packet. Must be 4. version: u8, /// Type of signature. typ: SignatureType, /// Public-key algorithm used for this signature. pk_algo: PublicKeyAlgorithm, /// Hash algorithm used to compute the signature. hash_algo: HashAlgorithm, /// Subpackets. subpackets: SubpacketAreas, } assert_send_and_sync!(SignatureFields); #[cfg(test)] impl ArbitraryBounded for SignatureFields { fn arbitrary_bounded(g: &mut Gen, depth: usize) -> Self { SignatureFields { // XXX: Make this more interesting once we dig other // versions. version: 4, typ: Arbitrary::arbitrary(g), pk_algo: PublicKeyAlgorithm::arbitrary_for_signing(g), hash_algo: Arbitrary::arbitrary(g), subpackets: ArbitraryBounded::arbitrary_bounded(g, depth), } } } #[cfg(test)] impl_arbitrary_with_bound!(SignatureFields); impl Deref for SignatureFields { type Target = SubpacketAreas; fn deref(&self) -> &Self::Target { &self.subpackets } } impl DerefMut for SignatureFields { fn deref_mut(&mut self) -> &mut Self::Target { &mut self.subpackets } } impl SignatureFields { /// Gets the version. pub fn version(&self) -> u8 { self.version } /// Gets the signature type. /// /// This function is called `typ` and not `type`, because `type` /// is a reserved word. pub fn typ(&self) -> SignatureType { self.typ } /// Gets the public key algorithm. /// /// This is `pub(crate)`, because it shouldn't be exported by /// `SignatureBuilder` where it is only set at the end. pub(crate) fn pk_algo(&self) -> PublicKeyAlgorithm { self.pk_algo } /// Gets the hash algorithm. pub fn hash_algo(&self) -> HashAlgorithm { self.hash_algo } } #[derive(Clone, Debug, Hash, PartialEq, Eq)] pub(crate) enum SBVersion { V4 {}, V6 { salt: Vec<u8>, }, } impl Default for SBVersion { fn default() -> Self { SBVersion::V4 {} } } impl SBVersion { fn to_u8(&self) -> u8 { match self { SBVersion::V4 { .. } => 4, SBVersion::V6 { .. } => 6, } } /// Returns the salt, if any. pub(crate) fn salt(&self) -> Option<&[u8]> { match self { SBVersion::V4 { .. } => None, SBVersion::V6 { salt, .. } => Some(&salt), } } } /// A Signature builder. /// /// The `SignatureBuilder` is used to create [`Signature`]s. Although /// it can be used to generate a signature over a document (using /// [`SignatureBuilder::sign_message`]), it is usually better to use /// the [streaming `Signer`] for that. /// /// [`Signature`]: super::Signature /// [streaming `Signer`]: crate::serialize::stream::Signer /// [`SignatureBuilder::sign_message`]: SignatureBuilder::sign_message() /// /// Oftentimes, you won't want to create a new signature from scratch, /// but modify a copy of an existing signature. This is /// straightforward to do since `SignatureBuilder` implements [`From`] /// for Signature. /// /// [`From`]: std::convert::From /// /// When converting a `Signature` to a `SignatureBuilder`, the hash /// algorithm is reset to the default hash algorithm /// (`HashAlgorithm::Default()`). This ensures that a recommended /// hash algorithm is used even when an old signature is used as a /// template, which is often the case when updating self signatures, /// and binding signatures. /// /// According to [Section 5.2.3.11 of RFC 9580], `Signatures` must /// include a [`Signature Creation Time`] subpacket. Since this /// should usually be set to the current time, and is easy to forget /// to update, we remove any `Signature Creation Time` subpackets /// from both the hashed subpacket area and the unhashed subpacket /// area when converting a `Signature` to a `SignatureBuilder`, and /// when the `SignatureBuilder` is finalized, we automatically insert /// a `Signature Creation Time` subpacket into the hashed subpacket /// area unless the `Signature Creation Time` subpacket has been set /// using the [`set_signature_creation_time`] method or preserved /// using the [`preserve_signature_creation_time`] method or /// suppressed using the [`suppress_signature_creation_time`] method. /// /// If the `SignatureBuilder` has been created from scratch, the /// current time is used as signature creation time. If it has been /// created from a template, we make sure that the generated signature /// is newer. If that is not possible (i.e. the generated signature /// would have a future creation time), the signing operation fails. /// This ensures that binding signatures can be updated by deriving a /// `SignatureBuilder` from the existing binding. To disable this, /// explicitly set a signature creation time, or preserve the original /// one, or suppress the insertion of a timestamp. /// /// [Section 5.2.3.11 of RFC 9580]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.11 /// [`Signature Creation Time`]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.11 /// [`set_signature_creation_time`]: SignatureBuilder::set_signature_creation_time() /// [`preserve_signature_creation_time`]: SignatureBuilder::preserve_signature_creation_time() /// [`suppress_signature_creation_time`]: SignatureBuilder::suppress_signature_creation_time() /// /// Similarly, most OpenPGP implementations cannot verify a signature /// if neither the [`Issuer`] subpacket nor the [`Issuer Fingerprint`] /// subpacket has been correctly set. To avoid subtle bugs due to the /// use of a stale `Issuer` subpacket or a stale `Issuer Fingerprint` /// subpacket, we remove any `Issuer` subpackets, and `Issuer /// Fingerprint` subpackets from both the hashed and unhashed areas /// when converting a `Signature` to a `SigantureBuilder`. Since the /// [`Signer`] passed to the finalization routine contains the /// required information, we also automatically add appropriate /// `Issuer` and `Issuer Fingerprint` subpackets to the hashed /// subpacket area when the `SignatureBuilder` is finalized unless an /// `Issuer` subpacket or an `IssuerFingerprint` subpacket has been /// added to either of the subpacket areas (which can be done using /// the [`set_issuer`] method and the [`set_issuer_fingerprint`] /// method, respectively). /// /// [`Issuer`]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.12 /// [`Issuer Fingerprint`]: https://www.rfc-editor.org/rfc/rfc9580.html#name-issuer-fingerprint /// [`Signer`]: super::super::crypto::Signer /// [`set_issuer`]: SignatureBuilder::set_issuer() /// [`set_issuer_fingerprint`]: SignatureBuilder::set_issuer_fingerprint() /// /// To finalize the builder, call [`sign_hash`], [`sign_message`], /// [`sign_direct_key`], [`sign_subkey_binding`], /// [`sign_primary_key_binding`], [`sign_userid_binding`], /// [`sign_user_attribute_binding`], [`sign_standalone`], or /// [`sign_timestamp`], as appropriate. These functions turn the /// `SignatureBuilder` into a valid `Signature`. /// /// [`sign_hash`]: SignatureBuilder::sign_hash() /// [`sign_message`]: SignatureBuilder::sign_message() /// [`sign_direct_key`]: SignatureBuilder::sign_direct_key() /// [`sign_subkey_binding`]: SignatureBuilder::sign_subkey_binding() /// [`sign_primary_key_binding`]: SignatureBuilder::sign_primary_key_binding() /// [`sign_userid_binding`]: SignatureBuilder::sign_userid_binding() /// [`sign_user_attribute_binding`]: SignatureBuilder::sign_user_attribute_binding() /// [`sign_standalone`]: SignatureBuilder::sign_standalone() /// [`sign_timestamp`]: SignatureBuilder::sign_timestamp() /// /// This structure `Deref`s to its containing [`SignatureFields`] /// structure, which in turn `Deref`s to its subpacket areas /// (a [`SubpacketAreas`]). /// /// [`SubpacketAreas`]: subpacket::SubpacketAreas /// /// # Examples /// /// Update a certificate's feature set by updating the `Features` /// subpacket on any direct key signature, and any User ID binding /// signatures. See the [`Preferences`] trait for how preferences /// like these are looked up. /// /// [`Preferences`]: crate::cert::Preferences /// /// ``` /// use sequoia_openpgp as openpgp; /// use openpgp::cert::prelude::*; /// use openpgp::packet::prelude::*; /// use openpgp::packet::signature::subpacket::{Subpacket, SubpacketValue}; /// use openpgp::policy::StandardPolicy; /// use openpgp::types::Features; /// /// # fn main() -> openpgp::Result<()> { /// let p = &StandardPolicy::new(); /// /// let (cert, _) = CertBuilder::new().add_userid("Alice").generate()?; /// /// // Derive a signer (the primary key is always certification capable). /// let pk = cert.primary_key().key(); /// let mut signer = pk.clone().parts_into_secret()?.into_keypair()?; /// /// let mut sigs = Vec::new(); /// /// let vc = cert.with_policy(p, None)?; /// /// if let Ok(sig) = vc.direct_key_signature() { /// sigs.push(SignatureBuilder::from(sig.clone()) /// .modify_hashed_area(|mut a| { /// a.replace(Subpacket::new( /// SubpacketValue::Features(Features::sequoia().set(10)), /// false)?)?; /// Ok(a) /// })? /// // Update the direct key signature. /// .sign_direct_key(&mut signer, None)?); /// } /// /// for ua in vc.userids() { /// sigs.push(SignatureBuilder::from(ua.binding_signature().clone()) /// .modify_hashed_area(|mut a| { /// a.replace(Subpacket::new( /// SubpacketValue::Features(Features::sequoia().set(10)), /// false)?)?; /// Ok(a) /// })? /// // Update the binding signature. /// .sign_userid_binding(&mut signer, pk, ua.userid())?); /// } /// /// // Merge in the new signatures. /// let cert = cert.insert_packets(sigs.into_iter().map(Packet::from))?.0; /// # assert_eq!(cert.bad_signatures().count(), 0); /// # Ok(()) /// # } /// ``` // IMPORTANT: If you add fields to this struct, you need to explicitly // IMPORTANT: implement PartialEq, Eq, and Hash. #[derive(Clone, Hash, PartialEq, Eq)] pub struct SignatureBuilder { reference_time: Option<SystemTime>, overrode_creation_time: bool, original_creation_time: Option<SystemTime>, pub(crate) fields: SignatureFields, /// The version of signature in the signature builder. /// /// Note: this is called sb_version instead of version, because /// currently SignaturBuilder derefs to SignatureFields, which /// also has a field called version, which lead to a lot of /// confusion. Consider renaming it once the deref is gone. pub(crate) sb_version: SBVersion, } assert_send_and_sync!(SignatureBuilder); impl Deref for SignatureBuilder { type Target = SignatureFields; fn deref(&self) -> &Self::Target { &self.fields } } impl DerefMut for SignatureBuilder { fn deref_mut(&mut self) -> &mut Self::Target { &mut self.fields } } impl SignatureBuilder { /// Returns a new `SignatureBuilder` object. pub fn new(typ: SignatureType) -> Self { SignatureBuilder { reference_time: None, overrode_creation_time: false, original_creation_time: None, fields: SignatureFields { version: 4, typ, pk_algo: PublicKeyAlgorithm::Unknown(0), hash_algo: HashAlgorithm::default(), subpackets: SubpacketAreas::default(), }, sb_version: SBVersion::default(), } } /// Sets the signature type. pub fn set_type(mut self, t: SignatureType) -> Self { self.typ = t; self } /// Sets the hash algorithm. pub fn set_hash_algo(mut self, h: HashAlgorithm) -> Self { self.hash_algo = h; self } /// Generates a standalone signature. /// /// A [Standalone Signature] ([`SignatureType::Standalone`]) is a /// self-contained signature, which is only over the signature /// packet. /// /// [Standalone Signature]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.1 /// [`SignatureType::Standalone`]: crate::types::SignatureType::Standalone /// /// This function checks that the [signature type] (passed to /// [`SignatureBuilder::new`], set via /// [`SignatureBuilder::set_type`], or copied when using /// `SignatureBuilder::From`) is [`SignatureType::Standalone`] or /// [`SignatureType::Unknown`]. /// /// [signature type]: crate::types::SignatureType /// [`SignatureBuilder::new`]: SignatureBuilder::new() /// [`SignatureBuilder::set_type`]: SignatureBuilder::set_type() /// [`SignatureType::Timestamp`]: crate::types::SignatureType::Timestamp /// [`SignatureType::Unknown`]: crate::types::SignatureType::Unknown /// /// The [`Signature`]'s public-key algorithm field is set to the /// algorithm used by `signer`. /// /// [`Signature`]: super::Signature /// /// If neither an [`Issuer`] subpacket (set using /// [`SignatureBuilder::set_issuer`], for instance) nor an /// [`Issuer Fingerprint`] subpacket (set using /// [`SignatureBuilder::set_issuer_fingerprint`], for instance) is /// set, they are both added to the new `Signature`'s hashed /// subpacket area and set to the `signer`'s `KeyID` and /// `Fingerprint`, respectively. /// /// [`Issuer`]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.12 /// [`SignatureBuilder::set_issuer`]: SignatureBuilder::set_issuer() /// [`Issuer Fingerprint`]: https://www.rfc-editor.org/rfc/rfc9580.html#name-issuer-fingerprint /// [`SignatureBuilder::set_issuer_fingerprint`]: SignatureBuilder::set_issuer_fingerprint() /// /// Likewise, a [`Signature Creation Time`] subpacket set to the /// current time is added to the hashed area if the `Signature /// Creation Time` subpacket hasn't been set using, for instance, /// the [`set_signature_creation_time`] method or the /// [`preserve_signature_creation_time`] method. /// /// [`Signature Creation Time`]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.11 /// [`set_signature_creation_time`]: SignatureBuilder::set_signature_creation_time() /// [`preserve_signature_creation_time`]: SignatureBuilder::preserve_signature_creation_time() /// /// # Examples /// /// ``` /// use sequoia_openpgp as openpgp; /// use openpgp::cert::prelude::*; /// use openpgp::packet::prelude::*; /// use openpgp::policy::StandardPolicy; /// use openpgp::types::SignatureType; /// /// # fn main() -> openpgp::Result<()> { /// let p = &StandardPolicy::new(); /// /// let (cert, _) = CertBuilder::new().add_signing_subkey().generate()?; /// /// // Get a usable (alive, non-revoked) signing key. /// let key : &Key<_, _> = cert /// .keys().with_policy(p, None) /// .for_signing().alive().revoked(false).nth(0).unwrap().key(); /// // Derive a signer. /// let mut signer = key.clone().parts_into_secret()?.into_keypair()?; /// /// let mut sig = SignatureBuilder::new(SignatureType::Standalone) /// .sign_standalone(&mut signer)?; /// /// // Verify it. /// sig.verify_standalone(signer.public())?; /// # Ok(()) /// # } /// ``` pub fn sign_standalone(mut self, signer: &mut dyn Signer) -> Result<Signature> { self = self.pre_sign(signer)?; let mut hash = self.hash_algo().context()?.for_signature(self.version()); self.hash_standalone(&mut hash)?; self.sign(signer, hash.into_digest()?) } /// Generates a Timestamp Signature. /// /// Like a [Standalone Signature] (created using /// [`SignatureBuilder::sign_standalone`]), a [Timestamp /// Signature] is a self-contained signature, but its emphasis in /// on the contained timestamp, specifically, the timestamp stored /// in the [`Signature Creation Time`] subpacket. This type of /// signature is primarily used by [timestamping services]. To /// timestamp a signature, you can include either a [Signature /// Target subpacket] (set using /// [`SignatureBuilder::set_signature_target`]), or an [Embedded /// Signature] (set using /// [`SignatureBuilder::set_embedded_signature`]) in the hashed /// area. /// /// /// [Standalone Signature]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.1 /// [`SignatureBuilder::sign_standalone`]: SignatureBuilder::sign_standalone() /// [Timestamp Signature]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.1 /// [`Signature Creation Time`]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.11 /// [timestamping services]: https://en.wikipedia.org/wiki/Trusted_timestamping /// [Signature Target subpacket]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.33 /// [`SignatureBuilder::set_signature_target`]: SignatureBuilder::set_signature_target() /// [Embedded Signature]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.34 /// [`SignatureBuilder::set_embedded_signature`]: SignatureBuilder::set_embedded_signature() /// /// This function checks that the [signature type] (passed to /// [`SignatureBuilder::new`], set via /// [`SignatureBuilder::set_type`], or copied when using /// `SignatureBuilder::From`) is [`SignatureType::Timestamp`] or /// [`SignatureType::Unknown`]. /// /// [signature type]: crate::types::SignatureType /// [`SignatureBuilder::new`]: SignatureBuilder::new() /// [`SignatureBuilder::set_type`]: SignatureBuilder::set_type() /// [`SignatureType::Timestamp`]: crate::types::SignatureType::Timestamp /// [`SignatureType::Unknown`]: crate::types::SignatureType::Unknown /// /// The [`Signature`]'s public-key algorithm field is set to the /// algorithm used by `signer`. /// /// [`Signature`]: super::Signature /// /// If neither an [`Issuer`] subpacket (set using /// [`SignatureBuilder::set_issuer`], for instance) nor an /// [`Issuer Fingerprint`] subpacket (set using /// [`SignatureBuilder::set_issuer_fingerprint`], for instance) is /// set, they are both added to the new `Signature`'s hashed /// subpacket area and set to the `signer`'s `KeyID` and /// `Fingerprint`, respectively. /// /// [`Issuer`]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.12 /// [`SignatureBuilder::set_issuer`]: SignatureBuilder::set_issuer() /// [`Issuer Fingerprint`]: https://www.rfc-editor.org/rfc/rfc9580.html#name-issuer-fingerprint /// [`SignatureBuilder::set_issuer_fingerprint`]: SignatureBuilder::set_issuer_fingerprint() /// /// Likewise, a [`Signature Creation Time`] subpacket set to the /// current time is added to the hashed area if the `Signature /// Creation Time` subpacket hasn't been set using, for instance, /// the [`set_signature_creation_time`] method or the /// [`preserve_signature_creation_time`] method. /// /// [`Signature Creation Time`]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.11 /// [`set_signature_creation_time`]: SignatureBuilder::set_signature_creation_time() /// [`preserve_signature_creation_time`]: SignatureBuilder::preserve_signature_creation_time() /// /// # Examples /// /// Create a timestamp signature: /// /// ``` /// use sequoia_openpgp as openpgp; /// use openpgp::cert::prelude::*; /// use openpgp::packet::prelude::*; /// use openpgp::policy::StandardPolicy; /// use openpgp::types::SignatureType; /// /// # fn main() -> openpgp::Result<()> { /// let p = &StandardPolicy::new(); /// /// let (cert, _) = CertBuilder::new().add_signing_subkey().generate()?; /// /// // Get a usable (alive, non-revoked) signing key. /// let key : &Key<_, _> = cert /// .keys().with_policy(p, None) /// .for_signing().alive().revoked(false).nth(0).unwrap().key(); /// // Derive a signer. /// let mut signer = key.clone().parts_into_secret()?.into_keypair()?; /// /// let mut sig = SignatureBuilder::new(SignatureType::Timestamp) /// .sign_timestamp(&mut signer)?; /// /// // Verify it. /// sig.verify_timestamp(signer.public())?; /// # Ok(()) /// # } /// ``` pub fn sign_timestamp(mut self, signer: &mut dyn Signer) -> Result<Signature> { self = self.pre_sign(signer)?; let mut hash = self.hash_algo().context()?.for_signature(self.version()); self.hash_timestamp(&mut hash)?; self.sign(signer, hash.into_digest()?) } /// Generates a Direct Key Signature. /// /// A [Direct Key Signature] is a signature over the primary key. /// It is primarily used to hold fallback [preferences]. For /// instance, when addressing the Certificate by a User ID, the /// OpenPGP implementation is supposed to look for preferences /// like the [Preferred Symmetric Algorithms] on the User ID, and /// only if there is no such packet, look on the direct key /// signature. /// /// This function is also used to create a [Key Revocation /// Signature], which revokes the certificate. /// /// [preferences]: crate::cert::Preferences /// [Direct Key Signature]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.1 /// [Preferred Symmetric Algorithms]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.14 /// [Key Revocation Signature]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.1 /// /// This function checks that the [signature type] (passed to /// [`SignatureBuilder::new`], set via /// [`SignatureBuilder::set_type`], or copied when using /// `SignatureBuilder::From`) is [`SignatureType::DirectKey`], /// [`SignatureType::KeyRevocation`], or /// [`SignatureType::Unknown`]. /// /// [signature type]: crate::types::SignatureType /// [`SignatureBuilder::new`]: SignatureBuilder::new() /// [`SignatureBuilder::set_type`]: SignatureBuilder::set_type() /// [`SignatureType::DirectKey`]: crate::types::SignatureType::DirectKey /// [`SignatureType::KeyRevocation`]: crate::types::SignatureType::KeyRevocation /// [`SignatureType::Unknown`]: crate::types::SignatureType::Unknown /// /// The [`Signature`]'s public-key algorithm field is set to the /// algorithm used by `signer`. /// /// [`Signature`]: super::Signature /// /// If neither an [`Issuer`] subpacket (set using /// [`SignatureBuilder::set_issuer`], for instance) nor an /// [`Issuer Fingerprint`] subpacket (set using /// [`SignatureBuilder::set_issuer_fingerprint`], for instance) is /// set, they are both added to the new `Signature`'s hashed /// subpacket area and set to the `signer`'s `KeyID` and /// `Fingerprint`, respectively. /// /// [`Issuer`]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.12 /// [`SignatureBuilder::set_issuer`]: SignatureBuilder::set_issuer() /// [`Issuer Fingerprint`]: https://www.rfc-editor.org/rfc/rfc9580.html#name-issuer-fingerprint /// [`SignatureBuilder::set_issuer_fingerprint`]: SignatureBuilder::set_issuer_fingerprint() /// /// Likewise, a [`Signature Creation Time`] subpacket set to the /// current time is added to the hashed area if the `Signature /// Creation Time` subpacket hasn't been set using, for instance, /// the [`set_signature_creation_time`] method or the /// [`preserve_signature_creation_time`] method. /// /// [`Signature Creation Time`]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.11 /// [`set_signature_creation_time`]: SignatureBuilder::set_signature_creation_time() /// [`preserve_signature_creation_time`]: SignatureBuilder::preserve_signature_creation_time() /// /// If `pk` is set to `None` the signature will be computed over the public key /// retrieved from the `signer` parameter, i.e. a self-signature will be created. /// To create a third-party-signature provide an explicit public key as the /// `pk` parameter. /// /// # Examples /// /// Set the default value for the [Preferred Symmetric Algorithms /// subpacket]: /// /// [Preferred Symmetric Algorithms subpacket]: SignatureBuilder::set_preferred_symmetric_algorithms() /// /// ``` /// use sequoia_openpgp as openpgp; /// use openpgp::cert::prelude::*; /// use openpgp::packet::prelude::*; /// use openpgp::policy::StandardPolicy; /// use openpgp::types::SignatureType; /// use openpgp::types::SymmetricAlgorithm; /// /// # fn main() -> openpgp::Result<()> { /// let p = &StandardPolicy::new(); /// /// let (cert, _) = CertBuilder::new().add_signing_subkey().generate()?; /// /// // Get a usable (alive, non-revoked) certification key. /// let key : &Key<_, _> = cert /// .keys().with_policy(p, None) /// .for_certification().alive().revoked(false).nth(0).unwrap().key(); /// // Derive a signer. /// let mut signer = key.clone().parts_into_secret()?.into_keypair()?; /// /// // A direct key signature is always over the primary key. /// let pk = cert.primary_key().key(); /// /// // Modify the existing direct key signature. /// let mut sig = SignatureBuilder::from( /// cert.with_policy(p, None)?.direct_key_signature()?.clone()) /// .set_preferred_symmetric_algorithms( /// vec![ SymmetricAlgorithm::AES256, /// SymmetricAlgorithm::AES128, /// ])? /// .sign_direct_key(&mut signer, None)?; /// /// // Verify it. /// sig.verify_direct_key(signer.public(), pk)?; /// # Ok(()) /// # } /// ``` pub fn sign_direct_key<'a, PK>(mut self, signer: &mut dyn Signer, pk: PK) -> Result<Signature> where PK: Into<Option<&'a Key<key::PublicParts, key::PrimaryRole>>> { self = self.pre_sign(signer)?; let mut hash = self.hash_algo().context()?.for_signature(self.version()); let pk = pk.into().unwrap_or_else(|| signer.public().role_as_primary()); self.hash_direct_key(&mut hash, pk)?; self.sign(signer, hash.into_digest()?) } /// Generates a User ID binding signature. /// /// A User ID binding signature (a self signature) or a [User ID /// certification] (a third-party signature) is a signature over a /// `User ID` and a `Primary Key` made by a certification-capable /// key. It asserts that the signer is convinced that the `User /// ID` should be associated with the `Certificate`, i.e., that /// the binding is authentic. /// /// [User ID certification]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.1 /// /// OpenPGP has four types of `User ID` certifications. They are /// intended to express the degree of the signer's conviction, /// i.e., how well the signer authenticated the binding. In /// practice, the `Positive Certification` type is used for /// self-signatures, and the `Generic Certification` is used for /// third-party certifications; the other types are not normally /// used. /// /// This function is also used to create [Certification /// Revocations]. /// /// [Certification Revocations]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.1 /// /// This function checks that the [signature type] (passed to /// [`SignatureBuilder::new`], set via /// [`SignatureBuilder::set_type`], or copied when using /// `SignatureBuilder::From`) is [`GenericCertification`], /// [`PersonaCertification`], [`CasualCertification`], /// [`PositiveCertification`], [`CertificationRevocation`], or /// [`SignatureType::Unknown`]. /// /// [signature type]: crate::types::SignatureType /// [`SignatureBuilder::new`]: SignatureBuilder::new() /// [`SignatureBuilder::set_type`]: SignatureBuilder::set_type() /// [`GenericCertification`]: crate::types::SignatureType::GenericCertification /// [`PersonaCertification`]: crate::types::SignatureType::PersonaCertification /// [`CasualCertification`]: crate::types::SignatureType::CasualCertification /// [`PositiveCertification`]: crate::types::SignatureType::PositiveCertification /// [`CertificationRevocation`]: crate::types::SignatureType::CertificationRevocation /// [`SignatureType::Unknown`]: crate::types::SignatureType::Unknown /// /// The [`Signature`]'s public-key algorithm field is set to the /// algorithm used by `signer`. /// /// [`Signature`]: super::Signature /// /// If neither an [`Issuer`] subpacket (set using /// [`SignatureBuilder::set_issuer`], for instance) nor an /// [`Issuer Fingerprint`] subpacket (set using /// [`SignatureBuilder::set_issuer_fingerprint`], for instance) is /// set, they are both added to the new `Signature`'s hashed /// subpacket area and set to the `signer`'s `KeyID` and /// `Fingerprint`, respectively. /// /// [`Issuer`]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.12 /// [`SignatureBuilder::set_issuer`]: SignatureBuilder::set_issuer() /// [`Issuer Fingerprint`]: https://www.rfc-editor.org/rfc/rfc9580.html#name-issuer-fingerprint /// [`SignatureBuilder::set_issuer_fingerprint`]: SignatureBuilder::set_issuer_fingerprint() /// /// Likewise, a [`Signature Creation Time`] subpacket set to the /// current time is added to the hashed area if the `Signature /// Creation Time` subpacket hasn't been set using, for instance, /// the [`set_signature_creation_time`] method or the /// [`preserve_signature_creation_time`] method. /// /// [`Signature Creation Time`]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.11 /// [`set_signature_creation_time`]: SignatureBuilder::set_signature_creation_time() /// [`preserve_signature_creation_time`]: SignatureBuilder::preserve_signature_creation_time() /// /// If `pk` is set to `None` the signature will be computed over the public key /// retrieved from the `signer` parameter, i.e. a self-signature will be created. /// To create a third-party-signature provide an explicit public key as the /// `pk` parameter. /// /// # Examples /// /// Set the [Preferred Symmetric Algorithms subpacket], which will /// be used when addressing the certificate via the associated /// User ID: /// /// [Preferred Symmetric Algorithms subpacket]: SignatureBuilder::set_preferred_symmetric_algorithms() /// /// ``` /// use sequoia_openpgp as openpgp; /// use openpgp::cert::prelude::*; /// use openpgp::packet::prelude::*; /// use openpgp::policy::StandardPolicy; /// use openpgp::types::SymmetricAlgorithm; /// /// # fn main() -> openpgp::Result<()> { /// let p = &StandardPolicy::new(); /// /// let (cert, _) = CertBuilder::new().add_userid("Alice").generate()?; /// /// // Get a usable (alive, non-revoked) certification key. /// let key : &Key<_, _> = cert /// .keys().with_policy(p, None) /// .for_certification().alive().revoked(false).nth(0).unwrap().key(); /// // Derive a signer. /// let mut signer = key.clone().parts_into_secret()?.into_keypair()?; /// /// // Update the User ID's binding signature. /// let ua = cert.with_policy(p, None)?.userids().nth(0).unwrap(); /// let mut new_sig = SignatureBuilder::from( /// ua.binding_signature().clone()) /// .set_preferred_symmetric_algorithms( /// vec![ SymmetricAlgorithm::AES256, /// SymmetricAlgorithm::AES128, /// ])? /// .sign_userid_binding(&mut signer, None, ua.userid())?; /// /// // Verify it. /// let pk = cert.primary_key().key(); /// /// new_sig.verify_userid_binding(signer.public(), pk, ua.userid())?; /// # Ok(()) /// # } /// ``` pub fn sign_userid_binding<'a, PK>(mut self, signer: &mut dyn Signer, key: PK, userid: &UserID) -> Result<Signature> where PK: Into<Option<&'a Key<key::PublicParts, key::PrimaryRole>>> { self = self.pre_sign(signer)?; let key = key.into().unwrap_or_else(|| signer.public().role_as_primary()); let mut hash = self.hash_algo().context()?.for_signature(self.version()); self.hash_userid_binding(&mut hash, key, userid)?; self.sign(signer, hash.into_digest()?) } /// Generates a subkey binding signature. /// /// A [subkey binding signature] is a signature over the primary /// key and a subkey, which is made by the primary key. It is an /// assertion by the certificate that the subkey really belongs to /// the certificate. That is, it binds the subkey to the /// certificate. /// /// Note: this function does not create a back signature, which is /// needed by certification-capable, signing-capable, and /// authentication-capable subkeys. A back signature can be /// created using [`SignatureBuilder::sign_primary_key_binding`]. /// /// This function is also used to create subkey revocations. /// /// [subkey binding signature]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.1 /// [`SignatureBuilder::sign_primary_key_binding`]: SignatureBuilder::sign_primary_key_binding() /// /// This function checks that the [signature type] (passed to /// [`SignatureBuilder::new`], set via /// [`SignatureBuilder::set_type`], or copied when using /// `SignatureBuilder::From`) is /// [`SignatureType::SubkeyBinding`], [`SignatureType::SubkeyRevocation`], or /// [`SignatureType::Unknown`]. /// /// [signature type]: crate::types::SignatureType /// [`SignatureBuilder::new`]: SignatureBuilder::new() /// [`SignatureBuilder::set_type`]: SignatureBuilder::set_type() /// [`SignatureType::SubkeyBinding`]: crate::types::SignatureType::SubkeyBinding /// [`SignatureType::SubkeyRevocation`]: crate::types::SignatureType::SubkeyRevocation /// [`SignatureType::Unknown`]: crate::types::SignatureType::Unknown /// /// The [`Signature`]'s public-key algorithm field is set to the /// algorithm used by `signer`. /// /// [`Signature`]: super::Signature /// /// If neither an [`Issuer`] subpacket (set using /// [`SignatureBuilder::set_issuer`], for instance) nor an /// [`Issuer Fingerprint`] subpacket (set using /// [`SignatureBuilder::set_issuer_fingerprint`], for instance) is /// set, they are both added to the new `Signature`'s hashed /// subpacket area and set to the `signer`'s `KeyID` and /// `Fingerprint`, respectively. /// /// [`Issuer`]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.12 /// [`SignatureBuilder::set_issuer`]: SignatureBuilder::set_issuer() /// [`Issuer Fingerprint`]: https://www.rfc-editor.org/rfc/rfc9580.html#name-issuer-fingerprint /// [`SignatureBuilder::set_issuer_fingerprint`]: SignatureBuilder::set_issuer_fingerprint() /// /// Likewise, a [`Signature Creation Time`] subpacket set to the /// current time is added to the hashed area if the `Signature /// Creation Time` subpacket hasn't been set using, for instance, /// the [`set_signature_creation_time`] method or the /// [`preserve_signature_creation_time`] method. /// /// [`Signature Creation Time`]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.11 /// [`set_signature_creation_time`]: SignatureBuilder::set_signature_creation_time() /// [`preserve_signature_creation_time`]: SignatureBuilder::preserve_signature_creation_time() /// /// If `pk` is set to `None` the signature will be computed over the public key /// retrieved from the `signer` parameter. /// /// # Examples /// /// Add a new subkey intended for encrypting data in motion to an /// existing certificate: /// /// ``` /// use sequoia_openpgp as openpgp; /// use openpgp::cert::prelude::*; /// use openpgp::packet::prelude::*; /// use openpgp::policy::StandardPolicy; /// use openpgp::types::{Curve, KeyFlags, SignatureType}; /// /// # fn main() -> openpgp::Result<()> { /// let p = &StandardPolicy::new(); /// /// let (cert, _) = CertBuilder::new().generate()?; /// # assert_eq!(cert.keys().count(), 1); /// /// let pk = cert.primary_key().key().clone().parts_into_secret()?; /// // Derive a signer. /// let mut pk_signer = pk.clone().into_keypair()?; /// /// // Generate an encryption subkey. /// let mut subkey: Key<_, _> = /// Key6::generate_ecc(false, Curve::Cv25519)?.into(); /// // Derive a signer. /// let mut sk_signer = subkey.clone().into_keypair()?; /// /// let sig = SignatureBuilder::new(SignatureType::SubkeyBinding) /// .set_key_flags(KeyFlags::empty().set_transport_encryption())? /// .sign_subkey_binding(&mut pk_signer, None, &subkey)?; /// /// let cert = cert.insert_packets(vec![Packet::SecretSubkey(subkey), /// sig.into()])?.0; /// /// assert_eq!(cert.with_policy(p, None)?.keys().count(), 2); /// # Ok(()) /// # } /// ``` pub fn sign_subkey_binding<'a, PK, Q>(mut self, signer: &mut dyn Signer, primary: PK, subkey: &Key<Q, key::SubordinateRole>) -> Result<Signature> where Q: key::KeyParts, PK: Into<Option<&'a Key<key::PublicParts, key::PrimaryRole>>>, { self = self.pre_sign(signer)?; let primary = primary.into().unwrap_or_else(|| signer.public().role_as_primary()); let mut hash = self.hash_algo().context()?.for_signature(self.version()); self.hash_subkey_binding(&mut hash, primary, subkey)?; self.sign(signer, hash.into_digest()?) } /// Generates a primary key binding signature. /// /// A [primary key binding signature], also referred to as a back /// signature or backsig, is a signature over the primary key and /// a subkey, which is made by the subkey. This signature is a /// statement by the subkey that it belongs to the primary key. /// That is, it binds the certificate to the subkey. It is /// normally stored in the subkey binding signature (see /// [`SignatureBuilder::sign_subkey_binding`]) in the [`Embedded /// Signature`] subpacket (set using /// [`SignatureBuilder::set_embedded_signature`]). /// /// [primary key binding signature]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.1 /// [`SignatureBuilder::sign_subkey_binding`]: SignatureBuilder::sign_subkey_binding() /// [`Embedded Signature`]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.34 /// [`SignatureBuilder::set_embedded_signature`]: SignatureBuilder::set_embedded_signature() /// /// All subkeys that make signatures of any sort (signature /// subkeys, certification subkeys, and authentication subkeys) /// must include this signature in their binding signature. This /// signature ensures that an attacker (Mallory) can't claim /// someone else's (Alice's) signing key by just creating a subkey /// binding signature. If that were the case, anyone who has /// Mallory's certificate could be tricked into thinking that /// Mallory made signatures that were actually made by Alice. /// This signature prevents this attack, because it proves that /// the person who controls the private key for the primary key /// also controls the private key for the subkey and therefore /// intended that the subkey be associated with the primary key. /// Thus, although Mallory controls his own primary key and can /// issue a subkey binding signature for Alice's signing key, he /// doesn't control her signing key, and therefore can't create a /// valid backsig. /// /// A primary key binding signature is not needed for /// encryption-capable subkeys. This is firstly because /// encryption-capable keys cannot make signatures. But also /// because an attacker doesn't gain anything by adopting an /// encryption-capable subkey: without the private key material, /// they still can't read the message's content. /// /// This function checks that the [signature type] (passed to /// [`SignatureBuilder::new`], set via /// [`SignatureBuilder::set_type`], or copied when using /// `SignatureBuilder::From`) is /// [`SignatureType::PrimaryKeyBinding`], or /// [`SignatureType::Unknown`]. /// /// [signature type]: crate::types::SignatureType /// [`SignatureBuilder::new`]: SignatureBuilder::new() /// [`SignatureBuilder::set_type`]: SignatureBuilder::set_type() /// [`SignatureType::PrimaryKeyBinding`]: crate::types::SignatureType::PrimaryKeyBinding /// [`SignatureType::Unknown`]: crate::types::SignatureType::Unknown /// /// The [`Signature`]'s public-key algorithm field is set to the /// algorithm used by `signer`. /// /// [`Signature`]: super::Signature /// /// If neither an [`Issuer`] subpacket (set using /// [`SignatureBuilder::set_issuer`], for instance) nor an /// [`Issuer Fingerprint`] subpacket (set using /// [`SignatureBuilder::set_issuer_fingerprint`], for instance) is /// set, they are both added to the new `Signature`'s hashed /// subpacket area and set to the `signer`'s `KeyID` and /// `Fingerprint`, respectively. /// /// [`Issuer`]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.12 /// [`SignatureBuilder::set_issuer`]: SignatureBuilder::set_issuer() /// [`Issuer Fingerprint`]: https://www.rfc-editor.org/rfc/rfc9580.html#name-issuer-fingerprint /// [`SignatureBuilder::set_issuer_fingerprint`]: SignatureBuilder::set_issuer_fingerprint() /// /// Likewise, a [`Signature Creation Time`] subpacket set to the /// current time is added to the hashed area if the `Signature /// Creation Time` subpacket hasn't been set using, for instance, /// the [`set_signature_creation_time`] method or the /// [`preserve_signature_creation_time`] method. /// /// [`Signature Creation Time`]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.11 /// [`set_signature_creation_time`]: SignatureBuilder::set_signature_creation_time() /// [`preserve_signature_creation_time`]: SignatureBuilder::preserve_signature_creation_time() /// /// # Examples /// /// Add a new signing-capable subkey to an existing certificate. /// Because we are adding a signing-capable subkey, the binding /// signature needs to include a backsig. /// /// ``` /// use sequoia_openpgp as openpgp; /// use openpgp::cert::prelude::*; /// use openpgp::packet::prelude::*; /// use openpgp::policy::StandardPolicy; /// use openpgp::types::{Curve, KeyFlags, SignatureType}; /// /// # fn main() -> openpgp::Result<()> { /// let p = &StandardPolicy::new(); /// /// let (cert, _) = CertBuilder::new().generate()?; /// # assert_eq!(cert.keys().count(), 1); /// /// let pk = cert.primary_key().key().clone().parts_into_secret()?; /// // Derive a signer. /// let mut pk_signer = pk.clone().into_keypair()?; /// /// // Generate a signing subkey. /// let mut subkey: Key<_, _> = /// Key6::generate_ecc(true, Curve::Ed25519)?.into(); /// // Derive a signer. /// let mut sk_signer = subkey.clone().into_keypair()?; /// /// let sig = SignatureBuilder::new(SignatureType::SubkeyBinding) /// .set_key_flags(KeyFlags::empty().set_signing())? /// // The backsig. This is essential for subkeys that create signatures! /// .set_embedded_signature( /// SignatureBuilder::new(SignatureType::PrimaryKeyBinding) /// .sign_primary_key_binding(&mut sk_signer, &pk, &subkey)?)? /// .sign_subkey_binding(&mut pk_signer, None, &subkey)?; /// /// let cert = cert.insert_packets(vec![Packet::SecretSubkey(subkey), /// sig.into()])?.0; /// /// assert_eq!(cert.with_policy(p, None)?.keys().count(), 2); /// # assert_eq!(cert.bad_signatures().count(), 0); /// # Ok(()) /// # } /// ``` pub fn sign_primary_key_binding<P, Q>(mut self, subkey_signer: &mut dyn Signer, primary: &Key<P, key::PrimaryRole>, subkey: &Key<Q, key::SubordinateRole>) -> Result<Signature> where P: key::KeyParts, Q: key::KeyParts, { self = self.pre_sign(subkey_signer)?; let mut hash = self.hash_algo().context()?.for_signature(self.version()); self.hash_primary_key_binding(&mut hash, primary, subkey)?; self.sign(subkey_signer, hash.into_digest()?) } /// Generates a User Attribute binding signature. /// /// A User Attribute binding signature or certification, a type of /// [User ID certification], is a signature over a User Attribute /// and a Primary Key. It asserts that the signer is convinced /// that the User Attribute should be associated with the /// Certificate, i.e., that the binding is authentic. /// /// [User ID certification]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.1 /// /// OpenPGP has four types of User Attribute certifications. They /// are intended to express the degree of the signer's conviction. /// In practice, the `Positive Certification` type is used for /// self-signatures, and the `Generic Certification` is used for /// third-party certifications; the other types are not normally /// used. /// /// This function checks that the [signature type] (passed to /// [`SignatureBuilder::new`], set via /// [`SignatureBuilder::set_type`], or copied when using /// `SignatureBuilder::From`) is [`GenericCertification`], /// [`PersonaCertification`], [`CasualCertification`], /// [`PositiveCertification`], [`CertificationRevocation`], or /// [`SignatureType::Unknown`]. /// /// [signature type]: crate::types::SignatureType /// [`SignatureBuilder::new`]: SignatureBuilder::new() /// [`SignatureBuilder::set_type`]: SignatureBuilder::set_type() /// [`GenericCertification`]: crate::types::SignatureType::GenericCertification /// [`PersonaCertification`]: crate::types::SignatureType::PersonaCertification /// [`CasualCertification`]: crate::types::SignatureType::CasualCertification /// [`PositiveCertification`]: crate::types::SignatureType::PositiveCertification /// [`CertificationRevocation`]: crate::types::SignatureType::CertificationRevocation /// [`SignatureType::Unknown`]: crate::types::SignatureType::Unknown /// /// The [`Signature`]'s public-key algorithm field is set to the /// algorithm used by `signer`. /// /// [`Signature`]: super::Signature /// /// If neither an [`Issuer`] subpacket (set using /// [`SignatureBuilder::set_issuer`], for instance) nor an /// [`Issuer Fingerprint`] subpacket (set using /// [`SignatureBuilder::set_issuer_fingerprint`], for instance) is /// set, they are both added to the new `Signature`'s hashed /// subpacket area and set to the `signer`'s `KeyID` and /// `Fingerprint`, respectively. /// /// [`Issuer`]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.12 /// [`SignatureBuilder::set_issuer`]: SignatureBuilder::set_issuer() /// [`Issuer Fingerprint`]: https://www.rfc-editor.org/rfc/rfc9580.html#name-issuer-fingerprint /// [`SignatureBuilder::set_issuer_fingerprint`]: SignatureBuilder::set_issuer_fingerprint() /// /// Likewise, a [`Signature Creation Time`] subpacket set to the /// current time is added to the hashed area if the `Signature /// Creation Time` subpacket hasn't been set using, for instance, /// the [`set_signature_creation_time`] method or the /// [`preserve_signature_creation_time`] method. /// /// [`Signature Creation Time`]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.11 /// [`set_signature_creation_time`]: SignatureBuilder::set_signature_creation_time() /// [`preserve_signature_creation_time`]: SignatureBuilder::preserve_signature_creation_time() /// /// If `pk` is set to `None` the signature will be computed over the public key /// retrieved from the `signer` parameter, i.e. a self-signature will be created. /// To create a third-party-signature provide an explicit public key as the /// `pk` parameter. /// /// # Examples /// /// Add a new User Attribute to an existing certificate: /// /// ``` /// use sequoia_openpgp as openpgp; /// use openpgp::cert::prelude::*; /// use openpgp::packet::prelude::*; /// use openpgp::policy::StandardPolicy; /// use openpgp::types::SignatureType; /// # use openpgp::packet::user_attribute::{Subpacket, Image}; /// /// # fn main() -> openpgp::Result<()> { /// let p = &StandardPolicy::new(); /// /// # // Add a bare user attribute. /// # let ua = UserAttribute::new(&[ /// # Subpacket::Image( /// # Image::Private(100, vec![0, 1, 2].into_boxed_slice())), /// # ])?; /// # /// let (cert, _) = CertBuilder::new().generate()?; /// # assert_eq!(cert.user_attributes().count(), 0); /// /// // Add a user attribute. /// /// // Get a usable (alive, non-revoked) certification key. /// let key : &Key<_, _> = cert /// .keys().with_policy(p, None) /// .for_certification().alive().revoked(false).nth(0).unwrap().key(); /// // Derive a signer. /// let mut signer = key.clone().parts_into_secret()?.into_keypair()?; /// /// let pk = cert.primary_key().key(); /// /// let mut sig = /// SignatureBuilder::new(SignatureType::PositiveCertification) /// .sign_user_attribute_binding(&mut signer, None, &ua)?; /// /// // Verify it. /// sig.verify_user_attribute_binding(signer.public(), pk, &ua)?; /// /// let cert = cert.insert_packets(vec![Packet::from(ua), sig.into()])?.0; /// assert_eq!(cert.with_policy(p, None)?.user_attributes().count(), 1); /// # Ok(()) /// # } /// ``` pub fn sign_user_attribute_binding<'a, PK>(mut self, signer: &mut dyn Signer, key: PK, ua: &UserAttribute) -> Result<Signature> where PK: Into<Option<&'a Key<key::PublicParts, key::PrimaryRole>>> { self = self.pre_sign(signer)?; let key = key.into().unwrap_or_else(|| signer.public().role_as_primary()); let mut hash = self.hash_algo().context()?.for_signature(self.version()); self.hash_user_attribute_binding(&mut hash, key, ua)?; self.sign(signer, hash.into_digest()?) } /// Generates a signature. /// /// This is a low-level function. Normally, you'll want to use /// one of the higher-level functions, like /// [`SignatureBuilder::sign_userid_binding`]. But, this function /// is useful if you want to create a [`Signature`] for an /// unsupported signature type. /// /// [`SignatureBuilder::sign_userid_binding`]: SignatureBuilder::sign_userid_binding() /// [`Signature`]: super::Signature /// /// The `Signature`'s public-key algorithm field is set to the /// algorithm used by `signer`. /// /// If neither an [`Issuer`] subpacket (set using /// [`SignatureBuilder::set_issuer`], for instance) nor an /// [`Issuer Fingerprint`] subpacket (set using /// [`SignatureBuilder::set_issuer_fingerprint`], for instance) is /// set, they are both added to the new `Signature`'s hashed /// subpacket area and set to the `signer`'s `KeyID` and /// `Fingerprint`, respectively. /// /// [`Issuer`]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.12 /// [`SignatureBuilder::set_issuer`]: SignatureBuilder::set_issuer() /// [`Issuer Fingerprint`]: https://www.rfc-editor.org/rfc/rfc9580.html#name-issuer-fingerprint /// [`SignatureBuilder::set_issuer_fingerprint`]: SignatureBuilder::set_issuer_fingerprint() /// /// Likewise, a [`Signature Creation Time`] subpacket set to the /// current time is added to the hashed area if the `Signature /// Creation Time` subpacket hasn't been set using, for instance, /// the [`set_signature_creation_time`] method or the /// [`preserve_signature_creation_time`] method. /// /// [`Signature Creation Time`]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.11 /// [`set_signature_creation_time`]: SignatureBuilder::set_signature_creation_time() /// [`preserve_signature_creation_time`]: SignatureBuilder::preserve_signature_creation_time() pub fn sign_hash(mut self, signer: &mut dyn Signer, mut hash: hash::Context) -> Result<Signature> { self.hash_algo = hash.algo(); self = self.pre_sign(signer)?; self.hash(&mut hash)?; let mut digest = vec![0u8; hash.digest_size()]; hash.digest(&mut digest)?; self.sign(signer, digest) } /// Signs a message. /// /// Normally, you'll want to use the [streaming `Signer`] to sign /// a message. /// /// [streaming `Signer`]: crate::serialize::stream::Signer /// /// OpenPGP supports two types of signatures over messages: binary /// and text. The text version normalizes line endings. But, /// since nearly all software today can deal with both Unix and /// DOS line endings, it is better to just use the binary version /// even when dealing with text. This avoids any possible /// ambiguity. /// /// This function checks that the [signature type] (passed to /// [`SignatureBuilder::new`], set via /// [`SignatureBuilder::set_type`], or copied when using /// `SignatureBuilder::From`) is [`Binary`], [`Text`], or /// [`SignatureType::Unknown`]. /// /// [signature type]: crate::types::SignatureType /// [`SignatureBuilder::new`]: SignatureBuilder::new() /// [`SignatureBuilder::set_type`]: SignatureBuilder::set_type() /// [`Binary`]: crate::types::SignatureType::Binary /// [`Text`]: crate::types::SignatureType::Text /// [`SignatureType::Unknown`]: crate::types::SignatureType::Unknown /// /// The [`Signature`]'s public-key algorithm field is set to the /// algorithm used by `signer`. /// /// [`Signature`]: super::Signature /// /// If neither an [`Issuer`] subpacket (set using /// [`SignatureBuilder::set_issuer`], for instance) nor an /// [`Issuer Fingerprint`] subpacket (set using /// [`SignatureBuilder::set_issuer_fingerprint`], for instance) is /// set, they are both added to the new `Signature`'s hashed /// subpacket area and set to the `signer`'s `KeyID` and /// `Fingerprint`, respectively. /// /// [`Issuer`]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.12 /// [`SignatureBuilder::set_issuer`]: SignatureBuilder::set_issuer() /// [`Issuer Fingerprint`]: https://www.rfc-editor.org/rfc/rfc9580.html#name-issuer-fingerprint /// [`SignatureBuilder::set_issuer_fingerprint`]: SignatureBuilder::set_issuer_fingerprint() /// /// Likewise, a [`Signature Creation Time`] subpacket set to the /// current time is added to the hashed area if the `Signature /// Creation Time` subpacket hasn't been set using, for instance, /// the [`set_signature_creation_time`] method or the /// [`preserve_signature_creation_time`] method. /// /// [`Signature Creation Time`]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.11 /// [`set_signature_creation_time`]: SignatureBuilder::set_signature_creation_time() /// [`preserve_signature_creation_time`]: SignatureBuilder::preserve_signature_creation_time() /// /// # Examples /// /// Signs a document. For large messages, you should use the /// [streaming `Signer`], which streams the message's content. /// /// [streaming `Signer`]: crate::serialize::stream::Signer /// /// ``` /// use sequoia_openpgp as openpgp; /// use openpgp::cert::prelude::*; /// use openpgp::packet::prelude::*; /// use openpgp::policy::StandardPolicy; /// use openpgp::types::SignatureType; /// /// # fn main() -> openpgp::Result<()> { /// let p = &StandardPolicy::new(); /// /// let (cert, _) = CertBuilder::new().add_signing_subkey().generate()?; /// /// // Get a usable (alive, non-revoked) signing key. /// let key : &Key<_, _> = cert /// .keys().with_policy(p, None) /// .for_signing().alive().revoked(false).nth(0).unwrap().key(); /// // Derive a signer. /// let mut signer = key.clone().parts_into_secret()?.into_keypair()?; /// /// // For large messages, you should use openpgp::serialize::stream::Signer, /// // which streams the message's content. /// let msg = b"Hello, world!"; /// let mut sig = SignatureBuilder::new(SignatureType::Binary) /// .sign_message(&mut signer, msg)?; /// /// // Verify it. /// sig.verify_message(signer.public(), msg)?; /// # Ok(()) /// # } /// ``` pub fn sign_message<M>(mut self, signer: &mut dyn Signer, msg: M) -> Result<Signature> where M: AsRef<[u8]> { match self.typ { SignatureType::Binary => (), SignatureType::Text => (), SignatureType::Unknown(_) => (), _ => return Err(Error::UnsupportedSignatureType(self.typ).into()), } self = self.pre_sign(signer)?; // Hash the message let mut hash = self.hash_algo.context()?.for_signature(self.version()); if let Some(salt) = self.sb_version.salt() { hash.update(salt); } hash.update(msg.as_ref()); self.hash(&mut hash)?; let mut digest = vec![0u8; hash.digest_size()]; hash.digest(&mut digest)?; self.sign(signer, digest) } /// Sets the signature builder's default reference time. /// /// The reference time is used when no time is specified. The /// reference time is the current time by default and is evaluated /// on demand. /// /// # Examples /// /// ``` /// use std::time::{Duration, SystemTime}; /// use sequoia_openpgp as openpgp; /// use openpgp::types::SignatureType; /// use openpgp::packet::prelude::*; /// /// # fn main() -> openpgp::Result<()> { /// # /// // If we don't set a reference time, then the current time is used /// // when the signature is created. /// let sig = SignatureBuilder::new(SignatureType::PositiveCertification); /// let ct = sig.effective_signature_creation_time()?.expect("creation time"); /// assert!(SystemTime::now().duration_since(ct).expect("ct is in the past") /// < Duration::new(1, 0)); /// /// // If we set a reference time and don't set a creation time, /// // then that time is used for the creation time. /// let t = std::time::UNIX_EPOCH + Duration::new(1646660000, 0); /// let sig = sig.set_reference_time(t)?; /// assert_eq!(sig.effective_signature_creation_time()?, Some(t)); /// # Ok(()) } /// ``` pub fn set_reference_time<T>(mut self, reference_time: T) -> Result<Self> where T: Into<Option<SystemTime>>, { let reference_time = reference_time.into(); // Make sure the time is representable. if let Some(t) = reference_time.clone() { Timestamp::try_from(t)?; } self.reference_time = reference_time; Ok(self) } /// Returns the signature creation time that would be used if a /// signature were created now. /// /// # Examples /// /// ``` /// use std::time::{Duration, SystemTime}; /// use sequoia_openpgp as openpgp; /// use openpgp::types::SignatureType; /// use openpgp::packet::prelude::*; /// /// # fn main() -> openpgp::Result<()> { /// # /// // If we don't set a creation time, then the current time is used. /// let sig = SignatureBuilder::new(SignatureType::PositiveCertification); /// let ct = sig.effective_signature_creation_time()?.expect("creation time"); /// assert!(SystemTime::now().duration_since(ct).expect("ct is in the past") /// < Duration::new(1, 0)); /// /// // If we set a signature creation time, then we should get it back. /// let t = SystemTime::now() - Duration::new(24 * 60 * 60, 0); /// let sig = sig.set_signature_creation_time(t)?; /// assert!(t.duration_since( /// sig.effective_signature_creation_time()?.unwrap()).unwrap() /// < Duration::new(1, 0)); /// # Ok(()) } /// ``` pub fn effective_signature_creation_time(&self) -> Result<Option<SystemTime>> { use std::time; let now = || -> Result<SystemTime> { let rt = self.reference_time.unwrap_or_else(crate::now); // Roundtrip via Timestamp to ensure that the time has the // right resolution and is representable. Ok(SystemTime::from(Timestamp::try_from(rt)?)) }; if ! self.overrode_creation_time { // See if we want to backdate the signature. if let Some(orig) = self.original_creation_time { let now = now()?; let t = (orig + time::Duration::new(1, 0)).max( now - time::Duration::new(SIG_BACKDATE_BY, 0)); if t > now { return Err(Error::InvalidOperation( "Cannot create valid signature newer than SignatureBuilder template" .into()).into()); } Ok(Some(t)) } else { Ok(Some(now()?)) } } else { Ok(self.signature_creation_time()) } } /// Adjusts signature prior to signing. /// /// This function is called implicitly when a signature is created /// (e.g. using [`SignatureBuilder::sign_message`]). Usually, /// there is no need to call it explicitly. /// /// This function makes sure that generated signatures have a /// creation time, issuer information, and are not predictable by /// including a salt. Then, it sorts the subpackets. The /// function is idempotent modulo salt value. /// /// # Examples /// /// Occasionally, it is useful to determine the available space in /// a subpacket area. To take the effect of this function into /// account, call this function explicitly: /// /// ``` /// # use sequoia_openpgp as openpgp; /// # fn main() -> openpgp::Result<()> { /// # use openpgp::packet::prelude::*; /// # use openpgp::types::Curve; /// # use openpgp::packet::signature::subpacket::SubpacketArea; /// # use openpgp::types::SignatureType; /// # /// # let key: Key<key::SecretParts, key::PrimaryRole> /// # = Key::from(Key6::generate_ecc(true, Curve::Ed25519)?); /// # let mut signer = key.into_keypair()?; /// let sig = SignatureBuilder::new(SignatureType::Binary) /// .pre_sign(&mut signer)?; // Important for size calculation. /// /// // Compute the available space in the hashed area. For this, /// // it is important that template.pre_sign has been called. /// use openpgp::serialize::MarshalInto; /// let available_space = /// SubpacketArea::MAX_SIZE - sig.hashed_area().serialized_len(); /// /// // Let's check whether our prediction was right. /// let sig = sig.sign_message(&mut signer, b"Hello World :)")?; /// assert_eq!( /// available_space, /// SubpacketArea::MAX_SIZE - sig.hashed_area().serialized_len()); /// # Ok(()) } /// ``` pub fn pre_sign(mut self, signer: &dyn Signer) -> Result<Self> { let pk = signer.public(); self.pk_algo = pk.pk_algo(); // Set the version. A Key6 will create a Signature6, a key4 // will create a Signature4. If necessary, generate a salt. // If the salt has been explicitly set, it is not changed. self.sb_version = match (self.sb_version, pk.version()) { (SBVersion::V4 {}, 4) => SBVersion::V4 {}, (SBVersion::V6 { .. }, 4) => SBVersion::V4 {}, (SBVersion::V4 {}, 6) => { let mut salt = vec![0; self.fields.hash_algo().salt_size()?]; crate::crypto::random(&mut salt)?; SBVersion::V6 { salt } }, (SBVersion::V6 { salt }, 6) => SBVersion::V6 { salt }, (_, n) => return Err(Error::InvalidOperation( format!("Unsupported key version {}", n)).into()), }; // Update the version in the field's struct. self.fields.version = self.sb_version.to_u8(); // Set the creation time. if ! self.overrode_creation_time { if let Some(t) = self.effective_signature_creation_time()? { self = self.set_signature_creation_time(t)?; } } match &self.sb_version { SBVersion::V4 {} => { // Make sure we have an issuer packet. if self.issuers().next().is_none() && self.issuer_fingerprints().next().is_none() { self = self.set_issuer(signer.public().keyid())? .set_issuer_fingerprint(signer.public().fingerprint())?; } // Add a salt to v4 signatures to make the signature // unpredictable. let mut salt = [0; 32]; crate::crypto::random(&mut salt)?; self = self.set_notation("salt@notations.sequoia-pgp.org", salt, None, false)?; }, SBVersion::V6 { .. } => { // Make sure we have an issuer fingerprint packet. if self.issuer_fingerprints().next().is_none() { self = self .set_issuer_fingerprint(signer.public().fingerprint())?; } // In v6 signatures, we have a proper prefix salt. }, } self.sort(); Ok(self) } /// Returns the prefix salt. /// /// In OpenPGP v6 signatures, a salt is prefixed to the data /// stream hashed in the signature. If a v6 signature is /// generated by using a v6 key, then a salt will be added /// automatically. If a salt has been set explicitly (see /// [`SignatureBuilder::set_prefix_salt`]), this function will /// return the salt. pub fn prefix_salt(&self) -> Option<&[u8]> { match &self.sb_version { SBVersion::V4 {} => None, SBVersion::V6 { salt } => Some(salt), } } /// Explicitly sets the prefix salt. /// /// In OpenPGP v6 signatures, a salt is prefixed to the data /// stream hashed in the signature. If a v6 signature is /// generated by using a v6 key, then a salt will be added /// automatically. In general, you do not need to call this /// function. /// /// When streaming a signed message, it is useful to explicitly /// set the salt using this function. pub fn set_prefix_salt(mut self, new_salt: Vec<u8>) -> (Self, Option<Vec<u8>>) { let mut old = None; self.sb_version = match std::mem::take(&mut self.sb_version) { SBVersion::V4 {} => SBVersion::V6 { salt: new_salt }, SBVersion::V6 { salt } => { old = Some(salt); SBVersion::V6 { salt: new_salt } }, }; (self, old) } fn sign(self, signer: &mut dyn Signer, digest: Vec<u8>) -> Result<Signature> { // DSA is phased out in RFC9580. #[allow(deprecated)] if matches!(self.sb_version, SBVersion::V6 { .. }) && self.fields.pk_algo() == PublicKeyAlgorithm::DSA { return Err(Error::BadSignature( "Version 6 signatures using DSA MUST NOT be created".into()) .into()); } let mpis = signer.sign(self.hash_algo, &digest)?; let v4 = Signature4 { common: Default::default(), fields: self.fields, digest_prefix: [digest[0], digest[1]], mpis, computed_digest: digest.into(), level: 0, additional_issuers: OnceLock::new(), }; match self.sb_version { SBVersion::V4 {} => Ok(v4.into()), SBVersion::V6 { salt } => Ok(Signature6::from_common(v4, salt)?.into()) } } } impl From<Signature> for SignatureBuilder { fn from(sig: Signature) -> Self { match sig { Signature::V3(sig) => sig.into(), Signature::V4(sig) => sig.into(), Signature::V6(sig) => sig.into(), } } } impl From<Signature4> for SignatureBuilder { fn from(sig: Signature4) -> Self { let mut fields = sig.fields; fields.hash_algo = HashAlgorithm::default(); let creation_time = fields.signature_creation_time(); fields.hashed_area_mut().remove_all(SubpacketTag::SignatureCreationTime); fields.hashed_area_mut().remove_all(SubpacketTag::Issuer); fields.hashed_area_mut().remove_all(SubpacketTag::IssuerFingerprint); fields.unhashed_area_mut().remove_all(SubpacketTag::SignatureCreationTime); fields.unhashed_area_mut().remove_all(SubpacketTag::Issuer); fields.unhashed_area_mut().remove_all(SubpacketTag::IssuerFingerprint); SignatureBuilder { reference_time: None, overrode_creation_time: false, original_creation_time: creation_time, fields, sb_version: Default::default(), } } } impl From<Signature6> for SignatureBuilder { fn from(sig: Signature6) -> Self { SignatureBuilder::from(sig.common) } } /// Holds a v4 Signature packet. /// /// This holds a [version 4] Signature packet. Normally, you won't /// directly work with this data structure, but with the [`Signature`] /// enum, which is version agnostic. An exception is when you need to /// do version-specific operations. But currently, there aren't any /// version-specific methods. /// /// [version 4]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2 /// [`Signature`]: super::Signature #[derive(Clone)] pub struct Signature4 { /// CTB packet header fields. pub(crate) common: packet::Common, /// Fields as configured using the SignatureBuilder. pub(crate) fields: SignatureFields, /// Upper 16 bits of the signed hash value. digest_prefix: [u8; 2], /// Signature MPIs. mpis: mpi::Signature, /// The computed digest. /// /// When used in conjunction with a one-pass signature, this is the /// hash computed over the enclosed message. /// /// This is also set when a signature is successfully verified, /// and on signatures during certificate canonicalization. computed_digest: OnceLock<Vec<u8>>, /// Signature level. /// /// A level of 0 indicates that the signature is directly over the /// data, a level of 1 means that the signature is a notarization /// over all level 0 signatures and the data, and so on. level: usize, /// Additional issuer information. /// /// When we verify a signature successfully, we know the key that /// made the signature. Hence, we can compute the fingerprint, /// either a V4 one or a later one. If this information is /// missing from the signature, we can add it to the unhashed /// subpacket area at a convenient time. We don't add it when /// verifying, because that would mean that verifying a signature /// would change the serialized representation, and signature /// verification is usually expected to be idempotent. additional_issuers: OnceLock<Vec<KeyHandle>>, } assert_send_and_sync!(Signature4); impl fmt::Debug for Signature4 { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.debug_struct("Signature4") .field("version", &self.version()) .field("typ", &self.typ()) .field("pk_algo", &self.pk_algo()) .field("hash_algo", &self.hash_algo()) .field("hashed_area", self.hashed_area()) .field("unhashed_area", self.unhashed_area()) .field("additional_issuers", &self.additional_issuers()) .field("digest_prefix", &crate::fmt::to_hex(&self.digest_prefix, false)) .field( "computed_digest", &self .computed_digest .get() .map(|hash| crate::fmt::to_hex(&hash[..], false)), ) .field("level", &self.level) .field("mpis", &self.mpis) .finish() } } impl PartialEq for Signature4 { /// This method tests for self and other values to be equal, and /// is used by ==. /// /// This method compares the serialized version of the two /// packets. Thus, the computed values are ignored ([`level`], /// [`computed_digest`]). /// /// Note: because this function also compares the unhashed /// subpacket area, it is possible for a malicious party to take /// valid signatures, add subpackets to the unhashed area, /// yielding valid but distinct signatures. If you want to ignore /// the unhashed area, you should instead use the /// [`Signature::normalized_eq`] method. /// /// [`level`]: Signature4::level() /// [`computed_digest`]: Signature4::computed_digest() fn eq(&self, other: &Signature4) -> bool { self.cmp(other) == Ordering::Equal } } impl Eq for Signature4 {} impl PartialOrd for Signature4 { fn partial_cmp(&self, other: &Signature4) -> Option<Ordering> { Some(self.cmp(other)) } } impl Ord for Signature4 { fn cmp(&self, other: &Signature4) -> Ordering { self.fields.cmp(&other.fields) .then_with(|| self.digest_prefix.cmp(&other.digest_prefix)) .then_with(|| self.mpis.cmp(&other.mpis)) } } impl std::hash::Hash for Signature4 { fn hash<H: std::hash::Hasher>(&self, state: &mut H) { use std::hash::Hash as StdHash; StdHash::hash(&self.mpis, state); StdHash::hash(&self.fields, state); self.digest_prefix.hash(state); } } impl Signature4 { /// Creates a new signature packet. /// /// If you want to sign something, consider using the [`SignatureBuilder`] /// interface. pub fn new(typ: SignatureType, pk_algo: PublicKeyAlgorithm, hash_algo: HashAlgorithm, hashed_area: SubpacketArea, unhashed_area: SubpacketArea, digest_prefix: [u8; 2], mpis: mpi::Signature) -> Self { Signature4 { common: Default::default(), fields: SignatureFields { version: 4, typ, pk_algo, hash_algo, subpackets: SubpacketAreas::new(hashed_area, unhashed_area), }, digest_prefix, mpis, computed_digest: OnceLock::new(), level: 0, additional_issuers: OnceLock::new(), } } /// Gets the public key algorithm. // SigantureFields::pk_algo is private, because we don't want it // available on SignatureBuilder, which also derefs to // &SignatureFields. pub fn pk_algo(&self) -> PublicKeyAlgorithm { self.fields.pk_algo() } /// Gets the hash prefix. pub fn digest_prefix(&self) -> &[u8; 2] { &self.digest_prefix } /// Sets the hash prefix. #[allow(dead_code)] pub(crate) fn set_digest_prefix(&mut self, prefix: [u8; 2]) -> [u8; 2] { ::std::mem::replace(&mut self.digest_prefix, prefix) } /// Gets the signature packet's MPIs. pub fn mpis(&self) -> &mpi::Signature { &self.mpis } /// Sets the signature packet's MPIs. #[allow(dead_code)] pub(crate) fn set_mpis(&mut self, mpis: mpi::Signature) -> mpi::Signature { ::std::mem::replace(&mut self.mpis, mpis) } /// Gets the computed hash value. /// /// This is set by the [`PacketParser`] when parsing the message, /// during successful verification of signatures, and on /// signatures during certificate canonicalization. /// /// [`PacketParser`]: crate::parse::PacketParser pub fn computed_digest(&self) -> Option<&[u8]> { self.computed_digest.get().map(|d| &d[..]) } /// Sets the computed hash value, once. /// /// Calling this function a second time has no effect. pub(crate) fn set_computed_digest(&self, hash: Option<Vec<u8>>) { let _ = self.computed_digest.set(hash.unwrap_or_default()); } /// Gets the signature level. /// /// A level of 0 indicates that the signature is directly over the /// data, a level of 1 means that the signature is a notarization /// over all level 0 signatures and the data, and so on. pub fn level(&self) -> usize { self.level } /// Sets the signature level. /// /// A level of 0 indicates that the signature is directly over the /// data, a level of 1 means that the signature is a notarization /// over all level 0 signatures and the data, and so on. pub(crate) fn set_level(&mut self, level: usize) -> usize { ::std::mem::replace(&mut self.level, level) } /// Returns whether this signature should be exported. /// /// This checks whether the [`Exportable Certification`] subpacket /// is absent or present and 1, and that the signature does not /// include any sensitive [`Revocation Key`] (designated revokers) /// subpackets. /// /// [`Exportable Certification`]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.19 /// [`Revocation Key`]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.23 pub fn exportable(&self) -> Result<()> { if ! self.exportable_certification().unwrap_or(true) { return Err(Error::InvalidOperation( "Cannot export non-exportable certification".into()).into()); } if self.revocation_keys().any(|r| r.sensitive()) { return Err(Error::InvalidOperation( "Cannot export signature with sensitive designated revoker" .into()).into()); } Ok(()) } /// Returns the additional (i.e. newly discovered) issuers. fn additional_issuers(&self) -> &[KeyHandle] { self.additional_issuers.get().map(|v| v.as_slice()).unwrap_or(&[]) } } impl From<Signature3> for SignatureBuilder { fn from(sig: Signature3) -> Self { SignatureBuilder::from(sig.intern) } } /// Holds a v3 Signature packet. /// /// This holds a [version 3] Signature packet. Normally, you won't /// directly work with this data structure, but with the [`Signature`] /// enum, which is version agnostic. An exception is when you need to /// do version-specific operations. But currently, there aren't any /// version-specific methods. /// /// [version 3]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2 /// [`Signature`]: super::Signature /// /// Note: Per [Section 5.2 of RFC 9580], v3 signatures should not be /// generated, but they should be accepted. As such, support for /// version 3 signatures is limited to verifying them, but not /// generating them. /// /// [Section 5.2 of RFC 9580]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2 #[derive(Clone)] pub struct Signature3 { pub(crate) intern: Signature4, } assert_send_and_sync!(Signature3); impl TryFrom<Signature> for Signature3 { type Error = anyhow::Error; fn try_from(sig: Signature) -> Result<Self> { match sig { Signature::V3(sig) => Ok(sig), sig => Err( Error::InvalidArgument( format!( "Got a v{}, require a v3 signature", sig.version())) .into()), } } } // Yes, Signature3 derefs to Signature4. This is because Signature // derefs to Signature4 so this is the only way to add support for v3 // sigs without breaking the semver. impl Deref for Signature3 { type Target = Signature4; fn deref(&self) -> &Self::Target { &self.intern } } impl DerefMut for Signature3 { fn deref_mut(&mut self) -> &mut Self::Target { &mut self.intern } } impl fmt::Debug for Signature3 { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.debug_struct("Signature3") .field("version", &self.version()) .field("typ", &self.typ()) .field("pk_algo", &self.pk_algo()) .field("hash_algo", &self.hash_algo()) .field("hashed_area", self.hashed_area()) .field("unhashed_area", self.unhashed_area()) .field("additional_issuers", &self.additional_issuers()) .field("digest_prefix", &crate::fmt::to_hex(&self.digest_prefix, false)) .field( "computed_digest", &self .computed_digest .get() .map(|hash| crate::fmt::to_hex(&hash[..], false)), ) .field("level", &self.level) .field("mpis", &self.mpis) .finish() } } impl PartialEq for Signature3 { /// This method tests for self and other values to be equal, and /// is used by ==. /// /// This method compares the serialized version of the two /// packets. Thus, the computed values are ignored ([`level`], /// [`computed_digest`]). /// /// [`level`]: Signature3::level() /// [`computed_digest`]: Signature3::computed_digest() fn eq(&self, other: &Signature3) -> bool { self.cmp(other) == Ordering::Equal } } impl Eq for Signature3 {} impl PartialOrd for Signature3 { fn partial_cmp(&self, other: &Signature3) -> Option<Ordering> { Some(self.cmp(other)) } } impl Ord for Signature3 { fn cmp(&self, other: &Signature3) -> Ordering { self.intern.cmp(&other.intern) } } impl std::hash::Hash for Signature3 { fn hash<H: std::hash::Hasher>(&self, state: &mut H) { use std::hash::Hash as StdHash; StdHash::hash(&self.intern, state); } } impl Signature3 { /// Creates a new signature packet. /// /// If you want to sign something, consider using the [`SignatureBuilder`] /// interface. /// pub fn new(typ: SignatureType, creation_time: Timestamp, issuer: KeyID, pk_algo: PublicKeyAlgorithm, hash_algo: HashAlgorithm, digest_prefix: [u8; 2], mpis: mpi::Signature) -> Self { let hashed_area = SubpacketArea::new(vec![ Subpacket::new( SubpacketValue::SignatureCreationTime(creation_time), true).expect("fits"), ]).expect("fits"); let unhashed_area = SubpacketArea::new(vec![ Subpacket::new( SubpacketValue::Issuer(issuer), false).expect("fits"), ]).expect("fits"); let mut sig = Signature4::new(typ, pk_algo, hash_algo, hashed_area, unhashed_area, digest_prefix, mpis); sig.version = 3; Signature3 { intern: sig, } } /// Gets the public key algorithm. // SigantureFields::pk_algo is private, because we don't want it // available on SignatureBuilder, which also derefs to // &SignatureFields. pub fn pk_algo(&self) -> PublicKeyAlgorithm { self.fields.pk_algo() } /// Gets the hash prefix. pub fn digest_prefix(&self) -> &[u8; 2] { &self.digest_prefix } /// Gets the signature packet's MPIs. pub fn mpis(&self) -> &mpi::Signature { &self.mpis } /// Gets the computed hash value. /// /// This is set by the [`PacketParser`] when parsing the message. /// /// [`PacketParser`]: crate::parse::PacketParser pub fn computed_digest(&self) -> Option<&[u8]> { self.computed_digest.get().map(|d| &d[..]) } /// Gets the signature level. /// /// A level of 0 indicates that the signature is directly over the /// data, a level of 1 means that the signature is a notarization /// over all level 0 signatures and the data, and so on. pub fn level(&self) -> usize { self.level } } impl crate::packet::Signature { /// Returns the value of any Issuer and Issuer Fingerprint subpackets. /// /// The [Issuer subpacket] and [Issuer Fingerprint subpacket] are /// used when processing a signature to identify which certificate /// created the signature. Since this information is /// self-authenticating (the act of validating the signature /// authenticates the subpacket), it is typically stored in the /// unhashed subpacket area. /// /// [Issuer subpacket]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.12 /// [Issuer Fingerprint subpacket]: https://www.rfc-editor.org/rfc/rfc9580.html#name-issuer-fingerprint /// /// This function returns all instances of the Issuer subpacket /// and the Issuer Fingerprint subpacket in both the hashed /// subpacket area and the unhashed subpacket area. /// /// If you are only looking for the issuer subpackets use /// [`SubpacketAreas::issuers`], or if you are looking only for /// the issuer fingerprint subpackets use /// [`SubpacketAreas::issuer_fingerprints`]. /// /// The issuers are sorted so that the `Fingerprints` come before /// `KeyID`s. The `Fingerprint`s and `KeyID`s are not further /// sorted, but are returned in the order that they are /// encountered. pub fn get_issuers(&self) -> Vec<crate::KeyHandle> { let mut issuers: Vec<_> = self.hashed_area().iter() .chain(self.unhashed_area().iter()) .filter_map(|subpacket| { match subpacket.value() { SubpacketValue::Issuer(i) => Some(i.into()), SubpacketValue::IssuerFingerprint(i) => Some(i.into()), _ => None, } }) .collect(); // Sort the issuers so that the fingerprints come first. issuers.sort_by(|a, b| { use crate::KeyHandle::*; use std::cmp::Ordering::*; match (a, b) { (Fingerprint(_), Fingerprint(_)) => Equal, (KeyID(_), Fingerprint(_)) => Greater, (Fingerprint(_), KeyID(_)) => Less, (KeyID(_), KeyID(_)) => Equal, } }); issuers } /// Compares Signatures ignoring the unhashed subpacket area. /// /// This comparison function ignores the unhashed subpacket area /// when comparing two signatures. This prevents a malicious /// party from taking valid signatures, adding subpackets to the /// unhashed area, and deriving valid but distinct signatures, /// which could be used to perform a denial-of-service attack. /// For instance, an attacker could create a lot of signatures, /// which need to be validated. Ignoring the unhashed subpackets /// means that we can deduplicate signatures using this predicate. /// /// Unlike [`Signature::normalize`], this method ignores /// authenticated packets in the unhashed subpacket area. /// /// # Examples /// /// ``` /// use sequoia_openpgp as openpgp; /// use openpgp::cert::prelude::*; /// use openpgp::packet::prelude::*; /// use openpgp::packet::signature::subpacket::{Subpacket, SubpacketValue}; /// use openpgp::policy::StandardPolicy; /// use openpgp::types::SignatureType; /// use openpgp::types::Features; /// /// # fn main() -> openpgp::Result<()> { /// let p = &StandardPolicy::new(); /// /// let (cert, _) = CertBuilder::new().generate()?; /// /// let orig = cert.with_policy(p, None)?.direct_key_signature()?; /// /// // Add an inconspicuous subpacket to the unhashed area. /// let sb = Subpacket::new(SubpacketValue::Features(Features::empty()), false)?; /// let mut modified = orig.clone(); /// modified.unhashed_area_mut().add(sb); /// /// // We modified the signature, but the signature is still valid. /// modified.verify_direct_key(cert.primary_key().key(), cert.primary_key().key()); /// /// // PartialEq considers the packets to not be equal... /// assert!(orig != &modified); /// // ... but normalized_eq does. /// assert!(orig.normalized_eq(&modified)); /// # Ok(()) /// # } /// ``` pub fn normalized_eq(&self, other: &Signature) -> bool { self.normalized_cmp(other) == Ordering::Equal } /// Compares Signatures ignoring the unhashed subpacket area. /// /// This is useful to deduplicate signatures by first sorting them /// using this function, and then deduplicating using the /// [`Signature::normalized_eq`] predicate. /// /// This comparison function ignores the unhashed subpacket area /// when comparing two signatures. This prevents a malicious /// party from taking valid signatures, adding subpackets to the /// unhashed area, and deriving valid but distinct signatures, /// which could be used to perform a denial-of-service attack. /// For instance, an attacker could create a lot of signatures, /// which need to be validated. Ignoring the unhashed subpackets /// means that we can deduplicate signatures using this predicate. /// /// Unlike [`Signature::normalize`], this method ignores /// authenticated packets in the unhashed subpacket area. /// /// # Examples /// /// ``` /// use std::cmp::Ordering; /// use sequoia_openpgp as openpgp; /// use openpgp::cert::prelude::*; /// use openpgp::packet::prelude::*; /// use openpgp::packet::signature::subpacket::{Subpacket, SubpacketValue}; /// use openpgp::policy::StandardPolicy; /// use openpgp::types::SignatureType; /// use openpgp::types::Features; /// /// # fn main() -> openpgp::Result<()> { /// let p = &StandardPolicy::new(); /// /// let (cert, _) = CertBuilder::new().generate()?; /// /// let orig = cert.with_policy(p, None)?.direct_key_signature()?; /// /// // Add an inconspicuous subpacket to the unhashed area. /// let sb = Subpacket::new(SubpacketValue::Features(Features::empty()), false)?; /// let mut modified = orig.clone(); /// modified.unhashed_area_mut().add(sb); /// /// // We modified the signature, but the signature is still valid. /// modified.verify_direct_key(cert.primary_key().key(), cert.primary_key().key()); /// /// // PartialEq considers the packets to not be equal... /// assert!(orig != &modified); /// // ... but normalized_partial_cmp does. /// assert!(orig.normalized_cmp(&modified) == Ordering::Equal); /// # Ok(()) } /// ``` pub fn normalized_cmp(&self, other: &Signature) -> Ordering { self.version().cmp(&other.version()) .then_with(|| self.typ().cmp(&other.typ())) .then_with(|| self.pk_algo().cmp(&other.pk_algo())) .then_with(|| self.hash_algo().cmp(&other.hash_algo())) .then_with(|| self.hashed_area().cmp(other.hashed_area())) .then_with(|| self.digest_prefix().cmp(other.digest_prefix())) .then_with(|| self.mpis().cmp(other.mpis())) } /// Hashes everything but the unhashed subpacket area into state. /// /// This is an alternate implementation of [`Hash`], which does /// not hash the unhashed subpacket area. /// /// [`Hash`]: std::hash::Hash /// /// Unlike [`Signature::normalize`], this method ignores /// authenticated packets in the unhashed subpacket area. pub fn normalized_hash<H>(&self, state: &mut H) where H: Hasher { use std::hash::Hash; self.version.hash(state); self.typ.hash(state); self.pk_algo.hash(state); self.hash_algo.hash(state); self.hashed_area().hash(state); self.digest_prefix().hash(state); Hash::hash(&self.mpis(), state); } /// Normalizes the signature. /// /// This function normalizes the *unhashed* signature subpackets. /// /// First, it removes all but the following self-authenticating /// subpackets: /// /// - `SubpacketValue::Issuer` /// - `SubpacketValue::IssuerFingerprint` /// - `SubpacketValue::EmbeddedSignature` /// /// Note: the retained subpackets are not checked for validity. /// /// Then, it adds any missing issuer information to the unhashed /// subpacket area that has been computed when verifying the /// signature. pub fn normalize(&self) -> Self { use subpacket::SubpacketTag::*; let mut sig = self.clone(); { let area = sig.unhashed_area_mut(); area.clear(); for spkt in self.unhashed_area().iter() .filter(|s| s.tag() == Issuer || s.tag() == IssuerFingerprint || s.tag() == EmbeddedSignature) { area.add(spkt.clone()) .expect("it did fit into the old area"); } // Add missing issuer information. This is icing on the // cake, hence it is only a best-effort mechanism that // silently fails. let _ = sig.add_missing_issuers(); // Normalize the order of subpackets. sig.unhashed_area_mut().sort(); } sig } /// Adds missing issuer information. /// /// Calling this function adds any missing issuer information to /// the unhashed subpacket area. /// /// When a signature is verified, the identity of the signing key /// is computed and stored in the `Signature` struct. This /// information can be used to complement the issuer information /// stored in the signature. Note that we don't do this /// automatically when verifying signatures, because that would /// change the serialized representation of the signature as a /// side effect of verifying the signature. pub fn add_missing_issuers(&mut self) -> Result<()> { if self.additional_issuers().is_empty() { return Ok(()); } if self.version() == 3 { return Err(Error::InvalidOperation( "cannot add information to v3 signature".into()).into()); } let issuers = self.get_issuers(); for id in self.additional_issuers.take().expect("not empty") { if ! issuers.contains(&id) { match id { KeyHandle::KeyID(id) => self.unhashed_area_mut().add_internal( Subpacket::new(SubpacketValue::Issuer(id), false)?, true)?, KeyHandle::Fingerprint(fp) => self.unhashed_area_mut().add_internal( Subpacket::new(SubpacketValue::IssuerFingerprint(fp), false)?, true)?, } } } Ok(()) } /// Merges two signatures. /// /// Two signatures that are equal according to /// [`Signature::normalized_eq`] may differ in the contents of the /// unhashed subpacket areas. This function merges two signatures /// trying hard to incorporate all the information into one /// signature while avoiding denial-of-service attacks by merging /// in bad information. /// /// The merge strategy is as follows: /// /// - If the signatures differ according to /// [`Signature::normalized_eq`], the merge fails. /// /// - Do not consider any subpacket that does not belong into /// the unhashed subpacket area. /// /// - Consider all remaining subpackets, in the following order. /// If we run out of space, all remaining subpackets are /// ignored. /// /// - Authenticated subpackets from `self` /// - Authenticated subpackets from `other` /// - Unauthenticated subpackets from `self` commonly found in /// unhashed areas /// - Unauthenticated subpackets from `other` commonly found in /// unhashed areas /// - Remaining subpackets from `self` /// - Remaining subpackets from `other` /// /// See [`Subpacket::authenticated`] for how subpackets are /// authenticated. Subpackets commonly found in unhashed /// areas are issuer information and embedded signatures. pub fn merge(mut self, other: Signature) -> Result<Signature> { self.merge_internal(&other)?; Ok(self) } /// Same as Signature::merge, but non-consuming for use with /// Vec::dedup_by. pub(crate) fn merge_internal(&mut self, other: &Signature) -> Result<()> { use crate::serialize::MarshalInto; if ! self.normalized_eq(other) { return Err(Error::InvalidArgument( "Signatures are not equal modulo unhashed subpackets".into()) .into()); } // Filters subpackets that plausibly could be in the unhashed // area. fn eligible(p: &Subpacket) -> bool { use SubpacketTag::*; #[allow(deprecated)] match p.tag() { SignatureCreationTime | SignatureExpirationTime | ExportableCertification | TrustSignature | RegularExpression | Revocable | KeyExpirationTime | PlaceholderForBackwardCompatibility | PreferredSymmetricAlgorithms | RevocationKey | PreferredHashAlgorithms | PreferredCompressionAlgorithms | KeyServerPreferences | PreferredKeyServer | PrimaryUserID | PolicyURI | KeyFlags | SignersUserID | ReasonForRevocation | Features | SignatureTarget | PreferredAEADAlgorithms | IntendedRecipient | ApprovedCertifications | PreferredAEADCiphersuites | Reserved(_) => false, Issuer | NotationData | EmbeddedSignature | IssuerFingerprint | Private(_) | Unknown(_) => true, } } // Filters subpackets that usually are in the unhashed area. fn prefer(p: &Subpacket) -> bool { use SubpacketTag::*; matches!(p.tag(), Issuer | EmbeddedSignature | IssuerFingerprint) } // Collect subpackets keeping track of the size. #[allow(clippy::mutable_key_type)] // In general, the keys of a HashSet should not have interior mutability. // This particular use should be safe: The hash set is only constructed // for the merge, we own all objects we put into the set, and we don't // modify them while they are in the set. let mut acc = std::collections::HashSet::new(); let mut size = 0; // Start with missing issuer information. for id in self.additional_issuers.take().unwrap_or_default().into_iter() .chain(other.additional_issuers().iter().cloned()) { let p = match id { KeyHandle::KeyID(id) => Subpacket::new( SubpacketValue::Issuer(id), false)?, KeyHandle::Fingerprint(fp) => Subpacket::new( SubpacketValue::IssuerFingerprint(fp), false)?, }; let l = p.serialized_len(); if size + l <= std::u16::MAX as usize && acc.insert(p) { size += l; } } // Make multiple passes over the subpacket areas. Always // start with self, then other. Only consider eligible // packets. Consider authenticated ones first, then plausible // unauthenticated ones, then the rest. for p in self.unhashed_area().iter() .filter(|p| eligible(p) && p.authenticated()) .chain(other.unhashed_area().iter() .filter(|p| eligible(p) && p.authenticated())) .chain(self.unhashed_area().iter() .filter(|p| eligible(p) && ! p.authenticated() && prefer(p))) .chain(other.unhashed_area().iter() .filter(|p| eligible(p) && ! p.authenticated() && prefer(p))) .chain(self.unhashed_area().iter() .filter(|p| eligible(p) && ! p.authenticated() && ! prefer(p))) .chain(other.unhashed_area().iter() .filter(|p| eligible(p) && ! p.authenticated() && ! prefer(p))) { let l = p.serialized_len(); if size + l <= std::u16::MAX as usize && acc.insert(p.clone()) { size += l; } } assert!(size <= std::u16::MAX as usize); let mut a = SubpacketArea::new(acc.into_iter().collect()) .expect("must fit"); a.sort(); *self.unhashed_area_mut() = a; Ok(()) } } /// Verification-related functionality. /// /// <a id="verification-functions"></a> impl Signature { /// Verifies the signature using `key`. /// /// Verifies the signature using `key`, using the previously /// computed stored digest (see [`Signature4::computed_digest`]). /// If the computed digest has not been set prior to calling this /// function, it will fail. /// /// Because the context (i.e. what the signature covers) is hashed /// and stored in the computed digest, and not handed in as part /// of the signature verification, this interface must only be /// used if the context can be robustly inferred. /// /// For example, when verifying a third-party certification while /// iterating over user IDs in a certificate, this function can be /// used because the context is the current certificate and user /// ID, and this context has been hashed and the digest stored /// during certificate canonicalization. On the other hand, when /// verifying a dangling user ID revocation signature, the context /// has to be provided explicitly in a call to /// [`Signature::verify_userid_revocation`]. /// /// Note: Due to limited context, this only verifies the /// cryptographic signature, and checks that the key predates the /// signature. Further constraints on the signature, like /// signature type, creation and expiration time, or signature /// revocations must be checked by the caller. /// /// Likewise, this function does not check whether `key` can make /// valid signatures; it is up to the caller to make sure the key /// is not revoked, not expired, has a valid self-signature, has a /// subkey binding signature (if appropriate), has the signing /// capability, etc. pub fn verify_signature<P, R>(&self, key: &Key<P, R>) -> Result<()> where P: key::KeyParts, R: key::KeyRole, { self.verify_digest_internal( key.parts_as_public().role_as_unspecified(), None) } /// Verifies the signature against `hash`. /// /// The `hash` should only be computed over the payload, this /// function hashes in the signature itself before verifying it. /// /// Note: Due to limited context, this only verifies the /// cryptographic signature and checks that the key predates the /// signature. Further constraints on the signature, like /// creation and expiration time, or signature revocations must be /// checked by the caller. /// /// Likewise, this function does not check whether `key` can make /// valid signatures; it is up to the caller to make sure the key /// is not revoked, not expired, has a valid self-signature, has a /// subkey binding signature (if appropriate), has the signing /// capability, etc. pub fn verify_hash<P, R>(&self, key: &Key<P, R>, mut hash: hash::Context) -> Result<()> where P: key::KeyParts, R: key::KeyRole, { self.hash(&mut hash)?; self.verify_digest_internal( key.parts_as_public().role_as_unspecified(), Some(hash.into_digest()?.into())) } /// Verifies the signature against `digest`. /// /// Note: Due to limited context, this only verifies the /// cryptographic signature and checks that the key predates the /// signature. Further constraints on the signature, like /// creation and expiration time, or signature revocations must be /// checked by the caller. /// /// Likewise, this function does not check whether `key` can make /// valid signatures; it is up to the caller to make sure the key /// is not revoked, not expired, has a valid self-signature, has a /// subkey binding signature (if appropriate), has the signing /// capability, etc. pub fn verify_digest<P, R, D>(&self, key: &Key<P, R>, digest: D) -> Result<()> where P: key::KeyParts, R: key::KeyRole, D: AsRef<[u8]>, { self.verify_digest_internal( key.parts_as_public().role_as_unspecified(), Some(digest.as_ref().into())) } /// Verifies the signature against `computed_digest`, or /// `self.computed_digest` if the former is `None`. fn verify_digest_internal(&self, key: &Key<key::PublicParts, key::UnspecifiedRole>, computed_digest: Option<Cow<[u8]>>) -> Result<()> { // Only v6 keys may create v6 signatures. if (self.version() == 6) != (key.version() == 6) { return Err(Error::BadSignature( format!("Signature (v{}) and key (v{}) version mismatch", self.version(), key.version())).into()); } // Check salt size. if self.version() == 6 && Some(self.hash_algo().salt_size()?) != self.salt().map(|s| s.len()) { return Err(Error::BadSignature( format!("Salt of size {} bytes is wrong, expected {} bytes ", self.salt().map(|s| s.len()).unwrap_or(0), self.hash_algo().salt_size()?)).into()); } // DSA is phased out in RFC9580. #[allow(deprecated)] if self.version() == 6 && self.pk_algo() == PublicKeyAlgorithm::DSA { return Err(Error::BadSignature( "Version 6 signatures using DSA MUST be rejected".into()) .into()); } if let Some(creation_time) = self.signature_creation_time() { if creation_time < key.creation_time() { return Err(Error::BadSignature( format!("Signature (created {:?}) predates key ({:?})", creation_time, key.creation_time())).into()); } } else { return Err(Error::BadSignature( "Signature has no creation time subpacket".into()).into()); } // Either the digest has been given as argument, or it has // been stashed in the signature by the packet parser, or // error out. let digest = computed_digest.as_ref().map(AsRef::as_ref) .or(self.computed_digest()) .ok_or_else(|| Error::BadSignature("Hash not computed.".into()))?; let result = if let Ok(entry) = cache::Entry::new( self, digest, key.parts_as_public().role_as_unspecified()) { if entry.present() { // The signature is good. Ok(()) } else { // It's not in the cache. let result = key.verify( self.mpis(), self.hash_algo(), digest); // Insert the result in the cache. entry.insert(result.is_ok()); result } } else { key.verify(self.mpis(), self.hash_algo(), digest) }; if let Ok(expected_salt_len) = self.hash_algo().salt_size() { let salt_len = self.salt().map(|s| s.len()).unwrap_or(0); if self.version() == 6 && salt_len != expected_salt_len { return Err(Error::BadSignature(format!( "bad salt length, expected {} got {}", expected_salt_len, salt_len)).into()); } } if result.is_ok() { // Mark information in this signature as authenticated. // The hashed subpackets are authenticated by the // signature. self.hashed_area().iter().for_each(|p| { p.set_authenticated(true); }); // The self-authenticating unhashed subpackets are // authenticated by the key's identity. self.unhashed_area().iter().for_each(|p| { let authenticated = match p.value() { SubpacketValue::Issuer(id) => id == &key.keyid(), SubpacketValue::IssuerFingerprint(fp) => fp == &key.fingerprint(), _ => false, }; p.set_authenticated(authenticated); }); // Compute and record any issuer information not yet // contained in the signature. let issuers = self.get_issuers(); let mut additional_issuers = Vec::with_capacity(0); let id = KeyHandle::from(key.keyid()); if self.version() <= 4 && ! issuers.contains(&id) { additional_issuers.push(id); } if self.version() >= 4 { let fp = KeyHandle::from(key.fingerprint()); if ! issuers.contains(&fp) { additional_issuers.push(fp); } } // Replace it. If it was already set, we simply ignore // the error. let _ = self.additional_issuers.set(additional_issuers); // Finally, remember the digest. if let Some(digest) = computed_digest { self.set_computed_digest(Some(digest.into_owned())); } } result } /// Verifies the signature over text or binary documents using /// `key`. /// /// Note: Due to limited context, this only verifies the /// cryptographic signature, checks the signature's type, and /// checks that the key predates the signature. Further /// constraints on the signature, like creation and expiration /// time, or signature revocations must be checked by the caller. /// /// Likewise, this function does not check whether `key` can make /// valid signatures; it is up to the caller to make sure the key /// is not revoked, not expired, has a valid self-signature, has a /// subkey binding signature (if appropriate), has the signing /// capability, etc. pub fn verify_document<P, R>(&self, key: &Key<P, R>) -> Result<()> where P: key::KeyParts, R: key::KeyRole, { if !(self.typ() == SignatureType::Binary || self.typ() == SignatureType::Text) { return Err(Error::UnsupportedSignatureType(self.typ()).into()); } self.verify_digest_internal( key.parts_as_public().role_as_unspecified(), None) } /// Verifies the standalone signature using `key`. /// /// Note: Due to limited context, this only verifies the /// cryptographic signature, checks the signature's type, and /// checks that the key predates the signature. Further /// constraints on the signature, like creation and expiration /// time, or signature revocations must be checked by the caller. /// /// Likewise, this function does not check whether `key` can make /// valid signatures; it is up to the caller to make sure the key /// is not revoked, not expired, has a valid self-signature, has a /// subkey binding signature (if appropriate), has the signing /// capability, etc. pub fn verify_standalone<P, R>(&self, key: &Key<P, R>) -> Result<()> where P: key::KeyParts, R: key::KeyRole, { if self.typ() != SignatureType::Standalone { return Err(Error::UnsupportedSignatureType(self.typ()).into()); } // Standalone signatures are like binary-signatures over the // zero-sized string. let mut hash = self.hash_algo().context()?.for_signature(self.version()); self.hash_standalone(&mut hash)?; self.verify_digest_internal(key.parts_as_public().role_as_unspecified(), Some(hash.into_digest()?.into())) } /// Verifies the timestamp signature using `key`. /// /// Note: Due to limited context, this only verifies the /// cryptographic signature, checks the signature's type, and /// checks that the key predates the signature. Further /// constraints on the signature, like creation and expiration /// time, or signature revocations must be checked by the caller. /// /// Likewise, this function does not check whether `key` can make /// valid signatures; it is up to the caller to make sure the key /// is not revoked, not expired, has a valid self-signature, has a /// subkey binding signature (if appropriate), has the signing /// capability, etc. pub fn verify_timestamp<P, R>(&self, key: &Key<P, R>) -> Result<()> where P: key::KeyParts, R: key::KeyRole, { if self.typ() != SignatureType::Timestamp { return Err(Error::UnsupportedSignatureType(self.typ()).into()); } // Timestamp signatures are like binary-signatures over the // zero-sized string. let mut hash = self.hash_algo().context()?.for_signature(self.version()); self.hash_timestamp(&mut hash)?; self.verify_digest_internal( key.parts_as_public().role_as_unspecified(), Some(hash.into_digest()?.into())) } /// Verifies the direct key signature. /// /// `self` is the direct key signature, `signer` is the /// key that allegedly made the signature, and `pk` is the primary /// key. /// /// For a self-signature, `signer` and `pk` will be the same. /// /// Note: Due to limited context, this only verifies the /// cryptographic signature, checks the signature's type, and /// checks that the key predates the signature. Further /// constraints on the signature, like creation and expiration /// time, or signature revocations must be checked by the caller. /// /// Likewise, this function does not check whether `signer` can /// make valid signatures; it is up to the caller to make sure the /// key is not revoked, not expired, has a valid self-signature, /// has a subkey binding signature (if appropriate), has the /// signing capability, etc. pub fn verify_direct_key<P, Q, R>(&self, signer: &Key<P, R>, pk: &Key<Q, key::PrimaryRole>) -> Result<()> where P: key::KeyParts, Q: key::KeyParts, R: key::KeyRole, { if self.typ() != SignatureType::DirectKey { return Err(Error::UnsupportedSignatureType(self.typ()).into()); } let mut hash = self.hash_algo().context()?.for_signature(self.version()); self.hash_direct_key(&mut hash, pk)?; self.verify_digest_internal( signer.parts_as_public().role_as_unspecified(), Some(hash.into_digest()?.into())) } /// Verifies the primary key revocation certificate. /// /// `self` is the primary key revocation certificate, `signer` is /// the key that allegedly made the signature, and `pk` is the /// primary key, /// /// For a self-signature, `signer` and `pk` will be the same. /// /// Note: Due to limited context, this only verifies the /// cryptographic signature, checks the signature's type, and /// checks that the key predates the signature. Further /// constraints on the signature, like creation and expiration /// time, or signature revocations must be checked by the caller. /// /// Likewise, this function does not check whether `signer` can /// make valid signatures; it is up to the caller to make sure the /// key is not revoked, not expired, has a valid self-signature, /// has a subkey binding signature (if appropriate), has the /// signing capability, etc. pub fn verify_primary_key_revocation<P, Q, R>(&self, signer: &Key<P, R>, pk: &Key<Q, key::PrimaryRole>) -> Result<()> where P: key::KeyParts, Q: key::KeyParts, R: key::KeyRole, { if self.typ() != SignatureType::KeyRevocation { return Err(Error::UnsupportedSignatureType(self.typ()).into()); } let mut hash = self.hash_algo().context()?.for_signature(self.version()); self.hash_direct_key(&mut hash, pk)?; self.verify_digest_internal( signer.parts_as_public().role_as_unspecified(), Some(hash.into_digest()?.into())) } /// Verifies the subkey binding. /// /// `self` is the subkey key binding signature, `signer` is the /// key that allegedly made the signature, `pk` is the primary /// key, and `subkey` is the subkey. /// /// For a self-signature, `signer` and `pk` will be the same. /// /// If the signature indicates that this is a `Signing` capable /// subkey, then the back signature is also verified. If it is /// missing or can't be verified, then this function returns /// false. /// /// Note: Due to limited context, this only verifies the /// cryptographic signature, checks the signature's type, and /// checks that the key predates the signature. Further /// constraints on the signature, like creation and expiration /// time, or signature revocations must be checked by the caller. /// /// Likewise, this function does not check whether `signer` can /// make valid signatures; it is up to the caller to make sure the /// key is not revoked, not expired, has a valid self-signature, /// has a subkey binding signature (if appropriate), has the /// signing capability, etc. pub fn verify_subkey_binding<P, Q, R, S>( &self, signer: &Key<P, R>, pk: &Key<Q, key::PrimaryRole>, subkey: &Key<S, key::SubordinateRole>) -> Result<()> where P: key::KeyParts, Q: key::KeyParts, R: key::KeyRole, S: key::KeyParts, { if self.typ() != SignatureType::SubkeyBinding { return Err(Error::UnsupportedSignatureType(self.typ()).into()); } let mut hash = self.hash_algo().context()?.for_signature(self.version()); self.hash_subkey_binding(&mut hash, pk, subkey)?; self.verify_digest_internal( signer.parts_as_public().role_as_unspecified(), Some(hash.into_digest()?.into()))?; // The signature is good, but we may still need to verify the // back sig. if self.key_flags().map(|kf| kf.for_signing()).unwrap_or(false) { let mut last_result = Err(Error::BadSignature( "Primary key binding signature missing".into()).into()); for backsig in self.subpackets(SubpacketTag::EmbeddedSignature) { let result = if let SubpacketValue::EmbeddedSignature(sig) = backsig.value() { sig.verify_primary_key_binding(pk, subkey) } else { unreachable!("subpackets(EmbeddedSignature) returns \ EmbeddedSignatures"); }; if result.is_ok() { // Mark the subpacket as authenticated by the // embedded signature. backsig.set_authenticated(true); return result; } last_result = result; } last_result } else { // No backsig required. Ok(()) } } /// Verifies the primary key binding. /// /// `self` is the primary key binding signature, `pk` is the /// primary key, and `subkey` is the subkey. /// /// Note: Due to limited context, this only verifies the /// cryptographic signature, checks the signature's type, and /// checks that the key predates the signature. Further /// constraints on the signature, like creation and expiration /// time, or signature revocations must be checked by the caller. /// /// Likewise, this function does not check whether `subkey` can /// make valid signatures; it is up to the caller to make sure the /// key is not revoked, not expired, has a valid self-signature, /// has a subkey binding signature (if appropriate), has the /// signing capability, etc. pub fn verify_primary_key_binding<P, Q>( &self, pk: &Key<P, key::PrimaryRole>, subkey: &Key<Q, key::SubordinateRole>) -> Result<()> where P: key::KeyParts, Q: key::KeyParts, { if self.typ() != SignatureType::PrimaryKeyBinding { return Err(Error::UnsupportedSignatureType(self.typ()).into()); } let mut hash = self.hash_algo().context()?.for_signature(self.version()); self.hash_primary_key_binding(&mut hash, pk, subkey)?; self.verify_digest_internal( subkey.parts_as_public().role_as_unspecified(), Some(hash.into_digest()?.into())) } /// Verifies the subkey revocation. /// /// `self` is the subkey key revocation certificate, `signer` is /// the key that allegedly made the signature, `pk` is the primary /// key, and `subkey` is the subkey. /// /// For a self-revocation, `signer` and `pk` will be the same. /// /// Note: Due to limited context, this only verifies the /// cryptographic signature, checks the signature's type, and /// checks that the key predates the signature. Further /// constraints on the signature, like creation and expiration /// time, or signature revocations must be checked by the caller. /// /// Likewise, this function does not check whether `signer` can /// make valid signatures; it is up to the caller to make sure the /// key is not revoked, not expired, has a valid self-signature, /// has a subkey binding signature (if appropriate), has the /// signing capability, etc. pub fn verify_subkey_revocation<P, Q, R, S>( &self, signer: &Key<P, R>, pk: &Key<Q, key::PrimaryRole>, subkey: &Key<S, key::SubordinateRole>) -> Result<()> where P: key::KeyParts, Q: key::KeyParts, R: key::KeyRole, S: key::KeyParts, { if self.typ() != SignatureType::SubkeyRevocation { return Err(Error::UnsupportedSignatureType(self.typ()).into()); } let mut hash = self.hash_algo().context()?.for_signature(self.version()); self.hash_subkey_binding(&mut hash, pk, subkey)?; self.verify_digest_internal( signer.parts_as_public().role_as_unspecified(), Some(hash.into_digest()?.into())) } /// Verifies the user id binding. /// /// `self` is the user id binding signature, `signer` is the key /// that allegedly made the signature, `pk` is the primary key, /// and `userid` is the user id. /// /// For a self-signature, `signer` and `pk` will be the same. /// /// Note: Due to limited context, this only verifies the /// cryptographic signature, checks the signature's type, and /// checks that the key predates the signature. Further /// constraints on the signature, like creation and expiration /// time, or signature revocations must be checked by the caller. /// /// Likewise, this function does not check whether `signer` can /// make valid signatures; it is up to the caller to make sure the /// key is not revoked, not expired, has a valid self-signature, /// has a subkey binding signature (if appropriate), has the /// signing capability, etc. pub fn verify_userid_binding<P, Q, R>(&self, signer: &Key<P, R>, pk: &Key<Q, key::PrimaryRole>, userid: &UserID) -> Result<()> where P: key::KeyParts, Q: key::KeyParts, R: key::KeyRole, { if !(self.typ() == SignatureType::GenericCertification || self.typ() == SignatureType::PersonaCertification || self.typ() == SignatureType::CasualCertification || self.typ() == SignatureType::PositiveCertification) { return Err(Error::UnsupportedSignatureType(self.typ()).into()); } let mut hash = self.hash_algo().context()?.for_signature(self.version()); self.hash_userid_binding(&mut hash, pk, userid)?; self.verify_digest_internal( signer.parts_as_public().role_as_unspecified(), Some(hash.into_digest()?.into())) } /// Verifies the user id revocation certificate. /// /// `self` is the revocation certificate, `signer` is the key /// that allegedly made the signature, `pk` is the primary key, /// and `userid` is the user id. /// /// For a self-signature, `signer` and `pk` will be the same. /// /// Note: Due to limited context, this only verifies the /// cryptographic signature, checks the signature's type, and /// checks that the key predates the signature. Further /// constraints on the signature, like creation and expiration /// time, or signature revocations must be checked by the caller. /// /// Likewise, this function does not check whether `signer` can /// make valid signatures; it is up to the caller to make sure the /// key is not revoked, not expired, has a valid self-signature, /// has a subkey binding signature (if appropriate), has the /// signing capability, etc. pub fn verify_userid_revocation<P, Q, R>(&self, signer: &Key<P, R>, pk: &Key<Q, key::PrimaryRole>, userid: &UserID) -> Result<()> where P: key::KeyParts, Q: key::KeyParts, R: key::KeyRole, { if self.typ() != SignatureType::CertificationRevocation { return Err(Error::UnsupportedSignatureType(self.typ()).into()); } let mut hash = self.hash_algo().context()?.for_signature(self.version()); self.hash_userid_binding(&mut hash, pk, userid)?; self.verify_digest_internal( signer.parts_as_public().role_as_unspecified(), Some(hash.into_digest()?.into())) } /// Verifies an certification approval key signature on a user ID. /// /// This feature is [experimental](crate#experimental-features). /// /// Allows the certificate owner to approve of third party /// certifications. See [Certification Approval Key Signature] for /// details. /// /// `self` is the certification approval key signature, `signer` /// is the key that allegedly made the signature, `pk` is the /// primary key, and `userid` is the user ID. /// /// Note: Due to limited context, this only verifies the /// cryptographic signature, checks the signature's type, and /// checks that the key predates the signature. Further /// constraints on the signature, like creation and expiration /// time, or signature revocations must be checked by the caller. /// /// Likewise, this function does not check whether `signer` can /// make valid signatures; it is up to the caller to make sure the /// key is not revoked, not expired, has a valid self-signature, /// has a subkey binding signature (if appropriate), has the /// signing capability, etc. /// /// [Certification Approval Key Signature]: https://www.ietf.org/archive/id/draft-dkg-openpgp-1pa3pc-02.html#name-certification-approval-key- pub fn verify_userid_approval<P, Q, R>( &self, signer: &Key<P, R>, pk: &Key<Q, key::PrimaryRole>, userid: &UserID) -> Result<()> where P: key::KeyParts, Q: key::KeyParts, R: key::KeyRole, { let mut hash = self.hash_algo().context()?.for_signature(self.version()); if self.approved_certifications()? .any(|d| d.len() != hash.digest_size()) { return Err(Error::BadSignature( "Wrong number of bytes in certification subpacket".into()) .into()); } self.hash_userid_approval(&mut hash, pk, userid)?; self.verify_digest_internal( signer.parts_as_public().role_as_unspecified(), Some(hash.into_digest()?.into())) } /// Verifies the user attribute binding. /// /// `self` is the user attribute binding signature, `signer` is /// the key that allegedly made the signature, `pk` is the primary /// key, and `ua` is the user attribute. /// /// For a self-signature, `signer` and `pk` will be the same. /// /// Note: Due to limited context, this only verifies the /// cryptographic signature, checks the signature's type, and /// checks that the key predates the signature. Further /// constraints on the signature, like creation and expiration /// time, or signature revocations must be checked by the caller. /// /// Likewise, this function does not check whether `signer` can /// make valid signatures; it is up to the caller to make sure the /// key is not revoked, not expired, has a valid self-signature, /// has a subkey binding signature (if appropriate), has the /// signing capability, etc. pub fn verify_user_attribute_binding<P, Q, R>(&self, signer: &Key<P, R>, pk: &Key<Q, key::PrimaryRole>, ua: &UserAttribute) -> Result<()> where P: key::KeyParts, Q: key::KeyParts, R: key::KeyRole, { if !(self.typ() == SignatureType::GenericCertification || self.typ() == SignatureType::PersonaCertification || self.typ() == SignatureType::CasualCertification || self.typ() == SignatureType::PositiveCertification) { return Err(Error::UnsupportedSignatureType(self.typ()).into()); } let mut hash = self.hash_algo().context()?.for_signature(self.version()); self.hash_user_attribute_binding(&mut hash, pk, ua)?; self.verify_digest_internal( signer.parts_as_public().role_as_unspecified(), Some(hash.into_digest()?.into())) } /// Verifies the user attribute revocation certificate. /// /// `self` is the user attribute binding signature, `signer` is /// the key that allegedly made the signature, `pk` is the primary /// key, and `ua` is the user attribute. /// /// For a self-signature, `signer` and `pk` will be the same. /// /// Note: Due to limited context, this only verifies the /// cryptographic signature, checks the signature's type, and /// checks that the key predates the signature. Further /// constraints on the signature, like creation and expiration /// time, or signature revocations must be checked by the caller. /// /// Likewise, this function does not check whether `signer` can /// make valid signatures; it is up to the caller to make sure the /// key is not revoked, not expired, has a valid self-signature, /// has a subkey binding signature (if appropriate), has the /// signing capability, etc. pub fn verify_user_attribute_revocation<P, Q, R>( &self, signer: &Key<P, R>, pk: &Key<Q, key::PrimaryRole>, ua: &UserAttribute) -> Result<()> where P: key::KeyParts, Q: key::KeyParts, R: key::KeyRole, { if self.typ() != SignatureType::CertificationRevocation { return Err(Error::UnsupportedSignatureType(self.typ()).into()); } let mut hash = self.hash_algo().context()?.for_signature(self.version()); self.hash_user_attribute_binding(&mut hash, pk, ua)?; self.verify_digest_internal( signer.parts_as_public().role_as_unspecified(), Some(hash.into_digest()?.into())) } /// Verifies an certification approval key signature on a user /// attribute. /// /// This feature is [experimental](crate#experimental-features). /// /// Allows the certificate owner to approve of third party /// certifications. See [Certification Approval Key Signature] for /// details. /// /// `self` is the certification approval key signature, `signer` /// is the key that allegedly made the signature, `pk` is the /// primary key, and `ua` is the user attribute. /// /// Note: Due to limited context, this only verifies the /// cryptographic signature, checks the signature's type, and /// checks that the key predates the signature. Further /// constraints on the signature, like creation and expiration /// time, or signature revocations must be checked by the caller. /// /// Likewise, this function does not check whether `signer` can /// make valid signatures; it is up to the caller to make sure the /// key is not revoked, not expired, has a valid self-signature, /// has a subkey binding signature (if appropriate), has the /// signing capability, etc. /// /// [Certification Approval Key Signature]: https://www.ietf.org/archive/id/draft-dkg-openpgp-1pa3pc-02.html#name-certification-approval-key- pub fn verify_user_attribute_approval<P, Q, R>( &self, signer: &Key<P, R>, pk: &Key<Q, key::PrimaryRole>, ua: &UserAttribute) -> Result<()> where P: key::KeyParts, Q: key::KeyParts, R: key::KeyRole, { let mut hash = self.hash_algo().context()?.for_signature(self.version()); if self.approved_certifications()? .any(|d| d.len() != hash.digest_size()) { return Err(Error::BadSignature( "Wrong number of bytes in certification subpacket".into()) .into()); } self.hash_user_attribute_approval(&mut hash, pk, ua)?; self.verify_digest_internal( signer.parts_as_public().role_as_unspecified(), Some(hash.into_digest()?.into())) } /// Verifies a signature of a message. /// /// `self` is the message signature, `signer` is /// the key that allegedly made the signature and `msg` is the message. /// /// This function is for short messages, if you want to verify larger files /// use `Verifier`. /// /// Note: Due to limited context, this only verifies the /// cryptographic signature, checks the signature's type, and /// checks that the key predates the signature. Further /// constraints on the signature, like creation and expiration /// time, or signature revocations must be checked by the caller. /// /// Likewise, this function does not check whether `signer` can /// make valid signatures; it is up to the caller to make sure the /// key is not revoked, not expired, has a valid self-signature, /// has a subkey binding signature (if appropriate), has the /// signing capability, etc. pub fn verify_message<M, P, R>(&self, signer: &Key<P, R>, msg: M) -> Result<()> where M: AsRef<[u8]>, P: key::KeyParts, R: key::KeyRole, { if self.typ() != SignatureType::Binary && self.typ() != SignatureType::Text { return Err(Error::UnsupportedSignatureType(self.typ()).into()); } // Compute the digest. let mut hash = self.hash_algo().context()?.for_signature(self.version()); if let Some(salt) = self.salt() { hash.update(salt); } hash.update(msg.as_ref()); self.hash(&mut hash)?; self.verify_digest_internal( signer.parts_as_public().role_as_unspecified(), Some(hash.into_digest()?.into())) } } impl From<Signature3> for Packet { fn from(s: Signature3) -> Self { Packet::Signature(s.into()) } } impl From<Signature3> for super::Signature { fn from(s: Signature3) -> Self { super::Signature::V3(s) } } impl From<Signature4> for Packet { fn from(s: Signature4) -> Self { Packet::Signature(s.into()) } } impl From<Signature4> for super::Signature { fn from(s: Signature4) -> Self { super::Signature::V4(s) } } #[cfg(test)] impl ArbitraryBounded for super::Signature { fn arbitrary_bounded(g: &mut Gen, depth: usize) -> Self { match u8::arbitrary(g) % 3 { 0 => Signature3::arbitrary_bounded(g, depth).into(), 1 => Signature4::arbitrary_bounded(g, depth).into(), 2 => Signature6::arbitrary_bounded(g, depth).into(), _ => unreachable!(), } } } #[cfg(test)] impl_arbitrary_with_bound!(super::Signature); #[cfg(test)] impl ArbitraryBounded for Signature4 { fn arbitrary_bounded(g: &mut Gen, depth: usize) -> Self { use mpi::MPI; use PublicKeyAlgorithm::*; let fields = SignatureFields::arbitrary_bounded(g, depth); #[allow(deprecated)] let mpis = match fields.pk_algo() { RSAEncryptSign | RSASign => mpi::Signature::RSA { s: MPI::arbitrary(g), }, DSA => mpi::Signature::DSA { r: MPI::arbitrary(g), s: MPI::arbitrary(g), }, EdDSA => mpi::Signature::EdDSA { r: MPI::arbitrary(g), s: MPI::arbitrary(g), }, ECDSA => mpi::Signature::ECDSA { r: MPI::arbitrary(g), s: MPI::arbitrary(g), }, Ed25519 => mpi::Signature::Ed25519 { s: { let mut s = [0; 64]; s.iter_mut().for_each(|p| *p = u8::arbitrary(g)); Box::new(s) }, }, Ed448 => mpi::Signature::Ed448 { s: { let mut s = [0; 114]; s.iter_mut().for_each(|p| *p = u8::arbitrary(g)); Box::new(s) }, }, ElGamalEncryptSign | RSAEncrypt | ElGamalEncrypt | ECDH | X25519 | X448 | Private(_) | Unknown(_) => unreachable!(), }; Signature4 { common: Arbitrary::arbitrary(g), fields, digest_prefix: [Arbitrary::arbitrary(g), Arbitrary::arbitrary(g)], mpis, computed_digest: OnceLock::new(), level: 0, additional_issuers: OnceLock::new(), } } } #[cfg(test)] impl_arbitrary_with_bound!(Signature4); #[cfg(test)] impl ArbitraryBounded for Signature3 { fn arbitrary_bounded(g: &mut Gen, _depth: usize) -> Self { use mpi::{arbitrarize, MPI}; use PublicKeyAlgorithm::*; let pk_algo = PublicKeyAlgorithm::arbitrary_for_signing(g); #[allow(deprecated)] let mpis = match pk_algo { RSAEncryptSign | RSASign => mpi::Signature::RSA { s: MPI::arbitrary(g), }, DSA => mpi::Signature::DSA { r: MPI::arbitrary(g), s: MPI::arbitrary(g), }, EdDSA => mpi::Signature::EdDSA { r: MPI::arbitrary(g), s: MPI::arbitrary(g), }, ECDSA => mpi::Signature::ECDSA { r: MPI::arbitrary(g), s: MPI::arbitrary(g), }, Ed25519 => mpi::Signature::Ed25519 { s: Box::new(arbitrarize(g, [0; 64])), }, Ed448 => mpi::Signature::Ed448 { s: Box::new(arbitrarize(g, [0; 114])), }, _ => unreachable!(), }; Signature3::new( SignatureType::arbitrary(g), Timestamp::arbitrary(g), KeyID::arbitrary(g), pk_algo, HashAlgorithm::arbitrary(g), [Arbitrary::arbitrary(g), Arbitrary::arbitrary(g)], mpis) } } #[cfg(test)] impl_arbitrary_with_bound!(Signature3); #[cfg(test)] mod test { use super::*; use crate::KeyID; use crate::cert::prelude::*; use crate::crypto; use crate::parse::Parse; use crate::packet::Key; use crate::packet::key::{Key4, Key6}; use crate::types::Curve; use crate::policy::StandardPolicy as P; #[cfg(feature = "compression-deflate")] #[test] fn signature_verification_test() { use super::*; use crate::Cert; use crate::parse::{ PacketParserBuilder, PacketParserResult, PacketParser, }; struct Test<'a> { key: &'a str, data: &'a str, good: usize, } let tests = [ Test { key: "neal.pgp", data: "signed-1.gpg", good: 1, }, Test { key: "neal.pgp", data: "signed-1-sha1-neal.gpg", good: 1, }, Test { key: "testy.pgp", data: "signed-1-sha256-testy.gpg", good: 1, }, Test { key: "dennis-simon-anton.pgp", data: "signed-1-dsa.pgp", good: 1, }, Test { key: "erika-corinna-daniela-simone-antonia-nistp256.pgp", data: "signed-1-ecdsa-nistp256.pgp", good: 1, }, Test { key: "erika-corinna-daniela-simone-antonia-nistp384.pgp", data: "signed-1-ecdsa-nistp384.pgp", good: 1, }, Test { key: "erika-corinna-daniela-simone-antonia-nistp521.pgp", data: "signed-1-ecdsa-nistp521.pgp", good: 1, }, Test { key: "emmelie-dorothea-dina-samantha-awina-ed25519.pgp", data: "signed-1-eddsa-ed25519.pgp", good: 1, }, Test { key: "emmelie-dorothea-dina-samantha-awina-ed25519.pgp", data: "signed-twice-by-ed25519.pgp", good: 2, }, Test { key: "neal.pgp", data: "signed-1-notarized-by-ed25519.pgp", good: 1, }, Test { key: "emmelie-dorothea-dina-samantha-awina-ed25519.pgp", data: "signed-1-notarized-by-ed25519.pgp", good: 1, }, // Check with the wrong key. Test { key: "neal.pgp", data: "signed-1-sha256-testy.gpg", good: 0, }, Test { key: "neal.pgp", data: "signed-2-partial-body.gpg", good: 1, }, ]; for test in tests.iter() { eprintln!("{}, expect {} good signatures:", test.data, test.good); let cert = Cert::from_bytes(crate::tests::key(test.key)).unwrap(); if ! cert.keys().all(|ka| ka.key().pk_algo().is_supported()) { eprintln!("Skipping because one algorithm is not supported"); continue; } if let Some(curve) = match cert.primary_key().key().mpis() { mpi::PublicKey::EdDSA { curve, .. } => Some(curve), mpi::PublicKey::ECDSA { curve, .. } => Some(curve), _ => None, } { if ! curve.is_supported() { eprintln!("Skipping because we don't support {}", curve); continue; } } let mut good = 0; let mut ppr = PacketParser::from_bytes( crate::tests::message(test.data)).unwrap(); while let PacketParserResult::Some(mut pp) = ppr { if let Packet::Signature(sig) = &mut pp.packet { let result = sig.verify_document(cert.primary_key().key()).is_ok(); eprintln!(" Primary {:?}: {:?}", cert.fingerprint(), result); if result { good += 1; } for sk in cert.subkeys() { let result = sig.verify_document(sk.key()).is_ok(); eprintln!(" Subkey {:?}: {:?}", sk.key().fingerprint(), result); if result { good += 1; } } } // Get the next packet. ppr = pp.recurse().unwrap().1; } assert_eq!(good, test.good, "Signature verification failed."); // Again, this time with explicit hashing. let mut good = 0; let mut ppr = PacketParserBuilder::from_bytes( crate::tests::message(test.data)).unwrap() .automatic_hashing(false) .build().unwrap(); while let PacketParserResult::Some(mut pp) = ppr { if let Packet::OnePassSig(_) = &pp.packet { pp.start_hashing().unwrap(); } if let Packet::Signature(sig) = &mut pp.packet { let result = sig.verify_document(cert.primary_key().key()).is_ok(); eprintln!(" Primary {:?}: {:?}", cert.fingerprint(), result); if result { good += 1; } for sk in cert.subkeys() { let result = sig.verify_document(sk.key()).is_ok(); eprintln!(" Subkey {:?}: {:?}", sk.key().fingerprint(), result); if result { good += 1; } } } // Get the next packet. ppr = pp.recurse().unwrap().1; } assert_eq!(good, test.good, "Signature verification with \ explicit hashing failed."); } } #[test] fn signature_level() { use crate::PacketPile; let p = PacketPile::from_bytes( crate::tests::message("signed-1-notarized-by-ed25519.pgp")).unwrap() .into_children().collect::<Vec<Packet>>(); if let Packet::Signature(ref sig) = &p[3] { assert_eq!(sig.level(), 0); } else { panic!("expected signature") } if let Packet::Signature(ref sig) = &p[4] { assert_eq!(sig.level(), 1); } else { panic!("expected signature") } } #[test] fn sign_verify() { let hash_algo = HashAlgorithm::SHA512; let mut hash = vec![0; hash_algo.digest_size().unwrap()]; crypto::random(&mut hash).unwrap(); for key in &[ "testy-private.pgp", "dennis-simon-anton-private.pgp", "erika-corinna-daniela-simone-antonia-nistp256-private.pgp", "erika-corinna-daniela-simone-antonia-nistp384-private.pgp", "erika-corinna-daniela-simone-antonia-nistp521-private.pgp", "emmelie-dorothea-dina-samantha-awina-ed25519-private.pgp", ] { eprintln!("{}...", key); let cert = Cert::from_bytes(crate::tests::key(key)).unwrap(); if ! cert.primary_key().key().pk_algo().is_supported() { eprintln!("Skipping because we don't support the algo"); continue; } if let Some(curve) = match cert.primary_key().key().mpis() { mpi::PublicKey::EdDSA { curve, .. } => Some(curve), mpi::PublicKey::ECDSA { curve, .. } => Some(curve), _ => None, } { if ! curve.is_supported() { eprintln!("Skipping because we don't support {}", curve); continue; } } let mut pair = cert.primary_key().key().clone() .parts_into_secret().unwrap() .into_keypair() .expect("secret key is encrypted/missing"); let sig = SignatureBuilder::new(SignatureType::Binary); let hash = hash_algo.context().unwrap() .for_signature(pair.public().version()); // Make signature. let sig = sig.sign_hash(&mut pair, hash).unwrap(); // Good signature. let mut hash = hash_algo.context().unwrap() .for_signature(sig.version()); sig.hash(&mut hash).unwrap(); let mut digest = vec![0u8; hash.digest_size()]; hash.digest(&mut digest).unwrap(); sig.verify_digest(pair.public(), &digest[..]).unwrap(); // Bad signature. digest[0] ^= 0xff; sig.verify_digest(pair.public(), &digest[..]).unwrap_err(); } } #[test] fn sign_message() { use crate::types::Curve::*; for curve in vec![ Ed25519, NistP256, NistP384, NistP521, ] { if ! curve.is_supported() { eprintln!("Skipping unsupported {:?}", curve); continue; } let key: Key<key::SecretParts, key::PrimaryRole> = Key6::generate_ecc(true, curve).unwrap().into(); let msg = b"Hello, World"; let mut pair = key.into_keypair().unwrap(); let sig = SignatureBuilder::new(SignatureType::Binary) .sign_message(&mut pair, msg).unwrap(); sig.verify_message(pair.public(), msg).unwrap(); } } #[test] fn verify_message() { let cert = Cert::from_bytes(crate::tests::key( "emmelie-dorothea-dina-samantha-awina-ed25519.pgp")).unwrap(); let msg = crate::tests::manifesto(); let p = Packet::from_bytes( crate::tests::message("a-cypherpunks-manifesto.txt.ed25519.sig")) .unwrap(); let sig = if let Packet::Signature(s) = p { s } else { panic!("Expected a Signature, got: {:?}", p); }; sig.verify_message(cert.primary_key().key(), msg).unwrap(); } #[test] #[allow(deprecated)] fn verify_v3_sig() { if ! PublicKeyAlgorithm::DSA.is_supported() { return; } let cert = Cert::from_bytes(crate::tests::key( "dennis-simon-anton-private.pgp")).unwrap(); let msg = crate::tests::manifesto(); let p = Packet::from_bytes( crate::tests::message("a-cypherpunks-manifesto.txt.dennis-simon-anton-v3.sig")) .unwrap(); let sig = if let Packet::Signature(s) = p { assert_eq!(s.version(), 3); s } else { panic!("Expected a Signature, got: {:?}", p); }; sig.verify_message(cert.primary_key().key(), msg).unwrap(); } #[test] fn sign_with_short_ed25519_secret_key() { // 20 byte sec key let secret_key = [ 0x0,0x0, 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, 0x1,0x2,0x2,0x2,0x2,0x2,0x2,0x2,0x2,0x2, 0x1,0x2,0x2,0x2,0x2,0x2,0x2,0x2,0x2,0x2 ]; let key: key::SecretKey = Key4::import_secret_ed25519(&secret_key, None) .unwrap().into(); let mut pair = key.into_keypair().unwrap(); let msg = b"Hello, World"; let mut hash = HashAlgorithm::SHA256.context().unwrap() .for_signature(pair.public().version()); hash.update(&msg[..]); SignatureBuilder::new(SignatureType::Text) .sign_hash(&mut pair, hash).unwrap(); } #[test] fn verify_gpg_3rd_party_cert() { use crate::Cert; let p = &P::new(); let test1 = Cert::from_bytes( crate::tests::key("test1-certification-key.pgp")).unwrap(); let cert_key1 = test1.keys().with_policy(p, None) .for_certification() .next() .map(|ka| ka.key()) .unwrap(); let test2 = Cert::from_bytes( crate::tests::key("test2-signed-by-test1.pgp")).unwrap(); let uid = test2.userids().with_policy(p, None).next().unwrap(); let cert = uid.certifications().next().unwrap().clone(); cert.verify_userid_binding(cert_key1, test2.primary_key().key(), uid.userid()).unwrap(); } #[test] fn normalize() { use crate::Fingerprint; use crate::packet::signature::subpacket::*; let key : key::SecretKey = Key6::generate_ecc(true, Curve::Ed25519).unwrap().into(); let mut pair = key.into_keypair().unwrap(); let msg = b"Hello, World"; let mut hash = HashAlgorithm::SHA256.context().unwrap() .for_signature(pair.public().version()); hash.update(&msg[..]); let fp = Fingerprint::from_bytes(4, b"bbbbbbbbbbbbbbbbbbbb").unwrap(); let keyid = KeyID::from(&fp); // First, make sure any superfluous subpackets are removed, // yet the Issuer, IssuerFingerprint and EmbeddedSignature // ones are kept. let mut builder = SignatureBuilder::new(SignatureType::Text); builder.unhashed_area_mut().add(Subpacket::new( SubpacketValue::IssuerFingerprint(fp.clone()), false).unwrap()) .unwrap(); builder.unhashed_area_mut().add(Subpacket::new( SubpacketValue::Issuer(keyid.clone()), false).unwrap()) .unwrap(); // This subpacket does not belong there, and should be // removed. builder.unhashed_area_mut().add(Subpacket::new( SubpacketValue::PreferredSymmetricAlgorithms(Vec::new()), false).unwrap()).unwrap(); // Build and add an embedded sig. let embedded_sig = SignatureBuilder::new(SignatureType::PrimaryKeyBinding) .sign_hash(&mut pair, hash.clone()).unwrap(); builder.unhashed_area_mut().add(Subpacket::new( SubpacketValue::EmbeddedSignature(embedded_sig), false).unwrap()) .unwrap(); let sig = builder.sign_hash(&mut pair, hash.clone()).unwrap().normalize(); assert_eq!(sig.unhashed_area().iter().count(), 3); assert_eq!(*sig.unhashed_area().iter().next().unwrap(), Subpacket::new(SubpacketValue::Issuer(keyid.clone()), false).unwrap()); assert_eq!(sig.unhashed_area().iter().nth(1).unwrap().tag(), SubpacketTag::EmbeddedSignature); assert_eq!(*sig.unhashed_area().iter().nth(2).unwrap(), Subpacket::new(SubpacketValue::IssuerFingerprint(fp.clone()), false).unwrap()); } #[test] fn standalone_signature_roundtrip() { let key : key::SecretKey = Key6::generate_ecc(true, Curve::Ed25519).unwrap().into(); let mut pair = key.into_keypair().unwrap(); let sig = SignatureBuilder::new(SignatureType::Standalone) .sign_standalone(&mut pair) .unwrap(); sig.verify_standalone(pair.public()).unwrap(); } #[test] #[allow(deprecated)] fn timestamp_signature() { if ! PublicKeyAlgorithm::DSA.is_supported() { eprintln!("Skipping test, algorithm is not supported."); return; } let alpha = Cert::from_bytes(crate::tests::file( "contrib/gnupg/keys/alpha.pgp")).unwrap(); let p = Packet::from_bytes(crate::tests::file( "contrib/gnupg/timestamp-signature-by-alice.asc")).unwrap(); if let Packet::Signature(sig) = p { let mut hash = sig.hash_algo().context().unwrap() .for_signature(sig.version()); sig.hash_timestamp(&mut hash).unwrap(); let digest = hash.into_digest().unwrap(); eprintln!("{}", crate::fmt::hex::encode(&digest)); sig.verify_timestamp(alpha.primary_key().key()).unwrap(); } else { panic!("expected a signature packet"); } } #[test] fn timestamp_signature_roundtrip() { let key : key::SecretKey = Key6::generate_ecc(true, Curve::Ed25519).unwrap().into(); let mut pair = key.into_keypair().unwrap(); let sig = SignatureBuilder::new(SignatureType::Timestamp) .sign_timestamp(&mut pair) .unwrap(); sig.verify_timestamp(pair.public()).unwrap(); } #[test] fn get_issuers_prefers_fingerprints() -> Result<()> { use crate::KeyHandle; for f in [ // This has Fingerprint in the hashed, Issuer in the // unhashed area. "messages/sig.gpg", // This has [Issuer, Fingerprint] in the hashed area. "contrib/gnupg/timestamp-signature-by-alice.asc", ].iter() { let p = Packet::from_bytes(crate::tests::file(f))?; if let Packet::Signature(sig) = p { let issuers = sig.get_issuers(); assert_match!(KeyHandle::Fingerprint(_) = &issuers[0]); assert_match!(KeyHandle::KeyID(_) = &issuers[1]); } else { panic!("expected a signature packet"); } } Ok(()) } /// Checks that binding signatures of newly created certificates /// can be conveniently and robustly be overwritten without /// fiddling with creation timestamps. #[test] fn binding_signatures_are_overrideable() -> Result<()> { use crate::packet::signature::subpacket::NotationDataFlags; let notation_key = "override-test@sequoia-pgp.org"; let p = &P::new(); // Create a certificate and try to update the userid's binding // signature. let (mut alice, _) = CertBuilder::general_purpose(Some("alice@example.org")) .generate()?; let mut primary_signer = alice.primary_key().key().clone() .parts_into_secret()?.into_keypair()?; assert_eq!(alice.userids().len(), 1); assert_eq!(alice.userids().next().unwrap().self_signatures().count(), 1); const TRIES: u64 = 5; assert!(TRIES * 10 < SIG_BACKDATE_BY); for i in 0..TRIES { assert_eq!(alice.userids().next().unwrap().self_signatures().count(), 1 + i as usize); // Get the binding signature so that we can modify it. let sig = alice.with_policy(p, None)?.userids().next().unwrap() .binding_signature().clone(); let new_sig = match SignatureBuilder::from(sig) .set_notation(notation_key, i.to_string().as_bytes(), NotationDataFlags::empty().set_human_readable(), false)? .sign_userid_binding(&mut primary_signer, alice.primary_key().component(), alice.userids().next().unwrap().userid()) { Ok(v) => v, Err(e) => { eprintln!("Failed to make {} signatures on top of \ the original one.", i); return Err(e); // Not cool. }, }; // Merge it and check that the new binding signature is // the current one. alice = alice.insert_packets(new_sig.clone())?.0; let sig = alice.with_policy(p, None)?.userids().next().unwrap() .binding_signature(); assert_eq!(sig, &new_sig); } Ok(()) } /// Checks that subpackets are marked as authentic on signature /// verification. #[test] fn subpacket_authentication() -> Result<()> { use subpacket::{Subpacket, SubpacketValue}; // We'll study this certificate, because it contains a // signing-capable subkey. let mut pp = crate::PacketPile::from_bytes(crate::tests::key( "emmelie-dorothea-dina-samantha-awina-ed25519.pgp"))?; assert_eq!(pp.children().count(), 5); // The signatures have not been verified, hence no subpacket // is authenticated. if let Some(Packet::Signature(sig)) = pp.path_ref_mut(&[4]) { assert!(sig.hashed_area().iter().all(|p| ! p.authenticated())); assert!(sig.unhashed_area().iter().all(|p| ! p.authenticated())); // Add a bogus issuer subpacket. sig.unhashed_area_mut().add(Subpacket::new( SubpacketValue::Issuer("AAAA BBBB CCCC DDDD".parse()?), false)?)?; } else { panic!("expected a signature"); } // Break the userid binding signature. if let Some(Packet::Signature(sig)) = pp.path_ref_mut(&[2]) { assert!(sig.hashed_area().iter().all(|p| ! p.authenticated())); assert!(sig.unhashed_area().iter().all(|p| ! p.authenticated())); // Add a bogus issuer subpacket to the hashed area // breaking the signature. sig.hashed_area_mut().add(Subpacket::new( SubpacketValue::Issuer("AAAA BBBB CCCC DDDD".parse()?), false)?)?; } else { panic!("expected a signature"); } // Parse into cert verifying the signatures. use std::convert::TryFrom; let cert = Cert::try_from(pp)?; assert_eq!(cert.bad_signatures().count(), 1); assert_eq!(cert.keys().subkeys().count(), 1); let subkey = cert.keys().subkeys().next().unwrap(); assert_eq!(subkey.self_signatures().count(), 1); // All the authentic information in the self signature has // been authenticated by the verification process. let sig = &subkey.self_signatures().next().unwrap(); assert!(sig.hashed_area().iter().all(|p| p.authenticated())); // All but our fake issuer information. assert!(sig.unhashed_area().iter().all(|p| { if let SubpacketValue::Issuer(id) = p.value() { if id == &"AAAA BBBB CCCC DDDD".parse().unwrap() { // Our fake id... true } else { p.authenticated() } } else { p.authenticated() } })); // Check the subpackets in the embedded signature. let sig = sig.embedded_signatures().next().unwrap(); assert!(sig.hashed_area().iter().all(|p| p.authenticated())); assert!(sig.unhashed_area().iter().all(|p| p.authenticated())); // No information in the bad signature has been authenticated. let sig = cert.bad_signatures().next().unwrap(); assert!(sig.hashed_area().iter().all(|p| ! p.authenticated())); assert!(sig.unhashed_area().iter().all(|p| ! p.authenticated())); Ok(()) } /// Checks that signature normalization adds missing issuer /// information. #[test] fn normalization_adds_missing_issuers() -> Result<()> { use subpacket::SubpacketTag; let mut pp = crate::PacketPile::from_bytes(crate::tests::key( "emmelie-dorothea-dina-samantha-awina-ed25519.pgp"))?; assert_eq!(pp.children().count(), 5); // Remove the issuer subpacket from a binding signature. if let Some(Packet::Signature(sig)) = pp.path_ref_mut(&[4]) { sig.unhashed_area_mut().remove_all(SubpacketTag::Issuer); assert_eq!(sig.get_issuers().len(), 1); } else { panic!("expected a signature"); } // Verify the subkey binding without parsing into cert. let primary_key = if let Some(Packet::PublicKey(key)) = pp.path_ref(&[0]) { key } else { panic!("Expected a primary key"); }; let subkey = if let Some(Packet::PublicSubkey(key)) = pp.path_ref(&[3]) { key } else { panic!("Expected a subkey"); }; let sig = if let Some(Packet::Signature(sig)) = pp.path_ref(&[4]) { sig.clone() } else { panic!("expected a signature"); }; // The signature has only an issuer fingerprint. assert_eq!(sig.get_issuers().len(), 1); assert_eq!(sig.subpackets(SubpacketTag::Issuer).count(), 0); // But normalization after verification adds the missing // information. sig.verify_subkey_binding(primary_key, primary_key, subkey)?; let normalized_sig = sig.normalize(); assert_eq!(normalized_sig.subpackets(SubpacketTag::Issuer).count(), 1); Ok(()) } /// Tests signature merging. #[test] fn merging() -> Result<()> { use crate::packet::signature::subpacket::*; let key: key::SecretKey = Key6::generate_ecc(true, Curve::Ed25519)?.into(); let mut pair = key.into_keypair()?; let msg = b"Hello, World"; let mut hash = HashAlgorithm::SHA256.context()? .for_signature(pair.public().version()); hash.update(&msg[..]); let fp = pair.public().fingerprint(); let keyid = KeyID::from(&fp); // Make a feeble signature with issuer information in the // unhashed area. let sig = SignatureBuilder::new(SignatureType::Text) .modify_unhashed_area(|mut a| { a.add(Subpacket::new( SubpacketValue::IssuerFingerprint(fp.clone()), false)?)?; a.add(Subpacket::new( SubpacketValue::Issuer(keyid.clone()), false)?)?; Ok(a) })? .sign_hash(&mut pair, hash.clone())?; // Try to displace the issuer information. let dummy: crate::KeyID = "AAAA BBBB CCCC DDDD".parse()?; let mut malicious = sig.clone(); malicious.unhashed_area_mut().clear(); loop { let r = malicious.unhashed_area_mut().add(Subpacket::new( SubpacketValue::Issuer(dummy.clone()), false)?); if r.is_err() { break; } } // Merge and check that the issuer information is intact. // This works without any issuer being authenticated because // of the deduplicating nature of the merge. let merged = sig.clone().merge(malicious.clone())?; let issuers = merged.get_issuers(); let keyid_issuers = merged.issuers().collect::<Vec<&KeyID>>(); assert_eq!(issuers.len(), 3); assert!(issuers.contains(&KeyHandle::from(&fp))); assert!(keyid_issuers.contains(&&keyid)); assert!(keyid_issuers.contains(&&dummy)); // Same, but the other way around. let merged = malicious.clone().merge(sig.clone())?; let issuers = merged.get_issuers(); let keyid_issuers = merged.issuers().collect::<Vec<_>>(); assert_eq!(issuers.len(), 3); assert!(issuers.contains(&KeyHandle::from(&fp))); assert!(keyid_issuers.contains(&&keyid)); assert!(keyid_issuers.contains(&&dummy)); // Try to displace the issuer information using garbage // packets. let mut malicious = sig.clone(); malicious.unhashed_area_mut().clear(); let mut i: u64 = 0; loop { let r = malicious.unhashed_area_mut().add(Subpacket::new( SubpacketValue::Unknown { tag: SubpacketTag::Unknown(231), body: i.to_be_bytes().iter().cloned().collect(), }, false)?); if r.is_err() { break; } i += 1; } // Merge and check that the issuer information is intact. // This works without any issuer being authenticated because // the merge prefers plausible packets. let merged = sig.clone().merge(malicious.clone())?; let issuers = merged.get_issuers(); let keyid_issuers = merged.issuers().collect::<Vec<_>>(); assert_eq!(issuers.len(), 2); assert!(issuers.contains(&KeyHandle::from(&fp))); assert!(keyid_issuers.contains(&&keyid)); // Same, but the other way around. let merged = malicious.clone().merge(sig.clone())?; let issuers = merged.get_issuers(); let keyid_issuers = merged.issuers().collect::<Vec<_>>(); assert_eq!(issuers.len(), 2); assert!(issuers.contains(&KeyHandle::from(&fp))); assert!(keyid_issuers.contains(&&keyid)); // Try to displace the issuer information by using random keyids. let mut malicious = sig.clone(); malicious.unhashed_area_mut().clear(); let mut i: u64 = 1; loop { let r = malicious.unhashed_area_mut().add(Subpacket::new( SubpacketValue::Issuer(i.into()), false)?); if r.is_err() { break; } i += 1; } // Merge and check that the issuer information is intact. // This works because the issuer information is being // authenticated by the verification, and the merge process // prefers authenticated information. let verified = sig.clone(); verified.verify_hash(pair.public(), hash.clone())?; let merged = verified.clone().merge(malicious.clone())?; let issuers = merged.get_issuers(); let keyid_issuers = merged.issuers().collect::<Vec<_>>(); assert!(issuers.contains(&KeyHandle::from(&fp))); assert!(keyid_issuers.contains(&&keyid)); // Same, but the other way around. let merged = malicious.clone().merge(verified.clone())?; let issuers = merged.get_issuers(); let keyid_issuers = merged.issuers().collect::<Vec<_>>(); assert!(issuers.contains(&KeyHandle::from(&fp))); assert!(keyid_issuers.contains(&&keyid)); Ok(()) } #[test] fn issue_998() -> Result<()> { let now_t = Timestamp::try_from(crate::now())?; let now = SystemTime::from(now_t); let hour = std::time::Duration::new(3600, 0); let hour_t = crate::types::Duration::from(3600); let past = now - 2 * hour; let sig = SignatureBuilder::new(SignatureType::PositiveCertification) .modify_hashed_area(|mut a| { a.add(Subpacket::new( SubpacketValue::SignatureCreationTime(now_t), true)?)?; a.add(Subpacket::new( SubpacketValue::SignatureExpirationTime(hour_t), true)?)?; Ok(a) })?; let sig = sig.set_reference_time(now)?; assert_eq!(sig.signature_expiration_time(), Some(now + hour)); let sig = sig.set_reference_time(past)?; assert_eq!(sig.signature_expiration_time(), Some(now - hour)); Ok(()) } } �������������������������������������������sequoia-openpgp-2.0.0/src/packet/skesk/v4.rs��������������������������������������������������������0000644�0000000�0000000�00000027006�10461020230�0017021�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������//! Symmetric-Key Encrypted Session Key Packets. //! //! SKESK packets hold symmetrically encrypted session keys. The //! session key is needed to decrypt the actual ciphertext. See //! [Section 5.3 of RFC 9580] for details. //! //! [Section 5.3 of RFC 9580]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.3 #[cfg(test)] use quickcheck::{Arbitrary, Gen}; use crate::Result; use crate::crypto::{ S2K, Password, SessionKey, }; use crate::Error; use crate::types::{ SymmetricAlgorithm, }; use crate::packet::{self, SKESK}; use crate::Packet; /// Holds a symmetrically encrypted session key version 4. /// /// Holds a symmetrically encrypted session key. The session key is /// needed to decrypt the actual ciphertext. See [Section 5.3 of RFC /// 9580] for details. /// /// [Section 5.3 of RFC 9580]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.3 #[derive(Clone, Debug)] pub struct SKESK4 { /// CTB header fields. pub(crate) common: packet::Common, /// Packet version. Must be 4 or 5. /// /// This struct is also used by SKESK6, hence we have a version /// field. pub(crate) version: u8, /// Symmetric algorithm used to encrypt the session key. pub(crate) sym_algo: SymmetricAlgorithm, /// Key derivation method for the symmetric key. pub(crate) s2k: S2K, /// The encrypted session key. /// /// If we recognized the S2K object during parsing, we can /// successfully parse the data into S2K and ciphertext. However, /// if we do not recognize the S2K type, we do not know how large /// its parameters are, so we cannot cleanly parse it, and have to /// accept that the S2K's body bleeds into the rest of the data. pub(crate) esk: std::result::Result<Option<Box<[u8]>>, // optional ciphertext. Box<[u8]>>, // S2K body + maybe ciphertext. } assert_send_and_sync!(SKESK4); // Because the S2K and ESK cannot be cleanly separated at parse time, // we need to carefully compare and hash SKESK4 packets. impl PartialEq for SKESK4 { fn eq(&self, other: &SKESK4) -> bool { self.version == other.version && self.sym_algo == other.sym_algo // Treat S2K and ESK as opaque blob. && { // XXX: This would be nicer without the allocations. use crate::serialize::MarshalInto; let mut a = self.s2k.to_vec().unwrap(); let mut b = other.s2k.to_vec().unwrap(); a.extend_from_slice(self.raw_esk()); b.extend_from_slice(other.raw_esk()); a == b } } } impl Eq for SKESK4 {} impl std::hash::Hash for SKESK4 { fn hash<H: std::hash::Hasher>(&self, state: &mut H) { self.version.hash(state); self.sym_algo.hash(state); // Treat S2K and ESK as opaque blob. // XXX: This would be nicer without the allocations. use crate::serialize::MarshalInto; let mut a = self.s2k.to_vec().unwrap(); a.extend_from_slice(self.raw_esk()); a.hash(state); } } impl SKESK4 { /// Creates a new SKESK version 4 packet. /// /// The given symmetric algorithm is the one used to encrypt the /// session key. pub fn new(esk_algo: SymmetricAlgorithm, s2k: S2K, esk: Option<Box<[u8]>>) -> Result<SKESK4> { Self::new_raw(esk_algo, s2k, Ok(esk.and_then(|esk| { if esk.len() == 0 { None } else { Some(esk) } }))) } /// Creates a new SKESK version 4 packet. /// /// The given symmetric algorithm is the one used to encrypt the /// session key. pub(crate) fn new_raw(esk_algo: SymmetricAlgorithm, s2k: S2K, esk: std::result::Result<Option<Box<[u8]>>, Box<[u8]>>) -> Result<SKESK4> { Ok(SKESK4{ common: Default::default(), version: 4, sym_algo: esk_algo, s2k, esk, }) } /// Creates a new SKESK4 packet with the given password. /// /// This function takes two [`SymmetricAlgorithm`] arguments: The /// first, `payload_algo`, is the algorithm used to encrypt the /// message's payload (i.e. the one used in the [`SEIP`]), and the /// second, `esk_algo`, is used to encrypt the session key. /// Usually, one should use the same algorithm, but if they /// differ, the `esk_algo` should be at least as strong as the /// `payload_algo` as not to weaken the security of the payload /// encryption. /// /// [`SymmetricAlgorithm`]: crate::types::SymmetricAlgorithm /// [`SEIP`]: crate::packet::SEIP pub fn with_password(payload_algo: SymmetricAlgorithm, esk_algo: SymmetricAlgorithm, s2k: S2K, session_key: &SessionKey, password: &Password) -> Result<SKESK4> { if session_key.len() != payload_algo.key_size()? { return Err(Error::InvalidArgument(format!( "Invalid size of session key, got {} want {}", session_key.len(), payload_algo.key_size()?)).into()); } // Derive key and make a cipher. let key = s2k.derive_key(password, esk_algo.key_size()?)?; let block_size = esk_algo.block_size()?; let iv = vec![0u8; block_size]; let mut cipher = esk_algo.make_encrypt_cfb(&key[..], iv)?; // We need to prefix the cipher specifier to the session key. let mut psk: SessionKey = vec![0; 1 + session_key.len()].into(); psk[0] = payload_algo.into(); psk[1..].copy_from_slice(session_key); let mut esk = vec![0u8; psk.len()]; for (pt, ct) in psk[..].chunks(block_size) .zip(esk.chunks_mut(block_size)) { cipher.encrypt(ct, pt)?; } SKESK4::new(esk_algo, s2k, Some(esk.into())) } /// Gets the symmetric encryption algorithm. pub fn symmetric_algo(&self) -> SymmetricAlgorithm { self.sym_algo } /// Sets the symmetric encryption algorithm. pub fn set_symmetric_algo(&mut self, algo: SymmetricAlgorithm) -> SymmetricAlgorithm { ::std::mem::replace(&mut self.sym_algo, algo) } /// Gets the key derivation method. pub fn s2k(&self) -> &S2K { &self.s2k } /// Sets the key derivation method. pub fn set_s2k(&mut self, s2k: S2K) -> S2K { ::std::mem::replace(&mut self.s2k, s2k) } /// Gets the encrypted session key. /// /// If the [`S2K`] mechanism is not supported by Sequoia, this /// function will fail. Note that the information is not lost, /// but stored in the packet. If the packet is serialized again, /// it is written out. /// /// [`S2K`]: crate::crypto::S2K pub fn esk(&self) -> Result<Option<&[u8]>> { self.esk.as_ref() .map(|esko| esko.as_ref().map(|esk| &esk[..])) .map_err(|_| Error::MalformedPacket( format!("Unknown S2K: {:?}", self.s2k)).into()) } /// Returns the encrypted session key, possibly including the body /// of the S2K object. pub(crate) fn raw_esk(&self) -> &[u8] { match self.esk.as_ref() { Ok(Some(esk)) => &esk[..], Ok(None) => &[][..], Err(s2k_esk) => &s2k_esk[..], } } /// Sets the encrypted session key. pub fn set_esk(&mut self, esk: Option<Box<[u8]>>) -> Option<Box<[u8]>> { ::std::mem::replace( &mut self.esk, Ok(esk.and_then(|esk| { if esk.len() == 0 { None } else { Some(esk) } }))) .unwrap_or(None) } /// Derives the key inside this SKESK4 from `password`. /// /// Returns a tuple of the symmetric cipher to use with the key /// and the key itself. pub fn decrypt(&self, password: &Password) -> Result<(SymmetricAlgorithm, SessionKey)> { let key = self.s2k.derive_key(password, self.sym_algo.key_size()?)?; if let Some(esk) = self.esk()? { // Use the derived key to decrypt the ESK. Unlike SEP & // SEIP we have to use plain CFB here. let blk_sz = self.sym_algo.block_size()?; let iv = vec![0u8; blk_sz]; let mut dec = self.sym_algo.make_decrypt_cfb(&key[..], iv)?; let mut plain: SessionKey = vec![0u8; esk.len()].into(); let cipher = esk; for (pl, ct) in plain[..].chunks_mut(blk_sz).zip(cipher.chunks(blk_sz)) { dec.decrypt(pl, ct)?; } // Get the algorithm from the front. let sym = SymmetricAlgorithm::from(plain[0]); Ok((sym, plain[1..].into())) } else { // No ESK, we return the derived key. Ok((self.sym_algo, key)) } } } impl From<SKESK4> for super::SKESK { fn from(p: SKESK4) -> Self { super::SKESK::V4(p) } } impl From<SKESK4> for Packet { fn from(s: SKESK4) -> Self { Packet::SKESK(SKESK::V4(s)) } } #[cfg(test)] impl Arbitrary for SKESK4 { fn arbitrary(g: &mut Gen) -> Self { SKESK4::new(SymmetricAlgorithm::arbitrary(g), S2K::arbitrary(g), Option::<Vec<u8>>::arbitrary(g).map(|v| v.into())) .unwrap() } } #[cfg(test)] mod test { use super::*; use crate::parse::Parse; use crate::serialize::MarshalInto; quickcheck! { fn roundtrip_v4(p: SKESK4) -> bool { let p = SKESK::from(p); let q = SKESK::from_bytes(&p.to_vec().unwrap()).unwrap(); assert_eq!(p, q); true } } /// Tests various S2K methods, with and without encrypted session /// key. #[test] fn skesk4_s2k_variants() -> Result<()> { use std::io::Read; use crate::{ Cert, packet::{SKESK, PKESK}, parse::stream::*, }; struct H(); impl VerificationHelper for H { fn get_certs(&mut self, _ids: &[crate::KeyHandle]) -> Result<Vec<Cert>> { Ok(Vec::new()) } fn check(&mut self, _m: MessageStructure) -> Result<()> { Ok(()) } } impl DecryptionHelper for H { fn decrypt(&mut self, _: &[PKESK], skesks: &[SKESK], _: Option<SymmetricAlgorithm>, decrypt: &mut dyn FnMut(Option<SymmetricAlgorithm>, &SessionKey) -> bool) -> Result<Option<Cert>> { assert_eq!(skesks.len(), 1); let (cipher, sk) = skesks[0].decrypt(&"password".into())?; assert_eq!(cipher, Some(SymmetricAlgorithm::AES256)); let r = decrypt(cipher, &sk); assert!(r); Ok(None) } } let p = &crate::policy::StandardPolicy::new(); for variant in &["simple", "salted", "iterated.min", "iterated.max"] { for esk in &["", ".esk"] { let name = format!("s2k/{}{}.pgp", variant, esk); eprintln!("{}", name); let mut verifier = DecryptorBuilder::from_bytes( crate::tests::message(&name))? .with_policy(p, None, H())?; let mut b = Vec::new(); verifier.read_to_end(&mut b)?; assert_eq!(&b, b"Hello World :)"); } } Ok(()) } } ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/src/packet/skesk/v6.rs��������������������������������������������������������0000644�0000000�0000000�00000025236�10461020230�0017026�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������//! Symmetric-Key Encrypted Session Key Packets. //! //! SKESK packets hold symmetrically encrypted session keys. The //! session key is needed to decrypt the actual ciphertext. See //! [Section 5.3 of RFC 9580] for details. //! //! [Section 5.3 of RFC 9580]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.3 #[cfg(test)] use quickcheck::{Arbitrary, Gen}; use crate::Result; use crate::crypto::{ self, S2K, Password, SessionKey, backend::{Backend, interface::Kdf}, }; use crate::crypto::aead::CipherOp; use crate::Error; use crate::types::{ AEADAlgorithm, SymmetricAlgorithm, }; use crate::packet::{ Packet, SKESK, skesk::SKESK4, }; /// Holds a symmetrically encrypted session key version 6. /// /// Holds a symmetrically encrypted session key. The session key is /// needed to decrypt the actual ciphertext. See [Version 6 Symmetric /// Key Encrypted Session Key Packet Format] for details. /// /// [Version 6 Symmetric Key Encrypted Session Key Packet Format]: https://www.rfc-editor.org/rfc/rfc9580.html#name-version-6-symmetric-key-enc #[derive(Clone, Debug, PartialEq, Eq, Hash)] pub struct SKESK6 { /// Common fields. pub(crate) skesk4: SKESK4, /// AEAD algorithm. aead_algo: AEADAlgorithm, /// Initialization vector for the AEAD algorithm. aead_iv: Box<[u8]>, } assert_send_and_sync!(SKESK6); impl SKESK6 { /// Creates a new SKESK version 6 packet. /// /// The given symmetric algorithm is the one used to encrypt the /// session key. pub fn new(sym_algo: SymmetricAlgorithm, aead_algo: AEADAlgorithm, s2k: S2K, aead_iv: Box<[u8]>, esk: Box<[u8]>) -> Result<Self> { Ok(SKESK6 { skesk4: SKESK4 { common: Default::default(), version: 6, sym_algo, s2k, esk: Ok(Some(esk)), }, aead_algo, aead_iv, }) } /// Creates a new SKESK version 6 packet with the given password. /// /// This function takes two [`SymmetricAlgorithm`] arguments: The /// first, `payload_algo`, is the algorithm used to encrypt the /// message's payload (i.e. the one used in the [`SEIP`]), and the /// second, `esk_algo`, is used to encrypt the session key. /// Usually, one should use the same algorithm, but if they /// differ, the `esk_algo` should be at least as strong as the /// `payload_algo` as not to weaken the security of the payload /// encryption. /// /// [`SymmetricAlgorithm`]: crate::types::SymmetricAlgorithm /// [`SEIP`]: crate::packet::SEIP pub fn with_password(payload_algo: SymmetricAlgorithm, esk_algo: SymmetricAlgorithm, esk_aead: AEADAlgorithm, s2k: S2K, session_key: &SessionKey, password: &Password) -> Result<Self> { if session_key.len() != payload_algo.key_size()? { return Err(Error::InvalidArgument(format!( "Invalid size of session key, got {} want {}", session_key.len(), payload_algo.key_size()?)).into()); } // Derive key and make a cipher. let ad = [0xc3, 6, esk_algo.into(), esk_aead.into()]; let key = s2k.derive_key(password, esk_algo.key_size()?)?; let mut kek: SessionKey = vec![0; esk_algo.key_size()?].into(); Backend::hkdf_sha256(&key, None, &ad, &mut kek)?; // Encrypt the session key with the KEK. let mut iv = vec![0u8; esk_aead.nonce_size()?]; crypto::random(&mut iv)?; let mut ctx = esk_aead.context(esk_algo, &kek, &ad, &iv, CipherOp::Encrypt)?; let mut esk_digest = vec![0u8; session_key.len() + esk_aead.digest_size()?]; ctx.encrypt_seal(&mut esk_digest, session_key)?; // Attach digest to the ESK, we model it as one. SKESK6::new(esk_algo, esk_aead, s2k, iv.into_boxed_slice(), esk_digest.into()) } /// Derives the key inside this `SKESK6` from `password`. /// /// Returns a tuple containing a placeholder symmetric cipher and /// the key itself. `SKESK6` packets do not contain the symmetric /// cipher algorithm and instead rely on the `SEIPDv2` packet that /// contains it. pub fn decrypt(&self, password: &Password) -> Result<SessionKey> { let key = self.s2k().derive_key(password, self.symmetric_algo().key_size()?)?; let mut kek: SessionKey = vec![0; self.symmetric_algo().key_size()?].into(); let ad = [0xc3, 6 /* Version. */, self.symmetric_algo().into(), self.aead_algo.into()]; Backend::hkdf_sha256(&key, None, &ad, &mut kek)?; // Use the derived key to decrypt the ESK. let mut cipher = self.aead_algo.context( self.symmetric_algo(), &kek, &ad, self.aead_iv(), CipherOp::Decrypt)?; let mut plain: SessionKey = vec![0; self.esk().len() - self.aead_algo.digest_size()?].into(); cipher.decrypt_verify(&mut plain, self.esk())?; Ok(plain) } /// Gets the symmetric encryption algorithm. pub fn symmetric_algo(&self) -> SymmetricAlgorithm { self.skesk4.sym_algo } /// Sets the symmetric encryption algorithm. pub fn set_symmetric_algo(&mut self, algo: SymmetricAlgorithm) -> SymmetricAlgorithm { ::std::mem::replace(&mut self.skesk4.sym_algo, algo) } /// Gets the key derivation method. pub fn s2k(&self) -> &S2K { &self.skesk4.s2k } /// Sets the key derivation method. pub fn set_s2k(&mut self, s2k: S2K) -> S2K { ::std::mem::replace(&mut self.skesk4.s2k, s2k) } /// Gets the AEAD algorithm. pub fn aead_algo(&self) -> AEADAlgorithm { self.aead_algo } /// Sets the AEAD algorithm. pub fn set_aead_algo(&mut self, algo: AEADAlgorithm) -> AEADAlgorithm { ::std::mem::replace(&mut self.aead_algo, algo) } /// Gets the AEAD initialization vector. pub fn aead_iv(&self) -> &[u8] { &self.aead_iv } /// Sets the AEAD initialization vector. pub fn set_aead_iv(&mut self, iv: Box<[u8]>) -> Box<[u8]> { ::std::mem::replace(&mut self.aead_iv, iv) } /// Gets the encrypted session key. pub fn esk(&self) -> &[u8] { self.skesk4.raw_esk() } /// Sets the encrypted session key. pub fn set_esk(&mut self, esk: Box<[u8]>) -> Box<[u8]> { ::std::mem::replace(&mut self.skesk4.esk, Ok(Some(esk))) .expect("v6 SKESK can always be parsed") .expect("v6 SKESK packets always have an ESK") } } impl From<SKESK6> for super::SKESK { fn from(p: SKESK6) -> Self { super::SKESK::V6(p) } } impl From<SKESK6> for Packet { fn from(s: SKESK6) -> Self { Packet::SKESK(SKESK::V6(s)) } } #[cfg(test)] impl Arbitrary for SKESK6 { fn arbitrary(g: &mut Gen) -> Self { let algo = AEADAlgorithm::const_default(); let mut iv = vec![0u8; algo.nonce_size().unwrap()]; for b in iv.iter_mut() { *b = u8::arbitrary(g); } let esk_len = (u8::arbitrary(g) % 64) as usize + algo.digest_size().unwrap(); let mut esk = vec![0u8; esk_len]; for b in esk.iter_mut() { *b = u8::arbitrary(g); } SKESK6::new(SymmetricAlgorithm::arbitrary(g), algo, S2K::arbitrary(g), iv.into(), esk.into()) .unwrap() } } #[cfg(test)] mod test { use super::*; use crate::PacketPile; use crate::parse::Parse; use crate::serialize::MarshalInto; quickcheck! { fn roundtrip_v6(p: SKESK6) -> bool { let p = SKESK::from(p); let q = SKESK::from_bytes(&p.to_vec().unwrap()).unwrap(); assert_eq!(p, q); true } } /// This sample packet is from RFC9580. #[test] fn v6skesk_aes128_ocb() -> Result<()> { sample_skesk6_packet( SymmetricAlgorithm::AES128, AEADAlgorithm::OCB, "crypto-refresh/v6skesk-aes128-ocb.pgp", b"\xe8\x0d\xe2\x43\xa3\x62\xd9\x3b\ \x9d\xc6\x07\xed\xe9\x6a\x73\x56", b"\x28\xe7\x9a\xb8\x23\x97\xd3\xc6\ \x3d\xe2\x4a\xc2\x17\xd7\xb7\x91") } /// This sample packet is from RFC9580. #[test] fn v6skesk_aes128_eax() -> Result<()> { sample_skesk6_packet( SymmetricAlgorithm::AES128, AEADAlgorithm::EAX, "crypto-refresh/v6skesk-aes128-eax.pgp", b"\x15\x49\x67\xe5\x90\xaa\x1f\x92\ \x3e\x1c\x0a\xc6\x4c\x88\xf2\x3d", b"\x38\x81\xba\xfe\x98\x54\x12\x45\ \x9b\x86\xc3\x6f\x98\xcb\x9a\x5e") } /// This sample packet is from RFC9580. #[test] fn v6skesk_aes128_gcm() -> Result<()> { sample_skesk6_packet( SymmetricAlgorithm::AES128, AEADAlgorithm::GCM, "crypto-refresh/v6skesk-aes128-gcm.pgp", b"\x25\x02\x81\x71\x5b\xba\x78\x28\ \xef\x71\xef\x64\xc4\x78\x47\x53", b"\x19\x36\xfc\x85\x68\x98\x02\x74\ \xbb\x90\x0d\x83\x19\x36\x0c\x77") } fn sample_skesk6_packet(cipher: SymmetricAlgorithm, aead: AEADAlgorithm, name: &str, derived_key: &[u8], session_key: &[u8]) -> Result<()> { let password: Password = String::from("password").into(); let packets: Vec<Packet> = PacketPile::from_bytes( crate::tests::file(name))? .into_children().collect(); assert_eq!(packets.len(), 2); if let Packet::SKESK(SKESK::V6(ref s)) = packets[0] { let derived = s.s2k().derive_key( &password, s.symmetric_algo().key_size()?)?; eprintln!("derived: {:x?}", &derived[..]); assert_eq!(&derived[..], derived_key); if aead.is_supported() && aead.supports_symmetric_algo(&cipher) { let sk = s.decrypt(&password)?; eprintln!("sk: {:x?}", &sk[..]); assert_eq!(&sk[..], session_key); } else { eprintln!("{}-{} is not supported, skipping decryption.", cipher, aead); } } else { panic!("bad packet, expected v6 SKESK: {:?}", packets[0]); } Ok(()) } } ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/src/packet/skesk.rs�����������������������������������������������������������0000644�0000000�0000000�00000005115�10461020230�0016465�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������//! Symmetric-Key Encrypted Session Key Packets. //! //! SKESK packets hold symmetrically encrypted session keys. The //! session key is needed to decrypt the actual ciphertext. See //! [Section 5.3 of RFC 9580] for details. //! //! [Section 5.3 of RFC 9580]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.3 #[cfg(test)] use quickcheck::{Arbitrary, Gen}; use crate::Result; use crate::crypto::{ Password, SessionKey, }; use crate::types::{ SymmetricAlgorithm, }; use crate::Packet; mod v4; pub use v4::SKESK4; mod v6; pub use v6::SKESK6; /// Holds a symmetrically encrypted session key. /// /// The session key is used to decrypt the actual ciphertext, which is /// typically stored in a [SEIP] packet. See [Section 5.3 of RFC /// 9580] for details. /// /// An SKESK packet is not normally instantiated directly. In most /// cases, you'll create one as a side effect of encrypting a message /// using the [streaming serializer], or parsing an encrypted message /// using the [`PacketParser`]. /// /// [SEIP]: crate::packet::SEIP /// [Section 5.3 of RFC 9580]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.3 /// [streaming serializer]: crate::serialize::stream /// [`PacketParser`]: crate::parse::PacketParser #[non_exhaustive] #[derive(PartialEq, Eq, Hash, Clone, Debug)] pub enum SKESK { /// SKESK packet version 4. V4(self::SKESK4), /// SKESK packet version 6. V6(self::SKESK6), } assert_send_and_sync!(SKESK); impl SKESK { /// Gets the version. pub fn version(&self) -> u8 { match self { SKESK::V4(_) => 4, SKESK::V6(_) => 6, } } /// Derives the key inside this SKESK from `password`. /// /// Returns a tuple of the symmetric cipher to use with the key /// and the key itself, if this information is parsed from the /// SKESK4 pakcket. The symmetric cipher will be omitted for /// SKESK6 packets, which don't carry that information. pub fn decrypt(&self, password: &Password) -> Result<(Option<SymmetricAlgorithm>, SessionKey)> { match self { SKESK::V4(s) => s.decrypt(password) .map(|(algo, sk)| (Some(algo), sk)), SKESK::V6(ref s) => Ok((None, s.decrypt(password)?)), } } } impl From<SKESK> for Packet { fn from(p: SKESK) -> Self { Packet::SKESK(p) } } #[cfg(test)] impl Arbitrary for SKESK { fn arbitrary(g: &mut Gen) -> Self { if bool::arbitrary(g) { SKESK::V4(SKESK4::arbitrary(g)) } else { SKESK::V6(SKESK6::arbitrary(g)) } } } ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/src/packet/tag.rs�������������������������������������������������������������0000644�0000000�0000000�00000024401�10461020230�0016117�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������use std::fmt; use std::cmp::Ordering; use std::hash::{Hash, Hasher}; #[cfg(test)] use quickcheck::{Arbitrary, Gen}; use crate::packet::Packet; /// The OpenPGP packet tags as defined in [Section 5 of RFC 9580]. /// /// [Section 5 of RFC 9580]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5 /// /// The values correspond to the serialized format. #[derive(Clone, Copy, Debug)] #[non_exhaustive] pub enum Tag { /// Reserved Packet tag. Reserved, /// Public-Key Encrypted Session Key Packet. PKESK, /// Signature Packet. Signature, /// Symmetric-Key Encrypted Session Key Packet. SKESK, /// One-Pass Signature Packet. OnePassSig, /// Secret-Key Packet. SecretKey, /// Public-Key Packet. PublicKey, /// Secret-Subkey Packet. SecretSubkey, /// Compressed Data Packet. CompressedData, /// Symmetrically Encrypted Data Packet. SED, /// Marker Packet (Obsolete Literal Packet). Marker, /// Literal Data Packet. Literal, /// Trust Packet. Trust, /// User ID Packet. UserID, /// Public-Subkey Packet. PublicSubkey, /// User Attribute Packet. UserAttribute, /// Sym. Encrypted and Integrity Protected Data Packet. SEIP, /// Modification Detection Code Packet. MDC, /// Reserved ("AEAD Encrypted Data Packet"). AED, /// Padding packet. Padding, /// Unassigned packets (as of RFC4880). Unknown(u8), /// Experimental packets. Private(u8), } assert_send_and_sync!(Tag); impl Eq for Tag {} impl PartialEq for Tag { fn eq(&self, other: &Tag) -> bool { self.cmp(other) == Ordering::Equal } } impl PartialOrd for Tag { fn partial_cmp(&self, other: &Self) -> Option<Ordering> { Some(self.cmp(other)) } } impl Ord for Tag { fn cmp(&self, other: &Self) -> Ordering { let a : u8 = (*self).into(); let b : u8 = (*other).into(); a.cmp(&b) } } impl Hash for Tag { fn hash<H: Hasher>(&self, state: &mut H) { let t: u8 = (*self).into(); t.hash(state); } } impl From<u8> for Tag { fn from(u: u8) -> Self { use crate::packet::Tag::*; match u { 0 => Reserved, 1 => PKESK, 2 => Signature, 3 => SKESK, 4 => OnePassSig, 5 => SecretKey, 6 => PublicKey, 7 => SecretSubkey, 8 => CompressedData, 9 => SED, 10 => Marker, 11 => Literal, 12 => Trust, 13 => UserID, 14 => PublicSubkey, 17 => UserAttribute, 18 => SEIP, 19 => MDC, 20 => AED, 21 => Padding, 60..=63 => Private(u), _ => Unknown(u), } } } impl From<Tag> for u8 { fn from(t: Tag) -> u8 { (&t).into() } } impl From<&Tag> for u8 { fn from(t: &Tag) -> u8 { match t { Tag::Reserved => 0, Tag::PKESK => 1, Tag::Signature => 2, Tag::SKESK => 3, Tag::OnePassSig => 4, Tag::SecretKey => 5, Tag::PublicKey => 6, Tag::SecretSubkey => 7, Tag::CompressedData => 8, Tag::SED => 9, Tag::Marker => 10, Tag::Literal => 11, Tag::Trust => 12, Tag::UserID => 13, Tag::PublicSubkey => 14, Tag::UserAttribute => 17, Tag::SEIP => 18, Tag::MDC => 19, Tag::AED => 20, Tag::Padding => 21, Tag::Private(x) => *x, Tag::Unknown(x) => *x, } } } impl From<&Packet> for Tag { fn from(p: &Packet) -> Tag { p.tag() } } impl From<Packet> for Tag { fn from(p: Packet) -> Tag { p.tag() } } impl fmt::Display for Tag { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match *self { Tag::Reserved => f.write_str("Reserved - a packet tag MUST NOT have this value"), Tag::PKESK => f.write_str("Public-Key Encrypted Session Key Packet"), Tag::Signature => f.write_str("Signature Packet"), Tag::SKESK => f.write_str("Symmetric-Key Encrypted Session Key Packet"), Tag::OnePassSig => f.write_str("One-Pass Signature Packet"), Tag::SecretKey => f.write_str("Secret-Key Packet"), Tag::PublicKey => f.write_str("Public-Key Packet"), Tag::SecretSubkey => f.write_str("Secret-Subkey Packet"), Tag::CompressedData => f.write_str("Compressed Data Packet"), Tag::SED => f.write_str("Symmetrically Encrypted Data Packet"), Tag::Marker => f.write_str("Marker Packet"), Tag::Literal => f.write_str("Literal Data Packet"), Tag::Trust => f.write_str("Trust Packet"), Tag::UserID => f.write_str("User ID Packet"), Tag::PublicSubkey => f.write_str("Public-Subkey Packet"), Tag::UserAttribute => f.write_str("User Attribute Packet"), Tag::SEIP => f.write_str("Sym. Encrypted and Integrity Protected Data Packet"), Tag::MDC => f.write_str("Modification Detection Code Packet"), Tag::AED => f.write_str("AEAD Encrypted Data Packet"), Tag::Padding => f.write_str("Padding Packet"), Tag::Private(u) => f.write_fmt(format_args!("Private/Experimental Packet {}", u)), Tag::Unknown(u) => f.write_fmt(format_args!("Unknown Packet {}", u)), } } } const PACKET_TAG_VARIANTS: [Tag; 19] = [ Tag::PKESK, Tag::Signature, Tag::SKESK, Tag::OnePassSig, Tag::SecretKey, Tag::PublicKey, Tag::SecretSubkey, Tag::CompressedData, Tag::SED, Tag::Marker, Tag::Literal, Tag::Trust, Tag::UserID, Tag::PublicSubkey, Tag::UserAttribute, Tag::SEIP, Tag::MDC, Tag::AED, Tag::Padding, ]; #[cfg(test)] impl Arbitrary for Tag { fn arbitrary(g: &mut Gen) -> Self { loop { match u8::arbitrary(g) { n @ 0..=63 => break n.into(), _ => (), // try again } } } } impl Tag { /// Returns whether the `Tag` denotes a critical packet. /// /// Upon encountering an unknown critical packet, implementations /// MUST reject the whole packet sequence. On the other hand, /// unknown non-critical packets MUST be ignored. See [Section /// 4.3 of RFC 9580]. /// /// [Section 4.3 of RFC 9580]: https://www.rfc-editor.org/rfc/rfc9580.html#section-4.3 pub fn is_critical(&self) -> bool { match u8::from(self) { 0..=39 => true, 40..=63 => false, // Should never happen, but let's map this to critical as // a conservative choice. 64..=255 => true, } } /// Returns whether the `Tag` can be at the start of a valid /// message. /// /// [Certs] can start with `PublicKey`, [TSKs] with a `SecretKey`. /// /// [Certs]: https://www.rfc-editor.org/rfc/rfc9580.html#section-10.1 /// [TSKs]: https://www.rfc-editor.org/rfc/rfc9580.html#section-10.2 /// /// [Messages] start with a `OnePassSig`, `Signature` (old style /// non-one pass signatures), `PKESK`, `SKESK`, `CompressedData`, /// or `Literal`. /// /// [Messages]: https://www.rfc-editor.org/rfc/rfc9580.html#section-10.3 /// /// Signatures can stand alone either as a [detached signature], a /// third-party certification, or a revocation certificate. /// /// [detached signature]: https://www.rfc-editor.org/rfc/rfc9580.html#section-10.3 pub fn valid_start_of_message(&self) -> bool { // Cert *self == Tag::PublicKey || *self == Tag::SecretKey // Message. || *self == Tag::PKESK || *self == Tag::SKESK || *self == Tag::Literal || *self == Tag::CompressedData // Signed message. || *self == Tag::OnePassSig // Standalone signature, old-style signature. || *self == Tag::Signature } /// Returns an iterator over all valid variants. /// /// Returns an iterator over all known variants. This does not /// include the [`Tag::Reserved`], [`Tag::Private`], or /// [`Tag::Unknown`] variants. pub fn variants() -> impl Iterator<Item=Tag> { PACKET_TAG_VARIANTS.iter().cloned() } } #[cfg(test)] mod tests { use super::*; quickcheck! { fn roundtrip(tag: Tag) -> bool { let val: u8 = tag.into(); tag == Tag::from(val) } } quickcheck! { fn display(tag: Tag) -> bool { let s = format!("{}", tag); !s.is_empty() } } quickcheck! { fn unknown_private(tag: Tag) -> bool { match tag { Tag::Unknown(u) => u > 19 || u == 15 || u == 16, Tag::Private(u) => (60..=63).contains(&u), _ => true } } } #[test] fn parse() { for i in 0..u8::MAX { let _ = Tag::from(i); } } #[test] fn tag_variants() { use std::collections::HashSet; use std::iter::FromIterator; // PACKET_TAG_VARIANTS is a list. Derive it in a different way // to double-check that nothing is missing. let derived_variants = (0..=u8::MAX) .map(Tag::from) .filter(|t| { match t { Tag::Reserved => false, Tag::Private(_) => false, Tag::Unknown(_) => false, _ => true, } }) .collect::<HashSet<_>>(); let known_variants = HashSet::from_iter(PACKET_TAG_VARIANTS.iter().cloned()); let missing = known_variants .symmetric_difference(&derived_variants) .collect::<Vec<_>>(); assert!(missing.is_empty(), "{:?}", missing); } } ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/src/packet/trust.rs�����������������������������������������������������������0000644�0000000�0000000�00000003615�10461020230�0016531�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������use std::fmt; #[cfg(test)] use quickcheck::{Arbitrary, Gen}; use crate::packet; use crate::Packet; /// Holds a Trust packet. /// /// Trust packets are used to hold implementation specific information /// in an implementation-defined format. Trust packets are normally /// not exported. /// /// See [Section 5.10 of RFC 9580] for details. /// /// [Section 5.10 of RFC 9580]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.10 // IMPORTANT: If you add fields to this struct, you need to explicitly // IMPORTANT: implement PartialEq, Eq, and Hash. #[derive(Clone, PartialEq, Eq, Hash)] pub struct Trust { pub(crate) common: packet::Common, value: Vec<u8>, } assert_send_and_sync!(Trust); impl From<Vec<u8>> for Trust { fn from(u: Vec<u8>) -> Self { Trust { common: Default::default(), value: u, } } } impl fmt::Display for Trust { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let trust = String::from_utf8_lossy(&self.value[..]); write!(f, "{}", trust) } } impl fmt::Debug for Trust { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.debug_struct("Trust") .field("value", &crate::fmt::hex::encode(&self.value)) .finish() } } impl Trust { /// Gets the trust packet's value. pub fn value(&self) -> &[u8] { self.value.as_slice() } } impl From<Trust> for Packet { fn from(s: Trust) -> Self { Packet::Trust(s) } } #[cfg(test)] impl Arbitrary for Trust { fn arbitrary(g: &mut Gen) -> Self { Vec::<u8>::arbitrary(g).into() } } #[cfg(test)] mod tests { use super::*; use crate::parse::Parse; use crate::serialize::MarshalInto; quickcheck! { fn roundtrip(p: Trust) -> bool { let q = Trust::from_bytes(&p.to_vec().unwrap()).unwrap(); assert_eq!(p, q); true } } } �������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/src/packet/unknown.rs���������������������������������������������������������0000644�0000000�0000000�00000023710�10461020230�0017045�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������use std::hash::{Hash, Hasher}; use std::cmp::Ordering; use crate::packet::Tag; use crate::packet; use crate::Packet; use crate::policy::HashAlgoSecurity; /// Holds an unknown packet. /// /// This is used by the parser to hold packets that it doesn't know /// how to process rather than abort. /// /// This packet effectively holds a binary blob. /// /// # A note on equality /// /// Two `Unknown` packets are considered equal if their tags and their /// bodies are equal. #[derive(Debug)] pub struct Unknown { /// CTB packet header fields. pub(crate) common: packet::Common, /// Packet tag. tag: Tag, /// Error that caused parsing or processing to abort. error: anyhow::Error, /// The unknown data packet is a container packet, but cannot /// store packets. /// /// This is written when serialized, and set by the packet parser /// if `buffer_unread_content` is used. container: packet::Container, } assert_send_and_sync!(Unknown); impl PartialEq for Unknown { fn eq(&self, other: &Unknown) -> bool { self.tag == other.tag && self.container == other.container } } impl Eq for Unknown { } impl Hash for Unknown { fn hash<H: Hasher>(&self, state: &mut H) { self.tag.hash(state); self.container.hash(state); } } impl Clone for Unknown { fn clone(&self) -> Self { Unknown { common: self.common.clone(), tag: self.tag, error: { // anyhow::Error isn't Clone, so we cannot, in // general, duplicate the error without losing // information. We can try to downcast to the most // likely errors, and clone them, but this can never // cover all possibilities. use std::io; if let Some(e) = self.error.downcast_ref::<crate::Error>() { e.clone().into() } else if let Some(e) = self.error.downcast_ref::<io::Error>() { if let Some(wrapped) = e.get_ref() { // The wrapped error isn't clone, so this // loses information here. This will always // be lossy, even once we changed this crate // to return concrete errors. io::Error::new(e.kind(), wrapped.to_string()).into() } else { io::Error::from(e.kind()).into() } } else { // Here, we lose information, but the conversion // was lossy before. crate::Error::InvalidOperation(self.error.to_string()) .into() } }, container: self.container.clone(), } } } impl Unknown { /// Returns a new `Unknown` packet. pub fn new(tag: Tag, error: anyhow::Error) -> Self { Unknown { common: Default::default(), tag, error, container: packet::Container::default_unprocessed(), } } /// The security requirements of the hash algorithm for /// self-signatures. /// /// A cryptographic hash algorithm usually has [three security /// properties]: pre-image resistance, second pre-image /// resistance, and collision resistance. If an attacker can /// influence the signed data, then the hash algorithm needs to /// have both second pre-image resistance, and collision /// resistance. If not, second pre-image resistance is /// sufficient. /// /// [three security properties]: https://en.wikipedia.org/wiki/Cryptographic_hash_function#Properties /// /// In general, an attacker may be able to influence third-party /// signatures. But direct key signatures, and binding signatures /// are only over data fully determined by signer. And, an /// attacker's control over self signatures over User IDs is /// limited due to their structure. /// /// These observations can be used to extend the life of a hash /// algorithm after its collision resistance has been partially /// compromised, but not completely broken. For more details, /// please refer to the documentation for [HashAlgoSecurity]. /// /// [HashAlgoSecurity]: crate::policy::HashAlgoSecurity pub fn hash_algo_security(&self) -> HashAlgoSecurity { HashAlgoSecurity::CollisionResistance } /// Gets the unknown packet's tag. pub fn tag(&self) -> Tag { self.tag } /// Sets the unknown packet's tag. pub fn set_tag(&mut self, tag: Tag) -> Tag { ::std::mem::replace(&mut self.tag, tag) } /// Gets the unknown packet's error. /// /// This is the error that caused parsing or processing to abort. pub fn error(&self) -> &anyhow::Error { &self.error } /// Sets the unknown packet's error. /// /// This is the error that caused parsing or processing to abort. pub fn set_error(&mut self, error: anyhow::Error) -> anyhow::Error { ::std::mem::replace(&mut self.error, error) } /// Returns the error. pub fn into_error(self) -> anyhow::Error { self.error } /// Best effort Ord implementation. /// /// The Cert canonicalization needs to order Unknown packets. /// However, due to potential streaming, Unknown cannot implement /// Eq. This is cheating a little, we simply ignore the streaming /// case. pub(crate) // For cert/mod.rs fn best_effort_cmp(&self, other: &Unknown) -> Ordering { self.tag.cmp(&other.tag).then_with(|| self.body().cmp(other.body())) } } impl_unprocessed_body_forwards!(Unknown); impl From<Unknown> for Packet { fn from(s: Unknown) -> Self { Packet::Unknown(s) } } impl std::convert::TryFrom<Packet> for Unknown { type Error = crate::Error; /// Tries to convert a packet to an `Unknown`. Returns an error /// if the given packet is a container packet (i.e. a compressed /// data packet or an encrypted data packet of any kind). fn try_from(p: Packet) -> std::result::Result<Self, Self::Error> { use packet::{Any, Body, Common, Container}; use crate::serialize::MarshalInto; let tag = p.tag(); // First, short-circuit happy and unhappy paths so that we // avoid copying the potentially large packet parser maps in // common. match &p { // Happy path. Packet::Unknown(_) => return Ok(p.downcast().expect("is an unknown")), // The container packets we flat-out refuse to convert. // The Unknown packet has an unprocessed body, and we // cannot recreate that from processed or structured // bodies. Packet::CompressedData(_) | Packet::SEIP(_) => return Err(Self::Error::InvalidOperation( format!("Cannot convert {} to unknown packets", tag))), _ => (), } // Now we copy the common bits that we'll need. let common = p.common().clone(); fn convert<V>(tag: Tag, common: Common, body: V) -> Result<Unknown, crate::Error> where V: MarshalInto, { let container = { let mut c = Container::default_unprocessed(); c.set_body(Body::Unprocessed( body.to_vec().expect("infallible serialization"))); c }; Ok(Unknown { container, common, tag, error: crate::Error::MalformedPacket( format!("Implicit conversion from {} to unknown packet", tag)).into(), }) } match p { // Happy path. Packet::Unknown(_) => unreachable!("handled above"), // These packets convert infallibly. Packet::Signature(v) => convert(tag, common, v), Packet::OnePassSig(v) => convert(tag, common, v), Packet::PublicKey(v) => convert(tag, common, v), Packet::PublicSubkey(v) => convert(tag, common, v), Packet::SecretKey(v) => convert(tag, common, v), Packet::SecretSubkey(v) => convert(tag, common, v), Packet::Marker(v) => convert(tag, common, v), Packet::Trust(v) => convert(tag, common, v), Packet::UserID(v) => convert(tag, common, v), Packet::UserAttribute(v) => convert(tag, common, v), Packet::PKESK(v) => convert(tag, common, v), Packet::SKESK(v) => convert(tag, common, v), #[allow(deprecated)] Packet::MDC(v) => convert(tag, common, v), Packet::Padding(v) => convert(tag, common, v), // XXX: can we do better like for the Literal? // Here we can avoid copying the body. Packet::Literal(mut v) => { let container = { let mut c = Container::default_unprocessed(); // Get v's body out without copying. c.set_body(Body::Unprocessed(v.set_body( Vec::with_capacity(0)))); c }; let common = v.common.clone(); // XXX why can't I decompose `p`? Ok(Unknown { container, common, tag, error: crate::Error::MalformedPacket( format!("Implicit conversion from {} to unknown packet", tag)).into(), }) }, // The container packets we flat-out refuse to convert. // The Unknown packet has an unprocessed body, and we // cannot recreate that from processed or structured // bodies. Packet::CompressedData(_) | Packet::SEIP(_) => unreachable!("handled above"), } } } ��������������������������������������������������������sequoia-openpgp-2.0.0/src/packet/user_attribute.rs��������������������������������������������������0000644�0000000�0000000�00000032403�10461020230�0020406�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������//! User Attribute packets and subpackets. //! //! See [Section 5.12 of RFC 9580] for details. //! //! [Section 5.12 of RFC 9580]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.12 use std::fmt; #[cfg(test)] use quickcheck::{Arbitrary, Gen}; use buffered_reader::BufferedReader; use crate::Error; use crate::Result; use crate::packet::{ self, header::BodyLength, }; use crate::Packet; use crate::policy::HashAlgoSecurity; use crate::serialize::Marshal; use crate::serialize::MarshalInto; /// Holds a UserAttribute packet. /// /// See [Section 5.12 of RFC 9580] for details. /// /// [Section 5.12 of RFC 9580]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.12 // IMPORTANT: If you add fields to this struct, you need to explicitly // IMPORTANT: implement PartialEq, Eq, and Hash. #[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct UserAttribute { /// CTB packet header fields. pub(crate) common: packet::Common, /// The user attribute. value: Vec<u8>, } assert_send_and_sync!(UserAttribute); impl From<Vec<u8>> for UserAttribute { fn from(u: Vec<u8>) -> Self { UserAttribute { common: Default::default(), value: u, } } } impl fmt::Debug for UserAttribute { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.debug_struct("UserAttribute") .field("value (bytes)", &self.value.len()) .finish() } } impl UserAttribute { /// Returns a new `UserAttribute` packet. /// /// Note: a valid UserAttribute has at least one subpacket. pub fn new(subpackets: &[Subpacket]) -> Result<Self> { let mut value = Vec::with_capacity( subpackets.iter().fold(0, |l, s| l + s.serialized_len())); for s in subpackets { s.serialize(&mut value)? } Ok(UserAttribute { common: Default::default(), value }) } /// The security requirements of the hash algorithm for /// self-signatures. /// /// A cryptographic hash algorithm usually has [three security /// properties]: pre-image resistance, second pre-image /// resistance, and collision resistance. If an attacker can /// influence the signed data, then the hash algorithm needs to /// have both second pre-image resistance, and collision /// resistance. If not, second pre-image resistance is /// sufficient. /// /// [three security properties]: https://en.wikipedia.org/wiki/Cryptographic_hash_function#Properties /// /// In general, an attacker may be able to influence third-party /// signatures. But direct key signatures, and binding signatures /// are only over data fully determined by signer. And, an /// attacker's control over self signatures over User IDs is /// limited due to their structure. /// /// These observations can be used to extend the life of a hash /// algorithm after its collision resistance has been partially /// compromised, but not completely broken. For more details, /// please refer to the documentation for [HashAlgoSecurity]. /// /// [HashAlgoSecurity]: crate::policy::HashAlgoSecurity pub fn hash_algo_security(&self) -> HashAlgoSecurity { HashAlgoSecurity::CollisionResistance } /// Gets the user attribute packet's raw, unparsed value. /// /// Most likely you will want to use [`subpackets()`] to iterate /// over the subpackets. /// /// [`subpackets()`]: UserAttribute::subpackets() pub fn value(&self) -> &[u8] { self.value.as_slice() } /// Gets a mutable reference to the user attribute packet's raw /// value. pub fn value_mut(&mut self) -> &mut Vec<u8> { &mut self.value } /// Iterates over the subpackets. pub fn subpackets(&self) -> SubpacketIterator { SubpacketIterator { reader: buffered_reader::Memory::new(&self.value[..]), } } } impl From<UserAttribute> for Packet { fn from(s: UserAttribute) -> Self { Packet::UserAttribute(s) } } #[cfg(test)] impl Arbitrary for UserAttribute { fn arbitrary(g: &mut Gen) -> Self { use crate::arbitrary_helper::gen_arbitrary_from_range; UserAttribute::new( &(0..gen_arbitrary_from_range(1..10, g)) .map(|_| Subpacket::arbitrary(g)) .collect::<Vec<_>>()[..]).unwrap() } } /// Iterates over subpackets. pub struct SubpacketIterator<'a> { reader: buffered_reader::Memory<'a, ()>, } assert_send_and_sync!(SubpacketIterator<'_>); impl<'a> Iterator for SubpacketIterator<'a> { type Item = Result<Subpacket>; fn next(&mut self) -> Option<Self::Item> { let length = match BodyLength::parse_new_format(&mut self.reader) { Ok(BodyLength::Full(l)) => l, Ok(BodyLength::Partial(_)) | Ok(BodyLength::Indeterminate) => return Some(Err(Error::MalformedPacket( "Partial or Indeterminate length of subpacket".into()) .into())), Err(e) => if e.kind() == ::std::io::ErrorKind::UnexpectedEof { return None; } else { return Some(Err(e.into())); }, }; let raw = match self.reader.data_consume_hard(length as usize) { Ok(r) => &r[..length as usize], Err(e) => return Some(Err(e.into())), }; if raw.is_empty() { return Some(Err(Error::MalformedPacket( "Subpacket without type octet".into()).into())); } let typ = raw[0]; let raw = &raw[1..]; match typ { // Image. 1 => if raw.len() >= 16 && raw[..3] == [0x10, 0x00, 0x01] && raw[4..16].iter().all(|b| *b == 0) { let image_kind = raw[3]; Some(Ok(Subpacket::Image(match image_kind { 1 => Image::JPEG(Vec::from(&raw[16..]).into_boxed_slice()), n @ 100..=110 => Image::Private( n, Vec::from(&raw[16..]).into_boxed_slice()), n => Image::Unknown( n, Vec::from(&raw[16..]).into_boxed_slice()), }))) } else { Some(Err(Error::MalformedPacket( "Malformed image subpacket".into()).into())) }, n => Some(Ok(Subpacket::Unknown( n, Vec::from(raw).into_boxed_slice()))), } } } /// User Attribute subpackets. /// /// See [Section 5.12 of RFC 9580] for details. /// /// [Section 5.12 of RFC 9580]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.12 #[derive(Clone, Debug, PartialEq, Eq, Hash)] #[non_exhaustive] pub enum Subpacket { /// Image subpacket. /// /// See [Section 5.12.1 of RFC 9580] for details. /// /// [Section 5.12.1 of RFC 9580]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.12.1 Image(Image), /// Unknown subpacket. Unknown(u8, Box<[u8]>), } assert_send_and_sync!(Subpacket); #[cfg(test)] impl Arbitrary for Subpacket { fn arbitrary(g: &mut Gen) -> Self { use crate::arbitrary_helper::gen_arbitrary_from_range; match gen_arbitrary_from_range(0..3, g) { 0 => Subpacket::Image(Image::arbitrary(g)), 1 => Subpacket::Unknown( 0, Vec::<u8>::arbitrary(g).into_boxed_slice() ), 2 => Subpacket::Unknown( gen_arbitrary_from_range(2..256, g) as u8, Vec::<u8>::arbitrary(g).into_boxed_slice() ), _ => unreachable!(), } } } /// Image subpacket. /// /// See [Section 5.12.1 of RFC 9580] for details. /// /// [Section 5.12.1 of RFC 9580]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.12.1 #[derive(Clone, Debug, PartialEq, Eq, Hash)] #[non_exhaustive] pub enum Image { /// A JPEG image format. JPEG(Box<[u8]>), /// Private, experimental image format. Private(u8, Box<[u8]>), /// Unknown image format. Unknown(u8, Box<[u8]>), } assert_send_and_sync!(Image); #[cfg(test)] impl Arbitrary for Image { fn arbitrary(g: &mut Gen) -> Self { use crate::arbitrary_helper::gen_arbitrary_from_range; match gen_arbitrary_from_range(0..5, g) { 0 => Image::JPEG( Vec::<u8>::arbitrary(g).into_boxed_slice() ), 1 => Image::Unknown( gen_arbitrary_from_range(2..100, g), Vec::<u8>::arbitrary(g).into_boxed_slice() ), 2 => Image::Private( gen_arbitrary_from_range(100..111, g), Vec::<u8>::arbitrary(g).into_boxed_slice() ), 3 => Image::Unknown( 0, Vec::<u8>::arbitrary(g).into_boxed_slice() ), 4 => Image::Unknown( gen_arbitrary_from_range(111..256, g) as u8, Vec::<u8>::arbitrary(g).into_boxed_slice() ), _ => unreachable!(), } } } #[cfg(test)] mod tests { use super::*; use crate::parse::Parse; quickcheck! { fn roundtrip(p: UserAttribute) -> bool { let buf = p.to_vec().unwrap(); assert_eq!(p.serialized_len(), buf.len()); let q = UserAttribute::from_bytes(&buf).unwrap(); assert_eq!(p, q); true } } quickcheck! { fn roundtrip_subpacket(sp: Subpacket) -> bool { let value = sp.to_vec().unwrap(); assert_eq!(sp.serialized_len(), value.len()); let ua = UserAttribute { common: Default::default(), value, }; let buf = ua.to_vec().unwrap(); let q = UserAttribute::from_bytes(&buf).unwrap(); let subpackets = q.subpackets().collect::<Vec<_>>(); assert_eq!(subpackets.len(), 1); assert_eq!(&sp, subpackets[0].as_ref().unwrap()); true } } quickcheck! { fn roundtrip_image(img: Image) -> bool { let mut body = img.to_vec().unwrap(); assert_eq!(img.serialized_len(), body.len()); let mut value = BodyLength::Full(1 + body.len() as u32).to_vec().unwrap(); value.push(1); // Image subpacket tag. value.append(&mut body); let ua = UserAttribute { common: Default::default(), value, }; let buf = ua.to_vec().unwrap(); let q = UserAttribute::from_bytes(&buf).unwrap(); let subpackets = q.subpackets().collect::<Vec<_>>(); assert_eq!(subpackets.len(), 1); if let Ok(Subpacket::Image(i)) = &subpackets[0] { assert_eq!(&img, i); } else { panic!("expected image subpacket, got {:?}", subpackets[0]); } true } } #[test] fn image() { use crate::Packet; let p = Packet::from_bytes(" -----BEGIN PGP ARMORED FILE----- 0cFuwWwBEAABAQAAAAAAAAAAAAAAAP/Y/+AAEEpGSUYAAQEBASwBLAAA//4AE0Ny ZWF0ZWQgd2l0aCBHSU1Q/9sAQwADAgIDAgIDAwMDBAMDBAUIBQUEBAUKBwcGCAwK DAwLCgsLDQ4SEA0OEQ4LCxAWEBETFBUVFQwPFxgWFBgSFBUU/9sAQwEDBAQFBAUJ BQUJFA0LDRQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQU FBQUFBQUFBQU/8IAEQgAAQABAwERAAIRAQMRAf/EABQAAQAAAAAAAAAAAAAAAAAA AAj/xAAUAQEAAAAAAAAAAAAAAAAAAAAA/9oADAMBAAIQAxAAAAFUn//EABQQAQAA AAAAAAAAAAAAAAAAAAD/2gAIAQEAAQUCf//EABQRAQAAAAAAAAAAAAAAAAAAAAD/ 2gAIAQMBAT8Bf//EABQRAQAAAAAAAAAAAAAAAAAAAAD/2gAIAQIBAT8Bf//EABQQ AQAAAAAAAAAAAAAAAAAAAAD/2gAIAQEABj8Cf//EABQQAQAAAAAAAAAAAAAAAAAA AAD/2gAIAQEAAT8hf//aAAwDAQACAAMAAAAQn//EABQRAQAAAAAAAAAAAAAAAAAA AAD/2gAIAQMBAT8Qf//EABQRAQAAAAAAAAAAAAAAAAAAAAD/2gAIAQIBAT8Qf//E ABQQAQAAAAAAAAAAAAAAAAAAAAD/2gAIAQEAAT8Qf//Z =nUQg -----END PGP ARMORED FILE----- ").unwrap(); let subpackets: Vec<_> = if let Packet::UserAttribute(ua) = p { ua.subpackets().collect() } else { panic!("Expected a UserAttribute, got: {:?}", p); }; assert_eq!(subpackets.len(), 1); if let Ok(Subpacket::Image(Image::JPEG(img))) = &subpackets[0] { assert_eq!(img.len(), 539 /* Image data */); assert_eq!(&img[6..10], b"JFIF"); assert_eq!(&img[24..41], b"Created with GIMP"); } else { panic!("Expected JPEG, got {:?}", &subpackets[0]); } if let Ok(Subpacket::Image(img)) = &subpackets[0] { let buf = img.to_vec().unwrap(); assert_eq!(buf.len(), 539 + 16 /* Image header */); assert_eq!(img.serialized_len(), 539 + 16 /* Image header */); } else { unreachable!("decomposed fine before"); } if let Ok(img) = &subpackets[0] { let buf = img.to_vec().unwrap(); assert_eq!(buf.len(), 539 + 16 + 3 /* Subpacket header */); assert_eq!(img.serialized_len(), 539 + 16 + 3 /* Subpacket header */); } else { unreachable!("decomposed fine before"); } } } �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/src/packet/userid.rs����������������������������������������������������������0000644�0000000�0000000�00000150357�10461020230�0016651�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������use std::borrow::Cow; use std::fmt; use std::str; use std::hash::{Hash, Hasher}; use std::cmp::Ordering; use std::sync::OnceLock; #[cfg(test)] use quickcheck::{Arbitrary, Gen}; use regex::Regex; use crate::Result; use crate::packet; use crate::Packet; use crate::Error; use crate::policy::HashAlgoSecurity; /// A conventionally parsed UserID. #[derive(Clone, Debug)] pub struct ConventionallyParsedUserID { userid: String, name: Option<(usize, usize)>, comment: Option<(usize, usize)>, email: Option<(usize, usize)>, uri: Option<(usize, usize)>, } assert_send_and_sync!(ConventionallyParsedUserID); impl ConventionallyParsedUserID { /// Parses the userid according to the usual conventions. pub fn new<S>(userid: S) -> Result<Self> where S: Into<String> { Ok(Self::parse(userid.into())?) } /// Returns the User ID's name component, if any. pub fn name(&self) -> Option<&str> { self.name.map(|(s, e)| &self.userid[s..e]) } /// Returns the User ID's comment field, if any. pub fn comment(&self) -> Option<&str> { self.comment.map(|(s, e)| &self.userid[s..e]) } /// Returns the User ID's email component, if any. pub fn email(&self) -> Option<&str> { self.email.map(|(s, e)| &self.userid[s..e]) } /// Returns the User ID's URI component, if any. /// /// Note: the URI is returned as is; dot segments are not removed, /// escape sequences are not unescaped, etc. pub fn uri(&self) -> Option<&str> { self.uri.map(|(s, e)| &self.userid[s..e]) } fn parse(userid: String) -> Result<Self> { fn user_id_parser() -> &'static Regex { use std::sync::OnceLock; static USER_ID_PARSER: OnceLock<Regex> = OnceLock::new(); USER_ID_PARSER.get_or_init(|| { // Whitespace. let ws_bare = " "; let ws = format!("[{}]", ws_bare); let optional_ws = format!("(?:{}*)", ws); // Specials minus ( and ). let comment_specials_bare = r#"<>\[\]:;@\\,.""#; let _comment_specials = format!("[{}]", comment_specials_bare); let atext_specials_bare = r#"()\[\]:;@\\,.""#; let _atext_specials = format!("[{}]", atext_specials_bare); // "Text" let atext_bare = "-A-Za-z0-9!#$%&'*+/=?^_`{|}~\u{80}-\u{10ffff}"; let atext = format!("[{}]", atext_bare); // An atext with dots and the added restriction that // it may not start or end with a dot. let dot_atom_text = format!(r"(?:{}+(?:\.{}+)*)", atext, atext); let name_char_start = format!("[{}{}]", atext_bare, atext_specials_bare); let name_char_rest = format!("[{}{}{}]", atext_bare, atext_specials_bare, ws_bare); // We need to minimize the match as otherwise we // swallow any comment. let name = format!("(?:{}{}*?)", name_char_start, name_char_rest); let comment_char = format!("[{}{}{}]", atext_bare, comment_specials_bare, ws_bare); let comment = |prefix| { format!(r#"(?:\({}(?P<{}_comment>{}*?){}\))"#, optional_ws, prefix, comment_char, optional_ws) }; let addr_spec = format!("(?:{}@{})", dot_atom_text, dot_atom_text); let uri = |prefix| { // The regex suggested from the RFC: // // ^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))? // ^schema ^authority ^path ^query ^fragment // // Since only the path component is required, and // the path matches everything but the '?' and '#' // characters, this regular expression will match // almost any string. // // This regular expression is good for picking // apart strings that are known to be URIs. But, // we want to detect URIs and distinguish them // from things that are almost certainly not URIs, // like email addresses. // // As such, we require the URI to have a // well-formed schema, and the schema must be // followed by a non-empty component. Further, we // restrict the alphabet to approximately what the // grammar permits. // Looking at the productions for the schema, // authority, path, query, and fragment // components, we can distil the following useful // alphabets (the symbols are drawn from the // following pct-encoded, unreserved, gen-delims, // sub-delims, pchar, and IP-literal productions): let symbols = "-{}0-9._~%!$&'()*+,;=:@\\[\\]"; let ascii_alpha = "a-zA-Z"; let utf8_alpha = "a-zA-Z\u{80}-\u{10ffff}"; // We strictly match the schema production: // // scheme = ALPHA *( ALPHA / DIGIT / "+" / "-" / "." ) let schema = format!("(?:[{}][-+.{}0-9]*:)", ascii_alpha, ascii_alpha); // The symbols that can occur in a fragment are a // superset of those that can occur in a query and // its delimiters. Likewise, the symbols that can // occur in a query are a superset of those that // can occur in a path and its delimiters. The // symbols that can occur in a path are *almost* a // subset of those that can occur in an authority: // '[' and ']' can occur in an authority component // (via the IP-literal production, e.g., // '[2001:db8::7]'), but not in a path. But, URI // parsers appear to accept '[' and ']' as part of // a path. So, we accept them too. // // Given this, a fragment matches all components // and everything that precedes it. Since we // don't need to distinguish the individual parts // here, matching what follows the schema in a URI // is straightforward: let rest = format!("(?:[{}{}/\\?#]+)", symbols, utf8_alpha); format!("(?P<{}_uri>{}{})", prefix, schema, rest) }; let raw_addr_spec = format!("(?P<raw_addr_spec>{})", addr_spec); let raw_uri = format!("(?:{})", uri("raw")); // whitespace is ignored. It is allowed (but not // required) at the start and between components, but // it is not allowed after the closing '>'. space is // not allowed. let wrapped_addr_spec = format!("{}(?P<wrapped_addr_spec_name>{})?{}\ (?:{})?{}\ <(?P<wrapped_addr_spec>{})>", optional_ws, name, optional_ws, comment("wrapped_addr_spec"), optional_ws, addr_spec); let wrapped_uri = format!("{}(?P<wrapped_uri_name>{})?{}\ (?:{})?{}\ <(?:{})>", optional_ws, name, optional_ws, comment("wrapped_uri"), optional_ws, uri("wrapped")); let bare_name = format!("{}(?P<bare_name>{}){}\ (?:{})?{}", optional_ws, name, optional_ws, comment("bare"), optional_ws); // Note: bare-name has to come after addr-spec-raw as // prefer addr-spec-raw to bare-name when the match is // ambiguous. let pgp_uid_convention = format!("^(?:{}|{}|{}|{}|{})$", raw_addr_spec, raw_uri, wrapped_addr_spec, wrapped_uri, bare_name); Regex::new(&pgp_uid_convention).unwrap() }) } // The regex is anchored at the start and at the end so we // have either 0 or 1 matches. if let Some(cap) = user_id_parser().captures_iter(&userid).next() { let to_range = |m: regex::Match| (m.start(), m.end()); // We need to figure out which branch matched. Match on a // required capture for each branch. if let Some(email) = cap.name("raw_addr_spec") { // raw-addr-spec let email = Some(to_range(email)); Ok(ConventionallyParsedUserID { userid, name: None, comment: None, email, uri: None, }) } else if let Some(uri) = cap.name("raw_uri") { // raw-uri let uri = Some(to_range(uri)); Ok(ConventionallyParsedUserID { userid, name: None, comment: None, email: None, uri, }) } else if let Some(email) = cap.name("wrapped_addr_spec") { // wrapped-addr-spec let name = cap.name("wrapped_addr_spec_name").map(to_range); let comment = cap.name("wrapped_addr_spec_comment").map(to_range); let email = Some(to_range(email)); Ok(ConventionallyParsedUserID { userid, name, comment, email, uri: None, }) } else if let Some(uri) = cap.name("wrapped_uri") { // uri-wrapped let name = cap.name("wrapped_uri_name").map(to_range); let comment = cap.name("wrapped_uri_comment").map(to_range); let uri = Some(to_range(uri)); Ok(ConventionallyParsedUserID { userid, name, comment, email: None, uri, }) } else if let Some(name) = cap.name("bare_name") { // name-bare let name = to_range(name); let comment = cap.name("bare_comment").map(to_range); Ok(ConventionallyParsedUserID { userid, name: Some(name), comment, email: None, uri: None, }) } else { panic!("Unexpected result"); } } else { Err(Error::InvalidArgument( "Failed to parse UserID".into()).into()) } } } /// Holds a UserID packet. /// /// The standard imposes no structure on UserIDs, but suggests to /// follow [RFC 2822]. See [Section 5.11 of RFC 9580] for details. /// In practice though, implementations do not follow [RFC 2822], or /// do not even help their users in producing well-formed User IDs. /// Experience has shown that parsing User IDs using [RFC 2822] does /// not work, so we are taking a more pragmatic approach and define /// what we call *Conventional User IDs*. /// /// [RFC 2822]: https://tools.ietf.org/html/rfc2822 /// [Section 5.11 of RFC 9580]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.11 /// /// Using this definition, we provide methods to extract the [name], /// [comment], [email address], or [URI] from `UserID` packets. /// Furthermore, we provide a way to [canonicalize the email address] /// found in a `UserID` packet. We provide [two] [constructors] that /// create well-formed User IDs from email address, and optional name /// and comment. /// /// [name]: UserID::name() /// [comment]: UserID::comment() /// [email address]: UserID::email() /// [URI]: UserID::uri() /// [canonicalize the email address]: UserID::email_normalized() /// [two]: UserID::from_address() /// [constructors]: UserID::from_unchecked_address() /// /// # Conventional User IDs /// /// Informally, conventional User IDs are of the form: /// /// - `First Last (Comment) <name@example.org>` /// - `First Last <name@example.org>` /// - `First Last` /// - `name@example.org <name@example.org>` /// - `<name@example.org>` /// - `name@example.org` /// /// - `Name (Comment) <scheme://hostname/path>` /// - `Name (Comment) <mailto:user@example.org>` /// - `Name <scheme://hostname/path>` /// - `<scheme://hostname/path>` /// - `scheme://hostname/path` /// /// Names consist of UTF-8 non-control characters and may include /// punctuation. For instance, the following names are valid: /// /// - `Acme Industries, Inc.` /// - `Michael O'Brian` /// - `Smith, John` /// - `e.e. cummings` /// /// (Note: according to [RFC 2822] and its successors, all of these /// would need to be quoted. Conventionally, no implementation quotes /// names.) /// /// Conventional User IDs are UTF-8. [RFC 2822] only covers US-ASCII /// and allows character set switching using [RFC 2047]. For example, /// an [RFC 2822] parser would parse: /// /// - <code>Bj=?utf-8?q?=C3=B6?=rn Bj=?utf-8?q?=C3=B6?=rnson</code> /// /// [RFC 2047]: https://tools.ietf.org/html/rfc2047 /// /// "Björn Björnson". Nobody uses this in practice, and, as such, /// this extension is not supported by this parser. /// /// Comments can include any UTF-8 text except parentheses. Thus, the /// following is not a valid comment even though the parentheses are /// balanced: /// /// - `(foo (bar))` /// /// URIs /// ---- /// /// The URI parser recognizes URIs using a regular expression similar /// to the one recommended in [RFC 3986] with the following extensions /// and restrictions: /// /// - UTF-8 characters are in the range `\u{80}-\u{10ffff}` are /// allowed wherever percent-encoded characters are allowed (i.e., /// everywhere but the schema). /// /// - The scheme component and its trailing `:` are required. /// /// - The URI must have an authority component (`//domain`) or a /// path component (`/path/to/resource`). /// /// - Although the RFC does not allow it, in practice, the `[` and /// `]` characters are allowed wherever percent-encoded characters /// are allowed (i.e., everywhere but the schema). /// /// URIs are neither normalized nor interpreted. For instance, dot /// segments are not removed, escape sequences are not decoded, etc. /// /// Note: the recommended regular expression is less strict than the /// grammar. For instance, a percent encoded character must consist /// of three characters: the percent character followed by two hex /// digits. The parser that we use does not enforce this either. /// /// [RFC 3986]: https://tools.ietf.org/html/rfc3986 /// /// Formal Grammar /// -------------- /// /// Formally, the following grammar is used to decompose a User ID: /// /// ```text /// WS = 0x20 (space character) /// /// comment-specials = "<" / ">" / ; RFC 2822 specials - "(" and ")" /// "[" / "]" / /// ":" / ";" / /// "@" / "\" / /// "," / "." / /// DQUOTE /// /// atext-specials = "(" / ")" / ; RFC 2822 specials - "<" and ">". /// "[" / "]" / /// ":" / ";" / /// "@" / "\" / /// "," / "." / /// DQUOTE /// /// atext = ALPHA / DIGIT / ; Any character except controls, /// "!" / "#" / ; SP, and specials. /// "$" / "%" / ; Used for atoms /// "&" / "'" / /// "*" / "+" / /// "-" / "/" / /// "=" / "?" / /// "^" / "_" / /// "`" / "{" / /// "|" / "}" / /// "~" / /// \u{80}-\u{10ffff} ; Non-ascii, non-control UTF-8 /// /// dot_atom_text = 1*atext *("." *atext) /// /// name-char-start = atext / atext-specials /// /// name-char-rest = atext / atext-specials / WS /// /// name = name-char-start *name-char-rest /// /// comment-char = atext / comment-specials / WS /// /// comment-content = *comment-char /// /// comment = "(" *WS comment-content *WS ")" /// /// addr-spec = dot-atom-text "@" dot-atom-text /// /// uri = See [RFC 3986] and the note on URIs above. /// /// pgp-uid-convention = addr-spec / /// uri / /// *WS [name] *WS [comment] *WS "<" addr-spec ">" / /// *WS [name] *WS [comment] *WS "<" uri ">" / /// *WS name *WS [comment] *WS /// ``` pub struct UserID { /// CTB packet header fields. pub(crate) common: packet::Common, /// The user id. /// /// According to [Section 5.11 of RFC 9580], the text is by convention UTF-8 encoded /// and in "mail name-addr" form, i.e., "Name (Comment) /// <email@example.com>". /// /// [Section 5.11 of RFC 9580]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.11 /// /// Use `UserID::default()` to get a UserID with a default settings. value: Cow<'static, [u8]>, hash_algo_security: OnceLock<HashAlgoSecurity>, parsed: OnceLock<ConventionallyParsedUserID>, } assert_send_and_sync!(UserID); impl UserID { /// Returns a User ID. /// /// This is equivalent to using `UserID::from`, but the function /// is constant, and the slice must have a static lifetime. /// /// # Examples /// /// ``` /// use sequoia_openpgp::packet::UserID; /// /// const TRUST_ROOT_USERID: UserID /// = UserID::from_static_bytes(b"Local Trust Root"); /// ``` pub const fn from_static_bytes(u: &'static [u8]) -> Self { UserID { common: packet::Common::new(), hash_algo_security: OnceLock::new(), value: Cow::Borrowed(u), parsed: OnceLock::new(), } } } impl From<Vec<u8>> for UserID { fn from(u: Vec<u8>) -> Self { UserID { common: Default::default(), hash_algo_security: Default::default(), value: Cow::Owned(u), parsed: Default::default(), } } } impl From<&[u8]> for UserID { fn from(u: &[u8]) -> Self { u.to_vec().into() } } impl<'a> From<&'a str> for UserID { fn from(u: &'a str) -> Self { u.as_bytes().into() } } impl From<String> for UserID { fn from(u: String) -> Self { u.into_bytes().into() } } impl<'a> From<Cow<'a, str>> for UserID { fn from(u: Cow<'a, str>) -> Self { match u { Cow::Owned(u) => u.into(), Cow::Borrowed(u) => u.into(), } } } impl fmt::Display for UserID { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let userid = String::from_utf8_lossy(&self.value[..]); write!(f, "{}", userid) } } impl fmt::Debug for UserID { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let userid = String::from_utf8_lossy(&self.value[..]); f.debug_struct("UserID") .field("value", &userid) .finish() } } impl PartialEq for UserID { fn eq(&self, other: &UserID) -> bool { self.common == other.common && self.value == other.value } } impl Eq for UserID { } impl PartialOrd for UserID { fn partial_cmp(&self, other: &Self) -> Option<Ordering> { Some(self.cmp(other)) } } impl Ord for UserID { fn cmp(&self, other: &Self) -> Ordering { self.common.cmp(&other.common).then_with( || self.value.cmp(&other.value)) } } impl Hash for UserID { fn hash<H: Hasher>(&self, state: &mut H) { // We hash only the data; the cache does not implement hash. self.common.hash(state); self.value.hash(state); } } impl Clone for UserID { fn clone(&self) -> Self { UserID { common: self.common.clone(), hash_algo_security: self.hash_algo_security.clone(), value: self.value.clone(), parsed: if let Some(p) = self.parsed.get() { p.clone().into() } else { OnceLock::new() }, } } } impl UserID { fn assemble(name: Option<&str>, comment: Option<&str>, address: &str, check_address: bool) -> Result<Self> { let mut value = String::with_capacity(64); // Make sure the individual components are valid. if let Some(ref name) = name { match ConventionallyParsedUserID::new(name.to_string()) { Err(err) => return Err(err.context(format!( "Validating name ({:?})", name))), Ok(p) => { if !(p.name().is_some() && p.comment().is_none() && p.email().is_none()) { return Err(Error::InvalidArgument( format!("Invalid name ({:?})", name)).into()); } } } value.push_str(name); } if let Some(ref comment) = comment { match ConventionallyParsedUserID::new( format!("x ({})", comment)) { Err(err) => return Err(err.context(format!( "Validating comment ({:?})", comment))), Ok(p) => { if !(p.name().is_some() && p.comment().is_some() && p.email().is_none()) { return Err(Error::InvalidArgument( format!("Invalid comment ({:?})", comment)).into()); } } } if !value.is_empty() { value.push(' '); } value.push('('); value.push_str(comment); value.push(')'); } if check_address { match ConventionallyParsedUserID::new( format!("<{}>", address)) { Err(err) => return Err(err.context(format!( "Validating address ({:?})", address))), Ok(p) => { if !(p.name().is_none() && p.comment().is_none() && p.email().is_some()) { return Err(Error::InvalidArgument( format!("Invalid address address ({:?})", address)) .into()); } } } } if !value.is_empty() { value.push(' '); } value.push('<'); value.push_str(address); value.push('>'); if check_address { // Make sure the combined thing is valid. match ConventionallyParsedUserID::new(value.clone()) { Err(err) => return Err(err.context(format!( "Validating User ID ({:?})", value))), Ok(p) => { if !(p.name().is_none() == name.is_none() && p.comment().is_none() == comment.is_none() && p.email().is_some()) { return Err(Error::InvalidArgument( format!("Invalid User ID ({:?})", value)).into()); } } } } Ok(UserID::from(value)) } /// The security requirements of the hash algorithm for /// self-signatures. /// /// A cryptographic hash algorithm usually has [three security /// properties]: pre-image resistance, second pre-image /// resistance, and collision resistance. If an attacker can /// influence the signed data, then the hash algorithm needs to /// have both second pre-image resistance, and collision /// resistance. If not, second pre-image resistance is /// sufficient. /// /// [three security properties]: https://en.wikipedia.org/wiki/Cryptographic_hash_function#Properties /// /// In general, an attacker may be able to influence third-party /// signatures. But direct key signatures, and binding signatures /// are only over data fully determined by signer. And, an /// attacker's control over self signatures over User IDs is /// limited due to their structure. /// /// In the case of self signatures over User IDs, an attacker may /// be able to control the content of the User ID packet. /// However, unlike an image, there is no easy way to hide large /// amounts of arbitrary data (e.g., the 512 bytes needed by the /// [SHA-1 is a Shambles] attack) from the user. Further, normal /// User IDs are short and encoded using UTF-8. /// /// [SHA-1 is a Shambles]: https://sha-mbles.github.io/ /// /// These observations can be used to extend the life of a hash /// algorithm after its collision resistance has been partially /// compromised, but not completely broken. Specifically for the /// case of User IDs, we relax the requirement for strong /// collision resistance for self signatures over User IDs if: /// /// - The User ID is at most 96 bytes long, /// - It contains valid UTF-8, and /// - It doesn't contain a UTF-8 control character (this includes /// the NUL byte). /// /// /// For more details, please refer to the documentation for /// [HashAlgoSecurity]. /// /// [HashAlgoSecurity]: crate::policy::HashAlgoSecurity pub fn hash_algo_security(&self) -> HashAlgoSecurity { self.hash_algo_security .get_or_init(|| { UserID::determine_hash_algo_security(&self.value) }) .clone() } // See documentation for hash_algo_security. fn determine_hash_algo_security(u: &[u8]) -> HashAlgoSecurity { // SHA-1 has 64 byte (512-bit) blocks. A block and a half (96 // bytes) is more than enough for all but malicious users. if u.len() > 96 { return HashAlgoSecurity::CollisionResistance; } // Check that the User ID is valid UTF-8. match str::from_utf8(u) { Ok(s) => { // And doesn't contain control characters. if s.chars().any(char::is_control) { return HashAlgoSecurity::CollisionResistance; } } Err(_err) => { return HashAlgoSecurity::CollisionResistance; } } HashAlgoSecurity::SecondPreImageResistance } /// Constructs a User ID. /// /// This does a basic check and any necessary escaping to form a /// [conventional User ID]. /// /// Only the address is required. If a comment is supplied, then /// a name is also required. /// /// If you already have a User ID value, then you can just /// use `UserID::from()`. /// /// [conventional User ID]: #conventional-user-ids /// /// ``` /// # fn main() -> sequoia_openpgp::Result<()> { /// # use sequoia_openpgp as openpgp; /// # use openpgp::packet::UserID; /// assert_eq!(UserID::from_address( /// "John Smith", /// None, /// "boat@example.org")?.value(), /// &b"John Smith <boat@example.org>"[..]); /// /// assert_eq!(UserID::from_address( /// "John Smith", /// "Who is Advok?", /// "boat@example.org")?.value(), /// &b"John Smith (Who is Advok?) <boat@example.org>"[..]); /// # Ok(()) } /// ``` pub fn from_address<'a, N, C, E>(name: N, comment: C, email: E) -> Result<Self> where N: Into<Option<&'a str>>, C: Into<Option<&'a str>>, E: AsRef<str>, { Self::assemble(name.into().as_ref().map(|s| s.as_ref()), comment.into().as_ref().map(|s| s.as_ref()), email.as_ref(), true) } /// Constructs a User ID. /// /// This does a basic check and any necessary escaping to form a /// [conventional User ID] modulo the address, which is not /// checked. /// /// This is useful when you want to specify a URI instead of an /// email address. /// /// If you already have a User ID value, then you can just /// use `UserID::from()`. /// /// [conventional User ID]: #conventional-user-ids /// /// ``` /// # fn main() -> sequoia_openpgp::Result<()> { /// # use sequoia_openpgp as openpgp; /// # use openpgp::packet::UserID; /// assert_eq!(UserID::from_unchecked_address( /// "NAS", /// None, "ssh://host.example.org")?.value(), /// &b"NAS <ssh://host.example.org>"[..]); /// # Ok(()) } /// ``` pub fn from_unchecked_address<'a, N, C, E>(name: N, comment: C, address: E) -> Result<Self> where N: Into<Option<&'a str>>, C: Into<Option<&'a str>>, E: AsRef<str>, { Self::assemble(name.into().as_ref().map(|s| s.as_ref()), comment.into().as_ref().map(|s| s.as_ref()), address.as_ref(), false) } /// Gets the user ID packet's value. /// /// This returns the raw, uninterpreted value. See /// [`UserID::name`], [`UserID::email`], /// [`UserID::email_normalized`], [`UserID::uri`], and /// [`UserID::comment`] for how to extract parts of [conventional /// User ID]s. /// /// [`UserID::name`]: UserID::name() /// [`UserID::email`]: UserID::email() /// [`UserID::email_normalized`]: UserID::email_normalized() /// [`UserID::uri`]: UserID::uri() /// [`UserID::comment`]: UserID::comment() /// [conventional User ID]: #conventional-user-ids pub fn value(&self) -> &[u8] { &self.value } fn do_parse(&self) -> Result<&ConventionallyParsedUserID> { if let Some(p) = self.parsed.get() { return Ok(p); } let s = str::from_utf8(&self.value)?; let p = ConventionallyParsedUserID::parse(s.to_string())?; let _lost_race = self.parsed.set(p.clone()); Ok(self.parsed.get().expect("just set")) } /// Parses the User ID according to de facto conventions, and /// returns the name component, if any. /// /// See [conventional User ID] for more information. /// /// [conventional User ID]: #conventional-user-ids pub fn name(&self) -> Result<Option<&str>> { Ok(self.do_parse()?.name()) } /// Parses the User ID according to de facto conventions, and /// returns the comment field, if any. /// /// See [conventional User ID] for more information. /// /// [conventional User ID]: #conventional-user-ids pub fn comment(&self) -> Result<Option<&str>> { Ok(self.do_parse()?.comment()) } /// Parses the User ID according to de facto conventions, and /// returns the email address, if any. /// /// See [conventional User ID] for more information. /// /// [conventional User ID]: #conventional-user-ids pub fn email(&self) -> Result<Option<&str>> { Ok(self.do_parse()?.email()) } /// Parses the User ID according to de facto conventions, and /// returns the URI, if any. /// /// See [conventional User ID] for more information. /// /// [conventional User ID]: #conventional-user-ids pub fn uri(&self) -> Result<Option<&str>> { Ok(self.do_parse()?.uri()) } /// Returns a normalized version of the UserID's email address. /// /// Normalized email addresses are primarily needed when email /// addresses are compared. /// /// Note: normalized email addresses are still valid email /// addresses. /// /// This function normalizes an email address by doing [puny-code /// normalization] on the domain, and lowercasing the local part in /// the so-called [empty locale]. /// /// Note: this normalization procedure is the same as the /// normalization procedure recommended by [Autocrypt]. /// /// [puny-code normalization]: https://tools.ietf.org/html/rfc5891.html#section-4.4 /// [empty locale]: https://www.w3.org/International/wiki/Case_folding /// [Autocrypt]: https://autocrypt.org/level1.html#e-mail-address-canonicalization pub fn email_normalized(&self) -> Result<Option<String>> { match self.email()? { None => Ok(None), Some(address) => { let mut iter = address.split('@'); let localpart = iter.next().expect("Invalid email address"); let domain = iter.next().expect("Invalid email address"); assert!(iter.next().is_none(), "Invalid email address"); // Normalize Unicode in domains. let domain = idna::domain_to_ascii(domain) .map_err(|e| anyhow::Error::from(e) .context("punycode conversion failed"))?; // Join. let address = format!("{}@{}", localpart, domain); // Convert to lowercase without tailoring, i.e. without taking // any locale into account. See: // // - https://www.w3.org/International/wiki/Case_folding // - https://doc.rust-lang.org/std/primitive.str.html#method.to_lowercase // - http://www.unicode.org/versions/Unicode7.0.0/ch03.pdf#G33992 let address = address.to_lowercase(); Ok(Some(address)) } } } } impl From<UserID> for Packet { fn from(s: UserID) -> Self { Packet::UserID(s) } } #[cfg(test)] impl Arbitrary for UserID { fn arbitrary(g: &mut Gen) -> Self { Vec::<u8>::arbitrary(g).into() } } #[cfg(test)] mod tests { use super::*; use crate::parse::Parse; use crate::serialize::MarshalInto; quickcheck! { fn roundtrip(p: UserID) -> bool { let q = UserID::from_bytes(&p.to_vec().unwrap()).unwrap(); assert_eq!(p, q); true } } #[test] fn decompose() { tracer!(true, "decompose", 0); fn c(userid: &str, name: Option<&str>, comment: Option<&str>, email: Option<&str>, uri: Option<&str>) -> bool { assert!(email.is_none() || uri.is_none()); t!("userid: {}, name: {:?}, comment: {:?}, email: {:?}, uri: {:?}", userid, name, comment, email, uri); match ConventionallyParsedUserID::new(userid) { Ok(puid) => { let good = puid.name() == name && puid.comment() == comment && puid.email() == email && puid.uri() == uri; if ! good { t!("userid: {}", userid); t!(" -> {:?}", puid); t!(" {:?} {}= {:?}", puid.name(), if puid.name() == name { "=" } else { "!" }, name); t!(" {:?} {}= {:?}", puid.comment(), if puid.comment() == comment { "=" } else { "!" }, comment); t!(" {:?} {}= {:?}", puid.email(), if puid.email() == email { "=" } else { "!" }, email); t!(" {:?} {}= {:?}", puid.uri(), if puid.uri() == uri { "=" } else { "!" }, uri); t!(" -> BAD PARSE"); } good } Err(err) => { t!("userid: {} -> PARSE ERROR: {:?}", userid, err); false } } } let mut g = true; // Conventional User IDs: g &= c("First Last (Comment) <name@example.org>", Some("First Last"), Some("Comment"), Some("name@example.org"), None); g &= c("First Last <name@example.org>", Some("First Last"), None, Some("name@example.org"), None); g &= c("First Last", Some("First Last"), None, None, None); g &= c("name@example.org <name@example.org>", Some("name@example.org"), None, Some("name@example.org"), None); g &= c("<name@example.org>", None, None, Some("name@example.org"), None); g &= c("name@example.org", None, None, Some("name@example.org"), None); // Examples from dkg's mail: g &= c("Björn Björnson <bjoern@example.net>", Some("Björn Björnson"), None, Some("bjoern@example.net"), None); // We explicitly don't support RFC 2047 so the following is // correctly not escaped. g &= c("Bj=?utf-8?q?=C3=B6?=rn Bj=?utf-8?q?=C3=B6?=rnson \ <bjoern@example.net>", Some("Bj=?utf-8?q?=C3=B6?=rn Bj=?utf-8?q?=C3=B6?=rnson"), None, Some("bjoern@example.net"), None); g &= c("Acme Industries, Inc. <info@acme.example>", Some("Acme Industries, Inc."), None, Some("info@acme.example"), None); g &= c("Michael O'Brian <obrian@example.biz>", Some("Michael O'Brian"), None, Some("obrian@example.biz"), None); g &= c("Smith, John <jsmith@example.com>", Some("Smith, John"), None, Some("jsmith@example.com"), None); g &= c("mariag@example.org", None, None, Some("mariag@example.org"), None); g &= c("joe@example.net <joe@example.net>", Some("joe@example.net"), None, Some("joe@example.net"), None); g &= c("иван.сергеев@пример.рф", None, None, Some("иван.сергеев@пример.рф"), None); g &= c("Dörte@Sörensen.example.com", None, None, Some("Dörte@Sörensen.example.com"), None); // Some craziness. g &= c("Vorname Nachname, Dr.", Some("Vorname Nachname, Dr."), None, None, None); g &= c("Vorname Nachname, Dr. <dr@example.org>", Some("Vorname Nachname, Dr."), None, Some("dr@example.org"), None); // Only the last comment counts as a comment. The rest if // part of the name. g &= c("Foo (Bar) (Baz)", Some("Foo (Bar)"), Some("Baz"), None, None); // The same with extra whitespace. g &= c("Foo (Bar) (Baz)", Some("Foo (Bar)"), Some("Baz"), None, None); g &= c("Foo (Bar (Baz)", Some("Foo (Bar"), Some("Baz"), None, None); // Make sure whitespace is stripped. g &= c(" Name Last ( some comment ) <name@example.org>", Some("Name Last"), Some("some comment"), Some("name@example.org"), None); // Make sure an email is a comment is recognized as a comment. g &= c(" Name Last (email@example.org)", Some("Name Last"), Some("email@example.org"), None, None); // Quoting in the local part of the email address is not // allowed, but it is recognized as a name. That's fine. g &= c("\"user\"@example.org", Some("\"user\"@example.org"), None, None, None); // Even unbalanced quotes. g &= c("\"user@example.org", Some("\"user@example.org"), None, None, None); g &= c("Henry Ford (CEO) <henry@ford.com>", Some("Henry Ford"), Some("CEO"), Some("henry@ford.com"), None); g &= c("Thomas \"Tomakin\" (DHC) <thomas@clh.co.uk>", Some("Thomas \"Tomakin\""), Some("DHC"), Some("thomas@clh.co.uk"), None); g &= c("Aldous L. Huxley <huxley@old-world.org>", Some("Aldous L. Huxley"), None, Some("huxley@old-world.org"), None); // Some URIs. // Examples from https://tools.ietf.org/html/rfc3986#section-1.1.2 g &= c("<ftp://ftp.is.co.za/rfc/rfc1808.txt>", None, None, None, Some("ftp://ftp.is.co.za/rfc/rfc1808.txt")); g &= c("<http://www.ietf.org/rfc/rfc2396.txt>", None, None, None, Some("http://www.ietf.org/rfc/rfc2396.txt")); g &= c("<ldap://[2001:db8::7]/c=GB?objectClass?one>", None, None, None, Some("ldap://[2001:db8::7]/c=GB?objectClass?one")); g &= c("<mailto:John.Doe@example.com>", None, None, None, Some("mailto:John.Doe@example.com")); g &= c("<news:comp.infosystems.www.servers.unix>", None, None, None, Some("news:comp.infosystems.www.servers.unix")); g &= c("<tel:+1-816-555-1212>", None, None, None, Some("tel:+1-816-555-1212")); g &= c("<telnet://192.0.2.16:80/>", None, None, None, Some("telnet://192.0.2.16:80/")); g &= c("<urn:oasis:names:specification:docbook:dtd:xml:4.1.2>", None, None, None, Some("urn:oasis:names:specification:docbook:dtd:xml:4.1.2")); g &= c("Foo's ssh server <ssh://hostname>", Some("Foo's ssh server"), None, None, Some("ssh://hostname")); g &= c("Foo (ssh server) <ssh://hostname>", Some("Foo"), Some("ssh server"), None, Some("ssh://hostname")); g &= c("<ssh://hostname>", None, None, None, Some("ssh://hostname")); g &= c("Warez <ftp://127.0.0.1>", Some("Warez"), None, None, Some("ftp://127.0.0.1")); g &= c("ssh://hostname", None, None, None, Some("ssh://hostname")); g &= c("ssh:hostname", None, None, None, Some("ssh:hostname")); g &= c("Frank Füber <ssh://ïntérnätïònál.eu>", Some("Frank Füber"), None, None, Some("ssh://ïntérnätïònál.eu")); g &= c("ssh://ïntérnätïònál.eu", None, None, None, Some("ssh://ïntérnätïònál.eu")); g &= c("<foo://domain.org>", None, None, None, Some("foo://domain.org")); g &= c("<foo-bar://domain.org>", None, None, None, Some("foo-bar://domain.org")); g &= c("<foo+bar://domain.org>", None, None, None, Some("foo+bar://domain.org")); g &= c("<foo.bar://domain.org>", None, None, None, Some("foo.bar://domain.org")); g &= c("<foo.bar://domain.org#anchor?query>", None, None, None, Some("foo.bar://domain.org#anchor?query")); // Is it an email address or a URI? It should show up as a URI. g &= c("<foo://user:password@domain.org>", None, None, None, Some("foo://user:password@domain.org")); // Ports... g &= c("<foo://domain.org:348>", None, None, None, Some("foo://domain.org:348")); g &= c("<foo://domain.org:348/>", None, None, None, Some("foo://domain.org:348/")); // Some test vectors from // https://github.com/cweb/iri-tests/blob/master/iris.txt g &= c("<http://[:]>", None, None, None, Some("http://[:]")); g &= c("<http://2001:db8::1>", None, None, None, Some("http://2001:db8::1")); g &= c("<http://[www.google.com]/>", None, None, None, Some("http://[www.google.com]/")); g &= c("<http:////////user:@google.com:99?foo>", None, None, None, Some("http:////////user:@google.com:99?foo")); g &= c("<http:path>", None, None, None, Some("http:path")); g &= c("<http:/path>", None, None, None, Some("http:/path")); g &= c("<http:host>", None, None, None, Some("http:host")); g &= c("<http://user:pass@foo:21/bar;par?b#c>", None, None, None, Some("http://user:pass@foo:21/bar;par?b#c")); g &= c("<http:foo.com>", None, None, None, Some("http:foo.com")); g &= c("<http://f:/c>", None, None, None, Some("http://f:/c")); g &= c("<http://f:0/c>", None, None, None, Some("http://f:0/c")); g &= c("<http://f:00000000000000/c>", None, None, None, Some("http://f:00000000000000/c")); g &= c("<http://f:&#x000A;/c>", None, None, None, Some("http://f:&#x000A;/c")); g &= c("<http://f:fifty-two/c>", None, None, None, Some("http://f:fifty-two/c")); g &= c("<foo://>", None, None, None, Some("foo://")); g &= c("<http://a:b@c:29/d>", None, None, None, Some("http://a:b@c:29/d")); g &= c("<http::@c:29>", None, None, None, Some("http::@c:29")); g &= c("<http://&amp;a:foo(b]c@d:2/>", None, None, None, Some("http://&amp;a:foo(b]c@d:2/")); g &= c("<http://iris.test.ing/re&#x301;sume&#x301;/re&#x301;sume&#x301;.html>", None, None, None, Some("http://iris.test.ing/re&#x301;sume&#x301;/re&#x301;sume&#x301;.html")); g &= c("<http://google.com/foo[bar]>", None, None, None, Some("http://google.com/foo[bar]")); if !g { panic!("Parse error"); } } #[test] fn compose() { tracer!(true, "compose", 0); fn c(userid: &str, name: Option<&str>, comment: Option<&str>, email: Option<&str>, uri: Option<&str>) { assert!(email.xor(uri).is_some()); t!("userid: {}, name: {:?}, comment: {:?}, email: {:?}, uri: {:?}", userid, name, comment, email, uri); if let Some(email) = email { let uid = UserID::from_address(name, comment, email).unwrap(); assert_eq!(userid, String::from_utf8_lossy(uid.value())); } if let Some(uri) = uri { let uid = UserID::from_unchecked_address(name, comment, uri).unwrap(); assert_eq!(userid, String::from_utf8_lossy(uid.value())); } } // Conventional User IDs: c("First Last (Comment) <name@example.org>", Some("First Last"), Some("Comment"), Some("name@example.org"), None); c("First Last <name@example.org>", Some("First Last"), None, Some("name@example.org"), None); c("<name@example.org>", None, None, Some("name@example.org"), None); } // Make sure we can't parse non-conventional User IDs. #[test] fn decompose_non_conventional() { // Empty string is not allowed. assert!(ConventionallyParsedUserID::new("").is_err()); // Likewise, only whitespace. assert!(ConventionallyParsedUserID::new(" ").is_err()); assert!(ConventionallyParsedUserID::new(" ").is_err()); // Double dots are not allowed. assert!(ConventionallyParsedUserID::new( "<a..b@example.org>").is_err()); // Nor are dots at the start or end of the local part. assert!(ConventionallyParsedUserID::new( "<dr.@example.org>").is_err()); assert!(ConventionallyParsedUserID::new( "<.drb@example.org>").is_err()); assert!(ConventionallyParsedUserID::new( "<hallo> <hello@example.org>").is_err()); assert!(ConventionallyParsedUserID::new( "<hallo <hello@example.org>").is_err()); assert!(ConventionallyParsedUserID::new( "hallo> <hello@example.org>").is_err()); // No @. assert!(ConventionallyParsedUserID::new( "foo <example.org>").is_err()); // Two @s. assert!(ConventionallyParsedUserID::new( "Huxley <huxley@@old-world.org>").is_err()); // Unfortunately, the following is accepted as a name: // // assert!(ConventionallyParsedUserID::new( // "huxley@@old-world.org").is_err()); // No local part. assert!(ConventionallyParsedUserID::new( "foo <@example.org>").is_err()); // No leading/ending dot in the email address. assert!(ConventionallyParsedUserID::new( "<huxley@.old-world.org>").is_err()); assert!(ConventionallyParsedUserID::new( "<huxley@old-world.org.>").is_err()); // Unfortunately, the following are recognized as names: // // assert!(ConventionallyParsedUserID::new( // "huxley@.old-world.org").is_err()); // assert!(ConventionallyParsedUserID::new( // "huxley@old-world.org.").is_err()); // Need something in the local part. assert!(ConventionallyParsedUserID::new( "<@old-world.org>").is_err()); // Unfortunately, the following is recognized as a name: // // assert!(ConventionallyParsedUserID::new( // "@old-world.org").is_err()); // URI schemas must be ASCII. assert!(ConventionallyParsedUserID::new( "<über://domain.org>").is_err()); // Whitespace is not allowed. assert!(ConventionallyParsedUserID::new( "<http://some domain.org>").is_err()); } #[test] fn email_normalized() { fn c(value: &str, expected: &str) { let u = UserID::from(value); let got = u.email_normalized().unwrap().unwrap(); assert_eq!(expected, got); } c("Henry Ford (CEO) <henry@ford.com>", "henry@ford.com"); c("Henry Ford (CEO) <Henry@Ford.com>", "henry@ford.com"); c("Henry Ford (CEO) <Henry@Ford.com>", "henry@ford.com"); c("hans@bücher.tld", "hans@xn--bcher-kva.tld"); c("hANS@bücher.tld", "hans@xn--bcher-kva.tld"); } #[test] fn from_address() { assert_eq!(UserID::from_address(None, None, "foo@bar.com") .unwrap().value(), b"<foo@bar.com>"); assert!(UserID::from_address(None, None, "foo@@bar.com").is_err()); assert_eq!(UserID::from_address("Foo Q. Bar", None, "foo@bar.com") .unwrap().value(), b"Foo Q. Bar <foo@bar.com>"); } #[test] fn hash_algo_security() { // Acceptable. assert_eq!(UserID::from("Alice Lovelace <alice@lovelace.org>") .hash_algo_security(), HashAlgoSecurity::SecondPreImageResistance); // Embedded NUL. assert_eq!(UserID::from(&b"Alice Lovelace <alice@lovelace.org>\0"[..]) .hash_algo_security(), HashAlgoSecurity::CollisionResistance); assert_eq!( UserID::from( &b"Alice Lovelace <alice@lovelace.org>\0Hidden!"[..]) .hash_algo_security(), HashAlgoSecurity::CollisionResistance); // Long strings. assert_eq!( UserID::from(String::from_utf8(vec![b'a'; 90]).unwrap()) .hash_algo_security(), HashAlgoSecurity::SecondPreImageResistance); assert_eq!( UserID::from(String::from_utf8(vec![b'a'; 100]).unwrap()) .hash_algo_security(), HashAlgoSecurity::CollisionResistance); } } ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/src/packet_pile.rs������������������������������������������������������������0000644�0000000�0000000�00000111624�10461020230�0016361�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������use std::convert::TryFrom; use std::fmt; use std::vec; use std::iter::FromIterator; use std::iter::IntoIterator; use buffered_reader::BufferedReader; use crate::Result; use crate::Error; use crate::Packet; use crate::cert::Cert; use crate::packet::{self, Container}; use crate::parse::PacketParserResult; use crate::parse::PacketParserBuilder; use crate::parse::Parse; use crate::parse::Cookie; /// An unstructured [packet] sequence. /// /// To parse an OpenPGP packet stream into a `PacketPile`, you can use /// [`PacketParser`], [`PacketPileParser`], or /// [`PacketPile::from_file`] (or related routines). /// /// [packet]: https://www.rfc-editor.org/rfc/rfc9580.html#section-4 /// [`PacketParser`]: crate::parse::PacketParser /// [`PacketPileParser`]: crate::parse::PacketPileParser /// /// You can also convert a [`Cert`] into a `PacketPile` using /// `PacketPile::from`. Unlike serializing a `Cert`, this does not /// drop any secret key material. /// /// Normally, you'll want to convert the `PacketPile` to a `Cert` or a /// `Message`. /// /// # Examples /// /// This example shows how to modify packets in PacketPile using [`pathspec`]s. /// /// [`pathspec`]: PacketPile::path_ref() /// /// ```rust /// # use sequoia_openpgp as openpgp; /// use std::convert::TryFrom; /// use openpgp::{Packet, PacketPile}; /// use openpgp::packet::signature::Signature4; /// use openpgp::packet::Signature; /// use openpgp::cert::prelude::*; /// use openpgp::parse::Parse; /// use openpgp::serialize::Serialize; /// use openpgp::policy::StandardPolicy; /// use openpgp::crypto::mpi; /// use openpgp::types::RevocationStatus; /// /// # fn main() -> openpgp::Result<()> { /// let (cert, revocation) = CertBuilder::new().generate()?; /// /// let mut buffer = Vec::new(); /// cert.serialize(&mut buffer)?; /// let packet: Packet = revocation.into(); /// packet.serialize(&mut buffer)?; /// /// let policy = &StandardPolicy::new(); /// /// // Certificate is considered revoked because it is accompanied by its /// // revocation signature /// let pp: PacketPile = PacketPile::from_bytes(&buffer)?; /// let cert = Cert::try_from(pp)?; /// if let RevocationStatus::Revoked(_) = cert.revocation_status(policy, None) { /// // cert is considered revoked /// } /// # else { /// # unreachable!(); /// # } /// /// // Breaking the revocation signature changes certificate's status /// let mut pp: PacketPile = PacketPile::from_bytes(&buffer)?; /// if let Some(Packet::Signature(ref mut sig)) = pp.path_ref_mut(&[2]) { /// *sig = Signature4::new( /// sig.typ(), /// sig.pk_algo(), /// sig.hash_algo(), /// sig.hashed_area().clone(), /// sig.unhashed_area().clone(), /// *sig.digest_prefix(), /// // MPI is replaced with a dummy one /// mpi::Signature::RSA { /// s: mpi::MPI::from(vec![1, 2, 3]) /// }).into(); /// } /// /// let cert = Cert::try_from(pp)?; /// if let RevocationStatus::NotAsFarAsWeKnow = cert.revocation_status(policy, None) { /// // revocation signature is broken and the cert is not revoked /// assert_eq!(cert.bad_signatures().count(), 1); /// } /// # else { /// # unreachable!(); /// # } /// # Ok(()) /// # } /// ``` #[derive(PartialEq, Clone, Default)] pub struct PacketPile { /// At the top level, we have a sequence of packets, which may be /// containers. top_level: Container, } assert_send_and_sync!(PacketPile); impl fmt::Debug for PacketPile { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.debug_struct("PacketPile") .field("packets", &self.top_level.children_ref()) .finish() } } impl<'a> Parse<'a, PacketPile> for PacketPile { /// Deserializes the OpenPGP message stored in the file named by /// `path`. /// /// Although this method is easier to use to parse a sequence of /// OpenPGP packets than a [`PacketParser`] or a /// [`PacketPileParser`], this interface buffers the whole message /// in memory. Thus, the caller must be certain that the /// *deserialized* message is not too large. /// /// Note: this interface *does* buffer the contents of packets. /// /// [`PacketParser`]: crate::parse::PacketParser /// [`PacketPileParser`]: crate::parse::PacketPileParser fn from_buffered_reader<R>(reader: R) -> Result<PacketPile> where R: BufferedReader<Cookie> + 'a, { PacketPile::from_cookie_reader(reader.into_boxed()) } } impl std::str::FromStr for PacketPile { type Err = anyhow::Error; fn from_str(s: &str) -> std::result::Result<Self, Self::Err> { Self::from_bytes(s.as_bytes()) } } impl From<Vec<Packet>> for PacketPile { fn from(p: Vec<Packet>) -> Self { PacketPile { top_level: Container::from(p) } } } impl From<Packet> for PacketPile { fn from(p: Packet) -> Self { Self::from(vec![p]) } } impl FromIterator<Packet> for PacketPile { fn from_iter<I: IntoIterator<Item=Packet>>(iter: I) -> Self { Self::from(Vec::from_iter(iter)) } } impl From<PacketPile> for Vec<Packet> { fn from(pp: PacketPile) -> Self { pp.into_children().collect() } } impl PacketPile { /// Accessor for PacketPileParser. pub(crate) fn top_level_mut(&mut self) -> &mut Container { &mut self.top_level } /// Returns an error if operating on a non-container packet. fn error() -> crate::Error { crate::Error::InvalidOperation("Not a container packet".into()) } /// Pretty prints the message to stderr. /// /// This function is primarily intended for debugging purposes. #[cfg(test)] pub fn pretty_print(&self) { self.top_level.pretty_print(0); } /// Returns a reference to the packet at the location described by /// `pathspec`. /// /// `pathspec` is a slice of the form `[0, 1, 2]`. Each element /// is the index of packet in a container. Thus, the previous /// path specification means: return the third child of the second /// child of the first top-level packet. In other words, the /// starred packet in the following tree: /// /// ```text /// PacketPile /// / | \ /// 0 1 2 ... /// / \ /// / \ /// 0 1 ... /// / | \ ... /// 0 1 2 /// * /// ``` /// /// And, `[10]` means return the 11th top-level packet. /// /// Note: there is no packet at the root. Thus, the path `[]` /// returns None. /// /// # Examples /// /// ```rust /// # use sequoia_openpgp as openpgp; /// # use openpgp::{Result, types::{CompressionAlgorithm, DataFormat}, /// # Packet, PacketPile, packet::Literal, packet::CompressedData}; /// # fn main() -> Result<()> { /// # let mut lit = Literal::new(DataFormat::Unicode); /// # lit.set_body(b"test".to_vec()); /// # let packets = vec![lit.into()]; /// let pile = PacketPile::from(packets); /// /// if let Some(packet) = pile.path_ref(&[0]) { /// // There is a packet at this path. /// } /// # else { /// # unreachable!(); /// # } /// /// if let None = pile.path_ref(&[0, 1, 2]) { /// // But none here. /// } /// # else { /// # unreachable!(); /// # } /// # Ok(()) /// # } /// ``` pub fn path_ref(&self, pathspec: &[usize]) -> Option<&Packet> { let mut packet : Option<&Packet> = None; let mut cont = Some(&self.top_level); for i in pathspec { if let Some(c) = cont.take() { if let Some(children) = c.children_ref() { if *i < children.len() { let p = &children[*i]; packet = Some(p); cont = p.container_ref(); continue; } } } return None; } packet } /// Returns a mutable reference to the packet at the location /// described by `pathspec`. /// /// See the description of the `path_spec` for more details. /// /// # Examples /// /// ```rust /// # use sequoia_openpgp as openpgp; /// # use openpgp::{Result, types::{CompressionAlgorithm, DataFormat}, /// # Packet, PacketPile, packet::Literal, packet::CompressedData}; /// # fn main() -> Result<()> { /// # let mut lit = Literal::new(DataFormat::Unicode); /// # lit.set_body(b"test".to_vec()); /// # let packets = vec![lit.into()]; /// let mut pile = PacketPile::from(packets); /// /// if let Some(ref packet) = pile.path_ref_mut(&[0]) { /// // There is a packet at this path. /// } /// # else { /// # unreachable!(); /// # } /// /// if let None = pile.path_ref_mut(&[0, 1, 2]) { /// // But none here. /// } /// # else { /// # unreachable!(); /// # } /// # Ok(()) /// # } /// ``` pub fn path_ref_mut(&mut self, pathspec: &[usize]) -> Option<&mut Packet> { let mut container = &mut self.top_level; for (level, &i) in pathspec.iter().enumerate() { let tmp = container; let p = tmp.children_mut().and_then(|c| c.get_mut(i))?; if level == pathspec.len() - 1 { return Some(p) } container = p.container_mut().unwrap(); } None } /// Replaces the specified packets at the location described by /// `pathspec` with `packets`. /// /// If a packet is a container, the subtree rooted at the /// container is removed. /// /// Note: the number of packets to remove need not match the /// number of packets to insert. /// /// The removed packets are returned. /// /// If the path was invalid, then `Error::IndexOutOfRange` is /// returned instead. /// /// # Examples /// /// ```rust /// # use sequoia_openpgp as openpgp; /// # use openpgp::{Result, types::{CompressionAlgorithm, DataFormat}, /// # Packet, PacketPile, packet::Literal, packet::CompressedData}; /// # fn main() -> Result<()> { /// // A compressed data packet that contains a literal data packet. /// let mut literal = Literal::new(DataFormat::Unicode); /// literal.set_body(b"old".to_vec()); /// let mut compressed = /// CompressedData::new(CompressionAlgorithm::Uncompressed); /// compressed.container_mut().children_mut().unwrap().push(literal.into()); /// let mut pile = PacketPile::from(Packet::from(compressed)); /// /// // Replace the literal data packet. /// let mut literal = Literal::new(DataFormat::Unicode); /// literal.set_body(b"new".to_vec()); /// pile.replace( /// &[0, 0], 1, /// [literal.into()].to_vec())?; /// # if let Some(Packet::Literal(lit)) = pile.path_ref(&[0, 0]) { /// # assert_eq!(lit.body(), &b"new"[..], "{:#?}", lit); /// # } else { /// # panic!("Unexpected packet!"); /// # } /// # Ok(()) /// # } /// ``` pub fn replace(&mut self, pathspec: &[usize], count: usize, mut packets: Vec<Packet>) -> Result<Vec<Packet>> { let mut container = &mut self.top_level; for (level, &i) in pathspec.iter().enumerate() { let tmp = container; if level == pathspec.len() - 1 { if tmp.children_ref().map(|c| i + count > c.len()) .unwrap_or(true) { return Err(Error::IndexOutOfRange.into()); } // Out with the old... let old = tmp.children_mut() .expect("checked above") .drain(i..i + count) .collect::<Vec<Packet>>(); assert_eq!(old.len(), count); // In with the new... let mut tail = tmp.children_mut() .expect("checked above") .drain(i..) .collect::<Vec<Packet>>(); tmp.children_mut().expect("checked above").append(&mut packets); tmp.children_mut().expect("checked above").append(&mut tail); return Ok(old) } if tmp.children_ref().map(|c| i >= c.len()).unwrap_or(true) { return Err(Error::IndexOutOfRange.into()); } match tmp.children_ref().expect("checked above")[i] { // The structured container types. Packet::CompressedData(_) | Packet::SEIP(_) => (), // Ok. _ => return Err(Error::IndexOutOfRange.into()), } container = tmp.children_mut().expect("checked above")[i].container_mut() .expect("The above packets are structured containers"); } Err(Error::IndexOutOfRange.into()) } /// Returns an iterator over all the packet's descendants, in /// depth-first order. /// /// ```rust /// # use sequoia_openpgp as openpgp; /// # use openpgp::{Result, types::{CompressionAlgorithm, DataFormat}, /// # Packet, PacketPile, packet::Literal, packet::Tag}; /// # use std::iter::Iterator; /// # fn main() -> Result<()> { /// let mut lit = Literal::new(DataFormat::Unicode); /// lit.set_body(b"test".to_vec()); /// /// let pile = PacketPile::from(vec![lit.into()]); /// /// for packet in pile.descendants() { /// assert_eq!(packet.tag(), Tag::Literal); /// } /// # Ok(()) /// # } /// ``` pub fn descendants(&self) -> packet::Iter { self.top_level.descendants().expect("toplevel is a container") } /// Returns an iterator over the top-level packets. /// /// ```rust /// # use sequoia_openpgp as openpgp; /// # use openpgp::{Result, types::{CompressionAlgorithm, DataFormat}, /// # Packet, PacketPile, packet::Literal, packet::CompressedData}; /// # fn main() -> Result<()> { /// let mut lit = Literal::new(DataFormat::Unicode); /// lit.set_body(b"test".to_vec()); /// /// let pile = PacketPile::from(vec![lit.into()]); /// /// assert_eq!(pile.children().len(), 1); /// # Ok(()) /// # } /// ``` pub fn children(&self) -> impl Iterator<Item=&Packet> + ExactSizeIterator + Send + Sync { self.top_level.children().expect("toplevel is a container") } /// Returns an `IntoIter` over the top-level packets. /// /// ```rust /// # use sequoia_openpgp as openpgp; /// # use openpgp::{Result, types::{CompressionAlgorithm, DataFormat}, /// # Packet, PacketPile, packet::Literal, packet::Tag}; /// # fn main() -> Result<()> { /// let mut lit = Literal::new(DataFormat::Unicode); /// lit.set_body(b"test".to_vec()); /// /// let pile = PacketPile::from(vec![lit.into()]); /// /// for packet in pile.into_children() { /// assert_eq!(packet.tag(), Tag::Literal); /// } /// # Ok(()) /// # } /// ``` pub fn into_children(self) -> impl Iterator<Item=Packet> + ExactSizeIterator + Send + Sync { self.top_level.into_children().expect("toplevel is a container") } pub(crate) fn from_cookie_reader<'a>(bio: Box<dyn BufferedReader<Cookie> + 'a>) -> Result<PacketPile> { PacketParserBuilder::from_cookie_reader(bio)? .buffer_unread_content() .into_packet_pile() } } impl From<Cert> for PacketPile { /// Converts the `Cert` into a `PacketPile`. /// /// If any packets include secret key material, that secret key /// material is included in the resulting `PacketPile`. In /// contrast, when serializing a `Cert`, or converting a cert to /// packets with [`Cert::into_packets`], the secret key material /// not included. /// /// Note: This will change in sequoia-openpgp version 2, which /// will harmonize the behavior and not include secret key /// material. // XXXv2: Drop the note in the doc comment and mentioned it in the // release notes. fn from(cert: Cert) -> PacketPile { #[allow(deprecated)] PacketPile::from(cert.into_packets().collect::<Vec<Packet>>()) } } impl<'a> TryFrom<PacketParserResult<'a>> for PacketPile { type Error = anyhow::Error; /// Reads all the packets from a `PacketParser`, and turns them /// into a message. /// /// Note: this assumes that `ppr` points to a top-level packet. fn try_from(ppr: PacketParserResult<'a>) -> Result<PacketPile> { // Things are not going to work out if we don't start with a // top-level packet. We should only pop until // ppo.recursion_depth and leave the rest of the message, but // it is hard to imagine that that is what the caller wants. // Instead of hiding that error, fail fast. if let PacketParserResult::Some(ref pp) = ppr { if pp.recursion_depth() != 0 { return Err(Error::InvalidOperation( format!("Expected top-level packet, \ but the parser is at level {}", pp.recursion_depth())).into()); } } // Create a top-level container. let mut top_level = Container::default(); let mut last_position = 0; if ppr.is_eof() { // Empty message. return Ok(PacketPile::from(Vec::new())); } let mut pp = ppr.unwrap(); 'outer: loop { let recursion_depth = pp.recursion_depth(); let (mut packet, mut ppr) = pp.recurse()?; let mut position = recursion_depth as isize; let mut relative_position : isize = position - last_position; assert!(relative_position <= 1); // Find the right container for `packet`. let mut container = &mut top_level; // If we recurse, don't create the new container here. for _ in 0..(position - if relative_position > 0 { 1 } else { 0 }) { // Do a little dance to prevent container from // being reborrowed and preventing us from // assigning to it. let tmp = container; let packets_len = tmp.children_ref().ok_or_else(Self::error)?.len(); let p = &mut tmp.children_mut() .ok_or_else(Self::error)? [packets_len - 1]; container = p.container_mut().unwrap(); } if relative_position < 0 { relative_position = 0; } // If next packet will be inserted in the same container // or the current container's child, we don't need to walk // the tree from the root. loop { if relative_position == 1 { // Create a new container. let tmp = container; let i = tmp.children_ref().ok_or_else(Self::error)?.len() - 1; container = tmp.children_mut() .ok_or_else(Self::error)? [i].container_mut().unwrap(); } container.children_mut().unwrap().push(packet); if ppr.is_eof() { break 'outer; } pp = ppr.unwrap(); last_position = position; position = pp.recursion_depth() as isize; relative_position = position - last_position; if position < last_position { // There was a pop, we need to restart from the // root. break; } let recursion_depth = pp.recursion_depth(); let (packet_, ppr_) = pp.recurse()?; packet = packet_; ppr = ppr_; assert_eq!(position, recursion_depth as isize); } } Ok(PacketPile { top_level }) } } impl<'a> PacketParserBuilder<'a> { /// Finishes configuring the `PacketParser` and returns a fully /// parsed message. /// /// Note: calling this function does not change the default /// settings. Thus, by default, the content of packets will *not* /// be buffered. /// /// Note: to avoid denial-of-service attacks, the `PacketParser` /// interface should be preferred unless the size of the message /// is known to fit in memory. /// /// # Examples /// /// ```rust /// # use sequoia_openpgp as openpgp; /// # use openpgp::Result; /// # use openpgp::PacketPile; /// # use openpgp::parse::{Parse, PacketParser, PacketParserBuilder}; /// # f(include_bytes!("../tests/data/keys/public-key.gpg")); /// # /// # fn f(message_data: &[u8]) -> Result<PacketPile> { /// let message = PacketParserBuilder::from_bytes(message_data)? /// .buffer_unread_content() /// .into_packet_pile()?; /// # return Ok(message); /// # } /// ``` pub fn into_packet_pile(self) -> Result<PacketPile> { PacketPile::try_from(self.build()?) } } #[cfg(test)] mod test { use super::*; use crate::types::CompressionAlgorithm; use crate::types::DataFormat::Unicode; use crate::packet::Literal; use crate::packet::CompressedData; use crate::packet::seip::SEIP1; use crate::packet::Tag; use crate::parse::Parse; #[test] fn deserialize_test_1 () { // XXX: This test should be more thorough. Right now, we mostly // just rely on the fact that an assertion is not thrown. // A flat message. let pile = PacketPile::from_bytes(crate::tests::key("public-key.gpg")) .unwrap(); eprintln!("PacketPile has {} top-level packets.", pile.children().len()); eprintln!("PacketPile: {:?}", pile); let mut count = 0; for (i, p) in pile.descendants().enumerate() { eprintln!("{}: {:?}", i, p); count += 1; } assert_eq!(count, 61); } #[cfg(feature = "compression-deflate")] #[test] fn deserialize_test_2 () { // A message containing a compressed packet that contains a // literal packet. let pile = PacketPile::from_bytes( crate::tests::message("compressed-data-algo-1.gpg")).unwrap(); eprintln!("PacketPile has {} top-level packets.", pile.children().len()); eprintln!("PacketPile: {:?}", pile); let mut count = 0; for (i, p) in pile.descendants().enumerate() { eprintln!("{}: {:?}", i, p); count += 1; } assert_eq!(count, 2); } #[cfg(feature = "compression-deflate")] #[test] fn deserialize_test_3 () { let pile = PacketPile::from_bytes(crate::tests::message("signed.gpg")).unwrap(); eprintln!("PacketPile has {} top-level packets.", pile.children().len()); eprintln!("PacketPile: {:?}", pile); let mut count = 0; for (i, p) in pile.descendants().enumerate() { count += 1; eprintln!("{}: {:?}", i, p); } // We expect 6 packets. assert_eq!(count, 6); } // dkg's key contains packets from different OpenPGP // implementations. And, it even includes some v3 signatures. // // lutz's key is a v3 key. #[test] fn torture() { use std::convert::TryInto; use crate::parse::PacketPileParser; let data = crate::tests::key("dkg.gpg"); let mut ppp: PacketPileParser = PacketParserBuilder::from_bytes(data).unwrap() //.trace() .buffer_unread_content() .try_into().unwrap(); while ppp.packet().is_ok() { ppp.recurse().unwrap(); } let pile = ppp.finish(); //pile.pretty_print(); assert_eq!(pile.children().len(), 1450); let data = crate::tests::key("lutz.gpg"); let mut ppp: PacketPileParser = PacketParserBuilder::from_bytes(data).unwrap() //.trace() .buffer_unread_content() .try_into().unwrap(); while let Ok(pp) = ppp.packet().as_ref() { eprintln!("{:?}", pp); ppp.recurse().unwrap(); } let pile = ppp.finish(); pile.pretty_print(); assert_eq!(pile.children().len(), 77); } #[cfg(feature = "compression-deflate")] #[test] fn compression_quine_test_1 () { // Use the PacketPile::from_file interface to parse an OpenPGP // quine. let max_recursion_depth = 128; let pile = PacketParserBuilder::from_bytes( crate::tests::message("compression-quine.gpg")).unwrap() .max_recursion_depth(max_recursion_depth) .into_packet_pile().unwrap(); let mut count = 0; for (i, p) in pile.descendants().enumerate() { count += 1; if false { eprintln!("{}: p: {:?}", i, p); } } assert_eq!(count, 1 + max_recursion_depth); } #[cfg(feature = "compression-deflate")] #[test] fn compression_quine_test_2 () { // Use the iterator interface to parse an OpenPGP quine. let max_recursion_depth = 255; let mut ppr : PacketParserResult = PacketParserBuilder::from_bytes( crate::tests::message("compression-quine.gpg")).unwrap() .max_recursion_depth(max_recursion_depth) .build().unwrap(); let mut count = 0; loop { if let PacketParserResult::Some(pp2) = ppr { count += 1; let packet_depth = pp2.recursion_depth(); let pp2 = pp2.recurse().unwrap().1; assert_eq!(packet_depth, count - 1); if pp2.is_some() { assert_eq!(pp2.as_ref().unwrap().recursion_depth(), count); } ppr = pp2; } else { break; } } assert_eq!(count, 1 + max_recursion_depth as isize); } #[cfg(feature = "compression-deflate")] #[test] fn consume_content_1 () { use std::io::Read; use crate::parse::PacketParser; // A message containing a compressed packet that contains a // literal packet. When we read some of the compressed // packet, we expect recurse() to not recurse. let ppr = PacketParserBuilder::from_bytes( crate::tests::message("compressed-data-algo-1.gpg")).unwrap() .buffer_unread_content() .build().unwrap(); let mut pp = ppr.unwrap(); if let Packet::CompressedData(_) = pp.packet { } else { panic!("Expected a compressed packet!"); } // Read some of the body of the compressed packet. let mut data = [0u8; 1]; let amount = pp.read(&mut data).unwrap(); assert_eq!(amount, 1); // recurse should now not recurse. Since there is nothing // following the compressed packet, ppr should be EOF. let (packet, ppr) = pp.next().unwrap(); assert!(ppr.is_eof()); // Get the rest of the content and put the initial byte that // we stole back. let mut content = packet.processed_body().unwrap().to_vec(); content.insert(0, data[0]); let ppr = PacketParser::from_bytes(&content).unwrap(); let pp = ppr.unwrap(); if let Packet::Literal(_) = pp.packet { } else { panic!("Expected a literal packet!"); } // And we're done... let ppr = pp.next().unwrap().1; assert!(ppr.is_eof()); } #[test] fn path_ref() { // 0: SEIP // 0: CompressedData // 0: Literal("one") // 1: Literal("two") // 2: Literal("three") // 3: Literal("four") let mut packets : Vec<Packet> = Vec::new(); let text = [ &b"one"[..], &b"two"[..], &b"three"[..], &b"four"[..] ].to_vec(); let mut cd = CompressedData::new(CompressionAlgorithm::Uncompressed); for t in text.iter() { let mut lit = Literal::new(Unicode); lit.set_body(t.to_vec()); cd = cd.push(lit.into()) } let mut seip = SEIP1::new(); seip.container_mut().children_mut().unwrap().push(cd.into()); packets.push(seip.into()); eprintln!("{:#?}", packets); let mut pile = PacketPile::from(packets); assert_eq!(pile.path_ref(&[ 0 ]).unwrap().tag(), Tag::SEIP); assert_eq!(pile.path_ref_mut(&[ 0 ]).unwrap().tag(), Tag::SEIP); assert_eq!(pile.path_ref(&[ 0, 0 ]).unwrap().tag(), Tag::CompressedData); assert_eq!(pile.path_ref_mut(&[ 0, 0 ]).unwrap().tag(), Tag::CompressedData); for (i, t) in text.into_iter().enumerate() { assert_eq!(pile.path_ref(&[ 0, 0, i ]).unwrap().tag(), Tag::Literal); assert_eq!(pile.path_ref_mut(&[ 0, 0, i ]).unwrap().tag(), Tag::Literal); let packet = pile.path_ref(&[ 0, 0, i ]).unwrap(); if let Packet::Literal(l) = packet { assert_eq!(l.body(), t); } else { panic!("Expected literal, got: {:?}", packet); } let packet = pile.path_ref_mut(&[ 0, 0, i ]).unwrap(); if let Packet::Literal(l) = packet { assert_eq!(l.body(), t); } else { panic!("Expected literal, got: {:?}", packet); } } // Try a few out of bounds accesses. assert!(pile.path_ref(&[ 0, 0, 4 ]).is_none()); assert!(pile.path_ref_mut(&[ 0, 0, 4 ]).is_none()); assert!(pile.path_ref(&[ 0, 0, 5 ]).is_none()); assert!(pile.path_ref_mut(&[ 0, 0, 5 ]).is_none()); assert!(pile.path_ref(&[ 0, 1 ]).is_none()); assert!(pile.path_ref_mut(&[ 0, 1 ]).is_none()); assert!(pile.path_ref(&[ 0, 2 ]).is_none()); assert!(pile.path_ref_mut(&[ 0, 2 ]).is_none()); assert!(pile.path_ref(&[ 1 ]).is_none()); assert!(pile.path_ref_mut(&[ 1 ]).is_none()); assert!(pile.path_ref(&[ 2 ]).is_none()); assert!(pile.path_ref_mut(&[ 2 ]).is_none()); assert!(pile.path_ref(&[ 0, 1, 0 ]).is_none()); assert!(pile.path_ref_mut(&[ 0, 1, 0 ]).is_none()); assert!(pile.path_ref(&[ 0, 2, 0 ]).is_none()); assert!(pile.path_ref_mut(&[ 0, 2, 0 ]).is_none()); } #[test] fn replace() { // 0: Literal("one") // => // 0: Literal("two") let mut one = Literal::new(Unicode); one.set_body(b"one".to_vec()); let mut two = Literal::new(Unicode); two.set_body(b"two".to_vec()); let mut packets : Vec<Packet> = Vec::new(); packets.push(one.into()); assert!(packets.iter().map(|p| p.tag()).collect::<Vec<Tag>>() == [ Tag::Literal ]); let mut pile = PacketPile::from(packets.clone()); pile.replace( &[ 0 ], 1, [ two.into() ].to_vec()).unwrap(); let children = pile.into_children().collect::<Vec<Packet>>(); assert_eq!(children.len(), 1, "{:#?}", children); if let Packet::Literal(ref literal) = children[0] { assert_eq!(literal.body(), &b"two"[..], "{:#?}", literal); } else { panic!("WTF"); } // We start with four packets, and replace some of them with // up to 3 packets. let initial = [ &b"one"[..], &b"two"[..], &b"three"[..], &b"four"[..] ].to_vec(); let inserted = [ &b"a"[..], &b"b"[..], &b"c"[..] ].to_vec(); let mut packets : Vec<Packet> = Vec::new(); for text in initial.iter() { let mut lit = Literal::new(Unicode); lit.set_body(text.to_vec()); packets.push(lit.into()) } for start in 0..initial.len() + 1 { for delete in 0..initial.len() - start + 1 { for insert in 0..inserted.len() + 1 { let mut pile = PacketPile::from(packets.clone()); let mut replacement : Vec<Packet> = Vec::new(); for &text in inserted[0..insert].iter() { let mut lit = Literal::new(Unicode); lit.set_body(text.to_vec()); replacement.push(lit.into()); } pile.replace(&[ start ], delete, replacement).unwrap(); let values = pile .children() .map(|p| { if let Packet::Literal(ref literal) = p { literal.body() } else { panic!("Expected a literal packet, got: {:?}", p); } }) .collect::<Vec<&[u8]>>(); assert_eq!(values.len(), initial.len() - delete + insert); assert_eq!(values[..start], initial[..start]); assert_eq!(values[start..start + insert], inserted[..insert]); assert_eq!(values[start + insert..], initial[start + delete..]); } } } // Like above, but the packets to replace are not at the // top-level, but in a compressed data packet. let initial = [ &b"one"[..], &b"two"[..], &b"three"[..], &b"four"[..] ].to_vec(); let inserted = [ &b"a"[..], &b"b"[..], &b"c"[..] ].to_vec(); let mut cd = CompressedData::new(CompressionAlgorithm::Uncompressed); for l in initial.iter() { let mut lit = Literal::new(Unicode); lit.set_body(l.to_vec()); cd = cd.push(lit.into()); } for start in 0..initial.len() + 1 { for delete in 0..initial.len() - start + 1 { for insert in 0..inserted.len() + 1 { let mut pile = PacketPile::from( vec![ cd.clone().into() ]); let mut replacement : Vec<Packet> = Vec::new(); for &text in inserted[0..insert].iter() { let mut lit = Literal::new(Unicode); lit.set_body(text.to_vec()); replacement.push(lit.into()); } pile.replace(&[ 0, start ], delete, replacement).unwrap(); let top_level = pile.children().collect::<Vec<&Packet>>(); assert_eq!(top_level.len(), 1); let values = top_level[0] .children().unwrap() .map(|p| { if let Packet::Literal(ref literal) = p { literal.body() } else { panic!("Expected a literal packet, got: {:?}", p); } }) .collect::<Vec<&[u8]>>(); assert_eq!(values.len(), initial.len() - delete + insert); assert_eq!(values[..start], initial[..start]); assert_eq!(values[start..start + insert], inserted[..insert]); assert_eq!(values[start + insert..], initial[start + delete..]); } } } // Make sure out-of-range accesses error out. let mut one = Literal::new(Unicode); one.set_body(b"one".to_vec()); let mut packets : Vec<Packet> = Vec::new(); packets.push(one.into()); let mut pile = PacketPile::from(packets.clone()); assert!(pile.replace(&[ 1 ], 0, Vec::new()).is_ok()); assert!(pile.replace(&[ 2 ], 0, Vec::new()).is_err()); assert!(pile.replace(&[ 0 ], 2, Vec::new()).is_err()); assert!(pile.replace(&[ 0, 0 ], 0, Vec::new()).is_err()); assert!(pile.replace(&[ 0, 1 ], 0, Vec::new()).is_err()); // Try the same thing, but with a container. let mut packets : Vec<Packet> = Vec::new(); packets.push(CompressedData::new(CompressionAlgorithm::Uncompressed) .into()); let mut pile = PacketPile::from(packets.clone()); assert!(pile.replace(&[ 1 ], 0, Vec::new()).is_ok()); assert!(pile.replace(&[ 2 ], 0, Vec::new()).is_err()); assert!(pile.replace(&[ 0 ], 2, Vec::new()).is_err()); // Since this is a container, this should be okay. assert!(pile.replace(&[ 0, 0 ], 0, Vec::new()).is_ok()); assert!(pile.replace(&[ 0, 1 ], 0, Vec::new()).is_err()); } } ������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/src/parse/hashed_reader.rs����������������������������������������������������0000644�0000000�0000000�00000052204�10461020230�0017767�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������use std::io; use std::cmp; use std::mem; use std::fmt; use buffered_reader::BufferedReader; use buffered_reader::buffered_reader_generic_read_impl; use crate::fmt::hex; use crate::packet::Signature; use crate::parse::{Cookie, HashesFor, Hashing}; use crate::Result; use crate::types::HashAlgorithm; use crate::types::SignatureType; const TRACE : bool = false; /// Controls line-ending normalization during hashing. /// /// OpenPGP normalizes line endings when signing or verifying text /// signatures. #[derive(Clone, Eq)] pub(crate) enum HashingMode<T> { /// Hash for a binary signature. /// /// The data is hashed as-is. Binary(Vec<u8>, T), /// Hash for a text signature. /// /// The data is hashed with line endings normalized to `\r\n`. Text(Vec<u8>, T), /// Like Text, but the last character that we hashed was a '\r' /// that we converted to a '\r\n'. TextLastWasCr(Vec<u8>, T), } impl<T: std::fmt::Debug> std::fmt::Debug for HashingMode<T> { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { use self::HashingMode::*; match self { Binary(salt, t) if salt.is_empty() => write!(f, "Binary({:?})", t), Binary(salt, t) => write!(f, "Binary({}, {:?})", hex::encode(salt), t), Text(salt, t) if salt.is_empty() => write!(f, "Text({:?})", t), Text(salt, t) => write!(f, "Text({}, {:?})", hex::encode(salt), t), TextLastWasCr(salt, t) if salt.is_empty() => write!(f, "Text(last was CR, {:?})", t), TextLastWasCr(salt, t) => write!(f, "Text(last was CR, {}, {:?})", hex::encode(salt), t), } } } impl<T: PartialEq> PartialEq for HashingMode<T> { fn eq(&self, other: &Self) -> bool { use self::HashingMode::*; match (self, other) { (Binary(salt_s, s), Binary(salt_o, o)) => salt_s == salt_o && s == o, (Text(salt_s, s), Text(salt_o, o)) => salt_s == salt_o && s == o, (TextLastWasCr(salt_s, s), Text(salt_o, o)) => salt_s == salt_o && s == o, (Text(salt_s, s), TextLastWasCr(salt_o, o)) => salt_s == salt_o && s == o, (TextLastWasCr(salt_s, s), TextLastWasCr(salt_o, o)) => salt_s == salt_o && s == o, _ => false, } } } impl<T> HashingMode<T> { pub(crate) fn map<U, F: Fn(&T) -> U>(&self, f: F) -> HashingMode<U> { use self::HashingMode::*; match self { Binary(salt, t) => Binary(salt.clone(), f(t)), Text(salt, t) => Text(salt.clone(), f(t)), TextLastWasCr(salt, t) => TextLastWasCr(salt.clone(), f(t)), } } pub(crate) fn mapf<U, F: Fn(T) -> Result<U>>(self, f: F) -> Result<HashingMode<U>> { use self::HashingMode::*; match self { Binary(salt, t) => Ok(Binary(salt.clone(), f(t)?)), Text(salt, t) => Ok(Text(salt.clone(), f(t)?)), TextLastWasCr(salt, t) => Ok(TextLastWasCr(salt.clone(), f(t)?)), } } pub(crate) fn salt(&self) -> &[u8] { use self::HashingMode::*; match self { Binary(salt, _t) => salt, Text(salt, _t) => salt, TextLastWasCr(salt, _t) => salt, } } pub(crate) fn as_ref(&self) -> &T { use self::HashingMode::*; match self { Binary(_salt, t) => t, Text(_salt, t) => t, TextLastWasCr(_salt, t) => t, } } pub(crate) fn as_mut(&mut self) -> &mut T { use self::HashingMode::*; match self { Binary(_salt, t) => t, Text(_salt, t) => t, TextLastWasCr(_salt, t) => t, } } pub(crate) fn for_signature(t: T, s: &Signature) -> Self { match s { Signature::V3(s) => Self::for_salt_and_type(t, &[], s.typ()), Signature::V4(s) => Self::for_salt_and_type(t, &[], s.typ()), Signature::V6(s) => Self::for_salt_and_type(t, s.salt(), s.typ()), } } pub(crate) fn for_salt_and_type(t: T, salt: &[u8], typ: SignatureType) -> Self { if typ == SignatureType::Text { HashingMode::Text(salt.into(), t) } else { HashingMode::Binary(salt.into(), t) } } pub(crate) fn into_inner(self) -> T { use self::HashingMode::*; match self { Binary(_salt, t) => t, Text(_salt, t) => t, TextLastWasCr(_salt, t) => t, } } } impl HashingMode<crate::crypto::hash::Context> { /// Updates the given hash context. When in text mode, normalize /// the line endings to "\r\n" on the fly. pub(crate) fn update(&mut self, data: &[u8]) { if data.is_empty() { // This isn't just a short circuit. It preserves // `last_was_cr`, which running through the code would // not. return; } let (h, mut last_was_cr) = match self { HashingMode::Text(_salt, h) => (h, false), HashingMode::TextLastWasCr(_salt, h) => (h, true), HashingMode::Binary(_salt, h) => return h.update(data), }; let mut line = data; let last_is_cr = line.last() == Some(&b'\r'); while ! line.is_empty() { let mut next = 0; for (i, c) in line.iter().cloned().enumerate() { match c { b'\n' if last_was_cr => { // We already hash the \n. assert_eq!(i, 0); assert_eq!(next, 0); next = 1; break; }, b'\r' | b'\n' => { h.update(&line[..i]); h.update(b"\r\n"); next = i + 1; if c == b'\r' && line.get(next) == Some(&b'\n') { next += 1; } break; }, _ => (), } last_was_cr = false; } if next > 0 { line = &line[next..]; } else { h.update(line); break; } } match (&mut *self, last_is_cr) { (&mut HashingMode::Text(_, _), false) => { // This is the common case. Getting a crlf that is // split across two chunks is extremely rare. Hence, // the clones used to change the variant are rarely // needed. }, (&mut HashingMode::Text(ref mut salt, ref mut h), true) => { *self = HashingMode::TextLastWasCr(std::mem::take(salt), h.clone()); } (&mut HashingMode::TextLastWasCr(ref mut salt, ref mut h), false) => { *self = HashingMode::Text(std::mem::take(salt), h.clone()); }, (&mut HashingMode::TextLastWasCr(_, _), true) => (), _ => unreachable!("handled above"), } } } pub(crate) struct HashedReader<R: BufferedReader<Cookie>> { reader: R, cookie: Cookie, } impl<R: BufferedReader<Cookie>> fmt::Display for HashedReader<R> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "HashedReader") } } impl<R: BufferedReader<Cookie>> fmt::Debug for HashedReader<R> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.debug_struct("HashedReader") .field("cookie", &self.cookie) .field("reader", &self.reader) .finish() } } impl<R: BufferedReader<Cookie>> HashedReader<R> { /// Instantiates a new hashed reader. `hashes_for` is the hash's /// purpose. `algos` is a list of algorithms for which we should /// compute the hash. pub fn new(reader: R, hashes_for: HashesFor, algos: Vec<HashingMode<HashAlgorithm>>) -> Result<Self> { let mut cookie = Cookie::default(); for mode in algos { let salt = mode.salt().to_vec(); let mode = mode.mapf(|algo| { let mut ctx = algo.context()? // XXX: This is not quite correct, but since this is // only important for hashing keys, which we don't do // in streaming operation, we can get away with it. .for_digest(); ctx.update(&salt); Ok(ctx) })?; cookie.sig_group_mut().hashes.push(mode); } cookie.hashes_for = hashes_for; Ok(HashedReader { reader, cookie, }) } } impl Cookie { fn hash_update(&mut self, data: &[u8]) { let level = self.level.unwrap_or(0); let hashes_for = self.hashes_for; let ngroups = self.sig_groups.len(); tracer!(TRACE, "Cookie::hash_update", level); t!("({} bytes, {} hashes, enabled: {:?})", data.len(), self.sig_group().hashes.len(), self.hashing); if self.hashes_for == HashesFor::CleartextSignature { return self.hash_update_csf(data); } // Hash stashed data first. if let Some(stashed_data) = self.hash_stash.take() { // The stashed data was supposed to be hashed into the // then-topmost signature-group's hash, but wasn't, // because framing isn't hashed into the topmost signature // group. By the time the parser encountered a new // signature group, the data has already been consumed. // We fix that here by hashing the stashed data into the // former topmost signature-group's hash. assert!(ngroups > 1); for h in self.sig_groups[ngroups-2].hashes.iter_mut() { t!("({:?}): group {} {:?} hashing {} stashed bytes.", hashes_for, ngroups-2, h.map(|ctx| ctx.algo()), data.len()); h.update(&stashed_data); } } if data.is_empty() { return; } if self.hashing == Hashing::Disabled { t!(" hash_update: NOT hashing {} bytes: {}.", data.len(), crate::fmt::to_hex(data, true)); return; } let topmost_group = |i| i == ngroups - 1; for (i, sig_group) in self.sig_groups.iter_mut().enumerate() { if topmost_group(i) && self.hashing != Hashing::Enabled { t!("topmost group {} NOT hashing {} bytes: {}.", i, data.len(), crate::fmt::to_hex(data, true)); return; } for h in sig_group.hashes.iter_mut() { t!("{:?}: group {} {:?} hashing {} bytes.", hashes_for, i, h.map(|ctx| ctx.algo()), data.len()); h.update(data); } } } fn hash_update_csf(&mut self, data: &[u8]) { let level = self.level.unwrap_or(0); let hashes_for = self.hashes_for; let ngroups = self.sig_groups.len(); assert_eq!(self.hashes_for, HashesFor::CleartextSignature); // There is exactly one group. However, this can momentarily // be violated if there are One-Pass-Signature packets in the // signature block. This doesn't last long though: the // message parser will reject the message because it doesn't // adhere to the grammar. assert!(ngroups == 1 || ngroups == /* momentarily */ 2); tracer!(TRACE, "Cookie::hash_update_csf", level); t!("Cleartext Signature Framework message"); if data.is_empty() { return; } if self.hashing == Hashing::Disabled { t!(" hash_update: NOT hashing {} bytes: {}.", data.len(), crate::fmt::to_hex(data, true)); return; } // Hash the data. for h in self.sig_groups[0].hashes.iter_mut() { t!("{:?}: {:?} hashing {} bytes.", hashes_for, h.map(|ctx| ctx.algo()), data.len()); h.update(data); } } } impl<T: BufferedReader<Cookie>> io::Read for HashedReader<T> { fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> { buffered_reader_generic_read_impl(self, buf) } } // Wrap a BufferedReader so that any data that is consumed is added to // the hash. impl<R: BufferedReader<Cookie>> BufferedReader<Cookie> for HashedReader<R> { fn buffer(&self) -> &[u8] { self.reader.buffer() } fn data(&mut self, amount: usize) -> io::Result<&[u8]> { self.reader.data(amount) } fn data_hard(&mut self, amount: usize) -> io::Result<&[u8]> { self.reader.data_hard(amount) } fn consume(&mut self, amount: usize) -> &[u8] { // We need to take the state rather than get a mutable // reference to it, because self.reader.buffer() requires a // reference as well. let mut state = self.cookie_set(Cookie::default()); { // The inner buffered reader must return at least `amount` // bytes, because the caller can't `consume(amount)` if // the internal buffer doesn't have at least that many // bytes. let data = self.reader.buffer(); assert!(data.len() >= amount); state.hash_update(&data[..amount]); } self.cookie_set(state); self.reader.consume(amount) } fn data_consume(&mut self, amount: usize) -> io::Result<&[u8]> { // See consume() for an explanation of the following // acrobatics. let mut state = self.cookie_set(Cookie::default()); let got = { let data = self.reader.data(amount)?; let data = &data[..cmp::min(data.len(), amount)]; state.hash_update(data); data.len() }; self.cookie_set(state); if let Ok(data) = self.reader.data_consume(amount) { assert!(data.len() >= got); Ok(data) } else { panic!("reader.data_consume() returned less than reader.data()!"); } } fn data_consume_hard(&mut self, amount: usize) -> io::Result<&[u8]> { // See consume() for an explanation of the following // acrobatics. let mut state = self.cookie_set(Cookie::default()); { let data = self.reader.data_hard(amount)?; assert!(data.len() >= amount); state.hash_update(&data[..amount]); } self.cookie_set(state); let result = self.reader.data_consume(amount); assert!(result.is_ok()); result } fn get_mut(&mut self) -> Option<&mut dyn BufferedReader<Cookie>> { Some(&mut self.reader) } fn get_ref(&self) -> Option<&dyn BufferedReader<Cookie>> { Some(&self.reader) } fn into_inner<'b>(self: Box<Self>) -> Option<Box<dyn BufferedReader<Cookie> + 'b>> where Self: 'b { Some(self.reader.into_boxed()) } fn cookie_set(&mut self, cookie: Cookie) -> Cookie { mem::replace(&mut self.cookie, cookie) } fn cookie_ref(&self) -> &Cookie { &self.cookie } fn cookie_mut(&mut self) -> &mut Cookie { &mut self.cookie } } /// Hashes the given buffered reader. /// /// This can be used to verify detached signatures. For a more /// convenient method, see [`DetachedVerifier`]. /// /// [`DetachedVerifier`]: crate::parse::stream::DetachedVerifier pub(crate) fn hash_buffered_reader<R>(reader: R, algos: &[HashingMode<HashAlgorithm>]) -> Result<Vec<HashingMode<crate::crypto::hash::Context>>> where R: BufferedReader<crate::parse::Cookie>, { let mut reader = HashedReader::new(reader, HashesFor::Signature, algos.to_vec())?; // Hash all the data. reader.drop_eof()?; let hashes = mem::take(&mut reader.cookie_mut().sig_group_mut().hashes); Ok(hashes) } #[cfg(test)] mod test { use super::*; use buffered_reader::BufferedReader; #[test] fn hash_test_1() { use std::collections::HashMap; struct Test<'a> { data: &'a [u8], expected: HashMap<HashAlgorithm, &'a str>, } let tests = [ Test { data: &b"foobar\n"[..], expected: [ (HashAlgorithm::SHA1, "988881adc9fc3655077dc2d4d757d480b5ea0e11"), ].iter().cloned().collect(), }, Test { data: &b"0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789\n"[..], expected: [ (HashAlgorithm::SHA1, "1d12c55b3a85daab4776a1df41a8f30ada099e11"), (HashAlgorithm::SHA224, "a4c1bde77c682a0e9e30c6afdd1ece2397ffeec61dde2a0eaa23191e"), (HashAlgorithm::SHA256, "151a1d51a1870dc244f07f4844f46ee65fae19a8efeb60b203a074aff899e27d"), (HashAlgorithm::SHA384, "5bea68c8c696bbed95e152d61c446ad0e05bf68f7df39cbfeae568bee6f6691c840fb1d5dd2599737b08dbb33eed344b"), (HashAlgorithm::SHA512, "5fa032487774082af5cc833c2db5f943e31cc75cd2bfaa7d9bbd0ccabf5403b6dbcb484254727a524588f20e9ef336d8ce8533332c5ac1b9d50af3003a0da8d8"), ].iter().filter(|(hash, _)| hash.is_supported()).cloned().collect(), }, ]; for test in tests.iter() { let reader = buffered_reader::Generic::with_cookie( test.data, None, Default::default()); let mut reader = HashedReader::new(reader, HashesFor::MDC, test.expected.keys().cloned() .map(|v| HashingMode::Binary(vec![], v)) .collect()).unwrap(); assert_eq!(reader.steal_eof().unwrap(), test.data); let cookie = reader.cookie_mut(); let mut hashes = std::mem::take(&mut cookie.sig_group_mut().hashes); for mode in hashes.iter_mut() { let hash = mode.as_mut(); let algo = hash.algo(); let mut digest = vec![0u8; hash.digest_size()]; let _ = hash.digest(&mut digest); assert_eq!(digest, &crate::fmt::from_hex(test.expected.get(&algo) .unwrap(), true) .unwrap()[..], "Algo: {:?}", algo); } } } #[test] fn hash_update_text() -> crate::Result<()> { for text in &[ "one\r\ntwo\r\nthree", "one\ntwo\nthree", "one\rtwo\rthree", "one\ntwo\r\nthree", ] { for chunk_size in &[ text.len(), 1 ] { let mut ctx = HashingMode::Text(vec![], HashAlgorithm::SHA256.context()? .for_digest()); for chunk in text.as_bytes().chunks(*chunk_size) { ctx.update(chunk); } let mut ctx = ctx.into_inner(); let mut digest = vec![0; ctx.digest_size()]; let _ = ctx.digest(&mut digest); assert_eq!( &crate::fmt::hex::encode(&digest), "5536758151607BB81CE8D6F49189B2E84763DA9EA84965AB7327E704DAE415EB", "{:?}, chunk size: {}", text, chunk_size); } } Ok(()) } #[test] fn hash_reader_test() { use std::collections::HashMap; let expected: HashMap<HashAlgorithm, &str> = [ (HashAlgorithm::SHA1, "7945E3DA269C25C04F9EF435A5C0F25D9662C771"), (HashAlgorithm::SHA512, "DDE60DB05C3958AF1E576CD006A7F3D2C343DD8C\ 8DECE789A15D148DF90E6E0D1454DE734F834350\ 2CA93759F22C8F6221BE35B6BDE9728BD12D2891\ 22437CB1"), ].iter().cloned().collect(); let reader = buffered_reader::Generic::with_cookie( std::io::Cursor::new(crate::tests::manifesto()), None, Default::default()); let result = hash_buffered_reader( reader, &expected.keys().cloned() .map(|v| HashingMode::Binary(vec![], v)). collect::<Vec<_>>()) .unwrap(); for mut mode in result.into_iter() { let hash = mode.as_mut(); let algo = hash.algo(); let mut digest = vec![0u8; hash.digest_size()]; let _ = hash.digest(&mut digest); assert_eq!(*expected.get(&algo).unwrap(), &crate::fmt::to_hex(&digest[..], false)); } } } ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/src/parse/map.rs��������������������������������������������������������������0000644�0000000�0000000�00000020536�10461020230�0015771�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������//! Packet maps. //! //! If configured to do so, a `PacketParser` will create a map that //! charts the byte-stream, describing where the information was //! extracted from. //! //! # Examples //! //! ``` //! # fn main() -> sequoia_openpgp::Result<()> { //! use sequoia_openpgp as openpgp; //! use openpgp::parse::{Parse, PacketParserBuilder}; //! //! let message_data = b"\xcb\x12t\x00\x00\x00\x00\x00Hello world."; //! let pp = PacketParserBuilder::from_bytes(message_data)? //! .map(true) // Enable mapping. //! .buffer_unread_content() // For the packet body. //! .build()? //! .expect("One packet, not EOF"); //! let map = pp.map().expect("Mapping is enabled"); //! //! assert_eq!(map.iter().nth(0).unwrap().name(), "CTB"); //! assert_eq!(map.iter().nth(0).unwrap().offset(), 0); //! assert_eq!(map.iter().nth(0).unwrap().as_bytes(), &[0xcb]); //! # Ok(()) } //! ``` use std::cmp; /// Map created during parsing. #[derive(Clone, Debug)] pub struct Map { length: usize, entries: Vec<Entry>, header: Vec<u8>, data: Vec<u8>, } assert_send_and_sync!(Map); /// Represents an entry in the map. #[derive(Clone, Debug)] struct Entry { offset: usize, length: usize, field: &'static str, } impl Map { /// Creates a new map. pub(super) fn new(header: Vec<u8>) -> Self { Map { length: 0, entries: Vec::new(), header, data: Vec::new(), } } /// Adds a field to the map. pub(super) fn add(&mut self, field: &'static str, length: usize) { self.entries.push(Entry { offset: self.length, length, field }); self.length += length; } /// Finalizes the map providing the actual data. pub(super) fn finalize(&mut self, data: Vec<u8>) { self.data = data; } /// Creates an iterator over the map. /// /// Returns references to [`Field`]s. /// /// /// # Examples /// /// ``` /// # fn main() -> sequoia_openpgp::Result<()> { /// use sequoia_openpgp as openpgp; /// use openpgp::parse::{Parse, PacketParserBuilder}; /// /// let message_data = b"\xcb\x12t\x00\x00\x00\x00\x00Hello world."; /// let pp = PacketParserBuilder::from_bytes(message_data)? /// .map(true) // Enable mapping. /// .buffer_unread_content() // For the packet body. /// .build()? /// .expect("One packet, not EOF"); /// let map = pp.map().expect("Mapping is enabled"); /// /// assert_eq!(map.iter().count(), 6); /// # Ok(()) } /// ``` pub fn iter(&self) -> impl Iterator<Item = Field> + Send + Sync { Iter::new(self) } } /// Represents an entry in the map. /// /// A field has a [`name`] returning a human-readable field name /// (e.g. "CTB", or "version"), an [`offset`] into the packet, and the /// read [`data`]. /// /// [`name`]: Field::name /// [`offset`]: Field::offset /// [`data`]: Field::as_bytes #[derive(Clone, Debug)] pub struct Field<'a> { /// Name of the field. name: &'static str, /// Offset of the field in the packet. offset: usize, /// Value of the field. data: &'a [u8], } assert_send_and_sync!(Field<'_>); impl<'a> Field<'a> { fn new(map: &'a Map, i: usize) -> Option<Field<'a>> { // Synthetic packets have no CTB. let has_ctb = ! map.header.is_empty(); // Old-style CTB with indeterminate length emits no length // field. let has_length = map.header.len() > 1; if i == 0 && has_ctb { Some(Field { offset: 0, name: "CTB", data: &map.header.as_slice()[..1], }) } else if i == 1 && has_length { Some(Field { offset: 1, name: "length", data: &map.header.as_slice()[1..] }) } else { let offset = if has_ctb { 1 } else { 0 } + if has_length { 1 } else { 0 }; map.entries.get(i - offset).map(|e| { let len = map.data.len(); let start = cmp::min(len, e.offset); let end = cmp::min(len, e.offset + e.length); Field { offset: map.header.len() + e.offset, name: e.field, data: &map.data[start..end], } }) } } /// Returns the name of the field. /// /// Note: The returned names are for display purposes only and may /// change in the future. /// /// # Examples /// /// ``` /// # fn main() -> sequoia_openpgp::Result<()> { /// use sequoia_openpgp as openpgp; /// use openpgp::parse::{Parse, PacketParserBuilder}; /// /// let message_data = b"\xcb\x12t\x00\x00\x00\x00\x00Hello world."; /// let pp = PacketParserBuilder::from_bytes(message_data)? /// .map(true) // Enable mapping. /// .buffer_unread_content() // For the packet body. /// .build()? /// .expect("One packet, not EOF"); /// let map = pp.map().expect("Mapping is enabled"); /// /// assert_eq!(map.iter().nth(0).unwrap().name(), "CTB"); /// assert_eq!(map.iter().nth(1).unwrap().name(), "length"); /// assert_eq!(map.iter().nth(2).unwrap().name(), "format"); /// assert_eq!(map.iter().nth(3).unwrap().name(), "filename_len"); /// assert_eq!(map.iter().nth(4).unwrap().name(), "date"); /// assert_eq!(map.iter().nth(5).unwrap().name(), "body"); /// assert!(map.iter().nth(6).is_none()); /// # Ok(()) } /// ``` pub fn name(&self) -> &'a str { self.name } /// Returns the offset of the field in the packet. /// /// # Examples /// /// ``` /// # fn main() -> sequoia_openpgp::Result<()> { /// use sequoia_openpgp as openpgp; /// use openpgp::parse::{Parse, PacketParserBuilder}; /// /// let message_data = b"\xcb\x12t\x00\x00\x00\x00\x00Hello world."; /// let pp = PacketParserBuilder::from_bytes(message_data)? /// .map(true) // Enable mapping. /// .buffer_unread_content() // For the packet body. /// .build()? /// .expect("One packet, not EOF"); /// let map = pp.map().expect("Mapping is enabled"); /// /// assert_eq!(map.iter().nth(0).unwrap().offset(), 0); /// assert_eq!(map.iter().nth(1).unwrap().offset(), 1); /// assert_eq!(map.iter().nth(2).unwrap().offset(), 2); /// assert_eq!(map.iter().nth(3).unwrap().offset(), 3); /// assert_eq!(map.iter().nth(4).unwrap().offset(), 4); /// assert_eq!(map.iter().nth(5).unwrap().offset(), 8); /// assert!(map.iter().nth(6).is_none()); /// # Ok(()) } /// ``` pub fn offset(&self) -> usize { self.offset } /// Returns the value of the field. /// /// # Examples /// /// ``` /// # fn main() -> sequoia_openpgp::Result<()> { /// use sequoia_openpgp as openpgp; /// use openpgp::parse::{Parse, PacketParserBuilder}; /// /// let message_data = b"\xcb\x12t\x00\x00\x00\x00\x00Hello world."; /// let pp = PacketParserBuilder::from_bytes(message_data)? /// .map(true) // Enable mapping. /// .buffer_unread_content() // For the packet body. /// .build()? /// .expect("One packet, not EOF"); /// let map = pp.map().expect("Mapping is enabled"); /// /// assert_eq!(map.iter().nth(0).unwrap().as_bytes(), &[0xcb]); /// assert_eq!(map.iter().nth(1).unwrap().as_bytes(), &[0x12]); /// assert_eq!(map.iter().nth(2).unwrap().as_bytes(), "t".as_bytes()); /// assert_eq!(map.iter().nth(3).unwrap().as_bytes(), &[0x00]); /// assert_eq!(map.iter().nth(4).unwrap().as_bytes(), /// &[0x00, 0x00, 0x00, 0x00]); /// assert_eq!(map.iter().nth(5).unwrap().as_bytes(), /// "Hello world.".as_bytes()); /// assert!(map.iter().nth(6).is_none()); /// # Ok(()) } /// ``` pub fn as_bytes(&self) -> &'a [u8] { self.data } } /// An iterator over the map. struct Iter<'a> { map: &'a Map, i: usize, } impl<'a> Iter<'a> { fn new(map: &'a Map) -> Iter<'a> { Iter { map, i: 0, } } } impl<'a> Iterator for Iter<'a> { type Item = Field<'a>; fn next(&mut self) -> Option<Self::Item> { let field = Field::new(self.map, self.i); if field.is_some() { self.i += 1; } field } } ������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/src/parse/mpis.rs�������������������������������������������������������������0000644�0000000�0000000�00000056052�10461020230�0016166�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������//! Functions for parsing MPIs. use std::io::Read; use buffered_reader::BufferedReader; use crate::{ Result, Error, PublicKeyAlgorithm, SymmetricAlgorithm, HashAlgorithm, }; use crate::types::Curve; use crate::crypto::{ mem::Protected, mpi::{self, MPI, ProtectedMPI}, }; use crate::parse::{ PacketHeaderParser, Cookie, }; impl mpi::PublicKey { /// Parses a set of OpenPGP MPIs representing a public key. /// /// See [Section 3.2 of RFC 9580] for details. /// /// [Section 3.2 of RFC 9580]: https://www.rfc-editor.org/rfc/rfc9580.html#section-3.2 pub fn parse<R: Read + Send + Sync>(algo: PublicKeyAlgorithm, reader: R) -> Result<Self> { let bio = buffered_reader::Generic::with_cookie( reader, None, Cookie::default()); let mut php = PacketHeaderParser::new_naked(bio.into_boxed()); Self::_parse(algo, &mut php) } /// Parses a set of OpenPGP MPIs representing a public key. /// /// See [Section 3.2 of RFC 9580] for details. /// /// [Section 3.2 of RFC 9580]: https://www.rfc-editor.org/rfc/rfc9580.html#section-3.2 pub(crate) fn _parse( algo: PublicKeyAlgorithm, php: &mut PacketHeaderParser<'_>) -> Result<Self> { use crate::PublicKeyAlgorithm::*; #[allow(deprecated)] match algo { RSAEncryptSign | RSAEncrypt | RSASign => { let n = MPI::parse("rsa_public_n_len", "rsa_public_n", php)?; let e = MPI::parse("rsa_public_e_len", "rsa_public_e", php)?; Ok(mpi::PublicKey::RSA { e, n }) } DSA => { let p = MPI::parse("dsa_public_p_len", "dsa_public_p", php)?; let q = MPI::parse("dsa_public_q_len", "dsa_public_q", php)?; let g = MPI::parse("dsa_public_g_len", "dsa_public_g", php)?; let y = MPI::parse("dsa_public_y_len", "dsa_public_y", php)?; Ok(mpi::PublicKey::DSA { p, q, g, y, }) } ElGamalEncrypt | ElGamalEncryptSign => { let p = MPI::parse("elgamal_public_p_len", "elgamal_public_p", php)?; let g = MPI::parse("elgamal_public_g_len", "elgamal_public_g", php)?; let y = MPI::parse("elgamal_public_y_len", "elgamal_public_y", php)?; Ok(mpi::PublicKey::ElGamal { p, g, y, }) } EdDSA => { let curve_len = php.parse_u8("curve_len")? as usize; let curve = php.parse_bytes("curve", curve_len)?; let q = MPI::parse("eddsa_public_len", "eddsa_public", php)?; Ok(mpi::PublicKey::EdDSA { curve: Curve::from_oid(&curve), q }) } ECDSA => { let curve_len = php.parse_u8("curve_len")? as usize; let curve = php.parse_bytes("curve", curve_len)?; let q = MPI::parse("ecdsa_public_len", "ecdsa_public", php)?; Ok(mpi::PublicKey::ECDSA { curve: Curve::from_oid(&curve), q }) } ECDH => { let curve_len = php.parse_u8("curve_len")? as usize; let curve = php.parse_bytes("curve", curve_len)?; let q = MPI::parse("ecdh_public_len", "ecdh_public", php)?; let kdf_len = php.parse_u8("kdf_len")?; if kdf_len != 3 { return Err(Error::MalformedPacket( "wrong kdf length".into()).into()); } let reserved = php.parse_u8("kdf_reserved")?; if reserved != 1 { return Err(Error::MalformedPacket( format!("Reserved kdf field must be 0x01, \ got 0x{:x}", reserved)).into()); } let hash: HashAlgorithm = php.parse_u8("kdf_hash")?.into(); let sym: SymmetricAlgorithm = php.parse_u8("kek_symm")?.into(); Ok(mpi::PublicKey::ECDH { curve: Curve::from_oid(&curve), q, hash, sym }) } X25519 => { let mut u = [0; 32]; php.parse_bytes_into("x25519_public", &mut u)?; Ok(mpi::PublicKey::X25519 { u }) }, X448 => { let mut u = [0; 56]; php.parse_bytes_into("x448_public", &mut u)?; Ok(mpi::PublicKey::X448 { u: Box::new(u) }) }, Ed25519 => { let mut a = [0; 32]; php.parse_bytes_into("ed25519_public", &mut a)?; Ok(mpi::PublicKey::Ed25519 { a }) }, Ed448 => { let mut a = [0; 57]; php.parse_bytes_into("ed448_public", &mut a)?; Ok(mpi::PublicKey::Ed448 { a: Box::new(a) }) }, Unknown(_) | Private(_) => { let mut mpis = Vec::new(); while let Ok(mpi) = MPI::parse("unknown_len", "unknown", php) { mpis.push(mpi); } let rest = php.parse_bytes_eof("rest")?; Ok(mpi::PublicKey::Unknown { mpis: mpis.into_boxed_slice(), rest: rest.into_boxed_slice(), }) } } } } impl mpi::SecretKeyMaterial { /// Parses secret key MPIs for `algo` plus their SHA1 checksum. /// /// Fails if the checksum is wrong. #[deprecated( since = "1.14.0", note = "Leaks secrets into the heap, use [`SecretKeyMaterial::from_bytes_with_checksum`]")] pub fn parse_with_checksum<R: Read + Send + Sync>(algo: PublicKeyAlgorithm, reader: R, checksum: mpi::SecretKeyChecksum) -> Result<Self> { let bio = buffered_reader::Generic::with_cookie( reader, None, Cookie::default()); let mut php = PacketHeaderParser::new_naked(bio.into_boxed()); Self::_parse(algo, &mut php, Some(checksum)) } /// Parses secret key MPIs for `algo` plus their SHA1 checksum. /// /// Fails if the checksum is wrong. pub fn from_bytes_with_checksum(algo: PublicKeyAlgorithm, bytes: &[u8], checksum: mpi::SecretKeyChecksum) -> Result<Self> { let bio = buffered_reader::Memory::with_cookie( bytes, Cookie::default()); let mut php = PacketHeaderParser::new_naked(bio.into_boxed()); Self::_parse(algo, &mut php, Some(checksum)) } /// Parses a set of OpenPGP MPIs representing a secret key. /// /// See [Section 3.2 of RFC 9580] for details. /// /// [Section 3.2 of RFC 9580]: https://www.rfc-editor.org/rfc/rfc9580.html#section-3.2 #[deprecated( since = "1.14.0", note = "Leaks secrets into the heap, use [`SecretKeyMaterial::from_bytes`]")] pub fn parse<R: Read + Send + Sync>(algo: PublicKeyAlgorithm, reader: R) -> Result<Self> { let bio = buffered_reader::Generic::with_cookie( reader, None, Cookie::default()); let mut php = PacketHeaderParser::new_naked(bio.into_boxed()); Self::_parse(algo, &mut php, None) } /// Parses a set of OpenPGP MPIs representing a secret key. /// /// See [Section 3.2 of RFC 9580] for details. /// /// [Section 3.2 of RFC 9580]: https://www.rfc-editor.org/rfc/rfc9580.html#section-3.2 pub fn from_bytes(algo: PublicKeyAlgorithm, buf: &[u8]) -> Result<Self> { let bio = buffered_reader::Memory::with_cookie( buf, Cookie::default()); let mut php = PacketHeaderParser::new_naked(bio.into_boxed()); Self::_parse(algo, &mut php, None) } /// Parses a set of OpenPGP MPIs representing a secret key. /// /// See [Section 3.2 of RFC 9580] for details. /// /// [Section 3.2 of RFC 9580]: https://www.rfc-editor.org/rfc/rfc9580.html#section-3.2 pub(crate) fn _parse( algo: PublicKeyAlgorithm, php: &mut PacketHeaderParser<'_>, checksum: Option<mpi::SecretKeyChecksum>, ) -> Result<Self> { use crate::PublicKeyAlgorithm::*; // We want to get the data we are going to read next as raw // bytes later. To do so, we remember the cursor position now // before reading the MPIs. let mpis_start = php.reader.total_out(); #[allow(deprecated)] let mpis: Result<Self> = match algo { RSAEncryptSign | RSAEncrypt | RSASign => { Ok(mpi::SecretKeyMaterial::RSA { d: ProtectedMPI::parse( "rsa_secret_d_len", "rsa_secret_d", php)?, p: ProtectedMPI::parse( "rsa_secret_p_len", "rsa_secret_p", php)?, q: ProtectedMPI::parse( "rsa_secret_q_len", "rsa_secret_q", php)?, u: ProtectedMPI::parse( "rsa_secret_u_len", "rsa_secret_u", php)?, }) } DSA => { Ok(mpi::SecretKeyMaterial::DSA { x: ProtectedMPI::parse( "dsa_secret_len", "dsa_secret", php)?, }) } ElGamalEncrypt | ElGamalEncryptSign => { Ok(mpi::SecretKeyMaterial::ElGamal { x: ProtectedMPI::parse( "elgamal_secret_len", "elgamal_secret", php)?, }) } EdDSA => { Ok(mpi::SecretKeyMaterial::EdDSA { scalar: ProtectedMPI::parse( "eddsa_secret_len", "eddsa_secret", php)?, }) } ECDSA => { Ok(mpi::SecretKeyMaterial::ECDSA { scalar: ProtectedMPI::parse( "ecdsa_secret_len", "ecdsa_secret", php)?, }) } ECDH => { Ok(mpi::SecretKeyMaterial::ECDH { scalar: ProtectedMPI::parse( "ecdh_secret_len", "ecdh_secret", php)?, }) } X25519 => { let mut x: Protected = vec![0; 32].into(); php.parse_bytes_into("x25519_secret", &mut x)?; Ok(mpi::SecretKeyMaterial::X25519 { x }) }, X448 => { let mut x: Protected = vec![0; 56].into(); php.parse_bytes_into("x448_secret", &mut x)?; Ok(mpi::SecretKeyMaterial::X448 { x }) }, Ed25519 => { let mut x: Protected = vec![0; 32].into(); php.parse_bytes_into("ed25519_secret", &mut x)?; Ok(mpi::SecretKeyMaterial::Ed25519 { x }) }, Ed448 => { let mut x: Protected = vec![0; 57].into(); php.parse_bytes_into("ed448_secret", &mut x)?; Ok(mpi::SecretKeyMaterial::Ed448 { x }) }, Unknown(_) | Private(_) => { let mut mpis = Vec::new(); while let Ok(mpi) = ProtectedMPI::parse("unknown_len", "unknown", php) { mpis.push(mpi); } let rest = php.parse_bytes_eof("rest")?; Ok(mpi::SecretKeyMaterial::Unknown { mpis: mpis.into_boxed_slice(), rest: rest.into(), }) } }; let mpis = mpis?; if let Some(checksum) = checksum { // We want to get the data we are going to read next as // raw bytes later. To do so, we remember the cursor // position now after reading the MPIs and compute the // length. let mpis_len = php.reader.total_out() - mpis_start; // We do a bit of acrobatics to avoid copying the secrets. // We read the checksum now, so that we can freely // manipulate the Dup reader and get a borrow of the raw // MPIs. let their_chksum = php.parse_bytes("checksum", checksum.len())?; // Remember how much we read in total for a sanity check. let total_out = php.reader.total_out(); // Now get the secrets as raw byte slice. php.reader.rewind(); php.reader.consume(mpis_start); let data = &php.reader.data_consume_hard(mpis_len)?[..mpis_len]; let good = match checksum { mpi::SecretKeyChecksum::SHA1 => { // Compute SHA1 hash. let mut hsh = HashAlgorithm::SHA1.context().unwrap() .for_digest(); hsh.update(data); let mut our_chksum = [0u8; 20]; let _ = hsh.digest(&mut our_chksum); our_chksum == their_chksum[..] }, mpi::SecretKeyChecksum::Sum16 => { // Compute sum. let our_chksum = data.iter() .fold(0u16, |acc, v| acc.wrapping_add(*v as u16)) .to_be_bytes(); our_chksum == their_chksum[..] }, }; // Finally, consume the checksum to fix the state of the // Dup reader. php.reader.consume(checksum.len()); // See if we got the state right. debug_assert_eq!(total_out, php.reader.total_out()); if good { Ok(mpis) } else { Err(Error::MalformedMPI("checksum wrong".to_string()).into()) } } else { Ok(mpis) } } } impl mpi::Ciphertext { /// Parses a set of OpenPGP MPIs representing a ciphertext. /// /// Expects MPIs for a public key algorithm `algo`s ciphertext. /// See [Section 3.2 of RFC 9580] for details. /// /// [Section 3.2 of RFC 9580]: https://www.rfc-editor.org/rfc/rfc9580.html#section-3.2 pub fn parse<R: Read + Send + Sync>(algo: PublicKeyAlgorithm, reader: R) -> Result<Self> { let bio = buffered_reader::Generic::with_cookie( reader, None, Cookie::default()); let mut php = PacketHeaderParser::new_naked(bio.into_boxed()); Self::_parse(algo, &mut php) } /// Parses a set of OpenPGP MPIs representing a ciphertext. /// /// Expects MPIs for a public key algorithm `algo`s ciphertext. /// See [Section 3.2 of RFC 9580] for details. /// /// [Section 3.2 of RFC 9580]: https://www.rfc-editor.org/rfc/rfc9580.html#section-3.2 pub(crate) fn _parse( algo: PublicKeyAlgorithm, php: &mut PacketHeaderParser<'_>) -> Result<Self> { use crate::PublicKeyAlgorithm::*; #[allow(deprecated)] match algo { RSAEncryptSign | RSAEncrypt => { let c = MPI::parse("rsa_ciphertxt_len", "rsa_ciphertxt", php)?; Ok(mpi::Ciphertext::RSA { c, }) } ElGamalEncrypt | ElGamalEncryptSign => { let e = MPI::parse("elgamal_e_len", "elgamal_e", php)?; let c = MPI::parse("elgamal_c_len", "elgamal_c", php)?; Ok(mpi::Ciphertext::ElGamal { e, c, }) } ECDH => { let e = MPI::parse("ecdh_e_len", "ecdh_e", php)?; let key_len = php.parse_u8("ecdh_esk_len")? as usize; let key = Vec::from(&php.parse_bytes("ecdh_esk", key_len)? [..key_len]); Ok(mpi::Ciphertext::ECDH { e, key: key.into_boxed_slice() }) } X25519 => { let mut e = [0; 32]; php.parse_bytes_into("x25519_e", &mut e)?; let key_len = php.parse_u8("x25519_esk_len")? as usize; let key = Vec::from(&php.parse_bytes("x25519_esk", key_len)? [..key_len]); Ok(mpi::Ciphertext::X25519 { e: Box::new(e), key: key.into() }) }, X448 => { let mut e = [0; 56]; php.parse_bytes_into("x448_e", &mut e)?; let key_len = php.parse_u8("x448_esk_len")? as usize; let key = Vec::from(&php.parse_bytes("x448_esk", key_len)? [..key_len]); Ok(mpi::Ciphertext::X448 { e: Box::new(e), key: key.into() }) }, Unknown(_) | Private(_) => { let mut mpis = Vec::new(); while let Ok(mpi) = MPI::parse("unknown_len", "unknown", php) { mpis.push(mpi); } let rest = php.parse_bytes_eof("rest")?; Ok(mpi::Ciphertext::Unknown { mpis: mpis.into_boxed_slice(), rest: rest.into_boxed_slice(), }) } RSASign | DSA | EdDSA | ECDSA | Ed25519 | Ed448 => Err(Error::InvalidArgument( format!("not an encryption algorithm: {:?}", algo)).into()), } } } impl mpi::Signature { /// Parses a set of OpenPGP MPIs representing a signature. /// /// Expects MPIs for a public key algorithm `algo`s signature. /// See [Section 3.2 of RFC 9580] for details. /// /// [Section 3.2 of RFC 9580]: https://www.rfc-editor.org/rfc/rfc9580.html#section-3.2 pub fn parse<R: Read + Send + Sync>(algo: PublicKeyAlgorithm, reader: R) -> Result<Self> { let bio = buffered_reader::Generic::with_cookie( reader, None, Cookie::default()); let mut php = PacketHeaderParser::new_naked(bio.into_boxed()); Self::_parse(algo, &mut php) } /// Parses a set of OpenPGP MPIs representing a signature. /// /// Expects MPIs for a public key algorithm `algo`s signature. /// See [Section 3.2 of RFC 9580] for details. /// /// [Section 3.2 of RFC 9580]: https://www.rfc-editor.org/rfc/rfc9580.html#section-3.2 pub(crate) fn _parse( algo: PublicKeyAlgorithm, php: &mut PacketHeaderParser<'_>) -> Result<Self> { use crate::PublicKeyAlgorithm::*; #[allow(deprecated)] match algo { RSAEncryptSign | RSASign => { let s = MPI::parse("rsa_signature_len", "rsa_signature", php)?; Ok(mpi::Signature::RSA { s, }) } DSA => { let r = MPI::parse("dsa_sig_r_len", "dsa_sig_r", php)?; let s = MPI::parse("dsa_sig_s_len", "dsa_sig_s", php)?; Ok(mpi::Signature::DSA { r, s, }) } ElGamalEncryptSign => { let r = MPI::parse("elgamal_sig_r_len", "elgamal_sig_r", php)?; let s = MPI::parse("elgamal_sig_s_len", "elgamal_sig_s", php)?; Ok(mpi::Signature::ElGamal { r, s, }) } EdDSA => { let r = MPI::parse("eddsa_sig_r_len", "eddsa_sig_r", php)?; let s = MPI::parse("eddsa_sig_s_len", "eddsa_sig_s", php)?; Ok(mpi::Signature::EdDSA { r, s, }) } ECDSA => { let r = MPI::parse("ecdsa_sig_r_len", "ecdsa_sig_r", php)?; let s = MPI::parse("ecdsa_sig_s_len", "ecdsa_sig_s", php)?; Ok(mpi::Signature::ECDSA { r, s, }) } Ed25519 => { let mut s = [0; 64]; php.parse_bytes_into("ed25519_sig", &mut s)?; Ok(mpi::Signature::Ed25519 { s: Box::new(s) }) }, Ed448 => { let mut s = [0; 114]; php.parse_bytes_into("ed448_sig", &mut s)?; Ok(mpi::Signature::Ed448 { s: Box::new(s) }) }, Unknown(_) | Private(_) => { let mut mpis = Vec::new(); while let Ok(mpi) = MPI::parse("unknown_len", "unknown", php) { mpis.push(mpi); } let rest = php.parse_bytes_eof("rest")?; Ok(mpi::Signature::Unknown { mpis: mpis.into_boxed_slice(), rest: rest.into_boxed_slice(), }) } RSAEncrypt | ElGamalEncrypt | ECDH | X25519 | X448 => Err(Error::InvalidArgument( format!("not a signature algorithm: {:?}", algo)).into()), } } } #[test] fn mpis_parse_test() { use std::io::Cursor; use super::Parse; use crate::PublicKeyAlgorithm::*; use crate::serialize::MarshalInto; // Dummy RSA public key. { let buf = Cursor::new("\x00\x01\x01\x00\x02\x02"); let mpis = mpi::PublicKey::parse(RSAEncryptSign, buf).unwrap(); //assert_eq!(mpis.serialized_len(), 6); match &mpis { &mpi::PublicKey::RSA{ ref n, ref e } => { assert_eq!(n.bits(), 1); assert_eq!(n.value()[0], 1); assert_eq!(n.value().len(), 1); assert_eq!(e.bits(), 2); assert_eq!(e.value()[0], 2); assert_eq!(e.value().len(), 1); } _ => assert!(false), } } // The number 2. { let buf = Cursor::new("\x00\x02\x02"); let mpis = mpi::Ciphertext::parse(RSAEncryptSign, buf).unwrap(); assert_eq!(mpis.serialized_len(), 3); } // The number 511. let mpi = MPI::from_bytes(b"\x00\x09\x01\xff").unwrap(); assert_eq!(mpi.value().len(), 2); assert_eq!(mpi.bits(), 9); assert_eq!(mpi.value()[0], 1); assert_eq!(mpi.value()[1], 0xff); // The number 1, incorrectly encoded (the length should be 1, // not 2). assert!(MPI::from_bytes(b"\x00\x02\x01").is_err()); } ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/src/parse/packet_parser_builder.rs��������������������������������������������0000644�0000000�0000000�00000044702�10461020230�0021546�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������use buffered_reader::BufferedReader; use crate::Result; use crate::parse::PacketParserResult; use crate::parse::PacketParser; use crate::parse::PacketParserEOF; use crate::parse::PacketParserState; use crate::parse::PacketParserSettings; use crate::parse::ParserResult; use crate::parse::Parse; use crate::parse::Cookie; use crate::armor; use crate::packet; /// Controls transparent stripping of ASCII armor when parsing. /// /// When parsing OpenPGP data streams, the [`PacketParser`] will by /// default automatically detect and remove any ASCII armor encoding /// (see [Section 6 of RFC 9580]). This automatism can be disabled /// and fine-tuned using [`PacketParserBuilder::dearmor`]. /// /// [Section 6 of RFC 9580]: https://www.rfc-editor.org/rfc/rfc9580.html#section-6 /// [`PacketParserBuilder::dearmor`]: PacketParserBuilder::dearmor() #[derive(PartialEq)] #[non_exhaustive] pub enum Dearmor { /// Unconditionally treat the input as if it were an OpenPGP /// message encoded using ASCII armor. /// /// Parsing a binary encoded OpenPGP message using this mode will /// fail. The [`ReaderMode`] allow further customization of the /// ASCII armor parser. /// /// [`ReaderMode`]: crate::armor::ReaderMode Enabled(armor::ReaderMode), /// Unconditionally treat the input as if it were a binary OpenPGP /// message. /// /// Parsing an ASCII armor encoded OpenPGP message using this mode will /// fail. Disabled, /// If input does not appear to be a binary encoded OpenPGP /// message, treat it as if it were encoded using ASCII armor. /// /// This is the default. The [`ReaderMode`] allow further /// customization of the ASCII armor parser. /// /// [`ReaderMode`]: crate::armor::ReaderMode Auto(armor::ReaderMode), } assert_send_and_sync!(Dearmor); impl Default for Dearmor { fn default() -> Self { Dearmor::Auto(Default::default()) } } /// This is the level at which we insert the dearmoring filter into /// the buffered reader stack. pub(super) const ARMOR_READER_LEVEL: isize = -2; /// A builder for configuring a `PacketParser`. /// /// Since the default settings are usually appropriate, this mechanism /// will only be needed in exceptional circumstances. Instead use, /// for instance, `PacketParser::from_file` or /// `PacketParser::from_reader` to start parsing an OpenPGP message. /// /// # Examples /// /// ```rust /// # fn main() -> sequoia_openpgp::Result<()> { /// use sequoia_openpgp as openpgp; /// use openpgp::parse::{Parse, PacketParserResult, PacketParserBuilder}; /// /// // Parse a message. /// let message_data: &[u8] = // ... /// # include_bytes!("../../tests/data/messages/compressed-data-algo-0.pgp"); /// let mut ppr = PacketParserBuilder::from_bytes(message_data)? /// // Customize the `PacketParserBuilder` here. /// .build()?; /// while let PacketParserResult::Some(mut pp) = ppr { /// // ... /// /// // Start parsing the next packet, recursing. /// ppr = pp.recurse()?.1; /// } /// # Ok(()) } /// ``` pub struct PacketParserBuilder<'a> { bio: Box<dyn BufferedReader<Cookie> + 'a>, dearmor: Dearmor, settings: PacketParserSettings, csf_transformation: bool, } assert_send_and_sync!(PacketParserBuilder<'_>); impl<'a> Parse<'a, PacketParserBuilder<'a>> for PacketParserBuilder<'a> { /// Starts parsing an OpenPGP object stored in a `BufferedReader` object. /// /// This function returns a `PacketParser` for the first packet in /// the stream. fn from_buffered_reader<R>(reader: R) -> Result<PacketParserBuilder<'a>> where R: BufferedReader<Cookie> + 'a, { PacketParserBuilder::from_cookie_reader(reader.into_boxed()) } } impl<'a> crate::seal::Sealed for PacketParserBuilder<'a> {} impl<'a> PacketParserBuilder<'a> { // Creates a `PacketParserBuilder` for an OpenPGP message stored // in a `BufferedReader` object. // // Note: this clears the `level` field of the // `Cookie` cookie. pub(crate) fn from_cookie_reader(mut bio: Box<dyn BufferedReader<Cookie> + 'a>) -> Result<Self> { bio.cookie_mut().level = None; Ok(PacketParserBuilder { bio, dearmor: Default::default(), settings: PacketParserSettings::default(), csf_transformation: false, }) } /// Sets the maximum recursion depth. /// /// Setting this to 0 means that the `PacketParser` will never /// recurse; it will only parse the top-level packets. /// /// This is a u8, because recursing more than 255 times makes no /// sense. The default is [`DEFAULT_MAX_RECURSION_DEPTH`]. /// (GnuPG defaults to a maximum recursion depth of 32.) /// /// [`DEFAULT_MAX_RECURSION_DEPTH`]: crate::parse::DEFAULT_MAX_RECURSION_DEPTH /// /// # Examples /// /// ```rust /// # fn main() -> sequoia_openpgp::Result<()> { /// use sequoia_openpgp as openpgp; /// use openpgp::Packet; /// use openpgp::parse::{Parse, PacketParserResult, PacketParserBuilder}; /// /// // Parse a compressed message. /// let message_data: &[u8] = // ... /// # include_bytes!("../../tests/data/messages/compressed-data-algo-0.pgp"); /// let mut ppr = PacketParserBuilder::from_bytes(message_data)? /// .max_recursion_depth(0) /// .build()?; /// while let PacketParserResult::Some(mut pp) = ppr { /// assert_eq!(pp.recursion_depth(), 0); /// /// // Start parsing the next packet, recursing. /// ppr = pp.recurse()?.1; /// } /// # Ok(()) } /// ``` pub fn max_recursion_depth(mut self, value: u8) -> Self { self.settings.max_recursion_depth = value; self } /// Sets the maximum size in bytes of non-container packets. /// /// Packets that exceed this limit will be returned as /// `Packet::Unknown`, with the error set to /// `Error::PacketTooLarge`. /// /// This limit applies to any packet type that is *not* a /// container packet, i.e. any packet that is not a literal data /// packet, a compressed data packet, a symmetrically encrypted /// data packet, or an AEAD encrypted data packet. /// /// The default is [`DEFAULT_MAX_PACKET_SIZE`]. /// /// [`DEFAULT_MAX_PACKET_SIZE`]: crate::parse::DEFAULT_MAX_PACKET_SIZE /// /// # Examples /// /// ```rust /// # fn main() -> sequoia_openpgp::Result<()> { /// use sequoia_openpgp as openpgp; /// use openpgp::{Error, Packet}; /// use openpgp::packet::Tag; /// use openpgp::parse::{Parse, PacketParserResult, PacketParserBuilder}; /// use openpgp::serialize::MarshalInto; /// /// // Parse a signed message. /// let message_data: &[u8] = // ... /// # include_bytes!("../../tests/data/messages/signed-1.gpg"); /// let mut ppr = PacketParserBuilder::from_bytes(message_data)? /// .max_packet_size(256) // Only parse 256 bytes of headers. /// .buffer_unread_content() // Used below. /// .build()?; /// while let PacketParserResult::Some(mut pp) = ppr { /// match &pp.packet { /// Packet::OnePassSig(p) => /// // The OnePassSig packet was small enough. /// assert!(p.serialized_len() < 256), /// Packet::Literal(p) => /// // Likewise the `Literal` packet, excluding the body. /// assert!(p.serialized_len() - p.body().len() < 256), /// Packet::Unknown(p) => /// // The signature packet was too big. /// assert_eq!( /// &Error::PacketTooLarge(Tag::Signature, 307, 256), /// p.error().downcast_ref().unwrap()), /// _ => unreachable!(), /// } /// /// // Start parsing the next packet, recursing. /// ppr = pp.recurse()?.1; /// } /// # Ok(()) } /// ``` pub fn max_packet_size(mut self, value: u32) -> Self { self.settings.max_packet_size = value; self } /// Causes `PacketParser::build()` to buffer any unread content. /// /// The unread content can be accessed using [`Literal::body`], /// [`Unknown::body`], or [`Container::body`]. /// /// [`Literal::body`]: crate::packet::Literal::body() /// [`Unknown::body`]: crate::packet::Unknown::body() /// [`Container::body`]: crate::packet::Container::body() /// /// # Examples /// /// ```rust /// # fn main() -> sequoia_openpgp::Result<()> { /// use sequoia_openpgp as openpgp; /// use openpgp::Packet; /// use openpgp::parse::{Parse, PacketParserResult, PacketParserBuilder}; /// /// // Parse a simple message. /// let message_data = b"\xcb\x12t\x00\x00\x00\x00\x00Hello world."; /// let mut ppr = PacketParserBuilder::from_bytes(message_data)? /// .buffer_unread_content() /// .build()?; /// while let PacketParserResult::Some(mut pp) = ppr { /// // Start parsing the next packet, recursing. /// let (packet, tmp) = pp.recurse()?; /// ppr = tmp; /// /// match packet { /// Packet::Literal(l) => assert_eq!(l.body(), b"Hello world."), /// _ => unreachable!(), /// } /// } /// # Ok(()) } /// ``` pub fn buffer_unread_content(mut self) -> Self { self.settings.buffer_unread_content = true; self } /// Causes `PacketParser::finish()` to drop any unread content. /// /// This is the default. /// /// # Examples /// /// ```rust /// # fn main() -> sequoia_openpgp::Result<()> { /// use sequoia_openpgp as openpgp; /// use openpgp::Packet; /// use openpgp::parse::{Parse, PacketParserResult, PacketParserBuilder}; /// /// // Parse a simple message. /// let message_data = b"\xcb\x12t\x00\x00\x00\x00\x00Hello world."; /// let mut ppr = PacketParserBuilder::from_bytes(message_data)? /// .drop_unread_content() /// .build()?; /// while let PacketParserResult::Some(mut pp) = ppr { /// // Start parsing the next packet, recursing. /// let (packet, tmp) = pp.recurse()?; /// ppr = tmp; /// /// match packet { /// Packet::Literal(l) => assert_eq!(l.body(), b""), /// _ => unreachable!(), /// } /// } /// # Ok(()) } /// ``` pub fn drop_unread_content(mut self) -> Self { self.settings.buffer_unread_content = false; self } /// Controls mapping. /// /// Note that enabling mapping buffers all the data. /// /// # Examples /// /// ``` /// # fn main() -> sequoia_openpgp::Result<()> { /// use sequoia_openpgp as openpgp; /// use openpgp::parse::{Parse, PacketParserBuilder}; /// /// let message_data = b"\xcb\x12t\x00\x00\x00\x00\x00Hello world."; /// let pp = PacketParserBuilder::from_bytes(message_data)? /// .map(true) // Enable mapping. /// .build()? /// .expect("One packet, not EOF"); /// let map = pp.map().expect("Mapping is enabled"); /// /// assert_eq!(map.iter().nth(0).unwrap().name(), "CTB"); /// assert_eq!(map.iter().nth(0).unwrap().offset(), 0); /// assert_eq!(map.iter().nth(0).unwrap().as_bytes(), &[0xcb]); /// # Ok(()) } /// ``` pub fn map(mut self, enable: bool) -> Self { self.settings.map = enable; self } /// Controls dearmoring. /// /// By default, if the input does not appear to be plain binary /// OpenPGP data, we assume that it is ASCII-armored. This method /// can be used to tweak the behavior. See [`Dearmor`] for /// details. /// /// /// # Examples /// /// ``` /// # fn main() -> sequoia_openpgp::Result<()> { /// use sequoia_openpgp as openpgp; /// use openpgp::parse::{Parse, PacketParserBuilder, Dearmor}; /// /// let message_data = b"\xcb\x12t\x00\x00\x00\x00\x00Hello world."; /// let pp = PacketParserBuilder::from_bytes(message_data)? /// .dearmor(Dearmor::Disabled) // Disable dearmoring. /// .build()? /// .expect("One packet, not EOF"); /// # Ok(()) } /// ``` pub fn dearmor(mut self, mode: Dearmor) -> Self { self.dearmor = mode; self } /// Controls automatic hashing. /// /// When encountering a [`OnePassSig`] packet, the packet parser /// will, by default, start hashing later packets using the hash /// algorithm specified in the packet. In some cases, this is not /// needed, and hashing will incur a non-trivial overhead. /// /// If automatic hashing is disabled, then hashing may be /// explicitly enabled using [`PacketParser::start_hashing`] while /// parsing each [`OnePassSig`] packet. /// /// [`OnePassSig`]: crate::packet::OnePassSig /// /// # Examples /// /// ``` /// # fn main() -> sequoia_openpgp::Result<()> { /// # use sequoia_openpgp as openpgp; /// # use openpgp::parse::{Parse, PacketParserBuilder}; /// # /// let message_data = b"\xcb\x12t\x00\x00\x00\x00\x00Hello world."; /// let pp = PacketParserBuilder::from_bytes(message_data)? /// .automatic_hashing(false) // Disable automatic hashing. /// .build()? /// .expect("One packet, not EOF"); /// # Ok(()) } /// ``` pub fn automatic_hashing(mut self, enable: bool) -> Self { self.settings.automatic_hashing = enable; self } /// Controls transparent transformation of messages using the /// cleartext signature framework into signed messages. /// /// XXX: This could be controlled by `Dearmor`, but we cannot add /// values to that now. pub(crate) fn csf_transformation(mut self, enable: bool) -> Self { self.csf_transformation = enable; self } /// Builds the `PacketParser`. /// /// # Examples /// /// ```rust /// # fn main() -> sequoia_openpgp::Result<()> { /// use sequoia_openpgp as openpgp; /// use openpgp::parse::{Parse, PacketParserResult, PacketParserBuilder}; /// /// // Parse a message. /// let message_data: &[u8] = // ... /// # include_bytes!("../../tests/data/messages/compressed-data-algo-0.pgp"); /// let mut ppr = PacketParserBuilder::from_bytes(message_data)? /// // Customize the `PacketParserBuilder` here. /// .build()?; /// while let PacketParserResult::Some(mut pp) = ppr { /// // ... /// /// // Start parsing the next packet, recursing. /// ppr = pp.recurse()?.1; /// } /// # Ok(()) } #[allow(clippy::redundant_pattern_matching)] pub fn build(mut self) -> Result<PacketParserResult<'a>> where Self: 'a { let state = PacketParserState::new(self.settings); let dearmor_mode = match self.dearmor { Dearmor::Enabled(mode) => Some(mode), Dearmor::Disabled => None, Dearmor::Auto(mode) => { if self.bio.eof() { None } else { let mut reader = buffered_reader::Dup::with_cookie( self.bio, Cookie::default()); let header = packet::Header::parse(&mut reader); self.bio = Box::new(reader).into_inner().unwrap(); if let Ok(header) = header { if let Err(_) = header.valid(false) { // Invalid header: better try an ASCII armor // decoder. Some(mode) } else { None } } else { // Failed to parse the header: better try an ASCII // armor decoder. Some(mode) } } } }; if let Some(mode) = dearmor_mode { // Add a top-level filter so that it is peeled off when // the packet parser is finished. We use level -2 for that. self.bio = armor::Reader::from_cookie_reader_csft(self.bio, Some(mode), Cookie::new(ARMOR_READER_LEVEL), self.csf_transformation) .into_boxed(); } // Parse the first packet. match PacketParser::parse(Box::new(self.bio), state, vec![ 0 ])? { ParserResult::Success(mut pp) => { // We successfully parsed the first packet's header. pp.state.message_validator.push( pp.packet.tag(), pp.packet.version(), &[0]); pp.state.keyring_validator.push(pp.packet.tag()); pp.state.cert_validator.push(pp.packet.tag()); Ok(PacketParserResult::Some(pp)) }, ParserResult::EOF((reader, state, _path)) => { // `bio` is empty. We're done. Ok(PacketParserResult::EOF(PacketParserEOF::new(state, reader))) } } } } #[cfg(test)] mod tests { use super::*; #[test] fn armor() { // Not ASCII armor encoded data. let msg = crate::tests::message("sig.gpg"); // Make sure we can read the first packet. let ppr = PacketParserBuilder::from_bytes(msg).unwrap() .dearmor(Dearmor::Disabled) .build(); assert_match!(Ok(PacketParserResult::Some(ref _pp)) = ppr); let ppr = PacketParserBuilder::from_bytes(msg).unwrap() .dearmor(Dearmor::Auto(Default::default())) .build(); assert_match!(Ok(PacketParserResult::Some(ref _pp)) = ppr); let ppr = PacketParserBuilder::from_bytes(msg).unwrap() .dearmor(Dearmor::Enabled(Default::default())) .build(); assert_match!(Err(_) = ppr); // ASCII armor encoded data. let msg = crate::tests::message("a-cypherpunks-manifesto.txt.ed25519.sig"); // Make sure we can read the first packet. let ppr = PacketParserBuilder::from_bytes(msg).unwrap() .dearmor(Dearmor::Disabled) .build(); assert_match!(Err(_) = ppr); let ppr = PacketParserBuilder::from_bytes(msg).unwrap() .dearmor(Dearmor::Auto(Default::default())) .build(); assert_match!(Ok(PacketParserResult::Some(ref _pp)) = ppr); let ppr = PacketParserBuilder::from_bytes(msg).unwrap() .dearmor(Dearmor::Enabled(Default::default())) .build(); assert_match!(Ok(PacketParserResult::Some(ref _pp)) = ppr); } } ��������������������������������������������������������������sequoia-openpgp-2.0.0/src/parse/packet_pile_parser.rs�����������������������������������������������0000644�0000000�0000000�00000043401�10461020230�0021044�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������use std::convert::TryFrom; use crate::{ Result, Packet, PacketPile, }; use crate::parse::{ PacketParserBuilder, PacketParserEOF, PacketParserResult, PacketParser, Parse, Cookie }; use buffered_reader::BufferedReader; /// Parses an OpenPGP stream with the convenience of /// [`PacketPile::from_file`] and the flexibility of a /// [`PacketParser`]. /// /// [`PacketPile::from_file`]: ../struct.PacketPile.html#impl-Parse<%27a%2C%20PacketPile> /// /// Like [`PacketPile::from_file`] (and unlike [`PacketParser`]), a /// `PacketPileParser` parses an OpenPGP message and returns a /// [`PacketPile`]. But, unlike [`PacketPile::from_file`] (and like /// [`PacketParser`]), it allows the caller to inspect each packet as /// it is being parsed. /// /// [`PacketPile`]: crate::PacketPile /// /// Thus, using a `PacketPileParser`, it is possible to decide on a /// per-packet basis whether to stream, buffer or drop the packet's /// body, whether to recurse into a container, or whether to abort /// processing, for example. And, `PacketPileParser` conveniently packs /// the packets into a [`PacketPile`]. /// /// If old packets don't need to be retained, then [`PacketParser`] /// should be preferred. If no per-packet processing needs to be /// done, then [`PacketPile::from_file`] will be slightly faster. /// /// # Examples /// /// These examples demonstrate how to process packet bodies by parsing /// the simplest possible OpenPGP message containing just a single /// literal data packet with the body "Hello world.". There are three /// options. First, the body can be dropped. Second, it can be /// buffered. Lastly, the body can be streamed. In general, /// streaming should be preferred, because it avoids buffering in /// Sequoia. /// /// This example demonstrates simply ignoring the packet body: /// /// ```rust /// # fn main() -> sequoia_openpgp::Result<()> { /// use sequoia_openpgp as openpgp; /// use openpgp::Packet; /// use openpgp::parse::{Parse, PacketPileParser}; /// /// // By default, the `PacketPileParser` will drop packet bodies. /// let mut ppp = /// PacketPileParser::from_bytes( /// b"\xcb\x12b\x00\x00\x00\x00\x00Hello world.")?; /// while ppp.packet().is_ok() { /// // Start parsing the next packet, recursing. /// ppp.recurse()?; /// } /// /// let pile = ppp.finish(); /// // Process the packet. /// if let Some(Packet::Literal(literal)) = pile.path_ref(&[0]) { /// // The body was dropped. /// assert_eq!(literal.body(), b""); /// } else { /// unreachable!("We know it is a literal packet."); /// } /// # Ok(()) } /// ``` /// /// This example demonstrates how the body can be buffered by /// configuring the `PacketPileParser` to buffer all packet bodies: /// /// ```rust /// # fn main() -> sequoia_openpgp::Result<()> { /// use std::convert::TryFrom; /// /// use sequoia_openpgp as openpgp; /// use openpgp::Packet; /// use openpgp::parse::{Parse, PacketPileParser, PacketParserBuilder}; /// /// // By default, the `PacketPileParser` will drop packet bodies. /// // Use a `PacketParserBuilder` to change that. /// let mut ppb = /// PacketParserBuilder::from_bytes( /// b"\xcb\x12b\x00\x00\x00\x00\x00Hello world.")? /// .buffer_unread_content(); /// let mut ppp = PacketPileParser::try_from(ppb)?; /// while ppp.packet().is_ok() { /// // Start parsing the next packet, recursing. /// ppp.recurse()?; /// } /// /// let pile = ppp.finish(); /// // Process the packet. /// if let Some(Packet::Literal(literal)) = pile.path_ref(&[0]) { /// // The body was buffered. /// assert_eq!(literal.body(), b"Hello world."); /// } else { /// unreachable!("We know it is a literal packet."); /// } /// # Ok(()) } /// ``` /// /// This example demonstrates how the body can be buffered by /// buffering an individual packet: /// /// ```rust /// # fn main() -> sequoia_openpgp::Result<()> { /// use sequoia_openpgp as openpgp; /// use openpgp::Packet; /// use openpgp::parse::{Parse, PacketPileParser}; /// /// // By default, the `PacketPileParser` will drop packet bodies. /// let mut ppp = /// PacketPileParser::from_bytes( /// b"\xcb\x12b\x00\x00\x00\x00\x00Hello world.")?; /// while let Ok(pp) = ppp.packet_mut() { /// if let Packet::Literal(_) = pp.packet { /// // Buffer this packet's body. /// pp.buffer_unread_content()?; /// } /// /// // Start parsing the next packet, recursing. /// ppp.recurse()?; /// } /// /// let pile = ppp.finish(); /// // Process the packet. /// if let Some(Packet::Literal(literal)) = pile.path_ref(&[0]) { /// // The body was buffered. /// assert_eq!(literal.body(), b"Hello world."); /// } else { /// unreachable!("We know it is a literal packet."); /// } /// # Ok(()) } /// ``` /// /// This example demonstrates how to stream the packet body: /// /// ```rust /// # fn main() -> sequoia_openpgp::Result<()> { /// use std::io::Read; /// /// use sequoia_openpgp as openpgp; /// use openpgp::Packet; /// use openpgp::parse::{Parse, PacketPileParser}; /// /// // By default, the `PacketPileParser` will drop packet bodies. /// let mut ppp = /// PacketPileParser::from_bytes( /// b"\xcb\x12b\x00\x00\x00\x00\x00Hello world.")?; /// while let Ok(pp) = ppp.packet_mut() { /// if let Packet::Literal(_) = pp.packet { /// // Stream the body. /// let mut buf = Vec::new(); /// pp.read_to_end(&mut buf)?; /// assert_eq!(buf, b"Hello world."); /// } /// /// // Start parsing the next packet, recursing. /// ppp.recurse()?; /// } /// /// let pile = ppp.finish(); /// // Process the packet. /// if let Some(Packet::Literal(literal)) = pile.path_ref(&[0]) { /// // The body was streamed, not buffered. /// assert_eq!(literal.body(), b""); /// } else { /// unreachable!("We know it is a literal packet."); /// } /// # Ok(()) } /// ``` #[derive(Debug)] pub struct PacketPileParser<'a> { /// The current packet. ppr: PacketParserResult<'a>, /// The packet pile that has been assembled so far. pile: PacketPile, } assert_send_and_sync!(PacketPileParser<'_>); impl<'a> TryFrom<PacketParserBuilder<'a>> for PacketPileParser<'a> { type Error = anyhow::Error; /// Finishes configuring the `PacketParser` and returns a /// `PacketPileParser`. fn try_from(ppb: PacketParserBuilder<'a>) -> Result<PacketPileParser<'a>> { Self::from_packet_parser(ppb.build()?) } } impl<'a> Parse<'a, PacketPileParser<'a>> for PacketPileParser<'a> { fn from_buffered_reader<R>(reader: R) -> Result<PacketPileParser<'a>> where R: BufferedReader<Cookie> + 'a { PacketPileParser::from_cookie_reader(reader.into_boxed()) } } impl<'a> crate::seal::Sealed for PacketPileParser<'a> {} impl<'a> PacketPileParser<'a> { /// Creates a `PacketPileParser` from a *fresh* `PacketParser`. fn from_packet_parser(ppr: PacketParserResult<'a>) -> Result<PacketPileParser<'a>> { Ok(PacketPileParser { pile: Default::default(), ppr, }) } /// Creates a `PacketPileParser` to parse the OpenPGP message stored /// in the `BufferedReader` object. pub(crate) fn from_cookie_reader(bio: Box<dyn BufferedReader<Cookie> + 'a>) -> Result<PacketPileParser<'a>> { Self::from_packet_parser(PacketParser::from_cookie_reader(bio)?) } /// Inserts the next packet into the `PacketPile`. fn insert_packet(&mut self, packet: Packet, position: isize) { // Find the right container. let mut container = self.pile.top_level_mut(); assert!(position >= 0); for i in 0..position { // The most recent child. let tmp = container; let packets_len = tmp.children_ref().expect("is a container").len(); let p = &mut tmp.children_mut() .expect("is a container") [packets_len - 1]; if p.children().expect("is a container").next().is_none() { assert!(i == position - 1, "Internal inconsistency while building message."); } container = p.container_mut().unwrap(); } container.children_mut().unwrap().push(packet); } /// Returns a reference to the current packet. pub fn packet(&self) -> std::result::Result<&PacketParser<'a>, &PacketParserEOF> { self.ppr.as_ref() } /// Returns a mutable reference to the current packet. pub fn packet_mut<>(&mut self) -> std::result::Result<&mut PacketParser<'a>, &mut PacketParserEOF<'a>> { self.ppr.as_mut() } /// Finishes parsing the current packet and starts parsing the /// next one, recursing if possible. /// /// This method is similar to the [`next()`] method (see that /// method for more details), but if the current packet is a /// container (and we haven't reached the maximum recursion depth, /// and the user hasn't started reading the packet's contents), we /// recurse into the container, and return a `PacketParser` for /// its first child. Otherwise, we return the next packet in the /// packet stream. If this function recurses, then the new /// packet's recursion depth will be `last_recursion_depth() + 1`; /// because we always visit interior nodes, we can't recurse more /// than one level at a time. /// /// [`next()`]: PacketPileParser::next() /// /// # Examples /// /// ```rust /// # fn main() -> sequoia_openpgp::Result<()> { /// use sequoia_openpgp as openpgp; /// use openpgp::parse::{Parse, PacketPileParser}; /// /// // Parse a message. /// let message_data: &[u8] = // ... /// # include_bytes!("../../tests/data/messages/compressed-data-algo-0.pgp"); /// let mut ppp = PacketPileParser::from_bytes(message_data)?; /// while let Ok(pp) = ppp.packet() { /// // Do something interesting with `pp` here. /// /// // Start parsing the next packet, recursing. /// ppp.recurse()?; /// } /// /// let pile = ppp.finish(); /// # Ok(()) } /// ``` pub fn recurse(&mut self) -> Result<()> { match self.ppr.take() { PacketParserResult::Some(pp) => { let recursion_depth = pp.recursion_depth(); let (packet, ppr) = pp.recurse()?; self.insert_packet( packet, recursion_depth as isize); self.ppr = ppr; } eof @ PacketParserResult::EOF(_) => { self.ppr = eof; } } Ok(()) } /// Finishes parsing the current packet and starts parsing the /// next one. /// /// This function finishes parsing the current packet. By /// default, any unread content is dropped. (See /// [`PacketParsererBuilder`] for how to configure this.) It then /// creates a new packet parser for the next packet. If the /// current packet is a container, this function does *not* /// recurse into the container, but skips any packets it contains. /// To recurse into the container, use the [`recurse()`] method. /// /// [`PacketParsererBuilder`]: PacketParserBuilder /// [`recurse()`]: PacketPileParser::recurse() /// /// # Examples /// /// ```rust /// # fn main() -> sequoia_openpgp::Result<()> { /// use sequoia_openpgp as openpgp; /// use openpgp::parse::{Parse, PacketPileParser}; /// /// // Parse a message. /// let message_data: &[u8] = // ... /// # include_bytes!("../../tests/data/messages/compressed-data-algo-0.pgp"); /// let mut ppp = PacketPileParser::from_bytes(message_data)?; /// while let Ok(pp) = ppp.packet() { /// // Do something interesting with `pp` here. /// /// // Start parsing the next packet. /// ppp.next()?; /// } /// /// let pile = ppp.finish(); /// # Ok(()) } /// ``` pub fn next(&mut self) -> Result<()> { match self.ppr.take() { PacketParserResult::Some(pp) => { let recursion_depth = pp.recursion_depth(); let (packet, ppr) = pp.next()?; self.insert_packet( packet, recursion_depth as isize); self.ppr = ppr; }, eof @ PacketParserResult::EOF(_) => { self.ppr = eof }, } Ok(()) } /// Returns the current packet's recursion depth. /// /// A top-level packet has a recursion depth of 0. Packets in a /// top-level container have a recursion depth of 1. Etc. /// /// # Examples /// /// ```rust /// # fn main() -> sequoia_openpgp::Result<()> { /// use sequoia_openpgp as openpgp; /// use openpgp::Packet; /// use openpgp::parse::{Parse, PacketPileParser}; /// /// // Parse a simple compressed message. /// let message_data: &[u8] = // ... /// # include_bytes!("../../tests/data/messages/compressed-data-algo-0.pgp"); /// let mut ppp = PacketPileParser::from_bytes(message_data)?; /// while let Ok(pp) = ppp.packet() { /// match pp.packet { /// Packet::CompressedData(_) => /// assert_eq!(ppp.recursion_depth(), Some(0)), /// Packet::Literal(_) => /// assert_eq!(ppp.recursion_depth(), Some(1)), /// _ => unreachable!(), /// } /// /// // Alternatively, the recursion depth can be queried /// // from the packet parser. /// assert_eq!(ppp.recursion_depth(), Some(pp.recursion_depth())); /// /// // Start parsing the next packet. /// ppp.next()?; /// } /// /// let pile = ppp.finish(); /// # Ok(()) } /// ``` pub fn recursion_depth(&self) -> Option<isize> { if let PacketParserResult::Some(ref pp) = self.ppr { Some(pp.recursion_depth()) } else { None } } /// Returns whether the message has been completely parsed. /// /// # Examples /// /// ```rust /// # fn main() -> sequoia_openpgp::Result<()> { /// use sequoia_openpgp as openpgp; /// use openpgp::Packet; /// use openpgp::parse::{Parse, PacketPileParser}; /// /// // Parse a message. /// let message_data: &[u8] = // ... /// # include_bytes!("../../tests/data/messages/compressed-data-algo-0.pgp"); /// let mut ppp = PacketPileParser::from_bytes(message_data)?; /// while ppp.packet().is_ok() { /// // Start parsing the next packet. /// ppp.next()?; /// } /// /// assert!(ppp.is_done()); /// let pile = ppp.finish(); /// # Ok(()) } /// ``` pub fn is_done(&self) -> bool { self.ppr.is_eof() } /// Finishes parsing the message and returns the assembled /// `PacketPile`. /// /// This function can be called at any time, not only when the /// message has been completely parsed. If the packet sequence has not /// been completely parsed, this function aborts processing, and /// the returned `PacketPile` just contains those packets that were /// completely processed; the packet that is currently being /// processed is not included in the `PacketPile`. /// /// # Examples /// /// ```rust /// # fn main() -> sequoia_openpgp::Result<()> { /// use sequoia_openpgp as openpgp; /// use openpgp::Packet; /// use openpgp::parse::{Parse, PacketPileParser}; /// /// // Parse a message. /// let message_data: &[u8] = // ... /// # include_bytes!("../../tests/data/messages/compressed-data-algo-0.pgp"); /// let mut ppp = PacketPileParser::from_bytes(message_data)?; /// ppp.next()?; /// /// let pp = ppp.finish(); /// assert_eq!(pp.children().count(), 1); /// # Ok(()) } /// ``` pub fn finish(self) -> PacketPile { self.pile } } #[test] fn test_recurse() -> Result<()> { let mut count = 0; let mut ppp = PacketPileParser::from_bytes(crate::tests::key("public-key.gpg"))?; while ppp.packet().is_ok() { count += 1; ppp.recurse().unwrap(); } assert_eq!(count, 61); let pp = ppp.finish(); assert_eq!(pp.children().count(), 61); Ok(()) } #[test] fn test_next() -> Result<()> { let mut count = 0; let mut ppp = PacketPileParser::from_bytes(crate::tests::key("public-key.gpg"))?; while ppp.packet().is_ok() { count += 1; ppp.next().unwrap(); } assert_eq!(count, 61); let pp = ppp.finish(); assert_eq!(pp.children().count(), 61); Ok(()) } /// Check that we can use the read interface to stream the contents of /// a packet. #[cfg(feature = "compression-deflate")] #[test] fn message_parser_reader_interface() { use std::io::Read; let expected = crate::tests::manifesto(); // A message containing a compressed packet that contains a // literal packet. let mut ppp = PacketPileParser::from_bytes( crate::tests::message("compressed-data-algo-1.gpg")).unwrap(); let mut count = 0; while let Ok(pp) = ppp.packet_mut() { if let Packet::Literal(_) = pp.packet { assert_eq!(count, 1); // The *second* packet. // Check that we can read the packet's contents. We do this one // byte at a time to exercise the cursor implementation. for i in 0..expected.len() { let mut buf = [0u8; 1]; let r = pp.read(&mut buf).unwrap(); assert_eq!(r, 1); assert_eq!(buf[0], expected[i]); } // And, now an EOF. let mut buf = [0u8; 1]; let r = pp.read(&mut buf).unwrap(); assert_eq!(r, 0); } ppp.recurse().unwrap(); count += 1; } assert_eq!(count, 2); } ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/src/parse/partial_body.rs�����������������������������������������������������0000644�0000000�0000000�00000035537�10461020230�0017674�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������ use std::cmp; use std::io; use std::io::{Error, ErrorKind}; use buffered_reader::{buffered_reader_generic_read_impl, BufferedReader}; use crate::{vec_resize, vec_truncate}; use crate::packet::header::BodyLength; use crate::parse::{Cookie, Hashing}; const TRACE : bool = false; /// A `BufferedReader` that transparently handles OpenPGP's chunking /// scheme. This implicitly implements a limitor. pub(crate) struct BufferedReaderPartialBodyFilter<T: BufferedReader<Cookie>> { // The underlying reader. reader: T, // The amount of unread data in the current partial body chunk. // That is, if `buffer` contains 10 bytes and // `partial_body_length` is 20, then there are 30 bytes of // unprocessed (unconsumed) data in the current chunk. partial_body_length: u32, // Whether this is the last partial body chuck. last: bool, // Sometimes we have to double buffer. This happens if the caller // requests X bytes and that chunk straddles a partial body length // boundary. buffer: Option<Vec<u8>>, // The position within the buffer. cursor: usize, /// Currently unused buffers. unused_buffers: Vec<Vec<u8>>, // The user-defined cookie. cookie: Cookie, // Whether to include the headers in any hash directly over the // current packet. If not, calls Cookie::hashing at // the current level to disable hashing while reading headers. hash_headers: bool, } impl<T: BufferedReader<Cookie>> std::fmt::Display for BufferedReaderPartialBodyFilter<T> { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!(f, "BufferedReaderPartialBodyFilter") } } impl<T: BufferedReader<Cookie>> std::fmt::Debug for BufferedReaderPartialBodyFilter<T> { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { f.debug_struct("BufferedReaderPartialBodyFilter") .field("partial_body_length", &self.partial_body_length) .field("last", &self.last) .field("hash headers", &self.hash_headers) .field("buffer (bytes left)", &self.buffer.as_ref().map(|buffer| buffer.len())) .field("reader", &self.reader) .finish() } } impl<T: BufferedReader<Cookie>> BufferedReaderPartialBodyFilter<T> { /// Create a new BufferedReaderPartialBodyFilter object. /// `partial_body_length` is the amount of data in the initial /// partial body chunk. pub fn with_cookie(reader: T, partial_body_length: u32, hash_headers: bool, cookie: Cookie) -> Self { BufferedReaderPartialBodyFilter { reader, partial_body_length, last: false, buffer: None, cursor: 0, unused_buffers: Vec::with_capacity(2), cookie, hash_headers, } } // Make sure that the local buffer contains `amount` bytes. fn do_fill_buffer (&mut self, amount: usize) -> Result<(), std::io::Error> { tracer!(TRACE, "PBF::do_fill_buffer", self.cookie.level.unwrap_or(0)); t!("BufferedReaderPartialBodyFilter::do_fill_buffer(\ amount: {}) (partial body length: {}, last: {})", amount, self.partial_body_length, self.last); if self.last && self.partial_body_length == 0 { // We reached the end. Avoid fruitlessly copying data // over and over again trying to buffer more data. return Ok(()); } // We want to avoid double buffering as much as possible. // Thus, we only buffer as much as needed. let mut buffer = self.unused_buffers.pop() .map(|mut v| { vec_resize(&mut v, amount); v }) .unwrap_or_else(|| vec![0u8; amount]); let mut amount_buffered = 0; if let Some(ref old_buffer) = self.buffer { // The amount of data that is left in the old buffer. let amount_left = old_buffer.len() - self.cursor; // This function should only be called if we actually need // to read something. assert!(amount > amount_left); amount_buffered = amount_left; // Copy the data that is still in buffer. buffer[..amount_buffered] .copy_from_slice(&old_buffer[self.cursor..]); t!("Copied {} bytes from the old buffer", amount_buffered); } let mut err = None; loop { let to_read = cmp::min( // Data in current chunk. self.partial_body_length as usize, // Space left in the buffer. buffer.len() - amount_buffered); t!("Trying to buffer {} bytes \ (partial body length: {}; space: {})", to_read, self.partial_body_length, buffer.len() - amount_buffered); if to_read > 0 { let result = self.reader.read( &mut buffer[amount_buffered..amount_buffered + to_read]); match result { Ok(did_read) => { t!("Buffered {} bytes", did_read); amount_buffered += did_read; self.partial_body_length -= did_read as u32; if did_read < to_read { // Short read => EOF. We're done. // (Although the underlying message is // probably corrupt.) break; } }, Err(e) => { t!("Err reading: {:?}", e); err = Some(e); break; }, } } if amount_buffered == amount || self.last { // We're read enough or we've read everything. break; } // Read the next partial body length header. assert_eq!(self.partial_body_length, 0); // Disable hashing, if necessary. if ! self.hash_headers { if let Some(level) = self.reader.cookie_ref().level { Cookie::hashing( &mut self.reader, Hashing::Disabled, level); } } t!("Reading next chunk's header (hashing: {}, level: {:?})", self.hash_headers, self.reader.cookie_ref().level); let body_length = BodyLength::parse_new_format(&mut self.reader); if ! self.hash_headers { if let Some(level) = self.reader.cookie_ref().level { Cookie::hashing( &mut self.reader, Hashing::Enabled, level); } } match body_length { Ok(BodyLength::Full(len)) => { t!("Last chunk: {} bytes", len); self.last = true; self.partial_body_length = len; }, Ok(BodyLength::Partial(len)) => { t!("Next chunk: {} bytes", len); self.partial_body_length = len; }, Ok(BodyLength::Indeterminate) => { // A new format packet can't return Indeterminate. unreachable!(); }, Err(e) => { t!("Err reading next chunk: {:?}", e); err = Some(e); break; } } } vec_truncate(&mut buffer, amount_buffered); // We're done. if let Some(b) = self.buffer.take() { self.unused_buffers.push(b); } self.buffer = Some(buffer); self.cursor = 0; if let Some(err) = err { Err(err) } else { Ok(()) } } fn data_helper(&mut self, amount: usize, hard: bool, and_consume: bool) -> Result<&[u8], std::io::Error> { tracer!(TRACE, "PBF::data_helper", self.cookie.level.unwrap_or(0)); let mut need_fill = false; t!("amount {}, hard {:?}, and_consume {:?}, buffered {}, cursor {}", amount, hard, and_consume, self.buffer.as_ref().map(|b| b.len()).unwrap_or(0), self.cursor); if self.buffer.as_ref().map(|b| b.len() == self.cursor).unwrap_or(false) { // We have data and it has been exhausted exactly. This // is our opportunity to get back to the happy path where // we don't need to double buffer. self.unused_buffers.push(self.buffer.take().expect("have buffer")); self.cursor = 0; } if let Some(ref buffer) = self.buffer { // We have some data buffered locally. //println!(" Reading from buffer"); let amount_buffered = buffer.len() - self.cursor; if amount > amount_buffered { // The requested amount exceeds what is in the buffer. // Read more. // We can't call self.do_fill_buffer here, because self // is borrowed. Set a flag and do it after the borrow // ends. need_fill = true; t!("Read of {} bytes exceeds buffered {} bytes", amount, amount_buffered); } } else { // We don't have any data buffered. assert_eq!(self.cursor, 0); if amount <= self.partial_body_length as usize || /* Short read. */ self.last { // The amount of data that the caller requested does // not exceed the amount of data in the current chunk. // As such, there is no need to double buffer. //println!(" Reading from inner reader"); let result = if hard && and_consume { self.reader.data_consume_hard (amount) } else if and_consume { self.reader.data_consume (amount) } else { self.reader.data(amount) }; match result { Ok(buffer) => { let amount_buffered = std::cmp::min(buffer.len(), self.partial_body_length as usize); if hard && amount_buffered < amount { return Err(Error::new(ErrorKind::UnexpectedEof, "unexpected EOF")); } else { if and_consume { self.partial_body_length -= cmp::min(amount, amount_buffered) as u32; } return Ok(&buffer[..amount_buffered]); } }, Err(err) => return Err(err), } } else { // `amount` crosses a partial body length boundary. // Do some buffering. //println!(" Read crosses chunk boundary. Need to buffer."); need_fill = true; t!("Read straddles partial body chunk boundary"); } } if need_fill { t!("Need to refill the buffer."); let result = self.do_fill_buffer(amount); if let Err(err) = result { return Err(err); } } //println!(" Buffer: {:?} (cursor at {})", // if let Some(ref buffer) = self.buffer { Some(buffer.len()) } else { None }, // self.cursor); // Note: if we hit the EOF, then we might still have less // than `amount` data. But, that's okay. We just need to // return as much as we can in that case. let buffer = &self.buffer.as_ref().unwrap()[self.cursor..]; if hard && buffer.len() < amount { return Err(Error::new(ErrorKind::UnexpectedEof, "unexpected EOF")); } if and_consume { self.cursor += cmp::min(amount, buffer.len()); } Ok(buffer) } } impl<T: BufferedReader<Cookie>> std::io::Read for BufferedReaderPartialBodyFilter<T> { fn read(&mut self, buf: &mut [u8]) -> Result<usize, std::io::Error> { buffered_reader_generic_read_impl(self, buf) } } impl<T: BufferedReader<Cookie>> BufferedReader<Cookie> for BufferedReaderPartialBodyFilter<T> { fn buffer(&self) -> &[u8] { if let Some(ref buffer) = self.buffer { &buffer[self.cursor..] } else { let buf = self.reader.buffer(); &buf[..cmp::min(buf.len(), self.partial_body_length as usize)] } } // Due to the mixing of usize (for lengths) and u32 (for OpenPGP), // we require that usize is at least as large as u32. // #[cfg(target_point_with = "32") or cfg(target_point_with = "64")] fn data(&mut self, amount: usize) -> Result<&[u8], std::io::Error> { self.data_helper(amount, false, false) } fn data_hard(&mut self, amount: usize) -> Result<&[u8], io::Error> { self.data_helper(amount, true, false) } fn consume(&mut self, amount: usize) -> &[u8] { if let Some(ref buffer) = self.buffer { // We have a local buffer. self.cursor += amount; // The caller can't consume more than is buffered! assert!(self.cursor <= buffer.len()); &buffer[self.cursor - amount..] } else { // Since we don't have a buffer, just pass through to the // underlying reader. assert!(amount <= self.partial_body_length as usize); self.partial_body_length -= amount as u32; self.reader.consume(amount) } } fn data_consume(&mut self, amount: usize) -> Result<&[u8], std::io::Error> { self.data_helper(amount, false, true) } fn data_consume_hard(&mut self, amount: usize) -> Result<&[u8], std::io::Error> { self.data_helper(amount, true, true) } fn consummated(&mut self) -> bool { self.partial_body_length == 0 && self.last } fn get_mut(&mut self) -> Option<&mut dyn BufferedReader<Cookie>> { Some(&mut self.reader) } fn get_ref(&self) -> Option<&dyn BufferedReader<Cookie>> { Some(&self.reader) } fn into_inner<'b>(self: Box<Self>) -> Option<Box<dyn BufferedReader<Cookie> + 'b>> where Self: 'b { Some(self.reader.into_boxed()) } fn cookie_set(&mut self, cookie: Cookie) -> Cookie { use std::mem; mem::replace(&mut self.cookie, cookie) } fn cookie_ref(&self) -> &Cookie { &self.cookie } fn cookie_mut(&mut self) -> &mut Cookie { &mut self.cookie } } �����������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/src/parse/stream.rs�����������������������������������������������������������0000644�0000000�0000000�00000462076�10461020230�0016520�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������//! Streaming decryption and verification. //! //! This module provides convenient filters for decryption and //! verification of OpenPGP messages (see [Section 10.3 of RFC 9580]). //! It is the preferred interface to process OpenPGP messages: //! //! [Section 10.3 of RFC 9580]: https://www.rfc-editor.org/rfc/rfc9580.html#section-10.3 //! //! - Use the [`Verifier`] to verify a signed message, //! - [`DetachedVerifier`] to verify a detached signature, //! - or [`Decryptor`] to decrypt and verify an encrypted and //! possibly signed message. //! //! //! Consuming OpenPGP messages is more difficult than producing them. //! When we produce the message, we control the packet structure being //! generated using our programs control flow. However, when we //! consume a message, the control flow is determined by the message //! being processed. //! //! To use Sequoia's streaming [`Verifier`] and [`Decryptor`], you //! need to provide an object that implements [`VerificationHelper`], //! and for the [`Decryptor`] also [`DecryptionHelper`]. //! //! //! The [`VerificationHelper`] trait give certificates for the //! signature verification to the [`Verifier`] or [`Decryptor`], let //! you inspect the message structure (see [Section 10.3 of RFC //! 9580]), and implements the signature verification policy. //! //! The [`DecryptionHelper`] trait is concerned with producing the //! session key to decrypt a message, most commonly by decrypting one //! of the messages' [`PKESK`] or [`SKESK`] packets. It could also //! use a cached session key, or one that has been explicitly provided //! to the decryption operation. //! //! [`PKESK`]: crate::packet::PKESK //! [`SKESK`]: crate::packet::SKESK //! //! The [`Verifier`] and [`Decryptor`] are filters: they consume //! OpenPGP data from a reader, file, or bytes, and implement //! [`io::Read`] that can be used to read the verified and/or //! decrypted data. //! //! [`io::Read`]: std::io::Read //! //! [`DetachedVerifier`] does not provide the [`io::Read`] interface, //! because in this case, the data to be verified is easily available //! without any transformation. Not providing a filter-like interface //! allows for a very performant implementation of the verification. //! //! # Examples //! //! This example demonstrates how to use the streaming interface using //! the [`Verifier`]. For brevity, no certificates are fed to the //! verifier, and the message structure is not verified, i.e. this //! merely extracts the literal data. See the [`Verifier` examples] //! and the [`Decryptor` examples] for how to verify the message and //! its structure. //! //! [`Verifier` examples]: Verifier#examples //! [`Decryptor` examples]: Decryptor#examples //! //! ``` //! # fn main() -> sequoia_openpgp::Result<()> { //! use std::io::Read; //! use sequoia_openpgp as openpgp; //! use openpgp::{KeyHandle, Cert, Result}; //! use openpgp::parse::{Parse, stream::*}; //! use openpgp::policy::StandardPolicy; //! //! let p = &StandardPolicy::new(); //! //! // This fetches keys and computes the validity of the verification. //! struct Helper {} //! impl VerificationHelper for Helper { //! fn get_certs(&mut self, _ids: &[KeyHandle]) -> Result<Vec<Cert>> { //! Ok(Vec::new()) // Feed the Certs to the verifier here... //! } //! fn check(&mut self, structure: MessageStructure) -> Result<()> { //! Ok(()) // Implement your verification policy here. //! } //! } //! //! let message = //! b"-----BEGIN PGP MESSAGE----- //! //! xA0DAAoWBpwMNI3YLBkByxJiAAAAAABIZWxsbyBXb3JsZCHCdQQAFgoAJwWCW37P //! 8RahBI6MM/pGJjN5dtl5eAacDDSN2CwZCZAGnAw0jdgsGQAAeZQA/2amPbBXT96Q //! O7PFms9DRuehsVVrFkaDtjN2WSxI4RGvAQDq/pzNdCMpy/Yo7AZNqZv5qNMtDdhE //! b2WH5lghfKe/AQ== //! =DjuO //! -----END PGP MESSAGE-----"; //! //! let h = Helper {}; //! let mut v = VerifierBuilder::from_bytes(&message[..])? //! .with_policy(p, None, h)?; //! //! let mut content = Vec::new(); //! v.read_to_end(&mut content)?; //! assert_eq!(content, b"Hello World!"); //! # Ok(()) } //! ``` use std::cmp; use std::io; use std::path::Path; use std::time; use buffered_reader::BufferedReader; use crate::{ Error, Fingerprint, types::{ AEADAlgorithm, CompressionAlgorithm, RevocationStatus, SymmetricAlgorithm, }, packet::{ key, OnePassSig, PKESK, SEIP, SKESK, }, KeyHandle, Packet, Result, packet, packet::{Signature, Unknown}, cert::prelude::*, crypto::SessionKey, policy::Policy, }; use crate::parse::{ Cookie, HashingMode, PacketParser, PacketParserBuilder, PacketParserResult, Parse, }; /// Whether to trace execution by default (on stderr). const TRACE : bool = false; /// Indentation level for tracing in this module. const TRACE_INDENT: isize = 5; /// How much data to buffer before giving it to the caller. /// /// Signature verification and detection of ciphertext tampering /// requires processing the whole message first. Therefore, OpenPGP /// implementations supporting streaming operations necessarily must /// output unverified data. This has been a source of problems in the /// past. To alleviate this, we buffer the message first (up to 25 /// megabytes of net message data by default), and verify the /// signatures if the message fits into our buffer. Nevertheless it /// is important to treat the data as unverified and untrustworthy /// until you have seen a positive verification. /// /// The default can be changed using [`VerifierBuilder::buffer_size`] /// and [`DecryptorBuilder::buffer_size`]. /// /// [`VerifierBuilder::buffer_size`]: VerifierBuilder::buffer_size() /// [`DecryptorBuilder::buffer_size`]: DecryptorBuilder::buffer_size() pub const DEFAULT_BUFFER_SIZE: usize = 25 * 1024 * 1024; /// Result of a signature verification. /// /// A signature verification is either successful yielding a /// [`GoodChecksum`], or there was some [`VerificationError`] /// explaining the verification failure. /// pub type VerificationResult<'a> = std::result::Result<GoodChecksum<'a>, VerificationError<'a>>; /// A good signature. /// /// Represents the result of a successful signature verification. It /// includes the signature and the signing key with all the necessary /// context (i.e. certificate, time, policy) to evaluate the /// trustworthiness of the signature using a trust model. /// /// `GoodChecksum` is used in [`VerificationResult`]. See also /// [`VerificationError`]. /// /// /// A signature is considered good if and only if all the following /// conditions are met: /// /// - The signature has a Signature Creation Time subpacket. /// /// - The signature is alive at the specified time (the time /// parameter passed to, e.g., [`VerifierBuilder::with_policy`]). /// /// [`VerifierBuilder::with_policy`]: VerifierBuilder::with_policy() /// /// - The certificate is alive and not revoked as of the signature's /// creation time. /// /// - The signing key is alive, not revoked, and signing capable as /// of the signature's creation time. /// /// - The signature was generated by the signing key. /// /// **Note**: This doesn't mean that the key that generated the /// signature is in any way trustworthy in the sense that it /// belongs to the person or entity that the user thinks it /// belongs to. This property can only be evaluated within a /// trust model, such as the [web of trust] (WoT). This policy is /// normally implemented in the [`VerificationHelper::check`] /// method. /// /// [web of trust]: https://en.wikipedia.org/wiki/Web_of_trust #[derive(Debug)] pub struct GoodChecksum<'a> { /// The signature. pub sig: &'a Signature, /// The signing key that made the signature. /// /// The amalgamation of the signing key includes the necessary /// context (i.e. certificate, time, policy) to evaluate the /// trustworthiness of the signature using a trust model. pub ka: ValidErasedKeyAmalgamation<'a, key::PublicParts>, } assert_send_and_sync!(GoodChecksum<'_>); /// A bad signature. /// /// Represents the result of an unsuccessful signature verification. /// It contains all the context that could be gathered until the /// verification process failed. /// /// `VerificationError` is used in [`VerificationResult`]. See also /// [`GoodChecksum`]. /// /// /// You can either explicitly match on the variants, or convert to /// [`Error`] using [`From`]. /// /// [`Error`]: super::super::Error /// [`From`]: std::convert::From #[non_exhaustive] #[derive(Debug)] pub enum VerificationError<'a> { /// Missing Key MissingKey { /// The signature. sig: &'a Signature, }, /// Unbound key. /// /// There is no valid binding signature at the time the signature /// was created under the given policy. UnboundKey { /// The signature. sig: &'a Signature, /// The certificate that made the signature. cert: &'a Cert, /// The reason why the key is not bound. error: anyhow::Error, }, /// Bad key (have a key, but it is not alive, etc.) BadKey { /// The signature. sig: &'a Signature, /// The signing key that made the signature. ka: ValidErasedKeyAmalgamation<'a, key::PublicParts>, /// The reason why the key is bad. error: anyhow::Error, }, /// Bad signature (have a valid key, but the signature didn't check out) BadSignature { /// The signature. sig: &'a Signature, /// The signing key that made the signature. ka: ValidErasedKeyAmalgamation<'a, key::PublicParts>, /// The reason why the signature is bad. error: anyhow::Error, }, /// Malformed signature (no signature creation subpacket, etc.). MalformedSignature { /// The signature. sig: &'a Signature, /// The reason why the signature is malformed. error: anyhow::Error, }, /// A signature that failed to parse at all. UnknownSignature { /// The signature parsed into an [`crate::packet::Unknown`] /// packet. sig: &'a Unknown, } } assert_send_and_sync!(VerificationError<'_>); impl<'a> std::fmt::Display for VerificationError<'a> { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { use self::VerificationError::*; match self { MalformedSignature { error, .. } => write!(f, "Malformed signature: {}", error), UnknownSignature { sig, .. } => write!(f, "Malformed signature: {}", sig.error()), MissingKey { sig } => if let Some(issuer) = sig.get_issuers().get(0) { write!(f, "Missing key: {}", issuer) } else { write!(f, "Missing key") }, UnboundKey { cert, error, .. } => write!(f, "Subkey of {} not bound: {}", cert, error), BadKey { ka, error, .. } => write!(f, "Subkey of {} is bad: {}", ka.cert(), error), BadSignature { error, .. } => write!(f, "Bad signature: {}", error), } } } impl<'a> std::error::Error for VerificationError<'a> {} impl<'a> From<VerificationError<'a>> for Error { fn from(e: VerificationError<'a>) -> Self { use self::VerificationError::*; match e { MalformedSignature { .. } => Error::MalformedPacket(e.to_string()), UnknownSignature { sig } => Error::MalformedPacket(sig.error().to_string()), MissingKey { .. } => Error::InvalidKey(e.to_string()), UnboundKey { .. } => Error::InvalidKey(e.to_string()), BadKey { .. } => Error::InvalidKey(e.to_string()), BadSignature { .. } => Error::BadSignature(e.to_string()), } } } /// Like VerificationError, but without referencing the signature. /// /// This avoids borrowing the signature, so that we can continue to /// mutably borrow the signature trying other keys. After all keys /// are tried, we attach the reference to the signature, yielding a /// `VerificationError`. enum VerificationErrorInternal<'a> { // MalformedSignature is not used, so it is omitted here. /// Missing Key MissingKey { }, /// Unbound key. /// /// There is no valid binding signature at the time the signature /// was created under the given policy. UnboundKey { /// The certificate that made the signature. cert: &'a Cert, /// The reason why the key is not bound. error: anyhow::Error, }, /// Bad key (have a key, but it is not alive, etc.) BadKey { /// The signing key that made the signature. ka: ValidErasedKeyAmalgamation<'a, key::PublicParts>, /// The reason why the key is bad. error: anyhow::Error, }, /// Bad signature (have a valid key, but the signature didn't check out) BadSignature { /// The signing key that made the signature. ka: ValidErasedKeyAmalgamation<'a, key::PublicParts>, /// The reason why the signature is bad. error: anyhow::Error, }, } impl<'a> VerificationErrorInternal<'a> { fn attach_sig(self, sig: &'a Signature) -> VerificationError<'a> { use self::VerificationErrorInternal::*; match self { MissingKey {} => VerificationError::MissingKey { sig }, UnboundKey { cert, error } => VerificationError::UnboundKey { sig, cert, error }, BadKey { ka, error } => VerificationError::BadKey { sig, ka, error }, BadSignature { ka, error } => VerificationError::BadSignature { sig, ka, error }, } } } /// Communicates the message structure to the VerificationHelper. /// /// A valid OpenPGP message contains one literal data packet with /// optional [encryption, signing, and compression layers] freely /// combined on top. This structure is passed to /// [`VerificationHelper::check`] for verification. /// /// [encryption, signing, and compression layers]: MessageLayer /// /// The most common structure is an optionally encrypted, optionally /// compressed, and optionally signed message, i.e. if the message is /// encrypted, then the encryption is the outermost layer; if the /// message is signed, then the signature group is the innermost /// layer. This is a sketch of such a message: /// /// ```text /// [ encryption layer: [ compression layer: [ signature group: [ literal data ]]]] /// ``` /// /// However, OpenPGP allows encryption, signing, and compression /// operations to be freely combined (see [Section 10.3 of RFC 9580]). /// This is represented as a stack of [`MessageLayer`]s, where /// signatures of the same level (i.e. those over the same data: /// either directly over the literal data, or over other signatures /// and the literal data) are grouped into one layer. See also /// [`Signature::level`]. /// /// [Section 10.3 of RFC 9580]: https://www.rfc-editor.org/rfc/rfc9580.html#section-10.3 /// [`Signature::level`]: crate::packet::Signature#method.level /// /// Consider the following structure. This is a set of notarizing /// signatures *N* over a set of signatures *S* over the literal data: /// /// ```text /// [ signature group: [ signature group: [ literal data ]]] /// ``` /// /// The notarizing signatures *N* are said to be of level 1, /// i.e. signatures over the signatures *S* and the literal data. The /// signatures *S* are level 0 signatures, i.e. signatures over the /// literal data. /// /// OpenPGP's flexibility allows adaption to new use cases, but also /// presents a challenge to implementations and downstream users. The /// message structure must be both validated, and possibly /// communicated to the application's user. Note that if /// compatibility is a concern, generated messages must be restricted /// to a narrow subset of possible structures, see this [test of /// unusual message structures]. /// /// [test of unusual message structures]: https://tests.sequoia-pgp.org/#Unusual_Message_Structure #[derive(Debug)] pub struct MessageStructure<'a>(Vec<MessageLayer<'a>>); assert_send_and_sync!(MessageStructure<'_>); impl<'a> MessageStructure<'a> { fn new() -> Self { MessageStructure(Vec::new()) } fn new_compression_layer(&mut self, algo: CompressionAlgorithm) { self.0.push(MessageLayer::Compression { algo, }) } fn new_encryption_layer(&mut self, sym_algo: SymmetricAlgorithm, aead_algo: Option<AEADAlgorithm>) { self.0.push(MessageLayer::Encryption { sym_algo, aead_algo, }) } fn new_signature_group(&mut self) { self.0.push(MessageLayer::SignatureGroup { results: Vec::new(), }) } fn push_verification_result(&mut self, sig: VerificationResult<'a>) { if let Some(MessageLayer::SignatureGroup { ref mut results }) = self.0.iter_mut().last() { results.push(sig); } else { panic!("cannot push to encryption or compression layer"); } } /// Returns an iterator over the message layers. pub fn iter(&self) -> impl Iterator<Item=&MessageLayer<'a>> { self.0.iter() } } impl<'a> IntoIterator for MessageStructure<'a> { type Item = MessageLayer<'a>; type IntoIter = std::vec::IntoIter<MessageLayer<'a>>; fn into_iter(self) -> Self::IntoIter { self.0.into_iter() } } /// Represents a layer of the message structure. /// /// A valid OpenPGP message contains one literal data packet with /// optional encryption, signing, and compression layers freely /// combined on top (see [Section 10.3 of RFC 9580]). This enum /// represents the layers. The [`MessageStructure`] is communicated /// to the [`VerificationHelper::check`]. Iterating over the /// [`MessageStructure`] yields the individual message layers. /// /// [Section 10.3 of RFC 9580]: https://www.rfc-editor.org/rfc/rfc9580.html#section-10.3 #[derive(Debug)] pub enum MessageLayer<'a> { /// Represents a compression container. /// /// Compression is usually transparent in OpenPGP, though it may /// sometimes be interesting for advanced users to indicate that /// the message was compressed, and how (see [Section 5.6 of RFC /// 9580]). /// /// [Section 5.6 of RFC 9580]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.6 Compression { /// Compression algorithm used. algo: CompressionAlgorithm, }, /// Represents an encryption container. /// /// Indicates the fact that the message was encrypted (see /// [Section 5.13 of RFC 9580]). If you expect encrypted /// messages, make sure that there is at least one encryption /// container present. /// /// [Section 5.13 of RFC 9580]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.13 Encryption { /// Symmetric algorithm used. sym_algo: SymmetricAlgorithm, /// AEAD algorithm used, if any. aead_algo: Option<AEADAlgorithm>, }, /// Represents a signature group. /// /// A signature group consists of all signatures with the same /// level (see [Section 5.2 of RFC 9580]). Each /// [`VerificationResult`] represents the result of a single /// signature verification. In your [`VerificationHelper::check`] /// method, iterate over the verification results, see if it meets /// your policies' demands, and communicate it to the user, if /// applicable. /// /// [Section 5.2 of RFC 9580]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2 SignatureGroup { /// The results of the signature verifications. results: Vec<VerificationResult<'a>>, } } assert_send_and_sync!(MessageLayer<'_>); /// Internal version of the message structure. /// /// In contrast to MessageStructure, this owns unverified /// signature packets. #[derive(Debug)] struct IMessageStructure { layers: Vec<IMessageLayer>, // We insert a SignatureGroup layer every time we see a OnePassSig // packet with the last flag. // // However, we need to make sure that we insert a SignatureGroup // layer even if the OnePassSig packet has the last flag set to // false. To do that, we keep track of the fact that we saw such // a OPS packet. sig_group_counter: usize, } impl IMessageStructure { fn new() -> Self { IMessageStructure { layers: Vec::new(), sig_group_counter: 0, } } fn new_compression_layer(&mut self, algo: CompressionAlgorithm) { tracer!(TRACE, "IMessageStructure::new_compression_layer", TRACE_INDENT); t!("pushing a {:?} layer", algo); self.insert_missing_signature_group(); self.layers.push(IMessageLayer::Compression { algo, }); } fn new_encryption_layer(&mut self, depth: isize, expect_mdc: bool, sym_algo: SymmetricAlgorithm, aead_algo: Option<AEADAlgorithm>) { tracer!(TRACE, "IMessageStructure::new_encryption_layer", TRACE_INDENT); t!("pushing a {:?}/{:?} layer", sym_algo, aead_algo); self.insert_missing_signature_group(); self.layers.push(IMessageLayer::Encryption { depth, expect_mdc, sym_algo, aead_algo, }); } /// Returns whether we expect an MDC packet in an /// encryption container at this recursion depth. /// /// Handling MDC packets has to be done carefully, otherwise, we /// may create a decryption oracle. fn expect_mdc_at(&self, at: isize) -> bool { for l in &self.layers { match l { IMessageLayer::Encryption { depth, expect_mdc, .. } if *depth == at && *expect_mdc => return true, _ => (), } } false } /// Makes sure that we insert a signature group even if the /// previous OPS packet had the last flag set to false. fn insert_missing_signature_group(&mut self) { tracer!(TRACE, "IMessageStructure::insert_missing_signature_group", TRACE_INDENT); if self.sig_group_counter > 0 { t!("implicit insert of signature group for {} sigs", self.sig_group_counter); self.layers.push(IMessageLayer::SignatureGroup { sigs: Vec::new(), count: self.sig_group_counter, }); } self.sig_group_counter = 0; } fn push_ops(&mut self, ops: &OnePassSig) { tracer!(TRACE, "IMessageStructure::push_ops", TRACE_INDENT); t!("Pushing {:?}", ops); self.sig_group_counter += 1; if ops.last() { self.layers.push(IMessageLayer::SignatureGroup { sigs: Vec::new(), count: self.sig_group_counter, }); self.sig_group_counter = 0; } } fn push_signature(&mut self, sig: MaybeSignature, csf_message: bool) { tracer!(TRACE, "IMessageStructure::push_signature", TRACE_INDENT); t!("Pushing {:?}", sig); if csf_message { t!("Cleartext Signature Framework transformation enabled"); } for (i, layer) in self.layers.iter_mut().enumerate().rev() { t!("{}: {:?}", i, layer); match layer { IMessageLayer::SignatureGroup { ref mut sigs, ref mut count, } if *count > 0 => { t!("Layer {} is a signature group with {} outstanding sigs", i, *count); sigs.push(sig); if csf_message { // The CSF transformation does not know how // many signatures will follow, so we may end // up with too few synthesized OPS packets. // But, we only have one layer anyway, and no // notarizations, so we don't need to concern // ourselves with the counter. } else { *count -= 1; } return; }, _ => (), } } // As a last resort, push a new signature group for this // signature. This may not accurately describe the structure, // but if we get to this point, we failed to grasp the message // structure in some way, so there is nothing we can do really. t!("signature unaccounted for"); self.layers.push(IMessageLayer::SignatureGroup { sigs: vec![sig], count: 0, }); } fn push_bare_signature(&mut self, sig: MaybeSignature) { if let Some(IMessageLayer::SignatureGroup { .. }) = self.layers.iter().last() { // The last layer is a SignatureGroup. We will append the // signature there without accounting for it. } else { // The last layer is not a SignatureGroup, or there is no // layer at all. Create one. self.layers.push(IMessageLayer::SignatureGroup { sigs: Vec::new(), count: 0, }); } if let IMessageLayer::SignatureGroup { ref mut sigs, .. } = self.layers.iter_mut().last().expect("just checked or created") { sigs.push(sig); } else { unreachable!("just checked or created") } } } /// Internal version of a layer of the message structure. /// /// In contrast to MessageLayer, this owns unverified signature packets. #[derive(Debug)] enum IMessageLayer { Compression { algo: CompressionAlgorithm, }, Encryption { /// Recursion depth of this container. depth: isize, /// Do we expect an MDC packet? /// /// I.e. is this a SEIPv1 container? expect_mdc: bool, sym_algo: SymmetricAlgorithm, aead_algo: Option<AEADAlgorithm>, }, SignatureGroup { sigs: Vec<MaybeSignature>, count: usize, } } /// Represents [`Signature`]s and those that failed to parse in the /// form of [`Unknown`] packets. type MaybeSignature = std::result::Result<Signature, Unknown>; /// Helper for signature verification. /// /// This trait abstracts over signature and message structure /// verification. It allows us to provide the [`Verifier`], /// [`DetachedVerifier`], and [`Decryptor`] without imposing a policy /// on how certificates for signature verification are looked up, or /// what message structure is considered acceptable. /// /// /// It also allows you to inspect each packet that is processed during /// verification or decryption, optionally providing a [`Map`] for /// each packet. /// /// [`Map`]: super::map::Map pub trait VerificationHelper { /// Inspects the message. /// /// Called once per packet. Can be used to inspect and dump /// packets in encrypted messages. /// /// The default implementation does nothing. fn inspect(&mut self, pp: &PacketParser) -> Result<()> { // Do nothing. let _ = pp; Ok(()) } /// Retrieves the certificates containing the specified keys. /// /// When implementing this method, you should return as many /// certificates corresponding to the `ids` as you can. /// /// If an identifier is ambiguous, because, for instance, there /// are multiple certificates with the same Key ID, then you /// should return all of them. /// /// You should only return an error if processing should be /// aborted. In general, you shouldn't return an error if you /// don't have a certificate for a given identifier: if there are /// multiple signatures, then, depending on your policy, verifying /// a subset of them may be sufficient. /// /// This method will be called at most once per message. /// /// # Examples /// /// This example demonstrates how to look up the certificates for /// the signature verification given the list of signature /// issuers. /// /// ``` /// use sequoia_openpgp as openpgp; /// use openpgp::{KeyHandle, Cert, Result}; /// use openpgp::parse::stream::*; /// # fn lookup_cert_by_handle(_: &KeyHandle) -> Result<Cert> { /// # unimplemented!() /// # } /// /// struct Helper { /* ... */ } /// impl VerificationHelper for Helper { /// fn get_certs(&mut self, ids: &[KeyHandle]) -> Result<Vec<Cert>> { /// let mut certs = Vec::new(); /// for id in ids { /// certs.push(lookup_cert_by_handle(id)?); /// } /// Ok(certs) /// } /// // ... /// # fn check(&mut self, structure: MessageStructure) -> Result<()> { /// # unimplemented!() /// # } /// } /// ``` fn get_certs(&mut self, ids: &[crate::KeyHandle]) -> Result<Vec<Cert>>; /// Validates the message structure. /// /// This function must validate the message's structure according /// to an application specific policy. For example, it could /// check that the required number of signatures or notarizations /// were confirmed as good, and evaluate every signature's /// validity under a trust model. /// /// A valid OpenPGP message contains one literal data packet with /// optional encryption, signing, and compression layers on top. /// Notably, the message structure contains the results of /// signature verifications. See [`MessageStructure`] for more /// information. /// /// /// When verifying a message, this callback will be called exactly /// once per message *after* the last signature has been verified /// and *before* all the data has been returned. Any error /// returned by this function will abort reading, and the error /// will be propagated via the [`io::Read`] operation. /// /// [`io::Read`]: std::io::Read /// /// After this method was called, [`Verifier::message_processed`] /// and [`Decryptor::message_processed`] return `true`. /// /// [`Verifier::message_processed`]: Verifier::message_processed() /// [`Decryptor::message_processed`]: Decryptor::message_processed() /// /// When verifying a detached signature using the /// [`DetachedVerifier`], this method will be called with a /// [`MessageStructure`] containing exactly one layer, a signature /// group. /// /// /// # Examples /// /// This example demonstrates how to verify that the message is an /// encrypted, optionally compressed, and signed message that has /// at least one valid signature. /// /// ``` /// use sequoia_openpgp as openpgp; /// use openpgp::{KeyHandle, Cert, Result}; /// use openpgp::parse::stream::*; /// /// struct Helper { /* ... */ } /// impl VerificationHelper for Helper { /// # fn get_certs(&mut self, ids: &[KeyHandle]) -> Result<Vec<Cert>> { /// # unimplemented!(); /// # } /// fn check(&mut self, structure: MessageStructure) -> Result<()> { /// for (i, layer) in structure.into_iter().enumerate() { /// match layer { /// MessageLayer::Encryption { .. } if i == 0 => (), /// MessageLayer::Compression { .. } if i == 1 => (), /// MessageLayer::SignatureGroup { ref results } /// if i == 1 || i == 2 => /// { /// if ! results.iter().any(|r| r.is_ok()) { /// return Err(anyhow::anyhow!( /// "No valid signature")); /// } /// } /// _ => return Err(anyhow::anyhow!( /// "Unexpected message structure")), /// } /// } /// Ok(()) /// } /// // ... /// } /// ``` fn check(&mut self, structure: MessageStructure) -> Result<()>; } /// Wraps a VerificationHelper and adds a non-functional /// DecryptionHelper implementation. struct NoDecryptionHelper<V: VerificationHelper> { v: V, } impl<V: VerificationHelper> VerificationHelper for NoDecryptionHelper<V> { fn get_certs(&mut self, ids: &[crate::KeyHandle]) -> Result<Vec<Cert>> { self.v.get_certs(ids) } fn check(&mut self, structure: MessageStructure) -> Result<()> { self.v.check(structure) } fn inspect(&mut self, pp: &PacketParser) -> Result<()> { self.v.inspect(pp) } } impl<V: VerificationHelper> DecryptionHelper for NoDecryptionHelper<V> { fn decrypt(&mut self, _: &[PKESK], _: &[SKESK], _: Option<SymmetricAlgorithm>, _: &mut dyn FnMut(Option<SymmetricAlgorithm>, &SessionKey) -> bool) -> Result<Option<Cert>> { unreachable!("This is not used for verifications") } } /// Verifies a signed OpenPGP message. /// /// To create a `Verifier`, create a [`VerifierBuilder`] using /// [`Parse`], and customize it to your needs. /// /// [`Parse`]: super::Parse /// /// Signature verification requires processing the whole message /// first. Therefore, OpenPGP implementations supporting streaming /// operations necessarily must output unverified data. This has been /// a source of problems in the past. To alleviate this, we buffer /// the message first (up to 25 megabytes of net message data by /// default, see [`DEFAULT_BUFFER_SIZE`]), and verify the signatures /// if the message fits into our buffer. Nevertheless it is important /// to treat the data as unverified and untrustworthy until you have /// seen a positive verification. See [`Verifier::message_processed`] /// for more information. /// /// [`Verifier::message_processed`]: Verifier::message_processed() /// /// See [`GoodChecksum`] for what it means for a signature to be /// considered valid. /// /// /// # Examples /// /// ``` /// # fn main() -> sequoia_openpgp::Result<()> { /// use std::io::Read; /// use sequoia_openpgp as openpgp; /// use openpgp::{KeyHandle, Cert, Result}; /// use openpgp::parse::{Parse, stream::*}; /// use openpgp::policy::StandardPolicy; /// # fn lookup_cert_by_handle(_: &KeyHandle) -> Result<Cert> { /// # Cert::from_bytes( /// # &b"-----BEGIN PGP PUBLIC KEY BLOCK----- /// # /// # xjMEWlNvABYJKwYBBAHaRw8BAQdA+EC2pvebpEbzPA9YplVgVXzkIG5eK+7wEAez /// # lcBgLJrNMVRlc3R5IE1jVGVzdGZhY2UgKG15IG5ldyBrZXkpIDx0ZXN0eUBleGFt /// # cGxlLm9yZz7CkAQTFggAOBYhBDnRAKtn1b2MBAECBfs3UfFYfa7xBQJaU28AAhsD /// # BQsJCAcCBhUICQoLAgQWAgMBAh4BAheAAAoJEPs3UfFYfa7xJHQBAO4/GABMWUcJ /// # 5D/DZ9b+6YiFnysSjCT/gILJgxMgl7uoAPwJherI1pAAh49RnPHBR1IkWDtwzX65 /// # CJG8sDyO2FhzDs44BFpTbwASCisGAQQBl1UBBQEBB0B+A0GRHuBgdDX50T1nePjb /// # mKQ5PeqXJbWEtVrUtVJaPwMBCAfCeAQYFggAIBYhBDnRAKtn1b2MBAECBfs3UfFY /// # fa7xBQJaU28AAhsMAAoJEPs3UfFYfa7xzjIBANX2/FgDX3WkmvwpEHg/sn40zACM /// # W2hrBY5x0sZ8H7JlAP47mCfCuRVBqyaePuzKbxLJeLe2BpDdc0n2izMVj8t9Cg== /// # =QetZ /// # -----END PGP PUBLIC KEY BLOCK-----"[..]) /// # } /// /// let p = &StandardPolicy::new(); /// /// // This fetches keys and computes the validity of the verification. /// struct Helper {} /// impl VerificationHelper for Helper { /// fn get_certs(&mut self, ids: &[KeyHandle]) -> Result<Vec<Cert>> { /// let mut certs = Vec::new(); /// for id in ids { /// certs.push(lookup_cert_by_handle(id)?); /// } /// Ok(certs) /// } /// /// fn check(&mut self, structure: MessageStructure) -> Result<()> { /// for (i, layer) in structure.into_iter().enumerate() { /// match layer { /// MessageLayer::Encryption { .. } if i == 0 => (), /// MessageLayer::Compression { .. } if i == 1 => (), /// MessageLayer::SignatureGroup { ref results } => { /// if ! results.iter().any(|r| r.is_ok()) { /// return Err(anyhow::anyhow!( /// "No valid signature")); /// } /// } /// _ => return Err(anyhow::anyhow!( /// "Unexpected message structure")), /// } /// } /// Ok(()) /// } /// } /// /// let message = /// b"-----BEGIN PGP MESSAGE----- /// /// xA0DAAoW+zdR8Vh9rvEByxJiAAAAAABIZWxsbyBXb3JsZCHCdQQAFgoABgWCXrLl /// AQAhCRD7N1HxWH2u8RYhBDnRAKtn1b2MBAECBfs3UfFYfa7xRUsBAJaxkU/RCstf /// UD7TM30IorO1Mb9cDa/hPRxyzipulT55AQDN1m9LMqi9yJDjHNHwYYVwxDcg+pLY /// YmAFv/UfO0vYBw== /// =+l94 /// -----END PGP MESSAGE----- /// "; /// /// let h = Helper {}; /// let mut v = VerifierBuilder::from_bytes(&message[..])? /// .with_policy(p, None, h)?; /// /// let mut content = Vec::new(); /// v.read_to_end(&mut content)?; /// assert_eq!(content, b"Hello World!"); /// # Ok(()) } pub struct Verifier<'a, H: VerificationHelper> { decryptor: Decryptor<'a, NoDecryptionHelper<H>>, } assert_send_and_sync!(Verifier<'_, H> where H: VerificationHelper); /// A builder for `Verifier`. /// /// This allows the customization of [`Verifier`], which can /// be built using [`VerifierBuilder::with_policy`]. /// /// [`VerifierBuilder::with_policy`]: VerifierBuilder::with_policy() pub struct VerifierBuilder<'a> { message: Box<dyn BufferedReader<Cookie> + 'a>, buffer_size: usize, mapping: bool, } assert_send_and_sync!(VerifierBuilder<'_>); impl<'a> Parse<'a, VerifierBuilder<'a>> for VerifierBuilder<'a> { fn from_buffered_reader<R>(reader: R) -> Result<VerifierBuilder<'a>> where R: BufferedReader<Cookie> + 'a, { VerifierBuilder::new(reader) } } impl<'a> crate::seal::Sealed for VerifierBuilder<'a> {} impl<'a> VerifierBuilder<'a> { fn new<B>(signatures: B) -> Result<Self> where B: buffered_reader::BufferedReader<Cookie> + 'a { Ok(VerifierBuilder { message: Box::new(signatures), buffer_size: DEFAULT_BUFFER_SIZE, mapping: false, }) } /// Changes the amount of buffered data. /// /// By default, we buffer up to 25 megabytes of net message data /// (see [`DEFAULT_BUFFER_SIZE`]). This changes the default. /// /// /// # Examples /// /// ``` /// # fn main() -> sequoia_openpgp::Result<()> { /// use sequoia_openpgp as openpgp; /// # use openpgp::{KeyHandle, Cert, Result}; /// use openpgp::parse::{Parse, stream::*}; /// use openpgp::policy::StandardPolicy; /// /// let p = &StandardPolicy::new(); /// /// struct Helper {} /// impl VerificationHelper for Helper { /// // ... /// # fn get_certs(&mut self, ids: &[KeyHandle]) -> Result<Vec<Cert>> { /// # Ok(Vec::new()) /// # } /// # /// # fn check(&mut self, structure: MessageStructure) -> Result<()> { /// # Ok(()) /// # } /// } /// /// let message = /// // ... /// # &b"-----BEGIN PGP MESSAGE----- /// # /// # xA0DAAoW+zdR8Vh9rvEByxJiAAAAAABIZWxsbyBXb3JsZCHCdQQAFgoABgWCXrLl /// # AQAhCRD7N1HxWH2u8RYhBDnRAKtn1b2MBAECBfs3UfFYfa7xRUsBAJaxkU/RCstf /// # UD7TM30IorO1Mb9cDa/hPRxyzipulT55AQDN1m9LMqi9yJDjHNHwYYVwxDcg+pLY /// # YmAFv/UfO0vYBw== /// # =+l94 /// # -----END PGP MESSAGE----- /// # "[..]; /// /// let h = Helper {}; /// let mut v = VerifierBuilder::from_bytes(message)? /// .buffer_size(1 << 12) /// .with_policy(p, None, h)?; /// # let _ = v; /// # Ok(()) } /// ``` pub fn buffer_size(mut self, size: usize) -> Self { self.buffer_size = size; self } /// Enables mapping. /// /// If mapping is enabled, the packet parser will create a [`Map`] /// of the packets that can be inspected in /// [`VerificationHelper::inspect`]. Note that this buffers the /// packets contents, and is not recommended unless you know that /// the packets are small. /// /// [`Map`]: super::map::Map /// /// # Examples /// /// ``` /// # fn main() -> sequoia_openpgp::Result<()> { /// use sequoia_openpgp as openpgp; /// # use openpgp::{KeyHandle, Cert, Result}; /// use openpgp::parse::{Parse, stream::*}; /// use openpgp::policy::StandardPolicy; /// /// let p = &StandardPolicy::new(); /// /// struct Helper {} /// impl VerificationHelper for Helper { /// // ... /// # fn get_certs(&mut self, ids: &[KeyHandle]) -> Result<Vec<Cert>> { /// # Ok(Vec::new()) /// # } /// # /// # fn check(&mut self, structure: MessageStructure) -> Result<()> { /// # Ok(()) /// # } /// } /// /// let message = /// // ... /// # &b"-----BEGIN PGP MESSAGE----- /// # /// # xA0DAAoW+zdR8Vh9rvEByxJiAAAAAABIZWxsbyBXb3JsZCHCdQQAFgoABgWCXrLl /// # AQAhCRD7N1HxWH2u8RYhBDnRAKtn1b2MBAECBfs3UfFYfa7xRUsBAJaxkU/RCstf /// # UD7TM30IorO1Mb9cDa/hPRxyzipulT55AQDN1m9LMqi9yJDjHNHwYYVwxDcg+pLY /// # YmAFv/UfO0vYBw== /// # =+l94 /// # -----END PGP MESSAGE----- /// # "[..]; /// /// let h = Helper {}; /// let mut v = VerifierBuilder::from_bytes(message)? /// .mapping(true) /// .with_policy(p, None, h)?; /// # let _ = v; /// # Ok(()) } /// ``` pub fn mapping(mut self, enabled: bool) -> Self { self.mapping = enabled; self } /// Creates the `Verifier`. /// /// Signature verifications are done under the given `policy` and /// relative to time `time`, or the current time, if `time` is /// `None`. `helper` is the [`VerificationHelper`] to use. /// /// /// # Examples /// /// ``` /// # fn main() -> sequoia_openpgp::Result<()> { /// use sequoia_openpgp as openpgp; /// # use openpgp::{KeyHandle, Cert, Result}; /// use openpgp::parse::{Parse, stream::*}; /// use openpgp::policy::StandardPolicy; /// /// let p = &StandardPolicy::new(); /// /// struct Helper {} /// impl VerificationHelper for Helper { /// // ... /// # fn get_certs(&mut self, ids: &[KeyHandle]) -> Result<Vec<Cert>> { /// # Ok(Vec::new()) /// # } /// # /// # fn check(&mut self, structure: MessageStructure) -> Result<()> { /// # Ok(()) /// # } /// } /// /// let message = /// // ... /// # &b"-----BEGIN PGP MESSAGE----- /// # /// # xA0DAAoW+zdR8Vh9rvEByxJiAAAAAABIZWxsbyBXb3JsZCHCdQQAFgoABgWCXrLl /// # AQAhCRD7N1HxWH2u8RYhBDnRAKtn1b2MBAECBfs3UfFYfa7xRUsBAJaxkU/RCstf /// # UD7TM30IorO1Mb9cDa/hPRxyzipulT55AQDN1m9LMqi9yJDjHNHwYYVwxDcg+pLY /// # YmAFv/UfO0vYBw== /// # =+l94 /// # -----END PGP MESSAGE----- /// # "[..]; /// /// let h = Helper {}; /// let mut v = VerifierBuilder::from_bytes(message)? /// // Customize the `Verifier` here. /// .with_policy(p, None, h)?; /// # let _ = v; /// # Ok(()) } /// ``` pub fn with_policy<T, H>(self, policy: &'a dyn Policy, time: T, helper: H) -> Result<Verifier<'a, H>> where H: VerificationHelper, T: Into<Option<time::SystemTime>>, { // Do not eagerly map `t` to the current time. let t = time.into(); Ok(Verifier { decryptor: Decryptor::from_cookie_reader( policy, self.message, NoDecryptionHelper { v: helper, }, t, Mode::Verify, self.buffer_size, self.mapping, true)?, }) } } impl<'a, H: VerificationHelper> Verifier<'a, H> { /// Returns a reference to the helper. pub fn helper_ref(&self) -> &H { &self.decryptor.helper_ref().v } /// Returns a mutable reference to the helper. pub fn helper_mut(&mut self) -> &mut H { &mut self.decryptor.helper_mut().v } /// Recovers the helper. pub fn into_helper(self) -> H { self.decryptor.into_helper().v } /// Returns true if the whole message has been processed and /// authenticated. /// /// If the function returns `true`, the whole message has been /// processed, the signatures are verified, and the message /// structure has been passed to [`VerificationHelper::check`]. /// Data read from this `Verifier` using [`io::Read`] has been /// authenticated. /// /// [`io::Read`]: std::io::Read /// /// If the function returns `false`, the message did not fit into /// the internal buffer, and therefore data read from this /// `Verifier` using [`io::Read`] has **not yet been /// authenticated**. It is important to treat this data as /// attacker controlled and not use it until it has been /// authenticated. /// /// # Examples /// /// This example demonstrates how to verify a message in a /// streaming fashion, writing the data to a temporary file and /// only commit the result once the data is authenticated. /// /// ``` /// # fn main() -> sequoia_openpgp::Result<()> { /// use std::io::{Read, Seek, SeekFrom}; /// use sequoia_openpgp as openpgp; /// use openpgp::{KeyHandle, Cert, Result}; /// use openpgp::parse::{Parse, stream::*}; /// use openpgp::policy::StandardPolicy; /// # /// # // Mock of `tempfile::tempfile`. /// # mod tempfile { /// # pub fn tempfile() -> sequoia_openpgp::Result<std::fs::File> { /// # unimplemented!() /// # } /// # } /// /// let p = &StandardPolicy::new(); /// /// // This fetches keys and computes the validity of the verification. /// struct Helper {} /// impl VerificationHelper for Helper { /// // ... /// # fn get_certs(&mut self, ids: &[KeyHandle]) -> Result<Vec<Cert>> { /// # Ok(Vec::new()) /// # } /// # fn check(&mut self, _: MessageStructure) -> Result<()> { /// # Ok(()) /// # } /// } /// /// let mut source = /// // ... /// # std::io::Cursor::new(&b"-----BEGIN PGP MESSAGE----- /// # /// # xA0DAAoW+zdR8Vh9rvEByxJiAAAAAABIZWxsbyBXb3JsZCHCdQQAFgoABgWCXrLl /// # AQAhCRD7N1HxWH2u8RYhBDnRAKtn1b2MBAECBfs3UfFYfa7xRUsBAJaxkU/RCstf /// # UD7TM30IorO1Mb9cDa/hPRxyzipulT55AQDN1m9LMqi9yJDjHNHwYYVwxDcg+pLY /// # YmAFv/UfO0vYBw== /// # =+l94 /// # -----END PGP MESSAGE----- /// # "[..]); /// /// fn consume(r: &mut dyn Read) -> Result<()> { /// // ... /// # let _ = r; Ok(()) /// } /// /// let h = Helper {}; /// let mut v = VerifierBuilder::from_reader(&mut source)? /// .with_policy(p, None, h)?; /// /// if v.message_processed() { /// // The data has been authenticated. /// consume(&mut v)?; /// } else { /// let mut tmp = tempfile::tempfile()?; /// std::io::copy(&mut v, &mut tmp)?; /// /// // If the copy succeeds, the message has been fully /// // processed and the data has been authenticated. /// assert!(v.message_processed()); /// /// // Rewind and consume. /// tmp.seek(SeekFrom::Start(0))?; /// consume(&mut tmp)?; /// } /// # Ok(()) } /// ``` pub fn message_processed(&self) -> bool { self.decryptor.message_processed() } } impl<'a, H: VerificationHelper> io::Read for Verifier<'a, H> { fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> { self.decryptor.read(buf) } } /// Verifies a detached signature. /// /// To create a `DetachedVerifier`, create a /// [`DetachedVerifierBuilder`] using [`Parse`], and customize it to /// your needs. /// /// [`Parse`]: super::Parse /// /// See [`GoodChecksum`] for what it means for a signature to be /// considered valid. When the signature(s) are processed, /// [`VerificationHelper::check`] will be called with a /// [`MessageStructure`] containing exactly one layer, a signature /// group. /// /// /// # Examples /// /// ``` /// # fn main() -> sequoia_openpgp::Result<()> { /// use std::io::{self, Read}; /// use sequoia_openpgp as openpgp; /// use openpgp::{KeyHandle, Cert, Result}; /// use openpgp::parse::{Parse, stream::*}; /// use sequoia_openpgp::policy::StandardPolicy; /// /// let p = &StandardPolicy::new(); /// /// // This fetches keys and computes the validity of the verification. /// struct Helper {} /// impl VerificationHelper for Helper { /// fn get_certs(&mut self, _ids: &[KeyHandle]) -> Result<Vec<Cert>> { /// Ok(Vec::new()) // Feed the Certs to the verifier here... /// } /// fn check(&mut self, structure: MessageStructure) -> Result<()> { /// Ok(()) // Implement your verification policy here. /// } /// } /// /// let signature = /// b"-----BEGIN PGP SIGNATURE----- /// /// wnUEABYKACcFglt+z/EWoQSOjDP6RiYzeXbZeXgGnAw0jdgsGQmQBpwMNI3YLBkA /// AHmUAP9mpj2wV0/ekDuzxZrPQ0bnobFVaxZGg7YzdlksSOERrwEA6v6czXQjKcv2 /// KOwGTamb+ajTLQ3YRG9lh+ZYIXynvwE= /// =IJ29 /// -----END PGP SIGNATURE-----"; /// /// let data = b"Hello World!"; /// let h = Helper {}; /// let mut v = DetachedVerifierBuilder::from_bytes(&signature[..])? /// .with_policy(p, None, h)?; /// v.verify_bytes(data)?; /// # Ok(()) } pub struct DetachedVerifier<'a, H: VerificationHelper> { decryptor: Decryptor<'a, NoDecryptionHelper<H>>, } assert_send_and_sync!(DetachedVerifier<'_, H> where H: VerificationHelper); /// A builder for `DetachedVerifier`. /// /// This allows the customization of [`DetachedVerifier`], which can /// be built using [`DetachedVerifierBuilder::with_policy`]. /// /// [`DetachedVerifierBuilder::with_policy`]: DetachedVerifierBuilder::with_policy() pub struct DetachedVerifierBuilder<'a> { signatures: Box<dyn BufferedReader<Cookie> + 'a>, mapping: bool, } assert_send_and_sync!(DetachedVerifierBuilder<'_>); impl<'a> Parse<'a, DetachedVerifierBuilder<'a>> for DetachedVerifierBuilder<'a> { fn from_buffered_reader<R>(reader: R) -> Result<DetachedVerifierBuilder<'a>> where R: BufferedReader<Cookie> + 'a, { DetachedVerifierBuilder::new(reader) } } impl<'a> crate::seal::Sealed for DetachedVerifierBuilder<'a> {} impl<'a> DetachedVerifierBuilder<'a> { fn new<B>(signatures: B) -> Result<Self> where B: buffered_reader::BufferedReader<Cookie> + 'a { Ok(DetachedVerifierBuilder { signatures: Box::new(signatures), mapping: false, }) } /// Enables mapping. /// /// If mapping is enabled, the packet parser will create a [`Map`] /// of the packets that can be inspected in /// [`VerificationHelper::inspect`]. Note that this buffers the /// packets contents, and is not recommended unless you know that /// the packets are small. /// /// [`Map`]: super::map::Map /// /// # Examples /// /// ``` /// # fn main() -> sequoia_openpgp::Result<()> { /// use sequoia_openpgp as openpgp; /// # use openpgp::{KeyHandle, Cert, Result}; /// use openpgp::parse::{Parse, stream::*}; /// use openpgp::policy::StandardPolicy; /// /// let p = &StandardPolicy::new(); /// /// struct Helper {} /// impl VerificationHelper for Helper { /// // ... /// # fn get_certs(&mut self, ids: &[KeyHandle]) -> Result<Vec<Cert>> { /// # Ok(Vec::new()) /// # } /// # /// # fn check(&mut self, structure: MessageStructure) -> Result<()> { /// # Ok(()) /// # } /// } /// /// let signature = /// // ... /// # b"-----BEGIN PGP SIGNATURE----- /// # /// # wnUEABYKACcFglt+z/EWoQSOjDP6RiYzeXbZeXgGnAw0jdgsGQmQBpwMNI3YLBkA /// # AHmUAP9mpj2wV0/ekDuzxZrPQ0bnobFVaxZGg7YzdlksSOERrwEA6v6czXQjKcv2 /// # KOwGTamb+ajTLQ3YRG9lh+ZYIXynvwE= /// # =IJ29 /// # -----END PGP SIGNATURE-----"; /// /// let h = Helper {}; /// let mut v = DetachedVerifierBuilder::from_bytes(&signature[..])? /// .mapping(true) /// .with_policy(p, None, h)?; /// # let _ = v; /// # Ok(()) } /// ``` pub fn mapping(mut self, enabled: bool) -> Self { self.mapping = enabled; self } /// Creates the `DetachedVerifier`. /// /// Signature verifications are done under the given `policy` and /// relative to time `time`, or the current time, if `time` is /// `None`. `helper` is the [`VerificationHelper`] to use. /// [`VerificationHelper::check`] will be called with a /// [`MessageStructure`] containing exactly one layer, a signature /// group. /// /// /// # Examples /// /// ``` /// # fn main() -> sequoia_openpgp::Result<()> { /// use sequoia_openpgp as openpgp; /// # use openpgp::{KeyHandle, Cert, Result}; /// use openpgp::parse::{Parse, stream::*}; /// use openpgp::policy::StandardPolicy; /// /// let p = &StandardPolicy::new(); /// /// struct Helper {} /// impl VerificationHelper for Helper { /// // ... /// # fn get_certs(&mut self, ids: &[KeyHandle]) -> Result<Vec<Cert>> { /// # Ok(Vec::new()) /// # } /// # /// # fn check(&mut self, structure: MessageStructure) -> Result<()> { /// # Ok(()) /// # } /// } /// /// let signature = /// // ... /// # b"-----BEGIN PGP SIGNATURE----- /// # /// # wnUEABYKACcFglt+z/EWoQSOjDP6RiYzeXbZeXgGnAw0jdgsGQmQBpwMNI3YLBkA /// # AHmUAP9mpj2wV0/ekDuzxZrPQ0bnobFVaxZGg7YzdlksSOERrwEA6v6czXQjKcv2 /// # KOwGTamb+ajTLQ3YRG9lh+ZYIXynvwE= /// # =IJ29 /// # -----END PGP SIGNATURE-----"; /// /// let h = Helper {}; /// let mut v = DetachedVerifierBuilder::from_bytes(&signature[..])? /// // Customize the `DetachedVerifier` here. /// .with_policy(p, None, h)?; /// # let _ = v; /// # Ok(()) } /// ``` pub fn with_policy<T, H>(self, policy: &'a dyn Policy, time: T, helper: H) -> Result<DetachedVerifier<'a, H>> where H: VerificationHelper, T: Into<Option<time::SystemTime>>, { // Do not eagerly map `t` to the current time. let t = time.into(); Ok(DetachedVerifier { decryptor: Decryptor::from_cookie_reader( policy, self.signatures, NoDecryptionHelper { v: helper, }, t, Mode::VerifyDetached, 0, self.mapping, false)?, }) } } impl<'a, H: VerificationHelper> DetachedVerifier<'a, H> { /// Verifies the given data. pub fn verify_buffered_reader<R>(&mut self, reader: R) -> Result<()> where R: BufferedReader<Cookie>, { self.decryptor.verify_detached(reader.into_boxed()) } /// Verifies the given data. pub fn verify_reader<R: io::Read + Send + Sync>(&mut self, reader: R) -> Result<()> { self.verify_buffered_reader(buffered_reader::Generic::with_cookie( reader, None, Default::default())) } /// Verifies the given data. pub fn verify_file<P: AsRef<Path>>(&mut self, path: P) -> Result<()> { self.verify_buffered_reader(buffered_reader::File::with_cookie( path, Default::default())?) } /// Verifies the given data. pub fn verify_bytes<B: AsRef<[u8]>>(&mut self, buf: B) -> Result<()> { self.verify_buffered_reader(buffered_reader::Memory::with_cookie( buf.as_ref(), Default::default())) } /// Returns a reference to the helper. pub fn helper_ref(&self) -> &H { &self.decryptor.helper_ref().v } /// Returns a mutable reference to the helper. pub fn helper_mut(&mut self) -> &mut H { &mut self.decryptor.helper_mut().v } /// Recovers the helper. pub fn into_helper(self) -> H { self.decryptor.into_helper().v } } /// Modes of operation for the Decryptor. #[derive(Debug, PartialEq, Eq)] enum Mode { Decrypt, Verify, VerifyDetached, } /// Decrypts and verifies an encrypted and optionally signed OpenPGP /// message. /// /// To create a `Decryptor`, create a [`DecryptorBuilder`] using /// [`Parse`], and customize it to your needs. /// /// [`Parse`]: super::Parse /// /// Signature verification and detection of ciphertext tampering /// requires processing the whole message first. Therefore, OpenPGP /// implementations supporting streaming operations necessarily must /// output unverified data. This has been a source of problems in the /// past. To alleviate this, we buffer the message first (up to 25 /// megabytes of net message data by default, see /// [`DEFAULT_BUFFER_SIZE`]), and verify the signatures if the message /// fits into our buffer. Nevertheless it is important to treat the /// data as unverified and untrustworthy until you have seen a /// positive verification. See [`Decryptor::message_processed`] for /// more information. /// /// [`Decryptor::message_processed`]: Decryptor::message_processed() /// /// See [`GoodChecksum`] for what it means for a signature to be /// considered valid. /// /// /// # Examples /// /// ``` /// # fn main() -> sequoia_openpgp::Result<()> { /// use std::io::Read; /// use sequoia_openpgp as openpgp; /// use openpgp::crypto::SessionKey; /// use openpgp::types::SymmetricAlgorithm; /// use openpgp::{KeyID, Cert, Result, packet::{Key, PKESK, SKESK}}; /// use openpgp::parse::{Parse, stream::*}; /// use sequoia_openpgp::policy::StandardPolicy; /// /// let p = &StandardPolicy::new(); /// /// // This fetches keys and computes the validity of the verification. /// struct Helper {} /// impl VerificationHelper for Helper { /// fn get_certs(&mut self, _ids: &[openpgp::KeyHandle]) -> Result<Vec<Cert>> { /// Ok(Vec::new()) // Feed the Certs to the verifier here... /// } /// fn check(&mut self, structure: MessageStructure) -> Result<()> { /// Ok(()) // Implement your verification policy here. /// } /// } /// impl DecryptionHelper for Helper { /// fn decrypt(&mut self, _: &[PKESK], skesks: &[SKESK], /// _sym_algo: Option<SymmetricAlgorithm>, /// decrypt: &mut dyn FnMut(Option<SymmetricAlgorithm>, &SessionKey) -> bool) /// -> Result<Option<Cert>> /// { /// skesks[0].decrypt(&"streng geheim".into()) /// .map(|(algo, session_key)| decrypt(algo, &session_key)); /// Ok(None) /// } /// } /// /// let message = /// b"-----BEGIN PGP MESSAGE----- /// /// wy4ECQMIY5Zs8RerVcXp85UgoUKjKkevNPX3WfcS5eb7rkT9I6kw6N2eEc5PJUDh /// 0j0B9mnPKeIwhp2kBHpLX/en6RfNqYauX9eSeia7aqsd/AOLbO9WMCLZS5d2LTxN /// rwwb8Aggyukj13Mi0FF5 /// =OB/8 /// -----END PGP MESSAGE-----"; /// /// let h = Helper {}; /// let mut v = DecryptorBuilder::from_bytes(&message[..])? /// .with_policy(p, None, h)?; /// /// let mut content = Vec::new(); /// v.read_to_end(&mut content)?; /// assert_eq!(content, b"Hello World!"); /// # Ok(()) } pub struct Decryptor<'a, H: VerificationHelper + DecryptionHelper> { helper: H, /// The issuers collected from OPS and Signature packets. issuers: Vec<KeyHandle>, /// The certificates used for signature verification. certs: Vec<Cert>, oppr: Option<PacketParserResult<'a>>, identity: Option<Fingerprint>, structure: IMessageStructure, /// We want to hold back some data until the signatures checked /// out. We buffer this here, cursor is the offset of unread /// bytes in the buffer. buffer_size: usize, reserve: Option<Vec<u8>>, cursor: usize, /// The mode of operation. mode: Mode, /// Whether we are actually processing a cleartext signature /// framework message. If so, we need to tweak our behavior a /// bit. processing_csf_message: Option<bool>, /// Signature verification relative to this time. /// /// This is needed for checking the signature's liveness. /// /// We want the same semantics as `Subpacket::signature_alive`. /// Specifically, when using the current time, we want to tolerate /// some clock skew, but when using some specific time, we don't. /// (See `Subpacket::signature_alive` for an explanation.) /// /// These semantics can be realized by making `time` an /// `Option<time::SystemTime>` and passing that as is to /// `Subpacket::signature_alive`. But that approach has two new /// problems. First, if we are told to use the current time, then /// we want to use the time at which the Verifier was /// instantiated, not the time at which we call /// `Subpacket::signature_alive`. Second, if we call /// `Subpacket::signature_alive` multiple times, they should all /// use the same time. To work around these issues, when a /// Verifier is instantiated, we evaluate `time` and we record how /// much we want to tolerate clock skew in the same way as /// `Subpacket::signature_alive`. time: time::SystemTime, clock_skew_tolerance: time::Duration, policy: &'a dyn Policy, } assert_send_and_sync!(Decryptor<'_, H> where H: VerificationHelper + DecryptionHelper); /// A builder for `Decryptor`. /// /// This allows the customization of [`Decryptor`], which can /// be built using [`DecryptorBuilder::with_policy`]. /// /// [`DecryptorBuilder::with_policy`]: DecryptorBuilder::with_policy() pub struct DecryptorBuilder<'a> { message: Box<dyn BufferedReader<Cookie> + 'a>, buffer_size: usize, mapping: bool, } assert_send_and_sync!(DecryptorBuilder<'_>); impl<'a> Parse<'a, DecryptorBuilder<'a>> for DecryptorBuilder<'a> { fn from_buffered_reader<R>(reader: R) -> Result<DecryptorBuilder<'a>> where R: BufferedReader<Cookie> + 'a, { DecryptorBuilder::new(reader) } } impl<'a> crate::seal::Sealed for DecryptorBuilder<'a> {} impl<'a> DecryptorBuilder<'a> { fn new<B>(signatures: B) -> Result<Self> where B: buffered_reader::BufferedReader<Cookie> + 'a { Ok(DecryptorBuilder { message: Box::new(signatures), buffer_size: DEFAULT_BUFFER_SIZE, mapping: false, }) } /// Changes the amount of buffered data. /// /// By default, we buffer up to 25 megabytes of net message data /// (see [`DEFAULT_BUFFER_SIZE`]). This changes the default. /// /// /// # Examples /// /// ``` /// # fn main() -> sequoia_openpgp::Result<()> { /// use sequoia_openpgp as openpgp; /// # use openpgp::{*, crypto::*, packet::prelude::*, types::*}; /// use openpgp::parse::{Parse, stream::*}; /// use openpgp::policy::StandardPolicy; /// /// let p = &StandardPolicy::new(); /// /// struct Helper {} /// impl VerificationHelper for Helper { /// // ... /// # fn get_certs(&mut self, ids: &[KeyHandle]) -> Result<Vec<Cert>> { /// # Ok(Vec::new()) /// # } /// # /// # fn check(&mut self, structure: MessageStructure) -> Result<()> { /// # Ok(()) /// # } /// } /// impl DecryptionHelper for Helper { /// // ... /// # fn decrypt(&mut self, _: &[PKESK], skesks: &[SKESK], /// # _sym_algo: Option<SymmetricAlgorithm>, /// # decrypt: &mut dyn FnMut(Option<SymmetricAlgorithm>, &SessionKey) -> bool) /// # -> Result<Option<Cert>> /// # { /// # Ok(None) /// # } /// } /// /// let message = /// // ... /// # &b"-----BEGIN PGP MESSAGE----- /// # /// # xA0DAAoW+zdR8Vh9rvEByxJiAAAAAABIZWxsbyBXb3JsZCHCdQQAFgoABgWCXrLl /// # AQAhCRD7N1HxWH2u8RYhBDnRAKtn1b2MBAECBfs3UfFYfa7xRUsBAJaxkU/RCstf /// # UD7TM30IorO1Mb9cDa/hPRxyzipulT55AQDN1m9LMqi9yJDjHNHwYYVwxDcg+pLY /// # YmAFv/UfO0vYBw== /// # =+l94 /// # -----END PGP MESSAGE----- /// # "[..]; /// /// let h = Helper {}; /// let mut v = DecryptorBuilder::from_bytes(message)? /// .buffer_size(1 << 12) /// .with_policy(p, None, h)?; /// # let _ = v; /// # Ok(()) } /// ``` pub fn buffer_size(mut self, size: usize) -> Self { self.buffer_size = size; self } /// Enables mapping. /// /// If mapping is enabled, the packet parser will create a [`Map`] /// of the packets that can be inspected in /// [`VerificationHelper::inspect`]. Note that this buffers the /// packets contents, and is not recommended unless you know that /// the packets are small. /// /// [`Map`]: super::map::Map /// /// # Examples /// /// ``` /// # fn main() -> sequoia_openpgp::Result<()> { /// use sequoia_openpgp as openpgp; /// # use openpgp::{*, crypto::*, packet::prelude::*, types::*}; /// use openpgp::parse::{Parse, stream::*}; /// use openpgp::policy::StandardPolicy; /// /// let p = &StandardPolicy::new(); /// /// struct Helper {} /// impl VerificationHelper for Helper { /// // ... /// # fn get_certs(&mut self, ids: &[KeyHandle]) -> Result<Vec<Cert>> { /// # Ok(Vec::new()) /// # } /// # /// # fn check(&mut self, structure: MessageStructure) -> Result<()> { /// # Ok(()) /// # } /// } /// impl DecryptionHelper for Helper { /// // ... /// # fn decrypt(&mut self, _: &[PKESK], skesks: &[SKESK], /// # _sym_algo: Option<SymmetricAlgorithm>, /// # decrypt: &mut dyn FnMut(Option<SymmetricAlgorithm>, &SessionKey) -> bool) /// # -> Result<Option<Cert>> /// # { /// # Ok(None) /// # } /// } /// /// let message = /// // ... /// # &b"-----BEGIN PGP MESSAGE----- /// # /// # xA0DAAoW+zdR8Vh9rvEByxJiAAAAAABIZWxsbyBXb3JsZCHCdQQAFgoABgWCXrLl /// # AQAhCRD7N1HxWH2u8RYhBDnRAKtn1b2MBAECBfs3UfFYfa7xRUsBAJaxkU/RCstf /// # UD7TM30IorO1Mb9cDa/hPRxyzipulT55AQDN1m9LMqi9yJDjHNHwYYVwxDcg+pLY /// # YmAFv/UfO0vYBw== /// # =+l94 /// # -----END PGP MESSAGE----- /// # "[..]; /// /// let h = Helper {}; /// let mut v = DecryptorBuilder::from_bytes(message)? /// .mapping(true) /// .with_policy(p, None, h)?; /// # let _ = v; /// # Ok(()) } /// ``` pub fn mapping(mut self, enabled: bool) -> Self { self.mapping = enabled; self } /// Creates the `Decryptor`. /// /// Signature verifications are done under the given `policy` and /// relative to time `time`, or the current time, if `time` is /// `None`. `helper` is the [`VerificationHelper`] and /// [`DecryptionHelper`] to use. /// /// /// # Examples /// /// ``` /// # fn main() -> sequoia_openpgp::Result<()> { /// use sequoia_openpgp as openpgp; /// # use openpgp::{*, crypto::*, packet::prelude::*, types::*}; /// use openpgp::parse::{Parse, stream::*}; /// use openpgp::policy::StandardPolicy; /// /// let p = &StandardPolicy::new(); /// /// struct Helper {} /// impl VerificationHelper for Helper { /// // ... /// # fn get_certs(&mut self, ids: &[KeyHandle]) -> Result<Vec<Cert>> { /// # Ok(Vec::new()) /// # } /// # /// # fn check(&mut self, structure: MessageStructure) -> Result<()> { /// # Ok(()) /// # } /// } /// impl DecryptionHelper for Helper { /// // ... /// # fn decrypt(&mut self, _: &[PKESK], skesks: &[SKESK], /// # _sym_algo: Option<SymmetricAlgorithm>, /// # decrypt: &mut dyn FnMut(Option<SymmetricAlgorithm>, &SessionKey) -> bool) /// # -> Result<Option<Cert>> /// # { /// # Ok(None) /// # } /// } /// /// let message = /// // ... /// # &b"-----BEGIN PGP MESSAGE----- /// # /// # xA0DAAoW+zdR8Vh9rvEByxJiAAAAAABIZWxsbyBXb3JsZCHCdQQAFgoABgWCXrLl /// # AQAhCRD7N1HxWH2u8RYhBDnRAKtn1b2MBAECBfs3UfFYfa7xRUsBAJaxkU/RCstf /// # UD7TM30IorO1Mb9cDa/hPRxyzipulT55AQDN1m9LMqi9yJDjHNHwYYVwxDcg+pLY /// # YmAFv/UfO0vYBw== /// # =+l94 /// # -----END PGP MESSAGE----- /// # "[..]; /// /// let h = Helper {}; /// let mut v = DecryptorBuilder::from_bytes(message)? /// // Customize the `Decryptor` here. /// .with_policy(p, None, h)?; /// # let _ = v; /// # Ok(()) } /// ``` pub fn with_policy<T, H>(self, policy: &'a dyn Policy, time: T, helper: H) -> Result<Decryptor<'a, H>> where H: VerificationHelper + DecryptionHelper, T: Into<Option<time::SystemTime>>, { // Do not eagerly map `t` to the current time. let t = time.into(); Decryptor::from_cookie_reader( policy, self.message, helper, t, Mode::Decrypt, self.buffer_size, self.mapping, false) } } /// Helper for decrypting messages. /// /// This trait abstracts over session key decryption. It allows us to /// provide the [`Decryptor`] without imposing any policy on how the /// session key is decrypted. /// pub trait DecryptionHelper { /// Decrypts the message. /// /// This function is called with every [`PKESK`] and [`SKESK`] /// packet found in the message. The implementation must decrypt /// the symmetric algorithm and session key from one of the /// [`PKESK`] packets, the [`SKESK`] packets, or retrieve it from /// a cache, and then call `decrypt` with the symmetric algorithm /// and session key. `decrypt` returns `true` if the decryption /// was successful. /// /// [`PKESK`]: crate::packet::PKESK /// [`SKESK`]: crate::packet::SKESK /// /// If a symmetric algorithm is given, it should be passed on to /// [`PKESK::decrypt`]. /// /// [`PKESK::decrypt`]: crate::packet::PKESK#method.decrypt /// /// If the message is decrypted using a [`PKESK`] packet, then the /// fingerprint of the certificate containing the encryption /// subkey should be returned. This is used in conjunction with /// the intended recipient subpacket (see [Intended Recipient /// Fingerprint]) to prevent [*Surreptitious Forwarding*]. /// /// [Intended Recipient Fingerprint]: https://www.rfc-editor.org/rfc/rfc9580.html#name-intended-recipient-fingerpr /// [*Surreptitious Forwarding*]: http://world.std.com/~dtd/sign_encrypt/sign_encrypt7.html /// /// This method will be called once per encryption layer. /// /// # Examples /// /// This example demonstrates how to decrypt a message using local /// keys (i.e. excluding remote keys like smart cards) while /// maximizing convenience for the user. /// /// ``` /// use sequoia_openpgp as openpgp; /// use openpgp::{Cert, Fingerprint, KeyHandle, KeyID, Result}; /// use openpgp::crypto::SessionKey; /// use openpgp::types::SymmetricAlgorithm; /// use openpgp::packet::{PKESK, SKESK}; /// # use openpgp::packet::{Key, key::*}; /// use openpgp::parse::stream::*; /// # fn lookup_cache(_: &[PKESK], _: &[SKESK]) /// # -> Option<(Option<Cert>, Option<SymmetricAlgorithm>, SessionKey)> { /// # unimplemented!() /// # } /// # fn lookup_key(_: Option<KeyHandle>) /// # -> Option<(Cert, Key<SecretParts, UnspecifiedRole>)> { /// # unimplemented!() /// # } /// # fn all_keys() -> impl Iterator<Item = (Cert, Key<SecretParts, UnspecifiedRole>)> { /// # Vec::new().into_iter() /// # } /// /// struct Helper { /* ... */ } /// impl DecryptionHelper for Helper { /// fn decrypt(&mut self, pkesks: &[PKESK], skesks: &[SKESK], /// sym_algo: Option<SymmetricAlgorithm>, /// decrypt: &mut dyn FnMut(Option<SymmetricAlgorithm>, &SessionKey) -> bool) /// -> Result<Option<Cert>> /// { /// // Try to decrypt, from the most convenient method to the /// // least convenient one. /// /// // First, see if it is in the cache. /// if let Some((cert, algo, sk)) = lookup_cache(pkesks, skesks) { /// if decrypt(algo, &sk) { /// return Ok(cert); /// } /// } /// /// // Second, we try those keys that we can use without /// // prompting for a password. /// for pkesk in pkesks { /// if let Some((cert, key)) = lookup_key(pkesk.recipient()) { /// if ! key.secret().is_encrypted() { /// let mut keypair = key.clone().into_keypair()?; /// if pkesk.decrypt(&mut keypair, sym_algo) /// .map(|(algo, sk)| decrypt(algo, &sk)) /// .unwrap_or(false) /// { /// return Ok(Some(cert)); /// } /// } /// } /// } /// /// // Third, we try to decrypt PKESK packets with /// // wildcard recipients using those keys that we can /// // use without prompting for a password. /// for pkesk in pkesks.iter().filter( /// |p| p.recipient().is_none()) /// { /// for (cert, key) in all_keys() { /// if ! key.secret().is_encrypted() { /// let mut keypair = key.clone().into_keypair()?; /// if pkesk.decrypt(&mut keypair, sym_algo) /// .map(|(algo, sk)| decrypt(algo, &sk)) /// .unwrap_or(false) /// { /// return Ok(Some(cert)); /// } /// } /// } /// } /// /// // Fourth, we try to decrypt all PKESK packets that we /// // need encrypted keys for. /// // [...] /// /// // Fifth, we try to decrypt all PKESK packets with /// // wildcard recipients using encrypted keys. /// // [...] /// /// // At this point, we have exhausted our options at /// // decrypting the PKESK packets. /// if skesks.is_empty() { /// return /// Err(anyhow::anyhow!("No key to decrypt message")); /// } /// /// // Finally, try to decrypt using the SKESKs. /// loop { /// let password = // Prompt for a password. /// # "".into(); /// /// for skesk in skesks { /// if skesk.decrypt(&password) /// .map(|(algo, sk)| decrypt(algo, &sk)) /// .unwrap_or(false) /// { /// return Ok(None); /// } /// } /// /// eprintln!("Bad password."); /// } /// } /// } /// ``` fn decrypt(&mut self, pkesks: &[PKESK], skesks: &[SKESK], sym_algo: Option<SymmetricAlgorithm>, decrypt: &mut dyn FnMut(Option<SymmetricAlgorithm>, &SessionKey) -> bool) -> Result<Option<Cert>>; } impl<'a, H: VerificationHelper + DecryptionHelper> Decryptor<'a, H> { /// Returns a reference to the helper. pub fn helper_ref(&self) -> &H { &self.helper } /// Returns a mutable reference to the helper. pub fn helper_mut(&mut self) -> &mut H { &mut self.helper } /// Recovers the helper. pub fn into_helper(self) -> H { self.helper } /// Returns true if the whole message has been processed and /// authenticated. /// /// If the function returns `true`, the whole message has been /// processed, the signatures are verified, and the message /// structure has been passed to [`VerificationHelper::check`]. /// Data read from this `Verifier` using [`io::Read`] has been /// authenticated. /// /// [`io::Read`]: std::io::Read /// /// If the function returns `false`, the message did not fit into /// the internal buffer, and therefore data read from this /// `Verifier` using [`io::Read`] has **not yet been /// authenticated**. It is important to treat this data as /// attacker controlled and not use it until it has been /// authenticated. /// /// # Examples /// /// This example demonstrates how to verify a message in a /// streaming fashion, writing the data to a temporary file and /// only commit the result once the data is authenticated. /// /// ``` /// # fn main() -> sequoia_openpgp::Result<()> { /// use std::io::{Read, Seek, SeekFrom}; /// use sequoia_openpgp as openpgp; /// use openpgp::{KeyHandle, Cert, Result}; /// use openpgp::parse::{Parse, stream::*}; /// use openpgp::policy::StandardPolicy; /// # /// # // Mock of `tempfile::tempfile`. /// # mod tempfile { /// # pub fn tempfile() -> sequoia_openpgp::Result<std::fs::File> { /// # unimplemented!() /// # } /// # } /// /// let p = &StandardPolicy::new(); /// /// // This fetches keys and computes the validity of the verification. /// struct Helper {} /// impl VerificationHelper for Helper { /// // ... /// # fn get_certs(&mut self, ids: &[KeyHandle]) -> Result<Vec<Cert>> { /// # Ok(Vec::new()) /// # } /// # fn check(&mut self, _: MessageStructure) -> Result<()> { /// # Ok(()) /// # } /// } /// /// let mut source = /// // ... /// # std::io::Cursor::new(&b"-----BEGIN PGP MESSAGE----- /// # /// # xA0DAAoW+zdR8Vh9rvEByxJiAAAAAABIZWxsbyBXb3JsZCHCdQQAFgoABgWCXrLl /// # AQAhCRD7N1HxWH2u8RYhBDnRAKtn1b2MBAECBfs3UfFYfa7xRUsBAJaxkU/RCstf /// # UD7TM30IorO1Mb9cDa/hPRxyzipulT55AQDN1m9LMqi9yJDjHNHwYYVwxDcg+pLY /// # YmAFv/UfO0vYBw== /// # =+l94 /// # -----END PGP MESSAGE----- /// # "[..]); /// /// fn consume(r: &mut dyn Read) -> Result<()> { /// // ... /// # let _ = r; Ok(()) /// } /// /// let h = Helper {}; /// let mut v = VerifierBuilder::from_reader(&mut source)? /// .with_policy(p, None, h)?; /// /// if v.message_processed() { /// // The data has been authenticated. /// consume(&mut v)?; /// } else { /// let mut tmp = tempfile::tempfile()?; /// std::io::copy(&mut v, &mut tmp)?; /// /// // If the copy succeeds, the message has been fully /// // processed and the data has been authenticated. /// assert!(v.message_processed()); /// /// // Rewind and consume. /// tmp.seek(SeekFrom::Start(0))?; /// consume(&mut tmp)?; /// } /// # Ok(()) } /// ``` pub fn message_processed(&self) -> bool { // oppr is only None after we've processed the packet sequence. self.oppr.is_none() } /// Creates the `Decryptor`, and buffers the data up to `buffer_size`. fn from_cookie_reader<T>( policy: &'a dyn Policy, bio: Box<dyn BufferedReader<Cookie> + 'a>, helper: H, time: T, mode: Mode, buffer_size: usize, mapping: bool, csf_transformation: bool, ) -> Result<Decryptor<'a, H>> where T: Into<Option<time::SystemTime>> { tracer!(TRACE, "Decryptor::from_cookie_reader", TRACE_INDENT); let time = time.into(); let tolerance = time .map(|_| time::Duration::new(0, 0)) .unwrap_or( crate::packet::signature::subpacket::CLOCK_SKEW_TOLERANCE); let time = time.unwrap_or_else(crate::now); let mut ppr = PacketParserBuilder::from_cookie_reader(bio)? .map(mapping) .csf_transformation(csf_transformation) .build()?; let mut v = Decryptor { helper, issuers: Vec::new(), certs: Vec::new(), oppr: None, identity: None, structure: IMessageStructure::new(), buffer_size, reserve: None, cursor: 0, mode, time, clock_skew_tolerance: tolerance, policy, processing_csf_message: None, // We don't know yet. }; let mut pkesks: Vec<packet::PKESK> = Vec::new(); let mut skesks: Vec<packet::SKESK> = Vec::new(); while let PacketParserResult::Some(mut pp) = ppr { match &pp.packet { Packet::PKESK(p) => t!("Found a {:?}v{} at depth {}", pp.packet.tag(), p.version(), pp.recursion_depth()), Packet::SKESK(p) => t!("Found a {:?}v{} at depth {}", pp.packet.tag(), p.version(), pp.recursion_depth()), Packet::SEIP(p) => t!("Found a {:?}v{} at depth {}", pp.packet.tag(), p.version(), pp.recursion_depth()), _ => t!("Found a {:?} at depth {}", pp.packet.tag(), pp.recursion_depth()), } // Check whether we are actually processing a cleartext // signature framework message. if v.processing_csf_message.is_none() { v.processing_csf_message = Some(pp.processing_csf_message()); } v.policy.packet(&pp.packet)?; v.helper.inspect(&pp)?; // When verifying detached signatures, we parse only the // signatures here, which on their own are not a valid // message. if v.mode == Mode::VerifyDetached { if pp.packet.tag() != packet::Tag::Signature && pp.packet.tag() != packet::Tag::Marker { return Err(Error::MalformedMessage( format!("Expected signature, got {}", pp.packet.tag())) .into()); } } else if let Err(err) = pp.possible_message() { if v.processing_csf_message.expect("set by now") { // Our CSF transformation yields just one OPS // packet per encountered 'Hash' algorithm header, // and it cannot know how many signatures are in // fact following. Therefore, the message will // not be well-formed according to the grammar. // But, since we created the message structure // during the transformation, we know it is good, // even if it is a little out of spec. } else { t!("Malformed message: {}", err); return Err(err.context("Malformed OpenPGP message")); } } let sym_algo_hint = match &pp.packet { Packet::SEIP(SEIP::V2(seip)) => Some(seip.symmetric_algo()), _ => None, }; match pp.packet { Packet::CompressedData(ref p) => v.structure.new_compression_layer(p.algo()), Packet::SEIP(_) if v.mode == Mode::Decrypt => { t!("Found the encryption container"); // Get the symmetric algorithm from the decryption // proxy function. This is necessary because we // cannot get the algorithm from the SEIP packet. let mut sym_algo = None; { let mut decryption_proxy = |algo, secret: &SessionKey| { // Take the algo from the SEIPDv2 packet over // the dummy one from the SKESK6 packet. let algo = sym_algo_hint.or(algo); let result = pp.decrypt(algo, secret); t!("pp.decrypt({:?}, {:?}) => {:?}", algo, secret, result); if let Ok(_) = result { sym_algo = Some(algo); true } else { false } }; v.identity = v.helper.decrypt(&pkesks[..], &skesks[..], sym_algo_hint, &mut decryption_proxy)? .map(|cert| cert.fingerprint()); } if ! pp.processed() { return Err( Error::MissingSessionKey( "No session key decrypted".into()).into()); } let sym_algo = if let Some(Some(a)) = sym_algo { a } else { return Err(Error::InvalidOperation( "No symmetric algorithm known".into()).into()); }; v.policy.symmetric_algorithm(sym_algo)?; if let Packet::SEIP(SEIP::V2(p)) = &pp.packet { v.policy.aead_algorithm(p.aead())?; } v.structure.new_encryption_layer( pp.recursion_depth(), pp.packet.tag() == packet::Tag::SEIP && pp.packet.version() == Some(1), sym_algo, if let Packet::SEIP(SEIP::V2(p)) = &pp.packet { Some(p.aead()) } else { None }); }, Packet::OnePassSig(ref ops) => { v.structure.push_ops(ops); v.push_issuer(ops.issuer().clone()); }, Packet::Literal(_) => { v.structure.insert_missing_signature_group(); v.oppr = Some(PacketParserResult::Some(pp)); v.finish_maybe()?; return Ok(v); }, #[allow(deprecated)] Packet::MDC(ref mdc) => if ! mdc.valid() { return Err(Error::ManipulatedMessage.into()); }, _ => (), } let (p, ppr_tmp) = pp.recurse()?; match p { Packet::PKESK(pkesk) => pkesks.push(pkesk), Packet::SKESK(skesk) => skesks.push(skesk), Packet::Signature(sig) => { // The following structure is allowed: // // SIG LITERAL // // In this case, we get the issuer from the // signature itself. sig.get_issuers().into_iter() .for_each(|i| v.push_issuer(i)); v.structure.push_bare_signature(Ok(sig)); }, Packet::Unknown(u) if u.tag() == packet::Tag::Signature => { v.structure.push_bare_signature(Err(u)); }, _ => (), } ppr = ppr_tmp; } if v.mode == Mode::VerifyDetached && !v.structure.layers.is_empty() { return Ok(v); } // We can only get here if we didn't encounter a literal data // packet. Err(Error::MalformedMessage( "Malformed OpenPGP message".into()).into()) } /// Verifies the given data in detached verification mode. fn verify_detached<'d>(&mut self, data: Box<dyn BufferedReader<Cookie> + 'd>) -> Result<()> { assert_eq!(self.mode, Mode::VerifyDetached); let sigs = if let IMessageLayer::SignatureGroup { sigs, .. } = &mut self.structure.layers[0] { sigs } else { unreachable!("There is exactly one signature group layer") }; // Compute the necessary hashes. let algos: Vec<_> = sigs.iter().filter_map(|s| { let s = s.as_ref().ok()?; let h = s.hash_algo(); Some(HashingMode::for_signature(h, s)) }).collect(); let hashes = crate::parse::hashed_reader::hash_buffered_reader(data, &algos)?; // Attach the digests. for sig in sigs.iter_mut().filter_map(|s| s.as_ref().ok()) { let need_hash = HashingMode::for_signature(sig.hash_algo(), sig); // Note: |hashes| < 10, most likely 1. for mode in hashes.iter() .filter(|m| m.map(|c| c.algo()) == need_hash) { // Clone the hash context, update it with the // signature. use crate::crypto::hash::Hash; let mut hash = mode.as_ref().clone(); sig.hash(&mut hash)?; // Attach digest to the signature. let mut digest = vec![0; hash.digest_size()]; let _ = hash.digest(&mut digest); sig.set_computed_digest(Some(digest)); } } self.verify_signatures() } /// Stashes the given Signature (if it is one) for later /// verification. fn push_sig(&mut self, p: Packet) -> Result<()> { match p { Packet::Signature(sig) => { sig.get_issuers().into_iter().for_each(|i| self.push_issuer(i)); self.structure.push_signature( Ok(sig), self.processing_csf_message.expect("set by now")); }, Packet::Unknown(sig) if sig.tag() == packet::Tag::Signature => { self.structure.push_signature( Err(sig), self.processing_csf_message.expect("set by now")); }, _ => (), } Ok(()) } /// Records the issuer for the later certificate lookup. fn push_issuer<I: Into<KeyHandle>>(&mut self, issuer: I) { let issuer = issuer.into(); match issuer { KeyHandle::KeyID(id) if id.is_wildcard() => { // Ignore, they are not useful for lookups. }, KeyHandle::KeyID(_) => { for known in self.issuers.iter() { if known.aliases(&issuer) { return; } } // Unknown, record. self.issuers.push(issuer); }, KeyHandle::Fingerprint(_) => { for known in self.issuers.iter_mut() { if known.aliases(&issuer) { // Replace. We may upgrade a KeyID to a // Fingerprint. *known = issuer; return; } } // Unknown, record. self.issuers.push(issuer); }, } } // If the amount of remaining data does not exceed the reserve, // finish processing the OpenPGP packet sequence. // // Note: once this call succeeds, you may not call it again. fn finish_maybe(&mut self) -> Result<()> { tracer!(TRACE, "Decryptor::finish_maybe", TRACE_INDENT); if let Some(PacketParserResult::Some(mut pp)) = self.oppr.take() { // Check if we hit EOF. let data_len = pp.data(self.buffer_size + 1)?.len(); if data_len - self.cursor <= self.buffer_size { // Stash the reserve. t!("Hit eof with {} bytes of the current buffer consumed.", self.cursor); pp.consume(self.cursor); self.cursor = 0; self.reserve = Some(pp.steal_eof()?); // Process the rest of the packets. let mut ppr = PacketParserResult::Some(pp); let mut first = true; while let PacketParserResult::Some(pp) = ppr { t!("Found a {:?} at depth {}", pp.packet.tag(), pp.recursion_depth()); // The literal data packet was already inspected. if first { assert_eq!(pp.packet.tag(), packet::Tag::Literal); first = false; } else { self.helper.inspect(&pp)?; } let possible_message = pp.possible_message(); // If we are ascending, and the packet was the // last packet in a SEIP container, we need to be // extra careful with reporting errors to avoid // creating a decryption oracle. let last_recursion_depth = pp.recursion_depth(); let (p, ppr_tmp) = match pp.recurse() { Ok(v) => v, Err(e) => { // Assuming we just tried to ascend, // should there have been a MDC packet? // If so, this may be an attack. if self.structure.expect_mdc_at( last_recursion_depth - 1) { return Err(Error::ManipulatedMessage.into()); } else { return Err(e); } }, }; ppr = ppr_tmp; let recursion_depth = ppr.as_ref() .map(|pp| pp.recursion_depth()).unwrap_or(0); // Did we just ascend? if recursion_depth + 1 == last_recursion_depth && self.structure.expect_mdc_at(recursion_depth) { match &p { #[allow(deprecated)] Packet::MDC(mdc) if mdc.valid() => (), // Good. _ => // Bad. return Err(Error::ManipulatedMessage.into()), } if possible_message.is_err() { return Err(Error::ManipulatedMessage.into()); } } if let Err(_err) = possible_message { if self.processing_csf_message.expect("set by now") { // CSF transformation creates slightly out // of spec message structure. See above // for longer explanation. } else { return Err(Error::ManipulatedMessage.into()); } } self.push_sig(p)?; } // If we finished parsing, validate the message structure. if let PacketParserResult::EOF(eof) = ppr { // If we parse a signed message synthesized from a // cleartext signature framework message, we don't // quite get the structure right, so relax the // requirement in this case. if ! self.processing_csf_message.expect("set by now") { eof.is_message()?; } } self.verify_signatures() } else { t!("Didn't hit EOF."); self.oppr = Some(PacketParserResult::Some(pp)); Ok(()) } } else { panic!("No ppr."); } } /// Verifies the signatures. fn verify_signatures(&mut self) -> Result<()> { tracer!(TRACE, "Decryptor::verify_signatures", TRACE_INDENT); t!("called"); self.certs = self.helper.get_certs(&self.issuers)?; t!("VerificationHelper::get_certs produced {} certs", self.certs.len()); let mut results = MessageStructure::new(); for layer in self.structure.layers.iter_mut() { match layer { IMessageLayer::Compression { algo } => results.new_compression_layer(*algo), IMessageLayer::Encryption { sym_algo, aead_algo, .. } => results.new_encryption_layer(*sym_algo, *aead_algo), IMessageLayer::SignatureGroup { sigs, .. } => { results.new_signature_group(); 'sigs: for sig in sigs.iter_mut() { let sig = match sig { Ok(s) => s, Err(u) => { // Unparsablee signature. t!("Unparsablee signature: {}", u.error()); results.push_verification_result( Err(VerificationError::UnknownSignature { sig: u, })); continue; } }; let sigid = *sig.digest_prefix(); let sig_time = if let Some(t) = sig.signature_creation_time() { t } else { // Invalid signature. results.push_verification_result( Err(VerificationError::MalformedSignature { sig, error: Error::MalformedPacket( "missing a Signature Creation Time \ subpacket" .into()).into(), })); t!("{:02X}{:02X}: Missing a signature creation time subpacket", sigid[0], sigid[1]); continue; }; let mut err = VerificationErrorInternal::MissingKey {}; let issuers = sig.get_issuers(); // Note: If there are no issuers, the only way // to verify the signature is to try every key // that could possibly have created the // signature. While this may be feasible if // the set of potential signing keys is small, // the use case of hiding the signer's // identity seems better solved using // encryption. Furthermore, no other OpenPGP // implementation seems to support this kind // of wildcard signatures. let no_issuers = issuers.is_empty(); for ka in self.certs.iter().flat_map( |c| c.keys().key_handles(issuers.clone())) { if no_issuers { // Slightly awkward control flow // change. Below this loop, we still // have to add this signature to the // results with the default error, // `VerificationError::MissingKey`. break; } let cert = ka.cert(); let fingerprint = ka.key().fingerprint(); let ka = match ka.with_policy(self.policy, sig_time) { Err(policy_err) => { t!("{:02X}{:02X}: key {} rejected by policy: {}", sigid[0], sigid[1], fingerprint, policy_err); err = VerificationErrorInternal::UnboundKey { cert, error: policy_err, }; continue; } Ok(ka) => { t!("{:02X}{:02X}: key {} accepted by policy", sigid[0], sigid[1], fingerprint); ka } }; err = if let Err(error) = ka.valid_cert().alive() { t!("{:02X}{:02X}: cert {} not alive: {}", sigid[0], sigid[1], ka.cert().fingerprint(), error); VerificationErrorInternal::BadKey { ka, error, } } else if let Err(error) = ka.alive() { t!("{:02X}{:02X}: key {} not alive: {}", sigid[0], sigid[1], ka.key().fingerprint(), error); VerificationErrorInternal::BadKey { ka, error, } } else if let RevocationStatus::Revoked(rev) = ka.valid_cert().revocation_status() { t!("{:02X}{:02X}: cert {} revoked: {:?}", sigid[0], sigid[1], ka.cert().fingerprint(), rev); VerificationErrorInternal::BadKey { ka, error: Error::InvalidKey( "certificate is revoked".into()) .into(), } } else if let RevocationStatus::Revoked(rev) = ka.revocation_status() { t!("{:02X}{:02X}: key {} revoked: {:?}", sigid[0], sigid[1], ka.key().fingerprint(), rev); VerificationErrorInternal::BadKey { ka, error: Error::InvalidKey( "signing key is revoked".into()) .into(), } } else if ! ka.for_signing() { t!("{:02X}{:02X}: key {} not signing capable", sigid[0], sigid[1], ka.key().fingerprint()); VerificationErrorInternal::BadKey { ka, error: Error::InvalidKey( "key is not signing capable".into()) .into(), } } else if let Err(error) = sig.signature_alive( self.time, self.clock_skew_tolerance) { t!("{:02X}{:02X}: Signature not alive: {}", sigid[0], sigid[1], error); VerificationErrorInternal::BadSignature { ka, error, } } else if self.identity.as_ref().map(|identity| { let (have_one, contains_identity) = sig.intended_recipients() .fold((false, false), |(_, contains_one), ir| { ( true, contains_one || identity == ir ) }); have_one && ! contains_identity }).unwrap_or(false) { // The signature contains intended // recipients, but we are not one. // Treat the signature as bad. t!("{:02X}{:02X}: not an intended recipient", sigid[0], sigid[1]); VerificationErrorInternal::BadSignature { ka, error: Error::BadSignature( "Not an intended recipient".into()) .into(), } } else { match sig.verify_document(ka.key()) { Ok(()) => { if let Err(error) = self.policy.signature( sig, Default::default()) { t!("{:02X}{:02X}: signature rejected by policy: {}", sigid[0], sigid[1], error); VerificationErrorInternal::BadSignature { ka, error, } } else { t!("{:02X}{:02X}: good checksum using {}", sigid[0], sigid[1], ka.key().fingerprint()); results.push_verification_result( Ok(GoodChecksum { sig, ka, })); // Continue to the next sig. continue 'sigs; } } Err(error) => { t!("{:02X}{:02X} using {}: error: {}", sigid[0], sigid[1], ka.key().fingerprint(), error); VerificationErrorInternal::BadSignature { ka, error, } } } } } let err = err.attach_sig(sig); t!("{:02X}{:02X}: returning: {:?}", sigid[0], sigid[1], err); results.push_verification_result(Err(err)); } } } } let r = self.helper.check(results); t!("-> {:?}", r); r } /// Like `io::Read::read()`, but returns our `Result`. fn read_helper(&mut self, buf: &mut [u8]) -> Result<usize> { tracer!(TRACE, "Decryptor::read_helper", TRACE_INDENT); t!("read(buf of {} bytes)", buf.len()); if buf.is_empty() { return Ok(0); } if let Some(ref mut reserve) = self.reserve { // The message has been verified. We can now drain the // reserve. t!("Message verified, draining reserve."); assert!(self.oppr.is_none()); assert!(self.cursor <= reserve.len()); let n = cmp::min(buf.len(), reserve.len() - self.cursor); buf[..n] .copy_from_slice(&reserve[self.cursor..n + self.cursor]); self.cursor += n; return Ok(n); } // Read the data from the Literal data packet. if let Some(PacketParserResult::Some(mut pp)) = self.oppr.take() { // Be careful to not read from the reserve. if self.cursor >= self.buffer_size { // Consume the active part of the buffer. t!("Consuming first part of the buffer."); pp.consume(self.buffer_size); self.cursor -= self.buffer_size; } // We request two times what our buffer size is, the first // part is the one we give out, the second part is the one // we hold back. let data_len = pp.data(2 * self.buffer_size)?.len(); t!("Read {} bytes.", data_len); if data_len - self.cursor <= self.buffer_size { self.oppr = Some(PacketParserResult::Some(pp)); self.finish_maybe()?; self.read_helper(buf) } else { let data = pp.data(2 * self.buffer_size - self.cursor)?; assert_eq!(data.len(), data_len); let n = buf.len().min(data_len - self.buffer_size - self.cursor); buf[..n].copy_from_slice(&data[self.cursor..self.cursor + n]); self.cursor += n; self.oppr = Some(PacketParserResult::Some(pp)); t!("Copied {} bytes from buffer, cursor is {}.", n, self.cursor); Ok(n) } } else { panic!("No ppr."); } } } impl<'a, H: VerificationHelper + DecryptionHelper> io::Read for Decryptor<'a, H> { fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> { match self.read_helper(buf) { Ok(n) => Ok(n), Err(e) => match e.downcast::<io::Error>() { // An io::Error. Pass as-is. Ok(e) => Err(e), // A failure. Wrap it. Err(e) => Err(io::Error::new(io::ErrorKind::Other, e)), }, } } } #[cfg(test)] pub(crate) mod test { use std::io::Read; use super::*; use std::convert::TryFrom; use crate::parse::Parse; use crate::policy::{NullPolicy as NP, StandardPolicy as P}; use crate::serialize::Serialize; use crate::{ crypto::Password, }; /// Verification helper for the tests. #[derive(Clone)] pub struct VHelper { good: usize, unknown: usize, bad: usize, error: usize, certs: Vec<Cert>, keys: Vec<Cert>, passwords: Vec<Password>, for_decryption: bool, error_out: bool, pub packets: Vec<Packet>, } impl std::fmt::Debug for VHelper { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { f.debug_struct("VHelper") .field("good", &self.good) .field("unknown", &self.unknown) .field("bad", &self.bad) .field("error", &self.error) .field("error_out", &self.error_out) .finish() } } impl Default for VHelper { fn default() -> Self { VHelper { good: 0, unknown: 0, bad: 0, error: 0, certs: Vec::default(), keys: Vec::default(), passwords: Default::default(), for_decryption: false, error_out: true, packets: Default::default(), } } } impl VHelper { /// Creates a new verification helper. pub fn new(good: usize, unknown: usize, bad: usize, error: usize, certs: Vec<Cert>) -> Self { VHelper { good, unknown, bad, error, certs, keys: Default::default(), passwords: Default::default(), for_decryption: false, error_out: true, packets: Default::default(), } } /// Creates a new decryption helper. pub fn for_decryption(good: usize, unknown: usize, bad: usize, error: usize, certs: Vec<Cert>, keys: Vec<Cert>, passwords: Vec<Password>) -> Self { VHelper { good, unknown, bad, error, certs, keys, passwords, for_decryption: true, error_out: true, packets: Default::default(), } } /// Compares the stats. pub fn assert_stats_eq(&self, other: &Self) { assert_eq!(self.good, other.good); assert_eq!(self.unknown, other.unknown); assert_eq!(self.bad, other.bad); assert_eq!(self.error, other.error); } } impl VerificationHelper for VHelper { fn inspect(&mut self, pp: &PacketParser<'_>) -> Result<()> { self.packets.push(pp.packet.clone()); Ok(()) } fn get_certs(&mut self, _ids: &[crate::KeyHandle]) -> Result<Vec<Cert>> { Ok(self.certs.clone()) } fn check(&mut self, structure: MessageStructure) -> Result<()> { use self::VerificationError::*; for layer in structure.iter() { match layer { MessageLayer::SignatureGroup { ref results } => for result in results { match result { Ok(_) => self.good += 1, Err(MissingKey { .. }) => self.unknown += 1, Err(UnboundKey { .. }) => self.unknown += 1, Err(MalformedSignature { .. }) => self.bad += 1, Err(UnknownSignature { .. }) => self.bad += 1, Err(BadKey { .. }) => self.bad += 1, Err(BadSignature { error, .. }) => { eprintln!("error: {}", error); self.bad += 1; }, } } MessageLayer::Compression { .. } => (), MessageLayer::Encryption { .. } => (), } } if ! self.error_out || (self.good > 0 && self.bad == 0) || (self.for_decryption && self.certs.is_empty()) { Ok(()) } else { Err(anyhow::anyhow!("Verification failed: {:?}", self)) } } } impl DecryptionHelper for VHelper { fn decrypt(&mut self, pkesks: &[PKESK], skesks: &[SKESK], sym_algo: Option<SymmetricAlgorithm>, decrypt: &mut dyn FnMut(Option<SymmetricAlgorithm>, &SessionKey) -> bool) -> Result<Option<Cert>> { tracer!(TRACE, "VHelper::decrypt", TRACE_INDENT); let p = P::new(); if ! self.for_decryption { unreachable!("Shouldn't be called for verifications"); } t!("Trying SKESKS: {:?}", skesks); for (i, skesk) in skesks.iter().enumerate() { for p in &self.passwords { let r = skesk.decrypt(p); t!("decrypting SKESK {}: {:?}", i, r); if let Ok((algo, sk)) = r { if decrypt(algo, &sk) { t!("successfully decrypted encryption container"); return Ok(None); } } } } t!("Trying PKESKS: {:?}", pkesks); for pkesk in pkesks.iter().filter(|p| p.recipient().is_some()) { for key in &self.keys { for subkey in key.with_policy(&p, None)?.keys().secret() .key_handles(pkesk.recipient()) { t!("Trying to decrypt {:?} with {:?}", pkesk, subkey); if let Some((algo, sk)) = subkey.key().clone().into_keypair().ok() .and_then(|mut k| pkesk.decrypt(&mut k, sym_algo)) { if decrypt(algo, &sk) { t!("successfully decrypted encryption container"); return Ok(None); } } } } } t!("decryption of session key failed"); Err(Error::MissingSessionKey("Decryption failed".into()).into()) } } #[test] fn verifier() -> Result<()> { let p = P::new(); let certs = [ "keys/neal.pgp", "keys/testy-new.pgp", "keys/emmelie-dorothea-dina-samantha-awina-ed25519.pgp", "crypto-refresh/v6-minimal-cert.key", ].iter() .map(|f| Cert::from_bytes(crate::tests::file(f)).unwrap()) .collect::<Vec<_>>(); let tests = &[ // Signed messages. (crate::tests::message("signed-1.gpg").to_vec(), crate::tests::manifesto().to_vec(), true, Some(crate::frozen_time()), VHelper::new(1, 0, 0, 0, certs.clone())), // The same, but with a marker packet. ({ let pp = crate::PacketPile::from_bytes( crate::tests::message("signed-1.gpg"))?; let mut buf = Vec::new(); Packet::Marker(Default::default()).serialize(&mut buf)?; pp.serialize(&mut buf)?; buf }, crate::tests::manifesto().to_vec(), true, Some(crate::frozen_time()), VHelper::new(1, 0, 0, 0, certs.clone())), (crate::tests::message("signed-1-sha256-testy.gpg").to_vec(), crate::tests::manifesto().to_vec(), true, Some(crate::frozen_time()), VHelper::new(0, 1, 0, 0, certs.clone())), (crate::tests::message("signed-1-notarized-by-ed25519.pgp") .to_vec(), crate::tests::manifesto().to_vec(), true, Some(crate::frozen_time()), VHelper::new(2, 0, 0, 0, certs.clone())), // Signed messages using the Cleartext Signature Framework. (crate::tests::message("a-cypherpunks-manifesto.txt.cleartext.sig") .to_vec(), { // The test vector, created by GnuPG, does not preserve // the final newline. // // The transformation process trims trailing whitespace, // and the manifesto has a trailing whitespace right at // the end. let mut manifesto = crate::tests::manifesto().to_vec(); assert_eq!(manifesto.pop(), Some(b'\n')); assert_eq!(manifesto.pop(), Some(b' ')); manifesto }, false, None, VHelper::new(1, 0, 0, 0, certs.clone())), (crate::tests::message("a-problematic-poem.txt.cleartext.sig") .to_vec(), { // The test vector, created by GnuPG, does not preserve // the final newline. let mut reference = crate::tests::message("a-problematic-poem.txt").to_vec(); assert_eq!(reference.pop(), Some(b'\n')); reference }, false, None, VHelper::new(1, 0, 0, 0, certs.clone())), (crate::tests::file("crypto-refresh/cleartext-signed-message.txt") .to_vec(), crate::tests::file("crypto-refresh/cleartext-signed-message.txt.plain") .to_vec(), false, None, VHelper::new(1, 0, 0, 0, certs.clone())), // A key as example of an invalid message. (crate::tests::key("neal.pgp").to_vec(), crate::tests::manifesto().to_vec(), true, Some(crate::frozen_time()), VHelper::new(0, 0, 0, 1, certs.clone())), // A signed message where the signature type is text and a // crlf straddles two chunks. (crate::tests::message("crlf-straddles-chunks.txt.sig").to_vec(), crate::tests::message("crlf-straddles-chunks.txt").to_vec(), false, None, VHelper::new(1, 0, 0, 0, certs.clone())), // Like crlf-straddles-chunks, but the signature includes a // notation with a '\n'. Make sure it is not converted to // a '\r\n'. (crate::tests::message("text-signature-notation-has-lf.txt.sig").to_vec(), crate::tests::message("text-signature-notation-has-lf.txt").to_vec(), false, None, VHelper::new(1, 0, 0, 0, certs.clone())), ]; for (i, (signed, reference, test_decryptor, time, r)) in tests.iter().enumerate() { eprintln!("{}...", i); // Test Verifier. let h = VHelper::new(0, 0, 0, 0, certs.clone()); let mut v = match VerifierBuilder::from_bytes(&signed)? .with_policy(&p, *time, h) { Ok(v) => v, Err(e) => if r.error > 0 || r.unknown > 0 { // Expected error. No point in trying to read // something. continue; } else { panic!("{}: {}", i, e); }, }; assert!(v.message_processed()); r.assert_stats_eq(v.helper_ref()); if v.helper_ref().error > 0 { // Expected error. No point in trying to read // something. continue; } let mut content = Vec::new(); v.read_to_end(&mut content).unwrap(); assert_eq!(reference.len(), content.len()); assert_eq!(&reference[..], &content[..]); if ! test_decryptor { continue; } // Test Decryptor. let h = VHelper::new(0, 0, 0, 0, certs.clone()); let mut v = match DecryptorBuilder::from_bytes(&signed)? .with_policy(&p, *time, h) { Ok(v) => v, Err(e) => if r.error > 0 || r.unknown > 0 { // Expected error. No point in trying to read // something. continue; } else { panic!("{}: {}", i, e); }, }; assert!(v.message_processed()); r.assert_stats_eq(v.helper_ref()); if v.helper_ref().error > 0 { // Expected error. No point in trying to read // something. continue; } let mut content = Vec::new(); v.read_to_end(&mut content).unwrap(); assert_eq!(reference.len(), content.len()); assert_eq!(&reference[..], &content[..]); } Ok(()) } #[test] fn decryptor() -> Result<()> { let p = P::new(); for alg in &[ "rsa", "elg", "cv25519", "cv25519.unclamped", "nistp256", "nistp384", "nistp521", "brainpoolP256r1", "brainpoolP384r1", "brainpoolP512r1", "secp256k1", "x448", ] { eprintln!("Test vector {:?}...", alg); let key = Cert::from_bytes(crate::tests::message( &format!("encrypted/{}.sec.pgp", alg)))?; if ! key.primary_key().key().pk_algo().is_supported() { eprintln!("Skipping {} because we don't support {}", alg, key.primary_key().key().pk_algo()); continue; } if let Some(k) = key.with_policy(&p, None)?.keys().subkeys().supported().next() { use crate::crypto::mpi::PublicKey; match k.key().mpis() { PublicKey::ECDH { curve, .. } if ! curve.is_supported() => { eprintln!("Skipping {} because we don't support \ the curve {}", alg, curve); continue; }, _ => (), } } else { eprintln!("Skipping {} because we don't support the algorithm", alg); continue; } let h = VHelper::for_decryption(0, 0, 0, 0, Vec::new(), vec![key], Vec::new()); let mut d = DecryptorBuilder::from_bytes( crate::tests::message(&format!("encrypted/{}.msg.pgp", alg)))? .with_policy(&p, None, h)?; assert!(d.message_processed()); if d.helper_ref().error > 0 { // Expected error. No point in trying to read // something. continue; } let mut content = Vec::new(); d.read_to_end(&mut content).unwrap(); if content[0] == b'H' { assert_eq!(&b"Hello World!\n"[..], &content[..]); } else { assert_eq!("дружба", &String::from_utf8_lossy(&content)); } eprintln!("decrypted {:?} using {}", String::from_utf8(content).unwrap(), alg); } Ok(()) } /// Tests legacy two-pass signature scheme, corner cases. /// /// XXX: This test needs to be adapted once /// https://gitlab.com/sequoia-pgp/sequoia/-/issues/128 is /// implemented. #[test] fn verifier_legacy() -> Result<()> { let packets = crate::PacketPile::from_bytes( crate::tests::message("signed-1.gpg") )? .into_children() .collect::<Vec<_>>(); fn check(msg: &str, buf: &[u8], expect_good: usize) -> Result<()> { eprintln!("{}...", msg); let p = P::new(); let certs = [ "neal.pgp", ] .iter() .map(|f| Cert::from_bytes(crate::tests::key(f)).unwrap()) .collect::<Vec<_>>(); let mut h = VHelper::new(0, 0, 0, 0, certs.clone()); h.error_out = false; let mut v = VerifierBuilder::from_bytes(buf)? .with_policy(&p, crate::frozen_time(), h)?; assert!(v.message_processed()); assert_eq!(v.helper_ref().good, expect_good); let mut content = Vec::new(); v.read_to_end(&mut content).unwrap(); let reference = crate::tests::manifesto(); assert_eq!(reference.len(), content.len()); assert_eq!(reference, &content[..]); Ok(()) } // Bare legacy signed message: SIG Literal let mut o = Vec::new(); packets[2].serialize(&mut o)?; packets[1].serialize(&mut o)?; check("bare", &o, 0 /* XXX: should be 1 once #128 is implemented. */)?; // Legacy signed message, two signatures: SIG SIG Literal let mut o = Vec::new(); packets[2].serialize(&mut o)?; packets[2].serialize(&mut o)?; packets[1].serialize(&mut o)?; check("double", &o, 0 /* XXX: should be 2 once #128 is implemented. */)?; // Weird legacy signed message: OPS SIG Literal SIG let mut o = Vec::new(); packets[0].serialize(&mut o)?; packets[2].serialize(&mut o)?; packets[1].serialize(&mut o)?; packets[2].serialize(&mut o)?; check("weird", &o, 0 /* XXX: should be 2 once #128 is implemented. */)?; // Fubar legacy signed message: SIG OPS Literal SIG let mut o = Vec::new(); packets[2].serialize(&mut o)?; packets[0].serialize(&mut o)?; packets[1].serialize(&mut o)?; packets[2].serialize(&mut o)?; check("fubar", &o, 1 /* XXX: should be 2 once #128 is implemented. */)?; Ok(()) } /// Tests the order of signatures given to /// VerificationHelper::check(). #[test] fn verifier_levels() -> Result<()> { let p = P::new(); struct VHelper(()); impl VerificationHelper for VHelper { fn get_certs(&mut self, _ids: &[crate::KeyHandle]) -> Result<Vec<Cert>> { Ok(Vec::new()) } fn check(&mut self, structure: MessageStructure) -> Result<()> { assert_eq!(structure.iter().count(), 2); for (i, layer) in structure.into_iter().enumerate() { match layer { MessageLayer::SignatureGroup { results } => { assert_eq!(results.len(), 1); if let Err(VerificationError::MissingKey { sig, .. }) = &results[0] { assert_eq!( &sig.issuer_fingerprints().next().unwrap() .to_hex(), match i { 0 => "8E8C33FA4626337976D97978069C0C348DD82C19", 1 => "C03FA6411B03AE12576461187223B56678E02528", _ => unreachable!(), } ); } else { unreachable!() } }, _ => unreachable!(), } } Ok(()) } } impl DecryptionHelper for VHelper { fn decrypt(&mut self, _: &[PKESK], _: &[SKESK], _: Option<SymmetricAlgorithm>, _: &mut dyn FnMut(Option<SymmetricAlgorithm>, &SessionKey) -> bool) -> Result<Option<Cert>> { unreachable!(); } } // Test verifier. let v = VerifierBuilder::from_bytes( crate::tests::message("signed-1-notarized-by-ed25519.pgp"))? .with_policy(&p, crate::frozen_time(), VHelper(()))?; assert!(v.message_processed()); // Test decryptor. let v = DecryptorBuilder::from_bytes( crate::tests::message("signed-1-notarized-by-ed25519.pgp"))? .with_policy(&p, crate::frozen_time(), VHelper(()))?; assert!(v.message_processed()); Ok(()) } #[test] fn detached_verifier() -> Result<()> { fn zeros() -> &'static [u8] { use std::sync::OnceLock; static ZEROS: OnceLock<Vec<u8>> = OnceLock::new(); ZEROS.get_or_init(|| vec![0; 100 * 1024 * 1024]) } let p = P::new(); struct Test<'a> { sig: Vec<u8>, content: &'a [u8], reference: time::SystemTime, } let tests = [ Test { sig: crate::tests::message( "a-cypherpunks-manifesto.txt.ed25519.sig").to_vec(), content: crate::tests::manifesto(), reference: crate::frozen_time(), }, // The same, but with a marker packet. Test { sig: { let sig = crate::PacketPile::from_bytes( crate::tests::message( "a-cypherpunks-manifesto.txt.ed25519.sig"))?; let mut buf = Vec::new(); Packet::Marker(Default::default()).serialize(&mut buf)?; sig.serialize(&mut buf)?; buf }, content: crate::tests::manifesto(), reference: crate::frozen_time(), }, Test { sig: crate::tests::message( "emmelie-dorothea-dina-samantha-awina-detached-signature-of-100MB-of-zeros.sig") .to_vec(), content: zeros(), reference: crate::types::Timestamp::try_from(1572602018).unwrap().into(), }, ]; let certs = [ "emmelie-dorothea-dina-samantha-awina-ed25519.pgp" ].iter() .map(|f| Cert::from_bytes(crate::tests::key(f)).unwrap()) .collect::<Vec<_>>(); for test in tests.iter() { let sig = &test.sig; let content = test.content; let reference = test.reference; let h = VHelper::new(0, 0, 0, 0, certs.clone()); let mut v = DetachedVerifierBuilder::from_bytes(sig).unwrap() .with_policy(&p, reference, h).unwrap(); v.verify_bytes(content).unwrap(); let h = v.into_helper(); assert_eq!(h.good, 1); assert_eq!(h.bad, 0); } Ok(()) } #[test] fn issue_682() -> Result<()> { let p = P::new(); let sig = crate::tests::message("signature-with-broken-mpis.sig"); let h = VHelper::new(0, 0, 0, 0, vec![]); let mut v = DetachedVerifierBuilder::from_bytes(sig)? .with_policy(&p, None, h)?; assert!(v.verify_bytes(b"").is_err()); let h = v.into_helper(); assert_eq!(h.bad, 1); Ok(()) } #[test] fn verify_long_message() -> Result<()> { use std::io::Write; use crate::serialize::stream::{LiteralWriter, Signer, Message}; let p = &P::new(); let (cert, _) = CertBuilder::new() .set_cipher_suite(CipherSuite::Cv25519) .add_signing_subkey() .generate().unwrap(); // sign 3MiB message let mut buf = vec![]; { let key = cert.keys().with_policy(p, None).for_signing().next().unwrap().key(); let keypair = key.clone().parts_into_secret().unwrap() .into_keypair().unwrap(); let m = Message::new(&mut buf); let signer = Signer::new(m, keypair)?.build().unwrap(); let mut ls = LiteralWriter::new(signer).build().unwrap(); ls.write_all(&mut vec![42u8; 3 * 1024 * 1024]).unwrap(); ls.finalize().unwrap(); } // Test Verifier. let h = VHelper::new(0, 0, 0, 0, vec![cert.clone()]); let mut v = VerifierBuilder::from_bytes(&buf)? .buffer_size(2 * 2usize.pow(20)) .with_policy(p, None, h)?; assert!(!v.message_processed()); assert!(v.helper_ref().good == 0); assert!(v.helper_ref().bad == 0); assert!(v.helper_ref().unknown == 0); assert!(v.helper_ref().error == 0); let mut message = Vec::new(); v.read_to_end(&mut message).unwrap(); assert!(v.message_processed()); assert_eq!(3 * 1024 * 1024, message.len()); assert!(message.iter().all(|&b| b == 42)); assert!(v.helper_ref().good == 1); assert!(v.helper_ref().bad == 0); assert!(v.helper_ref().unknown == 0); assert!(v.helper_ref().error == 0); // Try the same, but this time we let .check() fail. let h = VHelper::new(0, 0, /* makes check() fail: */ 1, 0, vec![cert.clone()]); let mut v = VerifierBuilder::from_bytes(&buf)? .buffer_size(2 * 2usize.pow(20)) .with_policy(p, None, h)?; assert!(!v.message_processed()); assert!(v.helper_ref().good == 0); assert!(v.helper_ref().bad == 1); assert!(v.helper_ref().unknown == 0); assert!(v.helper_ref().error == 0); let mut message = Vec::new(); let r = v.read_to_end(&mut message); assert!(r.is_err()); // Check that we only got a truncated message. assert!(v.message_processed()); assert!(!message.is_empty()); assert!(message.len() <= 1 * 1024 * 1024); assert!(message.iter().all(|&b| b == 42)); assert!(v.helper_ref().good == 1); assert!(v.helper_ref().bad == 1); assert!(v.helper_ref().unknown == 0); assert!(v.helper_ref().error == 0); // Test Decryptor. let h = VHelper::new(0, 0, 0, 0, vec![cert.clone()]); let mut v = DecryptorBuilder::from_bytes(&buf)? .buffer_size(2 * 2usize.pow(20)) .with_policy(p, None, h)?; assert!(!v.message_processed()); assert!(v.helper_ref().good == 0); assert!(v.helper_ref().bad == 0); assert!(v.helper_ref().unknown == 0); assert!(v.helper_ref().error == 0); let mut message = Vec::new(); v.read_to_end(&mut message).unwrap(); assert!(v.message_processed()); assert_eq!(3 * 1024 * 1024, message.len()); assert!(message.iter().all(|&b| b == 42)); assert!(v.helper_ref().good == 1); assert!(v.helper_ref().bad == 0); assert!(v.helper_ref().unknown == 0); assert!(v.helper_ref().error == 0); // Try the same, but this time we let .check() fail. let h = VHelper::new(0, 0, /* makes check() fail: */ 1, 0, vec![cert.clone()]); let mut v = DecryptorBuilder::from_bytes(&buf)? .buffer_size(2 * 2usize.pow(20)) .with_policy(p, None, h)?; assert!(!v.message_processed()); assert!(v.helper_ref().good == 0); assert!(v.helper_ref().bad == 1); assert!(v.helper_ref().unknown == 0); assert!(v.helper_ref().error == 0); let mut message = Vec::new(); let r = v.read_to_end(&mut message); assert!(r.is_err()); // Check that we only got a truncated message. assert!(v.message_processed()); assert!(!message.is_empty()); assert!(message.len() <= 1 * 1024 * 1024); assert!(message.iter().all(|&b| b == 42)); assert!(v.helper_ref().good == 1); assert!(v.helper_ref().bad == 1); assert!(v.helper_ref().unknown == 0); assert!(v.helper_ref().error == 0); Ok(()) } /// Checks that tampering with the MDC yields a uniform error /// response. #[test] fn issue_693() -> Result<()> { struct H(); impl VerificationHelper for H { fn get_certs(&mut self, _ids: &[crate::KeyHandle]) -> Result<Vec<Cert>> { Ok(Vec::new()) } fn check(&mut self, _: MessageStructure) -> Result<()> { Ok(()) } } impl DecryptionHelper for H { fn decrypt(&mut self, _: &[PKESK], s: &[SKESK], _: Option<SymmetricAlgorithm>, decrypt: &mut dyn FnMut(Option<SymmetricAlgorithm>, &SessionKey) -> bool) -> Result<Option<Cert>> { let (algo, sk) = s[0].decrypt(&"123".into()).unwrap(); let r = decrypt(algo, &sk); assert!(r); Ok(None) } } fn check(m: &str) -> Result<()> { let doit = || -> Result<()> { let p = &P::new(); let mut decryptor = DecryptorBuilder::from_bytes(m.as_bytes())? .with_policy(p, None, H())?; let mut b = Vec::new(); decryptor.read_to_end(&mut b)?; Ok(()) }; let e = doit().unwrap_err(); match e.downcast::<io::Error>() { Ok(e) => assert_eq!(e.into_inner().unwrap().downcast().unwrap(), Box::new(Error::ManipulatedMessage)), Err(e) => assert_eq!(e.downcast::<Error>().unwrap(), Error::ManipulatedMessage), }; Ok(()) } // Bad hash. check("-----BEGIN PGP MESSAGE----- wx4EBwMI7dKRUiOYGCUAWmzhiYGS8Pn/16QkyTous6vSOgFMcilte26C7kej rKhvjj6uYNT+mt+L2Yg/FHFvpgVF3KfP0fb+9jZwgt4qpDkTMY7AWPTK6wXX Jo8= =LS8u -----END PGP MESSAGE----- ")?; // Bad header. check("-----BEGIN PGP MESSAGE----- wx4EBwMI7sPTdlgQwd8AogIcbF/hLVrYbvVbgj4EC6/SOgGNaCyffrR4Fuwl Ft2w56/hB/gTaGEhCgDGXg8NiFGIURqF3eIwxxdKWghUutYmsGwqOZmdJ49a 9gE= =DzKF -----END PGP MESSAGE----- ")?; // Bad header matching other packet type. check("-----BEGIN PGP MESSAGE----- wx4EBwMIhpEGBh3v0oMAYgGcj+4CG1mcWQwmyGIDRdvSOgFSHlL2GZ1ZKeXS 29kScqGg2U8N6ZF9vmj/9Sn7CFtO5PGXn2owQVsopeUSTofV3BNUBpxaBDCO EK8= =TgeJ -----END PGP MESSAGE----- ")?; Ok(()) } /// Tests samples of messages signed with the cleartext signature /// framework. #[test] fn csf_verification() -> Result<()> { struct H(Vec<Cert>, bool); impl VerificationHelper for H { fn get_certs(&mut self, _ids: &[crate::KeyHandle]) -> Result<Vec<Cert>> { Ok(std::mem::take(&mut self.0)) } fn check(&mut self, m: MessageStructure) -> Result<()> { for (i, layer) in m.into_iter().enumerate() { assert_eq!(i, 0); if let MessageLayer::SignatureGroup { results } = layer { assert!(! results.is_empty()); for result in results { result.unwrap(); } self.1 = true; } else { panic!(); } } Ok(()) } } for (m, c) in [ ("InRelease", "InRelease.signers.pgp"), ("InRelease.msft", "InRelease.msft.signers.pgp"), ] { let certs = crate::cert::CertParser::from_bytes( crate::tests::key(c))?.collect::<Result<Vec<_>>>()?; // The Microsoft cert uses SHA-1. let p = unsafe { &NP::new() }; eprintln!("Parsing {}...", m); let mut verifier = VerifierBuilder::from_bytes( crate::tests::message(m))? .with_policy(p, None, H(certs, false))?; let mut b = Vec::new(); verifier.read_to_end(&mut b)?; let h = verifier.into_helper(); assert!(h.1); } Ok(()) } /// Tests whether messages using the cleartext signature framework /// with multiple signatures and signers are correctly handled. #[test] fn csf_multiple_signers() -> Result<()> { struct H(bool); impl VerificationHelper for H { fn get_certs(&mut self, _ids: &[crate::KeyHandle]) -> Result<Vec<Cert>> { crate::cert::CertParser::from_bytes( crate::tests::key("InRelease.signers.pgp"))? .collect() } fn check(&mut self, m: MessageStructure) -> Result<()> { for (i, layer) in m.into_iter().enumerate() { assert_eq!(i, 0); if let MessageLayer::SignatureGroup { results } = layer { assert_eq!(results.len(), 3); for result in results { assert!(result.is_ok()); } self.0 = true; } else { panic!(); } } Ok(()) } } let p = &P::new(); let mut verifier = VerifierBuilder::from_bytes( crate::tests::message("InRelease"))? .with_policy(p, None, H(false))?; let mut b = Vec::new(); verifier.read_to_end(&mut b)?; let h = verifier.into_helper(); assert!(h.0); Ok(()) } /// This sample from our test suite generated using GnuPG. #[test] fn v4skesk_v1seip_aes128() -> Result<()> { test_password_encrypted_message( SymmetricAlgorithm::AES128, "messages/encrypted-aes128-password-123456789.gpg", "123456789", crate::tests::manifesto()) } /// This sample from our test suite generated using GnuPG. #[test] fn v4skesk_v1seip_aes192() -> Result<()> { test_password_encrypted_message( SymmetricAlgorithm::AES192, "messages/encrypted-aes192-password-123456.gpg", "123456", crate::tests::manifesto()) } /// This sample from our test suite generated using GnuPG. #[test] fn v4skesk_v1seip_aes256() -> Result<()> { test_password_encrypted_message( SymmetricAlgorithm::AES256, "messages/encrypted-aes256-password-123.gpg", "123", crate::tests::manifesto()) } fn test_password_encrypted_message(cipher: SymmetricAlgorithm, name: &str, password: &str, plaintext: &[u8]) -> Result<()> { if ! cipher.is_supported() { eprintln!("Skipping test vector {:?}...", name); return Ok(()); } eprintln!("Test vector {:?}...", name); let p = &P::new(); let password: Password = String::from(password).into(); let h = VHelper::for_decryption(0, 0, 0, 0, vec![], vec![], vec![password]); let mut d = DecryptorBuilder::from_bytes(crate::tests::file(name))? .with_policy(p, None, h)?; assert!(d.message_processed()); let mut content = Vec::new(); d.read_to_end(&mut content).unwrap(); assert_eq!(&content, plaintext); Ok(()) } /// Checks for a crash with signatures that are unaccounted for. #[test] fn unaccounted_signatures() -> Result<()> { let p = P::new(); let m = b"-----BEGIN PGP MESSAGE----- wgoEAAAAAAB6CkAAxADLBq8AAKurq8IKBCC/CAAAAAD0sA== =KRn6 -----END PGP MESSAGE----- "; let mut h = VHelper::new(0, 0, 0, 0, vec![ Cert::from_bytes(crate::tests::key("testy.pgp"))?, ]); h.error_out = false; VerifierBuilder::from_bytes(m)? .with_policy(&p, None, h) .unwrap(); Ok(()) } /// Checks for a crash related to HashedReader's HashingMode. #[test] fn csf_hashing_mode_assertion_failure() -> Result<()> { let p = P::new(); let m = b"-----BEGIN PGP SIGNED MESSAGE----- ---BEGIN PGP SIGNATURE 0iHUEARYIAB0QCyUHMcArrZbte9msAndEO9clJG5wpCAEA2/"; let mut h = VHelper::new(0, 0, 0, 0, vec![ Cert::from_bytes(crate::tests::key("testy.pgp"))?, ]); h.error_out = false; let _ = VerifierBuilder::from_bytes(m)? .with_policy(&p, None, h); Ok(()) } /// Checks for a crash related to HashedReader's assumptions about /// the number of signature groups. #[test] fn csf_sig_group_count_assertion_failure() -> Result<()> { let p = P::new(); let m = b"-----BEGIN PGP SIGNED MESSAGE----- -----BEGIN PGP SIGNATURE----- xHUDBRY0WIQ+50WENDPP"; let mut h = VHelper::new(0, 0, 0, 0, vec![ Cert::from_bytes(crate::tests::key("testy.pgp"))?, ]); h.error_out = false; let _ = VerifierBuilder::from_bytes(m)? .with_policy(&p, None, h); Ok(()) } /// Tests that the message structure is checked at the end of /// parsing the packet stream. #[test] fn message_grammar_check() -> Result<()> { let p = P::new(); let certs = vec![Cert::from_bytes(crate::tests::key("neal.pgp"))?]; let helper = VHelper::new(1, 0, 0, 0, certs.clone()); let pp = crate::PacketPile::from_bytes( crate::tests::message("signed-1-notarized-by-ed25519.pgp"))?; let mut buf = Vec::new(); assert_eq!(pp.children().count(), 5); // Drop the last signature packet! Now the OPS and Signature // packets no longer bracket. pp.children().take(4).for_each(|p| p.serialize(&mut buf).unwrap()); // Test verifier. let do_it = || -> Result<()> { let v = VerifierBuilder::from_bytes(&buf)? .with_policy(&p, crate::frozen_time(), helper.clone())?; assert!(v.message_processed()); Ok(()) }; assert!(do_it().is_err()); // Test decryptor. let do_it = || -> Result<()> { let v = DecryptorBuilder::from_bytes(&buf)? .with_policy(&p, crate::frozen_time(), helper)?; assert!(v.message_processed()); Ok(()) }; assert!(do_it().is_err()); Ok(()) } /// Tests that an inline-signed message using two different hash /// algorithms verifies correctly. #[test] fn inline_signed_two_hashes() -> Result<()> { use crate::{ types::{DataFormat, HashAlgorithm, SignatureType}, packet::Literal, parse::SignatureBuilder, }; let p = P::new(); let cert = Cert::from_bytes(crate::tests::key("testy-private.pgp"))?; let helper = VHelper::new(0, 0, 0, 0, vec![cert.clone()]); let mut signer = cert.primary_key().key().clone().parts_into_secret()? .into_keypair()?; let msg = b"Hello, world!"; let sig0 = SignatureBuilder::new(SignatureType::Binary) .set_signature_creation_time(crate::frozen_time())? .set_hash_algo(HashAlgorithm::SHA256) .sign_message(&mut signer, msg)?; let sig1 = SignatureBuilder::new(SignatureType::Binary) .set_signature_creation_time(crate::frozen_time())? .set_hash_algo(HashAlgorithm::SHA512) .sign_message(&mut signer, msg)?; let packets: Vec<Packet> = vec![ OnePassSig::try_from(&sig0)?.into(), { let mut ops = OnePassSig::try_from(&sig1)?; ops.set_last(true); ops.into() }, { let mut lit = Literal::new(DataFormat::Binary); lit.set_body((*msg).into()); lit.into() }, sig1.into(), sig0.into(), ]; let mut buf = Vec::new(); packets.iter().for_each(|p| p.serialize(&mut buf).unwrap()); let v = VerifierBuilder::from_bytes(&buf)? .with_policy(&p, crate::frozen_time(), helper)?; assert!(v.message_processed()); assert_eq!(v.helper_ref().good, 2); Ok(()) } /// This sample packet is from RFC9580. #[test] fn v6skesk_v2seip_aes128_ocb() -> Result<()> { sample_skesk6_packet( SymmetricAlgorithm::AES128, AEADAlgorithm::OCB, "password", "crypto-refresh/v6skesk-aes128-ocb.pgp", b"Hello, world!") } /// This sample packet is from RFC9580. #[test] fn v6skesk_v2seip_aes128_eax() -> Result<()> { sample_skesk6_packet( SymmetricAlgorithm::AES128, AEADAlgorithm::EAX, "password", "crypto-refresh/v6skesk-aes128-eax.pgp", b"Hello, world!") } /// This sample packet is from RFC9580. #[test] fn v6skesk_v2seip_aes128_gcm() -> Result<()> { sample_skesk6_packet( SymmetricAlgorithm::AES128, AEADAlgorithm::GCM, "password", "crypto-refresh/v6skesk-aes128-gcm.pgp", b"Hello, world!") } fn sample_skesk6_packet(cipher: SymmetricAlgorithm, aead: AEADAlgorithm, password: &str, name: &str, plaintext: &[u8]) -> Result<()> { if ! (aead.is_supported() && aead.supports_symmetric_algo(&cipher)) { eprintln!("Skipping test vector {:?}...", name); return Ok(()); } eprintln!("Test vector {:?}...", name); let p = &P::new(); let password: Password = String::from(password).into(); let h = VHelper::for_decryption(0, 0, 0, 0, vec![], vec![], vec![password]); let mut d = DecryptorBuilder::from_bytes(crate::tests::file(name))? .with_policy(p, None, h)?; assert!(d.message_processed()); let mut content = Vec::new(); d.read_to_end(&mut content).unwrap(); assert_eq!(&content, plaintext); Ok(()) } } ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/src/parse.rs������������������������������������������������������������������0000644�0000000�0000000�00001027633�10461020230�0015222�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������//! Packet parsing infrastructure. //! //! OpenPGP defines a binary representation suitable for storing and //! communicating OpenPGP data structures (see [Section 3 ff. of RFC //! 9580]). Parsing is the process of interpreting the binary //! representation. //! //! [Section 3 ff. of RFC 9580]: https://www.rfc-editor.org/rfc/rfc9580.html#section-3 //! //! An OpenPGP stream represents a sequence of packets. Some of the //! packets contain other packets. These so-called containers include //! encrypted data packets (the SED and [SEIP] packets), and //! [compressed data] packets. This structure results in a tree, //! which is laid out in depth-first order. //! //! [SEIP]: crate::packet::SEIP //! [compressed data]: crate::packet::CompressedData //! //! OpenPGP defines objects consisting of several packets with a //! specific structure. These objects are [`Message`]s, [`Cert`]s and //! sequences of [`Cert`]s ("keyrings"). Verifying the structure of //! these objects is also an act of parsing. //! //! [`Message`]: super::Message //! [`Cert`]: crate::cert::Cert //! //! This crate provides several interfaces to parse OpenPGP data. //! They fall in roughly three categories: //! //! - First, most data structures in this crate implement the //! [`Parse`] trait. It provides a uniform interface to parse data //! from an [`io::Read`]er, a file identified by its [`Path`], or //! simply a byte slice. //! //! - Second, there is a convenient interface to decrypt and/or //! verify OpenPGP messages in a streaming fashion. Encrypted //! and/or signed data is read using the [`Parse`] interface, and //! decrypted and/or verified data can be read using [`io::Read`]. //! //! - Finally, we expose the low-level [`PacketParser`], allowing //! fine-grained control over the parsing. //! //! [`io::Read`]: std::io::Read //! [`Path`]: std::path::Path //! //! The choice of interface depends on the specific use case. In many //! circumstances, OpenPGP data can not be trusted until it has been //! authenticated. Therefore, it has to be treated as attacker //! controlled data, and it has to be treated with great care. See //! the section [Security Considerations] below. //! //! [Security Considerations]: #security-considerations //! //! # Common Operations //! //! - *Decrypt a message*: Use a [streaming `Decryptor`]. //! - *Verify a message*: Use a [streaming `Verifier`]. //! - *Verify a detached signature*: Use a [`DetachedVerifier`]. //! - *Parse a [`Cert`]*: Use [`Cert`]'s [`Parse`] interface. //! - *Parse a keyring*: Use [`CertParser`]'s [`Parse`] interface. //! - *Parse an unstructured sequence of small packets from a trusted //! source*: Use [`PacketPile`]s [`Parse`] interface (e.g. //! [`PacketPile::from_file`]). //! - *Parse an unstructured sequence of packets*: Use the //! [`PacketPileParser`]. //! - *Parse an unstructured sequence of packets with full control //! over the parser*: Use a [`PacketParser`]. //! - *Customize the parser behavior even more*: Use a //! [`PacketParserBuilder`]. //! //! [`CertParser`]: crate::cert::CertParser //! [streaming `Decryptor`]: stream::Decryptor //! [streaming `Verifier`]: stream::Verifier //! [`DetachedVerifier`]: stream::DetachedVerifier //! [`PacketPile`]: crate::PacketPile //! [`PacketPile::from_file`]: super::PacketPile::from_file() //! //! # Data Structures and Interfaces //! //! This crate provides several interfaces for parsing OpenPGP //! streams, ordered from the most convenient but least flexible to //! the least convenient but most flexible: //! //! - The streaming [`Verifier`], [`DetachedVerifier`], and //! [`Decryptor`] are the most convenient way to parse OpenPGP //! messages. //! //! - The [`PacketPile::from_file`] (and related methods) is the //! most convenient, but least flexible way to parse an arbitrary //! sequence of OpenPGP packets. Whereas a [`PacketPileParser`] //! allows the caller to determine how to handle individual //! packets, the [`PacketPile::from_file`] parses the whole stream //! at once and returns a [`PacketPile`]. //! //! - The [`PacketPileParser`] abstraction builds on the //! [`PacketParser`] abstraction and provides a similar interface. //! However, after each iteration, the [`PacketPileParser`] adds the //! packet to a [`PacketPile`], which is returned once the packets are //! completely processed. //! //! This interface should only be used if the caller actually //! wants a [`PacketPile`]; if the OpenPGP stream is parsed in place, //! then using a [`PacketParser`] is better. //! //! This interface should only be used if the caller is certain //! that the parsed stream will fit in memory. //! //! - The [`PacketParser`] abstraction produces one packet at a //! time. What is done with those packets is completely up to the //! caller. //! //! The behavior of the [`PacketParser`] can be configured using a //! [`PacketParserBuilder`]. //! //! [`Decryptor`]: stream::Decryptor //! [`Verifier`]: stream::Verifier //! //! # ASCII armored data //! //! The [`PacketParser`] will by default automatically detect and //! remove any ASCII armor encoding (see [Section 6 of RFC 9580]). //! This automatism can be disabled and fine-tuned using //! [`PacketParserBuilder::dearmor`]. //! //! [Section 6 of RFC 9580]: https://www.rfc-editor.org/rfc/rfc9580.html#section-6 //! [`PacketParserBuilder::dearmor`]: PacketParserBuilder::dearmor() //! //! # Security Considerations //! //! In general, OpenPGP data must be considered attacker controlled //! and thus treated with great care. Even though we use a //! memory-safe language, there are several aspects to be aware of: //! //! - OpenPGP messages may be compressed. Therefore, one cannot //! predict the uncompressed size of a message by looking at the //! compressed representation. Operations that parse OpenPGP //! streams and buffer the packet data (like using the //! [`PacketPile`]'s [`Parse`] interface) are inherently unsafe and //! must only be used on trusted data. //! //! - The authenticity of an OpenPGP message can only be checked once //! it has been fully processed. Therefore, the plaintext must be //! buffered and not be trusted until the whole message is //! processed and signatures and/or ciphertext integrity are //! verified. On the other hand, buffering an unbounded amount of //! data is problematic and can lead to out-of-memory situations //! resulting in denial of service. The streaming message //! processing interfaces address this problem by buffering a //! configurable amount of data before releasing any data to the //! caller, and only revert to streaming unverified data if the //! message exceeds the buffer. See [`DEFAULT_BUFFER_SIZE`] for //! more information. //! //! - Not all parts of signed-then-encrypted OpenPGP messages are //! authenticated. Notably, all packets outside the encryption //! container (any [`PKESK`] and [`SKESK`] packets, as well as the //! encryption container itself), the [`Literal`] packet's headers, //! as well as parts of the [`Signature`] are not covered by the //! signatures. //! //! - Ciphertext integrity is provided by the [version 2 SEIP] //! packet's use of authenticated encryption. //! //! - In messages compatible with [RFC 4880], ciphertext integrity is //! provided by the [`version 1 SEIP`] packet's [`MDC`] mechanism, //! but the integrity can only be checked after decrypting the whole //! container. //! //! [`DEFAULT_BUFFER_SIZE`]: stream::DEFAULT_BUFFER_SIZE //! [`PKESK`]: crate::packet::PKESK //! [`SKESK`]: crate::packet::PKESK //! [`Literal`]: crate::packet::Literal //! [`Signature`]: crate::packet::Signature //! [`version 2 SEIP`]: crate::packet::seip::SEIP2 //! [RFC 4880]: https://datatracker.ietf.org/doc/html/rfc4880 //! [`version 1 SEIP`]: crate::packet::seip::SEIP1 //! [`SEIP`]: crate::packet::SEIP //! [`MDC`]: crate::packet::MDC use std::io; use std::io::prelude::*; use std::convert::TryFrom; use std::cmp; use std::str; use std::mem; use std::fmt; use std::path::Path; use std::result::Result as StdResult; use xxhash_rust::xxh3::Xxh3; // Re-export buffered_reader. // // We use this in our API, and re-exporting it here makes it easy to // use the correct version of the crate in downstream code without // having to explicitly depend on it. pub use buffered_reader; use ::buffered_reader::*; use crate::{ cert::CertValidator, cert::CertValidity, cert::KeyringValidator, cert::KeyringValidity, crypto::{aead, hash::Hash}, Result, packet::header::{ CTB, BodyLength, PacketLengthType, }, crypto::S2K, Error, packet::{ Container, Header, }, packet::signature::Signature3, packet::signature::Signature4, packet::signature::Signature6, packet::prelude::*, Packet, Fingerprint, KeyID, crypto::SessionKey, }; use crate::types::{ AEADAlgorithm, CompressionAlgorithm, Features, HashAlgorithm, KeyFlags, KeyServerPreferences, PublicKeyAlgorithm, RevocationKey, SignatureType, SymmetricAlgorithm, Timestamp, }; use crate::crypto::{self, mpi::{PublicKey, MPI, ProtectedMPI}}; use crate::crypto::symmetric::{Decryptor, BufferedReaderDecryptor}; use crate::message; use crate::message::MessageValidator; mod partial_body; use self::partial_body::BufferedReaderPartialBodyFilter; use crate::packet::signature::subpacket::{ NotationData, NotationDataFlags, Subpacket, SubpacketArea, SubpacketLength, SubpacketTag, SubpacketValue, }; use crate::serialize::MarshalInto; mod packet_pile_parser; pub use self::packet_pile_parser::PacketPileParser; mod hashed_reader; pub(crate) use self::hashed_reader::{ HashingMode, HashedReader, }; mod packet_parser_builder; pub use self::packet_parser_builder::{Dearmor, PacketParserBuilder}; use packet_parser_builder::ARMOR_READER_LEVEL; pub mod map; mod mpis; pub mod stream; // Whether to trace execution by default (on stderr). const TRACE : bool = false; // How much junk the packet parser is willing to skip when recovering. // This is an internal implementation detail and hence not exported. pub(crate) const RECOVERY_THRESHOLD: usize = 32 * 1024; /// Parsing of packets and related structures. /// /// This is a uniform interface to parse packets, messages, keys, and /// related data structures. /// /// # Sealed trait /// /// This trait is [sealed] and cannot be implemented for types outside this crate. /// Therefore it can be extended in a non-breaking way. /// If you want to implement the trait inside the crate /// you also need to implement the `seal::Sealed` marker trait. /// /// [sealed]: https://rust-lang.github.io/api-guidelines/future-proofing.html#sealed-traits-protect-against-downstream-implementations-c-sealed pub trait Parse<'a, T>: crate::seal::Sealed { /// Reads from the given buffered reader. /// /// Implementations of this function should be short. Ideally, /// they should hand of the reader to a private function erasing /// the readers type by invoking [`BufferedReader::into_boxed`]. fn from_buffered_reader<R>(reader: R) -> Result<T> where R: BufferedReader<Cookie> + 'a; /// Reads from the given reader. /// /// The default implementation just uses /// [`Parse::from_buffered_reader`], but implementations can /// provide their own specialized version. fn from_reader<R: 'a + Read + Send + Sync>(reader: R) -> Result<T> { Self::from_buffered_reader( buffered_reader::Generic::with_cookie(reader, None, Default::default()) .into_boxed()) } /// Reads from the given file. /// /// The default implementation just uses /// [`Parse::from_buffered_reader`], but implementations can /// provide their own specialized version. fn from_file<P: AsRef<Path>>(path: P) -> Result<T> { Self::from_buffered_reader( buffered_reader::File::with_cookie(path.as_ref(), Default::default())? .into_boxed()) } /// Reads from the given slice. /// /// The default implementation just uses /// [`Parse::from_buffered_reader`], but implementations can /// provide their own specialized version. fn from_bytes<D: AsRef<[u8]> + ?Sized + Send + Sync>(data: &'a D) -> Result<T> { Self::from_buffered_reader( buffered_reader::Memory::with_cookie(data.as_ref(), Default::default()) .into_boxed()) } } // Implement type::from_buffered_reader and the Parse trait in terms // of type::from_buffered_reader for a particular packet type. If the // generic from_buffered_reader implementation is inappropriate, then // it can be overridden. macro_rules! impl_parse_with_buffered_reader { ($typ: ident) => { impl_parse_with_buffered_reader!( $typ, |br: Box<dyn BufferedReader<Cookie>>| -> Result<$typ> { let parser = PacketHeaderParser::new_naked(br); let mut pp = Self::parse(parser)?; pp.buffer_unread_content()?; match pp.next()? { #[allow(deprecated)] (Packet::$typ(o), PacketParserResult::EOF(_)) => Ok(o), (Packet::Unknown(u), PacketParserResult::EOF(_)) => Err(u.into_error()), (p, PacketParserResult::EOF(_)) => Err(Error::InvalidOperation( format!("Not a {} packet: {:?}", stringify!($typ), p)).into()), (_, PacketParserResult::Some(_)) => Err(Error::InvalidOperation( "Excess data after packet".into()).into()), } }); }; // from_buffered_reader should be a closure that takes a // BufferedReader and returns a Result<Self>. ($typ: ident, $from_buffered_reader: expr) => { impl<'a> Parse<'a, $typ> for $typ { fn from_buffered_reader<R>(reader: R) -> Result<Self> where R: BufferedReader<Cookie> + 'a, { Ok($from_buffered_reader(reader.into_boxed())?) } } } } /// The default amount of acceptable nesting. /// /// The default is `16`. /// /// Typically, we expect a message to look like: /// /// ```text /// [ encryption container: [ compression container: [ signature: [ literal data ]]]] /// ``` /// /// So, this should be more than enough. /// /// To change the maximum recursion depth, use /// [`PacketParserBuilder::max_recursion_depth`]. /// /// [`PacketParserBuilder::max_recursion_depth`]: PacketParserBuilder::max_recursion_depth() pub const DEFAULT_MAX_RECURSION_DEPTH : u8 = 16; /// The default maximum size of non-container packets. /// /// The default is `1 MiB`. /// /// Packets that exceed this limit will be returned as /// `Packet::Unknown`, with the error set to `Error::PacketTooLarge`. /// /// This limit applies to any packet type that is *not* a container /// packet, i.e. any packet that is not a literal data packet, a /// compressed data packet, a symmetrically encrypted data packet, or /// an AEAD encrypted data packet. /// /// To change the maximum recursion depth, use /// [`PacketParserBuilder::max_packet_size`]. /// /// [`PacketParserBuilder::max_packet_size`]: PacketParserBuilder::max_packet_size() pub const DEFAULT_MAX_PACKET_SIZE: u32 = 1 << 20; // 1 MiB // Used to parse an OpenPGP packet's header (note: in this case, the // header means a Packet's fixed data, not the OpenPGP framing // information, such as the CTB, and length information). // // This struct is not exposed to the user. Instead, when a header has // been successfully parsed, a `PacketParser` is returned. pub(crate) struct PacketHeaderParser<'a> { // The reader stack wrapped in a buffered_reader::Dup so that if // there is a parse error, we can abort and still return an // Unknown packet. reader: buffered_reader::Dup<Box<dyn BufferedReader<Cookie> + 'a>, Cookie>, // The current packet's header. header: Header, header_bytes: Vec<u8>, // This packet's path. path: Vec<usize>, // The `PacketParser`'s state. state: PacketParserState, /// A map of this packet. map: Option<map::Map>, } /// Creates a local marco called php_try! that returns an Unknown /// packet instead of an Error like try! on parsing-related errors. /// (Errors like read errors are still returned as usual.) /// /// If you want to fail like this in a non-try! context, use /// php.fail("reason"). macro_rules! make_php_try { ($parser:expr) => { macro_rules! php_try { ($e:expr) => { match $e { Ok(b) => { Ok(b) }, Err(e) => { t!("parsing failed at {}:{}: {}", file!(), line!(), e); let e = match e.downcast::<io::Error>() { Ok(e) => if let io::ErrorKind::UnexpectedEof = e.kind() { return $parser.error(e.into()); } else { e.into() }, Err(e) => e, }; let e = match e.downcast::<Error>() { Ok(e) => return $parser.error(e.into()), Err(e) => e, }; Err(e) }, }? }; } }; } impl std::fmt::Debug for PacketHeaderParser<'_> { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { f.debug_struct("PacketHeaderParser") .field("header", &self.header) .field("path", &self.path) .field("reader", &self.reader) .field("state", &self.state) .field("map", &self.map) .finish() } } impl<'a> PacketHeaderParser<'a> { // Returns a `PacketHeaderParser` to parse an OpenPGP packet. // `inner` points to the start of the OpenPGP framing information, // i.e., the CTB. fn new(inner: Box<dyn BufferedReader<Cookie> + 'a>, state: PacketParserState, path: Vec<usize>, header: Header, header_bytes: Vec<u8>) -> Self { assert!(!path.is_empty()); let cookie = Cookie { level: inner.cookie_ref().level, ..Default::default() }; let map = if state.settings.map { Some(map::Map::new(header_bytes.clone())) } else { None }; PacketHeaderParser { reader: buffered_reader::Dup::with_cookie(inner, cookie), header, header_bytes, path, state, map, } } // Returns a `PacketHeaderParser` that parses a bare packet. That // is, `inner` points to the start of the packet; the OpenPGP // framing has already been processed, and `inner` already // includes any required filters (e.g., a // `BufferedReaderPartialBodyFilter`, etc.). fn new_naked(inner: Box<dyn BufferedReader<Cookie> + 'a>) -> Self { PacketHeaderParser::new(inner, PacketParserState::new(Default::default()), vec![ 0 ], Header::new(CTB::new(Tag::Reserved), BodyLength::Full(0)), Vec::new()) } // Consumes the bytes belonging to the packet's header (i.e., the // number of bytes read) from the reader, and returns a // `PacketParser` that can be returned to the user. // // Only call this function if the packet's header has been // completely and correctly parsed. If a failure occurs while // parsing the header, use `fail()` instead. fn ok(mut self, packet: Packet) -> Result<PacketParser<'a>> { tracer!(TRACE, "PacketHeaderParser::ok", self.reader.cookie_ref().level.unwrap_or(0)); let total_out = self.reader.total_out(); t!("total_out = {}", total_out); if self.state.settings.map { // Steal the body for the map. self.reader.rewind(); let body = if self.state.settings.buffer_unread_content { self.reader.steal_eof()? } else { self.reader.steal(total_out)? }; t!("got {} bytes of body for the map", body.len()); if body.len() > total_out { self.field("body", body.len() - total_out); } self.map.as_mut().unwrap().finalize(body); } // This is a buffered_reader::Dup, so this always has an // inner. let mut reader = Box::new(self.reader).into_inner().unwrap(); if total_out > 0 { // We know the data has been read, so this cannot fail. reader.data_consume_hard(total_out).unwrap(); } Ok(PacketParser { header: self.header, packet, path: self.path, last_path: vec![], reader, content_was_read: false, processed: true, finished: false, map: self.map, body_hash: Some(Container::make_body_hash()), state: self.state, }) } // Something went wrong while parsing the packet's header. Aborts // and returns an Unknown packet instead. fn fail(self, reason: &'static str) -> Result<PacketParser<'a>> { self.error(Error::MalformedPacket(reason.into()).into()) } fn error(mut self, error: anyhow::Error) -> Result<PacketParser<'a>> { // Rewind the dup reader, so that the caller has a chance to // buffer the whole body of the unknown packet. self.reader.rewind(); Unknown::parse(self, error) } fn field(&mut self, name: &'static str, size: usize) { if let Some(ref mut map) = self.map { map.add(name, size) } } fn parse_u8(&mut self, name: &'static str) -> Result<u8> { let r = self.reader.data_consume_hard(1)?[0]; self.field(name, 1); Ok(r) } fn parse_u8_len(&mut self, name: &'static str) -> Result<usize> { self.parse_u8(name).map(Into::into) } fn parse_be_u16(&mut self, name: &'static str) -> Result<u16> { let r = self.reader.read_be_u16()?; self.field(name, 2); Ok(r) } fn parse_be_u32(&mut self, name: &'static str) -> Result<u32> { let r = self.reader.read_be_u32()?; self.field(name, 4); Ok(r) } fn parse_bool(&mut self, name: &'static str) -> Result<bool> { let v = self.reader.data_consume_hard(1)?[0]; self.field(name, 1); match v { 0 => Ok(false), 1 => Ok(true), n => Err(Error::MalformedPacket( format!("Invalid value for bool: {}", n)).into()), } } fn parse_bytes(&mut self, name: &'static str, amount: usize) -> Result<Vec<u8>> { let r = self.reader.steal(amount)?; self.field(name, amount); Ok(r) } fn parse_bytes_into(&mut self, name: &'static str, buf: &mut [u8]) -> Result<()> { self.reader.read_exact(buf)?; self.field(name, buf.len()); Ok(()) } fn parse_bytes_eof(&mut self, name: &'static str) -> Result<Vec<u8>> { let r = self.reader.steal_eof()?; self.field(name, r.len()); Ok(r) } fn recursion_depth(&self) -> isize { self.path.len() as isize - 1 } /// Marks the start of a variable-sized field `name` of length /// `len`. /// /// After parsing the variable-sized field, hand the returned /// object to [`PacketHeaderParser::variable_sized_field_end`]. fn variable_sized_field_start<L>(&self, name: &'static str, len: L) -> VariableSizedField where L: Into<u32>, { VariableSizedField { name, start: self.reader.total_out().try_into() .expect("offsets in packet headers cannot exceed u32"), length: len.into(), } } /// Returns the remaining bytes in a variable-sized field. fn variable_sized_field_remaining(&self, f: &VariableSizedField) -> usize { let current: u32 = self.reader.total_out().try_into() .expect("offsets in packet headers cannot exceed u32"); f.length.saturating_sub(current - f.start) as usize } /// Marks the start of a variable-sized field and checks whether /// the correct amount of data has been consumed. fn variable_sized_field_end(&self, f: VariableSizedField) -> Result<()> { let l = u32::try_from(self.reader.total_out()) .expect("offsets in packet headers cannot exceed u32") - f.start; use std::cmp::Ordering; match l.cmp(&f.length) { Ordering::Less => Err(Error::MalformedPacket(format!( "{}: length {} but only consumed {} bytes", f.name, f.length, l)).into()), Ordering::Equal => Ok(()), Ordering::Greater => Err(Error::MalformedPacket(format!( "{}: length {} but consumed {} bytes", f.name, f.length, l)).into()), } } } /// Represents a variable-sized field in a packet header. #[must_use] struct VariableSizedField { /// Name of the field. name: &'static str, /// The amount of bytes consumed in self.reader at the start of /// the field. start: u32, /// The expected length of the variable-sized field. length: u32, } /// What the hash in the Cookie is for. #[derive(Copy, Clone, PartialEq, Debug)] pub(crate) enum HashesFor { Nothing, MDC, Signature, CleartextSignature, } /// Controls whether a hashed reader hashes data. #[derive(Copy, Clone, PartialEq, Debug)] enum Hashing { /// Hashing is enabled. Enabled, /// Hashing is enabled for notarized signatures. Notarized, /// Hashing is disabled. Disabled, } /// Private state used by the `PacketParser`. /// /// This is not intended to be used. It is possible to explicitly /// create `Cookie` instances using its `Default` implementation for /// low-level interfacing with parsing code. #[derive(Debug)] pub struct Cookie { // `BufferedReader`s managed by a `PacketParser` have // `Some(level)`; an external `BufferedReader` (i.e., the // underlying `BufferedReader`) has no level. // // Before parsing a top-level packet, we may push a // `buffered_reader::Limitor` in front of the external // `BufferedReader`. Such `BufferedReader`s are assigned a level // of 0. // // When a top-level packet (i.e., a packet with a recursion depth // of 0) reads from the `BufferedReader` stack, the top // `BufferedReader` will have a level of at most 0. // // If the top-level packet is a container, say, a `CompressedData` // packet, then it pushes a decompression filter with a level of 0 // onto the `BufferedReader` stack, and it recursively invokes the // parser. // // When the parser encounters the `CompressedData`'s first child, // say, a `Literal` packet, it pushes a `buffered_reader::Limitor` on // the `BufferedReader` stack with a level of 1. Then, a // `PacketParser` for the `Literal` data packet is created with a // recursion depth of 1. // // There are several things to note: // // - When a `PacketParser` with a recursion depth of N reads // from the `BufferedReader` stack, the top `BufferedReader`'s // level is (at most) N. // // - Because we sometimes don't need to push a limitor // (specifically, when the length is indeterminate), the // `BufferedReader` at the top of the stack may have a level // less than the current `PacketParser`'s recursion depth. // // - When a packet at depth N is a container that filters the // data, it pushes a `BufferedReader` at level N onto the // `BufferedReader` stack. // // - When we finish parsing a packet at depth N, we pop all // `BufferedReader`s from the `BufferedReader` stack that are // at level N. The intuition is: the `BufferedReaders` at // level N are associated with the packet at depth N. // // - If a OnePassSig packet occurs at the top level, then we // need to push a HashedReader above the current level. The // top level is level 0, thus we push the HashedReader at // level -1. level: Option<isize>, hashes_for: HashesFor, hashing: Hashing, /// Keeps track of whether the last one pass signature packet had /// the last flag set. saw_last: bool, sig_groups: Vec<SignatureGroup>, /// Keep track of the maximal size of sig_groups to compute /// signature levels. sig_groups_max_len: usize, /// Stashed bytes that need to be hashed. /// /// When checking nested signatures, we need to hash the framing. /// However, at the time we know that we want to hash it, it has /// already been consumed. Deferring the consumption of headers /// failed due to complications with the partial body decoder /// eagerly consuming data. I (Justus) decided that doing the /// right thing is not worth the trouble, at least for now. Also, /// hash stash sounds funny. hash_stash: Option<Vec<u8>>, /// Whether this `BufferedReader` is actually an interior EOF in a /// container. /// /// This is used by the SEIP parser to prevent a child packet from /// accidentally swallowing the trailing MDC packet. This can /// happen when there is a compressed data packet with an /// indeterminate body length encoding. In this case, due to /// buffering, the decompressor consumes data beyond the end of /// the compressed data. /// /// When set, buffered_reader_stack_pop will return early when it /// encounters a fake EOF at the level it is popping to. fake_eof: bool, /// Indicates that this is the top-level armor reader that is /// doing a transformation of a message using the cleartext /// signature framework into a signed message. csf_transformation: bool, } assert_send_and_sync!(Cookie); /// Contains hashes for consecutive one pass signature packets ending /// in one with the last flag set. #[derive(Default)] pub(crate) struct SignatureGroup { /// Counts the number of one pass signature packets this group is /// for. Once this drops to zero, we pop the group from the /// stack. ops_count: usize, /// The hash contexts. /// /// We store a salt and the hash context as tuples. /// /// In v6, the hash is salted. We store the salt here so that we /// can find the right hash context again when we encounter the /// signature packet. /// /// In v4, the hash is not salted. Hence, salt is the zero-length /// vector. The fact that the hash is not salted allows for an /// optimization: to verify two signatures using the same hash /// algorithm, the hash must be computed just once. We implement /// this optimization for v4 signatures. pub(crate) hashes: Vec<HashingMode<crypto::hash::Context>>, } impl fmt::Debug for SignatureGroup { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let algos = self.hashes.iter() .map(|mode| mode.map(|ctx| ctx.algo())) .collect::<Vec<_>>(); f.debug_struct("Cookie") .field("ops_count", &self.ops_count) .field("hashes", &algos) .finish() } } impl SignatureGroup { /// Clears the signature group. fn clear(&mut self) { self.ops_count = 0; self.hashes.clear(); } } impl Default for Cookie { fn default() -> Self { Cookie { level: None, hashing: Hashing::Enabled, hashes_for: HashesFor::Nothing, saw_last: false, sig_groups: vec![Default::default()], sig_groups_max_len: 1, hash_stash: None, fake_eof: false, csf_transformation: false, } } } impl Cookie { fn new(level: isize) -> Cookie { Cookie { level: Some(level), hashing: Hashing::Enabled, hashes_for: HashesFor::Nothing, saw_last: false, sig_groups: vec![Default::default()], sig_groups_max_len: 1, hash_stash: None, fake_eof: false, csf_transformation: false, } } /// Returns a reference to the topmost signature group. pub(crate) fn sig_group(&self) -> &SignatureGroup { assert!(!self.sig_groups.is_empty()); &self.sig_groups[self.sig_groups.len() - 1] } /// Returns a mutable reference to the topmost signature group. pub(crate) fn sig_group_mut(&mut self) -> &mut SignatureGroup { assert!(!self.sig_groups.is_empty()); let len = self.sig_groups.len(); &mut self.sig_groups[len - 1] } /// Returns the level of the currently parsed signature. fn signature_level(&self) -> usize { // The signature with the deepest "nesting" is closest to the // data, and hence level 0. self.sig_groups_max_len - self.sig_groups.len() } /// Tests whether the topmost signature group is no longer used. fn sig_group_unused(&self) -> bool { assert!(!self.sig_groups.is_empty()); self.sig_groups[self.sig_groups.len() - 1].ops_count == 0 } /// Pushes a new signature group to the stack. fn sig_group_push(&mut self) { self.sig_groups.push(Default::default()); self.sig_groups_max_len += 1; } /// Pops a signature group from the stack. fn sig_group_pop(&mut self) { if self.sig_groups.len() == 1 { // Don't pop the last one, just clear it. self.sig_groups[0].clear(); self.hashes_for = HashesFor::Nothing; } else { self.sig_groups.pop(); } } } impl Cookie { // Enables or disables signature hashers (HashesFor::Signature) at // level `level`. // // Thus to disable the hashing of a level 3 literal packet's // meta-data, we disable hashing at level 2. fn hashing(reader: &mut dyn BufferedReader<Cookie>, how: Hashing, level: isize) { let mut reader : Option<&mut dyn BufferedReader<Cookie>> = Some(reader); while let Some(r) = reader { { let cookie = r.cookie_mut(); if let Some(br_level) = cookie.level { if br_level < level { break; } if br_level == level && (cookie.hashes_for == HashesFor::Signature || cookie.hashes_for == HashesFor::CleartextSignature) { cookie.hashing = how; } } else { break; } } reader = r.get_mut(); } } /// Signals that we are processing a message using the Cleartext /// Signature Framework. /// /// This is used by the armor reader to signal that it has /// encountered such a message and is transforming it into an /// inline signed message. pub(crate) fn set_processing_csf_message(&mut self) { tracer!(TRACE, "set_processing_csf_message", self.level.unwrap_or(0)); t!("Enabling CSF Transformation mode"); self.csf_transformation = true; } /// Checks if we are processing a signed message using the /// Cleartext Signature Framework. fn processing_csf_message(reader: &dyn BufferedReader<Cookie>) -> bool { let mut reader: Option<&dyn BufferedReader<Cookie>> = Some(reader); while let Some(r) = reader { if r.cookie_ref().level == Some(ARMOR_READER_LEVEL) { return r.cookie_ref().csf_transformation; } else { reader = r.get_ref(); } } false } } // Pops readers from a buffered reader stack at the specified level. fn buffered_reader_stack_pop<'a>( mut reader: Box<dyn BufferedReader<Cookie> + 'a>, depth: isize) -> Result<(bool, Box<dyn BufferedReader<Cookie> + 'a>)> { tracer!(TRACE, "buffered_reader_stack_pop", depth); t!("(reader level: {:?}, pop through: {})", reader.cookie_ref().level, depth); while let Some(level) = reader.cookie_ref().level { assert!(level <= depth // Peel off exactly one level. || depth < 0); // Except for the topmost filters. if level >= depth { let fake_eof = reader.cookie_ref().fake_eof; t!("top reader at level {:?} (fake eof: {}), pop through: {}", reader.cookie_ref().level, fake_eof, depth); t!("popping level {:?} reader, reader: {:?}", reader.cookie_ref().level, reader); if reader.eof() && ! reader.consummated() { return Err(Error::MalformedPacket("Truncated packet".into()) .into()); } reader.drop_eof()?; reader = reader.into_inner().unwrap(); if level == depth && fake_eof { t!("Popped a fake EOF reader at level {}, stopping.", depth); return Ok((true, reader)); } t!("now at level {:?} reader: {:?}", reader.cookie_ref().level, reader); } else { break; } } Ok((false, reader)) } // A `PacketParser`'s settings. #[derive(Clone, Debug)] struct PacketParserSettings { // The maximum allowed recursion depth. // // There is absolutely no reason that this should be more than // 255. (GnuPG defaults to 32.) Moreover, if it is too large, // then a read from the reader pipeline could blow the stack. max_recursion_depth: u8, // The maximum size of non-container packets. // // Packets that exceed this limit will be returned as // `Packet::Unknown`, with the error set to // `Error::PacketTooLarge`. // // This limit applies to any packet type that is *not* a // container packet, i.e. any packet that is not a literal data // packet, a compressed data packet, a symmetrically encrypted // data packet, or an AEAD encrypted data packet. max_packet_size: u32, // Whether a packet's contents should be buffered or dropped when // the next packet is retrieved. buffer_unread_content: bool, // Whether to create a map. map: bool, // Whether to implicitly start hashing upon parsing OnePassSig // packets. automatic_hashing: bool, } // The default `PacketParser` settings. impl Default for PacketParserSettings { fn default() -> Self { PacketParserSettings { max_recursion_depth: DEFAULT_MAX_RECURSION_DEPTH, max_packet_size: DEFAULT_MAX_PACKET_SIZE, buffer_unread_content: false, map: false, automatic_hashing: true, } } } impl S2K { /// Reads an S2K from `php`. fn parse_v4(php: &mut PacketHeaderParser<'_>) -> Result<Self> { Self::parse_common(php, None) } /// Reads an S2K from `php` with explicit S2K length. fn parse_v6(php: &mut PacketHeaderParser, s2k_len: u8) -> Result<Self> { Self::parse_common(php, Some(s2k_len)) } /// Reads an S2K from `php` with optional explicit S2K length. fn parse_common(php: &mut PacketHeaderParser<'_>, s2k_len: Option<u8>) -> Result<Self> { if s2k_len == Some(0) { return Err(Error::MalformedPacket( "Invalid size for S2K object: 0 octets".into()).into()); } let check_size = |expected| { if let Some(got) = s2k_len { if got != expected { return Err(Error::MalformedPacket(format!( "Invalid size for S2K object: {} octets, expected {}", got, expected))); } } Ok(()) }; let s2k = php.parse_u8("s2k_type")?; #[allow(deprecated)] let ret = match s2k { 0 => { check_size(2)?; S2K::Simple { hash: HashAlgorithm::from(php.parse_u8("s2k_hash_algo")?), } }, 1 => { check_size(10)?; S2K::Salted { hash: HashAlgorithm::from(php.parse_u8("s2k_hash_algo")?), salt: Self::read_salt(php)?, } }, 3 => { check_size(11)?; S2K::Iterated { hash: HashAlgorithm::from(php.parse_u8("s2k_hash_algo")?), salt: Self::read_salt(php)?, hash_bytes: S2K::decode_count(php.parse_u8("s2k_count")?), } }, 4 => S2K::Argon2 { salt: { let mut b = [0u8; 16]; let b_len = b.len(); b.copy_from_slice( &php.parse_bytes("argon2_salt", b_len)?); b }, t: php.parse_u8("argon2_t")?, p: php.parse_u8("argon2_p")?, m: php.parse_u8("argon2_m")?, }, 100..=110 => S2K::Private { tag: s2k, parameters: if let Some(l) = s2k_len { Some( php.parse_bytes("parameters", l as usize - 1 /* Tag */)? .into()) } else { None }, }, u => S2K::Unknown { tag: u, parameters: if let Some(l) = s2k_len { Some( php.parse_bytes("parameters", l as usize - 1 /* Tag */)? .into()) } else { None }, }, }; Ok(ret) } fn read_salt(php: &mut PacketHeaderParser<'_>) -> Result<[u8; 8]> { let mut b = [0u8; 8]; b.copy_from_slice(&php.parse_bytes("s2k_salt", 8)?); Ok(b) } } impl_parse_with_buffered_reader!( S2K, |bio: Box<dyn BufferedReader<Cookie>>| -> Result<Self> { let mut parser = PacketHeaderParser::new_naked(bio.into_boxed()); Self::parse_v4(&mut parser) }); impl Header { pub(crate) fn parse<R: BufferedReader<C>, C: fmt::Debug + Send + Sync> (bio: &mut R) -> Result<Header> { let ctb = CTB::try_from(bio.data_consume_hard(1)?[0])?; let length = match ctb { CTB::New(_) => BodyLength::parse_new_format(bio)?, CTB::Old(ref ctb) => BodyLength::parse_old_format(bio, ctb.length_type())?, }; Ok(Header::new(ctb, length)) } } impl_parse_with_buffered_reader!( Header, |mut reader| -> Result<Self> { Header::parse(&mut reader) }); impl BodyLength { /// Decodes a new format body length as described in [Section /// 4.2.1 of RFC 9580]. /// /// [Section 4.2.1 of RFC 9580]: https://www.rfc-editor.org/rfc/rfc9580.html#section-4.2.1 pub(crate) fn parse_new_format<T: BufferedReader<C>, C: fmt::Debug + Send + Sync> (bio: &mut T) -> io::Result<BodyLength> { let octet1 : u8 = bio.data_consume_hard(1)?[0]; match octet1 { 0..=191 => // One octet. Ok(BodyLength::Full(octet1 as u32)), 192..=223 => { // Two octets length. let octet2 = bio.data_consume_hard(1)?[0]; Ok(BodyLength::Full(((octet1 as u32 - 192) << 8) + octet2 as u32 + 192)) }, 224..=254 => // Partial body length. Ok(BodyLength::Partial(1 << (octet1 & 0x1F))), 255 => // Five octets. Ok(BodyLength::Full(bio.read_be_u32()?)), } } /// Decodes an old format body length as described in [Section /// 4.2.2 of RFC 9580]. /// /// [Section 4.2.2 of RFC 9580]: https://www.rfc-editor.org/rfc/rfc9580.html#section-4.2.2 pub(crate) fn parse_old_format<T: BufferedReader<C>, C: fmt::Debug + Send + Sync> (bio: &mut T, length_type: PacketLengthType) -> Result<BodyLength> { match length_type { PacketLengthType::OneOctet => Ok(BodyLength::Full(bio.data_consume_hard(1)?[0] as u32)), PacketLengthType::TwoOctets => Ok(BodyLength::Full(bio.read_be_u16()? as u32)), PacketLengthType::FourOctets => Ok(BodyLength::Full(bio.read_be_u32()? as u32)), PacketLengthType::Indeterminate => Ok(BodyLength::Indeterminate), } } } #[test] fn body_length_new_format() { fn test(input: &[u8], expected_result: BodyLength) { assert_eq!( BodyLength::parse_new_format( &mut buffered_reader::Memory::new(input)).unwrap(), expected_result); } // Examples from Section 4.2.3 of RFC4880. // Example #1. test(&[0x64][..], BodyLength::Full(100)); // Example #2. test(&[0xC5, 0xFB][..], BodyLength::Full(1723)); // Example #3. test(&[0xFF, 0x00, 0x01, 0x86, 0xA0][..], BodyLength::Full(100000)); // Example #4. test(&[0xEF][..], BodyLength::Partial(32768)); test(&[0xE1][..], BodyLength::Partial(2)); test(&[0xF0][..], BodyLength::Partial(65536)); test(&[0xC5, 0xDD][..], BodyLength::Full(1693)); } #[test] fn body_length_old_format() { fn test(input: &[u8], plt: PacketLengthType, expected_result: BodyLength, expected_rest: &[u8]) { let mut bio = buffered_reader::Memory::new(input); assert_eq!(BodyLength::parse_old_format(&mut bio, plt).unwrap(), expected_result); let rest = bio.data_eof(); assert_eq!(rest.unwrap(), expected_rest); } test(&[1], PacketLengthType::OneOctet, BodyLength::Full(1), &b""[..]); test(&[1, 2], PacketLengthType::TwoOctets, BodyLength::Full((1 << 8) + 2), &b""[..]); test(&[1, 2, 3, 4], PacketLengthType::FourOctets, BodyLength::Full((1 << 24) + (2 << 16) + (3 << 8) + 4), &b""[..]); test(&[1, 2, 3, 4, 5, 6], PacketLengthType::FourOctets, BodyLength::Full((1 << 24) + (2 << 16) + (3 << 8) + 4), &[5, 6][..]); test(&[1, 2, 3, 4], PacketLengthType::Indeterminate, BodyLength::Indeterminate, &[1, 2, 3, 4][..]); } impl Unknown { /// Parses the body of any packet and returns an Unknown. fn parse(php: PacketHeaderParser, error: anyhow::Error) -> Result<PacketParser> { let tag = php.header.ctb().tag(); php.ok(Packet::Unknown(Unknown::new(tag, error))) } } // Read the next packet as an unknown packet. // // The `reader` must point to the packet's header, i.e., the CTB. // This buffers the packet's contents. // // Note: we only need this function for testing purposes in a // different module. #[cfg(test)] pub(crate) fn to_unknown_packet<R: Read + Send + Sync>(reader: R) -> Result<Unknown> { let mut reader = buffered_reader::Generic::with_cookie( reader, None, Cookie::default()); let header = Header::parse(&mut reader)?; let reader : Box<dyn BufferedReader<Cookie>> = match header.length() { &BodyLength::Full(len) => Box::new(buffered_reader::Limitor::with_cookie( reader, len as u64, Cookie::default())), &BodyLength::Partial(len) => Box::new(BufferedReaderPartialBodyFilter::with_cookie( reader, len, true, Cookie::default())), _ => Box::new(reader), }; let parser = PacketHeaderParser::new( reader, PacketParserState::new(Default::default()), vec![ 0 ], header, Vec::new()); let mut pp = Unknown::parse(parser, anyhow::anyhow!("explicit conversion to unknown"))?; pp.buffer_unread_content()?; pp.finish()?; if let Packet::Unknown(packet) = pp.packet { Ok(packet) } else { panic!("Internal inconsistency."); } } impl Signature { // Parses a signature packet. fn parse(mut php: PacketHeaderParser) -> Result<PacketParser> { let indent = php.recursion_depth(); tracer!(TRACE, "Signature::parse", indent); make_php_try!(php); let version = php_try!(php.parse_u8("version")); match version { 3 => Signature3::parse(php), 4 => Signature4::parse(php), 6 => Signature6::parse(php), _ => { t!("Ignoring version {} packet.", version); php.fail("unknown version") }, } } /// Returns whether the data appears to be a signature (no promises). fn plausible<C, T>(bio: &mut buffered_reader::Dup<T, C>, header: &Header) -> Result<()> where T: BufferedReader<C>, C: fmt::Debug + Send + Sync { // XXX: Support other versions. Signature4::plausible(bio, header) } /// When parsing an inline-signed message, attaches the digest to /// the signature. fn parse_finish(indent: isize, mut pp: PacketParser, hash_algo: HashAlgorithm) -> Result<PacketParser> { tracer!(TRACE, "Signature::parse_finish", indent); let sig: &Signature = pp.packet.downcast_ref() .ok_or_else( || Error::InvalidOperation( format!("Called Signature::parse_finish on a {:?}", pp.packet)))?; // If we are not parsing an inline-signed message, we are // done. if sig.typ() != SignatureType::Binary && sig.typ() != SignatureType::Text { return Ok(pp); } let need_hash = HashingMode::for_signature(hash_algo, sig); t!("Need a {:?}", need_hash); if TRACE { pp.reader.dump(&mut std::io::stderr())?; } // Locate the corresponding HashedReader and extract the // computed hash. let mut computed_digest = None; { let recursion_depth = pp.recursion_depth(); // We know that the top reader is not a HashedReader (it's // a buffered_reader::Dup). So, start with its child. let mut r = (&mut pp.reader).get_mut(); while let Some(tmp) = r { { let cookie = tmp.cookie_mut(); assert!(cookie.level.unwrap_or(-1) <= recursion_depth); // The HashedReader has to be at level // 'recursion_depth - 1'. if cookie.level.is_none() || cookie.level.unwrap() < recursion_depth - 1 { t!("Abandoning search for suitable \ hashed reader at {:?}.", cookie.level); break } if cookie.hashes_for == HashesFor::Signature { // When verifying cleartext signed messages, // we may have more signatures than // one-pass-signature packets, but are // guaranteed to only have one signature // group. // // Only decrement the count when hashing for // signatures, not when hashing for cleartext // signatures. cookie.sig_group_mut().ops_count -= 1; } if cookie.hashes_for == HashesFor::Signature || cookie.hashes_for == HashesFor::CleartextSignature { t!("Have: {:?}", cookie.sig_group().hashes.iter() .map(|h| h.map(|h| h.algo())) .collect::<Vec<_>>()); if let Some(hash) = cookie.sig_group().hashes.iter().find_map( |mode| if mode.map(|ctx| ctx.algo()) == need_hash { Some(mode.as_ref()) } else { None }) { t!("found a {:?} HashedReader", need_hash); computed_digest = Some((cookie.signature_level(), hash.clone())); } if cookie.sig_group_unused() { cookie.sig_group_pop(); } break; } } r = tmp.get_mut(); } } if let Some((level, mut hash)) = computed_digest { if let Packet::Signature(ref mut sig) = pp.packet { sig.hash(&mut hash)?; let mut digest = vec![0u8; hash.digest_size()]; let _ = hash.digest(&mut digest); sig.set_computed_digest(Some(digest)); sig.set_level(level); } else { unreachable!() } } Ok(pp) } } impl Signature6 { // Parses a signature packet. fn parse(mut php: PacketHeaderParser) -> Result<PacketParser> { let indent = php.recursion_depth(); tracer!(TRACE, "Signature6::parse", indent); make_php_try!(php); let typ = php_try!(php.parse_u8("type")); let pk_algo: PublicKeyAlgorithm = php_try!(php.parse_u8("pk_algo")).into(); let hash_algo: HashAlgorithm = php_try!(php.parse_u8("hash_algo")).into(); let hashed_area_len = php_try!(php.parse_be_u32("hashed_area_len")); let hashed_area = php_try!(SubpacketArea::parse(&mut php, hashed_area_len as usize, hash_algo)); let unhashed_area_len = php_try!(php.parse_be_u32("unhashed_area_len")); let unhashed_area = php_try!(SubpacketArea::parse(&mut php, unhashed_area_len as usize, hash_algo)); let digest_prefix1 = php_try!(php.parse_u8("digest_prefix1")); let digest_prefix2 = php_try!(php.parse_u8("digest_prefix2")); if ! pk_algo.for_signing() { return php.fail("not a signature algorithm"); } let salt_len = php_try!(php.parse_u8("salt_len")) as usize; let salt = php_try!(php.parse_bytes("salt", salt_len)); let mpis = php_try!( crypto::mpi::Signature::_parse(pk_algo, &mut php)); let typ = typ.into(); let sig = php_try!(Signature6::new( typ, pk_algo, hash_algo, hashed_area, unhashed_area, [digest_prefix1, digest_prefix2], salt, mpis)); let pp = php.ok(sig.into())?; Signature::parse_finish(indent, pp, hash_algo) } } impl Signature4 { // Parses a signature packet. fn parse(mut php: PacketHeaderParser) -> Result<PacketParser> { let indent = php.recursion_depth(); tracer!(TRACE, "Signature4::parse", indent); make_php_try!(php); let typ = php_try!(php.parse_u8("type")); let pk_algo: PublicKeyAlgorithm = php_try!(php.parse_u8("pk_algo")).into(); let hash_algo: HashAlgorithm = php_try!(php.parse_u8("hash_algo")).into(); let hashed_area_len = php_try!(php.parse_be_u16("hashed_area_len")); let hashed_area = php_try!(SubpacketArea::parse(&mut php, hashed_area_len as usize, hash_algo)); let unhashed_area_len = php_try!(php.parse_be_u16("unhashed_area_len")); let unhashed_area = php_try!(SubpacketArea::parse(&mut php, unhashed_area_len as usize, hash_algo)); let digest_prefix1 = php_try!(php.parse_u8("digest_prefix1")); let digest_prefix2 = php_try!(php.parse_u8("digest_prefix2")); if ! pk_algo.for_signing() { return php.fail("not a signature algorithm"); } let mpis = php_try!( crypto::mpi::Signature::_parse(pk_algo, &mut php)); let typ = typ.into(); let pp = php.ok(Packet::Signature(Signature4::new( typ, pk_algo, hash_algo, hashed_area, unhashed_area, [digest_prefix1, digest_prefix2], mpis).into()))?; Signature::parse_finish(indent, pp, hash_algo) } /// Returns whether the data appears to be a signature (no promises). fn plausible<C, T>(bio: &mut buffered_reader::Dup<T, C>, header: &Header) -> Result<()> where T: BufferedReader<C>, C: fmt::Debug + Send + Sync { // The absolute minimum size for the header is 11 bytes (this // doesn't include the signature MPIs). if let BodyLength::Full(len) = header.length() { if *len < 11 { // Much too short. return Err( Error::MalformedPacket("Packet too short".into()).into()); } } else { return Err( Error::MalformedPacket( format!("Unexpected body length encoding: {:?}", header.length())).into()); } // Make sure we have a minimum header. let data = bio.data(11)?; if data.len() < 11 { return Err( Error::MalformedPacket("Short read".into()).into()); } // Assume unknown == bad. let version = data[0]; let typ : SignatureType = data[1].into(); let pk_algo : PublicKeyAlgorithm = data[2].into(); let hash_algo : HashAlgorithm = data[3].into(); if version == 4 && !matches!(typ, SignatureType::Unknown(_)) && !matches!(pk_algo, PublicKeyAlgorithm::Unknown(_)) && !matches!(hash_algo, HashAlgorithm::Unknown(_)) { Ok(()) } else { Err(Error::MalformedPacket("Invalid or unsupported data".into()) .into()) } } } impl Signature3 { // Parses a v3 signature packet. fn parse(mut php: PacketHeaderParser) -> Result<PacketParser> { let indent = php.recursion_depth(); tracer!(TRACE, "Signature3::parse", indent); make_php_try!(php); let len = php_try!(php.parse_u8("hashed length")); if len != 5 { return php.fail("invalid length \ (a v3 sig has 5 bytes of hashed data)"); } let typ = php_try!(php.parse_u8("type")); let creation_time: Timestamp = php_try!(php.parse_be_u32("creation_time")).into(); let issuer: KeyID = KeyID::from_bytes(&php_try!(php.parse_bytes("issuer", 8))[..]); let pk_algo: PublicKeyAlgorithm = php_try!(php.parse_u8("pk_algo")).into(); let hash_algo: HashAlgorithm = php_try!(php.parse_u8("hash_algo")).into(); let digest_prefix1 = php_try!(php.parse_u8("digest_prefix1")); let digest_prefix2 = php_try!(php.parse_u8("digest_prefix2")); if ! pk_algo.for_signing() { return php.fail("not a signature algorithm"); } let mpis = php_try!( crypto::mpi::Signature::_parse(pk_algo, &mut php)); let typ = typ.into(); let pp = php.ok(Packet::Signature(Signature3::new( typ, creation_time, issuer, pk_algo, hash_algo, [digest_prefix1, digest_prefix2], mpis).into()))?; Signature::parse_finish(indent, pp, hash_algo) } } impl_parse_with_buffered_reader!(Signature); #[test] fn signature_parser_test () { use crate::serialize::MarshalInto; let data = crate::tests::message("sig.gpg"); { let pp = PacketParser::from_bytes(data).unwrap().unwrap(); assert_eq!(pp.header.length(), &BodyLength::Full(307)); if let Packet::Signature(ref p) = pp.packet { assert_eq!(p.version(), 4); assert_eq!(p.typ(), SignatureType::Binary); assert_eq!(p.pk_algo(), PublicKeyAlgorithm::RSAEncryptSign); assert_eq!(p.hash_algo(), HashAlgorithm::SHA512); assert_eq!(p.hashed_area().iter().count(), 2); assert_eq!(p.unhashed_area().iter().count(), 1); assert_eq!(p.digest_prefix(), &[0x65u8, 0x74]); assert_eq!(p.mpis().serialized_len(), 258); } else { panic!("Wrong packet!"); } } } impl SubpacketArea { // Parses a subpacket area. fn parse(php: &mut PacketHeaderParser, mut limit: usize, hash_algo: HashAlgorithm) -> Result<Self> { let indent = php.recursion_depth(); tracer!(TRACE, "SubpacketArea::parse", indent); let mut packets = Vec::new(); while limit > 0 { let r = Subpacket::parse(php, limit, hash_algo); t!("Subpacket::parse(_, {}, {:?}) => {:?}", limit, hash_algo, r); let p = r?; assert!(limit >= p.length.len() + p.length.serialized_len()); limit -= p.length.len() + p.length.serialized_len(); packets.push(p); } assert!(limit == 0); Self::new(packets) } } impl Subpacket { // Parses a raw subpacket. fn parse(php: &mut PacketHeaderParser, limit: usize, hash_algo: HashAlgorithm) -> Result<Self> { let length = SubpacketLength::parse(&mut php.reader)?; php.field("subpacket length", length.serialized_len()); let len = length.len() as usize; if limit < length.serialized_len() + len { return Err(Error::MalformedPacket( "Subpacket extends beyond the end of the subpacket area".into()) .into()); } if len == 0 { return Err(Error::MalformedPacket("Zero-length subpacket".into()) .into()); } let tag = php.parse_u8("subpacket tag")?; let len = len - 1; // Remember our position in the reader to check subpacket boundaries. let total_out_before = php.reader.total_out(); // The critical bit is the high bit. Extract it. let critical = tag & (1 << 7) != 0; // Then clear it from the type and convert it. let tag: SubpacketTag = (tag & !(1 << 7)).into(); #[allow(deprecated)] let value = match tag { SubpacketTag::SignatureCreationTime => SubpacketValue::SignatureCreationTime( php.parse_be_u32("sig creation time")?.into()), SubpacketTag::SignatureExpirationTime => SubpacketValue::SignatureExpirationTime( php.parse_be_u32("sig expiry time")?.into()), SubpacketTag::ExportableCertification => SubpacketValue::ExportableCertification( php.parse_bool("exportable")?), SubpacketTag::TrustSignature => SubpacketValue::TrustSignature { level: php.parse_u8("trust level")?, trust: php.parse_u8("trust value")?, }, SubpacketTag::RegularExpression => { let mut v = php.parse_bytes("regular expr", len)?; if v.is_empty() || v[v.len() - 1] != 0 { return Err(Error::MalformedPacket( "Regular expression not 0-terminated".into()) .into()); } v.pop(); SubpacketValue::RegularExpression(v) }, SubpacketTag::Revocable => SubpacketValue::Revocable(php.parse_bool("revocable")?), SubpacketTag::KeyExpirationTime => SubpacketValue::KeyExpirationTime( php.parse_be_u32("key expiry time")?.into()), SubpacketTag::PreferredSymmetricAlgorithms => SubpacketValue::PreferredSymmetricAlgorithms( php.parse_bytes("pref sym algos", len)? .iter().map(|o| (*o).into()).collect()), SubpacketTag::RevocationKey => { // 1 octet of class, 1 octet of pk algorithm, 20 bytes // for a v4 fingerprint and 32 bytes for a v6 // fingerprint. if len < 22 { return Err(Error::MalformedPacket( "Short revocation key subpacket".into()) .into()); } let class = php.parse_u8("class")?; let pk_algo = php.parse_u8("pk algo")?.into(); let fp = Fingerprint::from_bytes_intern( None, &php.parse_bytes("fingerprint", len - 2)?)?; SubpacketValue::RevocationKey( RevocationKey::from_bits(pk_algo, fp, class)?) }, SubpacketTag::Issuer => SubpacketValue::Issuer( KeyID::from_bytes(&php.parse_bytes("issuer", len)?)), SubpacketTag::NotationData => { let flags = php.parse_bytes("flags", 4)?; let name_len = php.parse_be_u16("name len")? as usize; let value_len = php.parse_be_u16("value len")? as usize; if len != 8 + name_len + value_len { return Err(Error::MalformedPacket( format!("Malformed notation data subpacket: \ expected {} bytes, got {}", 8 + name_len + value_len, len)).into()); } SubpacketValue::NotationData( NotationData::new( std::str::from_utf8( &php.parse_bytes("notation name", name_len)?) .map_err(|e| anyhow::Error::from( Error::MalformedPacket( format!("Malformed notation name: {}", e))) )?, &php.parse_bytes("notation value", value_len)?, Some(NotationDataFlags::new(&flags)?))) }, SubpacketTag::PreferredHashAlgorithms => SubpacketValue::PreferredHashAlgorithms( php.parse_bytes("pref hash algos", len)? .iter().map(|o| (*o).into()).collect()), SubpacketTag::PreferredCompressionAlgorithms => SubpacketValue::PreferredCompressionAlgorithms( php.parse_bytes("pref compression algos", len)? .iter().map(|o| (*o).into()).collect()), SubpacketTag::KeyServerPreferences => SubpacketValue::KeyServerPreferences( KeyServerPreferences::new( &php.parse_bytes("key server pref", len)? )), SubpacketTag::PreferredKeyServer => SubpacketValue::PreferredKeyServer( php.parse_bytes("pref key server", len)?), SubpacketTag::PrimaryUserID => SubpacketValue::PrimaryUserID( php.parse_bool("primary user id")?), SubpacketTag::PolicyURI => SubpacketValue::PolicyURI(php.parse_bytes("policy URI", len)?), SubpacketTag::KeyFlags => SubpacketValue::KeyFlags(KeyFlags::new( &php.parse_bytes("key flags", len)?)), SubpacketTag::SignersUserID => SubpacketValue::SignersUserID( php.parse_bytes("signers user id", len)?), SubpacketTag::ReasonForRevocation => { if len == 0 { return Err(Error::MalformedPacket( "Short reason for revocation subpacket".into()).into()); } SubpacketValue::ReasonForRevocation { code: php.parse_u8("revocation reason")?.into(), reason: php.parse_bytes("human-readable", len - 1)?, } }, SubpacketTag::Features => SubpacketValue::Features(Features::new( &php.parse_bytes("features", len)?)), SubpacketTag::SignatureTarget => { if len < 2 { return Err(Error::MalformedPacket( "Short reason for revocation subpacket".into()).into()); } SubpacketValue::SignatureTarget { pk_algo: php.parse_u8("pk algo")?.into(), hash_algo: php.parse_u8("hash algo")?.into(), digest: php.parse_bytes("digest", len - 2)?, } }, SubpacketTag::EmbeddedSignature => SubpacketValue::EmbeddedSignature( Signature::from_bytes( &php.parse_bytes("embedded sig", len)?)?), SubpacketTag::IssuerFingerprint => { if len == 0 { return Err(Error::MalformedPacket( "Short issuer fingerprint subpacket".into()).into()); } let version = php.parse_u8("version")?; if let Some(expect_len) = match version { 4 => Some(1 + 20), 6 => Some(1 + 32), _ => None, } { if len != expect_len { return Err(Error::MalformedPacket( format!("Malformed issuer fingerprint subpacket: \ expected {} bytes, got {}", expect_len, len)).into()); } } let bytes = php.parse_bytes("issuer fp", len - 1)?; SubpacketValue::IssuerFingerprint( Fingerprint::from_bytes(version, &bytes)?) }, SubpacketTag::IntendedRecipient => { if len == 0 { return Err(Error::MalformedPacket( "Short intended recipient subpacket".into()).into()); } let version = php.parse_u8("version")?; if let Some(expect_len) = match version { 4 => Some(1 + 20), 6 => Some(1 + 32), _ => None, } { if len != expect_len { return Err(Error::MalformedPacket( format!("Malformed intended recipient subpacket: \ expected {} bytes, got {}", expect_len, len)).into()); } } let bytes = php.parse_bytes("intended rcpt", len - 1)?; SubpacketValue::IntendedRecipient( Fingerprint::from_bytes(version, &bytes)?) }, SubpacketTag::ApprovedCertifications => { // If we don't know the hash algorithm, put all digest // into one bucket. That way, at least it will // roundtrip. It will never verify, because we don't // know the hash. let digest_size = hash_algo.context().map(|c| c.for_digest().digest_size()) .unwrap_or(len); if digest_size == 0 { // Empty body with unknown hash algorithm. SubpacketValue::ApprovedCertifications( Vec::with_capacity(0)) } else { if len % digest_size != 0 { return Err(Error::BadSignature( "Wrong number of bytes in certification subpacket" .into()).into()); } let bytes = php.parse_bytes("attested crts", len)?; SubpacketValue::ApprovedCertifications( bytes.chunks(digest_size).map(Into::into).collect()) } }, SubpacketTag::PreferredAEADCiphersuites => { if len % 2 != 0 { return Err(Error::BadSignature( "Wrong number of bytes in preferred AEAD \ Ciphersuites subpacket" .into()).into()); } SubpacketValue::PreferredAEADCiphersuites( php.parse_bytes("pref aead ciphersuites", len)? .chunks(2).map(|o| (o[0].into(), o[1].into())).collect()) }, SubpacketTag::Reserved(_) | SubpacketTag::PlaceholderForBackwardCompatibility | SubpacketTag::PreferredAEADAlgorithms | SubpacketTag::Private(_) | SubpacketTag::Unknown(_) => SubpacketValue::Unknown { tag, body: php.parse_bytes("unknown subpacket", len)?, }, }; let total_out = php.reader.total_out(); if total_out_before + len != total_out { return Err(Error::MalformedPacket( format!("Malformed subpacket: \ body length is {} bytes, but read {}", len, total_out - total_out_before)).into()); } Ok(Subpacket::with_length( length, value, critical, )) } } impl SubpacketLength { /// Parses a subpacket length. fn parse<R: BufferedReader<C>, C: fmt::Debug + Send + Sync>(bio: &mut R) -> Result<Self> { let octet1 = bio.data_consume_hard(1)?[0]; if octet1 < 192 { // One octet. Ok(Self::new( octet1 as u32, // Unambiguous. None)) } else if (192..255).contains(&octet1) { // Two octets length. let octet2 = bio.data_consume_hard(1)?[0]; let len = ((octet1 as u32 - 192) << 8) + octet2 as u32 + 192; Ok(Self::new( len, if Self::len_optimal_encoding(len) == 2 { None } else { Some(vec![octet1, octet2]) })) } else { // Five octets. assert_eq!(octet1, 255); let len = bio.read_be_u32()?; Ok(Self::new( len, if Self::len_optimal_encoding(len) == 5 { None } else { let mut out = Vec::with_capacity(5); out.push(octet1); out.extend_from_slice(&len.to_be_bytes()); Some(out) })) } } } #[cfg(test)] quickcheck! { fn length_roundtrip(l: u32) -> bool { use crate::serialize::Marshal; let length = SubpacketLength::from(l); let mut encoded = Vec::new(); length.serialize(&mut encoded).unwrap(); assert_eq!(encoded.len(), length.serialized_len()); let mut reader = buffered_reader::Memory::new(&encoded); SubpacketLength::parse(&mut reader).unwrap().len() == l as usize } } impl OnePassSig { fn parse(mut php: PacketHeaderParser) -> Result<PacketParser> { let indent = php.recursion_depth(); tracer!(TRACE, "OnePassSig", indent); make_php_try!(php); let version = php_try!(php.parse_u8("version")); match version { 3 => OnePassSig3::parse(php), 6 => OnePassSig6::parse(php), _ => { t!("Ignoring version {} packet", version); // Unknown version. Return an unknown packet. php.fail("unknown version") }, } } } impl_parse_with_buffered_reader!(OnePassSig); impl OnePassSig3 { fn parse(mut php: PacketHeaderParser) -> Result<PacketParser> { let indent = php.recursion_depth(); tracer!(TRACE, "OnePassSig3", indent); make_php_try!(php); let typ = php_try!(php.parse_u8("type")); let hash_algo = php_try!(php.parse_u8("hash_algo")); let pk_algo = php_try!(php.parse_u8("pk_algo")); let mut issuer = [0u8; 8]; issuer.copy_from_slice(&php_try!(php.parse_bytes("issuer", 8))); let last = php_try!(php.parse_u8("last")); let hash_algo = hash_algo.into(); let typ = typ.into(); let mut sig = OnePassSig3::new(typ); sig.set_hash_algo(hash_algo); sig.set_pk_algo(pk_algo.into()); sig.set_issuer(KeyID::from_bytes(&issuer)); sig.set_last_raw(last); let need_hash = HashingMode::for_salt_and_type(hash_algo, &[], typ); let recursion_depth = php.recursion_depth(); // Check if we are processing a cleartext signed message. let want_hashes_for = if Cookie::processing_csf_message(&php.reader) { HashesFor::CleartextSignature } else { HashesFor::Signature }; // Walk up the reader chain to see if there is already a // hashed reader on level recursion_depth - 1. let done = { let mut done = false; let mut reader : Option<&mut dyn BufferedReader<Cookie>> = Some(&mut php.reader); while let Some(r) = reader { { let cookie = r.cookie_mut(); if let Some(br_level) = cookie.level { if br_level < recursion_depth - 1 { break; } if br_level == recursion_depth - 1 && cookie.hashes_for == want_hashes_for { // We found a suitable hashed reader. if cookie.saw_last { cookie.sig_group_push(); cookie.saw_last = false; cookie.hash_stash = Some(php.header_bytes.clone()); } // Make sure that it uses the required // hash algorithm. if php.state.settings.automatic_hashing && ! cookie.sig_group().hashes.iter() .any(|mode| { mode.map(|ctx| ctx.algo()) == need_hash }) { if let Ok(ctx) = hash_algo.context() { let ctx = ctx.for_signature(4); cookie.sig_group_mut().hashes.push( HashingMode::for_salt_and_type( ctx, &[], typ) ); } } // Account for this OPS packet. cookie.sig_group_mut().ops_count += 1; // Keep track of the last flag. cookie.saw_last = last > 0; // We're done. done = true; break; } } else { break; } } reader = r.get_mut(); } done }; // Commit here after potentially pushing a signature group. let mut pp = php.ok(Packet::OnePassSig(sig.into()))?; if done { return Ok(pp); } // We create an empty hashed reader even if we don't support // the hash algorithm so that we have something to match // against when we get to the Signature packet. Or, automatic // hashing may be disabled, and we want to be able to enable // it explicitly. let mut algos = Vec::new(); if pp.state.settings.automatic_hashing && hash_algo.is_supported() { algos.push(HashingMode::for_salt_and_type(hash_algo, &[], typ)); } // We can't push the HashedReader on the BufferedReader stack: // when we finish processing this OnePassSig packet, it will // be popped. Instead, we need to insert it at the next // higher level. Unfortunately, this isn't possible. But, // since we're done reading the current packet, we can pop the // readers associated with it, and then push the HashedReader. // This is a bit of a layering violation, but I (Neal) can't // think of a more elegant solution. assert!(pp.reader.cookie_ref().level <= Some(recursion_depth)); let (fake_eof, reader) = buffered_reader_stack_pop(Box::new(pp.take_reader()), recursion_depth)?; // We only pop the buffered readers for the OPS, and we // (currently) never use a fake eof for OPS packets. assert!(! fake_eof); let mut reader = HashedReader::new( reader, want_hashes_for, algos)?; reader.cookie_mut().level = Some(recursion_depth - 1); // Account for this OPS packet. reader.cookie_mut().sig_group_mut().ops_count += 1; // Keep track of the last flag. reader.cookie_mut().saw_last = last > 0; t!("Pushed a hashed reader, level {:?}", reader.cookie_mut().level); // We add an empty limitor on top of the hashed reader, // because when we are done processing a packet, // PacketParser::finish discards any unread data from the top // reader. Since the top reader is the HashedReader, this // discards any following packets. To prevent this, we push a // Limitor on the reader stack. let mut reader = buffered_reader::Limitor::with_cookie( reader, 0, Cookie::default()); reader.cookie_mut().level = Some(recursion_depth); pp.reader = Box::new(reader); Ok(pp) } } impl PacketParser<'_> { /// Starts hashing for the current [`OnePassSig`] packet. /// /// If automatic hashing is disabled using /// [`PacketParserBuilder::automatic_hashing`], then hashing can /// be explicitly enabled while parsing a [`OnePassSig`] packet. /// /// If this function is called on a packet other than a /// [`OnePassSig`] packet, it returns [`Error::InvalidOperation`]. /// /// [`Error::InvalidOperation`]: crate::Error::InvalidOperation /// /// # Examples /// /// ```rust /// # fn main() -> sequoia_openpgp::Result<()> { /// # use sequoia_openpgp as openpgp; /// # use openpgp::{Cert, Packet}; /// # use openpgp::parse::{Parse, PacketParserResult, PacketParserBuilder}; /// // Parse a signed message, verify using the signer's key. /// let message_data: &[u8] = // ... /// # include_bytes!("../tests/data/messages/signed-1-eddsa-ed25519.pgp"); /// # let cert: Cert = // ... /// # Cert::from_bytes(include_bytes!("../tests/data/keys/emmelie-dorothea-dina-samantha-awina-ed25519.pgp"))?; /// let signer = // ... /// # cert.primary_key().key(); /// let mut good = false; /// let mut ppr = PacketParserBuilder::from_bytes(message_data)? /// .automatic_hashing(false) /// .build()?; /// while let PacketParserResult::Some(mut pp) = ppr { /// if let Packet::OnePassSig(_) = &pp.packet { /// pp.start_hashing()?; /// } /// if let Packet::Signature(sig) = &mut pp.packet { /// good |= sig.verify_document(signer).is_ok(); /// } /// // Start parsing the next packet, recursing. /// ppr = pp.recurse()?.1; /// } /// assert!(good); /// # Ok(()) } /// ``` pub fn start_hashing(&mut self) -> Result<()> { let ops: &OnePassSig = self.packet.downcast_ref() .ok_or_else(|| Error::InvalidOperation( "Must only be invoked on one-pass-signature packets".into()) )?; let sig_version = match ops.version() { 3 => 4, n => return Err(Error::InvalidOperation( format!("don't know how to hash for v{} one pass sig", n)).into()), }; let hash_algo = ops.hash_algo(); let typ = ops.typ(); let salt = ops.salt().unwrap_or(&[]); let need_hash = HashingMode::for_salt_and_type(hash_algo, salt, typ); let recursion_depth = self.recursion_depth(); let want_hashes_for = if Cookie::processing_csf_message(&self.reader) { HashesFor::CleartextSignature } else { HashesFor::Signature }; // Walk up the reader chain to find the hashed reader on level // recursion_depth - 1. let mut reader : Option<&mut dyn BufferedReader<Cookie>> = Some(&mut self.reader); while let Some(r) = reader { { let cookie = r.cookie_mut(); if let Some(br_level) = cookie.level { if br_level < recursion_depth - 1 { break; } if br_level == recursion_depth - 1 && cookie.hashes_for == want_hashes_for { // We found a suitable hashed reader. // Make sure that it uses the required // hash algorithm. if ! cookie.sig_group().hashes.iter() .any(|mode| { mode.map(|ctx| ctx.algo()) == need_hash }) { let mut ctx = hash_algo.context()? .for_signature(sig_version); ctx.update(&salt); cookie.sig_group_mut().hashes.push( HashingMode::for_salt_and_type( ctx, salt, typ)); } break; } } else { break; } } reader = r.get_mut(); } Ok(()) } } #[test] fn one_pass_sig3_parser_test () { use crate::SignatureType; use crate::PublicKeyAlgorithm; // This test assumes that the first packet is a OnePassSig packet. let data = crate::tests::message("signed-1.gpg"); let mut pp = PacketParser::from_bytes(data).unwrap().unwrap(); let p = pp.finish().unwrap(); // eprintln!("packet: {:?}", p); if let &Packet::OnePassSig(ref p) = p { assert_eq!(p.version(), 3); assert_eq!(p.typ(), SignatureType::Binary); assert_eq!(p.hash_algo(), HashAlgorithm::SHA512); assert_eq!(p.pk_algo(), PublicKeyAlgorithm::RSAEncryptSign); assert_eq!(format!("{:X}", p.issuer()), "7223B56678E02528"); assert_eq!(p.last_raw(), 1); } else { panic!("Wrong packet!"); } } impl_parse_with_buffered_reader!( OnePassSig3, |reader| -> Result<Self> { OnePassSig::from_buffered_reader(reader).and_then(|p| match p { OnePassSig::V3(p) => Ok(p), p => Err(Error::InvalidOperation( format!("Not a OnePassSig::V3 packet: {:?}", p)).into()), }) }); impl OnePassSig6 { #[allow(clippy::blocks_in_if_conditions)] fn parse(mut php: PacketHeaderParser) -> Result<PacketParser> { let indent = php.recursion_depth(); tracer!(TRACE, "OnePassSig6", indent); make_php_try!(php); let typ = php_try!(php.parse_u8("type")); let hash_algo = php_try!(php.parse_u8("hash_algo")); let pk_algo = php_try!(php.parse_u8("pk_algo")); let salt_len = php_try!(php.parse_u8("salt_len")); let salt = php_try!(php.parse_bytes("salt", salt_len.into())); let mut issuer = [0u8; 32]; issuer.copy_from_slice(&php_try!(php.parse_bytes("issuer", 32))); let last = php_try!(php.parse_u8("last")); let hash_algo = hash_algo.into(); let typ = typ.into(); let mut sig = OnePassSig6::new(typ, Fingerprint::from_bytes(6, &issuer)?); sig.set_salt(salt.clone()); sig.set_hash_algo(hash_algo); sig.set_pk_algo(pk_algo.into()); sig.set_last_raw(last); let need_hash = HashingMode::for_salt_and_type(hash_algo, &salt, typ); let recursion_depth = php.recursion_depth(); // Check if we are processing a cleartext signed message. let want_hashes_for = if Cookie::processing_csf_message(&php.reader) { HashesFor::CleartextSignature } else { HashesFor::Signature }; // Walk up the reader chain to see if there is already a // hashed reader on level recursion_depth - 1. let done = { let mut done = false; let mut reader : Option<&mut dyn BufferedReader<Cookie>> = Some(&mut php.reader); while let Some(r) = reader { { let cookie = r.cookie_mut(); if let Some(br_level) = cookie.level { if br_level < recursion_depth - 1 { break; } if br_level == recursion_depth - 1 && cookie.hashes_for == want_hashes_for { // We found a suitable hashed reader. if cookie.saw_last { cookie.sig_group_push(); cookie.saw_last = false; cookie.hash_stash = Some(php.header_bytes.clone()); } // Make sure that it uses the required // hash algorithm. if php.state.settings.automatic_hashing && ! cookie.sig_group().hashes.iter() .any(|mode| { mode.map(|ctx| ctx.algo()) == need_hash }) { if let Ok(ctx) = hash_algo.context() { let mut ctx = ctx.for_signature(6); ctx.update(&salt); cookie.sig_group_mut().hashes.push( HashingMode::for_salt_and_type( ctx, &salt, typ) ); } } // Account for this OPS packet. cookie.sig_group_mut().ops_count += 1; // Keep track of the last flag. cookie.saw_last = last > 0; // We're done. done = true; break; } } else { break; } } reader = r.get_mut(); } done }; // Commit here after potentially pushing a signature group. let mut pp = php.ok(Packet::OnePassSig(sig.into()))?; if done { return Ok(pp); } // We create an empty hashed reader even if we don't support // the hash algorithm so that we have something to match // against when we get to the Signature packet. let mut algos = Vec::new(); if pp.state.settings.automatic_hashing && hash_algo.is_supported() { algos.push(HashingMode::for_salt_and_type(hash_algo, &salt, typ)); } // We can't push the HashedReader on the BufferedReader stack: // when we finish processing this OnePassSig packet, it will // be popped. Instead, we need to insert it at the next // higher level. Unfortunately, this isn't possible. But, // since we're done reading the current packet, we can pop the // readers associated with it, and then push the HashedReader. // This is a bit of a layering violation, but I (Neal) can't // think of a more elegant solution. assert!(pp.reader.cookie_ref().level <= Some(recursion_depth)); let (fake_eof, reader) = buffered_reader_stack_pop(Box::new(pp.take_reader()), recursion_depth)?; // We only pop the buffered readers for the OPS, and we // (currently) never use a fake eof for OPS packets. assert!(! fake_eof); let mut reader = HashedReader::new( reader, want_hashes_for, algos)?; reader.cookie_mut().level = Some(recursion_depth - 1); // Account for this OPS packet. reader.cookie_mut().sig_group_mut().ops_count += 1; // Keep track of the last flag. reader.cookie_mut().saw_last = last > 0; t!("Pushed a hashed reader, level {:?}", reader.cookie_mut().level); // We add an empty limitor on top of the hashed reader, // because when we are done processing a packet, // PacketParser::finish discards any unread data from the top // reader. Since the top reader is the HashedReader, this // discards any following packets. To prevent this, we push a // Limitor on the reader stack. let mut reader = buffered_reader::Limitor::with_cookie( reader, 0, Cookie::default()); reader.cookie_mut().level = Some(recursion_depth); pp.reader = Box::new(reader); Ok(pp) } } impl_parse_with_buffered_reader!( OnePassSig6, |reader| -> Result<Self> { OnePassSig::from_buffered_reader(reader).and_then(|p| match p { OnePassSig::V6(p) => Ok(p), p => Err(Error::InvalidOperation( format!("Not a OnePassSig::V6 packet: {:?}", p)).into()), }) }); #[test] fn one_pass_sig_test () { struct Test<'a> { filename: &'a str, digest_prefix: Vec<[u8; 2]>, } let tests = [ Test { filename: "signed-1.gpg", digest_prefix: vec![ [ 0x83, 0xF5 ] ], }, Test { filename: "signed-2-partial-body.gpg", digest_prefix: vec![ [ 0x2F, 0xBE ] ], }, Test { filename: "signed-3-partial-body-multiple-sigs.gpg", digest_prefix: vec![ [ 0x29, 0x64 ], [ 0xff, 0x7d ] ], }, ]; for test in tests.iter() { eprintln!("Trying {}...", test.filename); let mut ppr = PacketParserBuilder::from_bytes( crate::tests::message(test.filename)) .expect(&format!("Reading {}", test.filename)[..]) .build().unwrap(); let mut one_pass_sigs = 0; let mut sigs = 0; while let PacketParserResult::Some(pp) = ppr { if let Packet::OnePassSig(_) = pp.packet { one_pass_sigs += 1; } else if let Packet::Signature(ref sig) = pp.packet { eprintln!(" {}:\n prefix: expected: {}, in sig: {}", test.filename, crate::fmt::to_hex(&test.digest_prefix[sigs][..], false), crate::fmt::to_hex(sig.digest_prefix(), false)); eprintln!(" computed hash: {}", crate::fmt::to_hex(sig.computed_digest().unwrap(), false)); assert_eq!(&test.digest_prefix[sigs], sig.digest_prefix()); assert_eq!(&test.digest_prefix[sigs][..], &sig.computed_digest().unwrap()[..2]); sigs += 1; } else if one_pass_sigs > 0 { assert_eq!(one_pass_sigs, test.digest_prefix.len(), "Number of OnePassSig packets does not match \ number of expected OnePassSig packets."); } ppr = pp.recurse().expect("Parsing message").1; } assert_eq!(one_pass_sigs, sigs, "Number of OnePassSig packets does not match \ number of signature packets."); eprintln!("done."); } } // Key::parse doesn't actually use the Key type parameters. So, we // can just set them to anything. This avoids the caller having to // set them to something. impl Key<key::UnspecifiedParts, key::UnspecifiedRole> { /// Parses the body of a public key, public subkey, secret key or /// secret subkey packet. fn parse(mut php: PacketHeaderParser) -> Result<PacketParser> { tracer!(TRACE, "Key::parse", php.recursion_depth()); make_php_try!(php); let tag = php.header.ctb().tag(); assert!(tag == Tag::Reserved || tag == Tag::PublicKey || tag == Tag::PublicSubkey || tag == Tag::SecretKey || tag == Tag::SecretSubkey); let version = php_try!(php.parse_u8("version")); match version { 4 => Key4::parse(php), 6 => Key6::parse(php), _ => php.fail("unknown version"), } } /// Returns whether the data appears to be a key (no promises). fn plausible<C, T>(bio: &mut buffered_reader::Dup<T, C>, header: &Header) -> Result<()> where T: BufferedReader<C>, C: fmt::Debug + Send + Sync { // The packet's header is 6 bytes. if let BodyLength::Full(len) = header.length() { if *len < 6 { // Much too short. return Err(Error::MalformedPacket( format!("Packet too short ({} bytes)", len)).into()); } } else { return Err( Error::MalformedPacket( format!("Unexpected body length encoding: {:?}", header.length())).into()); } // Make sure we have a minimum header. let data = bio.data(6)?; if data.len() < 6 { return Err( Error::MalformedPacket("Short read".into()).into()); } // Assume unknown == bad. let version = data[0]; match version { 4 => Key4::plausible(bio, header), 6 => Key6::plausible(bio, header), n => Err(Error::MalformedPacket( format!("Unknown version {}", n)).into()), } } } // Key4::parse doesn't actually use the Key4 type parameters. So, we // can just set them to anything. This avoids the caller having to // set them to something. impl Key4<key::UnspecifiedParts, key::UnspecifiedRole> { /// Parses the body of a public key, public subkey, secret key or /// secret subkey packet. fn parse(mut php: PacketHeaderParser) -> Result<PacketParser> { tracer!(TRACE, "Key4::parse", php.recursion_depth()); make_php_try!(php); let tag = php.header.ctb().tag(); assert!(tag == Tag::Reserved || tag == Tag::PublicKey || tag == Tag::PublicSubkey || tag == Tag::SecretKey || tag == Tag::SecretSubkey); let creation_time = php_try!(php.parse_be_u32("creation_time")); let pk_algo: PublicKeyAlgorithm = php_try!(php.parse_u8("pk_algo")).into(); let mpis = php_try!(PublicKey::_parse(pk_algo, &mut php)); let secret = if let Ok(s2k_usage) = php.parse_u8("s2k_usage") { use crypto::mpi; let sec = match s2k_usage { // Unencrypted 0 => { let sec = php_try!( mpi::SecretKeyMaterial::_parse( pk_algo, &mut php, Some(mpi::SecretKeyChecksum::Sum16))); sec.into() } // AEAD encrypted secrets. 253 => { let sym_algo: SymmetricAlgorithm = php_try!(php.parse_u8("sym_algo")).into(); let aead_algo: AEADAlgorithm = php_try!(php.parse_u8("aead_algo")).into(); let s2k = php_try!(S2K::parse_v4(&mut php)); let aead_iv = php_try!(php.parse_bytes( "aead_iv", // If we don't know the AEAD mode, we won't // know the nonce size, and all the IV will // end up in the ciphertext. This is an // inherent limitation of the v4 packet // format. aead_algo.nonce_size().unwrap_or(0))) .into(); let cipher = php_try!(php.parse_bytes_eof("encrypted_mpis")) .into_boxed_slice(); crate::packet::key::Encrypted::new_aead( s2k, sym_algo, aead_algo, aead_iv, cipher).into() }, // Encrypted, whether we support the S2K method or not. _ => { let sk: SymmetricAlgorithm = match s2k_usage { 254 | 255 => php_try!(php.parse_u8("sym_algo")).into(), _ => s2k_usage.into(), }; let s2k = match s2k_usage { 254 | 255 => php_try!(S2K::parse_v4(&mut php)), _ => { #[allow(deprecated)] S2K::Implicit }, }; let s2k_supported = s2k.is_supported(); let cipher = php_try!(php.parse_bytes_eof("encrypted_mpis")) .into_boxed_slice(); crate::packet::key::Encrypted::new_raw( s2k, sk, match s2k_usage { 254 => Some(mpi::SecretKeyChecksum::SHA1), 255 => Some(mpi::SecretKeyChecksum::Sum16), _ => Some(mpi::SecretKeyChecksum::Sum16), }, if s2k_supported { Ok((0, cipher)) } else { Err(cipher) }, ).into() } }; Some(sec) } else { None }; let have_secret = secret.is_some(); if have_secret { if tag == Tag::PublicKey || tag == Tag::PublicSubkey { return php.error(Error::MalformedPacket( format!("Unexpected secret key found in {:?} packet", tag) ).into()); } } else if tag == Tag::SecretKey || tag == Tag::SecretSubkey { return php.error(Error::MalformedPacket( format!("Expected secret key in {:?} packet", tag) ).into()); } fn k<R>(creation_time: u32, pk_algo: PublicKeyAlgorithm, mpis: PublicKey) -> Result<Key4<key::PublicParts, R>> where R: key::KeyRole { Key4::make(creation_time, pk_algo, mpis, None) } fn s<R>(creation_time: u32, pk_algo: PublicKeyAlgorithm, mpis: PublicKey, secret: SecretKeyMaterial) -> Result<Key4<key::SecretParts, R>> where R: key::KeyRole { Key4::make(creation_time, pk_algo, mpis, Some(secret)) } let tag = php.header.ctb().tag(); let p : Packet = match tag { // For the benefit of Key::from_bytes. Tag::Reserved => if have_secret { Packet::SecretKey( php_try!(s(creation_time, pk_algo, mpis, secret.unwrap())) .into()) } else { Packet::PublicKey( php_try!(k(creation_time, pk_algo, mpis)).into()) }, Tag::PublicKey => Packet::PublicKey( php_try!(k(creation_time, pk_algo, mpis)).into()), Tag::PublicSubkey => Packet::PublicSubkey( php_try!(k(creation_time, pk_algo, mpis)).into()), Tag::SecretKey => Packet::SecretKey( php_try!(s(creation_time, pk_algo, mpis, secret.unwrap())) .into()), Tag::SecretSubkey => Packet::SecretSubkey( php_try!(s(creation_time, pk_algo, mpis, secret.unwrap())) .into()), _ => unreachable!(), }; php.ok(p) } /// Returns whether the data appears to be a version 4 key (no /// promises). fn plausible<C>(bio: &mut dyn BufferedReader<C>, _: &Header) -> Result<()> where C: fmt::Debug + Send + Sync, { // Make sure we have a minimum header. let data = bio.data(6)?; if data.len() < 6 { return Err( Error::MalformedPacket("Short read".into()).into()); } // Assume unknown == bad. let version = data[0]; let pk_algo : PublicKeyAlgorithm = data[5].into(); if version == 4 && !matches!(pk_algo, PublicKeyAlgorithm::Unknown(_)) { Ok(()) } else { Err(Error::MalformedPacket("Invalid or unsupported data".into()) .into()) } } } // Key6::parse doesn't actually use the Key6 type parameters. So, we // can just set them to anything. This avoids the caller having to // set them to something. impl Key6<key::UnspecifiedParts, key::UnspecifiedRole> { /// Parses the body of a public key, public subkey, secret key or /// secret subkey packet. fn parse(mut php: PacketHeaderParser) -> Result<PacketParser> { tracer!(TRACE, "Key6::parse", php.recursion_depth()); make_php_try!(php); let tag = php.header.ctb().tag(); assert!(tag == Tag::Reserved || tag == Tag::PublicKey || tag == Tag::PublicSubkey || tag == Tag::SecretKey || tag == Tag::SecretSubkey); let creation_time = php_try!(php.parse_be_u32("creation_time")); let pk_algo: PublicKeyAlgorithm = php_try!(php.parse_u8("pk_algo")).into(); let public_len = php_try!(php.parse_be_u32("public_len")); let public_mpis = php.variable_sized_field_start("public_mpis", public_len); let mpis = php_try!(PublicKey::_parse(pk_algo, &mut php)); php_try!(php.variable_sized_field_end(public_mpis)); let secret = if let Ok(s2k_usage) = php.parse_u8("s2k_usage") { use crypto::mpi; let sec = match s2k_usage { // Unencrypted secrets. 0 => { let sec = php_try!( mpi::SecretKeyMaterial::_parse( pk_algo, &mut php, None)); sec.into() }, // Encrypted & MD5 for key derivation: unsupported. // // XXX: Technically, we could/should parse them, then // fail later. But, this limitation has been with us // since the beginning, and no-one complained. 1..=252 => { return php.fail("unsupported secret key encryption"); }, // AEAD encrypted secrets. 253 => { let parameters_len = php_try!(php.parse_u8("parameters_len")); let parameters = php.variable_sized_field_start("parameters", parameters_len); let sym_algo: SymmetricAlgorithm = php_try!(php.parse_u8("sym_algo")).into(); let aead_algo: AEADAlgorithm = php_try!(php.parse_u8("aead_algo")).into(); let s2k_len = php_try!(php.parse_u8("s2k_len")); let s2k_params = php.variable_sized_field_start("s2k_params", s2k_len); let s2k = php_try!(S2K::parse_v6(&mut php, s2k_len as _)); php_try!(php.variable_sized_field_end(s2k_params)); let aead_iv = php_try!(php.parse_bytes( "aead_iv", php.variable_sized_field_remaining(&parameters))) .into(); php_try!(php.variable_sized_field_end(parameters)); let cipher = php_try!(php.parse_bytes_eof("encrypted_mpis")) .into_boxed_slice(); crate::packet::key::Encrypted::new_aead( s2k, sym_algo, aead_algo, aead_iv, cipher).into() }, // Encrypted secrets. 254 | 255 => { let parameters_len = php_try!(php.parse_u8("parameters_len")); let parameters = php.variable_sized_field_start("parameters", parameters_len); let sym_algo: SymmetricAlgorithm = php_try!(php.parse_u8("sym_algo")).into(); let s2k_len = php_try!(php.parse_u8("s2k_len")); let s2k_params = php.variable_sized_field_start("s2k_params", s2k_len); let s2k = php_try!(S2K::parse_v6(&mut php, s2k_len as _)); php_try!(php.variable_sized_field_end(s2k_params)); // The "IV" is part of the sized parameter field. let cfb_iv = php_try!(php.parse_bytes( "cfb_iv", php.variable_sized_field_remaining(&parameters))); php_try!(php.variable_sized_field_end(parameters)); let cipher = php_try!(php.parse_bytes_eof("encrypted_mpis")); // But we store "IV" and ciphertext as one. let cfb_iv_len = cfb_iv.len(); let mut combined_ciphertext = cfb_iv; combined_ciphertext.extend_from_slice(&cipher); crate::packet::key::Encrypted::new_raw( s2k, sym_algo, if s2k_usage == 254 { Some(mpi::SecretKeyChecksum::SHA1) } else { Some(mpi::SecretKeyChecksum::Sum16) }, Ok((cfb_iv_len, combined_ciphertext.into()))) .into() }, }; Some(sec) } else { None }; let have_secret = secret.is_some(); if have_secret { if tag == Tag::PublicKey || tag == Tag::PublicSubkey { return php.error(Error::MalformedPacket( format!("Unexpected secret key found in {:?} packet", tag) ).into()); } } else if tag == Tag::SecretKey || tag == Tag::SecretSubkey { return php.error(Error::MalformedPacket( format!("Expected secret key in {:?} packet", tag) ).into()); } fn k<R>(creation_time: u32, pk_algo: PublicKeyAlgorithm, mpis: PublicKey) -> Result<Key6<key::PublicParts, R>> where R: key::KeyRole { Key6::make(creation_time, pk_algo, mpis, None) } fn s<R>(creation_time: u32, pk_algo: PublicKeyAlgorithm, mpis: PublicKey, secret: SecretKeyMaterial) -> Result<Key6<key::SecretParts, R>> where R: key::KeyRole { Key6::make(creation_time, pk_algo, mpis, Some(secret)) } let tag = php.header.ctb().tag(); let p : Packet = match tag { // For the benefit of Key::from_bytes. Tag::Reserved => if have_secret { Packet::SecretKey( php_try!(s(creation_time, pk_algo, mpis, secret.unwrap())) .into()) } else { Packet::PublicKey( php_try!(k(creation_time, pk_algo, mpis)).into()) }, Tag::PublicKey => Packet::PublicKey( php_try!(k(creation_time, pk_algo, mpis)).into()), Tag::PublicSubkey => Packet::PublicSubkey( php_try!(k(creation_time, pk_algo, mpis)).into()), Tag::SecretKey => Packet::SecretKey( php_try!(s(creation_time, pk_algo, mpis, secret.unwrap())) .into()), Tag::SecretSubkey => Packet::SecretSubkey( php_try!(s(creation_time, pk_algo, mpis, secret.unwrap())) .into()), _ => unreachable!(), }; php.ok(p) } /// Returns whether the data appears to be a version 6 key (no /// promises). fn plausible<C>(bio: &mut dyn BufferedReader<C>, header: &Header) -> Result<()> where C: fmt::Debug + Send + Sync, { // Make sure we have a minimum header. const MIN: usize = 10; let data = bio.data(MIN)?; if data.len() < MIN { return Err( Error::MalformedPacket("Short read".into()).into()); } // Assume unknown == bad. let version = data[0]; let creation_time = u32::from_be_bytes(data[1..5].try_into().unwrap()); let pk_algo: PublicKeyAlgorithm = data[5].into(); let public_len = u32::from_be_bytes(data[6..10].try_into().unwrap()); /// The unix time at which RFC9580 was published, 2024-07-31. const RFC9580_PUBLICATION_TIME: u32 = 1722376800; if version == 6 && !matches!(pk_algo, PublicKeyAlgorithm::Unknown(_)) && creation_time >= RFC9580_PUBLICATION_TIME && match header.length() { BodyLength::Full(len) => public_len < *len, _ => false, } { Ok(()) } else { Err(Error::MalformedPacket("Invalid or unsupported data".into()) .into()) } } } use key::UnspecifiedKey; impl_parse_with_buffered_reader!( UnspecifiedKey, |br| -> Result<Self> { let parser = PacketHeaderParser::new_naked(br); let mut pp = Self::parse(parser)?; pp.buffer_unread_content()?; match pp.next()? { (Packet::PublicKey(o), PacketParserResult::EOF(_)) => Ok(o.into()), (Packet::PublicSubkey(o), PacketParserResult::EOF(_)) => Ok(o.into()), (Packet::SecretKey(o), PacketParserResult::EOF(_)) => Ok(o.into()), (Packet::SecretSubkey(o), PacketParserResult::EOF(_)) => Ok(o.into()), (Packet::Unknown(u), PacketParserResult::EOF(_)) => Err(u.into_error()), (p, PacketParserResult::EOF(_)) => Err(Error::InvalidOperation( format!("Not a Key packet: {:?}", p)).into()), (_, PacketParserResult::Some(_)) => Err(Error::InvalidOperation( "Excess data after packet".into()).into()), } }); impl Trust { /// Parses the body of a trust packet. fn parse(mut php: PacketHeaderParser) -> Result<PacketParser> { tracer!(TRACE, "Trust::parse", php.recursion_depth()); make_php_try!(php); let value = php_try!(php.parse_bytes_eof("value")); php.ok(Packet::Trust(Trust::from(value))) } } impl_parse_with_buffered_reader!(Trust); impl UserID { /// Parses the body of a user id packet. fn parse(mut php: PacketHeaderParser) -> Result<PacketParser> { tracer!(TRACE, "UserID::parse", php.recursion_depth()); make_php_try!(php); let value = php_try!(php.parse_bytes_eof("value")); php.ok(Packet::UserID(UserID::from(value))) } } impl_parse_with_buffered_reader!(UserID); impl UserAttribute { /// Parses the body of a user attribute packet. fn parse(mut php: PacketHeaderParser) -> Result<PacketParser> { tracer!(TRACE, "UserAttribute::parse", php.recursion_depth()); make_php_try!(php); let value = php_try!(php.parse_bytes_eof("value")); php.ok(Packet::UserAttribute(UserAttribute::from(value))) } } impl_parse_with_buffered_reader!(UserAttribute); impl Marker { /// Parses the body of a marker packet. fn parse(mut php: PacketHeaderParser) -> Result<PacketParser> { tracer!(TRACE, "Marker::parse", php.recursion_depth()); make_php_try!(php); let marker = php_try!(php.parse_bytes("marker", Marker::BODY.len())); if &marker[..] == Marker::BODY { php.ok(Marker::default().into()) } else { php.fail("invalid marker") } } /// Returns whether the data is a marker packet. fn plausible<C, T>(bio: &mut buffered_reader::Dup<T, C>, header: &Header) -> Result<()> where T: BufferedReader<C>, C: fmt::Debug + Send + Sync { if let BodyLength::Full(len) = header.length() { let len = *len; if len as usize != Marker::BODY.len() { return Err(Error::MalformedPacket( format!("Unexpected packet length {}", len)).into()); } } else { return Err(Error::MalformedPacket( format!("Unexpected body length encoding: {:?}", header.length())).into()); } // Check the body. let data = bio.data(Marker::BODY.len())?; if data.len() < Marker::BODY.len() { return Err(Error::MalformedPacket("Short read".into()).into()); } if data == Marker::BODY { Ok(()) } else { Err(Error::MalformedPacket("Invalid or unsupported data".into()) .into()) } } } impl_parse_with_buffered_reader!(Marker); impl Literal { /// Parses the body of a literal packet. /// /// Condition: Hashing has been disabled by the callee. fn parse(mut php: PacketHeaderParser) -> Result<PacketParser> { tracer!(TRACE, "Literal::parse", php.recursion_depth()); make_php_try!(php); // Directly hashing a literal data packet is... strange. // Neither the packet's header, the packet's meta-data nor the // length encoding information is included in the hash. let format = php_try!(php.parse_u8("format")); let filename_len = php_try!(php.parse_u8("filename_len")); let filename = if filename_len > 0 { Some(php_try!(php.parse_bytes("filename", filename_len as usize))) } else { None }; let date = php_try!(php.parse_be_u32("date")); // The header is consumed while hashing is disabled. let recursion_depth = php.recursion_depth(); let mut literal = Literal::new(format.into()); if let Some(filename) = filename { literal.set_filename(&filename) .expect("length checked above"); } literal.set_date( Some(std::time::SystemTime::from(Timestamp::from(date))))?; let mut pp = php.ok(Packet::Literal(literal))?; // Enable hashing of the body. Cookie::hashing(pp.mut_reader(), Hashing::Enabled, recursion_depth - 1); Ok(pp) } } impl_parse_with_buffered_reader!(Literal); #[test] fn literal_parser_test () { use crate::types::DataFormat; { let data = crate::tests::message("literal-mode-b.gpg"); let mut pp = PacketParser::from_bytes(data).unwrap().unwrap(); assert_eq!(pp.header.length(), &BodyLength::Full(18)); let content = pp.steal_eof().unwrap(); let p = pp.finish().unwrap(); // eprintln!("{:?}", p); if let &Packet::Literal(ref p) = p { assert_eq!(p.format(), DataFormat::Binary); assert_eq!(p.filename().unwrap()[..], b"foobar"[..]); assert_eq!(p.date().unwrap(), Timestamp::from(1507458744).into()); assert_eq!(content, b"FOOBAR"); } else { panic!("Wrong packet!"); } } { let data = crate::tests::message("literal-mode-t-partial-body.gpg"); let mut pp = PacketParser::from_bytes(data).unwrap().unwrap(); assert_eq!(pp.header.length(), &BodyLength::Partial(4096)); let content = pp.steal_eof().unwrap(); let p = pp.finish().unwrap(); if let &Packet::Literal(ref p) = p { #[allow(deprecated)] { assert_eq!(p.format(), DataFormat::Text); } assert_eq!(p.filename().unwrap()[..], b"manifesto.txt"[..]); assert_eq!(p.date().unwrap(), Timestamp::from(1508000649).into()); let expected = crate::tests::manifesto(); assert_eq!(&content[..], expected); } else { panic!("Wrong packet!"); } } } impl CompressedData { /// Parses the body of a compressed data packet. fn parse(mut php: PacketHeaderParser) -> Result<PacketParser> { let recursion_depth = php.recursion_depth(); tracer!(TRACE, "CompressedData::parse", recursion_depth); make_php_try!(php); let algo: CompressionAlgorithm = php_try!(php.parse_u8("algo")).into(); let recursion_depth = php.recursion_depth(); let mut pp = php.ok(Packet::CompressedData(CompressedData::new(algo)))?; #[allow(unreachable_patterns)] match algo { CompressionAlgorithm::Uncompressed => (), #[cfg(feature = "compression-deflate")] CompressionAlgorithm::Zip | CompressionAlgorithm::Zlib => (), #[cfg(feature = "compression-bzip2")] CompressionAlgorithm::BZip2 => (), _ => { // We don't know or support this algorithm. Return a // CompressedData packet without pushing a filter, so // that it has an opaque body. t!("Algorithm {} unknown or unsupported.", algo); return Ok(pp.set_processed(false)); }, } t!("Pushing a decompressor for {}, recursion depth = {:?}.", algo, recursion_depth); let reader = pp.take_reader(); let reader = match algo { CompressionAlgorithm::Uncompressed => { if TRACE { eprintln!("CompressedData::parse(): Actually, no need \ for a compression filter: this is an \ \"uncompressed compression packet\"."); } let _ = recursion_depth; reader }, #[cfg(feature = "compression-deflate")] CompressionAlgorithm::Zip => Box::new(buffered_reader::Deflate::with_cookie( reader, Cookie::new(recursion_depth))), #[cfg(feature = "compression-deflate")] CompressionAlgorithm::Zlib => Box::new(buffered_reader::Zlib::with_cookie( reader, Cookie::new(recursion_depth))), #[cfg(feature = "compression-bzip2")] CompressionAlgorithm::BZip2 => Box::new(buffered_reader::Bzip::with_cookie( reader, Cookie::new(recursion_depth))), _ => unreachable!(), // Validated above. }; pp.set_reader(reader); Ok(pp) } } impl_parse_with_buffered_reader!(CompressedData); #[cfg(any(feature = "compression-deflate", feature = "compression-bzip2"))] #[test] fn compressed_data_parser_test () { use crate::types::DataFormat; let expected = crate::tests::manifesto(); for i in 1..4 { match CompressionAlgorithm::from(i) { #[cfg(feature = "compression-deflate")] CompressionAlgorithm::Zip | CompressionAlgorithm::Zlib => (), #[cfg(feature = "compression-bzip2")] CompressionAlgorithm::BZip2 => (), _ => continue, } let pp = PacketParser::from_bytes(crate::tests::message( &format!("compressed-data-algo-{}.gpg", i))).unwrap().unwrap(); // We expect a compressed packet containing a literal data // packet, and that is it. if let Packet::CompressedData(ref compressed) = pp.packet { assert_eq!(compressed.algo(), i.into()); } else { panic!("Wrong packet!"); } let ppr = pp.recurse().unwrap().1; // ppr should be the literal data packet. let mut pp = ppr.unwrap(); // It is a child. assert_eq!(pp.recursion_depth(), 1); let content = pp.steal_eof().unwrap(); let (literal, ppr) = pp.recurse().unwrap(); if let Packet::Literal(literal) = literal { assert_eq!(literal.filename(), None); assert_eq!(literal.format(), DataFormat::Binary); assert_eq!(literal.date().unwrap(), Timestamp::from(1509219866).into()); assert_eq!(content, expected.to_vec()); } else { panic!("Wrong packet!"); } // And, we're done... assert!(ppr.is_eof()); } } impl SKESK { /// Parses the body of an SK-ESK packet. fn parse(mut php: PacketHeaderParser) -> Result<PacketParser> { tracer!(TRACE, "SKESK::parse", php.recursion_depth()); make_php_try!(php); let version = php_try!(php.parse_u8("version")); match version { 4 => SKESK4::parse(php), 6 => SKESK6::parse(php), _ => php.fail("unknown version"), } } } impl SKESK4 { /// Parses the body of an SK-ESK packet. fn parse(mut php: PacketHeaderParser) -> Result<PacketParser> { tracer!(TRACE, "SKESK4::parse", php.recursion_depth()); make_php_try!(php); let sym_algo = php_try!(php.parse_u8("sym_algo")); let s2k = php_try!(S2K::parse_v4(&mut php)); let s2k_supported = s2k.is_supported(); let esk = php_try!(php.parse_bytes_eof("esk")); let skesk = php_try!(SKESK4::new_raw( sym_algo.into(), s2k, if s2k_supported || esk.is_empty() { Ok(if ! esk.is_empty() { Some(esk.into()) } else { None }) } else { Err(esk.into()) }, )); php.ok(skesk.into()) } } impl SKESK6 { /// Parses the body of an SK-ESK packet. fn parse(mut php: PacketHeaderParser) -> Result<PacketParser> { tracer!(TRACE, "SKESK5::parse", php.recursion_depth()); make_php_try!(php); // Octet count of the following 5 fields. let parameter_len = php_try!(php.parse_u8_len("parameter_len")); if parameter_len < 1 + 1 + 1 + 2 /* S2K */ + 12 /* IV */ { return php.fail("expected at least 16 parameter octets"); } let sym_algo: SymmetricAlgorithm = php_try!(php.parse_u8("sym_algo")).into(); let aead_algo: AEADAlgorithm = php_try!(php.parse_u8("aead_algo")).into(); // The S2K object's length and the S2K. let s2k_len = php_try!(php.parse_u8_len("s2k_len")); if parameter_len < 1 + 1 + 1 + s2k_len + 12 /* IV */ { return php.fail("S2K overflows parameter count"); } let s2k = php_try!(S2K::parse_v6(&mut php, s2k_len as u8)); // And the IV. let iv = if let Some(iv_len) = parameter_len.checked_sub(1 + 1 + 1 + s2k_len) { php_try!(php.parse_bytes("iv", iv_len as usize)).into() } else { return php.fail("IV overflows parameter count"); }; // Finally, the ESK including the AEAD tag. let esk = php_try!(php.parse_bytes_eof("esk")).into(); let skesk = php_try!(SKESK6::new( sym_algo, aead_algo, s2k, iv, esk, )); php.ok(skesk.into()) } } impl_parse_with_buffered_reader!(SKESK); #[test] fn skesk_parser_test() { use crate::crypto::Password; struct Test<'a> { filename: &'a str, s2k: S2K, cipher_algo: SymmetricAlgorithm, password: Password, key_hex: &'a str, } let tests = [ Test { filename: "s2k/mode-3-encrypted-key-password-bgtyhn.gpg", cipher_algo: SymmetricAlgorithm::AES128, s2k: S2K::Iterated { hash: HashAlgorithm::SHA1, salt: [0x82, 0x59, 0xa0, 0x6e, 0x98, 0xda, 0x94, 0x1c], hash_bytes: S2K::decode_count(238), }, password: "bgtyhn".into(), key_hex: "474E5C373BA18AF0A499FCAFE6093F131DF636F6A3812B9A8AE707F1F0214AE9", }, ]; for test in tests.iter() { let pp = PacketParser::from_bytes( crate::tests::message(test.filename)).unwrap().unwrap(); if let Packet::SKESK(SKESK::V4(ref skesk)) = pp.packet { eprintln!("{:?}", skesk); assert_eq!(skesk.symmetric_algo(), test.cipher_algo); assert_eq!(skesk.s2k(), &test.s2k); match skesk.decrypt(&test.password) { Ok((_sym_algo, key)) => { let key = crate::fmt::to_hex(&key[..], false); assert_eq!(&key[..], test.key_hex); } Err(e) => { panic!("No session key, got: {:?}", e); } } } else { panic!("Wrong packet!"); } } } impl SEIP { /// Parses the body of a SEIP packet. fn parse(mut php: PacketHeaderParser) -> Result<PacketParser> { tracer!(TRACE, "SEIP::parse", php.recursion_depth()); make_php_try!(php); let version = php_try!(php.parse_u8("version")); match version { 1 => SEIP1::parse(php), 2 => SEIP2::parse(php), _ => php.fail("unknown version"), } } } impl_parse_with_buffered_reader!(SEIP); impl SEIP1 { /// Parses the body of a SEIP1 packet. fn parse(php: PacketHeaderParser) -> Result<PacketParser> { php.ok(SEIP1::new().into()) .map(|pp| pp.set_processed(false)) } } impl SEIP2 { /// Parses the body of a SEIP2 packet. fn parse(mut php: PacketHeaderParser) -> Result<PacketParser> { tracer!(TRACE, "SEIP2::parse", php.recursion_depth()); make_php_try!(php); let cipher: SymmetricAlgorithm = php_try!(php.parse_u8("sym_algo")).into(); let aead: AEADAlgorithm = php_try!(php.parse_u8("aead_algo")).into(); let chunk_size = php_try!(php.parse_u8("chunk_size")); // An implementation MUST accept chunk size octets with values // from 0 to 16. An implementation MUST NOT create data with a // chunk size octet value larger than 16 (4 MiB chunks). if chunk_size > 16 { return php.fail("unsupported chunk size"); } let chunk_size: u64 = 1 << (chunk_size + 6); let salt_v = php_try!(php.parse_bytes("salt", 32)); let mut salt = [0u8; 32]; salt.copy_from_slice(&salt_v); let seip2 = php_try!(Self::new(cipher, aead, chunk_size, salt)); php.ok(seip2.into()).map(|pp| pp.set_processed(false)) } } impl MDC { /// Parses the body of an MDC packet. fn parse(mut php: PacketHeaderParser) -> Result<PacketParser> { tracer!(TRACE, "MDC::parse", php.recursion_depth()); make_php_try!(php); // Find the HashedReader pushed by the containing SEIP packet. // In a well-formed message, this will be the outermost // HashedReader on the BufferedReader stack: we pushed it // there when we started decrypting the SEIP packet, and an // MDC packet is the last packet in a SEIP container. // Nevertheless, we take some basic precautions to check // whether it is really the matching HashedReader. let mut computed_digest : [u8; 20] = Default::default(); { let mut r : Option<&mut dyn BufferedReader<Cookie>> = Some(&mut php.reader); while let Some(bio) = r { { let state = bio.cookie_mut(); if state.hashes_for == HashesFor::MDC { if !state.sig_group().hashes.is_empty() { let h = state.sig_group_mut().hashes .iter_mut().find_map( |mode| if matches!(mode.map(|ctx| ctx.algo()), HashingMode::Binary(_, HashAlgorithm::SHA1)) { Some(mode.as_mut()) } else { None }).unwrap(); let _ = h.digest(&mut computed_digest); } // If the outermost HashedReader is not the // matching HashedReader, then the message is // malformed. break; } } r = bio.get_mut(); } } let mut digest: [u8; 20] = Default::default(); digest.copy_from_slice(&php_try!(php.parse_bytes("digest", 20))); #[allow(deprecated)] php.ok(Packet::MDC(MDC::new(digest, computed_digest))) } } impl_parse_with_buffered_reader!(MDC); impl Padding { /// Parses the body of a padding packet. fn parse(mut php: PacketHeaderParser) -> Result<PacketParser> { tracer!(TRACE, "Padding::parse", php.recursion_depth()); make_php_try!(php); // XXX: I don't think we should capture the body. let value = php_try!(php.parse_bytes_eof("value")); php.ok(Packet::Padding(Padding::from(value))) } } impl_parse_with_buffered_reader!(Padding); impl MPI { /// Parses an OpenPGP MPI. /// /// See [Section 3.2 of RFC 9580] for details. /// /// [Section 3.2 of RFC 9580]: https://www.rfc-editor.org/rfc/rfc9580.html#section-3.2 fn parse(name_len: &'static str, name: &'static str, php: &mut PacketHeaderParser<'_>) -> Result<Self> { Ok(MPI::parse_common(name_len, name, false, false, php)?.into()) } /// Parses an OpenPGP MPI. /// /// If `parsing_secrets` is `true`, errors are normalized as not /// to reveal parts of the plaintext to the caller. /// /// If `lenient_parsing` is `true`, this function will accept MPIs /// that are not well-formed (notably, issues related to leading /// zeros). fn parse_common( name_len: &'static str, name: &'static str, parsing_secrets: bool, lenient_parsing: bool, php: &mut PacketHeaderParser<'_>) -> Result<Vec<u8>> { // When we are parsing secrets, we don't want to leak it // accidentally by revealing it in error messages, or indeed // by the kind of error. // // All errors returned by this function that are depend on // secret data must be uniform and return the following error. // We make an exception for i/o errors, which may reveal // truncation, because swallowing i/o errors may be very // confusing when diagnosing errors, and we don't consider the // length of the value to be confidential as it can also be // inferred from the size of the ciphertext. let uniform_error_for_secrets = |e: Error| { if parsing_secrets { Err(Error::MalformedMPI("Details omitted, \ parsing secret".into()).into()) } else { Err(e.into()) } }; // This function is used to parse MPIs from unknown // algorithms, which may use an encoding unknown to us. // Therefore, we need to be extra careful only to consume the // data once we found a well-formed MPI. let bits = { let buf = php.reader.data_hard(2)?; u16::from_be_bytes([buf[0], buf[1]]) as usize }; if bits == 0 { // Now consume the data. php.parse_be_u16(name_len).expect("worked before"); return Ok(vec![]); } let bytes = (bits + 7) / 8; let value = { let buf = php.reader.data_hard(2 + bytes)?; Vec::from(&buf[2..2 + bytes]) }; let unused_bits = bytes * 8 - bits; assert_eq!(bytes * 8 - unused_bits, bits); // Make sure the unused bits are zeroed. if unused_bits > 0 { let mask = !((1 << (8 - unused_bits)) - 1); let unused_value = value[0] & mask; if unused_value != 0 && ! lenient_parsing { return uniform_error_for_secrets( Error::MalformedMPI( format!("{} unused bits not zeroed: ({:x})", unused_bits, unused_value) )); } } let first_used_bit = 8 - unused_bits; if value[0] & (1 << (first_used_bit - 1)) == 0 && ! lenient_parsing { return uniform_error_for_secrets( Error::MalformedMPI( format!("leading bit is not set: \ expected bit {} to be set in {:8b} ({:x})", first_used_bit, value[0], value[0]) )); } // Now consume the data. Note: we avoid using parse_bytes // here because MPIs may contain secrets, and we don't want to // casually leak them into the heap. Also, we avoid doing a // heap allocation. php.reader.consume(2 + bytes); // Now fix the map. php.field(name_len, 2); php.field(name, bytes); Ok(value) } } impl_parse_with_buffered_reader!( MPI, |bio: Box<dyn BufferedReader<Cookie>>| -> Result<Self> { let mut parser = PacketHeaderParser::new_naked(bio.into_boxed()); Self::parse("(none_len)", "(none)", &mut parser) }); impl ProtectedMPI { /// Parses an OpenPGP MPI containing secrets. /// /// See [Section 3.2 of RFC 9580] for details. /// /// [Section 3.2 of RFC 9580]: https://www.rfc-editor.org/rfc/rfc9580.html#section-3.2 fn parse(name_len: &'static str, name: &'static str, php: &mut PacketHeaderParser<'_>) -> Result<Self> { // XXX: While lenient parsing seemed like the right thing to // do, this breaks equality and round-tripping: we normalize // the non-canonical encoding, so two distinct wire // representations are folded into one in-core representation. Ok(MPI::parse_common(name_len, name, true, false, php)?.into()) } } impl PKESK { /// Parses the body of an PK-ESK packet. fn parse(mut php: PacketHeaderParser) -> Result<PacketParser> { tracer!(TRACE, "PKESK::parse", php.recursion_depth()); make_php_try!(php); let version = php_try!(php.parse_u8("version")); match version { 3 => PKESK3::parse(php), 6 => PKESK6::parse(php), _ => php.fail("unknown version"), } } } impl_parse_with_buffered_reader!(PKESK); impl PKESK3 { /// Parses the body of an PK-ESK packet. fn parse(mut php: PacketHeaderParser) -> Result<PacketParser> { tracer!(TRACE, "PKESK3::parse", php.recursion_depth()); make_php_try!(php); let keyid = { let mut keyid = [0u8; 8]; keyid.copy_from_slice(&php_try!(php.parse_bytes("keyid", 8))); let keyid = KeyID::from_bytes(&keyid); if keyid.is_wildcard() { None } else { Some(keyid) } }; let pk_algo: PublicKeyAlgorithm = php_try!(php.parse_u8("pk_algo")).into(); if ! pk_algo.for_encryption() { return php.fail("not an encryption algorithm"); } let mpis = crypto::mpi::Ciphertext::_parse(pk_algo, &mut php)?; let pkesk = php_try!(PKESK3::new(keyid, pk_algo, mpis)); php.ok(pkesk.into()) } } impl_parse_with_buffered_reader!( PKESK3, |reader| -> Result<Self> { PKESK::from_buffered_reader(reader).and_then(|p| match p { PKESK::V3(p) => Ok(p), p => Err(Error::InvalidOperation( format!("Not a PKESKv3 packet: {:?}", p)).into()), }) }); impl PKESK6 { /// Parses the body of an PKESKv6 packet. fn parse(mut php: PacketHeaderParser) -> Result<PacketParser> { tracer!(TRACE, "PKESK6::parse", php.recursion_depth()); make_php_try!(php); let fp_len = php_try!(php.parse_u8("recipient_len")); let fingerprint = if fp_len == 0 { None } else { // Get the version and sanity check the length. let fp_version = php_try!(php.parse_u8("recipient_version")); if let Some(expected_length) = match fp_version { 4 => Some(20), 6 => Some(32), _ => None, } { if fp_len - 1 != expected_length { return php.fail("bad fingerprint length"); } } Some(Fingerprint::from_bytes( fp_version, &php_try!(php.parse_bytes("recipient", (fp_len - 1).into())))?) }; let pk_algo: PublicKeyAlgorithm = php_try!(php.parse_u8("pk_algo")).into(); if ! pk_algo.for_encryption() { // XXX return php.fail("not an encryption algorithm"); } let mpis = crypto::mpi::Ciphertext::_parse(pk_algo, &mut php)?; let pkesk = php_try!(PKESK6::new(fingerprint, pk_algo, mpis)); php.ok(pkesk.into()) } } impl_parse_with_buffered_reader!( PKESK6, |reader| -> Result<Self> { PKESK::from_buffered_reader(reader).and_then(|p| match p { PKESK::V6(p) => Ok(p), p => Err(Error::InvalidOperation( format!("Not a PKESKv6 packet: {:?}", p)).into()), }) }); impl_parse_with_buffered_reader!( Packet, |br| -> Result<Self> { let ppr = PacketParserBuilder::from_buffered_reader(br) ?.buffer_unread_content().build()?; let (p, ppr) = match ppr { PacketParserResult::Some(pp) => { pp.next()? }, PacketParserResult::EOF(_) => return Err(Error::InvalidOperation( "Unexpected EOF".into()).into()), }; match (p, ppr) { (p, PacketParserResult::EOF(_)) => Ok(p), (_, PacketParserResult::Some(_)) => Err(Error::InvalidOperation( "Excess data after packet".into()).into()), } }); // State that lives for the life of the packet parser, not the life of // an individual packet. #[derive(Debug)] struct PacketParserState { // The `PacketParser`'s settings settings: PacketParserSettings, /// Whether the packet sequence is a valid OpenPGP Message. message_validator: MessageValidator, /// Whether the packet sequence is a valid OpenPGP keyring. keyring_validator: KeyringValidator, /// Whether the packet sequence is a valid OpenPGP Cert. cert_validator: CertValidator, // Whether this is the first packet in the packet sequence. first_packet: bool, // Whether PacketParser::parse encountered an unrecoverable error. pending_error: Option<anyhow::Error>, } impl PacketParserState { fn new(settings: PacketParserSettings) -> Self { PacketParserState { settings, message_validator: Default::default(), keyring_validator: Default::default(), cert_validator: Default::default(), first_packet: true, pending_error: None, } } } /// A low-level OpenPGP message parser. /// /// A `PacketParser` provides a low-level, iterator-like interface to /// parse OpenPGP messages. /// /// For each iteration, the user is presented with a [`Packet`] /// corresponding to the last packet, a `PacketParser` for the next /// packet, and their positions within the message. /// /// Using the `PacketParser`, the user is able to configure how the /// new packet will be parsed. For instance, it is possible to stream /// the packet's contents (a `PacketParser` implements the /// [`std::io::Read`] and the [`BufferedReader`] traits), buffer them /// within the [`Packet`], or drop them. The user can also decide to /// recurse into the packet, if it is a container, instead of getting /// the following packet. /// /// See the [`PacketParser::next`] and [`PacketParser::recurse`] /// methods for more details. /// /// [`Packet`]: super::Packet /// [`BufferedReader`]: https://docs.rs/buffered-reader/*/buffered_reader/trait.BufferedReader.html /// [`PacketParser::next`]: PacketParser::next() /// [`PacketParser::recurse`]: PacketParser::recurse() /// /// # Examples /// /// These examples demonstrate how to process packet bodies by parsing /// the simplest possible OpenPGP message containing just a single /// literal data packet with the body "Hello world.". There are three /// options. First, the body can be dropped. Second, it can be /// buffered. Lastly, the body can be streamed. In general, /// streaming should be preferred, because it avoids buffering in /// Sequoia. /// /// This example demonstrates simply ignoring the packet body: /// /// ```rust /// # fn main() -> sequoia_openpgp::Result<()> { /// use sequoia_openpgp as openpgp; /// use openpgp::Packet; /// use openpgp::parse::{Parse, PacketParserResult, PacketParser}; /// /// // By default, the `PacketParser` will drop packet bodies. /// let mut ppr = /// PacketParser::from_bytes(b"\xcb\x12b\x00\x00\x00\x00\x00Hello world.")?; /// while let PacketParserResult::Some(pp) = ppr { /// // Get the packet out of the parser and start parsing the next /// // packet, recursing. /// let (packet, next_ppr) = pp.recurse()?; /// ppr = next_ppr; /// /// // Process the packet. /// if let Packet::Literal(literal) = packet { /// // The body was dropped. /// assert_eq!(literal.body(), b""); /// } else { /// unreachable!("We know it is a literal packet."); /// } /// } /// # Ok(()) } /// ``` /// /// This example demonstrates how the body can be buffered by /// configuring the `PacketParser` to buffer all packet bodies: /// /// ```rust /// # fn main() -> sequoia_openpgp::Result<()> { /// use sequoia_openpgp as openpgp; /// use openpgp::Packet; /// use openpgp::parse::{Parse, PacketParserResult, PacketParserBuilder}; /// /// // By default, the `PacketParser` will drop packet bodies. Use a /// // `PacketParserBuilder` to change that. /// let mut ppr = /// PacketParserBuilder::from_bytes( /// b"\xcb\x12b\x00\x00\x00\x00\x00Hello world.")? /// .buffer_unread_content() /// .build()?; /// while let PacketParserResult::Some(pp) = ppr { /// // Get the packet out of the parser and start parsing the next /// // packet, recursing. /// let (packet, next_ppr) = pp.recurse()?; /// ppr = next_ppr; /// /// // Process the packet. /// if let Packet::Literal(literal) = packet { /// // The body was buffered. /// assert_eq!(literal.body(), b"Hello world."); /// } else { /// unreachable!("We know it is a literal packet."); /// } /// } /// # Ok(()) } /// ``` /// /// This example demonstrates how the body can be buffered by /// buffering an individual packet: /// /// ```rust /// # fn main() -> sequoia_openpgp::Result<()> { /// use sequoia_openpgp as openpgp; /// use openpgp::Packet; /// use openpgp::parse::{Parse, PacketParserResult, PacketParser}; /// /// // By default, the `PacketParser` will drop packet bodies. /// let mut ppr = /// PacketParser::from_bytes(b"\xcb\x12b\x00\x00\x00\x00\x00Hello world.")?; /// while let PacketParserResult::Some(mut pp) = ppr { /// if let Packet::Literal(_) = pp.packet { /// // Buffer this packet's body. /// pp.buffer_unread_content()?; /// } /// /// // Get the packet out of the parser and start parsing the next /// // packet, recursing. /// let (packet, next_ppr) = pp.recurse()?; /// ppr = next_ppr; /// /// // Process the packet. /// if let Packet::Literal(literal) = packet { /// // The body was buffered. /// assert_eq!(literal.body(), b"Hello world."); /// } else { /// unreachable!("We know it is a literal packet."); /// } /// } /// # Ok(()) } /// ``` /// /// This example demonstrates how to stream the packet body: /// /// ```rust /// # fn main() -> sequoia_openpgp::Result<()> { /// use std::io::Read; /// /// use sequoia_openpgp as openpgp; /// use openpgp::Packet; /// use openpgp::parse::{Parse, PacketParserResult, PacketParser}; /// /// let mut ppr = /// PacketParser::from_bytes(b"\xcb\x12b\x00\x00\x00\x00\x00Hello world.")?; /// while let PacketParserResult::Some(mut pp) = ppr { /// if let Packet::Literal(_) = pp.packet { /// // Stream the body. /// let mut buf = Vec::new(); /// pp.read_to_end(&mut buf)?; /// assert_eq!(buf, b"Hello world."); /// } else { /// unreachable!("We know it is a literal packet."); /// } /// /// // Get the packet out of the parser and start parsing the next /// // packet, recursing. /// let (packet, next_ppr) = pp.recurse()?; /// ppr = next_ppr; /// /// // Process the packet. /// if let Packet::Literal(literal) = packet { /// // The body was streamed, not buffered. /// assert_eq!(literal.body(), b""); /// } else { /// unreachable!("We know it is a literal packet."); /// } /// } /// # Ok(()) } /// ``` /// /// # Packet Parser Design /// /// There are two major concerns that inform the design of the parsing /// API. /// /// First, when processing a container, it is possible to either /// recurse into the container, and process its children, or treat the /// contents of the container as an opaque byte stream, and process /// the packet following the container. The low-level /// [`PacketParser`] and mid-level [`PacketPileParser`] abstractions /// allow the caller to choose the behavior by either calling the /// [`PacketParser::recurse`] method or the [`PacketParser::next`] /// method, as appropriate. OpenPGP doesn't impose any restrictions /// on the amount of nesting. So, to prevent a denial-of-service /// attack, the parsers don't recurse more than /// [`DEFAULT_MAX_RECURSION_DEPTH`] times, by default. /// /// /// Second, packets can contain an effectively unbounded amount of /// data. To avoid errors due to memory exhaustion, the /// `PacketParser` and [`PacketPileParser`] abstractions support /// parsing packets in a streaming manner, i.e., never buffering more /// than O(1) bytes of data. To do this, the parsers initially only /// parse a packet's header (which is rarely more than a few kilobytes /// of data), and return control to the caller. After inspecting that /// data, the caller can decide how to handle the packet's contents. /// If the content is deemed interesting, it can be streamed or /// buffered. Otherwise, it can be dropped. Streaming is possible /// not only for literal data packets, but also containers (other /// packets also support the interface, but just return EOF). For /// instance, encryption can be stripped by saving the decrypted /// content of an encryption packet, which is just an OpenPGP message. /// /// ## Iterator Design /// /// We explicitly chose to not use a callback-based API, but something /// that is closer to Rust's iterator API. Unfortunately, because a /// `PacketParser` needs mutable access to the input stream (so that /// the content can be streamed), only a single `PacketParser` item /// can be live at a time (without a fair amount of unsafe nastiness). /// This is incompatible with Rust's iterator concept, which allows /// any number of items to be live at any time. For instance: /// /// ```rust /// let mut v = vec![1, 2, 3, 4]; /// let mut iter = v.iter_mut(); /// /// let x = iter.next().unwrap(); /// let y = iter.next().unwrap(); /// /// *x += 10; // This does not cause an error! /// *y += 10; /// ``` pub struct PacketParser<'a> { /// The current packet's header. header: Header, /// The packet that is being parsed. pub packet: Packet, // The path of the packet that is currently being parsed. path: Vec<usize>, // The path of the packet that was most recently returned by // `next()` or `recurse()`. last_path: Vec<usize>, reader: Box<dyn BufferedReader<Cookie> + 'a>, // Whether the caller read the packet's content. If so, then we // can't recurse, because we're missing some of the packet! content_was_read: bool, // Whether PacketParser::finish has been called. finished: bool, // Whether the content has been processed. processed: bool, /// A map of this packet. map: Option<map::Map>, /// We compute a hashsum over the body to implement comparison on /// containers that have been streamed. body_hash: Option<Box<Xxh3>>, state: PacketParserState, } assert_send_and_sync!(PacketParser<'_>); impl<'a> std::fmt::Display for PacketParser<'a> { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!(f, "PacketParser") } } impl<'a> std::fmt::Debug for PacketParser<'a> { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { f.debug_struct("PacketParser") .field("header", &self.header) .field("packet", &self.packet) .field("path", &self.path) .field("last_path", &self.last_path) .field("processed", &self.processed) .field("content_was_read", &self.content_was_read) .field("settings", &self.state.settings) .field("map", &self.map) .finish() } } /// The return value of PacketParser::parse. enum ParserResult<'a> { Success(PacketParser<'a>), EOF((Box<dyn BufferedReader<Cookie> + 'a>, PacketParserState, Vec<usize>)), } /// Information about the stream of packets parsed by the /// `PacketParser`. /// /// Once the [`PacketParser`] reaches the end of the input stream, it /// returns a [`PacketParserResult::EOF`] with a `PacketParserEOF`. /// This object provides information about the parsed stream, notably /// whether the packet stream was a well-formed [`Message`], /// [`Cert`] or keyring. /// /// [`Message`]: super::Message /// [`Cert`]: crate::cert::Cert /// /// # Examples /// /// Parse some OpenPGP stream using a [`PacketParser`] and detects the /// kind of data: /// /// ```rust /// # fn main() -> sequoia_openpgp::Result<()> { /// use sequoia_openpgp as openpgp; /// use openpgp::Packet; /// use openpgp::parse::{Parse, PacketParserResult, PacketParser}; /// /// let openpgp_data: &[u8] = // ... /// # include_bytes!("../tests/data/keys/public-key.gpg"); /// let mut ppr = PacketParser::from_bytes(openpgp_data)?; /// while let PacketParserResult::Some(mut pp) = ppr { /// // Start parsing the next packet, recursing. /// ppr = pp.recurse()?.1; /// } /// /// if let PacketParserResult::EOF(eof) = ppr { /// if eof.is_message().is_ok() { /// // ... /// } else if eof.is_cert().is_ok() { /// // ... /// } else if eof.is_keyring().is_ok() { /// // ... /// } else { /// // ... /// } /// } /// # Ok(()) } /// ``` #[derive(Debug)] pub struct PacketParserEOF<'a> { state: PacketParserState, reader: Box<dyn BufferedReader<Cookie> + 'a>, last_path: Vec<usize>, } assert_send_and_sync!(PacketParserEOF<'_>); impl<'a> PacketParserEOF<'a> { /// Copies the important information in `pp` into a new /// `PacketParserEOF` instance. fn new(mut state: PacketParserState, reader: Box<dyn BufferedReader<Cookie> + 'a>) -> Self { state.message_validator.finish(); state.keyring_validator.finish(); state.cert_validator.finish(); PacketParserEOF { state, reader, last_path: vec![], } } /// Creates a placeholder instance for PacketParserResult::take. fn empty() -> Self { Self::new( PacketParserState::new(Default::default()), buffered_reader::Memory::with_cookie(b"", Default::default()) .into_boxed()) } /// Returns whether the stream is an OpenPGP Message. /// /// A [`Message`] has a very specific structure. Returns `true` /// if the stream is of that form, as opposed to a [`Cert`] or /// just a bunch of packets. /// /// [`Message`]: super::Message /// [`Cert`]: crate::cert::Cert /// /// # Examples /// /// Parse some OpenPGP stream using a [`PacketParser`] and detects the /// kind of data: /// /// /// ```rust /// # fn main() -> sequoia_openpgp::Result<()> { /// use sequoia_openpgp as openpgp; /// use openpgp::Packet; /// use openpgp::parse::{Parse, PacketParserResult, PacketParser}; /// /// let openpgp_data: &[u8] = // ... /// # include_bytes!("../tests/data/keys/public-key.gpg"); /// let mut ppr = PacketParser::from_bytes(openpgp_data)?; /// while let PacketParserResult::Some(mut pp) = ppr { /// // Start parsing the next packet, recursing. /// ppr = pp.recurse()?.1; /// } /// /// if let PacketParserResult::EOF(eof) = ppr { /// if eof.is_message().is_ok() { /// // ... /// } /// } /// # Ok(()) } /// ``` pub fn is_message(&self) -> Result<()> { use crate::message::MessageValidity; match self.state.message_validator.check() { MessageValidity::Message => Ok(()), MessageValidity::MessagePrefix => unreachable!(), MessageValidity::Error(err) => Err(err), } } /// Returns whether the message is an OpenPGP keyring. /// /// A keyring has a very specific structure. Returns `true` if /// the stream is of that form, as opposed to a [`Message`] or /// just a bunch of packets. /// /// [`Message`]: super::Message /// /// # Examples /// /// Parse some OpenPGP stream using a [`PacketParser`] and detects the /// kind of data: /// /// /// ```rust /// # fn main() -> sequoia_openpgp::Result<()> { /// use sequoia_openpgp as openpgp; /// use openpgp::Packet; /// use openpgp::parse::{Parse, PacketParserResult, PacketParser}; /// /// let openpgp_data: &[u8] = // ... /// # include_bytes!("../tests/data/keys/public-key.gpg"); /// let mut ppr = PacketParser::from_bytes(openpgp_data)?; /// while let PacketParserResult::Some(mut pp) = ppr { /// // Start parsing the next packet, recursing. /// ppr = pp.recurse()?.1; /// } /// /// if let PacketParserResult::EOF(eof) = ppr { /// if eof.is_keyring().is_ok() { /// // ... /// } /// } /// # Ok(()) } /// ``` pub fn is_keyring(&self) -> Result<()> { match self.state.keyring_validator.check() { KeyringValidity::Keyring => Ok(()), KeyringValidity::KeyringPrefix => unreachable!(), KeyringValidity::Error(err) => Err(err), } } /// Returns whether the message is an OpenPGP Cert. /// /// A [`Cert`] has a very specific structure. Returns `true` if /// the stream is of that form, as opposed to a [`Message`] or /// just a bunch of packets. /// /// [`Message`]: super::Message /// [`Cert`]: crate::cert::Cert /// /// # Examples /// /// Parse some OpenPGP stream using a [`PacketParser`] and detects the /// kind of data: /// /// /// ```rust /// # fn main() -> sequoia_openpgp::Result<()> { /// use sequoia_openpgp as openpgp; /// use openpgp::Packet; /// use openpgp::parse::{Parse, PacketParserResult, PacketParser}; /// /// let openpgp_data: &[u8] = // ... /// # include_bytes!("../tests/data/keys/public-key.gpg"); /// let mut ppr = PacketParser::from_bytes(openpgp_data)?; /// while let PacketParserResult::Some(mut pp) = ppr { /// // Start parsing the next packet, recursing. /// ppr = pp.recurse()?.1; /// } /// /// if let PacketParserResult::EOF(eof) = ppr { /// if eof.is_cert().is_ok() { /// // ... /// } /// } /// # Ok(()) } /// ``` pub fn is_cert(&self) -> Result<()> { match self.state.cert_validator.check() { CertValidity::Cert => Ok(()), CertValidity::CertPrefix => unreachable!(), CertValidity::Error(err) => Err(err), } } /// Returns the path of the last packet. /// /// # Examples /// /// Parse some OpenPGP stream using a [`PacketParser`] and returns /// the path (see [`PacketPile::path_ref`]) of the last packet: /// /// [`PacketPile::path_ref`]: super::PacketPile::path_ref() /// /// ```rust /// # fn main() -> sequoia_openpgp::Result<()> { /// use sequoia_openpgp as openpgp; /// use openpgp::Packet; /// use openpgp::parse::{Parse, PacketParserResult, PacketParser}; /// /// let openpgp_data: &[u8] = // ... /// # include_bytes!("../tests/data/keys/public-key.gpg"); /// let mut ppr = PacketParser::from_bytes(openpgp_data)?; /// while let PacketParserResult::Some(mut pp) = ppr { /// // Start parsing the next packet, recursing. /// ppr = pp.recurse()?.1; /// } /// /// if let PacketParserResult::EOF(eof) = ppr { /// let _ = eof.last_path(); /// } /// # Ok(()) } /// ``` pub fn last_path(&self) -> &[usize] { &self.last_path[..] } /// The last packet's recursion depth. /// /// A top-level packet has a recursion depth of 0. Packets in a /// top-level container have a recursion depth of 1, etc. /// /// # Examples /// /// Parse some OpenPGP stream using a [`PacketParser`] and returns /// the recursion depth of the last packet: /// /// /// ```rust /// # fn main() -> sequoia_openpgp::Result<()> { /// use sequoia_openpgp as openpgp; /// use openpgp::Packet; /// use openpgp::parse::{Parse, PacketParserResult, PacketParser}; /// /// let openpgp_data: &[u8] = // ... /// # include_bytes!("../tests/data/keys/public-key.gpg"); /// let mut ppr = PacketParser::from_bytes(openpgp_data)?; /// while let PacketParserResult::Some(mut pp) = ppr { /// // Start parsing the next packet, recursing. /// ppr = pp.recurse()?.1; /// } /// /// if let PacketParserResult::EOF(eof) = ppr { /// let _ = eof.last_recursion_depth(); /// } /// # Ok(()) } /// ``` pub fn last_recursion_depth(&self) -> Option<isize> { if self.last_path.is_empty() { None } else { Some(self.last_path.len() as isize - 1) } } /// Returns the exhausted reader. pub fn into_reader(self) -> Box<dyn BufferedReader<Cookie> + 'a> { self.reader } } /// The result of parsing a packet. /// /// This type is returned by [`PacketParser::next`], /// [`PacketParser::recurse`], [`PacketParserBuilder::build`], and the /// implementation of [`PacketParser`]'s [`Parse` trait]. The result /// is either `Some(PacketParser)`, indicating successful parsing of a /// packet, or `EOF(PacketParserEOF)` if the end of the input stream /// has been reached. /// /// [`PacketParser::next`]: PacketParser::next() /// [`PacketParser::recurse`]: PacketParser::recurse() /// [`PacketParserBuilder::build`]: PacketParserBuilder::build() /// [`Parse` trait]: struct.PacketParser.html#impl-Parse%3C%27a%2C%20PacketParserResult%3C%27a%3E%3E #[derive(Debug)] pub enum PacketParserResult<'a> { /// A `PacketParser` for the next packet. Some(PacketParser<'a>), /// Information about a fully parsed packet sequence. EOF(PacketParserEOF<'a>), } assert_send_and_sync!(PacketParserResult<'_>); impl<'a> PacketParserResult<'a> { /// Returns `true` if the result is `EOF`. pub fn is_eof(&self) -> bool { matches!(self, PacketParserResult::EOF(_)) } /// Returns `true` if the result is `Some`. pub fn is_some(&self) -> bool { ! Self::is_eof(self) } /// Unwraps a result, yielding the content of an `Some`. /// /// # Panics /// /// Panics if the value is an `EOF`, with a panic message /// including the passed message, and the information in the /// [`PacketParserEOF`] object. /// pub fn expect(self, msg: &str) -> PacketParser<'a> { if let PacketParserResult::Some(pp) = self { pp } else { panic!("{}", msg); } } /// Unwraps a result, yielding the content of an `Some`. /// /// # Panics /// /// Panics if the value is an `EOF`, with a panic message /// including the information in the [`PacketParserEOF`] object. /// pub fn unwrap(self) -> PacketParser<'a> { self.expect("called `PacketParserResult::unwrap()` on a \ `PacketParserResult::PacketParserEOF` value") } /// Converts from `PacketParserResult` to `Result<&PacketParser, /// &PacketParserEOF>`. /// /// Produces a new `Result`, containing references into the /// original `PacketParserResult`, leaving the original in place. pub fn as_ref(&self) -> StdResult<&PacketParser<'a>, &PacketParserEOF> { match self { PacketParserResult::Some(pp) => Ok(pp), PacketParserResult::EOF(eof) => Err(eof), } } /// Converts from `PacketParserResult` to `Result<&mut /// PacketParser, &mut PacketParserEOF>`. /// /// Produces a new `Result`, containing mutable references into the /// original `PacketParserResult`, leaving the original in place. pub fn as_mut(&mut self) -> StdResult<&mut PacketParser<'a>, &mut PacketParserEOF<'a>> { match self { PacketParserResult::Some(pp) => Ok(pp), PacketParserResult::EOF(eof) => Err(eof), } } /// Takes the value out of the `PacketParserResult`, leaving a /// `EOF` in its place. /// /// The `EOF` left in place carries a [`PacketParserEOF`] with /// default values. /// pub fn take(&mut self) -> Self { mem::replace( self, PacketParserResult::EOF(PacketParserEOF::empty())) } /// Maps a `PacketParserResult` to `Result<PacketParser, /// PacketParserEOF>` by applying a function to a contained `Some` /// value, leaving an `EOF` value untouched. pub fn map<U, F>(self, f: F) -> StdResult<U, PacketParserEOF<'a>> where F: FnOnce(PacketParser<'a>) -> U { match self { PacketParserResult::Some(x) => Ok(f(x)), PacketParserResult::EOF(e) => Err(e), } } } impl<'a> Parse<'a, PacketParserResult<'a>> for PacketParser<'a> { /// Starts parsing an OpenPGP object stored in a `BufferedReader` object. /// /// This function returns a `PacketParser` for the first packet in /// the stream. fn from_buffered_reader<R>(reader: R) -> Result<PacketParserResult<'a>> where R: BufferedReader<Cookie> + 'a, { PacketParserBuilder::from_buffered_reader(reader.into_boxed())?.build() } } impl<'a> crate::seal::Sealed for PacketParser<'a> {} impl <'a> PacketParser<'a> { /// Starts parsing an OpenPGP message stored in a `BufferedReader` /// object. /// /// This function returns a `PacketParser` for the first packet in /// the stream. pub(crate) fn from_cookie_reader(bio: Box<dyn BufferedReader<Cookie> + 'a>) -> Result<PacketParserResult<'a>> { PacketParserBuilder::from_cookie_reader(bio)?.build() } /// Returns the reader stack, replacing it with a /// `buffered_reader::EOF` reader. /// /// This function may only be called when the `PacketParser` is in /// State::Body. fn take_reader(&mut self) -> Box<dyn BufferedReader<Cookie> + 'a> { self.set_reader( Box::new(buffered_reader::EOF::with_cookie(Default::default()))) } /// Replaces the reader stack. /// /// This function may only be called when the `PacketParser` is in /// State::Body. fn set_reader(&mut self, reader: Box<dyn BufferedReader<Cookie> + 'a>) -> Box<dyn BufferedReader<Cookie> + 'a> { mem::replace(&mut self.reader, reader) } /// Returns a mutable reference to the reader stack. fn mut_reader(&mut self) -> &mut dyn BufferedReader<Cookie> { &mut self.reader } /// Marks the packet's contents as processed or not. fn set_processed(mut self, v: bool) -> Self { self.processed = v; self } /// Returns whether the packet's contents have been processed. /// /// This function returns `true` while processing an encryption /// container before it is decrypted using /// [`PacketParser::decrypt`]. Once successfully decrypted, it /// returns `false`. /// /// [`PacketParser::decrypt`]: PacketParser::decrypt() /// /// # Examples /// /// ```rust /// # fn main() -> sequoia_openpgp::Result<()> { /// use sequoia_openpgp as openpgp; /// use openpgp::Packet; /// use openpgp::fmt::hex; /// use openpgp::types::SymmetricAlgorithm; /// use openpgp::parse::{Parse, PacketParserResult, PacketParser}; /// /// // Parse an encrypted message. /// let message_data: &[u8] = // ... /// # include_bytes!("../tests/data/messages/encrypted-aes256-password-123.gpg"); /// let mut ppr = PacketParser::from_bytes(message_data)?; /// while let PacketParserResult::Some(mut pp) = ppr { /// if let Packet::SEIP(_) = pp.packet { /// assert!(!pp.processed()); /// pp.decrypt(SymmetricAlgorithm::AES256, /// &hex::decode("7EF4F08C44F780BEA866961423306166\ /// B8912C43352F3D9617F745E4E3939710")? /// .into())?; /// assert!(pp.processed()); /// } /// /// // Start parsing the next packet, recursing. /// ppr = pp.recurse()?.1; /// } /// # Ok(()) } /// ``` pub fn processed(&self) -> bool { self.processed } /// Returns the path of the last packet. /// /// This function returns the path (see [`PacketPile::path_ref`] /// for a description of paths) of the packet last returned by a /// call to [`PacketParser::recurse`] or [`PacketParser::next`]. /// If no packet has been returned (i.e. the current packet is the /// first packet), this returns the empty slice. /// /// [`PacketPile::path_ref`]: super::PacketPile::path_ref() /// [`PacketParser::recurse`]: PacketParser::recurse() /// [`PacketParser::next`]: PacketParser::next() /// /// # Examples /// /// ```rust /// # fn main() -> sequoia_openpgp::Result<()> { /// use sequoia_openpgp as openpgp; /// use openpgp::Packet; /// use openpgp::parse::{Parse, PacketParserResult, PacketParser}; /// /// // Parse a compressed message. /// let message_data: &[u8] = // ... /// # include_bytes!("../tests/data/messages/compressed-data-algo-0.pgp"); /// let mut ppr = PacketParser::from_bytes(message_data)?; /// while let PacketParserResult::Some(mut pp) = ppr { /// match pp.packet { /// Packet::CompressedData(_) => assert_eq!(pp.last_path(), &[]), /// Packet::Literal(_) => assert_eq!(pp.last_path(), &[0]), /// _ => (), /// } /// /// // Start parsing the next packet, recursing. /// ppr = pp.recurse()?.1; /// } /// # Ok(()) } /// ``` pub fn last_path(&self) -> &[usize] { &self.last_path[..] } /// Returns the path of the current packet. /// /// This function returns the path (see [`PacketPile::path_ref`] /// for a description of paths) of the packet currently being /// processed (see [`PacketParser::packet`]). /// /// [`PacketPile::path_ref`]: super::PacketPile::path_ref() /// /// # Examples /// /// ```rust /// # fn main() -> sequoia_openpgp::Result<()> { /// use sequoia_openpgp as openpgp; /// use openpgp::Packet; /// use openpgp::parse::{Parse, PacketParserResult, PacketParser}; /// /// // Parse a compressed message. /// let message_data: &[u8] = // ... /// # include_bytes!("../tests/data/messages/compressed-data-algo-0.pgp"); /// let mut ppr = PacketParser::from_bytes(message_data)?; /// while let PacketParserResult::Some(mut pp) = ppr { /// match pp.packet { /// Packet::CompressedData(_) => assert_eq!(pp.path(), &[0]), /// Packet::Literal(_) => assert_eq!(pp.path(), &[0, 0]), /// _ => (), /// } /// /// // Start parsing the next packet, recursing. /// ppr = pp.recurse()?.1; /// } /// # Ok(()) } /// ``` pub fn path(&self) -> &[usize] { &self.path[..] } /// The current packet's recursion depth. /// /// A top-level packet has a recursion depth of 0. Packets in a /// top-level container have a recursion depth of 1, etc. /// /// # Examples /// /// ```rust /// # fn main() -> sequoia_openpgp::Result<()> { /// use sequoia_openpgp as openpgp; /// use openpgp::Packet; /// use openpgp::parse::{Parse, PacketParserResult, PacketParser}; /// /// // Parse a compressed message. /// let message_data: &[u8] = // ... /// # include_bytes!("../tests/data/messages/compressed-data-algo-0.pgp"); /// let mut ppr = PacketParser::from_bytes(message_data)?; /// while let PacketParserResult::Some(mut pp) = ppr { /// match pp.packet { /// Packet::CompressedData(_) => assert_eq!(pp.recursion_depth(), 0), /// Packet::Literal(_) => assert_eq!(pp.recursion_depth(), 1), /// _ => (), /// } /// /// // Start parsing the next packet, recursing. /// ppr = pp.recurse()?.1; /// } /// # Ok(()) } /// ``` pub fn recursion_depth(&self) -> isize { self.path.len() as isize - 1 } /// The last packet's recursion depth. /// /// A top-level packet has a recursion depth of 0. Packets in a /// top-level container have a recursion depth of 1, etc. /// /// Note: if no packet has been returned yet, this returns None. /// /// # Examples /// /// ```rust /// # fn main() -> sequoia_openpgp::Result<()> { /// use sequoia_openpgp as openpgp; /// use openpgp::Packet; /// use openpgp::parse::{Parse, PacketParserResult, PacketParser}; /// /// // Parse a compressed message. /// let message_data: &[u8] = // ... /// # include_bytes!("../tests/data/messages/compressed-data-algo-0.pgp"); /// let mut ppr = PacketParser::from_bytes(message_data)?; /// while let PacketParserResult::Some(mut pp) = ppr { /// match pp.packet { /// Packet::CompressedData(_) => assert_eq!(pp.last_recursion_depth(), None), /// Packet::Literal(_) => assert_eq!(pp.last_recursion_depth(), Some(0)), /// _ => (), /// } /// /// // Start parsing the next packet, recursing. /// ppr = pp.recurse()?.1; /// } /// # Ok(()) } /// ``` pub fn last_recursion_depth(&self) -> Option<isize> { if self.last_path.is_empty() { assert_eq!(&self.path[..], &[ 0 ]); None } else { Some(self.last_path.len() as isize - 1) } } /// Returns whether the message appears to be an OpenPGP Message. /// /// Only when the whole message has been processed is it possible /// to say whether the message is definitely an OpenPGP Message. /// Before that, it is only possible to say that the message is a /// valid prefix or definitely not an OpenPGP message (see /// [`PacketParserEOF::is_message`]). /// /// [`PacketParserEOF::is_message`]: PacketParserEOF::is_message() /// /// # Examples /// /// ```rust /// # fn main() -> sequoia_openpgp::Result<()> { /// use sequoia_openpgp as openpgp; /// use openpgp::Packet; /// use openpgp::parse::{Parse, PacketParserResult, PacketParser}; /// /// // Parse a compressed message. /// let message_data: &[u8] = // ... /// # include_bytes!("../tests/data/messages/compressed-data-algo-0.pgp"); /// let mut ppr = PacketParser::from_bytes(message_data)?; /// while let PacketParserResult::Some(mut pp) = ppr { /// pp.possible_message()?; /// /// // Start parsing the next packet, recursing. /// ppr = pp.recurse()?.1; /// } /// # Ok(()) } /// ``` pub fn possible_message(&self) -> Result<()> { use crate::message::MessageValidity; match self.state.message_validator.check() { MessageValidity::Message => unreachable!(), MessageValidity::MessagePrefix => Ok(()), MessageValidity::Error(err) => Err(err), } } /// Returns whether the message appears to be an OpenPGP keyring. /// /// Only when the whole message has been processed is it possible /// to say whether the message is definitely an OpenPGP keyring. /// Before that, it is only possible to say that the message is a /// valid prefix or definitely not an OpenPGP keyring (see /// [`PacketParserEOF::is_keyring`]). /// /// [`PacketParserEOF::is_keyring`]: PacketParserEOF::is_keyring() /// /// # Examples /// /// ```rust /// # fn main() -> sequoia_openpgp::Result<()> { /// use sequoia_openpgp as openpgp; /// use openpgp::Packet; /// use openpgp::parse::{Parse, PacketParserResult, PacketParser}; /// /// // Parse a certificate. /// let message_data: &[u8] = // ... /// # include_bytes!("../tests/data/keys/testy.pgp"); /// let mut ppr = PacketParser::from_bytes(message_data)?; /// while let PacketParserResult::Some(mut pp) = ppr { /// pp.possible_keyring()?; /// /// // Start parsing the next packet, recursing. /// ppr = pp.recurse()?.1; /// } /// # Ok(()) } /// ``` pub fn possible_keyring(&self) -> Result<()> { match self.state.keyring_validator.check() { KeyringValidity::Keyring => unreachable!(), KeyringValidity::KeyringPrefix => Ok(()), KeyringValidity::Error(err) => Err(err), } } /// Returns whether the message appears to be an OpenPGP Cert. /// /// Only when the whole message has been processed is it possible /// to say whether the message is definitely an OpenPGP Cert. /// Before that, it is only possible to say that the message is a /// valid prefix or definitely not an OpenPGP Cert (see /// [`PacketParserEOF::is_cert`]). /// /// [`PacketParserEOF::is_cert`]: PacketParserEOF::is_cert() /// /// # Examples /// /// ```rust /// # fn main() -> sequoia_openpgp::Result<()> { /// use sequoia_openpgp as openpgp; /// use openpgp::Packet; /// use openpgp::parse::{Parse, PacketParserResult, PacketParser}; /// /// // Parse a certificate. /// let message_data: &[u8] = // ... /// # include_bytes!("../tests/data/keys/testy.pgp"); /// let mut ppr = PacketParser::from_bytes(message_data)?; /// while let PacketParserResult::Some(mut pp) = ppr { /// pp.possible_cert()?; /// /// // Start parsing the next packet, recursing. /// ppr = pp.recurse()?.1; /// } /// # Ok(()) } /// ``` pub fn possible_cert(&self) -> Result<()> { match self.state.cert_validator.check() { CertValidity::Cert => unreachable!(), CertValidity::CertPrefix => Ok(()), CertValidity::Error(err) => Err(err), } } /// Tests whether the data appears to be a legal cert packet. /// /// This is just a heuristic. It can be used for recovering from /// garbage. /// /// Successfully reading the header only means that the top bit of /// the ptag is 1. Assuming a uniform distribution, there's a 50% /// chance that that is the case. /// /// To improve our chances of a correct recovery, we make sure the /// tag is known (for new format CTBs, there are 64 possible tags, /// but only a third of them are reasonable; for old format /// packets, there are only 16 and nearly all are plausible), and /// we make sure the packet contents are reasonable. /// /// Currently, we only try to recover the most interesting /// packets. pub(crate) fn plausible_cert<C, T>(bio: &mut buffered_reader::Dup<T, C>, header: &Header) -> Result<()> where T: BufferedReader<C>, C: fmt::Debug + Send + Sync { let bad = Err( Error::MalformedPacket("Can't make an educated case".into()).into()); match header.ctb().tag() { Tag::Reserved | Tag::Unknown(_) | Tag::Private(_) => Err(Error::MalformedPacket("Looks like garbage".into()).into()), Tag::Marker => Marker::plausible(bio, header), Tag::Padding => { // Even though a padding packet may occur here, it has // so little structure, that we're likely better off // trying to find the next packet. // // XXX: We could optimize that though, by using the // potential padding packet's length to see if the // next packet is plausible. bad }, Tag::Signature => Signature::plausible(bio, header), Tag::SecretKey => Key::plausible(bio, header), Tag::PublicKey => Key::plausible(bio, header), Tag::SecretSubkey => Key::plausible(bio, header), Tag::PublicSubkey => Key::plausible(bio, header), Tag::UserID => bad, Tag::UserAttribute => bad, // It is reasonable to try and ignore garbage in Certs, // because who knows what the keyservers return, etc. // But, if we have what appears to be an OpenPGP message, // then, ignore. Tag::PKESK => bad, Tag::SKESK => bad, Tag::OnePassSig => bad, Tag::CompressedData => bad, Tag::SED => bad, Tag::Literal => bad, Tag::Trust => bad, Tag::SEIP => bad, Tag::MDC => bad, Tag::AED => bad, } } /// Returns a `PacketParser` for the next OpenPGP packet in the /// stream. If there are no packets left, this function returns /// `bio`. fn parse(mut bio: Box<dyn BufferedReader<Cookie> + 'a>, mut state: PacketParserState, path: Vec<usize>) -> Result<ParserResult<'a>> { assert!(!path.is_empty()); let indent = path.len() as isize - 1; tracer!(TRACE, "PacketParser::parse", indent); if let Some(err) = state.pending_error.take() { t!("Returning pending error: {}", err); return Err(err); } t!("Parsing packet at {:?}", path); let recursion_depth = path.len() as isize - 1; // When header encounters an EOF, it returns an error. But, // we want to return None. Try a one byte read. if bio.data(1)?.is_empty() { t!("No packet at {:?} (EOF).", path); return Ok(ParserResult::EOF((bio, state, path))); } // When computing a hash for a signature, most of the // signature packet should not be included in the hash. That // is: // // [ one pass sig ] [ ... message ... ] [ sig ] // ^^^^^^^^^^^^^^^^^^^ // hash only this // // (The special logic for the Signature packet is in // Signature::parse.) // // To avoid this, we use a Dup reader to figure out if the // next packet is a sig packet without consuming the headers, // which would cause the headers to be hashed. If so, we // extract the hash context. let mut bio = buffered_reader::Dup::with_cookie(bio, Cookie::default()); let header; // Read the header. let mut skip = 0; let mut orig_error : Option<anyhow::Error> = None; loop { bio.rewind(); if let Err(_err) = bio.data_consume_hard(skip) { // EOF. We checked for EOF above when skip was 0, so // we must have skipped something. assert!(skip > 0); // Fabricate a header. header = Header::new(CTB::new(Tag::Reserved), BodyLength::Full(skip as u32)); break; } match Header::parse(&mut bio) { Ok(header_) => { if skip == 0 { header = header_; break; } match Self::plausible_cert(&mut bio, &header_) { Ok(()) => { header = Header::new(CTB::new(Tag::Reserved), BodyLength::Full(skip as u32)); break; } Err(err_) => { t!("{} not plausible @ {}: {}", header_.ctb().tag(), skip, err_); }, } } Err(err) => { t!("Failed to read a header after skipping {} bytes: {}", skip, err); if orig_error.is_none() { orig_error = Some(err); } if state.first_packet { // We don't try to recover if we haven't seen // any packets. return Err(orig_error.unwrap()); } if skip > RECOVERY_THRESHOLD { // Limit the search space. This should be // enough to find a reasonable recovery point // in a Cert. state.pending_error = orig_error; // Fabricate a header. header = Header::new(CTB::new(Tag::Reserved), BodyLength::Full(skip as u32)); break; } } } skip += 1; } // Prepare to actually consume the header or garbage. let consumed = if skip == 0 { bio.total_out() } else { t!("turning {} bytes of junk into an Unknown packet", skip); bio.rewind(); 0 }; let tag = header.ctb().tag(); t!("Packet's tag is {}", tag); // A buffered_reader::Dup always has an inner. let mut bio = Box::new(bio).into_inner().unwrap(); // Disable hashing for literal packets, Literal::parse will // enable it for the body. Signatures and OnePassSig packets // are only hashed by notarizing signatures. if tag == Tag::Literal { Cookie::hashing( &mut bio, Hashing::Disabled, recursion_depth - 1); } else if tag == Tag::OnePassSig || tag == Tag::Signature { if Cookie::processing_csf_message(&bio) { // When processing a CSF message, the hashing reader // is not peeled off, because the number of signature // packets cannot be known from the number of OPS // packets. Instead, we simply disable hashing. // // XXX: It would be nice to peel off the hashing // reader and drop this workaround. Cookie::hashing( &mut bio, Hashing::Disabled, recursion_depth - 1); } else { Cookie::hashing( &mut bio, Hashing::Notarized, recursion_depth - 1); } } // Save header for the map or nested signatures. let header_bytes = Vec::from(&bio.data_consume_hard(consumed)?[..consumed]); let bio : Box<dyn BufferedReader<Cookie>> = match header.length() { &BodyLength::Full(len) => { t!("Pushing a limitor ({} bytes), level: {}.", len, recursion_depth); Box::new(buffered_reader::Limitor::with_cookie( bio, len as u64, Cookie::new(recursion_depth))) }, &BodyLength::Partial(len) => { t!("Pushing a partial body chunk decoder, level: {}.", recursion_depth); Box::new(BufferedReaderPartialBodyFilter::with_cookie( bio, len, // When hashing a literal data packet, we only // hash the packet's contents; we don't hash // the literal data packet's meta-data or the // length information, which includes the // partial body headers. tag != Tag::Literal, Cookie::new(recursion_depth))) }, BodyLength::Indeterminate => { t!("Indeterminate length packet, not adding a limitor."); bio }, }; // Our parser should not accept packets that fail our header // syntax check. Doing so breaks roundtripping, and seems // like a bad idea anyway. let mut header_syntax_error = header.valid(true).err(); // Check packet size. if header_syntax_error.is_none() { let max_size = state.settings.max_packet_size; match tag { // Don't check the size for container packets, those // can be safely streamed. Tag::Literal | Tag::CompressedData | Tag::SED | Tag::SEIP | Tag::AED => (), _ => match header.length() { BodyLength::Full(l) => if *l > max_size { header_syntax_error = Some( Error::PacketTooLarge(tag, *l, max_size).into()); }, _ => unreachable!("non-data packets have full length, \ syntax check above"), } } } let parser = PacketHeaderParser::new(bio, state, path, header, header_bytes); let mut result = match tag { Tag::Reserved if skip > 0 => Unknown::parse( parser, Error::MalformedPacket(format!( "Skipped {} bytes of junk", skip)).into()), _ if header_syntax_error.is_some() => Unknown::parse(parser, header_syntax_error.unwrap()), Tag::Signature => Signature::parse(parser), Tag::OnePassSig => OnePassSig::parse(parser), Tag::PublicSubkey => Key::parse(parser), Tag::PublicKey => Key::parse(parser), Tag::SecretKey => Key::parse(parser), Tag::SecretSubkey => Key::parse(parser), Tag::Trust => Trust::parse(parser), Tag::UserID => UserID::parse(parser), Tag::UserAttribute => UserAttribute::parse(parser), Tag::Marker => Marker::parse(parser), Tag::Literal => Literal::parse(parser), Tag::CompressedData => CompressedData::parse(parser), Tag::SKESK => SKESK::parse(parser), Tag::SEIP => SEIP::parse(parser), Tag::MDC => MDC::parse(parser), Tag::PKESK => PKESK::parse(parser), Tag::Padding => Padding::parse(parser), _ => Unknown::parse(parser, Error::UnsupportedPacketType(tag).into()), }?; if tag == Tag::OnePassSig { Cookie::hashing( &mut result, Hashing::Enabled, recursion_depth - 1); } result.state.first_packet = false; t!(" -> {:?}, path: {:?}, level: {:?}.", result.packet.tag(), result.path, result.cookie_ref().level); return Ok(ParserResult::Success(result)); } /// Finishes parsing the current packet and starts parsing the /// next one. /// /// This function finishes parsing the current packet. By /// default, any unread content is dropped. (See /// [`PacketParsererBuilder`] for how to configure this.) It then /// creates a new packet parser for the next packet. If the /// current packet is a container, this function does *not* /// recurse into the container, but skips any packets it contains. /// To recurse into the container, use the [`recurse()`] method. /// /// [`PacketParsererBuilder`]: PacketParserBuilder /// [`recurse()`]: PacketParser::recurse() /// /// The return value is a tuple containing: /// /// - A `Packet` holding the fully processed old packet; /// /// - A `PacketParser` holding the new packet; /// /// To determine the two packet's position within the parse tree, /// you can use `last_path()` and `path()`, respectively. To /// determine their depth, you can use `last_recursion_depth()` /// and `recursion_depth()`, respectively. /// /// Note: A recursion depth of 0 means that the packet is a /// top-level packet, a recursion depth of 1 means that the packet /// is an immediate child of a top-level-packet, etc. /// /// Since the packets are serialized in depth-first order and all /// interior nodes are visited, we know that if the recursion /// depth is the same, then the packets are siblings (they have a /// common parent) and not, e.g., cousins (they have a common /// grandparent). This is because, if we move up the tree, the /// only way to move back down is to first visit a new container /// (e.g., an aunt). /// /// Using the two positions, we can compute the change in depth as /// new_depth - old_depth. Thus, if the change in depth is 0, the /// two packets are siblings. If the value is 1, the old packet /// is a container, and the new packet is its first child. And, /// if the value is -1, the new packet is contained in the old /// packet's grandparent. The idea is illustrated below: /// /// ```text /// ancestor /// | \ /// ... -n /// | /// grandparent /// | \ /// parent -1 /// | \ /// packet 0 /// | /// 1 /// ``` /// /// Note: since this function does not automatically recurse into /// a container, the change in depth will always be non-positive. /// If the current container is empty, this function DOES pop that /// container off the container stack, and returns the following /// packet in the parent container. /// /// # Examples /// /// ```rust /// # fn main() -> sequoia_openpgp::Result<()> { /// use sequoia_openpgp as openpgp; /// use openpgp::Packet; /// use openpgp::parse::{Parse, PacketParserResult, PacketParser}; /// /// // Parse a message. /// let message_data: &[u8] = // ... /// # include_bytes!("../tests/data/messages/compressed-data-algo-0.pgp"); /// let mut ppr = PacketParser::from_bytes(message_data)?; /// while let PacketParserResult::Some(mut pp) = ppr { /// // Start parsing the next packet. /// ppr = pp.next()?.1; /// } /// # Ok(()) } /// ``` pub fn next(mut self) -> Result<(Packet, PacketParserResult<'a>)> { let indent = self.recursion_depth(); tracer!(TRACE, "PacketParser::next", indent); t!("({:?}, path: {:?}, level: {:?}).", self.packet.tag(), self.path, self.cookie_ref().level); self.finish()?; let (mut fake_eof, mut reader) = buffered_reader_stack_pop( mem::replace(&mut self.reader, Box::new(buffered_reader::EOF::with_cookie( Default::default()))), self.recursion_depth())?; self.last_path.clear(); self.last_path.extend_from_slice(&self.path[..]); // Assume that we succeed in parsing the next packet. If not, // then we'll adjust the path. *self.path.last_mut().expect("A path is never empty") += 1; // Now read the next packet. loop { // Parse the next packet. t!("Reading packet at {:?}", self.path); let recursion_depth = self.recursion_depth(); let ppr = PacketParser::parse(reader, self.state, self.path)?; match ppr { ParserResult::EOF((reader_, state_, path_)) => { // We got EOF on the current container. The // container at recursion depth n is empty. Pop // it and any filters for it, i.e., those at level // n (e.g., the limitor that caused us to hit // EOF), and then try again. t!("depth: {}, got EOF trying to read the next packet", recursion_depth); self.path = path_; if ! fake_eof && recursion_depth == 0 { t!("Popped top-level container, done reading message."); // Pop topmost filters (e.g. the armor::Reader). let (_, reader_) = buffered_reader_stack_pop( reader_, ARMOR_READER_LEVEL)?; let mut eof = PacketParserEOF::new(state_, reader_); eof.last_path = self.last_path; return Ok((self.packet, PacketParserResult::EOF(eof))); } else { self.state = state_; self.finish()?; let (fake_eof_, reader_) = buffered_reader_stack_pop( reader_, recursion_depth - 1)?; fake_eof = fake_eof_; if ! fake_eof { self.path.pop().unwrap(); *self.path.last_mut() .expect("A path is never empty") += 1; } reader = reader_; } }, ParserResult::Success(mut pp) => { let path = pp.path().to_vec(); pp.state.message_validator.push( pp.packet.tag(), pp.packet.version(), &path); pp.state.keyring_validator.push(pp.packet.tag()); pp.state.cert_validator.push(pp.packet.tag()); pp.last_path = self.last_path; return Ok((self.packet, PacketParserResult::Some(pp))); } } } } /// Finishes parsing the current packet and starts parsing the /// next one, recursing if possible. /// /// This method is similar to the [`next()`] method (see that /// method for more details), but if the current packet is a /// container (and we haven't reached the maximum recursion depth, /// and the user hasn't started reading the packet's contents), we /// recurse into the container, and return a `PacketParser` for /// its first child. Otherwise, we return the next packet in the /// packet stream. If this function recurses, then the new /// packet's recursion depth will be `last_recursion_depth() + 1`; /// because we always visit interior nodes, we can't recurse more /// than one level at a time. /// /// [`next()`]: PacketParser::next() /// /// # Examples /// /// ```rust /// # fn main() -> sequoia_openpgp::Result<()> { /// use sequoia_openpgp as openpgp; /// use openpgp::Packet; /// use openpgp::parse::{Parse, PacketParserResult, PacketParser}; /// /// // Parse a message. /// let message_data: &[u8] = // ... /// # include_bytes!("../tests/data/messages/compressed-data-algo-0.pgp"); /// let mut ppr = PacketParser::from_bytes(message_data)?; /// while let PacketParserResult::Some(mut pp) = ppr { /// // Start parsing the next packet, recursing. /// ppr = pp.recurse()?.1; /// } /// # Ok(()) } /// ``` pub fn recurse(self) -> Result<(Packet, PacketParserResult<'a>)> { let indent = self.recursion_depth(); tracer!(TRACE, "PacketParser::recurse", indent); t!("({:?}, path: {:?}, level: {:?})", self.packet.tag(), self.path, self.cookie_ref().level); match self.packet { // Packets that recurse. Packet::CompressedData(_) | Packet::SEIP(_) if self.processed => { if self.recursion_depth() as u8 >= self.state.settings.max_recursion_depth { t!("Not recursing into the {:?} packet, maximum recursion \ depth ({}) reached.", self.packet.tag(), self.state.settings.max_recursion_depth); // Drop through. } else if self.content_was_read { t!("Not recursing into the {:?} packet, some data was \ already read.", self.packet.tag()); // Drop through. } else { let mut last_path = self.last_path; last_path.clear(); last_path.extend_from_slice(&self.path[..]); let mut path = self.path; path.push(0); match PacketParser::parse(self.reader, self.state, path.clone())? { ParserResult::Success(mut pp) => { t!("Recursed into the {:?} packet, got a {:?}.", self.packet.tag(), pp.packet.tag()); pp.state.message_validator.push( pp.packet.tag(), pp.packet.version(), &path); pp.state.keyring_validator.push(pp.packet.tag()); pp.state.cert_validator.push(pp.packet.tag()); pp.last_path = last_path; return Ok((self.packet, PacketParserResult::Some(pp))); }, ParserResult::EOF(_) => { return Err(Error::MalformedPacket( "Container is truncated".into()).into()); }, } } }, // Packets that don't recurse. #[allow(deprecated)] Packet::Unknown(_) | Packet::Signature(_) | Packet::OnePassSig(_) | Packet::PublicKey(_) | Packet::PublicSubkey(_) | Packet::SecretKey(_) | Packet::SecretSubkey(_) | Packet::Marker(_) | Packet::Trust(_) | Packet::UserID(_) | Packet::UserAttribute(_) | Packet::Literal(_) | Packet::PKESK(_) | Packet::SKESK(_) | Packet::SEIP(_) | Packet::MDC(_) | Packet::CompressedData(_) | Packet::Padding(_) => { // Drop through. t!("A {:?} packet is not a container, not recursing.", self.packet.tag()); }, } // No recursion. self.next() } /// Causes the PacketParser to buffer the packet's contents. /// /// The packet's contents can be retrieved using /// e.g. [`Container::body`]. In general, you should avoid /// buffering a packet's content and prefer streaming its content /// unless you are certain that the content is small. /// /// [`Container::body`]: crate::packet::Container::body() /// /// ```rust /// # fn main() -> sequoia_openpgp::Result<()> { /// use sequoia_openpgp as openpgp; /// use openpgp::Packet; /// use openpgp::parse::{Parse, PacketParserResult, PacketParser}; /// /// // Parse a message. /// let message_data: &[u8] = // ... /// # include_bytes!("../tests/data/messages/literal-mode-t-partial-body.gpg"); /// let mut ppr = PacketParser::from_bytes(message_data)?; /// while let PacketParserResult::Some(mut pp) = ppr { /// // Process the packet. /// /// if let Packet::Literal(_) = pp.packet { /// assert!(pp.buffer_unread_content()? /// .starts_with(b"A Cypherpunk's Manifesto")); /// # assert!(pp.buffer_unread_content()? /// # .starts_with(b"A Cypherpunk's Manifesto")); /// if let Packet::Literal(l) = &pp.packet { /// assert!(l.body().starts_with(b"A Cypherpunk's Manifesto")); /// assert_eq!(l.body().len(), 5158); /// } else { /// unreachable!(); /// } /// } /// /// // Start parsing the next packet, recursing. /// ppr = pp.recurse()?.1; /// } /// # Ok(()) } /// ``` pub fn buffer_unread_content(&mut self) -> Result<&[u8]> { let rest = self.steal_eof()?; fn set_or_extend(rest: Vec<u8>, c: &mut Container, processed: bool) -> Result<&[u8]> { if !rest.is_empty() { let current = match c.body() { Body::Unprocessed(bytes) => &bytes[..], Body::Processed(bytes) => &bytes[..], Body::Structured(packets) if packets.is_empty() => &[][..], Body::Structured(_) => return Err(Error::InvalidOperation( "cannot append unread bytes to parsed packets" .into()).into()), }; let rest = if !current.is_empty() { let mut new = Vec::with_capacity(current.len() + rest.len()); new.extend_from_slice(current); new.extend_from_slice(&rest); new } else { rest }; c.set_body(if processed { Body::Processed(rest) } else { Body::Unprocessed(rest) }); } match c.body() { Body::Unprocessed(bytes) => Ok(bytes), Body::Processed(bytes) => Ok(bytes), Body::Structured(packets) if packets.is_empty() => Ok(&[][..]), Body::Structured(_) => Err(Error::InvalidOperation( "cannot append unread bytes to parsed packets" .into()).into()), } } match &mut self.packet { Packet::Literal(p) => set_or_extend(rest, p.container_mut(), false), Packet::Unknown(p) => set_or_extend(rest, p.container_mut(), false), Packet::CompressedData(p) => set_or_extend(rest, p.container_mut(), self.processed), Packet::SEIP(SEIP::V1(p)) => set_or_extend(rest, p.container_mut(), self.processed), Packet::SEIP(SEIP::V2(p)) => set_or_extend(rest, p.container_mut(), self.processed), p => { if !rest.is_empty() { Err(Error::MalformedPacket( format!("Unexpected body data for {:?}: {}", p, crate::fmt::hex::encode_pretty(rest))) .into()) } else { Ok(&b""[..]) } }, } } /// Finishes parsing the current packet. /// /// By default, this drops any unread content. Use, for instance, /// [`PacketParserBuilder`] to customize the default behavior. /// /// /// # Examples /// /// ```rust /// # fn main() -> sequoia_openpgp::Result<()> { /// use sequoia_openpgp as openpgp; /// use openpgp::Packet; /// use openpgp::parse::{Parse, PacketParserResult, PacketParser}; /// /// // Parse a message. /// let message_data: &[u8] = // ... /// # include_bytes!("../tests/data/messages/compressed-data-algo-0.pgp"); /// let mut ppr = PacketParser::from_bytes(message_data)?; /// while let PacketParserResult::Some(mut pp) = ppr { /// let p = pp.finish()?; /// # let _ = p; /// /// // Start parsing the next packet, recursing. /// ppr = pp.recurse()?.1; /// } /// # Ok(()) } // Note: this function is public and may be called multiple times! pub fn finish(&mut self) -> Result<&Packet> { let indent = self.recursion_depth(); tracer!(TRACE, "PacketParser::finish", indent); if self.finished { return Ok(&self.packet); } let recursion_depth = self.recursion_depth(); let unread_content = if self.state.settings.buffer_unread_content { t!("({:?} at depth {}): buffering {} bytes of unread content", self.packet.tag(), recursion_depth, self.data_eof().unwrap_or(&[]).len()); !self.buffer_unread_content()?.is_empty() } else { t!("({:?} at depth {}): dropping {} bytes of unread content", self.packet.tag(), recursion_depth, self.data_eof().unwrap_or(&[]).len()); self.drop_eof()? }; if unread_content { match self.packet.tag() { Tag::SEIP | Tag::AED | Tag::SED | Tag::CompressedData => { // We didn't (fully) process a container's content. Add // this as opaque content to the message validator. let mut path = self.path().to_vec(); path.push(0); self.state.message_validator.push_token( message::Token::OpaqueContent, &path); } _ => {}, } } if let Some(c) = self.packet.container_mut() { let h = self.body_hash.take() .expect("body_hash is Some"); c.set_body_hash(h); } self.finished = true; Ok(&self.packet) } /// Hashes content that has been streamed. fn hash_read_content(&mut self, b: &[u8]) { if !b.is_empty() { assert!(self.body_hash.is_some()); if let Some(h) = self.body_hash.as_mut() { h.update(b); } self.content_was_read = true; } } /// Returns a reference to the current packet's header. /// /// # Examples /// /// ```rust /// # fn main() -> sequoia_openpgp::Result<()> { /// use sequoia_openpgp as openpgp; /// use openpgp::Packet; /// use openpgp::parse::{Parse, PacketParserResult, PacketParser}; /// /// // Parse a message. /// let message_data: &[u8] = // ... /// # include_bytes!("../tests/data/messages/compressed-data-algo-0.pgp"); /// let mut ppr = PacketParser::from_bytes(message_data)?; /// while let PacketParserResult::Some(mut pp) = ppr { /// pp.header().valid(false)?; /// /// // Start parsing the next packet, recursing. /// ppr = pp.recurse()?.1; /// } /// # Ok(()) } /// ``` pub fn header(&self) -> &Header { &self.header } /// Returns a reference to the map (if any is written). /// /// # Examples /// /// ``` /// # fn main() -> sequoia_openpgp::Result<()> { /// use sequoia_openpgp as openpgp; /// use openpgp::parse::{Parse, PacketParserBuilder}; /// /// let message_data = b"\xcb\x12t\x00\x00\x00\x00\x00Hello world."; /// let pp = PacketParserBuilder::from_bytes(message_data)? /// .map(true) // Enable mapping. /// .build()? /// .expect("One packet, not EOF"); /// let map = pp.map().expect("Mapping is enabled"); /// /// assert_eq!(map.iter().nth(0).unwrap().name(), "CTB"); /// assert_eq!(map.iter().nth(0).unwrap().offset(), 0); /// assert_eq!(map.iter().nth(0).unwrap().as_bytes(), &[0xcb]); /// # Ok(()) } /// ``` pub fn map(&self) -> Option<&map::Map> { self.map.as_ref() } /// Takes the map (if any is written). /// /// # Examples /// /// ``` /// # fn main() -> sequoia_openpgp::Result<()> { /// use sequoia_openpgp as openpgp; /// use openpgp::parse::{Parse, PacketParserBuilder}; /// /// let message_data = b"\xcb\x12t\x00\x00\x00\x00\x00Hello world."; /// let mut pp = PacketParserBuilder::from_bytes(message_data)? /// .map(true) // Enable mapping. /// .build()? /// .expect("One packet, not EOF"); /// let map = pp.take_map().expect("Mapping is enabled"); /// /// assert_eq!(map.iter().nth(0).unwrap().name(), "CTB"); /// assert_eq!(map.iter().nth(0).unwrap().offset(), 0); /// assert_eq!(map.iter().nth(0).unwrap().as_bytes(), &[0xcb]); /// # Ok(()) } /// ``` pub fn take_map(&mut self) -> Option<map::Map> { self.map.take() } /// Checks if we are processing a signed message using the /// Cleartext Signature Framework. pub(crate) fn processing_csf_message(&self) -> bool { Cookie::processing_csf_message(&self.reader) } } /// This interface allows a caller to read the content of a /// `PacketParser` using the `Read` interface. This is essential to /// supporting streaming operation. /// /// Note: it is safe to mix the use of the `std::io::Read` and /// `BufferedReader` interfaces. impl<'a> io::Read for PacketParser<'a> { fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> { // The BufferedReader interface takes care of hashing the read // values. buffered_reader_generic_read_impl(self, buf) } } /// This interface allows a caller to read the content of a /// `PacketParser` using the `BufferedReader` interface. This is /// essential to supporting streaming operation. /// /// Note: it is safe to mix the use of the `std::io::Read` and /// `BufferedReader` interfaces. impl<'a> BufferedReader<Cookie> for PacketParser<'a> { fn buffer(&self) -> &[u8] { self.reader.buffer() } fn data(&mut self, amount: usize) -> io::Result<&[u8]> { // There is no need to set `content_was_read`, because this // doesn't actually consume any data. self.reader.data(amount) } fn data_hard(&mut self, amount: usize) -> io::Result<&[u8]> { // There is no need to set `content_was_read`, because this // doesn't actually consume any data. self.reader.data_hard(amount) } fn data_eof(&mut self) -> io::Result<&[u8]> { // There is no need to set `content_was_read`, because this // doesn't actually consume any data. self.reader.data_eof() } fn consume(&mut self, amount: usize) -> &[u8] { // This is awkward. Juggle mutable references around. if let Some(mut body_hash) = self.body_hash.take() { let data = self.data_hard(amount) .expect("It is an error to consume more than data returns"); body_hash.update(&data[..amount]); self.body_hash = Some(body_hash); self.content_was_read |= amount > 0; } else { panic!("body_hash is None"); } self.reader.consume(amount) } fn data_consume(&mut self, mut amount: usize) -> io::Result<&[u8]> { // This is awkward. Juggle mutable references around. if let Some(mut body_hash) = self.body_hash.take() { let data = self.data(amount)?; amount = cmp::min(data.len(), amount); body_hash.update(&data[..amount]); self.body_hash = Some(body_hash); self.content_was_read |= amount > 0; } else { panic!("body_hash is None"); } self.reader.data_consume(amount) } fn data_consume_hard(&mut self, amount: usize) -> io::Result<&[u8]> { // This is awkward. Juggle mutable references around. if let Some(mut body_hash) = self.body_hash.take() { let data = self.data_hard(amount)?; body_hash.update(&data[..amount]); self.body_hash = Some(body_hash); self.content_was_read |= amount > 0; } else { panic!("body_hash is None"); } self.reader.data_consume_hard(amount) } fn steal(&mut self, amount: usize) -> io::Result<Vec<u8>> { let v = self.reader.steal(amount)?; self.hash_read_content(&v); Ok(v) } fn steal_eof(&mut self) -> io::Result<Vec<u8>> { let v = self.reader.steal_eof()?; self.hash_read_content(&v); Ok(v) } fn get_mut(&mut self) -> Option<&mut dyn BufferedReader<Cookie>> { None } fn get_ref(&self) -> Option<&dyn BufferedReader<Cookie>> { None } fn into_inner<'b>(self: Box<Self>) -> Option<Box<dyn BufferedReader<Cookie> + 'b>> where Self: 'b { None } fn cookie_set(&mut self, cookie: Cookie) -> Cookie { self.reader.cookie_set(cookie) } fn cookie_ref(&self) -> &Cookie { self.reader.cookie_ref() } fn cookie_mut(&mut self) -> &mut Cookie { self.reader.cookie_mut() } } // Check that we can use the read interface to stream the contents of // a packet. #[cfg(feature = "compression-deflate")] #[test] fn packet_parser_reader_interface() { // We need the Read trait. use std::io::Read; let expected = crate::tests::manifesto(); // A message containing a compressed packet that contains a // literal packet. let pp = PacketParser::from_bytes( crate::tests::message("compressed-data-algo-1.gpg")).unwrap().unwrap(); // The message has the form: // // [ compressed data [ literal data ] ] // // packet is the compressed data packet; ppo is the literal data // packet. let packet_depth = pp.recursion_depth(); let (packet, ppr) = pp.recurse().unwrap(); let pp_depth = ppr.as_ref().unwrap().recursion_depth(); if let Packet::CompressedData(_) = packet { } else { panic!("Expected a compressed data packet."); } let relative_position = pp_depth - packet_depth; assert_eq!(relative_position, 1); let mut pp = ppr.unwrap(); if let Packet::Literal(_) = pp.packet { } else { panic!("Expected a literal data packet."); } // Check that we can read the packet's contents. We do this one // byte at a time to exercise the cursor implementation. for i in 0..expected.len() { let mut buf = [0u8; 1]; let r = pp.read(&mut buf).unwrap(); assert_eq!(r, 1); assert_eq!(buf[0], expected[i]); } // And, now an EOF. let mut buf = [0u8; 1]; let r = pp.read(&mut buf).unwrap(); assert_eq!(r, 0); // Make sure we can still get the next packet (which in this case // is just EOF). let (packet, ppr) = pp.recurse().unwrap(); assert!(ppr.is_eof()); // Since we read all the data, we expect content to be None. assert_eq!(packet.unprocessed_body().unwrap().len(), 0); } impl<'a> PacketParser<'a> { /// Tries to decrypt the current packet. /// /// On success, this function pushes one or more readers onto the /// `PacketParser`'s reader stack, and sets the packet parser's /// `processed` flag (see [`PacketParser::processed`]). /// /// [`PacketParser::processed`]: PacketParser::processed() /// /// If this function is called on a packet that does not contain /// encrypted data, or some of the data was already read, then it /// returns [`Error::InvalidOperation`]. /// /// [`Error::InvalidOperation`]: super::Error::InvalidOperation /// /// # Examples /// /// ```rust /// # fn main() -> sequoia_openpgp::Result<()> { /// use sequoia_openpgp as openpgp; /// use openpgp::Packet; /// use openpgp::fmt::hex; /// use openpgp::types::SymmetricAlgorithm; /// use openpgp::parse::{Parse, PacketParserResult, PacketParser}; /// /// // Parse an encrypted message. /// let message_data: &[u8] = // ... /// # include_bytes!("../tests/data/messages/encrypted-aes256-password-123.gpg"); /// let mut ppr = PacketParser::from_bytes(message_data)?; /// while let PacketParserResult::Some(mut pp) = ppr { /// if let Packet::SEIP(_) = pp.packet { /// pp.decrypt(SymmetricAlgorithm::AES256, /// &hex::decode("7EF4F08C44F780BEA866961423306166\ /// B8912C43352F3D9617F745E4E3939710")? /// .into())?; /// } /// /// // Start parsing the next packet, recursing. /// ppr = pp.recurse()?.1; /// } /// # Ok(()) } /// ``` /// /// # Security Considerations /// /// This functions returns rich errors in case the decryption /// fails. In combination with certain asymmetric algorithms /// (RSA), this may lead to compromise of secret key material or /// (partial) recovery of the message's plain text. See [Section /// 13 of RFC 9580]. /// /// [Section 13 of RFC 9580]: https://www.rfc-editor.org/rfc/rfc9580.html#section-13 /// /// DO NOT relay these errors in situations where an attacker can /// request decryption of messages in an automated fashion. The /// API of the streaming [`Decryptor`] prevents leaking rich /// decryption errors. /// /// [`Decryptor`]: stream::Decryptor /// /// Nevertheless, decrypting messages that do not use an /// authenticated encryption mode in an automated fashion that /// relays or leaks information to a third party is NEVER SAFE due /// to unavoidable format oracles, see [Format Oracles on /// OpenPGP]. /// /// [Format Oracles on OpenPGP]: https://www.ssi.gouv.fr/uploads/2015/05/format-Oracles-on-OpenPGP.pdf pub fn decrypt<A>(&mut self, algo: A, key: &SessionKey) -> Result<()> where A: Into<Option<SymmetricAlgorithm>>, { self.decrypt_(algo.into(), key) } fn decrypt_(&mut self, algo: Option<SymmetricAlgorithm>, key: &SessionKey) -> Result<()> { let indent = self.recursion_depth(); tracer!(TRACE, "PacketParser::decrypt", indent); if self.content_was_read { return Err(Error::InvalidOperation( "Packet's content has already been read.".to_string()).into()); } if self.processed { return Err(Error::InvalidOperation( "Packet not encrypted.".to_string()).into()); } match self.packet.clone() { Packet::SEIP(SEIP::V1(_)) => { let algo = if let Some(a) = algo { a } else { return Err(Error::InvalidOperation( "Trying to decrypt a SEIPDv1 packet: \ no symmetric algorithm given".into()).into()); }; if algo.key_size()? != key.len () { return Err(Error::InvalidOperation( format!("Bad key size: {} expected: {}", key.len(), algo.key_size()?)).into()); } // Get the first blocksize plus two bytes and check // whether we can decrypt them using the provided key. // Don't actually consume them in case we can't. let bl = algo.block_size()?; { let mut dec = Decryptor::new( algo, key, &self.data_hard(bl + 2)?[..bl + 2])?; let mut header = vec![ 0u8; bl + 2 ]; dec.read_exact(&mut header)?; if !(header[bl - 2] == header[bl] && header[bl - 1] == header[bl + 1]) { return Err(Error::InvalidSessionKey( "Decryption failed".into()).into()); } } // Ok, we can decrypt the data. Push a Decryptor and // a HashedReader on the `BufferedReader` stack. // This can't fail, because we create a decryptor // above with the same parameters. let reader = self.take_reader(); let mut reader = BufferedReaderDecryptor::with_cookie( algo, key, reader, Cookie::default()).unwrap(); reader.cookie_mut().level = Some(self.recursion_depth()); t!("Pushing Decryptor, level {:?}.", reader.cookie_ref().level); // And the hasher. let mut reader = HashedReader::new( reader, HashesFor::MDC, vec![HashingMode::Binary(vec![], HashAlgorithm::SHA1)])?; reader.cookie_mut().level = Some(self.recursion_depth()); t!("Pushing HashedReader, level {:?}.", reader.cookie_ref().level); // A SEIP packet is a container that always ends with // an MDC packet. But, if the packet preceding the // MDC packet uses an indeterminate length encoding // (gpg generates these for compressed data packets, // for instance), the parser has to detect the EOF and // be careful to not read any further. Unfortunately, // our decompressor buffers the data. To stop the // decompressor from buffering the MDC packet, we use // a buffered_reader::Reserve. Note: we do this // unconditionally, since it doesn't otherwise // interfere with parsing. // An MDC consists of a 1-byte CTB, a 1-byte length // encoding, and a 20-byte hash. let mut reader = buffered_reader::Reserve::with_cookie( reader, 1 + 1 + 20, Cookie::new(self.recursion_depth())); reader.cookie_mut().fake_eof = true; t!("Pushing buffered_reader::Reserve, level: {}.", self.recursion_depth()); // Consume the header. This shouldn't fail, because // it worked when reading the header. reader.data_consume_hard(bl + 2).unwrap(); self.reader = Box::new(reader); self.processed = true; Ok(()) }, Packet::SEIP(SEIP::V2(seip)) => { let chunk_size = aead::chunk_size_usize(seip.chunk_size())?; // Read the first chunk and check whether we can // decrypt it using the provided key. Don't actually // consume them in case we can't. { // We need a bit more than one chunk so that // `aead::Decryptor` won't see EOF and think that // it has a partial block and it needs to verify // the final chunk. let amount = aead::chunk_size_usize( seip.chunk_digest_size()? + seip.aead().digest_size()? as u64)?; let data = self.data(amount)?; let (message_key, schedule) = aead::SEIPv2Schedule::new( key, seip.symmetric_algo(), seip.aead(), chunk_size, seip.salt())?; let dec = aead::Decryptor::new( seip.symmetric_algo(), seip.aead(), chunk_size, schedule, message_key, &data[..cmp::min(data.len(), amount)])?; let mut chunk = Vec::new(); dec.take(seip.chunk_size() as u64).read_to_end(&mut chunk)?; } // Ok, we can decrypt the data. Push a Decryptor and // a HashedReader on the `BufferedReader` stack. // This can't fail, because we create a decryptor // above with the same parameters. let (message_key, schedule) = aead::SEIPv2Schedule::new( key, seip.symmetric_algo(), seip.aead(), chunk_size, seip.salt())?; let reader = self.take_reader(); let mut reader = aead::BufferedReaderDecryptor::with_cookie( seip.symmetric_algo(), seip.aead(), chunk_size, schedule, message_key, reader, Cookie::default()).unwrap(); reader.cookie_mut().level = Some(self.recursion_depth()); t!("Pushing aead::Decryptor, level {:?}.", reader.cookie_ref().level); self.reader = Box::new(reader); self.processed = true; Ok(()) }, _ => Err(Error::InvalidOperation( format!("Can't decrypt {:?} packets.", self.packet.tag())).into()) } } } #[cfg(test)] mod test { use super::*; use crate::serialize::Serialize; enum Data<'a> { File(&'a str), String(&'a [u8]), } impl<'a> Data<'a> { fn content(&self) -> Vec<u8> { match self { Data::File(filename) => crate::tests::message(filename).to_vec(), Data::String(data) => data.to_vec(), } } } struct DecryptTest<'a> { filename: &'a str, algo: SymmetricAlgorithm, aead_algo: Option<AEADAlgorithm>, key_hex: &'a str, plaintext: Data<'a>, paths: &'a[ (Tag, &'a[ usize ] ) ], } const DECRYPT_TESTS: &[DecryptTest] = &[ // Messages with a relatively simple structure: // // [ SKESK SEIP [ Literal MDC ] ]. // // And simple length encodings (no indeterminate length // encodings). DecryptTest { filename: "encrypted-aes256-password-123.gpg", algo: SymmetricAlgorithm::AES256, aead_algo: None, key_hex: "7EF4F08C44F780BEA866961423306166B8912C43352F3D9617F745E4E3939710", plaintext: Data::File("a-cypherpunks-manifesto.txt"), paths: &[ (Tag::SKESK, &[ 0 ]), (Tag::SEIP, &[ 1 ]), (Tag::Literal, &[ 1, 0 ]), (Tag::MDC, &[ 1, 1 ]), ], }, DecryptTest { filename: "encrypted-aes192-password-123456.gpg", algo: SymmetricAlgorithm::AES192, aead_algo: None, key_hex: "B2F747F207EFF198A6C826F1D398DE037986218ED468DB61", plaintext: Data::File("a-cypherpunks-manifesto.txt"), paths: &[ (Tag::SKESK, &[ 0 ]), (Tag::SEIP, &[ 1 ]), (Tag::Literal, &[ 1, 0 ]), (Tag::MDC, &[ 1, 1 ]), ], }, DecryptTest { filename: "encrypted-aes128-password-123456789.gpg", algo: SymmetricAlgorithm::AES128, aead_algo: None, key_hex: "AC0553096429260B4A90B1CEC842D6A0", plaintext: Data::File("a-cypherpunks-manifesto.txt"), paths: &[ (Tag::SKESK, &[ 0 ]), (Tag::SEIP, &[ 1 ]), (Tag::Literal, &[ 1, 0 ]), (Tag::MDC, &[ 1, 1 ]), ], }, // Created using: // // gpg --compression-algo none \ // --s2k-digest-algo sha256 \ // --cipher-algo camellia256 \ // --s2k-cipher-algo camellia256 \ // --encrypt --symmetric \ // -o encrypted-camellia256-password-123.gpg \ // a-cypherpunks-manifesto.txt DecryptTest { filename: "encrypted-camellia256-password-123.gpg", algo: SymmetricAlgorithm::Camellia256, aead_algo: None, key_hex: "FC9644B500B9D0540880CB44B40F8C89\ A7D817F2EF7EF9DA0D34A574377E300A", plaintext: Data::File("a-cypherpunks-manifesto.txt"), paths: &[ (Tag::SKESK, &[ 0 ]), (Tag::SEIP, &[ 1 ]), (Tag::Literal, &[ 1, 0 ]), (Tag::MDC, &[ 1, 1 ]), ], }, DecryptTest { filename: "encrypted-camellia192-password-123.gpg", algo: SymmetricAlgorithm::Camellia192, aead_algo: None, key_hex: "EC941DB1C5F4D3605E3F3C10B30888DA3287256E55CC978B", plaintext: Data::File("a-cypherpunks-manifesto.txt"), paths: &[ (Tag::SKESK, &[ 0 ]), (Tag::SEIP, &[ 1 ]), (Tag::Literal, &[ 1, 0 ]), (Tag::MDC, &[ 1, 1 ]), ], }, DecryptTest { filename: "encrypted-camellia128-password-123.gpg", algo: SymmetricAlgorithm::Camellia128, aead_algo: None, key_hex: "E1CF87BF2E030CC89CBC0F03EC2B7DF5", plaintext: Data::File("a-cypherpunks-manifesto.txt"), paths: &[ (Tag::SKESK, &[ 0 ]), (Tag::SEIP, &[ 1 ]), (Tag::Literal, &[ 1, 0 ]), (Tag::MDC, &[ 1, 1 ]), ], }, DecryptTest { filename: "encrypted-twofish-password-red-fish-blue-fish.gpg", algo: SymmetricAlgorithm::Twofish, aead_algo: None, key_hex: "96AFE1EDFA7C9CB7E8B23484C718015E5159CFA268594180D4DB68B2543393CB", plaintext: Data::File("a-cypherpunks-manifesto.txt"), paths: &[ (Tag::SKESK, &[ 0 ]), (Tag::SEIP, &[ 1 ]), (Tag::Literal, &[ 1, 0 ]), (Tag::MDC, &[ 1, 1 ]), ], }, // More complex messages. In particular, some of these // messages include compressed data packets, and some are // signed. But what makes these particularly complex is the // use of an indeterminate length encoding, which checks the // buffered_reader::Reserve hack. #[cfg(feature = "compression-deflate")] DecryptTest { filename: "seip/msg-compression-not-signed-password-123.pgp", algo: SymmetricAlgorithm::AES128, aead_algo: None, key_hex: "86A8C1C7961F55A3BE181A990D0ABB2A", plaintext: Data::String(b"compression, not signed\n"), paths: &[ (Tag::SKESK, &[ 0 ]), (Tag::SEIP, &[ 1 ]), (Tag::CompressedData, &[ 1, 0 ]), (Tag::Literal, &[ 1, 0, 0 ]), (Tag::MDC, &[ 1, 1 ]), ], }, #[cfg(feature = "compression-deflate")] DecryptTest { filename: "seip/msg-compression-signed-password-123.pgp", algo: SymmetricAlgorithm::AES128, aead_algo: None, key_hex: "1B195CD35CAD4A99D9399B4CDA4CDA4E", plaintext: Data::String(b"compression, signed\n"), paths: &[ (Tag::SKESK, &[ 0 ]), (Tag::SEIP, &[ 1 ]), (Tag::CompressedData, &[ 1, 0 ]), (Tag::OnePassSig, &[ 1, 0, 0 ]), (Tag::Literal, &[ 1, 0, 1 ]), (Tag::Signature, &[ 1, 0, 2 ]), (Tag::MDC, &[ 1, 1 ]), ], }, DecryptTest { filename: "seip/msg-no-compression-not-signed-password-123.pgp", algo: SymmetricAlgorithm::AES128, aead_algo: None, key_hex: "AFB43B83A4B9D971E4B4A4C53749076A", plaintext: Data::String(b"no compression, not signed\n"), paths: &[ (Tag::SKESK, &[ 0 ]), (Tag::SEIP, &[ 1 ]), (Tag::Literal, &[ 1, 0 ]), (Tag::MDC, &[ 1, 1 ]), ], }, DecryptTest { filename: "seip/msg-no-compression-signed-password-123.pgp", algo: SymmetricAlgorithm::AES128, aead_algo: None, key_hex: "9D5DB92F77F0E4A356EE53813EF2C3DC", plaintext: Data::String(b"no compression, signed\n"), paths: &[ (Tag::SKESK, &[ 0 ]), (Tag::SEIP, &[ 1 ]), (Tag::OnePassSig, &[ 1, 0 ]), (Tag::Literal, &[ 1, 1 ]), (Tag::Signature, &[ 1, 2 ]), (Tag::MDC, &[ 1, 3 ]), ], }, ]; // Consume packets until we get to one in `keep`. fn consume_until<'a>(mut ppr: PacketParserResult<'a>, ignore_first: bool, keep: &[Tag], skip: &[Tag]) -> PacketParserResult<'a> { if ignore_first { ppr = ppr.unwrap().recurse().unwrap().1; } while let PacketParserResult::Some(pp) = ppr { let tag = pp.packet.tag(); for t in keep.iter() { if *t == tag { return PacketParserResult::Some(pp); } } let mut ok = false; for t in skip.iter() { if *t == tag { ok = true; } } if !ok { panic!("Packet not in keep ({:?}) or skip ({:?}) set: {:?}", keep, skip, pp.packet); } ppr = pp.recurse().unwrap().1; } ppr } #[test] fn decrypt_test() { decrypt_test_common(false); } #[test] fn decrypt_test_stream() { decrypt_test_common(true); } #[allow(deprecated)] fn decrypt_test_common(stream: bool) { for test in DECRYPT_TESTS.iter() { if !test.algo.is_supported() { eprintln!("Algorithm {} unsupported, skipping", test.algo); continue; } if let Some(aead_algo) = test.aead_algo { if !aead_algo.is_supported() { eprintln!("AEAD algorithm {} unsupported by selected crypto backend, skipping", aead_algo); continue; } } eprintln!("Decrypting {}, streaming content: {}", test.filename, stream); let ppr = PacketParserBuilder::from_bytes( crate::tests::message(test.filename)).unwrap() .buffer_unread_content() .build() .expect(&format!("Error reading {}", test.filename)[..]); let mut ppr = consume_until( ppr, false, &[ Tag::SEIP, Tag::AED ][..], &[ Tag::SKESK, Tag::PKESK ][..] ); if let PacketParserResult::Some(ref mut pp) = ppr { let key = crate::fmt::from_hex(test.key_hex, false) .unwrap().into(); pp.decrypt(Some(test.algo), &key).unwrap(); } else { panic!("Expected a SEIP packet. Got: {:?}", ppr); } let mut ppr = consume_until( ppr, true, &[ Tag::Literal ][..], &[ Tag::OnePassSig, Tag::CompressedData ][..]); if let PacketParserResult::Some(ref mut pp) = ppr { if stream { let mut body = Vec::new(); loop { let mut b = [0]; if pp.read(&mut b).unwrap() == 0 { break; } body.push(b[0]); } assert_eq!(&body[..], &test.plaintext.content()[..], "{:?}", pp.packet); } else { pp.buffer_unread_content().unwrap(); if let Packet::Literal(l) = &pp.packet { assert_eq!(l.body(), &test.plaintext.content()[..], "{:?}", pp.packet); } else { panic!("Expected literal, got: {:?}", pp.packet); } } } else { panic!("Expected a Literal packet. Got: {:?}", ppr); } let ppr = consume_until( ppr, true, &[ Tag::MDC ][..], &[ Tag::Signature ][..]); if let PacketParserResult::Some( PacketParser { packet: Packet::MDC(ref mdc), .. }) = ppr { assert_eq!(mdc.computed_digest(), mdc.digest(), "MDC doesn't match"); } if ppr.is_eof() { // AED packets don't have an MDC packet. continue; } let ppr = consume_until( ppr, true, &[][..], &[][..]); assert!(ppr.is_eof()); } } #[test] fn message_validator() { for marker in 0..4 { let marker_before = marker & 1 > 0; let marker_after = marker & 2 > 0; for test in DECRYPT_TESTS.iter() { if !test.algo.is_supported() { eprintln!("Algorithm {} unsupported, skipping", test.algo); continue; } if let Some(aead_algo) = test.aead_algo { if !aead_algo.is_supported() { eprintln!("AEAD algorithm {} unsupported by selected crypto backend, skipping", aead_algo); continue; } } let mut buf = Vec::new(); if marker_before { Packet::Marker(Default::default()).serialize(&mut buf).unwrap(); } buf.extend_from_slice(crate::tests::message(test.filename)); if marker_after { Packet::Marker(Default::default()).serialize(&mut buf).unwrap(); } let mut ppr = PacketParserBuilder::from_bytes(&buf) .unwrap() .build() .expect(&format!("Error reading {}", test.filename)[..]); // Make sure we actually decrypted... let mut saw_literal = false; while let PacketParserResult::Some(mut pp) = ppr { pp.possible_message().unwrap(); match pp.packet { Packet::SEIP(_) => { let key = crate::fmt::from_hex(test.key_hex, false) .unwrap().into(); pp.decrypt(Some(test.algo), &key).unwrap(); }, Packet::Literal(_) => { assert!(! saw_literal); saw_literal = true; }, _ => {}, } ppr = pp.recurse().unwrap().1; } assert!(saw_literal); if let PacketParserResult::EOF(eof) = ppr { eof.is_message().unwrap(); } else { unreachable!(); } } } } #[test] fn keyring_validator() { for marker in 0..4 { let marker_before = marker & 1 > 0; let marker_after = marker & 2 > 0; for test in &["testy.pgp", "lutz.gpg", "testy-new.pgp", "neal.pgp"] { let mut buf = Vec::new(); if marker_before { Packet::Marker(Default::default()).serialize(&mut buf).unwrap(); } buf.extend_from_slice(crate::tests::key("testy.pgp")); buf.extend_from_slice(crate::tests::key(test)); if marker_after { Packet::Marker(Default::default()).serialize(&mut buf).unwrap(); } let mut ppr = PacketParserBuilder::from_bytes(&buf) .unwrap() .build() .expect(&format!("Error reading {:?}", test)); while let PacketParserResult::Some(pp) = ppr { assert!(pp.possible_keyring().is_ok()); ppr = pp.recurse().unwrap().1; } if let PacketParserResult::EOF(eof) = ppr { assert!(eof.is_keyring().is_ok()); assert!(eof.is_cert().is_err()); } else { unreachable!(); } } } } #[test] fn cert_validator() { for marker in 0..4 { let marker_before = marker & 1 > 0; let marker_after = marker & 2 > 0; for test in &["testy.pgp", "lutz.gpg", "testy-new.pgp", "neal.pgp"] { let mut buf = Vec::new(); if marker_before { Packet::Marker(Default::default()).serialize(&mut buf).unwrap(); } buf.extend_from_slice(crate::tests::key(test)); if marker_after { Packet::Marker(Default::default()).serialize(&mut buf).unwrap(); } let mut ppr = PacketParserBuilder::from_bytes(&buf) .unwrap() .build() .expect(&format!("Error reading {:?}", test)); while let PacketParserResult::Some(pp) = ppr { assert!(pp.possible_keyring().is_ok()); assert!(pp.possible_cert().is_ok()); ppr = pp.recurse().unwrap().1; } if let PacketParserResult::EOF(eof) = ppr { assert!(eof.is_keyring().is_ok()); assert!(eof.is_cert().is_ok()); } else { unreachable!(); } } } } // If we don't decrypt the SEIP packet, it shows up as opaque // content. #[test] fn message_validator_opaque_content() { for test in DECRYPT_TESTS.iter() { let mut ppr = PacketParserBuilder::from_bytes( crate::tests::message(test.filename)).unwrap() .build() .expect(&format!("Error reading {}", test.filename)[..]); let mut saw_literal = false; while let PacketParserResult::Some(pp) = ppr { assert!(pp.possible_message().is_ok()); match pp.packet { Packet::Literal(_) => { assert!(! saw_literal); saw_literal = true; }, _ => {}, } ppr = pp.recurse().unwrap().1; } assert!(! saw_literal); if let PacketParserResult::EOF(eof) = ppr { eprintln!("eof: {:?}; message: {:?}", eof, eof.is_message()); assert!(eof.is_message().is_ok()); } else { unreachable!(); } } } #[test] fn path() { for test in DECRYPT_TESTS.iter() { if !test.algo.is_supported() { eprintln!("Algorithm {} unsupported, skipping", test.algo); continue; } if let Some(aead_algo) = test.aead_algo { if !aead_algo.is_supported() { eprintln!("AEAD algorithm {} unsupported, skipping", aead_algo); continue; } } eprintln!("Decrypting {}", test.filename); let mut ppr = PacketParserBuilder::from_bytes( crate::tests::message(test.filename)).unwrap() .build() .expect(&format!("Error reading {}", test.filename)[..]); let mut last_path = vec![]; let mut paths = test.paths.to_vec(); // We pop from the end. paths.reverse(); while let PacketParserResult::Some(mut pp) = ppr { let path = paths.pop().expect("Message longer than expect"); assert_eq!(path.0, pp.packet.tag()); assert_eq!(path.1, pp.path()); assert_eq!(last_path, pp.last_path()); last_path = pp.path.to_vec(); eprintln!(" {}: {:?}", pp.packet.tag(), pp.path()); match pp.packet { Packet::SEIP(_) => { let key = crate::fmt::from_hex(test.key_hex, false) .unwrap().into(); pp.decrypt(test.algo, &key).unwrap(); } _ => (), } ppr = pp.recurse().unwrap().1; } paths.reverse(); assert_eq!(paths.len(), 0, "Message shorter than expected (expecting: {:?})", paths); if let PacketParserResult::EOF(eof) = ppr { assert_eq!(last_path, eof.last_path()); } else { panic!("Expect an EOF"); } } } #[test] fn corrupted_cert() { use crate::armor::{Reader, ReaderMode, Kind}; // The following Cert is corrupted about a third the way // through. Make sure we can recover. let mut ppr = PacketParser::from_reader( Reader::from_bytes(crate::tests::key("corrupted.pgp"), ReaderMode::Tolerant(Some(Kind::PublicKey)))) .unwrap(); let mut sigs = 0; let mut subkeys = 0; let mut userids = 0; let mut uas = 0; let mut unknown = 0; while let PacketParserResult::Some(pp) = ppr { match pp.packet { Packet::Signature(_) => sigs += 1, Packet::PublicSubkey(_) => subkeys += 1, Packet::UserID(_) => userids += 1, Packet::UserAttribute(_) => uas += 1, Packet::Unknown(ref p) => { dbg!(p); unknown += 1; }, _ => (), } ppr = pp.next().unwrap().1; } assert_eq!(sigs, 53); assert_eq!(subkeys, 3); assert_eq!(userids, 5); assert_eq!(uas, 0); assert_eq!(unknown, 2); } #[test] fn junk_prefix() { // Make sure we can read the first packet. let msg = crate::tests::message("sig.gpg"); let ppr = PacketParserBuilder::from_bytes(msg).unwrap() .dearmor(packet_parser_builder::Dearmor::Disabled) .build(); assert_match!(Ok(PacketParserResult::Some(ref _pp)) = ppr); // Prepend an invalid byte and make sure we fail. Note: we // have a mechanism to skip corruption, however, that is only // activated once we've seen a good packet. This test checks // that we don't try to recover. let mut msg2 = Vec::new(); msg2.push(0); msg2.extend_from_slice(msg); let ppr = PacketParserBuilder::from_bytes(&msg2[..]).unwrap() .dearmor(packet_parser_builder::Dearmor::Disabled) .build(); assert_match!(Err(_) = ppr); } /// Issue #141. #[test] fn truncated_packet() { for msg in &[crate::tests::message("literal-mode-b.gpg"), crate::tests::message("literal-mode-t-partial-body.gpg"), ] { // Make sure we can read the first packet. let ppr = PacketParserBuilder::from_bytes(msg).unwrap() .dearmor(packet_parser_builder::Dearmor::Disabled) .build(); assert_match!(Ok(PacketParserResult::Some(ref _pp)) = ppr); // Now truncate the packet. let msg2 = &msg[..msg.len() - 1]; let ppr = PacketParserBuilder::from_bytes(msg2).unwrap() .dearmor(packet_parser_builder::Dearmor::Disabled) .build().unwrap(); if let PacketParserResult::Some(pp) = ppr { let err = pp.next().err().unwrap(); assert_match!(Some(&Error::MalformedPacket(_)) = err.downcast_ref()); } else { panic!("No packet!?"); } } } #[test] fn max_packet_size() { use crate::serialize::Serialize; let uid = Packet::UserID("foobar".into()); let mut buf = Vec::new(); uid.serialize(&mut buf).unwrap(); // Make sure we can read it. let ppr = PacketParserBuilder::from_bytes(&buf).unwrap() .build().unwrap(); if let PacketParserResult::Some(pp) = ppr { assert_eq!(Packet::UserID("foobar".into()), pp.packet); } else { panic!("failed to parse userid"); } // But if we set the maximum packet size too low, it is parsed // into an unknown packet. let ppr = PacketParserBuilder::from_bytes(&buf).unwrap() .max_packet_size(5) .build().unwrap(); if let PacketParserResult::Some(pp) = ppr { if let Packet::Unknown(ref u) = pp.packet { assert_eq!(u.tag(), Tag::UserID); assert_match!(Some(&Error::PacketTooLarge(_, _, _)) = u.error().downcast_ref()); } else { panic!("expected an unknown packet, got {:?}", pp.packet); } } else { panic!("failed to parse userid"); } } /// We erroneously assumed that when BufferedReader::next() is /// called, a SEIP container be opaque and hence there cannot be a /// buffered_reader::Reserve on the stack with Cookie::fake_eof /// set. But, we could simply call BufferedReader::next() after /// the SEIP packet is decrypted, or buffer a SEIP packet's body, /// then call BufferedReader::recurse(), which falls back to /// BufferedReader::next() because some data has been read. #[test] fn issue_455() -> Result<()> { let sk: SessionKey = crate::fmt::hex::decode("3E99593760EE241488462BAFAE4FA268\ 260B14B82D310D196DCEC82FD4F67678")?.into(); let algo = SymmetricAlgorithm::AES256; // Decrypt, then call BufferedReader::next(). eprintln!("Decrypt, then next():\n"); let mut ppr = PacketParser::from_bytes( crate::tests::message("encrypted-to-testy.gpg"))?; while let PacketParserResult::Some(mut pp) = ppr { match &pp.packet { Packet::SEIP(_) => { pp.decrypt(algo, &sk)?; }, _ => (), } // Used to trigger the assertion failure on the SEIP // packet: ppr = pp.next()?.1; } // Decrypt, buffer, then call BufferedReader::recurse(). eprintln!("\nDecrypt, buffer, then recurse():\n"); let mut ppr = PacketParser::from_bytes( crate::tests::message("encrypted-to-testy.gpg"))?; while let PacketParserResult::Some(mut pp) = ppr { match &pp.packet { Packet::SEIP(_) => { pp.decrypt(algo, &sk)?; pp.buffer_unread_content()?; }, _ => (), } // Used to trigger the assertion failure on the SEIP // packet: ppr = pp.recurse()?.1; } Ok(()) } /// Crash in the AED parser due to missing chunk size validation. #[test] fn issue_514() -> Result<()> { let data = &[212, 43, 1, 0, 0, 125, 212, 0, 10, 10, 10]; let ppr = PacketParser::from_bytes(&data)?; let packet = &ppr.unwrap().packet; if let Packet::Unknown(_) = packet { Ok(()) } else { panic!("expected unknown packet, got: {:?}", packet); } } /// Malformed subpackets must not cause a hard parsing error. #[test] fn malformed_embedded_signature() -> Result<()> { let ppr = PacketParser::from_bytes( crate::tests::file("edge-cases/malformed-embedded-sig.pgp"))?; let packet = &ppr.unwrap().packet; if let Packet::Unknown(_) = packet { Ok(()) } else { panic!("expected unknown packet, got: {:?}", packet); } } /// Malformed notation names must not cause hard parsing errors. #[test] fn malformed_notation_name() -> Result<()> { let ppr = PacketParser::from_bytes( crate::tests::file("edge-cases/malformed-notation-name.pgp"))?; let packet = &ppr.unwrap().packet; if let Packet::Unknown(_) = packet { Ok(()) } else { panic!("expected unknown packet, got: {:?}", packet); } } /// Checks that the content hash is correctly computed whether /// the content has been (fully) read. #[test] fn issue_537() -> Result<()> { // Buffer unread content. let ppr0 = PacketParserBuilder::from_bytes( crate::tests::message("literal-mode-b.gpg"))? .buffer_unread_content() .build()?; let pp0 = ppr0.unwrap(); let (packet0, _) = pp0.recurse()?; // Drop unread content. let ppr1 = PacketParser::from_bytes( crate::tests::message("literal-mode-b.gpg"))?; let pp1 = ppr1.unwrap(); let (packet1, _) = pp1.recurse()?; // Read content. let ppr2 = PacketParser::from_bytes( crate::tests::message("literal-mode-b.gpg"))?; let mut pp2 = ppr2.unwrap(); io::copy(&mut pp2, &mut io::sink())?; let (packet2, _) = pp2.recurse()?; // Partially read content. let ppr3 = PacketParser::from_bytes( crate::tests::message("literal-mode-b.gpg"))?; let mut pp3 = ppr3.unwrap(); let mut buf = [0]; let nread = pp3.read(&mut buf)?; assert_eq!(buf.len(), nread); let (packet3, _) = pp3.recurse()?; assert_eq!(packet0, packet1); assert_eq!(packet1, packet2); assert_eq!(packet2, packet3); Ok(()) } /// Checks that newlines are properly normalized when verifying /// text signatures. #[test] fn issue_530_verifying() -> Result<()> { use std::io::Write; use crate::*; use crate::packet::signature; use crate::serialize::stream::{Message, Signer}; use crate::policy::StandardPolicy; use crate::{Result, Cert}; use crate::parse::Parse; use crate::parse::stream::*; let data = b"one\r\ntwo\r\nthree"; let p = &StandardPolicy::new(); let cert: Cert = Cert::from_bytes(crate::tests::key("testy-new-private.pgp"))?; let signing_keypair = cert.keys().secret() .with_policy(p, None).alive().revoked(false).for_signing().next().unwrap() .key().clone().into_keypair()?; let mut signature = vec![]; { let message = Message::new(&mut signature); let mut message = Signer::with_template( message, signing_keypair, signature::SignatureBuilder::new(SignatureType::Text) )?.detached().build()?; message.write_all(data)?; message.finalize()?; } struct Helper {} impl VerificationHelper for Helper { fn get_certs(&mut self, _ids: &[KeyHandle]) -> Result<Vec<Cert>> { Ok(vec![Cert::from_bytes(crate::tests::key("testy-new.pgp"))?]) } fn check(&mut self, structure: MessageStructure) -> Result<()> { for (i, layer) in structure.iter().enumerate() { assert_eq!(i, 0); if let MessageLayer::SignatureGroup { results } = layer { assert_eq!(results.len(), 1); results[0].as_ref().unwrap(); assert!(results[0].is_ok()); return Ok(()); } else { unreachable!(); } } unreachable!() } } let h = Helper {}; let mut v = DetachedVerifierBuilder::from_bytes(&signature)? .with_policy(p, None, h)?; for data in &[ &b"one\r\ntwo\r\nthree"[..], // dos b"one\ntwo\nthree", // unix b"one\ntwo\r\nthree", // mixed b"one\r\ntwo\nthree", b"one\rtwo\rthree", // classic mac ] { v.verify_bytes(data)?; } Ok(()) } /// Tests for a panic in the SKESK parser. #[test] fn issue_588() -> Result<()> { let data = vec![0x8c, 0x34, 0x05, 0x12, 0x02, 0x00, 0xaf, 0x0d, 0xff, 0xff, 0x65]; let _ = PacketParser::from_bytes(&data); Ok(()) } /// Tests for a panic in the packet parser. #[test] fn packet_parser_on_mangled_cert() -> Result<()> { // The armored input cert is mangled. Currently, Sequoia // doesn't grok the mangled armor, but it should not panic. let mut ppr = match PacketParser::from_bytes( crate::tests::key("bobs-cert-badly-mangled.asc")) { Ok(ppr) => ppr, Err(_) => return Ok(()), }; while let PacketParserResult::Some(pp) = ppr { dbg!(&pp.packet); if let Ok((_, tmp)) = pp.recurse() { ppr = tmp; } else { break; } } Ok(()) } // Issue 967. #[test] fn packet_before_junk_emitted() -> Result<()> { let bytes = crate::tests::key("testy-new.pgp"); let mut ppr = match PacketParser::from_bytes(bytes) { Ok(ppr) => ppr, Err(_) => panic!("valid"), }; let mut packets_ok = Vec::new(); while let PacketParserResult::Some(pp) = ppr { if let Ok((packet, tmp)) = pp.recurse() { packets_ok.push(packet); ppr = tmp; } else { break; } } let mut bytes = bytes.to_vec(); // Add some junk. bytes.push(0); let mut ppr = match PacketParser::from_bytes(&bytes[..]) { Ok(ppr) => ppr, Err(_) => panic!("valid"), }; let mut packets_mangled = Vec::new(); while let PacketParserResult::Some(pp) = ppr { if let Ok((packet, tmp)) = pp.recurse() { packets_mangled.push(packet); ppr = tmp; } else { break; } } assert_eq!(packets_ok.len(), packets_mangled.len()); assert_eq!(packets_ok, packets_mangled); Ok(()) } /// Tests for a panic in the packet parser. fn parse_message(message: &str) { eprintln!("parsing {:?}", message); let mut ppr = match PacketParser::from_bytes(message) { Ok(ppr) => ppr, Err(_) => return, }; while let PacketParserResult::Some(pp) = ppr { dbg!(&pp.packet); if let Ok((_, tmp)) = pp.recurse() { ppr = tmp; } else { break; } } } /// Tests issue 1005. #[test] fn panic_on_short_zip() { parse_message("-----BEGIN PGP SIGNATURE----- owGjAA0= zXvj -----END PGP SIGNATURE----- "); } /// Tests issue 957. #[test] fn panic_on_malformed_armor() { parse_message("-----BEGIN PGP MESSAGE----- heLBX8Pq0kUBwQz2iFAzRwOdgTBvH5KsDU9lmE -----END PGP MESSAGE----- "); } /// Tests issue 1024. #[test] // XXX: While lenient parsing seemed like the right thing to do, // this breaks equality and round-tripping: we normalize the // non-canonical encoding, so two distinct wire representations // are folded into one in-core representation. #[ignore] fn parse_secret_with_leading_zeros() -> Result<()> { crate::Cert::from_bytes( crate::tests::key("leading-zeros-private.pgp"))? .primary_key().key().clone() .parts_into_secret()? .decrypt_secret(&("hunter22"[..]).into())? .into_keypair()?; Ok(()) } /// Tests that junk pseudo-packets have a proper map when /// buffering is turned on. #[test] #[cfg(feature = "compression-deflate")] fn parse_junk_with_mapping() -> Result<()> { let silly = "-----BEGIN PGP MESSAGE----- yCsBO81bKqlfklugX5yRX5qTopuXX6KbWpFZXKJXUlGSetb4dXm+gYFBCRcA =IHpt -----END PGP MESSAGE----- "; let mut ppr = PacketParserBuilder::from_bytes(silly)? .map(true).buffer_unread_content().build()?; let mut i = 0; while let PacketParserResult::Some(pp) = ppr { assert!(pp.map().unwrap().iter().count() > 0); for f in pp.map().unwrap().iter() { eprintln!("{:?}", f); } ppr = match pp.recurse() { Ok((_, ppr)) => { i += 1; ppr }, Err(_) => { // The third packet is a junk pseudo-packet, and // recursing will fail. assert_eq!(i, 2); break; }, } } Ok(()) } /// Tests for issue 1095, parsing a secret key packet with an /// unknown S2K mechanism. #[test] fn key_unknown_s2k() -> Result<()> { let mut ppr = PacketParser::from_bytes( crate::tests::key("hardware-backed-secret.pgp"))?; let mut i = 0; while let PacketParserResult::Some(pp) = ppr { if i == 0 { assert!(matches!(&pp.packet, Packet::SecretKey(_))); } if i == 3 { assert!(matches!(&pp.packet, Packet::SecretSubkey(_))); } // Make sure it roundtrips. let p = &pp.packet; let v = p.to_vec()?; let q = Packet::from_bytes(&v)?; assert_eq!(p, &q); ppr = pp.recurse()?.1; i += 1; } Ok(()) } } �����������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/src/policy/cutofflist.rs������������������������������������������������������0000644�0000000�0000000�00000042265�10461020230�0017566�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������use std::fmt; use std::ops::{Deref, Index, IndexMut}; use crate::{ Error, Result, types::Timestamp, types::Duration, }; // A `const fn` function can only use a subset of Rust's // functionality. The subset is growing, but we restrict ourselves to // only use `const fn` functionality that is available in Debian // stable, which, as of 2024, is rustc version 1.63.0. This // requires a bit of creativity. #[derive(Debug, Clone)] pub(super) enum VecOrSlice<'a, T> { Vec(Vec<T>), Slice(&'a [T]), } // Make a `VecOrSlice` act like a `Vec`. impl<'a, T> VecOrSlice<'a, T> { // Returns an empty `VecOrSlice`. const fn empty() -> Self { VecOrSlice::Vec(Vec::new()) } // Like `Vec::get`. fn get(&self, i: usize) -> Option<&T> { match self { VecOrSlice::Vec(v) => v.get(i), VecOrSlice::Slice(s) => s.get(i), } } // Like `Vec::len`. fn len(&self) -> usize { match self { VecOrSlice::Vec(v) => v.len(), VecOrSlice::Slice(s) => s.len(), } } // Like `Vec::resize`. fn resize(&mut self, size: usize, value: T) where T: Clone { let v = self.as_mut(); v.resize(size, value); } pub(super) fn as_mut(&mut self) -> &mut Vec<T> where T: Clone { let v: Vec<T> = match self { VecOrSlice::Vec(ref mut v) => std::mem::take(v), VecOrSlice::Slice(s) => s.to_vec(), }; *self = VecOrSlice::Vec(v); if let VecOrSlice::Vec(ref mut v) = self { v } else { unreachable!() } } } impl<'a, T> Deref for VecOrSlice<'a, T> { type Target = [T]; fn deref(&self) -> &Self::Target { match self { VecOrSlice::Vec(ref v) => &v[..], VecOrSlice::Slice(s) => s, } } } impl<'a, T> Index<usize> for VecOrSlice<'a, T> { type Output = T; fn index(&self, i: usize) -> &T { match self { VecOrSlice::Vec(v) => &v[i], VecOrSlice::Slice(s) => &s[i], } } } impl<'a, T> IndexMut<usize> for VecOrSlice<'a, T> where T: Clone { fn index_mut(&mut self, i: usize) -> &mut T { if let VecOrSlice::Slice(s) = self { *self = VecOrSlice::Vec(s.to_vec()); }; match self { VecOrSlice::Vec(v) => &mut v[i], VecOrSlice::Slice(_) => unreachable!(), } } } /// A given algorithm may be considered: completely broken, safe, or /// too weak to be used after a certain time. #[derive(Debug, Clone)] pub(super) struct CutoffList<A> { // Indexed by `A as u8`. // // A value of `None` means that no vulnerabilities are known. // // Note: we use `u64` and not `SystemTime`, because there is no // way to construct a `SystemTime` in a `const fn`. pub(super) cutoffs: VecOrSlice<'static, Option<Timestamp>>, pub(super) _a: std::marker::PhantomData<A>, } pub(super) const REJECT : Option<Timestamp> = Some(Timestamp::UNIX_EPOCH); pub(super) const ACCEPT : Option<Timestamp> = None; pub(super) const DEFAULT_POLICY : Option<Timestamp> = REJECT; impl<A> Default for CutoffList<A> { fn default() -> Self { Self::reject_all() } } impl<A> CutoffList<A> { // Rejects all algorithms. pub(super) const fn reject_all() -> Self { Self { cutoffs: VecOrSlice::empty(), _a: std::marker::PhantomData, } } } impl<A> CutoffList<A> where u8: From<A>, A: fmt::Display, A: std::clone::Clone { // Sets a cutoff time. pub(super) fn set(&mut self, a: A, cutoff: Option<Timestamp>) { let i : u8 = a.into(); let i : usize = i.into(); if i >= self.cutoffs.len() { // We reject by default. self.cutoffs.resize(i + 1, DEFAULT_POLICY); } self.cutoffs[i] = cutoff; } // Returns the cutoff time for algorithm `a`. #[inline] pub(super) fn cutoff(&self, a: A) -> Option<Timestamp> { let i : u8 = a.into(); *self.cutoffs.get(i as usize).unwrap_or(&DEFAULT_POLICY) } // Checks whether the `a` is safe to use at time `time`. // // `tolerance` is added to the cutoff time. #[inline] pub(super) fn check(&self, a: A, time: Timestamp, tolerance: Option<Duration>) -> Result<()> { if let Some(cutoff) = self.cutoff(a.clone()) { let cutoff = cutoff .checked_add(tolerance.unwrap_or_else(|| Duration::seconds(0))) .unwrap_or(Timestamp::MAX); if time >= cutoff { Err(Error::PolicyViolation( a.to_string(), Some(cutoff.into())).into()) } else { Ok(()) } } else { // None => always secure. Ok(()) } } } macro_rules! a_cutoff_list { ($name:ident, $algo:ty, $values_count:expr, $values:expr) => { // It would be nicer to just have a `CutoffList` and store the // default as a `VecOrSlice::Slice`. Unfortunately, we can't // create a slice in a `const fn`, so that doesn't work. // // To work around that issue, we store the array in the // wrapper type, and remember if we are using it or a custom // version. #[derive(Debug, Clone)] enum $name { Default(), Custom(CutoffList<$algo>), } #[allow(unused)] impl $name { const DEFAULTS : [ Option<Timestamp>; $values_count ] = $values; // Turn the `Foo::Default` into a `Foo::Custom`, if // necessary. fn force(&mut self) -> &mut CutoffList<$algo> { use crate::policy::cutofflist::VecOrSlice; if let $name::Default() = self { *self = $name::Custom(CutoffList { cutoffs: VecOrSlice::Vec(Self::DEFAULTS.to_vec()), _a: std::marker::PhantomData, }); } match self { $name::Custom(ref mut l) => l, _ => unreachable!(), } } fn set(&mut self, a: $algo, cutoff: Option<Timestamp>) { self.force().set(a, cutoff) } // Reset the cutoff list to its defaults. fn defaults(&mut self) { *self = Self::Default(); } fn reject_all(&mut self) { *self = Self::Custom(CutoffList::reject_all()); } fn cutoff(&self, a: $algo) -> Option<Timestamp> { use crate::policy::cutofflist::DEFAULT_POLICY; match self { $name::Default() => { let i : u8 = a.into(); let i : usize = i.into(); if i >= Self::DEFAULTS.len() { DEFAULT_POLICY } else { Self::DEFAULTS[i] } } $name::Custom(ref l) => l.cutoff(a), } } fn check(&self, a: $algo, time: Timestamp, d: Option<types::Duration>) -> Result<()> { use crate::policy::cutofflist::VecOrSlice; match self { $name::Default() => { // Convert the default to a `CutoffList` on // the fly to avoid duplicating // `CutoffList::check`. CutoffList { cutoffs: VecOrSlice::Slice(&Self::DEFAULTS[..]), _a: std::marker::PhantomData, }.check(a, time, d) } $name::Custom(ref l) => l.check(a, time, d), } } } } } /// A data structure may have multiple versions. For instance, there /// are multiple versions of packets. Each version of a given packet /// may have different security properties. #[derive(Debug, Clone)] pub(super) struct VersionedCutoffList<A> where A: 'static { // Indexed by `A as u8`. // // A value of `None` means that no vulnerabilities are known. // // Note: we use `u64` and not `SystemTime`, because there is no // way to construct a `SystemTime` in a `const fn`. pub(super) unversioned_cutoffs: VecOrSlice<'static, Option<Timestamp>>, // The content is: (algo, version, policy). pub(super) versioned_cutoffs: VecOrSlice<'static, (A, u8, Option<Timestamp>)>, pub(super) _a: std::marker::PhantomData<A>, } impl<A> Default for VersionedCutoffList<A> { fn default() -> Self { Self::reject_all() } } impl<A> VersionedCutoffList<A> { // Rejects all algorithms. pub(super) const fn reject_all() -> Self { Self { unversioned_cutoffs: VecOrSlice::empty(), versioned_cutoffs: VecOrSlice::empty(), _a: std::marker::PhantomData, } } } impl<A> VersionedCutoffList<A> where u8: From<A>, A: fmt::Display, A: std::clone::Clone, A: Eq, A: Ord, { // versioned_cutoffs must be sorted and deduplicated. Make sure // it is so. pub(super) fn assert_sorted(&self) { if cfg!(debug_assertions) || cfg!(test) { for window in self.versioned_cutoffs.windows(2) { let a = &window[0]; let b = &window[1]; // Sorted, no duplicates. assert!((&a.0, a.1) < (&b.0, b.1)); } } } // Sets a cutoff time for version `version` of algorithm `algo`. pub(super) fn set_versioned(&mut self, algo: A, version: u8, cutoff: Option<Timestamp>) { self.assert_sorted(); let cutofflist = self.versioned_cutoffs.as_mut(); match cutofflist.binary_search_by(|(a, v, _)| { algo.cmp(a).then(version.cmp(v)).reverse() }) { Ok(i) => { // Replace. cutofflist[i] = (algo, version, cutoff); } Err(i) => { // Insert. cutofflist.insert(i, (algo, version, cutoff)); } }; self.assert_sorted(); } // Sets a cutoff time for algorithm `algo`. pub(super) fn set_unversioned(&mut self, algo: A, cutoff: Option<Timestamp>) { let i: u8 = algo.into(); let i: usize = i.into(); if i >= self.unversioned_cutoffs.len() { // We reject by default. self.unversioned_cutoffs.resize(i + 1, DEFAULT_POLICY); } self.unversioned_cutoffs[i] = cutoff; } // Returns the cutoff time for version `version` of algorithm `algo`. #[inline] pub(super) fn cutoff(&self, algo: A, version: u8) -> Option<Timestamp> { self.assert_sorted(); match self.versioned_cutoffs.binary_search_by(|(a, v, _)| { algo.cmp(a).then(version.cmp(v)).reverse() }) { Ok(i) => { self.versioned_cutoffs[i].2 } Err(_loc) => { // Fallback to the unversioned cutoff list. *self.unversioned_cutoffs.get(u8::from(algo) as usize) .unwrap_or(&DEFAULT_POLICY) } } } // Checks whether version `version` of the algorithm `algo` is safe // to use at time `time`. // // `tolerance` is added to the cutoff time. #[inline] pub(super) fn check(&self, algo: A, version: u8, time: Timestamp, tolerance: Option<Duration>) -> Result<()> { if let Some(cutoff) = self.cutoff(algo.clone(), version) { let cutoff = cutoff .checked_add(tolerance.unwrap_or_else(|| Duration::seconds(0))) .unwrap_or(Timestamp::MAX); if time >= cutoff { Err(Error::PolicyViolation( format!("{} v{}", algo, version), Some(cutoff.into())).into()) } else { Ok(()) } } else { // None => always secure. Ok(()) } } } macro_rules! a_versioned_cutoff_list { ($name:ident, $algo:ty, // A slice indexed by the algorithm. $unversioned_values_count: expr, $unversioned_values: expr, // A slice of the form: [ (algo, version, cutoff), ... ] // // Note: the values must be sorted and (algo, version) must be // unique! $versioned_values_count:expr, $versioned_values:expr) => { // It would be nicer to just have a `CutoffList` and store the // default as a `VecOrSlice::Slice`. Unfortunately, we can't // create a slice in a `const fn`, so that doesn't work. // // To work around that issue, we store the array in the // wrapper type, and remember if we are using it or a custom // version. #[derive(Debug, Clone)] enum $name { Default(), Custom(VersionedCutoffList<$algo>), } impl std::ops::Deref for $name { type Target = VersionedCutoffList<$algo>; fn deref(&self) -> &Self::Target { match self { $name::Default() => &Self::DEFAULT, $name::Custom(l) => l, } } } #[allow(unused)] impl $name { const VERSIONED_DEFAULTS: [ ($algo, u8, Option<Timestamp>); $versioned_values_count ] = $versioned_values; const UNVERSIONED_DEFAULTS: [ Option<Timestamp>; $unversioned_values_count ] = $unversioned_values; const DEFAULT: VersionedCutoffList<$algo> = VersionedCutoffList { versioned_cutoffs: crate::policy::cutofflist::VecOrSlice::Slice( &Self::VERSIONED_DEFAULTS), unversioned_cutoffs: crate::policy::cutofflist::VecOrSlice::Slice( &Self::UNVERSIONED_DEFAULTS), _a: std::marker::PhantomData, }; // Turn the `Foo::Default` into a `Foo::Custom`, if // necessary, to allow modification. fn force(&mut self) -> &mut VersionedCutoffList<$algo> { use crate::policy::cutofflist::VecOrSlice; if let $name::Default() = self { *self = Self::Custom($name::DEFAULT); } match self { $name::Custom(ref mut l) => l, _ => unreachable!(), } } // Set the cutoff for the specified version of the // specified algorithm. fn set_versioned(&mut self, algo: $algo, version: u8, cutoff: Option<Timestamp>) { self.force().set_versioned(algo, version, cutoff) } // Sets the cutoff for the specified algorithm independent // of its version. fn set_unversioned(&mut self, algo: $algo, cutoff: Option<Timestamp>) { // Clear any versioned cutoffs. let l = self.force(); l.versioned_cutoffs.as_mut().retain(|(a, _v, _c)| { &algo != a }); l.set_unversioned(algo, cutoff) } // Resets the cutoff list to its defaults. fn defaults(&mut self) { *self = Self::Default(); } // Causes the cutoff list to reject everything. fn reject_all(&mut self) { *self = Self::Custom(VersionedCutoffList::reject_all()); } // Returns the cutoff for the specified version of the // specified algorithm. // // This first considers the versioned cutoff list. If // there is no entry in the versioned list, it fallsback // to the unversioned cutoff list. If there is also no // entry there, then it falls back to the default. fn cutoff(&self, algo: $algo, version: u8) -> Option<Timestamp> { let cutofflist = if let $name::Custom(ref l) = self { l } else { &Self::DEFAULT }; cutofflist.cutoff(algo, version) } fn check(&self, algo: $algo, version: u8, time: Timestamp, d: Option<types::Duration>) -> Result<()> { let cutofflist = if let $name::Custom(ref l) = self { l } else { &Self::DEFAULT }; cutofflist.check(algo, version, time, d) } } // Make sure VERSIONED_DEFAULTS is sorted and the keys are // unique. #[test] #[allow(non_snake_case)] fn $name() { $name::DEFAULT.assert_sorted(); } } } �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/src/policy.rs�����������������������������������������������������������������0000644�0000000�0000000�00000362016�10461020230�0015403�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������//! A mechanism to specify policy. //! //! A major goal of the Sequoia OpenPGP crate is to be policy free. //! However, many mid-level operations build on low-level primitives. //! For instance, finding a certificate's primary User ID means //! examining each of its User IDs and their current self-signature. //! Some algorithms are considered broken (e.g., MD5) and some are //! considered weak (e.g. SHA-1). When dealing with data from an //! untrusted source, for instance, callers will often prefer to //! ignore signatures that rely on these algorithms even though [RFC //! 4880] says that "\[i\]mplementations MUST implement SHA-1." When //! trying to decrypt old archives, however, users probably don't want //! to ignore keys using MD5, even though [Section 9.5 of RFC 9580] //! deprecates MD5. //! //! Rather than not provide this mid-level functionality, the `Policy` //! trait allows callers to specify their preferred policy. This can be //! highly customized by providing a custom implementation of the //! `Policy` trait, or it can be slightly refined by tweaking the //! `StandardPolicy`'s parameters. //! //! When implementing the `Policy` trait, it is *essential* that the //! functions are [pure]. That is, if the same `Policy` is used //! to determine whether a given `Signature` is valid, it must always //! return the same value. //! //! [Section 9.5 of RFC 9580]: https://www.rfc-editor.org/rfc/rfc9580.html#section-9.5 //! [pure]: https://en.wikipedia.org/wiki/Pure_function use std::fmt; use std::time::{SystemTime, Duration}; use std::u32; use anyhow::Context; use crate::{ cert::prelude::*, Error, Packet, packet::{ key, Signature, signature::subpacket::{ SubpacketTag, SubpacketValue, }, Tag, }, Result, types, types::{ AEADAlgorithm, HashAlgorithm, SignatureType, SymmetricAlgorithm, Timestamp, }, }; #[macro_use] mod cutofflist; use cutofflist::{ CutoffList, REJECT, ACCEPT, VersionedCutoffList, }; /// A policy for cryptographic operations. pub trait Policy : fmt::Debug + Send + Sync { /// Returns an error if the signature violates the policy. /// /// This function performs the last check before the library /// decides that a signature is valid. That is, after the library /// has determined that the signature is well-formed, alive, not /// revoked, etc., it calls this function to allow you to /// implement any additional policy. For instance, you may reject /// signatures that make use of cryptographically insecure /// algorithms like SHA-1. /// /// Note: Whereas it is generally better to reject suspicious /// signatures, one should be more liberal when considering /// revocations: if you reject a revocation certificate, it may /// inadvertently make something else valid! fn signature(&self, _sig: &Signature, _sec: HashAlgoSecurity) -> Result<()> { Err(Error::PolicyViolation( "By default all signatures are rejected.".into(), None).into()) } /// Returns an error if the key violates the policy. /// /// This function performs one of the last checks before a /// `KeyAmalgamation` or a related data structures is turned into /// a `ValidKeyAmalgamation`, or similar. /// /// Internally, the library always does this before using a key. /// The sole exception is when creating a key using `CertBuilder`. /// In that case, the primary key is not validated before it is /// used to create any binding signatures. /// /// Thus, you can prevent keys that make use of insecure /// algorithms, don't have a sufficiently high security margin /// (e.g., 1024-bit RSA keys), are on a bad list, etc. from being /// used here. /// /// If you implement this function, make sure to consider the Key /// Derivation Function and Key Encapsulation parameters of ECDH /// keys, see [`PublicKey::ECDH`]. /// /// [`PublicKey::ECDH`]: crate::crypto::mpi::PublicKey::ECDH fn key(&self, _ka: &ValidErasedKeyAmalgamation<key::PublicParts>) -> Result<()> { Err(Error::PolicyViolation( "By default all keys are rejected.".into(), None).into()) } /// Returns an error if the symmetric encryption algorithm /// violates the policy. /// /// This function performs the last check before an encryption /// container is decrypted by the streaming decryptor. /// /// With this function, you can prevent the use of insecure /// symmetric encryption algorithms. fn symmetric_algorithm(&self, _algo: SymmetricAlgorithm) -> Result<()> { Err(Error::PolicyViolation( "By default all symmetric algorithms are rejected.".into(), None).into()) } /// Returns an error if the AEAD mode violates the policy. /// /// This function performs the last check before an encryption /// container is decrypted by the streaming decryptor. /// /// With this function, you can prevent the use of insecure AEAD /// constructions. /// /// This feature is [experimental](super#experimental-features). fn aead_algorithm(&self, _algo: AEADAlgorithm) -> Result<()> { Err(Error::PolicyViolation( "By default all AEAD algorithms are rejected.".into(), None).into()) } /// Returns an error if the packet violates the policy. /// /// This function performs the last check before a packet is /// considered by the streaming verifier and decryptor. /// /// With this function, you can prevent the use of insecure /// encryption containers, notably the *Symmetrically Encrypted /// Data Packet*. fn packet(&self, _packet: &Packet) -> Result<()> { Err(Error::PolicyViolation( "By default all packets are rejected.".into(), None).into()) } } /// Whether the signed data requires a hash algorithm with collision /// resistance. /// /// Since the context of a signature is not passed to /// `Policy::signature`, it is not possible to determine from that /// function whether the signature requires a hash algorithm with /// collision resistance. This enum indicates this. /// /// In short, many self signatures only require second pre-image /// resistance. This can be used to extend the life of hash /// algorithms whose collision resistance has been partially /// compromised. Be careful. Read the background and the warning /// before accepting the use of weak hash algorithms! /// /// # Warning /// /// Although distinguishing whether signed data requires collision /// resistance can be used to permit the continued use of a hash /// algorithm in certain situations, once attacks against a hash /// algorithm are known, it is imperative to retire the use of the /// hash algorithm as soon as it is feasible. Cryptoanalytic attacks /// improve quickly, as demonstrated by the attacks on SHA-1. /// /// # Background /// /// Cryptographic hash functions normally have three security /// properties: /// /// - Pre-image resistance, /// - Second pre-image resistance, and /// - Collision resistance. /// /// A hash algorithm has pre-image resistance if given a hash `h`, it /// is impractical for an attacker to find a message `m` such that `h /// = hash(m)`. In other words, a hash algorithm has pre-image /// resistance if it is hard to invert. A hash algorithm has second /// pre-image resistance if it is impractical for an attacker to find /// a second message with the same hash as the first. That is, given /// `m1`, it is hard for an attacker to find an `m2` such that /// `hash(m1) = hash(m2)`. And, a hash algorithm has collision /// resistance if it is impractical for an attacker to find two /// messages with the same hash. That is, it is hard for an attacker /// to find an `m1` and an `m2` such that `hash(m1) = hash(m2)`. /// /// In the context of verifying an OpenPGP signature, we don't need a /// hash algorithm with pre-image resistance. Pre-image resistance is /// only required when the message is a secret, e.g., a password. We /// always need a hash algorithm with second pre-image resistance, /// because an attacker must not be able to repurpose an arbitrary /// signature, i.e., create a collision with respect to a *known* /// hash. And, we need collision resistance when a signature is over /// data that could have been influenced by an attacker: if an /// attacker creates a pair of colliding messages and convinces the /// user to sign one of them, then the attacker can copy the signature /// to the other message. /// /// Collision resistance implies second pre-image resistance, but not /// vice versa. If an attacker can find a second message with the /// same hash as some known message, they can also create a collision /// by choosing an arbitrary message and using their pre-image attack /// to find a colliding message. Thus, a context that requires /// collision resistance also requires second pre-image resistance. /// /// Because collision resistance is with respect to two arbitrary /// messages, collision resistance is always susceptible to a /// [birthday paradox]. This means that the security margin of a hash /// algorithm's collision resistance is half of the security margin of /// its second pre-image resistance. And, in practice, the collision /// resistance of industry standard hash algorithms has been /// practically attacked multiple times. In the context of SHA-1, /// Wang et al. described how to find collisions in SHA-1 in their /// 2005 paper [Finding Collisions in the Full SHA-1]. In 2017, /// Stevens et al. published [The First Collision for Full SHA-1], /// which demonstrates the first practical attack on SHA-1's collision /// resistance, an identical-prefix collision attack. This attack /// only gives the attacker limited control over the content of the /// collided messages, which limits its applicability. However, in /// 2020, Leurent and Peyrin published [SHA-1 is a Shambles], which /// demonstrates a practical chosen-prefix collision attack. This /// attack gives the attacker complete control over the prefixes of /// the collided messages. /// /// [birthday paradox]: https://en.wikipedia.org/wiki/Birthday_attack#Digital_signature_susceptibility /// [Finding Collisions in the Full SHA-1]: https://link.springer.com/chapter/10.1007/11535218_2 /// [The first collision for full SHA-1]: https://shattered.io/ /// [SHA-1 is a Shambles]: https://sha-mbles.github.io/ /// /// A chosen-prefix collision attack works as follows: an attacker /// chooses two arbitrary message prefixes, and then searches for /// so-called near collision blocks. These near collision blocks /// cause the internal state of the hashes to converge and eventually /// result in a collision, i.e., an identical hash value. The attack /// described in the [SHA-1 is a Shambles] paper requires 8 to 10 near /// collision blocks (512 to 640 bytes) to fully synchronize the /// internal state. /// /// SHA-1 is a [Merkle-Damgård hash function]. This means that the /// hash function processes blocks one after the other, and the /// internal state of the hash function at any given point only /// depends on earlier blocks in the stream. A consequence of this is /// that it is possible to append a common suffix to the collided /// messages without any additional computational effort. That is, if /// `hash(m1) = hash(m2)`, then it necessarily holds that `hash(m1 || /// suffix) = hash(m2 || suffix)`. This is called a [length extension /// attack]. /// /// [Merkle-Damgård hash function]: https://en.wikipedia.org/wiki/Merkle%E2%80%93Damg%C3%A5rd_construction /// [length extension attack]: https://en.wikipedia.org/wiki/Length_extension_attack /// /// Thus, the [SHA-1 is a Shambles] attack solves the following: /// /// ```text /// hash(m1 || collision blocks 1 || suffix) = hash(m2 || collision blocks 2 || suffix) /// ``` /// /// Where `m1`, `m2`, and `suffix` are controlled by the attacker, and /// only the collision blocks are controlled by the algorithm. /// /// If an attacker can convince an OpenPGP user to sign a message of /// their choosing (some `m1 || collision blocks 1 || suffix`), then /// the attacker also has a valid signature from the victim for a /// colliding message (some `m2 || collision blocks 2 || suffix`). /// /// The OpenPGP format imposes some additional constraints on the /// attacker. Although the attacker may control the message, the /// signature is also over a [signature packet], and a trailer. /// Specifically, [the following is signed] when signing a document: /// /// ```text /// hash(document || sig packet || 0x04 || sig packet len) /// ``` /// /// and the [following is signed] when signing a binding signature: /// /// ```text /// hash(public key || subkey || sig packet || 0x04 || sig packet len) /// ``` /// /// [signature packet]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3 /// [the following is signed]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.4 /// /// Since the signature packet is chosen by the victim's OpenPGP /// implementation, the attacker may be able to predict it, but they /// cannot store the collision blocks there. Thus, the signature /// packet is necessarily part of the common suffix, and the collision /// blocks must occur earlier in the stream. /// /// This restriction on the signature packet means that an attacker /// cannot convince the victim to sign a document, and then transfer /// that signature to a colliding binding signature. These signatures /// necessarily have different [signature packet]s: the value of the /// [signature type] field is different. And, as just described, for /// this attack, the signature packets must be identical, because they /// are part of the common suffix. Finally, the trailer, which /// contains the signature packet's length, prevents hiding a /// signature in a signature. /// /// [signature type]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.1 /// /// Given this, if we know for a given signature type that an attacker /// cannot control any of the data that is signed, then that type of /// signature does not need collision resistance; it is still /// vulnerable to an attack on the hash's second pre-image resistance /// (a collision with a specific message), but not one on its /// collision resistance (a collision with any message). This is the /// case for binding signatures, and direct key signatures. But, it /// is not normally the case for documents (the attacker may be able /// to control the content of the document), certifications (the /// attacker may be able to control the key packet, the User ID /// packet, or the User Attribute packet), or certificate revocations /// (the attacker may be able to control the key packet). /// /// Certification signatures and revocations signatures can be further /// divided into self signatures and third-party signatures. If an /// attacker can convince a victim into signing a third-party /// signature, as was done in the [SHA-1 is a Shambles], they may be /// able to transfer the signature to a colliding self signature. If /// we can show that an attacker can't collide a self signature, and a /// third-party signature, then we may be able to show that self /// signatures don't require collision resistance. The same /// consideration holds for revocations and third-party revocations. /// /// We first consider revocations, which are more straightforward. /// The attack is the following: an attacker creates a fake /// certificate (A), and sets the victim as a designated revoker. /// They then ask the victim to revoke their certificate (V). The /// attacker than transfers the signature to a colliding self /// revocation, which causes the victim's certificate (V) to be /// revoked. /// /// A revocation is over a public key packet and a signature packet. /// In this scenario, the attacker controls the fake certificate (A) /// and thus the public key packet that the victim actually signs. /// But the victim's public key packet is determined by their /// certificate (V). Thus, the attacker would have to insert the near /// collision blocks in the signature packet, which, as we argued /// before, is not possible. Thus, it is safe to only use a hash with /// pre-image resistance to protect a self-revocation. /// /// We now turn to self signatures. The attack is similar to the /// [SHA-1 is a Shambles] attack. An attacker creates a certificate /// (A) and convinces the victim to sign it. The attacker can then /// transfer the third-party certification to a colliding self /// signature for the victim's certificate (V). If successful, this /// attack allows the attacker to add a User ID or a User Attribute to /// the victim's certificate (V). This can confuse people who use the /// victim's certificate. For instance, if the attacker adds the /// identity `alice@example.org` to the victim's certificate, and Bob /// receives a message signed using the victim's certificate (V), he /// may think that Alice signed the message instead of the victim. /// Bob won't be tricked if he uses strong authentication, but many /// OpenPGP users use weak authentication (e.g., TOFU) or don't /// authenticate keys at all. /// /// A certification is over a public key packet, a User ID or User /// Attribute packet, and a signature packet. The attacker controls /// the fake certificate (A) and therefore the public key packet, and /// the User ID or User Attribute packet that the victim signs. /// However, to trick the victim, the User ID packet or User Attribute /// packet needs to correspond to an identity that the attacker /// appears to control. Thus, if the near collision blocks are stored /// in the User ID or User Attribute packet of A, they have to be /// hidden to avoid making the victim suspicious. This is /// straightforward for User Attributes, which are currently images, /// and have many places to hide this type of data. However, User IDs /// are normally [UTF-8 encoded RFC 2822 mailbox]es, which makes /// hiding half a kilobyte of binary data impractical. The attacker /// does not control the victim's public key (in V). But, they do /// control the malicious User ID or User Attribute that they want to /// attack to the victim's certificate (V). But again, the near /// collision blocks have to be hidden in order to trick Bob, the /// second victim. Thus, the attack has two possibilities: they can /// hide the near collision blocks in the fake public key (in A), and /// the User ID or User Attribute (added to V); or, they can hide them /// in the fake User IDs or User Attributes (in A and the one added to /// V). /// /// As evidenced by the [SHA-1 is a Shambles] attack, it is possible /// to hide near collision blocks in User Attribute packets. Thus, /// this attack can be used to transfer a third-party certification /// over a User Attribute to a self signature over a User Attribute. /// As such, self signatures over User Attributes need collision /// resistance. /// /// The final case to consider is hiding the near collision blocks in /// the User ID that the attacker wants to add to the victim's /// certificate. Again, it is possible to store the near collision /// blocks there. However, there are two mitigating factors. First, /// there is no place to hide the blocks. As such, the user must be /// convinced to ignore them. Second, a User ID is structure: it /// normally contains a [UTF-8 encoded RFC 2822 mailbox]. Thus, if we /// only consider valid UTF-8 strings, and limit the maximum size, we /// can dramatically increase the workfactor, which can extend the life /// of a hash algorithm whose collision resistance has been weakened. /// /// [UTF-8 encoded RFC 2822 mailbox]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.11 #[derive(Debug, Clone, Copy, Eq, PartialEq)] pub enum HashAlgoSecurity { /// The signed data only requires second pre-image resistance. /// /// If a signature is over data that an attacker cannot influence, /// then the hash function does not need to provide collision /// resistance. This is **only** the case for: /// /// - Subkey binding signatures /// - Primary key binding signatures /// - Self revocations /// /// Due to the structure of User IDs (they are normally short, /// UTF-8 encoded RFC 2822 mailboxes), self signatures over short, /// reasonable User IDs (**not** User Attributes) also don't /// require strong collision resistance. Thus, we also only /// require a signature with second pre-image resistance for: /// /// - Self signatures over reasonable User IDs SecondPreImageResistance, /// The signed data requires collision resistance. /// /// If a signature is over data that an attacker can influence, /// then the hash function must provide collision resistance. /// This is the case for documents, third-party certifications, /// and third-party revocations. /// /// Note: collision resistance implies second pre-image /// resistance. Thus, when evaluating whether a hash algorithm /// has collision resistance, we also check whether it has second /// pre-image resistance. CollisionResistance, } impl Default for HashAlgoSecurity { /// The default is the most conservative policy. fn default() -> Self { HashAlgoSecurity::CollisionResistance } } /// The standard policy. /// /// The standard policy stores when each algorithm in a family of /// algorithms is no longer considered safe. Attempts to use an /// algorithm after its cutoff time should fail. /// /// A `StandardPolicy` can be configured using Rust. Sometimes it is /// useful to configure it via a configuration file. This can be done /// using the [`sequoia-policy-config`] crate. /// /// [`sequoia-policy-config`]: https://docs.rs/sequoia-policy-config/latest/sequoia_policy_config/ /// /// It is recommended to support using a configuration file when the /// program should respect the system's crypto policy. This is /// required on Fedora, for instance. See the [Fedora Crypto /// Policies] project for more information. /// /// [Fedora]: https://gitlab.com/redhat-crypto/fedora-crypto-policies /// /// When validating a signature, we normally want to know whether the /// algorithms used are safe *now*. That is, we don't use the /// signature's alleged creation time when considering whether an /// algorithm is safe, because if an algorithm is discovered to be /// compromised at time X, then an attacker could forge a message /// after time X with a signature creation time that is prior to X, /// which would be incorrectly accepted. /// /// Occasionally, we know that a signature has not been tampered with /// since some time in the past. We might know this if the signature /// was stored on some tamper-proof medium. In those cases, it is /// reasonable to use the time that the signature was saved, since an /// attacker could not have taken advantage of any weaknesses found /// after that time. /// /// # Examples /// /// A `StandardPolicy` object can be used to build specialized policies. /// For example the following policy filters out Persona certifications mimicking /// what GnuPG does when calculating the Web of Trust. /// /// ```rust /// use sequoia_openpgp as openpgp; /// use std::io::{Cursor, Read}; /// use openpgp::Result; /// use openpgp::packet::{Packet, Signature, key::PublicParts}; /// use openpgp::cert::prelude::*; /// use openpgp::parse::Parse; /// use openpgp::armor::{Reader, ReaderMode, Kind}; /// use openpgp::policy::{HashAlgoSecurity, Policy, StandardPolicy}; /// use openpgp::types::{ /// SymmetricAlgorithm, /// AEADAlgorithm, /// SignatureType /// }; /// /// #[derive(Debug)] /// struct RejectPersonaCertificationsPolicy<'a>(StandardPolicy<'a>); /// /// impl Policy for RejectPersonaCertificationsPolicy<'_> { /// fn key(&self, ka: &ValidErasedKeyAmalgamation<PublicParts>) /// -> Result<()> /// { /// self.0.key(ka) /// } /// /// fn signature(&self, sig: &Signature, sec: HashAlgoSecurity) -> Result<()> { /// if sig.typ() == SignatureType::PersonaCertification { /// Err(anyhow::anyhow!("Persona certifications are ignored.")) /// } else { /// self.0.signature(sig, sec) /// } /// } /// /// fn symmetric_algorithm(&self, algo: SymmetricAlgorithm) -> Result<()> { /// self.0.symmetric_algorithm(algo) /// } /// /// fn aead_algorithm(&self, algo: AEADAlgorithm) -> Result<()> { /// self.0.aead_algorithm(algo) /// } /// /// fn packet(&self, packet: &Packet) -> Result<()> { /// self.0.packet(packet) /// } /// } /// /// impl RejectPersonaCertificationsPolicy<'_> { /// fn new() -> Self { /// Self(StandardPolicy::new()) /// } /// } /// /// # fn main() -> Result<()> { /// // this key has one persona certification /// let data = r#" /// -----BEGIN PGP PUBLIC KEY BLOCK----- /// /// mDMEX7JGrxYJKwYBBAHaRw8BAQdASKGcnowaZBDc2Z3rZZlWb6jEjne9sK76afbJ /// trd5Uw+0BlRlc3QgMoiQBBMWCAA4FiEEyZ6oBYFia3z+ooCBqR9BqiGp8AQFAl+y /// Rq8CGwMFCwkIBwIGFQoJCAsCBBYCAwECHgECF4AACgkQqR9BqiGp8ASfxwEAvEb0 /// bFr7ZgFZSDOITNptm+FEynib8mmLACsvHAmCjvIA+gOaSNyxMW6N59q7/j0sDjp1 /// aYNgpNFLbYBZpkXXVL0GiHUEERYIAB0WIQTE4QfdkkisIbWVOcHmlsuS3dbWEwUC /// X7JG4gAKCRDmlsuS3dbWExEwAQCpqfiVMhjDwVFMsMpwd5r0N/8rAx8/nmgpCsK3 /// M9TUrAD7BhTYVPRbkJqTZYd9DlLtBcbF3yNPTHlB+F2sFjI+cgo= /// =ZfYu /// -----END PGP PUBLIC KEY BLOCK----- /// "#; /// /// let mut cursor = Cursor::new(&data); /// let mut reader = Reader::from_reader(&mut cursor, ReaderMode::Tolerant(Some(Kind::PublicKey))); /// /// let mut buf = Vec::new(); /// reader.read_to_end(&mut buf)?; /// let cert = Cert::from_bytes(&buf)?; /// /// let ref sp = StandardPolicy::new(); /// let u = cert.with_policy(sp, None)?.userids().nth(0).unwrap(); /// /// // Under the standard policy the persona certification is visible. /// assert_eq!(u.certifications().count(), 1); /// /// // Under our custom policy the persona certification is not available. /// let ref p = RejectPersonaCertificationsPolicy::new(); /// assert_eq!(u.with_policy(p, None)?.certifications().count(), 0); /// # /// # Ok(()) /// # } /// ``` #[derive(Clone, Debug)] pub struct StandardPolicy<'a> { // The time. If None, the current time is used. time: Option<Timestamp>, // Hash algorithms. collision_resistant_hash_algos: CollisionResistantHashCutoffList, second_pre_image_resistant_hash_algos: SecondPreImageResistantHashCutoffList, hash_revocation_tolerance: types::Duration, // Critical subpacket tags. critical_subpackets: SubpacketTagCutoffList, // Critical notation good-list. good_critical_notations: &'a [&'a str], // Packet types. packet_tags: PacketTagCutoffList, // Symmetric algorithms. symmetric_algos: SymmetricAlgorithmCutoffList, // AEAD algorithms. aead_algos: AEADAlgorithmCutoffList, // Asymmetric algorithms. asymmetric_algos: AsymmetricAlgorithmCutoffList, } assert_send_and_sync!(StandardPolicy<'_>); impl<'a> Default for StandardPolicy<'a> { fn default() -> Self { Self::new() } } impl<'a> From<&'a StandardPolicy<'a>> for Option<&'a dyn Policy> { fn from(p: &'a StandardPolicy<'a>) -> Self { Some(p as &dyn Policy) } } // Signatures that require a hash with collision Resistance and second // Pre-image Resistance. See the documentation for HashAlgoSecurity // for more details. a_cutoff_list!(CollisionResistantHashCutoffList, HashAlgorithm, 15, [ REJECT, // 0. Not assigned. Some(Timestamp::Y1997M2), // 1. MD5 Some(Timestamp::Y2013M2), // 2. SHA-1 Some(Timestamp::Y2013M2), // 3. RIPE-MD/160 REJECT, // 4. Reserved. REJECT, // 5. Reserved. REJECT, // 6. Reserved. REJECT, // 7. Reserved. ACCEPT, // 8. SHA256 ACCEPT, // 9. SHA384 ACCEPT, // 10. SHA512 ACCEPT, // 11. SHA224 ACCEPT, // 12. SHA3-256 REJECT, // 13. Reserved. ACCEPT, // 14. SHA3-512 ]); // Signatures that *only* require a hash with Second Pre-image // Resistance. See the documentation for HashAlgoSecurity for more // details. a_cutoff_list!(SecondPreImageResistantHashCutoffList, HashAlgorithm, 15, [ REJECT, // 0. Not assigned. Some(Timestamp::Y2004M2), // 1. MD5 Some(Timestamp::Y2023M2), // 2. SHA-1 Some(Timestamp::Y2013M2), // 3. RIPE-MD/160 REJECT, // 4. Reserved. REJECT, // 5. Reserved. REJECT, // 6. Reserved. REJECT, // 7. Reserved. ACCEPT, // 8. SHA256 ACCEPT, // 9. SHA384 ACCEPT, // 10. SHA512 ACCEPT, // 11. SHA224 ACCEPT, // 12. SHA3-256 REJECT, // 13. Reserved. ACCEPT, // 14. SHA3-512 ]); a_cutoff_list!(SubpacketTagCutoffList, SubpacketTag, 40, [ REJECT, // 0. Reserved. REJECT, // 1. Reserved. ACCEPT, // 2. SignatureCreationTime. ACCEPT, // 3. SignatureExpirationTime. ACCEPT, // 4. ExportableCertification. ACCEPT, // 5. TrustSignature. ACCEPT, // 6. RegularExpression. // Note: Even though we don't explicitly honor the // Revocable flag, we don't support signature // revocations, hence it is safe to ACCEPT it. ACCEPT, // 7. Revocable. REJECT, // 8. Reserved. ACCEPT, // 9. KeyExpirationTime. REJECT, // 10. PlaceholderForBackwardCompatibility. ACCEPT, // 11. PreferredSymmetricAlgorithms. ACCEPT, // 12. RevocationKey. REJECT, // 13. Reserved. REJECT, // 14. Reserved. REJECT, // 15. Reserved. ACCEPT, // 16. Issuer. REJECT, // 17. Reserved. REJECT, // 18. Reserved. REJECT, // 19. Reserved. ACCEPT, // 20. NotationData. ACCEPT, // 21. PreferredHashAlgorithms. ACCEPT, // 22. PreferredCompressionAlgorithms. ACCEPT, // 23. KeyServerPreferences. ACCEPT, // 24. PreferredKeyServer. ACCEPT, // 25. PrimaryUserID. ACCEPT, // 26. PolicyURI. ACCEPT, // 27. KeyFlags. ACCEPT, // 28. SignersUserID. ACCEPT, // 29. ReasonForRevocation. ACCEPT, // 30. Features. REJECT, // 31. SignatureTarget. ACCEPT, // 32. EmbeddedSignature. ACCEPT, // 33. IssuerFingerprint. REJECT, // 34. Reserved (PreferredAEADAlgorithms). ACCEPT, // 35. IntendedRecipient. REJECT, // 36. Reserved. ACCEPT, // 37. ApprovedCertifications. REJECT, // 38. Reserved. ACCEPT, // 39. PreferredAEADCiphersuites. ]); a_cutoff_list!(AsymmetricAlgorithmCutoffList, AsymmetricAlgorithm, 23, [ Some(Timestamp::Y2014M2), // 0. RSA1024. ACCEPT, // 1. RSA2048. ACCEPT, // 2. RSA3072. ACCEPT, // 3. RSA4096. Some(Timestamp::Y2014M2), // 4. ElGamal1024. Some(Timestamp::Y2025M2), // 5. ElGamal2048. Some(Timestamp::Y2025M2), // 6. ElGamal3072. Some(Timestamp::Y2025M2), // 7. ElGamal4096. Some(Timestamp::Y2014M2), // 8. DSA1024. Some(Timestamp::Y2030M2), // 9. DSA2048. Some(Timestamp::Y2030M2), // 10. DSA3072. Some(Timestamp::Y2030M2), // 11. DSA4096. ACCEPT, // 12. NistP256. ACCEPT, // 13. NistP384. ACCEPT, // 14. NistP521. ACCEPT, // 15. BrainpoolP256. ACCEPT, // 16. BrainpoolP384. ACCEPT, // 17. BrainpoolP512. ACCEPT, // 18. Cv25519. ACCEPT, // 19. X25519. ACCEPT, // 20. X448. ACCEPT, // 21. Ed25519. ACCEPT, // 22. Ed448. ]); a_cutoff_list!(SymmetricAlgorithmCutoffList, SymmetricAlgorithm, 14, [ REJECT, // 0. Unencrypted. Some(Timestamp::Y2025M2), // 1. IDEA. Some(Timestamp::Y2017M2), // 2. TripleDES. Some(Timestamp::Y2025M2), // 3. CAST5. ACCEPT, // 4. Blowfish. REJECT, // 5. Reserved. REJECT, // 6. Reserved. ACCEPT, // 7. AES128. ACCEPT, // 8. AES192. ACCEPT, // 9. AES256. ACCEPT, // 10. Twofish. ACCEPT, // 11. Camellia128. ACCEPT, // 12. Camellia192. ACCEPT, // 13. Camellia256. ]); a_cutoff_list!(AEADAlgorithmCutoffList, AEADAlgorithm, 4, [ REJECT, // 0. Reserved. ACCEPT, // 1. EAX. ACCEPT, // 2. OCB. ACCEPT, // 3. GCM. ]); a_versioned_cutoff_list!(PacketTagCutoffList, Tag, 22, [ REJECT, // 0. Reserved. ACCEPT, // 1. PKESK. ACCEPT, // 2. Signature. ACCEPT, // 3. SKESK. ACCEPT, // 4. OnePassSig. ACCEPT, // 5. SecretKey. ACCEPT, // 6. PublicKey. ACCEPT, // 7. SecretSubkey. ACCEPT, // 8. CompressedData. Some(Timestamp::Y2004M2), // 9. SED. ACCEPT, // 10. Marker. ACCEPT, // 11. Literal. ACCEPT, // 12. Trust. ACCEPT, // 13. UserID. ACCEPT, // 14. PublicSubkey. REJECT, // 15. Not assigned. REJECT, // 16. Not assigned. ACCEPT, // 17. UserAttribute. ACCEPT, // 18. SEIP. ACCEPT, // 19. MDC. REJECT, // 20. "v5" AED. ACCEPT, // 21. Padding. ], // The versioned list overrides the unversioned list. So we only // need to tweak the above. // // Note: this list must be sorted and the tag and version must be unique! 2, [ (Tag::Signature, 3, Some(Timestamp::Y2021M2)), (Tag::Signature, 5, REJECT), // "v5" Signatures. ]); // We need to convert a `SystemTime` to a `Timestamp` in // `StandardPolicy::reject_hash_at`. Unfortunately, a `SystemTime` // can represent a larger range of time than a `Timestamp` can. Since // the times passed to this function are cutoff points, and we only // compare them to OpenPGP timestamps, any `SystemTime` that is prior // to the Unix Epoch is equivalent to the Unix Epoch: it will reject // all timestamps. Similarly, any `SystemTime` that is later than the // latest time representable by a `Timestamp` is equivalent to // accepting all time stamps, which is equivalent to passing None. fn system_time_cutoff_to_timestamp(t: SystemTime) -> Option<Timestamp> { let t = t .duration_since(SystemTime::UNIX_EPOCH) // An error can only occur if the SystemTime is less than the // reference time (SystemTime::UNIX_EPOCH). Map that to // SystemTime::UNIX_EPOCH, as above. .unwrap_or_else(|_| Duration::new(0, 0)); let t = t.as_secs(); if t > u32::MAX as u64 { // Map to None, as above. None } else { Some((t as u32).into()) } } impl<'a> StandardPolicy<'a> { /// Instantiates a new `StandardPolicy` with the default parameters. pub const fn new() -> Self { const EMPTY_LIST: &[&str] = &[]; Self { time: None, collision_resistant_hash_algos: CollisionResistantHashCutoffList::Default(), second_pre_image_resistant_hash_algos: SecondPreImageResistantHashCutoffList::Default(), // There are 365.2425 days in a year. Use a reasonable // approximation. hash_revocation_tolerance: types::Duration::seconds((7 * 365 + 2) * 24 * 60 * 60), critical_subpackets: SubpacketTagCutoffList::Default(), good_critical_notations: EMPTY_LIST, asymmetric_algos: AsymmetricAlgorithmCutoffList::Default(), symmetric_algos: SymmetricAlgorithmCutoffList::Default(), aead_algos: AEADAlgorithmCutoffList::Default(), packet_tags: PacketTagCutoffList::Default(), } } /// Instantiates a new `StandardPolicy` with parameters /// appropriate for `time`. /// /// `time` is a meta-parameter that selects a security profile /// that is appropriate for the given point in time. When /// evaluating an object, the reference time should be set to the /// time that the object was stored to non-tamperable storage. /// Since most applications don't record when they received an /// object, they should conservatively use the current time. /// /// Note that the reference time is a security parameter and is /// different from the time that the object was allegedly created. /// Consider evaluating a signature whose `Signature Creation /// Time` subpacket indicates that it was created in 2007. Since /// the subpacket is under the control of the sender, setting the /// reference time according to the subpacket means that the /// sender chooses the security profile. If the sender were an /// attacker, she could have forged this to take advantage of /// security weaknesses found since 2007. This is why the /// reference time must be set---at the earliest---to the time /// that the message was stored to non-tamperable storage. When /// that is not available, the current time should be used. pub fn at<T>(time: T) -> Self where T: Into<SystemTime>, { let time = time.into(); let mut p = Self::new(); p.time = Some(system_time_cutoff_to_timestamp(time) // Map "ACCEPT" to the end of time (None // here means the current time). .unwrap_or(Timestamp::MAX)); p } /// Returns the policy's reference time. /// /// The current time is None. /// /// See [`StandardPolicy::at`] for details. /// /// [`StandardPolicy::at`]: StandardPolicy::at() pub fn time(&self) -> Option<SystemTime> { self.time.map(Into::into) } /// Always considers `h` to be secure. /// /// A cryptographic hash algorithm normally has three security /// properties: /// /// - Pre-image resistance, /// - Second pre-image resistance, and /// - Collision resistance. /// /// A hash algorithm should only be unconditionally accepted if it /// has all three of these properties. See the documentation for /// [`HashAlgoSecurity`] for more details. pub fn accept_hash(&mut self, h: HashAlgorithm) { self.accept_hash_property(h, HashAlgoSecurity::CollisionResistance); self.accept_hash_property(h, HashAlgoSecurity::SecondPreImageResistance); } /// Considers hash algorithm `h` to be secure for the specified /// security property `sec`. /// /// For instance, an application may choose to allow an algorithm /// like SHA-1 in contexts like User ID binding signatures where /// only [second preimage /// resistance][`HashAlgoSecurity::SecondPreImageResistance`] is /// required but not in contexts like signatures over data where /// [collision /// resistance][`HashAlgoSecurity::CollisionResistance`] is also /// required. Whereas SHA-1's collision resistance is /// [definitively broken](https://shattered.io/), depending on the /// application's threat model, it may be acceptable to continue /// to accept SHA-1 in these specific contexts. pub fn accept_hash_property(&mut self, h: HashAlgorithm, sec: HashAlgoSecurity) { self.reject_hash_property_at(h, sec, None); } /// Considers `h` to be insecure in all security contexts. /// /// A cryptographic hash algorithm normally has three security /// properties: /// /// - Pre-image resistance, /// - Second pre-image resistance, and /// - Collision resistance. /// /// This method causes the hash algorithm to be considered unsafe /// in all security contexts. /// /// See the documentation for [`HashAlgoSecurity`] for more /// details. /// /// /// To express a more nuanced policy, use /// [`StandardPolicy::reject_hash_at`] or /// [`StandardPolicy::reject_hash_property_at`]. /// /// [`StandardPolicy::reject_hash_at`]: StandardPolicy::reject_hash_at() /// [`StandardPolicy::reject_hash_property_at`]: StandardPolicy::reject_hash_property_at() pub fn reject_hash(&mut self, h: HashAlgorithm) { self.collision_resistant_hash_algos.set(h, REJECT); self.second_pre_image_resistant_hash_algos.set(h, REJECT); } /// Considers all hash algorithms to be insecure. /// /// Causes all hash algorithms to be considered insecure in all /// security contexts. /// /// This is useful when using a good list to determine what /// algorithms are allowed. pub fn reject_all_hashes(&mut self) { self.collision_resistant_hash_algos.reject_all(); self.second_pre_image_resistant_hash_algos.reject_all(); } /// Considers `h` to be insecure in all security contexts starting /// at time `t`. /// /// A cryptographic hash algorithm normally has three security /// properties: /// /// - Pre-image resistance, /// - Second pre-image resistance, and /// - Collision resistance. /// /// This method causes the hash algorithm to be considered unsafe /// in all security contexts starting at time `t`. /// /// See the documentation for [`HashAlgoSecurity`] for more /// details. /// /// /// To express a more nuanced policy, use /// [`StandardPolicy::reject_hash_property_at`]. /// /// [`StandardPolicy::reject_hash_property_at`]: StandardPolicy::reject_hash_property_at() pub fn reject_hash_at<T>(&mut self, h: HashAlgorithm, t: T) where T: Into<Option<SystemTime>>, { let t = t.into().and_then(system_time_cutoff_to_timestamp); self.collision_resistant_hash_algos.set(h, t); self.second_pre_image_resistant_hash_algos.set(h, t); } /// Considers `h` to be insecure starting at `t` for the specified /// security property. /// /// A hash algorithm is considered secure if it has all of the /// following security properties: /// /// - Pre-image resistance, /// - Second pre-image resistance, and /// - Collision resistance. /// /// Some contexts only require a subset of these security /// properties. Specifically, if an attacker is unable to /// influence the data that a user signs, then the hash algorithm /// only needs second pre-image resistance; it doesn't need /// collision resistance. See the documentation for /// [`HashAlgoSecurity`] for more details. /// /// /// This method makes it possible to specify different policies /// depending on the security requirements. /// /// A cutoff of `None` means that there is no cutoff and the /// algorithm has no known vulnerabilities for the specified /// security policy. /// /// As a rule of thumb, collision resistance is easier to attack /// than second pre-image resistance. And in practice there are /// practical attacks against several widely-used hash algorithms' /// collision resistance, but only theoretical attacks against /// their second pre-image resistance. Nevertheless, once one /// property of a hash has been compromised, we want to deprecate /// its use as soon as it is feasible. Unfortunately, because /// OpenPGP certificates are long-lived, this can take years. /// /// Given this, we start rejecting [MD5] in cases where collision /// resistance is required in 1997 and completely reject it /// starting in 2004: /// /// > In 1996, Dobbertin announced a collision of the /// > compression function of MD5 (Dobbertin, 1996). While this /// > was not an attack on the full MD5 hash function, it was /// > close enough for cryptographers to recommend switching to /// > a replacement, such as SHA-1 or RIPEMD-160. /// > /// > MD5CRK ended shortly after 17 August 2004, when collisions /// > for the full MD5 were announced by Xiaoyun Wang, Dengguo /// > Feng, Xuejia Lai, and Hongbo Yu. Their analytical attack /// > was reported to take only one hour on an IBM p690 cluster. /// > /// > (Accessed Feb. 2020.) /// /// [MD5]: https://en.wikipedia.org/wiki/MD5 /// /// And we start rejecting [SHA-1] in cases where collision /// resistance is required in 2013, and completely reject it in /// 2023: /// /// > Since 2005 SHA-1 has not been considered secure against /// > well-funded opponents, as of 2010 many organizations have /// > recommended its replacement. NIST formally deprecated use /// > of SHA-1 in 2011 and disallowed its use for digital /// > signatures in 2013. As of 2020, attacks against SHA-1 are /// > as practical as against MD5; as such, it is recommended to /// > remove SHA-1 from products as soon as possible and use /// > instead SHA-256 or SHA-3. Replacing SHA-1 is urgent where /// > it's used for signatures. /// > /// > (Accessed Feb. 2020.) /// /// [SHA-1]: https://en.wikipedia.org/wiki/SHA-1 /// /// There are two main reasons why we have decided to accept SHA-1 /// for so long. First, as of the end of 2020, there are still a /// large number of [certificates that rely on SHA-1]. Second, /// Sequoia uses a variant of SHA-1 called [SHA1CD], which is able /// to detect and *mitigate* the known attacks on SHA-1's /// collision resistance. /// /// [certificates that rely on SHA-1]: https://gitlab.com/sequoia-pgp/sequoia/-/issues/595 /// [SHA1CD]: https://github.com/cr-marcstevens/sha1collisiondetection /// /// Since RIPE-MD is structured similarly to SHA-1, we /// conservatively consider it to be broken as well. But, because /// it is not widely used in the OpenPGP ecosystem, we don't make /// provisions for it. /// /// Note: if a context indicates that it requires collision /// resistance, then it requires both collision resistance and /// second pre-image resistance, and both policies must indicate /// that the hash algorithm can be safely used at the specified /// time. pub fn reject_hash_property_at<T>(&mut self, h: HashAlgorithm, sec: HashAlgoSecurity, t: T) where T: Into<Option<SystemTime>>, { let t = t.into().and_then(system_time_cutoff_to_timestamp); match sec { HashAlgoSecurity::CollisionResistance => self.collision_resistant_hash_algos.set(h, t), HashAlgoSecurity::SecondPreImageResistance => self.second_pre_image_resistant_hash_algos.set(h, t), } } /// Returns the cutoff time for the specified hash algorithm and /// security policy. pub fn hash_cutoff(&self, h: HashAlgorithm, sec: HashAlgoSecurity) -> Option<SystemTime> { match sec { HashAlgoSecurity::CollisionResistance => self.collision_resistant_hash_algos.cutoff(h), HashAlgoSecurity::SecondPreImageResistance => self.second_pre_image_resistant_hash_algos.cutoff(h), }.map(|t| t.into()) } /// Sets the amount of time to continue to accept revocation /// certificates after a hash algorithm should be rejected. /// /// Using [`StandardPolicy::reject_hash_at`], it is possible to /// indicate when a hash algorithm's security has been /// compromised, and, as such, should no longer be accepted. /// /// [`StandardPolicy::reject_hash_at`]: StandardPolicy::reject_hash_at() /// /// Applying this policy to revocation certificates can have some /// unfortunate side effects. In particular, if a certificate has /// been revoked using a revocation certificate that relies on a /// broken hash algorithm, but the most recent self signature uses /// a strong acceptable hash algorithm, then rejecting the /// revocation certificate would mean considering the certificate /// to not be revoked! This would be a catastrophe if the secret /// key material were compromised. /// /// Unfortunately, this happens in practice. A common example /// appears to be a certificate that has been updated many times, /// and is then revoked using a revocation certificate that was /// generated when the certificate was generated. /// /// Since the consequences of allowing an invalid revocation /// certificate are significantly less severe (a denial of /// service) than ignoring a valid revocation certificate /// (compromised confidentiality, integrity, and authentication), /// this option makes it possible to accept revocations using weak /// hash algorithms longer than other types of signatures. /// /// By default, the standard policy accepts revocation /// certificates seven years after the hash they are using was /// initially compromised. pub fn hash_revocation_tolerance<D>(&mut self, d: D) where D: Into<types::Duration> { self.hash_revocation_tolerance = d.into(); } /// Sets the amount of time to continue to accept revocation /// certificates after a hash algorithm should be rejected. /// /// See [`StandardPolicy::hash_revocation_tolerance`] for details. /// /// [`StandardPolicy::hash_revocation_tolerance`]: StandardPolicy::hash_revocation_tolerance() pub fn get_hash_revocation_tolerance(&self) -> types::Duration { self.hash_revocation_tolerance } /// Always considers `s` to be secure. pub fn accept_critical_subpacket(&mut self, s: SubpacketTag) { self.critical_subpackets.set(s, ACCEPT); } /// Always considers `s` to be insecure. pub fn reject_critical_subpacket(&mut self, s: SubpacketTag) { self.critical_subpackets.set(s, REJECT); } /// Considers all critical subpackets to be insecure. /// /// This is useful when using a good list to determine what /// critical subpackets are allowed. pub fn reject_all_critical_subpackets(&mut self) { self.critical_subpackets.reject_all(); } /// Considers `s` to be insecure starting at `cutoff`. /// /// A cutoff of `None` means that there is no cutoff and the /// subpacket has no known vulnerabilities. /// /// By default, we accept all critical subpackets that Sequoia /// understands and honors. pub fn reject_critical_subpacket_at<C>(&mut self, s: SubpacketTag, cutoff: C) where C: Into<Option<SystemTime>>, { self.critical_subpackets.set( s, cutoff.into().and_then(system_time_cutoff_to_timestamp)); } /// Returns the cutoff times for the specified subpacket tag. pub fn critical_subpacket_cutoff(&self, s: SubpacketTag) -> Option<SystemTime> { self.critical_subpackets.cutoff(s).map(|t| t.into()) } /// Sets the list of accepted critical notations. /// /// By default, we reject all critical notations. pub fn good_critical_notations(&mut self, good_list: &'a [&'a str]) { self.good_critical_notations = good_list; } /// Always considers `s` to be secure. pub fn accept_asymmetric_algo(&mut self, a: AsymmetricAlgorithm) { self.asymmetric_algos.set(a, ACCEPT); } /// Always considers `s` to be insecure. pub fn reject_asymmetric_algo(&mut self, a: AsymmetricAlgorithm) { self.asymmetric_algos.set(a, REJECT); } /// Considers all asymmetric algorithms to be insecure. /// /// This is useful when using a good list to determine what /// algorithms are allowed. pub fn reject_all_asymmetric_algos(&mut self) { self.asymmetric_algos.reject_all(); } /// Considers `a` to be insecure starting at `cutoff`. /// /// A cutoff of `None` means that there is no cutoff and the /// algorithm has no known vulnerabilities. /// /// By default, we reject the use of asymmetric key sizes lower /// than 2048 bits starting in 2014 following [NIST Special /// Publication 800-131A]. /// /// [NIST Special Publication 800-131A]: https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-131Ar2.pdf pub fn reject_asymmetric_algo_at<C>(&mut self, a: AsymmetricAlgorithm, cutoff: C) where C: Into<Option<SystemTime>>, { self.asymmetric_algos.set( a, cutoff.into().and_then(system_time_cutoff_to_timestamp)); } /// Returns the cutoff times for the specified hash algorithm. pub fn asymmetric_algo_cutoff(&self, a: AsymmetricAlgorithm) -> Option<SystemTime> { self.asymmetric_algos.cutoff(a).map(|t| t.into()) } /// Always considers `s` to be secure. pub fn accept_symmetric_algo(&mut self, s: SymmetricAlgorithm) { self.symmetric_algos.set(s, ACCEPT); } /// Always considers `s` to be insecure. pub fn reject_symmetric_algo(&mut self, s: SymmetricAlgorithm) { self.symmetric_algos.set(s, REJECT); } /// Considers all symmetric algorithms to be insecure. /// /// This is useful when using a good list to determine what /// algorithms are allowed. pub fn reject_all_symmetric_algos(&mut self) { self.symmetric_algos.reject_all(); } /// Considers `s` to be insecure starting at `cutoff`. /// /// A cutoff of `None` means that there is no cutoff and the /// algorithm has no known vulnerabilities. /// /// By default, we reject the use of TripleDES (3DES) starting in /// the year 2017. While 3DES is still a ["MUST implement"] /// algorithm in RFC4880, released in 2007, there are plenty of /// other symmetric algorithms defined in RFC4880, and it says /// AES-128 SHOULD be implemented. Support for other algorithms /// in OpenPGP implementations is [excellent]. We chose 2017 as /// the cutoff year because [NIST deprecated 3DES] that year. /// /// ["MUST implement"]: https://www.rfc-editor.org/rfc/rfc9580.html#section-9.3 /// [excellent]: https://tests.sequoia-pgp.org/#Symmetric_Encryption_Algorithm_support /// [NIST deprecated 3DES]: https://csrc.nist.gov/News/2017/Update-to-Current-Use-and-Deprecation-of-TDEA pub fn reject_symmetric_algo_at<C>(&mut self, s: SymmetricAlgorithm, cutoff: C) where C: Into<Option<SystemTime>>, { self.symmetric_algos.set( s, cutoff.into().and_then(system_time_cutoff_to_timestamp)); } /// Returns the cutoff times for the specified hash algorithm. pub fn symmetric_algo_cutoff(&self, s: SymmetricAlgorithm) -> Option<SystemTime> { self.symmetric_algos.cutoff(s).map(|t| t.into()) } /// Always considers `s` to be secure. /// /// This feature is [experimental](super#experimental-features). pub fn accept_aead_algo(&mut self, a: AEADAlgorithm) { self.aead_algos.set(a, ACCEPT); } /// Always considers `s` to be insecure. /// /// This feature is [experimental](super#experimental-features). pub fn reject_aead_algo(&mut self, a: AEADAlgorithm) { self.aead_algos.set(a, REJECT); } /// Considers all AEAD algorithms to be insecure. /// /// This is useful when using a good list to determine what /// algorithms are allowed. pub fn reject_all_aead_algos(&mut self) { self.aead_algos.reject_all(); } /// Considers `a` to be insecure starting at `cutoff`. /// /// A cutoff of `None` means that there is no cutoff and the /// algorithm has no known vulnerabilities. /// /// By default, we accept all AEAD modes. /// /// This feature is [experimental](super#experimental-features). pub fn reject_aead_algo_at<C>(&mut self, a: AEADAlgorithm, cutoff: C) where C: Into<Option<SystemTime>>, { self.aead_algos.set( a, cutoff.into().and_then(system_time_cutoff_to_timestamp)); } /// Returns the cutoff times for the specified hash algorithm. /// /// This feature is [experimental](super#experimental-features). pub fn aead_algo_cutoff(&self, a: AEADAlgorithm) -> Option<SystemTime> { self.aead_algos.cutoff(a).map(|t| t.into()) } /// Always accept the specified version of the packet. /// /// If a packet does not have a version field, then its version is /// `0`. pub fn accept_packet_tag_version(&mut self, tag: Tag, version: u8) { self.packet_tags.set_versioned(tag, version, ACCEPT); } /// Always accept packets with the given tag independent of their /// version. /// /// If you previously set a cutoff for a specific version of a /// packet, this overrides that. pub fn accept_packet_tag(&mut self, tag: Tag) { self.packet_tags.set_unversioned(tag, ACCEPT); } /// Always reject the specified version of the packet. /// /// If a packet does not have a version field, then its version is /// `0`. pub fn reject_packet_tag_version(&mut self, tag: Tag, version: u8) { self.packet_tags.set_versioned(tag, version, REJECT); } /// Always reject packets with the given tag. pub fn reject_packet_tag(&mut self, tag: Tag) { self.packet_tags.set_unversioned(tag, REJECT); } /// Considers all packets to be insecure. /// /// This is useful when using a good list to determine what /// packets are allowed. pub fn reject_all_packet_tags(&mut self) { self.packet_tags.reject_all(); } /// Start rejecting the specified version of packets with the /// given tag at `t`. /// /// A cutoff of `None` means that there is no cutoff and the /// packet has no known vulnerabilities. /// /// By default, we consider the *Symmetrically Encrypted Data /// Packet* (SED) insecure in messages created in the year 2004 or /// later. The rationale here is that *Symmetrically Encrypted /// Integrity Protected Data Packet* (SEIP) can be downgraded to /// SED packets, enabling attacks exploiting the malleability of /// the CFB stream (see [EFAIL]). /// /// [EFAIL]: https://en.wikipedia.org/wiki/EFAIL /// /// We chose 2004 as a cutoff-date because [Debian 3.0] (Woody), /// released on 2002-07-19, was the first release of Debian to /// ship a version of GnuPG that emitted SEIP packets by default. /// The first version that emitted SEIP packets was [GnuPG 1.0.3], /// released on 2000-09-18. Mid 2002 plus an 18 months grace /// period of people still using older versions is 2004. /// /// [Debian 3.0]: https://www.debian.org/News/2002/20020719 /// [GnuPG 1.0.3]: https://lists.gnupg.org/pipermail/gnupg-announce/2000q3/000075.html pub fn reject_packet_tag_version_at<C>(&mut self, tag: Tag, version: u8, cutoff: C) where C: Into<Option<SystemTime>>, { self.packet_tags.set_versioned( tag, version, cutoff.into().and_then(system_time_cutoff_to_timestamp)); } /// Start rejecting packets with the given tag at `t`. /// /// See the documentation for /// [`StandardPolicy::reject_packet_tag_version_at`]. pub fn reject_packet_tag_at<C>(&mut self, tag: Tag, cutoff: C) where C: Into<Option<SystemTime>>, { self.packet_tags.set_unversioned( tag, cutoff.into().and_then(system_time_cutoff_to_timestamp)); } /// Returns the cutoff for the specified version of the specified /// packet tag. /// /// This first considers the versioned cutoff list. If there is /// no entry in the versioned list, it fallsback to the /// unversioned cutoff list. If there is also no entry there, /// then it falls back to the default. pub fn packet_tag_version_cutoff(&self, tag: Tag, version: u8) -> Option<SystemTime> { self.packet_tags.cutoff(tag, version).map(|t| t.into()) } } impl<'a> Policy for StandardPolicy<'a> { fn signature(&self, sig: &Signature, sec: HashAlgoSecurity) -> Result<()> { let time = self.time.unwrap_or_else(Timestamp::now); let rev = matches!(sig.typ(), SignatureType::KeyRevocation | SignatureType::SubkeyRevocation | SignatureType::CertificationRevocation); // Note: collision resistance requires 2nd pre-image resistance. if sec == HashAlgoSecurity::CollisionResistance { if rev { self .collision_resistant_hash_algos .check(sig.hash_algo(), time, Some(self.hash_revocation_tolerance)) .with_context(|| format!( "Policy rejected revocation signature ({}) requiring \ collision resistance", sig.typ()))? } else { self .collision_resistant_hash_algos .check(sig.hash_algo(), time, None) .with_context(|| format!( "Policy rejected non-revocation signature ({}) requiring \ collision resistance", sig.typ()))? } } if rev { self .second_pre_image_resistant_hash_algos .check(sig.hash_algo(), time, Some(self.hash_revocation_tolerance)) .with_context(|| format!( "Policy rejected revocation signature ({}) requiring \ second pre-image resistance", sig.typ()))? } else { self .second_pre_image_resistant_hash_algos .check(sig.hash_algo(), time, None) .with_context(|| format!( "Policy rejected non-revocation signature ({}) requiring \ second pre-image resistance", sig.typ()))? } for csp in sig.hashed_area().iter().filter(|sp| sp.critical()) { self.critical_subpackets.check(csp.tag(), time, None) .context("Policy rejected critical signature subpacket")?; if let SubpacketValue::NotationData(n) = csp.value() { if ! self.good_critical_notations.contains(&n.name()) { return Err(anyhow::Error::from( Error::PolicyViolation( format!("Critical notation {:?}", n.name()), None)) .context("Policy rejected critical notation")); } } } Ok(()) } fn key(&self, ka: &ValidErasedKeyAmalgamation<key::PublicParts>) -> Result<()> { use self::AsymmetricAlgorithm::{*, Unknown}; use crate::types::PublicKeyAlgorithm::{self, *}; use crate::crypto::mpi::PublicKey; #[allow(deprecated)] let a = match (ka.key().pk_algo(), ka.key().mpis().bits()) { // RSA. (RSAEncryptSign, Some(b)) | (RSAEncrypt, Some(b)) | (RSASign, Some(b)) if b < 2048 => RSA1024, (RSAEncryptSign, Some(b)) | (RSAEncrypt, Some(b)) | (RSASign, Some(b)) if b < 3072 => RSA2048, (RSAEncryptSign, Some(b)) | (RSAEncrypt, Some(b)) | (RSASign, Some(b)) if b < 4096 => RSA3072, (RSAEncryptSign, Some(_)) | (RSAEncrypt, Some(_)) | (RSASign, Some(_)) => RSA4096, (RSAEncryptSign, None) | (RSAEncrypt, None) | (RSASign, None) => unreachable!(), // ElGamal. (ElGamalEncryptSign, Some(b)) | (ElGamalEncrypt, Some(b)) if b < 2048 => ElGamal1024, (ElGamalEncryptSign, Some(b)) | (ElGamalEncrypt, Some(b)) if b < 3072 => ElGamal2048, (ElGamalEncryptSign, Some(b)) | (ElGamalEncrypt, Some(b)) if b < 4096 => ElGamal3072, (ElGamalEncryptSign, Some(_)) | (ElGamalEncrypt, Some(_)) => ElGamal4096, (ElGamalEncryptSign, None) | (ElGamalEncrypt, None) => unreachable!(), // DSA. (DSA, Some(b)) if b < 2048 => DSA1024, (DSA, Some(b)) if b < 3072 => DSA2048, (DSA, Some(b)) if b < 4096 => DSA3072, (DSA, Some(_)) => DSA4096, (DSA, None) => unreachable!(), // ECC. (ECDH, _) | (ECDSA, _) | (EdDSA, _) => { let curve = match ka.key().mpis() { PublicKey::EdDSA { curve, .. } => curve, PublicKey::ECDSA { curve, .. } => curve, PublicKey::ECDH { curve, .. } => curve, _ => unreachable!(), }; use crate::types::Curve; match curve { Curve::NistP256 => NistP256, Curve::NistP384 => NistP384, Curve::NistP521 => NistP521, Curve::BrainpoolP256 => BrainpoolP256, Curve::BrainpoolP384 => BrainpoolP384, Curve::BrainpoolP512 => BrainpoolP512, Curve::Ed25519 => Cv25519, Curve::Cv25519 => Cv25519, Curve::Unknown(_) => Unknown, } }, (PublicKeyAlgorithm::X25519, _) => AsymmetricAlgorithm::X25519, (PublicKeyAlgorithm::X448, _) => AsymmetricAlgorithm::X448, (PublicKeyAlgorithm::Ed25519, _) => AsymmetricAlgorithm::Ed25519, (PublicKeyAlgorithm::Ed448, _) => AsymmetricAlgorithm::Ed448, _ => Unknown, }; let time = self.time.unwrap_or_else(Timestamp::now); self.asymmetric_algos.check(a, time, None) .context("Policy rejected asymmetric algorithm")?; // Check ECDH KDF and KEK parameters. if let PublicKey::ECDH { hash, sym, .. } = ka.key().mpis() { self.symmetric_algorithm(*sym) .context("Policy rejected ECDH \ key encapsulation algorithm")?; // RFC6637 says: // // > Refer to Section 13 for the details regarding the // > choice of the KEK algorithm, which SHOULD be one of // > three AES algorithms. // // Furthermore, GnuPG rejects anything other than AES. // I checked the SKS dump, and there are no keys out // there that use a different KEK algorithm. match sym { SymmetricAlgorithm::AES128 | SymmetricAlgorithm::AES192 | SymmetricAlgorithm::AES256 => (), // Good. _ => return Err(anyhow::Error::from( Error::PolicyViolation(sym.to_string(), None)) .context("Policy rejected ECDH \ key encapsulation algorithm")), } // For use in a KDF the hash algorithm does not // necessarily be collision resistant, but this is the // weakest property that we otherwise care for, so // (somewhat arbitrarily) use this. self .collision_resistant_hash_algos .check(*hash, time, None) .context("Policy rejected ECDH \ key derivation hash function")?; } Ok(()) } fn packet(&self, packet: &Packet) -> Result<()> { let time = self.time.unwrap_or_else(Timestamp::now); self.packet_tags .check( packet.tag(), packet.version().unwrap_or(0), time, None) .context("Policy rejected packet type") } fn symmetric_algorithm(&self, algo: SymmetricAlgorithm) -> Result<()> { let time = self.time.unwrap_or_else(Timestamp::now); self.symmetric_algos.check(algo, time, None) .context("Policy rejected symmetric encryption algorithm") } fn aead_algorithm(&self, algo: AEADAlgorithm) -> Result<()> { let time = self.time.unwrap_or_else(Timestamp::now); self.aead_algos.check(algo, time, None) .context("Policy rejected authenticated encryption algorithm") } } /// Asymmetric encryption algorithms. /// /// This type is for refining the [`StandardPolicy`] with respect to /// asymmetric algorithms. In contrast to [`PublicKeyAlgorithm`], it /// does not concern itself with the use (encryption or signing), and /// it does include key sizes (if applicable) and elliptic curves. /// /// [`PublicKeyAlgorithm`]: crate::types::PublicKeyAlgorithm /// /// Key sizes put into are buckets, rounding down to the nearest /// bucket. For example, a 3253-bit RSA key is categorized as /// `RSA3072`. #[non_exhaustive] #[derive(Clone, Debug, PartialEq, Eq, Copy)] pub enum AsymmetricAlgorithm { /// RSA with key sizes up to 2048-1 bit. RSA1024, /// RSA with key sizes up to 3072-1 bit. RSA2048, /// RSA with key sizes up to 4096-1 bit. RSA3072, /// RSA with key sizes larger or equal to 4096 bit. RSA4096, /// ElGamal with key sizes up to 2048-1 bit. ElGamal1024, /// ElGamal with key sizes up to 3072-1 bit. ElGamal2048, /// ElGamal with key sizes up to 4096-1 bit. ElGamal3072, /// ElGamal with key sizes larger or equal to 4096 bit. ElGamal4096, /// DSA with key sizes up to 2048-1 bit. DSA1024, /// DSA with key sizes up to 3072-1 bit. DSA2048, /// DSA with key sizes up to 4096-1 bit. DSA3072, /// DSA with key sizes larger or equal to 4096 bit. DSA4096, /// NIST curve P-256. NistP256, /// NIST curve P-384. NistP384, /// NIST curve P-521. NistP521, /// brainpoolP256r1. BrainpoolP256, /// brainpoolP384r1. BrainpoolP384, /// brainpoolP512r1. BrainpoolP512, /// D.J. Bernstein's Curve25519. Cv25519, /// X25519 (RFC 7748). X25519, /// X448 (RFC 7748). X448, /// Ed25519 (RFC 8032). Ed25519, /// Ed448 (RFC 8032). Ed448, /// Unknown algorithm. Unknown, } assert_send_and_sync!(AsymmetricAlgorithm); const ASYMMETRIC_ALGORITHM_VARIANTS: [AsymmetricAlgorithm; 23] = [ AsymmetricAlgorithm::RSA1024, AsymmetricAlgorithm::RSA2048, AsymmetricAlgorithm::RSA3072, AsymmetricAlgorithm::RSA4096, AsymmetricAlgorithm::ElGamal1024, AsymmetricAlgorithm::ElGamal2048, AsymmetricAlgorithm::ElGamal3072, AsymmetricAlgorithm::ElGamal4096, AsymmetricAlgorithm::DSA1024, AsymmetricAlgorithm::DSA2048, AsymmetricAlgorithm::DSA3072, AsymmetricAlgorithm::DSA4096, AsymmetricAlgorithm::NistP256, AsymmetricAlgorithm::NistP384, AsymmetricAlgorithm::NistP521, AsymmetricAlgorithm::BrainpoolP256, AsymmetricAlgorithm::BrainpoolP384, AsymmetricAlgorithm::BrainpoolP512, AsymmetricAlgorithm::Cv25519, AsymmetricAlgorithm::X25519, AsymmetricAlgorithm::X448, AsymmetricAlgorithm::Ed25519, AsymmetricAlgorithm::Ed448, ]; impl AsymmetricAlgorithm { /// Returns an iterator over all valid variants. /// /// Returns an iterator over all known variants. This does not /// include the [`AsymmetricAlgorithm::Unknown`] variant. pub fn variants() -> impl Iterator<Item=AsymmetricAlgorithm> { ASYMMETRIC_ALGORITHM_VARIANTS.iter().cloned() } } impl std::fmt::Display for AsymmetricAlgorithm { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!(f, "{:?}", self) } } impl From<AsymmetricAlgorithm> for u8 { fn from(a: AsymmetricAlgorithm) -> Self { use self::AsymmetricAlgorithm::*; match a { RSA1024 => 0, RSA2048 => 1, RSA3072 => 2, RSA4096 => 3, ElGamal1024 => 4, ElGamal2048 => 5, ElGamal3072 => 6, ElGamal4096 => 7, DSA1024 => 8, DSA2048 => 9, DSA3072 => 10, DSA4096 => 11, NistP256 => 12, NistP384 => 13, NistP521 => 14, BrainpoolP256 => 15, BrainpoolP384 => 16, BrainpoolP512 => 17, Cv25519 => 18, X25519 => 19, X448 => 20, Ed25519 => 21, Ed448 => 22, Unknown => 255, } } } /// The Null Policy. /// /// Danger, here be dragons. /// /// This policy imposes no additional policy, i.e., accepts /// everything. This includes the MD5 hash algorithm, and SED /// packets. /// /// The Null policy has a limited set of valid use cases, e.g., packet statistics. /// For other purposes, it is more advisable to use the [`StandardPolicy`] and /// adjust it by selectively allowing items considered insecure by default, e.g., /// via [`StandardPolicy::accept_hash`] function. If this is still too inflexible /// consider creating a specialized policy based on the [`StandardPolicy`] as /// [the example for `StandardPolicy`] illustrates. /// /// [`StandardPolicy::accept_hash`]: StandardPolicy::accept_hash() /// [the example for `StandardPolicy`]: StandardPolicy#examples #[derive(Debug)] pub struct NullPolicy { } assert_send_and_sync!(NullPolicy); impl NullPolicy { /// Instantiates a new `NullPolicy`. pub const unsafe fn new() -> Self { NullPolicy {} } } impl Policy for NullPolicy { fn signature(&self, _sig: &Signature, _sec: HashAlgoSecurity) -> Result<()> { Ok(()) } fn key(&self, _ka: &ValidErasedKeyAmalgamation<key::PublicParts>) -> Result<()> { Ok(()) } fn symmetric_algorithm(&self, _algo: SymmetricAlgorithm) -> Result<()> { Ok(()) } fn aead_algorithm(&self, _algo: AEADAlgorithm) -> Result<()> { Ok(()) } fn packet(&self, _packet: &Packet) -> Result<()> { Ok(()) } } #[cfg(test)] mod test { use std::io::Read; use std::time::Duration; use super::*; use crate::Error; use crate::crypto::SessionKey; use crate::packet::key::Key4; use crate::packet::signature; use crate::packet::{PKESK, SKESK}; use crate::parse::Parse; use crate::parse::stream::DecryptionHelper; use crate::parse::stream::DecryptorBuilder; use crate::parse::stream::DetachedVerifierBuilder; use crate::parse::stream::MessageLayer; use crate::parse::stream::MessageStructure; use crate::parse::stream::VerificationHelper; use crate::parse::stream::VerifierBuilder; use crate::policy::StandardPolicy as P; use crate::types::Curve; use crate::types::KeyFlags; use crate::types::SymmetricAlgorithm; // Test that the constructor is const. const _A_STANDARD_POLICY: StandardPolicy = StandardPolicy::new(); #[test] fn binding_signature() { let p = &P::new(); // A primary and two subkeys. let (cert, _) = CertBuilder::new() .add_signing_subkey() .add_transport_encryption_subkey() .generate().unwrap(); assert_eq!(cert.keys().with_policy(p, None).count(), 3); // Reject all direct key signatures. #[derive(Debug)] struct NoDirectKeySigs; impl Policy for NoDirectKeySigs { fn signature(&self, sig: &Signature, _sec: HashAlgoSecurity) -> Result<()> { use crate::types::SignatureType::*; match sig.typ() { DirectKey => Err(anyhow::anyhow!("direct key!")), _ => Ok(()), } } fn key(&self, _ka: &ValidErasedKeyAmalgamation<key::PublicParts>) -> Result<()> { Ok(()) } fn symmetric_algorithm(&self, _algo: SymmetricAlgorithm) -> Result<()> { Ok(()) } fn aead_algorithm(&self, _algo: AEADAlgorithm) -> Result<()> { Ok(()) } fn packet(&self, _packet: &Packet) -> Result<()> { Ok(()) } } let p = &NoDirectKeySigs {}; assert_eq!(cert.keys().with_policy(p, None).count(), 0); // Reject all subkey signatures. #[derive(Debug)] struct NoSubkeySigs; impl Policy for NoSubkeySigs { fn signature(&self, sig: &Signature, _sec: HashAlgoSecurity) -> Result<()> { use crate::types::SignatureType::*; match sig.typ() { SubkeyBinding => Err(anyhow::anyhow!("subkey signature!")), _ => Ok(()), } } fn key(&self, _ka: &ValidErasedKeyAmalgamation<key::PublicParts>) -> Result<()> { Ok(()) } fn symmetric_algorithm(&self, _algo: SymmetricAlgorithm) -> Result<()> { Ok(()) } fn aead_algorithm(&self, _algo: AEADAlgorithm) -> Result<()> { Ok(()) } fn packet(&self, _packet: &Packet) -> Result<()> { Ok(()) } } let p = &NoSubkeySigs {}; assert_eq!(cert.keys().with_policy(p, None).count(), 1); } #[test] fn revocation() -> Result<()> { use crate::cert::prelude::*; use crate::types::SignatureType; use crate::types::ReasonForRevocation; let p = &P::new(); // A primary and two subkeys. let (cert, _) = CertBuilder::new() .add_userid("Alice") .add_signing_subkey() .add_transport_encryption_subkey() .generate()?; // Make sure we have all keys and all user ids. assert_eq!(cert.keys().with_policy(p, None).count(), 3); assert_eq!(cert.userids().with_policy(p, None).count(), 1); // Reject all user id signatures. #[derive(Debug)] struct NoPositiveCertifications; impl Policy for NoPositiveCertifications { fn signature(&self, sig: &Signature, _sec: HashAlgoSecurity) -> Result<()> { use crate::types::SignatureType::*; match sig.typ() { PositiveCertification => Err(anyhow::anyhow!("positive certification!")), _ => Ok(()), } } fn key(&self, _ka: &ValidErasedKeyAmalgamation<key::PublicParts>) -> Result<()> { Ok(()) } fn symmetric_algorithm(&self, _algo: SymmetricAlgorithm) -> Result<()> { Ok(()) } fn aead_algorithm(&self, _algo: AEADAlgorithm) -> Result<()> { Ok(()) } fn packet(&self, _packet: &Packet) -> Result<()> { Ok(()) } } let p = &NoPositiveCertifications {}; assert_eq!(cert.userids().with_policy(p, None).count(), 0); // Revoke it. let mut keypair = cert.primary_key().key().clone() .parts_into_secret()?.into_keypair()?; let ca = cert.userids().next().unwrap(); // Generate the revocation for the first and only UserID. let revocation = UserIDRevocationBuilder::new() .set_reason_for_revocation( ReasonForRevocation::KeyRetired, b"Left example.org.")? .build(&mut keypair, &cert, ca.userid(), None)?; assert_eq!(revocation.typ(), SignatureType::CertificationRevocation); // Now merge the revocation signature into the Cert. let cert = cert.insert_packets(revocation.clone())?.0; // Check that it is revoked. assert_eq!(cert.userids().with_policy(p, None).revoked(false).count(), 0); // Reject all user id signatures. #[derive(Debug)] struct NoCertificationRevocation; impl Policy for NoCertificationRevocation { fn signature(&self, sig: &Signature, _sec: HashAlgoSecurity) -> Result<()> { use crate::types::SignatureType::*; match sig.typ() { CertificationRevocation => Err(anyhow::anyhow!("certification certification!")), _ => Ok(()), } } fn key(&self, _ka: &ValidErasedKeyAmalgamation<key::PublicParts>) -> Result<()> { Ok(()) } fn symmetric_algorithm(&self, _algo: SymmetricAlgorithm) -> Result<()> { Ok(()) } fn aead_algorithm(&self, _algo: AEADAlgorithm) -> Result<()> { Ok(()) } fn packet(&self, _packet: &Packet) -> Result<()> { Ok(()) } } let p = &NoCertificationRevocation {}; // Check that the user id is no longer revoked. assert_eq!(cert.userids().with_policy(p, None).revoked(false).count(), 1); // Generate the revocation for the first subkey. let subkey = cert.keys().subkeys().next().unwrap(); let revocation = SubkeyRevocationBuilder::new() .set_reason_for_revocation( ReasonForRevocation::KeyRetired, b"Smells funny.").unwrap() .build(&mut keypair, &cert, subkey.key(), None)?; assert_eq!(revocation.typ(), SignatureType::SubkeyRevocation); // Now merge the revocation signature into the Cert. assert_eq!(cert.keys().with_policy(p, None).revoked(false).count(), 3); let cert = cert.insert_packets(revocation.clone())?.0; assert_eq!(cert.keys().with_policy(p, None).revoked(false).count(), 2); // Reject all subkey revocations. #[derive(Debug)] struct NoSubkeyRevocation; impl Policy for NoSubkeyRevocation { fn signature(&self, sig: &Signature, _sec: HashAlgoSecurity) -> Result<()> { use crate::types::SignatureType::*; match sig.typ() { SubkeyRevocation => Err(anyhow::anyhow!("subkey revocation!")), _ => Ok(()), } } fn key(&self, _ka: &ValidErasedKeyAmalgamation<key::PublicParts>) -> Result<()> { Ok(()) } fn symmetric_algorithm(&self, _algo: SymmetricAlgorithm) -> Result<()> { Ok(()) } fn aead_algorithm(&self, _algo: AEADAlgorithm) -> Result<()> { Ok(()) } fn packet(&self, _packet: &Packet) -> Result<()> { Ok(()) } } let p = &NoSubkeyRevocation {}; // Check that the key is no longer revoked. assert_eq!(cert.keys().with_policy(p, None).revoked(false).count(), 3); Ok(()) } #[test] fn binary_signature() -> Result<()> { #[derive(PartialEq, Debug)] struct VHelper { good: usize, errors: usize, keys: Vec<Cert>, } impl VHelper { fn new(keys: Vec<Cert>) -> Self { VHelper { good: 0, errors: 0, keys, } } } impl VerificationHelper for VHelper { fn get_certs(&mut self, _ids: &[crate::KeyHandle]) -> Result<Vec<Cert>> { Ok(self.keys.clone()) } fn check(&mut self, structure: MessageStructure) -> Result<()> { for layer in structure { match layer { MessageLayer::SignatureGroup { ref results } => for result in results { eprintln!("result: {:?}", result); match result { Ok(_) => self.good += 1, Err(_) => self.errors += 1, } } MessageLayer::Compression { .. } => (), _ => unreachable!(), } } Ok(()) } } impl DecryptionHelper for VHelper { fn decrypt(&mut self, _: &[PKESK], _: &[SKESK], _: Option<SymmetricAlgorithm>, _: &mut dyn FnMut(Option<SymmetricAlgorithm>, &SessionKey) -> bool) -> Result<Option<Cert>> { unreachable!(); } } // Reject all data (binary) signatures. #[derive(Debug)] struct NoBinarySigantures; impl Policy for NoBinarySigantures { fn signature(&self, sig: &Signature, _sec: HashAlgoSecurity) -> Result<()> { use crate::types::SignatureType::*; eprintln!("{:?}", sig.typ()); match sig.typ() { Binary => Err(anyhow::anyhow!("binary!")), _ => Ok(()), } } fn key(&self, _ka: &ValidErasedKeyAmalgamation<key::PublicParts>) -> Result<()> { Ok(()) } fn symmetric_algorithm(&self, _algo: SymmetricAlgorithm) -> Result<()> { Ok(()) } fn aead_algorithm(&self, _algo: AEADAlgorithm) -> Result<()> { Ok(()) } fn packet(&self, _packet: &Packet) -> Result<()> { Ok(()) } } let no_binary_signatures = &NoBinarySigantures {}; // Reject all subkey signatures. #[derive(Debug)] struct NoSubkeySigs; impl Policy for NoSubkeySigs { fn signature(&self, sig: &Signature, _sec: HashAlgoSecurity) -> Result<()> { use crate::types::SignatureType::*; match sig.typ() { SubkeyBinding => Err(anyhow::anyhow!("subkey signature!")), _ => Ok(()), } } fn key(&self, _ka: &ValidErasedKeyAmalgamation<key::PublicParts>) -> Result<()> { Ok(()) } fn symmetric_algorithm(&self, _algo: SymmetricAlgorithm) -> Result<()> { Ok(()) } fn aead_algorithm(&self, _algo: AEADAlgorithm) -> Result<()> { Ok(()) } fn packet(&self, _packet: &Packet) -> Result<()> { Ok(()) } } let no_subkey_signatures = &NoSubkeySigs {}; let standard = &P::new(); let keys = [ "neal.pgp", ].iter() .map(|f| Cert::from_bytes(crate::tests::key(f)).unwrap()) .collect::<Vec<_>>(); let data = "messages/signed-1.gpg"; let reference = crate::tests::manifesto(); // Test Verifier. // Standard policy => ok. let h = VHelper::new(keys.clone()); let mut v = VerifierBuilder::from_bytes(crate::tests::file(data))? .with_policy(standard, crate::frozen_time(), h)?; assert!(v.message_processed()); assert_eq!(v.helper_ref().good, 1); assert_eq!(v.helper_ref().errors, 0); let mut content = Vec::new(); v.read_to_end(&mut content).unwrap(); assert_eq!(reference.len(), content.len()); assert_eq!(reference, &content[..]); // Kill the subkey. let h = VHelper::new(keys.clone()); let mut v = VerifierBuilder::from_bytes(crate::tests::file(data))? .with_policy(no_subkey_signatures, crate::frozen_time(), h)?; assert!(v.message_processed()); assert_eq!(v.helper_ref().good, 0); assert_eq!(v.helper_ref().errors, 1); let mut content = Vec::new(); v.read_to_end(&mut content).unwrap(); assert_eq!(reference.len(), content.len()); assert_eq!(reference, &content[..]); // Kill the data signature. let h = VHelper::new(keys.clone()); let mut v = VerifierBuilder::from_bytes(crate::tests::file(data))? .with_policy(no_binary_signatures, crate::frozen_time(), h)?; assert!(v.message_processed()); assert_eq!(v.helper_ref().good, 0); assert_eq!(v.helper_ref().errors, 1); let mut content = Vec::new(); v.read_to_end(&mut content).unwrap(); assert_eq!(reference.len(), content.len()); assert_eq!(reference, &content[..]); // Test Decryptor. // Standard policy. let h = VHelper::new(keys.clone()); let mut v = DecryptorBuilder::from_bytes(crate::tests::file(data))? .with_policy(standard, crate::frozen_time(), h)?; assert!(v.message_processed()); assert_eq!(v.helper_ref().good, 1); assert_eq!(v.helper_ref().errors, 0); let mut content = Vec::new(); v.read_to_end(&mut content).unwrap(); assert_eq!(reference.len(), content.len()); assert_eq!(reference, &content[..]); // Kill the subkey. let h = VHelper::new(keys.clone()); let mut v = DecryptorBuilder::from_bytes(crate::tests::file(data))? .with_policy(no_subkey_signatures, crate::frozen_time(), h)?; assert!(v.message_processed()); assert_eq!(v.helper_ref().good, 0); assert_eq!(v.helper_ref().errors, 1); let mut content = Vec::new(); v.read_to_end(&mut content).unwrap(); assert_eq!(reference.len(), content.len()); assert_eq!(reference, &content[..]); // Kill the data signature. let h = VHelper::new(keys.clone()); let mut v = DecryptorBuilder::from_bytes(crate::tests::file(data))? .with_policy(no_binary_signatures, crate::frozen_time(), h)?; assert!(v.message_processed()); assert_eq!(v.helper_ref().good, 0); assert_eq!(v.helper_ref().errors, 1); let mut content = Vec::new(); v.read_to_end(&mut content).unwrap(); assert_eq!(reference.len(), content.len()); assert_eq!(reference, &content[..]); Ok(()) } #[test] fn hash_algo() -> Result<()> { use crate::types::RevocationStatus; use crate::types::ReasonForRevocation; const SECS_IN_YEAR : u64 = 365 * 24 * 60 * 60; // A `const fn` is only guaranteed to be evaluated at compile // time if the result is assigned to a `const` variable. Make // sure that works. const DEFAULT : StandardPolicy = StandardPolicy::new(); let (cert, _) = CertBuilder::new() .add_userid("Alice") .generate()?; let algo = cert.primary_key() .binding_signature(&DEFAULT, None).unwrap().hash_algo(); eprintln!("{:?}", algo); // Create a revoked version. let mut keypair = cert.primary_key().key().clone() .parts_into_secret()?.into_keypair()?; let rev = cert.revoke( &mut keypair, ReasonForRevocation::KeyCompromised, b"It was the maid :/")?; let cert_revoked = cert.clone().insert_packets(rev)?.0; match cert_revoked.revocation_status(&DEFAULT, None) { RevocationStatus::Revoked(sigs) => { assert_eq!(sigs.len(), 1); assert_eq!(sigs[0].hash_algo(), algo); } _ => panic!("not revoked"), } // Reject the hash algorithm unconditionally. let mut reject : StandardPolicy = StandardPolicy::new(); reject.reject_hash(algo); assert!(cert.primary_key() .binding_signature(&reject, None).is_err()); assert_match!(RevocationStatus::NotAsFarAsWeKnow = cert_revoked.revocation_status(&reject, None)); // Reject the hash algorithm next year. let mut reject : StandardPolicy = StandardPolicy::new(); reject.reject_hash_at( algo, crate::now().checked_add(Duration::from_secs(SECS_IN_YEAR))); reject.hash_revocation_tolerance(0); cert.primary_key().binding_signature(&reject, None)?; assert_match!(RevocationStatus::Revoked(_) = cert_revoked.revocation_status(&reject, None)); // Reject the hash algorithm last year. let mut reject : StandardPolicy = StandardPolicy::new(); reject.reject_hash_at( algo, crate::now().checked_sub(Duration::from_secs(SECS_IN_YEAR))); reject.hash_revocation_tolerance(0); assert!(cert.primary_key() .binding_signature(&reject, None).is_err()); assert_match!(RevocationStatus::NotAsFarAsWeKnow = cert_revoked.revocation_status(&reject, None)); // Reject the hash algorithm for normal signatures last year, // and revocations next year. let mut reject : StandardPolicy = StandardPolicy::new(); reject.reject_hash_at( algo, crate::now().checked_sub(Duration::from_secs(SECS_IN_YEAR))); reject.hash_revocation_tolerance(2 * SECS_IN_YEAR as u32); assert!(cert.primary_key() .binding_signature(&reject, None).is_err()); assert_match!(RevocationStatus::Revoked(_) = cert_revoked.revocation_status(&reject, None)); // Accept algo, but reject the algos with id - 1 and id + 1. let mut reject : StandardPolicy = StandardPolicy::new(); let algo_u8 : u8 = algo.into(); assert!(algo_u8 != 0u8); reject.reject_hash_at( (algo_u8 - 1).into(), crate::now().checked_sub(Duration::from_secs(SECS_IN_YEAR))); reject.reject_hash_at( (algo_u8 + 1).into(), crate::now().checked_sub(Duration::from_secs(SECS_IN_YEAR))); reject.hash_revocation_tolerance(0); cert.primary_key().binding_signature(&reject, None)?; assert_match!(RevocationStatus::Revoked(_) = cert_revoked.revocation_status(&reject, None)); // Reject the hash algorithm since before the Unix epoch. // Since the earliest representable time using a Timestamp is // the Unix epoch, this is equivalent to rejecting everything. let mut reject : StandardPolicy = StandardPolicy::new(); reject.reject_hash_at( algo, crate::now().checked_sub(Duration::from_secs(SECS_IN_YEAR))); reject.hash_revocation_tolerance(0); assert!(cert.primary_key() .binding_signature(&reject, None).is_err()); assert_match!(RevocationStatus::NotAsFarAsWeKnow = cert_revoked.revocation_status(&reject, None)); // Reject the hash algorithm after the end of time that is // representable by a Timestamp (2106). This should accept // everything. let mut reject : StandardPolicy = StandardPolicy::new(); reject.reject_hash_at( algo, SystemTime::UNIX_EPOCH.checked_add(Duration::from_secs(500 * SECS_IN_YEAR))); reject.hash_revocation_tolerance(0); cert.primary_key().binding_signature(&reject, None)?; assert_match!(RevocationStatus::Revoked(_) = cert_revoked.revocation_status(&reject, None)); Ok(()) } #[test] fn key_verify_self_signature() -> Result<()> { let p = &P::new(); #[derive(Debug)] struct NoRsa; impl Policy for NoRsa { fn key(&self, ka: &ValidErasedKeyAmalgamation<key::PublicParts>) -> Result<()> { use crate::types::PublicKeyAlgorithm::*; eprintln!("algo: {}", ka.key().pk_algo()); if ka.key().pk_algo() == RSAEncryptSign { Err(anyhow::anyhow!("RSA!")) } else { Ok(()) } } fn signature(&self, _sig: &Signature, _sec: HashAlgoSecurity) -> Result<()> { Ok(()) } fn symmetric_algorithm(&self, _algo: SymmetricAlgorithm) -> Result<()> { Ok(()) } fn aead_algorithm(&self, _algo: AEADAlgorithm) -> Result<()> { Ok(()) } fn packet(&self, _packet: &Packet) -> Result<()> { Ok(()) } } let norsa = &NoRsa {}; // Generate a certificate with an RSA primary and two RSA // subkeys. let (cert,_) = CertBuilder::new() .set_cipher_suite(CipherSuite::RSA2k) .add_signing_subkey() .add_signing_subkey() .generate()?; assert_eq!(cert.keys().with_policy(p, None).count(), 3); assert_eq!(cert.keys().with_policy(norsa, None).count(), 0); assert!(cert.primary_key().with_policy(p, None).is_ok()); assert!(cert.primary_key().with_policy(norsa, None).is_err()); // Generate a certificate with an ECC primary, an ECC subkey, // and an RSA subkey. let (cert,_) = CertBuilder::new() .set_cipher_suite(CipherSuite::Cv25519) .add_signing_subkey() .generate()?; let pk = cert.primary_key().key().parts_as_secret()?; let subkey: key::SecretSubkey = Key4::generate_rsa(2048)?.into(); let binding = signature::SignatureBuilder::new(SignatureType::SubkeyBinding) .set_key_flags(KeyFlags::empty().set_transport_encryption())? .sign_subkey_binding(&mut pk.clone().into_keypair()?, pk.parts_as_public(), &subkey)?; let cert = cert.insert_packets( vec![ Packet::from(subkey), binding.into() ])?.0; assert_eq!(cert.keys().with_policy(p, None).count(), 3); assert_eq!(cert.keys().with_policy(norsa, None).count(), 2); assert!(cert.primary_key().with_policy(p, None).is_ok()); assert!(cert.primary_key().with_policy(norsa, None).is_ok()); // Generate a certificate with an RSA primary, an RSA subkey, // and an ECC subkey. let (cert,_) = CertBuilder::new() .set_cipher_suite(CipherSuite::RSA2k) .add_signing_subkey() .generate()?; let pk = cert.primary_key().key().parts_as_secret()?; let subkey: key::SecretSubkey = key::Key6::generate_ecc(true, Curve::Ed25519)?.into(); let binding = signature::SignatureBuilder::new(SignatureType::SubkeyBinding) .set_key_flags(KeyFlags::empty().set_transport_encryption())? .sign_subkey_binding(&mut pk.clone().into_keypair()?, pk.parts_as_public(), &subkey)?; let cert = cert.insert_packets( vec![ Packet::from(subkey), binding.into() ])?.0; assert_eq!(cert.keys().with_policy(p, None).count(), 3); assert_eq!(cert.keys().with_policy(norsa, None).count(), 0); assert!(cert.primary_key().with_policy(p, None).is_ok()); assert!(cert.primary_key().with_policy(norsa, None).is_err()); // Generate a certificate with an ECC primary and two ECC // subkeys. let (cert,_) = CertBuilder::new() .set_cipher_suite(CipherSuite::Cv25519) .add_signing_subkey() .add_signing_subkey() .generate()?; assert_eq!(cert.keys().with_policy(p, None).count(), 3); assert_eq!(cert.keys().with_policy(norsa, None).count(), 3); assert!(cert.primary_key().with_policy(p, None).is_ok()); assert!(cert.primary_key().with_policy(norsa, None).is_ok()); Ok(()) } #[test] fn key_verify_binary_signature() -> Result<()> { use crate::packet::signature; use crate::serialize::SerializeInto; use crate::Packet; use crate::types::KeyFlags; let p = &P::new(); #[derive(Debug)] struct NoRsa; impl Policy for NoRsa { fn key(&self, ka: &ValidErasedKeyAmalgamation<key::PublicParts>) -> Result<()> { use crate::types::PublicKeyAlgorithm::*; eprintln!("algo: {} is {}", ka.key().fingerprint(), ka.key().pk_algo()); if ka.key().pk_algo() == RSAEncryptSign { Err(anyhow::anyhow!("RSA!")) } else { Ok(()) } } fn signature(&self, _sig: &Signature, _sec: HashAlgoSecurity) -> Result<()> { Ok(()) } fn symmetric_algorithm(&self, _algo: SymmetricAlgorithm) -> Result<()> { Ok(()) } fn aead_algorithm(&self, _algo: AEADAlgorithm) -> Result<()> { Ok(()) } fn packet(&self, _packet: &Packet) -> Result<()> { Ok(()) } } let norsa = &NoRsa {}; #[derive(PartialEq, Debug)] struct VHelper { good: usize, errors: usize, keys: Vec<Cert>, } impl VHelper { fn new(keys: Vec<Cert>) -> Self { VHelper { good: 0, errors: 0, keys, } } } impl VerificationHelper for VHelper { fn get_certs(&mut self, _ids: &[crate::KeyHandle]) -> Result<Vec<Cert>> { Ok(self.keys.clone()) } fn check(&mut self, structure: MessageStructure) -> Result<()> { for layer in structure { match layer { MessageLayer::SignatureGroup { ref results } => for result in results { match result { Ok(_) => self.good += 1, Err(e) => { eprintln!("{}", e); self.errors += 1 }, } } MessageLayer::Compression { .. } => (), _ => unreachable!(), } } Ok(()) } } impl DecryptionHelper for VHelper { fn decrypt(&mut self, _: &[PKESK], _: &[SKESK], _: Option<SymmetricAlgorithm>, _: &mut dyn FnMut(Option<SymmetricAlgorithm>, &SessionKey) -> bool) -> Result<Option<Cert>> { unreachable!(); } } // Sign msg using cert's first subkey, return the signature. fn sign_and_verify(p: &dyn Policy, cert: &Cert, good: bool) { eprintln!("Expect verification to be {}", if good { "good" } else { "bad" }); for (i, k) in cert.keys().enumerate() { eprintln!(" {}. {}", i, k.key().fingerprint()); } let msg = b"Hello, World"; // We always use the first subkey. let key = cert.keys().nth(1).unwrap().key(); let mut keypair = key.clone() .parts_into_secret().unwrap() .into_keypair().unwrap(); // Create a signature. let sig = signature::SignatureBuilder::new(SignatureType::Binary) .sign_message(&mut keypair, msg).unwrap(); // Make sure the signature is ok. sig.verify_message(key, msg).unwrap(); // Turn it into a detached signature. let sig = Packet::from(sig).to_vec().unwrap(); let h = VHelper::new(vec![ cert.clone() ]); let mut v = DetachedVerifierBuilder::from_bytes(&sig).unwrap() .with_policy(p, None, h).unwrap(); v.verify_bytes(msg).unwrap(); assert_eq!(v.helper_ref().good, if good { 1 } else { 0 }); assert_eq!(v.helper_ref().errors, if good { 0 } else { 1 }); } // A certificate with an ECC primary and an ECC signing // subkey. eprintln!("Trying ECC primary, ECC sub:"); let (cert,_) = CertBuilder::new() .set_cipher_suite(CipherSuite::Cv25519) .add_subkey(KeyFlags::empty().set_signing(), None, None) .generate()?; assert_eq!(cert.keys().with_policy(p, None).count(), 2); assert_eq!(cert.keys().with_policy(norsa, None).count(), 2); assert!(cert.primary_key().with_policy(p, None).is_ok()); assert!(cert.primary_key().with_policy(norsa, None).is_ok()); sign_and_verify(p, &cert, true); sign_and_verify(norsa, &cert, true); // A certificate with an RSA primary and an RCC signing // subkey. eprintln!("Trying RSA primary, ECC sub:"); let (cert,_) = CertBuilder::new() .set_cipher_suite(CipherSuite::RSA2k) .add_subkey(KeyFlags::empty().set_signing(), None, CipherSuite::Cv25519) .generate()?; assert_eq!(cert.keys().with_policy(p, None).count(), 2); assert_eq!(cert.keys().with_policy(norsa, None).count(), 0); assert!(cert.primary_key().with_policy(p, None).is_ok()); assert!(cert.primary_key().with_policy(norsa, None).is_err()); sign_and_verify(p, &cert, true); sign_and_verify(norsa, &cert, false); // A certificate with an ECC primary and an RSA signing // subkey. eprintln!("Trying ECC primary, RSA sub:"); let (cert,_) = CertBuilder::new() .set_cipher_suite(CipherSuite::Cv25519) .add_subkey(KeyFlags::empty().set_signing(), None, CipherSuite::RSA2k) .generate()?; assert_eq!(cert.keys().with_policy(p, None).count(), 2); assert_eq!(cert.keys().with_policy(norsa, None).count(), 1); assert!(cert.primary_key().with_policy(p, None).is_ok()); assert!(cert.primary_key().with_policy(norsa, None).is_ok()); sign_and_verify(p, &cert, true); sign_and_verify(norsa, &cert, false); Ok(()) } #[test] fn reject_seip_packet() -> Result<()> { #[derive(PartialEq, Debug)] struct Helper {} impl VerificationHelper for Helper { fn get_certs(&mut self, _: &[crate::KeyHandle]) -> Result<Vec<Cert>> { unreachable!() } fn check(&mut self, _: MessageStructure) -> Result<()> { unreachable!() } } impl DecryptionHelper for Helper { fn decrypt(&mut self, _: &[PKESK], _: &[SKESK], _: Option<SymmetricAlgorithm>, _: &mut dyn FnMut(Option<SymmetricAlgorithm>, &SessionKey) -> bool) -> Result<Option<Cert>> { Ok(None) } } let p = &P::new(); let r = DecryptorBuilder::from_bytes(crate::tests::message( "encrypted-to-testy.gpg"))? .with_policy(p, crate::frozen_time(), Helper {}); match r { Ok(_) => panic!(), Err(e) => assert_match!(Error::MissingSessionKey(_) = e.downcast().unwrap()), } // Reject the SEIP packet. let p = &mut P::new(); p.reject_packet_tag(Tag::SEIP); let r = DecryptorBuilder::from_bytes(crate::tests::message( "encrypted-to-testy.gpg"))? .with_policy(p, crate::frozen_time(), Helper {}); match r { Ok(_) => panic!(), Err(e) => assert_match!(Error::PolicyViolation(_, _) = e.downcast().unwrap()), } Ok(()) } #[test] fn reject_cipher() -> Result<()> { struct Helper {} impl VerificationHelper for Helper { fn get_certs(&mut self, _: &[crate::KeyHandle]) -> Result<Vec<Cert>> { Ok(Default::default()) } fn check(&mut self, _: MessageStructure) -> Result<()> { Ok(()) } } impl DecryptionHelper for Helper { fn decrypt(&mut self, pkesks: &[PKESK], _: &[SKESK], algo: Option<SymmetricAlgorithm>, decrypt: &mut dyn FnMut(Option<SymmetricAlgorithm>, &SessionKey) -> bool) -> Result<Option<Cert>> { let p = &P::new(); let mut pair = Cert::from_bytes( crate::tests::key("testy-private.pgp"))? .keys().with_policy(p, None) .for_transport_encryption().secret().next().unwrap() .key().clone().into_keypair()?; pkesks[0].decrypt(&mut pair, algo) .map(|(algo, session_key)| decrypt(algo, &session_key)); Ok(None) } } let p = &P::new(); DecryptorBuilder::from_bytes(crate::tests::message( "encrypted-to-testy-no-compression.gpg"))? .with_policy(p, crate::frozen_time(), Helper {})?; // Reject the AES256. let p = &mut P::new(); p.reject_symmetric_algo(SymmetricAlgorithm::AES256); let r = DecryptorBuilder::from_bytes(crate::tests::message( "encrypted-to-testy-no-compression.gpg"))? .with_policy(p, crate::frozen_time(), Helper {}); match r { Ok(_) => panic!(), Err(e) => assert_match!(Error::PolicyViolation(_, _) = e.downcast().unwrap()), } Ok(()) } #[test] fn reject_asymmetric_algos() -> Result<()> { let cert = Cert::from_bytes(crate::tests::key("neal.pgp"))?; let p = &mut P::new(); let t = crate::frozen_time(); assert_eq!(cert.with_policy(p, t).unwrap().keys().count(), 4); p.reject_asymmetric_algo(AsymmetricAlgorithm::RSA1024); assert_eq!(cert.with_policy(p, t).unwrap().keys().count(), 4); p.reject_asymmetric_algo(AsymmetricAlgorithm::RSA2048); assert_eq!(cert.with_policy(p, t).unwrap().keys().count(), 1); Ok(()) } #[test] fn reject_all_hashes() -> Result<()> { let mut p = StandardPolicy::new(); let set_variants = [ HashAlgorithm::MD5, HashAlgorithm::Unknown(234), ]; let check_variants = [ HashAlgorithm::SHA512, HashAlgorithm::Unknown(239), ]; // Accept a few hashes explicitly. for v in set_variants.iter().cloned() { p.accept_hash(v); assert_eq!( p.hash_cutoff( v, HashAlgoSecurity::SecondPreImageResistance), ACCEPT.map(Into::into)); assert_eq!( p.hash_cutoff( v, HashAlgoSecurity::CollisionResistance), ACCEPT.map(Into::into)); } // Reject all hashes. p.reject_all_hashes(); for v in set_variants.iter().chain(check_variants.iter()).cloned() { assert_eq!( p.hash_cutoff( v, HashAlgoSecurity::SecondPreImageResistance), REJECT.map(Into::into)); assert_eq!( p.hash_cutoff( v, HashAlgoSecurity::CollisionResistance), REJECT.map(Into::into)); } Ok(()) } macro_rules! reject_all_check { ($reject_all:ident, $accept_one:ident, $cutoff:ident, $set_variants:expr, $check_variants:expr) => { #[test] fn $reject_all() -> Result<()> { let mut p = StandardPolicy::new(); // Accept a few hashes explicitly. for v in $set_variants.iter().cloned() { p.$accept_one(v); assert_eq!(p.$cutoff(v), ACCEPT.map(Into::into)); } // Reject all hashes. p.$reject_all(); for v in $set_variants.iter() .chain($check_variants.iter()).cloned() { assert_eq!( p.$cutoff(v), REJECT.map(Into::into)); } Ok(()) } } } reject_all_check!(reject_all_critical_subpackets, accept_critical_subpacket, critical_subpacket_cutoff, &[ SubpacketTag::TrustSignature, SubpacketTag::Unknown(252) ], &[ SubpacketTag::Unknown(253), SubpacketTag::SignatureCreationTime ]); reject_all_check!(reject_all_asymmetric_algos, accept_asymmetric_algo, asymmetric_algo_cutoff, &[ AsymmetricAlgorithm::RSA3072, AsymmetricAlgorithm::Cv25519 ], &[ AsymmetricAlgorithm::Unknown, AsymmetricAlgorithm::NistP256 ]); reject_all_check!(reject_all_symmetric_algos, accept_symmetric_algo, symmetric_algo_cutoff, &[ SymmetricAlgorithm::Unencrypted, SymmetricAlgorithm::Unknown(252) ], &[ SymmetricAlgorithm::AES256, SymmetricAlgorithm::Unknown(230) ]); reject_all_check!(reject_all_aead_algos, accept_aead_algo, aead_algo_cutoff, &[ AEADAlgorithm::OCB ], &[ AEADAlgorithm::EAX ]); #[test] fn reject_all_packets() -> Result<()> { let mut p = StandardPolicy::new(); let set_variants = [ (Tag::SEIP, 4), (Tag::Unknown(252), 17), ]; let check_variants = [ (Tag::Signature, 4), (Tag::Unknown(230), 9), ]; // Accept a few packets explicitly. for (t, v) in set_variants.iter().cloned() { p.accept_packet_tag_version(t, v); assert_eq!( p.packet_tag_version_cutoff(t, v), ACCEPT.map(Into::into)); } // Reject all hashes. p.reject_all_packet_tags(); for (t, v) in set_variants.iter().chain(check_variants.iter()).cloned() { assert_eq!( p.packet_tag_version_cutoff(t, v), REJECT.map(Into::into)); } Ok(()) } #[test] fn packet_versions() -> Result<()> { // Accept the version of a packet. Optionally make sure a // different version is not accepted. fn accept_and_check(p: &mut StandardPolicy, tag: Tag, accept_versions: &[u8], good_versions: &[u8], bad_versions: &[u8]) { for v in accept_versions { p.accept_packet_tag_version(tag, *v); assert_eq!( p.packet_tag_version_cutoff(tag, *v), ACCEPT.map(Into::into)); } for v in good_versions.iter() { assert_eq!( p.packet_tag_version_cutoff(tag, *v), ACCEPT.map(Into::into)); } for v in bad_versions.iter() { assert_eq!( p.packet_tag_version_cutoff(tag, *v), REJECT.map(Into::into)); } } use rand::seq::SliceRandom; let mut rng = rand::thread_rng(); let mut all_versions = (0..=u8::MAX).collect::<Vec<_>>(); all_versions.shuffle(&mut rng); let all_versions = &all_versions[..]; let mut not_v5 = all_versions.iter() .filter(|&&v| v != 5) .cloned() .collect::<Vec<_>>(); not_v5.shuffle(&mut rng); let not_v5 = &not_v5[..]; let p = &mut StandardPolicy::new(); p.reject_all_packet_tags(); // First only use the versioned interfaces. accept_and_check(p, Tag::Signature, &[3], &[], &[4, 5]); accept_and_check(p, Tag::Signature, &[4], &[3], &[5]); // Only use an unversioned policy. accept_and_check(p, Tag::SEIP, &[], // set to accept &[], // good all_versions, // bad ); p.accept_packet_tag(Tag::SEIP); accept_and_check(p, Tag::SEIP, &[], // set to accept all_versions, // good &[], // bad ); // Set an unversioned policy and then a versioned policy. accept_and_check(p, Tag::PKESK, &[], // set to accept &[], // good all_versions, // bad ); p.accept_packet_tag(Tag::PKESK); accept_and_check(p, Tag::PKESK, &[], // set to accept &(0..u8::MAX).collect::<Vec<_>>()[..], // good &[], // bad ); p.reject_packet_tag_version(Tag::PKESK, 5); accept_and_check(p, Tag::PKESK, &[], // set to accept not_v5, // good &[5], // bad ); // Set a versioned policy and then an unversioned policy. // Make sure that the versioned policy is cleared by the // unversioned policy. accept_and_check(p, Tag::SKESK, &[], // set to accept &[], // good all_versions, // bad ); p.accept_packet_tag_version(Tag::SKESK, 5); accept_and_check(p, Tag::SKESK, &[], // set to accept &[5], // good not_v5, // bad ); p.reject_packet_tag(Tag::SKESK); // All versions should be bad now... accept_and_check(p, Tag::SKESK, &[], // set to accept &[], // good all_versions, // bad ); Ok(()) } } ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/src/regex/grammar.lalrpop�����������������������������������������������������0000644�0000000�0000000�00000014446�10461020230�0017672�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������// -*- mode: Rust; -*- use super::generate_class; use super::lexer; use super::lexer::{Token, LexicalError}; use regex_syntax::hir::{self, Hir}; // Pass in the original, untokenized input to facilitate error // recovery. grammar<'input>(input: &'input str); // This is a straightforward translation of the regular expression // grammar from section 8 of RFC 4880. // // https://www.rfc-editor.org/rfc/rfc9580.html#section-8 pub(crate) Regex : Hir = { <l:LBranch> <r:RBranch*> => { let mut r = r; r.insert(0, l); // If any of the branches are empty, then that branch matches // everything, and we can just short circuit the whole // alternation. // // This is actually required for version 1.3.7 of the regex // crate, which is the version that is in Debian Bullseye. // See issue #694 for details. if r.iter().any(|b| *b.kind() == hir::HirKind::Empty) { hir::Hir::empty() } else { Hir::alternation(r) } }, } LBranch : Hir = { Branch, } RBranch : Hir = { PIPE <Branch>, } Branch : Hir = { => { hir::Hir::empty() }, <p:Piece+> => { if p.iter().all(|p| *p.kind() == hir::HirKind::Empty) { // All pieces are empty. Just return empty. hir::Hir::empty() } else { hir::Hir::concat(p) } }, } Piece : Hir = { <a:Atom> => a, <a:Atom> STAR => { if *a.kind() == hir::HirKind::Empty { // Piece is empty. This is equivalent to empty so just // return it. a } else { hir::Hir::repetition(hir::Repetition { min: 0, max: None, greedy: true, sub: Box::new(a) }) } }, <a:Atom> PLUS => { if *a.kind() == hir::HirKind::Empty { // Piece is empty. This is equivalent to empty so just // return it. a } else { hir::Hir::repetition(hir::Repetition { min: 1, max: None, greedy: true, sub: Box::new(a) }) } }, <a:Atom> QUESTION => { if *a.kind() == hir::HirKind::Empty { // Piece is empty. This is equivalent to empty so just // return it. a } else { hir::Hir::repetition(hir::Repetition { min: 0, max: Some(1), greedy: true, sub: Box::new(a) }) } }, } Atom : Hir = { LPAREN <r:Regex> RPAREN => { r }, Range, DOT => { hir::Hir::dot(hir::Dot::AnyChar) }, CARET => { hir::Hir::look(hir::Look::Start) }, DOLLAR => { hir::Hir::look(hir::Look::End) }, BACKSLASH <t:AnyChar> => { // "A buffer of length four is large enough to encode any // char." // // https://doc.rust-lang.org/std/primitive.char.html#method.encode_utf8 let mut buffer = [0; 4]; // Convert the Unicode character t to a string. let s = t.to_char().encode_utf8(&mut buffer); hir::Hir::literal(s.as_bytes()) }, DASH => { hir::Hir::literal("-".as_bytes()) }, <t:OTHER> => { // "A buffer of length four is large enough to encode any // char." // // https://doc.rust-lang.org/std/primitive.char.html#method.encode_utf8 let mut buffer = [0; 4]; let s = t.to_char().encode_utf8(&mut buffer); hir::Hir::literal(s.as_bytes()) }, } Range : Hir = { LBRACKET <c:CARET?> <class1:RBRACKET> <class2:NotRBracket*> RBRACKET => { generate_class(c.is_some(), std::iter::once(class1.to_char()) .chain(class2.into_iter().map(|t| t.to_char()))) }, LBRACKET CARET <class:NotRBracket+> RBRACKET => { generate_class(true, class.into_iter().map(|t| t.to_char())) }, LBRACKET <class1:NotCaretNotRBracket> <class2:NotRBracket*> RBRACKET => { generate_class(false, std::iter::once(class1.to_char()) .chain(class2.into_iter().map(|t| t.to_char()))) }, } NotRBracket : Token = { PIPE => Token::OTHER('|'), STAR => Token::OTHER('*'), PLUS => Token::OTHER('+'), QUESTION => Token::OTHER('?'), LPAREN => Token::OTHER('('), RPAREN => Token::OTHER(')'), DOT => Token::OTHER('.'), CARET => Token::OTHER('^'), DOLLAR => Token::OTHER('$'), BACKSLASH => Token::OTHER('\\'), LBRACKET => Token::OTHER('['), // RBRACKET => Token::OTHER(']'), DASH => Token::OTHER('-'), OTHER, } NotCaretNotRBracket : Token = { PIPE => Token::OTHER('|'), STAR => Token::OTHER('*'), PLUS => Token::OTHER('+'), QUESTION => Token::OTHER('?'), LPAREN => Token::OTHER('('), RPAREN => Token::OTHER(')'), DOT => Token::OTHER('.'), // CARET => Token::OTHER('^'), DOLLAR => Token::OTHER('$'), BACKSLASH => Token::OTHER('\\'), LBRACKET => Token::OTHER('['), // RBRACKET => Token::OTHER(']'), DASH => Token::OTHER('-'), OTHER, } AnyChar : Token = { PIPE => Token::OTHER('|'), STAR => Token::OTHER('*'), PLUS => Token::OTHER('+'), QUESTION => Token::OTHER('?'), LPAREN => Token::OTHER('('), RPAREN => Token::OTHER(')'), DOT => Token::OTHER('.'), CARET => Token::OTHER('^'), DOLLAR => Token::OTHER('$'), BACKSLASH => Token::OTHER('\\'), LBRACKET => Token::OTHER('['), RBRACKET => Token::OTHER(']'), DASH => Token::OTHER('-'), OTHER, } extern { type Location = usize; type Error = LexicalError; enum lexer::Token { PIPE => lexer::Token::PIPE, STAR => lexer::Token::STAR, PLUS => lexer::Token::PLUS, QUESTION => lexer::Token::QUESTION, LPAREN => lexer::Token::LPAREN, RPAREN => lexer::Token::RPAREN, DOT => lexer::Token::DOT, CARET => lexer::Token::CARET, DOLLAR => lexer::Token::DOLLAR, BACKSLASH => lexer::Token::BACKSLASH, LBRACKET => lexer::Token::LBRACKET, RBRACKET => lexer::Token::RBRACKET, DASH => lexer::Token::DASH, OTHER => lexer::Token::OTHER(_), } } ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/src/regex/lexer.rs������������������������������������������������������������0000644�0000000�0000000�00000012531�10461020230�0016327�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������use std::fmt; #[derive(Clone, PartialEq, Eq, Debug)] pub enum LexicalError { } impl fmt::Display for LexicalError { // This trait requires `fmt` with this exact signature. fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.write_str("{}") } } pub type Spanned<Token, Loc, LexicalError> = Result<(Loc, Token, Loc), LexicalError>; // The type of the parser's input. // // The parser iterators over tuples consisting of the token's starting // position, the token itself, and the token's ending position. pub(crate) type LexerItem<Token, Loc, LexicalError> = Spanned<Token, Loc, LexicalError>; /// The components of an OpenPGP Message. #[derive(Debug, Clone, PartialEq)] pub enum Token { PIPE, STAR, PLUS, QUESTION, LPAREN, RPAREN, DOT, CARET, DOLLAR, BACKSLASH, LBRACKET, RBRACKET, DASH, OTHER(char), } assert_send_and_sync!(Token); impl fmt::Display for Token { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.write_str(&format!("{:?}", self)[..]) } } impl From<Token> for String { fn from(t: Token) -> String { use self::Token::*; match t { PIPE => '|'.to_string(), STAR => '*'.to_string(), PLUS => '+'.to_string(), QUESTION => '?'.to_string(), LPAREN => '('.to_string(), RPAREN => ')'.to_string(), DOT => '.'.to_string(), CARET => '^'.to_string(), DOLLAR => '$'.to_string(), BACKSLASH => '\\'.to_string(), LBRACKET => '['.to_string(), RBRACKET => ']'.to_string(), DASH => '-'.to_string(), OTHER(c) => c.to_string(), } } } impl Token { pub fn to_char(&self) -> char { use self::Token::*; match self { PIPE => '|', STAR => '*', PLUS => '+', QUESTION => '?', LPAREN => '(', RPAREN => ')', DOT => '.', CARET => '^', DOLLAR => '$', BACKSLASH => '\\', LBRACKET => '[', RBRACKET => ']', DASH => '-', OTHER(c) => *c, } } } pub(crate) struct Lexer<'input> { offset: usize, input: &'input str, } impl<'input> Lexer<'input> { pub fn new(input: &'input str) -> Self { Lexer { offset: 0, input } } } impl<'input> Iterator for Lexer<'input> { type Item = LexerItem<Token, usize, LexicalError>; fn next(&mut self) -> Option<Self::Item> { use self::Token::*; tracer!(super::TRACE, "regex::Lexer::next"); // Returns the length of the first character in s in bytes. // If s is empty, returns 0. fn char_bytes(s: &str) -> usize { if let Some(c) = s.chars().next() { c.len_utf8() } else { 0 } } let one = |input: &'input str| -> Option<Token> { let c = input.chars().next()?; Some(match c { '|' => PIPE, '*' => STAR, '+' => PLUS, '?' => QUESTION, '(' => LPAREN, ')' => RPAREN, '.' => DOT, '^' => CARET, '$' => DOLLAR, '\\' => BACKSLASH, '[' => LBRACKET, ']' => RBRACKET, '-' => DASH, _ => OTHER(c), }) }; let l = char_bytes(self.input); let t = match one(self.input) { Some(t) => t, None => return None, }; self.input = &self.input[l..]; let start = self.offset; let end = start + l; self.offset += l; t!("Returning token at offset {}: '{:?}'", start, t); Some(Ok((start, t, end))) } } impl<'input> From<&'input str> for Lexer<'input> { fn from(i: &'input str) -> Lexer<'input> { Lexer::new(i) } } #[cfg(test)] mod tests { use super::*; #[test] fn lexer() { fn lex(s: &str, expected: &[Token]) { let tokens: Vec<Token> = Lexer::new(s) .map(|t| t.unwrap().1) .collect(); assert_eq!(&tokens[..], expected, "{}", s); } use Token::*; lex("|", &[ PIPE ]); lex("*", &[ STAR ]); lex("+", &[ PLUS ]); lex("?", &[ QUESTION ]); lex("(", &[ LPAREN ]); lex(")", &[ RPAREN ]); lex(".", &[ DOT ]); lex("^", &[ CARET ]); lex("$", &[ DOLLAR ]); lex("\\", &[ BACKSLASH ]); lex("[", &[ LBRACKET ]); lex("]", &[ RBRACKET ]); lex("-", &[ DASH ]); lex("a", &[ OTHER('a') ]); lex("aa", &[ OTHER('a'), OTHER('a') ]); lex("foo", &[ OTHER('f'), OTHER('o'), OTHER('o') ]); lex("foo\\bar", &[ OTHER('f'), OTHER('o'), OTHER('o'), BACKSLASH, OTHER('b'), OTHER('a'), OTHER('r') ]); lex("*?!", &[ STAR, QUESTION, OTHER('!') ]); // Multi-byte UTF-8. lex("ßℝ💣", &[ OTHER('ß'), OTHER('ℝ'), OTHER('💣'), ]); lex("(ß|ℝ|💣", &[ LPAREN, OTHER('ß'), PIPE, OTHER('ℝ'), PIPE, OTHER('💣') ]); lex("東京", &[ OTHER('東'), OTHER('京') ]); } } �����������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/src/regex/mod.rs��������������������������������������������������������������0000644�0000000�0000000�00000206402�10461020230�0015771�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������//! OpenPGP regex parser. //! //! OpenPGP defines a [regular expression language]. It is used with //! [trust signatures] to scope the trust that they extend. //! //! [regular expression language]: https://www.rfc-editor.org/rfc/rfc9580.html#section-8 //! [trust signatures]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.21 //! //! Compared with most regular expression languages, OpenPGP's is //! quite simple. In particular, it only includes the following //! features: //! //! - Alternations using `|`, //! - Grouping using `(` and `)`, //! - The `*`, `+`, and `?` glob operators, //! - The `^`, and `$` anchors, //! - The '.' operator, positive *non-empty* ranges //! (e.g. `[a-zA-Z]`) and negative *non-empty* ranges (`[^@]`), and //! - The backslash operator to escape special characters (except //! in ranges). //! //! The regular expression engine defined in this module implements //! that language with two differences. The first difference is that //! the compiler only works on UTF-8 strings (not bytes). The second //! difference is that ranges in character classes are between UTF-8 //! characters, not just ASCII characters. //! //! # Data Structures //! //! This module defines two data structures. [`Regex`] encapsulates a //! valid regular expression, and provides methods to check whether //! the regular expression matches a string or a [`UserID`]. //! [`RegexSet`] is similar, but encapsulates zero or more regular //! expressions, which may or may not be valid. Its match methods //! return `true` if there are no regular expressions, or, if there is //! at least one regular expression, they return whether at least one //! of the regular expressions matches it. `RegexSet`'s matcher //! handles invalid regular expressions by considering them to be //! regular expressions that don't match anything. These semantics //! are consistent with a trust signature's scoping rules. Further, //! strings that contain control characters never match. This //! behavior can be overridden using [`Regex::disable_sanitizations`] //! and [`RegexSet::disable_sanitizations`]. //! //! [`UserID`]: crate::packet::UserID //! [`Regex::disable_sanitizations`]: Regex::disable_sanitizations() //! [`RegexSet::disable_sanitizations`]: RegexSet::disable_sanitizations() //! //! # Scoped Trust Signatures //! //! To create a trust signature, you create a signature whose [type] //! is either [GenericCertification], [PersonaCertification], //! [CasualCertification], or [PositiveCertification], and add a //! [Trust Signature] subpacket using, for instance, the //! [`SignatureBuilder::set_trust_signature`] method. //! //! [type]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.1 //! [GenericCertification]: crate::types::SignatureType::GenericCertification //! [PersonaCertification]: crate::types::SignatureType::PersonaCertification //! [CasualCertification]: crate::types::SignatureType::CasualCertification //! [PositiveCertification]: crate::types::SignatureType::PositiveCertification //! [Trust Signature]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.21 //! [`SignatureBuilder::set_trust_signature`]: crate::packet::signature::SignatureBuilder::set_trust_signature() //! //! To scope a trust signature, you add a [Regular Expression //! subpacket] to it using //! [`SignatureBuilder::set_regular_expression`] or //! [`SignatureBuilder::add_regular_expression`]. //! //! To extract any regular expressions, you can use //! [`SubpacketAreas::regular_expressions`]. //! //! [Regular Expression subpacket]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.22 //! [`SignatureBuilder::set_regular_expression`]: crate::packet::signature::SignatureBuilder::set_regular_expression() //! [`SignatureBuilder::add_regular_expression`]: crate::packet::signature::SignatureBuilder::add_regular_expression() //! [`SubpacketAreas::regular_expressions`]: crate::packet::signature::subpacket::SubpacketAreas::regular_expressions() //! //! # Caveat Emptor //! //! Note: GnuPG has [very limited regular expression support]. In //! particular, it only recognizes regular expressions with the //! following form: //! //! [very limited regular expression support]: https://dev.gnupg.org/source/gnupg/browse/master/g10/trustdb.c;15e065dee891eef9545556f210b4199107999869$1558 //! //! ```text //! <[^>]+[@.]example\.com>$ //! ``` //! //! Further, it escapes any operators between the `<[^>]+[@.]` and the //! `>$` except `.` and `\`. Otherwise, GnuPG treats the regular //! expression as a literal domain (e.g., `example.com`). //! //! Further, until [version 2.2.22] (released in August 2020), GnuPG //! did not support regular expressions on Windows, and other systems //! that don't include `regcomp`. On these systems, if a trust //! signature included a regular expression, GnuPG conservatively //! considered the whole trust signature to match nothing. //! //! [version 2.2.22]: https://dev.gnupg.org/T5030 //! //! # Examples //! //! A CA signs two certificates, one for Alice, who works at //! `example.com`, and one for Bob, who is associated with `some.org`. //! Carol then creates a trust signature for the CA, which she scopes //! to `example.org` and `example.com`. We then confirm that Carol //! can use the CA to authenticate Alice, but not Bob. //! //! ``` //! use sequoia_openpgp as openpgp; //! use openpgp::cert::prelude::*; //! use openpgp::packet::prelude::*; //! use openpgp::policy::StandardPolicy; //! use openpgp::regex::RegexSet; //! use openpgp::types::SignatureType; //! //! # fn main() -> openpgp::Result<()> { //! let p = &StandardPolicy::new(); //! //! let (ca, _) //! = CertBuilder::general_purpose(Some("OpenPGP CA <openpgp-ca@example.com>")) //! .generate()?; //! let mut ca_signer = ca.primary_key().key().clone() //! .parts_into_secret()?.into_keypair()?; //! let ca_userid = ca.with_policy(p, None)? //! .userids().nth(0).expect("Added a User ID").userid(); //! //! // The CA certifies "Alice <alice@example.com>". //! let (alice, _) //! = CertBuilder::general_purpose(Some("Alice <alice@example.com>")) //! .generate()?; //! let alice_userid = alice.with_policy(p, None)? //! .userids().nth(0).expect("Added a User ID").userid(); //! let alice_certification = SignatureBuilder::new(SignatureType::GenericCertification) //! .sign_userid_binding( //! &mut ca_signer, //! alice.primary_key().component(), //! alice_userid)?; //! let alice = alice.insert_packets(alice_certification.clone())?.0; //! # assert!(alice.clone().into_packets().any(|p| { //! # match p { //! # Packet::Signature(sig) => sig == alice_certification, //! # _ => false, //! # } //! # })); //! //! // The CA certifies "Bob <bob@some.org>". //! let (bob, _) //! = CertBuilder::general_purpose(Some("Bob <bob@some.org>")) //! .generate()?; //! let bob_userid = bob.with_policy(p, None)? //! .userids().nth(0).expect("Added a User ID").userid(); //! let bob_certification = SignatureBuilder::new(SignatureType::GenericCertification) //! .sign_userid_binding( //! &mut ca_signer, //! bob.primary_key().component(), //! bob_userid)?; //! let bob = bob.insert_packets(bob_certification.clone())?.0; //! # assert!(bob.clone().into_packets().any(|p| { //! # match p { //! # Packet::Signature(sig) => sig == bob_certification, //! # _ => false, //! # } //! # })); //! //! //! // Carol tsigns the CA's certificate. //! let (carol, _) //! = CertBuilder::general_purpose(Some("Carol <carol@another.net>")) //! .generate()?; //! let mut carol_signer = carol.primary_key().key().clone() //! .parts_into_secret()?.into_keypair()?; //! //! let ca_tsig = SignatureBuilder::new(SignatureType::GenericCertification) //! .set_trust_signature(2, 120)? //! .set_regular_expression("<[^>]+[@.]example\\.org>$")? //! .add_regular_expression("<[^>]+[@.]example\\.com>$")? //! .sign_userid_binding( //! &mut carol_signer, //! ca.primary_key().component(), //! ca_userid)?; //! let ca = ca.insert_packets(ca_tsig.clone())?.0; //! # assert!(ca.clone().into_packets().any(|p| { //! # match p { //! # Packet::Signature(sig) => sig == ca_tsig, //! # _ => false, //! # } //! # })); //! //! //! // Carol now tries to authenticate Alice and Bob's certificates //! // using the CA as a trusted introducer based on `ca_tsig`. //! let res = RegexSet::from_signature(&ca_tsig)?; //! //! // Should be able to authenticate Alice. //! let alice_ua = alice.with_policy(p, None)? //! .userids().nth(0).expect("Added a User ID"); //! # assert!(res.matches_userid(alice_ua.userid())); //! let mut authenticated = false; //! for c in alice_ua.certifications() { //! if c.get_issuers().into_iter().any(|h| h.aliases(ca.key_handle())) { //! if c.clone().verify_userid_binding( //! ca.primary_key().key(), //! alice.primary_key().key(), //! alice_ua.userid()).is_ok() //! { //! authenticated |= res.matches_userid(alice_ua.userid()); //! } //! } //! } //! assert!(authenticated); //! //! // But, although the CA has certified Bob's key, Carol doesn't rely //! // on it, because Bob's email address ("bob@some.org") is out of //! // scope (some.org, not example.com). //! let bob_ua = bob.with_policy(p, None)? //! .userids().nth(0).expect("Added a User ID"); //! # assert!(! res.matches_userid(bob_ua.userid())); //! let mut have_certification = false; //! let mut authenticated = false; //! for c in bob_ua.certifications() { //! if c.get_issuers().into_iter().any(|h| h.aliases(ca.key_handle())) { //! if c.clone().verify_userid_binding( //! ca.primary_key().key(), //! bob.primary_key().key(), //! bob_ua.userid()).is_ok() //! { //! have_certification = true; //! authenticated |= res.matches_userid(bob_ua.userid()); //! } //! } //! } //! assert!(have_certification); //! assert!(! authenticated); //! # Ok(()) } //! ``` use std::borrow::Borrow; use std::fmt; use lalrpop_util::ParseError; use regex_syntax::hir::{self, Hir}; use crate::Error; use crate::Result; use crate::packet::prelude::*; use crate::types::SignatureType; pub(crate) mod lexer; lalrpop_util::lalrpop_mod!( #[allow(clippy::all)] #[allow(unused_parens)] grammar, "/regex/grammar.rs" ); pub(crate) use self::lexer::Token; pub(crate) use self::lexer::{Lexer, LexicalError}; const TRACE: bool = false; // Convert tokens into strings. // // Unfortunately, we can't implement From, because we don't define // ParseError in this crate. pub(crate) fn parse_error_downcast(e: ParseError<usize, Token, LexicalError>) -> ParseError<usize, String, LexicalError> { match e { ParseError::UnrecognizedToken { token: (start, t, end), expected, } => ParseError::UnrecognizedToken { token: (start, t.into(), end), expected, }, ParseError::ExtraToken { token: (start, t, end), } => ParseError::ExtraToken { token: (start, t.into(), end), }, ParseError::InvalidToken { location } => ParseError::InvalidToken { location }, ParseError::User { error } => ParseError::User { error }, ParseError::UnrecognizedEof { location, expected } => ParseError::UnrecognizedEof { location, expected }, } } // Used by grammar.lalrpop to generate a regex class (e.g. '[a-ce]'). fn generate_class(caret: bool, chars: impl Iterator<Item=char>) -> Hir { tracer!(TRACE, "generate_class"); // Dealing with ranges is a bit tricky. We need to examine three // tokens. If the middle one is a dash, it's a range. let chars: Vec<Option<char>> = chars // Pad it out so what we can use windows to get three // characters at a time, and be sure to process all // characters. .map(Some) .chain(std::iter::once(None)) .chain(std::iter::once(None)) .collect(); if chars.len() == 2 { // The grammar doesn't allow an empty class. unreachable!(); } else { let r = chars .windows(3) .scan(0, |skip: &mut usize, x: &[Option<char>]| // Scan stops if the result is None. // filter_map keeps only those elements that // are Some. -> Option<Option<hir::ClassUnicodeRange>> { if *skip > 0 { *skip -= 1; t!("Skipping: {:?} (skip now: {})", x, skip); Some(None) } else { match (x[0], x[1], x[2]) { (Some(a), Some('-'), Some(c)) => { // We've got a real range. *skip = 2; t!("range for '{}-{}'", a, c); Some(Some(hir::ClassUnicodeRange::new(a, c))) } (Some(a), _, _) => { t!("range for '{}'", a); Some(Some(hir::ClassUnicodeRange::new(a, a))) } (None, _, _) => unreachable!(), } } }) .flatten(); let mut class = hir::Class::Unicode(hir::ClassUnicode::new(r)); if caret { class.negate(); } Hir::class(class) } } /// A compiled OpenPGP regular expression for matching UTF-8 encoded /// strings. /// /// A `Regex` contains a regular expression compiled according to the /// rules defined in [Section 8 of RFC 9580] modulo two differences. /// First, the compiler only works on UTF-8 strings (not bytes). /// Second, ranges in character classes are between UTF-8 characters, /// not just ASCII characters. Further, by default, strings that /// don't pass a sanity check (in particular, include Unicode control /// characters) never match. This behavior can be customized using /// [`Regex::disable_sanitizations`]. /// /// [Section 8 of RFC 9580]: https://www.rfc-editor.org/rfc/rfc9580.html#section-8 /// [trust signatures]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.21 /// [`Regex::disable_sanitizations`]: Regex::disable_sanitizations() /// /// Regular expressions are used to scope the trust that [trust /// signatures] extend. /// /// When working with trust signatures, you'll usually want to use the /// [`RegexSet`] data structure, which already implements the correct /// semantics. /// /// /// See the [module-level documentation] for more details. /// /// [module-level documentation]: self /// /// # A note on equality /// /// We define equality on `Regex` as the equality of the uncompiled /// regular expression given to the constructor and whether /// sanitizations are enabled. #[derive(Clone, Debug)] pub struct Regex { /// The original regular expression. /// /// Equality is defined using this and `disable_sanitizations`. re: String, regex: regex::Regex, disable_sanitizations: bool, } assert_send_and_sync!(Regex); impl PartialEq for Regex { fn eq(&self, other: &Self) -> bool { self.re == other.re && self.disable_sanitizations == other.disable_sanitizations } } impl Eq for Regex {} impl Regex { /// Parses and compiles the regular expression. /// /// By default, strings that don't pass a sanity check (in /// particular, include Unicode control characters) never match. /// This behavior can be customized using /// [`Regex::disable_sanitizations`]. /// /// [`Regex::disable_sanitizations`]: Regex::disable_sanitizations() pub fn new(re: &str) -> Result<Self> { let lexer = Lexer::new(re); let hir = match grammar::RegexParser::new().parse(re, lexer) { Ok(hir) => hir, Err(err) => return Err(parse_error_downcast(err).into()), }; // Converting the Hir to a string and the compiling that is // apparently the canonical way to convert a Hir to a Regex // (at least it is what rip-grep does), which the author of // regex also wrote. See // ripgrep/crates/regex/src/config.rs:ConfiguredHir::regex. let regex = regex::RegexBuilder::new(&hir.to_string()) .build()?; Ok(Self { re: re.into(), regex, disable_sanitizations: false, }) } /// Parses and compiles the regular expression. /// /// Returns an error if `re` is not a valid UTF-8 string. /// /// By default, strings that don't pass a sanity check (in /// particular, include Unicode control characters) never match. /// This behavior can be customized using /// [`Regex::disable_sanitizations`]. /// /// [`Regex::disable_sanitizations`]: Regex::disable_sanitizations() pub fn from_bytes(re: &[u8]) -> Result<Self> { Self::new(std::str::from_utf8(re)?) } /// Returns the string-representation of the regular expression. pub fn as_str(&self) -> &str { &self.re } /// Controls whether matched strings must pass a sanity check. /// /// If `false` (the default), i.e., sanity checks are enabled, and /// the string doesn't pass the sanity check (in particular, it /// contains a Unicode control character according to /// [`char::is_control`], including newlines and an embedded `NUL` /// byte), this returns `false`. /// /// [`char::is_control`]: https://doc.rust-lang.org/std/primitive.char.html#method.is_control pub fn disable_sanitizations(&mut self, disabled: bool) { self.disable_sanitizations = disabled; } /// Returns whether the regular expression matches the string. /// /// If sanity checks are enabled (the default) and the string /// doesn't pass the sanity check (in particular, it contains a /// Unicode control character according to [`char::is_control`], /// including newlines and an embedded `NUL` byte), this returns /// `false`. /// /// [`char::is_control`]: https://doc.rust-lang.org/std/primitive.char.html#method.is_control pub fn is_match(&self, s: &str) -> bool { if ! self.disable_sanitizations && s.chars().any(char::is_control) { return false; } self.is_match_clean(s) } // is_match, but without the sanity check. fn is_match_clean(&self, s: &str) -> bool { self.regex.is_match(s) } /// Returns whether the regular expression matches the User ID. /// /// If the User ID is not a valid UTF-8 string, this returns /// `false`. /// /// If sanity checks are enabled (the default) and the string /// doesn't pass the sanity check (in particular, it contains a /// Unicode control character according to [`char::is_control`], /// including newlines and an embedded `NUL` byte), this returns /// `false`. /// /// [`char::is_control`]: https://doc.rust-lang.org/std/primitive.char.html#method.is_control pub fn matches_userid(&self, u: &UserID) -> bool { if let Ok(u) = std::str::from_utf8(u.value()) { self.is_match(u) } else { false } } } #[derive(Clone, Debug)] enum RegexSet_ { Regex(Regex), Invalid, Everything, } assert_send_and_sync!(RegexSet_); /// A set of regular expressions. /// /// A `RegexSet` encapsulates a set of regular expressions. The /// regular expressions are compiled according to the rules defined in /// [Section 8 of RFC 9580] modulo two differences. First, the /// compiler only works on UTF-8 strings (not bytes). Second, ranges /// in character classes are between UTF-8 characters, not just ASCII /// characters. Further, by default, strings that don't pass a sanity /// check (in particular, include Unicode control characters) never /// match. This behavior can be customized using /// [`RegexSet::disable_sanitizations`]. /// /// [Section 8 of RFC 9580]: https://www.rfc-editor.org/rfc/rfc9580.html#section-8 /// [`RegexSet::disable_sanitizations`]: RegexSet::disable_sanitizations() /// /// `RegexSet` implements the semantics of [regular expression]s used /// in [Trust Signatures]. In particular, a `RegexSet` makes it /// easier to deal with trust signatures that: /// /// - Contain multiple Regular Expression subpackts, /// - Have no Regular Expression subpackets, and/or /// - Include one or more Regular Expression subpackets that are invalid. /// /// [regular expressions]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.22 /// [Trust Signatures]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.21 /// /// `RegexSet` compiles each regular expression individually. If /// there are no regular expressions, the `RegexSet` matches /// everything. If a regular expression is invalid, `RegexSet` treats /// it as if it doesn't match anything. Thus, if all regular /// expressions are invalid, the `RegexSet` matches nothing (not /// everything!). /// /// See the [module-level documentation] for more details. /// /// [module-level documentation]: self /// /// # A note on equality /// /// We define equality on `RegexSet` as the equality of the uncompiled /// regular expressions given to the constructor and whether /// sanitizations are enabled. #[derive(Clone)] pub struct RegexSet { /// The original regular expressions. /// /// Equality is defined using this and `disable_sanitizations`. re_bytes: Vec<Vec<u8>>, re_set: RegexSet_, disable_sanitizations: bool, } assert_send_and_sync!(RegexSet); impl PartialEq for RegexSet { fn eq(&self, other: &Self) -> bool { self.re_bytes == other.re_bytes && self.disable_sanitizations == other.disable_sanitizations } } impl Eq for RegexSet {} impl fmt::Debug for RegexSet { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let mut d = f.debug_struct("RegexSet"); match self.re_set { RegexSet_::Everything => { d.field("regex", &"<Everything>") } RegexSet_::Invalid => { d.field("regex", &"<Invalid>") } RegexSet_::Regex(ref r) => { d.field("regex", &r.regex) } } .field("sanitizations", &!self.disable_sanitizations) .finish() } } impl RegexSet { /// Parses and compiles the regular expressions. /// /// Invalid regular expressions do not cause this to fail. See /// [`RegexSet`]'s top-level documentation for details. /// /// /// By default, strings that don't pass a sanity check (in /// particular, include Unicode control characters) never match. /// This behavior can be customized using /// [`RegexSet::disable_sanitizations`]. /// /// [`RegexSet::disable_sanitizations`]: RegexSet::disable_sanitizations() /// /// # Examples /// /// ``` /// use sequoia_openpgp as openpgp; /// use openpgp::regex::RegexSet; /// /// # fn main() -> openpgp::Result<()> { /// // Extract the regex and compile it. /// let res = &[ /// "<[^>]+[@.]example\\.org>$", /// // Invalid. /// "[..", /// ]; /// /// let res = RegexSet::new(res)?; /// /// assert!(res.is_match("Alice <alice@example.org>")); /// assert!(! res.is_match("Bob <bob@example.com>")); /// # Ok(()) } /// ``` pub fn new<'a, RE, I>(res: I) -> Result<Self> where RE: Borrow<&'a str>, I: IntoIterator<Item=RE>, { tracer!(TRACE, "RegexSet::new"); let mut regexes = Vec::with_capacity(2); let mut had_good = false; let mut had_bad = false; let mut re_bytes = Vec::new(); for re in res { let re = re.borrow(); re_bytes.push(re.as_bytes().into()); let lexer = Lexer::new(re); match grammar::RegexParser::new().parse(re, lexer) { Ok(hir) => { had_good = true; regexes.push(hir); } Err(err) => { had_bad = true; t!("Compiling {:?}: {}", re, err); } } } if had_bad && ! had_good { t!("All regular expressions were invalid."); Ok(RegexSet { re_bytes, re_set: RegexSet_::Invalid, disable_sanitizations: false, }) } else if ! had_bad && ! had_good { // Match everything. t!("No regular expressions provided."); Ok(RegexSet { re_bytes, re_set: RegexSet_::Everything, disable_sanitizations: false, }) } else { // Match any of the regular expressions. Ok(RegexSet { re_bytes, re_set: RegexSet_::Regex( Regex { re: String::new(), regex: regex::RegexBuilder::new( &Hir::alternation(regexes).to_string()) .build()?, disable_sanitizations: false, }), disable_sanitizations: false, }) } } /// Parses and compiles the regular expressions. /// /// The regular expressions are first converted to UTF-8 strings. /// Byte sequences that are not valid UTF-8 strings are considered /// to be invalid regular expressions. Invalid regular /// expressions do not cause this to fail. See [`RegexSet`]'s /// top-level documentation for details. /// /// /// By default, strings that don't pass a sanity check (in /// particular, include Unicode control characters) never match. /// This behavior can be customized using /// [`RegexSet::disable_sanitizations`]. /// /// [`RegexSet::disable_sanitizations`]: RegexSet::disable_sanitizations() /// /// # Examples /// /// ``` /// use sequoia_openpgp as openpgp; /// use openpgp::regex::RegexSet; /// /// # fn main() -> openpgp::Result<()> { /// // A valid and an invalid UTF-8 byte sequence. The invalid /// // sequence doesn't match anything. But, that doesn't impact /// // the other regular expressions. /// let res: &[ &[u8] ] = &[ /// &b"<[^>]+[@.]example\\.org>$"[..], /// // Invalid UTF-8. /// &b"\xC3\x28"[..], /// ]; /// assert!(std::str::from_utf8(res[0]).is_ok()); /// assert!(std::str::from_utf8(res[1]).is_err()); /// /// let re_set = RegexSet::from_bytes(res.into_iter())?; /// /// assert!(re_set.is_match("Alice <alice@example.org>")); /// assert!(! re_set.is_match("Bob <bob@example.com>")); /// /// // If we only have invalid UTF-8 strings, then nothing /// // matches. /// let res: &[ &[u8] ] = &[ /// // Invalid UTF-8. /// &b"\xC3\x28"[..], /// ]; /// assert!(std::str::from_utf8(res[0]).is_err()); /// /// let re_set = RegexSet::from_bytes(res.into_iter())?; /// /// assert!(! re_set.is_match("Alice <alice@example.org>")); /// assert!(! re_set.is_match("Bob <bob@example.com>")); /// /// /// // But, if we have no regular expressions, everything matches. /// let res: &[ &[u8] ] = &[]; /// let re_set = RegexSet::from_bytes(res.into_iter())?; /// /// assert!(re_set.is_match("Alice <alice@example.org>")); /// assert!(re_set.is_match("Bob <bob@example.com>")); /// # Ok(()) } /// ``` pub fn from_bytes<'a, I, RE>(res: I) -> Result<Self> where I: IntoIterator<Item=RE>, RE: Borrow<&'a [u8]>, { let mut have_valid_utf8 = false; let mut have_invalid_utf8 = false; let mut re_bytes = Vec::new(); let re_set = Self::new( res .into_iter() .scan((&mut have_valid_utf8, &mut have_invalid_utf8), |(valid, invalid), re| { re_bytes.push(re.borrow().to_vec()); if let Ok(re) = std::str::from_utf8(re.borrow()) { **valid = true; Some(Some(re)) } else { **invalid = true; Some(None) } }) .flatten()); if !have_valid_utf8 && have_invalid_utf8 { // None of the strings were valid UTF-8. Reject // everything. Ok(RegexSet { re_bytes, re_set: RegexSet_::Invalid, disable_sanitizations: false, }) } else { // We had nothing or at least one string was valid UTF-8. // RegexSet::new did the right thing. re_set.map(|mut r| { r.re_bytes = re_bytes; r }) } } /// Returns the bytes-representation of the regular expressions. pub fn as_bytes(&self) -> &[Vec<u8>] { &self.re_bytes } /// Creates a `RegexSet` from the regular expressions stored in a /// trust signature. /// /// This method is a convenience function, which extracts any /// regular expressions from a [Trust Signature] and wraps them in a /// `RegexSet`. /// /// [Trust Signature]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.21 /// /// If the signature is not a valid trust signature (its [type] is /// [GenericCertification], [PersonaCertification], /// [CasualCertification], or [PositiveCertification], and the /// [Trust Signature] subpacket is present), this returns an /// error. /// /// [type]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.1 /// [GenericCertification]: crate::types::SignatureType::GenericCertification /// [PersonaCertification]: crate::types::SignatureType::PersonaCertification /// [CasualCertification]: crate::types::SignatureType::CasualCertification /// [PositiveCertification]: crate::types::SignatureType::PositiveCertification /// /// By default, strings that don't pass a sanity check (in /// particular, include Unicode control characters) never match. /// This behavior can be customized using /// [`RegexSet::disable_sanitizations`]. /// /// [`RegexSet::disable_sanitizations`]: RegexSet::disable_sanitizations() /// /// # Examples /// /// ``` /// use sequoia_openpgp as openpgp; /// # use openpgp::cert::prelude::*; /// # use openpgp::packet::prelude::*; /// # use openpgp::policy::StandardPolicy; /// use openpgp::regex::RegexSet; /// # use openpgp::types::SignatureType; /// # /// # fn main() -> openpgp::Result<()> { /// # let p = &StandardPolicy::new(); /// # /// # let (alice, _) /// # = CertBuilder::general_purpose(Some("Alice <alice@example.org>")) /// # .generate()?; /// # let mut alices_signer = alice.primary_key().key().clone() /// # .parts_into_secret()?.into_keypair()?; /// # /// # let (example_com, _) /// # = CertBuilder::general_purpose(Some("OpenPGP CA <openpgp-ca@example.com>")) /// # .generate()?; /// # let example_com_userid = example_com.with_policy(p, None)? /// # .userids().nth(0).expect("Added a User ID").userid(); /// # /// # let certification = SignatureBuilder::new(SignatureType::GenericCertification) /// # .set_trust_signature(1, 120)? /// # .set_regular_expression("<[^>]+[@.]example\\.org>$")? /// # .add_regular_expression("<[^>]+[@.]example\\.com>$")? /// # .sign_userid_binding( /// # &mut alices_signer, /// # example_com.primary_key().component(), /// # example_com_userid)?; /// /// // certification is a trust signature, which contains two regular /// // expressions: one that matches all mail addresses for 'example.org' /// // and another that matches all mail addresses for 'example.com'. /// let certification: &Signature = // ...; /// # &certification; /// /// // Extract the regex and compile it. /// let res = RegexSet::from_signature(certification)?; /// /// // Some positive examples. /// assert!(res.is_match("Alice <alice@example.org>")); /// assert!(res.is_match("Bob <bob@example.com>")); /// /// // Wrong domain. /// assert!(! res.is_match("Carol <carol@acme.com>")); /// /// // The standard regex, "<[^>]+[@.]example\\.org>$" only matches /// // email addresses wrapped in <>. /// assert!(! res.is_match("dave@example.com")); /// /// // And, it is case-sensitive. /// assert!(res.is_match("Ellen <ellen@example.com>")); /// assert!(! res.is_match("Ellen <ellen@EXAMPLE.COM>")); /// # Ok(()) } /// ``` pub fn from_signature(sig: &Signature) -> Result<Self> { use SignatureType::*; match sig.typ() { GenericCertification => (), PersonaCertification => (), CasualCertification => (), PositiveCertification => (), t => return Err( Error::InvalidArgument( format!( "Expected a certification signature, found a {}", t)) .into()), } if sig.trust_signature().is_none() { return Err( Error::InvalidArgument( "Expected a trust signature, \ but the signature does not include \ a valid Trust Signature subpacket".into()) .into()); } Self::from_bytes(sig.regular_expressions()) } /// Returns a `RegexSet` that matches everything. /// /// Note: sanitizations are still enabled. So, to really match /// everything, you still need to call /// [`RegexSet::disable_sanitizations`]. /// /// [`RegexSet::disable_sanitizations`]: RegexSet::disable_sanitizations() /// /// This can be used to optimize the evaluation of scoping rules /// along a path: if a `RegexSet` matches everything, then it /// doesn't further constrain the path. pub fn everything() -> Self { Self { re_bytes: vec![vec![]], re_set: RegexSet_::Everything, disable_sanitizations: false, } } /// Returns whether a `RegexSet` matches everything. /// /// Normally, this only returns true if the `RegexSet` was created /// using [`RegexSet::everything`]. [`RegexSet::new`], /// [`RegexSet::from_bytes`], [`RegexSet::from_signature`] do /// detect some regular expressions that match everything (e.g., /// if no regular expressions are supplied). But, they do not /// guarantee that a `RegexSet` containing a regular expression /// like `.?`, which does in fact match everything, is detected as /// matching everything. /// /// [`RegexSet::everything`]: RegexSet::everything() /// [`RegexSet::new`]: RegexSet::everything() /// [`RegexSet::from_bytes`]: RegexSet::from_bytes() /// [`RegexSet::from_signature`]: RegexSet::from_signature() /// /// # Examples /// /// ``` /// use sequoia_openpgp as openpgp; /// use openpgp::regex::RegexSet; /// /// # fn main() -> openpgp::Result<()> { /// assert!(RegexSet::everything().matches_everything()); /// let empty: &[ &str ] = &[]; /// assert!(RegexSet::new(empty)?.matches_everything()); /// /// // A regular expression that matches everything. But /// // `RegexSet` returns false, because it can't detect it. /// let res: &[ &str ] = &[ /// &".?"[..], /// ]; /// let re_set = RegexSet::new(res.into_iter())?; /// assert!(! re_set.matches_everything()); /// # Ok(()) } /// ``` pub fn matches_everything(&self) -> bool { matches!(self.re_set, RegexSet_::Everything) } /// Controls whether strings with control characters are allowed. /// /// If `false` (the default), i.e., sanity checks are enabled, and /// the string doesn't pass the sanity check (in particular, it /// contains a Unicode control character according to /// [`char::is_control`], including newlines and an embedded `NUL` /// byte), this returns `false`. /// /// [`char::is_control`]: https://doc.rust-lang.org/std/primitive.char.html#method.is_control pub fn disable_sanitizations(&mut self, allowed: bool) { self.disable_sanitizations = allowed; if let RegexSet_::Regex(ref mut re) = self.re_set { re.disable_sanitizations(allowed); } } /// Returns whether the regular expression set matches the string. /// /// If sanity checks are enabled (the default) and the string /// doesn't pass the sanity check (in particular, it contains a /// Unicode control character according to [`char::is_control`], /// including newlines and an embedded `NUL` byte), this returns /// `false`. /// /// [`char::is_control`]: https://doc.rust-lang.org/std/primitive.char.html#method.is_control /// /// If the `RegexSet` contains one or more regular expressions, /// this method returns whether at least one of the regular /// expressions matches. Invalid regular expressions never match. /// /// If the `RegexSet` does not contain any regular expressions /// (valid or otherwise), this method returns `true`. /// /// # Examples /// /// ``` /// use sequoia_openpgp as openpgp; /// use openpgp::regex::RegexSet; /// /// # fn main() -> openpgp::Result<()> { /// // A regular expression that matches anything. (Note: this is /// // equivalent to providing no regular expressions.) /// let res: &[ &str ] = &[ /// &""[..], /// ]; /// let re_set = RegexSet::new(res.into_iter())?; /// /// assert!(re_set.is_match("Alice Lovelace <alice@example.org>")); /// /// // If a User ID has an embedded control character, it doesn't /// // match. /// assert!(! re_set.is_match("Alice <alice@example.org>\0")); /// # Ok(()) } /// ``` pub fn is_match(&self, s: &str) -> bool { if ! self.disable_sanitizations && s.chars().any(char::is_control) { return false; } match self.re_set { RegexSet_::Regex(ref re) => re.is_match_clean(s), RegexSet_::Invalid => false, RegexSet_::Everything => true, } } /// Returns whether the regular expression matches the User ID. /// /// If the User ID is not a valid UTF-8 string, this returns `false`. /// /// If sanity checks are enabled (the default) and the string /// doesn't pass the sanity check (in particular, it contains a /// Unicode control character according to [`char::is_control`], /// including newlines and an embedded `NUL` byte), this returns /// `false`. /// /// [`char::is_control`]: https://doc.rust-lang.org/std/primitive.char.html#method.is_control /// /// If the `RegexSet` contains one or more regular expressions, /// this method returns whether at least one of the regular /// expressions matches. Invalid regular expressions never match. /// /// If the `RegexSet` does not contain any regular expressions /// (valid or otherwise), this method returns `true`. /// /// # Examples /// /// ``` /// use sequoia_openpgp as openpgp; /// use openpgp::packet::UserID; /// use openpgp::regex::RegexSet; /// /// # fn main() -> openpgp::Result<()> { /// // A regular expression that matches anything. (Note: this is /// // equivalent to providing no regular expressions.) /// let res: &[ &str ] = &[ /// "", /// ]; /// let re_set = RegexSet::new(res.into_iter())?; /// /// assert!(re_set.matches_userid( /// &UserID::from(&b"Alice Lovelace <alice@example.org>"[..]))); /// /// // If a User ID is not valid UTF-8, it never matches. /// assert!(! re_set.matches_userid( /// &UserID::from(&b"Alice \xC3\x28 Lovelace <alice@example.org>"[..]))); /// /// // If a User ID has an embedded control character, it doesn't /// // match. /// assert!(! re_set.matches_userid( /// &UserID::from(&b"Alice <alice@example.org>\0"[..]))); /// # Ok(()) } /// ``` pub fn matches_userid(&self, u: &UserID) -> bool { if let Ok(u) = std::str::from_utf8(u.value()) { self.is_match(u) } else { false } } } #[cfg(test)] mod tests { use super::*; #[test] fn regex() -> Result<()> { fn a(regex: &str, matches: &[(bool, &str)]) { eprint!("{} -> ", regex); let mut compiled = Regex::new(regex).unwrap(); compiled.disable_sanitizations(true); eprintln!("{:?}", compiled); for &(matches, text) in matches { assert_eq!(matches, compiled.is_match(text), "regex: {}\n text: {:?} should{} match", regex, text, if matches { "" } else { " not" }); } } fn f(regex: &str) { eprint!("{} -> ", regex); let compiled = Regex::new(regex); assert!(compiled.is_err()); eprintln!("failed (expected)"); } // Test an important corner case: the + should only apply to // the b! See: https://github.com/rust-lang/regex/issues/731 a("xab+y", &[ (true, "xaby"), (true, "xabby"), (false, "xababy"), ]); a("x(ab+)y", &[ (false, "xy"), (false, "xay"), (true, "xaby"), (true, "xabby"), (true, "xabbby"), (false, "xababy"), ]); // But here the + matches "ab", not just the "b". a("x(ab)+y", &[ (false, "xy"), (true, "xaby"), (false, "xabby"), (true, "xababy"), (true, "xabababy"), (false, "x(ab)y"), ]); a("", &[ (true, "s"), (true, "ss"), ]); a("s", &[ (true, "s"), (true, "ss"), (false, "a"), (true, "hello, my prettiessss"), (false, "S"), ]); a("ss", &[ (false, "s"), (true, "ss"), (true, "sss"), (false, "this has lots of ses, but not two ses together"), (true, "halloss"), ]); a("a|b", &[ (true, "a"), (true, "b"), (false, "c"), (true, "xxxaxxxbxxx"), ]); a("a|b|c", &[ (true, "a"), (true, "b"), (true, "c"), (false, "d"), (true, "xxxaxxxbxxx"), ]); // This should match anything. a("|a", &[ (true, "a"), (true, "b"), ]); a("a|", &[ (true, "a"), (true, "b"), ]); a("|a|b", &[ (true, "a"), (true, "b"), (true, "c"), ]); a("|a|b|c|d", &[ (true, "a"), (true, "b"), (true, "c"), (true, "d"), (true, "eeee"), ]); a("a|b|", &[ (true, "a"), (true, "b"), (true, "c"), ]); a("a|b|c|", &[ (true, "a"), (true, "b"), (true, "c"), (true, "d"), (true, "eeee"), ]); a("|", &[ (true, "a"), (true, "b"), (true, "c"), (true, "d"), (true, "eeee"), ]); a("|a|", &[ (true, "a"), (true, "b"), (true, "c"), (true, "d"), (true, "eeee"), ]); a("|a|b|", &[ (true, "a"), (true, "b"), (true, "c"), (true, "d"), (true, "eeee"), ]); // A nested empty. a("(a|)|b", &[ (true, "a"), (true, "b"), ]); // empty+ a("(a|b|()+)", &[ (true, "a"), (true, "b"), ]); // (empty)+ a("(a|b|(())+)", &[ (true, "a"), (true, "b"), ]); // Multiple empty branches. a("(a|b|(()())())", &[ (true, "a"), (true, "b"), ]); a("(a|b|(()())())|", &[ (true, "a"), (true, "b"), ]); // This is: "ab" or "cd", not a followed by b or c followed by d: // // A regular expression is zero or more branches, separated by '|'. // ... // A branch is zero or more pieces, concatenated. // ... // A piece is an atom // ... // An atom is... a single character. a("ab|cd", &[ (true, "abd"), (true, "acd"), (true, "abcd"), (false, "ad"), (false, "b"), (false, "c"), (false, "bb"), ]); a("a*", &[ (true, ""), (true, "a"), (true, "aa"), (true, "b"), ]); a("xa*y", &[ (true, "xy"), (true, "xay"), (true, "xaay"), (false, "y"), (false, "ay"), (false, "aay"), (false, "x y"), (false, "x ay"), (false, "x aay"), ]); f("*"); a("a+", &[ (false, ""), (true, "a"), (true, "aa"), (false, "b"), (true, "baab"), (true, "by ab"), (true, "baa b"), ]); a("ab+", &[ (false, ""), (false, "a"), (false, "b"), (true, "ab"), (false, "bb"), (true, "baab"), (true, "by ab"), (false, "baa b"), ]); f("+"); a("a?", &[ (true, ""), (true, "a"), (true, "aa"), (true, "aaa"), (true, "b"), (true, "baab"), (true, "by ab"), (true, "baa b"), ]); a("xa?y", &[ (false, ""), (true, "xy"), (false, "a"), (true, "xay"), (false, "aa"), (false, "xaay"), (false, "b"), (false, "bxaayb"), (true, "by xayb"), (true, "baxay b"), ]); f("?"); f("a*?"); a("a*b?c+", &[ (false, ""), (true, "c"), (true, "abc"), (true, "aabbcc"), (false, "aab"), (true, "aaaaaabcccccccc"), ]); f("a?*+"); a("a?|b+", &[ (true, ""), (true, "aaa"), (true, "bbb"), (true, "abaa"), ]); a("a+|b+", &[ (false, ""), (true, "a"), (true, "aaa"), (true, "b"), (true, "bbb"), (true, "abaa"), ]); a("a+|b+|c+", &[ (false, ""), (true, "a"), (true, "aaa"), (true, "b"), (true, "bbb"), (true, "abaa"), (true, "c"), (true, "ccc"), (true, "abaaccc"), ]); a("xa+|b+|c+y", &[ (false, ""), (true, "xa"), (true, "xaa"), (true, "b"), (true, "bb"), (true, "cy"), (true, "ccy"), (false, "a"), (false, "aaa"), (false, "c"), (false, "ccc"), ]); a("xa+y|sb+u", &[ (false, ""), (true, "xay"), (true, "xaay"), (true, "sbu"), (true, "sbbu"), (true, "xysbu"), (false, "a"), (false, "aaa"), (false, "xyu"), (false, "ccc"), ]); a("a*|a+|ab+cd+|", &[ (true, ""), ]); a("()", &[ (true, ""), (true, "xyzzy"), ]); a("(())", &[ (true, ""), (true, "xyzzy"), ]); a("((()))", &[ (true, ""), (true, "xyzzy"), ]); f("((())"); f("((())))"); a("(a)", &[ (true, "a"), (true, "(a)"), (false, "b"), ]); a("x(a)y", &[ (false, "xy"), (true, "xay"), (false, "x(a)y"), (true, "(xay)"), (false, "a"), (false, "yax"), ]); a("x(ab)y", &[ (false, "xy"), (false, "xay"), (false, "xby"), (true, "xaby"), (false, "x(ab)y"), (true, "(xaby)"), ]); a("x(ab)(cd)y", &[ (true, "xabcdy"), (true, "zxabcdyz"), ]); a("a(bc)d(ef)g", &[ (true, "abcdefg"), (true, "xabcdefgy"), (false, "xa(bc)d(ef)gy"), ]); a("a((bc))d((ef))g", &[ (true, "abcdefg"), (true, "xabcdefgy"), (false, "xa(bc)d(ef)gy"), ]); a("a(b(c)d)e", &[ (true, "abcde"), (true, "xabcdey"), (false, "xa(b(c)d)ey"), ]); a("x(a|b)y", &[ (false, "xy"), (true, "xay"), (true, "xby"), (false, "xaay"), (false, "xbby"), (false, "xaby"), (false, "xaaby"), (false, "xabby"), (false, "xaabby"), (false, "xcy"), ]); a("x(a|bc)y", &[ (false, "xy"), (true, "xay"), (false, "xby"), (true, "xbcy"), (false, "xaay"), (false, "xbby"), (false, "xaby"), (false, "xabcy"), (false, "xabby"), (false, "xaabby"), (false, "xcy"), (false, "xacy"), ]); a("x(a|b|c)y", &[ (false, "xy"), (true, "xay"), (true, "xby"), (true, "xcy"), (false, "xaay"), (false, "xbby"), (false, "xaby"), (false, "xabcy"), (false, "xabby"), (false, "xaabby"), (false, "xacy"), ]); a("x(a|b)(c|d)y", &[ (false, "xy"), (false, "xay"), (false, "xby"), (false, "xcy"), (false, "xdy"), (false, "xaay"), (false, "xbby"), (false, "xccy"), (false, "xddy"), (false, "xaby"), (false, "xcdy"), (true, "xacy"), (true, "xady"), (true, "xbcy"), (true, "xbdy"), (false, "xabcy"), (false, "xabby"), (false, "xaabby"), ]); a("x(a+|b+)y", &[ (false, "xy"), (true, "xay"), (true, "xby"), (true, "xaay"), (true, "xbby"), (false, "xaby"), (false, "xaaby"), (false, "xabby"), (false, "xaabby"), (false, "xcy"), ]); a(".", &[ (false, ""), (true, "a"), (true, "ab"), (true, "ab\nc"), (true, "ab.c"), ]); a("x.y", &[ (false, ""), (false, "xy"), (true, "xay"), (true, "x\ny"), (true, "x.y"), (false, "x..y"), ]); a("^", &[ (true, ""), (true, "xx"), ]); a("^abc", &[ (false, ""), (true, "abcdef"), (false, "xabcdef"), (false, "\nabcdef"), ]); a("(^abc|^def)", &[ (false, ""), (true, "abcd"), (true, "defg"), (false, "xabcd"), (false, "xdefg"), (false, "^abc"), (false, "^(abc|def)"), (false, "\nabcdef"), ]); a("(^abc|def)", &[ (false, ""), (true, "abcd"), (true, "defg"), (false, "xabcd"), (true, "xdefg"), (false, "^abc"), (true, "^(abc|def)"), (false, "\nabcde"), ]); a("^^", &[ (true, ""), (true, "abcdef"), ]); a("^abc^", &[ (false, ""), (false, "abcdef"), (false, "xabcdef"), (false, "abc\n"), (false, "\nabc\n"), (false, "^abc^"), ]); a("$", &[ (true, ""), (true, "abc"), ]); a("abc$", &[ (false, ""), (true, "abc"), (false, "abcx"), (false, "abc\n"), (false, "abc$"), ]); a("abc$$", &[ (false, ""), (true, "abc"), (false, "abcx"), (false, "abc\n"), (false, "abc$"), ]); a("(abc$)x", &[ (false, ""), (false, "abc"), (false, "abcx"), (false, "abc\nx"), (false, "abc$x"), ]); a("abc$|def$", &[ (false, ""), (true, "abc"), (false, "abcx"), (false, "abc\n"), (false, "abc$"), (true, "def"), (false, "defx"), (false, "def\n"), (false, "def$"), (true, "abcdef"), ]); a("\\|", &[ (true, "|"), (false, ""), (false, "a"), ]); a("\\*", &[ (true, "*"), (false, ""), (false, "a"), ]); a("\\+", &[ (true, "+"), (false, ""), (false, "a"), ]); a("\\?", &[ (true, "?"), (false, ""), (false, "a"), ]); a("\\.", &[ (true, "."), (false, ""), (false, "a"), ]); a("\\^", &[ (true, "^"), (false, ""), (false, "a"), ]); a("\\$", &[ (true, "$"), (false, ""), (false, "a"), ]); a("\\\\", &[ (true, "\\"), (false, ""), (false, "a"), ]); a("\\[", &[ (true, "["), (false, ""), (false, "a"), ]); a("\\]", &[ (true, "]"), (false, ""), (false, "a"), ]); a("\\-", &[ (true, "-"), (false, ""), (false, "a"), ]); f("\\"); a("[a]", &[ (true, "a"), (false, "b"), ]); a("[abc]", &[ (true, "a"), (true, "b"), (true, "c"), (false, "d"), ]); a("[a-c]", &[ (true, "a"), (true, "b"), (true, "c"), (false, "d"), ]); a("[xa-c]", &[ (true, "a"), (true, "b"), (true, "c"), (true, "x"), (false, "d"), ]); a("[a-cxyz]", &[ (true, "a"), (true, "b"), (true, "c"), (true, "x"), (false, "d"), ]); a("[a-c]x", &[ (false, "a"), (false, "b"), (false, "c"), (false, "x"), (true, "ax"), (true, "bx"), (true, "cx"), (false, "d"), (false, "dx"), ]); a("[a-cxy]", &[ (true, "a"), (true, "b"), (true, "c"), (true, "x"), (true, "y"), (false, "d"), ]); a("[a-c]xy", &[ (false, "a"), (false, "b"), (false, "c"), (false, "x"), (false, "ax"), (false, "bx"), (false, "cx"), (true, "axy"), (true, "bxy"), (true, "cxy"), (false, "d"), ]); a("[a-cxyz]", &[ (true, "a"), (true, "b"), (true, "c"), (true, "x"), (true, "y"), (true, "z"), (false, "d"), ]); a("[a-c]xyz", &[ (false, "a"), (false, "b"), (false, "c"), (false, "x"), (false, "ax"), (false, "bx"), (false, "cx"), (false, "axy"), (false, "bxy"), (false, "cxy"), (true, "axyz"), (true, "bxyz"), (true, "cxyz"), (false, "d"), ]); a("xyz[a-c]", &[ (false, "a"), (false, "b"), (false, "c"), (false, "x"), (false, "xa"), (false, "xb"), (false, "xc"), (false, "xya"), (false, "xyb"), (false, "xyc"), (true, "xyza"), (true, "xyzb"), (true, "xyzc"), (false, "d"), ]); a("[xyza-c]", &[ (true, "a"), (true, "b"), (true, "c"), (true, "x"), (true, "y"), (true, "z"), (false, "d"), ]); a("[xya-cyz]", &[ (true, "a"), (true, "b"), (true, "c"), (true, "x"), (true, "y"), (true, "z"), (false, "d"), ]); a("[x-za-c]", &[ (true, "a"), (true, "b"), (true, "c"), (true, "x"), (true, "y"), (true, "z"), (false, "d"), ]); a("[x-zmna-c]", &[ (true, "a"), (true, "b"), (true, "c"), (true, "x"), (true, "y"), (true, "z"), (true, "m"), (true, "n"), (false, "d"), ]); a("[-]", &[ (true, "-"), (false, "d"), ]); a("[a-]", &[ (true, "-"), (true, "a"), (false, "d"), ]); a("[-b]", &[ (true, "-"), (true, "b"), (false, "d"), ]); a("[-bd-g]", &[ (false, "a"), (true, "-"), (true, "b"), (true, "d"), (true, "f"), ]); a("[bd-g-]", &[ (false, "a"), (true, "-"), (true, "b"), (true, "d"), (true, "f"), ]); // Backwards ranges. a("[9-0]", &[ (false, "a"), (false, "-"), (true, "9"), (true, "0"), (true, "5"), ]); a("[^a]", &[ (false, "a"), (true, "b"), ]); a("[^abc]", &[ (false, "a"), (false, "b"), (false, "c"), (true, "d"), ]); a("[^a-c]", &[ (false, "a"), (false, "b"), (false, "c"), (true, "d"), ]); a("[^xa-c]", &[ (false, "a"), (false, "b"), (false, "c"), (false, "x"), (true, "d"), ]); a("[^a-cxyz]", &[ (false, "a"), (false, "b"), (false, "c"), (false, "x"), (true, "d"), ]); a("[^a-c]x", &[ (false, "a"), (false, "b"), (false, "c"), (false, "x"), (false, "ax"), (false, "bx"), (false, "cx"), (false, "d"), (true, "dx"), ]); a("[^a-cxy]", &[ (false, "a"), (false, "b"), (false, "c"), (false, "x"), (false, "y"), (true, "d"), ]); a("[^a-c]xy", &[ (false, "a"), (false, "b"), (false, "c"), (false, "x"), (false, "ax"), (false, "bx"), (false, "cx"), (false, "axy"), (false, "bxy"), (false, "cxy"), (true, "dxy"), (false, "d"), ]); a("[^a-cxyz]", &[ (false, "a"), (false, "b"), (false, "c"), (false, "x"), (false, "y"), (false, "z"), (true, "d"), ]); a("[^a-c]xyz", &[ (false, "a"), (false, "b"), (false, "c"), (false, "x"), (false, "ax"), (false, "bx"), (false, "cx"), (false, "axy"), (false, "bxy"), (false, "cxy"), (false, "axyz"), (false, "bxyz"), (false, "cxyz"), (true, "dxyz"), (false, "d"), ]); a("xyz[^a-c]", &[ (false, "a"), (false, "b"), (false, "c"), (false, "x"), (false, "xa"), (false, "xb"), (false, "xc"), (false, "xya"), (false, "xyb"), (false, "xyc"), (false, "xyza"), (false, "xyzb"), (false, "xyzc"), (true, "xyzd"), (false, "d"), ]); a("[^xyza-c]", &[ (false, "a"), (false, "b"), (false, "c"), (false, "x"), (false, "y"), (false, "z"), (true, "d"), ]); a("[^xya-cyz]", &[ (false, "a"), (false, "b"), (false, "c"), (false, "x"), (false, "y"), (false, "z"), (true, "d"), ]); a("[^x-za-c]", &[ (false, "a"), (false, "b"), (false, "c"), (false, "x"), (false, "y"), (false, "z"), (true, "d"), ]); a("[^x-zmna-c]", &[ (false, "a"), (false, "b"), (false, "c"), (false, "x"), (false, "y"), (false, "z"), (false, "m"), (false, "n"), (true, "d"), ]); a("[^-]", &[ (false, "-"), (true, "d"), ]); a("[^a-]", &[ (false, "-"), (false, "a"), (true, "d"), ]); a("[^-b]", &[ (false, "-"), (false, "b"), (true, "d"), ]); a("[^-bd-g]", &[ (true, "a"), (false, "-"), (false, "b"), (false, "d"), (false, "f"), ]); a("[^bd-g-]", &[ (true, "a"), (false, "-"), (false, "b"), (false, "d"), (false, "f"), ]); a("[a|b]", &[ (true, "a"), (true, "|"), (false, "c"), ]); a("[a\\|b]", &[ (true, "a"), (true, "|"), (true, "\\"), (false, "c"), ]); a("[a(b]", &[ (true, "a"), (true, "("), (false, "c"), ]); a("[a)b]", &[ (true, "a"), (true, ")"), (false, "c"), ]); a("[a^b]", &[ (true, "a"), (true, "^"), (false, "c"), ]); f("[]"); f("[^]"); a("[^]]", &[ (true, "a"), (false, "]"), (true, "^"), ]); a("[]]", &[ (false, "a"), (true, "]"), ]); // Matches [ or ]. a("[][]", &[ (false, "a"), (true, "["), (true, "]"), ]); // Matches anything but [ or ]. a("[^][]", &[ (true, "a"), (false, "["), (false, "]"), ]); // Anything but ^. a("[^^]", &[ (true, "a"), (false, "^"), (true, "c"), ]); // Make sure - is recognized as an atom when it is not part of // a range. That is: a-z matches a or - or z, but it doesn't // match b (it's not a range). a("a-z", &[ (true, "a-z"), (false, "a"), (false, "-"), (false, "z"), (false, "c"), ]); a("a|-|z", &[ (true, "a"), (true, "-"), (true, "z"), (false, "c"), ]); Ok(()) } #[test] fn regex_set() -> Result<()> { let re = RegexSet::new(&[ "ab", "cd" ])?; assert!(re.is_match("ab")); assert!(re.is_match("cdef")); assert!(!re.is_match("xxx")); // Try to make sure one re does not leak into another. let re = RegexSet::new(&[ "cd$", "^ab" ])?; assert!(re.is_match("abxx")); assert!(! re.is_match("xabxx")); assert!(re.is_match("xxcd")); assert!(! re.is_match("xxcdx")); assert!(re.is_match("abcdx")); // Invalid regular expressions should be ignored. let re = RegexSet::new(&[ "[ab", "cd]", "x" ])?; assert!(!re.is_match("a")); assert!(!re.is_match("ab")); assert!(!re.is_match("[ab")); assert!(!re.is_match("c")); assert!(!re.is_match("cd")); assert!(!re.is_match("cd]")); assert!(re.is_match("x")); // If all regular expressions are invalid, nothing should // match. let re = RegexSet::new(&[ "[ab", "cd]" ])?; assert!(!re.is_match("a")); assert!(!re.is_match("ab")); assert!(!re.is_match("[ab")); assert!(!re.is_match("c")); assert!(!re.is_match("cd")); assert!(!re.is_match("cd]")); assert!(!re.is_match("x")); // If there are no regular expressions, everything should // match. let s: [&str; 0] = []; let re = RegexSet::new(&s)?; assert!(re.is_match("a")); assert!(re.is_match("ab")); assert!(re.is_match("[ab")); assert!(re.is_match("c")); assert!(re.is_match("cd")); assert!(re.is_match("cd]")); assert!(re.is_match("x")); // The empty branch of the alternation should match everything. let re = RegexSet::new(&[ "ab|", "cd" ])?; assert!(re.is_match("a")); assert!(re.is_match("b")); assert!(re.is_match("x")); assert!(re.is_match("xyx")); assert!(re.is_match("")); Ok(()) } #[test] fn regex_set_sequoia() -> Result<()> { let re = RegexSet::new(&["<[^>]+[@.]sequoia-pgp\\.org>$"])?; dbg!(&re); assert!(re.is_match("<justus@sequoia-pgp.org>")); assert!(!re.is_match("<justus@gnupg.org>")); Ok(()) } #[test] fn regex_set_sequoia_nodash() -> Result<()> { let re = RegexSet::new(&["<[^>]+[@.]sequoiapgp\\.org>$"])?; dbg!(&re); assert!(re.is_match("<justus@sequoiapgp.org>")); assert!(!re.is_match("<justus@gnupg.org>")); Ok(()) } } ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/src/seal.rs�������������������������������������������������������������������0000644�0000000�0000000�00000003700�10461020230�0015020�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������//! [Sealing Traits] //! //! [Sealing Traits]: https://rust-lang.github.io/api-guidelines/future-proofing.html#sealed-traits-protect-against-downstream-implementations-c-sealed //! //! Prevent the implementation of traits outside the crate //! to allow extension of the traits at a later time. //! //! Mark a trait as sealed by deriving it from seal::Sealed. //! //! Only Implementations of seal::Sealed will be able to implement the trait. //! Since seal::Sealed is only visible inside the crate //! sealed traits can only be implemented in the crate. /// This trait is used to [seal] other traits so they cannot /// be implemented for types outside this crate. /// Therefore they can be extended in a non-breaking way. /// /// [seal]: https://rust-lang.github.io/api-guidelines/future-proofing.html#sealed-traits-protect-against-downstream-implementations-c-sealed /// /// # Examples /// /// For example the [`cert::Preferences`] trait is sealed. /// Therefore attempts to implement it will not compile: /// /// [`cert::Preferences`]: crate::cert::Preferences /// /// ```compile_fail /// # extern crate sequoia_openpgp as openpgp; /// use openpgp::cert::prelude::*; /// use openpgp::cert::Preferences; /// use openpgp::types::*; /// /// pub struct InvalidComponentAmalgamation {} /// impl<'a> Preferences<'a> for InvalidComponentAmalgamation { //~ ERROR `_x @` is not allowed in a tuple /// fn preferred_symmetric_algorithms(&self) /// -> Option<&'a [SymmetricAlgorithm]> { None } /// fn preferred_hash_algorithms(&self) -> Option<&'a [HashAlgorithm]> { None } /// fn preferred_compression_algorithms(&self) /// -> Option<&'a [CompressionAlgorithm]> { None } /// fn preferred_aead_algorithms(&self) -> Option<&'a [AEADAlgorithm]> { None } /// fn key_server_preferences(&self) -> Option<KeyServerPreferences> { None } /// fn preferred_key_server(&self) -> Option<&'a [u8]> { None } /// fn features(&self) -> Option<Features> { None } /// } /// ``` pub trait Sealed {} ����������������������������������������������������������������sequoia-openpgp-2.0.0/src/serialize/cert.rs���������������������������������������������������������0000644�0000000�0000000�00000116445�10461020230�0017033�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������use std::{ borrow::Cow, }; use crate::Result; use crate::cert::prelude::*; use crate::packet::{ header::BodyLength, key, Key, Signature, Tag, }; use crate::seal; use crate::serialize::{ PacketRef, Marshal, MarshalInto, NetLength, generic_serialize_into, generic_export_into, }; impl Cert { /// Returns whether the certificate should be exported. /// /// A certificate should only be exported if it has at least one /// exportable direct key signature, or there is at least one user /// ID with at least one exportable self signature. /// /// # Examples /// /// ``` /// use sequoia_openpgp as openpgp; /// use openpgp::cert::prelude::*; /// /// # fn main() -> openpgp::Result<()> { /// // By default, certificates are exportable. /// let (cert, _) = /// CertBuilder::general_purpose(Some("alice@example.org")) /// .generate()?; /// assert!(cert.exportable()); /// /// // Setting the exportable flag to false makes them /// // not-exportable. /// let (cert, _) = /// CertBuilder::general_purpose(Some("alice@example.org")) /// .set_exportable(false) /// .generate()?; /// assert!(! cert.exportable()); /// # Ok(()) /// # } /// ``` pub fn exportable(&self) -> bool { let pk = self.primary_key(); if pk.self_signatures().chain(pk.self_revocations()) .any(|sig| sig.exportable().is_ok()) { // Exportable direct key signature. Export it. true } else if self.userids().any(|userid| { userid.self_signatures() .chain(userid.self_revocations()) .any(|sig| sig.exportable().is_ok()) }) { // User ID with exportable self signature. Export it. true } else if self.user_attributes().any(|ua| { ua.self_signatures() .chain(ua.self_revocations()) .any(|sig| sig.exportable().is_ok()) }) { // User attribute with exportable self signature. Export // it. true } else { // Don't export it. false } } /// Serializes or exports the Cert. /// /// If `export` is true, then non-exportable signatures are not /// written, and components without any exportable binding /// signature or revocation are not exported. /// /// The signatures are ordered from authenticated and most /// important to not authenticated and most likely to be abused. /// The order is: /// /// - Self revocations first. They are authenticated and the /// most important information. /// - Self signatures. They are authenticated. /// - Other signatures. They are not authenticated at this point. /// - Other revocations. They are not authenticated, and likely /// not well-supported in other implementations, hence the /// least reliable way of revoking keys and therefore least /// useful and most likely to be abused. fn serialize_common(&self, o: &mut dyn std::io::Write, export: bool) -> Result<()> { if export && ! self.exportable() { return Ok(()) } let primary = self.primary_key(); PacketRef::PublicKey(primary.key()) .serialize(o)?; // Writes a signature if it is exportable or `! export`. let serialize_sig = |o: &mut dyn std::io::Write, sig: &Signature| -> Result<()> { if export { if sig.exportable().is_ok() { PacketRef::Signature(sig).export(o)?; } } else { PacketRef::Signature(sig).serialize(o)?; } Ok(()) }; for s in primary.signatures() { serialize_sig(o, s)?; } for u in self.userids() { if export && ! u.self_signatures().chain(u.self_revocations()).any( |s| s.exportable().is_ok()) { // No exportable selfsig on this component, skip it. continue; } PacketRef::UserID(u.userid()).serialize(o)?; for s in u.signatures() { serialize_sig(o, s)?; } } for u in self.user_attributes() { if export && ! u.self_signatures().chain(u.self_revocations()).any( |s| s.exportable().is_ok()) { // No exportable selfsig on this component, skip it. continue; } PacketRef::UserAttribute(u.user_attribute()).serialize(o)?; for s in u.signatures() { serialize_sig(o, s)?; } } for k in self.keys().subkeys() { if export && ! k.self_signatures().chain(k.self_revocations()).any( |s| s.exportable().is_ok()) { // No exportable selfsig on this component, skip it. continue; } PacketRef::PublicSubkey(k.key()).serialize(o)?; for s in k.signatures() { serialize_sig(o, s)?; } } for u in self.unknowns() { if export && ! u.certifications().any( |s| s.exportable().is_ok()) { // No exportable selfsig on this component, skip it. continue; } PacketRef::Unknown(u.unknown()).serialize(o)?; for s in u.signatures() { serialize_sig(o, s)?; } } for s in self.bad_signatures() { serialize_sig(o, s)?; } Ok(()) } } impl crate::serialize::Serialize for Cert {} impl seal::Sealed for Cert {} impl Marshal for Cert { fn serialize(&self, o: &mut dyn std::io::Write) -> Result<()> { self.serialize_common(o, false) } fn export(&self, o: &mut dyn std::io::Write) -> Result<()> { self.serialize_common(o, true) } } impl crate::serialize::SerializeInto for Cert {} impl MarshalInto for Cert { fn serialized_len(&self) -> usize { let mut l = 0; let primary = self.primary_key(); l += PacketRef::PublicKey(primary.key()).serialized_len(); for s in primary.signatures() { l += PacketRef::Signature(s).serialized_len(); } for u in self.userids() { l += PacketRef::UserID(u.userid()).serialized_len(); for s in u.signatures() { l += PacketRef::Signature(s).serialized_len(); } } for u in self.user_attributes() { l += PacketRef::UserAttribute(u.user_attribute()).serialized_len(); for s in u.signatures() { l += PacketRef::Signature(s).serialized_len(); } } for k in self.keys().subkeys() { l += PacketRef::PublicSubkey(k.key()).serialized_len(); for s in k.signatures() { l += PacketRef::Signature(s).serialized_len(); } } for u in self.unknowns() { l += PacketRef::Unknown(u.unknown()).serialized_len(); for s in u.signatures() { l += PacketRef::Signature(s).serialized_len(); } } for s in self.bad_signatures() { l += PacketRef::Signature(s).serialized_len(); } l } fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> { generic_serialize_into(self, self.serialized_len(), buf) } fn export_into(&self, buf: &mut [u8]) -> Result<usize> { generic_export_into(self, self.serialized_len(), buf) } } impl Cert { /// Derive a [`TSK`] object from this key. /// /// This object writes out secret keys during serialization. /// /// [`TSK`]: crate::serialize::TSK pub fn as_tsk(&self) -> TSK { TSK::new(self) } /// Derive a [`TSK`] object from this key that owns the `Cert`. /// /// This object writes out secret keys during serialization. /// /// [`TSK`]: crate::serialize::TSK pub fn into_tsk(self) -> TSK<'static> { TSK::from(self) } } /// A reference to a `Cert` that allows serialization of secret keys. /// /// To avoid accidental leakage, secret keys are not serialized when a /// serializing a [`Cert`]. To serialize [`Cert`]s with secret keys, /// use [`Cert::as_tsk()`] to create a `TSK`, which is a shim on top /// of the `Cert`, and serialize this. /// /// [`Cert`]: crate::cert::Cert /// [`Cert::as_tsk()`]: crate::cert::Cert::as_tsk() /// /// # Examples /// /// ``` /// # use sequoia_openpgp::{*, cert::*, parse::Parse, serialize::Serialize}; /// # fn main() -> Result<()> { /// let (cert, _) = CertBuilder::new().generate()?; /// assert!(cert.is_tsk()); /// /// let mut buf = Vec::new(); /// cert.as_tsk().serialize(&mut buf)?; /// /// let cert_ = Cert::from_bytes(&buf)?; /// assert!(cert_.is_tsk()); /// assert_eq!(cert, cert_); /// # Ok(()) } /// ``` pub struct TSK<'a> { pub(crate) cert: Cow<'a, Cert>, filter: Box<dyn Fn(&key::UnspecifiedSecret) -> bool + Send + Sync + 'a>, emit_stubs: bool, } impl PartialEq for TSK<'_> { fn eq(&self, other: &Self) -> bool { // First, compare the certs. If they are not equal, then the // TSK's cannot possibly be equal. if self.cert != other.cert { return false; } // Second, consider all the keys. for (a, b) in self.cert.keys() .zip(other.cert.keys()) { match (a.has_secret() && (self.filter)(a.key().parts_as_secret().expect("has secret")), b.has_secret() && (other.filter)(b.key().parts_as_secret().expect("has_secret"))) { // Both have secrets. Compare secrets. (true, true) => if a.key().optional_secret() != b.key().optional_secret() { return false; }, // No secrets. Equal iff both or neither emit stubs. (false, false) => if self.emit_stubs != other.emit_stubs { return false; }, // Otherwise, they differ. _ => return false, } } // Everything matched. true } } impl From<Cert> for TSK<'_> { fn from(c: Cert) -> Self { Self { cert: Cow::Owned(c), filter: Box::new(|_| true), emit_stubs: false, } } } impl<'a> TSK<'a> { /// Creates a new view for the given `Cert`. fn new(cert: &'a Cert) -> Self { Self { cert: Cow::Borrowed(cert), filter: Box::new(|_| true), emit_stubs: false, } } /// Decomposes the TSK. pub(crate) fn decompose(self) -> (Cow<'a, Cert>, Box<dyn Fn(&key::UnspecifiedSecret) -> bool + Send + Sync + 'a>, bool) { (self.cert, self.filter, self.emit_stubs) } /// Returns whether we're emitting secret (sub)key packets when /// serializing. /// /// Computes whether we have secrets, taking the filter into /// account, and whether we are emitting stubs. This can be used /// to determine the correct armor label to use. pub(crate) fn emits_secret_key_packets(&self) -> bool { self.emit_stubs || self.cert.keys().secret().any(|skb| (self.filter)(skb.key())) } /// Filters which secret keys to export using the given predicate. /// /// Note that the given filter replaces any existing filter. /// /// # Examples /// /// This example demonstrates how to create a TSK with a detached /// primary secret key. /// /// ``` /// # use sequoia_openpgp::{*, cert::*, parse::Parse, serialize::Serialize}; /// use sequoia_openpgp::policy::StandardPolicy; /// /// # fn main() -> Result<()> { /// let p = &StandardPolicy::new(); /// /// let (cert, _) = CertBuilder::new().add_signing_subkey().generate()?; /// assert_eq!(cert.keys().with_policy(p, None).alive().revoked(false).secret().count(), 2); /// /// // Only write out the subkey's secret. /// let mut buf = Vec::new(); /// cert.as_tsk() /// .set_filter(|k| k.fingerprint() != cert.fingerprint()) /// .serialize(&mut buf)?; /// /// let cert_ = Cert::from_bytes(&buf)?; /// assert!(! cert_.primary_key().key().has_secret()); /// assert_eq!(cert_.keys().with_policy(p, None).alive().revoked(false).secret().count(), 1); /// # Ok(()) } /// ``` pub fn set_filter<P>(mut self, predicate: P) -> Self where P: Fn(&key::UnspecifiedSecret)-> bool + Send + Sync + 'a { self.filter = Box::new(predicate); self } /// Changes `TSK` to emit secret key stubs. /// /// If [`TSK::set_filter`] is used to selectively export secret /// keys, or if the cert contains both keys without secret key /// material and with secret key material, then are two ways to /// serialize this cert. Neither is sanctioned by the OpenPGP /// standard. /// /// The default way is to simply emit public key packets when no /// secret key material is available. While straight forward, /// this may be in violation of [Section 10.2 of RFC 9580]. /// /// The alternative is to emit a secret key packet with a /// placeholder secret key value. GnuPG uses this variant with a /// private [`S2K`] format. If interoperability with GnuPG is a /// concern, use this variant. /// /// See [this test] for support in other implementations. /// /// [`TSK::set_filter`]: TSK::set_filter() /// [Section 10.2 of RFC 9580]: https://www.rfc-editor.org/rfc/rfc9580.html#section-10.2 /// [`S2K`]: super::crypto::S2K /// [this test]: https://tests.sequoia-pgp.org/#Detached_primary_key /// /// # Examples /// /// This example demonstrates how to create a TSK with a detached /// primary secret key, serializing it using secret key stubs. /// /// ``` /// # fn main() -> sequoia_openpgp::Result<()> { /// # use std::convert::TryFrom; /// use sequoia_openpgp as openpgp; /// use openpgp::packet::key::*; /// # use openpgp::{types::*, crypto::S2K}; /// # use openpgp::{*, cert::*, parse::Parse, serialize::Serialize}; /// /// let p = &openpgp::policy::StandardPolicy::new(); /// /// let (cert, _) = CertBuilder::new().add_signing_subkey().generate()?; /// assert_eq!(cert.keys().with_policy(p, None) /// .alive().revoked(false).unencrypted_secret().count(), 2); /// /// // Only write out the subkey's secret, the primary key is "detached". /// let mut buf = Vec::new(); /// cert.as_tsk() /// .set_filter(|k| k.fingerprint() != cert.fingerprint()) /// .emit_secret_key_stubs(true) /// .serialize(&mut buf)?; /// /// # let pp = PacketPile::from_bytes(&buf)?; /// # assert_eq!(pp.path_ref(&[0]).unwrap().kind(), /// # Some(packet::Tag::SecretKey)); /// let cert_ = Cert::from_bytes(&buf)?; /// // The primary key has an "encrypted" stub. /// assert!(cert_.primary_key().key().has_secret()); /// assert_eq!(cert_.keys().with_policy(p, None) /// .alive().revoked(false).unencrypted_secret().count(), 1); /// # if let Some(SecretKeyMaterial::Encrypted(sec)) = /// # cert_.primary_key().key().optional_secret() /// # { /// # assert_eq!(sec.algo(), SymmetricAlgorithm::Unencrypted); /// # if let S2K::Private { tag, .. } = sec.s2k() { /// # assert_eq!(*tag, 101); /// # } else { /// # panic!("expected proprietary S2K type"); /// # } /// # } else { /// # panic!("expected ''encrypted'' secret key stub"); /// # } /// # Ok(()) } /// ``` pub fn emit_secret_key_stubs(mut self, emit_stubs: bool) -> Self { self.emit_stubs = emit_stubs; self } /// Adds a GnuPG-style secret key stub to the key. pub(crate) fn add_stub<P, R>(key: Key<P, R>) -> key::UnspecifiedSecret where P: key::KeyParts, R: key::KeyRole, { let key = key.parts_into_public().role_into_unspecified(); // Emit a GnuPG-style secret key stub. let stub = crate::crypto::S2K::Private { tag: 101, parameters: Some(vec![ 0, // "hash algo" 0x47, // 'G' 0x4e, // 'N' 0x55, // 'U' 1 // "mode" ].into()), }; key.add_secret(key::SecretKeyMaterial::Encrypted( key::Encrypted::new( stub, 0.into(), // Mirrors more closely what GnuPG 2.1 // does (oddly, GnuPG 1.4 emits 0xfe // here). Some(crate::crypto::mpi::SecretKeyChecksum::Sum16), vec![].into()))) .0.into() } /// Serializes or exports the Cert. /// /// If `export` is true, then non-exportable signatures are not /// written, and components without any exportable binding /// signature or revocation are not exported. fn serialize_common(&self, o: &mut dyn std::io::Write, export: bool) -> Result<()> { // Writes a signature if it is exportable or `! export`. let serialize_sig = |o: &mut dyn std::io::Write, sig: &Signature| -> Result<()> { if export { if sig.exportable().is_ok() { PacketRef::Signature(sig).export(o)?; } } else { PacketRef::Signature(sig).serialize(o)?; } Ok(()) }; // Serializes public or secret key depending on the filter. let serialize_key = |o: &mut dyn std::io::Write, key: &'a key::UnspecifiedKey, tag_public, tag_secret| { // We check for secrets here. let tag = if key.has_secret() && (self.filter)(key.try_into().expect("checked for secrets")) { tag_secret } else { tag_public }; if self.emit_stubs && (tag == Tag::PublicKey || tag == Tag::PublicSubkey) { let key_with_stub = Self::add_stub(key.clone()); return match tag { Tag::PublicKey => crate::Packet::SecretKey(key_with_stub.into()) .serialize(o), Tag::PublicSubkey => crate::Packet::SecretSubkey(key_with_stub.into()) .serialize(o), _ => unreachable!(), }; } match tag { Tag::PublicKey => PacketRef::PublicKey(key.into()).serialize(o), Tag::PublicSubkey => PacketRef::PublicSubkey(key.into()).serialize(o), Tag::SecretKey => { PacketRef::SecretKey( key.try_into().expect("checked for secrets")) .serialize(o) } Tag::SecretSubkey => { PacketRef::SecretSubkey( key.try_into().expect("checked for secrets")) .serialize(o) } _ => unreachable!(), } }; if export && ! self.cert.exportable() { return Ok(()) } let primary = self.cert.primary_key(); serialize_key(o, primary.key().into(), Tag::PublicKey, Tag::SecretKey)?; for s in primary.signatures() { serialize_sig(o, s)?; } for u in self.cert.userids() { if export && ! u.self_signatures().chain(u.self_revocations()).any( |s| s.exportable().is_ok()) { // No exportable selfsig on this component, skip it. continue; } PacketRef::UserID(u.userid()).serialize(o)?; for s in u.signatures() { serialize_sig(o, s)?; } } for u in self.cert.user_attributes() { if export && ! u.self_signatures().chain(u.self_revocations()).any( |s| s.exportable().is_ok()) { // No exportable selfsig on this component, skip it. continue; } PacketRef::UserAttribute(u.user_attribute()).serialize(o)?; for s in u.signatures() { serialize_sig(o, s)?; } } for k in self.cert.keys().subkeys() { if export && ! k.self_signatures().chain(k.self_revocations()).any( |s| s.exportable().is_ok()) { // No exportable selfsig on this component, skip it. continue; } serialize_key(o, k.key().into(), Tag::PublicSubkey, Tag::SecretSubkey)?; for s in k.signatures() { serialize_sig(o, s)?; } } for u in self.cert.unknowns() { if export && ! u.certifications().any( |s| s.exportable().is_ok()) { // No exportable selfsig on this component, skip it. continue; } PacketRef::Unknown(u.unknown()).serialize(o)?; for s in u.signatures() { serialize_sig(o, s)?; } } for s in self.cert.bad_signatures() { serialize_sig(o, s)?; } Ok(()) } } impl<'a> crate::serialize::Serialize for TSK<'a> {} impl<'a> seal::Sealed for TSK<'a> {} impl<'a> Marshal for TSK<'a> { fn serialize(&self, o: &mut dyn std::io::Write) -> Result<()> { self.serialize_common(o, false) } fn export(&self, o: &mut dyn std::io::Write) -> Result<()> { self.serialize_common(o, true) } } impl<'a> crate::serialize::SerializeInto for TSK<'a> {} impl<'a> MarshalInto for TSK<'a> { fn serialized_len(&self) -> usize { let mut l = 0; // Serializes public or secret key depending on the filter. let serialized_len_key = |key: &'a key::UnspecifiedKey, tag_public, tag_secret| { // We check for secrets here. let tag = if key.has_secret() && (self.filter)(key.try_into().expect("have secrets")) { tag_secret } else { tag_public }; if self.emit_stubs && (tag == Tag::PublicKey || tag == Tag::PublicSubkey) { // Emit a GnuPG-style secret key stub. The stub // extends the public key by 8 bytes (plus 4 secret // length octets for v6). let l = key.parts_as_public().net_len() + match key.version() { 4 => 8, 6 => 12, _ => 0, // Serialization will fail anyway. }; return 1 // CTB + BodyLength::Full(l as u32).serialized_len() + l; } let packet = match tag { Tag::PublicKey => PacketRef::PublicKey(key.into()), Tag::PublicSubkey => PacketRef::PublicSubkey(key.into()), Tag::SecretKey => { PacketRef::SecretKey( key.try_into().expect("checked for secrets")) } Tag::SecretSubkey => { PacketRef::SecretSubkey( key.try_into().expect("checked for secrets")) } _ => unreachable!(), }; packet.serialized_len() }; let primary = self.cert.primary_key(); l += serialized_len_key(primary.key().into(), Tag::PublicKey, Tag::SecretKey); for s in primary.signatures() { l += PacketRef::Signature(s).serialized_len(); } for u in self.cert.userids() { l += PacketRef::UserID(u.userid()).serialized_len(); for s in u.signatures() { l += PacketRef::Signature(s).serialized_len(); } } for u in self.cert.user_attributes() { l += PacketRef::UserAttribute(u.user_attribute()).serialized_len(); for s in u.signatures() { l += PacketRef::Signature(s).serialized_len(); } } for k in self.cert.keys().subkeys() { l += serialized_len_key(k.key().into(), Tag::PublicSubkey, Tag::SecretSubkey); for s in k.signatures() { l += PacketRef::Signature(s).serialized_len(); } } for u in self.cert.unknowns() { l += PacketRef::Unknown(u.unknown()).serialized_len(); for s in u.signatures() { l += PacketRef::Signature(s).serialized_len(); } } for s in self.cert.bad_signatures() { l += PacketRef::Signature(s).serialized_len(); } l } fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> { generic_serialize_into(self, self.serialized_len(), buf) } fn export_into(&self, buf: &mut [u8]) -> Result<usize> { generic_export_into(self, self.serialized_len(), buf) } } #[cfg(test)] mod test { use super::*; use crate::vec_truncate; use crate::parse::Parse; use crate::packet::key; use crate::policy::StandardPolicy as P; /// Demonstrates that public keys and all components are /// serialized. #[test] fn roundtrip_cert() { for test in crate::tests::CERTS { let cert = match Cert::from_bytes(test.bytes) { Ok(t) => t, Err(_) => continue, }; assert!(! cert.is_tsk()); let buf = cert.as_tsk().to_vec().unwrap(); let cert_ = Cert::from_bytes(&buf).unwrap(); assert_eq!(cert, cert_, "roundtripping {}.pgp failed", test); } } /// Demonstrates that secret keys and all components are /// serialized. #[test] fn roundtrip_tsk() { for test in crate::tests::TSKS { let cert = Cert::from_bytes(test.bytes).unwrap(); assert!(cert.is_tsk()); let mut buf = Vec::new(); cert.as_tsk().serialize(&mut buf).unwrap(); let cert_ = Cert::from_bytes(&buf).unwrap(); assert_eq!(cert, cert_, "roundtripping {}-private.pgp failed", test); // This time, use a trivial filter. let mut buf = Vec::new(); cert.as_tsk().set_filter(|_| true).serialize(&mut buf).unwrap(); let cert_ = Cert::from_bytes(&buf).unwrap(); assert_eq!(cert, cert_, "roundtripping {}-private.pgp failed", test); } } /// Demonstrates that TSK::serialize() with the right filter /// reduces to Cert::serialize(). #[test] fn reduce_to_cert_serialize() { for test in crate::tests::TSKS { let cert = Cert::from_bytes(test.bytes).unwrap(); assert!(cert.is_tsk()); // First, use Cert::serialize(). let mut buf_cert = Vec::new(); cert.serialize(&mut buf_cert).unwrap(); // When serializing using TSK::serialize, filter out all // secret keys. let mut buf_tsk = Vec::new(); cert.as_tsk().set_filter(|_| false).serialize(&mut buf_tsk).unwrap(); // Check for equality. let cert_ = Cert::from_bytes(&buf_cert).unwrap(); let tsk_ = Cert::from_bytes(&buf_tsk).unwrap(); assert_eq!(cert_, tsk_, "reducing failed on {}-private.pgp: not Cert::eq", test); // Check for identinty. assert_eq!(buf_cert, buf_tsk, "reducing failed on {}-private.pgp: serialized identity", test); } } #[test] fn export() { use crate::Packet; use crate::cert::prelude::*; use crate::types::{Curve, KeyFlags, SignatureType}; use crate::packet::{ signature, UserID, user_attribute::{UserAttribute, Subpacket}, key::Key6, }; let p = &P::new(); let (cert, _) = CertBuilder::new().generate().unwrap(); let mut keypair = cert.primary_key().key().clone().parts_into_secret() .unwrap().into_keypair().unwrap(); let key: key::SecretSubkey = Key6::generate_ecc(false, Curve::Cv25519).unwrap().into(); let key_binding = key.bind( &mut keypair, &cert, signature::SignatureBuilder::new(SignatureType::SubkeyBinding) .set_key_flags( KeyFlags::empty().set_transport_encryption()) .unwrap() .set_exportable_certification(false).unwrap()).unwrap(); let uid = UserID::from("foo"); let uid_binding = uid.bind( &mut keypair, &cert, signature::SignatureBuilder::from( cert.primary_key().with_policy(p, None).unwrap() .direct_key_signature().unwrap().clone()) .set_type(SignatureType::PositiveCertification) .preserve_signature_creation_time().unwrap() .set_exportable_certification(false).unwrap()).unwrap(); let ua = UserAttribute::new(&[ Subpacket::Unknown(2, b"foo".to_vec().into_boxed_slice()), ]).unwrap(); let ua_binding = ua.bind( &mut keypair, &cert, signature::SignatureBuilder::from( cert.primary_key().with_policy(p, None).unwrap() .direct_key_signature().unwrap().clone()) .set_type(SignatureType::PositiveCertification) .preserve_signature_creation_time().unwrap() .set_exportable_certification(false).unwrap()).unwrap(); let cert = cert.insert_packets(vec![ Packet::SecretSubkey(key), key_binding.into(), uid.into(), uid_binding.into(), ua.into(), ua_binding.into(), ]).unwrap().0; assert_eq!(cert.subkeys().count(), 1); cert.subkeys().next().unwrap().binding_signature(p, None).unwrap(); assert_eq!(cert.userids().count(), 1); assert!(cert.userids().with_policy(p, None).next().is_some()); assert_eq!(cert.user_attributes().count(), 1); assert!(cert.user_attributes().with_policy(p, None).next().is_some()); // The binding signature is not exportable, so when we export // and re-parse, we expect the userid to be gone. let mut buf = Vec::new(); cert.export(&mut buf).unwrap(); let cert_ = Cert::from_bytes(&buf).unwrap(); assert_eq!(cert_.subkeys().count(), 0); assert_eq!(cert_.userids().count(), 0); assert_eq!(cert_.user_attributes().count(), 0); let mut buf = vec![0; cert.serialized_len()]; let l = cert.export_into(&mut buf).unwrap(); vec_truncate(&mut buf, l); let cert_ = Cert::from_bytes(&buf).unwrap(); assert_eq!(cert_.subkeys().count(), 0); assert_eq!(cert_.userids().count(), 0); assert_eq!(cert_.user_attributes().count(), 0); let cert_ = Cert::from_bytes(&cert.export_to_vec().unwrap()).unwrap(); assert_eq!(cert_.subkeys().count(), 0); assert_eq!(cert_.userids().count(), 0); assert_eq!(cert_.user_attributes().count(), 0); // Same, this time using the armor encoder. let mut buf = Vec::new(); cert.armored().export(&mut buf).unwrap(); let cert_ = Cert::from_bytes(&buf).unwrap(); assert_eq!(cert_.subkeys().count(), 0); assert_eq!(cert_.userids().count(), 0); assert_eq!(cert_.user_attributes().count(), 0); let mut buf = vec![0; cert.serialized_len()]; let l = cert.armored().export_into(&mut buf).unwrap(); vec_truncate(&mut buf, l); let cert_ = Cert::from_bytes(&buf).unwrap(); assert_eq!(cert_.subkeys().count(), 0); assert_eq!(cert_.userids().count(), 0); assert_eq!(cert_.user_attributes().count(), 0); let cert_ = Cert::from_bytes(&cert.armored().export_to_vec().unwrap()).unwrap(); assert_eq!(cert_.subkeys().count(), 0); assert_eq!(cert_.userids().count(), 0); assert_eq!(cert_.user_attributes().count(), 0); // Same, this time as TSKs. let mut buf = Vec::new(); cert.as_tsk().export(&mut buf).unwrap(); let cert_ = Cert::from_bytes(&buf).unwrap(); assert_eq!(cert_.subkeys().count(), 0); assert_eq!(cert_.userids().count(), 0); assert_eq!(cert_.user_attributes().count(), 0); let mut buf = vec![0; cert.serialized_len()]; let l = cert.as_tsk().export_into(&mut buf).unwrap(); vec_truncate(&mut buf, l); let cert_ = Cert::from_bytes(&buf).unwrap(); assert_eq!(cert_.subkeys().count(), 0); assert_eq!(cert_.userids().count(), 0); assert_eq!(cert_.user_attributes().count(), 0); let cert_ = Cert::from_bytes(&cert.as_tsk().export_to_vec().unwrap()).unwrap(); assert_eq!(cert_.subkeys().count(), 0); assert_eq!(cert_.userids().count(), 0); assert_eq!(cert_.user_attributes().count(), 0); } /// Tests that GnuPG-style stubs are preserved when roundtripping. #[test] fn issue_613() -> Result<()> { use crate::packet::key::*; use crate::{types::*, crypto::S2K}; use crate::{*, cert::*, parse::Parse}; let p = &crate::policy::StandardPolicy::new(); let (cert, _) = CertBuilder::new().add_signing_subkey().generate()?; assert_eq!(cert.keys().with_policy(p, None) .alive().revoked(false).unencrypted_secret().count(), 2); // Only write out the subkey's secret, the primary key is "detached". let buf = cert.as_tsk() .set_filter(|k| k.fingerprint() != cert.fingerprint()) .emit_secret_key_stubs(true) .to_vec()?; // Try parsing it. let cert_ = Cert::from_bytes(&buf)?; // The primary key has an "encrypted" stub. assert!(cert_.primary_key().has_secret()); assert_eq!(cert_.keys().with_policy(p, None) .alive().revoked(false).unencrypted_secret().count(), 1); if let Some(SecretKeyMaterial::Encrypted(sec)) = cert_.primary_key().key().optional_secret() { assert_eq!(sec.algo(), SymmetricAlgorithm::Unencrypted); if let S2K::Private { tag, .. } = sec.s2k() { assert_eq!(*tag, 101); } else { panic!("expected proprietary S2K type"); } } else { panic!("expected ''encrypted'' secret key stub"); } // When roundtripping such a key, the stub should be preserved. let buf_ = cert_.as_tsk().to_vec()?; assert_eq!(buf, buf_); Ok(()) } /// Checks partial equality of certificates and TSKs. #[test] fn issue_701() -> Result<()> { let cert_0 = Cert::from_bytes(crate::tests::key("testy.pgp"))?; let cert_1 = Cert::from_bytes(crate::tests::key("testy.pgp"))?; let tsk_0 = Cert::from_bytes(crate::tests::key("testy-private.pgp"))?; let tsk_1 = Cert::from_bytes(crate::tests::key("testy-private.pgp"))?; // We define equality by equality of the serialized form. // This macro checks that. macro_rules! check { ($a: expr, $b: expr, $expectation: expr) => {{ let a = $a; let b = $b; let eq = a == b; let serialized_eq = a.to_vec()? == b.to_vec()?; let armored_eq = a.armored().to_vec()? == b.armored().to_vec()?; assert_eq!(serialized_eq, eq); assert_eq!(armored_eq, eq); assert_eq!(eq, $expectation); }}; } // Equal as certs, because equality is defined by equality of // the serialized form, and serializing a cert never writes // out any secrets. check!(&cert_0, &cert_1, true); check!(&cert_0, &tsk_0, true); // Filters out secrets. let no_secrets = |_: &_| false; // TSK's equality. check!(tsk_0.as_tsk(), tsk_1.as_tsk(), true); // Without secrets. check!(tsk_0.as_tsk().set_filter(no_secrets), tsk_1.as_tsk().set_filter(no_secrets), true); // Still equal if one uses stubs and the other one does not if // every key has a secret, i.e. stubs are not in fact used. check!( tsk_0.as_tsk().emit_secret_key_stubs(true), tsk_1.as_tsk(), true); // No longer equal if one actually emits stubs. check!( tsk_0.as_tsk().emit_secret_key_stubs(true).set_filter(no_secrets), tsk_1.as_tsk(), false); // Certs are not equal to TSKs, because here the secrets are // written out when serialized. check!(cert_0.as_tsk(), tsk_0.as_tsk(), false); // Equal, if we filter out the secrets. check!(cert_0.as_tsk(), tsk_0.as_tsk().set_filter(no_secrets), true); Ok(()) } #[test] fn issue_1075() -> Result<()> { let cert = Cert::from_bytes(crate::tests::key("testy.pgp"))?; let tsk = Cert::from_bytes(crate::tests::key("testy-private.pgp"))?; // Filters out secrets. let no_secrets = |_: &_| false; assert!(! cert.as_tsk().emits_secret_key_packets()); assert!(cert.as_tsk().emit_secret_key_stubs(true) .emits_secret_key_packets()); assert!(tsk.as_tsk().emits_secret_key_packets()); assert!(! tsk.as_tsk().set_filter(no_secrets) .emits_secret_key_packets()); assert!(cert.armored().to_vec()? != tsk.as_tsk().armored().to_vec()?); assert_eq!(cert.armored().to_vec()?, cert.as_tsk().armored().to_vec()?); assert_eq!(cert.armored().to_vec()?, tsk.as_tsk().set_filter(no_secrets).armored().to_vec()?); Ok(()) } #[test] fn into_packets() -> Result<()> { let cert = Cert::from_bytes(crate::tests::key("testy-private.pgp"))?; fn t(tsk: TSK) { let a = tsk.to_vec().unwrap(); let b = { let mut b = Vec::new(); tsk.into_packets().for_each(|p| p.serialize(&mut b).unwrap()); b }; assert_eq!(a, b); } let tsk = cert.clone().into_tsk(); t(tsk); let tsk = cert.clone().into_tsk().set_filter(|_| false); t(tsk); let tsk = cert.clone().into_tsk() .set_filter(|k| k.fingerprint() == cert.fingerprint()); t(tsk); let tsk = cert.clone().into_tsk() .set_filter(|k| k.fingerprint() == cert.fingerprint()) .emit_secret_key_stubs(true); t(tsk); Ok(()) } } ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/src/serialize/cert_armored.rs�������������������������������������������������0000644�0000000�0000000�00000034303�10461020230�0020534�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������//! Module to serialize and enarmor a Cert and add informative headers. use std::io; use std::str; use crate::armor; use crate::cert::{Cert, amalgamation::ValidAmalgamation}; use crate::Result; use crate::types::RevocationStatus; use crate::seal; use crate::serialize::{ Marshal, MarshalInto, generic_serialize_into, generic_export_into, TSK, }; use crate::policy::StandardPolicy as P; /// Whether a character is printable. pub(crate) fn is_printable(c: &char) -> bool { // c.is_ascii_alphanumeric || c.is_whitespace || c.is_ascii_punctuation // would exclude any utf8 character, so it seems that to obtain all // printable chars, it works just excluding the control chars. !c.is_control() && !c.is_ascii_control() } impl Cert { /// Creates descriptive armor headers. /// /// Returns armor headers that describe this Cert. The Cert's /// primary fingerprint and valid userids (according to the /// default policy) are included as comments, so that it is easier /// to identify the Cert when looking at the armored data. pub fn armor_headers(&self) -> Vec<String> { let p = &P::default(); let length_value = armor::LINE_LENGTH - "Comment: ".len(); // Create a header per userid. let mut headers: Vec<String> = self.userids().with_policy(p, None) // Ignore revoked userids. .filter(|uidb| { !matches!(uidb.revocation_status(), RevocationStatus::Revoked(_)) // Ignore userids with non-printable characters. }).filter_map(|uidb| { let value = str::from_utf8(uidb.userid().value()).ok()?; for c in value.chars().take(length_value) { if !is_printable(&c){ return None; } } // Make sure the line length does not exceed armor::LINE_LENGTH Some(value.chars().take(length_value).collect()) }).collect(); // Add the fingerprint to the front. headers.insert(0, self.fingerprint().to_spaced_hex()); headers } /// Wraps this Cert in an armor structure when serialized. /// /// Derives an object from this `Cert` that adds an armor structure /// to the serialized `Cert` when it is serialized. Additionally, /// the `Cert`'s User IDs are added as comments, so that it is easier /// to identify the Cert when looking at the armored data. /// /// # Examples /// /// ```rust /// use sequoia_openpgp as openpgp; /// use openpgp::cert::prelude::*; /// use openpgp::serialize::SerializeInto; /// /// # fn main() -> openpgp::Result<()> { /// let (cert, _) = /// CertBuilder::general_purpose(Some("Mr. Pink ☮☮☮")) /// .generate()?; /// let armored = String::from_utf8(cert.armored().to_vec()?)?; /// /// assert!(armored.starts_with("-----BEGIN PGP PUBLIC KEY BLOCK-----")); /// assert!(armored.contains("Mr. Pink ☮☮☮")); /// # Ok(()) } /// ``` pub fn armored(&self) -> impl crate::serialize::Serialize + crate::serialize::SerializeInto + '_ { Encoder::new(self) } } impl<'a> TSK<'a> { /// Wraps this TSK in an armor structure when serialized. /// /// Derives an object from this `TSK` that adds an armor structure /// to the serialized `TSK` when it is serialized. Additionally, /// the `TSK`'s User IDs are added as comments, so that it is easier /// to identify the `TSK` when looking at the armored data. /// /// # Examples /// /// ```rust /// use sequoia_openpgp as openpgp; /// use openpgp::cert::prelude::*; /// use openpgp::serialize::SerializeInto; /// /// # fn main() -> openpgp::Result<()> { /// let (cert, _) = /// CertBuilder::general_purpose(Some("Mr. Pink ☮☮☮")) /// .generate()?; /// let armored = String::from_utf8(cert.as_tsk().armored().to_vec()?)?; /// /// assert!(armored.starts_with("-----BEGIN PGP PRIVATE KEY BLOCK-----")); /// assert!(armored.contains("Mr. Pink ☮☮☮")); /// # Ok(()) } /// ``` pub fn armored(self) -> impl crate::serialize::Serialize + crate::serialize::SerializeInto + 'a { Encoder::new_tsk(self) } } /// A `Cert` or `TSK` to be armored and serialized. enum Encoder<'a> { Cert(&'a Cert), TSK(TSK<'a>), } impl<'a> Encoder<'a> { /// Returns a new Encoder to enarmor and serialize a `Cert`. fn new(cert: &'a Cert) -> Self { Encoder::Cert(cert) } /// Returns a new Encoder to enarmor and serialize a `TSK`. fn new_tsk(tsk: TSK<'a>) -> Self { Encoder::TSK(tsk) } /// Returns the cert's primary key version. fn primary_key_version(&self) -> u8 { match self { Encoder::Cert(cert) => cert.primary_key().key().version(), Encoder::TSK(tsk) => tsk.cert.primary_key().key().version(), } } fn serialize_common(&self, o: &mut dyn io::Write, export: bool) -> Result<()> { if export { let exportable = match self { Encoder::Cert(cert) => cert.exportable(), Encoder::TSK(tsk) => tsk.cert.exportable(), }; if ! exportable { return Ok(()); } } let (prelude, headers) = match self { Encoder::Cert(cert) => (armor::Kind::PublicKey, cert.armor_headers()), Encoder::TSK(tsk) => if tsk.emits_secret_key_packets() { (armor::Kind::SecretKey, tsk.cert.armor_headers()) } else { (armor::Kind::PublicKey, tsk.cert.armor_headers()) }, }; // Convert the Vec<String> into Vec<(&str, &str)> // `iter_into` can not be used here because will take ownership and // what is needed is the reference. let headers: Vec<_> = headers.iter() .map(|value| ("Comment", value.as_str())) .collect(); let mut w = armor::Writer::with_headers(o, prelude, headers)?; if self.primary_key_version() > 4 { w.set_profile(crate::Profile::RFC9580)?; } else { w.set_profile(crate::Profile::RFC4880)?; } if export { match self { Encoder::Cert(cert) => cert.export(&mut w)?, Encoder::TSK(ref tsk) => tsk.export(&mut w)?, } } else { match self { Encoder::Cert(cert) => cert.serialize(&mut w)?, Encoder::TSK(ref tsk) => tsk.serialize(&mut w)?, } } w.finalize()?; Ok(()) } } impl<'a> crate::serialize::Serialize for Encoder<'a> {} impl<'a> seal::Sealed for Encoder<'a> {} impl<'a> Marshal for Encoder<'a> { fn serialize(&self, o: &mut dyn io::Write) -> Result<()> { self.serialize_common(o, false) } fn export(&self, o: &mut dyn io::Write) -> Result<()> { self.serialize_common(o, true) } } impl<'a> crate::serialize::SerializeInto for Encoder<'a> {} impl<'a> MarshalInto for Encoder<'a> { fn serialized_len(&self) -> usize { let h = match self { Encoder::Cert(cert) => cert.armor_headers(), Encoder::TSK(ref tsk) => tsk.cert.armor_headers(), }; let headers_len = ("Comment: ".len() + 1 /* NL */) * h.len() + h.iter().map(|c| c.len()).sum::<usize>(); let body_len = (match self { Self::Cert(cert) => cert.serialized_len(), Self::TSK(ref tsk) => tsk.serialized_len(), } + 2) / 3 * 4; // base64 let crc_len = if self.primary_key_version() > 4 { 0 } else { "=FUaG\n".len() }; let word = match self { Self::Cert(_) => "PUBLIC", Self::TSK(tsk) => if tsk.emits_secret_key_packets() { "PRIVATE" } else { "PUBLIC" }, }.len(); "-----BEGIN PGP ".len() + word + " KEY BLOCK-----\n\n".len() + headers_len + body_len + (body_len + armor::LINE_LENGTH - 1) / armor::LINE_LENGTH // NLs + crc_len + "-----END PGP ".len() + word + " KEY BLOCK-----\n".len() } fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> { generic_serialize_into(self, self.serialized_len(), buf) } fn export_into(&self, buf: &mut [u8]) -> Result<usize> { generic_export_into(self, self.serialized_len(), buf) } } #[cfg(test)] mod tests { use crate::armor::{Kind, Reader, ReaderMode}; use crate::cert::prelude::*; use crate::parse::Parse; use super::*; #[test] fn is_printable_succeed() { let chars: Vec<char> = vec![ 'a', 'z', 'A', 'Z', '1', '9', '0', '|', '!', '#', '$', '%', '^', '&', '*', '-', '+', '/', // The following Unicode characters were taken from: // https://doc.rust-lang.org/std/primitive.char.html 'é', 'ß', 'ℝ', '💣', '❤', '東', '京', '𝕊', '💝', 'δ', 'Δ', '中', '越', '٣', '7', '৬', '¾', '①', 'K', 'و', '藏', '山', 'I', 'ï', 'İ', 'i' ]; for c in &chars { assert!(is_printable(c)); } } #[test] fn is_printable_fail() { let chars: Vec<char> = vec![ '\n', 0x1b_u8.into(), // U+009C, STRING TERMINATOR 'œ' ]; for c in &chars { assert!(!is_printable(c)); } } #[test] fn serialize_succeed() { let cert = Cert::from_bytes(crate::tests::key("neal.pgp")).unwrap(); // Enarmor the Cert. let mut buffer = Vec::new(); cert.armored() .serialize(&mut buffer) .unwrap(); // Parse the armor. let mut cursor = io::Cursor::new(&buffer); let mut reader = Reader::from_reader( &mut cursor, ReaderMode::Tolerant(Some(Kind::PublicKey))); // Extract the headers. let mut headers: Vec<&str> = reader.headers() .unwrap() .into_iter() .map(|header| { assert_eq!(&header.0[..], "Comment"); &header.1[..]}) .collect(); headers.sort(); // Ensure the headers are correct let mut expected_headers = [ "Neal H. Walfield <neal@walfield.org>", "Neal H. Walfield <neal@gnupg.org>", "Neal H. Walfield <neal@pep-project.org>", "Neal H. Walfield <neal@pep.foundation>", "Neal H. Walfield <neal@sequoia-pgp.org>", "8F17 7771 18A3 3DDA 9BA4 8E62 AACB 3243 6300 52D9"]; expected_headers.sort(); assert_eq!(&expected_headers[..], &headers[..]); } #[test] fn serialize_length_succeed() { let length_value = armor::LINE_LENGTH - "Comment: ".len(); // Create userids one character longer than the size allowed in the // header and expect headers with the correct length. // 1 byte character // Can not use `to_string` here because not such method for //`std::vec::Vec<char>` let userid1: String = vec!['a'; length_value + 1].into_iter() .collect(); let userid1_expected: String = vec!['a'; length_value].into_iter() .collect(); // 2 bytes character. let userid2: String = vec!['ß'; length_value + 1].into_iter() .collect(); let userid2_expected: String = vec!['ß'; length_value].into_iter() .collect(); // 3 bytes character. let userid3: String = vec!['€'; length_value + 1].into_iter() .collect(); let userid3_expected: String = vec!['€'; length_value].into_iter() .collect(); // 4 bytes character. let userid4: String = vec!['𐍈'; length_value + 1].into_iter() .collect(); let userid4_expected: String = vec!['𐍈'; length_value].into_iter() .collect(); let mut userid5 = vec!['a'; length_value]; userid5[length_value-1] = 'ß'; let userid5: String = userid5.into_iter().collect(); // Create a Cert with the userids. let (cert, _) = CertBuilder::general_purpose(Some(&userid1[..])) .add_userid(&userid2[..]) .add_userid(&userid3[..]) .add_userid(&userid4[..]) .add_userid(&userid5[..]) .generate() .unwrap(); // Enarmor the Cert. let mut buffer = Vec::new(); cert.armored() .serialize(&mut buffer) .unwrap(); // Parse the armor. let mut cursor = io::Cursor::new(&buffer); let mut reader = Reader::from_reader( &mut cursor, ReaderMode::Tolerant(Some(Kind::PublicKey))); // Extract the headers. let mut headers: Vec<&str> = reader.headers() .unwrap() .into_iter() .map(|header| { assert_eq!(&header.0[..], "Comment"); &header.1[..]}) .skip(1) // Ignore the first header since it is the fingerprint .collect(); // Cert canonicalization does not preserve the order of // userids. headers.sort(); let mut headers_iter = headers.into_iter(); assert_eq!(headers_iter.next().unwrap(), &userid1_expected); assert_eq!(headers_iter.next().unwrap(), &userid5); assert_eq!(headers_iter.next().unwrap(), &userid2_expected); assert_eq!(headers_iter.next().unwrap(), &userid3_expected); assert_eq!(headers_iter.next().unwrap(), &userid4_expected); } #[test] fn serialize_into() { let cert = Cert::from_bytes(crate::tests::key("neal.pgp")).unwrap(); let mut v = Vec::new(); cert.armored().serialize(&mut v).unwrap(); let v_ = cert.armored().to_vec().unwrap(); assert_eq!(v, v_); // Test truncation. let mut v = vec![0; cert.armored().serialized_len() - 1]; let r = cert.armored().serialize_into(&mut v[..]); assert_match!( crate::Error::InvalidArgument(_) = r.unwrap_err().downcast().expect("not an openpgp::Error")); } } �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/src/serialize/stream/dash_escape.rs�������������������������������������������0000644�0000000�0000000�00000020600�10461020230�0021613�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������//! Encodes a byte stream as OpenPGP's dash-escaped text. //! //! This filter is used to generate messages using the Cleartext //! Signature Framework (see [Section 7.2 of RFC 9580]). //! //! [Section 7.2 of RFC 9580]: https://www.rfc-editor.org/rfc/rfc9580.html#section-7.2 use std::fmt; use std::io; use crate::{ Error, Result, serialize::stream::{ writer, Message, Cookie, }, }; pub(super) struct DashEscapeFilter<'a, C: 'a> { // The underlying writer. inner: writer::BoxStack<'a, C>, // The cookie. cookie: C, // The buffer. buffer: Vec<u8>, // The number of bytes written to this filter. position: u64, } assert_send_and_sync!(DashEscapeFilter<'_, C> where C); impl<'a> DashEscapeFilter<'a, Cookie> { /// Returns a new filter applying dash-escaping to all lines. pub fn new(inner: Message<'a>, cookie: Cookie) -> Message<'a> { Message::from(Box::new(DashEscapeFilter { inner: inner.into(), cookie, buffer: Vec::new(), position: 0, })) } } impl<'a, C: 'a> DashEscapeFilter<'a, C> { /// Writes out any complete lines between `self.buffer` and `other`. /// /// Any extra data is buffered. /// /// If `done` is set, then flushes any data. fn write_out(&mut self, other: &[u8], done: bool) -> io::Result<()> { // XXX: Currently, we don't mind copying the data. This // could be optimized. self.buffer.extend_from_slice(other); // Write out all whole lines (i.e. those terminated by a // newline). This is a bit awkward, because we only know that // a line was whole when we are looking at the next line. let mut last_line: Option<&[u8]> = None; for line in self.buffer.split(|b| *b == b'\n') { if let Some(l) = last_line.take() { if l.starts_with(b"-") || l.starts_with(b"From ") { // Dash-escape! self.inner.write_all(b"- ")?; } self.inner.write_all(l)?; self.inner.write_all(b"\n")?; } last_line = Some(line); } if done { if let Some(l) = last_line.take() { if l.starts_with(b"-") || l.starts_with(b"From ") { // Dash-escape! self.inner.write_all(b"- ")?; } self.inner.write_all(l)?; } } let new_buffer = last_line.map(|l| l.to_vec()) .unwrap_or_else(Vec::new); crate::vec_truncate(&mut self.buffer, 0); self.buffer = new_buffer; Ok(()) } } impl<'a, C: 'a> io::Write for DashEscapeFilter<'a, C> { fn write(&mut self, buf: &[u8]) -> io::Result<usize> { self.write_out(buf, false)?; self.position += buf.len() as u64; Ok(buf.len()) } // XXX: The API says that `flush` is supposed to flush any // internal buffers to disk. We don't do that. fn flush(&mut self) -> io::Result<()> { Ok(()) } } impl<'a, C: 'a> fmt::Debug for DashEscapeFilter<'a, C> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.debug_struct("DashEscapeFilter") .field("inner", &self.inner) .finish() } } impl<'a, C: 'a> writer::Stackable<'a, C> for DashEscapeFilter<'a, C> { fn into_inner(mut self: Box<Self>) -> Result<Option<writer::BoxStack<'a, C>>> { self.write_out(&b""[..], true)?; Ok(Some(self.inner)) } fn pop(&mut self) -> Result<Option<writer::BoxStack<'a, C>>> { Err(Error::InvalidOperation( "Cannot pop DashEscapeFilter".into()).into()) } fn mount(&mut self, new: writer::BoxStack<'a, C>) { self.inner = new; } fn inner_mut(&mut self) -> Option<&mut (dyn writer::Stackable<'a, C> + Send + Sync)> { Some(&mut self.inner) } fn inner_ref(&self) -> Option<&(dyn writer::Stackable<'a, C> + Send + Sync)> { Some(&self.inner) } fn cookie_set(&mut self, cookie: C) -> C { ::std::mem::replace(&mut self.cookie, cookie) } fn cookie_ref(&self) -> &C { &self.cookie } fn cookie_mut(&mut self) -> &mut C { &mut self.cookie } fn position(&self) -> u64 { self.position } } #[cfg(test)] mod test { use std::io::Write; use super::*; use crate::serialize::stream::Message; #[test] fn no_escape() -> Result<()> { let mut buf = Vec::new(); { let m = Message::new(&mut buf); let mut m = DashEscapeFilter::new(m, Default::default()); m.write_all(b"0123")?; m.write_all(b"4567\n")?; m.write_all(b"89ab")?; m.write_all(b"cdef")?; m.write_all(b"\n")?; m.finalize()?; } assert_eq!(&buf[..], &b"01234567\n89abcdef\n"[..]); let mut buf = Vec::new(); { let m = Message::new(&mut buf); let mut m = DashEscapeFilter::new(m, Default::default()); m.write_all(b"0123")?; m.write_all(b"4567\n")?; m.write_all(b"89ab")?; m.write_all(b"cdef")?; // No final newline. m.finalize()?; } assert_eq!(&buf[..], &b"01234567\n89abcdef"[..]); Ok(()) } #[test] fn dash_escape() -> Result<()> { let mut buf = Vec::new(); { let m = Message::new(&mut buf); let mut m = DashEscapeFilter::new(m, Default::default()); m.write_all(b"-0123")?; m.write_all(b"-4567\n")?; m.write_all(b"-89ab")?; m.write_all(b"-cdef")?; m.write_all(b"-\n")?; m.finalize()?; } assert_eq!(&buf[..], &b"- -0123-4567\n- -89ab-cdef-\n"[..]); let mut buf = Vec::new(); { let m = Message::new(&mut buf); let mut m = DashEscapeFilter::new(m, Default::default()); m.write_all(b"-0123")?; m.write_all(b"-4567\n")?; m.write_all(b"-89ab")?; m.write_all(b"-cdef")?; m.write_all(b"-")?; // No final newline. m.finalize()?; } assert_eq!(&buf[..], &b"- -0123-4567\n- -89ab-cdef-"[..]); Ok(()) } #[test] fn from_escape() -> Result<()> { let mut buf = Vec::new(); { let m = Message::new(&mut buf); let mut m = DashEscapeFilter::new(m, Default::default()); m.write_all(b"From 0123")?; m.write_all(b"From 4567\n")?; m.write_all(b"From 89ab")?; m.write_all(b"From cdef")?; m.write_all(b"From \n")?; m.finalize()?; } assert_eq!(&buf[..], &b"- From 0123From 4567\n- From 89abFrom cdefFrom \n"[..]); let mut buf = Vec::new(); { let m = Message::new(&mut buf); let mut m = DashEscapeFilter::new(m, Default::default()); m.write_all(b"From 0123")?; m.write_all(b"From 4567\n")?; m.write_all(b"From 89ab")?; m.write_all(b"From cdef")?; m.write_all(b"From ")?; // No final newline. m.finalize()?; } assert_eq!(&buf[..], &b"- From 0123From 4567\n- From 89abFrom cdefFrom "[..]); Ok(()) } #[test] fn one_line() -> Result<()> { let mut buf = Vec::new(); { let m = Message::new(&mut buf); let mut m = DashEscapeFilter::new(m, Default::default()); m.write_all(b"From 0123")?; m.write_all(b"From 4567")?; m.write_all(b"From 89ab")?; m.write_all(b"From cdef")?; m.write_all(b"From \n")?; m.finalize()?; } assert_eq!(&buf[..], &b"- From 0123From 4567From 89abFrom cdefFrom \n"[..]); let mut buf = Vec::new(); { let m = Message::new(&mut buf); let mut m = DashEscapeFilter::new(m, Default::default()); m.write_all(b"From 0123")?; m.write_all(b"From 4567")?; m.write_all(b"From 89ab")?; m.write_all(b"From cdef")?; m.write_all(b"From ")?; // No final newline. m.finalize()?; } assert_eq!(&buf[..], &b"- From 0123From 4567From 89abFrom cdefFrom "[..]); Ok(()) } } ��������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/src/serialize/stream/padding.rs�����������������������������������������������0000644�0000000�0000000�00000034207�10461020230�0020772�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������//! Padding for OpenPGP messages. //! //! To reduce the amount of information leaked via the message length, //! encrypted OpenPGP messages (see [Section 10.3 of RFC 9580]) should //! be padded. //! //! [Section 10.3 of RFC 9580]: https://www.rfc-editor.org/rfc/rfc9580.html#section-10.3 //! //! To pad a message using the streaming serialization interface, the //! [`Padder`] needs to be inserted into the writing stack between the //! [`Encryptor`] and [`Signer`]. This is illustrated in this //! [example]. //! //! [`Encryptor`]: super::Encryptor //! [`Signer`]: super::Signer //! [example]: Padder#examples //! //! # Padding in OpenPGP //! //! RFC9580 introduced a [padding packet] that will be emitted when //! composing an RFC9580 message. Unfortunately, RFC4880 does not //! have a robust way to pad messages. Therefore, when composing an //! RFC4880 message, the message will not be padded. //! //! [padding packet]: https://www.rfc-editor.org/rfc/rfc9580.html#name-padding-packet-type-id-21 //! //! To be effective, the padding layer must be placed inside the //! encryption container. To increase compatibility, the padding //! layer must not be signed. That is to say, the message structure //! should be `(encryption (ops literal signature padding))`. use std::fmt; use std::io; use crate::{ Profile, Result, packet::prelude::*, }; use crate::serialize::{ Marshal, stream::{ writer, Cookie, Message, Private, }, }; /// Pads a packet stream. /// /// Writes a compressed data packet containing all packets written to /// this writer, and pads it according to the given policy. /// /// The policy is a `Fn(u64) -> u64`, that given the number of bytes /// written to this writer `N`, computes the size the compression /// container should be padded up to. It is an error to return a /// number that is smaller than `N`. /// /// # Compatibility /// /// RFC9580 introduced a [padding packet] that will be emitted when /// composing an RFC9580 message. Unfortunately, RFC4880 does not /// have a robust way to pad messages. Therefore, when composing an /// RFC4880 message, the message will not be padded. /// /// [padding packet]: https://www.rfc-editor.org/rfc/rfc9580.html#name-padding-packet-type-id-21 /// # Examples /// /// This example illustrates the use of `Padder` with the [Padmé] /// policy. Note that for brevity, the encryption and signature /// filters are omitted. /// /// [Padmé]: padme() /// /// ``` /// use std::io::Write; /// use sequoia_openpgp as openpgp; /// use openpgp::serialize::stream::{Message, LiteralWriter}; /// use openpgp::serialize::stream::padding::Padder; /// use openpgp::types::CompressionAlgorithm; /// # fn main() -> sequoia_openpgp::Result<()> { /// /// let mut unpadded = vec![]; /// { /// let message = Message::new(&mut unpadded); /// // XXX: Insert Encryptor here. /// // XXX: Insert Signer here. /// let mut message = LiteralWriter::new(message).build()?; /// message.write_all(b"Hello world.")?; /// message.finalize()?; /// } /// /// let mut padded = vec![]; /// { /// let message = Message::new(&mut padded); /// // XXX: Insert Encryptor here. /// let message = Padder::new(message).build()?; /// // XXX: Insert Signer here. /// let mut message = LiteralWriter::new(message).build()?; /// message.write_all(b"Hello world.")?; /// message.finalize()?; /// } /// # Ok(()) /// # } pub struct Padder<'a, 'p: 'a> { inner: writer::BoxStack<'a, Cookie>, policy: Box<dyn Fn(u64) -> u64 + Send + Sync + 'p>, cookie: Cookie, } assert_send_and_sync!(Padder<'_, '_>); impl<'a, 'p> Padder<'a, 'p> { /// Creates a new padder with the given policy. /// /// # Examples /// /// This example illustrates the use of `Padder` with the [Padmé] /// policy. /// /// [Padmé]: padme() /// /// The most useful filter to push to the writer stack next is the /// [`Signer`] or the [`LiteralWriter`]. Finally, literal data /// *must* be wrapped using the [`LiteralWriter`]. /// /// [`Signer`]: super::Signer /// [`LiteralWriter`]: super::LiteralWriter /// /// ``` /// # fn main() -> sequoia_openpgp::Result<()> { /// use sequoia_openpgp as openpgp; /// use openpgp::serialize::stream::padding::Padder; /// /// # let message = openpgp::serialize::stream::Message::new(vec![]); /// // XXX: Insert Encryptor here. /// let message = Padder::new(message).build()?; /// // XXX: Optionally add a `Signer` here. /// // XXX: Add a `LiteralWriter` here. /// # let _ = message; /// # Ok(()) } /// ``` pub fn new(inner: Message<'a>) -> Self { let level = inner.as_ref().cookie_ref().level; let cookie = Cookie::new(level + 1); Self { inner: writer::BoxStack::from(inner), policy: Box::new(padme), cookie, } } /// Sets padding policy, returning the padder. /// /// # Examples /// /// This example illustrates the use of `Padder` with an explicit policy. /// /// ``` /// # fn main() -> sequoia_openpgp::Result<()> { /// use sequoia_openpgp as openpgp; /// use openpgp::serialize::stream::padding::{Padder, padme}; /// /// # let message = openpgp::serialize::stream::Message::new(vec![]); /// // XXX: Insert Encryptor here. /// let message = Padder::new(message).with_policy(padme).build()?; /// // XXX: Optionally add a `Signer` here. /// // XXX: Add a `LiteralWriter` here. /// # let _ = message; /// # Ok(()) } /// ``` pub fn with_policy<P>(mut self, p: P) -> Self where P: Fn(u64) -> u64 + Send + Sync + 'p, { self.policy = Box::new(p); self } /// Builds the padder, returning the writer stack. /// /// # Examples /// /// This example illustrates the use of `Padder` with the [Padmé] /// policy. /// /// [Padmé]: padme() /// /// The most useful filter to push to the writer stack next is the /// [`Signer`] or the [`LiteralWriter`]. Finally, literal data /// *must* be wrapped using the [`LiteralWriter`]. /// /// [`Signer`]: super::Signer /// [`LiteralWriter`]: super::LiteralWriter /// /// ``` /// # fn main() -> sequoia_openpgp::Result<()> { /// use sequoia_openpgp as openpgp; /// use openpgp::serialize::stream::padding::Padder; /// /// # let message = openpgp::serialize::stream::Message::new(vec![]); /// // XXX: Insert Encryptor here. /// let message = Padder::new(message).build()?; /// // XXX: Optionally add a `Signer` here. /// // XXX: Add a `LiteralWriter` here. /// # let _ = message; /// # Ok(()) } /// ``` pub fn build(self) -> Result<Message<'a>> { Ok(Message::from(Box::new(self))) } } impl<'a, 'p> fmt::Debug for Padder<'a, 'p> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.debug_struct("Padder") .field("inner", &self.inner) .field("cookie", &self.cookie) .finish() } } impl<'a, 'p> io::Write for Padder<'a, 'p> { fn write(&mut self, buf: &[u8]) -> io::Result<usize> { self.inner.write(buf) } fn flush(&mut self) -> io::Result<()> { self.inner.flush() } } impl<'a, 'p> writer::Stackable<'a, Cookie> for Padder<'a, 'p> { fn into_inner(mut self: Box<Self>) -> Result<Option<writer::BoxStack<'a, Cookie>>> { let enabled = writer::map( self.as_ref(), |w| match w.cookie_ref().private { Private::Encryptor { profile, .. } => Some(profile == Profile::RFC9580), _ => None, }) .unwrap_or(false); if enabled { // Make a note of the amount of data written to this // filter. let size = self.position(); // Compute the amount of padding required according to the // given policy. let padded_size = (self.policy)(size); if padded_size < size { return Err(crate::Error::InvalidOperation( format!("Padding policy({}) returned {}: \ smaller than argument", size, padded_size)).into()); } let amount = padded_size - size; // Write 'amount' of padding. Packet::from(Padding::new(amount.try_into() .unwrap_or(usize::MAX))?) .serialize(&mut self)?; } Ok(Some(self.inner)) } fn pop(&mut self) -> Result<Option<writer::BoxStack<'a, Cookie>>> { unreachable!("Only implemented by Signer") } /// Sets the inner stackable. fn mount(&mut self, _new: writer::BoxStack<'a, Cookie>) { unreachable!("Only implemented by Signer") } fn inner_ref(&self) -> Option<&(dyn writer::Stackable<'a, Cookie> + Send + Sync)> { Some(self.inner.as_ref()) } fn inner_mut(&mut self) -> Option<&mut (dyn writer::Stackable<'a, Cookie> + Send + Sync)> { Some(self.inner.as_mut()) } fn cookie_set(&mut self, cookie: Cookie) -> Cookie { std::mem::replace(&mut self.cookie, cookie) } fn cookie_ref(&self) -> &Cookie { &self.cookie } fn cookie_mut(&mut self) -> &mut Cookie { &mut self.cookie } fn position(&self) -> u64 { self.inner.position() } } /// Padmé padding scheme. /// /// Padmé leaks at most O(log log M) bits of information (with M being /// the maximum length of all messages) with an overhead of at most /// 12%, decreasing with message size. /// /// This scheme leaks the same order of information as padding to the /// next power of two, while avoiding an overhead of up to 100%. /// /// See Section 4 of [Reducing Metadata Leakage from Encrypted Files /// and Communication with /// PURBs](https://bford.info/pub/sec/purb.pdf). /// /// This function is meant to be used with [`Padder`], see this /// [example]. /// /// [example]: Padder#examples pub fn padme(l: u64) -> u64 { if l < 2 { return 1; // Avoid cornercase. } let e = log2(l); // l's floating-point exponent let s = log2(e as u64) + 1; // # of bits to represent e let z = e - s; // # of low bits to set to 0 let m = (1 << z) - 1; // mask of z 1's in LSB (l + (m as u64)) & !(m as u64) // round up using mask m to clear last z bits } /// Compute the log2 of an integer. (This is simply the most /// significant bit.) Note: log2(0) = -Inf, but this function returns /// log2(0) as 0 (which is the closest number that we can represent). fn log2(x: u64) -> usize { if x == 0 { 0 } else { 63 - x.leading_zeros() as usize } } #[cfg(test)] mod test { use super::*; #[test] fn log2_test() { for i in 0..64 { assert_eq!(log2(1u64 << i), i); if i > 0 { assert_eq!(log2((1u64 << i) - 1), i - 1); assert_eq!(log2((1u64 << i) + 1), i); } } } fn padme_multiplicative_overhead(p: u64) -> f32 { let c = padme(p); let (p, c) = (p as f32, c as f32); (c - p) / p } /// Experimentally, we observe the maximum overhead to be ~11.63% /// when padding 129 bytes to 144. const MAX_OVERHEAD: f32 = 0.1163; #[test] fn padme_max_overhead() { // The paper says the maximum multiplicative overhead is // 11.(11)% when padding 9 bytes to 10. assert!(0.111 < padme_multiplicative_overhead(9)); assert!(padme_multiplicative_overhead(9) < 0.112); // Contrary to that, we observe the maximum overhead to be // ~11.63% when padding 129 bytes to 144. assert!(padme_multiplicative_overhead(129) < MAX_OVERHEAD); } quickcheck! { fn padme_overhead(l: u32) -> bool { if l < 2 { return true; // Avoid cornercase. } let o = padme_multiplicative_overhead(l as u64); let l_ = l as f32; let e = l_.log2().floor(); // l's floating-point exponent let s = e.log2().floor() + 1.; // # of bits to represent e let max_overhead = (2.0_f32.powf(e-s) - 1.) / l_; assert!(o < MAX_OVERHEAD, "padme({}) => {}: overhead {} exceeds maximum overhead {}", l, padme(l.into()), o, MAX_OVERHEAD); assert!(o <= max_overhead, "padme({}) => {}: overhead {} exceeds maximum overhead {}", l, padme(l.into()), o, max_overhead); true } } /// Asserts that we can consume the padded messages. #[test] fn roundtrip() { use std::io::Write; use crate::parse::Parse; use crate::serialize::stream::*; let mut msg = vec![0; rand::random::<usize>() % 1024]; crate::crypto::random(&mut msg).unwrap(); let mut padded = vec![]; { let message = Message::new(&mut padded); let padder = Padder::new(message).with_policy(padme).build().unwrap(); let mut w = LiteralWriter::new(padder).build().unwrap(); w.write_all(&msg).unwrap(); w.finalize().unwrap(); } let m = crate::Message::from_bytes(&padded).unwrap(); assert_eq!(m.body().unwrap().body(), &msg[..]); } /// Asserts that no actual compression is done. /// /// We want to avoid having the size of the data stream depend on /// the data's compressibility, therefore it is best to disable /// the compression. #[test] fn no_compression() { use std::io::Write; use crate::serialize::stream::*; const MSG: &[u8] = b"@@@@@@@@@@@@@@"; let mut padded = vec![]; { let message = Message::new(&mut padded); let padder = Padder::new(message).build().unwrap(); let mut w = LiteralWriter::new(padder).build().unwrap(); w.write_all(MSG).unwrap(); w.finalize().unwrap(); } assert!(padded.windows(MSG.len()).any(|ch| ch == MSG), "Could not find uncompressed message"); } } �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/src/serialize/stream/partial_body.rs������������������������������������������0000644�0000000�0000000�00000023242�10461020230�0022032�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������//! Encodes a byte stream using OpenPGP's partial body encoding. use std::fmt; use std::io; use std::cmp; use crate::Error; use crate::Result; use crate::packet::header::BodyLength; use crate::serialize::{ log2, stream::{ writer, Message, Cookie, }, write_byte, Marshal, }; pub struct PartialBodyFilter<'a, C: 'a> { // The underlying writer. // // XXX: Opportunity for optimization. Previously, this writer // implemented `Drop`, so we could not move the inner writer out // of this writer. We therefore wrapped it with `Option` so that // we can `take()` it. This writer no longer implements Drop, so // we could avoid the Option here. inner: Option<writer::BoxStack<'a, C>>, // The cookie. cookie: C, // The buffer. buffer: Vec<u8>, // The amount to buffer before flushing. buffer_threshold: usize, // The maximum size of a partial body chunk. The standard allows // for chunks up to 1 GB in size. max_chunk_size: usize, // The number of bytes written to this filter. position: u64, } assert_send_and_sync!(PartialBodyFilter<'_, C> where C); const PARTIAL_BODY_FILTER_MAX_CHUNK_SIZE : usize = 1 << 30; // The amount to buffer before flushing. If this is small, we get // lots of small partial body packets, which is annoying. const PARTIAL_BODY_FILTER_BUFFER_THRESHOLD : usize = 4 * 1024 * 1024; impl<'a> PartialBodyFilter<'a, Cookie> { /// Returns a new partial body encoder. pub fn new(inner: Message<'a>, cookie: Cookie) -> Message<'a> { Self::with_limits(inner, cookie, PARTIAL_BODY_FILTER_BUFFER_THRESHOLD, PARTIAL_BODY_FILTER_MAX_CHUNK_SIZE) .expect("safe limits") } /// Returns a new partial body encoder with the given limits. pub fn with_limits(inner: Message<'a>, cookie: Cookie, buffer_threshold: usize, max_chunk_size: usize) -> Result<Message<'a>> { if buffer_threshold.count_ones() != 1 { return Err(Error::InvalidArgument( "buffer_threshold is not a power of two".into()).into()); } if max_chunk_size.count_ones() != 1 { return Err(Error::InvalidArgument( "max_chunk_size is not a power of two".into()).into()); } if max_chunk_size > PARTIAL_BODY_FILTER_MAX_CHUNK_SIZE { return Err(Error::InvalidArgument( "max_chunk_size exceeds limit".into()).into()); } Ok(Message::from(Box::new(PartialBodyFilter { inner: Some(inner.into()), cookie, buffer: Vec::with_capacity(buffer_threshold), buffer_threshold, max_chunk_size, position: 0, }))) } } impl<'a, C: 'a> PartialBodyFilter<'a, C> { // Writes out any full chunks between `self.buffer` and `other`. // Any extra data is buffered. // // If `done` is set, then flushes any data, and writes the end of // the partial body encoding. fn write_out(&mut self, mut other: &[u8], done: bool) -> io::Result<()> { if self.inner.is_none() { return Ok(()); } let mut inner = self.inner.as_mut().unwrap(); if done { // We're done. The last header MUST be a non-partial body // header. We have to write it even if it is 0 bytes // long. // Write the header. let l = self.buffer.len() + other.len(); if l > std::u32::MAX as usize { unimplemented!(); } BodyLength::Full(l as u32).serialize(inner).map_err( |e| match e.downcast::<io::Error>() { // An io::Error. Pass as-is. Ok(err) => err, // A failure. Wrap it. Err(e) => io::Error::new(io::ErrorKind::Other, e), })?; // Write the body. inner.write_all(&self.buffer[..])?; crate::vec_truncate(&mut self.buffer, 0); inner.write_all(other)?; } else { while self.buffer.len() + other.len() > self.buffer_threshold { // Write a partial body length header. let chunk_size_log2 = log2(cmp::min(self.max_chunk_size, self.buffer.len() + other.len()) as u32); let chunk_size = (1usize) << chunk_size_log2; let size = BodyLength::Partial(chunk_size as u32); let mut size_byte = [0u8]; size.serialize(&mut io::Cursor::new(&mut size_byte[..])) .expect("size should be representable"); let size_byte = size_byte[0]; // Write out the chunk... write_byte(&mut inner, size_byte)?; // ... from our buffer first... let l = cmp::min(self.buffer.len(), chunk_size); inner.write_all(&self.buffer[..l])?; crate::vec_drain_prefix(&mut self.buffer, l); // ... then from other. if chunk_size > l { inner.write_all(&other[..chunk_size - l])?; other = &other[chunk_size - l..]; } } self.buffer.extend_from_slice(other); assert!(self.buffer.len() <= self.buffer_threshold); } Ok(()) } } impl<'a, C: 'a> io::Write for PartialBodyFilter<'a, C> { fn write(&mut self, buf: &[u8]) -> io::Result<usize> { // If we can write out a chunk, avoid an extra copy. if buf.len() >= self.buffer_threshold - self.buffer.len() { self.write_out(buf, false)?; } else { self.buffer.append(buf.to_vec().as_mut()); } self.position += buf.len() as u64; Ok(buf.len()) } // XXX: The API says that `flush` is supposed to flush any // internal buffers to disk. We don't do that. fn flush(&mut self) -> io::Result<()> { self.write_out(&b""[..], false) } } impl<'a, C: 'a> fmt::Debug for PartialBodyFilter<'a, C> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.debug_struct("PartialBodyFilter") .field("inner", &self.inner) .finish() } } impl<'a, C: 'a> writer::Stackable<'a, C> for PartialBodyFilter<'a, C> { fn into_inner(mut self: Box<Self>) -> Result<Option<writer::BoxStack<'a, C>>> { self.write_out(&b""[..], true)?; Ok(self.inner.take()) } fn pop(&mut self) -> Result<Option<writer::BoxStack<'a, C>>> { self.write_out(&b""[..], true)?; Ok(self.inner.take()) } fn mount(&mut self, new: writer::BoxStack<'a, C>) { self.inner = Some(new); } fn inner_mut(&mut self) -> Option<&mut (dyn writer::Stackable<'a, C> + Send + Sync)> { if let Some(ref mut i) = self.inner { Some(i) } else { None } } fn inner_ref(&self) -> Option<&(dyn writer::Stackable<'a, C> + Send + Sync)> { if let Some(ref i) = self.inner { Some(i) } else { None } } fn cookie_set(&mut self, cookie: C) -> C { ::std::mem::replace(&mut self.cookie, cookie) } fn cookie_ref(&self) -> &C { &self.cookie } fn cookie_mut(&mut self) -> &mut C { &mut self.cookie } fn position(&self) -> u64 { self.position } } #[cfg(test)] mod test { use std::io::Write; use super::*; use crate::serialize::stream::Message; #[test] fn basic() { let mut buf = Vec::new(); { let message = Message::new(&mut buf); let mut pb = PartialBodyFilter::with_limits( message, Default::default(), /* buffer_threshold: */ 16, /* max_chunk_size: */ 16) .unwrap(); pb.write_all(b"0123").unwrap(); pb.write_all(b"4567").unwrap(); pb.finalize().unwrap(); } assert_eq!(&buf, &[8, // no chunking 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37]); } #[test] fn no_avoidable_chunking() { let mut buf = Vec::new(); { let message = Message::new(&mut buf); let mut pb = PartialBodyFilter::with_limits( message, Default::default(), /* buffer_threshold: */ 4, /* max_chunk_size: */ 16) .unwrap(); pb.write_all(b"01234567").unwrap(); pb.finalize().unwrap(); } assert_eq!(&buf, &[0xe0 + 3, // first chunk 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0, // rest ]); } #[test] fn write_exceeding_buffer_threshold() { let mut buf = Vec::new(); { let message = Message::new(&mut buf); let mut pb = PartialBodyFilter::with_limits( message, Default::default(), /* buffer_threshold: */ 8, /* max_chunk_size: */ 16) .unwrap(); pb.write_all(b"012345670123456701234567").unwrap(); pb.finalize().unwrap(); } assert_eq!(&buf, &[0xe0 + 4, // first chunk 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 8, // rest 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37]); } } ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/src/serialize/stream/trim_whitespace.rs���������������������������������������0000644�0000000�0000000�00000022703�10461020230�0022551�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������//! Trims trailing whitespace. //! //! This filter is used to generate messages using the Cleartext //! Signature Framework (see [Section 7.2 of RFC 9580]). //! //! [Section 7.2 of RFC 9580]: https://www.rfc-editor.org/rfc/rfc9580.html#section-7.2 use std::fmt; use std::io; use crate::{ Error, Result, serialize::stream::{ writer, Message, Cookie, }, }; pub(super) struct TrailingWSFilter<'a, C: 'a> { // The underlying writer. inner: writer::BoxStack<'a, C>, // The cookie. cookie: C, // The buffer. buffer: Vec<u8>, // The number of bytes written to this filter. position: u64, } assert_send_and_sync!(TrailingWSFilter<'_, C> where C); impl<'a> TrailingWSFilter<'a, Cookie> { /// Returns a new filter trimming spaces and tabs from lines. pub fn new(inner: Message<'a>, cookie: Cookie) -> Message<'a> { Message::from(Box::new(TrailingWSFilter { inner: inner.into(), cookie, buffer: Vec::new(), position: 0, })) } } impl<'a, C: 'a> TrailingWSFilter<'a, C> { /// Writes out any complete lines between `self.buffer` and `other`. /// /// Any extra data is buffered. /// /// If `done` is set, then flushes any data. fn write_out(&mut self, other: &[u8], done: bool) -> io::Result<()> { // XXX: Currently, we don't mind copying the data. This // could be optimized. self.buffer.extend_from_slice(other); // Write out all whole lines (i.e. those terminated by a // newline). This is a bit awkward, because we only know that // a line was whole when we are looking at the next line. let mut last_line: Option<&[u8]> = None; for line in self.buffer.split(|b| *b == b'\n') { if let Some(mut l) = last_line.take() { let crlf_line_end = l.ends_with(b"\r"); if crlf_line_end { l = &l[..l.len() - 1]; } // Trim trailing whitespace according to Section 7.1 // of RFC4880, i.e. "spaces (0x20) and tabs (0x09)". while Some(&b' ') == l.last() || Some(&b'\t') == l.last() { l = &l[..l.len() - 1]; } self.inner.write_all(l)?; if crlf_line_end { self.inner.write_all(b"\r\n")?; } else { self.inner.write_all(b"\n")?; } } last_line = Some(line); } if done { if let Some(mut l) = last_line { // Flush the last line. while Some(&b' ') == l.last() || Some(&b'\t') == l.last() { l = &l[..l.len() - 1]; } self.inner.write_all(l)?; } } let new_buffer = last_line.map(|l| l.to_vec()) .unwrap_or_else(Vec::new); crate::vec_truncate(&mut self.buffer, 0); self.buffer = new_buffer; Ok(()) } } impl<'a, C: 'a> io::Write for TrailingWSFilter<'a, C> { fn write(&mut self, buf: &[u8]) -> io::Result<usize> { self.write_out(buf, false)?; self.position += buf.len() as u64; Ok(buf.len()) } // XXX: The API says that `flush` is supposed to flush any // internal buffers to disk. We don't do that. fn flush(&mut self) -> io::Result<()> { Ok(()) } } impl<'a, C: 'a> fmt::Debug for TrailingWSFilter<'a, C> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.debug_struct("TrailingWSFilter") .field("inner", &self.inner) .finish() } } impl<'a, C: 'a> writer::Stackable<'a, C> for TrailingWSFilter<'a, C> { fn into_inner(mut self: Box<Self>) -> Result<Option<writer::BoxStack<'a, C>>> { self.write_out(&b""[..], true)?; Ok(Some(self.inner)) } fn pop(&mut self) -> Result<Option<writer::BoxStack<'a, C>>> { Err(Error::InvalidOperation( "Cannot pop TrailingWSFilter".into()).into()) } fn mount(&mut self, new: writer::BoxStack<'a, C>) { self.inner = new; } fn inner_mut(&mut self) -> Option<&mut (dyn writer::Stackable<'a, C> + Send + Sync)> { Some(&mut self.inner) } fn inner_ref(&self) -> Option<&(dyn writer::Stackable<'a, C> + Send + Sync)> { Some(&self.inner) } fn cookie_set(&mut self, cookie: C) -> C { ::std::mem::replace(&mut self.cookie, cookie) } fn cookie_ref(&self) -> &C { &self.cookie } fn cookie_mut(&mut self) -> &mut C { &mut self.cookie } fn position(&self) -> u64 { self.position } } #[cfg(test)] mod test { use std::io::Write; use super::*; use crate::serialize::stream::Message; #[test] fn no_trimming() -> Result<()> { let mut buf = Vec::new(); { let m = Message::new(&mut buf); let mut m = TrailingWSFilter::new(m, Default::default()); m.write_all(b"0123")?; m.write_all(b"4567\n")?; m.write_all(b"89ab")?; m.write_all(b"cdef")?; m.write_all(b"\n")?; m.finalize()?; } assert_eq!(&buf[..], &b"01234567\n89abcdef\n"[..]); let mut buf = Vec::new(); { let m = Message::new(&mut buf); let mut m = TrailingWSFilter::new(m, Default::default()); m.write_all(b"0123")?; m.write_all(b"4567\n")?; m.write_all(b"89ab")?; m.write_all(b"cdef")?; // No final newline. m.finalize()?; } assert_eq!(&buf[..], &b"01234567\n89abcdef"[..]); Ok(()) } #[test] fn space_trimming() -> Result<()> { let mut buf = Vec::new(); { let m = Message::new(&mut buf); let mut m = TrailingWSFilter::new(m, Default::default()); m.write_all(b"0123 ")?; m.write_all(b"4567 \n")?; m.write_all(b"89ab ")?; m.write_all(b"cdef ")?; m.write_all(b"\n")?; m.finalize()?; } assert_eq!(&buf[..], &b"0123 4567\n89ab cdef\n"[..]); let mut buf = Vec::new(); { let m = Message::new(&mut buf); let mut m = TrailingWSFilter::new(m, Default::default()); m.write_all(b"0123 ")?; m.write_all(b"4567 \n")?; m.write_all(b"89ab ")?; m.write_all(b"cdef ")?; // No final newline. m.finalize()?; } assert_eq!(&buf[..], &b"0123 4567\n89ab cdef"[..]); Ok(()) } #[test] fn tab_trimming() -> Result<()> { let mut buf = Vec::new(); { let m = Message::new(&mut buf); let mut m = TrailingWSFilter::new(m, Default::default()); m.write_all(b"0123\t")?; m.write_all(b"4567\t\n")?; m.write_all(b"89ab\t")?; m.write_all(b"cdef\t")?; m.write_all(b"\n")?; m.finalize()?; } assert_eq!(&buf[..], &b"0123\t4567\n89ab\tcdef\n"[..]); let mut buf = Vec::new(); { let m = Message::new(&mut buf); let mut m = TrailingWSFilter::new(m, Default::default()); m.write_all(b"0123\t")?; m.write_all(b"4567\t\n")?; m.write_all(b"89ab\t")?; m.write_all(b"cdef\t")?; // No final newline. m.finalize()?; } assert_eq!(&buf[..], &b"0123\t4567\n89ab\tcdef"[..]); Ok(()) } #[test] fn no_ff_trimming() -> Result<()> { let mut buf = Vec::new(); { let m = Message::new(&mut buf); let mut m = TrailingWSFilter::new(m, Default::default()); m.write_all(b"0123\x0c")?; m.write_all(b"4567\x0c\n")?; m.write_all(b"89ab\x0c")?; m.write_all(b"cdef\x0c")?; m.write_all(b"\n")?; m.finalize()?; } assert_eq!(&buf[..], &b"0123\x0c4567\x0c\n89ab\x0ccdef\x0c\n"[..]); let mut buf = Vec::new(); { let m = Message::new(&mut buf); let mut m = TrailingWSFilter::new(m, Default::default()); m.write_all(b"0123\x0c")?; m.write_all(b"4567\x0c\n")?; m.write_all(b"89ab\x0c")?; m.write_all(b"cdef\x0c")?; // No final newline. m.finalize()?; } assert_eq!(&buf[..], &b"0123\x0c4567\x0c\n89ab\x0ccdef\x0c"[..]); Ok(()) } #[test] fn one_line() -> Result<()> { let mut buf = Vec::new(); { let m = Message::new(&mut buf); let mut m = TrailingWSFilter::new(m, Default::default()); m.write_all(b"0123 ")?; m.write_all(b"4567 ")?; m.write_all(b"89ab ")?; m.write_all(b"cdef ")?; m.write_all(b"\n")?; m.finalize()?; } assert_eq!(&buf[..], &b"0123 4567 89ab cdef\n"[..]); let mut buf = Vec::new(); { let m = Message::new(&mut buf); let mut m = TrailingWSFilter::new(m, Default::default()); m.write_all(b"0123 ")?; m.write_all(b"4567 ")?; m.write_all(b"89ab ")?; m.write_all(b"cdef ")?; // No final newline. m.finalize()?; } assert_eq!(&buf[..], &b"0123 4567 89ab cdef"[..]); Ok(()) } } �������������������������������������������������������������sequoia-openpgp-2.0.0/src/serialize/stream/writer/mod.rs��������������������������������������������0000644�0000000�0000000�00000045577�10461020230�0021473�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������//! Stackable writers. #[cfg(feature = "compression-bzip2")] mod writer_bzip2; #[cfg(feature = "compression-bzip2")] pub use self::writer_bzip2::BZ; #[cfg(feature = "compression-deflate")] mod writer_deflate; #[cfg(feature = "compression-deflate")] pub use self::writer_deflate::{ZIP, ZLIB}; use std::fmt; use std::io; use crate::armor; use crate::crypto::{aead, symmetric}; use crate::types::{ AEADAlgorithm, SymmetricAlgorithm, }; use crate::{ Profile, Result, crypto::SessionKey, }; use super::{Message, Cookie}; impl<'a> Message<'a> { pub(super) fn from(bs: BoxStack<'a, Cookie>) -> Self { Message(bs) } pub(super) fn as_ref(&self) -> &BoxStack<'a, Cookie> { &self.0 } pub(super) fn as_mut(&mut self) -> &mut BoxStack<'a, Cookie> { &mut self.0 } } impl<'a> io::Write for Message<'a> { fn write(&mut self, buf: &[u8]) -> io::Result<usize> { self.0.write(buf) } fn flush(&mut self) -> io::Result<()> { self.0.flush() } } impl<'a> From<Message<'a>> for BoxStack<'a, Cookie> { fn from(s: Message<'a>) -> Self { s.0 } } pub(crate) type BoxStack<'a, C> = Box<dyn Stackable<'a, C> + Send + Sync + 'a>; /// Makes a writer stackable and provides convenience functions. pub(crate) trait Stackable<'a, C> : io::Write + fmt::Debug { /// Recovers the inner stackable. /// /// This can fail if the current `Stackable` has buffered data /// that hasn't been written to the underlying `Stackable`. fn into_inner(self: Box<Self>) -> Result<Option<BoxStack<'a, C>>>; /// Pops the stackable from the stack, detaching it. /// /// Returns the detached stack. /// /// Note: Only the Signer implements this interface. fn pop(&mut self) -> Result<Option<BoxStack<'a, C>>>; /// Sets the inner stackable. /// /// Note: Only the Signer implements this interface. fn mount(&mut self, new: BoxStack<'a, C>); /// Returns a mutable reference to the inner `Writer`, if /// any. /// /// It is a very bad idea to write any data from the inner /// `Writer`, but it can sometimes be useful to get the cookie. fn inner_mut(&mut self) -> Option<&mut (dyn Stackable<'a, C> + Send + Sync)>; /// Returns a reference to the inner `Writer`. fn inner_ref(&self) -> Option<&(dyn Stackable<'a, C> + Send + Sync)>; /// Sets the cookie and returns the old value. #[allow(dead_code)] fn cookie_set(&mut self, cookie: C) -> C; /// Returns a reference to the cookie. fn cookie_ref(&self) -> &C; /// Returns a mutable reference to the cookie. #[allow(dead_code)] fn cookie_mut(&mut self) -> &mut C; /// Returns the number of bytes written to this filter. fn position(&self) -> u64; /// Writes a byte. fn write_u8(&mut self, b: u8) -> io::Result<()> { self.write_all(&[b]) } } /// Make a `Box<Stackable>` look like a Stackable. impl <'a, C> Stackable<'a, C> for BoxStack<'a, C> { fn into_inner(self: Box<Self>) -> Result<Option<BoxStack<'a, C>>> { (*self).into_inner() } /// Recovers the inner stackable. fn pop(&mut self) -> Result<Option<BoxStack<'a, C>>> { self.as_mut().pop() } /// Sets the inner stackable. fn mount(&mut self, new: BoxStack<'a, C>) { self.as_mut().mount(new); } fn inner_mut(&mut self) -> Option<&mut (dyn Stackable<'a, C> + Send + Sync)> { self.as_mut().inner_mut() } fn inner_ref(&self) -> Option<&(dyn Stackable<'a, C> + Send + Sync)> { self.as_ref().inner_ref() } fn cookie_set(&mut self, cookie: C) -> C { self.as_mut().cookie_set(cookie) } fn cookie_ref(&self) -> &C { self.as_ref().cookie_ref() } fn cookie_mut(&mut self) -> &mut C { self.as_mut().cookie_mut() } fn position(&self) -> u64 { self.as_ref().position() } } /// Maps a function over the stack of writers. #[allow(dead_code)] pub(crate) fn map<C, F, T>(head: &(dyn Stackable<C> + Send + Sync), mut fun: F) -> Option<T> where F: FnMut(&(dyn Stackable<C> + Send + Sync)) -> Option<T>, { let mut ow = Some(head); while let Some(w) = ow { if let Some(r) = fun(w) { return Some(r); } ow = w.inner_ref() } None } /// Maps a function over the stack of mutable writers. #[allow(dead_code)] pub(crate) fn map_mut<C, F, T>(head: &mut (dyn Stackable<C> + Send + Sync), mut fun: F) -> Option<T> where F: FnMut(&mut (dyn Stackable<C> + Send + Sync)) -> Option<T> { let mut ow = Some(head); while let Some(w) = ow { if let Some(r) = fun(w) { return Some(r); } ow = w.inner_mut() } None } /// Dumps the writer stack. #[allow(dead_code)] pub(crate) fn dump<C>(head: &(dyn Stackable<C> + Send + Sync)) { let mut depth = 0; map(head, |w| { eprintln!("{}: {:?}", depth, w); depth += 1; Some(()) }); } /// The identity writer just relays anything written. pub struct Identity<'a, C> { inner: Option<BoxStack<'a, C>>, cookie: C, } assert_send_and_sync!(Identity<'_, C> where C); impl<'a> Identity<'a, Cookie> { /// Makes an identity writer. pub fn new(inner: Message<'a>, cookie: Cookie) -> Message<'a> { Message::from(Box::new(Self{inner: Some(inner.into()), cookie })) } } impl<'a, C> fmt::Debug for Identity<'a, C> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.debug_struct("Identity") .field("inner", &self.inner) .finish() } } impl<'a, C> io::Write for Identity<'a, C> { fn write(&mut self, buf: &[u8]) -> io::Result<usize> { let writer = self.inner.as_mut() .ok_or_else(|| io::Error::new(io::ErrorKind::BrokenPipe, "Writer is finalized."))?; writer.write(buf) } fn flush(&mut self) -> io::Result<()> { let writer = self.inner.as_mut() .ok_or_else(|| io::Error::new(io::ErrorKind::BrokenPipe, "Writer is finalized."))?; writer.flush() } } impl<'a, C> Stackable<'a, C> for Identity<'a, C> { /// Recovers the inner stackable. fn into_inner(self: Box<Self>) -> Result<Option<BoxStack<'a, C>>> { Ok(self.inner) } /// Recovers the inner stackable. fn pop(&mut self) -> Result<Option<BoxStack<'a, C>>> { Ok(self.inner.take()) } /// Sets the inner stackable. fn mount(&mut self, new: BoxStack<'a, C>) { self.inner = Some(new); } fn inner_ref(&self) -> Option<&(dyn Stackable<'a, C> + Send + Sync)> { if let Some(ref i) = self.inner { Some(i) } else { None } } fn inner_mut(&mut self) -> Option<&mut (dyn Stackable<'a, C> + Send + Sync)> { if let Some(ref mut i) = self.inner { Some(i) } else { None } } fn cookie_set(&mut self, cookie: C) -> C { ::std::mem::replace(&mut self.cookie, cookie) } fn cookie_ref(&self) -> &C { &self.cookie } fn cookie_mut(&mut self) -> &mut C { &mut self.cookie } fn position(&self) -> u64 { self.inner.as_ref().map(|i| i.position()).unwrap_or(0) } } /// Generic writer wrapping `io::Write`. pub struct Generic<W: io::Write + Send + Sync, C> { inner: W, cookie: C, position: u64, } assert_send_and_sync!(Generic<W, C> where W: io::Write, C); impl<'a, W: 'a + io::Write + Send + Sync> Generic<W, Cookie> { /// Wraps an `io::Write`r. pub fn new(inner: W, cookie: Cookie) -> Message<'a> { Message::from(Box::new(Self::new_unboxed(inner, cookie))) } fn new_unboxed(inner: W, cookie: Cookie) -> Self { Generic { inner, cookie, position: 0, } } } impl<W: io::Write + Send + Sync, C> fmt::Debug for Generic<W, C> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.debug_struct("writer::Generic") .finish() } } impl<W: io::Write + Send + Sync, C> io::Write for Generic<W, C> { fn write(&mut self, bytes: &[u8]) -> io::Result<usize> { match self.inner.write(bytes) { Ok(n) => { self.position += n as u64; Ok(n) }, Err(e) => Err(e), } } fn flush(&mut self) -> io::Result<()> { self.inner.flush() } } impl<'a, W: io::Write + Send + Sync, C> Stackable<'a, C> for Generic<W, C> { /// Recovers the inner stackable. fn into_inner(self: Box<Self>) -> Result<Option<BoxStack<'a, C>>> { Ok(None) } /// Recovers the inner stackable. fn pop(&mut self) -> Result<Option<BoxStack<'a, C>>> { Ok(None) } /// Sets the inner stackable. fn mount(&mut self, _new: BoxStack<'a, C>) { } fn inner_mut(&mut self) -> Option<&mut (dyn Stackable<'a, C> + Send + Sync)> { // If you use Generic to wrap an io::Writer, and you know that // the io::Writer's inner is also a Stackable, then return a // reference to the innermost Stackable in your // implementation. See e.g. writer::ZLIB. None } fn inner_ref(&self) -> Option<&(dyn Stackable<'a, C> + Send + Sync)> { // If you use Generic to wrap an io::Writer, and you know that // the io::Writer's inner is also a Stackable, then return a // reference to the innermost Stackable in your // implementation. See e.g. writer::ZLIB. None } fn cookie_set(&mut self, cookie: C) -> C { ::std::mem::replace(&mut self.cookie, cookie) } fn cookie_ref(&self) -> &C { &self.cookie } fn cookie_mut(&mut self) -> &mut C { &mut self.cookie } fn position(&self) -> u64 { self.position } } /// Armoring writer. pub struct Armorer<'a, C: 'a> { inner: Generic<armor::Writer<BoxStack<'a, C>>, C>, } assert_send_and_sync!(Armorer<'_, C> where C); impl<'a> Armorer<'a, Cookie> { /// Makes an armoring writer. pub fn new<I, K, V>(inner: Message<'a>, cookie: Cookie, kind: armor::Kind, headers: I) -> Result<Message<'a>> where I: IntoIterator<Item = (K, V)>, K: AsRef<str>, V: AsRef<str>, { Ok(Message::from(Box::new(Armorer { inner: Generic::new_unboxed( armor::Writer::with_headers(inner.into(), kind, headers)?, cookie), }))) } /// Sets the version of OpenPGP to generate ASCII Armor for. /// /// Walks up the writer stack, looking for the next Armorer. /// Configure it to use the given profile. /// /// This function can only be called once. Calling it repeatedly /// does nothing. pub fn set_profile(stack: &mut (dyn Stackable<'a, Cookie> + Send + Sync), profile: Profile) { map_mut(stack, |w| match &mut w.cookie_mut().private { super::Private::Armorer { set_profile, .. } => { *set_profile = Some(profile); Some(()) }, _ => None, // Keep looking. }); } } impl<'a, C: 'a> fmt::Debug for Armorer<'a, C> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.debug_struct("writer::Armorer") .field("inner", &self.inner) .finish() } } impl<'a> io::Write for Armorer<'a, Cookie> { fn write(&mut self, bytes: &[u8]) -> io::Result<usize> { if bytes.len() == 0 { return Ok(0); } // Lazily configure the armor writer. if let Some(p) = match &mut self.cookie_mut().private { super::Private::Armorer { set_profile } => set_profile.take(), _ => None, } { let _ = self.inner.inner.set_profile(p); } self.inner.write(bytes) } fn flush(&mut self) -> io::Result<()> { self.inner.flush() } } impl<'a> Stackable<'a, Cookie> for Armorer<'a, Cookie> { fn into_inner(self: Box<Self>) -> Result<Option<BoxStack<'a, Cookie>>> { let inner = self.inner.inner.finalize()?; Ok(Some(inner)) } fn pop(&mut self) -> Result<Option<BoxStack<'a, Cookie>>> { unreachable!("Only implemented by Signer") } fn mount(&mut self, _new: BoxStack<'a, Cookie>) { unreachable!("Only implemented by Signer") } fn inner_mut(&mut self) -> Option<&mut (dyn Stackable<'a, Cookie> + Send + Sync)> { Some(self.inner.inner.get_mut().as_mut()) } fn inner_ref(&self) -> Option<&(dyn Stackable<'a, Cookie> + Send + Sync)> { Some(self.inner.inner.get_ref().as_ref()) } fn cookie_set(&mut self, cookie: Cookie) -> Cookie { self.inner.cookie_set(cookie) } fn cookie_ref(&self) -> &Cookie { self.inner.cookie_ref() } fn cookie_mut(&mut self) -> &mut Cookie { self.inner.cookie_mut() } fn position(&self) -> u64 { self.inner.position } } /// Encrypting writer. pub struct Encryptor<'a, C: 'a> { inner: Generic<symmetric::Encryptor<Box<dyn Stackable<'a, C> + Send + Sync + 'a>>, C>, } assert_send_and_sync!(Encryptor<'_, C> where C); impl<'a> Encryptor<'a, Cookie> { /// Makes an encrypting writer. pub fn new(inner: Message<'a>, cookie: Cookie, algo: SymmetricAlgorithm, key: &[u8]) -> Result<Message<'a>> { Ok(Message::from(Box::new(Encryptor { inner: Generic::new_unboxed( symmetric::Encryptor::new(algo, key, inner.into())?, cookie), }))) } } impl<'a, C: 'a> fmt::Debug for Encryptor<'a, C> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.debug_struct("writer::Encryptor") .field("inner", &self.inner) .finish() } } impl<'a, C: 'a> io::Write for Encryptor<'a, C> { fn write(&mut self, bytes: &[u8]) -> io::Result<usize> { self.inner.write(bytes) } fn flush(&mut self) -> io::Result<()> { self.inner.flush() } } impl<'a, C: 'a> Stackable<'a, C> for Encryptor<'a, C> { fn into_inner(mut self: Box<Self>) -> Result<Option<BoxStack<'a, C>>> { let inner = self.inner.inner.finish()?; Ok(Some(inner)) } fn pop(&mut self) -> Result<Option<BoxStack<'a, C>>> { unreachable!("Only implemented by Signer") } fn mount(&mut self, _new: BoxStack<'a, C>) { unreachable!("Only implemented by Signer") } fn inner_mut(&mut self) -> Option<&mut (dyn Stackable<'a, C> + Send + Sync)> { // XXX: Unfortunately, this doesn't work due to a lifetime mismatch: // self.inner.inner.get_mut().map(|r| r.as_mut()) None } fn inner_ref(&self) -> Option<&(dyn Stackable<'a, C> + Send + Sync)> { self.inner.inner.get_ref().map(|r| r.as_ref()) } fn cookie_set(&mut self, cookie: C) -> C { self.inner.cookie_set(cookie) } fn cookie_ref(&self) -> &C { self.inner.cookie_ref() } fn cookie_mut(&mut self) -> &mut C { self.inner.cookie_mut() } fn position(&self) -> u64 { self.inner.position } } /// AEAD encrypting writer. pub struct AEADEncryptor<'a, C: 'a, S: aead::Schedule> { inner: Generic<aead::Encryptor<BoxStack<'a, C>, S>, C>, } assert_send_and_sync!(AEADEncryptor<'_, C, S> where C, S: aead::Schedule); impl<'a, S: 'a + aead::Schedule> AEADEncryptor<'a, Cookie, S> { /// Makes an encrypting writer. pub fn new(inner: Message<'a>, cookie: Cookie, cipher: SymmetricAlgorithm, aead: AEADAlgorithm, chunk_size: usize, schedule: S, key: SessionKey) -> Result<Message<'a>> { Ok(Message::from(Box::new(AEADEncryptor { inner: Generic::new_unboxed( aead::Encryptor::new(cipher, aead, chunk_size, schedule, key, inner.into())?, cookie), }))) } } impl<'a, C: 'a, S: aead::Schedule> fmt::Debug for AEADEncryptor<'a, C, S> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.debug_struct("writer::AEADEncryptor") .field("inner", &self.inner) .finish() } } impl<'a, C: 'a, S: aead::Schedule> io::Write for AEADEncryptor<'a, C, S> { fn write(&mut self, bytes: &[u8]) -> io::Result<usize> { self.inner.write(bytes) } fn flush(&mut self) -> io::Result<()> { self.inner.flush() } } impl<'a, C: 'a, S: aead::Schedule> Stackable<'a, C> for AEADEncryptor<'a, C, S> { fn into_inner(mut self: Box<Self>) -> Result<Option<BoxStack<'a, C>>> { let inner = self.inner.inner.finish()?; Ok(Some(inner)) } fn pop(&mut self) -> Result<Option<BoxStack<'a, C>>> { unreachable!("Only implemented by Signer") } fn mount(&mut self, _new: BoxStack<'a, C>) { unreachable!("Only implemented by Signer") } fn inner_mut(&mut self) -> Option<&mut (dyn Stackable<'a, C> + Send + Sync)> { // XXX: Unfortunately, this doesn't work due to a lifetime mismatch: // self.inner.inner.get_mut().map(|r| r.as_mut()) None } fn inner_ref(&self) -> Option<&(dyn Stackable<'a, C> + Send + Sync)> { self.inner.inner.get_ref().map(|r| r.as_ref()) } fn cookie_set(&mut self, cookie: C) -> C { self.inner.cookie_set(cookie) } fn cookie_ref(&self) -> &C { self.inner.cookie_ref() } fn cookie_mut(&mut self) -> &mut C { self.inner.cookie_mut() } fn position(&self) -> u64 { self.inner.position } } #[cfg(test)] mod test { use std::io::Write; use super::*; #[test] fn generic_writer() { let mut inner = Vec::new(); { let mut w = Generic::new(&mut inner, Cookie::new(0)); assert_eq!(w.as_ref().cookie_ref().level, 0); dump(w.as_ref()); *w.as_mut().cookie_mut() = Cookie::new(1); assert_eq!(w.as_ref().cookie_ref().level, 1); w.write_all(b"be happy").unwrap(); let mut count = 0; map_mut(w.as_mut(), |g| -> Option<()> { let new = Cookie::new(0); let old = g.cookie_set(new); assert_eq!(old.level, 1); count += 1; None }); assert_eq!(count, 1); assert_eq!(w.as_ref().cookie_ref().level, 0); } assert_eq!(&inner, b"be happy"); } #[test] fn stack() { let mut inner = Vec::new(); { let w = Generic::new(&mut inner, Cookie::new(0)); dump(w.as_ref()); let w = Identity::new(w, Cookie::new(0)); dump(w.as_ref()); let mut count = 0; map(w.as_ref(), |g| -> Option<()> { assert_eq!(g.cookie_ref().level, 0); count += 1; None }); assert_eq!(count, 2); } } } ���������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/src/serialize/stream/writer/writer_bzip2.rs�����������������������������������0000644�0000000�0000000�00000004170�10461020230�0023316�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������use bzip2::write::BzEncoder; use std::fmt; use std::io; use crate::Result; use crate::types::CompressionLevel; use super::{Generic, Message, BoxStack, Stackable, Cookie}; /// BZing writer. pub struct BZ<'a, C: 'a> { inner: Generic<BzEncoder<BoxStack<'a, C>>, C>, } assert_send_and_sync!(BZ<'_, C> where C); impl<'a> BZ<'a, Cookie> { /// Makes a BZ compressing writer. pub fn new<L>(inner: Message<'a>, cookie: Cookie, level: L) -> Message<'a> where L: Into<Option<CompressionLevel>> { Message::from(Box::new(BZ { inner: Generic::new_unboxed( BzEncoder::new(inner.into(), level.into().unwrap_or_default().into()), cookie), })) } } impl<'a, C: 'a> fmt::Debug for BZ<'a, C> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.debug_struct("writer::BZ") .field("inner", &self.inner) .finish() } } impl<'a, C: 'a> io::Write for BZ<'a, C> { fn write(&mut self, bytes: &[u8]) -> io::Result<usize> { self.inner.write(bytes) } fn flush(&mut self) -> io::Result<()> { self.inner.flush() } } impl<'a, C: 'a> Stackable<'a, C> for BZ<'a, C> { fn into_inner(self: Box<Self>) -> Result<Option<BoxStack<'a, C>>> { let inner = self.inner.inner.finish()?; Ok(Some(inner)) } fn pop(&mut self) -> Result<Option<BoxStack<'a, C>>> { unreachable!("Only implemented by Signer") } fn mount(&mut self, _new: BoxStack<'a, C>) { unreachable!("Only implemented by Signer") } fn inner_mut(&mut self) -> Option<&mut (dyn Stackable<'a, C> + Send + Sync)> { Some(self.inner.inner.get_mut()) } fn inner_ref(&self) -> Option<&(dyn Stackable<'a, C> + Send + Sync)> { Some(self.inner.inner.get_ref()) } fn cookie_set(&mut self, cookie: C) -> C { self.inner.cookie_set(cookie) } fn cookie_ref(&self) -> &C { self.inner.cookie_ref() } fn cookie_mut(&mut self) -> &mut C { self.inner.cookie_mut() } fn position(&self) -> u64 { self.inner.position } } ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/src/serialize/stream/writer/writer_deflate.rs���������������������������������0000644�0000000�0000000�00000010233�10461020230�0023671�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������use flate2::write::{DeflateEncoder, ZlibEncoder}; use std::fmt; use std::io; use crate::Result; use crate::types::CompressionLevel; use super::{Generic, Message, BoxStack, Stackable, Cookie}; /// ZIP compressing writer. pub struct ZIP<'a, C: 'a> { inner: Generic<DeflateEncoder<BoxStack<'a, C>>, C>, } assert_send_and_sync!(ZIP<'_, C> where C); impl<'a> ZIP<'a, Cookie> { /// Makes a ZIP compressing writer. pub fn new<L>(inner: Message<'a>, cookie: Cookie, level: L) -> Message<'a> where L: Into<Option<CompressionLevel>> { Message::from(Box::new(ZIP { inner: Generic::new_unboxed( DeflateEncoder::new(inner.into(), level.into().unwrap_or_default().into()), cookie), })) } } impl<'a, C: 'a> fmt::Debug for ZIP<'a, C> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.debug_struct("writer::ZIP") .field("inner", &self.inner) .finish() } } impl<'a, C: 'a> io::Write for ZIP<'a, C> { fn write(&mut self, bytes: &[u8]) -> io::Result<usize> { self.inner.write(bytes) } fn flush(&mut self) -> io::Result<()> { self.inner.flush() } } impl<'a, C: 'a> Stackable<'a, C> for ZIP<'a, C> { fn into_inner(self: Box<Self>) -> Result<Option<BoxStack<'a, C>>> { let inner = self.inner.inner.finish()?; Ok(Some(inner)) } fn pop(&mut self) -> Result<Option<BoxStack<'a, C>>> { unreachable!("Only implemented by Signer") } fn mount(&mut self, _new: BoxStack<'a, C>) { unreachable!("Only implemented by Signer") } fn inner_mut(&mut self) -> Option<&mut (dyn Stackable<'a, C> + Send + Sync)> { Some(self.inner.inner.get_mut()) } fn inner_ref(&self) -> Option<&(dyn Stackable<'a, C> + Send + Sync)> { Some(self.inner.inner.get_ref()) } fn cookie_set(&mut self, cookie: C) -> C { self.inner.cookie_set(cookie) } fn cookie_ref(&self) -> &C { self.inner.cookie_ref() } fn cookie_mut(&mut self) -> &mut C { self.inner.cookie_mut() } fn position(&self) -> u64 { self.inner.position } } /// ZLIB compressing writer. pub struct ZLIB<'a, C: 'a> { inner: Generic<ZlibEncoder<BoxStack<'a, C>>, C>, } assert_send_and_sync!(ZLIB<'_, C> where C); impl<'a> ZLIB<'a, Cookie> { /// Makes a ZLIB compressing writer. pub fn new<L>(inner: Message<'a>, cookie: Cookie, level: L) -> Message<'a> where L: Into<Option<CompressionLevel>> { Message::from(Box::new(ZLIB { inner: Generic::new_unboxed( ZlibEncoder::new(inner.into(), level.into().unwrap_or_default().into()), cookie), })) } } impl<'a, C:> fmt::Debug for ZLIB<'a, C> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.debug_struct("writer::ZLIB") .field("inner", &self.inner) .finish() } } impl<'a, C: 'a> io::Write for ZLIB<'a, C> { fn write(&mut self, bytes: &[u8]) -> io::Result<usize> { self.inner.write(bytes) } fn flush(&mut self) -> io::Result<()> { self.inner.flush() } } impl<'a, C: 'a> Stackable<'a, C> for ZLIB<'a, C> { fn into_inner(self: Box<Self>) -> Result<Option<BoxStack<'a, C>>> { let inner = self.inner.inner.finish()?; Ok(Some(inner)) } fn pop(&mut self) -> Result<Option<BoxStack<'a, C>>> { unreachable!("Only implemented by Signer") } fn mount(&mut self, _new: BoxStack<'a, C>) { unreachable!("Only implemented by Signer") } fn inner_mut(&mut self) -> Option<&mut (dyn Stackable<'a, C> + Send + Sync)> { Some(self.inner.inner.get_mut()) } fn inner_ref(&self) -> Option<&(dyn Stackable<'a, C> + Send + Sync)> { Some(self.inner.inner.get_ref()) } fn cookie_set(&mut self, cookie: C) -> C { self.inner.cookie_set(cookie) } fn cookie_ref(&self) -> &C { self.inner.cookie_ref() } fn cookie_mut(&mut self) -> &mut C { self.inner.cookie_mut() } fn position(&self) -> u64 { self.inner.position } } ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/src/serialize/stream.rs�������������������������������������������������������0000644�0000000�0000000�00000476754�10461020230�0017405�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������//! Streaming packet serialization. //! //! This interface provides a convenient way to create signed and/or //! encrypted OpenPGP messages (see [Section 10.3 of RFC 9580]) and is //! the preferred interface to generate messages using Sequoia. It //! takes advantage of OpenPGP's streaming nature to avoid unnecessary //! buffering. //! //! [Section 10.3 of RFC 9580]: https://www.rfc-editor.org/rfc/rfc9580.html#section-10.3 //! //! To use this interface, a sink implementing [`io::Write`] is //! wrapped by [`Message::new`] returning a streaming [`Message`]. //! The writer stack is a structure to compose filters that create the //! desired message structure. There are a number of filters that can //! be freely combined: //! //! - [`Armorer`] applies ASCII-Armor to the stream, //! - [`Encryptor`] encrypts data fed into it, //! - [`Compressor`] compresses data, //! - [`Padder`] pads data, //! - [`Signer`] signs data, //! - [`LiteralWriter`] wraps literal data (i.e. the payload) into //! a literal data packet, //! - and finally, [`ArbitraryWriter`] can be used to create //! arbitrary packets for testing purposes. //! //! [`io::Write`]: std::io::Write //! [`Message::new`]: Message::new() //! [`Padder`]: padding::Padder //! //! The most common structure is an optionally encrypted, optionally //! compressed, and optionally signed message. This structure is //! [supported] by all OpenPGP implementations, and applications //! should only create messages of that structure to increase //! compatibility. See the example below on how to create this //! structure. This is a sketch of such a message: //! //! ```text //! [ encryption layer: [ compression layer: [ signature group: [ literal data ]]]] //! ``` //! //! [supported]: https://tests.sequoia-pgp.org/#Unusual_Message_Structure //! //! # Examples //! //! This example demonstrates how to create the simplest possible //! OpenPGP message (see [Section 10.3 of RFC 9580]) containing just a //! literal data packet (see [Section 5.9 of RFC 9580]): //! //! [Section 5.9 of RFC 9580]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.9 //! //! ``` //! # fn main() -> sequoia_openpgp::Result<()> { //! use std::io::Write; //! use sequoia_openpgp as openpgp; //! use openpgp::serialize::stream::{Message, LiteralWriter}; //! //! let mut sink = vec![]; //! { //! let message = Message::new(&mut sink); //! let mut message = LiteralWriter::new(message).build()?; //! message.write_all(b"Hello world.")?; //! message.finalize()?; //! } //! assert_eq!(b"\xcb\x12b\x00\x00\x00\x00\x00Hello world.", sink.as_slice()); //! # Ok(()) } //! ``` //! //! This example demonstrates how to create the most common OpenPGP //! message structure (see [Section 10.3 of RFC 9580]). The plaintext //! is first signed, then padded, encrypted, and finally ASCII armored. //! //! ``` //! # fn main() -> sequoia_openpgp::Result<()> { //! use std::io::Write; //! use sequoia_openpgp as openpgp; //! use openpgp::policy::StandardPolicy; //! use openpgp::cert::prelude::*; //! use openpgp::serialize::stream::{ //! Message, Armorer, Encryptor, Signer, LiteralWriter, padding::Padder, //! }; //! # use openpgp::parse::Parse; //! //! let p = &StandardPolicy::new(); //! //! let sender: Cert = // ... //! # Cert::from_bytes(&include_bytes!( //! # "../../tests/data/keys/testy-new-private.pgp")[..])?; //! let signing_keypair = sender.keys().secret() //! .with_policy(p, None).supported().alive().revoked(false).for_signing() //! .nth(0).unwrap() //! .key().clone().into_keypair()?; //! //! let recipient: Cert = // ... //! # sender.clone(); //! // Note: One certificate may contain several suitable encryption keys. //! let recipients = //! recipient.keys().with_policy(p, None).supported().alive().revoked(false) //! // Or `for_storage_encryption()`, for data at rest. //! .for_transport_encryption(); //! //! # let mut sink = vec![]; //! let message = Message::new(&mut sink); //! let message = Armorer::new(message).build()?; //! let message = Encryptor::for_recipients(message, recipients).build()?; //! // Reduce metadata leakage by concealing the message size. //! let message = Padder::new(message).build()?; //! let message = Signer::new(message, signing_keypair)? //! // Prevent Surreptitious Forwarding. //! .add_intended_recipient(&recipient) //! .build()?; //! let mut message = LiteralWriter::new(message).build()?; //! message.write_all(b"Hello world.")?; //! message.finalize()?; //! # Ok(()) } //! ``` use std::fmt; use std::io::{self, Write}; use std::time::SystemTime; use crate::{ armor, crypto, Error, Fingerprint, HashAlgorithm, KeyHandle, Profile, Result, crypto::Password, crypto::SessionKey, packet::prelude::*, packet::signature, packet::key, cert::prelude::*, }; use crate::packet::header::CTB; use crate::packet::header::BodyLength; use crate::parse::HashingMode; use super::{ Marshal, }; use crate::types::{ AEADAlgorithm, CompressionAlgorithm, CompressionLevel, DataFormat, Features, SignatureType, SymmetricAlgorithm, }; pub(crate) mod writer; pub mod padding; mod partial_body; use partial_body::PartialBodyFilter; mod dash_escape; use dash_escape::DashEscapeFilter; mod trim_whitespace; use trim_whitespace::TrailingWSFilter; /// Cookie must be public because the writers are. #[derive(Debug)] struct Cookie { level: usize, private: Private, } impl Cookie { /// Sets the private data part of the cookie. pub fn set_private(mut self, p: Private) -> Self { self.private = p; self } } /// An enum to store writer-specific data. #[derive(Debug)] enum Private { Nothing, Signer, Armorer { set_profile: Option<Profile>, }, Encryptor { profile: Profile, }, } impl Cookie { fn new(level: usize) -> Self { Cookie { level, private: Private::Nothing, } } } impl Default for Cookie { fn default() -> Self { Cookie::new(0) } } /// Streams an OpenPGP message. /// /// Wraps an [`io::Write`]r for use with the streaming subsystem. The /// `Message` is a stack of filters that create the desired message /// structure. Literal data must be framed using the /// [`LiteralWriter`] filter. Once all the has been written, the /// `Message` must be finalized using [`Message::finalize`]. /// /// [`io::Write`]: std::io::Write /// [`Message::finalize`]: Message::finalize() #[derive(Debug)] pub struct Message<'a>(writer::BoxStack<'a, Cookie>); assert_send_and_sync!(Message<'_>); impl<'a> Message<'a> { /// Starts streaming an OpenPGP message. /// /// # Examples /// /// ``` /// # fn main() -> sequoia_openpgp::Result<()> { /// use sequoia_openpgp as openpgp; /// use openpgp::serialize::stream::{Message, LiteralWriter}; /// /// # let mut sink = vec![]; // Vec<u8> implements io::Write. /// let message = Message::new(&mut sink); /// // Construct the writer stack here. /// let mut message = LiteralWriter::new(message).build()?; /// // Write literal data to `message` here. /// // ... /// // Finalize the message. /// message.finalize()?; /// # Ok(()) } /// ``` pub fn new<W: 'a + io::Write + Send + Sync>(w: W) -> Message<'a> { writer::Generic::new(w, Cookie::new(0)) } /// Finalizes the topmost writer, returning the underlying writer. /// /// Finalizes the topmost writer, i.e. flushes any buffered data, /// and pops it of the stack. This allows for fine-grained /// control of the resulting message, but must be done with great /// care. If done improperly, the resulting message may be /// malformed. /// /// # Examples /// /// This demonstrates how to create a compressed, signed message /// from a detached signature. /// /// ``` /// # fn main() -> sequoia_openpgp::Result<()> { /// use std::io::Write; /// use std::convert::TryFrom; /// use sequoia_openpgp as openpgp; /// use openpgp::packet::{Packet, Signature, one_pass_sig::OnePassSig3}; /// # use openpgp::parse::Parse; /// use openpgp::serialize::Serialize; /// use openpgp::serialize::stream::{Message, Compressor, LiteralWriter}; /// /// let data: &[u8] = // ... /// # &include_bytes!( /// # "../../tests/data/messages/a-cypherpunks-manifesto.txt")[..]; /// let sig: Signature = // ... /// # if let Packet::Signature(s) = Packet::from_bytes(&include_bytes!( /// # "../../tests/data/messages/a-cypherpunks-manifesto.txt.ed25519.sig")[..])? /// # { s } else { panic!() }; /// /// # let mut sink = vec![]; // Vec<u8> implements io::Write. /// let message = Message::new(&mut sink); /// let mut message = Compressor::new(message).build()?; /// /// // First, write a one-pass-signature packet. /// Packet::from(OnePassSig3::try_from(&sig)?) /// .serialize(&mut message)?; /// /// // Then, add the literal data. /// let mut message = LiteralWriter::new(message).build()?; /// message.write_all(data)?; /// /// // Finally, pop the `LiteralWriter` off the stack to write the /// // signature. /// let mut message = message.finalize_one()?.unwrap(); /// Packet::from(sig).serialize(&mut message)?; /// /// // Finalize the message. /// message.finalize()?; /// # Ok(()) } /// ``` pub fn finalize_one(self) -> Result<Option<Message<'a>>> { Ok(self.0.into_inner()?.map(|bs| Self::from(bs))) } /// Finalizes the message. /// /// Finalizes all writers on the stack, flushing any buffered /// data. /// /// # Note /// /// Failing to finalize the message may result in corrupted /// messages. /// /// # Examples /// /// ``` /// # fn main() -> sequoia_openpgp::Result<()> { /// use sequoia_openpgp as openpgp; /// use openpgp::serialize::stream::{Message, LiteralWriter}; /// /// # let mut sink = vec![]; // Vec<u8> implements io::Write. /// let message = Message::new(&mut sink); /// // Construct the writer stack here. /// let mut message = LiteralWriter::new(message).build()?; /// // Write literal data to `message` here. /// // ... /// // Finalize the message. /// message.finalize()?; /// # Ok(()) } /// ``` pub fn finalize(self) -> Result<()> { let mut stack = self; while let Some(s) = stack.finalize_one()? { stack = s; } Ok(()) } } impl<'a> From<&'a mut (dyn io::Write + Send + Sync)> for Message<'a> { fn from(w: &'a mut (dyn io::Write + Send + Sync)) -> Self { writer::Generic::new(w, Cookie::new(0)) } } /// Applies ASCII Armor to the message. /// /// ASCII armored data (see [Section 6 of RFC 9580]) is a OpenPGP data /// stream that has been base64-encoded and decorated with a header, /// footer, and optional headers representing key-value pairs. It can /// be safely transmitted over protocols that can only transmit /// printable characters, and can be handled by end users (e.g. copied /// and pasted). /// /// [Section 6 of RFC 9580]: https://www.rfc-editor.org/rfc/rfc9580.html#section-6 pub struct Armorer<'a> { kind: armor::Kind, headers: Vec<(String, String)>, inner: Message<'a>, } assert_send_and_sync!(Armorer<'_>); impl<'a> Armorer<'a> { /// Creates a new armoring filter. /// /// By default, the type of the armored data is set to /// [`armor::Kind`]`::Message`. To change it, use /// [`Armorer::kind`]. To add headers to the armor, use /// [`Armorer::add_header`]. /// /// [`armor::Kind`]: crate::armor::Kind /// [`Armorer::kind`]: Armorer::kind() /// [`Armorer::add_header`]: Armorer::add_header() /// /// # Examples /// /// ``` /// # fn main() -> sequoia_openpgp::Result<()> { /// use std::io::Write; /// use sequoia_openpgp as openpgp; /// use openpgp::serialize::stream::{Message, Armorer, LiteralWriter}; /// /// let mut sink = vec![]; /// { /// let message = Message::new(&mut sink); /// let message = Armorer::new(message) /// // Customize the `Armorer` here. /// .build()?; /// let mut message = LiteralWriter::new(message).build()?; /// message.write_all(b"Hello world.")?; /// message.finalize()?; /// } /// assert_eq!("-----BEGIN PGP MESSAGE-----\n\ /// \n\ /// yxJiAAAAAABIZWxsbyB3b3JsZC4=\n\ /// =6nHv\n\ /// -----END PGP MESSAGE-----\n", /// std::str::from_utf8(&sink)?); /// # Ok(()) } pub fn new(inner: Message<'a>) -> Self { Self { kind: armor::Kind::Message, headers: Vec::with_capacity(0), inner, } } /// Changes the kind of armoring. /// /// The armor header and footer changes depending on the type of /// wrapped data. See [`armor::Kind`] for the possible values. /// /// [`armor::Kind`]: crate::armor::Kind /// /// # Examples /// /// ``` /// # fn main() -> sequoia_openpgp::Result<()> { /// use std::io::Write; /// use sequoia_openpgp as openpgp; /// use openpgp::armor; /// use openpgp::serialize::stream::{Message, Armorer, Signer}; /// # use sequoia_openpgp::policy::StandardPolicy; /// # use openpgp::{Result, Cert}; /// # use openpgp::packet::prelude::*; /// # use openpgp::crypto::KeyPair; /// # use openpgp::parse::Parse; /// # use openpgp::parse::stream::*; /// # let p = &StandardPolicy::new(); /// # let cert = Cert::from_bytes(&include_bytes!( /// # "../../tests/data/keys/testy-new-private.pgp")[..])?; /// # let signing_keypair /// # = cert.keys().secret() /// # .with_policy(p, None).alive().revoked(false).for_signing() /// # .nth(0).unwrap() /// # .key().clone().into_keypair()?; /// /// let mut sink = vec![]; /// { /// let message = Message::new(&mut sink); /// let message = Armorer::new(message) /// .kind(armor::Kind::Signature) /// .build()?; /// let mut signer = Signer::new(message, signing_keypair)? /// .detached() /// .build()?; /// /// // Write the data directly to the `Signer`. /// signer.write_all(b"Make it so, number one!")?; /// // In reality, just io::copy() the file to be signed. /// signer.finalize()?; /// } /// /// assert!(std::str::from_utf8(&sink)? /// .starts_with("-----BEGIN PGP SIGNATURE-----\n")); /// # Ok(()) } pub fn kind(mut self, kind: armor::Kind) -> Self { self.kind = kind; self } /// Adds a header to the armor block. /// /// There are a number of defined armor header keys (see [Section /// 6 of RFC 9580]), but in practice, any key may be used, as /// implementations should simply ignore unknown keys. /// /// [Section 6 of RFC 9580]: https://www.rfc-editor.org/rfc/rfc9580.html#section-6 /// /// # Examples /// /// ``` /// # fn main() -> sequoia_openpgp::Result<()> { /// use std::io::Write; /// use sequoia_openpgp as openpgp; /// use openpgp::serialize::stream::{Message, Armorer, LiteralWriter}; /// /// let mut sink = vec![]; /// { /// let message = Message::new(&mut sink); /// let message = Armorer::new(message) /// .add_header("Comment", "No comment.") /// .build()?; /// let mut message = LiteralWriter::new(message).build()?; /// message.write_all(b"Hello world.")?; /// message.finalize()?; /// } /// assert_eq!("-----BEGIN PGP MESSAGE-----\n\ /// Comment: No comment.\n\ /// \n\ /// yxJiAAAAAABIZWxsbyB3b3JsZC4=\n\ /// =6nHv\n\ /// -----END PGP MESSAGE-----\n", /// std::str::from_utf8(&sink)?); /// # Ok(()) } pub fn add_header<K, V>(mut self, key: K, value: V) -> Self where K: AsRef<str>, V: AsRef<str>, { self.headers.push((key.as_ref().to_string(), value.as_ref().to_string())); self } /// Builds the armor writer, returning the writer stack. /// /// # Examples /// /// ``` /// # fn main() -> sequoia_openpgp::Result<()> { /// use sequoia_openpgp as openpgp; /// use openpgp::serialize::stream::{Message, Armorer, LiteralWriter}; /// /// # let mut sink = vec![]; /// let message = Message::new(&mut sink); /// let message = Armorer::new(message) /// // Customize the `Armorer` here. /// .build()?; /// # Ok(()) } pub fn build(self) -> Result<Message<'a>> { let level = self.inner.as_ref().cookie_ref().level; let mut cookie = Cookie::new(level + 1); cookie.private = Private::Armorer { set_profile: None, }; writer::Armorer::new( self.inner, cookie, self.kind, self.headers, ) } } impl<'a> fmt::Debug for Armorer<'a> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.debug_struct("Armorer") .field("inner", &self.inner) .field("kind", &self.kind) .field("headers", &self.headers) .finish() } } /// Writes an arbitrary packet. /// /// This writer can be used to construct arbitrary OpenPGP packets. /// This is mainly useful for testing. The body will be written using /// partial length encoding, or, if the body is short, using full /// length encoding. pub struct ArbitraryWriter<'a> { inner: writer::BoxStack<'a, Cookie>, } assert_send_and_sync!(ArbitraryWriter<'_>); impl<'a> ArbitraryWriter<'a> { /// Creates a new writer with the given tag. /// /// # Examples /// /// ``` /// # fn main() -> sequoia_openpgp::Result<()> { /// use std::io::Write; /// use sequoia_openpgp as openpgp; /// use openpgp::packet::Tag; /// use openpgp::serialize::stream::{Message, ArbitraryWriter}; /// /// let mut sink = vec![]; /// { /// let message = Message::new(&mut sink); /// let mut message = ArbitraryWriter::new(message, Tag::Literal)?; /// message.write_all(b"t")?; // type /// message.write_all(b"\x00")?; // filename length /// message.write_all(b"\x00\x00\x00\x00")?; // date /// message.write_all(b"Hello world.")?; // body /// message.finalize()?; /// } /// assert_eq!(b"\xcb\x12t\x00\x00\x00\x00\x00Hello world.", /// sink.as_slice()); /// # Ok(()) } pub fn new(mut inner: Message<'a>, tag: Tag) -> Result<Message<'a>> { let level = inner.as_ref().cookie_ref().level + 1; CTB::new(tag).serialize(&mut inner)?; Ok(Message::from(Box::new(ArbitraryWriter { inner: PartialBodyFilter::new(inner, Cookie::new(level)).into() }))) } } impl<'a> fmt::Debug for ArbitraryWriter<'a> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.debug_struct("ArbitraryWriter") .field("inner", &self.inner) .finish() } } impl<'a> Write for ArbitraryWriter<'a> { fn write(&mut self, buf: &[u8]) -> io::Result<usize> { self.inner.write(buf) } fn flush(&mut self) -> io::Result<()> { self.inner.flush() } } impl<'a> writer::Stackable<'a, Cookie> for ArbitraryWriter<'a> { fn into_inner(self: Box<Self>) -> Result<Option<writer::BoxStack<'a, Cookie>>> { Box::new(self.inner).into_inner() } fn pop(&mut self) -> Result<Option<writer::BoxStack<'a, Cookie>>> { unreachable!("Only implemented by Signer") } /// Sets the inner stackable. fn mount(&mut self, _new: writer::BoxStack<'a, Cookie>) { unreachable!("Only implemented by Signer") } fn inner_ref(&self) -> Option<&(dyn writer::Stackable<'a, Cookie> + Send + Sync)> { Some(self.inner.as_ref()) } fn inner_mut(&mut self) -> Option<&mut (dyn writer::Stackable<'a, Cookie> + Send + Sync)> { Some(self.inner.as_mut()) } fn cookie_set(&mut self, cookie: Cookie) -> Cookie { self.inner.cookie_set(cookie) } fn cookie_ref(&self) -> &Cookie { self.inner.cookie_ref() } fn cookie_mut(&mut self) -> &mut Cookie { self.inner.cookie_mut() } fn position(&self) -> u64 { self.inner.position() } } /// Signs a message. /// /// Signs a message with every [`crypto::Signer`] added to the /// streaming signer. /// /// [`crypto::Signer`]: super::super::crypto::Signer pub struct Signer<'a> { // The underlying writer. // // Because this writer implements `Drop`, we cannot move the inner // writer out of this writer. We therefore wrap it with `Option` // so that we can `take()` it. // // Furthermore, the LiteralWriter will pop us off the stack, and // take our inner reader. If that happens, we only update the // digests. inner: Option<writer::BoxStack<'a, Cookie>>, signers: Vec<(Box<dyn crypto::Signer + Send + Sync + 'a>, HashAlgorithm, Vec<u8>)>, /// The set of acceptable hashes. acceptable_hash_algos: Vec<HashAlgorithm>, /// The explicitly selected algo, if any. hash_algo: Option<HashAlgorithm>, intended_recipients: Vec<Fingerprint>, mode: SignatureMode, template: signature::SignatureBuilder, creation_time: Option<SystemTime>, hashes: Vec<HashingMode<crypto::hash::Context>>, cookie: Cookie, position: u64, } assert_send_and_sync!(Signer<'_>); #[derive(Clone, Copy, Debug, PartialEq, Eq)] enum SignatureMode { Inline, Detached, Cleartext, } impl<'a> Signer<'a> { /// Creates a signer. /// /// Signs the message with the given [`crypto::Signer`]. To /// create more than one signature, add more [`crypto::Signer`]s /// using [`Signer::add_signer`]. Properties of the signatures /// can be tweaked using the methods of this type. Notably, to /// generate a detached signature (see [Section 10.4 of RFC /// 9580]), use [`Signer::detached`]. For even more control over /// the generated signatures, use [`Signer::with_template`]. /// /// [`crypto::Signer`]: super::super::crypto::Signer /// [`Signer::add_signer`]: Signer::add_signer() /// [Section 10.4 of RFC 9580]: https://www.rfc-editor.org/rfc/rfc9580.html#section-10.4 /// [`Signer::detached`]: Signer::detached() /// [`Signer::with_template`]: Signer::with_template() /// /// # Examples /// /// ``` /// # fn main() -> sequoia_openpgp::Result<()> { /// use std::io::{Read, Write}; /// use sequoia_openpgp as openpgp; /// use openpgp::serialize::stream::{Message, Signer, LiteralWriter}; /// use openpgp::policy::StandardPolicy; /// # use openpgp::{Result, Cert}; /// # use openpgp::packet::prelude::*; /// # use openpgp::parse::Parse; /// # use openpgp::parse::stream::*; /// /// let p = &StandardPolicy::new(); /// let cert: Cert = // ... /// # Cert::from_bytes(&include_bytes!( /// # "../../tests/data/keys/testy-new-private.pgp")[..])?; /// let signing_keypair = cert.keys().secret() /// .with_policy(p, None).supported().alive().revoked(false).for_signing() /// .nth(0).unwrap() /// .key().clone().into_keypair()?; /// /// let mut sink = vec![]; /// { /// let message = Message::new(&mut sink); /// let message = Signer::new(message, signing_keypair)? /// // Customize the `Signer` here. /// .build()?; /// let mut message = LiteralWriter::new(message).build()?; /// message.write_all(b"Make it so, number one!")?; /// message.finalize()?; /// } /// /// // Now check the signature. /// struct Helper<'a>(&'a openpgp::Cert); /// impl<'a> VerificationHelper for Helper<'a> { /// fn get_certs(&mut self, _: &[openpgp::KeyHandle]) /// -> openpgp::Result<Vec<openpgp::Cert>> { /// Ok(vec![self.0.clone()]) /// } /// /// fn check(&mut self, structure: MessageStructure) /// -> openpgp::Result<()> { /// if let MessageLayer::SignatureGroup { ref results } = /// structure.iter().nth(0).unwrap() /// { /// results.get(0).unwrap().as_ref().unwrap(); /// Ok(()) /// } else { panic!() } /// } /// } /// /// let mut verifier = VerifierBuilder::from_bytes(&sink)? /// .with_policy(p, None, Helper(&cert))?; /// /// let mut message = String::new(); /// verifier.read_to_string(&mut message)?; /// assert_eq!(&message, "Make it so, number one!"); /// # Ok(()) } /// ``` pub fn new<S>(inner: Message<'a>, signer: S) -> Result<Self> where S: crypto::Signer + Send + Sync + 'a { Self::with_template(inner, signer, signature::SignatureBuilder::new(SignatureType::Binary)) } /// Creates a signer with a given signature template. /// /// Signs the message with the given [`crypto::Signer`] like /// [`Signer::new`], but allows more control over the generated /// signatures. The given [`signature::SignatureBuilder`] is used to /// create all the signatures. /// /// For every signature, the creation time is set to the current /// time or the one specified using [`Signer::creation_time`], the /// intended recipients are added (see /// [`Signer::add_intended_recipient`]), the issuer and issuer /// fingerprint subpackets are set according to the signing key, /// and the hash algorithm set using [`Signer::hash_algo`] is used /// to create the signature. /// /// [`crypto::Signer`]: super::super::crypto::Signer /// [`Signer::new`]: Message::new() /// [`signature::SignatureBuilder`]: crate::packet::signature::SignatureBuilder /// [`Signer::creation_time`]: Signer::creation_time() /// [`Signer::hash_algo`]: Signer::hash_algo() /// [`Signer::add_intended_recipient`]: Signer::add_intended_recipient() /// /// # Examples /// /// ``` /// # fn main() -> sequoia_openpgp::Result<()> { /// use std::io::{Read, Write}; /// use sequoia_openpgp as openpgp; /// use openpgp::types::SignatureType; /// use openpgp::packet::signature; /// use openpgp::serialize::stream::{Message, Signer, LiteralWriter}; /// # use openpgp::policy::StandardPolicy; /// # use openpgp::{Result, Cert}; /// # use openpgp::packet::prelude::*; /// # use openpgp::parse::Parse; /// # use openpgp::parse::stream::*; /// # /// # let p = &StandardPolicy::new(); /// # let cert: Cert = // ... /// # Cert::from_bytes(&include_bytes!( /// # "../../tests/data/keys/testy-new-private.pgp")[..])?; /// # let signing_keypair = cert.keys().secret() /// # .with_policy(p, None).supported().alive().revoked(false).for_signing() /// # .nth(0).unwrap() /// # .key().clone().into_keypair()?; /// # let mut sink = vec![]; /// /// let message = Message::new(&mut sink); /// let message = Signer::with_template( /// message, signing_keypair, /// signature::SignatureBuilder::new(SignatureType::Text) /// .add_notation("issuer@starfleet.command", "Jean-Luc Picard", /// None, true)?)? /// // Further customize the `Signer` here. /// .build()?; /// let mut message = LiteralWriter::new(message).build()?; /// message.write_all(b"Make it so, number one!")?; /// message.finalize()?; /// # Ok(()) } /// ``` pub fn with_template<S, T>(inner: Message<'a>, signer: S, template: T) -> Result<Self> where S: crypto::Signer + Send + Sync + 'a, T: Into<signature::SignatureBuilder>, { let inner = writer::BoxStack::from(inner); let level = inner.cookie_ref().level + 1; Signer { inner: Some(inner), signers: Default::default(), acceptable_hash_algos: crate::crypto::hash::default_hashes().to_vec(), intended_recipients: Vec::new(), mode: SignatureMode::Inline, template: template.into(), creation_time: None, hash_algo: Default::default(), hashes: vec![], cookie: Cookie { level, private: Private::Signer, }, position: 0, }.add_signer(signer) } /// Creates a signer for a detached signature. /// /// Changes the `Signer` to create a detached signature (see /// [Section 10.4 of RFC 9580]). Note that the literal data *must /// not* be wrapped using the [`LiteralWriter`]. /// /// This overrides any prior call to [`Signer::cleartext`]. /// /// [Section 10.4 of RFC 9580]: https://www.rfc-editor.org/rfc/rfc9580.html#section-10.4 /// [`Signer::cleartext`]: Signer::cleartext() /// /// # Examples /// /// ``` /// # fn main() -> sequoia_openpgp::Result<()> { /// use std::io::Write; /// use sequoia_openpgp as openpgp; /// use openpgp::serialize::stream::{Message, Signer}; /// use openpgp::policy::StandardPolicy; /// # use openpgp::{Result, Cert}; /// # use openpgp::packet::prelude::*; /// # use openpgp::crypto::KeyPair; /// # use openpgp::parse::Parse; /// # use openpgp::parse::stream::*; /// /// let p = &StandardPolicy::new(); /// # let cert = Cert::from_bytes(&include_bytes!( /// # "../../tests/data/keys/testy-new-private.pgp")[..])?; /// # let signing_keypair /// # = cert.keys().secret() /// # .with_policy(p, None).supported().alive().revoked(false).for_signing() /// # .nth(0).unwrap() /// # .key().clone().into_keypair()?; /// /// let mut sink = vec![]; /// { /// let message = Message::new(&mut sink); /// let mut signer = Signer::new(message, signing_keypair)? /// .detached() /// // Customize the `Signer` here. /// .build()?; /// /// // Write the data directly to the `Signer`. /// signer.write_all(b"Make it so, number one!")?; /// // In reality, just io::copy() the file to be signed. /// signer.finalize()?; /// } /// /// // Now check the signature. /// struct Helper<'a>(&'a openpgp::Cert); /// impl<'a> VerificationHelper for Helper<'a> { /// fn get_certs(&mut self, _: &[openpgp::KeyHandle]) /// -> openpgp::Result<Vec<openpgp::Cert>> { /// Ok(vec![self.0.clone()]) /// } /// /// fn check(&mut self, structure: MessageStructure) /// -> openpgp::Result<()> { /// if let MessageLayer::SignatureGroup { ref results } = /// structure.iter().nth(0).unwrap() /// { /// results.get(0).unwrap().as_ref().unwrap(); /// Ok(()) /// } else { panic!() } /// } /// } /// /// let mut verifier = DetachedVerifierBuilder::from_bytes(&sink)? /// .with_policy(p, None, Helper(&cert))?; /// /// verifier.verify_bytes(b"Make it so, number one!")?; /// # Ok(()) } /// ``` pub fn detached(mut self) -> Self { self.mode = SignatureMode::Detached; self } /// Creates a signer for a cleartext signed message. /// /// Changes the `Signer` to create a cleartext signed message (see /// [Section 7 of RFC 9580]). Note that the literal data *must /// not* be wrapped using the [`LiteralWriter`]. This implies /// ASCII armored output, *do not* add an [`Armorer`] to the /// stack. /// /// Note: /// /// - The cleartext signature framework does not hash trailing /// whitespace (in this case, space and tab, see [Section 7.2 of /// RFC 9580] for more information). We align what we emit and /// what is being signed by trimming whitespace off of line /// endings. /// /// - That means that you can not recover a byte-accurate copy of /// the signed message if your message contains either a line /// with trailing whitespace, or no final newline. This is a /// limitation of the Cleartext Signature Framework, which is /// not designed to be reversible (see [Section 7 of RFC 9580]). /// /// This overrides any prior call to [`Signer::detached`]. /// /// [Section 7 of RFC 9580]: https://www.rfc-editor.org/rfc/rfc9580.html#section-7 /// [Section 7.2 of RFC 9580]: https://www.rfc-editor.org/rfc/rfc9580.html#section-7.2 /// [`Signer::detached`]: Signer::detached() /// /// # Examples /// /// ``` /// # fn main() -> sequoia_openpgp::Result<()> { /// use std::io::{Write, Read}; /// use sequoia_openpgp as openpgp; /// use openpgp::serialize::stream::{Message, Signer}; /// use openpgp::policy::StandardPolicy; /// # use openpgp::{Result, Cert}; /// # use openpgp::packet::prelude::*; /// # use openpgp::crypto::KeyPair; /// # use openpgp::parse::Parse; /// # use openpgp::parse::stream::*; /// /// let p = &StandardPolicy::new(); /// # let cert = Cert::from_bytes(&include_bytes!( /// # "../../tests/data/keys/testy-new-private.pgp")[..])?; /// # let signing_keypair /// # = cert.keys().secret() /// # .with_policy(p, None).supported().alive().revoked(false).for_signing() /// # .nth(0).unwrap() /// # .key().clone().into_keypair()?; /// /// let mut sink = vec![]; /// { /// let message = Message::new(&mut sink); /// let mut signer = Signer::new(message, signing_keypair)? /// .cleartext() /// // Customize the `Signer` here. /// .build()?; /// /// // Write the data directly to the `Signer`. /// signer.write_all(b"Make it so, number one!")?; /// // In reality, just io::copy() the file to be signed. /// signer.finalize()?; /// } /// /// // Now check the signature. /// struct Helper<'a>(&'a openpgp::Cert); /// impl<'a> VerificationHelper for Helper<'a> { /// fn get_certs(&mut self, _: &[openpgp::KeyHandle]) /// -> openpgp::Result<Vec<openpgp::Cert>> { /// Ok(vec![self.0.clone()]) /// } /// /// fn check(&mut self, structure: MessageStructure) /// -> openpgp::Result<()> { /// if let MessageLayer::SignatureGroup { ref results } = /// structure.iter().nth(0).unwrap() /// { /// results.get(0).unwrap().as_ref().unwrap(); /// Ok(()) /// } else { panic!() } /// } /// } /// /// let mut verifier = VerifierBuilder::from_bytes(&sink)? /// .with_policy(p, None, Helper(&cert))?; /// /// let mut content = Vec::new(); /// verifier.read_to_end(&mut content)?; /// assert_eq!(content, b"Make it so, number one!"); /// # Ok(()) } /// ``` // // Some notes on the implementation: // // There are a few pitfalls when implementing the CSF. We // separate concerns as much as possible. // // - Trailing whitespace must be stripped. We do this using the // TrailingWSFilter before the data hits this streaming signer. // This filter also adds a final newline, if missing. // // - We hash what we get from the TrailingWSFilter. // // - We write into the DashEscapeFilter, which takes care of the // dash-escaping. pub fn cleartext(mut self) -> Self { self.mode = SignatureMode::Cleartext; self } /// Adds an additional signer. /// /// Can be used multiple times. /// /// Note that some signers only support a subset of hash /// algorithms, see [`crate::crypto::Signer.acceptable_hashes`]. /// If the given signer supports at least one hash from the /// current set of acceptable hashes, the signer is added and all /// algorithms not supported by it are removed from the set of /// acceptable hashes. Otherwise, an error is returned. /// /// # Examples /// /// ``` /// # fn main() -> sequoia_openpgp::Result<()> { /// use std::io::Write; /// use sequoia_openpgp as openpgp; /// use openpgp::serialize::stream::{Message, Signer, LiteralWriter}; /// # use openpgp::policy::StandardPolicy; /// # use openpgp::{Result, Cert}; /// # use openpgp::packet::prelude::*; /// # use openpgp::parse::Parse; /// # use openpgp::parse::stream::*; /// /// # let p = &StandardPolicy::new(); /// # let cert = Cert::from_bytes(&include_bytes!( /// # "../../tests/data/keys/testy-new-private.pgp")[..])?; /// # let signing_keypair = cert.keys().secret() /// # .with_policy(p, None).supported().alive().revoked(false).for_signing() /// # .nth(0).unwrap() /// # .key().clone().into_keypair()?; /// # let additional_signing_keypair = cert.keys().secret() /// # .with_policy(p, None).supported().alive().revoked(false).for_signing() /// # .nth(0).unwrap() /// # .key().clone().into_keypair()?; /// /// # let mut sink = vec![]; /// let message = Message::new(&mut sink); /// let message = Signer::new(message, signing_keypair)? /// .add_signer(additional_signing_keypair)? /// .build()?; /// let mut message = LiteralWriter::new(message).build()?; /// message.write_all(b"Make it so, number one!")?; /// message.finalize()?; /// # Ok(()) } /// ``` pub fn add_signer<S>(mut self, signer: S) -> Result<Self> where S: crypto::Signer + Send + Sync + 'a { // Update the set of acceptable hash algorithms. let is_sorted = |data: &[HashAlgorithm]| { data.windows(2).all(|w| w[0] <= w[1]) }; let mut signer_hashes = signer.acceptable_hashes(); let mut signer_hashes_; if ! is_sorted(signer_hashes) { signer_hashes_ = signer_hashes.to_vec(); signer_hashes_.sort(); signer_hashes = &signer_hashes_; } self.acceptable_hash_algos.retain( |hash| signer_hashes.binary_search(hash).is_ok()); if self.acceptable_hash_algos.is_empty() { return Err(Error::NoAcceptableHash.into()); } if let Some(a) = self.hash_algo { if ! self.acceptable_hash_algos.contains(&a) { return Err(Error::NoAcceptableHash.into()); } } self.signers.push((Box::new(signer), Default::default(), Vec::new())); Ok(self) } /// Adds an intended recipient. /// /// Indicates that the given certificate is an intended recipient /// of this message. Can be used multiple times. This prevents /// [*Surreptitious Forwarding*] of encrypted and signed messages, /// i.e. forwarding a signed message using a different encryption /// context. /// /// [*Surreptitious Forwarding*]: http://world.std.com/~dtd/sign_encrypt/sign_encrypt7.html /// /// # Examples /// /// ``` /// # fn main() -> sequoia_openpgp::Result<()> { /// use std::io::Write; /// use sequoia_openpgp as openpgp; /// use openpgp::serialize::stream::{Message, Signer, LiteralWriter}; /// # use openpgp::policy::StandardPolicy; /// # use openpgp::{Result, Cert}; /// # use openpgp::packet::prelude::*; /// # use openpgp::crypto::KeyPair; /// # use openpgp::parse::Parse; /// # use openpgp::parse::stream::*; /// /// # let p = &StandardPolicy::new(); /// # let cert = Cert::from_bytes(&include_bytes!( /// # "../../tests/data/keys/testy-new-private.pgp")[..])?; /// # let signing_keypair = cert.keys().secret() /// # .with_policy(p, None).supported().alive().revoked(false).for_signing() /// # .nth(0).unwrap() /// # .key().clone().into_keypair()?; /// let recipient: Cert = // ... /// # Cert::from_bytes(&include_bytes!( /// # "../../tests/data/keys/testy.pgp")[..])?; /// /// # let mut sink = vec![]; /// let message = Message::new(&mut sink); /// let message = Signer::new(message, signing_keypair)? /// .add_intended_recipient(&recipient) /// .build()?; /// let mut message = LiteralWriter::new(message).build()?; /// message.write_all(b"Make it so, number one!")?; /// message.finalize()?; /// # Ok(()) } /// ``` pub fn add_intended_recipient(mut self, recipient: &Cert) -> Self { self.intended_recipients.push(recipient.fingerprint()); self } /// Sets the preferred hash algorithm to use for the signatures. /// /// Note that some signers only support a subset of hash /// algorithms, see [`crate::crypto::Signer.acceptable_hashes`]. /// If the given algorithm is not supported by all signers, an /// error is returned. /// /// # Examples /// /// ``` /// # fn main() -> sequoia_openpgp::Result<()> { /// use std::io::Write; /// use sequoia_openpgp as openpgp; /// use openpgp::types::HashAlgorithm; /// use openpgp::serialize::stream::{Message, Signer, LiteralWriter}; /// # use openpgp::policy::StandardPolicy; /// # use openpgp::{Result, Cert}; /// # use openpgp::packet::prelude::*; /// # use openpgp::parse::Parse; /// # use openpgp::parse::stream::*; /// /// # let p = &StandardPolicy::new(); /// # let cert = Cert::from_bytes(&include_bytes!( /// # "../../tests/data/keys/testy-new-private.pgp")[..])?; /// # let signing_keypair = cert.keys().secret() /// # .with_policy(p, None).supported().alive().revoked(false).for_signing() /// # .nth(0).unwrap() /// # .key().clone().into_keypair()?; /// /// # let mut sink = vec![]; /// let message = Message::new(&mut sink); /// let message = Signer::new(message, signing_keypair)? /// .hash_algo(HashAlgorithm::SHA384)? /// .build()?; /// let mut message = LiteralWriter::new(message).build()?; /// message.write_all(b"Make it so, number one!")?; /// message.finalize()?; /// # Ok(()) } /// ``` pub fn hash_algo(mut self, algo: HashAlgorithm) -> Result<Self> { if self.acceptable_hash_algos.contains(&algo) { self.hash_algo = Some(algo); Ok(self) } else { Err(Error::NoAcceptableHash.into()) } } /// Sets the signature's creation time to `time`. /// /// Note: it is up to the caller to make sure the signing keys are /// actually valid as of `time`. /// /// # Examples /// /// ``` /// # fn main() -> sequoia_openpgp::Result<()> { /// use std::io::Write; /// use sequoia_openpgp as openpgp; /// use openpgp::types::Timestamp; /// use openpgp::serialize::stream::{Message, Signer, LiteralWriter}; /// use openpgp::policy::StandardPolicy; /// # use openpgp::{Result, Cert}; /// # use openpgp::packet::prelude::*; /// # use openpgp::parse::Parse; /// # use openpgp::parse::stream::*; /// /// let p = &StandardPolicy::new(); /// let cert: Cert = // ... /// # Cert::from_bytes(&include_bytes!( /// # "../../tests/data/keys/testy-new-private.pgp")[..])?; /// let signing_key = cert.keys().secret() /// .with_policy(p, None).supported().alive().revoked(false).for_signing() /// .nth(0).unwrap() /// .key(); /// let signing_keypair = signing_key.clone().into_keypair()?; /// /// # let mut sink = vec![]; /// let message = Message::new(&mut sink); /// let message = Signer::new(message, signing_keypair)? /// .creation_time(Timestamp::now() /// .round_down(None, signing_key.creation_time())?) /// .build()?; /// let mut message = LiteralWriter::new(message).build()?; /// message.write_all(b"Make it so, number one!")?; /// message.finalize()?; /// # Ok(()) } /// ``` pub fn creation_time<T: Into<SystemTime>>(mut self, creation_time: T) -> Self { self.creation_time = Some(creation_time.into()); self } /// Builds the signer, returning the writer stack. /// /// The most useful filter to push to the writer stack next is the /// [`LiteralWriter`]. Note, if you are creating a signed OpenPGP /// message (see [Section 10.3 of RFC 9580]), literal data *must* /// be wrapped using the [`LiteralWriter`]. On the other hand, if /// you are creating a detached signature (see [Section 10.4 of /// RFC 9580]), the literal data *must not* be wrapped using the /// [`LiteralWriter`]. /// /// [Section 10.3 of RFC 9580]: https://www.rfc-editor.org/rfc/rfc9580.html#section-10.3 /// [Section 10.4 of RFC 9580]: https://www.rfc-editor.org/rfc/rfc9580.html#section-10.4 /// /// # Examples /// /// ``` /// # fn main() -> sequoia_openpgp::Result<()> { /// use std::io::Write; /// use sequoia_openpgp as openpgp; /// use openpgp::types::Timestamp; /// use openpgp::serialize::stream::{Message, Signer}; /// # use openpgp::policy::StandardPolicy; /// # use openpgp::{Result, Cert}; /// # use openpgp::packet::prelude::*; /// # use openpgp::parse::Parse; /// # use openpgp::parse::stream::*; /// /// # let p = &StandardPolicy::new(); /// # let cert: Cert = // ... /// # Cert::from_bytes(&include_bytes!( /// # "../../tests/data/keys/testy-new-private.pgp")[..])?; /// # let signing_keypair /// # = cert.keys().secret() /// # .with_policy(p, None).supported().alive().revoked(false).for_signing() /// # .nth(0).unwrap() /// # .key().clone().into_keypair()?; /// # /// # let mut sink = vec![]; /// let message = Message::new(&mut sink); /// let message = Signer::new(message, signing_keypair)? /// // Customize the `Signer` here. /// .build()?; /// # Ok(()) } /// ``` pub fn build(mut self) -> Result<Message<'a>> { assert!(!self.signers.is_empty(), "The constructor adds a signer."); assert!(self.inner.is_some(), "The constructor adds an inner writer."); // Possibly configure any armor writer above us. if self.signers.iter().all(|(kp, _, _)| kp.public().version() > 4) { writer::Armorer::set_profile(&mut self, Profile::RFC9580); } for (keypair, signer_hash, signer_salt) in self.signers.iter_mut() { let algo = if let Some(a) = self.hash_algo { a } else { self.acceptable_hash_algos.get(0) .expect("we make sure the set is never empty") .clone() }; *signer_hash = algo; let mut hash = algo.context()? .for_signature(keypair.public().version()); match keypair.public().version() { 4 => { self.hashes.push( if self.template.typ() == SignatureType::Text || self.mode == SignatureMode::Cleartext { HashingMode::Text(vec![], hash) } else { HashingMode::Binary(vec![], hash) }); }, 6 => { // Version 6 signatures are salted, and we // need to include it in the OPS packet. // Generate and remember the salt here. let mut salt = vec![0; algo.salt_size()?]; crate::crypto::random(&mut salt)?; // Add the salted context. hash.update(&salt); self.hashes.push( if self.template.typ() == SignatureType::Text || self.mode == SignatureMode::Cleartext { HashingMode::Text(salt.clone(), hash) } else { HashingMode::Binary(salt.clone(), hash) }); // And remember which signer used which salt. *signer_salt = salt; }, v => return Err(Error::InvalidOperation( format!("Unsupported Key version {}", v)).into()), } } match self.mode { SignatureMode::Inline => { // For every key we collected, build and emit a one pass // signature packet. let signers_count = self.signers.len(); for (i, (keypair, hash_algo, salt)) in self.signers.iter().enumerate() { let last = i == signers_count - 1; let key = keypair.public(); match key.version() { 4 => { let mut ops = OnePassSig3::new(self.template.typ()); ops.set_pk_algo(key.pk_algo()); ops.set_hash_algo(*hash_algo); ops.set_issuer(key.keyid()); ops.set_last(last); Packet::from(ops) .serialize(self.inner.as_mut().unwrap())?; }, 6 => { // Version 6 signatures are salted, and we // need to include it in the OPS packet. let mut ops = OnePassSig6::new( self.template.typ(), key.fingerprint()); ops.set_pk_algo(key.pk_algo()); ops.set_hash_algo(*hash_algo); ops.set_salt(salt.clone()); ops.set_last(last); Packet::from(ops) .serialize(self.inner.as_mut().unwrap())?; }, v => return Err(Error::InvalidOperation( format!("Unsupported Key version {}", v)).into()), } } }, SignatureMode::Detached => (), // Do nothing. SignatureMode::Cleartext => { // Cleartext signatures are always text signatures. self.template = self.template.set_type(SignatureType::Text); // Write the header. let mut sink = self.inner.take().unwrap(); writeln!(sink, "-----BEGIN PGP SIGNED MESSAGE-----")?; let mut hashes = self.signers.iter().filter_map( |(keypair, algo, _)| if keypair.public().version() == 4 { Some(algo) } else { None }) .collect::<Vec<_>>(); hashes.sort(); hashes.dedup(); for hash in hashes { writeln!(sink, "Hash: {}", hash.text_name()?)?; } writeln!(sink)?; // We now install two filters. See the comment on // Signer::cleartext. // Install the filter dash-escaping the text below us. self.inner = Some(writer::BoxStack::from( DashEscapeFilter::new(Message::from(sink), Default::default()))); // Install the filter trimming the trailing whitespace // above us. return Ok(TrailingWSFilter::new(Message::from(Box::new(self)), Default::default())); }, } Ok(Message::from(Box::new(self))) } fn emit_signatures(&mut self) -> Result<()> { if self.mode == SignatureMode::Cleartext { // Pop off the DashEscapeFilter. let mut inner = self.inner.take().expect("It's the DashEscapeFilter") .into_inner()?.expect("It's the DashEscapeFilter"); // Add the separating newline that is not part of the message. writeln!(inner)?; // And install an armorer. self.inner = Some(writer::BoxStack::from( writer::Armorer::new(Message::from(inner), Default::default(), armor::Kind::Signature, Option::<(&str, &str)>::None)?)); } if let Some(ref mut sink) = self.inner { // Emit the signatures in reverse, so that the // one-pass-signature and signature packets "bracket" the // message. for (signer, algo, signer_salt) in self.signers.iter_mut().rev() { let (mut sig, hash) = match signer.public().version() { 4 => { // V4 signature. let hash = self.hashes.iter() .find_map(|hash| { if hash.salt().is_empty() && hash.as_ref().algo() == *algo { Some(hash.clone()) } else { None } }) .expect("we put it in there"); // Make and hash a signature packet. let sig = self.template.clone(); (sig, hash) }, 6 => { // V6 signature. let hash = self.hashes.iter() .find_map(|hash| if signer_salt == hash.salt() { Some(hash.clone()) } else { None }) .expect("we put it in there"); // Make and hash a signature packet. let sig = self.template.clone() .set_prefix_salt(signer_salt.clone()).0; (sig, hash) }, v => return Err(Error::InvalidOperation( format!("Unsupported Key version {}", v)).into()), }; sig = sig.set_signature_creation_time( self.creation_time .unwrap_or_else(crate::now))?; if ! self.intended_recipients.is_empty() { sig = sig.set_intended_recipients( self.intended_recipients.clone())?; } // Compute the signature. let sig = sig.sign_hash(signer.as_mut(), hash.into_inner())?; // And emit the packet. Packet::Signature(sig).serialize(sink)?; } } Ok(()) } } impl<'a> fmt::Debug for Signer<'a> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.debug_struct("Signer") .field("inner", &self.inner) .field("cookie", &self.cookie) .field("mode", &self.mode) .finish() } } impl<'a> Write for Signer<'a> { fn write(&mut self, buf: &[u8]) -> io::Result<usize> { // Shortcut empty writes. This is important for the code // below that delays hashing newlines when creating cleartext // signed messages. if buf.is_empty() { return Ok(0); } use SignatureMode::*; let written = match (self.inner.as_mut(), self.mode) { // If we are creating a normal signature, pass data // through. (Some(ref mut w), Inline) => w.write(buf), // If we are creating a detached signature, just hash all // bytes. (Some(_), Detached) => Ok(buf.len()), // If we are creating a cleartext signed message, just // write through (the DashEscapeFilter takes care of the // encoding), and hash all bytes as is. (Some(ref mut w), Cleartext) => w.write(buf), // When we are popped off the stack, we have no inner // writer. Just hash all bytes. (None, _) => Ok(buf.len()), }; if let Ok(amount) = written { let data = &buf[..amount]; self.hashes.iter_mut().for_each( |hash| hash.update(data)); self.position += amount as u64; } written } fn flush(&mut self) -> io::Result<()> { match self.inner.as_mut() { Some(ref mut w) => w.flush(), // When we are popped off the stack, we have no inner // writer. Just do nothing. None => Ok(()), } } } impl<'a> writer::Stackable<'a, Cookie> for Signer<'a> { fn pop(&mut self) -> Result<Option<writer::BoxStack<'a, Cookie>>> { Ok(self.inner.take()) } fn mount(&mut self, new: writer::BoxStack<'a, Cookie>) { self.inner = Some(new); } fn inner_mut(&mut self) -> Option<&mut (dyn writer::Stackable<'a, Cookie> + Send + Sync)> { if let Some(ref mut i) = self.inner { Some(i) } else { None } } fn inner_ref(&self) -> Option<&(dyn writer::Stackable<'a, Cookie> + Send + Sync)> { self.inner.as_ref().map(|r| r.as_ref()) } fn into_inner(mut self: Box<Self>) -> Result<Option<writer::BoxStack<'a, Cookie>>> { self.emit_signatures()?; Ok(self.inner.take()) } fn cookie_set(&mut self, cookie: Cookie) -> Cookie { ::std::mem::replace(&mut self.cookie, cookie) } fn cookie_ref(&self) -> &Cookie { &self.cookie } fn cookie_mut(&mut self) -> &mut Cookie { &mut self.cookie } fn position(&self) -> u64 { self.position } } /// Writes a literal data packet. /// /// Literal data, i.e. the payload or plaintext, must be wrapped in a /// literal data packet to be transported over OpenPGP (see [Section /// 5.9 of RFC 9580]). The body will be written using partial length /// encoding, or, if the body is short, using full length encoding. /// /// [Section 5.9 of RFC 9580]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.9 /// /// # Note on metadata /// /// A literal data packet can communicate some metadata: a hint as to /// what kind of data is transported, the original file name, and a /// timestamp. Note that this metadata will not be authenticated by /// signatures (but will be authenticated by a SEIP/MDC container), /// and are therefore unreliable and should not be trusted. /// /// Therefore, it is good practice not to set this metadata when /// creating a literal data packet, and not to interpret it when /// consuming one. pub struct LiteralWriter<'a> { template: Literal, inner: writer::BoxStack<'a, Cookie>, signature_writer: Option<writer::BoxStack<'a, Cookie>>, } assert_send_and_sync!(LiteralWriter<'_>); impl<'a> LiteralWriter<'a> { /// Creates a new literal writer. /// /// # Examples /// /// ``` /// # fn main() -> sequoia_openpgp::Result<()> { /// use std::io::Write; /// use sequoia_openpgp as openpgp; /// use openpgp::serialize::stream::{Message, LiteralWriter}; /// /// let mut sink = vec![]; /// { /// let message = Message::new(&mut sink); /// let mut message = LiteralWriter::new(message) /// // Customize the `LiteralWriter` here. /// .build()?; /// message.write_all(b"Hello world.")?; /// message.finalize()?; /// } /// assert_eq!(b"\xcb\x12b\x00\x00\x00\x00\x00Hello world.", /// sink.as_slice()); /// # Ok(()) } /// ``` pub fn new(inner: Message<'a>) -> Self { LiteralWriter { template: Literal::new(DataFormat::default()), inner: writer::BoxStack::from(inner), signature_writer: None, } } /// Sets the data format. /// /// # Examples /// /// ``` /// # fn main() -> sequoia_openpgp::Result<()> { /// use std::io::Write; /// use sequoia_openpgp as openpgp; /// use openpgp::types::DataFormat; /// use openpgp::serialize::stream::{Message, LiteralWriter}; /// /// let mut sink = vec![]; /// { /// let message = Message::new(&mut sink); /// let mut message = LiteralWriter::new(message) /// .format(DataFormat::Unicode) /// .build()?; /// message.write_all(b"Hello world.")?; /// message.finalize()?; /// } /// assert_eq!(b"\xcb\x12u\x00\x00\x00\x00\x00Hello world.", /// sink.as_slice()); /// # Ok(()) } /// ``` pub fn format(mut self, format: DataFormat) -> Self { self.template.set_format(format); self } /// Sets the filename. /// /// The standard does not specify the encoding. Filenames must /// not be longer than 255 bytes. Returns an error if the given /// name is longer than that. /// /// # Examples /// /// ``` /// # fn main() -> sequoia_openpgp::Result<()> { /// use std::io::Write; /// use sequoia_openpgp as openpgp; /// use openpgp::serialize::stream::{Message, LiteralWriter}; /// /// let mut sink = vec![]; /// { /// let message = Message::new(&mut sink); /// let mut message = LiteralWriter::new(message) /// .filename("foobar")? /// .build()?; /// message.write_all(b"Hello world.")?; /// message.finalize()?; /// } /// assert_eq!(b"\xcb\x18b\x06foobar\x00\x00\x00\x00Hello world.", /// sink.as_slice()); /// # Ok(()) } /// ``` pub fn filename<B: AsRef<[u8]>>(mut self, filename: B) -> Result<Self> { self.template.set_filename(filename.as_ref())?; Ok(self) } /// Sets the date. /// /// This date may be the modification date or the creation date. /// Returns an error if the given date is not representable by /// OpenPGP. /// /// # Examples /// /// ``` /// # fn main() -> sequoia_openpgp::Result<()> { /// use std::io::Write; /// use sequoia_openpgp as openpgp; /// use openpgp::types::Timestamp; /// use openpgp::serialize::stream::{Message, LiteralWriter}; /// /// let mut sink = vec![]; /// { /// let message = Message::new(&mut sink); /// let mut message = LiteralWriter::new(message) /// .date(Timestamp::from(1585925313))? /// .build()?; /// message.write_all(b"Hello world.")?; /// message.finalize()?; /// } /// assert_eq!(b"\xcb\x12b\x00\x5e\x87\x4c\xc1Hello world.", /// sink.as_slice()); /// # Ok(()) } /// ``` pub fn date<T: Into<SystemTime>>(mut self, timestamp: T) -> Result<Self> { self.template.set_date(Some(timestamp.into()))?; Ok(self) } /// Builds the literal writer, returning the writer stack. /// /// The next step is to write the payload to the writer stack. /// /// # Examples /// /// ``` /// # fn main() -> sequoia_openpgp::Result<()> { /// use std::io::Write; /// use sequoia_openpgp as openpgp; /// use openpgp::serialize::stream::{Message, LiteralWriter}; /// /// let mut sink = vec![]; /// { /// let message = Message::new(&mut sink); /// let mut message = LiteralWriter::new(message) /// // Customize the `LiteralWriter` here. /// .build()?; /// message.write_all(b"Hello world.")?; /// message.finalize()?; /// } /// assert_eq!(b"\xcb\x12b\x00\x00\x00\x00\x00Hello world.", /// sink.as_slice()); /// # Ok(()) } /// ``` pub fn build(mut self) -> Result<Message<'a>> { let level = self.inner.cookie_ref().level + 1; // For historical reasons, signatures over literal data // packets only include the body without metadata or framing. // Therefore, we check whether the writer is a // Signer, and if so, we pop it off the stack and // store it in 'self.signature_writer'. let signer_above = matches!(self.inner.cookie_ref(), &Cookie { private: Private::Signer{..}, .. }); if signer_above { let stack = self.inner.pop()?; // We know a signer has an inner stackable. let stack = stack.unwrap(); self.signature_writer = Some(self.inner); self.inner = stack; } // Not hashed by the signature_writer (see above). CTB::new(Tag::Literal).serialize(&mut self.inner)?; // Neither is any framing added by the PartialBodyFilter. self.inner = PartialBodyFilter::new(Message::from(self.inner), Cookie::new(level)).into(); // Nor the headers. self.template.serialize_headers(&mut self.inner, false)?; Ok(Message::from(Box::new(self))) } } impl<'a> fmt::Debug for LiteralWriter<'a> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.debug_struct("LiteralWriter") .field("inner", &self.inner) .finish() } } impl<'a> Write for LiteralWriter<'a> { fn write(&mut self, buf: &[u8]) -> io::Result<usize> { let written = self.inner.write(buf); // Any successful written bytes needs to be hashed too. if let (&Ok(ref amount), &mut Some(ref mut sig)) = (&written, &mut self.signature_writer) { sig.write_all(&buf[..*amount])?; }; written } fn flush(&mut self) -> io::Result<()> { self.inner.flush() } } impl<'a> writer::Stackable<'a, Cookie> for LiteralWriter<'a> { fn into_inner(mut self: Box<Self>) -> Result<Option<writer::BoxStack<'a, Cookie>>> { let signer = self.signature_writer.take(); let stack = self.inner .into_inner()?.unwrap(); // Peel off the PartialBodyFilter. if let Some(mut signer) = signer { // We stashed away a Signer. Reattach it to the // stack and return it. signer.mount(stack); Ok(Some(signer)) } else { Ok(Some(stack)) } } fn pop(&mut self) -> Result<Option<writer::BoxStack<'a, Cookie>>> { unreachable!("Only implemented by Signer") } /// Sets the inner stackable. fn mount(&mut self, _new: writer::BoxStack<'a, Cookie>) { unreachable!("Only implemented by Signer") } fn inner_ref(&self) -> Option<&(dyn writer::Stackable<'a, Cookie> + Send + Sync)> { Some(self.inner.as_ref()) } fn inner_mut(&mut self) -> Option<&mut (dyn writer::Stackable<'a, Cookie> + Send + Sync)> { Some(self.inner.as_mut()) } fn cookie_set(&mut self, cookie: Cookie) -> Cookie { self.inner.cookie_set(cookie) } fn cookie_ref(&self) -> &Cookie { self.inner.cookie_ref() } fn cookie_mut(&mut self) -> &mut Cookie { self.inner.cookie_mut() } fn position(&self) -> u64 { self.inner.position() } } /// Compresses a message. /// /// Writes a compressed data packet containing all packets written to /// this writer. pub struct Compressor<'a> { algo: CompressionAlgorithm, level: CompressionLevel, inner: writer::BoxStack<'a, Cookie>, } assert_send_and_sync!(Compressor<'_>); impl<'a> Compressor<'a> { /// Creates a new compressor using the default algorithm and /// compression level. /// /// To change the compression algorithm use [`Compressor::algo`]. /// Use [`Compressor::level`] to change the compression level. /// /// [`Compressor::algo`]: Compressor::algo() /// [`Compressor::level`]: Compressor::level() /// /// # Examples /// /// ``` /// # fn main() -> sequoia_openpgp::Result<()> { /// use std::io::Write; /// use sequoia_openpgp as openpgp; /// use openpgp::serialize::stream::{Message, Compressor, LiteralWriter}; /// use openpgp::types::CompressionAlgorithm; /// /// # let mut sink = vec![]; /// let message = Message::new(&mut sink); /// let message = Compressor::new(message) /// // Customize the `Compressor` here. /// # .algo(CompressionAlgorithm::Uncompressed) /// .build()?; /// let mut message = LiteralWriter::new(message).build()?; /// message.write_all(b"Hello world.")?; /// message.finalize()?; /// # Ok(()) } /// ``` pub fn new(inner: Message<'a>) -> Self { Self { algo: Default::default(), level: Default::default(), inner: inner.into(), } } /// Sets the compression algorithm. /// /// # Examples /// /// ``` /// # fn main() -> sequoia_openpgp::Result<()> { /// use std::io::Write; /// use sequoia_openpgp as openpgp; /// use openpgp::serialize::stream::{Message, Compressor, LiteralWriter}; /// use openpgp::types::CompressionAlgorithm; /// /// let mut sink = vec![]; /// { /// let message = Message::new(&mut sink); /// let message = Compressor::new(message) /// .algo(CompressionAlgorithm::Uncompressed) /// .build()?; /// let mut message = LiteralWriter::new(message).build()?; /// message.write_all(b"Hello world.")?; /// message.finalize()?; /// } /// assert_eq!(b"\xc8\x15\x00\xcb\x12b\x00\x00\x00\x00\x00Hello world.", /// sink.as_slice()); /// # Ok(()) } /// ``` pub fn algo(mut self, algo: CompressionAlgorithm) -> Self { self.algo = algo; self } /// Sets the compression level. /// /// # Examples /// /// ``` /// # fn main() -> sequoia_openpgp::Result<()> { /// use std::io::Write; /// use sequoia_openpgp as openpgp; /// use openpgp::serialize::stream::{Message, Compressor, LiteralWriter}; /// use openpgp::types::{CompressionAlgorithm, CompressionLevel}; /// /// # let mut sink = vec![]; /// let message = Message::new(&mut sink); /// let message = Compressor::new(message) /// # .algo(CompressionAlgorithm::Uncompressed) /// .level(CompressionLevel::fastest()) /// .build()?; /// let mut message = LiteralWriter::new(message).build()?; /// message.write_all(b"Hello world.")?; /// message.finalize()?; /// # Ok(()) } /// ``` pub fn level(mut self, level: CompressionLevel) -> Self { self.level = level; self } /// Builds the compressor, returning the writer stack. /// /// The most useful filter to push to the writer stack next is the /// [`Signer`] or the [`LiteralWriter`]. Finally, literal data /// *must* be wrapped using the [`LiteralWriter`]. /// /// /// # Examples /// /// ``` /// # fn main() -> sequoia_openpgp::Result<()> { /// use std::io::Write; /// use sequoia_openpgp as openpgp; /// use openpgp::serialize::stream::{Message, Compressor, LiteralWriter}; /// use openpgp::types::CompressionAlgorithm; /// /// # let mut sink = vec![]; /// let message = Message::new(&mut sink); /// let message = Compressor::new(message) /// // Customize the `Compressor` here. /// # .algo(CompressionAlgorithm::Uncompressed) /// .build()?; /// let mut message = LiteralWriter::new(message).build()?; /// message.write_all(b"Hello world.")?; /// message.finalize()?; /// # Ok(()) } /// ``` pub fn build(mut self) -> Result<Message<'a>> { let level = self.inner.cookie_ref().level + 1; // Packet header. CTB::new(Tag::CompressedData).serialize(&mut self.inner)?; let inner: Message<'a> = PartialBodyFilter::new(Message::from(self.inner), Cookie::new(level)); Self::new_naked(inner, self.algo, self.level, level) } /// Creates a new compressor using the given algorithm. pub(crate) // For CompressedData::serialize. fn new_naked(mut inner: Message<'a>, algo: CompressionAlgorithm, compression_level: CompressionLevel, level: usize) -> Result<Message<'a>> { // Compressed data header. inner.as_mut().write_u8(algo.into())?; // Create an appropriate filter. let inner: Message<'a> = match algo { CompressionAlgorithm::Uncompressed => { // Avoid warning about unused value if compiled // without any compression support. let _ = compression_level; writer::Identity::new(inner, Cookie::new(level)) }, #[cfg(feature = "compression-deflate")] CompressionAlgorithm::Zip => writer::ZIP::new(inner, Cookie::new(level), compression_level), #[cfg(feature = "compression-deflate")] CompressionAlgorithm::Zlib => writer::ZLIB::new(inner, Cookie::new(level), compression_level), #[cfg(feature = "compression-bzip2")] CompressionAlgorithm::BZip2 => writer::BZ::new(inner, Cookie::new(level), compression_level), a => return Err(Error::UnsupportedCompressionAlgorithm(a).into()), }; Ok(Message::from(Box::new(Self { algo, level: compression_level, inner: inner.into(), }))) } } impl<'a> fmt::Debug for Compressor<'a> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.debug_struct("Compressor") .field("inner", &self.inner) .finish() } } impl<'a> io::Write for Compressor<'a> { fn write(&mut self, buf: &[u8]) -> io::Result<usize> { self.inner.write(buf) } fn flush(&mut self) -> io::Result<()> { self.inner.flush() } } impl<'a> writer::Stackable<'a, Cookie> for Compressor<'a> { fn into_inner(self: Box<Self>) -> Result<Option<writer::BoxStack<'a, Cookie>>> { Box::new(self.inner).into_inner()?.unwrap().into_inner() } fn pop(&mut self) -> Result<Option<writer::BoxStack<'a, Cookie>>> { unreachable!("Only implemented by Signer") } /// Sets the inner stackable. fn mount(&mut self, _new: writer::BoxStack<'a, Cookie>) { unreachable!("Only implemented by Signer") } fn inner_ref(&self) -> Option<&(dyn writer::Stackable<'a, Cookie> + Send + Sync)> { Some(self.inner.as_ref()) } fn inner_mut(&mut self) -> Option<&mut (dyn writer::Stackable<'a, Cookie> + Send + Sync)> { Some(self.inner.as_mut()) } fn cookie_set(&mut self, cookie: Cookie) -> Cookie { self.inner.cookie_set(cookie) } fn cookie_ref(&self) -> &Cookie { self.inner.cookie_ref() } fn cookie_mut(&mut self) -> &mut Cookie { self.inner.cookie_mut() } fn position(&self) -> u64 { self.inner.position() } } /// A recipient of an encrypted message. /// /// OpenPGP messages are encrypted with the subkeys of recipients, /// identified by the keyid of said subkeys in the [`recipient`] field /// of [`PKESK`] packets (see [Section 5.1 of RFC 9580]). The keyid /// may be a wildcard (as returned by [`KeyID::wildcard()`]) to /// obscure the identity of the recipient. /// /// [`recipient`]: crate::packet::PKESK#method.recipient /// [`PKESK`]: crate::packet::PKESK /// [Section 5.1 of RFC 9580]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.1 /// [`KeyID::wildcard()`]: crate::KeyID::wildcard() /// /// Note that several subkeys in a certificate may be suitable /// encryption subkeys. OpenPGP does not specify what should happen /// in this case. Some implementations arbitrarily pick one /// encryption subkey, while others use all of them. This crate does /// not dictate a policy, but allows for arbitrary policies. We do, /// however, suggest to encrypt to all suitable subkeys. #[derive(Debug)] pub struct Recipient<'a> { handle: Option<KeyHandle>, features: Features, key: &'a Key<key::PublicParts, key::UnspecifiedRole>, } assert_send_and_sync!(Recipient<'_>); impl<'a, P> From<ValidSubordinateKeyAmalgamation<'a, P>> for Recipient<'a> where P: key::KeyParts, { fn from(ka: ValidSubordinateKeyAmalgamation<'a, P>) -> Self { let features = ka.valid_cert().features() .unwrap_or_else(Features::empty); let handle: KeyHandle = if features.supports_seipdv2() { ka.key().fingerprint().into() } else { ka.key().keyid().into() }; use crate::cert::Preferences; use crate::cert::amalgamation::ValidAmalgamation; Self::new(features, handle, ka.key().parts_as_public().role_as_unspecified()) } } impl<'a, P> From<ValidErasedKeyAmalgamation<'a, P>> for Recipient<'a> where P: key::KeyParts, { fn from(ka: ValidErasedKeyAmalgamation<'a, P>) -> Self { let features = ka.valid_cert().features() .unwrap_or_else(Features::empty); let handle: KeyHandle = if features.supports_seipdv2() { ka.key().fingerprint().into() } else { ka.key().keyid().into() }; use crate::cert::Preferences; use crate::cert::amalgamation::ValidAmalgamation; Self::new(features, handle, ka.key().parts_as_public().role_as_unspecified()) } } impl<'a> Recipient<'a> { /// Creates a new recipient with an explicit recipient keyid. /// /// Note: If you don't want to change the recipient keyid, /// `Recipient`s can be created from [`Key`] and /// [`ValidKeyAmalgamation`] using [`From`]. /// /// [`Key`]: crate::packet::Key /// [`ValidKeyAmalgamation`]: crate::cert::amalgamation::key::ValidKeyAmalgamation /// [`From`]: std::convert::From /// /// # Examples /// /// ``` /// # fn main() -> sequoia_openpgp::Result<()> { /// use std::io::Write; /// use sequoia_openpgp as openpgp; /// use openpgp::cert::prelude::*; /// use openpgp::serialize::stream::{ /// Recipient, Message, Encryptor, /// }; /// use openpgp::policy::StandardPolicy; /// # use openpgp::parse::Parse; /// /// let p = &StandardPolicy::new(); /// /// let cert = Cert::from_bytes( /// # // We do some acrobatics here to abbreviate the Cert. /// "-----BEGIN PGP PUBLIC KEY BLOCK----- /// /// xjMEWlNvABYJKwYBBAHaRw8BAQdA+EC2pvebpEbzPA9YplVgVXzkIG5eK+7wEAez /// # lcBgLJrNMVRlc3R5IE1jVGVzdGZhY2UgKG15IG5ldyBrZXkpIDx0ZXN0eUBleGFt /// # cGxlLm9yZz7CkAQTFggAOBYhBDnRAKtn1b2MBAECBfs3UfFYfa7xBQJaU28AAhsD /// # BQsJCAcCBhUICQoLAgQWAgMBAh4BAheAAAoJEPs3UfFYfa7xJHQBAO4/GABMWUcJ /// # 5D/DZ9b+6YiFnysSjCT/gILJgxMgl7uoAPwJherI1pAAh49RnPHBR1IkWDtwzX65 /// # CJG8sDyO2FhzDs44BFpTbwASCisGAQQBl1UBBQEBB0B+A0GRHuBgdDX50T1nePjb /// # mKQ5PeqXJbWEtVrUtVJaPwMBCAfCeAQYFggAIBYhBDnRAKtn1b2MBAECBfs3UfFY /// # fa7xBQJaU28AAhsMAAoJEPs3UfFYfa7xzjIBANX2/FgDX3WkmvwpEHg/sn40zACM /// # W2hrBY5x0sZ8H7JlAP47mCfCuRVBqyaePuzKbxLJeLe2BpDdc0n2izMVj8t9Cg== /// # =QetZ /// # -----END PGP PUBLIC KEY BLOCK-----" /// # /* /// ... /// -----END PGP PUBLIC KEY BLOCK-----" /// # */ /// )?; /// /// let recipients = /// cert.keys().with_policy(p, None).supported().alive().revoked(false) /// // Or `for_storage_encryption()`, for data at rest. /// .for_transport_encryption() /// // Make an anonymous recipient. /// .map(|ka| Recipient::new(ka.valid_cert().features(), None, ka.key())); /// /// # let mut sink = vec![]; /// let message = Message::new(&mut sink); /// let message = Encryptor::for_recipients(message, recipients).build()?; /// # let _ = message; /// # Ok(()) } /// ``` pub fn new<F, H, P, R>(features: F, handle: H, key: &'a Key<P, R>) -> Recipient<'a> where F: Into<Option<Features>>, H: Into<Option<KeyHandle>>, P: key::KeyParts, R: key::KeyRole, { Recipient { features: features.into().unwrap_or_else(Features::sequoia), handle: handle.into(), key: key.parts_as_public().role_as_unspecified(), } } /// Gets the recipient keyid. /// /// # Examples /// /// ``` /// # fn main() -> sequoia_openpgp::Result<()> { /// use std::io::Write; /// use sequoia_openpgp as openpgp; /// use openpgp::cert::prelude::*; /// use openpgp::serialize::stream::Recipient; /// use openpgp::policy::StandardPolicy; /// # use openpgp::parse::Parse; /// /// let p = &StandardPolicy::new(); /// /// let cert = Cert::from_bytes( /// # // We do some acrobatics here to abbreviate the Cert. /// "-----BEGIN PGP PUBLIC KEY BLOCK----- /// /// xjMEWlNvABYJKwYBBAHaRw8BAQdA+EC2pvebpEbzPA9YplVgVXzkIG5eK+7wEAez /// # lcBgLJrNMVRlc3R5IE1jVGVzdGZhY2UgKG15IG5ldyBrZXkpIDx0ZXN0eUBleGFt /// # cGxlLm9yZz7CkAQTFggAOBYhBDnRAKtn1b2MBAECBfs3UfFYfa7xBQJaU28AAhsD /// # BQsJCAcCBhUICQoLAgQWAgMBAh4BAheAAAoJEPs3UfFYfa7xJHQBAO4/GABMWUcJ /// # 5D/DZ9b+6YiFnysSjCT/gILJgxMgl7uoAPwJherI1pAAh49RnPHBR1IkWDtwzX65 /// # CJG8sDyO2FhzDs44BFpTbwASCisGAQQBl1UBBQEBB0B+A0GRHuBgdDX50T1nePjb /// # mKQ5PeqXJbWEtVrUtVJaPwMBCAfCeAQYFggAIBYhBDnRAKtn1b2MBAECBfs3UfFY /// # fa7xBQJaU28AAhsMAAoJEPs3UfFYfa7xzjIBANX2/FgDX3WkmvwpEHg/sn40zACM /// # W2hrBY5x0sZ8H7JlAP47mCfCuRVBqyaePuzKbxLJeLe2BpDdc0n2izMVj8t9Cg== /// # =QetZ /// # -----END PGP PUBLIC KEY BLOCK-----" /// # /* /// ... /// -----END PGP PUBLIC KEY BLOCK-----" /// # */ /// )?; /// /// let recipients = /// cert.keys().with_policy(p, None).supported().alive().revoked(false) /// // Or `for_storage_encryption()`, for data at rest. /// .for_transport_encryption() /// .map(Into::into) /// .collect::<Vec<Recipient>>(); /// /// assert_eq!(recipients[0].key_handle().unwrap(), /// "8BD8 8E94 C0D2 0333".parse()?); /// # Ok(()) } /// ``` pub fn key_handle(&self) -> Option<KeyHandle> { self.handle.clone() } /// Sets the recipient key ID or fingerprint. /// /// When setting the recipient for a v6 key, either `None` or a /// fingerprint must be supplied. Returns /// [`Error::InvalidOperation`] if a key ID is given instead. /// /// # Examples /// /// ``` /// # fn main() -> sequoia_openpgp::Result<()> { /// use std::io::Write; /// use sequoia_openpgp as openpgp; /// use openpgp::{KeyHandle, KeyID}; /// use openpgp::cert::prelude::*; /// use openpgp::serialize::stream::{ /// Recipient, Message, Encryptor, /// }; /// use openpgp::policy::StandardPolicy; /// # use openpgp::parse::Parse; /// /// let p = &StandardPolicy::new(); /// /// let cert = Cert::from_bytes( /// # // We do some acrobatics here to abbreviate the Cert. /// "-----BEGIN PGP PUBLIC KEY BLOCK----- /// /// xjMEWlNvABYJKwYBBAHaRw8BAQdA+EC2pvebpEbzPA9YplVgVXzkIG5eK+7wEAez /// # lcBgLJrNMVRlc3R5IE1jVGVzdGZhY2UgKG15IG5ldyBrZXkpIDx0ZXN0eUBleGFt /// # cGxlLm9yZz7CkAQTFggAOBYhBDnRAKtn1b2MBAECBfs3UfFYfa7xBQJaU28AAhsD /// # BQsJCAcCBhUICQoLAgQWAgMBAh4BAheAAAoJEPs3UfFYfa7xJHQBAO4/GABMWUcJ /// # 5D/DZ9b+6YiFnysSjCT/gILJgxMgl7uoAPwJherI1pAAh49RnPHBR1IkWDtwzX65 /// # CJG8sDyO2FhzDs44BFpTbwASCisGAQQBl1UBBQEBB0B+A0GRHuBgdDX50T1nePjb /// # mKQ5PeqXJbWEtVrUtVJaPwMBCAfCeAQYFggAIBYhBDnRAKtn1b2MBAECBfs3UfFY /// # fa7xBQJaU28AAhsMAAoJEPs3UfFYfa7xzjIBANX2/FgDX3WkmvwpEHg/sn40zACM /// # W2hrBY5x0sZ8H7JlAP47mCfCuRVBqyaePuzKbxLJeLe2BpDdc0n2izMVj8t9Cg== /// # =QetZ /// # -----END PGP PUBLIC KEY BLOCK-----" /// # /* /// ... /// -----END PGP PUBLIC KEY BLOCK-----" /// # */ /// )?; /// /// let recipients = /// cert.keys().with_policy(p, None).supported().alive().revoked(false) /// // Or `for_storage_encryption()`, for data at rest. /// .for_transport_encryption() /// .map(|ka| Recipient::from(ka) /// // Set the recipient keyid to the wildcard id. /// .set_key_handle(None) /// .expect("always safe") /// // Same, but explicit. Don't do this. /// .set_key_handle(KeyHandle::KeyID(KeyID::wildcard())) /// .expect("safe for v4 recipient") /// ); /// /// # let mut sink = vec![]; /// let message = Message::new(&mut sink); /// let message = Encryptor::for_recipients(message, recipients).build()?; /// # let _ = message; /// # Ok(()) } /// ``` pub fn set_key_handle<H>(mut self, handle: H) -> Result<Self> where H: Into<Option<KeyHandle>>, { let handle = handle.into(); if self.key.version() == 6 && matches!(handle, Some(KeyHandle::KeyID(_))) { return Err(Error::InvalidOperation( "need a fingerprint for v6 recipient key".into()).into()); } self.handle = handle; Ok(self) } } /// Encrypts a message. /// /// The stream will be encrypted using a generated session key, which /// will be encrypted using the given passwords, and for all given /// recipients. /// /// An [`Recipient`] is an encryption-capable (sub)key. Note that a /// certificate may have more than one encryption-capable subkey, and /// even the primary key may be encryption-capable. /// /// /// To encrypt for more than one certificate, iterate over the /// certificates and select encryption-capable keys, making sure that /// at least one key is selected from each certificate. /// /// # Examples /// /// This demonstrates encrypting for multiple certificates. /// /// ``` /// # fn main() -> sequoia_openpgp::Result<()> { /// # use std::io::Write; /// # use sequoia_openpgp as openpgp; /// # use openpgp::cert::prelude::*; /// # use openpgp::parse::Parse; /// use openpgp::serialize::stream::{ /// Message, Encryptor, LiteralWriter, /// }; /// use openpgp::policy::StandardPolicy; /// let p = &StandardPolicy::new(); /// /// # let (cert_0, _) = /// # CertBuilder::general_purpose(Some("Mr. Pink ☮☮☮")) /// # .generate()?; /// # let (cert_1, _) = /// # CertBuilder::general_purpose(Some("Mr. Pink ☮☮☮")) /// # .generate()?; /// let recipient_certs = vec![cert_0, cert_1]; /// let mut recipients = Vec::new(); /// for cert in recipient_certs.iter() { /// // Make sure we add at least one subkey from every /// // certificate. /// let mut found_one = false; /// for key in cert.keys().with_policy(p, None) /// .supported().alive().revoked(false).for_transport_encryption() /// { /// recipients.push(key); /// found_one = true; /// } /// /// if ! found_one { /// return Err(anyhow::anyhow!("No suitable encryption subkey for {}", /// cert)); /// } /// } /// # assert_eq!(recipients.len(), 2); /// /// # let mut sink = vec![]; /// let message = Message::new(&mut sink); /// let message = Encryptor::for_recipients(message, recipients).build()?; /// let mut w = LiteralWriter::new(message).build()?; /// w.write_all(b"Hello world.")?; /// w.finalize()?; /// # Ok(()) } /// ``` pub struct Encryptor<'a, 'b> where 'b: 'a { inner: writer::BoxStack<'a, Cookie>, session_key: Option<SessionKey>, recipients: Vec<Recipient<'b>>, passwords: Vec<Password>, sym_algo: SymmetricAlgorithm, aead_algo: Option<AEADAlgorithm>, /// For the MDC packet. hash: crypto::hash::Context, cookie: Cookie, } assert_send_and_sync!(Encryptor<'_, '_>); impl<'a, 'b> Encryptor<'a, 'b> { /// Creates a new encryptor for the given recipients. /// /// To add more recipients, use [`Encryptor::add_recipients`]. To /// add passwords, use [`Encryptor::add_passwords`]. To change /// the symmetric encryption algorithm, use /// [`Encryptor::symmetric_algo`]. /// /// # Examples /// /// ``` /// # fn main() -> sequoia_openpgp::Result<()> { /// use std::io::Write; /// use sequoia_openpgp as openpgp; /// use openpgp::cert::prelude::*; /// use openpgp::serialize::stream::{ /// Message, Encryptor, LiteralWriter, /// }; /// use openpgp::policy::StandardPolicy; /// # use openpgp::parse::Parse; /// let p = &StandardPolicy::new(); /// /// let cert = Cert::from_bytes( /// # // We do some acrobatics here to abbreviate the Cert. /// "-----BEGIN PGP PUBLIC KEY BLOCK----- /// /// xjMEWlNvABYJKwYBBAHaRw8BAQdA+EC2pvebpEbzPA9YplVgVXzkIG5eK+7wEAez /// # lcBgLJrNMVRlc3R5IE1jVGVzdGZhY2UgKG15IG5ldyBrZXkpIDx0ZXN0eUBleGFt /// # cGxlLm9yZz7CkAQTFggAOBYhBDnRAKtn1b2MBAECBfs3UfFYfa7xBQJaU28AAhsD /// # BQsJCAcCBhUICQoLAgQWAgMBAh4BAheAAAoJEPs3UfFYfa7xJHQBAO4/GABMWUcJ /// # 5D/DZ9b+6YiFnysSjCT/gILJgxMgl7uoAPwJherI1pAAh49RnPHBR1IkWDtwzX65 /// # CJG8sDyO2FhzDs44BFpTbwASCisGAQQBl1UBBQEBB0B+A0GRHuBgdDX50T1nePjb /// # mKQ5PeqXJbWEtVrUtVJaPwMBCAfCeAQYFggAIBYhBDnRAKtn1b2MBAECBfs3UfFY /// # fa7xBQJaU28AAhsMAAoJEPs3UfFYfa7xzjIBANX2/FgDX3WkmvwpEHg/sn40zACM /// # W2hrBY5x0sZ8H7JlAP47mCfCuRVBqyaePuzKbxLJeLe2BpDdc0n2izMVj8t9Cg== /// # =QetZ /// # -----END PGP PUBLIC KEY BLOCK-----" /// # /* /// ... /// -----END PGP PUBLIC KEY BLOCK-----" /// # */ /// )?; /// /// let recipients = /// cert.keys().with_policy(p, None).supported().alive().revoked(false) /// // Or `for_storage_encryption()`, for data at rest. /// .for_transport_encryption(); /// /// # let mut sink = vec![]; /// let message = Message::new(&mut sink); /// let message = Encryptor::for_recipients(message, recipients).build()?; /// let mut w = LiteralWriter::new(message).build()?; /// w.write_all(b"Hello world.")?; /// w.finalize()?; /// # Ok(()) } /// ``` pub fn for_recipients<R>(inner: Message<'a>, recipients: R) -> Self where R: IntoIterator, R::Item: Into<Recipient<'b>>, { Self { inner: inner.into(), session_key: None, recipients: recipients.into_iter().map(|r| r.into()).collect(), passwords: Vec::new(), sym_algo: Default::default(), aead_algo: Default::default(), hash: HashAlgorithm::SHA1.context().unwrap().for_digest(), cookie: Default::default(), // Will be fixed in build. } } /// Creates a new encryptor for the given passwords. /// /// To add more passwords, use [`Encryptor::add_passwords`]. To /// add recipients, use [`Encryptor::add_recipients`]. To change /// the symmetric encryption algorithm, use /// [`Encryptor::symmetric_algo`]. /// /// # Examples /// /// ``` /// # fn main() -> sequoia_openpgp::Result<()> { /// use std::io::Write; /// use sequoia_openpgp as openpgp; /// use openpgp::serialize::stream::{ /// Message, Encryptor, LiteralWriter, /// }; /// /// # let mut sink = vec![]; /// let message = Message::new(&mut sink); /// let message = Encryptor::with_passwords( /// message, Some("совершенно секретно")).build()?; /// let mut w = LiteralWriter::new(message).build()?; /// w.write_all(b"Hello world.")?; /// w.finalize()?; /// # Ok(()) } /// ``` pub fn with_passwords<P>(inner: Message<'a>, passwords: P) -> Self where P: IntoIterator, P::Item: Into<Password>, { Self { inner: inner.into(), session_key: None, recipients: Vec::new(), passwords: passwords.into_iter().map(|p| p.into()).collect(), sym_algo: Default::default(), aead_algo: Default::default(), hash: HashAlgorithm::SHA1.context().unwrap().for_digest(), cookie: Default::default(), // Will be fixed in build. } } /// Creates a new encryptor for the given algorithm and session /// key. /// /// Usually, the encryptor creates a session key and decrypts it /// for the given recipients and passwords. Using this function, /// the session key can be supplied instead. There are two main /// use cases for this: /// /// - Replying to an encrypted message usually requires the /// encryption (sub)keys for every recipient. If even one key /// is not available, it is not possible to encrypt the new /// session key. Rather than falling back to replying /// unencrypted, one can reuse the original message's session /// key that was encrypted for every recipient and reuse the /// original [`PKESK`]s. /// /// - Using the encryptor if the session key is transmitted or /// derived using a scheme not supported by Sequoia. /// /// To add more passwords, use [`Encryptor::add_passwords`]. To /// add recipients, use [`Encryptor::add_recipients`]. /// /// # Examples /// /// This example demonstrates how to fall back to the original /// message's session key in order to encrypt a reply. /// /// ``` /// # fn main() -> sequoia_openpgp::Result<()> { /// # use std::io::{self, Write}; /// # use sequoia_openpgp as openpgp; /// # use openpgp::{KeyHandle, KeyID, Fingerprint, Result}; /// # use openpgp::cert::prelude::*; /// # use openpgp::packet::prelude::*; /// # use openpgp::crypto::{KeyPair, SessionKey}; /// # use openpgp::types::SymmetricAlgorithm; /// # use openpgp::parse::{Parse, stream::*}; /// # use openpgp::serialize::{Serialize, stream::*}; /// # use openpgp::policy::{Policy, StandardPolicy}; /// # let p = &StandardPolicy::new(); /// # /// // Generate two keys. /// let (alice, _) = CertBuilder::general_purpose( /// Some("Alice Lovelace <alice@example.org>")).generate()?; /// let (bob, _) = CertBuilder::general_purpose( /// Some("Bob Babbage <bob@example.org>")).generate()?; /// /// // Encrypt a message for both keys. /// let recipients = vec![&alice, &bob].into_iter().flat_map(|cert| { /// cert.keys().with_policy(p, None).supported().alive().revoked(false) /// .for_transport_encryption() /// }); /// /// let mut original = vec![]; /// let message = Message::new(&mut original); /// let message = Encryptor::for_recipients(message, recipients).build()?; /// let mut w = LiteralWriter::new(message).build()?; /// w.write_all(b"Original message")?; /// w.finalize()?; /// /// // Decrypt original message using Alice's key. /// let mut decryptor = DecryptorBuilder::from_bytes(&original)? /// .with_policy(p, None, Helper::new(alice))?; /// io::copy(&mut decryptor, &mut io::sink())?; /// let (algo, sk, pkesks) = decryptor.into_helper().recycling_bin.unwrap(); /// /// // Compose the reply using the same session key. /// let mut reply = vec![]; /// let mut message = Message::new(&mut reply); /// for p in pkesks { // Emit the stashed PKESK packets. /// Packet::from(p).serialize(&mut message)?; /// } /// let message = Encryptor::with_session_key( /// message, algo.unwrap_or_default(), sk)? /// .aead_algo(Default::default()) /// .build()?; /// let mut w = LiteralWriter::new(message).build()?; /// w.write_all(b"Encrypted reply")?; /// w.finalize()?; /// /// // Check that Bob can decrypt it. /// let mut decryptor = DecryptorBuilder::from_bytes(&reply)? /// .with_policy(p, None, Helper::new(bob))?; /// io::copy(&mut decryptor, &mut io::sink())?; /// /// /// Decrypts the message preserving algo, session key, and PKESKs. /// struct Helper { /// key: Cert, /// recycling_bin: Option<(Option<SymmetricAlgorithm>, SessionKey, Vec<PKESK>)>, /// } /// /// # impl Helper { /// # fn new(key: Cert) -> Self { /// # Helper { key, recycling_bin: None, } /// # } /// # } /// # /// impl DecryptionHelper for Helper { /// fn decrypt(&mut self, pkesks: &[PKESK], _skesks: &[SKESK], /// sym_algo: Option<SymmetricAlgorithm>, /// decrypt: &mut dyn FnMut(Option<SymmetricAlgorithm>, &SessionKey) -> bool) /// -> Result<Option<Cert>> /// { /// let p = &StandardPolicy::new(); /// let mut encryption_context = None; /// /// for pkesk in pkesks { // Try each PKESK until we succeed. /// for ka in self.key.keys().with_policy(p, None) /// .supported().unencrypted_secret() /// .key_handles(pkesk.recipient()) /// .for_storage_encryption().for_transport_encryption() /// { /// let mut pair = ka.key().clone().into_keypair().unwrap(); /// if pkesk.decrypt(&mut pair, sym_algo) /// .map(|(algo, session_key)| { /// let success = decrypt(algo, &session_key); /// if success { /// // Copy algor, session key, and PKESKs. /// encryption_context = /// Some((algo, session_key.clone(), /// pkesks.iter().cloned().collect())); /// } /// success /// }) /// .unwrap_or(false) /// { /// break; // Decryption successful. /// } /// } /// } /// /// self.recycling_bin = encryption_context; // Store for the reply. /// Ok(Some(self.key.clone())) /// } /// } /// /// impl VerificationHelper for Helper { /// // ... /// # fn get_certs(&mut self, _ids: &[KeyHandle]) -> Result<Vec<Cert>> { /// # Ok(Vec::new()) // Lookup certificates here. /// # } /// # fn check(&mut self, structure: MessageStructure) -> Result<()> { /// # Ok(()) // Implement your verification policy here. /// # } /// } /// # Ok(()) } /// ``` pub fn with_session_key(inner: Message<'a>, sym_algo: SymmetricAlgorithm, session_key: SessionKey) -> Result<Self> { let sym_key_size = sym_algo.key_size()?; if session_key.len() != sym_key_size { return Err(Error::InvalidArgument( format!("{} requires a {} bit key, but session key has {}", sym_algo, sym_key_size, session_key.len())).into()); } Ok(Self { inner: inner.into(), session_key: Some(session_key), recipients: Vec::new(), passwords: Vec::with_capacity(0), sym_algo, aead_algo: Default::default(), hash: HashAlgorithm::SHA1.context().unwrap().for_digest(), cookie: Default::default(), // Will be fixed in build. }) } /// Adds recipients. /// /// The resulting message can be encrypted by any recipient and /// with any password. /// /// # Examples /// /// ``` /// # fn main() -> sequoia_openpgp::Result<()> { /// use std::io::Write; /// use sequoia_openpgp as openpgp; /// use openpgp::cert::prelude::*; /// use openpgp::serialize::stream::{ /// Message, Encryptor, LiteralWriter, /// }; /// use openpgp::policy::StandardPolicy; /// # use openpgp::parse::Parse; /// let p = &StandardPolicy::new(); /// /// let cert = Cert::from_bytes( /// # // We do some acrobatics here to abbreviate the Cert. /// "-----BEGIN PGP PUBLIC KEY BLOCK----- /// /// mQENBFpxtsABCADZcBa1Q3ZLZnju18o0+t8LoQuIIeyeUQ0H45y6xUqyrD5HSkVM /// # VGQs6IHLq70mAizBJ4VznUVqVOh/NhOlapXi6/TKpjHvttdg45o6Pgqa0Kx64luT /// # ZY+TEKyILcdBdhr3CzsEILnQst5jadgMvU9fnT/EkJIvxtWPlUzU5R7nnALO626x /// # 2M5Pj3k0h3ZNHMmYQQtReX/RP/xUh2SfOYG6i/MCclIlee8BXHB9k0bW2NAX2W7H /// # rLDGPm1LzmyqxFGDvDvfPlYZ5nN2cbGsv3w75LDzv75kMhVnkZsrUjnHjVRzFq7q /// # fSIpxlvJMEMKSIJ/TFztQoOBO5OlBb5qzYPpABEBAAG0F+G8iM+BzrnPg8+Ezr/P /// # hM6tzrvOt8+CiQFUBBMBCAA+FiEEfcpYtU6xQxad3uFfJH9tq8hJFP4FAlpxtsAC /// # GwMFCQPCZwAFCwkIBwIGFQgJCgsCBBYCAwECHgECF4AACgkQJH9tq8hJFP49hgf+ /// # IKvec0RkD9EHSLFc6AKDm/knaI4AIH0isZTz9jRCF8H/j3h8QVUE+/0jtCcyvR6F /// # TGVSfO3pelDPYGIjDFI3aA6H/UlhZWzYRXZ+QQRrV0zwvLna3XjiW8ib3Ky+5bpQ /// # 0uVeee30u+U3SnaCL9QB4+UvwVvAxRuk49Z0Q8TsRrQyQNYpeZDN7uNrvA134cf6 /// # 6pLUvzPG4lMLIvSXFuHou704EhT7NS3wAzFtjMrsLLieVqtbEi/kBaJTQSZQwjVB /// # sE/Z8lp1heKw/33Br3cB63n4cTf0FdoFywDBhCAMU7fKboU5xBpm5bQJ4ck6j6w+ /// # BKG1FiQRR6PCUeb6GjxVOrkBDQRacbbAAQgAw538MMb/pRdpt7PTgBCedw+rU9fh /// # onZYKwmCO7wz5VrVf8zIVvWKxhX6fBTSAy8mxaYbeL/3woQ9Leuo8f0PQNs9zw1N /// # mdH+cnm2KQmL9l7/HQKMLgEAu/0C/q7ii/j8OMYitaMUyrwy+OzW3nCal/uJHIfj /// # bdKx29MbKgF/zaBs8mhTvf/Tu0rIVNDPEicwijDEolGSGebZxdGdHJA31uayMHDK /// # /mwySJViMZ8b+Lzc/dRgNbQoY6yjsjso7U9OZpQK1fooHOSQS6iLsSSsZLcGPD+7 /// # m7j3jwq68SIJPMsu0O8hdjFWL4Cfj815CwptAxRGkp00CIusAabO7m8DzwARAQAB /// # iQE2BBgBCAAgFiEEfcpYtU6xQxad3uFfJH9tq8hJFP4FAlpxtsACGwwACgkQJH9t /// # q8hJFP5rmQgAoYOUXolTiQmWipJTdMG/VZ5X7mL8JiBWAQ11K1o01cZCMlziyHnJ /// # xJ6Mqjb6wAFpYBtqysJG/vfjc/XEoKgfFs7+zcuEnt41xJQ6tl/L0VTxs+tEwjZu /// # Rp/owB9GCkqN9+xNEnlH77TLW1UisW+l0F8CJ2WFOj4lk9rcXcLlEdGmXfWIlVCb /// # 2/o0DD+HDNsF8nWHpDEy0mcajkgIUTvXQaDXKbccX6Wgep8dyBP7YucGmRPd9Z6H /// # bGeT3KvlJlH5kthQ9shsmT14gYwGMR6rKpNUXmlpetkjqUK7pGVaHGgJWUZ9QPGU /// # awwPdWWvZSyXJAPZ9lC5sTKwMJDwIxILug== /// # =lAie /// # -----END PGP PUBLIC KEY BLOCK-----" /// # /* /// ... /// -----END PGP PUBLIC KEY BLOCK-----" /// # */ /// )?; /// /// let recipients = /// cert.keys().with_policy(p, None).supported().alive().revoked(false) /// // Or `for_storage_encryption()`, for data at rest. /// .for_transport_encryption(); /// /// # let mut sink = vec![]; /// let message = Message::new(&mut sink); /// let message = /// Encryptor::with_passwords(message, Some("совершенно секретно")) /// .add_recipients(recipients) /// .build()?; /// let mut message = LiteralWriter::new(message).build()?; /// message.write_all(b"Hello world.")?; /// message.finalize()?; /// # Ok(()) } /// ``` pub fn add_recipients<R>(mut self, recipients: R) -> Self where R: IntoIterator, R::Item: Into<Recipient<'b>>, { for r in recipients { self.recipients.push(r.into()); } self } /// Adds passwords to encrypt with. /// /// The resulting message can be encrypted with any password and /// by any recipient. /// /// # Examples /// /// ``` /// # fn main() -> sequoia_openpgp::Result<()> { /// use std::io::Write; /// use sequoia_openpgp as openpgp; /// use openpgp::cert::prelude::*; /// use openpgp::serialize::stream::{ /// Message, Encryptor, LiteralWriter, /// }; /// use openpgp::policy::StandardPolicy; /// # use openpgp::parse::Parse; /// let p = &StandardPolicy::new(); /// /// let cert = Cert::from_bytes( /// # // We do some acrobatics here to abbreviate the Cert. /// "-----BEGIN PGP PUBLIC KEY BLOCK----- /// /// mQENBFpxtsABCADZcBa1Q3ZLZnju18o0+t8LoQuIIeyeUQ0H45y6xUqyrD5HSkVM /// # VGQs6IHLq70mAizBJ4VznUVqVOh/NhOlapXi6/TKpjHvttdg45o6Pgqa0Kx64luT /// # ZY+TEKyILcdBdhr3CzsEILnQst5jadgMvU9fnT/EkJIvxtWPlUzU5R7nnALO626x /// # 2M5Pj3k0h3ZNHMmYQQtReX/RP/xUh2SfOYG6i/MCclIlee8BXHB9k0bW2NAX2W7H /// # rLDGPm1LzmyqxFGDvDvfPlYZ5nN2cbGsv3w75LDzv75kMhVnkZsrUjnHjVRzFq7q /// # fSIpxlvJMEMKSIJ/TFztQoOBO5OlBb5qzYPpABEBAAG0F+G8iM+BzrnPg8+Ezr/P /// # hM6tzrvOt8+CiQFUBBMBCAA+FiEEfcpYtU6xQxad3uFfJH9tq8hJFP4FAlpxtsAC /// # GwMFCQPCZwAFCwkIBwIGFQgJCgsCBBYCAwECHgECF4AACgkQJH9tq8hJFP49hgf+ /// # IKvec0RkD9EHSLFc6AKDm/knaI4AIH0isZTz9jRCF8H/j3h8QVUE+/0jtCcyvR6F /// # TGVSfO3pelDPYGIjDFI3aA6H/UlhZWzYRXZ+QQRrV0zwvLna3XjiW8ib3Ky+5bpQ /// # 0uVeee30u+U3SnaCL9QB4+UvwVvAxRuk49Z0Q8TsRrQyQNYpeZDN7uNrvA134cf6 /// # 6pLUvzPG4lMLIvSXFuHou704EhT7NS3wAzFtjMrsLLieVqtbEi/kBaJTQSZQwjVB /// # sE/Z8lp1heKw/33Br3cB63n4cTf0FdoFywDBhCAMU7fKboU5xBpm5bQJ4ck6j6w+ /// # BKG1FiQRR6PCUeb6GjxVOrkBDQRacbbAAQgAw538MMb/pRdpt7PTgBCedw+rU9fh /// # onZYKwmCO7wz5VrVf8zIVvWKxhX6fBTSAy8mxaYbeL/3woQ9Leuo8f0PQNs9zw1N /// # mdH+cnm2KQmL9l7/HQKMLgEAu/0C/q7ii/j8OMYitaMUyrwy+OzW3nCal/uJHIfj /// # bdKx29MbKgF/zaBs8mhTvf/Tu0rIVNDPEicwijDEolGSGebZxdGdHJA31uayMHDK /// # /mwySJViMZ8b+Lzc/dRgNbQoY6yjsjso7U9OZpQK1fooHOSQS6iLsSSsZLcGPD+7 /// # m7j3jwq68SIJPMsu0O8hdjFWL4Cfj815CwptAxRGkp00CIusAabO7m8DzwARAQAB /// # iQE2BBgBCAAgFiEEfcpYtU6xQxad3uFfJH9tq8hJFP4FAlpxtsACGwwACgkQJH9t /// # q8hJFP5rmQgAoYOUXolTiQmWipJTdMG/VZ5X7mL8JiBWAQ11K1o01cZCMlziyHnJ /// # xJ6Mqjb6wAFpYBtqysJG/vfjc/XEoKgfFs7+zcuEnt41xJQ6tl/L0VTxs+tEwjZu /// # Rp/owB9GCkqN9+xNEnlH77TLW1UisW+l0F8CJ2WFOj4lk9rcXcLlEdGmXfWIlVCb /// # 2/o0DD+HDNsF8nWHpDEy0mcajkgIUTvXQaDXKbccX6Wgep8dyBP7YucGmRPd9Z6H /// # bGeT3KvlJlH5kthQ9shsmT14gYwGMR6rKpNUXmlpetkjqUK7pGVaHGgJWUZ9QPGU /// # awwPdWWvZSyXJAPZ9lC5sTKwMJDwIxILug== /// # =lAie /// # -----END PGP PUBLIC KEY BLOCK-----" /// # /* /// ... /// -----END PGP PUBLIC KEY BLOCK-----" /// # */ /// )?; /// /// let recipients = /// cert.keys().with_policy(p, None).supported().alive().revoked(false) /// // Or `for_storage_encryption()`, for data at rest. /// .for_transport_encryption(); /// /// # let mut sink = vec![]; /// let message = Message::new(&mut sink); /// let message = /// Encryptor::for_recipients(message, recipients) /// .add_passwords(Some("совершенно секретно")) /// .build()?; /// let mut message = LiteralWriter::new(message).build()?; /// message.write_all(b"Hello world.")?; /// message.finalize()?; /// # Ok(()) } /// ``` pub fn add_passwords<P>(mut self, passwords: P) -> Self where P: IntoIterator, P::Item: Into<Password>, { for p in passwords { self.passwords.push(p.into()); } self } /// Sets the symmetric algorithm to use. /// /// # Examples /// /// ``` /// # fn main() -> sequoia_openpgp::Result<()> { /// use std::io::Write; /// use sequoia_openpgp as openpgp; /// use openpgp::types::SymmetricAlgorithm; /// use openpgp::serialize::stream::{ /// Message, Encryptor, LiteralWriter, /// }; /// /// # let mut sink = vec![]; /// let message = Message::new(&mut sink); /// let message = /// Encryptor::with_passwords(message, Some("совершенно секретно")) /// .symmetric_algo(SymmetricAlgorithm::AES128) /// .build()?; /// let mut message = LiteralWriter::new(message).build()?; /// message.write_all(b"Hello world.")?; /// message.finalize()?; /// # Ok(()) } /// ``` pub fn symmetric_algo(mut self, algo: SymmetricAlgorithm) -> Self { self.sym_algo = algo; self } /// Enables AEAD and sets the AEAD algorithm to use. /// /// # Examples /// /// ``` /// # fn main() -> sequoia_openpgp::Result<()> { /// use std::io::Write; /// use sequoia_openpgp as openpgp; /// use openpgp::types::AEADAlgorithm; /// use openpgp::serialize::stream::{ /// Message, Encryptor, LiteralWriter, /// }; /// /// # let mut sink = vec![]; /// let message = Message::new(&mut sink); /// let message = /// Encryptor::with_passwords(message, Some("совершенно секретно")) /// .aead_algo(AEADAlgorithm::default()) /// .build()?; /// let mut message = LiteralWriter::new(message).build()?; /// message.write_all(b"Hello world.")?; /// message.finalize()?; /// # Ok(()) } /// ``` pub fn aead_algo(mut self, algo: AEADAlgorithm) -> Self { self.aead_algo = Some(algo); self } // The default chunk size. // // A page, 3 per mille overhead. const AEAD_CHUNK_SIZE : usize = 4096; /// Builds the encryptor, returning the writer stack. /// /// The most useful filters to push to the writer stack next are /// the [`Padder`] or [`Compressor`], and after that the /// [`Signer`]. Finally, literal data *must* be wrapped using the /// [`LiteralWriter`]. /// /// [`Padder`]: padding::Padder /// /// # Examples /// /// ``` /// # fn main() -> sequoia_openpgp::Result<()> { /// use std::io::Write; /// use sequoia_openpgp as openpgp; /// use openpgp::serialize::stream::{ /// Message, Encryptor, LiteralWriter, /// }; /// /// # let mut sink = vec![]; /// let message = Message::new(&mut sink); /// let message = /// Encryptor::with_passwords(message, Some("совершенно секретно")) /// // Customize the `Encryptor` here. /// .build()?; /// /// // Optionally add a `Padder` or `Compressor` here. /// // Optionally add a `Signer` here. /// /// let mut message = LiteralWriter::new(message).build()?; /// message.write_all(b"Hello world.")?; /// message.finalize()?; /// # Ok(()) } /// ``` pub fn build(mut self) -> Result<Message<'a>> { if self.recipients.len() + self.passwords.len() == 0 && self.session_key.is_none() { return Err(Error::InvalidOperation( "Neither recipients, passwords, nor session key given".into() ).into()); } if self.aead_algo.is_none() { // See whether all recipients support SEIPDv2. if ! self.recipients.is_empty() && self.recipients.iter().all(|r| { r.features.supports_seipdv2() }) { // This prefers OCB if supported. OCB is MTI. self.aead_algo = Some(AEADAlgorithm::const_default()); } } struct AEADParameters { algo: AEADAlgorithm, chunk_size: usize, salt: [u8; 32], } let aead = if let Some(algo) = self.aead_algo { // Configure any armor writer above us. writer::Armorer::set_profile(&mut self, Profile::RFC9580); let mut salt = [0u8; 32]; crypto::random(&mut salt)?; Some(AEADParameters { algo, chunk_size: Self::AEAD_CHUNK_SIZE, salt, }) } else { None }; let mut inner = self.inner; let level = inner.as_ref().cookie_ref().level + 1; // Reuse existing session key or generate a new one. let sym_key_size = self.sym_algo.key_size()?; let sk = self.session_key.take() .map(|sk| Ok(sk)) .unwrap_or_else(|| SessionKey::new(sym_key_size))?; if sk.len() != sym_key_size { return Err(Error::InvalidOperation( format!("{} requires a {} bit key, but session key has {}", self.sym_algo, sym_key_size, sk.len())).into()); } // Write the PKESK packet(s). for recipient in self.recipients.iter() { if aead.is_some() { let mut pkesk = PKESK6::for_recipient(&sk, recipient.key)?; pkesk.set_recipient(recipient.key_handle() .map(TryInto::try_into) .transpose()?); Packet::from(pkesk).serialize(&mut inner)?; } else { let mut pkesk = PKESK3::for_recipient(self.sym_algo, &sk, recipient.key)?; pkesk.set_recipient(recipient.key_handle().map(Into::into)); Packet::PKESK(pkesk.into()).serialize(&mut inner)?; } } // Write the SKESK packet(s). for password in self.passwords.iter() { if let Some(aead) = aead.as_ref() { let skesk = SKESK6::with_password(self.sym_algo, self.sym_algo, aead.algo, Default::default(), &sk, password).unwrap(); Packet::SKESK(skesk.into()).serialize(&mut inner)?; } else { let skesk = SKESK4::with_password(self.sym_algo, self.sym_algo, Default::default(), &sk, password).unwrap(); Packet::SKESK(skesk.into()).serialize(&mut inner)?; } } if let Some(aead) = aead { // Write the SEIPDv2 packet. CTB::new(Tag::SEIP).serialize(&mut inner)?; let mut inner = PartialBodyFilter::new(Message::from(inner), Cookie::new(level)); let seip = SEIP2::new(self.sym_algo, aead.algo, aead.chunk_size as u64, aead.salt)?; seip.serialize_headers(&mut inner)?; use crate::crypto::aead::SEIPv2Schedule; let (message_key, schedule) = SEIPv2Schedule::new( &sk, seip.symmetric_algo(), seip.aead(), aead.chunk_size, seip.salt())?; // Note: we have consumed self, and we are returning a // different encryptor here. self will be dropped, and // therefore, Self::emit_mdc will not be invoked. writer::AEADEncryptor::new( inner, Cookie::new(level).set_private(Private::Encryptor { profile: Profile::RFC9580, }), seip.symmetric_algo(), seip.aead(), aead.chunk_size, schedule, message_key, ) } else { // Write the SEIPDv1 packet. CTB::new(Tag::SEIP).serialize(&mut inner)?; let mut inner = PartialBodyFilter::new(Message::from(inner), Cookie::new(level)); inner.write_all(&[1])?; // Version. // Install encryptor. self.inner = writer::Encryptor::new( inner, Cookie::new(level), self.sym_algo, &sk, )?.into(); self.cookie = Cookie::new(level) .set_private(Private::Encryptor { profile: Profile::RFC4880, }); // Write the initialization vector, and the quick-check // bytes. The hash for the MDC must include the // initialization vector, hence we must write this to // self after installing the encryptor at self.inner. let mut iv = vec![0; self.sym_algo.block_size()?]; crypto::random(&mut iv)?; self.write_all(&iv)?; self.write_all(&iv[iv.len() - 2..])?; Ok(Message::from(Box::new(self))) } } /// Emits the MDC packet and recovers the original writer. /// /// Note: This is only invoked for SEIPDv1 messages, because /// Self::build consumes self, and will only return a writer stack /// with Self on top for SEIPDv1 messages. For SEIPDv2 messages, /// a different writer is returned. fn emit_mdc(mut self) -> Result<writer::BoxStack<'a, Cookie>> { let mut w = self.inner; // Write the MDC, which must be the last packet inside the // encrypted packet stream. The hash includes the MDC's // CTB and length octet. let mut header = Vec::new(); CTB::new(Tag::MDC).serialize(&mut header)?; BodyLength::Full(20).serialize(&mut header)?; self.hash.update(&header); #[allow(deprecated)] Packet::MDC(MDC::from(self.hash.clone())).serialize(&mut w)?; // Now recover the original writer. First, strip the // Encryptor. let w = w.into_inner()?.unwrap(); // And the partial body filter. let w = w.into_inner()?.unwrap(); Ok(w) } } impl<'a, 'b> fmt::Debug for Encryptor<'a, 'b> where 'b: 'a { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.debug_struct("Encryptor") .field("inner", &self.inner) .finish() } } impl<'a, 'b> Write for Encryptor<'a, 'b> where 'b: 'a { fn write(&mut self, buf: &[u8]) -> io::Result<usize> { let written = self.inner.write(buf); if let Ok(amount) = written { self.hash.update(&buf[..amount]); } written } fn flush(&mut self) -> io::Result<()> { self.inner.flush() } } impl<'a, 'b> writer::Stackable<'a, Cookie> for Encryptor<'a, 'b> where 'b: 'a { fn pop(&mut self) -> Result<Option<writer::BoxStack<'a, Cookie>>> { unreachable!("Only implemented by Signer") } /// Sets the inner stackable. fn mount(&mut self, _new: writer::BoxStack<'a, Cookie>) { unreachable!("Only implemented by Signer") } fn inner_ref(&self) -> Option<&(dyn writer::Stackable<'a, Cookie> + Send + Sync)> { Some(&self.inner) } fn inner_mut(&mut self) -> Option<&mut (dyn writer::Stackable<'a, Cookie> + Send + Sync)> { Some(&mut self.inner) } fn into_inner(self: Box<Self>) -> Result<Option<writer::BoxStack<'a, Cookie>>> { Ok(Some(self.emit_mdc()?)) } fn cookie_set(&mut self, cookie: Cookie) -> Cookie { ::std::mem::replace(&mut self.cookie, cookie) } fn cookie_ref(&self) -> &Cookie { &self.cookie } fn cookie_mut(&mut self) -> &mut Cookie { &mut self.cookie } fn position(&self) -> u64 { self.inner.position() } } #[cfg(test)] mod test { use std::io::Read; use crate::{Packet, PacketPile, Profile, packet::CompressedData}; use crate::parse::{Parse, PacketParserResult, PacketParser}; use super::*; use crate::types::DataFormat::Unicode as T; use crate::policy::Policy; use crate::policy::StandardPolicy as P; #[test] fn arbitrary() { let mut o = vec![]; { let m = Message::new(&mut o); let mut ustr = ArbitraryWriter::new(m, Tag::Literal).unwrap(); ustr.write_all(b"u").unwrap(); // type ustr.write_all(b"\x00").unwrap(); // fn length ustr.write_all(b"\x00\x00\x00\x00").unwrap(); // date ustr.write_all(b"Hello world.").unwrap(); // body ustr.finalize().unwrap(); } let mut pp = PacketParser::from_bytes(&o).unwrap().unwrap(); if let Packet::Literal(ref l) = pp.packet { assert_eq!(l.format(), DataFormat::Unicode); assert_eq!(l.filename(), None); assert_eq!(l.date(), None); } else { panic!("Unexpected packet type."); } let mut body = vec![]; pp.read_to_end(&mut body).unwrap(); assert_eq!(&body, b"Hello world."); // Make sure it is the only packet. let (_, ppr) = pp.recurse().unwrap(); assert!(ppr.is_eof()); } // Create some crazy nesting structures, serialize the messages, // reparse them, and make sure we get the same result. #[test] fn stream_0() { // 1: CompressedData(CompressedData { algo: 0 }) // 1: Literal(Literal { body: "one (3 bytes)" }) // 2: Literal(Literal { body: "two (3 bytes)" }) // 2: Literal(Literal { body: "three (5 bytes)" }) let mut one = Literal::new(T); one.set_body(b"one".to_vec()); let mut two = Literal::new(T); two.set_body(b"two".to_vec()); let mut three = Literal::new(T); three.set_body(b"three".to_vec()); let mut reference = Vec::new(); reference.push( CompressedData::new(CompressionAlgorithm::Uncompressed) .push(one.into()) .push(two.into()) .into()); reference.push(three.into()); let mut o = vec![]; { let m = Message::new(&mut o); let c = Compressor::new(m) .algo(CompressionAlgorithm::Uncompressed).build().unwrap(); let mut ls = LiteralWriter::new(c).format(T).build().unwrap(); write!(ls, "one").unwrap(); let c = ls.finalize_one().unwrap().unwrap(); // Pop the LiteralWriter. let mut ls = LiteralWriter::new(c).format(T).build().unwrap(); write!(ls, "two").unwrap(); let c = ls.finalize_one().unwrap().unwrap(); // Pop the LiteralWriter. let c = c.finalize_one().unwrap().unwrap(); // Pop the Compressor. let mut ls = LiteralWriter::new(c).format(T).build().unwrap(); write!(ls, "three").unwrap(); ls.finalize().unwrap(); } let pile = PacketPile::from(reference); let pile2 = PacketPile::from_bytes(&o).unwrap(); if pile != pile2 { eprintln!("REFERENCE..."); pile.pretty_print(); eprintln!("REPARSED..."); pile2.pretty_print(); panic!("Reparsed packet does not match reference packet!"); } } // Create some crazy nesting structures, serialize the messages, // reparse them, and make sure we get the same result. #[test] fn stream_1() { // 1: CompressedData(CompressedData { algo: 0 }) // 1: CompressedData(CompressedData { algo: 0 }) // 1: Literal(Literal { body: "one (3 bytes)" }) // 2: Literal(Literal { body: "two (3 bytes)" }) // 2: CompressedData(CompressedData { algo: 0 }) // 1: Literal(Literal { body: "three (5 bytes)" }) // 2: Literal(Literal { body: "four (4 bytes)" }) let mut one = Literal::new(T); one.set_body(b"one".to_vec()); let mut two = Literal::new(T); two.set_body(b"two".to_vec()); let mut three = Literal::new(T); three.set_body(b"three".to_vec()); let mut four = Literal::new(T); four.set_body(b"four".to_vec()); let mut reference = Vec::new(); reference.push( CompressedData::new(CompressionAlgorithm::Uncompressed) .push(CompressedData::new(CompressionAlgorithm::Uncompressed) .push(one.into()) .push(two.into()) .into()) .push(CompressedData::new(CompressionAlgorithm::Uncompressed) .push(three.into()) .push(four.into()) .into()) .into()); let mut o = vec![]; { let m = Message::new(&mut o); let c0 = Compressor::new(m) .algo(CompressionAlgorithm::Uncompressed).build().unwrap(); let c = Compressor::new(c0) .algo(CompressionAlgorithm::Uncompressed).build().unwrap(); let mut ls = LiteralWriter::new(c).format(T).build().unwrap(); write!(ls, "one").unwrap(); let c = ls.finalize_one().unwrap().unwrap(); let mut ls = LiteralWriter::new(c).format(T).build().unwrap(); write!(ls, "two").unwrap(); let c = ls.finalize_one().unwrap().unwrap(); let c0 = c.finalize_one().unwrap().unwrap(); let c = Compressor::new(c0) .algo(CompressionAlgorithm::Uncompressed).build().unwrap(); let mut ls = LiteralWriter::new(c).format(T).build().unwrap(); write!(ls, "three").unwrap(); let c = ls.finalize_one().unwrap().unwrap(); let mut ls = LiteralWriter::new(c).format(T).build().unwrap(); write!(ls, "four").unwrap(); ls.finalize().unwrap(); } let pile = PacketPile::from(reference); let pile2 = PacketPile::from_bytes(&o).unwrap(); if pile != pile2 { eprintln!("REFERENCE..."); pile.pretty_print(); eprintln!("REPARSED..."); pile2.pretty_print(); panic!("Reparsed packet does not match reference packet!"); } } #[cfg(feature = "compression-bzip2")] #[test] fn stream_big() { let zeros = vec![0; 1024 * 1024 * 4]; let mut o = vec![]; { let m = Message::new(&mut o); let c = Compressor::new(m) .algo(CompressionAlgorithm::BZip2).build().unwrap(); let mut ls = LiteralWriter::new(c).build().unwrap(); // Write 64 megabytes of zeroes. for _ in 0 .. 16 { ls.write_all(&zeros).unwrap(); } } assert!(o.len() < 1024); } #[test] fn signature() { let p = &P::new(); use crate::crypto::KeyPair; use std::collections::HashMap; use crate::Fingerprint; let mut keys: HashMap<Fingerprint, key::UnspecifiedPublic> = HashMap::new(); for tsk in &[ Cert::from_bytes(crate::tests::key("testy-private.pgp")).unwrap(), Cert::from_bytes(crate::tests::key("testy-new-private.pgp")).unwrap(), ] { for key in tsk.keys().with_policy(p, crate::frozen_time()) .for_signing().map(|ka| ka.key()) { keys.insert(key.fingerprint(), key.clone()); } } let mut o = vec![]; { let mut signers = keys.iter().map(|(_, key)| { key.clone().parts_into_secret().unwrap().into_keypair() .expect("expected unencrypted secret key") }).collect::<Vec<KeyPair>>(); let m = Message::new(&mut o); let mut signer = Signer::new(m, signers.pop().unwrap()).unwrap(); for s in signers.into_iter() { signer = signer.add_signer(s).unwrap(); } let signer = signer.build().unwrap(); let mut ls = LiteralWriter::new(signer).build().unwrap(); ls.write_all(b"Tis, tis, tis. Tis is important.").unwrap(); let _ = ls.finalize().unwrap(); } let mut ppr = PacketParser::from_bytes(&o).unwrap(); let mut good = 0; while let PacketParserResult::Some(mut pp) = ppr { if let Packet::Signature(sig) = &mut pp.packet { let key = keys.get(sig.issuer_fingerprints().next().unwrap()) .unwrap(); sig.verify_document(key).unwrap(); good += 1; } // Get the next packet. ppr = pp.recurse().unwrap().1; } assert_eq!(good, 2); } #[test] fn encryptor() { let passwords = vec!["streng geheim".into(), "top secret".into()]; let message = b"Hello world."; // Write a simple encrypted message... let mut o = vec![]; { let m = Message::new(&mut o); let encryptor = Encryptor::with_passwords(m, passwords.clone()) .build().unwrap(); let mut literal = LiteralWriter::new(encryptor).build() .unwrap(); literal.write_all(message).unwrap(); literal.finalize().unwrap(); } // ... and recover it... #[derive(Debug, PartialEq)] enum State { Start, Decrypted(Vec<(Option<SymmetricAlgorithm>, SessionKey)>), Deciphered, MDC, Done, } // ... with every password. for password in &passwords { let mut state = State::Start; let mut ppr = PacketParser::from_bytes(&o).unwrap(); while let PacketParserResult::Some(mut pp) = ppr { state = match state { // Look for the SKESK packet. State::Start => if let Packet::SKESK(ref skesk) = pp.packet { match skesk.decrypt(password) { Ok((algo, key)) => State::Decrypted( vec![(algo, key)]), Err(e) => panic!("Decryption failed: {}", e), } } else { panic!("Unexpected packet: {:?}", pp.packet) }, // Look for the SEIP packet. State::Decrypted(mut keys) => match pp.packet { Packet::SEIP(_) => loop { if let Some((algo, key)) = keys.pop() { let r = pp.decrypt(algo, &key); if r.is_ok() { break State::Deciphered; } } else { panic!("seip decryption failed"); } }, Packet::SKESK(ref skesk) => match skesk.decrypt(password) { Ok((algo, key)) => { keys.push((algo, key)); State::Decrypted(keys) }, Err(e) => panic!("Decryption failed: {}", e), }, _ => panic!("Unexpected packet: {:?}", pp.packet), }, // Look for the literal data packet. State::Deciphered => if let Packet::Literal(_) = pp.packet { let mut body = Vec::new(); pp.read_to_end(&mut body).unwrap(); assert_eq!(&body, message); State::MDC } else { panic!("Unexpected packet: {:?}", pp.packet) }, // Look for the MDC packet. #[allow(deprecated)] State::MDC => if let Packet::MDC(ref mdc) = pp.packet { assert_eq!(mdc.digest(), mdc.computed_digest()); State::Done } else { panic!("Unexpected packet: {:?}", pp.packet) }, State::Done => panic!("Unexpected packet: {:?}", pp.packet), }; // Next? ppr = pp.recurse().unwrap().1; } assert_eq!(state, State::Done); } } #[test] fn aead_eax() -> Result<()> { test_aead_messages(AEADAlgorithm::EAX) } #[test] fn aead_ocb() -> Result<()> { test_aead_messages(AEADAlgorithm::OCB) } #[test] fn aead_gcm() -> Result<()> { test_aead_messages(AEADAlgorithm::GCM) } fn test_aead_messages(algo: AEADAlgorithm) -> Result<()> { test_aead_messages_v(algo, Profile::RFC4880)?; test_aead_messages_v(algo, Profile::RFC9580)?; Ok(()) } fn test_aead_messages_v(algo: AEADAlgorithm, profile: Profile) -> Result<()> { eprintln!("Testing with {:?}", profile); if ! algo.is_supported() { eprintln!("Skipping because {} is not supported.", algo); return Ok(()); } // AEAD data is of the form: // // [ chunk1 ][ tag1 ] ... [ chunkN ][ tagN ][ tag ] // // All chunks are the same size except for the last chunk, which may // be shorter. // // In `Decryptor::read_helper`, we read a chunk and a tag worth of // data at a time. Because only the last chunk can be shorter, if // the amount read is less than `chunk_size + tag_size`, then we know // that we've read the last chunk. // // Unfortunately, this is not sufficient: if the last chunk is // `chunk_size - tag size` bytes large, then when we read it, we'll // read `chunk_size + tag_size` bytes, because we'll have also read // the final tag! // // Make sure we handle this situation correctly. use std::cmp; use crate::parse::{ stream::{ DecryptorBuilder, DecryptionHelper, VerificationHelper, MessageStructure, }, }; use crate::cert::prelude::*; let (tsk, _) = CertBuilder::new() .set_cipher_suite(CipherSuite::Cv25519) .set_profile(profile)? .add_transport_encryption_subkey() .generate().unwrap(); struct Helper<'a> { policy: &'a dyn Policy, tsk: &'a Cert, } impl<'a> VerificationHelper for Helper<'a> { fn get_certs(&mut self, _ids: &[crate::KeyHandle]) -> Result<Vec<Cert>> { Ok(Vec::new()) } fn check(&mut self, _structure: MessageStructure) -> Result<()> { Ok(()) } fn inspect(&mut self, pp: &PacketParser<'_>) -> Result<()> { assert!(! matches!(&pp.packet, Packet::Unknown(_))); eprintln!("Parsed {:?}", pp.packet); Ok(()) } } impl<'a> DecryptionHelper for Helper<'a> { fn decrypt(&mut self, pkesks: &[PKESK], _skesks: &[SKESK], sym_algo: Option<SymmetricAlgorithm>, decrypt: &mut dyn FnMut(Option<SymmetricAlgorithm>, &SessionKey) -> bool) -> Result<Option<Cert>> { let mut keypair = self.tsk.keys().with_policy(self.policy, None) .for_transport_encryption() .map(|ka| ka.key()).next().unwrap() .clone().parts_into_secret().unwrap() .into_keypair().unwrap(); pkesks[0].decrypt(&mut keypair, sym_algo) .map(|(algo, session_key)| decrypt(algo, &session_key)); Ok(None) } } let p = unsafe { &crate::policy::NullPolicy::new() }; for chunks in 0..3 { for msg_len in cmp::max(24, chunks * Encryptor::AEAD_CHUNK_SIZE) - 24 ..chunks * Encryptor::AEAD_CHUNK_SIZE + 24 { eprintln!("Encrypting message of size: {}", msg_len); let mut content : Vec<u8> = Vec::new(); for i in 0..msg_len { content.push(b'0' + ((i % 10) as u8)); } let mut msg = vec![]; { let m = Message::new(&mut msg); let recipients = tsk .keys().with_policy(p, None) .for_storage_encryption().for_transport_encryption(); let encryptor = Encryptor::for_recipients(m, recipients) .aead_algo(algo) .build().unwrap(); let mut literal = LiteralWriter::new(encryptor).build() .unwrap(); literal.write_all(&content).unwrap(); literal.finalize().unwrap(); } for &read_len in &[ 37, Encryptor::AEAD_CHUNK_SIZE - 1, Encryptor::AEAD_CHUNK_SIZE, 100 * Encryptor::AEAD_CHUNK_SIZE ] { for &do_err in &[ false, true ] { let mut msg = msg.clone(); if do_err { let l = msg.len() - 1; if msg[l] == 0 { msg[l] = 1; } else { msg[l] = 0; } } let h = Helper { policy: p, tsk: &tsk }; // Note: a corrupted message is only guaranteed // to error out before it returns EOF. let mut v = match DecryptorBuilder::from_bytes(&msg)? .with_policy(p, None, h) { Ok(v) => v, Err(_) if do_err => continue, Err(err) => panic!("Decrypting message: {}", err), }; let mut buffer = Vec::new(); buffer.resize(read_len, 0); let mut decrypted_content = Vec::new(); loop { match v.read(&mut buffer[..read_len]) { Ok(0) if do_err => panic!("Expected an error, got EOF"), Ok(0) => break, Ok(len) => decrypted_content.extend_from_slice( &buffer[..len]), Err(_) if do_err => break, Err(err) => panic!("Decrypting data: {:?}", err), } } if do_err { // If we get an error once, we should get // one again. for _ in 0..3 { assert!(v.read(&mut buffer[..read_len]).is_err()); } } // We only corrupted the final tag, so we // should get all the content. assert_eq!(msg_len, decrypted_content.len()); assert_eq!(content, decrypted_content); } } } } Ok(()) } #[test] fn signature_at_time() { // Generates a signature with a specific Signature Creation // Time. use crate::cert::prelude::*; use crate::serialize::stream::{LiteralWriter, Message}; use crate::crypto::KeyPair; let p = &P::new(); let (cert, _) = CertBuilder::new() .add_signing_subkey() .set_cipher_suite(CipherSuite::Cv25519) .generate().unwrap(); // What we're going to sign with. let ka = cert.keys().with_policy(p, None).for_signing().next().unwrap(); // A timestamp later than the key's creation. let timestamp = ka.key().creation_time() + std::time::Duration::from_secs(14 * 24 * 60 * 60); assert!(ka.key().creation_time() < timestamp); let mut o = vec![]; { let signer_keypair : KeyPair = ka.key().clone().parts_into_secret().unwrap().into_keypair() .expect("expected unencrypted secret key"); let m = Message::new(&mut o); let signer = Signer::new(m, signer_keypair).unwrap(); let signer = signer.creation_time(timestamp); let signer = signer.build().unwrap(); let mut ls = LiteralWriter::new(signer).build().unwrap(); ls.write_all(b"Tis, tis, tis. Tis is important.").unwrap(); let signer = ls.finalize_one().unwrap().unwrap(); let _ = signer.finalize_one().unwrap().unwrap(); } let mut ppr = PacketParser::from_bytes(&o).unwrap(); let mut good = 0; while let PacketParserResult::Some(mut pp) = ppr { if let Packet::Signature(sig) = &mut pp.packet { assert_eq!(sig.signature_creation_time(), Some(timestamp)); sig.verify_document(ka.key()).unwrap(); good += 1; } // Get the next packet. ppr = pp.recurse().unwrap().1; } assert_eq!(good, 1); } /// Checks that newlines are properly normalized when verifying /// text signatures. #[test] fn issue_530_signing() -> Result<()> { use std::io::Write; use crate::*; use crate::packet::signature; use crate::serialize::stream::{Message, Signer}; use crate::policy::StandardPolicy; use crate::{Result, Cert}; use crate::parse::Parse; use crate::parse::stream::*; let normalized_data = b"one\r\ntwo\r\nthree"; let p = &StandardPolicy::new(); let cert: Cert = Cert::from_bytes(crate::tests::key("testy-new-private.pgp"))?; for data in &[ &b"one\r\ntwo\r\nthree"[..], // dos b"one\ntwo\nthree", // unix b"one\ntwo\r\nthree", // mixed b"one\r\ntwo\nthree", b"one\rtwo\rthree", // classic mac ] { eprintln!("{:?}", String::from_utf8(data.to_vec())?); let signing_keypair = cert.keys().secret() .with_policy(p, None).supported() .alive().revoked(false).for_signing().next().unwrap() .key().clone().into_keypair()?; let mut signature = vec![]; { let message = Message::new(&mut signature); let mut message = Signer::with_template( message, signing_keypair, signature::SignatureBuilder::new(SignatureType::Text) )?.detached().build()?; message.write_all(data)?; message.finalize()?; } struct Helper {} impl VerificationHelper for Helper { fn get_certs(&mut self, _ids: &[KeyHandle]) -> Result<Vec<Cert>> { Ok(vec![ Cert::from_bytes(crate::tests::key("testy-new.pgp"))?]) } fn check(&mut self, structure: MessageStructure) -> Result<()> { for (i, layer) in structure.iter().enumerate() { assert_eq!(i, 0); if let MessageLayer::SignatureGroup { results } = layer { assert_eq!(results.len(), 1); results[0].as_ref().unwrap(); assert!(results[0].is_ok()); return Ok(()); } else { unreachable!(); } } unreachable!() } } let h = Helper {}; let mut v = DetachedVerifierBuilder::from_bytes(&signature)? .with_policy(p, None, h)?; v.verify_bytes(data)?; v.verify_bytes(normalized_data)?; } Ok(()) } struct BadSigner; impl crypto::Signer for BadSigner { fn public(&self) -> &Key<key::PublicParts, key::UnspecifiedRole> { panic!("public not impl") } /// Returns a list of hashes that this signer accepts. fn acceptable_hashes(&self) -> &[HashAlgorithm] { &[] } fn sign(&mut self, _hash_algo: HashAlgorithm, _digest: &[u8]) -> Result<crypto::mpi::Signature> { panic!("sign not impl") } } struct GoodSigner(Vec<HashAlgorithm>, Key<key::PublicParts, key::UnspecifiedRole>); impl crypto::Signer for GoodSigner { fn public(&self) -> &Key<key::PublicParts, key::UnspecifiedRole> { &self.1 } /// Returns a list of hashes that this signer accepts. fn acceptable_hashes(&self) -> &[HashAlgorithm] { &self.0 } fn sign(&mut self, _hash_algo: HashAlgorithm, _digest: &[u8]) -> Result<crypto::mpi::Signature> { unimplemented!() } } impl Default for GoodSigner { fn default() -> Self { let p = &P::new(); let (cert, _) = CertBuilder::new().generate().unwrap(); let ka = cert.keys().with_policy(p, None).next().unwrap(); Self(vec![HashAlgorithm::default()], ka.key().clone()) } } #[test] fn overlapping_hashes() { let mut signature = vec![]; let message = Message::new(&mut signature); Signer::new(message, GoodSigner::default()).unwrap().build().unwrap(); } #[test] fn no_overlapping_hashes() { let mut signature = vec![]; let message = Message::new(&mut signature); if let Err(e) = Signer::new(message, BadSigner) { assert_eq!(e.downcast_ref::<Error>(), Some(&Error::NoAcceptableHash)); } else { unreachable!(); }; } #[test] fn no_overlapping_hashes_for_new_signer() { let mut signature = vec![]; let message = Message::new(&mut signature); let signer = Signer::new(message, GoodSigner::default()).unwrap(); if let Err(e) = signer.add_signer(BadSigner) { assert_eq!(e.downcast_ref::<Error>(), Some(&Error::NoAcceptableHash)); } else { unreachable!(); }; } /// Tests that multiple signatures are in the correct order. #[test] fn issue_816() -> Result<()> { use crate::{ packet::key::{Key4, PrimaryRole}, types::Curve, KeyHandle, }; let signer_a = Key4::<_, PrimaryRole>::generate_ecc(true, Curve::Ed25519)? .into_keypair()?; let signer_b = Key4::<_, PrimaryRole>::generate_ecc(true, Curve::Ed25519)? .into_keypair()?; let mut sink = Vec::new(); let message = Message::new(&mut sink); let message = Signer::new(message, signer_a)? .add_signer(signer_b)? .build()?; let mut message = LiteralWriter::new(message).build()?; message.write_all(b"Make it so, number one!")?; message.finalize()?; let pp = crate::PacketPile::from_bytes(&sink)?; assert_eq!(pp.children().count(), 5); let first_signer: KeyHandle = if let Packet::OnePassSig(ops) = pp.path_ref(&[0]).unwrap() { ops.issuer().into() } else { panic!("expected ops packet") }; let second_signer: KeyHandle = if let Packet::OnePassSig(ops) = pp.path_ref(&[1]).unwrap() { ops.issuer().into() } else { panic!("expected ops packet") }; assert!(matches!(pp.path_ref(&[2]).unwrap(), Packet::Literal(_))); // OPS and Signature packets "bracket" the literal, i.e. the // last occurring ops packet is met by the first occurring // signature packet. if let Packet::Signature(sig) = pp.path_ref(&[3]).unwrap() { assert!(sig.get_issuers()[0].aliases(&second_signer)); } else { panic!("expected sig packet") } if let Packet::Signature(sig) = pp.path_ref(&[4]).unwrap() { assert!(sig.get_issuers()[0].aliases(&first_signer)); } else { panic!("expected sig packet") } Ok(()) } // Example copied from `Encryptor::aead_algo` and slightly // adjusted since the doctest from `Encryptor::aead_algo` does not // run. Additionally this test case utilizes // `AEADAlgorithm::const_default` to detect which algorithm to // use. #[test] fn experimental_aead_encryptor() -> Result<()> { use std::io::Write; use crate::types::AEADAlgorithm; use crate::policy::NullPolicy; use crate::serialize::stream::{ Message, Encryptor, LiteralWriter, }; use crate::parse::stream::{ DecryptorBuilder, VerificationHelper, DecryptionHelper, MessageStructure, }; let mut sink = vec![]; let message = Message::new(&mut sink); let message = Encryptor::with_passwords(message, Some("совершенно секретно")) .aead_algo(AEADAlgorithm::const_default()) .build()?; let mut message = LiteralWriter::new(message).build()?; message.write_all(b"Hello world.")?; message.finalize()?; struct Helper; impl VerificationHelper for Helper { fn get_certs(&mut self, _ids: &[crate::KeyHandle]) -> Result<Vec<Cert>> where { Ok(Vec::new()) } fn check(&mut self, _structure: MessageStructure) -> Result<()> { Ok(()) } } impl DecryptionHelper for Helper { fn decrypt(&mut self, _: &[PKESK], skesks: &[SKESK], _sym_algo: Option<SymmetricAlgorithm>, decrypt: &mut dyn FnMut(Option<SymmetricAlgorithm>, &SessionKey) -> bool) -> Result<Option<Cert>> { skesks[0].decrypt(&"совершенно секретно".into()) .map(|(algo, session_key)| decrypt(algo, &session_key))?; Ok(None) } } let p = unsafe { &NullPolicy::new() }; let mut v = DecryptorBuilder::from_bytes(&sink)?.with_policy(p, None, Helper)?; let mut content = vec![]; v.read_to_end(&mut content)?; assert_eq!(content, b"Hello world."); Ok(()) } /// Signs using our set of public keys. #[test] fn signer() -> Result<()> { use crate::policy::StandardPolicy; use crate::parse::stream::{ VerifierBuilder, test::VHelper, }; let p = StandardPolicy::new(); for alg in &[ "rsa", "dsa", "nistp256", "nistp384", "nistp521", "brainpoolP256r1", "brainpoolP384r1", "brainpoolP512r1", "secp256k1", ] { eprintln!("Test vector {:?}...", alg); let key = Cert::from_bytes(crate::tests::key( &format!("signing/{}.gpg", alg)))?; if let Some(k) = key.with_policy(&p, None).ok() .and_then(|vcert| vcert.keys().for_signing().supported().next()) { use crate::crypto::mpi::PublicKey; match k.key().mpis() { PublicKey::ECDSA { curve, .. } | PublicKey::EdDSA { curve, .. } if ! curve.is_supported() => { eprintln!("Skipping {} because we don't support \ the curve {}", alg, curve); continue; }, _ => (), } } else { eprintln!("Skipping {} because we don't support the algorithm", alg); continue; } let signing_keypair = key.keys().secret() .with_policy(&p, None).supported() .alive().revoked(false).for_signing() .nth(0).unwrap() .key().clone().into_keypair()?; let mut sink = vec![]; let message = Message::new(&mut sink); let message = Signer::new(message, signing_keypair)? .build()?; let mut message = LiteralWriter::new(message).build()?; message.write_all(b"Hello world.")?; message.finalize()?; let h = VHelper::new(1, 0, 0, 0, vec![key]); let mut d = VerifierBuilder::from_bytes(&sink)? .with_policy(&p, None, h)?; assert!(d.message_processed()); let mut content = Vec::new(); d.read_to_end(&mut content).unwrap(); assert_eq!(&b"Hello world."[..], &content[..]); } Ok(()) } /// Encrypts using public key cryptography. #[test] fn pk_encryptor() -> Result<()> { use crate::policy::StandardPolicy; use crate::parse::stream::{ DecryptorBuilder, test::VHelper, }; let p = StandardPolicy::new(); for path in [ "rsa", "elg", "cv25519", "cv25519.unclamped", "nistp256", "nistp384", "nistp521", "brainpoolP256r1", "brainpoolP384r1", "brainpoolP512r1", "secp256k1", ].iter().map(|alg| format!("messages/encrypted/{}.sec.pgp", alg)) .chain(vec![ "crypto-refresh/v6-minimal-secret.key".into(), ].into_iter()) { eprintln!("Test vector {:?}...", path); let key = Cert::from_bytes(crate::tests::file(&path))?; if let Some(k) = key.with_policy(&p, None)?.keys().subkeys().supported().next() { use crate::crypto::mpi::PublicKey; match k.key().mpis() { PublicKey::ECDH { curve, .. } if ! curve.is_supported() => { eprintln!("Skipping {} because we don't support \ the curve {}", path, curve); continue; }, _ => (), } } else { eprintln!("Skipping {} because we don't support the algorithm", path); continue; } let recipients = key.with_policy(&p, None)?.keys().for_storage_encryption(); let mut sink = vec![]; let message = Message::new(&mut sink); let message = Encryptor::for_recipients(message, recipients) .build()?; let mut message = LiteralWriter::new(message).build()?; message.write_all(b"Hello world.")?; message.finalize()?; let h = VHelper::for_decryption(0, 0, 0, 0, Vec::new(), vec![key], Vec::new()); let mut d = DecryptorBuilder::from_bytes(&sink)? .with_policy(&p, None, h)?; assert!(d.message_processed()); let mut content = Vec::new(); d.read_to_end(&mut content).unwrap(); assert_eq!(&b"Hello world."[..], &content[..]); } Ok(()) } #[test] fn encryptor_lifetime() { // See https://gitlab.com/sequoia-pgp/sequoia/-/issues/1028 // // Using Encryptor instead of Encryptor, we get the error: // // pub fn _encrypt_data<'a, B: AsRef<[u8]>, R>(data: B, recipients: R) // -- lifetime `'a` defined here // // let message = Message::new(&mut sink); // -------------^^^^^^^^^- // | | // | borrowed value does not live long enough // argument requires that `sink` is borrowed for `'a` // // } // - `sink` dropped here while still borrowed pub fn _encrypt_data<'a, B: AsRef<[u8]>, R>(data: B, recipients: R) -> anyhow::Result<Vec<u8>> where R: IntoIterator, R::Item: Into<Recipient<'a>>, { let mut sink = vec![]; let message = Message::new(&mut sink); let armorer = Armorer::new(message).build()?; let encryptor = Encryptor::for_recipients(armorer, recipients).build()?; let mut writer = LiteralWriter::new(encryptor).build()?; writer.write_all(data.as_ref())?; writer.finalize()?; Ok(sink) } } /// Encrypts to a v4 and a v6 recipient using SEIPDv1. #[test] fn mixed_recipients_seipd1() -> Result<()> { let alice = CertBuilder::general_purpose(Some("alice")) .set_profile(Profile::RFC9580)? .generate()?.0; let bob = CertBuilder::general_purpose(Some("bob")) .set_profile(Profile::RFC4880)? .set_features(Features::empty().set_seipdv1())? .generate()?.0; mixed_recipients_intern(alice, bob, 1) } /// Encrypts to a v4 and a v6 recipient using SEIPDv2. #[test] fn mixed_recipients_seipd2() -> Result<()> { let alice = CertBuilder::general_purpose(Some("alice")) .set_profile(Profile::RFC9580)? .generate()?.0; let bob = CertBuilder::general_purpose(Some("bob")) .set_profile(Profile::RFC4880)? .generate()?.0; mixed_recipients_intern(alice, bob, 2) } fn mixed_recipients_intern(alice: Cert, bob: Cert, seipdv: u8) -> Result<()> { use crate::policy::StandardPolicy; use crate::parse::stream::{ DecryptorBuilder, test::VHelper, }; let p = StandardPolicy::new(); let recipients = [&alice, &bob].into_iter().flat_map( |c| c.keys().with_policy(&p, None).for_storage_encryption()); let mut sink = vec![]; let message = Message::new(&mut sink); let message = Encryptor::for_recipients(message, recipients) .build()?; let mut message = LiteralWriter::new(message).build()?; message.write_all(b"Hello world.")?; message.finalize()?; for key in [alice, bob] { eprintln!("Decrypting with key version {}", key.primary_key().key().version()); let h = VHelper::for_decryption(0, 0, 0, 0, Vec::new(), vec![key], Vec::new()); let mut d = DecryptorBuilder::from_bytes(&sink)? .with_policy(&p, None, h)?; assert!(d.message_processed()); let mut content = Vec::new(); d.read_to_end(&mut content).unwrap(); assert_eq!(&b"Hello world."[..], &content[..]); use Packet::*; match seipdv { 1 => d.helper_ref().packets.iter().for_each( |p| match p { PKESK(p) => assert_eq!(p.version(), 3), SKESK(p) => assert_eq!(p.version(), 4), SEIP(p) => assert_eq!(p.version(), 1), _ => (), }), 2 => d.helper_ref().packets.iter().for_each( |p| match p { PKESK(p) => assert_eq!(p.version(), 6), SKESK(p) => assert_eq!(p.version(), 6), SEIP(p) => assert_eq!(p.version(), 2), _ => (), }), _ => unreachable!(), } } Ok(()) } } ��������������������sequoia-openpgp-2.0.0/src/serialize.rs��������������������������������������������������������������0000644�0000000�0000000�00000415012�10461020230�0016066�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������//! Packet serialization infrastructure. //! //! OpenPGP defines a binary representation suitable for storing and //! communicating OpenPGP data structures (see [Section 3 ff. of RFC //! 9580]). Serialization is the process of creating the binary //! representation. //! //! [Section 3 ff. of RFC 9580]: https://www.rfc-editor.org/rfc/rfc9580.html#section-3 //! //! There are two interfaces to serialize OpenPGP data. Which one is //! applicable depends on whether or not the packet structure is //! already assembled in memory, with all information already in place //! (e.g. because it was previously parsed). //! //! If it is, you can use the [`Serialize`] or [`SerializeInto`] //! trait. Otherwise, please use our [streaming serialization //! interface]. //! //! [streaming serialization interface]: stream //! //! # Streaming serialization //! //! The [streaming serialization interface] is the preferred way to //! create OpenPGP messages (see [Section 10.3 of RFC 9580]). It is //! ergonomic, yet flexible enough to accommodate most use cases. It //! requires little buffering, minimizing the memory footprint of the //! operation. //! //! This example demonstrates how to create the simplest possible //! OpenPGP message (see [Section 10.3 of RFC 9580]) containing just a //! literal data packet (see [Section 5.9 of RFC 9580]): //! //! [Section 10.3 of RFC 9580]: https://www.rfc-editor.org/rfc/rfc9580.html#section-10.3 //! [Section 5.9 of RFC 9580]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.9 //! //! ``` //! # fn main() -> sequoia_openpgp::Result<()> { //! use std::io::Write; //! use sequoia_openpgp as openpgp; //! use openpgp::serialize::stream::{Message, LiteralWriter}; //! //! let mut o = vec![]; //! { //! let message = Message::new(&mut o); //! let mut w = LiteralWriter::new(message).build()?; //! w.write_all(b"Hello world.")?; //! w.finalize()?; //! } //! assert_eq!(b"\xcb\x12b\x00\x00\x00\x00\x00Hello world.", o.as_slice()); //! # Ok(()) } //! ``` //! //! For a more complete example, see the [streaming examples]. //! //! [streaming examples]: stream#examples //! //! # Serializing objects //! //! The traits [`Serialize`] and [`SerializeInto`] provide a mechanism //! to serialize OpenPGP data structures. [`Serialize`] writes to //! [`io::Write`]rs, while [`SerializeInto`] writes into pre-allocated //! buffers, computes the size of the serialized representation, and //! provides a convenient method to create byte vectors with the //! serialized form. //! //! [`io::Write`]: std::io::Write //! //! To prevent accidentally serializing data structures that are not //! commonly exchanged between OpenPGP implementations, [`Serialize`] //! and [`SerializeInto`] is only implemented for types like //! [`Packet`], [`Cert`], and [`Message`], but not for packet bodies //! like [`Signature`]. //! //! [`Packet`]: super::Packet //! [`Cert`]: super::Cert //! [`Message`]: super::Message //! [`Signature`]: crate::packet::Signature //! //! This example demonstrates how to serialize a literal data packet //! (see [Section 5.9 of RFC 9580]): //! //! ``` //! # fn main() -> sequoia_openpgp::Result<()> { //! use sequoia_openpgp as openpgp; //! use openpgp::packet::{Literal, Packet}; //! use openpgp::serialize::{Serialize, SerializeInto}; //! //! let mut l = Literal::default(); //! l.set_body(b"Hello world.".to_vec()); //! //! // Add packet framing. //! let p = Packet::from(l); //! //! // Using Serialize. //! let mut b = vec![]; //! p.serialize(&mut b)?; //! assert_eq!(b"\xcb\x12b\x00\x00\x00\x00\x00Hello world.", b.as_slice()); //! //! // Using SerializeInto. //! let b = p.to_vec()?; //! assert_eq!(b"\xcb\x12b\x00\x00\x00\x00\x00Hello world.", b.as_slice()); //! # Ok(()) } //! ``` //! //! # Marshalling objects //! //! The traits [`Marshal`] and [`MarshalInto`] provide a mechanism to //! serialize all OpenPGP data structures in this crate, even those //! not commonly interchanged between OpenPGP implementations. For //! example, it allows the serialization of unframed packet bodies: //! //! //! ``` //! # fn main() -> sequoia_openpgp::Result<()> { //! use sequoia_openpgp as openpgp; //! use openpgp::packet::Literal; //! use openpgp::serialize::{Marshal, MarshalInto}; //! //! let mut l = Literal::default(); //! l.set_body(b"Hello world.".to_vec()); //! //! // Using Marshal. //! let mut b = vec![]; //! l.serialize(&mut b)?; //! assert_eq!(b"b\x00\x00\x00\x00\x00Hello world.", b.as_slice()); //! //! // Using MarshalInto. //! let b = l.to_vec()?; //! assert_eq!(b"b\x00\x00\x00\x00\x00Hello world.", b.as_slice()); //! # Ok(()) } //! ``` use std::io::{self, Write}; use std::cmp; use std::convert::{TryFrom, TryInto}; use super::*; mod cert; pub use self::cert::TSK; mod cert_armored; pub mod stream; use crate::crypto::S2K; use crate::packet::header::{ BodyLength, CTB, CTBNew, CTBOld, }; use crate::packet::signature::subpacket::{ SubpacketArea, Subpacket, SubpacketValue, SubpacketLength }; use crate::packet::prelude::*; use crate::packet::signature::Signature3; use crate::seal; use crate::types::{ RevocationKey, Timestamp, }; // Whether to trace the modules execution (on stderr). const TRACE : bool = false; /// Serializes OpenPGP data structures. /// /// This trait provides the same interface as the [`Marshal`] trait (in /// fact, it is just a wrapper around that trait), but only data /// structures that it makes sense to export implement it. /// /// /// Having a separate trait for data structures that it makes sense to /// export avoids an easy-to-make and hard-to-debug bug: inadvertently /// exporting an OpenPGP data structure without any framing /// information. /// /// This bug is easy to make, because Rust infers types, which means /// that it is often not clear from the immediate context exactly what /// is being serialized. This bug is hard to debug, because errors /// parsing data that has been incorrectly exported, are removed from /// the serialization code. /// /// The following example shows how to correctly export a revocation /// certificate. It should make clear how easy it is to forget to /// convert a bare signature into an OpenPGP packet before serializing /// it: /// /// ``` /// # use sequoia_openpgp as openpgp; /// # use openpgp::Result; /// use openpgp::cert::prelude::*; /// use openpgp::Packet; /// use openpgp::serialize::Serialize; /// /// # fn main() -> Result<()> { /// let (_cert, rev) = /// CertBuilder::general_purpose(Some("alice@example.org")) /// .generate()?; /// let rev : Packet = rev.into(); /// # let output = &mut Vec::new(); /// rev.serialize(output)?; /// # Ok(()) /// # } /// ``` /// /// Note: if you `use` both `Serialize` and [`Marshal`], then, because /// they both have the same methods, and all data structures that /// implement `Serialize` also implement [`Marshal`], you will have to /// use the Universal Function Call Syntax (UFCS) to call the methods /// on those objects, for example: /// /// ``` /// # use sequoia_openpgp as openpgp; /// # use openpgp::Result; /// # use openpgp::cert::prelude::*; /// # use openpgp::Packet; /// # use openpgp::serialize::Serialize; /// # /// # fn main() -> Result<()> { /// # let (_cert, rev) = /// # CertBuilder::general_purpose(Some("alice@example.org")) /// # .generate()?; /// # let rev : Packet = rev.into(); /// # let output = &mut Vec::new(); /// Serialize::serialize(&rev, output)?; /// # Ok(()) /// # } /// ``` /// /// If you really needed [`Marshal`], we strongly recommend importing it /// in as small a scope as possible to avoid this, and to avoid /// accidentally exporting data without the required framing. pub trait Serialize : Marshal { /// Writes a serialized version of the object to `o`. fn serialize(&self, o: &mut dyn std::io::Write) -> Result<()> { Marshal::serialize(self, o) } /// Exports a serialized version of the object to `o`. /// /// This is similar to [`serialize(..)`], with these exceptions: /// /// - It is an error to export a [`Signature`] if it is marked /// as non-exportable. /// - When exporting a [`Cert`], non-exportable signatures are /// not exported, and any component bound merely by /// non-exportable signatures is not exported. /// /// [`serialize(..)`]: Serialize::serialize /// [`Signature`]: crate::packet::Signature /// [`Cert`]: super::Cert fn export(&self, o: &mut dyn std::io::Write) -> Result<()> { Marshal::export(self, o) } } /// Serializes OpenPGP data structures. /// /// This trait provides the same interface as [`Serialize`], but is /// implemented for all data structures that can be serialized. /// /// /// In general, you should prefer the [`Serialize`] trait, as it is only /// implemented for data structures that are normally exported. See /// the documentation for [`Serialize`] for more details. /// /// # Sealed trait /// /// This trait is [sealed] and cannot be implemented for types outside this crate. /// Therefore it can be extended in a non-breaking way. /// If you want to implement the trait inside the crate /// you also need to implement the `seal::Sealed` marker trait. /// /// [sealed]: https://rust-lang.github.io/api-guidelines/future-proofing.html#sealed-traits-protect-against-downstream-implementations-c-sealed pub trait Marshal: seal::Sealed { /// Writes a serialized version of the object to `o`. fn serialize(&self, o: &mut dyn std::io::Write) -> Result<()>; /// Exports a serialized version of the object to `o`. /// /// This is similar to [`serialize(..)`], with these exceptions: /// /// - It is an error to export a [`Signature`] if it is marked /// as non-exportable. /// - When exporting a [`Cert`], non-exportable signatures are /// not exported, and any component bound merely by /// non-exportable signatures is not exported. /// /// [`serialize(..)`]: Marshal::serialize /// [`Signature`]: crate::packet::Signature /// [`Cert`]: super::Cert fn export(&self, o: &mut dyn std::io::Write) -> Result<()> { self.serialize(o) } } /// Serializes OpenPGP data structures into pre-allocated buffers. /// /// This trait provides the same interface as [`MarshalInto`], but is /// only implemented for data structures that can be serialized. /// /// /// In general, you should prefer this trait to [`MarshalInto`], as it /// is only implemented for data structures that are normally /// exported. See the documentation for [`Serialize`] for more details. /// pub trait SerializeInto : MarshalInto { /// Computes the maximal length of the serialized representation. /// /// # Errors /// /// If serialization would fail, this function underestimates the /// length. fn serialized_len(&self) -> usize { MarshalInto::serialized_len(self) } /// Serializes into the given buffer. /// /// Returns the length of the serialized representation. /// /// # Errors /// /// If the length of the given slice is smaller than the maximal /// length computed by `serialized_len()`, this function returns /// [`Error::InvalidArgument`]. fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> { MarshalInto::serialize_into(self, buf) } /// Serializes the packet to a vector. fn to_vec(&self) -> Result<Vec<u8>> { MarshalInto::to_vec(self) } /// Exports into the given buffer. /// /// This is similar to [`serialize_into(..)`], with these /// exceptions: /// /// - It is an error to export a [`Signature`] if it is marked /// as non-exportable. /// - When exporting a [`Cert`], non-exportable signatures are /// not exported, and any component bound merely by /// non-exportable signatures is not exported. /// /// [`serialize_into(..)`]: SerializeInto::serialize_into /// [`Signature`]: crate::packet::Signature /// [`Cert`]: super::Cert /// /// Returns the length of the serialized representation. /// /// # Errors /// /// If the length of the given slice is smaller than the maximal /// length computed by `serialized_len()`, this function returns /// [`Error::InvalidArgument`]. fn export_into(&self, buf: &mut [u8]) -> Result<usize> { MarshalInto::export_into(self, buf) } /// Exports to a vector. /// /// This is similar to [`to_vec()`], with these exceptions: /// /// - It is an error to export a [`Signature`] if it is marked /// as non-exportable. /// - When exporting a [`Cert`], non-exportable signatures are /// not exported, and any component bound merely by /// non-exportable signatures is not exported. /// /// [`to_vec()`]: SerializeInto::to_vec() /// [`Signature`]: crate::packet::Signature /// [`Cert`]: super::Cert fn export_to_vec(&self) -> Result<Vec<u8>> { MarshalInto::export_to_vec(self) } } /// Serializes OpenPGP data structures into pre-allocated buffers. /// /// This trait provides the same interface as [`SerializeInto`], but is /// implemented for all data structures that can be serialized. /// /// /// In general, you should prefer the [`SerializeInto`] trait, as it is /// only implemented for data structures that are normally exported. /// See the documentation for [`Serialize`] for more details. /// /// /// # Sealed trait /// /// This trait is [sealed] and cannot be implemented for types outside this crate. /// Therefore it can be extended in a non-breaking way. /// If you want to implement the trait inside the crate /// you also need to implement the `seal::Sealed` marker trait. /// /// [sealed]: https://rust-lang.github.io/api-guidelines/future-proofing.html#sealed-traits-protect-against-downstream-implementations-c-sealed pub trait MarshalInto : seal::Sealed { /// Computes the maximal length of the serialized representation. /// /// # Errors /// /// If serialization would fail, this function underestimates the /// length. fn serialized_len(&self) -> usize; /// Serializes into the given buffer. /// /// Returns the length of the serialized representation. /// /// # Errors /// /// If the length of the given slice is smaller than the maximal /// length computed by `serialized_len()`, this function returns /// [`Error::InvalidArgument`]. fn serialize_into(&self, buf: &mut [u8]) -> Result<usize>; /// Serializes the packet to a vector. fn to_vec(&self) -> Result<Vec<u8>> { let mut o = vec![0; self.serialized_len()]; let len = self.serialize_into(&mut o[..])?; vec_truncate(&mut o, len); o.shrink_to_fit(); Ok(o) } /// Exports into the given buffer. /// /// This is similar to [`serialize_into(..)`], with these /// exceptions: /// /// - It is an error to export a [`Signature`] if it is marked /// as non-exportable. /// - When exporting a [`Cert`], non-exportable signatures are /// not exported, and any component bound merely by /// non-exportable signatures is not exported. /// /// [`serialize_into(..)`]: MarshalInto::serialize_into /// [`Signature`]: crate::packet::Signature /// [`Cert`]: super::Cert /// /// Returns the length of the serialized representation. /// /// # Errors /// /// If the length of the given slice is smaller than the maximal /// length computed by `serialized_len()`, this function returns /// [`Error::InvalidArgument`]. fn export_into(&self, buf: &mut [u8]) -> Result<usize> { self.serialize_into(buf) } /// Exports to a vector. /// /// This is similar to [`to_vec()`], with these exceptions: /// /// - It is an error to export a [`Signature`] if it is marked /// as non-exportable. /// - When exporting a [`Cert`], non-exportable signatures are /// not exported, and any component bound merely by /// non-exportable signatures is not exported. /// /// [`to_vec()`]: MarshalInto::to_vec() /// [`Signature`]: crate::packet::Signature /// [`Cert`]: super::Cert fn export_to_vec(&self) -> Result<Vec<u8>> { let mut o = vec![0; self.serialized_len()]; let len = self.export_into(&mut o[..])?; vec_truncate(&mut o, len); o.shrink_to_fit(); Ok(o) } } trait NetLength { /// Computes the maximal length of the serialized representation /// without framing. /// /// # Errors /// /// If serialization would fail, this function underestimates the /// length. fn net_len(&self) -> usize; /// Computes the maximal length of the serialized representation /// with framing. /// /// # Errors /// /// If serialization would fail, this function underestimates the /// length. fn gross_len(&self) -> usize { let net = self.net_len(); 1 // CTB + BodyLength::Full(net as u32).serialized_len() + net } } /// Provides a generic implementation for SerializeInto::serialize_into. /// /// For now, we express SerializeInto using Serialize. In the future, /// we may provide implementations not relying on Serialize for a /// no_std configuration of this crate. fn generic_serialize_into(o: &dyn Marshal, serialized_len: usize, buf: &mut [u8]) -> Result<usize> { let buf_len = buf.len(); let mut cursor = ::std::io::Cursor::new(buf); match o.serialize(&mut cursor) { Ok(_) => (), Err(e) => { let short_write = if let Some(ioe) = e.downcast_ref::<io::Error>() { ioe.kind() == io::ErrorKind::WriteZero } else { false }; return if short_write { if buf_len >= serialized_len { let mut b = Vec::new(); let need_len = o.serialize(&mut b).map(|_| b.len()); panic!("o.serialized_len() = {} underestimated required \ space, need {:?}", serialized_len, need_len); } Err(Error::InvalidArgument( format!("Invalid buffer size, expected {}, got {}", serialized_len, buf_len)).into()) } else { Err(e) } } }; Ok(cursor.position() as usize) } /// Provides a generic implementation for SerializeInto::export_into. /// /// For now, we express SerializeInto using Serialize. In the future, /// we may provide implementations not relying on Serialize for a /// no_std configuration of this crate. fn generic_export_into(o: &dyn Marshal, serialized_len: usize, buf: &mut [u8]) -> Result<usize> { let buf_len = buf.len(); let mut cursor = ::std::io::Cursor::new(buf); match o.export(&mut cursor) { Ok(_) => (), Err(e) => { let short_write = if let Some(ioe) = e.downcast_ref::<io::Error>() { ioe.kind() == io::ErrorKind::WriteZero } else { false }; return if short_write { if buf_len >= serialized_len { let mut b = Vec::new(); let need_len = o.serialize(&mut b).map(|_| b.len()); panic!("o.serialized_len() = {} underestimated required \ space, need {:?}", serialized_len, need_len); } Err(Error::InvalidArgument( format!("Invalid buffer size, expected {}, got {}", serialized_len, buf_len)).into()) } else { Err(e) } } }; Ok(cursor.position() as usize) } #[test] fn test_generic_serialize_into() { let u = UserID::from("Mr. Pink"); let mut b = vec![0; u.serialized_len()]; u.serialize_into(&mut b[..]).unwrap(); // Short buffer. let mut b = vec![0; u.serialized_len() - 1]; let e = u.serialize_into(&mut b[..]).unwrap_err(); assert_match!(Some(Error::InvalidArgument(_)) = e.downcast_ref()); } #[test] fn test_generic_export_into() { let u = UserID::from("Mr. Pink"); let mut b = vec![0; u.serialized_len()]; u.export_into(&mut b[..]).unwrap(); // Short buffer. let mut b = vec![0; u.serialized_len() - 1]; let e = u.export_into(&mut b[..]).unwrap_err(); assert_match!(Some(Error::InvalidArgument(_)) = e.downcast_ref()); } fn write_byte(o: &mut dyn std::io::Write, b: u8) -> io::Result<()> { o.write_all(&[b]) } fn write_be_u16(o: &mut dyn std::io::Write, n: u16) -> io::Result<()> { o.write_all(&n.to_be_bytes()) } fn write_be_u32(o: &mut dyn std::io::Write, n: u32) -> io::Result<()> { o.write_all(&n.to_be_bytes()) } // Compute the log2 of an integer. (This is simply the most // significant bit.) Note: log2(0) = -Inf, but this function returns // log2(0) as 0 (which is the closest number that we can represent). fn log2(x: u32) -> usize { if x == 0 { 0 } else { 31 - x.leading_zeros() as usize } } #[test] fn log2_test() { for i in 0..32 { // eprintln!("log2(1 << {} = {}) = {}", i, 1u32 << i, log2(1u32 << i)); assert_eq!(log2(1u32 << i), i); if i > 0 { assert_eq!(log2((1u32 << i) - 1), i - 1); assert_eq!(log2((1u32 << i) + 1), i); } } } impl seal::Sealed for BodyLength {} impl Marshal for BodyLength { /// Emits the length encoded for use with new-style CTBs. /// /// Note: the CTB itself is not emitted. /// /// # Errors /// /// Returns [`Error::InvalidArgument`] if invoked on /// [`BodyLength::Indeterminate`]. If you want to serialize an /// old-style length, use [`serialize_old(..)`]. /// /// [`Error::InvalidArgument`]: Error::InvalidArgument /// [`serialize_old(..)`]: BodyLength::serialize_old() fn serialize(&self, o: &mut dyn std::io::Write) -> Result<()> { match self { BodyLength::Full(l) => { let l = *l; if l <= 191 { write_byte(o, l as u8)?; } else if l <= 8383 { let v = l - 192; let v = v + (192 << 8); write_be_u16(o, v as u16)?; } else { write_byte(o, 0xff)?; write_be_u32(o, l)?; } }, BodyLength::Partial(l) => { let l = *l; if l > 1 << 30 { return Err(Error::InvalidArgument( format!("Partial length too large: {}", l)).into()); } let chunk_size_log2 = log2(l); let chunk_size = 1 << chunk_size_log2; if l != chunk_size { return Err(Error::InvalidArgument( format!("Not a power of two: {}", l)).into()); } let size_byte = 224 + chunk_size_log2; assert!(size_byte < 255); write_byte(o, size_byte as u8)?; }, BodyLength::Indeterminate => return Err(Error::InvalidArgument( "Indeterminate lengths are not support for new format packets". into()).into()), } Ok(()) } } impl MarshalInto for BodyLength { fn serialized_len(&self) -> usize { match self { BodyLength::Full(l) => { let l = *l; if l <= 191 { 1 } else if l <= 8383 { 2 } else { 5 } }, BodyLength::Partial(_) => 1, BodyLength::Indeterminate => 0, } } fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> { generic_serialize_into(self, MarshalInto::serialized_len(self), buf) } } impl BodyLength { /// Emits the length encoded for use with old-style CTBs. /// /// Note: the CTB itself is not emitted. /// /// # Errors /// /// Returns [`Error::InvalidArgument`] if invoked on /// [`BodyLength::Partial`]. If you want to serialize a /// new-style length, use [`serialize(..)`]. /// /// [`Error::InvalidArgument`]: Error::InvalidArgument /// [`serialize(..)`]: Serialize pub fn serialize_old<W: io::Write + ?Sized>(&self, o: &mut W) -> Result<()> { // Assume an optimal encoding is desired. let mut buffer = Vec::with_capacity(4); match self { BodyLength::Full(l) => { let l = *l; match l { // One octet length. // write_byte can't fail for a Vec. 0 ..= 0xFF => write_byte(&mut buffer, l as u8).unwrap(), // Two octet length. 0x1_00 ..= 0xFF_FF => write_be_u16(&mut buffer, l as u16).unwrap(), // Four octet length, _ => write_be_u32(&mut buffer, l as u32).unwrap(), } }, BodyLength::Indeterminate => {}, BodyLength::Partial(_) => return Err(Error::InvalidArgument( "Partial body lengths are not support for old format packets". into()).into()), } o.write_all(&buffer)?; Ok(()) } /// Computes the length of the length encoded for use with /// old-style CTBs. fn old_serialized_len(&self) -> usize { // Assume an optimal encoding is desired. match self { BodyLength::Full(l) => { match *l { 0 ..= 0xFF => 1, 0x1_00 ..= 0xFF_FF => 2, _ => 4, } }, BodyLength::Indeterminate => 0, BodyLength::Partial(_) => 0, } } } impl seal::Sealed for CTBNew {} impl Marshal for CTBNew { fn serialize(&self, o: &mut dyn std::io::Write) -> Result<()> { let tag: u8 = self.tag().into(); o.write_all(&[0b1100_0000u8 | tag])?; Ok(()) } } impl MarshalInto for CTBNew { fn serialized_len(&self) -> usize { 1 } fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> { generic_serialize_into(self, MarshalInto::serialized_len(self), buf) } } impl seal::Sealed for CTBOld {} impl Marshal for CTBOld { fn serialize(&self, o: &mut dyn std::io::Write) -> Result<()> { let tag: u8 = self.tag().into(); let length_type: u8 = self.length_type().into(); o.write_all(&[0b1000_0000u8 | (tag << 2) | length_type])?; Ok(()) } } impl MarshalInto for CTBOld { fn serialized_len(&self) -> usize { 1 } fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> { generic_serialize_into(self, MarshalInto::serialized_len(self), buf) } } impl seal::Sealed for CTB {} impl Marshal for CTB { fn serialize(&self, o: &mut dyn std::io::Write) -> Result<()> { match self { CTB::New(ref c) => c.serialize(o), CTB::Old(ref c) => c.serialize(o), }?; Ok(()) } } impl MarshalInto for CTB { fn serialized_len(&self) -> usize { 1 } fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> { generic_serialize_into(self, MarshalInto::serialized_len(self), buf) } } impl seal::Sealed for Header {} impl Marshal for Header { fn serialize(&self, o: &mut dyn std::io::Write) -> Result<()> { self.ctb().serialize(o)?; match self.ctb() { CTB::New(_) => self.length().serialize(o)?, CTB::Old(_) => self.length().serialize_old(o)?, } Ok(()) } } impl MarshalInto for Header { fn serialized_len(&self) -> usize { self.ctb().serialized_len() + match self.ctb() { CTB::New(_) => self.length().serialized_len(), CTB::Old(_) => self.length().old_serialized_len(), } } fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> { generic_serialize_into(self, MarshalInto::serialized_len(self), buf) } } #[test] fn legacy_header() -> Result<()> { use crate::serialize::MarshalInto; let len = BodyLength::Indeterminate; let ctb = CTB::Old(CTBOld::new(Tag::Literal, len)?); assert_eq!(&ctb.to_vec()?[..], &[0b1_0_1011_11]); // Bit encoding: OpenPGP_Legacy_Literal_Indeterminate Ok(()) } impl Serialize for KeyID {} impl seal::Sealed for KeyID {} impl Marshal for KeyID { fn serialize(&self, o: &mut dyn std::io::Write) -> Result<()> { let raw = match self { KeyID::Long(ref fp) => &fp[..], KeyID::Invalid(ref fp) => &fp[..], }; o.write_all(raw)?; Ok(()) } } impl SerializeInto for KeyID {} impl MarshalInto for KeyID { fn serialized_len(&self) -> usize { match self { KeyID::Long(_) => 8, KeyID::Invalid(ref fp) => fp.len(), } } fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> { generic_serialize_into(self, MarshalInto::serialized_len(self), buf) } } impl Serialize for Fingerprint {} impl seal::Sealed for Fingerprint {} impl Marshal for Fingerprint { fn serialize(&self, o: &mut dyn std::io::Write) -> Result<()> { o.write_all(self.as_bytes())?; Ok(()) } } impl SerializeInto for Fingerprint {} impl MarshalInto for Fingerprint { fn serialized_len(&self) -> usize { self.as_bytes().len() } fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> { generic_serialize_into(self, MarshalInto::serialized_len(self), buf) } } impl seal::Sealed for crypto::mpi::MPI {} impl Marshal for crypto::mpi::MPI { fn serialize(&self, w: &mut dyn std::io::Write) -> Result<()> { write_be_u16(w, self.bits() as u16)?; w.write_all(self.value())?; Ok(()) } } impl MarshalInto for crypto::mpi::MPI { fn serialized_len(&self) -> usize { 2 + self.value().len() } fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> { generic_serialize_into(self, MarshalInto::serialized_len(self), buf) } } impl seal::Sealed for crypto::mpi::ProtectedMPI {} impl Marshal for crypto::mpi::ProtectedMPI { fn serialize(&self, w: &mut dyn std::io::Write) -> Result<()> { write_be_u16(w, self.bits() as u16)?; w.write_all(self.value())?; Ok(()) } } impl MarshalInto for crypto::mpi::ProtectedMPI { fn serialized_len(&self) -> usize { 2 + self.value().len() } fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> { generic_serialize_into(self, MarshalInto::serialized_len(self), buf) } } /// Writes `buf` into `w` prefixed by the length as u8, bailing out if /// the length exceeds 256 bytes. fn write_field_with_u8_size(w: &mut dyn Write, name: &str, buf: &[u8]) -> Result<()> { w.write_all(&[buf.len().try_into() .map_err(|_| anyhow::Error::from( Error::InvalidArgument( format!("{} exceeds 255 bytes: {:?}", name, buf))))?])?; w.write_all(buf)?; Ok(()) } impl seal::Sealed for crypto::mpi::PublicKey {} impl Marshal for crypto::mpi::PublicKey { fn serialize(&self, w: &mut dyn std::io::Write) -> Result<()> { use crate::crypto::mpi::PublicKey::*; match self { RSA { ref e, ref n } => { n.serialize(w)?; e.serialize(w)?; } DSA { ref p, ref q, ref g, ref y } => { p.serialize(w)?; q.serialize(w)?; g.serialize(w)?; y.serialize(w)?; } ElGamal { ref p, ref g, ref y } => { p.serialize(w)?; g.serialize(w)?; y.serialize(w)?; } EdDSA { ref curve, ref q } => { write_field_with_u8_size(w, "Curve's OID", curve.oid())?; q.serialize(w)?; } ECDSA { ref curve, ref q } => { write_field_with_u8_size(w, "Curve's OID", curve.oid())?; q.serialize(w)?; } ECDH { ref curve, ref q, hash, sym } => { write_field_with_u8_size(w, "Curve's OID", curve.oid())?; q.serialize(w)?; w.write_all(&[3u8, 1u8, u8::from(*hash), u8::from(*sym)])?; } X25519 { u } => w.write_all(&u[..])?, X448 { u } => w.write_all(&u[..])?, Ed25519 { a } => w.write_all(&a[..])?, Ed448 { a } => w.write_all(&a[..])?, Unknown { ref mpis, ref rest } => { for mpi in mpis.iter() { mpi.serialize(w)?; } w.write_all(rest)?; } } Ok(()) } } impl MarshalInto for crypto::mpi::PublicKey { fn serialized_len(&self) -> usize { use crate::crypto::mpi::PublicKey::*; match self { RSA { ref e, ref n } => { n.serialized_len() + e.serialized_len() } DSA { ref p, ref q, ref g, ref y } => { p.serialized_len() + q.serialized_len() + g.serialized_len() + y.serialized_len() } ElGamal { ref p, ref g, ref y } => { p.serialized_len() + g.serialized_len() + y.serialized_len() } EdDSA { ref curve, ref q } => { 1 + curve.oid().len() + q.serialized_len() } ECDSA { ref curve, ref q } => { 1 + curve.oid().len() + q.serialized_len() } ECDH { ref curve, ref q, hash: _, sym: _ } => { 1 + curve.oid().len() + q.serialized_len() + 4 } X25519 { .. } => 32, X448 { .. } => 56, Ed25519 { .. } => 32, Ed448 { .. } => 57, Unknown { ref mpis, ref rest } => { mpis.iter().map(|mpi| mpi.serialized_len()).sum::<usize>() + rest.len() } } } fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> { generic_serialize_into(self, MarshalInto::serialized_len(self), buf) } } impl seal::Sealed for crypto::mpi::SecretKeyMaterial {} impl Marshal for crypto::mpi::SecretKeyMaterial { fn serialize(&self, w: &mut dyn std::io::Write) -> Result<()> { use crate::crypto::mpi::SecretKeyMaterial::*; match self { RSA{ ref d, ref p, ref q, ref u } => { d.serialize(w)?; p.serialize(w)?; q.serialize(w)?; u.serialize(w)?; } DSA{ ref x } => { x.serialize(w)?; } ElGamal{ ref x } => { x.serialize(w)?; } EdDSA{ ref scalar } => { scalar.serialize(w)?; } ECDSA{ ref scalar } => { scalar.serialize(w)?; } ECDH{ ref scalar } => { scalar.serialize(w)?; } X25519 { x } => w.write_all(x)?, X448 { x } => w.write_all(x)?, Ed25519 { x } => w.write_all(x)?, Ed448 { x } => w.write_all(x)?, Unknown { ref mpis, ref rest } => { for mpi in mpis.iter() { mpi.serialize(w)?; } w.write_all(rest)?; } } Ok(()) } } impl MarshalInto for crypto::mpi::SecretKeyMaterial { fn serialized_len(&self) -> usize { use crate::crypto::mpi::SecretKeyMaterial::*; match self { RSA{ ref d, ref p, ref q, ref u } => { d.serialized_len() + p.serialized_len() + q.serialized_len() + u.serialized_len() } DSA{ ref x } => { x.serialized_len() } ElGamal{ ref x } => { x.serialized_len() } EdDSA{ ref scalar } => { scalar.serialized_len() } ECDSA{ ref scalar } => { scalar.serialized_len() } ECDH{ ref scalar } => { scalar.serialized_len() } X25519 { .. } => 32, X448 { .. } => 56, Ed25519 { .. } => 32, Ed448 { .. } => 57, Unknown { ref mpis, ref rest } => { mpis.iter().map(|mpi| mpi.serialized_len()).sum::<usize>() + rest.len() } } } fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> { generic_serialize_into(self, MarshalInto::serialized_len(self), buf) } } impl crypto::mpi::SecretKeyMaterial { /// Writes this secret key with a checksum to `w`. pub fn serialize_with_checksum( &self, w: &mut dyn io::Write, checksum: crypto::mpi::SecretKeyChecksum) -> Result<()> { // First, the MPIs. self.serialize(w)?; match checksum { crypto::mpi::SecretKeyChecksum::SHA1 => { // The checksum is SHA1 over the serialized MPIs. let mut hash = HashAlgorithm::SHA1.context().unwrap().for_digest(); self.serialize(&mut hash)?; let mut digest = [0u8; 20]; let _ = hash.digest(&mut digest); w.write_all(&digest)?; }, crypto::mpi::SecretKeyChecksum::Sum16 => { w.write_all(&self.to_vec()?.iter() .fold(0u16, |acc, v| acc.wrapping_add(*v as u16)) .to_be_bytes())?; }, } Ok(()) } } impl seal::Sealed for crypto::mpi::Ciphertext {} impl Marshal for crypto::mpi::Ciphertext { fn serialize(&self, w: &mut dyn std::io::Write) -> Result<()> { use crate::crypto::mpi::Ciphertext::*; match self { RSA{ ref c } => { c.serialize(w)?; } ElGamal{ ref e, ref c } => { e.serialize(w)?; c.serialize(w)?; } ECDH{ ref e, ref key } => { e.serialize(w)?; write_field_with_u8_size(w, "Key", key)?; } X25519 { e, key } => { w.write_all(&e[..])?; write_field_with_u8_size(w, "Key", key)?; } X448 { e, key } => { w.write_all(&e[..])?; write_field_with_u8_size(w, "Key", key)?; } Unknown { ref mpis, ref rest } => { for mpi in mpis.iter() { mpi.serialize(w)?; } w.write_all(rest)?; } } Ok(()) } } impl MarshalInto for crypto::mpi::Ciphertext { fn serialized_len(&self) -> usize { use crate::crypto::mpi::Ciphertext::*; match self { RSA{ ref c } => { c.serialized_len() } ElGamal{ ref e, ref c } => { e.serialized_len() + c.serialized_len() } ECDH{ ref e, ref key } => { e.serialized_len() + 1 + key.len() } X25519 { key, .. } => { 32 + 1 + key.len() } X448 { key, .. } => { 56 + 1 + key.len() } Unknown { ref mpis, ref rest } => { mpis.iter().map(|mpi| mpi.serialized_len()).sum::<usize>() + rest.len() } } } fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> { generic_serialize_into(self, MarshalInto::serialized_len(self), buf) } } impl seal::Sealed for crypto::mpi::Signature {} impl Marshal for crypto::mpi::Signature { fn serialize(&self, w: &mut dyn std::io::Write) -> Result<()> { use crate::crypto::mpi::Signature::*; match self { RSA { ref s } => { s.serialize(w)?; } DSA { ref r, ref s } => { r.serialize(w)?; s.serialize(w)?; } ElGamal { ref r, ref s } => { r.serialize(w)?; s.serialize(w)?; } EdDSA { ref r, ref s } => { r.serialize(w)?; s.serialize(w)?; } ECDSA { ref r, ref s } => { r.serialize(w)?; s.serialize(w)?; } Ed25519 { s } => w.write_all(&s[..])?, Ed448 { s } => w.write_all(&s[..])?, Unknown { ref mpis, ref rest } => { for mpi in mpis.iter() { mpi.serialize(w)?; } w.write_all(rest)?; } } Ok(()) } } impl MarshalInto for crypto::mpi::Signature { fn serialized_len(&self) -> usize { use crate::crypto::mpi::Signature::*; match self { RSA { ref s } => { s.serialized_len() } DSA { ref r, ref s } => { r.serialized_len() + s.serialized_len() } ElGamal { ref r, ref s } => { r.serialized_len() + s.serialized_len() } EdDSA { ref r, ref s } => { r.serialized_len() + s.serialized_len() } ECDSA { ref r, ref s } => { r.serialized_len() + s.serialized_len() } Ed25519 { .. } => 64, Ed448 { .. } => 114, Unknown { ref mpis, ref rest } => { mpis.iter().map(|mpi| mpi.serialized_len()).sum::<usize>() + rest.len() } } } fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> { generic_serialize_into(self, MarshalInto::serialized_len(self), buf) } } impl seal::Sealed for S2K {} impl Marshal for S2K { fn serialize(&self, w: &mut dyn std::io::Write) -> Result<()> { #[allow(deprecated)] match self { &S2K::Simple{ hash } => { w.write_all(&[0, hash.into()])?; } &S2K::Salted{ hash, salt } => { w.write_all(&[1, hash.into()])?; w.write_all(&salt[..])?; } &S2K::Iterated{ hash, salt, hash_bytes } => { w.write_all(&[3, hash.into()])?; w.write_all(&salt[..])?; w.write_all(&[S2K::encode_count(hash_bytes)?])?; } S2K::Implicit => (), S2K::Argon2 { salt, t, p, m, } => { w.write_all(&[4])?; w.write_all(salt)?; w.write_all(&[*t, *p, *m])?; }, S2K::Private { tag, parameters } | S2K::Unknown { tag, parameters} => { w.write_all(&[*tag])?; if let Some(p) = parameters.as_ref() { w.write_all(p)?; } } } Ok(()) } } impl MarshalInto for S2K { fn serialized_len(&self) -> usize { #[allow(deprecated)] match self { &S2K::Simple{ .. } => 2, &S2K::Salted{ .. } => 2 + 8, &S2K::Iterated{ .. } => 2 + 8 + 1, S2K::Implicit => 0, S2K::Argon2 { .. } => 20, S2K::Private { parameters, .. } | S2K::Unknown { parameters, .. } => 1 + parameters.as_ref().map(|p| p.len()).unwrap_or(0), } } fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> { generic_serialize_into(self, MarshalInto::serialized_len(self), buf) } } impl seal::Sealed for Unknown {} impl Marshal for Unknown { fn serialize(&self, o: &mut dyn std::io::Write) -> Result<()> { o.write_all(self.body())?; Ok(()) } } impl NetLength for Unknown { fn net_len(&self) -> usize { self.body().len() } } impl MarshalInto for Unknown { fn serialized_len(&self) -> usize { self.net_len() } fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> { generic_serialize_into(self, MarshalInto::serialized_len(self), buf) } } impl seal::Sealed for SubpacketArea {} impl Marshal for SubpacketArea { fn serialize(&self, o: &mut dyn std::io::Write) -> Result<()> { for sb in self.iter() { sb.serialize(o)?; } Ok(()) } } impl MarshalInto for SubpacketArea { fn serialized_len(&self) -> usize { self.iter().map(|sb| sb.serialized_len()).sum() } fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> { let mut written = 0; for sb in self.iter() { let n = sb.serialize_into(&mut buf[written..])?; written += cmp::min(buf.len() - written, n); } Ok(written) } } impl seal::Sealed for Subpacket {} impl Marshal for Subpacket { fn serialize(&self, o: &mut dyn std::io::Write) -> Result<()> { let tag = u8::from(self.tag()) | if self.critical() { 1 << 7 } else { 0 }; self.length.serialize(o)?; o.write_all(&[tag])?; self.value().serialize(o) } } impl MarshalInto for Subpacket { fn serialized_len(&self) -> usize { self.length.serialized_len() + 1 + self.value().serialized_len() } fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> { generic_serialize_into(self, MarshalInto::serialized_len(self), buf) } } impl seal::Sealed for SubpacketValue {} impl Marshal for SubpacketValue { fn serialize(&self, o: &mut dyn std::io::Write) -> Result<()> { use self::SubpacketValue::*; #[allow(deprecated)] match self { SignatureCreationTime(t) => write_be_u32(o, (*t).into())?, SignatureExpirationTime(t) => write_be_u32(o, (*t).into())?, ExportableCertification(e) => o.write_all(&[if *e { 1 } else { 0 }])?, TrustSignature { ref level, ref trust } => o.write_all(&[*level, *trust])?, RegularExpression(ref re) => { o.write_all(re)?; o.write_all(&[0])?; }, Revocable(r) => o.write_all(&[if *r { 1 } else { 0 }])?, KeyExpirationTime(t) => write_be_u32(o, (*t).into())?, PreferredSymmetricAlgorithms(ref p) => for a in p { o.write_all(&[(*a).into()])?; }, RevocationKey(rk) => rk.serialize(o)?, Issuer(ref id) => o.write_all(id.as_bytes())?, NotationData(nd) => { o.write_all(nd.flags().as_bitfield().as_bytes())?; write_be_u16(o, nd.name().len() as u16)?; write_be_u16(o, nd.value().len() as u16)?; o.write_all(nd.name().as_bytes())?; o.write_all(nd.value())?; }, PreferredHashAlgorithms(ref p) => for a in p { o.write_all(&[(*a).into()])?; }, PreferredCompressionAlgorithms(ref p) => for a in p { o.write_all(&[(*a).into()])?; }, KeyServerPreferences(ref p) => o.write_all(p.as_bitfield().as_bytes())?, PreferredKeyServer(ref p) => o.write_all(p)?, PrimaryUserID(p) => o.write_all(&[if *p { 1 } else { 0 }])?, PolicyURI(ref p) => o.write_all(p)?, KeyFlags(ref f) => o.write_all(f.as_bitfield().as_bytes())?, SignersUserID(ref uid) => o.write_all(uid)?, ReasonForRevocation { ref code, ref reason } => { o.write_all(&[(*code).into()])?; o.write_all(reason)?; }, Features(ref f) => o.write_all(f.as_bitfield().as_bytes())?, SignatureTarget { pk_algo, hash_algo, ref digest } => { o.write_all(&[(*pk_algo).into(), (*hash_algo).into()])?; o.write_all(digest)?; }, EmbeddedSignature(sig) => sig.serialize(o)?, IssuerFingerprint(ref fp) => match fp { Fingerprint::V4(_) => { o.write_all(&[4])?; o.write_all(fp.as_bytes())?; }, Fingerprint::V6(_) => { o.write_all(&[6])?; o.write_all(fp.as_bytes())?; }, _ => return Err(Error::InvalidArgument( "Unknown kind of fingerprint".into()).into()), } IntendedRecipient(ref fp) => match fp { Fingerprint::V4(_) => { o.write_all(&[4])?; o.write_all(fp.as_bytes())?; }, Fingerprint::V6(_) => { o.write_all(&[6])?; o.write_all(fp.as_bytes())?; }, _ => return Err(Error::InvalidArgument( "Unknown kind of fingerprint".into()).into()), } ApprovedCertifications(digests) => { for digest in digests { o.write_all(digest)?; } }, PreferredAEADCiphersuites(p) => for (symm, aead) in p { o.write_all(&[(*symm).into(), (*aead).into()])?; }, Unknown { body, .. } => o.write_all(body)?, } Ok(()) } } impl MarshalInto for SubpacketValue { fn serialized_len(&self) -> usize { use self::SubpacketValue::*; #[allow(deprecated)] match self { SignatureCreationTime(_) => 4, SignatureExpirationTime(_) => 4, ExportableCertification(_) => 1, TrustSignature { .. } => 2, RegularExpression(ref re) => re.len() + 1, Revocable(_) => 1, KeyExpirationTime(_) => 4, PreferredSymmetricAlgorithms(ref p) => p.len(), RevocationKey(rk) => rk.serialized_len(), Issuer(ref id) => (id as &dyn MarshalInto).serialized_len(), NotationData(nd) => 4 + 2 + 2 + nd.name().len() + nd.value().len(), PreferredHashAlgorithms(ref p) => p.len(), PreferredCompressionAlgorithms(ref p) => p.len(), KeyServerPreferences(p) => p.as_bitfield().as_bytes().len(), PreferredKeyServer(ref p) => p.len(), PrimaryUserID(_) => 1, PolicyURI(ref p) => p.len(), KeyFlags(f) => f.as_bitfield().as_bytes().len(), SignersUserID(ref uid) => uid.len(), ReasonForRevocation { ref reason, .. } => 1 + reason.len(), Features(f) => f.as_bitfield().as_bytes().len(), SignatureTarget { ref digest, .. } => 2 + digest.len(), EmbeddedSignature(sig) => sig.serialized_len(), IssuerFingerprint(ref fp) => 1 + (fp as &dyn MarshalInto).serialized_len(), IntendedRecipient(ref fp) => 1 + (fp as &dyn MarshalInto).serialized_len(), ApprovedCertifications(digests) => digests.iter().map(|d| d.len()).sum(), PreferredAEADCiphersuites(c) => c.len() * 2, Unknown { body, .. } => body.len(), } } fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> { generic_serialize_into(self, MarshalInto::serialized_len(self), buf) } } impl seal::Sealed for SubpacketLength {} impl Marshal for SubpacketLength { /// Writes the subpacket length to `sink`. fn serialize(&self, sink: &mut dyn std::io::Write) -> Result<()> { match self.raw { Some(ref raw) => sink.write_all(raw)?, None => { BodyLength::serialize(&BodyLength::Full(self.len() as u32), sink)? } }; Ok(()) } } impl MarshalInto for SubpacketLength { /// Returns the length of the serialized subpacket length. fn serialized_len(&self) -> usize { if let Some(ref raw) = self.raw { raw.len() } else { Self::len_optimal_encoding(self.len() as u32) } } fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> { generic_serialize_into(self, MarshalInto::serialized_len(self), buf) } } impl seal::Sealed for RevocationKey {} impl Marshal for RevocationKey { fn serialize(&self, o: &mut dyn std::io::Write) -> Result<()> { let (pk_algo, fp) = self.revoker(); o.write_all(&[self.class(), pk_algo.into()])?; o.write_all(fp.as_bytes())?; Ok(()) } } impl MarshalInto for RevocationKey { fn serialized_len(&self) -> usize { 1 + 1 + self.revoker().1.as_bytes().len() } fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> { generic_serialize_into(self, MarshalInto::serialized_len(self), buf) } } impl seal::Sealed for Signature {} impl Marshal for Signature { fn serialize(&self, o: &mut dyn std::io::Write) -> Result<()> { match self { Signature::V3(ref s) => s.serialize(o), Signature::V4(ref s) => s.serialize(o), Signature::V6(ref s) => s.serialize(o), } } fn export(&self, o: &mut dyn std::io::Write) -> Result<()> { match self { Signature::V3(ref s) => s.export(o), Signature::V4(ref s) => s.export(o), Signature::V6(ref s) => s.export(o), } } } impl MarshalInto for Signature { fn serialized_len(&self) -> usize { match self { Signature::V3(ref s) => s.serialized_len(), Signature::V4(ref s) => s.serialized_len(), Signature::V6(ref s) => s.serialized_len(), } } fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> { match self { Signature::V3(ref s) => s.serialize_into(buf), Signature::V4(ref s) => s.serialize_into(buf), Signature::V6(ref s) => s.serialize_into(buf), } } fn export_into(&self, buf: &mut [u8]) -> Result<usize> { match self { Signature::V3(ref s) => s.export_into(buf), Signature::V4(ref s) => s.export_into(buf), Signature::V6(ref s) => s.export_into(buf), } } fn export_to_vec(&self) -> Result<Vec<u8>> { match self { Signature::V3(ref s) => s.export_to_vec(), Signature::V4(ref s) => s.export_to_vec(), Signature::V6(ref s) => s.export_to_vec(), } } } impl NetLength for Signature { fn net_len(&self) -> usize { match self { Signature::V3(sig) => sig.net_len(), Signature::V4(sig) => sig.net_len(), Signature::V6(sig) => sig.net_len(), } } } impl seal::Sealed for Signature3 {} impl Marshal for Signature3 { /// Writes a serialized version of the specified `Signature` /// packet to `o`. /// /// # Errors /// /// Returns [`Error::InvalidArgument`] if `self` does not contain /// a valid v3 signature. Because v3 signature support was added /// late in the 1.x release cycle, `Signature3` is just a thin /// wrapper around a `Signature4`. As such, it is possible to add /// v4 specific data to a `Signature3`. In general, this isn't a /// significnat problem as generating v3 is deprecated. /// /// [`Error::InvalidArgument`]: Error::InvalidArgument fn serialize(&self, o: &mut dyn std::io::Write) -> Result<()> { use crate::packet::signature::subpacket::SubpacketTag; assert_eq!(self.version(), 3); write_byte(o, self.version())?; // hashed length. write_byte(o, 5)?; write_byte(o, self.typ().into())?; if let Some(SubpacketValue::SignatureCreationTime(ct)) = self.hashed_area().subpacket( SubpacketTag::SignatureCreationTime) .map(|sp| sp.value()) { write_be_u32(o, u32::from(*ct))?; } else { return Err(Error::InvalidArgument( "Invalid v3 signature, missing creation time.".into()).into()); } // Only one signature creation time subpacket is allowed in // the hashed area. let mut iter = self.hashed_area().iter(); let _ = iter.next(); if iter.next().is_some() { return Err(Error::InvalidArgument( format!("Invalid v3 signature: \ subpackets are not allowed: {}", self.hashed_area().iter().map(|sp| { format!("{}: {:?}", sp.tag(), sp.value()) }).collect::<Vec<String>>().join(", "))).into()); } if let Some(SubpacketValue::Issuer(keyid)) = self.unhashed_area().subpacket(SubpacketTag::Issuer) .map(|sp| sp.value()) { match keyid { KeyID::Long(bytes) => { assert_eq!(bytes.len(), 8); o.write_all(&bytes[..])?; } KeyID::Invalid(_) => { return Err(Error::InvalidArgument( "Invalid v3 signature, invalid issuer.".into()).into()); } } } else { return Err(Error::InvalidArgument( "Invalid v3 signature, missing issuer.".into()).into()); } // Only one issuer subpacket is allowed in the unhashed area. let mut iter = self.unhashed_area().iter(); let _ = iter.next(); if iter.next().is_some() { return Err(Error::InvalidArgument( format!("Invalid v3 signature: \ subpackets are not allowed: {}", self.unhashed_area().iter().map(|sp| { format!("{}: {:?}", sp.tag(), sp.value()) }).collect::<Vec<String>>().join(", "))).into()); } write_byte(o, self.pk_algo().into())?; write_byte(o, self.hash_algo().into())?; write_byte(o, self.digest_prefix()[0])?; write_byte(o, self.digest_prefix()[1])?; self.mpis().serialize(o)?; Ok(()) } fn export(&self, o: &mut dyn std::io::Write) -> Result<()> { self.exportable()?; self.serialize(o) } } impl NetLength for Signature3 { fn net_len(&self) -> usize { assert_eq!(self.version(), 3); 1 // Version. + 1 // Hashed length. + 1 // Signature type. + 4 // Creation time. + 8 // Issuer. + 1 // PK algorithm. + 1 // Hash algorithm. + 2 // Hash prefix. + self.mpis().serialized_len() } } impl MarshalInto for Signature3 { fn serialized_len(&self) -> usize { self.net_len() } fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> { generic_serialize_into(self, MarshalInto::serialized_len(self), buf) } fn export_into(&self, buf: &mut [u8]) -> Result<usize> { self.exportable()?; self.serialize_into(buf) } fn export_to_vec(&self) -> Result<Vec<u8>> { self.exportable()?; self.to_vec() } } impl seal::Sealed for Signature4 {} impl Marshal for Signature4 { /// Writes a serialized version of the specified `Signature` /// packet to `o`. /// /// # Errors /// /// Returns [`Error::InvalidArgument`] if either the hashed-area /// or the unhashed-area exceeds the size limit of 2^16. /// /// [`Error::InvalidArgument`]: Error::InvalidArgument fn serialize(&self, o: &mut dyn std::io::Write) -> Result<()> { assert_eq!(self.version(), 4); write_byte(o, self.version())?; write_byte(o, self.typ().into())?; write_byte(o, self.pk_algo().into())?; write_byte(o, self.hash_algo().into())?; let l = self.hashed_area().serialized_len(); if l > std::u16::MAX as usize { return Err(Error::InvalidArgument( "Hashed area too large".into()).into()); } write_be_u16(o, l as u16)?; self.hashed_area().serialize(o)?; let l = self.unhashed_area().serialized_len(); if l > std::u16::MAX as usize { return Err(Error::InvalidArgument( "Unhashed area too large".into()).into()); } write_be_u16(o, l as u16)?; self.unhashed_area().serialize(o)?; write_byte(o, self.digest_prefix()[0])?; write_byte(o, self.digest_prefix()[1])?; self.mpis().serialize(o)?; Ok(()) } fn export(&self, o: &mut dyn std::io::Write) -> Result<()> { self.exportable()?; self.serialize(o) } } impl NetLength for Signature4 { fn net_len(&self) -> usize { assert_eq!(self.version(), 4); 1 // Version. + 1 // Signature type. + 1 // PK algorithm. + 1 // Hash algorithm. + 2 // Hashed area size. + self.hashed_area().serialized_len() + 2 // Unhashed area size. + self.unhashed_area().serialized_len() + 2 // Hash prefix. + self.mpis().serialized_len() } } impl MarshalInto for Signature4 { fn serialized_len(&self) -> usize { self.net_len() } fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> { generic_serialize_into(self, MarshalInto::serialized_len(self), buf) } fn export_into(&self, buf: &mut [u8]) -> Result<usize> { self.exportable()?; self.serialize_into(buf) } fn export_to_vec(&self) -> Result<Vec<u8>> { self.exportable()?; self.to_vec() } } impl seal::Sealed for Signature6 {} impl Marshal for Signature6 { /// Writes a serialized version of the specified `Signature` /// packet to `o`. /// /// # Errors /// /// Returns [`Error::InvalidArgument`] if either the hashed-area /// or the unhashed-area exceeds the size limit of 2^16. /// /// [`Error::InvalidArgument`]: Error::InvalidArgument fn serialize(&self, o: &mut dyn std::io::Write) -> Result<()> { assert_eq!(self.version(), 6); write_byte(o, self.version())?; write_byte(o, self.typ().into())?; write_byte(o, self.pk_algo().into())?; write_byte(o, self.hash_algo().into())?; let l = self.hashed_area().serialized_len(); if l > u32::MAX as usize { return Err(Error::InvalidArgument( "Hashed area too large".into()).into()); } write_be_u32(o, l as u32)?; self.hashed_area().serialize(o)?; let l = self.unhashed_area().serialized_len(); if l > u32::MAX as usize { return Err(Error::InvalidArgument( "Unhashed area too large".into()).into()); } write_be_u32(o, l as u32)?; self.unhashed_area().serialize(o)?; write_byte(o, self.digest_prefix()[0])?; write_byte(o, self.digest_prefix()[1])?; write_byte(o, self.salt().len() as u8)?; o.write_all(self.salt())?; self.mpis().serialize(o)?; Ok(()) } fn export(&self, o: &mut dyn std::io::Write) -> Result<()> { self.exportable()?; self.serialize(o) } } impl NetLength for Signature6 { fn net_len(&self) -> usize { assert_eq!(self.version(), 6); 1 // Version. + 1 // Signature type. + 1 // PK algorithm. + 1 // Hash algorithm. + 4 // Hashed area size. + self.hashed_area().serialized_len() + 4 // Unhashed area size. + self.unhashed_area().serialized_len() + 2 // Hash prefix. + 1 // Salt length. + self.salt().len() + self.mpis().serialized_len() } } impl MarshalInto for Signature6 { fn serialized_len(&self) -> usize { self.net_len() } fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> { generic_serialize_into(self, MarshalInto::serialized_len(self), buf) } fn export_into(&self, buf: &mut [u8]) -> Result<usize> { self.exportable()?; self.serialize_into(buf) } fn export_to_vec(&self) -> Result<Vec<u8>> { self.exportable()?; self.to_vec() } } impl seal::Sealed for OnePassSig {} impl Marshal for OnePassSig { fn serialize(&self, o: &mut dyn std::io::Write) -> Result<()> { match self { OnePassSig::V3(ref s) => s.serialize(o), OnePassSig::V6(ref s) => s.serialize(o), } } } impl MarshalInto for OnePassSig { fn serialized_len(&self) -> usize { match self { OnePassSig::V3(ref s) => s.serialized_len(), OnePassSig::V6(ref s) => s.serialized_len(), } } fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> { match self { OnePassSig::V3(ref s) => s.serialize_into(buf), OnePassSig::V6(ref s) => s.serialize_into(buf), } } } impl NetLength for OnePassSig { fn net_len(&self) -> usize { match self { OnePassSig::V3(ref s) => s.net_len(), OnePassSig::V6(ref s) => s.net_len(), } } } impl seal::Sealed for OnePassSig3 {} impl Marshal for OnePassSig3 { fn serialize(&self, o: &mut dyn std::io::Write) -> Result<()> { write_byte(o, 3)?; // Version. write_byte(o, self.typ().into())?; write_byte(o, self.hash_algo().into())?; write_byte(o, self.pk_algo().into())?; o.write_all(self.issuer().as_bytes())?; write_byte(o, self.last_raw())?; Ok(()) } } impl NetLength for OnePassSig3 { fn net_len(&self) -> usize { 1 // Version. + 1 // Signature type. + 1 // Hash algorithm + 1 // PK algorithm. + 8 // Issuer. + 1 // Last. } } impl MarshalInto for OnePassSig3 { fn serialized_len(&self) -> usize { self.net_len() } fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> { generic_serialize_into(self, MarshalInto::serialized_len(self), buf) } } impl seal::Sealed for OnePassSig6 {} impl Marshal for OnePassSig6 { fn serialize(&self, o: &mut dyn std::io::Write) -> Result<()> { write_byte(o, 6)?; // Version. write_byte(o, self.typ().into())?; write_byte(o, self.hash_algo().into())?; write_byte(o, self.pk_algo().into())?; write_byte(o, self.salt().len().try_into().map_err( |_| Error::InvalidArgument("Salt too large".into()))?)?; o.write_all(self.salt())?; if let Fingerprint::V6(bytes) = self.issuer() { o.write_all(bytes)?; } else { return Err(Error::InvalidArgument( "Need a v6 fingerprint as issuer".into()).into()); } write_byte(o, self.last_raw())?; Ok(()) } } impl NetLength for OnePassSig6 { fn net_len(&self) -> usize { 1 // Version. + 1 // Signature type. + 1 // Hash algorithm + 1 // PK algorithm. + 1 // The salt length. + self.salt().len() // The salt. + 32 // Issuer fingerprint. + 1 // Last. } } impl MarshalInto for OnePassSig6 { fn serialized_len(&self) -> usize { self.net_len() } fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> { generic_serialize_into(self, MarshalInto::serialized_len(self), buf) } } impl<P: key::KeyParts, R: key::KeyRole> seal::Sealed for Key<P, R> {} impl<P: key::KeyParts, R: key::KeyRole> Marshal for Key<P, R> { fn serialize(&self, o: &mut dyn io::Write) -> Result<()> { match self { Key::V4(ref p) => p.serialize(o), Key::V6(ref p) => p.serialize(o), } } } impl<P: key::KeyParts, R: key::KeyRole> MarshalInto for Key<P, R> { fn serialized_len(&self) -> usize { match self { Key::V4(ref p) => p.serialized_len(), Key::V6(ref p) => p.serialized_len(), } } fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> { match self { Key::V4(ref p) => p.serialize_into(buf), Key::V6(ref p) => p.serialize_into(buf), } } } impl<P: key::KeyParts, R: key::KeyRole> NetLength for Key<P, R> { fn net_len(&self) -> usize { match self { Key::V4(ref p) => p.net_len(), Key::V6(ref p) => p.net_len(), } } } impl<P, R> seal::Sealed for Key4<P, R> where P: key::KeyParts, R: key::KeyRole, {} impl<P, R> Marshal for Key4<P, R> where P: key::KeyParts, R: key::KeyRole, { fn serialize(&self, o: &mut dyn io::Write) -> Result<()> { let have_secret_key = P::significant_secrets() && self.has_secret(); write_byte(o, 4)?; // Version. write_be_u32(o, self.creation_time_raw().into())?; write_byte(o, self.pk_algo().into())?; self.mpis().serialize(o)?; if have_secret_key { use crypto::mpi::SecretKeyChecksum; match self.optional_secret().unwrap() { SecretKeyMaterial::Unencrypted(ref u) => u.map(|mpis| -> Result<()> { write_byte(o, 0)?; // S2K usage. mpis.serialize_with_checksum(o, SecretKeyChecksum::Sum16) })?, SecretKeyMaterial::Encrypted(ref e) => { if let (Some(aead_algo), Some(aead_iv)) = (e.aead_algo(), e.aead_iv()) { o.write_all(&[ 253, // S2K usage. // No Parameter length for v4 packets. e.algo().into(), aead_algo.into(), ])?; e.s2k().serialize(o)?; o.write_all(aead_iv)?; o.write_all(e.raw_ciphertext())?; } else { // S2K usage #[allow(deprecated)] if let S2K::Implicit = e.s2k() { // When the legacy implicit S2K mechanism is // in use, the symmetric algorithm octet below // takes the place of the S2K usage octet. } else { write_byte(o, match e.checksum() { Some(SecretKeyChecksum::SHA1) => 254, Some(SecretKeyChecksum::Sum16) => 255, None => return Err(Error::InvalidOperation( "In Key4 packets, CFB encrypted secret keys must be \ checksummed".into()).into()), })?; } write_byte(o, e.algo().into())?; e.s2k().serialize(o)?; o.write_all(e.raw_ciphertext())?; } }, } } Ok(()) } } impl<P, R> NetLength for Key4<P, R> where P: key::KeyParts, R: key::KeyRole, { #[allow(deprecated)] fn net_len(&self) -> usize { let have_secret_key = P::significant_secrets() && self.has_secret(); 1 // Version. + 4 // Creation time. + 1 // PK algo. + self.mpis().serialized_len() + if have_secret_key { 1 + match self.optional_secret().unwrap() { SecretKeyMaterial::Unencrypted(ref u) => u.map(|mpis| mpis.serialized_len()) + 2, // Two octet checksum. SecretKeyMaterial::Encrypted(ref e) => matches!(e.s2k(), S2K::Implicit) .then_some(0).unwrap_or(1) + if e.aead_algo().is_some() { 1 } else { 0 } + e.s2k().serialized_len() + e.aead_iv().map(|iv| iv.len()).unwrap_or(0) + e.raw_ciphertext().len(), } } else { 0 } } } impl<P, R> MarshalInto for Key4<P, R> where P: key::KeyParts, R: key::KeyRole, { fn serialized_len(&self) -> usize { self.net_len() } fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> { generic_serialize_into(self, MarshalInto::serialized_len(self), buf) } } impl<P, R> seal::Sealed for Key6<P, R> where P: key::KeyParts, R: key::KeyRole, {} impl<P, R> Marshal for Key6<P, R> where P: key::KeyParts, R: key::KeyRole, { fn serialize(&self, o: &mut dyn io::Write) -> Result<()> { let have_secret_key = P::significant_secrets() && self.has_secret(); write_byte(o, 6)?; // Version. write_be_u32(o, self.creation_time_raw().into())?; write_byte(o, self.pk_algo().into())?; write_be_u32(o, self.mpis().serialized_len() as u32)?; self.mpis().serialize(o)?; if have_secret_key { use crypto::mpi::SecretKeyChecksum; match self.optional_secret().unwrap() { SecretKeyMaterial::Unencrypted(u) => u.map(|mpis| { write_byte(o, 0)?; // S2K usage. mpis.serialize(o) })?, SecretKeyMaterial::Encrypted(e) => { if let (Some(aead_algo), Some(aead_iv)) = (e.aead_algo(), e.aead_iv()) { o.write_all(&[ 253, // S2K usage. (1 + 1 + 1 + e.s2k().serialized_len() + aead_iv.len()) .try_into().unwrap_or(0), e.algo().into(), aead_algo.into(), e.s2k().serialized_len().try_into().unwrap_or(0), ])?; e.s2k().serialize(o)?; o.write_all(aead_iv)?; o.write_all(e.raw_ciphertext())?; } else { o.write_all(&[ // S2K usage. match e.checksum() { Some(SecretKeyChecksum::SHA1) => 254, Some(SecretKeyChecksum::Sum16) => 255, None => return Err(Error::InvalidOperation( "In Key6 packets, CFB encrypted secret keys \ must be checksummed".into()).into()), }, // Parameter length octet. (1 + 1 + e.s2k().serialized_len() + e.cfb_iv_len()) .try_into().unwrap_or(0), // Cipher octet. e.algo().into(), // S2k length octet. e.s2k().serialized_len().try_into().unwrap_or(0), ])?; e.s2k().serialize(o)?; o.write_all(e.raw_ciphertext())?; } }, } } Ok(()) } } impl<P, R> NetLength for Key6<P, R> where P: key::KeyParts, R: key::KeyRole, { fn net_len(&self) -> usize { let have_secret_key = P::significant_secrets() && self.has_secret(); 1 // Version. + 4 // Creation time. + 1 // PK algo. + 4 // Size of the public key material. + self.mpis().serialized_len() + if have_secret_key { 1 // S2K method octet. + match self.optional_secret().unwrap() { SecretKeyMaterial::Unencrypted(u) => u.map(|mpis| mpis.serialized_len()), SecretKeyMaterial::Encrypted(e) => 1 // Parameter length octet. + 1 // Cipher octet + if e.aead_algo().is_some() { 1 } else { 0 } + 1 // S2K length octet + e.s2k().serialized_len() + e.aead_iv().map(|iv| iv.len()).unwrap_or(0) + e.raw_ciphertext().len(), } } else { 0 } } } impl<P, R> MarshalInto for Key6<P, R> where P: key::KeyParts, R: key::KeyRole, { fn serialized_len(&self) -> usize { self.net_len() } fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> { generic_serialize_into(self, MarshalInto::serialized_len(self), buf) } } impl seal::Sealed for Marker {} impl Marshal for Marker { fn serialize(&self, o: &mut dyn std::io::Write) -> Result<()> { o.write_all(Marker::BODY)?; Ok(()) } } impl NetLength for Marker { fn net_len(&self) -> usize { Marker::BODY.len() } } impl MarshalInto for Marker { fn serialized_len(&self) -> usize { self.net_len() } fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> { generic_serialize_into(self, MarshalInto::serialized_len(self), buf) } } impl seal::Sealed for Trust {} impl Marshal for Trust { fn serialize(&self, o: &mut dyn std::io::Write) -> Result<()> { o.write_all(self.value())?; Ok(()) } } impl NetLength for Trust { fn net_len(&self) -> usize { self.value().len() } } impl MarshalInto for Trust { fn serialized_len(&self) -> usize { self.net_len() } fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> { generic_serialize_into(self, MarshalInto::serialized_len(self), buf) } } impl seal::Sealed for UserID {} impl Marshal for UserID { fn serialize(&self, o: &mut dyn std::io::Write) -> Result<()> { o.write_all(self.value())?; Ok(()) } } impl NetLength for UserID { fn net_len(&self) -> usize { self.value().len() } } impl MarshalInto for UserID { fn serialized_len(&self) -> usize { self.net_len() } fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> { generic_serialize_into(self, MarshalInto::serialized_len(self), buf) } } impl seal::Sealed for UserAttribute {} impl Marshal for UserAttribute { fn serialize(&self, o: &mut dyn std::io::Write) -> Result<()> { o.write_all(self.value())?; Ok(()) } } impl NetLength for UserAttribute { fn net_len(&self) -> usize { self.value().len() } } impl MarshalInto for UserAttribute { fn serialized_len(&self) -> usize { self.net_len() } fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> { generic_serialize_into(self, MarshalInto::serialized_len(self), buf) } } impl seal::Sealed for user_attribute::Subpacket {} impl Marshal for user_attribute::Subpacket { fn serialize(&self, o: &mut dyn std::io::Write) -> Result<()> { let body_len = match self { user_attribute::Subpacket::Image(image) => image.serialized_len(), user_attribute::Subpacket::Unknown(_tag, data) => data.len(), }; BodyLength::Full(1 + body_len as u32).serialize(o)?; match self { user_attribute::Subpacket::Image(image) => { write_byte(o, 1)?; image.serialize(o)?; }, user_attribute::Subpacket::Unknown(tag, data) => { write_byte(o, *tag)?; o.write_all(&data[..])?; } } Ok(()) } } impl MarshalInto for user_attribute::Subpacket { fn serialized_len(&self) -> usize { let body_len = match self { user_attribute::Subpacket::Image(image) => image.serialized_len(), user_attribute::Subpacket::Unknown(_tag, data) => data.len(), }; let header_len = BodyLength::Full(1 + body_len as u32).serialized_len(); header_len + 1 + body_len } fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> { generic_serialize_into(self, MarshalInto::serialized_len(self), buf) } } impl seal::Sealed for user_attribute::Image {} impl Marshal for user_attribute::Image { fn serialize(&self, o: &mut dyn std::io::Write) -> Result<()> { const V1HEADER_TOP: [u8; 3] = [0x10, 0x00, 0x01]; const V1HEADER_PAD: [u8; 12] = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; match self { user_attribute::Image::JPEG(data) => { o.write_all(&V1HEADER_TOP[..])?; write_byte(o, 1)?; o.write_all(&V1HEADER_PAD[..])?; o.write_all(&data[..])?; } user_attribute::Image::Unknown(tag, data) | user_attribute::Image::Private(tag, data) => { o.write_all(&V1HEADER_TOP[..])?; write_byte(o, *tag)?; o.write_all(&V1HEADER_PAD[..])?; o.write_all(&data[..])?; } } Ok(()) } } impl MarshalInto for user_attribute::Image { fn serialized_len(&self) -> usize { const V1HEADER_LEN: usize = 2 /* Length */ + 1 /* Version */ + 1 /* Tag */ + 12; /* Reserved padding */ match self { user_attribute::Image::JPEG(data) | user_attribute::Image::Unknown(_, data) | user_attribute::Image::Private(_, data) => V1HEADER_LEN + data.len(), } } fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> { generic_serialize_into(self, MarshalInto::serialized_len(self), buf) } } impl Literal { /// Writes the headers of the `Literal` data packet to `o`. pub(crate) fn serialize_headers(&self, o: &mut dyn std::io::Write, write_tag: bool) -> Result<()> { let filename = if let Some(filename) = self.filename() { let len = cmp::min(filename.len(), 255) as u8; &filename[..len as usize] } else { &b""[..] }; let date = if let Some(d) = self.date() { Timestamp::try_from(d)?.into() } else { 0 }; if write_tag { let len = 1 + (1 + filename.len()) + 4 + self.body().len(); CTB::new(Tag::Literal).serialize(o)?; BodyLength::Full(len as u32).serialize(o)?; } write_byte(o, self.format().into())?; write_byte(o, filename.len() as u8)?; o.write_all(filename)?; write_be_u32(o, date)?; Ok(()) } } impl seal::Sealed for Literal {} impl Marshal for Literal { fn serialize(&self, o: &mut dyn std::io::Write) -> Result<()> { let body = self.body(); if TRACE { let prefix = &body[..cmp::min(body.len(), 20)]; eprintln!("Literal::serialize({}{}, {} bytes)", String::from_utf8_lossy(prefix), if body.len() > 20 { "..." } else { "" }, body.len()); } self.serialize_headers(o, false)?; o.write_all(body)?; Ok(()) } } impl NetLength for Literal { fn net_len(&self) -> usize { 1 + (1 + self.filename().map(|f| f.len()).unwrap_or(0)) + 4 + self.body().len() } } impl MarshalInto for Literal { fn serialized_len(&self) -> usize { self.net_len() } fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> { generic_serialize_into(self, MarshalInto::serialized_len(self), buf) } } impl seal::Sealed for CompressedData {} impl Marshal for CompressedData { /// Writes a serialized version of the specified `CompressedData` /// packet to `o`. /// /// This function works recursively: if the `CompressedData` packet /// contains any packets, they are also serialized. fn serialize(&self, o: &mut dyn std::io::Write) -> Result<()> { // The streaming serialization framework requires the sink to // be Send + Sync, but `o` is not. Knowing that we create the // message here and don't keep the message object around, we // can cheat by creating a shim that is Send + Sync. struct Shim<'a>(&'a mut dyn std::io::Write); impl std::io::Write for Shim<'_> { fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> { self.0.write(buf) } fn flush(&mut self) -> std::io::Result<()> { self.0.flush() } } unsafe impl Send for Shim<'_> {} unsafe impl Sync for Shim<'_> {} match self.body() { Body::Unprocessed(bytes) => { if TRACE { eprintln!("CompressedData::serialize(\ algo: {}, {} bytes of unprocessed body)", self.algo(), bytes.len()); } o.write_all(&[self.algo().into()])?; o.write_all(bytes)?; }, Body::Processed(bytes) => { if TRACE { eprintln!("CompressedData::serialize(\ algo: {}, {} bytes of processed body)", self.algo(), bytes.len()); } let o = stream::Message::new(Shim(o)); let mut o = stream::Compressor::new_naked( o, self.algo(), Default::default(), 0)?; o.write_all(bytes)?; o.finalize()?; }, Body::Structured(children) => { if TRACE { eprintln!("CompressedData::serialize(\ algo: {}, {:?} children)", self.algo(), children.len()); } let o = stream::Message::new(Shim(o)); let mut o = stream::Compressor::new_naked( o, self.algo(), Default::default(), 0)?; // Serialize the packets. for p in children { (p as &dyn Marshal).serialize(&mut o)?; } o.finalize()?; }, } Ok(()) } } impl NetLength for CompressedData { fn net_len(&self) -> usize { // Worst case, the data gets larger. Account for that. // Experiments suggest that the overhead of compressing random // data is worse for BZIP2, but it converges to 20% starting // at ~2k of random data. let compressed = |l| l + cmp::max(l / 5, 4096); match self.body() { Body::Unprocessed(bytes) => 1 /* Algo */ + bytes.len(), Body::Processed(bytes) => 1 /* Algo */ + compressed(bytes.len()), Body::Structured(packets) => 1 // Algo + compressed(packets.iter().map(|p| { (p as &dyn MarshalInto).serialized_len() }).sum::<usize>()), } } } impl MarshalInto for CompressedData { /// Computes the maximal length of the serialized representation. /// /// The size of the serialized compressed data packet is tricky to /// predict. First, it depends on the data being compressed. /// Second, we emit partial body encoded data. /// /// This function tries overestimates the length. However, it may /// happen that `serialize_into()` fails. /// /// # Errors /// /// If serialization would fail, this function returns 0. fn serialized_len(&self) -> usize { self.net_len() } fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> { generic_serialize_into(self, MarshalInto::serialized_len(self), buf) } } impl seal::Sealed for PKESK {} impl Marshal for PKESK { fn serialize(&self, o: &mut dyn std::io::Write) -> Result<()> { match self { PKESK::V3(ref p) => p.serialize(o), PKESK::V6(p) => p.serialize(o), } } } impl NetLength for PKESK { fn net_len(&self) -> usize { match self { PKESK::V3(p) => p.net_len(), PKESK::V6(p) => p.net_len(), } } } impl MarshalInto for PKESK { fn serialized_len(&self) -> usize { match self { PKESK::V3(ref p) => p.serialized_len(), PKESK::V6(p) => p.serialized_len(), } } fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> { match self { PKESK::V3(p) => generic_serialize_into(p, MarshalInto::serialized_len(p), buf), PKESK::V6(p) => generic_serialize_into(p, MarshalInto::serialized_len(p), buf), } } } impl seal::Sealed for PKESK3 {} impl Marshal for PKESK3 { fn serialize(&self, o: &mut dyn std::io::Write) -> Result<()> { write_byte(o, 3)?; // Version. let wildcard = KeyID::wildcard(); (self.recipient().unwrap_or(&wildcard) as &dyn Marshal).serialize(o)?; write_byte(o, self.pk_algo().into())?; self.esk().serialize(o)?; Ok(()) } } impl NetLength for PKESK3 { fn net_len(&self) -> usize { 1 // Version. + 8 // Recipient's key id. + 1 // Algo. + self.esk().serialized_len() } } impl MarshalInto for PKESK3 { fn serialized_len(&self) -> usize { self.net_len() } fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> { generic_serialize_into(self, MarshalInto::serialized_len(self), buf) } } impl seal::Sealed for PKESK6 {} impl Marshal for PKESK6 { fn serialize(&self, o: &mut dyn std::io::Write) -> Result<()> { write_byte(o, 6)?; // Version. if let Some(recipient) = self.recipient() { // Recipient length. write_byte(o, ((recipient as &dyn MarshalInto).serialized_len() + 1) as u8)?; match recipient { Fingerprint::V4(_) => write_byte(o, 4)?, Fingerprint::V6(_) => write_byte(o, 6)?, Fingerprint::Unknown { version, .. } => write_byte(o, version.unwrap_or(0xff))?, } (recipient as &dyn Marshal).serialize(o)?; } else { // No recipient. write_byte(o, 0)?; } write_byte(o, self.pk_algo().into())?; self.esk().serialize(o)?; Ok(()) } } impl NetLength for PKESK6 { fn net_len(&self) -> usize { 1 // Version. + 1 // Recipient length. // Recipient's versioned fingerprint, if any: + self.recipient().map(|r| r.as_bytes().len() + 1).unwrap_or(0) + 1 // Algo. + self.esk().serialized_len() } } impl MarshalInto for PKESK6 { fn serialized_len(&self) -> usize { self.net_len() } fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> { generic_serialize_into(self, MarshalInto::serialized_len(self), buf) } } impl seal::Sealed for SKESK {} impl Marshal for SKESK { fn serialize(&self, o: &mut dyn std::io::Write) -> Result<()> { match self { SKESK::V4(ref s) => s.serialize(o), SKESK::V6(ref s) => s.serialize(o), } } } impl NetLength for SKESK { fn net_len(&self) -> usize { match self { SKESK::V4(ref s) => s.net_len(), SKESK::V6(ref s) => s.net_len(), } } } impl MarshalInto for SKESK { fn serialized_len(&self) -> usize { match self { SKESK::V4(ref s) => s.serialized_len(), SKESK::V6(ref s) => s.serialized_len(), } } fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> { match self { SKESK::V4(s) => generic_serialize_into(s, MarshalInto::serialized_len(s), buf), SKESK::V6(s) => generic_serialize_into(s, MarshalInto::serialized_len(s), buf), } } } impl seal::Sealed for SKESK4 {} impl Marshal for SKESK4 { fn serialize(&self, o: &mut dyn std::io::Write) -> Result<()> { write_byte(o, 4)?; // Version. write_byte(o, self.symmetric_algo().into())?; self.s2k().serialize(o)?; o.write_all(self.raw_esk())?; Ok(()) } } impl NetLength for SKESK4 { fn net_len(&self) -> usize { 1 // Version. + 1 // Algo. + self.s2k().serialized_len() + self.raw_esk().len() } } impl MarshalInto for SKESK4 { fn serialized_len(&self) -> usize { self.net_len() } fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> { generic_serialize_into(self, MarshalInto::serialized_len(self), buf) } } impl seal::Sealed for SKESK6 {} impl Marshal for SKESK6 { fn serialize(&self, o: &mut dyn std::io::Write) -> Result<()> { let s2k_len = self.s2k().serialized_len(); write_byte(o, 6)?; // Version. // Parameter octet count. write_byte(o, (1 // Symmetric algorithm. + 1 // AEAD mode. + 1 // S2K octet count. + s2k_len + self.aead_iv().len()) as u8)?; write_byte(o, self.symmetric_algo().into())?; write_byte(o, self.aead_algo().into())?; // S2K octet count. write_byte(o, s2k_len as u8)?; self.s2k().serialize(o)?; o.write_all(self.aead_iv())?; o.write_all(self.esk())?; Ok(()) } } impl NetLength for SKESK6 { fn net_len(&self) -> usize { 1 // Version. + 1 // Parameter octet count. + 1 // Cipher algo. + 1 // AEAD algo. + 1 // S2K octet count. + self.s2k().serialized_len() + self.aead_iv().len() + self.esk().len() } } impl MarshalInto for SKESK6 { fn serialized_len(&self) -> usize { self.net_len() } fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> { generic_serialize_into(self, MarshalInto::serialized_len(self), buf) } } impl seal::Sealed for SEIP {} impl Marshal for SEIP { fn serialize(&self, o: &mut dyn std::io::Write) -> Result<()> { match self { SEIP::V1(p) => p.serialize(o), SEIP::V2(p) => p.serialize(o), } } } impl NetLength for SEIP { fn net_len(&self) -> usize { match self { SEIP::V1(p) => p.net_len(), SEIP::V2(p) => p.net_len(), } } } impl MarshalInto for SEIP { fn serialized_len(&self) -> usize { self.net_len() } fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> { generic_serialize_into(self, MarshalInto::serialized_len(self), buf) } } impl seal::Sealed for SEIP1 {} impl Marshal for SEIP1 { /// Writes a serialized version of the specified `SEIP1` /// packet to `o`. /// /// # Errors /// /// Returns `Error::InvalidOperation` if this packet has children. /// To construct an encrypted message, use /// `serialize::stream::Encryptor`. fn serialize(&self, o: &mut dyn std::io::Write) -> Result<()> { match self.body() { Body::Unprocessed(bytes) => { o.write_all(&[1])?; o.write_all(bytes)?; Ok(()) }, _ => Err(Error::InvalidOperation( "Cannot encrypt, use serialize::stream::Encryptor".into()) .into()), } } } impl NetLength for SEIP1 { fn net_len(&self) -> usize { match self.body() { Body::Unprocessed(bytes) => 1 /* Version */ + bytes.len(), _ => 0, } } } impl MarshalInto for SEIP1 { fn serialized_len(&self) -> usize { self.net_len() } fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> { generic_serialize_into(self, MarshalInto::serialized_len(self), buf) } } impl SEIP2 { /// Writes the headers of the `SEIP2` data packet to `o`. fn serialize_headers(&self, o: &mut dyn std::io::Write) -> Result<()> { o.write_all(&[2, // Version. self.symmetric_algo().into(), self.aead().into(), self.chunk_size().trailing_zeros() as u8 - 6])?; o.write_all(self.salt())?; Ok(()) } } impl seal::Sealed for SEIP2 {} impl Marshal for SEIP2 { /// Writes a serialized version of the specified `SEIPv2` /// packet to `o`. /// /// # Errors /// /// Returns `Error::InvalidOperation` if this packet has children. /// To construct an encrypted message, use /// `serialize::stream::Encryptor`. fn serialize(&self, o: &mut dyn std::io::Write) -> Result<()> { match self.body() { Body::Unprocessed(bytes) => { self.serialize_headers(o)?; o.write_all(bytes)?; Ok(()) }, _ => Err(Error::InvalidOperation( "Cannot encrypt, use serialize::stream::Encryptor".into()) .into()), } } } impl NetLength for SEIP2 { fn net_len(&self) -> usize { match self.body() { Body::Unprocessed(bytes) => 4 // Headers. + self.salt().len() + bytes.len(), _ => 0, } } } impl MarshalInto for SEIP2 { fn serialized_len(&self) -> usize { self.net_len() } fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> { generic_serialize_into(self, MarshalInto::serialized_len(self), buf) } } impl seal::Sealed for MDC {} impl Marshal for MDC { fn serialize(&self, o: &mut dyn std::io::Write) -> Result<()> { o.write_all(self.digest())?; Ok(()) } } impl NetLength for MDC { fn net_len(&self) -> usize { 20 } } impl MarshalInto for MDC { fn serialized_len(&self) -> usize { self.net_len() } fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> { generic_serialize_into(self, MarshalInto::serialized_len(self), buf) } } impl seal::Sealed for Padding {} impl Marshal for Padding { fn serialize(&self, o: &mut dyn std::io::Write) -> Result<()> { o.write_all(self.value())?; Ok(()) } } impl NetLength for Padding { fn net_len(&self) -> usize { self.value().len() } } impl MarshalInto for Padding { fn serialized_len(&self) -> usize { self.net_len() } fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> { generic_serialize_into(self, MarshalInto::serialized_len(self), buf) } } impl Serialize for Packet {} impl seal::Sealed for Packet {} impl Marshal for Packet { /// Writes a serialized version of the specified `Packet` to `o`. /// /// This function works recursively: if the packet contains any /// packets, they are also serialized. fn serialize(&self, o: &mut dyn std::io::Write) -> Result<()> { CTB::new(self.tag()).serialize(o)?; // Special-case the compressed data packet, because we need // the accurate length, and CompressedData::net_len() // overestimates the size. if let Packet::CompressedData(ref p) = self { let mut body = Vec::new(); p.serialize(&mut body)?; BodyLength::Full(body.len() as u32).serialize(o)?; o.write_all(&body)?; return Ok(()); } BodyLength::Full(self.net_len() as u32).serialize(o)?; match self { Packet::Unknown(ref p) => p.serialize(o), Packet::Signature(ref p) => p.serialize(o), Packet::OnePassSig(ref p) => p.serialize(o), Packet::PublicKey(ref p) => p.serialize(o), Packet::PublicSubkey(ref p) => p.serialize(o), Packet::SecretKey(ref p) => p.serialize(o), Packet::SecretSubkey(ref p) => p.serialize(o), Packet::Marker(ref p) => p.serialize(o), Packet::Trust(ref p) => p.serialize(o), Packet::UserID(ref p) => p.serialize(o), Packet::UserAttribute(ref p) => p.serialize(o), Packet::Literal(ref p) => p.serialize(o), Packet::CompressedData(_) => unreachable!("handled above"), Packet::PKESK(ref p) => p.serialize(o), Packet::SKESK(ref p) => p.serialize(o), Packet::SEIP(ref p) => p.serialize(o), #[allow(deprecated)] Packet::MDC(ref p) => p.serialize(o), Packet::Padding(p) => p.serialize(o), } } /// Exports a serialized version of the specified `Packet` to `o`. /// /// This function works recursively: if the packet contains any /// packets, they are also serialized. fn export(&self, o: &mut dyn std::io::Write) -> Result<()> { CTB::new(self.tag()).serialize(o)?; // Special-case the compressed data packet, because we need // the accurate length, and CompressedData::net_len() // overestimates the size. if let Packet::CompressedData(ref p) = self { let mut body = Vec::new(); p.export(&mut body)?; BodyLength::Full(body.len() as u32).export(o)?; o.write_all(&body)?; return Ok(()); } BodyLength::Full(self.net_len() as u32).export(o)?; match self { Packet::Unknown(ref p) => p.export(o), Packet::Signature(ref p) => p.export(o), Packet::OnePassSig(ref p) => p.export(o), Packet::PublicKey(ref p) => p.export(o), Packet::PublicSubkey(ref p) => p.export(o), Packet::SecretKey(ref p) => p.export(o), Packet::SecretSubkey(ref p) => p.export(o), Packet::Marker(ref p) => p.export(o), Packet::Trust(ref p) => p.export(o), Packet::UserID(ref p) => p.export(o), Packet::UserAttribute(ref p) => p.export(o), Packet::Literal(ref p) => p.export(o), Packet::CompressedData(_) => unreachable!("handled above"), Packet::PKESK(ref p) => p.export(o), Packet::SKESK(ref p) => p.export(o), Packet::SEIP(ref p) => p.export(o), #[allow(deprecated)] Packet::MDC(ref p) => p.export(o), Packet::Padding(p) => p.export(o), } } } impl NetLength for Packet { fn net_len(&self) -> usize { match self { Packet::Unknown(ref p) => p.net_len(), Packet::Signature(ref p) => p.net_len(), Packet::OnePassSig(ref p) => p.net_len(), Packet::PublicKey(ref p) => p.net_len(), Packet::PublicSubkey(ref p) => p.net_len(), Packet::SecretKey(ref p) => p.net_len(), Packet::SecretSubkey(ref p) => p.net_len(), Packet::Marker(ref p) => p.net_len(), Packet::Trust(ref p) => p.net_len(), Packet::UserID(ref p) => p.net_len(), Packet::UserAttribute(ref p) => p.net_len(), Packet::Literal(ref p) => p.net_len(), Packet::CompressedData(ref p) => p.net_len(), Packet::PKESK(ref p) => p.net_len(), Packet::SKESK(ref p) => p.net_len(), Packet::SEIP(ref p) => p.net_len(), #[allow(deprecated)] Packet::MDC(ref p) => p.net_len(), Packet::Padding(p) => p.net_len(), } } } impl SerializeInto for Packet {} impl MarshalInto for Packet { fn serialized_len(&self) -> usize { self.gross_len() } fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> { generic_serialize_into(self, MarshalInto::serialized_len(self), buf) } fn export_into(&self, buf: &mut [u8]) -> Result<usize> { generic_export_into(self, MarshalInto::serialized_len(self), buf) } } /// References packet bodies. /// /// Like [`openpgp::Packet`], but instead of owning the packet's bodies, /// they are referenced. `PacketRef` is only used to serialize packet /// bodies (like [`packet::Signature`]) encapsulating them in OpenPGP /// frames. /// /// [`openpgp::Packet`]: super::Packet /// [`packet::Signature`]: crate::packet::Signature #[allow(dead_code)] enum PacketRef<'a> { /// Unknown packet. Unknown(&'a packet::Unknown), /// Signature packet. Signature(&'a packet::Signature), /// One pass signature packet. OnePassSig(&'a packet::OnePassSig), /// Public key packet. PublicKey(&'a packet::key::PublicKey), /// Public subkey packet. PublicSubkey(&'a packet::key::PublicSubkey), /// Public/Secret key pair. SecretKey(&'a packet::key::SecretKey), /// Public/Secret subkey pair. SecretSubkey(&'a packet::key::SecretSubkey), /// Marker packet. Marker(&'a packet::Marker), /// Trust packet. Trust(&'a packet::Trust), /// User ID packet. UserID(&'a packet::UserID), /// User attribute packet. UserAttribute(&'a packet::UserAttribute), /// Literal data packet. Literal(&'a packet::Literal), /// Compressed literal data packet. CompressedData(&'a packet::CompressedData), /// Public key encrypted data packet. PKESK(&'a packet::PKESK), /// Symmetric key encrypted data packet. SKESK(&'a packet::SKESK), /// Symmetric key encrypted, integrity protected data packet. SEIP(&'a packet::SEIP), /// Modification detection code packet. MDC(&'a packet::MDC), /// Padding packet. Padding(&'a packet::Padding), } impl<'a> PacketRef<'a> { /// Returns the `PacketRef's` corresponding OpenPGP tag. /// /// Tags are explained in [Section 5 of RFC 9580]. /// /// [Section 5 of RFC 9580]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5 fn tag(&self) -> packet::Tag { match self { PacketRef::Unknown(packet) => packet.tag(), PacketRef::Signature(_) => Tag::Signature, PacketRef::OnePassSig(_) => Tag::OnePassSig, PacketRef::PublicKey(_) => Tag::PublicKey, PacketRef::PublicSubkey(_) => Tag::PublicSubkey, PacketRef::SecretKey(_) => Tag::SecretKey, PacketRef::SecretSubkey(_) => Tag::SecretSubkey, PacketRef::Marker(_) => Tag::Marker, PacketRef::Trust(_) => Tag::Trust, PacketRef::UserID(_) => Tag::UserID, PacketRef::UserAttribute(_) => Tag::UserAttribute, PacketRef::Literal(_) => Tag::Literal, PacketRef::CompressedData(_) => Tag::CompressedData, PacketRef::PKESK(_) => Tag::PKESK, PacketRef::SKESK(_) => Tag::SKESK, PacketRef::SEIP(_) => Tag::SEIP, PacketRef::MDC(_) => Tag::MDC, PacketRef::Padding(_) => Tag::Padding, } } } impl<'a> Serialize for PacketRef<'a> {} impl<'a> seal::Sealed for PacketRef<'a> {} impl<'a> Marshal for PacketRef<'a> { /// Writes a serialized version of the specified `Packet` to `o`. /// /// This function works recursively: if the packet contains any /// packets, they are also serialized. fn serialize(&self, o: &mut dyn std::io::Write) -> Result<()> { CTB::new(self.tag()).serialize(o)?; // Special-case the compressed data packet, because we need // the accurate length, and CompressedData::net_len() // overestimates the size. if let PacketRef::CompressedData(p) = self { let mut body = Vec::new(); p.serialize(&mut body)?; BodyLength::Full(body.len() as u32).serialize(o)?; o.write_all(&body)?; return Ok(()); } BodyLength::Full(self.net_len() as u32).serialize(o)?; match self { PacketRef::Unknown(p) => p.serialize(o), PacketRef::Signature(p) => p.serialize(o), PacketRef::OnePassSig(p) => p.serialize(o), PacketRef::PublicKey(p) => p.serialize(o), PacketRef::PublicSubkey(p) => p.serialize(o), PacketRef::SecretKey(p) => p.serialize(o), PacketRef::SecretSubkey(p) => p.serialize(o), PacketRef::Marker(p) => p.serialize(o), PacketRef::Trust(p) => p.serialize(o), PacketRef::UserID(p) => p.serialize(o), PacketRef::UserAttribute(p) => p.serialize(o), PacketRef::Literal(p) => p.serialize(o), PacketRef::CompressedData(_) => unreachable!("handled above"), PacketRef::PKESK(p) => p.serialize(o), PacketRef::SKESK(p) => p.serialize(o), PacketRef::SEIP(p) => p.serialize(o), PacketRef::MDC(p) => p.serialize(o), PacketRef::Padding(p) => p.serialize(o), } } /// Exports a serialized version of the specified `Packet` to `o`. /// /// This function works recursively: if the packet contains any /// packets, they are also serialized. fn export(&self, o: &mut dyn std::io::Write) -> Result<()> { CTB::new(self.tag()).serialize(o)?; // Special-case the compressed data packet, because we need // the accurate length, and CompressedData::net_len() // overestimates the size. if let PacketRef::CompressedData(p) = self { let mut body = Vec::new(); p.export(&mut body)?; BodyLength::Full(body.len() as u32).export(o)?; o.write_all(&body)?; return Ok(()); } BodyLength::Full(self.net_len() as u32).export(o)?; match self { PacketRef::Unknown(p) => p.export(o), PacketRef::Signature(p) => p.export(o), PacketRef::OnePassSig(p) => p.export(o), PacketRef::PublicKey(p) => p.export(o), PacketRef::PublicSubkey(p) => p.export(o), PacketRef::SecretKey(p) => p.export(o), PacketRef::SecretSubkey(p) => p.export(o), PacketRef::Marker(p) => p.export(o), PacketRef::Trust(p) => p.export(o), PacketRef::UserID(p) => p.export(o), PacketRef::UserAttribute(p) => p.export(o), PacketRef::Literal(p) => p.export(o), PacketRef::CompressedData(_) => unreachable!("handled above"), PacketRef::PKESK(p) => p.export(o), PacketRef::SKESK(p) => p.export(o), PacketRef::SEIP(p) => p.export(o), PacketRef::MDC(p) => p.export(o), PacketRef::Padding(p) => p.export(o), } } } impl<'a> NetLength for PacketRef<'a> { fn net_len(&self) -> usize { match self { PacketRef::Unknown(p) => p.net_len(), PacketRef::Signature(p) => p.net_len(), PacketRef::OnePassSig(p) => p.net_len(), PacketRef::PublicKey(p) => p.net_len(), PacketRef::PublicSubkey(p) => p.net_len(), PacketRef::SecretKey(p) => p.net_len(), PacketRef::SecretSubkey(p) => p.net_len(), PacketRef::Marker(p) => p.net_len(), PacketRef::Trust(p) => p.net_len(), PacketRef::UserID(p) => p.net_len(), PacketRef::UserAttribute(p) => p.net_len(), PacketRef::Literal(p) => p.net_len(), PacketRef::CompressedData(p) => p.net_len(), PacketRef::PKESK(p) => p.net_len(), PacketRef::SKESK(p) => p.net_len(), PacketRef::SEIP(p) => p.net_len(), PacketRef::MDC(p) => p.net_len(), PacketRef::Padding(p) => p.net_len(), } } } impl<'a> SerializeInto for PacketRef<'a> {} impl<'a> MarshalInto for PacketRef<'a> { fn serialized_len(&self) -> usize { self.gross_len() } fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> { generic_serialize_into(self, MarshalInto::serialized_len(self), buf) } fn export_into(&self, buf: &mut [u8]) -> Result<usize> { generic_export_into(self, MarshalInto::serialized_len(self), buf) } } impl Serialize for PacketPile {} impl seal::Sealed for PacketPile {} impl Marshal for PacketPile { /// Writes a serialized version of the specified `PacketPile` to `o`. fn serialize(&self, o: &mut dyn std::io::Write) -> Result<()> { for p in self.children() { (p as &dyn Marshal).serialize(o)?; } Ok(()) } /// Exports a serialized version of the specified `PacketPile` to `o`. fn export(&self, o: &mut dyn std::io::Write) -> Result<()> { for p in self.children() { (p as &dyn Marshal).export(o)?; } Ok(()) } } impl SerializeInto for PacketPile {} impl MarshalInto for PacketPile { fn serialized_len(&self) -> usize { self.children().map(|p| { (p as &dyn MarshalInto).serialized_len() }).sum() } fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> { generic_serialize_into(self, MarshalInto::serialized_len(self), buf) } fn export_into(&self, buf: &mut [u8]) -> Result<usize> { generic_export_into(self, MarshalInto::serialized_len(self), buf) } } impl Serialize for Message {} impl seal::Sealed for Message {} impl Marshal for Message { /// Writes a serialized version of the specified `Message` to `o`. fn serialize(&self, o: &mut dyn std::io::Write) -> Result<()> { Marshal::serialize(self.packets(), o) } } impl SerializeInto for Message {} impl MarshalInto for Message { fn serialized_len(&self) -> usize { MarshalInto::serialized_len(self.packets()) } fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> { MarshalInto::serialize_into(self.packets(), buf) } fn export_into(&self, buf: &mut [u8]) -> Result<usize> { MarshalInto::export_into(self.packets(), buf) } } #[cfg(test)] mod test { use super::*; use crate::types::CompressionAlgorithm; use crate::parse::to_unknown_packet; use crate::parse::PacketParserBuilder; use crate::parse::Parse; // A convenient function to dump binary data to stdout. fn binary_pp(data: &[u8]) -> String { let mut output = Vec::new(); crate::fmt::hex::Dumper::new(&mut output, "") .write_ascii(data).unwrap(); // We know the content is valid UTF-8. String::from_utf8(output).unwrap() } // Does a bit-wise comparison of two packets ignoring the CTB // format, the body length encoding, and whether partial body // length encoding was used. fn packets_bitwise_compare(filename: &str, packet: &Packet, expected: &[u8], got: &[u8]) { let expected = to_unknown_packet(expected).unwrap(); let got = to_unknown_packet(got).unwrap(); let expected_body = expected.body(); let got_body = got.body(); let mut fail = false; if expected.tag() != got.tag() { eprintln!("Expected a {:?}, got a {:?}", expected.tag(), got.tag()); fail = true; } if expected_body != got_body { eprintln!("Packet contents don't match (for {}):", filename); eprintln!("Expected ({} bytes):\n{}", expected_body.len(), binary_pp(expected_body)); eprintln!("Got ({} bytes):\n{}", got_body.len(), binary_pp(got_body)); eprintln!("Packet: {:#?}", packet); fail = true; } if fail { panic!("Packets don't match (for {}).", filename); } } #[test] fn serialize_test_1() { // Given a packet in serialized form: // // - Parse and reserialize it; // // - Do a bitwise comparison (modulo the body length encoding) // of the original and reserialized data. // // Note: This test only works on messages with a single packet. // // Note: This test does not work with non-deterministic // packets, like compressed data packets, since the serialized // forms may be different. let filenames = [ "literal-mode-b.gpg", "literal-mode-t-partial-body.gpg", "sig.gpg", "public-key-bare.gpg", "public-subkey-bare.gpg", "userid-bare.gpg", "s2k/mode-0-password-1234.gpg", "s2k/mode-0-password-1234.gpg", "s2k/mode-1-password-123456-1.gpg", "s2k/mode-1-password-foobar-2.gpg", "s2k/mode-3-aes128-password-13-times-0123456789.gpg", "s2k/mode-3-aes192-password-123.gpg", "s2k/mode-3-encrypted-key-password-bgtyhn.gpg", "s2k/mode-3-password-9876-2.gpg", "s2k/mode-3-password-qwerty-1.gpg", "s2k/mode-3-twofish-password-13-times-0123456789.gpg", ]; for filename in filenames.iter() { // 1. Read the message byte stream into a local buffer. let data = crate::tests::message(filename); // 2. Parse the message. let pile = PacketPile::from_bytes(data).unwrap(); // The following test only works if the message has a // single top-level packet. assert_eq!(pile.children().len(), 1); // 3. Serialize the packet it into a local buffer. let p = pile.descendants().next().unwrap(); let mut buffer = Vec::new(); match p { Packet::Literal(_) | Packet::Signature(_) | Packet::PublicKey(_) | Packet::PublicSubkey(_) | Packet::UserID(_) | Packet::SKESK(_) => (), p => { panic!("Didn't expect a {:?} packet.", p.tag()); }, } (p as &dyn Marshal).serialize(&mut buffer).unwrap(); // 4. Modulo the body length encoding, check that the // reserialized content is identical to the original data. packets_bitwise_compare(filename, p, data, &buffer[..]); } } #[test] fn serialize_test_1_unknown() { // This is an variant of serialize_test_1 that tests the // unknown packet serializer. let filenames = [ "compressed-data-algo-1.gpg", "compressed-data-algo-2.gpg", "compressed-data-algo-3.gpg", "recursive-2.gpg", "recursive-3.gpg", ]; for filename in filenames.iter() { // 1. Read the message byte stream into a local buffer. let data = crate::tests::message(filename); // 2. Parse the message. let u = Packet::Unknown(to_unknown_packet(data).unwrap()); // 3. Serialize the packet it into a local buffer. let data2 = (&u as &dyn MarshalInto).to_vec().unwrap(); // 4. Modulo the body length encoding, check that the // reserialized content is identical to the original data. packets_bitwise_compare(filename, &u, data, &data2[..]); } } #[test] fn serialize_test_2() { // Given a packet in serialized form: // // - Parse, reserialize, and reparse it; // // - Compare the messages. // // Note: This test only works on messages with a single packet // top-level packet. // // Note: serialize_test_1 is a better test, because it // compares the serialized data, but serialize_test_1 doesn't // work if the content is non-deterministic. let filenames = [ "compressed-data-algo-1.gpg", "compressed-data-algo-2.gpg", "compressed-data-algo-3.gpg", "recursive-2.gpg", "recursive-3.gpg", ]; for filename in filenames.iter() { eprintln!("{}...", filename); // 1. Read the message into a local buffer. let data = crate::tests::message(filename); // 2. Do a shallow parse of the message. In other words, // never recurse so that the resulting message only // contains the top-level packets. Any containers will // have their raw content stored in packet.content. let pile = PacketParserBuilder::from_bytes(data).unwrap() .max_recursion_depth(0) .buffer_unread_content() //.trace() .into_packet_pile().unwrap(); // 3. Get the first packet. let po = pile.descendants().next(); if let Some(&Packet::CompressedData(ref cd)) = po { if ! cd.algo().is_supported() { eprintln!("Skipping {} because {} is not supported.", filename, cd.algo()); continue; } // 4. Serialize the container. let buffer = (&Packet::CompressedData(cd.clone()) as &dyn MarshalInto) .to_vec().unwrap(); // 5. Reparse it. let pile2 = PacketParserBuilder::from_bytes(&buffer[..]).unwrap() .max_recursion_depth(0) .buffer_unread_content() //.trace() .into_packet_pile().unwrap(); // 6. Make sure the original message matches the // serialized and reparsed message. if pile != pile2 { eprintln!("Orig:"); let p = pile.children().next().unwrap(); eprintln!("{:?}", p); let body = p.processed_body().unwrap(); eprintln!("Body: {}", body.len()); eprintln!("{}", binary_pp(body)); eprintln!("Reparsed:"); let p = pile2.children().next().unwrap(); eprintln!("{:?}", p); let body = p.processed_body().unwrap(); eprintln!("Body: {}", body.len()); eprintln!("{}", binary_pp(body)); assert_eq!(pile, pile2); } } else { panic!("Expected a compressed data data packet."); } } } // Create some crazy nesting structures, serialize the messages, // reparse them, and make sure we get the same result. #[test] fn serialize_test_3() { use crate::types::DataFormat::Unicode as T; // serialize_test_1 and serialize_test_2 parse a byte stream. // This tests creates the message, and then serializes and // reparses it. let mut messages = Vec::new(); // 1: CompressedData(CompressedData { algo: 0 }) // 1: Literal(Literal { body: "one (3 bytes)" }) // 2: Literal(Literal { body: "two (3 bytes)" }) // 2: Literal(Literal { body: "three (5 bytes)" }) let mut one = Literal::new(T); one.set_body(b"one".to_vec()); let mut two = Literal::new(T); two.set_body(b"two".to_vec()); let mut three = Literal::new(T); three.set_body(b"three".to_vec()); let mut four = Literal::new(T); four.set_body(b"four".to_vec()); let mut five = Literal::new(T); five.set_body(b"five".to_vec()); let mut six = Literal::new(T); six.set_body(b"six".to_vec()); let mut top_level = Vec::new(); top_level.push( CompressedData::new(CompressionAlgorithm::Uncompressed) .push(one.clone().into()) .push(two.clone().into()) .into()); top_level.push(three.clone().into()); messages.push(top_level); // 1: CompressedData(CompressedData { algo: 0 }) // 1: CompressedData(CompressedData { algo: 0 }) // 1: Literal(Literal { body: "one (3 bytes)" }) // 2: Literal(Literal { body: "two (3 bytes)" }) // 2: CompressedData(CompressedData { algo: 0 }) // 1: Literal(Literal { body: "three (5 bytes)" }) // 2: Literal(Literal { body: "four (4 bytes)" }) let mut top_level = Vec::new(); top_level.push( CompressedData::new(CompressionAlgorithm::Uncompressed) .push(CompressedData::new(CompressionAlgorithm::Uncompressed) .push(one.clone().into()) .push(two.clone().into()) .into()) .push(CompressedData::new(CompressionAlgorithm::Uncompressed) .push(three.clone().into()) .push(four.clone().into()) .into()) .into()); messages.push(top_level); // 1: CompressedData(CompressedData { algo: 0 }) // 1: CompressedData(CompressedData { algo: 0 }) // 1: CompressedData(CompressedData { algo: 0 }) // 1: CompressedData(CompressedData { algo: 0 }) // 1: Literal(Literal { body: "one (3 bytes)" }) // 2: Literal(Literal { body: "two (3 bytes)" }) // 2: CompressedData(CompressedData { algo: 0 }) // 1: CompressedData(CompressedData { algo: 0 }) // 1: Literal(Literal { body: "three (5 bytes)" }) // 2: Literal(Literal { body: "four (4 bytes)" }) let mut top_level = Vec::new(); top_level.push( CompressedData::new(CompressionAlgorithm::Uncompressed) .push(CompressedData::new(CompressionAlgorithm::Uncompressed) .push(CompressedData::new(CompressionAlgorithm::Uncompressed) .push(CompressedData::new(CompressionAlgorithm::Uncompressed) .push(one.clone().into()) .push(two.clone().into()) .into()) .into()) .into()) .push(CompressedData::new(CompressionAlgorithm::Uncompressed) .push(CompressedData::new(CompressionAlgorithm::Uncompressed) .push(three.clone().into()) .into()) .push(four.clone().into()) .into()) .into()); messages.push(top_level); // 1: CompressedData(CompressedData { algo: 0 }) // 1: Literal(Literal { body: "one (3 bytes)" }) // 2: Literal(Literal { body: "two (3 bytes)" }) // 2: Literal(Literal { body: "three (5 bytes)" }) // 3: Literal(Literal { body: "four (4 bytes)" }) // 4: CompressedData(CompressedData { algo: 0 }) // 1: Literal(Literal { body: "five (4 bytes)" }) // 2: Literal(Literal { body: "six (3 bytes)" }) let mut top_level = Vec::new(); top_level.push( CompressedData::new(CompressionAlgorithm::Uncompressed) .push(one.clone().into()) .push(two.clone().into()) .into()); top_level.push( three.clone().into()); top_level.push( four.clone().into()); top_level.push( CompressedData::new(CompressionAlgorithm::Uncompressed) .push(five.into()) .push(six.into()) .into()); messages.push(top_level); // 1: UserID(UserID { value: "Foo" }) let mut top_level = Vec::new(); let uid = UserID::from("Foo"); top_level.push(uid.into()); messages.push(top_level); for m in messages.into_iter() { // 1. The message. let pile = PacketPile::from(m); pile.pretty_print(); // 2. Serialize the message into a buffer. let mut buffer = Vec::new(); (&pile as &dyn Marshal).serialize(&mut buffer).unwrap(); // 3. Reparse it. let pile2 = PacketParserBuilder::from_bytes(&buffer[..]).unwrap() //.trace() .buffer_unread_content() .into_packet_pile().unwrap(); // 4. Compare the messages. if pile != pile2 { eprintln!("ORIG..."); pile.pretty_print(); eprintln!("REPARSED..."); pile2.pretty_print(); panic!("Reparsed packet does not match original packet!"); } } } #[test] fn body_length_edge_cases() { { let mut buf = vec![]; BodyLength::Full(0).serialize(&mut buf).unwrap(); assert_eq!(&buf[..], &b"\x00"[..]); } { let mut buf = vec![]; BodyLength::Full(1).serialize(&mut buf).unwrap(); assert_eq!(&buf[..], &b"\x01"[..]); } { let mut buf = vec![]; BodyLength::Full(191).serialize(&mut buf).unwrap(); assert_eq!(&buf[..], &b"\xbf"[..]); } { let mut buf = vec![]; BodyLength::Full(192).serialize(&mut buf).unwrap(); assert_eq!(&buf[..], &b"\xc0\x00"[..]); } { let mut buf = vec![]; BodyLength::Full(193).serialize(&mut buf).unwrap(); assert_eq!(&buf[..], &b"\xc0\x01"[..]); } { let mut buf = vec![]; BodyLength::Full(8383).serialize(&mut buf).unwrap(); assert_eq!(&buf[..], &b"\xdf\xff"[..]); } { let mut buf = vec![]; BodyLength::Full(8384).serialize(&mut buf).unwrap(); assert_eq!(&buf[..], &b"\xff\x00\x00\x20\xc0"[..]); } { let mut buf = vec![]; BodyLength::Full(0xffffffff).serialize(&mut buf).unwrap(); assert_eq!(&buf[..], &b"\xff\xff\xff\xff\xff"[..]); } } #[test] fn export_signature() { use crate::cert::prelude::*; let (cert, _) = CertBuilder::new().generate().unwrap(); let mut keypair = cert.primary_key().key().clone().parts_into_secret() .unwrap().into_keypair().unwrap(); let uid = UserID::from("foo"); // Make a signature w/o an exportable certification subpacket. let sig = uid.bind( &mut keypair, &cert, signature::SignatureBuilder::new(SignatureType::GenericCertification)) .unwrap(); // The signature is exportable. Try to export it in // various ways. sig.export(&mut Vec::new()).unwrap(); sig.export_into(&mut vec![0; sig.serialized_len()]).unwrap(); sig.export_to_vec().unwrap(); (&PacketRef::Signature(&sig) as &dyn Marshal) .export(&mut Vec::new()).unwrap(); (&PacketRef::Signature(&sig) as &dyn MarshalInto).export_into( &mut vec![0; (&PacketRef::Signature(&sig) as &dyn MarshalInto) .serialized_len()]).unwrap(); (&PacketRef::Signature(&sig) as &dyn MarshalInto) .export_to_vec().unwrap(); let p = Packet::Signature(sig); (&p as &dyn Marshal).export(&mut Vec::new()).unwrap(); (&p as &dyn MarshalInto) .export_into( &mut vec![0; (&p as &dyn MarshalInto).serialized_len()]) .unwrap(); (&p as &dyn MarshalInto).export_to_vec().unwrap(); let pp = PacketPile::from(vec![p]); (&pp as &dyn Marshal).export(&mut Vec::new()).unwrap(); (&pp as &dyn MarshalInto) .export_into( &mut vec![0; (&pp as &dyn MarshalInto).serialized_len()]) .unwrap(); (&pp as &dyn MarshalInto).export_to_vec().unwrap(); // Make a signature that is explicitly marked as exportable. let sig = uid.bind( &mut keypair, &cert, signature::SignatureBuilder::new(SignatureType::GenericCertification) .set_exportable_certification(true).unwrap()).unwrap(); // The signature is exportable. Try to export it in // various ways. sig.export(&mut Vec::new()).unwrap(); sig.export_into(&mut vec![0; sig.serialized_len()]).unwrap(); sig.export_to_vec().unwrap(); (&PacketRef::Signature(&sig) as &dyn Marshal) .export(&mut Vec::new()).unwrap(); (&PacketRef::Signature(&sig) as &dyn MarshalInto) .export_into( &mut vec![0; (&PacketRef::Signature(&sig) as &dyn MarshalInto).serialized_len()]) .unwrap(); (&PacketRef::Signature(&sig) as &dyn MarshalInto) .export_to_vec().unwrap(); let p = Packet::Signature(sig); (&p as &dyn Marshal).export(&mut Vec::new()).unwrap(); (&p as &dyn MarshalInto) .export_into( &mut vec![0; (&p as &dyn MarshalInto).serialized_len()]) .unwrap(); (&p as &dyn MarshalInto).export_to_vec().unwrap(); let pp = PacketPile::from(vec![p]); (&pp as &dyn Marshal).export(&mut Vec::new()).unwrap(); (&pp as &dyn MarshalInto) .export_into( &mut vec![0; (&pp as &dyn MarshalInto).serialized_len()]) .unwrap(); (&pp as &dyn MarshalInto).export_to_vec().unwrap(); // Make a non-exportable signature. let sig = uid.bind( &mut keypair, &cert, signature::SignatureBuilder::new(SignatureType::GenericCertification) .set_exportable_certification(false).unwrap()).unwrap(); // The signature is not exportable. Try to export it in // various ways. sig.export(&mut Vec::new()).unwrap_err(); sig.export_into(&mut vec![0; sig.serialized_len()]).unwrap_err(); sig.export_to_vec().unwrap_err(); (&PacketRef::Signature(&sig) as &dyn Marshal) .export(&mut Vec::new()).unwrap_err(); (&PacketRef::Signature(&sig) as &dyn MarshalInto) .export_into( &mut vec![0; (&PacketRef::Signature(&sig) as &dyn MarshalInto).serialized_len()]) .unwrap_err(); (&PacketRef::Signature(&sig) as &dyn MarshalInto) .export_to_vec().unwrap_err(); let p = Packet::Signature(sig); (&p as &dyn Marshal).export(&mut Vec::new()).unwrap_err(); (&p as &dyn MarshalInto) .export_into(&mut vec![0; (&p as &dyn MarshalInto).serialized_len()]) .unwrap_err(); (&p as &dyn MarshalInto).export_to_vec().unwrap_err(); let pp = PacketPile::from(vec![p]); (&pp as &dyn Marshal).export(&mut Vec::new()).unwrap_err(); (&pp as &dyn MarshalInto) .export_into( &mut vec![0; (&pp as &dyn MarshalInto).serialized_len()]) .unwrap_err(); (&pp as &dyn MarshalInto).export_to_vec().unwrap_err(); } quickcheck! { /// Checks that SerializeInto::serialized_len computes the /// exact size (except for CompressedData packets where we may /// overestimate the size). fn packet_serialized_len(p: Packet) -> bool { let p_as_vec = SerializeInto::to_vec(&p).unwrap(); if let Packet::CompressedData(_) = p { // serialized length may be an over-estimate assert!(SerializeInto::serialized_len(&p) >= p_as_vec.len()); } else { // serialized length should be exact assert_eq!(SerializeInto::serialized_len(&p), p_as_vec.len()); } true } } } ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/src/tests.rs������������������������������������������������������������������0000644�0000000�0000000�00000005672�10461020230�0015250�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������//! Test data for Sequoia. //! //! This module includes the test data from `openpgp/tests/data` in a //! structured way. use std::fmt; use std::collections::BTreeMap; pub struct Test { path: &'static str, pub bytes: &'static [u8], } impl fmt::Display for Test { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "openpgp/tests/data/{}", self.path) } } macro_rules! t { ( $path: expr ) => { &Test { path: $path, bytes: include_bytes!(concat!("../tests/data/", $path)), } } } pub const CERTS: &[&Test] = &[ t!("keys/dennis-simon-anton.pgp"), t!("keys/dsa2048-elgamal3072.pgp"), t!("keys/emmelie-dorothea-dina-samantha-awina-ed25519.pgp"), t!("keys/erika-corinna-daniela-simone-antonia-nistp256.pgp"), t!("keys/erika-corinna-daniela-simone-antonia-nistp384.pgp"), t!("keys/erika-corinna-daniela-simone-antonia-nistp521.pgp"), t!("keys/testy-new.pgp"), t!("keys/testy.pgp"), t!("keys/neal.pgp"), t!("keys/dkg-sigs-out-of-order.pgp"), ]; pub const TSKS: &[&Test] = &[ t!("keys/dennis-simon-anton-private.pgp"), t!("keys/dsa2048-elgamal3072-private.pgp"), t!("keys/emmelie-dorothea-dina-samantha-awina-ed25519-private.pgp"), t!("keys/erika-corinna-daniela-simone-antonia-nistp256-private.pgp"), t!("keys/erika-corinna-daniela-simone-antonia-nistp384-private.pgp"), t!("keys/erika-corinna-daniela-simone-antonia-nistp521-private.pgp"), t!("keys/testy-new-private.pgp"), t!("keys/testy-nistp256-private.pgp"), t!("keys/testy-nistp384-private.pgp"), t!("keys/testy-nistp521-private.pgp"), t!("keys/testy-private.pgp"), ]; /// Returns the content of the given file below `openpgp/tests/data`. pub fn file(name: &str) -> &'static [u8] { use std::sync::OnceLock; static FILES: OnceLock<BTreeMap<&'static str, &'static [u8]>> = OnceLock::new(); FILES.get_or_init(|| { let mut m: BTreeMap<&'static str, &'static [u8]> = Default::default(); macro_rules! add { ( $key: expr, $path: expr ) => { m.insert($key, include_bytes!($path)) } } include!(concat!(env!("OUT_DIR"), "/tests.index.rs.inc")); // Sanity checks. assert!(m.contains_key("messages/a-cypherpunks-manifesto.txt")); assert!(m.contains_key("keys/testy.pgp")); assert!(m.contains_key("keys/testy-private.pgp")); m }).get(name).unwrap_or_else(|| panic!("No such file {:?}", name)) } /// Returns the content of the given file below `openpgp/tests/data/keys`. pub fn key(name: &str) -> &'static [u8] { file(&format!("keys/{}", name)) } /// Returns the content of the given file below `openpgp/tests/data/messages`. pub fn message(name: &str) -> &'static [u8] { file(&format!("messages/{}", name)) } /// Returns the cypherpunks manifesto. pub fn manifesto() -> &'static [u8] { message("a-cypherpunks-manifesto.txt") } ����������������������������������������������������������������������sequoia-openpgp-2.0.0/src/types/bitfield.rs���������������������������������������������������������0000644�0000000�0000000�00000014526�10461020230�0017032�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������//! A variable-sized set of boolean flags. /// A variable-sized set of boolean flags. /// /// This encodes flags in signature subpackets such as [`Features`] /// and [`KeyFlags`]. The `Bitfield` grows to accommodate all bits /// that are set, and querying a bit outside the allocated space will /// return `false`. Note that it will not automatically shrink if /// clearing a bit would leave trailing bytes to be zero. To do that, /// explicitly call [`Bitfield::canonicalize`]. /// /// [`Features`]: crate::types::Features /// [`KeyFlags`]: crate::types::KeyFlags #[derive(Default, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct Bitfield { raw: Vec<u8>, } impl From<Vec<u8>> for Bitfield { fn from(raw: Vec<u8>) -> Self { Self { raw } } } impl AsRef<[u8]> for Bitfield { fn as_ref(&self) -> &[u8] { self.as_bytes() } } impl AsMut<[u8]> for Bitfield { fn as_mut(&mut self) -> &mut [u8] { self.as_bytes_mut() } } impl Bitfield { /// Returns all bits that are set starting from bit 0, the /// least-significant bit in the left-most byte. /// /// # Examples /// /// ```rust /// # use sequoia_openpgp::types::Bitfield; /// let f = Bitfield::from(vec![0b0000_0001, 0b0000_0010]); /// let mut i = f.iter_set(); /// assert_eq!(i.next(), Some(0)); /// assert_eq!(i.next(), Some(9)); /// assert_eq!(i.next(), None); /// ``` pub fn iter_set(&self) -> impl Iterator<Item = usize> + Send + Sync + '_ { self.raw.iter() .flat_map(|b| { (0..8).into_iter().map(move |i| { b & (1 << i) != 0 }) }) .enumerate() .filter_map(|(i, v)| if v { Some(i) } else { None }) } /// Returns the number of trailing zero bytes. /// /// # Examples /// /// ```rust /// # use sequoia_openpgp::types::Bitfield; /// let mut f = Bitfield::from(vec![0b0000_0001]); /// assert!(f.padding_bytes().is_none()); /// f.clear(0); /// assert_eq!(f.padding_bytes().unwrap().get(), 1); /// f.canonicalize(); /// assert!(f.padding_bytes().is_none()); /// ``` pub fn padding_bytes(&self) -> Option<std::num::NonZeroUsize> { std::num::NonZeroUsize::new( self.raw.iter().rev().take_while(|b| **b == 0).count()) } /// Compares two feature sets for semantic equality. /// /// Returns true if both sets have the same flags set, i.e. this /// function ignores any trailing zero bytes. /// /// # Examples /// /// ```rust /// # use sequoia_openpgp::types::Bitfield; /// let f = Bitfield::from(vec![0b0000_0001]); /// let g = Bitfield::from(vec![0b0000_0001, 0b0000_0000]); /// assert!(f != g); /// assert!(f.normalized_eq(&g)); /// ``` pub fn normalized_eq(&self, other: &Self) -> bool { let (small, big) = if self.raw.len() < other.raw.len() { (self, other) } else { (other, self) }; for (s, b) in small.raw.iter().zip(big.raw.iter()) { if s != b { return false; } } for &b in &big.raw[small.raw.len()..] { if b != 0 { return false; } } true } /// Returns a slice containing the raw values. /// /// # Examples /// /// ```rust /// # use sequoia_openpgp::types::Bitfield; /// let mut f = Bitfield::default(); /// assert_eq!(f.as_bytes(), &[]); /// f.set(0); /// assert_eq!(f.as_bytes(), &[0b0000_0001]); /// ``` pub fn as_bytes(&self) -> &[u8] { &self.raw } /// Returns a mutable slice containing the raw values. /// /// # Examples /// /// ```rust /// # use sequoia_openpgp::types::Bitfield; /// let mut f = Bitfield::from(vec![0b0000_0000]); /// assert_eq!(f.get(0), false); /// f.as_bytes_mut()[0] = 0b0000_0001; /// assert_eq!(f.get(0), true); /// ``` pub fn as_bytes_mut(&mut self) -> &mut [u8] { &mut self.raw } /// Returns whether the specified flag is set. /// /// # Examples /// /// ```rust /// # use sequoia_openpgp::types::Bitfield; /// let f = Bitfield::default(); /// assert_eq!(f.get(0), false); /// assert_eq!(f.get(23), false); /// /// let f = Bitfield::from(vec![0b0000_0001]); /// assert_eq!(f.get(0), true); /// ``` pub fn get(&self, bit: usize) -> bool { let byte = bit / 8; if byte >= self.raw.len() { // Unset bits are false. false } else { (self.raw[byte] & (1 << (bit % 8))) != 0 } } /// Canonicalize by removing any trailing zero bytes. /// /// # Examples /// /// ```rust /// # use sequoia_openpgp::types::Bitfield; /// let mut f = Bitfield::from(vec![0b0000_0001]); /// assert!(f.padding_bytes().is_none()); /// f.clear(0); /// assert_eq!(f.padding_bytes().unwrap().get(), 1); /// f.canonicalize(); /// assert!(f.padding_bytes().is_none()); /// ``` pub fn canonicalize(&mut self) { while !self.raw.is_empty() && self.raw[self.raw.len() - 1] == 0 { self.raw.truncate(self.raw.len() - 1); } } /// Sets the specified flag. /// /// # Examples /// /// ```rust /// # use sequoia_openpgp::types::Bitfield; /// let mut f = Bitfield::default(); /// assert_eq!(f.get(0), false); /// f.set(0); /// assert_eq!(f.get(0), true); /// ``` pub fn set(&mut self, bit: usize) { let byte = bit / 8; while self.raw.len() <= byte { self.raw.push(0); } self.raw[byte] |= 1 << (bit % 8); } /// Clears the specified flag. /// /// Note: This does not implicitly canonicalize the bit field. To /// do that, invoke [`Bitfield::canonicalize`]. /// /// # Examples /// /// ```rust /// # use sequoia_openpgp::types::Bitfield; /// let mut f = Bitfield::from(vec![0b0000_0001]); /// assert_eq!(f.get(0), true); /// f.clear(0); /// assert_eq!(f.get(0), false); /// assert_eq!(f.padding_bytes().unwrap().get(), 1); /// ``` pub fn clear(&mut self, bit: usize) { let byte = bit / 8; if byte < self.raw.len() { self.raw[byte] &= !(1 << (bit % 8)); } } } ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/src/types/compression_level.rs������������������������������������������������0000644�0000000�0000000�00000006113�10461020230�0020771�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������//! Common code for the compression writers. use crate::{ Error, Result, }; /// Compression level. /// /// This value is used by the encoders to tune their compression /// strategy. The level is restricted to levels commonly used by /// compression libraries, `0` to `9`, where `0` means no compression, /// `1` means fastest compression, `6` being a good default, and /// meaning `9` best compression. /// /// Note that compression is [dangerous when used naively]. /// /// [dangerous when used naively]: https://mailarchive.ietf.org/arch/msg/openpgp/2FQUVt6Dw8XAsaMELyo5BNlh2pM #[cfg_attr(feature = "compression-deflate", doc = r##" To mitigate some of these issues messages should [use padding]. [use padding]: crate::serialize::stream::padding # Examples Write a message using the given [CompressionAlgorithm]: [CompressionAlgorithm]: super::CompressionAlgorithm ``` use sequoia_openpgp as openpgp; # fn main() -> openpgp::Result<()> { use std::io::Write; use openpgp::serialize::stream::{Message, Compressor, LiteralWriter}; use openpgp::serialize::stream::padding::Padder; use openpgp::types::{CompressionAlgorithm, CompressionLevel}; let mut sink = Vec::new(); let message = Message::new(&mut sink); let message = Compressor::new(message) .algo(CompressionAlgorithm::Zlib) # .algo(CompressionAlgorithm::Uncompressed) .level(CompressionLevel::fastest()) .build()?; let message = Padder::new(message).build()?; let mut message = LiteralWriter::new(message).build()?; message.write_all(b"Hello world.")?; message.finalize()?; # Ok(()) } ``` "##)] #[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct CompressionLevel(u8); assert_send_and_sync!(CompressionLevel); impl Default for CompressionLevel { fn default() -> Self { Self(6) } } impl CompressionLevel { /// Creates a new compression level. /// /// `level` must be in range `0..10`, where `0` means no /// compression, `1` means fastest compression, `6` being a good /// default, and meaning `9` best compression. pub fn new(level: u8) -> Result<CompressionLevel> { if level < 10 { Ok(Self(level)) } else { Err(Error::InvalidArgument( format!("compression level out of range: {}", level)).into()) } } /// No compression. pub fn none() -> CompressionLevel { Self(0) } /// Fastest compression. pub fn fastest() -> CompressionLevel { Self(1) } /// Best compression. pub fn best() -> CompressionLevel { Self(9) } } #[cfg(feature = "compression-deflate")] mod into_deflate_compression { use flate2::Compression; use super::*; impl From<CompressionLevel> for Compression { fn from(l: CompressionLevel) -> Self { Compression::new(l.0 as u32) } } } #[cfg(feature = "compression-bzip2")] mod into_bzip2_compression { use bzip2::Compression; use super::*; impl From<CompressionLevel> for Compression { fn from(l: CompressionLevel) -> Self { Compression::new(l.0 as u32) } } } �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/src/types/features.rs���������������������������������������������������������0000644�0000000�0000000�00000031126�10461020230�0017061�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������use std::fmt; #[cfg(test)] use quickcheck::{Arbitrary, Gen}; use crate::types::Bitfield; /// Describes the features supported by an OpenPGP implementation. /// /// The feature flags are defined in [Section 5.2.3.32 of RFC 9580], /// and [Section 5.2.3.32 of draft-ietf-openpgp-crypto-refresh]. /// /// [Section 5.2.3.32 of RFC 9580]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.32 /// [Section 5.2.3.32 of draft-ietf-openpgp-crypto-refresh]: https://www.ietf.org/archive/id/draft-ietf-openpgp-crypto-refresh-10.html#features-subpacket /// /// The feature flags are set by the user's OpenPGP implementation to /// signal to any senders what features the implementation supports. /// /// # A note on equality /// /// `PartialEq` compares the serialized form of the two feature sets. /// If you prefer to compare two feature sets for semantic equality, /// you should use [`Features::normalized_eq`]. The difference /// between semantic equality and serialized equality is that semantic /// equality ignores differences in the amount of padding. /// /// [`Features::normalized_eq`]: Features::normalized_eq() /// /// # Examples /// /// ``` /// use sequoia_openpgp as openpgp; /// # use openpgp::Result; /// use openpgp::cert::prelude::*; /// use openpgp::policy::StandardPolicy; /// /// # fn main() -> Result<()> { /// let p = &StandardPolicy::new(); /// /// let (cert, _) = /// CertBuilder::general_purpose(Some("alice@example.org")) /// .generate()?; /// match cert.with_policy(p, None)?.primary_userid()?.features() { /// Some(features) => { /// println!("Certificate holder's supported features:"); /// assert!(features.supports_seipdv1()); /// assert!(features.supports_seipdv2()); /// } /// None => { /// println!("Certificate Holder did not specify any features."); /// # unreachable!(); /// } /// } /// # Ok(()) } /// ``` #[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct Features(Bitfield); assert_send_and_sync!(Features); impl fmt::Debug for Features { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { // Print known features first. let mut need_comma = false; if self.supports_seipdv1() { f.write_str("SEIPDv1")?; need_comma = true; } if self.supports_seipdv2() { if need_comma { f.write_str(", ")?; } f.write_str("SEIPDv2")?; need_comma = true; } // Now print any unknown features. for i in self.0.iter_set() { match i { FEATURE_FLAG_SEIPDV1 => (), FEATURE_FLAG_SEIPDV2 => (), i => { if need_comma { f.write_str(", ")?; } write!(f, "#{}", i)?; need_comma = true; } } } // Mention any padding, as equality is sensitive to this. if let Some(padding) = self.0.padding_bytes() { if need_comma { f.write_str(", ")?; } write!(f, "+padding({} bytes)", padding)?; } Ok(()) } } impl Features { /// Creates a new instance from `bytes`. /// /// This does not remove any trailing padding from `bytes`. pub fn new<B>(bytes: B) -> Self where B: AsRef<[u8]> { Features(bytes.as_ref().to_vec().into()) } /// Returns an empty feature set. pub fn empty() -> Self { Self::new(&[][..]) } /// Returns a feature set describing Sequoia's capabilities. pub fn sequoia() -> Self { let v : [u8; 1] = [ 0 ]; Self::new(&v[..]) .set_seipdv1() .set_seipdv2() } /// Returns a reference to the underlying [`Bitfield`]. pub fn as_bitfield(&self) -> &Bitfield { &self.0 } /// Returns a mutable reference to the underlying [`Bitfield`]. pub fn as_bitfield_mut(&mut self) -> &mut Bitfield { &mut self.0 } /// Compares two feature sets for semantic equality. /// /// `Features` implementation of `PartialEq` compares two feature /// sets for serialized equality. That is, the `PartialEq` /// implementation considers two feature sets to *not* be equal if /// they have different amounts of padding. This comparison /// function ignores padding. /// /// # Examples /// /// ``` /// use sequoia_openpgp as openpgp; /// # use openpgp::Result; /// use openpgp::types::Features; /// /// # fn main() -> Result<()> { /// let a = Features::new(&[0x1]); /// let b = Features::new(&[0x1, 0x0]); /// /// assert!(a != b); /// assert!(a.normalized_eq(&b)); /// # Ok(()) } /// ``` pub fn normalized_eq(&self, other: &Self) -> bool { self.0.normalized_eq(&other.0) } /// Returns whether the specified feature flag is set. /// /// # Examples /// /// ``` /// use sequoia_openpgp as openpgp; /// # use openpgp::Result; /// use openpgp::types::Features; /// /// # fn main() -> Result<()> { /// // Feature flags 0 and 3. /// let f = Features::new(&[0x9]); /// /// assert!(f.get(0)); /// assert!(! f.get(1)); /// assert!(! f.get(2)); /// assert!(f.get(3)); /// assert!(! f.get(4)); /// assert!(! f.get(8)); /// assert!(! f.get(80)); /// # Ok(()) } /// ``` pub fn get(&self, bit: usize) -> bool { self.0.get(bit) } /// Sets the specified feature flag. /// /// This also clears any padding (trailing NUL bytes). /// /// # Examples /// /// ``` /// use sequoia_openpgp as openpgp; /// # use openpgp::Result; /// use openpgp::types::Features; /// /// # fn main() -> Result<()> { /// let f = Features::empty().set(0).set(3); /// /// assert!(f.get(0)); /// assert!(! f.get(1)); /// assert!(! f.get(2)); /// assert!(f.get(3)); /// assert!(! f.get(4)); /// assert!(! f.get(8)); /// assert!(! f.get(80)); /// # Ok(()) } /// ``` pub fn set(mut self, bit: usize) -> Self { self.0.set(bit); self.0.canonicalize(); self } /// Clears the specified feature flag. /// /// This also clears any padding (trailing NUL bytes). /// /// # Examples /// /// ``` /// use sequoia_openpgp as openpgp; /// # use openpgp::Result; /// use openpgp::types::Features; /// /// # fn main() -> Result<()> { /// let f = Features::empty().set(0).set(3).clear(3); /// /// assert!(f.get(0)); /// assert!(! f.get(1)); /// assert!(! f.get(2)); /// assert!(! f.get(3)); /// # Ok(()) } /// ``` pub fn clear(mut self, bit: usize) -> Self { self.0.clear(bit); self.0.canonicalize(); self } /// Returns whether the SEIPDv1 feature flag is set. /// /// # Examples /// /// ``` /// use sequoia_openpgp as openpgp; /// # use openpgp::Result; /// use openpgp::types::Features; /// /// # fn main() -> Result<()> { /// let f = Features::empty(); /// /// assert!(! f.supports_seipdv1()); /// # Ok(()) } /// ``` pub fn supports_seipdv1(&self) -> bool { self.get(FEATURE_FLAG_SEIPDV1) } /// Sets the SEIPDv1 feature flag. /// /// # Examples /// /// ``` /// use sequoia_openpgp as openpgp; /// # use openpgp::Result; /// use openpgp::types::Features; /// /// # fn main() -> Result<()> { /// let f = Features::empty().set_seipdv1(); /// /// assert!(f.supports_seipdv1()); /// # assert!(f.get(0)); /// # Ok(()) } /// ``` pub fn set_seipdv1(self) -> Self { self.set(FEATURE_FLAG_SEIPDV1) } /// Clears the SEIPDv1 feature flag. /// /// # Examples /// /// ``` /// use sequoia_openpgp as openpgp; /// # use openpgp::Result; /// use openpgp::types::Features; /// /// # fn main() -> Result<()> { /// let f = Features::new(&[0x1]); /// assert!(f.supports_seipdv1()); /// /// let f = f.clear_seipdv1(); /// assert!(! f.supports_seipdv1()); /// # Ok(()) } /// ``` pub fn clear_seipdv1(self) -> Self { self.clear(FEATURE_FLAG_SEIPDV1) } /// Returns whether the SEIPDv2 feature flag is set. /// /// # Examples /// /// ``` /// use sequoia_openpgp as openpgp; /// # use openpgp::Result; /// use openpgp::types::Features; /// /// # fn main() -> Result<()> { /// let f = Features::empty(); /// /// assert!(! f.supports_seipdv2()); /// # Ok(()) } /// ``` pub fn supports_seipdv2(&self) -> bool { self.get(FEATURE_FLAG_SEIPDV2) } /// Sets the SEIPDv2 feature flag. /// /// # Examples /// /// ``` /// use sequoia_openpgp as openpgp; /// # use openpgp::Result; /// use openpgp::types::Features; /// /// # fn main() -> Result<()> { /// let f = Features::empty().set_seipdv2(); /// /// assert!(f.supports_seipdv2()); /// # assert!(f.get(3)); /// # Ok(()) } /// ``` pub fn set_seipdv2(self) -> Self { self.set(FEATURE_FLAG_SEIPDV2) } /// Clears the SEIPDv2 feature flag. /// /// # Examples /// /// ``` /// use sequoia_openpgp as openpgp; /// # use openpgp::Result; /// use openpgp::types::Features; /// /// # fn main() -> Result<()> { /// let f = Features::new(&[0x8]); /// assert!(f.supports_seipdv2()); /// /// let f = f.clear_seipdv2(); /// assert!(! f.supports_seipdv2()); /// # Ok(()) } /// ``` pub fn clear_seipdv2(self) -> Self { self.clear(FEATURE_FLAG_SEIPDV2) } } /// Symmetrically Encrypted and Integrity Protected Data packet /// version 1. const FEATURE_FLAG_SEIPDV1: usize = 0; /// Symmetrically Encrypted and Integrity Protected Data packet /// version 2. const FEATURE_FLAG_SEIPDV2: usize = 3; #[cfg(test)] impl Arbitrary for Features { fn arbitrary(g: &mut Gen) -> Self { Self::new(Vec::arbitrary(g)) } } #[cfg(test)] mod tests { use super::*; quickcheck! { fn roundtrip(val: Features) -> bool { let mut q_bytes = val.as_bitfield().as_bytes().to_vec(); let q = Features::new(&q_bytes); assert_eq!(val, q); assert!(val.normalized_eq(&q)); // Add some padding to q. Make sure they are still equal. q_bytes.push(0); let q = Features::new(&q_bytes); assert!(val != q); assert!(val.normalized_eq(&q)); q_bytes.push(0); let q = Features::new(&q_bytes); assert!(val != q); assert!(val.normalized_eq(&q)); true } } #[test] fn set_clear() { let a = Features::new(&[ 0x5, 0x1, 0x0, 0xff ]); let b = Features::new(&[]) .set(0).set(2) .set(8) .set(24).set(25).set(26).set(27).set(28).set(29).set(30).set(31); assert_eq!(a, b); // Clear a bit and make sure they are not equal. let b = b.clear(0); assert!(a != b); assert!(! a.normalized_eq(&b)); let b = b.set(0); assert_eq!(a, b); assert!(a.normalized_eq(&b)); let b = b.clear(8); assert!(a != b); assert!(! a.normalized_eq(&b)); let b = b.set(8); assert_eq!(a, b); assert!(a.normalized_eq(&b)); let b = b.clear(31); assert!(a != b); assert!(! a.normalized_eq(&b)); let b = b.set(31); assert_eq!(a, b); assert!(a.normalized_eq(&b)); // Add a bit. let a = a.set(10); assert!(a != b); assert!(! a.normalized_eq(&b)); let b = b.set(10); assert_eq!(a, b); assert!(a.normalized_eq(&b)); let a = a.set(32); assert!(a != b); assert!(! a.normalized_eq(&b)); let b = b.set(32); assert_eq!(a, b); assert!(a.normalized_eq(&b)); let a = a.set(1000); assert!(a != b); assert!(! a.normalized_eq(&b)); let b = b.set(1000); assert_eq!(a, b); assert!(a.normalized_eq(&b)); } #[test] fn known() { let a = Features::empty().set_seipdv1(); let b = Features::new(&[ 0x1 ]); assert_eq!(a, b); assert!(a.normalized_eq(&b)); let a = Features::empty().set_seipdv2(); let b = Features::new(&[ 0x8 ]); assert_eq!(a, b); assert!(a.normalized_eq(&b)); #[allow(deprecated)] let a = Features::empty().set_seipdv1().set_seipdv2(); let b = Features::new(&[ 0x1 | 0x8 ]); assert_eq!(a, b); assert!(a.normalized_eq(&b)); } } ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/src/types/key_flags.rs��������������������������������������������������������0000644�0000000�0000000�00000040252�10461020230�0017207�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������use std::fmt; use std::ops::{BitAnd, BitOr}; #[cfg(test)] use quickcheck::{Arbitrary, Gen}; use crate::types::Bitfield; /// Describes how a key may be used, and stores additional information. /// /// Key flags are described in [Section 5.2.3.29 of RFC 9580]. /// /// [Section 5.2.3.29 of RFC 9580]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.29 /// /// # A note on equality /// /// `PartialEq` compares the serialized form of the key flag sets. If /// you prefer to compare two key flag sets for semantic equality, you /// should use [`KeyFlags::normalized_eq`]. The difference between /// semantic equality and serialized equality is that semantic /// equality ignores differences in the amount of padding. /// /// [`KeyFlags::normalized_eq`]: KeyFlags::normalized_eq() /// /// # Examples /// /// ``` /// use sequoia_openpgp as openpgp; /// # use openpgp::Result; /// use openpgp::cert::prelude::*; /// use openpgp::policy::StandardPolicy; /// /// # fn main() -> Result<()> { /// let p = &StandardPolicy::new(); /// /// let (cert, _) = /// CertBuilder::new() /// .add_userid("Alice <alice@example.com>") /// .add_transport_encryption_subkey() /// .generate()?; /// /// for subkey in cert.with_policy(p, None)?.keys().subkeys() { /// // Key contains one Encryption subkey: /// assert!(subkey.key_flags().unwrap().for_transport_encryption()); /// } /// # Ok(()) } /// ``` #[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct KeyFlags(Bitfield); assert_send_and_sync!(KeyFlags); impl fmt::Debug for KeyFlags { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { if self.for_certification() { f.write_str("C")?; } if self.for_signing() { f.write_str("S")?; } if self.for_transport_encryption() { f.write_str("Et")?; } if self.for_storage_encryption() { f.write_str("Er")?; } if self.for_authentication() { f.write_str("A")?; } if self.is_split_key() { f.write_str("D")?; } if self.is_group_key() { f.write_str("G")?; } let mut need_comma = false; for i in self.0.iter_set() { match i { KEY_FLAG_CERTIFY | KEY_FLAG_SIGN | KEY_FLAG_ENCRYPT_FOR_TRANSPORT | KEY_FLAG_ENCRYPT_AT_REST | KEY_FLAG_SPLIT_KEY | KEY_FLAG_AUTHENTICATE | KEY_FLAG_GROUP_KEY => (), i => { if need_comma { f.write_str(", ")?; } write!(f, "#{}", i)?; need_comma = true; }, } } // Mention any padding, as equality is sensitive to this. if let Some(padding) = self.0.padding_bytes() { if need_comma { f.write_str(", ")?; } write!(f, "+padding({} bytes)", padding)?; } Ok(()) } } impl BitAnd for &KeyFlags { type Output = KeyFlags; fn bitand(self, rhs: Self) -> KeyFlags { let l = self.as_bitfield().as_bytes(); let r = rhs.as_bitfield().as_bytes(); let mut c = Vec::with_capacity(std::cmp::min(l.len(), r.len())); for (l, r) in l.iter().zip(r.iter()) { c.push(l & r); } KeyFlags(c.into()) } } impl BitOr for &KeyFlags { type Output = KeyFlags; fn bitor(self, rhs: Self) -> KeyFlags { let l = self.as_bitfield().as_bytes(); let r = rhs.as_bitfield().as_bytes(); // Make l the longer one. let (l, r) = if l.len() > r.len() { (l, r) } else { (r, l) }; let mut l = l.to_vec(); for (i, r) in r.iter().enumerate() { l[i] |= r; } KeyFlags(l.into()) } } impl AsRef<KeyFlags> for KeyFlags { fn as_ref(&self) -> &KeyFlags { self } } impl KeyFlags { /// Creates a new instance from `bits`. pub fn new<B: AsRef<[u8]>>(bits: B) -> Self { Self(bits.as_ref().to_vec().into()) } /// Returns a new `KeyFlags` with all capabilities disabled. pub fn empty() -> Self { KeyFlags::new(&[]) } /// Returns a reference to the underlying [`Bitfield`]. pub fn as_bitfield(&self) -> &Bitfield { &self.0 } /// Returns a mutable reference to the underlying [`Bitfield`]. pub fn as_bitfield_mut(&mut self) -> &mut Bitfield { &mut self.0 } /// Compares two key flag sets for semantic equality. /// /// `KeyFlags` implementation of `PartialEq` compares two key /// flag sets for serialized equality. That is, the `PartialEq` /// implementation considers two key flag sets to *not* be equal /// if they have different amounts of padding. This comparison /// function ignores padding. /// /// # Examples /// /// ``` /// use sequoia_openpgp as openpgp; /// use openpgp::types::KeyFlags; /// /// # fn main() -> openpgp::Result<()> { /// let a = KeyFlags::new(&[0x1]); /// let b = KeyFlags::new(&[0x1, 0x0]); /// /// assert!(a != b); /// assert!(a.normalized_eq(&b)); /// # Ok(()) } /// ``` pub fn normalized_eq(&self, other: &Self) -> bool { self.0.normalized_eq(&other.0) } /// Returns whether the specified key flag is set. /// /// # Examples /// /// ``` /// use sequoia_openpgp as openpgp; /// use openpgp::types::KeyFlags; /// /// # fn main() -> openpgp::Result<()> { /// // Key flags 0 and 2. /// let kf = KeyFlags::new(&[0x5]); /// /// assert!(kf.get(0)); /// assert!(! kf.get(1)); /// assert!(kf.get(2)); /// assert!(! kf.get(3)); /// assert!(! kf.get(8)); /// assert!(! kf.get(80)); /// # assert!(kf.for_certification()); /// # Ok(()) } /// ``` pub fn get(&self, bit: usize) -> bool { self.0.get(bit) } /// Sets the specified key flag. /// /// This also clears any padding (trailing NUL bytes). /// /// # Examples /// /// ``` /// use sequoia_openpgp as openpgp; /// use openpgp::types::KeyFlags; /// /// # fn main() -> openpgp::Result<()> { /// let kf = KeyFlags::empty().set(0).set(2); /// /// assert!(kf.get(0)); /// assert!(! kf.get(1)); /// assert!(kf.get(2)); /// assert!(! kf.get(3)); /// # assert!(kf.for_certification()); /// # Ok(()) } /// ``` pub fn set(mut self, bit: usize) -> Self { self.0.set(bit); self.0.canonicalize(); self } /// Clears the specified key flag. /// /// This also clears any padding (trailing NUL bytes). /// /// # Examples /// /// ``` /// use sequoia_openpgp as openpgp; /// use openpgp::types::KeyFlags; /// /// # fn main() -> openpgp::Result<()> { /// let kf = KeyFlags::empty().set(0).set(2).clear(2); /// /// assert!(kf.get(0)); /// assert!(! kf.get(1)); /// assert!(! kf.get(2)); /// assert!(! kf.get(3)); /// # assert!(kf.for_certification()); /// # Ok(()) } /// ``` pub fn clear(mut self, bit: usize) -> Self { self.0.clear(bit); self.0.canonicalize(); self } /// This key may be used to certify other keys. pub fn for_certification(&self) -> bool { self.get(KEY_FLAG_CERTIFY) } /// Returns a KeyFlags where the certificate flag is set. pub fn certification() -> Self { KeyFlags::empty().set_certification() } /// Declares that this key may be used to certify other keys. pub fn set_certification(self) -> Self { self.set(KEY_FLAG_CERTIFY) } /// Declares that this key may not be used to certify other keys. pub fn clear_certification(self) -> Self { self.clear(KEY_FLAG_CERTIFY) } /// Declares whether this key may be used to certify other keys. pub fn set_certification_to(self, value: bool) -> Self { if value { self.set(KEY_FLAG_CERTIFY) } else { self.clear(KEY_FLAG_CERTIFY) } } /// This key may be used to sign data. pub fn for_signing(&self) -> bool { self.get(KEY_FLAG_SIGN) } /// Returns a KeyFlags where the signing flag is set. pub fn signing() -> Self { KeyFlags::empty().set_signing() } /// Declares that this key may be used to sign data. pub fn set_signing(self) -> Self { self.set(KEY_FLAG_SIGN) } /// Declares that this key may not be used to sign data. pub fn clear_signing(self) -> Self { self.clear(KEY_FLAG_SIGN) } /// Declares whether this key may be used to sign data. pub fn set_signing_to(self, value: bool) -> Self { if value { self.set(KEY_FLAG_SIGN) } else { self.clear(KEY_FLAG_SIGN) } } /// This key may be used to encrypt communications. pub fn for_transport_encryption(&self) -> bool { self.get(KEY_FLAG_ENCRYPT_FOR_TRANSPORT) } /// Returns a KeyFlags where the transport encryption flag is set. pub fn transport_encryption() -> Self { KeyFlags::empty().set_transport_encryption() } /// Declares that this key may be used to encrypt communications. pub fn set_transport_encryption(self) -> Self { self.set(KEY_FLAG_ENCRYPT_FOR_TRANSPORT) } /// Declares that this key may not be used to encrypt communications. pub fn clear_transport_encryption(self) -> Self { self.clear(KEY_FLAG_ENCRYPT_FOR_TRANSPORT) } /// Declares whether this key may be used to encrypt communications. pub fn set_transport_encryption_to(self, value: bool) -> Self { if value { self.set(KEY_FLAG_ENCRYPT_FOR_TRANSPORT) } else { self.clear(KEY_FLAG_ENCRYPT_FOR_TRANSPORT) } } /// This key may be used to encrypt storage. pub fn for_storage_encryption(&self) -> bool { self.get(KEY_FLAG_ENCRYPT_AT_REST) } /// Returns a KeyFlags where the storage encryption flag is set. pub fn storage_encryption() -> Self { KeyFlags::empty().set_storage_encryption() } /// Declares that this key may be used to encrypt storage. pub fn set_storage_encryption(self) -> Self { self.set(KEY_FLAG_ENCRYPT_AT_REST) } /// Declares that this key may not be used to encrypt storage. pub fn clear_storage_encryption(self) -> Self { self.clear(KEY_FLAG_ENCRYPT_AT_REST) } /// Declares whether this key may be used to encrypt storage. pub fn set_storage_encryption_to(self, value: bool) -> Self { if value { self.set(KEY_FLAG_ENCRYPT_AT_REST) } else { self.clear(KEY_FLAG_ENCRYPT_AT_REST) } } /// This key may be used for authentication. pub fn for_authentication(&self) -> bool { self.get(KEY_FLAG_AUTHENTICATE) } /// Returns a KeyFlags where the authentication flag is set. pub fn authentication() -> Self { KeyFlags::empty().set_authentication() } /// Declares that this key may be used for authentication. pub fn set_authentication(self) -> Self { self.set(KEY_FLAG_AUTHENTICATE) } /// Declares that this key may not be used for authentication. pub fn clear_authentication(self) -> Self { self.clear(KEY_FLAG_AUTHENTICATE) } /// Declares whether this key may be used for authentication. pub fn set_authentication_to(self, value: bool) -> Self { if value { self.set(KEY_FLAG_AUTHENTICATE) } else { self.clear(KEY_FLAG_AUTHENTICATE) } } /// The private component of this key may have been split /// using a secret-sharing mechanism. pub fn is_split_key(&self) -> bool { self.get(KEY_FLAG_SPLIT_KEY) } /// Returns a KeyFlags where the split key flag is set. pub fn split_key() -> Self { KeyFlags::empty().set_split_key() } /// Declares that the private component of this key may have been /// split using a secret-sharing mechanism. pub fn set_split_key(self) -> Self { self.set(KEY_FLAG_SPLIT_KEY) } /// Declares that the private component of this key has not been /// split using a secret-sharing mechanism. pub fn clear_split_key(self) -> Self { self.clear(KEY_FLAG_SPLIT_KEY) } /// Declares whether the private component of this key may have been /// split using a secret-sharing mechanism. pub fn set_split_key_to(self, value: bool) -> Self { if value { self.set(KEY_FLAG_SPLIT_KEY) } else { self.clear(KEY_FLAG_SPLIT_KEY) } } /// The private component of this key may be in possession of more /// than one person. pub fn is_group_key(&self) -> bool { self.get(KEY_FLAG_GROUP_KEY) } /// Returns a KeyFlags where the group flag is set. pub fn group_key() -> Self { KeyFlags::empty().set_group_key() } /// Declares that the private component of this key is in /// possession of more than one person. pub fn set_group_key(self) -> Self { self.set(KEY_FLAG_GROUP_KEY) } /// Declares that the private component of this key should not be /// in possession of more than one person. pub fn clear_group_key(self) -> Self { self.clear(KEY_FLAG_GROUP_KEY) } /// Declares whether the private component of this key is in /// possession of more than one person. pub fn set_group_key_to(self, value: bool) -> Self { if value { self.set(KEY_FLAG_GROUP_KEY) } else { self.clear(KEY_FLAG_GROUP_KEY) } } /// Returns whether no flags are set. pub fn is_empty(&self) -> bool { self.as_bitfield().as_bytes().iter().all(|b| *b == 0) } } /// This key may be used to certify other keys. const KEY_FLAG_CERTIFY: usize = 0; /// This key may be used to sign data. const KEY_FLAG_SIGN: usize = 1; /// This key may be used to encrypt communications. const KEY_FLAG_ENCRYPT_FOR_TRANSPORT: usize = 2; /// This key may be used to encrypt storage. const KEY_FLAG_ENCRYPT_AT_REST: usize = 3; /// The private component of this key may have been split by a /// secret-sharing mechanism. const KEY_FLAG_SPLIT_KEY: usize = 4; /// This key may be used for authentication. const KEY_FLAG_AUTHENTICATE: usize = 5; /// The private component of this key may be in the possession of more /// than one person. const KEY_FLAG_GROUP_KEY: usize = 7; #[cfg(test)] impl Arbitrary for KeyFlags { fn arbitrary(g: &mut Gen) -> Self { Self::new(Vec::arbitrary(g)) } } #[cfg(test)] mod tests { use super::*; quickcheck! { fn roundtrip(val: KeyFlags) -> bool { let mut q_bytes = val.as_bitfield().as_bytes().to_vec(); let q = KeyFlags::new(&q_bytes); assert_eq!(val, q); assert!(val.normalized_eq(&q)); // Add some padding to q. Make sure they are still equal. q_bytes.push(0); let q = KeyFlags::new(&q_bytes); assert!(val != q); assert!(val.normalized_eq(&q)); q_bytes.push(0); let q = KeyFlags::new(&q_bytes); assert!(val != q); assert!(val.normalized_eq(&q)); true } } #[test] fn test_set_to() { macro_rules! t { ($set:ident, $set2:ident) => { // Set using set2. assert_eq!(KeyFlags::empty().$set(), KeyFlags::empty().$set2(true)); // Clear using set2. assert_eq!(KeyFlags::empty().$set2(false), KeyFlags::empty()); // Set using set, then clear using set2. assert_eq!(KeyFlags::empty().$set().$set2(false), KeyFlags::empty()); } } t!(set_certification, set_certification_to); t!(set_signing, set_signing_to); t!(set_transport_encryption, set_transport_encryption_to); t!(set_storage_encryption, set_storage_encryption_to); t!(set_split_key, set_split_key_to); t!(set_group_key, set_group_key_to); } } ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/src/types/mod.rs��������������������������������������������������������������0000644�0000000�0000000�00000104200�10461020230�0016014�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������//! Primitive types. //! //! This module provides types used in OpenPGP, like enumerations //! describing algorithms. //! //! # Common Operations //! //! - *Rounding the creation time of signatures*: See the [`Timestamp::round_down`] method. //! - *Checking key usage flags*: See the [`KeyFlags`] data structure. //! - *Setting key validity ranges*: See the [`Timestamp`] and [`Duration`] data structures. //! //! # Data structures //! //! ## `CompressionLevel` //! //! Allows adjusting the amount of effort spent on compressing encoded data. //! This structure additionally has several helper methods for commonly used //! compression strategies. //! //! ## `Features` //! //! Describes particular features supported by the given OpenPGP implementation. //! //! ## `KeyFlags` //! //! Holds information about a key in particular how the given key can be used. //! //! ## `RevocationKey` //! //! Describes a key that has been designated to issue revocation signatures. //! //! # `KeyServerPreferences` //! //! Describes preferences regarding to key servers. //! //! ## `Timestamp` and `Duration` //! //! In OpenPGP time is represented as the number of seconds since the UNIX epoch stored //! as an `u32`. These two data structures allow manipulating OpenPGP time ensuring //! that adding or subtracting durations will never overflow or underflow without //! notice. //! //! [`Timestamp::round_down`]: Timestamp::round_down() use std::fmt; #[cfg(test)] use quickcheck::{Arbitrary, Gen}; mod bitfield; pub use bitfield::Bitfield; mod compression_level; pub use compression_level::CompressionLevel; mod features; pub use self::features::Features; mod key_flags; pub use self::key_flags::KeyFlags; mod revocation_key; pub use revocation_key::RevocationKey; mod server_preferences; pub use self::server_preferences::KeyServerPreferences; mod timestamp; pub use timestamp::{Timestamp, Duration}; pub(crate) use timestamp::normalize_systemtime; #[allow(dead_code)] // Used in assert_send_and_sync. pub(crate) trait Sendable : Send {} #[allow(dead_code)] // Used in assert_send_and_sync. pub(crate) trait Syncable : Sync {} pub use crate::crypto::AEADAlgorithm; pub use crate::crypto::Curve; pub use crate::crypto::HashAlgorithm; pub use crate::crypto::PublicKeyAlgorithm; pub use crate::crypto::SymmetricAlgorithm; /// The OpenPGP compression algorithms as defined in [Section 9.4 of RFC 9580]. /// /// [Section 9.4 of RFC 9580]: https://www.rfc-editor.org/rfc/rfc9580.html#section-9.4 /// /// # Examples /// /// Use `CompressionAlgorithm` to set the preferred compressions algorithms on /// a signature: /// /// ```rust /// use sequoia_openpgp as openpgp; /// use openpgp::packet::signature::SignatureBuilder; /// use openpgp::types::{HashAlgorithm, CompressionAlgorithm, SignatureType}; /// /// # fn main() -> openpgp::Result<()> { /// let mut builder = SignatureBuilder::new(SignatureType::DirectKey) /// .set_hash_algo(HashAlgorithm::SHA512) /// .set_preferred_compression_algorithms(vec![ /// CompressionAlgorithm::Zlib, /// CompressionAlgorithm::BZip2, /// ])?; /// # Ok(()) } #[non_exhaustive] #[derive(Clone, Copy, Hash, PartialEq, Eq, Debug, PartialOrd, Ord)] pub enum CompressionAlgorithm { /// Null compression. Uncompressed, /// DEFLATE Compressed Data. /// /// See [RFC 1951] for details. [Section 9.4 of RFC 9580] /// recommends that this algorithm should be implemented. /// /// [RFC 1951]: https://tools.ietf.org/html/rfc1951 /// [Section 9.4 of RFC 9580]: https://www.rfc-editor.org/rfc/rfc9580.html#section-9.4 Zip, /// ZLIB Compressed Data. /// /// See [RFC 1950] for details. /// /// [RFC 1950]: https://tools.ietf.org/html/rfc1950 Zlib, /// bzip2 BZip2, /// Private compression algorithm identifier. Private(u8), /// Unknown compression algorithm identifier. Unknown(u8), } assert_send_and_sync!(CompressionAlgorithm); const COMPRESSION_ALGORITHM_VARIANTS: [CompressionAlgorithm; 4] = [ CompressionAlgorithm::Uncompressed, CompressionAlgorithm::Zip, CompressionAlgorithm::Zlib, CompressionAlgorithm::BZip2, ]; impl Default for CompressionAlgorithm { fn default() -> Self { use self::CompressionAlgorithm::*; #[cfg(feature = "compression-deflate")] { Zip } #[cfg(all(feature = "compression-bzip2", not(feature = "compression-deflate")))] { BZip2 } #[cfg(all(not(feature = "compression-bzip2"), not(feature = "compression-deflate")))] { Uncompressed } } } impl CompressionAlgorithm { /// Returns whether this algorithm is supported. /// /// # Examples /// /// ```rust /// use sequoia_openpgp as openpgp; /// use openpgp::types::CompressionAlgorithm; /// /// assert!(CompressionAlgorithm::Uncompressed.is_supported()); /// /// assert!(!CompressionAlgorithm::Private(101).is_supported()); /// ``` pub fn is_supported(&self) -> bool { use self::CompressionAlgorithm::*; match &self { Uncompressed => true, #[cfg(feature = "compression-deflate")] Zip | Zlib => true, #[cfg(feature = "compression-bzip2")] BZip2 => true, _ => false, } } /// Returns an iterator over all valid variants. /// /// Returns an iterator over all known variants. This does not /// include the [`CompressionAlgorithm::Private`], or /// [`CompressionAlgorithm::Unknown`] variants. pub fn variants() -> impl Iterator<Item=Self> { COMPRESSION_ALGORITHM_VARIANTS.iter().cloned() } } impl From<u8> for CompressionAlgorithm { fn from(u: u8) -> Self { match u { 0 => CompressionAlgorithm::Uncompressed, 1 => CompressionAlgorithm::Zip, 2 => CompressionAlgorithm::Zlib, 3 => CompressionAlgorithm::BZip2, 100..=110 => CompressionAlgorithm::Private(u), u => CompressionAlgorithm::Unknown(u), } } } impl From<CompressionAlgorithm> for u8 { fn from(c: CompressionAlgorithm) -> u8 { match c { CompressionAlgorithm::Uncompressed => 0, CompressionAlgorithm::Zip => 1, CompressionAlgorithm::Zlib => 2, CompressionAlgorithm::BZip2 => 3, CompressionAlgorithm::Private(u) => u, CompressionAlgorithm::Unknown(u) => u, } } } impl fmt::Display for CompressionAlgorithm { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match *self { CompressionAlgorithm::Uncompressed => f.write_str("Uncompressed"), CompressionAlgorithm::Zip => f.write_str("ZIP"), CompressionAlgorithm::Zlib => f.write_str("ZLIB"), CompressionAlgorithm::BZip2 => f.write_str("BZip2"), CompressionAlgorithm::Private(u) => f.write_fmt(format_args!("Private/Experimental compression algorithm {}", u)), CompressionAlgorithm::Unknown(u) => f.write_fmt(format_args!("Unknown compression algorithm {}", u)), } } } #[cfg(test)] impl Arbitrary for CompressionAlgorithm { fn arbitrary(g: &mut Gen) -> Self { u8::arbitrary(g).into() } } /// Signature type as defined in [Section 5.2.1 of RFC 9580]. /// /// [Section 5.2.1 of RFC 9580]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.1 /// /// # Examples /// /// Use `SignatureType` to create a timestamp signature: /// /// ```rust /// use sequoia_openpgp as openpgp; /// use std::time::SystemTime; /// use openpgp::packet::signature::SignatureBuilder; /// use openpgp::types::SignatureType; /// /// # fn main() -> openpgp::Result<()> { /// let mut builder = SignatureBuilder::new(SignatureType::Timestamp) /// .set_signature_creation_time(SystemTime::now())?; /// # Ok(()) } /// ``` #[non_exhaustive] #[derive(Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord, Debug)] pub enum SignatureType { /// Signature over a binary document. Binary, /// Signature over a canonical text document. Text, /// Standalone signature. Standalone, /// Generic certification of a User ID and Public-Key packet. GenericCertification, /// Persona certification of a User ID and Public-Key packet. PersonaCertification, /// Casual certification of a User ID and Public-Key packet. CasualCertification, /// Positive certification of a User ID and Public-Key packet. PositiveCertification, /// Certification Approval Key Signature (experimental). /// /// Allows the certificate owner to attest to third party /// certifications. See [Certification Approval Key Signature] for /// details. /// /// [Certification Approval Key Signature]: https://www.ietf.org/archive/id/draft-dkg-openpgp-1pa3pc-02.html#name-certification-approval-key- CertificationApproval, /// Subkey Binding Signature SubkeyBinding, /// Primary Key Binding Signature PrimaryKeyBinding, /// Signature directly on a key DirectKey, /// Key revocation signature KeyRevocation, /// Subkey revocation signature SubkeyRevocation, /// Certification revocation signature CertificationRevocation, /// Timestamp signature. Timestamp, /// Third-Party Confirmation signature. Confirmation, /// Catchall. Unknown(u8), } assert_send_and_sync!(SignatureType); const SIGNATURE_TYPE_VARIANTS: [SignatureType; 16] = [ SignatureType::Binary, SignatureType::Text, SignatureType::Standalone, SignatureType::GenericCertification, SignatureType::PersonaCertification, SignatureType::CasualCertification, SignatureType::PositiveCertification, SignatureType::CertificationApproval, SignatureType::SubkeyBinding, SignatureType::PrimaryKeyBinding, SignatureType::DirectKey, SignatureType::KeyRevocation, SignatureType::SubkeyRevocation, SignatureType::CertificationRevocation, SignatureType::Timestamp, SignatureType::Confirmation, ]; impl From<u8> for SignatureType { fn from(u: u8) -> Self { match u { 0x00 => SignatureType::Binary, 0x01 => SignatureType::Text, 0x02 => SignatureType::Standalone, 0x10 => SignatureType::GenericCertification, 0x11 => SignatureType::PersonaCertification, 0x12 => SignatureType::CasualCertification, 0x13 => SignatureType::PositiveCertification, 0x16 => SignatureType::CertificationApproval, 0x18 => SignatureType::SubkeyBinding, 0x19 => SignatureType::PrimaryKeyBinding, 0x1f => SignatureType::DirectKey, 0x20 => SignatureType::KeyRevocation, 0x28 => SignatureType::SubkeyRevocation, 0x30 => SignatureType::CertificationRevocation, 0x40 => SignatureType::Timestamp, 0x50 => SignatureType::Confirmation, _ => SignatureType::Unknown(u), } } } impl From<SignatureType> for u8 { fn from(t: SignatureType) -> Self { match t { SignatureType::Binary => 0x00, SignatureType::Text => 0x01, SignatureType::Standalone => 0x02, SignatureType::GenericCertification => 0x10, SignatureType::PersonaCertification => 0x11, SignatureType::CasualCertification => 0x12, SignatureType::PositiveCertification => 0x13, SignatureType::CertificationApproval => 0x16, SignatureType::SubkeyBinding => 0x18, SignatureType::PrimaryKeyBinding => 0x19, SignatureType::DirectKey => 0x1f, SignatureType::KeyRevocation => 0x20, SignatureType::SubkeyRevocation => 0x28, SignatureType::CertificationRevocation => 0x30, SignatureType::Timestamp => 0x40, SignatureType::Confirmation => 0x50, SignatureType::Unknown(u) => u, } } } impl fmt::Display for SignatureType { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match *self { SignatureType::Binary => f.write_str("Binary"), SignatureType::Text => f.write_str("Text"), SignatureType::Standalone => f.write_str("Standalone"), SignatureType::GenericCertification => f.write_str("GenericCertification"), SignatureType::PersonaCertification => f.write_str("PersonaCertification"), SignatureType::CasualCertification => f.write_str("CasualCertification"), SignatureType::PositiveCertification => f.write_str("PositiveCertification"), SignatureType::CertificationApproval => f.write_str("CertificationApproval"), SignatureType::SubkeyBinding => f.write_str("SubkeyBinding"), SignatureType::PrimaryKeyBinding => f.write_str("PrimaryKeyBinding"), SignatureType::DirectKey => f.write_str("DirectKey"), SignatureType::KeyRevocation => f.write_str("KeyRevocation"), SignatureType::SubkeyRevocation => f.write_str("SubkeyRevocation"), SignatureType::CertificationRevocation => f.write_str("CertificationRevocation"), SignatureType::Timestamp => f.write_str("Timestamp"), SignatureType::Confirmation => f.write_str("Confirmation"), SignatureType::Unknown(u) => f.write_fmt(format_args!("Unknown signature type 0x{:x}", u)), } } } #[cfg(test)] impl Arbitrary for SignatureType { fn arbitrary(g: &mut Gen) -> Self { u8::arbitrary(g).into() } } impl SignatureType { /// Returns an iterator over all valid variants. /// /// Returns an iterator over all known variants. This does not /// include the [`SignatureType::Unknown`] variants. pub fn variants() -> impl Iterator<Item=Self> { SIGNATURE_TYPE_VARIANTS.iter().cloned() } } /// Describes the reason for a revocation. /// /// See the description of revocation subpackets [Section 5.2.3.31 of RFC 9580]. /// /// [Section 5.2.3.31 of RFC 9580]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.31 /// /// # Examples /// /// ```rust /// use sequoia_openpgp as openpgp; /// use openpgp::cert::prelude::*; /// use openpgp::policy::StandardPolicy; /// use openpgp::types::{RevocationStatus, ReasonForRevocation, SignatureType}; /// /// # fn main() -> openpgp::Result<()> { /// let p = &StandardPolicy::new(); /// /// // A certificate with a User ID. /// let (cert, _) = CertBuilder::new() /// .add_userid("Alice <alice@example.org>") /// .generate()?; /// /// let mut keypair = cert.primary_key().key().clone() /// .parts_into_secret()?.into_keypair()?; /// let ca = cert.userids().nth(0).unwrap(); /// /// // Generate the revocation for the first and only UserID. /// let revocation = /// UserIDRevocationBuilder::new() /// .set_reason_for_revocation( /// ReasonForRevocation::UIDRetired, /// b"Left example.org.")? /// .build(&mut keypair, &cert, ca.userid(), None)?; /// assert_eq!(revocation.typ(), SignatureType::CertificationRevocation); /// /// // Now merge the revocation signature into the Cert. /// let cert = cert.insert_packets(revocation.clone())?; /// /// // Check that it is revoked. /// let ca = cert.0.userids().nth(0).unwrap(); /// let status = ca.with_policy(p, None)?.revocation_status(); /// if let RevocationStatus::Revoked(revs) = status { /// assert_eq!(revs.len(), 1); /// let rev = revs[0]; /// /// assert_eq!(rev.typ(), SignatureType::CertificationRevocation); /// assert_eq!(rev.reason_for_revocation(), /// Some((ReasonForRevocation::UIDRetired, /// "Left example.org.".as_bytes()))); /// // User ID has been revoked. /// } /// # else { unreachable!(); } /// # Ok(()) } /// ``` #[non_exhaustive] #[derive(Clone, Copy, Hash, PartialEq, Eq, Debug, PartialOrd, Ord)] pub enum ReasonForRevocation { /// No reason specified (key revocations or cert revocations) Unspecified, /// Key is superseded (key revocations) KeySuperseded, /// Key material has been compromised (key revocations) KeyCompromised, /// Key is retired and no longer used (key revocations) KeyRetired, /// User ID information is no longer valid (cert revocations) UIDRetired, /// Private reason identifier. Private(u8), /// Unknown reason identifier. Unknown(u8), } assert_send_and_sync!(ReasonForRevocation); const REASON_FOR_REVOCATION_VARIANTS: [ReasonForRevocation; 5] = [ ReasonForRevocation::Unspecified, ReasonForRevocation::KeySuperseded, ReasonForRevocation::KeyCompromised, ReasonForRevocation::KeyRetired, ReasonForRevocation::UIDRetired, ]; impl From<u8> for ReasonForRevocation { fn from(u: u8) -> Self { use self::ReasonForRevocation::*; match u { 0 => Unspecified, 1 => KeySuperseded, 2 => KeyCompromised, 3 => KeyRetired, 32 => UIDRetired, 100..=110 => Private(u), u => Unknown(u), } } } impl From<ReasonForRevocation> for u8 { fn from(r: ReasonForRevocation) -> u8 { use self::ReasonForRevocation::*; match r { Unspecified => 0, KeySuperseded => 1, KeyCompromised => 2, KeyRetired => 3, UIDRetired => 32, Private(u) => u, Unknown(u) => u, } } } impl fmt::Display for ReasonForRevocation { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { use self::ReasonForRevocation::*; match *self { Unspecified => f.write_str("No reason specified"), KeySuperseded => f.write_str("Key is superseded"), KeyCompromised => f.write_str("Key material has been compromised"), KeyRetired => f.write_str("Key is retired and no longer used"), UIDRetired => f.write_str("User ID information is no longer valid"), Private(u) => f.write_fmt(format_args!( "Private/Experimental revocation reason {}", u)), Unknown(u) => f.write_fmt(format_args!( "Unknown revocation reason {}", u)), } } } #[cfg(test)] impl Arbitrary for ReasonForRevocation { fn arbitrary(g: &mut Gen) -> Self { u8::arbitrary(g).into() } } /// Describes whether a `ReasonForRevocation` should be consider hard /// or soft. /// /// A hard revocation is a revocation that indicates that the key was /// somehow compromised, and the provenance of *all* artifacts should /// be called into question. /// /// A soft revocation is a revocation that indicates that the key /// should be considered invalid *after* the revocation signature's /// creation time. `KeySuperseded`, `KeyRetired`, and `UIDRetired` /// are considered soft revocations. /// /// # Examples /// /// A certificate is considered to be revoked when a hard revocation is present /// even if it is not live at the specified time. /// /// Here, a certificate is generated at `t0` and then revoked later at `t2`. /// At `t1` (`t0` < `t1` < `t2`) depending on the revocation type it will be /// either considered revoked (hard revocation) or not revoked (soft revocation): /// /// ```rust /// # use sequoia_openpgp as openpgp; /// use std::time::{Duration, SystemTime}; /// use openpgp::cert::prelude::*; /// use openpgp::types::{RevocationStatus, ReasonForRevocation}; /// use openpgp::policy::StandardPolicy; /// /// # fn main() -> openpgp::Result<()> { /// let p = &StandardPolicy::new(); /// /// let t0 = SystemTime::now(); /// let (cert, _) = /// CertBuilder::general_purpose(Some("alice@example.org")) /// .set_creation_time(t0) /// .generate()?; /// /// let t2 = t0 + Duration::from_secs(3600); /// /// let mut signer = cert.primary_key().key().clone() /// .parts_into_secret()?.into_keypair()?; /// /// // Create a hard revocation (KeyCompromised): /// let sig = CertRevocationBuilder::new() /// .set_reason_for_revocation(ReasonForRevocation::KeyCompromised, /// b"The butler did it :/")? /// .set_signature_creation_time(t2)? /// .build(&mut signer, &cert, None)?; /// /// let t1 = t0 + Duration::from_secs(1200); /// let cert1 = cert.clone().insert_packets(sig.clone())?.0; /// assert_eq!(cert1.revocation_status(p, Some(t1)), /// RevocationStatus::Revoked(vec![&sig.into()])); /// /// // Create a soft revocation (KeySuperseded): /// let sig = CertRevocationBuilder::new() /// .set_reason_for_revocation(ReasonForRevocation::KeySuperseded, /// b"Migrated to key XYZ")? /// .set_signature_creation_time(t2)? /// .build(&mut signer, &cert, None)?; /// /// let t1 = t0 + Duration::from_secs(1200); /// let cert2 = cert.clone().insert_packets(sig.clone())?.0; /// assert_eq!(cert2.revocation_status(p, Some(t1)), /// RevocationStatus::NotAsFarAsWeKnow); /// # Ok(()) /// # } /// ``` #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum RevocationType { /// A hard revocation. /// /// Artifacts stemming from the revoked object should not be /// trusted. Hard, /// A soft revocation. /// /// Artifacts stemming from the revoked object *after* the /// revocation time should not be trusted. Earlier objects should /// be considered okay. /// /// Only `KeySuperseded`, `KeyRetired`, and `UIDRetired` are /// considered soft revocations. All other reasons for /// revocations including unknown reasons are considered hard /// revocations. Soft, } assert_send_and_sync!(RevocationType); impl ReasonForRevocation { /// Returns the revocation's `RevocationType`. /// /// # Examples /// /// ```rust /// use sequoia_openpgp as openpgp; /// use openpgp::types::{ReasonForRevocation, RevocationType}; /// /// assert_eq!(ReasonForRevocation::KeyCompromised.revocation_type(), RevocationType::Hard); /// assert_eq!(ReasonForRevocation::Private(101).revocation_type(), RevocationType::Hard); /// /// assert_eq!(ReasonForRevocation::KeyRetired.revocation_type(), RevocationType::Soft); /// ``` pub fn revocation_type(&self) -> RevocationType { match self { ReasonForRevocation::Unspecified => RevocationType::Hard, ReasonForRevocation::KeySuperseded => RevocationType::Soft, ReasonForRevocation::KeyCompromised => RevocationType::Hard, ReasonForRevocation::KeyRetired => RevocationType::Soft, ReasonForRevocation::UIDRetired => RevocationType::Soft, ReasonForRevocation::Private(_) => RevocationType::Hard, ReasonForRevocation::Unknown(_) => RevocationType::Hard, } } /// Returns an iterator over all valid variants. /// /// Returns an iterator over all known variants. This does not /// include the [`ReasonForRevocation::Private`] or /// [`ReasonForRevocation::Unknown`] variants. pub fn variants() -> impl Iterator<Item=Self> { REASON_FOR_REVOCATION_VARIANTS.iter().cloned() } } /// Describes the format of the body of a literal data packet. /// /// See the description of literal data packets [Section 5.9 of RFC 9580]. /// /// [Section 5.9 of RFC 9580]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.9 /// /// # Examples /// /// Construct a new [`Message`] containing one text literal packet: /// /// [`Message`]: crate::Message /// /// ```rust /// use sequoia_openpgp as openpgp; /// use std::convert::TryFrom; /// use openpgp::packet::prelude::*; /// use openpgp::types::DataFormat; /// use openpgp::message::Message; /// /// let mut packets = Vec::new(); /// let mut lit = Literal::new(DataFormat::Unicode); /// lit.set_body(b"data".to_vec()); /// packets.push(lit.into()); /// /// let message = Message::try_from(packets); /// assert!(message.is_ok(), "{:?}", message); /// ``` #[non_exhaustive] #[derive(Clone, Copy, Hash, PartialEq, Eq, Debug, PartialOrd, Ord)] pub enum DataFormat { /// Binary data. /// /// This is a hint that the content is probably binary data. Binary, /// Text data, probably valid UTF-8. /// /// This is a hint that the content is probably UTF-8 encoded. Unicode, /// Text data. /// /// This is a hint that the content is probably text; the encoding /// is not specified. #[deprecated(note = "Use Dataformat::Unicode instead.")] Text, /// Unknown format specifier. Unknown(u8), } assert_send_and_sync!(DataFormat); #[allow(deprecated)] const DATA_FORMAT_VARIANTS: [DataFormat; 3] = [ DataFormat::Binary, DataFormat::Text, DataFormat::Unicode, ]; impl Default for DataFormat { fn default() -> Self { DataFormat::Binary } } impl From<u8> for DataFormat { fn from(u: u8) -> Self { #[allow(deprecated)] match u { b'b' => DataFormat::Binary, b'u' => DataFormat::Unicode, b't' => DataFormat::Text, _ => DataFormat::Unknown(u), } } } impl From<DataFormat> for u8 { fn from(f: DataFormat) -> u8 { use self::DataFormat::*; match f { Binary => b'b', Unicode => b'u', #[allow(deprecated)] Text => b't', Unknown(c) => c, } } } impl fmt::Display for DataFormat { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { use self::DataFormat::*; match *self { Binary => f.write_str("Binary data"), #[allow(deprecated)] Text => f.write_str("Text data"), Unicode => f.write_str("Text data (UTF-8)"), Unknown(c) => f.write_fmt(format_args!( "Unknown data format identifier {:?}", c)), } } } #[cfg(test)] impl Arbitrary for DataFormat { fn arbitrary(g: &mut Gen) -> Self { u8::arbitrary(g).into() } } impl DataFormat { /// Returns an iterator over all valid variants. /// /// Returns an iterator over all known variants. This does not /// include the [`DataFormat::Unknown`] variants. pub fn variants() -> impl Iterator<Item=Self> { DATA_FORMAT_VARIANTS.iter().cloned() } } /// The revocation status. /// /// # Examples /// /// Generates a new certificate then checks if the User ID is revoked or not under /// the given policy using [`ValidUserIDAmalgamation`]: /// /// [`ValidUserIDAmalgamation`]: crate::cert::amalgamation::ValidUserIDAmalgamation /// /// ```rust /// use sequoia_openpgp as openpgp; /// use openpgp::cert::prelude::*; /// use openpgp::policy::StandardPolicy; /// use openpgp::types::RevocationStatus; /// /// # fn main() -> openpgp::Result<()> { /// let p = &StandardPolicy::new(); /// /// let (cert, _) = /// CertBuilder::general_purpose(Some("alice@example.org")) /// .generate()?; /// let cert = cert.with_policy(p, None)?; /// let ua = cert.userids().nth(0).expect("User IDs"); /// /// match ua.revocation_status() { /// RevocationStatus::Revoked(revs) => { /// // The certificate holder revoked the User ID. /// # unreachable!(); /// } /// RevocationStatus::CouldBe(revs) => { /// // There are third-party revocations. You still need /// // to check that they are valid (this is necessary, /// // because without the Certificates are not normally /// // available to Sequoia). /// # unreachable!(); /// } /// RevocationStatus::NotAsFarAsWeKnow => { /// // We have no evidence that the User ID is revoked. /// } /// } /// # Ok(()) /// # } /// ``` #[derive(Debug, Clone, PartialEq, Eq)] pub enum RevocationStatus<'a> { /// The key is definitely revoked. /// /// The relevant self-revocations are returned. Revoked(Vec<&'a crate::packet::Signature>), /// There is a revocation certificate from a possible designated /// revoker. CouldBe(Vec<&'a crate::packet::Signature>), /// The key does not appear to be revoked. /// /// An attacker could still have performed a DoS, which prevents /// us from seeing the revocation certificate. NotAsFarAsWeKnow, } assert_send_and_sync!(RevocationStatus<'_>); #[cfg(test)] mod tests { use super::*; quickcheck! { fn comp_roundtrip(comp: CompressionAlgorithm) -> bool { let val: u8 = comp.into(); comp == CompressionAlgorithm::from(val) } } quickcheck! { fn comp_display(comp: CompressionAlgorithm) -> bool { let s = format!("{}", comp); !s.is_empty() } } quickcheck! { fn comp_parse(comp: CompressionAlgorithm) -> bool { match comp { CompressionAlgorithm::Unknown(u) => u > 110 || (u > 3 && u < 100), CompressionAlgorithm::Private(u) => (100..=110).contains(&u), _ => true } } } quickcheck! { fn signature_type_roundtrip(t: SignatureType) -> bool { let val: u8 = t.into(); t == SignatureType::from(val) } } quickcheck! { fn signature_type_display(t: SignatureType) -> bool { let s = format!("{}", t); !s.is_empty() } } quickcheck! { fn rfr_roundtrip(rfr: ReasonForRevocation) -> bool { let val: u8 = rfr.into(); rfr == ReasonForRevocation::from(val) } } quickcheck! { fn rfr_display(rfr: ReasonForRevocation) -> bool { let s = format!("{}", rfr); !s.is_empty() } } quickcheck! { fn rfr_parse(rfr: ReasonForRevocation) -> bool { match rfr { ReasonForRevocation::Unknown(u) => (u > 3 && u < 32) || (u > 32 && u < 100) || u > 110, ReasonForRevocation::Private(u) => (100..=110).contains(&u), _ => true } } } quickcheck! { fn df_roundtrip(df: DataFormat) -> bool { let val: u8 = df.into(); df == DataFormat::from(val) } } quickcheck! { fn df_display(df: DataFormat) -> bool { let s = format!("{}", df); !s.is_empty() } } quickcheck! { fn df_parse(df: DataFormat) -> bool { match df { DataFormat::Unknown(u) => u != b'b' && u != b't' && u != b'u', _ => true } } } #[test] fn compression_algorithms_variants() { use std::collections::HashSet; use std::iter::FromIterator; // COMPRESSION_ALGORITHM_VARIANTS is a list. Derive it in a // different way to double check that nothing is missing. let derived_variants = (0..=u8::MAX) .map(CompressionAlgorithm::from) .filter(|t| { match t { CompressionAlgorithm::Private(_) => false, CompressionAlgorithm::Unknown(_) => false, _ => true, } }) .collect::<HashSet<_>>(); let known_variants = HashSet::from_iter(COMPRESSION_ALGORITHM_VARIANTS .iter().cloned()); let missing = known_variants .symmetric_difference(&derived_variants) .collect::<Vec<_>>(); assert!(missing.is_empty(), "{:?}", missing); } #[test] fn signature_types_variants() { use std::collections::HashSet; use std::iter::FromIterator; // SIGNATURE_TYPE_VARIANTS is a list. Derive it in a // different way to double check that nothing is missing. let derived_variants = (0..=u8::MAX) .map(SignatureType::from) .filter(|t| { match t { SignatureType::Unknown(_) => false, _ => true, } }) .collect::<HashSet<_>>(); let known_variants = HashSet::from_iter(SIGNATURE_TYPE_VARIANTS .iter().cloned()); let missing = known_variants .symmetric_difference(&derived_variants) .collect::<Vec<_>>(); assert!(missing.is_empty(), "{:?}", missing); } #[test] fn reason_for_revocation_variants() { use std::collections::HashSet; use std::iter::FromIterator; // REASON_FOR_REVOCATION_VARIANTS is a list. Derive it in a // different way to double check that nothing is missing. let derived_variants = (0..=u8::MAX) .map(ReasonForRevocation::from) .filter(|t| { match t { ReasonForRevocation::Private(_) => false, ReasonForRevocation::Unknown(_) => false, _ => true, } }) .collect::<HashSet<_>>(); let known_variants = HashSet::from_iter(REASON_FOR_REVOCATION_VARIANTS .iter().cloned()); let missing = known_variants .symmetric_difference(&derived_variants) .collect::<Vec<_>>(); assert!(missing.is_empty(), "{:?}", missing); } #[test] fn data_format_variants() { use std::collections::HashSet; use std::iter::FromIterator; // DATA_FORMAT_VARIANTS is a list. Derive it in a different // way to double check that nothing is missing. let derived_variants = (0..=u8::MAX) .map(DataFormat::from) .filter(|t| { match t { DataFormat::Unknown(_) => false, _ => true, } }) .collect::<HashSet<_>>(); let known_variants = HashSet::from_iter(DATA_FORMAT_VARIANTS .iter().cloned()); let missing = known_variants .symmetric_difference(&derived_variants) .collect::<Vec<_>>(); assert!(missing.is_empty(), "{:?}", missing); } } ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/src/types/revocation_key.rs���������������������������������������������������0000644�0000000�0000000�00000011564�10461020230�0020270�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#[cfg(test)] use quickcheck::{Arbitrary, Gen}; use crate::{ cert::prelude::*, Error, Fingerprint, Result, types::{ PublicKeyAlgorithm, }, }; /// Designates a key as a valid third-party revoker. /// /// This is described in [Section 5.2.3.23 of RFC 9580]. /// /// [Section 5.2.3.23 of RFC 9580]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.23 /// /// Revocation keys can be retrieved using [`ValidAmalgamation::revocation_keys`] /// and set using [`CertBuilder::set_revocation_keys`]. /// /// [`ValidAmalgamation::revocation_keys`]: crate::cert::amalgamation::ValidAmalgamation::revocation_keys() /// [`CertBuilder::set_revocation_keys`]: crate::cert::CertBuilder::set_revocation_keys() /// /// # Examples /// /// ``` /// use sequoia_openpgp as openpgp; /// # use openpgp::Result; /// use openpgp::cert::prelude::*; /// use openpgp::policy::StandardPolicy; /// use openpgp::types::RevocationKey; /// /// # fn main() -> Result<()> { /// let p = &StandardPolicy::new(); /// /// let (alice, _) = /// CertBuilder::general_purpose(Some("alice@example.org")) /// .generate()?; /// /// // Make Alice a designated revoker for Bob. /// let (bob, _) = /// CertBuilder::general_purpose(Some("bob@example.org")) /// .set_revocation_keys(vec![(&alice).into()]) /// .generate()?; /// /// // Make sure Alice is listed as a designated revoker for Bob. /// assert_eq!(bob.with_policy(p, None)?.revocation_keys() /// .collect::<Vec<&RevocationKey>>(), /// vec![&(&alice).into()]); /// # Ok(()) } /// ``` #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct RevocationKey { /// The public key algorithm used by the authorized key. pk_algo: PublicKeyAlgorithm, /// Fingerprint of authorized key. fp: Fingerprint, /// Indicates that the relation between revoker and revokee is /// of a sensitive nature. sensitive: bool, /// Other bits are for future expansion to other kinds of /// authorizations. unknown: u8, } assert_send_and_sync!(RevocationKey); impl From<&Cert> for RevocationKey { fn from(cert: &Cert) -> Self { RevocationKey::new(cert.primary_key().key().pk_algo(), cert.fingerprint(), false) } } impl RevocationKey { /// Creates a new instance. pub fn new(pk_algo: PublicKeyAlgorithm, fp: Fingerprint, sensitive: bool) -> Self { RevocationKey { pk_algo, fp, sensitive, unknown: 0, } } /// Creates a new instance from the raw `class` parameter. pub fn from_bits(pk_algo: PublicKeyAlgorithm, fp: Fingerprint, class: u8) -> Result<Self> { if class & REVOCATION_KEY_FLAG_MUST_BE_SET == 0 { return Err(Error::InvalidArgument( "Most significant bit of class must be set".into()).into()); } let sensitive = class & REVOCATION_KEY_FLAG_SENSITIVE > 0; let unknown = class & REVOCATION_KEY_MASK_UNKNOWN; Ok(RevocationKey { pk_algo, fp, sensitive, unknown, }) } /// Returns the `class` octet, the sum of all flags. pub fn class(&self) -> u8 { REVOCATION_KEY_FLAG_MUST_BE_SET | if self.sensitive() { REVOCATION_KEY_FLAG_SENSITIVE } else { 0 } | self.unknown } /// Returns the revoker's identity. pub fn revoker(&self) -> (PublicKeyAlgorithm, &Fingerprint) { (self.pk_algo, &self.fp) } /// Sets the revoker's identity. pub fn set_revoker(&mut self, pk_algo: PublicKeyAlgorithm, fp: Fingerprint) -> (PublicKeyAlgorithm, Fingerprint) { let pk_algo = std::mem::replace(&mut self.pk_algo, pk_algo); let fp = std::mem::replace(&mut self.fp, fp); (pk_algo, fp) } /// Returns whether the relation between revoker and /// revokee is of a sensitive nature. pub fn sensitive(&self) -> bool { self.sensitive } /// Sets whether the relation between revoker and revokee /// is of a sensitive nature. pub fn set_sensitive(mut self, v: bool) -> Self { self.sensitive = v; self } } /// This bit must be set. const REVOCATION_KEY_FLAG_MUST_BE_SET: u8 = 0x80; /// Relation is of a sensitive nature. const REVOCATION_KEY_FLAG_SENSITIVE: u8 = 0x40; /// Mask covering the unknown bits. const REVOCATION_KEY_MASK_UNKNOWN: u8 = ! (REVOCATION_KEY_FLAG_MUST_BE_SET | REVOCATION_KEY_FLAG_SENSITIVE); #[cfg(test)] impl Arbitrary for RevocationKey { fn arbitrary(g: &mut Gen) -> Self { RevocationKey { pk_algo: Arbitrary::arbitrary(g), fp: Arbitrary::arbitrary(g), sensitive: Arbitrary::arbitrary(g), unknown: u8::arbitrary(g) & REVOCATION_KEY_MASK_UNKNOWN, } } } ��������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/src/types/server_preferences.rs�����������������������������������������������0000644�0000000�0000000�00000022752�10461020230�0021137�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������use std::fmt; #[cfg(test)] use quickcheck::{Arbitrary, Gen}; use crate::types::Bitfield; /// Describes preferences regarding key servers. /// /// Key server preferences are specified in [Section 5.2.3.25 of RFC 9580]. /// /// [Section 5.2.3.25 of RFC 9580]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.25 /// /// The keyserver preferences are set by the user's OpenPGP /// implementation to communicate them to any peers. /// /// # A note on equality /// /// `PartialEq` compares the serialized form of the two key server /// preference sets. If you prefer to compare two key server /// preference sets for semantic equality, you should use /// [`KeyServerPreferences::normalized_eq`]. The difference between /// semantic equality and serialized equality is that semantic /// equality ignores differences in the amount of padding. /// /// [`KeyServerPreferences::normalized_eq`]: KeyServerPreferences::normalized_eq() /// /// # Examples /// /// ``` /// use sequoia_openpgp as openpgp; /// # use openpgp::Result; /// use openpgp::cert::prelude::*; /// use openpgp::policy::StandardPolicy; /// /// # fn main() -> Result<()> { /// let p = &StandardPolicy::new(); /// /// let (cert, _) = /// CertBuilder::general_purpose(Some("alice@example.org")) /// .generate()?; /// /// match cert.with_policy(p, None)?.primary_userid()?.key_server_preferences() { /// Some(preferences) => { /// println!("Certificate holder's keyserver preferences:"); /// assert!(preferences.no_modify()); /// # unreachable!(); /// } /// None => { /// println!("Certificate Holder did not specify any key server preferences."); /// } /// } /// # Ok(()) } /// ``` #[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct KeyServerPreferences(Bitfield); assert_send_and_sync!(KeyServerPreferences); impl fmt::Debug for KeyServerPreferences { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let mut need_comma = false; if self.no_modify() { f.write_str("no modify")?; need_comma = true; } for i in self.0.iter_set() { match i { KEYSERVER_PREFERENCE_NO_MODIFY => (), i => { if need_comma { f.write_str(", ")?; } write!(f, "#{}", i)?; need_comma = true; }, } } // Mention any padding, as equality is sensitive to this. if let Some(padding) = self.0.padding_bytes() { if need_comma { f.write_str(", ")?; } write!(f, "+padding({} bytes)", padding)?; } Ok(()) } } impl KeyServerPreferences { /// Creates a new instance from `bits`. pub fn new<B: AsRef<[u8]>>(bits: B) -> Self { KeyServerPreferences(bits.as_ref().to_vec().into()) } /// Returns an empty key server preference set. pub fn empty() -> Self { Self::new(&[]) } /// Returns a reference to the underlying [`Bitfield`]. pub fn as_bitfield(&self) -> &Bitfield { &self.0 } /// Returns a mutable reference to the underlying [`Bitfield`]. pub fn as_bitfield_mut(&mut self) -> &mut Bitfield { &mut self.0 } /// Compares two key server preference sets for semantic equality. /// /// `KeyServerPreferences` implementation of `PartialEq` compares /// two key server preference sets for serialized equality. That /// is, the `PartialEq` implementation considers two key server /// preference sets to *not* be equal if they have different /// amounts of padding. This comparison function ignores padding. /// /// # Examples /// /// ``` /// use sequoia_openpgp as openpgp; /// use openpgp::types::KeyServerPreferences; /// /// # fn main() -> openpgp::Result<()> { /// let a = KeyServerPreferences::new(&[0x1]); /// let b = KeyServerPreferences::new(&[0x1, 0x0]); /// /// assert!(a != b); /// assert!(a.normalized_eq(&b)); /// # Ok(()) } /// ``` pub fn normalized_eq(&self, other: &Self) -> bool { self.0.normalized_eq(&other.0) } /// Returns whether the specified keyserver preference flag is set. /// /// # Examples /// /// ``` /// use sequoia_openpgp as openpgp; /// use openpgp::types::KeyServerPreferences; /// /// # fn main() -> openpgp::Result<()> { /// // Keyserver Preferences flags 0 and 2. /// let ksp = KeyServerPreferences::new(&[0x5]); /// /// assert!(ksp.get(0)); /// assert!(! ksp.get(1)); /// assert!(ksp.get(2)); /// assert!(! ksp.get(3)); /// assert!(! ksp.get(8)); /// assert!(! ksp.get(80)); /// # assert!(! ksp.no_modify()); /// # Ok(()) } /// ``` pub fn get(&self, bit: usize) -> bool { self.0.get(bit) } /// Sets the specified keyserver preference flag. /// /// This also clears any padding (trailing NUL bytes). /// /// # Examples /// /// ``` /// use sequoia_openpgp as openpgp; /// use openpgp::types::KeyServerPreferences; /// /// # fn main() -> openpgp::Result<()> { /// let ksp = KeyServerPreferences::empty().set(0).set(2); /// /// assert!(ksp.get(0)); /// assert!(! ksp.get(1)); /// assert!(ksp.get(2)); /// assert!(! ksp.get(3)); /// # assert!(! ksp.no_modify()); /// # Ok(()) } /// ``` pub fn set(mut self, bit: usize) -> Self { self.0.set(bit); self.0.canonicalize(); self } /// Clears the specified keyserver preference flag. /// /// This also clears any padding (trailing NUL bytes). /// /// # Examples /// /// ``` /// use sequoia_openpgp as openpgp; /// use openpgp::types::KeyServerPreferences; /// /// # fn main() -> openpgp::Result<()> { /// let ksp = KeyServerPreferences::empty().set(0).set(2).clear(2); /// /// assert!(ksp.get(0)); /// assert!(! ksp.get(1)); /// assert!(! ksp.get(2)); /// assert!(! ksp.get(3)); /// # assert!(! ksp.no_modify()); /// # Ok(()) } /// ``` pub fn clear(mut self, bit: usize) -> Self { self.0.clear(bit); self.0.canonicalize(); self } /// Returns whether the certificate's owner requests that the /// certificate is not modified. /// /// If this flag is set, the certificate's owner requests that the /// certificate should only be changed by the owner and the key /// server's operator. /// /// # Examples /// /// ``` /// use sequoia_openpgp as openpgp; /// use openpgp::types::KeyServerPreferences; /// /// # fn main() -> openpgp::Result<()> { /// let ksp = KeyServerPreferences::empty(); /// assert!(! ksp.no_modify()); /// # Ok(()) } /// ``` pub fn no_modify(&self) -> bool { self.get(KEYSERVER_PREFERENCE_NO_MODIFY) } /// Requests that the certificate is not modified. /// /// See [`KeyServerPreferences::no_modify`]. /// /// [`KeyServerPreferences::no_modify`]: KeyServerPreferences::no_modify() /// /// # Examples /// /// ``` /// use sequoia_openpgp as openpgp; /// use openpgp::types::KeyServerPreferences; /// /// # fn main() -> openpgp::Result<()> { /// let ksp = KeyServerPreferences::empty().set_no_modify(); /// assert!(ksp.no_modify()); /// # Ok(()) } /// ``` pub fn set_no_modify(self) -> Self { self.set(KEYSERVER_PREFERENCE_NO_MODIFY) } /// Clears the request that the certificate is not modified. /// /// See [`KeyServerPreferences::no_modify`]. /// /// [`KeyServerPreferences::no_modify`]: KeyServerPreferences::no_modify() /// /// # Examples /// /// ``` /// use sequoia_openpgp as openpgp; /// use openpgp::types::KeyServerPreferences; /// /// # fn main() -> openpgp::Result<()> { /// let ksp = KeyServerPreferences::new(&[0x80][..]); /// assert!(ksp.no_modify()); /// let ksp = ksp.clear_no_modify(); /// assert!(! ksp.no_modify()); /// # Ok(()) } /// ``` pub fn clear_no_modify(self) -> Self { self.clear(KEYSERVER_PREFERENCE_NO_MODIFY) } } /// The key holder requests that this key only be modified or updated /// by the key holder or an administrator of the key server. const KEYSERVER_PREFERENCE_NO_MODIFY: usize = 7; #[cfg(test)] impl Arbitrary for KeyServerPreferences { fn arbitrary(g: &mut Gen) -> Self { Self::new(Vec::arbitrary(g)) } } #[cfg(test)] mod tests { use super::*; #[test] fn basics() -> crate::Result<()> { let p = KeyServerPreferences::empty(); assert_eq!(p.no_modify(), false); let p = KeyServerPreferences::new(&[]); assert_eq!(p.no_modify(), false); let p = KeyServerPreferences::new(&[0xff]); assert_eq!(p.no_modify(), true); Ok(()) } quickcheck! { fn roundtrip(val: KeyServerPreferences) -> bool { let mut q_bytes = val.as_bitfield().as_bytes().to_vec(); let q = KeyServerPreferences::new(&q_bytes); assert_eq!(val, q); assert!(val.normalized_eq(&q)); // Add some padding to q. Make sure they are still equal. q_bytes.push(0); let q = KeyServerPreferences::new(&q_bytes); assert!(val != q); assert!(val.normalized_eq(&q)); q_bytes.push(0); let q = KeyServerPreferences::new(&q_bytes); assert!(val != q); assert!(val.normalized_eq(&q)); true } } } ����������������������sequoia-openpgp-2.0.0/src/types/timestamp.rs��������������������������������������������������������0000644�0000000�0000000�00000067615�10461020230�0017262�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������use std::cmp; use std::convert::{TryFrom, TryInto}; use std::fmt; use std::time::{SystemTime, Duration as SystemDuration, UNIX_EPOCH}; use std::u32; #[cfg(test)] use quickcheck::{Arbitrary, Gen}; use crate::{ Error, Result, }; /// A timestamp representable by OpenPGP. /// /// OpenPGP timestamps are represented as `u32` containing the number of seconds /// elapsed since midnight, 1 January 1970 UTC ([Section 3.5 of RFC 9580]). /// /// They cannot express dates further than 7th February 2106 or earlier than /// the [UNIX epoch]. Unlike Unix's `time_t`, OpenPGP's timestamp is unsigned so /// it rolls over in 2106, not 2038. /// /// # Examples /// /// Signature creation time is internally stored as a `Timestamp`: /// /// Note that this example retrieves raw packet value. /// Use [`SubpacketAreas::signature_creation_time`] to get the signature creation time. /// /// [`SubpacketAreas::signature_creation_time`]: crate::packet::signature::subpacket::SubpacketAreas::signature_creation_time() /// /// ``` /// use sequoia_openpgp as openpgp; /// # use openpgp::Result; /// use std::convert::From; /// use std::time::SystemTime; /// use openpgp::cert::prelude::*; /// use openpgp::policy::StandardPolicy; /// use openpgp::packet::signature::subpacket::{SubpacketTag, SubpacketValue}; /// /// # fn main() -> Result<()> { /// let (cert, _) = /// CertBuilder::general_purpose(Some("alice@example.org")) /// .generate()?; /// /// let subkey = cert.keys().subkeys().next().unwrap(); /// let packets = subkey.bundle().self_signatures().next().unwrap().hashed_area(); /// /// match packets.subpacket(SubpacketTag::SignatureCreationTime).unwrap().value() { /// SubpacketValue::SignatureCreationTime(ts) => assert!(u32::from(*ts) > 0), /// v => panic!("Unexpected subpacket: {:?}", v), /// } /// /// let p = &StandardPolicy::new(); /// let now = SystemTime::now(); /// assert!(subkey.binding_signature(p, now)?.signature_creation_time().is_some()); /// # Ok(()) } /// ``` /// /// [Section 3.5 of RFC 9580]: https://www.rfc-editor.org/rfc/rfc9580.html#section-3.5 /// [UNIX epoch]: https://en.wikipedia.org/wiki/Unix_time /// [`Timestamp::round_down`]: crate::types::Timestamp::round_down() #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct Timestamp(u32); assert_send_and_sync!(Timestamp); impl From<Timestamp> for u32 { fn from(t: Timestamp) -> Self { t.0 } } impl From<u32> for Timestamp { fn from(t: u32) -> Self { Timestamp(t) } } impl TryFrom<SystemTime> for Timestamp { type Error = anyhow::Error; fn try_from(t: SystemTime) -> Result<Self> { match t.duration_since(std::time::UNIX_EPOCH) { Ok(d) if d.as_secs() <= std::u32::MAX as u64 => Ok(Timestamp(d.as_secs() as u32)), _ => Err(Error::InvalidArgument( format!("time {:?} is not representable in OpenPGP", t)) .into()), } } } /// SystemTime's underlying datatype may be only `i32`, e.g. on 32bit Unix. /// As OpenPGP's timestamp datatype is `u32`, there are timestamps (`i32::MAX + 1` /// to `u32::MAX`) which are not representable on such systems. /// /// In this case, the result is clamped to `i32::MAX`. impl From<Timestamp> for SystemTime { fn from(t: Timestamp) -> Self { UNIX_EPOCH.checked_add(SystemDuration::new(t.0 as u64, 0)) .unwrap_or_else(|| UNIX_EPOCH + SystemDuration::new(i32::MAX as u64, 0)) } } impl From<Timestamp> for Option<SystemTime> { fn from(t: Timestamp) -> Self { Some(t.into()) } } impl fmt::Display for Timestamp { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{}", crate::fmt::time(&SystemTime::from(*self))) } } impl fmt::Debug for Timestamp { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{}", self.0) } } impl Timestamp { /// Returns the current time. pub fn now() -> Timestamp { crate::now().try_into() .expect("representable for the next hundred years") } /// Adds a duration to this timestamp. /// /// Returns `None` if the resulting timestamp is not /// representable. pub fn checked_add(&self, d: Duration) -> Option<Timestamp> { self.0.checked_add(d.0).map(Self) } /// Subtracts a duration from this timestamp. /// /// Returns `None` if the resulting timestamp is not /// representable. pub fn checked_sub(&self, d: Duration) -> Option<Timestamp> { self.0.checked_sub(d.0).map(Self) } /// Rounds down to the given level of precision. /// /// This can be used to reduce the metadata leak resulting from /// time stamps. For example, a group of people attending a key /// signing event could be identified by comparing the time stamps /// of resulting certifications. By rounding the creation time of /// these signatures down, all of them, and others, fall into the /// same bucket. /// /// The given level `p` determines the resulting resolution of /// `2^p` seconds. The default is `21`, which results in a /// resolution of 24 days, or roughly a month. `p` must be lower /// than 32. /// /// The lower limit `floor` represents the earliest time the timestamp will be /// rounded down to. /// /// See also [`Duration::round_up`](Duration::round_up()). /// /// # Important note /// /// If we create a signature, it is important that the signature's /// creation time does not predate the signing keys creation time, /// or otherwise violate the key's validity constraints. /// This can be achieved by using the `floor` parameter. /// /// To ensure validity, use this function to round the time down, /// using the latest known relevant timestamp as a floor. /// Then, lookup all keys and other objects like userids using this /// timestamp, and on success create the signature: /// /// ```rust /// # use sequoia_openpgp::{*, packet::prelude::*, types::*, cert::*}; /// use sequoia_openpgp::policy::StandardPolicy; /// /// # fn main() -> Result<()> { /// let policy = &StandardPolicy::new(); /// /// // Let's fix a time. /// let now = Timestamp::from(1583436160); /// /// let cert_creation_alice = now.checked_sub(Duration::weeks(2)?).unwrap(); /// let cert_creation_bob = now.checked_sub(Duration::weeks(1)?).unwrap(); /// /// // Generate a Cert for Alice. /// let (alice, _) = CertBuilder::new() /// .set_creation_time(cert_creation_alice) /// .set_primary_key_flags(KeyFlags::empty().set_certification()) /// .add_userid("alice@example.org") /// .generate()?; /// /// // Generate a Cert for Bob. /// let (bob, _) = CertBuilder::new() /// .set_creation_time(cert_creation_bob) /// .set_primary_key_flags(KeyFlags::empty().set_certification()) /// .add_userid("bob@example.org") /// .generate()?; /// /// let sign_with_p = |p| -> Result<Signature> { /// // Round `now` down, then use `t` for all lookups. /// // Use the creation time of Bob's Cert as lower bound for rounding. /// let t: std::time::SystemTime = now.round_down(p, cert_creation_bob)?.into(); /// /// // First, get the certification key. /// let mut keypair = /// alice.keys().with_policy(policy, t).secret().for_certification() /// .nth(0).ok_or_else(|| anyhow::anyhow!("no valid key at"))? /// .key().clone().into_keypair()?; /// /// // Then, lookup the binding between `bob@example.org` and /// // `bob` at `t`. /// let ca = bob.userids().with_policy(policy, t) /// .filter(|ca| ca.userid().value() == b"bob@example.org") /// .nth(0).ok_or_else(|| anyhow::anyhow!("no valid userid"))?; /// /// // Finally, Alice certifies the binding between /// // `bob@example.org` and `bob` at `t`. /// ca.userid().certify(&mut keypair, &bob, /// SignatureType::PositiveCertification, None, t) /// }; /// /// assert!(sign_with_p(21).is_ok()); /// assert!(sign_with_p(22).is_ok()); // Rounded to Bob's cert's creation time. /// assert!(sign_with_p(32).is_err()); // Invalid precision /// # Ok(()) } /// ``` pub fn round_down<P, F>(&self, precision: P, floor: F) -> Result<Timestamp> where P: Into<Option<u8>>, F: Into<Option<SystemTime>> { let p = precision.into().unwrap_or(21) as u32; if p < 32 { let rounded = Self(self.0 & !((1 << p) - 1)); match floor.into() { Some(floor) => { Ok(cmp::max(rounded, floor.try_into()?)) } None => { Ok(rounded) } } } else { Err(Error::InvalidArgument( format!("Invalid precision {}", p)).into()) } } } #[cfg(test)] impl Arbitrary for Timestamp { fn arbitrary(g: &mut Gen) -> Self { Timestamp(u32::arbitrary(g)) } } /// A duration representable by OpenPGP. /// /// # Examples /// /// ``` /// use sequoia_openpgp as openpgp; /// # use openpgp::Result; /// use openpgp::cert::prelude::*; /// use openpgp::policy::StandardPolicy; /// use openpgp::packet::signature::subpacket::{SubpacketTag, SubpacketValue}; /// use openpgp::types::{Timestamp, Duration}; /// /// # fn main() -> Result<()> { /// let p = &StandardPolicy::new(); /// /// let now = Timestamp::now(); /// let validity_period = Duration::days(365)?; /// /// let (cert,_) = CertBuilder::new() /// .set_creation_time(now) /// .set_validity_period(validity_period) /// .generate()?; /// /// let vc = cert.with_policy(p, now)?; /// assert!(vc.alive().is_ok()); /// # Ok(()) } /// ``` #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct Duration(u32); assert_send_and_sync!(Duration); impl From<Duration> for u32 { fn from(d: Duration) -> Self { d.0 } } impl From<u32> for Duration { fn from(d: u32) -> Self { Duration(d) } } impl TryFrom<SystemDuration> for Duration { type Error = anyhow::Error; fn try_from(d: SystemDuration) -> Result<Self> { if d.as_secs() <= std::u32::MAX as u64 { Ok(Duration(d.as_secs() as u32)) } else { Err(Error::InvalidArgument( format!("Duration exceeds u32: {:?}", d)) .into()) } } } impl From<Duration> for SystemDuration { fn from(d: Duration) -> Self { SystemDuration::new(d.0 as u64, 0) } } impl From<Duration> for Option<SystemDuration> { fn from(d: Duration) -> Self { Some(d.into()) } } impl fmt::Debug for Duration { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{:?}", SystemDuration::from(*self)) } } impl Duration { /// Returns a `Duration` with the given number of seconds. pub const fn seconds(n: u32) -> Duration { Self(n) } /// Returns a `Duration` with the given number of minutes, if /// representable. pub fn minutes(n: u32) -> Result<Duration> { match 60u32.checked_mul(n) { Some(val) => Ok(Self::seconds(val)), None => { Err(Error::InvalidArgument(format!( "Not representable: {} minutes in seconds exceeds u32", n )).into()) } } } /// Returns a `Duration` with the given number of hours, if /// representable. pub fn hours(n: u32) -> Result<Duration> { match 60u32.checked_mul(n) { Some(val) => Self::minutes(val), None => { Err(Error::InvalidArgument(format!( "Not representable: {} hours in seconds exceeds u32", n )).into()) } } } /// Returns a `Duration` with the given number of days, if /// representable. pub fn days(n: u32) -> Result<Duration> { match 24u32.checked_mul(n) { Some(val) => Self::hours(val), None => { Err(Error::InvalidArgument(format!( "Not representable: {} days in seconds exceeds u32", n )).into()) } } } /// Returns a `Duration` with the given number of weeks, if /// representable. pub fn weeks(n: u32) -> Result<Duration> { match 7u32.checked_mul(n) { Some(val) => Self::days(val), None => { Err(Error::InvalidArgument(format!( "Not representable: {} weeks in seconds exceeds u32", n )).into()) } } } /// Returns a `Duration` with the given number of years, if /// representable. /// /// This function assumes that there are 365.2425 [days in a /// year], the average number of days in a year in the Gregorian /// calendar. /// /// [days in a year]: https://en.wikipedia.org/wiki/Year pub fn years(n: u32) -> Result<Duration> { let s = (365.2425 * n as f64).trunc(); if s > u32::MAX as f64 { Err(Error::InvalidArgument( format!("Not representable: {} years in seconds exceeds u32", n)) .into()) } else { Ok((s as u32).into()) } } /// Returns the duration as seconds. pub fn as_secs(self) -> u64 { self.0 as u64 } /// Rounds up to the given level of precision. /// /// If [`Timestamp::round_down`] is used to round the creation /// timestamp of a key or signature down, then this function may /// be used to round the corresponding expiration time up. This /// ensures validity during the originally intended lifetime, /// while avoiding the metadata leak associated with preserving /// the originally intended expiration time. /// /// [`Timestamp::round_down`]: Timestamp::round_down() /// /// The given level `p` determines the resulting resolution of /// `2^p` seconds. The default is `21`, which results in a /// resolution of 24 days, or roughly a month. `p` must be lower /// than 32. /// /// The upper limit `ceil` represents the maximum time to round up to. pub fn round_up<P, C>(&self, precision: P, ceil: C) -> Result<Duration> where P: Into<Option<u8>>, C: Into<Option<SystemDuration>> { let p = precision.into().unwrap_or(21) as u32; if p < 32 { if let Some(sum) = self.0.checked_add((1 << p) - 1) { let rounded = Self(sum & !((1 << p) - 1)); match ceil.into() { Some(ceil) => { Ok(cmp::min(rounded, ceil.try_into()?)) }, None => Ok(rounded) } } else { Ok(Self(std::u32::MAX)) } } else { Err(Error::InvalidArgument( format!("Invalid precision {}", p)).into()) } } } #[allow(unused)] impl Timestamp { pub(crate) const UNIX_EPOCH : Timestamp = Timestamp(0); pub(crate) const MAX : Timestamp = Timestamp(u32::MAX); pub(crate) const Y1970 : Timestamp = Timestamp(0); // for y in $(seq 1970 2106); do echo " pub(crate) const Y${y}M2 : Timestamp = Timestamp($(date -u --date="Feb. 1, $y" '+%s'));"; done pub(crate) const Y1970M2 : Timestamp = Timestamp(2678400); pub(crate) const Y1971M2 : Timestamp = Timestamp(34214400); pub(crate) const Y1972M2 : Timestamp = Timestamp(65750400); pub(crate) const Y1973M2 : Timestamp = Timestamp(97372800); pub(crate) const Y1974M2 : Timestamp = Timestamp(128908800); pub(crate) const Y1975M2 : Timestamp = Timestamp(160444800); pub(crate) const Y1976M2 : Timestamp = Timestamp(191980800); pub(crate) const Y1977M2 : Timestamp = Timestamp(223603200); pub(crate) const Y1978M2 : Timestamp = Timestamp(255139200); pub(crate) const Y1979M2 : Timestamp = Timestamp(286675200); pub(crate) const Y1980M2 : Timestamp = Timestamp(318211200); pub(crate) const Y1981M2 : Timestamp = Timestamp(349833600); pub(crate) const Y1982M2 : Timestamp = Timestamp(381369600); pub(crate) const Y1983M2 : Timestamp = Timestamp(412905600); pub(crate) const Y1984M2 : Timestamp = Timestamp(444441600); pub(crate) const Y1985M2 : Timestamp = Timestamp(476064000); pub(crate) const Y1986M2 : Timestamp = Timestamp(507600000); pub(crate) const Y1987M2 : Timestamp = Timestamp(539136000); pub(crate) const Y1988M2 : Timestamp = Timestamp(570672000); pub(crate) const Y1989M2 : Timestamp = Timestamp(602294400); pub(crate) const Y1990M2 : Timestamp = Timestamp(633830400); pub(crate) const Y1991M2 : Timestamp = Timestamp(665366400); pub(crate) const Y1992M2 : Timestamp = Timestamp(696902400); pub(crate) const Y1993M2 : Timestamp = Timestamp(728524800); pub(crate) const Y1994M2 : Timestamp = Timestamp(760060800); pub(crate) const Y1995M2 : Timestamp = Timestamp(791596800); pub(crate) const Y1996M2 : Timestamp = Timestamp(823132800); pub(crate) const Y1997M2 : Timestamp = Timestamp(854755200); pub(crate) const Y1998M2 : Timestamp = Timestamp(886291200); pub(crate) const Y1999M2 : Timestamp = Timestamp(917827200); pub(crate) const Y2000M2 : Timestamp = Timestamp(949363200); pub(crate) const Y2001M2 : Timestamp = Timestamp(980985600); pub(crate) const Y2002M2 : Timestamp = Timestamp(1012521600); pub(crate) const Y2003M2 : Timestamp = Timestamp(1044057600); pub(crate) const Y2004M2 : Timestamp = Timestamp(1075593600); pub(crate) const Y2005M2 : Timestamp = Timestamp(1107216000); pub(crate) const Y2006M2 : Timestamp = Timestamp(1138752000); pub(crate) const Y2007M2 : Timestamp = Timestamp(1170288000); pub(crate) const Y2008M2 : Timestamp = Timestamp(1201824000); pub(crate) const Y2009M2 : Timestamp = Timestamp(1233446400); pub(crate) const Y2010M2 : Timestamp = Timestamp(1264982400); pub(crate) const Y2011M2 : Timestamp = Timestamp(1296518400); pub(crate) const Y2012M2 : Timestamp = Timestamp(1328054400); pub(crate) const Y2013M2 : Timestamp = Timestamp(1359676800); pub(crate) const Y2014M2 : Timestamp = Timestamp(1391212800); pub(crate) const Y2015M2 : Timestamp = Timestamp(1422748800); pub(crate) const Y2016M2 : Timestamp = Timestamp(1454284800); pub(crate) const Y2017M2 : Timestamp = Timestamp(1485907200); pub(crate) const Y2018M2 : Timestamp = Timestamp(1517443200); pub(crate) const Y2019M2 : Timestamp = Timestamp(1548979200); pub(crate) const Y2020M2 : Timestamp = Timestamp(1580515200); pub(crate) const Y2021M2 : Timestamp = Timestamp(1612137600); pub(crate) const Y2022M2 : Timestamp = Timestamp(1643673600); pub(crate) const Y2023M2 : Timestamp = Timestamp(1675209600); pub(crate) const Y2024M2 : Timestamp = Timestamp(1706745600); pub(crate) const Y2025M2 : Timestamp = Timestamp(1738368000); pub(crate) const Y2026M2 : Timestamp = Timestamp(1769904000); pub(crate) const Y2027M2 : Timestamp = Timestamp(1801440000); pub(crate) const Y2028M2 : Timestamp = Timestamp(1832976000); pub(crate) const Y2029M2 : Timestamp = Timestamp(1864598400); pub(crate) const Y2030M2 : Timestamp = Timestamp(1896134400); pub(crate) const Y2031M2 : Timestamp = Timestamp(1927670400); pub(crate) const Y2032M2 : Timestamp = Timestamp(1959206400); pub(crate) const Y2033M2 : Timestamp = Timestamp(1990828800); pub(crate) const Y2034M2 : Timestamp = Timestamp(2022364800); pub(crate) const Y2035M2 : Timestamp = Timestamp(2053900800); pub(crate) const Y2036M2 : Timestamp = Timestamp(2085436800); pub(crate) const Y2037M2 : Timestamp = Timestamp(2117059200); pub(crate) const Y2038M2 : Timestamp = Timestamp(2148595200); pub(crate) const Y2039M2 : Timestamp = Timestamp(2180131200); pub(crate) const Y2040M2 : Timestamp = Timestamp(2211667200); pub(crate) const Y2041M2 : Timestamp = Timestamp(2243289600); pub(crate) const Y2042M2 : Timestamp = Timestamp(2274825600); pub(crate) const Y2043M2 : Timestamp = Timestamp(2306361600); pub(crate) const Y2044M2 : Timestamp = Timestamp(2337897600); pub(crate) const Y2045M2 : Timestamp = Timestamp(2369520000); pub(crate) const Y2046M2 : Timestamp = Timestamp(2401056000); pub(crate) const Y2047M2 : Timestamp = Timestamp(2432592000); pub(crate) const Y2048M2 : Timestamp = Timestamp(2464128000); pub(crate) const Y2049M2 : Timestamp = Timestamp(2495750400); pub(crate) const Y2050M2 : Timestamp = Timestamp(2527286400); pub(crate) const Y2051M2 : Timestamp = Timestamp(2558822400); pub(crate) const Y2052M2 : Timestamp = Timestamp(2590358400); pub(crate) const Y2053M2 : Timestamp = Timestamp(2621980800); pub(crate) const Y2054M2 : Timestamp = Timestamp(2653516800); pub(crate) const Y2055M2 : Timestamp = Timestamp(2685052800); pub(crate) const Y2056M2 : Timestamp = Timestamp(2716588800); pub(crate) const Y2057M2 : Timestamp = Timestamp(2748211200); pub(crate) const Y2058M2 : Timestamp = Timestamp(2779747200); pub(crate) const Y2059M2 : Timestamp = Timestamp(2811283200); pub(crate) const Y2060M2 : Timestamp = Timestamp(2842819200); pub(crate) const Y2061M2 : Timestamp = Timestamp(2874441600); pub(crate) const Y2062M2 : Timestamp = Timestamp(2905977600); pub(crate) const Y2063M2 : Timestamp = Timestamp(2937513600); pub(crate) const Y2064M2 : Timestamp = Timestamp(2969049600); pub(crate) const Y2065M2 : Timestamp = Timestamp(3000672000); pub(crate) const Y2066M2 : Timestamp = Timestamp(3032208000); pub(crate) const Y2067M2 : Timestamp = Timestamp(3063744000); pub(crate) const Y2068M2 : Timestamp = Timestamp(3095280000); pub(crate) const Y2069M2 : Timestamp = Timestamp(3126902400); pub(crate) const Y2070M2 : Timestamp = Timestamp(3158438400); pub(crate) const Y2071M2 : Timestamp = Timestamp(3189974400); pub(crate) const Y2072M2 : Timestamp = Timestamp(3221510400); pub(crate) const Y2073M2 : Timestamp = Timestamp(3253132800); pub(crate) const Y2074M2 : Timestamp = Timestamp(3284668800); pub(crate) const Y2075M2 : Timestamp = Timestamp(3316204800); pub(crate) const Y2076M2 : Timestamp = Timestamp(3347740800); pub(crate) const Y2077M2 : Timestamp = Timestamp(3379363200); pub(crate) const Y2078M2 : Timestamp = Timestamp(3410899200); pub(crate) const Y2079M2 : Timestamp = Timestamp(3442435200); pub(crate) const Y2080M2 : Timestamp = Timestamp(3473971200); pub(crate) const Y2081M2 : Timestamp = Timestamp(3505593600); pub(crate) const Y2082M2 : Timestamp = Timestamp(3537129600); pub(crate) const Y2083M2 : Timestamp = Timestamp(3568665600); pub(crate) const Y2084M2 : Timestamp = Timestamp(3600201600); pub(crate) const Y2085M2 : Timestamp = Timestamp(3631824000); pub(crate) const Y2086M2 : Timestamp = Timestamp(3663360000); pub(crate) const Y2087M2 : Timestamp = Timestamp(3694896000); pub(crate) const Y2088M2 : Timestamp = Timestamp(3726432000); pub(crate) const Y2089M2 : Timestamp = Timestamp(3758054400); pub(crate) const Y2090M2 : Timestamp = Timestamp(3789590400); pub(crate) const Y2091M2 : Timestamp = Timestamp(3821126400); pub(crate) const Y2092M2 : Timestamp = Timestamp(3852662400); pub(crate) const Y2093M2 : Timestamp = Timestamp(3884284800); pub(crate) const Y2094M2 : Timestamp = Timestamp(3915820800); pub(crate) const Y2095M2 : Timestamp = Timestamp(3947356800); pub(crate) const Y2096M2 : Timestamp = Timestamp(3978892800); pub(crate) const Y2097M2 : Timestamp = Timestamp(4010515200); pub(crate) const Y2098M2 : Timestamp = Timestamp(4042051200); pub(crate) const Y2099M2 : Timestamp = Timestamp(4073587200); pub(crate) const Y2100M2 : Timestamp = Timestamp(4105123200); pub(crate) const Y2101M2 : Timestamp = Timestamp(4136659200); pub(crate) const Y2102M2 : Timestamp = Timestamp(4168195200); pub(crate) const Y2103M2 : Timestamp = Timestamp(4199731200); pub(crate) const Y2104M2 : Timestamp = Timestamp(4231267200); pub(crate) const Y2105M2 : Timestamp = Timestamp(4262889600); pub(crate) const Y2106M2 : Timestamp = Timestamp(4294425600); } #[cfg(test)] impl Arbitrary for Duration { fn arbitrary(g: &mut Gen) -> Self { Duration(u32::arbitrary(g)) } } /// Normalizes the given SystemTime to the resolution OpenPGP /// supports. pub(crate) fn normalize_systemtime(t: SystemTime) -> SystemTime { UNIX_EPOCH + SystemDuration::new( t.duration_since(UNIX_EPOCH).unwrap().as_secs(), 0) } #[cfg(test)] mod tests { use super::*; quickcheck! { fn timestamp_round_down(t: Timestamp) -> bool { let u = t.round_down(None, None).unwrap(); assert!(u <= t); assert_eq!(u32::from(u) & 0b1_1111_1111_1111_1111_1111, 0); assert!(u32::from(t) - u32::from(u) < 2_u32.pow(21)); true } } #[test] fn timestamp_round_down_floor() -> Result<()> { let t = Timestamp(1585753307); let floor = t.checked_sub(Duration::weeks(1).unwrap()).unwrap(); let u = t.round_down(21, floor).unwrap(); assert!(u < t); assert!(floor < u); assert_eq!(u32::from(u) & 0b1_1111_1111_1111_1111_1111, 0); let floor = t.checked_sub(Duration::days(1).unwrap()).unwrap(); let u = t.round_down(21, floor).unwrap(); assert_eq!(u, floor); Ok(()) } quickcheck! { fn duration_round_up(d: Duration) -> bool { let u = d.round_up(None, None).unwrap(); assert!(d <= u); assert!(u32::from(u) & 0b1_1111_1111_1111_1111_1111 == 0 || u32::from(u) == u32::MAX ); assert!(u32::from(u) - u32::from(d) < 2_u32.pow(21)); true } } #[test] fn duration_round_up_ceil() -> Result<()> { let d = Duration(123); let ceil = Duration(2_u32.pow(23)); let u = d.round_up(21, ceil)?; assert!(d < u); assert!(u < ceil); assert_eq!(u32::from(u) & 0b1_1111_1111_1111_1111_1111, 0); let ceil = Duration::days(1).unwrap(); let u = d.round_up(21, ceil)?; assert!(d < u); assert_eq!(u, ceil); Ok(()) } // #668 // Ensure that, on systems where the SystemTime can only represent values // up to i32::MAX (generally, 32-bit systems), Timestamps between // i32::MAX + 1 and u32::MAX are clamped down to i32::MAX, and values below // are not altered. #[test] fn system_time_32_bit() -> Result<()> { let is_system_time_too_small = UNIX_EPOCH .checked_add(SystemDuration::new(i32::MAX as u64 + 1, 0)) .is_none(); let t1 = Timestamp::from(i32::MAX as u32 - 1); let t2 = Timestamp::from(i32::MAX as u32); let t3 = Timestamp::from(i32::MAX as u32 + 1); let t4 = Timestamp::from(u32::MAX); if is_system_time_too_small { assert_eq!(SystemTime::from(t1), UNIX_EPOCH + SystemDuration::new(i32::MAX as u64 - 1, 0)); assert_eq!(SystemTime::from(t2), UNIX_EPOCH + SystemDuration::new(i32::MAX as u64, 0)); assert_eq!(SystemTime::from(t3), UNIX_EPOCH + SystemDuration::new(i32::MAX as u64, 0)); assert_eq!(SystemTime::from(t4), UNIX_EPOCH + SystemDuration::new(i32::MAX as u64, 0)); } else { assert_eq!(SystemTime::from(t1), UNIX_EPOCH + SystemDuration::new(i32::MAX as u64 - 1, 0)); assert_eq!(SystemTime::from(t2), UNIX_EPOCH + SystemDuration::new(i32::MAX as u64, 0)); assert_eq!(SystemTime::from(t3), UNIX_EPOCH + SystemDuration::new(i32::MAX as u64 + 1, 0)); assert_eq!(SystemTime::from(t4), UNIX_EPOCH + SystemDuration::new(u32::MAX as u64, 0)); } Ok(()) } } �������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/src/utils.rs������������������������������������������������������������������0000644�0000000�0000000�00000001003�10461020230�0015226�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������//! Utility functions that don't fit anywhere else. use std::convert::TryFrom; pub fn read_be_u64(b: &[u8]) -> u64 { let array = <[u8; 8]>::try_from(b).unwrap(); u64::from_be_bytes(array) } pub fn write_be_u64(b: &mut [u8], n: u64) { b.copy_from_slice(&n.to_be_bytes()); } #[cfg(test)] mod test { use super::*; quickcheck! { fn be_u64_roundtrip(n: u64) -> bool { let mut b = [0; 8]; write_be_u64(&mut b, n); n == read_be_u64(&b) } } } �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/armor/README�������������������������������������������������������0000644�0000000�0000000�00000001115�10461020230�0017010�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������This directory contains test vectors for the armor module. # literal-$n.* These files are generated using: (cd ../../../.. && cargo build -p sequoia-openpgp --example wrap-literal) for n in 0 1 2 3 47 48 49 50 51 do dd if=/dev/urandom bs=1 count=$n \ | ../../../../target/debug/examples/wrap-literal > literal-$n.asc sq dearmor -o literal-$n.bin literal-$n.asc grep -v - literal-$n.asc > literal-$n-no-header-with-chksum.asc grep -v - literal-$n.asc | head -n -1 > literal-$n-no-header.asc tr "\n" " " <literal-$n.asc > literal-$n-no-newlines.asc done ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/armor/literal-0-no-header-with-chksum.asc��������������������������0000644�0000000�0000000�00000000024�10461020230�0024510�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������ ywZiAAAAAAA= =JgGx ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/armor/literal-0-no-header.asc��������������������������������������0000644�0000000�0000000�00000000016�10461020230�0022250�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������ ywZiAAAAAAA= ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/armor/literal-0-no-newlines.asc������������������������������������0000644�0000000�0000000�00000000112�10461020230�0022641�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������-----BEGIN PGP MESSAGE----- ywZiAAAAAAA= =JgGx -----END PGP MESSAGE----- ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/armor/literal-0.asc������������������������������������������������0000644�0000000�0000000�00000000112�10461020230�0020405�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������-----BEGIN PGP MESSAGE----- ywZiAAAAAAA= =JgGx -----END PGP MESSAGE----- ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/armor/literal-0.bin������������������������������������������������0000644�0000000�0000000�00000000010�10461020230�0020404�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������b�����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/armor/literal-1-no-header-with-chksum.asc��������������������������0000644�0000000�0000000�00000000024�10461020230�0024511�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������ ywdiAAAAAAAG =+GtG ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/armor/literal-1-no-header.asc��������������������������������������0000644�0000000�0000000�00000000016�10461020230�0022251�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������ ywdiAAAAAAAG ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/armor/literal-1-no-newlines.asc������������������������������������0000644�0000000�0000000�00000000112�10461020230�0022642�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������-----BEGIN PGP MESSAGE----- ywdiAAAAAAAG =+GtG -----END PGP MESSAGE----- ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/armor/literal-1.asc������������������������������������������������0000644�0000000�0000000�00000000112�10461020230�0020406�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������-----BEGIN PGP MESSAGE----- ywdiAAAAAAAG =+GtG -----END PGP MESSAGE----- ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/armor/literal-1.bin������������������������������������������������0000644�0000000�0000000�00000000011�10461020230�0020406�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������b����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/armor/literal-2-no-header-with-chksum.asc��������������������������0000644�0000000�0000000�00000000030�10461020230�0024507�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������ ywhiAAAAAACbFA== =wkzG ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/armor/literal-2-no-header.asc��������������������������������������0000644�0000000�0000000�00000000022�10461020230�0022247�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������ ywhiAAAAAACbFA== ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/armor/literal-2-no-newlines.asc������������������������������������0000644�0000000�0000000�00000000116�10461020230�0022647�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������-----BEGIN PGP MESSAGE----- ywhiAAAAAACbFA== =wkzG -----END PGP MESSAGE----- ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/armor/literal-2.asc������������������������������������������������0000644�0000000�0000000�00000000116�10461020230�0020413�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������-----BEGIN PGP MESSAGE----- ywhiAAAAAACbFA== =wkzG -----END PGP MESSAGE----- ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/armor/literal-2.bin������������������������������������������������0000644�0000000�0000000�00000000012�10461020230�0020410�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������b���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/armor/literal-3-no-header-with-chksum.asc��������������������������0000644�0000000�0000000�00000000030�10461020230�0024510�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������ ywliAAAAAACC7XQ= =EBpN ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/armor/literal-3-no-header.asc��������������������������������������0000644�0000000�0000000�00000000022�10461020230�0022250�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������ ywliAAAAAACC7XQ= ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/armor/literal-3-no-newlines.asc������������������������������������0000644�0000000�0000000�00000000116�10461020230�0022650�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������-----BEGIN PGP MESSAGE----- ywliAAAAAACC7XQ= =EBpN -----END PGP MESSAGE----- ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/armor/literal-3.asc������������������������������������������������0000644�0000000�0000000�00000000116�10461020230�0020414�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������-----BEGIN PGP MESSAGE----- ywliAAAAAACC7XQ= =EBpN -----END PGP MESSAGE----- ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/armor/literal-3.bin������������������������������������������������0000644�0000000�0000000�00000000013�10461020230�0020412�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������ b�����t���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/armor/literal-47-no-header-with-chksum.asc�������������������������0000644�0000000�0000000�00000000125�10461020230�0024605�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������ yzViAAAAAAAu+RbYYqLiPxrbAE2b5G/9FLK/59YiRxzH2rVW60Uor4I9dPLill2d kS2Upbc15A== =jgHY �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/armor/literal-47-no-header.asc�������������������������������������0000644�0000000�0000000�00000000117�10461020230�0022345�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������ yzViAAAAAAAu+RbYYqLiPxrbAE2b5G/9FLK/59YiRxzH2rVW60Uor4I9dPLill2d kS2Upbc15A== �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/armor/literal-47-no-newlines.asc�����������������������������������0000644�0000000�0000000�00000000213�10461020230�0022736�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������-----BEGIN PGP MESSAGE----- yzViAAAAAAAu+RbYYqLiPxrbAE2b5G/9FLK/59YiRxzH2rVW60Uor4I9dPLill2d kS2Upbc15A== =jgHY -----END PGP MESSAGE----- �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/armor/literal-47.asc�����������������������������������������������0000644�0000000�0000000�00000000213�10461020230�0020502�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������-----BEGIN PGP MESSAGE----- yzViAAAAAAAu+RbYYqLiPxrbAE2b5G/9FLK/59YiRxzH2rVW60Uor4I9dPLill2d kS2Upbc15A== =jgHY -----END PGP MESSAGE----- �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/armor/literal-47.bin�����������������������������������������������0000644�0000000�0000000�00000000067�10461020230�0020513�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������5b�����.b?�Mo"GڵVE(=t]-5�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/armor/literal-48-no-header-with-chksum.asc�������������������������0000644�0000000�0000000�00000000125�10461020230�0024606�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������ yzZiAAAAAAAB+RqN/31/ZIrDdjPkr0f0tmBIyR6up5Dbhqbqbc0bcSNq6sTGByhT KBToa+1N4BM= =py+D �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/armor/literal-48-no-header.asc�������������������������������������0000644�0000000�0000000�00000000117�10461020230�0022346�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������ yzZiAAAAAAAB+RqN/31/ZIrDdjPkr0f0tmBIyR6up5Dbhqbqbc0bcSNq6sTGByhT KBToa+1N4BM= �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/armor/literal-48-no-newlines.asc�����������������������������������0000644�0000000�0000000�00000000213�10461020230�0022737�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������-----BEGIN PGP MESSAGE----- yzZiAAAAAAAB+RqN/31/ZIrDdjPkr0f0tmBIyR6up5Dbhqbqbc0bcSNq6sTGByhT KBToa+1N4BM= =py+D -----END PGP MESSAGE----- �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/armor/literal-48.asc�����������������������������������������������0000644�0000000�0000000�00000000213�10461020230�0020503�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������-----BEGIN PGP MESSAGE----- yzZiAAAAAAAB+RqN/31/ZIrDdjPkr0f0tmBIyR6up5Dbhqbqbc0bcSNq6sTGByhT KBToa+1N4BM= =py+D -----END PGP MESSAGE----- �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/armor/literal-48.bin�����������������������������������������������0000644�0000000�0000000�00000000070�10461020230�0020506�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������6b�����}dv3G`Hۆmq#j(S(kM������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/armor/literal-49-no-header-with-chksum.asc�������������������������0000644�0000000�0000000�00000000125�10461020230�0024607�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������ yzdiAAAAAAA13Wsm+LgnhFS3W4A1wbcLJVaznjMZjj5EXAQPG2c99zr6XiJ1KisS kJFNlVbsWkvL =PrpW �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/armor/literal-49-no-header.asc�������������������������������������0000644�0000000�0000000�00000000117�10461020230�0022347�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������ yzdiAAAAAAA13Wsm+LgnhFS3W4A1wbcLJVaznjMZjj5EXAQPG2c99zr6XiJ1KisS kJFNlVbsWkvL �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/armor/literal-49-no-newlines.asc�����������������������������������0000644�0000000�0000000�00000000213�10461020230�0022740�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������-----BEGIN PGP MESSAGE----- yzdiAAAAAAA13Wsm+LgnhFS3W4A1wbcLJVaznjMZjj5EXAQPG2c99zr6XiJ1KisS kJFNlVbsWkvL =PrpW -----END PGP MESSAGE----- �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/armor/literal-49.asc�����������������������������������������������0000644�0000000�0000000�00000000213�10461020230�0020504�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������-----BEGIN PGP MESSAGE----- yzdiAAAAAAA13Wsm+LgnhFS3W4A1wbcLJVaznjMZjj5EXAQPG2c99zr6XiJ1KisS kJFNlVbsWkvL =PrpW -----END PGP MESSAGE----- �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/armor/literal-49.bin�����������������������������������������������0000644�0000000�0000000�00000000071�10461020230�0020510�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������7b�����5k&'T[5 %V3>D\g=:^"u*+MVZK�����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/armor/literal-50-no-header-with-chksum.asc�������������������������0000644�0000000�0000000�00000000131�10461020230�0024574�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������ yzhiAAAAAAA0SHlL3w6uPWhW1tZyUCn6sfzWbCmepdvPGK8QoxGrMpjfAYDIf2RX 2DstnrFcBeR3ow== =SZ+v ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/armor/literal-50-no-header.asc�������������������������������������0000644�0000000�0000000�00000000123�10461020230�0022334�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������ yzhiAAAAAAA0SHlL3w6uPWhW1tZyUCn6sfzWbCmepdvPGK8QoxGrMpjfAYDIf2RX 2DstnrFcBeR3ow== ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/armor/literal-50-no-newlines.asc�����������������������������������0000644�0000000�0000000�00000000217�10461020230�0022734�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������-----BEGIN PGP MESSAGE----- yzhiAAAAAAA0SHlL3w6uPWhW1tZyUCn6sfzWbCmepdvPGK8QoxGrMpjfAYDIf2RX 2DstnrFcBeR3ow== =SZ+v -----END PGP MESSAGE----- ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/armor/literal-50.asc�����������������������������������������������0000644�0000000�0000000�00000000217�10461020230�0020500�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������-----BEGIN PGP MESSAGE----- yzhiAAAAAAA0SHlL3w6uPWhW1tZyUCn6sfzWbCmepdvPGK8QoxGrMpjfAYDIf2RX 2DstnrFcBeR3ow== =SZ+v -----END PGP MESSAGE----- ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/armor/literal-50.bin�����������������������������������������������0000644�0000000�0000000�00000000072�10461020230�0020501�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������8b�����4HyK=hVrP)l)2dW;-\w����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/armor/literal-51-no-header-with-chksum.asc�������������������������0000644�0000000�0000000�00000000131�10461020230�0024575�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������ yzliAAAAAADRPEBKydU3g0wATjZaEJUR4IEP/WsRKpBAQesOu8y8Q5o0lc/u0M9q ruzIG+/3/L6kTe0= =Mv+C ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/armor/literal-51-no-header.asc�������������������������������������0000644�0000000�0000000�00000000123�10461020230�0022335�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������ yzliAAAAAADRPEBKydU3g0wATjZaEJUR4IEP/WsRKpBAQesOu8y8Q5o0lc/u0M9q ruzIG+/3/L6kTe0= ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/armor/literal-51-no-newlines.asc�����������������������������������0000644�0000000�0000000�00000000217�10461020230�0022735�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������-----BEGIN PGP MESSAGE----- yzliAAAAAADRPEBKydU3g0wATjZaEJUR4IEP/WsRKpBAQesOu8y8Q5o0lc/u0M9q ruzIG+/3/L6kTe0= =Mv+C -----END PGP MESSAGE----- ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/armor/literal-51.asc�����������������������������������������������0000644�0000000�0000000�00000000217�10461020230�0020501�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������-----BEGIN PGP MESSAGE----- yzliAAAAAADRPEBKydU3g0wATjZaEJUR4IEP/WsRKpBAQesOu8y8Q5o0lc/u0M9q ruzIG+/3/L6kTe0= =Mv+C -----END PGP MESSAGE----- ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/armor/literal-51.bin�����������������������������������������������0000644�0000000�0000000�00000000073�10461020230�0020503�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������9b�����<@J7L�N6Zk*@A̼C4jM���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/armor/test-0.asc���������������������������������������������������0000644�0000000�0000000�00000000107�10461020230�0017734�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������-----BEGIN PGP ARMORED FILE----- =twTO -----END PGP ARMORED FILE----- ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/armor/test-0.bad-crc.asc�������������������������������������������0000644�0000000�0000000�00000000107�10461020230�0021226�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������-----BEGIN PGP ARMORED FILE----- =twTP -----END PGP ARMORED FILE----- ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/armor/test-0.bin���������������������������������������������������0000644�0000000�0000000�00000000000�10461020230�0017726�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/armor/test-1.asc���������������������������������������������������0000644�0000000�0000000�00000000114�10461020230�0017733�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������-----BEGIN PGP ARMORED FILE----- 3g== =NnH5 -----END PGP ARMORED FILE----- ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/armor/test-1.bin���������������������������������������������������0000644�0000000�0000000�00000000001�10461020230�0017730�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/armor/test-1.no-crc.asc��������������������������������������������0000644�0000000�0000000�00000000106�10461020230�0021114�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������-----BEGIN PGP ARMORED FILE----- 3g== -----END PGP ARMORED FILE----- ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/armor/test-2.asc���������������������������������������������������0000644�0000000�0000000�00000000114�10461020230�0017734�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������-----BEGIN PGP ARMORED FILE----- 5to= =zwFF -----END PGP ARMORED FILE----- ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/armor/test-2.bad-footer.asc����������������������������������������0000644�0000000�0000000�00000000117�10461020230�0021760�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������-----BEGIN PGP ARMORED FILE----- 5to= =zwFF -----END PGP ARMORED MESSAGE----- �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/armor/test-2.bin���������������������������������������������������0000644�0000000�0000000�00000000002�10461020230�0017732�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/armor/test-3.asc���������������������������������������������������0000644�0000000�0000000�00000000114�10461020230�0017735�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������-----BEGIN PGP ARMORED FILE----- NdWx =oSKZ -----END PGP ARMORED FILE----- ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/armor/test-3.bin���������������������������������������������������0000644�0000000�0000000�00000000003�10461020230�0017734�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������5ձ�����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/armor/test-3.no-dashes.asc�����������������������������������������0000644�0000000�0000000�00000000070�10461020230�0021616�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������BEGIN PGP ARMORED FILE NdWx =oSKZ END PGP ARMORED FILE ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/armor/test-3.unbalanced-dashes.asc���������������������������������0000644�0000000�0000000�00000000107�10461020230�0023277�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������-------BEGIN PGP ARMORED FILE- NdWx =oSKZ -END PGP ARMORED FILE------ ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/armor/test-3.unicode-dashes.asc������������������������������������0000644�0000000�0000000�00000000162�10461020230�0022632�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������֊־᐀᠆‐BEGIN PGP ARMORED FILE‑‒–—― NdWx =oSKZ ⸗⸚⸺⸻⹀END PGP ARMORED FILE〜〰゠﹘﹣ ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/armor/test-3.with-headers-quoted-a-lot.asc�������������������������0000644�0000000�0000000�00000000256�10461020230�0024640�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������] } >>> -----BEGIN PGP ARMORED FILE----- ] } >>> Comment: Some Header ] } >>> Comment: Another one ] } >>> ] } >>> NdWx ] } >>> =oSKZ ] } >>> -----END PGP ARMORED FILE----- ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/armor/test-3.with-headers-quoted-badly.asc�������������������������0000644�0000000�0000000�00000000212�10461020230�0024707�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������>> -----BEGIN PGP ARMORED FILE----- >> Comment: Some Header >> Comment: Another one >> > NdWx >> =oSKZ >> -----END PGP ARMORED FILE----- ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/armor/test-3.with-headers-quoted-stripped.asc����������������������0000644�0000000�0000000�00000000203�10461020230�0025446�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������> -----BEGIN PGP ARMORED FILE----- > Comment: Some Header > Comment: Another one > > NdWx > =oSKZ > -----END PGP ARMORED FILE----- ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/armor/test-3.with-headers-quoted.asc�������������������������������0000644�0000000�0000000�00000000204�10461020230�0023617�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������> -----BEGIN PGP ARMORED FILE----- > Comment: Some Header > Comment: Another one > > NdWx > =oSKZ > -----END PGP ARMORED FILE----- ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/armor/test-3.with-headers.asc��������������������������������������0000644�0000000�0000000�00000000166�10461020230�0022327�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������-----BEGIN PGP ARMORED FILE----- Comment: Some Header Comment: Another one NdWx =oSKZ -----END PGP ARMORED FILE----- ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/armor/test-47.asc��������������������������������������������������0000644�0000000�0000000�00000000210�10461020230�0020022�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������-----BEGIN PGP ARMORED FILE----- VLC0mzt0PHN3ltTJyAerW70C+qwT7xu3vWD0+hteywQZ5fNVhx6OuOBWZgvrB4Y= =F7up -----END PGP ARMORED FILE----- ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/armor/test-47.bin��������������������������������������������������0000644�0000000�0000000�00000000057�10461020230�0020035�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������T;t<sw[`^UVf ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/armor/test-48.asc��������������������������������������������������0000644�0000000�0000000�00000000210�10461020230�0020023�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������-----BEGIN PGP ARMORED FILE----- d2g7QIIm+Qlt2/MjQGqSP6rjq6Q3UV72TKfx1pv7weHfatKMpHlt2A8EIwz9K/dM =vZCz -----END PGP ARMORED FILE----- ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/armor/test-48.bin��������������������������������������������������0000644�0000000�0000000�00000000060�10461020230�0020030�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������wh;@& m#@j?㫤7Q^L֛jҌym# +L��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/armor/test-49.asc��������������������������������������������������0000644�0000000�0000000�00000000215�10461020230�0020031�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������-----BEGIN PGP ARMORED FILE----- p8wnk/jjQRUkq9wWI6ZUPx1iQ51HiItFN/aJSfOGcHH9s4freOWCm9iYadHE4sns Ow== =s29R -----END PGP ARMORED FILE----- �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/armor/test-49.bin��������������������������������������������������0000644�0000000�0000000�00000000061�10461020230�0020032�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������'A$#T?bCGE7Ipqx傛ؘi;�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/armor/test-50.asc��������������������������������������������������0000644�0000000�0000000�00000000215�10461020230�0020021�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������-----BEGIN PGP ARMORED FILE----- oTSs55iwdhccW8tp2VOlAnxgeopNJaHojjVDgs/jhTJYTm5sz9fiOW20VsPyZ3Kv JPw= =YITk -----END PGP ARMORED FILE----- �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/armor/test-50.bin��������������������������������������������������0000644�0000000�0000000�00000000062�10461020230�0020023�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������4瘰v[iS|`zM%5C2XNnl9mVgr$������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/armor/test-51.asc��������������������������������������������������0000644�0000000�0000000�00000000215�10461020230�0020022�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������-----BEGIN PGP ARMORED FILE----- PIDR4EXgeoMOwwTtbKTwBGMPVeTiQnJi9V0t686F1VhgNYrY14HoFLQEQt6jHLbN Eyon =yNnc -----END PGP ARMORED FILE----- �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/armor/test-51.bin��������������������������������������������������0000644�0000000�0000000�00000000063�10461020230�0020025�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������<EzlcUBrb]-΅X`5ׁBޣ*'�����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/contrib/gnupg/keys/alpha.pgp���������������������������������������0000644�0000000�0000000�00000002150�10461020230�0022340�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������6㎞�eC_L&$2,6tx .gʗh1V irlʛqԽI 9 ,򳥻^g3.$Dv碛ӛeU vX1'oIj� #UFK2z(tuVGȵzu^Rab;qeQQA B?/6/aspfE/ϕ gxߩ!Mr0YY*oMեrJc2xWG:.r6Uf]j^@oσEn4ßÓj#QrRq"賾 RcEԍa E̸/}4C[3͑A 5+~~.#!+ )W&5 d)iȯj)Alpha Test (demo key) <alpha@example.net>U�6㎞ � -r|hiw49x�.diezH�[�D?YLg0e.L.Alice (demo key)U�6㶫 � -r|hiw4'�LyEkj 9%Ώw^7�nqД=[R*<o?@7'Alfa Test (demo key) <alfa@example.net>U�6X � -r|hiw4�kVnUso f<�]%|gֵ/r 6�W#}}l;Ө;m> ;4YV$Qg׈ LﵙoLy~*ϋTru*+ID8 "-9,:17^g*({Ʉ #7�2ީo62,Tj/ r�Ri3sSS iCAYNYbVtA ؆HKi7ᮿ(UPp)?Od{'ȔR^fF�6� -r|hiw4 �p5ˍUҠ/oJwj{�sU"{k}*������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/contrib/gnupg/timestamp-signature-by-alice.asc���������������������0000644�0000000�0000000�00000001302�10461020230�0025743�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������-----BEGIN PGP SIGNATURE----- Comment: Produced by DKGPG. Comment: Source: https://files.gnupg.net/file/data/5adubcunuri5q7qw4262/PHID-FILE-isbk5gbsmz67r7pipmoa/GnuPG-Alice-TimestampTest1.sig Version: LibTMCG 1.3.14 wv8AAAFKBEARCgEU/wAAAAWCW4Qtpv8AAAAChwD/AAAACZAtcnzHaGl3NP8AAAA0 FIAAAAAAIQAKc2VyaWFsbnVtYmVyQGRvdHMudGVzdGRvbWFpbi50ZXN0VEVTVDAw MDAwMf8AAAAtGmh0dHBzOi8vcG9saWN5LnRlc3Rkb21haW4udGVzdC90aW1lc3Rh bXBpbmcv/wAAAGqgBAARCgAz/wAAAAUCW4QsOf8AAAAJEC1yfMdoaXc0/wAAABYh BKD/RZC7YSLt7248VC1yfMdoaXc0AACjhgCZAWMQ7G7Y8ZgA49Om7rP8M6bzpKUA n11pnt+6XH3ytxMjWIPmIypkSH42/wAAABYhBKD/RZC7YSLt7248VC1yfMdoaXc0 AAAnJwCcC33Agj/STYlb283+HqWQAw/ZIJQAn2lN4+6WHkSc8gm6d2iPfC+3JJGl =40KQ -----END PGP SIGNATURE----- ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/contrib/pep/pEpkey-netpgp.asc��������������������������������������0000644�0000000�0000000�00000003242�10461020230�0022457�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������-----BEGIN PGP PUBLIC KEY BLOCK----- Version: NetPGP for pEp 3.99.99/[beta0] xsFNBF0KGt4BEACeUKUOmThkHAzOzIpLUQFHxntspjv+2n75O6ZCVipJpoGT8wYcgmogBsyNR8uD AosFrw1hAeTN3s2ZB4Kew4xKpmG0R/2aHLn8V/k/cjadzsPHDdLd0CO9/kmIvs1bfQlzKdltjnbc tdgaXG751O2czyXU0qJU+BYgghY2k50Ewf9alY92xBFp9EOipsUzfOs1CpwsYqQrnkTvDoUbhqBq 9ctK/UFCsp3qo0CpFG5+4+Oj3upHLHNfEl6oafFAaJbN4LDn7PvLnp/Ij/4VdFklVsdpuFa+qgBi ZAUBXS8VhSvOiebaYttuCacRoi2WwX9IQyULnVPrMRRcPof63sqFUA4sHJztad46mPfUkVi7+9hE v2Ni4vHIEv+Bwzu77gJ+y5XySjNM6jsgDLY1Y9n75yUSzGgjsJc+7do2/2iArhrD3WsJlnwMWAdd Yjkia95qaXsvlzhcCG+9GXLf0RTCV6TVDwKI4uITIlQaDUNSiqA1CJYRoHk4WXhoIsG1Koac3/xG jka2SQkneTHxcSmWqZmI5HfG6kBP2EqxYk31xDk09z1roqHVXpe0Bbf+OsMU1IzDlQfTD4X9/BGM 3xhEeyPRcMwTCHXElMX0fUEx9SWEB9kGxOxygdQurQHprd4r3EB2Cnvc0wO1K6YCpe0OgmdkQoWs j2z2TDd32qgApwARAQABzSthbmRyb2lkMDJAcGVwdGVzdC5jaCA8YW5kcm9pZDAyQHBlcHRlc3Qu Y2g+wsF9BBMBAgAxBQJdChreBQkB4TOACRAXThS7qOa2DgIZAQIbBgYLCQcDAgEGFQgCCQoLAxYC AwIeAQAA9sYP/i2/POM1cqMOZK3aH0byBl3lsd3iIOC9jXeRirlNM24tLg5EhnRVMBjl3Kxb4bB3 IIWrMTaPCYEkyYQncd1t2+U5fI77GP0OSpqA3voBASNCu5JQPQy8RV72C179bEppgGwZkPmFJprO hmgLt3vBKclEa3oS3RWItSO+JhD8DoCO31o7MppBeMECMcntX+lGwGREnV1/dKjvZWjaEHb1NyIv 1p97YjS6id8QwTZXVPnTcGN4eD70+9iRnuKGr+dOBNwpNPno2zaYLDsSo1SI2BgQetVw0ZVHlmxx wOUvIfDAAT893PbwE+3hO89UQK85huh2xs6VeYCUCzoblb2LmKf5OKhaBRiQath1wWgMKlcKw1EQ qidpLarNaatGfMPVXobS36TsdLJRvsEczAkr0LT59Syso9RCnrLSISSI/lFA0H6hWsQx7VN12c0l N4S5yq5afW9tcPoGleEaGFzYMkMqyRFMkKIAf+yDgelftvFTYC89rILt6azUvzSTFC1sKfTliaTc mMyHuugEr7d6rfA/HHXwcpl/m0CRScTFK8iu4umN5/uYc2cjIBlE6JlFaJXpTdUHxcGfSX79emSD F118WJBAZmVQBMTHnJ1CXPEANmtfC09CJ+dezz1cI1H1txv+XUKeLXpLMks8hBbh+M75p2SWn2Sk mHdhvYtgzXe0 =sLW7 -----END PGP PUBLIC KEY BLOCK----- ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/crypto-refresh/cleartext-signed-message.txt������������������������0000644�0000000�0000000�00000000576�10461020230�0025423�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������-----BEGIN PGP SIGNED MESSAGE----- What we need from the grocery store: - - tofu - - vegetables - - noodles -----BEGIN PGP SIGNATURE----- wpgGARsKAAAAKQWCY5ijYyIhBssYbE8GCaaX5NUt+mxyKwwfHifBilZwj2Ul7Ce6 2azJAAAAAGk2IHZJX1AhiJD39eLuPBgiUU9wUA9VHYblySHkBONKU/usJ9BvuAqo /FvLFuGWMbKAdA+epq7V4HOtAPlBWmU8QOd6aud+aSunHQaaEJ+iTFjP2OMW0KBr NK2ay45cX1IVAQ== -----END PGP SIGNATURE----- ����������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/crypto-refresh/cleartext-signed-message.txt.plain������������������0000644�0000000�0000000�00000000104�10461020230�0026510�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������What we need from the grocery store: - tofu - vegetables - noodles ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/crypto-refresh/v6-minimal-cert.key���������������������������������0000644�0000000�0000000�00000001212�10461020230�0023406�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������-----BEGIN PGP PUBLIC KEY BLOCK----- xioGY4d/4xsAAAAg+U2nu0jWCmHlZ3BqZYfQMxmZu52JGggkLq2EVD34laPCsQYf GwoAAABCBYJjh3/jAwsJBwUVCg4IDAIWAAKbAwIeCSIhBssYbE8GCaaX5NUt+mxy KwwfHifBilZwj2Ul7Ce62azJBScJAgcCAAAAAK0oIBA+LX0ifsDm185Ecds2v8lw gyU2kCcUmKfvBXbAf6rhRYWzuQOwEn7E/aLwIwRaLsdry0+VcallHhSu4RN6HWaE QsiPlR4zxP/TP7mhfVEe7XWPxtnMUMtf15OyA51YBM4qBmOHf+MZAAAAIIaTJINn +eUBXbki+PSAld2nhJh/LVmFsS+60WyvXkQ1wpsGGBsKAAAALAWCY4d/4wKbDCIh BssYbE8GCaaX5NUt+mxyKwwfHifBilZwj2Ul7Ce62azJAAAAAAQBIKbpGG2dWTX8 j+VjFM21J0hqWlEg+bdiojWnKfA5AQpWUWtnNwDEM0g12vYxoWM8Y81W+bHBw805 I8kWVkXU6vFOi+HWvv/ira7ofJu16NnoUkhclkUrk0mXubZvyl4GBg== -----END PGP PUBLIC KEY BLOCK----- ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/crypto-refresh/v6-minimal-secret-locked-for-constrained-envs.key���0000644�0000000�0000000�00000001722�10461020230�0031247�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������-----BEGIN PGP PRIVATE KEY BLOCK----- Comment: CB18 6C4F 0609 A697 E4D5 2DFA 6C72 2B0C 1F1E 27C1 8A56 708F 6525 EC27 BAD9 ACC9 xYIGY4d/4xsAAAAg+U2nu0jWCmHlZ3BqZYfQMxmZu52JGggkLq2EVD34laP9JgcC FATbzyz86Vt7/33+vUo5sKmIAwQQMvv5PRrZqPSldLyxYAYso0uv13b3CCLDSkCK Pt1W6w+advjhOCLe9F1i7MCd69Wr6FNkgkjTrDAHVPRfnaagwrEGHxsKAAAAQgWC Y4d/4wMLCQcFFQoOCAwCFgACmwMCHgkiIQbLGGxPBgmml+TVLfpscisMHx4nwYpW cI9lJewnutmsyQUnCQIHAgAAAACtKCAQPi19In7A5tfORHHbNr/JcIMlNpAnFJin 7wV2wH+q4UWFs7kDsBJ+xP2i8CMEWi7Ha8tPlXGpZR4UruETeh1mhELIj5UeM8T/ 0z+5oX1RHu11j8bZzFDLX9eTsgOdWATHggZjh3/jGQAAACCGkySDZ/nlAV25Ivj0 gJXdp4SYfy1ZhbEvutFsr15ENf0mBwIUBN+f6fKv9o9lPv6iTeHwx70DBBBZ56oY Vzh/NrcnN8JaJv30Wymh5CFRxH5D2/aoMhXTQ4swr4ANH3IFZ5xCcT5CyEzmt9qI btAmHRlgqF+MSrTCmwYYGwoAAAAsBYJjh3/jApsMIiEGyxhsTwYJppfk1S36bHIr DB8eJ8GKVnCPZSXsJ7rZrMkAAAAABAEgpukYbZ1ZNfyP5WMUzbUnSGpaUSD5t2Ki Nacp8DkBClZRa2c3AMQzSDXa9jGhYzxjzVb5scHDzTkjyRZWRdTq8U6L4da+/+Kt ruh8m7Xo2ehSSFyWRSuTSZe5tm/KXgYG -----END PGP PRIVATE KEY BLOCK----- ����������������������������������������������sequoia-openpgp-2.0.0/tests/data/crypto-refresh/v6-minimal-secret-locked.key������������������������0000644�0000000�0000000�00000001570�10461020230�0025204�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������-----BEGIN PGP PRIVATE KEY BLOCK----- xYIGY4d/4xsAAAAg+U2nu0jWCmHlZ3BqZYfQMxmZu52JGggkLq2EVD34laP9JgkC FARdb9ccngltHraRe25uHuyuAQQVtKipJ0+r5jL4dacGWSAheCWPpITYiyfyIOPS 3gIDyg8f7strd1OB4+LZsUhcIjOMpVHgmiY/IutJkulneoBYwrEGHxsKAAAAQgWC Y4d/4wMLCQcFFQoOCAwCFgACmwMCHgkiIQbLGGxPBgmml+TVLfpscisMHx4nwYpW cI9lJewnutmsyQUnCQIHAgAAAACtKCAQPi19In7A5tfORHHbNr/JcIMlNpAnFJin 7wV2wH+q4UWFs7kDsBJ+xP2i8CMEWi7Ha8tPlXGpZR4UruETeh1mhELIj5UeM8T/ 0z+5oX1RHu11j8bZzFDLX9eTsgOdWATHggZjh3/jGQAAACCGkySDZ/nlAV25Ivj0 gJXdp4SYfy1ZhbEvutFsr15ENf0mCQIUBA5hhGgp2oaavg6mFUXcFMwBBBUuE8qf 9Ock+xwusd+GAglBr5LVyr/lup3xxQvHXFSjjA2haXfoN6xUGRdDEHI6+uevKjVR v5oAxgu7eJpaXNjCmwYYGwoAAAAsBYJjh3/jApsMIiEGyxhsTwYJppfk1S36bHIr DB8eJ8GKVnCPZSXsJ7rZrMkAAAAABAEgpukYbZ1ZNfyP5WMUzbUnSGpaUSD5t2Ki Nacp8DkBClZRa2c3AMQzSDXa9jGhYzxjzVb5scHDzTkjyRZWRdTq8U6L4da+/+Kt ruh8m7Xo2ehSSFyWRSuTSZe5tm/KXgYG -----END PGP PRIVATE KEY BLOCK----- ����������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/crypto-refresh/v6-minimal-secret.key�������������������������������0000644�0000000�0000000�00000001346�10461020230�0023746�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������-----BEGIN PGP PRIVATE KEY BLOCK----- xUsGY4d/4xsAAAAg+U2nu0jWCmHlZ3BqZYfQMxmZu52JGggkLq2EVD34laMAGXKB exK+cH6NX1hs5hNhIB00TrJmosgv3mg1ditlsLfCsQYfGwoAAABCBYJjh3/jAwsJ BwUVCg4IDAIWAAKbAwIeCSIhBssYbE8GCaaX5NUt+mxyKwwfHifBilZwj2Ul7Ce6 2azJBScJAgcCAAAAAK0oIBA+LX0ifsDm185Ecds2v8lwgyU2kCcUmKfvBXbAf6rh RYWzuQOwEn7E/aLwIwRaLsdry0+VcallHhSu4RN6HWaEQsiPlR4zxP/TP7mhfVEe 7XWPxtnMUMtf15OyA51YBMdLBmOHf+MZAAAAIIaTJINn+eUBXbki+PSAld2nhJh/ LVmFsS+60WyvXkQ1AE1gCk95TUR3XFeibg/u/tVY6a//1q0NWC1X+yui3O24wpsG GBsKAAAALAWCY4d/4wKbDCIhBssYbE8GCaaX5NUt+mxyKwwfHifBilZwj2Ul7Ce6 2azJAAAAAAQBIKbpGG2dWTX8j+VjFM21J0hqWlEg+bdiojWnKfA5AQpWUWtnNwDE M0g12vYxoWM8Y81W+bHBw805I8kWVkXU6vFOi+HWvv/ira7ofJu16NnoUkhclkUr k0mXubZvyl4GBg== -----END PGP PRIVATE KEY BLOCK----- ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/crypto-refresh/v6skesk-aes128-eax.pgp������������������������������0000644�0000000�0000000�00000000443�10461020230�0023647�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������-----BEGIN PGP MESSAGE----- w0AGHgcBCwMIpa5XnR/F2Cv/aSJPkZmTs1Bvo7WaanPP+MXvxfQcV/tU4cImgV14 KPX5LEVOtl6+AKtZhsaObnxV0mkCBwEGn/kOOzIZZPOkKRPI3MZhkyUBUifvt+rq pJ8EwuZ0F11KPSJu1q/LnKmsEiwUcOEcY9TAqyQcapOK1Iv5mlqZuQu6gyXeYQR1 QCWKt5Wala0FHdqW6xVDHf719eIlXKeCYVRuM5o= -----END PGP MESSAGE----- �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/crypto-refresh/v6skesk-aes128-gcm.pgp������������������������������0000644�0000000�0000000�00000000437�10461020230�0023643�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������-----BEGIN PGP MESSAGE----- wzwGGgcDCwMI6dOXhbIHAAj/tC58SD70iERXyzcmubPbn/d25fTZpAlS4kRymIUa v/91Jt8t1VRBdXmneZ/SaQIHAwb8uUSQvLmLvcnRBsYJAmaUD3LontwhtVlrFXax Ae0Pn/xvxtZbv9JNzQeQlm5tHoWjAFN4TLHYtqBpnvEhVaeyrWJYUxtXZR/Xd3kS +pXjXZtAIW9ppMJI2yj/QzHxYykHOZ5v+Q== -----END PGP MESSAGE----- ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/crypto-refresh/v6skesk-aes128-ocb.pgp������������������������������0000644�0000000�0000000�00000000443�10461020230�0023635�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������-----BEGIN PGP MESSAGE----- wz8GHQcCCwMIVqKY0vXjZFP/z8xcEWZO2520JZDX3EawckG2EsOBLP/76gDyNHsl ZBEj+IeuYNT9YU4IN9gZ02zSaQIHAgYgpmH3MfyaMDK1YjMmAn46XY21dI6+/wsM WRDQns3WQf+f04VidYA1vEl1TOG/P/+n2tCjuBBPUTPPQqQQCoPu9MobSAGohGv0 K82nyM6dZeIS8wHLzZj9yt5pSod61CRzI/boVw== -----END PGP MESSAGE----- �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/edge-cases/malformed-embedded-sig.pgp������������������������������0000644�0000000�0000000�00000001202�10461020230�0023772�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������� S@�j lӮ:_ �S@� +U _Y�Ro880b-qlYS@ �Of7Ow#tBЯժp�ZV@Z ~uJm|߿it!: 3nGgW y\v@g/Hvkh-l/|;Q׳Y]SEC~7DA: >urً)d[B`O^WT-_AQ7Ϥ%43>e^w_5zRX?@D6G.ӴO; B@<Z7??mbsĔ L6ßQAN$FS-F[q@w?�Q.V0B'uYN))I5jnEG[VS)/8MAEi<6{twE1;h#YAb[!Ԧ_?_$s'4G4,o?t0+zdW5*Ы*wv,9shzٮ xB4K&u'6bQ@;c)A ܇UJ4xkg@:>N@ WkY3NQ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/edge-cases/malformed-notation-name.pgp�����������������������������0000644�0000000�0000000�00000000612�10461020230�0024236�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000�������������������������������������������������������������������������������������������������������������������������������������������������������������������������qD$0���� �preferred-email-W|B>�I7NS&S8nngldap://keyserver.pgp.com������ � BN8-L_zNO{?yĴfF,; &NR:"T c`gaqa`_bĸ#_Ү0lJB3Hߞ~;ΗzipمP.1aqas\@~[Zsm" IMvLt(M_bWJ#0|:\K,<2lѮ[h# ?;Z=@iyyuWQGTt{)吏/Ë/ȥ˗^ ,OH!R[����������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/keys/1pa3pc-dkgpg.pgp����������������������������������������������0000644�0000000�0000000�00000004137�10461020230�0020663�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������-----BEGIN PGP PUBLIC KEY BLOCK----- mQGNBF2lnPIBDAC5cL9PQoQLTMuhjbYvb4Ncuuo0bfmgPRFywX53jPhoFf4Zg6mv /seOXpgecTdOcVttfzC8ycIKrt3aQTiwOG/ctaR4Bk/t6ayNFfdUNxHWk4WCKzdz /56fW2O0F23qIRd8UUJp5IIlN4RDdRCtdhVQIAuzvp2oVy/LaS2kxQoKvph/5pQ/ 5whqsyroEWDJoSV0yOb25B/iwk/pLUFoyhDG9bj0kIzDxrEqW+7Ba8nocQlecMF3 X5KMN5kp2zraLv9dlBBpWW43XktjcCZgMy20SouraVma8Je/ECwUWYUiAZxLIlMv 9CurEOtxUw6N3RdOtLmYZS9uEnn5y1UkF88o8Nku890uk6BrewFzJyLAx5wRZ4F0 qV/yq36UWQ0JB/AUGhHVPdFf6pl6eaxBwT5GXvbBUibtf8YI2og5RsgTWtXfU7eb SGXrl5ZMpbA6mbfhd0R8aPxWfmDWiIOhBufhMCvUHh1sApMKVZnvIff9/0Dca3wb vLIwa3T4CyshfT0AEQEAAbQhQm9iIEJhYmJhZ2UgPGJvYkBvcGVucGdwLmV4YW1w bGU+iQHOBBMBCgA4AhsDBQsJCAcCBhUKCQgLAgQWAgMBAh4BAheAFiEE0aZuGiOx gsmYD3iM+/zIKgFeczAFAl2lnvoACgkQ+/zIKgFeczBvbAv/VNk90a6hG8Od9xTz XxH5YRFUSGfIA1yjPIVOnKqhMwps2U+sWE3urL+MvjyQRlyRV8oY9IOhQ5Esm6DO ZYrTnE7qVETm1ajIAP2OFChEc55uH88x/anpPOXOJY7S8jbn3naC9qad75BrZ+3g 9EBUWiy5p8TykP05WSnSxNRt7vFKLfEB4nGkehpwHXOVF0CRNwYle42bg8lpmdXF DcCZCi+qEbafmTQzkAqyzS3nCh3IAqq6Y0kBuaKLm2tSNUOlZbD+OHYQNZ5Jix7c ZUzs6Xh4+I55NRWl5smrLq66yOQoFPy9jot/Qxikx/wP3MsAzeGaZSEPc0fHp5G1 6rlGbxQ3vl8/usUV7W+TMEMljgwd5x8POR6HC8EaCDfVnUBCPi/Gv+egLjsIbPJZ ZEroiE40e6/UoCiQtlpQB5exPJYSd1Q1txCwueih99PHepsDhmUQKiACszNU+RRo zAYau2VdHqnRJ7QYdxHDiH49jPK4NTMyb/tJh2TiIwcmsIpGwnUEEBEKACcFAmBN CpAJEJunidx21oSaFiEEcf/aAEQJ5d2ww+jxm6eJ3HbWhJoAABmgAP9KtTytfXXD 2X+mlrizBMpx47U23r02symW7fY5YccuGwD9Eaca7pxQ2g0Y3+dz737GTZF6pmKn qyJ+TG3K4HzQ9PTCwTUEFgEKAGkFgmBNCwkJkPv8yCoBXnMwFiEE0aZuGiOxgsmY D3iM+/zIKgFeczBBpdmDltBPUyiISTTaDtkW4PimJ8bImwgTTLrJbHOhwD0EiW9z Bbk+ygDencFVoWXGmdJiRDaYH8YnSzmQcRNSzZgAAMkHDACchoM/7czeOaKt2Gr2 20YxgLQXdIwpDiiUgPuYJvarFrgxFhz1FZKiuNOsb0Lla9vqAwT9c+tefOLKPzLR 1nQl2RO52v4G1yGtj9nQD5eT1fCERMroHFWP05xXt0auBV2sGaq9mLhMXf9S0O7Z NPduKiKmiVBd7NfeEk46wpXQqNJuvknIA2aLJ9bHMtRHUgZDRkAR+2bhrLyaqPmU gmVAVhZWAwlimz7EGhqLkZMRBOsGlKO1HsxJHdVtaF/MkwUrTP3DEqf2Lyzd9ZYf iUwE6kOdRo9IRW3Z3fkncm2UfoRo1LuNohVDJpKij2P/QOQNEdtqv8OK9ea0xLib OVD9/n9odYiVQYWN3MasQyc965w99LJGWFPWD7FRYiBqfVl6xqHQqvx3QLOcyx4t aCdj1Qi0cexk1BzqXdFlPQ01exOE8YKYzPzv2pOFDBBGcNPpeTDMjZ7q0FRBjlh/ XlaxvSSw8GkW/a0Rt9YWzVZXQWrRpoEgK9sLvKSSuOLeJjM= =vrAw -----END PGP PUBLIC KEY BLOCK----- ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/keys/InRelease.msft.signers.pgp������������������������������������0000644�0000000�0000000�00000001201�10461020230�0022757�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������ V1X�*afRqіΥ~$ �bd-vj!",Z=m';61CB,UW>42V,�_ocbδ;p)WP S:6EƗv9=y3-QKmgFeI""<?ue?- ̑eDo�epF8,a ~27WbR㊼k3/cHH̽+MQ��7Microsoft (Release signing) <gpgsecurity@microsoft.com>5�V1X � >)}+-u_1o)V$Ӏ\CM&gw<q0ѱ q'nټDFB~Q6r.9mfK2%&tS 2(=h|P2PZ8ŝȜk6eHLU(wn͂}o1kWϡ]78oi 1/I]`]:O'dt6#B)'Ԇ.z$f>5ЅMncA[.�����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/keys/InRelease.signers.pgp�����������������������������������������0000644�0000000�0000000�00000063125�10461020230�0022024�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������-----BEGIN PGP PUBLIC KEY BLOCK----- xsFNBFyy5ecBEACxXGKUyi5dFjPhEFoz3IwKlVfDxySVg+hlhcUEO657UHf/7Ba5 wr9eHxjlbpxetAymSNnptgh8oaJWcokr9UjeaTbKrYGpRra7Wd1W+f++9tF7BVvV +AWBaltD5NDuq+eQ7kj72oeMa7KAr4702ZokLgiTsS9dPeDAodx3/jMuV9VxlJ7q w07bAoUdzhlPBcII3MOCMfQmtwIg27/qqekeOnrGtNwscugwVqcBATxRZ1wNAebJ 60FH9FQOtPZJnuv/q3KXqoneuSMKiBKferQhLXDG/1fUyojNF9Dcae+HmHAZmVsV K8cHQwgSICWOgWOKVHUH0YHYvElhNIWayaw1EswEW3WMa0F4tY+EDNHEII1TGOxc X9VzbGT998Hiuf9iJuWuCgYZ75XGA/tUooOwLE77lxPGpTtLL0tr/lTJOkfwxVeY ERH1LranSQhZAXDHozKPylGo2vLxfA4WNKfaC7Mgq2WKpDWjYtF4kO6/Eiyoiq8L DqOkCtvt84PFoXEGMk3I1yd7d3bhIUwsgt6nkvn54xebJwVe5aK4MM7qCNZAm+7i 94iZjXTH9wUWX27n9UESqYeHjer1L0m/yL8sn4ceCMzpri2HsI71URwJp47GJTSV 6oAm7NJkiT5Oihcex/tvObZZXZZNqtwROBCkBcdb4Ii3upIfx8uQ3WBkSQARAQAB wsGOBB8BCgA4FiEEgNFYI7f9FWH597zd3DDXwjy7q+4FAlyy5mwXDIABjII97RCq gEFjnhIQWs6NbgwUpHACBwAACgkQ3DDXwjy7q+7ogg/9HDJxheL/RseW2Fa9Ll0B 9aWmRAG4bllmbEKDDQ6Z5MU+sgAyTZNnuaB6I8Z63Ca/N77lB5GbLjN1Jl/JpBbF xaggT6hRpYQBEuVIYhOcNWfp+ICCNypq5PdLDcRanZQBUi8k6jPn1pkj6ax2CUjz Z5I76/9ehn8GTcqvCsAKUKqw98idd2h0t4FsPFD4IkYaOrzuyNLoMzwtkVHpjetw l/1BsZJTVkb5uM1viep17b14iqGsk9x+HinXYAM0tR/QwVaHwoORU7L1yokf8Z5h feQ/GXxwMPqzk/ASytxMhrUhrDtxhLFFFlAlh4J/3//aSfjFA7MXBKhUMcuODi1O By0vNyaJ5upkC2hkYsBAYOrZKuc9z28rfPxUyDOskzWk2QuuEC9Nm3LLDzlur9Jd R/KY+dYOGTnTvW/rJ+cgwu2/OAD4VzJsPqZEsLW1QWF96qpWLnNvDWFq6cafCh1J PD/PYFyBH190IYDx5u8jCHy3FCwMc0bXQTx1bAXyl797y5J1qCYhNMT1Bg3TitX9 FExjpYnz7anPXZdBmYDXwE+WM/ZUgdlwGDkKbaxKCM0D+unCUa0DED7O2KbCdXf2 n3QEARVRyjwBdcz1wFrJranE1/lTSGPrEwbbsWohx2abIqXKzabqQ8sO7GsXSE8E hrdYzmMQpd6tyuwwYL7H1WjCwY4EHwEKADgWIQSA0Vgjt/0VYfn3vN3cMNfCPLur 7gUCXLLmbBcMgAH7+r21QbXclVvZum7bFs9bsSUlxAIHAAAKCRDcMNfCPLur7ihB D/4iace5p4gK5MTRNTibKNktYfpOr47BccPGdfeEx+PrVXPHAvFVoo6cwTBa0VeS n8jXkosgwlXREUTsXFTWq0XFOKBg1OLzofKQyxfyYZLM4ge2VAGuI20HuwnAVHUU /+8BIzH31CJmvsehWIhALaCxA7RbI01aREpiDJoiBNppHCqwXBRxzk3y7Shmo4pt J+joRw4x9OZXjBC1y4q70bafOufglKGU11qMDqTan9LpbVT8eN/7xLuGQsUC+Nt5 ZB/UZkN7shfHiI8bEOTfR9hawf83i/ErAv3PhFmcI9D9SAe11PYGTYwZtGs6Osnv SXyJNyxvanaFbNfowEUou4NGGdRMXff6W3qe7SQG976SHmJtHB5V5QlO9gVxU5TC TQc1IL7+JJRhJN83Yo/CnOo6xeY0/jlhZDvVFylGuHDe2L87Q4GqU4ztwrq6KYPA OuPCGrDTo6Dzc0+WAiZfnrtx11qSawa6hlP0pJdjw09fhBaugrdPyIr23b0iMwp+ Q8mMaqU8ud4Sfae8KuMvcaNF5dCNe4qJ3xVfeQCkZIsFVSWdq8LHxmQoVZYH+ZsQ 7QzjKZT5s6sb5We7scGYm6O0+1SzT0j4IoiXM39kovzmq40eEZktOm0l7qmDO5vW 2DcMSdFrf9bY4yP0/XiCgKIntl6xKC8FP6lBYl+fd4Jq1MLBjgQfAQoAOBYhBIDR WCO3/RVh+fe83dww18I8u6vuBQJcsuZsFwyAATCZEb6pZtBhMFMEVxG05f8VsP2C AgcAAAoJENww18I8u6vuh+8P/ji24OcPKRhp5tbdCNkKC1Ss92zWC39GieUsDg2I 150Gy4HBI3Lf+IHsU8t2oT0es7F9nKtARlx/NANp04PTD9tAVDo7UJxo0aM5/RaL UzouR7MaMiAXD+aX5Ns3H3LJKUyXIalYQeUF5sPr5QiDrY0oC460j5kZ4R6ikfG4 zM78KPo4wsE5VlQbY1PVJwNY0Ybx806fEiwuBx2N9cgIlOhWwJbYQtFLwnMP3NBc RGnGYsuLQ2CVHoVdXtrXcB3/NskWpmav8J1OohDPSClDXMRyoKVMcDkivNrq6T9Y JA293fEKeRFZIU7gjvAuMLOj7xSIHttXvl4Ay4Hkdgevkj1PMiXWSKWCXqjfXho5 cpuieWMAYuAUhhFPLYl734NGQvs23I/NPZe9EDLSnAm+whV0HCKX7gR7qkDpDdCK YcLWEyfLvhAAxVsG0z6i67ITfOQUEOOCTRfwUpJe42ZxycFKjCMJYj9scQX3hwQ4 kmhpAo7tcUIeH0Qd6G9lAP1tAAAKbbmslJCktYDFbntmN9qszzmO5sAimoS0NcJ/ AAfo1YZ3R1jIDWw3ydNgbQd6Bsr1g7lGktF2Chp3M9ABPFDQ8p9l4+LhEiRUGdyT 9uBVRiA81VCZGKHReJkmS/yiLIzl0gRorVge7lZYes2c7k5MEm4lb0Y++a+sY5ky SJQ/wsGOBB8BCgA4FiEEgNFYI7f9FWH597zd3DDXwjy7q+4FAlyy5mwXDIABx09q yekzswZ/UvM/pFnsZxWwcF8CBwAACgkQ3DDXwjy7q+76zw//SOadNMyeNjFtMGwB GOEBo4gFUbL9S8NVVKJHk9oerNLAYWJ/kjPpUwgL2j6e6tChhYxzklQiCpcHglf9 SMPRA7kXi6X2NnijRVkQ4/pSmEiO+E6LO97DRwUb6ecV5Bai7zawZk2H73Kq9hSA 3xei1wGW/8lIOjYxOBPd5IpcKvJYft5z6jPWqVqPR+n6+qxnw7dYZa8FA86yOTu0 ebY40NXXe/WD2AV0ukm3NyyPJkx+hyCk1YDsNa7APIWhC9ScMxYgp25iGeLOXXNs nPgC6VXNznVCqwk6VPwLYUALv0UlxjfQHY3S2DwxmGhXq96Wbzdja4dWQfI/uDGN WIjqdKoC0EsWgaqvyUWO41QzcTMjQA2bn+44uKngKfZ2yIgloyuKKiiphhu/AcVc dOc07Xlj7XMZFUrDzHzoI2hHn2oGCyIL/AXtfmr1YIW6C16ziKxwhT31e1jXi3Cw /DMM+/yUxT/l6GwL4ptOkEnubv8CXPOQBUyj1fqAsXZ9HRPNW6dhva+P8qUTgQRJ GJ6BTdXYZsrF7PHNMUQG6CuelsVooVUIYiu3yzqJzM5nHjHomirELnzaDkNWt6zu KneA5LOHMF1ohoVkV0hVmNXAm8T9+KKP3xSgMSwXakYTfyI2n1PrcOUsoFv95pDB ylcSdk2/u9ukD4W/3GH0X6V+dvnCwY4EHwEKADgWIQSA0Vgjt/0VYfn3vN3cMNfC PLur7gUCXLLmbBcMgAGA6XbxSlCKSOnKP+m8NyJSyhz5ZAIHAAAKCRDcMNfCPLur 7u7+D/9bPPbR2VDKZ+Su2+BYBc3yrDqJ+RC13+Q27rUDm7kwaD8RWIWea9l21hF6 e1/eqewOXdmCNiEcwnmEtUbXoZvaeuSlBB7qsFfbo8ySD/A/m55tSc9CxwQ7QqlW UkGmk7j+uYk0qymGxFgKSBtk5EwvJNGKOs340nDcuKuTn3KSRbK7Q9A3RvL5oMw6 ODhC3y9+k3F54QG0KDvCTle4CtmYPByOprORefTiXvgtHNfKcrYcVqRE4JZlBDGX zuCkVcDNx25qVt6drNgjedZyk8y1ou86hamx7iMzZzhQL19f48s4I6HTU84y7/v6 iZ4p+5IsnvueBLuOQJ1ufzLjr14QEWHm0YPamI2DciBIWMYU4gq3x445hAnl1gsJ ikNDXao7cH9OdGIjBYYM9aDfDFlQSRVL5mSplwF7eoioRDiv9mAba8HJZ2IoMRgY iTq8hM4/1IHNYTb0zzzegC+J9wbdxPDI4OXTpEx78MmRnAzFu2LSqfYWnDpPiSjN VUjfBua4ubuRKFxPF4Ti+iU/jhzeY5yojJcig5B4IsfLfhKZiTOZInuITfa7pXiH O8861/EVtHpVQo4a8UIZRMt+9msCoXY+JRbD0H9teQPqPuOzt5yVjP0KWAOdbynk fMDjFWhUC9khI1vDG2LB+WmVthPq3Uy/WxTaLKOOudxjYBhXWc1HRGViaWFuIEFy Y2hpdmUgQXV0b21hdGljIFNpZ25pbmcgS2V5ICgxMC9idXN0ZXIpIDxmdHBtYXN0 ZXJAZGViaWFuLm9yZz7CwZQEEwEKAD4WIQSA0Vgjt/0VYfn3vN3cMNfCPLur7gUC XLLl5wIbAwUJDwmcAAULCQgHAwUVCgkICwUWAgMBAAIeAQIXgAAKCRDcMNfCPLur 7p8KD/4gCYmz6IjMnhsz8x9d5lP3h+wIdUdt0L0QCNceoHcblUFhqx74HwVMLFyY k+8/WHrLry/N83mgWmP8GOeOsQG0+1Fpd+0ew1+smYagSjyON4crv8W47Yb48qfV UwT9VRJqdW0zga6KD8F17I3ssOVr9pZTDHa33ykwzg4eUvBs4wYdb5dZMYJImgRA NRzgeiw70LOMZyaPh6yu7i+qcDuVUP1R8xF14GWmKgczsNnOGvaHTo+lc8SSTwjb OhkNOSN9X6EYdqXRgyeGGiLcgWL7cOmezLNVOV4pDUD1T0jOXMV/t+2hQaPNmIJO 2hFa4m8ewi4Yo7QUw9q/NToJNMwtr4ZeFH4taCfHbfIJBQE+BQJ1MXDckH95LFNF v3Zfh9iwEXyM1P5IgcgGp5mh7Uzs+FfyNLBzIoC09Kgbtrgohihm5S7jJD7ghogW tQP6Gvz1XWvXOmljv2ccJKezbL82ChED/uSBnWypPxs2zbtyEvX16QnwJsNZMrvT Whh4/4jaDrM7wncmU4RoV96KwwTlx8V4XlkEielMCt1Po/9Ws3JbdcFKVEIUrLOB p631evHuUG+mmBlGAX1k8uiEVK3Xvrn3wdDc8+tPSxDQ9GCnQ4YPOv4SU02eUB+q tBs85NbpULxAweKyMumARNVuqC82viB2YryUZF5+JslFnmb8psLBlQQQAQoAPxYh BPv6vbVBtdyVW9m6btsWz1uxJSXEBQJcs4trIRpodHRwOi8vZ3BnLmdhbm5lZmYu ZGUvcG9saWN5LnR4dAAKCRDbFs9bsSUlxKm/D/9pB+G1mLPt2DZveRhLQXi9w0QJ lmOH3Ec/KYZKLbrk74yV6hgJS5fP9NYMT5/89wDDKajmXy30UpiX99Y1nOeSGV7x k0LikiVvv1ZQl3YhsIgyiHiCtYgVXxZPhFYhxHw5P+7Zdl00gkTilTBuVbaVQLH+ S593MBla/IX7PXPZFyPkArh3pyDleiE3AQiU8EWo0Zjhntrfa9VQtk79vC1ho0// p+W0EPyhiLl9nzRvxoCjveSMFw8Pn+Qr51FzC/Y+EGjYao0H2PLce4CcogWh2no0 o1zeFSm8xoyGUgNczs0hMLkrQTkr2+YQj9NJ5oKdhZM1uRzsJ/DDXaEQTZjj2iIy U8e0E/OhOaq3OnTMVeiZEy5ZvyfyYlkzb5QmcufvOCh5rFtUj5+6TGl3ywRyTrs2 1MjCVwggBn2KU0Kg/gqh2IkPavlV+LecH6CJwplAlsH1cnnnm2RJwOQhcdAAjbpj vkAVi4k+XJGnVZaeU1KCG8nmVSWdKd60Li4EOPlOswc5K9GmPFjEfHkY6dynKbzM h8ukSozSF2f7Z0wL+c53jMCHpZ/UZUBqNjmhKcoSPCME5pKP9rUr+L+sucw9gNC9 mwWRTj6KbjLWo7fvQpJaBvcbYNIpKU7ViBe4BlvbSl0Me56Cmew4s8G5T2cpUG2A umg/Rr5lR+MXdfGjVMLBcwQQAQoAHRYhBIDpdvFKUIpI6co/6bw3IlLKHPlkBQJc su7uAAoJELw3IlLKHPlkqAYQAIx6nxzslVIR3qGyuYRPnSF3xTe2XDOGXCO40zXz WY3U/V1X5NyOmvP4OvdvlTd+4D4iueKOfdV++hr8MPmPv13OnxVU7kOVLPHBMzx+ ezCUm1K/81oIAWwSvFfsaLniWDALVpu5Siv9h02KaKPTqGDk9EPxm2MAz6M3c4jr i8+yaFL5A8PezPWe6K6Hn0BegFxW00PkSPucdwrdY7B/zGG+KbmD57Kho9JPjtbN rBaNIr9KAFBV4mxTQHS9v4NAZ6ad3hDJUjm0YpbP6/DSIBeJ5SSyqchgOW01JSCu 7cj4zQvpwiX0L2iP6C9efi9LCm6HClIllLutq0iuG9j65KEGrRPWBLLVu4tzqGQO /JL50iqcRnM98IECiDdDmY8PQZnmvOT9cn089OAfhAS6Dnsy/JjIWa8gO1DVmZ8C XFZwoUwI0Sm4aEvcNyZlap34WqzgiT9SwQ9ClJWHFywt/zAM7V0jMkFqaUXggkci age2yYwUYj3PA0PAGjWM84pw+3cVsyVDD0pSG38fteDqltFDrIZWL2ml2IaTURvk H8nvZCPBJpUqELVb+c81m8Xk3FHAiZNbWLcuXLLTpZ5ul6sXv/xaSyTWkegYpke3 klLzXoNN85WuyOQWoLrqYkJgHLe8Q840lY6j2CiHEDtXRI/28wRklrxSlpisMbKi eOrhwsFzBBABCAAdFiEEbtb1y1+m+y9GCuiO7aDSOIriK6kFAlyy7NwACgkQ7aDS OIriK6mzKhAAhd7CQ/3Bl9Cvk8x+Gt5NEDnj80gLGKqxUxoRekSAp6Rkh4b7XOBb Sb+LHgniPgmXZnnVhNChfAlSmnmS4i+chJbu9Y2B987exiNXdBYWE3VBMvzy8a5J bUF8Guqqb9DlzAaD3rHOUSOK3HWi+Rhf9wdFKVzDUXku32v4fmxMSSTOqpXRj2iV nuKLCKR18hNiZK5ez434gQDqYDvHuU4/jzsXsG4nPKfxvSjZk6hykb0rWvxbmDA1 RVTLKAdlL+nm1dNoJKRz7/OmHf/u5VohinSDhlXbtWHL1PO7mqgqst5+0qkjImEN psQE9lKAyyV8xo/PsS+pu6N6NPxyjfTLtHHyBnUOwS09vvib8aVYSH+3GqCz0c0Z pmGaTeDT2fhdCBFs7DKV6HYT3DbnqBnjtQF2PBFUSDJlbRafDAu2JwLVPC3QL/iY KUn6NQHQkrKPYp8uQAMSLLRCr8lGMCG64oqsMcVXHv3QYrYqQE+83dNSsZa+BabY Tyz+tZS9EtJkN65UgrRvRLPvVazAEmJquiHZxLuwEuSUmnpSfTY0KGGJMhzsN8AI 98K1sqDjrUvmgHH7ACWj0hU3xzkd0yOGRjH507xOBFNpgN9LsPpRe9h5vpisFOrJ YeIp2hQcoPDKHvgdeyFau3qdOItI7S5bKJUW7UvfXu0pH+HyydTpZX/CwXMEEAEI AB0WIQThzyDd/+S4noAmWPHgsRiU9mrsmAUCXLLr3AAKCRDgsRiU9mrsmKEjD/wK VJ6/juqs35AjZMVnMhXv3Ei7IymAzXp3mhiFZ335NYMJbUOv8d+NN1GFvh1hTHTn /j80big9IWm57ZnTGT92tbdGIiZEReKxl9q6gXoHrD4gae+H0pR7bsunD1pbuz2I tKlbNbmVolBd3gaPQ4TeY9MNL02hFQRYda3moYTsOUpU149iExlYiV3ELRYHJ+Qn qW2fanLMIJwr+UVNal1Maojk0/8wZlXTrDYmYpeV9/S+bfBhOsjQjWkK5viTTPzl j7MBInzoTeHogsrCA8CsRi6hb68N4tfIlfM1oyfrEDto13qlRWsX09x0d0tuETvV aY7SMLOVXRY4XM5KhFgB1f1bREQ4K4YhAd7VcKxAETRo7iBCb1hKOuNykZCNsPii uxgdEaeJngVx4cg7nH37LP9LeTamcp42fwwxlmvm7JP5dgSNyrz4tjWV1wl5GR2w 0REJGpI5UILDPAstPmv/01Nl/pe/mlx0m68FZQHxesgOi3+srvxkzMktPK7OaUsM VhbnLnGD4ADRg/lely5+PdLp+4nsHI45pelSm4tHxKh5FFfed7YGbZ0BoGfZNyi+ 2jXQFfxrSMXOO7C/BxLFoLwTnOrNYV28O79fm7yPIUuOm4cTGFpl4M5wHUDwfvl5 XOZB7kDAMkH2sWLounF2a2uvturCmb/MWJq6EFc5Pc7BTQRcsuXnARAArgqqMQG0 iABrEdAG6Twzp+wZV7r/2IVqJyhnGyu0+yoOcYqai9eeP8XM3yZk1Y95FE09g7RJ 2jacyhhC5Tsrg+GVJ/1eSsvudegZn+QnqEZ7HrmwJsYKFKhntak11Tvvhsw08sKM 4KVoxZSmMgBq84OUW95ILySM9vm8ge1+aYgr70flXhKne+o1VKeHWlovtmIGpWaJ 7fCHj95pDoJhe6uUkmEIJzMrNIaM7FQ0r4GdBYwqDImW07zMRWk80Av7uf6f+5xc v27y2yW8ZjKF5u0ZKWln+VZX4EfUdCgJ/0LeV/v9gVbCeanNqGJB6k6DpKu6IzGz KXi7rHFi1GiuoiVgy9Svx27iRpJaykLxnGFn8C7Lpzo9q034gGIWLwQnjT1FdPya 2pFV1VHNFZQ3JnQRJwE8yGhw/5bpllaUUJKvydSWvBMgOscEHQdtRnA4IMUXrHGV IhYN/awYkjhubeVJuhbsxaQDqpdAodaoIz20PVBfE+XFbfnLCBwxgzR/m+mE0iW1 GCOBSoFw5SPQBihCF/PPBjqQjZKJz1btUvrv7gpLNuLEyA0RsHBFGqtqvT1K4Hvx 6Y7di35/Nm/Jgty2e75vMSGUm1B+G2pFjEypZjtOckOHQ9hVN4svvMJGFnqcwZIa gMF+67twWmv/AVb5CovsXLKv1qTzplRJWiEAEQEAAcLDsgQYAQoAJhYhBIDRWCO3 /RVh+fe83dww18I8u6vuBQJcsuXnAhsCBQkPCZwAAkAJENww18I8u6vuwXQgBBkB CgAdFiEEAUbcbUoLKRS97TTbZIrP1iLz0TgFAlyy5ecACgkQZIrP1iLz0TiL/g// UwdPym98fCTVZJ+HwHId+Ssqo6vTgxA/6DLGRvFILie40vA4OnFrozusDVh/x+Vv +pxbtdw3w16kfpDifKicx2o4ZyEYl30pdVuBmSEOhFvI3ZgN6P79/Dv3KhD3QQPK OMSxXO2vCh7BebmpfT2rdukgFED9vxbj1Ec7IMfm4VobFJZaFXZKsTBc09MQU2Bm 1JvtzINsdwzp/sFTilxmqO7kX4DmTM3k1KYmMkx7xq5KUaxSORZHIqDcIy74pOIw TuvHN98cYujCKFDk0MfHBovXPUnFHFxd+OgSEbxGnb4Uuus1h89VIU5xviQHPGe0 T9qG6tUBvFuCkPzcWxUg4AN6nxZz8stZHhd0ceuSDeYnGBk6X/eEcYmy/kEbJEqj f+kuY4VFIDkShnnDrKchyoi/LmkfvW4fOEtTpmB8nkflolKfVaN2dEo2hyma3iKC 5zp8n8hlNwhkt3DiGyYXU0RD7JAbX4jVZSVov5PhAjmrEksxslv/ICrAJ7zfCx62 zzm37TGwiQJTWQsIcQ2PRPWFWk/CHAVjNPsu2QpMsGUWccGUOI6a70LsVnnufLzt c73TM37Jv9hCXljRvVRikTy+StjFZlVQdXoZvNJhhIE/W+/iNoBvChD8pKSWe6RJ Yto5CxCQtN6IKgAiUtoXusAgFSB7TZ5CJF1NFZ0VQabJcw/9GunyNNj+RRdMXbHI VbrDQoqKY1FAhIUE0cURfkVE7z0mYUUZ5bwILchQsvwVsQKorVmryh1fgaYCOi+H 4kvmhljN9HqB9I7vgRaYAJ3qwgYIUselclYN4SNniHzatRMROppUMs9W5ytENGhx oPARiZpRVL+rPPaFdip33c27pVdNAU/lRq2ZpzkdSTv+2V9GmVfDtcKv9A4uDqJ9 7ttgZCaifNbHShzMEWRCXSsT7/52XB7KlxmAynwPNMLeM+/0JTCLyFBEvyejvgCM GqgvMDEddarHhd6ChdXLJLBAeXVBGRygWcDBO5rX8GPMb0y5/yE+UVprkx3jSb2m sl9nUW2UcOhfrtu+CPS3qazu6h/QkTwitzAFSn57DtGmwKLzqk63g9TgcjBg1HtZ S66DzdsJ4Y6Iy51oNyHx3EBLzmdFfxKAeABsapvJl7fhiC93CC3hZTKUyBjr6Dru I2wktWCAAMHFE0eeyIreCHdzzMtu+V2H+X9GJMxzd5jOYBI3vy946R2jG5gX+WyD calvWyo8N+XrZKD8NQnWQ/BocU9r5S5aJFcovdcmm1s1Ymdlo5Yuk8WHZDOsSf38 VzY12szoQ9eMbBJOH7MhseS/gIWC/4x1eEEhGbPQbkzKZlJifv+55Mqqq7emGyBG qn8+ouVQUr65+xcIST13Ffg80zfGwU0EYAQdDAEQALsyGkhCUuJzBHy2YwfTIWQw iyoht+52cEK5XTjexJJJ1RP9eMBcp2x04Kl9vpN+Jq7W5j1hvyBYszwnGZK5ohrW MqViJFnkbv0Ps05UhyoGuqneXNUdKEsRezjjbV/RjhtIy4Nxj9x2ZG35L6qUm+oo vABvJ6PKLWLU3f1R8m0r851l+482PPxWhY+Al9/sJohhhESjhBbMCe6rrmA3oM/e rfe+Hk5yhmAwNH62gQpjIaqGNchgQlT2pjP/CmCXKKNsWjJLeOfSgJMelLY3ApzT W8EZVml9H4NVKANZZAeOWtKlv0TtsDGfZF9RmdDpKHepyiKE6PIjRFn1lgfy5CW6 vUlJLaff9HG0JpXq5G1cMHka5eM1+o2ymtlWFhiEFxfjP/igxWKbkAGRfAY1Ax7O sRIcareiIh4loEjk6yjaD8PUwhLZbkHo3NJOoHgJobNn4hjfS++TGGcgbuCvjLvo XNxp90kozVwf9eUVeOdIZDpSYDQTqGleeOxBYY8vsiekNgcVWXV0fazIc6odSYRT wJxlrJvqFXKCdAeHsOWGyu2es3zioSS5d+K0LgCpqruUx+UYH7KamHEEz4ILg5+L K9bYUO7NAAhY9VHCYq4rEwJGc+ZJ35Q+TIPSObrJvUXYWXxjlPiVg4kn0586lTPf wsSp1XHQGiwGfryyELA/ABEBAAHCwY4EHwEKADgWIQQfiZg+AIH94BjzzJZzpPJ7 jdR5NgUCYAQdDxcMgAGMgj3tEKqAQWOeEhBazo1uDBSkcAIHAAAKCRBzpPJ7jdR5 NkJnD/wNSAcUDFGj6Jvzc0rSYTjImxlBoHAsd9OoqKtwcoLuS+k36OUKGiSDY+ez XnvtA8tnR3vRQmjHb/gpe3Bi+noKulciIQ8mHn/3nVUV2rpM4v3VCFue+9OJQ8rn Rygctik282P1iQTTgV5kRu+kO708V+CTmL+sIbXebpcmJ6JtilJXLTidbHzV4Pg9 EFqNl44U5Hr3XsDBOGkBGj7iWmwHH3K9S6Edxp7MPpdOoQc7lJ9ZDjDt+6HL3hs8 C7IcyHntm9G4QnHNqNkh+0dscRpAAJXknkt517J9Kh/0ft47kH8CqqJ5qhf3TMfi YXGyLxovE32dL84Qt2CRqlHStdrp9MBRdHNCB0d2Z25Lz+BYfsk4RYE8TIUd14l9 u/p/4nPwtX5t6TdEZ0Gm1Bzg5T9FYAFZNn8KGvq42avl6osGXt6qn1w0Kwem5Ref yvY8hGqYdw/VIO2dMFchSfAQYDePm9Q2gURbeN70zjsdrlbkQ6hoxfy7gQMPe5KY RSuHvMQJCn1M48oEKmUbfMha9i732hcqwZJRKmVw4wrNv/rGZEtHvxGeE+smlDNo z87ejATPQOuVWRNC7b1ky0Lr8aW3a8MQ8jn6l/RltEQgF289PMuYehKgXgYWtLOT LXctFeBYBTTU3k1EeRgk4vsFlJMJ4t2gjgT5xIx8AlmErLgS8MLBjgQfAQoAOBYh BB+JmD4Agf3gGPPMlnOk8nuN1Hk2BQJgBB0PFwyAAYDpdvFKUIpI6co/6bw3IlLK HPlkAgcAAAoJEHOk8nuN1Hk2FK4P/ixDLh6d8zML0es6JOwgFZ+lstkDY+7jRL8e 0uI5nfKDHIz2apBeZIzbKNoibPpfugHpbqEKuG+IJwmxpWq6gRHtAcCnRyqNHl3D s5sd16CexreCfv4iKwQDbeHF5r0+pMq3qubp4+AJMUAw4HmgV1JJK+wdFrc2aUVs mIaxxC5RhBHT0gIJyM5i2DV0E+XFKwcC2sLZ9SHwuEmKVX3oVvpba/quzpVdfIIC 8Y7F0w+BKaTxrVJ/ZPDWMzCuiAejZw8U9qcArCWcyLcb/U1PJlcHISfjWfhCgkYC t0H2ZMRH17mbsUXY8mad2WLqvtZ0/+wUcdZv0pJI/P867/2hUIi4vbk94KgBtD0W QYF22Axxgck2smWsmblu95QVeXWchKQRCeeQ14G5w5O65ijw0IrV6Vb1xGVQ6N8T Gsbvlz/zN0x89efZZd///DIFCnkFsNMB2A/f4lkg5nqNIul0/unEFN5ISb+yL6Ii uUurJTmtod0UaDW7SB4bUFRs+0OUX6FZHMznleI6PXoiVt0PFdklIcyPFiCKxI3U CWhv4JwzTPHZTuIJ7wn00FU4RLcZGBAFga3qNg31rCsKou2SWozHaTxkyL1PqScA 2Oou8zbqB65KYdbIHPHWK2l9Fc47XyWCzL9M/qE33R5DSkOrP84QgcPVd2ucOFLS CmkihlMIwsGOBB8BCgA4FiEEH4mYPgCB/eAY88yWc6Tye43UeTYFAmAEHQ8XDIAB MJkRvqlm0GEwUwRXEbTl/xWw/YICBwAACgkQc6Tye43UeTY3wQ/+LjebzIjgcLJa FePuVICRZdTjtyj0EEWDc3rjbYUhLH/oMMDt5wjvKaRiF5TixJdP+BqbYOaNbC1q 1zSXe3WKp7rKf3Y23A4ib6qpI8jiAG3vZRyki5yh4Upe3BsTlRHYVd4O4pWzNktv 3NYwxg0HHv6T7ZMs0oGT+ewQDbVpovWaiaaLgFPtFYrN2qPhi66J+K+QTNJdTpvW UQo1m92YRVlG2C7rx3Y1x2do5SM/vhRJ8Di9bMU0ZCXQGLoNedTEq/3OgjqPUUdE tcUwf0jO/fPnaEhaqRDjtTteGNx21Iy5adM8otUw4XQmmDe7makdmYTi3LDTlOVk OyMlnWQT4k601ySvnSmdRwUT7vOV7pqUnHPTklBwoWO99/N0DF524LW8/IobNuUy X8hkQ70krpC7/suT7cq+l8Q45nJ1zTNnYNUdtLktB4MwQchedynsmPjGjADpqgCF F5gCyY25RIJ/S2CBObE+z9Kx9s+CAvQyoTYVaQdwXmavybHpPmocXGJCBG0V6JAk JTpJDFNZM4MstcAltUH6JgNZ5YkKvDAzLBFXROvo0Se4xsEiMkhPixXqqtiITiyn QIIgLgb9BQB9MxZ1FD1E5xC+ayMuD5W0gXGNQUNflaywJHIGTY66axrIVXPXhi6v hLWO8YYIsewgcR/rQDc9kc5SGBvDxs/CwY4EHwEKADgWIQQfiZg+AIH94BjzzJZz pPJ7jdR5NgUCYAQdDxcMgAH7+r21QbXclVvZum7bFs9bsSUlxAIHAAAKCRBzpPJ7 jdR5Nmn4EACMtvbnCpFKD+MzkF3b5ccFQLk03cC7sPzRipKsR1SoKKXV7Vcps2te lPZPx88FzjRoj3jBLtsFNELYvpFANFCLO1Nexv9a79sG8vYrhqKDLT6ecgSJDHbR l9DovAjlVbAGsHBjbmV4J7o7F6xcXgB4t0DIObe2yU4oiCa+S4ku2p9a5ZPrKMJm bRg8EfwD2VVfw8KCycW977JV7MuihXYjjrHugI40h76+rTbKbuZLcTBxMsi1Dfx5 rpLVYZgukMU0N9WwBdCC+x6WBQGmOFMDy15f0cuXYTjDuiZExFaSb04e9O6p3wf2 vOjfsexFIQIy9sXJ7KLfpZoULVzoUuAWgZfKxtH3D4imJ9jeiFKbPomeLpo7vsxf Z9W8UMRfFCKUZG5kS6HKC00ThKD8qXCOz66Ypfy6BJvvTAKr32Y8lgQNqqu7DInt jNrmAJXYSKlE5h+B/tVD5VdszimE1tEEcgf8lA19C3iqUTIle17w0WvhJgBITE+T P2SUiw4tfWYQ55y4oUfJi4lJVck4PuV/ELzwlZmN2A8PSgj7JmivfEQhq+ANGRpn GJ7AvmhAOsuPfakHmsiAdeo0EOIPy5hYFxWGZcFI8xX0ywMH9Kh4hS97oZInCeOs BfWGWUrL4NWogLYDIsdVLDxlDT+ZPnXzqlbtHhwuoniVpVWXH6sMbsLBjgQfAQoA OBYhBB+JmD4Agf3gGPPMlnOk8nuN1Hk2BQJgBB0PFwyAAcdPasnpM7MGf1LzP6RZ 7GcVsHBfAgcAAAoJEHOk8nuN1Hk2GxAQAJ/wLCu17ajxmhSjoaEtqwkXQmSHR3mQ Lu62rErfTnrKRHPhBweZpxZ1jNOFmhi2xLMz1S51yVOA5ttGecYVnP1f5SUTMz8P XCBDS4Nt1phsyk217EKFGehi6CeDOGBxkj2TNZctIZ5FHXdNWiocVNGIOSXr04LE Z/TTYyhJgoCrBRShoGdv64CgMV2mP5SgXAqpwKINRxnTS9e19HrZ/yL/6RgeNUwy sT2SSyTRDxD5yPwrZ7vOdosPaElat7Zy0tRf0c43ZeL4OfD3jga1t1UwhmOhOB5O mFb54avwHf5YdhSg7Uwcxvd57MFlJkcptguA8u/erPp2b9UdUFHQw5Im7vLaWMMh vhcC+9qbRu0r8l6i2FdaBSv6i7cGNzthcf5DgpiAnUAuzfg+abJNoqZPeWlsj7er INy/HjIQZkksTeET1+47Bgu+1cJzz7DF8ulF1Dum64VMAv3uLV4cY4OP9lkOZSK+ nV5TOOhFRMPK69Y4xW0LsPYIdqL7JFBW03q4KKdPAKeGWr/4H4wyuUQv3aQ0S+hX BWQayiBO6lOJTL0PJKThvyLIqzK2GfA3bMIX4Z/CMQN61cmc7iQtAZzRNfTVNIun hv2aoZcophnKna0U4mbf5C0Pj/Xi2kYVNlap8sPMI1dRJdRAVMdQVwn2C5Qmnrnf Tnhh8kAiGXRWzUlEZWJpYW4gQXJjaGl2ZSBBdXRvbWF0aWMgU2lnbmluZyBLZXkg KDExL2J1bGxzZXllKSA8ZnRwbWFzdGVyQGRlYmlhbi5vcmc+wsGUBBMBCgA+FiEE H4mYPgCB/eAY88yWc6Tye43UeTYFAmAEHQwCGwMFCQ8JnAAFCwkIBwMFFQoJCAsF FgIDAQACHgECF4AACgkQc6Tye43UeTajmhAAtRyJhQVv4Qp3SnPnMNilhMvHLMWT p3WYxUkJ6MbxmIQnkm/oVVgy8OIaaS7TKsiOcp7m989cYc5A7tY4FuB7Yz9VByZC n+aQ4Ry8DP7aUXvV08erv9Fp0kUuZHGe8eFsMA2X6WA7gs1YcQlE3uzb0glUHOt8 5cCgOBWziAqNUrkkLio0Dtt75Mi5DDIJB9azq2Y6kpoXnvwA99h15qjeQ1lN7zlq FSbpau1UQFxC2/hOXFjRfj/kQFKWHI27HXZ+8D/T+z+IHs5/5IUduPvFn8SnhHUH GZf1wEVo1VQP1RBSQkuy0xvQqcXvk9FyiE+Mac83pyl43A5fcPbMDCfuYZ5HEk/e 22mlXTfUfPMjlYGIpPqMI4024dLq7DfKF9MP3o4c7CEMbeKSY6HyM7tyoA/QF2XV MgJndEgyoxH+JgsWYsuuWoYjc5khEwwll77sLy4VEc3+FfdjZ/jUULuV/4gp9Ngv ffHYIbNOVbR9AjP9wRGS8dMhj61SM68LnXwX/xGrf4wCDR6hr+FBH2/tzi8UHMLt XcjHz4DUwmr4TloSqzLa8gGhVsw5/lyNTLmr9sKrsdTm/+GvyDNAcCt+8hiCQL8B d9YzXsbcNHZ+xQT6XJzZF4Cd4yv+ipKXe7nVJxIAcx43r/ot7vdpnjJjCE8hLYz8 oN/bA1RG/+sxzQ3CwZUEEgEKAD8WIQT7+r21QbXclVvZum7bFs9bsSUlxAUCYAQy ziEaaHR0cDovL2dwZy5nYW5uZWZmLmRlL3BvbGljeS50eHQACgkQ2xbPW7ElJcSn dxAAiZFxjtM3OalPJ/VI8yF16lNHrHR1KMpSt9azMRMRvEx2B1LkNCxCFL+ZiIY4 SgXdG8pt4nRNRUwOh+mbPIxjTi6BU6jJbNEV/x0aZHMvthPXqzY5T3ZcfYxvvAm2 PiOE/T37Vj5OAlkmuEhBi9TA88wpjFiMzNvkhXxnjiezviAStsjADjqxJ8cipX4c Tcoqt9A+ftdEp8HkqMWewMBLkRWizDFW7uXCFXGcLvi6FnXAOvi4CU6g/VUkDhEx rqA0rRNXdmTJRNDCWEGH9i/2vafMHziEpBWDCLESSxpjt2X0YAEWr/NSWRfiygVk l23mC+Cgs8N5QUUb/w9BeO0kagaelCak28aHvfJRsdD7qObDlQdhWRWqXZlemEcH GyaMsVsZRDArPxe3y6OSeyR3c/cET/KalAsYhC7LL5YSjeVL8D7fgSpMahnmB09n mMztWFQ0XXMnvhBRZZfwM+GDeIxNhVUb+R1hgCibc/aMLZvzZXqF/urupWVAycVz qTD3vi5zrYFEZ0C6q+YzcHENHN0t2HyNlGFobiTmv0DQiuAu3Wcpor3zFAwaHIbZ iq6jhesJOq4vAjVTdVoYY/NhwSSe2EdaFuaDTh1CNnk0tpAKP/SxQ+3Odn7xQZ0w lKl4vFl3EiFv+dD+q0M2KlEjaoj/d8kunKPnO+A/kS1ene7CwXMEEAEKAB0WIQSA 6XbxSlCKSOnKP+m8NyJSyhz5ZAUCYAQikwAKCRC8NyJSyhz5ZBbSD/9MNWbfmO3L dXsOaqX0/aKgMhuxMZvVFMZ0ivWniAtlPID2NsnJWkSPJTBYdr8NsBYA4tiS4+O6 MUfzSvjf2Qpi9xvA1wDojc/ye91giSCnFPoDIXxgDaUtYHoxZDL3cGbRHZ+Tsns7 Ujv+68EuOzeluSWFLJCwNJo8P1UTmMn3NjAV1iRISpw6sLlcqq32mn17XL5BVNZW Je85yH2Xk1qcDt8wSk7nrG57QRhGddzF+i+jT+6cG5RwUnvPBiYVMCJwqUFaYStF xXefwNxNtMXnVnIwe2SrfQr9lBv9/0tVI3zU6b2CykDNGlQ/9cHchjpydulshzF2 ACmGbw7xuEyMLO2N88r3NzCGbMCmtbp/lL69+Xmd0YGE2QBwV0Qmw4NVU9hA1H6f F9SN/JlOXf+UQSl2FqCSOTDYM9K2lK0YoNbA2eYazVEw04oJm2cojWz6h4EXHGdQ VD78NhyNbP63btAgBk3Ha8Zs0PQkiMZ5t/ZYUWkNeOoXvFprSZF8JS27uPK40ACP d6D58BGx6upG6DKgey7ZCUYjbgcbIdA7TZ6d7qXv6fljczcW2CwtGM+UpQ/yBQ73 FbcGmLX6mdpLNszzmkrp2igfu2Po4X2k1dxIx/UWrSXUXD+AS34fdtfnWLZ4mRB8 ZzHcWv73SJtVixK0WH+5ezpsfjQzmryTHMLBcwQQAQoAHRYhBKxTDVIPLzJp9emD E6SESQRKrVxdBQJgBB/dAAoJEKSESQRKrVxd1WEQAKIOigIdl5WR/YqQrn7u8nXd U0ghMPNz9xTQvbIQC6f+A5Qk1Lwu6mD3keKEKu/aQ6wN1DSu86xAKwnW1ZRzcHJd 1HVjpjNIQ2j53KmPAtMjQSlzsUz1yfp1wSai4BGa9LbobIbC3nbtndiUmbYVtvn4 fGa6k2Qhtti+TzSy3wQ3lPEe3aVD+3BWr9F0kOO5f2N2Os6iaF4ZFffn99D5qry1 K0sg3IBFfLryUVkOUokHV5W5TaKfpvM71iJU/Sua6E0XvDiD6pXksqOVG3kQNqa7 AEESzPHm2+X1XydUxFkXK41F/8z+mNOy1z5wYz3QfL9gp76IV48jjYNaIFCkq1jQ OlOo7YDaEvlKJPJ/0/eejI6mLJO/7irqYaSgYlCTe60SHLMjmx4rmYi0YEdgyEk9 tnqnKvwsSYdPZdaC8Kl3VSM2lg7B6AFjD4NCvrBcbKgZBNx/NrUg5i88lHFmK3Er GyBSFNoLVbEsaEzUm2Wml/S58XOlxB7vKSnVL26WfedqF/W/6jihABb0EN6I8Hra a7/V59dViKa1EmvEz64/C1J2nAb7cnNAPPnkdgwqrsBMcP6GXPpwOSA9U1tcHSFJ fxuMuAY6nWns2e3cC6FpTHR9Tnnp+wpv53Nd0CYdo6jYngPPaPRvQSZo2PcYNF54 lq8UaowZvm+emPRqJ59AwsFzBBABCgAdFiEEXmGyFyZdqYB6I8X/TfqycMqpbfoF AmAEHxcACgkQTfqycMqpbfqFUg//Z0I0spxoc5QRdPNe5iTUR95tXlxHlnOb27hJ DB046YXLYnX4RIJguQMvw7yZTfhzYbC6GGW/SLeb8oZDxADOOlK6EOJnMfUo/yLi Ta1Be1ASxsfU1g8fu1ECAO/6wg50nTHx5F9Zf9Sg/Le8orE10YsFryJ+qRMJWUOn 1DoM8tnpmVO5dVJVkCP5FhW9oJpIAG/v40rW47CCSEWkAlk75GrIFOerwruaKm9v 29oFEl9DAUyG6LMxGcZ2Xtc9eaF/0Q/xFN8z69MOMJje73SwbamxcW57lYQwDyR+ M0YD3UwfRP59D3hRN+zsajpQuHkCTCqxa+MsNZ5+TuRzHp5ER511kB8DA0hPGREO TgdwUQvCenlM+YZNKFDXqXGoNIdg1A7L0nrPzoH9Gs4qXT94dlAjazanukVyjteC ZkfdNvC6abNi8Tp3I6aDylsO+Mw9dE7N4jtSLgjnPejIr0LcG57uaUIavy8jSpl7 CJDFb+6FtAmvxLlJe91FV6bcC8iSUxLRSYF4m5mPTUPWlSIu5iTi6KUfG6mD9xLJ fFKrZP49lLFfk5f1eGtioG6UdG+HJnxiQO0sq7QVkbVQ0in80r62ZLwvXgEHYs05 XBjN9q+Y3rDZ7ovx8gXEY19+tIFs8zHjnRHB6kUNu6opoEJPj4TF3yfbYXBtPU64 tWbfKyjCwXMEEAEKAB0WIQSA0Vgjt/0VYfn3vN3cMNfCPLur7gUCYAQe2wAKCRDc MNfCPLur7jSJD/9qbydgsDhU4DD/+XPwIyCT5TKfkdLa3ZoB0y96nnABU9UZllvl Zrl6g2HhGDkae6tW+XY3Q5ejnu8P29ceEmSigDen9Rp5UxGeKU2BJZ2mFbWXitFc VYLDqXx+sunSJWrjI7GiU5ZOtaakJgj5+BGs6SedqKpRLn5MfRrpEm0Umw7THvCM +ht1eFjq9+eEEjN1+cn+gf4SRhteG0w22lRPvlvLC0HXYGPpj4I9t6Xu73Iw7AEr tH23/nqumoXxbATGv07AmSZ+BWRJzLxI4FO/Ra/6Mb0lIZkpU2oIBNicqBr9CA4y 4Oashsh744yOBGGJJ9zNcFVZDtd30MzOHnrzb4MSIrtxrAGamgack2QRFHQ/e6qf y+f5NiQQem80dDSQhK3/0giIj0E5fdoDbrFoPdExKV2wLOrafrhjdMfBdfaFNPwU JgtICWt2VqpVqOyACwkeKZxsOc9WZh+rZiUF4SJFt155mKMo784qaAq1JuV7f/tn wnKXIIgaxnNi7rllive/lB9JFKYlg+wb8y8jV0ymKiEGz2jj8liumHr+6WBlUxoy rJacC/v4ywFIClc9GLTXeTWgiec762hS8VaHY12gBErMJCdV//GjOSFK/IBtVMzn yNNlCOwBUg5QAqLF5FXvFH31VSglgOtkpJ7CgT5weRdZg9N/TzXA8EN2Fc7BTQRg BB0MARAAvHIbiGfjtiqTxIoTTdq+40WUs/Q0VaDXj2TtyxUgNFk3KCJx/7pFIJkd Kvp6neBghf9/zGyapLJyTOch6sCAJGavlMOqztKwY4jIx604eiX6yiImqCC6eZfp yWRUVPYoA55yr/cDq7Dgzck2W8Zd5JG2wyVIdN1LTArabef75VWv21n/80WWadKe cA4BrOiYzc53vg17p8P3FF8UX06Xg1Lb2herAlLk2mwJTSd+jEQQAMVFUGYuIYVN sw3Qu0EvNyLOdOwa8qxk10EBp8Ro2BbpEVhj55N2WBBt0gvBe5TtMT6f4BSFVpax XSV0KM2UV1SFN17WV1MF/JykEX3yHN1XUqC4Te+klbrGY9KteC0esHMUcykwQE/n 0nlxGygPiQlTsh2EYacqpkZWahddk6pgNezyInvg8rJ7mOT3qig0c7p61fSUutvs cxUkhFeQ+K/c6PThAMRlzGvGepnRK7abxLn9IsrftbhGen8pNZqJ9Q8jTHIQR+yz P+L991e1r5e+hoxIfjMfNpdCdVhAOg30IffXaTQ349hIOXFJUyKcGfYuHUlOCFEO X5bDwamVw5Kg7yTyFE96ptcycLB+letTsY4+Qy/sr3ykBnHjtuiGBU0D5IZjzd5Q WWZDsx2EiJa7V14slPyNCgXPZo15bcPGA3Uu2rGHx70AEO9hN00AEQEAAcLDsgQY AQoAJhYhBB+JmD4Agf3gGPPMlnOk8nuN1Hk2BQJgBB0MAhsCBQkPCZwAAkAJEHOk 8nuN1Hk2wXQgBBkBCgAdFiEEpyNohvPMyq0Uiif4DphATThvodkFAmAEHQwACgkQ DphATThvodlthBAAs/kSbv49ox2eG3J4YRfukhKq6hk64EDhFXmhaQm0KNiO1arz n90NGijXsTMiTGEkJTKmcQvBq909U6sqYM2/fqat212Q57zGyb4hM3jDQRsyJ/5I HK9e1MS/S70IFh96rgQoKKDVhwKCVQ9pMmyZItlS/+T5351CG30VBoRST1lJiOlh 1TWBk6x/NVjcUzAulSUa2DomhAw5unGxb241JhRsQOXyYV9zWh3P6UXDeaFTs2i/ whbVA0yuDxi7qFjjJCTHDwDtuz4kHPxcpVBcw//Rlvk1E64hIj9u08OSZB9Tqu4P 3Vw9VdcbL0+W5Xn4T2DsKz9GVozxOYCnQ0BYpCLGc8eg7B9Ga6phNzRZa7JlJpB0 TnPSc8oH1Zz2cZR1mFeiiocAOubpNcWT/jDbESQoqd4ZcccEJoqE+NBGnBwqbrBU nSBKav1f0UsKOse74rkWvLlSRf6lZWxt0vaAjIorkW03HS6O4CVnET3g9K1BQLvi tnaz9pzpCpsPcUoRbWNxV7Uo8wer/r4nNpI3uKwiL6zqOxeDsr+xgyHknSHqF4yl mGgi4wapD3d2jID2tFrjvAsHiDaHVzaHHa4srVVLhIn37TK+Q2g0lJgbMWgxzRSc FWo/WnTexIHHB3oX1yzdu84TeNdeyVQFybLMU/3vV4ojkpzszBt9sziQWhZY4A// bubvNG5lKsCQUPZQJpsJRZsRDCni9p9xiOx0kW56edouGrJtB7jU/khe9wnC8MiP Mld+CurcjIGtY0j59nMKjcjtd5Yt2bcxxc/RD1f9SYwe5nRoGpzbo4e8Ufl/ah3+ HBzcb2wHrqwHj+t9YE4cxYkdOQ2fM0yWNMruQ8L1hPufgNhWt09Npy9euHYyQcbP WESDOeVbdXMXUyTipgGRZ4ilx1fAcN8dcNhYhVda2sSKoEyMsMfwGnHmd573wq8V ClPMxtT2UTWsidKyTDv6IZE40QFdwazd9EkHw28AuxgynQxTuHHfBBaZf2rnyZvi pe/KkWf4ipM643UBUKpJUQOf6Bx16EIby+SM4gowA18oSpGttCZjsIm0jgHGQnC1 QJNU9pENV1bdFO/grwrwoAz6FXPuucVhM42I7nXI8IK5UdJD2K9m/kaiFvIM7mc1 O97WejoXOXmX8JKlvlcK85wYy+AxY/baeUsahI0biDdNaVRpvGteY8QA0bsP6oSG G55t4ULo6lfaLUmBgSlSChMW0UL3rb30rfwlJZEr/HQU+KDPG9STFW3usbcA8g8m VhCFhGrauYMDjNFAv9kO0UX35Acgi8wOVhhkIsqXGgSNiUK6wkuLAeI7m6IcOItE DW+7Wp4lUnyjjfmD+BEskAtmiNnVQqHArYFWGpOYJZvGwU0EYCgSTgEQAMbo1DQT HmDLQ6WcaUVmxOmJlXYuJSEAIWPpkdURhBA1aASBGdm5l4BdtOcXLDOslRiHHnWk 2PF+hWI3/gnKfagyuZoES4PETCFVkOwxPNv13HQw2eaMyvjnP/gQaOWfTu0XN6z4 n7R5WLMk5Z6FDjQgTDIJkgb1A5+wLV7V6y2bQotkpZviBOKEYVJqEzjN4PpeVnyS tOWRZX2Lv6hq0KQ3OK45N/z5lcSqg2b90nEbqkZZxqWj4OJMTeXgcIWvoxmiIKhZ VopifWO0hR6KgNVbzM9qW3dVw+aO9hvpk0eEUrx3cMNXXvC/tMUkg6fWPafzaPNs NnBABYgDqIo+NcMkmqa/ipxnlrmXyqKuGNmFAR6Z+1iwYEtOPH0A189wMWhDr2iM PDfg1yGKDacn7u632HmaN4H1CY7HXQcOmixFpmrzI8KEWmF4c2UrCH09htrRn1Vv eYjTjLCTDWgZzphAP9UaGgaIx6ZvMQrXXQOwJxl6cPl05Ye6DksXtBMf7XbR4bhC 3RFw8uojff7cVQ6PM5C5vDRABNizwPDYTfotU5THXKu0OqMmz9ld+v4sJ9plGyG9 h9yXeygcJfFWoTChdSbEkrvjVFLh6PzDv1yWeJk8wtzrxekVnuqwqgoEd6gbWXxe +4Cn0Slbi+6Ox6ETBG52fVI2hzl9MUb2TyqVABEBAAHNSURlYmlhbiBTdGFibGUg UmVsZWFzZSBLZXkgKDExL2J1bGxzZXllKSA8ZGViaWFuLXJlbGVhc2VAbGlzdHMu ZGViaWFuLm9yZz7CwZQEEwEKAD4WIQSkKFKV/HsagWAAYqlgXGbwDWyXkwUCYCgS TgIbAwUJDwmcAAULCQgHAgYVCgkICwIEFgIDAQIeAQIXgAAKCRBgXGbwDWyXk61s EAC4j1Xy7O0Ff6BmG8jsthFmS5bOY1r+qIMYcAXoMgA5gqoJlQNKuQX2qwiR55l0 Q/uEugijpOSmu8XmIsVN/MVY9PVVH0T+K0EHMRlrZWuBPTJ7yBiuxF02Syja+plB 3ea4Mm+D1StPfv423R46kUB9zclh+rCOFUieCI0vCMZMLmiKcR6x0MKCrGjxnp7o 3Iwt0O1LhbbqrsbFy4blhwGhiLemPEnHxkaw0eoR7pG9TdgZqESBZZjzTEJFqubx FViB9Tgtl+boPijVk7cHZSYrr5OCRONhbML/IytxsriXK6YHfo4JKDxvKsnepQ3v MopTQMjE+oUUIvHGFjF/hCsw+KTQFOCWhae0/ZUUtSjcPElAoCpBQuv4f9YSBuyA C5TxTRUtOWP2wJefSI3SkfM1oQknizPmZ0hY50kmhuRuJBP7EAbB+goN9/2X1+7z annr2wr1T6iYcy4N/FNPRfXdT/p3abmcgXB/UyK2yFTx+tjG5mzcunRCLVeyq3Bz /CsgO3lzQzjCW7Km7P/95EsvCwyfooSXX8F79F5sdJsUo4Da12xz+F8AGxVAIWlE eAGakt8v/lDB38rt0oqm6H/ulXgHVw2C+IrAwWGFHZ4bNN9terHK2diYmRX75sou 1fVKQ3HVMFviWogLN0U5XlCO2M8FVlHtSDHuynvJmfhvDcJ1BBAWCAAdFiEEymGd Zacqe638ltKAGWQYqut0yKEFAmA2qJwACgkQGWQYqut0yKEeNQEAi3LPlpWmU2dK 4P3FobwKYtdpmaV0AutG8b5q7U8hx+ABANqSS3xlRzrmpUQjW3hhGI3wtFogvhGA gtzQVXtfLJcPwsFzBBABCgAdFiEEClW3xRIjOUKG7HTDU5RHndNSTFEFAmA2p6YA CgkQU5RHndNSTFFhJQ/9G30VPJshfA5boUTcs8Xsa8RZf/+WjEpGGIFJDwXCaR1y HPeIlIBxnweBwhIwfk/vsinxT3PwYC5zLjoGLu2xqKxY5RXG7mC+HrMu6Ls+QJG5 sjFwLED4mNfmhmbctLLj2zVIQL+jQwHhNHCmpF+4Si2zofRn14fO8kqOh5f0q7tK N9JJFZjdNYwwiNAEBNFkZxOaADeInj/10WYEaxiFCrVaxjtsKYJqF6YCYCR0LGQH eGvwEDUOb8YeAvT763EmGMIXAqPz3KH46vyX8L3mqm1La4DGKgMz33nYNSbrmha0 CmwWzxWlwnFJ0CmZaYfizz8H8RI8q/tqkurcFKAvLhQDgV0XP5dL3HDcYi1w+6fM ZlB8joN3mM9QzBhipyvVxUHa9OJAH/+yY2NarayRZMoG2OTbGk84wIoLmZ0Yjjrt FPwekMxwpZRuDWRo+oILOlD00lC6T4z36PDvwwQu1QZWLzAhlJkKNHbNd256fJVR aZBMdI19LZHeJaxgfqbt8vaID2PhmLuwSN98ZsuRB9Cof5zhcENY3MmcL7+NB5Yo b77Nxi0Qe/SaTftqDDaoH0dB1M7bEYlx80X516u8CPU7Z+9qw4yP1qm1FlWkpUyz bnNuSmUFHmao9e96f5KtoM5Bvl+mlw+so+3zwbd750G4HMSU5cFakiwwm6+60WfC wXMEEAEIAB0WIQRyA2MOLI5yclFoT+vFzl3CxULNWQUCYCgSfgAKCRDFzl3CxULN WR3gD/wLYa1UBOMszWu/BTLt42QHcd6onTTboP4S9w1Gs/ak5iQiEN45CVVLbJ5w S1iaeuMZ85fOtcEvJ9KqMvwvGXlsCD/+O0QJJbEpeJpHarj4ZtxaL659ipciqeSI QAsAb6/9SKZZ7HGQFD6DAF9kzV9HpKnNvE8BGQ8I38Ez9lfRiQuD16r4cqNgS076 Z1AoQU8ES5N8VO5v1fbAHsyLq9ZToE28BKGU4o59Fj5uqpfDrm0DrnSn053jUK94 2IGmIwKtUAn/j2sG9mcow47xjifVTKuMXyNGDM30n6ITRtiTaZsUZGIw/yKM3Zos uxobxvJoef8B43MpEHYV/xZHYxegT3xlu5h8FlUQrr/WR7FtT7Awlapm6llIa/2G 0nrPhQlX5nN5gJiKO92rOvKM4wTadBjL41jfYZb5EE44T51hCpJUB1g2GSQkUpYM /MgcNfqmq7+7bAxinej/iCzhziv925mUOhIGhAUEYCZMFI4tIEVFFAUb4pi1CtXo 3V8DJRu5TkuETwDdK+FfBU2e3q7b0q/CTHdHfD8T7VuTaYm01meCqWG8HS3h2OSg tWrUgDBDKAU3O83KK7n6K+SAXW1iOaUzW9GErZnYqlEOMJVQn1pU+txUAAdTfQy0 5mUvCUvHo5VOcC3wybU5HCZJ3cWe6HCOviBheeIysa+iSvBAUw== =PLZd -----END PGP PUBLIC KEY BLOCK----- �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/keys/about-to-expire-private.pgp�����������������������������������0000644�0000000�0000000�00000000375�10461020230�0023176�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������X[ +G@fcIteؑI M,H/��"׻Y! P,A`/{ c6[y"About To Expire�8   !. s" V(Y[O� " V(Y;� @d6\ bA;as�@xLÊ 1\&sך�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/keys/about-to-expire.expired.pgp�����������������������������������0000644�0000000�0000000�00000000336�10461020230�0023162�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������3[ +G@fcIteؑI M,H/ǴAbout To Expire�>!. s" V(Y[ �Q   � " V(Y@�-6AK 8U8[ϸf^H�ÃV񞩇(^vzUWp^)F<M8��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/keys/about-to-expire.update-no-uid.pgp�����������������������������0000644�0000000�0000000�00000000307�10461020230�0024173�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������3[ +G@fcIteؑI M,H/Lj�8   !. s" V(Y[O� " V(Y;� @d6\ bA;as�@xLÊ 1\&sך�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/keys/already-revoked-direct-revocation.pgp�������������������������0000644�0000000�0000000�00000002772�10461020230�0025202�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������ [k�6;nA7W?(O=f6PT_.V(΋Î`Zm(+SGg"yX@.ft&+pKfsl{ܘaMgW.͚q#bqN/#g4~?r mb+'xwCXjVx\}yXpwMJJ]޾p:ҵr:�Kէÿ=Iɩujӓq{vro)��Already RevokedT�>!$06ק"hI[k g�  � ק"hIG|(tf].*!醛P5:2K~PfHo}qIRY8aފ561\s'S?-1wE/J>&jd HNdN0  #K9�( Q՛G2T"O5CPC~4uNt;usIgՖ{N!ە)EqIdPc}Q&pIqJ٦HQw{xͯr%H [k�i� "=E ŦwƃsuQ~ty7~G1)hd piz՗UNۣX*Fnչy4x?RN{!ZX }\F; cGimѕT*k{cwwеHli" ?zZ8AIZg$H Z^ |$<9N<$tbZ%̲T +?šZ*��6� !$06ק"hI[k � ק"hI(uG>}7\؝ͭ-�]F\p@~A�y1=8Y"$w{~A _J >N4 /&~ooB2?z0WE}PX8\iMN�X?zTANo|S5qC.sCBrkg >GY/^ty"_#-\"Ƚ%';>,16[>2"y4 yg) 6 � !$06ק"hI[k�� ק"hI�QT6hSR-8aiiӕĜx֖3i. Cgh| ` J (W?v~S.AҨJ|~ PX}:G$i�~vuςv-M vot|i"qeLbO!P+^:ʌ4 T' <3?'e1{PW7`D;V,\Јl>pPIn ������sequoia-openpgp-2.0.0/tests/data/keys/already-revoked-private.pgp�����������������������������������0000644�0000000�0000000�00000004727�10461020230�0023235�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������[k�6;nA7W?(O=f6PT_.V(΋Î`Zm(+SGg"yX@.ft&+pKfsl{ܘaMgW.͚q#bqN/#g4~?r mb+'xwCXjVx\}yXpwMJJ]޾p:ҵr:�Kէÿ=Iɩujӓq{vro)���^ )/yL㴋kn)L&!Npb&] e6VLa<#JS}SuO .hn,j_IgݟJE _įW.BSJiТ>3�HVD/4"<թA؀PPܯCS"浈u8B JS|ȂC6CY(XD<�?noJJ#URȜ-NJȸìϼ,`*?)r d4~z[vvX[< )ph}dIk6AEkdfd›,8Njj_0HmG'�kh">̴QZ;ͨ{ՄS\~(VVyHF<!%ɸF. h;e{qf'i( <,G4e|͡%H,/*Uq~/�ǵ*xL79tCY.)T#3:=rK$Ehi*Uo7TvM @ښ)}R-: kpgxR|bבrC~L}Already RevokedT�>!$06ק"hI[k g�  � ק"hIG|(tf].*!醛P5:2K~PfHo}qIRY8aފ561\s'S?-1wE/J>&jd HNdN0  #K9�( Q՛G2T"O5CPC~4uNt;usIgՖ{N!ە)EqIdPc}Q&pIqJ٦HQw{xͯr%H[k�i� "=E ŦwƃsuQ~ty7~G1)hd piz՗UNۣX*Fnչy4x?RN{!ZX }\F; cGimѕT*k{cwwеHli" ?zZ8AIZg$H Z^ |$<9N<$tbZ%̲T +?šZ*���0S`ڶ~WHkV*R Vzll6gMϊ У0{GQ~H:c%]FT_b~KpTVG_.h&';e[OI]G~UCOB^ ] )6aWӐQd;e<4vػTG8qli1�ad9s>͊pjVzl/0mq:6:Ie>5�7>q{@ : /*W-`T(C+pa>BXBQ˳v&e%wQ\g! ,-cI  !kMUJᄋ@�`rFz2N~<*Baܨt?Kx,ސhN= #s"Cidԡ"F"%B9zs|;.*WZ^ ]Q�[5L#YQ2V3$cSڙ W0:N<[EMMBoS~!=BBr 60Wpgϲ\yi׶}@8Lxr@`z=)"|dRm|JnA6� !$06ק"hI[k � ק"hI(uG>}7\؝ͭ-�]F\p@~A�y1=8Y"$w{~A _J >N4 /&~ooB2?z0WE}PX8\iMN�X?zTANo|S5qC.sCBrkg >GY/^ty"_#-\"Ƚ%';>,16[>2"y4 yg) �����������������������������������������sequoia-openpgp-2.0.0/tests/data/keys/already-revoked-subkey-revocation.pgp�������������������������0000644�0000000�0000000�00000002772�10461020230�0025232�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������ [k�6;nA7W?(O=f6PT_.V(΋Î`Zm(+SGg"yX@.ft&+pKfsl{ܘaMgW.͚q#bqN/#g4~?r mb+'xwCXjVx\}yXpwMJJ]޾p:ҵr:�Kէÿ=Iɩujӓq{vro)��Already RevokedT�>!$06ק"hI[k g�  � ק"hIG|(tf].*!醛P5:2K~PfHo}qIRY8aފ561\s'S?-1wE/J>&jd HNdN0  #K9�( Q՛G2T"O5CPC~4uNt;usIgՖ{N!ە)EqIdPc}Q&pIqJ٦HQw{xͯr%H [k�i� "=E ŦwƃsuQ~ty7~G1)hd piz՗UNۣX*Fnչy4x?RN{!ZX }\F; cGimѕT*k{cwwеHli" ?zZ8AIZg$H Z^ |$<9N<$tbZ%̲T +?šZ*��6(� !$06ק"hI[{�� ק"hI!.#-S+0oZ&M,Es(O?uLr1b' !6UL ZxE& `љ+oضOygFB)2YaX|xYCB]<EW>* cgS2YѸU|vM WK\5ɥ/7,3G6)/G]]X`X2gXҘ@ػ '!Z6� !$06ק"hI[k � ק"hI(uG>}7\؝ͭ-�]F\p@~A�y1=8Y"$w{~A _J >N4 /&~ooB2?z0WE}PX8\iMN�X?zTANo|S5qC.sCBrkg >GY/^ty"_#-\"Ƚ%';>,16[>2"y4 yg) ������sequoia-openpgp-2.0.0/tests/data/keys/already-revoked-userid-revocation.pgp�������������������������0000644�0000000�0000000�00000002772�10461020230�0025223�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������ [k�6;nA7W?(O=f6PT_.V(΋Î`Zm(+SGg"yX@.ft&+pKfsl{ܘaMgW.͚q#bqN/#g4~?r mb+'xwCXjVx\}yXpwMJJ]޾p:ҵr:�Kէÿ=Iɩujӓq{vro)��Already Revoked60� !$06ק"hI[�� ק"hI�Ro00^@鉲èhgGYKc$rՍZ4]9#�mDr7˕Xr@p1aE\~@DrF -uqےO|-ѽ1wTC >K[U |{йbYyh>IW>,"|UŕdoɅP@x=: ߵY]=Q,W.o?. j۝ב>v9{^!𷩳 T�>!$06ק"hI[k g�  � ק"hIG|(tf].*!醛P5:2K~PfHo}qIRY8aފ561\s'S?-1wE/J>&jd HNdN0  #K9�( Q՛G2T"O5CPC~4uNt;usIgՖ{N!ە)EqIdPc}Q&pIqJ٦HQw{xͯr%H [k�i� "=E ŦwƃsuQ~ty7~G1)hd piz՗UNۣX*Fnչy4x?RN{!ZX }\F; cGimѕT*k{cwwеHli" ?zZ8AIZg$H Z^ |$<9N<$tbZ%̲T +?šZ*��6� !$06ק"hI[k � ק"hI(uG>}7\؝ͭ-�]F\p@~A�y1=8Y"$w{~A _J >N4 /&~ooB2?z0WE}PX8\iMN�X?zTANo|S5qC.sCBrkg >GY/^ty"_#-\"Ƚ%';>,16[>2"y4 yg) ������sequoia-openpgp-2.0.0/tests/data/keys/already-revoked.pgp�������������������������������������������0000644�0000000�0000000�00000002301�10461020230�0021547�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������ [k�6;nA7W?(O=f6PT_.V(΋Î`Zm(+SGg"yX@.ft&+pKfsl{ܘaMgW.͚q#bqN/#g4~?r mb+'xwCXjVx\}yXpwMJJ]޾p:ҵr:�Kէÿ=Iɩujӓq{vro)��Already RevokedT�>!$06ק"hI[k g�  � ק"hIG|(tf].*!醛P5:2K~PfHo}qIRY8aފ561\s'S?-1wE/J>&jd HNdN0  #K9�( Q՛G2T"O5CPC~4uNt;usIgՖ{N!ە)EqIdPc}Q&pIqJ٦HQw{xͯr%H [k�i� "=E ŦwƃsuQ~ty7~G1)hd piz՗UNۣX*Fnչy4x?RN{!ZX }\F; cGimѕT*k{cwwеHli" ?zZ8AIZg$H Z^ |$<9N<$tbZ%̲T +?šZ*��6� !$06ק"hI[k � ק"hI(uG>}7\؝ͭ-�]F\p@~A�y1=8Y"$w{~A _J >N4 /&~ooB2?z0WE}PX8\iMN�X?zTANo|S5qC.sCBrkg >GY/^ty"_#-\"Ƚ%';>,16[>2"y4 yg) �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/keys/already-revoked.rev�������������������������������������������0000644�0000000�0000000�00000001044�10461020230�0021560�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������-----BEGIN PGP PUBLIC KEY BLOCK----- Comment: This is a revocation certificate iQE2BCABCAAgFiEEuiQRBRzb8RaZMLw29NenlCJoukkFAluba40CHQAACgkQ9Nen lCJoukm8mggA2VFUNmj5l4XzU1LFEy2nOGFplgObjGmbx+S305X/zsSc7AgReNaW 5zPJaS4NQ2fnlPRo4HwLtrzRDQufEWAL9ZFK9wkoq/pX5D92flMuQaq5oNKoHkp8 99N+DP0g+MbLD8r9UFinpxH0tn06xR1HJGkAf37rA3Z1z4IVtHbXLerrTeOnBP8M doW2+/3bb+p0fPdpywUiunF/9/Vl5pr8sxafTGJPhCHUUCuHXjrKjKsX/zT+DL+b VAQnDPI8M+783RPYHd4/J2WTsPesMad7uhwIGKUbt9v3UFfIN2DvwwP26/JEO/lW LOJc69CIx8oTHdBs+sw+cKTXUO5JboGgDA== =TsDa -----END PGP PUBLIC KEY BLOCK----- ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/keys/bad-subkey-keyring.pgp����������������������������������������0000644�0000000�0000000�00000043220�10461020230�0022172�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������ YoJP}K5E8n[џ QZ6&JN=D1'q&_ .!hJ )+ |ӭIJ11S#ʡu>5'KKW-YQomo&'<lv4%EIvyKvI)PEU\i겂ɾ"wAw�\ɝȞl\!*?wꖽ0=gO\)9Ӗ 0[_ �1v>_1LwBmrW@rzm5C)f-*XB@SIZn7[9rikj$W9"ʹֆ$R.74y-]p'na~z*klZE݈+l.T''~N(Zvbp/G]ވIM,` dg, } aJp qH|nsnQS8Vl뫫q_.@5��+Justus Winter <teythoon@avior.uberspace.de>M �;!͏e>e}3TJYo f    � }3TJ,kc)(`\C \?jldH]la,jTEl}6;k~�q<l$B܌THi1 NYS0#ǔ C7- #lĒcp0p+%˴r͎~Qf'Z V!q JT xy@VoʨhP98pf!b3nfyu<AHj6(?_j  >Cv8nG=^ƅ !,Z<5ɫ*S4Lo c }FDTTAwh+pqK}ߟa * ֙Z€|:WʿW*;SL]Gnn5{v!6poKO}d](~pnb {N2d=F@zP+0TD6\  M蟜Fu[�Fbwu;zi/J9oH[M?QZI XLK %Īy(,<񜙴#Justus Winter <justuswinter@gmx.de>M �;!͏e>e}3TJYo f    � }3TJT0|\hagL<SjkPM~e?:c3pwDJ:F_ GNj1*f 6{^A_}-UiDNE3$Mfp�* AFu8,Xڋ oKiYҒZ,=|[bXn=s)qOeZ7Bd.kX-.\GyKL 7�}B1M7N'Ftc_hq Bm%<mo*vh.�"v>êu; y9w:AjAkjb7$X访9�%\@ .4D)]Ũ[87S5ണ[S@brDsy{)7c [VmŨq0F,f?qm#0aIHs5<יtbGwxuJS0C=SH9 M6ѵˤmSgkHYp&fy Yo�c *. U{ Na uwٌ; L9;b .GTh3M{`] NPqiU~aq 8\|d0\ye+HFs] Hni圑J}%2-S;1R~aqok"QdVOeaDz $"g<"3 BAojLZIKc*},>+Zc\JϷ g=Zf��n �&!͏e>e}3TJYo g�@ }3TJt  �!%jNU-$h~38_yYo� ~38_yfV!Ok;_ѫxf/BqB_ͱ% L[xH~O OIHq=xgu(�oP[8<k8k:cr0oC-G,gnz<+ Bce =,q^D`)6Eemd)YSgFša$fb^-`B.m#'b)r ϓä=cs%/O}a y#yWhJmZ�IkEζf f3<O#$3#o,r6�n!`VBLuaR}87HCqlXVr!##kvpiUB,D1~�T JQtj@5Ygב6QqH7U<E:摇h軬pͶu[IsNTK{-b qG8ށ1(bLᴽ #@4gy'z{&„Q~67ja""fp,3>2y"B�3b3 >M6�6{S۔qbgA9tQ;c4 ߞ0+qnd7.| N�59d=9^Lh目(O}«Uꩠ#4(J|ah_`@15IVo^`R^0ug+h|iggV-2v ;k U,-Ak ZLZ Yo�)ajK?ù9v#hAF6ܦ3c['c6qd5 #XV/=D2Czw͊5+(|S:Dav\"'<d́LkR5N@[ZsY5h5CFAEyr;=wɠ'A<8LlH #ag,Ӳ Ww,!fPVsj?H ŒXeSjjgT��8 �&!͏e>e}3TJYo  g�� }3TJOt=g壞MY7=VY|F~r `I+dC�}y_qBVir<|[cGW^ZGrI��D)6`-?ĭqzCi鰺0Acpv׹@d;Ƶ܏܃^فހ׌Y(.FPAw>>:xO(S3Bf5ho`Z ٪#"K ~T8c]”+ 37%+&1@3b>9%9-nsKop \{W#= Gg> zucz e]>^7KwrWx3Xā&Oލp4݇ e]!9Ac\i+Ehu`iMQI?BU_M%keRN"Hge.]XV'4zK+ #8 1esOnh=;q^"ǣ[7\ Yo�sɕ᪜`C1Gf ~ljCIYJ=xt1\>=7&aW($rv~gO>|;gw_&B =!m[^S2owg&lQ skʖ==khc;}5~#P}(u%lS^zYjOD[<>$\ovVX焲Ÿ!lpo'-h=snBna-￀ dOm��8 �&!͏e>e}3TJYo  g�� }3TJ\<=s R=1Cu؆<e_&9\?}sm/ hwSA;=J" W =}C%Zh[$347ٟt<yu.+ '@DN1_h3&`ZrRFol!P=$;Rr]\7RU(3_@c: wJTo6ZӾb}8Ȟ9@ ЄtSN+c+{l5d>to滰_8:6*!*f@R75bQ;Li'�k =k'\BtmD;]A@(X�ʈ9cq%w նPD6U[1(mZCϮW-Rpũ_` NgY)b2+ .QilHJ~DMN 6Xܮ'<8H`GϏԛMwJR֖ P3? (ZT U#�5Q$_|p|CxTsls_yAHxeYZvS2j[ױØ;?|4%߯š p-xc a!Л&d #$݀['yX2}H& ۨ9=[@S I~GP=LqW?2$c?rrw(eQ+9]:ߤ0 \.NBMenO]U�� �U# g�) 2Cc�R]  �U#� r#fx%(yΆ>H ܖ]Wk>-N$`$48b觶FRL[Pэ 4ڀ9R W6 փ玮d)~6mR#2C#?5dȏˤ>WX|ьk))`ZB1w$1Q#3Qz6M} dQ<\^U&v8c&jPb{10|iX #I'TJ+ɖx1 2٤'ELOOH ߅5cF€dQ|G/5E01uƐl="HS\n|zU f0 Q:# lP$PŦ"JE&Mb:ʏp ,_'N'Pkڛ3<"54m;XQlz3 f묟CQɻ[7qX%ΝFغfJ1Zԣ|Ef4bI29֠^)Ӵ oY\/9+M~%贐\%�]] h5NDu=sǪ ⅤzY<A#_s}qG" C&˥>g>' ]P i   4y cauQfU8ڐ^뎼 rLU} X%Hm"E^$8x0bq#-=Fʼn �X#  )]  �U#� r#fx%(yΆ>H ܖ]Wk>-N$`$48b觶FRL[Pэ 4ڀ9R W6 փ玮d)~6mR#2C#?5dȏˤ>WX|ьk))`ZB1w$1Q#3Qz6M} dQ<\^U&v8c&jPb{10|iX #I'TJ+ɖx1 2٤ 2Cc�R0?友rɳ~PFbJ=vj/nI {<\+}9ٚ/H0qJԚpd FE᳐k/ЦS10[[ܾ؉z/ǀvԧwK((V.lwdtNr%oC/mv`~3 HV+Vz1mĀcW iPϕ=E8ZzNdlkh<O$AE!&".tȻ'*|?a5/6@vXzpP ·ذ(Vx@lc9Swp%N7z3�DU3?cr5 btYq�DсSq/x7)~Vhxip D (j<V/Tj98Ne?P$Օ' U#�RGWJ$y&IMڌP=�,szz1IP}1>(TOѻner/&3[ScLT] }4 (3FC'8�}9}B06>)9 2"^2DWby8C:MSГdjlߏ3챆)>> SvtرuVТB #:!o8|/'CD�-`(f-3�� �U#  g�� 2Cc�Rم]qbON@6>Hܐ<bhȘ H/tDwk 0?T#uM ~  P; SUED1vV_'ԀBoO䪢=A5ǁ7XNJIܗ$;\�|-2<U.qL\1WiP: 0FnQfLM|XyI0hHG)RHӸ ͺEJNN&+-+^IŊϴ�(+@m| l zP3/'$7 :2Dm-koׁl, *rF{t#w]U):7`9rOoIɹ~yqQ- =x@`jnVxtG7,jG|Ă0Q6{{eh7™,v>ŌE � X# #� 2Cc�RS-`KsAN.) $Xt@F18iVȘY%䠠y"^ApJԛW(8)g<ܜg#$1�]'_e5ef;}GI~?fLE K6*I~XiIb\t؀@ffkjGL>47�_;B5dwEц0N@-ɇQ1/t~i`]١lهkO758.,{2wdӱU"TowI| -H:G"=qbk,)td.b5 ^k @Sr}*ISЦ$N%T 0QZ<eL)s3Kg^fF@ɦ�AҨOS7bP;-Z}y6͢_)Dg]Ϲ U#�֭�ݣ#x [s`T@IhZ>-Wp>- OkеH)AZ6G:?D P/?%+&e_Hon1~-W=Xf.y:6d`pm1l+* &N_(ob2 v }.\To(M U::k;L<w^WMM-*96ogOx{.W ?Oy\G[ uЌ1Fp<�� �U#  g�� 2Cc�Rd$#7ђ̠MLJhHT"m|k'rӗu4d̊eA"Zړ0R"*oj<L K9 _dXXf%L(RTx{nFB)h%Hh<c8JHYE͔p)Ek/XsNNP"PWAeqR7oǟQ| o9""BwSr!d#_V ,+~ zUh ^.sNT%(<<7 UƥH0k=S2ze;&~>/'D(/,H5nvm8:8wB�F(Hi.5nT-tዐl9[! I_18ukM<Xe۴6eB^yaoMyEfX8olOgV>trr$!4Г+}ow � X# � 2Cc�RrQy-c@ygճ1CWsk,L [4ykX/GpK]hϤ?uS|W3HNS~|+!Qp~)gC^Ji"jJNopvLrQ9 '�~*ud"$S瓵4DE+zd=&F~=Osb<8'}w &:˦u#0@"=XT %Rx\7ll%{wLR  K(&W=*Es@yALLDV*mŁz/%;,Ui6uuu݋u_7gE&rqh$,9?)6J )%2xH1Q8DcJEJ$0n[|G²L#㦫tYI)kMX![v=;wtNkQ( �>    !wq=ڛb2Cc�RY6{ L� 2Cc�Rك+M4<ZLh 4<Anqt ¬`#>ޒ=.#Шw3|շA 4"o)WlYn! n]`tq؀?KjTSKc:X8L':@(�a'<! kOCKZz[o4)Sg[Նvq@�PVmRbJleWG9qdET5 Nl<ל@)s{~][4RɒҘ d~*IL#`=1/?lȴ'7AQ 0Haݒv&eXژ6Jx;t/i.OXX "-p7ly0)&-a>@j Q\ 1�^оeS<t9Y!Qi o蕩 p?ueom^{-vM&HDL~OU#R;U1RLo\G^zO`DR#2ܰ5q1~l߈J^jgL%<yxQz,N܉ў[~W ݳ.T'D6NOmV6|j&umr~Nu8>XC]?}]$ *%|ic8x1"R2'bעo{i(1< T dvWBʰűٺzv/'hrd@w1z<+͙aыfb{f?A'[}*P.6. ~(|M褮 =&_{]gAS#wq; p]B3U�x!g6}(!ScOҽ~&URgEXґ!,-=֑jiA.f= B+Rڔ*- ?DG��$Neal H. Walfield <neal@walfield.org>( �>    !wq=ڛb2Cc�RY6{ L� 2Cc�Rك+M4<ZLh 4<Anqt ¬`#>ޒ=.#Шw3|շA 4"o)WlYn! n]`tq؀?KjTSKc:X8L':@(�a'<! kOCKZz[o4)Sg[Նvq@�PVmRbJleWG9qdET5 Nl<ל@)s{~][4RɒҘ d~*IL#`=1/?lȴ'7AQ 0Haݒv&eXژ6Jx;t/i.OXX "-p7ly0)&-a>@j Q\ 1�^оeS<t9Y!Qi o蕩 p?ueom^{-vM&HDL~O!Neal H. Walfield <neal@gnupg.org>% �;    !wq=ڛb2Cc�RY6{ L� 2Cc�Rٷ-pȿd4\<ɰe #~5g,jO,a-uzC<[٧Q;>G5aG)x1*]T{S&k4-zJ=r.UVt*aof-/XGOiGֆP}XkmTW̕1rKqHww^uyIJ t]wO\_*;K:{eۖ$z ;Ǜۀ }AzI$sc尽&ʅ xK_ˆFruQV^Oy'O0;챐z҆%2/R@cĻK 8T+3 $eyE`+Z49M.7^e>q< <%J8&ߕkS}SwMA[]ӌ#Neal H. Walfield <neal@g10code.com> 0 � !wq=ڛb2Cc�RY5 � 2Cc�R<qmE*\bsp!qLzRb(i/4!j"b8E,7X�@/kme qXy2kd/A[eI˪ݕ -:g gs>@:z#pʀmO^ӀރhrYH, F4@uL5R_l(B#PĊuVr2.Z |Ed)K<UCeв?L� &mZ?K<Dh)xuC!ܥeC|Z9H`p/ri�zC(/wTSbH}}L=: mɘ@o:P>3Z\15W{ cHnzZ_UyŲß_U8v矜nByA #3BB:b#շ73+ ==&J{r_&Neal H. Walfield <neal@pep.foundation>% �;    !wq=ڛb2Cc�RY6{ L� 2Cc�R$4 P뀁W M;8LtW-*ҹ,Vҿs0+Q3ԗLsbя߲ nީ[w-~pJZ\) #(y7t=wKKk Z}9AB#tRBD!8{=w'U58z![ī�Xop~<@?9/6K<Ottlq <wHQwO8tbz6rnzAl7lrDfY 4pNKt*P/9E 0X[F%ED/98mw cՉGC>RJO$?֪c)I4mQ{Ta%,gVL_fZK< mXcNYjegVKߊMu7<vOՅle|;;L&M jзDOFRwh5xnL5}:N'Neal H. Walfield <neal@pep-project.org>% �;    !wq=ڛb2Cc�RY6| L� 2Cc�RH} (&*(.txc:ྪ!ѴL]<. Aa�j|ijwEyꓥ/|,mcw*a~v=xw:g ]judHnuX�1tlǩM06z(l)tDgϱd< A΁B'wqWy}\ŅgY5E;*=RIFp=.)L�yz4%&fO1nxFu;{s0:܌[zǪHO.*%8.*+JtqAu]et#뼊9 4, +5[rt˙O9|L.j5!BC<:feb1ܬ~/ٝǏzia-ͻB'| p+9=,m,LRd'Neal H. Walfield <neal@sequoia-pgp.org>% �;!wq=ڛb2Cc�RZ<  L    � 2Cc�RyP-vΦ&ʾHEiFdn6mZE#H"�S0R¤O`?>`ՄM-nG<KhEvϬdN8GLQ@-.`\NbfY7�?yz:﬑%g G#}Lg2Zze\b9l<y̒+!_BƠk:=M;F̐nsÒCt%CﶺPOP͛"ɫ�J;]cB;mۜ$ɶËj(QKSc[QQy6솏6-:QiYjpc„ˎfwfuQ#-D22J(Α>S#=! Gi xP(,6zU 8W,poj4t R�_1k R+GebW}Y^;sfWn\5NvL>Fs0D U#�5Q$_|p|CxTsls_yAHxeYZvS2j[ױØ;?|4%߯š p-xc a!Л&d #$݀['yX2}H& ۨ9=[@S I~GP=LqW?2$c?rrw(eQ+9]:ߤ0 \.NBMenO]U�� �U# g�) 2Cc�R]  �U#� r#fx%(yΆ>H ܖ]Wk>-N$`$48b觶FRL[Pэ 4ڀ9R W6 փ玮d)~6mR#2C#?5dȏˤ>WX|ьk))`ZB1w$1Q#3Qz6M} dQ<\^U&v8c&jPb{10|iX #I'TJ+ɖx1 2٤'ELOOH ߅5cF€dQ|G/5E01uƐl="HS\n|zU f0 Q:# lP$PŦ"JE&Mb:ʏp ,_'N'Pkڛ3<"54m;XQlz3 f묟CQɻ[7qX%ΝFغfJ1Zԣ|Ef4bI29֠^)Ӵ oY\/9+M~%贐\%�]] h5NDu=sǪ ⅤzY<A#_s}qG" C&˥>g>' ]P i   4y cauQfU8ڐ^뎼 rLU} X%Hm"E^$8x0bq#-=Fʼn �X#  )]  �U#� r#fx%(yΆ>H ܖ]Wk>-N$`$48b觶FRL[Pэ 4ڀ9R W6 փ玮d)~6mR#2C#?5dȏˤ>WX|ьk))`ZB1w$1Q#3Qz6M} dQ<\^U&v8c&jPb{10|iX #I'TJ+ɖx1 2٤ 2Cc�R0?友rɳ~PFbJ=vj/nI {<\+}9ٚ/H0qJԚpd FE᳐k/ЦS10[[ܾ؉z/ǀvԧwK((V.lwdtNr%oC/mv`~3 HV+Vz1mĀcW iPϕ=E8ZzNdlkh<O$AE!&".tȻ'*|?a5/6@vXzpP ·ذ(Vx@lc9Swp%N7z3�DU3?cr5 btYq�DсSq/x7)~Vhxip D (j<V/Tj98Ne?P$Օ' U#�RGWJ$y&IMڌP=�,szz1IP}1>(TOѻner/&3[ScLT] }4 (3FC'8�}9}B06>)9 2"^2DWby8C:MSГdjlߏ3챆)>> SvtرuVТB #:!o8|/'CD�-`(f-3�� �U#  g�� 2Cc�Rم]qbON@6>Hܐ<bhȘ H/tDwk 0?T#uM ~  P; SUED1vV_'ԀBoO䪢=A5ǁ7XNJIܗ$;\�|-2<U.qL\1WiP: 0FnQfLM|XyI0hHG)RHӸ ͺEJNN&+-+^IŊϴ�(+@m| l zP3/'$7 :2Dm-koׁl, *rF{t#w]U):7`9rOoIɹ~yqQ- =x@`jnVxtG7,jG|Ă0Q6{{eh7™,v>ŌE � X# #� 2Cc�RS-`KsAN.) $Xt@F18iVȘY%䠠y"^ApJԛW(8)g<ܜg#$1�]'_e5ef;}GI~?fLE K6*I~XiIb\t؀@ffkjGL>47�_;B5dwEц0N@-ɇQ1/t~i`]١lهkO758.,{2wdӱU"TowI| -H:G"=qbk,)td.b5 ^k @Sr}*ISЦ$N%T 0QZ<eL)s3Kg^fF@ɦ�AҨOS7bP;-Z}y6͢_)Dg]Ϲ U#�֭�ݣ#x [s`T@IhZ>-Wp>- OkеH)AZ6G:?D P/?%+&e_Hon1~-W=Xf.y:6d`pm1l+* &N_(ob2 v }.\To(M U::k;L<w^WMM-*96ogOx{.W ?Oy\G[ uЌ1Fp<�� �U#  g�� 2Cc�Rd$#7ђ̠MLJhHT"m|k'rӗu4d̊eA"Zړ0R"*oj<L K9 _dXXf%L(RTx{nFB)h%Hh<c8JHYE͔p)Ek/XsNNP"PWAeqR7oǟQ| o9""BwSr!d#_V ,+~ zUh ^.sNT%(<<7 UƥH0k=S2ze;&~>/'D(/,H5nvm8:8wB�F(Hi.5nT-tዐl9[! I_18ukM<Xe۴6eB^yaoMyEfX8olOgV>trr$!4Г+}ow � X# � 2Cc�RrQy-c@ygճ1CWsk,L [4ykX/GpK]hϤ?uS|W3HNS~|+!Qp~)gC^Ji"jJNopvLrQ9 '�~*ud"$S瓵4DE+zd=&F~=Osb<8'}w &:˦u#0@"=XT %Rx\7ll%{wLR  K(&W=*Es@yALLDV*mŁz/%;,Ui6uuu݋u_7gE&rqh$,9?)6J )%2xH1Q8DcJEJ$0n[|G²L#㦫tYI)kMX![v=;wtNkQ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/keys/bannon-add-subkey-1-private.gpg�������������������������������0000644�0000000�0000000�00000007763�10461020230�0023610�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������ZQ&�8ZISIU?XݴO0ݧ.Dߡ4gB.. j7Ԥ{h**lM]4@i|Q%=A @Mn jNE/8aF?8'āLFDIb! (?>NnqxKPW3x!;{ez˘BJ!U9 L+WES^!vUvC/.r6GV۩)M([gC{F4ͲL���B8qFb˺}+<oǧ qzzu8$k+�gD|\dzv3,<+ƢfFD9W[W g,czf׮'Gk&LԆ^H3Lrp]{PY%)9ϷuZK%�Pb<{zgDL)k@ȅnX c'^Nw$ %GY,ܤ6"p pN`ΉUu#�[ غukC D.5"bBAO`#-ǓUzsa(|Fsr!p{b(S2@Mȧ$hIYRjK2ܭI-ǚ �g6G@f3VDBc!š߇;3CY]-P@DoT &\GG ?4])~{QP&.8zܦhX؁jXV[߽cEbaּelqB�qʧYph+HI#Yfv�U~ʚRj"(d�:t6-eg@4rz婕M-y2ˬ|#ۨ$Tޖz5Wެ ҮE B?�"Steve Bannon <steve@breitbart.com>S�>!ᩏ&Ra$"E܃ٱZQ& g�  � E܃ٱjliD:v2f&FD*2芡P4+٩!7E6SOOQU섷|EfRkOs7ј(g| Ns U-m\3Žy]С%ȍ<Ae,2;t S�e1dN|"&|Qo+9Wܹk/l"ߌc<@RyDqz!k3>F-%H^ZQ&�gߜMc)bVy{s/tp^M)]񢇕d)KSJ'' x;ߌ0+I%>E;I@"% zW`+Lr`k?^ ucV-9i<Ex2:`9J{j-yMfmqRpIMQ6vI6GS1}!D7%dޜT'$jcP) R<]_���(Oe|<Kg&|[S@>r&ʝ+wt0*Ƣ[W)<l{΂ER6]Š#jh QBK@ַp@;OI_o9_ , VC(&ˆQ\GƄi~N]ptK }B710гaDdv;>G=h%LH%` Z1I>^\]Hf[}ePb'vOlf&L4�H\v`}܅,U sZuLgV?|,+TTo?qn03_z4h2ŏpC)XBS&[vNNE4bi2�/<L:>3:7A] 29$zZw j9@׹i/j݆/ҁqvL")\5]p wS߱C$ƅ:~!Re/ HG֑Ά0�6]vȊ1.؜0Zir'SHxZQ*,[-φH*n݁3{f i/tG*Li7}!=ι,zlgǓBQ^\.)92C؉6� !ᩏ&Ra$"E܃ٱZQ& � E܃ٱgAY[z2N1PXՙ #pjx{8ȧ[QfZdW{]^Dw3HdHᴐ2TQzڲX^?9|N+ 埲O(~-/T4+`jPǼ>6Ai9FF ^+$<K;3X;(bq7Ȁë)ɥEh91=+�e_:ۃt"w|9 z{ZP9 n#{HT&c.sS)xsqDaZQ�! 0X'9^>$!fEXidu#i88f\}] Ip7S(km:,Щ'-W{qlmE8\YN~ x4L(6ZS\=bA#[̖39yN~8S1xAiiو6lOPBWĬTު#I oCd R�t`u [('%ԫ䕔���+;v^\Q;Ҩ/PL~TQZ`Zѣȯ.=`-MWTsަSH (xy]"c [;uñ N|;]%RBrdA|?}^X C+BGoqv鏡fcU5"!\;ACixrð[6-Num{{3m/ឫըUTЖ! @+G6�B-Tf|rɟ.ki'gQG/4PHt^ti}j2}<CZ�>j t yɫeS%U= o)Ā_�̭AJ<UVˈgSŻAlR. *��͚} }c̼uӛ&P?R'z Z3#R1ғ5.j k]DީYXN]�]ؒeXkSfk.XJz_k|9q=+BOzr}=z,pjkvPoR ιZ$-j ʼ{Mul� !ᩏ&Ra$"E܃ٱZQ@ E܃ٱt �!G^~Gr%ժZQ� Gr%ժZ^0磰GҸJAm3xU:Db>QVD �?T_5Ҙs$_| YsxӅ '4Qw)nh]58֏z".=>څ )Hf.hT4dG~#b2+36]@QNJwSE|GJq\MIîcpqhC% d;1ls;bDvg=IAmޢI!urpX Q4O(0/$7͠<ϽnҮ k~�6in(2TV x<nDa-vUFS.4I1A? CZ7)s]~Zܙ!d}*xvh0:eHQp̢GUj,J?wke0frGPʏQ-gޘ#pCD?Z<TRzg+GEH�������������sequoia-openpgp-2.0.0/tests/data/keys/bannon-add-subkey-1.gpg���������������������������������������0000644�0000000�0000000�00000004122�10461020230�0022122�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������ ZQ&�8ZISIU?XݴO0ݧ.Dߡ4gB.. j7Ԥ{h**lM]4@i|Q%=A @Mn jNE/8aF?8'āLFDIb! (?>NnqxKPW3x!;{ez˘BJ!U9 L+WES^!vUvC/.r6GV۩)M([gC{F4ͲL��"Steve Bannon <steve@breitbart.com>S�>!ᩏ&Ra$"E܃ٱZQ& g�  � E܃ٱjliD:v2f&FD*2芡P4+٩!7E6SOOQU섷|EfRkOs7ј(g| Ns U-m\3Žy]С%ȍ<Ae,2;t S�e1dN|"&|Qo+9Wܹk/l"ߌc<@RyDqz!k3>F-%H^ ZQ&�gߜMc)bVy{s/tp^M)]񢇕d)KSJ'' x;ߌ0+I%>E;I@"% zW`+Lr`k?^ ucV-9i<Ex2:`9J{j-yMfmqRpIMQ6vI6GS1}!D7%dޜT'$jcP) R<]_��6� !ᩏ&Ra$"E܃ٱZQ& � E܃ٱgAY[z2N1PXՙ #pjx{8ȧ[QfZdW{]^Dw3HdHᴐ2TQzڲX^?9|N+ 埲O(~-/T4+`jPǼ>6Ai9FF ^+$<K;3X;(bq7Ȁë)ɥEh91=+�e_:ۃt"w|9 z{ZP9 n#{HT&c.sS)xsqDa ZQ�! 0X'9^>$!fEXidu#i88f\}] Ip7S(km:,Щ'-W{qlmE8\YN~ x4L(6ZS\=bA#[̖39yN~8S1xAiiو6lOPBWĬTު#I oCd R�t`u [('%ԫ䕔��l� !ᩏ&Ra$"E܃ٱZQ@ E܃ٱt �!G^~Gr%ժZQ� Gr%ժZ^0磰GҸJAm3xU:Db>QVD �?T_5Ҙs$_| YsxӅ '4Qw)nh]58֏z".=>څ )Hf.hT4dG~#b2+36]@QNJwSE|GJq\MIîcpqhC% d;1ls;bDvg=IAmޢI!urpX Q4O(0/$7͠<ϽnҮ k~�6in(2TV x<nDa-vUFS.4I1A? CZ7)s]~Zܙ!d}*xvh0:eHQp̢GUj,J?wke0frGPʏQ-gޘ#pCD?Z<TRzg+GEH����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/keys/bannon-add-subkey-2-private.gpg�������������������������������0000644�0000000�0000000�00000007275�10461020230�0023607�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������ZQ&�8ZISIU?XݴO0ݧ.Dߡ4gB.. j7Ԥ{h**lM]4@i|Q%=A @Mn jNE/8aF?8'āLFDIb! (?>NnqxKPW3x!;{ez˘BJ!U9 L+WES^!vUvC/.r6GV۩)M([gC{F4ͲL���B8qFb˺}+<oǧ qzzu8$k+�gD|\dzv3,<+ƢfFD9W[W g,czf׮'Gk&LԆ^H3Lrp]{PY%)9ϷuZK%�Pb<{zgDL)k@ȅnX c'^Nw$ %GY,ܤ6"p pN`ΉUu#�[ غukC D.5"bBAO`#-ǓUzsa(|Fsr!p{b(S2@Mȧ$hIYRjK2ܭI-ǚ �g6G@f3VDBc!š߇;3CY]-P@DoT &\GG ?4])~{QP&.8zܦhX؁jXV[߽cEbaּelqB�qʧYph+HI#Yfv�U~ʚRj"(d�:t6-eg@4rz婕M-y2ˬ|#ۨ$Tޖz5Wެ ҮE B?�"Steve Bannon <steve@breitbart.com>S�>!ᩏ&Ra$"E܃ٱZQ& g�  � E܃ٱjliD:v2f&FD*2芡P4+٩!7E6SOOQU섷|EfRkOs7ј(g| Ns U-m\3Žy]С%ȍ<Ae,2;t S�e1dN|"&|Qo+9Wܹk/l"ߌc<@RyDqz!k3>F-%H^ZQ&�gߜMc)bVy{s/tp^M)]񢇕d)KSJ'' x;ߌ0+I%>E;I@"% zW`+Lr`k?^ ucV-9i<Ex2:`9J{j-yMfmqRpIMQ6vI6GS1}!D7%dޜT'$jcP) R<]_���(Oe|<Kg&|[S@>r&ʝ+wt0*Ƣ[W)<l{΂ER6]Š#jh QBK@ַp@;OI_o9_ , VC(&ˆQ\GƄi~N]ptK }B710гaDdv;>G=h%LH%` Z1I>^\]Hf[}ePb'vOlf&L4�H\v`}܅,U sZuLgV?|,+TTo?qn03_z4h2ŏpC)XBS&[vNNE4bi2�/<L:>3:7A] 29$zZw j9@׹i/j݆/ҁqvL")\5]p wS߱C$ƅ:~!Re/ HG֑Ά0�6]vȊ1.؜0Zir'SHxZQ*,[-φH*n݁3{f i/tG*Li7}!=ι,zlgǓBQ^\.)92C؉6� !ᩏ&Ra$"E܃ٱZQ& � E܃ٱgAY[z2N1PXՙ #pjx{8ȧ[QfZdW{]^Dw3HdHᴐ2TQzڲX^?9|N+ 埲O(~-/T4+`jPǼ>6Ai9FF ^+$<K;3X;(bq7Ȁë)ɥEh91=+�e_:ۃt"w|9 z{ZP9 n#{HT&c.sS)xsqDaZQ7�Tl54^h=VfRcB[t(tz֯3u :j˘vI2:.MW+ �䪏]N}7]N5:U/h)Ŵ9yۮ,@LӚ˴k[vEYG\zr z(o�\sǖHv(%4:*5@q2/K#F#vr&ّJvKʜ'n ���+Z1ix|W@ĤFG?rk;|4h& 72O,ݗA-)ˣ&Œ$} +6g2 ~,0a]-`8-,iZ9*_pjE▼}9CjxQk$EFEAPSq뤲V5Z|EBǏtQ:ނKBI`'YhA()ry[:xGBbHOKؓ><__�7yCt$ʖx-mmF{}] 1+T UG &-3rdwwΧח/?HaDC=C-WsD31iʫpɴS?;wAwE{lz)<�OfWVCN ~wĚ$kW`PA@Ȓd*@ o>M% £e~ s6,aMKEo#X.1PI;�/QRG6!6|Q<2؊)�QT1F G2PaCgйQ$Iwʏ:I=R."tFvC 1N? >SwBϨloi7]/W$G29`6� !ᩏ&Ra$"E܃ٱZQ7 � E܃ٱ[a[|M gFUn �x\M:iAc&.Vh^#Tgs̥2}P?bt3ϫD*b>)^vjl0oXW\a>x;zXCAϩkABVPw}kDHG7z〽~έM1ɲ?T]MPL 8)!H7JQ+q"jI$l,nUҶG~R]vg"}N|4 j�����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/keys/bannon-add-subkey-2.gpg���������������������������������������0000644�0000000�0000000�00000003434�10461020230�0022130�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������ ZQ&�8ZISIU?XݴO0ݧ.Dߡ4gB.. j7Ԥ{h**lM]4@i|Q%=A @Mn jNE/8aF?8'āLFDIb! (?>NnqxKPW3x!;{ez˘BJ!U9 L+WES^!vUvC/.r6GV۩)M([gC{F4ͲL��"Steve Bannon <steve@breitbart.com>S�>!ᩏ&Ra$"E܃ٱZQ& g�  � E܃ٱjliD:v2f&FD*2芡P4+٩!7E6SOOQU섷|EfRkOs7ј(g| Ns U-m\3Žy]С%ȍ<Ae,2;t S�e1dN|"&|Qo+9Wܹk/l"ߌc<@RyDqz!k3>F-%H^ ZQ&�gߜMc)bVy{s/tp^M)]񢇕d)KSJ'' x;ߌ0+I%>E;I@"% zW`+Lr`k?^ ucV-9i<Ex2:`9J{j-yMfmqRpIMQ6vI6GS1}!D7%dޜT'$jcP) R<]_��6� !ᩏ&Ra$"E܃ٱZQ& � E܃ٱgAY[z2N1PXՙ #pjx{8ȧ[QfZdW{]^Dw3HdHᴐ2TQzڲX^?9|N+ 埲O(~-/T4+`jPǼ>6Ai9FF ^+$<K;3X;(bq7Ȁë)ɥEh91=+�e_:ۃt"w|9 z{ZP9 n#{HT&c.sS)xsqDa ZQ7�Tl54^h=VfRcB[t(tz֯3u :j˘vI2:.MW+ �䪏]N}7]N5:U/h)Ŵ9yۮ,@LӚ˴k[vEYG\zr z(o�\sǖHv(%4:*5@q2/K#F#vr&ّJvKʜ'n ��6� !ᩏ&Ra$"E܃ٱZQ7 � E܃ٱ[a[|M gFUn �x\M:iAc&.Vh^#Tgs̥2}P?bt3ϫD*b>)^vjl0oXW\a>x;zXCAϩkABVPw}kDHG7z〽~έM1ɲ?T]MPL 8)!H7JQ+q"jI$l,nUҶG~R]vg"}N|4 j������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/keys/bannon-add-subkey-3-private.gpg�������������������������������0000644�0000000�0000000�00000012163�10461020230�0023600�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������ZQ&�8ZISIU?XݴO0ݧ.Dߡ4gB.. j7Ԥ{h**lM]4@i|Q%=A @Mn jNE/8aF?8'āLFDIb! (?>NnqxKPW3x!;{ez˘BJ!U9 L+WES^!vUvC/.r6GV۩)M([gC{F4ͲL���B8qFb˺}+<oǧ qzzu8$k+�gD|\dzv3,<+ƢfFD9W[W g,czf׮'Gk&LԆ^H3Lrp]{PY%)9ϷuZK%�Pb<{zgDL)k@ȅnX c'^Nw$ %GY,ܤ6"p pN`ΉUu#�[ غukC D.5"bBAO`#-ǓUzsa(|Fsr!p{b(S2@Mȧ$hIYRjK2ܭI-ǚ �g6G@f3VDBc!š߇;3CY]-P@DoT &\GG ?4])~{QP&.8zܦhX؁jXV[߽cEbaּelqB�qʧYph+HI#Yfv�U~ʚRj"(d�:t6-eg@4rz婕M-y2ˬ|#ۨ$Tޖz5Wެ ҮE B?�"Steve Bannon <steve@breitbart.com>S�>!ᩏ&Ra$"E܃ٱZQ& g�  � E܃ٱjliD:v2f&FD*2芡P4+٩!7E6SOOQU섷|EfRkOs7ј(g| Ns U-m\3Žy]С%ȍ<Ae,2;t S�e1dN|"&|Qo+9Wܹk/l"ߌc<@RyDqz!k3>F-%H^ZQ&�gߜMc)bVy{s/tp^M)]񢇕d)KSJ'' x;ߌ0+I%>E;I@"% zW`+Lr`k?^ ucV-9i<Ex2:`9J{j-yMfmqRpIMQ6vI6GS1}!D7%dޜT'$jcP) R<]_���(Oe|<Kg&|[S@>r&ʝ+wt0*Ƣ[W)<l{΂ER6]Š#jh QBK@ַp@;OI_o9_ , VC(&ˆQ\GƄi~N]ptK }B710гaDdv;>G=h%LH%` Z1I>^\]Hf[}ePb'vOlf&L4�H\v`}܅,U sZuLgV?|,+TTo?qn03_z4h2ŏpC)XBS&[vNNE4bi2�/<L:>3:7A] 29$zZw j9@׹i/j݆/ҁqvL")\5]p wS߱C$ƅ:~!Re/ HG֑Ά0�6]vȊ1.؜0Zir'SHxZQ*,[-φH*n݁3{f i/tG*Li7}!=ι,zlgǓBQ^\.)92C؉6� !ᩏ&Ra$"E܃ٱZQ& � E܃ٱgAY[z2N1PXՙ #pjx{8ȧ[QfZdW{]^Dw3HdHᴐ2TQzڲX^?9|N+ 埲O(~-/T4+`jPǼ>6Ai9FF ^+$<K;3X;(bq7Ȁë)ɥEh91=+�e_:ۃt"w|9 z{ZP9 n#{HT&c.sS)xsqDaZQ/�ͦ˃> Hq.Gyf!Amy5pj{ĵR;krfn[Ӡ{uE ;J#1Y m'('a -ܟeZo*HHyS>Gd+4Lͦ $6 F~O(IK4HOZٮpîjSy0@�b,@;{Xʻh$=rHN7Ň<Xɹ&7C!ŢPά)<Y[z�t#i%ye%w^8$s>k]+-kJS_^H-6fƮJ 0 =/e'7KS,dCuLZIL,ȶ=YaKZ}+aڀߜ 8ϣcR=Հov ߲0&XI,e['޾G 9?{g?>ީff-xM塀+UF5}8%GEgJ7diׂ8Pfκl`1���!P>*88 J#~-l$n߭#ɠi2#ۈ[^p8ڧ:'߸Jy?4/o]$%h5i<X mӀ=kʁKC;9=?ܩOQ(SA̞N1jOβ!-*+[]s˩9"F2:˂85u=Z]' -t6buQqk@(MY |{^̮da)_Q?:w XwIhl@S5|$B3&B/�eF*)5NQ-]v�8BeCa(Q'2>9ӈrO;PI[�;Aסx#B6Ǧ n&pFLHgpqާիs1KܷF*yLr=%X~v:j%^8ֺ +D%*Y&[@_=OMv=B}Tq.r%~l<�&tտk%y@7j̈́۵r,ulƢ�}N1YY3 hUôlj_1EP<^DUf8{{d}N{~n0OgT7~ l,bZ &K|`WO'0ÃQX(-0 KSpxm=q4 RqZ}=[-.3}cxAԐ gp gC � 442mG3o�(l@Sq<{uG|qQ}wxZ|Gt…~LE 4xx2\4$4:Pح,.e$5KxDo:XbG0T 1H}#*JP{r' <׈͡@Obd@ -F#¥Ƹoq`7j,855c$Z,5n:ֳaS^{ VWQ3+_Ǖӫe4%uf<,3 1KWO�%őy.IvrkֺXEP$Gw,P?HંLf֑HkhZ]5 h3R΁/w:7I{#0P{A)ݙ,FO4El�XVqS_arvLp/aE'Ai乵20U)tb.>Ffcϧ>>cqJW^,b@j- <  xl� !ᩏ&Ra$"E܃ٱZQ/@ E܃ٱt �!<Keb$u|8w!&ZQ/� 8w!&UwPǙ]FEݟ~<r*c � $ VoV.~7 ^{](z\L~ʾ}RHHpv< sbIcilLT|%͋(M "e#i- fN**NڱxނʾKVs }Pϯ=r-6]GWT bQ9ڶCR(QI" {H8q8f\Iz^X}PN)_Y;lqV89*AnV_%pSS3jR0,Fj"u3'⇁iЈb!0 4C@9tx~bfA&?㉂b]ɒEVTm-%;L ܔT&u } RQJ oHlOW=(9OZӋ8ZY,u;2~Y[ڐ_�`lliqD?:l�MGm 6^L0i|G.<}o>z'Ȫsx:Ax yL :CCP 0x_G^jJ*ǿ1 EQ߽à-65aH[o9 &H^ *P^/{àlM_11`7XA{a3`~uj<GRm~oc(?Np;=lm>jC`!�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/keys/bannon-add-subkey-3.gpg���������������������������������������0000644�0000000�0000000�00000005122�10461020230�0022125�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������ ZQ&�8ZISIU?XݴO0ݧ.Dߡ4gB.. j7Ԥ{h**lM]4@i|Q%=A @Mn jNE/8aF?8'āLFDIb! (?>NnqxKPW3x!;{ez˘BJ!U9 L+WES^!vUvC/.r6GV۩)M([gC{F4ͲL��"Steve Bannon <steve@breitbart.com>S�>!ᩏ&Ra$"E܃ٱZQ& g�  � E܃ٱjliD:v2f&FD*2芡P4+٩!7E6SOOQU섷|EfRkOs7ј(g| Ns U-m\3Žy]С%ȍ<Ae,2;t S�e1dN|"&|Qo+9Wܹk/l"ߌc<@RyDqz!k3>F-%H^ ZQ&�gߜMc)bVy{s/tp^M)]񢇕d)KSJ'' x;ߌ0+I%>E;I@"% zW`+Lr`k?^ ucV-9i<Ex2:`9J{j-yMfmqRpIMQ6vI6GS1}!D7%dޜT'$jcP) R<]_��6� !ᩏ&Ra$"E܃ٱZQ& � E܃ٱgAY[z2N1PXՙ #pjx{8ȧ[QfZdW{]^Dw3HdHᴐ2TQzڲX^?9|N+ 埲O(~-/T4+`jPǼ>6Ai9FF ^+$<K;3X;(bq7Ȁë)ɥEh91=+�e_:ۃt"w|9 z{ZP9 n#{HT&c.sS)xsqDa ZQ/�ͦ˃> Hq.Gyf!Amy5pj{ĵR;krfn[Ӡ{uE ;J#1Y m'('a -ܟeZo*HHyS>Gd+4Lͦ $6 F~O(IK4HOZٮpîjSy0@�b,@;{Xʻh$=rHN7Ň<Xɹ&7C!ŢPά)<Y[z�t#i%ye%w^8$s>k]+-kJS_^H-6fƮJ 0 =/e'7KS,dCuLZIL,ȶ=YaKZ}+aڀߜ 8ϣcR=Հov ߲0&XI,e['޾G 9?{g?>ީff-xM塀+UF5}8%GEgJ7diׂ8Pfκl`1��l� !ᩏ&Ra$"E܃ٱZQ/@ E܃ٱt �!<Keb$u|8w!&ZQ/� 8w!&UwPǙ]FEݟ~<r*c � $ VoV.~7 ^{](z\L~ʾ}RHHpv< sbIcilLT|%͋(M "e#i- fN**NڱxނʾKVs }Pϯ=r-6]GWT bQ9ڶCR(QI" {H8q8f\Iz^X}PN)_Y;lqV89*AnV_%pSS3jR0,Fj"u3'⇁iЈb!0 4C@9tx~bfA&?㉂b]ɒEVTm-%;L ܔT&u } RQJ oHlOW=(9OZӋ8ZY,u;2~Y[ڐ_�`lliqD?:l�MGm 6^L0i|G.<}o>z'Ȫsx:Ax yL :CCP 0x_G^jJ*ǿ1 EQ߽à-65aH[o9 &H^ *P^/{àlM_11`7XA{a3`~uj<GRm~oc(?Np;=lm>jC`!����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/keys/bannon-add-uid-1-whitehouse.gov.gpg���������������������������0000644�0000000�0000000�00000003117�10461020230�0024360�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������ ZQ&�8ZISIU?XݴO0ݧ.Dߡ4gB.. j7Ԥ{h**lM]4@i|Q%=A @Mn jNE/8aF?8'āLFDIb! (?>NnqxKPW3x!;{ez˘BJ!U9 L+WES^!vUvC/.r6GV۩)M([gC{F4ͲL��"Steve Bannon <steve@breitbart.com>S�>!ᩏ&Ra$"E܃ٱZQ& g�  � E܃ٱjliD:v2f&FD*2芡P4+٩!7E6SOOQU섷|EfRkOs7ј(g| Ns U-m\3Žy]С%ȍ<Ae,2;t S�e1dN|"&|Qo+9Wܹk/l"ߌc<@RyDqz!k3>F-%H^#Steve Bannon <steve@whitehouse.gov>T�>!ᩏ&Ra$"E܃ٱZQ$ g�  � E܃ٱ�VYI)$uS6.jCj]d.nc%kK<wE ^p[M oαƘ!ee_Uq#2]#F Jb!ab1&XC}ptwF(WShZJ0Z ժivFOB]  >ă%vF6Ls1UH es])y{E ZQ&�gߜMc)bVy{s/tp^M)]񢇕d)KSJ'' x;ߌ0+I%>E;I@"% zW`+Lr`k?^ ucV-9i<Ex2:`9J{j-yMfmqRpIMQ6vI6GS1}!D7%dޜT'$jcP) R<]_��6� !ᩏ&Ra$"E܃ٱZQ& � E܃ٱgAY[z2N1PXՙ #pjx{8ȧ[QfZdW{]^Dw3HdHᴐ2TQzڲX^?9|N+ 埲O(~-/T4+`jPǼ>6Ai9FF ^+$<K;3X;(bq7Ȁë)ɥEh91=+�e_:ۃt"w|9 z{ZP9 n#{HT&c.sS)xsqDa�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/keys/bannon-add-uid-2-fox.com.gpg����������������������������������0000644�0000000�0000000�00000003110�10461020230�0022745�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������ ZQ&�8ZISIU?XݴO0ݧ.Dߡ4gB.. j7Ԥ{h**lM]4@i|Q%=A @Mn jNE/8aF?8'āLFDIb! (?>NnqxKPW3x!;{ez˘BJ!U9 L+WES^!vUvC/.r6GV۩)M([gC{F4ͲL��"Steve Bannon <steve@breitbart.com>S�>!ᩏ&Ra$"E܃ٱZQ& g�  � E܃ٱjliD:v2f&FD*2芡P4+٩!7E6SOOQU섷|EfRkOs7ј(g| Ns U-m\3Žy]С%ȍ<Ae,2;t S�e1dN|"&|Qo+9Wܹk/l"ߌc<@RyDqz!k3>F-%H^Steve Bannon <steve@fox.com>T�>!ᩏ&Ra$"E܃ٱZQP g�  � E܃ٱ�I .bvveɹ[amKtmP (SKZKo/C'< 2FN >.o5Ew3 eJ\dy!JK;FTPBaB l#\Hߝw}ʗJ*]^`ޣr#h䚌j4]-)̞uR Hh; xk!O IvE z_|W_QO'TV- ZQ&�gߜMc)bVy{s/tp^M)]񢇕d)KSJ'' x;ߌ0+I%>E;I@"% zW`+Lr`k?^ ucV-9i<Ex2:`9J{j-yMfmqRpIMQ6vI6GS1}!D7%dޜT'$jcP) R<]_��6� !ᩏ&Ra$"E܃ٱZQ& � E܃ٱgAY[z2N1PXՙ #pjx{8ȧ[QfZdW{]^Dw3HdHᴐ2TQzڲX^?9|N+ 埲O(~-/T4+`jPǼ>6Ai9FF ^+$<K;3X;(bq7Ȁë)ɥEh91=+�e_:ۃt"w|9 z{ZP9 n#{HT&c.sS)xsqDa��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/keys/bannon-add-uid-3-whitehouse.gov-dup.gpg�����������������������0000644�0000000�0000000�00000003117�10461020230�0025150�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������ ZQ&�8ZISIU?XݴO0ݧ.Dߡ4gB.. j7Ԥ{h**lM]4@i|Q%=A @Mn jNE/8aF?8'āLFDIb! (?>NnqxKPW3x!;{ez˘BJ!U9 L+WES^!vUvC/.r6GV۩)M([gC{F4ͲL��"Steve Bannon <steve@breitbart.com>S�>!ᩏ&Ra$"E܃ٱZQ& g�  � E܃ٱjliD:v2f&FD*2芡P4+٩!7E6SOOQU섷|EfRkOs7ј(g| Ns U-m\3Žy]С%ȍ<Ae,2;t S�e1dN|"&|Qo+9Wܹk/l"ߌc<@RyDqz!k3>F-%H^#Steve Bannon <steve@whitehouse.gov>T�>!ᩏ&Ra$"E܃ٱZR  g�  � E܃ٱ�+J}LAl@ڨ{Xm" ֢oQw=4!k\jwD<݋A|%! 8{m 1oŐ+`n<c\QLW~tKVOc$A䮣KS&P1{}ԵF0�Q~asfG|Vty: (.k{xwZxT<ΔIfFL$Etp+68pgvݭTa~@ҲE3%4] ZQ&�gߜMc)bVy{s/tp^M)]񢇕d)KSJ'' x;ߌ0+I%>E;I@"% zW`+Lr`k?^ ucV-9i<Ex2:`9J{j-yMfmqRpIMQ6vI6GS1}!D7%dޜT'$jcP) R<]_��6� !ᩏ&Ra$"E܃ٱZQ& � E܃ٱgAY[z2N1PXՙ #pjx{8ȧ[QfZdW{]^Dw3HdHᴐ2TQzڲX^?9|N+ 埲O(~-/T4+`jPǼ>6Ai9FF ^+$<K;3X;(bq7Ȁë)ɥEh91=+�e_:ۃt"w|9 z{ZP9 n#{HT&c.sS)xsqDa�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/keys/bannon-all-subkeys.gpg����������������������������������������0000644�0000000�0000000�00000010032�10461020230�0022164�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������ ZQ&�8ZISIU?XݴO0ݧ.Dߡ4gB.. j7Ԥ{h**lM]4@i|Q%=A @Mn jNE/8aF?8'āLFDIb! (?>NnqxKPW3x!;{ez˘BJ!U9 L+WES^!vUvC/.r6GV۩)M([gC{F4ͲL��"Steve Bannon <steve@breitbart.com>S�>!ᩏ&Ra$"E܃ٱZQ& g�  � E܃ٱjliD:v2f&FD*2芡P4+٩!7E6SOOQU섷|EfRkOs7ј(g| Ns U-m\3Žy]С%ȍ<Ae,2;t S�e1dN|"&|Qo+9Wܹk/l"ߌc<@RyDqz!k3>F-%H^ ZQ&�gߜMc)bVy{s/tp^M)]񢇕d)KSJ'' x;ߌ0+I%>E;I@"% zW`+Lr`k?^ ucV-9i<Ex2:`9J{j-yMfmqRpIMQ6vI6GS1}!D7%dޜT'$jcP) R<]_��6� !ᩏ&Ra$"E܃ٱZQ& � E܃ٱgAY[z2N1PXՙ #pjx{8ȧ[QfZdW{]^Dw3HdHᴐ2TQzڲX^?9|N+ 埲O(~-/T4+`jPǼ>6Ai9FF ^+$<K;3X;(bq7Ȁë)ɥEh91=+�e_:ۃt"w|9 z{ZP9 n#{HT&c.sS)xsqDa ZQ�! 0X'9^>$!fEXidu#i88f\}] Ip7S(km:,Щ'-W{qlmE8\YN~ x4L(6ZS\=bA#[̖39yN~8S1xAiiو6lOPBWĬTު#I oCd R�t`u [('%ԫ䕔��l� !ᩏ&Ra$"E܃ٱZQ@ E܃ٱt �!G^~Gr%ժZQ� Gr%ժZ^0磰GҸJAm3xU:Db>QVD �?T_5Ҙs$_| YsxӅ '4Qw)nh]58֏z".=>څ )Hf.hT4dG~#b2+36]@QNJwSE|GJq\MIîcpqhC% d;1ls;bDvg=IAmޢI!urpX Q4O(0/$7͠<ϽnҮ k~�6in(2TV x<nDa-vUFS.4I1A? CZ7)s]~Zܙ!d}*xvh0:eHQp̢GUj,J?wke0frGPʏQ-gޘ#pCD?Z<TRzg+GEHܹ ZQ7�Tl54^h=VfRcB[t(tz֯3u :j˘vI2:.MW+ �䪏]N}7]N5:U/h)Ŵ9yۮ,@LӚ˴k[vEYG\zr z(o�\sǖHv(%4:*5@q2/K#F#vr&ّJvKʜ'n ��6� !ᩏ&Ra$"E܃ٱZQ7 � E܃ٱ[a[|M gFUn �x\M:iAc&.Vh^#Tgs̥2}P?bt3ϫD*b>)^vjl0oXW\a>x;zXCAϩkABVPw}kDHG7z〽~έM1ɲ?T]MPL 8)!H7JQ+q"jI$l,nUҶG~R]vg"}N|4 j ZQ/�ͦ˃> Hq.Gyf!Amy5pj{ĵR;krfn[Ӡ{uE ;J#1Y m'('a -ܟeZo*HHyS>Gd+4Lͦ $6 F~O(IK4HOZٮpîjSy0@�b,@;{Xʻh$=rHN7Ň<Xɹ&7C!ŢPά)<Y[z�t#i%ye%w^8$s>k]+-kJS_^H-6fƮJ 0 =/e'7KS,dCuLZIL,ȶ=YaKZ}+aڀߜ 8ϣcR=Հov ߲0&XI,e['޾G 9?{g?>ީff-xM塀+UF5}8%GEgJ7diׂ8Pfκl`1��l� !ᩏ&Ra$"E܃ٱZQ/@ E܃ٱt �!<Keb$u|8w!&ZQ/� 8w!&UwPǙ]FEݟ~<r*c � $ VoV.~7 ^{](z\L~ʾ}RHHpv< sbIcilLT|%͋(M "e#i- fN**NڱxނʾKVs }Pϯ=r-6]GWT bQ9ڶCR(QI" {H8q8f\Iz^X}PN)_Y;lqV89*AnV_%pSS3jR0,Fj"u3'⇁iЈb!0 4C@9tx~bfA&?㉂b]ɒEVTm-%;L ܔT&u } RQJ oHlOW=(9OZӋ8ZY,u;2~Y[ڐ_�`lliqD?:l�MGm 6^L0i|G.<}o>z'Ȫsx:Ax yL :CCP 0x_G^jJ*ǿ1 EQ߽à-65aH[o9 &H^ *P^/{àlM_11`7XA{a3`~uj<GRm~oc(?Np;=lm>jC`!������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/keys/bannon-all-uids-subkeys.gpg�����������������������������������0000644�0000000�0000000�00000012142�10461020230�0023132�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������ ZQ&�8ZISIU?XݴO0ݧ.Dߡ4gB.. j7Ԥ{h**lM]4@i|Q%=A @Mn jNE/8aF?8'āLFDIb! (?>NnqxKPW3x!;{ez˘BJ!U9 L+WES^!vUvC/.r6GV۩)M([gC{F4ͲL��"Steve Bannon <steve@breitbart.com>S�>!ᩏ&Ra$"E܃ٱZQ& g�  � E܃ٱjliD:v2f&FD*2芡P4+٩!7E6SOOQU섷|EfRkOs7ј(g| Ns U-m\3Žy]С%ȍ<Ae,2;t S�e1dN|"&|Qo+9Wܹk/l"ߌc<@RyDqz!k3>F-%H^#Steve Bannon <steve@whitehouse.gov>T�>!ᩏ&Ra$"E܃ٱZR  g�  � E܃ٱ�+J}LAl@ڨ{Xm" ֢oQw=4!k\jwD<݋A|%! 8{m 1oŐ+`n<c\QLW~tKVOc$A䮣KS&P1{}ԵF0�Q~asfG|Vty: (.k{xwZxT<ΔIfFL$Etp+68pgvݭTa~@ҲE3%4]T�>!ᩏ&Ra$"E܃ٱZQ$ g�  � E܃ٱ�VYI)$uS6.jCj]d.nc%kK<wE ^p[M oαƘ!ee_Uq#2]#F Jb!ab1&XC}ptwF(WShZJ0Z ժivFOB]  >ă%vF6Ls1UH es])y{ESteve Bannon <steve@fox.com>T�>!ᩏ&Ra$"E܃ٱZQP g�  � E܃ٱ�I .bvveɹ[amKtmP (SKZKo/C'< 2FN >.o5Ew3 eJ\dy!JK;FTPBaB l#\Hߝw}ʗJ*]^`ޣr#h䚌j4]-)̞uR Hh; xk!O IvE z_|W_QO'TV- ZQ&�gߜMc)bVy{s/tp^M)]񢇕d)KSJ'' x;ߌ0+I%>E;I@"% zW`+Lr`k?^ ucV-9i<Ex2:`9J{j-yMfmqRpIMQ6vI6GS1}!D7%dޜT'$jcP) R<]_��6� !ᩏ&Ra$"E܃ٱZQ& � E܃ٱgAY[z2N1PXՙ #pjx{8ȧ[QfZdW{]^Dw3HdHᴐ2TQzڲX^?9|N+ 埲O(~-/T4+`jPǼ>6Ai9FF ^+$<K;3X;(bq7Ȁë)ɥEh91=+�e_:ۃt"w|9 z{ZP9 n#{HT&c.sS)xsqDa ZQ�! 0X'9^>$!fEXidu#i88f\}] Ip7S(km:,Щ'-W{qlmE8\YN~ x4L(6ZS\=bA#[̖39yN~8S1xAiiو6lOPBWĬTު#I oCd R�t`u [('%ԫ䕔��l� !ᩏ&Ra$"E܃ٱZQ@ E܃ٱt �!G^~Gr%ժZQ� Gr%ժZ^0磰GҸJAm3xU:Db>QVD �?T_5Ҙs$_| YsxӅ '4Qw)nh]58֏z".=>څ )Hf.hT4dG~#b2+36]@QNJwSE|GJq\MIîcpqhC% d;1ls;bDvg=IAmޢI!urpX Q4O(0/$7͠<ϽnҮ k~�6in(2TV x<nDa-vUFS.4I1A? CZ7)s]~Zܙ!d}*xvh0:eHQp̢GUj,J?wke0frGPʏQ-gޘ#pCD?Z<TRzg+GEHܹ ZQ7�Tl54^h=VfRcB[t(tz֯3u :j˘vI2:.MW+ �䪏]N}7]N5:U/h)Ŵ9yۮ,@LӚ˴k[vEYG\zr z(o�\sǖHv(%4:*5@q2/K#F#vr&ّJvKʜ'n ��6� !ᩏ&Ra$"E܃ٱZQ7 � E܃ٱ[a[|M gFUn �x\M:iAc&.Vh^#Tgs̥2}P?bt3ϫD*b>)^vjl0oXW\a>x;zXCAϩkABVPw}kDHG7z〽~έM1ɲ?T]MPL 8)!H7JQ+q"jI$l,nUҶG~R]vg"}N|4 j ZQ/�ͦ˃> Hq.Gyf!Amy5pj{ĵR;krfn[Ӡ{uE ;J#1Y m'('a -ܟeZo*HHyS>Gd+4Lͦ $6 F~O(IK4HOZٮpîjSy0@�b,@;{Xʻh$=rHN7Ň<Xɹ&7C!ŢPά)<Y[z�t#i%ye%w^8$s>k]+-kJS_^H-6fƮJ 0 =/e'7KS,dCuLZIL,ȶ=YaKZ}+aڀߜ 8ϣcR=Հov ߲0&XI,e['޾G 9?{g?>ީff-xM塀+UF5}8%GEgJ7diׂ8Pfκl`1��l� !ᩏ&Ra$"E܃ٱZQ/@ E܃ٱt �!<Keb$u|8w!&ZQ/� 8w!&UwPǙ]FEݟ~<r*c � $ VoV.~7 ^{](z\L~ʾ}RHHpv< sbIcilLT|%͋(M "e#i- fN**NڱxނʾKVs }Pϯ=r-6]GWT bQ9ڶCR(QI" {H8q8f\Iz^X}PN)_Y;lqV89*AnV_%pSS3jR0,Fj"u3'⇁iЈb!0 4C@9tx~bfA&?㉂b]ɒEVTm-%;L ܔT&u } RQJ oHlOW=(9OZӋ8ZY,u;2~Y[ڐ_�`lliqD?:l�MGm 6^L0i|G.<}o>z'Ȫsx:Ax yL :CCP 0x_G^jJ*ǿ1 EQ߽à-65aH[o9 &H^ *P^/{àlM_11`7XA{a3`~uj<GRm~oc(?Np;=lm>jC`!������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/keys/bannon-all-uids.gpg�������������������������������������������0000644�0000000�0000000�00000004433�10461020230�0021453�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������ ZQ&�8ZISIU?XݴO0ݧ.Dߡ4gB.. j7Ԥ{h**lM]4@i|Q%=A @Mn jNE/8aF?8'āLFDIb! (?>NnqxKPW3x!;{ez˘BJ!U9 L+WES^!vUvC/.r6GV۩)M([gC{F4ͲL��"Steve Bannon <steve@breitbart.com>S�>!ᩏ&Ra$"E܃ٱZQ& g�  � E܃ٱjliD:v2f&FD*2芡P4+٩!7E6SOOQU섷|EfRkOs7ј(g| Ns U-m\3Žy]С%ȍ<Ae,2;t S�e1dN|"&|Qo+9Wܹk/l"ߌc<@RyDqz!k3>F-%H^#Steve Bannon <steve@whitehouse.gov>T�>!ᩏ&Ra$"E܃ٱZR  g�  � E܃ٱ�+J}LAl@ڨ{Xm" ֢oQw=4!k\jwD<݋A|%! 8{m 1oŐ+`n<c\QLW~tKVOc$A䮣KS&P1{}ԵF0�Q~asfG|Vty: (.k{xwZxT<ΔIfFL$Etp+68pgvݭTa~@ҲE3%4]T�>!ᩏ&Ra$"E܃ٱZQ$ g�  � E܃ٱ�VYI)$uS6.jCj]d.nc%kK<wE ^p[M oαƘ!ee_Uq#2]#F Jb!ab1&XC}ptwF(WShZJ0Z ժivFOB]  >ă%vF6Ls1UH es])y{ESteve Bannon <steve@fox.com>T�>!ᩏ&Ra$"E܃ٱZQP g�  � E܃ٱ�I .bvveɹ[amKtmP (SKZKo/C'< 2FN >.o5Ew3 eJ\dy!JK;FTPBaB l#\Hߝw}ʗJ*]^`ޣr#h䚌j4]-)̞uR Hh; xk!O IvE z_|W_QO'TV- ZQ&�gߜMc)bVy{s/tp^M)]񢇕d)KSJ'' x;ߌ0+I%>E;I@"% zW`+Lr`k?^ ucV-9i<Ex2:`9J{j-yMfmqRpIMQ6vI6GS1}!D7%dޜT'$jcP) R<]_��6� !ᩏ&Ra$"E܃ٱZQ& � E܃ٱgAY[z2N1PXՙ #pjx{8ȧ[QfZdW{]^Dw3HdHᴐ2TQzڲX^?9|N+ 埲O(~-/T4+`jPǼ>6Ai9FF ^+$<K;3X;(bq7Ȁë)ɥEh91=+�e_:ۃt"w|9 z{ZP9 n#{HT&c.sS)xsqDa�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/keys/bannon-base-private.gpg���������������������������������������0000644�0000000�0000000�00000004751�10461020230�0022326�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������ZQ&�8ZISIU?XݴO0ݧ.Dߡ4gB.. j7Ԥ{h**lM]4@i|Q%=A @Mn jNE/8aF?8'āLFDIb! (?>NnqxKPW3x!;{ez˘BJ!U9 L+WES^!vUvC/.r6GV۩)M([gC{F4ͲL���B8qFb˺}+<oǧ qzzu8$k+�gD|\dzv3,<+ƢfFD9W[W g,czf׮'Gk&LԆ^H3Lrp]{PY%)9ϷuZK%�Pb<{zgDL)k@ȅnX c'^Nw$ %GY,ܤ6"p pN`ΉUu#�[ غukC D.5"bBAO`#-ǓUzsa(|Fsr!p{b(S2@Mȧ$hIYRjK2ܭI-ǚ �g6G@f3VDBc!š߇;3CY]-P@DoT &\GG ?4])~{QP&.8zܦhX؁jXV[߽cEbaּelqB�qʧYph+HI#Yfv�U~ʚRj"(d�:t6-eg@4rz婕M-y2ˬ|#ۨ$Tޖz5Wެ ҮE B?�"Steve Bannon <steve@breitbart.com>S�>!ᩏ&Ra$"E܃ٱZQ& g�  � E܃ٱjliD:v2f&FD*2芡P4+٩!7E6SOOQU섷|EfRkOs7ј(g| Ns U-m\3Žy]С%ȍ<Ae,2;t S�e1dN|"&|Qo+9Wܹk/l"ߌc<@RyDqz!k3>F-%H^ZQ&�gߜMc)bVy{s/tp^M)]񢇕d)KSJ'' x;ߌ0+I%>E;I@"% zW`+Lr`k?^ ucV-9i<Ex2:`9J{j-yMfmqRpIMQ6vI6GS1}!D7%dޜT'$jcP) R<]_���(Oe|<Kg&|[S@>r&ʝ+wt0*Ƣ[W)<l{΂ER6]Š#jh QBK@ַp@;OI_o9_ , VC(&ˆQ\GƄi~N]ptK }B710гaDdv;>G=h%LH%` Z1I>^\]Hf[}ePb'vOlf&L4�H\v`}܅,U sZuLgV?|,+TTo?qn03_z4h2ŏpC)XBS&[vNNE4bi2�/<L:>3:7A] 29$zZw j9@׹i/j݆/ҁqvL")\5]p wS߱C$ƅ:~!Re/ HG֑Ά0�6]vȊ1.؜0Zir'SHxZQ*,[-φH*n݁3{f i/tG*Li7}!=ι,zlgǓBQ^\.)92C؉6� !ᩏ&Ra$"E܃ٱZQ& � E܃ٱgAY[z2N1PXՙ #pjx{8ȧ[QfZdW{]^Dw3HdHᴐ2TQzڲX^?9|N+ 埲O(~-/T4+`jPǼ>6Ai9FF ^+$<K;3X;(bq7Ȁë)ɥEh91=+�e_:ۃt"w|9 z{ZP9 n#{HT&c.sS)xsqDa�����������������������sequoia-openpgp-2.0.0/tests/data/keys/bannon-base.gpg�����������������������������������������������0000644�0000000�0000000�00000002323�10461020230�0020647�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������ ZQ&�8ZISIU?XݴO0ݧ.Dߡ4gB.. j7Ԥ{h**lM]4@i|Q%=A @Mn jNE/8aF?8'āLFDIb! (?>NnqxKPW3x!;{ez˘BJ!U9 L+WES^!vUvC/.r6GV۩)M([gC{F4ͲL��"Steve Bannon <steve@breitbart.com>S�>!ᩏ&Ra$"E܃ٱZQ& g�  � E܃ٱjliD:v2f&FD*2芡P4+٩!7E6SOOQU섷|EfRkOs7ј(g| Ns U-m\3Žy]С%ȍ<Ae,2;t S�e1dN|"&|Qo+9Wܹk/l"ߌc<@RyDqz!k3>F-%H^ ZQ&�gߜMc)bVy{s/tp^M)]񢇕d)KSJ'' x;ߌ0+I%>E;I@"% zW`+Lr`k?^ ucV-9i<Ex2:`9J{j-yMfmqRpIMQ6vI6GS1}!D7%dޜT'$jcP) R<]_��6� !ᩏ&Ra$"E܃ٱZQ& � E܃ٱgAY[z2N1PXՙ #pjx{8ȧ[QfZdW{]^Dw3HdHᴐ2TQzڲX^?9|N+ 埲O(~-/T4+`jPǼ>6Ai9FF ^+$<K;3X;(bq7Ȁë)ɥEh91=+�e_:ۃt"w|9 z{ZP9 n#{HT&c.sS)xsqDa�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/keys/bannon-ivanka-signs-all-uids.gpg������������������������������0000644�0000000�0000000�00000006275�10461020230�0024051�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������ ZQ&�8ZISIU?XݴO0ݧ.Dߡ4gB.. j7Ԥ{h**lM]4@i|Q%=A @Mn jNE/8aF?8'āLFDIb! (?>NnqxKPW3x!;{ez˘BJ!U9 L+WES^!vUvC/.r6GV۩)M([gC{F4ͲL��#Steve Bannon <steve@whitehouse.gov>T�>!ᩏ&Ra$"E܃ٱZR  g�  � E܃ٱ�+J}LAl@ڨ{Xm" ֢oQw=4!k\jwD<݋A|%! 8{m 1oŐ+`n<c\QLW~tKVOc$A䮣KS&P1{}ԵF0�Q~asfG|Vty: (.k{xwZxT<ΔIfFL$Etp+68pgvݭTa~@ҲE3%4]T�>!ᩏ&Ra$"E܃ٱZQ$ g�  � E܃ٱ�VYI)$uS6.jCj]d.nc%kK<wE ^p[M oαƘ!ee_Uq#2]#F Jb!ab1&XC}ptwF(WShZJ0Z ժivFOB]  >ă%vF6Ls1UH es])y{E3�!(}YS ZR(y�  �_.;Zܗ!'_:0 4Y끑3 pgЁiZbۓ,$gG~˄q�Rk8JR `=yPH Űg79K<ů%^Jspqu1~GMt# i oq<7<O[-z#NxhSZ` a>|yDb@?' Bm8\2|U[|ؔ=]l,$~?"Steve Bannon <steve@breitbart.com>S�>!ᩏ&Ra$"E܃ٱZQ& g�  � E܃ٱjliD:v2f&FD*2芡P4+٩!7E6SOOQU섷|EfRkOs7ј(g| Ns U-m\3Žy]С%ȍ<Ae,2;t S�e1dN|"&|Qo+9Wܹk/l"ߌc<@RyDqz!k3>F-%H^3�!(}YS ZR(y�   !ı+J2wsk|y[#?V+_wЊ�%�Kr-ZK |B&{r%J'IDgLJ:ڡB|*bQKݪDEX''yd.NlsN<;;c+%s׌  jCU>5 1NŊ<oa"* OڇTRU^h*�/:R0~?Steve Bannon <steve@fox.com>T�>!ᩏ&Ra$"E܃ٱZQP g�  � E܃ٱ�I .bvveɹ[amKtmP (SKZKo/C'< 2FN >.o5Ew3 eJ\dy!JK;FTPBaB l#\Hߝw}ʗJ*]^`ޣr#h䚌j4]-)̞uR Hh; xk!O IvE z_|W_QO'TV-3�!(}YS ZR(y�  6>T\k͟G ͵,>]]"m @1s>$x.͂D r죢k599@QNyU,5'/)bsLY=r-¶9bHGMf AjiKQH˕gU`5-|꺵mxC@/ViԢml!y�S3LLdUCO:o[* 3透^pݓuA#*L]G ZQ&�gߜMc)bVy{s/tp^M)]񢇕d)KSJ'' x;ߌ0+I%>E;I@"% zW`+Lr`k?^ ucV-9i<Ex2:`9J{j-yMfmqRpIMQ6vI6GS1}!D7%dޜT'$jcP) R<]_��6� !ᩏ&Ra$"E܃ٱZQ& � E܃ٱgAY[z2N1PXՙ #pjx{8ȧ[QfZdW{]^Dw3HdHᴐ2TQzڲX^?9|N+ 埲O(~-/T4+`jPǼ>6Ai9FF ^+$<K;3X;(bq7Ȁë)ɥEh91=+�e_:ۃt"w|9 z{ZP9 n#{HT&c.sS)xsqDa�����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/keys/bannon-ivanka-signs-base.gpg����������������������������������0000644�0000000�0000000�00000003011�10461020230�0023232�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������ ZQ&�8ZISIU?XݴO0ݧ.Dߡ4gB.. j7Ԥ{h**lM]4@i|Q%=A @Mn jNE/8aF?8'āLFDIb! (?>NnqxKPW3x!;{ez˘BJ!U9 L+WES^!vUvC/.r6GV۩)M([gC{F4ͲL��"Steve Bannon <steve@breitbart.com>S�>!ᩏ&Ra$"E܃ٱZQ& g�  � E܃ٱjliD:v2f&FD*2芡P4+٩!7E6SOOQU섷|EfRkOs7ј(g| Ns U-m\3Žy]С%ȍ<Ae,2;t S�e1dN|"&|Qo+9Wܹk/l"ߌc<@RyDqz!k3>F-%H^3�!(}YS ZR(U�  �̿&+%WǴ9z|h 1:Dz=MmO2˜q,V dVs}X̂:uӣqg֚(Ȩy1AS*nكZָ+~Y9{t?]2*4Z %Zi_:SILRJS`L(zAle $OO&:~{ *ɵIHx\�bN6'~%U}- /ʗ#:3rK �xXӅ\)XY. KN{_ ZQ&�gߜMc)bVy{s/tp^M)]񢇕d)KSJ'' x;ߌ0+I%>E;I@"% zW`+Lr`k?^ ucV-9i<Ex2:`9J{j-yMfmqRpIMQ6vI6GS1}!D7%dޜT'$jcP) R<]_��6� !ᩏ&Ra$"E܃ٱZQ& � E܃ٱgAY[z2N1PXՙ #pjx{8ȧ[QfZdW{]^Dw3HdHᴐ2TQzڲX^?9|N+ 埲O(~-/T4+`jPǼ>6Ai9FF ^+$<K;3X;(bq7Ȁë)ɥEh91=+�e_:ۃt"w|9 z{ZP9 n#{HT&c.sS)xsqDa�����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/keys/bannon-the-donald-signs-all-uids.gpg��������������������������0000644�0000000�0000000�00000006275�10461020230�0024617�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������ ZQ&�8ZISIU?XݴO0ݧ.Dߡ4gB.. j7Ԥ{h**lM]4@i|Q%=A @Mn jNE/8aF?8'āLFDIb! (?>NnqxKPW3x!;{ez˘BJ!U9 L+WES^!vUvC/.r6GV۩)M([gC{F4ͲL��#Steve Bannon <steve@whitehouse.gov>T�>!ᩏ&Ra$"E܃ٱZR  g�  � E܃ٱ�+J}LAl@ڨ{Xm" ֢oQw=4!k\jwD<݋A|%! 8{m 1oŐ+`n<c\QLW~tKVOc$A䮣KS&P1{}ԵF0�Q~asfG|Vty: (.k{xwZxT<ΔIfFL$Etp+68pgvݭTa~@ҲE3%4]T�>!ᩏ&Ra$"E܃ٱZQ$ g�  � E܃ٱ�VYI)$uS6.jCj]d.nc%kK<wE ^p[M oαƘ!ee_Uq#2]#F Jb!ab1&XC}ptwF(WShZJ0Z ժivFOB]  >ă%vF6Ls1UH es])y{E3�!T%z�Hr%ZR(� Hr%�yOaoQ/=Ovc6F%?Sa_K~/w+6Ӧ'@+Ej(##x{3Hr 1}Đ`p Аboz:I 0ؽ| -)Ucq^HZ`O 'a^_vSCp J@/kY4{Y<y a']96 c"@Gs/ Le8\ "Steve Bannon <steve@breitbart.com>S�>!ᩏ&Ra$"E܃ٱZQ& g�  � E܃ٱjliD:v2f&FD*2芡P4+٩!7E6SOOQU섷|EfRkOs7ј(g| Ns U-m\3Žy]С%ȍ<Ae,2;t S�e1dN|"&|Qo+9Wܹk/l"ߌc<@RyDqz!k3>F-%H^3�!T%z�Hr%ZR(� Hr%"y!G#R!|{FI|�@tck!@ݙ ȥ0sUhMGkͺN1Xo]> B L% Q2&F(~_U kjœk<:]^p(^CљϏݞ8dTg$1kK/&܍UBf5<,,&l2SΦgZ$W37$qԽʞ<-Om9rHJ@Steve Bannon <steve@fox.com>T�>!ᩏ&Ra$"E܃ٱZQP g�  � E܃ٱ�I .bvveɹ[amKtmP (SKZKo/C'< 2FN >.o5Ew3 eJ\dy!JK;FTPBaB l#\Hߝw}ʗJ*]^`ޣr#h䚌j4]-)̞uR Hh; xk!O IvE z_|W_QO'TV-3�!T%z�Hr%ZR(� Hr%MW5_qLxվXlD?b2OuZroZ&`!,$G}ٙr*+8/Jd0>aڏ)Fa;Q zcp|ʺ|^?%Djl_ݒu�C6a:I$ k?R)Ok(kjLr1 QPnSoH##ڲlRHGqkuI^ʵB*d-aqdN)\ibJ#~ ZQ&�gߜMc)bVy{s/tp^M)]񢇕d)KSJ'' x;ߌ0+I%>E;I@"% zW`+Lr`k?^ ucV-9i<Ex2:`9J{j-yMfmqRpIMQ6vI6GS1}!D7%dޜT'$jcP) R<]_��6� !ᩏ&Ra$"E܃ٱZQ& � E܃ٱgAY[z2N1PXՙ #pjx{8ȧ[QfZdW{]^Dw3HdHᴐ2TQzڲX^?9|N+ 埲O(~-/T4+`jPǼ>6Ai9FF ^+$<K;3X;(bq7Ȁë)ɥEh91=+�e_:ۃt"w|9 z{ZP9 n#{HT&c.sS)xsqDa�����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/keys/bannon-the-donald-signs-base.gpg������������������������������0000644�0000000�0000000�00000003011�10461020230�0024000�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������ ZQ&�8ZISIU?XݴO0ݧ.Dߡ4gB.. j7Ԥ{h**lM]4@i|Q%=A @Mn jNE/8aF?8'āLFDIb! (?>NnqxKPW3x!;{ez˘BJ!U9 L+WES^!vUvC/.r6GV۩)M([gC{F4ͲL��"Steve Bannon <steve@breitbart.com>S�>!ᩏ&Ra$"E܃ٱZQ& g�  � E܃ٱjliD:v2f&FD*2芡P4+٩!7E6SOOQU섷|EfRkOs7ј(g| Ns U-m\3Žy]С%ȍ<Ae,2;t S�e1dN|"&|Qo+9Wܹk/l"ߌc<@RyDqz!k3>F-%H^3�!T%z�Hr%ZR'� Hr%P[]0% U$0,UZ@LW{Y@_C3 y6 YUKG6}uwiywsD$^P;ی-DwjK)Mz=&Kv(J1)M({u ( JR2t0OgGM>{<@Qܹ~d//&+*Q泌s_/B4aDM)3Ռ ZQ&�gߜMc)bVy{s/tp^M)]񢇕d)KSJ'' x;ߌ0+I%>E;I@"% zW`+Lr`k?^ ucV-9i<Ex2:`9J{j-yMfmqRpIMQ6vI6GS1}!D7%dޜT'$jcP) R<]_��6� !ᩏ&Ra$"E܃ٱZQ& � E܃ٱgAY[z2N1PXՙ #pjx{8ȧ[QfZdW{]^Dw3HdHᴐ2TQzڲX^?9|N+ 埲O(~-/T4+`jPǼ>6Ai9FF ^+$<K;3X;(bq7Ȁë)ɥEh91=+�e_:ۃt"w|9 z{ZP9 n#{HT&c.sS)xsqDa�����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/keys/bobs-cert-badly-mangled.asc�����������������������������������0000644�0000000�0000000�00000004657�10461020230�0023047�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������-----BEGIN PGP PUBLIC KEY BLOCK----- Comment: Bob's OpenPGP certificate mQGN BF2lnPIBDAC5cL9PQoQLTMuhjbYvb4Ncuuo0bfmgPRFywX53jPhoFf4Zg6mv /se OXpgecTdOcVttfzC8ycIKrt3aQTiwOG/ctaR4Bk/t6ayNFfdUNxHWk4WCKzdz /5 6fW2O0F23qIRd8UUJp5IIlN4RDdRCtdhVQIAuzvp2oVy/LaS2kxQoKvph/5pQ/ 5 whqsyroEWDJoSV0yOb25B/iwk/pLUFoyhDG9bj0kIzDxrEqW+7Ba8nocQlecMF3 X5KMN5kp2zraLv9dlBBpWW43XktjcCZgMy20SouraVma8Je/ECwUWYUiAZxLIlMv 9CurEOtxUw6N3RdOtLmYZS9uEnn5y1UkF88o8Nku890uk6BrewFzJyLAx5wRZ4F0 qV/yq36UWQ0JB/AUGhHVPdFf6pl6eaxBwT5GXvbBUibtf8YI2og5RsgTWtXfU7eb SGXrl5ZMpbA6mbfhd0R8aPxWfmDWiIOhBufhMCvUHh1sApMKVZnvIff9/0Dca3wb vLIwa3T4CyshfT0AEQEAAbQhQm9iIEJhYmJhZ2UgPGJvYkBvcGVucGdwLmV4YW1w bGU+iQHOBBMBCgA4AhsDBQsJCAcCBhUKCQgLAgQWAgMBAh4BAheAFiEE0aZuGiOx gsmYD3iM+/zIKgFeczAFAl2lnvoACgkQ+/zIKgFeczBvbAv/VNk90a6hG8Od9xTz XxH5YRFUSGfIA1yjPIVOnKqhMwps2U+sWE3urL+MvjyQRlyRV8oY9IOhQ5Esm6DO ZYrTnE7qVETm1ajIAP2OFChEc55uH88x/anpPOXOJY7S8jbn3naC9qad75BrZ+3g 9EBUWiy5p8TykP05WSnSxNRt7vFKLfEB4nGkehpwHXOVF0CRNwYle42bg8lpmdXF DcCZCi+qEbafmTQzkAqyzS3nCh3IAqq6Y0kBuaKLm2tSNUOlZbD+OHYQNZ5Jix7c ZUzs6Xh4+I55NRWl5smrLq66yOQoFPy9jot/Qxikx/wP3MsAzeGaZSEPc0fHp5G1 6rlGbxQ3vl8/usUV7W+TMEMljgwd5x8POR6HC8EaCDfVnUBCPi/Gv+egLjsIbPJZ ZEroiE40e6/UoCiQtlpQB5exPJYSd1Q1txCwueih99PHepsDhmUQKiACszNU+RRo zAYau2VdHqnRJ7QYdxHDiH49jPK4NTMyb/tJh2TiIwcmsIpGuQGNBF2lnPIBDADW ML9cbGMrp12CtF9b2P6z9TTT74S8iyBOzaSvdGDQY/sUtZXRg21HWamXnn9sSXvI DEINOQ6A9QxdxoqWdCHrOuW3ofneYXoG+zeKc4dC86wa1TR2q9vW+RMXSO4uImA+ Uzula/6k1DogDf28qhCxMwG/i/m9g1c/0aApuDyKdQ1PXsHHNlgd/Dn6rrd5y2AO baifV7wIhEJnvqgFXDN2RXGjLeCOHV4Q2WTYPg/S4k1nMXVDwZXrvIsA0YwIMgIT 86Rafp1qKlgPNbiIlC1g9RY/iFaGN2b4Ir6GDohBQSfZW2+LXoPZuVE/wGlQ01rh 827KVZW4lXvqsge+wtnWlszcselGATyzqOK9LdHPdZGzROZYI2e8c+paLNDdVPL6 vdRBUnkCaEkOtl1mr2JpQi5nTU+gTX4IeInC7E+1a9UDF/Y85ybUz8XV8rUnR76U qVC7KidNepdHbZjjXCt8/Zo+Tec9JNbYNQB/e9ExmDntmlHEsSEQzFwzj8sxH48A EQEAAYkBtgQYAQoAIBYhBNGmbhojsYLJmA94jPv8yCoBXnMwBQJdpZzyAhsMAAoJ EPv8yCoBXnMw6f8L/26C34dkjBffTzMj5Bdzm8MtF67OYneJ4TQMw7+41IL4rVcS KhIhk/3Ud5knaRtP2ef1+5F66h9/RPQOJ5+tvBwhBAcUWSupKnUrdVaZQanYmtSx cVV2PL9+QEiNN3tzluhaWO//rACxJ+K/ZXQlIzwQVTpNhfGzAaMVV9zpf3u0k14i tcv6alKY8+rLZvO1wIIeRZLmU0tZDD5HtWDvUV7rIFI1WuoLb+KZgbYn3OWjCPHV dTrdZ2CqnZbG3SXw6awH9bzRLV9EXkbhIMez0deCVdeo+wFFklh8/5VK2b0vk/+w qMJxfpa1lHvJLobzOP9fvrswsr92MA2+k901WeISR7qEzcI0Fdg8AyFAExaEK6Vy jP7SXGLwvfisw34OxuZr3qmx1Sufu4toH3XrB7QJN8XyqqbsGxUCBqWif9RSK4xj zRTe56iPeiSJJOIciMP9i2ldI+KgLycyeDvGoBj0HCLO3gVaBe4ubVrj5KjhX2PV NEJd3XZRzaXZE2aAMQ== =NXei -----END PGP PUBLIC KEY BLOCK----- ���������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/keys/certificate-with-unsigned-components.asc����������������������0000644�0000000�0000000�00000024360�10461020230�0025712�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������-----BEGIN PGP ARMORED FILE----- mQGNBF6WgJUBDAC0tSD9LmOUmmpkwHme1yh+UREIMS0UoW9LLmLzrCG9c9ujw4ar sagPVJJSb6XoMivZ3UKIOvlLvBeXW5Fa2HytBinubszcQeOgzUI1yvgHY97fc3Ox 3OxPU7ptXU00QOuQCOhWz7Goy72+hvRl6eL9SdTa00waUwXCEtrywdozvMruDF7l BFtrf54Oi5CstqGhDrq8vne03zIHSMzjOYLv0P4n/1PEBRdJ5RtN6/QWFRdO/W1/ a25N9GHn7yGeG8TF9gh1e7+isZtk7ALJKOULLEYbR5wvtHNbtqxN/QbriSJ9sqyI +X+JvQmOKAHdcvt/HQdVXO66LW6f6QEQKsZYcbRWqpxb29B8XEuwlUfFMV16fmqx BaetrVzbUvwz4lMKpgpFKxPCF34tCDKT5EBGxdyIzNmTpVwLqBxiOCE9gX27vSpc jkdOZBd4bLiMPvt4GSO8OIPmZdB2YOsfJ2TB3T3+RnSRL31g12caUXScl6NQOZLS PPiXKtFycTHqWNEAEQEAAbQRYWxpY2VAZXhhbXBsZS5vcmeJAdQEEwEKAD4WIQRt 6LHZxBo01nq4m9K225sfcWqQmAUCXpaAlQIbAwUJA8JnAAULCQgHAgYVCgkICwIE FgIDAQIeAQIXgAAKCRC225sfcWqQmLPOC/wJCYG1nEMY+tPAiyPRGQfX5S6ef7Y7 7kJ5w+QnGnr7BLC07ZCYM1iJY8rIre831SxzVnSEv5V9THgwzQ/2Em9qaRH5cBsJ khEmvJYNCWC9O/cAedhSXEb3Sd3yMAAgnDFh418mB5O1fDbTXjKBrhhilJJ4jBKN x6JtV+CvhCauZSZyN9bmAc/yDoqGhfTiIwXMkkEBYraSAULYgREF07+s7MwMyLAn wlY0qMVqO3XNtz5zKxvgOdclIvCz7KrwbwqNM1RDrNHrOCd6Hk1IBMZfsDFryWaz cyI23Kv+/9xK9YRrcTurobOxhry5lAOSCU28xDUEnsDsg165dNXbKXYzL9OxP6Z3 k/7jJmj6TkqJjx3Qai0hhJSM1WHV1VuYg3LDZY3kyGrp9d8aG7V1d1N2eI2WkHB9 RJnJ7c0F+EnxUDFDgM87NwqjHvszjh/19UYgj+Iy36wgTHkZwWRECSTghTlhFgMF Qfa2gMLadwPvYdngZWJ2Qjcx3F7cxGmdkDzRwW7BbAEQAAEBAAAAAAAAAAAAAAAA /9j/4AAQSkZJRgABAQEBLAEsAAD//gATQ3JlYXRlZCB3aXRoIEdJTVD/2wBDAAMC AgMCAgMDAwMEAwMEBQgFBQQEBQoHBwYIDAoMDAsKCwsNDhIQDQ4RDgsLEBYQERMU FRUVDA8XGBYUGBIUFRT/2wBDAQMEBAUEBQkFBQkUDQsNFBQUFBQUFBQUFBQUFBQU FBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBT/wgARCAACAAIDAREA AhEBAxEB/8QAFAABAAAAAAAAAAAAAAAAAAAACP/EABQBAQAAAAAAAAAAAAAAAAAA AAD/2gAMAwEAAhADEAAAASof/8QAFBABAAAAAAAAAAAAAAAAAAAAAP/aAAgBAQAB BQJ//8QAFBEBAAAAAAAAAAAAAAAAAAAAAP/aAAgBAwEBPwF//8QAFBEBAAAAAAAA AAAAAAAAAAAAAP/aAAgBAgEBPwF//8QAFBABAAAAAAAAAAAAAAAAAAAAAP/aAAgB AQAGPwJ//8QAFBABAAAAAAAAAAAAAAAAAAAAAP/aAAgBAQABPyF//9oADAMBAAIA AwAAABCf/8QAFBEBAAAAAAAAAAAAAAAAAAAAAP/aAAgBAwEBPxB//8QAFBEBAAAA AAAAAAAAAAAAAAAAAP/aAAgBAgEBPxB//8QAFBABAAAAAAAAAAAAAAAAAAAAAP/a AAgBAQABPxB//9mJAdQEEwEKAD4WIQRt6LHZxBo01nq4m9K225sfcWqQmAUCXpaA 9AIbAwUJA8JnAAULCQgHAgYVCgkICwIEFgIDAQIeAQIXgAAKCRC225sfcWqQmBqa C/40qFTh1UqG7oHrRdkthAyJ6FGl86pRpRlfpRVBpNGTVWOcYRfM9Y+zhFDj/WYR OGhgowLkXG4P51ZcLb46N42v3LZX0zrqFvWpwQcgjcwqwIyjlpy0NeopkXQCgN0r hWxnrdz+tbnR98Dv/+hdh5C18cYgyRVpUFvGGHXtENdg1400xYb27eRS+deyaGIU wPHvNngm5+1MnyatEr+N4e54BICgFuYaWE03PwnEsG8Z9L/I/yWuLZ/EeXic9+8Q odr9Jg7BcmQ98ejuIgVh81BhqwAxoXZxpGQ+4X+N8G6EERgsZ91J8IVgObaSO/Sw rZgRGq4b6+DZMCS5NqxjpugAcL0T4IGOEJOY5Kn/EN7gzdqJO7+Wi6tqezMCvQGu U11/Nj2CI9P8N66dH3/Jq4p5Wh5nUIHMd41EzhxbX7Z5CpNXcW/DOaX7DNRiQRm9 M7nydWFfdQSpQeGdpaS6zuEqCEm9yDTJOnovTufBtO5FOfJnHrtfTnZi5VQUBwW3 heO5AY0EXpaAlQEMALqtk3nsVPmVQHiNLmEPw1b1TXKYNdM2E65JLQTLcHeCJxGW yR+S3T76mELZAmgm44D0g6LGxe027iSH/OwPC5AHS9eWEZYrN2ExdC7qJlb/sKVN Sbuk33DsjLxiteriCX/62D8O8YcF1E5gZCi604bJRVif9knY8GFQ/tswVCklsUj6 3h1qVAdljlwLWMUoUedPF0UOYU2b/i0NQbXH017jBAgfus11ivX5I5Gf/+PmYAdK Ex84Zj0FKGv1wmupLjXEDPLi790iztke5u7IIoCnbwrdAKQmXPtqRTaDbByypVZL zytCubWaDzP8R2k+pw7MxxIh9RK4Agp1r7GNET63zAOyS1fi0KPsG+wttr6NPriW pmtCysKke4X6fHbVLXsBAi/ww15bfX3Y6gmZLzLFlNt3vPCWAsxUE46ZSTNhE9Ya f8wMyw9jKaBQartErFhSdB/mRO5IONWQUUrjdDLmdEIDGkX3bwPzx7W+f3Pi7fU2 0bW7SBC+qkN0VmfH2wARAQABiQG2BBgBCgAgFiEEbeix2cQaNNZ6uJvSttubH3Fq kJgFAl6WgJUCGwwACgkQttubH3FqkJjzNAv9HDRy/v4EsmAHFr3JMqEQKfyaBVxc k7LgBVN2JeM0gfsA2KeZ4MzHBXyd63FzUBZgXsxwhz1LEjhzyQcGfhUD5GGkUsXw GPLP1npCb0ThyZdvAaW5g9W7q0Ogdd5g68G7y0wXTpPlq4LCCSbdAcbSJJgBLY+z PD0sqFOZjKNtSR6Hewq6P9vTQFak7XVTaDj1+eV9LkQkSC/uPdG/SeOuW7KNmZK1 3RcpK0N940peERaeFogOUdt0z64VH1USrSlCASVnZVs8E3p8q2HmYXj1Jus4tj6n FXkCrANgbFiwm8NBSTmAgyUSXA7vlZxcSbgW6UJP2yc0Mjg9jIzzIMxNRbPag40Y tVzCTU5xwlqXjtzV1qKDXtx0llQ2BGVOPtfB6UWBjw2c8vtReiqKpJeA6cvt+J2h 9HYHiJ8JtFA6UUlOtix20uZGrIlaaH3jm24epdi0wPSmMzZwjH/H+XCRhJ9/Adeh fJAcRtZrPyNESOwys+m9P/I5qdLmydQlejyTuQGNBF6WghwBDADECIKU2+727kxB K1q6n2lnO56VbRdQ5vx4MWomRMAzpd0OIsOwLceVbgSlWcSnEv4alDhLHqP7YV7o h2l4hqqDpOl0RjfK+AXsWS8uPByFb4Dm4ruH/6Dt0ZevUKrFiBZvGYkHMGeWhg9k kl2j7QRTXgRs3HyIp2uxwopXbW8qtfu2RGCIc+ZQV44ImQ6YpVUG6Lm79+nLspps H/TIvx2MgWHf8sODrsQJE6wKuZmWvx+uGcNivduKR7foUkbK1kn+zI10Iycpfh+8 iwOx9mXU+acTM43o6b8F/8RmsSnuKXxfUsKLXkTfGCHUcg42hkKlPXdr87RuK7VR p/FjBPZP2qLhbuFC1utv9ypFUXmc6PgSdNyo6u3m8c+liPWhaqtdvyVPqPVGxqmw A7kBLAOJraKnP3Q0SLaMZ41n5p2dMTYU2KRiDgIg6/MMs442UMmEXcBRKKxRa2P0 mTj01kAemA02N9piMDPPLxRmzBqVwkCjKfOw0d25Qhow/SkOxrkAEQEAAYkDbAQY AQoAIBYhBG3osdnEGjTWerib0rbbmx9xapCYBQJeloIcAhsCAcAJELbbmx9xapCY wPQgBBkBCgAdFiEE4gbIAxnfldG9AK7DQfF4hIJXpCAFAl6WghwACgkQQfF4hIJX pCD7UAv+Kg4o6zdFhR9q/z7h635MxB88Zb500cZpLnGmNsCaHQmiOCHB3yFNs8lS iJ5Z2FW/CC6bry1AYJIpwC7JizJFTEmlcfuo3nlZtQ3Mk/PQLb7zA+pHtKp2+WmX DFW4O+1ovzTVBBtYfYXp3+9s7IzWS/lhQfRh/WYaPAnjoSr/vmRTGMNhpaAVPGqf 0M95wZ6dhntsMfcKkiJj1LFOJuz1Bdo6RoD2WAVr3GcC77v76MW6+QZSY4ByZhJU qcQTvkQn0JUcXg+N+uu4iYQz7Aprj+wLIcbMbZ5/S9tWaa0qLT7Te/hCzqe5AxUD o1aYy8bs4idQNq+QMO9VOnbzDvMZ4qoJYU+fkYRQRIoniyaUO+G5tfLx9nEWsta1 q/5C+XW0jxgB21A0/RAbp4qJYSVyBmtXYhW8VNbtBUYGS3bD8XjcJpDo08DBkmaj rOHehEPgnT7aLobtDE8miaxkQIfE07xG4IMiApJr6D9ftVx1P3R0w3BN8djcgqkI uTL8hMmCyQ4L/1hwtv1b2uR3/RRKSqPhEIPks3OlzeZITot+i/tyAnp1wWCsBPbv 8XvWuzs5phaSJWXmGP/0v/8c6+Kex7Qvn3KZ8RjPHrk5uNZx6WwPxScnMTaGzjfr VTN0D12YO9zwmOqW50j/BCIcfEupNe+MClQw3KigN0kYp4fzbR6tAX7qv7YFfSxT gDBkafslBeC6CuKmtbiNUaVFBRPRWslJUOu9tnGs+1uTKzpd2CHo033aGfc5fxmt jQNFzLcxTWHNsLl1tAW5Hnsyk0c9a/70Byfa7LlVXuSnWR1znNK6pBPg9qY+VHbH bNZzsQ026pYiro3dykpG3SYZdiCe/lmqNvU0qeUqV7m/ATp3YlIzc7vOU2FH2yvA Uol3uK3koCj1tBR2Zwf/jQq+PUAjMFpraXV6ZJBYzuG8hEnSqlYh+wMypmr0mxTC uM/zWbJF8rP5kejIVIKdqnNfLZnFrOFjC55/vNZE8G8YjHnCar3nSXuDBmfINb5b WgUrKq5r58Q6BbQRYWxpY2VAZXhhbXBsZS5uZXSJAdQEEwEKAD4WIQSw3cga2Ufx BLWvKlXK2ELu1W4DjAUCXpaAnwIbAwUJA8JnAAULCQgHAgYVCgkICwIEFgIDAQIe AQIXgAAKCRDK2ELu1W4DjHrkC/0dLda7+jz6Wla/h1ejGpgEKsZOBxf+07RuABwB NPON8MNTNTu21K2tUB1egz5xEuARzg2aqnsIidv+f16Vc+haf5PefkznZmlt6BgM IgJd03QzC0JACwGGqSb/naz5zmI3+igZaaqA86s08YubwP1NSnqVf8BbL03lG2Zz X4g5U6FGAsYRpROEul2Jk49dRF008DfMmMvl43TEqsobwy1N6yo85n9nsuijzAhm 09IFtgMS8fRRSKrpeLNRjcAd8gXqEJXz5FnnEFRSTGoegxI6oL+v3cnQVhwqLUNH yHbm4RBiZbZtc+zYfD8SnII7tzjYEEYn61cksMPOK5ElDU9Jj/Vh5BWR1xbEqbci l4NyhONAkPCpy/AOFYbvv9aaS7KaqTmMZL9t/OkUDwYV3TNCM+949xbZpkw7Pt40 1IdRC72OSU5WWriVhXKDEOipCtddWYxFVff5SAYD5lbLXtIRG8VdeHIYx9IYB9Ra Euoeg7gjzCtSNaAXu3wMQTx5eOPRwW7BbAEQAAEBAAAAAAAAAAAAAAAA/9j/4AAQ SkZJRgABAQEBLAEsAAD//gATQ3JlYXRlZCB3aXRoIEdJTVD/2wBDAAMCAgMCAgMD AwMEAwMEBQgFBQQEBQoHBwYIDAoMDAsKCwsNDhIQDQ4RDgsLEBYQERMUFRUVDA8X GBYUGBIUFRT/2wBDAQMEBAUEBQkFBQkUDQsNFBQUFBQUFBQUFBQUFBQUFBQUFBQU FBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBT/wgARCAACAAIDAREAAhEBAxEB /8QAFAABAAAAAAAAAAAAAAAAAAAACP/EABQBAQAAAAAAAAAAAAAAAAAAAAD/2gAM AwEAAhADEAAAAVQf/8QAFBABAAAAAAAAAAAAAAAAAAAAAP/aAAgBAQABBQJ//8QA FBEBAAAAAAAAAAAAAAAAAAAAAP/aAAgBAwEBPwF//8QAFBEBAAAAAAAAAAAAAAAA AAAAAP/aAAgBAgEBPwF//8QAFBABAAAAAAAAAAAAAAAAAAAAAP/aAAgBAQAGPwJ/ /8QAFBABAAAAAAAAAAAAAAAAAAAAAP/aAAgBAQABPyF//9oADAMBAAIAAwAAABAf /8QAFBEBAAAAAAAAAAAAAAAAAAAAAP/aAAgBAwEBPxB//8QAFBEBAAAAAAAAAAAA AAAAAAAAAP/aAAgBAgEBPxB//8QAFBABAAAAAAAAAAAAAAAAAAAAAP/aAAgBAQAB PxB//9mJAdQEEwEKAD4WIQSw3cga2UfxBLWvKlXK2ELu1W4DjAUCXpaBAgIbAwUJ A8JnAAULCQgHAgYVCgkICwIEFgIDAQIeAQIXgAAKCRDK2ELu1W4DjMNTDACrxvf/ ZTfQkXaIZHrvjR5zZsUvqGiIgZ0tf7u5rMFe3I7TD7dbqrD+3gBarGtjRLYAiLe6 49zMQXkTQL12xxEboTxpmXWnccM2/fxmDwWA/0Kettsc9lF3vkzoOxnmhWkzjQJ5 pIkxrGUdIzWEssOO4YYYSRf5Ha5KfvfoRBmDnp6KcP+BdiveqR84hz+trg8dR5K7 P58QEsAC93vr42/eTYAUrV5RJHE1Y6RSrPPlISf1JfnhGJpOXi0eD1GALjBrG9o8 4u6mAfRhtL3CzY1aQOAWN96n9HAig8KaOn2DAbWIG2KlAfLWD/SccTmzya7Ap9YJ cOgDtfL9g/MXP7qvxJ+EsfRrcaCRbTFRCZCkK44kydjf6ApmbRnw/lwqcAnPsXIj +8hHweJ8MH/dXbr5S3zYKp6M6cKoXptB5H9199VuV2BigrKqej34dh6igS57qTW/ fnBpKGZwVy8m5rUULag3seAmSNFHyL20hyM2AvrBXrRwiUrUfa0maJlvKhK5AY0E XpaAnwEMAJUd9+lY7oUVysG0zhcRpqM8YLhBTd+EG1plUwH/7Iy64OBgqKkie+0V 1UIAL0eqorN+kgIMjh4d/BiRIDy+/DIdAukuetN+7qvJcm3d6fAEY/jalX2NgeJ0 hObsV9ykW85HJtpTF7NATwcm0xDKUeAcFZD1YWsP1qzJAnu1reeQvmQloJf56j2O xQ5M+z4PGRSFVwx0s/5SmLEHlHkO4BWJ17eZvn17RSrLB3C47eBHZmKkjLvz39jV bXx7Ek0yfjdIYoCDNUPSsUx6gKqPfrCtwtjrj2moNL/nwDUoGQUcQhfvQWuvbCFr uby1s12nuHuc+f57QakP5VA6xd/gWrVV/rzc2D9VwN/8I+Wi/dZiQccGkCWBvcA1 TeKYGF++O+EVFhqkO0YqM/Q3Jg6DXuprsTMtbwbhng8XeZP2Jy1zsCwtrAk2dA9P JvgZR/wc7T1v3MAVgR1Y+HRzpckvc6go64MnfCXyjdPFEyN2YFDnYXjK2keL8OIW p8Cby6fMVQARAQABiQG2BBgBCgAgFiEEsN3IGtlH8QS1rypVythC7tVuA4wFAl6W gJ8CGwwACgkQythC7tVuA4zBigv5AToWuvbDT9/rzW0vkZa3gXklFo/U3oXI+bu/ 01RF/SUfYDgwNMiAmr//K63EO2zELS4TQ4d3uaGMDHWnlWn5SgYBwIUjaShFuEgy 9Q5vfIhFQ77hGqzMRXDkKlPtbLDb5PGrQVULDhFOL1+Nb0/aks6aTjcUq+XV4+7d 2YqrQAd6f366xxvRKlvEbx0D6dfiFNS4T9as4l+n5BB/1nLT8IkF/oK3b9384/yf 1vqfSWr0Hf1VwqvXn1alHuRgYrH1vB2zaXo+PrDFI/chilBFxnVbaXhV3xlWT8v6 3zoNM5OuRW5aQgce0teD0XkY4PnSS7uqOOrCb6+CyTYDF3+nqU0gmy4lvwPVSnUI /j7WGL/GuMRQdrzo61cF8fs2qeDcWv27HpkCmOBn9QnnTME/6Bi7/c/CQR+nunZM 7UzGJN7WEUMq0YzPqtqjKrc+yFmQQgwQoj0yMicLmZe2bS/1Sb/lkfTkQHjE3MIp FBUSkOoIYnLvRVyB5V5Lbx0sCLKMuQGNBF6Wgi4BDACmqhgCUwg+3Ob1up2GA4IM 0r1R+29hT3Wk0zFt3uQnnnkuJh6T0rLRsXckIqCSBAcI/VTITiRYhnTDPyMIJqIG zJFZpun6rVXlvDgo8ZQXJkaijMFmiBnEDCB1BPYAQpRo8pVRXqnaZHyFdxjAPuj0 uneeafVNC45RXYur2OVIaq92fnu9+BqWfSKEUl9Bit2r5XtV7plo6wwrUvUGlveF Mna/FNfrYpmvYjJ9fubZwREpX37ejqSFtDf/dtnqHszkCPGbXiAVg6kAkrV2uCBR E/AMvkdZ1hY0S9GGcb8eoq8xLX4sh1c2LWQD9W36FngFvZlqCtFUCvIQRq0ioJWH qm8pPSwlKDq6fMU6UEz8Zha/xZdfSvegE42pxhajXqoaspnjCx16h6AS84PbbWMl jTq4UvaYoVt5a1jnkOjBdnBJeESuD+Gy3DWoB3PyjfrHj/1oT+zshJ7PdWtiEilN FFwx0ljlx7xRmRZLUMwpW71yrhFYr8cU75kX1iMqrAUAEQEAAYkDbAQYAQoAIBYh BLDdyBrZR/EEta8qVcrYQu7VbgOMBQJeloIuAhsCAcAJEMrYQu7VbgOMwPQgBBkB CgAdFiEERVd+PCKfg5qAQ05ALNT6GuGQYA0FAl6Wgi4ACgkQLNT6GuGQYA28kQv+ IsIyoNWhxVw5rIEzCKyZiSRno7qyDTxl5uh6hwS90KDW2Ivc+JvgFL8juUKFk8/Q 9I1o+h1dlGv4kahxBDeEoZuaXxYDVFSsqm8u+9rhAnDVQCgXFiTLsfdH+hT0j5gW Cmlsz9qU3jqOUALINAHYo+6TMbJbQwu30HERRcqpKDbhP8HjpDwU1nFA66A/E57i EEuZCzU4babgoBWowFvRhaG2Iujrh3z4nb3XhYFZTN4OiyMxZE+MNELXPtkWPoWD HM0l8rhZbqJLQHkz0hkF/UPMdHe6sotWEjJe+wRPlFY2v50EAwlOgo1ikCTuuKhI TSwsNlwWXDuUOSQtw3HIjF3LmNXkb16v45N+TZB+2YL/Iq5BQB5SoYUOXylFhbvX CceFncJLFT/rNtohzDCFy+71wy08fSf5oS4tvI380fz2NABmz8ulxT2N6JOC8wBa W2jT5JS1fKvDd1OWnzt83Ej2QwNtyNLzzmAKtj+Y/gAKw13A+Q3yCnN3QgewZiGx mTgL/1Y9/m3fAAYEl3t3MlHkkPClE5+e19hZZAPAUdaDv1QaSBd0sjPQx7QE46YA 4FwueLD+tJHkMGKXN99upbIRnB2DbUno0x20K5u/h+4vp4VNrfbEIvwz+1sQCzGr bYHzTMvmb8Nc5qKtdorpprYt22l6khoayXpkfnWwqOpM8355SDiaHSzqyzgPnQPq nq8+1EFhLQKzsuhYSO+Jg771uw/dmLOPAVIBP2sr+XMmArg+8x+mpOB1HwAvY4hI XZL6KSJ+Qwmh0r4JoJSrwa0vtWwoc4KlKlrH46Wcbe4u9eJ+Sfpt2lzu1kKNkq0d m3zen7YwkOLMb07BKi2vke3/PI1ObdSV/soFMS1wECPqW34riElRrbmOTjYR1An6 OLXUT/7HpT0aB2xcAWYKMtMyKZVNc63wOy8Rz/pTJbuJrAU+APaRm0UktL9VFi4A bisCIbOyDUVZnS0OAMKdxTg2c97WBMfgxAguhpdT0cM0bcXCtLozEb8/uAoD0uYj EnvXeQ== =8niG -----END PGP ARMORED FILE----- ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/keys/corrupted.pgp�������������������������������������������������0000644�0000000�0000000�00000153031�10461020230�0020507�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������-----BEGIN PGP PUBLIC KEY BLOCK----- xsFNBFVtaNcBEADGvG/feYrSCKlkW2xAD901hAEKgebimSIMsE6AYq9CqOT+cFtK b0JvrkUXFDQzwt5jObmhoLkIzHvZKEnasjNuKMWIKhr8nOXfcz2MiNJhJZJ4qNCY lwAFG+CMlveEPemyz/z/hwntvHSiijImAzOnTdX8Cf5MycDrnrEIJRsMJwvHM6Rg S6ra1lO1sZwLrqpUk8MVn89RT5XLACutFglUGXzYS75L2pJ9nrISY3j8ZU88We3a QahuXXNoLdr3ZG1kvumWspWXKE9+lcQn2rDR4ePvwlzan5xDLRgrn1QJZmJu8sea BFuy/SEbrQ2pQi43SWeoNxI0pc5B5xnmxzeIt3AG8F6yVrMl7rBzyia+vYDu9DWS NZKo4iUTlqfNH/E2EkwyH1hCYXb6Vw8FKXqkkD4mQbrLkqEpGTTsoABm5Rt+xTKE qhc0PymQAqPeHK1kRtyv1gP9STZmnCW20F5iewW/gG4fJLryVwypAcBXM59VB+xS 5mvZCTBvji2oOojTzlKrr8Iv1OZD6dcHLANIwc5VSYYMODptSeREubDLr89XAkqT fn3WxHp+Iag2Ht/DKPPg2nFq78J9M861b7ulyKMJMdrczM37tb1wQujrYCbcCIGs gD0VEO2qZUeVlgoqpcD35LZotYH/freimthotrm1qrpUff/5vPVTZPjnRQARAQAB zSRKZWZmcmV5IEJ1cmRnZXMgPGJ1cmRnZXNAZ251bmV0Lm9yZz7CwZMEEwEKAD0C GwMCHgECF4AGCwkIBwMCBhUKCQgLAgUWAgMBABYhBGrvEcoJ3oBuadNwlwfTEC7x obmtBQJa54XVAhkBAAoJEAfTEC7xobmtQfgQAIoGWWFRu0SMEEG2QlcSllcnDBlX 8KdqZO4GdmmuioE7vUXlPLhzOUY6C+VvHHrSCZmQjdda9AikbBZiSPyCF8LzDVFf a9fsqS5C5O1Iczk4eFQosXISMXqco1eHDBx3iPpsy/N1gSD4v/7Xd6VFXmqKNik2 5s+KWgMnBbR+psYngC7LRqEfb0tgPkooJ5CxJcpUtafE4v86Vk3GOeKFmQ4Xqwux b9WR6LTHsPt1oq6yhqlwS9820nFPeK5NVj3nYhAUCWF/sUidTmCWa9KHy+0Yq64R wrGo+E/NxBTJiw3CDKyl0uNuZDr5eTa9MvzpIeY4WkI6rvwsD+OBrcrWSLsRNjW+ 7QGsLbg4Y9MBk2F2AE+sEkdMn2tXuF+A3LKdkbgJeinO857vnSLXUL5ru75vta4V qfKrR/VXcmp3XTxW4MsXIboiy2funVtsCvaVqY5iPbSCICUiU4tzaTJgGope0PlF aMp0xM4Q9uRrTCrdo/tB/9omJaJECtIrV80vmQJcJd1UfZXyVx4fXGnBSb57mXfP DgJGlbxWO3+Kz5Hazq0NXFHRIlBg8oq3Jh61aqWxdGSiBqj8/VQ+JZOb0te8W1Ly HBNacX5AMljrlTDRbfcyeRs9ENEHyFYZ2VdlM5GNQE9yvVV7tdwPrsf+znyALjo9 5txrIQc3V7QYxBegwsGTBBMBCgA9AhsDAh4BAheABgsJCAcDAgYVCgkICwIFFgID AQAWIQRq7xHKCd6AbmnTcJcH0xAu8aG5rQUCWtT9jQIZAQAKCRAH0xAu8aG5rfHD D/9gvC/BKsEuMNeCz53cJ/tw7LNWcq2/fy8jpIBPxZENdiz+basuzgLw28yCLvf8 VUdIhG59AOvo/LUphNQcxpi5BRkbGpKAAB1JgUelv7j+gW5kTMv7nggQDRk3qSOS cccd+QR1YqJ5y9jwMeSycjipjcXKokogDjnKTg0d669vA/AZRcpcmTZnoLVQo+ts AqkaidbWyahMmP9UylI4w0REWsl1I3Y8PuPoOKmuOYCUm25cjxomRZCAviuzYg7w e7golwGDv1NiX2u0WK3lwQ+0E3bqC997jw49Z7Dk5jQn+DT3l+PVrhgIYHbyMRZw L3lc9JAJUt8ZD6oufLY8aVOL/KoavXZxruaGf2ki0i4y3IqDFIcGBM9p0XbmTXRB 7ALc8fV/yYu9jlo0WsgmY24EZbUrGNr0qRbYi29SZ1gRCjybEvZF5bxaqtSaKHaJ C6uWePXfWDo45WJw3rn0OYpOncKzkg4O9qIvVWYt61sYARG/WjjpEZI2y3s5iZlh 4nQjM8JdBxL1uQtPnCUxF8IwXV7cKzDpe51gpW/RTsfHQQgkYLkANvjr4OuBA9NN Y2RSPIjipvbjwB29Fcd4PatYktFJsa2VHKqOQyUA5F+Nly3SmqMIfNoxBfIEMVn7 cOZiHPB6SY8QjBWhhit9s+Kkq7xStPaeUmkHWf6DPiQ0HcLBfAQTAQoAJgIbAwIe AQIXgAIZAQYLCQgHAwIGFQoJCAsCBRYCAwEABQJZMr7lAAoJEAfTEC7xobmtLCAP /RFVFtmDZywM9S1ZxF48e2wE50SvvYex1m1D10Fn8CKqjI453vbpzD8tiSjGNE/n 7NVX+n+iXrS1Mr/nVPgpmzEUyH+fx/G5z4awfWNWnc2rv4aoRYqUDImPEIG19CWl Ca+12tXs2xHFofQw2OjKqEe0sokM+xGt/4WZzCDAZ8IxAdOlGlb/QF7Y7n5zu7LJ effvp0sHpiIds3jKIs+ZTCaTfINWZYPYFiIWKJYGoYAqkzFTMCoT+lw+NezE44oB 6/vzObglidSwETOJJCtdjBruhNXAPkybTom3Uv4z9R1cuY2w87ud9dm3OPXhNTby h1jp/zSRcUv3eI8uHLFGPDeHz7r3XJCmpFp9RR+eXp5X92W/S1PhHnYqiG5pa1tA GmEFffDUaeDbDDgCtNuRrbQAZKnfDjS6b+Otc6uipvRulgcIUYm76hyie0lV/D06 XqxxMpFw/NDLkTCQQKPHjI0WduTPEd+anQ85e8bdFbS43q0RCE/BeQSvIrZ6GMcg mJgubyO4Wpk3uVjf1a5nyPrs0u+TiqUITNWrTPCJodzekMsUnhHMWL+hOdlCbtjq MMoDGWG5gt0/p8msJjvPaRtDdB0CTELq1dorg15OFW2znIQ8kMcTb9fwsl+23N7t 9EfUeGG0DGEl57W3xcTtS4C86ss7Ao33leg+57JimJO3wsF8BBMBCgAmAhsDAh4B AheAAhkBBQJWsifiBgsJCAcDAgYVCgkICwIFFgIDAQAACgkQB9MQLvGhua2/0RAA pA5KRz8HGBvxaJvTnruCJ8kHpwmvD6tG9LRKD47xlW3Ezo4/HOIuPO7n/OQJJ99A qluJ2wm5qlkNn4WTHqE/xFntaYkAQfmgVa9ia6O0VQ6Aa+F0lzGJTZfXrm0dAmDX e4oc3NT7uEWqcEr2nzj4PF/iqrHXG7PQ9mkaFvzqtcFdq140x0j7DTIq1eW2wEla 0sM2V92X0hS2fdYkLD7qlTyHZRyqzPP3+H1kCYObwegVFRwWMF8Tz5P7MQuyEVKq dSuMY6ixI9+aD3P+uc0AQ/Cu2oAPjc52uK60rbV1zjLUAQo1m8sOQPFRgUhAy7xI 08jF1AK2jSE7dwljDP/Q3C54Zzb42QT5Ka47NMgmfVuyuYdpv4CyiEgItYdTjpe6 Qu9oZHZexQLXGDB4LNPUKabdWZLqG0hzRZTa/j/yTAsDsHfOJXljTyeeaxVMQbs2 04pnY5mP+eeD2QHlJ1laIrG4kl0EHA9Yz7KufenAOkhoaFRSNYUxRWRIPECZ5Yhu YYfoWu0j245EI0tDknoSmIDNQi5zz9pwRSi791FK3UaLWrthMN0OPQv6oGZthKgi uedOuwHNNdoS/Il16WAVfcnRIowwEkW7IYL0tU0sd4rfS/z9HO8AyUyb4ORNftHU xU6Mtg12KmpUdUB6iXXLSARxmh77/9oCrZ2P4QmmA8PCwXsEEwECACUCGwMGCwkI BwMCBhUIAgkKCwQWAgMBAh4BAheABQJVbvYtAhkBAAoJEAfTEC7xobmtP4EP/A0A p92iUZfYIAXxGwsNAOy8SQpLjNeDojDhUx3HoKmz+9+2IBDyhUYEi5hUAXVGofyP IXFqdMCzK0pgWun4azIvfovkpnmxuiFZZsnEJlAhJkr9m22wXJMRRburLjs4qtJU 4C7EtUCQvNqZQsJrw4wAelUTuj/iTAlXxLc2rD2J25c4Z08W5CoYIZMhOMh6EFpQ 61vYu5Kl95GB41yIvBHOTXvkFT4f482OvDpLgnAFtAJPy7IcLGAoOyIBGIvxQvvW hlZQcddRnMk4eC9rfxnuSc3J83K2/4grbWrJnVZeGMPAFHzzCJqwwdh+WNgAEyNB ZgjIa1gBojFytiApONXXtLKfol9yhSVnxB8+06DVMlKATU79G4lzkApYV5nQgWLV n2AUaYO/4WA1BFgoT5ECbRHGt7Lv8CGP6v1cGvUtmFPNbXjbJ0SbvGMGZkvESNM7 B9qVWagSsagtsgE2o0tem1Tz5eugkUX1LLdEeDxITKzYpiViLgdjxIP3zStq+f/A uyAVtqK3rQL/xjNEfTXlX1Zg0ErDrpEtm0Ir8EMzlVxDo2y/S92wvGD6XUAa1o0r ETCajVrGuZt9JGf4H1pyLdTwIpRj04UnjUnogzzUOONefCB5ueKSEPvLBBdv736a Gn7jt7S99XFswzum2sN735HJejFXqlCTAoFTSCDxwsF4BBMBAgAiBQJVbWjXAhsD BgsJCAcDAgYVCAIJCgsEFgIDAQIeAQIXgAAKCRAH0xAu8aG5rVlSD/4rowwzS0q5 gy5dIoYh1o1ff0pJJGkRq/NH8lDLF1dq2iYDZ63wzoz+jjaPlBR5qXUyoa/Gx9sG 0TxCgQW+tUUfDpMf+omNlTSPtiivQjc8uz8l/kqkAFsng9UA4NynfE/Zmqf7XMQC Il6L6BqZXr0z2UmfLp0kL7GmBVa2PloPDhABXLhWlkO2QVPWSwjekzyzZdY6Jc2G PCT74i8dXTAQ5BYR0kmXGAY9aYHTqgmGtWtmQyrQlntAJtErsvls0meVEONV+7vq +ghXz2wNZAyYzus9fq7ZlcvfzD8tF50s1Ok2DMKAbkb1nwOD1wPErEorQG+gQkzl Q9VT4/GNoVwVwnJ1A0vBFWY0bejZRt9Iu752pkqd4O1hqlsUMTLeomXh5UjEzor5 /SaSSLgff2IOrfPMdlagG6es+Ex2gQh5v1HBHx8tzjIllFzK0g9c5biwjBISEcx4 nkxh3DlsUoBxZ22SiNxOVmeWLRcdldaDjKnL7KyAeqzN8V5ZrnuPUZ/Ci78OMtng POqGfvqOKXoQ4EMkMavAaDgLjPObD0lDa4AeLD1n0ngM3YNGAd0y4jVvRP+GeIpR HUUJ6Y31wnn6Aanch/QlJnTm0p8P5uWIATVM9wXNEZYt/YYsTQX5FMBL19IEqa// BqGX+4o7hIT42WYNCOnOp7/LTjkHGBZKUMLAXAQQAQoABgUCWtfMXwAKCRCrD5KQ VLObJLMyB/45rOjzkSKUSHWsyt6AJl+/CzUW4w5NE91oX0dRZXv7i1aQU7oCVSDv UafbahGDF2dRx7DuLdwlmQpuT0Ruz0fIruIYOyktR0QmMGcI2tHKxqt3/ub21Slh 41UDw87MO35htiK86qG6cBxLVuTr/rGT1DzHu+sFeJh93yRPzMlQlEwZWkU4zS0l JDObfLY7GjmFSGaSua1UuudEy2Kys6IRgDTPEiyzJ5q5ySfNRlXj3Buvqd+Ui3Pc hE8ujgJaXEwlsa/jP79t+nQ8J24Qr/4szzEzDxS7YzJgYKuCRZvykhvBO/jnQhMC pjFPwxeEonXKcD6xMKiDz41uVDNf4cw9wsFcBBABCAAGBQJYVw0vAAoJENAbnXeR L5Y8CKMP/j+NzMe0kEgSp+MFi6PQ7spSkLXZJ2v8q8Mg40U5/uNPsERvIWCOr1Pq Y/WqJg01fa6/z/vnw3B5kkiiFo3F5j8jE7kSD/2iCeZ4TEu9G1V6V5CsAcrQWZVX N50b6gaSj1XOTJavy5AcvqgBolygrmbp+471ArF9JHz4x1vvs6VEkmAdIpBZrAwT 2kiRDrCgoaiyWs30ByE4uTfBe+TJtEiKjI1ldLLZJGET8IkaAYMOKbiO1cAVrj58 G9ixfVi/1+GlrJFrA6OmGqP5BQlrh5HpqyG+lVzGu5vKZwPvrbTdJUPYHQyU7C1a EqmlrWeOtxO6DzG1Ayo8uNkOABO6zgCiVNAe/mRZv0NPK3X5+lhHGvtDnhb0FLdl KU5L/3w0uf3kGLCfLJei4DtdXOASfJ1boOuOLltg0tPNNS6EzkG4++sX9s5y2ZwH FM+JIRyXAVpXa18gTsByaK7Q0JSoJm8LPZU2DYEpPFK0eYk9DUimBJLmKsLhvEJl y69KjGMXC1MPaJ8EvhyKz9YUzgaG9/DKJfdOEcugD48fE5wEfJmYwv17e73Gv6L6 vPh5kmZymw3hxC4sOoOtwUxb1VDMa5otb31uP6wJ4JFm/xJLQo2B180VtFsv1qw8 GP4scG9aX+fKal78jYDcwZk/HqY/cnwS/K22t4cL9u6eVhC/sggKwsFiBBIBCgAM BQJWJAc+BYMHhh+AAAoJEEUMun+WjwlLcEIP/1UWclB2I/ZZkaT7CVCQ4Rie44H9 eE5IxWUL1fr1Hzb+uGKs8Z2BvAwVMgzFfKH5hmTDa5jh0d5FPsQdMpwmIbe5vve0 RbV1Hn6V32N0oyxHfHbTXkTe9noo2NrHDUDkU6JJG2a7pj7Gty1MA5ybAj2iLAaK FIUYgN7qfKz2a6zpRRb6amdZKrdmH8/729vlHnz/4TDw+gjsP+bdfq/F/By2n6l/ fvhnr0d+0k1NV6MF7UPW53fzxhBXhT80D+AcQ4tkgUZnYdBkgLSINx9FLwyzC2ni rCP9akjfNrzwLpmKcELFKbe1N2HHvF5RkOhnX2NWdUcT/zD0iAgGyUsWfikLIvGZ 6VtgnvWuIyI+rwmQla6Q7sIvtvB8QuYgBdVIXI1Uj9TJkd8wqv1P/O306C4/Hwdh HcyzkuvjdNZuxGQNtKQ2oHPG0G9ytdNSqsxof3XaiyiECh37snsoKZKSZ5W1L/N/ wtlgkmbZJvuJPo7izag/29z/NVKlEhS8uxLoc+0e7l6zHgRKcEvqm7kymZoeOUss NiG3xWSyuBeVvGPcPzlx/4y18/jQxff4bMfousTBvC0dySq0PQImSTvKW8UL7ZG2 Ib5+v3hfT62ZBCljOXMaGqy4RUsgNRHn3w7SUxzCgiYWt0hAYmHzJTvrRLo7pjAQ s+4qZiGc+ETQ+PL/wsFcBBABAgAGBQJWF3wMAAoJEE79T9w/RtQe4wUQALVAUklu H9FDQnlQCipcnJrpOv3e4hfJAk3bPpWwsyezWkyiCbvPKobD3Wus8alFI0uUPW9F GgopeHRfSaJYXWFIER4iQt0jMQmd49gRb/x0vtpWz1Ft/YfvhfWhBAqL4IT/5Ple zb/nLdrAm1Da2ax/9kwLRijWLVHHfbm7PiQHXJZP8UWNp4SZo8sA5iJYvULLp9ih 6MZAdMpCVLAJZ2LYhSB8235wOrkiNZzE7W6FPJZdfWA//XCsQDzQCOHLMEZpdGim wHo0I/10YRMpiaXVXVZBLTgqSv3F3lNPfW9gxLwcgjb0szFBIGRMZkCq0HS0Zvv7 L1ffd73cQixGZM/F9dscDGJwNmDbisR3oWMEkWBCRF9lWVwOYKqQaszXiOUGzAaO IsjScw959PrlSFCLP6wWVbJpt5xk/QEDXYwKN1EqGgFldE9gN6Mtpt6BD23M2M56 I8x73PvoW/OZtM48Gj3UE3xaUkawbpPb7hhialInbIPWuTCi+friSQpycXI0zccK +6EfEeYPX90MUoydgzrr95mXAghI/OwRQ7X+OgpOMnH1BpBxRRb0NyT5anp6kQus LdThVWI5ABFwH+oLK1R4VboO7bCCRaDwa8dCL6Q2ECltGFLfvi8+NkTVCj6hTmGa a193MnxC2xxy2LVV1wwCZZt4THhPoKi5vUHjwsFcBBABCAAGBQJWEASFAAoJEMIY UlgZ94RR/n4QAJQKANp0WM5z3xIRXI89kjsdnxHUr6ZfSl4vZEGnUMAeXZANI5QV yAWdJknvEWAz1ytd3Zp77bgcsjBYPuO24lhCPUo+paDF7jYzb2kaqIuVTaWew7T7 UDrV4f3BvB+978WIroRXk8+em6hhecIlI5qTGGOjeX/GtO2zK+rHcGK0XXEqpfuo HeOGtIv9szdoc/LZeayXRBzDwJxkHLmTNwPghlPByJzVpHzQfRKGwpMneH5M6P+E dGTaavSYxY/BBiTpKWkl6TKFNqfcuMBsNDj74ddm2Y3IX5ZU3REPYjtdj0uGGoIh 2xpNo96HBRedkKzZpEjT8RsPED7RrC8lPXVM+xvWb3uDYxWmnqxg7bVOZ9tGlzTL 2nbmlgkexP5ZyjmR7j4z5ml09x9MP178t6Mh61vs3Ui8FF3QANztMg1lzYrom5xN mwhoFwIdRHLw3AuMa67HV0s3DZ0CSS4GIwtXhHHSjMjDuiCTsuVolN0kMTyMbp6w N++5MTV+KM09PEp1BM79pRmF/Akzptlu8/YBVUQ1R802PMuCdHTQIO2eWhzCYV7E wlnnxTRcAFXkrRUUmRUtfasNODryWzppNAJb6mGpcLTe0lUGJuZ5KHicxh6kD2Zk MH2vXKUsDgOuVH540JayltzNzodO0hlWoa8cB5rjw/c2Z/8VA19/7eVawsBcBBAB CgAGBQJWC5nCAAoJEC2zYkPPOLFgVhgIAJJmn4z6dBFaQ5ks+8h56IR+U+XlfMLI bVFX7L1315TDYKA91pzC18FjumBuoxzv+V+KTiADfMNSQxQlvxNN4gGT6D7MgJHm sPtXHAIFmOi2eTvlEUuL8/f4RoqoGveco8i7f/Qi62mLbG7X3K88UeqczEGivlJB lAlWSnCBaQyOgj8yS2glel7aV/SsOF+45b39SuRxX+e3DbrcILgyRebNGDBAJYUl nZ9FYGa/tPbQA80T9wyN7IMAh6dwNZC0LBtg38G4dZvnaijLjElN2ysZLaGgBj4r bb0LK3T9ivVrYgK+4F9qDa1f39RB+bY8BeI8KmhJOS7d7WarsDscKgzCwVwEEwEI AAYFAlVwOmkACgkQk55r4eKfw8wO4RAAgXuQtIvIb8nfwG/2oibI5Qh127nO33+i WO1TkuLzR4C0sqXMjLBWa+i4m1LSB0MYMehy3YNSTqT2gCQN9I2AWOk/ZXgkAc2z oSO+/OOKWggeJqba996DbFQZqZkPoEiKYGLSE6JfUYfu+VlYdy/WSW+v0YHR43Rx vaxlBgwGVVREaT7pQrc4DTPC/8+V52fPfIbpy/E3P2+YivZudcLTUCzxMFpm3W8S 13RY8S2fdi2mqd6ghbZfyx9YVr8YgWaw7ooB+meVWh6jAwbT6RXAAyOxnjvx4aft XVsCP0ok0IH0+hwzUn9R/dkG6ljpsE1xRk7wRBqd3M1pICcNioesw3P9hRDVKI4P jx9qIMUDHZN09Gyu6pJ1Pmay+6Pjsoyu1BB2xUeB0djUDmbWAIVKz2+Y2+/gZeXS vJjKrxifD4kHQFmkugh3OOcQTvYHYiCRzBe+PXOLfzJVsVfyca1WIaVSTEl+iZlM 0DPjiRqj5z6pgHlEjBRpfGvsvIEpfSjixcv9dqgEkmjqp7IFJhqlOW/KJsg40hWy oFHGlT4+h2HN+ZrCGv+zwBDHz4RmKRAULjtXyajjDIEH8RrmyxQOSXdRhFhQoYDk a3oJezdPnuvun3VUpCuwmXamM7oEncSRLIY/Dst7L6zG8QyAGLOOSsEp1ddB470N g+H7sPclWMzNI0plZmZyZXkgQnVyZGdlcyA8YnVyZGdlc0BnbWFpbC5jb20+wsF5 BBMBCgAjAhsDAh4BAheABgsJCAcDAgYVCgkICwIFFgIDAQAFAlkyvuUACgkQB9MQ LvGhua047g//aXLG3905AohEURBZjQT0zI5hq6KH4unjYiM0sgYFUSkxX/Mdh+cC PgNJInYOeNcOZPrrWOh/T8Af/4U7kPgdCIjGV+Ocge5XvBS0PXI75JbITlrPA8oH 7upmJFyb76cKNlCnKStZHbntHBl45J93I1wB25jyy2rZ+bz56vox8ZUPF9i9kZF0 Oq8LjR9wp+8r4U0ERxbK1uhndIKz+SdVO8ZouagaQjtqMB5SpMVFgVDhEFN1ue89 2vuuC8YweY784eZ9teoYBls65KR/N5Ah/GrNn2Iw4Lar4ShgjZdML4LVJAi9g3iM SmpcBoSWbHKoEep2rMb/wXl5Bqx9ZHC5ExLeAfH8Dg6MvoA5b0tnTQ4/rbXdkkNZ OFO3US33sx+Mb82FtB7d4SmKxRiX4YhBc8gPlz0U9UgFdH/i18HXf+CcIypOsLnL 3sgIyqDv4MIgQiMdeXbBq+UUebLV5hmqTl8h8KmUApIAWW7VB7B/3fZUgSv+SCFw CcL9S2Wh758J3OnnPARh440BCLfKuf3EgHMPMnQTowqICvWxCW+iqGEIQYNfyQhY vr0Zj5muCHDfoUT/uEigfogCbQpv7yt1eIOPUYV9A9vI1cMcTRaLxCv4xJp+0Kbe KWafwn4Rgt/DFBaKgHynS6kirZrijnFzCmqgczjjQHeYX5Qotsj5PfPCwXkEEwEK ACMCGwMCHgECF4AFAlayJ+oGCwkIBwMCBhUKCQgLAgUWAgMBAAAKCRAH0xAu8aG5 rVAJEADByc3tIinu3h1iL7rML0w5T/lHOsWjktqX5fh0jDCN0xzXO1ic50+XZZ5v wVDY1brDuq7zdFnthWrEZ9gva4Z46gA27+K3y+o40NtdhPzMCPwEgt6Wa8Myez6w T9LPGCwAJSHBnqJdQt0na6YaTQean5XmEfZoDOrahs06nJZJ0QRix/W9P2LLT5dX EwrzPcATbfoPO5OWfXUB7q+tHX19FfikY8JJhrWYZjHAeOv1GnYwCSd5O9C+M8Tj J83SY861D/vFTdI+TgudEsnpBB4xRv0T3mgorc/vjXsXzdRPQDX073M8FI76/W9x HlPDljw8PaGVDeEi0VVID3p4I9MMXPlxf39eP9VAzPHVaBOpWiyGHQmvfoazP5iJ UFbXz8IGWFV7VPMaqjE5swvZ6WWs1Yto9NZPdFbfoCoSEgUGEVY69pfjpFzpwuhr 8r2hUeaRt5iuiF/5Vs3K9cmDFP3ufpk2ZwgMM/mzqbQzRo/92aU3JzTfyR7ws7+l H5coga+hgAvSYUj/Z74Xa6I80KdKEXIWDh2GktT+1OzcpcPj5gtRMD2t6ln5c7Vp e7a9hMLCweNJ2jLjjTNZ5j+d+ZNJeVYyh33kYm7vUaJth4ULER7KrZ1ruPS6lYXI ffa1vCkjNdEehOp+95N5zZV4SWNE3WIjI8FT1hZnXeyDSYTLM8LBeAQTAQIAIgUC VW71aQIbAwYLCQgHAwIGFQgCCQoLBBYCAwECHgECF4AACgkQB9MQLvGhua2nGg// Q1yCkWYbKheQb6DaLoyfThgQoZWLHjgIyCdqR4KYRsrVlV3UBMDV9IJaHuZWrwl0 /nVE24+n6aXs2lDxDhP2+SayZsSbERztthP4+pTrEfUQjUc2II5WsCkXu/mp8KAr 447F8a2BnFekMCOdpnEbASqePg3/IKlAQm4iq+mjalxrX2J2h8MC7ePUJtc69uig mjQxhJHu9QuNmenFknprmwcUM6Uzvp6mE0RdAS1Iv66lvBIu7SZaSsQJ+GKgdL+Y uk3hUNZZ+zydLOAywZmQxI7m6T6rsQNvSJ2XPGGzJ3ZmrEHM3v5ddfAvccA1MYEZ tUlF7ztSiZ0TQtAqtexfueOT0tlJttNGpqtj/MwPO8OOn7C0CwEqZXRYHCvQ3p8F MxYM/LoQKERs9xKAwOQbC7kPIcitmTnQKc55oCGRK43a3mEWXR2Cm6GDWGCUY48U OlBTEnrROIwBrPgx0/KWaaU/wcihvkhbDyB14eg2MYzgDx4NemA72lT70BHBz2CF cTj0E0rICgm006zahfdzUjgRkuPK3OZdfWsH26A8kqeRLGpy4b4Hy0ZJPkO5wPga qAF0fGXIeOntJmQy3NWYy5I9AF2ueFgp23vySptdoFAfCiSr7MPaF+cg4dQw/MQg pDJU8mqmypFMSawjn3+hvBqXIAFreu3R/LT0ncwnAWrCwFwEEAEKAAYFAlrXzF8A CgkQqw+SkFSzmyR02wf+NHskqsyTfCI2ul1gdksHqizy/o3Goc1vEwDdo2yWq+e6 HkF1Ll0lbP9WkL5gASDeg6LOORLYaqBe0oslLCEad7oOlPz74gSLgNuRF/8WDUTU QzDdQGGuGMYjAAHa833cU3MggQbnCgkfa4SAWM0Fz8cTmf2zDYT5ltArm6PPGk0z oDt+5f/I50McvKAWtERw+5eI8AZ+7UPIjW1MDQkMSE9wBO4AMYFo8ErFcgSKZshT BUn9naYAeeMrVDuzGp8t2WbqLHHPyjCFoibeLY3GJguHpQddJY23Csuk6uHaCPoL +CO7yxlREhEAX5Z1NAIMIviW+xxfuCZnpXGuLowV7cLBXAQQAQgABgUCWFcNLwAK CRDQG513kS+WPMDTD/9ytlVHwMXKHGzdZNk1VAKGTXfOqYUHy+jAwzX79rqMCfsD 7Yg6dKDUiKS4k8slXJajFlTA/XaKgA9wuD2ZD/17CG2TgqGMLYSLbpbRUheR0WaL VKd1EnDWxLk9+Ai1UcL4gnz81QIOhivCQwCd7ZmPFTHkFTk3om8nN9PPTm5khHn+ BmuDJChIs1ZbJMq98iweXGn5vC36+H+bmIWWe9pPHEp6bLTOxM3z/2aPT8Ok/4xJ oGbY09ERawwK2AUqQxl6QW+CeOOqCAGe7i44u/nSt0sb84eCr4ksRSdBWcufbN7f hTtuKNAw244eE/0HZEieTSP8mxhDWz/mXr89H7J6G169LhCTFzxG4JgE7565Y+Sz GI7fjap8DcuiIJtOAbC17SxKCrujfbyJKH2kI4VdrlhE3Fwz6Sn/MDMeJ/hZHG6j iWnTawIJ2qbX4WX3xoJfTPetqBxvydRvb1azU8Obc+FJc98CkquioM45EutuW0IV xHbbSIesuQuYbqVkZu4uPqMkM5viiulNOr/EWVsifBTN2xCfHEoUNJIrML5lil/W 6Sbja6FuB+WGO9Art3j3+HeYA2qWRGw+Lse93e/khKY9d/XotUw/53baNI4kKbsd JFCiORUWt/9WNI0M42qNroyIY3OhLJw3GM/FwbWEO639v1L63tfok2vY5T5k6cLB YgQSAQoADAUCViQHPgWDB4YfgAAKCRBFDLp/lo8JS1iqEACVdFQF+Cfv+OY9SouI HCSKkTmXU+eEiN/q6KqRGx05sEiXzqLrFAeaekZsP2aoTVmmf3/ZW6W/H3j8Gts2 Zl/CCyrtivEszqqVNiap89wFBStrLos8HgXZIn5ePkaKFU8AF23urlxtHM3Au+eU 5wG33Zp/5uQ1kj/YwvjIb/u0TmnFAVhZnzlzWEm9v3pyImp+C8z9asg5wNTTC/ba UyokNK4QbkK7rLe0HfX9FttxVk1AEo+FyDl+SOBADRLSuLWwSGFSZhuOvLoffeLn nI93R+JoG74+tsSV8HL8u63WQ1ZxJ9mITN590yPm2esFhopiBgbkCMGQsPZrSlDK +XGFOJzajmTNBPek1/wLrapoSqyCwcwcN0c8USjNYhcMeV94ia9+M5xXfmKDuunN M2syz0mpvG4zZ6oj4uMSYL4XDGyMmYtKCr6hJ5R7HjBlUGUENljxCXvwHcZEhdfd uJJrnImfCBYcXZdnrHo0ierwJndLxBDR4JZHYuANck29V0TsHaqgG6g98w4pNce+ GQIBA3bLIHlxzOF3xS4nD6nBdiedIXp3T+/XFH32FlxfbwKSW/TvZjB3oC1RG1VX +N3XDVg1z41dWPJyS/QzqiaVZU9KUR78rhCO6GPjEzF4isGK9lObJIm8iBtX/eJj U9T/ZXXA/73YUUuYXn8jRRxM7MLBXAQQAQIABgUCVhd8DAAKCRBO/U/cP0bUHs2f D/4h4WHoornebw+nRjPAvUBDZRPTv/NP6aK30qJzzBfZMl0YI7tY055IFVrlPj2v T5jRga1hu9kWO8nwxEjkqt623gAPhK7tma3amoT3qAoRZDUO9VO0nnUALs8TaVvU sd5F2HzLRhg7eyQn6BH0UKt8yjzE9slK0SMBA1GHZfjWuhYsJWlvAV5aQgKyNqdj 6gAEy3qPyD0bpTLZCoc6jwE4hdUAP12miKHUIbwCmIqIjcn9rQHkVZjcuqQqcH1/ U76V+7tcwhmKld4sMcndBKhxPB/xsOfNPQfwfrl/QqLyzRBACzWQdOAx3hiH1SYM 5xqhFkQMB6MSeck9IcB1kWC0gzg4fmIPZI638/XIbsLXoXrU+hZ8+jPdhvfyed8e 1AJKIoeT0S1JupZD865yeV/eWE1z4C/poLnDf7oPv5ItzrYCEEDIY6BA3clupdVm 1Gyw1HLkIgjX+E0vUdev3LPFkQNDV9Ds5XCbj1i4qMLYJVyRdZ3XUund/6Xe3by2 Q3xgJllliwe4fZNoxtkPUZ9Nmh+xwEuKCPKgg7kSVnjyg1deckQ9Cl/GgD//2A9w fNiK6imF5uxH+3OZxxmtGcEzW3yYt84dDlEuIsaeOLpsPcBMIbTidFwkhL8CziKl zjRrdur6Bc6JY6OM0Z1/JB9HWWFVa45VL9Vi5pyj9HnhXcLBXAQQAQgABgUCVhAE hQAKCRDCGFJYGfeEUXsZD/42NaQ78Sr0FAvJGqtHHII/husNqbuaCMWOR2Hoq2WO p5tH65pnlVWHAOpjrCM3o4+aoTjUWOrFBxd8n3vjZEp+LtrUhOSipBNbLvpWG7BV KY7PtMJ282A64qo56s/j7+HScQ+kVap4gANDK5BBYmY9MGjeixhGkTC6mam3tSUf qLlWXwhpD3nyttFPnL/YTJ4HZ6Yew3vJl3m82+d/7A/PRtkzRdEny0qvEscvWUWR Kp/ULxJ0cvRMrKgI8vXlZDZwuzQEZq2R7rfFXwJBWNzAxVBKkTLnUuBgbmzxG+sf 4BVPGrTKAm8+zYz6GP2p/GvZzmPGYmvfpD1QTekw0WnguDv8UTtdqAl2lO1N5pba 32MBWCcXGET0A7KclP4jq+JXxiu5xUcU0LlSNBkU6Snbe1tVm5xStzxx4EcCAO6c C8w3qjZvWz7CZJw5SXLjccsZ8uq5++YBjuv/k5VikmOoBs9fb2vu9qcp6RR0Aygq mgndSwBcDGQPhbyoqrGlYqWIzyvAokmRUnaf69MqaA4zWINL3aYk+ClLGRI7QgRG GNQe1L3ryKgCRVDg7qPWuEjiXVk35HGsioIqGkcfuUaijc28dnMuS2lQUR3kV4Hc 60TZl1Au7n6dEUIIQ3L2iQxCgqtEDFy0tzMGIkDFhXtoEnCumauopJYtrXDZsKRA jsLAXAQQAQoABgUCVguZxQAKCRAts2JDzzixYP25B/9yzA6Za+tdgF3uqEqnb20a sKAzm23TGExnDjA58wMF+cgyvbs/0hnzLks0VRd2moK8QhAlBXXFYJ08kE/paGm8 ABeU6KUaXJBudApsi1sjwL8XfsCKdKal59AkW9hT9ee33mQFckFicYFRI7+xQVow krLg6QdYDIawOznQPt2ZFB1VrKO4CzckJde5K+9JrpWmjXepcbUTFssOPdrEHfdC nDGPTE53jaMejm0C9ubW17ZtuUygwjKIpaLTBfrDcLh8ZudKtV5P3V1Qq1ArpBIh Bb81XI8ITssPxOH6gff9FLO+YiJbiOS7HMj+LnLdioseF+7c7rHUggRHvdZvJfwj wsFcBBMBCAAGBQJVcDppAAoJEJOea+Hin8PMlyUP/j7tmzclgowYFE09FNHmYUv7 wWL2uUb2qSmBtnZZITBcenx+pSLieTP/x7kaQws666nWGfkAcZI6G/4xk7odCtg+ RcGB5B/dp/enPCMUjYg25rgaf1vcz9X0HdfuqmaVrdxOzUODPaVs3FJjVW11cr+s Dji5uRFeb89MbM7WXSN3qbn+aW/s7JE9xdAZXhp3SLw1P+/RKyV0iBwXvvBjVtS9 6kzI/uMp3BLOQYk/Za4ntumFNykRzE2nSYFOInhWqlsiAMkHH5UboYEOGcIT+l/u Zkj0btAN6zFI35ZnnBJ8UA55MKAyXzl5vPXbyxo5eKeV2ybQ2tOC4yF29r8HfUZZ Pv22Iv3A4Rl4aXr1KbIAks3cSdHzpros1hsXRcC/oObB8w/K04KUZjHEwq38nNAK di9UzUnrIvKUQXkXTbaBEyAe8XVBs/mAyT2iBUVVJ/SQdLhAdUBsN8FhmDWaMBUY kAVl6lm7k10wZS2eeRAGMjtTzOum3EZ6KkGJfSdU0TIcPQmqKQZrajPOOXcxEAu+ hIMPFG3Z8WIVktVPH5mneTUEDdV+icczPURSubOXR1Gy3SdexWbIfOwUINmVYWeD bAWlUbnSf6dxhOyXLiaIEgHzAjPpaDl2/PJcBaKCwykB6VJABkA3RGPgXm3sQ6m8 +NXi6sGHGNm9FCGW1WANzSNKZWZmcmV5IEJ1cmRnZXMgPGJ1cmRnZXNAdGFsZXIu bmV0PsLBjQQTAQoANwIbAwQLCQgHBRUKCQgLBRYCAwEAAh4BAheAFiEEau8Rygne gG5p03CXB9MQLvGhua0FAlrnhcsACgkQB9MQLvGhua3TGBAAteqxrRco7msqWMEp I/SfO9VPpgZJo5zyNp9i84pfsixSqWZCKXEXa50BA9BCach00t6NNGhiBD0sH6kA pP8w4XDyYTQNidxDbopgAHnT/e5H+r7TZayiM1diELv74A/XBPjNRySTPae8zpoK RaAy4Z0Uu6K4iQuh/V1DEW3C3attU6T3CEFxIRpABE3bd7LI2VER675SHf4tjaZd JPdf3F+R3PBccyxBHuEcS4f98Oe99tcpjA+xjjgnWNZdoqmhpWODRXdEeYI5k5sh 2jPReQj8X70JXKu7S/C8Joaq4Ukld+UeE68jAkm2anJZqEnjFm0yEPbu/WSCIl0P WSAsEsmK+flQOTPFNTKoXWlI7kYSOHl+gJl/3rYpFoWgB4Ub45P+qEO/4pcBDHSr bu5COmFcp0p6xVjh1f0MZh0BSQhz+V2532l0Mq63QLM0n6GjYkQqNc/74yYLLMOO rLMJghD88LtamyVMWwu1D5Lxo34FZQ9a+eaAkVcqPS8nAWGi6YARnrLf/Z8/6qtq N6KQFaqVu1WluKxn3aKipo7l2lEgsaJnoqBUgH/LIGgpxHIoPoEjIwUSsbOqc9ex T9+euzwTre5Mp3kpq8xykRoQXPUZ19pRHjkZdlGw93whP+lealR/dBHDlC3fEQw2 wm3VFYrIn6El68hIyvKrLbxb7a/NJkplZmZyZXkgQnVyZGdlcyA8amVmZkB3ZWIz LmZvdW5kYXRpb24+wsGNBBMBCgA3AhsDBAsJCAcFFQoJCAsFFgIDAQACHgECF4AW IQRq7xHKCd6AbmnTcJcH0xAu8aG5rQUCWueF1QAKCRAH0xAu8aG5rQ11EADBS7HA RV1ak3ETAUkx0QT6xoNt/jdbeX3waizhJgzxOSthU9pVSlYNP1cnEPQHBBR9brhv wME0pUHYtfVd2kM9htHAMM35ZoG3WE/zAUQEgQmAioZ0DnYBnNmVXZaw8Vh18aTm Rt4eSCE1g/faJuq8peYIkWjhdS867vyBnDkxQHM66U1HCen5NKdlzHBR49h3fzyV /DHmYgz/R2+IYeSzuU4od8ZHfVelczlfdOyyYOh/6jDML37jOrAE4PUKNNYL3Wzv eTFMBupYl/Lb1VveMX3v8A+qZTQtY0lxs2f5RRWtjRLzhM0gwB+tceGziW0RjOVh j6/DEBuSRUDD8sMYx0WkNXo5YpdwtOUFPnuOt5VVS832kwUyu0VwvrOav2rTSDCH BKEAZmbKfQcEMYMqBFf5DxCMXOploLhr9lJMZIICCHeLOULB4VCDH5RptcCprQkm SY/Pswmvf+V61ff7GGO5yWrwtNSGCVZJmpV2xTHON2NfFpxThnJC6qR+C5Z945M9 PIuF+Haq62k/H7tRCFM7P+Q3nADsx1A3hCpGqJx3mQ8XBeyToRCxa+HG7B9Uqi0i aHaAGQEf1VXBYmpDr2ik7ZDWTkZ4rk+TsEkWIe7QLTza12L08Z9L8Ewc1Vc4sihZ cHQqcoLLxevUdbtEkynW2Mj3kaWmpf74gKzue8LBjQQTAQoANwIbAwQLCQgHBRUK CQgLBRYCAwEAAh4BAheAFiEEau8RygnegG5p03CXB9MQLvGhua0FAlrU/YwACgkQ B9MQLvGhua2+KxAAuUJuUEZIs9QvNrNrkQIfd348wngRLQywcdmqLpjxGI8RJuKl vKiKR2pC5WOXDPOt7E3DVAe4mBIlI6zGcbi+iG/OMOgit/kkDIZqfa0mrm0TBywG Nz2tNZdpcEArjxgMBQ03omlaTSjEZf2E9g5mfFQuY6itq9D3x+7QW9K9qcy34B+d sl+AJb8ierZD4f6kgb16rSF2ck6x3VdpMK8pzTR7FUFqdkv71h9+MolECHqP4/bY MhBcozrWbeDdtqYGR0+Hw0J4ZkQ4SN8Z/BaJIGC9tNVffy0UzOPtSUwlTahPsXKA XNn7oTISi1HFn+yxs90hDMNV8sQ1GKqYqfRe8ENe5JmHGx4YBiABfYQSg8DQoQlt TfoEUaVQ0ogz1lvWvAPv3zdQqbfiO0JEPxjdnPflRU6EdsYWsitCHmOagu50SJ7j Pi79BRzECgbIzP7mdjBWDkqptOHSw1FAnmgh5pp5u2i7zf46BGEW5UiJN3ky9c+0 HxhBI8V/wRfkz+wCRTPg2h7DTEvb5wuEA3WoD9ehPhTArhFrpU51Gc5baxUXn0Bx yeBDTCEqSW+8UgUSnPmTQKMa7aHT0CZ+4EUhDuFUvBX5qTqqi6wUGycryb3SmiPs eVWq3nHuZhejLXF8ipsWccjvYJGFQR70JvYCjEc2m7jOwr8+e5eJLJfgSGbCwFwE EAEKAAYFAlrXzF8ACgkQqw+SkFSzmyTJrQf+P5qJexOoPe3/IKi7CsYIy3TYpk9q HoR9bmomdDSA5q5DX2QYE+gBL4VOq/QuTtqhXPba0OUC0RHJSGtPGSXQmHzzqP3Y C9zKPmcfTEFsAW55+KqNhNmIWWu/xKI16mEJlY+xIvQgayUIL5ELvqTyihmwzg4E IqRUpfNA7KbIKfAvVcYMDXsnWNGQRVEa59nxYVK1Y2mF1AUzg1BdckFZnzVYcYv1 a39dSQGdvYAI64uKVQXp7o2ChE7BDAvlFr87GHP9sPFWYn5lLYwJBwDGYs1x5/DB 9jtYFa5S8XMyhkvCz45MkBL9pLz7l30trhMTJf5cjPko2YsVScCE2G7B4c0qSmVm ZnJleSBCdXJkZ2VzIDxqZWZmcmV5LmJ1cmRnZXNAaW5yaWEuZnI+wsF5BBMBCgAj AhsDAh4BAheABgsJCAcDAgYVCgkICwIFFgIDAQAFAlkyvuUACgkQB9MQLvGhua2t gQ//XWlkZYKXvM1XuzWE83uiCUGogKoPJgUSK53HqFSxPC3mwwRSfPs95ahdhJdD t8KK8b/uS+m/epSmidhhZW2YBM1BQLYsQMvt6gxj3GmqgtJN9pSfF+mcIxartzFe bPnHTuksNC0kY2L4QAmQU7WjMaJkxhBCol9CH5xOtTzYWGNRdOyh+NXSYaOJXJj9 Gs0xn5ezXhgvD30fo0pMRzLP7itKiO59zoXP+vyOz/0HkQ9P9WRucbiqOUFkYJBG 6DdemJbW5xm3XE5VHg8uwrPzXgLHEhOGplxed1wozJGZS0rEWbYpYtKmz3lzT3L5 sYCG5+q6qYx/FJybF585rKykSfUx064eATsG0e04N7CCkWcaanrrhPgcuEv9y8pg zNGA2rAOcSGh/0RlHT/U5z/2jkFeDPicADYEgIZ3phY7Pu+W9QzMCkDvhdkkVHHa Lg2SpQsLqX7NTWQomegp3+k8ZEsjH5YjqbKDFuSrqdx1mgL1YrzDRhBR9JxF80C8 Y7GzvjnWJ7VW973364mNve3hC5tmOWHhSSWMOYIxe4XpOjaCXEGDsviNNQysil4+ dCg8oxVBKmDZzxA6oxYtrQw1jUDXTOL+TSaEh1UoNI+WA+Ae3wXtf6C0ycz9lgzT VCE+SZoSoRCBirNcahr9Kvdv7F5wvzczCsjBkgl+x4MPMofCwXkEEwEKACMCGwMC HgECF4AFAlayJ+oGCwkIBwMCBhUKCQgLAgUWAgMBAAAKCRAH0xAu8aG5rdl2EACI PyaH7gYbZMaI0RSc7kHQygpbBCiOBijdWG4nqWRv6+Bj0pEvoxDoMup7degwuajO qb+pXTsvVJKgw2qL+3Stwb3/df1tBFVOOCSPzX31lsl5PFHZxWizuqwWSzBsdvuL mPHf2pnnwVvvngWahO05yTEuNcKOmGWanoRSsCRURWXLkGodE/+mz1YEhDKdORSC 31JiXh6f9EPDVyvktXV1drxJA3o92Q/xM9AbeTiW/NrRfzRnfeb6rz3IcC18UAvy kA9saR3BiYegh/v9lZHt9GbKu6U3cIwpGu0CHwIF5fITbJdqJmFWrdEuTxQNQ5Zi doKe03ll8/Wh19HkkpTWR5kabDpXkqM1tnVcI0UuZrBcjRPnxe28vQhvlT980fNj TtnhzqwCaGH9MNGDaCl9/vF/yGq0znuRKWQRS0SoC16ql98jGHFGmWvzK7lxRwjH /sz3pKTWz4qX65QwfpxCzOh4gl8fb+SzUI/Ybd4eC9YUEQWV0CFlh/A+wYpNT6/O qO9prDIWR+G/uyd59LxTXkctvpDUQQLUBczHl7QY0/LBCZbH/qMPVOK4oh8OPCxA 3TYzkP6RSC44DDGbggeiSRZ/v8UFrV4DKrOKSjaNxyJfJayie8KjYw8P0rv5ztCU LqdTwSv2KbK3J9YDhYCg7V6aJS1/uE4BQ0cIUTqIy8LBeAQTAQIAIgIbAwYLCQgH AwIGFQgCCQoLBBYCAwECHgECF4AFAlVu9iAACgkQB9MQLvGhua3mAw/8D5xWCJ9l zIyhqNHr9jLDF+YQeQlgJI+XEKIgzEf8lLUjDXCLbG8q50uq3YO1JEj/FOdv7xcA gaOM3lP8eGDXq8XH+GG9mTrA6EzBRZXzw3BGtrQDXxOU4CgiM2T8+3ZQU5FmyQfK FXI9enfbUk0dDxwxH+dM3pWWL6eJM2ht3TS1HyR4Hg/C9QwihNvj96ZUGDtgMfBT HX70e43SLsKLaozyp7KK1Y8c8Pbph8BN3uYvfdpx2J50RDnSibL2Gl/k4LeCtf09 +7r+cemBEHOnp7Lu05L9evx4uCmOWu0WBfN/qXchT7Z9qNKPmCZgE+LKNhwm9Vgb Nht+MQJNVVx03fIT8HTy0tDm2eh2FWqY4lh0URfXDbzEXb8l/zUOkMqYexB/4+3i cehqInn1xbxJoPEuperMfeOTKlduLqC2TYR3cVpUFbnou+IQlU1vbMN/wWGcMQTB nOwuJjJCaeG9Usn4x5ZPmCj890rGa8oPWCdQhcx1Hv9NSiSuwrbJ+ddn/wb5lBZw uROAYAcPsBQDZ5ilXq9OyWbixPOX4azBzsrIL/9qpoJRR/crlnllg9DQ3FoPBwmI 9MDwthS+6xIkHT9f/vFY+4GC+5d6pjmBWS/b0L7npk4uF0evGhnYN1np+XrjZneM aSV6gnATUzDVAdJystF/7FbgRXQ4xt4FHrLCwXgEEwECACIFAlVu1fYCGwMGCwkI BwMCBhUIAgkKCwQWAgMBAh4BAheAAAoJEAfTEC7xobmtxucP/0nhSncs7mKYfef2 fbKm+znlZZQ7g4Diz1hzjMGIzcohHyQKIZXCUJW1ZIqcxeB+ZMIZ155VzL4//ldh KO9B6v9fGtMbvfEb4TUnDJd0VeVfqrsURcBhy3HD82Nn5itZyhB1fG62V+ChgAj0 3VItkWdi8A5juNgK6DtdZRGjb/K/k5xyMWfv+DSwnFNxPQo0xI2AT+RkpXGzkQb7 mINAR9mmAQtkYPRTvCPGyBwj11xY+zXvaWWFoJ2dDY7bGdXiHHTNa3wZBbXkee5z Zm2Obnv0okWZecdFFhJFBfpEr/QtdHBs12HEKQhsRQc4Djf+Vw+ehDcC2MWRWhWh YawqxSy9+VLb1zwP/CoxHUB8HQaPrHHv3CJsQCkebY+u9kihQIr25/s6qFmeGUFX bHp/xqAeL5xvBnuTZqqfVTN63DNsRIl0SJaFFh98wjlWhQqdQhOqQcP48SErRdRR DHQb4lA7IeIX5+D7rwEVTxBeR+G29GkYBvO4EqmzgSwMO+JKvLKa4u1qAYp7R5uO zcIWqf3/hc3kVlGhqpz403+jc6B7glelyg6u5g5SqIIlY0HuwnP671JrV3rcHRVr 1+OAZFVBGM7WSeVmi0NEJOCoqEse6RdQQcSlMYUsscxyCfdAQ0iwsCHSIOczayFj Ihj707HNWumFQbsog+A7IDjyAqI8wsFcBBABCAAGBQJYVw0vAAoJENAbnXeRL5Y8 D5YP/3ZpfNK4/BQ+2RBx8d/O8NvmeCpRJvunmUZqGDhP4h08ypOcj+V9bklyNrOL hIKiXyEgcE+uggNofBCiiT9T+WjFIaFs1uV3YY0JxFQczHwP8sej/Gn+OrQVA16r lvlQl1/Qsw9tj2Q+cPjU8QVqTeYgu9KvQcpzfEwb2XhHdW761wNAtvoUtpiJ5qsx jndFjriYGz8pkAVTJLceRMTWnNw4HIc1ggZ+qxD6sMjTWq9z3daGj7DTlWtaW/pA 5aUzz8O2S9OywbxUBdBKyaqoCcfcNs31p48YMdIPGfvd89dUADKTcZzXOe+Sj3FH S8e92Z2VKkz/80PbBMMIsS9gsKI8LKCd/0+Q5Z9h5I94aLUBZEVa31wOjGw280H3 yQusFlNFuCnWihfK9lEMqC5Et2yl3cYMrtvKLkGJO52JZY80ujCDamaX99u8t5yB yAvSIMHDblKNHqjJ4rkAIGp1LeYcV1Cb/DlWhRgux0hBJVOyiFXVELw76bDJc8WD aaCWwYf35H3gZbr3CVZa7GQWrQfAnA3lZJMUSoKSmMrUoNeUT5nJQkgSTk8xteQn Rtapi3FbO4bo3uwNEOrmH/WGcL0jPG4Cy2X+xBsSZvxR4rgSUJpb2BxLifUlRnmE r1bd1w+gg6OI7jUd32Z+B2UfTjbhT4QeXmNmWCKQIDj77PRjwsFiBBIBCgAMBQJW JAc+BYMHhh+AAAoJEEUMun+WjwlLsj8P/R6JDXhaelK+rUJ3x2KTCp354u2n/Jo9 B1PrzZx4BG0xCd/w4F4CAfe1EPVxvgghIrVyX45cHQc7bgvO7ZMgya59lpjbIHn3 +OWAovJje6Cz25GjHDtOPWC8CPnfH5fiVE03a/pKFzAYxHBPcX4dqROXj5g4owsz asahiLubWu+O6gNXlXGm9fYnLptGwF2vklPlt5M9cBfbAKoviRNqdqLzBI99pArg db/EJqdzxU5dRZixg/AngEOveqDII4qW/LS4KadFBltG3bZ1916XoRhgthpUDFyp dVdHD4DoH4bvXIgKELkGYAnVUdILSPK7r8pk+0mHJicHRmfm4zDaVfQFJXOnc3WH KB+bXulmXK9dYOrlCHW52tfX29IGvuMhziWmvaFjv0rOBo+w2i4s/6izOh/OGXQO lVBNdk7PhFcjXC4+5r1QmIjf6AiI4YUSCmI7eWOP2/zNotIREuK+1BsBGzwPNAI7 G+5Ci2Z1tTgEbjOVLM809PyL6NZQL7kdOa9KLTBR3SdDKCpZz2jZefLtoKi7uUAz ZqXs3Um9+J04Z1vGCDZ458ftLPssdJdG3eDtS1tbmUfY1qG4bJd8t5PtLGGU7+du A58GWRLtk95VgCp8/aaX9Fflqay7SrJGbD8UWNnlO8q/sAhQSOedGHwqu3NZyCx0 7m0h7gbqfS+1wsFcBBABAgAGBQJWF3wNAAoJEE79T9w/RtQeAC0P/A2Xc8d6G8Es IVcvVZOfU/ZZbyKyFcgUmCfZhZz4facN8eodjPelGiFVJFBGtTiXWLWuW1ITsece s5fY/4qXF5ZRJzOMVS/+DRxudzvlFi8ceeFL4am17SamKaNZjwgn99NkuAs+57dX bvieHBmI4dRvmRpzDJT/76VQOa2HvlxYta+AxJdckUX2kjTOQ2EM4quFnHN4ZbCs XRYXve7NoB8aeDSNsq2mMPtOi/KF78I3fOL41lL8pszeW8h27zQARTBs8kDBOaC5 vLD2M8dVasHgB4BtYVDijicQX8gqUul5TptXqocD0rCsloVxe0+BxrvwwZCNSHsf HpxrYMAQPPl1rX5nj+2e+HIQWrQ0eOcb7pZ0ubiBQhof57b8634zWIi7JG0qSFM5 50ug/l4KwidImpcKh5Nwwa7gHT1ADiFAXxNo5++olzWfcaFNc/xXkCBwHLfdmrMR MTd/4Asce/5gJJ7a0vUQ+bQTcEhD8gSbvwKJ8ZwsC1CW4s+X1++LqhMpS3HECBY7 UE4tPQ8mjgQNg4oMndc1/IMbRmOtX01dvkrSComxfiqyGIOZgZQrTYxikqa+kLhx 9MAM8+25Gk+YCatGiTy1jofWWng/DzPmtXFKb5TXXiSNoO9/Ts7VLYwqq9IE9Lgu 7dGsG7/NtB/ASnGtc2BaxvDZa7uLXF1/wsFcBBABCAAGBQJWEASFAAoJEMIYUlgZ 94RRnNkP/iSNh3il4TBiIkaklWHMv6yCQ/9L3QWFrIjv+HuXONUl6BvQDSerBOcW 4oUA7sYPsRHRc2m1goQDbOf0rZp31W14QgJmFapMpEILmUKFdr1UtsWRMYlEzLLO zYoFPcvGATrx9gZ9c73p9s9N276naF8fYvDwAd5VbIz2siyRyLjjMEe6wkYksnBl 4vY24x59dwxQzK8DVS6lD8kQj+wtCGmTFPh1Wodi22aRB+Tu//2E8oXFDU33tmLg 5M5TarACvXpTTgT+xorUubKVhR0hO/nmVvvlcxqeBzwNrGZLj6mnZ0js6FkfD3ZB 1GHUUPlQhgvnn2zgDkj9B4eL8nA01nifA2TPDl5g1aejgumY7HiuCO+H7ShzvXv0 tWBVdUjvlYF/IRkcf0T3EZgUC/9OPC7wFEVV9WwVrrfoBh6J2eAf7fl9S1JghH8w DAzDhRJGXDDnpI26ElS2KDtXa6dNJvQGx3Cw4CwmhJkhDjQ12Lab9X61qrhzr2Yw 0MZ+/A7HYiMQkzWTUOBnHlmba8DggM38P9ccVhTxeaXw6eHg7vHvlLy52YC0OATJ 8SVZTsKFaBOEmD5bZkvaseEMmSROlP/Nq1oiR44KCCdiKK8fvSjdYzpdkpE8g/z4 oVLuHy/gHlCg+G8eq3k5sswdQx9tmoyPhbxsc9nF8yvd66mcemVLwsBcBBABCgAG BQJWC5nIAAoJEC2zYkPPOLFgfxYH/2imHB9LGnbSXiLufAsQQkWCXpcJ83E0vFdC PsL3YPD6mnuxVxr12ASskqvLk9tAGltsqVYn0D8Kq6Wx2CQ5jutlyvNDLKa6wEUy xeQQhxy/vQLAi9J7YRnRKlm9kkKVt6yHlms/7cUu+2fEqaMRYbcnsDebYlp0giti d7xxM9F06A8T7HBHiBjjlNDTPqcSh6e0ww/Eguk9cHwiRJt5rYGZdfeCfH17pqbz 9T8pCZNtCCK3LQ8bjZNdzMMyoOSb8XuT0a38ReDnTdKg9xUiy8GHXybt8b1lcsGv BM2GmfAjAGApH4XRb5YANX5VZ+T32PF+CPZr801cbYgEcztxksTCwVwEEwEIAAYF AlVwOmkACgkQk55r4eKfw8wSlxAAl5L6AdDrSKjRgp0/gZnrAMHolB5t5JRzXeFY ES320ZxY1F5BGiU4iA2Gzyp02OXhqBmLz6/xNvyJ3SHy/vevfd04B0YYrLUODaof iganJeWFnDxHNzDjaCJgac78k0MXaQNKK+1idoZvYRWKQxA8Dfc2KM2Bv+ft6OJn sS1t0+v9EhC3XevIs/g3A5Legurki4x0+Dl4E/8rOiSuveFO4yZciL8QbPoz7xgO jhxVUusVOUb3mP4nVsPoYDWfzhWHh2JLP6P5qCRIxQ7QjkBJHpUE7YGIDFAHvtke gNHcnWq/GuowCjg7t+6PgV1Tbqc972UrHu3JvXJoWNI0iULbi828wKRO8uF/jmLM R0B2mH2LS37hHgJ8/4Nu08tAA4e4yPIbzb+fiUPICoRGiDhNJ5hHj40d83Ln+VxW tv6oZZ+1YSCeHktFOF0rvS861fmP/dwaYItQO6mdAzOqpjEErUqdvbxWFwNN2ZX4 wKJsGQLgPlvC/0EHERrQ+h23oyEzlUmvADm11X1PSzyO8dM6QoJ9QL1ot9jFqDpN AERO3p2b3VkCKPR28Tb+KTV/vebnh3sUSW/LyFgQ/bWaQhbx7j5F3xvV3UofDN3U 3v6cXV9GF66ptO4RMTpIzmltjt9b0NeAdhz7uvY2bec2EsPwUaqZaCERywOlmd1D OlTDPFzRAAAh9P8AACHvARAAAQEAAAAAAAAAAAAAAAD/2P/gABBKRklGAAEBAQAB AAEAAP/tADZQaG90b3Nob3AgMy4wADhCSU0EBAAAAAAAGRwCZwAUX29reGJmVjZN akhkNGNTTGRoN00A/+ICHElDQ19QUk9GSUxFAAEBAAACDGxjbXMCEAAAbW50clJH QiBYWVogB9wAAQAZAAMAKQA5YWNzcEFQUEwAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAPbWAAEAAAAA0y1sY21zAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAKZGVzYwAAAPwAAABeY3BydAAAAVwAAAALd3RwdAAA AWgAAAAUYmtwdAAAAXwAAAAUclhZWgAAAZAAAAAUZ1hZWgAAAaQAAAAUYlhZWgAA AbgAAAAUclRSQwAAAcwAAABAZ1RSQwAAAcwAAABAYlRSQwAAAcwAAABAZGVzYwAA AAAAAAADYzIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAdGV4dAAA AABGQgAAWFlaIAAAAAAAAPbWAAEAAAAA0y1YWVogAAAAAAAAAxYAAAMzAAACpFhZ WiAAAAAAAABvogAAOPUAAAOQWFlaIAAAAAAAAGKZAAC3hQAAGNpYWVogAAAAAAAA JKAAAA+EAAC2z2N1cnYAAAAAAAAAGgAAAMsByQNjBZIIawv2ED8VURs0IfEpkDIY O5JGBVF3Xe1rcHoFibGafKxpv33Tw+kw////2wBDAAUDBAQEAwUEBAQFBQUGBwwI BwcHBw8LCwkMEQ8SEhEPERETFhwXExQaFRERGCEYGh0dHx8fExciJCIeJBweHx7/ 2wBDAQUFBQcGBw4ICA4eFBEUHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4e Hh4eHh4eHh4eHh4eHh4eHh4eHh7/wAARCACgAHgDASIAAhEBAxEB/8QAHwAAAQUB AQEBAQEAAAAAAAAAAAECAwQFBgcICQoL/8QAtRAAAgEDAwIEAwUFBAQAAAF9AQID AAQRBRIhMUEGE1FhByJxFDKBkaEII0KxwRVS0fAkM2JyggkKFhcYGRolJicoKSo0 NTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqDhIWGh4iJipKT lJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uHi4+Tl 5ufo6erx8vP09fb3+Pn6/8QAHwEAAwEBAQEBAQEBAQAAAAAAAAECAwQFBgcICQoL /8QAtREAAgECBAQDBAcFBAQAAQJ3AAECAxEEBSExBhJBUQdhcRMiMoEIFEKRobHB CSMzUvAVYnLRChYkNOEl8RcYGRomJygpKjU2Nzg5OkNERUZHSElKU1RVVldYWVpj ZGVmZ2hpanN0dXZ3eHl6goOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3 uLm6wsPExcbHyMnK0tPU1dbX2Nna4uPk5ebn6Onq8vP09fb3+Pn6/9oADAMBAAIR AxEAPwDwG7judLvWtbuIxyxnpnII7EHoQexHFbi6jFbaLPesy5C7Yx3Zj0H9a19N 8GWs3h+Bb3VJzFt84bWURxkjkrnoPxxWHCnhHT5M3ZvNVMTHyklcRxf98jk5ryHO FR2icl4y0Wpg+Eoy2s27uCEG5ixHGACTXo2gx317FYa3Z3n2S7spibfdGHRsMCCw P0x+FZt14jutc0uaC1sIba3LLb26IOS7HGB6DFdTp0C2Gnw2RwPIUIGA4PqfzNdm Dh7So5yVrFwu5ts9IPjbxNrep2t74gsLCJooDELixkby58tk5RuUI+pFdDZ3MskD Xccbm1gMYmkTpHuOBn681wXgof2vqltoqXUcUl3MsUTPyoLcc13qp/YfwhuXk+aW XXHt5Dg87Dt+v8J/OvV5uXRGx5xYMrJqflI651Cb7xBJPmPkjHUZzj2xVyK6htrj yppoopQzIY5GCsGXGRg9+RXVeIPh94g0b4U3mqjUXspHma5NpBGokVJJRjc55+VW LEDpwPWvB71EtbWZLmaKe/lfCujblIHJbPUntz6HivYy+jh8TPk5nfyW3qXCjGXU 6jXvFWreHvFEs9o7LB+6VDs3LvMee3f6g0ureDviP4l1nStYntLq806K4ic291dn eELLufqMbuTlcbQPz57SNejnv7ePUt99FHcJKWbCEOo7nuAOOe1fR13rIs9VtJAf 9GltU3Adh2I+lY4/LJ4dpzd09rESg6ejPL/jd4Y0Dwdosup6bZ31xLdSq7SSSF0g IJ2hjjJUsRxnqPcmvnu9mvtSu1eWae4DJlCBny0A5VRk4A6Yr2P9pO/vLrxJ9m3/ AGuNIhIpYMojQ7vlUHgjnO4cnr2Fcv4L1dbM2TLptpco0Eivp7WH2pZ1+bDPuB/i IzwOM4YV4dSzlYk+uvht8MPDVh4Pszod0J9OvdQttZUEZiIV0kUIP4QQkfBJAINd 74v0FdY0aeG1+zW+o4Y2l48IdrWVl2GVcj7wUnB+lcF8BfF/hebwbpOmWNuuhlo3 Y6fPOx8iUtkxxmQ5KckqP4RxXqzEnhcCoqOdJ2mrGtj568W/Bb4ReEPDa3WpCeCC G4WQXlxcl5bmQAlYcY2hOMsdvQde9FemePvDFjcwW11dyWsxglLeTeRSTowIPypC hG9iccHOeaK1jLTcl3Phiz0261Oz/s+S6uIrcH5VVyFH4f0rD8S6LpmkgKdQlub3 bzGqAAH3P9K9P8N6RfavMlro1s91cEHakWM4Ayc+nHrVO3+GN7d/EKOy8TC48M25 jkvJrm8Vf3kUagsYucMSSBjJxn8K+fhilGTcnZHJRU5vRFf4axQ6fZ2N1qVuJZo2 Z441UfLu4GffHeul8UaxocExDCS3EuTErqGYfXHHWsqzuLSGaWIBtkS7z8uTz0HH U9OK5LxTBqN1ffaLuFofMOIo24IUe39a6cJiJwn5b2Jp1JKVj274Cadaa54/068i 1S3iazK3MVu6c3Gw/OAezAHPfOPavQvjJqmmeG7fyNaS4+zT66b+BbZBvciEcc8D L55PHXr0rxz4R/GLw94BOnadd+EdNmtxIPtGrQRkXZJ6sdwO7HPQjIxjvmpe2Hij 4la5HeaheTwaFJcSmz1PUFKx4fc6xozYBztwAM47e/0cadCpXjOrLkp76vVr1PRp xjLc9f8AhpbWvxSs9Yu7zxHqDIJmiFsqgNHGxOwuzA7iQDwuB+lcp8Vvgqnhi0n1 DStYSS2lYAC9kUNbqW+Y7QC0pAHAQZ+vex4e8QW/wl0caUNQUXxKXqWxQSt5THmN 8ECOTHJB7DsTXNeK/HvjHxrexas98dM07Tz58BjmjiK4IGdu4NISRwBnHPQV6GDy /EUp1JUpqFG+jeunTdG8aXL8Lsjy/TdN01p44LvUoZoGukU7FbIXnLAEDHTv7Zr6 A12ZZri3wekIHPBODXnPxLi8KXN9pt34es7p7+5UzalPcz5Mr4A4jjyqHJ3HB/rW r4XN5eQPK+pfbgrLEgnl2tEec7jjdgd8g/rXJi8bUjL2deW23T8DnrSV7NnC3vhf xB4p8dPp8c1wYmdAhaYhdrMQAd2frg44BI4r6B0nwS3gryV8Pxfb209DC8ZVBNcE 8u24Yy244xgnC8VyGtRa7oC2WqQwWU6Q3Aka4gt5HMe48SlVO5tv0571U1D4vW9v a20Jj3hS6z3UDENcMuDlVOSCT3PqffHEsqnjWrTik+j3Ip0ufd2OO8R+Ib7RPFf2 +WG8t7r7d5jLLH+7jPO4bTwWbI619feGPFkOtzWESxTRyXVil5loWVMNxgMeCQQe lfGN3rd5q962ua/bQ3duboyKr4w4AzhRxkr0J5HBz05+sfhr4vude+G9nrk99plv M6kGS4+WNQpwM4wPTuPwr0eK8NWdGjHflsuZflb5aHVM9G2Ayeb5aGULtDkcgema K5vWvEWt6Hpr3tz4XvNTjQ/MuluJZAOOdjYJ7ngnpRXiU4PlOdnwpeXGp6UjTafe XFqwdZA0LlWDrnBBHP8AEfzr0Xwpe3vjLS7O3+IV1fT6oCqaIJEVFuIZQC0rvtIY LtGCcdCCSRiu9sfgLqdy6rrOq2VvAQd4tkLsOOOoArTv/Btr4G0+K10DWlhti4bV Zrm3+0P9n2sfLjjAwmS24t6qp5r46rXjUjyR+LoxYWnNfGtDyrxN4Hk0DU7tfDA+ 2rbIzyzC4VSozgr82A/tgA5BwDXmHi+XU7W+ePWkuLeZBjypRhgB29+tfVnhyx8H 3k7T2eqyW0DFltbQSssRkZfkJfGAMk/Kx44PGTn54/aTvNW/tK3sPE+hJbapas8Q 1K2k3R3gGBkgn5NuMFBnB716GVfWFUjHE2afVb/1+JtUw1BvmjoecQ6ja38NvY6h KbO388mSWOMM4U+ufQ46ds19B+JdE1vw3+z7oesal4gTULCHTEmGnMitDIPMBjJy PmwHXB45wcV8p3suWxwG6YU8D8a9T+K3xDbxR4D8DeGdLv5FtrDRootSgjYhWuFK qqsvcr5eR/vZr6utXdWal2sl8hRXM1YNUkvZBHdTS7UuY96yjq6OMrzk8EfhTdPu Ht3F7ZeeDCVUMAXClicD271P4d0vUtI0OTSvE9jNaXMMIa0E0Xz7H6Lg9cZ3DPGM j0qlc/aLe8SMyzJaKodwImZdoHy5AOPz719vlmb0cXDkk/eS1v8Ajpc9HkkkuZG7 4V1zWNNmSG1ieGLcZ2Ji3FQQTlz6E468cipdS8Ui08WWPiC8tw8M0UUjLjJVgMFx /eUnJ79SMevLaXHLql1MbWK6mvH/ANTEkg/eKOOQOAO2eAK0Na8LazFpGbqc3M4c uVUH5cgdD+GK8rP69CvSg6v8ReW68/60Ilh3VXNY920HXtF1CFZrTUprRHwd8HK8 nrjlWHuPoal8T+C9H17wpqV7f3/n3EEytG3lJEzPyFD8jePm6npk8181eBPEsmg3 V9ZSWKXJuIxHHJICZLR9wPmxYIG84xXsOn6d47k8PmRo21ae4YW9nZBDJMkC7nMj AZVQCASpORgD2PzWAwlKriVGrNxjdbbf8A4aWDk5PeyON8faFaaDZwgSglERUFsv yJyPkfuDz1JwccZ617R8LPir4M07TdB0XURaQWF3pn2TUoymIrW5iYqrvn+GVX5P TOM96+ddUXULi2lS4ky0szuYCp3bwcElQOD149unSseX7TCxNrZ+YhG1lHI3HopJ 78dua+xzOlTnS/eXaSsrbt2eptVjboe+eIPinqfgTWtQ0Pwr4gvpfC8+mJf6XLKg neyMqCSKNNw+4Pu7WJ4I6Y5K8auJdc1q+xrF8i5ZFkgtemyONURdo9AqjvjHrRXw aqpvU4efU9s1j4+a3GtvqWoCbTLTcSsWnbJo7mQbWKlnJIGDg4xjOKr/AAm8LeKP HfiyfxP4qnurLTUm86a8N4RcXULAGO0QqcoilgW6EBgB1rz7xN4F8f3qbtc0ua0e RvON9f7UlAwOMjLEbcfKBjPvmu++CPiGL+ybSw8QGPTfDvha4k1C/lUAnU5mlJgt 0Uddr7TsXO4queAK6cRgcDQk1hEuz6ndUqJ+6j3bVAsmj6voujWlppltJlVaQApc FgVxtxx1A55PFfJHxB1vwbpmk6v4T1z4e3MGuWNw9uZ7XWmRUnGQJfLwQwIwTkc9 M17XrXxr8v4Nr43k0a5KXmqGzhSWdNwnSTzH6A4QbSozzhQcc183eENAv/iP41vr 64k3yPP9pmMrkmUu5JBY9z715mHoTp3dR3Y6cOd2RxGmaRdXrABcA+2K9K+HGgQa JrVnq0lrDdywOHEdwm5D7Ed69H8V+CNJ0fVEg0m2eONkDCF1PmRnuG/x9Kz4LLy2 ClduOvFbOo5HrUcLGnq0Wvidry+I7nT5YrZoBbQeSUY7sAEkAN1IAOBnmuftY4/J xKjbjkD0wRg8etdALKN2+bBrX0fwlLrTtDYqhZRliTwtKFSVOV0zr5VIzvh5pemW PlWcAihkeQ/v2GC2TkKxHb0Neh+KPDPk6ObqKNJeMSgdCvY49fcV5xc6dd6Tqktl cDDxNgkdD7ivR/CeuXFvpnk6hG8tkV2+ZjJj/wARVTk5vmb3CKaVkfL3xO06PSfE UV/bKyxXGQfZga+gPhT480HTvB2m6lc2NxcXK7YFWEhVDOAN7EsBgfMMHgnk4riP jX4etrqzu/sjo6BfOt2HqOcf0rgPhN40fTbSfR7izhubC5KicvKFkjjJ+ZUzkAnr mumjNJanH7SNGo4y2kdp4o8DXttpGsa3b67ZLbQXQhxbz7txcltqnPJHQ9+/SuE0 22eARqygoZP+Wo6k5BIPbp17Vp/GzV9Hs9VtbLwnZalZxXECzs12VcAnjERB5HXJ Iz0ArifCmoTJK2m3BkPVoxu5BHJX+v516GMzbFyfuztbseXja8NPZHb2csUdxLNc SPYxfvBGiw798m3IXeB39c54zRWONQma0+yS4WAymbYn3i2MZJ7DH86K8RW7Hm6P U+u/2iLKyt9Pe7CSeeEZpmRiSIwuOQeABxj618ceJ1zYiSS5jV1fMFs8bKwz1fJH TgcZ719TfFvxBa3Mur2NwHu5GATKkqoijAYkn0LEYH+yM18zeItNuLi7juZI8mRd +WH3c89f6e1cmUTrYag6Lejd/wCme5PBPlUluzHfWddufCdn4Wku3bRre9kvkg2D 5JWUKzZxnoOhOMk+te//ALJng68ntZNZuI2S3+05B7OQOufTmvENK0e9udT8pkYE kbsA5ORtr73+Hfh6Pw94HsNPiUYhgXOOCWIyf1rvnLm0NKEFRXOzyb4l309v42e7 t2XzLeRQuRn7vauf1S60h9Ln1G+l+z6g03+ojTCMp/u+mP61b8dQXS63csgkxNKW VnxkKT1qk9zow0y50yfTL7V7gI26aCN2QAemwEuw9uPesVa56rTS06HF6n4u0i2B AlKkcEZ5q14E+JVhp2vRzJO5j+7IpHBU9a8+v47e5urgQ6RPCIQd3m2pXH4Mc/1q bw1pkV/KFFsFVerqMD/9ftVzSS1RjTlJy3R9N6zLoviLQL/VIjG5hgVo5wMfMcnB rkrDxjZ2Giy2PmRFWG2eGTG5D0yPUfyrL0e2n0vwre6dA0kkF2n7zJ+6ccEV4VLZ 3+sXlxcJdFCr7SXlCnOfQ0qauXWqez0Suepa9dpNBMYpN0LAkc14Z4VklTX/ACrZ lG5z9DzXT41bS4ZYriWRgYzk5/hx1HauR8K3Rs9VF1DgunIB5zXTFJI8XFScmj0f xW0F9e6Nd3GP9GDY7nqP5HH5VyGvaYbW/llErNcrIJunBVgGBz+JFamvXa3EMNxG 0KYJ3IHO47iOBx25qz4caG8TURcyRPL9nQRCR8AImSx6+n1rOqv3nM3ozx53jIxb G7ns76K9gdRJEwZQwBBI5wyngj2PHrRWfcsZJJCoChmJxnoCelFTygl5n0j4xica rq7ljO5QIxkbO/PqfwrnI7qO70B9CttJDTtcFzdFz8iLnjGK2dbvYG1qeDaDutEa Q7uDIpBY4/4Ew/CtHTL1tEsr3TjbwtFeBZRKBtPK/KwbngjqK5ZeR9jhpKTTZ6J8 LfAFjHBY6rqlpE93FGhCFcANjO7Hc89a9nRY5bbyw2CRjjtXCeCdSF3oNhKzHzTC BJgHqBg11+mzYkOe3NawWhyY2MpO/Yim8LadLJBPNbRS+XEYz5i53D3qK/05oItu nwWtuoGAPL2gflXQQXEcg29RRdRRvEfm4xk1tGKOCOJqKa5j52+KHgDWdVZ9SA0w MoAJjd846A4NcZ4e8INpjnz5mmcnsMAfSvorxRcabZ2M7XUirGRtI9T2rh9A0a9v 9Q8owCMseMr29aznvyo+koNTp88laxg6dpjsPLCZB4HHSvKvE/gfw7pHi2/h17Rp wt4BcWs0e/5c9QVU+or6707wrY27Ju4YYJya4j46aLHJYRatF5Yn09sgjuhIyP5G tIQlBHI8VRrzUErnynq/h9f7N1BrGaeWJbeXajIVA+U9M9PpXjmlSmK4SQc7eSPU V9yeKJbTU/BcErJCcx7mYAA9MGvia7tks/EF7apzHDcSRr9AxA/StIttXZ52YUlT mmjqLuCOWxQxASdHHuDWUVLIQNqsPXgCtzSGRNJti65aORk4P3hjOP5iszUJIlvG i2NsXuW6+/5U2rnk1o3tIxnlJmJdv0oqeZFdh8oOfmAzxRU6HNc9y8W2baH4un0q eYySKnlrIxxuLx9D7ZIH5VT8Na3c3WlfY7x+I0IXf1TaR+nJOO3NT+PRd6xqsupf 62WRflbgngY/kPyFZGhJPqWmzXKoBcwoxnj7uCMFl9egNcztqfT0nJJHt/wJ8SSm abSryTJZyQpPf2/z2r22M7fmT0r5Z8G6glh4ylBcBdyROAM7SQCrj6MMfjX0x4fv RfadFcKVO5ckA8Z70oPodWIjdcxrC48mOPZxnk1Xur2YxnaTj+f0pXP7xQx4547V X1iQxabI8ThXK4V/7taXaOOEI8y0ODi0y88SeM997KU0+wcP5Oc7m7A+nrXW63Ya ukDP4entILrHH2tWKZ9fl5rE8M+IfCejW8lkdYt1mR2eZ55Bvlfu3v8AhV2+8e6K 0jW9nY6xfSonmOYbN8BDxuyR096qnGFtXqzvqzrTqJQjovuPO/EOsfEfwzdrfa94 o0e5i3c2tvp7kkdwDkEfU15n43+KPiHxJiDi2sd2GVScv9a9k8TeL/CtrI8+r6bd xXKR7hBNA6yFT0wCO/rXz54s13w/q2oXbafbT2UzvzFJjv0PtTUNbsqs5Qim0kaU /iyez8N3FrM+Y4o2kHPYDOPxNeP+DdN/tS7vbu9RXWNHuZtxIzgHC8erMPyroPHV 21porxlh5k4WMD2HJP5Vi+CtRktrS7jQjbOqpICOqg5/nWsLI8jG1HUnbsW50+za OONu2bgDpnj/ABrKvZrYyfaXyXKjPJwP8/0roNXXdow2EkCRXI98GsIWCyypDIWK tGBlepIBNOMG3buefX2My8kAsgEZ8A8E+lFT6pA0duF+UASFBjq2Byfp0oq3Dl0O VR7o+hLW5spbiez3eVLHbhUkz0lUZyPZv61zPg3cPFMMajY299wA42n7w+n+FVYJ J5LeOUZMygIQozwen8qraXcyWmqvchtkxU4wehw38+a89xabPoqbuki3dy3GnfEG 9t0fmQkxHPXGGX8f8K+kPg54pg1a0YEqC4EuAMFWP31x9a+YNcnXU9U85CUuIpkm jAPLqQAwB9Rj8s10Xwm8dwaB4ub7U5jtZpSjEnC88Bh+PWmjZT3i+p9iyMrFSp56 ZFVtSiWe1aJiNp61R0zUYLq1jmhcMH5GGzmtMMJF25HIoumiOVwaM2Hwv4c1COBL uwt2uLUk29wIwJImIIOD7g9OlSaho97Zy3F/aakpupbNbUmf5gEVt2ccDcSck96v RRFSWUZNZuvvex2zNEN59D2rqhOMY2aCMXOe+nmcd4vvdZ1OHVIpLXSQl3tTzZnd zGqg9FwM9T3GK+ctV8O29rqt3cSTJdTSSEvKq4B+npivXPHD64YpP3UrZyQqsQK8 f8X30mi6LNd3WfNUYRWGNzngD86I2a0R0Yp0YQtCNjzrxncw33iFNO8zbBbDyi3X Dnqfw4H4Gs2xSSxkcE4YEjFZ1uzPcl5CWaRsknqSeSa3NV8h7KCWORzK6ATAgcMO 4qna1jwpScpcx1EHlXmntHuHKBhk4wTxz7ZIH41gaoYzcGMxjYikpnqSAMfyxU/h m4Hmx2sh5deMjg+oP1rdvdCkukLRO0cYGWYqPkBPcnp6VVK7kmZ12uVnLaqytGHG G3MxBHvjP60Vu33gu6s9Hubsfa57S1mSJpAoYDcByORkbyBxnnPoaKrmu3Y5ebm1 ILDVZ4ZGP2plZueGOfzqeHUpF1ZZ5Xc7wPmPXiuW3FBjOQeAa0bSRZhsYgPnI96z rQW52Yeq9jpdQVgIbqJMmN8NtPO3qGH6frWfet9utydyCbrkjiT+maj03Um3/Zpy GBGAc4x/k1DLAEneNXKqxJx2PuPQ1zrQ9Bu+qPRfhN8TdV0LZpl2r3NpG3yjOXjx 2H+FfRXhbxxpmsWcb213G+7tnlfY+lfGGjzmDXIPOAZHPlk579j+levaToNlqTmW 0vZNL1JVDCRG+8R6jvWVRJO500ZOUbM+otO1mLb8si8jv2qa9vbZkyrqR1JB5r5N 1nxf488JXPkXqRXMR+7KM7JPx7H2rPvfjPrsEEl21okWFCbyxOPp6mtoSujNuEJX Z9MeKtQshZvLIyYC9G7V8c/tA+IU1jWLW0gdfLi3OQvTjgf1qt4j+LfiDWLaS3S4 eOPH3egb19zXnk9zLc3LXFw5kdzyxrWEbK5y4rFxqR5Yli3CpbCZm5U/zrbjhEum GUJkgKyjPY//AF81lfZQbKCRXO6UsGXHGO1dNp1oB4fVed8kixjA5HfH5Gm9jjjq 9CfT7KKOzt5Sr+Y0rIGVSccZ6/UD869R0SztLi3syyszYO8jOCpGGU/lx7isTTdM jjt1hk2hVBc/73YfpXVeE7R4NY0yFg7xM3zsrDbGGOMfiMj8RWM5PksjPFK0Jal2 +0cXd1ZRragSRahNc7S3JiSFJAzcZGSfzYe9FTeNb2LRrFrmxvJ919GbeU55VlA5 X0DKkR7ckiiuOKdjx03Y+aMblIHXPTFT6Xg3iCToA2c9+KguIwspzuwf7vUe4/wp sEtvHcgLLvOcbgCDz6g9K9qoro9OnJxki1YSn7cjS4xI/Xtz1rXvHSRBDc5H92Tu CP8AGsa1kZGw0fzJ0Pr6irUsiyoVZtjdieh/wrjaPQhPQQ7k3EMDs7jufWvTfC2q SanpEN1kpKgxuU4OR3ryVJysoR2XO4KQD2r0D4YBg9zabspu3KPT/PFDjdamlKo1 I6ybVpJIpFvCkylcEOOo9K8Z+IN0ouorOJQijMjKD0zwB/OvXNU025850jxg15V4 40iY311OyEPGePcAUqcLMMVUlKFjkg3BII6elP3I0MY2kPzkkdeeKbaQtNIyjoBn J6e1WhAY7i3jnB2K6k/7pIyR+FbNnmq9rksdwRAgAO4cAenrXZ+DroXeqRJsLW8W wvzjBCqCR+I61zviy3tU11k0/wD1C4XIIwxHBYY4wcV3fw1s/IgexCxu18gkQh+W HPynjOeen0rCc/duW6jjHmR2scUIuPPhU+Wq7Qjg+X0AwT1P866DS7OOK5S+hmCR rPkIXyWxnnB5wCR83bAFYl840y0aO2ujdyoN6R2sX+qdTkKykHdkZ6Hgk/Wq0+qX FvaQXt0/22QEi2EcO0SBTl9o5OFDAZHr6g1xVKspe6meXVqzqbs6fW9H1l9FhZ44 s20EiTwK6lfkf92+7PzZV1HHpz04K5mbX7m6tpB/aMkcTFZv7N2nyxjIU4JIwVyO T9etFVHVaq/4EX8j/9nCwXkEEwEKACMCGwMCHgECF4AGCwkIBwMCBhUKCQgLAgUW AgMBAAUCWTK+5QAKCRAH0xAu8aG5rQyvD/0Z6VON/VbPtZ2cVu65wtNksfj13kLt g41EaTm1Oqyq5PoNiXKraod3QV/POMqHLuHDTmE7I+Ul8HvOb4CImADeSNPLUbRm 37iGw0LRGjSOu0ukvRk/0DPywgiyys1HiQlpH/alAazlTjq3cEVHgGx2hrUMkr+7 63oNBkUNMW6PHUKTaUIW/ULT2cIRR3zBkBpSXBM41XQIW9mZ/6wCjlP6Wqun9YKs forZfS+E1vl198JPeOoLNcCA/9JT8mQyKnhOdxt4d3w1HWf/ifSS7E0J2UEDz009 MZ3QPM1OM/WBunWAx4e0nGwnQ7/7SPhtZyoE1Sk9jJElibxShszXbK9cQyuI7J+k PP9DgV0HtbKOixytM7VOpinK2dHrXCucbrnoMXhATmsPJCokQxW6luVw5TQSiGJ4 rHbxQT0O+fRILTtpNVU2r2RnHzDby6/0zpY/JCi0vp+Ox+Ai/tOe1Wn5+z1nBHQX 2G4/LiLrsyjZ5lXuD27vIIs7Yux9WPQR16ND9QkgIzGCVirpr9CNGBKS4t9slV3J 7LABK0vW6tiGK6Z8VOCK1Vyp7pU/RFTapnysGSM6jNnq4fXJ5Jn1awqOgUNMmQWc 5EqL9wcIzN/BYVOyf0qrnn1AZoTD3Onpfadzs+brTjxw62b/qACOYmlaLjxcHZV1 k/wAihWMcY5BjcLBeQQTAQoAIwIbAwIeAQIXgAUCVrIn6gYLCQgHAwIGFQoJCAsC BRYCAwEAAAoJEAfTEC7xobmtZ8cP/j7OBwANcR2RTpV3OC1/zE3DdecsDPrWV5rs gvN9pXBhDDq/ImwSCLBAFEjj8qnC4f1C6dZJH/r1MltWc729GadzhpimqS0QC1Z4 2o1dwYlkECFiLNC4AXJ+lQfvG7hryRewbqWavpCq/1HpMPYevb7gv+STqXqx8q25 R82nPD5p01lPOWpTQHlFxDzIJP5BftAOgPrF8J9sL+G9zq+l6VHnJmRrG4yozT8f iHaSQSB+En3qlstMw2aBH29qP+x+wOEZvcHCjkVYAj0fe0qrAO4jZCUzCRpk4wqw vG88mZLGjunE9uhNsaLP4yPw+cvFxxEk8HgGQHUciFKTv3CT1ebPffzR5nMQFL+2 maoNSGMziaWfBtsIS+QiBVb04//AfRHBj0dysxj4SZRxwBeJLv76Wlz+mOatcAmH deW1Xlcv3wFrSSrpSd9IaBJ5xQvbcpQ2E9RIYVFKT8GiXKMIGqyzJnZyErHcRWFw kFwe8nLtyopIByN0MWqLsWd0x+5vNQ6VbvQoCV1Gl9OHhUmgARr93mNObnrFu8Wh 7hX29jP3fxuC6WnP2EEisb9pTHKEFvOTSg8YwusSFaJoQv1Qaigax061NN/FYUra Av3pu75CKZbeDAnlySafGcq/rqMH0y9BWHCxfX/Z+Es2Ocg1YnvR1RKYJ4/Uq80m yXmmxEwWwsF4BBMBAgAiBQJVbw61AhsDBgsJCAcDAgYVCAIJCgsEFgIDAQIeAQIX gAAKCRAH0xAu8aG5rTiAD/9EaS+Zp3j1DmQlFc8Ybihm7nAV2iqgBMAOEAbKFTHA PFF5eHnu5cmNjaD0lKELzI/MLLOGcPCOYHmsb3+oLzSO+nHu+WtFUldoy6l/z2QT qRkmX6uYEWjIC8EeG9ECu0bowhsp15mn+7nXI78kj9jdGs4QZ/HRkVdrGzTRuk1u 185H6drXKhy7B83k33Ckkr3T6am27SZAxhryHot02DBgskU7cHIxfbUHb/K2SeVz dQpRgFHxuDZcPEjnEmFCEWSvsA4dWXSzAlIT7jSwYMnELNrEcfA0NPji/iU80GkU pMFgYiq7jSt1zLq8MXpsxmjDouEK5kSuuk1Ol7l9TPhCcpL6hQnKK31lQfQ9BwTD h5oy9UGlaMh5EODDgHeswnbd9K0sK5GL4RHrL0mRk7m1TaDrTTG9+9Gun5vDxQTo O3fFJlDWRtxqRD3eZWr5yFeeE7xl/ZdTexMDh0S5idCwV7/YM1l8yMnoTJ0DWFJT uxlHiuEdD1DVpuPAItzv0R8ij0Bzg1T35E5lcmr7YrrG82+o1G+KDjMlHjBhgNh7 bvVh4D9mYHsR2WeMmpsYnfYgTiQyNaCUlUlJSiG8j+HaSwxFiD7+77DeS9Odp1eK ZtQmcmCZZSeL1C1SoNMW/cQ3Nsi9mC3Yg9uHk2FccbN1pkYG8XRF+15rm22fn8K3 c8LBXAQQAQgABgUCWFcNLwAKCRDQG513kS+WPCKzD/0RKShEUjzQXybQAZZ+Hy8I LUFzh/M/4ODsMaQODXDS0Dg73NQsrglCYsSW7H8c+4Nb7rxpDFENDnExBgUaxkEo Wx2XVCWqQOmpDFuj4+6tD7aC5HWjzfXaxINBoIuXX0iGT0PhJNFilD5CK4RqJL7A LxaFj/ls0fW2eHfDnmBFJr91Jl6YI+sqcMLmmCUTjJJkcTWn8SwWRGljtjh5W5fZ T+z+cWQHACd4riq9t3MDQSVOOEsYQ0KyHYrfW4IF9RH0NrZA+/zEZEZdIhmElQIm hZ94dTbXU5iHGsIEcA4qUaUmCWqUfUPMkiRzB9HFU+fEJv2XQH92fGUUG4KP0cVD XHhmI3wIcdxfYs2Fnmq/w+UbykXVjtfd4E8vgsOoXNXMpkfr9Da0UouuBObVWTjj D1FjvRA8nVPWzFQ5GIAzGrlTVqi+C+1dI9thWNkpbrkq9tZCrXqes7McjFCNnUPB iuNFjt0MsKDPROPHyDIWWpNfN3aTEzjYD7TpctSaTIO/8zEjPYN46NaGzb57w1Fk /aKQrXK4Wo5eM8KOX67ajRnkaKN7ejEK82xTjoGElK4kTSMnNzVtr5h60IJbvXwz 4kvu51W5AvuRb0Ot3k3OL18rlnZXJovbj2CUpN8QrRDmal9eW9d1Vs1pxyoOrHZB qD9DSz1+r6NBwASnCaTvqsLBYgQSAQoADAUCViQHPgWDB4YfgAAKCRBFDLp/lo8J S9HXD/0ZsZDTbHtMoY6l6L1zJbqx3HZLYpO36f87Cz3qsVNEhsNexmW8kUEN+cOI 5kaiPkSwySe57Ub6ZZBQko3Kn/dOh4KxKf9x6vZJjkYWQunCIWO32yvl0V8P8kWd GiLD1rO2ldidjBKyyHhdb/rH/VoG2fDGUfC5zqI/R5Z2KXOOFXATs95UFzz+vQaz oDICt0JRjQkYd4EVd3H9ZJPDCjYHir+sIL1BP+LFvGtAt+kFsxrmDuS7ESYd9UCL 4SLykeaVo+Ip/v+IYjqq+fZDevdNmazKkBKyIYKvNCs6KteFzAEO3Ih11kPElE2A Ws5dre60iIHiPbd/MqQs1m3Rd1YMyv6Z7ujd9HqbqlnStTjApjB+xIacaDD15AIe v7OgnwEB/jcGZrsVNvSpdLZhGg4puoa7pm8Vc823Qrlu4A93BI4yZUqhh5PeVkHN VEXYDyFuSkIbWHc/vOl2cfBa6uotKusC+tldxI+DyaeXrqzZwmqhBhBMHFx3Qad1 e0f4NfWMa8mNO8BeFupKQQbI8CUW2k1RRMNP1H9fttbNvAsGGYRBTDgM/GTm9m6n VaWRuih+ImEqpI/7A1qb6WtnA69eEsPPEmFtQf6jozApuZvWncn9UB1upvaLSbmq wI4WvbAhAsjGNNYjzNVPG7HPX0yJRQ0YeCDjE5spmU6PQ5R2UcLBXAQQAQIABgUC Vhd8DQAKCRBO/U/cP0bUHsVJEACylNmGoSSe0V3WYiESSRmr3xFfhuw/Dahp4E41 Kz2UmBDq3iDzA9B2e42/c20/lEUkCF0ax2af3/UDwsl3B8UPsNTQd0itX/M8NCMM l7BswfenYpP+kej6/4Nr08M287qr97MyzEQJ3JdmA8DfrIyoSyDtZQdxvN1+WPYD /LzoTtSrlhrfI149Fm5b0JaU/zypYnhq0rjd6zWUbhqip3VDucHAluqxikDn77yK Bo2E1Ktd4NSyXJZfPR/2X2QyFlBPKFEOWNoDVG3sfo2dtMzY4B10EWJJno1WCd/x jMRAjKxW53xv6Z5GmRU9aHYHGI5rrM9qJNFZMNNwYPlPqsvzU6jatIZ/B1FNHRR5 xv8oJ47fJgqznMPZISltDL7NYGaUaF61N3QUNF128CVDtgWuvH+TNK7Z1s60Gw6k T9HJGhEnSyjzle+z8WwdeQwAq2lcl/Y1bZGGM+wiKm5/hB1zpdmFysJlTTlxQJSV AfrMTz4Rz/bqF47hkt0AFHy4ZayxqYehQhyBgraJWFkhZWUSEyJfLkdNue6JwDyE 9xMZtzHwOGa33P7W2j2iq4LDtV6L5zV2lC1/ZUkr4F+3o90HeQgwTCSzv3WpXc3m /dGHZk+rbNU5+qx4BhC1l7hCL+oxv26IiY923xKjNOZoKkZGlh8DHX9NiiLnD9dC qmgIX8LBXAQQAQgABgUCVhAEhQAKCRDCGFJYGfeEUQvpD/sElDDSgzJ2SXIe2n4A 06aIc6mhSKeRHeSehALhlUKM4JnGP3K4QNTzmLbRS/jjSEudJXPGlFFRNQ0+/+m7 wc1Miv6xLABemAS4OoGm9tShZ7PhliZUiiroy0Pmiq/CgD0aKChNYHOcm0xTZsdJ 3vgxJ2KktpxfrrJCSyZpHZDKb+DGGq6xIlf6TKPbNgKOP3JeAaBAeHUrI8ju3bgA Wcq2tekN0wa9fOCl5QNnHPEaoQyC4mcwZMwkE3fTL96tsm4Hz2sLPZbWQzGTTCJ6 CVVB6uEnuyLgKIGg7Tl3Umx7CjwX+Xj9nr8AD9D5aMX3fE4TKq0oc4O7Md1DwO5X OGddQ8/gfAyzAAECpz0Wedgg4KdTVQ1bHTDSgeQGtv0cQSyLL6iMv+KXpxcbJ4bt +iOvDhHMYslRNhsPTsc/Jy7ABFYtNgVN3ufV2Vi/2TYfHMO5DZ3PsY3P1NUbq01O bGeHmwM96h2GXTkdaPr7fyU3RnTlsN++kJOUpV3kO4G9a7HxvwfbcT3atxZDH5RT xWoHL4aqoz1wC/QBWiPB2kAtmiQiO0sYvjwU1ZMyXX0Urr1AcUhfBGxVVH9T8gFn QThqPK5mS0PCkpCiMhdiZ4C78TEAzDzXILfIzfmLr6MUNmEp4cMKCkIl08KWgXHS pyyzYKkQiPGMhAHyL3DqKOZL/8LAXAQQAQoABgUCVguZywAKCRAts2JDzzixYMb1 B/9yI8HOkygpWmigIwsjWp8eczUG4mFCUSG6Xd6tqbkY2d5vabWrDk9t7bnKfSmD CEsW3c0NGJb8M0WWnslK42zsY+EULrUNJyvVca6NvyQCDICiTzOqCBpROHO5DlKx pYB6ppnbH8q0nu4lnowRb5mzAyOr0qmahBxwW7Vh6F9H11XeTI0wVxSYFAQhZhL7 3AP6Rb+c4ADia2H/+4Flq0FeYjZMZ47sOnbnpe5QFuVO3YW4zBWnUbpKGTuDeuDK uQZvSsKGeAS/rs8WBPy5J78jNGw8xXXJ5z48bB01+5GAlAsKvfoG6qCVUU10eIX+ VBkhGUK6/jnMY1MHdIrtjDuFwsFcBBMBCAAGBQJVcDppAAoJEJOea+Hin8PM31IP /3OC8ZNmsrOirnwET/MHKBgwBqulX+Z/E+RryKFz4/6VkgxIfr2RZpNxTGUZRDwL wVTpdIEegrZ9szcwE4R+P64DI6Q/2o6IbFScP/wJzGeO8/tOGmbIfbNpLm22JyjY z8jRB7IIihg5hPe333ZGDSzbMfOVYX67Q56J4PYSCM88UjKKPru2+37Hr0sf6YJl QovoToxpeiGFhoY7WiNmBYYv+84+H6MQ78xP4hFbKHIBekHyMWpf1uei0UGkj5rX sIMPGLXtGVcQxAjwC8bLwBUHu0xuy25oFPVDi1tHpBazpeeemJ5LyAg+zyiD6rvZ hwDEVVxzzyP+CKBPJqTR/Aw2OiqARowtJrx78Y6CefjgAdxK7PXVpFGRdnZ8VU7t 2O6zl+OuVibmUEZVfg7vzmhgaq+O1HPgWdy46LbGnK/23/Y3eXGRxoaolddSwvoj 6ImNbNSpm0VCjZqlJRDt32CiKikyqpERg+LRqYnLSq1ViYJUxt7llzaV//yv5i/E gymm7oHD10/BhnOvIJGOjL/0W+ACGhpOugYXZT4zlJ4M6g3AVoLLD6/7nEyaoBAB ToGD8KQsiNYIvNSq4v0RlnMlCbZ1NspfZuW1lLlzZgqa2Yuw0Hw4QHxk6Nwz35lb SveR9Je9AtS40Ll/Y3bJgLPBjKmkfJDHtz230m4vIy0gzsFNBFVtdmMBEADs3IhX R8zrn0LQIDGvUky+L3oL/SZGYBzXtULe5IV1TBMdBBxEJzcGDufnagxIBOmHWWVx KXpTVPvSsx4RQIvnAMwYFLp+66D6DyTizBbFFgNvofsh5sGNfKZUe+C66TJ9yG6P srmDKGDOs4yNAm9lt+hZXlu3hyzVvzgd70NmN++lpOq4tJTABoUSHwqBen0H56c8 aRlIcsOdApqJJsnjYJhosqFR/nK7zEk0Rv+++iEoIrsp9P69jnA8iHFINYxdXpod 6VFyWvu5d0RllE4YhAg4AcGWaZnhhL0mtqPpntILCU5I53g4qDXaL5u5KmWwtHw2 0deRePCt6ix5O4SqWFSJR1OeBrx/RHHd8+XBBVY03I8ThdzG2Pmtf/jvqYAcI091 KZiIhgc3cy1JuBy7q0LsORWlXUPoMcWunukw9jiuVtiakE/OIF3O0DFqpxpjiotO TwVCAow0C2XLfEdrhfvH9rx2266OExl9Tvt4fkIwZR535fH/7pp5m74cioQth0xX 2ixgqUsUpR9dBAOGe/AjUFmqW/yLP5NHg7d69CpMkeHYw9nQlLr+3HpnLPaIP783 2EwjeQCnYasJqKzfSRcCzWl4a+gwoMtRHgaECFG9gq2Xj2wPnh4bDLmmeIM/fMD4 F8NK7hGhEJl0OAjayGAoQn4JTdl4riyNHXqO8QARAQABwsF8BBgBCgAmAhsMFiEE au8RygnegG5p03CXB9MQLvGhua0FAlrU/k0FCQkp7uoACgkQB9MQLvGhua09mBAA wcSD+/lclkayNIZ0K+k3YTo4ZkoVtYNk33CnAgA3jpGp3gH80r21JMgmmVNli9k6 lC+uZ4VZYHvdkCx9LyUgMby+x9fsxJAPDjZYUYSpQItWyynDDC8ztGsTBv2EHtXE 0NiwlApxtIxwfPLTR0LuBunRmAspPdNuEDMwrlmArSi9mKsRHrJjRq9TBa+O2UyX 7T3UOvMHZrkO9TiFZfUeexV2XOsN6zo5wYga+OeJisM1lzA7zsOz98XKYEsksJ4c NzZE2/tcW4xHWNbSSMiexowhN4dD7dLPEZQTOHSTDS7FFnN1koTz1vIqYChEOgtm 2CGOthAKltKO+7ih25sx6x1gDUvJqx6q5/2xbJEomKWfiYKCUvem2Sj2jp5ucUx4 V0jW1aWViRi7404rSuHGz920k45hPZkOaa/URQVuejK6Bk245XPDQhkRu5H7Nzop 8/EcA3rRXWhHhNpRXG95FnKW7mGD1sVTqA5jUcEHbTRMjzYOWX1jmQIAt+dN5xq5 sL2okR4WAiHEkNJP7GDVYvEsd3wzSmQuDBSzuESVidToo+0BXWUVEzfAgY+Il0BO fGirkSwlCTIVEkdNPAzXf8ut5jM55+9nvrUZGbOyWTS828TOpnBD1Y1PHHuB58/l PCdDbqkchGmdCI+7uhahryMgmXGKYKBblR4SbkynZIjCwWUEGAECAA8CGwwFAlky vx4FCQeHr7gACgkQB9MQLvGhua19dA/7BkGCuiYT1G6M/C0kSuHqDnrsXjs7X4oB tNqy2Mti8MA88KEtfnL13jrbg+3tRtE728euoMGMKymqaFrhrs7VqrOaakYr1kXn UnLXOB+waIGAIYJ4x8FQtFp2d9QZ/VnKYLTMscoBWlT0AKHTIb1XOLjyMSkHSZig 1dB+wEjG/0zW9ZQqfA80OePtSDVJjnAg0m5CRxKzoOWojXtQ8/sfyv4VtS8CZDNJ /cB0bG1AeENnEVf6MMpgCihMga0SJxgNuXUyraIGgqQjVpvADXsgvkTpzcMJVGTm a2axMYqU0E0CAzcZK8e0Iif4tpLIo94xIzrC74AQOxGs733xFFMhHkPOeHojr/V1 dQ8eAddaPoJ86zEaikvrAH/BioX+nUiMGPlj9mnqPiRc483ts/xh3TEWb9yTtihN NDx7hM0qO45FmY0BPrxW9yujzDKjseKG0mlUtHqMFHV9ycHhQc8PUjQjKVMY0csQ lo9rV3IEPbYwN4To3uw2SE4BrFdLrlj4gIJAhrUlKmyczFWYYDr7lwXo350ByI6X se/GUuDduxPfj5h6X0ckMF0w6b/Q2NmAektZutH2vm5xp0QXZRTNxZ0RzTidcXo+ lRk4H1aknXeydTdXwVFCKmgTjMOOpbgIK8vIltysx8fPTFJccak8Uu17FOJsbOFW k5MFuspryXnCwWUEGAECAA8FAlVtdmMCGwwFCQPCZwAACgkQB9MQLvGhua1LmxAA t4M4+zldWBSjF3o7dDibqRbxHc4WScxkZWJFpdn1bKLJcngwRoSbNe5aNAUahRci prdBFU5uQodq9kXhf0CBSIHi2PgHaxJdNiSE5jjdWW4P5Uq6m1DadtMKx9fGERhR UDC3KJbs62UKCjiVaeDf1sNiI2B2m/ZiQ9PPeus1TWYNsVHM1Y+d9TsHy7S8sDwG H296a+ipOdnzAx6wThYF/5u90KVXrBaSQ8GDu4CPTSffwxsM1KqbGZw5+nt4oDaT gYc+wLtcDCXB6hkWj86a05Xc+ZWPzlaegi88m19LVgiJVx2JVdTiVfRxE76zJONz 6BfajWGOEGpRxhEMfq36T5xfr0RDRCO32YqBsMqorKBjglxlwqlEljNgdLRM0eG4 S3Yzh7xMkWnyryeCo4bmmkqm/0zrX0G2aPkJsXURlc0F/c/DyAz3nMq5xrFHH4M0 OgRdWl5mhJxv+Uu55s5HziinYJEXM6nBgmL13j+rUA1PeYPQU9n09AoM5EPRghoP u7BxlxkSzWaj09v3z/eB4cccvgrPXC97xj8DqNae7cQaFHhcWHTx/giw0cn8MHnZ 6DBwbI1mkuvvbIKMhuYNLlhhDd68IkAoAaTq4bw8I0Mnbxtq2eOd26RsiavO5LdU r45pDdLym3i2qIfgV3eU9OEM8QXqEjMn6p/ucbrit2/OwU0EVW1uIgEQAOPhkanm K3G9zj/OqCB6u449U7kciJc5fUVKEERH6GQ0cS3tV/Du8FfjIss3XmUN3evXjlMU AaGgnpe9YU/FsyAWO5/D1nWQb2u6iQ8CqfCW6AFYdpSYkmJquh0RFrSt+2UBOh7/ v5aOEURwn/8Y6xhNcloxDdJGc9Tcvf0rMWaxF1EHGqxfxWj+fUGw2s1m+BHlvGvq Xbc1yNIXrBOSyFq+AUgYOS2wh6GnqVKLu12CVMgHS7P/UAggBIMF0cGNcDgpEsNQ bzS9ldJhcD0OCtz/VJvnlrVQMtI6YII68XDpX7dTet9iC3tHanHIu9Jfx8CrdPOh 6g52X1XaKK9DdVVUF5202dSefbYCOPWEqxEDfmvBxpHwuruSOVbwbn5qXRb+n5Iu HkdRQ3aXDQbDYObQnscM3QAVeVLxb6z17Ot+njPSPpdEKPHbQg3mjDFXTNaMWGCi 1l5NXKhGWx+37S+QZymHYJmaExA0kX3ltx0HwLvrZQ4ZrQeMVTstYC/sKjr92R/I 7iL/snCHJ7CZVfDF9yG/hEWAMT+5ovcjN1l9COJ5psLh52gNmzhrKB5tn3E8m/l5 OCUOgREWAa8gp3L8YjjBEG1b/l/5GJB3F8xMhPH5EcjxqX+mr3Ffw1ZrnvydgsXr Fzy3OxaRxxtoqPQWX8B/zcXp300PiBS0tTcTABEBAAHCw5sEGAEKACYCGwIWIQRq 7xHKCd6AbmnTcJcH0xAu8aG5rQUCWtT+TQUJCSn3KwIpwV0gBBkBAgAGBQJVbW4i AAoJEKusf9HMEAp0ZwYP/jESlL+P7vAJ/ykLpp10u3Va794SAfCEemys069vUxty +OzC04BVdB8X0AGqQGt8NooKtd5zoGrGL409N8sSGl7VXyamuaCoRhkE6eR4aO5u gbHvM9ka9aGmK8tdUwqzgRg8qLhsbTvPPplBwSHNl66cvEqh1xwkdZ0YqZDLz1IU Kn6ACsDiJNfrax406e4t5OeoAjtdNoN4T05coMRxTvspBRMewGmyn5sJUGkydWlG qgyd6rmSW+kypdgV1tczW+7hgHkHVEPzUsKksKEmRlK0BADVQtgs12DGk1ftWTQ3 5WEWlXBpgxaJm2Y6+fzOzDxbJIDAwSaQQFnzchFTv+qHdmM3zK8PolBdvVBiju8I aazZtOo3oEki9r5JP43nabGqmQEOJMyCcKhKjj7bWSk7mcx+NKWTwRf1wqnv0Qe/ xkmFfnK7bFbfoo1/8wP/p+9bhsAWcbWG3PtIxS4binc17+09ZZj68MiOOTRvIhlx dVoA9IbFSQhYrTspZGzCka5kbHPzVdw4LedkHxVSNctSMds0JqIoewei2Kl35aCK vXLs6Dk6ACqX/eYL8wbKZu3AQ9W+48sEc/OZNYsIwTjlaWuqs/ULscToXIu5FnI3 2g/U5yDuKIYLjVUXekCMn5LHAzLf4Ub1kKxhqp0p/mvyDg9CDFv2oosaeSGBSayD CRAH0xAu8aG5rR4wD/9ayKZmVbpdoy4A92L+rED3iIXveLpvwmTDMX/XnIXg3agM q+CgRud0R3z+Dz3xQwqLG46lIdlsBVcI/hMzg2zP+zRhI2agNFpSPCrT3x/COvOm uhxbJ+xjJKoG1G6o73ieusD2+w1bFmRf3yZ0LSB2fmfyPZPtyW1tpAku21puDNn+ nPbTqt9IPzFrRP2NMoKVWsPjXt4G0w46zBacto1tqzglNAPnYLAHtcdMLrWbBNou XS+3m+8tmX5NCvdBNOBLRmkKftz16JYMdemJSTs4krCWUJDTOT+QH0JVe11ARDNs Muts1IIolycQG3fDfKgljQma6xMrYcYIzrsBJOJaRyfi0MefiX3GFsar+Sx3lgBK 1422aLxkPyRS16FX3OnQcKxt34XoY8llQ6sk8Swjq9FqX6ryXrY34oGJM0RJ865R kGSXc2+k/8jRpg9mSPGpaqO9j66fx5kvSi8jZvyefZSCIwGwML+okz5oi0xdpCle dCK+Pd3PAC9NlDHOvGg/B3IhbWy7zQEJKwLcKzmw+dWlaJwCSaLUOYB9owCF7Ibr ROUkYx+HWfpYq1WvOvMlS9bAPHj0pKxMpyIU9jUpJdNQuv53Lwmj+eORBN4sfpzT 0LzinS0ukiEudW4tTOi2TiF4i8w0o6uzCm0W0auHESD4M1/tg/uiRHDxtDvCDsLD hAQYAQIADwIbAgUCWTK/AQUJB4e32gIpwV0gBBkBAgAGBQJVbW4iAAoJEKusf9HM EAp0ZwYP/jESlL+P7vAJ/ykLpp10u3Va794SAfCEemys069vUxty+OzC04BVdB8X 0AGqQGt8NooKtd5zoGrGL409N8sSGl7VXyamuaCoRhkE6eR4aO5ugbHvM9ka9aGm K8tdUwqzgRg8qLhsbTvPPplBwSHNl66cvEqh1xwkdZ0YqZDLz1IUKn6ACsDiJNfr ax406e4t5OeoAjtdNoN4T05coMRxTvspBRMewGmyn5sJUGkydWlGqgyd6rmSW+ky pdgV1tczW+7hgHkHVEPzUsKksKEmRlK0BADVQtgs12DGk1ftWTQ35WEWlXBpgxaJ m2Y6+fzOzDxbJIDAwSaQQFnzchFTv+qHdmM3zK8PolBdvVBiju8IaazZtOo3oEki 9r5JP43nabGqmQEOJMyCcKhKjj7bWSk7mcx+NKWTwRf1wqnv0Qe/xkmFfnK7bFbf oo1/8wP/p+9bhsAWcbWG3PtIxS4binc17+09ZZj68MiOOTRvIhlxdVoA9IbFSQhY rTspZGzCka5kbHPzVdw4LedkHxVSNctSMds0JqIoewei2Kl35aCKvXLs6Dk6ACqX /eYL8wbKZu3AQ9W+48sEc/OZNYsIwTjlaWuqs/ULscToXIu5FnI32g/U5yDuKIYL jVUXekCMn5LHAzLf4Ub1kKxhqp0p/mvyDg9CDFv2oosaeSGBSayDCRAH0xAu8aG5 rWP6D/0d4z2e4QcsGo/kKFZeF0AHbPtlrQz9pHHe+9fk67JNNt/k+sfH70r3rFHU +NxHR8Jpj+DXWwLLTDKSMO9ngr/iov0ZIcdR6c7nr4oPkIN1v8PhbG0J/qBqX+KD c6OXJln13X/laCzDMbuuO6xtZgaH7MGh0W6Ki73P235smE7hxj1S3K8oMVX40JVz xPqFEKMKTQky5LiFGU45dRPhqA1m2aQybVRE1qQPD3O52XMrHgyVWXRarZZMVXyr fueMYB7IfAcf8Mj4FcF9ZZvhhT5o2DYfH7aH1d040LOdeuKfMpVcRNiDoYcoL991 1kvJSiHO5UHLOS9cBNvTWUToM0E4huUNa1ZOfvBAPECadBAH3HJQd03YBB8VI+CK 6PIqUTOJaCGPiwaRCi1Z0wkxY5CelHiHFPamGPf63Ey+SebV5jMGD0x8ze+HwVBA rVIuNjCwXKuWGk8fwJfoHEUBi/6EQL8xeQxmAFDalD7PScT3UycGPphzfnMDzpP7 wT6TnFQweneAhkIeR3ApHX0qhiZhavUoybzbUrbmiP2lC0MakLxtWlQiR4nn5h2S fAZ9mkvYHzjaVBbYubY2Brh0wIsI3GzOzA1jk5MTHkOjnFM3yBtaBp0uvpP53HhJ YLsys6M+KQ1YIQHmzARGZM2sJrRlKa0LC0MOSWGQpk6ac6JX5sLDhAQYAQIADwUC VW1uIgIbAgUJA8JnAAIpCRAH0xAu8aG5rcFdIAQZAQIABgUCVW1uIgAKCRCrrH/R zBAKdGcGD/4xEpS/j+7wCf8pC6addLt1Wu/eEgHwhHpsrNOvb1MbcvjswtOAVXQf F9ABqkBrfDaKCrXec6Bqxi+NPTfLEhpe1V8mprmgqEYZBOnkeGjuboGx7zPZGvWh pivLXVMKs4EYPKi4bG07zz6ZQcEhzZeunLxKodccJHWdGKmQy89SFCp+gArA4iTX 62seNOnuLeTnqAI7XTaDeE9OXKDEcU77KQUTHsBpsp+bCVBpMnVpRqoMneq5klvp MqXYFdbXM1vu4YB5B1RD81LCpLChJkZStAQA1ULYLNdgxpNX7Vk0N+VhFpVwaYMW iZtmOvn8zsw8WySAwMEmkEBZ83IRU7/qh3ZjN8yvD6JQXb1QYo7vCGms2bTqN6BJ Iva+ST+N52mxqpkBDiTMgnCoSo4+21kpO5nMfjSlk8EX9cKp79EHv8ZJhX5yu2xW 36KNf/MD/6fvW4bAFnG1htz7SMUuG4p3Ne/tPWWY+vDIjjk0byIZcXVaAPSGxUkI WK07KWRswpGuZGxz81XcOC3nZB8VUjXLUjHbNCaiKHsHotipd+Wgir1y7Og5OgAq l/3mC/MGymbtwEPVvuPLBHPzmTWLCME45WlrqrP1C7HE6FyLuRZyN9oP1Ocg7iiG C41VF3pAjJ+SxwMy3+FG9ZCsYaqdKf5r8g4PQgxb9qKLGnkhgUmsg1QEEACFd2bd 6N+5bsxudQwNA243quQeKFcxt35OILjRABDm3/08t2zktEU60NYmkFtgd0uV04+f mpX+ZfOWIYN+bvQzGa1gQ7MAP9PwCYBNfSgPywxb+5R8Jfgco1anDqL9tkbHnKZU 0Qi45PdYG1ISHsq9NYOyvz0z5EPURMbCtpzJzGv894qxK0xbay7OQGUGIF6G3+RO J/6wsb9gz4XnaCqdh6QrPMhdFyQ7hJc8EEIpTp6UEww7fOrMT9/4d2ATGbDUL+S+ Rs3LtfAES/ETYXUBlVmBXggWxOKEhbzydsQrNkY94kDsKB49SDcyuLZc5I/fn+Pf GnG2uAd4DAQZ9HrDWZI7VpRGmvGKCb0yj3tA3haLv85Lwc2JJ3dT9R8TcCIsO9Yk 16Wxwd6pSKKkAYIW/EpRK8elAvXTP+/ZVQ0lbwHxcIIdHELTFtb9YmPMNzmYjoGM gywIGLAoABRaALZQ1pTIkC2bktX+JyL8sm4QslXZ+lQ1CL+oxDGndWux4/3fRGeX COsU5z5VGIXideYqOkHscJvglDXc7xd/z0HiS5bJQuvnh3yb+rl0+w+MrpeRJXD1 PmbqDYE6eG7B8u5W7wFcwvC1rlormGQIxSGPo0opk0ATZnowvtpVYImRpEas020n uzsNpwP+XHZi6BKI1F4vQYRRTA50FUZwDZrjzs7BTQRVbWjXARAA3+Zw2lmZcdaT by0bGmK6vKZeHcxAFwQag5PEvoaY8JJI7OEckI7MiNmqW9am5MSHGdF8t2x/hI55 Yu+3BLGRLlOz5loIz9/o6HVvJrN4f0SsvMWuy9zoIY7Be3z+USeKLBWk3l/pThuH aKT3RY+lAio4jyejCHsgTXxCF9/jkCfLsXJQYdDuwhynCPJlIkpE1ZikgeRKyMG6 f7svIt7T4yP4H1ysw8/8JlP3pHBoNkDWtBd5ZL7BphBH6arBsTouVPtHgKhATU2L iHbB78ORmwqL0ZmEuCbIq71Kx4Emrn6yB2zcIHcHvGMHyMSSbSrf5QAq+Z8pCMuU BhL4dgREzODEoydBivibhZNvtqaghsdCQ4OAoC/ONeJEhlsDMr6vb1zkI38rQmx2 5KtHok8mvhM2salRY5XlahqhIut9lH/E28vdMC8uyTFk7Re6fxFhUQBUKu7X70GR h1uvhykqUYxOFyZ+LGVyp/843kyD2CGIQtOEJXaO0nCaCM+TM4RqIADFTWmYTX3N MgwEIZDWZtmCy1RtXVvi/ICOkJmpiWkWEfrdGQJobY9TGdCn2gc18R3OV5FTx/XL +HS4392JHIMkfUBmGGcjMKmZZ7W+nDdqIrq/GyOMoMpP8TtNc0PgyaakA/h5vR3M 5vETlg2jVlqkxwkvj5jsc7HB0w7YD5cAEQEAAcLBXwQYAQIACQUCVW1o1wIbDAAK CRAH0xAu8aG5rcD/D/4rUvKoOZUCfdiTued8d84ClUhcwZrEzPWdiEzVOD2mTISq 5vOUhQ9CJYKm0JDksGSpEgc/lbJz2yswwmuEQ+M0c17wzxPqMigkVIyZ9UFoSASS tL33t8OWUwWDf+8fvz7zQT/Qowg++LbxOe5pINy27jmDYPd4PyM4Q4Vz1er5Nlbj 3wS2K3trPI7iDybcwtId0C+dW3jDMxE43o0RixFbJg7lahtyTdncbpCwwtsfakkz 6089DvKOsdjMOs5hu8BPM9E6xikMLKgXaTBc3ZujQOfaLect4JSIh+ioenInRvQl P7nIrnn0ObgkXI5ow8Kb2B8cK2eseK5qpDhKmmwFVyx/obYZPLIX9WolHe43kbzW uPdP8RVPYVwloPZ5YZI6TwRWNE/o1HVXEnF+n3bAOh0p5GYUh6Qs1OLkswJX0vsh WIbG85P7Ls0iZm9AIAgp6RO6RV9uHMD3QXwynB0iw/d0zuU68s4v5ENytyRs7Lt+ jZFMf6wOG+jVL+HcoWj8D1xaEmrMj5l+tkzcLd28fLMWcwgqpehv6xJccP5aaYeL HOY2LQGsAIBaZ9Xgfzit2LFsZDuv/mOtlvGuWfhjOYsgRK4185Fv+NQw4A5eHS+m 8h4rHaEvDRLzB557zDzPmBnvrVv3MkOLbXbRkx0WW7zni4TkhO3ZC0PRWJoijg== =68BU -----END PGP PUBLIC KEY BLOCK----- �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/keys/dennis-simon-anton-private.pgp��������������������������������0000644�0000000�0000000�00000001774�10461020230�0023676�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������S[Q�,wKuhu$Zo`Gy($9`Yf1ǚG7=*m Vbģ|juشm�?]34b~DKNQds<E+M8^rE13 Z\M tjܬYs*ps;UlGl,o=֍ N?;rƵ2$ 6I"꛺~Mz+�<r[d>l[/TTEOΞpJ`VqIGH'QFsiagBMܚ1( F,9S%DNj&ģ*i~2c+A%S\{�8?|I0h4 PMn}le&CEkX(S:Άa Bz Ph#3]v ejbd j\@; e`ZZPfKT p.li*]fL8uT￸[cK5j+,W~*i7g[űLzͿ29N3ݖ5o ?o2_vՄ_@.'R/~eLMG^m7xVr v.XO[RڒJ|2nΈ ô Η Z H ٣-zĄz`0b׀A=ZrsƱ3l8T}]Ng]/?.s[ 0᫺|zgͨn��2.kնiCKflz*PDennis Simon Anton�8![*#kwG`k\[Q   � G`k\�Rmʬ8j,艼DR4ds EHP^�(ĠJì$֞kGl����sequoia-openpgp-2.0.0/tests/data/keys/dennis-simon-anton.pgp����������������������������������������0000644�0000000�0000000�00000001727�10461020230�0022224�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������.[Q�,wKuhu$Zo`Gy($9`Yf1ǚG7=*m Vbģ|juشm�?]34b~DKNQds<E+M8^rE13 Z\M tjܬYs*ps;UlGl,o=֍ N?;rƵ2$ 6I"꛺~Mz+�<r[d>l[/TTEOΞpJ`VqIGH'QFsiagBMܚ1( F,9S%DNj&ģ*i~2c+A%S\{�8?|I0h4 PMn}le&CEkX(S:Άa Bz Ph#3]v ejbd j\@; e`ZZPfKT p.li*]fL8uT￸[cK5j+,W~*i7g[űLzͿ29N3ݖ5o ?o2_vՄ_@.'R/~eLMG^m7xVr v.XO[RڒJ|2nΈ ô Η Z H ٣-zĄz`0b׀A=ZrsƱ3l8T}]Ng]/?.s[ 0᫺|zgͨnDennis Simon Anton�8![*#kwG`k\[Q   � G`k\�Rmʬ8j,艼DR4ds EHP^�(ĠJì$֞kGl�����������������������������������������sequoia-openpgp-2.0.0/tests/data/keys/different-preferences.asc�������������������������������������0000644�0000000�0000000�00000006103�10461020230�0022722�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������-----BEGIN PGP PUBLIC KEY BLOCK----- mQGNBF6Ay7EBDADcM9ImVQ1FXCQLWIiqn89g5SE6g9IXBNNqmpBQdsU6s6CQn6O3 m8v9t8N0Moo24gzkSQeuksHC5xvHua+RnbfcnI28htCgYNjjSK5DKzctcF6U/90l 8v2OTPcOBzfvNLi6a+fg6eXrLrAwo8HLAcITYjitIqrq0ZKBIfppv8scm2DC7Mbf 9fogdC79vk5PZ43ZUiIwxR9gmnD/X1WxHeRB+hYyMqsYnmIFqTcPqP+mNpoGdd+d LyS/F7dti+oyUe9g16N9y9KtMQFPf1OMwxbdYyhMQp+m7lH9Z3+4XnnBV6WX0elD KV0zq7bj25PoI1vTjTAJVdjCLfwGEY+Y1hcYJek9N26Jj0bRl0yZaMxTbdekDOTq MQtJ9IP7Eh8eYdAA53tvMALcnEKmTYmTj/CqSsTWYb+lC3jtoQP8Q4RqEqZ65uSA YAstjvGectWKCwSxf3Nu3PnQCHni93kO8ZP/FD5ak5jts1VHrOfl1x3gN9ec98O2 s3XydJDrtYKybM0AEQEAAbQjQWxpY2UgQ29uZnVzaW9uIDxhbGljZUBleGFtcGxl LmNvbT6JAdQEEwEKAD4WIQSWU2Az2V+NMCv034TlCzqWwQhcXQUCXoDL0gIbAwUJ A8JnAAULCQgHAgYVCgkICwIEFgIDAQIeAQIXgAAKCRDlCzqWwQhcXYifC/9Sja5R zmU7X9B2u8HN4R1OzesmwARTN9CenE8u3BpaHc+V3VEt1amrUTSZFYZj9KXf+Cdr 5+jCq5lCEYl6bv81Jqc8ke5wkwOACPDpXwpb9JiwZMv12gUXi8ldm7ETXIyCxJ9j peeUaVu/vuPHpswV/25Qkq0oKD249d+IOPFvMUg+FM/FrBIO4tkVoyVvutS3CX0Y vdwxVi2ywmUwgyDvc630totva/E1jH4nfbubP5peE+TrjJF20q58SlrIctHdUZbk T58QSiSoUH323Jh2349KAefbZqZfUUS6QWt3dxDbeht5C3yR453si7lD4aDk+IN3 FMj2zqAdJ9fPg0pzBsTpq3ukyPBSeGJ6veuRxCKnnWDxXrplQZ0MlbiSiiwk7yIz QU+BhCJYqQQQIFLqx50XG4Lz3vBmYK1EllqpYduOsGtWNTLqGDMTlpIwQC7o6k/e j9YTeVZjSbqKHx/7jglXTIcNMXloZUraCzQ5hXIsKx6RaCrp79ge9YSzD+m0I0Fs aWNlIENvbmZ1c2lvbiA8YWxpY2VAZXhhbXBsZS5uZXQ+iQHUBBMBCgA+AhsDBQkD wmcAAh4BAheAFiEEllNgM9lfjTAr9N+E5Qs6lsEIXF0FAl6Ay9IFCwgJBwIGFQkK CAsCBBYDAgEACgkQ5Qs6lsEIXF0/zwv/fc9hntvvlxNnQMrMHwZdXegKvTsgg/t1 BlK0y3A9kTlvLUWKGcAOHCA60Y07/BHoE369w0d3G6UdZi4xogizMtsGjhfoT+4I 2M0z8xIe5YQuQF+LRdvGmtSfEu1H1htHSkHtDun4aOygog2j2IwoKE5wLbb4BAfm 2CKwD06uxZGcaIoGEhhOGJkF5pEqpeUMeJaMaZnc55r8EWfha8WJ25PEoOlvEKwy fsC3q+/h+52+YYyOkIpXTbDGwYDKbq+NSF06xzJcpfPURu+8+sFBtBetMIUPWPqI BvoA9JyM0o7ILXN5ClOMskGrM9195u5PJ1iRpp56XXsTJSPwvHMhqoT0KdBNmtXF H/wYDy2HyKyim8LsKLsxWmGV3Z3x64kRVZK+3vhaszW1hB2qlnuHtf17fmn9KpsO QZiwhSjwiPV/OiHkIJhkWkMnvEwG+CKzYdIxEsZEM++4wDUyBZy53xU1dYfDg6ah CBXLPD7IKkDbpINXOD+aFN8+CFJ7BeP3uQGNBF6Ay7EBDADDlxT9RQW5imR3yLDP fQTPAxx0XRArhkyIeLxZ0Jb26kNMDa0Q66DKtWGW86BQFXHwwmHdiDPHB/rGj61l YGXkhnGsZgHhAe4hgRN75+jq9wVQVa5oP4xa9ntOGwtmuLZBJd/i7U0rWtiN04dl GWCDg3RH2+tar6Vr3oytEFCRzw2uLsMX5gFLu6nFJj4vsFdnP8V2XZO+Y4dqfqK9 pxcm6wZs1BI/MQTXN7inKXBQMokIcTp3KLlK96v6OKEyI+nE042ipvdHdvTMcddO uc0CwElmWN0uXGbq4XhwnYRTuDPc67kwtHEEVztcmFw74yFL9Jh5Sot8iMKRVuC+ zToctrbRmNjNGTkCL+8hst8cteYWUNEcWxFcrQla6mmi1l+jHeauX36BFFS37STN BeW4WKKW93jRxIRhG1uT77muRUaCUcJ/658CXnkfBgnrlpvtK4AYO8A4aB1mGrQK YeoKdfSttsdGcndD5LH/dh9NLNImAeaSX9UBXRro8LD0rMMAEQEAAYkBtgQYAQoA IBYhBJZTYDPZX40wK/TfhOULOpbBCFxdBQJegMuxAhsMAAoJEOULOpbBCFxd1vEM AJzVwttJWg/Uqc95s4tSYtU1tIZvGFoz+NgeAhu/v2kqWS3BcslJv4vbkkArKEX/ 2FfObgyeJE40XPxjbrjTH8EIyb2wN9RToaR6ZD/xJqkctno5nD4SysHTRirPNPDY c8q2VD8NeygIDYy43V3Bh2FOfryM4XxoNwfoAc1O1w5dnyYmonOtOQ0F8GMWrwmP 1tAfba95tViQb+ssuFWQ+h66JMdl5nnn/nqAVDxccWTp0WD2WyxbPv3dnsmBhphM DkWixBhNsqRIpbU0fhZvMu6ycYc9Z5/Sr+rqLOSe2dTtwvhw5TRO11EgV7dtNXDl GQ1XBDcnxy6i32Dfz661eV+dvipDODRQwzYJIadYfneQE8XU6rDGd6uO4qel+PZO IJ2atsZT5smwE5FxLeFLMifpaYT8zvpz8w8c45znMsgTa0XMuGdJ+RiFTRw2mmou C1s3U+0tshmh98Jl6i2pgjQPFZbGXtmQhs+PFmtD3ibG2iNp6/tHfQj7t81MrKna Tg== =URg9 -----END PGP PUBLIC KEY BLOCK----- �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/keys/dkg-sigs-out-of-order.pgp�������������������������������������0000644�0000000�0000000�00000065640�10461020230�0022540�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������ Fa:�^KGYv+"�Tef,D<2B$G`wwݏd/|[%_o!2,.` E@Ɗ@ c(7Q�K.Zm$[u?\VLG:Z9kp W.6+ʘ|TGE]֞nQ/.2_812x}r64W5S_{{s+c&J }2F="L6YH/ǔ[>uj~= XY5bV0J_Uw0ިB> ;\}`8FrsbGU7[譙]deəRM7Pc=Jh,D!:/pb @jKn:޶F!LmrJM^ɣ:Gݞ )ܖfUeť<ENT& RRؗIdra-ġhMEOkcOwL7��+Daniel Kahn Gillmor <dkg@fifthhorseman.net>'Daniel Kahn Gillmor <dkg@openflows.com>,Daniel Kahn Gillmor <dkg@astro.columbia.edu>6Daniel Kahn Gillmor <dkg-debian.org@fifthhorseman.net>$Daniel Kahn Gillmor <dkg@debian.org>"Daniel Kahn Gillmor <dkg@aclu.org> ��������������JFIF������C�   !*$( %2%(,-/0/#484.7*./.�C  ...................................................���x"�����������������4������!1AQa"2qBR$br��������������� �����������!1AQ"� ��?�QO"IR#R0J+;TPn'w#⫬u4D7 8@֩zچURr<c>{* Yo�K`1h.Í>4oԐ�Tb(i6&48!XRv0āA5a\ǽ|2(*xnOV4j,6ж%Pރ]]«oi#̎ E*ZwRh+̟�4ѐ�  A-6/-XV$f0<KKi(?iZLb#*u` ޥGQSIT|U,�w5Eae8ا?CRO6<N_ ɠλE=ޡ]I,h?žo}L O!"#$v,l1UIq vTS6֙i횢xO CybyK;º*7 LN|<+�ϯv4\M<"qP2H�>Yiw <ksr/,ONEV#j~ڀ>*giW.4/il>tL(G2J{R FaJaJH+\Ci%C.ѧl5>F?^8]; !==T1.9e>'| <sZvIR =*@h6{r#m\RRf?yjt/uJ߽;22LܐjLH %?5{-ןI̷Q/Tg?jO?�*lΥw5q(wg$ nO6vvbWʤ^sk,0&D"ʷJn�$#r9t�iE!?(PP.-4wJ!<zj5躌z\#6{0aM0JaJaJĄ1Oʒ.3}j> c}5x*u;E%܅#T8lVi֟mKzY7}mxҍ_ΊD"2pWО4A<;:\M=p{U-ڧH4u$7$ ܳgT:՜w#V\3czU)S UwRPm@GEw |&}8c xPϱ`29ŚLqfp)<ߑ*kHPA̛Fk[YLVsFp9ᅙZČ&rO8>ݣ4=*-st uH˖bqqS=:$DL��4?;Qg3HGްڧLDL �;M%*ׄ.ҭcI 8Qɭ~� & \gq֩:~!cXDUzKo~E=G&tZ9Z6Z_i&I[!2hiMUT!By1sQ'ucw5N9c}Fh lگāFrYΚ)Co_ۏޛT.Ek졾u]C(F3t"*~`YV Vo'͏C�;Rwl`)PrNNiljvl? MLd\qt, 9�gʜcMͭt44M<+SA%xA kCg4 {7=ډ:}%LD2S+ܾq. >@b|Cm 5qp>Q <dT2=Zdk8`��1z "P(UHUOMYQÖ" r:7q0 L T/ix#=jb9(!LQX/ftH|kdA3Wum" n]_[\^ߖhׇc?N{U[g|Suچ[G-G`o>AFI#Ԛc]�QB� �)5cm**W"v%Vu_f[H'2h5SH#\`j#iCke4q?RY2BżT] G8'*Nk[uI-[tm,WQ*csڲhk51e >}I六YOJ]F֗q IB j-zUbkfUX derHBKQ/@,uen[[4!.ɢQ1,Q TQ�⇿W.pD/ Z⧷1XL <sJvX@j` eA�#=-RKmQV6z,(w`�֫@mf Xo3GzLkhd<À·F  7uݏz+g 5ʝV:T^C}$,fI 0{ӱu> @Uo�nΞU%w_OEjWHKv�U4d4qWldn>G<Vgn ymbD;#@_qWQ}DTʟ1U^?52SlfoAD{lޗM 3R٩P ǵAqSmܬQݸEL AYU7SRTbsn<uh$ZɷeH۾ONZhSpR'#ό`"@d�th4ŜLN8 P?R@8ˏOCWb1U]A[ꑳA1p�_C\r[dk(rKq, rw1 =[˛mWG',$ :n>?Zu+y&cc>0zΪ}Cծm�tOa?QnRYYppOO* t}..%muKM v j'HW7FyR mq̲<2U2U?jeHlشHΙG*]D8Ҥqٹ HZuj�5>V`0]7 B>p (y ]kL.кgiww?) vqNv)+G.WX[oK;E1*>c^(Pُ{.dS|n OzOôOOkΊ1'g ч㶶0"ϝ=0S5uP Ā׷(qJTw\ͭZ|, QQ"f ykv,.KǑ}aa[M'O }p2k~Sj)%7�� Fa/�.X[G>Ucj(J'P%y̝U#[yo`L&[ߟw>P`l? *oy?Wq{9|_MſFqy2Scg}NbtZ#`%V!@]͋dqBÓމ>`,7^:OY|rSHHa! 7/Rڣc6 @y2!`7O$T8,?W§͟vˏ^?e Bm[Tl Yj i;fz=JrCO?Ç;0 a7-țR)c0qM5ʥ3TE*йT1Z~|ePfWzMɍuZq0? d,}wD@TGՠ-oSX/z7HfB 3cB<V(7m�� Q?D�E.Rcl-Ni-6'{r& {/'RKxϥ#zծ"dpz WɬQpn)fڒEOMaLM+2=m{%ekBWGf>$ntQ¿l<a?:og}X�;}ǻmǏ|ݱKU<xK)+޿Al"*?$rl~y ޱQ_mWp kSd׃.. vPozϨbS_)ܕOsR$I:p[{^ۏ~aL0ci8fnq^�"aE(x"lCEi;ˇ5 L~{H.&z![0ř8T+&WK`ۮ\Dmh߻c4!@NF'p 2 Bkcb-eW'x:{>JnƁm.( ~\TXG{��=R0n 着RduvcjuIE&H^cG:r鼞Ml?\-IUԃc䆓P ,B'Ëty3P< PH2I#_?k [ cɋ(;y1]#Cek=td:؟pԪa(ξlM[�;Sx+KgmLP$yg.J7#[Qma "vuG�/z!wwT@c>rsnCb'LPJʞ`f�� T�co1�^yDH d} _.`)8iZ{c*1T3ipN!ca/" EA͉ۙ";hW V'6vj[~|~kde"kZ}&a?g' D}}Ԧv!yOFMSt2|H+/o(JqW�oEm!drvwKl\TJt <uy`ܯg9UB7 P{PTe^xh6�/w E݈ X(WF#+>ɰ[q$@!@LX 5CA6r[@8LMjzC1wS *]e-?e']/85|{SyƘڷ~d(l t9+h{y&"ZT<bc&N& OY Z*TL9 T��3T\n +G@p"lI>urT_!dK|^8 Ve�Ʀw5r܎Q<'~5}peo {"|D'3u`9SXdG\GZ_hFºbz*}m^ty~\~$YAVQE)L?&v5(:e+5nT~8^"6_xQK6YMHe8ߦXDYjTI)_>DH&R�g==c-�ST[o!xE kn/P"(N(c5,2'#Yt_XBtSp[ '$^,mK�v/W&Sm&DW+ɖgL5䳬 JA1x[x5D|v@x)^a.]Д7/qU1O= Y $[Ec+XRz{Ư 3:yĪqEp`?ҟ0,Gɛ>rd"jŤPfOqqv)*�l~v@I\n-Ev�� Ve�QSVhQ:<UH >ҕDʁIF,E~3C9IO3vUԪܩ+6NߡҦ5Ğ>}YQ\B]1~ؓ.ZQ gKVTpm|Sk% C]V8ث͜~{a) $oKE[WANB:sbxb|-(m܊U౦G8EgTphd| Y8a@{xᵧB&Zv�#=/;'+#|=Q Rh-&EԐzbH`P̵73ܡNMM2Ҭ_RB ށhSP DHޑɳK$ioߒG]Վo _T@]%!?'+[]ՑӵՑ7mH"7x\�� XZ�` 2$u�'δւll|`>$U8,Ffkj _W7}iAR|7Gfɬu$$Ҝ9TN0ɢ[{T 'NPzKhҿ2ߥ܃qW3ZuPv}Nv51%}83WmE~rQ8kaB`%NP-ٟ6KJ8ai!)}WȤZ 8( ІL5ՠ(rͰ("]lbG/!*Pg{ޒJ_utЃn*`gAƉ ;ӵ'"|D]IR g%gY̐lD)| WԳgC\5';K*eVF(˞H05K#lk[~f!;Ii;7$Z&'>j]0X{VZ_w?NStoи7�� XZ�Ӏz) ftK=>,#&-Dp^],hFGZ~v]A!@}FOq2o` 5j?>AW<~xvab:Ee$${ %׆/=J)A1zs,P [4glX({0]9( %BTeu!L\ d.ezq7wO'S.>Ł?}`lv%l/%kseZaC@ݯ+L6g_@Vb&́'}9̺$^f~?3 @Nו n-.q)`k2u@(�ĶGdwnT/IsK0'e'*&؀T?gO'=a]#F8InC!ձ*DPG~DpvX2p\`4 I*a7w%=z^]Em^ݍ;��Z%T �Kt%0mf^&'|*䞤g%{Td^V:c/:zVMpt \{4~uv`{rE{ 3jr37l/z 4= X b$%"qL)i B@_V4KX#W}]eƢ5f@+$$L*FZXd")2ٿQdi+/ur(Ԗzqt25f ʫ( x.F,-g;vW\It+f@*`!}Ƣ**!Ps{o <@yyH`LMODUx{G>vں@2G h0h1i&8Q  gcCCUx �� Z%�Nx"sb˒p6{DMMhH8%R!J֮$ojMC�]0JTDw;xyBJRwjepyS9-݃ϒDa�ڲ)_aVfuT\ܧ~vKh؝&* eHh,1Fvx)zE )$lyy䯷o~ 9x=aG.<PlloFi9n �皈tK"Sc4cqj3Rr i-53t؀}ϔ�g(DK;5QH E#ޯޘ?\6u;ۮkYupy)ɱ0 MyAHm # L[P]/P-{JFi %ӬE;-i3JsV-Y댚oٗnx8 Ja.sLyeOx/!�� Z% �/* 2躊۾#&XƒD7fl|FzI_*F3(9'0IJ[496ƃ/3T79⭻ ՈgֳEG$^A69O;I<9;赵 t& hN@"+(VeU+8'uA=6J>!⸁cQBg lyq.`P;� I 2%LR)2vGoS,#_z?.XeѢ=lP"=3ANR.C 5m&mz4|$bzUPAUom|\+Cgy@EL3.]fZr�-oҧ|aFX}- 'Y=7FW͑OpodlX(X{ ơ5z np3T"?K6|[u:&cf5\%Q��U �?   �!得 u@9Z% Ƞ[� 9�I4#EBgQqo B5AuB*V2#KGN::!%]m3)Q0Ew_AmxU :b};>(8 -^q叞TなkIc鹿{>a;\ahNH}%l) #2X}S]h_\-:7NB.*@"KbpnAMUj͂eh=ֺ=�9JRM<L'> Q 1Nm8i$9V%<DZx4ċں*?!}uг~džM]ij^&`\U]Qd0o.Ο )?�YѻMLhU͇l2{@rGq<Ϻ a9FdZf,HpjdN^߱)J 4 0]cy3:vCAߜ �R �<   �!得 u@9Z% Ƞ[� 9_j BүKcT\Vf0kyeV]<BgnC_M[o<k4@(-6P "'?Uo;dDALzV ?3k@Tenmil}ʖ`X X2,,P3''H,n.?L[в} +56#u>Y% X3o7!٪]Haj.#M\-sbS']TX$G_ᵰPRFT;bF�+&Im#W\V+P|Qdz ]}m?њ* ' .S-]ne7\Eˮct& <'W8@;.ږ2> ks})vy:ʁ{I󁅎n߻vƉynԆrڈ(ѓ>)`�N(n!#q!i*SݳGycAݰׇnn9ҁt}Hrf0 � J%e� � 9pVŻZoD9fjT\@ݚ~tam-0ko3)pAj 88WgI)ϯGp%` )9W9>LF$�U3(TmA`UƜ k Ã0ixCrTm+sy45T V5cʋDZC[.h1�T]볖mHSaer.]n'#?V@TK+T(RRls{*2 aeMʈGP;U@293Evq4 ղ!;MpWǛm~*`퀄S%KOstna΢XwS73侜|:ݻ=mVЙ3/D%yB3`uCE6P᷋Cv%]P ݁H B&�Y67nirZjz\7l_-&$LZg0 �tJ%eQm I still receive mail directed to dkg-debian.org@fifthhorseman.net, but I no longer actively use this alias.� 9&_}2w>պ /i7a4 ;}#Q }߾xOgkLQe1G$ $]zeݚ_7͋ =;1KT4 YR}Tq@2n} "S#<Gwbv i.ǺDBHE8zdk,leu,Уw},3:Hq@<zZZc8VծASyp7<Ўz<K̇sEI+?< IFdʢhpǃvDD#Ҕ z_Y5_a\h=k2y^q4NWq)#eZkH6q�אCHb~ :DL/vUUQbL]|JYiA83\P$;. bJ/}Y 1JuS|+ ҟ0e4FjLriBe]$J5RUBt`2բR �<   �!得 u@9Z% Ƞ[� 9n2;M piօQU \e@e;J; c9fQC< [Z˝/ߎbД~ `Q&nU U*XIoG^"QAu=]BiC5<Rai3[⪃"Y?J‰ U=ZINa|=tKǻ}^@ ;Z|7/*x#wGL~CɡϢ.w&Xi=f,i)@~[nwp)fk"WatHVOO" CXB Q3~y$CAh�pxa;796 c?  V@#ihw'M^9Tf*.H#.b>XU\LhU6Ilق<ιztp֛9-XL FPT"975 SNʜWq!FrN97 >6e$T űI~reŪȔT �>   �!得 u@9Z% Ƞ[� 9.q'9Qk^Åkn6cqKJIO#M,`M1ڛTi+ə*Ź[_;]YYdJ;tjREc~WY+I^kQwS ;Fxm�J8fjNl-ɰ( .ofIAnHnxrb3C$pc"kts,88rұjƖ8/.Ǵ'0d~�[)ZoMtv^Rb i.yR]A_qUIsR䆚)1H@፤.皑D0\SINvAɓb 막s7"UCNO}iEuJQ?Jd21P&ì_u tVPM"vE,g" 9]vh%ſZI2ekQ'|G;(cD|@AbҁST �>   �!得 u@9Z% Ƞ[� 9,7GV!m"IEߏٵ ԉN^v,`+!3x\; &Ah-pe6.$ h)"<S,ڭK̼ץl-oMC{\8/dD&~QxWvGrhMǠ(!zôU28!].)FE2jz@2.1 ]u.,RG.?#C?tN,;JT J.dN3[Wzju*'x}w|i7t�_r+`g�G< шu|QFԉP,yyt}0:1^4?2L#U rD$qt2%wE\sh8Qp1%^܉h+7L 7 +Ɔ$>QҼMG>-, p.OmI qCۉ%� OKi |� 9Agh6Ģ:$W`q-VRz*k0a'Xv.r`u:e+T˂ J['-NK=0m1 2@czA;J1mU'= 4u*_9_?|7ѥ;zX.&D%*WXcekWt 3_ }碦νў{brKAR/ѽraz y_51LYI[]ߜDs' F�FPz򥬜ِ5Q3k +CaDy_ֺ("PNjЪ\`O<G!ahA,|B`Zga7g^i4N «[õ<mo7=<qf\jA'.I#{J陙.bȣqiol}?!rLw!IU)Fg0m"cĔ*' q#x`Z&hw2% � T F� 9 +Io^�߽wzDFz#ψ{ՠB$ua#+7'&�b4"eQzJ)|E_-_zv  țH hoDC ]A#_ROk иp$~�+B^]K@Gf@#BFc m>͎߯)8ZzCNZtH$ZƷy*1\oRFZT],I]&a:�hݏO2 MbS>%{q{)pl35yˤF2aslA:))8,wbv yQv 1);%qp @G (Nj]ΕWIdIlb2ֳqz0uXܯ_D6j' ̉\*kN2 $JF "Y)u:pQ!zZo#_/QCߪMȞ(I:o)?#LS!H>!:C%x_iXr$9F̉% � Ve #� 9,�ǩ1*=tڲJ3\)I_9iіY:P=~) `..?kJ?:>]щc8$+]b;()c[):d_O,39DbH廯J }i+Zl)=Br'-& |H;A} 6MhϹݛ,=)oP J xGjn|%IB?t:} kXv~7 `Ϩį_C<Ngr"Eъ)[:Ml05`NyY` RLN6f,vG p6:f޷&AQ1SeOC͛cxqŋжc2ϑzngS:xwvQJY&;Z]?8_RB;/cRa $uRKaN F[J| b>!GNM6e64ku< �& !得 u@9XZ }� 9@{ <$݈M13]%c8ҫlL`1Ds&잉KH Ge-+*: 9Mhcv{*Ɵ}T6~9ע%G6[yShLڱĄ =.+ʛX pL'k_\$u>NVLZoP ؙr\l 1֙h2aݙ;+#Xa"]4ifgx RWtn[ /曀ʉxChyZewc㒣~HFioFy'Ә[sq*y LGCn^cds8.8u~AhBY N*{ JTcb%A+*6ՔFZ3"hef& 9ER8a;sOsLR.X?_1GMOV_aыect: $#X&w(Ӽr%R;V bs~FX�2&gJ%� OK_ � 9Yk} =W3a[2_AL?=SU }s2WZfD'jwD}ج^ke5;^WX)qGGz3w^Q4zFlo-c-޹.IbO%k*^YDa !82r�bd)$mL`}~M8osG"U p.;¬DS3D6FE*\̩޽5,S<? ?)u_W N]'^JcY�񒶋�;;h\ +~l6Z HK%'{L&R 2-oS]/ I%>"/)ZJI0;uS؛pȐ&a8$hLipb wb3l )Xh"44D5\$TC7888iHi28X /?MAAR �Q?D g� 9  �fQ?D_����.�(issuer-fpr@notations.openpgp.fifthhorseman.netEB9691287A7ADDE3757D911EA52401B11BFDFA5C� $\wAi`"ILr{IaL[h&lTak#17E2z6|>7 w'O~3F:KNC2 kH5DtU0z#OXa+ CsF/00DH6F0C.S̳YHs.Ñ}t ?'z[71ǟshy - rC/He;gpw%1gI%6s}H:iEO>2d39l}�=42FP=7k7t n z|zr>pxexYRkҁ@qnVKf(r1OjLKm �m_E6}:)EZE;$463a\MQ\/E ?L©)X%eLvxX  EamXf&P~Ut ? ~rhWu QحL(qY|#e|E/ )xy``$?ň Ix`>Sڤa"h23Wv dfFFsȰ-t('u0i=IU �kicW\ņV$ z*VA 9OPrtM qnwx"$+ M[z'vq_٧|f8t1OZh>;G{XS:+㨝І ,5ڗ1T [y|,[钑 1#>>=QV_3c6j B^[u$e 'U20S^z%#gL'.rO`û#m8imZ%0Xq,h ld`x|&.0c.�p8inl>BR;H v&?=@[zEx&Ppj3IOtԀRu 's9o<2n8ΌQkؑnlm� �T a  �fQ?D_����.�(issuer-fpr@notations.openpgp.fifthhorseman.netEB9691287A7ADDE3757D911EA52401B11BFDFA5C� $\wAi`"ILr{IaL[h&lTak#17E2z6|>7 w'O~3F:KNC2 kH5DtU0z#OXa+ CsF/00DH6F0C.S̳YHs.Ñ}t ?'z[71ǟshy - rC/He;gpw%1gI%6s}H:iEO>2d39l}�=42FP=7k7t n z|zr>pxexYRkҁ@qnVKf(r1OjLKm �m_E6}:)EZE;$463a\MQ\/E ?L©)X%eLvxX  EamXf&P~Ut ? ~rhWu QحL(qY|#e| 9g_DeSގ&Q #Mw*iS[D *>A,+YR3m:ՊzA0cM#q1 G[5겠0t 냥aw14|ӳN/ίx ڽI)uT[ #حj/w3G|$[_c;a|`0>LBQ+]jE_0�(mG T"j0y|eAy5׈zR$&V͡~v5vox�I7p+7�bf&Yim-(9�AVK)Sp.tP[WOmd$}:$>b}Y0Śm kn#m"Xt >2:tAx>yRpFf=i]0@z*4\]U vaJ J@74,'-`7zf5Pros2*='2+gtdx/ġ7 V 0^ �HR0n8����!�context@openpgp.monkeysphere.infoopenvpn-client� 3� 9f �Tgw35?H EwW?fA^Y�37waEDdyX^74oeWF;hXYsT]ʆ 5k Dj8qvfQ/h0Re\*QxVQOu!ģԝ1/dU ATuI"/'�F@\ ؍m`w2z?! {J&YXV~χe AŪztDGEMb6aY*Jz'WL ZCV!T Iz>sfߝZm M閔Iu,oݵ^#Izף;m|r,#G"G%~l286 @)VrB g%{;Vx\mJ@iۍN(G;ny!e?yGF, .lFB9isai�9bMs5+=0j>ʷjFNeSM^ �H8����!�context@openpgp.monkeysphere.infoopenvpn-client�U:K  � 9];57OӔ$V<98Ӓ߲-w&{q!�"ZUʩ�Oq۝1r Ա%N t>>~;ro0h$pFYᴒCLD;KLb2N:9}GimRH3,GpWw1[w9|G H|=̺{!\DW/qxIE\CuMMGV5;{9akLAiw碌4>ef_k;0ő9k1XԻ f NS.Pɚ.|x+ ;!z(([?.ƒ3 ⦊a gFʅ}ㅧbqԼw;YӀGOi1,OUō%vc-E@-Kr#:[UQZvfU/�ֳ(Bf^W5,׀xY7lXL݉z.D ppo @Ң,]v Ѡ-|}?iS~cȉ% �T  3� 9鲵fM �Uf|T`]T  VuK9uNkmZqj8ہ=|Y;zdjӳ#Vy,tuņl].%R@%1y)]m2c\vXleK0܆ Rk՞lo= '�V`_" z.2;W>KΪ\٬hEA꒤7Ӿ@3eߍuGNgX h$z% ˀ<<\!2KЬz[X$M<G&/AV �}o_LCu%Ѽ*fV,;3U)cIiO(?TLkڽ $>:fbMl ZnG2 %Y0<D z]c{  au}mRS .Dzp -i]v/{-d>#A ؏;Ou ǬqpO'̇>zW衊#g z% �T\n  �N�� 9ƐcP`Q+>ktyP ,_J][K <`�@Dק:-mjcZ1TFi' `W-֋" Ґ(H"twX0$v(419)3dٻ~n\*<E YG4|UqL\U%ep/ xv^׵ tY52Ut76A,-Qܶ;hB!#aH-氥k4<I,896 .lK6FFbл gY| j)?hD7b FeD' P0'd;C9VOj[4mS>$˥!=̺{=3ElfNWjnN.mD.lQodkI4B¹FUlzwdq27e9:u n%JvMѰU=7'p%F �Ve 3 9  �fVe_����.�(issuer-fpr@notations.openpgp.fifthhorseman.netEDB2E74F56FCF2B67297B73524ECFF5AFF68370A� $Zh7 q']sF7ryc=KXn(q&=1tM>S/sn>MH،i`9}vb nӭ�ZBSуYtQ@D `be ݮs< td&~-NB Wְ:0}¢bߋj�}!ͅ<]|%Cws ~sCէkt7|e~t g9_.i+uS d:m~C ER]oNDC[d|wGJ]w}W؛ie0ݝyHh)% %S@?,kґ؎k]W8sh&f[^H&ԝ%Gb-R6 >eaTlluy{@5*'h{'+Xf<3 Yu�I:,:D8ZVdP߿ "`1öԯJ( 8`$ %8H�yZ:j@=ud޹N !C^FwˆM=w`vfo9CK|KMτ)k3Cjz ,G9|m`S@C2:(|tI"͎Tim<C%ơX l<^4"3ż tzʞF ~y,P,8wq3 ‚T챞c,*&N/L[TQmn7ބ"+6!.5\75[y6_3Eː  -$w hxƨc}]WN/IDonm~!YZ\|sQpC2v<Ӿ^?NJa5 _:*ZU+y.L*xr3Ňƅ\8,bCq,ls:UgQmvTq= ]g[QK'IM225 GBmBon]},5-lA!i[% �Ve  3� 9}+�9EDWjq{Օb2$hs[ fwSֿW(k+# .5NfV mD`Ʌ#nbp!I dZBWFh* r|. -E{2~ۅhѝ~r 9 qMzjC/y_Y+(Q2+͝Y Q$I9AC}O5Ta#C~=s/L/eVݖ!XH)�;o-,Y)ڌhq򓼀nE wmyIu~Υ e62}m 4 [`.zX>H@ oK5,q^}2Й.iި@(G,3A< /{ȐSav-|G[B+љD: ‰ךJ .ڒCӉo�o<hjAFAHǨ'M C.L.%fV< �&!得 u@9XZ  3� 9;I 3C=yh=-qz˼'tNt3s% w1k5Wk@M L x!780�Ipps4r< WcD+of#(i4ͦ3;$Z)hHVRåAWtYY {…LllM1p„_ L3⣦VXt;Jg*> q϶X h lBcU2)݆^APo''u_hf8//'j@nsiN�h 'wU8jv9J)(.%BnĢC#'?b pջ/sHvͮ*KS{?$OL"z:9z6D35W\pm8"T 6 YcN)Ϝ CPok]&u&,_'uSi>\'WYr �&!得 u@9XZ 3@ 9t  �!8'`QG9227XZ� 27D1dl=$e ȡ<dOĿ+cem�MW~RXȦ �<V)mМ[3^׃43:F7@T#M{#1E"ݜܶR q℘ * q̲Ĵ@9{;0oV F_Ȝ}͈@t,}rrp*i޻&4e@8:jK$9~0%}Ʃ-4~h:�N$`ov"Zr I]Mynv˰**kpO59yf~X@I1m`F.w�j09D<@PS 5Q%VZ ip@wtB|.?\RPԞlQ1]ZIVB+'D3Αuţ fNfa6$5K#ٳ.hw ͱ@%HX#*@ZjgP^�jPz`),\}GRq Hxx?HIoFD__ QUN�JX#CGC#W~U9,7.w'.-j@: ⧹¤ϱhBE}rW5tLN&V"ѹ 0seD4`zZG<H8@O&ϑ d<_`6VĔ \ZT|="bLh!g>NX']/K5Qٟ1<!%'jGڡ){W q[ޯouOb`"ǭ61Պd̕μUz{s:Q'N0PR<p+ nZmKQ8?#$Y\_h"ɲ$L-җ=̇P!x3tD`|"[b!J)Q`^;k!y8kKzPt zc^q 1< �&!得 u@9Z%T  �� 9鰆K> \l.H0ʈ o s|KAטQ`mô6 sciIT!rKK_)\2fmҵIl(ɗ,Չț�8H\J{EX t[veY_#u/c M8Vj?{qjI͈|$a_BΜ"0T;F> Έ| L(4 9B/{61V#d fnlW'vy /%_K2uWʻ]Ȇ*5>D?ぜVE5 e,GDHx-@~F 3]l6\/qI>4h]J뀶ϥwR4v`LAAl=d_ MvGFހ�ܠKw020>ޘLUm5 Hp9$ZӳYP3ؠ)Zw|ރ' L:/@7-(owiiI{3/}46< �&!得 u@9Z%  �� 9tyeYv#C6.@b#xN]AnPՐ_HX${;?RZаWRDй.ׇPuؽ`ejM %<lzFcw%Gǝ8ZI~'Zf 9b+iU _R;vA�.�+vٌ4?gz7zկFw8_!v(j7`a/ow }̼Nk\F|VH=Fgt)D} (ki]s[i/YƯ[* N6P(8 0=S:s@j@tLF(@җch�gvTӛ0[s 1LjC?uʴdfa0AE)~ۧ!%X HNwn\/ms<̮t0y@z) _>8Y$QR<d\6g-r �&!得 u@9Z%  �@ 9t  �!'/2糖!gS3Z% � 糖!gS3g!g蓅.7TLUԅŐhwto=qŒLG>qX1.BKG?Hr!4 't+%2Ŕ4.Ύ%8 5ٛ6$G|5B>cH W%<xƄO # 9dAMhbYHhu; Wagg  eW�f݋=n4/C=]] ^10=.m{_kS*tg (Q b[F'r՗qg ck5G8Bx7X<AbACk $X1f~zt1{YOb}0J �^EVG;_k#v(9)-pnb"*#htq&B=N'eIRZaƆ- ]�P Di,RI6+) ?ܩmPs tkC\KZv&]]IKn;sIn70`hSk[ KCA=P_(,l7*YP(^FxU1-8O Kj[}UkbvՕ*-8r7)Du]((p<9m3J˘Fv&w-- %Т>kdg_' Y) gT1GeNMan}SȔTqn K9ּY V-푊9ږ9p Xӵ-n:[g>opXAzχ|L,K * yLrxխAq Xk�N'?Ti'܀ H4L&O:n<w]4֔`ptR4#3ߐ!Z<.Žn&&/xIZXڒFYVgG;E$Dֺzی`F0Ol������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/keys/dkg.gpg�������������������������������������������������������0000644�0000000�0000000�00002437333�10461020230�0017250�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������ Fa:�^KGYv+"�Tef,D<2B$G`wwݏd/|[%_o!2,.` E@Ɗ@ c(7Q�K.Zm$[u?\VLG:Z9kp W.6+ʘ|TGE]֞nQ/.2_812x}r64W5S_{{s+c&J }2F="L6YH/ǔ[>uj~= XY5bV0J_Uw0ިB> ;\}`8FrsbGU7[譙]deəRM7Pc=Jh,D!:/pb @jKn:޶F!LmrJM^ɣ:Gݞ )ܖfUeť<ENT& RRؗIdra-ġhMEOkcOwL7��+Daniel Kahn Gillmor <dkg@fifthhorseman.net><�&Fa: f � 9=.]ϣ mwONrOɿ^x9H!\҃:u "j?U"!z]N x.`h�aߨ,J.Z :0@aPPa;J uݼ!2!;Pq֙5+yD5ƻdgE%T!~]HOF@i"1!zܢTCl,:B>pðw `W.tX}mTG#[F8A A=IЫx L0 f"t*t&d 3cKuvORkx*+tN{YX`?g Vuk7GIO=M`{$5&j_n)?^D_ fE3$z ]m2|H1q +GXCcĜe -( xa8Н|Z*2<4|=_!UCLMj5fLIqdgoF�FaƬ� tKce�!h!]n\UPn�C@3E7:�Fc{� #~p2>[�<riuqї)-f~p(@tEd> ͳۨs\ּ DNh)\0Sq)cDp8.$A.$7w>W=+服 Y�ėS%fTdyzoB3) ԷL;}ib {3wZhkJ Ѿ{r@WHUy0@VCJh,s<A7xI+}/]SJjKO؋1yGo5ְ6UAf{kk>}pM6�3j @nO POꕜX';Sy8T6!#;ũD L{}�V4�GT-= ޿DG#ӑ*.oܫ2P¦{Hl웴[X~Gl)27`\Ѩi-=UBHO5�18*P;\4AYO�r(ɯ1!Pm~X(Y �Faԯ� 4Ϋ{`vR d3W0}o@T84,qBpoi@ h1\~ mfFn -Y7մquZK" Fb'0wOgC,6ݯUWb~e%pU&6ARmcn[LO'�SZuAOulO*š % ڔ[NmV/';A* ^d)MTXҊ#`=mDTFo@-@@r* kݸY[3/::zT9]({@olC|j)ҲcxOQ };#/5TKUم`k73CI$zbZDg= D[PG5HX48�c4ZsŒ̚6#,n0 y;/�؅B*Yq+>x"k{ߊOC^ϨNc@b.)p<g7Zz`JWV5?ꈈJ� Fx� 7bv}�{nt·+.4@�Kd1Wqz1ikfYF�Fc � ~@z�>oOh4�5\uP@pJZ-CVXF�FY� ӱga9H�)S"eύ/�!3_V*jm F�G3(�  sU_9�LfPEloʄ;b4�nF4BG-Ck WڈF�G3� Qٚb +�16{u?Wk{�0S,QU6F�GCpc� ~*]*�{ /ݏm� `$HA`è@k CЈF�GQ� &Ჾ>,�a5$ґ�Łn,rdNqy EF�GQs� _q%}8�?iY�BG!؊Ӂ;p�`ܕ$!dF�GQB� hyd!�x?*mr6Xm窹{F >�OJoMe]D/ocgF�G͑� CB;u�ݮ8K5r �q er q~W>ڈF�G� dc ;A�lӉWZqrsXM *`{�E˜$H˝d˥9�HCC�  k>�&.*>�a*QB3'EsB/߯ᣲyj|bF'EHwt904@Vj ^4qLNi3)˼߉x9U T^hÿ,}0$ł'>W /,`pE/L 䰮ߢl&Y҃(!Z6Jd9ロ!LJqGfd#N;m=oG]uIObK^c:̐Tk]d�\,ݾNwt2+Ѧ<:;%TGn{ $w=-UdNa+]v1X*W-)F`,ox.?uQ&T6dv}ҷBE{9]_?#AqR/ΕIdٍ&q!lޗbxڸK#Gt7][sSЖ '0Xx|^bpSUW&o`~jF�HD� P,O)*RS�m!:*L�aRo7 09F�Ha]� %[J}:A�٘<vslxoH�|L ]twF�H3� Pp,c�3K?F,z�z0{=/xވF�I� apD�VjQ A]8_P �>KQl~nkZ5F�I+*� cUV �?!x瞚-//y9� nX0mzF�Ia7� ާ=>|�aӋ%c22f�DS Xa#%7�H]7� }XhPz*`Vf,ʢ:9*9x#8xud}JMLjt?n=n@VC};N ,$q OjN!UtDl9ս.y^^̞px8A ʗ5`h0$r=`Ɲn>o}>ܽx|,ǃ`gY"5lVaS( N@p-3X#Uz {n}i% $>�( fI>   �� 9}Ge"f5OɍwSBuB lZ@Al3P |23(~fD}Ftv|Jk0-lhJ})cVcRPjip{F|!o@6Fc\Dܵ#7�2+Rx~,ߒw~�rRsƋoQ]AU,s�dӫR'Z)/<X"3P8Zǫt:_4ExY bTU gU:#1>x *h*w*xBZ[~r]ݼ?BS쬳2VI mFOT~zWοJZ0䰓ۆ>aKB3 bz }ݷwe'~'n%gGڽ=VT@/4T&RMu8Z&ACuu kQ׸w)D/,UBΕ:7ʞTvf쒩X^*`%ʋo!RB.> �(   �OK= y� 9ϳE+ai-BPfر@Au,ɼAx^®eٖ I~4C]5Ac5_:+Cy{ F7r`*lg}wj~ӃUrGՈޖU.e7sꞯ6j.cW<)ƌhxӾ6G$m px&ƕZK/ƴW,AncJЧ3[+�Gq$N*rPQw 8Z{¦4bP#F%4!3 Li ި*i؆m==f^iZ�E5(PI| ,2ߟiw/ED'sh)~ߔvi0 ڄk R25Y3CAJށ4“ׁVDoh�OVX:}rDi*W7&~?`ȝ\Nul¹aOSOrko󒎿#<-yD#?" �fpY(PM "㨊>�( fIؓ   �� 9@@0s1\=A"+ \)2xTrCrR^s=ahG”348/߬%ĥ?ܯy (R�@-P1UT]?ǿHrsϩjwA9FyGLϳkX M%#Õ^kȇIkJ{)b[0?}쏓, !Lpl]&d f曗6.)5dlҥ~XK_פ:pXpt;+(۰9!:6F?K^ƂU\<̳`J=Y"v ݿ gh';<2qM8$#rBsDk%XTwH'. cqwZD+X¿:9R4&cqtk;C]I}:I^TI4(9Vv́70 wKV|+,g~zPն];Rܕ-?�) f Fa� 9MnzG&+y gM3 [cKPhU yd@}cO ƴv'M%TvE4<񰖽a C B\~)o1FFK/{6Vtc*SܯqWj-j뾲"?gį WѪŁle'VX`o6'kU\b;1Bt&rˋ%?PB\ [Ϯ&h z DY#kFUi]gmIw٠o{". "-`撁2W%.}OܳUJ%V.PEo"""mrN3AU =N.ŎRXk }G#$4ƺh!B]|tR!X Vȯu@Uos| }Ǿź.z!9@uE �J� I[?o`ı;CfRhd<]q&7JNӟi:P!:$~83$]w51緆;Ⱥ;/{.I$*#$ pF$󕸨uO 0B`(@exK8BEXKvc?f!r5IEޓ@^\qus7&[CR! r=`h}93a 9K=&t�SAJ_!RRET,kFtV,&e@vy :yA3R~6cpö$B3.6t}]` hyvփŻ?St�֍)3;"R+siuw0thخo'(/yH5 xFiHLO/tf"?D/a $bs7�{|xYC&|PəѼ/ս2[m �J� 2(adyGMQYgY"ZM4wUzD'd8ڊ5d�K=o_�b A:v+JKݺZpBO-Kc!'<}}>"Am^\֕@pDJvՍ6TN3~vԞ<8+]LBG/}9>xۼELBlkVҥL8oRj}biLA5>dq{rqW0~QwWQGnzF7i`SL"ٔPoT,)lj- &׌#vj0@fhZ,UɅ+ʸ$ځm8z0e[@̱Y Anc0X=~8{ 8ٱe� }[7@ftBQNW% :C$Jq;Ndmu0:n89<G^m$&|OSBҮM kfEf3j�J� f5g@)�RxgNLf,Ůp+d̵զ_鏂@Sl2cy_ 'JMƟE݁SJWSR6+L^D(9K2K@l=s;VK y9:M#w_oےřtڭpi? $$ʮV p9?hoBF L!3t>oٙl64=h֣BSl( 3:̠f0;b."NՃw"e�YW4JGA "лE +ŧuZ)y8Lp`Ww'o  f@hǘ隳{BM}4HPEẃC{+06Ӊx3T aXK-CGY2e$?5Z*If%ߨ�'6<,׽(&L5TG[DznS=\LZaiIXҠCVaӥӴUmEQ F�J^� FGmj9�0e `la`ka�)AԏNnp/6#lj�Jk� 8yM&*!^HbzلzZ|释^g1fZsPe(KU ;?DDQ\*&䯭*TX0Q1}5U*=(Ӛ&C1vsL9^+z[z[5O ZjbCa'b^Pg'gaFǞ`�k8wRhw*%ߎ>cLQ`u9͌GtYX d MدR8_ O($8Һ}m܈ �Jj�  JEoR} h!L$#T?Qaʃ{E2/WƺFNn'] 1!s$!%Q,е%�/wec):EGȞ xI'8>}nlڟ&EKp cl}MFn %,d%@ܧ%|%w?5\2Hm<.oWE@<dT,s}]WNpn}I~uDmV;¢i%|E":ҒGf?LdW~::NEHkͬB,77Dk [޼<rk]r,G_;N0.O;D7 ds{M;R ӸyE 4ҿ>":qj ԢͰE/^1"g6ٱgK.�̑\<LQ qpk{=c2Er$F(lp@(�|[8/4b=W�J� wC${vk۞A*!#,K?{; \xfڱpKO-/d楍̡ aTa{JD{]qtn Mo| M#AHP1ޭ:t#Qؕ3<\0XVƷW=<.+SG\FZ8j5gtTd׮H6P\m!RpK>#8 ~Qj~8E8G&"~bPepCƅ)KoN]ZSLeoW7=8jh*)R7^:;-E;_CڜhWNfCEWp۪{CY0_5SFt7V?wv?x%nhlh@ھ$Tf\''VC7A@]DNuDSӱyGVw5-MOvr,:)ЈF �J~� 5J4� EeI ^}*�F6q3ؗb �J� uݧy7۲lt~^=ɱ<v¯Dz.*A$єWE=LK\ۻp[rw:`(g_h Jj,5` cDgmRU+ *r`R2;]rlQqd3u^QIP*wt*??}^g*ohkGS@evѸmS]xj'/pمK.[ =D02$h¢@_=6 U/Ol[r9C.cZ9Tv~pА9Jhesf8?t{𖑈2Y$C$o@n̗zb͐u DUߝ&'=,fQO>88Qb|o Iķ%U<=%$TE jv$}8pҘ0�E8ՠUМ.NW"jiuDS~8.AzCEpZ;p/!(U{[m-{rbhp8}m  �J^�  ~_.I5v�R^~˟txz ~v*}¹ū^S Sc(<N8p8[?&˯}1'gԾOT: 5{j}%2
2 r E_5MY5T}iȑK0|tw˩Nm gGUEPvP/HUntq7|wĶϥń j \/ӫ\ {T\}}>kӅf[B̚߅XOzٌ: Tb0ֿ0H@rhn<*TwDm G]qTPTkBRIѩi;_qS~{<$v[fyD92ab #FA 9vΖȼ]7~-M[ZFʖE'1/={ & [#HfX(wB ;9/߲cu~A(Oan<<;"wͮh6r덣#?V(+�J<Z� L=X&Dp1["J}^PfTqi<Hm,w߰3rަ-Ւ=| 5<)ZfvP`- J+PϚMIayhYokVZ*{)SGUtdH+C&躹9('52M{ 4|BI`hPrrdAޤ:B5D-'K1KbM!}Kc -D$Q[xUWm:C(|$'\9<JHgu�jd �T[,߆m6-~= Ә02~"5*xl}ވa͓lOhG%GwzJòe<굼)G$vzC 8*B0>5ubԂH\u6XF;իS0ilio68Vj:u?,P J&|!j9vF�J<mw� Ik ZH�z6TN#%֝LU�c_/"sZ‰�J<}B� 7K�6ϮnñV߽厱GCߑ "|!cMsCTC^6eR5+,%:<79-802c|]vL]w򲱄P<PMvk,נG`\i@7P�2Gnz&�ͮI9G- ZBJi _KL3' .@SXN*ks^Y;[>&6_ҲKo#7|}v"9PGq ߣ ר{͒R=呋Gc;}ů豐F,O H8�eZƕF g:rmH]R50I@"6A :<ʃy@$<=Ơg{ҡRT,m -~;Hg촧8wG  ]+F{T9+Ojf!>+sh'A%Ƽ?n\F]`2-;&*-}t놑3掬F�J<� Ik ZNE�j-ybԠl=+bʞ�ihܙY~׉�JAZ� zI4�sw=(F?εGrC>IxeܮUs A0C T,gd5Xq_vo}@#秽1Š"fJ_ҿ˘6 _dX/S}@6ئ0huWƵS0^箵 *B9xjˌKGR�p}~Nq(q#}zcR0h/Oԛ[ `1sSeY&/M==,UL0.::ʹ6KVaP=.ү A)K&%n0fCf=K:mѮ`a ÜkIQ-%18p0–LH� XC<L�K*30ۏܟH,B4gd3L"GyTҲrOk"w|Y<*eu:�S p1|v|q|OX] ”(j/ʋG9wKmCOl[v}�Jl� ;��q.ZO"GϨ Jrg=쑴k2Njd8ϊ, Ϙ`A~#vtZ)]syʏ(;W-^8pUA?B` w:'%,fB lϱ_wr16m'WX Z!Rrb/<#zQpכr}Mn zB8SMj9 v_弛`bh^9s~ ߷ =,b 1rW]FlMo#5Tx180M_]I}Wld鿍ԥWaIT7b8%dhgC(Saڧs!Þf-N0wˑ l`iq"kֳQBYfw<32XcT֌7f_ݰ`RŲ!t莎 )ObrSw�JL� MM&�6cܘO^3<D^Ҿxw鬦"V8Gzu^Al'(UPri5H!J;~ٖVt,#Wgj"J'*yL¦\Q6Έ.t%擦ZM [|YbN~6:ݢSw(!,ד"U�cw+2|'ʾ}Qs*'MK<9HaƟ<'%ZzI"YOHw]0JnoWSEcB _9]mV_d(U6x_G$Ke C}K,rGdu?2J~􊜵:q<^0l+J_ftjᛄ(^*|E]:6vJ]3;<b1<26w,֙Cn7GDKG1`wbI=[d}Ff뮌ʐ^b>ǑЖV& ?#;ln+͙|+J �Ke9� Uܣ(�2 C yJK2Nd#\3-#Rq^د((cn}ajLe- m2/?N#qZCC'xUܱ{OY.;<o8h˔^:ч ?ot!/ay=3dH.xV5Oҽekm<rNHQ5U B~U'C*<yxz{nIy}<N힦${ Zi7͕B ɣ?_^V~ ?@Rя@g!`1ӧ]oL_J-FGDĆjE ʔSa^S0_bhv0B^.!8qCZbtݧdhtgʫWV\'|tS6503 󿜚N4*+wGK7[7" Y^XTAzs2Ӊ�Ke� iĭg p�i넵E!5r(l"ElS2ljͳI4 (U%d& X/c9v#WėãW2LOS+P?<:ȴ6&1yDkjfDPqcUq1r;Zxӭ}_4}s\q$@ӽi+g.缾'}]=rJT*rڎ*2Ŀ@h-w> Y`u{k§x:| #!@xq'E|Y@ߞgʃOZAj J>5k#rߛA/ S u&͹^I$\4g +U_~ȥ8XL5WX_}NSbp$,d%!qW<FH"Hry<6<ퟀ/ԳW0بb;JIЂ 6}/ uRml<?a}$�m-Z 8crF7�Kdˢ� K7e/pd?-ʶ޲|&y0/+f[SX'jnCS/.;R wrKwA"NR4fu=Xy<Hr+[m٦;ؕreJr@a[nB{q,.? ӄ~=lܮ9tkN5م%ÉE"̸-'Vs7X EM$?הqW(TU܌V֯<SI03U eOYW2#2UJU@:G=*o�Wa1I![t |$2о悄.㹒a3Q??L<j#gy�c~TuU#f>Ȫ L-F2`ez&NFp<^xǎ^jD(DŮ[4wo-Le"#B+/~|'7n)V`i+/$6s[l#T)$ϯbԗdOO|@q߈F�Kd� /We{4�Z:q1Fx^ON�~7j.Ĭde<C�Kf$� b]q.'f Ǥq!iT%G(e“,A>=*q<XE.ve*WiNmrԸÈX5c?i UW$fԂFN7@-UtaˏͲ-9bK̩K5ވkZ9k׾d|�2wCfhN9qϓ9IɾG28OR4c``_L%(A}vqݙ/ԪH MVL0.&Y!p87[HЩ�KhC� � GVGeKAme`P^p^HkY 8Ez4IjGvAl_O*Hl>3 spHâJ4Exp^3S4iBOEڲ#+ڈn7q'}+ F.bdv6QY6᫅ht5an[ZdYno'gVPY֖^&8W=k#JN}/FyYpJ,:P!ԯ-�$H!NŵZh3mT. _5S" F4LWH.]wËnݰV(!NW9^x(9Q-&EH/h}lB0.y<S -_p`,*C~&hiFtջF#c<XSpUK@\駦Dt9r5Zv278h·$犞x4XY:E^9U ]m�Kr� 730~AfQYB҂f?`;C<0}J)z"4+i>k˅l@#EgYcSP&`OX[pDh\蘰oYB%yOz}R冀'?RW1}g 1F' bZ8/~# ˆsz/Q}fmIF^<1ý 4G>UmkvFcFw�Ky� 7/�AG9X`2'+Qg'Ox>nj9nr PZ"hjò4#&�*|KYCDxKgn>[,~Ln~Ż"n͙l~VBAC-ڤdNXi`>�|#.ͩ |ϫ9,LG2 njD834;T'o͉[sw*sHVi!L.vDpC9Xߋ�K}� @ִp߉ +>-kV? %f $@n;ܫ`HQ),F#VAIe(kyrP^g`xQVp0MHP6ՍnYWA`/E(Ȣ=yHMeI匐Kٞ("L޷A6{GDسYb#P6> ^`l:"Q٧`2=􍳡f7;rK ᆽ7 ͻvo| U{]"*]AIuA"O6lLۣ4Yt1,ԝn;F4yvU<bhhѴD[2@ ռasFe0sULQ<R/>{[k oMfs8_�c0ڔrD %?Yoyla?('SI˓(kÌ)[kMQ 98j EVc!�K;� P*IF'C)9SzW,YUٗ9l M:jZH,[}zQȜCmZzd R/_:0^6!CH7-k =93oBM-w3BGujX{sˁ"/JnyMvE5HVkG6/;vC тS#̌bC0%֭r7f${vucME+΄:>Z!x5Rdr\$)-6(ŏrIGG<p< ;< H#G7 Q#[5bt:8ʛOJ |z٧` 簿L?$AdOt07 fC&ģBRɊ3cfֿf8Li܉-+OꏀuZ7ps\^x4)W`M {AݠBLԤk&q30%ПW&TyF2=h#�K� 6ܜ}P\*=U= X<4lGϿh"<D \M^^̄Zw,`!9"$eW OFJjtI>l',0L-.as :`.$@V@9eo N<D;^욋sR]l6ZɽWyYXA9ӝ^$QFYu�MVF+%CQ?w]&ZpzR$I_ JuG*!i>7gS]$,.tfu|n:nsgې2MfZKEuVFwF{IZڽz9I>piy<p P=v8ǟ"z!JxAI3b-6*a^x�ͫGo"p:2# Dlg35Ċg^QzDF<Z#hZ@_⠗L3s *!TS]P!1PeQ\@#ֈ�K� @^Zg%�oMR~ݵ%N !5TB7Nl P0yHEfzw}M_@:F!$^~K D'GKE *:dH{#^S_[1UϮKZWC#8F�KH� ; ?1KR�*i=f);d�ʯi�9n@ҡ)tG"�K� #AWI kyD3ԜqKdr2�tҽZN"0|Q rQc"#'&u L@htEf;Մ^G;@lBP'=/)!rrRjPw~Ӓe ,N/qYE[sPØzJf('x@:äFNfPWv"Cd)IcPsadQyRЗH_1tUc],E#;XcT�K�� _y/8Uk8̓G%j^P2Ne ~$L9v L`,7\A= EF TO1ۅ@S .0ۚJ hG?xҁc/t̙2<~e4vkAiwm>Fk|hм ȣӼ2< Vzrď %W"qfUNL‰ r_0ϐꊐ>%fx`..x5eANrP0W 26Z>ϙpj%;~ m]/4^ٲv]lSM1%$JMTϞ*l#DUɩ L6b|zG]\r60vmeYyP\ z,ߴiGq5|$_v,}ϡVhH;"N>uY},Գ w m;&Y�2rV.v!Qt>AV40Kވ!i< x_F�L*|y� ;.;]�D{5p SƍFJ�0 ȟBa{!L*|:Ci, N yO.Wyqp^T� NxX5X6A3z"o>:_!`ET-*#s s>nb'$D P2+oCUuv3-P+Z<;Niw2*j n@!t >.Dg~t6if {a~FD~*Jl< f=Y3{[g;I5R]} f):فd5P^h8'nB/i$T`*M ӗZ:i%8ۘwe`l)PޘjDwaB`0 N&^\VZf1WI`5lVS:`!~qp)֘S&,(`׹{zU4Ĭ ;t%9m|PWx > ?;ޚvס-.&. 4}x[[ 5Vvjqt nj8,Oelط݉�LPՌ� AocY+d_f?G* ]m[\F76ڵWNeLf̩KG{r~vl*#qNb@płRwK"\+cEp]&Hjv蹡Zot:=b:oH Sęݖt>6ssWgLe/ **~J*߾>�whמ]쩅rl+ǸyDjTKR<I<h\m(l[fύfQ HK@MZ-9�LP:� 뎿9nf\ ORƽqt= ׭]H5kdžIjiafSrtMC,[9G* a ks`*{Rt4bstqи<jdOSB�CnTU*?/o)2~ZGjQQT<䝁<4g|Q@ aʄokm3Q~,6R(D @*?EMCmk/luy/^E9?oK$>]b}RX< O,p+"6'΅/«`F(,TfQÀUބkr0F𜈙1jX+_Y#|Ӭ1j+`VQX}=4 b+@h!Z \]< 9g~1%;;~?OÔA:Z3[{ߜW~b+d88g 1J%_Om7w]|oPbi! j \|D{z- ^TH,D oe �L]Z� ]2*@�nge6&<L-@gr{?_,񁔭'A|!b�,ttS⿔HۖRJ{eA;xߚ2DKo8iOtɏ~L\}fM}G۫uV5{v\LkU7;IIR7* ]a 05Co~ђ^!+F罂s0"Y:6&[AS;V1Lw*E]&]aQz_KnSa�sWAN+- 6J.Ab/1 kkW&q8Z10(ȕU;{=^9d&5ࠀKY=94J'$.ox{sEB.hn#ɑk{'&۟Όijj,OLF6 Th;}gƩp"\/. O𘲯�L\� )Qg`bϛNC!0%^/ ;h LݱةJVDRل9ђ; %crU?#Å!A9O8ND2jootF݌xq(kB�|i1N׉�j@ qf`Rz:AJ5gIWQhQgOK|ц15ӳ=">XLOJT]IQ_>]u| QC@V)K C}a0G[LUz mtevb˓-_b}yb_k\4 ħh=w>>jlOqsZS--R"QyJXO`Qz (j#ݬ ;oubKP>%KÍJ^V£f1}3DT `^ 9INt@Rfk7]BFg^<vt]uQ_o9H\YkTA ?#*n$;]{AaF�L]Z� {TUm6�@_pe:?- GLQ�?J~J}F �L\� 3% L�Hl@=¡^ i�{!`v㋉*|*#�L\]� ,|1F!�z8dΥnf x=yLL3/7a7Ze:Qcք rq>q˭_6AP֓2HdCS $w:̣r:.cu͠g0dmx.4{Dbx} usoAņirYnL.싄n7�uXJ"oCUHq=K婦%vxρn'^%|ޖjeڪsdqLk%UkO7`VZ;BXP.X edBVJ<è]aEJr۹4fH+Țǵ^ar}{ZgM&E `{ N)0C-j4j>z 7]dh&J"uP$ZT9߷7ⰔFG!i^cWq* $.ˉ𛏁N,a z5y'kj4'$WqI;bvhɞ 1FD"VF�L_)� @ tv�k͡,:Jʦ%/�Xǥ`!mjUmf4v6‰�L_)� ]18�As)�ѰT8= cD$T'\Qp93]s& +K& ']cҟBSuѨ);G0'zB{€\"h*GoFA1`VP}6BHXkq[2V'K, Cquf{ү]Rblk(_D^7_7+SBy%D'H<2\�3 ?f xGDׯ4 _ wظ4|&RaF;&C|ۯ -0 ٖ >'ꕷpY?44 \cZ8WIIp3b@)eSMG) ^9Iثcm=j8o7U'|?"ҠNmcM"PJ췥VB9V8lxv3Gh޿v臬<˫B r^DEX\;dKkE5h�y-=x5f/\;@>3 ۼ:^R~A]-.mֶ1|ĸ@l_ΈF�L]=� =-�s�t1SkV �C|a#g1ї<Pى�L]>� /p,Mn%�wyu<(# YCRfj e< ݔDE$(:X\cRV+5dIK&l}Ә_藦MAaN'Vq=-o(H3b ЏX钕3G5I-�F. !Ѻ! 4W氎'jZ)(VTrNͩ�iţY\[@w, uwE)B[i8g ҍ8a!:Ļ� 15 ]fGtQ/Q.n-3}uvmjʱiQ)E&_! ^otmq}JP"xNLӀKWHW䎢5:H; �<I_YzU[&S!02!0 x&%[Ft 6M)S#|i u)b}D&خWOytQI{Ɯ' Dz�L]C� SGRLQY�T:GRs$|,0ػӵ벷~e?X[N]y4bhj>+Ux*�szҒ> '/:aǭe1UOKzT`+/˘� `3Ἵ:oZ&5n a3yąo'~m֤ݒ&*^ TR->\DppMu` $>:WLrEZv(<4y-K5qSl]�jU<e!gta%޿'1:}UA e8'3Or-Yri푢/ **^rq9~W.K 7EC͝Jw M櫏KZՁ61AhtQ? fֹ崀c.fM̫MsZ.(@`ewF3 =NMZXLQ.嚉7 lb6;rݑE俬MhL͛xCX5Oku'faA=Nk[T.R|1^�L]� y!RR{uf9C  ìPN$HI:?ax0Y>c<u.y" �7ڪC�ᡫx|8QAS.NdV�R 1*w'}ex-daols9 KgQD\JS(M\<V%}V䒈0co;xl_缷h2쒠 /TS<fH<YN!\YلٴuuM9FY^8^M72MldP9tNU}! 4SiɅ-S(gfVE_yKBx>* \Ub<t$1%[!Z6B!Xc%o~>!gY{DKq<빗 }sF߶};<1ɠB ]M\WF luT&MJ_Q]^UOO� k1~Bj*A? YЌԎ.ԂGWW }C�L^� ":UTό j~بf"s 1ˇlXZ-M5݄z^�5EoRVNbBfV|{Ptژ0"%=�OpaOyv 14Abh}q'lM�b›46~[pԻ"[EAeW35IjMPE;jC1#?9йAM&;+7�k踊Ux̻\U2(j.44bB)E_渪 JE }t嗅h.?oEgcog?y5o�-ε٘X$XEp>hˁV1}qG/F�L\_� )]"hT�FU s z%bB[�-Bϙԭ�v}C�L_� urjtFROS1ZHl{@,xv%6 tL 魈 rdQ(b+S 2'zkYOOʉChlL %* )K?JҎc"!$̴ i8!՝'WAi%@XӶ˳>5ooR2F&, [ͪQUCi9aB( |i,uMn৔GE$]P4uCL8%i <zbhG/4s-g@w ˥.wQȑ�>QQv%awb 3p/p;nt!6 Gфcm9T~QGJp U^gb# z_3uV|1Wjx?o/Z .~T2 �Y&9d*sr3n.6mfyW1[v]|[0Dq]F�L`� Nz) �XE`jȨ2/~$}�TP_"6$2o�L\b� =la]J2Q~pr>we 4\{Lx&=`K[7[{?x4JO+ N} 'DG][9|ns,}#'lhW]Hz(95/ݢ*1o2Bf|Zт4 i ;>D̂ f<lԁQ#| + fu3͜ ͼ[Q?q|z ΙD[ e% T׼a-/傃z"6D'qZu\_*"bn['jUF;! ԢO`_.yߨ̑Mr[g0>5~S92!hQ)QtG7I[ $cICؙ$)TWڮ&-+L0N]_#T6;[3Pg4a7ׅC#>uxQqTsݟ3αD.YvN9C GIGTi�L`� QH c'/?XоWz76zS?{EF ѧ: #7&iWx/8l/Ts/C(AxꁞEѳ|p8Ur 1#R&y\cd橤kpzlfЌ&x [,7HDSb"g>86s,GۑH2@(t! f86X,ȭ"l#e /[EfffȣVXĿ ~'5�{[ߤ$E.9ݗ -Dys>@D=˭=3痎^J'x.SfMu#Q][%y+lRvhB+Xi)NA%&aUh{'f d7(hj[>ڧ;K.ӄƏ 2x9;#y2@c<i^xZ><UD٢LUV:I An3iwl :Y3~5;JPe4ҩ𹆉�Lo�  ^[r6Z: EΙ8t_#H6'!]ʫc+ɸȰd筐|f `-%]_MRl:c΍Kà:S:/ALĝ11QuA_Rt)m7f$ $ y' eiwiZ)#.mql72ٳ27~`$p.~ :Kzy� çn~?~R;?I:x?oLi^2]/ VA{%rA_-(, A]R9*xNE<G*mR.ZS*M$侢o|*0^hc(M}]`ˆo$1+᰻_AL~oݦS_V˦IEjh i~̦!~t 8 n؏}+;iU"1 R3z p e 2.6RO@fwH)}6&|W.;xgcZP<�Lb� g:ے T] \kst>N'vܵa3iwnIvVi\.%r,6 |B 2HR~nX\b1q!O3T[gbKA_Oq;I~VH \⪲ɿ{i l^z �#"rWj(Y_3K *0KYfc(;~(P"#=0kRd;&#d-&$X3:S敍[QɌt+D%4myv&QF zv_bK (ynx`n.)̄qDl@Xhc'h|er7n)G#twH rS'۸['FY_/7]8q4 ;PAEл p3=(nDkK?֫awYcb# 6/3t1NaW@Os%Ž_fG�La� w1cwmK%€|$pN1 ,|Q%$vn>d'nMt]D s:&6d2` ?$:s(5xV4sej"}[_hI �'ww*T'^Ӝ:@UC{'0T>m;Ή`\m$wi/-vw]-3e\P;2=|A0}pQ_nZYV$UR:Xt3`WV_4?堔&/w6 % xsǡqѣ["Wxօᆯ@Ckh%P4fm26YW p65׎'ԀМ%[Wda6&ʬ:2r(d�cEbb6B>-�Y>˻Y$S¦Fe!Y*#k4u}%I FKsOV dXl$͈xBؕG pƬD^& {,�La� p7<) 6V[ysڃP1!dmUKy~F͸$̻k֚f)1* ղ'$D䨬%^mFn>Ԓ8g|KFwr27߯8޵:? FFrRNaqx,Fi(]!j)PsD ֢8KVVbO#́Zh$F0 xr$^1DCd"o-&pkG伽:aqL^ g:nQ*(˚::L\0 ×0a`y*t * }d!wYt:\L_C</aC.S:1h9[<ŽgZjgt ARo M_]~Q)8H,Zxmigkh AcAܑQNQ&.sCu7�UӹoNr yÌ,0Չ�Ld� "%Dž�9N[푱{ƚD"@Ե4+4甪AF :*,&Yo"3W9, S=ԷO][,PwnJlaGԢD[O,h޴RؐfL<@ɶr ZZ@m?B_w~ݻ^:b`k<0: K}Aurp8)SRՉu\($`MsHuNj<3Z&4Mt۝2@2TI Lc#Tڦݙpǘ;kd5Kț Aȸ]KMJn lۑ"l/eڅ `  d@8uuer"d"#ݾL3M!iȊ?a#߄c;yF \;BPaΣ]Lf=U琲Ű[ z-c"e- ?=<-փJ1MTjDP<aܵe;gf_b|#eF�Ld� yx?L`;�?l{5JEۿ'�AvuJ{^@�Ld_� K%5̖ZO~T`htdrer_Wyb@lHwYSV_rWJ/S4߬fwAQ;ʨ[m]!+V굌w'?� ~w|)S=ϝbc "GF^pEqqRr&!{Fr> @Zz%N'~gLn+�g=Rc8PSPE:d�l8e]I\$ -}շ&�OM'gL82!i Q(3RX*?*Hu&$N0C2ҫV+b/]%$\d„ޘVs#JbSz{ɉ2=.Vod/0\}BWf tqꂊ{is򥚷c1p>֒nOEa%$kx#ht 0,_+g NC224g �Lc� 2B/RY�!ߡMО˲ЃLS\ 5f8˜s򂪎'[)A9^ߥ0?Vg"J =M6Td?2 ں׿ÿZWA_ݱn;aV jcĈ3*(0�Y1qRH\Y?OGZFqϾ-ʼnf?ۡV/1x,Wդn9|5Z<hU*ϭ .9/.7W'9g\2]S= uRn!XkҐö262EY-+~3h9- % I@j">60Dd[�RkFkq3;4/[tz@ݻfh\nL|,w,?ekUok-D)"JW:LWi ?CX)F5yNhD5˽WW=[ }<,W�Lcr"� -�rMH$M)yFSo0E}5Ke ro^z5սi$ܷ$ȷ4zy3_6^D5=V2#{EY&CB+tuɍY( ]]%i\űƸQ:XR\jz JI0kfyX(y2:ף7RS'saRTzOQ$pr)֮\&}"j-#9u4 £džd)()i3,_&UH1MW?wm$v:fCO;ĕ#׊O%,y8ȻwQQ9u=?QD# r^;+"A-9TA _5( �z#mAf SĒ1UsDbqrQ +ԣtRnqUltb'?gNϊ3@s l�ESP\fr[Elq  \qQU*T��LeR� v#*(0-$/ zax!WzW_%6n!DL`xŤTt >tQ[Ζ'8Xh\ZWQ!WI["w/ニI ;$ڲ#d$)AAͺ >Qwb7hg26f#eZ ~UƠmĮ#;kveƨ aǰI#+9Ӧ=a=owS{ ѪU0A^B6c+hJsX/OͶ=jMK'. $J 2aGfMU- S6_ƞ1wuEcCrJ9]Ia 'BבEQEWV-$ʫiT/ժk\IPY|Ӹ{ Sq,3@hSV!Ʒ�0Fs+S8 S`Ϩߺ$9JZ]U̘%GP~:O P%4pnKd% 5KWWӉ�Le� Ф/Pʆ�b}o 3iu+5P+FJ> zP:EGlQT\J-ij*7TN&; _Ijz8%ÆWW,@q H\2.Ե=أysށzQ[ \s/꟒yaLښT{=& BmpV%XܘS+aO>8tUv ؏t^*[zBB1NFȂ9d"hDě5bg&Ha+D$}us\R[~\C t< +C�哳7*yr>/>aQN"�ZL 9,w~\ }NRpv/R$;P,Hq =mh<^1ȧL(, )澝R)v<KmW5c[  >Ar;XC skSuއX!� ɷ22QCVVlq(x�Lf� C<Z{�:E% qqF׆>FdtK=v $.^m| E80}Dqx)PlŠ6člw4zxWmxR]sAK/~V{K=B7'c $'pv oE@["P+\}}K<ꕋ#b#9Z*&hZ�D]#*x:l"ռ}dঢ!|%`F�cW[=|k15�iRY &.X2$9FL{0 mcJ6__8k"0E _x!SJqʠY^`9*߫OioNÇQɮ7sB)R\Z%N Y2JCH?buLꝞj~ϾOOh!30r@*`xIܓ~}~JXx?r ?S]U+ˏ`tmѤR�"k4g`ږ�L\� 4Tӧ]4T�oZV D_'.2~*a'(e'9 IHːN|d%[ؽEzR8B+ƒv뎳JB"H't95C^~c-O9`'@];nwlOŰ[65#gǠk_F栃QFs` ͜JWSdvG ~3ܢQ;O]DֽHQY87O%Α8dH^Lʙ6 ֲ(1rhAiHq ۇyp[JeT}EJndlY+4с:sƹI#5鴄bWo_Aj|֨dyJN (58L~ZYw{ 8sBmSK�6ZCe@Sp̐qVsR XTcT�Ή) QUMz�mmCpBY6wʈ`Ġ%_88'3/<�LgNJ� FOjYHyH΋J{w mN_&ͮ2yU5Dڪ;f篴qS=\}L9XBs ҿtU5Έ䡃?cxT#4[?1\>XkSF96DRg:"Jd;zVCdYwWC(~Ѯݰ //|)kn|3' ɭ#iϰվ/etl}~JJgnvI VcU E޽t_yު" R0,۔[ئX 9Ǽme KYq7VaWVnB=f9Dj:&MQYNך2μU\8ΡHj"e]1]rNgX]BdӳK X̊op.gH1K۳OdϩHyXW}G0~딇](5qRibte3rSXI:t7>M="v<~§e,F�LgL� x<4ac&�#|:.Zw~=�H9gg WUwh߈F �LhP� ;A07, A�O /mC �u;bj#:. �LhP� taCф�is XW׌ƝYLuT㍣|]7uS,),V+K?W` Os!9\Hj8hZ9^ F8S1tL!p@+B3 PK|[ 0q- Cތw Œe?7p5ՠu8v3;:p 8 8ASC/e?ų:ۚ|{�NDT3iAC 3;}kkj+'[CIj(zN3J4*͖ijThxc$L^+1bZAANGrA< ]YR{T7̍ãxm@n`lN+.v(K'eG6k?m|$99"֝HC z Eug W ܊#Cby^wFi#=` sXZ~x�Lj�  Eq>>pDp8 gJ@5>E[X�@6d*͐ƪC_@OCRDETͤԋJdߗfdl+N t<ңvnfP*&6oÎ]k{} &{-l(LvjǧsN1j=p[P  QI0N*di:S-P8m c5Nt01͹[k2#$mx{[{Gw%P8L+a5O^F:D)joʯt <!%M\D i$)D>~QW Y[q~(}�Ef/3B@aM(TgJiWA;5Ow =8~H;t!Bpg iyZv^󳓂QO^x]ֈ'yz�LlK`� ⿨B BKAyG[@\ V󐑏ZCbsOI)lXk55̑7{@ۥ U֡:el?p.iJrǼ(zFEob!'mx֯SY  ssMIo˄i'PkZ֪ݯ} B<b`y\i/\cA87DN5l{vwHYEC`*6 /mݶB'Ut"hVfG&W  d$ҌX22e)};qR ` 4$ZJ(.XPs{YnKb@܋6 (U桷*h&x\( t)S$$ 3<hWQly׆ K+}k$p%"3Bz ]G#3ꦱé5LK0@*F߮8aݜp%yO<z1Q .R2�Ll� Ocbq�.wnm_"m:�F-Jͱԇ56#edYR�SfL>űRgދ2:qvj0f[anIDvN0!A݌Qs5UaRN [dCx5'?dsǥrRIYMχcL0r,Ř*vŰac"8.`ֹ,z�w3J_0_mƑ9? Ċ#m+T~zYz԰x) RK86c3k;_hXֵ{{<B#QC,QlؒNk L|V{نRKIMjH@0jãr?iz@7-?J9gp8ЀP3,?s~۪T�Đ/]kjZTj .5gQC tGG`B詍H2>>/eC 2M WlюbB�Ll� -hU.odlpz+=Ta68^¦NxxE XWB&vu=[cGw3ɮ<6eXrHGLFCUUcuc5V?rH+drakaq wzJQ;7F|0]kU%˄.X"]4Z~Y�^3D՟zOX=E (Pv+*o/d<j9ˬKJx]ig LA%Sk 2H x08 &?bk~Q~#` fNJOҭdHPuWa/'7H-ʞSUۥ5OӅb^.3sEu] m a-�fu% ^% z(G UmM0MW̗\B+s[4ѳ`SuZ++�Wte#^ܝ;To2]!GtTqxψp ȺÃϭii�LlV� |VxدxfWZ'qG� u7�ԫ@"&}quRn1lЩgG<W#kȽ@GDNi*LݕM H*@yyCXܿZe_z\dQIy Un4yEj߅᜛u{B FS?7MJgy(tŇ퇮Vk@xkA(Ӄ8&4kJ;uj .Nl;Ku3�9ʸwE 5�~ҙ 8X:x"iiX!)G8绞( "r'i{[*Pj"A,1W#UDF>yk Le֕qibbKQtry=dz�}54 D)4t>̀Y.Mq+hJ:(Z:?m"dn%;:ʠf̸ + 6jzv;oTRI �Ln� DRxa+]=OPިӀ\ֺ͙O7</Ȭ w i)ʁPߘQ 7R0م02P۰{pNT1nd2S߂b[M{]_zb佭gV=6:'eI> A.�aQ\ⶃtcXQ-2V70,}B sK�7Eѭȸ%mICė#5*DRpD+%S2݄cDeQ)kv=JMP˞t\2ݺ#yyݙڝJ$a)Kc8e ii4`ƞpzq_,2 sO+J4!Sل�[u"ŕO SԲIz#kecPiNW=j=:pU%- %̴DdTG]9`SHıF S nXޜﱓiڝ8 u_fwV! T�Lp� Wufl̓0 0#+~H/3# f=`:aпtbҘ[oU@s}8fݐ=cΠ$zi{:FTӈTc@Aذϰk7 ұVX,4@οV'%@'=-j&oa@ _�?_1BbeRwCq.$t'o:g[fglwc)#T-%T@9N]dO&hSv:߉lxDiFG,ߗbs8UyW^,kK{jg3msAc˯z˔[ aLq#d؉Cr z162.L\zGcfXyՍ92oJ/2V�Lq!-plH׋WJ ,)kkÒ! 4zW:t ;^NG5B�L}�� 6t~�17z?[f5ڻ6H7X\ JViyt(iq.E6=O BkAj|<9(>Ϙj*#z'fhXQmbz:0rd3buG5&-?O] u^sI#-DY<F?޼ P`[#Ցp=/[q{@G*�V~.үn|-\[Ow†2-{s`3i[G8%AR%-]\#0Eji;.Ϟa{O0ļ;:ONOӰ6lB=(w$<locqZM\3}ZKb>Ħ;1[_>[ix֤b}5 Hߓe[Vv@S&OC%U2-E_yR v^tb˅wwƶ2mkɇ?zUi?ZHQF̆#F۽ww#;DNkpS' 44M(ľ u�F �Lt+� Z(w@3�^.QBoE0�:1%ArYQyvi �Lt+� zwJ;R_5)\m# *wӈto&xE+ Gv43Lmc^BgY :*vqiyfg-1]2 z{[6jlϙ /?A&75LƧA#-Aw)fܟ9.%S5+ <;5r13]fp 5c~Lc:1_\"AUL٘$,(E/&MאKs*f#IRm�!:2x&.H~?C/T-t꒒M¼ WF*'&$Ϻf*/$|L%1I_#pYuvuz7[P4FQLxė.z~!IohV4G,Pq S ҋc4lZ]/sc툅8!/=Pl��]KG1ި5FH�ߴS ~HQgz06M`+s o[�Lu� $SVy�Nȱ[ iQ9^{7" dy·p7?{4b3 Dqm[~0P#aoCNqU A>7 fq`<#7J#UDHP1�2 H}6}#O"?/ &/8s/^]9I|C?7PNv4L%RLhYDYyw/Bgژ! {waԓɉ9o'\3ؖ=UCs/&6z'-$;p >'dzgr,hYF/^-j E㨏 4!R*};fWu~~u6m> pjODBze ?c7~AK6hx-4eء;M–IB@x deE37F7N`o^+L<Ǫbkw1ߚ+/,[T�LvU� "v{ �r oej1=>YROOX3DT1~M¿gC?xsLa jb 6}0 Ko{f?p{yu6s;/<uZbH̓%hET=i.CK[/sCkҐ/53ʈ}.~TCŊd> qIwzLŖ몎.5#6Jpj.+n`71ZZ$>-!;cKgKaX]L&Q2 +O(:vQ(m* To%1eV}KbtoBTpƯI(TGUfr␜LV vk;AMi|�imM3Fb^;Dd7BMM3k9-c+XPe-�,IVek0/˃L&](ް4,,Sz�Fi`PR+BϥР!HVf �Lz}� 'F'pTPwBEg9򒯠f:Hx0鍷3аnN]iQ{0*;nMM٥7Iے:DB+)N٩}ǵo%@s6NôVZ+/NU&->*֘H# S!\qDrB@aV t$.FuبZG)wsnJ*FSfQ #<G p� MsA z.=A(=ΰz~$t'\e쾇o@H^1e QP/we}oGԘ2 jkp jRjFn< +y!NE']y*c )Z[ 23S'd]Rl2EMb]~Qq4IRk{\td4k-R�r^ma?pm;f6ǁ P)Jzӝ2Г[^vKS2KO�Lxy� 'F'pYHeaW΀ V|e0ɣ)yh?S[Ap5U`%M�0yUY?NXi+ xT0i&wqAH�j IלW~rH_ ֧``k4ӾXغ$n".׌\WԥYɎ^#η)x /~?Lꚗ蚐;JZt2V<V>Xj@aBf_}nh뵆68ڍ0s ;G&e!EϔA_¾]y/z⇿ʍmC<dy p<6yVLd"P-i"Rz_gwH0Ӵ5R> W,#VBTi["RsnD3vzhI"&#gf(nB1h KTa =Ӆ CA6t"7q9}N #'kn1� ex_3@r։ �L� @]mx}Gi$BJʹמH/n:Jf]�mT!>NZZ,~ImB|KjԓMcFDRXGyoY> ɰXu$3[kg=NY&5c(>;"M09`F/_i?gffTan@q+j?VHѶ0UL' >{I"p4_[KorU|iZޮZ)#n,a'"%Q[Ii5 s%C S<ޮVo=\|M)"x_dU8d2D;T7R8ek^$4E&hQafs?6,K,72 �@Xdq`= L!6eГ~:ՂHtZע؇ԅuY$i, �L� a{Xm'6ƌbu$ބk�G#x]գUY1n[jH)ʄ,g+f%SLXp~Ocm^կ {B(IU1|qhs Ph=L4=Yh`C؛Ԑ:uCNT qg 2Lo3*CJichefhJ k眙@qB󎗾 h)WIrH$�@�b+esWTNjƏ-sӖg_5+!m0 )љqª5W<x[Rpbr5b'Cz_u 01JTRM'xed^ [wF4p|l+HzmdgBSxs~sv2 Iz/=_jD=F0cI$!jmۓ=Q)w\`R72 4B`%&_D6^r9K(�g�L�  2q<ǀi�TԖdEEfCV3hN~m<o(Cj/]uWDf}ԓ,Z-}ݯ @f4nrl'NyL,GF�@Qirg-#-3(E#<5O(LʸCS5a&,`U-o#ȯ={\& g<eC6.uxH9x.Ap@Шyy4~%�L � `7.|J�b]|~7.ٴQ�#ĦG{bY+i0oWCgAYٵ3CBl^1 p5xS|! Q) A',�.4=JDuP@,@(KI)}|A)SHP1-Glzxɷ9䪣{Yw9@cl8ah٩7"{$+yj{*D)' vHk}ɹ$C^LHVi^9ZQ80qޢ1m9<oj_w9۴]oi8U@EKW!?v"Ү(ﶔ' 5ѥ$j%ubh:|B K01];/<J=!cU�̵Y DMO:>z8;%ذeQnUZ+= NJ, Z|?yRHk竳Pމ�LѸ� OmU*d�)M Yg5t5Aa U1e?!sYiffrFEz G0 tu>؍WtNFZA2hOsswt9z/p[M 6D%iG]c?J~p~G{eadBd7P BfjB^FF1i9l֚'+LM_y)Ǡ1H$N 㕋LHࢳs"tuP! =JڣeϣQ #᫑)}Ți4bb lc7{u5Ɲp ,^k�{w5Mc1@scx}jM ;gRn:1X`.uqɪن^4Y:0=EznvH6O7;FkXL](q, ̀p \�t5^a [WH 6zrruy̶F�L\E� Pr6X5 L2�i'q˨#r�Ib! L̝̖�L\f�  V@�.Nm\CspFL#s˞ Fhp[XA{29_!&&a^d;A,:z;E\S 4yI Ml7sʶr$@_xv" ~x Z G#OqX-8}Sɣ91eTۥw'-t"‡JT|?IjxAset4e 1u_mBP/rn/N ڝMy3> &qT1)kzs{ޒHOWlZOBj`椔o^;ӯ{a/؝8,"bΕdBל27: R'@쩃hQ6֯h?@My"3Gܙ1jT컎fLr1ac)#ߍڭݹF[ sj[5RY5hĉ�Mk6� tJ'j_�ؚL C*,*<`fB@?PDs+1.Q?%#*3r-./ h"go=;o0 Ld-̌'%[8W GE_0\%*Ŵvۅ͡sz f688.N8h9Zɡ]$n<=j=ZHFgYFaJ jigܑ%o;ji8ڍ`�#&diT|0 KုZyN5Óm4$;"AtP1Z$QG qmLR|yǃ"wPV.c&OH־GSFy2L.n ʮl6z1eaydK�xkq,Xg|`I7|FlœXqo*t Ĩ u{Er; fOΤˎ nc8Qb)Jw1D$ +EL[P͹vOc6`F3S&H#Ή�L� ^�D!�7i$Geg% TVJռ~B~P< j~P9vvq*KTcὥō9pt`h r`szBL=Qez]֟N[5AZ+ϴWoje'H'Di(sWї0u9ȇhkt>s'PcIm̚$vpHV^: `]#68Ij185-. *l[lF�M1F� Bkm �Acˤ#Q*pBgi-c*d.)v8ɶ}@t2^PΒwp!CFvaĚymx'+Y)4+EvOiOxX̤409 qmkMhzG<e-V3-YxI?le$5"yDq*yK|gO1l1Y3)[#}7YHHamk{;�N<� HF5{[L[9e^;y2]NW+0L3EKlz}06IW fEm~ Ǝ&ok) kD?xV{/_CEBٌVnOo. }Z㔧</G yϴBק:e Η*y7"ˬc<Kd?e '{枠TPQYͷ@$\zkXj;/E2HAR| �N/>�  ־u2�ߦ/8 p9J+DgQtv,O?Jn9N’t9- 2M@eE>=Iiqo 3Bﶲ-{; af+6Duwjvxhq,YO^݀g%2[nt%`tPtR>;d.{G3~<n�7E]7aK06zHHnz5B'81G)xy| .0'%ouc{1jrLvJ{FXlFL8 m XiL[HĤ7CjGOI]sZY~tqF�wTuG.ؖPN\e[62<>:5QbĸʒJʙZDBeb$JoܖVf8wH ^٣FW xqbg (fbmpjZA5dvjmq݅Յ܂kBMQ=k�N/;� xocjx�4d�+<5 *hXD;A* gx2nμpƢlCsqEE ; u*ȱ+43 Em{燽ae5ŐC%�#LH9U)8z<G7  ͂qK.sgABJ,:trc\0bʈ.-y@Y\DYAVMNwW5@ j Q W.pp+zPaKn4}IlS6!`i%ZmعlW:O']2Js3 V DntO:K UNFM4@!fRǃ<KqV7.?_2hq#LAf0`zX+ n&D0Ten/J2[EZq;bCrKTRVsǬF,; u#Hi dg5^l}詤 >�+3e\vS̉�N/,2�  bh(}%1�/ŀMׂڿiEY"Jd*X�:WCŚ]z8@p/ͥj g*~ w ^OK#,\%I ~&:Sz4F16M1DUQrOK~C#xn%ע܄Ԫog]@1I›&8!A9$37T˶:6UU^g r.->q* 2jڛ{Foor(13ktp>xn0uSjZ rPgrtI3u"9]kW%d`U Y& vGQcc2t)<y tdV6iߥ+Ύ cDĮE%Meo `}+2=|$9Qݡ4pOU EISXOe1m6EwJ *#v$L$2ĹŹ +ԜC1 �N0H �  bh(}%1}j0WՅbg@;-[Ww3f9pB5)<>Ze_!*=$O-ߑ#>/Q.�[%tYMa}XĂpdAH 4 {ł?"VN.F%*M$q1^ [X4_ڍ߫ygKO&ď7xi$ooԢIyl�;TFoXVbSlfL$\?N?$fzn^a(UԊH �lmަ]0[3LBS<6K[`@NYMsXؙqw_MXݥNbO3mPw�HBUj9ҩxؖǏa>Z 1ȥ†e/sBhfhxiF3 w$ѯ7YBKԬ8@sxUy= �N/� n`c}gMC?%dž HuCh5|ifƒf@ek$",5#H84Z\Y-8:&(А(TF1lwO;J֋s{ͤ5#RߨM(I_i�Z1(Zps_fTxvIG~WIR@%-Zʻ(ۀ~3\%k[iӥ2QH`Di(p"̔Q-d #jX[Z FӰ1^/YR�鰟M^jmj (o/\84:_j\Ňߥ,X>t4` ☂[WdY[LHP Ŝϼi^{@DVJ#{"]uYU`pvq-IGv!h lݗ* #zCWG^ #a+-]3`,d/ԋ_RcG)Ա(qi[T#Gr@cPz^ڜ �N/� =%;j6SAb<'Ȏ7JCؗK+*=ff4?%ɬEJNGVUtBZr` xP<4 NۛW9YQgkIW5wM\5V3 f43ujDurرgė!LV|KfqÆQiDtTP6iT',6OI_p|y1^RuǨ׻d!+R3VNS)fM*)65A=/,=8;ާo>` Cz) yqL&"V%D|38@,1^}C)d ݴi~޴Keu6"O.(A\b3p敔#H 5ֳ@qz6:S&xg H/^?|t4I4HzX�o#F�N/�� 0}V1(�m]SDBm 1� t<`ޛt.>6怉�N/F�  �lhzR4.ELA宝_ ^>`$ʍ>s�㊉_:.1jLNhi<a i6Mߦa1P4OaZ(hL6$xpZ|N6p C"A"_naU11~xO(N- R*7N3:{i({;G wrA݌Q R m\m CQT%%}/ p.?]@'T-H~݊x4 +i�Aǯ{`4j;]Y<Z??˅aw%p2_�"I_s&\X�9^UxRLN=\'{ã[Z$()"ODz[N1g:B .H n &zA<퓈A38wWг25^F!5fpAD=5jg �N/http://martin-krafft.net/gpg/cert-policy/55c9882d999bbcc4/200907121833?sha512sum=f33b17c9af515bd98b2927cb453a992d3d7500e9f671966616e90510b9940895108d241648d1a0eb46b32bcbf3251a136a6ee1e2275745e11bb328c14e7e7263� UɈ-ĈX|bU'ڌ1 q8oep3}Pi-Ն̇%. F8@*'^(zŷڐBQ={zD'CyQ8fafs,1I3f&fl *eRTKKO>sYv' '_% -6B;4uvgAЀf{-?W<7i2iF}& ҙ'"Ǎ KgXW!k]_?*>e2>'/Y`,gчjm kY{7+::$#θ݀xˍÆKQ?'$=f4%E5CUKm#lo@ 3< ƺ~yDΫIEZqn_obd;]bh''iFhE,#@jGOcGCK3]ijі)-PjӉ%#uN=Yo„꣧^O2ۉ�N1 � hx 'ӝpc/e@z\Qd洨P#{WN{0ˑŒuұ RLuc_t,UT>@uH&(תVDz_4@H' 9;FRpv�Y vVX>gݴ)=Wqj0V:Z`qcQJr%c~w@ezapcyu(.GeҳxMa=YǼ;ARrbv=87 ѐKXwI0},3Sd)b?݌Č�:/ztʉs5UχU[LV2x?x7Կʈ>bERQ1sg Ior.sχGQb]hʁ. j~CyMX̬ĥŞQgʅpuK,CٕȢ|J3JY+==g=ؓMpLoҹEuʯ`/)qd$�B,y[^Ge:\CY0 �N0p� :aA�=uCD1Pd́:Rf �Bs6tL}J]nZ𰙭):YL);omDg66ZS2aVՔڃSA;mWjߞ4ۏv IFܹw.^審q ^<u_xjf,+.9U$FS˖Wq]*`.l,ѶkXYQ'2Nɫ,I6?C\WJ2`N BȪ c\xl䲳v; 'Ϙ׫8a8?m:sم߅v.0)UZ gBNYnm @֨' 4WSF9H<.|]-Oo ̇laA-GJ&<1)r+zSB482%C-'ShtqS4Ex$֎O:XF)TFuJ6jY4΢K$>X-[b}(<Ab^˰Pƻ@1a?UU1|K^ �N0j,� PcM:)&]P8^2c�뀊�\ |f3,f}{00F#˂ G f|[~ "HvID'۲䉸e?A/H?_Ea^-.Y)La]ë[HhTcIZAB%8$uEvեLјleQc9Bꝅ0OC"H/*Fo_?EUk#1" H ʭK<4-(:1Z-csfmgy[Ĝ -' gԪPhyg9K;e-K7,wĥ?�5Rj(@Lp<JFj'l Vl?# y 2\2[qw&wL,vGt<fA(5臬a \{A`9N; Rg޻(g?6�L5NLwXN~y 0Hth5a'n ÑB1{lL39RB[-*Au3%Q(Kǿa`Gg/&"l{Q&Q8VER"r6TtJٵí�PZYL<c2e>]]mg"UiJ0ԚXkLDamj+<U|?1'f5` M&`I8٨LM2ֆ\"j<=>J3~qnN)P~%wwh͉M{R=>iI[�>0v`<]m&sS{o%)/=D'LWdҕGgL0/vP<sj  {H_F bJ`mTrxd\ \=%947uG0{a I3 |[YYV9l7F�N0j8� 93[�Hj0L cN!\�W+J{lԾs}ROۈF�N0h� ʛ#.W�hhuzK�9�3ڿBrcy&Mى�N0h� 1P<mc} LI" /}?uM{0h*UFD{Lzn1G%K!.Rr e6$;"'+3M``gpSv`r`RM_ӻj#[@6rR5yz7+DHТDXV!ջ-s5t?jg�kHHKd׻=c|dF@UsէMa"P]3VOs&otzo07hm`@U/ja%G~0.$'h^3NR.xo":9Y {'wIgn++"X3ޜŖuHH&d./d]^~ 9@Y( $j?SCZcO#8p[(1~rXǁh"Q?Tc):q%x3+:D (I4˄_?jϘ3kfyf嚛B �N6S� 92߾B?'J#/r cͼS3 Տk{ qṅNÌ`xBCDӴYHUnS׈f//^e@ڼ�jnř�]VT&5Q(<9A7Z>9R3Bڜ{-}U\mr!/pf$ On W*7|v6Q6d]t [?*~{[{)~PŦMepOXA}ʧ؟c]ڭ" ŋp={-;\#R d]cl(f隖NwSiaٗraz9xT4@lݟYDoEa$_Jޘ|# a/<m}%ml_$uEtRnwPyza="K;6~4P=a/}<;e) WH_=Ęp{G&~܂^$[R@=O�ybB'[$r�yщKEڎW2F�N81� zn�@r�=Ո�ˁ*HVSr5ޱԈ�N8� 0{z�;2zJ`"H�zqn;B}e_G (~2[ EF` Jcyy߂%:B=w=vi] ?>EG'/?P m-xGy:_*�N/� CWy2WAo:n`'\8b rOg/bE8JptDuaKa d VQ %]@xUPIu/FTC4RE%S]U-9 ;oٓS|Љ^:5d e }vM ~T"5:ܺǫ@-AJ*ٱc$NؾLx>}Tjh<W UBwG2I|`İVsaTzi2`f䖾sԭ6!%;& ^LpշlRCwGJfxW=o>HÑ&,곎d9 b _; #^1_D}}#LJ FHWoceΗ7{[ 3-317) qJJ$hŕer#` X"ck�; h΀U$6~S`5 93L�N:.� 6oC_g9+ M$L N(%ϓζQ?B+c1Ws)̮;αP�JDPHj qr-)Y.vT2kpmRLk _)nT7"l*ʼ�Ry5HӖ6\0߈Eoh\C5zaEE �Ȋ@ujovhIMQʗ3+Y1P[ཽme\7i-19ۂ9jcn#UR@UGkj+ Ј0(dZՊ gZ_pcrv\oWQfQxY]AUu2i?[< lH}嘎6\LRlx+*zAVA}8oJP4DjKX`_}wpgT>d Q 5u[$ ޕEyvr`X23�N:T� |%w!vE QMܠӷ>'g:_q"_n@+*y֡ e{z24Ղv ƮLg; /~)k" j.6WwUGjr]4 w!KQXuAdP>ɳ6) +9lo \,M.s%! seRTW~:;LGر=ah%c-~0$b5o-sg }۳8}$|JvS$$u@H _"'tXՈQ;}ϣ+*c%|NF "ctGf܊0Ͷ/K_*'w9T }J `Nfm `!6p <3(m" V;U5f]=_!:c%Ec|#SCo[e $Z5њ9r=0hyH)XAea",(*'/J�N=n� hl՟aHRCG#\0_HO <n*jD SB٥J5#诩/.Ś!Q+�Zj- ǛrE;zBq2D#\qoxmr^zhADZJm n.RSB䚀!G֡t񟆋{oϷ/< <RVrYQ<u(s>F71Ca M-N.阦JUHŜ)gZ`85[;[<tӻ�Pwg0a{s+,bPb2"voq莒%wp*a< x ywaC0AMVN0e,H,Ej-O0j8E<UX*Qqvk||RXA \tVr=1i xJ?FCI2AAc ;:C "ڊ`?�N>'� 1S-3u1)lqzS dФBc+f@ )88+읹 Ñ5NkĀV#ۛ(߱m"& ,-PYnmJ7; yC,EW,vApwVC|Frӌc*DKE IG �sPЊ#ObxeBr>_%ǖN8nL2w$�|P^+ [+WML*J;5Ft8So[Bg Xap1W2ɒ-ǂ6W(_DfA^}Z' ۝Sh :y{3콇~~k#fNǗ$s A+Vut~1W_߸7♣.KXp4O}U5{ g|JszCHC3e-\ R$iZcQmCdQ{�NC� 7"Rdi�_}GXjIkKQ@ЄeH(4G5v{[^L$tl{wwBhrssXQJ?p3\&}qB,'I«yl'^&f{ f`Yd8%uMX3g$eϳT% Hz;^6X>(kj1F].bΝWdw*F+VTXP=r;-& OcjMlaBfMcXbN/%WupV0߿qTHJ똸êqNdWVզd|[sp"j(Wu?Yps25`v%[\-Kd=S*܍*Q5z%QV32 ^r/±eozAk#~s<WY?uC9Ph ʓfGȟT-6Pߞx|UyV $  .a1Z+O_͕X:(baa798�NFȩ� WF]i+ }J[3#`9 _`e(9WW*r?<!Ξ .LdBbͦ.ZLemMt;V,a}JbMq:$ @JSY~;jv (. r9kC:L7#WUqZ:95.RXM; !y?>t+kC1-7D+go#v* OesADv*A<FxLpHaG\ oB~iJ{$C BX\ج w> |ǶmUUFo%CC6pѤ;0 y,/ ꆛW8Ɨ+ >ߗWL~vӍ{ ? u0 )P%2co >#tOڭ}4?0ڭO(Wn4anP-_LrQ`s+5y[62R WwQ^3F �NG$� ~BU̹�lqFp^lh+iλ�Zn겥ܼ`9Hy3? �NG$� гKƒ载 *E:.AYrl69r2@Q![s\MtQ?tC QY3mu\e !()3v= dbŋʝx]#H'zbzv,uVb{ -J>7?e-XV")S_�el\#>%I|Gz"sa^CJ2;oԒmcBtsz)ApQV=T 4>n[dcpsN hB=C[t[W⟦ٴjb(u1y/"tI!NBȖmOEn1J7={*H=ct J^*;|,3f04(n.qiG~%ybZkڠ&&Je<*۷#/ 1 xqHʞ_ŖBp4LmV.09s!~~ i] 5a_zkjl۠kF �NIP� \_p;�Drf(B].^&ħ_3�L:oQ/ Nul^ �NIP� ɑ٫E~l(DŽ/.oN@zdL]�L.ߝihRNj~ۑ� %7I8 ~,B SC>,e=խs`ZcK_s >̨*_S>nwlwn$Iegܴjd x ,D|X=xQ_>"@2CV (AЩA9Z&"nFٶDWeZ ynU'a<|pRcd u܊s`tkKQ!W4 *bђoImxd#1<&Eu{f=Jjpdֶ_]S7y3I|ti@SA�r!9:s㧹gs6z,~2״%*]2%V) ̇\/(S*,dJX1cߕπ(?lWcZlLe`d"M.YOS.*SdQXջI!0 �NIP� tтyq�C.% LQÉAy!�5,zC9<?. aRzu\8PosbY0N/llh8=@8riJt )0蔻5yDS,zׇZg$dsa);/M ҭtM_ ݿrd{jY6s5-5`܇@&%H -2V'7 |Qzݷ b\#?b'p~)#zʛkwmqXϸGz*{ٙMj ITP[ +.M1D&{P6QbRpyGz]lr1bw =8.=[gg騛ۤNy y~m85$z*&.r0Y^ZRxTbV dFMTK8N#mӦ97Sg&#5do!rfo- vl=2PgЀ[crt9] oTL(ϵ�NS?� 䩑äu+Aħ' Nxs>mZ jIg uwPAΖwgvw4ഃ`bp|FUPɆ]7YH j/e$]hPi&sV <#|,$ؐbAf FA>RzBɦ=Hr|l ƔcRoO66[LTvOwXw- d;~z:j͜%^�NS?!� 1a;i�RR\�lZKi6Cn:>͹r[/]'[`ϳJ8m Ku1S B%0*b_jY_3maFy<Xn~nZRrUKS+C݊$f 8hD:G fmY?��'fO% ڦ;eSև"ߡǕ#[{fq\룈=dYJ?#72C/m2*AF�NV$� y~M�Dۏ>cpҳ�MeIapf�NV'� cOKUh*Df|L 4@ҕ!m4$Pxʇs"I `DV̱rQvwk<]AtDKn <hmc}i\`E\qRQzA"3&S={>vaJ.{e5ūe]UM(Q]vhGRRvEQTBTT vPi6f2$fYUjN|]Ȉy?4ѬԆέP=`QɀLV^nAD,2 0C;mrtm'+QeH3N{fмNr"r1^HM0!!RLS3@VQO!!B1>g &m ?T+⨭ef�oQ=@I综ϧ#!<#ԛ/rw$aƠc#B偘!a=WNɎ'!tƻW7ۉ'�NX� Ki�D`_ܕ71'7/ϛfՊPЬ^aO:k]&ІX: K=WWE^$J3iEI.D-3La c@HT&`xz^)J)/H.%k9flxLnN&㕢4-,O1E"&Å?OhibHE";;\?sŨsH<,LzbEf0ÌQM${t#bHiC~R?};2 25YwVQ^Rס,tk1|MRɀnMLMM>q2-ևW8]xrvFNkrkxBTl?|~sYK`܎q1T J=>4" Yb i,3CRvqx3g}?YT$%ImO0CbJWK1�NoF� *!z �:*9no>.C'ٻ;V/6D\0mrIN®�iދ`Y‡˦Qa2O YNp˨zGj>FBV%HIFa 隰#޽釦*l^[oI`KxwərŸ8A L^>p6F<@5Ӏ%( EebLaߧl6rvha-Leϑ}sхVvhіdC1ل ;HrCoz[qw1qPZ;CmQ<Jf )֓Ũ&Ǘ,1)yAgv$F湌Vܮп5˫|Gahm?"ʋ8cy8 3 [T6l]-C4^K3G$?@hkrR<Q @=_jxD e!E;+~ �Nv~=� f㗃/T*3SC"�./UM1uzj 3FL"j@0^;B'd%j=bOkmsTnui,2l"f 1/ADiz罅M 6].jrЊ򉛭ljӢȧeMX17Y7c-SĆΙ}}M>CFF)YC^NӃt,:UIdgy ӰmK%AĚLvmͼi>?svb!@G\h)#CH]ψ<Wl3M @Gw߮'LYlͯ:.6q?P ZXiLLmF\: �ʅx]JTwҿq}.uqId<=і:p&ߠ}=<%B+d5j2uZRG5F>-5imH[O%+\B%ebrmqbӒ>f�N+� v+WxBbuxdlѯ[ U/p1 {d䣥(MbOr]R # $ =#?BR) 3$؜ޟWq!-2lc6p-IVߕve 7NGaHk%J�a@^'ԩe` doIS1yxq]hd1@_vuN<y\2ñw &ˆD$!KR)pqlӮ&L< ѶW pp`L!%CqD }zrɸk[x-/ʾԻ5aY2UŦ;M?S:v\Vq"$F'7KFkRf#mri|%y V j3iPFr}o 1:DMa N 73]4`l~ATUMA|~�t!܇澃! F�N� kbb8U�w񸛶Q�"-LޅONC�N\� Je�ހs $ݱ^qDZr0ϐ18x'USivKgRko%-Ǩ5r1zZp2$brx#ɨ׫ 2Bi6o8.eWû,fFN i;|!.y ~l,j]8^zg+Żs,k!AZ|,\�Ja㬈a=H1V5.6i̼OSG#Gh,pآ>בE.tXG�N?� ky$f>qZS47JϹ[+[jTx:ZbOQ_)J w qJWKHZuSK:vmf7Sf`i8..T֡* >bX~ 70Գ6NRJtL i{TEKL:7'2֥qYƓOcs2Wʾziod?0!c.ؖӆD|YC4$jꑓJeK|fiAcR_?GO2"rPjp~v8FOpa9 \ɛeSXb�6"""Cmg,"iiwcƅ3h> u<K`e9(G=+-qymtYY^d?Aju�Cm pa|fx=l (YmX~,BM(lAi W7b rD]Ÿx(>TF�Ne� ȸx*<=ox|B͝O}þU<>KBAE>1ήo UXՀ\R$bjc0\Y�VT>֨ݔ4*/BsNQFiVjPUR_ hVSd oݶAjK_/S gO{uέ"aîrAk?xʙs>c@κ8. N+O27(ݣ{2]0#NGO(<opsKw2*G.ϳGgb7>ND�;JwJ̿"E)ɨAqجb񹮊)"02do&c]=qmvUqe^Sai$򪆗.6;A~^} "GMeۓ9{ <|fKb9t g y#0L?JE8rMFndU^VOݻgf�O-� _%|0]'UZS/sӱ2 I9$iy_?hDm%sAN- -- 㛤;L*XK`$y8d_T(muV%"C7|`"no{`o,90~bh ը%v</ZZ6۫/^h'H^b!]ʏ<Lj 9DU)]j+BBm[26�cy\Bh8:1p/%C9 A'Gs1OZͳ&v<`ir {5 œu(:%,LISs!*f (B;n+<۝ӽN/`.@,-S ׋2c<0ȈVU3[� <$ <>LKQ+0ŸjGQ(>&žFwڑCNJ{џNFrָh/k(Tk0bځȺB2Z�J$� w/<&�`sLPp~D[Q瑸QjRMh-Th'5t=3%r8&[{LvݯHαUSnҜ%[ك7C1 ~ES=ͼb`_j:3"_4_gY`f0 wWB-F4L{]N`,Dqwؔ-Sels$|1ǷEt˰q6+gC/uk9S lBIA!z~|iDOZ ڎ9c"0klG=}[>IdmoA.A6neYQGr)A}[G I�Z<QICۜL}QĬ�npw lRLG!r93r'~K~J-7hy`+"mW877c)rFϺsYcq i}7*]w!fM\oҰ^!x�^> �( f   �I?P� 96#�ڧǷR sf4W ĺŬg.+fG`Vq7<۩b HfHx)A9G2l[ly4[t FU*ZQ`+zŭ{Iؽ%գ<-~Գ.gB < tݨfTdJ/iRH9L*."E?uڙ =q �=�5�X8ya Ŵ*vMhnn7;̚@ le.`R\ [nX:{(uvu|\Mp0c?RH) @ﻭUw88QyT} vըoAySox�~sk }vc,d˰%q/A[fP0$\lnR>242ɫ+Ǎ%HvGvl͢_j#]@OHjz9ĨOcCO �Of� #>,$A|�'snnaG.�REB\b <T2P;{CfΗ56/A";B P'۲JiIW,D+A# 2 񱚋Ӕ}>y4<=dE44 c*b/U޺Pmsy\7CCѴ: 2 4p/5]GYS0c0>i㩥=A{ ҹ8IGnZ"bM31dCMNp P;V'WwfCV_ x͇~y[?b#U ۉ*brgK<G{}[ QCA$a/KKG{5 9Q&v"ғb$^eC;:G%^ԏ3c3 wE0HZ8x_v%ESho&_ƥh"4xǩfacd# tcs|UD*SD;fJݼDϧ6IUl@ _ZΗ҇JnqDp8.F�ON� q9�83ڿtl�B!>tSL`(hЉ�O� F9dʓsS0!Nw._J&Ll:=ȑn'`֑U޶Zak85IԟQlcjÚ()<w<-&>KKBuL٤. @_kt'JԠmZo0"3ckiИ(5_IɀquO%AuEeyl}/SOfaN8^@F N r1H3~ vd u�FD_^CEՌXf2xLL?ۡ%hf^;zN z$5 3ʮ"1u mxi칬fgNeZeoipk`F7e8=;ԽO:@ mF�ffp+NLI,>�g} |VN*[+M2fоcC)V,pdRtaB٩΁Ll@fԛ(p@Nn0}cw^�O� FFտ�(<6ZC.Fdcb@| y'!�[^@aMvKrF0Qn[�`�Oˣ� hNTo1j�|ԗb~L1UR hF%NH$#/uGQޗ_Uh b-"0KIV 3CDW3w!TҧH,&.8ύ굆C:Ad%YS7{ܜ¦n0!axT=KɝO˽ "ơ=QxP)Gi\UFLSE-{~z6iZ招6`; hw&eL,jҕlu Jq Ժ:t9; ,7gV�nJH PhAa0yKo vx&[!4rn8o ~8| '-ay|ЇV1(EW?<ئRW|8-*{d'=mg⍳՝{|7jf'%`&Ad k`3֮1I Y(z43ֆ&썙Ϣ<X/>[(vi&I]jz b.u԰}�Oim� z,ۋ5v %Zu7dD~nP.u_+V992Tx,#�O>PwXO|uP"EI!z4o>0E� * &ryd_L{mKȲa";1fzаVM%;LZO+a˙O ̬/O8%}=pl)2kY4.@Я?A:~/IאF?�ݳຼr�{ r*ւxl~>hv$ȿmAے3O$Y\ 2Q;R$L͕<38`w$jޙ۲A8KoݗR(0/A.kVfHҜ*>J˷ϊᗲX%P,`pEՕ(P@.n2!`X6 &i&Q;CQSXF8Uapv'LUma�O1� up<..7Y<Ǩpx΢#ZGL@o#@"`& svqr$ =h5#dٜ䨒f&b#tݕ2])!Evd>4ଃc:ħ;w!r�ĊP4)Peh^%H,LRdnȭ)EŔ\BnsZ+8 .؞ES-b<=>\0)q|TX5FѩXzf-q OC ^�v,`<ɪfeөOrXODfIƣv}e'Ccc4SR"1<EYӲBD!}YnHGmO(SN8 Ooט1z6Lkj"ٱH߼,kBxpρ.G9Dtvy`XÉBZF, a{$/L.+FƜF(̃aRHsa~a�OA� o4?zr�,1ȓ2uDm|o!@ew wgh ݪT!8/{Pp*ZR۠dH>ɲ6~Sj@P$Q<`&Du#;"NC E#�߄Q:.)6 jtJy8h0PZů.<8F{!:3y憅*|ol7DZD+gpd8,!|r?RF CƬ&`<\7 I}8P>W+Y c.>#?7;gI"6e^I? (| ejNf)v1{ޥ4T)(/^+3D?`*5^Њ>.ovl72(צzAWB!8R/l<{|z1.bCg  $9HӱFg!ч|k=w+*bP*))dy9<_Wߨ-\t�P�n� u|8It1]8@Pݵ͂WJj:^n;P]xKZQpP\Mv* /8+ͥVI̫~Y\oB~(szwWdډ.KG8o3"Mm|ěa]&'Y)aΗ۩s^}Dp[H 7@ 5dl5GR|~RK*J28wJ`e`XE)no.zBUɉ�P�l� {5Fz?U.1=�߅o㑋71 (cg5,t?7B.Q$e(ځ$;HHocJIʍ "TdUdR_O깻| c[0ޤ!vj1m2O̾ɗ5~{QrM]*>,Pv-r_'2V.@!6cTSWӣX?N?Dn78Yfe Lv@F^_, �P�w{�  d6A^]4Z^n.,hK]3aZWS<g nIy`p,e�-@?)P M[_%s%1~Y"(=/U)_]8g/KX%Ƿj(CJZ6QT-ܕ VWƴ0C^@$XQqǩaXM肦ָ-7 \o=^Q(k!t%ey?<F e}t2&GfIhs}3Eua&*cbMq+Np 7Z~q <4ב[88�="z H ߫?57�Ii|?jok[3C"#\Vk҅+ H daF7P>9 XHV1\1O#}'G1$P{8= ۅp2N{tQ�<&pp"-t%c$N7@"A EK6 �P � -hmArBIR?ڕGDZ}4iT&7&cKnCQRg6(@}=}ȸ@{)KVލ20ΑM3ڊ S~`VU#]N> Q^IŻhG15pے TNTTcyt �&F X`-\Oޣyr+dtC)kW;"?[/9_-86Nھ.W^l^:}3R|_ڀ0|^׹H؎>*1Lxv͟V{2uӼEQA*/P2\]W +�F> t=3r 2;1@E>Cs1OrxDp4'gFe7&.qqW/6<ũYg% nG\}3{ =ٿBD X@m!" ޜ)'n}"@OŎ=uiHB;51v(]d'o^)ZcN=^F([_ ω �P‚� ̝ZӠ洡>>H;ԢV|b&ׁ=yQ5J5�va8o <وM�y߀04&[)3G )AlKWY0d5ğ*\^x^9zkP!dX�J*Rmj8'}7*ǽު9'_}Ѱe!Oge0k)GO)b"`C&5HIw(X߅$V#~ɭF\(qv#^J B/qjdzGK"=+=!Eu-P FO@RsuhCvW뻶c2]IoyG=Fq&_3:aaBAн#O!Τ!#cc=%5f[œ<N)‘n {)v֎EM7Vua\(xՔdAh"P7z71 ;%q}NïˮWΖsQK+}TZu�P?�   *e~�J ?EP!@gYi熞/Fŝ-2 wW: ۊ&JgI ˟b%X/';+Y 'f"lKYYDfJ+~-HY\})a>!H O6BR5۔$/7J$L xlJ#+X0� d_k٫QB2q~%=1G*}*Igz><N{t|^/IcȱE-/x_F41&Ѽ%#?;d8АIx([(rʣ<uv1P}z,R1rɉbz @.S۷oDn%PG8vIb廭{ݔ ir4۝.i# 6eV쎯paW#gbY ?'T74 DOҁpIs=?ɩED_Pݙz}h!G�P [� 5\ 2 &1^Öϴ}9l,5.Ǘ˵>3kxVvSΊAYPoo?jqlzx~5: Pq iY ro5#U0yrlX)P7HKWLG$4㓬Y`4?}`ݓ;ZFN@fs\!uCΏ\}Z&t�>oe &KH'-ZHʄ0=d;jpC@\Gܕ) y7?t,kg+, \lk a@~?UR+ʃt4nTH3ߨ~@WLJT7u#M,d'TGo{|�P`� Z獪.9+M;<0_m�虊9"ir<k a\h#ڍ>N6;sVȺZgLd C\UЉO�5VYv^qj CPBp1}&Y$30@a/W|M2շ;.K8c.dDV]Rp^7&"w~ J[V3C,r0XFNպ8LLoI:n <폘}};Rͬv|�7wt>gWoQ#"Ã#3oM, tTi?)詿2Z՞pZ;qgia<^> vqtH�ӵMNʭ=-9BQ˒!KB0ib;RgG<zgՎY(3IJd [XJ-7?'8ۤĝ`!r3L{cr €w3U?WARxlBɊHV�lr䚈F�P� yv;�V` 2c#b3J䓓�clhf/H[�P� ؠ뺪/'�R8@Klx"~Ax^Ջ@VADMQ0=oO J.cMHY՛z?/%43!g-7*ݸޭH%`b L[Vb4ܚRF^p{.%ETd9 S8O J6yxiLTR;*πjy>+fywх]҇rsB39sE;Y z?d;&-{}E&7|E|=R4ĤM� Hmgxg l-^gGBSJFE Ng9C7B|WkjmU2ticsprq[a{ScJ:O_8Y0(bS ؐ) Po3s"ynW-nT\2Gϊ/,ȋ&}h?ȇ Df\^Ŧ1꾇�P.� '7<m߹ :s @#06/�Y,TPt(틆LSHD0eY<(3_r-ޓhD&j3I!ޮ ?ȨqI-lvp3U?fr!9QWoʘsyCT =aBŔeI}5,N&$`qKrt:(*wqqzNwQr]Qzk%i*ry!d#54挗jp:t1pQlc-hUNr I=wC`A\#/.ި$2%KB}-S>S@\qZᚯ�``7b#OE7[9ς懽vb U ܔ=* ƱuO?.*\5Vr>st89oPc9Agk>0hJ`ru ;" ֡dk ˨qQqb#q#MSBfdT_f1 �Pɇ� ';tN#yIϸpZGI!B: jnv!^\qρ"67 #@)5 dJhtxI7Ju$BxX8@&)ɐ=L�Ӕ|x߬z/g+@\kæZ+ JŽQhG)Dh9$'i me$6q>�S-WfrxD5Tx/xm%MmF@_^uabvV}BK@b;ܢ(WL�kWbc hwx ׊mU3K֭ :/pU>l/Di":'~>f'c@ڕ98np̞=XW"n+/5H .s^�n(@8+O!PE%D�]>ApSQ-Sx[z)$є5dt2kdDűz86zB�V4MF4F |ˉ�P� 7+-AYjƃIr `:V!G.ڃ[ s&, "JʝMNV_\imzYRzDpuԸͪgeҭ/V0}XW Nη@DSƯ/W6$IikRJИ{&R|<P)n_8~\]6j_{ 8a} pnQC4)�߽"vErxlLńomsRI9@Ņ8t j&0}z]I�<O #!D>l0HV}~FGr1/M MBK}jV42lu6"dmr>oPkV^M7w&Gjd͚d$vn:i4Nh4[:7LzRx}EB:]C8(46W�c_v(6cj[vT ,[F�.}<H؉�P� t\Gf5c:<. 8Kf/z~;*W%—o:`A-#Ve (STIlgR_GV^ aH<AZYg%#lz=y~A?RfxtE+x7'F}2wiiϓLfiFUHM'o_te]h b@6'ݎp֭w '^NFR M[^Ml#@*5l#}YDRn.aPbUg}s}t|$ة+S8ȞBsa2nr!_᪶!zݕo#];/* 'Tdz֧B] cNeCZftCdyq62LSp6{|2Kҁ9X;E*GT;}kWCTWd*ӻҞjY<'[�iȐ| u<] ~=b01 T_'=x}r'6& v<'�M,� !RWx "Ǔ =A]X83o(>F*gF % KV'\94{0Z,|؎h}sllȝu bG?ɕSMV^Hߦ_G\l-@J w>�6vkvwbk\I0h\k&( /om ])dp,_OȒ:{v5gl5NAŗ!n LÊXN`3ݯlB,rSC9ƩBΈH|I>Ǚyt`==:(Eq~hΘ! zd,O@0?kP{i|R1 @+E/i{_Ugq} k? xקYOۦѼl3!4-)df, 'g.[ʭUxb7<BeiX9 `VӰhE`jN^->3ńתnuN,$IwXnM;f�O]� xyc(~f�NW l=Ҿ:g|^Ƶ3uxusIx]R! =m 1=ȉ=;vl=Yzk 7.6+)3.iRZ6l3s3ys&$ͳqr.ZftJxG8FbSm+3 ?AƝ0>YzܦSJ7o.(O~@{`f;=?=jO)32b^9{SL+DkHr:07m~ ܨlp^'}2q2hJ*XluDe [f1K 4JuڸT|t^PB,?ËtfD4C};-Q9%vAK*4;](EZbҵl UCU T@ )W+ ˃A f�gSMGH؍k(P{�PG� u-ޥ&1/ngoU.ך>eZ5n^yJdzRLD.="ie0ޚ:<uFzhpx5O㩃ĻxL2\>s;?-/u::UN/}g>OpP]YYr2p(PʹO"'n@thV֭;c2ޙ76Ɇt9bȹ?Xdt}ځꡂ(H%6Twf>}ʋ*4IK~'߽B1WM 3mI8SDs/m:SA_ c<a$%:? T"qӪSYAނ0W0]C$칪¬-\~~9.pOs p٩5C&L:rb0�W"2( ѣu<7Rʓ[x$ڍF?b|yw>毂s.bZP g,A99#K=絎0SJ;oAM9=K=g+Ӏzg>f=S1�(S(bb�P� xAӬew0l2 67Na4ܿU[8cUo_A:nL�4ݳ#rFdJ+@w ֝g.쭎Cr9qܩwO]q!A-Kt+}\ ٞz�s]+4R$r]̒^>r,^& 36Ǫٗ֝7Z<[$V%$=%}i6elsYVR4 2?5B-ﱛ =.+Ox6)wr<mU>DAlk2 uļR҂:,>ڂ-7V&5zט_E4G֖&dIi#]P,/%oˆG o_OjRaV&N[syR�a@jo1J(Op:։)ZVw:&T 4kyORw 9B)8k] :I谮�Pz2� ƐI<E(�+ަgz) <Oj-1YGMrZaV a}`3KRFV f}ϋ(M �샲 M8@3O:W1qnk7\2m�sq[{bf["f}/te9b G<De8wi>Ɛk-gpkXNxb</'.vK r㿏Hz޳5a+ N r2) 8]W 2c#ku*!vj|,] 3`F=nWѸ ]T^5}jfiJlgt[ (Iz|Җk$%l6jg&i=j [J֔7܊҄.h1!bwDUo\i͆9&=*{ ʹ\`̟4Dq9cmwEh2W>Iٽ"ū@4�;go !Biz{b�P5u� rɡw3r+'<]&Q(HY�f�z0%o:rŊ̔I29#ʺ<1??<uUB[|%rl<@ ؍z:D\77Y)Hn]1sQdwm 7N$q ;^CS#e@TT:;Re8n 3wu#ۋgdK~e4/542 'ZD;~Y`B1;�6Mm콵9}:(<M@HkVˉ樉cGKŒߦU d)ѐ߭H~ (lUP-n<^m7-J?zD(MPn[8Id׿B؍ ?aHS緻kͽxU9 Ƞ}{R? Vdr#K-g/$�Kظp\Aiݢ쨿F�P 7� _ #n4Ŧ�(>PQ $u݁~�k e,'MAZC�QT� &S'Xr|oƨy 9 7`8e+pvuKup33 ,2o,zVGC$EBBy &Q; S0V(ԥ#u3R4^D4i31) I^dڙxEߔԮ0:0jG;�&A4 'Yu(?.N"tͲuoUA,`![\~tO)[]ɣnP-)6(1߉�P� O<M8r�(x@x?T҆1'aQmaH66 x5˞(o$= H'Z%={rHP|LkK7ÇT�֡=F}}MD_hge<bd41g�iM TyպB\c!YzfGlk%AB|J)/scuT_vej--qqVb(�ڭC')`s^U0ZQW$,BK"O6V mm_"* [mK=ƮF$!'Slyh\Ǔ-sW_l vݫn1@[ևݛͼ|C8.)΄)y!D&yXYr{((mCx%W&?]`;6~=0tfM|zIi "KX qr4lHh �sǁ:GY[De]W>n&P0ge9 1R�K~� Bkm 9Xfe13+tϢR Gњ%P7-<pHd\<LSLg@  #?j$]' N5.Ӄ;sS5АUJ޻"jmdP;� 1\Nˮ@*&D9$�ӹf))<^B1a҆ sw3S`G rI)v KP|Lq_;'[@_?;*)}z9i~ɉ�Q9<� )ȴıE3Tx4^a/B_|fw叆@JypN'� j?R(nRu D$LRs'q+qu>O51Aˍ莊.l�aւ FS.Wea[8ߪ C]MXM=ذ:Ė - Y,~ 5_V1)]m7К+U4]aY^3C Y"B~;[66zj?M ,pbos!N>&'zn}vDmiD<5ce_Y8ov@J&鰠DJc쀿3ҲY'Z͡Tv:p̂g�a_ W5 :8›]Jǩ<Xe71*zb_1cD#)l:jDޭ*-7q%ѻO(^ &a6s3fh?˻ KLOQ h[13 �Q;� �3ukUC|Kvs/߁c1$G�b) >2 ,S.CX]$f1*ذ_Xz%x`+#@c f+@6JOYxH?p :Mf4.}FÎJhSr |$5{#޸nbҝLcYڠ8&uwXg,C9`bMi+j7`RMvV6 xFW 9(,�iwN2<8+ȓ֘hÙ?w-l(ΠG1 & Q:Ífn.jeSonB (W|n2nLdu9[Ah ͺ*/h';G"X%k"Y'-UN1/5E,[Sb�$9Ͳeo0R}F4)q+Ac:Ej҆j;SD΂ ?Ywß/f܈F �Q;� Kbj�AyH~tc�gs VƿMJg�Qi� wf\ G.x +΀N)K\" i֖5tH'h2rM‘lC?զ&G[Q;(KZ~6-HaOzI`2U^\Ic{!΅ڪAD V\'.d&'C@$K|Qiɾ6+DFa8̴ʅv"ӭ.8o,TAW LDXRVh.&,|{=[`>(v&į�Q|� <@$�*7!(Mo+!Kg֖=œɏBg…^A٘WDLĀSm; P肱gLb8ū;Gkf=K"#IO-%VZ'ipһ#x/Bp.GC Qs`䊴BiJnW:m hT~u yX1^%bzgW.BL RKh,0.`e8 dS0^B3>Mt ^4jRSXuEXjZY:Ι[@*`J=UB$5l]/ .7|"#Ù/ ۥ�}#@kx4P*Eh*aoNPip;}*/䣣W"ǿ/+)MnD*,Wı5! `1ݺH/ UjU qIJJ4>@#�R5� t&;7U B9ǮN�E"MGQuNT VyiE6dUpTOEQ<Q#} uPz$I w恰^y*V%SxyYǧ' hh-[Y"KO^AnS'a`xCY?ǚ/[?x=Ka{GdaU;\koRh{7-K`oj`C >lN]VBXH\p,WþU0JoE(M̞e0y6aBIǪkӉb|`ݾ!B:srǠC0iAJjW|N$V؎)- h>dԪi 9?6[rp/hF2o"ϻ{1(;4( 'Wa AXc 3<vic$(:YȶI ZYb X~BiDCɪK ;;>m�R.g� r|V(£�LEn c+4 >z#9ݯ\N"d-$ 싊=z"v>q1OgItB/&?%Lٌwh.:f=-23e[W>ceBӔ-gQ6#(&Q"*fZiqKtuW|ͫqWnG]|E5%-(u{5>,GB3\>n~)DQ {x'~U_fc ܭ'T%4 hSu}&Q` ]pK:c+? @N#SBpwmhr2]]x mgAz۟ff,%8=MFЈԫ{ƊqXB3w O rQ(3;22 iZ +dwQ=:9G7qNhSx�,�چ: So id;&ߊ6\8L lNS٫擉" � R=� D=[8E^:Q9M9HS ~Ri(FiZ^+dhɮBܲZ/h*b֝o?"Ǿ >ee5JuxzZ{úR4;umQ;<]m]nGkd KeyF)REX/pe 5�)C0УeY3:Q\OOC0$ĥ}GP Du޾ 4m5'F�RA� Z|�W5K.-լ˷4y\z?xPȋC}fzTQ>q3AB7\AZ]@lL)~Di9Lhipk'?]w ZޫsJz'c{6ɿHl }Pvmj݆īL(FQ*B ~h(wuF6M25{ ,|6ԑT)囸?ƥ5vL0ZS@HQH�ag[(cWc5DyăŴ)KaAN $A̖<+"/j Awj>2Ks7(EqSYn=@F=ԗW dp5l,ajIBbVs(~^=^Ax.,n!I|bEbvKe N)NVmgYB{ )5ş<1Cjeth\V2J~�RzuH� @L"�ŏtGԥb;Xp?֘No/ؙ/8h= P/ֶEhMR6Ous WrhGy\.\B}BF{c]oD sMZ`"3 4ꋫ5Y6D+B@n1<3{tF䑖 JB'OB eR˝`d1~arg+ ͟_U71&k >1QBkO滅_XاU8 B�yS߰=/oSWנCBcB,Hl\%\6?Ob3D) [0KISfQm[.U.ӯm^Eͯh� @N`*i~jd6AĨЛm&(_[8Zfx|`^fZ c~aB<KZoК<aZ�R� ND `lzX8!N-^+cHSљKSNfoW7Zd%|?[&8,viZdyjā[~/e"Mb89}pY֍7m\bffT-9Ig㙸mI2{5CMD[ ҋ3VG:� hPY[ݰWc}( Rd ZȚ+4& q\sh{5c$T)O *V/ݺ& 2{Ͻj`lC9<S=HmMV՛XZ-ˁ@V~e&>Bj9i6{ƿò61Ѭiҕ-yraA@W:]kls$dnO _i d „̔b9!T)jiqkuI7?'.~p8¦f=\1�R� d˘l(YWvȗ$glA&&![(xnn6ǧgMZG4O"kM<[¡�Noį~f;/C朱U}Z9׆[M=Ɖ}+hjX8 < ~[fF5 !#>-C{uP8 [I5;^7(==MUN9gH?Ccw EP4Cʱ4{0QARΈF�S/� Z 'Ȏ�)Rn.)?+�A$0o`ohF�Sؾ� -zzͮM9G�{IPGHw9Kf �nXo忟ѲڼAF�Sy� ^>F�L/nsbpzvCy�Jb+oCѵ b)~1F�UN&� ůwJXQ Z1� ,}*`�t `tҀb/V�U\� 򭅬Bg~�bT "�0_Q _IhN�IM=iFOC%�S� 8y{juG F;>r~0^hR6"_/`%d0+nn>oi k[i7KҳgZ '98Y>USY.o ۛKC_Ie#(%S#X'D&Lvqw'&GƌtV9,qz$Pe/j3#(*<"r'tK1|bfC^;_ Ol%aԳv@c8g׺Us~F&f(/"�T4M� �^L?^7~+64}#b11 Lю`pmZVU�pM!eQ@qTBi3_Cc �o^2PDDP6$j2$˘ :RFUWs''WBB9C8-r@,A=YD w"փ\ë&ZvBQ{4e ҉}ߕmmTDA*EXN`4֭nF�T$� Bj*6A+]&ڔrT&bd `.hC6#ߴig|ڗpA {ซl޹jk`~<AIi띒f9iGaJb*Wi["D-}gp3O1*dz8I �ey,B֤: ^$6J{CUeM ʍ0Jbu&G@9*cd v}sIo�UC� PKP+d* "LI>B?'zuELmu Z7~pc%ȀԔܮg2!j;xrAv{c|v<; oKX _t&%|s\KSO`mȮTz+<wL&'DB(lsK2J<tzi{DRN ĿP7m!校-]!񒌹̥6-#'�U[� vB}S 71�+a"0wv's_SΉr�"j`Lid I40/�1YG*T⩰$ Z\USS'x͂?C$n ZV�8L:'N.#7MF,ha]P:tPl / 4K j06l6C ! 4ݠзY9 'F˥e}.Hb~U0Z@ ^x�U]d�  mGڔ�݅ \ł@ "p\[ߊLww= 8 $O1_J'}\K=H ީDo]Rda UA?9r9/ЙDnWN9;RxAH-xvľWǥnhro ?Z_ܩqm1_bD|*n{W^/Vُ<@S� &sZiVv ,9jF5�Ta3M� ^*�8mĴ\0DX9am4hnOxPy vhx1n/t&ڨpo)p6Fh~}{ݜ.Z0w;mV֒sv̨<_a}"6axV 9Ns\%e뗱tej)j ڹf)ȼ*^t CVkWt> ‚!~X=w%/�_.- �U#� ISNtN;nIXܖ6s{VGx]"ƬK;w[~BM0)uqwu[:#?/U)#.IН]﹣8 '1ko}԰FͿhHkɄIB@Uڡdl}޶r6kPUNf1*ݶu|/HԸyݳJE& ̞R}Q(CנGIR6D"� Q\� ~(�U �{-"mpjx=0*X8p1rcwPj扨6|jȶ_#J©e)rŢ0d12z(MCTO )K-6췙>Bi2Xqw[DMcyooA"̑ʱCi)kpcM"2 \NI9g7>KALdwb0?wε73Zh~˛z6q&gh�U؈K� ϊK[ 2|KlD!h"1؍ĝHڴ6G?Cv<5Xj*wbA(=l//l{LFx6.Es_ʖo[D/2# V8 {)Ͽet}4.znw`<ym%q?D>ܟX8Pd+.tz8DpY6�B`P"Q5(]3BUbᬎ37R-oP6Hy/`;lQԓ=y$g~R!8y!J� #E |sl߹XL2\SŒ3mmy NwÐ5ǒWH7$"X|ؙ2G�xVfTf˞)ms\ler$+i �U � (٦duL o/8mIb 'v3OLrߴhuEN|عNcIu=eFi< 2/vZ0a 8A,b̙|*I%^`ڠSC`25!n h!MR:R꣐|kxR9,׬`=}EZїCIdA٦_WoS"Y=#Oa#. MZ ;GVlOiY$SH[Ve>`KA(<MH2ӽ[Ǩ0D 5?8"q3G43Cg KF0 tP(EmE@Di`ہ�'ze"CW(4nj Չ�STC�  `B kRz{ )By*$9*ɉw3 ו]Q=ֻݙt8|8{%VYSK+Vs|r?W?AzH;#`l% #{h%Ua }{u&KDvTȡ x`Ѹ)Bmg6 [uQ]U (S=0SROc螙eDg-!Õہ|D.n]lz&"OB;. g#^(�Φzpp=P ;BQ;_BqgNZ>!2UG2_ƥHLѤ_'Rot)g|;ݧZhbm7s  -’+^Za̋+5ԅnt)TH:* hOLvcfjp`B9 ͸eH"ޫ|Fc0UK)XplQ_uv[̸6�S'b� cZ쑪$�n)t%[ѳp uW0u+I}*nOUqɃ8>ZaCf܉=]!U#K+'FXC[_2k�Ia_ȷ=K<==sf'^A?#!0h@r<tL`Wnc); ֒omOhq]e-0;6k)OncZHu?3+ayhfp&3 YuޭP΃yuXF*r;4iE|6k:PuaTűԞ>!mN_ya΃&{l Pg ;EoEɴIQ9hBy02eު߸yY8dpv]ٌ66fu@&_9#rhv2PR:1u.USF0X' `*Qa%"ݣAx}QJ4HivP-$^|jՉܜ% 4F+22=7JR�S/� C0RVے{TD/B*e: UTNEDǜIU5 G :_ɸBB܏X<וJh&(Zx3f#?b0'+WVk'*穐H+I<&r|�SJ6{ԙ6s\툛1WU7 -If\-[ZIA9UAȗDPg^ gi}B'LLIXJ;q̭n&o:Ɠ!DDPۏn(?0IWGUG͹}]p֦\ ;s DOxn5G7#gJL2*7ۤpލA]O^F̮h}ՖEQ5k d}5 4d10n4gQE( 9kPä!*7WF`Wup+bq˶|ꄄZ(x55r]o'Yܾ5w?g: U0IsՔ+ ,�SN� f yev]+-(ٟ`2}W0/yNޔ'8EF 1\wC�z myT ԞxqO}NaN.]m 1zQjMN@%\DFiq@z ­QobH¦(HloBϟy=,!!_,.xbΎ�)Cpu \8*"/X$\ 6왴1Vʣ`22ಱ^0@"c$>-̯/jG R|mU4/Mrw#Dfjr p5$EZAooul<?8ю7槓Ṯl$,员2 j] FG0"8| |l".~=jUR"aiu75EqٵA3$KtJ?'(/w[} ZCG NvRx D Hj% Fd3�SkE� B2�)6ۗ3?UqAC{OU _4@ f*5;@?<8[zQEіDTbxv跨iLox4BfG [&񎁕Sn+λ?.M{9e~)'dOvi!*x(QP+ :˓C*霂xyv7Djc/`dxcE MWez^51E$Ӻ*׺k1xfrAa)eݸחř4ԡms=T| *Lc8=pT<?viGkml[ 4/x`ŒCkGTi26֖sV׸!J`J.lHit{)EnuDgotcmo@PUCyj5lFjP`RO$6 F+·-Xv?;.!Mݣl؉�SY^� h SW",:!i }ͼ[ci]&�d\ꯪ]@\J*UHcw_{&Q҄" ()wn/IVzr.0$6G!g X W[-&)D+nKof^‡w29h|nI])+G'Ϝ<JM^Niu\_gR_$15jU`&KPZƇsE&ڞvi@_7l0/o_T'?@-/DumC1i\̰\i] fږu2Wam"ż<b8GK5fڕz0:}Ӛ{zȎp^D I AD3!Ox~<MxD%}XCF]tτ#CeR& v}0r0|q${.չgJv.'VvJq+b:W-�T-"� GG䶁=�D~[+nj_1oY1m=؊.rJA~U$TZY# P7kIO.u4 VVc9m:Otvrn 6R9mgpzfDZN0&T V_MgAa2̽%kIϏOsS(꩙)e�qx܍ >/]O>6_[ZvV+c{CkҹZ $7ܰ)PJV&@4|cyN??F@2Hs>r~+O8ص]ʁ58riU?79do}@K{H>6/Do4䐚8YR!^Qj<ӁA QMCJs�R9FB-.NyzDwH;3Y3w#3-$q V =Ϊ˺j[i_?TD_ zȢ=2\O<%�T� ncK�@DZѼS?cӇ b4 OqL3TV'Eh;qJ7WDq~⧻5fWڱ^pPJ;oӎK<�<TGz  f'Mkj!6q/ѐgX/ifiHda2Q/&˝`$B)lcpR`*x`vo'Qb8K \{u(И=t-@/8`PqV'P,鿑$=EEt:_y~mÿv Ḻ:&w}bn50,<=3Dv^kRP i3>Ex@\eC]._+Z 댿MXmƴ&RUh&f]c>8UP k.rVe/37J52qs>HTwu@1ߥQn`V\gUR�L|N{]<w ɻa� �T6� a;d Y4nGy"RiJ˿ʦ8'%G&Q"YܖEN  ԉ>Gǡn|H6{uN_Hnh#MXL ׋12$ !$]wMo5p$b}(^6VxXUu3?NJ|*B[/pz!B{m%N3C ʢ-SY-W=@fjny+Y߱LQ.+Jc70)]\| *&%2.�Yd 7"(f  }nߴ_?G^Nwތd<xѶR|b؊縇o̢?[yC[{P [1mDX/۞hlUnjei!>q־?kSZm5HC`3eru6E#] "�HF�Tu� EQqMh`7 40kLÐa7)̊aaX~O�UV BEiz"Π[7yUTQ:> lvTf?i޴Q2 m^<pP߼i<+V2a*Ɠ|zZ]ieN �e0򞷠�=6ԲBE9Ua|]|V#֯}Ҭ y fKu86`+RgP%y+k7{j<\ zQm5p @n!XFZOO DŌ"))pS~ mN(\P�m0r!KIۛ%&/[ oޓ :!ܦ5F#`ׄ_%~r(N *_nA̴!lİ.dv${'PBTH!kl[B|] ǔ�C#F@;yq6MAɻ݋F+�T� 6\ @c)=F E1fֵ*JJ{Vթ Ձa`Sz”iX )!f^Unxi*㛥}Gr3<|M@a2w9L pu苿;k99*o.U8-Х{30P{ &Xhڹ5jAP5er1i ݯR\D#y&.sWO5+j{aEGDUAkShz_9yק"(`Lė&!rR8UdTl8b>4NNIfBLdh2!P#4Wsvᜪyyo?x='A.--5Jadk $4f3MLǮ![pz$վ0/uf59EruNT&^Jzˈ>'cCt58c4E1{GW{->(uDpJu.Zӻ �U� T70;0{AjΤ{9c mӡLJO[7>OͣU=>vBA(=8Y0 cbzG!.uj]jRCiM+Ƒ$9ח6v9*λwR/aM_g!PgPYG,6_FZ^0K&R[Wv;C: ?7m uw4 3ۂuíR;Q ǠA!iqloԓ_[%CAzM};xJ5a]fl2;X% b%2iF-nm<gls!qIA^\NHC9N[ ?lx6<!.ـ*?]jrlo! ʊ)Bٹ?o@;-]gn効|{@źujJ\ ,�FJC~Ag,IF+vwa �Uy� @.c ,�-+x Yok]:n?܁etOU8ȃ[?sҖ Ńj6,yq/ �=FޣZL/@nt?+Y"utIDi:7?>٨i hw?>j5PI z Hh#tڢvn⮚ 52oH'Y# g!8G`=nRg-6hYWIA^�yp~hh9 TI-(̺bޗHvrT!ةr;BCVTu=cF Ht)\ySȘPSr tyr '׎& HގHoT )by`,f@QGpgB_I?{'1]<0,LeP$1T+x9:QM%(k.HI4LޏsM9vH\#>r,-F>H}!6^P0AO3D 2$4ma L̙uқhvfщ�U� xr77g�ʒ;*×m~ #ERO<XIҿͨq3p BWPΗ[Ӭ^GH)Хk(A",Y�*juyE 83_%͈(c=@Sk2$Q57}Kr숭Or̘x%i˽00;<kfGS! 3HbQ`z.`:;;#>,# Gd& k:%k/EU<T5] |ǔ*^`Jxya>A0}Y<u\>4,K',u P*54&QgG[0Lat'Mb-zW";&%E6;x. /]ikT\!w)3x$ޛӨr..~zȢ(#6=^`J9֏O"Ãw3,p yѫmkti@9o(O_�L\SQ1)OMywe53O]aL0͵�U� [�$sOu|t*V8}Pyo۷a d�|pRwdrDPQ4ê[MlX&իSaptCH/^ffdy6${&RҤ).^ uҒ�Cݢ<2fTt+"WH(  qYI|F{; ī>-)RKr6am8<Rƴ V FH|v/Jd_zБXd�lX:ҘMSm$"4k>i5Q=X+mTzM~thuRt 7 LE#CZRو\ڨ{dm?jrAPzsE9 cGM^v3tNB9z3<@O,kpHBx5^sޗCz)/De^3ivuhXAea4$,fwr<ID+!4`{Y% BHw_䗓rt-T^E{�U^� ajA0"�MBgȹlw8&A2L>uPC iqxZ p| 97$dH!jP1l|V{X0>)v${ֳB_s[zsDB-2w-rgWBr>vwFXu,[;CPfkxRd 9dL!ڴԿL|}@EXЯzEγij {F0 K#M )v;6-M06$�/sU"Su瑭ΧW`iC-ݰޮƮ5uz1D7'99{W<Q3LavJ2#gr:]%Οd 2ͬZȺ_ LNTHGrU鶝ŧ0$o6Tc+VZhnEq uEmDι_V>GlU`qAU͟6N bο(э9wk7�U� 8_W"])S־@؍agTC?(RPCDmctIB$9ѷ\a%Y bpB"L,En z7~KvucNtfG7pSa@=0/)yQ&,G&&i4Sc1A q4<> >W8#18*jU1^"Ȟ;e#RNƤ!*azl:"pnA1<>-r T5/21Vn+< u RauhȫF{I/C;>Պ M7kT^J'aͷUb%DSh/IXW'~O#$,It<jNI/$vϦd<lEJf_tYk[�rDL߬A?H|ĞaPw?X~OC\x�U\`� K?͹DE@�>jQs+ `Z!]/QGn<…e?*C#xIfπq_(6[R"Y NF*+Tk @GU^ch8Yj$,F-t~0;}iB n{qs V!*ZBPM^R1K XR׊Bc' GYg-6ՠ߸J#5rX4C/y9nGs 1tJ,?^(ᖯ ↢課NOӮ NÚ,OKt,D;d~L&+swyt |QQOh|JۉšTާYH127vZ: (Nɉg"wEy/(!?FcGs 2.@\C𠴈:`102,) R!g.6)&BMkх$ډ�Ur� OWcoӊV}?H&՞Sq:`ħHQ.Ujv:^HQh0:u8BYQi\!p;� *h91:9ݫ{j|gJve(bޣe҈aB_KJ9__W@m13|i;Jv '>qS堈F1M*&FcH>cҚ׉65--L[lR o%uGґsc~l�9";&w[hgJXWbE,מnPP@uPjRZ{ۏV1\LFoUZۚ锭RYdưt/mv=݆'OcfS?/9H;0+ZNV@ 蚭(~su !!ظhy,ߌ \X*oHugݰQXYdD͵lYoJh^`7^VڦS-/̿<;ŴPswu9>΢iCÉ�Ulc� f˔;C ݝߛCyU[&%tٝ\<)3}z=ԠB?!#b<lWW/Uoh%V>Ku̢ 2 ^Q},I*lH<u4 dccE*{ oL3pl)?2] /nv7ځtAp2؞=v{(K=dOYHQOpPvHH "-P=Jh3Ro]$x3/6Ynh7eo:T|D4ϥB$/q6\ |Ɣ)TKI^ap֚�{|Z%+.}fSl nW57 _3hƲбT۾Wj}W!ETױh9ժ2Be/cL8!,V B8ZoeF3U8U0eStؖ΅�S� ؊hU*!(m&T}ah,ѩe)U:ψ5mM[gȃX>$ӗ'si88rXݣ~2kCv{udTyBb`T'=W<O}~{G@ԣ,׷ $œ64G1N=,=K/ pS*>?:W0zXC&Aepk8Ħ5*]5E`cfg·Y-̟z>j�^ (F%`}Mai^1MybڒDi&WغJzgcM D@@{y*؅Kq< fmNETv*ѽ#̋D5ϻ!$PSu12ߞ7o^ˉ,oaӑǬB=k*IĦ d8ǮhN}T\87V; W!W�S� g-ɱ%N\4U,yKx36|@6"MHr`'M8UdTKq^~?R>l;oFަEB`75cp nsT= cθπRY[jq`O s{[_ё|xugƬErL)=Vp-TlF:"7^S9u5 ݬD t'L1Yb +-> ch!Gە ̓f[J0NMtSRmAn}/a܄Ifo? x^BV0L�q[`>rifrj%53ZC&bpzxc֤ G$f|kp,[}3Xhp8vW[ؓqtcN㭏5+|sl[zVFV4|7)@J8Q뒳[%_t21ZL5"u ZMw怴,�S$n� I#)e$ڽ'ew=2- 09'Ţ*U3!zwHAK$U؇޹8" "/O}#_O9m6 lM"97jV3 ϠLC^~fE/w4ԧ0"ȎF,^ekdYBrkׇfmd^i9[_^]Eg0@IGL ~kyn}7e.[_h.u~K)x=,R{JU9[zX^m4s`6y &]M) ]=g%�,c.$< X̷Dѣ:t瀍hA=x&pf_=v. bt-pkӽvTg) �[9~*l/^o 1Z?NcLYuڿ2taêO?0( ?Q�SV� y5M8X Vg-s'>ȿy7bxl>:ѷ0 zɇ8l?7(7ZYc-ƴ;1sdakV8 }Ṵ:;%pޣ#2Lc'`7Dnl< ðP~uʳyK7qz񃘍.< j&ngnX> \_Th~hb9w"g̿,IWl|0+L=gLלcgP 8(Cqf,Akn`ٽJCjC\@ a.XYQ 8}Vy0jo!C;)!7p$S|oiҵQ[#)}!>OX8.9MP&:baJ4d3!DV:Rv[p 'хKKN6xtd5ʴ.V͐z-G>H-l;�T� ;V?ܱǬc"hҝjЏXm<D3X[k cxij%%\+_mbj) i_ >RnИ $cOe=vMOy$ R;itG\xR,;dfR}{n 5)WV� e˔UY;iX>`_9<{>r0o̹w3M汃k_<}=O32WȘ\ދN?[П&{MRt ީFp»Ko62$D/@K'H /ɍJ@N!{ 1rJVH=ThV[^r5ӂ9 c 2V)r[2T;2h6>F33Д708|%3ᓰDW54!鰘9:CZ3xp4)'sXudW5,,jhk>CF H5VgStUBɭU2bd \V.�T � UB̥\a+er uDLtAם"TС [wLΐtt<ܠYj5'gl˙.K/Eb]2&d\eO TiEkRfC ]:᫳׎kp~ =~oja, #Oѵ[{q m[2zmfZT4<0@yvGGO#vq['F6U%M !3Gss=,.)W8i%eNZ)āM& E AT7!(_2G:g[I搻8'�XQ0_]T ld+OR9jp"b\}afZt'lpCɲ-qF%`%p9 ց&rүkk*Q/ȾI]?UKIΫEf.eb9&0qIQk gVo8;U9޽�T q� ǁI�9) 9Au6Fg*X0~{;hWo&7²itp7& .c1R}UGC\b0jEPEho VZ1Jƶyg5غV&bg {AH0!M=;^)߾*#t2ߴΒЬ?(WaLT7quTzSuNwnqbҀoҎPHl+  eG˵# j][bkR7'| t0]y2qe+O̕RsBI@ C;yaA>ɃNǸHI�Nx;mIP@\˄y@5LTT8I NjdbN4|A;}a_e& -gUC4pq&&r{^U9ri9Mya`jҎlF5Kk]eq$�T/� )+ 3 A2M H/G‘ב+`rݠkR _" i"y;($Qz𥳐F%-ћ/)#0.`wI9m&׷k$}A1zAc۱.W?jl=i9MzSթ^fZޞt%E`r?7U\f2*0xY*Ce^WUæA{;"C_1v;(!-^UK'$k(O"WA6Z>;KB_G'2nGyDLXt>+*}Vp#]#teXYz z(m9tގ]T3ZU[kN<9-9KO87[ϴ!QXStƊOrǎpyʡ\*59XKʘxw$K%~Ur^zػ.v+�TJ � U"Ԛ1�$WnEx%Iu0_tWr 0SD%y5t5 ࿗89Z(+gC%]lDG""'rv@!P@MtʖӤCި9A1 s4Vɯ22~V�F.X];Õ%<C%_=g5M3p<[ Li/:Jr*HA 6ecNēZ>(GMxsx$b1Ά2 ƴߩ+9p/wZe7qQ -DvC[h/3+3=g.7ڗE\fYTGᦟ-cŊI'l6'ĹT'&5 fn5fm/h^m9l!Zr[\}mMYHh�TZ�  `}O�< Yubėf\Fл5,֦v@C a@;"I HQ4\> /hw$Ff_2] ?g|D7csf 0y )8ےm x0))24L$nm5(9MuϤh%쏇1o\.#i2x.2`Y/s|۫YerEC* >@5<Z3]yp u7k%P?"Gf:KֿOU;>G{6&kZM4u[^yS@ʞCФ" %Ƕ+8^D['tܷV%>.inц:>fBOY�쑇m _$.sgtVgpޱrgdKaDR6L ` !gģ4&PЌh sl,P=lG$�T� bX/c.8L'vJn_ʓ8j^^*Fa Rd7L(BTXQ2W~ueN |='<<PY4?'=K;:<FI`)nR ڗ�ȫg+^7_Քq y,O`VXD%]%*y"oIX>Ê]ZȽ gMrF_9?2iYgktu(NkI:-Nm dWU͢;riI^Yf;΃uOm>AM 3G_-Yl4%uז|Bd.l[9!TzLY&zqrZvyn+1zOH rY]PӖzI V `Uv>\ͨ2�UZ]#r ?; $qK4ͥ-PoNEUTbeÇਲ਼I /Dž+`=9% �Ui� jb~^$V�b M�13ٱx. WP^==X*ɤɺyI hs"1Q)k�G$%"~Mȶm8VyF[~,G<S IH=+|]gI¿JHKݛU[$X̂*X|t~e1dD)jG|{>gE.z6s ګ{L K N1/3:\}2 j_|R_2v ?!~wBNKAj}{ŬfOvo >+S\@M2>ZxO�zlb o/q? G^B ~:sY rsh'?l7 2Z!ʁ |{EO/N=8430nb&/YDw33v rQIzxa/ls}вIN-t^xat�UN4� LZkҧ {I=�D=]B[ۦw6n -ؕ]~,͐EFd?ǥФe;jF8w}.|#i_4-9M]V)o?s^щw 8@ kl44 >k]~iܱ"7TFg.+uBBVl@ iHkEdr8Լѫb[J>VI@r;' +b s1[pYquC%UvvXy0uo¥궪Q\|=DS5V1y Ӭ�ARp!.=9!bfKt?If%@=<n,H[.̢]e7P&<L$C$_cE%܍NaE˴bkOECcm9Br|k(RPF4fv_zUcLכ-T '3rC8'! |>,Hnc8�U[^� f܁O;i@f+MĈw voZB{=6$Y_3v`kS{|o�q{UCVix)$Uq'.zzu=1F$\uM_1vRX3HJVX[|1.^}1YIu+S@{ Xdd˩" X ڣ--Xh>d|JLԆ)b:+!U>ɮb^\$ؐ$pTOo pvKR{R L%U> fם(�YA5ĭG43ZC %-?cO8#GVu*M9q^){ؖɏ41om+@/ l>8@&.D:c8~_ծV>B7D�ǚ*)H1$(�Ģ,m;}B vA0f'M Ț UTu&d @@t IZ ߚRLriS< QҾ³�U� r'Yw :o@&<5Iܽ` ]ƳѬ_rߴv5'/iF=@ů~#O*ޖ+ -]alSk rj*2ojT[}2hT*ՋB�/ jbǾҍ̻G$hB; <?&ˆbgXM"5HuR?kcޔrb %o)'41OH=þFXs 1aV&]ιm5Ҹu[+E.ЋotJ]G̸9t{M‰fa^*F{U`{;\$wF S`Q7}k n "wVLlBʎJcb}w'BU:YA; $%IɈ`@oz n5iBBZzH6ϥ֢e G=)8*ZX5)982@Gwy/_Mz'' d3F�U B� >~0f_3=tJ&M<K^84討ϺfDsH1XyE ,z-CDŽMYgŬ)W{Å5.9&UBO lIY@'{>2{#;~j�0W'!;h\#i)d<p}WxZ h~5oe[ypl' \90dUZ%qE8%jս'Z\a#jQ+orTo) cn9>ݧ64L+;P%qFJfh!\}FW#n4,HU.4=\_`BΪBj- #x[QDŽp59>f@*HcT-) )^>WƋyӆѹ3hD\0IX[.J_ݟ=Z^_kv&f=Ȳ!|gzĐʚ]аlr{7"=Ԛ!kR6L%y [n^ l&Pth0.DPV 6DXik�U I� _UIhQÖ1d8 ]䷋'bu3Fz^+RB7i cW$WӐ3 tr15s8df1#g>jwDǶqK^sӽ%G󨕃<]KS3p Y4g:!Β3/KW +էEaUs0{pMsS$sO eLʞzS txgV:�bO8^iY D*1CŰ{E3W(1b:7%n,UO 䒗a񮹻]m̟$T0.9j*S|TEnmPMR%\: <ɞWzٍC</,()ۗSB�ͧzѭH"_Vfm*for/Y(%F74JU`3D;vL&}M7FDnrR!].c,C>9E#\6,'�UF� pJd-qY6 G ts3K%!|~,R0CjTkCbWy8kj`YswDb VA'FYy#*3Kfl|N2'㨸#dZZOUYLՐv6`=NSdZνAűsVa0j7�,?@]Ao#o%fʘ5 ]n͉w<I(U5J@LƣstNx˴jAI2r quyPl6籔qT7& a9Z(CC@@S~){M5"M]\6<?vϴ hk ,wf-ʛ'[;|y%s0a߹T mcBMt1 KRӁF$!{Zb.KxKIÖkZnz$rOGp_Re?H͜|,i k* N^2pfn"gm�U\m�  9HN�Rl݃}!>iFwA"bzהm[u)v@V%sb`9.c9U\ےCdny( Yu@o+?wTl|O�t)APAKF>8a{Sqžڰ{A9*22o A 6}V0}4 14FGY^_{@Lh ]%T;sJڣǚj_ U%>Df DŽm̏.貦TQpr_MNQ26;d*XvVV-y<7Rs=}3$C8OD5gCN^&1R?=r'GKMsoHʑqhKv~YNF^ "##|e_k  Bhhz_!gEmq*U珣wt cx&C^aή]ݰk8=Zɸ�EPR[# �Rε)� Z`{u5Ҧ'|eNZeRR*!s#S R#<5CTgu܂c(@wЛMe;O]QG%_k^PZ)č,㩰̱"^77Ͱ籁ey %Wh)"20kI MЅjg7.|Kofj$�ױ\Vg~?&wRj8I/v}kVֽK V6@-"e,=+Byd,،ZC`op?4~,*Qso â=gsofeF@]pגm7&=?gpY"Kޙ2%/I&?O@ h;}梲 3d>iS+@JBėJ9rȳ7=i΍|7m|x!_Q [oN%;֙Ee{>*4F[Hj/twktIěAl �S� 7Ҭr:e_B ) {I(YPm )e>ש4yaNGFZҴ'n(vnjT<5j¯Dz n> ۇĿ )6 !Q[l|k V$Ph2t;D{N %-8D`) w" 7|3~}$S>T{ [*s3t_t}HF*M'Z cIжZ6| $XkMvDb@/(%Xg =#ern=~A~L뇒L~"4fqlõ3,{1(I/N?{2@2aRq_NhG.x8Fuu2׃KYc F:!f1bNS[(1~ls;2vMa5э}&LظPw+8y& T �S_� 1^iuCVAa;ϴ_]pPXZ<ŚA|/}oSO_Q9V# D\co_ceR~62sz%}PZ@n,$1CjzP\' n:ԍ_n1Ғ:'<i#.'I@?vQS*n;_؝_ =oYK˩hH*[^yھf}ɥ7RD iv5jbļ3o@ꇼ꣊ļŢ 1-=A$tռxؓbh =5=BbݞoFyQZRcD-Ko;Ki-X0fAC`%6Ν8W M'Asr~k=m' &O06ϵ?'~vh}KJB'9@T<h-jGsܾbpv�?e޷yKͣRwߜ=WZ` �S(f� *XGBCD62Ifhj>^CESDqfV.1-rIw,n*  !m~2 #(/G㥍鏂@u@nϨ=~Z_]sH@6!YP6BUbr/8YG%g69*h۱QFm\CpBN;~\V `Ѵdjp}=d߳$BL{w\)`6 FRsSo�Bտyk*8?Zs-w腣*UI}bel=H7_/UXOw.b~A=Ex`2GhMBiJ rHdF)VU(£-*DfK ^&׻=&9th9GJL B24; OXQĹ>yH%#/mSY_>??/y ('"| )w%IF1ZH?16 5z �T:O� :hIY|#�o P %#*LL�8k˜{[�3y }8F&âd%p9hJjK @u'5<u_ RMB$j9R ޶l9n) ;Ss�V!ITkQzFw0ih,jMF3U,3j a Og1yxn.yk]u},8)ɺM?9Sf.P*?$N+Āӯ ::<:C@ZLilr@k˥^Vծ$!!pa+\3PuK@t?~ia?78 ȵP1Jz8O{�/C;o,Ri74#c\*JԢ{0ѲT:-Ij6K:v{0o70o=sCߗ&4>Iw;nn%^Ʌlbް2l+hJU_ 2ɘF{l �T� uxkIOf:D(jsp1.M^wj]'ȒFT ,"9DbU[JQfRٍ;'4FhE_Y11Ah[S ?s[s7Z!y 6 NVY9-t"fҪEZJ%fӟ^_Q]>NgXp:hE~Q zXԬ±e!Td!e7@Ֆ1R5Э9{e_ʉ^Mcq2?S`nw' (3%G}]mPV6go>"$CYȃ'"Uܟm& aN\6"iޕi=c ΝMDfiW~.9~q׍hMmIkDwg[?^e-.QHF?b;_,wKPaו3\K*l+ւ4NY �U � V3wqZ+~~-k-4?NR|pOER R17;_KRY\~�-pл l=] U</|H&Rh_bz,ԜS*5:jmٝ=aB31Le %MUԃHk T^վM+<+ ?O'POA9mx< 2tg�9ol1+L_sMWV^W(sh47 r6z ښ`%?$K>W5B;S%Xth\;.ܞ=gjQ"5D7B0u݇Y: 8/*^+be: ;:Ӡ j3"5E= @$KXL\'Au.ͷ_ҕ j"ʾWBIJ|=N6ȰEŴ#dqCqsܺי$N7כC}NH?=_7%r_H摿 �U5;L� ?㘂�8^/8xaqaXy J`䯉@qPB1z5RwpvsCS97$)~HO:D ]:3Wy;ކz#Es}À2o ?ؠqmسb2y !#B~׷Ll>*'K] ]!bUUU53S0M˾⣨n͍"Zj0fٴҴ)K3(J(И[0=@D C\~~á#B{'w}bznWʨ= 1{-}|fVŴJNn& ~fR1 /fSIgBoA%]{_ƘGKok �N=C@0s~l *Nx7OۓU01jM/THN]yѵt kJɫa8ׄí=}CaR49& �U[� $hh7C{ot-u*Nxn!'`mΞ招rxiZ(kj7DrIdç^"5]Qy/7N|KC(eDkOK4/Tv8ƣ;y]6X*؛--3 wymi>n㨸_?@IĂRcӨeyZRC!;ISԒQ {~7ɨiOs۔k 8}a5< D!y7\$V iz3 Qu粆ҀC0 Ep@K6ITÊ� Hʊ<|+#]8(cYNئS^ E\ U4 xjݝcv{|`?TMyU$>Utg1<Fʦ^_ K @; nj_`2 ˼<6)Ͼl�) _>ź ɟ0ۙBR$rc's Q*Q �U7� D:s03DDWsbnXjmL"myU)$#x/)&| 6L(Y=Vv5` .9)X;Omg9U?ie4q^fPk^*+j>hrbAgf/&(/}'~& ~$2?dN̫Kx@ gt@]fq,T0 F t̶"b%ۈ8^{ eqλɪ�T[cttw%uL+91P6:&fIt[8fަ B( n-ظJƵCH( OCqǣmҺH`q~^e7@d365@ 1cִH (<L' [I ('cτRԿcg.Xɜ8BS49e> }hHauԎ&;e;eTwʭ8lT9 �U� `LJN ֑rqk,̌eW0僴jc$ĄA]mbzˡӱ YO,<UR;PVKyzRL.4%u`|ٻ/&y<p^Ao"kw ޙ7i9Tdޔ>ᶑ rInw2ZI u8yp� 2=  Wx2`[+2//c+3tGoZop*Bx)_^qw P]@ʚTIJy95{ӲS &iLG&|F2d):n5Y )B8RV <pYp_tQ <qSH֯O g+=,+Wm/ZeWO7ތ 毅OS\sʍ~oS%3%d TkOmYvYOBV ݬsH>inc&@7ፐS'Cai㽝&?($(zzɡ �U+}� Ƈ_5AE@@dn?"3C6dD)/-vz0zw"X =+ ;s){-oΖS]>ړҜ) (!=Dpp(.aROS>}y#UgsM>ڔ匱đչ6IwM*b{5MHOTfalQ^+!M E Gl)t0VpGDS6HaXZBo ed<mI}1y܂gT Z^qbO[p6q%{p[ɿe%p="4FA`A-B&c(/yR\ cDs9 $+5 jLInqG+S9D>+ QlҽjITy:bq!ZͽOkeҽ*28pQV"AQxVFroQ];ռf.bbQE8J4~L�2֍AVe{G$-kz �U1;� :G~#@7)"mFp �YЕ0T'2؛^; ;a3e97aTb_Aʈgi R.Gc 0Vm* ne$WW>rb HY@#\\l̻׽L ZEqSЊ;åzh:RC=:9ߜ~F):;p ihlʥ>7#¹.XaDHg(@\|i)Ãi1Z=?>؀נW1F\dz!-r Umr51؎A;qWMgX܊ =߶ZyfZ_L^w lknn;Ô=:kt]"Q$93دCcjyO%P^ є'7\ S;&4&T@.ч: {(rܬ\xןdS zC%6'49(G5A%t#PWR<v8b?QHj^;PƻQ �U � 㭰P`V6v�=|X!Ơ,ؾ/*D.?|ѧ<j#Rxˠ-#[c71,!ʲ"0U@1ϗn_y}}Ҡ3{,n~1=nxmz,W*ҕ <`0F 0ĐB&ubrzQCsegΓ|+HIc}g#x t-iXbng�Pb#V)K|"ddjv9*Uv9~F9̘;1]slSi9Jp -7}XHb, XCbXPQ L#�;Kp\t${xn&d^f 51="Uű _҈p,nb]x$A\z11)P^I FyMw%Q`< z'cAu徆$ )ρ%��˄ 5R:K?{68i2|̕(>4jU^IE6H!ޞ:R*{#&sx%k �U � UB΁SuҚ墼Ik mbA tEQ Y n8`8 hUJPs`OT=Z26"m3 (t] iiTC`[T�кJBIdG3^70W&8M\,?6PF6oN70ct5 ];glǚrm_ٶu2j[ <l}ؗ2elLED¶E VX£Tl LRWo=.. U!3vs;}3=Tb3$ـ=_-zfH^t$,&)k5lITc܊$HC`%N7иS]䠓*.anμ@<Y7(�xդ0^V("grF$f-xz�_�C_ fL`GuAt%<+O}>wKK+1NG}eelUs$~.׏5c �UϞ� y>b 5V%^I\*}A;)GR q2?7bV "C ,JUhɞO]6&Dҩcr`v& =} <}Nv!kU+C! NJ'C,_<-&2df60|*^6SzZ7:aÅg4ofpK]B)4oĝ7<1&B":a/p'G㷁FlFDDSҟw!oqyDwj|cxbhN~AG Dq*ΈA:t9&ӛ˲h L$@ʂYiF" r1;Xūi}2�^cu;yDCKݢwD� FnFLKt=s<^!"L�yśѬX*]~z̸B`88<@I{`F!tH9(c4r2g7(@i#�V,xgeN^ )d7�Rni� {olw�f 9~csĩ MGʝDÇ]V#-;t%X=v ޴)k^2W7W' 8v}dh^^mP~wMq>>{v_%%ۥ 0dr|/u/l"dIoG\R#h;9$H%X-砍TK!cfxI|u&M(N7}>eN ^]G R\k`_/N9} {Lc{ݛ67>zooۥװN@unGǧ}E_9I~+cF{p}u '-0?G~nlz.Q9>9?~HtuG/:@Γ1)~nNW ځ{X^6 +X_Wb`Q(E^OdU@u}e_ r3',^ÛÈTRi+VC �T� (!Anpn3 IIFg9롐L@U2 bAa7sxp]QB$ U~g*@>]X?$W6+cE0socT%+doY8xD@!8$gEil5,oe-a6(O4o#% #w.1GlHL~bLjŠo;<Vh)?ujQ<*n[/$De."Ƌ<ǎڦ"71*(lk<`-.ڳЕ_`�!vS̠jZDHq#*tRiOlQMrǍ܇&tƁNh؞ڨMzb:P{J }>CRh2]K6gHài PaR >y坩z - ,W*tg>ǹ݀g{dwuG%|ssFYɯ�T u�  }MSN8_fʸ(&*bLKY)_X7ujʨzz!YJ !3B,ᨭ@߼g'j3lmӫQ)o% i>SCuO9/4*#Ŀh]:HڨT^s_[we?A[άc?C�;8&8T9r H-NӦ;+.+nClB1 )PtVƤm,עS2G[؟P)W7fԲgP>,ĩx8&Bzv n;xbki-("C3jd$8Eȣ#C\#/?["'mpS>Q(<p*x5KE?܀"-1}&VF|s&).t1OR $Y|awʪàPiLt;Et3X},֡׮"ռfc.dĔA# "rE8~YGތ .XoHKHW뭘p,A�T�� 4whw�i3ˎA9RFc2&z~/a+{Dz/Ժ'۰SRgod r"y7싓q #;dͭޛsTw%<M|>={9Eݻ˯/N^.Jb)/HNЉgq-o![fiQf]FIoj>ĵ~ZV2jXTГ  # 4pNe]&~4(:r~X�8v;ΪX&?Q2/&vq!Qaڦ@, 0N=ރ}, Seg<<i9`"fn `ԏP cR hSܞ-p &it{z!i2$Ȋ׊V~[b> b:5ӥvPT90b:_퍈?~=? m.\+X$w= \C �U>� )\ti .OL*krbJo+|]).JPFS$oi#lM^\;D[DIl"ѕQsv1QF ]> REkr&iZcl;q$qP6饟1^~o>j`ӣ NۯD2<mgNZ[xS <^^ޫXyIJ|Ixq?I߭#ZL~ x#@SQ>1AAS7j4KݏtřA+-^83^et8E;~Wp_ܜ-0FR˝?fM; iC)oe։>QFE*(Lp,h-F9O¯/}%Bxh4n&>Q뷑(cˡ{P$]x|nK$�lCNeLݔW793\t9]'T~Ux3\sOv?]t&}s/~ �U>� /!wTi?�q\^~J%og*a-0-z *E..qK*f1v~;$QՑg[Ӓ0D`G�q }h6-/BohPUqmuQP `{X9(]NG`-x1Un?;TL}Ik%j~M�^OMYxw[ʫ�M] V:[n,$f\{P tA<Wjy2xFI ѩuK2gcĪ%xU"( SKP`-ּ=&Yn.C ZgJk`ycx4}E@ nWkv0![3CQכ`CJ%! B2L(X9ٿ~11ݢi!7Xb7Z]b㣼TPoAou6ٻPeTceN2x`[ bo0U}/8!ZI 3!_vŒV0 �U>�  -C�U0AeDbQ:N&y ,Zl 댬*,9V5V/8|!8iY/bu ,mƯpԥqV|:GT;uNI^Oh~UPDT<eoBdڭ5pF^XP!I 7x-:N[ENJ�9ܓlO˼%7V{|wbS.*n9}N{f\A.^={/}H ^H(W#$2O-鉪4=8IfuĽjd؉XE߫NFO|VA˃_%#Qތo.xֿW8W'?<zǣcd\fDT!kh{6<H-tl3I $C1AFm|Mŕg8L&trҾ'=7癊cJ*'5 �U� U8 kH2trl2<ύ"yZҾT /w̓S px yvͷQIrG:2\b?d*51&OU¥2R LMSjE|tH!.%og;5\X,/[~uV~B_G 9s=>sqIs>�Ǵ\qdX/QUũvR][L)Vu:K.V?% Qd+lJI͕%/ǦYPok(.#-N=3Xf u"ay>qQ\Sb<kbzw %sfq~ æHU54 u!p,r|A/u#v$P-d~9W oSI@5`ފ8AzGlI0\Rs[B//3^x9}J|s|D<d @YKX5c"� S�� kC�vk[P01N97! eé· [ ѳQ}.1Ef*ȶYY&8K /)Bm((:oy"CESKfqL\'QV;^eцbѺ%fte@7V۸WȊk!S]�hlQaC!4Dc" nlȽ'K$ SD005jA 6ws>h/¨rXrkVBZ$JE8,Zrey:<plDHrpNn3|`_x.ieO9uT|V:gD)F\dt:R`Aph>$&W {m6t`^ѺZHy<rz(eQ*]V-x.:La՟<D߅D +d5aTo{ ]4ě? Yb Ԯe3^!;lR< > �(   �T ?~X� 9<b�HYtxjV'RVLpprJkM5P+*#CDV6kҿ}\T'n=rȫUsC-< .m+Dnd9#ܶJލ箵 _K~fqȄ\f?pQr I cx�5v'N֢,x"ds6T X/F/5X,[oR*$Ft+jifyfQz.>k|ɦ |%#fRu\*^xCNk1,ANG;R9c2`#)ޣsGnƟs>i )?VO5wфOn vH\yua? ˔%-ǘppť KYo_7gBi;!a̩x yW#_ׂV=z9HdI48BW\;F;pP 1 H4(trWbQ1D �.RK'git://github.com/infinity0/pubkeys.git� _@�{<cZ\L"˙F< L'XRM]Ld N\y2{4dx|i~35 FhAaC 3eT\%K%>'bVmdMKɆNIhA=a�Aʮa!"Y?1j!0 ;�%EM2O |O Cuean<vZqxY{�䅬�5cxFp(ZI(-8JlXڹ&>t<q<ˉp9|"&C 퍖S#|ҥ)t/+M=-ZgW5|zblf-)ܸP=.|cM_^{W. K=Ql.?_]ޗpʚ}/p�9LY6JEa{hXa%OKr \uɅtTԬ S@~MNJ듨yܤ阙ZT.gZmo+H �2T+http://www.headstrong.de/keysigning-policy� !چ; �Di\\H |F0h1Ɂޙss\df#[<OyI16i}{D*F 0F:JݤD:.ߘ/fpZs/x%빗_ [쾑tuRG%>=~& FX*{C.NpW\"*v^5ٻ:.l9|R%"ɀ*稯v%g=,9|S*i[V].%H-bqp ˥>LuUyYwL ſ=6*`}`~{{.,o:8_ܞqtàNM @DU�Up|„�"Hԅf Pe41jЙ3"8Gp/,IAjWK3R6v24zr*m1IjM040RA@$-aʞזlssqJJsU �?U۪8http://nicolas.braud-santoni.eu/gpg-policy-20150222.asc� sԀ�:#s{m>rPC.oFޠ%?CypȬ^6ols׸cX-dskѫf\&U8;[Ș{#4ϖ 7|081ct)-56DtA � CV!|K--$AmlvXSjW() Y?\))N٥:z蝯ɵ"\َe ;Jê8EtK* | dFA^4x#2nűO1bQaXއWʂ'A� !G($jF9i-/w74*åLbjSpˬ 9 剆�Wv AmP&Vkfc ѳnXoZ_!2QϏmik(<Z=F�޷XX1)GoBm^B+BbӤ s4Bx0HCm-o'�T � ŊM fT/*ܧm2A"Y28AR*1=+T'Ik|Z9u-'Aɘ+Go[ReHوY1UB &T+mO&սuGߕ5(Aii/-D1Dkɸ:f4U+?qjx�sb=\}B%E-/*Ǫ dsGE*aҚW$=.8Ejü\UE>=h?qYX,uaux􏆴7A:_�Z덎a'mp ˶VЩJHE^f%TѢ9D˦𤜗h]7}0S!mq{۽Ad\&a#kHxQ|g =bDwG q_W?1%q1Vw夰[-]GD:fkl8.U*9%˰0Gtm2~).MPZk+G>IC;cθɸ|}VkDBhjVC^Y0#fKjbLD"`�SMeej3/v=Rs6| Lނ|?~e83 >O΢; tFb긫ZTYSg0?Y^˅!nH\@WRSe!s)bd66-v2Ax-r8(�E*#-#L̈o5開%pT0Lqq-Puo.Uȍ@na(Yd}vD9<y-@ޢ{7>ӨObRxPo.U6X"'cDBZ`Arf"rUV.2|uNzZ t]F+0C?_FŜ Nᅯ跏s9mj5)x}`-OG �U#� kАr ,v̴*xHz8L%Pt14C`z<QR=T=7M1`tus ϘeRD!5)!Jy!@LRP{w2LP,@ 4BJo$%k` ONnD :Js:* Z͞ B`<'ҼXyfx/L>'q ZZLy5n]K$ XWJ{?Y櫁|jѻ%xw[p2"-F �ypJ՛ż�]X\_|bjUi(CWGMea~|N>8�p ÓYaȄP^: Hsa�MZAv=˼$We  m\Rz>d[I< )mn2jg:ה"@d7[Rls]l^| :`XX!sv*P4jr4\<)}39ڠ}ׂʥΠj#e//_&N엍{u7\& �b}j`2!&`<9 h|Ms1+ ^T^m`bw͜h*Nܣ>\;8˽M'!Iq]'.v-(bn@ &)&%Z^YOWi$>~蜒>殛[ZS#%<9>D lnh̯6F\*[F|Cx,e`}LU? oye%ҺEKzV1WĶD/h;zZlf/3@!CkSE/}s$;frM1,LHš8`L[ۭK$]{6ֺn9˺K l3ԙg&'8(3ȁrm%=һ4Wfd({j~pSh6EfϺ"3ǯQ&ى �S(�  F1@�0i#үcW`0@ F*3E31{zxp֐Yxk~:1quPib )ofy@X~9k6d.b$$? OE{M-31ppY (+Gp1k_Ěƃڙ8VJնDPp韋~NELzс6ha55ΔfvǎŐ5h8ܬ3S d s%(. R9'M\yQRu^԰-؂ #F8YVP&@'[} N,.{PN{/U9* &-dXH~eT.̱Ubaoаw!g>#]gؐn&՜TPNkN DxFW��'ޗXHG3=wȽXWZQh>j^[aw4dfղ-`jXZ>@3y_]sPf VW#smN~#,̻}v7-s"OO{RjC1%:{&dxKonL ] >Y! 0+I,x�P$^y10@T+-›Jh|DgW]MaFs1O{ʪc4ש#8 wI8/<+5E@Pf�fMv#o<jgf}S$QtT #Hwg'ͽMF:jo`HIʠ!齬f7o<uh�sP7NqK]ER֔80gQ (Cu-xd̽KjLT=OGm!nT*&j#Ɨ= xJ5',XM9eL6x)ǠB:h3fjJZML݊pq 蛸L&Vxs/3'31xvQ<*ጵm䓱5yAE4^[~ZePH&\IWI$f1K3'vzItZLyhݺ-M竛gk./Q"�/1 ,p=] ss۠ݣ:؋cm9UDL|z l!L?4FArS#(he!*J"@KUА,L_ hitWfQ濿'\Bb"%PעT,F8V-?�?+ Q2?%:䭗9}T .l-h( H{+_APJ}7D K51# ?;b 롋܇RhRlV^j舵wy مƂ#_u0C8:WDC]UB?{Q84kxArxDuٚ2~| #]-Dk7=c<F}S4@szAi;B_B#9br fFS§Gih͖NV>9_A#l^ȀP%f�p[w4cWgX0G ]+yo*f}نxZ3AUMOsQ ;T�c rR,uþ<uw&mc9g7b>6i)ں./ڥx�Þ8pfi10@Z[67~0. 譩5&v@`I;i9%v"Px|ĻfA$bjTV7t]^qHly͞L&SV4p V<Yَ]aŀ<Wq7}\_\fS)ň{g `\M+H((lj0vUb*'4!t ~$b0NjiK03U-,{`'ۏ mIoݲnѶEZBzF�VdHo� aQ٦!S&�cRO_:I<Q߮(T�Zɇϝ"._foDF �V}� /|\�]Qi]QVT�$ iDavzoj#݌�V`e"� c!G*2hyٹ<C>">5Mըt@p*@Zr%= 5qj@⤛W_3Iȣ&׸7C!=BE%tv\ YĊ8TJj!6qS\`.ǰpQX ~q2<ߗ fpO E?]7$V݅ÆՐIcY Q(u6eRFX1<.Q}$-?6\�V:� 0_wM�qR|o`Pzr/\(hQg,d+XYR\$)X[ 󞠗¨RE(ԩC:^ hu K^*CJ�!u[J'fb5=|)JVR<"6^ tcRC_P |Ilֶ"_ kG(ЖRbdjuj.xυ~:6;Qu@+p¹�h-|bdig+r"� Vea�;� .+eX{'"L$ZN^{(W {hHxPe<8$im|4d;#2}$%ձN43t!29pn5ZP!z$!A2;Tq}#g(w1Y}[{%vqd~gD QَfzC-^f"%0~' _(ji&۾n QxL=)ۉ�Vf� >'1X pQH?^Ϸq#9 ?JТ/ ga4!R3qH(KL�+̻{ZMP{ʭ髕Gcy)#o 9;O1荓+00EHRyQJjVVBHTU bգ@{?lG,0 G�k4R0GnUI�{ea-ˊX*~> 5Ja1)3yl,,pG뫹 i^Ͻ&!JgY<kp(o=]{#<д \{ȣ6T)Fcw^yAr*t#JhBkY5{x/* iA4׻4n]/.-俍'RNƌv9E$b 61I˕FlY<tybl}SOI3*aVpy pF{8O HԳu OyS}J<V�Vi<�  g�gc,Pmh8Y0R~;+=y:.؜.qݶB|'wH'KegQ.?$r\dO+u<c]d-;qY&q@ÿR\:(kzG�<Vya1\gwAĔLJEZpycl]F<xÁWә=5EQP!׭֟_ILOޞu.MƑA L<k4lBIi.њ0.`xۑl@ Bh\/th&[z  2h)o?1b|ؼϛ-fdP.Heԭa?&;=(ac5=u@cEobM:l~B̠e5ύt&wʢC[hܛlg? %gP9/F ?\Uɋ1vCnR[(=^}~\(Z̋esNS\:AcNi�Vm� 2N/h<G3;9XGHwzSaR $a$0(gu)SWdr p;1zRC�UYE]H=gv^u 0<ړU)rAD e\ XfJ,s6Q'ҕĵEdfgpWx@%YP ͠0Q[3Bs'fiv5tH`4.VCdC.vdRwo+y[aiɮ4$/iQN,gd?{ _碚\IG6+``" .iȿ�AjMU G>/a?A!zYi)tt C擠XJ^9@qXK4/*iOf^i z/=kOH�pՑ 1prA@ȟaktmfE2`RʉSXXP[-Km?qRM |wKSkiO/rX%%}�V?� E  K~�<F[>-T}Ua>\<~:(b+Xmj9|z7XدDH&L s>\em<-槠g:oB?LO`a؂oF+I lvd΍^03H?0AT4#O' 7jIZt Ǽnj] &iڪJo>);(^?oą 0Rz%?A8Nd0+lp0&sP<-5dǮc\"mmoFYwz̐͸H|m|ϣF@xnxѱysc/Jgbqb2<0/0�HZ΍> r2MtHTN^8ߥCp?e1hGNm\-4� u1zL’`G p;1yǾGǢ\oEpjkBpa2^T3J::}H<h�VV� *˝@�瘟e3) V,'KLTjš#-i}Tw\QC{6x⵷eiٖp~B.p:ת;o$  ۄ Zk=7Xы)(F!JQnVlG!)yGتft^?Scc^!(&~C8a|sGcB VC+O'y)oa/6- s_' XBNk BD x̭z'(,c*_'Q,b wWdFL>U0F+B89MbD $+sxaaCL:fx+0i~=CIچHtح-ZŊ)vs:RUQGl.g,PQ1`E";/ѣ>ޒ<°6b\G+J *@ZsU4l1(Rxk=:;}=ӻQlj�WNC� Jɽb{U&v 0i`;,urw팷6�ʬ?ĨQG(-? 2^[}߽.zf~E52KD$F 3J')xwK]lw| WbO(p$ ,  ' ȣ:Ҕw)]_]X:]? \o٫s.(5tsb<Y(֋HuϪLh,gB+ׁŽXzFn7*dRK�040qzxYrO'e'3D_ dhfƵ[(9%So^|>;YcDsQv38{ ىQ'{�+HΆ݄W_1>5ƂgMAFL Hi^c)bf:?$Qzj( +V7ݰqEPf "LݭScY\PQP6L*`kg01Zz5[E ɉ�VgXX� `~ %+p^okIf[ģm[D/,ߙ^Z]_%Acb8U]DTeՃTVcJ Ќq6/x6t#< fv"Q^#!F"4ZU$�]V)'xbmBipgvDa6l rQk{n xO#Bc~xX'7u}[gqPL h BEHfVkaSBw`<~T9bBBzGָ :ywMbQDνH~e BSS~*tmۨōbZ77 ß g2>5W2ͷ-2ZKgp#,YEroG6S߈RBCƷ |[4v[k?ۂKcwt~UU]uA=LQA*;^uЍI|QUHlTYnafA؝Y8�V� @<&W͙Os�^tVSfMB3^` xT/o~0ySң�7 D#@ ` 4t B?1 |4VI, -Zfǣ %_xcM%$ݍg6갪E3Om[cx5ԫ%CQJ'K)�{T!6¤-H<tvhV/zUF?#Py8if-.JsZemR_tE9o<U΅ڷ})$ u|0y`kʺk>rr8d8bZ-_*Ji| DVo|^Գ؅ | d3Ke9lǥQ(iq5?bąp57nn7!!#R<(9-l-0!H\gf,ąl= u~ ,eaҵ.a_Aѹ 8@.-9Fѧfqf΁3P&;{HPOz�ݍD�V� e>Mafk'K.aLq5ϐBBSt9UK8’ 2 C#E/ u4b{"hyݷ;2a]$VX6M2#G'%9'oYHXcj@C * dG( 8M_w9暨Bn7HMP.`Y_y#^QA#fXE$rkxtIZ[K@+$}Z)r�G3Ω=fN~2_EVw*VXٷ2 ](fm h^1+:ut쪎&e72փ}ɬ)H+* gģL٢kN`YBà \b_@(FsZd?lQP,1�Fx~]FUnCT=i̸0 l /) d�u;[SHQ^,@ Zq[7,\f_4 P1?j843 Z?}~/+Dq�Vo� q}qs3:MњiI܍37s0ϋYٔ0w7}ѽ4�y͸3-f^+R^Q@Vp  !| z'@PࡣWQ%$\`l֦!} o#Öu=٤Zv]c92TW[9xr<aȷ$rhU0òXwFbəv WMD&73gb&K9ȃl+ G i/sb~N譄 EsNG&>$Gߛ ̜eqJmLp�Fi"zAO$Mjkz&OE"re2ߖ )iFA(\{_5&o5 Ɗ{~# 0+bdBM|Q?u@Uw?"e3H ੴ0÷CsI�Rxh#)38 Ѷ~v 0&A3Yw]g ]s@ye7[DW Y �U� &tF~J\KPU0ݳav&wZB.g|cޮO٢5 [\ƇQxI#l77m# ϛcD憔!3%?\B͚{2m{04 eK* lw�{m+r  F:$#<=[Y<E.-+Iu[4>'<',Zat/Nr)xy5l@cfc+C[yΠI 4vi#&~5bGaӷS!1<C#BwK3#TUd)_x1eİeG)#+` AA^̥-c΂q=Y0dy~'吳X3wj)FN<nRl/\0W^w2$|>܃VYMX|=tf[9[bb>Ȅ1A �V�1� !xl((dIHڀSZT)@Z恨$ ~bd{ރUTMw|"ߑ,jǨU=4 L'yB݂ WN4w'XCi r9wbHBc9 +FS}{lt<ޯsU )̃IV sa+Up+I4{6hʘF&(4!<t56I g5!N&H qо@W+kgvMRY2!s1-Mz͛EB:ؕL|`<\kHNQ%8/(=<&-Z;t#XǪu<tB>bQq9/հwךEKf>KL& nznRSqh5OajUHAR%v.818"&GXa"�Q �V,� ýx2h l*q? <eʪ`p\Gdup#9,\00g`G1Br!*BL!Sn13Vx8ePRf-l2KtRpz0F_{l撩V,`._2}{#0&![({ c]ȑ RW7-Wg)wEqv.8CX?(BAoH) ] @*wR~: >>3#k؍o[ 9XLFYHcj%<*ʛ"0$]J[7{B%=�Dc(tGE{t3P]0 tm70 _t:e ͯH [ˡ٦kmmzv;*]cl Q-DnFKPeP쭓Kk]Vh]R-\2Dx)#|"#Y~^X۴7 �VR|� .8pXb'T@I<Bhb֏kS>8eJjvyRx1j1PF$dNuWPqqvo#qgT%1D{N߀j7n}4$1[DzcMkF$瀔Iu!:r\Z(r4/Y 9:BJlD㱓97擺E #δ @Io|rm [/.{kd+Gl:?&r$-C K:"ʘ. r^;8n:_&'lsv >d:Ѝ= M_σ, \|ѫdd7䍦}MLzhK"wF@Cܺt|5#\%#!R4zs&.>+Ŵx ,+SG$qЧ@*yR50-Ϡ7aCO@TWkAg�mf ' �Vo&_� �UV}aE'cw>>X_#;V9]Fj+>>'BUWߟ-̶or>̿TH! Tib_1Adܻ4(d;{!W�ͺbsy3R,ri#pU ")hrS/u7Par&HaZ?Hk6Ǯe3l8t<;kI= 8N9V{ "(Z,j;\L"S4gRx'hTH AM܅ B}vkleGXQ͕Sx)c2iS\d!A\f𜗚}9 .ʗ3B)g:)aQ8U)ጭ=2S {/Ā7-!g`h1It@ψ[`V5# Weڔȕ Rk~#;xya[p~v#P\,u �V� Qm͒r,r�}bq<ʢn"3OO`hT1H7~QȎKUe:g׺(T&9DƊfRjK3 އWyR)M;lKe|zl-8ղbSc7./AjJWP,l{˸ VРK15lc�VKLo "ME5zgS8`:$WqG KR?FGla']ށ z?vg C tZo<xreal:)d.̪7 |"<ZRd1Dd&uY?ھ9Rg\HkD}j;-Rm #ӻQ>7̙kg_CU@p2~\e"1�U_ʷ# W-Q vCh#7U \CfxΗ/ �VLv� 2|L2EGbskf$x yB;Ii;d :DGD}oF�j=µ5󝨶$cy:%*Aқe5o-F 4B{ܼ)|(sx鴂uNx"gpf1iYErYrrB0 `)_Otz^O hI} I*3 cvAFs2gPB!ѰАGubaZfJPmŵ~fqM|Oַp{Kѳ1pz $\-U楀E=M\HZkL19":9~ dPNˌ̙vbKe#+c)f,JK"2sk=w{ՋUg<B(:Zj�M P_~˻ec[Ii4 -R699ԣs8Is3-5 }یsBy,+ʔԃ4HŞѳ ZA(*R% >O�Vj� M̡{E#*釐xC*atD0U5X&!@("M]FdE!:'T\d6;YA6YC7Jcp�|՞ TaL\vz`<]8`vza=�%B&P?eS/01@P9H'J-֑r)1lkJDZ/2pW3 >f PR;5.0CxU@{e[L�"To^jϤɯ?)Jg" ^}XFUْ>+)Z"L[-óP/juOq,VK2�21B)oOU.P! X��S}^bٵ[6qcR^p}Ra-&`[ [0[~WD ܫNkY/JJp8;�ZfEɋM{N$%veдµ@(Eb�Ul� b@1.�9�UeYJ I/ c&|p0Fv .wE\1 D]Rp[aͽXU9^F uN}UF/x ^qZMG*WꑋDRSusxYl}6NԤw?2-*M4t_}8%ȣT =\? F�gh M'hW}{JwcˍߘۗKjEKP\73,WZe,%irV|N5~9p88?"8*b1+PSrlҳ JamXog .[ݠ+W!ׅo }7de~6&:y(ӿ" WM]tRjp!ʯ@5acw>MH\D'+[SMWi6C k3{4ԉ(t|9U#v ]M3mY Y5wC'ʷl:]b(hMo~ޯ?omd�Vc_�  =tM/5{ah@ҶdI Nz=ZE{w99κ1Se qc5m@Z|HGHZeT)VZEH~Xr_&Po O;A$*̆# H4EN늊p�pHAupa >n!>TIv9/%&وk9` *>[n`2C0{Sd`RscMQSvqx %~(GZ{p/ƩLbG4$MvzrCMȬ"" wHĐ~c:4* ”zZ{j`B>l7Ty p˒@Nl=p䉪 SbRc$)X-mQ*ۻ�!&srㆋ0,P9(nP$.c \Fۉ\Qy;(CFX M}^br.7cۃb;.+_hcN �Vg� _i;o�G=tUmxqVǙ/ ٜeXx:)]X;)'Ԅ[-KFN-ub�&iʰo 5A  V$l.ٕ'P*O2`_©_p5%,sana${ADx$pW${Ύfq$"b "#r#9-H@gT/S?-~fΊ{;9T%pJ)5ov1Kp& :ԻlIZ)%qN .:ʏYߧ):ANÓuP2`ulXeZCs(} 1<GkO CFpc}5>�bf\Zq=ɰ٧eU_VZ \[VpE`VLX0m_=3daWQZ} e[nB+[e!V{';p-z_.D"4A" � Vdy� 4yp4EPzha|W n0 4mO^.!W\Z7JPSg 60\ib.E7�RI(!]ЄRG�UxכHqp?B@ YWӚZGVA#"<Y)k3{s7XeSt7.ndm9q~hSaVg}5dѢ_>G3u{k:ԣgl~,a'kRR,Pp?<ʪl|O} ubk2uyj_s\OWDU �=J<NW P%LgEA_+)rrx4*+tiϳFp| 35cYB;<D+}]>BЃЖg4$?E·Xa݋g737s]j.^>w@쐇ȓ5gm]5twZERaiVRn!,ljɖxP^NFtoMPl> �(   �VeF  Ҍ� 9鷛vИ=^(!rzUEZ"ڶ>"A_LȀ2;oLV?CBoAU5iGT/n3L% zQC z|(_e5u&Һ , U{y uE`HLp peXD%[=li_s<�DEd$@htr59^FFk�Rtϖ~[B'x=;{Ԅ+H9?K𨗍QfI0oZ׎n%u�9<'Q9-kq<Xr=i 1TK!>Avd^M%ֆ~Qx}_Grl+ }%Pt] R} [cmle mIIor is2ݔ)Y??M }h<qFDn6́jmcfCC/޶SLGS<iU �?V38http://nicolas.braud-santoni.eu/gpg-policy-20150222.asc� sN >9c #r¦.%*PcTIoxŅt1{"<L'nEne f\JNS ͦi>w@>>yU3 YH(<>_1ueA2Qe}+I^#*� 1R75S! $_<`ʯWBc mbc}u2[Ue6R2+fN>m~@ʄcwi`]`Yb s?GĤ\Sm%m7Wή 11jD@iS3vE#կvm{wM:/ȋkƵ�Yik#zC.' aL+RlCˀ,- ve B>鯀6 Q^/e'Tm KTp)lfoMLq ueēFQTj+٢.j[h3ytx\ ٲZ= �'Vp https://mike.tig.as/pgp/policy/� n#rO ťnoI%J?zA#;4#SZ5|5#T ütC7@6MCC?}z1ku e@/&{//`bpW1*ӻT- N q 7Nŗ/KOd"B>D_bRMZAI+ 2.r.]chK4x|%̪D:C!� Mn;t˛:5Y6?rKe}Uui78dm;W`gY0?qC […? p8v #Uf5#t4 Aӑ5=y (_pȐ"۶[qK[h*YI(qU&7�+Pi]hh-\GIȟྟ0p j= r&Zx<0{VI5B[g*5yLN3s&VW.v+gNҋ/OnEV1a%dsM.bjLk=(x{`cLE9ͬJ@e\>:MAu15i>/ՆYWܐ;Ogqњ)nlsHZQS7 xsy7GS2тdOH }4T!]FWqdO=`aB'Cyۢ_:׸,F>G|GLɌn[YR*PC}B_@ÃvEb<bbm`)hZjΜ>,@< 88\1/jGJ?q e"Y1S$*vinc#[zJ�CrT"riIOJњ_qm٠Z CL3d)9Rx 3=3PBY0ݯ6pHbz Ÿl\P?N3b̜ "ܤQJ-#!Y%٭:^zj~:/+\ 8;G6TjR@fL�V � �m�v zk`T#T2̉t'eHz,R4oFA<XIͣ!d]! i$sW!aarᒆ-Q$-2cFrNFHa':0�L>2"K0Ws `vٱe* ę!O&)*% SWܺ)(]ͳO6@:$8%h53⊷{QryT(w$4ƿ L!'$z}47[; f \YF]ȗX{w�M'%Ƚܗ$_Nl%Q{UsqhIC D5^WC] M1a. err;p_&8@-r:ddp_˿O<JYu:aݶfC$ �BK�Vn� \)=�S"..6+etj¤"\4zȶ%!d~JCA]ރtF[.Yɷ=$܄b@N!C`<8xc(cKO:xPSW;T5J{i:Ts5@^O^9CI�v,SVA2pF҃s!|%iHf�m?ן}Rנ'l6h)K9bIC _]1P+GV(>)zx7N:>i*+uyנ @O=+zm7©G4(af}]+*kh0:ћ^F롒ՐAZBENaKq7a>SQm4 0ɪMݘRhq9PRv'eOnj*@܇d{_npjCq2AQ:-YKPTi3`fĢA@kW+KSh{-eл=fy1To{X 4ˠ�W<� C�F( ԓY$lTvc}&?y͋0R-VN~V l{dQؙ(4Q X{ؗOoRf4Ȗx5Z.ޠ D!xD"➩x7(hj$u@u%M1(jC}Ɏ�|d|p}D&ϺLU;KR3ED_V[0ۀsM*_=8^b^.#e l`Kps8߶SGB\O0ZۤY4Ϫ.Jr:&[ ? KĆ4 3z^-T. ? iĝYt%j#B`%fVx(BsSmvaB;jAzLJ ĥnQyOU}%C￶^5/uPfRA)&"K;2{ZY๻+v쐶NG^uo`�V� _;$m�ǚ7xN2,0(a%[hS~ $qh)$x%|橫U-5\׾-z'n^},E$Oߦ0Em0"eadA~?Jn83c}h]1`a ϳb%Sj%kBy]KZF['R1WP#BYf']QsS}\ 4i.2woN7P #.o7Q͢<]hL|AQYQ0~V4Y(һ1YaJ&xFjYؾǮ`S0 QVwnAk7 |O"9e~_.9.o|~ټ}SpiG{K]+v *; 7:ۗQA ԸV*;O`AVy5bz"dbIcS_/cǺ3UPgc-qD5o$kmk?r E䲉 �W3� 9'bDk@�`.kɅ_&B*qzЛur>h$DۍŢdp@>vղ# *Jp%0E m܏$SηŎݛ]*"~Pw.o_ͻTmћEz?RwoGzmg $oxW>F JC'GsڱQ9�?ht}STnmBu i /S ۪`,[$\aft2Wʧ]|S3a6H#QM&'G6baiH6, Zfλ4-K \JV 1&\Q󀥥?nwڼw/#$<U%*td, ^<}/^a"H'XCk.Zf;PD^[L^UYyw* L+| tJT>=oZnNEPjb,N.W*mzq(HGHU �?   �!得 u@9XZ ,� 9�^D|Tojk,ŀ\Zn %o Motqwʅ0(ߑէ$;DϺ$P"E]MFෑ@hfKP&V,Ƣ%%& =nDy"Ħᇩ뒅1Ӿyۏ4�M[1g*Q7xMkw`?<+yࢽ!^  YVʁٛ`sPĸ] *5z7<tyw+ZVOqs,Ӑ.hJ1P \w"Tf&˗Vz䔅Iba]zME-{{F6BXG{zf ͸QGoF6H&MMXqht˯k)vbmS/l.gzZi£w5J/E/K,z}ϣ/,mF=+%ch, f;ųJFedqX @56l㝖] �W[  fO�����(verified@patternsinthevoid.net0EE5BE979282D80B9F7540F1CCD2ED94D21739E9K�����(isis@patternsinthevoid.net0A6A58A14B5946ABDE18E207A3ADB67A2CDB8B35.https://blog.patternsinthevoid.net/policy.txt� z,ۋ5+b<gVIu=VaGL`fY/S^BsNUy['.T,o"Nj2E^wư0/U]\`ukn{B^e6>9!eIu}0+*R(a+锰*L?Er-4;͢R<%) &drls6:*(li?ƦCE=(`"B^d)Y($I aT%B3}٭*pvh·QUN6vo=3: :l6ƀ,j1 o%=A$+ie֪de.HYr Ơў^!'J=.BE^28;:E-tS̢!!W@|Ea9";W?]q'G΄YNݐ$,W"2*^/{H"3?#0?@銵0p||~H7hu ת.qlf'T�V[� kXbvL �.MxjkL/(Uq8N֡6E)Nv}f+)ZǿfS0k�4ܭuU~t]@P'}Rn\8 ;G`6PljF1a-NI�a |iȅpޯpņ |bv{pXD<nC u&�gH1A}h"!b+šRT9bNũUt V1=Mݳ] F eGkZ%#~M;DeOfAjE펾Zbݰ4nϻfh{nJ4Tu C\@I8ڄ&?`6(MjI'fӛQ,6l^fh*H-]71CnbtGh6Wu'OB'$MDYWvi*LӶ0<^V=9flOSܮ4wϋO=b+ŀj#VŦY'RE/q:[ݓϩv[N T׹,lPD_XpH?")1RYw>6]~@*sfˮ驅A#`%ȟM%Uih' fgҠzH]uYl*B*h  4Z ;`Sl[6,A_1h2}y& qvſ4ی0B8{3~aÚAѥce3@1އ v.(:ҩZ mXD{ݽ,% ʵ^! XH}zH^]`*=NEt[v5Tjȅ4 6BA D"^^mwL q2z_ ;yYL<wرI:9Zџ-\USJ-qݜqxdl\ iC&)g$+.V½B^kpur\lO3�!oP@lH90Kxs+YXk'� Kxs+YN�@sµ?>R^,TQ ;PlTkgrA'�FlPSą#ʆ&qJ3>b]n6[{O75.l<;ndG) m8g7b!dw-(*އ^>/M 5{sb lf%YM Ö;4Q8}cxvnlڅks"r(PE7dP^n5@qž9@{S3 �!뻉!vΟ PZp�  Pu4_Ho98)cWlN(wmpoVَ߂z9{B/5޸LhHF@fz Gy>ײ0@U(�l4h=5s=11zr 5u_/)BCtȱΊdž8WG4-~I o&e?W,_R"|do8b *5uہy- @QZaޤuE sp �ZId� ;{-i Cy^u:t*CqMcuUXA`s_-8xۭI_~cߙl\DGu/ن; PRF߸ vg3cB5iE]+RFրdv\(2)2Ŝ|߫'_Hl"O6 !01&"[ N%Q"JJ3cY+y}]*zl+ 3�9�Utv im;*eV7`mKmO7]or_1z@? ,ABlCWDm<Lbr蓧"jRG+ +}|:},VU8AKO!R-,*<biub}xd`n 1'@&hw~M]<NHZ 2_jD-2+q@FNG3x$vNKeFdԹO*:E`O1.:=1jECڧhO{eFp>̆ɉ3�!T=8ݸsz6+qXr� +q"P<̨Ql,>,ȇ!T˗VAGV^4V3γN:O0 asp/{M:AՌh0Ђ|E>hG`V4<q3?K8t@B 8ǃ-_vL>\pӣmb^CfJ)\ŧ]. p+9@'r-aɯ'UƔτ>QuS ¶ϵo: dy^a>܀92'Yz+ bK蛽6ʇ2QrI+@̭HePuK@zuPN:Bp"܎ aЌMK @cD%fћŔR㽰{݆Y>Cґ9zd"@O4>R\ؼRC\3&o0\+scXYL/]m1Vr0_ᗶZKIt>2e9 uh_6iߝZIV}4r}.IȰk(Ue.1\p 6QxU �?   �!得 u@9Z% Ƞ[� 9�I4#EBgQqo B5AuB*V2#KGN::!%]m3)Q0Ew_AmxU :b};>(8 -^q叞TなkIc鹿{>a;\ahNH}%l) #2X}S]h_\-:7NB.*@"KbpnAMUj͂eh=ֺ=�9JRM<L'> Q 1Nm8i$9V%<DZx4ċں*?!}uг~džM]ij^&`\U]Qd0o.Ο )?�YѻMLhU͇l2{@rGq<Ϻ a9FdZf,HpjdN^߱)J 4 0]cy3:vCAߜ �'Daniel Kahn Gillmor <dkg@openflows.com><�&FaB f � 9h'U(Zެv#OӋ1%ƾ% 5Ya*$ʒ5R>36H,ߝҹe`�$Ģ8m0;-_\^g5P3KwoYM\m?, ϦUb4Mrݰ±g?~^ MyV\z?D˛ȓw()t5gYb1xArEcƖ(b)V|FD ֈb YFQ�y Bt bNJ]xз;R< OjYsĀIyb*~Y)l([T ȍe佑91j l]2պg9RŹXp_d6ʻnT/+jm'ǣ*Y_C<v$iW/ر n3R_C9TsjwaUuMwq 7lk%&/�k?̛eͭLBJzewẉ�Fc� #~p2`dUċV8\*H:8wgY�*_ wzV=~;9[\nk%t6WjƘ96_+BgOuv vHq@=ɣ˥r?ewBG34�CS!ۺ>IFZL Wg!QǃQ?=rwMGM6xYRyR?pkz>! QpgX<6H::P%&s6NRyřCXLqIN\?F-"k fe+p+�0Fxn j2qbL=z`^mLTDCk_ǁ�W 1; ?%Q*Á٦v H([Dz%0MQYB</[B,vdVmĀSUq[C ҍ#1I߮\P_�FaԵ� 4Ϋ{ �Zqm_UߡBț^E >v0cF0qp^앱}E!¯rJ0eާMn+QYG҆ ^ANk橶sy̅U-WҀ^Ȧd]}+}0}z*zwP#ٱHw̆̀ @ g>*C2e f`QژEni! H{ŀC^.[vt:E/0IpjǜtC>q킨elqi`#PC,JU _|+5CwS+v}g:, r+\`3V(([8?WWG*Y34 ] F{~dX Z.ÈKZv#V9sEG؇35fO%>՟kT=SaFٶ;PH[4qjuJ� Fx� 7bv}&� 8k}N (R im}{�w΋s,mquLn$dT*9F�FY� ӱga9)�E1cp #f.>�Rk?:I<B:hwbP F�G3(�  sUk��Z2~V@T(yNUV{[[�\y'@Y=GF�G3� Qٚb #$�)9O!߆R@�>|+w,]�F�GCpg� ~*]*(�&"ٰȶf&h[ {�mbdFs#|F�GQ� &Ჾ4�fTS1]e꫼�wϪlބ,>zF�GQ� _q%}R�u-W^X*7w'�ԥS7[7TmcbF�Gb� CB;u�^_>pf*Q[<�0R_'h$|:ο c�HCC�  kp xQ!X5k4X@0m=yY¨xi RwoWYU9o}MNٲ;εѴ)WZzY#:(J|-t?X;i<n9_{r-Y(pąS俑LGA BanV>dZ*L]:}݇RchyW8qcP)჉5q)8櫧EC-d__[`h%(˼RO>!ZIr .E^0A[`4B Ѿx#oҖO^X:0}`x&t7JP! vKv@Ry .3YiҬIc S!7="m2�{$cUO45\TC:5#RsDpUH2WbV)tP&YPXl^BS,^ FnLMwґPjrz`s-CaeSl _O Z$\%�/1gm~-CoeF�HD� P,O)*w�h[=[[K�a?l \HGqRF�H3� Pp,W�.DO@*�d]XP�tx xYڙ26F�Im]� hyd�n#`E8 L1FV�\P?mƤ \U c7ǡF�I� apƕZ� )qIt?ܬ  wj�SbXy41-fP[niF�I+*� cUV '�<G%u�5/qd�Eh=l(F�Ia7� ާ=>j�񎛦B\ڥp.b�{'B m�H]8 � }Xhw~ݧ#V3(>UMe</_bĢjtbţwx8$RjqSG-̗;<_ t4S6@>ϡ zhuT)יϔejW1Y4dMsU>.If2մ+4&Z}_I6BTB�PR U0ZQl͛3 Br< oj)g(i{*۰'qjX:bpT#&uݥ;�% fI>   �� 96ecBtrSo2wQyCE�fdG:_YX:PeS?ضĿa+<⧦σ^hc�WZtܘ.*&g"_)0 !w_&@yr)]W�PXؚjaEl,]{<[Jʵӫ"LMR@B{Yp;�ԌZ/nXFʄ4+L. V$ ~ "aF@eqn[HHPVi#B~{%hfȷ )&~&nL_|<Y>f{*3x\,6܅1T`qQl2"q/ԨWCв, [$s8j(9P ]j?h+1x-V0Ϛe$^a; OL2#τNQؠ@O�^4uru[BffPSb5prU-HQ; �%   �OKB y� 9jyS}M7ْ˷ռ7l~l *3LyO  Si?4Ǭx*8bSnFЍT tnZK8z!d6¿S'vUSMi#ϫDS{8W^U^aVh@\*BBm HfAQQ:aXu;\-׸(UٸgyQf<"̞ڜ$�<JrG5N08&\ !\0elZ�<ـ`QZ e)?Uylz<mc-X!yPWOOg;)Ŷ=' !NR<||3L!G~kgT.z{,Ҭ~<Űے.h> vYF }iU>~[#,Px,Q IOuy]' Dz1C9Q`,PW>+Z(~F�I+*� cUV �?!x瞚-//y9� nX0mz;�% fI؝   �� 9UaG]ICb?_OXU_CQ!κP,{K˃#xqC EXU,jXX iȓ20.-2r'CiC7GǂnLBQIo2L|P+W![̪B'\egRVGV 7VW{R2-w(ko:Wƺ죫G0)1\j ĤCWCD ڿ ͸ -=7 m<PYv/x:B¿q:HǤDl:vBJ^^D F! >~+t9fp;FVŌzJqQeX#"2n,DCKpYT)gWi{[1?IM]!i;J خ]S xd,Ay`jpḈ~,^VwE$z!3YEk NT}GbM`E)=~`bjމ�J� f5g@M~&tK2- <3qVyԧr>59{]`nN=<qr,_?>OʴWAkGG:Wb9 4g³>@g'h )R;eo�<,PΊPCjĞ-b�!W So:{/I,-9%;i&9ȓQĄ퉺δB]jP*^WǢV!NEW!- N(-<d}A5*!IлE\ G֡,IbA> ..0eZi{k3l(s=r>cD$l9zeY,.!FU%bB7\\%2n.dtTQ["dx޷ _T Z@4\]c#HhCi6x&4J�t^Cj<aIǁ? ` hE{ZŠJ2F�J^� FGmjxG�e3ԡJ;xPe�;2QǞ,Gʾ�Jk� 8yM� ʁl?A4~˗5t{ʖ`:F}L i6O0췌2z/V.Ŗ:̰%5p ~7f|7T."" дT2hqUZ7w$c#'1is\@,5@DX6c8ͫ2MktCl80fIG: D޽19V7+gka %ƣ]Ց*Lu/er4*UuT �Jj�  JEoǺ7fU>١E~s"4&xX <^g~�mų-T>_/@MV_$ϗrȉN@ sdYMtёz+bs+Yqpx[JYV$i(;,hsyW{YLRl1ɷ!.]&5.З�4YR53I<(IBy|:\%Ҭ@;<F<6[t(x:J@3p\Ȁ[ Nn% {fmam A8d"@Y3B0{[|SJIU1vfMK#dV[p>cJ$WA/)m= =7!n@EnCTL] ɿd@E0Hq$Al9F!S39<f3DRn2ݔi] 餑74IA镕+?+ᓨ<1V 63YWɉ�J� wC$0DbV$b.UP˒YX ZhU[ygvGS71a[OcO:OO}IJ\`Pr'1G}ʸBɈszg% :N(/>[ϸ KEZsc|})\F@S'=8Ssi2pA$,v#˃vs5Ls;qJkGedn 'D@vT;5~뇇ES5}%:RwBMf ƞIwn2߄vx(6ᖩU"Mׄ!ԍjm72 2&[s2HٽCzj݋6LLum^wDLN*G >uRIj`'hq6W[Czg@1_):Ċ^srїALN-t`<t0%*3$Sdiv LE �J~� 5JL�W2YCx .ݱ)A� HxWC( BQ` �J� uݧy̭[K^# Zʺ E#h| iSDH6Qm-g‘nkܙb*acyn.w{ ja\@E;!@}%gB8|{s=yk8rvmԒȟZ0DDΛ)WJ;F)nނU-"lH;@wҰDK4)a;"U}X@Iy¹8ᴞ`1W# j夫[BEL "3N p`1xuZFrPj0eGU9Ǝ0|k2=bE^qP1qpi_0_FaxbdZe*6Чn"G-obГz-dInQscJj0>qd4 ԖhL^ArBqzȘg]e#F �J^�  ~_.I5�S[Û,8@b?#A#j5 Ϥ ij'Q .ð=pJdٛ"-;�S ^ h__e8W'8+SC}}0p[gvr%M ?\˘%qpPkop.q6=-BH"IbL.A܇\Ҹ+}Cf.u(#* d vSj"d O .<F@m UVÏS8${NDV}HT6}|i;څ0"Z8z-7 WX| V=uhw/v %?X?p%Y+5.. 5Q|/Iwo]zFQJ|hMvSl sؓ;/ c\Vonk,ޅ#,nEml `ǹcX(zPhqa>*CƔ܌Q^�_7)܈F�J<mw� Ik ZJ�iF6m90DЪ�8vrDpt�J<}B� 7KX,�UF*vlP3a1(%L=#]mmw-%kp576X&{M la)zL6# �iJq=lXDLn,+ |%}B1c\aVTR*1/RЛsCiu;~V,h"QPD/S L.ez<̪S,-}1i$$`z|%6h0s"k5mD% F:M҃g$BwCmjF Xx MXF{*�{ ^(P3d'ՋU]`c.`'WZ(\o?Mؚy:Ոyaӈt?َUc^k:H ?X w.t9nA. ,=I&DMB_M5%XDYkלA#ٹ<q0gʶ~UOu F�J<� Ik Z� e�_?3/R ;�=세HȞV'�JAZ� zI4Wh-i/,UV4Q/thEr-!-I{r ɾ-\ dkIEX$|S0,,30#v^LM2Ul &YHͮ VEDYrE'yZuDmޢ%;<Q*RHp͚MQV^&Idy!TZ 4&@2JJИR vj;ɞ[}"tވʶ}5lhx6Æ)+Lϊ1.`㤪KHO$P΀?N… v%nڻ,o4]#5Oɠ#g ?oiWUX�a 0PWIyc˧ SQxJJX+=כI٢ zW^nJ:€Z;w�R'itp}AVI[Z�W:nDZ�Jl� ;""�'EXmM:(b_)Dh!r$uHړ tuL<f:=={N[JDבGE*SE%OA~8e r NQ9iQRieC׈JJT[gaC {r e`批V0q,038ȗ #_AVh>djAoƫ(j,'Y?yȬ,mYu.e {ޥ1U޷�=St?yͶP{EqGt)v[_K+K,TF!K%%d T[GC%6FYă; ,_Q 9~Y2^;M:`ԑc<k]Fv\̎;( _';unxK50zMl\yqg꽗m{ !Ls@RKW48yh,# nqّ݂tJ[,oމ�JL� MM!�1jCds ΂AÅUD+J�`YV=K<ODP%H/=-#oJkbbWLq6.�n=i�4֯i\@:MF%Ƞ#\nrGBBcX+`Au l[RrЄƱ! ᯻&m�YG!1 ԅd>hBA]%';~$3^^Q Od9J A䫆I`˴Jsܓ&<~THc<3Z@R̒fҸm[wd'((^Y+KOJ\JS?u#R| S3ě5j*وڒ{^}l׮ Nbx[76!sgAM3R[ڌVF(YG /ovSO5JoV.T|K1R+!m [YmU?)D Lqvj&H4j�Ke9� Uܣ(Pîz2#Gߌjbl[FNY4VbK_m/�Jm:Qa$WL K" 6<uZI>8L&𵐙iw}*n!ڇ؂Y- 5݌o4 B~R2 [.;l8]'dE畘qEI6#8rzYU:ZC%Ob%! mr)nUaڀ@Mʱ wa ӖI2rKԗÉnvd12{d_&|FG$l 76#jPz-b'ᱭâl[E-G㞁ubo<<"ޘSW:0j <U9B3ۮPF1צO0bM<i4j.6~r HΪSWB\�Ke� iĭg jRoV]HCwmx#? L'_Gӳʴ>cSA|3h,e6reɶ<2#(e]cQC 4 H ~ώ,WLʛNG:[4YQ(ΓX2uS�gh76ʌH_ճp݈nr7 '['{!*M*Czb=µ 8 bioy5ӸURA|r䬪>yA2GljMqAɠKp] 6A~I$tt荃"*`C@iP0WB>Iu8OdF!b]CʼKcDV]wJCLgX@-(rA}lazZbY(GHBdk{g1 ?>"F:zE!0㚽 5OI"K"q6X POI͛_‰�Kdˢ� K7e¡ <yTtfݚJ\{jHqzEsJ'kP%=l#ҾNiHsLcľRJxa|/N:%{H_]4ne*aS@bHoS^>]/e'M~5~ rbgcv(mˊ$ qA_ܺA֟9R=*.1־?=EY%QeV<4粶Ϟ]ĺqR5Lќ!Ν |C-5i6a/ g-WYyrh2E ެ=`NTֻߐM kJVރl*bO5{�6g-o׵p~ ݩd炔Bo~3Ҙ&Jw�qPJfW*ɍ(ݷ`7fxfj5sgaЀ&^%(xH麱@U*x!#$8/tlF�Kd� /We{$�+*\??.�d U;>z�Kf$� b]q� 4o'~:i6zɿ˼ @15b)' @gզǮ~n4bjVҭqV{0O$F_әXgCq͓x6G�Il6c5F=VZiK*W7rS8%@R@az)+Z5g*h^!_- %p,bի# 6l`-5x\ [ YߙPau/�KhC� ERϹe^D^^^^LuVQ&#Sm%L/hFԅ /왠:0ٵDx7kT<t@򺇡DF " !t釷\ڕvQn@| 0ԡT\ݘ7m?t^BgC"#U Du%aw<V!Dl MEڡ?ljQ)G+B .ZuɊ$ߔ9@N�wsZHEܘw,S[rB޷5{ D IfɽeD< "lF)yH=yX{hY͂:vqت*rcxr` TӤB$hXVG.pjT7")D&wz _MΤNsaz]ΧwoI8H~֔^M<8Ln 2ɰzDќ >WuvGr01fuVȦ2\vr�Ky� 7 ?p&\ .2G\ ۰hGаjV\5reCUHs bz"7#Bkvv*bc3cpn:E2o Z_+YZ2y  eCV0diu𝒚M"pcm OklW8jwdDm4CK+*{0U8wN1T)pAS #懬O�K}� @ִpߐsss֫.ֽ99は1Ɵ} fK Lus]\˅$('HhYEY6:q +u>553eGC\ 1l7+F{?d{lu.[d^ $�>I1C3_}3e<" Y Jh WW\B^i6q &V.�:=zR4``@*˪%L sP,j.s:_d.>�%sJ7i3V# s~#r>- .a|uиs]mL#_l)$d؜T-l!Ewr*ῃkQgwd�nr^ 3<P:IƸ=솒zפxў2xpƁ >۵\A@B*Y? W0]7oz*osshdW[OVnJʼn�K;� P*IFU6r˺1ͻͬ\w5!K-EńJ{?1+4cBmb oDP-v!6M"v:wZKst_ e>K6ve~{NZq K(XȐPاY[Al %I7RT$JV J{Fu )Tz)®`\jHek&FEvO{?~$b3}N|Unxy!9KTu䐏m_U{|]+c1.5@6l$ }4IE֭_(mYєi&ò{?js<lɍn?>-a]U/E  p 66]@LfO`d.&niZf%LbrWYƬNjd7WKU\q{|[\R" Y9'ؚ m9=%geZo3�K� 6ܜ}Pk-TFKMj8M;-ސ%lᒜeE&|;]F]<~歓_8r/:aWvXq<p'jl''bk<ِa_ڙ74!-a' HF[?>3aށph 0*#c˲ǒ(-!/Y\7ji _ 0Tb]4{Rey*�vl<';/ JTr3)ʃm a) r\gʊ8O9pP+)EjY?|%,mgdgl~RH݆, c;*>8t4F;Obgܪ>.顖|'rcGo 5Bצsp)9, 1� 4|dQ.Г5/P\UςQ)a&QП)gĭ>\P8I[gM6�K� #AWI ktDN/щ|Qw9IgX ]}o냁)6Z*D* �ջVgՍG_h~]6~LGd ٲC12Ra׳ۻ}A 2ס)S?�XnLeg1Vliyy(.}gaܷURlMj>NcTᡌ*uiЖT嗂ގDO%z590C="{F�L*|y� ;.;]2�ؒEg͋E=L~kFV�hЯƌ]=dˡs߉L*|:Ci, ]KlG.3Sj((f#ݯq+źw< 0Bg89 YIvDd40Fc"}]ˣQO %5OIqE;͆e).xuq'Li̕X|Q\m39h/ºxqP7̀ ^b'LOfOYܛ2\:ή .|b%f0%~VMJdx� }qMWiBt/g3=9eB^pnTr!@�AڃMXI9Xx(d*j]1g\T]ȨR[NX* TlWh@F.8(')F,vCC%llf@.5Sp{f{ˁ}|-ZIzof]Y#o'鋍sFJ~ P>�hͭ Cexv`TyJe,$/l}xJ񛥊7`#^_�.{, ̡kvɉ�LPՌ� AocYY2:7}#?�zۖ hL^H zE\=X EW]ag�TG]G1e+O$ڠkC K?=1Yd| )B37R?۸mIJlKߺi> e Ҍ.={qH{WI?LRPpV_/":k˪Z'ͻS}M-AJ/;%ȣqHf�ng@YTc�LP:� 뎿29022NCW(~vjw z)Z:ʯФ[EҼ!4G_e'1]Lyqs΋^@2Ä=cԽa3wςU4v,vGћt�\V((;WS߶:xV*B“<]c&;$K!yWS:fR [V!Wоh }%f!sA!Qә-($$"Ag3X %m&߃e,s۟MmE&0ʥ*@ h!~(E7Q?RzsN@"ZLmӰ!5]b~d7h ^7tMZC&Yet09+k|RۃߐYj( # ݉}E4G܉rg\\C6qL"* )TS {Q93<5u֏UISd  �L]Z� ]2*@#ꝭxUd<htZ@65,g#(ksg3LȁS.:/sm]|82 m(V9d`k\P$=I9{V}~ Y?1s覭=JX_ڴ I@ .@)֜+gr} zE6bEc<ז] ԶC{тHp 7uƐxV>Ew\tSl(RRſ 6Tj] FR/Æ}E/~ Vxx0z6lրISPCHt +$uC_2A"t/!ٮJ DH^]wߛlXYvZ-ǣfqFki)d޲GN$qPa+41uC^ϚJ$MQ ~іv’wdF�L]=� =-m�bb7 _2�.c/U&) dH�L]>� /p,Mn%S�0F_59jGԬ4FJBDh|oQv)Ww\Crd3% DpF(@0Ȣ0u%HxYVDP_ݗ |DߐbLX N MK.M+WSak@gԶ~|H$B%r|fB-WnЭ7A'dv(aC5$cƖ1 z wO'J͊ǁd"`^u;FpM!fY%1QLR扚g/k^rxPVy[a Uh뎐m,*E* xނq=rM躚MQV;;&+ 3 6s&q4gA̷ sQc\8O T `pې +JPp^Ï>(o UGiS] h[UkUgB6V#R`VnZbc+WlDv9T1Ւc:F�L_)� @ tv-�<ͣgSi _E;~n:�6Z(G}I_Zg�L_)� ]18�AsR�۾]Ta) ?פUQ 2ѯk0) O�/G^S `Ngs9upwYE] m %wTB\"MԨ0nzŊ\+Y %e=[+ɤ+!(iDQ(&h DnWDS_铩u߬Ò;l+ףgB,ٺ?$8n;R9ͽ;vaݱ:Xb ~HHZ6P0+\eGۈ70^s?>uL6E<tk�O�pw]^vgJUΪ8MG>ImJ{SXeG@Sudv*Sě_u);{Tgx:'#!T5H g s�m-B2˧.F75LCdnC%\ܼx1R?0SrdF0�Re( wH܅鍹7ovu�L]� y!RR{uΫ�;D--j7(LP6RG+EKrkcӜattIv|_1CHl+õHX]X}eXu~iNCSho{;]YEf.C`D@bHF \޺Ȓa5\9 ^Ln2(P3ã򬓨ry*4u7P#ΫR!mVr� ?m Y4 y3VJr䇆ɢޢC"!wB/5* `$.rKKBk 5$Eyhg>Q|k0gW`oon:klV*]bt*pyizX)ʥxy!;]BRm#e6L pÕBCY`l㶼tj)^e)<ݧ&G٨X.] ,JMpc<0ӐO|VŧM‰�L\]� ,|1F! �W)--qZ. 721:/hc=~*:&7]-ǹk~lga؀wE ~g~'#2M9dktX=fy ym^$Aqi {>a=w 7xxS:qΥ g L=HObVP/IMw6Qɦ#cNꞟf!<�։X8~d#~O/bU}W l$2#'R=>D"0m4gqأM.y*y DZ&Һ6ϚU&ڳ[Urx5;_\jDiTBE}$ �Xmۋ{ [4kQ40|_nY| ˾nS՗7p?`uxK0}e!U(ɮzEUp]o%nUcR (gG/N�L\� )C:Zi Q|Z1.3(l/ȱc>ƎΤ4uWTȠcp(V2h3uۏ%ԥ:h CJ*eYW�SfZ3^{ȧ`vABvHDh,MݦҤ&\%]/rFV+.Yqe$~C&E0 -R^vTdp,/d9}neL_V'H#֒imEnމ C=CIyk2L؏$'о"S*@ DŽD~m@ye{fՁJBɭ>-'Vl1of`^H=듈Z � !d?V(5]%\Č\*klc(IK@`a'ϜY} ?i=ApSЄ+zk!t`ojb+Ɠ=N6:=V̈́ `~F �L\� 3% �<}3Q\g�3 AF�L]Z� {TUmW;�wkBz+zQRa5-� Ŭ=ޤXA'Ue �L^� ":UTR �Hiė rʋK `X q\w X1,�5%nxi{|!csp^&qN?d,c0嵆S6hmyHL|W}[~Yu6g_w&p[ߜ\XKu9y{~rڠ2^^,(bj,>2yrG="6E4$aS8IHScwsள0Q,~ē,Yz!ZW;P@6h3+{y'PAR}M ܀}tFTQK.YBpyNu1v0R\]oVG/p夐<1͜'=u1B!2�L]C� SGRLQ/�I󹯂 4ӆ.$9ˉ !tR/`pt1(®nd +SҜ*l9,�]]k@Z@iw\7ѾKP9iFaA j}5UD7ԒO7 PS*/(u(YHY0S3);#O)V@'ntuox )&t;V-;'Wx g-HrCP\yd#V/77ߴXODAb>zf@x-qҢt#E$⸴e橔*pS*i0 0Ys<c@;],P~<tf ަ~E}:,4Ł,NMȼAfiO�rES\|gnXFYU=<uuϏ嗃"1" #ё~Yߨ{?F.KAX{qՂGFe<:nJyci|f vdF�L`� Nz) �$  M6+;%�~ FþTӻH:�L`� QH c'/V^0Z!l i$v0/lw]$e; k^誛YivlWtOz _x!2D)ޘ(ЃscCW$Ǔ,0SmFfRχ|QT?{ #UFciL]=c4 tt0_'%xڞ윒U֋Icqˀ;֎n,vk{C8}u\Ȕi1C^!cA nmLCb(T1(+L@BҸQ=Gʰtc2fߦqˎ5f.{&i$#�J5 93Sujy"pPs/#r;Z]<=VA+Cw۷Ь'{EsrE%#%/Қ~dַ lyY+\ѷv'r\*Y<x tP;R)�EzЭhub*Nzbo)E^I�Lb� g:ے.Gh#j12 룛!|䭡m鈊7i]xnm=T@8aҨ�GiX865mp8}hoJ+os"ЙWړ m|L,w̤`<q5q^m�9EόD(ڻwϜks:L`gɜ"XtH%ۜ @Yat- }K*|6ϑ|߄3 r$ ܖ]+kә|U?f�#%"&7~f\&@$ȱkO"`{^钥w&.1XcZYƟzx={`+)jŨDh9%i_ ?| .&+a#[,u .vBg=S WT*ƮkF!ل>x1~,8z'}v PYP6NxA.~F~Q(<íqvSn?_BRIq* },g�La� w1cwVx)t[IhI,ڄBu[HcX#W,šhmdy He:EJw!d^A{f18_g;@0z5y@Hbhcӏ6GXxm{V\CɏBP&lsTQWؓa1s;|aɾ+oMjkۗݢó}3n7=(?L rUpp)-)UWxu@ :cP-j~*@`C0zd 8]>R"[0Ft1+CuK԰$m/fO܆ Q,ؽhG#!D DQ 3֦1^4)L˲[?AC `<4YY_W^9ı!!#7ت}e4f6_oJq_&avvEX SRԎ"g5r{dypHf ֑+Er�La� p7<) Ή corlΥ�9&ZB˖I(8ytws45@a>d2|HFEC߈<gvZea"m*UwC!k_}BY(wz�sS *0EX5_LfF\Hxn_eڳ^pr><͟s2ŏn@0�?=J/_^5t ɪVOqaA ˦!ۙ[9RE`:`8 SAAYAIļn*86q@61u؟Iڏ5uUm|{{|[LgqGwb`egAo,l#8 g@fb  ֊oӲ7 qCx<Gypi|vR!߿^=~7ah?󳡶mzo3Z x(Wc޷EeAwԯGP MlSp mݩM*XU[籾3 D!㉎iO�Ld� "%U �7᠐,& YڏA eԢ:Ibewl@qN™ hunA)q4*DOPQyґf%^yM"PlG"$2gqSYxhz{}GʮfEk^RU$ZFvIZwy$\MkkSB[P~yg3Sð-߰ LS~*El $CaH$EHp%a"u6*X'eƟ�z)QNCA};b߈/D-j]:X~:|I#*l;ė'78邥T7pԼJ:2?s ݠs쿇\xvc#ljLp7cPl~L.0& ώ#GLF;ɞ*M2vO3Y3O_L^-yy3+{W,�]u:!m<ύQ~屁wɻ0/F�Ld� yx?L�;mp"--�$W80Vi@[f�Ld_� K%{vqB)wdPlpgb`#nx }b pӑ5䎈ܸkycvB.@@ 5RZF$#Hߌ3p]K/DjL7M٨ٚr&`xPCըz'1`ڤ=C YՑ8{r6\%'7݁85|jъ5ښiXowg#‰p?lWN<o$~ ).׀ZI!h sa@f/|,ʁ0r9V^k5 /Xax,&N$cYF6Kw",PI$zi|oRZ#uA#m zh׭F{a2gCG7.^wNgb}u}/m;E<ze$CF fv1-$'~ڤct|Oa@Or([ڹ=!e,jsɉ�Lc� 2B/R{�$h:'M@xTވ 7\},[Ĕ"$<6Zשl~X4G@V%,a6BIX"P;Σ"ixO<o3 A3ЯNX.?9Rod<W~!-`0~YoYR �F΃K/`]�z}vH\jtbRDAP(pmjwa0S՟7;\"3Jt/:WA^H@TI!틖[!D="+#ᥒ>Y{рV*OT!'xl(8#!|Eit}F>qfϭ^}w) gZBi|n5c!EOٱ>'[{,=r8]=Cd zd$y.4={d4d^%62RCP!FTE;:p9r�Lcr"� -�B�8UdOMTQ#B] 7덹`ꕯR@pHBDiˈovbl8`z,/t'n8QqMx'ȲΘ:$S{}| zWuOPjx3l2"p (g v8<2?+ wp7bOOrz0GjW4-jx6]7bid#5B,bqK9DKѽLֶ:jx/pfp `tʧj=FLVv ԩ'>hL_kg'RwԲ&Gk^VcQzKO1 GS?ݸjz-"}j'*Ŏ~A_ P2v%z{( W'ȍ !pW)୑VD#NhLV;+M>nyF_ȲEa.Ix`}aňvKo�LeR� v#*V�%A:*�RgkyR('d>e};׀3CFA`HPP"kAU,i6{Zr%FB[z̀ɝԯ0! f]"40 j\t:2 8*4 ~+~� lS[Ā^rXgiZt!"bz"J1 -7mI.21Am5<�eĴ-I;X %6@ӷ|k_6vG\3hhBff3٠y,akMWOHtm#/Rx02$*3zIB#XW$j(Zm"S+mX1 , `7)wE,aKD )?O/myXZ/,-M|?P~TKVs0o0!a'!&<TCB 2uWɉ�Le� Ф/PʛrsY.jy,2<ڇoB Ýz<wfYx‘jaa0p84n�|nae�7:/_̢L|M¿RhNׯܮ/IQyq1CԤH~L2Aq wk c:'ڶ@qF>iL 5</\59Ho�̹: tV4(Jc QG+D^;A E>X#* ;:90Iq�^P-ar'~AAm|C pq?qN!zo*_v=_ >_ X$iI Av74TĖ _nVpsz)tH*~fqvZvU9e}6w5؄г(ޠ֯jPMP&w]V\V A=gE8mr,ɲ1{ c>Ȝ(x㬣 J)`0Cn.�Lf� C<Ze!�xQ_R>3-mcABcPT'CX*5bX3I8Q,jDqW4H통OHT5 Y{&)~TK�ҝj-H'~Sĕn^��EPϨ+VPܮ.<¢_a>ѕ򱼱/Ŷm) ad>#tIdõ}jf#5U9 SKQ H訙. y=?,LY[wdo~k>%n]5EO7h">[ zP>J`*UdLkZQ8ǝ ug-%Û&73;>8m>cegHטp)d2kkD HX vq&mI{kiuxIWōݟqSr 9lҞ6JIstz#{Nq=CauIJg7/kJZy"mZ7/p.ӘǪҝ�L\� 4Tӧ]fiCu<eP6bb\K|r:-I9d[B̼΄kzuM4BGִ?7hPʎF}f9Ol�D1뺢š;dt*K]SKb%W7Aˤl҄րhn|"j(dF7sev[8P||'&nw^lWL>篘b:g͂XE?2ʑmW|gB/[HG!3[|Aczڬ/,\2aMߌG+FY=h:!JZH?S=ng&!Mդ#c! 8x1r%ď5@hA>{z[e3YɷTrMu #qqbNkɣ~^T�a4'Un0퀫-^&^wLKJPV8d| 6B/�kGnn~񦹃;6O˯O^}&ȉ�LgNJ� FOjYHjF~,Sv֍�&iYϹE%SA (V8WQ ~C `} +@靡b VOU1/-( j:.w*5�I;DjAbIG#EerIg襼Bs}5}iXXNzd<cM+*135:YViWZ1K _5_$SҴ#n•qi0)@% p䀠d>=ai5F0ͳX�dƚw<|.qr>j۱8*\X*U8"7'"~pk@u*i7nՅleM_9lt02/N`C1WbZ-v*W%7!* `I/zTUݠenՈm,:ΓU80d@/O[SCWl9V�򂜎?2WƚM ^ }sV?=VW<$Cq'ԏF�LgL� x<4aG�M { l›5O%Y�Hc(_ވF �LhP� ;A07,�<o}mm%T+�\];D3y=' �LhP� taC^�J_S"tCВ=K ٌ͑DJePUdZG i_o[| ɼȪ N$ {Qt(>C+MH^ȍ.B}(ީ;iem|rե/̷T?Es-SBEi'i }) Ljʼnn9qfϽ1Lop<qbc4e@fn}~r39h@:۳4%.xq4B۱r<tw\'ecq+A3}),{ڛ)ϩ %pȵNdsj[.lz_кjrꆨY=)d)O)FAh\oa#7`YXxoMMH)Ț2޿M L2G3CVG \fl^N uüY[ fBV+5#CŰ!aRa-PZ(eԶiƄ�Lj�  Eq>>p�t$@f1љqȤju/%wh"ۡ]<vYΥoc[ >{wNǥw'GϐuB;<T׎h*'Zwf $JrP;.bv?^VȐ29wR*韜DZjIvqfce[P H,hZ>J A $/;f5wo(ƅ& ^-;(Og_� Iɔh)b- w,KTa\6FeU?B7c7gQ|RV0TnAaұLf$KBОҘ0Y7wMڿ@=ӕ<Ք8dkc+huU leAӈDCD⧞d_['ٹ"N-9.Ğv 4܎jRShI~FA1hq' rA\rz\=|lI+ԁ'?$�LlK`� ⿨B Bto�RIx&.$AA;">i(b^Uq-s{-_}ݹxfu;Q NRzplxZ"]扔z^Jlj?߸}YB4v6d&MꌪaNf}QX/='$9]ӝ(1O.nE'XZ98oi*ixWE{ZNJ5r)!Y(\�X&' V1$-bVSe1̰ + re@V\_[0#FAƲ1(IpCU�Ig-rzL$-i&{a`dV52JE%Y;.ω'o0ؗE(ߖ&&E jXIgE0 Qn1#QhB\FA:~mA*  ]81&N;zȁMT�tP+c?J½%M[hO. cg'q�Ll� OcG<Q^ Hf$IԆTdrz*&VR.\TP]*K@~(zh+O$ynuURILD% '8W( P#QqLJ<Ykǹ&_I~^Y&#m- !_s w\F@x{#0e,'u{.I2gW :$dWDÃ+ N(3V{[FHN m0hVĵVYΚY"R|bZlIF;2)xYGUqB3 \�qssk?5E[sk)QlGCgA?LM7,ynzQ,nx2LA>h ȦQv6Pi:Glng֦V{nG FL$|E^u $#.G[;gX.O~}_2�Dk Vl�Ll� -hU.oo�d?I&ۋ1G WWA@,Ğ@QΥ1+: S?=/R r U1gH@Ӣ) _/IBnY_(e|ӛbFÚ@6 LT9H}? O<^#V஡}ퟜZ+D7i *>ǪG3ُdqx:+.weV.eFmcrCQ-iշPCJhd<C\/7ij~:`Pd,]H3 j_t|)%D)Gj)PXNwd )qi?܈/T:B֦b߮TT'>}:ג f47gIَw[D]tɒ\DKcW=uH�z@J0_{e`P=S-"\(S B@xZW:Mgfl$Wzw܎XWe+SۧU <HYj"�LlV� |Vx~xf8l<Pܱ_0Tyb#^We>9A'`mǴedG::>Ajlbz?kc>mؖbw&|!N?7gQ�0 c3E<"AgUnҿQD$c7:ӹoX&>.P9[/rB 2[݁m]OT9B˵>j&�Bƒ(3.D6# �l�$|943_N w@a%gS@'�e o}[f5?- 6]_6܍|2Y HR޴�=2AG"}L(+""*`7-?;\k3\(S )x&:)Vg ˼}<~FRLGKPVR鴴sJ%LɿhҌ9P[u(u0jGP| ;>fZASe}�Ln� DRxa+]Ċxs"{X \*cq IqAh30m:ΐnQ�A2%=٘gZ 1#Iip|C03/@ �tQ6rdOH| K$!z%Ŷfdo }ParVPȾGj6F<�ϭ>+?7V'~xEnyTHҮdI Rg >Y86ٍz&,(ܗN<qY_/⚴oƾ-S\"_O!౅Kblhɣ tғLvMv?[koƩ+fu6tY"t>gp+C(Bk1<v#O Ffs?_h9j]b}-h; 4ORpD=Wʹ/1{UTQ!FKm'Wl Jֲ`M S&`sz5iH^KseKmfSg/~6Ƌl4Vh3mp)a'הtN�Lp� qw-09�c3fJE�ptYgӭ?{J~p�3>D A6xrp4%bUdX- ZdަI<J8BSxeNv=K,h솚[-_#\lzE6=I+f9; MЯ .~d"@vksjP_"ɳ.S!5kG,",F4ATR820<}4z#qQߓy#瀨pl%;R !wGBjp]ZтS ϢDyyg,)bX'Vs!aD\|K<PyV HP[2_@Ec�Ѫ Gcl 3oZs;ǨC@<Yb%96f{C•۫xtXG4NL9bNyp_{O?3vT@K,�@-Pchx\B9ޛ/G/�Lo�  ^[R�Pw>ȾU2 EOGb%UnY#0n#I<MH`k?l%|ݫ9Yj'im:i-]'#u]ʞ$>P:4ɭ�Y *SlĻA9N N 9~KkIFAH<Hxtv>o/8!8-aY1yѽJiYlrPU8}d:q e\(~0Z_=OU8 Ѕ\mQ.}Bߐ$]o^yoc Pϥu(eԮ5#TDa!l={ٕ }עt@xpOwAlk9Pk] )/ǺXr\:}up$6 CHL56} E>'ؖtmsxo;`! ތ@[5LJ#Եbۜ3�0~TyWv/Ch�L}�� 6t~7j\WPwJ M(i߯Pa<$3\r=�GgUkܜrSu1,\̬p4,+fv}ԲbpN Yڈso7lh7i`vQ!n?v.va0=8cQ]+L?V@MMa1o1΃Ɋ 禃C)^`h:@'!T{e'XL aSwD8__v{; 9*frAJ0Vdf-7c)"]fIDqX.3R,dOLۏ#^OJwP35e/daU @nV:i:~Q!5Ad2j't H-ңq^vQ2#,3Tųk(|Y7Z7DRCuw{PV*%��T LMg-c4F �Lt+� Z(w@�'R ,cR-q�UBs5A �Lt+� zwK�%qTWlΖ&ϣO܏ЁA?QJݖCo(2f'[q^W Lm݆gkrW T[8pV/T:5ƅtχ뺭\NT&iO.7qiu3Fz&!SR hJ~2d˘ffo>-3H.A|?n{GPm"K.0txԘT5\"M*LXdqFzzUu[玽0#Zuu4}n[lxB+d?eLσU.RGxLyyg~Ö;c�;'�Fd-Z9pD;JlW&Z6D*X'aJrEM,l ʽE٬m/?VujPsa6+B>?BGKJY&EJ;g|6z` ߀/ȾqT�Lu� $SV%[]QuT1 }hfE�",*?cwS~rX:92AKXK8G+^*{/xQh8s Y 4><Üv\!&3$~o|.?.udş?qO.ͤyg#d,!P:X;ҹ"ͮ.&{h#Gh*�V4˚T?>ۤr\ls-hR R(JE{e:r ^H?[H@{7QLz^zΌv4?u0Rn]C.T>%Rafmmv u8+&&?k<zv`(xIğe @ Y,_W7wh{r r9x+8˔D{!å9BϾAx ՙƌKfF:^#]TMӧC8|)]•�LvU� "v{�4Pp b(Ռ벲glKDO84E(m.ICs-dBo^-k10OTRRPÅmz0 艦%KDr񣬸CP@FA,̞͈ -% 7t qRe઺W{Dxaè_OZ&x5%^eʪ1 U>Ψ ftEbg<y稱`@qK�Z9T;|.u>NDΞ %@Le!osDy-+Mi3$௑#rWv\ !ɕJ#mQWIQ+Mj꛹R{`gk mv w&#è|?eː G~&W]F3{*FeIjX\ǦkHy{w)PjD:qى �Lz}� 'F'pnQXj!+c={Ɛ2HNf!kچiӔ Q//̈sPA'?9 q0]jukY Ժ䊒t eu!XZ\$Zg-C"r<mQ q!Y4"s"=K?J9r=!tq8)uI6?4(ټdgjɤ^&o+}Dپ5C^wC5!}gv~vE+�7p?aRf$9T~]tmSH&rt{Ya76o 1XjUäo *M-Un3D@qt(+{IKxO% (YCDv#*((vCWO'OkK+,oI9¢uuFyEU Y =9a'x հ2B,Q} 6G�Lxy� 'F'p8 mP/LE@Ll}~*΅Yd:<cF]Z Ӭf6LkQ\|e'tpVSAͅ .l/\Ҡ=8oې`X[UTyhz(R7Ȓ! ?M.=ӃJvq+�BƗݿ |Lh%0#kn4t%ڼC5YA9e8|ړpsPI@,@8IaБ3B6 RG΃?w _j ȧv)V$2; +Qؾ6>{/xnqx!dЗűLǏ>ab6M?T(OknlrP7Pu6VI8҃k9oJ#3m\xxãP3S  ʍJm\ѷ 9ΓŋiS(T遧<{s9>⬲g+^qͳ5 �L� @]�g uv@KS\<$$҄l i,xM=$韈X`%ro J"Rk2N2Nv*zS=Q/S}KF9*_13]{ 0swa.KْO~qqS;Q k-Dcxjo3*\j<{tIu,|҆uقrY. mӌk#\i[]Q4NnHmEƭ; cp3*P*tsKejG9':ɧ'F] ژB|Y_0hdX<#-,ls1٬Q %T(~r\ Z\`\`=ԕ|ŧit]8{%q]-٪a-]C+Ÿ@ cCBH wTwclq.0?'&Mf x8a #FLEրs/jW �L� a{Xm =4~D`}6}_V4SG�FZ$jGwi8S_g2V7&UѸ.|FfnM)J/:CdlHFߩ9+ז@3>1nr!=v2r֘R'PkSA`N9蔏1zZu="}OS#E [z  {v$_69v:0p|Z:D:2!nƨlwLhWWVG G -tC)rBeKս [&$DI#2_PzZk1WWbM V f47H 6vDYAˋ=1 `>,K:%fڹ4GzNW.Y; ~XpH5EM4+-ș>2]ˑIj▹!nAuXU^Z%CMj`F8q~�L�  2q<ǀlkdn4<чjG$:fuNbTl>Esx.yIN؁:"/Ԋo\Zo7w/ RS."܄�>Rw$c>PW$ Ӿ7ӑNϩ�ިYb^L8%isk(BBQ<6[<+%?U91 GͬSK-8)*tAjjKFp/�L � `7.|J]]%C~�ޅ.pO18kZHS-f٠Fou,xxC4??- PCH/Essb{ hΆ±gE܋B†4<KD˚xΪfݪ @x ͢OՐ=a=,!m4y9 գ`Θ_"L^0GÚMwn ɪw^08WԗD CHm-vbiJs]E<2n؇d<u.9 Β ?ݛ{ ,,f Ebt3gWGi6h󞇸jTeQog.(oe+{y.8GuqqfяYb/EDZQT7;B$VaC1;;@*#3^(nl/LRy-ۺD9: <3/T3ҥp,oCQל�T}1'/+mMuo1Lx˥߿la~X#&y*�Lѹ� OmU*)� dT;!SJku E:$7ʺ(:|QmL"aD' 7QYviR@רD{6P1[HJB zԸ94q&D!3JF|X;k 1EM>(v!9.%L't=Tx;ɞj`1>fuɊ~Q:'F-4Aޡ%n',%)tL"t`F`&[1Yӈ(MwW6!w8}`\]yGM݆~�t[ :yYB_f#ࠊt{0GO5w[^HX#Y&o#g6(*SPq Xȼ45O n2hGEyo 2Χ=&לLO"q-2/ Wvn(MPKbNg'[ޮE4jP4mNȘǨ[F�L\E� Pr6X5 �8:݀h֜HGi�1%l& I<�n<�L\f�  VsU24;.`ĐN&$p'}}o0ϳ{VԔ`hl$\), tңq> ݰ8 gy)jx =EřŒd&IUn` ssvnU"kF2(2ySa?3=.'Aܜcϕhx֢>lL8Q?ǒ}p;s.LJд7B{Ud0+Cyݪm:4f+ i_ 1Ho`2.%ʾt@1=HR1oP,'WVj5FDK6-*[S@һ:P%d#>.Fz8o%VEdA '+ލt`59wORZ^#=\S%ݨ.55'ڦN r%65\[(h}C:.kG#?HH۱<s5eY$TkV {z~55n2M '79c,hc$-~A5d�Mk6� tJ'j)s(P<шיӯc0rڸKۍ$oml(SfEmQ3 !R /[.0-sa_Q$ ^L: (XD/T/<�͘w˺ohxO, @Q>NS#cB,K=Cl%?xl WS(wNLW |{ G)h^}8WXԢWԧJB:5-g1NM >_c:( NxQcj&`!Q$!"SROBt=xCkv\KԷ槡O`=IL$ || b$*(AGXax=ֲ5<J⻳Q|Eʳu`{x;Y]d՗!A ޺tsOABxD83460ګ<D^ilaYDh�L� ^�D;d* gץt{k#̳m(҄ eQ O6%n�@}A̩>0wE-U<L'7q�3D=󽸘*d)iyZBMf胮(eJ=VWzs_H́rM(BqIj]Me 0{ڲuoiĊBG#ˤ�yD%QH` Þ4^$ɰ{mKzD4 L( NP!�M1F� Bkm Yeq#+FJeM6FH]њ%eWYerY~k<Z^93;n -_ zȲGqSN%2vd>jpP{ awq x8_é@,JKJuo8va{>x(N.}T?0-؆]5@?)%==j$2md~U:Rջ<>-h)ߵn&=CJi�N<� HF5{ zI!یXsY)>=2"Ff'Qkc$}wѶwhi6f6R7`SLpyCNK]$=or5my{P%v0\WP9\-ȭ9kQX?ݑѴt$%K{ʝux% .©d.϶4":*e4r9SWF^�9-c<2AYvH>wx"7�N/,2�  bh(}%1py}[JF<"V&*B ^c(W{J_t0m[`kUʨAHZI}k]e~綼!D֣Rîu7^!isן(w̎oq]aN]%OCJP2bUl)QΉ`%ܑ{*P'Yc0J&[U^`jaLM2\˲ ~&)1G ?SR`+�Ej,^]v`չ^qmIRO䡿s"}\H1Hc5?mhj{ȱ ڞ9m0'9%+_!]fEm)/RZ,R.JV.~ZY@m{VzP>4J~6Ijz ˗?X 'QF'OB2~QaheR]x%v5xyj*7e8u{O [_@S1^i}Z DA �N/http://martin-krafft.net/gpg/cert-policy/55c9882d999bbcc4/200907121833?sha512sum=f33b17c9af515bd98b2927cb453a992d3d7500e9f671966616e90510b9940895108d241648d1a0eb46b32bcbf3251a136a6ee1e2275745e11bb328c14e7e7263� UɈ-cU轗-|!sPJT<ʑr=rRaMI GkvWc2m[l C3UiTcbY9v9~-]r@ ;2pEpSs+ɧ<4`\SC$N:"L;'(L\I1@/L!߹6ٸIWYi}" m1@gL4HAVa�"jڇvZb�\$La$W'D"%mɃFSPחI!(LMɧo3?O&e(6OkL~͏k82M ',/GP{ɐV[@LcTeTa=zґQzD&#:&ThaV?6sʹ1b׆w)r{8$_nntd|% wGsf5+"l=ZqdAuϨ �N/>�  ־)Pֽ͂@e򍞮%o K]; L߇rX@]aw_ sټ),\̓&55#^6D;/ hϰi<,`›bG5Wݳ`5oh1<(q,<4 a89�)i,s '`ߨ?L�awt*|X{rCx{ujk ^ §_tdB(“_ܴsJV΍jP4-0./DiKږL᠊'ܛtHC$Is 7ϒ$o=BBȺA.~;C|\4\۞#ۈuDғĝe{8 e=Lv9j;s5%qoG$BB8Π'c{j 9¡ t&vM"iĵ*ܔiٌiG#$Ȥ) ҆u];ŘhS\T �N0H �  bh(}%1{�~ej*[=yCH]kŞY{? :O]xT6eYiǏآbl 7ou;P`L:'JW m G&nзLQh: sҙ#<>g(}7n2`H]9YMGR�Qv9bQKo%a}Gm<n(a\#Tfg7%)<1Z p}&Q]iЙ|YQ; " �>;7D*bǴfnߢ$>ݻU m &< �hu#6G<8p[ٮ]ޢ_&lLdIzݩP==_0 4m\|Gzlkp:e7KL[>䏙$]p9U_`'klZܭ̯]YBS^2MYX3JZz/9[ IM#dVhv]NC >Q,1]L!v j% �N/� n`c}g4<ִCbfCpl/@[U-wuMr97A!zRO"x֦t':m _ACA*>7eL*v_g�%>RSTɢ>=FeʶЎtP AD܅DD+cH-PhtJ3Cc(Y)%LwF@N�mLL{n:j(ǒP?4�AemJw�LgyfonE[<{0'CP\/agݖ_j$/ "a3ͥ<F[XL 2)xr;vuwZ'3ꖸ^QሹkJg-Lh2(AE5vi+!znw oy ](\| K>LyLm +IVo+Z\܋!*vXK`;�]rQG:! �N/� =%;jvex3v(s=X/,H=ՐnO1x-c{mk(jTGsÚoJ}S8 ӿ Нl#`,%l)mt'U*cIxP;o߁Owzm(ZY>*a@j%hO<ExzxTbyYI50 :/D"Hm,roQ*S6 F px]ōpwz1\{MӦ/|\Vr e{e`<!r1ܵ}%7;e"O{Q֤҄'(�SOߒ֮CU|I[Iz Xļ!`IZneu!JtQNjl9t&"$*xglVCس8!fVK[ Jj@+!9YBw#+tW@S7+̓/rͮWHBa>EKSɷsEYOvt`Jr>%3hT^ZF�N/�� 0}VX�i/6KYAXMn�Jd-!W佋dV XP�N/F�  �lY�+A&5|e-}24,ef8ԅ~oGKZkty)>Amt+ ['?tE?\o6|D)_$xZ͘ Uqw1s3B禉]X'Pɷ9`|p#Fv Vq, |x"UƘ^1\SڋS}D`5T2ŝ>8v|{HAUdN 6*Hh7tu9}"Aðֶ- 3{}P [rrzDIv4(R aSt +9Bpٮ2!_8Ӧ$4�?A(Xb '9txwOus6#BR)ӏdEf ɲWWܛ iE(+0$dAod]Y5u߂`{-UYܨѣ@* �?o3lKXOg䈫ɌB>ԑ�N/;� xocVvׅޘADk[hSڶ [i(tzN1jMSvKΝ]a@U %X9V@)Nc0�jzLA I Cv]R݉ar3wL"-x@:{IF>ԣ̯u&5<9[tX‡cǫH=t'x.'<2Imr.ePY/'69WNG'9)rnT(4K=J{b9̤x#oz-ÛpWZcW l 7TɹSH٧k>9n !?JXJ:Ǽ6w6?W^'S*KU« MU V^.x,VcPW7{% i 4'̿)Gw u/uxfP֤U`FC.r^-,R E�m蠀 wQ|nЎ)Ah>k'‰�N1 � hx ڨt]t~ !ʻ0kr,]j[`";)<H)aU:i^Hڄ9tPhAnE M !0B0ձiO?䘲b,F"TP.d:((DO W\K:,'|HU{đa1]gϺ\)0Ùj<b"putjF;ם˗uJ2V L & k0-C 4ܶ�O3Km ![ؕU*;< Gt(Gb#jL 9,óx3p^0'z"@"Aqm1kUuSa5]ХBhd0<P@^61oDHL9ǻ[>,<S(ctc9{3NIYܚ89i t1.a[C~�mH]/qƷtYUC8bO^rwM F]}4o$F!g)H �N0p� :aAhAN.䧮-!%W WN2eA%I&!s#U!H)C Eݪ2@*e5i ŀ2jj6N Bxπ6”&/0R2ss|IJ|2_pcE 9dd$JR9O QlRNH&hOtҰ*~R᠏hý{!]75\>I]X|eHކAaaH5ySdЕ) ;cveh1Qd0,wbYF܂ޞzfާu5NЭ5o*/UR-%f2$3\/У@Uaѹ((O:WV[KX"ؾz)(\x) #B]GZI;fr\g>LC$xåu.oS~@}cM1>uG�N0j,� PcM:)z �/‘g[kp:Xh7ocqp)M̱EIPwaMUOo՚%WπMۂh֟RT@+p[p;MϐG":'E9gqh1غd>7ܟ4Vu8ZTS=srľǵޟ'#`acL_TLum㙵֭}(�%")q/e/$,MY4|zg(r+Jv9̠Qzu=*^{G9G'.^l)#;<oJzԁ�^PyG^~t?(stɜ:okkh:Lr1AKu.W"W/B]I^hw^0LJ  6RHΈ27H4foc!!pJe+R<"Ժs]~!bPFj&2({vQ[S'CZRyd![@Q ~m7hOxֲ >7aÉKx@,L Bp3"PG_iv`'Uz|A>� <l 8*)|V!;MN'@lg9m%ǐi,ĭz-YhN;0*xU`u&d~ p/rS4]m XeEc l! v [IY` Px ৰ N,hte-X{$ ռ7 \ Jp0&�C-@80a]n<=eI$4me\4M81y9M; V0,,J 6($V@53bs" Q֧O2oeKF衴*B\cN͙)~j-.UW%fsY|fJ?g8AP+H9fcZj|E1eI<q܅"&i\̙H|6F�N0j8� 93�3(HCXL[Ԧ�wg]OacD~I+ՈF�N0h� ʛ#�`FЃ Vxch8�o.^&2P-;�N0h� 1P<mcV7m@(cVӧL>~GIvDDTчW>/).@AdM6 b 8s5k9OQX[ % ,Ǟ-̯vNوhws- ˈ2# @!5aN)sdh,#4է Jv'~t S0"<$aP8(+2Dcar0yvY>8J<hIoXi-ZdhtKmT '~kE?ES~(=$>Ys4r4 հ џ!d++D'JƩ /l'Q)feN&oF"*Y;d❳b<T2ECXso,@Jfyѩ\Y7P4Xnp\nU <˭+ TPV^CpDž%t �N6S� 92߾ 4;b z˚[ѕx裊Hjf[(sGD{7@Cۧ*4TV=$;"v@a҄XvO<>V~{dj9cDð ZKKf[cOu"DB^XdNN&Y"�h=\WORTo?{zlŨ K.(+=]E)e[_V=3֐U>q _kٴLa2]Y x0wfJWLFxu1MW OV'RN^板OF0Ix]Y\_M8&10Oyjc*p,3E[QXm>ZyT$ۧ%OzC$g |זtͅ! cU^>"rHc!A{CB2Q]3w>P<}Lg2<)ĕ\;18N 2Bhs7aF�N81� zn�M)eü! �Kt-t,hH0a�N8� 0{zA8�P$mn|ϯE1˯,Y_Yˎ4ӃA*Ҥ{֌ 6bZzg.'zWR=fiYjOLJ eGLwΛH;eB؈PA�N/� CWy2WF.,pʣ0hb'uwZ Ԉ` X)~nS'!1d $S&HxH^E@9*_N]v]Fq00~b5Qdrd.-yI%*|;bے Aa1핪&v';cN((k}X}Z@ IP_ߔl�IAu_d&kE\A�Irm8#@K[(}䅮pO'=aP<O\m !=0J0VK:dѥOKb>%E!0uF3w?'r0_0 QQ1B0~ $&U9y6q+sO^m{:epEsViCr[FHϓM+"WcGc_0V7-߂<7ce<kPGGpL+sM%Ho-BMt.JA"ʉ�N:.� 6oC_< R�1R;P n!>ګX�+Fj#&<]0K7,"Ft[ D{jm#3X^kpk#'b]SEB@Yl̄LDbAϕ: +y†)6U]_GZhـ !{7=(!8,-ϖ}L q`̪.;R]$`7ش<T 8ߚDYwv<]X 2u>^ثz`6<ҞjFUa(?Y|L&=[4'xUbYnU jþy0D}_,xD%]ޑ m \ܣ`wZ#'|߾B'9/N#X4i4 gVCͰL4O'K J;eo^HMv;|=&xcڪ\PNh}@;d.dTS�N=n� hl՟a;z}6lԭ_V`K�*ֈ0Γ6C U6qWw~n{^- 0Km%)bDZxq.jȄVLkۛoo;%|0NlOXCjy4qp"CZ՝6*51ٰ*�ưﺣQs]x {Ʊ˸Jd-@5 ۲5mSq8/kJٖ;T|)گr3s_6g9LBP0 G [ u 0S`}`QqƲPP0UJ' R:?<S 09ODnGBҡ#$GROSvAm7jl:[C,ҿ&4sa@.ZmҨ;҉&0?'mי+D[٩- Qq7T( ȯCah(,a:0v c^`G7H7"$L *�N>'� 1S-3sP�r^[_5rc4xiYRU4} قU3F>4hymFmoez)<`56+if9PoĐ�aze *T@n_ &(�YMjFQ4FO}/<ø`?э$)_+^c86֤M\>S#PK< YMe :R#8(ccG$@f%>Ԙ8N%�';JEĄEj K&0}ɔB©w'Ai|6VM*ݶI]V.Oڅ TAW$G EEgD?~b'Exbf?I|̢J}مp {~3WU \cRK>:N`3SkeW*Q"L,J`h;.4†ѴJkXSJvp$̱z5cC��dF4l"9b�NC� 7"Rd<U6L;�w4H`xA͙6>Vl1F2 s314<%vCmQ@P3鄣ɖ=sΒ-mv{gd.ub [g54ve~)߼ow8(bZRm$U2hv龩MjTq'qHכq=Bo>}TűhONJ<#p~GVPj<i]/;V#Q^!"(vBHH!WqD0Wr}Slj6zFrDȁ8ڳmڽ 5itܸL�\C1}<*XP͘5G3uIVRh.c KZNvMŕ:^7q-crFzҼZM!q2'GL0QS8g4qx;A "_`̥rVkHWZ�NFȩ� WFiUSk=g44\$ߟ|Œ 2 Zc7z?n:G7\IZSvPHl#LaXkd'M\hBfNvĖ M;]~,#I'h:=ڱ_(0LTr0`2?%RrŬqJ"gS ( ͆HgaL!PB"'#xd"tqQnEa۫ߪ4\&B~r@0vLF̱L%6 `pS*yI$QF=`SʦwբF~̐.'ՓYӶT& ӯ"Jhlð@t3I$GY?p?-DVWNl�U*g■iuZ{�_\е-Bŷ-5{+l,ۙ3ZA) ~#m;jax6Z"`kx3i} C]w)K25F5|F �NG$� ~BU̹@7�J5ڔEiFg�4#^,7 �NG$� гKƒ Ir _uۯ%k8<v! *0L8[Sċ+CҾ L*dRRJZx %WZ :uh nfL-1<(+b~9C4cڔP؀26Yy㊧pr,`BomHad*�{2Sp nf(RG?7T  ?qR ] 8`EFz’NE>~v^t$-]Vyrep �g�5xV0bxe)>)poEj/TVh P֠ 1(, 4:z$Z\|LNa1 >V'!1^u:|HJSm _1P롟2\GMS-AiʣU-۹5�'S(/|\EE&U9JPODZ7Ѹ&.TsyDGkϖ <NǙ9eE]O'ܣ;F �NIP� \_p�sm�r?0W�&@52lBRՉ �NIP� ɑ٫E~-L.*j96lEg軚 x*ĕ j27y*dy]9z�r<QoPM?FJc'"N};^}OS|t7vjFP]NPb{7WR3KPHaT+io @-1O@ +\pgl輜6P.`mnM|Tdk`xZ ='s5g?iDlaFhďʚvo  oLzA lCOzwjRmmqjGg(C,=Y͍"> SF.b$'LlBۄcG<LҀn֫  C#Լpȁ ;_Xg`1!z|x(\:�OZ|,h*hk"ejU>òj:duEw/mk|zD-Dm7&ĝkm<k-Sm U[ꁠ �NIP� tтM\i~VG\j0? {/lĐZUZu s ɗ!ڹL#cu%!c/LAQ/T1`8S|}IX*.{""2%Y lF<C;1Z̤ фES]PM^&hXh@)#G[vt$p=Z> oE?Ln,$})..:4|?9͹.G U, /B8f*բ$OEޕU(V3\ZLK{n)Ho@>H�dSnG $] >0lו6G+|gc]mx2M/]k!Gf_b0s|mI =4L$~Ӟ3[0Iz>:˪g/Nrԝg_E:ɓ0אv:-L;:5 JR6ŰO.2e+jeknnSBg-T)Pȏ yj1G'/!Du"x4ypSuU*$ډ�NS?� 䩑t+8wcǂ*EÊ#>| �j@Pp,`<X7+5Bbz,$솣vV6Kh{u^gPiTqPܪױ6m̊oXt:}u_dNqM18 <x4KSEY*1c,+Þ߮& lࣆqpaKY �TO"X-Oy wY X@RIeF$?<ڹ #IFU<�NS?!� 1a n̋E@o\ᗁ#?i/iiq4[̀vqVI1sĔƒYЩ&ن.vR1*yO�+pS.͒`1 ~ǔ[ W t>vr{LX0ԯZw%z0ၻ?r-FVCjmCyAОEE+` צUi{@Pt !#-BiKQq]n:J2ԎF�NV$� y~�EwRoh,g $O*7�(/Yl:.M�NV'� cOKUhvAjg<}g P%>gz1)M3ss'^h%4@:y@ RRwPQj"_M EtO4Du-�vnu̸u׶8|p=PO&pA{R-? `H0zw2_p/Sl9{/,bAHy7(X<`]"/̒M<%YBZ{+e*ԈYbP*1ߩOӡ=yhFQZ*M,i;6&֛"qԀ=E 2O&qEZkKw:(& gk, ygAK\%8Clݍ g~K2P'<;{xnT٤�;AB "${yj0x xM*36HiFh)Dn (Z@yOCA fHB1 ߡ;v*Xp0^v|iR^Nik%XJTzV%�NX� K.Wޘ!e"'pqEdMwCCD9>*jmi,zk<X7G-J<oE'iR;Q 04_ I#TՀ:C/wMJRO}4 TLi|c03I )>:& YP'ю,fl}5 P"0޳+;5Մk>G8/]G.(869>Ik@r rw֓&NZ9YSΗPvw(%b q> 2"ƛ,%_ 5?oZ\k*LրI%1_:1O5YE3~} qN[ER0w /8׌߰Iqԯoހ1,յ'-6l�˃1p`M*0sjm)0^�Q|grێLekQ⊧\pjӁfGo �NoF� *!z L{ 's˺3N*䠯y~w^J M %=k/CwZٿ|O\75 UBȉe X̀-!ZJ˓bbUB|JCuluUɓmkGQ0 m{=/E !)hZHǯ+ߎ i פֿGAOF-GډB+^8A鋧C픊 z_茱 Vѧ%C^ >*;قM,09Ds3e|uх)>6BP%e\7.⹇T,[[D[5Ib/AP8" Uke؏L3io0"}~nIPi[zj:nu_X CĚI^2q⦣sfQG64#Mxy,$W4Y7yx[LtK<)ީu F,IS8ש"R �Nv~=� f㗃/q�"tC-Ę45 o][I=| .l|_5kAs+An? #yA~6gjӊY"$�T):,)\"Dhkp>0w�>6t@N':Lm<aWOqY %xX>?Md"3baQG#{�$3Oifr"jD5*�V+SXפB\HA4ceQ僪m.Pb pnŒ^.a@vQϴȝ:VT F~})+|NU$ܻl)lSS_~h!B{VHbg͠S^K,x3wTu$((*s`I͠TTY 6L{J''heqɪ*S,g_Cw]n!4p=BƦUA-5$5Ist-oq:IE%FNQ36K"}1i-]r*lXK2EPv=�N\� J�M;Wz`o-c>%1Lhq8RJ4$lתK2Pɓmhw?)T;YղEr`ʤ81;2℧PN28WœzZby$t*M?b.&jQ(4 9$z?jhee u<77�oEI/\;{ ˗p\[<Ridj oc[ C5AŲ�N?� ky$f>qZ�:>0yow#Q˭F "ʌ{{)VC-Zv{"٦nOs3`.qGMyqW^{ N{m+vx5ȚU ? v *`Tӊ܁ M[z43 wPT;Sy5GNc?;_k' <UV}E!gQ5!xz^uzO`h.A]j2pֻA=GC{~]:4@tP|̴6l˫䬳">=J~d1V]+ HDyċPf%ǐ; 25(FU>#N6sM[ڡ5 XiY@?/9|‹XvYVs&rixBN\m%t5]BB@Qt;ZI\&+싅bf}[_<8P�Ne� ȸx*C�=&s; Q$O. Z[SdSʰr}'+~u<+Q{g^1Ha!Z z_>$nRͩD}9d*e#.*'6E&򁽬B6$*]tdN!{T7"mT%YgʍirOvWy.rWd;ĺ fEIKx^[TiQiQً G }T5xx3ˊN8!Dߤk2M-#6/ (ӈv[h%Rbmx2Қu34ӹ*vAbfz/kV 6Db3y[A ;}=Y<ZUjU8GDWj…>ʧ5K%rݴtp+T!>JdL)&$vwQQ.`!�O-� _%|0]X8UUMndjAUR|$".%A{T36o) ;_CA:gX"34WD:Y<;d\ΉZc]q!x@Wkxbꑯu# !^V&NvR&:Ԝg/;+Lc26tXTHJ-)=ԶS+mj?�AmA_ UgmO'Q;X FE@DųcT?acC x|Q9`A#v<(efCkLG qdQ)O?&ĝ=P2 8+RFtu{&ၴXtòm5"|T�I%)U`1WH2ϻٸ*%Qκڧ/ p^R=f@ƍ:9gobpsQ/t6  C꿇ǫNόJcVyE׹-2hU0Ru�J$� w/<d�W ]S܂bTk;@3qLVۘԧF"*i.w7]>8�@"47T̋NqV\*WE�av¾PB$Y?.$:i(3:pK4eDSTwB;gz,d}Z#}e`a,#QWj}HKư>c: 5q;ak)b~"AChڿh=nt,2%4B@>'8`_s Q{dboP @0I'!Iا3J7]C osH]>uY}S5G `jKOnIodLIجHf'O=D@4W*oz!3:5#^#9cTl!$}1iIч5K j(|^=Yz�gZPgJuxFXeNAִ:Or`FgT;lNTg1Vp; �%I> f   �� 95 =C~OC1EMBcѭ EQ1s}lobIDF,amu7jYjChM`Ob= 4xo""Fa:4yasR0V,k _:Ǣ EھHBK_m8W޽ h%BǻM%rَ5&xFYukg$*>j5xܛњU+ ¾a 4= 5d4uMGJs ~CVL2G?IK IKl7=|Įǒypig8cpxfő F?c!9;R+~+fJj?O~�[R84Sqޓ!i@ME/y[,v2sgk3 0ܨ\AmʭD SLÆW0c X:}ekaԂ5͜\A )jW}{eHU(a\>)Bow4Kp2l[숷%|\8N. W9uon �Of� #>,$70~nQJR{E20-]}QfD-lx6_,3{S&HH0khNRV5 zDadJ'e1c8~ ,2:$w2'J_g$|F95D¸Lí]t! .@+vlM(/鹸dm柼]&FŠM%ddjR~aۯG;7>`X gFcٟ֬q(.S<mu)i:[&<C osnڸ?xeIxD(? gXrk.,j q9$fZt.׸07wbnTT3 lݱifa?R/iRw e�c߷9R;'kCn)hG龗L&~RlSAPiB I"qzCWˣ!S|RKPEb|CVւ@$]X_hYf'5~ j.\F�ON� q9�kt߃T2MBklS�gvDrRYa*& �O� F9debC519vNB4Ry^;l'_qFX| 1蛙?+:J1DYT@z((Es(*.Nt�6H~8Pp>%GYMݺ(}}쨔cI5)X}Ws�˶6رB1{fpe;jӅ|bsgvQRZfk,wk �"2ݿ4dOABHCCçr4q߾;_=Lvs= Rhg!+ץ$ڴsJ6QzJp˔S/"އ/2F0kȠD 0e?bya/'3-+߈n6J#3fx\g]wߠFE"@D.y܏ko(F%^!ʽ3aAX(6VG<v/Gq!vK LTu{3~ǐf1/C3pvV}%<.r kT߈^�O� FF$�'Tԭ6%kêN[L8l�WXe<z~4&wb5�Oˣ� hNTo1Ҥ� 8xW<B+<e/&-9Hd4Gw=P~M:4h}$5eA9<b Ha1v sk0VwňfrQS_-Z顡_xGv$oc 2x#P2( ߧ$S!e_A|Ŵlή V3#40smNj:<<tSekI6 �.$⧲Mp8ziBGawQƐ T: qRBd[ƤݐΈߦB]**3qe=XG||BnWg%@U@5~zy?.,⨜p<Ŵ. jQH}[ӻ\(x>" ZN5e]5j1%#8Ȥ&T6+> ^Lw7ĺ6G IN.S2 2wƞMG-+�Oim� z,ۋ5KQd=W�H lh<TX|z>K ֯>+zkg᯺ش!RDrz6s'&w> uɟy@k؍{?U&Ǎú}TͣN.޹-= E;pF5=J\py?<p9Ay6[f)أ;,.W:*ǖKg&H!s =Y>İ\ݭA: 5F4td䢀uD9'1f:'^B^pB?Wj?:0V`&s[`Fq滺|aBЖ@2J//a|x@ɹ,/RXJmFrmM+ Lq�q=I#(RV4X R"~ѝT>"Uf=1f.mJ86V`gT(J1oS�O1� up<.,�mWЕ?DqDkD~] 25;jQ4Y,`15}RA󚹎p|hS'*cXL* 3 ZxPWi4_C*Ksq &-r^scG<ߋþ`|_n._yyg)Wdô@/vn,Bui2 :y(9,>bńm3=yg*v dCʼlH?YZ6#A*YbELkYՈQ,- ch=Qۂ_"Mŗk"goU l(^"9r3X[/U Oגߛ'xo(8MI=7e6a0*^|+-0+9SIa*-UM%\mh1μY{ED)zy޽U&[j $~(†aU ]v,=V _IV1q�Op� o4?}q ^@ڊyƽVmS8U?NC!F!N07SaH~' bI3HKϽl;b߸㨣vܱfĆWn t,NN! <UpG=5;8i[?栭RF THr9k`ﶸ7?muH+'bz!Ci<65`EkqK,r)=M7m3Xw)?w18m,U2z jY){=%I$irwmD�BYpĞ*lG(!& #+eeT|8;ZH<e2Ʒ�QukDE'C` \Q|P'v^^3gYE  [W?ȭ.Nq˙o˭cǴ4u!;ғQb~ X >�P�l� {57|hUUBnz7!c,Mm*`#33CLWԯUԒx8IwPCLM?g&:|xāWfy w뻥p~-0O5x" frhס%*GW0$Cm=]4i�;Yм1F+cXڮ" ] 2]z?#Msr @cLdA *wWX�- �P�w{�  d6A^]u&cu\)K [P,J\4I# `#{]I ^<=01]nH)9}qMXxI?x"<V>(MN_eibH3 ?+iM*AH2uOGK9g6Eo򫤨{3}JB$ד̙M�lSVgcn�bAkҘjLP yT *zItJa=M9~١Fu}=o =q P3^P`%]ϱ,KD۱bBtvyD=TgrYb-04]QY[]0 5bNX0ŰŻ풣.@u-.6҂c9-6[;DC3Cg{-\(8G1)"t gDGmqJx WJ*'O-XO THS �P � -hmArX i>Ȑ"�6JQ%@vݓuV#_B)`㡈 uUnӅ\)IXPS*7v*6ܞ!nX.aXAFFR0 GMx5 pBiGO]!v/ϋ[ccSG؇f);/LJU^̂xdR h~聾.Bd% ܇ m|l0ճny֑)p󋃥To;>wNQs2O8cV5ƔompE�=$8=ܢky|=]"�_E#5΃;OSeMvX)nǐUNp`+M\fEr*?-&wfjt֩ȠENY\O\:ٿNv2Mµ މ�P?�   *e�f/4Vy{#~1X�Z j\I(/?[c3\ z Գʔ:cFHX)@<^/%yYï@3} gf@3\Ef@@n31ƹ|2㕰𫗳2!q?\W.@{-4]&V] mJAb[wX{n 'Wg8N q]s2y~.z)| HG lǀoD/(~<z5Q`21GB`0]/-GrX<ޮ rLટ܀tgfQp -S89sa^첆 A.)3%F\TP q.F!szUiG/�$4f=/~7,q&|W܋IS5iqJvykm|UNꫫ! &ڴn. x"ulD�tG N,}s�P [� 5\ 6_k;_oˡR}r[kxnmԑԡ6.3gK@leB~!D(#衖@R8CLuI+]4{twzgB 5:\6Ø:�evIq4Pu (°ֲ JHXRtG!;A*#$lg<~l!j& ]XTvJ Ec|78l*_3 Sy87wlhHLdq dG I#Q\`w@VB载g4OM^p�QK� 0b@SIRMfqcx#M9MOa6Ŵ#r; y+�P`� Z獪.lf,T]Ь oJC<f]L]]9[E5ȷ D>M`k|~m"y0Fl?{U' Dߡ`k<]ϮhwӴײNe!H0*clu]7D'QiabgN֣A)El0}!2yHe1l3~ PYppŁ4Lw :DBO'@P] "X357QtK$ٹ!3p 1$̊,RX_cW>͎.>O3Np@/9vZ 0ج7M-uS�ٰ`8V7YlnwmA"0؁Q}<Rk~��!hyZE]'ɚLWm&Q�5JKVhX LyBbH[d#ÖFsktڅV~Z>%,\ b*OPl[&GF�P� yv;�/3DE~69 �\Z;dcGqd&�P� ؠ뺪: "paK~{`uA̗B!_Qj%%a<_}?zB_9iRF(%1zDbeܸ_b/: {^&)xKfžcVc_/XFtv"?~˞a+=ߟ@JK|cRfb@z]׺vT% ZÏ2 i ] fz%h?vJMKAG[C YvCӦb*,<7^ 3#JatlYWNy<j` ۙMt@G1Ch(Ӳ[8bei1`d5 ';Dcbbd�{9z"ȸbHc8�'$ӆ]ΎV>d_a5V6pqwA걢NG᭙/GO8Lo=2N+$nNpN�P.� 'W_%K3eq9-F<kwt]:IN1N"K>u L&j** WZE턝 K㮴WGDBATl?yf[w)/vn):חLƾ[=2F&R0QxyRcl e+#yKQ\* 76MR)>KC̃l?`Kͪ(3v>NXmD´5\jw;ya _If}FvocN@lsުE+R@s3àĜ^spWIzJ{<lϷŦ&7(Yz/CێɂH*Aנ?;Q6GdTʗarwɲwhwvyE0j8?0Έ(}LCr5pZxxHd#uV*RO)6(wNZkmՄ|RO_OQYfv{~T@ �Pɇ� '�"Jkx3|wҵ/*#&]bDZn*DXس$xtI4P}a侘qʫ]o#x<}SzZ EK x\ w 1"G1T[KDFLXV;lKcJr9~qC2|[34L ]C)l |zU}32x+,d΋\OP6\iI4/c7>vXTSZ '!jƴ 9PCW.~+oK4w+vY ?zk6R[ʶu[| uN h9Ge�D: > dk׋Ru35XZk)5YHy?T[wp3t諈^^G?K7e^mlwKOY|{j^27)q/:|Ag[e[ 欆"KopFel 6CZIۉ�P� 7+-{�P"a(Ъ.tJXj!@MX9D{� Zwسm n8]ޔ#o\dDrדb[sOGtTF[REg˨5Gs?1"7RA]ʛ$f˲ GW {زϥMB]L\/"d?cVA!’_y^ ;d,wl+4V G}p jYX" o]*\\njda.vPM 7_T~Nk4"M&6kr$Br|qzHU]H ,Jzp P TҁÏmKgOHdžO>k"5}{|;}l[BT{,�*GZx2#U PEI45 6R]% ~�6[e.DW#`W(1ݖQCO3OfB]L7`d�P� t\Gf;�1N<0΋CkM |#]Gs!ʂf&B|zzٰ #)}fv!z?!i}ZM5CO=xmEPL5aVw{p_xkCҗM|W2sp//}:?[m`J6Gjv3]pYb_7pp:gFEmW籱�kZS͓f ׄ?/N)<|&Hԗ^vT0vNUCCa~X7�}̟Ҏ(9INks#X[%_ ".l0k5RkVN2M)F՗r>PYQy$*ju;A /cQA[ϯ<5%O~A\_ywv* mbDBbiKf]K_sI4/or,'ePI;d"Y@W#(#dP.mU�`,9B&�M,� !RW7Ofiߞ'~R0[Pv d} n~f[KKw!qEPt<5_ dS&:;kF&1K&͒iZsEwTcn=EZZWN'sEؚf*x.BrObz{)k;  z6K[5h˫d^ୢJ?G~HH/1jH '#V@ҵwÎi_^H}7 1XSkyѮy 00榌zˊo ulMTvLa,',BFVa(~C>hvդ@NPGH.ę,ذH?(ͽPb`78_›O9O"8C9ESP"k^D4:&ӆIB"? lWe`.\DP0$Ąrޜ:bh Q>s"+?:YF(�O]� xyc(~f|'ZsV8' . :Ak-WvCL21G8Vdebiq$s2MfdQ{%<"hfɬeZH`uy6*Fz{8MWY3'43ȜmGgHkm_@ 񤐫 zhxYԾneUiy ?+.9|wVMRh ԽK8amhPI3֡+0dIـlV^ e@/d<Z^X1Jn󸁕 (Er2&CK@x{iW˶ֵN+L~UAiVt-o)-V{3尕<袸rFKl"J|("MeD\G) Wp6@GoPaAzAr-WH2gFWܗq08Ʉ(&9&5p%腰NƶґҸb�PG� u-ޥ&u|K=mvXJV fj ^2HvY `P%SQ# *xÊfp=Ϯ ,8xz1=zz? ̈́g`WjWt^nq znt+HYE;M!)-c@~O ۶iK�ڃu�ZqpQF6~O5{u"Zx=|ďLkVa!Z@eHR&n׮ほ?iRg*^K=P�>~o1U5.m!9cRv.U %03468y1uF(W&";BAb&)J+;5yP=ppHWv`B=r=3ꆷV] +Fŀ\ȟ?̆/ᰔ޳wqӅFZjAC̹̩cOS.p|[;,85 +KEl6hV:Rڿ.*N�P� xAӬՃ�uK: IMFrYYyuJU7yrof4o ŀ h?snsH'ECɅq]2V %u'XtQp !Uԥ B1N}ٟ[DQ 9rh#\b\6yM:#Řg1 v]iibHD?4tLydіABh0o@]nm=YO0j;])Ld�@}s aD"41^$.0ҍC/?V(ahՃ@\70"y;r5~xzb,F6E(02/T3ʑL'M>S|bOx:lΞ-D^3Q2LqQB82bAiߓ60 pܱ(`o|L,:IOf:!8c3 7sbMP8o3E="U{Ni0EO~] oԉ�Pz2� ƐI<EИlР'N %ʴ[}w#BWQnol.i1M6qfmUU ;;nj5:u7L[b}"%HH8U퍻"M9pLp-璻E"~ypAxnbl=|69T@O24Jyo[Y|rf1L][ 8_hPtUb2$|EM$LW)$s̗fO]y*oV$Rrv|({\{QB9wiE ]`dLj h! }jXMhtOXeaA1R$(B`?T[@-IĕrY/qj dn:W0dGӸࠐY/ݏx4 g׉yQ9lLKE`ܦ\\3O}=rh8NXw.7h5þ0`a5x8۝IZ�P5u� rɡYe�N=J&r&u툦BY<n/GD|G7Ed!ܢTy*l<~K;52 ?O$=΄�|cDjjC,θ/*&~rdɰO) I�)%}1Opg2+ RϻkW4S#wO_j6-#<Kn:VR*%:5O "w-N)i}P\\ʯ57~폱9,Yr5¶aO46?W3txU,o*~xO;qZ/98s' ^SZI6aȅ0zM kwfV4DZ+"0!lvI;T E򱁜.VMh.2 ՜DPy 4nOV��E-"-9$H]]2FWNuKb{)AXYk :U)ŷ>N|r&C4L b{$<^]쁼%AbcF�P 7� _ #n4d�7{`)M%Q @o�{q%ֺ{݉�P� O<M8r�) {k<g2oyᤔ݋l`򓋁ILVũSP(]BE7(Ѭэkt㎗dw^l4~@$Ⱥ an̎^~=$LZZaHqMwϜ^9d-b{(FalngxpH`!Ŷ3ԃR/bKh?d6W.58 0lD4MK_ZfJaE@ YU S%x*ӞB¿EV\¼>gv)jǤɌZ7{iRZf@3)V=p4XR" O}@e5nAb?Ue}{#.ل>K޲:{W_Lc.ӅpծeCڅ2W[t&=zbTV8, 1>9MB#H%׉/ImѨ_ܽ�K~� Bkm M.c앢 î3@|L1ⲷרzSA[&,+i6qmMb׀d3W{Sa4 iX_�h^7f zA NMpfI泋eP~~~;JzewЬ3 NBSU/ɝh=~xLGi珴G8e=~碧"-j<e7WzrS5 w4gNibsL?#:f7lqX�Q9<� )ȴıE3>ylnk-kj8iw¡'FQLY.VgZ1vGq'ՔJfѠyi5Ga,h\ĿbJFR?zQ҈ Wx[WDMUڋ<W~E١TS(3D[<+%T9 ?gbFeiiklQ 59.3/#V/VZ˾Ĩ׼mϤ19bWvߍNNe�$rf}z`L8WKe總>>GLʹ`눕B޴{!6~jH(ϸT[) O`v:sm i͠|`]zܶʓa&<~=F«rFZ23EB+1=NWڡq&3GjMBd"%Le&%l.f~7;"1#EMZ!qԍˏz)Ֆ3!AG/}h% ay}FoZW.  �Q;� �3ukUzSGꯑƿ<<=4UCIkDpJxg_4]({_Q!~~IJIwfò+0 Dެ.K)Ų&im�m)#'1XGy-yhϬ2oUYt4Ԣ 9(EyXǞY4P\0??{y)|2:W�#M8Cڦ!B?6JQtj|ݞS\mV ?49@Aqj\x~ﮪ4yY=u 'uWƒܷ؋x{\r7 ~3"@*!^OGH|;|U=f1+جET*/ʯ8ᏖWhY\ 58Jh靱󳤮cCm:x Rt s _K\H3s/.f'lrx:ʉA#ID\A:oWPebצHF �Q;� K9�Tkc)I�a"WM1�Qi� wf NΗmbP "T+K{HrϢf!Kfy^Pa('(6�McGW^}`!U-қw0wX :w5OQŠ!E s{^y?ˡVdT=W3 R,NC=UwX> Ds-~0FU)WV/dz7rye΁@'qΘ6`x;]Zkt$ݐD&]L#FBIA*ZZ G�Q|� <@�S $IzPF"z kNsc:VR7H (cu+qD\Y 93הm![{_*ǾQb+L_dOjO;ALpF1/ 528cNJg+1*Z3[<A,9++!g ^ܾBW6?wa^-]pJqb#џMXe=e4!FsD?@!Xă kYᄔW 6a] Z g;ĽW%7M0b>QK \TKx> PR7GlE"T-",+@pt,>9W Fx#n`tA$`/iy̠UF0 4".)`}#nv}Wg#W>v&Ha:n$!9_IglACw~GV5xg&,É�R5� t&;7'�;Fp눝zOMIRZji/,Z^RŻ~<#K npD,l tRŹ~›uM8!&O `EzwBSaeY֒Ύ\K[SٟT(7ԶL/1?=?x5ݸȤC)6dշe2"HÞ *9'{Kݰq.4eaeQ֛:~y7=qP=VB?I1ֹ]BO~HlN,GiI̛KL5QN#7"nukPHDc G2A|÷N4tGA0 b?!Vþܥ`Pǟ3 =0Kvb`LLh�s>{ի<w< -DhyOSc @u9isTsS$p|e(^{`aS]"l" � R=� D=[t@�@эc"PXW7.�bӳD/\lv\�I)SGM�8M_`/a@ZaQ(F ,'C#8b$Ղ\�tbO1B,[]>ģR/=Ð{Ί{qGe[Ɔ4u_ȟK>?ghy de3D1Wgsac_X&wS!rCK?Ή�RA� Z^b)8kMî1rz- Wn HmES7PkQk٧}(̋~("OOJ]'!avAqI3}47{Zy1�:+$$8XaRO?tߊ+y۟pEQ'gH cdwN>˺ @ߊ*uH]J͋uvk=K4d㬵c_ .Dt`Sjp)~Q{Yf1?*B3ZbUGu\}%&Y[' U_nj>߁8г%cأx3I2Y{ @5Dz%R +(r &@ uC5C7ߺYv2z5H >E]~Br‰ 2G|\oe*Gk7enHYT�Sdۚl4mx7#F! ]|o]7/أa8 -Th�RzuH� @NZunpHxql oBY0X }x/XHB 6hмLOo9\T p+􋪕sEP2(,WwK7ȿ#PPCN(G{QġQ0́CCS)8ͱTmV Y05z/E]g9\y|Vc+ã^zaVngqТ߈l*G1c!"|+U)ǖKqG)uep:il3m@mϫGb_&Cm-  ^oRUqӈKk_ ,U =|h6@!O ywoq M [Yd I>Y5-a-(?}4cuԍ8utv5i j%x;9Ri|1NS*hZ%rץω�R� ND`0�"%uFR7]/Kֹ # D4P7�+G3/0fޤxm# f&/$#p^i(m0yV8pڎB !*hF 6NDe'dNي>JQcS#j5?{jQ]앬:'; QQF Gq) UO2=`><-DK=tE.??lFh;5BƔR9ЁWla,lwBfGK$Of_е(`sbu'>!+jHPD r3-M%IiXaiiʘgK{pk[3~MݳGo{7N%'KFA)A׹0-u a/mViKjXo+eyR(עZÄG1o4[I9uq|ta'<]욣H"?+3|̪`lz̹TY(^d(�义�R� d˘:�Ҩ o םatr93Y4f:}n joF~&5V 銲8tp{^R'R=%T{/^W ct ZQբ{ů }r Ӯo9xy=xK*x%:sAFf$騨* IR2PJ {pϝ8Dȱ>E: ƈF�S/� Z 'd�(H/4[I9#Z/�OiCo@GkHD TˆF�Sؾ� -zzͮM�!,ͪD/3.�RRȨ+֜/F�Sy� ^>�i^"}>*1MR~8�VT?T*V_6<гXhpF�UN&� ůwJXQ Z=�h\6Cm�3 >pS! ;zۈV�U]� 򭅬Bg:�fIPko!h_ŹD'%o�}�~ѧA\R38"Cd|kQ"�S� 8y{z�>Q7+]G^s^mEnZOQckȳ/a=8"ȵnI|OMv}4m@aI--␀iPlWٴx4]/;aIMKiȊh P iIWpIK3$T!ttsV1~H@&,;ь{%p舌'DK5w M3nv#dF"\zRN�T4M� �^LO�z)XƝ~>s_@kRPeA i@ e|֊K4Ȥ�؅[̻#d[1Sw\7oS*sp[$I^<\AD3QRdS~hꫠtD~'{G=f( taXn Y oMdV>ذ42|\ȣ3:=zԖZ7& QÇ޼ڪvs|RoJ�T$� Bj*6q+DU8PLzP7rp/q#v!T!6ܦ]'6;4z2nMz?>.cmnf^^ #|!6^2Gh4$ӌg>cC\ >eGfXbp<)P.֓tǕ%=aZdȂ7|u6".>8?]n^_0R(.0Y m<K y!qo^�UC� PKP+n;&[i&5V �D ENk{+$t j@*Z/u%VҼ6mAZfqJvj*ŒBc);54tA&vi1hmXac/QR 1w\}ph΋ f:<t0#Uenp 'mX >f&Y}1D s`CvH!n m2z]�8�U[� vB}S �"&םX3c-ʼn-įL*ͤ:Nwh9ez\q2 9WC=g:#G9-S<.[Y5�YX gBxk#׀ФUW'd,6Jf2ɧ]jkz@�c[C>DU+W1Bf\kaH}O^芉QV8jD@WhMVyޥ?H`&2@.l�Ta3c� ^<B،ŻP ʪY&/ facdKj� GF=Fr#u\ӥk4Ɏ#?O8%H\D=|:k1I TBF-`g-?mFёn02TkF T6)묘Z2!�~5ij&e" >NtE~BzsEhY fF9*T W rËM},\4>Xߗ=O/ �U#� ISN|w81_1i(_ҀҰE($1OK)t~lxtž=;o.Pte%(.'uY)$5R&9ܱf#7'1BQUHJwf:(XpJx^k8ꇹ]J'>ǭ~ֈi4fmE\9`&ru.!ϜX8^GKB6A`{k4R$ L`]vYńXSXu+&2)XfqAou"� Q\� ~?)ùDQNdÈ  �l~AZEi<U2[R]g?[x2%w 'i>-hN᠘b<~$y'ڙK;:MzՅgYv\qNBKWu$e@ݓ[K qPTl�Ylush0Zk_|.쮬u[w}¾wC 5�xj{J?qn�U؈K� ϊKz �N>G ᅈ½' x}۩;A q?Ur>Kd>&!K_h{}+ abrVϟeIĩAT<&n둦zJua9z_x]$?Ov)/Z1d0t5oi&B\5 DlxlLB_bc0GI(v' y?6<0npkF�u PO:ilO]=ר ]6tgO\p 3bȜoTxaֽPޟ= J9j#KӲQIMfNz3R ,] !H?WrW8hdw B7a(hT_ +k! :'~ �U � (٦du 3QNm:*"FZ%r%gv}rvMvUOFzYs&hor\A f`l]lQ'tS�Pװ =͖M`i^B1nrtV. 7ҫh=k/\kX%8Oܴ5$qj 1BN//+Gdq# e5\G"Td'ㅗ3@ujBBGVḣ>%o-á7g#[p[@͊&j~�&*0Zv\gϤཱུHs0)wG"SU\#T}^Ruu? ^Lp5rg T!\#}ئ &X>ir. 2Ǒck�STC�  4�֓*-F7-u`ؼ}3!#4G/- oWD0< gvtNzUDC#UbjF:KA6Q.0=;Y _�n?B6I;/xZDHٱbFB]1v.R( )?É,#E{\u^$3$&rcq-"ﴱÓM,qH89}Xۓ)x׍} @Ư9ܭ})hb/d;icy&뱡O|{ ARc;>vV<eZv/v=a%%Ki ߇3Ns3eQ(Y!_cISWHX'ڭp?aYZmٺ` ]7y&185-BC>T#E7":hs_X?Tܐbu b@f~"K \~R;p.{/ΦgЫQ9]Zm6wUæVmI"EG M�S'b� cZ쑪�`}5lߤ<vH_ mv^Y[è5?飄C~ar}O' 'GA<e&뷾EuMQ}HZM,ԁ̤rf*J2U M,# `3Lp H[tw8#ihuw�a61`3P :YT;*߶䏒B֬HD\g qt)+ܾ~?yL?,=w^<YbX fv.4fU9w]g! cb2 7aXUy#0Os 屺v<Z# JSDݹ0ŏJ1\l�f<}j<lu0P'0\tMTޡ հa嵦dp_/lyD)1}1w4 1ԑulp210αՍOhlߐ-ΪQ~-1 &㩱)$dC&_Ov�S/� C0RV*vUE[-uBz!<t& r}}W{f|뗴@[ϠD?-]JIT/!1|'X"b3h4Q9?-f'@Sizg@%E;#ptV^_G1 &5QU0f �ONW'u^v2+_{rFaN |NK#ĵMfC1Ur?P?C mLW�5KRuS)/R4p! #�|[Xm4b= "Ug 9Kc<)mX'P 2 @Kp\ʜ Q4hp5t9҄>IӀ c +N#qZ{2,Y\9u-ī=bkz"]RwG]nzPAK.%Oac'衚u c*11_i_ O"(+'É�SkE� B2;� t`:,+7e8A-"Dȏl1dSkL9>ܠTd#Z/ҷTp|N� O4^tt]V'8B I_cg{DߩMR|TlU9Qiw/ y-졈{ϝ!L/NM7Ab1W9:601{甉WgDpG/F[}cz; XW=rFŎh${΢EG>Gk{*:FQfhi4 k` {1AOoPsoaE أ%rBL.{Qy(SouiTJAzPMjY8YU=hTlD=6"ԑ7-�X Qvz7KbzZ�N(鲏a4BI[/wC;R\ϻtҠ9YV�SY^� h S_MبcyS3hyjlս9/oi@߰2@sCQ*<NyW۞HAՔEB^]ݡ{ d5R]x0SY4fn(݆=&CpIh<.-0ɇc+Χ \"< N@q` WSܽ5? <l_v<am"a ʢǻsѢ)XuomT}P><ǭYf(�.Џ%Y`]o71A01j0bIfh=C,e@e5[Rw׾b$;c%UL hMf=9]AGKيbكNt6B&kEz 7eFYX t$!lA9,ឃA[ /WQ*dMj޺(hOu0'3�T-"� GG䶁=-abO3z+C-,+㺵Px eu9 N=ꬭgS-k[Av*AQ@R$)gEfB^E)gz=Q ](Ix\Iz3ɅHN;Va687&>\=8)SDj;,g:8ާ(ΣmK */gōhczj}74h -VC/~$7,n Exnes1jyn[uՆqpYKNߋBE$-t90<O4sWp԰1wRt# F6�ύ9u +qzcnLX>6ԯ{WKUs_HXah=W_G|zP-#ۤ /N\76s̒ݷEѓPmY+a65!㾓@J E5ٓ{�T� ncK+"h1 YS1$e/ez Ӏ]r'%y:jk_ E8d||lS+CѳR'iɜ 5'aj8<՝$޻6)|Pu3QVq|B$D.-eι:Ow4&a1ޑfd.״3:%@;eoVcY$"`24)uFCtØLpx@L׺n6!,ʒ2HD 3bM`s^U ({X,PFqkn6Íf(Ǭ[ B'*EqS=A/LnY`4 `w�$zmVFyZmp �"`hcLP:hy6Oi=L+MҼvx(O8a<[(aw6833L# {.WU2\J~ vZ i=ݸz89�T6� a;v \f_%+ݧ[ /eUcvlhUVKx5qE$ķ*>$ Q]'DyDe3P3ߴ0h^*Tx$/h2b;fƳ#cwd7&1"Ek?d_8Ҕqٻ f=O)q):ay|!|[޹qZ:xI뼨Jrf4 G2�»!*p_迠C\* ^rza/[`<-rĸMtt Yy"[jjuV=n?<!x_`z8r0_$Ě%#b?C8i 4y9([l?=24s</UoqȍC$2.S&”Ar*ftn|,EN?ZC:zG}b<?+xf5ބxt!iTr=?JZZG9$j5ɧ}"�Tu� EQqMJę=ټ9.(uOtwpwowUBUCٟA| �Q~7]-{R֌{ߛgzk\o{om-hCԙB3,kЪ jhnQ'Q~۰zvT,*K"y__nL@@Ž|ԙN\3~{-/Hf ϓ4TVGDƉ)r$sƪvި1.d5haz ;dɭ-RTUh,V5,w]ŽPU{<D͹& :ۍ\(K{EB1 rgs}wືU_+x-GDR*ڂ^KK ׯ R:!Oa,9OXy*C� ੸{9 'ob =#+Z�0_~0Qn޳ QÒ]W? �  $h0qڍ&_"6މ�T� 6\ @2�޶º{ֈ"<{hWf޲ɵG vnd򣙺ц O3NuvbҊ x\"0'}'4'Ss <G{*vjL]c+<k Zl-?NlXKHzv 6̍ܒ/,7r(G~It-RIyHk;9e,TLq>"k;K/,>c V-By9t<g)$q.u:}u2h; J?QNTXBPIu-3VwF\G|C6 >>WFۗ*P,1!lxWT7A<dһ:㐌m\ĤHW`.UU\#qS7JKC(n ѕh iB:wu^_f/N@0? Vh61¨P;PhGSc<<j%[�U� T70u[�3F==HvsHt|'W+-h@O R{?ĹJS~85T+ʱ,Ibb| ruc:n=STNu\ empO3lN &3R oҺ1&!]녓Bx/A{Ο|%~duJ ĉ)RCŒ;}zꐕS:qv/x`ȶ쳈.t7RB fPē>ڮQ̌)Y+u5׀źn+0ҟT6ၘTDouS]OwpMX_~�&x{Z^^G!IDci_!Iax}<>/ۧFǵƸbSһFb O2I礹%i0RL.PtM <yj[L_M{ʬO7O>Rls-B%uۉ�Uy� @.c QۗYNK }Fb8mԡ+/Ş`Y�B\ءGF|um.|:h؎!QMhSN$1ZwQwqOi' ]rמGU7-d$bϾD];V(|* _[F*PkE!s+&7�LG|Ts-l[k1b+z0|;\MH%_B:<�h PqD%{O7?נGM^Kˌ}jN>$ܧ}vxNݫ6 U0ܹ bPQN7.mHx ɐ�QPd}TWý`mwځmsG|M[:y Pu%c3N'_?!$=<cc/*^^ioWN(yeEU :lE1�]7>-z9TW Nn8FUuq"g, l�U� xr774�ʢ7`ˎ_߮2R-TQ0?Q^e0&L"ЦpQjmMG~jly++mѥOюŎBtvKF[~6Z'X\#, E?ݑ䕿)B٨kgpwx=BvaVo3�ge Tju~ ]kB=hkH+H{ĉA{uf]-V>fw6/h J> E4ަgtu ۡ>ܜ4 0KY0M A*&E}ҫ6u 2OI5b ^5IcɎ‚!h %,cG*90K ]zy]){޳8U> 1*cR͎fqQuuN1+j]'F#I]Yp 1=뚟%5,(_QmΩτ̇zltrWJs@;̙.tqkڝ*) dU4ܣe�U� [%>i^E�9xOfV]%*"N)r %w/St|ġ?nx(ŏyM?1#YLvi!;=�78䵡yϚRlKb Xƈq5k 0Ds/0kFy9'4rL;paB#~BF7A߻Pٱr,# Hv%xKl¨;`ܞgn,pymu!c7VY@qݱ_u 5Ńj @ ڞ%w:Th"I5Pbd9gƸp ]ڗ+?4*?볱Rzkf lx7G @%˯IŰz;yryfunZXF^82uD&M,rdL]ӋB,T^I1->8Fn5+*(T$Վ"*?(OhG}__}ޔ)뻉�U^� ajA,X&oJ"[n4ʱfAgxa>;y_̀]aeIf0I@{Ĭݗ,*8ZIΣ9Ko<v Q燻e15!=lj$J0ghrKZs?NE M6?ٴ /ס3@~dpt=FV`@fiߞp8zeWTϟYTL}( _B,O7Zu򣯎)t[\TKy|ti͍[b!>ފۗD1mhpn2p6]LU. /%`ƠJW:P<�,ޝvІWڑ '.̕O0P}:w_VںC?d!YBNX-\`TYk ' wb& (a{Q K-qV1#��?=y8ޮӂzpVZtʂ�U\`� K?͹DE@O0נṦ ,KȝAݬ7˓ʘ8tv섺nPoU^]UY5gj}LㆆO"aٛly$; u A]FeZH@U%cL⴯ R8 Al{r976Qfa>*Քyy j0${/ oNц�gW(`)h#xl*滬WnsS☮U&g(7j | FzUA;}K]D:aVcu`|Ҧ0.o⽋m�e0W!b”Gc=34|>> /tS4KL·O( K6hjxFJǺa\C^\vRuIRX?nXMhg+Kmdfu~<ӬE X|$(!ԓ;Ird:�j[8�Ur� OWc) AH;N7Lcw>)A#QPn*Y}LR@E\h =$N>^)óaJy22lzmcA># i $jt}֦(P4|2F3RLeI|6is$G-ڕcukRw6tc:]Y+I3d>W ƛH׼99[qYoC`LZ '>o'ҽH`9H6*v!>h~fGs8/TzuEKlMΞ :N|V]iSem;˜v:6sE1ܧZn Lge992[3xQq}4"-)9 ˀ~9@ JwqKUvXy?\5`/��Rq1,M p\ձ5ZBtṟ̏1T=t*Vr}ochYh~�Ulc� f˔w�#7/� ؓG!ٌ,lal[܋B9{m h%Vj j>?kȳl|Y�OG\XPsɞK}Tzua1W?>.P|N%"o<o{9H�|QuUPZz'm6ߋc F M[#) W�-1=@#s}~N `>(SU k(D ʁ2a[">7Z>[ogz#e%u*;Ǵ‰Up0Q L) ."׻{}O9r^.$TD(n<q;Nq @"ƃ-440;L x`\fMIdKړ2aG:v/r%c±#J;{ٝh0zV`I*+.O)At~q(R,t % Lۙt�U.u�  HFO/(eK3_e39C _V`ƮRqts |L"E>i..A'8hEvb^ Tfr8r&"uovSa@Je=r'(�U@³Ip8aN=#n7QYt,l dRM)$L�.̋VT{|SGNm*AQj/:]הhУ|8 #XS9"᜻Ilz._.S}IxQuȋȺۊ7õ%LT'o:ئipm0j,mae5Q tV3-P-q~({&'J:ɓ4WMD b=BMkSO0%s #�f^fuJ.�8ggLJ؋~s&6 aS 4+o+WJ1y gGGwS.Dܨ�S� ؊ TؐȚ؜v&8IE ل߷SQaׅ97PeS`A%߻j [D 4R>&qɿ$6{fəPd5 # X뙇]kaV|`Hce'?U) ژً2P8ԍ[gG"H{61GF{D{}ó#'!\M?l�N!a8 =+WU|P r&"<vt8NY[YdfQ;�wxGGeCW<2d&ޖV4p5Yԋ!kQi)yeH|=ʳ Adsƕ<\+ Б -L�{]$'զA1bZ~QG>.kwwsj{6`ҘV-3պbYO`wFL@T bދԈmt,r9"?;n\PȖPueEx_Mv`G4\�S� g-ɱ1�<z'}6cӶH*Y1lWQD ]I|pU*|Pf T^tav%/^d>< AQμ@VDen$3n;߈ϙNؙl琂a1fvv~,dWLL3 -;R`= -T0"Jؑm/ i#STrKtgD|yLpXPB{qUCȽ9֜(s`z&./M@|.6|VcJy8e*pMLkāV#=LC΄^+Y;,X% 4)b(+=4B"lZu3` Z FQar`m^S /,7+'w%U0@-|II\L�:Zdg)kiq*φ1zhۧK.�SV� y5M8M�/`-vJP.Fߡ .ZX> ކ7L| YYoF �.Pׁj. H=,xoDav (~90gh#Mgp+>k&)q%?3"܏‹ECpW.o~?Y}`࿳ ߍ /PCw˕ Mһ]qv B>! ԙ!VI-_k͡ IRYo:*pթ?ʎ|Thu`inb_+XOkL^o>KЕֺ~82&8-ۢs+-C>1w& b-niFoIg= ~kf_LCzJ]rPDQah )9cA营xm~ {rn96r<ARԵPWD;FY \{h&AUEf֏@gl �T$� L=X�qk,&ju?v&AʄK懺5i49X!=(vVpW~^r.+7463m0n`S\,zh)#rd]aRP'bTL>?M3^TBUS07Cvsa'3f6%mD،Ǝmؖ+K=9@X,)^ pS[X桶i,t~ J= 65h$G|sSw!?wvtz~`(^#e4*Tgu nj-z[ r?J{l|6͒7/#u\+u<BIzʲ�6eEuEKFsoNjxVHrwyhdy`T=j+F̮3vGg~xyst@S\#2VW9`}Q{TD&&/$xc9ҙN*j(jzb9Zf &�T� ;V?ܱx+˓mU,{ڮĘTc˕MهlKƓY9YϿ1m5(D¹Cj#*'=X٨IUn̖UuIBwVTÜSi&.nð^$*WJ>7߬w8E f6Bl6dr/�@SSP( KP�/9EHY<eʟQɱ#wR%844!igbW^!̢ (yU/=t4$?P+iW I.%. IlK(ֽ.&L1~k]mYϔeoô8.+1Ho{Ot_#wsc즩~ +朰7a&fg Z7)FdȘqߞ yhv}/" _2�ˡo,3�*,:,Ƃݑ/#5&WbB<b2�T � UB̥T04M{J4R?M@i<Lt78p,hP&j�4(Q v_<m3psߧ:b_+{ݔŁQ.<SȈh51_̬tpNȘ7bELqA7(fk6RFÊ*f7DkjV${0H'F#|W zA� ʶuz s]Ym(Èo_sXŚ<>() ^l)Z[}|~K1%iCH8AG^O>fNѫa"ZnT&=(V9#8Pjׄ}h9seps[u Hv/;J-C.xd|#҃Z;~ L�&ʙe:o!h>£fAKH$hpjPZ֚cP7�lA0`"$'v\Wt=YJus;a;UZOBoAXybE0-�T q� ǁw<q/uqL 'n]]#e v]rmwUVB=�~b3vҸ釿 צB.XeS}zmϫ^ > 6Hu�YWL2ͷJedoa jX< .Grh*OU^@9XNgIrߵ.d<?9 үtydmIEv]vh)9xn u5ͺv8&Y4jR;~GˍA4y y{y3DT�"V*p(Nyt"uV ]+K˖m2VB>J|i,~j`INpϸ>}u822QqT- )wTC2^T�Lg'С` Cxe5 OF݇1%!fȍB{CK-�T � x݌mT[%,æ{'F�KE%Hw xRUyڒitY<o@$zPE�ljM.ઉx,j]>'j }ZWg$L񸦥7P#IMF׌EnMtT9,;@'f{ TH&XbDD_jKYH wxW5hxU:BU<=OwjV 6c/H@\7NPWNov x xlH$G(?4inLbB"h7+kXx ERRRuܶ9 ^C�TyHVO\W!mjydicD(~潼kQ{rLk59w�@ltGg xm`ۯH//x! i&^_dYZb2Gei3z`3V|ZڠQ�T/� )+ 3+`NXU5ꕗN o)ȖmŜfhr0c}ɦ< ̠B�m2LB'd^ sDNuO J3q\%\?4հu~zGZm%آALjHl$@=W14A�}>\PقL[i;=Gַؓq\&l9HƧGvh`;Y@s|2U&@9O~XHnzs*nBA zSk%iWH=m]A3_l z@>-~BE*톮#ĭOS٦sb^Z5X(C;HԸɽM0/FQ](NuI./N@GH? .PwgY׉Om{P,4W3joV<3D-| ci #!oA@(פu93nYx�TJ � U"Ԛ1ba *ɉﳙ~t;4>qi jiݘ4A+;!0v ![sdj%bh7)cM e}C wm\2ٳEoܟbs"T-ȏQԶ?: ^ G)I?').H7+p֭+YXr5><bnÕ 0;I~.;8s_QQ _(^Z^R招غ�D:.$~CJv"#TuޤRW-Yjט9,,0Ž,Iz%eG b9MhJ*W!'} <N97nԢڢ\^A⩍ ~;A.hFs=I9x' |Ӑȷ\h1UrQ:ɳaMvBE J| [?b(?J~V$7!/#uic#n;%�TZ�  `\:�cwHQ]NV6sQj5^U}0)lmSGq:a[�"�Nh0/⶚>$V5iP 4u<MjI\2͈ʚ H:=vҭ#Z0`e:)Ja "Y/s f̪4; N!uoPpd_FPY'E6sn$3!]Z)o @;p[e\p?hrCq0*wJڬp/]/r{s>#2�iNm¸-S_.33Sy5.*E@7wĺ^J]&iG4�(){ǣ eU&̓ xv[A`A}Jtwi3]ńX. Tz}+%>uІ)˸^(+?"d#K5- ζs wy{\; ໨3Ή�T� bX/;7�ix[3l @Vkc臶p-Kji]W+;-u g6 ~')U-L{3 ᘒ!.}`x<3DH:]zj*Q{^4"}s(k-mi@ққ!@S:/ɴķ[F q? xtͭ9Sh:,m6̠pWFDB>pQ5("{y-ُc*nc܇SBiA6.ar)Q\ѱt_5Πdqj+NcG\g(lp(jJ%p[fն)*;M�Y^}_" T5LөYlo"64-\>ͿBdƀscg]N} =�J0X' Uz%Ň`1H Ђ+}BO+so2CCtk\~evѽ@کDdFaqO7^.9AЄ.b2OqH�Ui� jb~^$m-W/{jMB=Qd~,M\G#Gn=>,9yz^SpkrjB%ߵ9lJdj J v͡!%cs 1J;|#2']p<$:y~԰m1s䡫vx<ϫ{Du 5-9*hyc4‡eJ1 舻"#W/9>8=èw#,mk/3vmxvFMťGl(pev(Ac 1#)@\ӌirXTvŋeתaُ3lfjdejkьk\bCΉ#a"pk?(WaƳ;Yi.ƛH_Y1ƑpOoYc$гt;ő" ǎXPm6wRNKq4U}덃seWC{ w1w�UN4� LZkҧE4j#PڥՈ_s!$' Xl<FͿ~5'.czA]#09=n dz&)/JU^1ǒneJ0 -oI% !_?=?2CICHs@V""6`hr@ۨFRCyQ:Qu%ruenv5%[<sUp R cr.v Fea-<)"*WK'f< ?D󠇶IO|~5x ~Ƭ8Ӥ4?tf@#( K(rN/ö2r#ԟ�^P:L:HAilj>S*y]r_etq}x\=l,~"SK7%z=y1btVz7<ecYVյpLkɉyT[ʷܖhX*�U[^� f܁O;-yEoS$`V |2Ѩjp|Ќ(Am"mmBXȿV(^/�/&tv/^XjڪRy۷8�ڣ% aLJDR~۠𫦡Z{^$H)Z;Ʊcᑧ#J=qWY#eDzsƅFs_G`{ <t ? F|W$+sɐӸp;B)=xq'/!$wnk`Uw.k[eP �Q?JPM>K~)ʱe4 m$_]TLfQs$Ľrzϋݻo FJ2^IK\-vM~,uSB";4QQ %Xt6d'MRcZώ?)̟V܄0K>Yyw#*# HD\os !0]`Йn#g+oRj)<æܹW�U� r/3 a祗A#7Z̎i2 dѼ_eJ oG$QJ-c-gӖ0NwD^t+nԼYrr\A7'W+(g<ƂXl5كY Γ1 r}󜳥ncG=:~=62'STҝ{?4H| Xuz_ {\%y|6<jBa _SQn~Jjn'=iDӔe-:eXWoۓl�XbDcT\'Ԡ p+S;:Qfߧ{gE5GH3ϰvPW8b#M7 ˕I;i>~ a`%K}/d4:݋y邭JSAT_z-QYԏ4Ao5^ػjoe4v \L0,92;UGfIԥ�QퟅݣMI0!�U B� >~0J�cG|QR�m E cH|v;V!^{{ MVC1Byw}O, {S7Ԉ*{$_kHY368$Й SsYZ넊l{Fم4Y|fR�tB|LL*?-=m ذH#@zؗE}~^ @א?@.1lLn^!-qipӖ)J(K+ 5dzI9kc}?+ݦºC rf+볠װK@kj-Cy']bz80p:0cv&bE삡l=OOnR>GUz^^mWS~QT],5 z\�DQϵ;ߠ~=t4$*j_?vVU`bSεLAm/�ÖZdPMg~u"%O�U I� _UIhѕ�d4UHZv4Ja_T PQL0`Of�ƷU{xIu<OF,G6xҕ!"%C5v'$(T3j[ʵܔ2%mr /|7 <kP%MYk̫>ݣZ~9OSۛ bsgq2[auTk%YBR#VO)A*u]JEV&4 0LGT᥵?e٧v}AdUtd Ob^wBWK =Hi`h_:V|NDftū #р. ͭ-ڽ[ Slk.o byJNlm w]X<jӏtX -])CST>X/)ll.5:4ߩld�[ĵ#&KЮC#|�UF� pJdG@Y>2tfb#qieso-dʇv*aLddEls~Xp9^~a:BNZoMb9> %\~xH/&A[,`7k.AZp!O1~hwf; &&97Yp{OOs!X4ZI{C)[ y>YoITl;6 _FX&SOtn�wPI~xyMMaKM!.kai$jJa W.zTUQH  #K<B "6jkέ)^4!##08!T3l\+\^ 57ܘin4"j6+6\4RQ? /;|vS*эK�"; {̄?V_krN7 Vʵ9q(fz ks<J@yk 7' Lb-;ȑ&6c("2J"O4�U\m�  9HV L59,GNR{jҩ NM{p=XK~~( 2Xý=7wq ]`G9SJ4sSʪ,2_DX D$+ᬼ!+Q}=XmJ*- ker0<ZN8`tJ{Ns<guqp57vuF<_(jdC.#&ݏ&|Xp +FFi}=W r'b+k)ġF|Tw|4_j™q -sXw��Ki3XeBbWC' 5\E8b|"wea+r„B@Sh๿2;7 Nr �|dbwߚObmQk0&9`1=ea?+օWn5\j�`a~K/Py@׼r4#tY`j5USB)'о �Rε)� Z`{u5]� ů3<?(9.9qV?Fw1d�q^oq6tw6Q(hڄ<-wLL?/vuJ{f~d)@`Bk)DC-v0i~gozzڏ7_S( տ];orx!YQ+f5M'P˸ߩS-NRqVN }nD"fAJrZU=6%3�Ss zEOmc=B< ^7Zo|oԽzӸ:98!,DEKoLiC΋hgW|x+)4J睎p)ALd`ݮ_E0{c\SVIV/*L(C|KG bʜ:ߑPgUl$|øM<ڇ}&B-3өƄ 5?$cXfJi �S� 7Ҭ4_|D"К[LkZMBkFmR⫝cx8EYKx7@� SK2ָA/C$ݜn PIUtW.5HCۮzRQT`KFB(;ÆXC`^%* sB̃hz%3^pϒ̵RlgK{ܘC 2{kHo~Өuԉح+3ƫʱcP *ΟDKs+qbRqy`cD,Id )+X' _ 0㏉*C] q&={L5e r ,g4nVMھ4y=^'/[i<BVOJ{dJvw$ cWf V/ T%u_(\gsB% Z4- d�3H5W@|m"+8vܶ*4%-CGBYᑉ �S(f� *XGc4!|" D" 26U&HiC0Iv+tWVd!ˤN>ľ:+/['C9\@@cXӃ`ۧ)*mr3b|*D:u >l69ǖԶ` )1:*8yx,cˠ h,9JOsT{_E@E(*Wp.c=(lb5+,4TYtu n kU_R.*D�6ȡ%b/VG&Q9j.J ijvS&?߅G{'#d\"yg !Mdá߅=^ÙNt{r?-% ׅۙ&Z&kZ'[ c܂ҡsCE=x ~BArRwto\zJ3d�5tY&vЄCE>wvMP/lc+M哂'Mejvrk<'l%XEmHCfv xn �T;]� :hIMK�H0C 6΀/H@섋>P%cêM@KRbdݑQMQI�G\*ȵd^d~cQ0OO9\ lݵtY$PK Xh;@} ogͤ#ȖsIyJ!GJWNqI0&~sp }ȹR}hjN bk4{�_?G>l&tsƯh[pJa@;ft43790V-QUfAU *[r\WWhm!>ՄR8z3C1)%ndVL_ ̅cZv<b* A{Zn= D;"XgNc<"ۑ(!B+؊d :W0]I'LtbҰԃtV8*s4lKӉU`cʉμ_=:{$z*-H �T� uxkIO#*Tpx%ok>\7�+[>UV<SffP<V[ze8FK<Cѐ`i{ \Pͧl]Ax9tʕj9<) 8ǶWCWU;|%$eqGzK�l=1}h"Ჹt%k t<rS|UgJv #T9 wy>Gs;bDSK:i↲J\6F7�i_CF\Bu- �﬍ɢǮ:v#`FĖh1w}}'K4(*^$a7u$gdPD0bҭRoK-wBl~y 4Ku!OGݠ/:yFhكcBS)Y\i+QR5zொ\ R"}?8?\|JC:)'ԅIW;raZc)`)Zy_}e,m|(< �U � V3fRnyDTiPNBjm1bά-ӷ;&·&'Hfs ń?a/Mv#qnff{୍!<,:6s"Lt}:m$͟{aQFմ[ %Yچ~څ DwEmD#aei-f: 6VᜑO0L+;Ps=l6D>%ЎಎuГ$ ꑷ1.Uj4[mVi6q^O?%čX\fOЭ>ztqt:C&8[\҃.<-Uu[[w2#{1?oc[w>GyZkşAT|�j+k-[ 0/;y`zw E]Cz\aFI=Xɒh)`KʚȢw8` E_`ĎF#iY&w �U5;L� ?㘂H/{[AT8w:ƹ&u) (J-<nFFb㟀?V�I! ZdYqSRE AˇwMzTFW)ka~T. ~5npzٹaZ"d_ Rq?шc׵~>?)`6O'`#7?X~'Y>XB8\!^%7pjޜ-N]4_9u{uMsa^y< {Ϙ@ 0BN-T牲� uv&=UB: ^qx K$4̙Fw: HGjT@Pp >N}W>8p*~?W>I4n)_~̂FD@xޟd?]?l\0J6Ks:MBRC+y4Yq̓$lfF#Vc<d^te;ln dIڴbGU޺4G  �U[� $hh7oH?-Rz }Vw/q".{NU{ 6oml>E=�%Z*[eQSQwepEVaq?Xo4u!% rV\T,%J*AbI*L݁蚮J#Y&n^fPEWRLJb[p6,*ҫ-jOxc밌OCp NFԎ,6TkްVKT0+"90d"rHԇp8qȓ'7Y40JI\D~"F;(ZVhh \aI?'u K@y<XO@dX'Z@h-t)Ta0L}ptx`P/N^Iُp=5, 7gsZcY>G̓kY>mGOOq^%Aow͆ɇ~7-Ve+4EJ r91I>m֍Kp~4%h0e?ˎVBS �U7� D:s&4�@(?4r"gY^o݉jtM)E y|2yiG+ ̣W%<WHz_IbUmÌCő4yVM6yF-!-U氮@I ?Bkn0]zb7` Rb V'0HŜ Ag>l绊0�ڈ  9`Faq7TƷL11�CQkyL}I؈׻.;`)ځ+h&5L0P*-N[.Q}מ 턇#c3oU% By{|*ZQZ(jV57Ì=RB-3nZ :g[Pܯy )QȐoˑ+!42""ƉV0bʞC~*/R/*qNIA1%_FӌnȉQ8NDd5+8_=çI{(Ha �U� `L$�܊_/g 0so'Dt͏ԧkr9iv25RỸ�m]L3D&U"ڃXTJDq̛.]6H+Vy!.}g6p'Ԕq-(܇n!3G$[**O34۱`2fQݼNx:#Oϱ 8XM$r|t^^Lʘa|̗sM?Րb*!ݹ_8ُ gUKҴIKd6 lfQ d`z`߀v (;01\^3+"N'><5H V#2ep`uj\<mi(9 ЄpGejឹZ zD NcE8$%ex[dV/ G ^cMmIVށ9H##TWH·WkAO,ӿty  )푺Z' �U+}� Ƈ_5A�@L&DOqéÎe6UAoB ?h,' `;�ORM3DhY*U|kh}$(a D.hL`K1i}L7zr7xAI4? <PkRG1@_5g-A4- icf[58|DqUpa*HߘAƯq%[p-{#L>�Dfc&h01º9טP̦}iƘZt8`9㥢/0jjsֽZ&҉87Y6y pꉁճphJgzM 9jli󇌣AQ?ZKu{wBӝ^Xޑ&di}x,I ͷtNLlzP},8]u 5v|EWrJ!dE2d+Nre8PP2!@71$O^[?u �U1;� :G~#XIA!jƖIZCy*GDNzcG)Y;%E=!3k�X|B֪qT-3uYslhC~Һ埮nFok)f8`&O?,+R&s}6#޵@+U\FV,݉@b#+ʈZf>e|)ɤ:G:׭@E=VR }65GaT&%Wе9X&6)h>t frZYr`(Ü|<`=\sO0X>^soϬaZ(~->�|St+QV /t.-9Dt _=*|\UC{;KX!V?ho nXH"&tFdܛ �w;`}KK`C~uOkm/=~b~EtbG uY>mBpI8ߝ:eQjFb2!Up �U � 㭰P`V6Y�&.950L _۠\},TAp.A/+I5f/_E$?^X)uLv-L\b<2\G ] 5L]UÞ";א%< } I ~?_e#W߂3+c"Y?pib9D<%q좱iHil_읝cZTi;>[N2e,�\Y951L |cXm~t]$a8(KE(Ejy?(p[Z7fWu%^:f  U$5UPGE@FfXX?{Ź3V)N�WF>ea6sRwb*U*S?߿BFEB]qKc *(ϕX 8EU罴ݻwtJnfȇ<k~U0>R<9 $8 jh܊&6ov& M63f!&8ʬfos3 �U � UB΁S+*[z46`]u_:cqgt:Wu} 3Iapf¦?7FrI^bwX궱cWYANU K;'gHvFOSr*~FEDG l&ҷmt}y3]=|;dJ^}ebjQw%Nf˟ Zl,HT愯ߪ)A{u y]M\F�1.4.!9z +ȃL@ T#$fI ^PB#jrt[x*G!&uQ H'1 /Owׄ:` d؉̓{X 2mrJ%Ӫr-ˢ^H6�5 a?vWf۠MA\cD]?mᖇKa%$p)cl'a/WTĐ0)oBU e0_~_Ι7Չ �UϞ� y>b 5bZLMxtwM|oJ9t:mj7O5̗BJCD<G˖4pJ, Wz7_"ws'3nP%_CBo{>-];gq9%SMsCr|Lf{ n#ןC^J1P9mJI$DOK*{ :Zu̗25G~vʥ&)gH Di^:SB%x >([3b׮{;0k6Vh.23n^P]GY_O`Z5̣Z.Z`,ڸ,UQ1F 든K '*xjWXeRw^*=![ Kikޯ8Ҵ|FGJ䢩~{?pTD_C>4Þ~7e5xʤFe:㬽mO}*r^r?l4gɧ ɴa 3zjP۽WCXn4ĤW�Rni� {oʶ�yS,?;P!] ^EG?<Og2F3A'Y.gz(L>jr7jKH4&vlA9TِlpVUd'AE#Q$}3>wI>'>EAfuiV9z4!3ʖXTsxݱx$ 0`εi_M7V.mǚ򟗬^R:3^m^ҭW<4KzMuNN`mpEh{:հɡF̠ gT� #zF.Bj1ZU?Mzpk޼9{|6B=Q75o @q3_<F3.JF�>C9Fy @IJF+p�%T؅ր,_6q.^K!l`Q ]wn$-/ijtA-\d\/#8efL-'лӓo�T� (!Anp8TqǮ_ҀNd$}b)4a4}fOTط.ѯ`} ,E M]et2s}xQ RoԒ7nWOm7M%ϩvVңIWi\76|OO*(\$Ϗ;jy8? d>Iz\DQf"0qCv3;,!⿔@M6>SC:?<^x$w8- cNbwbr'wە~4?m68-E` ,. x6A1S65fW$K?%1%L+V[`ܸ;?>^剹|2C0j0:/)Iqv|_]{!񧰟bn nZ狤4!#{ Ǵ/`&I3piLJ>] ȋUf<#x⽈M=�T u�  }MSmc(ց@v H dT ױaliV .[ds$tYɰs,4t݈g\6 ]cyɹP�^$%EN\P;:4 q >ۗ}Fm]HGl ܒ~ɒJShϋ̩_CSu+X:HRVE9EX`:J.i`. 5v0(c|'mԞy]I8E59|\@>L\<P6= RC$i3o%/^5#x71FPƥJ<Q G;b;#I+b7ui1mR?Uƺ[0TÀu|߉١~fDlqbia$S"bΌ %*/q7S@9lJ rLh-V 6YlNhFF^�_zcVwiL�T�� 4whrafƸ3!n}dDú] D:-UֶU1{#4χrW)@l_[YIԴ_2iBrCFWX;FPy"sܓ ,){j!A}| bR55Km跔<ФDMX TIi;W2>I<++7': XhHV֊WӅm ?HL ocbZ߃TQД{ŕzeHx+}pHYZ_6Xw7)[SB mvl=@k-�솂l#!!.V+qNLC="3( <ZJ-kGDz492%euIG?@t G;=!2]`L,߱8O׹> ҹ?,, G.;M~bH;VUrO �U>� )\ti &�=r}X0[-t s:Œl0ԒV(%Ɛ|X.0GH3܉ M˳cqE64i,5i ږ{ӟu<b \ږuV<0zNi0Z+Npg!u­ǢM3("W()<lREWF/9C"{r0b̈́;G$y0]mDUJ<]N_c\Ē:+6â}86s 6 @\j}EvZWWQfb'Í+5bi9ɸ:H<v"˨!?(~ි)9^F6.bMi΢' 8wQLm>kL< #wkRŬ SC+š0-Tw BxmTi./7nHCoZ 5p2d�yYT a 7-&ѳ$f9  �U>� /!wT�‰JIes$M$c#BD؊0UZdZ.h r*h:'ۡ?Zc.Uh3PܡĚ|ӠEz bmS 9ĺ ''A9I⨿ܡ$Lܬ晠Qw7Եg|U-8MY$?k?VaE/R|{bن׌iHn jºʬGpmYJ!OIHu}>,r]zgX 6uoOTTj+f=ot^r Y:LCaKqxLRrT#r|g1x|bDYў]S^R>ڨbਇ+4.Ư-ӣA3[E,1|xfvܰЈk H"1IpT$B/byʰB%d-ʁ/iXXkBG[Tśr\0gڎ.Lt;9 �U>�  -CBj,KMjB,CDMo=XNÔpTxwkB\SfoT*f&ܟDѤn &wB1 *:w)L2֜}nۙg ~  PYyQJ̷]{GJ ;EU?J-py)Rt[%&TmQZDQX>{?ojHT[ l\LeM` ů¯(m@6~)ͺz-ɠD+(oGu e!Ok`ƿ 50-k%F^lɲ/6�`8-MK v/-E4u,tq0l`7x88GGH=o"Lq̑ AI>ORD3o1'Sba]˔XJ`WJ΁2"v>)bgoT8eܧI5&_1dfcMҙ)%939}B �U� U81TNMF.ae:5jEPpO^efP�|\LRhs~T4?W4XMYR�s^6l#Uł-\tX%9YLKCO; 8g7@NC5\O4`E]:H:U@U@Dj@Y/r�H q؜TAPQ꓾єͻ/*Xq$"Dғg$7k;a͔E.b(;ct`ee3] 8!?3\R_K uQZ;Bi*XTu hv*6oBeu}QCȥ2Pi DiDef)�ઢ9&(%zapN*]LXι@MHQ>Bp:iѕ'qeu@>+\=!Z*C0NU0bj #FO"� S�� kC f|~jVJ=!-GdZL!dIڈ+8^,fVNu8H5=:nЊQH [ V 8KPB&L^L`BkInȀfE-WL52m`]_7!b0_%dXT25׿g*]oKg Sn8 e]SLRL h o8r$.1 =QokDY,bPZgUl)0�I9^8hn_M=A/] o e>at `1FЎW/w,4!w0ʽ:wq7_oO(I`wV ,aj#Dzao}W.b0єc? %G8n5Z)uTF1U ϟT ?n|FWit #oJU\lYV܂|ۿZw15[OBUmK bxL@u; �%   �T ?~X� 9'"h&osl_ܾ�Se~5/`OيNOw57=ME-hHPJ0"P0hS_R8/ˏ]shPtKZ W7WQ= i73fn.zb*YIJhh1�BoAA�:UϜ"&̐{n" 2px,|\{w%sMxXu8%QQP9J_}'܎ ,Z wjKih ] ȭz-ov+=NHGD>'?x x޵O/)6)$@ ?h. %li4X<D%eV*uUP>o3T~>c <mCb�.PA3cq 0];|}/ g:M,"Ai WzѐW{[R٘oT!X2yg7J;p2oC �.RK'git://github.com/infinity0/pubkeys.git� _S攈tzC>jՉqNJ.>ؾl=W&[y!/~[k)Q�c\0F"f/S6gh9/}Y/ԳxFއaq?WM Ā[uéiFiٙ%aѝDb1œcssRe2bFQ| ̐s0-UW8/X+dyf󐽿޿}x^H_XXb[ *CZ ];HS/rj/w2}rkvڰjciFc#(iUms*O+\IJD]_)| X- B]ӂUa>qiF \~k02sn7kkvx:<}b%[W7Ę)+INFWX묕XjA=ht-A$NiM-cq5دU50U:57$IdnU �?U۪8http://nicolas.braud-santoni.eu/gpg-policy-20150222.asc� sV�#Ia-4BQOGp[e&ы3Q9|C2%6wAnS2{{an jweOGqjLmh8_�dӒ܋;Kݚם|Is⴦G%G<iǯgw \ =̕S^ uAhÔ%O?\kXJ]Eܿgv\~XIjE3jG} Wc?Oek(H׻7vGB캇Q@3xG9suO ^sؠbr wzڶ!HKx5P`!,C ?gQKй6<w$ğz ޠ-O2**? \!# J%f>y;D[O8"ǟyJ{r GuO._R ]6]͛-yy}Jw9)c{ꪲo3y7۔;eWZ{ J- (j2�T � Ŋѹ �4 Ya%r"N9Q.b՗V|56TcR.${N}? UϕȎt_1.ܐc7n]. /7@ HwhZ1Lk4xS_ 8)?Z`f ẟ+!4j3l bmilE8<-<_nn*V2DCD ž֪t',sh;gVW4.s"/L2TK"WG0iG1%�nPn@װsmo.I&jLm~QهsG>I]T � X9cVl"Qk]VLN=C<Y}MtT�A0|S9RiJZ$l._ Z'nV!lY[wj>O ov6c-m SVs@{b2Ǡ" dd\Y=2tr18̡c#\jarM `6o-1OH'`5@fUZ<A}B}_ iE6ޛF5><]զMߖ8P٥1*c58^v6OT{~77uAjqR*&w<B|V2w_ {p2zE sp1c_yWl뻟~lFv~cngSr- e<2}) j/С&ގJdۓ`S|OVa8gTݙC46cC ˕x E$ lZ{W ^:HjEe ӧ \a|\'kǴ} vF]F΁!Zc>f/ܡ.<DC16AJ_,{@?-tXƁ]:߉ؓ&'堬sE 2E>di �U#� kg �IL3;F/۪Fd$7Vb9O\.UY,eQ<N$l !KA9LJfjτ^lpWZX@NTEm=<h�Z_] ~SԳY񺡼 �G䩼bdWYb17b,Uond`GbXw)ƻ B VgE[cA 㹞gQqI>~\ɏ@@ ĺ/�% TW_  dE?Iˀ`f  ` i;:[shC)(f0oȄY3 ͵7)1Nj@x,/HӰ)$Gc%C@|aE @^V&|xYٱ-k]Bu+48)z{ .AKՉa8R6X "؂?q, F3W8l!ߠwJ wEC6*,K)V]Nء(]�Mm6 4 Y@lYw؁)e #kPI@َ-Jۿ2h(\5RN E3Gz= r +L�5OS9Z [fbݶ>P2e!v><3&>bfPC?_~.AIC8;w^ TJ(v WtKfK J.t jUq[M'u 3˗q4g@ v- !P@o~i*C-s}bQ<ƞ$Ia\\^Ѕ^2p#}, LdݸVE$?0j#^s>_5.d+*erZ&jy=UJ:˽<N4[C@6CMCtou;e[�=RP=LHXf]%&09N#T*Tc ,Չ �S(�  F?]X$ߊ RZA}{3Ibi/RN>Hf~ yN9e8/Y;%ue1N_J9Q&[Vi@E$\\a`PD;\3GƵ*bfLIg킩:v5?~rn$#p>$4s0,"aJA�E;eh'G(W)D-CHUI5ӥsڳ96jӧ{?R3֞n?/%B_N%dc0eǹ:|8 ͒ B`1*؜ߓޑ)YYq\Kf0fոSFj 4 s2+!~C 1:Ą^TTpU1 ynSM".DVC.A0`4&utb Eҙ|]H_UbL犗'<]=^7FӴQw)Zy+԰x%zT T,dČ?'I=Ge]0+o5ӧJOSTp<t'M7<Pk1AI^pZ<+5Qk0;Vh% c4 Uݯb㷞h_袆a/s{8<l|^p -gH \}E;dl3Uә@y1.#vkDV攵- >Sub($Ƒت2\"X_# xY.>[7qhY9Z$^d(�ӮV8-[$JSOcy+TW ҿTFV5Z_Jn&-w $J]z5׋jȇL7ڧ[2MP{s,]ղ*]x3&Y3r .mq'u@C<䕘aϬx<tͨ|& l( ڣ<{1tnsr>ZhoO05V?[hJɾ4(9Τ*{-=^Yh#?%+WԈ,*Pbv61i~E[.n=n6LZӭJ�b[KNDz$"Y*>wMJ!ں,w~y]lٿg_:3݄%b;tG|,)keke>WwE x!߀%h‘+}zUbQ(ak@qH^E)*htwR~y�F򭼚0N*3)Lk#7sm=�mV ,0!ppi RŔa& `uȲ3t<&cmU=N9 \QgzUH0!mѽ4m}\dّR_''#cwל l7o(8i^* MkiWvk%@9& ѷM 6drކ-;q`Q_~}KZZEp% Ky}> &_-ƣb<vs~sGN#=iٞ r6 GLkI6q͊t?ȠYtiXҢگ.pmHCȒZK=G`:0�FXɰQ'!).v{ OA-FC`2_˗Ȟo tLT ̉Z$=NXt ,߭8ZiNOsL`  {݈sS@">j)SA\82qPE>qll>(_ڊӂt~HUYEĈ?`kL>xtV|Ԋ[䗠 xOA*`f$a9-d_;3^vg9@Qw'Ȣˊ'al:"Sp}J"ķej}dR^HЎZ[9Йm r;g2L/pb*KLaزvF�VdHo� aQ٦!SIj�кXGM1\nR5Uhn�0Fr ޺2jmQbF �V� /| 4�EoAx:gIL�.=h]\8.!c�V`e"� c!G*� K8<_VDy3tRi;10/269Ϋ}ǀ PVؗ O5#is-,Z&$bJ& Ģ=|885#�r#+M# h" |]v$G+_ f+imev9-G*0i!s ;Aa>I*?/+PzM OŻS2>A-tDKSV�V:� 0_wMDz &Rb,kb D%,ye^Z LX0\RJgZ:!wtN@>Hr"GW; gw]|3DӼEFTc_qyf $M78B/Uc^zpuVLwrJ::Ȭ�yc `xx[1t!5m�btG#wѡS;FŁ�Vi<�  g�gcO= 01Iݒw I G;CKxLhOrb}WԶ0/s)̈\ddhbueSɠ+EFaQ\?'j7CɆJkꝦ*3^bhv<q̓ NNtU5&jןvV18J?Ud#dF}Г',bȽd(\WHl,)EfnND(~)܄:~8cx6bw.'&DN{ֺ4fӑ�F1 1*: kn`?h\vSf%%TZ5iaAPE2;.N>^zV?ج B!M/[KBut ]'x,1|ۧSaȔB>󶤲\!``mr"e1^U)jaba0PBIr2 g[d.]%73ۜnrc6<Q /ig-x�Vm� 2N/�#O'lS I2#A$X�wߵ6o5gej5\ƻw{ ƶ4Sg?Ko,?~+fmf3Gel!@MUȮg#UvUjח:qh5FG!}lOp Ni$k궹(o,YzLR3Aw'- 1q/׍GpjK)y94'hS[SX|die?ΩCnjY <Dg% 'Ĩ.Z䷚߇ky.uLZ%LencSYDࡑR}͊^.>4CQt IpjL#=&eq_Z\S i<z6*eF26g@oU2ɹS\xJ7̯Ҍ6<t#7WNG' MM|avtcLW1zevz0k5T_ G'㓚Ț1�V?� E  K0n>"iv� Ҳȶ5PRrxLOZ ȑUEa}C)S& SgӞg5Jh3֖.M"7Fp:s]BrNzDŽFԙQ>}/f d; ' !0{N9@ht&|ވq|ι, Yځ4 =Cd' LxS4Nw(\bYRr]&$Dڗ~£`׶clcO7X3E;"㛀(nf$8rk:7iH.zJ09fi7Ai=۱ܼ/kRREd/}0q0P+`QzKu4Rb-E]kĨ4)1R;7KWyU6ȻUe6kq\TvlwcyRw PӠ<%rtjxyJ%Ly' ~�WNC� Jɽb{UgO>\P>3T4~hVL(sAQz֪�Z߁$_S~X ;ʞNDR8Öod�"[CŞu 42qS# vccY'dl|i6 f8*8{GY@#t!_an3;+PؙY({ŅqS6Mq>tu­˙lu&A@%`ڿb=^:*`>rrKKx(n=EY4h{ B}(Y3+Gb1+K?�qaeѬ]K:(3~p`"kC"@cј eɹ<-oL?70Q[/* XgX ,Ʈr{TT>VήU˶R)`ʗ_Eq no!n)uNhȬ�VgX\� `~ %g�fPaaiد ڔbJuR7yOy[BDQ{f%A9˵PEx�K龤rm+)]%_3R흇5)yڭݨ doCԗ汒¹c^B8LhP >!} 'oz!dSEP/fmQC|hܢ c^cNfz/1Y/Ҥ[ Ӛ5g>:_D+@`ܩk)7-?OɪYL&  jzÝ6Nhy �xkÿX"i昄RɩIxjv=!Hg3#tz:)s4cgPN-瓀";#:Kj0*ds_+}ܴ$@.cGal^i [A*xɄ_;q+]w}(BA;L{�V"� @<&W͙OsHMCSȠ2NEZ_Vl-3=Ah=ҿ(]gshj`~#}ZB!SIC(fYvakw@sCS[ lozYƯ[ dz(z4HR0g+=ol!hC0X;y< 閄wX&zWQfl3\k;U\]~ $9Ue}CNI'j$0+$+j[ǩ3 /===ջ<daLkM!|lIrjg9ϐ#eAY[))~ F˙L;Ъ;jxc+Bm:Eȴ8s@d4s)KaZ9DF,^qwq>{Bb-QU@E7BFNIGdFxBbkBs~lbb5x|Ƒ\bQ]`T.6z6 C94OM�V� e>Ma80(7hcIO="@@Mu |q҃Uy.`YuFYN Q|bӒCVMFȷPfGE lJC~Yʱ4"Fb3N%t<HL)aTಚ?�#u,FeQ3jâ&!U)fQeW9ފ{b6Ďiyϖx"8/2yY&-lĵtq/PؽL9Y@|ԱeEg=XB_RӓGT75:#BaxnW*,GގQ {"(AG~2WYWz|N #r6풳<9= %)%KX/`֩jݞƤt usPtv,5ĥ|y\F+0 WsBL@7KSs۽$pND:S[͙_8 v{k7{9)kfz0c) ܹl= T]]Ծ,�V� q}q2uSl'L<GÆ t\b(KkLDo rikkߴd}ǃd|r�7Mb).^Pm{ ]7bVx,8NW;2ub<{b>ၗ n RvFxwzN_<F&<O@'Nb٭j N!1hGfKOa5-IIF\BEaPr -psܢE+ĩju./ /O[V[Nx5 ;h$̇`~nqͼVMdKhuDd@%N;G`3kU)y?̝:r`aʈ6cX|_gqG<q-AM*VqTWGh.߭QY2+`gT[ȼp:-3d9k[9lѻ @5ߨaQ67uAn.E,.cI~ <FEzEY &d|D �U� &tF~JGEړ[*F*e rA;HR6012JHoalX޿pțǔqfm [=<FD~=Wgs]iAѺԣrTfx3qѲML"pp0'yJp9 X]sJr&n@LEGFw&nѩ~ϧ0y-ZtķTPăӁ;~T;S륟`KN'+ɜb;w|k%_6[RGwP7^rw#6Yeܴ_ sK #0t?&bJGRO-}ߚO(#k �/.[G_;}Z!p~u-CbÛX.h ]:ɦW+/-t '}薍%b™d G\O"VOK'ZsrTɒ6%>}śW4W!Kdbg2*x܏o(Sx%p #aM �V�1� !xl(ob3Y \=ʹ!މ3q%v,0gw[p ItU2M-e% wfZ*G|&fvI22 /caS:%Dq]ן…v" .E?"}|*/|zfȹe ]Zƛp;=bxOo'X8f{]+\Zx6pPjN8)șX6DXoL%e"8 ?5݊y([SFSB/+מ�Q7DG\:DhƝ<@ar$4Bci _Nv+lTt��4b5@vUw41X#NyzZ6s$XgNMѹo"%kY_Eb/ YSf+)u'cĆͽ+KPGwӎ"lM5lD˲ �V,� ýx2h l\ #BOwAH,ZZanC3mB7*}u(;Bp}*f 9Yr3`9M<NaD6Z =Gs &}JF7[g[N={w]>A]`y I^t;X'd k??9N. *`Cܱ֬,#v{.u?N̟Xl 3"oȓN'6"jgfA'>i<O0 ՗h2M˕QDj EEX':/�)7τt@gH:Hɮ#H[]{2M|yȂቺ=N#& /3~8lNb)mf^�*uEu%;qQ+~L*/WjLnW}"u3�5j!j\Z=%6V|hK:"Q-V⇧'Gcz#ϋI �VR|� .8pX54Wa3\csy[Y&2. o>J>D^Ըmb0%S <##Y(('Ol�cl|Mtn.?x v~@(8#rVn 3}YC" m#ݥ}S.0Ć3'y{(:m$&PԜ;nS ,h,@ 0@Vx݂l jί5"$$�_M~?!JJ6L1 XU xV'\ d(#`%\&g\<㐂 =_ PgKeI6AxbmCNڍWDH9bnW6^EZ)O !{H mTr K#Xgq Jb;RؒL|&@J޶6 nXjZu]QBh BzfVx*1y7ˮ$u4tʉ �Vo&c� �UVT,^k tFBi9]3gyy0KgHl%kX1ym+ڎx$L6ˢ֏.8N>BѾ/Ǚy ʥ`d>)<!zPȫuf| =Mq<VHۀ }k]qY&Ǟm= N2ӕg&x"TuH3c61N-PIo 8zoA7'x٩ݕZieLM.�tMc.ÃzW7S[=QUZ-< k m֞l)˜CYGt3n= `9{c[rlPe8ߘ%F|֯ 91^�Mo贇^<3$a-kuٮ?@EBF`?V?_*g=ʈ]!GjSt�)692紐A&^EjNI~0PmdU]u࡟hSܻm53,Deڿ v �V� Qm͒r[c P pt회qD_K=$*q+?P01/I`<< ”]* xPGed,'j ޒύ ^ENwY"}B棯A.~JW',ᶹ*;QKd6T(|%옥Vf HeTwG ztcH6J01{I|9u+y�G^'l<I"y+:#Al ^N PϚWŠP#46ZЃLupk5.}rva$1G RT1r=l@.<wuD* NC�S+3w'tBf>  )/Imgc?x RK}q[HI]z?:\[L)%[.n9gSx~7YrZ \RU4v8"&XM_�VL� 2|L2bqRѲpD!F,v+"�&?L2;aІd}I^g^6SkN&@n <]Sl 򅻎7rK_T&N|P\o_egfB:h%RMQDx䕜|�b?a^΄~i5mXm"z151V+˸2-YUxQblʑv`o 72:VFyߺZo|F_!) {S7bYq|8ͤn:;}PzŰr*t}x ᅏ6%\z`F"Ĩ=[).0Ȉ IBod)x}_&#=�mqQZs 2@AL-HJ$w1+l"0^HE;0wv\txu{PB"C5BeE82M[ pYOۭjSn#�Vj� M̡{?�-ajDd.Xe7( !q\-pV:Ò4MUmJx=M`}`#\ccy�|Sw9h߬ùsn[Ԯ` c\kMTJa \z�!BG%*&jA*<Ay "oXȷ~qg,LTE}ݓ2yՆ+x>; `r=2|L#cT$̿/xrkmK8kS{L:SarP ֯~Cf;NiӼ$/׿L x6K$viz;cu06r( na@SvՔΚ;?]w/Q>օi^V^h$bwH0Ee⑓Z[90?ɋKm龙:J__i -R�Ul� b@1.�98EI̓}L=bY r="+~u{nGqC#)7zB'Ўz<2s;F惑f`D0+R')ϰkbypVX!:TnTh948f;סрө [NJƇ|)oC"V1{Ђ -ԣh2I>o?_\E5X? @ٚ뾒~OYq Aeo+f/F6r Io9֔%G[-kG=;M#'[$O񛂆ݸQsߍsk`{zBwWaCΑaH";2F~2_}D;гlulf=@f`/Q]Qd<Y!ŞzAn�{ܲnZV$zli O` ?l55zF>k23z.8H NQ�͉; �%   �VeJ  Ҍ� 9A|NDĐ'.&wݷ�M8.KEB/IQk$U~L,sC됕:eu+@~nbilHgU7$6$1WY||gWd*mIR6H3P|2a(RD:^9B=" ZesD݆KUo n$;wgB&|Zq[8CuH͐ӱPmM~1 5cIjIb';5*>1Qzw:NoD!2 bT4xZKLvǭ}eR!6 }#]9Dwu*"EZd @6*Kk]@DRwz\=G?gȶ4@?H`AAeG,ض#UWSAֻiG)> )MU �?V38http://nicolas.braud-santoni.eu/gpg-policy-20150222.asc� sN�^@ؒi5+bJCNOKSMo)&B`U!(Vb^L{Cfhaoz9'<ed݄a$:9P:Gze-032Ye7XA:n̬a\XiSy m @W*X"yHr$ ׁv3stP@85 :ǂ~,EĄ{<=럌dj~ 1xOZS�FiBѢ)ȩ&P qadm"͐=:S|�"Yb|@?o[S^%y8O֒7Zt$gv10Vz1M@g;,r6A =>46!c^\�!ѶxKh{ExS:;-O3748IiRu+ (#%qX�`-:1"G^sǬp0c‰= �'Vp https://mike.tig.as/pgp/policy/� n#D(c񣞋r<&C;fMboBmB3in~HDaeDX [MD(u*aѥ8`])RW=�ԉF |u헇sNeT]7%n#!˶"4"Xu Z8]jlPK17yQAoDdoje\w> ps߁%=wQ;ͤc*m94%8gy>BzP5l?ʦCg_- ^#QbjZ@f PUNUuQ߸KI0qn̦]KxKg YPn1K�d}Uĩ g&9T3OwR f<Lo(n?R:ժM{<%F_!z{5D 9KNIc1ӶGDbf[i/jAz+ $/ջ^U_x9ME߆]i@^B!H&v9h 4w{f aB b&Mx,sd44k=Ugmv2C@8·eb GNϨ4+Vx�N:dbȂVɔIR=a1 Sߝ>X1m=ҷ,\3s^ӟ0k0NLjI3q\JÊ`"xɯ\◄ m_ʰ+=CHɠ*eG&9vٮaרV3DnþJ뙜W,,CrpnhkmWS'Fe5E_*זfb-*aL*pg k5"o5s[ YgƑ;~z|9ՔJpod<J 9w=uKr8\(O/XfScnÓo1l݄!]y>7-(] mʽꆧu�l3�[%6 p?ua,e3DڳG}�V � �mAg Ur ffgB}/$^!dĜ4 o7v+DƁfNY3YUV+ZC5X3Ɨ&ㆴ -iP~!f}=]L3"= 6FA ,ݷsTI8$Z_u77M(KtIB\GW1/#I"IaA枊<MdRIva+KQ.ڡ ɌPm3_tʰFR,ٙEP_m1*VͷT. "ۡ1İGzݭ4=O+dtM*4_⺎(EƷ:&gxFw: ur`_*܊'iB 8ڲ[kT<JEMtHZpdM[* VC6uFO]l3 f)DvokꙚ-%+XZ0`+N+y!e ?d$~L0 ׉�V%� I#)eɂ�nA58CܘNok4ڌNM=X-t\Q^oS {j`{^q- (BFr&Mإąu_U6{xQ{ᛘ'#6~Ü;aKδ՜1/#>\0,a/,-1f > fŔs!)Ȧw[EckfgkuT< P-]b *rQv)VЧ7}f6tU%1/#!=.8ʬsm ka<N]WϺGe7s3l;%uOA.`zG2^2JWn(5�rHː¯Io!gEޡ 98b 0(t #Xi%_xZ'4%.F<bsyq4r`.7>ޖ_VB54! WhTgFvJsKc܁ŨF4A~fYz-[a*%+R;vq�Vn� \)=PCoAX kٴabEN?LP\Yg` ^o;AEo�ѤԲP:UM㽽IѸ k-CDH]$v_) z�p3M .=.Ŝ?Z!ޣ_M Wv{Rэs]؁k$ INO+E0w^=j�`y$7ԋ,t yp(7(+G-9*}4Ѐ\H +<*NxR0̑aƾfI({Mk; Drٛ;:&\G\Of6;P|[UBS6 ;hWE:x,bb䡊iu*_'DGG+ kb-&>/W*.+GoE0ecPNŚSaր*mzhE=byBC>4{{9I$NS] /5(\ouM$:ד `4XmR=$ok@5@j-7#p�W<� C�F �`od#-dDO ն.6=6]ALx�xplDSU[)ӱbr(S,ȉg8XW6h̎-@?({Ad=鬔@ ~~R,kA.g뛔S0BR AN#[(ϐ08f\<3a\M/㔱n1}?xJ[9J a~ѷb @عXM 6Mf3ƺIV]\uبF\ڷ.3Zh^:{ ~͆|Al?E 9-f�ly4&~&#s>RwNVx.F%H&Ѳ2[4"Gff`^?ˠ 5XߦmAkaVtxo[j_qːܾ[`v=亂ELn C(cn7X$4rh֍�V� _;$mKwC{T)Ud7nI*ƥ8~S{J˳ 0?ϝEο[6PF@QԴN<2YDg nTiY%- f[ަp`�=EѽS :zA"J`~=V' #(iT%h~O()0 ף.1 \-N¤D+0@(T]3tI_*tA$qZ5[pVн@~HMݑ9h_X5)jG ˟C1MfWs)ͯ׎CO$B=-ޮe<hy=UESO_2߿?l%ynh/2ջFTA% m5>P;C}\oQ't?])6;? DzF둓8Z.6C. ߸`pXҢ*&QU.¡`"\\"/*.s`U>{r$�WR� I#)eO*�,%Af&)#g᱁}O{QtS,狯,gF/<0r(dcr{h#ppM|L,yճN{x|{8ptV>;݉j 6m ijII;;rDd,5C-W53udq1t}Oj\8*-xv>[M^-F#4V0`30S#ZJFO[e$1D~OdsO9b7'߸ Xa5`h~{s>蓑7P@R*TY559#ǜ% 0MMS%$Pj?B)N7Y~V -7 VdB[P[~B|[VV 5m0m=u՘"DUAc$Ԃܹ|zT52`D,#źZw]U11/{AC2\>7HIy3{H�X8c:5 �W3� 9'bDI�͔I5mm�o@Yʳy6]͚ PwkA>W(c}CrX:2fB)v(2VıP: VpF�ν N%/NsHbgpPnQzlC2.v/>^Q$%ᘿ�vaaQM*m"\9y-YNI.a7\m%9^|@_L;Q$ n ԯwP|ZñcJ~}-6Ȉ82t޿I^.?m6o+n>#j[&K{&~$fhQl) �kl$\5Mz|7&3f&;2bd@ſW(xDmrnw^\28>>ec$|D^ևiU(c˼w\F, IH�|Dg[NJvgqR �<   �!得 u@9XZ ,� 9^1�%KESˤrBD:~ml]MeQRQ 6;$0e#(n&!}D w*JQ{P$A@|y<8*|qoh)7b+u_ӵ_O=/ kfڿEZ+b*GNY 䚹vǨ9_jI ;aU|SCs4L4a4+>>nASkZ!j_j3`|*n'MxnYXH{^fΙk1GuI2'f'`oG=Y` fyO#>g.ݘ k ,(j]S*?c2 ]LBSV@TzCvtylٚ&Q<Cmt[I )ƝCڃ`/L@цҸRg&;�!< L<ΣwK �W[  fO�����(verified@patternsinthevoid.net0EE5BE979282D80B9F7540F1CCD2ED94D21739E9K�����(isis@patternsinthevoid.net0A6A58A14B5946ABDE18E207A3ADB67A2CDB8B35.https://blog.patternsinthevoid.net/policy.txt� z,ۋ5CӺM℞Ϛ.+vUɶ>ڡk)rUk-%sw# lC]Ɩ:aɝ` P*ȖS2~iڽ*B4 W<'ݱjU鐢A�ϓ0Gg#: #3@)"CFuE ^A=zocn VReRtQl/6n`i’EU[$!<!'{eTc.Μ>KwC; i! ^] [gBpQ]'$8+eR 5 nTMp̩0fx^ۡq|V:ˍ(zߚvdEgA}>JT8X1.,A걜zv滴`յh>N=g(Bkf̟Z1>oOA%N9⺩ ]T՝o(X74̰%#m;`utW'BYߏkzΚ�V[� kXbv$Njjk5D"f<%U:׏5Wx0TNUy(d7HIy2ٶYnï=Ԣ8xR@#N x ^=H\&8]p^;Ly\uNC']˟5e5MݝLkt CB}H@2%V-|uz^7hhIxrYSvg"+;P{vT SFgfþ -29 G <M$ȷL~q-t(Pz ^ Mވf|oyMS`@zW*u)a0LC!t rwNs-R%U𜄧U .S,* l"˵q<c#93C<!_"P8vِF Y];LHs& o.P}S_t=ɕDMobۦwǜaH7 Wk а7rU?y){esSln*~#V3>`FP:hze\^'tc M= -y\ěa (mnLcETמ.xv "jZ!�q3j|]s;zrV4ctoU몋>@~P>QiytW(:X>:Y393<[RUYcqZB!fov:?^oFMX"|=N1CKᥝD9۞}}+<QCq߼mjFHѸ49XdQU?הN(I:yAQNNk7*xvyDv?t9FV!@):c݁ }j~W"Ĥbv!*5c۟]z w}0n88(ǫۉ:s\ޠC;3lxoO&l(0%pw˯*5?Řĥ'1N+[3ߙC3 �!뻉!vΟ PZp�  P)̵UE&9/ߺBrƨ&Ӎ6/E$�&el,œrwb_F:Ҵu&@/1#PQ703Hľ̰ 6|f^TnA}AJd6`TR["B逢XL 9n6l (1H�\R ,#齧d~=RKLV@9)*+]'o �ZId� ;{-i F_mWɸ*O,[WNR`:jz3de7TbmX$+΢*>^bjѿhw"ABYE|R(6&M]fؙzi:;aZȃ .jo|2iZ7q�]eE\ =24ơCeعY۞DY[36@;A+N *jxʖc{"0aR*`=7.. f` _jVz_np?ʆ{N&YP D3 @۴DBTt-]Tٳ礤bcz\]XTתeB+naU;G~ZX9``*s]{2(?qdvA.Y JgL`xYd{ [nN/FGi,4gU;A5Y9#N63�!T=8ݸsz6+qXr� +q5�@aSһvW3-ί.P3ng]^ĹI276�xY"`D/,lTD"vM}dՎPp܉F'U@C+]t,Lڇa~k ͈*P}&Og}B8LWyԶO ص9%UukzHl.^&YoQ7+W8-[l::rt) ]#ew4� [oU2<`T+ڳEl,L>(s5 G֩e\6["7ҳa.54:v Ōܟd, wh}Tڸ3NRX)^,E}6VZC0vƷr !iAmGܩb<6'u-.pG ኾ4m:=6 ώLkeDdU4#\R �<   �!得 u@9Z% Ƞ[� 9_j BүKcT\Vf0kyeV]<BgnC_M[o<k4@(-6P "'?Uo;dDALzV ?3k@Tenmil}ʖ`X X2,,P3''H,n.?L[в} +56#u>Y% X3o7!٪]Haj.#M\-sbS']TX$G_ᵰPRFT;bF�+&Im#W\V+P|Qdz ]}m?њ* ' .S-]ne7\Eˮct& <'W8@;.ږ2> ks})vy:ʁ{I󁅎n߻vƉynԆrڈ(ѓ>)`�N(n!#q!i*SݳGycAݰׇnn9ҁt}Hrf,Daniel Kahn Gillmor <dkg@astro.columbia.edu>0 � J%e� � 9pVŻZoD9fjT\@ݚ~tam-0ko3)pAj 88WgI)ϯGp%` )9W9>LF$�U3(TmA`UƜ k Ã0ixCrTm+sy45T V5cʋDZC[.h1�T]볖mHSaer.]n'#?V@TK+T(RRls{*2 aeMʈGP;U@293Evq4 ղ!;MpWǛm~*`퀄S%KOstna΢XwS73侜|:ݻ=mVЙ3/D%yB3`uCE6P᷋Cv%]P ݁H B&�Y67nirZjz\7l_-&$LZg<�&FaW f � 9QM)Yd1wtG0 h>;<uZ>d> ]8 @/I:%bGM FpH&C2|^ %8^Y5uϦ�C= p&o@ t/L$7l psO g Wj8^'$}yT\?\I< dwd?烫Nxx'8h~о/[gC۵a̤+AY=ku?t1-_0cNJ,ʳ:_c{ Ԩ`Ya3(ؚM&yR%j2ݵI*t}2^:d֝/bu~_GgPǁݓ`9'kە ˍvcCj,=~x%sx1CaUx9ObVӀT¤H~�Fc� #~p2#nvn*¼ p_ #Q~p+~e>4@ 5&DZW0~Q%[z{AxGͧUs+]gNBҀ,.S/90$Ccha; RknjS,Vw[Hƣ!5gYRTp2n9.W?1%7%Uq8r ŨeSI|6wG8<LU{DlIc^ ZHnTot�$(O?0 Ja<]]m4U{�d!YtFѮ:?ųHESq_;P&-q,-M}JkKt߾eLU놻!rUhLU +T?6y5V"_"ӱ,sl�R6?'a�ʉ$GCj$0#",GLU_iP0"LU#i7l(1,:g9eR"4^8h�FaԶ� 4Ϋ{�b!r!ud>*Q4}Y#̗NÌZ(KDž #9(&1k!qO(z~+kwWغSyā56Ztc]~t DuECEwmT,|S3^7Ũ30xD5Kb7a,*z7UKC.*h P͠l+h݌9PN풶`?g0L05QOb;}DUv8j[7;l9,/4?}n�q]5T;nu'8)Y~60b~xm�DJDoC5~&"d ο x B@+''k+KY�R*Mvq !SdxC;8K準Fx!?pzbCZjw^* WrJxI9a7e1ImG~r5S3l;gxvPpm鵐@vJ� Fx� 7bv}|f�?/1%-ey a㮎d�OY)P¯YWGF�FY� ӱga9R�(YBAL#j~Կ(pA2�m֙==Ro 95F�G3� Qٚb �yDE EO΀v E_�ƅpjrtO F�GQ� &Ჾ �ZՔT'$3)ƒ46� UPhF�G[� CB;u5�|C"?D~惸}M-�c0Jɴ뗡?RUg �HCC�  k!�{Xܒ. W&}n1*\b'ysSџT*[CWY;~_򄯁;g .ٲ5\#ѕjjqٜSUS# &:\I8h~hB qտ+]&f 1՜>.5`Z9E'z *Ķ-$,~Q͡-<ތvu_L۷W{џgrGlI2O73|; dOJ)zm`B G-ߣ st >qؔ?YH<;?S[<8SVMP@oÞzTӋmۭ餆ݹ5sT_q6@[l VvǢU3V5(}_Evď^+X"<lȣc3 ތz`q,I7vv*:]IbŹ X#=9.bM^c귵.r2O-"yfmٯF�HD� P,O)*� e0zЇZҼ=&�T�[8|›{F�H]$� ~@zK�@|3ˉ),u빍"7c�p&  XF�H3� Pp,,.�4Pߡ:ϛ$ݛ�rMC".}yF�Im]� hyd5�^.EA000+L�r_X$LsbWF�I+*� cUV ߽�px :wo8cA~J.KY\�΃R]nUL�L2F�Ia7� ާ=>.�?'7 pk6�+ SRoU#;�H]8 � }XhVrj}k{V*<NX1HNP%<. ^ @_kGvC|Pȍ&sJk¥9tͼߍU=ަT*] d98x8 lZz&aՅq';e8,Irak~vbNM(Dύ`*jS}g fg ~鹍XX4Ǘa raMQ$` 4ۜtTAp,gZ!BE;{;�% fI>   �� 9Br GdM8 )L0Iٽ3y&\Y;CxW*^ynSyIYf/}.x= \ A!oz4@}E*OS-ʽ\qPFN%7)\>O@)իl9 ]3 DO zi\2 y-b@IU7e ꕅJ6EgfHZXP7}hrdPhP3_N�.܁>F_ëX<b"NYȁa{I.u"Ԑ7`RSPSH.2qV T^uy>_|`A密#H iɋ41k>./Aat V>j{<W%B;\'l&į{)5i"l WR wyP_!b,Ss0X1D:A�@ n?5(< ; �%I> f   �� 9^ VaP>d?`# \2 :w@ O n%ioug_QPg J{a_st3Og+=2|SfT;UA>VҢ@X u�Ul=TE0zGsGfrfXuɁ̻`Dl7| Xf<y0;wM-ljd /T\A.n_7*Š(bG6Ϋ{,( A/e;jxfg 1 S/e0Ϯ\4N钰xM?*.3:NP՞ﺛ#ÁKo)aqYHȀ89ҟR{RuvZh6h_g똃O1h y^Dlה18>-yg!uzגE)`>ђ`ԽTeZ3$}3 낉䜔(Z%Z$[@\+GB]f;�% fI؞   �� 94�ln cIPu.b7Pۧ90Fpc2rtݶzM? l<'PvK2VU}wDUeuYo $±$:x.ڄlə- $6B@]S�f.tI*T}C@k$L<h=^M}tqR/Ȗh҅XsW "R>s|y2brؘ _zJ1wrZo3R ڤG;a gwTД3ABޕ`GUD!x!t1mZء4R9d٫ѓ鬢 <> wVh(F#s{!zfVox9R$1ʍt NeC.c$)iDLF v::@̡Oi+Iț6%μFU j#EZ;e긐6TB,_7N;(}NͺI �Jj�  JEoK ,*$ gu' Si%p}wŒU=o9̏;ͼzUC`7fLm0ZtHĻK;Ak-'I,2qMedcpT#T b rrP8FaYOdZ'ݨbϹv*Q �Ke?ms/1Rr7ήOit|2Q"Nz DT?{J*0~#O0*OT♀voBAԝꇄJґ2;Qg*m NU�h5S?#L! g8} #0D{7!xpp56 eNeݳb3XS1}@U�[& @SK(:=lw'Dm1щ '8Jg>>ݧ"+geDz&du: ,f-}x-H8)-h/VnxT~]*/$|.](M�J<}B� 7Kp}HS> D5Vk\E:Ϗn*)e@IR_V;<?r+1U7鉺Z$~ ӣ##c {Hu^oR{ ˱Њ_군D;ѽ:ybS;] ۩( =XD! ~d!69@djBLa(IZIG cmҒer+|[Hؑg(m:ϱJxlRF@7'j]қb <S]7Obj/t#Y]U n8ߋM-(CŹX] d"h;UuɈ)Syeyi5Nr=wZGᇆU! \P+a]F kᴍz"Zxz#+ Z)CJR,n:dЖ+)AS?cMԺ9Njlc}wٯ-N췆gF�J<� Ik Z�48 ($�[.:ͪh}H:v7q�JAZ� zI4l8eLX[ݿ 0ʢj g44ғ4;䊰h̨|0$ PoPwX8`3Dgyfǩiw'm3\EлfS^~F,%YBN<8UQ{Ol�HпP׎tr"Ո`*{ Vfk%@?KC{AԖL-`klkKw||"V Hi+ .EV�sM}\im:%tʪ?WJhXfmXq6֤ ڰ/aLs\RȄ8=F`L̺gj[Y&rqXaO1Zghf"&G| :eRn�v&o&$ZAvN{#F7/ӊ q>gԻBe1߲39W "nB 4$uŝW�HI3|�H p$3&|ľ42/ ?%, '`�K� #AWI k>x՘vChιw6~k:K)fuow6Rzd͠ »\,f.IS䜋w)YgY<-{Q86GT-7ʑ!ϵ0JMx2C†=XD!F`/*;>.bbr[ '>MwhMuJy/u,.`dkT?z~ Jz7ǎ#re -^ܶ3q vF�L*|y� ;.; 6�[cKW IOkrW�:;Q*R{L*|:Ci, &�-Z11Ɉj9 K wF< =Us56l䊽\zN =Gzj_K_F 28-LGJ@*Z8TFx/_HOho1UfV x:9Yێu~\pD |wI™G MJ̄h�2p}4Ww-5 Iw,ua8)6WZBHuЖa3QmbcG'H h/ gF;b] :"oSE;Kή␧x(Z<Xwg x#uxΖ& '3y|0D++a .CNž`dH+�n‡~i S:b`2m(dn}<INkg]m )$BBCڢ뗿YI+ 4HOЮ8 asgv ;ouKv$D\w>K1KGS\Gh{Iő=S;�J$� w/<g� Ư27d7m[R@j: C] D=$Mn`toƊN4`�[j7r147b=Ɨ-  hQpc\ԈcKd"_ھbMS}༝{NZWMhKۛDOX8UY:Tpu3Iou\Ncnx~UkHUo^Y',> Ťx2鱐YdED+XSt}OM�G]!˭ t hd&π/k/\4[ntS[f>P?D%Dŷ>婘!eRf1^<SJ,TpFw ėr]j3y0 jrJ+Q(o}\ 7;ޅp+-sFQW=ZXۉ�Ui� jb~^$GnOUŚ^rb$A=4WIey+v�Xwi4ci2$W[&\Β!S�SEuq_�p.E Y`N.xFs"ƛyВՖiSg,تL)<ny؃DN$XTS=:n¬&AGX) % j6|K;kr6r)N=6Λ?_d]&|XA#%CL7w[BphxMe:m$RE.ԅA1-V@�p#ej|C(>L49pOC~`|P]Jћ)]ӡ4"FJ4 r'J`.F] ] x '?U1prtVu1́O}:rcSom99w @fW^9>9t1 hC^OA7I8 `<XE2 )ׇd&O;pto:6Daniel Kahn Gillmor <dkg-debian.org@fifthhorseman.net>0 �tJ%eQm I still receive mail directed to dkg-debian.org@fifthhorseman.net, but I no longer actively use this alias.� 9&_}2w>պ /i7a4 ;}#Q }߾xOgkLQe1G$ $]zeݚ_7͋ =;1KT4 YR}Tq@2n} "S#<Gwbv i.ǺDBHE8zdk,leu,Уw},3:Hq@<zZZc8VծASyp7<Ўz<K̇sEI+?< IFdʢhpǃvDD#Ҕ z_Y5_a\h=k2y^q4NWq)#eZkH6q�אCHb~ :DL/vUUQbL]|JYiA83\P$;. bJ/}Y 1JuS|+ ҟ0e4FjLriBe]$J5RUBt`2բ<�&Fa f � 9�7{IͿcمO:nSr_~G̦ i@fg:qnB'J\Ncܹ7Tܞn/- d{&7nflƺ[Z [j-S)G!xB݃:G[Lj>9UV3oRFDlJU}, 469+>N&!qq!0?<}'##w%@+L ,& ~F+)XINpN !%U$3+'s'Ѓ'@tœB&9;=`nV׻;Ƌtca>rۙtާi\<J4ūMp1DHj7dD^͕h=+6!!Ƕ `N-Eʔ|IVsQ!.“{̀bD< ;BG}>BiZLl2IUb\G TtJsW3e*'�Fc� #~p2,~b^6"3Pm3Iz+[;b|<sv7 M*vMf?cwEcq SchzW:g,VH3mr=qP,'ލ$3>}H<CsMM 0iʩf_D#3L=#ֺnZ&gk|rZf"ޡN""SطFȔS?|\Q>|z"Ƙ.0 L̜<Ka�Yak"o3XAcnfrn17::t ,x*N]{kAnK`Ő/S˥Y'vȮ!F�"ٵ뽰96g~6G~&nƭȑD`%6k)RoTݫLJI1=.dsjͅ,c2՚v͘"i6y 3Vp% :/%MUUeSkk|+�FaԷ� 4Ϋ{1wL$Jg%|*>*nd i j"5GӓYiׅ&쌎 ff|FAɪޮcKO}UJҳHb\9ØnIs|Ծj!ސNyȄNCO>{iՕ?A·z34k5K[''rcn6o.W'Bd6rc8`HKI^~1Nc6"}~Nr UmYaG]u|\d֒AƦ(M[p /Fn\XDd.}yQщZdS<&p0(<.U'IO.3C-%6Gp0C`Q}d`jX?9y &ו? Py )4F-APlVil|'{?;EVx<3Gҙ0`~9`=}p 4]~A 6n%"[#(*א�mU/qYZԱAS܈J� Fx� 7bv}/~�ąbEon8�s!HZgHˆeQlF�FY� ӱga9?c�>iET<P斜F�Y;{3YZ[PĈF�G3(�  sU|k�εB(u/}g�^QJ6(',9+/SF�G3� Qٚb ܾ�J%:ih8#wR�hߨTF[Gʁ?ֈF�GCpg� ~*]*�i�jsZl\z[ v��X7"|uO^~6|oF�GQ� &Ჾ9�V\!fOm;�T|A ntQeZskDF�GQ� _q%}!�.Su-  ]Vs�OX{1EΊ,>$;F�GQK� hydr�b ԫ:>I;LuD� ] �/raQLU<F�Gp� CB;up�y'ASKLߠg.� 8ڨ0,8�HCC�  k֐Gݾ|5M$F)L#fKH_ Tҭpe+> lT_Ԅ˹WӤ!~J�Lck@ޫz{*޵O1MZ3f&b/P`׹op?ʼEȪnDIf^6xxB\1y�R[/y:33*iPiu7pGD &? sXjvdzU+Q€]íuŋۂ1~yZ,L a<�Y58+??ְG>lf^*twj[WQdrҩ%2/ %'IrS o/uqEWw7cEg?4MdwʣHV݁m_]AxԆh lLBg�rs4g08P=<Z3 *qo�)wJP]4r<[ߞHD3hIzH{F襜|F�HD� P,O)*;�QIrU]OJ:�kCh/FP/F�H]$� ~@z#�o\ zb?vO�/du/AQF�H3� Pp,Օ�~Ǐ E,+RT8�bq:{8 Z"�F�I� apC$�8n<2+JE<�x9!``utGWqF�I+*� cUV ߊ�Y&sKI0Ր(�pxBαۈF�Ia7� ާ=>�&&V?Y s"䊹�|G}0PC+�H]8 � }XhǙ^ 1{#(^ø\PgN< d|@W{9_O#@䥵fkb̀"�9Bbz RO[ONm/ƨ:ݻe U +8=PniѽdbX)Ns4|K)q-n3n\7 |Q'. ωai칀Xprāw7|W2";�% fI>   �� 9ŀTQ3ȫKyxB&mr\*p9k9]g2&luMBJ=#W⳷z+~]>6]԰B,*M#Gr >ˌ;g%騇mG$=QU6 y!tC ]U4:'-FNlL_ l0]!Z.[b3Ƽ$0JcCʈkva*屢hC:lzKH ߵ.(<۫P]`a*Dl{ZF=�WAb搮 @(#1ckFJ zuY)"?3;5 [W?4LQ:׵.{ \Wr-lG^怫X8VLeu]Xy$(nniqJv~ݽZqU]z_z"t=DE;7LQ+x12î'; �%I> f   �� 9�xޠ*)&Fzļpt? +oʁ|tvZYG#:Q:.7 Oi *";7eaG<Qdc&N\bۺ ̌^y(h6j"܋;G!P 6b}(j||t�XbVi.jKFN3܀V N4U9\L0c;/<1@|$wy%<AsgU&,:dK?ŇC."W~g4 ꁟqvVʋ ٵ i2v)iȺBVHNc` މfO:Ab Kb]%*!^} `#D^vs1 LEA |Ml 5ps;XAÊLBfF<^PS3sI";ឝwR1 ^2?(Y I/�Qg)~eU)NZP $}ߌ-_;�% fI؞   �� 95�ݻ;r}Jf5Uy՛ BeE&EOHFkX2/_mj9�TŌwZ3<iԑ1A }0p 3htP[7 �?V:Ɗ(b½IFї\$кV>�G;" ~tV4ﻣ<I<|uV0 4*ƈZZt|s=c:CӠm{JFCVM\[&XZm oLL= `g qH2EiޝLA&@]`ZBUMX x/N/o-6$+->y41INwip-qQO4_k GHqvtaS2Mo8d(:][b-D#D>iOٸ,8=6,3QD#{k|krzseXv~'D*bEϗ!? �J� 2(a5/'1(#v<ЈG|x^<bTB>dBT|l}HLrB!#i1'iu79J],|p+-/MfJ??~@{�i 1]8divמJ6LCA9}ˡ7*640oFqrVղֶ)as!dҖbcIͯ$k".MŔ`1w[LI>A<= CY?oD!ڷ[` ,2G8H Loim/OW vSLʺXu% AoL}_80A!Po:HO9�o⹃3(vk dIkF*`B>V)+}2h!{t7"/nnDFEdgepOc@H,խgF5?rǦKx�fxc)<ӡƠa<*�x$Q+Oל=7Y:9wf#Hn!TIH+l �Jj�  JEo_]�@ SB0.|cfz1}ۘhT9lw,B2|#')"}Dnac};z�bGpjJ_M$D$Jk]vނxֹBh@=;P9Ԙ]sGgdN4BV2춛wڇ"UQҙІX3SsB\'Pp-i=zQrqeG '[o8J ƌILœ1ڰy mHlBD5œn>)'hckcK5̄}*-xpQ7MW尊UG86چo'ǁŞa 1Ia`a٢Wن u菪gЉMcٺ 5F:ΘM aw'98^jމ]4À:֧S :|6#p_m<�J� wC$@F]-t{lEɶvFIKG0ێU[gTlM˕�|W7 'v$p|`&zOWG"ryXwFf<VE'iT~2-1V}J7}%P-;9L")(#&%n<@+aCTCdU* j_Kڜ p?N ddoDBrP: D] P vnp˕@l (z?3#9K;ӠQyH(? Eb`?pYC^\0޵Xi8WXd Z!{=KSHl_hݟ C)q?8\Jfs9?/w\%˶8!mTco4SX!4\ M%XƕKocd|.˂J"w u ie#m%g2XVbW'+L7�M ЏP5oF �J~� 5J\�)1:z-s�Щ~F{{vK<gF �J� uݧy̲�ȦN]>\fFj~OH~a䀥t#gRYz)|pj9;T#?hE.x>*6M!,exVbJHwJyz;Zo򫜏xAE1mo wTz�&C+L ڡb̚ mx$D[ԍڜO<#0 ,=*d"GM,"=l8t`,ٗp{YcfU}6n>p+&uwG)]#JaJ80n�pưFi,ɂf3ğmV=.�(!uk7UPf8ǛaZv[ D\E \v^_jmPE)c"DM&ÿm[%pȑɁ@DbAKiلRHNB%O`N!kS �J_�  ~_.I5Tn5q7С3YMtf |_{"Lӵ\vk]- ߝop^=7YdQ9|aObEaA=FkKS;V' 7Vo qԻ6+ ~1{?(JK>.i6CxG wzY5/?⡦ug)@p; %yjK c^U""sIKztÊ|JTg{WZUm -81WUd'p6m(&DjI܉ƷL>L+?0uCJ,4ڕeM&4@ y6ư)Z<GGZ][5 e쉣z .Ǽ=iaaa{X2T鎫4^zvzG,Se2e"CEZП- q>HjKhUVbVqV$һeF�J^� FGmjz�mL*wU. ӏ(8�`=IسmԦQle�Jk� 8yMvג$? b+UjScHg}AѩBO) K5[e.%=ːy9 &A:0@H3kZ^9xAlk=^'6k9)pK()6�i9jhW{.SIrͯmr=ΞZXװ*&4_u(d.,j18Dw^N#ʡ$ s*6MJ^{OQ϶d�ON%�J� f5g@jim]BHp"#*"ةMQ<JB$srUU  xHc٢$q W +7'Ӓ\q>4 /Y/l岜1og!b +R�%2(VCMm\VͻROs<LEjBS!ZOQ^Ty|N uTN4L^ծ><eT Aڏnp* 5_؜7aBPHVX�<ՀڱyRЍ!ku (Hx!VZR_SˍJ7wh ZmN=gH *jZkvJu# M<=uܺ,֡IeJB[j,)'f"ɔ)EwQmaoZXowYz EadK[60ЊJcM#w$w -;eisp 4/Oc�J<}B� 7KjuRj. Zp_ ]>)c/BP1 K'~+HvWTPMeDFwp{ Se)K_#!`!QV`LLJ0ExZ坃s}eGH9H7迄.)%JPD i�Pz=| 0J "|n(`j!ty$dzN/s\,hu 61 ߤ8.V`�voyk_E4 bn =kr_#¢X!� 7N q?z© yt6p7s9ct/JJoF4!mEG!OǓk~ӗI 7yrDaGm9bDμ8Mc?HG>d3TF=E=ǺĔF(A{zU)_- t1\%CwGT^S4v=.ۛ2`5�eF�J<� Ik ZG�mcw͔=8aP�P;m|rɨ&Eз�JAZ� zI4{4cJɗfr{H FC+%^z$YyRbM(NW@u$ vRӎΛ8&iDû8jMަݔ8PrI,'Э&4SK8[v{ިZGw,@LI;fPd Rk[0ɭ'N*iDڪs^rDn#_AHh s G=AAUh9wGSX<w1'UJh]mVHI]Ei,FKD{aOU]n@౱8y p‰q؄3"ݦ{~[.{nuMJ(>EC18et8u[ۘcDζP$d%Ghl[/zT'ǜ1`u=v] gnH$nRG6H \jr완Hf ellP~DK|P�K� #AWI kΛmm- _!@YQ3=h,OFbxFJQWZٰCGWd8VEd_.oxCdN61u۞>'77`[w. 0NݳJY{2ks Bb#H GD4 `-,[i>c5 Δޖ�~[* kcB+5NМ-9g 9-{a161<�HDiJY#F�L*|y� ;.;'D�v٬kO9Dl8jt�%s9ˬuq1_L*|:Ci, J�eQ}\ni E~m_FӶ=2?`"A25뾛%xy~L>F)ߑ=%H Cﴛ($\w#sJ#jJza@5Lͯ@}ߎ Vr5Az@==@ i|y߂^ $M_oK`E~BY0.> 1k=Xu]&8߮k a6/{-|:>(CupޓL_]{cap( qp;4>ʼnh8ԍG+vM'>C,iut@]Ҝ}{0X*cz/jz*c)?B!I0(&#Y$!8ad~p@($6! B^D܀ gX[B/Нg"̕9&Q{T Stk|t\r4 _z@mvyE}ݽ>5!�J$� w/<6x%Mr"&_'Ok/Uf$μ>.\5\H.sfŋMo, ^PQ Zc ebs>[&rĩuGGT+`-: UƴFls%ORo5RqAW)Lw:=+e"9ȡ 3T\Flo5̈́%lȪ_Mx<8bp%RiBA{}``u4-AӘp4<jq E /,W`YFxߍ 'tXVwO-Gx5:yrVG2&nbrKN|]X Sy-o 4R^\HTF-ZZY&CٙIa ʰ_R6QZXۦP-d!n*s~1Oj+Z9m4|Ƒ}_GR&?[nռ̭uCY�Ui� jb~^$A[S}z;Ye<|tKnY<Y|,触bvGװ�Ue!T،&mļ*rR]|Չ~MtVLiWoͫ: [m֠y2Ē9m,ީ+4w"3 Jb7ޟ')Fzg0|'ڽU,2qmGwW!!y~�GZv@LQq^}ËyJ>=NBK]rZ^hBµC2w7yBo,7,ĴqO Z #>t9*j~žo@<sdexzx Ufh-DP j!?i يI(S&zM9A榩Е#!|2&W. ;Uµ˄Yp"dW*7NŶĸ%Ծ ��������������JFIF������C�   !*$( %2%(,-/0/#484.7*./.�C  ...................................................���x"�����������������4������!1AQa"2qBR$br��������������� �����������!1AQ"� ��?�QO"IR#R0J+;TPn'w#⫬u4D7 8@֩zچURr<c>{* Yo�K`1h.Í>4oԐ�Tb(i6&48!XRv0āA5a\ǽ|2(*xnOV4j,6ж%Pރ]]«oi#̎ E*ZwRh+̟�4ѐ�  A-6/-XV$f0<KKi(?iZLb#*u` ޥGQSIT|U,�w5Eae8ا?CRO6<N_ ɠλE=ޡ]I,h?žo}L O!"#$v,l1UIq vTS6֙i횢xO CybyK;º*7 LN|<+�ϯv4\M<"qP2H�>Yiw <ksr/,ONEV#j~ڀ>*giW.4/il>tL(G2J{R FaJaJH+\Ci%C.ѧl5>F?^8]; !==T1.9e>'| <sZvIR =*@h6{r#m\RRf?yjt/uJ߽;22LܐjLH %?5{-ןI̷Q/Tg?jO?�*lΥw5q(wg$ nO6vvbWʤ^sk,0&D"ʷJn�$#r9t�iE!?(PP.-4wJ!<zj5躌z\#6{0aM0JaJaJĄ1Oʒ.3}j> c}5x*u;E%܅#T8lVi֟mKzY7}mxҍ_ΊD"2pWО4A<;:\M=p{U-ڧH4u$7$ ܳgT:՜w#V\3czU)S UwRPm@GEw |&}8c xPϱ`29ŚLqfp)<ߑ*kHPA̛Fk[YLVsFp9ᅙZČ&rO8>ݣ4=*-st uH˖bqqS=:$DL��4?;Qg3HGްڧLDL �;M%*ׄ.ҭcI 8Qɭ~� & \gq֩:~!cXDUzKo~E=G&tZ9Z6Z_i&I[!2hiMUT!By1sQ'ucw5N9c}Fh lگāFrYΚ)Co_ۏޛT.Ek졾u]C(F3t"*~`YV Vo'͏C�;Rwl`)PrNNiljvl? MLd\qt, 9�gʜcMͭt44M<+SA%xA kCg4 {7=ډ:}%LD2S+ܾq. >@b|Cm 5qp>Q <dT2=Zdk8`��1z "P(UHUOMYQÖ" r:7q0 L T/ix#=jb9(!LQX/ftH|kdA3Wum" n]_[\^ߖhׇc?N{U[g|Suچ[G-G`o>AFI#Ԛc]�QB� �)5cm**W"v%Vu_f[H'2h5SH#\`j#iCke4q?RY2BżT] G8'*Nk[uI-[tm,WQ*csڲhk51e >}I六YOJ]F֗q IB j-zUbkfUX derHBKQ/@,uen[[4!.ɢQ1,Q TQ�⇿W.pD/ Z⧷1XL <sJvX@j` eA�#=-RKmQV6z,(w`�֫@mf Xo3GzLkhd<À·F  7uݏz+g 5ʝV:T^C}$,fI 0{ӱu> @Uo�nΞU%w_OEjWHKv�U4d4qWldn>G<Vgn ymbD;#@_qWQ}DTʟ1U^?52SlfoAD{lޗM 3R٩P ǵAqSmܬQݸEL AYU7SRTbsn<uh$ZɷeH۾ONZhSpR'#ό`"@d�th4ŜLN8 P?R@8ˏOCWb1U]A[ꑳA1p�_C\r[dk(rKq, rw1 =[˛mWG',$ :n>?Zu+y&cc>0zΪ}Cծm�tOa?QnRYYppOO* t}..%muKM v j'HW7FyR mq̲<2U2U?jeHlشHΙG*]D8Ҥqى�Fc� #~p2m�lxlD $V*U� ]x~˙I7?h6Is ;^(χ]\.cr-%:T M68\ G"CP دs7ts'<TauC 2g3�}C:`20t//Y`@}G \Џ͔h:״>,y(N2#ʅn(fCgܺSRnDtn _gmr<6S5dov=?1Ta1P]nèP5_u{0V*?,:c ^DԹ_EѓK_D pwA׀9QTivЁ$?2OD\|S>xEwǾ 9_E.Vз�M PDڲO-wE V9a.༈ ΰhgَiʼn�FaԷ� 4Ϋ{TAE}@FJÏ@|z1I-:fz c" 'դ/ːm 8'?1wfƒʒ&s\h$hGjUE&:e9ampdZ8C(=VI&Q~9ՙOaN�R>@߄tl|imE <PE`zc1Ѩ\=\L Yːwn+ĻQ6`0J_esQ8aĀ|^|[o1M#+y΢A1*W/uؑE,_޽*{`߮Ⱦ7St {;R:ڭ*=~ VWZ U0`(NQy7=I4:\)xIwpة괘HWIqz?<y]17e MOΕTNO{>Ah _5#&RF�EggXqs|� �MhJ� Fx� 7bv}~�azd-]BS%,u&@� ]:vv^ܼBF�Fc6� ~@zN?�av숍xٝ�ЈK��s|?b҈F�FY� ӱga9� gf%QTR��6ijTvvs~F�GCpg� ~*]*E�e SϏrO(�aa>yT wDgxA.F�GQ� &Ჾ� . BB}�@;:+ޫ@͑F�GQ� _q%}Wb�=H ڻWg| 5#�TX%e [N*I-LF�GQK� hydp� 6=b4uEm��]YQcqQsF�GQ� Qٚb =�"]l @#)y1�Q3;VTgF�Gڡ� CB;u�8]lxdXDR c�"l6[A_/޸�HCC�  kW|嵏B^ U\cP{3#r*p =ًsU痓ޙ++먣GJ./uXGn5Ⱬ`#EN#Kj]qG֤$Q#K8æ_$Z>ɕ`j8L=ȇ,s. GRrp3oeWَĔ+AIh"BHy? h1KpPUN6wGa" Y;4gJN(Zfl;7=jBOcƙn7,!J_ڟYvmX<3uξgVl/n[�o'rئƔsb5!J_iV~VYiEg�I81`^{PKcI1!סL l;ݛw׍ ;q_+OpѠr ץ+D z& ptfX}7&|F�HD� P,O)*V�*Hh ��n)fDZޅ2qHF�Hab� %[J}:�H®"XRJp�nbБ\D=dӻzE�H3� Pp,oG� ۈ5RH�ýTB c[xvF�Ia;M� ާ=>[�G.m"yR6F� OfV�I[fBVq8HF�If{� cUV (�؃&m ^Xi{5�PJ9=ѱw4b @�H]8 � }Xh>�Hess*#* o/ϗ']#&im|=!d}1:*�rb;ohw͑q/v@"q |ҫrd&`*?p( fkfc tvz- uiGTlb.$S/ͫ>Dk;l)ܒ~=5}RkAd S;`ۡRס#\??WBcKMDIb$/#0rt^C-;�% fI>   �� 9,+p0 a7w$�ńdhy !,wt2g'f�Rł['Fr+kê.qMJ >nm#)pyr~dvVܨ H!cLG:ɸP#,aayәa<]*7^%q#׋ٓhV=z_U0LCE{@.k( Շ\?z9)Í gcМ<hװ4t,n ґ~َ,/*]#h@=(D(Qimo]C!@xx{-AaR؝\#{g۶cgv1᫺76 f{1p;qw@sb_9!nDY$3R_V@yAUP9ڽ?XeF~~?9U'�]h<9q]$J.0qLaH6; �%   �OKB y� 9G3<sf4>]*Rt"R"ȈtLA_;RL=:R94TmJ)R#>=3Q$0_pRR`G g'½ k>*mGgn)F<a_e7e:#wW薦 +wkQd缠v<iۋI+;֚8YLp+0p!;򺕾~bJ'k�mꥁqMW=*b: E+˸+' 瀞w6cYΝ [oR*|.V}3@#`Ylu?[S*{F[ F"L@ԂܛB|&UOV*8E >D%E,sҗPP UZZ-+t = l�_i m7N*eSf撡_;?|d¶ƀXip8W1Ѹ6*H&{*uh5@q|`;�% fI؟   �� 9\'4SL0�әd뽟\;1]_ ^ tQ/?<Muci n&Y.]) \F5S/ Kx](q06!*0B8E㤩 |l-uoF'_lXVMr^u0;y/8 oMvm+$L(Rٮz)Ԅ2  '{8 ;)w0gP!{¾Vߣ:}i}xe%rvݐ)V3%TgDKhf+o`%l'h8hӑlM�gc3m,- u Α*dxpA^w`r%H8mFzQ.d/@]/ 3 a쾐DElp,' % [ivČnE @x<˸l/ [_k@w (7)Rν8ApSu_^l<�&FaB f � 9&˸vݘ1s3"JU` Xep_|]"ԧfoRbK&d!k<{c4ܔ?q!XRs / #&Xw')Uu08A?;{=_[dݛ|:j*r<c׹ xC:1</2xE6y69uoZIu[ P;VUhr+cϿJ~GRZRt&,R D�v xN!y/dS0?3lhe9.l iQWsU\a(,`̈́s7br1(CNNssՊX0_ iiy[@v3A <4s6Q�Ze,`@ֈ:=Nۏu7/`Kq'Lc:J"@Fnh|!:K+3ᚴVJN!W(P6/N>;=J�&$yNt&ȍ9"7S9 } �J� 2(au| r'rUAҀg@[F7, !~f1a 7tRH5\R`7'sfWE2{wJm)_q)k߂+`D˱T:g*@i!5/G�wwLۉN~G,#Š+ךVJq55tE'&~b-ݼz)HxIO~agס޼ ;cN>-.jg|kHur֕iW ) R=X=| `9Y}c95;O*vԷF'IuoM<%ꃊ%xҥ� ԝ˶s3HXG@=)w0yMK4IΩO^}(aiKnT^/} $0F**ꬎ6+:kPH˔՚vZt?cK4c-$�J� f5g@H:'O8֚C(Rnj?GHp@V0*5wbX8&Eprg|4V0mn<3"wxPox'*ģ{{QeG;|Յ#Q X8d]6"߷{{,C;d  K2jVՊR*E4=5@Z T,XJ # d7,`X(HcB:8gOˋ$J zR⼗<G4^z#U?0IHl(yU:yKnTɅk7=GnJGc(Vzeq�RJNjv P 3'h<oS'S  M;+iU`�4޿O|F-b/ .3gڦoDeT\ܻܽ W=$T?tHkVƕlJb8$6 ",w!8ZRdQB6xe] xF�J^� FGmj�z(͖ B?Z:�<-|Jw];SQ�Jk� 8yM .iَ;Fx!"M4_{Y^6T_c"_HD7KNrqJqipq~ 8[/ \.Hy[j1{ {` (O@�͉RZ jX׆(RRaGw,* z Svf`.+VX3�yZ~7: dK2!܉1R: 5WN:<=fa{x�O5 �Jj�  JEoJy>9,vϞN;fk!$qysa7P~јҍ ='Bh.W ʤ=�i~X ޼pT BfYhC5ϗtY kbH-C\AgqZntV[ny5 !MMf&psF?|b$U -R(CaH ɃtqBi࿛:EZ,j"]2aH?0qan%kHJJ h+-p7ڏ3QL[|`0;�Cos/ 1C.p&6=?$ wo' C6QK%%8am"=E>&̪˕m$zU>^F\e4`gyk(?[Vϖ?*Y(VR-ithGۼ}'vpڒFaOtg_#%�J� wC$?,eSwn(] ;9+G8&ZPNťhi8+g|jBLʛ*gk+U$L}�*V%|c=~Eq/<ȗE};qLf * #{gx=V23F7_L -,~-v6KͨPǬz?ܹػܒ%;|V~1w q5 Kkd= fbㇺaێq<Z7۠umzἁ4<U$߰R $Z-o -;ꯥ"0/0lAߖ+WHD@�054/Fyrt)h&h)jUZsFX(й@Xl@7l[pg5w *-)|(h]iK?y죆 gu0\(+kdXm_3\>_u!.˗M>,DF �J~� 5J�$MtrX^ly� ~g <mm �J� uݧyrjt.Tψy#n!d ZL:>1ϾP&{Xw"CЈSE'˓81М-0DAuյ(?"];Җrܫ(o4I!"}L4 邨UK8t9@Z!laQr(f.~#ѼR>RCΙ3Q(j�?QNw{$_UѶb%4xm2]кx^QolR# pz^xx`߯ 뼦۠1ށH'/E}%ɲI &�D.$88w�D,<9KHt?-8%`ǘc9bhQL8wCG{`k Bڑ #4%3t9ˍdגDsf8j?2fLJ2ۚehK9(-r?l}ê3TPGp:ybiW&ER(;I-x$J6z �J_�  ~_.I5 KQ:a- ?)-M/ ]uϲge5;+SN!qW}fC4m__s`N7q1�R?w< t9>Fgmof/^#VO59 utA›4A!ǰX",;ss1pK!ҭyTDUa&"}÷h'޵8ƿT!7}f$.zl n K")"xQR_C=(Z5R9$qKIM,$r,(ϋ#6 ln-iGi ҁQߧ"5AJӐqA!]H)cg'Ηcs֕T&??MiO݄D_UKy+.EIۻLM"'-Svr>|Mkb}(̐*j{ѿ^~@ -X<)ݎ-\vǥ�V�2'[EC+_&p#LrOOҮ؈F�J<mw� Ik Z�AfbrEJ蒀3�FU’ؕ\5Be}B�J<Z� L=XGG[˛ &8ޱ.d�ހ<Ie1*1c.QGIuofvv&p+t)]z1&?. D#ɉFWŲĠj SPkJWfA{17N:bNO:"Q;oPDClFkܷXPwݷn<k9\:Pɒ}z;,JB1Mw�}K;VjѩӔj 7ǀ;>4aXt c&@G Sg*9xIÇTOJi {B~@:8l}XlyV;$t~o,`7XJ6@3`~5iz'aĕ7ƅȞו]F>zz6FG9=T,s<!q t ?H+> ~pk 8\ (G-r8cnKB1j�JA9� zI4r5cTS辊{>MZt3Um^)`GR^ضڠ7Q3ַc0:ڢ%Lj=6% *u*\jl @3y]E~ncL E V7/M6iWȥq`MAX&܈u:O|gu513' M鮗~yِEXsx_MLJj{>y>]NowYi2IySM((ևp)X+ j8NZ-HKjT» EǸmo` AW)�>,ה & RpqpT1h*=aELϑ،#U[A؇!W^Qy<|O@/?Њ𽭭BIs,ZB{׷eeI:HTp߉.}ykT %B-u⭄>?n&Pl麠M. wR�Jl� ;f0%+ZnZc$#ExmGiY "䆖>1|0~f43hٞRhlz07f6m@q GVRSX Y:":a>S-ouc2OiiԠFu0H$e/ ,HٱPfØan3ퟣo. HUȼlHD&J\d4s:</._wl{[ʖ14Z[R赋�+{\v}f8<yMi%Z�Oг ZާH9/9~_$NeĶ OyL# jIcn 7^Ӿ=^ =)bx={n FH[V �#)9|7Q'H=?^2]8zm~x5k_T2gp*n/imVeu`Z] {% �JL� MMy~HJm5HیM K+%!xX:I/ɁB[wH(7AvEyBHOM{͹ (.1Wc+[ hڤ�gZ9.ē^9 m̚ȼ}0̺Q3&2% aQyY\y<P5JpšBYBM .'߼PȀ;(5ҭ!gFΥRN@ac^r|8DwaG[ֈ=4B>.0X2퉲up1 Өn F~ X<sFJF2NH8NEz LG\.yP> �yJILV5-IޥgL9mp%4-w3U0z ⁍n)㊴|L3EJr*A*}%X_)٥�Vph[ ~/ O(#ë-dyR<wZ:SK]k5N�Ke9� Uܣ(x]Y614kIOޝ{ʁV0C< Kp((8J k YiE:C-M[6{"9G!J;rP.q#B\D,gq@%K3cW7Yg z<yHH9kj*gVLP- 6PByZxi7KD:Ve%V 6J}Ar 3{̦Cvs}**m�6LTMDqKT@dRRu~#H_?$LhS׊_LS6.Q*{yo},?,UYA/n =RLcJG }66a@߷^<Н㴓Cv4h컚wGF#Wl,^u %BG~N{'N7]u룫8Tq4&%z}m�Ke� iĭg ^F/A &%`7�}QλTtEW ~.= NPa0=B~V N(7 kP&OM;IYH]%>֍̔N~ᾚ(2< *_aj<. = 0~>e'1nY53f?J/X'4QTۃ?+N`V$?}p!i+nV( #ӌP y-y{dѾ|f| m&*UĨo3"#Xa.hk?s꫔3uϱrƆ{ե+6&p L^^ܭfCVcXKlZe%I)eprB/p1N<:s'@)RxN/&"ɝ^A% `^FY(H%4:Lw։b0C< 7`9ݙg՚ _cwz oz-0X–@W[j(V!YZ KhA!joI O<Ȭ/�Kdˢ� K7e6|JK!^^~E:QD?U/D%jZss>)z. 8UmOlS*Sb$@Ҝ<7UM2,,Lul_M\ͫc؛-w駼0y:mHY&1JȃCkKn wN M!(kx೰Ћ aT +'?K\Hn(N +h"& hT.1zp<zlJjN{9W+ j [)X}U{] 1?H nN:@kX[$~VV2 dE0RAr`.cRo5#Ὠ'!Yĵih}$8R//R�0PiX<J04҄. `:m"n35\CLT0V6k֡>ZYx[/.HM#F�Kd� /We{~�e?׵˼T�ƅten Kū]l�Kf$� b]qւ$o^C~,g(2ѭ92-p"|/h!VDϫHo 8h߁… L8�Hh%je(+R Ÿ~{2c-"sT)L74�_ #"pZWJ}JL5�г# owFjq&'N jl%otqg"CKԂl3z2#^O޸!E*-G5uZzԜPFxA�^ �KhC� b�u4RZlEZMzh|/rؗ6߫p_\:TD;Nbu8U"۪e"-'i \TggY }V|Gv5"V%SͯNUɔ sK3t2;u#o7%JgMk,-P^'dw{{GXLkj*A1"["lTᅫՔcE<} �\hYVN; jPH = & 3쳫&v*>3*W ׍p]<Lح1d$XWյ<T30Ov] +ѿRZ_,hb!ui(m<cD@u ӫlARKOF> %OpGR|=O2/u"uS &wbĶ*J%tcPK>ڠ<br=Y&Y=TK!<=)�BF ;J1 ڲ2gى�Kr� 730ɴyJ2[²be1 Ћi#џ^o[~E ^?K NYT=ѳ#~Vߞe-BTQgoexGXO(أ�k, Bu+e\&0i06#1ũ"Nv2z'zY' ڢk{Lj.diFAv) y97&K4͇q/I"^)†|V�Ky� 7�Dѯ}eE:H^aRX˽-Tp"p纄/qޛ'`n2Cށ1 qnub'%;ާsoNr"*>|pҀ-x%s)uI#$8=g. "{PlvU_7)ު5", ?*4_Ne ;!Ii ;JTl\YAQ~&lN5vp|oMX�K;� P*IF[ ̫^@}@=]-*:�`Q;DFl娀X nJ\=|΂e!%w\nWMJhnEc5UjC42pI~b]xVlmBbOc\A�ܑZh:.'7ӅH2S/Fg` bP7 w2j|/0ۀF|HD9ܕ~<:>S]X$LbF�F8v$'id!ZSRcwZ7,(\g[|Bso]zK2<Vk6S& #*5o"Fe1dZG ONN:;sS&@oħbN 3A/O(uspȁ�K% ϗH9?b!<8> C?5a/! W/㙧!Paqø#‰�K� 6ܜ}P|tIl+8~>`5If4v?9Ja5Blbۂh*Q֠K#4ȐmnI<–O\ʢH|IJ2V<SȰv, XP/'yh쐀#[[48R/No,d5`]Mymn{ =+j)D׳|ȭЎSw4p*&,s#7~,*RP̹5ŒR  {YXЦ l"c{ }_5qk^ˠrHqq i-]6cnrer/Ԉ?Rd9Eª'#<+nUtLCdZc7edPRpuRz(myvL+<LX&mϺMxyY ɐ@KE\w~/V-mS4b_YaiB񜉾+wNΣ¾Fr i>L,TAal❈F�L*|y� ;.; �\4w 0.B7"پl(�HJݏT2}/L*|:Ci, �a7o:c7xtv>㡰5eFUsM[퀥$WG6Hq$77V7`wFPKÞEm  :# Q \ GB:͓ZJXxJ֪"oX 8ϪשMw0 ^6RBgpF𧻓#*:p3)22DXݱRP霵7%4q.L@ tF嶚ŲC�(Ju`#g-YlzbujKRX;+B\1@g(mُie V*Πs�źyC`^V.#d"O=DpN|mL߃i�-0g6(-f?X[zH┐ č_s#nӌmH%!`QM@60z9UV҄c:f}\QFeFVoQpeJ ̬/v&�LPՌ� AocY�MWgIM5g5 WgB#NЙ"jtcGm3"y+Zþa.Ϣ^ j #m)V0Pɘ$϶r.咭9JP#:   =K6I)eP]r-dR#{IR) KW[Nc ~.ydJh~H r8Cz 7nQ"#L'tg=ڋKW6]uc* �LP:� 뎿 @D2CT_AsAFYnQ>�7 s"_^؏-"Ɓ ga'-ۓ;.~y,L QZ;1IiD:"p0mʮbAIz*CL:ZF%c7q6qCnP/b  &p mAPt~Y0czj_jK @Al@&eǯ8:9\"RҶCn^+A.A;Yr;`&mٍxfЂ𨏱هSmdѱ>ܯ:*o'ݯ8X<h|8~ Vh;5)~PlshbIS홱i !հV+O"$jR5@8:Q3|r䚬 C.m]]�~$aޜiq3΃'</F럡BAjÜk֩Ɣf �L]Z� ]2*@k4xha|2~HlGL=7[5A;8HMP;ԥؠ5>|;k):;fM"eGBySV-kK lV(lu۴v =|зX?mkmyrֱ'a2!dޣlozHDu68׼g&vUN%ݘ  ss6 H.b3&aS ×*9|= 5./4⏩ʿYwBGl<ŰT6,CSH"ɎGOKBe͆g|яbq.?sp/6@b4`K|*am×q3iMe "hd8 ;ϡ- WCuCIL$ /tm*+@S{Oɨ٪WWq[[ �QKK~d-*gT Ʉlžj#v.{` < F�L]=� =-%�t Rfz9^i$Ĭ �/m\bN&/M,߉�L]>� /p,Mn%up�P{;tzr9oN‰uҋ<P r&^kC~*[PU#[$wܐf^bLʸamʳ~.(sኩ5(гjJ;++Tu U\5:c'jK["H,\`zg#0jWU&Gꭘ1׼N[ul^Ӡ| SnOICF,LdZϟhzu#mrdOx%R;%.@LxP&KO!v|C[mB$2.g©*.z2J6u5'(? ˆ7 7 :ǫgAh= y׍p1ϽX^t ~nϵ2C6-p?i`ɵ%Hxa <ih* -~XsJm[nŸbyģz|%6yҗ�L\]� ,|1F!ai_Œqئ)3>>J L0Sa1BL1#nv]vˮ9͔Ps/@B`2ؒyGՈ"�?huw{;"FY+L)Ty[IzX2;}eXYNc-q K'Ԛp˴NM_CfS@`le|Re閾!Ϸֳ8B$(<\ۍGn3mw/="9&hNad@Btk N/SA lM>[\$vfһGw_$kusn(=y0^Gdsa סC-Eb8*fY|l#ahMX]GIJpbBfJgLs9^g!pI_5=H|QP=h el Ham˾\svv62}L\ y)q7.B! ٱn#pF�L_)� @ tvl�ʷ)3.ǭNQh_�KȈX2|+H(s�L_)� ]18�Asc3r=>$9]3 `)jx*YTq ~__nX�Du7 O�,~HN<4q:r MqCKk{*<0wt- U2 ND}5G%t•t  n@YH꩙[S_O '�%v]}?qEԝ>$eXmo8وA3D~&KFM+lۇa.CO~ʵ%hjnP. {ޔk'5 AG 3 > c;_VO$:>Urf|6l۠tBd42�c~(A) B A%qamɱr\U u:<ܵ;( /97NҐㄴ}oiR)q5"G+8|OKbKd|5}ȑy HFN@B �L\� )fr\ww.\0G;~An8S%(*֐ NsB? .r6xBv" e,gp?F-T U<C;!IOk +L"&ߋy95UEk,ܚK6L4|,I΀PݜjT?1:*w=MOC]W.%Uթ%s]\03biL8wU#8P{i:\b?敼hu!�.&f"$䍰W:s-;M){;)9S$ĖTw<1*O:Z�zq56O_lJ<;o؟TяVXy;KʨK0qJ씒}h?ײH3 dleǺn$E<kJ{.|ˉiZ&7 <erw…}r-}> Mb� ;>�L]� y!RR{u\xC Ğ)rUYhG.lY:l)WG㬐L@�z} } Y*+ 1ccՒ,IT Ґ!F yh`cX% sRMTOJ zwV!z?SUwѰr݀=PDCRE[ {EH6lOU&0z+ xS"1ۡȹD#mS|AyC~R ͜Ͼ}7D4uH|33+(X +q<pM?_QfΙw�,+>zpM魬'ߵ>c\1]\1(pd�u 0`HpU4!ǡ]RVP$WF{V; U9#F#R8΁/VNJ34L4k̓Fa .f5l5nQ:$ Cg`}w,f1q�L]C� SGRLQ�,k W 7qMOm:hS/:Ot\2vAhl2*uد(lzʯ*AT9췃:L�<>DK2"fk<vxHy;779W$m֌]['-;OMOFi".Ps5BB_Ew <Ҏܡ6ƣXZikTG頴lb~(5sFOeX9Sk{%vMȧ $몣KAeVRo^ep?wiz㊈0pMD_敁HN=[1X<K$7MdH!iG8|5TYq n2"}vu~\xқR,52)@ɍS6Da;?-m<)I^YS `1pNUe} ¨^".6)8<W ;U`x!!C r ʲn/uXoF�L]Z� {TUm@�vuZؓQZ� ҫu9!4`gmF�L`� Nz) H�)U{/p/;Ts8b!�: VsFE3aĒۓ�Lb� g:ے2Yѳ>t9g*G5G0|cu^Үa-4x'ݡz^Q@0\t(щ ϭt!Y+il*ڀ}*v.J?,%ԀФ:'RrLUU omP.HA*N?dOWt588*%^wDK{aS+Hpϋ80%I'tx9$m6I mtF>N0;d7|d@3Nkdm| M-P-Ǭ|GҨlBtl, LxdBfPVHoDy3:f?_40ÿğ> g!Om:!kR<0#sѝ XfLT,Ԍ}NPGM=2H~C}5fJ1"Z<F5YU%DX*�œQ/+z9%�SɊle=S�La� w1cwإq*hOy p&;eUl/z$';0VL7$Yo�4S ȹA7l AS1vw4Zw?XGv090y l.Jx+]7>RtjaӦ:r_A9`x/vV,bqvH'aT3 B!ƽR8Z?ўJ LS̫-ʉf>QX;6OpuAST9ڞC^WZS"/M_+4|Zt_Q{8aыVQ= Dc= "rn9X_rΛJyڬڗ`ᵹx_X -(ڼSr#b/ײ;:k>r?"b,^$X q|jj!3H]*NhILRҁ.CE5Im|/$z4~{h*?.K ɀ=_ ;V;44S8�La� p7<) &KUVVg~b+a%2` UKvR;.}v3. \Ak b\ uɻm^I"?D:Z\oT){W{sYu) Ls#٣.(ԡJ�P2;D@}ď&5v=~!Z;<F}ڻ2Ȑ85Ar<HmKl_vm{TBc+}R玨C]]%ynUpOsT>Zd*vo'5a-)ldƃ!95�T#Gn;m0#_f|Q{uϱf#'>P2.gkRPm(ł1F�E/6$PT9BD'o搐ijG(/�ƻq|~-@ڙO\ċ1�Vƻ^4PӦ r$;Ƶt34UO.OId'ՖS(UkMHH CZT6n~g Zݜ<AJeYn>�Ld_� K%\ MfaTJ1 u3ΑEt;z@*ԃilM-P B+=ZN/Y3:mj+<g粰yc~T\6gדMDa7~U _PACHT xݬ:JDzJc�㤞MTY`X"?xy67U I7"PiHz)G,hgUb6-]4sQw:ȩc(@@+9(d0ڨ|N <{�JX̩qxjl Ji"Wgwutu FĮߕ˴:*`gN3tS7C4{jXMIzA6Cg3o3&)M'Vpg_€ EEN-fK 4_qko\T2,4){}΄MBw<L- qh3ҘY,/K&dY}2g�Lc� 2B/Rh�$MD8yP+:?tPthdi@Β+Ss[ӆ@Tigu946׬ ݎzk`%R+zs3\d+EG_l? .cZ<JkU�)y!<]oJ- (;#Ǭ0f,.ęݰ9U7@yA.3Q�"vB( rKuVՇTb1Rӗ^D&֗cykpWEQy*xmvP8DY<M9{"LW-܍- #kn^`_z!nG͡ŗ<4[݁mHxMBmB7)Ȝ;`I tO[xzm_~/׽�%Nc/I&P~¬jUI#WCt<zu{ I8#ca*�|W* Veؠ7UMHI@Uw�Lcr"� -��-gI'{e2@yfE :# <*k akq裭@˾"6$}1 O !ґkS FdZ,aZ:OM7QK~#mtf4 P MD54H8ѫ/1!TRdwS7E]MY2a\ M$g;{[и:SȠjDR w'S;*@h NΌQc0=Q fN0[c}Acfy>%x+&pPe:ՁSv@lQUʛQЬ4+p% V5wB=٪ 5Nۨ>Z� FdGs07h9s^{9 ~O=^UHʈ{n9X:\4 2H}_9 (?gp~i kKyʈ0|EJ3Ud ҭxoJtZ>l[�LeR� v#*\k# 6nEa 3jTlUjCym_$W8P"vA;,YхkX)#W!Mhg ;[|J.ɇ]4=s:fNŢdfuX"ܹ~K;1{vm]U/f]Iٗܯ ]>PDȼ>d!k3]WQm"X(:r҉I'ؖkV<^q1Qs. s~Mep rJd굈ӌ5I때j&DH<kqLR5Zƴ&ǃfRj-4F$saoł>v|w+"*FBi V v^z8ݡ|ac(|NjAy,U#Yf}-QiSa� ŞZVЪ겞9D٥h!ҝ;92j|dhf/1]΢f>=/gcwrg=P@`/m݉�Le� Ф/PݝnBxjL%Hu99x~9яk^ QncM gBP 浽L}r=v8-El ⡢KA Ge<PN8ZlrPaĒx g)ޅg/!<>{^3 )]fa@TKțgő <98V3[QDe`x%75#r K#{W(nw/!TWriM*1yr]&Эyjmʥt{`ُy,MѢ|H0]QQ6w f6JojF_,:bK9dy9Va^{7˗қzll^fr$q1nbh(;u$0 OܠžH|R;Y*a (ZGs@ԏOU+UQNO+Xgu-ٓɇ,Y1pl͉�L\� 4Tӧ]D%GG�Rl>ƈoIPIڌzh �jQ sʛ@R o&jUKR\.w,6@OTk@Y7G|#>Qk#Y]5�]MjYfs.9g~yH=e!&|.+퉝^ 0 BϮ}TE)$P'/(Zu;WsD٥Wӿm2_igV-a'Ugh=X[p-޻(easa=s9);rkC@ 8C`&7Ώ;B>;9;Q@?[_ V8XKNW ++V+ Rc2^%`@)KRrM (cōNPZKڪ8. /̼g) B@v T6}+A:7)Ԥ!4#:0hh3eb3oF�LgL� x<4a�oEh=ûÀq\m�\q,{�LgNJ� FOjYHܚnf.ѩwGErڄVTA[Bݎ" \Nu,cB5P4le4X/"eslJ  rX_6fp)&]` MߏdrŁF925s.ܖnf ȵSp|{ jKԧZWܤxA֘&bN]}QHAɎGL_~8d ҏGj}br`2ȀQW�(g 3 &)zR6u Ivؙ[wƥcb^#L_)*<6cKZ@co?sdKs>:B( sأ>e6(kcB lQ 8(0,+ƪ((s&֑Ӷ/:"9PZ�DR9?]P^CoO)ZQ6E?F줇gA֞!`P` W5툠cV_oy.g6ݨݞF �LhP� ;A07,�$k 47$K{aD�gۓ"u)U@ �LhP� taC G^c_ၝEnЂgf[lqF*A9EDʇ- ڒ8Νq>,sl.j5(߈ڄ>蜪(`P~U"U~jH^m{&['I5W� +&;z80YLog�)\]|]S.~*YT@}|5ۼzf+7WH1|nǧ q9#HNeJ}4?u!ʐ!? OU88&0R$*vE,OxPAӏ4A5&\y4N c]t7ANyLlېIGPv .z%*i$i!tD^7xeq(E ^GBe/x�瓷_0*Ϟfw$1 bC+ Tpzw\\FE L)`=^�Lj=� urjtUA4 9hRD`]7e0 \/BtMͰt;ӟ!:t ^S `e2E׆;IC fX&$@hU|bm8:;NV;Qpm:m o9okj%݉ʿp]_ME ??T%fkv xΧ�#֟!ml#2=ɤPAW#1 js#EŚOCr .q 3wӶ8?Ѫ$S3l9 >qT6v| Ÿ;t4,?2o|; G7Phv~%&pt+3Cz=:|Ox2pݞx&CfzDkǶn,HUVJpz)?[#ԟ`N Ƒ&l2 ^*( k 3&Iz5jy[%23YA3m8�Lj�  Eq>>p.$V':�{cBx߽ qZdjC8jtN޵3dqg7 50xVGIfhC9rŃ}]&R9ڸ˭ݘYjL`c.OV%T5/҈6aJ݄Ѯʗ1#T!L秒3|DIGTV 3A:E j tD!.hٺABrp6'߬_Na&wh\HG|9m zOP{dU(߶rXBaH':Ա]X Sw n =t^ ?h{795jP .5ai(L1o_7)L9' DK䍃%*4hw7NbiUIbuz�3-qN>?wx9W\ʖO%"R,B9&;mSᴯV�LlN2� ⿨B B|4q*;moL8֫G}m8UP CbyGg+C5tck*9֬F4|" XBuʞl�| .2k΢n$~p$k?}T6>4- !\zSHJ/e5I[fv?BgN| D,qS& R*mA / &Y) oQt N=\PtɣնWRFɤi^1M3ͭf\lcQa=gl.Z<)#;C-T_Y!<3{EUH[\JR{`BR%]LH_`,VYsMjS'ڭNWާ!8rz8ʌFz<l߉M 5Jy^hṨ)"Q #Mxv'<&?/g4񼖇.?$WDK͠3HOKZO@B3;GT``0ueh :d4>۬h/'θQ�L}�� 6t~sA06i[ Mqe]?5=o9?!%'ظes[2p�_rW];~Z.xt-{f <M-n!j it=Dif~Xq9nX$& œk&q@R?‘}" qx4+>N_o� *)4wc.@ lXH蠩BUAtewDjP'X)d֨ &J.VǓNDhiW0b\9Ps:R>[qH%& ۟;DԹ6IxzDE-Yul#56T%# P7'8Hc$_ghʈߏIΦf3x$V*B Ns-V-_hSA3=,·r"4Ъܔ"s -ZBv�X옫,>7B4Q2qL'WT}s2go}ė <~/׻光�LѺ� OmU*ZԚ . Dkϥ; X+tdͽJN%oDjàRR)a_ (~0lt,zu, 6 Cُ൳A<(~}?8ךuKw\Fƌ Z_ ؿ6QLnUH2*f'Pg_M<*t0Dp�i_`!@݄R0C302լS3F5 pݧ暙˛zRN; ,;]fq0ݿɭg/k?/ݹr4zcv8$?ʦCFJl=2nI` HW-2,8-~rNWQ*é;yLhmbr0 |q,R7 No#QR )nOXi#QeVML[Ŭ nZ0/hC=r֣_yV BR5:%ފ I2]Xɜ>0aÞzlT>ׅ7RF �Lt+� Z(w@M�dc ,@55(--�NzrQyGa2$j �Lt+� zw$iRlc*uBŚչȸ"z/Ve1;>;uDsi,`p$uYD[6Zc!lAmcbBQy,s)c�/a}ː1Ƥ)$nyp`�Fjd`'ut;+tXrKy$XϓNjGD ̾.L6BR1>ޞsۙ8!hUd卷:+lg@BLr$xcѳ%{[vݜ˹RS>t7d8dS[rNP큛"J\6c'Cy\j~ h MCl-zĬ)#4d99F<m&=ϡ-gztK|l"_Vpˢ^$AуK1GW=7 m|C;/yfF1^TxZĚf%_DWžbۉ�Lu� $SV$N<.ó=;оQgfěBz)3xxJX"1~z.h?,gf� %Zgnn\wmL>~V8}B3 B=( >z*@t8V!h]VyUKxz~$ >0+v<<_<}daFMq[J |~55v83V+J,|ShhbW:nok?!=N__YY d#~)x�)C,/۪hO+ oSSm  )n3'WyDS%2%�䮔q6Ah2~RۚIIe9es(Zh} YPYWQPH3J.XqY^1F]:W\$}K`!=DAF9UJt_e'G)Gap-2/A%CP"c{-cA�LvU� "v{3hP:?CY5$tbǣYLϋLE<۵G$Y5V'q:e]% A4'F:ЃfUi=;a֜O}EWF*@a{c-ci+0p\f@*/|j!H|iOA}~7#~ sB 拔xIٟB]4߉1mf#(OB۩̻-zO(k]E˔v?n֎N}ZU*Cx kO{>O?}YB邯][,`z4h1(9E !!f=?|"[u1бk"Uz72RuO"7*Zĥ&$ іj@lB4vy" [*yLp[s$44tzU2UIDA^6wcw[[O.ҵi �Lz}� 'F'pT'\+R;+iJhF=po-{^kޭERܬ9l*ì_.KHVa*yЀ/E1i>jlݚt6;r<~c)%d*j}wpjN7*7?`v?31n U\BφEb.WE<,~{7uqX"ˈ'i9V|盧1O}fܧ/6pN*]`,P]g)*C=r3}+ 5^ Uo57 aWRy&e)_LCIU`"ظ`3mUwt ;UpDešdi tecE|b KE!A)- )y<�*msvj_S)몜^ �L� @]cDR+<7JJepn; 6J+laD'P_ "1Y+#HS*jb"Eĵa\C@bZמ`эE4'l@"@IF;T@؉2/Jϒ; 8.c5$wLE*^Fڸ5,y JCQ)I\QP)zZ9XbRZ  {ClD-I1Q]wR-fų< [8vB &s9;N3*&mi{%&(^$yc Zsd(M)VFY"ZCIV5QQ{�]"_g\v2Ezlr ܱeŮ!o+ qj(/I$u~h}xFvB8%U]L3GBk"@giϨf̡c\IhS|qx bst�YgUJg�}j0Kwx3 �L� a{Xmga9'ӑ4YTIpޭY x~zВ V$ f,n(-%-QivG7\ڮ#P*}G(eDˈW +Eڰ{4>P+�[-Rv_vJnV !=tjOVU!~A^h^6֢G{ I!Ym`@6RjQ>ȯpv#WpWTQ.V  2  ڍ-_璧}Î[t ̣2A]ʮ*V-rؒzU葫O n}EKߜC%R2Ɩc՛˸sEƿd ok1sP*`QC<8"lm'm>^`XX*%<W-+U=)[9M?$K!k+!qhv=ﶻa-Zy.Ǥ y[,Le!H *c�L�  2q<ǀnTHG0O)?)֨r@V([^QBic/RRH8 H@9y<uwO_n.˺ 32[ GfڳO.qz{R}zYGΓo�|1(St3ܴůc2o {<2f\2#<7Kf3|oֽ\<9RG (MH"CwQ uV_L2�L � `7.|Jx�g  {S%Cf?ʯ Ow;6G[=t!8J1AdȆu0 )䌅g/U33UiY(Dcy\StuԞ*E 9N<cAJpeiGNr�lBmr]6v‰+Hoq&5}4XkI&IaJB?̿MF:OEZp$AN'/vZ1'4C +ݘgx u Dޝ-yGjW &#K |$# 'fЙQ2p296Ѣ<u 4>Ĥ]|^߷ WS�o3aol)@dPO'~Qp<c1HO8S'>E z27-O>-�/bv?:AMLZ QDtT,;I{Cω�L� ^�D"�i}.>= Ga4dT \ zXTX3(!iFaHtDmV+\//wL G@{Cnca+Fězʝ:rՂkSctЋ`==vx/O<Vc(_oఁPMzYpNT`<h+W[*;}ϩ8cX '<c cϤ*ND]:;49D8S�n7O��M1F� Bkm �޲dؚ �Nu1OϢg+A9ApgT=`~Sqvk̼?�!Kuuɔ_7m3Eh2c 0.T:׈i5Ϟ:Shr_JknۻnG<س(]|E3H{{h0e|y7hݑB~tDdR,73;ʮn:r1IZ as  6�N<� HF5{ �0)yINPgqzޡjĎp #Y!ט@S\76PENإI_ ~ s'G<Lᤩm5MPC/CvzY ׸<Y|ŘQB'hdC\e}o 0a<P~ !9؟;1'RQ2tAIKnxłD-Z6dmYurYVk7 p݉ �N0H �  bh(}%14VV)5+jRȣ1?gMg@=e?a}GFAI;iK!u!c56I{f^RSNj2rzTsuvJG):roW'9-v nыbƬ=dk,Vԙբ OHI2`&l;SfwE P(Fɡq(<Wh"e֢GЇMf lq8: C"P}9B@zS'FHUAAd5V7WF+[LNRwCM6?vfQ‘݋ @*ƐANtd iYN>̏#|Pڴiqo^Hpc%Z{H U~b15&xlcJBz/mk5FigmFgN kvQ$|U/:b]=eoMK$eDkjJ6i~,qyѧ=TUH7''qh$JտTk- �N/>�  ־1gDb ~ f1S0#Ka\WrD7&U (x 2|r?g]ęycN7h!}BgNV/ Tgɋ! se.) Ӌ!egiZVJ[:԰~LS<3S`!є鰒 vEȉb??k jZО~&SKgapno2 ]>aK1; eC;|'Oc@3gwB%RI6)ҩnߪ4iF4qbK" lTs6PWz޷ COET ?b="AeF6F_>l'"yg4w+%et?́ꚑX1ݲR㋎ԞQ3yܨ4L*\ ŀd>\R$Dll\9Sҵ g\7g)0B7#rzY}�N/;� xoc@7~S0MT3^$![UhJp%oA%r`Nn%Gn;84sP y@E䢻Hv{Ĭ> $^QsA~mnP.,f1ëMs8.fq;gL[b)gf$;Pճ6B}q"�n`IRRǧ/RZsQMѦLU=HƅewMLsckT}J ~:S`Cդ.],UW�+_]6"T2,B&fZ{UY:}0VQ% %|okKh(4qOuʭ:PAuϞHA<.=9\p "h=:ftlMMc�[\o\9!d\uPg\喲oZW {Ę.Vщ?k$օǯsH9WZ5XZ4/gp'42`x*ݱ\`bexV 7le뛹U�N/,2�  bh(}%1ԛ�P2L!Nl[jEe٬�>`fl҄UzR@wSs�-K`pl& S$2snlXy'Y� 9mܙo:d7)EU`"Fs=[sz})kJYZ(! Ȳ\,ZNF$f*1`GJr,:07DwZS^&dݑDi@!Oߋy=:&ERwq "ts;6Y|e hphxSI tH<1PAcsPG؄/| 0y;79#0%is)T'm9N䱇1 S:MWyi~̰ô^Xr,-\ǞJ6&,: 4ȇl'Db,ercCf M\VHb#_)X॰:bѓ~AqνǑZ2Z-nv%'qd=I)1{t 8. LãW/q �N/http://martin-krafft.net/gpg/cert-policy/55c9882d999bbcc4/200907121833?sha512sum=f33b17c9af515bd98b2927cb453a992d3d7500e9f671966616e90510b9940895108d241648d1a0eb46b32bcbf3251a136a6ee1e2275745e11bb328c14e7e7263� UɈ-Ė(?r5o'NI <z=+~Ӹ]i;GeyKt@Fo$MPQ LXj?:5^76eб0أ2 Fl�4kTX! Z(C|)LTiO P,'z"_RMHDI+S&JaZ\ax x{zn6 Rƴ|rI+z<u@HphӠj޺0%DƘUcK wgZlG+U'n!F)昪V8=&cYsuNg+V̼Ih5iJ`N֤fmVB:~rQySEe~ܥ5i*OE\"Y5+F9ltZ}iMZbe@'nU`'S 㩱̽B<ɕ=[¤eYU6t2s~x,I=QLxE#[p `wRn()Jq \G>W!ʵyʴ/"J)-^ئĥP|" �N/� n`c}g(!s! ?;ų"vi,9scl-Wugs]~�^6G->9R$ )@l:p� N i &ޭ4�?Bt[BG- f]<PƳ{s,VS$ 'Wb8Z+nxz܀]q_:3tj(MYB#Bx?nwQhu Qη@y& Z뉺Z[?M`5/XH7EP7Sϼ,A!jnW_F}eH%GN/P+%;�HHP|$4-",&/񊳞Wc<^Ӿx}wҒTF4`f @bX88 #]Vk8 _>eŃۓ*$V7z&P7@oU@.ϣ �N/� =%;j5gI* 0m0om]ؕ},JpĿQs+=5vrx۔XdV cAH;qZi!m_>IuxX+*h6=)xIӶSO2{{foC Q!A 9}x; 8 S3ѷ5i}[E08SJzj8dD! ksi2IAUFw~iT~BňA (RU,b-LbpU!'ϝ Ԣ;w/?ݧ:wX9ߙ0KR=pՕ*-�p%xNqVv&xWo*Bl KBF4'YmԼX 8zgR#aK[Tql`؜.7E~9Kz/x&6 (੊'MO(Ŕ:<@?3}ΞN^<×':(T5I^*Y]s;lֈF�N/�� 0}V.�57JhK(Y3^F�-\2;:tXM�N/F�  �l.lG6DIGX& Oʜcn߁TbDGzWnX\$/"#)AU_z A >q'AǧN4ʌfoTو)s2:V2Y+x@԰6n#g3ZRlad? a^5=yxr:?V[,K#%g LNܘ[Ea  KF'U MTPT; M Xax$YBY4h:GC)y KlG); etzy,<'S,;/o(</5Tlh rD dQB!ӭrNvS�}/VG>,(ΡHaVem1N�>PVȪBZ$F($"(W 2o�Wkє9jR;O$h',7(TV�N1 � hx ڒ]+N,>xNa;!ʁRe\-YthoZ!'*$$ePTY2GO['bG8*KKrY~]x h_kut򾢺XUMreN#~);b$0ΔCdqx%-|gd 2BiVB^Yx̹(M+VffQs &rD&gHl9?K93%c)Rv֠i^Dh^ & Cc!TL$+%' @g=G.vW'Z67;iL2tqkfcFի%{F0>w{S>8&sx~TO>w; xZ*<).i+ B3G}e_-~@j=oX{aX.>a.S yl �N0p� :aAHZCi;Q1ʞ5#Ƀ\�LpJ Z],DCn}0zYU+IS)oȵh~0ߥ$UiZ','h<!mXhWs}3--|uP:o8D=C57BsM,V0%*!vjQyxRefV(_,K zNw8 Uh[Mb&rThO, t42v§$f6q%`m#]1yr J^g+P_r;g+bH5D!N o]yL-~[]"Ik-aY]#uz͐b5?= ^0XN/NV}lN܅^S (WЃ x&H_Vjsdv <_~H!PQzV&~.[ a.R? kkC*J"#^HaF�N0h� ʛ#VP� h'?%SZ\�_PXPzL I�N0h� 1P<mc�_!Pֶ?j& FOy}ZgM(?͟Cj} r-~~q]^^.[8`$]g9˱b V4Ii*h|d'\4Ú63f= DPE座[)Id<rc+ ׁ/v*Ы0Mt߾;|drFu׊|ecBIoapםƲy1;xP,%"~."3S`e_i|Ow1.tWm]Z%<$6ɶ3W1?t1 5>+Ö/Ow@Zd{\:yx־zhU#+dp ދ*MςOϑ ~[e=?+eo W )wQ 3 Т+Dٚ[drߥŏ]rN[L%5m54EdAhZv+ :ŢAҵq;щ �N6S� 92߾{%&S@7<i^Бb>5${B" %ll^F`;Qr>n[5!&[�QªO\0>1?)Ъ?+#/,N.͖zI½jT�q9% '`% 4~klYCb JlS%I۲t[N&H,gbT^jX_$fz[HI԰}%r"7ϯU  [0]Vug"},WMĝIn`c(XWɜY8Ij(/ZV$5r�n'x3~xv^>#\Qăw ]rhوGͻ4++q RS7 FpͮaD,"rg˺(` ݝsj bqGhPd+(7MOz4t"0cx8HP~-U{d`/3aѡldj0bGc-�N8� 0{zcTvS6n2( dMێ]<3u7 i܋p0qWfqC'Z~DZ I2J%HОl.yJT7#rN1iymo �N/� CWy2Wj]NrWސ֘.]tpMH&SbНpf#V煚-¤% \Iv$GZimTcc%D�K_ ћ&julYh+y>̂bs5S@q 78M}o:SW@=L8> ۇFUt\˨;ڢl8?k>âUm|/F+f0FC40o~|ZIE8s܇9{9T6Mc)b!΍9^Фtf&<,tߌL1xLl#z=M\"y o9ap&oqQuƟ ؉wiD.cyI(%oPFJ9@2B僧KU8YYF]? Lgyso7:$ 5@YE*?LxwZ8~ \3^q ?ӷJtFbxxET_xx{t/n p�N:.� 6oC_�fO7d&%˔h~O qTT`x; w0=6?k8^b/g4.(=B{uWD}H1r%s9݌U{\hh9K]͍7_ȿ2qngVH^uH%atWCsLP|t}Z W-"ұ!֚_!9Ao>xp(n ʵL+2t9^@O`\`C6*_>xqr}$5#b^6�'9Ӏ$:/@LsZ:�wd~A)/1 V` 'z졧0ȾCdd^_3 J&nY.[з?!XrHضe .>чcBVeW2#ELm^Ѐ*8TU^_ف_5<s,dO>2kCa}q$9oM@N6ރvMw(=S(�N=n� hl՟ac�F<5};XDUÍ/x9z3!ap=Kݺ8A&TFԍ,xR,`+y:v:FTp݂vrݯf{1 YwM YIqgXѹ֝4:M4V} EyDpc].]P6%2l\)V .U"V^T=؛>A Jx}TnD菼50:lzѮ֥L8+݀vY%te6>Vkq<*bYqT0^ۃ Tx{bѬ�7=IANN0X`m�-嚝)9C\qσ9SUJY/k ^-7:Nה9ru:L߬/.o?>@NVd,B\4\m6$~b!APEGSg }~ Pρ'اјLZ2h�N>'� 1S-3]@S:u*%gѠwGVtc Бo_U۩V,E%0zY?~! !dm'Mvk_o74tBJLX˔#"gklq:BI|n�k40h֙6cs<5JcRܘ3%Mu�}~TgxQ/z{XTA댵1K-g<pB%JCܙ.4c,gt@ =VW�#x�o[{G$=Eth.1lb%v1hvҥaԷmm"^U$t dlԯ}rߡi~<+D+a?a= 50բst;\@R0O[&<&.+Sv-,+zq@T6|Ӆ.3ryCǍL::/tH0{n׋/i3h7d |!VY yȸ1>zQF �NC� 7"Rd2|/[(|kysjL9w &$gV�EY7!-`!z>Z~Jrjmx)$h'| !}dDpյLҚ7C! 5\0khtQ]pɷ[=XEY8cڹzGDUez"tDG@fѫ l WA T:א5s8!bZ3o!6MK}+k] v~=Ŗ_AO-m>2J~f@j_2eVqۜEP2s 4Œ=Hj .)__F@{Qq; ^ş�^|l)y" )*tAYI}I ~s<hR01= 9uS@|Ww)2�wR1m!#^zlcNj"xnwQևEzJ - :$ؑbk `q3�NFȩ� WFz}�4f߄%`i! |[qiUs[[y S2SNG`%3'rky3h U*4UNƧR_.<cNxڏmg"$̈́&G`)C_AMJ4&Jʰ.$\ < n8zӜAe|.l02*o t_".gOĘo^Sn90WP$ʣ{SHZ,Kx|vc&Fd ,X "TV6Fo=BAS|(1ٳIS4|‚䝼&*bE?K�1rIүVWTPXiPv(O*tlZ*Vz|;%+({~S<}YIg,_G#03 JӾNtJi P# !qQ84q�VFVSt Za.؏1A0Ԣs6G :B\71F �NG$� ~BU̹Ѡ�{œ+G8%:t4p�gZD>;g �NG$� гKƒ �n4]{7uFF7Ab2Hx֊۰]g4k{M6?01-r=m!vS Ib.'!65ɃR'5Y_Kv ?I0v=Ks@8c]2x{�1T6O.\|_DAنS\O) nw[!H[ 2P-Z9ӗ:^ΖN;ˤA^VwpoqY|$jH<}iZ&FFa=:ooܑ1P8C éRnz2km( ({uF \M{4%>.| + {"SZ(:=8ds2S^hJh5W@ "ؚ7;@E6r%v^V 6 !BiHZٱw�7: ;ݒe-@tb_p{v%q˵rѱWu=-$x蚩eCyļy#yqd,Љ�NX� Kam<H2 xXw�381ߑ~ Up- UwϞTQ %gA79LUFARGH pՀ%b'JG=hc&>$O16-.,|?N W'`%š{nn+H|En^匔MBG"ϕ8ܜhcCj hکR¤6r)pW5TZy'^holl0Q&U\谹Ql/ش 1w(In$\6G6 Ūd>:R@Yq驚zun䚹ACAaݠ^W0S+&!A1|*jw\} "}CE4gyxSǜ1 !"L H\^c">C}l;ٓ&ן/?K-JowZA�K7po9ti{7r9Z|=Y;-E�NoF� *!z WiI3p2%/iCĽc!Ұz$fx%Łϋ6b6OBe1b_c"4FCNZfx@ Oct>bFpo2ޞ :PN`eqRq?<.E 'ǀ98w0Y6^l,SSܞM,:.4Y\gH0-`MV$O]r=WhڴW^3٣7?5E9U|Fa~Td^+x hp-dތ}5cl5&EWvX4`(aGv7  /\S>dQi\v >r'[M7Ai\{u#$,3<w^@'�J5ۉk8%#oj" K;e /@ru(ǧF5`U3ob�F@|qqFDe]kw{$aAև^ɳ#uԤZ brL �Nv~=� f㗃/X8ŷa݀[kXrn~ӞϙeGxlWku RtT}gu�.ý˂{wFst;XF_J%zɰX1bxsc=3XRW5AEdH{#c7B'<3\p)"/7e>]u3i ֟[>SJ\(k߫oT�yy}{gvY#O.I MvjhžnG<xOT۴JR}kY ֚I Й&:7&h-9*3:g١MKENC(Eb̥SӭDqrOݲ8PM*<M$+c@N {ẽq7Iĉ&-=wW7 -L(;[cB{o ( ܐ(6"bUgYO^c+aH.Xl0L[c£?p|�Nl� v+WxBfpxK՝c_'Ij)nj,C#CF&4gOvD?Ŧ} bã L.[ѩ9n&0,a[DD2FMKð2AoZن`w ;x{䵥߅z3vكfi YW;.K�MljV^ۊ :E2iefHM\h~xWqhF+*(i5 kp7 ՃX6x[xOqcA|qsyTwz6Jғ: w 2<c6ǤOS6?ޅ!*=ZT :ip"96>oB'Z5"C 5�vIȌA_4!_Գm_uQCw!(baߠzf8( om�J,5oQK|ߤ%It&WƷ @,3]Bm3n:(@Gl,eczjy^Q>Zٷw/9 z3e?\35hF�N� kbb8G�[NŜb)}e-`�Ѵ1h{׾q;T&y�N\� J1 ;I2)?L:cBE6qSNn U1|sI%kls!NVP*Ob>߄}qKlaUx+ 7 )˄.mǥl|B8rh �|B{[t1h=F<: f_eu&τ8;=4ۆպb IR ;:ҦXS^$mF ݗbl<~櫋�N?� ky$f>qZb0S uH|1ޝՉygRP&C11zSe~w �" 9#1 Y 9;KB+]Ud/\DcRO֮B"<ly D>Sbe KHK¥9GClRTX2]2'77\ ~NOyzIզR0T?~tF0[Cjp=m}rK"bi餅nllw0=sI4V^m5\('ޤjfo>.E6Lzhc%H@;JE34էX75$tV_BT͐tܖr"mA(Ż$ vDpy"ۇYΫqB#@i\ 5F䚑_zpR:.vkasf9\؇*'#(GP/bsȤ]U!-g6s˖I싟;'0A.]?su;6%�Ne� ȸx*sNZ.z ~&VG[a$R(Iz|Q17o 3rX]6ҜBA=|L͌tϒQ _3A4gT<SG 𧏿D1|.}Y([k'9LT!a`r$DqV޷|x2fCl"AeӮ[K=-'x?]@g %4>IzT} 1BQzr󽐯I4FdۿMa2fLmWn*!-Q oK/fM598T؇$ؑ}77'ApB8}!c4f|"Kߢv,EkTB4 �E=.XNjFQ /'0<hJxiOAbj'+Dۉ&b#WdЈ*om B/솭tPraF&PqN]Į:v;O{E" <(r32t6+&m&R�O-� _%|0]Sa|k?q!pbHY$n/�I0eQ C%pP7dnA)7fYiJZRp=x҆Nwd-ޤs\(oصm<'w}!u[V;=<6u5{qN3%λ`q9B.Q_}lv=pCTWWQgXS&)wEq\,&"oӿCgʘҴ . `"UrfR]"QXH6; 4o`vC䭼P'YSʈ4 #^@y^究o\LB!Z#iTăT싹~"w ڍ<nn'\بjB:ųL\?:<˅>Sݡ#Q<9>B#-HRaߘP[ *8u7/%.&T$WAsn Gy4vUe`4,Y~NVE؄t�J$� w/<X2aAu5X:Ku[25BU_ \頮Gr'8hZ0)x�p|GQ'sK)+iOKk*ׯ9A[˿صzq%7Y>ʫ~psWN1v;#^@:}WY?2Jmʬ>bP::MuvXtY b뙘Џƿ0-sS~IȘ,yx_) ')f2[A'<dl'th?HHI,de ]n(~ҞK+?}DHEh+f}C^a)DH>QyEh ÀQq2;WwG-曓t�g&xSՒCoUf ooWaBM <`�D^0T,f2m1 <׶[ 9Ʉ4&]sP"TS8ZEɑocL xXFɇ; �%I> f   �� 9<�T3/W̉b}aj06o˾`Zʡʥ~MY~:x:tx kF<rgOR6!<R]5B߃c9fr]b:0FRd29ŖlA#ms Y~ x)0_&ș7sg`C [)?i%4%" ƇHRy~u#) 2F vu3jvâ֥NF?s/GomSYqįF{:Jŝ7QWY7u/ys2+]J%fWS'+сM.v@Sh[w1!By?Slc?Â`}":h8X6q!w* ֟մՎ'T %djU.C;Ak5?Gt>ַPP†{Ǥ/g'@P؀-?7qKS"KsZA �Of� #>,$iNIt6_\Q*iƽM;2ܲA' H1ZxI#zlׯJԳh"Kl#64S rke`T.`>Ѻ*.iK3Rӈe#7`~,%;5L.VZf7wpJ4plT`7#Оᄠ~P_)wA{<q_G֣Um߀n9+ø%]C'J0Uw5zcLAiPX$*CCn z4j){_iYV�CQ<,U(z6 U@DqGB=:NM"tPb5I Fdಧy`͒ U<}鿂zAG@? i.ƁuU~G#ks6/58zzfK :+�XѸj}B[j?S:#%_R6`_Mw:@=NBv5_3Amێ) t$hUff�O� F9dWvEz>K#|X8ȴ[wYnm;#ʾWa6,pאU.ۮUb{ 8f۟˳#Rg/9EX~_y|,|4\RPP)M bN.Esa-So!ǚ–|ܴ!%3АM#`Oa9)86"RGgЈV!p+I)lG4H8K#J;U' ]y`MS~#uY:�;螴W٭C_j}x-ULa }6Ϸ@~A`JFܽhJrY:њlR!mU&AP"�nФ^ ,+�^wDoayiO68Zf"Ņn4c1HT5Kp8�[kZ=[yZn.r6 r3媅J4fyկ DhpS*&2^n^�O� FF?T�l<M!q?f^$MCzG@^�y6UXFcΰ6bO"\+NOukl �Oˣ� hNTo1�~.{~oB -ϕlX?}rj  !ח4ݝiiƈ4՝a;)n$S\wysbzvI4$YI7x[h+8 @qӼTǰuJў㹭f,8X :4KL>zdyR+GwyUC` E*.$�ڄm}^^ʵ0FI?>@qȎwE0 ,2*<ҋ)WMa .V.28yLLO+5E֊$͒?YOSU7=1 a ]=#װ @e>Pqx(g蜥G&“|_>m?٥=M|*F=֮܉f`<ij .gR_00Aڜޭst7M-#IJ#@]-Yu�Oim� z,ۋ5�¼zZ̗/75w3')9dD?-9֣tND1}Xt/@@МSpoʠg{SϮ=;k[e!\rA÷9B!tYxN=,hmb�XuZ_> 'Ͻ(QHgi S'Wd`Klyla&1Īi~:C~a&} dC]5LXg0Q ؎U�jD|[?;t%k Ġ8\LPT qQJ{WyS a s@4=2YV.zyBt4OQ ]q%Z㧁I})|tTiA~CS�'ktny^4FӀ -&qֿW/I_kvI&$<ԐعS_=,m˪ x?(w`#40XtPzpm㴰ˉ�O1� up<.rS� Aho9oln-Tl:u"'%ow+ï[y2PSVᐩo;. ) _|IZ]Ž2y!aᕲb\=WUsv QJ}BEK}B6hOcCAhT)kwz3}(8@4<=0dH_JuOJBFºb+)V/RǷH 8d5y4=Lײ'wX l+ 12?n8^Uwh 0M5=&#sʽ8MSulќ;e KF'百y�l  \$G.`#hi hQxU.tf2lbU;P\@iG$ѻۮ``4ҡ>\OpbQ+|=bڃ6_VǏv'Ǡ.۴-tTzs7]^׬vEd�P�Hl� U/+hbE.JM0l"d.M,gM焽ۺP.l3B~+Rz#!i3m51]nimGݥfrZnV.p{0ǰg`&��.ItEZБx9>2D<_ 4Ú #y97Ԇ;!N>8 KgGWhLʀ6-8I}L(F"ͶKgH"i)LaWɔ]Z; !6GgBišq s]2v[0 33vB,q!,m@2*x\t׽0 `\RũeȆMl'8XmѸA{a>A#Ru4y*?1,7ӂG9%BmHEyh6KGJ;7@NP�0]jgrDFYB=>}(nic *hٰ *1Q6ab짣Pn/P+�P�n� u|8G'ej-~]c⟫X-Q+DδjWԂA56)j@ѳ}?<,UW(_Y :p[6MEBU I{i'wnf~?ïҥ� _-`tW[bʊ8`&B oԑK=_޳p(7<lFyE=OW=m}dU8�һOɞi:(8GNݟ_KY4<�P�l� {5g?i"dwM*1N2 fݚMDn*BO70ܲ;\ᥬdDLJ)쮍ڢ/tiH=03!?ȴy\.%$:1+/q(0`8wWkI`ak$|>L9'}$Z'`qT v 5(bF <�tHx(l_u_�_J `PCߜk<J∖yjx Ө 1~% PHc �P‚� ̝ZӠ洡y D"eT\2R&'Ƈ97l(~ѡ̱Fp+/:<)6tVTP2q>LR꿑)օE[ygQ4[` >l,^Ϻ2koz/ud R'A[KQ*;8\̊ w# NCTp 9&�mxM 5xuhp7 CAAΠ4$@*o1b~C=If)@G%Xrv]8L,fqn<𧜡ij`neqJ"7x} 8k -N3:(`X)iH>dv%Έ>'WQ%UTWv,+`[@T$2D^D΄ӧ@Ocv}I�q Pt&.j '( !ׄd$>d䋚Y ŞRd?5c/+74M A|M V^&]cwlz�P?�   *eN}zMŭ<?,IIs�g=uFWDw'MZ 3SG&"0 o2.Vx"- Ɗ޳тmCF7af�~q;(@[s 2)Yﲕ[a Saa&:8\.SĚ.B:zm YĽyMQHW -C޸'RmODਮs'W0ʷK g?[+427HeY|(e`|m_NJH愛-M{.=7T,G2SĚ ?Һ?^Jcq+;&эHGTLkx^ '?(xTo"j:5&DR=X ]AHCAuOi}<%hKEoĢ~:i˃v6{wA/)6f~P9FEi\9iӳ.`q rNM"$H�P [� 5\ OǠR+:sXRX޼}h]~e�Cq^YELy}l칋 |-D0B|%^+kIqP=e R$1qI Skktf-8gJ9 \" @ə5p!Z6]t<w|xNhk�?1go�Kh+c;9 B˛䅯<8k5CsO-jy*>ЈP~ҢQ畄ӂT}`4%pMq U:':AB1yͺ`SK2WzfяjҖI"�N\WO:0 !\n 4O.\\4A\hO&�P`� Z獪. J�TJΐ, oGO&_Aiڱ@!XͰQtQ툨yYʧBx ;/@JP A/&A:NHO}JĂH*™߅Zᨧ>[]x LZ|4Nk{ʟQO5+* D7%zI٭ B%jq7 >PM[ZO3`�CfU#!7lvV/p^Q&0)e^m,`”QS*Ǐ[DA>ེH w;cn!n ՋUny"pADSڀN1$F VxĉLJ#L;]R8O"k`>C,CP%lW/O~fFk!Mmqx6ȡMR3l3*;<Zv8Rno$%0DkK_+%_ygǮ׬\b'jt CM(d&IW A{9)F�P� yv;т�jrtK;$v_�#2)y+\#9B�P� ؠ뺪\!֞< K9y9Xk=30l|㣥wؑ$24�L?V_хݼ7ׅ"cw "vbWie$]zXj4d@e?C4uw05=iUAhE:i B ^dB5R֥2 %GXy+ju9{ȣa⠀G' pG7eY< !o9C#Vls7{>ތY?>FnxZ+nissL$zԇ9u<??3" yC팽/OA#]j1%ocP,eO3 -;}\'Gv Q,4ٕB �EtPa0JD$$/i:\ czX4֮reAEL\J̉�P.� 'ؙ�=F:G|-G=쥪] ,2:B\4$\a>QE8 -{;n(kzcFGdzHWgyuTVS9UD訖 >O qjU$]-+2vXDʮ􍗮(Er9KO ]�r-F}9|i 㽠5T\~5�cg6{Q*5V`a{1:%�&w i$qJATV٦g<ik_[Fn�@ySe xN~sˡ-ԶIB-=&6<_K/83ҝ?aRV\V�)^_Fæo oV.'ox7c "3kǞ>hyd%ɪrH]|3}$);?!m48FXrQa[, fŒ$Q,$t|7>%0ooÔ/co �Pɇ� '؝ i1ZlQd]U>ٙC(tK' !5>u> PVk\9W!B)Zx!'s*ɜ7Po[+wiz�4.4R2%!;GM7km$Ѡ?5|�G 4*3!܃BGV"eF0 "n0#!_<17*:-AG̖lc: QKK<7<gj+,/sXOж kMu&Ux$jDB0޿$r$ ]W1]RE~D VH2ŏ%*JO[)pj.kMRR:'7ES}BM_g/~İ%#X)"9>z?z29E]}:҅Fbxx^MPm;6? 'TGxH-؅E{d+J6ʧ7z;9oN[W&`Չ�P� 7+-h�ca?iЅ@Q"tiM$[O0rad졮[IӿSUBdA'3(7 Yc.82Y 1^䵔nEia4M.Qix�sE]Ӡ]__WGj3z+QMQβ~zFi]ρFF8 Z -ܞ]{>BjɎ气/ņfkRpP'0ЍIcc#tb/7|̣Z4H%0єؠSki£X'@̡-Px̾|l/U-, U~i]ܥ)"t%7Є@N>JO8;v.Un/Lp'H2EpSTCDCx�r_E4q 0+=S8TS�>E #l+>7�%obe?4b~g�M,� !RW�u9)_/'>´Z9X;a itlm? EuoZukqݲ\߱[Y� BNl"Ov@nɚU0nBq|GcU,lMȸ_1 cs.2s*vd@J#s$OU7MPX"~Z& \$`?wKS["e[À( Qx2ʵJji5Bt}lsNj _VTѲxmKʻ) �Wk*F7LHSnCVu1Ec}`S#Eޡm7�Wi Rsܑ,fC󄒇jlj!.>nR8$F0PV ;DQwm+m S-K _e]?(8vGO" 1N!p-STzw4ԩ~:cPUA�EAg*�O]� xyc(~fv[ 3D9yy*হ[qdt#c7 `&Y'ݑOQأ6.ՈYrYȆ }iLNcBd:YWĴ[.2@ͻw~B ]lX(v@h9̂9^K:jTɂİ^ZN 8fw̥ G r# b<@K$s9%5f-/93B cΜݱttDC'?|%1mloKygr%NC( Zί`G~Run;+ ʀ`Tf!Í=B69?'{]OXGӇB7_=<ބk0M q6Hˣw`X}QzH*ُû!ЌHCI[5cޱ)y 8NJ�PG� u-ޥ&�y9|#7?)]SO[9YXSlXLqsk\觓[S|ri\cP@&玨0VW85FL S?3|lr#'IDʪ[{+ ő.gܷ#BÙC<C[X%&!w�](Zwql&lbp[ rl�"o}Y:E6"˙ fQVUpj(9 d?~B<~{H"2˙BuQ*͟y9qg^ZnLwn9WNZDٮ598H +Z =^CQIweNEq2Vd m2qj&جͦ#S* nI-v /Vm\㙣SE58}tȏ4`S][23.rzRP*;p :X.X$0�Rgx�P� xAӬl9J` ΰÖ?C/ 4o5kL2-C#܃M͔'A(�_m.3\T!BX5C0ےt\pzz| U$2+AiH+Gb&i_ъ4W!�]%l׷IdosP%{*t`r.' qBc҆&;Bؕp2! @DȪ`~,?#QHHR.|O{ R Vy G}GUr1OP@`ɿ?~,Þ]|%`$lXvǣrK%0:.SI)'d - 2JA(S:u. ˻C�w yp.oP�Vi>=Ku9o\30U%aZ4GMVr7q{1�Pz2� ƐI<E�Aͦ9E�x4,.r M߉AΚ Jm «8ڤ`K73!iPC?ZzMF^xg[f yynӈU:wT mM̓f[H\儵/>Sb!Z nEx-:Wp(]^~ĉ_U�mPJ++H 9m°Ap);$`9(֧*r.eVpb.Jbڗ:&X}�Uʯ�4p*nQZG@5#R|a L<&3N= q@x1,ߍj{E>kfXYV  woW797+ps"(k[GyjIe:udN=~bÏϮL#yq I"I cԯYZ69,]ƚ'{eѸIEurܟm2�P5u� rɡ] b<KG82Gϳ@ᢒŻΙ,V nAx$+?D\p�nOIR ŘZ7+~GnY q1Wte(+0�CӸ5iU-~)`wBP`=0Z2<Y4x7i."*;wuǝou.c ^e;2%+@钐e9AJ%N+ޟ7^]܏[&K Ū5q,ڂk"p@H#P[.q)PkprȡāfK 22<6|8$Cێ}Eq'wfW'BUSy.ðlm}lU4.jU#'$釹:-D PؘhJ%I SSWYoMQ~xkGT.B&dcs �*6XK׾*PGI1Z1L${gøD`ڜF�P 7� _ #n4�n1r6n<Tqm[�ړq~^ȉ�P� O<M8r�R܌:!gfGܪJlM Fz2Z�mo}.|どad Hıa  L5R?00,iks5zPW. ,Z kkH+4Mŕ_{�kxgUB7=Ra A@^Q"wg)(SlS",0*3^lYF~1OŻNŸ!+#md{e][ϽkdKl$T΢usu )vDk@s-B2m$a(N�e{@C ٵ~« u&9fXFz,l=^Zl=1k-*Jj3-̂. 1cO|$PpVR{<ufhrdbK^ZBWD|ig|ЋkoC+͢ UT"H-@>Ӭu(/tMh3z{G~Y\Uį�K~� Bkm C{ ODzb;K"VEx3^t<A2s6EO|Phቸd܄z }++FQX$J=t$ܩ;DR (>`Uٻ'prLiqBO=vD7oCdC,!L(Sz?Fo?7֚ PݫxJDJ 6_Aq+�Q9<� )ȴıE3ߟ�'8Iu֧[iI{-o =*j! _n,̀^k8lCry=Y9+Yi{vʁ&о0N9*w7N|vԧ>LF7C7 YN98W&ggPyu*Vp } -!-$JӚbEN:GqDk17H~1Fq%ءD 0dpPVzu?ps Ǽ#eJi +lUg-^ 3U׉Am;kOdh%jcO-R &t]$cc$ <NC@4%4UcGj=<WUxy3Lu@z &Y3JC12uܤDGC!\c5ȯ- l?3Te^—g'2Q6uPETe0Ru �Q;� �3ukU$aek;3 59i$0 udSJY@( ',@>N~׹Qu!q'44Gf><Bj_>1J{OS Dƻ_J�lKNmh evDV(`br'&ۗ5:<fBd[҆HS aGzd b<%vF#GJ[үte-O1 T;us=%*]:r:GWfzޟȑ<ĭ %U4]1vѹ?2wDr}mßB  7$Hⲥf9 a-8IԔU M8sn<璳d0|k4eJFH\yY P6QɇPE5K6_LJpq>4l|[-\eȘsD�XB:s䊖td@5~_i:%{/tYhA;^UΗ\YF �Q;� K_�{LN-eBdYõ�7Sc_N�I�Qi� wf�WITKlQ-ZCo0-ez$RL\\aׁkGա!k=$I2(he=-$q1d�w-6qSF QJhc^ ""cHTsgsr5hcb9a\YXgvivfk_o6v dˀ riR1>a&kaH2i,7Aёr bxL�Q|� <@�^κniȾ%) 4Dy<֖bp j`ݫ}c]7XqڹLtez=] 4Qg1YX^쥙u N8hf6x{Eўpb �T&\.cyQ_!>}|Qn\1X ؁ڒ.^j8)' ]@(LjbsTAlߪ]:W%(%@o{B(pl;ЃgvZWl .l ~1{'h*]J.~6tz09-wf ĶC <<9݌\5ԭDZzbcY>ķ᭮9E v7u(LyZ:OZqVX=gJd-{c16|uϱOPGHqtz[?[f:xfuKu4˽Q�R5� t&;72n#]Az!dNJ qD6)l%|ld e!%E!{ =ʼnP8ﲃK}Ü#4vV4o%hxP-z1GVjkNg6 (:Wwk4Cp]^+-<!Hz+aP=q=E^Zm\Jx,65ё2A nwt ,nMB͇xH+ӹO`r2Y+M*lzO}U )So$4[^tfS�8@0begڶC޽d~;pkx4NGwU9^z8P^ 0GTȾp#r.!>ڑop`$Q ǢBeAf9r+5տqόK=}#% A*Ϲkzy4"ۨWB"|i%7}%L-" � R=� D=[,Hf =}/}BVդjG=(}�Ks HTEڸ[b7VQq;&롏9rx*#TV%$v}#i-G8.o%Zp+>$xI!|)|}6,pΛf_jyq8ḺRBC<Zm;TN ¦ B zx th�J@^Fے^ C{4b MLZ *ۭ�RA� Z$5/G*jK2+9 IlI>FRBAJ}z\ j.OE[U*BOa=gXOkh2?aբ^ǩ[S=܇kp֥W=\mT-MK[3<Nl9@=E; ?xC~IӟAf%lhgJJ.ST-r( ;@rmJtl�c:WуꍟcgpTKClCHֺb1//co;^?$�O=EDcww;Ob8m<zgKhjWγ]H8?bO ;0%RMB_UYc$^Dqi !Oa J9f~GkШѫn/Єbc{3,!mY+4߹usbd{M*.=fDZ"ADD|y3AKc H0YqPR)Y (k=ˆUozfo.jhPw;i:�R� ND5k͋mo~9_^ vv9PSngBd#%ņEJd-fpΝQ3�?Ŝ<v;֚z!mkὈ? (]Y Oj5WW ֤/M9|i%F?ٽs n%P]"&ȒȗF$5Bn7wJ#%m~xlapV.+&Jtc[Mj\ & )a43Ύ/HҞY?A(<hzڵ*,0frג떄omLr1!AgH3CڢZYMrGRS鈧�8vhYcDfj[*OxYN3nx#x,}J7فbqg;#闝Z8 �fmFѦv YjE*_2|eB_7[ƱR$F q#PNBq 3ga/ZE։�R� d˘护0U,Igw>߆+~^dlc.Ó4t NkeRפgybqPWa51۷wٯR.eٯ/zk\ͫ*Aw@_<6S8\ڛX^62՞|rWB,3ⴼ0RNuԪ٪ @U! u0amK-Gy:QGI<sat>}&񀛈F�S/� Z 'țW�Ũ1k'@̂ @_%�n:Ew6[hGkF�Sؾ� -zzͮMm�[>lx2482]LQܹP�'\SX{Ddǥ€*`F�Sy� ^>�,HF=09#^T�ZF2(`KFcN<�S� 8y{ړ�zs*�;=LfD[p28QZ.ļc_c8ŦcdpbOf {ԇ(sG7CJ4r;bdEJ(k(-UaSfgN0qg �OȢAqPmE.w &�gfpۓ,1It‘+Q9pL+3\ 8CՇAʙ\g?*3NJȧ �UC� PKP+C�?^hx~dWˤC`̻'wѥ(R;C *pQRH(`~܉ Xa…`VKwg&FzCͫԤt FGX:+9$3FٵaJ]ioܞ[PWPkY[@ į{1HDDwˤ=SIH!|N#a+&[PeaCZjQHjr{QKXEx R3帼@{|%�U 8� 䩑�Qi"or$oJa5]qof*t3>K͋w2v=5KƦC}Kf`}qܮWcBӶnu⊷ 0A:K&p;UeuLtC L}0UM  C˃VDO)8ԛHSws{PHj%0Qn=* 7j!LÕ ?V4\Hn}U`5(XNɡ$ad�Mȷ(=�U >� 1aY�qh_-WRei#!܄A ABeBګɡˣG3!~3~>Fb#} z1%\L&9=JwJ/YqCӉ{-�Ȍ$j:#ŚBź&^ J7z\k;u|J@Hyc~Y- ICOPӳKR4K9%'KԷp.]]Wq'i>dzrډњJq�U]d�  mGE_fe$\2̞Dgdi2H(;/�<^iYy񉨵99J/MfX`%w'Gv/=85ux3n,ꫨֻ}W]翮Q4 ]u G:Z L}2sVqTf̻뵂{ {%Z O]xCW7~QI.no98jwj\}m�Ta3g� ^h+?ש .bI5mv#!\0eQ;o\ *Sv>R ty¤3.`S mZxh6g 9)#y~%׹>r#! ɣ?Kxo&[5<l(%xvٚEasfnW a+IJjˤ=0E$CoUXGH<F0Vzߋ/Ҫ6JaY ^ oeb- �U#� ISN& 6ͿBaY @dHoWp�4`ROjVzt)0JMF !PnƏN/#WOn۪j(VgkJus6ٛ`T{ȇQ*OyxBAsFz:84^G$4(%/�PSc0Sfj3]I? ՘ځ@ 7ENurpȕ5h}sq>"� Q\� ~*4 "Ko%<'+z,qH)p=uTi&b{* P(?9~w Det=%U) I!D9I<)w#lw'D"a۱˵s6i+ 4s:NO=V/UhiTX7 Xo–.AYǚ 5W||7qZD;CcX۸Z2ӓA2{K!~prn7<MAK;�U؈K� ϊK �lwi[oYLFjItv?~ʯ;v&)_> "bS=JSZ/7Lfa~h*9!|0# ՕvW"TFU>Ne0;"*)%C+ 1QN0A鳯q:O,[kb \# .ZCL$:V[?4M[&hT9ITqߩg^ٖq�ekHK{^L O] Y:l+E8+?eqy74_yᓛq  ژ@i%l7ǧmG�:G0H?5VŁBȴo+ zkPy29OȂͧ^ # �U � (٦du )@+Nyo+|3[IL/ǰPZ@U(IS6x-:gg<ٗjjQX<yM'V 8]ɾT$I_W |ƤKN}^3<*{dZF, [{5H'<Cc~ `4ӆ/n[\HW8a064܅^Z1?=2R<-؛K#ڄƒEiZ[}Xc8Vc6tK_f7DD`D3jtq{!zE `bSޒ`4іQa"2~,!}JF#VC)f?2=P&+e?2׍9PIF('Z>J�STC�  S8 ƋLfcC2o(QT6x `HoVKJRb1Jp, -hr3U RCJQ{y[XwyֳfyD^1Fv('<"5vOk^Sfdҹ]K ;o}T+͆fӂq'F*(5CODŽ~ۨ}.k6'LOً9;�@s)Q_KgǴˌk5Nk%jBr�Z0 'c a.0udx2@j�F1-A9X;$lf_,"Pu h1ȷ~p<ܴV6'ByQU1 n wȑS#vv3OڻW6&n"AXݭR6BRW"l>i욪)) mR "JoT)w$% :E"vAhKνX;Rr~l 1�S/� C0RV-�ʭF*~B 4ϕ;XeG"06Im,;e̸]˙^˸81 (Lj!-x87K,Tv@ƲwmS@`M7G' d܀я8T[e dߗ|ֳ؉5OV?25ܓ\r+4+W*C) x_sG3th/42;|\lhNIhv,HD=܍SUCːm>b"VW78](ƼemC2/Ž̖FLru?FN62Ѵ85\2{4sgN}Q*GPr )fsX,vhmјyEK#u-&6ukN2r&N8aF: 4n}t.I Ta 9ʵ,r3f5Vh&o tLU;�Gh&4U)Qny> V �S1� cZ쑪>)�s'L2&xbRn] @?cŅᛵA/ERhy=Иt<u DXNIb?V҃Ay\" Y�GÉze$ j˰F i&F1 *u'\1u& C I<^{Y8k 1\zS̄뇙{^?'e K߻m7` Zqs*a>c̯8d]O6DYYb7q 'pUFˏB2В ^:ݻ<\K{Yp0Jqtj7S d!GtWV?۟Abs^?l2|xExNZ>.!iw5N1 CdY q~�:p4vp]W&k4o [&OӀ˵<Y L/$o7b5QNDmïJ<ķ03�SkE� B2X}E "U,ʾ9Bx!A䢓[99;MR>L=;�8Qj8WkcMȄ )smv2m^ Y][}t]{�B|8Z!+U.nh.PGщ˳jt5ݖ`@�QtShsbk+ꉑ2 yC94&DnR!!Aۗt9b�zԖRt瀞CBů! X\9"BdhT�c*`%gF' r;/bPV/Q:NeBWMŵ (g(Z׸LtH Bnwrc A>'ﲕ"iezQF>[WM(!v4ڠ bFk-B!q 3Iu+MYxIW=H֓�SY^� h S:USRnO�6>5ɰ EkMeq^np�p~hbWgR.2ݶas+!<k$]QJl-Z!zja�"$|?=.?T|}H( Zw;fP奟wC}9ȁ m Huh/ ATV}%Uö8F(>%ơp7eCyt>a"ʳP"!g9CWG`Qm!99ZXq|;98tg;rrzӮ܊VL Uv Sd`2iި &WcLʤ~@Jt:IM4/mt_>dT'{$8.濙Z3($?̈-uNݧW#NXhDt%vlxߦ;}+3#?kˈ~E}dQkqwM=:(CMYxԚ`�T-"� GG䶁=2kbPO 4FR#ty2K <`?ZcLzHs[aٛX OER<*а6 #-mm5bXqxla5XKK./;@b o N&pҀ -vc`Cra a|xBfP' }a8b}T81bRi7T3VYV!З1R)+;yVyzq:[T[&[րv>=T0uļF>a7+zO3dՊYM,AJ'Վ6=Qdd] > det,auuQ.}kB6 Ij|/bU1;vdl|ݵ~ ޕP{,}B^(2KO$k: ໣dLnJ|pV18Dio8%} ;a�$na.Nbu-:uK&'RD*�T� ncK@�Rp0S|KESS.)Hl'F#Z;cSL"%˷̚Uj(Q`#NinٟKڔ*>TAm3t#E&?79g665([`MCW9fTYq[Rse8-[z"k8G<;G7f-eʬLAQ`u }(osK@ knnkӌ3r0<;\=W*~lRYUӛWܝiOQ6ҳx ܀ sE(DXK_d`!<è5e!Ȱb]M c"R6ǃLy} ]S l ]aF1Oz`7-AbsvU S�y&]1hJT+ć!Lwx^=/s%z/9kv9GVB%-J 2eu3n@侼B߷OCe ^ 7;+PmXoZJ�T6� a;p X7ZjG7Ra5XL9e@QJ5s;͒1)5|(*ƕA Ϛ2Ii)",֙* 3U \`Ψ,xn P|ո{USJP!H *!=.SO1`Yu%םQg,ä8X(=<JmV ZŦx"? ][!f!cu�9+Ek]5LNA2Ck`KDQ]7ɅS 73p_7-]:ٞ�Gثn^?!(G(x;֫;ZGU1AT J魥 tᑚy ^9a#tlVIW`!i ɉߞjςsy75hdx(C|['.T1ea.q =q% M )lN®HpbD̙ oى�Tu� EQqMP{:" ()zܷVa!eUڪ>� !Ir Fv\r6j 0a`a/s+8!ʡڳlՑ<0yA+R[g~Քh&)^Z-'³5J÷"[j5nuDH{RCX\/2 Q=ȡȽIrj#jo^D<SAJS@Z:+CU5_%Z Ug\cj)7 }->%f+R vҦ"zOiH ܦiPp9cgгZ+^1R׻ZTm M߇kX.˺Phu [gsTV#԰f-B;SH+e\Pş1UUuVxqRBΚX*L ,nXaE9 fK9OôuTj.�T� 6\ @0 kBiP؄I&)MN Kc _09-Xa]<Iodlnr_npɃ..d 9$%7䤈ܢ&kw ;}%Z%&"7QT dY\j%weMO Rk2%4P?>>wbk!qτ;�Ylb\  A'>Kcaʷ mUDuȜ3{eU'%qcfyFhIw`ۆƳL�/(R v3iMA%y,yHn"}^ H̷?a%Fk: s~(W[--]!YJqMMb%x|֓$A\Q (b*ퟤ f69͖ʵ-#آ]P9Gv6;.vQPJ�okƕ#-`dn k)LtG:ԣ¬K:SyP9>AƗ6�UjT<� �wuA@< 'W"(0E Dïq(De%<PKwH':~DZRѻ1{ޗ� 'O.↩)˯2tuY1 ^ IOJ]$ߎMA7EP#7r)-"f'fL ۥ>%m+LcK_? 6Zq`ځo'O6"Jϟ)Jc|$g\AڎTf)3hrM޻C[LrՁ) M)a\$1ȡjR/ A'yawiQFa~S.,_nrk5xFnTC0!H.fAb뚍Cin9f& U1LwB `PDCn ^ xaWFS4u} @~You+aTvHj[b{-K9IooWy왒ݚF'^cP/myIҳ�U� T709^oQx0/E<`{P.XƋx=2Ϧ3\k;2A=j}aMRMCV)h 8<CiᲞ&sF-cbE13LHo2|5Q-^Čt MMǿj!tE8糔EU+^YRMc aqθC7EID(iZ4&6n{hj8%m[AA/)fJ8X֭0N;>FǒvQ a{KC;H5l/nV<(ֵ'_*Ρx~=چdloB; jԶв!M;s"' Dh`tc>fn'6喼 w4q0t[-צ;OHU=啉yHZ2;RQbkk 8]Xt2@$'1pEW%?i+A/`d P�Uy� @.c \3^HwSdfaFe^/}'8Q-vJ.btVoV6{y+U(3zyxzs?KQŇTB#gKYh{t҃)Q:p@y܉BigcޡG5M;jR%:1~V \6G=Q, {ENNMkkj(j{�u=qr=#Թ-YJn*c.t+@O^^`)=Ɯu;i-ƥLƸ|4Xi7}d KM!ѱWl B3i<<mxS{FuqH'P(u(xx9\d}+O^xqB5q@/1KZ#iM;%jdko3ֽqC  !$WODxi(c˸6�U� xr77IsCVҚlsw!\[i܊$r1.! ŔoQFTpmdfQd7@~ʣN]3-aKNjE҇M/r I-I;'y:g>)arx/ZA'=ԊaI3`Ȅ5,#sع7ͷw+șQFEՉmB,�#yqiwSf'@UA/z^4mt!@m-]75R~h" D03IjMO-16V`ի]p3 "mԳ8|҉,ՕRܦM2q�}"z25Xsld勓Dwy0s}hZ0=xzjs`@,Sa" kyi@6W/@Nl}135|<V@*4M'jr4jQq7yVY*: #zrm=€�U� [w2�eTE뿒];CdK` %Slc~Gu(m|'2K4Q%3V'>/2snm{�y:YJѦv\\+ ;q-z4 ^Е&1/7;:tZhq"nZ/QȌ&6u"KHIjq8l,fXri~\4^b`O-~5X{W)0wCO{kXU'EY\"_m*kjl=7Ya9[! B}FFei mu3! FbAwؕS#Qw{@ZPIH5N'&Qc9<ifRD֌RÃĬ#By_eOH^\0d(όުΡ*(r}hc '4ƦNQwS>"yp] oD^{ryRj�U^� ajA!F" SNb{dDgZ#h)qp}c{R>RZRf& .z*"b=g(i]wT9ۘWA+u^Uo4Lb>!Ѹ%1B7 o!:q_KꌊfŪ c$(S8KĴZRaNo0ܿ$2?EL7~\!aq!oC9M}":2*HU7@"j{7e~VMJ@?ȓ)ǛCl"*)*@MPljoǛoTNw4O9Ʀ!. %)8Q.x"(ZB7ğ;;1Xw>e[%h v|QdM+ `'ʾ}*OE ќu4\gW+#j~gҳ@6$Y>B%{r֙ v�U\`� K?͹DE@(zc.P$١;n(Z%|_=ޥ>0B{<L,N q>P By<7ZAvԔv3~l /69z}tѣFACe.kwJ3UB5+� ^w~\M:}3Wpuui>Oevaf#\s;r ,a)j0U+fp3xi&K@9:%3E}[,3mg@_d4Er #fd[fS pZ*O4)ɂ:[#\,H@1G⬓(<(7tbmT].l}ȏDz<ǺrόNB.} eґeƁ#>R PVr9HI*KEpbo TwβFgY4%=ɈOj _ȕ&\ÐF}߃vpeb?=b>�Ur� OWc,3:)s .+?C=9p`}9n O)ʜ~X56Gؔ{O2+. cM6}B&i7r[�T]/-[z?xsL#K34Xu6uwbX[G oru�EI<FI:-sMoY� f A&Gx$šBJ}66dNӠ8ij,Q0/+\El\;`0)0AcVZ+FZ@#iOoeʤͱ&cJr_HB:}oGD7 nc#BUӴE3cl;SB)PDuxi!8oRpn3I ?U"{:7@;^b~:Ϸ$) .F5ԉx¥lp3~k3 e+ػ˭3Qbى�U.u�  HFOf$αO[ݸGZ ,8ڼtfXfQ79'}:HxEJӝ>;!k#X cOWny˅`|}¨O߿(, On!UdX8C89j@]@ !Kxs/ '<V%.٦ם^[lO5~)S?V4Z'q+z?j"2fBoD0otBr>}cdvt\/9^@O:yLSZ Ozw9;fO`"C5%}.x9T})+VxZ?H/7@׏#@l!x/A h9t[7K1</K[bsBNO~[jE{9Ӓ=VPӃr"h\=y\c| `X~f bM85K8*`F1һq N$ aQQk dXe4ZkS%wV͈=G0>>I-*i߉�S� ؊w�7r&x VaՑ ­+Ԓ3W~>=S=𭬔ӽV̩f<6G j'&j?FMx', ИP˭s+L>&sxR |Ҿ{8mSA6uTBFѺPU 34#)7|�cLCz1W6umZ:)E|`NMo\P AMv޽5X3?9Mܢ)B]bk逆fuXZ!2Hч>H{&V $&(Zl1OmD\cJfҝd;~=)j|u.Flև78ݻ7́! qH!]ݭbt-:8A;ajFHjDlVCpugZqPK�+ YnۡGvy^ w/˺䨞M?͂kkTF�S� g-ɱ\|@侈ﭫy9hm.e^n Es΀ ~A<*7E H*9Uʓxc0_ HYݓv}0$D'UoK5 CX4fÄ *;YhTʻ|2 y$k8pV1`q)zLL rr̞0&﫣A"#o1z=>;X?45Ffw߿]LvC:򃏱ﹹO9#W`MN>ߌŠo)ߟ|?<CBf*rP8X19/_^/w4ǫK[΂YV:9 8O|č>>0ANEXgõ?'篫lWEqS,\UxBok�<Bf/^/ =:՝mZT{wRoQ_bH\%V{ą1б�SV� y5M8gRj- DZ =�MroSlab׌ BpqpGa(|F}z<QvPqS<󕪭c5s0> 9g(>܂V$΄04^GYG샗u(VH!=Ade>-PxM`i&=x&x@J9?gXwGg:qmt_"-ma"yp׉kxuٖBM~҉ta5ivHݏ9m-a:wrÇ"eg$*E0%TRK�]hq˺)x:uAR7p4`.-#N9A TCgJ?ez au4z%C{v�G>wuM۵J9(n£z[Aɏw�!sa @ڄD/,@~ oU?PZ{Gܡ";K<H{#-�</S�ST� d;"zodj0܉ sW-(z 3HI\$Ð~U[.+{EOpܙ&0/h 9l.ҋ(Hϸ [ ڸ=-ƽSMJsJ)7b] utd3h3Dm2h  ꠺H_~OS&B^BoT*#bwgo4A;+^qLd`< XFtѡ6go#A>.*VVujI8,~${ͺ[8-. `A?ޛ 0;hf.{n[kþJOƜ(B 둗k^ tF5/WSmuV6. Λy#.}` ]xO{J Y.=G3#wf gw]_ į(cU0E7Y:ep:`LЗͦ5h<e;usV?nzT1o_).;5�T� ;V?ܱU S-4.2XY [*zip։?v\y_-NLQ?n T;~d!C2DW|& 9G50F0D BrjE̜f,1d-@craq6Q]Liܒ%;S<:UL@ P>nre/#ۓ|*foHj3ׅZsJS}M6.< y(RsPk2<K/{!#k˳ NҚU<Γf!C|\_PM3IY RYX}T&)<5s$^S5?·إ~X\EFf[Y??08b/5TA^~~cYƯ?@NXm?L,Q$dxXҴP'9%k˦H 7]\FI+?Z,5EXW 8AJ&3HE~�T � UB̥8<8 ˻'u5 VKc6s t-@muqIHi!x i`B)E4_+<)5�j3ĉ`ݨ۫Bt謕$ m_QmW1 01͎u֏W2$'J$<3PmƘt%DŽ|-(f JXУ B=4a>=Z%-.8Y" /xH1iOs?}UCl+ye[ϙvYڱ /0bZ `j=p}>}]8<[3o(=׫hQ1C9 /Pı7I%#dǹYL Z7B 81< 7RU-<HD~4uY.5~_>E2у@BTȒ�UF+p/ʍ%w7 Xw//iֺi[ sa'K4O9ꃉ�T q� ǁ߷\;tF@|`vL6*:lպ#Pʹ|vsi-D-se.Jdyki-~k~VjS2u| 8`bcS>;ITxHG!!&9:UfZQ =G %աs{>p25$&z=ݣTr뮒s黥<. jcY=f^}_&g&PHpmٱIyB >SLF;$bqbC8u9&TXXGV[GKӷkZ"vPZ^ƒê ~()U4t] u8~nOH'1 #JiG={5Z j)-iXb'n5pcXֵ3߁H <Z<sIϱ?dк+$rl<tqMfx?zOᵐVf$[4cJd'HuD/EA^i87.xrxdrcQL�T � x݌׆�U- t-oXSaaZMF+6 uA�_jZkGq Lぐ7_8VpR~7!�{N3ʺ+zZ*ɼǢW=\/5u@#Ƃ޷_f'vRxn&vqn⯚Ȼɠ\EGcuC \ƉЦELFm_71li&mq*Ebn5&ɨ UW*&�4 $%ym&~?孈ʸ>Xob1=BK g4$'}jNF� ??&/GBUc۱` T8!TY60<;Z 蹠t/(S}pA"EBl!=RbW_j,/T֑I-| Q )سz7V (j6C x$gd-W'dfIn߂ _f�T/� )+ 3 �NZA]$S8mZ%AD(<ws5&lk%ɝl)_F9wX\-hSOI<WEG!q*k(F3p CC7;=HS!^z.O6gwKAIBKFY2TZAF"@Ic߸bLWwwj(q;c/|Ӌ(Y!mJ`H; X6(鐓rNcb,ڍ9J~_\  6V:F8w=-ʼn?@ XTA\7SOgyj @FeL^F _#FmU4+KCrV5> ;ua :-$_X !ͻz+|;-g!Äo}xF|=BQWTS>XIm%MC՘xV]h/Ϋ@λΑݗ4Hp8~O9x{>8xiyraQ jFm4SH Gq҉�TZ�  ` G\t\5D6u�;sk&j*K N' ?=xX]-PsKb(DhsmR8{6ݒ Cł^<" F"ʦ;˜  7(:V8ӊ+7ϜThK&>uc+cH,4ґ<kv-0rխpnHy̚v'qynu qo<̴3GK*�KLgf/Y8(Ȃ? lW4PV4*ѷOe_18ϤRXpn% Cľߊ{9F'd1TJ+ao_BmWm@\1{o܅3iZ7sGyab-L1aBzkviWN B@MⲄQ7cԹ[MQP` u-2F'|ۖ0Tqc0qy1g4r-cB&|^JL芸sa[_T �Dh VX</Edq/h t�T� bX/sh�ԛ'W26O' &tI^8'kA7J"ܮbq='o{] RNNSR�WQl*jG9DXgFF .֍AClXJgwj7͘yzq!4t \ζִJ`.oH^td^t , M_g2ハ�ky+D7E3Ya)KG'ڀF~22Sl_؉#V~-c$^/DHb7浠<Y,*owQޣK{UDرjF03ȣ=^jok[oV7]֤޶G5I[v"$3Ң@y^}T%գ $eb ]1Y"bЌ"VMa|V7rsKyCntA$EoߔP~ZsP񳞩Lv)�U7� rڭ�cdy$* Q 5i\&&1jKum}eAZ ) h`Տ8 gz|knR+Hp yMƋovT"0r3pf=&` TZnKj9E41܋ ꘺u#Qh#}UXy; ;5àeԩ';D8cjn$aPOq w"Ă(1)MuN~ZzIY3`!؞cTr'ԞN8�_󢊆L6LͯjxO`"/a<OE V=bjY_0<͒ѐ{i<�L՝kHB9k(ma8qdt>': iT̃h:#H>0'lH}!X@|zd;:Lt 7r6kr%:v,X%;mfU?~ Ku׷<m�U_� v1bS8DD.%N,N'A̍ϥVo2F.3Jf!g̒b7)P߳ȲW`Ym*@H+.%Xҁ-=bl0&sZ~~huen:|]�2.fKup fJwp7X(_$D=^۱N`Dj rYsWE e0ǢҶڪq#Crg}O<wGdl)͡�[ahLcƋTa{c+kFyMh@dVhKj�I sD5\N|(&!M'R̎Ua6,BzQHN{ժA4 M@U>C,4,dNLhX*Ql� Иt, 2s\> v-^ _2ƒ"OvI�=ks؉�U B� >~0.{syLP}yHLj�LYMeL9E<L/wV$(nB..'m}|JtJ++D\WʠL#XXt^W8a_nRb}͸ל#, -r2lyQP[΋C@]a}9sJ�FCQ@'gn4PRD7v6.\͂'EϪk^."w\jLt暻A;٭gUD\.L3~~(j>V!YV5ft7> +"ka'36$ 3P4" Q(.N=1<fQv8W!J+[,i:˞=ɻX?ү e7z-J){d~cHQz�?=(xB2ydΈht A ,(ɬYt\L ;)Sz(8;fg�U I� _UIh`U]78\bSAŵ@$<9'Q3en˹+*E7T69밃2(*RժW:^4CDy<%q#¯d=ڳ*XYԗ"=NMs(cxf\)!ԋDj wޚR%>z0zr& )}I�IGgR-o XuzG gbw}‡{n? 1v<vމ|TeyU"yjZҊ*͔Ӡȟu# ?gQIKpw46r<mU} BHNwPq xH=X\ġD(I!N Ay, fe$~X"^d19[g3< #4OFD 2\iSRK~- oYgaӋömkםم.4d ?¯Hۇ&Z,uXu$CI jҐ�UF� pJdt(3{̱ 7J(χ#,1+}j\Dnf"[ZhqVapq)[Pw?$yߟys5l{ LtI%y۝SϯksgD*>NL ߋ'™"? ,h63+Y[`J3|M7cji[*y'{67sw)u  4%O \*�\ξxNFʈ'F4uKdQC#~)ʞj�! &޸.=hOOΟ;z@"Ow5!8E,Ks&g|QXf@< J<q~x9 Hۻv~jzzr,_cNvZmAx>*K>Lu<3ZYY'"h9vrpĉ5 ۭjyfGPiИkQ~r ]ۙ��U�� IXIIGz *RXnMU޴()lX; mipLiRVȿ!j8#S{G i�JqcuLP/Zr$&~0<5t)Gf>y=ً OLy@xB&+&Zߩ2B2~x.3(5+LE<pmð+' a!itono o}y_[ Sz[?@ѿhvڝ޾!^1f?9!jE: cVL\ ''UnDab` HB]-dUO:V|y2q|YRK�ֲf3t�\!~N{gED1M_hIygcfqo H*0@Q&^^]'̮\c,@yƕE~Qժ+6?l0Px&hpsT瞆 �Rε)� Z`{u5�kΨ5Zkt1uf}t%i#>G-ϘGmH޴ nV$NwLK6#;߾=́] *hIkICFrwLDnKb"#BԊ,ͥ,=ݝQUޝKISD'ζ:jS0V!(jȝT)E Wc 28m5IBrbrx'T"w˧{97Uz(e<5,M(3QMC`4ah!;QD4{ F>g]&WoduVLZ~~LM[J$t”}4g?*Q˓|TRߚAv֭Qڀ-;ːĀ蓝 T9oMxIkq&;z.̘SC/I5^jd1&.7e{u~ͲY]Ż q\}+>ܟKwmؔ(X% ϙk �S_� 1^i}� Q[iAP +LǵSgfL$(]P 跟?IgX;,?~^ Gx` Nkb0-7. 5_x;R|jiCM6Lƭ]`.!;3r�|^)M:E#HE[03C6۲x8 N~\]`;!*(C\W.6_ͼC9-" q,!PXq;yAFt^4PY#9!脕vP[0c JذD{|<W_Z:|$<żBSx/%tۯ+cPK�\FB* -xvA֖=n8!HvvR"EHSE4Cb1v1fsRP/ХnҘ/k~wx-t`YA~܈} 1:cCCP"&? L(&l~8^D[?Ԝ`T5Ւ( H&C݀u �S(f� *XG�:D%< 3̧J�@.BތRj|v 4ƌVؘ3X Q=kڭ?lWAi;.?SyC3q (x2T9c5I(ÜKb-zdvx-xq_6W-M# WI*M-[zoPfEVRPKIqZj6A$_˽r�iM?Tܻ>gJeXİ\NoV/%cHk=H&@!nڒRaeikgH7tLƓ .|u% yہ^jfbX;?'8Gj^ DZ=r u(%|d%nĈza5C"dGv3PQ'9CLK;`^!uw^�Yv<wLDI8d TM>_>05@{ܩ}ޗ_YYm �T;]� :hI& vN 6R|K6h.6'akaS6R&$M?e 1IuL6@2ؑww%,87ASgPkeg $u{Mnc~}V2f;a &--&A^zdrwUitRSx%D*=]{o eү٭^4gvL KfqtB؛퀾C|À-]^: 6t~^'>iMqWcRYeڙye[mԑ5<~GXF.p)slBÚkwĺ0?J|fLK̰&p Vos3bTf$N&5(aRL(OƩdPYZs!>+˽|mi[ >{$=$3犌Q"F]PGx 7KѪO `46Py3:U �U � V3XB(ZC|5qnp[6{2hg$Ca~96Kz>4XgCEzn utG騩i'Kw_l<t8 pn.g~)Q!m_[TYE]ݦTo>b =׍3tc(AE'ðk~ele6GT D,.QuR[ם"USi츥O"�(2`O(Yd1!(v]dt҈=\L5?&a*i R<9€~!6;)~]Ai; '   Bm1J$'EG)sn>Ab=q?>ea'y)[󪈲Ci!UkJoB"a Dh6 ~ .!rMLFC2<XL2a<?ƫ {KʿQ|<0܌QAi(Gy1o݆K]I �U5;L� ?㘂b>[cyfYx!d$'21 KmG*"$3%0]nrFPO2¬ĤU-".)jEu*gJO߁fQӢ*/le-J w0-z2K䗉l#ܾ9gN)zJ R;ER(?:p=3 Uੁ^]O`p0t)6Y\`#^nU*Ʌ plo'gGd٠`2ujm*߀BkEecC<p|PRi 2^\\ #NZ)[R}7yu#d {_ %@md`YG@4 "1#_ӮTPo4~r3qgK:a18b?rdӘnĢ_,4׉ϊ8grTY NY P [(QÜ.E �U[� $hhz ,S3(G"a� cL\oʳM"%i+&k=hZ`QkM)y_QյeA*P;3wnW2HCfW RO&Շ�z*,O0 qq[qKiwm-J#4"g;wč)Quφ"Ԉ qK4n ]* ^-D9E `U"sɌ�WGf'Lً*_x(&eu,;_4։nj?ϡgc1֞C%9Ӌ#<J+b.}RbH[*9[u~Ig1+E"6.E`drOgNٍ BZ֚z䎅Jx[,U̞BK4&2rV'عcjw)D*n+ᭃ mn[~͒(-\;/l M^~:Rt݉ �U7� D:s�9@54W݃Ooau&g):cHGU>aM#=iMXlApz1Sf44Jk}s,N$xx;xt?JV_xKiz2qۉ6yTDȷԌy&]> `4pjwJfsrНH*]JLH*E迉 z U~)Jr•NJX'̂lOCr. Pp Īyq!h|c 6r"dꤕ[= GLY.@9>/BvT$E+Vy x1յl !�jM,hEfVTacf,tWPҹQlciɈc~M#H,xq1j=}>[+?+&}S zpwXx5 9�HP-9uP&&]*e{LebL 뵲/ߦY$pg?mY@1y.@CU �U1;� :G~#F" bNvkvA_Ȫnw 읅G! wfүG= ›!$J9q2u6(Deh" ߅t j]CFeV%qN|ǠuCm(8TضG SZ5 ud)PRgoPn00xђ LaG,*`u9NA {pX$T+ 9J{353CIAhG.$n$jKT =B(D;3!ȧykjn!u:롏я;k c@1s3BuW'I$Qqp.}L>vcщ<�ufgmqTmE(D"uOnJC~ۖ92B{ L :uzQ1g˧/DUJ7*�֥B mʉfP]uCx]~ )]ݠ_sa= �U � 㭰P`V6gf}?/m\}$-300J-`2A;h (_tY 0x�RCtC;AY@R}fOҩ9�|'ZHXߗ 6l26{cd ׷U%cP'hRJfK.ʯnA1X>J^,rVMAw]o%"KvC 00-,@p]WrA+ 7�G5($mל4(GN<rX=є';Cà z4n5Ⱥ \wb]&Mx WZgRgͼ 0~SLߐ)2?v@5/<Yb`7́X]#ğ1 iJJp ֦�>{f zg@тoE JNX=q27g'dw:$ 9a;MXR 1~ V23 �U � UB΁S9|?^1ze{c-PtaA!4Y펳WAE$Z]Sc(Zي} ݄C[0`KI=9:MJFػV-@"^Vzv}3w_BJ"5ZVAU j*v*C2OhБz@5#9/&?7'Wb"nBٽ+:~0|yZ!>U6BGuR/š[ `e<|<(cs͟{Ydk;=zuD9^Sh-`lH%xsV* 5Բpts )KY}+"2MԤ\HwF-I }@Z7 !V"̊&^2p:8G NdjNN#zM>\YXG_֏I*cx@73ZjU+! -Q^3^|P ]j"5 �Ur� ) </@=} � np8 tw(-U;. d$40U:gZ+ 3v9DF0b=l0S$E�BQC"u>993+4Y~ _rbG aWU٩%?6C\t OCQ[":4G`1vџ’+ΐ+�pp ^V;>|oX-4f>ް/zJ8x+Td#2h2l7 zNLUA[N }H 7hŅWMզ2i8JmIOճlQmm;z/ÙK2٤ȷ8D`΄sNk#rs3n3L:$InLim@#@#cnت0+*;ȈI*.> "}*-Uj/.gf,`=Tڿ:7('n4Z& V�F5�#~ 難�C#h!+ �U� >?, z !|MSBO&Kp듧cQ_8q8bu"#4tw:&唬C vy,/&e}Nӓ^IM卾OFZYL=՘\ũ1/QǗ'& ]<5ם=|ܻri*t^mz#ϲ+P) 5I&6�GNq?J8U ۰Hz( ")D>@_@aߥ5=(0{R QWާE3S|l'Pbx < 㳼D5ND~j1> -lNzJ]5ԲN82K{iVZB.+;. ʛY xl [YT!�MNv#Jw?m:BzZDncْ`AyL7wy8%Je Ӊ�Rni� {o9VoUG9mV.4~>X'%O47 ˍ7-yJY"S&Hg /tM!Kk#~ސ}2V ."/Zk-?ZJcm6 Á .|2 R5O?p?;;zp!e-ItQI|K n2Ϫ#M5-̕rވXf kgw@F/%kqj6 TzyمM%bb[vwª!MuKfW pzld( 7'kN|I3 4A8rP3"[|zH2. bCD]Hى'r%O΄= ś'oS '΂MC>tS`L&kPA*9oVysԲӬ>.d&(Zz7?; %$ݕ_KZO :yfa˼ol�DVH$]�T� (!Anp,�5=AwlŤbk_Ү]|r36>>>Gխ&q:ZxŜ%,Mb@؞״/q :R[,Z:AB X43.G<08�JI=_Q \J(MgHI4qKO|,n:/(}f^<5W.YY Pr` >4*-m.XakbtKYI/KKа D׳9FOn vt52 I ͦ%ٺ^*P�V%p)Q_i$\5xW9]4j%b 9cpV<HYtoE3Z =;%a}铃x}HC@D'*Jk-rsʠXUuѵiJn~Vao|姛֟bog1N"L;)߯"pήOܣasa��T u�  }MS3 lr<)ǒ#|ŵ~Z%Qz5Sr+QMNgF~͉ S Ld~WJX2#~xRZЄ'pBg֣j�k=Rj8A6iI sXYU0M͵КSo^+7b=VxC֑9ocpt;4uWwc0� c0g " zzHM,j'h|46}�eLKl> [W'śrnդ ؾ}<Ļ]:Xy(zPɯzSHP-{t@-n8~Y\]T;݁Sҡ wÅY1z~YMr{ϋNh- ໤{ţ9:POgcpl?@Ԧ󵻣s6i`>x&xbtJ�T�� 4wh�U>/}jxyXFeږӹ 5I0YLK/@|&"y_%;YjB}H=VF [kTO\y^t iSR+rh3"÷+X[围? Ď &d}Mu;pBX'7tS=�o Zh޹AghK Js?-SP(dH󒥮LN 2uGUrpa^Kc0=.@@âJ:1< ml>Alzd{;KǠ9?Bt 8떫dhŠvZXނw-vX-]i 2sMtKz3Ot lz!"Ȉb%c@7qfebz3gV8X2;,6t}ܽR*Nt`O( *h[ϩjE0HQ@(o0@2 �U>� )\ti LShoooZe4?!;Gg^<.I_m1/EF/tU+<BU rA;Sw_нVc';J3"٭ⳏ ͮnю]Ԝ~3>kcK;06GbDxUg>)4!-VD"PQ]r"4ɊtQY}v< 'w^1 5e} CH7ހnt%db׽1ų 숅/o.0Mh+Qݯ|l>/ö ClXo^z)6ibިk!Uo4/yJZוl3Gku"(|Oꅇ^cLcII%ݧ{djI{MM ceXO *O*(h]"3iP p;UƉ �U>� /!wT wiXm7a7 yK83s:3%V81_( Yc>K̔QItu4V# P1@ -Z\2ϯ^#fP`dm gS vi~ L H!k7Sf^{ނt:'rӻ,ZH1OT_'Y+?"0^oĦðaj 1 n],dc�nfe]Z7r2MH=As\HmT *ZɤpJ4 wk[Y6!Ro:9a \PUYZ.Ep W@ 08;d61jEnZ2�bb"pBΡq&ׯB}}Xzf ";_^:2ma^ϫ"k &u '?Sܰg|'9pS՜89(F.E%(}sF۬g%m-~ �U>�  -(\o=N}\_/'@mI,a$s_p Zw١U(p{"<? "bxѯk_3_91.왩bsZJI`} .ci>IcNGpE($zNy#mZꆙ%*x/Z u(ΆVJ'i`C%%ʜ 1Agj6d�4\"FI}%6v2mhYGNF*(,Ngwg[lNr'RLUM92>;ll|\S)>aS &G4:$8""MنQz|88_; =X\f/g+A[N+ʅ`1 J~TI)?D/aV?WK.a,1bA<fMk&ّwg#Hu [Nu15E9 �U� U8q%>MbŽ{vHT:.ӺE".+$(D@+e:VzQg v9H1\}F*InT<8H0IpD#Qq~\a2ȱw Ω@`r DFf3 h[G'qˌl6Yy?9*;=G3Bۛ&siyہw 4˷]/+wv86l$; Jv 5x7G̨y"7`g]G+�i>=k7]=B;%dp1P[%.%c: !#ﱛdSw !y[/< -X-E,ʝD j/tuG636{_Oφ9URҤ(dBǂ, BEÉ S6 Nl~kD٨j|Q{W!+3EɅJ"=%ǘT-�{`U"� S�� kC!ďn-rڍ/c4H`\d*(&A] bgBڧٵB'OykY+@/4ׅώ#i<E ڮ%Y?#7uxFVqR/UDtm`b{6-R)o (?k?i.5 HN nUөfq?jf]P,/Z})ḭ J٩?8"OXH%6gmuiCp3׬s.<2Ȗ'֕ġ@(jX hx_If+g*n" �\䂵 Ǟ)Y3`iy!hKnyC9=lU[~Tb7q&-=mCMt[~Y-ChT8dՠFiqV#Es-&iJ,y†Z@O9i2ZybLd=PMZׂK~kh; �%   �T ?~X� 9Q$Mיc*}ZrliYh 6kqxk_X ix7ǔBwq6S*jP,83�50b7̖M R%WSg1@l[RCE:kq ĺ [L.?&7bf.SۙOnJ=}f>DTdT46bAet\>SC[Q]摬Z-w_ ? ݖ;$z$\m9ʓOܐpmҮ%t /U&ॊ9"敇K|0ENjjdi> )X)O�r\>^4zf!Ɨ>/y 7д3&t0K}.[-a}If"2kԞTu]PK17n8] 8X�O"RUXAlr<tYڻꃦ3P 4n/$^'Ys+4i>k�ϰΤ¿ M8A62H �2T+http://www.headstrong.de/keysigning-policy� !چ;jz9DHv}jצk7ؖ2$]78͖ys(=Q3fpnq44 ad謑/i)8Z*׾p_>L[9ӛԊa6T] ߩݭ1k  |0CAZd_[z#l]ؗ5I<sWr83~? !_J-V0FJ?t2ƹe6t6Q W*7ωAno-ĦAPl>A2 Y'_�-`jQD1$B֤Q)FQ5hT\}޶OKB1#H0k9h|ft%fA .vz{yU\Ry0p?t,ڠě͏N1ߪDŽ=U,d ici_X§[^q+[;_}/ Q;~% ,4uZ`s+ӯ׀[1*�T � ŊSeGK6#W͂#뾴\ {YXג]\88agA =BRQj*=xä+}Z)%<ܮOߗضXoJj1,cxo4,^mC Q19ߪ)>;{N(`6KA}0? 07wt�ׄLZEMnb\+\:&WGMopܳ1PpZwYҭ/K�AȃҿXMPb-yȄ"{-' }uAMG$yF{;|T3�4aA> 8,Q>щ@4`vPvi| _-Wi$(O-:'E[1Roo\_4Eh蘑܁iǬ6^PP|][ RxVwlDXPc^LJўձi3~yxȽ\@9Ti/|E.Do>VLzѣ]3-HͫBh MdIzտV6쩂jT6._2[6ZO.A)19]cD{{4%ʦt>iJʧ}1oSa(.g,k=k/gBQi\ruW +d] 0{LWquN >:o{2}|/ܓ)x`O_kq} ֞jˈ=si](Vɑ$dͬP 6& ,oO@Gټ)U(Gn#fFN_)P=n|̪>zzz*%EYl4ERxҏ5xc _yot0Y^E ?vP"Pxq*>-\y*,J: g6gբ_8/ �U#� kr �k�$f "xFd5.^ƽ 6৳iUaKb_KFMqZhbb`HAF1'5s&źL])L9 `üR1E -*YrW9xKLNriWcz`Ng. >RP{#.1G Z$Pu+sh[iR<2ȲcAOFI"(rA5ͷ@=++`C{vTzrBF+$h+"E-C$Bl,g@ Y׭5UNnF&:HG4zuwO=?\&s ?VEc\?J>Wi$�l'ћ^h]g ).Og"g֌DZ 8j'L+5 Xf;73hl#<H"8$<s{nJZD6GfEpN! "Z3ڟdq[DljXw|'㻱-ǚ--kpԪ^3\hn?xh`y#%38g*r>N'\. ֶvo&_=HA@7]P˽ ;�ʤC2up!pRr|4s1j1ƟmOڨj x#jTRBZFyHNL!a ^pf hJ+]ns;FSWd= TC " ̟{S:,R۳ $4Y'Ǝ~B6KFcX='+F{Er֮b(b*wh8Zgi jӅZ0|g;lw-? 4/$uͳIkTvXJѩ{1~jB1W)tp?A oY6"[(!G;pG -Fse-8Aq�<߾P˯o �S(�  F̒|@�x?mW $2 h7U�$9^zuq|7Z.s],eGB4BC$G8`gIM<;Sb'o,?Ҙvx,wMR,f8FR(E�_pg{]40vk\@q7/dL ԫN߲L7 ڊo՗!%Cs(ҭr} ʫJ%PVYD Z#]obq)3Dκz�_*,g }D_WkF7b컦8c2$LΣ$X]G+\QG_Eu^YPA.|$Q~<c[:x$lnnn!-e {"FD.>z:wjۈ ŒܷGfc1|Ǔ( άqG.v|}`wleuPure7pymRq~T^C<5 ,o4͹WPV(kg@QSԀ?U[;jWn[!gV , קlaD&p҅+ Km`g(4 3?a 5wp:_f1]j^cJ D_j5vb2_l%m_z B٨;3!]Udke(Е⻬-z6MLSJ<[&jT}ʋh 4_=$e yPyv=K }S'[D"`)Km䒕#Ҋ﷨rop"ؐKgJ͔2i0x[(^uNPx8.&̥I~#]U+.4^+641ξC ؼ10.q#]y!u-+B!� 2%Ȓ3:s.-.dؤ.U~#BGO@Tk�I ] ajJQzh8?Lߐ*қ`h?T}UHV bZ J”KoE4I 06-^% \QMڋS-Jq n.i�@^`{T諗)E1\*8NX!^ ֟OI�n )=Mbt&_}Zf $׺j?֪=-9)emy<r~,c +8|S_;#XwOPYd8L*&wЊ<GP@1o|뽞&Mm?v:Jsa rK6}{ DHn>fB.{'I%a ִˊ0wZ=S+уe5џYQ˄ ߿<[3G4@XK킩6Sxqph|2,tiJoX)v|"Y}.plǬh'?n[;>{.Ԡ<~4?e iP b7DOKX4Z|m~YPH�EG(3;3{ۘC�م{?y:5tWGAwE~>teM">`uA5Չ0MgP{0G%{c Di]G 5aNA^n)N~4&W=n,eH6sFC)F8LxJ7JƼq1d$!Vld};+ CvZ6.N-H~GTajd*ke"y3ٶC`Qr";=ϖš Q@H;օX51>>fLlQgk[&7>B ܴ??y {RqWfYO<-g*:{¬Ct>V"p!q\͕ M^B3.?jhVe=ٔy50BtF �V� /|`G�/$QֶDD# o�]fJe.s>%6-ÿ�V`e"� c!G*O0�w(b⊇ 3ӵ y"l4Ǘi"|&~WC+3t^o㧵Z`=͂30][h#btS:nkԯ"C-g,yY没(% 4!\+lD7C37x;T՝egAl+\BG`V~ĝjA8(/8Nٛu 8*3@_?0K.ajüBԱz-e�V:� 0_wMϓ�AuuVȼԳ"$rm6~89:39ꭍ'>AXeOcdkO̒sђ@| A)@|T̃44+@dx4"AKy?J13m(D26o$^viBrRMCzIw]�CUg?̊3G'm 2%z0dGw }qM:K7ے1s\7::G.B~ŰO!@%SVZ�V�� ":UTCI gK_'0 AM̫VjSt$E]�%Ѵx/2 <hV8y:l"7WiBCT%>uא5 3T8rw6Pߪ 6n r %(|* yΌ0|<VHօTk jڦ(HxTv>jeoUi0gu@ou8ppu-DN�d$.XğP*08Wթ-DG}d2Iإ BVΦe`'.{Ӱ6~<i#Jt6郙)IH '6!if 挢hw=CJΔ=41&ר%�Ϙ/[఍b�Vm� 2N/P�ko2#Av*7I#h1񅐠6)eIURЪy1ē5b�:<5gF t.xz4w<Y|!8)0WpTK& m:c!P%T,BH3:3}cO>m�7w L2ƳaN)jkya}|#N\z58iq̰8\Vc6:m'N,fX�vj4+2h*?c{>6Py$Wod?wDП3Wg X%R_`R\`�mCEȊDFVQd@oŲ)(+8M0:4@[I5WΡNJ'rQK@\ZWX-tA[+PP4H*R#IcJ2>~ SbW!QHUJX�V?� E  K[_F{b6vm6WvHi[.޼lciuWLS8 ,xOa|grG5fxJ'60DgZFg}OzSIa4R.&V5%0z {ܡ7n8lَT$fOfQ^|$c ug<l;<_ 2ZtN.!JKBٚo4.7_v x)( ERs5944AKZI(jgzj= 2?hlT^D$ZNTM@�F 81GZ,^D7Fp +kq,Ѳɽ9 kIo}*zC+]+`48;?ፏrrF#*8 I{?&t+5cmP>?|n4;hB6,5&H22^րsF1YmzvV3slYbld8Wd޼l�WNC� Jɽb{+gR<POUg`xUvTI G"a6@ Ό-"2W֨D|X L/{3Y/y uj?ZQ=DI?XٯƯj1,NZV6tMɗ.6稲y<F#곞 :ؑ [bb/Ns2cm;Hu['J$uܷR|i 9Gz.7ԶDkǺ)@(PpiWP|c.?)Zph)FX-{k�zr ݔvY}Ż+BjMrq~7'/?k9Fϒ+Nq-[ѽ (r1 -; ͛Țٟ{ (=GN*c 4>O8AU5dV >n=A`%1&!y_ۻ}3F}qq;CbBFD%N {3 &Ʊ൉�V� pڬVB�eJ+J{3/D'6݂\5I; Ub{ d(`}"3diQˮs/ߞ4'Y9U.ꇿ 0~6) .:#Soe~E)2zp:+8 MN&tO"V2i{_I?ϐ10EfUsZq|ee?*}7 \5'YNN,(g`ri7Ll *j"`HMOu�!E#i~ƜMQ57 M�ú�-*MXlq $7 PCG{p_8_5TǓ=.;QMRC1"lŵIwrj$C TFA%\\Z n K Jm [4:RO54jtd(RqB& cplG/0Yޙ2]F6yunVÉ�V\� 6k�DH)=�C)5`؀,".R}x|K.g9yLWAgoF$4)n6wQ625[w6%PI)p nJpdBDP;848[>-` —N Ѿd|مc;;7QR٨R|<Gɜo=ON*5]Ѯ7sLV_ MPD.�Op'nO7 _j@Sݞe0*])=E+az*v<8pi;OOj՚MW!ԍ."=D<3m/aD+!xR.if{HXuyJ�hLtfo<[j^\R=ȰЖWY rjvsQ=_ԅP"_qV *8Ot`zrnJxw!z؋zW: cWu/a/fJ_I�Z3H}'yw0j*5d!: �VgX\� `~ %*O &"l\:\ a)V. sB,%!_ N#1VՀoNA cSMa2*Z @YpQϾ]B0mU;"SZZ21�7*OI.>K1g6sY}k&<zApP<Aen<8]݇ƃزܺ8nMѲSwVÏf <z#2 } ފ%_2&Le?#(QsKOߎ@g?C)lK+S38t90jyT!: C>z@z~GLLiQNP~*fӰmOFކQ>VNBZԬ<>Ysu~3&Qdw,wPƓ8?+'up,\\d&t2"E>g8i]sWjIRQM#z<`O*wJn蹗:�V#� @<&W͙OsU_|8JxKB|όn8PD8GD暧[^0* MWopb4;\EACW N .oS4NV{0 w'Oz 1 _ \/w4Z?8ymWM}Ԕ^`ІjYunaL(稈+GGJ vIlsE+l:pgxT<]z1ji05 �L1- ;8OGaJl5c-Qb,2A k.(WO^gsezUI,y^p׶yخA& d%b ׯ۩/B )`{$BG-74<@9[2i �MrO*!@I'2 +pV2=,p CjawT:$}ܸy�V� e>MaP�32S隠)Gm M=bkǸ7}QGA4/ɪ =;ypxFr*rƒCyUleV&OSF :#kǚP!zŜr,oz7OxՒjN}7Oz7ڶgۢl$ٲ8H,8)`㋮CFkOdďw\ q>y}γCV{%[*@xCwܒ?1p,y.\KS$Wm*z|1ݷSxae2^^c˪; U>jEѠ?*Djq@}Z_R);SOW4'HBwgBjfrWLfI@KEBs>]HzO5\l)lG׳#";g{C 4E yٰ~ܾX)pk:ޯ]�_1 88GXbOKO<e6�V� q}q; [R.6Y$A&/:ՈsP|\T$Wb1-mrxVWId@9pcc\SFnFaБaBy&eRLI+8k_Bu*{_[N^SjtUk#p#sSEӮSj5aA2_Cwm" SoA>j-BH㘀aol;\Q#^|Ȁ%uN095)W2ٓcE@NNޓMHĻ6:{DG"K}3}D2`箥6B.b?l|߲LҴ w<j}w& 7n#�1K/׼FkxUܳenI|׬rJ"zR #e'~naQK|RYv/W%%݄/<Ѐ~ #x2q9-&#} ~B|Q  �U� &tF~JѬi9גUfõ3yPmU lN  :L󫂐ΪYnٲ &,/ ]N}ǻ8M1 a f9f7O厱]�7@x3 |wQ O}G>mK AKC?-.>J#VO9>OOs`uIqý/5B"M5={f 9�>:80KWr\ ۆȱC_VH'O5yi1v<[#O?T'$c7vVpA=j`áQ5^- SSoJuE=/k[c=0X}]fMgBIX &tIn~s`"^UQ$F2Y *YRQ8u>9=+m wM(ҵBD��99w2+cWpYu0"͓9xzA耉 �V�1� !xl(hM*_BuO̶]:`t4 u< n R=bv$ SR??۝_G=,7%Zm5rґy,?6h|<lN|9\P<Q`M+<jk kBwabl ys:_Pi1ho犻ɞi"l?$j)*1/ vMMW%h/7kc.$vn(@6t!B;4˜2ztm^k K"sYw[4t@3Am(qM |ҘHݵew.rPg@2qA 2]=n͔hkP/GoDu5}H"PHyv`Xai\c+U){_3Wg,d<R\(Eniþ/HXmK8D5+4*}{<a_ 2xJof~n2O B+oB+Wcఒ QEQe&  �V,� ýx2h lBRΉ'{bTEL8>=W ˀr nX5UpK6ƺϜB}qݍpmz0-l0?blkB/ gW+}%uFf7N:iyP |}[s cz$k7/e3eIJ5W*sĢN=̣dAj .e/| dD'tt,sEO]a$��d CiqS軯RO,'+F4X-�;)Vty]nA3<`"74%3 OhNNHR*D'zؾmBP )& (UV6Y' A4 /:>} R(k߯ZXNfʼn.81_/Δ>OyToX,z g;/{MN^ d}놏*6Epn9~|?$8&<!ׅPjf))<GMW ) �V,�  ^[k2?, *᮫KO f*`%O>2o;1ne[ݟDak?At1Bn\9cKӣ;\l8Chn)fº.!ݧAP. GvŪB`K\O-1I$.+i\@_!4ޚ$`TA$3'UwH[OzP1]wГvV7O1LSïW,iVВ=L:u\8ïE9oy6T )q?Fȼ ye(b7Қd`<FbUl,#9bê~ <dB-g5`\7L}CBR9m  z05!XHbZse~TpjROQTJ,T؎+SGxaFsfTQ`zYc,�$V;<mN(뻱[OF/N,7J �Vo&d� �UV>�jX-<# H ~#5 xot*|7Y5 ed:[Y&qSmqw)�!OQH qlQb)cV Ӓ ?0@u&qmh~_(o`cWj@0/ hMrt}%U-*(R=Vg;oW.Bt�Y#�?^NZ_X~?57v]C1H 5; -QJyqڤeW(;STjqϛDɋF_*r6Ɠ'$`7<e:Q?obfb[mʚ{%_ިo|rc$ކag 7pj�]l/LV{94P}9Tx _DL d `^#*pHH2ԓ~0F*8Y Ο"*,ҩʼSJ+ �V� Qm͒rš�00|@�.iJ FNӀP?aR,C;L `qS~=)[G55C,HQGSl_Pf$Om՘bBQ)ӶyݞejLBк~Qrr#\7ɻT8AL.SZ> '+cMֈq[ݧV im2OS\J營͈JPFL 6n"l-Ngk6ޠ�QTh2@iJk_}:B,WaӃm:Q囖vY7 >/�(Mzd9t/]2 k1]F)c/rU: q] ҘGijЈ5?U;.VG1qArvǖ҅".:z7O 9"RI�e<Tj$۵l+Slz$3ޯ8JhVxLm}[V[�Vj7� �o+)Wٲ�"2W'[xQ xoZku >a ˚/uKbr|ek)R5VfP1y\fy 6_`w\kRoN+!e%UU G-}|ݿУݸR()F 3^<Ց<Qg;|:|GK{:%|4WԊ;)-~g 择#qw?Cv PO[̋^WҞ`ܑת(QE #grN!J4hKGN> +֩Bl\тr?p *cߧ3͜r`.3ȕpAh/ sC/{`Qe]0p`p!幔 wu gJ\|9bB/ek>Y 53ph[%oʥ<(dU�x%;j~x{׹SUJrW^OW? &tvji�Vj� M̡{L$!} \J[q% qؠo`}2x+dSPr^i&Ď:Mۣ7@+`jb= lAW߯IThEֱ98K}mU^0E$m|Ǒo(+;=¥A>䇼 =#*/*rmː<.>ЬT!)Zh^V2[  ~L*^ipndA=^�dݶK }\`#jyiHIb/ fpJ!քC` <PbnNZWMorj~$P+Re4>NzZSqP r <2,Mz'HuH$m:hLTrS AcIbW a + ��YAa4iP!܏:hJ7nh}m*-0fL}\^fhmg6`Ulw �Vg� _i;Mls@mHkwQ}`˯4:$vL)2M6ӛ62>DT-h:'" ,;joiIUӥ~am{rIR {FTvt+ J @>HfV5>E;͑_@.*/zdLJܻc!ɴY`n;T+ZóðRlN/-޾MK*gHWl]+l,RLOF Zdt:G69̸PsdEn8~RCtC7J|oWA9kYo-x˘@m/)7$)ڐ/e͸?s/<= kR0#5S_cSn"i"G "ʪx+nuUA<sͲŸtubӏa)<漆3ΐM)PσZv2 I]^86s�4'0HXz; �%   �VeM  Ҍ� 9|R)lE%zx0樝PCP&"!Y&P>X;/-_A %&8%b0&5 &ͥ5%vC&Oǩ~IK $^[2zxn}W[oa$/S!r@ÒFs]q2lYF,Dc�߳m>OCjE0FB$GC5r/,ϸF-#fKx;_T- ;̉z"ӽ.G&{{mD!5r3y76V58l{-U9nrAr=:Z-(p0P(%hqͥyVqR T)\|?SҞ<9hde$^w,;}txV(8 3 -ιV {GbYd.bӅ"_V`) \^|$~V?CX9ES,$v0~_=boŵ``(�V � �mp(XpY`g"UҶ8XadWo^LuM/Qt l_T&@[#SyO7 zOQY< gt6/F *›GΒEfWkxj\?'gH?ans{LbS`0,,9-;8 բSJ <Q[҄#K4rLJŹ}ހWUy ^@(z(u3XH7EIǍ=U=q9v*>D(T~!R+EݒkoZ{]4r,-,@Bo(xċkrlC'2$\vcS b۪oOC6Jsl=SwjMBF'ZxKEa7cT23./a4jh>ڕ082։'Lt Jcn5A͑`sZP)92F`dtNTJ [& 0O m~�V� _;$mc�sJs&#RZEStvX[ W Z%NL;jg}h+UH4 D,nZn($\WG`*VgîsE3`Gz(�yk/$q|zBt߄"CI~ܒnrb6̧ ?Cg%(QA'nB� ()n~MWfS(hҞLf? ~y L�m~8!]ںrAVP($C~PN4"߇Ы Rv $f5tizOgU}S $1xI,6N$~ǖzTyhj%yô/9g/J..=̏O„xw ӌM8GAUv] 0DiLB%e7hKDX|.a-"hKjs=4g[ Y=w#p|/&Ҩ0ܴjv|DGE�WR� I#)e^/!>덙\*Ś C\,xR.9r{4$ʑ$Gah{QF 1FR, "Vfe@ e&oJĥr UWJr:>.FK68\f;{Y0ԳqGMI5>11Qkx}YX(a߰RSV]t =TwA^�Y/c;L>9EOg<$s`73Ñ9]17,_IJ!0W"5|4̴Ru+F ӳ ^A8bi.4�!{!Z$.ч1E!L!RH릞?6@|=|T+: W9GSfdBz?q̖HvquҬE8Qش``ؖ Og# >PZt j;~e;ڽiÉw3ߤ �V4Y� kr mͦ@e)K(%]Ȍqoo ]S`6QPGI:@LBE[�6+$=Tw8l 5"е.p#ӯ%/) vrףQn={A57r0LAhPycD_ ,'KP:镧A=3QG+ba&YƖ?cnǞ ?/,X_W{D)ƫ ML?DrUC@.{\~}ߓ0d@>"-F=}dtZK*¡> b0ո| GI4Y5nZyq!viyq1_HM{ҕT>�|O|4@�d< )r54#7=w>3W{wSLN[0}n7Rʆi0q=+N\<[ZG2IJ"C<*ES*Ҧ WŽ9FR �<   �!得 u@9XZ ,� 9̎6gq9 H?֐ᰌMK{q!:ӵ$K1q]1<ʎR /ʪ=5&ć3PDUuGwm>^&w5? 6#*.flc,D^ &D6V+S{]>R0w[M.~gc028ghhKSO\q&b,5FES^%bE>%\uٱNC{_\t=`,�aaf"a�NaA"4X:G piG.[дX\s -:z:,' q:ExeY| o ꝬޥԨ1³_WNs\]#s2u|x d7M )o^j7k謯TeiU=~sBxC !< w~Ŀili:zJaλ4vW.C:ͬI 3 �!뻉!vΟ PZp�  P=� rk5:> (V15?nAw= 2:%"dYU=;;b(rM isfht�^mϔ1oJ V^aO"G<P2]֊"t(ǰOhmfrVe~K,u5?}͉er8;ۘҺ ~ ĤJC-$}Y+9.t"6NZdXi\l4H{-33�!T=8ݸsz6+qXr� +qQ�lrt7pFNDa?pi8+.~ģ!,Ry \m3n{"aiUvHlTRO _2 Y_jD7Any2Ҝ[>v K+_Դ!W!#<;Ҵ{mү3[+zz`Bay8iҟl5�ݹ G;J'_BM[ee*X(\Uey"&[S}gfCZIUH)"V~9i$iۇEZLa8iv4zU?ެHR"8@děp6qbŁN}evV,ЭpZǔ͹#UAb!0T /5NeskAZr12L4 "3cK9 ꫭ]�Q<=qR8/%.kmD՞3R �<   �!得 u@9Z% Ƞ[� 9n2;M piօQU \e@e;J; c9fQC< [Z˝/ߎbД~ `Q&nU U*XIoG^"QAu=]BiC5<Rai3[⪃"Y?J‰ U=ZINa|=tKǻ}^@ ;Z|7/*x#wGL~CɡϢ.w&Xi=f,i)@~[nwp)fk"WatHVOO" CXB Q3~y$CAh�pxa;796 c?  V@#ihw'M^9Tf*.H#.b>XU\LhU6Ilق<ιztp֛9-XL FPT"975 SNʜWq!FrN97 >6e$T űI~reŪȔ$Daniel Kahn Gillmor <dkg@debian.org>= �'   �OKC y� 9|&Q#ݗO7X8gkaz*z<_V"uDuDpYTM#:hG <At/H>԰TAr8|d& �S#5d$2I"*]` ̐VY%z z=-JzUQZ%OCݣtlK[ f &MTW3}0eXsh} DT6^Eߛd/ rCqAЇ\ sջq>`>�#Qy@{gׂ#p3z *L9.Gy+ۉ v�9n8(k7Ǧ;uut_,:_/�Qȗ4Q0yRq9` s50*MC $CE{g[س<HS芕[,b]?p9/ 9V0c[j%Q�J&0� 4Ϋ{Z �~f8vO7b@%b:Qp\FR5|cEFI':ȃ2fL\( ;8iiJa-RsuU=fW qQ7sdP[L*u;`\k 떣<mPBJgi7FPb:"J_Kfkz,O6Qj-sGכΈ־Cp(; ȏzI{3&Kߑ<\OuW5̡y% /B! %-qFicԬ(б bh" 2JT "iR}$SY69%;s'&I J'Ƕ,8~ fiLKdPC:daLy %32c:5YxZɜM @:)FO|~Wh-By>`Nωg#6*] ˛o`,Aޑ ʼn;%΂Q?YňF�J<mw� Ik Z6-�X|>G_X[�qOc_rWD�JA9� zI4;3T> -$DmKӻ�<r!l<77qb6N`;$aUO k&VrWU'Ez̥K0L]F:/!%uڅMЖJHhFDm4 Jút+n%kgB(8p3Tmzz9U~>BmF܏nu|@e ȍجk+4Y+>Σ[xl.Zl0QwJH,h^teE?m< dp[u~^O!upq$i %LO5w[pap0Fi>0GdSX\t��ƝLϽmO?socu$!:V6h ؏orILmmA )d szF1SW�RVUɓ7DA1$Str /g`dOm=Xʶ F�Jl� ;y{%+^܎fڟ)*E(ej4vjVHKF%e_`e>MX{dBMdJpnL_+9O-^i:mJJ$NvRsDA,^{f4W'sm HŠP=l[KJh�^cK׬p"=HajKj@{~lL*cz8?5X"LN @'8"af9P1i$(K_\*fBo:Ahi>$2~V`1hUk7?}gӶ̀Bu*e$S@~pdBJ'ƻqu#d[0ǭUQ2ދDj > 3G(HQ`kC4>F&Dԍ;"qo#D ={O\>"f'=` KwEgm:Xn\^_8j�JL� MM�3owߍVn-/H6I0@Ȃx4@ |j6Ns*و3쒸¹{_~O#16!pZb/(^Ϛ8P2J-e1.6x<2Wd6&=Ϧtc}5ykiGT]"|4Y3q뮟1?-/Ɖ4)Ou$SL(aNH[ҖV�YhEs /&S 9d'3BwP ň5.toeBeC S@B-~@s&dG=R|zU~:Z(7se)Bv"i/wy�|L5Hb20h ^IHe4HN4$6~)u_"k!K F+?e(ۭ!VGwg="xov".?#"^�Ke9� Uܣ(� xRy,)ځl> ?�M�wIN_貗x1Rez8/^�;Lw86=\D4AwMDXMcOE7,=\v!PP[6P@F B '(;z4$I} +~1ӰaSlu5nϚHJ,r/ Ļ.eXk9nH/ �jpؼ߲'%�clnI}S>bXc60ۿI^N%U?$/ʲL /:w,棆1}f2:7X^&Z<(i x*9j//2hE /TsQPpԨwZ0A7Z \'ՐvcFeISŖv[<wJn7'DXÈ]4�ȵqL/\Ւ{>h}B5ct�Ke� iĭg �ט4l,$D6aF60=tNJ"#!(wsf3'o sHXEIJ몜k֘Zo}I�?YXt+]zc+]X[0'J,302niw?y%2^5gD+JZq̜\B:ەXy#wakk[OZm̍/[~*s&<~뮶Sd`Yt@=/:<0G;O^0*EaH8lR:?Q OPLN p =P#D$%s SFF>rEVoL.Gy J HT+۳4$:LB_X2 z6gnՃ<C/RUF%! #u+@ܲۯdb@v%r|'9~Zo*[܉�Kdˢ� K7e |7wWoúO)35 *JBo@)`(gu܍ ܕMľHuFxߧ}�nyBP9AWs+[ ,Bص{aat-gkX#T�QVv8FPlVw!i|@~.`F*F˫G?NW8A]#eAfx% *sX?Yd8 lMP{< {ǃ r޺O{$r/#0t;r_I]Vk<o gATW*^[*7ޠxbQǠ$/�3;/*_>?uƴZ � }GɗP\foo-e>unJ0lό5Q^+İl$#ɬ c}QQx@ӛڴh HȓkSq%|(.pxTךyЙxցΟ=FdF�Kd� /We{�e!&MexUpWQ�!/]�9D0px�Kf$� b]qf�3Rq3Q ~f8�ӔzBUζBa`%͟I^0X"X^.D))QG\[tK{S $uH| TȘ{u,,;ws�R'rҪOAEPxm9eb~�YaKңVn w{qouUxJ1'KYB& 08/n4pE'F �Kg� uݧyO6dkwzL hM1#s?gfp"65@9D=сJBvz1=3+g78^ҟdBqnrwD?f. .'åmɰr$1YC6=6Fn�>IݴL3w8ص(_J<7)]TKGI _NzX~(oKvP97p->ntx >._t~SXzǡ{gԻwN'yL 5G;9/:چgb6=t<ѽ ad/{;5?z6"CW]\d1[yPmƑOhq^ò7Pn@]&XojLTn T&/G#}I#blvjPgJ}"z!MOR7@ &ý[%jlD~ݤd`[3K}*{Μ[p~SF#m&;�KhC� ,8EԘl"?3O']; + n8O]=T ݩ1 f1yXD5Uqr̴W!~"Jqf3,IA O.Zw`{>$;0IJ9 jl�MD,z*SRO.1/+0n qW #FXkn >q<0쀀 SAqwYmYnF,Mo7M _!䓞g.G22ݸSsuK(NFB~X*:@* N2!LҜw`.CPp9zu]iv` WhTz¥76ӋFҸITj 3Z wF3gd}P{ݺ"^AЗMi8] ue5Y^A)VB0j2"b-g^4ib7X9mz<*Ԙ2wYO}ߑDn"[�Ky� 7N6@ݧ֔a&d`&0D)HM_v9::̴·=ݿ֭^kbuwAU$? 7_^߼! I͌mec;b9|;>+cWK`d+drz(֧#ڀղ+QŘESZQT"=yw<|򮜱DOmRYɗ#_48he AzVA˅n(0p=#�K}� @ִp�` 旽Yb50R$O/br5XMlnVk=;JbRk2γ=U#dt+O.4G,UňfFyRG<yL*ֆHK 64�L!MῈ(BdSڡ`B+s /M(Vx`ϕFSڜ]m! ܄GUCWO :c'Sa YnK|k{#`QpʃGEנKa9Z.\nS$%?}^$W;)) ; ƋY6\o@>OS69vWP2 4Q"ܕ>FOwr�;.daX5Q\S܌Y3L+&![#[ahLxXzSV7AX27|6^~G;Zavmfj}m)8j<�lpa\;[sȉ�K;� P*IF!,>ʀ hP] }2ccZE/^ծ!=¿*\&WP#_4H4cuXmNAwB7%(]^D%`[! #Jżv5e�lvɲ,`&]>]$S+7ame\p*ND쒩W}ƒnvu^Wnvd gj1x]HiZlf4twp '?wT뺀OmvVSg}{sY'~4ƱTcr@̸F{,d.% m 8{)H)Up1/ՠ>ϐ0zY8L+&zI=-O8RhSIZʚo j z9ur[t5֗|Gq9\QziIFZŮ8ۢ;ϔ&7CƕJ啶sTCވG'`<|\X5ZMu]�K� 6ܜ}PÅ<1x;c]NIƧβ=i5[տs$RPx\H,1y/kX}P7q)-v6Z;7S1(w5mjCt'E{5аcn[Yjgv]D`Ux.!o$ nz�ȁ Ff04;ϧG)Z 5*R䵔vm`(;!3hg1ۚ\{׺6--F; fI�@kBK547AqsH7# cEBS2Ģ;]2S T+Z=`YIT/$Ӟl*sf6mYG_Nh_{⽛$ JQ3Y ^K@' Kv%vo+4:&sF͒c3`دR<hdjf~>q!ఘ<VD/z-~P<hRe`.R8m0V5n<Uu!/s�K�� _y/8V-_N[ i@0+j9KN9(f9~?*ⴄT\̉aRCUbb( Ǻ?ډ(F͙ip:2 %YxN L*e+6x]iReR5 >q-:}&KfhDl V:ͤT8:0;ƍN?(MH(#rf+;Ы2=<`ީl p$!j1O{v3p1ZEB:Y ^]gw0s%t3бc$E_jEYYr¾_S-p&:kȑre ̈́cMc~Yn$QI5B�f2U0e QU?'M3n5aRF&6,DD[ir{?_{<x# BEKLnlŒ[~gx@GC"%..ТwJa꩖ - YJӣj⒈F�L*|y� ;.;&�օ(& gXH�+>t;C۹M`ޅL*|:Ci, "f@)U5H1‹k#*J}!Z)e]$6x [[׷IЈsRџ=9/4m*GXJK8,  PI*p� m'p'wM- Kj%w|/хAD8~f֊Q ;u&` AڂmW{bc |_~<iAA%a�ciFmPGϕ BpM=3'ZH=Be^?M oi!aNK;)</>/"*lW%m-85ʪ$X$P[MϠGZ#xLFx0C\š%L59"< V~RgeSpu\!) j9Jl;c?$[e澦_%Ø(87t~7(avl#?\8 ְUQc�LPՌ� AocY�(HV 'Q?&em/fY"$ȴ­㈈k~OJH][uav~cKwꟜmBw9SJy15qdv"LS 7Qyq#E ZOE4B0K_tbF=>=lͭyP>oА<aKΠ7HrR ϿkY>J⤭Po �LP:� 뎿=k�;c[H~DM GESB7GrUn( LЅҢ;?oX2&LS/&)/x"NƦgw?dgs@ahB vUY_߾F$@ڐi2F$ZX^ n ;Yg<k@~XP<9+?EE�6$.)Wqk_F~PpO{eD 3 65cMz>:Hn%)8ǚ1"-df7qKH7 @r} ;kW DRū0sU`9oG%ƀt85!yai#w)'t/͸ٿhZ"kVu ڦSQ׻|~~ ZwTv[ 1\ǣ*`jwn2ESy׫r9} iqd 'ngckDfHkʉ �L]Z� ]2*@v!cΆSjBD"6D;P^?Q9{Xjw$<-p1ULTwӛj\hiM&`ڿXaȎB|%ugu\MNσS+>wTbeP%wrPe)D/t"D{I}q/}qQʏ% ; ~uMM#^6ӱcT+1﯎Jؔq(_ 5_[hؑChL~PᲹ@gk\);�}_I4`x+FF&f"{; R6%K\=> mFc 9Wc <45 M.Q7?_D0 v*1܍:3f/j-OLEHS"}(5Œlh]uN22)| [WA:'g:|{Os4tWJL#i-iѡ;[M}.҆�L\]� ,|1F!$6ϭɳϤ'P.wiz6=ݗְx l~[\LxQdI\7}t?ZQfhjɜ^tc'NW>쒚>|̎CulMѬ(4N(/rzbK5ag8yQL)/ ~Ū75\Dqm> | =Es$]edsWk\xw2%&aWA$~ {_F1ҒYՠ V*R<:?5?S5nY1vm5`4/0Z^M\uqɗRTFNo$fAŨ-͔d#Japk@`4>ml|b%(%YwV&/{3n RLRE[Rþvc4e+X)n(^ۯP߆f2EBA̹4w?Lk??h�L]C� SGRLQS9hgϷT^yZ"=ji\wcM:ߧ5ȿ3g/'tѬi=%BD5Mw5'YV#u�y�q=<C']J[7Ęsp%N"[�p_D;_ %x{\7<Ax ʫJomhFPݲK^ͻ>OcVy;I1tLtq[ :"A�/�>z^jqw4 dn|'ِ#*z99a 3.E,&3vu1`�$ח)R/uʼn5?gDU$r'qi.\s=p}wM[�F(kyUgdrx3 m5�`[(n.%%< qqRƣ^Zٙ2z>K㻿THj(TQe2"Ny"84MsU9Ω�OĖ)N|#| QU�fh6AD q ^HQ�L]� y!RR{u)۠$Zʚ �IȏΑoidC!1~SŤvC_VHT4ᛜoEU gxhu(cʞ w# MIJj@/ӉE&-۩+@_䍋BEpɲ(z ,Zξ'ˠe&*P~އcbӐTW@�C>bM)eeկ&o \h݌hp奀5sQ#y >ġ3)k۫P4mX-OLԕ3coW6Ε?jH4gחm;ⴞwpb7e� NE?8"}$ KcKMR[gmESi#lC"YխGlFFFI>E t7%loϥ.y"<ҝNu~θOm_K$Z"]@ĹVA6$�gA%LEj/F �L\� 3% 3�1gW97)$h�#e`5 %F�L]Z� {TUm@�n]R)nӥtV¯;�Rq K&WcurF�L_)� @ tv"� 0Z2$yXiY,�.AD$/^ łj)(,r�j�L_)� ]18�As[IG̀1Fcti`�bl03ݫPǫVr &jKy4KZRsKHi[/\ Cq5W eb_YJ6֋]c_FI%l>Vf,NMUHG61y9+ӎSJi̵k͋ü?W8`˰V, vX3%֊эO!g}�G=A$b$`}N|ytIbb'I)h'Bo6u7)F]cLw$'ZMU]b #,3R "b3 5ϐ.F0 jqv AU bʎg@_8>5A .K^,eԒrz2$;Ι A� y,do5d@LC`k@TLogFL!U03P5z[{1�Qntgh]Ѹn wo&ĞpF�L]=� =-"�tی$и$@Z]]<A�wƴRGvX F-`�L]>� /p,Mn%E3[(khF ,bd[qʐhIVP9V{b1EIʱCsR_%kK4܄xHQ8RzRY^2:pr4 tZDz:'_(„! * wKPX�Šbuߊ~9_ DD%X5ڙU( SrA $ݕlPǀT�c΄ wͿZWL+*ܔqJ<l @YPFPYۤcZSU9,^4%hʚބsʶ˴9Ⅺ?),3ӴAG1&뢉$XxI]Tb";X�ATt!iPU1qE߸հ"G;" by)_c�';!}XeT#>6A"_8c|@LX KAw>75/g;KYS`tn&j_ƹn*)3YUZ!DEzw�L\� )Fb //n@E=lLgagMG;�谢- _+i!MlRiFi%YSB�ЬctKb /X=xW8!ܡ֊#Ѩ%裓Œ7 Yrj "�ظ_;)Gme\*o~jKN<BU1"E;yO ʮ kz TD3bxk0:Uu_`=|R= "藛wA_n,?D�{ܖOů=n�SW _Ʌ0WAk)q܁=|J"@93,%<# è1K?x|٭F8ы Ф!Z JQ#)E'|B-T:`B_VWW& ̎I3|$x$MM{,Q=dk5Zfc0eL�L^� ":UT± =jݐ=IZj9S +XppE8b[܃VɠwKIV~VE*ї-Vr�xޒL-+o(~w҄g"k 43H*)Z׃p;(߲}6y'\˂F;]iߴ(&o))~<]?<M{=ʼnfxyvEw߾vI}C ZȆ"_rFiFҼ\BM4]Tjxd의f;]2] m *E3w0y55lb ^ȽWe[Bx#q^v,D5nm"}j<*GWǕ(s;zpd)ֹm"WHɈF�L\_� )]"hTm�Fhi)J!'�F;d۷(_%*<ۈF�L`� Nz) �y"2rr7|Hˎ�W$A흍@x Z�L\b� =la݅%Z䲪X'%27b+bݰR;v7iD W=krddEqwKRh+ۋ]nTʨG�w$ ޔi N+4,y$d+!S#J!H&ς " nK=4Cϗ?&3N:;w!F&5/ksp˻0kal0p#[2Y|qu@ ~?Vnd2gͅt# X/Z9` p""-jcYq åI` $1q-$fΨH7P!N�)>F::yxQUhxY1Kf@،M`ͭo? :jrI{uNf'Nc Qs'")2,#mbʲTWGv8Kf8!C= f0]SLIGHGY;K#V+|^u壵WMQ~Y2qICٶ�L`� QH c'/Bs}kSm<l|v0 L=aȼ=~DYDé)ⶴDw3iW3vcԢ>MY;;ս9GkJ'&[_u$$B$NzݽK�[ MY jE;2|UZv ߉i?,l;#;+aû <ѯC-;#@/0^+ nI 7dL)L(UTތ~ܹ(fgVo 4}GH�SWL%,!I59Ԝ & AA4}!Sy\Fy@w+ݾ0R>L<ul)ǞFFICGC uTlJ-iƶ,w;e/mX P�Y?ًL!%:ꤺ|ZPXbj_3},i )Og. gg!wՉ�Lb� g:ے`?%Fn)``W\(w;�|ϟIF#XDue:jUW5YӍq┪n Á=Xhr8ю!ͬƲ91ko |y&HJ US xX~Z_u횆B; +e4+ R48;�* "8>/{Can9]-^>� EQ .B)>$`4>nIZ勧Ƞ̓. (.Jd<.T b/hƾ3 @?q=a`^z7 !I ]m1jF̪r—8YeN5QDŤ',Rz|To7.$zs]G|\oBԁE-=tt֜='kJ�Ȯb{|HM7qv6آ[ _FbРj\qA4H6ILjC Lމ�La� w1cw]/Uv j`!܉1l/\$s0+tuHlY-Z.KA+M]e;!@g H&).k r"!e|rZ:6urYZy^a -s6_@= Jބ[H-_ gTNAWWPnaĶ*e.HSkr].?ߑ{FAKF+0˶6)Fw?)d/~ N \\d=b/N e>\{Mp g̋cN|(e G€1-xg ^$f_^LHޗԟyjaZjwѨ'…Fʴ:<4 :l^'&) &A 3A[߶A !sFji2Ċn0oOG\6'i^Bs}g?D�La� p7<) Ί#O,fDs8kƎY7ל{U޽} R =y0d ف{PV 4RK\E{g! hz޾X>EEGh" sMWC] i޼ <h"KK`9G|WM$+R_E!`iڲ,.zVcn%:ekON $V,& ,W@K7i $=(!"m=xM#oŒMJaV@R; }옘mek<a" W8I'phwFOA;ʴ+| $ER=ZwOuJQW~GlFtw,j&}{.HkEz62�+!mRq_oq!x 1grH{]Nab~-О>g1ĉ�Ld� "%ǝ&c }byfy䊡?i1gލ$ hLۄXb|`R!t@žR&m|,{T 2oOL=(!F\sI%p{q3Ihܥc='detG*1s•V炃ReON?cbsnrR� p٪5#sJYMݷ�K 5 /zֵ6%Iå&].f'=e.ܯ{Tkf?ݴ$#]ʹUl!-Ip !pxÉWYhѦ)VrJ n$cEE֛VY=R�QG*EA Z}53I]R=F]#}"8ʒUg-_AU{pc,:=x'&ǡ4z%޸t +yt?,Ep1F�Ld� yx?Lu�|>QxZh1P�F\)@<?�Ld_� K%7�&� = F�eyC_n ǥ6 F~WJG IJf8�bֳf*XL1M0,M,u749o gnE▩Rqf'$<Gcؓ)1Oxt @֡xH LWDTtUsd> SX$ #O)ǖ5g!bC[ͱ|e tYfJ'uR(cA#5-.DfTWXWft4QH/Ƃf%d)ɋMCnwaĒG5V9:%#~XjE~/W7h}(]NzMer}dɶ;9ArvgR\Qz<.!{F\%Bva@w҄GQyݤeXIBs,8&.x ] Cn-Jf2XM|y :�Lc� 2B/R/+X.u 2:m0w/SIͫ1]RP)S˦ӼYŝi/L#73UY"V4c%o=e{w41#]`3:l c4[{}9UϒPLpT\'?4$͕D#zM/v䭍T;##d4ѲxSd-MSe7T,wD!iAW"Q|tS~+*8[׼Jm}rBӜ6x1r\k)jldց '~_9eY~`}�57̓A_q<le]Qqz_)|7M،xOM%لo֤_J%k&ܫ0ا< ˆp]ZQjWs+>6& oyiw&v (:îVV}isGePF`�Lcr"� -�_|9GPCF iCP7ãO~!6djÙ4W߱ĉT sMJM M~oBN a'n~@>~Q cu9K�d)TѝaSPE4�i&(<܄i4OE)]V #)9* çhʁ>U%F=sp/M3DRGn|Kfriϐ<G1;134uqNlo·nd ;wzI(;|ze~,w㸃glaP l5_(xHllhi^i9ut~@ah-;A](C*AXfO:FɂlC 2# ʓ*qѸeNDi Re^oi B '1Q+0uA P<MtC0EwvI) RޕxjXp m�LeR� v#*V�67lb;^;_jp'tPLR}N3A)}^L1oJ ȭ)9fKl7 L.ݦ -ib 4`lǐO 7}7N/@xw�5 A줈 Ei~:˔la_FLtOjfmx`w8< :|>Ҡ4ѱ}!w+aZay <tRBz9JA0I'Bq졑K>WuxA*8M    qwqJ魐R (}ȇO~KP`zTcGg~{O5c5 f5s\\?ܯ; MC }aû%#fuB84ZY~~<MXkAM܍?NuU6)f1#֕v*|a+XXΥ54n31 9z{#=yE~5'%u�Le� Ф/P�K64%Zy`ګے}llqb>ۛ8DH~(`#kz6d=C Ͼ/CG(IĤUөfdHd%^p޶)1 A_"TN' =>Z"?|BLp%|lM~Z E`a7 G; y (h!�54( -c%N9G'=;W`Þ:mMJݥ~$$R͢8xp%$ok&rpj/V =(N(#|B(t(3zxɊL:ПsKB`ջ*oBQ2Q0gܪD{IvRpzNz wO(_v 3IdqҮA3,Xid[([R(�`ѓ^a8lQ�]bZLUy_ߙ=Y_LGJ`Bq/֟߭mΉ�Lf� C<Zdj<wnv=!%^9Ρ1Jjlm|x&Q~Ѓ�iD!Bo/>_-6{Pxd96 (g.}|X"%9 uG&č_2k<5 tp;1gUC=-_]r-Za!Dbhe&,ɭZС4/oǁmZqT[\4q.$ȟŏ3"eE+,֏s2<tM)UQ߳؉??4d>Ё^6q@wZ%"sd3c6QNo@WݼOO5#z]bL+ vR�L}mƘQXo3ABV"믩fɊA/7!v3 {u@[Zz mWK]O{PYb͔s%'PCa# j4k3 Qz tǓ�L\� 4Tӧ]֝�?,ɩCeh!ԙsԺI,\t64+lm-Quy!9Ѧ"ُe;5ڬ:V�wWc$2ڹi !0q_dg9q4C]`-_[_[o,oa*`@6ώJX #tJ_Hr#*[Mٮ <s$L<F6܄<sb}<@ir^)8 |l^S_6<9.!/ ^yܶ^Y2qP.پ&#)D\IPkEt!r묟MtnXWvXU ,F`$+Ѓ>y{+tƀC\lvӟ9Kr!_3֝tg~ c{uP R-B%DuķZR4 v&~<t`N*j0*&4XDR87I4ݪ? {%2[ ldlZv*wagF�LgL� x<4a�v)Y.1)!<p�5"H1$/š<6�LgNJ� FOjYHV <1|ijJ5ޙ2e~oA\`O^@jS>cEG89 lve_:~rrkvɛ*C{ {yD+Αx4g֟&IVY[Fܿ<h]~9Eui1coU8�nƒ(^V$<1(JQr< t_Kw;L�H  :YQ7o[͵3-FK sj F;›j߹}ֈ7!V+/ ٩\YӔƗ_gDߙ2V׹0ښf{1PAefw .0x FP&/n6Ir8`,]^=RnO#AdR/] yJFZm i�4e2PCnCyX|9&}6džkgF.0x xo 8֣pc Ye]۩ G-T,F �LhP� ;A07,7�IC7޲D.]S�h8b qMH.g �LhP� taCdWޜȇ Sc aqao{ťj(ϐ~${{O[xי .fɯV1bK,JSw"\$TX@26NQdYD<*%VwW ' VS-Q45TLZ@5/'Xͫ_aȥtf?Qx&Qyo(.>P;6 ||(OEvx)?:b2A$lѣ\TV [;}K0xZj|<&mJ2I!a`0ށV+Zd=l<=w;j#M�Մ oxl.@2 ПYe@ym3<#ַwǻHoaXIuIY0�:b`)ԓ (~?å0#"~:5*Gjs^m(.-芏41I=>4| 䇷]A`Ft*xmqE�Lj�  Eq>>p(& cCJ$?Oq*b#A;rP}I(6IfecR}޼cq|bz +S_cX t1;IAjE;`|CVԮ@xZA8v^7Z�Ζp>DyS>\?ӽJV�qLE%A0k6]m.tlaEyH29Ԫ{raR g_3^7n 7PyR?_)\t3Eߴ1GQ1Jn`y,BGc n\3[HA7Ž_D甉49p}>1]./ybo-,!8\~( çi>]]^<_ 7y;5q[땽_HC)7$s,E(ElI M*Mk1u1N?J6}͙h ).m04Uܑ`N~PDž^bfj[.!,V54sщ�LlK`� ⿨B B d_r(t/ Y</p<Ư[C8H[C؇,̓zRռ gqPcwnZ>fNjUD-a&<<3[M-QKcT GnW=Yxg(ubuwqI.*˝T^Rvh ۶ 7 낎W&w]]>Щ3im3 ůZKbu{%qL}XFil#Yn6L=2w G^Wc("\4TT[3u�͛-$Iڎ]BP.Dxuݥls z-jd)?'`6LW]cCeLy!MzQ`Vې)ʔݧo<b6E>8WokJ5ஷK{ it'$m DZb.lgFj豽2{BfˈiDC�Ll� Oc s $D>|mR:o;( ǝp&!ObҪkJɕ$YYk$FΆfeBV?hO_0ã#B'M}؞>&p-ҟ\HLB٣^!KD#5#MF=z%$h5XQA8x2C;eE?a2h)̮A<꿈tCl1{bAXiM !HiOF : Q.s cc= h8|zhxmοR$v@YͿU5@,Ȩ;j,cM0U קwZ{VceA$&h TCC+5uh3}G?sfyT_@/ðXOxdL)oec _;Kk<GwQXJ 2l2;p*ZEp@iPx0ʕUn~,xYb Dml5ゼY$�Ll� -hU.oLy�x "I 9 Fd6y %Qry]/$njY u޲/x5 ~5Ks_}ҟeҴ>}4D(O( wlæ$h~"pbڗBNJ_RÙwpp* c AiJ tƻM's7H?i#i_0э\nɡe1|gbQG. .P&[z $o6҈e!eq4{F/uq{Ȕj-ٽ~'2m$2 K eVy1ԇ'a8ٍ'SIU=-Miq fY#͂YS(.KXtˣitOVpc'dSl~sK4AΧg)aCXpґ)~ C0s v)[x{~% ŧ,UZӄY:iD֗׌)B;^�LlV� |Vxe� '/0yg@ھ98=xe0yEOPk*'E wDNZz";Fsۈ^�6v J�9;r :-L*r?FSߥq{bWV'q̞΢ԖִX=:<ISik|$+N[E=/K3GGgrŃr΂ԅ_Q)Za]�DlN,`F\&At e&MoK 0\ |<Dh3-ă6I,z/HhL{]4ÛdOXm,T{ҏಊbdEVQfv"{a_C'F(ty'c༊"xIKS0u/e-e*xz)r=nQGyG2 p66]A,cd^|?c%7Z* :2Hu �Ln� DRxa+];d!Vxe Xɂ +vKfPFaAlӜ=v)~:gQC7YC)ȃ#/A Y >pq՗!ύ#*fa˩_jՂMH:kGk-&y *L1FʽL7W�W=b&upj:#8TAAGaThm\`I5"Tc1xn[ekYyw}"2?f k\B%7%ʥq"/; 15 paw5Ֆ]S+:ذ+=<VwR^g/w_ѽW[&0M8%ÞBgx 4|6~v.DŽ;SHwPѺ tGnL�O=6'fą7ɑ9~ qq7Nkl7B 18:2!ӨԈ|YXY S ZIM~ř0KzVbKjF9ئk�Lp� 1 8u&{EP-GMc]u-rJe60y2%1/TƩn+[jU L,<Jl>ԎZ4Gw{{rB),xpגcR=бH, oҬ[V v\񫎒M > '<,m l0cAžpUi/f(j)ktn!ndρE�ouy +K)Ǟ"?KZv|gX>@?iES~Aak>=~fn.ȝ; a{xU7g+MPi QJJ[w F4XN[y.*cQf!lj6GRD LF}2n D�aَ5E6{IH͋i {X|1oNI4꾀]XP/o7f d_6He KQB# * kvj-1v?Ӟkc�Lo�  ^[CamzJ2=$J,=/7b?&S O~;d@?2mDs'WqF/8nUKpMaٝWy2K̲bޅ2ylÙQ'RSq@6M<!l~UARRJ-@> -cp){yb7␤'!1brc8 :m%>o`!:rG9;TBj YƆA5wFd d9?C<20t&5D>tSr}|Xfˇ�_̚q4?}Rdr| ILKUNeF6#u,VAw z)'\czpx8uGAqx[zFGp+ s]eZD!0(j(L^g! V:JzEX1l\ +`FYȷ\8!3؟|4VN S6nOxD�L}�� 6t~4çysv R [&Q ot+/l3$^k'i-g<Z: 59<KmG bI,q_; VaD^<4Ui?MWe$zLh%cu"Bdw& J87@y`P'a5媫înt-58#:ta>dLq|T>Ž@GIv` kp+3f#k. gi<LTTfmI׻YӴ {* &Fv|$ 'Np^0L|uqsZ 9T;5r: xRKa a6e (#Y )nxmE/%|a)kR)EeN;i�ghIWo9*|&sZ/D'e܀sB<*h<z9%�X<BeH=uf}Ty37P7zk%)Abq3vӈF �Lt+� Z(w@L�ig\pҽ-r:gj=�'9O =`!q�qcH �Lt+� zw?�J\'Mm/θiZT:%8'3M7OܒH8RO!9Kcv&Fk%eKg L' QגEԺ;=Goxuƻ@]G[O(=UH:nOB\<dow,`sXjji&}G8%b2ngeWjJZ`7O(�ˆ[YiK-i] Q+ۍ4FysOZMK7?ڠ۱ PY'ǎ89_}g~H>♫lkFfc~lan`N=dTi MmNamyyMv`BկܔǕM -o ۞cs1'ߑu\5N|h˵aIJr~gJzӿ c�A0k  #RUlox�Lu� $SVppdu_F1Fcd7BAcOKN$M6h<u$B :LjgvpaiJ(nuC +ySYVv1j[ *dKH%]YIl/ݮF7 ;" vKg0Q1+v=kUȍ<x¶RU[' -:TqY>2٨l!&ruvECV髣h r TvfJ`0ѱ9n!dGH4†]%JW7uxo4C> Yw10EsǃsSr umVgUDls(wɤɚcv8u ٷ<bno Pȓ=>/.&4+,hBiNoBRHFwi4f_%7MivgPuTu5�4T6L,(h�0pMGmJDGuJ̯wQOAu�LvU� "v{1�Cxg{FTQ}%5у�WLt/֐TSj QtÅ9͛%287YoR\ޛ,w�g)ƜGيIFo4 VKt{MC<n$9c?!uw/%`Dk$*K1ڟ`!k&LPǫ 5 ??)fhF,<!9X=Cq'z; E0nxC]hɪZ-n+sqNjddzk~ ]vrm6L[;JB�t'統 B Q)ʹ%K$̅Z 09[Gt:?UJg\Fם J1aZwL"FC]S!n}0g9aᤥ جe?(Ѻޤ!|w1g<*ly)|Ė z~2YMUTRɚit<b �Lz}� 'F'pV)Zr%Ztsa:KO(@3zĞ,P'xǎiNF֛dǑ7ܶ_p-TRt5 X70s6{{f_4fꄁG�;/$Iypi "pΑQxh(p"`bPZ o~ϑجXl-Hњ@CGIgWefUl*.v@Z]w.򾅓] _P<4@P]|Gm% &AS4sGcO8R3-+i(Cf\I*g?&^i�*!$xEװYZ2ݰT 4g4"OqS1V=b׌H(�u\w|zO ~sbG!eAENZ$3=:[d�Lxy� 'F'pXg�2r=C&rԾo1#a·iNٔW'klǣWʜ1O~K;?_q.^9FuQ?Q-"GVhP clvb{{Q-0j;ƭvOPӷ%‚}$:SPDz#+4'] ;Ni$ňI4w_onL I?:p+w9FJf(ϴlEq=d-|&rb}PzJ]7$&s6@yA¬_+l ~s;EB/qa J$Q 0-rFc7:?ם�/FX<%_$sf@/,Ӿ%⑹UP丬r#NeUvң>ڽkڑ]v:e1B/B-S̓LE8|mQ �L� @]$i1/$h4F5JjP<<ݵtSjD`|C7 nOiU"b-$n�G7p�fP4HwٕRP5 s=}Cq(�7F tfA/Y/&p/82\j)Ҳ`C*DaC{չ-M&7y7JB$i;EO(ZwU29y=Eg ĖL$6 `uU//-1fwGnW@2`a>c6'&ףW^ q~5S~K0LHD‰;ytaS2 #Ve:;Ae3XgrkVQ`1˰ж̼>2yj=;f!z{D89&j.n_J:稑9q;|g/>ot[wwÝ;O]�  �L� a{Xmq� F+ұxVIP5جPfQlRC[EiA`=kw9'›e^}#Z{Prk[h}riQ7EqpPUWM{١ruY3b!U^4ӧUb qHlQ X t�6a\9yhel":UQmji:j_Q{Cr]E{P-gCo3q3xḺ*+.�,#шr~lؽ=pU"ȇ398L9l+׸̽g3 ƓUZIjKG!XZ*q�ҥcYy`o3 D }Bgf50,mst.Lɳ?j*xRM3Cޕp@ #E?Zͪw܎K@Rkc@R#8;'�L�  2q<ǀW]8O?U/%t[Miqal/1R0 E%Ex,Q[Ef"*7,p LFIOjA,ƣ^tN 8cr9)̓^8PvAqH;O-IS(}uV#ݽkB+Sl}l ke5P+KgXՎz";kx:/DdpO!l7=mٺ`a+ۼ �L � `7.|JxQ{UT$)[~YҾi=<&,)Bm_@$0 ∴Ʈ-پGx,T"ff]p݌Cڦ!ߌJ Kڥ ڹmiҾB'.YNf.q8_Z/M@14運-F罤[\DӜVW"n҂-lHBn1+"K[ Uż5@/'4KCeQŭ:4@|N5? hTKRBmpB*8?JsW�mXĮ?>iyd_JvϞi ܞAvᕱ4‡yE52IZ: #gg]qn7ϛ 6{ ct~-U(M:Q{S-b3_~n}BZ"q?9ULRIpM6aƊ>״J)3}}�I]d sTB[rE%F�L\E� Pr6X5 �Ehn3Ureu�0-΅PnZ�L\f�  VqAY*!mv$PMYA)ֶɯchfv!\?7,3~ ˜kLGw1VyC]Wχ%8M&� Tiggq8i͎ڲX6GǦ*cA"]珒wMlk92w&2Wt Uo@ j[@y(!.0⤹S<5Yz<+.,oLD\�2KYI2|4Ms,:XLPZ^+Dl*0 e0<z=.ФU'F$_HJ:<8EwPU[:"KڀU)̞̉_v/Ʉm"~P̦/Sna6uSK%ݕrz `n 6Γ/>gƻ M)k Ad 0rs]%_w=Y,xWwS|1v׷Eʓ ؉�Mk6� tJ'j�E*}'. J@J/‹c�.1W!"yd$ ZoǍEmx)R%oq{tiM ).5 \|fB>,,Ab-5ucjä/Ads$;lPJ"k<8U;|@}dĤJiG+S\IksD!=X) 3XCxGɁyzK&5{9Yl|G^2WB!6uͽ5? M,>uCԓʴZG 8젰Qc g*Bl)ICfL^ ~ W3^?4(*[17 } Zj$ζ ??2!9g 7-xJ9՘fc0B>||&Gn<icg7,WMfYE36'i._g�L� ^�DPhSڮ%..90ˆiI[YF{(`hXC�fƋ{s7qֈrɥ` RXo=;> EOEvp#^sI 8zpnN :9+ vթ(쥈7{p#H$!8ky (�o:0hM,~g ^̷*)ĄOR(C�p-);Ql?9!`5T},D�M1F� Bkm I w h014ZoCt wz?qx2IIFAJL+AXaL}3ވvߒy"H{*8ucv/z_+$)a|A2y=c9WqH\-7΅%Kh v6 kLvr* T YZ^dsҰ| Wo5EP܋{:5l^+C~.J8$!— 6A":Q~a�N<� HF5{=�FoWx_^ .gEwP4'i\�AD(6 mPds Fn׮ܤ ?̖#?](2 !K0:uC&B)qWɬT/$;{X3M> ܪϦ@6 0LG ƵY4Á,mQ*3]\j/P0ʦ;̘CP(ax\bLv 2a 3KD-  �N0H �  bh(}%1Y$=adE]Jj \뷀3M'{آk;:lreܧ^>S[]`;,i 'ٮ gL?.lP #y"Nީ٥<iUkz0 eW rgiUZ* jC=T9_ aL l1`S$(?=NIr/w[58CD¢ *T խ/6ԉ>LʇJaޚTG"r# d2#3墊ƒ`:a_kI/261ld$.b j͵* e X`x]8h-rQϥg!0/Qho+Gߔ1:+3,xeiLτMiYmy=]K鴄!rcAcԢ?qʷhP-N[l'"g!@*Fbp �N/>�  ־�φ WeE>gq>"ъ̣tçKTVvs%/1 XFVR%^nPˉWc ;kSK"=F2*1A׋- m6 Ц*us}DafS rra):wXh%hx fqo77kI ` &miȡHxs.7C(t6We#ה֔ދWD50oN >,e<,؏joHpڛ!nRX`:dJ"&` |[TYSI`Di+>#Tד3AI=}hMw�W+To[wcOb."߫f.7^wط'ƋF<8IҰv.EW,P xճߌ" B$IzK!V{p'xI$H4mo ɼf�N/;� xocS�k +$fGj[2&(@t;qfJw*cI,ݒ9 [&d{K6C(FqʣbEԐVbOiE9gT*{KvƊzlr\g8P<Q'r:7{YZ$#!7u8�3?SS)(g*P#2'`(9}ܢcՑg3^&@ړ噫K{7w:,#P*5*0_[m Ny$o?CTiFi[@U}g  ;h$ eo=.ꇽ#{;]Ojm,W\) oz<pv:5Crb+ HUC N JT YdVv p5L_3s_A{n^K*@v=#Zt]+wsj)nܺ.km2<>6?R(9>Ud1bh?!wo'](9kZ0.$ �N/http://martin-krafft.net/gpg/cert-policy/55c9882d999bbcc4/200907121833?sha512sum=f33b17c9af515bd98b2927cb453a992d3d7500e9f671966616e90510b9940895108d241648d1a0eb46b32bcbf3251a136a6ee1e2275745e11bb328c14e7e7263� UɈ-g�+މ ߨճ8q [m7ߵ ZZ0}s`hŪL.o�B3:-@Ku /ٳa\gA#�pVF [ܫ|N3rGJOa9WYSs>+<Bs؁\a=(J-"/M8Lh}0[aImFYf$c�A\+BQ͔@C�+^EJ#,I7! n\K+>85H􊴟b5:\SVi?8 }h#�g[-Eo~K"w,5rs#yXh{Sp2fԙDx T )O%4-ܫs O>X9Yu"t, qߗG؉;:$W] ]J6h_v{5f޷P60< p b 'V'ӡjJ,UWX~}7 / �N/� n`c}g~H@ M#-V  2ʨkb^G#a>{H9DY?|1fB<Ezu+ y !* ܌h̓8l2e2}j3y2sA*T8!<bطf9="8=IN)0(8&9.{ S1۷4Hz_?~68_uB:_5ުA�:rSfe `t:Oڒ~r_FfJFEܝwatb9?rcWrUa{:ZyGq/|K}Fs->5q:Nskr84Kf?mKq!5A}KquhsAI2;'mBGSɞ0#sIlVH||/ Anb /^qJM {{uJcżmf;7Ԗ17,c#-[bzRqe)2 #P32 �N/� =%;jʲ;'VCT|gڢ*M:.{DۡlH^#^?33$zChWSA�p0to":cM/K8fYZxtuxT ,.Q|qm;HJ3SccDQh(kH; `AMߜ\,k]Q+ ?]V"`X]Mr«K}}ɥHT}y#h1t(!e^58g@d)[̢OGމUUbfhM̕ ,^z9ANRE(C{Or7/%if!L$9ݮT`,^cŝT8hwnݘ|b٬_2N5Θ=P>F9<s~炛)T_q_{u�*g٨曌hJyDS H6O t_8.ɷelT虍R4b#d*h20F�N/�� 0}VG�VK b &/.�O`ۜ>T D�N/F�  �luNqSl 2wZPՖr!S.6iGnEhhtI7>24|ݩܢc;o!�h_u[miK GW!QC?{x_6AK1.P0?2S&*Lt qqx_ij(%, pȓuW!y^3Ro$p# }(44+;?BWn\^A!u!-*d0X6&X([f-g%J|lH0E܈-=rkKnj˼)rؚn'%_9J5:5h-`7Ӌ?ɢ»%ΞRSX߀e)ײY t( |^>!lJɥ_kS'_U{]Yga97۝F:ϡ,�N/,2�  bh(}%1&jm3v XZֵ1Y"<i(N;{"N^/Sw̫GP {<2CjrhQ/`&pF%UrGV5Hx\K,/)|ț@ڻFLk⻂|6F!z -%掦NB5Z}cl3Ni[!:rԽppOl_(rR U4p ,SԈ L0/:㶃JNv.iT1yTw[K c$o#yWjȷw%6o+N;0U @OT!k\%HopPxX!_c XfFOڊD! je vnD9h8T qX h% n-4섖txJGM9Z]p t{rWub/oye˘Sdh?HX c]4獙�N1 � hx >ҡ #Y =wVvzT-|JxE3BQ`VMUՇb2d%uU+,A(_x^4\deiM MtES*T6Mr}4^8#.{CV\ faov"r;_SO&otT�[>֤#=%PנwKS D�,}r}82<mw+W /YhگTH@ccJt:E)wq*s#3gh_~Kw 6�|VtڻGa2N^~Ūu Ufg'>G4xi⽃ ^(U.+kWFVf?j2rq̕G2m\Hv7`MUWb]xPA9;`/FU̦^HiMv- i;MY#%[kv SC@Zeo \KL_\M19:t ZS �N0p� :aA@/4:0*E[g怓NJ fU QթԨ~WFBU6൅njEkrwS p27XⳅsN7[{ڰNfE3;"lXwD Jm DLOq?gJnYPPlطS+Guɟ^pǜ@4GO,b.-}~dΈJ% 5;Ț>WR#ޙxm rCho{ۢQ jq$HeH{T adI"`Č&WyؕdȡW/ڰĩ3-uwXEwVwBEPG=W=V<I(?{0`r%I^f3\ @ :bz iI~?݁\ތsD"Sfɦ-\E]/r^ `b^]" 催kclKA*"ݡ)кct iaB9F D`s#`W gE&X&YE�N0j,� PcM:)c,@h6`}M &XPp }H~!f僄IyM40GxxL7ɭT|/E-ks9!9H+Yߡd𓥝hEPp>hq-J!'\~LHA-r#fC`@S#k"5EOЦ1o/Ñm]14Pf 8|!NҦ )^~͑4޺Lt脜_~a߮17e_ U19i] *]6IB3|{O S%n ),)a&^d07iub."Ӏ>2b}asK )nH_(qWAӭh6CV"iϒ*ܩ ;nKQ>])^$i/tյ,=+SCRErRXMxǚ{ gx7 r?vQ~QRC]{iީm=A=t,4x:4gj\S4vq+o fuZIzH_y#0"J1'\*{jp$K !nvԲ.'2S^<nXtGo> R^Oz%se�e+7RVD2nl=�λ fj?v\Öf�1(0yǪ+k f 4zx�בmw*c%eӋgDfw/wioUو7.N+:˽tk+&r-�5gmVdyʗi_Ɏ13ܓkM"vܔ8+6I}0"ϧӲ <SkȎ/dNaQR #g,ߔQ3ez4hKcÅm`fΐЬM$^ J(}^gתl.Vv".">߈Dv2ԉPj?䂓9 FxY @Fؾ[?ҹ[f.dF�N0j8� 93rl�.t>9}΋n;9e� (]oA^<K"F�N0h� ʛ#�TlK|�EN�}]EoǨC։�N0h� 1P<mc-bԇ�B i%@)AzA RJݣLSɴGHogO "2D]RFW#+)Mt('=w/ 1P*$Fu> jD["Ý) �;K) =.0mQp'<mlro{#A?VC͗k??̭RPsHΨUG13U5Dä^AX9C$:rˉ8YwrR�'q64 w!5mh^ nf(uD:^pZf>m8[?b.́ڊh-b¬pg}kB|Q%ѽJ"\yڃz*Qk}S}[m@ʇ<&B 컿~ eqvkNaD҉Eh&qWC~5Tpܚfg*G!�Vٿ2V!,!ZrfqQt`B2<ل$ŗ �N6S� 92߾!DLZ܏v %z0,n% <rzg-g&5 ݴFB0y٤°np=0 zQ?'<*B)0SIJB߄h2`rH�xb("f#[.,!-=VCo+pvœ*E܆R$r[y|ep 8`U`rmi_T1D9q\ ޙzlE[JW"UVŎO(vO q]s=HcY[ dTo *:XXrs4LF?釤6WN_(Q1j% 1X Z&r 넼t?pt lºIgH0}ӌSGN5'JѰ۰uPr\YU(R!S}HDˌ 5CIAu3+ 2-F�N81� znp�""E"{ZM}-/w0�<_qkcm8�N8� 0{z} ܎b⤡@! J06oNFlz J\fX9&F2Aat?;p: I+,t"e}E㙁{L5ڢUkTgO)Ur\|�N/� CWy2W�%o/3˕gl@_~N6K d)AhPס@Y`oU^ 3[hϲ@k(Y6Rvvƪ)fǝ YKV_#:= X``=㒽F*Pcg8ES%W GRBzxӠO7bYF-?kKio!_ε` ɀ(__K->8x S>z2\ SqI</r[EzOjɎu8D_n i>6wZD8b&9!2MIuz'^^Rt-=vqT& +"Wm]ZƣX]~Ԓ.Falw0aY\рӥM9gK8)ZNS\WygP2 32oi;נd2O6IEJ|�N:.� 6oC_8R/9d b4_qyA׈rHAZKC !j9~?mOa씇&!eT'4Äα^U?<!۔ P!Z! 젶65aLqW HWJ-YUфi?*U0l ŭ)2r$|.[e*<5\w(alf_E4GL6m/ۇɳۙ0:&L:@=DQ֯!ҷnMjh<ElX=mF兦Y[iޔ~f^{[b?sqy wgq{,a$)u ^0'@zgŽo L&t&F@s:`yn;j~[B1nhKDznRF<P㡻|?eXѴJlXYV@}q:9AI\�$1E�N:ʞ� |%w!r!毚 \~,zY<$5*pZ5_ Goy8刵.]s;=-a)Vp2WBb S OQ=pl<mNG.pu,ѭ1xK7rCyF4 qcDd 3 =IO|l;7-%+"@)"ϤUKG-8/Ph lHI.Dc} iI\oXBSԏKj,yخVF&ڷq|c36YSj)kYO1;T)juBPf0 >HGѤ솾yUy&{û2 <:˭ 7Zm?/e핚Wm?BE,IpXh"vZ:'1 {5\k5@XcAQQz2]p`Չ�N=n� hl՟aIrv"ZD@@$ YQn4}D|i*$,_1/XʭC%t @u_*o? �ft2guvm6`X6 $?_,#ٙW,v\P%.W4&2:PzAfr24q&*c9 ko"ze7.sA.K <<uĽU^gojS¨(^JyņyMb;"*?yZRDX:k[mSj7sig~4sXE@6 HC,;8FiJY۾f2?S0:>c Q=?_xd=}u?k!l.m[+mfZ2F¤GJ.=y?[�1C3aH\o?S~投xq LWmyg=!Gr+ bgI\r*#�N>'� 1S-3W8�m@F &M#][HVOɫ/?i4x)NS:e.=Rsm@ 򧭧G&"Cu}ړ״MH'kDŽ,YOIi ػJ yXjbhc`);*qi~٠ TٳL85OZ*Y0$CNc9T{`kO ;JgKس[ȃ}󑬉#Q4h\J$Xcg;ȤyC?ޣuWծs0cP啯0D>abtmWw)Rloq٣ a9ЫZD)%h w%1;c6LEԥ֮@W9NwǢ#ڳ.E?v4?q{Xږf9FJ00x dpN(m IJtvxMSsZխOljc(?$ \f�NC� 7"Rdf;.~ۤ9!>@Ru*$}R^ۯňoG7(26fN!0"*k@G>O$KH/~I=2a2U=ujbKQRYwu}Nuk9W})c=Xʝ(SVvQO !mAo'_&QZt-2dJ̄ұfGYϷ6 *!n@ag33JѨ>TT-0(9J:TQǸo#"dysܿ:%"GJT&k**X(e%S<g'�Kâ~MSdYU>FB ]RN"Ko߭*]x:A4![�V*6f|+8&/O㍸3%eM䌧&CȪ#eSwtP۸$R#t%srR�NFȩ� WFya�0BaFm=AQF+_dNpU 1&˜K8_B[=~[M >U/M#DΓGEV`ϼdR eTnj5n˗-cakZ^ھ[aA|)(8DZwRh *N3hrڜRX^ @R<e(hX{k1m_Wz;xe3Iz7TjJ+xטO5!H8eZ=a&MyꇋT^R+Ar,b5+ׅ04BTIo-^<A **:%vοT%f;iJ E:32'\KoP xkz:ب\ZчFZV)mV@ EL]# }>Y0[w١{@D{I{ ezbZf{>Q̣ 7|s?9T`Eʾ<FfuE]vx'F �NG$� ~BU̹ �5M+WRԤQ;JD �=3C[_5xuW? g: �NG$� гKƒ?I÷ٕzg)Sm%M߄grA')ƣ.OX`YSҧ?۸#C5/"_ O^^|R0lˏi%|<y 5w'B0sƓ;xB;Sh_DVJIe@|Ԇ\۝ .3h\8_XBrAm/ 4rf#Xj.ETcw>c7dc\S=!TjY`h&80G^�NIeg T. !ų6a5A@0mw8d1ĕi!t_r"L s<bl=EP1Ʋ;$LuCOذ+Iru.nXzGunOYnz]MJߕMlW^8rZ\}ReDeIvrډ;')y8;ΨՏfGPK[Geug z#iF �NIP� \_p�M."n:w$=y�!2t>7ݲ 'K �NIP� ɑ٫E~]2̱TH=yZk Pd~5C3{@'+ ~8Ϋ3nd$40H~o#AjB F``nj\*묔_m"Z gG_{C%pNcVp<b gSav+nj W ib'tMI&CCà*-s#wԛ]WSB�WBc2i]9:{2v,\elL\ +5A/dTQLShH); #+lm4IRykO? }BZFJeD4Z5בԃ ,YMbCAPc!W̽"|E!NM4M]>6֜LU67IAqEV OAi<; {G5䎨)< ht!%g9sPs1%weJNZ �NIP� tтa"G `M+Io F<I`rjDj2ElKqLږSP]UTQ!L?EJf6qK[[˛]мO4n/uz%=NM^8){BE4 װaD ;b\lFlDz;&>^AL|]=/h9,~K{9/$,@V0dM@  >FaH@L"?WŽ>#ˈ+ *oW[B޹8`Ӓ!#TE}_y:+E펟2.Q>O<xpnDퟴ~�_ߙgUH#!>g5v tj| t6O<aV+C<> G!xsE\i~69V{Io ա7<;4'bg]ՇJ1ϦJ<TaOcA ?HP#M�NS?� 䩑)ȣl` a"+=b\O k�Qs!ڗP 炞Q4n;Pe*;5!\�*VYIMX3qi>oR:(+23WJD2L4Yvrgb p4[>,Ō6w >2@ՊUöBXr wn˽`G!ɟo5CMO1'?ų6y3Bu3+3ʻ=~ى�NS?!� 1a>s~E- ʨ$?dvįZgJU:k ݏO�rA1\k@3N,<8$\  %߷h$;-.Ы?p D-d"#9 ;uЫF{aU4v LB1og}2QJOw1xyjvS:vFτ BP춠xR%Q|/RiP)"@g=eʞWcF�NV$� y~4�:\׎TXvO �5_Բ?d36'ʼn�NV'� cOKUh+I.U0,43vCcm<()Y愺iBu2 !/W8E1D[]S )X+ej?j[R~3k!"!<jk#=Q㈻Y3wv�mɕ1nAh9!wUWG_a�L!uP6\UTLA+1Y *W9he{BA�G=;/3}:LG⌒q ÛC% <v3 'w\dq 0_ U*tPx %%m:<d9kI;(D;:Gn֝Pjd@$\hmhn˽1! mm P 1+& [O�mF2zgR ]}mا |8<4 }*ɖѵWW޳`;vٿW}3YҘlH.rI==f!td7'ԺR_㮱ݢ.Km:�NX� K<-_IP>Ku":gM]HnR!s Q�!BD}ȁȾ/M40}ޫϖP/r*KruP F k;#1*S}32#An 8 R^1WxY#aL,0V.4L8,Kt ) ~oB՟j7-U![G<#򖓆VL+6--F\J萴dDD1&FnJ tUdQ߆2PXF跣Rh hoP!Ν) ɒ_ A'=Ь*|'k~y񇄃9M׷&Y?7G{B2t/,EL\=Փkn%"¨P.,% Nn'7c2Yj)C&wHCtdoY W}ocD53PU_ o1 (?Crv;$ YY7 !�NoF� *!z _J"8 }OjSrH3<}>׀\V)wx0uۆƐp ?�haJRzN >39S{7dYׇXXآSEV`00BY[vNjן#Fܐ7)s/3¶8%,ym$4D'yT ֮P.S_\^ RϮWtJhU[O*>/E-cE�l\v} hh8J Z+Ѷ�+wʈ $i 6R׭>bRu>Mt )T24sr⊠*COqplύ<I B&mkJiiب ]^/ r!Z޼' [[mZ"7C۳K@oύoK1FHkr $y) 7[Vz=W^IK$G �Nv~=� f㗃/M�1: H"/!MΑas+T(m(%o`E-޽Җ `C]QފLk%:lc,8w=(طRpE*uV/PG'MoG@ jONjG A 7Dd%>WR%*)+7j4`CU$0.w]id_vLV%hD '41YjwZ¯o\QFKUELKo2F hY̏s *zc~rzшc~X;E&8c^ 762u�qUuKWQPl&DHӥqacul$_uKj>yLXAk\y2KH2Y_I_KypKQogsc'`6Fك'M(&JJ}RF1+9TI%�+u 8d:O9 )zaC2b oJnUO2RkQ �Nl� v+WxBx�y jm0PgcI'ch}SdGrP}n=p%kzzX&ǿ(J}\+p(4,:ٹW@At hT_p,,00 ~^s9]OQfvϸ2qn3YFS4:쟣.I� LJ [$-7^b2ܔC2z3& k1x1_m` J'HUeYw'Rfūi[0Fv ^mIJ=2iQWI.3gl@vGQ*o'|Q3P[=8MH *9Mߪ5DZo! SG:OXe |Α=3X!T2LEjAE <ͫm!}OUrCr~,5e ? #\ӕ+;Ay/[1L!JY\ e‑gئJqQ2�蔏xr �F�N� kbb8GZ�/mqaL03;ڋ#�=xz"en Ɖ�N\� Jd8(ٕ]pBb--tD7Jmr* .ϫNU:e(#i0h`ߨa%$)&#M8q-IGFDR]:!ȹvJ ʍίpݐ' O4mC \B[7b !rb#&jV5;JEt8ˍO94#AkQ�6psYza{n&1w\Fflj![ea|*E�N?� ky$f>qZ] ۧNJ֥5H�±~vLeQ̌w;9G X)S1ɾLrIdk-2)=mN!?H3HQrӨoK$m=քG- Q`_9ἐ8Dv 8v9u]"lʊhj{9tؿ*-!qV,̙f#۾|:|x knΛt#mGQPQ#\oͮ۲'\78 ? ]ȩ\I. *[I߄ȋ3UU-d~G5<P{& tc]P9p<b9uC w适^zW ?EU &5Dt6&k{N:7'2C[:YݵH 89i8?T6"[@y%KH(&lӼN)+?)չKq3H ƒ7`u1?;V�Ne� ȸx*f�'us.r"J>X5mb( ϋvQuY`P%سNva}>rOpX7t2*+b_<@!Wf|6rġuL~=<P61hU*%oBnGS΃#Ezc&J4/Ikd"_˂05CY yDy1X IAvExC5^$1%t)Bu|ݲzocn#6! _वRu<Wq(6Ңjr4O[ĩa{ʄ5sɁf2ԤU2f7w u{|xк^|> N[ڡd>�Xji?aZ?[G;06=V?f%th-4ƿG`j7G$l![K ѳpyUJ6#ӌnNqnRI[Zc\ ?nK ^ZL1ډ�O-� _%|0]J � #rũ Lq::YQ*VtJ+U{:x0WݏPnR~�PUy7=h<y*z%Rj aEcTbo*;HMe)ߙe7!Go7wGyr9`3plkrЌ :9ޢ1>3ك�d}Q26hu:� Y(g&~U Ty`Ǵ,x3S_r(ya&,<)U|W^: HnnځJeSI? v {R=f\rO?3 atⱻC[ma\CT%ʺ@nKH[meҿ^zW!Ǿ^dO?Pd`Ȟ)i5Hb}M~s34C_|hɥ.N\j2HkÄiUʆ ZvLIwJlc*5|pݕݱc1O= �'J%i f   �� 9x "[I =ϥd~ O?~*0l5V{Ï& - !'hO28E15 NdzQ.J0\S2<\A«m ̤j|]ft0Ou5*r&gwTX%Hwsry9Y$SQ޻д: 2-沵f)I8V!}gا`Xǀ9@ N,4 Zgdۀ5W+CPwoi5PmWNplK.u,m@W(m{T \e^[IT]34mgrU1~%9GK]]yG7.(A԰yi"Jj� P^tZ : BEMLⱝq.� QI=ʗ,+)YmSc6E[/w46iKhI:ʿL*F?G7*4WA}^T-ρ^f[UrZga</NXӤ gXZzW �Of� #>,$;&Šp}Bänv-.ᑰW4 O$Qd6J9}zaN;{ɮкEv<QIo B>ѻk /0W4'=>Av;N (sT/HX{~Nѓ긷!+fi0+Nu-8~}3ZQm^,(: /uR[(8 dr8 `uͿ G�Zr4̀PtUgby9/GT=Qe@թnHw8jR6}2VM"5i ӟtUDmu(yGG!d?P\/ܹ uM'K�~]M:Sn@F$s`qO.# P@_/%SkRg*"k}lX9L-�])4;_wvoa+mYU3l\ܺN� pw ǑCXF�ON� q9p<�Q6/oubTT}֐�2P/9irv|y@c�O� F9dubMhII,rá7ۈ|%cQ?uíi!Lٯ0_5YN;'$U ̀Fp.f~Yb<\@Es"B֚W/V2ݻW `;[_z}|?#>8j=$vjN`9b棼 83-hq E)oaDJe*H1t͊mbGh"lw٥hpc\ihgZK1A*F4K3vKְe@:su'Ԍ+1i}Pw6¥Z3A6AkLcv<yqpZg?DNep**rg%N&lIa=cUyM]wKξ2@0B75i3qS2ɒ. hChxTK݅A<-pz#*<F`E-t^�O� FFۃ�u0)U QV*3Fxߢo>d0�K$v'g|qY>FPZ^Vɫˉ�Oˣ� hNTo1lI9ݎ`J4e6*Xzb/J[t1iđ_?6ߡ#Ghhǒ4i . oF)c;^ g3pj"vwo< o�秲yp'0,#"@$4*m\䨬u]էd{6Gm$j؅5(#g12 �;ں`M{0VXQށ-‘W[#6]0LxOy0GԆ f1N;KM ;/| >V$' KtcRf?|Q. R_&~'StuZV6y`ՅDuξm']ikodK֦I-a;|)t^xȳP'qT ̧3*qEuf L;ש.zZg]2,n|-dG:3[Viv.cs*7),KJ ?&-u .7t�Oim� z,ۋ5 5ܒ?{f3? %m=�[6jN">=Ј?}izȮ#<r`9|4H;m19돋tb  H.qs5B?EYOXcVj,�J0Cлxc&~QapSLu18iYYW$9/(:J"\F?g#ͅ0Hd[WEK$`gW%ݡ~-Z*2k)ӻ3 gKR3ͤQr_!b 1_(",E`?57 UjhiBh�AVa}Q Cz]3g f<W jS[oPC86Ojc`QUW@_lLy7_^M0cPi2B&='f}G)bQN]\h@KޏYae"b ,i딉�O1� up<.<55s"{<!N)'C)*8uWm(L|?`hO[no[}~ '$PѽO.V/J1HL*-LEg)6*\9WqY"ݝ%l=Z9.=3'~#{/Y3ki_F}d_K۱=KGob3;w3P?W얲c1مơ`sf*v5v;~*/k@ZUa@N {Ag3 1j{&ƎHI\1̀.Bϲ2DD=S~~B7 zG_;_d<m(_rh&oKD 呻X7X[Hu~tMS;<q=�u-tFDP+.ʠ]f!n37@d_F0H+#Ag*4:[Mqy=VY) <�Op� o4?o].fy {�ph(yJҸ@)rgнVU|C^d0OD4sD +-+.f3wI `cѡ^9IŘbݷ4hIBg+Ҹ8 6 CP=&=TcЇ"dEF?΍I5ȫ[\1 u P@L�x׊jY5mQ|IJzc OZ繡c4^$ǷjƓf3╂!mB=OV@Y7#N YQX1cwQ_?/ϫN<dA[QK}9тE.ҼU)?g$Cz@ON#JP2oJ~e+.iIq0 ǡjEZ |=l?WA]Б=GD;>biǐ|=Ö/I֑a@4-!(n]cvһ?�P�Hl� U/+ �z8 `BF9>WS Z +�a%Ռ 1!s3_rEz%h肂?2)jcc\2dgW֏ǨK˽!" vHTp=άtG.UOTesa?"06!c!Sq1!E{thb(χ#4s`pniN~2 duUX]@IޡV[cJ "36�u#)>גD{=gneǛ\{X)1Ϋ4L/īK<8`ʷ4XjE~~7v64{E:BPǛ+ tx *FM b̊3uN);l=EiH G$ťKJu߇.$3USc ˀ3b y"dN_ReARKFx) �P�n� u|8$xZMS:Q>{ĸQrTWw b@drg( km37k9^&ӿ<8 e_d&!#'+1z^p6>Xߜ/La<J#6yHGa,"R+ߦqBNjIX'@0MBr:OVohD_ EXe+t{!J .Y %�b6[^rRnI?o0.�)D&U9՛S�P�l� {5b BңD>+�rBk,oF:�71 >!8ޭAFOa+>Ӗ Q#tsO(=tYXyX+F튩V7ۉ,xp`I#�yU@ VmsՇ]w)v}>=>:$YZ{Y /$f ӝo)"gMo8x|@5˥Y@� �P�w{�  d6A^]XsKHdwLiMN?|Fa*[([rWEIAMȇN*LUtRwkF4u{+}EQ[W,>Zs* 0ic<1ao\B]:vzHܟ.-n X:�byq%X زn- B4+TUyXa]հ鉵<{sxVȎܴ aBؖ$jf�ZƊfceZ*g'[w :3׫#'<KC}haHbɤֻx]u- - t60Gmҹm6P`vVo[U4R7W9<Y RiFe56t@w}K|6YybM0\ |;Q\(:+Qc{4AWgR@*c dJթTl6a)K٘XD[x �P � -hmAr3m�I71{V#@ v>.o<H'J\"ϻK/L` byê-[ TO. O=Vj/kƸi_4;ZxVONX)vjdrCL08RPb$v o|MCLLAi{uM+ԎA~p`)rJ8V%ţy0ҵiOQϯ*RezؚcMv8TݹfT4̓`'CS! U>`Jf f7kN>W['}AĶRX"p5~@~NZ vIv֕о)1X"F/% *~LJx7'oζaߚpC llߙy@G}1A$]M8 l \5CXvk Jh>40Ƥ,_즛Ԁ8gGH sHI+;ի[ j-]!C#̉�P?�   *ef�OKiWϦ`kŨ"ؾ2: "&:TДǠ4Rvem ΜOֺR{N2p r l/ebҮmmU \Ԃsm.L/uV4x:�'C`?\*s#;lC0q"w(y@ZSJ\={GsoC̙Z%XlUwZik(npP,!쭑LuB `qFg➙L'$�;qЩ2CP3auwh<D&M"jBO$MZTmC;- zcNL̘mZ dX//ܣOLV|Nf_{ػ XZ2[FX3;]j:a:`{lŒ>ia͑o<A ;EJڛfI/ĉ�P [� 5\ &KaB.3ڤreĺ]iy?O_:V}{O�bn#YV<ߴD"b]2ws,uQH瑘]B勏7m~m?12`]Jvsȳk~xȕR*6YYEJN,mv2f .'\"`׋j)iTowWk/AyuD~CzˉMM0;jKCu3q!ܸMc \CjFE ١ MTCE,a=xgDnx2]n-*@-VZPEΌRRU:! QM^7[..AeEZP�P`� Z獪.Hh}D" V +Z)xpOF>joY\D\nuiW㯗V$3þ~IԆFE i#촺AD,Lcs hl@uǻE2XRc(D.xBG=t'˶[D@}o\wc 8GR@^gZ>Rv A=u1*ҹ֬' Yԑn1Am17/^x(F^{ܮ4NۺkJl6+\גڢTC|ùh3] Yq/㕋9C&BB=pp7AӇwk陀kdkR<f'^re L|,.aeKLJ(/~O 9RD:<>nY�Kp%"Vv0u[= `J4S6Lcs_cL@۳Mur>%#hmO.`B^w5PEJ"AIa[F�P� yv;�h'.tDܪG'n�ۭWC@b\Ol�P� ؠ뺪O (3HrxǣRDkL\L?v^ↁZlߓ\S >O=*b5𠓻ziKD ;F FFCX紝omx(ό–3K. ~d7TS"½\ͤn ^rpKIm�bЊtC֛R a;o}\V|D?\1zan4O#^̰?nj<Yy\>lxJ-7n;1ti64`̃q/E\LU 5ŴkF`l匙 [ZH V-xXV`$m<H1jI0kl RuzCĽ " \q} >?ilkEis+^z+" o졠&KA)[F~2mH67im2{aΌe*kaۗkߨk�P.� 'ظ3$^%mp$Wն%)bKZP7ÍJXWIv uC1ɭ"N>b8Hq FgknBQuY�wxz Ɉ%YF3pbr3Xrh? 'ހy o M7oGq}Wjlqzfo_pʨ+IƕfsD �!AV6tsmuT_5d}\)k cH/0�^3g0<X 9] [7~{C3E<-^3`9dOQ|ѳ> r͌0ѫ4ޫH*[ص:`Ra ^ u< 爳VWR0?KЯ  YD^…'NLK!m5u[ր჌1mݢ1v:\ZiGz@fuб76-ʿ.o8u ;kMfXƪ HYkh~- �Pɇ� 'G'�QqC&1$'ͩ"፨X\?8gw{3fytK*W-Jp\^Ib`n-ޕ,|F(m>K}PKiP.0z#Q+wۼLb`ZVL~DxS~Yh˂$, 㜫׸.<\&Um-b?ʹY6[~1[\@+鈁qbdo^{?#Epx`#:-Ǜ hQj>`;OؙdMԺ.�b/$}Hry#nf/̲}0<fӒ̦"zHbI ޿~0D1 @cOq&T Uҕ񬙳DqTPu슶(42{?hwowIVBA>lڊώ!@ L ĭԏJfӺnh1ҁi!* K4B�P� 7+-;�9^r݃d)8̼=jT$*#U@eQNx-/aZDjA#Un2+H^x;bϠ{[".FO p!ˡKkӹ1YF=gK3FB L#>ם>cbݚl`=!gqrӚ9O<Z`/S3kmsȁhw5QN鄒ш$ jAm\F.^$Ъw&b7GJv 'G3! ,qpɖNů"*MQIsr+PAl:;sj}FpIGFt(hsq10Nz|D:b8<@n]6}##PbjS\,D.u>#9Wf|;+lI:ߥNJeOdc{Io۽O|5Q735-lM U!M_$uIeQP4[#/[׋�P� t\GfV�.޾hG/.ThqF�!T6Xj )o}y �>geڧX@~!2̅r[o?:P^la0]FXdAH}|fb)4kA<8mXa %w䐪iS Bl뎟W3;?sۥ2bp ZOCm a_7ބ'2!qu{o =Po.r'X_oD DgDDsI$$c<?_|%ShR< :!T c(gHz ѓ7ӓ�o>)x'ٝT]]G_hyXr?aYQ>fe@)jTJXsl^ LO# O1+7_ɞ=gG!>h-`j-p 9q `1GE|CT9Jwlv1ԚHٍJA"avGu2^IjqbMC`�M,� !RW�@ %B"3׋ ~C&L:^=)^T v ;^P^./H|HB( U՘yWƝ }'4he蔯$a&z X[rRO:݃Nඈ5;׀g8*_D2oPݙdH84e!lsaU"biv,#<EՌ PzS2X#Ml R�+Q*2Jw XaE 43yExW!yln5K+vtH;Esi2TZC4C^ͱWv%H08mȽ7'bd5~w-6AVBht5iJDA7ˤqOV7|ّ- iS>rT}<)+HBߣC-_Ev"՘) 6ߙzZr+ eЋ�O]� xyc(~fd}=!f"Ewh;hQg1ǽr#0bv8*�NJ8.Dam,5 Rˌe& #Jc YjpތJA,K![zRwSBSx:4#6?\`� i(2ن:)w)OFغl37ՍN:*E$NLwܰ:m=R(ԒVijD߇0L1Uz ?* BFN= D^ 98ҍӰ48K'yJ~Xblxu ʎ؁SƯ1ehX[Ԥ;EC\?3d `k`X;N(yxaxPy9WHo;9}jE!Jz:ӀKhR14q|aU2ya1+))2bNʛ̃6`U '>'˜lxg0pULxLӳqHLDSB^yMU+Z F+�R�PG� u-ޥ&6�ȷdZk[갹 v@d K}"~'~⽌ީo/�w# 3ShBACG$ 'k?@[ߎ T-gcO1!-{0JSG";nS0%جB$ҟ ԰g5utx׻v"+袍SAXB^AEX2"|~FWk >zL^&55W_" <zcUJ;:qvBn1x%f>emi]/ћ?�RG+*#Fc'nMQQmI&G)ew|LRpsN2aqPt!=8 c}z'N)1 <ma_;.]]r6XixA贰UۯcR,{+vE|d+ w*!mI3dpa״'t.3 ;>c v˜sBֹ- Q�P� xAӬ*�`bs*UUH u!qUѳl&KjIYuݿPh'd,TUfBID_ĥ\ZLҞWcASEtoy cS6nkQ-qJqzm?ȵoO{O3ߎҳC<ylޅǴ4&%2 r$^ZwxgZׁgAo?vlrD3 $NK&Vlo JGSx TRԁSı~�ɦޝXע#~K#f�ũgF?oگۙ@aֱ~}Q_eJOwpPraPE\e!GALS8ToZ57@{ɷ] l7ie3Fq_ӑ!Tj~#!ot[Jb\,3v9sOH�: Ht5D3l�n/h3b"[?ʧ}@}|u=܉�Pz2� ƐI<E�^^>AUeoF=m pM2y38♬F=F]*ǼXҴfz'"#?v3Ϻ n(߀|[B~)l v }v1h:KzC,go a@v }|Vae>=z4@e%>xfחa#wO,k!JK*Y/m9Qʴp6soe^i}_r7xFyʀgnzAhRH6JxG8t+&Cco+s )՞ݚY% 95P;L+8KjxVxpyVV!7p PJ/9[F56xCkM+@R4;}Wɣ3 ~ Y/ˊ"G4Bn.N/ؼ:ovY P+vq1 N�P5u� rɡ}�6LifA~2PnL9E-qz0:ͼrQ[&Qif^2Gw'rCUo;?�TGЅ_{Im~ |ʌHaJBaiΚiUg/aiD3d|A0e�;_`1a (Oq`I/ee} \eq;t60J*zʵri8g �C@d4%(;h5JÜ%Z朢x3ێ%8s?kke7;V;rB{ $pn"N_gQ]ṾF߹ 9q0XIrLmy�v#*jmOHֶ*Oq/J_0G:ҰlRXU\/UW;4;98Y=[0D6trzD.A-F[1ĸ2Y 2 }8~[>AZxI5`k&`TdIF�P 7� _ #n4q�  k2a�.new:�P� O<M8rwq�ƞoPdz8lh�0-:71ZB'L|32/ꡖNnf5 >Kr]}bGHʗ=Iv#pC[}0D6X!Wo3eaA!azb<b&r,ә&(mԞ11D8<֥$?SSzV H~ yI9t}\r< ͺ � 2?է׹wM,GyBtcZ�[֟)όΖ䨬?_&fݬKϧE5Tq7|$,]lh1Ina7 㖎9+AWgo5oqؓ~4;kzy E>V-ɿMš #vPnÜ1/ t�K~� Bkm j2њ|Jdf1::b֡p<;`X>r@pjҥM酄m~dVC.Ujv7&{0ҭzVL{YvXU&E7a 7i\85V~(Fo'Q˸ȚS8!N3Ϻ.ny4P\=oNR岶^\5qyxjIc4tGqbV �a�Q9<� )ȴıE3^1X3G W?}M<L@j?ۉIF*V<n;_ Aön)֞$U^ Q1cVImYsjզ{ygO0{L<x51^g \4j%-#ـ;)1%$-\HgqD65#JݴYMVA iӷlMoeX~'B9H퇩;^Z(0 JvHJj˷F~E%eG{Ğr}ɛh, (S ?*n*Oycvݒ"6^q]Xkm#zlQz -L]gYC'}Qo +<9&9|jvw Fu2a8\] 4ĿF@g@vH)Gn-L�?Hd:&R5oO ~gsw4J]md`Cm@Uڰ0y=4M=kqp �Q;� �3ukU%I3co DYD4U^|x)1k)tȲ8ŠrEe0%\D_ǦSz K-rlw+Ccmȩdpfh zCp:O4H$bacV=`W4JNrrL_qw |tRVPJta/]ZUT;IUEHt1Pu].!Y lQ:nK]u Qejoˈh" od`&g& 1iK:r!]Iz'&ֶiEi*p#~WM7Qzlމ1iO נ^"UBzr<ꉃKtaˆlλJ2Pw]](Xеđ{A] sk3Bዠ/^�HxS tV}P2R *ڽ vt榣4R }5F �Q;� K+u�+ڟ3+4tc�\u lE<wW�Qi� wfUq< nH$vUJ8)@(~,+ дEG?OUE{'̈́h+BҎ淽*p•wI>ϼ*H( fCł0 t6T]Y,AUV̟6%OcM`6`rɧM0<N2>r*[]\rWNrŁ3]vUV%>.s!a h�Q|� <@"^\.j$e~6bM\qgTds^WR)i8']SɞeĠYcB>8^luo$SyܮhC~�n"}ݶ'{-V/i8{=04z$+#Ȑ1(Rɹ S0P} m<(_H#96 ,!fl%څHhgoX}6+Gqb,Y>sM6ol݋=ͳ JswϦUF8ZQŖ9llgETB}f@ �`[wXhYBNFn9CZ482i:c>٘('҉wB҆}PQ�2=6'2iIN,7b~v -0`i5#Yn "bqg^̕q+"^S> -)\aB5�R5� t&;7[ےv.wHpzT @z]#[Z9{' ƶSª9? Ұxe֥y.k{aUWTCHt`ms!I"Ό59 ϣѺ i,o+@czjmHƆ-uYMF儣5NQdCjs+pJ5r ӗ ݾvYs,o>"j_ Σ9\s4�&QKCإu,F?՗gb˗*Oڮ7@v=ߏOAND=zG~Έb㳍$(#SBdyͰPMV5/f(Fr /Q须<#E+E,t$;FtƢ/Mc0[q[R-iZiwQ,Q+:5 j6BpUe+Է1k?LּOOvn)A/JgԜ,#~^5_Jn SYC" � R=� D=[�8˿OA%-bgR+yo r6Ő\ހ"G-vREyP|,hmɋÈu*;L"eh6:-gw^"8PyJoުW FЋ!xBROl.eШDq4cN #1!"Cv~C@V<zF3jVe{mYU�8rB<YՔOA&�RA� ZB�`])3Eؑ[*^ 5jhZ00=6OD, Nk=D8|y L(neX|WSfT}̞*[A-Ӯ aH9rG7'!dܙۚGo<t `U=Pg|hka49wwsJI�n;Y_PDNWl)`05`';AItq+Arl4&Ѡ|It <b&<$|RY 6b E~c+>}v%/ dSvOnMMy*0hL>CmPuy:K[ZΗ$eVuYhO% Ќ2�,b(oPcY\Z<RkTu}Bx_iZ#Mfz>Θ@llv/)55|D-5clҟ>TD|tC0Mm (39�RzuH� @Q�[dqr`܊a99޸k>Vd5ޅLQ{[ǽBz? F~xU}~]TbE8%!?ru{FC0R:뵍Úl!p㯪WM/103E@2ܬF:xXɖ v`:Zv?_^0y #+�Y a2Ɩ*0ࡘuVyGt |= Xhg,0JK} d3.B PCKS1 Q靃0g¸MKC (?#fXdL/r۾vfZڜkԔ >G2b'2yV-4FviI;i L,!pk/`{j2Sq[:*Wh$?PJ5 XZƑPpTM] 4Sf=QTˤslFEeiQ̷qp4Hzeژ8a҉�R� NDf7�mw~~<Ȝ;5eǴ؆-Gwd.̎ 2<ȃߞTn$k̖c U@dw?1.y.:Q TBK묻H M+2Jj g5u&Ӫ);k I.ϗ&4KeMX/p39>lļW=*0KrM6C}o~tCYqXT^E2&C|OmAg�o $}ͮʨdQ; :o�_u~<LQ ^Tᕳ[GZ$'!lPd g_왪f9麝!~ 0xp\^tP.;F~ V~pofDT?R9RR PM.(3#DqJD"u,*vbɡ_V󭂿J"n4i~8.L&4;OjD?@ �R� d˘ E2tڄq؜k ��Yhlo!Ae'thN[^MXA$V/e;XWd~#Q¿ޖ{ziI-|ĵXu6z|? CF-{RD+ ?<G;D]wSJ!MX7';tSwo_i$\pAgz)uhAhu-AkfZ :R..~^ VpA<J^Q++`yF�S/� Z 'Ȓ�}[qy$!Z T�n&^+uMIVF�Sؾ� -zzͮMv�de>zd&8/4�Elh[e46;R$1F�Sy� ^>�&"P" o�B�!`nd^Mr?vʩ<F�UN&� ůwJXQ ZL�)*Hܸ\Q-֤E�Jz̳qPB5/ =MV�U\� 򭅬Bg�'Ǹ*o ZߑwZ�V*=0{PMsO!y�S� 8y{\�+TO5`S q 2s�<UqPufCEl'>đ0obi PKB\`RaR(F*WbgHJk}Z<,JpFBWiF:0_r4E\t (jR;^ỻsvpw]Ƙ@Ʋ(do"8 jG _<5!M �~>>͉vZ* A3k|+~/N҉�T4M� �^L�&iR\q Xmùy{L`dW5«[/ꦕS^b`7ɗ}mU"%Xv|LjЩLARi Ѝ{Crr_k�e$.WTVwP"$,exD-*Z\ ˺ إeswNj ]={|{7,7_Yvc|?3ߒp!FqnN&~WpnƧn;cr L]I-_Pn#`�T$� Bj*6�,a$mɖUsJپy ͞΄k&u {7\-F*i9|%φ0[8Uun,M%+Ke{< !qO#_4E|F:UBJTy&: NH}ӟZog W19FM-bu!J_ӛO^hK&æ/ހ.!ZR$h @�UC� PKP+^`+cWğGO[v^ sYX|ς'Z5WK[;+[.]\H<9\m)ѓfGP- <v*VFO@`*١@Fb &.|TNPg3W2{ஈԏ}XNN,,N@I- 0ŒW>Ͷ'p^ƿJ Gj߄v;VI AF3:į+#196:B@ "س$y�U[� vB}S  �}vVF#pZ#9zDC&`ܛPan9dCnV1 Dt] bipulu^}o~K78HZ٤LE<('>vr^]10z+b1P}[㷎EZDZ=u@$Uaf[a5!G)/*vGE\0ؾWf ]й5O(dr,=k,>tTi;M<M;];OCY1)Av �U]d�  mGڂx�$K8*/-gcEO32.e )ϰ#pj?ghA;YfȄrP!fg-W\Q�B1kQQ�H({6Luy) w`B>ч`nXz dtϫᴩ 08 X=Y'1)Vh}D۰$Vh׶5O^d-\,MUwA?`SDdF"|NN�Ta3_� ^$�~mX1ù@]o[u|ɬP_O*]Gi-hrYщٰvaƩ#<"3{?eYp>.lӻ%$ ?BS\ol|r7>@51㧞qVp7^.-3O Y<W6&L~fl8CE$ߍ]cETp|a~>#(H>bwf {d[W �U#� ISN6 VlkS9~,1F_61op4)wK+ϔ;yv&TcC4=KnaBTו-E3AVzQ!}1S#`P2BņBy<A曟wUs&H˝ĩar{j YULRi;]٨ &@;Krh4׷%NJ{ul#c~y.@Z꼉"� Q\� ~<kZ[I^\19=JhtTQsPg:CP4dwdyz!Qh+if-6}zA轃{83]k3X^(P#7O-׺]A k篚�M|^?G0 0г`H C拫?)/:IAkΰW[�M Ebj2-t|b=ΝՖ)s1u�U؈K� ϊK < �й4K㛽^ l8|W%HM 0Χ<~9FS8DM>4mu5@A=a1PVLͰҌz3@aN*0իQe@8?Oaup2̄3fgA9FH`c*ƗĨchVFcٟd`-1nfȤm$II/c� _TgsrH`ghOkྉM-; D]Q_ﵫ 43]rXYZܔ 9A P}EObj2R l?.'~ ¡l93m??Ѧ_+AD1  #R}46TS>\ �U � (٦du� 1x$̄`Ȉ"ΠîpdQ HtyF-d7 ղs]o m Z1 =2wMC 7?@ܜg"vQKDcIuZ2;w'0<Л( rPhƖ(&w.9t48U٤ i VӸɎ0yž ݝHn- XVSjBz!mdJח-XҨUIίb[3z_¢IJ&;{יYMɇ% _,oAb)sLד/$q63{֕ M'HD%H?̖mN{cLtˀ m1H+ioBjVW;AI�STC�  (^3j (‹J7 ZCzRJ<h\%YLH̆nLst |i Ý.O|<V T=RױFGKi -o- Kf܏iM2`^j [w]^B2BFf!gr=`Uw&ܗ9i%"c.XD-NOC +Xg+`dm ɧ<WIG.l2ALiqi !)2PvdRpiQ{}q 7~S$f[)ΛָPw%Jk @A2d7k/]8P\͔[.wm Il6/ދPZH+r"Yͷ.X4Cχ %ߒoF|8XVQp(=N`,.exif>M4/G(@&uB%c H%l6+J)@.G|-j+�S'b� cZ쑪�Wg7s׵q8=LrUDth%&14`^ h鋏JCim2-/%c0 eI; 79 : yvE = \PjozC׹̗!#GȎOS< ^;V3) .̢f\*[JlKLMP[FN׸r~yM˯�:"Ʀ gYd1O3;[Đ TITHC`I\]t(ۈF!k\; m8g (\|3^9 *1+Sكj%{T[C�oAܙoL HɆ~y쩃V=j&8peZ9iYVQ,@/ȃhNڴmII�W֫ QDfzwPj ^Dڨ8I{uZI16,uyw#X8?pz'?�> 9 x�S/� C0RVO�nD{ud@ܺh?+yT&p; ݊3f1Ѿ-Co e QNL_uy-+Ԓ˕߀AYQ){Y :T> Ӓe%)ʶjT7KybH+ꈙJ8mkt: pm<l,"VA՞ Տxv�?:yv!u 배K/cds%m\[ y�U\[n?-)*=Uvzʼn9zUc^EMQ [˯T5M$40ۓ=cI"U4ckLSfwjwiWNm,I[yhzV\^>7L=M3=k<N*D191S3$K'g,&uR}4I`)i[w si9zH㒫| N OL_'}͢j,C F6qc2mCM(| �SkE� B2{0?j =ƉJfe{c?c XtM?W WD]اt<iO_k&1N~�SO˜19Ջh mλty-3|%%(7l7SE*Ǒ$yel?˛o¬3foR`uaX)p5~`k!;4@AL?f9^.D<KDz:Q]k"G^Q|Y7[!$Ȩ{߷q=+>Ou7qj١Ո9TN:PN䘔Rrƿjg/cX)^.i܎'לh,, s"zf#%(~ݗ3|8DQf̈m6b޳S%wj>;*b Y~2Pu;z]jCݙm9āTˎ? n gy/'8^\D=0y_�SY^� h SۯS1L:s|_|zB۲9V*^@+]-g4v\RŠ?L ƫy W,b'/=ZA<mzJR?đ -}P%9ԝԳ9 }gx_{h#B z;a]m8bsX)SU[^>KnLSCyT %ϩPOGɐŶu+ʴE�ά mv*ݹzK> &8^Ac=rC~^/t>�2}%J~]�ToIzf_g r YۢѹP`Vo1j6XUCr)[J(ɷX_K\Tuj48 $Jaul%5 raL(% xB[Q�J KV :!a7L0+L(DFѱܡpsn`CgX|1C<�T-"� GG䶁=�F"7f { A �)P8;uL[5q|޴pdL-,ᗘpd>O7"dX51'P_ب6H۪;p l SoN*Xa6V+ߟ!▫-wY0kOzߧ=3<tTmR ǫqq:Ac{=4<JZ'**x KO1깺<96De*}}biUb+/?6̘k\lB~&Zdu3a%Sǁ )e6Q&㑸/b%gم8 u9EZ|0PLh/ .I?gĴ-A�n ,T&UQ4<j~]81]ïHm '#a3,"|;ߒT Z]/oZ�٩PEmWl> iצ8xK3 ^294=�T� ncK�oQe[Qf!,cLcuLS){.p˷ GX$_CiQ>]I3[,]bzRAo] <1 /%_sfEe@T:nɼHCM$x] _y:SiK~"\57ǍWCSSd9VӴ!s$o9fJlMqkt,Wtz?@! G˛d9p\%nBrUtD ׀z_tKEEy7�8֏Nkjӕe9jo&*ޢxŋwXFl t &|[ܑ) |#ϺH+zO ?ňzul^%MP_fYG\Y xpd"l ֱG%f `G˅x\z9<}"2Zf1:I 9@r"�T6� a;ltB2O-S^}I]7x&kCWwW<|D-ܤ?8oB. gWa_yF^|: VkfCkSIp&g\O^Ea/lL$ }2$RϏcc8=hT{8ٷҶ;VFnPM1F}`!:ߚD2 ߠ+п`6i  l75 yܫ.}(˗̃Ux,}Y|ֺN|6g~S_^n$b۷ObPm4gpmz xO[Q\x @0Zȍd4ZAd3Kc !'Zt�XK\tͭ^띦Z zU 4q w.O @|eU&ΐtmaDsA� W)%^aYL v$&\:@H?ԆRGf߉�Tu� EQqMoG!|)9g_u-\G'R�Th+L4]Tx1t'ͺZje^vxhw\8,BOw=9FWQ@{x6zJPnWW?)}ݝ,E--63ؼRk\>l-~aa?nXcQNM+l m=ű0BJe�;S ͈OU&fU]ͪb8=vnYP#cf8tJ7Y-Syi yA{V^\1+!pWC@ѼDJ{@xsH`QƊgڐJ3Ag#E\ɬ6~S>l)C愮֬%ᘤ0HQpJtO]3߄?v(5"jdXO5"d tO EՆ㪋 m55F>I)E!yO}qg !|<4ؿ�T� 6\ @]�k+`bf|2#&NEyofqh| mX?xoFw jp$ B#j L:XM`[@j?UEwCsm}oL|i-7_vo3J5X.[3,=*SeI%7]-Y:7p&/ Fo= )fK�֑?VUHҶG5Ee|u9뤰/W3hL=-m{ep0*7_�K-*aݼ^F2�E+%ZZ\+/T|̊_҈U`cĭ/U,obfדA.3_?COi+ʥ&uE(:t"^KHQY, V. aan$|NlU껙.qjoOw 6 FrN žP#)=w�UjT<� �wuA,�u3Hّ>su˩e!;Ao|}^px#_(LюNP9/l)EdnDrc5D?,%IK["[)\n, h)O@xZZw\@P6vwX€́GjOsdYb=%0Ln÷ g(n A1?!�dJpO5XeၹpZ%\{ Uh"G@A?D9vl܁T9)v>?t@F*<\r:~ eCJ{Z`+E@d槥h|YQČ87Fc{7#8\صKXT*EǦR|fYq2:T^ڪg).z&]"AL_e E7%BmzBq=H޿f FOl4%?{h\<ORԠ)Q )#U3,u3�U� T70O�>cTw<*<Vqd9E(7)WP)daF&l.җD~A<_%; sY™WL(LA7[_ žb.Ȭz9e$|6Y!LZpU8UM꿆;pfc~t޷]o"|t7�DfY Γm";XVG~3JAwr`oIiz.>%v+FˍzmaD|M7Y_SM;7 83m(ZDJ_vӸ'z`M؈4\IԬni2C`쵻?!0rbC\P\�ylI6}׏=EimS{!@JՆ[͠N࣑X7?ߥ~N6~c+sBׄȩV rݐ4lezvX�Uӵ�  ڔDFP�oΘPF_)~h)ەw@'m56o؊/hԣ= w =G O3>?dUib=`xk ɼA{>=ÒYod˽2B(n^M`Q0`:(T]j ^1S)(a� |R`EI_f}D_z8ͨ1l xTZvJ; ]lz&cG'<6>͚U,�VF>qX�$"HEob@Ռ:AYr$o sX-}> Ӥ';խ@B~*Ւ#Meh&Vx } H mIIh7k Qm^(*?w>n't!(`'mE⤢aHQ0~A7[ |5^7}pdMwʶC9}^y=d,ꝿnx( "�Uy� @.c CgF=ÁmH)^|GU|\ $#g2d%xXZ{yjZVV* Զ>:__Nq:Uj|}92<t`C#[K*�9^Nm`8Z`.7La5@ŀ�ZgúYv&"uk"u8b!Z(| <Ǵ?? 9|ͬe9ʛGxMpI dpš mءY"eҵ#GLH" FCdzA?Uæّ S2Tm? v1LW.rsĜғRF YF#ESF, y"uEuw3#|a))LaKåFfaQ. Ӏ>=yK.1lG 2ZOSlN(g:ϪJD,Gi턌 utaS؊GJփq[A4Xllj�U� xr77߾�nլ͒ڏDi�HC®]&apDkjbBL[IףvFǫ y썹Kg-}vUr@#PD&ތJ9W2v/Eq3_ɄwhLPs<I0TZ):}f5A*O/~q'Nwcm5ŠEo4 ZƀIe m8+v}U!<9uF<PhlysI:^3_ASUAj#Gyd4.Lr@P;[G [&,!ý 5c)Wl skd>~L _5'mK=�?w#bxUJ`EWLۗKNګıg;su{Kc^SaVqʩ8ҖZJ-C#ةV?_A1�U� [FHl,}~ u8IdgT"S}!ec1ͨ}$2,+m<fTxeo,rghl݌/웯 S k$^^orm8*;zA8mjW#ઠ9kd*[1h |C#SMnv*kvZJvsiHh~t[xN`T"~SD]n ?tu\eܹK=,OnyŸl=r$pq[̸U35ȱQYm Waa^rLјohhne"ФڴY A -�L^26~a +] m4N$2@v6—{44XduZЖcrfHW*DZ*f2\[,k^uΆ6 W3jeR`*`*$⓬ZFIc>n8P3mq$8,!{Bv‹7i(NikMA`w4}5�U^� ajAKH)$V.4݁'a1%5U#4O�'FO&Ifd_s}|fQ/ZCHrf,V*uSܶL䳵drMs^Ai-A?!,pӇ WLD(} vI} OL&�OR !nXhby(&w<' 3EDQ;1kƸ܁i_a$ra.K+F $)쌓1}^i9lz`Z_W>ЫZR%;0qW.@}XVIE,T0.4,ak_JMD5S r)'nÂi%UOe}gcIݠQ{/ WJ.iMi$t%?"$1Kx^Z,ջoxpm҇Q$w0?dЛ&f=<P-uٝ_WOJ~%�U� &�k >r(@2-JI 'kgXᓊAǙk?eZY=qDmlZgw]Omq_fUxz$Fӷv\Un'`ɾDo9M&l\r z:F4aĪ:; h sƙ6^7))(GfsxD_ܻ +cM"c ;1d@D#E�֖M1wf:JJM<Gkd{u<g\uhoP랙s +zU<+f S^TjcnS5K t5؃IʳHK`I2.k+JY|K\V 9PTPFN+<)paq׶#=gmf>O.q:#g/L3<|]wu|nM("g}4;<_g@P(\XxL�U\`� K?͹DE@6oŞb zs9԰C Zyg% (Gh :Ah6˛C �p7* 6ɑ윇Vĵ-`t i�mEi2z3\Q+f_Ϋ 3i]t`2!,dTס-r@[sT#Ʀ㻫pϩIr](J"sTqS3y T?Zz9b= rJ8¸YJ(C/6{"9 VvhṮS-~`y'Y48DH@~r7"5򊴿Q͘{䬎J.#r{Z#Ioe/>H h%\ ?在cI F c) fj-6 w)Eȥ4ޮv,fxtpkZE\Q3zSqD[:1y4c;JB66 К)~VRUdV+_< yQtodž#Z5CYdcrRx LqOkAy{^}pIDb: V'ę*�Ur� OWcSl8ΡI xUJw3=: A 7j S= &gY }ɾ!oMzԓ%4ȷRȳ|M1nIOugN;g1T5|`)|጖e!,Tg!Iu-G,f:O[*S�*\)HNs e?w;k:o0Z<9n<CQ<l;] 8RoXIiICOY#xAQ% tOˁR Bm$,~u0uHߩ}mgYb�+ ZJቜrs17pIRjgP|P7F0K"2~aXcudɫxHHQ5cԻ,P6{M ij e0M$^y?i/sB+$2+E4TxR6�Ulc� f˔=�X=IK0]_WdžϜ l΅̌a�e_9z'_fE1ņ=Ub)K!ۆ&P*E2M#q|3Gx@T4ؼ/)h1@Ip�0E@z.> L@$1j ǐ|liQsM-So .LBQ>U\Ȇ�~~Ù|'e˲o<7C T9aP.:*aThuTGuE&ۓ4^-3hySޖ#V2ňҳd{޳Leh8>|p|}K]NZ*XAeFPSPS'φ? 9%|:a70f]RTJ;*hT9JYg PKp~}B76PPx-)4݇[a-4~TU`a]v|uʼnB ]nn qWJѦ:ج?@�S� ؊u:1*%Œ1"\ש'- oj W ! F夨t}N|w|+?)iY"t3+eD 3jfM`@NF3ֱ;Bc=jD>*a(,:lK)< nwu \\%*f(wF$S/*hh |̱ؒ5̸�mÇthXUWvN%g Zcv Mi@T~KnŝFP kt 7g>F(�' Rna\}7R{Hq0a}?7r6?o3 .lI2Hd0ߤkߺ&xf2jWS5e/FRA˗2gFo3BnNHLnR"]T DBVHwSIB7hjDZP>R�S� g-ɱpuv3`~z#^hj&{NIJ\`ߔTX P6+xE=J b޿l>q^5Zm:(./k̘jŽBn~R=`U59pkfIM%4y{e"'YdHH) %mD%Խ]轱vv0]BKwZe.wEM*.9e#aud`HZ5:Xr*[ �RjHʈM˻NrF_'7Ki}γK%nx@t)VZNy p%MH˜L8Xa5qΗ$DXfkG0F^&\�GUBOl +&>@`u*Z#�eQ7wistEiŴ%= W2[9X%[bb(7aP}`83h i$M,nT8LnmgEkeai�SV� y5M8zaWֆoZJ.E -pM8YHYfq𬷬ĶdwIz|Gu5 y3~~|brǑpژ_y@d^+_o{Y]Hc%Ko%SN9>Q#)JڪYfn{*9a1z ҡJ7TTk!癚˞QnlױԓDŁbA_t:^dXn:>nP$ Ʉ*ȳi�q!cX\&~}vt(NPc1uQd犂mWzdL]sZ5*]1VJ\H;#&HOmu_蚐M 󇯎 xxx?>Yn̕4B~:Aj%z"}�CL I-hy-IతL.T,-?E 0/z@A7W^E,@eDH�ST� d;"zpkI){Y'=.(mfW|*ϝ̆`Yf_,Fdsll$ %Ĥ4Ց{!"w|({yRRbej`=2fRțU{(*x;A72Kx(j2וU/ZU 6"w I1mDd ~t~ lheD!rMA VLY큡}|[6h" ]7Uʂ |70e>CN_B.Ac[MH Aո#zk-sb�~!%y^hNX4tq"וOjI #_Ry᰽P,! dF,x`S; 'A(W;427zɽsT[w]G9'`DyƬЭ&L#4>'*8/0ܑPo�T$� L=XAF&^gLfˮ M.9љVe}(W!v6y_R:֭ z/4�==h_Ty?OӣN֓[hH2.B_nlye}39&!]a \g:zr 9=odN N|帅W95i`(a}4ۦ9uwXonPyxޣ"E]@CI1Ӯk( OP8uU ^21N4uˉ~Ԩՠ4vUFs0MV?YdžӢfG܃tԸnG% qe{9@\32c 駤Ck WՖh5Oé[y</ZA6P K''vٖ:M8+{PTKPQY-ث �T� ;V?ܱZ�Skl)ϗ~!ҥ_rZ+�~td 2CD%FCIoN wB)Iw]+,p%5٨IBa~18W!,Ĩ]R\4.w^l ~n-Z̑_ԶqyI08UV?%\P¨Oȅ $A4g R}a'q=~Ru `Cm>tLv59l%eR L$,:xJb Y^N`Udl]줠b@ĶlHZ8ATʼnzs+85spWҰz2Y:?xWC f>SԶGFSnj"תg^/ӓ.U!\VߓrfN`\ޘ2?8-*~w1r?N-ZW>`J~JDLkZ"\9NϹlShɎx#[ir+_ϩ96y Čx3&lj�T � UB̥�þ=AS'~N*KVyz2Κ#7>sՋ:ژwMO^1Ɣ| 2%sE<Rk&oWPse/' NH)ɚY|> &]KhKoA# wE8z޻qjaG'<|SUrZ Ni_JT@Liܬ!s$Ph�y+-6c 2΢^wܐk=}7+UtyR'x5)p� Q.kO [8ɳ38`a.|P(M RhSӥRИAeAӗXI X6T/峥ş2] BICPkgɾ~XhD#J~Gh:{j_OPOz)ˋ+ v c>¡wl.ZG'g/$zȭ~L�OqŀjI�T q� ǁ߯vbjʹCR m?8ԗ*e\Mi Bs D\wc(N<-6Bx/\YSn`�LL/9GJ-�~)VQ=`vvMR&Zi]PԿ{h"wR.юUOR8<1V%ˈ}|DTgݝ&t9#[ D!E`r{gqyvuƃEWU\-I6mOzPrV$y%XLO|#LoVH T5k}}{<G oF>]h-^oҚVDJ<{1Dg" $90.` p6CޕK'\y[90巿?d^Rzsz�1 00s2Gsfr�|]Q)Omjwq$?v%h@ 6aoP3m"6BC]j�' o*f�T � x݌v�St J6JfVɂ]#AsvdЦS>1(iq=[zM$,pP厓<C-}C}C{}}/DϪ'LsNXݤL+[䊦&v!%T$VRWK`5<ad;g׹/N]u|ֶnB3n)%! /6#=?8ԳfޑAގd,L.,R%a֛8N\7P赮Sr^�1KDTwqK~OHH"ֹ8`HUu;! v&fsb}rJ06*ZI~ْ߭|7i?յ٭ ݩeaUN_)VU<SwF<1ۛK sO.I)Z<.wĆ׋+XO@(\#S2 {!(Wl�Ed,㔔k ;SmG�T � .\ 7;|ݗT L @ ;Pm B0VlL;C{s�-FE*p( II#XLėす-`Ho>Z+U_~ZV4~Z[q�1y^GI&{N 58�I$~7vO)#%U):Ag"$EQyoD;goQM)!ӓxc oEx<݅Pԃ V5ü{YU>݃Y3P;DQl.xFN Z`{c,]g?Ur/"uDa24 R)qUG+{�d [*:fT~"񔚮b8_u|e=I(9@I2osҧ#4/GqT1(�nƫ@X 1Pn`tFKt(#l5Jti ]+Y} [{TYw׃'Hmz�T/� )+ 3@LX3=+ [h?)rM}W!_4rHCM-vY<VbE268V'p(*<mԚoNΑy0n 'É"ɶw,g'Ks{V/J Sel chZa9Qcs!8 `/Xۼ6:HScWoMk=QkTm0 R43 ?PWQS.Ցwx09O;9'C-# $`76Dل�=;Ӏ|x6hpU/kv},(ZXl:z$tJHt7׳,$Nk%}z ұYV _-^l_.qAmU:dK./o Bv-E#ΈӐU")){G6঒\ۺ8<BJ:ӪߵE[kѫ{xs P` TtQ+5X Y_)0�TJ � U"Ԛ1VyWWހݪkM]s'kϿgh7aNNBsQ7; ~.:;C3?y#!/Ȥ6P\T—ih. Ĝ""ҘjuHm~A@Y7G*#!c01^钗&?eR]w}K'/[) +dΞ I-AʞŊ/ƕY vcĩ`uK 7К'^>ogCTxâĬhHlܨOF$)�iG`RKINy?) 8X!~FoU chI1Uމ)_ &CGeS+1[Uwz`$7n6 7`H#L43!.LRy2FɆ,<rOjER0هD]#aNO{&GDg[-H"�TZ�  `��򩱱U, 8r |BZVwxu?S^k?EVCd-3fGam<VD֋9"DJa׹z$zۆ~s17Y"gqLotkXV̭ vbX5zH%߳y,[^ 0T,WEs;~ &\qWl#>~=+[@Rr\ZjJqs #*֬zhzE,K3(v9vsPwWlܙMGlz2Ĉ(*l#wU8FyS΄ <sotjØ+.C20 G,7_NÒ2"}yWn_Yֹ=jek!!vGbx=F_؎_z*߻v_6ۖ.@HjqT[9)ЄwfixyCcKlvH[QG<!ؐNaP^:_6ϧv4!@4X�T� bX/aHc=):>jL'aK0UPTΗFԪ3HIq K?М7 Hu#t1h> p&lσT=?>#m2al=694雿850':F)Bwge`Lt8\B^_}X_Y'쎀{c&3K-n@33D.F*B9+^9Pxe>sĎD }�n`4FV5>8, $~3Օ${#ЯNexj�KfdauS׾4ZK-cPDuqT[P_?cs@I65a*v{mȫ@TDg#ar׌lG 8Q%ߩx˾(&Q`Q,ĊY>sʀegy:+"L�bSW-@5ܨ~ll5.Ӿh�Ui� jb~^$�-TSr,c=`:1oS|,GIzꥸDкl@5:<&EʉW+֏! &7/OyY*ۦz4 VtY($' H*toi'TN/*LKZz\qH6S]n]Juh [ڬ r[ "56%k6GI,5/e_A݅Ux Li>ěň߽dMOyT})q竜cu~Rgꔑu--jw É;�<4JwJ_4y)ê¨qrimoZJ\t *1d!C>A._S֒&PuϓSK@GVoÜ{گ[cOI;TɅw!Rod,%FoQBz/r6-CERFGdqI\/ARJY�UN4� LZkҧ3mx&v?a^$~7]BcvzD iz_]W2'!YMCf(ߘ<4rTsWdeGL"lH*dzKDT;L*F}<jEe.{ tni,Z=}ȶWK;q^z->fbatu]p[H ƳhxeV D[u-X?H+_UoYWp|<ܐ\Y",ND=G2 ӃY{U8hi_R#M$[p&fO\262w0է XBRxIbw÷K_.e[lͫ>L!Gr_w ~}SP0> `8H(`L\WyRG+[O*¿ZCh!UBiq_mAk =P�U[^� f܁O; 2%ZtmS>p AjhrH$TAx<="@H~%?ZRm,ʍyxB2H !-{zC-[}0sYc񄔆7$ƍ*'R]@W-Mht.bqA8F~TMY89(qoڣ47bUxHd~ʓw?S`5C8dbHݶoU57gB fnB}1 ԛpnS:\Dt4\FYg2_!KZrlEEܜ`l9ބic׉̙:Dy tpuS_Ŵ,XRgdEP`ip,֦nuEzDm);g:+n ݏ}«cId:1=( 1 S{Ɉ*^e> nƀeD~An;Q2-I-0A�U� r6iЋ84~3]-92w hGj[ MTh!P/6'=>-7HShpcV|mc|9 ܫzh6 V𒿜2 MƗ|(,FA.$&QȖJrT|o:#R%AK4׈vS梕[Iq4#={o]=25R쳇xCC%Taiw]iGs\A W |$U΃B@~Q{).[hɛm`Q<q~r`Vt+Fνﶰ| .qJ)"p1ِFا tTj2v-;뙯[[q8"Kg >ywFP95r51OM?ƶVǓr#Aub5ӊ9|E[T ^-PD᥂8TF-�U B� >~0uB�v(a(]㤢#bE.PAT NAf6$9J]˝ _l 10ae4N9]C?1؆]PM\n6(t3Q>qzkZ4p>A)�tlP1}[D8D[_uFDհ_/|@ Q ٴjW@lLrRÁ/C0;an .a<8deT$h/Q7ur /^szHh[1|cW�9&z ^dȬ2\_T]jpלu|z\)'R6*,%\&<?AIՐE`%_[�!vg.ףl+}ǽCp/R/<2t*3>Dw@ỉ#Wˇ2.W47x0MaIRUk< Ģpl 00!ͅ 2~S{EQdVD匝6#o\�U I� _UIhĪ�ASC^5%Z`v^:rB3"}A9%O7ZKăg8 D$ gn+TCk(#݊7+/_Sv,k-$}DuruMi"0ʙ͔O#puژu|&]k~wk&BUˌ�NOXNڼs1rxvUw  *gks�SϛlEK}Rd2 NKf3ARV81F՚vjK3y &XN}l_+ۤDŽvUB:NIG2w&M2'Kݥ?wŕ,Tu]_ԩ+ ɳ_ شb*iHEن`gܮLc>Ђ7e?H 3(HgˆUO\G..ؗ4]6゘>00�UF� pJdy8% F#{ۓ:\f\O" Ư~RR(I0dea4=Z89IK$jyȎ3)66471A):N)|SuCF8c'צnq?O=\b̍O=O\\gPǒE_.bkea띃xc+ڠ|l T:tsY谇 db{ZM!kW)?" VsѸi "cZ�[I5;&"rj\H}Tk,] gG:\]ca_̤ RUO2PU|8ޅy3?Z-FoekDd]JgKg*B;^9 m6Lhs-8F!P5^5 D{5Jq)[NaBᘴR]!"4M}xob9'XaS6ĉ�U\m�  9H2)G:aH$*ɫ^ve8j de Tڍ=Ra!c+&~t8,=Yo�狩eʧZ[/Cb EKqF-Abjŋ!%Zzg[ré�|ػN Ӊ-3iQ-"H,pZJ~4B^h3?UmJГ Cv9I򹞪Zp_c^Sö=I9,> ~#ò.$x5p [G@OH=z&XBu=9_Q`ܪ�rPɜMTO掬Op@%za.Z6;C-grJ,,c|cjL tcs57tIPX\GK8~=<䂴.kD[. h|hxS] XbXꟂO|v.F+Q*0iF+K+71ևB[#?YMHd/ Z?c"W3 �Rε)� Z`{u5=(`ej9ďWk /ȍ//ao | K f <aKF@5KNJd&K qt3*+BxUs<5~-:V@6uN!vsp ԛc'b# vu }f!ҧYH\D=Dʽ+\֟(-s͆^*m0F#�Uxk1Zеn;@gPX)߰mƩ ESF \AMUzhvB^")8 S8gzQ QՕe-{(<Nr^&fW~IcU)#k 8= lNqAr%Ѓ-lʹv43ACټr^e,@1ܶ޸7+9m*}w>t?钏Nj79}]+/"144 אsIcrJ@ĩqqJt �S� 7Ҭ䋎vXܸda/>i_d^ dZ M7,LIuVqG6 ޮ.<ҁv}4]ʞ#\'LZfl.ƽ/~pjql ph$򮕌sel&o.Oph=/sll!Ewz`13hPB||F<:F2�xvcRjVSv_n<PRDvcYy/(\ݲraqF̆2tp2y6"J7!&pTH`kh^]RH(c*uĹh>-LX­r#á_ k\ ـ,Oc`E% =zlhDo &I&p$I ]x*9kS= R];ٰg 5j 墽3J>!-M}}%%9L/yy:X3,E;^)ߍuHK[>h_$6 fR0"4A 4 �S_� 1^i&j]P'Aɼޕʉ1"#y&DՋ8wh.+w>~kPdJ=9Z#w+{ #XNvò >!m:U/]ڜKLwp U h~ѐ< G""IL1s&m6+w 3WZNKb^2{泌PnZR.zjG[EU &H{9O:+uE& v;j"Mcґ�pJ,~Z>H9A;.gFH<TH ʓ[ IS DY1ѩWIɡ)&iD<qn36_t"f/kB8pZ쓪JVRD>M9Gg7՜5nmJ+Ɩ zl3 Xze hc^#"؊P7@Y �S(f� *XGa^N?}6?2~w p+8o!šl"e,#&ߙᆑϹSHdj[}i?9g0Ҫ I1'U[)"{rHI* -`$qђDE΀ٔl|z'ZCE.Z ]c;-`[PUA=xw6vDCP̢5°?&rՂ˹h|sVO`+ᵂ ^M+̓S13QTVyh/,[kb6!OUlq(]gS*@<Q (qw3{t%<1L#kEd)4hyӭɭ.x»P>kC!0LSLgH =_h<ͳlIUj'Y !ܦF~ ϣkͷ'Gd V{dRN% NE;ҡ@b5ol"'|ʉ �T;]� :hIi=d9e5VOTg6L#c7:lgs92h:Q%I&r7ϟB;)~+N>waQ!jV֪"`n^:y7;A&䖣!P:<29e/xVbviKLsH7Uu9,è}Cc̨9e[SD΋AqnޘY@  (Xggf+u2!L`rN<!16LHOeLd�jZ1UR$U:v4W=V_C 6Fˑ !j\숕r,p)Z,gظE;:U4Y Gaqᒣr;'j,́fdA|(y.*X_ų^ /um̒ ud4I+$9 &eMہ};sOi?* C9Ffd�Uw Vo &Y= jA#P~Y[ �T� uxkIOrZWK?p o4*AȄ]K|g+KβPMJńqND΢gZ7*BSޱ6 0%32xu.lpo(KNVF%љ_naP>ȯu j]jU[5~ka)$ q%E|ѿ  t;49*!1Aq*~S<djOrP ˔ G=S\\0:#npCSr3"[9+WvvD֐Dn%? $nbJ9DuHI [Ǩ`=. K1T\%$_ k Z,<p.>:POR u5O,Qi?PWDN#sgMhmX%O,pJ LCd'g l0қtf dh37yƾV:�tځl!mͪ  �U � V3x -HW11P+mWbULsT1�s2^u|z6`39t~ ?Nq|+t*Ozs+AP:<B^[SXSXΙ5�b,m*:Ƹ,O|Ѹ_CzȒ}X=į#D3m$w0v@xkBI$7�ɢTՙӑRz;8ҡM5V¢dL$8 iEa=4xz[z$CзOw5CIb( =\e@Ltk.bcܳs$VPvraz BK�C0VtF= CW7oBSCcRxJg%81H9NעFd;J`Mߗ�b|eljϛ,3>f*1hDTj'!ylŚI$fbYs$6n_ L^*K[;My) Oyu7 �U5;L� ?㘂sHI;%8EN{voˤˣse`p$:D�C̎*^[ v\@ZfRyXu=c!ֿy>/>iu}Ku$N!F 묳\L_UQ3 :/=яk<7y#r]WU2RĿM{hi&yhJI5bێM ],7nMl_Hl..2jmCJA>+.DIF%B%?p7%(l)vf&h1b1W ~/M { xAERlr y}bg6LJX@_d$÷lIWFC):[zzLkd RRJˮcBFaC[t:ƭüRI ΏЯΖdDi=\|䂫NAD�!.Ľ3|+URy_(+BHx| �U[� $hh{-DEUU7c$0x4퍢XgZӯ~4@,Q~̆bI, i f1Dz4npwp#@DP9@jgd#:N_&d&7<u`yOjZ":~4'w{뭖Da v d=f}o5p\17d-Ivԑ>l?y6EܵfK@#g`lEgMo\2gB2쫏9D@m4p�-ĦSh+ePeN>He:w3NH11c~o<4?fѼڽg_'c3ݻ٤~ᖠn5J 鍲` B}hI Rx.N#N]~kNxZ4][~U4σ]/9y IMӂ}}D(sm\ Y|$۷Ftuyznk4~~Ryd  �U7� D:s*/A`mi~ 41eOQO\w ^ӷ]ưvR6A9}h:)o{nHiߝ&n@$:cR_4Hz?$Mgܧ7+ɤk{b"]T<4F`"&tah.eċ�ؾ[e~)/=i&PM^v:1".IUaL?xu:(a1$22An!sS1 5f"v�Yt"kYߎwoMlf঻CM듦[CZX<*8}(Y4&>+Xk/*߿>py<lM}EM#ȘAeiBq9J0q@!J{KOݠ {EjxUH؏h*Ei!H } [ƍK\x:\GkpBm ʀEЧ'mq �U� `L To!1+tGMzy*<hD+nYVImC~ɲA-ӢNL^~)4*‡ϩ,kdQoJ^ې@Td $X JkMԙNUye[W&):H^R @Nk8MEke-`'vDҸCXV&0.t^W,Hhc&WQ@:GKڹ>d&�}CZ}{p +y O/=% [Ô4#.3&ĵ <e8,xNk+0Pfn] idz Zn^gQ+p⩚8d}Kŗ*Nmj'ǷC=*̈VM&eziLΝd{8f om &LښSNEDQ�KEQaVT,>uz3&y=\BOX>z �U+}� Ƈ_5A֏]P\q|7v4+ϰn1M *+?KGm~ %wa!5_!d�OK@^4}# ?c+*˟:'fv]xBzN U%/9(03\b"tPX~ 7Dڇ}eF:ķF>Iʬ=<=!v'}H1-qRxkji J'v3|S%¿5 v\^ӓ`R{\P}_p\lS%jx FOɀE�Y$ ~@Y#L)SL'+X>$YC"Eb�BϕfQ=ZgBrH^5;[O;UPշsyaGEoSUH𿿻}]˄Q_K|REw ҼHB7 �U1;� :G~#w%K\&`5p\|O5(l2ApLXvڍVj0~iףjyS1 ;Mp3R:?]+{m Kt03aax_J �/PBz iAwhT9Psa7t]"PDN#k`f|q:LP{!!)|<u㑞œ`]~% Heu%[mj S}N)TOyz Lה9`QjX-ԁ[݃ _>ۆҾ;Fe_XTFf 37`N:i7i]Cfh_|-'~x.:G0'~ "C@q&_"V*$,2;{Q \=D7!) t@ tX>'HX7$&ez) a[ }L �U � 㭰P`V6z�+^κxIa74n�d4<.@x6B7nvЀ|i|Va}[n_cO?)kga&,F+F`j/׎u0Oߞ[}Z-e(ƕbĮjpd`>.{Dc92u(y訲P'w`=<*#KzuP#䠟92nfDܥXg @Hg+z KH:JP SF]gYgf ]}ue<\}p74.^/P}:hL?b)ޑ]-^< (ӥD" $p(7nq&lDjN2;w*xBY=P}vOaQMjPϷ$c)fP+y#t\3~ ~g7gşFJVOHِ%u^wZ0tNc@ �U � UB΁SM�-V.<IVV;vFCR ~z&+eF zo50aDo Tq,`ڔB2II*}6r5 Bq3qb׸S7 p`z`OϹ>&pS_xrFQRyx! <A>KTN 7BVfvo.@1iV ĥN19#oW@0uR:ycOnPklGzIN? 4ªݦԦT9e`jAh26c`. R`,!+瘅wYMb FIS Mml/�p>Xy >'tL>_XG`bʎk@_JF&%+!loyWݯ&BoL㞇4d\څ\{3gf=}kϘm 5t%ϋ1$ubyel �UϞ� y>b 5� &iQ;{fDKiiC8 v̌U w0Y! q SB)&'!' AH$L>`&I7ZnלBـ %{o$E!H֛}'qB1q2 bDVV KHh *I{ vqϾZ2\ZnEjrfCH$}'FꝳI|ql}n#>8 ?ⱨ ІؘMH1{5QzԪm|pC |GQe:[-|=5h^^KaH 3 屩@�+ rsA*<k�cPtS!SǤfh4>7f>S"(gA:.kGak�aYO2עt6y||�RD qy{�%C�U|+` 7!+|Gl\h'E#|RX9p Ɍ^ls5v �Rni� {oʡm=z(o8Pj\;tDXSA*5pR\Z_cJDe_T7>ewAV57'!PTU̎,߉<y%ߝm_WOKCɭ `A&rjLJ}2oЙ? 5l{ڒh \V4'5i[R?A[tbk!d*%-g`Y2m7aţ8ӧ=[iXgmr eL7qkYP7<PgcS5&|>vCzUٰ$4לv 읭pi'x;oUN@iVu7џ;,Ilxh[Ur"L8b) {҄a!2_qT> ]b[X"FU栗6=FX dJ%%7pdڊ6. Z]~j0I\2Ʃ#K3s�T� (!AnpVpK.O\'U-o\-|]eKT5${`ɃŶ\c"ֺskː[5Hc9 H27V%DB mi͗~C5}ݺ3�|!Lv L9Ms/WJ+~q@́@|qĬP!w\@J57@zI[i]/#=t<$BW)t|>nZA'J6q+?0dRn�+/A tv.N)Z87GwS1;\^+9<r`_ƒ#NbRK7\3u+P\r'PbA3A%L oxXPVv^Ѐ#$v332|HuM:1FgnQ@\*kh^LTQф^I)(u1y{%(W 6"_%ܫ焔RY0Z �T u�  }MSRCQ>ÑsRȻǔLU#V#f@0Omn>#=Ϊ�'WᭃOM~>FQ.\V+pd`r.IXh3R&w/`vBJ*V|bSOà5U+~4܇SJD`pXke8pbu֠ | tEXRwf!:I;n≩x]*<o_lĻzgHmS]v"2NipI;JJTw}W80<]SՐy*w!_ٶ4<3kj~`P2ċAxRl 3qȟG%+.KMsOs`)6TQd,wlP{8&A0/*2Zf^%\=NxRѵ dac x KCl\/d8?e/%.5f%ל�T�� 4wh>+i $ "'@A6A([mSj=kʬI}m@fYސA$a 0?sWfKg$A=a񦹮E%h4eP]:yGfjt;ك^q|K:[r v˓lj o;>#$@ 3Q ?˙ !i֩l<ſ+R%z Y4$m(ctrWIyϙ=g-cB`y-ȡОN==韇䂶lӻ'űlT0/ѕ?CӸЛ | “cVRˁj RrXt nVpjqu~^t“cin7`%BYF7z-0Zq_]2u\d%PrB,̪T4+U=\׳|3`!',{J،hU'vOM# :(e"I?)2Зxaoo״57 �U>� )\ti gp3vWo qjN5LX ,~I&"& Z.\/a4|-|’Zqdӽ+Qo{6Vt(664/8&1ʠ3*j�D(U:g Vxݰ VY Ҹ¬_p;#fV+C$lqC 8Z c1sV%id!wQ~91:PIvIN?3Ҽy;�t*_Ee6һ.4 wwzLBZX `^@6ӖH$،KW*[,Յ_`5dwPOq՚F"E^%/R #(;H6tdcD3(jYV1ޅ=xYPUW492R6 F8=P%;s`<[iU,R&w{QOcw4:o5Ҁ)G"<.:X%K]z6 �U>� /!wT&^Zz` U!baN&Z9`h5ѼD%MJ繒:g J �ZC@;:I&V2Se{pOUK7 p̌s.%N^ncC3CWn [^ĪZbpGm._'̆PGcf$DD | ^Mej9HEx'&ըFNAc_YhumnUG]ɕ;_)m!h8$M\V푿z|_b_˖?-TQ^ho f暘GZJaE q(ܙzǍy̡ܣ&g 3*j~vBڪ@)9g8kaʢ(zin12`<7.ہw{[[ ? vNߤ 69p8 55j~8~J3?#ƴɈs;q=ayNۉ �U>�  -{$}RBws� (p=:dm`^.*$Nsd7 U)8J%Τ 0ܣx5;/-Un-+m pz>0VyxŮF%Qd7c  R;HxNZk]7Yf|=ԋCh@}M5jLqV-#:v0{n͜)7+ 憺ѢvPeVGGD15<|)ٞnn&j[Ex)~;'5ѥy07|~[×`BmQϘ73|Ljq< ;9@NȉՃQ휠xM&q6rDd1/Qh~+2nxXY .8k_h]e{uaaߏ$(ÿP6Upܔը5|u\u߶+XEu gb[قE^s �U� U8xo1%Ԃ^7'}7OxEQmXȉiYx؉ s׫ђ=G |\0- V).k5rt2K6ouBM\DέXÓ[>q \6 Ş޹/aGUma9nG>ڋf_,"˘h=-h,GR|,bC~XQSNDt:m[ wMw\ak[BRu,.BatLS`.-3ip9!QTzǞ+yp_lji ցR2/feLSV^ٳlװCm|mV+`zZ(v:Q ӿFۼgqTf :/B[Ɇ^ƎVh≝~d T$WK6Qupp3 :/QmєZNNI͌\nS f;xF h$-Mőw"[nK֑/S"� S�� kC<9}ݺѿCd� ~~T4A)٢ʔݫ%@&El1p3LltFյuqPwׅCVנί`8@;t2{ib۵�C1LUD)?ωՄˢ o9e{<Ϫ b7Dj´Ne i b])3jF!t>u$BiS A`ҋG"p-됓UHYV<؋LRp[ݱ͙s0hVFO[t]zcHb vȶ0`Eٳ'}0-헱@FRUNѧYwR L\KWצkWU bL齗,Գ9#a1w2CNjAr�l2fnW 8w&pۓSV}U9 TȽ?;|=lYԢ19Tm{\ppI{= �'   �T ?~X� 9o~~lTÊ7#( æiJN "_0rttd@cwG,yRihۮ:jM1r|d^�}K|2;ԡ6)_J1F�yG_+|V֑F.=(XӬPDnR<({6`�C=z1Ԭ}qzveRM/RV7:4P$%[bD,C璜FBkн@{߃~P\V#)-YWuA-5 ;Ol/jGY!3&=h /<󇬜bCĻq/sy&2STto~7)FLyTH9T%.٣<&7E<&)4ߟ4-z{]7\Φ)<Z/UM}|7:h7%64{S\;T6-llu!3(4$Vtxdjtએ썗D �.RK'git://github.com/infinity0/pubkeys.git� _ΞLY]AϢ<NBcj%ſa"Fx QOb32h<qPpPM}QX޲+7 SmDGEim6pټ.sbO B>i ^XlBhD,zCTc#3INVtک/ih vdRMcO J`y0ƌpI<pk&lkw4ص(]8ٺ_j݆xW5{ IY;V6- 3ćz.}^ŖU1]j %THׁޞpK@UV &+  #e@jVu5w[{Z'l<ʴ࢛(`˿*\:ߓ nZA3s2d߫.\k)M5tGX$u`#l?"U �?U۪8http://nicolas.braud-santoni.eu/gpg-policy-20150222.asc� sK�5TB^. 7{f=d7.ۉN0kl? SivsEC\ ӎ.9- o" <.b䵑qe=w/Q-b8z r%&�M5g"^SbR<5uqaZ;H;6 Ve^,0Y*eJk2naЭ󗪌#Sb6oTjr v2֭&ʊ_Cxҫ4<cyrs+J *&>^nF4Wex=*@Nc|N "'..:j3(l5xX=a ڡu@(Kv>9#Y &JޑAˍӺ[dN/[ q2Vó)]ֳi`&a}稱u|2R#[Nʍ<>MNM$Qq@T I}$irb\]@fZ.p!Y+%$]8f�T � ŊL �Jѩ {�skoQBðahxMcDIr;ɯ?7KKW^2 80%#i*C澭W613{{;Ec敮Fn~YpQ>S�h 䏃2l}]c`w5;x2v9VWhyRooX7Y .nA:#4_!M s3>g"[B ۻ⚢\X$w/0O ZE]m$9<*sRˇgy$:ߨg8L=i0pKS$K,(|ś=fF-INE h*qAnO9U .Xn?}d.Xb~D޽hBQYeQqW# YdR^>>N')X <l_Gw%"~Ssd6T>t%qy }e8|>2$Iƣ#(Q(PU'p#! �iZ1KNWzޖxdXk¶ O)Z ((du#nKNmsXCޣB=S|wo/_IwÂYw2;uwO,>>Q>a&N*Bn7�'o˴ 4ZMsH: `Tf̻޹JqV-F 5*Zup1,G �pSM0 J!�<fk]TO(#쪶|TP#M{h׊z[]Mm˫ߑ@vC"̍, 7HFX)]y뵇8ulއLIb+-9/O,GrJ% W#1:\ϯ(Nm %>Ny`UʪD'fVAJDl)o"4 Zޒ<k 6JPtFopDI9cAtN3b>hq*IN{:lgʊy)h47։ �U#� k �U Ǫ;?<r|Z{I{5뮧pk?@L? EQ] ^{PB�QV!0#8[DZɛoaqnTӒ'<pzG/gt}ƨZ )};DvVa1h|&QcP~>'ː^Qv-K |S_-TN(.i5M]Kj9+9#d!j<^;Mo,O7) ț[QJ9I ts_3N'q$dR }j˗ 8AhVQ)>rwlzM"S'0*Lvr.V:4Ϲ1V(E-9|V!݁13^S/9~ϮQr?P< &DiNC|M)O?A@Ra+R}. ۞g2)ᚡJӳ</7F)( ,ʋT#wZtmfz/]C[c8 ctKh ꆁQÝ^PW2ĩW`-OB=ŭ@R ??E8G*%%_k~3IӛK jbu ͥ;RYBي;B\Ꜻ-sn1߉V qZͭ1n}:&CXA֜DXBWk--]7 7#uNa0^D x |Q|H f~R9ŸZEVMK'zteJMkKQBM,xe^ YyG`yvyS fuca-Jj_b5 {@$@6<1 `y!:cazVieAa7H)L.BZT# i+M:_h3:᷵6ӭNzq{ǎKނ[PDe;:GK]o#rW �S(�  F(@�r@gSmXx-YS,2R'36C{v=E&]NK~,:jK S%6yj),W o{opLXRuK>$7$˜dafmCEQY|PxWLM]G XP&WN"!aRQ[~.PL>&X- (o#-/‹k#T)!.r1=G< 'ڇ)4k#[. _[7hՂS qhTi5b6X/I d{SuWSVᎷ;,GmOEGmV}vLEWE1Z= {5c*zbZw,<P ܤ ]|8y],h?n Č"dp�NbPEްhfV$6=ƆI:[~Ȉ݌]-Š+>| "LXomUK";#|Z1eK;܎oI]h8E|Ct).0juƺY1%1mHbpq!+Z;JKkm~U`踁EW-xCrIuq}(/<lgOFb¡/A cgxyuL 4Ŧs$7|o_\k'zhiډ|f&X|Lj˨?<vl*/M@JBO MҜx9.4݈n/ȝ=IdmEk4WpYB8b,|Uzͧ<:r*ހsMS[/[M`ax<cK>058~bm嚜38ۼY"vmы\u{8lJ!/AJ2j&U %�%mHYHsC. 0'-\lR'<,^0m݅I.S۞@*fslܰkRosm .LDFt^ ByJp˯Z4D5ItP΅f;_ C>Aǽ]%`$KMk!||̱ A-fs. Y3ꐱ�Se]-D0QQgG])iڱhKXVy-V/[|R8BuKƭ<ͷRtڶW"~}*<eZ?7UA`u#cڽ}ZG$/ Vu-1HL641>0w_zdިܯf͆ ퟪRDr  昷+֣tz`cE�"E9gҤ_urCW`YaeȼoC b^V>y\"wf痚)"7ȿ&}(T3E^+cQq5 5zFǕI0XQXR $J*˜s%(>{7)sqoTZAct): [Uh1pP)D䢤# Jx$-0aT{1<ew#ñq?*C[ܞ T[qԪMK&d%MdҺci-8d_,#t}8S.h" `pY2�~ac|cANpvTՁX켐-{w2mM;[9=}T]Y81M=(CqU#0DqC3K+g.ۍ iZT38<o-_c`TTgr de 5"CS6@RpUD;6}�(Vn\Y !r,W;b_sa9o=BɃHƹ �I"L-*@8LyYdu.�i-֘FHP@QXbɤ.,lcе}Aw8# ?-˧xIS2jx-7c,\3F�VdHo� aQ٦!S3`�>n{o?�D];�]\'NH)l婓lF �V� /|�w_qX @@K�.EJ޹p2c? *�V`e"� c!G*6HD mvaQ&NHzR2:fwnS#Vi',Yi04ƮL)ж:2S\eqR0~;ϱ|qELN[m[VŐzYS^\}fXFzߟN^_Ja2isŜs/B%ba4խe|jW܂NrLqC"L?p>3L/Eb$'Cg¥ӵJ[3wiD8ڗs6Aܱۉ�V:� 0_wMeDG_yc:#G;d}B~S\ 8p׺afz=T͚}D8`e/ܨJ C|rGxF3 5h߿9,P JMD۾X7FM^g[`b;Db2)-ƽ%uc{'So3Q1zA2=,Y'&Gt ;^aXAZq7dl?D]Fr 2A}yO'.rmo7d(v ;O�Vi<�  g�gcRgKiqA[GOp}wVR3aҕ_FpJ-&a& Mp?fAICE94*ziF yI\F:ͶcnE8,8FEjX^ZC!̔ؕl9% ;)w%rc;B¿f+vR9GhYMp#6ߦS8 LqA:?_zԽQ鐗,l\ N}-nF-vup4HrYcVJ޻%k@Z8s ={w|N{Ƴs/NyhbP5K՚):qNaO.DrpJyNsnm.?1hWɔ^M a=ɜz}F$ᜬK -=r-p|t=PHN}~* ^/$&mly5V�Vm� 2N/F3`fm ĕ Oeu/jߑO\C]j\+X�-N©*ˍmo#Ճ~G7u [1?ҎU rC0Rd289=�8]]k,˺x?˓!ݫ[.Xa93@Ge叔1" 3N83#|3gqH-2S1h%sE]Io`릉ܔXaqњuj KWO28egQUЊ}ACOo-?̔v ttT<GzLQ%'V"QOݽߗs4HJ{kp<r NͅiY<ZI w9"(%0y@Z'9# }!axJ4Ǯa? ;5)-^%M>Z(nIF\rsbG]%V$[-2=;{nJplyO&?QEs `oiω�V?� E  KI�HMtXrsů_&ًwdkJ-Lrp^-V/5Bsu:]1)wWWiHQ~,e(BNggX)QҷVcBMO{p7Cu)T�cbT#9kH]2$ׄ*GKB'3~-Ʈ! #ddkr6h}t6iWQ'Bo+{n`R&=ՎAw<>+SӖ@ HqʼW*WJHHnv."׃.5HeM-s/9V }jmk; Y:6G^/ra#r,8';ҿc WYs.nn~+ح։ơbœA.bT;BjRi%wY菜oJ,:c oW$J4 ej Zp9譧opIw</ '$[/�VV� *˝B&3]E!Rc ]l,l\egxrY|@:Ee,Uy0|/s5!0JӢy[rZۃI1g=Hg{,U ^n+<\8iljJ]w 81""x sL 8`p4#iEe T.hӖJjuZ9y % CaJ & LUCP`%]\8!i!eA吔S"6lìI&[1})Umz R -_y4@!.s˪N�B0Z/i5<(rsԠ;P2DŽ#u�zϑ0M W ~;뺺^נeEmƖgELU/fKRe`̬D50ifE�_/,C#yeAƏǓ�WNC� Jɽb{�υL=ϹU{]E`I_� dwYq֦*x9uvzJ;!A2~h>Dzjo&$MqPE\6{ ^SKjNAbL\XB:&�SL6)4V?DuPWa6>mnc:c$9Ad:\vߴ8导 .-,exK"(TCAC4혧r¶NޮT3HV1kG Ar4�[XȰ,\ؓ]|X߬;rucyAyƗ2 x< vO"xN%Ab=tgl6O̅/+V$̢?�ͽ`AO$aTO1?j%ʫo`} VmQbtDOdVTӸyߐ׾,\wgsL?#HSa'Sb }&l6jBJ0FQPUyټ�VgX[� `~ %� "ʓC9Y<2gдqϩ'8^~ ,/.ܦ Ϝk1w2 VfV ZB3 |#\wyxZ{ZI#FnLO܁~lApr Mv};bmA8g7rxǶ(=>-1Aoҕ|I|SѓPUU`qc+H<X: HWorQzӣ{ @tI BJ]-eaN^@._Bll$1J(zKsۃ s4B%d\ tsAPG<go<iD ՃV?6+M_gzRfECYO cԙ_<?$3xbR(w:@�OLnUgt Zfj=! ϲm#:5&$Bay7|#eWhTW5"[n u"gz�V!� @<&W͙Osy�:O j_s!YW."L@ ȵvcϥ-ch?-45efٓ\|QwP}tlI>v07~K?FQJS AX8'J�yf3 ~b-"5neVWq5yV ÛFB{#Pֻr.O4;p{T(P<_|zc"!fŠ&ccޛT+jRor%]pg_g?:_rfjUX2ԛ>2DYTY5=cВh<N3mMYICb&· BOz R=g-vo=:]7A^U @OTEI {(WAzz+8 mBNNG 56|x xqIm{' ,T);U0sQ`<Q�s]{r V֏YwGy�V� e>Mahy'9쩔 ౞J0xe��,FIF$o_UT(cuH8N=R*:)6>:Z~ąP^J-a!6>!č>|[/E== 0na#N7~PPmAc+}U$f|¤0Dpkq"{xD FYJ gw>Aވ`E2Q|N bDN>~YO2_@.A˒--C{EY\hRLw$c q N L/77 D2dqiظ@fQi_3$gLcr͠ .\34a{};1aCɴ*oDHb-`}5 R�a)6^wDM?a h9];'|.LpInjc}Ѵa:s܌�ko[v-M BW;;DgۭoM[ ~deq9T +Ufj �V� q}q'gxMdXT(3|-A4vx~Z ڪ[H&ھt|$팶| H|lRg-FVV˼6jLo%dVcSج=NTfhLOa/9mx C23ݿh6hyMBH[gj_9@HOKC60 QR/�u/{/bȍ0_2]0k㵕A!xVYg٥LJh*)wL?tUmy=jWjޅ?1_jrXy'Q?5͟#_bQZ[WH"$ H5D^,>G# t#&k1{`}]ʃZe4w|N C:NOFBcz-N +W[9*๺b64̫8JO)k+g.z,K8kG֢,H �U� &tF~J7TdK =]mxM_!$$Nݡ"18:ufaEXnxTާNOAsDuNUt1�W^{geǪ(aѝ<pORr7@bs*F;�j3L$yTj\Equgد)'®{_5nVlRkPf6 ^p8˄Q 'r.6/1(xؙjyM7O6u&.mCߟ ZMLi'-eZ>LrUniOF?wv~iRz )@lBkï&B 'J]Kb" oBok^%9�LGR;׉e`v#@Re Dq@a2³bRZW7!X9j�B+^fT/X(_Sƿt/Olvmd`ځeoWջcYwґ1Gg\ {+cp#x\ �V�1� !xl(y!9.LW ;($ajQl8k4I)o7 -}䷅)$YXv\׳/ڈIe<X~>,M]KCF:k[7�ju' X` 8X1њr<:Aw3ibZ*bD*MՅ@-mْUeD4 5% 16Z@&[ت%nK4&^<Td/YߦNFbf~IIgD!8ɺ o[7 `GL,AEp}#ֳ}J/]_0Ƙzs5vxyQSn'fvo<- > }0�EWBz(]JCpK?+4nlFb]XI=t%PH,5{ʵ:g-pq̝˳J4wGS$Rq"P9ѐ 4!!Ot8'+=ZrM)D1O{�+Q ܉ �V,� ýx2h l0jwˋB@'6N`.shΌc`#N6gR41<B=XdϮc'gev"MH<w!Qzxԋ1Zf]qM`63#Y{�hùUrhzSԏN qc&�.ڶaKz*LeM~/L f@97 _eUܢ5i _-wIDJ%ti�q׾Aalsޫ>W-3ݹMo؜*ٶ~x xpC`h[6ݱ||S`vy܁5Bqno+t)="U\u^D!ٖqbA͟.\<k[˥v}!iI y4s%NeksBãb=1|&ǁ) ]0Fh"+$ìF&,A\f%R'jj걄6rj0m+0˷avQ �VR|� .8pXts*Ž|[^r'[f.a2rfj{](D�cJcH|g^m:M7\rRhJӕB֘ݗ�62.!*ӋVRF՚ Q|*h=]rZ~4&�}1=] ~I}4F׫eU\6q)VhG04ַf4u߭X}ՑY;0oT2]toO2ozN9�4 ~йrG%jGd?VM"=E/*8aczӃpGM6SdBeۃvp>ۧWǧ5*^e'Kʹ]*a|q^0JqZ7E1|Ďv0uU֎(ao.\h! <``t29ILOК?m]" e*ܪj/CV/ *:B34S$2 �Vo&d� �UV^yYFeeő(PzDV@MaԽhNMRn[`Td3H*\?ƴ26"lh<ԌT}b* rs-3#nr A"m'0 { ;ԻVjaY%\mMȷAkw6[L�B=HvXiv8A$%D8T.OR<ms#|T s\?&x.L�.#}W;л^ZD-K1-,46buYc>-P#yk@ܽ5w=#pϛj/A �2F檏BZ0ߩ:iӀ<Էwm6HpV?NRD{ٞwTJRqç+gN~sbIXQ D_BJS&ɎFK0h5oS{9T=/398Ȓ\X2>r/r]$YYuMuST7n?} �V� Qm͒rX�D 0'C},nKA I$�k(q-@j5 zc!m"P_6-dyaʵny9+ 2A~::E뾶FAfe<j}-[<O3[uB=`:IʍK`BW#69ԫ!BJ?8{Ԍe'*Eʚ5{s 4/.1lĊB9fz;Zp/P7bF `5&lyaNCp\І*hW j3RY낫Jߥ>: k%t"nbsO~הigdf<3Ֆ7h5:u$?4n?vQsثwwK qD,N8a@=eQ!J*\OeY{Q#y0pC^4QvcLgĢ7x?J8Jn['c-p%7܋*䜉�VL}� 2|L2*p(dAQv34KPE Q$caVn bx ]97۬,bb&!)Em"By%w/\ECSv;g >QA A & }\%ʚkp<2h&GMm"ڙVWgZ :M]+0J,ldO=lY()k/)ie?EdY$GlT3BU;0#v _HgBb.E ܾaƒ[8x؀n>Ї ߕ< oT~06-A\uIafֿvuqj4T#Eo.\b/w{T$ ]I aتt8o3EǏ/8gdնޖ%ݲFuouL3k 'Lw4 hXU ��v.Խ.8K9%s/YCJUzS Cr+Dn>L�Vj� M̡{vR Rk4`|t$i+=;H9FZ0\.r/h LPIA.RR)сIQB9\r<Jiү~^`r;7Hǵ1as@ Q+y6a ʔ`#m&~7 3m$6i3+_Ra<"uXI<8[DXFttՒ}ئ}Budu"-jԥ=?6}4_ `@'hcmQbp4pXLZ?n'%ƭT)ܚȠuD;tmibzF#ڧES ;uFR@ѿR_%Yڮ"D| ,2QWO^ٮ{=gW\$`F,OzFHz$(+{nM 4D&PAOWp̷<BkÝG蕁>(1Teu|ū_]WTw8*�Ul� b@1.�9 {QFm_޹?=_wD Z!X~ɭnUWdWtΊ-L U[t]@XM%= ˫j}Dsgđo3z/|rɟ'pؘB衏 S)�?k$[�̃<sT|:~i,?((f!iXثK`JzDq-_z5a:KCV#1]w7i_dOY9pLg3d%}6+ylS!e[=mC FL8*0\K*# Ǻ%6|m��rfmZAZWГ9c9nAl8&j,\έ?:Ley B ^E3:% M fݱ5 t۵QYrX 'e.V?ЦGHchoJEwi1OOj~ = �'   �VeP  Ҍ� 93{{N]]~5 ^9;o co[pfnMIcE=W#AL)mhsK;h#Z7Ŗ"gf}(LJ10hVGVIGL/5`j7sirv*e">zȑ)' G2t8Ǥ ;SڒY,tXItUol>\ьS~.W|7� kHyg}k,)% 5e⇕tTSn�U9Q{ rErh;>洒4>)WYd8x`Oݴ߆3QQŋ1-C!bݟHk"pE5@ПBAB-& v95oYB;Ki<NmA+X](4TlJ1,_}8̳Yr61橠PɜK/~҉U �?V38http://nicolas.braud-santoni.eu/gpg-policy-20150222.asc� s-t X񢡑: @S_F;8())vq 8C \%d5]QP̼5[Ӏykk~{�p3^R]/bw@-fMw}SV^5=X7J*+<ӿdS زiQI~Ѐ ,r-p`C`E'TH)Fgܘ� }G@I:K�Lפ‡rxa"LF3+L�*c!$xDX�SAn 2mPJWanG, jEaT۝}RAY`x+Uks!&S [f,^ja>#8>UHlF6fI~/ JUHD1&8'o"t>@B,"zCrMa)]a^Vv]C9RvdodHw( HyLc?Z w= �'Vp https://mike.tig.as/pgp/policy/� n#W}RB 5W֘&/5?!P,4U5z0Nih'E B&-f5*5(W&;ђ,`fk 0"bDW*>%4,R` k1zoSl$Z3t=;Ǒ<Y*sa/!Ft^B/-٭}~2Ϸ= !SX+bnK5R%`CMga&Mu{aiUժTEș`P'j[2S! 33LPi0FoǵH". #D7Q43$bZtR�o@(aThk*F_UM/35--ͥ(yu^ (*#kB[joc n޴@n#D\PN Ѩ\7ݭxaVIπf&^Q1,ۻ;(|)t&\܁Pۚ~'OxĪ;4 CuޞKMC>/5SZW-^{dwz T?8 x/YsၚXJS] B�!ީpBrgM~EYYiNK!rƤ7/)*~I`l6I-BΊ5A<b؜,-yy߼lc h1s->\dU);!sbн İ):[R| M39e=scU^$iCq�Ȉ$ FLƩs( 4F�$WAW&5xOdig|~~8*43'Oyb@(}vі[z CMtz[3C$S,Ɵٟޭq("Xs7"%7p| zF\* wl ֛µD>Ȼ wv`MZ2ZOL,Ծqک_~<iW] ÏDeOĂCJ-O�V � �m7:+vK+=CM zGC}4?ѩ\Nރgs,tPi!WҦ٨O\IX(l"8?HLW|,svC^L?7P pERu7 pT RTdoV # j#Kq77HFOJ!HfA'#X^_|8$\\P)xfn"+}b8gY+ <`M hKs})ɄOJJ| g.{?-k<5'' źfȟ] Q2__ B+"oMZYTW2F9u䏨B~'n7? ~ =m Scgtx; Zk[@ЦY89`  -0gkህ&CI%8lMn\V~M.ւ܋b�V%� I#)e7�ޮPdqg> +!95(!+F _XmU' ༞ea"\͍Z=f,y Zu*Z_51GN_/_Xi@oM¯b#XAN1`YEN[ hjyNCb<Ǘ .nGQ>hG^ݔ±r#oOA'Kѳ*4*G"eycǁZD.{E'@?s5C `0V*MYjm*r-maVy˥ cr3r-\gjd΃?occ#wZ 3rFuVrFW23rNԉ);%,4d%�=3BicK/Q菚t4 1=Kg]Vʤ.s_\7}uXHm xc$2b>jGf8XSP0dF䋉�Vn� \)=�_p].K_ZR7Q)^`X"W?Qqz@�yQ̹%iu&KD4{l_|Ü(i:Lשlk,=k (whym~/B?vd9Rƪ((+ij z?gprΰG$X$fɻր]w&$H*h),q,38}Ck{>�h9|rXI||⩓EvLg_F%eDLb(z) \w<[t44.Ы~*Z=s)s2*^ԶvIEk&O#VN~%wU suͰ cl<zQPlzoЦjaAnW%!$7rC3:K3|i4=FG; 1Մ-UvO ȷsO!p$$`>bNͬ思({I,28d KFĤ�WxJ� >͞�?w" D{02:ݟGYRh{.B5׬?%հ'WkΨLݐYeF.: _;2f #xwe5փ(^<{?IA89?wŠ1TwL5 'kg{-KTyu%lcKa ͉(\޳d3L;rP1IMbm�hRNX5g.s}rBP2Di]9BЪ\YSgkf@1)9%ȸyeQp0.U;`,iMBMY.dudl}9`N߆X< ]3nt `jZiU-`?B!" NF*7Kz6&#DN>ܵoGXaԕr0|N1Ofﮍeo,/:,Q|fa^={%k?mj}�W<� C�F[�#*vO/0I|'4 R-JΎ t~<:̯!]zN.bCXg�`dN)Yl'n/IT{uŕ6#H61. YPBv1c^y9pbC ^0>%yt9Lbuo9vBЮ�08uNrεcָ{);>Se襘7v^!" YCɃ,\ k2W&x(@bey�w 8ȮH^B+iK,.@&|n.T?o%XZۗj񜤱<"EX6|A47΀ɤZ& �ONqRLJQ �[W 4@k''~ !)&e"87ypu(f!kC-5C<Gݙ`ޖGzQȱ:�M Ip[Omd@_jOKIFs^�V� _;$mc%�"G%WUmqSR&29q'jZZ1CbdFvk_x".jّ%DW}j)" R;3H *?ou]$&0SNLRuxu= $)a˺GRAkw;/fB^*#S*};~5$88d*2P=;Cޱ^Φ/ wBl,mZ)>7DFC#vħvI&/DbSoUZ:*Yfqe*>׸;[G௔dy>`Elߢ8mh.Dch L6om@_+qnVmҸ3ay)dlyR*ͱ=;U!-�[:R27;QV[ZDTzLyg[eT1{gǍ\Lt6TAY6qVmU"z#y �WR� I#)eIPr}:iG=U<8Od>W ~Æ,BH Bsl2tL2:̋uH:H`Ւt2G$̅wR$SǬ#OFh|,‘�fQڝMe$l9#g`N0ug AD) 'zmG{ W ?b<'t\פM<VX78V?NAhr ET.y=0,X AH~<ƚJiA-N2VcYa=fͯ`s5zB'i֟3 ,3%MG)d?䱍өJ+2?(q3o`�oT^q r`T|#|b0?ΖTksA??G˷ X>ttܨV0dMcԐ<-1DBx4W Љ �W3� 9'bD�[ "޲<xqq )M8Dţg2d̸b |KheL,ыר#5~(ڪKȧP6U),bll&c3uF/4Tfy}Ip-V^䋞?&Mc> BEX�3 P>{xW[}<�Y"xޑTbp7VN۫7 ،q-ɔ`TM/N3mC"҆:} AR(Zr\Wڭ[YHE7C>##o3:|Oiߣ!8JME~=UL+g-I79bnRcm91O:RYXnH ~ $Nlrg#7ġ Ĝ× ɤ-PaR#J40 7TK~@p}ob2͞lN6j@ NSSEߣzT �>   �!得 u@9XZ ,� 9cOx%OǏ(8_#]aՀx`(kyi l{鍇F~tU<ftZrYh`r֧dIuD*I/LJg̪~νz�+(**RAׯ/x֐lX΁5*OQk7#5Vy9 8 a4 !&}Vzta<<A+q9LfO3g•D`<"g=(;`pyMPe4} F,sZA| 1U~Ӈ6PWTwpHZx"C⍄S>@J;<oܵurnvp"6Һ M I"KEJJffiص/pc.sb׫P|Sh/N�qzt8䬷?p%~"WGHtHNdMA #z5S3  �W[  fO�����(verified@patternsinthevoid.net0EE5BE979282D80B9F7540F1CCD2ED94D21739E9K�����(isis@patternsinthevoid.net0A6A58A14B5946ABDE18E207A3ADB67A2CDB8B35.https://blog.patternsinthevoid.net/policy.txt� z,ۋ5'�b \A7~ 뽨7߶v,X}/hK6:0K9i7a}QDŧPagL�(Y >ͧ)(ۦ;mݷ\W|\)F\#"@3Y $0NjŦgGE)&z,lfV\g3Dlb[ 8^|MhIfy}\7դƛaU3HĨNX(YA:O VH߅ztGePh<-wڥrU>FR ,9xHcB5WBxV*t;^5]x u?X~W|?0£v$"0qIǪhRpv diLQJ6nn9xX26M|K+s>fSRxe-lsO#\}<>: ~$q^|9{Hi4�63<$#r8GUoOyky:úΉ�V[� kXbvۂh"*=!g`iA&˥-ɔWXJ&7N^瓣C Lp{jٵ\J\PF9x B5-lC]rW7l"'՗4^1LrJ*Cu jmEXXS<-q r�j g%T7v@ s{ZTcuI!](_p(N7TS `#ItD=-2 PrӧnЅ*`W -oJw,x)zܙbGD&Rvj=: g3DvU(y&*%}f,Դ�6cnmpXҭiNY#ȣp~~,/!JTm&K#-K]$v!]$9W^svt0P-Kf�ME`҈ p/I4cqvI%PDte.RA+up;P<캆gUQHڍO Jb/؎-i$w% wvG۞QHkOWnFocy7eSs<ժ"s9Gb* T}<;zhnJAV4cT0$ Υ[#o JU3? yW.N#ugU#2Vr$0ՊrsN)MlOƵjɲ"?]f&;YLYu2ߞ*V5T =Fkf!447 Xvʷ,t 4U5З`Sw�Ϧ0ŝGs1*(+ 3b<sA 5HV(Iܠ_U#i:N,-#:&ޗG?H`CnL< jM?~KMa;c�%օ~%"}-DZ搠z/RDZ WWʳ ]Y>2ܺ;Xڇ9ڃl<3�!oP@lH90Kxs+YXk'� Kxs+YZG}N;]߸[-"zdBy5`OfY8(2aFMG\8CE^4"EǚAȟ8.>%$,Wj1hC we ՉݔVsOSI<{s#)^W3,dF [-4 *[o>MZ"%֨w Z 0)G ..UqN-/ lp& V^捼 kQe~}3 �!뻉!vΟ PZp�  P�׍eJmX :4 .0 Qx.йaZ7YZoے64-I9Vɔ_ 6xz9QZy%"yKֻ̟͗[ e`zrbR\QE2Bbۊwĵ[`NPJ{eU6Cyǁ8qQ'G*sڰJ (괐jZZ Ӯ>INgʆ>u �ZId� ;{-i TwQލkMـqns <L'TڍۧN3Frz\yعLMUVl.11V֫|Q7 n.EYz&UM2"9理U~{h輝ֲ=`<@#^νaXf}>EgsCeNk &3}/jM-,wD#2a'Ռq\ٍ Ļ2}' "=ȰlyÆr c W3 )?$WZOC/Fb�HH>kB{czowIШa$&(b[s2cԡmdurKkT *+9 _]/a_q*y!L=S'[ܒ ƢgLwږkH>P$1A{sGk;57Uh+v > Ln|z7-j֍lu[E �Hމ3�!T=8ݸsz6+qXr� +q�B"nxpyEU`u 8 ٧xH3ܼ9U]cS2Gap{t&k.&X#dTN^WZ!ʨq8wIA H hahK'Pv*WiP%_Bh T75 FR4G>,l>92ŢH|2ez}ҀqiH<֝١,m{lE+߳cTf "@ DW ;g^[F]r,^hai[ ɌeRc0b'``CtCiJn#ՕZ?;d`xTõz 虇b]|BuqWCOGRK1\%I|>҉'`)s}o76_}3=< j2 UQ3eDK7yF('Kք3 (\ CbT �>   �!得 u@9Z% Ƞ[� 9.q'9Qk^Åkn6cqKJIO#M,`M1ڛTi+ə*Ź[_;]YYdJ;tjREc~WY+I^kQwS ;Fxm�J8fjNl-ɰ( .ofIAnHnxrb3C$pc"kts,88rұjƖ8/.Ǵ'0d~�[)ZoMtv^Rb i.yR]A_qUIsR䆚)1H@፤.皑D0\SINvAɓb 막s7"UCNO}iEuJQ?Jd21P&ì_u tVPM"vE,g" 9]vh%ſZI2ekQ'|G;(cD|@AbҁS"Daniel Kahn Gillmor <dkg@aclu.org>F�S/� Z '�O(R]el?6� w;ՆKa+|F�Sy� ^>GN�R\d&[$[o" LokKd�Ah-)Fa+pVhF�T,l� 93�,?L^x3ԈCGP� 4ybC4m-"^"FƈV�U\� 򭅬Bg|�cK{q+BS$Zϱ຿[Ԙm�%"c3 %׋I@?J=9�S~j� 8y{T[ּԁ Rk/]݅n2 _1l#2&-kș5x-A7 75hZoZ$x XP:@LDOK-nr:HS{@U ֦E:X3LXÃUJq?1û2aIx�nU!iϑAj<(8PX$k:ޓrHXFnd6GQC/" )W%T{mu5R_{�Uj3� 7V�3sJY6}hUtϥa!,K:4VC(Y|WwFv:gwJ*qG+=V:o�H:& )U|4W$>m�;#ZdT2mYFۊO yWy9S;~J3B.oʤLSּ;ިf۰ezCr1MJ]c4}G%4e9q]9#msNvAN#}߉�T4M� �^LNΒ.9B0Hԃ8̈^1eЋbe1Ɇ>d"w/HN_r`R\BJ ]1<։i#-^OJYSL2XnKtrzRf=Gq X ʿ3ozjo$N:v_5nBlR1.</oZδ1BGǏ^彲b~Vx ߯XYRqza%Lwi"z9p~\\5�T$� Bj*6B�:KJUW65%0*nRB_ES^jSJE3t @'jӤ:o4HFF,xՊ͏YFj?N&H5n|v#2 D%숄8t~?vSdX$Bn8?ş5%eE<LoVVݧNZ5BU$rJ uu]\ά9Fq8iX<K~V]лU SEoE' *n:ɪ�UC� PKP+]t�0AGM) g"=5 (H+f$k 8Qr.͍[^-WmPR$n\=wI/Cз0 I}ϽhP*WB̸e(bH 7R'I+3kA-. +FI,'xR|[ U/8q{Pv%&G}|)mA[v ;c}P= epZE܉�U]d�  mGU�U}�h/yY2xAuN.bW5 f0/61TI ,^1U,8[wybww4&(f, QC^CHˑkF fFKJXA'EBP֡|vPW JZKSt3u }4w>/^4f�.#!#pEkĪz 8rO�gP1ѾopgՠC�Ta3Z� ^kvF#J5m|կq #_M`5}IռZ4%A|O_vZz-$&Fړki%yyL]&gn-R2>a ,�cRa /F$OZsP{qֱoӠgR}9n3ލ߹0aR<H'hK>8UϕҌ12՘$2d?7`>PdSiO]})n9UKsI�U؈K� ϊKI _բQpuU'練0=$ꀦ/JODe.,z“1BGhJWjtwSq\+m:8BV@cxL\O=Bk<rفPP!<Jc 7!Eq,-iӭ9[(L=MP�Rmn2^NuJEe^Zi>y rǀWk }9Զ}tX:˼J;?yoMB$H&bM=N3~&&OyONkOU2s%E@ #9y "p8$̙) *Dt9a/v#,\7J"[J Ph1u+.;o^oщ �U � (٦dul R;4.7(r> |r΋R );a€</a1-RB0ZsdW _�Vsi`gfkOjh4-$OT_\RTR {;#/v@ڄKI<-Al/Mz4QIkIA:"{32P M-:ao�k1rY/C[ 慛.D:-oQӆ4~Z0f>Rw*u?_jWDRAqdtd ƉvcP㲃ZSHLԄxAFR{22 "\Jbð LjC4:v.7g8 `巣8U0Aw"[�S/� C0RVT�ĥQ~U@ꑯVDVBC)cۦSUٲAgey^rw?!kfTWP kK<뽾ׇF)VvKʋ5YIyۦR¦ ]#p:/6!)\d\q.g$?*f5)ӿ/|u[+*5]6<G{4S^-W%؏.n6yiǍ< [{K݃r="$POWYJ &84+2@uӽ噐dKc`i9vՠxhTFr@"4ؤLL$9v? |GVxԕ\X,|'s�r,)+}젫"ADj0# 1,O¡pܻE}omyF`dpretCN DSp7vsU&%6{ʮLG\d0uJTGϮ �SkE� B2Q$i(#]R дk&U| YhOi)x_?wzhO]6 W}K[(C/;:ൿxIIb>J e[3׆eG7B:2mNj ۸_wѯjRN BcqEPd<;>b)6 Qq20ؘZpIdA/ xI=v֊EHUϺ3z,L;mဉ,<Z9jX<LsGLh|lk*mměqG5w\' N%xb /_ F :Jpރì^m-/ $'rށ9Ѱ)6hFt+o(P„ z͔ecg2/a�ڒhdi"j];K-dX¥щ#T`FEE8mRaۭ0(r! Cǰbۢ* YP,o�SY^� h S&$ lKApyI J}n:\O޼Q>:6)DvN=A<  \l<ُ.C=ۮƜ[*$M,TS~X஄vX_(|窴 S獍m|SW֭͝ЁlCᜓShX 1�9Q&O 2 \?dGDtPx209s W"rUAch7A'#gB/w[ 'bǫ_12awJ.J9~D3Gbȏ?SЧ*Y&IdG Vq@5�iUr"dBʾ.;eѤaoPZUщD7:0IH-sCC(fi݂|)ɘH Ɋ_;dH,!nh]):?V/rVI �T-"� GG䶁=ӁǗt 7�ITYssc(:Xv 8,8Bqr{}rnU(zSυib^DϊRS,$PX8`!uɹDC: 7IA~ (t*S~qLOz(}t˛O̘mU /c3\:T{{k&fc+NtH(ǔuZxS#lU^vu1sĊ=n F1˛c <fX\7eFM\D0'nW> $zW+'w.Dg`lBzkcf&UsZQxnMs30t$m~P CԽDOHhc:ə[GÓÛ&\Z"ʻhMh3Dp`Or ]UO2'#bOsrq<sP^ҠbN%>}9Z0((DQ8 UVaO K1s�T� ncK75R9ӪA=L8ZRO hn1a� <oQu6͂h5<wx&eL-?(w \|b7$ԗ?Y^=[YDGPy,>:1!t_sm{}P.r/kYϺm)[(JZ c53d 붊+iGp]87M@B+ >23}8G]<;`퇩(u'¢}˙4IA8^fteCR+Ä+S'7nx52(d&8-[ۼk$K@qڎf󇆪sk)d,LkSʌe\ZE)~<f' /~WO38vVRU_bf)nx1Cj+VƓOܗ9QnȟG٧1}M 5|Zm\2qaPqJԉ�T6� a;K;}iyqJ=_,#'nlZ/Ěݕk�l>/ ܤq+K{Lp~$΀K( ЍBz S f*̈́앧7) =gL<selu4y8Ẋ~]<utA15Rb&~uqd3CPTJiEle{;Y3 $?p". ( XGsrUib8!$S7R J- eF*BS24G <x,X ?I%Ab},HOq&)kxwśZĝLd ox?MPVԾPTff.B1>)HckFǬ5Pa" n _Y~!Ӻ ׳o(=? l#חTg)W]œ(KK]BZxt\ �T� 92߾Apa;3%S:Lwqt ]Ra=U÷PVҵIX(p=i X5Tr*AIwo ѕtp +տY:2 9K5-(Ɍ 9Wh^hp<K(Cly7*P /ˬݗکq+�|BevEJ S6x{k}#,7';ő~.$"ыDD|fhi=5; 9$<A<:G̎Ϯ-y#W۹6Ӭ}]A624T- ჸ2P_PS4ZmF#f2BPb*]'<d?i7pv\Zӹr /uL3x֔·QN_2MȞ-k)-8�=| Xs+_,KHJmV^@!&7IC;3!)^8kɹDnXtd�Tu� EQqM)-rk?2X;<L,Ej 9NuqGtf499~iuK5[ R"=6?Z�+!P^<@^.kMR֌24P̙T-ך)#5ǔR3}_6"2mq]c@Oád)4c_#uhl#)_tT |$ gګ0.=`#?x7ݒOwrocۏ45_uK+mӼ4j4 fz1϶]i9"E. !6t?+w15nYNH MzfMNyܛӣ: Db};o{ۖQ*{#;@a?h 0Čǘ&LSlixm͋rYdqZB{CmBnwD~H_b;a2˅gRh �*:D"]}_&ȟn8H} {X ͜cPm qipsֳ�T� 6\ @{7iH+ڎl;X[Pj?<R5KBWK Pl)N]wBvP—מb@fDo9LB"\W>OvKt.DșwMp!,=pSDl_M' ^RNM ,btw8&l *Or1^9HqeO R-O <5źAIWWrw$.@@Xqۣ" @Qb%\X�ͿU/N6{B|+@d!p8q:yT!:+G, WF tUUBl ]3!ϴx+<]M<bݙ$vmiW0xFo"t[ج3Hax5SR, WOYY,q/Mߓ]aGf@2>9Ǚc_z{OiQyݲ|p_퉏x2^cgҬ�U� T70Bhץiԩ1>R~%I)LIq:5rԫ[͢QjQM }?ǫ<*.$$N~{_ 0oz] oрR7i=yuG@ s&�|fEt"DÀF(=ƜJo` CAMyZ0q7Ja aۨ; /-yGȊ܈{Stw+:"skLb7^&е ]uЎ(RjHA _z9ƶI?l̢�Ud>*~ ;0 J/rQP9D$bћ/[g{AjI 4`җf΂:EM]<)om/8j=/,9գބ5w>f@�eDžJnpI ju6[9/|�*@¯M;r:yGx:.9h 8M)j^QN�Uy� @.c b�+4z2(fh(?V , p}c px!|qVV 'sasDL#$LHIBB?ib\kWi>Bi#k^W&:!džicZ+ i?tX7}ӡ12z:nhY=&3bqhHMY%yrkxO"^9eܓA9 6K[ t8tX"b'R!>z$ B1׺<Ql8k%lo#Ǚot.٦`z{`Rb8uDxѲJbMf@zMAO۲x7; Q X�*G`3'­#L#U݈W! sNW<H.C]CځԖ}>.kZE;p'mv'1'I|qP̼5y-ՆҝbBk''n龠�<ywR2Iz)RiwtŗnHHB݉�Ur� OWc`}[cz~g^ )ON,6(sQ Cuek~mbDOOSZ$mnW hs5U6sls]mk$R<PB JG=rH1<H/JfxѭԪs(}?xbG MkVPS9L5/ N{4ƾLx'SXpȈD/qg\U&p;{6d+Of&Y1y=nYz3 -TxJX gJi!]"(yݫE0-x`Oe/dߞYb*`V6ʙ:A9Bʽ5 $מ_$M&c"Cr|"0W'|,/�R;,nrȚwX51ћ[F?  !WJ71iFu6m*4VRtt\jfhL' q E8Ihh(}-,@df&e �Ulc� f˔wk\mVRdMl~ @1v|fN}�B-\"3UOK9q?K?sGR,V,pnx$Eo%rR~Tۄ+mcW4Kt94Pn}O Ά_<M0,HA3g5Z~/J,A[TW_phEugm-z QЭw%_1;_O:!g3//sxUmQEUa'B5/jS7iR4mRBi"#`˔b,s%aKr2qϠY؊RޟH^09 Op+6 &K{FxuP \Anб%Yq͏dtb_X$9z[?cFJP 0&;-Q za@|X-%K x�SV� y5M8 2tbey>fC[:TֵtNhmjpt%(c{`ܚ4 XD@&{](J)ҡcv4Usts;-ckmpPyO6ZLkt [yp,�+L}>W ߜ%$,qQ Rw:S4 0Y{yhɟ/3Fx V�wp~*U"׿wbz&f`Qiʘ'ƳJȷƔCT.'72Z)apW4f%(v.B؊&,lbb�ҳؔzn$\2 3tU _JQ@ǖj0GMR>5ӝή!d4S/t9qN9 Lsᦒo),QU2XV  d$G)1ԚY T9<AFz{pJ ެn[ݒT`x^oZ`ľmExz�S� ]18�As5Py9xTW^P7ρߚ IW iLٴh5j£y3Q/E~@.S틫JSg?]VcdC<ؠ_Ong svDjA[ eC-S4r͢GfվW%#z5Q&Kȯ&ʍ12lDF2zݞJ{icu;i//p~C!xcj h&67yZPF~)nuՃ-48ݥA4j( CwxS">r&Ш ڕ+ rWew-[ M{D>6)V ^+_q09wuNEhMeܤtb@rZ%7KK΍z5*vOEx[2TT8l% VK,-{:\`[Fw :[ (ߛS}|_1cG0hR%[үCq$h�ST� d;"z4"]=StIxqvd%wj]oˇO^I:CĿ*-tjJX78(gڢN)vU1wW/A ]YgO݂C]DžW*ErvOٳfCXzw#dT$fQEJ}aunmH|P:mW?Ʃ*Ϝ[$tRh 5XP4?!#6ȬD<T |`G|)>ξw<Ǝ<[n%RYTž`sː+.b0pՋ#?ݝuq{k4*xM&;ߘF9YY*u6'i.1ȟ*/,4$ ğj3i /^סv9p!KnE23VS%+;;VWئđ6&0hں6g�+3۟ <s:Ĺ%ĒvgV.Ft';H|�T� 1P<mc?/c4>\Ys ɂ1/fӜ/i:n#z{9hO/ UjȞ(IE gT~V?b虢:K9nWa!C%ӫ}*d]1\%OxSn_fP*QF #ǎ 8ooTΊoX4 jQ4Ύ)1.91풣!teB#R!#cLED3B<``kA!&v9*w&T;<MvP((bV;oFX U)}w kQF҈:2uZ%"TYfENm)4sJI9$ 0Eu8P]CIQedf :1)%G-AŠ[`㜶 "*A꠸ > GȊ* SZl5ַ06rW۬tc>ν[P&\Nwsu LU׉�T$� L=X0kղj?):H=Dn \x}r"A` ']U|/{b*/J#@]&$u~}2KFKǍ_G8z=|�@Ow ? v;/ى5&Ifb,4/t?ۋEĨ)ꛍ f4]RpxX}T&; S8!^U-T*PRU2P6J\|D䑳�sOT e}"Ўʛ :(N锊߲q^<x9�D\7_'tֿ2i"+f[{ߪ}ڎgs#o~r*,H,C(a=5$୫It97܄CTNGfHԙdi&Ԇ{gmad<^dnoJg9_%K~֍`]Ip9ZUZ- *BY)jXjŝ1R6*)+ } l1H cW5XiyV�T� FOjYH�{lX@E!F ˁc\0+g`y˒P c=d+|ݿ;)f,aϯzJ~k,[z$T"U1-9Ǚ Dd'}țKKcu�YiPp 5Abgi{NY0o26hIZs2l2[|V9|�ī Vլew9)!&+?%Fэ~ؐd_<g4\zOEC9AݔC9Agxeim=шors. ܗ<^l^YGyS )DR4KOHa#ջWDO+s(jLZP[-r'LrH'wDdl[>]mw|HF -Ǵ:i#{b{߬LB Bhx�"62crCA***t�T� ;V?ܱN�Rݢ<?2&<gMhgu T VJВܒ]jbZւXN!.ϝ%"P켩~s|Y:¤Rqjf ٙh�ô JcĖ e԰f%sV6$elt dlfBhi&~|idݶ$;aQL c&Tgo7}  S}� OX$*BHQLknӗ,8٩Z 4Kw[{|b]R 8 ۆn^N#)14_Sⳉ)?ᅄhe r8P4BCEAF5lQ|6;E^b%LLS'E禠gY `7syd,VB"2>%|5A<M1؀(pya${՘=1&PO̴A N13B�T � UB̥#ieUr!1߹ű*R,9pmGzrA_{ʑEč<!Veb!9 XuS>E \=f@+lrBUӾ+! G 8@gCF#ٯoHSTFZ~ g /09 PTxb:NxD[<}2&*Sq � ~dGikaoDNY剘=,kiL(0I}jrvY2x<K"@"ت<_b!1nHrv@N� /Y\OKovђ1wH˃el"9FgRJ [M?W8d@gV6§q w$wAl>>~%PE;̫ X#1mi+i `5T?bhù3f6=5.@Nhov^C9"bx2i �T q� ǁ �\WJ>32-|c&ގ)ΧuPMsum*g@P.Ymx(cUjv%ugDV,fR0|^ӥ`A2Q1WM46'S.4O֘x),-5^/ S :?\/AeI�/Xx nyKTC {ba+x_Iָ39/ 裬(OIẖ 'Ӏت9KʐPMiZ)$7\)~X_w.;�dsb(S ( ,#+{W&es8jZZ8@�{5!"ʗ 1UVTİ*nHxs]KjmvWe%BNd߶+6@ze"\hl+nMCTy_p4ϻgsW<p,�J/M3ٗ=F]ts}J<:=*@A:Sm$�T � x݌�DIBNG2"hcKmA=ɮ6q-V)Ycn.H}Ra Xp'ARwHC셭Z bkЈ{5ni_]4ʹruwk^`ލlfG„cؘ$Ij]YlK BIOE�kC Jb^x94%љ̌OBv%EhH-Tī>h?x:\dǶ)֕}bIՕL{ q gab>WA%ˏ7ԽsAQBOv-q9|v^(EOz]UFy8U N[<dE |n5~0Tۉ7Ǚ<X02#ζ dg8@ij:ҟ#4vʇQ)WAȠԉ�T/� )+ 3.%Q(�~Yq>'$ai,K3.#9&3'΄JT:*}z=h"r1 G]qy?)ua ó`R)0MX#p22\�8S Yn􆙼6wߏG)/䁉aAC#\z(]8cxf<Ŀzݷ/4Xц )+LΛ=~JZx2`b`J^-uճ0E1⦎ ш4mO)u?96*(ܷ#'*x*QK}j0C3s)u.Qby�AQB ML˴z1#z ')[N yFU0tGrQΓ[T٣?)Kn`rwx" /)/u?KrN{돠 4E)c,$+oqf;?`zB  .�TJ � U"Ԛ1hԌM=Ǟ멾q 6/*VC88-?R-7{H'dDYCuNY39] |f͞d׋] iKUe$B;s/BPZݬe,zjX3_OR* y(HK+YSH0^PȢ> SGay ND+YE Wnjp-Dj [nV -na_]>:<BkI]䪥J"%veIڅ8͘o< :ǟ8)TPVc dɒb&(kG#.Z{|A8|,& 8 :0me(q;G4h�}]X8A)!)t p q}kF \yVh1)a ^eL .1<Ӕ>~͝F>Ft4>9\�TZ�  `H{cBuϹy9|fV}ޑ@c7.]0up3Nvy OhBhPEa+T矹WؿVun`EIGatƩ{?&~-`f|ey@}R.OuK@>G#"(c)�veXzY&NQA;+﹣SXYk\JZ%?wfh!o.e=.½W*[)O c4`'Sµ_\VwQOU Hg#CB7qQ()32Bc{tjGS@mghL~L/Ml^e@[:N;+=_3yϷth<Ϊl/HCP>oOK+b\7k7t-?%-}ɾ`=Z$)ѩ]:Uf{x\t ;~Zx̣�T� bX/�HL[rgD0T 5*P_vh 1X_n-z}ϲӤ#eŽ# \1{�J_TsDPRϰv;a88+ʂKd]n<2s(rG^Id5l M�[8s30"gascfKʩ177ހB#C=XdK` m!D:Suhw%MDB>zB*rMQDr�HI5+'Q}oY"\#377WIAף&)PnK`[<HM(kDsv{[%fgx[G`)kw hԹ{IafGЂ2OdSKueW!0'L2=f-bt7G^:蝰;@;`/bnSY6{ZxfXϢB9GBi�ڼ�Ui� jb~^$t�)yJNV]Q9wWB=Gn0<O.< 6>7bJ0ZjO2)ːbL-mGfB%cM 1l{2_`AY5w@:81ArvQVШU7J6Xc'NA/ɟqhPP'yM2"՞꥗\MObU3De\b NtgG!^Nl%DUJaT Mo[wE/§*р­NڋE6}T_b_ǯ*X^9r%مRgv_G<bʀ\+ :w8*S m mAdnoǗm"QAbF �PW$lgI@TzqvwPjU7!?pa1`%5/^:B\~MK_ L># C^}npe;(kz=ƻFr\_5OTo@�U7� rS|e{^L`&ylRkq}vԓ6֞HA80 )ReY?7б7Wq.b?x*zLr|!2YQl=C \DZ\y׫,jCzOB$"F;\xxgFAūClǺ}MzzOڒÉ,/M c{3kϱ DM.D:lzd3?'!47(;^ڂKJ$˵:VZu\C!/#.ҀڀJ0G}\쾃t7**R־B0K˅!2u: 1꽵١)]ѝRWgF]Tl-R,kwWyaw+fy@IDžaY‹ zpTph;wC% h\܇$0C7DU(w]V0FeWr\Q [gvRWC�U_� v1GΜ.@lH&*Zκ:=3ъߑYz" A NO䆀T^fz s,Oe(Ďՠ*=1 X9X.TlMtH:�d*Qsf $f(ҥHXit9[X؛;al<}v138Xq!7p1gBn۫L<Lm(eI5/젗N�FE<StȌZ~nk눞ot#Z;'fY߫%7&jSBQ=i\Mp/FYD A3n7i,(9 E6A]5^JRL0ԟH)z6)kU5X5lidžjcJR#[x)9**ֺrAqCM~[30L>*Ы=:TtV FG$�U� 6oC_V$�a09x ՐVbR:u!hQ#BC; i`͎=r4 d)!*f q0XeL1LQN?pIVȲeR(DL0i|/0\`p$./k|\z܂![[8;;5 _ښ>梹S*y t_N̚kFXcɪT4v %Q:[`k/IUmv2F` wVe̜Y.@ǻHuDD? kYhڵsu+|nmZ0y=5gsل+Qc] hW3$orSKKN͚foL~^zC/j!k J<s2TzWOBSx= }&op}qY:<$ .v"U ñ σQSfƯ4B�U\m�  9Hd�s&rS~1_)t[T%mgԌRRyeo;Hjtl;8_}F6mE]F:H\ۨ<duVo_fބ&D`eǑM{ʶѹʴ2�pG^HΆY'>U=XMl?0nBE*ỿb?6cPfvKUZt1*;Mt�QwIY%G D\o7H;`+*'�$- T̚$./dc|*pe�'>[v {m{q�ODjNf'0��ͿB ΀<tP*mݨ o$pEZ9kT,vvM_ݸAuM}S!i{\X$Ky:oaMi.'ѰA�&./x )(^Mj04PΫ"N �S� 7Ҭ9`zUv2xa3rQt\[I=ݙ)̍c{Odf.%lFd.,lq"_[Ny~ BꏤV͆~2ɳOWYAnT?8u?TSt'0�dv\u='H1zQVWEh[D6 *o_Cݔqj@Y�o4(iv>c^?xQ”BYVv3:;8TZPC;&A-=+"#QErl{ ,$$(Ȓ볋 mry/ݼ .h(,*1NB)P <N+/IJ2Sgc}^٢OyV%V"F::$:|!Lm"(ع]Jָa遴Z>;\@4hasL8-fC!dd=  XzE_ [ �S_� 1^ipy]"g[)]xe~q*9 tP)V"j.qHAie C8�Q7N>~-́Aо9P'4>dR@1JA)[6=$Tܠ玃&ẃ뻸1v!V(pb1n|Fh!\}EZ#Ќ8Ia|4P>zPwv.SX4˪o )n9sOfJ{WBJ vga o/zϽЊ?Ըkk i!˓r"17 5[/C+p[k'z7wտ4ea�|ǟ-f4@XBJْqZ4<.ʈCjT{ u<OP A7BPJOM%7@slpk<RT$filNv�hme>F֑jPc'~]ZBY\ �S(f� *XG|�!ar1ϫ1MoIqC4=pp͚O9J`:p Ue/K7oTky十 {y垃߆d;L;}#۾LI>N3NÇ`V56xh PIĜ Djb{wZD>i�㩢kIJVn߻<𯵊)ymNʸ % hԔ @^(>bb"Da(7X>çJ22a75 pϜL*!Xa3Z.e_v?E^%E,Xflx&g mb\Q)!퓋Ҋl.* 3#Z'cR#pMӏjA -4 /F v`ȟ"- x#儅*̃rzx /$s12U"6toA\ b~ԙ)h1WGzC|CX8CE鈀#66z` �T;]� :hIgByO`>j&0묶=0tM=[qa<e)L'qVHCWrolpÏd228lxM} =+5 2sP%a3s$N37JQ6/f!Qn@V{gCX 2ET 62RyW#x)EKpZ d#aT0 /ur`4he| ;;#<TŇ~Rjg?ދ 6<jkJnLe!li$LPxq.E 1ғl^V@ ._bQE4 B X=4"r "PĪb4Јc;Ti`Ey%ȉWy+Z2b}͘ō)wu\$%&9,2rzc+u˞D*Zna(Ĉ?^eEKL;<2?O~ڪ;M ww!OMyg[J۔tɉ �T� uxkIO#\HPZդY(eD8_Uq?s81EbM{lǑF`旙0~h5 ->'+~ޭ-@Y{x~Wx95f- V=kZ_Q$b&4F)~fu5||2*6Ӎ(&d[ q&Y&D0?B{ȴQ=ygmBň$<O91I1N/.F~r֖$ $g-[%>^B\p29M~CwytSIOto ?h^8= A`N\R׆:DA^hiiL? ؍Q7+eD#,{f6"HvfQߒ9RC6n>N r Xt` aT=S#Ē!>. J~wvfC$pTQBo>0g3GմI.PٚҨHJ ;N !3M땆 �U � V3=Q##rѻFȱ; S5ZMHknxg hsΜ, s<R]RvB+{wzUcũt]! [2]x^Ic+r^0\SfkUim{|L(0sj"oa*V{/v;ӟtOV uҴ뿶=ea�&?8 s:i6} t}J =JUa;v1dCMVmBT'7;n5$d*³*Y@73N[y缯L@$s5V(b!H8m-q Gl^lf=wf2mԶஐK UCnQI<i:M ժUR(rGʩE+ܼ-quEg61�fSk̓^6>X?Ц )4GWgli)QPA0zk0 �U5;L� ?㘂ur,ˆZ|gz~EEcqɪ貔荩A <ve@<;ؓ^@+*�MS.Ah@B3R�G&;<] &_ڵ愔eE b`�GJBdMCj [VD.gt>S8$oRYnE5Mbmf]t8^#�@ p8)fm癄3) zyի24.T\0J<ROWzRlJ%۶c(UEhT-#_[ D )4Xed~ |Rf S6C[F S|wR:5 ՄF$N̹ФD,xRdŝU[뮖LzP&=\B`6ejYc[+kya$ RԨ Md߉>!wS5i:R֓^4.GQHjlj �U[� $hh|�6~ϺCq8kBWU NJ!:o$9lz�?$o2_Q8Lb3[7֢2 ]~x-5?Xw_W`KpI/MVV$y[/וQ̑O˨#CR{ٯaw) (k+6-I@4mO!3�ʟW^ٹB0;^fI z{B14 vk8[=? Ǝ>BfY_Яfgp>obJPX+d0fRco[&RBrt ·B&�JƧMi%aNrwDԚݺDrqJ?»8Mn n۲cI'v~蘻}=nNL2ໟhO 5ig~ Ŀy͗`x.$@%UHyh OKsQR%A*,K_ �U� `L uwף =+[f5hm,ߝnT1[D8X.YۍGH\LjjE6";2r&~3x,|\[6٦nڷ^?=bQN -&e;CH[kb4&50a$hL[K2):D(,w {8Qxp_U?t`~Ί_n$Ⱥv(\@T;xZCٛ*׿A-(g6Vp淛XT<`%9Zh $҇& F"!{06nWgMObB'].2~-Sx>!U,ma Z`1~U&{2f1DTs`7 u3O( c0GtU)K~gi&M9 MN4�.Ƽf7Ltd@,TS8<-!_:I,<lFsŲZS �UϞ� y>b 561G(bݛ2ڽ&Y9МzSgpP4.zp;Ռnj#g6@Yr^jgdZ\rofm|ip[+L?Mq*).ۯ&a0iEfW#L*,btvc d?mf]wY00NY]+&y$riqo=X52ucW¿P9'E 94=1̀TQ+v)4:VW>.{{zS94˱SUECjg0 3k-.H~S'ekYG'h֣3|g7vS{2T'e8SThoWX uC~z6spmT_#%BͦWDZuȬЭȸc:eibT7~U\ (Q^S 07Ŕ.~Os &|1'~_gn+/ŁeXW}EziW݈sigş Y4ɗɔy #Sػ҉�T� (!Anp?C[9Kz1 s@89MOƉ?mC(#L[H7y :HMj#K&o]!~ ;gln}>ˡIg|أ14)6T f9kk\@昦{aXkqRnh[7 Az[逡A0]u, RA >bYXrQ4(ҷi>4zrcS̎ ܪ[EuԺJ3Bho0� 4]N{ % f2l wZЦ#m.'aҐEF}14%@N\-{K |dW, |X-*8g<!T 1HT"ra㻰˰ և*CfLy]b!R%̫heY'8^Cq ?zaOq!@]]'e6_xWg#; JLrC=)Pk\=g�T u�  }MSY $GT&#=XWu+ CdhO ! Na 6cnYp$Ioӫ7Gڱ1T-:B5 )ٜ9.@)lBk-Z( m۽+/%׸ɣ1eHHju10 )tCȹe8-[J[G=-|z)WV%cfؗyy_ekޠ_Cwy03uDJ Ls}8h#=paDрԹK‡|6 T GCl1xgz7:d8) 5fAqP@V)Wrb9T&Te7N4wf,hA@0nC3VA&ޔߌۯs@zRW&+/榞~mKUu گѬFQ>{.=v^Y•yl[iGz7e�T�� 4wh�>٥o!#_#쬰4Wxhuƙu{Y% 4~:{5ɛzrz"3~—,"y:̎*V<.hW&9ĘΤ sZ*hʉ`7-" U*NHZE0-sX^T.%eYk!hy $7scy8;aJmsް6,Frp%0k%4|RnG(+^Xj&eJ-%yrPK*xQ!-E8CۛL6$u+3fokuϑ^Cw10d Za11.;3xu*$-p o4jIQpnɼA;߈(M]k\8l|9?h{@4^aq}l[*yD:0E)EJ縘~đ5<!q։ d:(AE3wQ߰(|M}kDNj �U>� )\ti _Xq~r/s !N@(tVRDM?r˝<2i÷kK ҡ;=Ӆ{imÊ5`&jmwg`擅}XJ9Lh3 $_kwTC |ݰ0ACs`?(|P:h �\ 鿷%@naXPѾ2Uˉa%*[dK#kԨLAx>*#oq$k&6$un 'ʺgCieW׵ح$Td0 Lݨec<p y#WohϬ?-3sWG.x/ۢ�<:kE[3E,j<<x&9rjSǬ9Fp׭7؉d:u}P1Z'F,ێ{ `_PA[ eHis2:9i .QA/g?s 4׮E&1bD;_apF �U>� /!wT`A�*`6\f4o*UǺ#bu,Y0"irAA6{-6Iczվb1C^� 񘭶ӑк]$ oVj'`3h< Ŏ:݀>`bϮI:;R3_Ͼ=qib4:9OWShqPΚwlUٶ@3::#As1P '>"1\^E֚ay'H&{_NpvGͰGƝy=Xa;@;7KR[6@.rwJ융A/&Gx)3LN{"xN!\p79#( Yjvzp 楟L :hC]OJe5XIQy9L~(wEJj.lDa6L:@j/ 7v (~ be ǐ_奥,*0ox p H+)Gs͢HJy, �U>�  -~yIU,$0JT}-|oQnS[r4ԕs`-Ih#۸{>yDI6#Pnu!`,ޕWcc"\XMntzcFyA�EJRQ{W<)Y}�\TLr&ڭ� dat9ih'vSaq4/ S4Nhs w׋0iVYoR&8p*<drY[C G\aexjyÐ SHlW_eJys1[Mǁvud~ă\MmYLGtB; ܢe<xN,RA+e|tYƻR|1E=d~a-zBm)7K7= sq-lx6.' Zډi`�@ ٍGq$´xTL[r^3H:0)6 N#<uݕ'q �U� U87�Dyٗj^5ҧE93,�81<}x NH O)nig1=2Gp+9R GUmhƧ6YX7mOϖKz#C}c2$-єc9 \ㄩKOTz\Pp}sٲ7;.`g >w#'W~<< [6�5b+ <s~ Ͷ ٝ{1`Srףyr֎,Z(Їdbc29NzÚhg%nZ9Ĭbr<W2RNىW.}b# Iv7e<_dppjߎ؞4Rv&!n$<Mi9EgoWzPᒰ_^�":]f!).:J9WN!>J5^)G:IYԇNJ6~nȤt0_<*Gw*Ut"� S�� kC�/Nv ?1jy{PQ|%AǛHm$uNh05a#s b>"`+*hŌP;DCWӥ>ԓ3c -W) ҷRt Nr3 SoLȋ~*lۂ5k ׎)wph 8v(VhB&+Yg9U*Xq[`7Ӫ4ay~U hUoS,јRkh>z(0Ɍc<qĥP KUF8t`e"ȉ$!Uej]ٙtmK"ub6Y׫x2Vs"[P _jVvɢp`#fi::_&OF+8E?̋q $=fkX$ ?ZE2)tqvkiͲXrpŖzV� 9K"6X*Aގ= �'   �T ?~X� 9鶪*!uBY_ըvcOBatBQtߩz35xusܬp|VZxLg(g(_<8WtܻTvh8r_ ޼ 4sHi+$bD), ݤz<ICw!I.Gy3(U|Oof sDss:RB#h;y 9V@TMl[&Ln?oЮ4<vRÏ P/C$l5 cyCsNc6:5ص8ʣe%esg LMK*Ezy5.&CqH]L[ Lh ؕ{QR$F.|b!"#s�i|o@F0+u5^P1[Ōf9 YgR' `< UJ8"w|/mI.ɒ1VEstpg-a7c= �'S(, y   �� 9L�4n(aM/47yVXtOфM)<Jm;B.XͮUQyb=rN|‾<NBŰJbP]sF@Jca8|+bDƨfP] #Rm1\ eԸ>a\<Z xk&[^'FÎex: P�lL'}2]7i .=oTw#=&P"zӎfൊkJi,]Ϳ!e&Gn�ѝP$zB܂70veJQB6竦U.? 3s'-B=E=EgdZ[*_wl)+rzqe]Dh ػ�> qu`0"[<,/els]%<vsiT mdO |@�Z;�T,t� PcM:)15I~ˤcu@XĞQZܥK{n7i ~*5Pઘ]kp.+ѩ0S{36�-l0j_fR u7@E9x";cT@#J CL$eNPQ|.x js�TPf.|zJ`A `0mO,FY6zV0@Pcχ[h;@肧l Xh73Do*>@#,sũbG;x=hIwH`)Y[Ѓu.+Ax廙#93݊ǏӏEN[Dȟ8?7+Yfbn[ݛ$?PRѼU]ЪU?WV:t8d: o'! y<n-P35UxƳ>ΖZ>4VC76fgyR 31ѹ)dOrt۲y ~6whmdw^R}@04$?Ko.2jԀ)U/2ɇ5]y J;}x9)8U#MY ̸&v G(]cgZ '"L>1�hCXyE@�1sr<YGqk=٥Rc:ڜ: h)2E+mՇ4McQ?32o?\WT u_4ȓr ;B5F<_OHx50VpR; dƧ"9$!�䮜T(FXE'|q.\w4vW(eM*u$i*#]۔6%.o0E_gZTPEPRatV;P0 nczHw'BePr`I&OwI2T+`*ӲE .#G$/ KPҤ[.6Ȗ ȉ�T � Ŋϙ �`)JKj5q;k % 8\dQCg'][T,NKv_#'y}Ōա d(9 <7!U=͏!QH]- j?\ }9<z԰xA#$ú<#@ko/C"<kj*[-x,wSߘT8U2SDgP*!M2g�(QIICGn +Ta RKp&Tȭ75H bS^U3{GtSYt}?q3:qσ~*Ҳb>1:TGL5]3]pl 2`tE3S;k.oB LLH;97 Y[<(w(&hOqgpsL"t=];,߁@Ml{S@=fٶeSr  _\RD1btծ"_{%lgLÏM^r E7C}fH OB&fk϶n@26w7BH m!rPO|ĭ`)2^2W,IC.9R\ϣ؁.ۍJEy�R<:'̣阂q$M),LhOqNjxkùU%Ի$q/|<Zm}Yfm>2Fv/LjZLz=!q6<L|ea"׹B)8TceۻtYX*;s0mه;3#Mj#-,`DK^=V�0:@}>ڴFmǡ{<D(~*6  dJ=(4>X])l�c;L'h4NF<�~FMK-Tנ1K7/xvBM$QtAoTéހ*)a%�ov*Nzx gE2q%щ �S(�  F@�J'*K}`f 9AJՒs]�/j.?0`zi^:-VkS| )9n:brGQ/ǘm(T| )nEh!W$i2W/4kWg9u&ϖ?n6<HNXT*Н|e&J;5 ?С&5P ڞKo-bmX_GoGlBָ[t 2QSX ɲ*;)Dtqah=RB&oOsE2;I 4?.yw1dC]96 䝧0yu-j}&?1*pT>Jw+(2?ϳQm#PQd6 ^/n̺b!OZP@#vtWW3F Kڇw)XA'z<dU 3&Wۃ#U?$/aT2]!Ưn0ӃbQ`EZdeL*b c ,&:V.WYuZ9-šۃuM=Zb2p*gFs)2s !5<v&B]` 93=|2H&z5Y#Al,G8,`6$K.6XQ{8eb.$]ޟ!r[IkAP["C<gޔg$r+'#Fs,G>iT;{M-jV ;Bώ91S_UzF4ѩ Y@0O6Zҭ^J8 V>]+c>0$wKev??'_4SZȜ0BC~�^ҹqwx䌧kYql[g..RG?KIu:?} _UV [a oEF^ O,ǰ9pU,+R\k.*ks6|Kb'gG$.MK.n7 AH4kdb3Ѣ0q5g բ�2bsolE0zx.9>(Νcoʈg+41;*&j u9(¸ae>zF):tx�i~#Tll~E`�FI^[,t"" VWhkJKY>PH�LnwoA~$~ 4M%l5hKRd[aIOϯ]yЙ>A̽-QՏ=^VYp W`vTG޵8'8$oa@6]#iou:zbt?Ȭ}8I:ܚ54p뽎 ?St߆xpm@3y$?:3uƫejp%O]"ݼrB}TUcA$ %pVxWEjc(#G7Ņ'ÚZ=mbc!Sw}J�蹭t_Qx_#sa}᮳9K[UT<.^I*KXd ag>m]/Ջ$CZX\jIBHk 9Y�@K ( Q[-xlGk!Qq߈z8D,M2<U C*1$ 4 ۃ^-5k'-˸i F=cNB0x[6Vvr\kG1tDU}B7=I'$0%a^p@u< zï�ǔ.\<H6̥ULAw2l' WҝcNV#3Ur\KDe z5ot&b*:�ѝ1]#o:JH]2XF(֩[//}׵ap-%8F�VdHo� aQ٦!S �eƏ:;Z9|�:4Fn|jMMMxChF�Ul� zn�<%=>=I\J\�Ao #Ui\,:F �V� /|�7O\g�4ejZ7\%ch�V`e"� c!G*Ā�;I@~7dϺDi5 ya7LLڊLgބ@=G(J'=z5 ) X \Wbn&ՃڹB o%DnAldjcWǡ݋ WHη+kԸՉ\%8nsqV,-OƅDr}/yt<>z25YjAjE-39Y8Oá K ŔSp|21g'�V:� 0_wM/FB V$E=pPSAGV\@Ë` :}�&S&= �O?&j$MjQF|LދUM:v{f,ṅP(ľO{}Hv^t\fM>Un>z#<<ʗ' MdK b {D14{ЧAԚEҰ~eá0pt<+nnC|okdX2 e<�dZMmD'brJ�V�� ":UTPf lf[ I0˅So(-2ZtP#k0Plǜ@W!hs]xM6)RGT˳a8ENϨI:a߲/PӐԗ ._M, Q7fv GʷTu W@^ fkz-ktbEeJ <]cF4`nJaP;FQHxjk _+b>^򎃪;K,WWjOK%u;b6~Q+QU$"eNT7/ԭC(5TBчgqEmᓖ�7+J&7P:)y*qc=�U'K� rɡp4SLVz&Gin!Y[GnSUY+'Ipw;X#L!|.!">Bzo&_QӘzfmPq1Z,}=: RHZh!d]i-2OJ[nwCsr$4ڍ [ 3{';|ZN;<5?~$ d⺣?ko7b8s)ΉZ,WG6$c/sN.-lHU"ˠ,K%kú͈ gv;Q׭ؒc9U#1'NkePS %gWPæ(AL[U*}SF],67b12FMңOk5[)] $!zKLV|ʵ]A&?^v,ppԲQtIܺ cN?:"ڎIʵO .eE?YI fdtо%@�U.u�  HFOrDHc*bU Ukk |Pl8kO: b=}&qśĴ2#a)[d,C6 }9N>L՟Ol@(DQ~_l3/+kڧh=95*in�klo:vĈ qG;CM_ix1/Wz,5jDW0=鉇H3\Zzm(e0 73_F3;sv=3y%OB(%0G+ӛBb l`n@yS 0\:DK`)(ß{0੽`T/yg̦U5$NQN|(6dWsVtu护f iXQv(dϼr9>4A4G:G![V?rmt}PZ~Cs2e<mm1I+= -uˉ�V&F_�  V<J8[f^@H:e\:[ɯR Spk&'燳!/Fe:/jW>;i9)MI\C[rC6ʷ'-la|&�o9eRRp9zb#IO5~]ee?Y?8�xL0#}Ľ&�-GY(tC² ᤺,S@K}j2Tx GyOk,+w/9:%J'XU<e}y;y7 h03asu<BAnRrK{0QF1Ήۨ.8eB%7a2աR8wf0Î=X*?`HiL`q9Xd)iw ]"plÛܜ/P2D<ԁ_FǶUOQN:RS9$b}b\6 M I*s-ߙOL܉nL:MGGq\@�Vi<�  g�gcҟˎ_YrxV Fc0S=�y :20G$t߽Lѷp_ZǧWt!)>Py6ˑZm+b<s/vAh7HoqsIn[ q`XZp.r9v1$LoVRzۓ @c\K][Jpyvح٧49l2 suT0yyN/읯ƃCߋjZ[ =6]yIt Še!Iu% %&DW0ZUp5;XN+m^/R[7EXвלsज़z]L$P iCiM cCR<჊P9=b`ݺ|?&فy߃0A-ǖ$_9L.[$oB8D{Z]-:RlTkK#breQ7L$@}BS UQz-;Eyy1 _!i'�Vk|� cZ쑪Қq(SQ c=a'蓉'$  "2!u`DX<נ3<>,;9IMx0+ .)Y�} 粏woVg;Mg+*g!c^#m!dU_d O,U"5etI@e2,6"">m}*uJ6>qgm.N7Vbȣ!Z)tQr@2!7JaP /5 n2ұ@GufW]ˊZ͆p`{MM;Noje470#aj "m-J1І,#̑+;{zv!|Koёo E (pN*L>I}! "Z~@�tWϩ PV^%.H6QϽ[]F:ܴ5K灐|s}(7`v𢡊/Ĥj 蛫y>jP E3$rGX~* _+7 x '�Vm� 2N/c=IQ]wx@.Ahi c6-?"BEr~/%CLj8dۭUz3ILӟE=3.7b.\| DT' Ȭؿw..cy8q&hD%!dZ@;˅{<Ɨ ΎpLkIT}z 1&Nz@}pd3ls.[h\>fD%:!r~2R/\mI/:ˁ=gb/#k٤HUY 1[Ϋ1� -G] br*p77:8d_'^B(XPf>58:ӣkfʘ<~&>jK*;dK?N^B꺵T5Viqc+._nj̩$7E`v.g6V28ݮ7.Bʉ:&qBx֯)*DJ!,x% E) 3 @:d̔pÜ!mO%1ۉ�V?� E  KYQ:6tq=R>bBCe b >5wG ZHT�AWBp?`c,fWg hHtn٠I2 ,2X$pvf FIۨvuAv;NF}R牏g"xw4؋j6 Lx�#׻ ]ékR5?ԅW@nYԶ@P:\xDC[J`8Ƌ.<OZzaLK%\ We6j̆AI__FPtP<ĺ #wpK ́RD%焍v'B @2?"F! pOaxL >y7$OLco/Zd^,u(9a9KQ?8N!CMI1jRq\]lo)wUN0dy+4\-Mv!ܵ%m}}40NwH |'!j-b/tQn.?_&9~ǚiijV|ևglj�VV� *˝ϵ�'@i}~)ghXO$1Y!/SV;?)IL}ɾ̦ƒ�ghiaǁe_켘_2fNqp`Bs+& QV+Beog=*/墤n\V`[R <|s8neȸY)Y~qT",:J{eQ5Qn Xqsvh'vͭy إ?׋MK8G86cꏟh9CFTTgS,`Wd<dO3A7nŎUJYL8+ nfB6ϣ̮yo^y2T6P.L[Vwawv#ApUkiF:|P QIPJNn~H, =M*xr.ɷ1agE3J:fT^}UѰǻv4DfBa !r6)FITk8h�WNC� Jɽb{n0!i|1Brr�SW=ȑhGd|(VnQnѤpv+F?6WuljA@V{J; >ɥýƹ|4ҾFMhK6ksz"25y&u$(XpH<'O5POm+ړꠙW;Oe%/3r≇BۏYP~jlŠrީ m+ýpBB۱jdϻ[}Y {L˚$lO8xrW{hNg ᡑ6_TlUd \e/km"uh615$3,v|/ CjJ W,*^@bt)\ {W%%D#Šdُ"4H Wm.[൩.٥fo7Pڐ*2mU=p*s^BIN"ŵ^}*Y�U�� IXIID޹㲽ΚثgݢSe ]Gɓz|F\b#/\D~S:E\1!26[>P6Vtyѣg }wHV5 6Qr' 0y*sTdoFb?#\R5Sq%IzIj0E:D >L20z2j(ߍD@mU׶4x_ĖEeB/#v#{5!=cFāS0a L%?ashDq_OJG]GqN,8 :b\<*#F,s[TxjeTachU)Q-,2ϫS: ψ:v4U �'(Fֳg$'KFy+zrÓIx1 ym>2%|S܀B6g?nDK>X*J�<yEc*}7P j5]�V� pڬV)�\bÔm\ svuke+ŮCIX5J?ElIIJ [XVIM:/E0HRvmB ˋjUwE_01+Fe'%A, ]^%&S 3C+i5m!aOAxjW4"ׄM}f6D׏U_^%*dl  >ۿ{ 1Ӽ2qFbƘT|bme. F(=Kp `IƉ]f찷v+nh^wZ2ygf1@n9z5AhZ.z2ls "d}[0yo(\ Q(n,[SpFSV8eld0xV *dh<[/HsLluv[l׌>mV~$JNS_z}LU2|�V\� 6k�ugt3AnE$qv-dCw3-Ъ?c{ܥcU w=r&|�(kY#S+e 덳 �x61z8p҆5}(X<\C|.oboaY+igq'D_&3wsCz S%_ kH戜Xf(u'g}*c ;}`r@Ub D0}nqüs ѝ'# |D1࣏U?<nחjuyֶ~$$z%DK]i9WSV1\t8kBa56.%q +R6(bt|Vs>t8:$'FY=�P࿧Ӱ G؈;SJvɎbq j`tYJ$1]'|Al'+L˞jjb[)kg wW]q։�VgX[� `~ %Pd[tQ:Vb}~\}UnPM *<{Ȫ<20LͰ'ZYD['XG)B;B#֐JaNA>k(^ț!v%^NX%[)7PC8Y24.I1],9KU2.\-Yp') ^M՘<S.&/Zk9ߜLD_wFDfdv(7I1}Vz!abSP߆pqb>;#٣0Rǖ?T O98\{t̲(tszk/4 =�_?fI:6wCT ZɢU jIY7z2@ej!:BF=Fm(m>i1MW]q2Męc%nc/PuȈ,oĩb]r{pN?l1'>)jW p�V� e>Ma�8 !�[9qb]T*T㖱za'GO Yy%b$Dʹx5+߼ ׭{Y0wZj9ERt;3WD i plߙ*Ϳ>jvp_/P\p|Ĝ@zK#Z)f`ĩ.hpn5װPD,gZ8V։BVf#弄3t\$mAR;two7E>VkQzE^hM85~^ɼSg#HH8ApAM1ȔȂ~oMM2C3J*dZ>lGRe;7Tn!`̐ƩE6 ]Ŷ!U:oPk`۔xrtA7\4A>ٿ^h)x$Ҙ[Ea *߃w~ML<mg}8&zJ$7Χ!# �V� q}q<[jtߦǝ/ kܸ⦥@'Cr4֘ 㳞+CernXbp'ȈC3'<ܗ1@%gb#&!eײV)zj q#L CcdZh!5&Mո0S.·L�lT^F7T(C,{�e{=9U{! Uq KPA#JBx p)�г@q"UQvE ?`u?J刦6tfieʜMeCۃ;E?*v($X& 뚥ܵC EjsjNesmр\%5 nK1i \mPLښy ¾"/hVGvoޥ7oUܢbVG 0yZ_Phi5 #ZѸjT<]؀p@qo2{$o@w%:؃C3fVǥ vyo $<`v?щ �U� &tF~JǞ� b4:KMdNQo< jq+-Ըf =۳˘X_*lyP&P\G DYo#3⚽kh-N"MNb2ygYao6tUǸ O6 p4zlZ>x{EFHٶMMN0Yv ]rRT5BT~+xZηA _k>47Qgp!{vMR wrjnW Ϣgq*DKHRdr l=2Ł9ʈ1-97v>Dds-[½C:q ?~ /)Q [ jP D$-XreYt}a]&SDP%[(x Uc!_2u-9fVpޖq+yo 5sM4_[ۂ73C�qz^ lqv% �Ur� ) </ut]߆g�Okg̝kiwZ&/&M4LƗZV5< ~2꼼BǨ2-ufG ]n6=]6~f:gv9zk p>¿:Ҹ7VZ}} ~J~)O‹GA v/. RrCĀ}؂9~3&^bqo@% \~qiuY^L3u mRp2EM ʀv3:o.f/АKgh]>* \/%#UH-1Q~[k< fp0%[#!_M)UzA8,UpSQT'_i^l5B� Pݪԭp_rB Ne"5AO;02餂 qsɾO~pW6"~Rmo)/I |Yy=6|!DZN$H[n_豾c5i22}W MR&DژT޻mf �U� 䣘�5w/`t*?>wl`PF&G˲g*ٜsH< ljNhmL^\�ٍ|ٌϾ%Y_`Y1I~5!a)9$�e̤E-n,\k#wW,J,~ xPP>IuB&-{kU"+" ZoQհFQREH¶y:&5f}VE+�̪T6Y((v^|<G ]O@4qI(N++n77^YpHL{Se<K+! z؂m؝-/-HQTC,ӮJ 4ff\aD̲BQ9;(юutK`䱥 / tgFnx׾c{ڂp�gz={^Qy!?^$2\ΫȺ3@7̗q.;ڋ8pMeL킴 } �V�� 7+-#] 7rPw yWm-SP~b1Y;F0J K@z&ȽF-VXT!)gȗG *2#1|>=2?ױjfGMd+~0_n%p8گ*ץYmjzQE?-~GҙM"u &^Njd={(3-ei|x.pd,0cac{s6_hB*[՜lsb1Kh*]p$ ,~C9zBKָ__@Ըn{Zte}j|.~PȢ?%Q4ҷCJrF y{ Yi@M6eYiNu3~A<hۣڶB� &ވF%P DNaLs>?بK5-^}k6C߼Vmn4o_p׺ӡiq:kP/G!:Q̵(Wz؂PMMv&TXv �V�1� !xl(�'y 6s?o`]�x))Iv/ phSR�裂P WX Ni-2(5f6(O123Еz ȴwxȞ$"(EϡQ'%r7<5c@5!dMV/Tn@U4j.-`dpdfE +z6@~7&(]=g󞻼%Xt[t+Kړd D :TCVO A[4Ed5׹B/1-}$6ˀ ަQ )joG92-x0MijNeF)b9MWH iA-JEP>sf5`ÑGwTXE6*Fd4R4C#JgHGsI3r"ޯ@j0> V?ֶ Bf@WSTY݊6S}lg9)ܖ۬Q^)Vߍ �V,� ýx2h lynULB7p@tl.4U "%J="oS=TZK+Ms$Bd0}fPW1h.'�/^aǂ?P' jLM`r7~ V~rF_"3#呚G@`s%>rxe]xqɛ{+_Y:<OiuQK0@I5 snJ6wg V5?7c+&ke8zD.'TisʰOf;')f4~>~ȉIح $S$ D=n5gx1|ɪgڣ*utfdSr*OXG 1(�8JϣeR̵ڢƬ5US43mڒ/^Mk4 `m%A^(5sԞ,w<ip41 y*Mlms#ȌA 븓l� �V,�  ^[D�B\|({P`FqF;5 oر&WzX 7/+߶'IEqno4;!U3K>*0-&@Cə V, -I>JV6AneZV#͙8DmRa5/RꉠWe=, ~]�~[@~pVKu<-5w{̇_ԭq# IE�~lq SokwR.JQub _}B ѶJh70O:"qsa֔i< 23?(VR|sƬx<:W3>?6I$7,p1Gk A棴Is6vrLDomUhOGWfٷ!@TKC \Rv ;?)uc C|f({9P} #=O(c?.K*Bvjma8PjEY ѝj7YIPhcͦʏ, �VR|� .8pX.ĉx054P6Ζ$B,\neC'x4 wDv1Fc8iw? nuOYl6 N>2| BAnqG-֑yqƪ�&mDCkMJ Sy׮.ח`>kDHZٙcI7hyN<ʿ$~gqfp93/+l)1m,�1Jr͞{k;nnjVc3P$Ok)Q>*piO48m;A"Y ;G%JO>[gҌCTrRIs4 E"v#9,Z"ΩyNڸE~a#SWCsuL"nn_ک4H~Cx6Fi *u@@&O#8eMf5[B[hY⃨�nf>^@[pw͔ݡz:Vɇgϗ0 //͛7V1F �Vo&d� �UV)#k195h1{$O30]!(0S҉ 0IހW>$ZY:BMY NiG#'xrJ 7Iplλ�rqşQcۜۄ!Qop }C%/os~[g_]7BQCD3{EMײUvJ8m .sr+#fC]NJδ(gs `׏0*mdG3%WJך<b.C 7eFL Վߠėܿ81>$ 3Uzī#4B`k+2#pdL*L6o6 }5q)BL '4Yz&’;;�V l$n.Իte*嵦 Xsr܄j%Pf *MAz `qb@ڰl=E7G"0ө2JxΒyf쪒u x vVa!8ĉ �V� Qm͒roQ_A[8.P�lPG{c 8hߔq~OW .p>y!LB |,#z7>DHDSLq0jͺ9~1y/仴<OReo=;awC$!!42d9\@Wܕ _vHXs7\{}*"Ŗ_ȢXBn11J.+}b|Qbebk[Œ6jm˻VvV(+");(xSypƯo|Q m ש1Zl[ݗBBas<eX73 KΩgv_tU>iRZaIΡgպ�\FLgh,!<~X y\îֶ (9_c)J(Z p Or rХʕ�o)*e33c[MpcnXqF y@zpm/K 7NPщ�VLz� 2|L2p-`? NďS(;M[ٶpPSj)v۞/PÚhQ){+7#lzgP91&:rY· 6I g݆ %X}!r1H**@]`S͝VO<|0 Ky�I; L(˯>$<˩Zwa?6 *]f$:ZϾ k2fmEu'$_uG^SE<e81#0[n *GDC{+kA?1/IֶyV|O#uB_G8,. D>I"[ BSg*UڿuLA? Sֱ͞*,z%4/$V*&o{;I)#)k+d4h6|Bd~ cuBnx, 0GjG5emĤ^> }x'"ώ 2�Vj7� �o+)W�z:ƳTaҪ�cRLu qB s>_�׾U<5I7DBoCV}�X=Ti9NxB{t~F(O4>_(>-եXX]/2J'Ŭw:`'[3b-dD\MpcUSWy+GوMWO_]Lϼ9ƕ] YpߖO~rTaFsl1 )Z&[;@so7i*'FR@,)ei)螘 N${_(A7J뙇edSx_UeΝR`dYpSr<I["a`vl{] w#.@$fdӰuz[^W -<)Q>i4z?ψU;NGeQ8<|>L;fTIYlVΫĹ�Vj� M̡{#:yU?Y6)=]dXQWʔnP?f,DZ/6{¤=gCcۑ:VV4Ҝelxa[%!b,+M?wc >8#i\j€U>QZØ<=} Fl0 iIm)"z]ۉ*V\dQZ e]i5.W'GzƐU&g.>aM>R8Ur.D`C*zs&3"K$} 2قUgi(+Dlct_bC|zWkë^bvCX?w)rY\Q(tvL8u {dMs W ~yK_w&-zV~ܲ ]@ߖ' X%r|K/6 �S=.HЊ NTf�XA*KX9ƚ>$LB -uhCrɯbakɉoᙀ]�Ul� b@1.�9&�8l/ w*W3 .o"V{Ջ uٮQs]"C]v. r:!ՐT #cӡ$Oڴ@myvdN޷C4qAWMÕ}C)N?n"Pz;'378b5DǨvnXYI"J[;?l�d/P`gAto2*bYX3W9jȺt6W"^V6x YC" }W~CV:futF Zhİb-+ ӝ7W\+ρ6o I~?Ԡ4: T5lf4JQZ_&=x'L Mpghb nn4V!e-Y4>hQ[ T4*2yJ;D+HEgrA1WcD� Ά' �Vg� _i;|2DJےM ֆ`6: u#VJLJU+Az7n/a3,Ku3[8GyF4np]j-gݧ Q.{tMB@R*r|x|᪞koh'Yۥbp; l1Em<GT1vGg|w^gDGꪷo~ᶥag;NITE \5Ea٩)<I}iQU|6S*43`駲(K 4]D`i*G z8 {^\]Ksn^:8e$>xujb.m&wRV2>֤q.֕P _rMEE̚AP1_OWIt�k2-�ꗨpțR4d E𳚳`Vԍ[u<f(%FEs@0NRW<5r= �'   �VeS  Ҍ� 9L閧ºw$ |i,`YӉ#b/.G`flwU :gz0}gCL#ŕmBt%6y+c 6!\:aSֺK؞'jq 8<j"0 ЉP yWbJ;c[k iӖL׿M[Bg$G@o&lTL !1qs*E.._|YAUkݕ2y^އx 2J﹧~d!] ^$oܒ_j\K񣾘Z#Ųj4˙6EE ͫmG(` N#㇫JKe:7M>F9bń@^1{(޳\ ]Ç<~{2IsExr& ^pA},+ !=%^ 5P9(I2)+曥D5Lk|pJ �4Vd'git://github.com/infinity0/pubkeys.git� _ر�&BMϣOo5etaYϋ%C 2לfJЗN�sQ->Sz@; 1LX %>(ݙ36̈́ {k}~6w&ʹaVhɰH=/4o)jlZQ~Qjx?azih#z:exlB\R!p6xX_TsDlwcʹ1r�" K֯ܘjU`FyH>y.t"h㪧d)_[7*zL)}43;4Ihg\pV h|ҀxGLRNvr)b^uA#d?̸LjGrBu#c FPj{f~ t\ fԾM HNF {1jXmz/)G85=bq/xhw_ұOU �?V38http://nicolas.braud-santoni.eu/gpg-policy-20150222.asc� s�ǮUI9r6aj�J2"xCjV*`Q:櫊 X}@M>rNDI] =Zp`l:<ˁ�0ru dQ]eڐzh�6Z"  (kh<-,\Qbd (\+n_:0>Cwm89co-DF l'RApЋAJ<zvf7 xWy{ +& :A?uz*~@pa)=P.K!bkFvmř8Vt <=1,J?;9uNC6tN=TED޽۶[kLwP-|(#gA`ϒ '̲ڔ0?Ge>3 !:{9T&[裪:Juq>mBf�(qZ3 95J<0MDjOYx)7 2U(O>hZQE= �'Vp https://mike.tig.as/pgp/policy/� n#^ �΂&W`N-`!` #"dYJF?ЁWlH#c埐&u%İn L0YWt)KZ]ocѹx,ɆaHyN*h f dλV:v44͌3iFyHv)%x7e#(N_E $m%m#*Q f@PJvF b_ZNV\8(ԕQ~gQ>@|휟άWU4 g9H*`X @ i/}jp`涭B<X| e3)ՀBTڊ݊uR<M4;]c¬52?gt? +Nw2"c xzuUL6z]a-O=@HRh�<BsuچYzҿ|xr6f.R춍nGw']|c5Jv,-+W'?WL;BIqֲP]_:1AK2&?6y-Jl|Ljԗ), 1e5΀{VCV1{K,)0qK+Y`1u9Z 놈@WU}I"!-Ukcő𴞺ߖ={^cv- 891E6 &©F=`[IsML1;6=O3xߵ_)-y*XwK)"A .j^gy4 aG!'ܖZ_ROϘ-"BZq`ZybQ#20HWݡnBC<ӎǮ]˵u%=? 9yFܴ +4$g\(@$rI#)l+uwڹj[^V 'ЏeƵb;öU�"*CMC!<E\;HCi.!>n od']l. ^E8s&u(R5 $7dGl"�V � �mSI-i:visbY#͠gʏg<f"e^׏s ꦈzt?qHBvgtR) SdڰԺ)rPSr_Z̾OWcNG?{ǵh p_cuI<^`֡ߢ?ps ԎO [Ԝu ȁ9b3(gt_K\zr%`30εcp nV:cݲH=Dɽh~0$ոpCA&uiWl~?ԡpSOjJm*Yԑ ~LY/+�mQ9Øʛ̖vXrG.abu<@@Oj wc(v|f'H\5ELS.oJdEq+dԘ{+5Ni3ةO9"W*f@#Ho=ս#uAMG\ΒdZ 0@ի*m}�V%� I#)em+Pw6/4("_z;;pCk]z%?jkM:*.'z0 <'[g]>T_pP4̼ fv9xK@pq">#oF h  +v]ry##!/Jͦ)lpgK ɲ2Ď_ AϬPlܫ"IF]>QF.>TV0S_x &VH=%Л7cK'3)1fM9ʲ̭�NR ʡ<}+I ,4b"Rd]SM+0 }6;>js3A<|!E_�ơ`qH=PI;_=RFdNE`{ @gNuIB !J}M${ ~oЩG~7d(Bq Q_/SVCQ5!�Dt1ێ^!Zkyh"T9*0�Vn� \)=7NFɒ͇hp M`&9Fgf/8Ne^ fLƛgL%rBS|moA%ޡ5^kK !rB}qU>X ВL&1V-xi t [~m~Y3;d[A!MNdbzh @EOs.ɋ7`̈<}]!*p,`19aNR8#~Hpy];Wꖳ,ꐵ{LQ d+U;V9p P+$\S]&L9$ɐ�*}OddseCE,jWT"OG'%e/A%'Zڔ871(ų"Hd┪I?$nnDor#zNŅ g|^QZʆ(EY!%'yY�CK!)ZC0~80i͓HHro[Kƹep~_Rx+G4?w E'�W<� C�F8�NJchM@?$M(:V8T>]zq?-;60K kZz5zmYEFp˛dePSH0綫P%db=TR2kɼ0%̐xf3Sf>!@#_*HTbָ4mX¤> (-x2d&A.D҅MiŧWhjy/*C<< �ν(I2Ver]EY6x@;dv6{1ƕvPU7J/\72:O )Y.EG">Ym͹zPJ\؉=TcFBEEMw`\!ğ$![suk{Ir['!؊) \`]<ZOfU_=HZ3 M� t2  5$0D+: +nNqZ2|XW,-mP^`TEDnHG}�V� _;$m,b`Hœc䁜Lؽx D%7m]eU [P] JFC);D�ƷG9Wh-ԱIAg7? QnH2zٗuncW~׎ւNMZ "'v3-Nb)X,+aWRi]^E@4ޑ_wpϔζXHz?rHy<w|,~]p k wQ-4Oבry:N+D|Sv _k|m|Vo#[cj'VN 3t!YtPB弌n@fx?R|jJrpkz)qaMܪX LAwƜ{!<lﺝ{27^;%fK{ L9Fρ("T(U:u~>IUaGZ|&IqG^H e ?Af>^ Dl%`2@;فnInf(^ى�WR� I#)eGoEh;n"اLb,hQ.�pU(Rz2U< 脔]HBx\rGlOg^aYӓי`)tIn]n`FfmlTwnM"wJj3ͦZXeJJˣm: x {`ժ%?A�LpHU% j)^뀹)9p8 $�5Vh. BFG�'=NXZFA ,>Ic(OAeR7|U.AGJc5mmQ Bj-mjݒl?v<79 qkPƱ-C* 1fQ$sp6 L9h鈱�<EC E#LS/4 db YFF2y4]n$Xc-er yer+H(/޽spx*1齤fDokE3s~ �V4N� kr m�ʈJ]K ({)]LyjDf-~՝4ܲo^16}:KZarN䟢LD,>y DKِ @^XjDv$䰧 P]a;$-fFX=7H9BdٓS֗s �8G2)~ׂ5xM \uK%}h%\DVUb=0]ݶˠ!!YRb�fs!ńW�W&�JbbN,\kL$Lr<K- M|L0*&utlxҔin;"X1SPܶ~=HcWvhSV~8etpwX;PU0-o<7lr-S yƸ7?擥;~iW#WFbH1ބv-<&$%q]"8mmnq$:~䦿?|2B,^Gރ|͇f޷hT �>   �!得 u@9XZ ,� 9Gk' ''%:d96O^e sȩYߜ(;9ރo'&|ӭPsn^f^ #!S?l#2҉& eV~SpU a8:*ZQU0Q5:{:izW oUhq(:n,Գ;Ty* 2%(q0 @1p|mGfW蕚o A;7yGнɗw;YFtTL0ۣS0r v:'t@ΧpI͐*>y-ZGEoMB~}n}&TC]:ZG~&b(Iχ|-'G^O@ʯi 0|B28i㖷/<2U�NWt-A] kMsSMqQ~iѓivd Ff9RL/M%BQAvkUh!Ȯ"n  �W[  fO�����(verified@patternsinthevoid.net0EE5BE979282D80B9F7540F1CCD2ED94D21739E9K�����(isis@patternsinthevoid.net0A6A58A14B5946ABDE18E207A3ADB67A2CDB8B35.https://blog.patternsinthevoid.net/policy.txt� z,ۋ5^7 B׆p о؍`j'^z)y ^ ( l [Nd n^ Mw<4uoW/~Yu܌qB6XqvzIBK:pžIqTI(Ćqa5y8d6­nV;4b.TF,Aд*ky "l}s۱.ݓ@+[b|d".;ȇӬۀ[EF+Ս$ dP|(uwxy#۩ qM^msI.'Թă6fMj'<oV#ixSNo?#uUH'@@dѡUfSP q{w!@DcK!c8wQ {1"?a&SDFZa�ᫍ(ܿiE@#xCBL9*8 c6:ƘF(crVQ;4ɟKܙsCfEWtkh&kam�V[� kXbv �#jy JU쟫Oi3q-.gRMݥLC69 )rW[+^"MeF%Qb3Ul#7LG9:�æ֣n"'Uv UԹ >ծc)0N]R5GSR 3 ,ԽbQ^e>"sR~͢]\IК%A &kec]y7YO6#u[ϗ2y'i!dTGӶ _~ H ɬFѭJ$r}n{h=N&ڐ휶<?rN#,72߄0X-wL[/Y <$PmkD {͜ xQn^c٠s<-S.\։GG?]$>PX#Hxn"dwua'cҹ#;*T6q>,p _ć|kjj3D2j :߬tB'呤, հho^w~E$I;@iwCF>^D`P%QDSlBac7g"#ZIjȈi4g,VV˓T+lrvcfٷb}=;P]|=B<x_'[7:ƾvv9,o򂭡a4^Ҷ&7e' am k3=R^+ >[e[Ed>[cMz4�Hdb4!rO+DDj5{RA<*m"L♇mX]f< ;<˂x@ٖPkˊly4O$A _~Q@YcCƕ}IQπSBS2z 0@h3[MPA!@The$]qyݩy0Ԥ|:~*o-e *ΜoWQN|m+3 �!뻉!vΟ PZp�  Pm6+ܑ~,G\eHTDcQPǁ:ʾ2YVt\A\v(i$aÊD 'I70 x{F/TE DGe ~:PF(8ZM˜6Ԟ;@ /(2Ih7;tRS}</!Џvu1ڏ!mݘ]+ lٽQQ.Z7)P6@.R^=X�I4Q }V` �.6g �ZId� ;{-i SZݳ1ڸԦ�\kBp[К]tm\VSӈJq]/CSqz`GAvHde] rPizҞ3B%Ob " rC ~DP(I :f3lYoX(XgnaЍW:NA#.luT$1�J?6K[~/<-?"!@V-BRnF7abݜ7};;wݍ(pJuӮ9/Jp c,cnyڜTa\Vzɱ3J1ݕ_qx 9%a`rZ^ס+NIm) R cLN K;kQpɷ4�$ K@kkP]0Mp PX/*4M; U8Z R)vtEîL n+j cR.ߒk. A\޵b\K3�!V#u*(X`k<κ l;iY=� <κ l;i�0/=1$tg$g9AG2-,w;:[|oLR:H{zhw [W${yLDSYTl]y�'?p|}Hh(:ްǴ(*KQ:v,z3͕"u2Vy2Śq'ehmC6"m7乷S"Kq5*^Z;Ys3K'ī7qoXy0-w^)ѻD^2u2T*\C_&*ŽO"\@�;*1ĥj٥N("tb9yq_<(GA/. ~1+qڕb8.ecQE:xOO3 zH\;<5@*6oFiYshKG0MeWK=PRt?φ3vn\!v[)_3�!T=8ݸsz6+qXr� +ql�Bлm`",lnB!l>?rHJܜ mw֘ e`& k]5 mm[H <D\C_z /2twhHPE_{~٘#Ny X3F_HZ堫r8-yoq?#a* { (9t}zb:]|||D DO<7q Ltl%t=g&r)b@R8 JH|;n * PI Q,wll:ۅɘ<_)Ə!<32GEwX2M/jڽw @:BYl 2IkZVf |ӮJ1m8)?7{T7a%ɶ*ka ϻt5D(}W;{ ӑB9 +mw0;6y7)Q=\`B 2?.Ebf1BՉT �>   �!得 u@9Z% Ƞ[� 9,7GV!m"IEߏٵ ԉN^v,`+!3x\; &Ah-pe6.$ h)"<S,ڭK̼ץl-oMC{\8/dD&~QxWvGrhMǠ(!zôU28!].)FE2jz@2.1 ]u.,RG.?#C?tN,;JT J.dN3[Wzju*'x}w|i7t�_r+`g�G< шu|QFԉP,yyt}0:1^4?2L#U rD$qt2%wE\sh8Qp1%^܉h+7L 7 +Ɔ$>QҼMG>-, p.OmI qC۹ HZuj�5>V`0]7 B>p (y ]kL.кgiww?) vqNv)+G.WX[oK;E1*>c^(Pُ{.dS|n OzOôOOkΊ1'g ч㶶0"ϝ=0S5uP Ā׷(qJTw\ͭZ|, QQ"f ykv,.KǑ}aa[M'O }p2k~Sj)%7��%� OKi |� 9Agh6Ģ:$W`q-VRz*k0a'Xv.r`u:e+T˂ J['-NK=0m1 2@czA;J1mU'= 4u*_9_?|7ѥ;zX.&D%*WXcekWt 3_ }碦νў{brKAR/ѽraz y_51LYI[]ߜDs' F�FPz򥬜ِ5Q3k +CaDy_ֺ("PNjЪ\`O<G!ahA,|B`Zga7g^i4N «[õ<mo7=<qf\jA'.I#{J陙.bȣqiol}?!rLw!IU)Fg0m"cĔ*' q#x`Z&hw2% � T F� 9 +Io^�߽wzDFz#ψ{ՠB$ua#+7'&�b4"eQzJ)|E_-_zv  țH hoDC ]A#_ROk иp$~�+B^]K@Gf@#BFc m>͎߯)8ZzCNZtH$ZƷy*1\oRFZT],I]&a:�hݏO2 MbS>%{q{)pl35yˤF2aslA:))8,wbv yQv 1);%qp @G (Nj]ΕWIdIlb2ֳqz0uXܯ_D6j' ̉\*kN2 $JF "Y)u:pQ!zZo#_/QCߪMȞ(I:o)?#LS!H>!:C%x_iXr$9F̉% � Ve #� 9,�ǩ1*=tڲJ3\)I_9iіY:P=~) `..?kJ?:>]щc8$+]b;()c[):d_O,39DbH廯J }i+Zl)=Br'-& |H;A} 6MhϹݛ,=)oP J xGjn|%IB?t:} kXv~7 `Ϩį_C<Ngr"Eъ)[:Ml05`NyY` RLN6f,vG p6:f޷&AQ1SeOC͛cxqŋжc2ϑzngS:xwvQJY&;Z]?8_RB;/cRa $uRKaN F[J| b>!GNM6e64ku< �& !得 u@9XZ }� 9@{ <$݈M13]%c8ҫlL`1Ds&잉KH Ge-+*: 9Mhcv{*Ɵ}T6~9ע%G6[yShLڱĄ =.+ʛX pL'k_\$u>NVLZoP ؙr\l 1֙h2aݙ;+#Xa"]4ifgx RWtn[ /曀ʉxChyZewc㒣~HFioFy'Ә[sq*y LGCn^cds8.8u~AhBY N*{ JTcb%A+*6ՔFZ3"hef& 9ER8a;sOsLR.X?_1GMOV_aыect: $#X&w(Ӽr%R;V bs~FX�2&gJ Fa/�.X[G>Ucj(J'P%y̝U#[yo`L&[ߟw>P`l? *oy?Wq{9|_MſFqy2Scg}NbtZ#`%V!@]͋dqBÓމ>`,7^:OY|rSHHa! 7/Rڣc6 @y2!`7O$T8,?W§͟vˏ^?e Bm[Tl Yj i;fz=JrCO?Ç;0 a7-țR)c0qM5ʥ3TE*йT1Z~|ePfWzMɍuZq0? d,}wD@TGՠ-oSX/z7HfB 3cB<V(7m��%� OK_ � 9Yk} =W3a[2_AL?=SU }s2WZfD'jwD}ج^ke5;^WX)qGGz3w^Q4zFlo-c-޹.IbO%k*^YDa !82r�bd)$mL`}~M8osG"U p.;¬DS3D6FE*\̩޽5,S<? ?)u_W N]'^JcY�񒶋�;;h\ +~l6Z HK%'{L&R 2-oS]/ I%>"/)ZJI0;uS؛pȐ&a8$hLipb wb3l )Xh"44D5\$TC7888iHi28X /?MAAR Q?D�E.Rcl-Ni-6'{r& {/'RKxϥ#zծ"dpz WɬQpn)fڒEOMaLM+2=m{%ekBWGf>$ntQ¿l<a?:og}X�;}ǻmǏ|ݱKU<xK)+޿Al"*?$rl~y ޱQ_mWp kSd׃.. vPozϨbS_)ܕOsR$I:p[{^ۏ~aL0ci8fnq^�"aE(x"lCEi;ˇ5 L~{H.&z![0ř8T+&WK`ۮ\Dmh߻c4!@NF'p 2 Bkcb-eW'x:{>JnƁm.( ~\TXG{�� �Q?D g� 9  �fQ?D_����.�(issuer-fpr@notations.openpgp.fifthhorseman.netEB9691287A7ADDE3757D911EA52401B11BFDFA5C� $\wAi`"ILr{IaL[h&lTak#17E2z6|>7 w'O~3F:KNC2 kH5DtU0z#OXa+ CsF/00DH6F0C.S̳YHs.Ñ}t ?'z[71ǟshy - rC/He;gpw%1gI%6s}H:iEO>2d39l}�=42FP=7k7t n z|zr>pxexYRkҁ@qnVKf(r1OjLKm �m_E6}:)EZE;$463a\MQ\/E ?L©)X%eLvxX  EamXf&P~Ut ? ~rhWu QحL(qY|#e|E/ )xy``$?ň Ix`>Sڤa"h23Wv dfFFsȰ-t('u0i=IU �kicW\ņV$ z*VA 9OPrtM qnwx"$+ M[z'vq_٧|f8t1OZh>;G{XS:+㨝І ,5ڗ1T [y|,[钑 1#>>=QV_3c6j B^[u$e 'U20S^z%#gL'.rO`û#m8imZ%0Xq,h ld`x|&.0c.�p8inl>BR;H v&?=@[zEx&Ppj3IOtԀRu 's9o<2n8ΌQkؑnlm� �T a  �fQ?D_����.�(issuer-fpr@notations.openpgp.fifthhorseman.netEB9691287A7ADDE3757D911EA52401B11BFDFA5C� $\wAi`"ILr{IaL[h&lTak#17E2z6|>7 w'O~3F:KNC2 kH5DtU0z#OXa+ CsF/00DH6F0C.S̳YHs.Ñ}t ?'z[71ǟshy - rC/He;gpw%1gI%6s}H:iEO>2d39l}�=42FP=7k7t n z|zr>pxexYRkҁ@qnVKf(r1OjLKm �m_E6}:)EZE;$463a\MQ\/E ?L©)X%eLvxX  EamXf&P~Ut ? ~rhWu QحL(qY|#e| 9g_DeSގ&Q #Mw*iS[D *>A,+YR3m:ՊzA0cM#q1 G[5겠0t 냥aw14|ӳN/ίx ڽI)uT[ #حj/w3G|$[_c;a|`0>LBQ+]jE_0�(mG T"j0y|eAy5׈zR$&V͡~v5vox�I7p+7�bf&Yim-(9�AVK)Sp.tP[WOmd$}:$>b}Y0Śm kn#m"Xt >2:tAx>yRpFf=i]0@z*4\]U vaJ J@74,'-`7zf5Pros2*='2+gtdx/ġ7 V 0=R0n 着RduvcjuIE&H^cG:r鼞Ml?\-IUԃc䆓P ,B'Ëty3P< PH2I#_?k [ cɋ(;y1]#Cek=td:؟pԪa(ξlM[�;Sx+KgmLP$yg.J7#[Qma "vuG�/z!wwT@c>rsnCb'LPJʞ`f��^ �HR0n8����!�context@openpgp.monkeysphere.infoopenvpn-client� 3� 9f �Tgw35?H EwW?fA^Y�37waEDdyX^74oeWF;hXYsT]ʆ 5k Dj8qvfQ/h0Re\*QxVQOu!ģԝ1/dU ATuI"/'�F@\ ؍m`w2z?! {J&YXV~χe AŪztDGEMb6aY*Jz'WL ZCV!T Iz>sfߝZm M閔Iu,oݵ^#Izף;m|r,#G"G%~l286 @)VrB g%{;Vx\mJ@iۍN(G;ny!e?yGF, .lFB9isai�9bMs5+=0j>ʷjFNeSM^ �H8����!�context@openpgp.monkeysphere.infoopenvpn-client�U:K  � 9];57OӔ$V<98Ӓ߲-w&{q!�"ZUʩ�Oq۝1r Ա%N t>>~;ro0h$pFYᴒCLD;KLb2N:9}GimRH3,GpWw1[w9|G H|=̺{!\DW/qxIE\CuMMGV5;{9akLAiw碌4>ef_k;0ő9k1XԻ f NS.Pɚ.|x+ ;!z(([?.ƒ3 ⦊a gFʅ}ㅧbqԼw;YӀGOi1,OUō%vc-E@-Kr#:[UQZvfU/�ֳ(Bf^W5,׀xY7lXL݉z.D ppo @Ң,]v Ѡ-|}?iS~cȹ T�co1�^yDH d} _.`)8iZ{c*1T3ipN!ca/" EA͉ۙ";hW V'6vj[~|~kde"kZ}&a?g' D}}Ԧv!yOFMSt2|H+/o(JqW�oEm!drvwKl\TJt <uy`ܯg9UB7 P{PTe^xh6�/w E݈ X(WF#+>ɰ[q$@!@LX 5CA6r[@8LMjzC1wS *]e-?e']/85|{SyƘڷ~d(l t9+h{y&"ZT<bc&N& OY Z*TL9 T��% �T  3� 9鲵fM �Uf|T`]T  VuK9uNkmZqj8ہ=|Y;zdjӳ#Vy,tuņl].%R@%1y)]m2c\vXleK0܆ Rk՞lo= '�V`_" z.2;W>KΪ\٬hEA꒤7Ӿ@3eߍuGNgX h$z% ˀ<<\!2KЬz[X$M<G&/AV �}o_LCu%Ѽ*fV,;3U)cIiO(?TLkڽ $>:fbMl ZnG2 %Y0<D z]c{  au}mRS .Dzp -i]v/{-d>#A ؏;Ou ǬqpO'̇>zW衊#g z3T\n +G@p"lI>urT_!dK|^8% �T\n  �N�� 9ƐcP`Q+>ktyP ,_J][K <`�@Dק:-mjcZ1TFi' `W-֋" Ґ(H"twX0$v(419)3dٻ~n\*<E YG4|UqL\U%ep/ xv^׵ tY52Ut76A,-Qܶ;hB!#aH-氥k4<I,896 .lK6FFbл gY| j)?hD7b FeD' P0'd;C9VOj[4mS>$˥!=̺{=3ElfNWjnN.mD.lQodkI4B¹FUlzwdq27e9:u n%JvMѰU=7'p%F Ve�Ʀw5r܎Q<'~5}peo {"|D'3u`9SXdG\GZ_hFºbz*}m^ty~\~$YAVQE)L?&v5(:e+5nT~8^"6_xQK6YMHe8ߦXDYjTI)_>DH&R�g==c-�ST[o!xE kn/P"(N(c5,2'#Yt_XBtSp[ '$^,mK�v/W&Sm&DW+ɖgL5䳬 JA1x[x5D|v@x)^a.]Д7/qU1O= Y $[Ec+XRz{Ư 3:yĪqEp`?ҟ0,Gɛ>rd"jŤPfOqqv)*�l~v@I\n-Ev�� �Ve 3 9  �fVe_����.�(issuer-fpr@notations.openpgp.fifthhorseman.netEDB2E74F56FCF2B67297B73524ECFF5AFF68370A� $Zh7 q']sF7ryc=KXn(q&=1tM>S/sn>MH،i`9}vb nӭ�ZBSуYtQ@D `be ݮs< td&~-NB Wְ:0}¢bߋj�}!ͅ<]|%Cws ~sCէkt7|e~t g9_.i+uS d:m~C ER]oNDC[d|wGJ]w}W؛ie0ݝyHh)% %S@?,kґ؎k]W8sh&f[^H&ԝ%Gb-R6 >eaTlluy{@5*'h{'+Xf<3 Yu�I:,:D8ZVdP߿ "`1öԯJ( 8`$ %8H�yZ:j@=ud޹N !C^FwˆM=w`vfo9CK|KMτ)k3Cjz ,G9|m`S@C2:(|tI"͎Tim<C%ơX l<^4"3ż tzʞF ~y,P,8wq3 ‚T챞c,*&N/L[TQmn7ބ"+6!.5\75[y6_3Eː  -$w hxƨc}]WN/IDonm~!YZ\|sQpC2v<Ӿ^?NJa5 _:*ZU+y.L*xr3Ňƅ\8,bCq,ls:UgQmvTq= ]g[QK'IM225 GBmBon]},5-lA!i[ Ve�QSVhQ:<UH >ҕDʁIF,E~3C9IO3vUԪܩ+6NߡҦ5Ğ>}YQ\B]1~ؓ.ZQ gKVTpm|Sk% C]V8ث͜~{a) $oKE[WANB:sbxb|-(m܊U౦G8EgTphd| Y8a@{xᵧB&Zv�#=/;'+#|=Q Rh-&EԐzbH`P̵73ܡNMM2Ҭ_RB ށhSP DHޑɳK$ioߒG]Վo _T@]%!?'+[]ՑӵՑ7mH"7x\��% �Ve  3� 9}+�9EDWjq{Օb2$hs[ fwSֿW(k+# .5NfV mD`Ʌ#nbp!I dZBWFh* r|. -E{2~ۅhѝ~r 9 qMzjC/y_Y+(Q2+͝Y Q$I9AC}O5Ta#C~=s/L/eVݖ!XH)�;o-,Y)ڌhq򓼀nE wmyIu~Υ e62}m 4 [`.zX>H@ oK5,q^}2Й.iި@(G,3A< /{ȐSav-|G[B+љD: ‰ךJ .ڒCӉo�o<hjAFAHǨ'M C.L.%fV XZ�` 2$u�'δւll|`>$U8,Ffkj _W7}iAR|7Gfɬu$$Ҝ9TN0ɢ[{T 'NPzKhҿ2ߥ܃qW3ZuPv}Nv51%}83WmE~rQ8kaB`%NP-ٟ6KJ8ai!)}WȤZ 8( ІL5ՠ(rͰ("]lbG/!*Pg{ޒJ_utЃn*`gAƉ ;ӵ'"|D]IR g%gY̐lD)| WԳgC\5';K*eVF(˞H05K#lk[~f!;Ii;7$Z&'>j]0X{VZ_w?NStoи7��< �&!得 u@9XZ  3� 9;I 3C=yh=-qz˼'tNt3s% w1k5Wk@M L x!780�Ipps4r< WcD+of#(i4ͦ3;$Z)hHVRåAWtYY {…LllM1p„_ L3⣦VXt;Jg*> q϶X h lBcU2)݆^APo''u_hf8//'j@nsiN�h 'wU8jv9J)(.%BnĢC#'?b pջ/sHvͮ*KS{?$OL"z:9z6D35W\pm8"T 6 YcN)Ϝ CPok]&u&,_'uSi>\'WY XZ�Ӏz) ftK=>,#&-Dp^],hFGZ~v]A!@}FOq2o` 5j?>AW<~xvab:Ee$${ %׆/=J)A1zs,P [4glX({0]9( %BTeu!L\ d.ezq7wO'S.>Ł?}`lv%l/%kseZaC@ݯ+L6g_@Vb&́'}9̺$^f~?3 @Nו n-.q)`k2u@(�ĶGdwnT/IsK0'e'*&؀T?gO'=a]#F8InC!ձ*DPG~DpvX2p\`4 I*a7w%=z^]Em^ݍ;��r �&!得 u@9XZ 3@ 9t  �!8'`QG9227XZ� 27D1dl=$e ȡ<dOĿ+cem�MW~RXȦ �<V)mМ[3^׃43:F7@T#M{#1E"ݜܶR q℘ * q̲Ĵ@9{;0oV F_Ȝ}͈@t,}rrp*i޻&4e@8:jK$9~0%}Ʃ-4~h:�N$`ov"Zr I]Mynv˰**kpO59yf~X@I1m`F.w�j09D<@PS 5Q%VZ ip@wtB|.?\RPԞlQ1]ZIVB+'D3Αuţ fNfa6$5K#ٳ.hw ͱ@%HX#*@ZjgP^�jPz`),\}GRq Hxx?HIoFD__ QUN�JX#CGC#W~U9,7.w'.-j@: ⧹¤ϱhBE}rW5tLN&V"ѹ 0seD4`zZG<H8@O&ϑ d<_`6VĔ \ZT|="bLh!g>NX']/K5Qٟ1<!%'jGڡ){W q[ޯouOb`"ǭ61Պd̕μUz{s:Q'N0PR<p+ nZmKQ8?#$Y\_h"ɲ$L-җ=̇P!x3tD`|"[b!J)Q`^;k!y8kKzPt zc^q 1Z%T �Kt%0mf^&'|*䞤g%{Td^V:c/:zVMpt \{4~uv`{rE{ 3jr37l/z 4= X b$%"qL)i B@_V4KX#W}]eƢ5f@+$$L*FZXd")2ٿQdi+/ur(Ԗzqt25f ʫ( x.F,-g;vW\It+f@*`!}Ƣ**!Ps{o <@yyH`LMODUx{G>vں@2G h0h1i&8Q  gcCCUx ��< �&!得 u@9Z%T  �� 9鰆K> \l.H0ʈ o s|KAטQ`mô6 sciIT!rKK_)\2fmҵIl(ɗ,Չț�8H\J{EX t[veY_#u/c M8Vj?{qjI͈|$a_BΜ"0T;F> Έ| L(4 9B/{61V#d fnlW'vy /%_K2uWʻ]Ȇ*5>D?ぜVE5 e,GDHx-@~F 3]l6\/qI>4h]J뀶ϥwR4v`LAAl=d_ MvGFހ�ܠKw020>ޘLUm5 Hp9$ZӳYP3ؠ)Zw|ރ' L:/@7-(owiiI{3/}46 Z%�Nx"sb˒p6{DMMhH8%R!J֮$ojMC�]0JTDw;xyBJRwjepyS9-݃ϒDa�ڲ)_aVfuT\ܧ~vKh؝&* eHh,1Fvx)zE )$lyy䯷o~ 9x=aG.<PlloFi9n �皈tK"Sc4cqj3Rr i-53t؀}ϔ�g(DK;5QH E#ޯޘ?\6u;ۮkYupy)ɱ0 MyAHm # L[P]/P-{JFi %ӬE;-i3JsV-Y댚oٗnx8 Ja.sLyeOx/!��< �&!得 u@9Z%  �� 9tyeYv#C6.@b#xN]AnPՐ_HX${;?RZаWRDй.ׇPuؽ`ejM %<lzFcw%Gǝ8ZI~'Zf 9b+iU _R;vA�.�+vٌ4?gz7zկFw8_!v(j7`a/ow }̼Nk\F|VH=Fgt)D} (ki]s[i/YƯ[* N6P(8 0=S:s@j@tLF(@җch�gvTӛ0[s 1LjC?uʴdfa0AE)~ۧ!%X HNwn\/ms<̮t0y@z) _>8Y$QR<d\6g- Z% �/* 2躊۾#&XƒD7fl|FzI_*F3(9'0IJ[496ƃ/3T79⭻ ՈgֳEG$^A69O;I<9;赵 t& hN@"+(VeU+8'uA=6J>!⸁cQBg lyq.`P;� I 2%LR)2vGoS,#_z?.XeѢ=lP"=3ANR.C 5m&mz4|$bzUPAUom|\+Cgy@EL3.]fZr�-oҧ|aFX}- 'Y=7FW͑OpodlX(X{ ơ5z np3T"?K6|[u:&cf5\%Q��r �&!得 u@9Z%  �@ 9t  �!'/2糖!gS3Z% � 糖!gS3g!g蓅.7TLUԅŐhwto=qŒLG>qX1.BKG?Hr!4 't+%2Ŕ4.Ύ%8 5ٛ6$G|5B>cH W%<xƄO # 9dAMhbYHhu; Wagg  eW�f݋=n4/C=]] ^10=.m{_kS*tg (Q b[F'r՗qg ck5G8Bx7X<AbACk $X1f~zt1{YOb}0J �^EVG;_k#v(9)-pnb"*#htq&B=N'eIRZaƆ- ]�P Di,RI6+) ?ܩmPs tkC\KZv&]]IKn;sIn70`hSk[ KCA=P_(,l7*YP(^FxU1-8O Kj[}UkbvՕ*-8r7)Du]((p<9m3J˘Fv&w-- %Т>kdg_' Y) gT1GeNMan}SȔTqn K9ּY V-푊9ږ9p Xӵ-n:[g>opXAzχ|L,K * yLrxխAq Xk�N'?Ti'܀ H4L&O:n<w]4֔`ptR4#3ߐ!Z<.Žn&&/xIZXڒFYVgG;E$Dֺzی`F0Ol�����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/keys/dsa2048-elgamal3072-private.pgp�������������������������������0000644�0000000�0000000�00000003705�10461020230�0023153�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������S\F�0pr鄀ɔLv+4uNFӄ{m2sPrq*x耟4+G| !ˡ-S8"}3 0K!D8,=eRJ,}}"}WR Ly[ AX f'Nt\;U>NJ[Tj—r3ϠLUzN<P?> а+olx (}x!e\y:cZ\{Y 8hq&#�?"՟x A +(pGh_8z[TG'X{Σ$-ۻi&gң$.>�uU/80;S*DFK˩::BbHW;88e*iؒBޝbղNE $!m/{Z&)8Q.6ϮC]44dk3KG񞪹[P�(u6%nU&YM <{,wY5ljMM`m1&~pgXlNbx5(>W"AǀFXǎg`)Pgx|h#vpn$"Mu!t 'q $6T `+UΉS'Hf`as%J\amR:PPj=Vh4GvW*{4Sa(ulF$GǞEhd;pwDǶ߿\)%odzu`* rWn A��6k$%L>T,TQF^ۗ"EEl Gamal <el@example.org>�8!H)G xyǁ)X?ոr\F   � )X?ոr9�~/9Þtd89*�a,k�M�Q _ )*^If5;E$z(E\F �vV:8U<h w "|r"; ˆSMõyF7p_}ߟc:FG*'偞}IG.gܛ`7V5q $*8r=d`uP]W:%�*jʅQLpRϐ>d!bBfD#'X쨕en]d9 **q;A&De:hKxWDNSsB;Q&6ZK޳TuՄc\*9 7O;c0K=VUҽjXgjܙ.>xL? ۪b *xB3j*V<&);+� Zb+RvGt~FII LQߗ1E+(Q!~!+.гX2'iIM=ꙨE39*v�Vjfhd@h"O]iŊ]t2 ݸyU}P/8vc?[\ׅU3Ԓ 4"+əcVD6y/3_~ <iA޶+Gz1>4U "ϻr oiS5b6P56J;6Z59Hkvb6!3\qҵN`:PNScm١ȱRpx-~ʙWQ;C +�)qdhkK3 &Ic+�?B=h/;K1( +Vhϕ|͕ k;rزlx� !H)G xyǁ)X?ոr\F � )X?ոrIT�{4Cn ~;q鸈w7#�]׮3, ޲O戹^Y�P�����������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/keys/dsa2048-elgamal3072.pgp���������������������������������������0000644�0000000�0000000�00000003550�10461020230�0021501�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������.\F�0pr鄀ɔLv+4uNFӄ{m2sPrq*x耟4+G| !ˡ-S8"}3 0K!D8,=eRJ,}}"}WR Ly[ AX f'Nt\;U>NJ[Tj—r3ϠLUzN<P?> а+olx (}x!e\y:cZ\{Y 8hq&#�?"՟x A +(pGh_8z[TG'X{Σ$-ۻi&gң$.>�uU/80;S*DFK˩::BbHW;88e*iؒBޝbղNE $!m/{Z&)8Q.6ϮC]44dk3KG񞪹[P�(u6%nU&YM <{,wY5ljMM`m1&~pgXlNbx5(>W"AǀFXǎg`)Pgx|h#vpn$"Mu!t 'q $6T `+UΉS'Hf`as%J\amR:PPj=Vh4GvW*{4Sa(ulF$GǞEhd;pwDǶ߿\)%odzu`* rWn AȴEl Gamal <el@example.org>�8!H)G xyǁ)X?ոr\F   � )X?ոr9�~/9Þtd89*�a,k�M�Q _ )*^If5;E$z( \F �vV:8U<h w "|r"; ˆSMõyF7p_}ߟc:FG*'偞}IG.gܛ`7V5q $*8r=d`uP]W:%�*jʅQLpRϐ>d!bBfD#'X쨕en]d9 **q;A&De:hKxWDNSsB;Q&6ZK޳TuՄc\*9 7O;c0K=VUҽjXgjܙ.>xL? ۪b *xB3j*V<&);+� Zb+RvGt~FII LQߗ1E+(Q!~!+.гX2'iIM=ꙨE39*v�Vjfhd@h"O]iŊ]t2 ݸyU}P/8vc?[\ׅU3Ԓ 4"+əcVD6y/3_~ <iA޶+Gz1>4U "ϻr oiS5b6P56J;6Z59Hkvb6!3\qҵN`:PNScm١ȱRpx-~ʙWQ;C +�)qdhkK3 &Ic+x� !H)G xyǁ)X?ոr\F � )X?ոrIT�{4Cn ~;q鸈w7#�]׮3, ޲O戹^Y�P��������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/keys/eike-v3-v4.pgp������������������������������������������������0000644�0000000�0000000�00000001610�10461020230�0020265�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������-----BEGIN PGP ARMORED FILE----- mQGiBEHsHZgRBAC9ZNtYMBP6ObDL/84R7l+Cg9YTd6uw4n3oRG1iTVa70rAlh3C5 kywcOqfuO62fCL1WgzoDJBb9r6q49qKZyCbMLQscISu9eAoeDvXa4oc0N/ynbdfg jSeYbsst6H0YO3BKHHfmOj1AAi44b9wI8qLxxtjnHwXHt2w9e6W/T0KLIwCgqJpO JxNE6GlXOowSAm3g1AUUfVsD+QHwg/5IFF7fUx2QNL4XwZ7uMqsE8FmSDGJ3sxoU IFDZ+9uiQFz2jyrSzo32UTsYcUiidqjJuX3YsJVEBjI2lov6/uqo+ZjrgJTlapUo bKhpGl/+s4HpvlhPVZydcXRXsBVfRrdCg5+dtc8LEx1Fkkml+wFQ6WuA/yzFBUSK jDrkA/9z1U1BTUL27v4NpIpKT51gRcC8EVLyB6D5XjY8SnoJ5j0Pj4TfuupbOkOI vQ0NITvEqIdEgDizaNBmcDPqTLBVk3NWaGflHkW5NWG6nldc7uV9KTZhzl6Tc0ZS vv1a4s7HA3SR9wjOLr59dlN+JXwouBhzaRrReNXaN+cENVmKCbQgUm9sZiBFaWtl IEJlZXIgPGVpa2VAc2YtbWFpbC5kZT6IPwMFEELKoSHb0kX8s7KhLBECOysAnAsL ryFu7bSo0ql7ScWDQJYv++ujAKCo7jrI2pCmVI6Wi2tWjfAMb7wFS4hbBBMRAgAb BQJB7B2YBgsJCAcDAgMVAgMDFgIBAh4BAheAAAoJEFykiT5pufxOISkAoJ44bxoN aNI8a/o3AsfnRQws4CfMAJ9l0AfjLHFM0ae0cqeemhvGa3qUbA== =HReF -----END PGP ARMORED FILE----- ������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/keys/emmelie-dorothea-dina-samantha-awina-ed25519-private.pgp������0000644�0000000�0000000�00000001145�10461020230�0030162�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������X[9 +G@~ Bn4ʾByx¾rI��npjb* D(a6/2SUG,Emmelie Dorothea Dina Samantha Awina Ed25519�8!3F&3yvyx 4,[9   �  4,h�`Ne^zNii>ΐٞA"�E�Ȑd(U2VR X[د +G@м(.c'Kd)٦c]A0ܔ&��B:ݸ h=puR >mgö� !3F&3yvyx 4,[د�  4,v �!<Jōn"l2[د� "l2hM�qTA8O<%3r:{W<&�)/֮N3 5Racf{`HC¶ '�J1_o]q2 G)?Y;y�Q4ɗ0Trw| t5 ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/keys/emmelie-dorothea-dina-samantha-awina-ed25519.pgp��������������0000644�0000000�0000000�00000001033�10461020230�0026506�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������3[9 +G@~ Bn4ʾByx¾rI,Emmelie Dorothea Dina Samantha Awina Ed25519�8!3F&3yvyx 4,[9   �  4,h�`Ne^zNii>ΐٞA"�E�Ȑd(U2VR 3[د +G@м(.c'Kd)٦c]A0ܔ&� !3F&3yvyx 4,[د�  4,v �!<Jōn"l2[د� "l2hM�qTA8O<%3r:{W<&�)/֮N3 5Racf{`HC¶ '�J1_o]q2 G)?Y;y�Q4ɗ0Trw| t5 �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/keys/erika-corinna-daniela-simone-antonia-nistp256-private.pgp�����0000644�0000000�0000000�00000000466�10461020230�0030577�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������w[h*H=v%B�4}C%VHqh!G˳3}<܈6_@OM$G��yb3V'hYڴ)Erika Corinna Daniela Simone Antonia P256�8!_{"|}k֐hF[h   � hFy�>d}] n @>-~$�d%`]PwH<ܽ [SQEŊ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/keys/erika-corinna-daniela-simone-antonia-nistp256.pgp�������������0000644�0000000�0000000�00000000421�10461020230�0027116�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������R[h*H=v%B�4}C%VHqh!G˳3}<܈6_@OM$G)Erika Corinna Daniela Simone Antonia P256�8!_{"|}k֐hF[h   � hFy�>d}] n @>-~$�d%`]PwH<ܽ [SQEŊ�����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/keys/erika-corinna-daniela-simone-antonia-nistp384-private.pgp�����0000644�0000000�0000000�00000000610�10461020230�0030570�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������[5|+�"S =]]>;c&hrFLj}I+L~7gŵ4�|>.}3N$JK. Z˟맮_�}ZHgͼ}UJa[X{pbIOTVPS~3OT^D.Erika Corinna Daniela Simone Antonia NIST-P384 �8!7cfL ժ}[5|   �  ժ}]6y/TFk4Ԃe_:Rȕ+#�'B_7 V^刵O|,YJW{.iq݄wt������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/keys/erika-corinna-daniela-simone-antonia-nistp384.pgp�������������0000644�0000000�0000000�00000000523�10461020230�0027123�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������o[5|+�"S =]]>;c&hrFLj}I+L~7gŵ4�|>.}3N$JK. Z˟맮_.Erika Corinna Daniela Simone Antonia NIST-P384 �8!7cfL ժ}[5|   �  ժ}]6y/TFk4Ԃe_:Rȕ+#�'B_7 V^刵O|,YJW{.iq݄wt�����������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/keys/erika-corinna-daniela-simone-antonia-nistp521-private.pgp�����0000644�0000000�0000000�00000000740�10461020230�0030565�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������[6?+�##�|:'@ U #_pq ?͜B> #c>gn9 �yǧ8jd[^C̸:: A}<¹Os9l˷:V{gCG4P ʄUL� KK-bj89 :4*9{f*oC!@>R1GUd Llڞ.Erika Corinna Daniela Simone Antonia NIST-P521 �8!I;ٚ@LA[6?   � @LAZ@غ]1p <l<kCHř8:2Ѷg+DӨcč[_`bZEvؐ:hTYQ֊3^t]HoqTx.O`��������������������������������sequoia-openpgp-2.0.0/tests/data/keys/erika-corinna-daniela-simone-antonia-nistp521.pgp�������������0000644�0000000�0000000�00000000631�10461020230�0027114�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������[6?+�##�|:'@ U #_pq ?͜B> #c>gn9 �yǧ8jd[^C̸:: A}<¹Os9l˷:V{gCG4P ʄULߴ.Erika Corinna Daniela Simone Antonia NIST-P521 �8!I;ٚ@LA[6?   � @LAZ@غ]1p <l<kCHř8:2Ѷg+DӨcč[_`bZEvؐ:hTYQ֊3^t]HoqTx.O`�������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/keys/hardware-backed-secret.pgp������������������������������������0000644�0000000�0000000�00000001317�10461020230�0022766�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������-----BEGIN PGP PRIVATE KEY BLOCK----- Comment: from https://gitlab.com/dkg/openpgp-hardware-secrets/-/merge_requests/2 xTQEZgWtcxYJKwYBBAHaRw8BAQdAlLK6UPQsVHR2ETk1SwVIG3tBmpiEtikYYlCy 1TIiqzb8zR08aGFyZHdhcmUtc2VjcmV0QGV4YW1wbGUub3JnPsKNBBAWCAA1AhkB BQJmBa1zAhsDCAsJCAcKDQwLBRUKCQgLAhYCFiEEXlP8Tur0WZR+f0I33/i9Uh4O HEkACgkQ3/i9Uh4OHEnryAD8CzH2ajJvASp46ApfI4pLPY57rjBX++d/2FQPRyqG HJUA/RLsNNgxiFYmK5cjtQe2/DgzWQ7R6PxPC6oa3XM7xPcCxzkEZgWtcxIKKwYB BAGXVQEFAQEHQE1YXOKeaklwG01Yab4xopP9wbu1E+pCrP1xQpiFZW5KAwEIB/zC eAQYFggAIAUCZgWtcwIbDBYhBF5T/E7q9FmUfn9CN9/4vVIeDhxJAAoJEN/4vVIe DhxJVTgA/1WaFrKdP3AgL0Ffdooc5XXbjQsj0uHo6FZSHRI4pchMAQCyJnKQ3RvW /0gm41JCqImyg2fxWG4hY0N5Q7Rc6PyzDQ== =3w/O -----END PGP PRIVATE KEY BLOCK----- �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/keys/hash-algos/MD5-private.gpg������������������������������������0000644�0000000�0000000�00000007112�10461020230�0022550�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������X^B �p㔬T8PPe>fl+s-~mZR+6nthyq<А(S#^g*Ƃ JfH?Lo) !I<`2�Ki34{FR1.FJ0O%;!(hfc7EՉ<:A ĻYg5~GR0+% ޢ ?[RgL\<&9wS X/Y6Y>pBr$gP|Tw$KvbֳN:@ҵ{-#Ŕ&頁l6AHe1ZD<jEQNhV+f'5qK{:ebL*n3՟��� >Ũm\\JP`Y$ًJ-ȤA4w;,tbf~$A(pɬzg M{p7a(ޓC$ [΂ ڼiy#Oo4ߏ?)<G kl+;M4ekvͪb)^^Q{̻4 �x=u 5mU6[4D?G[fTr4*G#9h4q>a-5oЪIUZe\mTj@([T6kڠ[W_mĈ:@" Uq<z[aLK@bTqk5�׈Ve,<ߺmUPޡW7& Jȑ }7›h�՘X#!ky� IlӲsuI>S꾠�(Q:WsO BWYѩ/v'<G[Ӷ\d'3* p"^Bix #g0ӨWB~cI`+ty5mo7}�}F/�(Ylf>j߾<NRW2 ;LjI?>!˾tI}rB(4} EEc0a"vv̬]gJ-pnv~[gHy/h|>\*XW-j�c\-IAP^w*Ϊq#yx*:0Di!=Ɛ]sT'\ן>*\_: _Dle�M3 (][_c|WY2@m&dpRٌe-\1?^Mv!|44mdLCx 6[dԥo_I6zK2sܷI@lWfk ^vWxLbEH_9R2MD5�>!x, \^B g�   � w\ �Ft{l<"l3GiS_\W@Ga+_RB Nr{qwuf{[W4 pW:y1)QW9D4g%+wf"l|\*M~4÷du W ݡ Pz;|݃ KچCkad |$2p#pxb%6thY~#*(^ykg5wLOgw.FHh8 �y^O'�\A `GIl ,2]fDsz AbV̽dv",hj#Tsמ>8&@KAz{(i;(Đo *_&UtW^B �٩z;*V˽\܅eRs_bQ.l鋝7#Q$Jܽ{ j2A#.ԗJ|F^@87Ky�|kV *cwhF!-z\{O�\g:ۏ[]Fr ţD|Ԕ=$ٕX|hzQqYU01a)L.mO}|?~*7奶5p{I\ ""^.f]x(}"]P8x�QqB zUkjўe^Sgז tA͹{F<\k�dCT>Eڷӗ#iJx-qtF-C��� 6'?ѷoEV]ddX lX; Yɞnwa[,25N!$70:s3"SCi`r}<Õh'[Pw,7ٵ9S>5dW lD{[s&p,o.]/G 3\;rG7M,$-^Yj7OW,s8t)f.ݸq(YQ gNBo"㫗҃h_uڿS4u~r;&[h[h_Lp/F*S#T_ĩ"Dt_Q]:Ꮁgaܠ %Pl̰SvFxdo#_�7^Z F(<)Zun#  ò͝v i,g�U1yyB0=Rw#cbMk\Q >crg<܂p;m5ݾNRեie#-4*‚$5Z a s g g.*ſ�p:u2_i@ 0!ِ)y`ޢה F1=zZc* )qf,29)rnJ?R喇d%A080Kֈ_#t뚙b Y؅+}:<- S?]w LGi'mx\ńJ哂p 'm<5GTkcv' N \Y룪Gx5ysAoU0`CԤ_<G P$@"lY!.L!x&xhKG'_z ŷ!Rtzto"5 r<oY\}crN87ǻ/J=|5k*1Cͤzg&ƂP2f(tgБ� !x, \^B � % XLD$ xl5b>ܠ?⑌eV쑬m|3\;9q\ NϒLTӲ^w/ Pu/W|4G؍B�WjsO:A)@l(Q{8;T6"_]$1n*/BIbOeoL~Y-f%N)p6|�t@wPMrLkA?{(%d{M1k`5(iD'wVSRQkoӤmDH)@J-pzZNE<vqTG4eit R6W1Ÿ qY Ebkh)T6L������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/keys/hash-algos/MD5.gpg��������������������������������������������0000644�0000000�0000000�00000003265�10461020230�0021105�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������^B �p㔬T8PPe>fl+s-~mZR+6nthyq<А(S#^g*Ƃ JfH?Lo) !I<`2�Ki34{FR1.FJ0O%;!(hfc7EՉ<:A ĻYg5~GR0+% ޢ ?[RgL\<&9wS X/Y6Y>pBr$gP|Tw$KvbֳN:@ҵ{-#Ŕ&頁l6AHe1ZD<jEQNhV+f'5qK{:ebL*n3՟��MD5�>!x, \^B g�   � w\ �Ft{l<"l3GiS_\W@Ga+_RB Nr{qwuf{[W4 pW:y1)QW9D4g%+wf"l|\*M~4÷du W ݡ Pz;|݃ KچCkad |$2p#pxb%6thY~#*(^ykg5wLOgw.FHh8 �y^O'�\A `GIl ,2]fDsz AbV̽dv",hj#Tsמ>8&@KAz{(i;(Đo *_&Ut^B �٩z;*V˽\܅eRs_bQ.l鋝7#Q$Jܽ{ j2A#.ԗJ|F^@87Ky�|kV *cwhF!-z\{O�\g:ۏ[]Fr ţD|Ԕ=$ٕX|hzQqYU01a)L.mO}|?~*7奶5p{I\ ""^.f]x(}"]P8x�QqB zUkjўe^Sgז tA͹{F<\k�dCT>Eڷӗ#iJx-qtF-C��� !x, \^B � % XLD$ xl5b>ܠ?⑌eV쑬m|3\;9q\ NϒLTӲ^w/ Pu/W|4G؍B�WjsO:A)@l(Q{8;T6"_]$1n*/BIbOeoL~Y-f%N)p6|�t@wPMrLkA?{(%d{M1k`5(iD'wVSRQkoӤmDH)@J-pzZNE<vqTG4eit R6W1Ÿ qY Ebkh)T6L�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/keys/hash-algos/README���������������������������������������������0000644�0000000�0000000�00000000344�10461020230�0020674�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������These keys were created using GnuPG: % export GNUPGHOME=$(mktemp -d) % gpg --cert-digest-algo MD5 --quick-generate-key MD5 [...] % gpg --export-secret-keys MD5 > MD5-private.gpg % gpg --export MD5 > MD5.gpg ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/keys/hash-algos/RipeMD160-private.gpg������������������������������0000644�0000000�0000000�00000007121�10461020230�0023532�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������X^By �9hl__T嗕a͛c |DfߌO�*"Tq(+ĕkjW/&X5eF;{^ m̷�`aYn"l; YK"'dҡj Z'>J 5߲ry~( +`z \φ's8EFj۹b_}<XvP]qGa@5SkeY{$v:v1rtWF1׏K�d.%\Jʬ(xeHK:8W1Tx1xn'ƥvpeVjG;U;N}tx:C{c?cjVZX7AY.[��� N<]7H@?mNEXYO؎s+'qO ?`RK0mS̴Uk5_E, R/s 69E-ŋ6]һ4r:/S rJR5eڒLr01g4YO*ɲ7Яxi22PٚPYS) [.0?@\E7i9 YY2\s;;<H9odjd ̨v )eŠ=S~2INƦM0Na:I5]}DHj!=Ȭ&9H3BL""j>qnJw~.}ahD֐p{??3.*I�Cjs(0g KTr X@ٓmؐK)GU 2X0ïG{D(Uvn.) ֈ&kpD"a )&$+?[1vjIMtT.ʸ̬6B9ˬv5<}R a)aŗ9�KM<KvDP8'\/b+,/)bi"AG,A08[ҝCXS25( /NmS'y9DB!1"OUaueވɞ@ erߟhe1?@E0NOK~1x�ނD:[R<|:[9P\~3�BB5xi7@(5ueR6 -/]v=#<J.) ~MYa\C.$mßR_?[eo/^bXtr,vyθѓ_sǠEU)!ʆv9`"5nbg՗ Ǫ�s^=DC‘)Դ RipeMD160�>!#F_g�._:^By g�   � �._: -W+؋Diqt(fmN(ͣe f-@(hp oRqBCbzfzJz&K*g\m# Ҭkd_&N:eգ\S7(,.F[i|8/|LNOt+o79S_50&mnORnr NУ&Y,^7EGS30LIS |c%GDw#d=nu[wQ#;I4y_#RNP%6:T2b) 3Ȥ[2se蹊DŽ gGeU{3.(l$ BoE[L-j5SCh WQX^By �d,*4hm|WߏBHHL0!ɣwag_6kWΜ>:;oGj}?jh=fmܱU;{f&NV`JGfj_Ɩ cW4cYTy8\I]Aj Wd5-~~Eu�p8a>�6A m�|+W{I <`K&HǚFk*]e+5k8i9>ho3Kr(ߦyPϒp;RRJxîanE]=gc7w4K=ATk'}c xU#hM#7>VI-lih@&? ·vOMCz`USlVB'Z.Ы��� ,V1:8% Fbp8SV4IäbW'.# jl"6}ձ;5$~wrܧO*pa,G ;/_X~)BշM 78C{ i`ŸF{5:Fy {Q8'6]".fڭszQן*f?e צԇ2]-N>NN w'/J<:Ξަȸ \V|&v3;F;( bx1M~f*A%M\PYӃ^cs-80L~r~;[Yj&q؅#n4%i%i.ڂ�:Dӻ�Ŵ> V $~Aٶ17Fy|P}L\8w2ڋhU5G͆0H<f/kz@^ P<<GȑhF{C[GaS* ;l83V [7(@uL( dKoN[E pj+ؑ_w�JJK%|R%7Nz,'up'3i ;[t*zZ:k>S|no{gS<Kk{8UWu<PNgCH|{LkMF1+4@87AhJo5=s_\ #y?p+HB3IG-?@m l['5֬|b8&տG.>c eմ}7 vl}9a+ /=/2xtyokac _k:6)-oTJ*A`$ @+KWBe$4b6f[9fYșf9Y /7� !#F_g�._:^By � �._:W �+D=Q^SŸE pNy9 gmS,ݞ 6;5*B,kpDP*A#wÓv;oKg}(U)uvuOJS;$Ɩ06[lWS2W̤o PӝU1^af{SmՍgpcV0-z< 1>%Шc@krys#5ELL+}ۨ4[~8H`0yb?hr7]rRQ crkI2DxRAO}=Yib{YF}kh ߟۑWX{�����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/keys/hash-algos/RipeMD160.gpg��������������������������������������0000644�0000000�0000000�00000003273�10461020230�0022066�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������^By �9hl__T嗕a͛c |DfߌO�*"Tq(+ĕkjW/&X5eF;{^ m̷�`aYn"l; YK"'dҡj Z'>J 5߲ry~( +`z \φ's8EFj۹b_}<XvP]qGa@5SkeY{$v:v1rtWF1׏K�d.%\Jʬ(xeHK:8W1Tx1xn'ƥvpeVjG;U;N}tx:C{c?cjVZX7AY.[�� RipeMD160�>!#F_g�._:^By g�   � �._: -W+؋Diqt(fmN(ͣe f-@(hp oRqBCbzfzJz&K*g\m# Ҭkd_&N:eգ\S7(,.F[i|8/|LNOt+o79S_50&mnORnr NУ&Y,^7EGS30LIS |c%GDw#d=nu[wQ#;I4y_#RNP%6:T2b) 3Ȥ[2se蹊DŽ gGeU{3.(l$ BoE[L-j5SCh WQ^By �d,*4hm|WߏBHHL0!ɣwag_6kWΜ>:;oGj}?jh=fmܱU;{f&NV`JGfj_Ɩ cW4cYTy8\I]Aj Wd5-~~Eu�p8a>�6A m�|+W{I <`K&HǚFk*]e+5k8i9>ho3Kr(ߦyPϒp;RRJxîanE]=gc7w4K=ATk'}c xU#hM#7>VI-lih@&? ·vOMCz`USlVB'Z.Ы��� !#F_g�._:^By � �._:W �+D=Q^SŸE pNy9 gmS,ݞ 6;5*B,kpDP*A#wÓv;oKg}(U)uvuOJS;$Ɩ06[lWS2W̤o PӝU1^af{SmՍgpcV0-z< 1>%Шc@krys#5ELL+}ۨ4[~8H`0yb?hr7]rRQ crkI2DxRAO}=Yib{YF}kh ߟۑWX{�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/keys/hash-algos/SHA1-private.gpg�����������������������������������0000644�0000000�0000000�00000007114�10461020230�0022661�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������X^B" �vi-XD~; 'rg\ j۶ӹGLw&guP|'[~7À`I^̘n|!8šKDe/b$*!at obOi<ȧYeW݃RWe(2={L_8i&8kSJ̿K>w8N[608ޟODx&HzS=NωF~\#iIG+O.r\]KjZ)IJJ{|gp=1 fPd7Ek7I amX9Ä5<C.%oA��� #mU?ߖ7SL6a֬m<:lS *̃5%G]\5j[V>!g~2af8EZ0yl)‚1h#ZQdU.U6~c%E>NY˪TS�f꥗D<;ķ}+^Xreo3 DdycYm*l&6mCqi$ $[.6=[PB:4K oĔD9bLgN{cc$?o-aK} +<l$`?C)P~[bri2 8OqBBK2CѬ`;M[%m#a9�C҂,vP+ĔFJ 8)7==n ]*JOVqc" _wE cND8`zYV\X.rbJ MP9PӚ`nT_:{<LZwW,ɝ@BeC}Bo�ǫO!{[Jڢo59^-9>keC|d6[MwquZVծ@Rok[ %.N̴yObR=:9qGӼ脲5Tit*"?뎏h9 Ӑ<`vPivыB0_b)#05 5ocB-<~ svJkG/64!Tgxz:42ee)="eQ?mC<X-#T1yK s+[6.ߢ<m,r]&X>tLp!|4�)1{@d&ZwO="jq:~ɏWlߡ, `nwYSHA1�>!DAF8^᎑-^B" g�   � - #YهxBNj"zA%<{r*!L4A~(i^'R%NFS^!N8Xp4^MIK+vy<׵͡D/`O*D| ?˳bK:Ϥ,~@OiH- Ȅs{YX-;? qcwZa0߇D,?e;^& 0:MZ heBzpY:^Jltr 7R{]|rqbz4 I}s5 mu=Up4?jx_> ׀`wJXQgK@%<ߑ;v`u"4hV2a`N_LJ;X^B" �vfҨ!!^ Rsًvr x)?x8lIcꜾV%"SGnsgV;mq!vf:UPG6 ʺU1"ʛ&_fl7F^ NG^D7{{U$=ռrZ@\5*u# ?J>DwD61^S@FE+?ޞ|/4qt񨴥+=ț60ycņ0aH0n?OPeKu#r݉7mkV9bdE *��* �v}F qL8mm2/Ə. -i)zr%:;E[\_5[��� <!goqX9 Fhuvn�vFk#8(ݾ NFFf-{bGu؇-SNn4EzP_ʬ;q. McE fr#$h?Pr5#*Zub1 (vKDo| Ԡvn*ZD4 ;]/2 P 4ﱐC_[ }>c)/dznH AǤj[B2M<fYƞ8sA, M*7;d蹫y^eE?T1J?O=ib/pO/אS1_q.i9aWUA Cexx0]@v�䚂ߖHf^eKwX.YST/MzW119E'8he,f�=gM5a %Bӂߵ(ر%p>:nX1 Z=4y&hlgH=1a vB3YĆcR!W> RH>a =NB@m4i�O=Fq ƿBM%&ι!̉'~3;Vd p%Szs &EXȴ{ @tXR86�WS0=]*VCٖ8K59T^2Q5j ~ޘaF?_a>ݿse|eo&ݘm"0Wzѽ>HB.Y')t+0�6y=laO=8~V4VӴL8IKblC7(s[얾NgͰ*$4HcƯg3/x%D4rQ݌[8d!:w>ާ|z/\5ʹ 97{Uu_ Ʋw䇋QC܋B� !DAF8^᎑-^B" � -8 f0p:@Ž?g^i.K0Ddx\vV񟜃;c̢> {X5ߝ _6 /@1T&PS}I%^s$MYxjNsjo`~w>e'PϞ/~d_@xfdCg*`hUR(g2ym?a=3F È+4% ;+C5%wX-b֏]62Xuqb/q)MႳw%cZ!~8pP1P3$Rhr+zb2m*o),(a1#”;6Yy&[>c=aRlB^e'7����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/keys/hash-algos/SHA1.gpg�������������������������������������������0000644�0000000�0000000�00000003266�10461020230�0021215�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������^B" �vi-XD~; 'rg\ j۶ӹGLw&guP|'[~7À`I^̘n|!8šKDe/b$*!at obOi<ȧYeW݃RWe(2={L_8i&8kSJ̿K>w8N[608ޟODx&HzS=NωF~\#iIG+O.r\]KjZ)IJJ{|gp=1 fPd7Ek7I amX9Ä5<C.%oA��SHA1�>!DAF8^᎑-^B" g�   � - #YهxBNj"zA%<{r*!L4A~(i^'R%NFS^!N8Xp4^MIK+vy<׵͡D/`O*D| ?˳bK:Ϥ,~@OiH- Ȅs{YX-;? qcwZa0߇D,?e;^& 0:MZ heBzpY:^Jltr 7R{]|rqbz4 I}s5 mu=Up4?jx_> ׀`wJXQgK@%<ߑ;v`u"4hV2a`N_LJ;^B" �vfҨ!!^ Rsًvr x)?x8lIcꜾV%"SGnsgV;mq!vf:UPG6 ʺU1"ʛ&_fl7F^ NG^D7{{U$=ռrZ@\5*u# ?J>DwD61^S@FE+?ޞ|/4qt񨴥+=ț60ycņ0aH0n?OPeKu#r݉7mkV9bdE *��* �v}F qL8mm2/Ə. -i)zr%:;E[\_5[��� !DAF8^᎑-^B" � -8 f0p:@Ž?g^i.K0Ddx\vV񟜃;c̢> {X5ߝ _6 /@1T&PS}I%^s$MYxjNsjo`~w>e'PϞ/~d_@xfdCg*`hUR(g2ym?a=3F È+4% ;+C5%wX-b֏]62Xuqb/q)MႳw%cZ!~8pP1P3$Rhr+zb2m*o),(a1#”;6Yy&[>c=aRlB^e'7������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/keys/hash-algos/SHA224-private.gpg���������������������������������0000644�0000000�0000000�00000004716�10461020230�0023035�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������ZUܰ�(JX8F2V{LڭvE7bᗆ+|#aa~yu6_"kVq< ,^hվBĒKLA\u3h9ZG9[?eN�-B*wwL5J4 `X^os oGK(Vau,} Fe|"@T'Qj*7pRe J8lR2opawVNkv~ r?֦e?Os0<���7 NK-r25<ѫ,ޝ ޒ~mSjg{\- 0;_z$-|EjbE_5 G4ҵ-hmΔC_?ˬI4iPnyt@|e�Bؖk "12I`=`فL|i6k=1]Q\u(HMN`6Jzb`r?=JeaRْ^?fY`U2՝hIh;�p%)9zH۟%}vhv$P_uGWM ܹ>ywp$ 2 ;ډ=(jv˨!D&%3h:?G†}ro+ZK�*ex]MPS ]/N'e!!ȁiT6'(8G', Q_l׳Z) V|Y,w7ͭUs '^"+/kixnQƠ'&nɩ�J/x "j=l7VY@~xϝc4@M$'ladBSgT-*m|/ |Fވ)Q { M9!HogCBC3Jt\o<V0MSHA224T �>!㱐}9RCWY`ZUܰ g�  � CWY`X怅'Foמ~ hiZ|d�OWߴ\_yK'Pp r`CF, hzt/ %ǒȌ#s77ftَ{jAy/*WQ&+}6c,4! X䁳V6.'V_Exvw-KPFyٳoâRhB'¼|yw(E�kyZUܰ� ^'@В${ RFwhٚlAGGa'Sun% $ĺh^^䐱ee"`G5ZMz%¿ǵS_FH�ޢ d$@s.h\䣛?aw<ETootܕP fܥ1EAnJ\nJyམV�1r{UN.^~zeu1@H&a@y���V#̎čC $ܰ;q[#W]�X:u]aw8:m%) xǮ˼TAܿ拠y_v͌7'cȸۄ&8wؿJ86Vd߯X]:]*jHSqdI`|\008̳THhp i-s^OܹY%K够hstdTEF̱KPzG@֋TT}�XUEJo^,ͫX<w,i)*IENM3|2D YGeAx |, D krRNY&+P^ϢruJa5+nmf^ ϙik`˧.�/Roi9A|b+f[ # 0r!YԪYh]IF4A{Dj4̸?  ˦4&',UP\S󋀃oRbև%K�M-:rz%�-9JqԸEOtEh=NSKrzqALMen 9|ޒ.$bmj] JI i))x9l.ĒE ,&b;Ty 7É6 � !㱐}9RCWY`ZUܰ � CWY` �3,Ü%q/Io,ڨO ET C5O M�? ;`{u. kq{V_iFl=*LRfU8n9ܼ>~n2`ex ֞s g$$DMX cR-vkv@Mh2D8󜚔u Q f,M(ov WA G~s \Rr%:3uG[2%= /•ޯ]��������������������������������������������������sequoia-openpgp-2.0.0/tests/data/keys/hash-algos/SHA224.gpg�����������������������������������������0000644�0000000�0000000�00000002270�10461020230�0021356�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������ ZUܰ�(JX8F2V{LڭvE7bᗆ+|#aa~yu6_"kVq< ,^hվBĒKLA\u3h9ZG9[?eN�-B*wwL5J4 `X^os oGK(Vau,} Fe|"@T'Qj*7pRe J8lR2opawVNkv~ r?֦e?Os0<��SHA224T �>!㱐}9RCWY`ZUܰ g�  � CWY`X怅'Foמ~ hiZ|d�OWߴ\_yK'Pp r`CF, hzt/ %ǒȌ#s77ftَ{jAy/*WQ&+}6c,4! X䁳V6.'V_Exvw-KPFyٳoâRhB'¼|yw(E�ky ZUܰ� ^'@В${ RFwhٚlAGGa'Sun% $ĺh^^䐱ee"`G5ZMz%¿ǵS_FH�ޢ d$@s.h\䣛?aw<ETootܕP fܥ1EAnJ\nJyམV�1r{UN.^~zeu1@H&a@y��6 � !㱐}9RCWY`ZUܰ � CWY` �3,Ü%q/Io,ڨO ET C5O M�? ;`{u. kq{V_iFl=*LRfU8n9ܼ>~n2`ex ֞s g$$DMX cR-vkv@Mh2D8󜚔u Q f,M(ov WA G~s \Rr%:3uG[2%= /•ޯ]����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/keys/hash-algos/SHA256-private.gpg���������������������������������0000644�0000000�0000000�00000004716�10461020230�0023042�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������ZU9�ݭf&ϚG;XYPL*`^4JX^W5j`-Ш8(ro`<2uY+!WԲ<W,Du1�x*}l"eNIaOpAytnـ.-D-*Ѳ^�X '_ ^sc~D]awE]eADfGz-Hf51k`xK2Y\LRBֺ2@pY}n:K{���I4N6X.ݫ_YAnz?9:XX~<bX臋w-'vBr#L㠾R-~pɻa@NZMC3 P&W vy٣ޠ"b:,z̗ HB6Dp[I[njzGGz>pf9 8o`**Yx<UvH%zWÎ*gUx |Inu]b # I{C?1�Je5J(9”T u4/KMfp`vE9M (_Č LnߠPB*R/b_hò'4#vpM4Fѱ!1F| +q2&Q#f�(W@D3Ȑ6-�&уV. 6uHĀ^$pw_%V8]w(BgBY`ZY 3ٮ8x"Hm1lU7{VA@v^|n� ?9clIS EG^B)+=[N,]Ǚ h(+b3Đ i�bKIlsDڤr5eWە$? <2M`y rkL*.? \5N+\Vn$T4ƴSHA256T�>!qVW"UCZZU9 g�  � "UCZ�P iV9T!@Gix0Fg.fE9WP;i HwEue7jeɡՌ^~-(y@3WlPXʵxOt[yx;"IF 90QTG,& r"_DNFnd D).zϯjvgEMϺWRRp&ұ쿌]nɏ"8tA9uk`/sEYGn=0CH6TZU9�FpS rX,1aJ ^ @rw&B~؄cr}hyFfcӾl)LM$/hr,6/\ i/]ӭB�+^8v[umӻu@ =hIn^0<:r<偐U7MYϸW�AN˻(Vl=Ǫn (]{ 5t9(X6J`I ס3���gPd^ y;6w cƸ@펁#^d{KWl?g)ѹ n.sN⡉ږū5-@rc<C+r*� YPZ/]Qe܀J+<RzdOZ/hq{֢D -WdE=[ C3 _JM] ˈLrhvDe  QA1qtȯ5Wu^|l?iʎŻI d W<Xr9�TaYYg=/ҽ%_Vl%*Qhhs|0v BǕsp?gbwý?dӲ9jx **K8}CX5S&`iF,5>B -�+J{!xM:WL]r]-ӜND [NnS8OpRP*j\:M+E}-FS{Q8X;א|=}8T |a<XS6_*UN)�pbyu\Ύ}o ̀Э7]%'}N%k5 C9'OwK)K oCԥmF_RyJ\vZU^cgѾ5]f,[[DjP[.6� !qVW"UCZZU9 � "UCZP�(y@T).t�A}s>tz,HKI8}{;1~ JYs2̋!H$ꤎk I" ^l:rK&=s;qEmO<MqA @\b: j=-@2 Y&#!XsOT"x0VG߻t}+*`dz_do7 > ZGh)�)a?iJ8)4��������������������������������������������������sequoia-openpgp-2.0.0/tests/data/keys/hash-algos/SHA256.gpg�����������������������������������������0000644�0000000�0000000�00000002270�10461020230�0021363�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������ ZU9�ݭf&ϚG;XYPL*`^4JX^W5j`-Ш8(ro`<2uY+!WԲ<W,Du1�x*}l"eNIaOpAytnـ.-D-*Ѳ^�X '_ ^sc~D]awE]eADfGz-Hf51k`xK2Y\LRBֺ2@pY}n:K{��SHA256T�>!qVW"UCZZU9 g�  � "UCZ�P iV9T!@Gix0Fg.fE9WP;i HwEue7jeɡՌ^~-(y@3WlPXʵxOt[yx;"IF 90QTG,& r"_DNFnd D).zϯjvgEMϺWRRp&ұ쿌]nɏ"8tA9uk`/sEYGn=0CH6T ZU9�FpS rX,1aJ ^ @rw&B~؄cr}hyFfcӾl)LM$/hr,6/\ i/]ӭB�+^8v[umӻu@ =hIn^0<:r<偐U7MYϸW�AN˻(Vl=Ǫn (]{ 5t9(X6J`I ס3��6� !qVW"UCZZU9 � "UCZP�(y@T).t�A}s>tz,HKI8}{;1~ JYs2̋!H$ꤎk I" ^l:rK&=s;qEmO<MqA @\b: j=-@2 Y&#!XsOT"x0VG߻t}+*`dz_do7 > ZGh)�)a?iJ8)4����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/keys/hash-algos/SHA384-private.gpg���������������������������������0000644�0000000�0000000�00000004716�10461020230�0023044�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������ZU �3ˊp<wv2~[1Q>802NqP':#ƻA8(n=`m|eHipx/GIP4.0?eyl! ZVB٪xR$ڛԽgyKþꮊ,ypn8@#+^ 宓+k VN>ڽФio.!݌Nw5? 51\&A[cSvژfqtn> #qF��� b˄3av=Q b SôCK3B?fpwHF_M)'W+wZ0y= 4^ e~6Jh^-v^w#0s�,#$bUr3y1�(m�WD:{$8|C 8Rowz=9-{7:%)G7Bͳ9VFttw¹qDPW?fEd[vz]p؇]!s_�i,<0TԒ"[arْsU+nx0Ёur^n(<B*:*}WXq]ӘJ[8HHb5V;bPXA?r٦}�J Ԟ�(|44|j `�#-xp?c]ޱ λ=mt~#n 1pl2u?SNghc2v4<5G7Ұo}Î`bżZ4 vir׈>/;UTF>kAs( }R9},R-KBk$ôoDʮ3WgAt-X*|TPYs7F1QKY{Q 27|tHd5LSHA384T �>!u ZN+}1S�YZU  g�  � 1S�YKhcl Z|1ME'9Y3{ =_n{#Xi!�0UAfZ#ZC9k,f^ a,;} ]2kN$ ˀ^dZ&=< c7V* +�0P 8bXaԠUq%cC^2 }+{az'ז6½:Ghkͬ1�W No%=s u#mw CԝZU �hd@周cHJ{Ԩ ~ۥ4k'ImdLiP*9tݴ>Г@/'}7-F;#ئdt5WK][p{X! J@I6{}"`aJ LjCaaYN#>6G7JYla&: Rw<LOJ{,eT~ieb`:xϲL|���=̬' 7;#B=N-$Mf7�g&GʽKj",)8akg?C8=.%gT2 `X$ą+]zT?˖v`½ïR.\ZT5EʈB26K^owRvmr>2KlWxrp~Sܭ:a8V'x]ܶ{@[Lռc2O Mmf *6&sQ''ɑ52R1�_Qd@+N5 iϚX`nG¦~8u'J>6qvQG^'*۷? V-+q.AącVԨ+;[genVؚ|F1u".ec5rҍ$6�2@-(G�J!XU}!VZh.%/`}zf aKxx>J M)_n{.JapTpuBZ3Mջ !p}(OX4YS5/yU1eӀKYm$|-H-u~QY~IA`0@MU< ]&r Ż$|$LAmID2o֦Ӈ,ՕVgT0(X&k)86 � !u ZN+}1S�YZU  � 1S�Y$#s@+`ܲi&{b$ +l$T[|=eKw"tѢӹ8YdW&.nV7hxyV@ƏڊL3'PDd{o QU{l h%Ҙ U(aꕫ+_X\p}PR(TۗE)YW˞Yp eSD /UOnd=KEdwƜɍuW O}<>*v )DYL_ҿ.��������������������������������������������������sequoia-openpgp-2.0.0/tests/data/keys/hash-algos/SHA384.gpg�����������������������������������������0000644�0000000�0000000�00000002270�10461020230�0021365�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������ ZU �3ˊp<wv2~[1Q>802NqP':#ƻA8(n=`m|eHipx/GIP4.0?eyl! ZVB٪xR$ڛԽgyKþꮊ,ypn8@#+^ 宓+k VN>ڽФio.!݌Nw5? 51\&A[cSvژfqtn> #qF��SHA384T �>!u ZN+}1S�YZU  g�  � 1S�YKhcl Z|1ME'9Y3{ =_n{#Xi!�0UAfZ#ZC9k,f^ a,;} ]2kN$ ˀ^dZ&=< c7V* +�0P 8bXaԠUq%cC^2 }+{az'ז6½:Ghkͬ1�W No%=s u#mw CԹ ZU �hd@周cHJ{Ԩ ~ۥ4k'ImdLiP*9tݴ>Г@/'}7-F;#ئdt5WK][p{X! J@I6{}"`aJ LjCaaYN#>6G7JYla&: Rw<LOJ{,eT~ieb`:xϲL|��6 � !u ZN+}1S�YZU  � 1S�Y$#s@+`ܲi&{b$ +l$T[|=eKw"tѢӹ8YdW&.nV7hxyV@ƏڊL3'PDd{o QU{l h%Ҙ U(aꕫ+_X\p}PR(TۗE)YW˞Yp eSD /UOnd=KEdwƜɍuW O}<>*v )DYL_ҿ.����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/keys/hash-algos/SHA512-private.gpg���������������������������������0000644�0000000�0000000�00000004716�10461020230�0023035�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������ZU_�LjTқ.U{g5C*?&6IHYq;jqhG\sCt[v x<נ'9,\D/SøNiO\5*8z/ק8[*&Q"Pu9?3ZGnOW4EV8,yTYB)N@{t6>J$Xà/5}6* |MtsMco kD0~߇%pL*|CQ>!V���'2EGdCBP��Xr!vJ6?Ꙭ#\GMZ |?=ːqF>Q0I gc7%`v~Ïcy �Xn>]V>wNQs=:=?UX`CQ Zs9Ls8ZӉ=WD)?vA X.x0@:y\ J[yP.a+u$ ypR&ؙ"LKeYo'/V0,>��+:P d?"w\.>qdmS$ٗQ6מAnhADJQ"y8 S;"v4Ükw**~%̹͡) 2{gUYZ׃s�gCqw'yEeI4JH/P.+p!9{br;R_p۔X%8F;PVJY:(/INcX �5T=CiCG9V&Kk5KmGp3|M1HpXnz8pȑ^G63=Xh'C/ McCgYiֽHUSs]Yx0 3RS(IdYk%V߃23:SHA512T �>!`K6 2IQƯ9zZU_ g�  � 9zL`�V=j ^[ٵmql@8DVlvٗJ~{@!m c,:p]Dy'%GTK3x!~%z0N!xDk3x"HZ:I3ȅmBR/ (եK>2Q񚥰MxS 'O<Chnde |o<DɌY!N)Rۊ͊0]sdŞ-ÂQpѴ= ZU_�kR-:bgqbN ~1U顏7q[QWOL7F[Vo+wFO=`#h"˝Z Ж^Ay )k ^Cs~[]nrT<Sozj�/�z4S<Kjެ!y||kS7v(P7+{)[1}�Q<a$E* yv۠b12f*E6nTv/nXMߦ6QF "<(���G�,WqtynǽzP*#;6+ǵTC;ssGG̽JEsuYvv= O:_ m@ߏ-q 3-0a:x,~Īc>XG U›ƹ %29l/I/3P+ʀеi7ERy=f"aIvkf�rOrd(I2X_(̡ߘGKa�uIY/Y\O_CqyEx*0ɿnX=d/qD+A,PQXmM&ܟuC+A[a6) i6Q0ft W" *d7-Rɼ?Y fÇLpӥnx� 5'Yླྀa~j-w`{!}DIfJ3N$1K.ijY,xg~ܷzY]Gm|C|pX�@G O!3;Xs0}?%:fV& +Q5P"Q2cSͧfJAqV:-g Gzt RfWlC1 P-8imm >96 � !`K6 2IQƯ9zZU_ � 9zf@bCv7'_GI"P&e F ow5s+ T %<s%_rÞ-ݲM9X=1lQ@QmƨcI<| ^!;06'Y1Aq cKWՄ* nCS{'A(zYk)܏$ɤ)*'2oGJQ~N)ki؀"Võ JĮ\Op_$*j☔ h/Cϸ֏��������������������������������������������������sequoia-openpgp-2.0.0/tests/data/keys/hash-algos/SHA512.gpg�����������������������������������������0000644�0000000�0000000�00000002270�10461020230�0021356�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������ ZU_�LjTқ.U{g5C*?&6IHYq;jqhG\sCt[v x<נ'9,\D/SøNiO\5*8z/ק8[*&Q"Pu9?3ZGnOW4EV8,yTYB)N@{t6>J$Xà/5}6* |MtsMco kD0~߇%pL*|CQ>!V��SHA512T �>!`K6 2IQƯ9zZU_ g�  � 9zL`�V=j ^[ٵmql@8DVlvٗJ~{@!m c,:p]Dy'%GTK3x!~%z0N!xDk3x"HZ:I3ȅmBR/ (եK>2Q񚥰MxS 'O<Chnde |o<DɌY!N)Rۊ͊0]sdŞ-ÂQpѴ=  ZU_�kR-:bgqbN ~1U顏7q[QWOL7F[Vo+wFO=`#h"˝Z Ж^Ay )k ^Cs~[]nrT<Sozj�/�z4S<Kjެ!y||kS7v(P7+{)[1}�Q<a$E* yv۠b12f*E6nTv/nXMߦ6QF "<(��6 � !`K6 2IQƯ9zZU_ � 9zf@bCv7'_GI"P&e F ow5s+ T %<s%_rÞ-ݲM9X=1lQ@QmƨcI<| ^!;06'Y1Aq cKWՄ* nCS{'A(zYk)܏$ɤ)*'2oGJQ~N)ki؀"Võ JĮ\Op_$*j☔ h/Cϸ֏����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/keys/issue-215-expiration-on-direct-key-sig.pgp��������������������0000644�0000000�0000000�00000001037�10461020230�0025543�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������-----BEGIN PGP PUBLIC KEY BLOCK----- xjMEXGFrtRYJKwYBBAHaRw8BAQdApdENuhSm9FSueg8jw6r1ihe2OEjhllHQz+jD q+fBR5zChwQfFgoAOQWCXGFrtQKbIwWJABJ1AAIeAwILCQIVChYhBBanP1v8VBgg 6QnrryP4HVnPaPcgCRAj+B1Zz2j3IAAAbbQBAPv2PH0RVPXwIOOoLvXQGLQDWA5p 9ZSZ/PUdEg48b2euAQDq2X1/L6kkJ1ESUjvhJ+w9iweMuMrJqNlmYPofhxgWC80d Sm9lIFNpeHBhY2sgPGpvZUBleGFtcGxlLm9yZz7CdQQTFgoAJwWCXGFrtRYhBBan P1v8VBgg6QnrryP4HVnPaPcgCRAj+B1Zz2j3IAAANdYA/0aJokj8mJnPx7mvXA0H AQIJY8xEaTyGdMqC6u8Bh9nkAP4lkhqEM8qcvEcAhWek6ecmqbOP/78EqO+xPmEm vQ3SDg== =vyeV -----END PGP PUBLIC KEY BLOCK----- �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/keys/ivanka-private.gpg��������������������������������������������0000644�0000000�0000000�00000004746�10461020230�0021420�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������ZR&"�KQƾh2[\j ɽ'R6N(ñr3ID (nE4Gk uwv. -iO6T 5>,^c }iDw-`"-n*iZbt1/]|ܝ4cMS3r mRihǠimNE{hI޺]b8=N΂iSi뗑GH+in!C]���K5 ڼ~4dpvfj(i/ࠋCst0j+"N 'PgS!!y.` IWԥu$߶\`}�٭xcX62F|+box pb=) =ڎg_gm7I"Ah"K^B_5s`NOܻ/޴Y_CgFwV}S]&U"C=COJ#Z}�\Ⱦ,aLK2,`:TS4 ĉ5$B*pbCFVnW?]6áv-=D {aR90"q#sq~myD]\q2=SW;�L J[^)~( "_)-JT`ʋ|0ԡ<0EJ~Z{07grz7Y {CVA@^<$(~hnԾzzh1Gz]H y?}x*S!Ճ:CsJ/@lrGQ9�!x_30Q" viT$?p܁.=\ #Qj:ֱFEIvanka Trump <ivanka@trump.com>T�>!(}YS ZR&" g�  �  l�z:IEH^4Ʊ;2p.B?|S(p}fGEghR?"oAf"xX�Ӏr]+CV7pg+&W!K,: |i3�#hGXUxE 8΃\ǝ,#7xOŦR_|?jYoI=ae3NCth_,H=_!C L7LΒR5ZGUm f>AnWlZR&"�ȰЎ~/phZ=^v5=u~c>;Ga4bSZmUA~2 Crw%K (M?ާ=S>;J(J{܉.TrjJg$`nr1$+hGy[MP (ɢ&{�~*]UvoŮd%1+ `GZHyn S#kta*R҇x4ʼ9 EN{,y'D$o3A~l\<R|] ���Y2Ӈ`EM$³Anh,;Tn6o}=?@ N}9_1-dncO'z.r&y@r` }>x@s)֒k0:D o5MMw0vQ87}q,%UsG/m�!҃Cط,Y Ѿ2xMU5awUGQ^,ɫ첈 x+%`AD{”PG\,�fus:x֌ x`!3Ζ2:]N_/Sb?HMݿ>ëŕ8:H (ل,4E3b[Z>jz{.̽M]C7z.6�z3]/YeEGGBW&۹bb"eڸ5ltܔxFS"},ѥ#{/p?yc W"wF\֛s"?N+@q#F`WG-A�{8%)+)B|楖)5ܾO֞r2EgW,~QNkQKomX; \U}ԚGwO1>Ě%+6E'IKɉ6� !(}YS ZR&" �  Q^;G;+@׹R-y.hfbH"4RCԚ |df6+{#=nDQZxjpZxK͡N!2b@B(diX<)]{kEՃx'g ~mb! / i9KG!n9Hq堼.|ht bT]25|;1:9Im4 5}\I0fƨlF"- hIŪ7Mc'��������������������������sequoia-openpgp-2.0.0/tests/data/keys/john-v3-secret.pgp��������������������������������������������0000644�0000000�0000000�00000001737�10461020230�0021254�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������-----BEGIN PGP PRIVATE KEY BLOCK----- lQHYA2JqgDIAAAEEAOYdcIKFQ5ZWBx0D5DKwMMNFcIhFyqmfDJ0v23ehMxOkXN/o HO/43+dq6ZqQn0gNw53Tp9no+EmcCYNrZuN0C4Zu8XHSyY6UB+CqzNkz/CwmV10E dRDipcG1O6scJyy2MWpuOG67til+o+wOLgEkkVkSW8Bl2oqtzVVP4swtKLRZAAUR AAP+JBiyRqt+DYr8GKE85NBX9nlS6DMaxUYgGKgibR5OSVsJjIjNUtG0sNmODjTN sPMZqlNln6wS3l7APMWNoStNGc9JG9Puz3eR2W69lPDzhuxuxrHIUBO+3UlEQB/p N3NPhnwCjh3OWHSMM6rzsX5ExUv0Z4FypnzvMG1x6GRJDVECAO6PyY8NDHsktMVN HAdgC61iIOz+GbLhNGeikuB+DQpSoyckAF0N5reBxRbyjzNZQ7aVvWpxigUp5OdK HMK7YcwTAgD275bcqhd+oWHDhyesi6RVswlqGfix48qahf9wOmDkc0nzp8evy/4V 4Qu5zUJGVzi4aEIbFaAnc5lMD9/ydTNjAf485vh4MDFRd3tPvx9mPrHQgaArCBX8 9oImPDk0oaKixwSIFzXeg1qZQeLiwv26Fs8gawWsLVZpR4+zZc1nhZlGnrQpSm9o biBRLiBTbWl0aCA8MTIzNDUuNjc4OUBjb21wdXNlcnZlLmNvbT6JAJUDBRBiaoAy VU/izC0otFkBAYIsBACykJ7s82vqCIKewgLpFuqoVGjlfwn9z+G1oa7vr/GxA/mF Dr4E8rZ1ytUFMUCfzy52FfOtDQUVUeOpAraWQ4JfjXkHuDuZcW2VRh2ctT/hVHG7 1GgOWUBH3EeXASSnFjvUVE39vEjEsidaMxZtMj5jmtieTMB8pSG1QJXPXGoNyQ== =p7Lr -----END PGP PRIVATE KEY BLOCK----- ���������������������������������sequoia-openpgp-2.0.0/tests/data/keys/john-v3.pgp���������������������������������������������������0000644�0000000�0000000�00000001033�10461020230�0017756�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������-----BEGIN PGP PUBLIC KEY BLOCK----- mQCNA2JqgDIAAAEEAOYdcIKFQ5ZWBx0D5DKwMMNFcIhFyqmfDJ0v23ehMxOkXN/o HO/43+dq6ZqQn0gNw53Tp9no+EmcCYNrZuN0C4Zu8XHSyY6UB+CqzNkz/CwmV10E dRDipcG1O6scJyy2MWpuOG67til+o+wOLgEkkVkSW8Bl2oqtzVVP4swtKLRZAAUR tClKb2huIFEuIFNtaXRoIDwxMjM0NS42Nzg5QGNvbXB1c2VydmUuY29tPokAlQMF EGJqgDJVT+LMLSi0WQEBgiwEALKQnuzza+oIgp7CAukW6qhUaOV/Cf3P4bWhru+v 8bED+YUOvgTytnXK1QUxQJ/PLnYV860NBRVR46kCtpZDgl+NeQe4O5lxbZVGHZy1 P+FUcbvUaA5ZQEfcR5cBJKcWO9RUTf28SMSyJ1ozFm0yPmOa2J5MwHylIbVAlc9c ag3J =GebS -----END PGP PUBLIC KEY BLOCK----- �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/keys/leading-zeros-private.pgp�������������������������������������0000644�0000000�0000000�00000001535�10461020230�0022714�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������-----BEGIN PGP PRIVATE KEY BLOCK----- lIYEZHeoxhYJKwYBBAHaRw8BAQdA2jbzJ3Ol+88vmu6SM52bEGvxfft/j/+8y+89 r9aUJXb+BwMCyL9vofICblT/6SFwgNGSk7c1vEA92d4yWFC08QTQhVZ6I100v4Po ml0uQ26F/iQl2c2nYWdlY0qizQJDxVBCmINMIuNMqNZH11oDwFTmtLQScGFzc3dk QGV4YW1wbGUuY29tiJkEExYKAEEWIQRVYU+0V0KlZhEucuCg9CG4lHDkpwUCZHeo xgIbAwUJA8JnAAULCQgHAgIiAgYVCgkICwIEFgIDAQIeBwIXgAAKCRCg9CG4lHDk pxwyAP9lDs654VolDwhO3HDBfLm8vKd5UxtB3B5cO7RZGG4OZQD/RzsK7gY4ZLk1 7FRmt2tyoGkDZ1VMqiw+GwuQgf/TbQSciwRkd6jGEgorBgEEAZdVAQUBAQdAin8I t5hyfn1f19Cx4xTt9id46Ym4ZCy7x1vUnAuoxgMDAQgH/gcDAsRux8aWmWYI/1gL mAtVu4lyKQLWlL3eeYi0uMg202vDPLOrof8xR6imGoEw6sSqeuv9i8EsrXJksIOP cyWhUQCHx3qr8ZWXimEsJmaqDsiIeAQYFgoAIBYhBFVhT7RXQqVmES5y4KD0IbiU cOSnBQJkd6jGAhsMAAoJEKD0IbiUcOSnZwQBAKsYHNXJOQI2suV5QUDCNuVcY4Hc KceLlPfzxDx/MmCwAQC0eoFExqefCSSymcidjyHDy23QR+LulFXgxpFnvXGvCQ== =dayD -----END PGP PRIVATE KEY BLOCK----- �������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/keys/lutz.gpg������������������������������������������������������0000644�0000000�0000000�00000026712�10461020230�0017472�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������3-'��gKQ.N֓-�T&uw()`ӿ33@lS|FQb֒es}^USws oa3󠻉T0fjmI uP]4nrTX~c<(v#O]B ԑ^N, �#Lutz Donnerhacke <lutz@iks-jena.de>?3C{ɗEljy� _1{N8W޿;�v.acJx]F�5c� e9趓<A�-zzׂߋS@!tk�'fHʀıᲳlrF�5� k9eWҽ�@B7W>CD�?GAlaqF�5� <Q!� D'W'�HCZF#q% $&dF�5� l5x�` u]qzXiY�ڄ4|*a~[eVzF�6k� VBÆq֥�M{z�#GME},?ɿeSF�7jTt� `nr�vu xG96j�!2rӄ/IH/`=F�7v}k� -^HYzF�+ L{'o@]~b%V�z,=AvR=4/yZF�7L� ]O|#ؕ�>cCx[8,qTFp`Z~6T�8h/䤳fLF�;:� 7ig$�&F 7'N>zQ?�@O'Gya{#~|FF�<=� [rKoq�F6[%UC�W,1>-QjfiNGeF�>-)� E.rtV%� 3]˞j1LԛvS�sR֏V%mNw%F�>� hR%6b+@�P,v&&�%.u\?CGFblnF�?( � pܘ b�6WT% b:$�cdJԵQ5F�D@c� Ež�y5<ߊH΀h�P]k :_*#%F�?� *͓uH�%V7!$N5X14�?h@`)kߍN-E&0[ȈF�=8� Wd|�Iل: ?YUd7)n�wYj@WG? <@F�>_d� 6>d: �1zb<xPeq9�7hy+~m֓"\\F�?D� Vm6, w^�|zgzՌ,[�gx݂%_F�?X� ;*A�p;ukϞ� )F4kUc:F�?1R� ʏ ;YW�df)-sx<"�}Օk(їmφ)Z%F�@9� x ?ӋiH�v s85k|68�OHͩ˾6ILF�AAb� bˆ�,1"kդn�?b҄_Ţ_kLF�CG[� JoIb�5ΏWkW*ODn6P�AǷ~Sƌ˜g8Fnu4HDtT591LM'a^^W$JoE #e<JpuӨ5$;.bٌ͙ 'ښ\Vb,Bԃ7;K>AjZnuŤjFV , 1B$ecӉ&{ 멤z*+~?{ȞJBǂ,%Rv?N%!7I3uȒ�)jBAҸ`ˆ7!O~Ekd�EO¨xݯJiǻi?nBAmOBsm)Sh [-$V{[.ÇfMY4V![ZgH*l�l~4XZjX .Hs*ӭ!nfzagazV=hGnos WLlp?ܠ!jpr0̧(4:O)`(;f|o{\Y>k&T3- l`Ms�\Ӓ;5˵JkC4_ckK@5L/gTn;&f(Jքa�!Ed Yl _;c& (_S3*$ 3Ul&}?һ3ľE4ץ EAQ"9@6t&U4M' KI 9Mm 3 =O)B_1ޏyk =5h`ۀf` B_0^a")Q~1N)1+q/|x$3MX>n }:�t4j oe=cƔX%߶6}=%@0?3!YCiRMGAQoi 5zpe@ØEY"vH@40L몲Sz![gx,Oy?WA8H ׵ٱSsDuk(W0WE5c394ּxs_m?P[qfU%▿MYƅrQitڀ6^E4.ŶcZ|B$DN>zW%RwXAI$uOfOQTM+AſuA‹QMmX'O!}.J3e#ćY�=YP*2rC4qkV(7�dT?Bb83WP*y܃0u9qe6?򞺜*""BʣiKZ(vvq?qtw<?x%+i׈4a 82#0tћ>mt Vc4+3 L>-?=992݂a20E@[ʉn'*'xHXA^HDKA"W(/;Մb96~=҈40,&}y I �xVke` Eʕ3dR fm ݌+ߊ7N~v<sv,!,<ec%Nbɢ!ĩl!vrtХ?KjZ~Z\?VJD+4(%3]IpPt[e<r%t*ۥ&.Kp[Bxұ NiI =ԙcy2}tKe3Cwj,6cK ~`A^=a횃[>eÍ3j^4f! [EUmaFD(? k^FU&փn' (zFj[\*NPc.Z;yc3/\s0R!-4+mA]2J.TR)e04p&v8*KREQ� G;;faHs+`!%VmiG3x}*,=v*߲).J|/ e=4uiw$i1b JkLc(WמD [º,u9nw,?Fû3]Wvz;"Iկٌ'FG?͸y|[{g#S%p,_}.T0| M6p3uu<F]E|K|cf|7,«ez&⊒B8ͬWq9öA34]U=i"" ڍPvYcעW0x_Oa?>ܰ6Y g:YK<~J 6I@F ">Ȋ!8S^BDb]Y1dz_X+ygcc+D.S_70rf;{46Bv@Pn/¶w,hPD6n_3L$hqО6yMľX(kN=9j$jr8>93N: =wsyP f!+ gSI 6xa_4 Dc-criQi:1x^{}ѝ~;)^O6:<qه=INJ }T=UC%q,vhdU٦ueLW|U^3=>PbˏޝK$7(ߖ1';~'.UYFp vԷ.ݛ,E;O"7=ZIUd�oEt밒V,Mf_ɕ=C .(#jWC'yN1\|RQBX3ec u5TL_Z_=wġBA>j._׍I7{5HHPB { 6 #/:'RZ&HΩq Lx$66!WҸ8Aʄ0o~t:ER| H·^3cDfA7/Sv̠3EzHHJJ Camو7Ɇz:j܅k�UG 5m<uc7T(U̜FZ!g52?N{_\L=pD7C0ti"P>"�RjLN? "p7돲(QKT4L*Àk3hswqf!Z)f=>φISGP A.{Q wwce[rIdvp#vvY):9 s:#=?l=/&Ytq(7XT5q]ef,�H*K[IX_V U|KԢFh*FURx5'-KfTPťڟ|P[t݈4.vE3җE2�NҼXU}i eg_mvR{uY${;XYF0Z,XRn5|3|v޵<V|_iBx2PnY5+HEzbq D솚[{!ώ:_4׾q_O <;gjyI^s[1 { ,vp('o$u}/WXٛ'Kݿ%QJ(%m!*K%zD9ep5Ժ}6XeՐ $d6L+5_>")ާ#ƣrV∉�r *Pjyn&PKԘ<Bop,cg]uq=Qտ+x@ ls8Ӎ].?I,kҽWN #6?76k;&RC�ZRsbY\β |!6]#ەlRѽ6cp=`f*fq%ZIn4?9igAVALj߭sr6i=,xH6G.ÖDBGQC{n*;�//Z^EUf~\H:_)rN2nFmǍX[Xi׵tPnkn>Q~1 0PɑCnN�lПR˶W6Fݢa^h|=o~1]zp T H>87I8v6ԊށFLuLɞqwC9m ԉa7gOQ`e`xm,$ Vfn}e7}P@ 0"ܣ+5`WBUp o5dT:.i/K,jط͊¯nwl</[DO.h},&f=p'VisRhh݈rQ7kU-{%.L*D|Ӌ8z_dϺM{swR  Y j ʠ, պ/28kD:IlVkwoZ~|:Muibe5!y1]3-'^N, Hd ۷w2/e.vUS C\q"۴xjæ¹OX[ruq#T?Df` R9K BSzRq\&_B)tilP׊3BeP$LtDfdpN38:4>%�Y28O%Т7maNp2Q�Paa`Z kg6@x F;袇}rBҷ]CASЋKxzw=%;aS{[Z*c)ekaljĺF.e2bn$MɈ3MLX -ZA#ݒ]y! u,\OߗW; Hb:vF쟍ҭ3v G·e^,fZ A`q�NGz3;1C3ʥ`h%'OѡƞqrW~7Q�XBS!.[9D3 VqУ#=v]w8=7nW&r!hbD![;a8crΕMy)p!E`@"#L pi&KsS}M^2m[kG?\ 92&OF"Ix:u X~*F9YNO- ]"HvԸrG+f~=窄b-s6> ikV2mSP2Pf `6k(0|96;f6:lT :(=(!7:yH4;u+!"Z9 ymc39<?մ,y߭!Ͼ2.zl9' uy Ӳ5{%1~f<E>֚*"ݝtCKfMƘ4xFA4N/ ҿooL-&Tӱ&@RGFA(+#\ wnȻ8d$`2�"cT< `u腑�,3 3-( p99]j �,Fv1@~K^Yh]͚U'@<Wݰ)Z_,x3`k}Q\jRb`LY 2ڑ`C_!xe\6Kz ;?3҄Ϲn[Z,FKA|8Wf.Kx_~z!dH>Ы.jKIj'׶-.*)ZS6 MsSqF1ΣRhԉ366| /Pq8КgqFV77 Y}[VF~[q'[tWž1G񠋝~ꗍi!Rjļ+dZսxi\=#ݛ'hĸ靋*u,]QꪠvFjH%S>NfPEl\掊ѵ'Ri Zzs ۬{u}f#$C|/Z!7pT[J9GM4v,12('!'d#mgq_oeLu|)x0*WkrDgrŷ…Ț40I# 1Gt 85:NwYho$JQ{ Ə4DelMY!bؽ >f7nԆNwB`֭{9lt@yUI;ok${1ZƤÛhi|#^ɩܪ>* D>{RB5[xӲW ~2W|[RSR1Ʉ"<:ouMxy=ܶ^Y TIm[%o&45/lY�B@q4Bdv%=s6{4=Jo/|F1`ꛁTwX^dsvDA:\;G=ΪP4}ެ{nl<5hfɱ^ՀԎ8r(η>0qg͏Ze^߃P$#5@oGU51 Sh5.w#Sʰ)>iC|ځU 7soeߣPU }Wg݌Bo$2f2QU6yϫ bz 5j5�&\l:Q�<\|h[EXwy#9ԖދN'4/\XnM?c\Z-&/TVKs?Cް&+nT{e B345Lꬳ$C!/ZcХHWvyԉP>&,xCl?ln^�1g :w�bR ]NŬHf.N-x,1 ær 'O^HHJn>DAw& ! uRqo6S1f+ou(9iyyaMb +9zgEv}5_)ܐX0t@| Dt6Pk(cuO ) eHdW"Qlr]A'Z(LJί jǿ"k7 9 �F(uUIzᤡ1̥#08џ(\S  ;l;l"V3%2Rsl@@#GN&K8M7&D>bljܒO*pCJb!O3CPIw~5 S8S'BN)rxTRcYpB ҴLԉ4j=7<2#:]:ܔݵ=s4PEPe7S+<FA<<!9pxF$-Ϣgb5~{U ]-+ڊTinA5s4UԱQKXK�?m|c .7i,qB=Ars^f=JpdHሙ ]-1 B%Rp O`"/eʫ:�+/%X02=lcSb4P5p kVD'D~4@ rNEz\$=ԣkS]>8''9_KAF"T+Kt%cu2z8$|f{ny([ڄ@cV P[";˒iKSRe4iV2lk'{>!^O?},<U#"ֲ|8noVnZ];/<K7w|O7?\z2"p62 )b'4IRnI=!H/zd&9]98 W=_ I <q}Kr'4vZ0j Q=۽:远?Ff?&~7da ѫ~N_�K-φ}5gThJĵy$XpgWљɪږkS.G鴤HM#""e# 9I96),ZwTAnRD`x;!p{TZک)UP<r"]g񞓺#׆-7j^)(+ɫ=R d%z7Z8i%Y\|jSl&VHUd4HWD /!uz?0d k40\`Qe/Z2:>N=t$FT oZsZ7H'k8@!�̿CmX.8% ! Z 1խ+!5zm]Ec.2`_2Uf8$Jb*%ͤ; 遢HkXF>)7'P$'N-^ :*Vxpfz(ϕ3!X}!ހ -~—[vD%;!3;aCp>{o]h&Sk#HJCML6Ig@wJgT,uR`ؕ,@֏pHO{e~CglӲd-ͪ^satx-7FĻܷd#BbmEњp9Gc)Րl59_s<܌͉�N2 � Ԇ<)m\G !sxؠL- Nb>3VbxQn3]@`ƽAm?5Xڛs'RpL^4/b;Œu/T([<hZch%X 56FrpTvYriA ;i#M-Taֵhƌe.^\(~sZʪQُK#ejakv.zL/4:N7&ۢrՒU+I=\Jc¸ɟ*nQJhG1<DADq e(f ٩A? - USz;9@p$ g!"E\jpvY1Ha������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/keys/neal-sigs-out-of-order.pgp������������������������������������0000644�0000000�0000000�00000020372�10461020230�0022703�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������U#R;U1RLo\G^zO`DR#2ܰ5q1~l߈J^jgL%<yxQz,N܉ў[~W ݳ.T'D6NOmV6|j&umr~Nu8>XC]?}]$ *%|ic8x1"R2'bעo{i(1< T dvWBʰűٺzv/'hrd@w1z<+͙aыfb{f?A'[}*P.6. ~(|M褮 =&_{]gAS#wq; p]B3U�x!g6}(!ScOҽ~&URgEXґ!,-=֑jiA.f= B+Rڔ*- ?DG��$Neal H. Walfield <neal@walfield.org>( �>    !wq=ڛb2Cc�RY6{ L� 2Cc�Rك+M4<ZLh 4<Anqt ¬`#>ޒ=.#Шw3|շA 4"o)WlYn! n]`tq؀?KjTSKc:X8L':@(�a'<! kOCKZz[o4)Sg[Նvq@�PVmRbJleWG9qdET5 Nl<ל@)s{~][4RɒҘ d~*IL#`=1/?lȴ'7AQ 0Haݒv&eXژ6Jx;t/i.OXX "-p7ly0)&-a>@j Q\ 1�^оeS<t9Y!Qi o蕩 p?ueom^{-vM&HDL~O!Neal H. Walfield <neal@gnupg.org> 0 � !wq=ڛb2Cc�RY5 � 2Cc�R<qmE*\bsp!qLzRb(i/4!j"b8E,7X�@/kme qXy2kd/A[eI˪ݕ -:g gs>@:z#pʀmO^ӀރhrYH, F4@uL5R_l(B#PĊuVr2.Z |Ed)K<UCeв?L� &mZ?K<Dh)xuC!ܥeC|Z9H`p/ri�zC(/wTSbH}}L=: mɘ@o:P>3Z\15W{ cHnzZ_UyŲß_U8v矜nByA #3BB:b#շ73+ ==&J{r_% �;    !wq=ڛb2Cc�RY6{ L� 2Cc�Rٷ-pȿd4\<ɰe #~5g,jO,a-uzC<[٧Q;>G5aG)x1*]T{S&k4-zJ=r.UVt*aof-/XGOiGֆP}XkmTW̕1rKqHww^uyIJ t]wO\_*;K:{eۖ$z ;Ǜۀ }AzI$sc尽&ʅ xK_ˆFruQV^Oy'O0;챐z҆%2/R@cĻK 8T+3 $eyE`+Z49M.7^e>q< <%J8&ߕkS}SwMA[]ӌ#Neal H. Walfield <neal@g10code.com>&Neal H. Walfield <neal@pep.foundation>% �;    !wq=ڛb2Cc�RY6| L� 2Cc�RH} (&*(.txc:ྪ!ѴL]<. Aa�j|ijwEyꓥ/|,mcw*a~v=xw:g ]judHnuX�1tlǩM06z(l)tDgϱd< A΁B'wqWy}\ŅgY5E;*=RIFp=.)L�yz4%&fO1nxFu;{s0:܌[zǪHO.*%8.*+JtqAu]et#뼊9 4, +5[rt˙O9|L.j5!BC<:feb1ܬ~/ٝǏzia-ͻB'| p+9=,m,LRd'Neal H. Walfield <neal@pep-project.org>% �;    !wq=ڛb2Cc�RY6{ L� 2Cc�R$4 P뀁W M;8LtW-*ҹ,Vҿs0+Q3ԗLsbя߲ nީ[w-~pJZ\) #(y7t=wKKk Z}9AB#tRBD!8{=w'U58z![ī�Xop~<@?9/6K<Ottlq <wHQwO8tbz6rnzAl7lrDfY 4pNKt*P/9E 0X[F%ED/98mw cՉGC>RJO$?֪c)I4mQ{Ta%,gVL_fZK< mXcNYjegVKߊMu7<vOՅle|;;L&M jзDOFRwh5xnL5}:N'Neal H. Walfield <neal@sequoia-pgp.org>% �;!wq=ڛb2Cc�RZ<  L    � 2Cc�RyP-vΦ&ʾHEiFdn6mZE#H"�S0R¤O`?>`ՄM-nG<KhEvϬdN8GLQ@-.`\NbfY7�?yz:﬑%g G#}Lg2Zze\b9l<y̒+!_BƠk:=M;F̐nsÒCt%CﶺPOP͛"ɫ�J;]cB;mۜ$ɶËj(QKSc[QQy6솏6-:QiYjpc„ˎfwfuQ#-D22J(Α>S#=! Gi xP(,6zU 8W,poj4t R�_1k R+GebW}Y^;sfWn\5NvL>Fs0D U#�5Q$_|p|CxTsls_yAHxeYZvS2j[ױØ;?|4%߯š p-xc a!Л&d #$݀['yX2}H& ۨ9=[@S I~GP=LqW?2$c?rrw(eQ+9]:ߤ0 \.NBMenO]U�� �U# g�) 2Cc�R]  �U#� r#fx%(yΆ>H ܖ]Wk>-N$`$48b觶FRL[Pэ 4ڀ9R W6 փ玮d)~6mR#2C#?5dȏˤ>WX|ьk))`ZB1w$1Q#3Qz6M} dQ<\^U&v8c&jPb{10|iX #I'TJ+ɖx1 2٤'ELOOH ߅5cF€dQ|G/5E01uƐl="HS\n|zU f0 Q:# lP$PŦ"JE&Mb:ʏp ,_'N'Pkڛ3<"54m;XQlz3 f묟CQɻ[7qX%ΝFغfJ1Zԣ|Ef4bI29֠^)Ӵ oY\/9+M~%贐\%�]] h5NDu=sǪ ⅤzY<A#_s}qG" C&˥>g>' ]P i   4y cauQfU8ڐ^뎼 rLU} X%Hm"E^$8x0bq#-=Fʼn �X#  )]  �U#� r#fx%(yΆ>H ܖ]Wk>-N$`$48b觶FRL[Pэ 4ڀ9R W6 փ玮d)~6mR#2C#?5dȏˤ>WX|ьk))`ZB1w$1Q#3Qz6M} dQ<\^U&v8c&jPb{10|iX #I'TJ+ɖx1 2٤ 2Cc�R0?友rɳ~PFbJ=vj/nI {<\+}9ٚ/H0qJԚpd FE᳐k/ЦS10[[ܾ؉z/ǀvԧwK((V.lwdtNr%oC/mv`~3 HV+Vz1mĀcW iPϕ=E8ZzNdlkh<O$AE!&".tȻ'*|?a5/6@vXzpP ·ذ(Vx@lc9Swp%N7z3�DU3?cr5 btYq�DсSq/x7)~Vhxip D (j<V/Tj98Ne?P$Օ' U#�RGWJ$y&IMڌP=�,szz1IP}1>(TOѻner/&3[ScLT] }4 (3FC'8�}9}B06>)9 2"^2DWby8C:MSГdjlߏ3챆)>> SvtرuVТB #:!o8|/'CD�-`(f-3�� �U#  g�� 2Cc�Rم]qbON@6>Hܐ<bhȘ H/tDwk 0?T#uM ~  P; SUED1vV_'ԀBoO䪢=A5ǁ7XNJIܗ$;\�|-2<U.qL\1WiP: 0FnQfLM|XyI0hHG)RHӸ ͺEJNN&+-+^IŊϴ�(+@m| l zP3/'$7 :2Dm-koׁl, *rF{t#w]U):7`9rOoIɹ~yqQ- =x@`jnVxtG7,jG|Ă0Q6{{eh7™,v>ŌE �U#  g�� 2Cc�Rd$#7ђ̠MLJhHT"m|k'rӗu4d̊eA"Zړ0R"*oj<L K9 _dXXf%L(RTx{nFB)h%Hh<c8JHYE͔p)Ek/XsNNP"PWAeqR7oǟQ| o9""BwSr!d#_V ,+~ zUh ^.sNT%(<<7 UƥH0k=S2ze;&~>/'D(/,H5nvm8:8wB�F(Hi.5nT-tዐl9[! I_18ukM<Xe۴6eB^yaoMyEfX8olOgV>trr$!4Г+}ow � X# � 2Cc�RrQy-c@ygճ1CWsk,L [4ykX/GpK]hϤ?uS|W3HNS~|+!Qp~)gC^Ji"jJNopvLrQ9 '�~*ud"$S瓵4DE+zd=&F~=Osb<8'}w &:˦u#0@"=XT %Rx\7ll%{wLR  K(&W=*Es@yALLDV*mŁz/%;,Ui6uuu݋u_7gE&rqh$,9?)6J )%2xH1Q8DcJEJ$0n[|G²L#㦫tYI)kMX![v=;wtNkQ U#�֭�ݣ#x [s`T@IhZ>-Wp>- OkеH)AZ6G:?D P/?%+&e_Hon1~-W=Xf.y:6d`pm1l+* &N_(ob2 v }.\To(M U::k;L<w^WMM-*96ogOx{.W ?Oy\G[ uЌ1Fp<�� � X# #� 2Cc�RS-`KsAN.) $Xt@F18iVȘY%䠠y"^ApJԛW(8)g<ܜg#$1�]'_e5ef;}GI~?fLE K6*I~XiIb\t؀@ffkjGL>47�_;B5dwEц0N@-ɇQ1/t~i`]١lهkO758.,{2wdӱU"TowI| -H:G"=qbk,)td.b5 ^k @Sr}*ISЦ$N%T 0QZ<eL)s3Kg^fF@ɦ�AҨOS7bP;-Z}y6͢_)Dg]����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/keys/neal.pgp������������������������������������������������������0000644�0000000�0000000�00000062215�10461020230�0017422�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������U#R;U1RLo\G^zO`DR#2ܰ5q1~l߈J^jgL%<yxQz,N܉ў[~W ݳ.T'D6NOmV6|j&umr~Nu8>XC]?}]$ *%|ic8x1"R2'bעo{i(1< T dvWBʰűٺzv/'hrd@w1z<+͙aыfb{f?A'[}*P.6. ~(|M褮 =&_{]gAS#wq; p]B3U�x!g6}(!ScOҽ~&URgEXґ!,-=֑jiA.f= B+Rڔ*- ?DG��$Neal H. Walfield <neal@walfield.org> �' �    U#� 2Cc�RAbIzr\ (z)1幸߬ |;S)~dUs8xgAW" ,e@U!K0_홴Bóv&9tA0RŚ 6IGgg>-XN;I+ijF` f}MR}f99M0 MyɌu )@xQƗ.l/BP su S鎼Y<r [1k@m44ↆ9:.9 gFw]+6B#>$th@N-` gBrcw.DKI~a}71E y+ rR\)@8? Cbl7<3K_ͭHZsD^2qzhCX1 i D x 'kQ7􃇍D F�U2� ]8\x"N�OC6J@�ֈߒ܄T8BF �U#å� ; Ƌͽ�Cyz==;R(�d؅xճג�U2� <C*t'j﯆Sf!-.#['y`#:cИ;<?m."g@avQziGoSa)8&M**xVE[A$N ~<quFj�O.R# H=A $$! :,#Y\Q:KDd ~Y&*kC@+M ;"ͰBZT.8e}ȳ;4;qTS}ܯ  �X� =ݘE }mjȒ2|8sf;ufʳ\Wby/JaR&Z}hRq){]"Gyv^bDzg$u2(hq�]s)1YtxJو:6ŭfؿexpUicHd+=1ffsp90,'r~[ԅk-ͅ¨Ry@D縅Ϩ*s` sm2# V1g{ 4y@|v_O ֱN칐_'sWtш I̸ %{I!ekؗps4 ^ p+()w} í`ٞ37?'잮I+pCY܃ފVXC�V+S� UW3�~8<֦ROK]B"~7,(( 8<Z Tؘ<R̉agK)JK֯y3/xV.Jm[TJm}`݅7`2j7~2JV\Q/>n >ڵڭ21)nuH]82OK .nγEo YkpX&߹HCO?`x�vn*1o{gсĭf2RP;#@S sX'K~`u2O :[ r6n)fq9~vf&4C+J =6+' ތ[],^ c1w  5_V<޾/HIl. |�p83摔$'EuJPo9{7O(H.JY9r6z�Q|r>+qfm 媔CbUzPS|iMh#14=;{Bw�WH� 7V[EI%Q0)X:uM&h(5ڂJ]OΫF2;fu9ލc9hY7:@b:֌8ۡ>>\x(N, 3:*C'^FpOjXO~){`׺rbP^+m[j>w\X%PqۂPNJ2d򆪑?1^qf~$r5tiЖb$Z T`a M�,D[7c @2^d4{Y0e&'F% g3duGw4xDMX>!Xo ѫ!hZ#SBqm $xYYM3x:}_afJՠkKLD,WqC[. i|ѪqCe)R1Y?hPn\LC괾:Dhw_^,O$6"vJͼ]zKc}Z|æd`ԋVs ~ʼn �U5@8� ?㘂b�ŘP:  Nx'dk%lT~k'^ev8m`0slc 9ߢ+Q`(}!"&'G%\9�\L]8_,|;Uę~tEᝍLY FfW\<B;@u;Vʔ $R֍cd!G6mP`QB >f-q7T <{ /(4ϥωʱg|Qr/a$(þ(w1FiNuEҮ@ c)<FP~̒$=޶]ɮ,U5E /VL9} cg¬=wɃ Sـj`:OxGpr ؒ 4Rt)0\�OE'@C`F!a&PSDάA^fh;V>;OJ/xs| (l= �Vk� 1@|E}�fVh?}#ǭ|2 *ʮ[G+1PX|us[",(iXV>Bv[T[~6";fHs3̅wYh߰/dX؀7&ȳױ)Ln>`>Wu$^dr¢785s_A`5,fl>ÛDXp ́ Ծ+yNmNk`ɮN:?0(DbVGy ^B X!vRBL;H �g={= U r} %Ӏ eKُfzP:q"6/%LEeZCXMd(!W5lDdU\gѮl~&25k֊[0;Ŵh~"\ \_wm_pa.qk{ HuΕSx/O-%_ V4!F �W2� 9'bDApZe hXl&i#e@RF7J3Ϋ?B ܓ.n1@ܻa<@WrIH$x{_?ď"W+3k[L80u1M̖=:\+x[SnUӳ|IPu`gyes*"Jq *W]•\,M9!/b5WO1CZn8:.m 9o�#/S?K1;}AI}[ OC|{6_!1 `3/SYĄ`Hx2-{Q}E*A-v!u9rWlVԠok=LG읜¼11mm~ަf'l&j|gg*kj4+i8,W.>c$.>=6w,Z;q񏧸!2=|?0L8IFȍ�WՏ�  `k0cB3ubusF.'8}_3&)d_Sl:p~O[c?s+1U)ycXcі\^tcıNӢ4xFY&Uje*dd-fQ//W1ҟ"H=@%~ڛݐ>m泗K3=cM0o^{TDvҀB$,2f# Mjxu&Nx5BOfiDymJ07"v LF[z/z6`wlnjMY=)zV]F^uXKht%DsTB"56;Uב_lQ=ƮH& $VDrApJ Btk_g1J#2h >%OXHax1BuLgI8Bt VX  \R{PM^Ƚ*Ƒc�W�  }v C O�g#k452 -zLãq-֏9힉xr=8 %nI5S~Cs%ULKl Hb�XOudc0kuA^BƙdmY ?hۍ]Hd %<*3Gv%1T~3*0l|܂8꣆?!H. g:DٍI>ݾU �`5HjΙv?A=h+Mp?\@E6r,r߭ԧT tK^�r[`8/4':7~8Y2h֖ĥ9E8 8h['O3J@eh7p˕FZ7֍k ׇBV^7a]CϘ`_ըjBkft. ( H#7^7gcf{!:AYiw`<x5(/[>*dIXxRHO+B޵8Ԙ*POdΤc爮sQ,iۿ٘@ �$U# �    � 2Cc�Rٽ2)qE-ըJ&EcUG;}ª"릂  6ldM.mN_c73ML[�ɱ�}fFT / I0sOb.J[P`*½ةsԏ"0Jtv%P!iP5;*q3]{?dȑ)ZDepYYDNEF^(r x7"�{gNHm6MtOIޘm7/aӄSu=j̪^׋Gg[T:aģx3rfb8oMS~xz ړhu3-�^fMNUBE0'`}�DV('](17ދP `"]=)vygS fh4ܼx9Z?) |>x=j>wDG %MDy( �>    !wq=ڛb2Cc�RY6{ L� 2Cc�Rك+M4<ZLh 4<Anqt ¬`#>ޒ=.#Шw3|շA 4"o)WlYn! n]`tq؀?KjTSKc:X8L':@(�a'<! kOCKZz[o4)Sg[Նvq@�PVmRbJleWG9qdET5 Nl<ל@)s{~][4RɒҘ d~*IL#`=1/?lȴ'7AQ 0Haݒv&eXژ6Jx;t/i.OXX "-p7ly0)&-a>@j Q\ 1�^оeS<t9Y!Qi o蕩 p?ueom^{-vM&HDL~O3 �!뻉!vΟ PY6�  P&�ӤS4Zxs,*iM<qŸubC)F< #xʹ`^}].mqw>Nx(E* IQ򈝧-޲Ztr_Tu ܷEӪ\M}Y{'¦:'`Rۃ`_i05uVI#EXrϿ kʔ?`fq ق�~ۙ8]=]ɐҫO|o~\(!Neal H. Walfield <neal@gnupg.org> �$U#o �    � 2Cc�Rـ:N,- ɭd33!mw'mq#\°,PI6 ,lH;;I4tZ1}( <Ἥ0Ѐ?054v+)>:{h*\r'XȴH;]zDHTEu_K3sVΑ0VK^aLAYGzWXEOZE<)Ep,CTe fW;3A'W {e$~EV{'})V9 WڜEcD B%A_ +]4ieH׎ؾLJAoN"v[8Ѵ�AuTT-V(U,s`&WPAcщ v/H|Q:{I/BN˺8E #ܒxdcWG*=(TH QD�*L ݈F�U2� ]8\x"#�ٞz MWPhCu�zzTqWg;aF �U#å� ; Ƌͽ�pp"59<�&c \w8%\Gw �U2� <C*H6�@%n_$4RT[�"c*L޲ir1!+d*"E<LX(4SX2CXc p=t- 2?a`I'ZA}EAj?4٠&G+f"hN7ĭ\a<(\8ڸ&ÙKEΠSX=J_Q,zH 3(a`@Acz/YgSt䶄#r] �X� =ݘER �3!_j2 gb@kKi3X~ԗ7HC( ㅥ ~=]c-BL2WH`h:|Gjֱ5PW MGSx\eA=)卜(yMLisBn.{K"Y|; %(_>~<V̓{PX&m+#ꑬf6~N+[ӣ36MSU}M\$d([D .98l[OK S[bHYO o/cy KCC^scF/|[?| n K|Atx 2Nɓ:Vzrb}kOKog]$�WՏ�  `ㆉkF) uAxAE〞sTz>v8o }n2ƕ6*34j<GUJPf+WMtQ>FQ#|rD/gA~k34q}a 6^hf#:Ͳx-W F%!q3ɑ:%=mrDnn-IcΨfqz-4z yV ?`Ӯ󽄷l$EF'-6m8S&8&5Iey'pawn11@4:\~@Y$Z\~C0Q:It%ſ @u|ÞJ3I/M3jn:"Dzg.Ѿ&KJa][t s;`|O(R%ϥ{ m+)<ê5vP?'ScZD|· I7d3z/ 7$I7Gy'ڇ7qѓ;#8wN53'\E+ks׻=�V+R� UW3N/2ት6 ;ɜP_?X,mKq0k,q\� R:d7 ܷg,/i["\KǴdKjےR>MDECv@ ɦVei9Oct56IbI]@]se7wiXȀFRg2Żs&�1O}aM+Z:E%�@wݾF~\$A4Sn%{S g&eaki9WJ 2Y:_Snui�]X8T䋒pƷKc31~Zl8U'98*#½Ws%LD0>Tnmhj>{< ޔj9+l&;H ͨh0&Gh/uA.lEfo͠0=Lz:h75RI#Ԃf\:^$ms6:J `V~1 !e[bw.rexY$25!ԉ�WH� 7V[Ë́#-;g Y9W`Q'[u;Y\~1 C9QɌl‹-ieBW6BtGs*?ͦN 3}'W7]BU=g~ٶAFѲ�/eRw?܃~LCv6hPkwyCpA}cv~!v AbM~iSg#e8ߟXL1JkIfMG~Da =h77AyC?c/uibtPo+nK�XXc/qQ޴=ʙtα.śDF�׀t_vIĥm9`բ*'vo"3\{z$L!/0N.� 4\n'9.KAһwlE+@Y(T~Btzr—5اvR_\&<#plS?9U5x3a;u5L:)fdD f pmIf։X �U5@8� ?㘂_�6c}K$*@WGUg6Ls8F}=I9龘}`#)-Jr�8 j.є+L� S<ӼҼUFⴕvtܐ8BҲ찴-IF!"Qse}9E|:t%͗<?iX>uTw�x h6 b/J61I)LX*i;-C1wRpZMhk{Td/C.x ={?COba';\U,S<OpNKqZ#χAG{[0fwh[_{xnYS1qYL9~ raįݛ,_*fNlNRimC<v"a>Aš})&(&"wvƁcvhhu[ƒBo>:Er[x6:7 sȖIF �Vk� 1@|Eb8a^l${:stĝؕwe[05 $Z(oki_ָȉ˱5 ]CeԞ@y$^3|~25\)>Ko@q1j(H5_1Of5o[)�F @kתҢy=ɅDKe'31R*Y^'n&ߎ("ڡXƝcBbͰ0 5nbx';hV5¹jQ`Qh^A_J=J]Q-eoIF2ܒ&*<@\jsmd;WBJ둌L](iVFO\[ު^]. er ,CghO^+ȀĞ;Ʉٖ Ŀ"r(%TmYE_:AIA}n]8 d ^Xw8R2EZyv G `F2"f.y ,wg[b UM3acIK;@ �W2� 9'bD =(q`֪4ʽ]\?r;ED<0Q `i+T*:-0`POC*ì9 \myh\C6.A& r;0&?le[|3{Q^)ƛ 8D&^X|դ2Ja6% ꀩ; })?<-#(9V6@wl!aN~K|hb@̈́US m k>QKܦiK?/O𴢎% b' W}}d<7pf3XVbǣ_B!lAbǓd2=47Dj˫lWA;X),uY1N25{ϵϢk)ּg-GfwRiW:G 쮾[ܼဓ9UsGc#Z3*Vi!PSGD~A94K_3V䥽'ȧi}NsMl;O[ȿ[*\b#me)wtc+ú�W�  }v Cܪ/V�-!V> EwpD<Zt#Ǒh UOߛu F0B 3A+KzGGt|E dNǔwԌ+3]Ot;n \>5ly3;kCd–ojHa?1ӠxUސFp*(R;b7] ~7+$ YmG*5;3U}Fb/QOHޕ@?e!mt{{׆'܆*I}`NГ:)rXAL;AT@P7UD8OF\mxB_702q4wP' zq+(%wxne(v~: CfB&kb%9"$2qْX8X;.F��):HJ>}Tp>- RJzg=)>j#.S8H#V@ % �;    !wq=ڛb2Cc�RY6{ L� 2Cc�Rٷ-pȿd4\<ɰe #~5g,jO,a-uzC<[٧Q;>G5aG)x1*]T{S&k4-zJ=r.UVt*aof-/XGOiGֆP}XkmTW̕1rKqHww^uyIJ t]wO\_*;K:{eۖ$z ;Ǜۀ }AzI$sc尽&ʅ xK_ˆFruQV^Oy'O0;챐z҆%2/R@cĻK 8T+3 $eyE`+Z49M.7^e>q< <%J8&ߕkS}SwMA[]ӌ3 �!뻉!vΟ PY6�  P�1^L`s\Iؤ1v⃭:5 dx%ފ< .'4D;gYT ʑmYsbA%꺪nL!tbK>RP*2D!RxݥlftݲQ^`]iJ)L=ĸbfb݃䇳OB*�u33ES\ 5'18FER4*-ا5ߐrG2Qe'ߞ<ٟDma`-#Neal H. Walfield <neal@g10code.com> �$U# �    � 2Cc�RFl64wm^-#'"gÍL 1'}{{id6)O!`7LD؞tѼ8b!pD pM t/QelМtT01D&O]KÒS/6vפ#/iwʖ#SS\l@;3"t>7 r MZ^0ֈ(^'7x(FJ!Wmuͤmܧ<%):Fbwj'#Et:WB0sIht ˉkB; nJs-ү+\X`Q<'?Y^/TS2~0:efS)rڱ>S;Ѹ'v'K`"d uݤ"Sd1E)Rѽڵ=h~ j7BlɃz9+ZF�U2� ]8\x"�A�tYzBYez3�f=qoڵ+$$@~.F �U#å� ; Ƌͽf)�G6DEI%k~6gE �d;|AoPM?ڻ8�U2� <C*�Hu =  D6ܸRRDDbH1v)`ȷ*AUWhzB<?{bgfsv+(_RXuկ”+<u<v$~n-)`]ňVE=W?OANL߄Ĩ ' 1L+ėe#_HU,n+e%YcfIt':s &L&#hef6) UNqT7l2!TUj- �X� =ݘEu �q.z5,&yaΧ2 _ż_?~sA-!}ah W>f:BOKBYI:%M(0SVrOT9~?XګѰ7Uq޶q k8*TsYO XYJGW YHrFSa5ӽZz%%வ,1r(.H=3<v.A>T>,&.Ixz9"m@b߾.Ij\_walf2'sURUz &ZIۜddWQk/XP$] =ȠP#x$Owe757j yTx? ��V+R� UW3�Hce! 0HX?ovPȄ&Y30]|+t67?*ec>\@�BMX})_)E|K¨6monOL3%ʩEvI9)5P6_l)c"v/~Ss`WF?@y Rةg뢤YgMc1m"FSk=q߰>t p -7ܺÀ/ 1_6. sMtJZQ܀p>o4N}`�1y0-o$t"IzV$$dʞ*6'6_cO\{uskAWl4}H.>41`RQ9(T["JUGPa{ 7* mhHosSW5_Gv~p%0G{i^Fݩ;DFa$Dhu'ݢt bͩvեD�WH� 7V[E eQWM=8Iz1ix @wX6ܢl4o|DR%I$6:2,k Ã`)C�L0"Kp_}^wާ7ź7%ka+Tp"1XUi(m `W#vAUvٞ7d UjV1#$! =X9f2U<S?q B3k#Έ7ܦR[V$$Hח W64jĊXk Ř-է|q+F U|Rx]/ռsSc{gʳk@I$E8-{}h9Ȳ-=-- D^ӑ.]om 9i}gXP0"W,!cFm0_*X-|t\%EW4'>ss Q:&O@H)Om]bT' �U5@8� ?㘂Bk1dNm.>pL_N:Ӭ�Q>b?>AZX\+Zd<5ڇ8YXS<+鸑vn3g't_`v#^6*;?,tpV,* T~Ys. )yX@sl"bYOc#"4"}8ryS~*� _>gupBG-bA_5FN;n'7/OݺnX_g4u =DI-} NJ`bLJ49ُAp.ww>�-E}9f#k:I/+'Whކ\?ߥ8Aw"[z.0ѱbć N,F=|e}It=hvҘc3eqmk9d EZvb.h?0)P+c@v=҈Sz"5iG 8IuUsV}%P# av6Qewo+ �Vk� 1@|E 1淚LͪU )$GnVR㊏,z!ud<'2'xIĔ |688 uIo'`Aݿ몽qM{>v\(�;zj0<6dJA YUҎ7 3K4Y @,]uFtK "AǎS[G\g~4V,E籟Y=n.iohƺ?ř'ٲ*W~V}&%oADLT;)El'XmGZ (L;( .+dl{`/S@X@Q.یǡmg ~ /ZZ%&QD8RƎ[FM2vKcdYObH�dUgNlyfKm[v*7ңN )&M[?n˜vʖ6R/{aK|qIgtω �W2� 9'bD i�m|:Y=LAKf@& ;rM4O8(.j6΍y*, \ߟlK?�ͮTKssY+7|ѷ\bINz)kCϓIsOG# o Hym)rfpB4j- =G,WP{=tPry~‡�\pQx?"H)yċSo1Ciⅅk󙋯{i<󋦼72U[gcN%C~]UEpyad >A# TM,d?mu�k<FCBnC`3wT?V欬ȮoQ4tFMqA6q%UN3bf`uX(4#Ů<@YL9铰7L0iC6I4;#`ת]h{CU9IDƳ8@.cw%f_N(79vUgS2&�WՏ!�  `Zx�gh1tS`$=?Ιܦ0Ė{hUgvK ّQgɽ|adv3Ν@ {�bT,".�wi*IMj=`l*O=ɣQ'ʪZriLm۬զ4{Z߶wv*4xFcM3́Y5ALC):s_w0e=&Q>n U2g}A0bviԼ.alAԥ "0>lw)Ԛ"J'?`ZҐͱOZf=A9)%Ǟ3DžIFԠMK{>jtȣwٛVŠ|.l6C2}EcJO5^ح6 // =yņ$g6KqbZO ن!B_ vX%0ocR n)fjp~E[V(I$㫈cމ�W.�  }v Cܾ�+w(,&h]Rw|n ueܓ~5#Cb{@󳜃x#KlʍytIh@;)iGBa-ch0W#tx= pE2uyX8YӍ OPR( u&084!N Ֆ6V+&pk?,*ai_?'yRfN-n2izu7{_s+(0&Lgz/d2$C|�[75BW;6vLgQ@ ?FmXPCJn;wiza2Z!(A{xuR%1PM䲜U+TyU5I\nKJnlJE6$�8smݞROV(!EСmH<i5.%~FQ; ϶Yo H.4v~ RRL^;ߌk;J+�5[E8/y9Z Iham;D| 0 � !wq=ڛb2Cc�RY5 � 2Cc�R<qmE*\bsp!qLzRb(i/4!j"b8E,7X�@/kme qXy2kd/A[eI˪ݕ -:g gs>@:z#pʀmO^ӀރhrYH, F4@uL5R_l(B#PĊuVr2.Z |Ed)K<UCeв?L� &mZ?K<Dh)xuC!ܥeC|Z9H`p/ri�zC(/wTSbH}}L=: mɘ@o:P>3Z\15W{ cHnzZ_UyŲß_U8v矜nByA #3BB:b#շ73+ ==&J{r_&Neal H. Walfield <neal@pep.foundation>% �;    !wq=ڛb2Cc�RY6{ L� 2Cc�R$4 P뀁W M;8LtW-*ҹ,Vҿs0+Q3ԗLsbя߲ nީ[w-~pJZ\) #(y7t=wKKk Z}9AB#tRBD!8{=w'U58z![ī�Xop~<@?9/6K<Ottlq <wHQwO8tbz6rnzAl7lrDfY 4pNKt*P/9E 0X[F%ED/98mw cՉGC>RJO$?֪c)I4mQ{Ta%,gVL_fZK< mXcNYjegVKߊMu7<vOՅle|;;L&M jзDOFRwh5xnL5}:N3 �!뻉!vΟ PY6�  P� )W>(mAPtiD-x=B坎fZsbfbw-'OW(|ֻJ$ۭ/G`ۓ ٻ{v*TbOnK@nwf@ Y(n8儕?6nҚgceYH\-]cT3yhtyrЌb0xl@o]Hba6Zپ 9hN'Neal H. Walfield <neal@pep-project.org>% �;    !wq=ڛb2Cc�RY6| L� 2Cc�RH} (&*(.txc:ྪ!ѴL]<. Aa�j|ijwEyꓥ/|,mcw*a~v=xw:g ]judHnuX�1tlǩM06z(l)tDgϱd< A΁B'wqWy}\ŅgY5E;*=RIFp=.)L�yz4%&fO1nxFu;{s0:܌[zǪHO.*%8.*+JtqAu]et#뼊9 4, +5[rt˙O9|L.j5!BC<:feb1ܬ~/ٝǏzia-ͻB'| p+9=,m,LRd3 �!뻉!vΟ PY6 �  P#� ›銘Wt<NZ #(;gΉhAm*w] pN!0M<hxlK9ڀ]x ?Qj,[C)&W zUkfP*%9GʋF{swy`piYw=6Q]9VkBcPC%3UV lr#/źׁ|BJ8Miٝ `UyhBabWh'Neal H. Walfield <neal@sequoia-pgp.org>% �;!wq=ڛb2Cc�RZ<  L    � 2Cc�RyP-vΦ&ʾHEiFdn6mZE#H"�S0R¤O`?>`ՄM-nG<KhEvϬdN8GLQ@-.`\NbfY7�?yz:﬑%g G#}Lg2Zze\b9l<y̒+!_BƠk:=M;F̐nsÒCt%CﶺPOP͛"ɫ�J;]cB;mۜ$ɶËj(QKSc[QQy6솏6-:QiYjpc„ˎfwfuQ#-D22J(Α>S#=! Gi xP(,6zU 8W,poj4t R�_1k R+GebW}Y^;sfWn\5NvL>Fs0D U#�5Q$_|p|CxTsls_yAHxeYZvS2j[ױØ;?|4%߯š p-xc a!Л&d #$݀['yX2}H& ۨ9=[@S I~GP=LqW?2$c?rrw(eQ+9]:ߤ0 \.NBMenO]U�� �U# g�) 2Cc�R]  �U#� r#fx%(yΆ>H ܖ]Wk>-N$`$48b觶FRL[Pэ 4ڀ9R W6 փ玮d)~6mR#2C#?5dȏˤ>WX|ьk))`ZB1w$1Q#3Qz6M} dQ<\^U&v8c&jPb{10|iX #I'TJ+ɖx1 2٤'ELOOH ߅5cF€dQ|G/5E01uƐl="HS\n|zU f0 Q:# lP$PŦ"JE&Mb:ʏp ,_'N'Pkڛ3<"54m;XQlz3 f묟CQɻ[7qX%ΝFغfJ1Zԣ|Ef4bI29֠^)Ӵ oY\/9+M~%贐\%�]] h5NDu=sǪ ⅤzY<A#_s}qG" C&˥>g>' ]P i   4y cauQfU8ڐ^뎼 rLU} X%Hm"E^$8x0bq#-=Fʼn �X#  )]  �U#� r#fx%(yΆ>H ܖ]Wk>-N$`$48b觶FRL[Pэ 4ڀ9R W6 փ玮d)~6mR#2C#?5dȏˤ>WX|ьk))`ZB1w$1Q#3Qz6M} dQ<\^U&v8c&jPb{10|iX #I'TJ+ɖx1 2٤ 2Cc�R0?友rɳ~PFbJ=vj/nI {<\+}9ٚ/H0qJԚpd FE᳐k/ЦS10[[ܾ؉z/ǀvԧwK((V.lwdtNr%oC/mv`~3 HV+Vz1mĀcW iPϕ=E8ZzNdlkh<O$AE!&".tȻ'*|?a5/6@vXzpP ·ذ(Vx@lc9Swp%N7z3�DU3?cr5 btYq�DсSq/x7)~Vhxip D (j<V/Tj98Ne?P$Օ' U#�RGWJ$y&IMڌP=�,szz1IP}1>(TOѻner/&3[ScLT] }4 (3FC'8�}9}B06>)9 2"^2DWby8C:MSГdjlߏ3챆)>> SvtرuVТB #:!o8|/'CD�-`(f-3�� �U#  g�� 2Cc�Rم]qbON@6>Hܐ<bhȘ H/tDwk 0?T#uM ~  P; SUED1vV_'ԀBoO䪢=A5ǁ7XNJIܗ$;\�|-2<U.qL\1WiP: 0FnQfLM|XyI0hHG)RHӸ ͺEJNN&+-+^IŊϴ�(+@m| l zP3/'$7 :2Dm-koׁl, *rF{t#w]U):7`9rOoIɹ~yqQ- =x@`jnVxtG7,jG|Ă0Q6{{eh7™,v>ŌE � X# #� 2Cc�RS-`KsAN.) $Xt@F18iVȘY%䠠y"^ApJԛW(8)g<ܜg#$1�]'_e5ef;}GI~?fLE K6*I~XiIb\t؀@ffkjGL>47�_;B5dwEц0N@-ɇQ1/t~i`]١lهkO758.,{2wdӱU"TowI| -H:G"=qbk,)td.b5 ^k @Sr}*ISЦ$N%T 0QZ<eL)s3Kg^fF@ɦ�AҨOS7bP;-Z}y6͢_)Dg]Ϲ U#�֭�ݣ#x [s`T@IhZ>-Wp>- OkеH)AZ6G:?D P/?%+&e_Hon1~-W=Xf.y:6d`pm1l+* &N_(ob2 v }.\To(M U::k;L<w^WMM-*96ogOx{.W ?Oy\G[ uЌ1Fp<�� �U#  g�� 2Cc�Rd$#7ђ̠MLJhHT"m|k'rӗu4d̊eA"Zړ0R"*oj<L K9 _dXXf%L(RTx{nFB)h%Hh<c8JHYE͔p)Ek/XsNNP"PWAeqR7oǟQ| o9""BwSr!d#_V ,+~ zUh ^.sNT%(<<7 UƥH0k=S2ze;&~>/'D(/,H5nvm8:8wB�F(Hi.5nT-tዐl9[! I_18ukM<Xe۴6eB^yaoMyEfX8olOgV>trr$!4Г+}ow � X# � 2Cc�RrQy-c@ygճ1CWsk,L [4ykX/GpK]hϤ?uS|W3HNS~|+!Qp~)gC^Ji"jJNopvLrQ9 '�~*ud"$S瓵4DE+zd=&F~=Osb<8'}w &:˦u#0@"=XT %Rx\7ll%{wLR  K(&W=*Es@yALLDV*mŁz/%;,Ui6uuu݋u_7gE&rqh$,9?)6J )%2xH1Q8DcJEJ$0n[|G²L#㦫tYI)kMX![v=;wtNkQ�����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/keys/peter-expired-backsig.pgp�������������������������������������0000644�0000000�0000000�00000004137�10461020230�0022660�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������^<7 �zQ 3R-d?HDX }j&U9j]GQ~aE;ZN[sXX�0fi!޻TYhw*+QuOE'=ɼyǾ5K2 (V@X2=[m6W+r Qa�?J!cD`ǠF;Zp()qPO<(E*$ȍ#zGțQP'1iP9/e>UuJ(J̴:$#E"iSp8n^!5z'|>B`lJcwaLguB-mB?;<F\@iEmƑF S��Peter� �^<7  �! % }-E!ACcdz]}% }-Ev ( Th}? lJH*qTfgtd�u:'^+I@[j+O/ >8]x,BIgTƤ9p>5¢]b o1bU[ztԓ|T,2;'OYt�Gz LAqu h!PNASu^78K ͗U%{#cYqL|Ѡ>yR/pl:} 9s %}^ns(rLӌ�YfZUhYS4-B]K<\KU`<p9h_ Vg4:IdFgjj̎UHs 9&SOL;^<7 �TgPU4q+,︡tF36}U?i1q\tK:nʃB`ͷLԘNffQ)M; HzT77`#Ca)م i|gpI vH :w8ɛ YMj;"5M[Mlǿ4dM6bkլTu_['*S,{nW;UUC{ťG{e!?Z՜2f}0VӢ\,ÿ]@MXv_;߀צpD0pH ڼ r81),Y9n x^Rbd2O-+L%w'޺%w2fsbd{��² � _-6 % }-E � ^<7�xL�! OG !SpOG / p<hTۡ`0a:@ o^<n,3&cS˷yaM(XQ_PiiR̦Q͢YMt;U* rVҤv[Q-CwDn8G<H?-䢺JYŨVdO媉:김UbpV`Hx}OW7Mc)<ViNYtº('~Fig@UA:_`2Fa+%L7ãO 1S­"m(M8εߥ1I~wV{ 2릭gһeFo^ SI-gs5yjl8=ewz!ACcdz]}% }-E+| � i)t>`5Ux n (7D�vʿ׽bdVr qtpcQliQA%%!Qf `MdT?$k2Ô_2B%yG ˩)4 BkS I�_)M7iȔ)#aλdS*rZX)@- G.I̼ҥtk!>+/L3J '6^΀*Ȧ<h4Z uMM9Lp,w W{n>.nh+ 'Dr C_M.e�/_>,9MkdsZ kj8}+f׌_ AC3���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/keys/peter-sha1-backsig.pgp����������������������������������������0000644�0000000�0000000�00000004131�10461020230�0022046�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������^< � rAgdlm^b! 3pUk-}G8$Kپj0;ˀH˯ZYwAd*\KՖPAdu(ٖob@"MQs`ETJ !y(d|24-CvJc~Ry]a6|ͭDR_ǰ m%k6M"QZkb2nsv$X Ĭ0Sm:.(a%İ 9%U#t/QJ;۠>Uq\c)Yļ?S=*9tQ\>5@w1E$#sbCA�ϴEdXNW��Peter� �^<  �! jqw!y7Vl{jqw � x:xiICUsUN (9=KM@S ²0\m"Sq}X%%uqm@<L~mۙS9;F6XJSʹx!A8^l8'1:D"_S X\RPfn[n& 5XtL]#!&ß@U#hKɠm#i2E`d5y(d]\GE$*Zګ6gmd5Cઉn¯ yo5H?<ZجP9QQR8O3v4 ]>X 蓎f~~ "˭I/VmWOzr7eЗ@ lha^< �j.%V�T=fnhBMTDCDf̰µx nl6>h#JvFTZaR}^VSpyoʯSG }_|%&Ҿs>7" aFQ-[:!Hԫ3j=5P8=Yϳ)eVøzMj ^:"Eo4߰xeXCHjbŲ]Ed7yb$@9{'i"-g W=߹w(~%(۳ *@ .4:;-S)㫪q*ގMZM%>Yi^J4y`$Bj!# P!4��¬ � _-6 jqw�^<�! ӥz!VX1Wdӥz lsWJr ӪYy(,Z6rʛ^"(/ʛ^>ӏO8OiJ t V[gĤ"p,LƷ ^kR6>Kq{i ,Fz*!TwQҏP,<wWac?}�#S~߭5~,w'k֬ۍm9{о=3VPiL_W:UyYL"1ҋ5qYKRIxǮN.ЂslR/� r6y)ѸHqeLozRbZM*oANF^/,^j"pVwm}!y7Vl{jqw *uIdz-d^F5n+L� FќOUfAL.϶sYT"4\lԗ|=,-=Bx \+t$ C7FEyw1v*Y3ճ&HjC66qq"6),9r6d6ਅ߮W.jJV4Ք\neb@U"$w9y]&Թ2VKK$yM˥LRisyaYRx[mը3C S1xշO Bʒh:G$ HiuEeM4-�~X#Ĥ#[;G̀a@d'v���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/keys/pgp5-dsa-elg-v3-subkey-binding.pgp����������������������������0000644�0000000�0000000�00000001275�10461020230�0024125�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������B5N�omk!yBi#�c/vIꖓ4kʧo!&Ji؂}+9^+.VF-k0 {A. E�Yr#"a�u{DXxkO{K9@u+;?ib\dUJ78}lܰ)zYH<JA\ߛ)rHlW�g�P&yOבZXP+c!㺬/*pk/6czC*$t$_jJ"y%2^/H >;W{+䶋R-pgp5 test <pgp5@dev.null>K� 5N � 4nCĊ�30)9a$B�Ȣr@΄>ΐ5V�k<Ve"2 Rw"Te7 )T^$3sYv`Uwtq仮'[Mvy::H;.w E�NW*#\p!DMs`3$2dNYb12-Of>rԜvJNxCʗO}=5r8Z  Ge?5V4nCv�N*`[�-:uװ<zW�����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/keys/primary-key-0-public.pgp��������������������������������������0000644�0000000�0000000�00000003261�10461020230�0022361�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������]o �͋Qa& p{4=ݿQD0B?]/tD3ٹWSofFr"0msOmAxKu)hLSD9R˳U *F,j$ctGܡ9oU`-MI)*e 2ch[I4xƤaH> & :,X aŽcU 󊼺齻W])͗Ӣc0RO7m[fcZ9^3Z-u]I *Zi/X ZM"S}N4ݖegl@rLeXrbjk)q= "°Xr~)m!ARPuMR} TNTeaG_JAj5%��aaaaa �8!G <0>jYϹ]o   � jYϹ( �ceVjl{(Ę t=]yD%7<X?}•cI1xpqʨc6ע䫽No4Ӓ*]eӚζWv' -8C.Ү#Ao^O(m;DtzζFet[8z"&EN0I #XbffO(ܯeMoKgmڽxfuXJ{!1ix7!˦eX{sFw4AQ$KhˠОk*D,0J ]У IiTy#-z'Y;cܾ-Ck?6A{5Oh#]o �)7wF}̘*Wprt?8. H| ;]>Q`R#<M˓Ӕ9bCzxaQ'iȿd`*)uVxP9xpྖtv~(upDAHΜ~H slVW En^D .JԵz- (iWlb!̰SuU6 IU7D.9?QY3ާݡ-V-~)~$&Ge(=\oG)8WXJ !Q@PK,G!mǴPҙ?A1MS ofIZcbLqDWw;t"dʄ ri1Hsv1Q<r2 y o-�� � !G <0>jYϹ]o � jYϹ tQ?xK{r_0IrZ+EZ 9A&zQL;YB* + c�"#0I4ԨK2V'�KH { fkJޖΖ udK>'=w0*aʥT/y*}At4cZiC[SiԊ>L*.ڹwԯ[(ݿ AdPH_V۝+RL_`?M\zT臛'͜`,/4nd@v*9 <y4S?=´YwNfZ*-mK /wvYY25g~.a9!3`?ʏ6!5UlR昬 O}+,q߳K�����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/keys/primary-key-0-secret.pgp��������������������������������������0000644�0000000�0000000�00000007107�10461020230�0022373�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������X]o �͋Qa& p{4=ݿQD0B?]/tD3ٹWSofFr"0msOmAxKu)hLSD9R˳U *F,j$ctGܡ9oU`-MI)*e 2ch[I4xƤaH> & :,X aŽcU 󊼺齻W])͗Ӣc0RO7m[fcZ9^3Z-u]I *Zi/X ZM"S}N4ݖegl@rLeXrbjk)q= "°Xr~)m!ARPuMR} TNTeaG_JAj5%��� ?@ g*}>kVa"4Ҡ4nޞ괄\Ԩ|^� aWjlzq+M!˶[+ښvK&phESޛFM\ʷNI(W5Mga۵XWD64ӤpZd+tFnfjQ x=N,_:4rF. .0/pcLI'kx\ZFWBdN+ra֗Myx;SцWf9ڂSwK1PH*6TVPX <Ǽur"u}1zbrb `7֋ Pw6[e{_$!�'PBk9p=veyf 4i 7{0- \N5ɣW I1Q"(q]XbDmSTe8 )ӶopG8jUC.qP? T� +pnv?폀G#mۈnFNE@Xn%f0m/EJv�hAp9X: }�ڒbKb !5�%oFqYFrkjDձ.pTMQ9[54T߬=C}xkžT *?-QHTA ':. 4] 6EO״K(o#vKasUG�hal҅[/l)^�7dC "d˄V̕fM3C)-M-SNR=Jئۘ?tUt#j{+(Ƒs"^?Ռ�d(�e3z U+ YV i$Y7j0~MdZJ3, i6rI TZ/ky_sEaaaaa �8!G <0>jYϹ]o   � jYϹ( �ceVjl{(Ę t=]yD%7<X?}•cI1xpqʨc6ע䫽No4Ӓ*]eӚζWv' -8C.Ү#Ao^O(m;DtzζFet[8z"&EN0I #XbffO(ܯeMoKgmڽxfuXJ{!1ix7!˦eX{sFw4AQ$KhˠОk*D,0J ]У IiTy#-z'Y;cܾ-Ck?6A{5Oh#X]o �)7wF}̘*Wprt?8. H| ;]>Q`R#<M˓Ӕ9bCzxaQ'iȿd`*)uVxP9xpྖtv~(upDAHΜ~H slVW En^D .JԵz- (iWlb!̰SuU6 IU7D.9?QY3ާݡ-V-~)~$&Ge(=\oG)8WXJ !Q@PK,G!mǴPҙ?A1MS ofIZcbLqDWw;t"dʄ ri1Hsv1Q<r2 y o-��� S :]6D#ٱC ޜGF9pJK!1u 4G/' +Uq2}c}v_/j<7(0oOGFO7(wg~v&C+Ժ3`JR+ʤ�5?FhGLAzjdgP]Ot~'SȟZ/YARXn&zJy^@CvIVi=pl׻|@B'y/Qxttrz"1B>5!,~,@wxs!F(Aͻ k||d!:u%q@<b5Ѝ8pQ섖HyoK= V3XҰ+O-$zaI WvO0�Fe �ByF 1ƙЛ< Gq=D\;{pӉ DX=HQ7V\ϠR vYLニ;bvx2f$8#Tu*e4Nz+l/>J5XdYa<y=7faM~(U"6 o$LȖ.h 9)�>u@SDH7|&3"�فwa5 zMf Et:Pmh~UxUonI.!"ΓtTee;6CN0�L`mSxcnF]ifc4KmzNBA ; jcKA/&X;j"}J~=Fr~$h!M:e\r֏tt(b+W޼+ͪ:@W);-8+*[9/~Bn#Խb;R` ܦsy@mU] ׯ;o6 O{M i/_BF5/E$ i lЌMGE<C^$wcry0\8#ګ � !G <0>jYϹ]o � jYϹ tQ?xK{r_0IrZ+EZ 9A&zQL;YB* + c�"#0I4ԨK2V'�KH { fkJޖΖ udK>'=w0*aʥT/y*}At4cZiC[SiԊ>L*.ڹwԯ[(ݿ AdPH_V۝+RL_`?M\zT臛'͜`,/4nd@v*9 <y4S?=´YwNfZ*-mK /wvYY25g~.a9!3`?ʏ6!5UlR昬 O}+,q߳K���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/keys/primary-key-1-add-userid-bbbbb.pgp����������������������������0000644�0000000�0000000�00000004211�10461020230�0024150�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������]o �͋Qa& p{4=ݿQD0B?]/tD3ٹWSofFr"0msOmAxKu)hLSD9R˳U *F,j$ctGܡ9oU`-MI)*e 2ch[I4xƤaH> & :,X aŽcU 󊼺齻W])͗Ӣc0RO7m[fcZ9^3Z-u]I *Zi/X ZM"S}N4ݖegl@rLeXrbjk)q= "°Xr~)m!ARPuMR} TNTeaG_JAj5%��aaaaa �8!G <0>jYϹ]o   � jYϹ( �ceVjl{(Ę t=]yD%7<X?}•cI1xpqʨc6ע䫽No4Ӓ*]eӚζWv' -8C.Ү#Ao^O(m;DtzζFet[8z"&EN0I #XbffO(ܯeMoKgmڽxfuXJ{!1ix7!˦eX{sFw4AQ$KhˠОk*D,0J ]У IiTy#-z'Y;cܾ-Ck?6A{5Oh#bbbbb �8!G <0>jYϹ]ĥ   � jYϹ{/ I%%d6d\+ueL&aMb21_"\0E뎸 0h`NQJkŪb NTD/t{r#ZL^sN]f cQ,Kr &i*vdhu7BY_$Rr7RKp:Z;8o!l!Hﵢȼ-�DfR1U%!j>rAIWVn횣&L(rtA_S%֡B6I17=D(zVjw$vspUp2K?xXEvZ\b�X0kk9?;3w]o �)7wF}̘*Wprt?8. H| ;]>Q`R#<M˓Ӕ9bCzxaQ'iȿd`*)uVxP9xpྖtv~(upDAHΜ~H slVW En^D .JԵz- (iWlb!̰SuU6 IU7D.9?QY3ާݡ-V-~)~$&Ge(=\oG)8WXJ !Q@PK,G!mǴPҙ?A1MS ofIZcbLqDWw;t"dʄ ri1Hsv1Q<r2 y o-�� � !G <0>jYϹ]o � jYϹ tQ?xK{r_0IrZ+EZ 9A&zQL;YB* + c�"#0I4ԨK2V'�KH { fkJޖΖ udK>'=w0*aʥT/y*}At4cZiC[SiԊ>L*.ڹwԯ[(ݿ AdPH_V۝+RL_`?M\zT臛'͜`,/4nd@v*9 <y4S?=´YwNfZ*-mK /wvYY25g~.a9!3`?ʏ6!5UlR昬 O}+,q߳K���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/keys/primary-key-2-make-aaaaa-primary.pgp��������������������������0000644�0000000�0000000�00000004214�10461020230�0024524�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������]o �͋Qa& p{4=ݿQD0B?]/tD3ٹWSofFr"0msOmAxKu)hLSD9R˳U *F,j$ctGܡ9oU`-MI)*e 2ch[I4xƤaH> & :,X aŽcU 󊼺齻W])͗Ӣc0RO7m[fcZ9^3Z-u]I *Zi/X ZM"S}N4ݖegl@rLeXrbjk)q= "°Xr~)m!ARPuMR} TNTeaG_JAj5%��bbbbb �8!G <0>jYϹ]ĥ   � jYϹ{/ I%%d6d\+ueL&aMb21_"\0E뎸 0h`NQJkŪb NTD/t{r#ZL^sN]f cQ,Kr &i*vdhu7BY_$Rr7RKp:Z;8o!l!Hﵢȼ-�DfR1U%!j>rAIWVn횣&L(rtA_S%֡B6I17=D(zVjw$vspUp2K?xXEvZ\b�X0kk9?;3waaaaa �;   !G <0>jYϹ]�� jYϹ D_ *OH3|0ӓ0ޯ;S); "/՚*W> g*{jQ<D ahDey61^4SD+b5s"-YP__(!dFig^m[UR@ <H/ќ > x-c۷>K�x5sfz=G7PdpSc:L< f؀5GT7WR]Y^M(S~G;֚K~@3afwj> >ńS8djIUp׎6 BR>:˗37!`9 #|Z)o1}N9u/YZY]o �)7wF}̘*Wprt?8. H| ;]>Q`R#<M˓Ӕ9bCzxaQ'iȿd`*)uVxP9xpྖtv~(upDAHΜ~H slVW En^D .JԵz- (iWlb!̰SuU6 IU7D.9?QY3ާݡ-V-~)~$&Ge(=\oG)8WXJ !Q@PK,G!mǴPҙ?A1MS ofIZcbLqDWw;t"dʄ ri1Hsv1Q<r2 y o-�� � !G <0>jYϹ]o � jYϹ tQ?xK{r_0IrZ+EZ 9A&zQL;YB* + c�"#0I4ԨK2V'�KH { fkJޖΖ udK>'=w0*aʥT/y*}At4cZiC[SiԊ>L*.ڹwԯ[(ݿ AdPH_V۝+RL_`?M\zT臛'͜`,/4nd@v*9 <y4S?=´YwNfZ*-mK /wvYY25g~.a9!3`?ʏ6!5UlR昬 O}+,q߳K������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/keys/primary-key-3-make-bbbbb-new-self-sig.pgp���������������������0000644�0000000�0000000�00000004214�10461020230�0025347�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������]o �͋Qa& p{4=ݿQD0B?]/tD3ٹWSofFr"0msOmAxKu)hLSD9R˳U *F,j$ctGܡ9oU`-MI)*e 2ch[I4xƤaH> & :,X aŽcU 󊼺齻W])͗Ӣc0RO7m[fcZ9^3Z-u]I *Zi/X ZM"S}N4ݖegl@rLeXrbjk)q= "°Xr~)m!ARPuMR} TNTeaG_JAj5%��aaaaa �;   !G <0>jYϹ]�� jYϹ D_ *OH3|0ӓ0ޯ;S); "/՚*W> g*{jQ<D ahDey61^4SD+b5s"-YP__(!dFig^m[UR@ <H/ќ > x-c۷>K�x5sfz=G7PdpSc:L< f؀5GT7WR]Y^M(S~G;֚K~@3afwj> >ńS8djIUp׎6 BR>:˗37!`9 #|Z)o1}N9u/YZYbbbbb �8!G <0>jYϹ]&   � jYϹ wh4<0{pHw͐QP Dy<M[:+W!n_:o 縭 _8p2v3\JESB7ۇH"=!�rs$ؾ_Ƶy=r4K~ГM=c)X/R}wiJ꯻MPWl똫t7@ʹfôNҖ�B'#�WZ9>Y6'Gqve~ˤsݗydK2цIE1V˛mקC)SqSGp| |ҒY>Lz'pmݺ=ƒz_O"ClNyHTY MM =xw }2+C״Їy$M` ۔x_ ە39<I]o �)7wF}̘*Wprt?8. H| ;]>Q`R#<M˓Ӕ9bCzxaQ'iȿd`*)uVxP9xpྖtv~(upDAHΜ~H slVW En^D .JԵz- (iWlb!̰SuU6 IU7D.9?QY3ާݡ-V-~)~$&Ge(=\oG)8WXJ !Q@PK,G!mǴPҙ?A1MS ofIZcbLqDWw;t"dʄ ri1Hsv1Q<r2 y o-�� � !G <0>jYϹ]o � jYϹ tQ?xK{r_0IrZ+EZ 9A&zQL;YB* + c�"#0I4ԨK2V'�KH { fkJޖΖ udK>'=w0*aʥT/y*}At4cZiC[SiԊ>L*.ڹwԯ[(ݿ AdPH_V۝+RL_`?M\zT臛'͜`,/4nd@v*9 <y4S?=´YwNfZ*-mK /wvYY25g~.a9!3`?ʏ6!5UlR昬 O}+,q߳K������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/keys/primary-key-4-make-bbbbb-primary.pgp��������������������������0000644�0000000�0000000�00000006056�10461020230�0024541�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������]o �͋Qa& p{4=ݿQD0B?]/tD3ٹWSofFr"0msOmAxKu)hLSD9R˳U *F,j$ctGܡ9oU`-MI)*e 2ch[I4xƤaH> & :,X aŽcU 󊼺齻W])͗Ӣc0RO7m[fcZ9^3Z-u]I *Zi/X ZM"S}N4ݖegl@rLeXrbjk)q= "°Xr~)m!ARPuMR} TNTeaG_JAj5%��aaaaa �8!G <0>jYϹ]o   � jYϹ( �ceVjl{(Ę t=]yD%7<X?}•cI1xpqʨc6ע䫽No4Ӓ*]eӚζWv' -8C.Ү#Ao^O(m;DtzζFet[8z"&EN0I #XbffO(ܯeMoKgmڽxfuXJ{!1ix7!˦eX{sFw4AQ$KhˠОk*D,0J ]У IiTy#-z'Y;cܾ-Ck?6A{5Oh# �8   !G <0>jYϹ]� jYϹK lRH6XicE4I~DJ u.N֭={t\32.DӲ4fIo�`7ɞ͞:7&r;YvQ`a{Ź@.oT;ա Q nȂ3E,R=f n29y�VE#-�㠍<(e1̮A.4@Uw ߇НCiTJ Om�/]@מV,jʆE2 zR!V陭$R:l?9nteb=YP|Yu[jP<Ϙ /h14mn Eܺ"m"OޟvځMUibbbbb �8!G <0>jYϹ]ĥ   � jYϹ{/ I%%d6d\+ueL&aMb21_"\0E뎸 0h`NQJkŪb NTD/t{r#ZL^sN]f cQ,Kr &i*vdhu7BY_$Rr7RKp:Z;8o!l!Hﵢȼ-�DfR1U%!j>rAIWVn횣&L(rtA_S%֡B6I17=D(zVjw$vspUp2K?xXEvZ\b�X0kk9?;3w �;   !G <0>jYϹ]� jYϹe *!K:|-�&3&%eĬ[ 3ùP]Q3W}.帒<)8o}۵ڬà`Jqv>QNUL.6F;n㨊j\`([eJ(f77,N-K\곜.nP/V=iгL̶s S8h< ) !24;EaG鷬Gy0Y^ԩX(qx 2JF3ǘ]a-5?")J j yȤp;l5UڒRU|VT©Ȁk7 Yk+4Fb\TN}9WL y2ƻ>V>Fԇﯳ8t(족]o �)7wF}̘*Wprt?8. H| ;]>Q`R#<M˓Ӕ9bCzxaQ'iȿd`*)uVxP9xpྖtv~(upDAHΜ~H slVW En^D .JԵz- (iWlb!̰SuU6 IU7D.9?QY3ާݡ-V-~)~$&Ge(=\oG)8WXJ !Q@PK,G!mǴPҙ?A1MS ofIZcbLqDWw;t"dʄ ri1Hsv1Q<r2 y o-�� � !G <0>jYϹ]o � jYϹ tQ?xK{r_0IrZ+EZ 9A&zQL;YB* + c�"#0I4ԨK2V'�KH { fkJޖΖ udK>'=w0*aʥT/y*}At4cZiC[SiԊ>L*.ڹwԯ[(ݿ AdPH_V۝+RL_`?M\zT臛'͜`,/4nd@v*9 <y4S?=´YwNfZ*-mK /wvYY25g~.a9!3`?ʏ6!5UlR昬 O}+,q߳K����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/keys/primary-key-5-make-aaaaa-self-sig.pgp�������������������������0000644�0000000�0000000�00000006056�10461020230�0024563�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������]o �͋Qa& p{4=ݿQD0B?]/tD3ٹWSofFr"0msOmAxKu)hLSD9R˳U *F,j$ctGܡ9oU`-MI)*e 2ch[I4xƤaH> & :,X aŽcU 󊼺齻W])͗Ӣc0RO7m[fcZ9^3Z-u]I *Zi/X ZM"S}N4ݖegl@rLeXrbjk)q= "°Xr~)m!ARPuMR} TNTeaG_JAj5%��bbbbb �8!G <0>jYϹ]ĥ   � jYϹ{/ I%%d6d\+ueL&aMb21_"\0E뎸 0h`NQJkŪb NTD/t{r#ZL^sN]f cQ,Kr &i*vdhu7BY_$Rr7RKp:Z;8o!l!Hﵢȼ-�DfR1U%!j>rAIWVn횣&L(rtA_S%֡B6I17=D(zVjw$vspUp2K?xXEvZ\b�X0kk9?;3w �;   !G <0>jYϹ]� jYϹe *!K:|-�&3&%eĬ[ 3ùP]Q3W}.帒<)8o}۵ڬà`Jqv>QNUL.6F;n㨊j\`([eJ(f77,N-K\곜.nP/V=iгL̶s S8h< ) !24;EaG鷬Gy0Y^ԩX(qx 2JF3ǘ]a-5?")J j yȤp;l5UڒRU|VT©Ȁk7 Yk+4Fb\TN}9WL y2ƻ>V>Fԇﯳ8t(족aaaaa �8!G <0>jYϹ]o   � jYϹ( �ceVjl{(Ę t=]yD%7<X?}•cI1xpqʨc6ע䫽No4Ӓ*]eӚζWv' -8C.Ү#Ao^O(m;DtzζFet[8z"&EN0I #XbffO(ܯeMoKgmڽxfuXJ{!1ix7!˦eX{sFw4AQ$KhˠОk*D,0J ]У IiTy#-z'Y;cܾ-Ck?6A{5Oh# �8!G <0>jYϹ]    � jYϹ EzL L'B:Txe#t2 (WdMIr]; | v c#6Ԡ2%ł)(.vx3ESAɥyG4Zr⽗v}x6v'qK<j;ڥ˳;BxraK:l=vڰk ܴȓcP%ѕ5= d45MoNV̆PѰQO[MJzi|AWCS Prp$Qj<^ ilE[q�:ɫ]WQ:} d6㚠-hFFa: oP˛# QLg~)Bv[M^c&Z,0IlE酟q3]MJ`I]o �)7wF}̘*Wprt?8. H| ;]>Q`R#<M˓Ӕ9bCzxaQ'iȿd`*)uVxP9xpྖtv~(upDAHΜ~H slVW En^D .JԵz- (iWlb!̰SuU6 IU7D.9?QY3ާݡ-V-~)~$&Ge(=\oG)8WXJ !Q@PK,G!mǴPҙ?A1MS ofIZcbLqDWw;t"dʄ ri1Hsv1Q<r2 y o-�� � !G <0>jYϹ]o � jYϹ tQ?xK{r_0IrZ+EZ 9A&zQL;YB* + c�"#0I4ԨK2V'�KH { fkJޖΖ udK>'=w0*aʥT/y*}At4cZiC[SiԊ>L*.ڹwԯ[(ݿ AdPH_V۝+RL_`?M\zT臛'͜`,/4nd@v*9 <y4S?=´YwNfZ*-mK /wvYY25g~.a9!3`?ʏ6!5UlR昬 O}+,q߳K����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/keys/primary-key-6-revoked-aaaaa.pgp�������������������������������0000644�0000000�0000000�00000006747�10461020230�0023606�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������]o �͋Qa& p{4=ݿQD0B?]/tD3ٹWSofFr"0msOmAxKu)hLSD9R˳U *F,j$ctGܡ9oU`-MI)*e 2ch[I4xƤaH> & :,X aŽcU 󊼺齻W])͗Ӣc0RO7m[fcZ9^3Z-u]I *Zi/X ZM"S}N4ݖegl@rLeXrbjk)q= "°Xr~)m!ARPuMR} TNTeaG_JAj5%��bbbbb �8!G <0>jYϹ]ĥ   � jYϹ{/ I%%d6d\+ueL&aMb21_"\0E뎸 0h`NQJkŪb NTD/t{r#ZL^sN]f cQ,Kr &i*vdhu7BY_$Rr7RKp:Z;8o!l!Hﵢȼ-�DfR1U%!j>rAIWVn횣&L(rtA_S%֡B6I17=D(zVjw$vspUp2K?xXEvZ\b�X0kk9?;3w �;   !G <0>jYϹ]� jYϹe *!K:|-�&3&%eĬ[ 3ùP]Q3W}.帒<)8o}۵ڬà`Jqv>QNUL.6F;n㨊j\`([eJ(f77,N-K\곜.nP/V=iгL̶s S8h< ) !24;EaG鷬Gy0Y^ԩX(qx 2JF3ǘ]a-5?")J j yȤp;l5UڒRU|VT©Ȁk7 Yk+4Fb\TN}9WL y2ƻ>V>Fԇﯳ8t(족aaaaa0 � !G <0>jYϹ]S�� jYϹ 3oB q4U(\uoVsQsvTlBs!t@@IVsr!6x0+JO>hֻnqkGg VHPCrn�ұ~)yL?�#,B==L=_?4~n8W~(Zc! 0"./eB%hnDw ӥ)7}iķX|@:q}U+yykO/er!kv{ϰzI;hYuoC]ͫh} t̰7uTZ5ŵrez>nlZ:*vZ&FQP$ `Y]}}DH^yyL5 �8!G <0>jYϹ]o   � jYϹ( �ceVjl{(Ę t=]yD%7<X?}•cI1xpqʨc6ע䫽No4Ӓ*]eӚζWv' -8C.Ү#Ao^O(m;DtzζFet[8z"&EN0I #XbffO(ܯeMoKgmڽxfuXJ{!1ix7!˦eX{sFw4AQ$KhˠОk*D,0J ]У IiTy#-z'Y;cܾ-Ck?6A{5Oh# �8!G <0>jYϹ]    � jYϹ EzL L'B:Txe#t2 (WdMIr]; | v c#6Ԡ2%ł)(.vx3ESAɥyG4Zr⽗v}x6v'qK<j;ڥ˳;BxraK:l=vڰk ܴȓcP%ѕ5= d45MoNV̆PѰQO[MJzi|AWCS Prp$Qj<^ ilE[q�:ɫ]WQ:} d6㚠-hFFa: oP˛# QLg~)Bv[M^c&Z,0IlE酟q3]MJ`I]o �)7wF}̘*Wprt?8. H| ;]>Q`R#<M˓Ӕ9bCzxaQ'iȿd`*)uVxP9xpྖtv~(upDAHΜ~H slVW En^D .JԵz- (iWlb!̰SuU6 IU7D.9?QY3ާݡ-V-~)~$&Ge(=\oG)8WXJ !Q@PK,G!mǴPҙ?A1MS ofIZcbLqDWw;t"dʄ ri1Hsv1Q<r2 y o-�� � !G <0>jYϹ]o � jYϹ tQ?xK{r_0IrZ+EZ 9A&zQL;YB* + c�"#0I4ԨK2V'�KH { fkJޖΖ udK>'=w0*aʥT/y*}At4cZiC[SiԊ>L*.ڹwԯ[(ݿ AdPH_V۝+RL_`?M\zT臛'͜`,/4nd@v*9 <y4S?=´YwNfZ*-mK /wvYY25g~.a9!3`?ʏ6!5UlR昬 O}+,q߳K�������������������������sequoia-openpgp-2.0.0/tests/data/keys/primary-key-is-also-subkey.pgp��������������������������������0000644�0000000�0000000�00000003210�10461020230�0023607�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������-----BEGIN PGP PUBLIC KEY BLOCK----- Comment: 8E8C 33FA 4626 3379 76D9 7978 069C 0C34 8DD8 2C19 Comment: Emmelie Dorothea Dina Samantha Awina Ed25519 xVgEWx6DORYJKwYBBAHaRw8BAQdABJa6xH6/nQoBQtVuqaenNLrKvkJ5gniGtBH3 tsK+ckkAAP9uxXBqYoH/Kh+rjNMKRO6pgdkoYTYvMh5TVcQHR6LzoA+tzSxFbW1l bGllIERvcm90aGVhIERpbmEgU2FtYW50aGEgQXdpbmEgRWQyNTUxOcKQBBMWCAA4 FiEEjowz+kYmM3l22Xl4BpwMNI3YLBkFAlsegzkCGwMFCwkIBwIGFQoJCAsCBBYC AwECHgECF4AACgkQBpwMNI3YLBlo5wD7B2CyTh/hEQOaZV56TqRpabY+zpCs2cTX 7IjZnkEi5OAA/0WxAICvyJBkKIittgbnyQXml1UysgZ/Vv0dzNb+UgsPx1gEWx6D ORYJKwYBBAHaRw8BAQdABJa6xH6/nQoBQtVuqaenNLrKvkJ5gniGtBH3tsK+ckkA AP9uxXBqYoH/Kh+rjNMKRO6pgdkoYTYvMh5TVcQHR6LzoA+twsC/BBgWCgExBYJf 4cXhCRAGnAw0jdgsGUcUAAAAAAAeACBzYWx0QG5vdGF0aW9ucy5zZXF1b2lhLXBn cC5vcme+n7H0EBLbDFbpvvx4eHLYbFeWacbznzObBC6WrksVTQKbA76gBBkWCgBv BYJf4cXhCRAGnAw0jdgsGUcUAAAAAAAeACBzYWx0QG5vdGF0aW9ucy5zZXF1b2lh LXBncC5vcmdRks7edU9bjyvlBTyKARbFpoiol8MYKqpt9A0Tim/rBxYhBI6MM/pG JjN5dtl5eAacDDSN2CwZAACzxgD8DfXRYsqoY0mHSoxr4uNgp5OdBzYy9/XHRLxu QAopyfkBAK31K6LNxHb2yopFvE/HI+p0csRTfoWLoMVRZBGg3x4LFiEEjowz+kYm M3l22Xl4BpwMNI3YLBkAANBLAQDHyejnvMLSfNveAesrKn624vMz5rs7nR7gnbC1 WiFuXQEA8NenjjR3JBhUCdIjpybOX4kX/597P3rT/vtJRWanJQ3HWARbg9ivFgkr BgEEAdpHDwEBB0DQvCiWhC75FONjJ0v7ZCn92aZjXd8VQbva0TDclOwmvwABAIZC rDr8zd249g2I3GjcPXDZdbWPUgk+bf5nFvVvEMIeEMzCwC8EGBYIACAWIQSOjDP6 RiYzeXbZeXgGnAw0jdgsGQUCW4PYrwIbAgCBCRAGnAw0jdgsGXYgBBkWCAAdFiEE Bhw8pEr/DsWNxm6VIuP6/pa1bDIFAluD2K8ACgkQIuP6/pa1bDJoTQD/cYH2EFRB ljjnT6DiPJYEJRoz5IAXgnKaOntXPA/9uCYBAN8po38vE9auBLpOM8QKNVISCGG3 Y2bOe2BIQ8K25bkKJ4ABAN1KMV+Lb5Bdgh1xMvjGILyT+aVH3dIppj/mBlnHO3mr AP9RgDT1iuvJlwIaML8Hq/uaG1Ryd9rwfAt0tfqj0dY1Cw== =zKol -----END PGP PUBLIC KEY BLOCK----- ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/keys/public-key.gpg������������������������������������������������0000644�0000000�0000000�00000061074�10461020230�0020540�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������U#R;U1RLo\G^zO`DR#2ܰ5q1~l߈J^jgL%<yxQz,N܉ў[~W ݳ.T'D6NOmV6|j&umr~Nu8>XC]?}]$ *%|ic8x1"R2'bעo{i(1< T dvWBʰűٺzv/'hrd@w1z<+͙aыfb{f?A'[}*P.6. ~(|M褮 =&_{]gAS#wq; p]B3U�x!g6}(!ScOҽ~&URgEXґ!,-=֑jiA.f= B+Rڔ*- ?DG��$Neal H. Walfield <neal@walfield.org> �' �    U#� 2Cc�RAbIzr\ (z)1幸߬ |;S)~dUs8xgAW" ,e@U!K0_홴Bóv&9tA0RŚ 6IGgg>-XN;I+ijF` f}MR}f99M0 MyɌu )@xQƗ.l/BP su S鎼Y<r [1k@m44ↆ9:.9 gFw]+6B#>$th@N-` gBrcw.DKI~a}71E y+ rR\)@8? Cbl7<3K_ͭHZsD^2qzhCX1 i D x 'kQ7􃇍D F�U2� ]8\x"N�OC6J@�ֈߒ܄T8BF �U#å� ; Ƌͽ�Cyz==;R(�d؅xճג�U2� <C*t'j﯆Sf!-.#['y`#:cИ;<?m."g@avQziGoSa)8&M**xVE[A$N ~<quFj�O.R# H=A $$! :,#Y\Q:KDd ~Y&*kC@+M ;"ͰBZT.8e}ȳ;4;qTS}ܯ  �X� =ݘE }mjȒ2|8sf;ufʳ\Wby/JaR&Z}hRq){]"Gyv^bDzg$u2(hq�]s)1YtxJو:6ŭfؿexpUicHd+=1ffsp90,'r~[ԅk-ͅ¨Ry@D縅Ϩ*s` sm2# V1g{ 4y@|v_O ֱN칐_'sWtш I̸ %{I!ekؗps4 ^ p+()w} í`ٞ37?'잮I+pCY܃ފVXC�V+S� UW3�~8<֦ROK]B"~7,(( 8<Z Tؘ<R̉agK)JK֯y3/xV.Jm[TJm}`݅7`2j7~2JV\Q/>n >ڵڭ21)nuH]82OK .nγEo YkpX&߹HCO?`x�vn*1o{gсĭf2RP;#@S sX'K~`u2O :[ r6n)fq9~vf&4C+J =6+' ތ[],^ c1w  5_V<޾/HIl. |�p83摔$'EuJPo9{7O(H.JY9r6z�Q|r>+qfm 媔CbUzPS|iMh#14=;{Bw�WH� 7V[EI%Q0)X:uM&h(5ڂJ]OΫF2;fu9ލc9hY7:@b:֌8ۡ>>\x(N, 3:*C'^FpOjXO~){`׺rbP^+m[j>w\X%PqۂPNJ2d򆪑?1^qf~$r5tiЖb$Z T`a M�,D[7c @2^d4{Y0e&'F% g3duGw4xDMX>!Xo ѫ!hZ#SBqm $xYYM3x:}_afJՠkKLD,WqC[. i|ѪqCe)R1Y?hPn\LC괾:Dhw_^,O$6"vJͼ]zKc}Z|æd`ԋVs ~ʼn �U5@8� ?㘂b�ŘP:  Nx'dk%lT~k'^ev8m`0slc 9ߢ+Q`(}!"&'G%\9�\L]8_,|;Uę~tEᝍLY FfW\<B;@u;Vʔ $R֍cd!G6mP`QB >f-q7T <{ /(4ϥωʱg|Qr/a$(þ(w1FiNuEҮ@ c)<FP~̒$=޶]ɮ,U5E /VL9} cg¬=wɃ Sـj`:OxGpr ؒ 4Rt)0\�OE'@C`F!a&PSDάA^fh;V>;OJ/xs| (l= �Vk� 1@|E}�fVh?}#ǭ|2 *ʮ[G+1PX|us[",(iXV>Bv[T[~6";fHs3̅wYh߰/dX؀7&ȳױ)Ln>`>Wu$^dr¢785s_A`5,fl>ÛDXp ́ Ծ+yNmNk`ɮN:?0(DbVGy ^B X!vRBL;H �g={= U r} %Ӏ eKُfzP:q"6/%LEeZCXMd(!W5lDdU\gѮl~&25k֊[0;Ŵh~"\ \_wm_pa.qk{ HuΕSx/O-%_ V4!F �W2� 9'bDApZe hXl&i#e@RF7J3Ϋ?B ܓ.n1@ܻa<@WrIH$x{_?ď"W+3k[L80u1M̖=:\+x[SnUӳ|IPu`gyes*"Jq *W]•\,M9!/b5WO1CZn8:.m 9o�#/S?K1;}AI}[ OC|{6_!1 `3/SYĄ`Hx2-{Q}E*A-v!u9rWlVԠok=LG읜¼11mm~ަf'l&j|gg*kj4+i8,W.>c$.>=6w,Z;q񏧸!2=|?0L8IFȍ�WՏ�  `k0cB3ubusF.'8}_3&)d_Sl:p~O[c?s+1U)ycXcі\^tcıNӢ4xFY&Uje*dd-fQ//W1ҟ"H=@%~ڛݐ>m泗K3=cM0o^{TDvҀB$,2f# Mjxu&Nx5BOfiDymJ07"v LF[z/z6`wlnjMY=)zV]F^uXKht%DsTB"56;Uב_lQ=ƮH& $VDrApJ Btk_g1J#2h >%OXHax1BuLgI8Bt VX  \R{PM^Ƚ*Ƒc�W�  }v C O�g#k452 -zLãq-֏9힉xr=8 %nI5S~Cs%ULKl Hb�XOudc0kuA^BƙdmY ?hۍ]Hd %<*3Gv%1T~3*0l|܂8꣆?!H. g:DٍI>ݾU �`5HjΙv?A=h+Mp?\@E6r,r߭ԧT tK^�r[`8/4':7~8Y2h֖ĥ9E8 8h['O3J@eh7p˕FZ7֍k ׇBV^7a]CϘ`_ըjBkft. ( H#7^7gcf{!:AYiw`<x5(/[>*dIXxRHO+B޵8Ԙ*POdΤc爮sQ,iۿ٘@ �$U# �    � 2Cc�Rٽ2)qE-ըJ&EcUG;}ª"릂  6ldM.mN_c73ML[�ɱ�}fFT / I0sOb.J[P`*½ةsԏ"0Jtv%P!iP5;*q3]{?dȑ)ZDepYYDNEF^(r x7"�{gNHm6MtOIޘm7/aӄSu=j̪^׋Gg[T:aģx3rfb8oMS~xz ړhu3-�^fMNUBE0'`}�DV('](17ދP `"]=)vygS fh4ܼx9Z?) |>x=j>wDG %MDy( �>    !wq=ڛb2Cc�RY6{ L� 2Cc�Rك+M4<ZLh 4<Anqt ¬`#>ޒ=.#Шw3|շA 4"o)WlYn! n]`tq؀?KjTSKc:X8L':@(�a'<! kOCKZz[o4)Sg[Նvq@�PVmRbJleWG9qdET5 Nl<ל@)s{~][4RɒҘ d~*IL#`=1/?lȴ'7AQ 0Haݒv&eXژ6Jx;t/i.OXX "-p7ly0)&-a>@j Q\ 1�^оeS<t9Y!Qi o蕩 p?ueom^{-vM&HDL~O3 �!뻉!vΟ PY6�  P&�ӤS4Zxs,*iM<qŸubC)F< #xʹ`^}].mqw>Nx(E* IQ򈝧-޲Ztr_Tu ܷEӪ\M}Y{'¦:'`Rۃ`_i05uVI#EXrϿ kʔ?`fq ق�~ۙ8]=]ɐҫO|o~\(!Neal H. Walfield <neal@gnupg.org> �$U#o �    � 2Cc�Rـ:N,- ɭd33!mw'mq#\°,PI6 ,lH;;I4tZ1}( <Ἥ0Ѐ?054v+)>:{h*\r'XȴH;]zDHTEu_K3sVΑ0VK^aLAYGzWXEOZE<)Ep,CTe fW;3A'W {e$~EV{'})V9 WڜEcD B%A_ +]4ieH׎ؾLJAoN"v[8Ѵ�AuTT-V(U,s`&WPAcщ v/H|Q:{I/BN˺8E #ܒxdcWG*=(TH QD�*L ݈F�U2� ]8\x"#�ٞz MWPhCu�zzTqWg;aF �U#å� ; Ƌͽ�pp"59<�&c \w8%\Gw �U2� <C*H6�@%n_$4RT[�"c*L޲ir1!+d*"E<LX(4SX2CXc p=t- 2?a`I'ZA}EAj?4٠&G+f"hN7ĭ\a<(\8ڸ&ÙKEΠSX=J_Q,zH 3(a`@Acz/YgSt䶄#r] �X� =ݘER �3!_j2 gb@kKi3X~ԗ7HC( ㅥ ~=]c-BL2WH`h:|Gjֱ5PW MGSx\eA=)卜(yMLisBn.{K"Y|; %(_>~<V̓{PX&m+#ꑬf6~N+[ӣ36MSU}M\$d([D .98l[OK S[bHYO o/cy KCC^scF/|[?| n K|Atx 2Nɓ:Vzrb}kOKog]$�WՏ�  `ㆉkF) uAxAE〞sTz>v8o }n2ƕ6*34j<GUJPf+WMtQ>FQ#|rD/gA~k34q}a 6^hf#:Ͳx-W F%!q3ɑ:%=mrDnn-IcΨfqz-4z yV ?`Ӯ󽄷l$EF'-6m8S&8&5Iey'pawn11@4:\~@Y$Z\~C0Q:It%ſ @u|ÞJ3I/M3jn:"Dzg.Ѿ&KJa][t s;`|O(R%ϥ{ m+)<ê5vP?'ScZD|· I7d3z/ 7$I7Gy'ڇ7qѓ;#8wN53'\E+ks׻=�V+R� UW3N/2ት6 ;ɜP_?X,mKq0k,q\� R:d7 ܷg,/i["\KǴdKjےR>MDECv@ ɦVei9Oct56IbI]@]se7wiXȀFRg2Żs&�1O}aM+Z:E%�@wݾF~\$A4Sn%{S g&eaki9WJ 2Y:_Snui�]X8T䋒pƷKc31~Zl8U'98*#½Ws%LD0>Tnmhj>{< ޔj9+l&;H ͨh0&Gh/uA.lEfo͠0=Lz:h75RI#Ԃf\:^$ms6:J `V~1 !e[bw.rexY$25!ԉ�WH� 7V[Ë́#-;g Y9W`Q'[u;Y\~1 C9QɌl‹-ieBW6BtGs*?ͦN 3}'W7]BU=g~ٶAFѲ�/eRw?܃~LCv6hPkwyCpA}cv~!v AbM~iSg#e8ߟXL1JkIfMG~Da =h77AyC?c/uibtPo+nK�XXc/qQ޴=ʙtα.śDF�׀t_vIĥm9`բ*'vo"3\{z$L!/0N.� 4\n'9.KAһwlE+@Y(T~Btzr—5اvR_\&<#plS?9U5x3a;u5L:)fdD f pmIf։X �U5@8� ?㘂_�6c}K$*@WGUg6Ls8F}=I9龘}`#)-Jr�8 j.є+L� S<ӼҼUFⴕvtܐ8BҲ찴-IF!"Qse}9E|:t%͗<?iX>uTw�x h6 b/J61I)LX*i;-C1wRpZMhk{Td/C.x ={?COba';\U,S<OpNKqZ#χAG{[0fwh[_{xnYS1qYL9~ raįݛ,_*fNlNRimC<v"a>Aš})&(&"wvƁcvhhu[ƒBo>:Er[x6:7 sȖIF �Vk� 1@|Eb8a^l${:stĝؕwe[05 $Z(oki_ָȉ˱5 ]CeԞ@y$^3|~25\)>Ko@q1j(H5_1Of5o[)�F @kתҢy=ɅDKe'31R*Y^'n&ߎ("ڡXƝcBbͰ0 5nbx';hV5¹jQ`Qh^A_J=J]Q-eoIF2ܒ&*<@\jsmd;WBJ둌L](iVFO\[ު^]. er ,CghO^+ȀĞ;Ʉٖ Ŀ"r(%TmYE_:AIA}n]8 d ^Xw8R2EZyv G `F2"f.y ,wg[b UM3acIK;@ �W2� 9'bD =(q`֪4ʽ]\?r;ED<0Q `i+T*:-0`POC*ì9 \myh\C6.A& r;0&?le[|3{Q^)ƛ 8D&^X|դ2Ja6% ꀩ; })?<-#(9V6@wl!aN~K|hb@̈́US m k>QKܦiK?/O𴢎% b' W}}d<7pf3XVbǣ_B!lAbǓd2=47Dj˫lWA;X),uY1N25{ϵϢk)ּg-GfwRiW:G 쮾[ܼဓ9UsGc#Z3*Vi!PSGD~A94K_3V䥽'ȧi}NsMl;O[ȿ[*\b#me)wtc+ú�W�  }v Cܪ/V�-!V> EwpD<Zt#Ǒh UOߛu F0B 3A+KzGGt|E dNǔwԌ+3]Ot;n \>5ly3;kCd–ojHa?1ӠxUސFp*(R;b7] ~7+$ YmG*5;3U}Fb/QOHޕ@?e!mt{{׆'܆*I}`NГ:)rXAL;AT@P7UD8OF\mxB_702q4wP' zq+(%wxne(v~: CfB&kb%9"$2qْX8X;.F��):HJ>}Tp>- RJzg=)>j#.S8H#V@ % �;    !wq=ڛb2Cc�RY6{ L� 2Cc�Rٷ-pȿd4\<ɰe #~5g,jO,a-uzC<[٧Q;>G5aG)x1*]T{S&k4-zJ=r.UVt*aof-/XGOiGֆP}XkmTW̕1rKqHww^uyIJ t]wO\_*;K:{eۖ$z ;Ǜۀ }AzI$sc尽&ʅ xK_ˆFruQV^Oy'O0;챐z҆%2/R@cĻK 8T+3 $eyE`+Z49M.7^e>q< <%J8&ߕkS}SwMA[]ӌ3 �!뻉!vΟ PY6�  P�1^L`s\Iؤ1v⃭:5 dx%ފ< .'4D;gYT ʑmYsbA%꺪nL!tbK>RP*2D!RxݥlftݲQ^`]iJ)L=ĸbfb݃䇳OB*�u33ES\ 5'18FER4*-ا5ߐrG2Qe'ߞ<ٟDma`-#Neal H. Walfield <neal@g10code.com> �$U# �    � 2Cc�RFl64wm^-#'"gÍL 1'}{{id6)O!`7LD؞tѼ8b!pD pM t/QelМtT01D&O]KÒS/6vפ#/iwʖ#SS\l@;3"t>7 r MZ^0ֈ(^'7x(FJ!Wmuͤmܧ<%):Fbwj'#Et:WB0sIht ˉkB; nJs-ү+\X`Q<'?Y^/TS2~0:efS)rڱ>S;Ѹ'v'K`"d uݤ"Sd1E)Rѽڵ=h~ j7BlɃz9+ZF�U2� ]8\x"�A�tYzBYez3�f=qoڵ+$$@~.F �U#å� ; Ƌͽf)�G6DEI%k~6gE �d;|AoPM?ڻ8�U2� <C*�Hu =  D6ܸRRDDbH1v)`ȷ*AUWhzB<?{bgfsv+(_RXuկ”+<u<v$~n-)`]ňVE=W?OANL߄Ĩ ' 1L+ėe#_HU,n+e%YcfIt':s &L&#hef6) UNqT7l2!TUj- �X� =ݘEu �q.z5,&yaΧ2 _ż_?~sA-!}ah W>f:BOKBYI:%M(0SVrOT9~?XګѰ7Uq޶q k8*TsYO XYJGW YHrFSa5ӽZz%%வ,1r(.H=3<v.A>T>,&.Ixz9"m@b߾.Ij\_walf2'sURUz &ZIۜddWQk/XP$] =ȠP#x$Owe757j yTx? ��V+R� UW3�Hce! 0HX?ovPȄ&Y30]|+t67?*ec>\@�BMX})_)E|K¨6monOL3%ʩEvI9)5P6_l)c"v/~Ss`WF?@y Rةg뢤YgMc1m"FSk=q߰>t p -7ܺÀ/ 1_6. sMtJZQ܀p>o4N}`�1y0-o$t"IzV$$dʞ*6'6_cO\{uskAWl4}H.>41`RQ9(T["JUGPa{ 7* mhHosSW5_Gv~p%0G{i^Fݩ;DFa$Dhu'ݢt bͩvեD�WH� 7V[E eQWM=8Iz1ix @wX6ܢl4o|DR%I$6:2,k Ã`)C�L0"Kp_}^wާ7ź7%ka+Tp"1XUi(m `W#vAUvٞ7d UjV1#$! =X9f2U<S?q B3k#Έ7ܦR[V$$Hח W64jĊXk Ř-է|q+F U|Rx]/ռsSc{gʳk@I$E8-{}h9Ȳ-=-- D^ӑ.]om 9i}gXP0"W,!cFm0_*X-|t\%EW4'>ss Q:&O@H)Om]bT' �U5@8� ?㘂Bk1dNm.>pL_N:Ӭ�Q>b?>AZX\+Zd<5ڇ8YXS<+鸑vn3g't_`v#^6*;?,tpV,* T~Ys. )yX@sl"bYOc#"4"}8ryS~*� _>gupBG-bA_5FN;n'7/OݺnX_g4u =DI-} NJ`bLJ49ُAp.ww>�-E}9f#k:I/+'Whކ\?ߥ8Aw"[z.0ѱbć N,F=|e}It=hvҘc3eqmk9d EZvb.h?0)P+c@v=҈Sz"5iG 8IuUsV}%P# av6Qewo+ �Vk� 1@|E 1淚LͪU )$GnVR㊏,z!ud<'2'xIĔ |688 uIo'`Aݿ몽qM{>v\(�;zj0<6dJA YUҎ7 3K4Y @,]uFtK "AǎS[G\g~4V,E籟Y=n.iohƺ?ř'ٲ*W~V}&%oADLT;)El'XmGZ (L;( .+dl{`/S@X@Q.یǡmg ~ /ZZ%&QD8RƎ[FM2vKcdYObH�dUgNlyfKm[v*7ңN )&M[?n˜vʖ6R/{aK|qIgtω �W2� 9'bD i�m|:Y=LAKf@& ;rM4O8(.j6΍y*, \ߟlK?�ͮTKssY+7|ѷ\bINz)kCϓIsOG# o Hym)rfpB4j- =G,WP{=tPry~‡�\pQx?"H)yċSo1Ciⅅk󙋯{i<󋦼72U[gcN%C~]UEpyad >A# TM,d?mu�k<FCBnC`3wT?V欬ȮoQ4tFMqA6q%UN3bf`uX(4#Ů<@YL9铰7L0iC6I4;#`ת]h{CU9IDƳ8@.cw%f_N(79vUgS2&�WՏ!�  `Zx�gh1tS`$=?Ιܦ0Ė{hUgvK ّQgɽ|adv3Ν@ {�bT,".�wi*IMj=`l*O=ɣQ'ʪZriLm۬զ4{Z߶wv*4xFcM3́Y5ALC):s_w0e=&Q>n U2g}A0bviԼ.alAԥ "0>lw)Ԛ"J'?`ZҐͱOZf=A9)%Ǟ3DžIFԠMK{>jtȣwٛVŠ|.l6C2}EcJO5^ح6 // =yņ$g6KqbZO ن!B_ vX%0ocR n)fjp~E[V(I$㫈cމ�W.�  }v Cܾ�+w(,&h]Rw|n ueܓ~5#Cb{@󳜃x#KlʍytIh@;)iGBa-ch0W#tx= pE2uyX8YӍ OPR( u&084!N Ֆ6V+&pk?,*ai_?'yRfN-n2izu7{_s+(0&Lgz/d2$C|�[75BW;6vLgQ@ ?FmXPCJn;wiza2Z!(A{xuR%1PM䲜U+TyU5I\nKJnlJE6$�8smݞROV(!EСmH<i5.%~FQ; ϶Yo H.4v~ RRL^;ߌk;J+�5[E8/y9Z Iham;D| 0 � !wq=ڛb2Cc�RY5 � 2Cc�R<qmE*\bsp!qLzRb(i/4!j"b8E,7X�@/kme qXy2kd/A[eI˪ݕ -:g gs>@:z#pʀmO^ӀރhrYH, F4@uL5R_l(B#PĊuVr2.Z |Ed)K<UCeв?L� &mZ?K<Dh)xuC!ܥeC|Z9H`p/ri�zC(/wTSbH}}L=: mɘ@o:P>3Z\15W{ cHnzZ_UyŲß_U8v矜nByA #3BB:b#շ73+ ==&J{r_&Neal H. Walfield <neal@pep.foundation>% �;    !wq=ڛb2Cc�RY6{ L� 2Cc�R$4 P뀁W M;8LtW-*ҹ,Vҿs0+Q3ԗLsbя߲ nީ[w-~pJZ\) #(y7t=wKKk Z}9AB#tRBD!8{=w'U58z![ī�Xop~<@?9/6K<Ottlq <wHQwO8tbz6rnzAl7lrDfY 4pNKt*P/9E 0X[F%ED/98mw cՉGC>RJO$?֪c)I4mQ{Ta%,gVL_fZK< mXcNYjegVKߊMu7<vOՅle|;;L&M jзDOFRwh5xnL5}:N3 �!뻉!vΟ PY6�  P� )W>(mAPtiD-x=B坎fZsbfbw-'OW(|ֻJ$ۭ/G`ۓ ٻ{v*TbOnK@nwf@ Y(n8儕?6nҚgceYH\-]cT3yhtyrЌb0xl@o]Hba6Zپ 9hN'Neal H. Walfield <neal@pep-project.org>% �;    !wq=ڛb2Cc�RY6| L� 2Cc�RH} (&*(.txc:ྪ!ѴL]<. Aa�j|ijwEyꓥ/|,mcw*a~v=xw:g ]judHnuX�1tlǩM06z(l)tDgϱd< A΁B'wqWy}\ŅgY5E;*=RIFp=.)L�yz4%&fO1nxFu;{s0:܌[zǪHO.*%8.*+JtqAu]et#뼊9 4, +5[rt˙O9|L.j5!BC<:feb1ܬ~/ٝǏzia-ͻB'| p+9=,m,LRd3 �!뻉!vΟ PY6 �  P#� ›銘Wt<NZ #(;gΉhAm*w] pN!0M<hxlK9ڀ]x ?Qj,[C)&W zUkfP*%9GʋF{swy`piYw=6Q]9VkBcPC%3UV lr#/źׁ|BJ8Miٝ `UyhBabWh U#�5Q$_|p|CxTsls_yAHxeYZvS2j[ױØ;?|4%߯š p-xc a!Л&d #$݀['yX2}H& ۨ9=[@S I~GP=LqW?2$c?rrw(eQ+9]:ߤ0 \.NBMenO]U�� �U# g�) 2Cc�R]  �U#� r#fx%(yΆ>H ܖ]Wk>-N$`$48b觶FRL[Pэ 4ڀ9R W6 փ玮d)~6mR#2C#?5dȏˤ>WX|ьk))`ZB1w$1Q#3Qz6M} dQ<\^U&v8c&jPb{10|iX #I'TJ+ɖx1 2٤'ELOOH ߅5cF€dQ|G/5E01uƐl="HS\n|zU f0 Q:# lP$PŦ"JE&Mb:ʏp ,_'N'Pkڛ3<"54m;XQlz3 f묟CQɻ[7qX%ΝFغfJ1Zԣ|Ef4bI29֠^)Ӵ oY\/9+M~%贐\%�]] h5NDu=sǪ ⅤzY<A#_s}qG" C&˥>g>' ]P i   4y cauQfU8ڐ^뎼 rLU} X%Hm"E^$8x0bq#-=Fʼn �X#  )]  �U#� r#fx%(yΆ>H ܖ]Wk>-N$`$48b觶FRL[Pэ 4ڀ9R W6 փ玮d)~6mR#2C#?5dȏˤ>WX|ьk))`ZB1w$1Q#3Qz6M} dQ<\^U&v8c&jPb{10|iX #I'TJ+ɖx1 2٤ 2Cc�R0?友rɳ~PFbJ=vj/nI {<\+}9ٚ/H0qJԚpd FE᳐k/ЦS10[[ܾ؉z/ǀvԧwK((V.lwdtNr%oC/mv`~3 HV+Vz1mĀcW iPϕ=E8ZzNdlkh<O$AE!&".tȻ'*|?a5/6@vXzpP ·ذ(Vx@lc9Swp%N7z3�DU3?cr5 btYq�DсSq/x7)~Vhxip D (j<V/Tj98Ne?P$Օ' U#�RGWJ$y&IMڌP=�,szz1IP}1>(TOѻner/&3[ScLT] }4 (3FC'8�}9}B06>)9 2"^2DWby8C:MSГdjlߏ3챆)>> SvtرuVТB #:!o8|/'CD�-`(f-3�� �U#  g�� 2Cc�Rم]qbON@6>Hܐ<bhȘ H/tDwk 0?T#uM ~  P; SUED1vV_'ԀBoO䪢=A5ǁ7XNJIܗ$;\�|-2<U.qL\1WiP: 0FnQfLM|XyI0hHG)RHӸ ͺEJNN&+-+^IŊϴ�(+@m| l zP3/'$7 :2Dm-koׁl, *rF{t#w]U):7`9rOoIɹ~yqQ- =x@`jnVxtG7,jG|Ă0Q6{{eh7™,v>ŌE � X# #� 2Cc�RS-`KsAN.) $Xt@F18iVȘY%䠠y"^ApJԛW(8)g<ܜg#$1�]'_e5ef;}GI~?fLE K6*I~XiIb\t؀@ffkjGL>47�_;B5dwEц0N@-ɇQ1/t~i`]١lهkO758.,{2wdӱU"TowI| -H:G"=qbk,)td.b5 ^k @Sr}*ISЦ$N%T 0QZ<eL)s3Kg^fF@ɦ�AҨOS7bP;-Z}y6͢_)Dg]Ϲ U#�֭�ݣ#x [s`T@IhZ>-Wp>- OkеH)AZ6G:?D P/?%+&e_Hon1~-W=Xf.y:6d`pm1l+* &N_(ob2 v }.\To(M U::k;L<w^WMM-*96ogOx{.W ?Oy\G[ uЌ1Fp<�� �U#  g�� 2Cc�Rd$#7ђ̠MLJhHT"m|k'rӗu4d̊eA"Zړ0R"*oj<L K9 _dXXf%L(RTx{nFB)h%Hh<c8JHYE͔p)Ek/XsNNP"PWAeqR7oǟQ| o9""BwSr!d#_V ,+~ zUh ^.sNT%(<<7 UƥH0k=S2ze;&~>/'D(/,H5nvm8:8wB�F(Hi.5nT-tዐl9[! I_18ukM<Xe۴6eB^yaoMyEfX8olOgV>trr$!4Г+}ow � X# � 2Cc�RrQy-c@ygճ1CWsk,L [4ykX/GpK]hϤ?uS|W3HNS~|+!Qp~)gC^Ji"jJNopvLrQ9 '�~*ud"$S瓵4DE+zd=&F~=Osb<8'}w &:˦u#0@"=XT %Rx\7ll%{wLR  K(&W=*Es@yALLDV*mŁz/%;,Ui6uuu݋u_7gE&rqh$,9?)6J )%2xH1Q8DcJEJ$0n[|G²L#㦫tYI)kMX![v=;wtNkQ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/keys/really-revoked-cert-0-public.pgp������������������������������0000644�0000000�0000000�00000003327�10461020230�0023773�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������]p  �}w%ߢ~gnw>&am?юEAQ&ҿ ䷏ysy"Te%g8}!."p{1SGQ�ۊkNDɊC@! #iCz>UJ ʽmVk%( !_%c~10=9/*%} 63>{8l\ƗVFCV~ڞa4p70;s-t+܋,&snbLRh^ߢ:W&dTEb$XK)g"IvRa Mj(R/(D>YcKԄ\'aɋ+Qd?5q!*U=HVIdZNK��+really revoked <really revoked@example.net> �8! 0�6x:^EY<{IM(J]p    � Y<{IM(JX gg 4@V x 0Fp) 4;d`U,//qP(lnE-٣ƣ/XEbX 0/$FClV$o6|o+�ܔ~Z OEkqJ7XcNր\=E;۞6j$ˁRlbȅdr\gN q /|lJ믻fno\o \ uZء/>%ӴA۫m\*YGq-`[ ݈0tOf~t`,B|6f "qI=A#W&z)J/uS4X4:[-ZB|P`+ڈ$ JObK>b]p  �Sb0?n7f\݊v)>%pΔW#C^T([ԓv4RZY0ؤ֎R[KHqA t !7MIHNm(K掜P e ; 4Ys…O~X_ :qYVÁ.|dj(ğ_5ZU$bI SQP)%>sMo^rIROĞ C<r hõpyo;+%[/B7iS A"ɟ藡 0)e7Uv i i+G6K *wv < f�� � ! 0�6x:^EY<{IM(J]p  � Y<{IM(J@ l{5Z_=_I3$Ⱥo|x,`wMZs<3t $ʇ}IY@ʲek[O^0X&#(*jk rUmfvH ޝv߯li4P0.Eٓ/Bۆ\SUm*OKIa)[Z7 -2f^B!IeW$@f;t܇c]�1(>16:|qb*T 1j~MK9jTj7pT$t@?\H|t}-59(#<$]H$gơmiws3>\J:J #o FRs*4n Gw9蓿���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/keys/really-revoked-cert-0-secret.pgp������������������������������0000644�0000000�0000000�00000007155�10461020230�0024005�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������X]p  �}w%ߢ~gnw>&am?юEAQ&ҿ ䷏ysy"Te%g8}!."p{1SGQ�ۊkNDɊC@! #iCz>UJ ʽmVk%( !_%c~10=9/*%} 63>{8l\ƗVFCV~ڞa4p70;s-t+܋,&snbLRh^ߢ:W&dTEb$XK)g"IvRa Mj(R/(D>YcKԄ\'aɋ+Qd?5q!*U=HVIdZNK��� Alݘ�Cs^'P0�CԼ <aF@Gk7 T x= J]eǎ [8sThm-~}5<L(>,S)s$ԠS,w|( j饿/] |Kw >@:)3,0{˧Zհ[GtQ, z98Уmg{6_t7,Km )ێ<:ЯOcnǝL$nmr&@ tK)*Ek]ppGbJR-1?_R+2!n`7c5}?qc6y$W8Cx dQ|gdXP>퇱aM}ɮN)C"682i> �7q؞B@rM|y!e % N 7&嫗$cxc=ɗ4tgK…i<|h$T(=|/Ż#X,uKAqml+7L}Vߙ*tUݽ ZQ3a!+S�"sk$a;p ^k%A;�OI/!kNj_,Ji*y:/D '~RY ƾ($BtRhcRK<{>L[Ê wRqз4=n#EcfvR&l43c=�uNyaOjYD J뒉H%̵AB+KLU6sOL@Ͻ�=Dn׮myӇk&wGc _~@2HH:*Wt&~8@0Ϥ<YeFﭵus0 sژ=mQ@ %%9at)Zk܉ch30aF/TQ$_NLn?0Z(\^,lď�yF }̴+really revoked <really revoked@example.net> �8! 0�6x:^EY<{IM(J]p    � Y<{IM(JX gg 4@V x 0Fp) 4;d`U,//qP(lnE-٣ƣ/XEbX 0/$FClV$o6|o+�ܔ~Z OEkqJ7XcNր\=E;۞6j$ˁRlbȅdr\gN q /|lJ믻fno\o \ uZء/>%ӴA۫m\*YGq-`[ ݈0tOf~t`,B|6f "qI=A#W&z)J/uS4X4:[-ZB|P`+ڈ$ JObK>bX]p  �Sb0?n7f\݊v)>%pΔW#C^T([ԓv4RZY0ؤ֎R[KHqA t !7MIHNm(K掜P e ; 4Ys…O~X_ :qYVÁ.|dj(ğ_5ZU$bI SQP)%>sMo^rIROĞ C<r hõpyo;+%[/B7iS A"ɟ藡 0)e7Uv i i+G6K *wv < f��� cNp|W Ucoĕgk54'Ў9+ ,/ukTT&7d܆ lrB놉>^m`q ovQBx7OCŊgR +>Tm&>Ռe..N ,4JϢʚkx]R<M4M pv,~Q,֢w2]R͵rmp1;-NQʐ0 \6/>.ܲIoHRowjP-+ J4 )St-GLҡ^A !( ƗC撀MIC< vi+pB4x'aP�\F-Uam|Bb.={Yki@ -0l dm L 1jji UW9Fg~o/s,μڪ=2H,I%%WF@R`'јZGʾ tE}̢>56Ut/rGIs* 3"̬�2t,ؽ 4u&c7NYɇNĝf GF#ͅ 'w@qz\=_QcWֽJjêHxڜP^y{*Z.o!xZ!k}sTA#t@OWYe74Nf ÒByyft&B/ �}s^>0]爇35x IP@K*1 H<㚎^lv9kz_>Q4Y=h.wciq3Md+诰Z%7C{V]өU› @ i u$E"4yB`hUjoX.ЁNJSjY=2FI?ûU /MȻ~b � ! 0�6x:^EY<{IM(J]p  � Y<{IM(J@ l{5Z_=_I3$Ⱥo|x,`wMZs<3t $ʇ}IY@ʲek[O^0X&#(*jk rUmfvH ޝv߯li4P0.Eٓ/Bۆ\SUm*OKIa)[Z7 -2f^B!IeW$@f;t܇c]�1(>16:|qb*T 1j~MK9jTj7pT$t@?\H|t}-59(#<$]H$gơmiws3>\J:J #o FRs*4n Gw9蓿�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/keys/really-revoked-cert-1-soft-revocation.pgp���������������������0000644�0000000�0000000�00000004257�10461020230�0025643�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������]p  �}w%ߢ~gnw>&am?юEAQ&ҿ ䷏ysy"Te%g8}!."p{1SGQ�ۊkNDɊC@! #iCz>UJ ʽmVk%( !_%c~10=9/*%} 63>{8l\ƗVFCV~ڞa4p70;s-t+܋,&snbLRh^ߢ:W&dTEb$XK)g"IvRa Mj(R/(D>YcKԄ\'aɋ+Qd?5q!*U=HVIdZNK��  �?! 0�6x:^EY<{IM(J]pJ!I don't use this key anymore...� Y<{IM(J ⬉ǎ1rNQ@[\F_9w9vVTwr^UC[dLĬ%t*`�Q@IB׵8�D}#Ȯ56eL:v%%0?,y"8e3Y6"0_ܟӇ DPiO⯞:2_D(OjS^oa :7Ke DE\.sZݾ&SmpT2}Y/%͉LH;RlfZ#oEN~M_#4VPetml0h|~z{ 6i=8~r F]_z ln5ɏpExVjeEHu Jy+really revoked <really revoked@example.net> �8! 0�6x:^EY<{IM(J]p    � Y<{IM(JX gg 4@V x 0Fp) 4;d`U,//qP(lnE-٣ƣ/XEbX 0/$FClV$o6|o+�ܔ~Z OEkqJ7XcNր\=E;۞6j$ˁRlbȅdr\gN q /|lJ믻fno\o \ uZء/>%ӴA۫m\*YGq-`[ ݈0tOf~t`,B|6f "qI=A#W&z)J/uS4X4:[-ZB|P`+ڈ$ JObK>b]p  �Sb0?n7f\݊v)>%pΔW#C^T([ԓv4RZY0ؤ֎R[KHqA t !7MIHNm(K掜P e ; 4Ys…O~X_ :qYVÁ.|dj(ğ_5ZU$bI SQP)%>sMo^rIROĞ C<r hõpyo;+%[/B7iS A"ɟ藡 0)e7Uv i i+G6K *wv < f�� � ! 0�6x:^EY<{IM(J]p  � Y<{IM(J@ l{5Z_=_I3$Ⱥo|x,`wMZs<3t $ʇ}IY@ʲek[O^0X&#(*jk rUmfvH ޝv߯li4P0.Eٓ/Bۆ\SUm*OKIa)[Z7 -2f^B!IeW$@f;t܇c]�1(>16:|qb*T 1j~MK9jTj7pT$t@?\H|t}-59(#<$]H$gơmiws3>\J:J #o FRs*4n Gw9蓿�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/keys/really-revoked-cert-2-new-self-sig.pgp������������������������0000644�0000000�0000000�00000003327�10461020230�0025017�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������]p  �}w%ߢ~gnw>&am?юEAQ&ҿ ䷏ysy"Te%g8}!."p{1SGQ�ۊkNDɊC@! #iCz>UJ ʽmVk%( !_%c~10=9/*%} 63>{8l\ƗVFCV~ڞa4p70;s-t+܋,&snbLRh^ߢ:W&dTEb$XK)g"IvRa Mj(R/(D>YcKԄ\'aɋ+Qd?5q!*U=HVIdZNK��+really revoked <really revoked@example.net> �8! 0�6x:^EY<{IM(J]p   � Y<{IM(Jˑ � U蹌eH*~)T p Rj,PE[~pnYmH#^{uJU/Tje >!(,CC qd2*ܒVWkRyϋvĞXh t8o~tuDЦ-MVƜQXiWY!̍wйhǩQpOLf"S}l7pyliE˭zNm3/| kN{!Vlq!!e#4g+<f;#nNW{Maʇb+J`'o$ժכMz)pK"րe_�o`T#\ 'ya]p  �Sb0?n7f\݊v)>%pΔW#C^T([ԓv4RZY0ؤ֎R[KHqA t !7MIHNm(K掜P e ; 4Ys…O~X_ :qYVÁ.|dj(ğ_5ZU$bI SQP)%>sMo^rIROĞ C<r hõpyo;+%[/B7iS A"ɟ藡 0)e7Uv i i+G6K *wv < f�� � ! 0�6x:^EY<{IM(J]p  � Y<{IM(J@ l{5Z_=_I3$Ⱥo|x,`wMZs<3t $ʇ}IY@ʲek[O^0X&#(*jk rUmfvH ޝv߯li4P0.Eٓ/Bۆ\SUm*OKIa)[Z7 -2f^B!IeW$@f;t܇c]�1(>16:|qb*T 1j~MK9jTj7pT$t@?\H|t}-59(#<$]H$gơmiws3>\J:J #o FRs*4n Gw9蓿���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/keys/really-revoked-cert-3-hard-revocation.pgp���������������������0000644�0000000�0000000�00000004234�10461020230�0025603�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������]p  �}w%ߢ~gnw>&am?юEAQ&ҿ ䷏ysy"Te%g8}!."p{1SGQ�ۊkNDɊC@! #iCz>UJ ʽmVk%( !_%c~10=9/*%} 63>{8l\ƗVFCV~ڞa4p70;s-t+܋,&snbLRh^ߢ:W&dTEb$XK)g"IvRa Mj(R/(D>YcKԄ\'aɋ+Qd?5q!*U=HVIdZNK��  �,! 0�6x:^EY<{IM(J]pcompromised!� Y<{IM(J " Zb%�w^QFiJʚ�?3JMINܷ(>qK%t$l,`OF|I*`0:8QDǮ1AZ}T]M͖绛4ɧ‹[yϝsJ?{m t GZg6tP@R MIdjy/ڮu, B2-rtRwO uYRSpUSĿ6h76郑Gɗ|@%HgxGoL&*G/fIP(s!yzw=_IA3Xly3 /@vK)Wk!h \W#9YmϾj( /ãW+!l+really revoked <really revoked@example.net> �8! 0�6x:^EY<{IM(J]p   � Y<{IM(Jˑ � U蹌eH*~)T p Rj,PE[~pnYmH#^{uJU/Tje >!(,CC qd2*ܒVWkRyϋvĞXh t8o~tuDЦ-MVƜQXiWY!̍wйhǩQpOLf"S}l7pyliE˭zNm3/| kN{!Vlq!!e#4g+<f;#nNW{Maʇb+J`'o$ժכMz)pK"րe_�o`T#\ 'ya]p  �Sb0?n7f\݊v)>%pΔW#C^T([ԓv4RZY0ؤ֎R[KHqA t !7MIHNm(K掜P e ; 4Ys…O~X_ :qYVÁ.|dj(ğ_5ZU$bI SQP)%>sMo^rIROĞ C<r hõpyo;+%[/B7iS A"ɟ藡 0)e7Uv i i+G6K *wv < f�� � ! 0�6x:^EY<{IM(J]p  � Y<{IM(J@ l{5Z_=_I3$Ⱥo|x,`wMZs<3t $ʇ}IY@ʲek[O^0X&#(*jk rUmfvH ޝv߯li4P0.Eٓ/Bۆ\SUm*OKIa)[Z7 -2f^B!IeW$@f;t܇c]�1(>16:|qb*T 1j~MK9jTj7pT$t@?\H|t}-59(#<$]H$gơmiws3>\J:J #o FRs*4n Gw9蓿��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/keys/really-revoked-cert-4-new-self-sig.pgp������������������������0000644�0000000�0000000�00000003327�10461020230�0025021�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������]p  �}w%ߢ~gnw>&am?юEAQ&ҿ ䷏ysy"Te%g8}!."p{1SGQ�ۊkNDɊC@! #iCz>UJ ʽmVk%( !_%c~10=9/*%} 63>{8l\ƗVFCV~ڞa4p70;s-t+܋,&snbLRh^ߢ:W&dTEb$XK)g"IvRa Mj(R/(D>YcKԄ\'aɋ+Qd?5q!*U=HVIdZNK��+really revoked <really revoked@example.net> �8! 0�6x:^EY<{IM(J]p:   � Y<{IM(J3 vg+aS{? aAsb .`� VO |WFO r>: +)!(H+R:mY 5߂"ԉ}jo#z0L}xIǿY{SG:c^HE}A8+#m΃V;黲1}bbKGu3X6?$PvK+ 7X<pb`TE,F9/晥x՚]y+i ):BnV4$mPFjG`΢_fo$1jsɞ?bҕ^r6 ]p  �Sb0?n7f\݊v)>%pΔW#C^T([ԓv4RZY0ؤ֎R[KHqA t !7MIHNm(K掜P e ; 4Ys…O~X_ :qYVÁ.|dj(ğ_5ZU$bI SQP)%>sMo^rIROĞ C<r hõpyo;+%[/B7iS A"ɟ藡 0)e7Uv i i+G6K *wv < f�� � ! 0�6x:^EY<{IM(J]p  � Y<{IM(J@ l{5Z_=_I3$Ⱥo|x,`wMZs<3t $ʇ}IY@ʲek[O^0X&#(*jk rUmfvH ޝv߯li4P0.Eٓ/Bۆ\SUm*OKIa)[Z7 -2f^B!IeW$@f;t܇c]�1(>16:|qb*T 1j~MK9jTj7pT$t@?\H|t}-59(#<$]H$gơmiws3>\J:J #o FRs*4n Gw9蓿���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/keys/really-revoked-subkey-0-public.pgp����������������������������0000644�0000000�0000000�00000003276�10461020230�0024343�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������]3 �e[m>r Rg̞qҷ(Q%I[rGy�nsǯΜ*@:m}J-wt3$b)IR6yg|x<Xc-$ŽS T9]h4 @>%}}%1)+u8>)DhV%M!-)P u28dty L@"[ce"x|_ug40bK ICmS[<!gtwP3:@_ mN^"$EC|`WY.aS t;2q_UV:�s;#z��Really Revoked Key �8!v s名]+@Xx]3   � +@Xx [\F9{v}=4au?<mW1;yׂ-/HrzfL�IGn\IZmAّSg i励Z vq E-y"vc,3tr~}$ㆴX]̀~ ˞\y[|L{K+р.mosgo2NܝC(OL c"k^Bry`@dTwҶtKgW}:\R ph\nlͻe?"& 8{Rm#sn$zptʬ sB۶($4|V4>nwM\7QUCt5mF]3 �wc]Jb(#-eFՕXU-w Zq>!�cRDSN#jWj7qU$`/mdS'>b <ȯXak ,?^nc<uxQ` Y�{zHqCYkaLYhM\B;pr:\e@FӋI>PC|i7/SoMzcOk_7cˍzuO)dW4Gh_p܊{̀iSQURrME+@ dtyԕ^Zͩ0* /V hu6R`mϴ8fMn6q)aU @CHDiԮ]eO /5~ y6M�� � !v s名]+@Xx]3 � +@Xx~ Tcad$+)BxK­gGM nHj*DQ0mj%B ~i+X|θ,EIq _D#Bs6h<7|. lD/4/8DXR4G #k[]@IQɉ9CO=zH{_[gq8G/?BK,{n}wD' zyreۃ4J2(5xeiD`GK Pw`_@4dγBCpji#Da,$ t"8r2@vlRĉ6 )pI'Y㖱ۑol7 =b'*#N\z����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/keys/really-revoked-subkey-0-secret.pgp����������������������������0000644�0000000�0000000�00000007124�10461020230�0024346�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������X]3 �e[m>r Rg̞qҷ(Q%I[rGy�nsǯΜ*@:m}J-wt3$b)IR6yg|x<Xc-$ŽS T9]h4 @>%}}%1)+u8>)DhV%M!-)P u28dty L@"[ce"x|_ug40bK ICmS[<!gtwP3:@_ mN^"$EC|`WY.aS t;2q_UV:�s;#z��� w1.u7=x|*?AAS{C_0by5D-Or8g M<j)o!|~2آhi?ERѡ#T|:MGլ"<ZLq8HsLḱWv%$upF ^ʗtm8 tdAB0PB$PvRгaߟt)4ι+Z0"T+B ֢vAL W,|_r- ]η瞔 4Qf,{u"a֪9c{ 0ݹ$KDz^pSn$N 2L/=L4M/ˆ|Q�jlEO ̡�Jw>d'9wOIy*ѩ�هk_(t/cuX[5\*?i|NN,g8_6]P:>qMuȃpQZI֯S3!�ӍؙSdp^TSzX~>ñNA%.Ɯ4?E>[,evsP:E>?Ցj)1dR' U[PmJ`�e-=m�Q]1vG-Q q2 B ؾT&plϳGv;1O9"2Rm)3c)_H4MӪW2x.HIfwOqw:8S HEB@ySO֑*xv 85EKAPϋrǐm.}4dOtZdu�,!d6PrI.,?h,r.s_ j^W\mQ3M0UQܡ�KY_p2K7̀ -474-S;Zf$LE@4t`ݰ+h+Hk1cW!־2<,,w|["T} r?g@ִReally Revoked Key �8!v s名]+@Xx]3   � +@Xx [\F9{v}=4au?<mW1;yׂ-/HrzfL�IGn\IZmAّSg i励Z vq E-y"vc,3tr~}$ㆴX]̀~ ˞\y[|L{K+р.mosgo2NܝC(OL c"k^Bry`@dTwҶtKgW}:\R ph\nlͻe?"& 8{Rm#sn$zptʬ sB۶($4|V4>nwM\7QUCt5mFX]3 �wc]Jb(#-eFՕXU-w Zq>!�cRDSN#jWj7qU$`/mdS'>b <ȯXak ,?^nc<uxQ` Y�{zHqCYkaLYhM\B;pr:\e@FӋI>PC|i7/SoMzcOk_7cˍzuO)dW4Gh_p܊{̀iSQURrME+@ dtyԕ^Zͩ0* /V hu6R`mϴ8fMn6q)aU @CHDiԮ]eO /5~ y6M��� Po.M{-CZ5;2ͱ±pWɿiPKPCuñ6LLUW3J `ba_լxF(A ߢY JwZ,.o~dr7y�*w,:쫽Uؐ6DUqv AQ:Q^=sVp63J(ٳR >`Ko p}$aCG;??F;ػyyGJi\wĻ}@FS=KE[ur0pLju?-T%\&po8~.&^GT;\YIi8xa�pW/ I@r]5}@cF83gfn F 7WvqMq}IF_<F- e63-[(qsPX.#yg@N>3pY2"M=h({ţjx`aQX8'_25!7SbNt[[�CVee55jDk"M%gշDKAMX"Ԩ)K($p < YYLB?WTpgs|Yqr~�}͠ {Tn)N<.hr:FJ@U#]*EDP%�Lu~2ib6iToD/I|PңwMW |Wȳ*m4"6sfxJ5.P'0JȤ)Ibn!4�_2Lz,rݤcYCrT@s}{uϖ!XCmʶPE譄~ZXiH{U"yb3 � !v s名]+@Xx]3 � +@Xx~ Tcad$+)BxK­gGM nHj*DQ0mj%B ~i+X|θ,EIq _D#Bs6h<7|. lD/4/8DXR4G #k[]@IQɉ9CO=zH{_[gq8G/?BK,{n}wD' zyreۃ4J2(5xeiD`GK Pw`_@4dγBCpji#Da,$ t"8r2@vlRĉ6 )pI'Y㖱ۑol7 =b'*#N\z��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/keys/really-revoked-subkey-1-soft-revocation.pgp�������������������0000644�0000000�0000000�00000004207�10461020230�0026203�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������]3 �e[m>r Rg̞qҷ(Q%I[rGy�nsǯΜ*@:m}J-wt3$b)IR6yg|x<Xc-$ŽS T9]h4 @>%}}%1)+u8>)DhV%M!-)P u28dty L@"[ce"x|_ug40bK ICmS[<!gtwP3:@_ mN^"$EC|`WY.aS t;2q_UV:�s;#z��Really Revoked Key �8!v s名]+@Xx]3   � +@Xx [\F9{v}=4au?<mW1;yׂ-/HrzfL�IGn\IZmAّSg i励Z vq E-y"vc,3tr~}$ㆴX]̀~ ˞\y[|L{K+р.mosgo2NܝC(OL c"k^Bry`@dTwҶtKgW}:\R ph\nlͻe?"& 8{Rm#sn$zptʬ sB۶($4|V4>nwM\7QUCt5mF]3 �wc]Jb(#-eFՕXU-w Zq>!�cRDSN#jWj7qU$`/mdS'>b <ȯXak ,?^nc<uxQ` Y�{zHqCYkaLYhM\B;pr:\e@FӋI>PC|i7/SoMzcOk_7cˍzuO)dW4Gh_p܊{̀iSQURrME+@ dtyԕ^Zͩ0* /V hu6R`mϴ8fMn6q)aU @CHDiԮ]eO /5~ y6M��( �0!v s名]+@Xx]6}Soft revocation.� +@Xx tS'Tl@Ah,hPz R'Q&+v֢%(㇏v}Ǥl`Or0t}VD?y]]UMnSKR%a7-V <(a$?Fp_c$YfK=Ƀht_jV<9/Cn# aJ)^5yc~%{a-?14P$I/ U,<StG;g/u.S|0" w#eH{ۋ 5]@ϵP>&&:d[j'=-|;Xtn p&$U 1\a&jo&p~ekI.9)?r7V\*+nq�P6ofkʖ JfQC>)} \oq͉ � !v s名]+@Xx]3 � +@Xx~ Tcad$+)BxK­gGM nHj*DQ0mj%B ~i+X|θ,EIq _D#Bs6h<7|. lD/4/8DXR4G #k[]@IQɉ9CO=zH{_[gq8G/?BK,{n}wD' zyreۃ4J2(5xeiD`GK Pw`_@4dγBCpji#Da,$ t"8r2@vlRĉ6 )pI'Y㖱ۑol7 =b'*#N\z�����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/keys/really-revoked-subkey-2-new-self-sig.pgp����������������������0000644�0000000�0000000�00000003276�10461020230�0025367�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������]3 �e[m>r Rg̞qҷ(Q%I[rGy�nsǯΜ*@:m}J-wt3$b)IR6yg|x<Xc-$ŽS T9]h4 @>%}}%1)+u8>)DhV%M!-)P u28dty L@"[ce"x|_ug40bK ICmS[<!gtwP3:@_ mN^"$EC|`WY.aS t;2q_UV:�s;#z��Really Revoked Key �8!v s名]+@Xx]3   � +@Xx [\F9{v}=4au?<mW1;yׂ-/HrzfL�IGn\IZmAّSg i励Z vq E-y"vc,3tr~}$ㆴX]̀~ ˞\y[|L{K+р.mosgo2NܝC(OL c"k^Bry`@dTwҶtKgW}:\R ph\nlͻe?"& 8{Rm#sn$zptʬ sB۶($4|V4>nwM\7QUCt5mF]3 �wc]Jb(#-eFՕXU-w Zq>!�cRDSN#jWj7qU$`/mdS'>b <ȯXak ,?^nc<uxQ` Y�{zHqCYkaLYhM\B;pr:\e@FӋI>PC|i7/SoMzcOk_7cˍzuO)dW4Gh_p܊{̀iSQURrME+@ dtyԕ^Zͩ0* /V hu6R`mϴ8fMn6q)aU @CHDiԮ]eO /5~ y6M�� �  !v s名]+@Xx]8e� +@Xxk  vunoTƽh\wjc3遐f @\RPLX9/Z Y΂]K>p֫_5"bVH+K;9rqՋdskk`L;pL)=#C|_)l7aaJi^9|M5} {_5SL5pi`nJ+ z"RoPHue0IZLQ"/:UGJ_$ v3 tNcHEo~ '420bxs1 >ty]UծY/y�蕘iL*U`FйܗP@/i<.MYcM$gTrg ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/keys/really-revoked-subkey-3-hard-revocation.pgp�������������������0000644�0000000�0000000�00000004206�10461020230�0026147�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������]3 �e[m>r Rg̞qҷ(Q%I[rGy�nsǯΜ*@:m}J-wt3$b)IR6yg|x<Xc-$ŽS T9]h4 @>%}}%1)+u8>)DhV%M!-)P u28dty L@"[ce"x|_ug40bK ICmS[<!gtwP3:@_ mN^"$EC|`WY.aS t;2q_UV:�s;#z��Really Revoked Key �8!v s名]+@Xx]3   � +@Xx [\F9{v}=4au?<mW1;yׂ-/HrzfL�IGn\IZmAّSg i励Z vq E-y"vc,3tr~}$ㆴX]̀~ ˞\y[|L{K+р.mosgo2NܝC(OL c"k^Bry`@dTwҶtKgW}:\R ph\nlͻe?"& 8{Rm#sn$zptʬ sB۶($4|V4>nwM\7QUCt5mF]3 �wc]Jb(#-eFՕXU-w Zq>!�cRDSN#jWj7qU$`/mdS'>b <ȯXak ,?^nc<uxQ` Y�{zHqCYkaLYhM\B;pr:\e@FӋI>PC|i7/SoMzcOk_7cˍzuO)dW4Gh_p܊{̀iSQURrME+@ dtyԕ^Zͩ0* /V hu6R`mϴ8fMn6q)aU @CHDiԮ]eO /5~ y6M��( �/!v s名]+@Xx]9AHard revocation� +@Xx具4 �pr,/L.66~a:ݳ9`!DC jЄֹ01GSw`aK*wG> /7 Y~̶PJs-10_hbƏC]!yv5X/iuЮl\#n �P'L[=J:<WOEO L^!x<R (H~y[8__+0Z㮳i ފ.N*媥v]'mb.4ɲqi׏&ΆsM` 1VX eE# l.@KzqF> W"&hDLq 38(zktqU\l4\VY �  !v s名]+@Xx]8e� +@Xxk  vunoTƽh\wjc3遐f @\RPLX9/Z Y΂]K>p֫_5"bVH+K;9rqՋdskk`L;pL)=#C|_)l7aaJi^9|M5} {_5SL5pi`nJ+ z"RoPHue0IZLQ"/:UGJ_$ v3 tNcHEo~ '420bxs1 >ty]UծY/y�蕘iL*U`FйܗP@/i<.MYcM$gTrg ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/keys/really-revoked-subkey-4-new-self-sig.pgp����������������������0000644�0000000�0000000�00000003276�10461020230�0025371�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������]3 �e[m>r Rg̞qҷ(Q%I[rGy�nsǯΜ*@:m}J-wt3$b)IR6yg|x<Xc-$ŽS T9]h4 @>%}}%1)+u8>)DhV%M!-)P u28dty L@"[ce"x|_ug40bK ICmS[<!gtwP3:@_ mN^"$EC|`WY.aS t;2q_UV:�s;#z��Really Revoked Key �8!v s名]+@Xx]3   � +@Xx [\F9{v}=4au?<mW1;yׂ-/HrzfL�IGn\IZmAّSg i励Z vq E-y"vc,3tr~}$ㆴX]̀~ ˞\y[|L{K+р.mosgo2NܝC(OL c"k^Bry`@dTwҶtKgW}:\R ph\nlͻe?"& 8{Rm#sn$zptʬ sB۶($4|V4>nwM\7QUCt5mF]3 �wc]Jb(#-eFՕXU-w Zq>!�cRDSN#jWj7qU$`/mdS'>b <ȯXak ,?^nc<uxQ` Y�{zHqCYkaLYhM\B;pr:\e@FӋI>PC|i7/SoMzcOk_7cˍzuO)dW4Gh_p܊{̀iSQURrME+@ dtyԕ^Zͩ0* /V hu6R`mϴ8fMn6q)aU @CHDiԮ]eO /5~ y6M�� �  !v s名]+@Xx]9o� +@Xx . �ϔ,�ۍC_x$HBĆ>ѝb㥓¸bc0-U(ⳤfi ZF:`-]ٙ!^%@l oyzf+!ڹ0Z;j>~M*=%X4 # :!4-O\c0qD݇39a%y<v."90,)*?t^W0hBa i'X<:iu".tCI"O#=< 0 !o1 _\,K8c>ÀD5#0}!:f0[ nxYU=!\jL^JšҘtAhh1EĚIr{TRwaC]����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/keys/really-revoked-user-attribute-0-public.pgp��������������������0000644�0000000�0000000�00000005313�10461020230�0026012�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������]d �JiW<5Aݶ$bgnt$ނÖ(+~rYt|#vbHeRnGL%U$MK4\Gn(@Q;$'qk@ñU*�Jf(Pa ~{ǷU_gzXE�FwD3!� ABn_Gr$B9DNQ$Bsd_k;k_LD])M9\.ac!}dnv6<qCscmOi@1rU;@"|~X?FbrUBl <$[,LCNU5BF1 !D&;FbVluw Cc ۓ��Really Revoked User Attribute �8!$BS]d   � St |8b8$d,rT_s+'<^x0܀ћJRQza?P?'!P\GQF12 p6#pPմtE8`/(Ȗ#ۜis[VٍK<5`Cg3=Rb.`.rWA "#$KbjToj$&Oa0,\H"Ypor &X _\jPyua yPsI6H--lcL5?#SMEO!m-5HL=$XⷘxIUMjL5`@t~%YniNoEgbo"Tbnl��������������JFIF�,,���Created with GIMP�C�     �C   ��������������������������������������� ���T�������������������������������������?������������������?�������������������?�������������������?!� �����������������������?������������������?�������������������?ى �8!$BS]d   � S "'_ʛw͹7;u#n+ϰia/f,/ήpȨ7h-VB5T¬݆b XK�_&^^ZȟMHOA.G~(`EhAO8э9|(S<c`=8ΔCbNT>3Ɏ͐|ONX)7[ϵ6Ap,G4�r@"^R*JZ)rQYtF�שi%ҔE~uœn]n׋jIy:kh'L-CEF\ވV}{>!Nm`Xzê$ 8x{8)s7գ,KSpu] ^/%,XIʘ!jct(@l&3s6elr]d �̐kJsj:Y{ ]Ni*j}Gl9IIFWb5YA.u(l *Ox#ҡ̎ϵ jr^kB)!wE%UxnrnJ^'c']N2N9#Nnש@cqoDi,n4Onh"q@;U,u촺܋:5 c6sПy0=wa~bED9y[" EUZÀ.Y|>,їX6Exj;Yt-+9ܧ|f0+[o &t+ 뗡u+v396Cn|6m=ξD$0mwK&waeG&vBlTh\A؁nLY[_ �� � !$BS]d � S aלStuv[2>Q'=l!Yk J{ 8[hEt v59j1#Z4 O ݗP}Zdg+<PfA@Kl jz$ F'44vv;+'Lw O NӳEuVIq ?sgMY\Li+27W:g6xUÇXT]3mH62WGc`yp4Rʗ*c<>t\I/$\}d0eP({x8p$&8 1D"T9P\i ދ&6N͈4^>#W���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/keys/really-revoked-user-attribute-0-secret.pgp��������������������0000644�0000000�0000000�00000011141�10461020230�0026015�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������X]d �JiW<5Aݶ$bgnt$ނÖ(+~rYt|#vbHeRnGL%U$MK4\Gn(@Q;$'qk@ñU*�Jf(Pa ~{ǷU_gzXE�FwD3!� ABn_Gr$B9DNQ$Bsd_k;k_LD])M9\.ac!}dnv6<qCscmOi@1rU;@"|~X?FbrUBl <$[,LCNU5BF1 !D&;FbVluw Cc ۓ��� A_KO@8 K3?aH( д3 ֘Pd(D'o:JJg̭/Sh; ɍs4Vl@djq-]P{s]F^iO�U,4xKOa|ޫnP3ҟU A'톡 u8ai" #KP4UP2 sF.Ѓ 5nb/30u jAwA^4ާz.پ#g1I[Gfx-@"qc՞pF^tVGe.v zpoU\츊xG �Mic HNfz6.J @x<i T'ffU.b oKˌ/j6n�4Z%wCw bCC,)KW<qJT.v;^ФU_aNߊ:||49ũeS+zâ%2q?Uy?c,"4'tDE`t$�{uoV09sڙZMBF~.;x5tk>6ٷza=�8C?a߁{hζų7q1&W;h46 lESڞwA)�o_O.^^y,IڞD3 qiP]"t=枷$ֽׄ1В23^{苠 MAsY=Ֆ~鰭92!9*O,67~Z̢I솚㚉bA!j9 MTBs%1WƢ3az+Z@6gXb)L-㧝j- hӽn]hcbx{KרS.>%)NԸ<4`LH/ϳQ6Di0[#<v�0Iӧ/H8w0Zt5lReally Revoked User Attribute �8!$BS]d   � St |8b8$d,rT_s+'<^x0܀ћJRQza?P?'!P\GQF12 p6#pPմtE8`/(Ȗ#ۜis[VٍK<5`Cg3=Rb.`.rWA "#$KbjToj$&Oa0,\H"Ypor &X _\jPyua yPsI6H--lcL5?#SMEO!m-5HL=$XⷘxIUMjL5`@t~%YniNoEgbo"Tbnl��������������JFIF�,,���Created with GIMP�C�     �C   ��������������������������������������� ���T�������������������������������������?������������������?�������������������?�������������������?!� �����������������������?������������������?�������������������?ى �8!$BS]d   � S "'_ʛw͹7;u#n+ϰia/f,/ήpȨ7h-VB5T¬݆b XK�_&^^ZȟMHOA.G~(`EhAO8э9|(S<c`=8ΔCbNT>3Ɏ͐|ONX)7[ϵ6Ap,G4�r@"^R*JZ)rQYtF�שi%ҔE~uœn]n׋jIy:kh'L-CEF\ވV}{>!Nm`Xzê$ 8x{8)s7գ,KSpu] ^/%,XIʘ!jct(@l&3s6elrX]d �̐kJsj:Y{ ]Ni*j}Gl9IIFWb5YA.u(l *Ox#ҡ̎ϵ jr^kB)!wE%UxnrnJ^'c']N2N9#Nnש@cqoDi,n4Onh"q@;U,u촺܋:5 c6sПy0=wa~bED9y[" EUZÀ.Y|>,їX6Exj;Yt-+9ܧ|f0+[o &t+ 뗡u+v396Cn|6m=ξD$0mwK&waeG&vBlTh\A؁nLY[_ ��� &j0ܣV <OɔFՔ<uTZO˜qzTXR;g]Q-rf`O;QSk/679i4|g±cmLyWk)T۟{+_ VҖ%MxK<Lm3?MtBT͋ {X6KW3Y/o ۓ 0P쳝Վּ_4`SLḥDdcGFR&8pEhb1n 쬬Swj-`Wg¤Nnc5f s'nSLus h_<H.dPOǣ�ɢP|6ޕb=15ENsK7bUwTvm>P�ʨz$i?,D_Zv@g]h~�ۀU?SآmBUu_2K1;v[^j4঱Pvs܁Vb&d-Pj%hAƅWG3Y?Pb?vH&�j9e9B ݵtTgE#m$e> : 8�8xL@l"ԑ4=A'PA8QRdTG)G?E `}[T޷!e�qZu`hIAvVɀoKψqwm˽=:-_ߎINw><#M*ͫ.I/F^q8]J<Lk{_;Z:[^H7QG.b P^ݨ!ڗ<R7)=$'BxW.թ �;V9 SeÙ 7+|^I\RD!)#G~Z7OAʎҵY@0'u؉aj吉 � !$BS]d � S aלStuv[2>Q'=l!Yk J{ 8[hEt v59j1#Z4 O ݗP}Zdg+<PfA@Kl jz$ F'44vv;+'Lw O NӳEuVIq ?sgMY\Li+27W:g6xUÇXT]3mH62WGc`yp4Rʗ*c<>t\I/$\}d0eP({x8p$&8 1D"T9P\i ދ&6N͈4^>#W�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/keys/really-revoked-user-attribute-1-soft-revocation.pgp�����������0000644�0000000�0000000�00000006223�10461020230�0027660�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������]d �JiW<5Aݶ$bgnt$ނÖ(+~rYt|#vbHeRnGL%U$MK4\Gn(@Q;$'qk@ñU*�Jf(Pa ~{ǷU_gzXE�FwD3!� ABn_Gr$B9DNQ$Bsd_k;k_LD])M9\.ac!}dnv6<qCscmOi@1rU;@"|~X?FbrUBl <$[,LCNU5BF1 !D&;FbVluw Cc ۓ��Really Revoked User Attribute �8!$BS]d   � St |8b8$d,rT_s+'<^x0܀ћJRQza?P?'!P\GQF12 p6#pPմtE8`/(Ȗ#ۜis[VٍK<5`Cg3=Rb.`.rWA "#$KbjToj$&Oa0,\H"Ypor &X _\jPyua yPsI6H--lcL5?#SMEO!m-5HL=$XⷘxIUMjL5`@t~%YniNoEgbo"Tbnl��������������JFIF�,,���Created with GIMP�C�     �C   ��������������������������������������� ���T�������������������������������������?������������������?�������������������?�������������������?!� �����������������������?������������������?�������������������?ى0 �/!$BS]e  Soft revocation� S 5@7-s(o;B_Şկ H"W$٧Z6 F e"1Q%IySqk6ōAPɃtzClj6\?!. Vu(49 ̈BrFiҥ# C1-dp6찬-Ch)Z=a mNq >< 1|ѹtw g 3=a"jL#jDyQ\Dx2>0:f8;-UxQPZtNT`GHЏ-E7Ȟd3ܾAn(#ⳎZ)1G4fAVpL8g&? մ4*9q Hy[O˻M �8!$BS]d   � S "'_ʛw͹7;u#n+ϰia/f,/ήpȨ7h-VB5T¬݆b XK�_&^^ZȟMHOA.G~(`EhAO8э9|(S<c`=8ΔCbNT>3Ɏ͐|ONX)7[ϵ6Ap,G4�r@"^R*JZ)rQYtF�שi%ҔE~uœn]n׋jIy:kh'L-CEF\ވV}{>!Nm`Xzê$ 8x{8)s7գ,KSpu] ^/%,XIʘ!jct(@l&3s6elr]d �̐kJsj:Y{ ]Ni*j}Gl9IIFWb5YA.u(l *Ox#ҡ̎ϵ jr^kB)!wE%UxnrnJ^'c']N2N9#Nnש@cqoDi,n4Onh"q@;U,u촺܋:5 c6sПy0=wa~bED9y[" EUZÀ.Y|>,їX6Exj;Yt-+9ܧ|f0+[o &t+ 뗡u+v396Cn|6m=ξD$0mwK&waeG&vBlTh\A؁nLY[_ �� � !$BS]d � S aלStuv[2>Q'=l!Yk J{ 8[hEt v59j1#Z4 O ݗP}Zdg+<PfA@Kl jz$ F'44vv;+'Lw O NӳEuVIq ?sgMY\Li+27W:g6xUÇXT]3mH62WGc`yp4Rʗ*c<>t\I/$\}d0eP({x8p$&8 1D"T9P\i ދ&6N͈4^>#W�����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/keys/really-revoked-user-attribute-2-new-self-sig.pgp��������������0000644�0000000�0000000�00000010225�10461020230�0027034�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������]d �JiW<5Aݶ$bgnt$ނÖ(+~rYt|#vbHeRnGL%U$MK4\Gn(@Q;$'qk@ñU*�Jf(Pa ~{ǷU_gzXE�FwD3!� ABn_Gr$B9DNQ$Bsd_k;k_LD])M9\.ac!}dnv6<qCscmOi@1rU;@"|~X?FbrUBl <$[,LCNU5BF1 !D&;FbVluw Cc ۓ��Really Revoked User Attribute �8!$BS]d   � St |8b8$d,rT_s+'<^x0܀ћJRQza?P?'!P\GQF12 p6#pPմtE8`/(Ȗ#ۜis[VٍK<5`Cg3=Rb.`.rWA "#$KbjToj$&Oa0,\H"Ypor &X _\jPyua yPsI6H--lcL5?#SMEO!m-5HL=$XⷘxIUMjL5`@t~%YniNoEgbo"Tbnl��������������JFIF�,,���Created with GIMP�C�     �C   ��������������������������������������� ���T�������������������������������������?������������������?�������������������?�������������������?!� �����������������������?������������������?�������������������?ى0 �/!$BS]e  Soft revocation� S 5@7-s(o;B_Şկ H"W$٧Z6 F e"1Q%IySqk6ōAPɃtzClj6\?!. Vu(49 ̈BrFiҥ# C1-dp6찬-Ch)Z=a mNq >< 1|ѹtw g 3=a"jL#jDyQ\Dx2>0:f8;-UxQPZtNT`GHЏ-E7Ȟd3ܾAn(#ⳎZ)1G4fAVpL8g&? մ4*9q Hy[O˻M �8!$BS]d   � S "'_ʛw͹7;u#n+ϰia/f,/ήpȨ7h-VB5T¬݆b XK�_&^^ZȟMHOA.G~(`EhAO8э9|(S<c`=8ΔCbNT>3Ɏ͐|ONX)7[ϵ6Ap,G4�r@"^R*JZ)rQYtF�שi%ҔE~uœn]n׋jIy:kh'L-CEF\ވV}{>!Nm`Xzê$ 8x{8)s7գ,KSpu] ^/%,XIʘ!jct(@l&3s6elrnl��������������JFIF�,,���Created with GIMP�C�     �C   ��������������������������������������� ���T�������������������������������������?������������������?�������������������?�������������������?!� �����������������������?������������������?�������������������?ى �8!$BS]e   � S7 kG% HYx {J(Kl->OQr]wAC&p: "Zl5OR̞4^!ݳW3:%}jBvjSfdX5<p-֦{C` o5OH0LKk>ndo{kުg-F.ugUh_``]e9=-Ls[rF]Oѳ,picu/$yD;q6Jc*AN=.( #yK(iigxgV6)&%k$sԖuša}9+jm3#~i?=M0^|#H%1$`-[PG6QZ+d`}d9n' 6dOolB]d �̐kJsj:Y{ ]Ni*j}Gl9IIFWb5YA.u(l *Ox#ҡ̎ϵ jr^kB)!wE%UxnrnJ^'c']N2N9#Nnש@cqoDi,n4Onh"q@;U,u촺܋:5 c6sПy0=wa~bED9y[" EUZÀ.Y|>,їX6Exj;Yt-+9ܧ|f0+[o &t+ 뗡u+v396Cn|6m=ξD$0mwK&waeG&vBlTh\A؁nLY[_ �� � !$BS]d � S aלStuv[2>Q'=l!Yk J{ 8[hEt v59j1#Z4 O ݗP}Zdg+<PfA@Kl jz$ F'44vv;+'Lw O NӳEuVIq ?sgMY\Li+27W:g6xUÇXT]3mH62WGc`yp4Rʗ*c<>t\I/$\}d0eP({x8p$&8 1D"T9P\i ދ&6N͈4^>#W���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/keys/really-revoked-user-attribute-3-hard-revocation.pgp�����������0000644�0000000�0000000�00000010054�10461020230�0027622�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������]d �JiW<5Aݶ$bgnt$ނÖ(+~rYt|#vbHeRnGL%U$MK4\Gn(@Q;$'qk@ñU*�Jf(Pa ~{ǷU_gzXE�FwD3!� ABn_Gr$B9DNQ$Bsd_k;k_LD])M9\.ac!}dnv6<qCscmOi@1rU;@"|~X?FbrUBl <$[,LCNU5BF1 !D&;FbVluw Cc ۓ��Really Revoked User Attribute �8!$BS]d   � St |8b8$d,rT_s+'<^x0܀ћJRQza?P?'!P\GQF12 p6#pPմtE8`/(Ȗ#ۜis[VٍK<5`Cg3=Rb.`.rWA "#$KbjToj$&Oa0,\H"Ypor &X _\jPyua yPsI6H--lcL5?#SMEO!m-5HL=$XⷘxIUMjL5`@t~%YniNoEgbo"Tbnl��������������JFIF�,,���Created with GIMP�C�     �C   ��������������������������������������� ���T�������������������������������������?������������������?�������������������?�������������������?!� �����������������������?������������������?�������������������?ى0 �/!$BS]e�Hard revocation� S" �ǩ"!Nv;u 'N$ AQ8BSz}ىGǸ1/xخ;R;{3sΙN"*!RW组4$|6:DNm xI Rj�LA1hSCVpל/j"<f%*N8qsVi,�`q@v2H}RT߳w#BUtּn QXIፗASʠmOEܘ( i6ź-c ] 67$@F$'G]݃k.g6VMnSs$3k+֟g"4"*8L}&P.Q%0K� �8!$BS]e   � S7 kG% HYx {J(Kl->OQr]wAC&p: "Zl5OR̞4^!ݳW3:%}jBvjSfdX5<p-֦{C` o5OH0LKk>ndo{kުg-F.ugUh_``]e9=-Ls[rF]Oѳ,picu/$yD;q6Jc*AN=.( #yK(iigxgV6)&%k$sԖuša}9+jm3#~i?=M0^|#H%1$`-[PG6QZ+d`}d9n' 6dOolB0 �/!$BS]e  Soft revocation� S 5@7-s(o;B_Şկ H"W$٧Z6 F e"1Q%IySqk6ōAPɃtzClj6\?!. Vu(49 ̈BrFiҥ# C1-dp6찬-Ch)Z=a mNq >< 1|ѹtw g 3=a"jL#jDyQ\Dx2>0:f8;-UxQPZtNT`GHЏ-E7Ȟd3ܾAn(#ⳎZ)1G4fAVpL8g&? մ4*9q Hy[O˻M �8!$BS]d   � S "'_ʛw͹7;u#n+ϰia/f,/ήpȨ7h-VB5T¬݆b XK�_&^^ZȟMHOA.G~(`EhAO8э9|(S<c`=8ΔCbNT>3Ɏ͐|ONX)7[ϵ6Ap,G4�r@"^R*JZ)rQYtF�שi%ҔE~uœn]n׋jIy:kh'L-CEF\ވV}{>!Nm`Xzê$ 8x{8)s7գ,KSpu] ^/%,XIʘ!jct(@l&3s6elr]d �̐kJsj:Y{ ]Ni*j}Gl9IIFWb5YA.u(l *Ox#ҡ̎ϵ jr^kB)!wE%UxnrnJ^'c']N2N9#Nnש@cqoDi,n4Onh"q@;U,u촺܋:5 c6sПy0=wa~bED9y[" EUZÀ.Y|>,їX6Exj;Yt-+9ܧ|f0+[o &t+ 뗡u+v396Cn|6m=ξD$0mwK&waeG&vBlTh\A؁nLY[_ �� � !$BS]d � S aלStuv[2>Q'=l!Yk J{ 8[hEt v59j1#Z4 O ݗP}Zdg+<PfA@Kl jz$ F'44vv;+'Lw O NӳEuVIq ?sgMY\Li+27W:g6xUÇXT]3mH62WGc`yp4Rʗ*c<>t\I/$\}d0eP({x8p$&8 1D"T9P\i ދ&6N͈4^>#W������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/keys/really-revoked-user-attribute-4-new-self-sig.pgp��������������0000644�0000000�0000000�00000012056�10461020230�0027042�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������]d �JiW<5Aݶ$bgnt$ނÖ(+~rYt|#vbHeRnGL%U$MK4\Gn(@Q;$'qk@ñU*�Jf(Pa ~{ǷU_gzXE�FwD3!� ABn_Gr$B9DNQ$Bsd_k;k_LD])M9\.ac!}dnv6<qCscmOi@1rU;@"|~X?FbrUBl <$[,LCNU5BF1 !D&;FbVluw Cc ۓ��Really Revoked User Attribute �8!$BS]d   � St |8b8$d,rT_s+'<^x0܀ћJRQza?P?'!P\GQF12 p6#pPմtE8`/(Ȗ#ۜis[VٍK<5`Cg3=Rb.`.rWA "#$KbjToj$&Oa0,\H"Ypor &X _\jPyua yPsI6H--lcL5?#SMEO!m-5HL=$XⷘxIUMjL5`@t~%YniNoEgbo"Tbnl��������������JFIF�,,���Created with GIMP�C�     �C   ��������������������������������������� ���T�������������������������������������?������������������?�������������������?�������������������?!� �����������������������?������������������?�������������������?ى0 �/!$BS]e�Hard revocation� S" �ǩ"!Nv;u 'N$ AQ8BSz}ىGǸ1/xخ;R;{3sΙN"*!RW组4$|6:DNm xI Rj�LA1hSCVpל/j"<f%*N8qsVi,�`q@v2H}RT߳w#BUtּn QXIፗASʠmOEܘ( i6ź-c ] 67$@F$'G]݃k.g6VMnSs$3k+֟g"4"*8L}&P.Q%0K� �8!$BS]e   � S7 kG% HYx {J(Kl->OQr]wAC&p: "Zl5OR̞4^!ݳW3:%}jBvjSfdX5<p-֦{C` o5OH0LKk>ndo{kުg-F.ugUh_``]e9=-Ls[rF]Oѳ,picu/$yD;q6Jc*AN=.( #yK(iigxgV6)&%k$sԖuša}9+jm3#~i?=M0^|#H%1$`-[PG6QZ+d`}d9n' 6dOolB0 �/!$BS]e  Soft revocation� S 5@7-s(o;B_Şկ H"W$٧Z6 F e"1Q%IySqk6ōAPɃtzClj6\?!. Vu(49 ̈BrFiҥ# C1-dp6찬-Ch)Z=a mNq >< 1|ѹtw g 3=a"jL#jDyQ\Dx2>0:f8;-UxQPZtNT`GHЏ-E7Ȟd3ܾAn(#ⳎZ)1G4fAVpL8g&? մ4*9q Hy[O˻M �8!$BS]d   � S "'_ʛw͹7;u#n+ϰia/f,/ήpȨ7h-VB5T¬݆b XK�_&^^ZȟMHOA.G~(`EhAO8э9|(S<c`=8ΔCbNT>3Ɏ͐|ONX)7[ϵ6Ap,G4�r@"^R*JZ)rQYtF�שi%ҔE~uœn]n׋jIy:kh'L-CEF\ވV}{>!Nm`Xzê$ 8x{8)s7գ,KSpu] ^/%,XIʘ!jct(@l&3s6elrnl��������������JFIF�,,���Created with GIMP�C�     �C   ��������������������������������������� ���T�������������������������������������?������������������?�������������������?�������������������?!� �����������������������?������������������?�������������������?ى �8!$BS]e   � S< s@og_(Pm Hh'MYf}:<D^14G܉JWaa_[Y"]Nswd3%eBRSD!$.|qΩd\J/MRjQg Г ޥAeۍ_G b[~ۀ}wtz<?(t'.(F$7n@�? 8b=z; ?,0.۾O@7uKb!# w1/| $5D֝9/ֿip bk<6ҍWId$LjQ(!:Tv|<kFqL;Ljܐ:C'lWR7Ss By"gL^&d'Tzls> w]d �̐kJsj:Y{ ]Ni*j}Gl9IIFWb5YA.u(l *Ox#ҡ̎ϵ jr^kB)!wE%UxnrnJ^'c']N2N9#Nnש@cqoDi,n4Onh"q@;U,u촺܋:5 c6sПy0=wa~bED9y[" EUZÀ.Y|>,їX6Exj;Yt-+9ܧ|f0+[o &t+ 뗡u+v396Cn|6m=ξD$0mwK&waeG&vBlTh\A؁nLY[_ �� � !$BS]d � S aלStuv[2>Q'=l!Yk J{ 8[hEt v59j1#Z4 O ݗP}Zdg+<PfA@Kl jz$ F'44vv;+'Lw O NӳEuVIq ?sgMY\Li+27W:g6xUÇXT]3mH62WGc`yp4Rʗ*c<>t\I/$\}d0eP({x8p$&8 1D"T9P\i ދ&6N͈4^>#W����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/keys/really-revoked-userid-0-public.pgp����������������������������0000644�0000000�0000000�00000004217�10461020230�0024330�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������]|c �:dL. 7?t zО1EZrV?I)t<)YwOx;FrW-CYU(:my:K+t焓T jõ|‹]~Nq9]UY%^!ft}δͨ~J[Xy~<@H( Z5A1]B_GZ"?o0pGK)#ieN~n bnYIJc&ۑ5T^"%jLӰd\sC"#;� sdsr ev廜u軄o�~'=c9 q/~_i.{" M[!�� Slim Shady �8!V#-\kcJ}%]|c   � }% �4lCnjb4e;e"zѸf1d=Ŭ=�Nɬz&Hy;.hzٱH!NF\VptA"JꍪLTn_ Y&ȭ&ȫkͦ6P9 {3>oJݴRW*i|�5Ԭb#3Q_[<DZ*m9Qwb-|{pdXȰ مb]k5!%zkfYgywDx@ҁ8a9zUObGFdIIZ_? X}4 7^dK.ThɈԎ^ڀ,8\mxY/t!7BPv/gEminem �8!V#-\kcJ}%]|   � }%J- �x*ߢV mX((:`E^W\CmƖ޵,lƻi:&jbw[; auÄNvOeI V//R[)I>9< A} !458/#ʱuZ=8T^+p_U1bD.k j ~ҮR-qTxYnWC Zrl<rGDJOAG5bd\+Ն! >.<QX!<Y,ֻxLJٳyxw(A�a�^M9͒ cB(C#i0[Ċfz۫`z$ ͩě] *ZEù]|c �ݓX(@ZA<x €8CiK$nZYֲKn'T% ߿n< EOsisg,wu�8|wC>LqyQۙt/09[UZC|z4 ;CtWqؕ8ebLb?+ΞFd''hps\M󨜻$V4[_wl?ޛ +T>xB΁tmd^[(.8aL*$}egg9- .'3<pfOL==M%6%}o뺚li}D M#r ϙ 15*Q==ۛ&Jp$=!ٸw`\b�� � !V#-\kcJ}%]|c � }%e �YJA^ 1wQw^m.{48I>_@ ׬lҜ^0~gvmE7۬,=n>:\j8rS~%`ȇ%iv|Ol+*HW, -^�F DB(;nCbbR#&!J޿㹎pѫNoT8T6Eo'.G֝O7Eu y *gSc%"~ٞSz7O3]+|^<j#M*#S;#,u:SM 2IT %dx?2gj+z�rO^Y0̾b%zK3-YsV=kG52欩���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/keys/really-revoked-userid-0-secret.pgp����������������������������0000644�0000000�0000000�00000010045�10461020230�0024333�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������X]|c �:dL. 7?t zО1EZrV?I)t<)YwOx;FrW-CYU(:my:K+t焓T jõ|‹]~Nq9]UY%^!ft}δͨ~J[Xy~<@H( Z5A1]B_GZ"?o0pGK)#ieN~n bnYIJc&ۑ5T^"%jLӰd\sC"#;� sdsr ev廜u軄o�~'=c9 q/~_i.{" M[!��� hJZlDSg>'HHǵhd/Gw@=d=K`brMKnve1)7?Zw8t C9Q Y_RMd(q6E*er P+@$6kӏ P?V`zWez3B{R$Mk5;oD܀hWk.Wh+W7 nUir g>) :%/W(~,o"3ZqFOW]SA,,x"zi[Hh8ޘ8h2⼁C육;UkJ]RZ?CY�YJKz`WoKl 31+<g6~!{bV3~{uȃ`(�JdV C DN:cHl|{[F=sdD+=t-Sý?=wܺ+֭C[8Nc.aخ`pv"Łݒf-8T^W"D1Z: I%eT!ϭQ)7=U7@ɜ;,Vߞ|ߣkAXKoN|\q�2be_4p7O�D+ULP0RH9?6goi'Y_W,x]qIkx(}pM"~NHul#qLaٰXiono:$C bc f:c"%V spD֨t@=0E %tM߿_ {�B8$ջ>/ VygT̡-^ Msp2TES%j{`IGVz#vӺ sT1d7Khqr*nEkU}YrN`AnY\ą}g6>6^h'|Z:~*t .!t&d>)uGeqٴ Slim Shady �8!V#-\kcJ}%]|c   � }% �4lCnjb4e;e"zѸf1d=Ŭ=�Nɬz&Hy;.hzٱH!NF\VptA"JꍪLTn_ Y&ȭ&ȫkͦ6P9 {3>oJݴRW*i|�5Ԭb#3Q_[<DZ*m9Qwb-|{pdXȰ مb]k5!%zkfYgywDx@ҁ8a9zUObGFdIIZ_? X}4 7^dK.ThɈԎ^ڀ,8\mxY/t!7BPv/gEminem �8!V#-\kcJ}%]|   � }%J- �x*ߢV mX((:`E^W\CmƖ޵,lƻi:&jbw[; auÄNvOeI V//R[)I>9< A} !458/#ʱuZ=8T^+p_U1bD.k j ~ҮR-qTxYnWC Zrl<rGDJOAG5bd\+Ն! >.<QX!<Y,ֻxLJٳyxw(A�a�^M9͒ cB(C#i0[Ċfz۫`z$ ͩě] *ZEÝX]|c �ݓX(@ZA<x €8CiK$nZYֲKn'T% ߿n< EOsisg,wu�8|wC>LqyQۙt/09[UZC|z4 ;CtWqؕ8ebLb?+ΞFd''hps\M󨜻$V4[_wl?ޛ +T>xB΁tmd^[(.8aL*$}egg9- .'3<pfOL==M%6%}o뺚li}D M#r ϙ 15*Q==ۛ&Jp$=!ٸw`\b��� m̐Xn_"6Τ0,ޛc(ZO.2TEU cPS*^mxqՉ[M~W >3Ȭ'|^ds"j, 0*r} r@ĻHoC Mu8aj;W-35J?>ncÐ`?+I�Wc J)Oq A-V1s tldVBxDxKwqäIٖC&Mƥۙ77ic51f , MAa#Y]Z@b.j0u@G`\C2TP\<dd:1niDUqJs >�UL9N 3}WTlx4V0nũMk uY%.E <cS̾? j 3PATZD54JhƄ;Jo@Cқ>CQ>)Hd "H@[k#^pE6Qo5qWjݫ7c� .,Y^MVDgowmg=N.zƕj \ rކB'ه2{n0zX  vgᖲ6�$ JK|-vflJBMSqP` ]5_|B{r96S>ɀoB~[dϳX,޾;eȨ,3*cPd7g'TjD!-uM{{7ɧO7 H{^*+EBE;7)e !ShTxZ[\ur$s%}X,~lR֧$9{c^2ef.\`μEWRK&aR7D %B IaXsێ � !V#-\kcJ}%]|c � }%e �YJA^ 1wQw^m.{48I>_@ ׬lҜ^0~gvmE7۬,=n>:\j8rS~%`ȇ%iv|Ol+*HW, -^�F DB(;nCbbR#&!J޿㹎pѫNoT8T6Eo'.G֝O7Eu y *gSc%"~ٞSz7O3]+|^<j#M*#S;#,u:SM 2IT %dx?2gj+z�rO^Y0̾b%zK3-YsV=kG52欩�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/keys/really-revoked-userid-1-soft-revocation.pgp�������������������0000644�0000000�0000000�00000005130�10461020230�0026170�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������]|c �:dL. 7?t zО1EZrV?I)t<)YwOx;FrW-CYU(:my:K+t焓T jõ|‹]~Nq9]UY%^!ft}δͨ~J[Xy~<@H( Z5A1]B_GZ"?o0pGK)#ieN~n bnYIJc&ۑ5T^"%jLӰd\sC"#;� sdsr ev廜u軄o�~'=c9 q/~_i.{" M[!��Eminem �8!V#-\kcJ}%]|   � }%J- �x*ߢV mX((:`E^W\CmƖ޵,lƻi:&jbw[; auÄNvOeI V//R[)I>9< A} !458/#ʱuZ=8T^+p_U1bD.k j ~ҮR-qTxYnWC Zrl<rGDJOAG5bd\+Ն! >.<QX!<Y,ֻxLJٳyxw(A�a�^M9͒ cB(C#i0[Ċfz۫`z$ ͩě] *ZEô Slim Shady0 �0!V#-\kcJ}%]| Soft revocation.� }% >U_Sg5yo (oeV Tfzh&*?ihU?Ģ1pZTЈ1Bm`2rիA!9CH=ˊ�lokY?wЋiaEV [Hv˞ Z?d\+l9JM[A'7ȇ' B޲X/pKb*T6n0x?U`m8KMCy !@ii3?$#s/nD2P0 MnuGy_*R,r `3 dd#f;+0AZ"r{bb8`=3!R{Ru �8!V#-\kcJ}%]|c   � }% �4lCnjb4e;e"zѸf1d=Ŭ=�Nɬz&Hy;.hzٱH!NF\VptA"JꍪLTn_ Y&ȭ&ȫkͦ6P9 {3>oJݴRW*i|�5Ԭb#3Q_[<DZ*m9Qwb-|{pdXȰ مb]k5!%zkfYgywDx@ҁ8a9zUObGFdIIZ_? X}4 7^dK.ThɈԎ^ڀ,8\mxY/t!7BPv/g]|c �ݓX(@ZA<x €8CiK$nZYֲKn'T% ߿n< EOsisg,wu�8|wC>LqyQۙt/09[UZC|z4 ;CtWqؕ8ebLb?+ΞFd''hps\M󨜻$V4[_wl?ޛ +T>xB΁tmd^[(.8aL*$}egg9- .'3<pfOL==M%6%}o뺚li}D M#r ϙ 15*Q==ۛ&Jp$=!ٸw`\b�� � !V#-\kcJ}%]|c � }%e �YJA^ 1wQw^m.{48I>_@ ׬lҜ^0~gvmE7۬,=n>:\j8rS~%`ȇ%iv|Ol+*HW, -^�F DB(;nCbbR#&!J޿㹎pѫNoT8T6Eo'.G֝O7Eu y *gSc%"~ٞSz7O3]+|^<j#M*#S;#,u:SM 2IT %dx?2gj+z�rO^Y0̾b%zK3-YsV=kG52欩����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/keys/really-revoked-userid-2-new-self-sig.pgp����������������������0000644�0000000�0000000�00000004217�10461020230�0025354�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������]|c �:dL. 7?t zО1EZrV?I)t<)YwOx;FrW-CYU(:my:K+t焓T jõ|‹]~Nq9]UY%^!ft}δͨ~J[Xy~<@H( Z5A1]B_GZ"?o0pGK)#ieN~n bnYIJc&ۑ5T^"%jLӰd\sC"#;� sdsr ev廜u軄o�~'=c9 q/~_i.{" M[!��Eminem �8!V#-\kcJ}%]|   � }%J- �x*ߢV mX((:`E^W\CmƖ޵,lƻi:&jbw[; auÄNvOeI V//R[)I>9< A} !458/#ʱuZ=8T^+p_U1bD.k j ~ҮR-qTxYnWC Zrl<rGDJOAG5bd\+Ն! >.<QX!<Y,ֻxLJٳyxw(A�a�^M9͒ cB(C#i0[Ċfz۫`z$ ͩě] *ZEô Slim Shady �8!V#-\kcJ}%]|   � }% , ڍu}k dvuvXVw!j?(30:Ok o rB<hu"cI�{~Y2EFHG0dݛ h4z *բ|F =k3$�P'Oyy#*O~OOlsšt]riƪvSV&4 6ZP�!Vv¯(BN9/Rvlu5&yNPk2!e;J{<]_4WrI9h]ۯWf3g>U#ĺ<HVJJ['Ӵk> V&դA%6 \tΜ62Z|vtDv_yWl1lу@e^]|c �ݓX(@ZA<x €8CiK$nZYֲKn'T% ߿n< EOsisg,wu�8|wC>LqyQۙt/09[UZC|z4 ;CtWqؕ8ebLb?+ΞFd''hps\M󨜻$V4[_wl?ޛ +T>xB΁tmd^[(.8aL*$}egg9- .'3<pfOL==M%6%}o뺚li}D M#r ϙ 15*Q==ۛ&Jp$=!ٸw`\b�� � !V#-\kcJ}%]|c � }%e �YJA^ 1wQw^m.{48I>_@ ׬lҜ^0~gvmE7۬,=n>:\j8rS~%`ȇ%iv|Ol+*HW, -^�F DB(;nCbbR#&!J޿㹎pѫNoT8T6Eo'.G֝O7Eu y *gSc%"~ٞSz7O3]+|^<j#M*#S;#,u:SM 2IT %dx?2gj+z�rO^Y0̾b%zK3-YsV=kG52欩���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/keys/really-revoked-userid-3-hard-revocation.pgp�������������������0000644�0000000�0000000�00000005130�10461020230�0026135�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������]|c �:dL. 7?t zО1EZrV?I)t<)YwOx;FrW-CYU(:my:K+t焓T jõ|‹]~Nq9]UY%^!ft}δͨ~J[Xy~<@H( Z5A1]B_GZ"?o0pGK)#ieN~n bnYIJc&ۑ5T^"%jLӰd\sC"#;� sdsr ev廜u軄o�~'=c9 q/~_i.{" M[!�� Slim Shady0 �0!V#-\kcJ}%]|�Hard revocation.� }% �.12o};𪅐JöTp]FCI̜Nr`Ym }D(gmH7Y5 _ rmM53aǵi֠\�g$ %*C(5q(Ѵ:u0^62E ^'sțƽׯ7LHI7Bp@E6nj4-<2ȔOʱK\_W SF''7>ʻ�-K;^ݿL7V/2I"۰>QCn c 'L!u~f?Sfg>EL%rE$Dyj5f|ѮFx} kPU �8!V#-\kcJ}%]|   � }% , ڍu}k dvuvXVw!j?(30:Ok o rB<hu"cI�{~Y2EFHG0dݛ h4z *բ|F =k3$�P'Oyy#*O~OOlsšt]riƪvSV&4 6ZP�!Vv¯(BN9/Rvlu5&yNPk2!e;J{<]_4WrI9h]ۯWf3g>U#ĺ<HVJJ['Ӵk> V&դA%6 \tΜ62Z|vtDv_yWl1lу@e^Eminem �8!V#-\kcJ}%]|   � }%J- �x*ߢV mX((:`E^W\CmƖ޵,lƻi:&jbw[; auÄNvOeI V//R[)I>9< A} !458/#ʱuZ=8T^+p_U1bD.k j ~ҮR-qTxYnWC Zrl<rGDJOAG5bd\+Ն! >.<QX!<Y,ֻxLJٳyxw(A�a�^M9͒ cB(C#i0[Ċfz۫`z$ ͩě] *ZEù]|c �ݓX(@ZA<x €8CiK$nZYֲKn'T% ߿n< EOsisg,wu�8|wC>LqyQۙt/09[UZC|z4 ;CtWqؕ8ebLb?+ΞFd''hps\M󨜻$V4[_wl?ޛ +T>xB΁tmd^[(.8aL*$}egg9- .'3<pfOL==M%6%}o뺚li}D M#r ϙ 15*Q==ۛ&Jp$=!ٸw`\b�� � !V#-\kcJ}%]|c � }%e �YJA^ 1wQw^m.{48I>_@ ׬lҜ^0~gvmE7۬,=n>:\j8rS~%`ȇ%iv|Ol+*HW, -^�F DB(;nCbbR#&!J޿㹎pѫNoT8T6Eo'.G֝O7Eu y *gSc%"~ٞSz7O3]+|^<j#M*#S;#,u:SM 2IT %dx?2gj+z�rO^Y0̾b%zK3-YsV=kG52欩����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/keys/really-revoked-userid-4-new-self-sig.pgp����������������������0000644�0000000�0000000�00000004217�10461020230�0025356�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������]|c �:dL. 7?t zО1EZrV?I)t<)YwOx;FrW-CYU(:my:K+t焓T jõ|‹]~Nq9]UY%^!ft}δͨ~J[Xy~<@H( Z5A1]B_GZ"?o0pGK)#ieN~n bnYIJc&ۑ5T^"%jLӰd\sC"#;� sdsr ev廜u軄o�~'=c9 q/~_i.{" M[!��Eminem �8!V#-\kcJ}%]|   � }%J- �x*ߢV mX((:`E^W\CmƖ޵,lƻi:&jbw[; auÄNvOeI V//R[)I>9< A} !458/#ʱuZ=8T^+p_U1bD.k j ~ҮR-qTxYnWC Zrl<rGDJOAG5bd\+Ն! >.<QX!<Y,ֻxLJٳyxw(A�a�^M9͒ cB(C#i0[Ċfz۫`z$ ͩě] *ZEô Slim Shady �8!V#-\kcJ}%]|   � }% _(HJE&*1NmŽ )rRr:'բ겇KV|Wz+ALh C[8LνBl~Q6ʗ&=>H6}T~~-EzPLQhl/jn!� x-[xؘJM,شƅsBO mh!|aƗ(E1)&,t,3 SVU:|vV7, Ԟb_gH̓PZ;¤| ra;'l`=4D\I-qA\gdi,JT}©Rb]ޤ@#\xїfDW-�U2fMJLV]|c �ݓX(@ZA<x €8CiK$nZYֲKn'T% ߿n< EOsisg,wu�8|wC>LqyQۙt/09[UZC|z4 ;CtWqؕ8ebLb?+ΞFd''hps\M󨜻$V4[_wl?ޛ +T>xB΁tmd^[(.8aL*$}egg9- .'3<pfOL==M%6%}o뺚li}D M#r ϙ 15*Q==ۛ&Jp$=!ٸw`\b�� � !V#-\kcJ}%]|c � }%e �YJA^ 1wQw^m.{48I>_@ ׬lҜ^0~gvmE7۬,=n>:\j8rS~%`ȇ%iv|Ol+*HW, -^�F DB(;nCbbR#&!J޿㹎pѫNoT8T6Eo'.G֝O7Eu y *gSc%"~ٞSz7O3]+|^<j#M*#S;#,u:SM 2IT %dx?2gj+z�rO^Y0̾b%zK3-YsV=kG52欩���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/keys/recipient.pgp�������������������������������������������������0000644�0000000�0000000�00000003622�10461020230�0020462�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������-----BEGIN PGP PRIVATE KEY BLOCK----- Comment: 5CF6 06BD 495F 956D FCB2 7ECC F1AB A7B5 80AC CEA4 Comment: <recipient@example.com> xVgEYCPsqRYJKwYBBAHaRw8BAQdAyl+b2R+JeEfZ31BuDWeGiFAy8eY94qCVc7qF u/rvtXYAAQCfwQXpLpl+IfcV3ZUEihbhMA9asAGJi2j4FoVBRxI9VA08wsALBB8W CgB9BYJgI+ypAwsJBwkQ8auntYCszqRHFAAAAAAAHgAgc2FsdEBub3RhdGlvbnMu c2VxdW9pYS1wZ3Aub3JnkBdVl2KuiH68rzSswYrKhIrUqWqGvVdRyRV6f733btID FQoIApsBAh4BFiEEXPYGvUlflW38sn7M8auntYCszqQAAAvPAP9lC6JGGFXKWov6 p+SZKq6qL83meOZQ54dBCq38h/zuTwD/eYjBNeNwSazSI+Qu9YD5YuJRgONTmUNP Sgz5swMj5gTNFzxyZWNpcGllbnRAZXhhbXBsZS5jb20+wsAOBBMWCgCABYJgI+yp AwsJBwkQ8auntYCszqRHFAAAAAAAHgAgc2FsdEBub3RhdGlvbnMuc2VxdW9pYS1w Z3Aub3JnofcsAmtPZEK1hy0wdsSoSFFyB77Z0hCce9ntDVvoLuUDFQoIApkBApsB Ah4BFiEEXPYGvUlflW38sn7M8auntYCszqQAAG6vAP43PGtJlVZFx/Mjk+VLK8Y2 rHeEslPPI0QbmQweIY7+igD/RkJU2WpL8TS2bhEk4gGD93cr+Yx9d34rzcDuRDI/ 3gjHWARgI+ypFgkrBgEEAdpHDwEBB0BVlAiKHOzhsAh9R4bdzayASc1xCQg6iRpS 9CEGgxrJmAABAIXd7slG2jRbHTdrsCIP4FUqaC3XLOXBVNOMRF40nx8hDm7CwMIE GBYKATQFgmAj7KkJEPGrp7WArM6kRxQAAAAAAB4AIHNhbHRAbm90YXRpb25zLnNl cXVvaWEtcGdwLm9yZ5QTcp0kytebTq8hEkY+lzYH9XFHfcIgOCRKKBBJKq14ApsC Ah4BvqAEGRYKAG8FgmAj7KkJEBpj51UNI+Y4RxQAAAAAAB4AIHNhbHRAbm90YXRp b25zLnNlcXVvaWEtcGdwLm9yZ6EvXHFWkX0SxwyNW4IA/fjYbP1OtH9FKOZ8fyOs hrLYFiEEF4/e0UMm/i0EBI7GGmPnVQ0j5jgAANGlAP0UiVrwDe9hpJqkvzSCuWZ3 dcRFN99oDr6wxmori3zUdQD+MSglK7p0HadqwWAKBXsXYWxDNXOb7shyl4fP8DCU jQ4WIQRc9ga9SV+Vbfyyfszxq6e1gKzOpAAALosBAI+CEzGmb+RowPzQX8GmpJsU cpdCHOs7m0oVMq93Yh/WAP9l20ZKarqyqqh4ce5c/ElSMwEAOzp1EOoiTQzCJM2S DMddBGAj7KkSCisGAQQBl1UBBQEBB0BZC7zH6V3bbQ3egHS/Jq/cWV2vkoswpt1A qzEwRC/CDQMBCAkAAP9rIgacMmlwbILiWz0gOkdk7rzPBVgLh4+IYaYBq2ZbEA2p wsADBBgWCgB1BYJgI+ypCRDxq6e1gKzOpEcUAAAAAAAeACBzYWx0QG5vdGF0aW9u cy5zZXF1b2lhLXBncC5vcmf3RzS0xBiVO+HAWnEH6SGYc5MRgyAO1bNr1SYwEWLL egKbDAIeARYhBFz2Br1JX5Vt/LJ+zPGrp7WArM6kAADRRQD+P4o3cQP72K1Zj3F1 OTmQZK5gbokA6pRlL+cXND1kX8wBAL9aVpcPIEictRneZHT9xytDwjW9LRKEteSi vjF27ooN =EzWF -----END PGP PRIVATE KEY BLOCK----- ��������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/keys/sender.pgp����������������������������������������������������0000644�0000000�0000000�00000003613�10461020230�0017760�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������-----BEGIN PGP PRIVATE KEY BLOCK----- Comment: C9CE CC00 2086 58E6 183D A1C6 AB27 F577 2E0E 7843 Comment: <sender@example.com> xVgEYCPsxRYJKwYBBAHaRw8BAQdAUYZaTFKQNbjjRYLzTwy3Q8bB0NTFtgvf+XCA IrCGlmQAAQC+mWrY1zkvlOUzTM86YCXHVv+17ANSxcQWcdRT06O8XhE4wsALBB8W CgB9BYJgI+zFAwsJBwkQqyf1dy4OeENHFAAAAAAAHgAgc2FsdEBub3RhdGlvbnMu c2VxdW9pYS1wZ3Aub3JnWXvgQQecnMWO/fYncaPEbcZfKx4gDAexicJP7jqTYroD FQoIApsBAh4BFiEEyc7MACCGWOYYPaHGqyf1dy4OeEMAAL/kAQCphI/dOFGLoyM8 nylwNNsR4eMwY2R+FQeg5/fa2hVw2QEA4bQLA3NV7ipe/NjhTr2Jg7VJOMTRRkz6 8mN/x6QGxgTNFDxzZW5kZXJAZXhhbXBsZS5jb20+wsAOBBMWCgCABYJgI+zFAwsJ BwkQqyf1dy4OeENHFAAAAAAAHgAgc2FsdEBub3RhdGlvbnMuc2VxdW9pYS1wZ3Au b3JnIXF3QU6P6+GXFwbQEECgzntsyO8gvXlLepy/ucn+vYoDFQoIApkBApsBAh4B FiEEyc7MACCGWOYYPaHGqyf1dy4OeEMAAEjUAQCFIkopGT8XtynPC8EK8xGUUDIQ r2iMHo8hWUj54P05dwEAz21A3Zsfri2aJCFm6PF/PI1lgveOhIkmymPHn4u0HgDH WARgI+zFFgkrBgEEAdpHDwEBB0CVc1gpEwyJwcfrKthXOf8Etrbyu5PYowQ4VjCf USIWsAABAP3mLXdK97WvUtw4EB3C7Eco9W++7tlz6qvJg+qwW4YVEqrCwMIEGBYK ATQFgmAj7MUJEKsn9XcuDnhDRxQAAAAAAB4AIHNhbHRAbm90YXRpb25zLnNlcXVv aWEtcGdwLm9yZyztCZP7RO0A16UDnaBGz5INoSUHpMfKK1qxNjliywEMApsCAh4B vqAEGRYKAG8FgmAj7MUJEJmUVZwG7s2BRxQAAAAAAB4AIHNhbHRAbm90YXRpb25z LnNlcXVvaWEtcGdwLm9yZ7S9NDPI3eUn/BpxSx/4hVGWIZw4lOcQK2Kh9jrhC6WD FiEEN7I0OVSvcntqZncCmZRVnAbuzYEAAML+AQCIcsXZ2eFMp29lZXubgWhu/A6+ 9s31IuaoxlfTq+mLJQEAhRkzX14wAInsjW0mU/ZOWYjgtqvQNv+MI0CjQdpEAwcW IQTJzswAIIZY5hg9ocarJ/V3Lg54QwAAU6YBAOOBEW6s61h+PJ1uai5tdPuAf0Zv YxG3MIYDolq5lXeGAQDyV6bwplTWAQycLo1X5+41Bt83NtxVOE4beqsV+/9TBcdd BGAj7MUSCisGAQQBl1UBBQEBB0AbjB79xZ0YRFyoiGFHC4dFWbGy4V+SfwsFl/fD vLhTRQMBCAkAAP9h4mp41OcmJVpHhX7aBdWBMYwnrDklRBRBkMgY3qcMYA7rwsAD BBgWCgB1BYJgI+zFCRCrJ/V3Lg54Q0cUAAAAAAAeACBzYWx0QG5vdGF0aW9ucy5z ZXF1b2lhLXBncC5vcmf1V+e7SqWcmY8cvjM69asiI1ja4oZDZ3OV4r8Wkqs2dgKb DAIeARYhBMnOzAAghljmGD2hxqsn9XcuDnhDAADlYwD+P3oMVWOsWhKkWrutqbgW Ozik7yeh1H90w5I2XHNFtVIA/1dW/azFsqrNHf8ZFmmfGsJ3h/MePsz/ao4BJ69v CsAI =lNEM -----END PGP PRIVATE KEY BLOCK----- ���������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/keys/sha-mbles.alice.asc�������������������������������������������0000644�0000000�0000000�00000006533�10461020230�0021413�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������-----BEGIN PGP PUBLIC KEY BLOCK----- mQQNBH/oF4ABIAD/S2V5IGlzIHBhcnQgb2YgYSBjb2xsaXNpb24hIEl0J3MgYSB0 cmFwIXnGGvCvzAVFFdknTnMHYksdx/sjmIu43otXXbp7nqsxwWdLbZdDeKgncy/1 hRx2ouYHcrWkfOHqxAu5k8EtjHDiSk+NX83twbMsnPGeMa8kKXWdQuTf2zFxn1h2 I+5VKTm23NxFn8pTVTtw+H7eMKJH6jr2x1mi8gsyDXYNtk/0eQhP08yzzdSDYtlq nEMGF8r/bDbGN+U/3ihBf2Jv7FTteUOkbl9XMPK7OPsd9uAJABDQDiSteL+SZBmT YI6NFYp4nzTEb+HmAn81pMv7gnB2xQ7KDot8ymm7LCt5Aln5v5Vw3Y1EN6MRX6/3 w8rAmtJSZgVcJxBHVReOrv+CWiyqKs+13mTOdkHcWaVBqfycdWdW4uI9xxPIwkyX kKprDjin9V8URSocooUN3ZVi/ZoYrUJJaqlwCPdGcvaO9GHriLCZM9YmtPkYdJzA J/3dbEJfxCFoNdATTRUoW6sst4Sk98u0+1FNS/D2I3zwCp6fEyuaBm5v0X9sQph0 eFhv9lGvlnR/tCa5hyuaiOQGP1m7M0zABlD4OoDEJ1G3GXTTAPwoGaLo8eMsG1HL GOa/xNubrvZ11Kr1sVdKBH+PbdLsFTqTQSKTl02Sj4jO2TY8/vl84udCvzTJa47z h1Z2/qXMqOX33qC6skE9TeAO5x7gHxYr220er9kl5q66rmo1TvF88gWkBPvbEvxF TUH92VzyRZZkoq0DLR2mCnMmQHXX8eDWwUA656DYYd8/5XBxiN1eB9FYm5+LZjBV P4/DUrPgwn2oC926TGQCDQYH9nQNLoASAgwRjxlZlJl+xnAClehPhvimlPrsylEs iZXJZuZjfmFlR1VUbBDRXshFngA85nlvVon7fAnZTYNiyM5PM5ZBHTH/9luEWef+ vMgBV5t3S1TZnD6dQpgStXi3kNZ77juY93ADOo64i5vmC8TONoSzg7AO+6KzQ2lR +zB0/AKu92MeHNcAEQEAAdEAAAEZwFcBEAABAQAAAAAAAAAAAAAAAP/Y/9sAQwD/ //////////////////////////////////////////////////////////////// /////////////////////8AACwgAQABYAQERAP/EACgAAQEBAAAAAAAAAAAAAAAA AAAEAxABAAAAAAAAAAAAAAAAAAAAAP/aAAgBAQAAPwDQTqABOoAE6gE6gATgAKAT ihOChOKE4AAAKE4AChOKE6gATqE6gE4oTigH/9lduLZvOS5WXb0QhggKNiGWIm/z IV/tc5s7KOrrL9gZCVAvnv1D5pEAuEqDOBDdABEBAAG0GUFsaWNlIDxhbGljZUBl eGFtcGxlLmNvbT6JBFQEEwEIAD4WIQRDzVxbBP9XQvoUGryp11WpY1SMeAUCf+gX gAIbLwUJAAFRgAULCQgHAgYVCgkICwIEFgIDAQIeAQIXgAAKCRCp11WpY1SMeJHD H/9jJj4FARmtNhFXqDhNcNV6C3mQAevQR1iCbFwWHXdd2yRfyFv9/dVY4Wa2Q16j BuCUhl7SDmO1IYlYBBo+91Kr11rDezL/y/POQsKzRp9nEo0r+QCVnIozjJ1DeP77 Xwxu77gRe1QkIql0aVUhP6vPRD+oFgViYUUWIdNgh4bBJGDMY43BJyeCxmuqRo8/ sHTWu7rmhuHpsQiTRd5valBPHXbZKAcS+TmgGM8dMv3N0ldq1wDZWDrWfD6vCmnP a4hH3UybM9G6kkkQ2xETt7hyW6XlsLXk9CtaZ2hz1kUUKtTa8LU1mj9T8dsLbTj0 qipYps/ry0Cro7gkaYiNGeeelNzPhfz8jXHrK47hZ0iZbcb7JUcR6WodGAWV6bFE +Jj02NOnX87QsWk1z5YdHsvDEbzbE9fQZ2r8hPEDsqEDXsSSw6h/FkhCcxwn9RV2 SGt5kZkU0qGP6chAITiGEEX40TcnfgyNvSJcHJ2iMaKGEaaQv5MfaefMRX9tGiz3 wmTYxSdUqkFiG2YtOkZWdCZDBKVL6Y8g7fM6G5DQc+/4alVqGdZ11TwAmKcZ142E 2JL6MAu9zfmjMcRkXytzUfkKGKa/kpAMPsn1cWYxlQe0PxxVAM8iZTPUSnBah13t Ec1fx9jJ1cJkHBW7w1AFS+kLKD0blX9rreTUDzBQLPkaXWqH5fLZvc+EGJpt6roL obVZ2mVDfmYNABYnw4nTDBGb2Vk60MpPh0k7pMMbXJA9kuwvgYa6VztyIZcyi8Mz L0Kbtwsog0S5KV01tOcKMGtXpni6Vsx/k1ydvtOBUuG7VjG8Y1EuGyOUzdIuXe1r UUTIV7GZfNpzxAumvoY2umKJQzpA/jG1yban05IViIIFotKCfA8cpKzY1roUtyjq FPgB0T2YOadFStarrypb+f3Snj0nK4UqyJ0YbJQiKbo/NVvY2cQdqR5wyBVKV7RR qjkNArxkHu0RUaBnywzyzwtOHNs0ojX3IyOeytLcIrbaKYJYaeZCwOWcNIWP5avq u4Lkfq+ariSGdbfHKBR1OJWBeD/Rne+E9rmwrvk2S8fd1pdx44R8RzIvWfCKUQeu wnfHBsP5BOZkjltSQnpBHsNSFnie0o5uSKnL8dyTbH3TtX1u3D7S95KZRley+n+5 hfQTTHWo8Xq/m6wr0TYxdA4DbaSJJ/wZFH0YaybXXkXhIQY3n3o+4mdgBQy3okIG enWdH6Jdty6xrDimgxMjOUIyQ0JTlHBksIIViqzaY52ATI2U5UBpK7DgNC+IzIFe QVWNIFVqT3Ifqcfd5iKZZvEEKt4QWDfIvc5bPkXraWV8Aqq3yl0iYiBFRVLVDBy3 AXrthG5XMjPFw/7TQEoIY5MYiQEcBBABAgAGBQJ/6VrwAAoJEK+7H+1pUalWFn0H /AmChC2A4mXbw6m6hIs/zWUTNRAM4I9daUst138jmNFKUz67zIxUvUeUuU2n6Lfj rerpVX8LuQbz2/e+g1AIcAKBRniBDyjzedWuya/faLW9rSpATUy/3Rv2G0T1QBSw fsQNlNT8oBbMYZl7ht0ylst2m9zJyI+XjktMiIIxfr8zlMjIpXU9xF0YPuSNM0Sm l38jeLHN9hHZpvsvsK2EmhCTF2KhHvozlFfJ8wr10cnWxdEzoYntx178Ty0KRgC+ oTLIccOSrNKjzkRuNnOsEEpAudFR6yxx8HLZatSXWpzitNdaG8APDwJiS5AHaqVQ yNnDiF8Aoz5caQEw4D8aDvk= =04wQ -----END PGP PUBLIC KEY BLOCK----- ���������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/keys/sha-mbles.bob.asc���������������������������������������������0000644�0000000�0000000�00000011052�10461020230�0021070�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������-----BEGIN PGP PUBLIC KEY BLOCK----- mQMNBH/oF4ABGAD/UHJhY3RpY2FsIFNIQS0xIGNob3Nlbi1wcmVmaXggY29sbGlz aW9uIR0nbGumYeEEDh99dn8HYkndx/szLIu4wrdXXb7Hnqsr4WdLfbNDeLTLcy/h iRx2oCYHcqUQfOH26Au5l30tjGhSSk+dX83tzQssnOGSMa8m6XWdUlDf2y1Nn1hy n+5VMxm23Mxhn8pPuTtw7HLeMKCH6jrmc1mi7icyDXKxtk/syQhPw8yzzdg7Ytl6 kEMGFQr/bCZyN+Uj4ihBe95v7E7NeUO0Sl9XLB67OO8R9uALwBDQHpCteKO+ZBmX 3I6NDTp4nyTEb+Hqun81tMf7gnK2xQ7auot81lW7LC/FAlnjn5VwzalEN7/9X6/j z8rAmBJSZhXoJxBbeReOqkOCWjQaKs+l3mTOevncWbVNqfyetWdW8lY9xw/0wkyT LKprFBin9U8wRSoAToUNyZli/ZjYrUJZ3qlwFNtGcvIy9GHzOLCZI9YmtPWgdJzQ K/3dboJfxDHcNdAPcRUoXxcst56E98uk31FNVxz2I2j8Cp6d0yuaFtpv0WNAQphw xFhv7uGvlmR/tCa1PyuamOgGP1t7M0zQslD4JrzEJ1ULGXTJIPwoCYbo8f/AG1Hf FOa/xhubrubB1KrpnVdKAMOPbcpcFTqDQSKTm/WSj5jC2TY+Pvl88lNCvyj1a473 O1Z25IXMqPXT3qCmXkE9WewO5xwgHxY7b20es/Ul5qoGrmot/vF84gWkBPdjEvxV QUH925zyRYbQoq0fER2mDs8mQG/38eDG5UA6+0zYYcsz5XBzSN1eF2VYm4OnZjBR g4/DSgPgwm2oC9229GQCHQYH9nQNLoASAgwRjxlZlJl+xnAClehPhvimlPrsylEs iZXJZuZjfmFlR1VUbBDRXshFngA85nlvVon7fAnZTYNiyM5PM5ZBHTH/9luEWef+ vMgBV5t3S1TZnD6dQpgStXi3kNZ77juY93ADOo64i5vmC8TONoSzg7AO+6KzQ2lR +zB0/AKu92MeHNcAEQEAAbQVQm9iIDxib2JAZXhhbXBsZS5jb20+iQNUBBMBCAA+ FiEExr/i/LvlGokr63eYEjPUzGHb2cQFAn/oF4ACGy8FCQABUYAFCwkIBwIGFQoJ CAsCBBYCAwECHgECF4AACgkQEjPUzGHb2cSLZhgAynF426xC9qX3Afh2jzWUFWMZ duiZ78JwpopGRo/BnAzlD/dmmK/OjaVfG7aCq/R/Fd3faeA1qxJuST6Z2bwDCbtj HjERNPATekCJaHP4EgHsFSDZQUNdUWswO7rclXnHmN7AxCkfXg+U5R1cxQkyBIL7 U9uxleMU6+Fr+QP2y4MsZpM1n1ipWJK1FuKKQeNY9J7fRdr0LyqiVfYOTqtI6kr2 tzk1oQ2CsiRmidC0MmAaW1L5tppmDiPMo3Yqv0lCrqeyfnOBpwOLFIz9DH6Ndf1z 213eDWgUpHbHvERS0rwxv/4eizUGmisxgjrWwjhdRch7ZNWQZYQVwtcjz7wWhVPW vaS+t86YayrDO8J1jw7FfkFdPHlt1TlawWcGsiehiB9EazJFM4zGw/IYjQWLhBvv e+NeKrSjIso3ZXSbqVdl/Dgj4Go9J3KXXqJSbaxedak3SX44XwO54NAgYsjl0ak5 yHKdBjrZYdtElzkkJklUEYdQyA5icRjJ2Kiy3g4xlR+7ev6i1QSoouBIbQD7qmeO 45iKG17m0e6qMPrPB9sTUue/gVfZaysR4nUX1zIIk6sHg2Q7Fl1ohYO5SFYvFVrj dtpGO1XeKXnEKaUb9Z90CUlh16SRL7/xXq86SKqi7SoIMNFspUmsuk4np/e6ipBn u2cAjpThNrELZjmvh0KCt/x6jv1vbdUiB+gg2SbTD5qFmoir2OXfKlNrz6PUhGI4 dQFKbBsmbJtUqTDEogRlHUZ2CXWpj5Zs9xvcGvaTpurqncoEmz96SKyhiPSpDw0x tIxEaupvGnug4/CtlvFE+vP7ZzucMSThLLXJVRATNWcCWosfXXWv4A7Lebbxqj2P ukwJFmmJOet8s2h6blFRm3MegHP7hqjqQe5XovZLqwLKrr3DqdZYhmx0jeQAToII BKvviJnh8XU293hHv8M4wGRVPvyMrTfwcMj+pgb4yTPShoysONWIE954c3yic94+ slaK45askyHC9ORw8oraPOyEOI9ewSfi5N3zwXImiQEcBBABAgAGBQJ/6VrwAAoJ EK+7H+1pUalWI6UH/3HgRCkMiyDRrRi8lZrMWcf0xIlC1kiSb62Kt2VhsG/RL2tO GJZhrOp3+fg3QiRFMnE+5ScJIBhL3PgScTEOoCcmA3+jo7s8fnfhLVzbHjcJEtxP fo4zlr9pKh5cUXlsSrZPXCHE/1IdEXjbETie6fS9aSWhnDYnBdwTw8CrHRY/tibn tMla3m8w89zfTcDVkHNSgj6nSQly+jlU+3I5Ny+pJGxEytMeBwyWoC0nR/qltm7U kG2yMV83tqkSVqMyIPGx5MQuc21sDWTmP3ct+VMrJD448+S2Emor0InhmkRuT5bS 7p8AHuEkhlPITSRvGpgQjnzDGuP6TUbVUGReTMvRwFnAVwEQAAEBAAAAAAAAAAAA AAAA/9j/2wBDAP////////////////////////////////////////////////// ////////////////////////////////////wAALCABAAFgBAREA/8QAKAABAQEA AAAAAAAAAAAAAAAAAAQDEAEAAAAAAAAAAAAAAAAAAAAA/9oACAEBAAA/ANBOoAE6 gATqATqABOAAoBOKE4KE4oTgAAAoTgAKE4oTqABOoTqATihOKAf/2V24tm85LlZd vRCGCAo2IZYib/MhX+1zmzso6usv2BkJUC+e/UPmkQC4SoM4EN0AEQEAAbQAAAAZ QWxpY2UgPGFsaWNlQGV4YW1wbGUuY29tPokDVAQTAQgAPhYhBMa/4vy75RqJK+t3 mBIz1Mxh29nEBQJ/6BeAAhsvBQkAAVGABQsJCAcCBhUKCQgLAgQWAgMBAh4BAheA AAoJEBIz1Mxh29nEsnkYALms73X14/+0sUPjojsWYeUzFSY67/ihObZBEMcp3qoz 4E5kFlg2ZquAEBnMa3IANy7kvZprxgYd3EABzaBUFkMsrGov0aaJCLmtSvWP9ooh 0vWgHIOtuTUZGBcdJhN0RnZ/BOW1azlrYGdzIiQOzMn+67BfUkJIIoHgdNOOZZkE f2fncC3vmyh9/8QhsUF553mOxo8VYYNS0/S/GSr6MD1eP+YpA3v2DznaHyTzwFmN l4apcEErudob/PSzn9SdHDjwC2WxqObEWllLVtnANi1GP+sSTVbWCREJ+m72VljV ADP5nKD3HPKeqoRNuQkv2TdQdkyTp3UBechnqK+u7xl72d0ZGJ9EOnT/o23zbxFz vrIcBbCQnqrtZnwUDIaxG8numABkfM6T5m0z0etIOctQoRw7MQsB4C6VxqDtho3k 5dSomtsiNsiuLlI6FT5YIUJWOV7r8UVzrAKq98buaAHlnBhpps2WHBrm8o0BO16g rJOEfhgYyB6cMG60hLAhkjPVbu5HxBngylu1ZrotktK+eWGtWyZRA6/pZN5t7EdO IzOiS6Ewp5yBHB5t24ZytrZhVPgExOka47yEJMKq8Q+uM8V3kfsk6+Kb6aK2cJyg A1igSL9T48a9es0dWm+rPxKg2Sb1+QdWCWpG52Nb4d+EfqQERdIgp97dgJaZ3ye7 0RR+tK9gpoJHDI1UBdm3M8PeYhiMb0WWvuIsuB5+/fstLTNngmyT+gqwr4F3XmG/ 3ZoclgmKxRG7VYn3DiT7PLZJmSovpuGay2pqAQvc+nJrjC0w67qUmBm8QHwbMWMh xZkfBLfD6V/7iWCIhPDmJteKW/pxBASJh9gA9h8T3BRTT0SM252aaG6EYF5Wc+ha /rhl1c6xSE6SEArf+aYcrz4KXap4HDC4pYqJIltWg8uvs2+qejYH4iW3KArmmfjU 3naa7LDUKEs7U2tcPyg1ZfTC529sROq50gJ1agcaHr8RJdIZ6qGVdaBQLMG9utUW 60uHZmhCPy+lS3Ftjp19/YkBHAQQAQIABgUCf+la8AAKCRCvux/taVGpVhZ9B/wJ goQtgOJl28OpuoSLP81lEzUQDOCPXWlLLdd/I5jRSlM+u8yMVL1HlLlNp+i3463q 6VV/C7kG89v3voNQCHACgUZ4gQ8o83nVrsmv32i1va0qQE1Mv90b9htE9UAUsH7E DZTU/KAWzGGZe4bdMpbLdpvcyciPl45LTIiCMX6/M5TIyKV1PcRdGD7kjTNEppd/ I3ixzfYR2ab7L7CthJoQkxdioR76M5RXyfMK9dHJ1sXRM6GJ7cde/E8tCkYAvqEy yHHDkqzSo85EbjZzrBBKQLnRUesscfBy2WrUl1qc4rTXWhvADw8CYkuQB2qlUMjZ w4hfAKM+XGkBMOA/Gg75 =T/Dd -----END PGP PUBLIC KEY BLOCK----- ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/keys/signing/brainpoolP256r1.gpg�����������������������������������0000644�0000000�0000000�00000000723�10461020230�0022731�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������-----BEGIN PGP PRIVATE KEY BLOCK----- lHgEY/9mBxMJKyQDAwIIAQEHAgMEJD0G9nwJGkBkJUe0DhRZJ1erbQsFjPXZksUQ S7wCJ8xIlGyhydUY4kMYnJRsoawbbdBy2sA7fejplMW7gDA0sAAA/RLbrozCFlnq Agrdw/pW6ipQ/CBldmX5yRyu2nPD+ZPCEuW0D2JyYWlucG9vbFAyNTZyMYiQBBMT CAA4FiEEZNtLgQj3+dGLmUhpRWp7U59W6l0FAmP/ZgcCGwMFCwkIBwIGFQoJCAsC BBYCAwECHgECF4AACgkQRWp7U59W6l3OrAD/d59+b7Gqup7ir5WCNO/khRjnsktv qUk86fc4gDcUIYsBAIgtJUzZfcX8YfHhygZmuYuZcCceNVZ8iFoYZ1BROcOM =NuVM -----END PGP PRIVATE KEY BLOCK----- ���������������������������������������������sequoia-openpgp-2.0.0/tests/data/keys/signing/brainpoolP384r1.gpg�����������������������������������0000644�0000000�0000000�00000001101�10461020230�0022722�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������-----BEGIN PGP PRIVATE KEY BLOCK----- lKgEY/9mBxMJKyQDAwIIAQELAwMEKIRdGjy868g7wcRO1mZYVM5IaX7fA5oT/NMO Jdq7X1EFhoimsDsZbo7nKPihM9GiF88E75sWunZo3Fh8JM8EMuoKAmj3aMfNb4ac jRxWqDG9AQVPrEqKxSdmlrgBWMHIAAF+PKtTUELcHpTKuaHe87GpDuB7IoJHYYZx V2pIrCcTCk+w80BfVlaeQyexsHOZXgCEFmG0D2JyYWlucG9vbFAzODRyMYiwBBMT CQA4FiEEk/Vr8NiUfKVsrttKpx2L7FhKCEUFAmP/ZgcCGwMFCwkIBwIGFQoJCAsC BBYCAwECHgECF4AACgkQpx2L7FhKCEVfTQF/clUzB/6zMT+SigopFeJADpVdgHho CahD7leES8kfq6EDDzuwkYX8St3XJqMMZkwMAX9nSetyrQKcWDXoRb198Ux0m7u+ hwQcNvCwEeDBdI8mKNO0AjT0Qt51LqqpHyRFIMg= =gugS -----END PGP PRIVATE KEY BLOCK----- ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/keys/signing/brainpoolP512r1.gpg�����������������������������������0000644�0000000�0000000�00000001257�10461020230�0022727�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������-----BEGIN PGP PRIVATE KEY BLOCK----- lNgEY/9mBxMJKyQDAwIIAQENBAMEeRwjU7K7MSC1c/XDjUyd7oTxZXz2w0R1sX9M iWxJOz2fRKVAEQDPIEOfcppAVMJ5BOeHCNwHHuCKEQ/ILK4a7SaBmSMfwCwvvOXy BMCcmavyp7HQLIxD3apRI8MoeL8u+tf90szCy45tlE/wgUrGKDsRYw91AKae0ax4 6mUnUJYAAf9JxX6d5byzl9paNhg2Ab8Ut9y8ELp6hqJNuzbyPB02OFvu0feFvsle H8dUnUi5WU+Izg/jJfjy7LnTrw1JDTkqIZG0D2JyYWlucG9vbFA1MTJyMYjQBBMT CgA4FiEE9W0rBrs7edL8W/l/SALdwOFXLNMFAmP/ZgcCGwMFCwkIBwIGFQoJCAsC BBYCAwECHgECF4AACgkQSALdwOFXLNNtqwH6A5WECbst7mZ4xLGyQIqjQfwYAdcT 4EwVO1Turdo+8p/zAousIfLMfS3xiij+yl1UAtEhxg2HA+xwe18DZjrwQwH/XG02 feQ6RK8HPykhCGTfc7TBhgVajIVwEo8rTg/i2F6sEcxfX5hfRIMTBph5XKlvUxHY G/J4Uo4GRCW/IXytKA== =sI9Q -----END PGP PRIVATE KEY BLOCK----- �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/keys/signing/dsa.gpg�����������������������������������������������0000644�0000000�0000000�00000002642�10461020230�0020675�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������-----BEGIN PGP PRIVATE KEY BLOCK----- lQNSBGP/ZgYRCADKOzx+xRWorSwfUkY1t+aDBdJ1nCctmRrH+uNx5/wY0ZwFkfDz /60LS8pt0tkJWU1afW/l58Rm+28Xe6aba+W5Vo3BHw8MJDzUpt8DB59rAFagWnTJ YtZYr1Qec/fD+XTYPMqFS/TIsGCLYj9Vmn/Q5jcTwhopYMvebgHiq9sRj03JWpGt B6Nw35W5MXDtwoh/KIyqCIVnf+Op2ZAkk4RbSyqp2mLdMHCUWXAGA46nF8xIpBls cFZDA0QsQiKkaRJBnsTt5X96RQ5CqMcOBvEtpIJ+Ce50+6xVDdDPoLmQdQ9HvVbH RrWFgTmmB49EisEY61tYtM70f5Adv/kmIlfvAQCkk6k1B0QqOfFrP0YgxaxqccaL zFxmNEt0urvCG/B4Fwf9GQJZzFDpXKNVBPZbEbEAFJnUaBWSV2rs0Afy81m93ZHO ZgaPdyq8ipwAxUoeRr7hspBMK5AKprfk62/d/gXlaZNBiDh00pDlZJ+WgakdMBvy TRzO4LqXALtLePTH8hUOHYQvcPDDcgSup4hI8fbc0CY7qNu6PPhqOqkWMoHN0piP QnEIs6T29zPUSAoNypLfsWrhwBkcY0rLRDSxurj6RvesdOwlZ7sAXZkL5YRpoosg S89OlUfG5irkO0P2F8QMkiHCphDR6OLnnn84u14nHvXZtd3nkzegTQV4Q+iTvk8j RIuT/oGAnQbb5k5lXlUkg9KLUkH8arod3QunwAJdxQgAkpwveFNyCWQasP9enfSE H4bbVI6SKurkJ+qWovLX5jLX4tEOtVNA9/hBPEFo2fEItuUPRpuSFZLCAEhRK3+e zWKIxHxJza/98FIq/xbkLd6LRKNKouQSBf0TCOZnAuBXieU4irKrsSvvwYP3nXUG pK7gIIE7T/44jjyxpinRGVq18Dpu4FeQtG6SR4nYXjqYNj3fCWPwl2alSFtVxaEU bQWiaKnauvk2LirWflUN4ZWA/AgWOqSwYT0Jpdg5yMEIxLqAUO+y14Z9R9oCK+zo vdlvwKvQxai8QfCPFrrmu6rauLHBVSYz14HbBIPhkRzFiQNutGQKQ3hG6gZjCf+x QAAA+MmQcmOfLol+wkN4tgLH4YbSvsK0jKrxWW3ouvl/0eQUH7QDZHNhiJAEExEI ADgWIQSRDIc0JIz7YZ8tjEKlTTrxc3lLIwUCY/9mBgIbAwULCQgHAgYVCgkICwIE FgIDAQIeAQIXgAAKCRClTTrxc3lLI1VEAP9VH3xUkD/xWJJs7CYvbo2TIBysNehp qhv6v9ibokDecAD+Oiu732lI3hYD4WEDYfBW5UPG45/7Vc3LVCZg0NKu+Ls= =YnTu -----END PGP PRIVATE KEY BLOCK----- ����������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/keys/signing/generator.sh������������������������������������������0000644�0000000�0000000�00000000445�10461020230�0021750�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#!/bin/sh export GNUPGHOME=`mktemp -d` for a in rsa dsa nistp256 nistp384 nistp521 brainpoolP256r1 brainpoolP384r1 brainpoolP512r1 secp256k1 do gpg --batch --passphrase '' --quick-generate-key $a $a sign never gpg --batch --passphrase '' --armor --export-secret-key $a > $a.gpg done ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/keys/signing/nistp256.gpg������������������������������������������0000644�0000000�0000000�00000000713�10461020230�0021515�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������-----BEGIN PGP PRIVATE KEY BLOCK----- lHcEY/9mBxMIKoZIzj0DAQcCAwT5ynSlWjz1TRxNzJuFFp9ci66CcH3YYmyeulgI BLK81FdDrayC3EsUlxVR5xEwrlPLA2hbTvc1aK4J5AQj2gJkAAEAu0nYyk6vre3O /Rm/fEE4ZvdDLXyxZoFac8nsFfI8Hs4RaLQIbmlzdHAyNTaIkAQTEwgAOBYhBNQa BqJXpkh0B80+Cyactk/lbAcBBQJj/2YHAhsDBQsJCAcCBhUKCQgLAgQWAgMBAh4B AheAAAoJECactk/lbAcB11wA/RV+dstFVIDO5D5IR3Ijdtg38cFjSD1bBbjj/PVc AUJDAP0ckAVUx4B6RsT980FEutkqYhlvuc7pQ54OLUA4hq5oFQ== =btTX -----END PGP PRIVATE KEY BLOCK----- �����������������������������������������������������sequoia-openpgp-2.0.0/tests/data/keys/signing/nistp384.gpg������������������������������������������0000644�0000000�0000000�00000001061�10461020230�0021514�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������-----BEGIN PGP PRIVATE KEY BLOCK----- lKQEY/9mBxMFK4EEACIDAwT6wpgSd/0+Yo6k3p7s8R5oI5gxPlqZQsBm1/fSvcDw gFbeJsTZrBVW77xmebY4uaVEJptEgLmLBgfT0REu2xcwSayoeT6TmIW0inZPxmI4 HhMA52jglJnMMoLHs5PuwWQAAX9Xl47YsdOZ9t/TTBGU+9m+wqGbqDna1zTgqwSQ IvX581HP9DeON+t75yXRk6CHenQd/bQIbmlzdHAzODSIsAQTEwkAOBYhBJt2P9al e6ckXB0An014A9W7zksrBQJj/2YHAhsDBQsJCAcCBhUKCQgLAgQWAgMBAh4BAheA AAoJEE14A9W7zksrukoBgPzTXUUogQqVIo7drZUvUeT2uXhvMoFqtpXJTe7UhshK 4PNl1Dz4CfaUk2hAirl2+AGAwGA0CK0ldkrCwW9v0BLMs1/B9rUgNvfcW+woURuf nxaL1cG5R4Vv9QjgoVVYdkNZ =j2L3 -----END PGP PRIVATE KEY BLOCK----- �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/keys/signing/nistp521.gpg������������������������������������������0000644�0000000�0000000�00000001247�10461020230�0021513�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������-----BEGIN PGP PRIVATE KEY BLOCK----- lNkEY/9mBxMFK4EEACMEIwQAdPLTTvSOQemmP38ok28fe8xD1Y3fv9Qo/bN0iBEZ CU7hChZ1qRo9fdCPrpa5mucb6M/kKerb8/crNv+Sf4QCs60ACeSF7l6MvvRdpxhi MfUb55WtZOx5h49XBCqWgiiy4aiJVeTBAhE6HwGHbEB3lGaZegZgHpC5saFpThfz l0LgRF8AAgj82Fx6toX/ArOEyfr5OP0hZFxiuxFjns/EnOFaSJL1demx8+ZZ1k+J L3Rafkk/H7vCgo/HLXAo0+Kcnh6Pww81AiMxtAhuaXN0cDUyMYjSBBMTCgA4FiEE X0QqsIUJo02rXWcqfRhl6/kFr1cFAmP/ZgcCGwMFCwkIBwIGFQoJCAsCBBYCAwEC HgECF4AACgkQfRhl6/kFr1dUWgIIqPyAsrzU1RBJbJ16tCLd2rehsC2JGg5SpWbr BGmryPKEq6p/ked0Y2STJGmx6oyfuJgb9Rfi+zi3/rK+GWkqHMICBR28RlQJ48Mq pgzM4SV3uWQJCjNIZTUV6CQ3drS8gkyjW9A1D8nTuKDwnhbaGuKdqY2yW0rE1ns9 NU9PUEL6eVyd =PtBK -----END PGP PRIVATE KEY BLOCK----- ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/keys/signing/rsa.gpg�����������������������������������������������0000644�0000000�0000000�00000005020�10461020230�0020704�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������-----BEGIN PGP PRIVATE KEY BLOCK----- lQVYBGP/ZgUBDADB643kOc7JPF2060VyCddkRypM6zYgJCzwzwZ2ErayuFKaOAEy mLiRV8YQDqCQhlOGLf2mWB9tiBQPUDilgiAAJxXQkThrEZlQmYYqRCSW1OUB1UYY ujPqz9Y1mu+1d3uIt/vfxLH2kzqWWR2oRVlKEq9iFmevm5M6VUQrdXVCr3KitRly RUpClz8lHMvJ0+BWKWEQvVYCcj4g2OEBMShJ5eGJQf1hO60jskpViB+sytNoA3jn 7aXBWnmUrf4li/QkXMo34Sk4jla2YusgW3/6T4i8OEdIEKO5SCxb7W/PcQ9PwoQd VTzSJj+H3oTy8SaRmTfoPmjIZ3C/Ho9BDzsxiJ3MaGhQpA04/CcaBE4Q/XyFQjk2 jX8AJaMiXm9nqExhHTXBD+e0ojCuVOui7cYP7pDsecFwFPl/3bmH0DuUZkjbif5b mAfBM8yCIHHscJudy6HukCHomJl0yo+5AFTkuHDqEqz9dHKcxVPzZ+nfgFnh/suI ebZSAM21bY9SpaUAEQEAAQAL+wZtpQ/+PJUeK6sOK4iqu9mmpEfVej9Zg6Xd7wRl /WlyQ8a5+YwwVNn4k2e/3GG+GjG1/5UOkcgjPSr+yUnvRNlMcmQuFDU0aaUq2bA3 /xCC8S583+x867CqgBrZzn1w/wbatn5F+nJGkigevyVvWxTiBq9VN82x9hD2d6KL K75wq3eqUjQgCOSpN94mlLjVjbe9emrAJARDvjx/hAHym7fLZ40Rw3EANe6bf+xS 7V3ggV6O0ui5T4fX1HTxyZ18BKaS3QfDyH4I8PYQ0EMalvi7TftB/LTh8DO/6uVg gQc50/8Ks0TN/8za8oPuQs7vKPAa1swsrUPVM3g/gvsmE2C7+lSIte1FAhIlUB8J YNwJKs9hePkvIUoxb7091bOIKmvwpuuD3gXVDEPIMRZ7URP9rMFZpYzpHLjsdJ2H NXMFD4EOnj9yfxS6Q3JYiPkCTa3l7O9Qf+mWA11t1IrJbXJkyvnG550fxbbLT5Sb 8cd9C8JrsbPhKCtouH/2Neo/hwYA2MjfymYHO7OTndzF2DqONo75xxxZCbn70JLJ rpMlnth/T0dlJdkAlDjl5vDwa0UBPh/YsilCRIwKg1OfMWr/mPtmGxLo2nD3x9P9 IfXDJVJ7eK5ysgJcbSkzBpRVbNjkhMwBYR8/ZBRBPZTMNV1axkezQ48XusDtieM7 oXtVdftx7KY4Oale7MuYq04cIYYIMEhHsZ49BUCcoO7A4pA6VZSOPIDKGj/tNMVy laqLn5/v03DlbGGGlrh/koN6ThoDBgDk/9d/8cOQKHnM9VhBfQZZIu1HPrJWxcQM wJ8my4q8uHhVAWsHmOqKFE9zO9q80gubQ0via/G5H4g8RCoiuXC0JHQGDb1yM2Wr rSuEwPWhuO1OS/ijhbe3m0ok6b9R90yCk3H7zdURrib2JYlXt3mRj66UxzSwmH5+ aYxWLeXMG3lMUds3tAhPVDVqxIK9+w0C6uRBXxoUyPCUUlrfZaXN3CNrxbA24e/O CwfZaAI6H4uk61QpErOGJqxxbXwDBTcGALmwzPviCGdXDviEmwGEk40adqvKEv1O qf+uiaziK2plAmQqcYTFnbwZI2GFKc+EHt+4xNAVWyxmIZTN/Dnxe2nKt6U/TA+Z d6qVOlXmKPQs3+W69eqbboWTE3Yaq+4JveDSDHjHmmv/n217zJKKZ4806EPfH/HU jERE7vFkJYREaoIU1RidcTa9WfvlC8+ywYPPVnAANvx03IYQgab0pt6X27hB33Gy 08et7+RwYzJ/xs2sVVW2DGE0d+dPA/PtD/BRtANyc2GJAc4EEwEKADgWIQR6sUWx ILsneSbj2bmc8d0x4cQXzQUCY/9mBQIbAwULCQgHAgYVCgkICwIEFgIDAQIeAQIX gAAKCRCc8d0x4cQXzWoYC/9f2XGndqCCBrysUPXgo9QafIymhjOeB2fkHIDAF/LK 2mHVG6TdMakt2QcfZmaw9eRl0ZfQzM6zCyNF5E6fAF/j3HfwAfcvVF17yiKV+gqR ZriwS5aFkGTK13qaVYPpEh2M9B/H/FF7lyPIi2KF4tdtT8w4p/FiIZeSptgN34iw 7J1wwsPi2O3RDvo0WivYNIAUdprNHH0URqjMyk+lpoduLHVqCimsF+/3F8+ZhYOV 3/Gf9kZc0Pbq5EPjvZMkkE+OMSqQrMiNTymzVqmVk5/4idds11dIDJyARXpjsQWx sJYZNuh3azpldE9Kj+UJIODZLi5Bq10VmvrycLXSm32SSZKEkNh0/uW5JwjBKNzz BrS7v41d2M2PqU72o3oJ7ffoLdDBqnTLoG4Ma4e8Z8h+Hqx5e7fkecyL0Zz1WejR q8lFChdIf/nJ0XYYmMJPVwQWwWL0yx9XHsK9BeejTA+VS1Phowfy5OH0pXLVojPE rGJyavLV+MABKZAuzdidt/Q= =erqz -----END PGP PRIVATE KEY BLOCK----- ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/keys/signing/secp256k1.gpg�����������������������������������������0000644�0000000�0000000�00000000707�10461020230�0021551�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������-----BEGIN PGP PRIVATE KEY BLOCK----- lHQEY/9mBxMFK4EEAAoCAwRNVBy4k9x+BkW/F0uTzd+nlrFYtORpGYIPpjbgjkqC 5F/AIHUrOUFrWZ+5fwbNeLu/u1d20w/xddvNgJP412/tAAEAoRqs012+WaJYFfnn QIL6Yi0TXo8Kf9Ray6mcU0ex+xgQDrQJc2VjcDI1NmsxiJAEExMIADgWIQQlx+Pu Au/0pUPe/6z9uYYncIt/TgUCY/9mBwIbAwULCQgHAgYVCgkICwIEFgIDAQIeAQIX gAAKCRD9uYYncIt/Tn2SAQDGiob5FgHVaEZ/IZ01d7V8yPynSrfqm/aMaRzizptc PQEA60MmAIQqJKMRT/0SxSGgLnkNCcZ8xpV5MvsbDXIHZ6U= =mzDG -----END PGP PRIVATE KEY BLOCK----- ���������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/keys/subpackets/marven-private.gpg���������������������������������0000644�0000000�0000000�00000012663�10461020230�0023600�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������ZY"m�9[Rнho^f\ !`=>Kuʹ�-S'j=ĄTzpn1,E>㼩kR@Xj^6% pFs5;>U! e3 ԢIBnT &/4..�TD#1).G8LxB4ekOx""`&.ڡ}5*23&$x;ZZ ;o^2 R`8sRW(,3- K?n2���D:N6 ^\P.mbjnoe,"TQ9۟z'*B0u&u$'EB;`rҹkJ wfi*$cս5m; m;ĨqZ$׽-ݕ\>41*c&>-ā1#cEi<5i"y >1 Sx+CEc8 ȉ +H9&WCq�h? דudR(f/pza8qmA-_ʵscZSRYoEgjM.75=rjNC314L{Q(ΐ.uOot A|hg7 U"x{>�:L }PNe뛃5&K9TqS 만Fm1v DVK^j$huDAjH@gP(p?m5i�ٺXn~(ڟAm˙="9F<RnJVj"9 wz�;@8 %v"&<Fџ`U@JX^'펩јP!΃ѶTBB@9N�8! έ! 4yWZY$ 6[ml%\Xa&�� έ! 4yWMFdF Dw_czgc`e8װ&3rߨg2%̨=GV̕6A]>֯[j02:sƏٶx]|-b4㾱(G{hÙ PIaUlva6vϰg+/OO+cvh(3x / w3l#)Bƾsr9L%n"(~lkwرN śB`NM.š׺ dN"Richard Marven <r.marven@navy.mil>T�>! έ! 4yWZZ g�  � έ! 4yWnaρ%CSTvңpY__M7UҔ-H{\ncEaȄcT8 UzX3LGn@3k )#mBh*qWXͷ4j⚈�p}p3’ln.GV�JLpl2udsbW4,d/UuV !Ecًo}+oA8Eq$ad6t&8>4< S] 6� !6[ml%\Xa&ZZ�� \Xa&:�h?vdA1ERl<reU]q<p} vas2q1=a$6zCd1+^/+=<= 肱&j" >9xk1]&(R{TQ |*ЯRfHx5?B hqcΫ/; N?JLi q1 a_ȋ:_$Jp=vr3X}@a9!rVWi9j\!Richard Marven <rmarven@navy.mil>� g�&���� �rank@navy.milthird lieutenant���� �foo@navy.milbar#�����whistleblower@navy.miltrue! έ! 4yWZY$k  � έ! 4yWNN#Y#<8F}5]ӓoAs$ j#q Drcs̮֧p;`e@+&B\<s3A)[4,,0ԯK7*9l>լ k{& k#cV ָFz&dAˆrt ;h Y�c u3<׎脘iN-RHT9~|[xNC �k8v0QRkO�9!6[ml%\Xa&ZY#wx<[^>]+[@.]navy\.mil>$�� \Xa&�Gکac4^&fþ/Y]̲GB|0 yūסOcه-:Y3ON�RC1)~eYc]tp{[k/yҟ =/I], D}E^f 7 ] {;z¨PA@1Dʈ81e.`$o =X| 1GuUr5>b,_xU2rЃb(5O><oZY"m�/鲯rsw¡YPhކ&zB3Z5BM5(xSN k9 9&Bue$}7 /E b&%B2m/Vdq2~*mJ&3ku 1KCbk;5<xhYّ%G@kn*>('G" .ӎ?6D܁>2GA)dGxvWu"t >:BGG&M3HoR#���I3D3lĤPKve&'%9+J vP3m$-xr.mjs`HreO|p\n6Bl}⚗|=i-%ϻ,=g.Pp_f3�ػUqJCʥaq77\¡b0atB'M!`iƺKe+X ךd@.9v@uZQvioK,;̕& }V!{b &z�͑t_Fsba-cm)Fha$mC^X$c nw◮v(x#jUmb%szFTN2)j3KT>ØA醠va�A((s�~Y=V˔A*лݣ9jga'UXsA[2eFiŷ˨<~ҙ޸s x 5x(** يQE! Y'KCȗ "�;~Ʊb~tOtmmCFO�� ~ s<A3oT`Q f?GCG\c=)AbL\2I; &`Sf([<'OmR$> mw_(KC6� ! έ! 4yWZY"m � έ! 4yW"xs=ͥLMf An{*~.PCo@Olb0-CZkR|{z!(YB4El!T栔_Us>�؞7s۾WpO[z!X2CI2qW27ݵ"~QpÕZ?6c |MLM 9�9}u.PDj<wˣ(R`YyM7Xi;Oi:.|_| FކZYA�߮q]o:IQG<4MT'BJ@~%\ȝ ~n4p\�Ьd5A;NeT5Ic4{\3 ?{M؟+褉yƛSxs~7b-s6RlG"I@]TMпa @Rgʈa!Tnb4x=! O.mT53 VeIbL5ۭmn`E.+=^r#:J( .|HU���a iqԁ#FJډJ_&14js w 9ə$blQ jurC]sK++ W.ruX!אN@es ^O,:,%("9[M=}sΟKlM1QI})KƱ2w:b{ [/$+34 پfB.;̩IwzAE .%wXIIN'�J.? !SʞzQ! )K"ͼ~|Xt߃Z>G9jWR�pA}4̖hB~MO(O;RG! p=ө.0R87F"Q bf>�D'Xآ6 l1>e4n[9n@M~# {_UjCas<ruGT0[q]b4sG >Ms"}{J'~_�{s BeǤ&Y]բO*~XA2)fq⌄ikP'<R(s<]_m?Wi'F45eܤIoFh9r�&! έ! 4yWZYA g�@ έ! 4yWt �!BGp4]_FlZYA� FlztOe{m9[Oݶ;d~mCZ{vW@7!p%Bb; Bұ < E@.R2q!.ҙd_EОzKP& *nA%B'0h.}[ſ j xfB)[N{ճQuEfߘG/5;~&3,n]۰!mÜYΫ^Go[\NԲ}/g˜.!$Al$xR>  onM7נ_ed>m8+xl"13i,L~|$ӎRS5NRpͷ7eYH%`_ A]W+M/S;UnDW[Oy'⚢ F0SK,QQҪ]<.:>1fߏWc#!ԓ9a k*ʒ}A{7�zN"}i4{Cަr6S@F 8>;D fL�����������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/keys/subpackets/marven.gpg�����������������������������������������0000644�0000000�0000000�00000007553�10461020230�0022132�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������ ZY"m�9[Rнho^f\ !`=>Kuʹ�-S'j=ĄTzpn1,E>㼩kR@Xj^6% pFs5;>U! e3 ԢIBnT &/4..�TD#1).G8LxB4ekOx""`&.ڡ}5*23&$x;ZZ ;o^2 R`8sRW(,3- K?n2��N�8! έ! 4yWZY$ 6[ml%\Xa&�� έ! 4yWMFdF Dw_czgc`e8װ&3rߨg2%̨=GV̕6A]>֯[j02:sƏٶx]|-b4㾱(G{hÙ PIaUlva6vϰg+/OO+cvh(3x / w3l#)Bƾsr9L%n"(~lkwرN śB`NM.š׺ dN"Richard Marven <r.marven@navy.mil>T�>! έ! 4yWZZ g�  � έ! 4yWnaρ%CSTvңpY__M7UҔ-H{\ncEaȄcT8 UzX3LGn@3k )#mBh*qWXͷ4j⚈�p}p3’ln.GV�JLpl2udsbW4,d/UuV !Ecًo}+oA8Eq$ad6t&8>4< S] 6� !6[ml%\Xa&ZZ�� \Xa&:�h?vdA1ERl<reU]q<p} vas2q1=a$6zCd1+^/+=<= 肱&j" >9xk1]&(R{TQ |*ЯRfHx5?B hqcΫ/; N?JLi q1 a_ȋ:_$Jp=vr3X}@a9!rVWi9j\!Richard Marven <rmarven@navy.mil>U0�?!6[ml%\Xa&ZZB!�Forgot to set a sig expiration.� \Xa&�n0e Z|![|9jk S?ɛ[P]ă]+?7@{g~n+H6"Eҡ(NH`U2+KH_m\> -c:|םdwG&WJ L O P2c`seY|\s_WZ]EՊŗt$GDYd4wo<8SBOu_~! 6d^1.+_޾!~� g�&���� �rank@navy.milthird lieutenant���� �foo@navy.milbar#�����whistleblower@navy.miltrue! έ! 4yWZY$k  � έ! 4yWNN#Y#<8F}5]ӓoAs$ j#q Drcs̮֧p;`e@+&B\<s3A)[4,,0ԯK7*9l>լ k{& k#cV ָFz&dAˆrt ;h Y�c u3<׎脘iN-RHT9~|[xNC �k8v0QRkO�9!6[ml%\Xa&ZY#wx<[^>]+[@.]navy\.mil>$�� \Xa&�Gکac4^&fþ/Y]̲GB|0 yūסOcه-:Y3ON�RC1)~eYc]tp{[k/yҟ =/I], D}E^f 7 ] {;z¨PA@1Dʈ81e.`$o =X| 1GuUr5>b,_xU2rЃb(5O><o ZY"m�/鲯rsw¡YPhކ&zB3Z5BM5(xSN k9 9&Bue$}7 /E b&%B2m/Vdq2~*mJ&3ku 1KCbk;5<xhYّ%G@kn*>('G" .ӎ?6D܁>2GA)dGxvWu"t >:BGG&M3HoR#��6� ! έ! 4yWZY"m � έ! 4yW"xs=ͥLMf An{*~.PCo@Olb0-CZkR|{z!(YB4El!T栔_Us>�؞7s۾WpO[z!X2CI2qW27ݵ"~QpÕZ?6c |MLM 9�9}u.PDj<wˣ(R`YyM7Xi;Oi:.|_| Fކ ZYA�߮q]o:IQG<4MT'BJ@~%\ȝ ~n4p\�Ьd5A;NeT5Ic4{\3 ?{M؟+褉yƛSxs~7b-s6RlG"I@]TMпa @Rgʈa!Tnb4x=! O.mT53 VeIbL5ۭmn`E.+=^r#:J( .|HU��r�&! έ! 4yWZYA g�@ έ! 4yWt �!BGp4]_FlZYA� FlztOe{m9[Oݶ;d~mCZ{vW@7!p%Bb; Bұ < E@.R2q!.ҙd_EОzKP& *nA%B'0h.}[ſ j xfB)[N{ճQuEfߘG/5;~&3,n]۰!mÜYΫ^Go[\NԲ}/g˜.!$Al$xR>  onM7נ_ed>m8+xl"13i,L~|$ӎRS5NRpͷ7eYH%`_ A]W+M/S;UnDW[Oy'⚢ F0SK,QQҪ]<.:>1fߏWc#!ԓ9a k*ʒ}A{7�zN"}i4{Cަr6S@F 8>;D fL�����������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/keys/subpackets/shaw-private.gpg�����������������������������������0000644�0000000�0000000�00000005005�10461020230�0023242�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������ZY".�Eq1l6sԘ!U>lHt+{)0h 4ʯNk Jω!@B ]J;h?n i*YCHƃWKSyGθ/3}พMq�qSEAԷqt ^nFp7W]cT=20Lpl Xe#d{Bca1(Trٴ65u._~j&5uVgZ*PWrŘ9 gVh���Gu2dmrR8T'j2l# )vã$(Y`R-q8ןpA7l[sx B(j(5Fv=)Ğ^W5$Br8pV޶N>]<K], RDHη�,EKu/yT_; U_rxFi,Iuh+ݲѬ`udq1IװL=//Q&�3%V[Pu# QzV0"$m=)S) z9ylH8Mn")aklPW+F'�XE=W/?bY)O3Gik>X 85. �7ΓqS1쀟 [Ҕ6Q|mB5F +Az2Kk%Քй``KM=\K{]i=C|+s֬urn I}MSdQ76u?hPްCRn!HWn׷N%b35" dfUBۿ *VD(j׼K>7 L|E,;Ew ](Ȭp5%-{M-A˴Samuel Shaw <sshaw@navy.mil>u�_ g�  !6[ml%\Xa&ZY$ ���� � rank@navy.milmidshipman� \Xa&�«+9;2!~%A0oqVoR7s`z8)9[߭Gט%)#%hZMɽ}O: 7j?Dr t grru, |i̅`sY6ܦTGq2K y kv_,%MbYKs4=~sl;$�D[WS&t2x: @tK,d_lZY".�4bZE:Th$ z@2^xgEqX_"T #>&o-1~dJH za.و1>Ŧ6Fv`�s?D60b\N>&['rB,ګ\Η̠39,~!\k{] j9+3B�}hWu *CLqHk5 k+!J".)yLovvJE7=nLJ���ae4@yr]G'δMPELjYb-)=lzd-˽!O3嘑*|:J-1[Ka ,MեyQbu] aUW&hbP,r5)vp4vyL`H(ۛ:y@\:щ>f?\X@ai_{1itzC9 RM@HV+7 ա0Y 1XDq %3M<Iwi�#R{s<>OFÑ5r馣Dq蕳j]uHI`Iv/8TN;&':`3翄i�V3ъV�sY`#f-&(Z9svwVӅ�>c|l=SbArMb V\0˻-0?dFNnpQ+3\skL }}zks?{%¤P-0GBoӠ~Q{=9ҫD Qc� 5KAFr]QMWbhP+|CxU&bɼ|@ 8 k>N -b XCڐđ'!î2k?r:ً>&9 Q�e$(96� !6[ml%\Xa&ZY". � \Xa&0�ksNW4ykeEu.va6%?1cu+ [C+sX&DQ_X^>vWl41xC`Ns7Wne4)i*.c2_lEr_! Q_ب!.{I[Px(, P!BR"u*^>Ԡ@#=GLS*P<J"_/,ΫaTΆFpgj%FjwZ%t���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/keys/subpackets/shaw.gpg�������������������������������������������0000644�0000000�0000000�00000003050�10461020230�0021570�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������ ZY".�Eq1l6sԘ!U>lHt+{)0h 4ʯNk Jω!@B ]J;h?n i*YCHƃWKSyGθ/3}พMq�qSEAԷqt ^nFp7W]cT=20Lpl Xe#d{Bca1(Trٴ65u._~j&5uVgZ*PWrŘ9 gVh��Samuel Shaw <sshaw@navy.mil>u�_ g�  !6[ml%\Xa&ZY$ ���� � rank@navy.milmidshipman� \Xa&�«+9;2!~%A0oqVoR7s`z8)9[߭Gט%)#%hZMɽ}O: 7j?Dr t grru, |i̅`sY6ܦTGq2K y kv_,%MbYKs4=~sl;$�D[WS&t2x: @tK,d_l6� ! έ! 4yWZY$�� έ! 4yWigV#EMOfi-ٚqj~H�JFEnU2mq)\]YED!i xR.`Y3Y9C0]07&{2ds "{s^q}ZN[^ ג GmkB5ɑcP,6/7#ַ/]hv &,;Z_Cd ëm"vG"nOvN@W/,,8pF?Ve8 sgy ZY".�4bZE:Th$ z@2^xgEqX_"T #>&o-1~dJH za.و1>Ŧ6Fv`�s?D60b\N>&['rB,ګ\Η̠39,~!\k{] j9+3B�}hWu *CLqHk5 k+!J".)yLovvJE7=nLJ��6� !6[ml%\Xa&ZY". � \Xa&0�ksNW4ykeEu.va6%?1cu+ [C+sX&DQ_X^>vWl41xC`Ns7Wne4)i*.c2_lEr_! Q_ب!.{I[Px(, P!BR"u*^>Ԡ@#=GLS*P<J"_/,ΫaTΆFpgj%FjwZ%t����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/keys/test1-certification-key.pgp�����������������������������������0000644�0000000�0000000�00000002303�10461020230�0023142�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������ \D�%9d.ewXB[{&0( �Sp] :=ϳ"4Yڿ3$dլ [? jut5@.y+'Qg{DC? gOFnR_DV7� CۀYa^"fYNʓҡNc6?&G)ӻ"WmŴqńɨ/kLem3*5{p>[��test1@example.comT�>!F1d/\3#u\D g�   � 3#u.tvY˓yDYv7y`C ri$wxN(s r:N*7nbM 2tp N}?˽u<,w>rj~Mɼo}iGj ?:nb)[v뛻wӂlS2d?G<I7bE''BLaHz0Fq5+X(}Lə,[.;\I7d0cyR9ɳVkn \D�JuS5 X*siFPtVN[|ԸO)%,(/0f{~�{ ZJQq)?OwONJ费q}|'QdaˋҘb`4N;&d;7_v4/BDD @[ȟH*RU, QcBKVѶ?lFʘzE f\Mmʞ֚.r)&Z[J7ic J�nsKiȉeG��6� !F1d/\3#u\D � 3#uv�߭(#~黍K,Lc:Iyc?%oT13-_<<GM g7U? W[a@EDeo(;%U*$jA=xJkY&pZBd|M�(NHj{/9K}` �1F&1.Epjw|񡇷H=n;Ognu~i2Sj.˄G( [2cQ$8}LL9%([p\SڨO<Ȍ~U}�����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/keys/test2-signed-by-test1.pgp�������������������������������������0000644�0000000�0000000�00000002771�10461020230�0022462�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������ \T�D]bX.: M s(vO[ Yך/s:lNم>6s=%p\V7 s4ʧUЅ�GVxs]ފl S)%6N3'ȹ$m~4ϓ%ֳ HxVa~SZٓ:!/ݨu؛Xuoc9EqIv L[%?4i+U-:\V5C}TiRL[H+DI09��test2@example.comT�>!'?a&\+\T g�   � a&\+ҷy&Pm)49�i[wZ0>0H qSJFbQ?}&'i.n\jO׾m"SGvxHU)Fdh$@w[)b95̀DJF|$JqW͡ox%2*^,L\T)%_N1י E͢ /713vyYc LBWiJBĂ3�!F1d/\3#u\� 3#uOBn�=DHX.Rjv˘; ?E[rCyn-VC$|pêCJqS}y'TC]5IU ̽*s7SU>|K}/$C^VB9ecLܕ&Dt wh^ "&E}B2CH*-xÄkd~ 'ZBL9 Aݰk8}V3,(yMƦ*!ߣD(P@ι \T�.8f�WTXlaG{jhӕ$>_W/U?$h(=@J9|4a/91AeANS=bDfZqCܹ~qz4 _ <~?-K˺M%dz`5fD 7,myAT3:?:�StLa1QۮD9e^0D#Z>o-�غY1��6� !'?a&\+\T � a&\+筼P< 29ci-Z6ϫR$gb]t#Me^]t=;ߨ><G`;qP$N:}:WҊrrx-rk?6I[ ݭKJ\ta문vj5f11e$§4d]S/EII5⎞#'9n"ԂV!;F-mqzK"o<ѓIҶ80ԧKj_ * �������sequoia-openpgp-2.0.0/tests/data/keys/testy-broken-no-pk.pgp����������������������������������������0000644�0000000�0000000�00000001706�10461020230�0022151�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������$Testy McTestface <testy@example.org>T�>!>ww'FQ?oR&Zr g�  � ?oR&ƏU̷jkHo|)gP4c̈́"KˏQߜY-{\CCwܞXH-֠{ @V<pY|hU-dx)hl {fh^I 曲X!wj ONN˩*j$ge!i 3Yhܰ-.tF:L'C!~Rp4%X3WCHw_Xҥ ueTu=1>QF A\|w46(f{¹ Zr�傮>YGh<">;v7R_YދFkM$�E9u񆇴:'JSZ1ޙ54&("<!+ E`Z9Z$#-P;=JrxE3Y9�m+R^j{E`*;*Aff'3]p Gڷartj`D]VX^|M.̚\f+I7��6� !>ww'FQ?oR&Zr � ?oR&3<Ʀ'Xm 9C2!QLG/$8|m{G>^1}E) $^1hlyS$c`fP䦈OO۵mo'?dxI3TMABUÙˊ1hr|]mHᚕs�fQ0N/ZH+,ȿ!짱oB+ɹ~u>q>{;V:'5B/1C6^pZ����������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/keys/testy-broken-no-sig-on-subkey.pgp�����������������������������0000644�0000000�0000000�00000001635�10461020230�0024234�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������ Zr�2%E$O:pbx£.&.|W^';CBL}M5j΍$F=_+ tKt3ja1W朧LEsA9}&ٲ-5tdjY%1'hV-?ҡ~6!GܩGtBc3<k/6^[ՁV~f<y)Yh5s1�LHxtҳ]fF]պv9I��$Testy McTestface <testy@example.org>T�>!>ww'FQ?oR&Zr g�  � ?oR&ƏU̷jkHo|)gP4c̈́"KˏQߜY-{\CCwܞXH-֠{ @V<pY|hU-dx)hl {fh^I 曲X!wj ONN˩*j$ge!i 3Yhܰ-.tF:L'C!~Rp4%X3WCHw_Xҥ ueTu=1>QF A\|w46(f{¹ Zr�傮>YGh<">;v7R_YދFkM$�E9u񆇴:'JSZ1ޙ54&("<!+ E`Z9Z$#-P;=JrxE3Y9�m+R^j{E`*;*Aff'3]p Gڷartj`D]VX^|M.̚\f+I7�����������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/keys/testy-broken-no-uid.pgp���������������������������������������0000644�0000000�0000000�00000001531�10461020230�0022314�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������ Zr�2%E$O:pbx£.&.|W^';CBL}M5j΍$F=_+ tKt3ja1W朧LEsA9}&ٲ-5tdjY%1'hV-?ҡ~6!GܩGtBc3<k/6^[ՁV~f<y)Yh5s1�LHxtҳ]fF]պv9I�� Zr�傮>YGh<">;v7R_YދFkM$�E9u񆇴:'JSZ1ޙ54&("<!+ E`Z9Z$#-P;=JrxE3Y9�m+R^j{E`*;*Aff'3]p Gڷartj`D]VX^|M.̚\f+I7��6� !>ww'FQ?oR&Zr � ?oR&3<Ʀ'Xm 9C2!QLG/$8|m{G>^1}E) $^1hlyS$c`fP䦈OO۵mo'?dxI3TMABUÙˊ1hr|]mHᚕs�fQ0N/ZH+,ȿ!짱oB+ɹ~u>q>{;V:'5B/1C6^pZ�����������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/keys/testy-new-encrypted-with-123.pgp������������������������������0000644�0000000�0000000�00000005132�10461020230�0023704�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������[�IA[s|wS"d@1kXD<YpZM)T}ˎVU8d)Q9nF8c:O(IҊNRVK~Cߕ3H ;8s߫>l<bֲ,'짅_fz�cDl7$dسG; 9 ]"Zsyq)?�s}`}tퟤ|crAS]szPXa~f A��Iui:r@Wޭ+Fq"I(gԲ?(4t n#-.+`@Ȩ8˷ށ6 MuѮuHnN(ҏ|XOV"UL7z~P0&zd΅\ٙS7s&yy EJMyAE6DyLU3y ?}̋iy;b|c!ZDCԭ"b{v3J'9÷̲jlqH!(Og t-md;WeČ,WSIF/~ >! VwƵQT=jr}S Yc@ZzuWԣ"`0>GV9h*S< mBbog@]T_ qt6 k:Cnb%9UֱCjִV_þSg Ҕ)ެ#JKY!Up oͱ@?>+KvI'A0&VJ8f0'hImGqڹ4H^jsH x g,J[O3n:|IB(B5IJn;BEʀs$9<^tW'KbZZM^N,׎U<<Testy McTestface (My new, encrypted key) <testy@example.org>N�8!K0plozd/F[   � d/F-́h=o!k:(hMvQ{~ݯlrSEH_Mke6-^g;K v{ A#2دUuIVl@D7 6L$kYܽoIOqnPwF4߮|cAIO*ݾ4Fַw'\-xXɎDp|k 1SzL9DZvuŒ[�?#WA\+bZ[n`(dKz]|Y.WyK#sDZYh`_ Q8o;'S5fh+NeG�pl.Up?k[8Z!EMi8Eu&觶2`@xwb!;}$5 $t.hG#+q|/Tv]0.II|Cg-p1��* ๪ώퟷ]fh{e/^Ò #be?_=;M4'e[ 7L 2dܯvQ_y|pvߡ]Y+ D;#ސ=RVL47䖫w#;o[h<ռnܺܓ<!O[CXb/x,Nf{G:#pqy\_iW\>KڌnlLSX^p3`R+MǫI#Evj`Q+ Mrqd%<o6fc[px*œbe ,k:cD00[q=5gҞ%ùsZrCC $:iE@ѝ^t8LcSY&fMW? $}Sk1਷YꞦ[m*|+%9SM@ڱ<{Wl_6:1ws{{ŀ,Wo@lMQ?bmr<6 YD2a&.8vK,crZ:D>Pa1ȶҰ:?/A`>R@ߨS33v U(&FmQvBPh W=jR?$?zO7~^조2},CiZJb]}n>^NN Ѽ2327ϒtRY,Pjf Y ^YΓMryr6� !K0plozd/F[ � d/F� 궷#xpdEeV9wc;r~d93Nh+\qGW5=eun;Xmj[7C4lf $ /8띲H~lў07arQV%`@xB#WCKa$4%8ᑧmtm q4e9cQn>3yQy-=h}{ Õy5Hf}7I /yŔYܬ'0�/GW^x8vzb0��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/keys/testy-new-private.pgp�����������������������������������������0000644�0000000�0000000�00000000770�10461020230�0022110�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������XZSo� +G@@F<XU`U| n^+`,��B2,Ԁ7>a7~sP(C&}1Testy McTestface (my new key) <testy@example.org>�8!9�gս7QX}ZSo�  � 7QX}$t�?�LYG ?g鈅+$Ƀ � ֐�QGR$X;p~<Xs]ZSo� +U@~A`t5=gxۘ9=%ZԵRZ?��vY8ŋ9Ž>`~:hx� !9�gս7QX}ZSo� � 7QX}2�X_u)x?~4�[hkq|e�;'¹A&>oxsI3} ��������sequoia-openpgp-2.0.0/tests/data/keys/testy-new-with-sig.pgp����������������������������������������0000644�0000000�0000000�00000001344�10461020230�0022167�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������3ZSo� +G@@F<XU`U| n^+`,1Testy McTestface (my new key) <testy@example.org>�8!9�gս7QX}ZSo�  � 7QX}$t�?�LYG ?g鈅+$Ƀ � ֐�QGR$X;p~<Xs3�!>ww'FQ?oR&ZSpE� ?oR&"yt"†a L8Z:ЖėVCB ob<`Y>~ȉn|؅3foN= |�#B98P\^6 +?8]ܰ\ӱ! yUq|ɤmo?x}sM-~<ךn$Id__}W0/9n_ *3 bEUY49JGmN5i@\6'2hz*Q6-8ZSo� +U@~A`t5=gxۘ9=%ZԵRZ?x� !9�gս7QX}ZSo� � 7QX}2�X_u)x?~4�[hkq|e�;'¹A&>oxsI3} ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/keys/testy-new.pgp�������������������������������������������������0000644�0000000�0000000�00000000656�10461020230�0020443�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������3ZSo� +G@@F<XU`U| n^+`,1Testy McTestface (my new key) <testy@example.org>�8!9�gս7QX}ZSo�  � 7QX}$t�?�LYG ?g鈅+$Ƀ � ֐�QGR$X;p~<Xs8ZSo� +U@~A`t5=gxۘ9=%ZԵRZ?x� !9�gս7QX}ZSo� � 7QX}2�X_u)x?~4�[hkq|e�;'¹A&>oxsI3} ����������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/keys/testy-nistp256-private.pgp������������������������������������0000644�0000000�0000000�00000001033�10461020230�0022702�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������w\eS*H=gs }wb(e8М5"`Z)~2l3<nnkI٢Zx4��nKof~k's5test1@example.com�>! MfUέ yY\eS g�   � έ yY�&(!MJ|}#4,< +BnS]�tx /3-%7$3vꉶ{\eT*H=f<8R}$8P`X; JL.I_4L@v\Ze��gdK Ǭ>lJWMQx� ! MfUέ yY\eT � έ yYꕯ�i[_Ik҃& h0}NT!�2%+PQ hwSU�����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/keys/testy-nistp384-private.pgp������������������������������������0000644�0000000�0000000�00000001265�10461020230�0022713�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������\g+�",#ҩ}x%85ֆ9'W.{>%턌ژ(u X 9iwP,I"Y41@@xؗ�:}C4ŀ(/P+C<N~p $言YZcZԴtest2@example.com �>!hR.qOϺ!J^\g g�   � Ϻ!J^4c\7؍zV.ȿF?-N5&eh 0eU` >T밌pPkX-ZMw傗:!s\g+�"Z-xֵ^Ou6 2 obb3p'en,v}uj@ ?n61'Gyљ+C+ZM" �| >[lX^p BUM^YH~$ PZ~1D � !hR.qOϺ!J^\g � Ϻ!J^ՁZZu\U$;JqJQ63fBLa)2CeiQYV*2_-`>lխ2 ﷗w .>lZ.R�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/keys/testy-nistp521-private.pgp������������������������������������0000644�0000000�0000000�00000001547�10461020230�0022707�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������\g+�##�Xܚ u-8ƫLW7\,& x+'yD&-oTflӅvB�vp%$A@G[P^Nj4v$ Y㛏RFƅهZ?l$&FqZ�m� ^Mnp?x\F#N/nWJ3`0ߏb_& d/C%/Q0^4 $itest3@example.com �>!9cRѩ:!wW\g g�   � :!wWQ_ *_i^wB1EVؘ-@ғI^i� L^?T h,<6~juP$ !ۆS X@bLJV�\rv2lS\g+�##�vr.u-x=dKf6yOk:;6 �a"j/5 ȬEvgiT(pP?t4Gr=!k GXo4 �or>lO|)k-Bfn6�/ߟ3yĚMM8̿lc � !9cRѩ:!wW\g � :!wWu _' J1j}-4׊ѵ$}pK0zGѢ_@x^>!%3 =B$^ʇ?!ȜZ˃yhXP&@L<&qS*nrc1[s1na���������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/keys/testy-no-subkey.pgp�������������������������������������������0000644�0000000�0000000�00000001215�10461020230�0021556�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������ Zr�2%E$O:pbx£.&.|W^';CBL}M5j΍$F=_+ tKt3ja1W朧LEsA9}&ٲ-5tdjY%1'hV-?ҡ~6!GܩGtBc3<k/6^[ՁV~f<y)Yh5s1�LHxtҳ]fF]պv9I��$Testy McTestface <testy@example.org>T�>!>ww'FQ?oR&Zr g�  � ?oR&ƏU̷jkHo|)gP4c̈́"KˏQߜY-{\CCwܞXH-֠{ @V<pY|hU-dx)hl {fh^I 曲X!wj ONN˩*j$ge!i 3Yhܰ-.tF:L'C!~Rp4%X3WCHw_Xҥ ueTu=1>QF A\|w46(f{�����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/keys/testy-only-a-pk.pgp�������������������������������������������0000644�0000000�0000000�00000000420�10461020230�0021446�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������ Zr�2%E$O:pbx£.&.|W^';CBL}M5j΍$F=_+ tKt3ja1W朧LEsA9}&ٲ-5tdjY%1'hV-?ҡ~6!GܩGtBc3<k/6^[ՁV~f<y)Yh5s1�LHxtҳ]fF]պv9I��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/keys/testy-private.pgp���������������������������������������������0000644�0000000�0000000�00000004754�10461020230�0021327�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������Zr�2%E$O:pbx£.&.|W^';CBL}M5j΍$F=_+ tKt3ja1W朧LEsA9}&ٲ-5tdjY%1'hV-?ҡ~6!GܩGtBc3<k/6^[ՁV~f<y)Yh5s1�LHxtҳ]fF]պv9I��� uy.240mOoGsSs <a֔e%ʑrݏ5kИ*lfWR?F2%3}$+p63#r!>LH&bAF}E.륒J H/{X2'kEngl;+1 ';Rר 5bB0pz?yB+͇^@Dq^HM3ZmeQoSIJ{z\_|a�ōL/E" בYz[]Fw{SINp͸C_�z;�O䔟xEb4B@(2MPKhwTLt�GPhv�ecci}XKÓ`@.>2gnG#Fщ:Ғ,Wwd+B[8c[ "hҕ`?+~fL*l >^.F`߰]%Wv aִ5}5pN4bIrn%^ʠ^h6L*S aÍ{Ik,$Λ$\y=lqV+%۫!Y?Oaie4rM<=8$Testy McTestface <testy@example.org>T�>!>ww'FQ?oR&Zr g�  � ?oR&ƏU̷jkHo|)gP4c̈́"KˏQߜY-{\CCwܞXH-֠{ @V<pY|hU-dx)hl {fh^I 曲X!wj ONN˩*j$ge!i 3Yhܰ-.tF:L'C!~Rp4%X3WCHw_Xҥ ueTu=1>QF A\|w46(f{Zr�傮>YGh<">;v7R_YދFkM$�E9u񆇴:'JSZ1ޙ54&("<!+ E`Z9Z$#-P;=JrxE3Y9�m+R^j{E`*;*Aff'3]p Gڷartj`D]VX^|M.̚\f+I7���I(\< WRhNSp^nV߇Vz579"8 J.p"<)G4kJe~`T`X&({ST̲~#"ʍy7j?: xWi0Ĺ"Lhtu�eVu!!(RH*'uu+:a@zĔ$ᜎL2{`[X`&ճLXRNdOGQݞ71tQTMwuOirGO,'1M�_J<M?BoNܜD] & }[VOA孉dPZ 58eD)V Bt1&*|[k,ε ZNe[+a \<vgT; LLlvNL�XϒA0z@c"A)澱BA*~-apExozebuIō(n"h S&SgC<!,âf_孜/٭ |X=�~ƴF:y ,ekP{I1R5fv~dvj B -{*e4ķqJ|FG<5a$aL8PSo?+7>6� !>ww'FQ?oR&Zr � ?oR&3<Ʀ'Xm 9C2!QLG/$8|m{G>^1}E) $^1hlyS$c`fP䦈OO۵mo'?dxI3TMABUÙˊ1hr|]mHᚕs�fQ0N/ZH+,ȿ!짱oB+ɹ~u>q>{;V:'5B/1C6^pZ��������������������sequoia-openpgp-2.0.0/tests/data/keys/testy.asc�����������������������������������������������������0000644�0000000�0000000�00000003335�10461020230�0017631�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������-----BEGIN PGP PUBLIC KEY BLOCK----- mQENBFoVcvoBCACykTKOJddF8SSUAfCDHk86cNTaYnjCoy72rMgWJsrMLnz/V16B J9M7l6nrQ0JMnH2Du02A3w+kNb5q97IZ/M6NkqOOl7uqjyRGPV+XKwt0G5mN/ovg 8630BZAYS3QzavYf3tni9aikiGH+zTFX5pynTNfYRXNBof3Xfzl92yad2bIt4ITD NfKPvHRko/tqWbclzzEn72gGVggt1/k/0dKhfsGzNogHxg4GIQ/jR/XcqbDFR3RC /JJjnTOUPGsC1y82Xlu8udWBVn5mlDyxkad5laUpWWg17anvczEAyx4TTOVItLSu 43iPdKHSs9vMXWYID0bg913VusZ2Ofv690nDABEBAAG0JFRlc3R5IE1jVGVzdGZh Y2UgPHRlc3R5QGV4YW1wbGUub3JnPokBVAQTAQgAPhYhBD6Id8h3J0aSl1GJ9dA/ b4ZSJv6LBQJaFXL6AhsDBQkDwmcABQsJCAcCBhUICQoLAgQWAgMBAh4BAheAAAoJ ENA/b4ZSJv6Lxo8H/1XMt+Nqa6e0SG/up3ypKe5nplA0p/9j/s2EIsP8S8uPUd+c WS17XOmPwkNDmHeL3J6hzwL74NlYSLEtyf7WoOV74xAKQA9WkqaKPHCtpll8aFWA ktQDLWTPeKuUuSlobAoRtO17ZmheSQzmm7JYt4Ahkxt3agqGT05OsaAey6nIKqpq ArokvdHTZ7AFZeSJIWmuCoT9M1lo3LAtLnRGOhBMJ5dDIeOwflJwNBXlJVi4mDPK +fumV0MbSPvZd1/ivFjSpQyudWWtv1R1nAK7+a4CPTGxPvAQkLtRsL/V+Q7F3BJG jAn4QVx8p4t3NOPuNgcoZpLBE3sc4Nfs5/CphMK5AQ0EWhVy+gEIALSpjYD+tuWC rj6FGP6crQjQzVlH+7axoM1ooTwiPs4fzzt2iLw3CJyDUviM5F9ZBQTei635RsAR a/CJTSQYAEU5yXXxhoe0OtwnuvsBSvVT7Fox3pkfNTQmwMvkEbodhfKpqBbDKCL8 f5A8Bb7aISsLf0XRHWDkHVqlz8LnOR3f44wEWiTeIxLc8S1QtwX/ExyW47oPsjs9 ShCmwfSpcngH/vGBRTO7WeI54xcAtKSm/20B/MgrUl5qFo17kUWot2C6KjuZKkHk 3WZmJwQz+6rTB11w4AXt8vKkptYQCkfat2FydGpgRO5dVg6aWNJefOJNkC7MmlzC ZrrAK8FJ6jcAEQEAAYkBNgQYAQgAIBYhBD6Id8h3J0aSl1GJ9dA/b4ZSJv6LBQJa FXL6AhsMAAoJENA/b4ZSJv6Lt7kH/jPr5wg8lcamuLj4lydYiLttvvTtDTlD1TL+ IfwVARB/ruoerlEDr0zX1t3DCEcvJDiZfOqJbXtHt70+7NzFXrYxfaNFmikMgSQT XqHrMQho4qpseVOeJPWGzGOcrxCdw/ZgrWbkDlAU5KaIvk+M4wFPivjbtW2Ro2/F J4I/ZHhJlIPmM+hUErHC103b08pBENXDQlXDma7LijH5kWhyfF2Ji7Ft0EjghBaW AeGalQHjc5kAZu5R76Mwt06MEQ/HL1pIvufTFxkr/SzIv8Ih7Kexb0IrybmfD351 Pu1xwz57O4zo1VYf6TqHJzVC3OMvMUM2hhdecMUe5x6GorNaj6g= =FUaG -----END PGP PUBLIC KEY BLOCK----- ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/keys/testy.pgp�����������������������������������������������������0000644�0000000�0000000�00000002326�10461020230�0017650�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������ Zr�2%E$O:pbx£.&.|W^';CBL}M5j΍$F=_+ tKt3ja1W朧LEsA9}&ٲ-5tdjY%1'hV-?ҡ~6!GܩGtBc3<k/6^[ՁV~f<y)Yh5s1�LHxtҳ]fF]պv9I��$Testy McTestface <testy@example.org>T�>!>ww'FQ?oR&Zr g�  � ?oR&ƏU̷jkHo|)gP4c̈́"KˏQߜY-{\CCwܞXH-֠{ @V<pY|hU-dx)hl {fh^I 曲X!wj ONN˩*j$ge!i 3Yhܰ-.tF:L'C!~Rp4%X3WCHw_Xҥ ueTu=1>QF A\|w46(f{¹ Zr�傮>YGh<">;v7R_YދFkM$�E9u񆇴:'JSZ1ޙ54&("<!+ E`Z9Z$#-P;=JrxE3Y9�m+R^j{E`*;*Aff'3]p Gڷartj`D]VX^|M.̚\f+I7��6� !>ww'FQ?oR&Zr � ?oR&3<Ʀ'Xm 9C2!QLG/$8|m{G>^1}E) $^1hlyS$c`fP䦈OO۵mo'?dxI3TMABUÙˊ1hr|]mHᚕs�fQ0N/ZH+,ȿ!짱oB+ɹ~u>q>{;V:'5B/1C6^pZ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/keys/the-donald-private.gpg����������������������������������������0000644�0000000�0000000�00000004745�10461020230�0022165�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������ZR& �BАߢBz7f4BcEi5Di4 ?7vBy[?XrтO .6lGkhc5m{Q9m1‹2x׍^ ߌ]rko2m÷4ټc,hbh{̬?-�J{[նi^D5[mNn,=5VI7ЄN ,69bf] {Wzm���Em~̢ Im {+Ep{^o O _O%';W-IwGG#B9f?(tòTzJ8WPXzAw`q?I5X.[WI{7t!mZ^$KC06d{+qݣ# A :jg<"1+{bU¸ >W(GF|ǀ'S_C<8A֥� *%lQy.@5sц%hفZ`8T (6PSp7n;1J׊*".q.`E8`^J3,D~/B\%hvWJK},7˓4�KHjBydY,*V+]h=+nK0(_Av=flm;c1cFPꞗ 1o|Eͧ@NQ}H$yma^<SlHQI?2�zlLMJ.p$lw; $qw:'j gdKR`SpA&(pVJwט='Üej7ɕ+B'@4A� Xd>gG6[l8The Donald <donald@trump.com>T�>!T%z�Hr%ZR&  g�  � Hr%G;#I\t%AÖv#L7]HO)<? t{(V>|M}J-5 cz'A!̍'RLϚ L4Io2[RWz7s#4 f@3Z۹jb.#hi Bш[c`P"禋j+#;mWu^XD72kp qG.bk`a+<=Fênbٔ̏1ZR& �\5tk?ju TK}&|?DVQDD d&lFMfŪ]7ewUCd kb<my>uū`f Wv Qqtc#�8Xxaln},22zZ,+ Edz*H\ l߬2ӯ+Bv!Ef)B GnD= >`ѽ}j(VSUD��� 4=lx ^~2S17.Ф:?ta\hvG^w}D^K%Pn|5Eu (VdMMN.&D=Y?;+GCA&Z|H3KuÍTP@so\ɄkJ#D߷ToߋP !3B.OYf<A(r~[3LY?(Z2a^ʷtj~քSa11�& >މMO+Dg8.d !\E5E> aȑ�K=FA#3&W*ۅjŐ" S^RsY Nt1d:NeUH3%qH?�Z={궗6CN k1}FT/}6Yx{"=4] hKuiu[ Z)DlR<`IcW+nr_oLp581+?!?z]�I& ^O2 74h4+u.\�ԧpX3›-zI?,b",vcRCDIlLX2Pƃqn/AXNkZ>EdX:G6� !T%z�Hr%ZR&  � Hr%+XZc{%(?rU&4{�п�.-0AwC<;Wm2Tǖ/zmn4XOB8R>3-@+ fd?z,or?-*)ϗ}<cB@2k!Ić_@b89O̤yhG1ΎN�PoɧL@ }#;E%@<_үF-SyMw[Bj8M%lD!&۷W({(Ŕ���������������������������sequoia-openpgp-2.0.0/tests/data/keys/un-revoked-userid.pgp�����������������������������������������0000644�0000000�0000000�00000004275�10461020230�0022055�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������ []�^[FU`ͦX\卫17:qT1]X:< Pr|~ AC0Ӡ VpAzsAoz{ {WEʔ\I㭄Jh|GEL&mf7a>#|Bf-/:e0j8[ݳ(fJcJ/u䙂{I&{ӻ1\):qjէ! 32P~=��test1@example.comT�>!.eo) &787=4\%[] g�   � =4\%�[e]ż\SI+*be/F]XW *_M{)4MfB[[eUm'j@<Oo`r[GeuGrQ뛷hz𺄎V%4U-(nc1*E~#&y%^ Ӥ!atHS1t%*'v&͟<"N%'MP} \}<?+.IKWL3=^test2@example.com60� !.eo) &787=4\%[^D � =4\%�lR/ͥHKօfй mX%O[_M  + y|M}WlX'2׾n{fzJ W9P:9$"t`=u<G5ޔrvos},>\/pOlM6KMG'pt*D])89Oh /W*$Z.!J)'t?k9 3o6pe3?!ec;e!O6 ^dT�>!.eo) &787=4\%[^7 g�   � =4\%@ReaĐ8L:Q!xbE8lk;&5qz/s%Z]ڋB/ g.Ҧ*Ms}O׬D]'Wg\)+>FšPat&2-48 +T`{j2I6n\ ż|v 'ȝZ;Y ",C�Қo +BkR�GBh8Q=Y9IͷF="Nũ|xcqgT�>!.eo) &787=4\%[^ g�   � =4\% �Y=6w[}nO&ĆpF Af@kn$Q4QP:QEupX_*nQ3qdgi~V-@QH&a'/ҹ~\ Yz+Nme.s>lLk_%A*^K"G؇@PK7y�{|,tho} PQ 3erwRQrz . EtYjk[ []�<@bUq>�@i&n36sЊ7{*0^2W܌l`3*?>3|R+hcrN;&Rca_+7RGj5CjX`C!gY7=T¿EEHw7Mj ?ekw󰹉 Ԣʦ<T㊗&!_I[t&]Bv(X$t'ҋVA:]P*IH 6n��6� !.eo) &787=4\%[] � =4\%~Qނ 6pF^|!}g>2  G nIS0=N ;REm7w[` ^KdӮ݊˹LBuPAOAcB*m"\Y L~yuzY τ@= _ 8.$#rlX9*%5R<(TlD;QsQ%F ^{VYϖBOQK&g`3,PQ[�����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/keys/v4-revoked-by-v3.pgp������������������������������������������0000644�0000000�0000000�00000003077�10461020230�0021430�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������-----BEGIN PGP PUBLIC KEY BLOCK----- xpMEVOErbRMFK4EEACMEIwQBoO/p4ydhEqbQqDltmNWgxTBsAhkU8JYxWU3ZwZhX xVFCVx80kBSiFCHN0gTxq2V1/AwESk3aWzFE+yaXyTU750YAUTirfwsNuvFWeJuP f7u8EsMhPQ3621MFiGghyRk+ouIdcTHMwTmbwmmoldmf/iumHvc2AZZ62FdpGoR7 koNvXGnNEnRlc3QgPHRlc3RrZXktZWNjPsLARAQQEwoAUwUCVOErbTAUgAAAAAAg AAdwcmVmZXJyZWQtZW1haWwtZW5jb2RpbmdAcGdwLmNvbXBncG1pbWUECwkIBwIZ AQUbAwAAAAIWAgUeAQAAAAQVCgkIACEJEHzZZJXwFgGpFiEElcguADpNPi+DuthN fNlklfAWAalW4QIHTxUXadSW2UeH0iylk37AYwWmlVA8UXt+5NvsEvNIUBJukL4H caGlzzOozvm3NwNPN9iZWsjFAzaqOpOsAatrdVcCAwUmJEmXBS/RtGaazNRGBDo7 wzVKOvdyHa/BeIaM1Jop0CUFfuCy9ABkO05bmK4jyyQQFz4CoxAdguVvtNkFAjUH zpcEVOErbRIFK4EEACMEIwQBUfFcAZ/0XXmmD8QddHh/WDNNF7X8n7+XCGRehn/x emMdkE25UIxlT2u0x+9vgW5+vdu4TquG1giro9PlnF5K/fIAz8uVYpctdUZRuibt wCBjRP4scGy7xSKGLpdeo77P5lDl58YH5+Pjlc1zE7Ma1FX4GjTjK356dWAxb3rH IYYWQwQDAQoJwrkEKBMKAAYFAleS0WEAIQkQfNlklfAWAakWIQSVyC4AOk0+L4O6 2E182WSV8BYBqbYnAgkBPbrFaN9vN65uPaHZ+J7fbSzICjS0E3O1+zmzePpPWdwA NksAGKFWRVdEsmoAfd5KgnUUFBfRE4SByQltSWrnwmUCCQGvBvIvOdol1UJZvBn/ ZFZwFDVdIR3reek4KQREj0bDV98fcutHuWznDRwc3Ob+y5X5Li8PyLpG1okSYQQt +B3188K+BBgTCgAMBQJU4SttBRsMAAAAACEJEHzZZJXwFgGpFiEElcguADpNPi+D uthNfNlklfAWAakgTgIDBLys0DHw3Z0f2mpMZ8oovEq5Cb+XADdxf81W1s/oSN0M dX5MkM7DYbyKR6sgV6sCFrcQtlwrUZfaWzUbQqt43w8CCQFIWxXASdndRR+BAHAX Ido7fkjYgrdOjZ4esPPcPVMzT3KsSox+Abov3E+sK6bpDZal/D0/Xql7E+YGrIin LjyyJ8KaAwUgV5LRYXzZZJXwFgGpEwq8ZgIJAWQUpVg6PCqCXYHETrLvDqzvAsbK CeIwsjhoUwzL+p/2fpwgWM37VIBnntH9ZQubQN2jnzlKAc2NAAnSPXnEiOWTAgUX lPxjPcyOD9i3+7NA/3N4S/evrVnDzL6xKMOwhGjcM6UaiPVnajzVe7C+1kCBIVKo da+D3zV9nFaLv13apQPkdA== =tiMT -----END PGP PUBLIC KEY BLOCK----- �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/keys/yuge-key-so-yuge-the-yugest.asc�������������������������������0000644�0000000�0000000�00000256266�10461020230�0023717�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������-----BEGIN PGP PRIVATE KEY BLOCK----- xcLYBFsibwwBCACzeH815rWkl1nO84AOc4JwGy0iFQea2oSmhJs1wuu1QeZY04Y0 RW1HFbKo43WA1s0s0hCuJ4GQGLlGLERz+txz2NzojBLx1aHqiTCux5CtBiNwl1ok yteLyIqsg+006pA1swLx2rCvO8+x2EPEVT0yfsv5HpFmMc/rXEfcKiP44SUZl30c c8wE4Vlmh0WS1W5T9PoZVuzSXwhH8DW8fDJx9yPRQd0bTAvKdPJjmDwSsia1hcSd KtasKx4gCPc8M6Ld2sC8i0M15d3NsiFDPgpQRY/szJNCg86Ju8VBKjaDs239txvo k/SA4ieBl2ikq0oYzwwSQw07s/LHUirDss5JABEBAAEAB/sFu/3kjQCUx7k44ZLf 41TxnAyvIBjkG8NNRsNmzlmVqwtfHzMUjHtXYBwbRVlFypc3rWaXCfAb2I5i7Zsq UYOlt/InBAK+82J/Ce8iRoIa5S1QtaVNs6V7c+bqaDS2EiCVdqjLbX9jufeC6TQR G+Aesup1hUKED2djT8mEAVyw4QMDVP28j6SVazTymN5M3Cx+dcE6cq/m6FsashTR XxGANTfzz5ri4BLq1qkbJbF347w/NgFb9uwxdnL7CbRCP9xVwl6DTxt3CmOXHEyO 8XRKlBgcrOQ65IDYpj+bYEpJjoYi0jvDtH4/FWyJ/kIfEshzpjs1IVjqc8MmRGJB 8+MBBADj2Ul5pDDIRfSueFqDySCwSFauFE+XP9MYfqIUOzA3ype4LV8iEVWkL6Uy 21eRQLaSU1lB2bgi7GQx9CSuwbDfdcBLTp5MEkekyILIq80Ldv8ATeaCvG1gaLoo XcAXK4xYcRaNXLOeDioApFzE4w+OYSXLU9pokdmUAKcC9g3W2QQAyaUKpKfFr7/t mda5e+RMqS9JPwkyFJU0Zru2Wc8QdP8QLiSNEUqjyUvjEjMQ6bW3lvUXqGvhM1XT lTB0I87Z+zNysV+LzRDPDBeJXZMJ6BsATYJpvmU73zH875P2REimRBX+3YewLO74 QlIBp8AH3SmEt2iEwYo6grcTvaVIbPED/RO5YCXKgGGpBENLvJ325DUm4tT2qWKI k9BYyDvBIK5LD8OkdEfg9AVVKwz33CPlSnj/t4xj5OD4yxZZ+Zd4qDO2LDDLOdU2 hdpcIMieOYq+wZ3x5WXlaN4U9RP673NJ5mxbbBISu0ax/q2M+oeDeo3O8tDuureM Y0su2nQ0fK0kPyDNIkZ1dHVyYSBQcm9vZmEgPGZ1dHVyYUBleGFtcGxlLm9yZz7C wHwEEwEKADACmwMFglsibwwFiQHf4gAWoQRGPg+CpDgv5HqrPjF/UGNVzQzfQAmQ f1BjVc0M30AAAJMnCACKE68Km1bigUmsp73r/n5cIGo29YT4FfzdNIw8VqWG3ICe 0y59NtAdF56sGoEfBtkdDv3uXL8UFHRU5kK+armNYAibF6DshmNg4sbj3/75m5TB KnDg+dC4pF1cnTcJjU+CHj9KPxlS+d5oUjJqADjixTcNGQr22bztdPq8T2TCYycm 12cJPKE2h7d2BT9yRmx8UDxdkgo2yfEIwH+/ov4HkW2Sw5SIFHFUorxIjOlSSbDF Q31ekuEqtzd6sq7ovOdLGpQyQ50SvZ7KMZ3zjiXsKVsDYS5PmDanadsGyYYp5KD0 F3m3LR7k5KWZHi552z79oCOA8fxbaFlzVj5kcjsSx8LYBFz5FowBCAC3AaIU5/O3 7Nzfrh+Lml+LiDklitEygRNgmKE5KzUYVY6q1RFaJEK0CIk1UJ1M1FWoaJFbNgBF e95ZiZT4NbZTeKyf10HAiYSV8evu5aoyHpvftvg64K7QhNGmKtZVisvBTUeSH5me 6xr0t98XutganAIwckhNbli0MDy8inj3yxyTJTML5/mGELUpQJ4XtXuXjw24DQ2b D1ctcz/QLVbIW1ldMjWThWcf2VgC7qhfsAgA1ZnHUiPV55dHLh/y1fg9f6m5MkhI nTvLIdqu2uQytK6ef+4EirgmQBTBBDXFKwztky1DpwHEU3mkXQoec/I1IEwxtk6s XoqiWcSBjOZrABEBAAEAB/9mOMxPPyz8nJrXeox2Tzl1WBcLqFmoCz9GoprTsxXK TOgO9krl/gEgTPBPToM+yhA6rIYc27IVHdaaTuZeKqp4P5y0/+jjYi0kEGjIHZMO wdgxgyNux9f982KjnaPxTkD37XG/5lTJ1utMrHQ57g0N9/ylEQDf93Ym2Bbk/bgK OID7Doxg7FReHi7HHP8eQxQTpsrOF+1bP1M8w6TRhGSYEWM+RS2yFSU1SGFcKGwM vqJ97ycvZPs2kgU4mHkNDlRivv2UM4tm4lgA+r4nCKJtTfJXzw6XK7DyfOWvmsNn H6UnqZb+7YTqhSDsqM+Hfl+dByKC0zey0MmTaU6/fwkBBADgfEw+Tex03HI2M+Jr HV0rnZMpE0lhNZlgUn63LKTUD7cxgWVZhDDlrOezsK5R72Ma9lXNQRyjcPGg/sJX TBSyuTZEVy29ILjdRd1PU9BoqZXoJj1xU3+xD5+xnnxu+k2JkbakffSDk7Dk/yHH ybLD6491wlyiJe78joqNnVtdgQQA0LKhmhCk5f9txmmzjnYL2K5xwNiigGTXM4je sJ4nbFxb70zM5RKBkVsEHQi7vPjGY57luVTCSMtqn8ByiqyJIlHHeWAGGU22iyfp C1/qrJ1D13jQ/d5LSZCBS1Vhu8Weu7U3IGUo1wRuRoFCpIGm0NIKL9RMi0WyQqeD ehq4kesD/2e4cu/ZW6nyy0t5j66+O+41RewBNCS9wZqG98J2xNOlx0axX9VL/GtB J/EGRHL/Og17I/J5ziaMPcihXEVWpRB2ZgqMqdsBj7Ey9t9eU4huD2AdSzjZMn1V ngJ/A27BhuIEdZmZhOfENaXH5CEud6zYWoNZcR+wVeV+nGhbPkniRHjCwHwEGAEK ADACmwQFglz5FowFiQAJOoAWoQRGPg+CpDgv5HqrPjF/UGNVzQzfQAmQf1BjVc0M 30AAAJ8ZB/4xKmL2t+7vV5IasY1aR4HQiNWcdFc4IN1NoAAcSYZj5LcZiTHFVnHL fBFMQTqr1jYZPaqG7p/5/He9VJPXa3qNpOzY5fUndJxREKEnmeTE+fnOrEfYZhVB b8yVIjVgTi9qFv8q5D16hn1BE1YsqMPJziumzLsa22pJr+c3oRfAq0I2RWzXH3yg 9dq0dRbYIhBqLsxl9ZHmQu1+Kk9Ll2LOEg21HRWToT/D5NBWY8JaUWmJ795sxFPO ixGENrFXeZ2vbeZ9hrE/DpiVWNWNNs5HdK8m4Z6g944V/Xqe2jQi/pVaKQsqpYIr vR20iyd5sJcqNUmsmrRkSGBoSIWQ7gXpx8LYBFzv3AwBCAD2EUHFmwmHPrzDvwHr tmHsFkEsBtANFKKzkUuuNzXTIx6vAWkXoB4pB+ArxAa5r4Z0ni0/OY4EEdnexq8p 8l4m/5Ga/Rd4GwuftybVdNPUZwIcdNBJI1BeN/AWPYtqHxxUa8jZpECdw6NmBfkH CXOgx6TH2T4QO6Eqkm/6AFDFC2nC4VM/hLNg256odMxV95SqGIF4swweNlKjY/gu 3j0i8smtHCfMrGhDgqEErD4+JEUcTdfO63ZpIsauhC4QhF/lqDY8aYigcjeERrSP /q9rbDqY/7NC92qEFq97XWAV09fNm9gjj2wbJL7pzTNUTMx4sRTXyrRQ9J7CbHTj Sb8bABEBAAEAB/9Q7WgGR3Egy99lOYK2NSuLa95PToZKZwkDqbuMNpg7Moe64unf XUfOEssFWdkkZLgBzqvu6Uztxbkz7YBaeV3B0bffk3GOjMaL5r6c4Wlxp9cn6Ls/ 8p9R/W+2wNMDOewlT7Wk7sJZoKgyu7AlQlSHAmINYENrL1FYIQU24beutechGddy cjLNAkNOXy8fYGYzXpkKJ+DjYUjTLhcSagqYKoGRjrTtduscu6jLoBUWtZwrWGiQ JRufQsyHNP3rReUV+WMo8XkjoEOYfah3Bgf97A/ujLUra2gy5mV2thQcCA0B+yfI cBnNIwdG1g8gVXZps5PkNQd8cxc1hGEGmOTRBAD7pnz0B2rXkhvVYemHkgHOSryO GNpcZaCEOemxH96ob+vCVPUum/J20LV73p6geG1eUPpR6MzZs7Hb+cpNMhxLQsGM xe5kEdq3lvWS9gMN3P6DXe6X6DbGsRAGo8jvfrag8PVzkgA/gfh+CmeO0zB/sI22 KBdOVQb5hDl59kQ/PQQA+lIQs+xsjmbb6Faw7NgcACVLUVx2YPXqrY3n5pAKzf4B tfwyNBM/xJq/NPo9ZsWQSlZORkEsdrKP0ttI9fuypB0ijouXUH1IMYFQpRLKlxTO U4DBGxFkdD2m5tPhLh/dnJnmAnfbx2oCv++a+K9UU9KvaAAlR7QcuwS3+VizXTcE APcgsdxaiPndTP9O5BPKMDYu+K71CANRhrDuaTGkEtwSdFxFEWcvkJfrALtcWJh5 EHL/3d/vPmrT8JuIoBPkitmY34mkaovuuJT1HbojTZsKXOiWAEnLCLflq080CKtj yDVwfwEgFSbCZmHRhbc9yj37jeRUnIBc5DeRngAOj5IbQefCwHwEGAEKADACmwQF glzv3AwFiQAJOoAWoQRGPg+CpDgv5HqrPjF/UGNVzQzfQAmQf1BjVc0M30AAAMOa B/9XOkThWrTJnlAauPeXx/myDzILdN5VhrHzKmI9Y+oBRGL2ainENK1tI8Pn+KkJ 31LzwmIu7ld0h+jvm11tKwy+G5S45l1CGqqNIMeFbh9uH0tb7kEHzR+UyO6x1bDk 18OLlnpPcdKQEEVbPAIxLFwOfcsajhSpuNUbowGxMIiEpWdJn1wBOlQHRLHCNr42 aeg2PANXoQ2MnBf/CBqdJLMTYsRWy3wNkwIpYsRQad6EfQy2soeZQI2dVtuX3vZh HPGWzH3IFzfQYHS7GnsGw0uQntUCMNDyIvH5zuJcF0grNWCwB44SCg0pHqOwgbkX Lzw0MsLmN0sBa3pfNuMbkgTNx8LYBFzmoYwBCAC7YNws7qLysQw9WE7zB4h7lByd 2ZDL7dUtcPKjB/SjZouIGewyVwpGVsl81UoCfo5VXFrOy8PH3yX3GkeKWNHd9JBh 9EaDUFQKNEjFPuTF4Lr6W+MqdCjtleHk565zHDT68UBGrIrvrSfTcHzURQtbhMz7 wC0OSPKk+XRIitblNZdkeumHy5Iki8KSuHeHQqz4sCZ/3e+iClVoNpBOXg0tqJG1 eQV4wgkNdO2k4ZcQAtbVG7SzyZdoeSiZNfCHcdyyy+PJiEMwP4d4kFNfFyTb6ZqE 1CdoVR3I77oDWmkcrqZkLetokulT2s2EBfmzDb++yByEcbwT3yiOpwKT/qnpABEB AAEAB/4nDMaV7p2y6kbcufyqFVXSO0nDc8YDWetlbvaCF3RaYVKO6KgazZzsj8Wp 6GFmJ63OCFM8aabBjrXAy49BO8Vw9jjo0Rmo4CLDhaXsxXCz9GPDLUcoX9Dvwp6z L71ha+12prR4hhjmXjXAkN7S9hip2AFILgK1yWoYC4y6WYTze12fmC5T7zASTd00 lcCh4MbQ5veFFAly3mOseWjACXkJsw5+F4J6SCjOB8D1Ha52pAvim63tY/5x7GSW jfV/hFxUMudzYeUeg3jRsmzIg76MSA4CcaqjmRMRfsYqJeIEgQJsatcnED/fQbtt eO187WUPqbtfKyYrOfTCYyTKWlPpBAD5yI2+AyJb0r6hS68JeTIM7/OYwjKtj4Ss WpJynLxd05ijreBGqknwPNnueVhChy9o0VWY0jotDuZAzGHyfOD+eC1zZ4iblz4S PD2i6C4ragmNmWkGwhpsLhf3Wh9YwpLFJy/qzvCIdwfN5zv8DiYMY0UAQN20xg68 NIh+L8HpEwQAwAq0anUAG3z83EFSIdm01+78phtR/cDz+ZjZIy1o+/Qh/ro5Gtdq p/mRiA6VmK4duDCKroLLSRmuEve0A8lg2ZHw4UBY8JPb3u120WXrIPSxtfo7YtP4 EQlbpY3AcK/SpmTkcbNmMaNTh3sebnIhgY3+FiL9+tBOAW6gY1/xXJMD/2nFIBpU 8a/B8vI8XqUvvLvuuKKXgO+eQJ8zSVNP/taZHviMA9OEp3NzR1XBcun+q+g4ucsn J4AHEQHdHF0cURmVq364ylpslXhP+kBTdbIaLQRHlDzJ7LAbAbhqXEpd1lcsTJdm O8VRDN7dStKpBkiLAXa5KTDIg89SnknxN3vvSb3CwHwEGAEKADACmwQFglzmoYwF iQAJOoAWoQRGPg+CpDgv5HqrPjF/UGNVzQzfQAmQf1BjVc0M30AAAOWvB/9lAqRs ZHsxM/UIWOtYQ1RUkjyWy2lmUF4sh1LJXq2seG+XQg5n9uUtQ4aKVtOB/BRirWRt c2Cryuwfq44Ml6TBXtqU+vyQAAMlRwB1VPcwDa3nNGBBqQFb2S1CrYZjnCuVtwEp k1SZz7AFq489MM3nAgp+mgcTzdTzR+13l1v8UmvtAANGNSJmP22okSI4mZ3+96K3 q7bjAHRcUPJxt8wg1PNX27yLJ+mEDP3GQrii+31Tpd3MdAtGBUAH3SwYJ3vaqq2O PM+F/xH7AMmdHmhTLUDJS0cqvnS9WoLwJJ0QO2yyjVWNjMfn9HspUHpgGzNiEKxm YGI3sXM2uC85aGYSx8LYBFzdZwwBCAC9jnTDGspaw0QN2GWM5AfGaytu3wmkS2CT lQ8BUUrpTvfMZoIFwzlm+UnKWhqTis/ouQ0UMDkFHrc0OVz3ij2vyR3NgL0UVbU/ OuVk+a2SfkE4OUundfGRba88RI5rb3Df1C1G5bCLWqZ/JQK+hdFAxiiOX3m7emec QCQ5J/Iip4f/pspfhSwC1C/IrvF2hQVoeA3+GlI48AMs2qXx/BkVtOLAt30xBEA2 +ZvOZ4KIxxSkBbuGFYVzSAQwzxeWwdiNfN+6iPF588g/ozD6AbTmvgerSX02xlnV okxhy9wFaxKRaT/pSP49ejgvIV9sCZRFD5Ya23h2fALN5UPkTgq/ABEBAAEAB/0R OhUadiDVxtE0gOoZt8+NFMhJtzNlHwST5QQMFps8QNT8WoSOiYN5/EXcgfvQVNk4 STkLEKbd9ECqGlx8kq+wfhKbTovggTmLmYqndplzqs26klpxyaI+mT1HuONImEQl 016aBt3YjFB9VZu+POMbg+bDqaGijClGZf3kw+CZjX/BdFqL0iKWpxsDZPJTiJH4 I9c7Tqlqq89gFHxEWJ7nAQxW4YwWJoXrGFZbt/5kjsosI/PqU69Wud3syJD5m/Ys i5CA9rU+nZdByhKVKMvDBI09JIeYtoPxIOXrZHrOC4Z/kOWnCXAM+cKNIBC9x9ts pNZfpOfZ9RT1FuCtBgVhBADASBUaU4FnOsAjI+blUO8786ktNwbYyrwgJXdo9PVB WoRaYeytsv8K3JB/NMGC0s9TBxVXhjilYjRhN7lpozI6uGLMjkpnwxeXRsp3bsU0 It5NxYZZTGeVTzwm5GOoWj9aRP1yfYKiNK3ECIXMDtLDV4C90ZQGVlGLzaDwH9op qwQA/F8xlIfx9OMriCbuqDx/tzOR3uxNjgEl32xQp7g3s9CCvgPC8jwPWWNtAOre eOVec5qc1NuacI3YHymiEZNpzUM7jkAMwYipZGeuwixb7RZiuU8T6hgvHZlGYXrl nUumn+yC9dOoe7y6l/Wq+AjFotRyANcmdRjnobe4eWx7Vz0EALDKS/OfN2t0s82F xM2Ckn1UJuhaVXOlyH3pCXq0eUqmCkfWpEjZGMALBxZXHDiT1ENZIIMOY5QF4WGr rIeeGlMZ+YJsxThpC8pEI/XlC8PG2pbMNBbtdfCGbGRYoUVrKyqPM2ZJ8jY9uDb8 V/NoHV1oFVIiNPr4xOy41eGywjxrQ3nCwHwEGAEKADACmwQFglzdZwwFiQAJOoAW oQRGPg+CpDgv5HqrPjF/UGNVzQzfQAmQf1BjVc0M30AAAOsfB/4oExXnF2pltdDe hoE8eVxI/gv2UGOEO7JhMoy+ggL0GoIapM8nu4e3GndtvWIygsdNtDXjQKhRZsaE vxi4mYUxMJcC5SEJjkeNeBpl4JI3SQ/23YAVi1hjSeah0geCGmNjsgiIjDjveBkr bGtt9dVe19G1M6iUfPQi00jcN6PbnrZa3mMr6C+DJiVQ6za6mT+FS6+CaafW97Rm gXr0ZQH7tHEBD3B1CzeU2/3k9haw+mHLDKnm3TYs+VRRq7hW1yjyrhrcDPZwLvVC EoXKNSQjNfJ5frTEmaUj3PddQ8cUsQPT8cEV+ZqkHaz9jJZ38tHt6xv1FVM86lvp Hj06Pbvxx8LYBFzULIwBCADtDS7c55eKYttDMCeHl3xmszuMqLq4yEhT/JAN/al5 hIqV0VUv6LWGhMCVYBJH4wlTtvxMkCx3V3e1CxcxEaQhbjrl34l6ffXYZRMhOfza Wi4tIzZpkp+EJs1+lEN/99seeFbvwJKNkN4Ucc0mZ/HMOYDu376hXDHF3HxlQ/EB a2jZB+17vFy/D7C5K3JgWi8bsx2TeDx3sYaUDKstl4g138QArS/NlxF77Muao90p 4gBDISpRYTVMsQOiUuMMQKM4gOD0Cce4xEEf183PqeRQ1MLZ2/OJ4qBbec+Jn1Yj ykHPSh8ago6GF7VWzTp9+BfJbWVu1mdoA226lJRJhejxABEBAAEACACUa0GhLBuU mKqh2Z+WZMGN58BsJCSslzG57BMhv2WB4PuILAXDNleZfrq5i+pGOh9X2+UB5u8A VbJslyIRJfN2vvZ/gMMp/32S3E3q6symxfnNZxOlbBkV+9mD3D6a+8uku0HrYM4h dselU09YMZKSBJr2dP59NyUutgrouG5ILQx/e37KQMBmF+Vqd9CTV8KhQkzx0AwQ q4FlHXptLDOFMAJiQoC+LFdoPiJ377TB7axM4N0t42Nuq9562TtpqVNVps3CQJ0E qaClrlXjSPi/M3DvihYnd9NYC2fBDsNRF6rwX9pvbqX/S2N/nT9Aff63HD1A/IyV mAEEju8B0TAVBAD35URL/sAMr+cc1kLT0SWGt/jHwL631Risk0oPAw97XsuxhWob eylhQ2njGRStdSpFyQd797S36vtyFGF63SgSF7bWrmbXymOgCjFoIg6DHO5RRUUL Qxens90GgSfijA7AJn7/JVPvid1waJ4QbAI+zryzi0eCrzDmdF94RxqZUwQA9M0o dKalPSTWNwyuynlRTo/hgG2BUBMH10KeXW+14gUxYpfhZ0hLxsOw8MQTwhwbEZZG KfZ416IJVksIZUb+FTfCidaLbWyt+IB44yHGmjs0aP1XFEnTOGRXFFvCOOEm1XDm jvIWTJhPPUtDgQEdI4VP69vyrkfVI4E6QlrsOCsEAIKKIOPzPc6QkY7oZCZUEIzy b0mpWUVnzk6Q+xh8lLHXNs9vyUimMbUfBx1InJHRKC19VxV5qEOFqKAcPeP0V0/X 2JirkS5grjdPp/9HGBRmtd/IXWKDY4ex4K80t82rOK5KCz+r+iZmIDj3goA4TTzO beJCm6F3hnZ7PztkC1p9N+PCwHwEGAEKADACmwQFglzULIwFiQAJOoAWoQRGPg+C pDgv5HqrPjF/UGNVzQzfQAmQf1BjVc0M30AAAG3eB/9zH0vzicHNmNh3WvJbfqoS JdNzDDf7Prt8jKiao4yvrS5Vl0pPxLEhkw5rPmoh+OoBH8oukMYIp6UZbh8GEKIh +Q8ivAOwK3Cmdrh4WBrqZoc4EWl1B/21p1ysnrQhOtf+eNPpCW7wEWSom2894c1d GKz7ID/1MaBKRM3hfZtQGFy2GRljZN7O5LVGPx3+D0xf5M619zSoLR40JkCsgKPO QCHYuVz5XTEZL9pR/r5pG513B+TfEztGPw26ZZQp+dUD0vmt/1QjAjKuY9yeboiQ gXWMZX5UlBp2EP/TJvfzSWNeuSROLWIChn+RNTtWDxMNa8Edloq5o06hiay67fAe x8LYBFzK8gwBCAC8iS30tqnkERdYQIhoobQ+OpReaDkWiBxcHp/jFDn84hACoLRD AXgQsQzB/VIzrXkWhiNtkt/QphH+YkzqIJ1Yjm2UC8GNzUPnpjJ9FAweK5CiJ5Vl Gaxa7UC4xeKfykkeIxjhc1BvZ1j3ZeKop+qAs7yRJajGSVvwB1gybO4DTDEyRlFa zfJ7ptcAXGx2V7/Kv0peVSEeyJNwwI55gogxRVYXZyvyvBU4tBlXNXMGWWE6hGMS zO4sFe7fh9MpBZcpGuBX/NyPepXbqpVXQm5DyBSchCny5puWH96cknLPbw+IQXyQ R6jF11y+ucghjCZUMDP6AXhTIG7DUzNArK35ABEBAAEAB/91kAzKBYUWo9msvEDT DdI444pU/CRs+l7Eyovkq14lZEmbed4t0iKuNdjAtlelxcw10VsSHn3Vu1iJCX7w l3nGGLoXkOUWqSJotmfROsj7nyrHEmvD6ShiYoLzNOHFxfumATiojKFhdk4xpqSZ imtd6bXxxEvjqSbdG5dRij84ah5PRfP4tj3T4m4Qm5f1v56YYuUQlLwv4wsY/l8a BUXtjLKoZD6oMfHJsZxoIUpGVKvI2fU9O5Isji49j7ZWqjB6n0PjwLXSzwMkwGRB Co6BHSq51hZ4YPL5pXbCGx0y0fwUUYR3FoVs/h1Zf45AceY5TX4vOqh/14Cp/wr7 VzgBBADgWVpH5d28UBX++hmiBcUHmeNT4hTDzsfxDddHe1a4vvdrWy4InPj+canY xtcr6MKUk0fRto4ePSMsD4a+WudDAi5q0BifdxZMLGPgv2GPCdo2FbFRItAggM2L nn1JrEUinrLhsSG6AV9OrCafI3yEjm3RoAHWsJJ782RfiarsgQQA1yJjlBiGYLrE kBcjpzzx3FYV2V8vm9bxJIJ/4oW3pOGUsMhu3gXwB5uBo1eda5BovjXqZ1gOQSep 6fEBWFH68/BMxuYRAaym2nfqH8r3MEOqhAM91y5iAhbiikWlzzs/yFBe2l0UvXHe qc6wOnhUBa6IZE0wW37yoFnji4WUZXkEANkjFhSzdmApRNxb64MXCyAethmX+Qt+ Yy+aMP2t0J8iLNjSGaoteTH4LZp5MRVx+tZYuzRFQLW1iPHrow6MEhv+pHTL8Gr5 b8/4XdhhuvCDtUpFAhkT8nIlGD1FdXYbvoOFgujgXmLoY1ok6k7yzDwMZYvZtYL9 ofTS3lUAO3TLQ6jCwHwEGAEKADACmwQFglzK8gwFiQAJOoAWoQRGPg+CpDgv5Hqr PjF/UGNVzQzfQAmQf1BjVc0M30AAABnECACDkuHNBmqe/Wh4PmLncZnmBJaIQPHU R10+acxOuGq1bxqu7wusDIK+iYXmcdQA0qwpmGm6Hr+0Iy7AxDiBE5VpuleQdQo2 Zv9M5Y+BXU3uFUpi53eLW68akWDMgdVBJjFi4b0EY7p5MW6a/P6vLsEMlV6UW+cf YgYeNDaruYmDjEY3uy8efAgitO9sKbeqL6BWHIhUEc/mfVAqnvvbIdmfodmIhMDQ toeTNafan5SOdgobx6Va4dGD4KtIOAnhMP0pMsz8bkbLhI5TK2yDVCGe2sCaeiao qfSfzUWNeUUbFX2b0/Lz+574QaX7FttnACTHuxhOa9OjwnqhoNiAoFw4x8LYBFzB t4wBCADHHXJrYai5IRbBImlfh1NpTfQKqtWgivKu16hwX04c6EyGcrQHVnkWLTio qPZd2V2IEkBxIShIekNALFTotVm80rJa7qX7jXMO+5sh3rHwadZU5zdqGFd2Nz6r EvGdOsUhMr95Ai4w0G8H8NxpxyJpO97skDBryv8zgtwq/2EQeafzy1OORy59baBR 8TOGn4RFsnmyLKkG1jEOj7D6DC+c1Ij2PnBtyLA2MsNySm1O2lFJQ/ePN4G8heZC IC/53fK+OYoI4rD1JA37Y2a8HFft8rvr/JS1ZV7A3RnJVr+Ykr0r4DefA88adEDz wJ3C97CXos1VQLAe3C95uvDHxxgRABEBAAEAB/9xGa7mg+B81+i+57cHBi1BAYB/ lq4ltQdIfUM2IUyTavgc3oaYLGw3RpSKaP8YK+HO6t8j43uoP5p3lzbbwDcq5Fte /3PUwXH7rrtdr8tPDi8qpvN5Fj6H7bVxIx+O+dUDmHneWHi4TYzj69KnWu+W6uUj Znu6nuH69nftDIar/g2J6VCgX4DrvKmjCyOJDqtE2of6UiDXlzEPvKmDN1l01MZN vx+HvQgZwTQ0ikHvgR+nkOex4hMXUo4e/DNeTuxzZTlrazelbtsuTO6ACkTYNypp cNphigmYsY3O6cJP51sC7IuWJOKR7F7WNErXNtMe/s3KJhD/GFYsBxW4qN1hBADw DZjnWAdlzJ9nW3rP+nRJ3arSyBYseYr+Z/xgujypj/u6hrMhv5Vl2ug8ruOFgoDv SaOAd6zJoiVqu9ZaP9NIMq8FlboEbJExRXaPc6ivqq1Yby5e9jr16Ajr8KVFCRVV nt8DeTv5OK6BwvWJHP/mnczDBTn6lNVum+CRzDD4dQQA1Fele8iMOAT0T6iYwXUT JJiYlYmXGrWJhpV7ONBGYSzIHX04K6E6BGYz400BfsaQtNxRH9wZcx4z8AzXA/au Jxj5U4zxY9cMsHhq0gzExXHwDjku2HcwYnlT/W4FhRys2VIPEGWKRLftRSYgVV0M JG+jXaP3gNcbf8t0m7GkTa0D/RFWiiNEW9nHSrOedTJFYoUH7tByKjOkN8pFQYzo V7BEISkprS+uTMsooNlgedEA1+Rks2nidZaR7wB/v07g6QzctOg5wfDT3VeU6XLq cf46nyd0LRMBzbdapBZrvY7L0vgJQRUidKV4neXjaNz2LswGwg/ojHVTWrutf2JL H63HR4HCwHwEGAEKADACmwQFglzBt4wFiQAJOoAWoQRGPg+CpDgv5HqrPjF/UGNV zQzfQAmQf1BjVc0M30AAAIp4B/9EP761g5/2QMh5jW5bV0evRZ5mP6bxk6lbp7Aw p6pXXld4rFtiWypRizY5fae2vS+7Xdw3jZxvRaFM8R97ku9td7EOe0xu120K1/fE HkOXpb+6l92+40BCaMf0WKWXtS6DiYMDfD9jkOhGmUK/LcwGRFfMk/1cfheov8cf 3j8WVAVRDy3hdN6GeFOeIVvzDTBnkZVUepyiwcQVWNtjuENn93tg8NDt4vDfbeJh LD3LxUdZjHjvmsr9jxkETGibLLIAMy32ys2ZqWLEJqP8ED/JvBr+7GsEpoEBMsuD 0M+lXucbs7mzCtb+D/lLgrJN+PfQd+iMhKaoGJa6Qe4vMF7ix8LYBFy4fQwBCADI Nb5bkZtL3R65Xgcnlyy40QrCRmQnKNV365CTQw3zTEzUu71yBVEZQt35eJ9c1qEi /NdUgBGx07YLW8+NBl3uV704Ih9qIqk7lAB71B9PoI5qMH0HqZhTl5vQHWNFruUq 9S2F8uqSyHmAC7fRArFFNTSSeVcpoeEr8E/PyBe0AVmx/03vBI7e0NWMTDGcTNmO GpqBLGmKhIL2l7xm3LT9fFKCuencbTRQwjCQ/JzpIKZh+8O+AXzcvJUOVckcClur nMPiLKjHxoVib0rljr3NQYXtxANM23iSbYdBTM3ZlgRfWTNwEA1wutOpkXMbh0+2 eTpYWtuyUJx7ByEefw3BABEBAAEAB/4phoQiB/RW6tkJh4giXQeomasKmoEBYkXM Zq6I7LqDAQtagEoN0S5999GEgdFD9zxavmiHHT0OTiQO/Q7yaCSpX8deUi2D3QaO 1ea3yEpqQJnpSn9UTIfMpsBpjP7fICRmIY48nyKqKSySM1v/3PgZq6xoyQQcHXhP nbtgFFdePR79aEUO1Wu0XBLJZK1eBrJ3vn/HFd5wErRFGfT3mqLTIAdFsdHSs4ij r/G7wyr/uM/VL3ajTcIVE8Kn+X2FJauKB596jl/rUeq4rJzqvbN76wlM2JZFz73N pMTirOl9eRwuJmeAkvkfDIpH8OJgk+3jyNLptCg3P8Jp0xX0zHglBADK/mB7kMPk xGxR/fvrwimLYujoRS83tQtYoSxy/owNzOZebbHO7fyzbQ9tRcArvKxO+mcZ8/Zl U843kuNzgjOOgJMSxAh7nE8Eplt1U4FYzFr3/B04Q+6nEbJeBj6rtBhO08N5T95f Sj+PTvIbs2Z+Ff1MjrikpoKWuE4tIxudpwQA/H1IGtYiyOsbSZ26r2FXJNcfjM08 za4NfUdiUiKyM3ujj/hPLAp+hx58EDIKAt6Z+oTq1ByjbTXXhoZZKfPNQkdcJyk3 z3/EdGNFxNI0meAjy8aQMOVxaVkOu6G83WqxHI7eoxMFS82FDddyJ4/JAnKv5To3 o6siYgN7A8Qy9lcD/RPdktLXxQnrtL0LjlJertIoqfmJXC/lLWEPDPT03i0iT97v 917c71CDBJIIKrDf2AcXbP6C25TXGWqJE+CBYK7sBzsOalMZU94WyfQeJo+7j/if kzqx7Zj/ckujSswJeCOTopajJMKoR72/atgJYyErwhyxujcoXxIh89NvBrJfQOzC wHwEGAEKADACmwQFgly4fQwFiQAJOoAWoQRGPg+CpDgv5HqrPjF/UGNVzQzfQAmQ f1BjVc0M30AAANlJB/4gIMhMgW79HbaHqVjVP0R6e8kKYmGndmUOS7rsU63XiQ2r ca7B/0Tdv8igHvt6oITnUbzraCGzW9jq6jJU0+bkd9lWs91o/mVonh+Bl5i9RDfJ /pO85cRE7r1w440e2cEC4EyQmfYJ79IQTNB2L+D2Y6eck3zAPvSHI/fSzx1HAJEh pFbpV9JRNzfKTaTh6/2tkgcpneuOKB40FHqsnOU+a9i+SWe3J31alg2pd7wuKcr3 Na1RMuBDw/WHVkdORIxBFDHxj0MBAA9gQBHyVz0iGmJlOjXqBONzLU8kh0x/kMUa QhuAQSvMONWzyZtav5r2NrYYxiDoE8e4UB/IupFGx8LYBFyvQowBCAC3q0iMpIVc E/zaQGBeX6SxWu1R0w+UbeWwTUW93YrijqQ1NtLu+mCXSsE7COxNqkiK2ftioxSR 5avZZJlvmWaMPk7QhvAwcNOI25iaFDF8xAhJRrzyrC63v4KKSA04OJEaex900ltw K7eLX0ckOHbitTz1Ldz7Gu/YFsaPTe86HFcCfFasPwavZqi4zwRUQ3BZsm1Zz/4y 5wNTRPQvA47Lh9qhVAe6AyeebtTgEoHgVGu9f9MC6ZrZKud+wpzVmXkEWw7umgep wsA9MXlfp3LYJWwOKZaAuiOvKPET7sGy+gwSUIeX4z+9F9GB8RO+FY1L+ITHsNEa 3J0TijWVv95vABEBAAEAB/9BPW8svfe86EClib5xZ0nJ0cGAkhbzo0G2KQx/z6TY qtA7MrkkN/19YAlZHedKAxV3dMxtkf3pgQpU0v4JfRTG9g7q1TOWEU2iTFZdTJTz e3JyDNVq4axKipUk50kC0l0AagGXbm0aX/z3XaWYgRIBn10CvgLDkyLkv9pR5t31 5qbz8833jrXr992R5+3FOnm5fPpzZFNzTWNGvSvBxoJ9Ckkq8TdB2eW5ccQe4ijW nuiuGb7VBrTHfNp4V+JcL7BRHTy1FY5aHU+bqoL0etBQFZUhAgfuMX/ttad6NaLq UKLxMmE1jZBe/MQyFPsPj6BwVtRKQxUs7jgCY+A9TYLJBADPlM4JP1vdp43XLVHw da6NNNoXmsZT9Wk+OXXISlsjgzgMd+qciiiF5VPJcYbARl0e6nMOQwgGU7tIaMom 1Iakgw8YqHGATpvsWrOtFJim6uR1TY/joUqfR05rWCvUjKJK7MlAOWQE9mabL3XL bC78yWkL+HgUj55+nnEGFqZvLQQA4oKfNFN83E7QK0AN7yM7vu5bprad0Z0Z7rZT PYtFY0rmBcVxy9bSAPQh4OvPYU2EdGnkO/W9Hbqnm5j8P0g8HlHAHVqe83XmcW2z mYUdGyO5JexSpcXaDh6Hqy4Kl7mvnjMpRYtbPLSfYk0O30UxBoIx0HGfK0ivK05m rLqQJYsD/2C6HJIvmGCpvS6WUG8TawJ1dOUeJ9trgiVqQJNeejgX55r8QVK/Gbfv Sm6KBVBbmOivChvHZzQI5G24IH4/WdvITYeGYAlzXX1MC1rKttBEcPFDZiiD0/IR T1w7iBBatvch83BXCu6kYcW9i7pNRTfpAhddWMjUfj+D0cz7qnMEOuDCwHwEGAEK ADACmwQFglyvQowFiQAJOoAWoQRGPg+CpDgv5HqrPjF/UGNVzQzfQAmQf1BjVc0M 30AAAMCsB/9KwrClq7UIw5q8udBk/tnAgsprzzybCmRoA0XS1tUJrfxF46h08f/r v1R5WdSUIcBAqAKtaQo3PrvNgDII8kUDoX/5E9bJ0eQsDFL+SGFljotbcV/ugPbq K0Adw5c5/SCi3C35LkQra7XvDZGg46qfpL59ktCBQtdOcy6PW0myeymoQIZbZP4p fa7+nDnDjVLa4LTV27UnPMQU3bSkq4dYqqhHuzhXx/Ek7DTg8w0aGNn+yrno0KBx MAX4+euLOAJHPPLUzPnVHubNjtQcyxdT2kimP+4gGIS6blLJNjKitd2dEfjOGOpO 15NTtvBj/GXPdU9cHe2xH1LFoCtbPYfex8LYBFymCAwBCAC6LfaxcIMC9UMB6h1x 0vrH3fLtLmTQ5VWh1Po3itHUYQCsHcDUAAZE6JzaoXnld/8DjffVLJTBIduP9jGm t2yIByLxyAgd3P0W2D7LQCAyLle0lEuzrdDLqeNPDomMwyne7E3X8WVbA/BdGTdR e2R8KCI+Vc9EAe3jffvia4O+PfNiVKqcWuvqyqL0XjD85GiBDoWCLc/Yyi7veLuR fjCFgkANz7HO//tU9St6XogeyqdRBr+GvXB5K0xWPP6v5j+eTM86rwUtWc0zmhsV 4f5r7CNc1N5z94b8BKeTu4eyl4kHPBL/sTXgoYFDfB0mSpmfdLNqCPP1I+Xkf3S9 NKNNABEBAAEACACYC0to8zp64vMbECeFukgFi3OFURsSIggBxp2lWGAt8h4Lcaz1 NSPpiJH4VmDnDk0biQg5dlPxOYZGlkdMIIWovTNDgxll0gB6dJUrha4FbSVBGs6L 6UoX2SECghvA8e0y2YZk8QXWA2x7i0lK6NtSLbBxecBviKfO3icjbKxFYoto7Np1 RPzOWpcLL1Dd/AnODdiitAGFTEdHbrMiUOC0YbqoEulx6GOdhGhgAASMgX6MuoUj sPpF16zW7fdkO2COFML4miTUbU/sh3QzU5r1Hdu6aCZXz7oli5F5i/FqjWypqzi3 IGcPcsLFbrxWj6qlVuTxi9FOyq4M2F0UozCBBADlZFgwMLprkh8eVHV7IIx0tt2p N/AJx+jDIYsy4/o+jhiTVqoyrEAVxf92h2jRWmauxnozeNm8aPzW106CHPdp15M2 IwBqnIqsEIuN1YMg/wHZRZmyoCgdCmPl5JQ0q59dRhiCaSWaIHWy0zbqIXUYYjMk QOcyEqwPirMA6KyKbQQAz8Zzz8j5INqB2ZgMbkxcbivppTgAALX/G4P4eZ7MK8Kf qEcPS6LfUwznwnbfeqjKmmIqFNZor2oyOY9Qm2VP64mZb4yHj70WElzMJfgywNGS tdg/OA2oU3O8fnwiY2XL6AxZm6UknIzYAk5q2n7PQJNZvdegt7yTbqKCqbGG8GED /1DPS26OJm4IWDnEqX1ZPMd5jKD9u9lZpxuadvjyf6jy1HHqvPb2YvIlLp1Wq2wc mTzvKn8Vr9xYIfpEuHQ665kk00/9vsfCBy7Ufjx/pKXAWgtSxFqbTOLlzC/QxYc6 e4CGj12t6T+Nv2M8aLo5/6KKaM78Lw7QPsp3B3NMqK7aP3bCwHwEGAEKADACmwQF glymCAwFiQAJOoAWoQRGPg+CpDgv5HqrPjF/UGNVzQzfQAmQf1BjVc0M30AAAAzU B/91LDa+cDCSWCvOvqmdLOL6TzaZJLYXbox+vL1NKCf47+zsI+SH28SekhZ7wwzN dhU7iE2VVem4u75GzOYIfVxTCfc2NM21DzJi5vK1eaZ7cA0Gtxd0/Mg/inQKnFXl opsL+LKqJ/896y2+UTFe4U5AdUtpEu3XF4ueQwyaZaRNheH0Qg7E5YBBFb02yptm toNdYdxeNrS/sfZWwQV5erMsdkTsurG5wyhgfmX7LQ9o1nqTWY1aR664E7j2IclB +G5akP4STOUjuHIdC7uo6kO+pu/iwE+DH/aPU+GJiC6JTk/8gYuXuEm/y/KZhw1u ZRQocyUpH6SiehgLwk797k3ix8LYBFyczYwBCADECWqI9GywgdnPU980EnTHZsMr qxxSlZTCTSxtpnChzMBSk9T7fAqWjgP9wEJrhqEAQUDwdIFfk3pDM8ZG05H4xFz4 eD2frVvw+MRMsZDogj9yu0TCt4dF6UBPZChQxtCn6ILSPuwYYzKFb3nbmJlrKuXu Tnm7zXXnQsK4oM3GUK+ZCOFvrxPNx6YbXLJLRRMLTN+scpFUY7fPbLPlSFxPd379 msaJfsvRiswT/w8aJywmwCMkiigFVltq6XxuRyUgFA504hbqWxXZeTHpsZevbTpS Q2gEBriSebwWPTXvFeShKhZxa/p45q7k2Xyf2ktbU8JxD8qKIiojPm1Di69JABEB AAEAB/9vgeOlb1L7u4DHW6/UkML4QU224yfDGe557yfcXxYmEq86yXgWbGIhbTp7 9gZR30xpNrTXkY2dbefXnfWkh+e6FwfDFbIHx7ZKhVYzQbVpa9znR/o+v+IkNB8u iYwdFlnNiIYRTVVCMfqkx9oKvOxXdxk+ykiYOLBeES1tk5o4uOGtH4zHXmSfajyD 131pNoKHefBAJaKH/QfFLuieHFOa5pouTu1GcNbMpJCPoLAdn7F2K1Nm4NlNCo7V xgirxiA8B+kXs9bkLU6rn2L0HnPn9AGkboLVCb2ERlWrqyxXN0rA53K5gcFQTMVR wTtNYsMHkjro/qaCZ2iXEU6LLZT5BADeitHFBAqyriZL5svBhPUICYzip+gjUih4 p6auxw7u0sBTLMH49CEB9GNDlrlHzD6IgYzrZq2wwTuzeheSivSoOStNexOZYHdA FrT6PGF3u+U4Pk/H4BaRi0ADgv/VD+r6IashytV9YcM6whpEQsvRfV1SBK0fascW fHVOgZGaSwQA4YJ09XLw3h+08D317DlCKLcHwPSfgftB2s8AedsDpRAWOJ+86S5C djk1V4mql7Kq2nYSM934l9vthltzJDvrPpm/Oez8XCnQlkJY4JbK0PJkVAsT+caX 8dx/fkyONOsQeh6t1aUkdT9d3qTvEHzTB5ewBLG1CdtXpmcj1xLDYDsD/jqz8dLy 4Xymo0DEr+0t4/j7rFC3FFHIxu+KfVO9eDqYqjzuGwphQkLDY1GByk0hvLqcaSVA VvZy2Aua9I8FUUQLZCHCWUBKBQnV5pgFFpYdLz4Lldi/qjrL8/oRSkG5Yhgf4pRr wYGLE3JgaREV37GEonkdKUsWxz/vRK9HBd8JQovCwHwEGAEKADACmwQFglyczYwF iQAJOoAWoQRGPg+CpDgv5HqrPjF/UGNVzQzfQAmQf1BjVc0M30AAAL2OB/9bKEVt NxRfgw/5W7qZpuejGDP2qsb3/OTwoTj1xIKwKZxR02VCRhzTf1VSvERTsUzMtfL/ XfymGXy7gDXc7n+e2kf6Fmk4T4tmCsXgD2nC5cm7lWf1PufGtUbG8hxpNx3pHnGB ADQkwjq1VKd6fy9JhwAkm1Qo2vpJnvJeWWb2HG/YHl3b0/2f6cquqBjm9PV2d/un fR6HTXzfe1CghzajYqmDRGOVPvAp4mgLpknFbNzmp7Yr8M0rPOTG5GwEKyNN3P27 XJ8DmHL7jxaQCPsysZv1dgVYJgn9GPbFloTOQ8dmVgUUAcoNGBmj8oxwv+MiPQ/N a63CIIL1JagAauHox8LYBFyTkwwBCACr2IlAfK1ULPR3RWMa7bXysn7TXL3ecSmD DcUoENbIYe4j83MFfg+BPzTKHEwMzFfg3082/we/ZjheSH3LSm9SIyIefqCfKdPi +YyTz4cC8Sj+rq3+vP39blwb15TvUYYi8jEXt4YGjL6AN7PSVpMK3tArc4DNmzzx uAcaacLX8loUgzApX9m5gnRlq84t+qtjv8fXBSdfImVJQWSQHfj4GC6T9C3KUsGr 5EPbi4/q1y+2hAGGPHtDGgsi2RFSQSKbhDDepws4mD0frGfCCY8lJZJ/JOj9lYMb 3ttKz0gc0At+wqfgV21T3xV6WUdzDWadixz5jZt3oom1XDFqR4DFABEBAAEAB/4v EoGGSKCyDh5pDXi4R6UvdfWHsD/STu0wGPWIpBQxj6HL90PqnT1iCg5LzrSGTPNF heYNCo70vutx6CmNJjaYKUePUuBsuOhxpCn90zsIc0bnfLHstZhdc32HouYJKCu2 JuGLvZnY8XS9aoS3eT+OGrqrLFh7+aoYDHDlq1LUE/vjO2p+TSJcLPTpkmbZTNmK MVJvll+86iBcjdhgEGnvmGY1kRyG9YFK7XK/OvHRnDNwYOySln1dz+Me5a2HTu71 D96pCyhltpYDahLnD6eQ3mkKppDZ1beTkMJ/R4765bOhtsWLf2uWKLCGdJoCC6dY Kqefj3mIR8W6swIyauABBADLlh6eShy68mfHN02Pyt7M2pLx/64dSjiuUTGvQ9GM Rs3gsrbtqmegaia77l0plz23duKdHHnCAn8mWGa5mxA6bBO1IfwSuURZj8ZwURvG dqoHVdQmhnc+idCerVuauNM15ZcF/b6pMuPaLWBr4ll2IBJtPcT0Ieo4s0CXk8eD wQQA2BZ5XXvm7rsFcRS1n9/coJpcV2zj04DsCQNEWAI/gTonxMhD1l6idMI28obE vqzXAILgaLK6vRHe3490cnQpSchTdBSZYasmUSqXncGplRvqhKTB/j0SJ7CCvfvE s0mumFg5N/wUjuMWhBjgqmpKZMydpurpm5Z//QI2HLoFbgUEAIKRIQWPbHZBjpdZ 3UyH9DHSIJ0ifx5jMRiQKIOz4ov9rWlVa+eNd6QQvJkx2FYyHKhKIBPHkKigEcI7 /7CQA4APib6k7+5mbNknwxfyf31wd6mZxBBYpdWYvUOwEN8H2Uhr134O6/wHkZwQ zVfDS0NiTcdEBr5N82+AxjEisOgyRSvCwHwEGAEKADACmwQFglyTkwwFiQAJOoAW oQRGPg+CpDgv5HqrPjF/UGNVzQzfQAmQf1BjVc0M30AAAMcDB/9TMZ/tX+fTBlGT ubWcdLwqAppi4AupSdLd3GkeEAsw0VxcTCKW8/bZxzY3oDPUl+Mr7rqGjAXRI74Y YBQvfyj2kx20S5HqK3ABA11F5pew18t3pGsqbNBhEf1fa8XHciR7T17+CPkvrd51 O1YwQKY0ZMp8CF3DopybD8rhPUMvI/I4Z1DqyJ55dAkKkn5jofnTllqVk0q2HWTY SeVLo3X2R5NuHS2wE7h7AsLTSm466TnkT8/FodxhC76/jgEpnW01srdiqMtNTnoU 4vMgLHEwAHp1iFR+9YDpwQFMXmLUVAopps0/5NjwsgsEUTJRS+DAW9ceGBkAqDRY iNtKKZZ+x8LYBFyKWIwBCAC8sfCUmfQ/VUagXH1cyHfxoGQBDyG3gWSPZIXFEoHj 6Ka+AfeE13hwnXTjrRfnk253qifCoKQYTJuJy3+h7gd+f3IbmLU5Nuaj+/bO2W54 vW8Vahwt2xaYZySKoq7Driq30+2zeQ3ssPhX0eaiQf4n2KHI+Hp2QOHT9i24hGS2 DBCORD52GsMGGHMV+dG317SS3w6/gVZTtOJbe0+m8ETo3nFmrQft0tB22KHgFHVJ qSbJ4WzSjm7utRqDbKysk3li/huW4tdMrFLSMPQebKcfQy5tTq8Y24jHrjzCnC49 yBGNNO1QiX2EdCrhdtwMEMkHNa8sbUmwoVOhFsIQGb9LABEBAAEAB/9UjQ8WJx4Q uu9Yr9jkmFdWh94Hs3YSOgKLVimysqZNL1R3033LopkvLP7RfiA1/hQDvtTuGvks gjc3uV3Je1UUU5sXzod1yanzVNW0vi0IISoDWHaj/YXUaHMxQ9A3knVFxcq/HsEk i+/bugw0LInr5OApo12MW2pVhfvasEhIYb59c6TbGF04FD9yEaZFxGft3pu79ApG /gg32A461XVMLXKkbnfrTrD4/15h5qif98H3ti6M7KX+4tSvPITPsDJXoKAdq+mv DswuHzz4UvwCVuEfpsbTc3mWCW++HAaAByDJiv9XF7U5HTaSXqzMPo2d5M949hFc wfnIHPDvlPhBBADUj0AEC4eEUTdoAdr8XhduEF1jouXAnMvpp1LnjLTxdMVRvqGE zGEIpchwBESkyf3Z8Se2EfLMgZLNUbUi+6OLYDqSyNOehzBNUqkMrb1cUKZsLK4F DyiYhTjkQxZ0Sh6gcGjE31r1KtDzilYZ7uCgk2VFaqLrsJyZ+GC4ML9C8QQA40Ij /ggyDw1vzmbOQz/Rsh30FMf6x1NsKQj1vVFP4YAsl2/TDeGgZk4AG4fVykTW9VOt OmDBr/XEEYNfYqtmyOSXtGhXuP+S+QYnD1Fw/lwkmb77GJeQ5WbaoNcnQe2LnuQr ibHJhzWuueF7dorKfrevcuUeOjLUh6MJ0pk67fsD/38VEZiOf1uS/aiPBD0fJNhG c6fpwRD6zVw9NsMRUqv/kyE6o7NXf48M5yrxcW/wq6MeyuSXmH/9NLhzzdhJZgv6 WGieT5DUabLXuqWSktBP4PpGzjefoHCoalARBddYNRJNF2qGkTPj9/aaIXVZbhvm pcShbegfSo6Op+6+6y5LTLHCwHwEGAEKADACmwQFglyKWIwFiQAJOoAWoQRGPg+C pDgv5HqrPjF/UGNVzQzfQAmQf1BjVc0M30AAABelCACM+Ii8kxZqe18C6C5Ekbh9 5QSK2BIjLsKzGejJYGCA2SDIHXSiqfMPx+C5YJSPc6+ZfkueTEaTMgdXhpPBCt+i n0/0bCCVf/zrkpJZa/jXS04Nx8NM0tFe/LY8Sv4HG65pA2T3z8qRwwvZ7K+YqDFT cvXX2SfRdU+Ghu0EWolP6QZ2hYjof7oPierF1c6a90kfBP6+J78V0zjBNsVKZLHs CU1Mzvp3SPZZGSDvT0HUdvaxf6H8PK4n9uAZQr7SdZFktvmBXGHo2xYvLRyb1bju G1Xhuy06Cnc+eP6+dEsLB2qxkOWocG4utpSqoL71BOA/N2OqMV6BmeAc5NTyCKjt x8LYBFyBHgwBCADA/SdTgV/cXkDLg3A5s/zs1uNKdJSRm/FPB9OkWcJ7yQMInN6j /618A4AJMouRj9xHFSjA1pPPDqjlh624Xbqm9YP00zQyOVVXD21gKd87FaNxe1XR zKWm7aaUOKYydTpdM6vdMzcZT2RWs5YNbQ7UYJObZHKsx/K0N+4fGJQDxZ5A0ETN qF2k9YRIELc4/e3wVoH9G/dzQFLsbgg9vds7sBjubFYnI4GU+QpGD4pTWMxjLe9d Yjcimjg6/LxLQxT2nKYNcBp6d6/tNL2kiSALjAHNx7A+phlHRs7xWUHXf7+T/Wvf ayCAysLqczS5yLKgfsSungF3ujFs5iGJj6nFABEBAAEAB/wM14urYduIJXl4UWnB XTnCZNZBGRrdyZu4H5Q0xohLhme+RfoGvbfEndMCSavB5unIjR7mHiE90QLqKyoj L5GBFKtQOlGp2ejU30Du+jpRizISFhvy1wRa5EuGZ3Kfvu9ATnTsRkEvXPoXTuH4 SL6I4VhRKdD7sF51IZel2BPZYEpXEIf3hZB7AcVgOCmdS2DzWnlT94KpP7SfswM9 gAh6DMprKD2TiIAacw8KNVFbMTWVVMS6DBAnJgvf/+WTdYyK9DmZaEWEzqCnWsh7 lgJPnVNnW1wN7X9nKlyFs/fFthydlV9vB2+FWB4zXuA2G33v0ZsqE/W/tVvDs95Y H9QNBAD2g+Qn4zsjokaIUMPigwtZtJbjhTVOCV7G3m1a3VzWgox36IHG0T3WTN5F 8AUIJ+OgYw3z8EdgjY2Si0v98EH2Dmj/v4tqQ8IyiDKVt3Q6brzM/fPVzCDqM/Vq jTwn/aPDYgyot97KV8VmCzONJDNYKaW495Ao4JgaWXDV3s1LdwQAyGoK3vtcQvVX 6BEJ65PmluQFyWmWUgUvc8l1u5xqS3RTF22pta5qW3Mfq1A1YvgPZUMVWojTF6uj VY4gTXNMup1AtS1wSU3jQBxPPcziTT5wDI+QBzhfa0PR2cqjNRuNxlMMlgwzXtxv lOdnXJ9nchpcL/PrwBKEzKNb0OvZi6MD/0E7vpDRY6DL1Cftk0bp/fK2Uxt3AL92 f3yjvnc3oMIafM3biK4nElkPukFl6xJMqtwJ0TYR3OI1LCIkRHeFxSeHk72GZVcR jkyHr5grDEI2CU2/qvVHC2DD/EdJS96VqYzIxOpXAAWkXlRDKCheaeBZwqvnwU4h TGcUNDV9eqM2N+jCwHwEGAEKADACmwQFglyBHgwFiQAJOoAWoQRGPg+CpDgv5Hqr PjF/UGNVzQzfQAmQf1BjVc0M30AAAB5vB/4lrG/Ik+N0KKVWwwKyHyCto6Solgrr MEV5Px6WAtMP3C//s26R3hsj4ZPdG9Fu9lZVkmwf7RcmGGmRjRo21e7ytNMuM9tI zR3pB6MhWnjR8XzFo4DMj+ZRVopk9vjDwswv8CCPlh6hziq8itpG9pDzX+Z5RsP+ BwtDyJGawiYzA99R+zihPgbtu0b+jQzuecOXeS1Y5p5m/HmVZyL6BWJrdYqz/BPn gye2YZyTug/2LxcoRfvlG+t9aUHq6ZHK+nsVJwtO2+dyqbV8XJQ5JVbtq2pAubzq 5SkkGo/KW/Ziv1/m6Djm/Ho4CdoLVcdGI6fjxgCZ6YpRRQuCIB4DTvAMx8LYBFx3 44wBCAC3WmVIN6suXbK8j+b6w2FV94jwYu6esyft3ajDlo/cjVyvrHVUBj4zBt67 9JzZS+WBSnvlFRkuqQyfq6SEENi5HRZwQ9K3/NQ9Vb3gMew6XkO3CTI+1pPuwsKv dIyybyqgaQlyN6MFzyGLGZaprAUJAiZlj2Ii4D7IaEsgu5u27P8m8y+CBY2GmGvU WX979f5xfOSupOmoP8VqYAfG8jkaXuDBk4RUtl9EtALFpcFZx7kuPWd5Zd6ByE+J mrvaokGODF9Ide3dXQqH5OFmdXYIZdQnAGnCAOHDwkwf9p6RDZN/E0tCcLIzWKGB tlunEtAvjDMth7sgTbNsHFcN6YCTABEBAAEAB/9E88O/AuBnUWlULDHLAArsz2g6 LppaKDnN5FwBIe/8G1VN42dEMAzYF69Ps9AAj+BBXQd2wRZ1S7Gpolz+JW+7Pcsg cQEfm/8dCc+cLmoOEUEZ5mV06DE9yxR38zauK8w2AwpAX9f46UpOC1Nzf3NtTdeu vdtEDlzN9Rq7tpH6mkbuRIeGcKSdg8UD1lQ8rnWMILVGIyFIpOBPCpCFnrIzQAST OaJ3y2Ih5ZanWeICLRNzoEU7X1q9u4dGK5AxJUJAk7kcfd0VvmVg9XZW/+x7cqjY foxFu7j5lbhR1euqS6IL4hizB9RWWwWlo+MLx5k0JagMA33i3PdHwsWjqNYhBADY 5zksudjtTV1MktWX1ZOqQhnbcPvTsJSR/uimxHXWFUIvsqp+eMQQoFm6b8uchlLr 9y7WL49JN0LF3ygPH3nTSD0KaRrHLNQshm5KJfMttodRwA9+tu+C2m5j8cWJaS7N 7+hgpp4rKeohJ4giPt80iyro4lS7xJ1S5ST1Pp2oeQQA2GcJdBL69vQ8Z5MqHkE0 6gXl8Lf0K230hhS+81H52AVGqEIDM5bKdo6ASyagez7zhyLzTYwvTvWSwqxZuKCX 7pNcLv+7nCaZfuM1E49NdY062PTAdv7vkFch/NWmVlJxmZXa/gACuuhy7R5wXK9d /3GerQQYE0GRJum0lB7LRmsD/jP1df2qIar1pMTgqk6gOz5rHCZFPdov/c/aLfR2 5DXgul4m/LK2N1SdAUAxgF+3/RmrOmTvMCoBze/idmK2/8WGLCkkPZjzhMSLJnPd L+MtDiXYs9AblEF6FdxW383p+/sUjqA8kmNfdMOX5FCuhCIF3/KVfBvtzlBS5O7O V5X+RHLCwHwEGAEKADACmwQFglx344wFiQAJOoAWoQRGPg+CpDgv5HqrPjF/UGNV zQzfQAmQf1BjVc0M30AAAOzCB/99cOIRsEwclM5/lQCvff5BrQznXKk+VNSu2USU GpzxxK7ASDYyj7/5hRp+yRulWYZDqdA5Xb0fcc/evjNPmU1b3PwfLcb2BGz9JgUF 5tW3GnmFEi7f1NLc4vu+foHPZASarCcUzpYNVgOielTcJ4WmZE1wJzobr7y0xxQN CkI9iec1dhRKKghijAHxrQOfHN4vkn/geOBKP0xnYce4KHCpwPV6zkxLL9skn6T6 TJEHNimYcGZQGIApTBHsw7ZRf1OIBFTgqzee5ZhV0YSwtTOtgcB7MIQcnYdN66sZ GH37UCwzBSxwHgH3uKOQVNJ7W9+5vB2X4fTHp2lWh2PKbfzMx8LYBFxuqQwBCADg o07tvXCkfuUiYvg4kUjcw6zL6CwWuqKkh4mrbnqZhZdVm1c0qPSsFE1QeRmx6GJG 0BiWD1rgyQ+Rpxd2twkiBLCqhbL/TmYnf7wWhe6oeWJAUHL+pDweFrNtjxXI86F8 HUAvIqcZtDt1j1qODt4FUmld6FwYrcn/Pmey2CA6UB/mncoewfDEg5abvfsBEJwQ TBVQu3sXgZY61nwP5eGrsyNDs2H9YiR9OBtBJ2oe2NCBwHYF9aoZTYuQPmp3w6J3 1oVeN/27Q2Bs31qG49F/D2ysG6a/8KX6mqAboZ2uIgcGRFmPR5mQiTJecTcU5csR ginMDCiS+ANmvhB1Nr5DABEBAAEACAC52cL6ZIomPio6qeEtg93hcC6tQPgBEgZv 0wcugygBjgontG6Qzdwn5mAU3SxKCbYNWiuNM0T/xrkPy0tZV5PBxlmXqyftnCJe OYsWo1ZRoqOMDEMntB9c0XE/imr+p7qJ4fVxSd0wIfIzkBfegiRkCMvN+uj/LgF6 IFpRGAiJ1KVBgjTL49/Z5/BSPHy06ZE86CzntoG8dJjVO2dZfpSzTesJYP2UK9y2 xjGEaa7A8C+IsBQEqaietDx8yQMZkURHu0wcW/ULJV1bUiruITYRUWrvnr3pT57T EZGQdUSEm+vDC3o4SiURbPqSwbf8xxFtqhd5MDDUexbyM493fviBBAD86quUiWNl lg1RGSOwywS05Knc/crCHqOauYUZRQoJ4K/TRCiWCZ2m6kn6hoon7u7M+3uHwrUt 3Wt307IzvMhzyVoLzSc29s97ojEJpmksSA+J/G6HwRsX+r7NgAXfM4ZdXwwYYbLV HbXpKobH+pJP9q+rsbwW/sH51vbuui1GgwQA42Bh9r2urUMAS9+yH7Y1RXgV7IlL yrcktm+zgPBsNXUBMhiYCSaFTLMVXvMdU4MHvgfRPkJH4xtwLnIKl6/LXoh9gUAC Blre1NqMEMl07621Nw7Lo0embfiHeLirZBVQDCWYyGG1ZTlq2lhb57CywhQRcoPd ypgPNgujG+FpHUEEAKCgdcOAXDp+siRJmg5kljAy+8ROZ44Q8AWeVkkGKVQiWbEO a/KxNKX+2YmjBdoPDPEJMadryU980JeByk+kd62PfEiveMU9B1or9A6DcnUI1s9A kXqXqeOtQiNHnQdLMaQP+SfyYMmy28UVBKVFRWwxEDVjxR0845o0PBtQBcbDNWPC wHwEGAEKADACmwQFglxuqQwFiQAJOoAWoQRGPg+CpDgv5HqrPjF/UGNVzQzfQAmQ f1BjVc0M30AAAJMbB/9YGmuWVhYeMY1UsQZ8Qk+3egxkLvy11OoaArFABHW8Uz+X PMJsIb9sO5kfpnBTD9knvLgrhdbodjbV/tBogNh3KKQeU9EyRqQ1RUcn9PXFGNaZ WCENDNPE9m5WevtZ16fQCNF277GzCsN+SrJ4ZjsAMOK8+4LtmDSueaTLr1MXJueU xiwBr009lVT7uPMwLYsZs5KhMnHhM67tzWuncJ76Pa8D9CgY3xuo3Oa1kz7d2iiz GsjQQIAqf24IIkwAJKZWwBf7A+BYN3nksacOYIcIt5BVEos/GcGxu327AqJYVYbh P2z3yrG7s20LqFaEQer4cJh0SZZV7oI6iepa/ouKx8LYBFxlbowBCADkVSGPWhzn /h14irKoA5DP2hVqAA3Bzi+sWBF5HyfAPkbqa9W84iP8dlAQRIQJIumeqMnheOTM 9cSIf97DJQc1lK6am6lTq6QcNnpp3tcenr2qDoGJGNvw7zGnWK9phYFLy0aXupMy FZZGX2R5EG1iW4fw2YxSjzCoE/FbFofNH52w3I3bNPX/zibNRtk2GyIjb3pZQfei N6m5m/CBd10tjG58i7IsGbFpBXDCvgYKs7lTfyunbB2So8+Ajo7ZWsO8RfrYDXd8 PmekNOm/5Df/bKnrlqo6/QuWH8Xy1gbjv76pJG/TavKy43e+eGvSDWI1IhVtCvuH xssyerZu3goRABEBAAEAB/wI6J1nl297Sbniox63WXRD99FQEnJ29lF72u/LlwCZ CpT/vndXaEdZKVCUYef450jQLOu7hVj/+jLR1JNfb8lpOMTbOWmQ3yiHBUUfbldc VXs28FvzemnSa7K3QcmOR2w1BdhaQShPEvKLClvEMXTjGokiR4qnCfkvBaAsGqPv x9IcsICImDkwItFAKVabi0/G7IqlBehfAGh5dsLoDAmOK7YRZWProkwxQaBi9KRG nW1suM76Z3QOPxmPb5qX3sbJNhI2QUF4WspafYMxFNoTOjmf6FRmGHGfv7gRkkBN jWX3ZnEoD9xXNHky8FehL/Ogub+xrVqCKQDlDwGFhallBADkaizRKSC+pURSOiAX mrIBcooRQ+QuJqH80gOcMQp+VaLZPWRUjhLepFDUZinEsAM/MbzDOljb2UpKojfv UXG2xlBAW6uuCRJhQpMQj5m+s/Y92K2L2K5K1lJyAetdPyxqX1930jEsPgs7UW6T Qe3dtrqhL59LPQsWFlkuqgjiUwQA/+hqIhrTdkqehBUlE9CuYHEwiqqWA3WzKZBE 5QC8zc15Ojqz5hMkYBQd3q5fRSINe2sfPygNGoIRwRXjFbjUqT7CbRACGi73StVh mtsv07FaGpCpnRcI/83IvaLLCcZx8DDmQIoVl7HvUpxW+sAFZ8rbJc4vf+JCdbq6 DRKBXYsEAJKsvn2sQJpnrpcEKskwV9OFr7asP1VnNYsw04TIaOF6tNTlz9wxmuht kUcHXUllWVl9S7jIqX9OWvPt0XfyKRAd1FPeBdx67OXtAdkVGefCqnoj09ax880c yrGDTGyrxpf/4/eluihNieYbxq8CWv1UsE6b3hn3PD7l31UdfAc8NCrCwHwEGAEK ADACmwQFglxlbowFiQAJOoAWoQRGPg+CpDgv5HqrPjF/UGNVzQzfQAmQf1BjVc0M 30AAAOtoCACwGB7eaNzTXJyV+7l8h9TNukRiHkmwpdcKRlM6rBfs2ITW07h9S747 /q2Vik8BxcrOEgmx+F8680hrYW5spYZ81ZnirtVTphyc0MbBncPBumYjxi8KSU9S MFt/GutN1pbuU+mZeahWOYM9R+nIgW6F0Q2QkyAj2V19o71smOJslRCnbMOWmHbZ CX2jYOujMSmkmZsLGLANgeLs63CXz5g1X+sZi6guiiAvN3XmOlxdz4dvESTtT+LL mSpzs7uDOIdL2wVY2j+5EZt1Bl0ssNEPDm22w7Ca8VMEwlfCfQK7/eZpNqlIeOND k3SVJMIVBxlpWhLGrZqhriDlsRdia8I/x8LYBFxcNAwBCADIz5Ett2/bsGscJ4pQ +RBL6zLcPLqEGpPvpcyDgwutFhKybPT0+SDB6NovKvyirgN+BnoyYttJbEcs0ywI W/djt7k1FXyj5ezlnh6Ual7pHLy0lyKrJ+pE7ghAephVBAsjxWiHg9vmlnlmntME sFN6n/x6uUqq+iHApqQo1JOSzxz5qaH7swAn43/cXJ/BBKjaGUM24glMgzVgEUqR EcFT3T3i7Y7ovNbxIcJwmwoIJmRhNY77dUttE/d6m699Lx71hVDVeOnu5QIn+91K tJX3Z9Vyaw5T61RbU3ArFfzrsAuGTlHfsHjY0IAClSK9OyGpdlabrPOUOXLSOoUq /sRpABEBAAEAB/93EGtaGeue7Ml1VhRy0lpRSVFMuE02yrNwYnQSff7MT8wiMuxC 2/wKPVAwq2JD6r1zWc/WVDsFG8hP0Z3IDw9t9p8/1E+ktk1yEAC9qxw/R6SzxvDt XqtO13vZD1eHAPDB8uh5gTs/S3UL3Zvsqce1a2q+MWMOYWTl32hyNSiS4GEZUdki GRSqWRXXqrXEAzOzwQ2i0IMllvtozgWjoaJNvNg6S3bxrQ7JIDXLW9GdhznBSEls 01++DwIZDDrf6mLL3G+YJ6SJBVx7pZZGQbfU2e9faq4i4Xzu0zahHeOZysw3YY5S 41+NjXnIgUpIovMgsNzGVbnx3zOdWqZdHzdJBADTSNYF9Sx/7Vb3EFE9MYZOFXlJ cYo85p4BS6/0p0/R1RytO3orH7Xh66ftsxNGurfdf0CVF/dWNpZBBwb8ybIF0HfW mqzD/TnJS9rZ5tAtHl+dI3BwV5rScbhtmO9fDufig5TaRPmi2rwJwVzJi+mZfMig ugSgJvMSKhx/fuOIVwQA809G8AYkUfUtkod/k4gYaY8BjJ7J2gjiQjFU//DZ4+d7 aqw/t8NBiQ33zWrQFJ3yIqKKKlSDFGQ1HGUtWDgmvKIWI0azWJogga2QWjxFgRaU X54JI+XkHxbP4GBk6K3LAZYZ/3KlvlXM1zEqGYn8bbS5AYf9hW7f9mZ8J5bbIT8D /iToGy6//c0X+J0itVoy9veC+Iiem7VFQMQPHFRst9q/uEfZyeynAK12lmORW+c2 6MGdJG6BqcnsXyty4TPzxvCX1vMzrRQ6hbm5o0OKnMylpPY7pJwlSuqJb9035Q7H IJf0TVq7J7eRGXJsfZrFpdoVbCBSVDTf7BmxoInBQWNcR6DCwHwEGAEKADACmwQF glxcNAwFiQAJOoAWoQRGPg+CpDgv5HqrPjF/UGNVzQzfQAmQf1BjVc0M30AAAKu8 CACpvR6HjFhNQkZZx/qIxDei1xCcZ84Q1wJ4jdBuOSvmonsfrymIKSJ8+wdVR3DE S8H8A/EfE4BrHUxUuaqUsTPJBcj1GV0q91mZu+ASFgwxe7OOZ5yaJuMQ3zScCggi kPR32yXJzAKhbSIs/5OqGc40fNQiilTmqhvAqat/k08j7Hm3bWrn4KLw4P5iVtfg afqmWpRgdXJZByQ01GyDCoBzToeS5TgK0oEja3wCfbN8HrEL1pLmZ/CHMwKVXNbq MXd38Qp7z7LO/yDCcgR+FSQjFlm2dRLJRDspuoK+a0iXVCV/A4poAVtuP9LG9OPb sqqhrAImfT/XJagLmcc4gH2ex8LYBFxS+YwBCAC/GoB/LrPIOJYiCXPud9qes9CW aA0jC8lv59lVcLChje6b8UgtCtUkJILDQSVTlFVOFBePNV4+uNjnDKOM50RjeTGP U4uTxvvQGQmUDC24dC3pWXMurgAB1ua1JfdBWhz2cdF0cl4VGBbOTlqcKBkhd8n3 c/3XXD7mMU0FqY6veM296kY3Y++oVtlzOq/1GZF2eAY2P7XI8+c3+Z+5si9btNsO 04bFqb4njdGWSrFDemJadpTVAohsEBWgfsiCsiuA674SqDFeb4Kqh+MB2s5q7Daz n1Bm5uCYnMP9cjKqZV99W0y370yNeCCtJyoxkA/cbq4iyQl8jsn0Y5L1XLeNABEB AAEACACWj1mnWfMYjsRxwoLM4S6jlEWFCis3bkTg3ogS0XrHfVv9DkZv/jXYiM9g 58VxqM7geeHp2QEMZ3Oz7Pg5vcbanBQYuJih78ZauC8crc+joBy/2NJvd3TX/ii7 lVLM8SMaYqfDl4taESV+mqq2lrzd6lN7mq3l624+y57EFEcvJJ37FZC4bK5ZhRH5 kGwHGfm1EJIg8TdPKTP16tB9pJz4XkUfPdkAKFIfdTzD4vUS8M1g+GrTyWA0AvAw IxhdyAFhXWkqX9z/TM3ckCxsp+JLhM4nBr5RrvAp5FP739bXiA0wJvd0+p4zRURY Sdyh0L8rUIPVonvj4vhDwkSxAi4dBAD4z7mXID/NbtUe/yp0SP1HLbpDh8Z5DYNf AAcl7AuhoXiBQM/bP2vCv3EaHQbQvs9bN6/AqIlK+aac97HSVhJnJhgjhbRSU4tP 939huxNvHvRBWEee2gH/5XAiVJQkMlrQZ5kPu4jMLNL1naVt5t1Yz9VHDtAKpsLo 1KqekY7IjwQAxJ/0Pv0OBFEKZmfvGvTUm3t0uiavavMOgWkXuUd60gZlPNE8uzJi e2KuadWszgo+eWzpJknLa4pIznuyF2JStn19Tk50vVOtRZFkffrvBa16ezId4XkT S8KlYskNFdCfZYfIncH/uYom3kWuvYSdzAlS0Z8I9TRpdLylzy7h9CMEANIgVZOM vWRdU14oZ5pVFZjmZOvsDTTJEKJ/q+uILJCiGSLjyobcpzQ7L2Pi2M3Z5ZJns3yp VaYvAkm9aax+x6R408z5ba9FN7HL6C4ijMd6/LCbw4GR6LkzJCYAowjIW/7oO8r/ WsASrEqSDbrGP7YYsGwHilgv5Poy0zTM7xn/S7bCwHwEGAEKADACmwQFglxS+YwF iQAJOoAWoQRGPg+CpDgv5HqrPjF/UGNVzQzfQAmQf1BjVc0M30AAAKBzB/4/bzUb L5/FGNGDS1ZXXBWzaXUawWJYelbIM+glSg9jhzwnG1aVQg/7k+yC2RK/cIj1S7d4 T+4bHtxbs/C3diTx5vtNR0tKTXZjt8PnLiJgZt8XU54tiFRznT63Dprs+t+whgxx lPTjsXOKpSZ61JcyCjpmS4gNvCddwKAETIH8967vnS5JZWUgfpjPQ13LUZw6VDsV 3YOcRfKQhAZMfJ1IytKGJn3hOjqB+XCk1EOds9OhyMii3pw2usg0dKTp3urIanKv PzE6I6mp+JrsDJ+nTXB7NG7NI0aSfuwDVCBhkDvAvG0f+8qda5dgpPuDB6wUPR6/ Qh48qoUIYf8obv6Ax8LYBFxJvwwBCAC9Rl0eKkg3KJrNvs/ZMKuJ0Oozr9hVhfGM apESNzTNuAeDcIcUEDg4JB3vR7jf4Lanm8DZjv5WTrrUKNw4hBzOqsUTW0Uh0Sjg cG3HMEgbL8TVeX/WTXFhge8WXBsA86Z8+CGHWxRVQMJkPxPCpsK9aThBoD2VpS5f rJXh+Tc0Kcb+pkCOj0ucCtwp4i0KR+7pE+lkd7AvfhXhh+IcMFD2qHgJIr5rUYTH nq7tlqdLABKebrYL/c17hNsKSvXTVKYe5LUWgpS2dXsdxl8n66GDspvLIhoDPdTZ bgpYj2gPp4K469ak0QTn3R6O7ZLCZMzKeC/JOuJqXfSRvjojdc2bABEBAAEAB/9K h4B3NlMFVTnPtkkFzsJdHCR8gct/saRxlCzXrWFfeA2NViv6XICmqqIW7HkBvuxt h0ki4cmIlqu+ivBcWLk3L8s2WgQY0tFzvxGbE4nxGpg5LwSFkC0LJizM8yu27joq j1I0iEqzXzKsYqIXTa71Ao+iV8SoPkjKZ50FRCrWoMXXioL4ZbkPp4kgTcEo3AYN j+ZsARy2rcu1I8JIzYPCiHCCnex2CJUYoSq+hBGXqmnNEAU+fjAGYx4OoCflXUgO 0PNwKJzLr59GOmboHObpyM7vwH8PMJtSBPhHGb8uJ/E/x+wtAE5l2bQp7j7eiirZ uOX0+tFWcSdKsiuwNaRRBADAzPry1TLNR885TBVmxx+TsUFa8tHPWsQ0XaYy+HA0 PWET9lApkqL5jbSW/mPNtZXMfNAd1FIF2QpDx5WddU+nqjwiOzb6kqVbn0NQOlvm TDZ9F85tM+scjeJrGZJOUNmSwbY8UucCwjWrvva1xtBZXRXCOGmKWhG3J8USCn0p bwQA+1GCaFc+cNLtcAl6x5XmZeBd4CCqDrQz5S0lFmcwlOBcxLmn39LRjQUJi/Gr rTQUDZdu6kq5L8+GZDbDKzIPu7tMyikWvOrEkQCWpjmw1a1MscBoQuKmo7bmDSkv E1uSXNor3JPbzwRXVUD9nu8mSJK/ENVHc0/SXQBUKRX2UJUD/3r6FgtXf54axPs2 cTfM9HzYxi+iDqDSqY8KsUvZtH7wBrhG75PqfGHqU9Mu2SqcP1xPdZa7mn6JboV2 kszoKj7VdPlWHK+Fcz3ZVelA46cDI701+j+6KLA1K3iCijiK1/QJqsHBvAPBKRCj DDt+Q8r7Bxu8o/LUY4fIUn7gKIbfPB7CwHwEGAEKADACmwQFglxJvwwFiQAJOoAW oQRGPg+CpDgv5HqrPjF/UGNVzQzfQAmQf1BjVc0M30AAAN8XB/sFA1f1k6WMjLp6 ejiQiHtz1ZVqmTda2E6sDhu+L087ujMJEluPqAviuCGmRxDiNtwLfDNZUAKLglqK cKt1zOUHvKFdQUCyT+2lAs/BUooceMRuKuVBDUa90A5Yva1oxyih1UY4QmtYnCqs 7PPiEfhsk85Eaf0iRBwlwxqT6eSrYPSRdd4X1qUwyhM22nAcKc7RhYvXORhvDaNW swKItHPtEMes/byyZ8ONY4gxzf8nxUc8Uw2LgblKGvhcgH84Tc6Wfat/sTAGsvY+ HEZof1qR35DhRGxo6KolTJelrF8vaEwlpzm5L9FqtjHkQyr5EB4peQZRRHycIdn5 GLAnSA+bx8LYBFxAhIwBCAD4RUHLjoNH6HDzncaxEhIg3g3lol+BzI1K1Jdc2+vF ObwmX2Nth7/e3jHFZVi9UsM2mROTnJ3kS2WTZvd7ypXQGg2RjRlKDpUpGDni+ZGx 4ZoBQWeI/nYrAdiEFZJeyer+Z0qS7aootq6x15SpmJvzuu7sZmfLptmCQLDrr2KN VVLR4qx/TUysgQCuuep13kNFr9kGFnCUwN9U6rEsmWD2hNMqTDP1kv1jOvmRbArU 47C/91YEvqjX3101OoPCe75l4uo3Hyl6YW6AqVsmZQQpZS1wijCmPo/O/f9TEeKY FmOS4suAHcmGt3KNiyZWlCw1ia7MsVLZBgfwoueP+sVbABEBAAEACACDiw6FcAkW /I18RsiT4WNBjuYGGbZI67yhPWrFhLCzWxfZrQjda+O5jjkIwd320fck4o6A06bE 4z36j7/pEzm1fVhbGkuf9YIKuA9Tt6/+c+5JZAIbj9fEicHSmitnlcyw5sGYf9x8 ne6JxBO8mGNpDE6zar7sJIdygBw9hDvPba691N0uX87UWl5zfL5oMzZp+XbMSwyl iSaMbNkqYnPyu5axCqBc5okJ0IWuCtYPpHq8agXy3sTgT9fFt097ZdWiIxg9doPA O5ZDdG5VM/jqwdFZTQI9dO+FkdTnawh07GYCMaZmqZB9fy5Lxi6mgrfnwQPyFNRE 3tDipRSPqyHhBAD54T2nJs4G9ExPzy1NOTjAe6NCPUZP4ckOVtwVF574ZgyHa5uu YNGiN6/wUm7Y+X4UscOgFcKP6scwaAAe9Hq7E9GOC9I32fSJmMC4mNgl+9nXFO7n DCyFyp4rFaa48CCtoIJ7jGSLXGv1lgLR+01W87j7DNy+dp4ezMTfdXvqJQQA/lns +6Yx+JLGSlLrO49h9dHdZllyQU904awFbTp96k9rR8niypcm7w7tNgWgtt3igqiE FQf8mb5vurHPOCq1t9tOtebz4EQ3SwGal5Iaqw1Z0sgF1k73X4xXYHRFF6rulQRl nbbMVDNwaShVnm32i1ZzhFYpwg0ob5ox5/vUGX8EAOeU80z6ISp+KhuD3m+AbAT2 IxsZeIDvBBmmAiP2pqE2Rtm2yonCJxPYqdY9oKX0gFiRrQhDJ7n3IagyesisTLUc 0Ut1P8ZFL/UsGo4STKJdOKHq3DFYmiifi+hHEQ/geJWlFVj40QfRx76kJPkjgCub qjsUNitRUfZVCl+pxmt9SgXCwHwEGAEKADACmwQFglxAhIwFiQAJOoAWoQRGPg+C pDgv5HqrPjF/UGNVzQzfQAmQf1BjVc0M30AAAMi2B/oDK5KWrzJiO7SiF2bwNnBv KmmFAJmg33jnfCpxktYLrOqBP7tOUfcW0QLxY2T8zo8H/0OXu9UE0bNUUIT0iPID qaFpLu1lq3Fg4pRTbi1GJ1GwjgJheUPpm7n/Xp9uW/NJGVyJsq5efdQ3vQsTxu4r tuBb/IIKtZJ9MKRjTSaQ+Ok2Q1ulOPqTHzeLgK5cfem77epst3v9wj+oiiCmB148 aqMVF4db3ShJ356zp73p+jyOklf2Lg2MzUViI2qH+8XumTXNQVPXaFMypHU0a7PH 18W7z+VcgEZJvKspzPjDoXWwvHx7BgWl7qbMp1As6FhxCjxAEmtOIBjWaJe+TcO5 x8LYBFw3SgwBCACx+Rqe3FfTOqBMEJB8L78eZDT35h7+XW+CqxHDWcUTfaeTm2uX a2fkeTEdsrWSk6pBN3eJTJ2Z2d8GxlvCBwOn+0XRphxzv8nzielFRVpd8Mlpv28P Np5JhNgBl4iQdbLqXiMk0COXKW/I0fFnT9z8jHRi5kTR8j+Ylc4axGQNMjCIcoCN F2empyqZ89GXmgf7QA/c5jZAD8CFd8EYv7me+KCNbEUhLwenFIAHcgMGFVXIA73l cT2sxuLSsROCi7aFn0bnsCEqXdobKYubcUi2pHrk3ISRnLQtRQbIZ3mnPwhIa+aK WesOFpdC2yxDW7vYRl0rtdqFclN1b7UtCIYJABEBAAEACACs9GFrasSq0PmAkkRn e1snJMjY9LjAB3tbp/XeO3orI9zbtJmNkGJphWE29DpCVOCK+mVfd6ZdIn78LX50 g66I68cBI4XY8tRGqYqZt/lG+74w10oNpc2TstbcTS/4+3jBDHTozKEZwLoSlfwc o18lyzU7+3177gjhtwuRtbNpofEYlTKLa7NUjg4Sc6LED7n+DzEPPVEG1uXh6gUf YBnEbBoyv89jonwMk/r7n3XfPJkvsIkDv1G8IXp4rZzSadcwvdQlY2bA//nFZgo/ Tu6rmrMq3eTgGXpOu3k4PpxhVOtCm2uN07uP/AtBuQDTodDpn3AxqlxZYpEym1lK 3DQlBADCWO21nYupH1aOo8gi90Yw5Y7gnWVuNrWoq0wVbAWYqkfYeknx1e176B9F 4xLK8RAOE3ceCqfsUoVKY+IPkFmMUfQF7+/upXDCNVqCBv5itcttd/8ICLn5k8W1 6xmdm7MGih9GdoJneaOsX9reC5IlswBMGYKIh1iTvjnSl0G88wQA6m5oOrp+vv9x 8dhMc9eCeoUQiRIhCgEk87m1kIVsVYmIy/hjWUoRF3kAebyYCH33/myMbbzrgID+ 4X7JNIaqsjZhzzClIh0puppTopspJOYXpUC12ttpzLTtcG9c0Vdg0Y4aNjCh8riZ H0lKCaJUotJJ/pfHhDCElfGpC4wagBMEALg3/TWyGCaek8XG/BToDp016dzFiRhJ au+9Kf0moiZNNXqq0GBuiQjOvSh8NkItZf5u/pqfRQw6RInb1RFq3tTUewEv31RI eW1LSf5inX6hgubYYxB7fvSz7mz9BLMMB3TrxRO13N+MyqylFovr3rx603RuaT0v 3/gWHwzDLwitSVPCwHwEGAEKADACmwQFglw3SgwFiQAJOoAWoQRGPg+CpDgv5Hqr PjF/UGNVzQzfQAmQf1BjVc0M30AAAEXmB/9tP/YjZwSHcm4sv976MHLia5WmsB9x fTDmq8X1cusy5uhscjXYijjGK5AqcYF0/15tEPxKQuItC8SV7uLBBxl7AnJz5SIY Hdno80dylSfcXD7R4DL5TlOyknYGZKfRv/VzLNNMHqmZRtwPkP9OEYoWmo4GBXms THQKmNfdXuvrkTiFhWb728Sxx2Oj458ca0LFo8IhMWZj06zirUbqns7dao8nZz1v fs+3myK8cnaT10VCvSow0CbhAlwqcZKA+xr+CLT3dR9PvaAAx734EDs8SEfApdQq 3u4LPyaMsHCsRH6vTfVmsBXzVLbei4HD8OqRiH3EK/iEgD1y1IVRioXLx8LYBFwu D4wBCACuvaoNuNbEVaqvI7TfK50oVOwnjamQWZAOio3lAZR/SpMsKCVUYvfTxVyo rDFMICUbdj9LZzBYhVh/evh5b1tXct1LYpH06yY85PIlXN+h8/Gr2qR/5zvP8By8 /qPGtr5orwMy6/QmlZxZg9kcVSmahyG+/VSJmbqdlzhwgriLHc23+HwhwMv9C6I0 RodkMBVgoPY/cMXc1TF9C3atIFXmhath2MLiPeEjyIhhfbDLBGsGAMDrTd7qyL2k JI+f/sBbV5vhkjeBnSjv7pgQDFQ7Y4twt5eM0TVk5hlJeDR8a1pICikeh6uYytlV Rkwg1KeLkkrri0kl/MHS7T9Dm9H1ABEBAAEACACoGAOyV5EiJVagTDT+SjJQTgEu u3PWTJHrqxV8qtxy5wZUf/oJB3tn2H+eMqbpmDKaDFIu8wDq5ruPRngoORshMHn4 vsWxWVPbWFIn5wOqA8UDkLyV3ZcIHO9IT5y0166rfVASeDDRvTI+WjtMqg/vevbX h1L/W0bNul6svNNR3q2EWvSGD0iUwzKm2FTlWq0QL7AR/9CNgNO+HJbVmZu4uajv oqPZlIBot0HBDoJaXchUVgvJjmDapPhTBtSr6e+pyqS1WXYbeKUKM/EDDecm32UI qDvikcp8GlW8U2WrdsRFQeIonfXRiwysBtjXYwz9xsnVkCVmLABNkUzBFRExBADl CE3ICHUueElfregKvZvO2HtZFMD4Q77p4E/TUS7c4SbCnmDWO8FgLPJaCfMZNcST 2gJpVgHREqm67EQeyem+r0feM3dx2XUQpQJACZhpyhlNkBZApz15xjj5IVtfvBLw NTAC8KFe/TLQqtCHqQFuSKHdWudjY1uZcGRdamJ4dwQAw1DbUGnohkqccwyu+F0q DiA1E2wlHPyRR7KgbYnX1iWO2chXzHC4yc6z1YapAC6URmSKawLP811V8oY+ISOI VLScqwRxCfQMFy02IySvSjS1/uNrN4Ew3BjuYYnnpQTWt9O1VFjP6DKpurR70oqV 9Hh5yx7/sFQwszRJEl8vj/MEAONDBGxvU/0pjslGERv8PFs6L3RLTceK64+kL7H5 aTuwx4jqxx56UKZfR2bRJ9ZxSI5R7FINummwlyKnyRngbxWcI2J2dnGj13iedsjR flBSvHIF6nfFPxFwUiXoehYKvClJzcMAPUku0oMzlzMRNL8ES6Fiq546GWv1QXyH PeKCN+HCwHwEGAEKADACmwQFglwuD4wFiQAJOoAWoQRGPg+CpDgv5HqrPjF/UGNV zQzfQAmQf1BjVc0M30AAAJAeB/9jJwI66pM18ZXntqysowNZ/vRnxLsgyRxf3VtM 8XShYkqI0zVEQWqFGZ8jbDIspYsCnJjbZo+nSzY4n7cykQ8gX/gf+QrOP1A5lL62 n8MReqqjQM62U+qOOqaReOWAPtsr6MWrMZ55bRpkbsCQJD+N/i7oIFYOLLdGij/T ICkatGVEF/ABmV15KdSoMzuEJcTUC7bsf+Ws2d4SghHcpFX9DYnlcb9teT7hDISb CuFR+a8R/o5D/y4gTeerZN2mo+teM9A3Gny44lcaH8vqCmopqrRy00/GDIEq5sph +H20bfh6zG65lR0d6bloCbm/gL1UcTlhMUqUwCb1jjvrAx4Xx8LYBFwk1QwBCACa l02KBM2QmtRs3WhAtnEpO77aro8x7UNke3FdeF5Dg4fM/HtR4LazTag7P6Wu3NS9 fDtpoQL81poItbscXm494+ZoLUZb8Pm+tGZ6KGgMTG3rmlYz9Dagw6tzHjF/GJl8 06GzluyuggzcNXYGsEq2t1s/4NQvZq6Au3f1ys4MEg73rKXJAv5P+BnJpwbQw6aY A1np3z+d050kIPGIUB+MwrdeN7x2lRbbriteK0jnhlDblvFz246R5MDXZeSZaD9+ /wxFOp7Kys3sI7PvKWyQ8mAgNIfyeWlvNmbTa0y5WVsTzAIyYmpNzn4X50I4uuFr WqNQE8ckhyMDtJFtwFiNABEBAAEAB/9b/DOzMHBw3vAPZx8lgmmLM3W5Aa1K8/bp z1oBWCIe4iDoJEPs0F1mC4tS1ehsSBJ+PXHMxHXWpIs7K4eOtdG7GeR7kJURGC5C /20KuwhOaRvu2QL6HxJyTnuyIAErsyOBVxwclG142QF19omoKTYLysaNF+ap3dtv 1hznMCmynA9z+iJi8TqF9xDOOyO6yTYEU1mb31nTUKLWttxtpFiqMl8BWx0wEL4K fzzwmGkcsbB7Aoj4dY7sc6Xd86h6syEF/xWfc3yPZPbUj3iQgCRGgAvf9XM1q7za BlMInsxZsnTrOlc27uqjjpu3y7K3koeNQSM6z2zge6r61Iqf1NIBBADIFlv59qD5 eCqDiompHc0QE9B5iuqAEgtWh72XzIN4QVcWts7cp/ltr0v66NE8b5+DLWuI2AJ7 jPKWRprrB7qXPbEImJhxNpdgjTNKkBljQZMQvPPspMGQYTlMXPw9zoMya/Ie9bAP mTTGeec8k7nWdUugu9Hgd7Fp0dx75/rgLQQAxcpGhQew/yZpkvSk7cvgf6/pQm1x 3JjsBs43eikIPxupVBxGZ9WouVqeEFF/zE9kQwBd9SC6ifxyiCR9hVSuV4A/Ql1M JzSOI0iuuu6rRmyEjmIdUeOPPBeoTkddkDbCa8oE4k9GLsi07SuDpHbiiRXGbT5z ldmn03ay1tA4NeEEAJXk+ahsXHEg6ULRRSDCOPImXT6YgdZ3GJDdR/XgTNMFgUF9 6Zkds+hipDO/NmVAZEkmLyGum2LluMg7THjlb8mkwJ/wys/6CJJXsbThGntqjT5l lWw4BXIdJf78/QYaGZL/9itd3xnyQCYELtRjdWehvtj4dpo2XAtcf7QJpuMoQiTC wHwEGAEKADACmwQFglwk1QwFiQAJOoAWoQRGPg+CpDgv5HqrPjF/UGNVzQzfQAmQ f1BjVc0M30AAAMoyB/9FuxFD3rQaG01eavXyjqvXVm+W7O96SAnCX+2mdH6kS4Ce HO2Oz6WYugKP77uuleJ/IvZlOhV3Knmp8WTmu5QyPbb27w+tB2qcEk33SOaU+Z3D WAhrpb6Q/3JO+7iTBnSF90/5xyS4rl9ECfsFf6BoSnxRBtwEZFGeQsY6QgHx03Ty zkq9hXKBJSIk7nMLwJFC6ktgNtmHo1tYgyN5ex0qa9/XqcJ8QDsVf5WmKxE7ccvU /uRcbpsSC4hbd7nL9/PIIk1JaXZNkIsrQlL4i2+od7nxCoCECbO8B8i2ZYuMcxBX nwUo7FCbthhpOit9hC11WTXTUY7BdBE3vFWgDspPx8LYBFwbmowBCADPpQ/2qyZI /FM4lX5qv8f05vnvpe28QqQaRpfezTEsGWZ2vrwGsg/FulFaXCCE1neNuQR4qpA3 utj8Tmd3eCcUfVCnslBXMWVGd+/6NLlVEaWAgKMG9ECtkUmF0BrvjjWMe5o7ilia zPiGL/VQghA9bounFU5ih9h4c7Zn+7c0siNSIGRN8XHxyV0OlPmySVNdQhBv3505 yryLqBV0k2Bhf3TLiy1UGQXMbl6btdk8k90V8G6KRb5CnH/Z4HtlBMXWxs9ziVPU 2qUqxmb4fMj8y+hv4ZsxsM3iPwCNgLdgGmv/uAFini/6feUuxLDYtlwNFrnO4ekl BsAwBErGk8BRABEBAAEAB/9jK25IXM1YJkqIx70stpOFP5s2/YRhWWKOuhBmdJF7 glYReF7Mw9YMlUOtaDqPtu46XZbpGmzucOqY9cksodHoU0FmFh+QxSKdWULfuLab DmaAzO8PrzAEtVLr+cwjhUPF6HJs5VIT/LjlPZpn7PZcoKNKFT7uh/q27Gy9lNaW 72/JUiOuzEhaPF/1F2s9LlY1UeTpLFgcJPOGpBNNDYvnozO3PNLOlGgg7zunmW6p sDRjSU92o5kPd/WzL8WejBmuQbjsnPdEbKl3Co112v9tt1kKeKdOlLOet3DY/DH/ bJ7pHB02cXHA59FdbsZGYVHgLi7D54zKEJfxjHeuz60BBADPt5MolmkASwhTGiNc Dk4csGT2MmhbuOhh83hWZq4EbpSyPLFpiMQk4kbY0QEpVyjtqMpCIlVzCNjbb9aB 858yilxBrG2DlDOYQzMlIINhiyYIqrd8PxFh08RK5d6SLLaScZrr+SIn3qo9JiNu B/181004Kh7UyqsO+ISfBo9iGQQA/+kvM0TESa9aMhXA4hOo345nkdLNc30tu8No OvjR83IGT9ZDts+frVgEznZ8oG3JLA15gq13k1atMJM3R2sJLzwYkzjg1ADj6zW7 l2ypw3/8xuySssNp+rv6ZwC+BwHerZI2BtUppuyKMPBBcHG4HgtrfYEdoXmfftEx iSzuxvkEAJGP8KjVZ1bJxNdGvd91zhqW8+R7YfVySDm8NLuY98seC69s396v+Tua 7/5Q6IPFoONedbLr+vx8ZsiS2Rt+Qjj3+YdUpQu8K/kdRRT9D2C2yxynKYWzFcjx Ctqd8CH6WVqTP5XKbGe/6BCW0Xr9YC06U8RQiaadIZ3DxJXW/Bj1SzPCwHwEGAEK ADACmwQFglwbmowFiQAJOoAWoQRGPg+CpDgv5HqrPjF/UGNVzQzfQAmQf1BjVc0M 30AAANfPB/0dFUbs59NkUqUMpOdSUVq6vDRxWj+fB14JwY3KtxiH3UgYfULBIqjX 42T2DT298XXuBiimfIueTBvB3oSMOkXVlcrcUfnckZXKCxPU5z3JPrjBOYP4LuzN DfxEUr5S23eKq4TVsoJUMeMWgBZM5miJbrWDsyQtN5EecMKsE+8qwgqM85jWDS09 Bp2nByEuYhJZPEH9Z+ErSM7Ocboj82xagCzBU5aKPnwoWzIT9++heiazpfA3CJRf EIKhQOHS+iDy55zaMQR06sCxF4URX+Yb+R9S3jFkIMQ396LqzxKu1b75gsHYsWrE u9gon9tTneSyFvghvKSo/eGckj2F2jetx8LYBFwSYAwBCADHYWbX6qnF6gsR1CvV WrzD0dq029YTfR/hTDAdqHSbZ4r3FR8t5OJjQq6pMAOK504F/i+0H6r15yC3La1F giSkIKDBe4LSjvilBupGlOcq4pudHhDnpTJBKZXsQJvwbZcK5yqDRI6r6QBLZ7+h wxj+IDkoUxK1JFDWVO3z5fi8UoISCysGQQ6kazx/73/r3oTFIuZ4u4QCvp/y5aXI HH77nBd8LR19pGYPQ4qxV0wzhPnnIkMoISxVPwE+YIx1AbY3/BzCF0GIWhj7JeG/ /xRDh1XvNDcZQ5+U+RA1YbWaLt99QuulaN1hIXSvsBl1UHAUhY9jZmsMGqszo2K2 aL2vABEBAAEAB/495Q3hL5ceuqZPFZe59x4siCQmCyztBeX0sgsp2dbBO6aXR7ZT L//boqLIIQCoiV/eWmeCumX/So4Mb6CfuGQZk4t0JRyaswmbaYcm/Ci7VnfKkb/b uZvdQMSq/++o9sxx9QtsjsbftUiDICUMWZ2Tnns/+nIPS8PQlbL9CUJra4CKDAjE jv6Woc+npygUJrSDcyrathYiA5PSZmrHzFzT3T0olLznIQWC/+fBn7NyZpszYkNJ /HdmqhYHu8JfgcxKuw7xd98KXLxF+uneGXHOXSRT3FlFWHOF+WAZH4Vquy2wRy0y u4ZhTMTZpMOB2Qtyl/LT2KZHLaKm4Vz5KXQJBADJqLzFWgyG2DlbDOhhaNAKYZHk czFj6nrVYa7zTybMr7wSpX78cAB1qx5fX1IO0gtvJTWjEAL3WRoqEIamxMTWA4kI ZKhXFtG6f16aPNxYyacFFUs9dV+oTNPStla1FlpPSJleE85iCYSuuKU331EnEB1J nhWp3I8U6+XUikKdpQQA/Rt5OCCml87OnDZODXiDx89Rdc+5GXZZ45UZSd4mZa0N ZIi+JCAM2Plcb8PT015AvCu9tSrh2rNe4H4lRIkKLoB73dYhllLVR9Z5VHW+VY6A FdmJYYDwRhIkCAZqQrxe3qeVg9SpVAkK6PC+SXNPyYTPr7v8SKSuVKdYcM9RtcMD +wTz6IXsnojkhO/4Ml3yQh+osinKoZ/kY1xrtRFshWPN3GzY2OfYJ9sc6TDE7W0T 8hJTz1Gz96PwftX3xaKXRguhePZJVBeCM0b9LCCVPHHhlYNvdaMX8ne/5ltI2tYN HEXfBnNBzZ8j2OWh88jolnQcGDMWBxC/sFKxN6Y+UWFuQ1bCwHwEGAEKADACmwQF glwSYAwFiQAJOoAWoQRGPg+CpDgv5HqrPjF/UGNVzQzfQAmQf1BjVc0M30AAANJK B/0YerEyARrZjfeAqEExnZ89rueYYoiOHzIroScBL7tOVBG/MFsOVjcVpTUgC0nB vG+hKfbp6+4fuThsvokk4NdFnP3Ky1AJmpdrJ3b0w7YFVcbSkXF+J3JHeUJjEH7L eqw+xTU49bBgEwqKN0MSsKhZU86j1uEbIDdzgY/UkA8wDI5r86cFbrTpP4BOpMm6 aGU+z8OsIfIwmXt+PW528kuThfIraRvFMbCH2t776cVSY4fGL6k2ZzqJm8jwEFps efVCc7B7BiJJ5VNW8JJOcpo8L965bI7dP05+wcIHJwliYay6zU9a6aS6NUDAnyRQ fHWD8xUyXA4XzJvHM9+GjWiZx8LYBFwJJYwBCADgvf5zLP6XIEdZovpmBXSGBf5z cnB5Xr7lOdYk2zckTQQqXQp7xA3d7PtI/ha2l5owf8vGamDb/2+XBdJpuknbqLUS lm7J5uroBOqrpwsRlqdQ0xe2oxLV7nZVTquBoI7s0ZUfbd+JrcAzgqBm9JaowcmG mP8NHkZYfd4DQ3Dti2Fp5608AiSgnGm3ece0uDBEQFoGd9R48V4TPqjcj15Xoa9n MyDIi27+xPuB8RdKIbAuQOPh3Gh+0JETs09CyTiC7X+yZKAjZvqMFkdNtYV1WjNH BYG04XIVriGMRUWYxweszoLN8+1ZUSlGNwZaf8XOIkGi1Th2A34Dd5wnhI4dABEB AAEAB/9LxOJXVFEVBGXpu3+uC6LIWJb7txZiZOX5BDHFRT7m9ywzWCDjryp5qp7h cgCYWK48Mamy4ER9hzXI9caFrK+f5dX+SUdavIZv6NBxRKT/rgoEvtZc1cfQ5k9w 3VAHKOlmwBYFfZaCpViKRTzs2QM+K+9UeJpsgst2pCDIeuFExDo68Lj72ZlfSttA dE0qJ04Zb/FvC23HTacMDqTlMFIKki2eqwOUc2wGOZ1dIrS+g6Op1N3n29BVIq8d gsjq+7ZyFIXfUFfEfk/PH30W2rCwDNmgEEhzCdToFpTBUJ+xPt7tKlTA7JFXabH9 iyqqnxZvXr+0NymAHFsNbGnLw4dFBADxm+uvlMfDoiO1y4v1MrKiBbHgIQk+efPm zsihyqJCPQkkCkKr6yJ3PKMpZlXejwoAAtXeUsli3x/+Xnc995p9yTZwi/0Pz1uc WZSGQLd1omWsOYmlzJGaznxnLM1i/6SoKURK6bBtXhK3BgItYws1wavykJtJ7bEN YyQAlihSswQA7iDilTgP4K05uYyIRcNxu/qFZ9JZ00SGuaAumTLjBunrTafxHTKC +T7XCSVTkrglKTfYE0fAiWJXBPhYBTkxeGtVesn3X1TjqHjj7ykOnMZX0IjAepoK /t6ZSdkBKPC4bPoKvB+d4b9C1TT3Q4RZFEouLt+Joy71VI4ee1gbw+8D/03NXHui o339QgLjgNOv7lQoDd/SHhbHVQp0NhS8tp9OG+N+lkQPYXtvAJvjc4O1H5vkAkQW iDOsFMib/KxfCjzVgeGyKqwrZIUyVCzg850p2bnmoI7zufg5ayrkj1yidrX54J4X W2kIisZkaBdtbD4iv/vZWs0d16Gqd+fHOuayRHTCwHwEGAEKADACmwQFglwJJYwF iQAJOoAWoQRGPg+CpDgv5HqrPjF/UGNVzQzfQAmQf1BjVc0M30AAAJhtCACsZl7x g5S8EEsMNgKQLDQ4ywAtxYnS+tGqGu5wuvzOUl+f+QpbImkqwdb8x8lT6hdxm1n2 Rut42lM4wJAJ8bWZ/LwVQsP4qLa5SQCl1BN74P0FFUv3RmUel3IE89raTf97tbLr Xrv88IN0GgvftweHlCH0yrGBCsnUiCd7JlY8G9m89Zv2ngjJpuT4q1B8c8o2h7Hc jaNyp+3AZBBwa6ZBJ6JXP/KcxTX5G6o/nhIK240kQrjiWh5VJeewPPtoK2O+AqLV lmvnksQ6SwiPx09ZkS0O3RuTQXYj/rDprvTDxVTydRDEOQECOUJWzE6w1QaY7JIe Pyd0IRpeq3bN1s47x8LYBFv/6wwBCADTZce54cymmd9TcHv/8Vd4X0otJieYD5AV mvxs7JWSmkATHGQzCotfjiRJiBXJ2ZezSesXlFfqBQyJqI8UoOiq2mb4cvh8oZ2q 0tjNyd9klQOcgRySMuOB/R2KtTSiHgXJae4kSPGbOYelY2lF1UhbtbF1RahU54e6 FCxIAWYKCNiSy4V4mXgK9HUZxehbM8FtrYWIEGCrr4+RaNew/GLz4YpoQcnxI6eX RJhWxGzTTqhJv+BtY1ihZX5NdUc4wvFmIb/jWwB/wiPqR6vMTtVSO+QEd22+kZGO BLDTmn4SJHu/35oShI6gaElMKIeCvVz7l5dl0x+Q3TENrNwVCK1lABEBAAEAB/9T 8mIKzcEAE7CcqNmE/KjYkzaYkUM93khAXCiLdLO84OY68JZVsoVAL+j3DkRgwLi+ KMdjuLPkqZad/8K51WAvoMUtOjK1A1TiJhKUPespPQePGbJn2C+CSip2D5lUG4n+ vsMjw5e2JXVZVw3R/m1ahd9vi3baKIs8eJBRE8UajEnmqR7VYuZzmYHmkDLtxQN+ 3xhyfeJwmIbAzRa/AyriY0a6V7iJVweVaB1a1f4rg5+qNxB99dAEkKS/gFcVX6Vg 0pm5L2vgtiYGWQjF5iik0Qzpr3JHsfBwF1rl5XQ/xobmt9eoccWo5JCliqdOzogC 79MDNiSumPxCzd5hwf1NBADvVSJx1sizyQ9FUrfm2J43rc1sry41oantR9+IA/K5 TN6RQQ3IeBPiqILJiKvAVqPNvQ8qQZKijeOAuont8M0kjREoaBa18sbWoKT93hKl s0AIM3CpSaRxikZ8x3uuaC4bhQd75oJ1UH1Rj9C1BmaQTW7GGGPjM4brxQ4J5q/e VwQA4h6dmixlwTuNDgRvSTXHn7nPZqtbIXGSv2uaNxLHj6YGv9kqCti/Vdo2D2w8 zcyDjenYsNlpCF3EsKjLlxP502oJygjOOIJHcMYHnNC+z97x7R8x82ptqOvkGq0Q nYsvgrcD7ULTOdryjmxKAM1bDP9zljkSLoRfTVeNHPaZRKMEAJvg9mYYvJ6bT5bv 6U9huRwWZi5wqb3V9bX5tyjJbDW8C7Hc8WtwXOrYGUGldixR5OwxOcJk52fxr3gW 0/Mr+bo9NTqS4axdd+eb4t2Tz3XY9OKPe4CsolwxDWbWQjWwGqSNwjVw9kpVa4ks FQpXm2LyREOuXII3dHMeeaLUCysQSp7CwHwEGAEKADACmwQFglv/6wwFiQAJOoAW oQRGPg+CpDgv5HqrPjF/UGNVzQzfQAmQf1BjVc0M30AAAPW+CACQ8hYLQ7hV89+9 G+QHnNsLhPjeN8rZmAgQqF+rjENF16eRetIt7ZZnrg/IsuqXFhokFSj7z9D9opVY HGsOMlIPi2quABw+j2LhvFYiFeS+ptpo7E4LtY8SzWLBv+mJYA5hSkYKqpjNIM9n jd2sEiqY1lOEt0qQZtzQHq14dVHZG8ETBhtneBpj/VhwwrAoFB5Z7nn6QRv/L6yj KfA91lA98SSWYKRpVLfdp1UEJUshEi/WOJd2C2o1cLOHuIHSwMiuOnZk40o8WF3Q BVLa+xax1c+bi84hFYghwhDiQHNdJOZG/2REkULb5GAh+HAJS3mW7DTGIiqb+n4a BOKae3DAx8LYBFv2sIwBCADeO/RXkkDtm/KygpzE+4XPxjPrFgWEZJ0EVGWuQrLY RUChJLq2uPudTbocrFKeLtD9gu+H7KqM5V8YKfpQaqHY5+U4JqlcCPndmeuCiozt qMk9cMOhcQzxJeXL/2oA6Rhd0+UMMvyKkk76gf9wW7nlkknFKVXDqIaGdugwA1jf gk88He5E97gQsZL3y9o0Rf1tJUbMBTAAYWmkZ3nkgo5FPWBSKVTV/+D0dFGmxRwh x/0s9izuZazgkHvUAqGeX0kUtdFuw6ojfW+D0G+96iJxt8hEZV9fsTmEyH2UsMMr vTKt0vxNRrEWgkSYYv9ctAdMjzDh8UPj9ww9GuAqVyvVABEBAAEAB/0TiQ+slwWw HXJTMNoZEscrgY/UzYXEopeO5uBdVi2kh0nsIG9tWot7w3ZlyNwmXZUvg9AMkchY RF18oUwIv6bki1YB/pwKpBta2In0WruStLCj/wKjcW2i0SMHMLefxt3Gebb7wIuk nsHkkYLj3JxVyutDMqQAZBMXROKaPeEVHJRdJf5ahFtxHJvNn3MIVKEndHi7aIWb prsz5yJRBOgtjf890A0qdzLWlhuvrk6HnBL3tAxD1WW9gaeMpLF2BK3vhlg6OtC6 cqHXqhXXso9wNiUV45X/TPIfIoWlgZ1j0h4YM9SJXOVMlHJaiJF4VhOw9ApS9a1V LoTqGe/pEQGNBADh0LbudzzU20AT3qbxNE0I2XhNydMNPSAXkAYhB6ZEetogXrWC xWnvlpZFy3gtf40qDsrtWVcjzOaiSInKs0VKr9DhEhfi35dzR1f+V+4s5Qb2mzPd ZpV5UWOacjPCF+K8R25NIS1PrurR7m1QfhcAo5Vnq8QVjJ5EYG7f/23PhwQA+/Cy V3RhcC+H8NiQ/t9e9ef9INRICtkaWyk0xG/vp2Xz8eJUzf+BfQp2RkGBW69mkbeE 97BbHm7Uga4fNLy0DtOeQEc0x1eoHdE0E6WCxjCuRdyITDWtniicnuDuuO/4aqeK d5LF8X3o/gSz9gRcPG1agy3pRMm7yA599rPTKMMEANS0+2mAlZ2keSpD+rON8u/4 dN3rY55NEDBErE4+FfraymE4XGzFB6Tdm54yuiXvI9zWGIjQYpavQ0oRFLWvB19s HHmZu1lP8W5wys1Zfm1Q9ckxSw8WYoQP+drHHBOaR7kIcVqgIPbJPHWndTA/5l/z ekCpNiypHsofvWUG3KOARyTCwHwEGAEKADACmwQFglv2sIwFiQAJOoAWoQRGPg+C pDgv5HqrPjF/UGNVzQzfQAmQf1BjVc0M30AAAB0dB/9/1w/JhhXw+dAV+De2lrV/ nQCPbs+ZslBAoESTy615dMLQkGSnFHSvQLlWK2lBblsTeLr+KMFk16tNOnpbENdU Ts63I4QYdIFdnZKRKcA985yXVDDZbRSWgUV4NdjqNFqjjpFVvyZw0FHjA/z1e4BF m2uu5ea5NVHd52IFKRDx6qDe7+5h8YqDTDHKlwv1y4coH0nEQluu141ZIH9R0d9u UnzYWG2lZqWZ9tuYA8JTskmXEkU1PKP52S+o6iqcFq4c5plLYHceKFpJ5DF9LHGK 1/bkrEiyVDD7kv/9e454Jia9gJdfjqAwOiB2snFh2lHHUjOnNkZ0HZySb8z6UvtM x8LYBFvtdgwBCAC/eOdFaSfQ1osnnJ2szKpemSN+HMaHhwFNV+9J5touYsHtfqFI 6FrSdPTG7PnTgQnLRgP9nh0KJzwsjLcCjDQ+kt8Ssf+/4bhPfIqRtMt0tUX+Psrh RbEyn74myQnir5bl08d5YwhNO+yojEIKN/k0lrQattFshyjD8YxnHNhOg3iYQtmv cgW08RuxTW8A15W91GzDta2YBvI105jM3MfcA6z5h3hNtT5TPgeXhP0eWRRnUGGf 3tp5GYGudnMc0IiGn8ivY6MEWWt9ztgDtgu6UuTX/8TIhPAygDiM5wtzOMSinuOl /z9Y3ZPPC9Ocn1rMKjZLZVLJDjom1T1qklbVABEBAAEAB/9FGri1d3dUSypb+I17 wT43+CM0L+SDI3mX1YXacUQLe78sHQjOzHWDAY0agorECA1l3PYxwbAPawuvtHWU qM9uSgnSsIVyVl/z6CoG3m6Tx5zkXvtQBJwofjjdvK8hrmfSuPWEXPaxEVCfoEdx crQxg7aq8ZJyDfHrjjEzQdjbtL2bvj854uf0L4DKXl/kNcPPecMfzm+pExUgeK1X QxkD7A3Efxa6KoKJi6adDlFkoqktTfH3+uw8Q8ymhcP94NvumxP4pyrQE+r49k1F koXHhDbMemOtUd4GEoR0EYzVbVmBW8nZES5NKPBOSEpuK+wkhIiVMZqwfZisC14p Gau9BADWbIGmg8FRkIDYp43y4lzMoRnoN5T8L/PxmHXHz2rBwq6ZnySTTN2NCWn+ quXcTSB58gjodyqKIkvofR2MX+beqkQ+4u1IdEnckvDaWuP/UQv3BsqwukM7HxKA YOMZNYYbctRjQwvd4Bs/jGKGZYOJuBcGrQOH4rwiyXPCSma74wQA5JkikLa2Upef 1zgfl3O3NgXispf+oKLNrY1GSfNBjgQQZ/OiA0MWp22K6XQpIRwo90cgwYM4M9Fr ELsh3vA0jQtlFsBh1r6zXbo/IEi5Vd4XZq7EGsevD4wZ0s/r0o/X+BGn+/eCS+XB py9bcWcjUxeYkcbbytr5k+kg/pFZj+cD/2W5IFNxd2w8UgWmuqhU+lPi1IobGFi/ AsZbtLRUNCiZd3vl/ugJQBbvgry+rA2VYmgjkDC70iZUcdlsGrgLv9a/0tplvQnc p6KsG+3ijYwYCB7MEJ1SK0ZosagLT0KTi7mRA8VFSQ6NU8++g/WET74nPujbo8jS PyPE1YQorcA9SCPCwHwEGAEKADACmwQFglvtdgwFiQAJOoAWoQRGPg+CpDgv5Hqr PjF/UGNVzQzfQAmQf1BjVc0M30AAAHFaB/9bnLp/xka/YUabeLW80hS9dbRhKm3A 4OzBoBbics4SIfzSxnSqXTXtkn8bodtQyGEr8AZtYpjoj5j+fn36z7op153cBsIZ J4isoLAHWZRy+Rfv7v7t+F9uqiZfETSBVvENTZK67R3PUE+QCh4pbg6YhbElXitH AK5aGAOtv3EYkxT2oJZbif7NsZXD65YRswUdlRJsnraWU6TzvaInKpF829lK30uH S6hYSqTGjfYfDhTOIOk7ZyJdbCwbkIF8Oqk9PBXIQEIXPiowGsIvGXOGo0dTMFov udmewTH2QF94fge7jMzrOVwtmbYYfZaqOR8l/2Oxut84IAvKuO4C2tCMx8LYBFvk O4wBCADDONmwmUDdvma8FcSWnSRiht+AdfP5l4VbiyVTxOdmOHGP350neIKmRwrp /ozjWXRcBM3Pjtu8uucB57fWal6hTZsUSJjGLy8wcsB43NU/eTVoUu4TmB+cHhdl 9wd43/vkG7pdGQ8M1WQFR1x08SCTpjeJjAeBqrHXMFs0oDgjrHGW56We3W3uE2hH 66BMLnHpX7nbgLD4/f2m3ZYNbd1M+JIf3dRDZNNHQ4ISAf69xbSM79F3YXx2qcEj RNm6TjPavS4qwT+k0F6jsfb8GYw0/rfPlJ48bTE+yD60rU3ueMzmxWN8dnDdEukW dr83l2xP9tsiqFw3IX5M32SqAThjABEBAAEACACx+AS0MrHotGfxtSJ3b6A41d2w vVcCRXiEYNBRkqKlFHWqhF8/GWR5czBm6vDDXcxvCs4ZFpIJdpWbrgNwy52jDR2A G0JzAZbSYvpF9IPPDo96oId7g2Xlq6UHBnFgZ0i5xfFaZ7rJp0s8aLbHwP5Cu/fL Q8WgFt1zYBj1W85x7WuKJ0uF+GndA7TVt+ef1zeZqrS5uYoWUch0zP9lFtn1xAgb d1vXbcsAsxpBQrMWc0SSNrmu66UAruB5CULDAm/gRrYvuxO3b+Gptv9eM5jagJpK 8qjNVaEnsqRLx8ATicw3OjVkaQJLLte8OoegZwaALp4LKZiGZQeG6dCIu3MhBADH c8t1i6Vw0MA6gT3KoD3DbrWFRRZfiZvDUQyRsw6wNLblXXhXHrAV/wXCt4KQ6F5Z gEOPISSISlncVFUMRb+3lG3jjYuYxrSUgjsr8Trn0OHAXktBVaS5U4bsJn0sKWHk 2nujhG6dBKKZKy3grrzaZRxQxbaV/+ySnzaeNOjnMwQA+pIGXcWr2A2FFMWieD2w 4dNJpN71B2oYwqSrS+A1eKJuV0hRez7/5O/GcZ63re4xy2sY8yQYVefMIy3szuyB r9/WsiXPcm+GsmPTTIsf8p6hOF5xH5OMKByZxnWk379L/0NrdwU9N+abtJrVvmpZ 6r30yv9B02D7XdG03Gb2qhEEAJyCVAnPmPCEBpqD8Zilb27r/vpoCWFJxLT670qJ Fy1xVq7l11xLQJBsYAsOmBmLs5oF97UzHjM0Ac7Ahl+wGN2uxSxU9hirF2xhxvXz 2qZfvE6HbDmvukgdushSjvvXqmvokD3xWypoK8iAM/iwj7mrbDe47VB0Yy5mkRHX vjChS/LCwHwEGAEKADACmwQFglvkO4wFiQAJOoAWoQRGPg+CpDgv5HqrPjF/UGNV zQzfQAmQf1BjVc0M30AAALTbB/9k/I2cqRc9MHvVNmtCDz33zJdSq6VTWYG9hmdt a9Ix8E6Ui39jL4LcTe/54lbM7kscsyOp5JNWmqdWF/MYp1k5vBXHt5UEIDZmVmKZ NgKVFfO7VGvyeSXpYVHNidJfrLeKknpkFzaWRQglCR/XV1OD3pJk2MVqDaKInFu/ Mq+9nkX1bnorC4Elrra3KcYebekLNBpWA+LdCFk1LnHFVFFx/1dF8/0Sy6P1uQ+8 e7224B6aemK5txSHCNNxIS+Wt3aQ4hpjFBdqlYwDxaakaOM5b22hSmYle8UC5PuD mvx0Jq0uzE8rexsUpIuHmyL3uCyFa9Agm4Er5x6z03pnLIKFx8LYBFvbAQwBCADC Pmt3W682/zgoK2BeJrbjq4g4bkZW1f9jdwSBzAWL+Q0F4AMAG0A8M4SKvSEax1Aa J3nMCGpnXkr5AGlqEVrq+nInvc/1mrML3KafmA60PliwOralJOPhAo0n0p2G19eQ wKC9cQ+0FqUGiRS2hx+5TL2TJG3RyUILTGZCOHz94QD5nWMOd2QMBE7LpCXNhq7j QwefH2yQ4bLu1vc/u23Cg0qkVK3xnv7OLU+2Mz7VtA0H7kp5bkPQ4fjflbFdWBtc 2iGiMe9ww977KlL2awh9g3wsUsuHKb8XIqDRnebJ0EUtKqne2J5c1N4erAfi46RJ rwB5xu63JFIydurgVIzRABEBAAEAB/9NHT7I2etLqDMBL4dImZIN/LFNxenC255S PJPbe8XP9gXHiVFnn2n2/HToJ4GXAf25BGcEK2sisnqNNC6lX1J66waa5G5FES93 zBeSUGm2APJwtD6CfYzCbaLgZmsHd3UrEG5ABzy2wC6v04gXlrlvdRnl6pZ2rLG9 jo0iSQ7GMvZD0m6iPq4lJjM+E9Ztp/IZdpWet/rZbNL1AJKLz/PGuik7I6IOf8qM w2Ins3fwxijEVW4k0+2pzqz2g9xUvDxVHBtE45sBhUB87opfYK9K9YOTWAnsOCa7 dbj9htLUGYWnH93mIVHRb6CPwriWQvQrLPuyHQtFxRhp3vFQXt9JBADThThQzDyI PCxxUA/cB8mDNAftAbOfPBtc9uqU7g1dE+2cseQfMtvVO2vjTLeKaMGjA6qf0nSc shYFaSCujDDDrAnme99cQzBfVQXuoZeXtHqocKI58KDSBp8M+XfAtrRj74w+eOe3 Be7RSZ1CR+Lo7qlcEf5UqyVi4QiFn9wuhwQA6xcmfRZukgS8cuuAQBNoM1NdMkuU 1h0bYiGvqPs/CsWM4i5fWL7cu/80mAJj6rJubwelqWjygS6xl/wHIvdTIwRsfunN T9ie6YS8wEpnz63t0ppgpB9wUX0YsrpCSScUTlU869dMRbYC3BgcK27ZMXIxM8YE fXIa22nW0ZHwJ+cD/3SEE789BdmrxAR2Slh2iP1tWZnBeVBuZ9JGwYdPaxE6u4LP 97E/XiyD7hzHUG3UMcs3603xorGFJte9LqDXUWqiS0GoAeMYF8i0C8W4/knl+v3+ etFP0/yEn2/MRQ4ZDQ4szn76+4Z9g+z57IimbtPuNBt5FhJLJDp4JMinnp4bQnzC wHwEGAEKADACmwQFglvbAQwFiQAJOoAWoQRGPg+CpDgv5HqrPjF/UGNVzQzfQAmQ f1BjVc0M30AAAMuBB/4yuzLBgNzm+P4lrqc0gUb7/GMVDs+JW/F4woXyY18Xbi0/ gQoTdwbUb+59hrVRok1N3/odipHBZEORF6wA0qJaYyBGeR4WaPIdKsMU6BkKQ7qE hRfxmr66yfm0Qbg59Pfc7G30W6di91wFcPNioJ/ckCbRr2i73PlE0zNa4ytpCT43 QVPNoHv97gDTLEpo4i4yV+UPrHi86KHdAEMEC+dz8cTA1S10Tr/E8oZ5gHAHt1Ao zAGlCzhvcim4y86vD9nzXo5efGRIDPZVGZQnOHlu8CG6cCZlav908uHNFDbgb62z 4bnyyplr0Jme+0CY2nXW4iJJ2fOqn2fH2KZRnnJdx8LYBFvRxowBCADapph8w4dB AVspj0jf3XAzJFm2hmbtbovDJ160cK4oq+VgzmfBWnvVfQgzzCCF12skrGc/EIcK /dTQ8euy+9be09K0n2Q3YwvsqdOUlqJFjSiszJRVD0RxcK5G7bfB+vFKJrV/NfKA 0N0N/PeP8mB8737jJPUpkZoKnh/0vpJKM/le8S9iziI+dV6ENxKqnAjP6MJvIGQI cRpUMjftuC1z5UxXfCN6nwYL8R98VBkFvlEJjMAy7ANthOdo11M7DaPMeUCiWZqc ig1JbI1WaS2jCAWGfarGOAe6ZXOp8qBkfJ7LIp9DD2xungEssR6FvrH17h7YJYL4 Z/5uBAJo+DiBABEBAAEAB/0SeRrPB7IIsvWhyEJO76WIHJ0bx7UwgDN8tHnS4uZD 92n0yNuJQXzO57ZmsetZg37A5XP/pwTRz+pIEPhp8c8QmnLl4cWUzGB4iUyf9C4X lDqIhuxNV3Hf57ZBtdndoLb8HdMdwVAQxBS5FtNPrRmr1Iwy4q936J5MIVnv0p4H eL6qI+JE21BymU0utWxjgYt6gtLbpi1dSAG52t7VaxLnLKdUW2pDUpK/RqCiktAb 7kxhl6F0oozU1vKFPu3WmS54nycNTOyoFvxdEW9LKFNy3rkGOFnmLYIbmpZzMDUJ 0STex0ze24jmchzm1lBZwBDsVZWHPW9OJLGjwNRu9NXFBAD1wKjYMCOcIdCjkSar 1IaGexyjVEN54kGbw0nfb3jCFzJQSJXPBsBxmsSSHqK8WXtHt1kh1sWGKlFiSZIf D4tqa6EMNEKBYsHSBJ+zwq49A5OI9H6dENXPeaqHuib63tB2/0A5fpxdKDMhmIqC lYyr0rmoiYAEiqZ59cZ8DI5JBwQA48ShvTP63X+8RhGBKbe2CSXyqfil2UxzUej0 47zmT5BROMN0+wwggcU1/PdyovYIfTxg4T+3RiIPt8lA5mzt8na7F5rkeUzY5rQY hNklTByaitwAELB2x+XkwuQRW4vEWbuxxm+u2hs74hPi9aFM2uPLSQFvyl17QEyq ccvrODcD/0Wa3hgzl1EqL6K6tRgFeY+EjYA82Wv2CICiUfHn7MFCbzBxWjwwDEbO 362EbNYRIr5MXKWjkhZZNFW04IK78idMnjbHIOJMJQkxH3XNo1z8xsKS0UJWUUvP cTxW7VS6PJ4bd/0Hr6ydE7QILScrPU7oJt9NElrOtITRTrrtFOjVTV/CwHwEGAEK ADACmwQFglvRxowFiQAJOoAWoQRGPg+CpDgv5HqrPjF/UGNVzQzfQAmQf1BjVc0M 30AAAMFvB/9HwogKUfeNqU2hfNX1KOdwKrfXP7ZDawbT4DvickaIs2jNX+LbTO66 avL29GlmGRvi7od5qIq8Lj5hh0E/1aDegx+8Diq3CqLv1RlQNZ3bPg/1hk4cRh7x UI/PVhwQiQs4wOo32kJ7DXw5hURjaXMuWNS2v1YILHYl4zbin0wokQzsf5u791Po hmbB4ueKkxv+CBFW/jsx4afpwR5l7SnUulN9x+6qZQU9U06fe9ntfapbZ8x6SipP m76PrYfvQVlD7XSSotJsDkNiKGk5xnxZzzdjpqwM9TdOTOgoY2I6mEcBz7P1c9E1 8krgFwfr+4e3ZFBjQqFVi+mub1gcrUlsx8LYBFvIjAwBCADQGjQE9zIcpGcNtENQ 9LKaqSRvoxNGzyjDVakyu8lSIzukxZLRnc8t/b8fRaceHymsSKrE60xWZikz6FB7 4F/VBh/tmjJqKBGVhlxVwtEndu8unxfUyZAjQNm1WMQOiqU3OcNLQJxeCRZcb2TI ynL4i3ssd/qqGqMvCoVKgx4MF8ImEW0An1WFZRsMqlqNI8S3TDMachidXKcn5tIa mm0O8IQFopo4BiC0ufu1aRQEFh8beEZ3frThhVR0d7o/ZC84mNfPZejABqG/Ss+3 rvNG3anIs7HDHq9A4FYAHfsJCFR/9W2E/Dyq006QaRQRs6q9IDWAHMcxPYXLkWAk YiODABEBAAEACAC38PFaEkWxG4AblOKLhD76hdc29iXryOB3CBhuR55Dg/EMS7bi oWKnfEqbEa4N6e2j4vdO58yp8dMplobNeB485RUDp/A39QLV79Vd2L2W7IGt6+Hc q+1Du+azbKqfT92JWv1MXufYFqA1RCEJeij1mBRf0g02niTvR5VZr1u3ww/7/alC Ix7DxxPH7P2zPxsyb+nI7yZpLGemrXEcq2NBFtW3UL7pvFa/erWtFJeopgcsjjUw sWvzS/K3P1fAKbFcPb336kcaJBzq7SBC0ts+z6Gipf20ThfuxgZzjYocIGFaaHWL 2VCWefDTjjiSokuIYSDoymiOIkrtCb1gtvNBBADeKIBs3kyFdT+E7ULe+9SjroOE Nj3tYxMeHwYBkhuo1WKAq6zGp+RQbYWWyzmoPh3HDhqI2s2al9QJViwpm/Tqqt3b RDwcieC3AtDSvK7znwB5jXTSY94I3Cyrc2fdmlSGUKFtfT+LBq/8/HLLEsytoEtO wiM7VOp9GjJmMtCMjwQA782Q1f4jAoS4IbShi3nZFpAJFHXb7XZggwvTT2kCkUJ0 bAITkTExDJNZ6uiDeO/yH1UllybKpK3H5MDzbmrupHGFjnX8KOQKl59oA6CV37Ao 2DB21jSHIKmCm1ch0HV6uC6l/J3/Y0MCOu1OcLrnR5eYputR0eUdbqhMuQ7Jm80E AIt2f4GdgKqqcT28KOH6v/rAeLIpwQBlDs98B9HWCK03D8bM8C5bz4ELybTkdPHe kwlKVbm+YjM91ttNjxV2uDNZw0+ajc5MIDLB5VO4JEN0zagjyunSiHrI8vV62xux WfqMv89DRW0JnaPV0rKpYdgQsLM9pFkHasJp8Hp9AESQTrjCwHwEGAEKADACmwQF glvIjAwFiQAJOoAWoQRGPg+CpDgv5HqrPjF/UGNVzQzfQAmQf1BjVc0M30AAACYR CACG41irwmBy4Vw156q5+IYserj6O8KO8sypKw7F6Ps9Sdz5DKu16cCewMSSLajb vDneylbC27IxQtH1biZl3l5gAgN/Ucd9lsgXcus/2ieD2kRYULxvxK6Sb5tP71zR dylUle7mqUoJsOvdc00Jp7LHQ+FMx6pVfMNTT7vTK4ak0k0VJidlE37erp9Lts7a duXUEr766CAKBw/p4KnWBeiaZtEQDpR6hZkSxRlYwhh2ukBSaz/OU0K1jxtM23ZD HgOQ2R/lXevQ3eSHSbxDDPQeO/n/nsoPvX7F8JBR08OuMmIYZDxhtjB9keDEA44A CiRmArBXqeIZ6cdmKxLFpLxZx8LYBFu/UYwBCADhzrtq2CukWTSc9wVBtEetq+WR mtUU/VRRDSTiOGMaN5vD1R44EI1NYNvVJma1W5e7opOp4l/xQZSABI7haedrqrVH SAbhp30/SCxWugfCbTceUXdOPloi/ULRzBKlTsgsSyExLgHTVXffxEXhoe7MUZrk 133MRrKygWQPpAqvTrIvXV3jP9DvS+9uoRiSRx+JzJSGsNHo78wAxrkmqiX7OWlE UmSmjYZeinQReAyYJCQQtnyYcChoG0SH5zpn2hAWCZAGesL3p1DPMV1j/wDyTnHx wzN0Elt6YN1lxyfIpMQGyN5Y3B7sWzYWN8y5wOfA2Zl1y2V8dOgvVYKyB1yLABEB AAEAB/0ZKZda7aHXW5VwVFqCmttN8BTR5+YMw7oMmiFdEBmDB2VVKj+G0dldIQj+ NJxHY2CCKTi5Op+kW8F3jop5dfqy1cADpHQX2zkqvxkpxOsucEsvFrbrwij7Myuq A0qknyXRQcH+H/lWGnUUO2PqI8hffkx7acP76njpSV/kGkySgkXlGIDTFkVgBv54 arRfhUtA2/Kz6twhO6BOdmH8cMBuPPQWHOisbxuT3yOAkACOM3G1i+rcd8FFHbO8 cRrZemRn/vvP7XQ8Mk2Tkmg8rOh7d2JTToQl87fxEeeBHcmkR+F23eLBXKdaU2qF xB8gyIruKlQHdULEr+XcJVoKV/L5BADxWkWurZ+4Ex5rU7yVv94tjCDA0tQqCaVd pQDR2qXCORELey3qMYABs3g6TJOx42w5p46k3szqVLP8VelmeM9F2J3e8dgXjUCx GIh6QaPJXkzPsx8iygvv4FeN/I7dEu4SRNkgjaF2pt5VpGz8SxSmHC3nft0TqpSo Dt7uYoM6VwQA74LyY8CcilrQtd4RUUhX87QD9+IUHBLCOh/yVhvuoCgvyVROsOJb 7w7hT+YSD4r4EVsuoP4Br/iWikRmhjLLENNDYuDH9iDNsNv+LjeYEMKZEyEzdJZC XdU2i341AVbWxx1GcYHqrIOBrzjgezga7qU49NH5SJo4xydTjcQkNu0D/0uSCmAD wZTYlC71k6AI+NILOtgDNLi/BPQxcz5GHgiqGOxff+xfo5zK11aQ8iKfqKytodnr RNevRWV+/QXALwgh19zaNcrTo/DWEbYGCXm02uqt+jpE4BB/m+zdvLJ+cyDzpgtl Jc8cu5oDpjt+XWNrKnSwuyyBd8gn6OBhXIHpRXTCwHwEGAEKADACmwQFglu/UYwF iQAJOoAWoQRGPg+CpDgv5HqrPjF/UGNVzQzfQAmQf1BjVc0M30AAANwoCACN8IO7 YL34IkxkXnoeQkDqHmNhdKKrxOOYzz/KHwIwB2iL2bkYwmnWLHAdp11YqP08KuPd JxNZjkfmSoIcXvBnZ+DfKPMZgRi41tCivYGae+a60S7752hnC1CbaizEgvMMQz3E el7NYOqdT+3rEOj/nnWp+5TEKL9kpWlR0RZNPr2JUUJ4At1oNoPkkye4Z6IUUIzm wLcGhFaXzhpzyeNKBIxR+5mVTm6zQ9rbbEe8xVYLLYcCdeD9ZE6A/XF/TymBhoHu KpQMgwkXATBI8/G+hZblG7gUfDOtq8ra5Gn/BRwpxto3nqxH49wAARdn/b5fKaMT pGTkdkZQvelrFADcx8LYBFu2FwwBCACjKg3g0lBZYo1cgIk8I0QF7z/2JrKpyC93 PkxL5Cfwj8dnoGbkc4nNpD9M/a96vgHZhBfFX9BtbC1bq5J15EjcMsNS4HXXqd80 KCb23UGetqlroB1738v8VuX6w81FGMsY+PUsmICqn09UGBWmkX7zhO87O8Gf2c0v 23dKr29Hp3R3Pf02YFfPatytVXeqjYR0CiTGQ+XGCy8fJa0pJhY/OjdbnFDEjC4F r1g/StvxhaJZCdZY0T2+QjNh8QQGWU4OhgWvUb1ayM/ktnJacXWu7xwLJna8XQ6c 54CziS1KG6KiVVvA4iEx7+K0p0FGYLEnFDpyBg1pSsRxB5JCMepHABEBAAEAB/44 FjyvC5yTHU16frSt93FqmNNjyemgcy/dzYxsigEwsDvbY0f7P7we9FbOIGYxyx7X ppCFSGpdMtJksJWn8NEa4FhEJORdPgd75lF+ipAD/sY+OMvpyQRVuYlUqfnlxqAJ 9YpRyhpwjFK4lY7bsyfwjcGumtv0FupxiP94rNWTy2+KLKeVi+DP/PfR2NYUQKcY hRupM4q94aycbSROzYANH6AhBkDIl4ulVdY3YwezWkKxRzyfRlcCoQrk8MumvtpM YjwzPLIIBSL2UlsknK3HH/nPHm5tTaKmahwltLX7Rz4sRRPc9yHq9Hr7KJchicxM 3fSKvFxkwlvZx5MVrYABBADB/FdBiMMa9YBKVUsw5pZlnrSQJDDd9F/jziyG0il+ n6Q8EX4e4dhttu4xzMM55517ck3uaLT+mW+z2w8ItVCg7dgv8QlLFqYa+FX7A5G1 dkBcJbXhU5H9WJ3qgYHfIXgeO0UsS1RvfmenmGXHE/mHwJHCiHTuXDJPUYjwpCxH TwQA11NO5RuTKlpOHooIXkDMQ6Ngn75D5RNYoSf0DO5rsjrcvXbFz4j4nLTrYe74 XuAQ/gr8HJdZcq2jzIy/+Zgv7+i361FF102ww4U5uOWDLaH7GkUqEOmQtE/HYDKQ cfof9Itj0fs/KVBeWGMf0KCTASSTLf7UUCEcFkIATaYD74kEAIWQkSfCw8C1JTlU nodp02a9zFeGknaWs3R8DJmDZ8PUtSzogWavjBnkXXvYDUfiEjRU7Gw/8FPGjrJD PAyhqnLhWlIcCi9G49CGDx+SMUzkG6gWmIYVMfdzdcVHJqJtmnGoai477Yae41+p v7lD1PwYhP7K319s/tBE4ziY3wBpSEDCwHwEGAEKADACmwQFglu2FwwFiQAJOoAW oQRGPg+CpDgv5HqrPjF/UGNVzQzfQAmQf1BjVc0M30AAAEuRCACQ80GyRvcySmkU R/LVhKb34sVsU9iTzviyZu61lopRYsnPU867IWBRAJ2EYjU9zTJnO+QEyJWKG9IC WFpy0PF+2L9w4Otpxa17qn4WCBvo2F2vDHD98QdHZd4CtN4QLaR1WcUCEbbkj5oT /d6N2g9Q9S0k/mKysoGqVaeXUYSw2a+q9iWQSEl5C09kV95l4oY4swZa6B3quUMv 1e/TdT093YwiXbNVctPKLPoeLGn2gS7yshbJ4CZHo5eKzEr+Gwuu8/BvVsUqpNuz ObaxkTajVUMlx+Qg9hZJ2/cChiou3KBqX534bf6K5M7bH00t+DrkxgIW1bJsCD92 7Kb9h5/+x8LYBFus3IwBCADZa1XnyHXe0Bd4LXDXIHZlCoKZXVK/1BdflXYbuKum guuJF0hnEE9zG4WnWK9GpQtpX319HMSXknGn+3PyzwPpESnaEREauHCMkyFU6GQQ CdBz7f/K4NteMmTIqfJ43SsESIgbOqFh/lz3epTyPwSWEPSPycsg/pcRFQIpoaei vr2oBz8iMV522Ki5nGc1WUJicDgG7Hmz7quZe9lEdi0TvtW/nwZe+JsIQkkmBbkh wpun5APh3686bBsd54x/gSWKIhEJedLcHkcNChwlSk3qAvGCp81Syc2vsbWCV0PA mDcCoWllXWP7uUQhkwHwkViYrllf7N+hji0jOApv7CfxABEBAAEAB/9wiwMYy8dX 4teDOeJjOUaP3vTjdk9TRPIkZDWS+/C+bJ6HhCnST7sQlnqEuX9hTiWuEePfU3jg DMoQbjUCxu37Qwsq+hKkhjycR7zFOtYxByOEHvp9hg+HnwZUaMQ+lB2kRfdOQPcw xn5RDH71NnGlTHmLxKUFG0QPb5SDAE7KCoUsGn99toc8BD5RNDThGs7orGuuCo9a g91shN37rJ1feZGsLVCeDUXhatx/yGMydk1VHISfFEfd6HT62HsiBSPfIQkA9sKe UFt6wTWk8uCzonE7+uY6wemYNFNCTQRa0jTBvYyyyPdPyAcfKsmEIPvALvFvLVZq u8EOShDw3YPRBAD+P0sRU0pPbwnHRVUChBxecMTLGszZ2dUOP8lw5c5NVssCT0vE wcnYGXuItSGF/NbVfdlsCOTA1uIa+tZb8V9luUFZBlVplBv9PMWHrHqjwfVyWSZ1 YA5NO1E7yT1LhYSMnDCG8ik+hq6lxMzGGKegFuqaN4h4XcgSSN1+i6WJcwQA2usL 9hW7V/BEhlB9j1WB9DAByE3oCwf8whUwiE0wbG4lpyTevFn58CE1yc8Afa4SJBMW M4ujnRVynY7BsoNxub8m/R8BP62W779zF5wnbjhUNsKK9LeZROnff56YyZiSLLWw johpSVd3DUTDRT05vg1ZuW/RyD++KvTVKgojwAsEALXxUWzVHuDuriomEocQXhk0 MAFuD2tkiltDK+V0f+J8GhZR1zRPn36fM6GSSJ6kN0Kk7QboK4dKlR5loRN7Oij+ gpRMgcA4k6d+YHjfNsuUbvf8qF7s9VxUoyTFsJhHce0LnOTCUfJ2rTfD+9ev0YSj ffTJN9RESppnq7zdQgGROTfCwHwEGAEKADACmwQFglus3IwFiQAJOoAWoQRGPg+C pDgv5HqrPjF/UGNVzQzfQAmQf1BjVc0M30AAANJSB/9zTc1RMVZ7R8rsLUDPAKvx NPoSyQoN0YQhkFTWP1FikbjWn6BG1DNZZ/g7aswFMJZ/iHa2l7Xm3vnXEEXpw6Dd SzToPkoWFYpZdbiJfehnHGiy0OTNOoAV221neMOl/aVMaj6tZqsheB1sQvf4Kxfm 1GX1B/oK8DfULaznr+3DFy7Se/bpSalJ9WFCx/G/aXtU9xVd04dMtfsFcAyu59Yc 82HrbBrvlF6lmJSd2SVU1svZ5ZJYT9sJo+VupKTGkBXF5a9u6i5tYVaehjPBGVNt 1v8aVW/nZqXhsGmyWJ2I50ua2JRE0dN0ymp/YH0Th1c91Y5i1xBgmd6TOO1VIOVx x8LYBFujogwBCADKMt7QWJBKqJ8OHvgzTb3Xd7c2mwYYaTbIAhwYgc+Wsj/SZUtQ LbpRbTz3/3iEq3GKV+Sgluh6hvYp/3r7Du0nIPeiasXCcpCEzIpqWUH2I9ymI3Nb 3YgpOTcZyX3pXYnTOXJpw1/mcevRURTr/GS2qzvU/QCvKeupn9UuwYzag8I3sEBN p/hN2Lwhc3NuvaeuTI3nepbgnB0CeYl1t+KbSV38i9bECxTUTX4FRV0HDRXcKiRx DPmDimtWll726ineP/EJriTJArluw885JjoaqKF+l+Na3Xa8hwcvUfJ5r5+xamK/ qTtiIm/LWM+WprDkQU9G3g8b0Y0vZ4hiohbnABEBAAEACACk6dCpBVLKUbVIgyHU VjfcIRwhQVc7WbUAdBgONPHm6wL6yvseLe2ks//iFU9qhZpZR06FXA/Iv4LOxjy7 v3TVUSIoKgWZjm8ooNrT2nDeHGm+Z3OTHaAMX0n9kkVLrKrYyCX09RCml4vKcOMr hfNuuUJXePKx6vL2qhHpzJrhLNpsMUENJpx1H7/Mciu2+XLokSigAMHzzE9aksrD 0VayxOifu+Vv+DCCfMQxPkDxLbMzCpmZNHHtY2Qo2UdOsZw7RHT/8eqaWEnCq2ol jQOyC6s60xZorAoTAgCQxq+YEtfpTcTa5Z4YseQOCs9QMqxvxSLAyheicTKglVld c1kBBADfF0k7w1w+eRuTLuAtl9NF5tXq9vBEg/U9LJAT1gIFAdrluaPE1viR2Lrt Yr9AX4fzF1j7vsmPOXvBZ3uD3j2jpBAgBYMk9dJwDPlWbwyeLdDKGe02IlPWaZnE N9V2DPMMbJoCJvkKTt+xHINnu8oNZOqYzvocOAy+Xw8IiNWuBwQA6AaePhPUBd4u uG9z6VK0ds3mZRgTE60j6ym3FpwlioNnBTzGD2D3dYpid5Yn2ODYbiZH+E5Fbk21 xCPgZ+wg7f+jd+AF787iLniLU0FqiwOCZLSKt1szYsG2qkTnoadJ/r2wX8nkRp2/ 26sadiatX9dNRncQByLyIQxFJCGKGCED/ic8JakxwzsJgVi03O+HT0trZUsi+tYn sFiBo/DahNsh8q1mD8OBjtmy1sxX7lAR5wzPtT4xySVLMD0ArWQmCbCXxH+DR+kM YteYTfK1FjvEme126JKJPreklZjixABQjJ/ieu+t7cSxbUwe/5RMduok0a3eTv+i /inciyTd6qy9RMTCwHwEGAEKADACmwQFglujogwFiQAJOoAWoQRGPg+CpDgv5Hqr PjF/UGNVzQzfQAmQf1BjVc0M30AAAB5SCACkE7EDRyAzj8E1nsakN/5IpIYkPUFn xK5QTdCN7RjF3TXJ9VGw66xYiv9JZoEnHZjWtn7rzLQtkGUZyx5ysqhlhUZFObIj HSG5sy5LW7kKNOICKYXaMlnxmDuQYkV8z/iNtez7p2rn587eJHs/1qJUocSxYw+U 3rJewUAM8FQ5YcnB+YdnmQPXS3XB1beUrgei354RLIkMxLfoPajZGiEr3QK5d7AT TMR9S+gQAaUomA2owoEQVaDGtF0zMcZl3rGezhVaE1Q7tX5YKgFm4bu5ZCbWzIZs dbXDfNmrgCphO6oz+9IrC5toJ/kMhlv53rHBCvc0paGYIeCNbZXvvFdLx8LYBFua Z4wBCADWHdnkH8g8WP2IPukfw8kjpXOVvb8z3hmDEm7vd3Pk9nZmTLwJhPYzekV7 Nkm8aG5Tg7y1/pwNOWQivEPan8xETZ1eP1l7RUfpiOC8gRsGTvZE3aYg5y4MK14A qf4dh3boM2YfEESwdYHfZOlSs86yAcz3A7KCcd/QANcwO0YeeRXHm4WJZ1BRdivn 51WjqZLYZE4kWqQacvHBEDmGbJ0bqkgFMbGdCSoAmIfxDfYJ+H01nqOkn/RH4BJW l4xs66ASYNaHkCdBFvUSiJBo8RfRu8UGU+q4UCUYZy16LbceOLB0lqfvRjr+n0te OQF/ybjVX4QfeKN1hEEY5D8RjcJlABEBAAEAB/92zHU6l0+3/AAfehhKoYyWFJR+ /pp5or8w9CdNAk/xN7YVqHmkJnubsDmg1UwwFxkviSZJLA1VYQRoKWDUilkBhLbN tgRl2ti9gE6BCHkAFQuaxggItXvdEKrFX4w4whJ9XxdIIPSbm3cFha3XTyBdr5YT b5+5tIjjlbPPdPPAr5WjtQiknPvh/X7VxK/9/JF5LV5BItnq7TckHaqy6MzN94CX gyzDedqd/C3v8imsFYQ7xOLRYvphCrCWVaqxXLkFzl1Kl2A00/ratJb23Zm4vgXK 7M3xQYwyilpcoQgYo55ztV6QO3vLzK50cGOxzUQhKvtnoQkSaJWcgnk2EQ8BBAD7 up/vZM7YGFVN8K3urOF9jin81Njc2C78jHOibBoQ6pTohbVbFu3+pljgQ+nwVfKM tZ0tX6+uOo7RBAkIWSDaip6RVP7L4yEQMOCl/Kniz/B+Yjnl0joUZ4+Ac67NwZqw pKOIgUOgk0GfKrDvfPCztqRJRxK3BIkJ3C7recvMhQQA2b/bv2pwKtfdHotwugj0 qo9/o/0Omv2rtBZYwhKm67ky5W0Dd8cZFbKZWWMaohhW32lB8nj/AO4X1kKdmmbm bFqbSZHjlQo1JYG6fYqPxkZFHKVZwYz5NCjHBrZL9nWHH+gkOFO8QZNdmbg4smb7 gQMyJp8H0Vqm32T6c0jVdGED/jmi0tQa1i4ARX2zEoIrhm45/PAXEBDzBL/hQAEA jy1HYsBS4H4Ps53dvfjxhed9Wn1rac3t7z84pJ+AD4abN3QWqKRkpHnmGbuXxtNZ fVYVrlqYm1YGmY+9Bek8IOKMCjAoil3EjOSv3Nu7Ex9KeC3ozwNP0Zp0ufYuZW2d VumjSivCwHwEGAEKADACmwQFgluaZ4wFiQAJOoAWoQRGPg+CpDgv5HqrPjF/UGNV zQzfQAmQf1BjVc0M30AAABGDCACo1EgEG7cU5z5banOoO2LwbXlsPQE0/JHghSH0 TuhaA4K+pOtuewvoRK/RUG+MrD2qsoekwcrv2ZsxHBFIL2LVjyIiJNLwrQsbp0Rq Suyxpr53vxqtwnvCmK1XdGAjy8TEaAGYHkQgMbckxeJflW927yBtdlW+n5bmSCL9 OoGwjT4OQYZ4h5W3dwRPHFOAwoJFFvl3eQcsxHHd/lqIBH5k5GcLMnJn5cSImKvq Hrp+3TgsM/a1GaQkjIK7tBi6/CGz68hB6CuP8DflmeWRP6lsRdXs8TCy6BTNEMo1 AyadpFQKsLfV5aR8grpqaGWxHdSIMuAo0KyCPvz+D41/Xc+9x8LYBFuRLQwBCADV mZ4MYFJIUSLxK69SBX+YUFu25S6GX3HCLJof2AgOTx7aw1XbAYHvOo32xPORQhma t6PhminlNtmkpxQN4suBJ0ncIL/uFYNVpdZ3/6VzxyTTKRyUX/loCGU5mGqb7Evn VSsPTa9iysJ4dhCDd2DGZaNtF9GNFSV7ZW4eDr4cxifp/qP1geDle/bX9UGlkfeB 93+mHPehCOmIkqibkDfUms86QoOYwhBQoPEAInnA01VFZW0VGUSUvnKlDLyvMUZq paH8WlvhaidrQZaaW+qhXCKZgnPXkbArfsvyBZ8z7bN5ruczcYixR6/BOBaDKrcj wzkXI7OA8XKeS4D78g6XABEBAAEAB/4mk4PTwGGb5etlmvoi2hNdMsYdle59Hiex ZdTAA4gQ7YEpCTYsQ21wAyc4dgF3LnTpWFkkmW8PkoRkFDiu+VK/lJTmSJPhL8l0 YmsFaQo8iq6/5CDhOyQUAdAmQryeRGLA+MS6EtGmCfBtSQVaeOjla93EChUyYatP SdZFX2w+TcbYh3Ycm1cgII4OwqgDf1nW3IJEZPsjzn9zt6UL0QfFzL1pU/s8xo2C Izaem501Zl4CC8eHiCQRDKJEpeW25s/VUXQkWrZJXopYgp33wI3L9KpV2J2LAJUq Y3zmcuPK/+vGBFRPgGf774YgVojxmHVQlnpqNtAymTbA6w+t0V2hBADqlcXLkk4B eHgE7e1oZge26LL3lH64jk6j8dDWl1CfCuRMajuZJ3wqDD0GA7VXjRquavGqXch1 QFneHEmBZEIgbClyO7WHR09pibKpvdF3V85cS1/RrsJxc2Aw4SzF1xPagqSsybQy Jp5K8rbPpgItEWRRDaxRuZV6uzcVYB5YUwQA6RltkA8T9EmjLFO8x41q/cK+lK46 VK9fRfk7RZz9gfkcyiSyHj+/UXH37g+rxmMN+BUoXxCbZSYtqFGP4G8WG5wAnEpm 0/6eKk5JqLjtpn5clYrkWvcagpHdj7C7jusWx/dMGlHCXfl/vDDFxQBA37MfFFFr vJb3aGXaLq7AWC0EAIBTnnj+gEIS7FiaLP1HQ6+q/wLDrmYgtGgbVN3xqfP3uCaG y54kABNBR8c9UKmVuSCbEFsyMPZuPamB2zNRjKvGS7VFi9wB4T4ThLCVgH5C11Dl 16rtWUTei43OaXneU7grdWiEni6GOTDaTcapmmMoszpXG7vaU/SUr6ghQLNjOw/C wHwEGAEKADACmwQFgluRLQwFiQAJOoAWoQRGPg+CpDgv5HqrPjF/UGNVzQzfQAmQ f1BjVc0M30AAABn1B/wNZtBOSUzt/69URow+Q1XX/aJ8IxJpdcpFY+9t+cJm2l9E fw8MYgSPUqQVBXBqDCL6KJWbrPlDq2mS/6UWrEfZlbsJ/4jzMYx/x8DhWJ9YJ+NB v1hkgoUw3Ky7TzRjyI0w6fO8XiEVf1Nlw92x1MLuTcIAQbVJRC2DDkIt/AJwdeN5 SvPO7kk036KPM0tedlH+SFp1B1FmCDe54Y/oAndBe7xwbCAejqW9ACt9gXVdNSOm i+KwpDSLaTfrBgz1a9TSAdhGxaLMRwgnpyxN1U73vU/Q/6EVl7HjQLXRCHxuLVyE FlrvyuvURL6VUzg2OnmbnW8L7bexxzNE+UsCwavmx8LYBFuH8owBCAC7KS2Ay0ZG LlYLImVUQwylZVQB7dl1sU0w46TLpi4YetHosQ70TUZOAhjhiAyLUlYpd5XZG1/5 ab+IBg1Oh4N3bpM5GnLMNCDUdfH/TiNZYwbhZDvXp2M/gYLUZ0pOA2yBrUnGfmkB gaPvHSqdj/LPoUhh+mpL443RlIiZqHI4L4qhNTRybkqY4t0hJ0A5ksupbAQYYlt6 45/kTwfbU+nFXBfbf4SY/b01ztjKqqAjSGJVHxayiLVdYtdn4e6xhEelML3lrCKP 5xaT1c2Rl1v2IZ3PMRw4cid0tAfR0k/LZKCGiF1nPaaKvNx8VrxbIyQ7tUWDu/q4 p6ZyTyZyO0m5ABEBAAEAB/47ZGTRMzB4wumBHQgkxiY0MkSHXDAe3gXN1t+L1I9a K1eDTPSe6ArAZH/6yc6ZQ6aeABnoTHyXTEyAYNNGMVCxzMAhZdg6HuywH0GNMAGd swQS8FjhJXgkRff7aU+2E/YR7Ki1uZWe6cHY9lt4pp557wdmOC4VKP/vGwCCKmcc lsYVGoNQGln5cq3rBN08XUY3KEaQj1SOKTw2NGjkhXSu96BHVXVTILHlXtks+Sq9 EGSRf/gizo6tlwlR3XvDNztPQWoLXdzz5Xe96NLsU7TFSfIeA8VvyHm/xToPtj4d 8DCq5x6jOkzPCiyLmXz+MxbS9FbT0Ds6dlqNHuOGC+clBADHiNrUUSjrrITsXp4b X9dylfdrVlp2CRqerRTS78KF1ts4UZSAXgiwodKv3w+xKLOQ5ALT1Er4dysr0yOk +5LIIfOPb9FeZ7EFnXcVASD7JEvdVzmxzACyX40Dkd9+Wx01YYHw3ti+JZVFFoEy PxRm9EDjZMlFGTyvpA+RGBIhdwQA8B/qgH8Mc2vJWS9RMbxXxQAGawfAwijUXJ7U GTZhbh5iKHXkzfA2wbxWrUxtYTU1rsrA7e+AuFSmMrQJUZesqDbM8872xC9FizDV QDmfqbdl0RyLoOLcgZkr95Jhmv3YrNOpgWSfpet5zJPVjot7W5pbXF7YhtaU6iIk 8BLiOk8D/3BMaNeI2u9jHpTmvfWaafPc4BKE+tTAPuOrMeS02ZUJNj5Hy0zKjYev 6b0SY7A0mH41VZxOG5hvPV/Z1JuLRZKU6+AjB1y2ZkOU5p8okMLfNijfpPSLoRxp ZSjHjmjyIC5drkKMHWkCrBKys3/4p3RXFoEFqD+Z+TDW66uOxu4uQqrCwHwEGAEK ADACmwQFgluH8owFiQAJOoAWoQRGPg+CpDgv5HqrPjF/UGNVzQzfQAmQf1BjVc0M 30AAAMiDB/0bC3lq7pMTU3N3BXgB0mNaSMyTJotiLOfcnycPh703YZ1a7X0HNRiK rLJ59DGbl4UfHSzuCpHosur/8Znng+ur6E5Yn05Wy0BZW7CI4zOwDwrfw6FYZxeD 7KkWGzhcKB7htp88m05oD6dJDfB6tD6XgXtEzKdFFPAhjyPam3GHX/63A/uixKZT ZCyyCeT0jSPeIi8kt2ZcWWCRV+BV8qfULsMFnVjpi3fvH8P9zdwYpGNPbu4HPNyH b4xdKMIe1nERWHINsCn9NiS2XwMwd6TMMYUU+qKioIyhqlG0wlQwS7slGuFoDRUX SIZEz/9/QpWIyWANJBrwen6KbJ2SMasqx8LYBFt+uAwBCADH0TBvBwuAX/6dy3Ie 6FpAhCfrI8mTPGGNXsAKkPfF4LQcHG1nH040xkqT/nToCMNldavwJM5435GwL0zz tfW6Y9bgfXNglJsCRgvEPrV+c8two+V2PUUssCn7nErIS5/CacYY389eFQGW+wOw wMot5OnYI8CJWl7kRGkIyWlrW7jWRNHiPRdtIHIAktkjIQCi9yVt3B1g7K+YLVt0 E4PmO/Yb/6HCwqGdV4IAOW4ix6hRqb5p3CFgLV0NIZRQC12RBWRxYmJZ01lXhvc6 Qb0ulvm9ypO7zw9bA6Ov1JDWBZCY4rMzorUykuRxc7rsUpbrM/z65vh4sNMqgm4g gOYNABEBAAEAB/0YTlX56312rDaosyq6n8D6wBiq/btzcZDcRX5OyruZEnBWSw7z 0FcPDF/eCMHnx8kc+mdj2tZS1b8/E0HlE2lEtwhYXmo+gVUpvaOIx6gELcm9dfJf o6xad0NkG+rs2mYgnAVA2Vi8NQnwd7BHO+xcENBxPkgZbSjF05yDORzdth+H/1lg NITO45L6p+lZRiwueTYyVj4F3/yZ46lbkoowOaj6eHAr7TeWGh4IBYbdHxs2rZXS vhwfVJJoaE2aodJsRraKdyvVsb+ziwnBYydroM0tmuIn6sQOj71fuGeVpfbv84ip 3MXY9aMgKgETp9M2sibMKv1s/YEJl5yQUwnxBADdGRbOuSB4yCw3vfZMUa28k454 68VqACy4kWilBHFc6z+g+dVVAHWetc36lt2ZX25hvxSexvoSUIg75qmZEc5iFcp+ OpitKa8cztYOHr3VBZTyRerxOdwsrTC7MWtT4+LJ7Dg9Dpj843VZBRp2PJfSO+cc hZtP2Z8EvR33QWwxEwQA51wbmZbgFmDdC/JdvPLczf6JiRIRCZBXEhGtnat+g+Oc 2xon5yTG7PYgMi5JPeDUMO+N6g/wWmouADxYtB3CR3TFZ+kFXOpXsy4Lz6kqeuOC c+5NVqHnMogx1aEJDTmh7B7HNucUQxt+xzkPoHl+sLGybrSxNguD7tNcTYNAkF8D /0d7UKskS1pkskjbEpKt6TYWAUrAFnUimlNrfuKY46/szXgj9dzsY5aIHcnQG9q3 GffOnAlrEgO1BnSB2DzWnfVsVKawGLe0qS6JW0vF7kFjdOBxd0QFOEJf86As9SJX pdP+OEfwXmSnHrt6gC9Zb04LJspA0gc8Dk0UKpmqlB6GPWzCwHwEGAEKADACmwQF glt+uAwFiQAJOoAWoQRGPg+CpDgv5HqrPjF/UGNVzQzfQAmQf1BjVc0M30AAANAm CACdR5xxTQ3DqsktLT3UgJIvxsb8wBd/OpkT9hmsGuX4I/psg4t5a3Yd+pcQapr8 BrRZuU4qXegi+DYcCrAAaOE08pNGfwRq0O5kdusr7IeywPsKzaSonqUlU7rzucQ+ 2PGCfo9iPX5UmATRuHDQwuWdEfNNPM6ZzZqVNbM0xvbA7V/CdEMukcBe9w+cNxXX lZ+9+C11wDMSPLhDOWOVxbzmI6ILM8nGcDfu1Bm7KoV1KBftr/93lh99sSGdKTih l60bW4YqcY2jklIFL56p3AxDFLLMkNQZGXD0UFOsrU+4JTzWa968WWqWcwb7K4pA J34+R339uH1Nu7Sq6+fwLjhUx8LYBFt1fYwBCACejG4K2WglgQ+9ySgUOFeEZtS4 foakLWvzxhn1Wuf1cdbY0/JPWu4djnPkmvw0+C5+rEIFdbBlKGk8WqfkT0UvFYJI haRFxNMTO1eqOnwOLzcxIm5+9KItR/VsJjQbndetWfpEO6d4wT9zdn02v8yEcKFy AufIkSYLk7Ar3qNGP4AmJtil+vKHt5umWlLcB5gmPeJB0nfjPTaA5avf2lDtrehc EtEK7g6jubAiFHNxL5Es9CsuGgzcEsc/RyYqc5+N+MFcbi38dHB/DxOHa/tp6BMT zVHy5mBcQUEox0Yil5R4McUwviBTw42+1yzv9qTfDa1i2YhGdi0cotNOwGmBABEB AAEAB/9xHFiJbu8JCplTWYY5XrS5Ja+O691clXcVk/gC/dbMWWngiNyu1n1YIr/1 kMhrdb+d6YC0anDsyjbBsx9iF1eRLrQizw0SJUg8yTJSpKTKbc7fQ+Q2uIpW6aZi gKLeLO6ooq+ULEbfNGbzFc5g19atibj0ILSBd2QAEF43/f7X5uf5KwPsDD6w8dLP 5SUwOvHKlLGETPQ6nvpsxhrx+5fC6hCpBo/yd+wIK52h1SGszcaj10FmJ/6WG08z +kbuE4eB26/DziBzbijk/aTpTECOkoGuE8sj3ZvrXzZqtBffTcKPcJx9Q5j6057p UW9AllmXhbTVNBFASbfcQ+wM3LONBADRJp0N80RpbJfHDBhdjPwK3jer2R1O4rpy OtvOEtlpxhpjUCCzAYNrI21ATVIwhMLQQ8wTTkLMo7RnJLLrr+pERTJ+ep4BscG4 RJnOk4sWmrhqEOQoYlby4xSLgY/Ji02GEk52247EMpueSEzw0A3Oy8CqTejUVbEu vCDwsMgO/wQAwhAeH78R6kU+Tr+LhktCzEy8Y0pdvOHw18c0YlCSwFIVFGx2ymrE 3T5jY/2ta2GZro3UDme+CczVVi5QpY179e70rCHN3zkipRzyzwfFAM5FfTHFj3lJ Mk0kIuc/iESJbyV2yPFlca2co3GRBATD0QAdEsqDrNvi/gbSg3ToB38D/2oX5W+J eAWDAP6w3btWu3QB0qg0tA0j5Lh36bmcWeAV1MZ842oK94SmpdQWzaHKub6thUe0 kqRIR4BVOYDxK3FWSNIp5jlSuJ8wZVmem/9PL5eUKvRKksPhMVlGTtMA7O7JP3lH H3pQ9g9yQtVN+J6CduzwyPJm/FuIaSfGEN7nT4fCwHwEGAEKADACmwQFglt1fYwF iQAJOoAWoQRGPg+CpDgv5HqrPjF/UGNVzQzfQAmQf1BjVc0M30AAAJUfB/9raPTd 85hyHhqJDjPV8UWdb4NX00Kh5vKUfwNhenehU/X2Wek88385Y4vXGF2LInRM/kEC ZiO3szKw7YTZyFxCWZDB7WfhKH5NazPpUsv3k+2HVG22KGhjTwZilnL/B3Qamidp BTZ6QpVhwe47Q8vle82TEsKHnGHuVitFxv9G/tdah4Mlt7Uy6TQE0FgQAA7tR1xn Vb1fHwbhm+1Uyb/I5TKAMXYsI+hYeRtsyYkR+/pBf3ARjUf7WqzaPQlubTDNUFjr GEDsUgWFqIlXrjdrqiZ7a060IC6bWC1AGxwuu1qloTzFlTYtK0/4ta5R2QXiWOzo qHF4JOgQ72RcHqNlx8LYBFtsQwwBCAC6CbdcGqhYcEtx0BhGDk0Kjilq52XjLOJh T2jxGzZmbq16gNIZnGmSyytn/Br/EiqfkiNmLrP29qNtIjciWx8oPbHLplupA1eA dvhCErqy6XZcQqkydPXKwDOZVamwyv4N9lEvAYpYspKzWfQQ5mqbwIMUaj5pFH1p AgQhzwMOZ8okefwKjURwad4+sofqGiFbu87VPcLBXanrLGm2XIyEFGEyeQ90nTej lrI7moIm775FSuaQrD7xEaYpF2/suAssPn9OIugEsK1/8FEpMXNVlHehlC1xLpIp TFUpx6uxRR5S26sxAIzGzz1b8WihzZ5hg/l6vp+CowjYfKX39ErbABEBAAEACAC0 l+gCJfKT33atZieaIhnRjE1SdIyoqhKcCh1CKHt3lisbR2As8nRdxpI9YDLIt1bf DlsEv4N90tC0iUliNXiadMFagwRpnk/b6iF26n2IXi/CwgdDbtyHVGsCh3CxqWxU G2+VpPYqwja8mi7eFP8acRgNwlERao2PWSCvvSgnWxtIUTrY1uSnN5tt5iGOJLyS 83q/z5Wn/WQ1lR27ZNU1l91MOrlABZK5um6ZSWbDpUvgQbEiug3qt/ZtYSNyhnv5 2nXKQlhs4gwwUjQHT9KGjHQXNw/pogzz7PYx89H9UX2bXbHOQ4spvzkmfKu+3sxy cNrQElfsqD3pkbuCS0eRBADO7tVBya7B1gLPqxca7vjYV5ZnL5KlRak9mMOKiSY/ pOODmFRDMHE6q0i+m/d6INE/pv7xcGOQReKcyBihtscP3RCTzORKSlS9zj9jkRTx RtTPXmyNuz4WSmFkI2q75EtW9g5C+s0VO90CKuURfHb1xPldwIaXytCpbaFq3XZy 2QQA5iaF/e6YsEQ7h/9VjlKGbrfHfrkt4j+QA3pVguKZAF7nIpiy9VLgRkHMISbY g3ZqVwxvIRw7JKPP9UH6/dLQxJUHinfjayE82NECC6zZ9uyBQE8NkXidTWXxIDbQ vxL1WUN+l9wK/QAFid+HEfKfVom2TrSHQwf5aS0fTcZHctMD/05R4rCdGRAb4byj 5ToNmap/pmMSs0s6O4UkXY3y347Hyy2BxaWQu9DYkD6f8c6pY01shn/9DeH99JmU ZWy+tZ2xGGCLnIbiK4aQk2NGvCtp84Nbt34YPpRhO24hqn/YUtPS86gUC3syO0kD 6YPtzAuK/Jb076SrkMHqD0JYWBR5RWTCwHwEGAEKADACmwQFgltsQwwFiQAJOoAW oQRGPg+CpDgv5HqrPjF/UGNVzQzfQAmQf1BjVc0M30AAAMXeB/4meK5uj2KdttTs 9EFY3t1d9oXYwEDsRpYsVtPvC4dq/F0PbtdfORpoTyBIJqPkz1jMX9oPu/vv8lFe DIPSQ4ZXKeAfvzYi/zKIOqjf22ZMP6hwTRxNyFJS/WBMvWXngU0MuWK35eUnXS3v k/UyuI4t1pR8ovROB1Jl0jInnu9qbqU1UreohKLrxzJ77bDhlc+h7ogLrnqa63OT JG9XvY4ckgxn7tdYS0CvCx5VkZAMg0FZBQ/6PBiB0NjV7XatFHCFhjbjrFnynjxF D8ge29a9jah9EbNovgb14tWvb1X4O2urQLAFYzG8SXX5rXxUsiXHRjidIB+HZfcU f0sMkto0x8LYBFtjCIwBCAC35tJV7rrl5x4KEEQiKC8v+tvDUetV9WSkKw9UqciL /586LY7gKMqjGx5hQAeRN/m7lYkefQb43xO3b8sMo8vDL0uRdHqRj8HV9CbWT0DG qrjaI3phP7VlAlhzGbAB8IqWWnHfMDpW235q7AWtEve3q/Zfp12Pg8ORWQNgPAf0 DWbv8bSPKnhkpQmeiJCOGp66A5jge4yqqe5CWpyRbW384b6znZiRN98m2LbDFrj7 VT0egZXEp6xYR2Edj3DyqLYWs7Z1/bP6OKWNHZgOwI0ChvfDb4Afk2GXSZHQ2Iui BD5/adMyA4daTjg/cKKwuEXKcmiKttLNHM/hOrgypXXzABEBAAEAB/4xCfgkuhhU niDAhj3k7dpHbRmVKA8dSOahcGoSJYQ48N1A4k0rMTV38Q9U14ItE0Qg5Ws/Gg/1 5WaYj9MRd8wqLD52dx0IG9GQgB6rH08iqvvID5XewOJaIRn892ib28zY8x1SasXj y3wWfwG0eU5dSBnWwAaO8o///kCMNPXZ+hRlhov9iMYmFR05lzkeQysBryDcD6Hf 0ijmtPtVzH6xkZ4CNNjFrgTjKQJB0O6e5YQyPgeruesnRgX8th8VzOiI5c0sMt9t G3ps/FptxPcVjBgFL8ih34tBm5xVjjobGL643CMIxZLiuCn/QUxLKsgMONOC8Mht ieK6U5/278OhBADAxq8hjxf+kR8x3zyI1KBtxb4IUVDhvQK7XYo9PSnCBuFB2GUt jV2kkW7ll/f9bLhWvjwVvkloE1Co8elJH7fMcFYg2oOOs7euaK+2MmZX+PWqQDEN eC28avfNN+zyCB3xbvVg2B3+zU3qEedUZ6UihF1o+qmmLbzLx1O/An/tPwQA9DcL lxnItboLgd3ReBZYexBdqx1PYNBp9PJSXS3UKK3ACjBvCw8AzhuROwHwnL0ikUzY E2xbtHXuf2kr/RwROQTnMWGVv8oulpJP4/BmjuJ/O5BfO/X4SoKlWqQiChVgF7g6 YYVsbFzKrkgXDYgT2jiIqwOYhYkEQL5XywlcZk0EALXIOZT5kxS3YHm3LkIY1T9W NFlZWXQZX6Uw8/xzt3lmw4PLnmRCicNMZxYNxfzIPz6nqsu+c3JxSHENJFGFoY9N QappQVN2vyVv3vx3B83FZwCUAUfOxPQda6HuQUrfGf0S7PqnUUVg6HHBSg6nnFww pjp2eR90Z4tWdpGal8GxN9HCwHwEGAEKADACmwQFgltjCIwFiQAJOoAWoQRGPg+C pDgv5HqrPjF/UGNVzQzfQAmQf1BjVc0M30AAACQ8CACADRj/Es8joxnm1hOwyacz JPFFVta2k51iIjK+3BV9abEV/SzRHaibfdi5TyDXcrEMxexuBVzjfiHIPSt6pEUX laWHCAQKTXO0WXMLAQ2YulE42jYiFGIm2KDaWiGBq/pIICqDCdVgkMWHYuBJ1CG0 U1nFzgMxbH5Ss61IPPKYFBRpvhDVpqKhhUoPTinw9fRrym9cApm+7Vz8Syij4cLw T/WrJBJRS7S2UiMD+7v9Xvj+rQqjRz9W3LdalZFqC+uzuUL/nmT9px9MccN0DIw7 tUbnScN0T3SO79wU+olUaE7u+WK8eZY5znEUuimcRrtCVQjFb4SZp7orBUtZiVxn x8LXBFtZzgwBCADyHvem5cjZBR7+JEuednue8OYxf0d94YvngCX5DnQGgMmiFgYN 9PdzJSv67r5Ee4+HZY8fpQXqRnyum6m7AxaW9qLue5HKo3zqjnCa6WgTAnKx+9a3 WSZJxLmps8xmgs9TKfwgANmR8RzhPvDmEv/a6QGlChVwf3sbIK4pTqe5ZCQkWsy9 Mvv2a8lvYZfzTIodDBAiRcxBHMm46JlIW19zn496G3RzGkmTTA1BK/YJslhFDf4G K/Xnmoez101F1Zl21KLLJf96Kfkw3F60nAZtakNVnOlGAe9x7ZOq1SCXu/8Jd2jG K/OZvqIKFzHY8hG8RBNHVJ/H7Y5MRqvJ+w2PABEBAAEAB/9/3/UiU5gpbnf498hs b91Ii6i4GaXziQzv/pGPoZ+L07cqgQE/m9KZINfClxcsaina87uMlgayvfmZ6qLR H4dtweM5m0/bu079Pq+gUYfjClbzQ7Qab9419c2tIEXjGlFPdgwwBuLiHnsHR8vJ Z91zi76PoOzD1sB5CmCRdnZFBeS3Mbog9TdWy4j84MIiLHwNG3aPHIMD4ZgLJUTs TtFm670QLCPXo2qdzR+Yb1JhjuOPr6qapzJcBTeqqNg3OGG2dftnaDKnf5PJp0+V /iBxF1BrqpjUTHKm9Zr/wJ/kIMENHqnhIgVdielo2azdLiNcvpjmlcjksL3hctQA 3UxJBAD+1Gytm2NQUEfpBQIn4C1+nsf+ZUeEGLouq4QOvvI3UssrsxV/8hRoyld7 ajdvIvgfck3qMp53ompd30D7EpKJabdWmupAarMJ3e7R3Bg9mcIh//RWAloxAIz5 aBpTJF+F6InjsXZwtNdEdLrAbFhyBHUhYQuno/eibLUaVrRQMwQA8zuaPY3uhlIq iOFJ4rTxTlO8FnPj2tOCxY82q5XjzOoczgPiTG4YMjx59lGH4U/JWnp9yrEUxGXt HtGO9J4KvBmDM5vAv9MPKJcHuRvlIrtTVJ564tbdTnxSVfwk+cbulqeXD1J/GieC vC5Nv8tDJwmK51va7ztqj3zXOJEQwTUD+LiB6/5FGCVOYllYlqnhB5sHygX0++Yc faraxkl6sER0SVBb76HzJOkK/n03utnPHFFdu9VUUooeNtcso0O4lBCUQaadhlD3 JnPBdIAeuZaOpFOZypGuKzrMX8UvLdFC/JWZHML1PGEQcwDRASAT2W/Bj/KFDZWd 4VQ46KlReMFIucLAfAQYAQoAMAKbBAWCW1nODAWJAAk6gBahBEY+D4KkOC/keqs+ MX9QY1XNDN9ACZB/UGNVzQzfQAAAJ7YH/iom0LIGD8f7C95bYpncbsK34eV0sFip MxSjgedjpphIlvweq34001u3dC8Mz81ecE7yRLJOOjB2rHzkODCOywQMbATVWjMc trOxr2A6A+svlKosQHtVyElebdRaA42diYP4TfP8k22/vRssi4qBoKWya5ks/PjI VzmVMWqaRBcx2vQiUB18Vc3Omkpo9BfgU5D8prDJ9l6re4u14sYRUP9cqHWWxH84 jRbfJ6d8MxODUP/QQKo2dN5ftirMqkc8LtRVAXaGyddGFRCp1QsbVpA5XXMhdd+b sAutbV7jDXcsBTg11fD21vInTJfYe2AUpX+nWH3J4pINUoDkWebKz2THwtcEW1CT jAEIALRDYkba9BvuNNvJRh+TpmkQCTUrIAlJUhQ5PioIsSLPWlFtq8veDLzScz+9 3zaYO9aJEESkXSN7WK4X3iH6bWfPl1gk4NAJJPfqwA1ZJROpytp3AqPwtcL3KjBo 8blGqfZzlhW8UozqC8JKuw3nwpvnPErqTSbLvSWLt3+gteLP0pjpSCZAPWJ8xNDV wP48cAVTSj7+2jk4sLuPyw75rLyl+SadT74CUNPlB4x9N3Bi3JGw8rF6RRTFqJUm b7oYhGo2HjCSprZXpzOqYuzszf3J4KYA5mpPJoeUT5MeZnvjRepGax7d56h3NAne HCvTNR1Qw/mQR4oy+TOpkUKP6psAEQEAAQAIAIY0hL5r1MHWntPjDaoEqygdID3N VZSUWd9knrt8rSRVa9Cj7fth3enWZKdYHQ7wV9xpPwtbs1vvQR6b9m0lnL5k9zBQ hG9d34AT7dgCPnBdQQFVCUo0s53rliVkfFKMIY2ykFKuWmc++HI2YP0BRwn7JhBA UCBKHxAM7Ri/9apd/rgHnaHWGFotC19PciJGAzOGXcoPb8Hrpton9cE315h5o0im FGrxcoR6KXjNZSj2rNHWh4RZQq5EJ0PiFZW3jqMvKfFUaCrKeh/2SEt99ahb6dDK OBpzVO04UqHoOQRKBt8VWvX+wP7WnKUBsROvq5G4jjx1in4QDSXk2xC1yQEEAOX2 HJsOARX5dL5eI06gWtCKmC01MSd1810e69Bcbkg4ba8yKpeQc31sDwejkj0U7508 9TBTDeqZM6BYfUkaFZCaZY7iSrFIs0bfw1clPbsR9yhE3ZCGmcJcvA9plCg0VqUo AWpTZHKLzp+gpBuDARsEFR6o1bm0M0/ldjws7O9xBADIrKwTFYpxqTkwkTKLSlIV tl4tBzc2LTIOczflfo0b+8wvlUkjdVhF1n8//R0FkqsAKEPqhd/0h7UCccA6mxLW dR7FyRsfXVefMTAUYBnZl+ieimtbvcm1wxllK/Wjv8oE/LefpjFEb0P91Lsf/GMg WGyhN7E9xb3JgqhZ4eDMywP2Kc71ydY3k925R36/hYFdq6OpmauxQkHPsCysAYpI MdOuO3Oyq95dZWTiHGSnHNOWcEYEeFFwk0CFOqKWH1lj4fcCWGsSmXkV4OTnkbOX nmeV5V6/Oc0ss/ciEhP2cavrHocGRypHV5G9BbU97UiNCeFUU76DKHxvz4GHqchm /zDzwsB8BBgBCgAwApsEBYJbUJOMBYkACTqAFqEERj4PgqQ4L+R6qz4xf1BjVc0M 30AJkH9QY1XNDN9AAADGFwf/bHVOCRxHGp6ck1UTtVqXpdQmdkDCFmGY5wK3o+0M 5/UvF96ln/XfPrLgIhDAId/s7l4KoXbn4wHLMDXAHqqcUh9qxLZMkEvtsCxazgHR Uw/mkkUmA2IFVqMmt5NXAfpJb6r1Grpf/W4fQQ7BXxfiKjBuixQGSKcFpZzwBhrN mxIdDLiTnICkvTdE9FPYKmsZLBvnO08l1KIq/QBn1VM0KddzmEvsdKzcNQ+69p7h 4SD59wu+iw9WJF1NUq+ei4dLo76W29Mc7NNnc5O9vHuiBdkyRyeBhByFvQGoo5re OMvUlzojJCO+fw6XDkEGgt0bHR3a7NAN7ekClu6L0aEPfsfC2ARbR1kMAQgAxaL8 b+tNXkTTMB2x4J4c5jYIlkwq7Cip9Me1gSzbj8i8Fb7eFk9z5ixYMRsOC3gnHFCn 96H0bkJjs2+569ZnQN3UrgNQmfokVI/V2FRfxAFWzD1eHiloNM+eva/jP0V8+9ZB o24739wjTuUSrQA7vyxP0nbdqbVDYulbUEkesaxQwM67Ki3d1yZeqwx5xVEWkTC1 AKiqoG5Jc6jkYjRG1NbgeHaRuJIZaYp4NQpwM03KeZmzcg/tg4zJNp4nWJpqGy1C YnA85wYPlIjUwlfD+AYsC9FHer0PfKJvGAjYk2xLIk6+ff0ESBBqAJK1+QbFaRO3 icikuMq6z7yEd7fl0wARAQABAAf6ArtwFfQAy87TxJSEgwBskdopYDqJVV5yiwm/ vptqJaC2yO4sj85lYcc3KOag2RO+JqwOxmOcx3Fvg2mF/namUsU+TXGKSbalje23 HRh9gm4qTwSCKpST9jbwkNq/n35MbgbWPXYmqb+XVV3hVjbsnIX2TIiMI+53kgR7 2K5KGFatREt4w/waMdHaOjYnyn5C6/mg40J0K+Q0yt6FQ3Fq1Jeqx/t/zWjwCRiP dUQjBd1IUw6KuvvFZ7O3OS0bXlel8ELNnpal8s6R8Hn4i3B4ub7M7LRysC0drVlL 92TMELVkLmlLyFASqLfzPGiCszd98xP6bKU9dudSDLFvLRkTwQQA7nQAVKJY+tvz j4aTmHSWZYXU2+R00XvLcAL/Y623uUL2+pC5JQPVxzvfbdk+FOXipKKoPujtPI1/ qTx4pysVQW8wXyYEZO3kRoIwvzK4BwpDhyQRDsbU1lMNc2/uQdPlyWAJO28WodBI uKtwEVNM+jTBTmYEOqEqQuBZ9h1l4jEEANQuFL22fc6orW1bvvRYoEvSIEQ7eGOA FD7bqgfHgiB/pm7DfjEcgkUrzevkG2RYEc4mYWjNX51UrMr095Jq1ML5w9gbAOJM n5i9meo2nN2k/luUSkhZiwUKOaB6HQCQxh05GD9aqsW0pBpYCFWbf9Jj0AIcg/Wi /XG3BMho8SNDA/wNaK7H+XLgRQu3g9jcoj3UZuO+R12udlu91y5oiHYG3p0fZ4Sa 1QjY63zRXsL4Eor8Dbz77VLvmWWBmassgSPZBf8FSpMfSLTxOVrRQB1anixzhIb7 Bz482S2/2S7HKP6iciEjpvCxJ2idIcJVPjSx5mEK/vq8xeD4FxSo0GF8ykYcwsB8 BBgBCgAwApsEBYJbR1kMBYkACTqAFqEERj4PgqQ4L+R6qz4xf1BjVc0M30AJkH9Q Y1XNDN9AAACHJQf+JePdM53mkJBNFbtLnu0uyYkFD5tQz36IhxAfvMpmQ1hbe47U JQg8BCoQW/LagI9HUozeAHRNpgCTda2RPj29IEWB9Qn6T2d8ibPvsE11lE+ajNge zyop5gR6l+kZcmRJCs+/0JKRZjcDl6LZzep4iZjQirbWUawcu2NLhnMj0AntO+8z 2LeJMUzsTPS8kOSsC6QarD/avCP4rGbn9NVAFfhPkfTsE3YrpobutR+oxcYLoGfZ 2te3IzSJ/JtQWwYYI93BQpBR6qm6r8tGmp7Kue9j5jOEECRTiJ2mN4LZDlvW7K5Y fCrXK1UoYRgIWzrNQdNebv3RY+X02u+v4JbK/8fC2ARbPh6MAQgA2WuIW9hFjdBI MYXwP1LGHIxAHsSjDgln646385ev2tiCHBkFwrzbCUVrHtePue/+pMbkD9qBvgDD On6GvwEfzBIYM78Me5I37SJ47rfQTbvn3JUw/shwYksFK1hxBhSI1Z7wEJDDUcAL xWrNhdd2eIq1A946hUshL1duiwPKNr6szIyllk3XyxVCAU9N8thLmpEBS9EGTxat 5JgMqHNBW9zMmJfWgL09R07bv8WfMxji0eP53++vO32pWvyLz0tPi1pz5ro16KVD zGc/ywbZ8jqGChe/dKn7Cap4gEsWmh+RT+px7ML5EpgIQYFoOATj9DLJ/ITYdA4q SYOxrgguqQARAQABAAf/dMemYTaj/UzvSfJYHZsXIARQ+HXD4uk4cTSiJ2vFH/YE xJTIHFYWDD/GDrXX1BZNHzJZEw7ZezWrpwKI25T+AD2D1F98k+7wrSVD8Wy3rNkN 69QSkfcVLiZ/a03Av3ROHveUmEE5N/LAH9SC+a3GS26tVZuKsUeuVmBO1so9VfNN KOY9bCo2lRWciSnBEMwsatDsXLTkINThdNP9BoR+a223Ms8oM23HZKr9acWofK6f TDTnArxcggtxhhPkG6iiyBJu55O6jV1qEr5+5mB2f6Ro6qprijz5J11UaH9tKVAj dPpM5Ld0mT4/D/TTzCtaqpFI0t+VvQHO0R5DVzeiBQQA7akJtzYP/Dt4ozST3oXL eNZ7J+o15oyI1QxgXipkJBHZl32Ub/j+Xt0WU1JNTy56Zv7GUaktuI9wfksYfXSe cKTOfXOD6uW1m3jrZpKvfLZBfhFKbPqefo3EcqsTIiy2ncBGKKyc6Ug0VnOs6QfD 2e1SDKXZU55e1Bz3bgJml2sEAOoypmHh5/L/hvcrdkAG5gXZJOMuF8Tc3bJ9zz/Q xd20vjkYJTP/HA9oGgvBq7LE5X4jecqDhDQbfRaSU+f1ZIrTONo9Z8Y4yhd+Igrx d8k3RKO0gNt+VtWBI3SWXQZ6y+cq+i7M4G6prcNQlWFivv4+9sPlsDv7R6Vzyffl ARs7A/9MvS7tx0JniaRD2UaCWkownacEvAhRApcbZH0zLsJl2qNnO45AIGP2Xf0m 7jVKMhRBEIXBbJiQwIWcxANa4K828U6diaAa7AbPrzvGunfh7ohXDRu9yZxD1MEp 2RZTWPGKr06pyGgE6ou5nMtZnzZIzdtW98B0zNcfN3Dddwx4x0TgwsB8BBgBCgAw ApsEBYJbPh6MBYkACTqAFqEERj4PgqQ4L+R6qz4xf1BjVc0M30AJkH9QY1XNDN9A AAA/rgf/ayJl+C2OBnSeXVekFapy8bsaGFZ6Z0ySP1m0blwi+tAR66b7HD/6lJbF OOmt7so8Vl94qHdkhhz92/D0S1Y3qkGWgkC+DMiDR69icPl12IuPE705/ZICde83 c7+g/SqATGSQ1ky/kFg/GxE+L/3G0gkKPcs/keHsIV7Wg65r4kvsD83bvx6ia14c ++aQI/D5BDevsHjS32HL3zMiSuKEdD3k3X/mJQIfLf+9iQrsXdO7/zcJ+U27wPNu PGr91A999x2sJXlOxO3SWM4sd6jmJhfU8veyVCGitD4sDUJfOcr2OUhWRbSmCVq6 2Nm8Jy3ChsbsLCmwp8ipmBtiwJPXBMfC2ARbNOQMAQgAu7WOZEpU3QrJ4tQ+MhTk /QzKMwVAAytsRvgOnD/MXHHvKNQOE+Sn3DZp5pRJN9uoY2n3ElKUxnAuvNWzqbq6 36nrpnB8CPSyJIGIj2toUmyNcnjK6KwKV6FFs+51IXsLv53hrxvdnh4IHQT/VRnP pOvGlRxdj0tdwwySJGk0LXCWGYk3sbHOdUtVRSnXK+9lno5n9VjNnbdp24aQX0+/ VmGsd+/rQUNra/3nYcpiyp8MZ+2SUJpQO4GzTnVuOINNfnseTbwSsN144W9eU1vw +g1GAB1RF6gy90AM4lyNJYKaO3oecp19/zkr7y7u9wz9AG5IFK/Hf21/CcBk2F41 SwARAQABAAf+JOkbmd7/9wNDFaZ0oHZ5XMUiEYxLvRPR09a9UL4+2kLRK28fYYwJ 8gSFKIEqMKCx6PKSUfkJJkgy+JDxJkQAsTCBlRXzXfN4ADO0fbc6KVMNMWBNThle Y0LkKPCP+chvU/ugwEUze/8dy08LQlmz+hMe1n1gbGf+hPc42Y4SY4i0rsZJ6iPW QRkD/0lYVM8of6Vl9eyDmA8PC+6jxitoqtl0UKN2SdYnG/YO7OQ1OGdbjzdSvaBC g8JMcPz6UzXdiOFwh5hLLNTIvg7PypWiQBezUwIMhMcBGMpVO5sADX/ULk0RBCgz tEjfjGQ2TT79tm3XIkZyOzI8xAMv9sozmQQA6IebFSKOSudLx9F11jnczT9tZDlf PJaw8i3VxTVgvx8XKoQfqDZGghjgWuqz0LoMk7ERc0wWhWoeT2t6QFrECIB8ceAC W8+lHQVFTd6Zr0bGIapIlbetJcTKgIxeT4i+c6geMTaLxhVvxpjLEQGSqbR5EuYX 8QSSBRiL/yMuw0cEAM6n0m31RP+XyUylelz1BdX0+Pl/Vme3fibjRc6BQaR0WoP6 Djjiz+sXI8TIjUL4WGMW/SM64p/4AmzcI6hjwKUu9mbNGck2cRTx9w3Td3LKx3Yg DI/arR+s9T1kybRgCpQRnbF/B5wh37o3G4ZddxaUu23f1jlBdUWSuk6+idfdA/43 iUpY8dTuQd0xB6KUosUEDgVmwMwsVBkKBS+4vuHGkWfGjVksdRjoKWu2Dm4cppvN Lp5iL7yXOppYugmAUSn2U7jW5qzmuq/Wr67N7gF518GWW3aH7kc9XXzWZl7JiXU3 lLKAcVO/OOl6BTyCdhldT5WtyB76YqIWeXMcp8f+GTduwsB8BBgBCgAwApsEBYJb NOQMBYkACTqAFqEERj4PgqQ4L+R6qz4xf1BjVc0M30AJkH9QY1XNDN9AAAC7ygf+ OYHvnYxr6yiPgQWBrul6dnhPhNG8k5HHQX9xVEjmPlj9asU9SHTTd06SnKK5k9rO ZqJv5BVE40YKjxRnpF9Ra9xYqQOOzimo68PrXP4JY9o+MiHvYqsvmwCHGX0AEaxh SQi4VXYVNjy8iasmJhKzVVl+td5WeVYvDGigOw1csazM26xJd9bdiuf+rfINWqFy 7yvSRNJQPS7j4ifiVGHJyOORMGnqBhk65E6Ab5bFEv9mAMUd0vpyVUEO5Mv973FT VSjqscX3Z8BOHG13dmZdoDDv5vsKFvj8GAmbd8LwuuOVuWgfNWkmcRR22L2UXuPY KlUnc+xWIKqVkwWCW8Xv/MfC2ARbK6mMAQgA4KaMUFtwNF58Hk7pwFLeJckP7y4n 6yBw/cRxrCJ4mf8put22ZmXYhaPXnG0yn+hOLFj0tx+aNpHVx28mG4SLFjS73sDP vdQMNp+FtAktbARz0oNZECz1Lj9+JeG4DXpeL8vNsOEg0T5RXWZpnq2XbX6LiY+t yD58h6OAvao5JTJ9ZYiYl6iIGBVCf9NlOk42HpokeO2PMrOZA1vuCUZmmRYfE1v0 qP/cCna4izbmzrhmUW+BdeOfHmaUn4BKjyeA+zqHPsjKIXcX5R+XJtvaadS6cqMw Gz+IVW6I0mc/4DASgMs7zU9VPmcrVgOVkyytrPaXnBF138V6JRvchsrlkQARAQAB AAf9E1VB7ghwd5FKDTGtXCfFhmrvPB40wT9hefVV8wJkbpxO8w3xjI5nO9kFzSh4 mtpbH+Xn6XqGwvt58lfWZ7fYYGjZYioltqhxBKJl10CS8CDR9uPm3kWANCKysH/k XwSQ5nwbj4VQVbGPPwJ5RHab+YhSadw7kGgQP3wKsEfd8x1j9RDlXfuLszt0z4qf VV2PeOD8KCRH3zKfMcwGhGipjeczFkTRH/RDIy3Ib8jQmiqxPSmOdKTLIZ/iZtbV G1/Z0mIV0IV8b1S/5SewKvclt8SUz78Yq086qK3O2iDiH8teFq4YStwlYXhFGaa8 NyFq+Kfjn2R2IL6hLIDYtJXJoQQA88CkSHldMWxIJDZe/FAXRkcsxt7st9GTonlN 9s1oM6NIE4um26GquQFkCqXSiWi27RXzbApPmyABHLB+kA+ZsqCvBJbZXX+4lQPM tB1rHN/kmrcbGlkFynE/7eM8RHE0dq0WVkUz2iSVcECi6kMMSqo9bvk0N42djS+6 OUsHIqsEAOvwM1zg4q73AWfdRUFV1tp8/UvB7TtxWiKjsKEnkglENGZs10I1NFDQ YmbVuJdcxzg9iVrbooz2RVTE1CjJZ85AlggqgkNhawdVLZthtbPIp0q/WTk8PRog zU9odlF2W+6GiQFy2Erp/nWj6O+tMVuncz7Rs/XS2QqXmsY1qPizA/wM7JhKrXB3 Y2awUecEx5WqpGVek7dFNwCUTaew/0aSLiVv6jVd8Ni0NEnPqHSqKimQwH9hQeeW wB5OumVH8iBVR+nZr0ZfBJ9aJUEZcEvMwDbBllSc3csauPXSChvWvzt/BurO4j0g JKNGHGhb96a8N2GDg2jhWqpq0zDQkqIdmT7mwsB8BBgBCgAwApsEBYJbK6mMBYkA CTqAFqEERj4PgqQ4L+R6qz4xf1BjVc0M30AJkH9QY1XNDN9AAABVQAf9Ex3MFZF8 qfCAyxqC7JQHcln0trCSzCc9KHqSQe4u3t1T3S8sh+Ncru8RA8Ly6uopVMPNxnTY ZV/ljqynbBC/NXU0Jav6bMk7PeBZtvk5WxfdQFzWbA2JgWOap66D4+5rla8Wcdeh sxWkIioOMgcOywiGrSTLTog+9Vz/LMGBPXf+Ng2HezrGtM4eeIKyoB3vtrzAmIsU DhkfzxLG11iRcMvM3EdSK8n3Wj56MmWo/FrPHilorbOLUjv9xDUGHVB0gxgg4/Qe z0aqD8zvMw6d29Unju+63VMg25hglolsYK6sgWJ8tZKvUkJl0EZdIEHntN/X5Hlk KXo8J7xzlkZlscfC2ARbIm8MAQgA57anvosWbyq94Epe0r49xsPHhmJkRawwUyK9 F6K9UsQ6jmXlXyL5DVGST2lkdSYMEFSr5XH6umiYLqRaOEIu/xIzA5bZ1BYudR3P wT0Ry36xpkGlKaKyCYAekN6LTVOuImQC1xOseEb220F57F+RSbrZ2bujqdW2Xgfo 68qm+8dP6z+sHlgfsrs1s9QCedILFooXMJ5JgETE6QAFRukvxtAfF7lINBFVSm6N tPsTamJpBKIb6YxcvIUHj/zR4axKt4r88HBu+3OAA9SKA+8bIQ/CfxAv4jy2dY5x q5xuongfJKC4/lgu/vho6E8majvqoXkyf/7fidO26+09SMT49wARAQABAAgAw/jT vm/cNYEsvfmoYQzNKsKCicHFlLeg129WBht5i/qym6TKbcAia4VF/Svh7sKTCWzT RWc061ty+l++fj5biRdXVf3LlXh9KkhgYcrIthcdIMP8cE5NzYMyVswhfPD9IdD8 JfNZtxAt0Bp8h9H8CyOhBGmSbh118+k0sdffiqjXZEhT10IhUetosDtqlZkoWK1Y Kz8DRgZtpiMCVMysaQL5/CfGgOwkXkcGLV0yGTYJ3ORSU+xkifnhzmueifciHxNh IGBTsdRhaqlA4B7igSgT2mTM22HO8g6vB/fBdBbW2oUkjAHY2frb01ZULZsqL6Kj ufZquw/HtIXAKYr4QQQA+rDiKeXIc+lSXbmLHt8uX1HiV4Xgbx6l97RbVx0eKRqF 0Q04ELPLCsfVkXxi0Va6RPolqBlF+ofYDk9Wj7ATA5mgrflnwuziTKdB9PHgid8Y dStB9ZSby4Be2HWnZeEvbUoXEVXXJwDOAjHDyLf5Za2povnW3AQc5O8ljFrxkTkE AOye4shVURhz+wBPGayG8mGoqKGuJQodS0e+bCV9is2r8SugkHbiMftRT4djFryI VzCusfU5yco+JFHDFzmmuATSCK3xbe4XzdZPdj6kD7NhNnra+tjQGeCmT6WFXaX0 XUe2PEnSQ30tRtfuwImg+Ie/YGcsUuPCCjzqe9BDz0uvA/4u1YbJ29OIW7ptoV9p VCPnndLcNxlqM9Js7kqXr3NAaMnh5DiEL5g9gfvr08gJTPUPeQeL2GYb8B/ANA8F xWb8jiZ3h21qB2RJhC5/ms5MnyP11JaISry2+IWH+L6klV+9re8OfURl6oPmHLB9 gVU05P91e/Eue7BSPk3P617JmUrewsB8BBgBCgAwApsEBYJbIm8MBYkACTqAFqEE Rj4PgqQ4L+R6qz4xf1BjVc0M30AJkH9QY1XNDN9AAACeDAf+NEPEPz7Vd4zm3qua 1wNL1mPpfjvfG/Nm52yX/hoDWHYVFPKIGTsvfmtbgXBtc3LrccTlENdAxJkgDShQ 4fgaNwLu4T2Wb7Slh5zujZfYmUhJqrMzpXGIBZ5i+ABLgUsE3fJYrsY2MXtW9Cdt 8CrEbcuXOtfFCuYQUq3181sMImIEUBE/XkAGv8q9Z80ho3mPlXw0VUbQjxqLaaM6 1sPlRqj3z4T8FsdaOn1yeL8IJYaqpcmP+8t+6RADhq6Q+cki8FMoHb6ZK+3y0ccf 5rJGKujsJnnCRvhTV5iC3vmzuMvTsnfmFV7af1uLmiHbgw4lIfLHwJ33uiko5JVV zEgC3A== =xW6s -----END PGP PRIVATE KEY BLOCK----- ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/messages/InRelease�������������������������������������������������0000644�0000000�0000000�00000342352�10461020230�0020424�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������-----BEGIN PGP SIGNED MESSAGE----- Hash: SHA256 Origin: Debian Label: Debian Suite: stable Version: 11.2 Codename: bullseye Changelogs: https://metadata.ftp-master.debian.org/changelogs/@CHANGEPATH@_changelog Date: Sat, 18 Dec 2021 10:38:58 UTC Acquire-By-Hash: yes No-Support-for-Architecture-all: Packages Architectures: all amd64 arm64 armel armhf i386 mips64el mipsel ppc64el s390x Components: main contrib non-free Description: Debian 11.2 Released 18 December 2021 MD5Sum: 7fdf4db15250af5368cc52a91e8edbce 738242 contrib/Contents-all cbd7bc4d3eb517ac2b22f929dfc07b47 57319 contrib/Contents-all.gz 37d6231ff08b9f383fba5134e90c1246 786460 contrib/Contents-amd64 f862fd63c5e4927f91c1cc77a27c89eb 54567 contrib/Contents-amd64.gz 098f43776cd6b43334b2c1eb867a4d64 370054 contrib/Contents-arm64 014eca050cdd0b30df0d5a72df217c5b 29661 contrib/Contents-arm64.gz b6d2673f17fbdb3a5ce92404a62c2d7e 359292 contrib/Contents-armel d02d94be587d56a1246b407669d2a24c 28039 contrib/Contents-armel.gz d272ba9da0f302b6c09a36899e738115 367655 contrib/Contents-armhf 317aa67ea34d625837d245f6fb00bdc4 29236 contrib/Contents-armhf.gz ccb13401b0f48dded08ed089f8074765 407328 contrib/Contents-i386 e496015d7e6e8d5a91cec31fc4bde74c 33556 contrib/Contents-i386.gz 44384de1db64f592fc69693b355a0ec7 359402 contrib/Contents-mips64el a2abf38d14c1c7e3aafcb21881b0fe7d 27962 contrib/Contents-mips64el.gz 457feed233db5ce7db62cc69e7a8a5c6 360549 contrib/Contents-mipsel 90ec76d0dca539a4c4aa33404de4c633 27942 contrib/Contents-mipsel.gz b9b7f20a6862237e3f73f027a5fef02d 369164 contrib/Contents-ppc64el 2d38f6dcbbf1cb9536511eaa92375028 29322 contrib/Contents-ppc64el.gz e2089c91540f7adb693675935dacf9e5 357860 contrib/Contents-s390x bb90fb42e72d39da53b3e1e2c2f46bc3 27518 contrib/Contents-s390x.gz 7523eead7a1c47ce357f9996a915aada 6714742 contrib/Contents-source a1f2993cc5150bbdd647254dcd083017 470120 contrib/Contents-source.gz d41d8cd98f00b204e9800998ecf8427e 0 contrib/Contents-udeb-all 4a4dd3598707603b3f76a2378a4504aa 20 contrib/Contents-udeb-all.gz d41d8cd98f00b204e9800998ecf8427e 0 contrib/Contents-udeb-amd64 4a4dd3598707603b3f76a2378a4504aa 20 contrib/Contents-udeb-amd64.gz d41d8cd98f00b204e9800998ecf8427e 0 contrib/Contents-udeb-arm64 4a4dd3598707603b3f76a2378a4504aa 20 contrib/Contents-udeb-arm64.gz d41d8cd98f00b204e9800998ecf8427e 0 contrib/Contents-udeb-armel 4a4dd3598707603b3f76a2378a4504aa 20 contrib/Contents-udeb-armel.gz d41d8cd98f00b204e9800998ecf8427e 0 contrib/Contents-udeb-armhf 4a4dd3598707603b3f76a2378a4504aa 20 contrib/Contents-udeb-armhf.gz d41d8cd98f00b204e9800998ecf8427e 0 contrib/Contents-udeb-i386 4a4dd3598707603b3f76a2378a4504aa 20 contrib/Contents-udeb-i386.gz d41d8cd98f00b204e9800998ecf8427e 0 contrib/Contents-udeb-mips64el 4a4dd3598707603b3f76a2378a4504aa 20 contrib/Contents-udeb-mips64el.gz d41d8cd98f00b204e9800998ecf8427e 0 contrib/Contents-udeb-mipsel 4a4dd3598707603b3f76a2378a4504aa 20 contrib/Contents-udeb-mipsel.gz d41d8cd98f00b204e9800998ecf8427e 0 contrib/Contents-udeb-ppc64el 4a4dd3598707603b3f76a2378a4504aa 20 contrib/Contents-udeb-ppc64el.gz d41d8cd98f00b204e9800998ecf8427e 0 contrib/Contents-udeb-s390x 4a4dd3598707603b3f76a2378a4504aa 20 contrib/Contents-udeb-s390x.gz ee111c5ba348acf3a21aae30b78295a9 103223 contrib/binary-all/Packages a00614008aadb6c665ff89c7003f7226 27334 contrib/binary-all/Packages.gz d994c197370662201c455e0010d8bb62 23912 contrib/binary-all/Packages.xz e0bad22fd686fc487cfb80a047b2dfbe 117 contrib/binary-all/Release cf4f12de2dddedd068776c4f2a5f385e 230485 contrib/binary-amd64/Packages 5af05a35a003103b58fc6d404b7e59e7 60785 contrib/binary-amd64/Packages.gz 8dc07073a1a348200b9ef4734868f1d1 50468 contrib/binary-amd64/Packages.xz 96dca646966230f3c91fb015dac4ab79 119 contrib/binary-amd64/Release 58ca3c2e5247de3ba82cf211fcbe25a4 179482 contrib/binary-arm64/Packages 3b64db81c8b3b7a631e5cb3c739229b9 48744 contrib/binary-arm64/Packages.gz 413207fc7f7edd2b9a369a93f1ecbca2 40828 contrib/binary-arm64/Packages.xz e09a2b5876bfb130426aba1b6034a433 119 contrib/binary-arm64/Release 8a9a0831df6c0b6c0c1c8d5ffa6d5c44 163026 contrib/binary-armel/Packages ae9ed362208450bb7e5f676b78d0a790 44492 contrib/binary-armel/Packages.gz c500c70f4478ce8d5bf67e186c35c150 37452 contrib/binary-armel/Packages.xz aed60bcb645d3d5fbed85d20e5d376e0 119 contrib/binary-armel/Release 03a9ea533d936d8b82092e4d4a22ac92 175400 contrib/binary-armhf/Packages f6ff60f1c7472340f2b86499b4fa19bc 47754 contrib/binary-armhf/Packages.gz a01eed75b8237d405ee441eb53f87d3b 40212 contrib/binary-armhf/Packages.xz f12ce15e4d9a53798da7f2dd0bd1cdfa 119 contrib/binary-armhf/Release 86ded1903059a9e5a10cd0a036d8a177 203348 contrib/binary-i386/Packages c0af809d37542a47127fcd4964f02a81 54188 contrib/binary-i386/Packages.gz 53123a7508a231ac92c2b1fcfd538e94 45328 contrib/binary-i386/Packages.xz 230310562973cfcd1b41b85ce9557b68 118 contrib/binary-i386/Release d11c244d2bc440e6639e83504269b2ce 163491 contrib/binary-mips64el/Packages cca51ee769666dbd9e30f7a40a2b5f06 44721 contrib/binary-mips64el/Packages.gz 5f728b2ab3bdbbc9d2e25bb36b9abba3 37488 contrib/binary-mips64el/Packages.xz 0c18737c22efb33f0444d2b50fc9e34e 122 contrib/binary-mips64el/Release e87482624fb8b6d22880980b32331700 164631 contrib/binary-mipsel/Packages 14d303b017b3c1508667191d2e10b457 44755 contrib/binary-mipsel/Packages.gz ecc9d364ea27de3ee8dc51f992b03c9e 37824 contrib/binary-mipsel/Packages.xz 194b02e7860bf2922943bd8278c88c8b 120 contrib/binary-mipsel/Release 684fd2b382d5983a1bfef274484146c5 179037 contrib/binary-ppc64el/Packages 3e818de3abb1185bba96d7d380e3b11d 48655 contrib/binary-ppc64el/Packages.gz 2507a38d7f549e74455a7e3185b9d26f 40668 contrib/binary-ppc64el/Packages.xz 0f939660cc4944904c97614e2e3feea5 121 contrib/binary-ppc64el/Release fa0ebfd876b369b44e6daa7a35c0fad9 162234 contrib/binary-s390x/Packages d4fa89337e773c5710a98eee6b3923a9 44289 contrib/binary-s390x/Packages.gz 3dd5755f5a1fa02b23e460bfc9fb1d0e 37248 contrib/binary-s390x/Packages.xz 3fb7e0ec0874144b27fda2c995804d71 119 contrib/binary-s390x/Release d41d8cd98f00b204e9800998ecf8427e 0 contrib/debian-installer/binary-all/Packages 4a4dd3598707603b3f76a2378a4504aa 20 contrib/debian-installer/binary-all/Packages.gz 8dc5aea5b03dff8595f096f9e368e888 32 contrib/debian-installer/binary-all/Packages.xz e0bad22fd686fc487cfb80a047b2dfbe 117 contrib/debian-installer/binary-all/Release d41d8cd98f00b204e9800998ecf8427e 0 contrib/debian-installer/binary-amd64/Packages 4a4dd3598707603b3f76a2378a4504aa 20 contrib/debian-installer/binary-amd64/Packages.gz 8dc5aea5b03dff8595f096f9e368e888 32 contrib/debian-installer/binary-amd64/Packages.xz 96dca646966230f3c91fb015dac4ab79 119 contrib/debian-installer/binary-amd64/Release d41d8cd98f00b204e9800998ecf8427e 0 contrib/debian-installer/binary-arm64/Packages 4a4dd3598707603b3f76a2378a4504aa 20 contrib/debian-installer/binary-arm64/Packages.gz 8dc5aea5b03dff8595f096f9e368e888 32 contrib/debian-installer/binary-arm64/Packages.xz e09a2b5876bfb130426aba1b6034a433 119 contrib/debian-installer/binary-arm64/Release d41d8cd98f00b204e9800998ecf8427e 0 contrib/debian-installer/binary-armel/Packages 4a4dd3598707603b3f76a2378a4504aa 20 contrib/debian-installer/binary-armel/Packages.gz 8dc5aea5b03dff8595f096f9e368e888 32 contrib/debian-installer/binary-armel/Packages.xz aed60bcb645d3d5fbed85d20e5d376e0 119 contrib/debian-installer/binary-armel/Release d41d8cd98f00b204e9800998ecf8427e 0 contrib/debian-installer/binary-armhf/Packages 4a4dd3598707603b3f76a2378a4504aa 20 contrib/debian-installer/binary-armhf/Packages.gz 8dc5aea5b03dff8595f096f9e368e888 32 contrib/debian-installer/binary-armhf/Packages.xz f12ce15e4d9a53798da7f2dd0bd1cdfa 119 contrib/debian-installer/binary-armhf/Release d41d8cd98f00b204e9800998ecf8427e 0 contrib/debian-installer/binary-i386/Packages 4a4dd3598707603b3f76a2378a4504aa 20 contrib/debian-installer/binary-i386/Packages.gz 8dc5aea5b03dff8595f096f9e368e888 32 contrib/debian-installer/binary-i386/Packages.xz 230310562973cfcd1b41b85ce9557b68 118 contrib/debian-installer/binary-i386/Release d41d8cd98f00b204e9800998ecf8427e 0 contrib/debian-installer/binary-mips64el/Packages 4a4dd3598707603b3f76a2378a4504aa 20 contrib/debian-installer/binary-mips64el/Packages.gz 8dc5aea5b03dff8595f096f9e368e888 32 contrib/debian-installer/binary-mips64el/Packages.xz 0c18737c22efb33f0444d2b50fc9e34e 122 contrib/debian-installer/binary-mips64el/Release d41d8cd98f00b204e9800998ecf8427e 0 contrib/debian-installer/binary-mipsel/Packages 4a4dd3598707603b3f76a2378a4504aa 20 contrib/debian-installer/binary-mipsel/Packages.gz 8dc5aea5b03dff8595f096f9e368e888 32 contrib/debian-installer/binary-mipsel/Packages.xz 194b02e7860bf2922943bd8278c88c8b 120 contrib/debian-installer/binary-mipsel/Release d41d8cd98f00b204e9800998ecf8427e 0 contrib/debian-installer/binary-ppc64el/Packages 4a4dd3598707603b3f76a2378a4504aa 20 contrib/debian-installer/binary-ppc64el/Packages.gz 8dc5aea5b03dff8595f096f9e368e888 32 contrib/debian-installer/binary-ppc64el/Packages.xz 0f939660cc4944904c97614e2e3feea5 121 contrib/debian-installer/binary-ppc64el/Release d41d8cd98f00b204e9800998ecf8427e 0 contrib/debian-installer/binary-s390x/Packages 4a4dd3598707603b3f76a2378a4504aa 20 contrib/debian-installer/binary-s390x/Packages.gz 8dc5aea5b03dff8595f096f9e368e888 32 contrib/debian-installer/binary-s390x/Packages.xz 3fb7e0ec0874144b27fda2c995804d71 119 contrib/debian-installer/binary-s390x/Release fc412a0e8fed50416ae55ca3a34c2654 119152 contrib/dep11/Components-amd64.yml 7473c932902284e9c636636a5ff0587b 15579 contrib/dep11/Components-amd64.yml.gz 751b272121122fce4882d17a9d099c44 13564 contrib/dep11/Components-amd64.yml.xz 49911a9d2f76ed13124c7cff0081266b 113437 contrib/dep11/Components-arm64.yml ee72e145d0e71d94c0d418d36dabfd8c 14251 contrib/dep11/Components-arm64.yml.gz 65f48dc9acec772076e60ce35239703f 12480 contrib/dep11/Components-arm64.yml.xz b1f970bbcdd889ccff5c2646bc2835ba 113437 contrib/dep11/Components-armel.yml d2a414b1147562c0ecfa1aab53fc0260 14029 contrib/dep11/Components-armel.yml.gz b450a677c3a5d4a52d2a0df274c222cf 12524 contrib/dep11/Components-armel.yml.xz 75c6b8bd42fc863caa66c454306c7d39 113437 contrib/dep11/Components-armhf.yml ac52f103d1c493d0f8d8e5662d758f78 14127 contrib/dep11/Components-armhf.yml.gz 80f4310b2d68bf09c7fbba34a0eec794 12480 contrib/dep11/Components-armhf.yml.xz a46b6878a89f45fab86aca68bffe081d 118972 contrib/dep11/Components-i386.yml 751ea67ac68d2e755726b4e9d62ab15e 15566 contrib/dep11/Components-i386.yml.gz 82c956565311c8a7d90bff6e0a226fbe 13560 contrib/dep11/Components-i386.yml.xz 6f822ef8f2c13dc4212ade261b4a8752 113437 contrib/dep11/Components-mips64el.yml a072aab0fb45dab4a6e25295f23e9b5f 14056 contrib/dep11/Components-mips64el.yml.gz e5c2dd7fd785fa1ab66099d7763bd670 12500 contrib/dep11/Components-mips64el.yml.xz 432a29a22c4a782f6edad376f386937f 113437 contrib/dep11/Components-ppc64el.yml b5202b5037949e593060f92290d6f949 14219 contrib/dep11/Components-ppc64el.yml.gz dd92a500c7807091665dbc207c9bef68 12496 contrib/dep11/Components-ppc64el.yml.xz 53c6b87820861b0ed316a88f7542cd76 113437 contrib/dep11/Components-s390x.yml 5a4872d3187bc79418b468890be4b5fe 14050 contrib/dep11/Components-s390x.yml.gz eefb3301e486aedbbbb1d735e2522a00 12488 contrib/dep11/Components-s390x.yml.xz 5d8e37f26e7e15f367751089fa13c876 271360 contrib/dep11/icons-128x128.tar 500b14a4cafa23b9106b402737f863a7 195507 contrib/dep11/icons-128x128.tar.gz d9651fb188be2221d2f583aeba83d8fc 83968 contrib/dep11/icons-48x48.tar 6b5ea4675ad78554aaa53b344f1bd146 47168 contrib/dep11/icons-48x48.tar.gz 7115d3a3d41fc9bca9cfcc3c608bebf2 138752 contrib/dep11/icons-64x64.tar c839e679f1d60d294d39884d0911e514 93294 contrib/dep11/icons-64x64.tar.gz 9caa84f2a99d36979adac344a896388d 192117 contrib/i18n/Translation-en 3d985d06545615094278a5ea1624f71c 46873 contrib/i18n/Translation-en.bz2 34e06d968a1c720bb3ae11b6d4fd97e8 120 contrib/source/Release 2ad2c751590464319eb4f0457c959e80 176962 contrib/source/Sources fb3fb3c96e5f62631286f4c2026424f0 51034 contrib/source/Sources.gz d4e86f216d10473e3dfe1b5d060c2c6c 42992 contrib/source/Sources.xz 8b755568ccf76e2a72d8d5af7c50d8b5 474533023 main/Contents-all 569754462a5d0a550c13914762d3d64f 30914666 main/Contents-all.gz 227e24c173bf6b505047cc367300f3d5 128441543 main/Contents-amd64 9c2a335eb3386e062add48568afcd563 10220624 main/Contents-amd64.gz c522d13c470f06b4fe3b946023051c7f 121824568 main/Contents-arm64 a978db9b9bdc407212d4f1cadabb5fba 9787793 main/Contents-arm64.gz 82aa8e5b3f258258c6d58de6bb366e13 104161855 main/Contents-armel dd5104b0f9f751c23cae0964d678edde 8658719 main/Contents-armel.gz 338129c0c4d9847b7cc70d07559a6f21 113081422 main/Contents-armhf e86945e3af92e830379e4f1db8bd0798 9261228 main/Contents-armhf.gz 90c94f5eef03fc5f7239526abee7c9a3 128473082 main/Contents-i386 6d905e4d10bef5910698b0d38f346169 10158425 main/Contents-i386.gz 0b818db942f0d1794da7badfa1255de0 110576495 main/Contents-mips64el 66f35e8181219e6c519ebebc703da1f7 9000146 main/Contents-mips64el.gz 6f2b4800ece15a02d336e6e529e1ec01 112069415 main/Contents-mipsel 6dc989cd32495bde2bc30190c59ac42e 9137554 main/Contents-mipsel.gz 71909495a0f762c240e94bbbb575959f 115472952 main/Contents-ppc64el e259e856306fce286c021e42ee435c75 9307164 main/Contents-ppc64el.gz fe0ab51a776dc25a6c4cd86d45ca6b10 103114399 main/Contents-s390x 558ed84cd52bee83d87b16a801ceeeab 8665807 main/Contents-s390x.gz 067a941e75a988ec416e359f386403c6 674687728 main/Contents-source 65fe784d1e8430e8b56cc4566be9cddc 72485245 main/Contents-source.gz 1f4bf598c355a2bbb0c8ddf889d9636e 157382 main/Contents-udeb-all 708ed31f29f9daf4c980b7abdd66c356 13516 main/Contents-udeb-all.gz 060ac52da882923e9728a2a2df479030 475045 main/Contents-udeb-amd64 a0fe8fc6f3e9d3450fab41b41c53ca73 36105 main/Contents-udeb-amd64.gz 9a494af48f1646356c8cdb2c28666ab5 506049 main/Contents-udeb-arm64 0230809d3b4f2740446630b92b88506a 37929 main/Contents-udeb-arm64.gz d8b249794ce65f95913352b0f357cf6f 322291 main/Contents-udeb-armel 3a6749e6bed21a46c2720ca87cfce86b 25681 main/Contents-udeb-armel.gz 6565fb500615e1b81c2a2c6a38fbc3fc 576604 main/Contents-udeb-armhf 05f5aacfb865ce3f7189c7765c6c4194 42929 main/Contents-udeb-armhf.gz b07775513ebbea83b30f5b59ebcda659 747165 main/Contents-udeb-i386 35686829e3145f133c945bc50a280a13 54484 main/Contents-udeb-i386.gz 7b9c431aee4abffc304b666f04290822 756385 main/Contents-udeb-mips64el e46319707c1cf800d25da453a7818ec4 52547 main/Contents-udeb-mips64el.gz 0f801a530d4179bb19fc536904618bd7 756067 main/Contents-udeb-mipsel 1293c1c2bfc2bf4b21f2c628ea956317 53066 main/Contents-udeb-mipsel.gz 2e60bc4a6075a31dc67fa22fb5d06d67 400327 main/Contents-udeb-ppc64el df3583de6e70825d7e9dc70436f34f7e 29807 main/Contents-udeb-ppc64el.gz a9a72668bbb2331e5bf247698a6f516b 258049 main/Contents-udeb-s390x b4cd072b284a26a7ea847119c41d4efd 20968 main/Contents-udeb-s390x.gz ec0b0e53c7a636c38e09e3202c50ed79 20355932 main/binary-all/Packages d27d4a47fac8c3f74ea29a69191212f3 5199413 main/binary-all/Packages.gz a8e02d5e683c0378422dd66ce8ed4e4f 3913044 main/binary-all/Packages.xz d1b84fffa9645c185d16cc7467e165bd 114 main/binary-all/Release 1f260feb829c23c75221035d3c14aa19 45491563 main/binary-amd64/Packages 3ef2e609e7ac7df6ad78ac9b3b471dc9 11091682 main/binary-amd64/Packages.gz 7d21c4a18942dd8979c0436e8c6f09e2 8183088 main/binary-amd64/Packages.xz 01e129c4000148386d9550627c855706 116 main/binary-amd64/Release dc8423ca5ae5afa23d065fae14d80a9c 44774341 main/binary-arm64/Packages 5b14446f8c7c203e22b909a9e9145ff2 10936186 main/binary-arm64/Packages.gz b21b113b5c6ad95d02edcce24fd4b1cc 8069960 main/binary-arm64/Packages.xz 020d23a0b4a8df334c5e35c9994c50f8 116 main/binary-arm64/Release 0691bc109a9674dc759273b566d8f275 43239670 main/binary-armel/Packages 55ad3b311a97e94889f1069309c173c4 10666338 main/binary-armel/Packages.gz 715211939c5b4c408bc7a9fde54e5fff 7864756 main/binary-armel/Packages.xz 7badbc5064fc85496caff9046eea179e 116 main/binary-armel/Release f4f800ed035c4ceee6628983d3273f69 43804389 main/binary-armhf/Packages 2a7c0be7d3b07c28b7fc30619f407765 10771139 main/binary-armhf/Packages.gz d14427639a057c225592c99691c58332 7943076 main/binary-armhf/Packages.xz f0b380abc985fe4df280bffd6a5ee0b4 116 main/binary-armhf/Release 3735639ab225ffbfd153327a392c9a17 45052618 main/binary-i386/Packages 31c06e0c84e9cbdc9686b02f9beb494d 11007596 main/binary-i386/Packages.gz 08d1456cd41a9a0543982002a7ec0377 8120844 main/binary-i386/Packages.xz df6b6978d6c89f9a0404b455ebeed2ac 115 main/binary-i386/Release cdf438ea56544bf627ae974f4c655751 43695918 main/binary-mips64el/Packages e22a8bdcab5c8cf14d9dc1155aca6264 10718907 main/binary-mips64el/Packages.gz cfe56a727e10a974d2f03b1d2f49dbc6 7905640 main/binary-mips64el/Packages.xz 49c9d3d865c6843fd22c299b8559f008 119 main/binary-mips64el/Release ecd5a7971e9e240e4c0065324eb2e9dc 43633475 main/binary-mipsel/Packages 843a98244001550738e107be05b31bb8 10723008 main/binary-mipsel/Packages.gz cdcc6d200c835d68196c607f63d51e85 7906536 main/binary-mipsel/Packages.xz 49ae2d3f61a33f4a280a79e8428c98f5 117 main/binary-mipsel/Release 3813d274c6bc15b47e894fd6279e8281 44621124 main/binary-ppc64el/Packages 22e72ae699ac32f187e0a18a976eb0a4 10880728 main/binary-ppc64el/Packages.gz 6908851e664fa6ab3f814c430a79acae 8029696 main/binary-ppc64el/Packages.xz b4e68cc523c7a7e2f1fc267d8da329a7 118 main/binary-ppc64el/Release a1b926d316b6b4ff45d8ef9ad39de302 43299770 main/binary-s390x/Packages d02e2e3bb65ad537d7705d1783afd7cb 10684109 main/binary-s390x/Packages.gz 99e001f3dfe0907ff10c320906586c20 7875500 main/binary-s390x/Packages.xz 943ab6afe74db3baca36892849d8d9f1 116 main/binary-s390x/Release 618cd1353362b797eaae20f63d683d9c 61112 main/debian-installer/binary-all/Packages d17362b4b751341876ddd1f1be6dcd2f 16427 main/debian-installer/binary-all/Packages.gz 805491761c952d1691430eef8f41f85d 14652 main/debian-installer/binary-all/Packages.xz d1b84fffa9645c185d16cc7467e165bd 114 main/debian-installer/binary-all/Release ddcb88eb971e580d7c0baf827a3734c3 273058 main/debian-installer/binary-amd64/Packages d9cea985124c828a700aa4f2cce1d4ac 67175 main/debian-installer/binary-amd64/Packages.gz 539f873005b629e948b61690d4965e33 56128 main/debian-installer/binary-amd64/Packages.xz 01e129c4000148386d9550627c855706 116 main/debian-installer/binary-amd64/Release c729a768400d17bd7d801cd5176e2d19 256214 main/debian-installer/binary-arm64/Packages 739d3e07059c8c343a8c9263bfdae293 64136 main/debian-installer/binary-arm64/Packages.gz 2c4e76a470595210595d8ef1d1bef987 53972 main/debian-installer/binary-arm64/Packages.xz 020d23a0b4a8df334c5e35c9994c50f8 116 main/debian-installer/binary-arm64/Release 1f516a1250647956d1b7c81a2d836b13 247408 main/debian-installer/binary-armel/Packages f1dc90be66c3adb56bd825493f323bb8 63426 main/debian-installer/binary-armel/Packages.gz 7c2c2a7f4a1f0d119dc27f13895a34f6 53216 main/debian-installer/binary-armel/Packages.xz 7badbc5064fc85496caff9046eea179e 116 main/debian-installer/binary-armel/Release 31eff9f3147f6ceab4d5bf0f72d98799 250818 main/debian-installer/binary-armhf/Packages 5fe34c4434beb3d9a07553a4933293cc 64303 main/debian-installer/binary-armhf/Packages.gz 3851dd78e750caf0f268e4424c836c58 53892 main/debian-installer/binary-armhf/Packages.xz f0b380abc985fe4df280bffd6a5ee0b4 116 main/debian-installer/binary-armhf/Release 9a4ae9bcd029ccefd3150fd7de85e0aa 347463 main/debian-installer/binary-i386/Packages fe98f2184156f8aeab39b31f3d95dcb4 77236 main/debian-installer/binary-i386/Packages.gz 507624130f546a7346a69c8d9e4ec673 64316 main/debian-installer/binary-i386/Packages.xz df6b6978d6c89f9a0404b455ebeed2ac 115 main/debian-installer/binary-i386/Release 01472f1f451b5f458c81307e94c5c07e 362988 main/debian-installer/binary-mips64el/Packages 93dbc7d45075eccca151477b19ec8f36 78951 main/debian-installer/binary-mips64el/Packages.gz 6624e0a4a124fdbda611bbe6061ac336 66556 main/debian-installer/binary-mips64el/Packages.xz 49c9d3d865c6843fd22c299b8559f008 119 main/debian-installer/binary-mips64el/Release 59672a6b775301332bb47496c960c688 362474 main/debian-installer/binary-mipsel/Packages d398c3b873a8e23c89a6ceed7866a9e6 79930 main/debian-installer/binary-mipsel/Packages.gz d2dc860ef3ecd2b6ef375d7b204a78ce 66756 main/debian-installer/binary-mipsel/Packages.xz 49ae2d3f61a33f4a280a79e8428c98f5 117 main/debian-installer/binary-mipsel/Release 9f1b8d0f08dae5933eb21c6474b0e3fe 255942 main/debian-installer/binary-ppc64el/Packages ab2eddba33c536cbc6469ef03bae0dab 64563 main/debian-installer/binary-ppc64el/Packages.gz 830763f1b814940f3ef749e0b1c92f51 54016 main/debian-installer/binary-ppc64el/Packages.xz b4e68cc523c7a7e2f1fc267d8da329a7 118 main/debian-installer/binary-ppc64el/Release 99bd9f7ed287fb247a72429e8fd92516 225456 main/debian-installer/binary-s390x/Packages 917a905ba59f198c0a12bd3d9fb44942 59861 main/debian-installer/binary-s390x/Packages.gz 4fe2166dac22465c9dc3bf504903c33e 50116 main/debian-installer/binary-s390x/Packages.xz 943ab6afe74db3baca36892849d8d9f1 116 main/debian-installer/binary-s390x/Release 97a6eda13094854f8838218d5869a796 18520413 main/dep11/Components-amd64.yml 9cd807c0b66a8489b5385bf4f343b288 6213469 main/dep11/Components-amd64.yml.gz c16ba02c289510dce9857dfa6cde4550 4048504 main/dep11/Components-amd64.yml.xz 3e8ecb0bbaecb88d0b16dfaa037dba73 18436837 main/dep11/Components-arm64.yml 09ef5a87673c946f916b0d8ef0c2471d 6191092 main/dep11/Components-arm64.yml.gz fef127cee05f3efb96261e78b4fe4568 4033216 main/dep11/Components-arm64.yml.xz 67becc674b536e310fe22492d55c8652 17658848 main/dep11/Components-armel.yml 34cd8a6a1206f804e6d5c54dcdd3ef63 5952269 main/dep11/Components-armel.yml.gz d7cc0222cae53bcfa1de29218fe5cb94 3879744 main/dep11/Components-armel.yml.xz 09010fea4c1cf082bd54aecc24182e45 18205252 main/dep11/Components-armhf.yml f5b7fd1a9cb147fa6b90e60a4d2139c1 6110587 main/dep11/Components-armhf.yml.gz f1f223ca9e69ad1901345ceb404a5666 3983180 main/dep11/Components-armhf.yml.xz ee8f83c597007ab84b58feec05d647fa 18485654 main/dep11/Components-i386.yml 5a6b35ea7b54d88842ab30bbbd469623 6201776 main/dep11/Components-i386.yml.gz 239cc12774e7c2925d1d783faaf01b5d 4041608 main/dep11/Components-i386.yml.xz dd59f50383f269a8e1ec09c49d8a786c 17819116 main/dep11/Components-mips64el.yml e3f03ed2f2c22dac3207e5f3fb98f862 5977494 main/dep11/Components-mips64el.yml.gz 437c9fa1e058fc9a3486fb8b224740f6 3896708 main/dep11/Components-mips64el.yml.xz 09d0cb63fdf4a4904155dc0d56ccc04b 17947079 main/dep11/Components-ppc64el.yml 3d396ef7d8293620c5160a75fda04d39 6023058 main/dep11/Components-ppc64el.yml.gz 23ebc600f44eb4973c351a4a324ba219 3925796 main/dep11/Components-ppc64el.yml.xz 64acc85d1d2ce3e3dc551ae85e80ca57 17735785 main/dep11/Components-s390x.yml b7f851e780c93532c1707895dfa22474 5976062 main/dep11/Components-s390x.yml.gz 117c2f52a672bca008f2c206ad8527a6 3894008 main/dep11/Components-s390x.yml.xz 3f40799bee1a72a060f7dff19efa7b05 13048320 main/dep11/icons-128x128.tar 6ac207d4fb6b76c25dc59edb50c3bf6b 11409337 main/dep11/icons-128x128.tar.gz 66ce5f075d189138824e736123711450 4878336 main/dep11/icons-48x48.tar 260bbc45bfa6b33e31399b4adb3b1f6d 3477622 main/dep11/icons-48x48.tar.gz 47dea6d08e37b4a5154a072f3ad92cf0 9378816 main/dep11/icons-64x64.tar 417f46677b9086f9dd0a425f0f39ee31 7315395 main/dep11/icons-64x64.tar.gz 180389879ed6715b463d05b637e191dc 6191 main/i18n/Translation-ca 8f8b7ffa4659d4f03b65ed28e69821f9 2673 main/i18n/Translation-ca.bz2 b4ef33a20d80c576c7b352e96a86e063 1205166 main/i18n/Translation-cs d70ae6198f35f876b3070d928d5cdba2 323247 main/i18n/Translation-cs.bz2 3fa5a10989da6ec5b19b5b6ba161b0bf 20240560 main/i18n/Translation-da e83f678061ca99aaedd2f20cb75bba77 4411163 main/i18n/Translation-da.bz2 9f5077418506388082a72c7023c56f8f 7801238 main/i18n/Translation-de a57e3821e975f45d21bf2388a190b770 1717951 main/i18n/Translation-de.bz2 a344219bf0eec9139d5270017ecfceee 1347 main/i18n/Translation-de_DE 0fe0725f74bb5249f15f30ce965142d5 830 main/i18n/Translation-de_DE.bz2 87bf9810c05aba15fb4aca6791feb73d 6257 main/i18n/Translation-el 002ddfc4187acd8414873fe9f0a6442a 1835 main/i18n/Translation-el.bz2 be2a4a5c8b4baaa3157784761a91d831 30236299 main/i18n/Translation-en 2ac13fc7b1471b2e722f3dc0992a7b9a 6243067 main/i18n/Translation-en.bz2 0fdd8948881357f49ead0845c7e621c1 2261 main/i18n/Translation-eo 43bd21f8b5d52b955e509e5893eef37e 1196 main/i18n/Translation-eo.bz2 2ad9740f4bf39f163c04bd0b7266c1aa 1325929 main/i18n/Translation-es b4d4140461b4d6195e3337dcf541554f 317946 main/i18n/Translation-es.bz2 2f7f0aac6c4ae5bd9c1499fd612ef996 10093 main/i18n/Translation-eu 3178567e5f21fe43e4cf1f1a38ed6adc 3914 main/i18n/Translation-eu.bz2 d1e71d50a88504d6b48c27960250acae 269212 main/i18n/Translation-fi 9ca11408c191cfc5270f39467ed80f9b 75849 main/i18n/Translation-fi.bz2 945a63eed28af4c45fd5185b334b33b3 11857302 main/i18n/Translation-fr 06100e8db22b6d72d2c466bc85ea117b 2433064 main/i18n/Translation-fr.bz2 f543980d7c6e8335eb0bb5d00b787418 1427 main/i18n/Translation-gl 09c22bb0dfa3874802c4e7e4389f2b58 824 main/i18n/Translation-gl.bz2 363537eb238e19bd527554a2d1de2533 21069 main/i18n/Translation-hr 3fbd3535dcc2e805f0283d54bd38f5f3 4695 main/i18n/Translation-hr.bz2 5393df220c56a4a92b91b2cac6843067 65236 main/i18n/Translation-hu 61236a1bada04fd4ab090269498c5393 22243 main/i18n/Translation-hu.bz2 d8d93a0510fedeb68fbbdae0342520c0 3983 main/i18n/Translation-id 7542ee230bbc1f2f9f873c265b3b467f 1780 main/i18n/Translation-id.bz2 87ba73fdeb9bac4348a4be42b2386f32 24489940 main/i18n/Translation-it 9c9cd08156baf73f9f088bb97ac00662 4844227 main/i18n/Translation-it.bz2 0f39595a0a049759d0d50ead781f73fd 4511401 main/i18n/Translation-ja 74ff41ba40e19c9ceb4c607b122b7811 803966 main/i18n/Translation-ja.bz2 85c4f9ec1e8e2d6faab177ef030ad2aa 11879 main/i18n/Translation-km 46d57c586859cecf5c1a4470f666000d 2371 main/i18n/Translation-km.bz2 def6a2d200b3c67b6a1c497524d0a631 2606190 main/i18n/Translation-ko 3210a7e112a3f29ecf785ba05a78559a 584643 main/i18n/Translation-ko.bz2 d41d8cd98f00b204e9800998ecf8427e 0 main/i18n/Translation-ml 4059d198768f9f8dc9372dc1c54bc3c3 14 main/i18n/Translation-ml.bz2 904af013a9ba73cd72f71a1ca451be5a 1193 main/i18n/Translation-nb bf917a722cf4d90cf2f56acb8edb1b31 738 main/i18n/Translation-nb.bz2 cb57eb70e5645204174caec8edcc4a2b 174332 main/i18n/Translation-nl ad8c86dde21a892ff20203dc71eb981c 47973 main/i18n/Translation-nl.bz2 bc88d84933fd8ae64ea0a7ba32a1e814 2051811 main/i18n/Translation-pl 3095483ca3926b759de515651199283a 491993 main/i18n/Translation-pl.bz2 d1736cf50b7994e7c6ce66962b7f4b03 1074959 main/i18n/Translation-pt 7f9e024af1c410635fc69db5bf5d090a 272186 main/i18n/Translation-pt.bz2 c3453467a749e3888da35949b643835d 3306707 main/i18n/Translation-pt_BR 89726f5a5abac29bd3a6069e27019c9a 802734 main/i18n/Translation-pt_BR.bz2 b50c9c49ea0a9da73b0a76db38a36ea4 1717 main/i18n/Translation-ro 22696f68e30228ffbd84b26dbc821f81 982 main/i18n/Translation-ro.bz2 52035b6ff376a4d7c38eea8bbd406751 3058931 main/i18n/Translation-ru d6c7de740e63ee4ce0e2044a0d449804 494782 main/i18n/Translation-ru.bz2 2b383f6dbb23852965418241eda484de 5984088 main/i18n/Translation-sk 04f2970e8de7fc5a090b84ab700cbb23 1304539 main/i18n/Translation-sk.bz2 cf58326418b53f94289ad593878bfda2 323953 main/i18n/Translation-sr 096b962e3404fbc28ebfb174e7587136 58385 main/i18n/Translation-sr.bz2 366024c5bc4dabb550f8481c2d662611 85612 main/i18n/Translation-sv 22b0c4eaa8e59ee11318ce2e68953f4b 27320 main/i18n/Translation-sv.bz2 ced97abb44ee155f744680871aa5a6e2 14670 main/i18n/Translation-tr 233a8366a334283e9b802cae336ed09b 5362 main/i18n/Translation-tr.bz2 c8840c6e4bbe54b098d5b589e5d9e08b 3740343 main/i18n/Translation-uk 7ed20cfd2585b8f77be6e2bab7561133 576766 main/i18n/Translation-uk.bz2 2adb559c8ab8415644e43781db4f739a 21882 main/i18n/Translation-vi 82caa7c535a1c4c7589a7b1647017f53 6510 main/i18n/Translation-vi.bz2 f895594ce62c202132bbbe9ae32f1bc2 2007 main/i18n/Translation-zh 3d2be55ee5ef9a79e0db9f90acc449cf 1215 main/i18n/Translation-zh.bz2 91e9eec000876a989969a700ac7b3821 425199 main/i18n/Translation-zh_CN ab34838b3553d042d515eb65f5aa8816 113621 main/i18n/Translation-zh_CN.bz2 34208715b80dcbd5fd1b87874a6705d4 39965 main/i18n/Translation-zh_TW 6ed487c9d90ac9866174796ce73dec77 14859 main/i18n/Translation-zh_TW.bz2 34dddb6211549653cea38f999bd0eaac 57705 main/installer-amd64/20210731+deb11u2/images/MD5SUMS a0d6c8bdc66589ad26cf20ed3b976588 77333 main/installer-amd64/20210731+deb11u2/images/SHA256SUMS 8521cd018a0e0b50238dab3cf673c4f7 57705 main/installer-amd64/20210731/images/MD5SUMS bb4d5d5a421f536dcaa3f2e4fc96c1c3 77333 main/installer-amd64/20210731/images/SHA256SUMS 34dddb6211549653cea38f999bd0eaac 57705 main/installer-amd64/current/images/MD5SUMS a0d6c8bdc66589ad26cf20ed3b976588 77333 main/installer-amd64/current/images/SHA256SUMS 2cd6720aca4231292b2d0ccc51e4ff14 68403 main/installer-arm64/20210731+deb11u2/images/MD5SUMS 24500973bd4090ea3caec1e3cc5e098d 93279 main/installer-arm64/20210731+deb11u2/images/SHA256SUMS 8544dac6e811bff5ed42e276cf530ebf 68403 main/installer-arm64/20210731/images/MD5SUMS 7989c6f2e37aeda05d7dfc58de88d7f5 93279 main/installer-arm64/20210731/images/SHA256SUMS 2cd6720aca4231292b2d0ccc51e4ff14 68403 main/installer-arm64/current/images/MD5SUMS 24500973bd4090ea3caec1e3cc5e098d 93279 main/installer-arm64/current/images/SHA256SUMS 5e84bb36b4d2709f659d816c8b808dc1 20183 main/installer-armel/20210731+deb11u2/images/MD5SUMS 1275b1791193ffaf0423882ac1ab8910 28195 main/installer-armel/20210731+deb11u2/images/SHA256SUMS 6e3afe07880cea11cee1a8ac19ce5d13 20182 main/installer-armel/20210731/images/MD5SUMS 350c18339820cfa3989e1297c80b9f12 28194 main/installer-armel/20210731/images/SHA256SUMS 5e84bb36b4d2709f659d816c8b808dc1 20183 main/installer-armel/current/images/MD5SUMS 1275b1791193ffaf0423882ac1ab8910 28195 main/installer-armel/current/images/SHA256SUMS 7e0f1f755b251475de78ed837ee9ed94 64240 main/installer-armhf/20210731+deb11u2/images/MD5SUMS 2110f18464978e01a845cac6d1902ea7 92476 main/installer-armhf/20210731+deb11u2/images/SHA256SUMS 3dca9930d681a0ba4186171684027ec6 64240 main/installer-armhf/20210731/images/MD5SUMS 869454c4efa0fcddd91e08ab8ccf9d3b 92476 main/installer-armhf/20210731/images/SHA256SUMS 7e0f1f755b251475de78ed837ee9ed94 64240 main/installer-armhf/current/images/MD5SUMS 2110f18464978e01a845cac6d1902ea7 92476 main/installer-armhf/current/images/SHA256SUMS 22bfa7148b25f6e41956a175db05475d 56286 main/installer-i386/20210731+deb11u2/images/MD5SUMS aec2805043f71f246332b18d30786457 75978 main/installer-i386/20210731+deb11u2/images/SHA256SUMS 8932831dfc7fb479ada48f6936639179 56286 main/installer-i386/20210731/images/MD5SUMS 0ccfb273991e3302a49093743aa9032f 75978 main/installer-i386/20210731/images/SHA256SUMS 22bfa7148b25f6e41956a175db05475d 56286 main/installer-i386/current/images/MD5SUMS aec2805043f71f246332b18d30786457 75978 main/installer-i386/current/images/SHA256SUMS f629b26b70e56ed960eb8a27ff98f4b7 630 main/installer-mips64el/20210731+deb11u2/images/MD5SUMS fd5bf852602213eb9ae219860eda85a9 1026 main/installer-mips64el/20210731+deb11u2/images/SHA256SUMS 9533fc15e5b64180b5ad78129a5230b2 627 main/installer-mips64el/20210731/images/MD5SUMS a776640760fbaacfb1681f3abd0fb40b 1023 main/installer-mips64el/20210731/images/SHA256SUMS f629b26b70e56ed960eb8a27ff98f4b7 630 main/installer-mips64el/current/images/MD5SUMS fd5bf852602213eb9ae219860eda85a9 1026 main/installer-mips64el/current/images/SHA256SUMS d8e4fd64c23486d2c885f60a8300353a 630 main/installer-mipsel/20210731+deb11u2/images/MD5SUMS 96b3c3e2c8a4afa3984ae968ddc15c68 1026 main/installer-mipsel/20210731+deb11u2/images/SHA256SUMS c3a9b6724a2ff5e2abf741f47a7600da 627 main/installer-mipsel/20210731/images/MD5SUMS 01da3e1833ca954309023210e9b16159 1023 main/installer-mipsel/20210731/images/SHA256SUMS d8e4fd64c23486d2c885f60a8300353a 630 main/installer-mipsel/current/images/MD5SUMS 96b3c3e2c8a4afa3984ae968ddc15c68 1026 main/installer-mipsel/current/images/SHA256SUMS 3897fc1cba928ab8995103a47c5ea0d4 576 main/installer-ppc64el/20210731+deb11u2/images/MD5SUMS 110240d69a405f35331e484d1caf69dd 972 main/installer-ppc64el/20210731+deb11u2/images/SHA256SUMS 37515f49026f1bc4682fefba24e9decf 576 main/installer-ppc64el/20210731/images/MD5SUMS 89c70369e7ab670f721a135f055d81a4 972 main/installer-ppc64el/20210731/images/SHA256SUMS 3897fc1cba928ab8995103a47c5ea0d4 576 main/installer-ppc64el/current/images/MD5SUMS 110240d69a405f35331e484d1caf69dd 972 main/installer-ppc64el/current/images/SHA256SUMS 877b9e7ac72593a38b9c7fa172bad283 374 main/installer-s390x/20210731+deb11u2/images/MD5SUMS 64fe36974e1fc81da11af2963eb2ae45 674 main/installer-s390x/20210731+deb11u2/images/SHA256SUMS 580b19117c2b6c6f2a8ad8aca5132826 374 main/installer-s390x/20210731/images/MD5SUMS da16ad53b0185c6e48397e05f2efadfc 674 main/installer-s390x/20210731/images/SHA256SUMS 877b9e7ac72593a38b9c7fa172bad283 374 main/installer-s390x/current/images/MD5SUMS 64fe36974e1fc81da11af2963eb2ae45 674 main/installer-s390x/current/images/SHA256SUMS e2eccfcb7d63452ef771118cfcde7471 117 main/source/Release d212aa1cad6e41b8935018fe1245e246 44587617 main/source/Sources d3e0b858498539b30daeed3f4e9b7080 11422998 main/source/Sources.gz 3a9bea0b782b89f7ca56ac3cae52dcf0 8626200 main/source/Sources.xz 5f624011d3b0a82f23445c2861deac99 17347341 non-free/Contents-all c64dcd5c2b4db85f729afa8623adb65a 888157 non-free/Contents-all.gz 518ecaf02e30ae0154fa8571720ec601 1012087 non-free/Contents-amd64 ad1187679dc2bfae989e2a616c1d1f43 75128 non-free/Contents-amd64.gz c4836acef58c9d8bb5610ccacf8f090e 421263 non-free/Contents-arm64 34f5639118ed6ce7e40fb9926e669c59 33327 non-free/Contents-arm64.gz f408ea79e9570389d5ee84acf709fefe 95417 non-free/Contents-armel b7a69ebcb774fa413e4016bb93c3d044 9298 non-free/Contents-armel.gz 997a3ab6fd5b0950d271943661be7b75 145256 non-free/Contents-armhf 2527018128f47a5d42e34e46d61fa895 13289 non-free/Contents-armhf.gz 4b27e7ff1532e2ac0723f2f355f7e066 323026 non-free/Contents-i386 e1908df5fc50473b60b02fbe9da0d177 28131 non-free/Contents-i386.gz 900df746b6e7accfd8883d31c7d28313 91215 non-free/Contents-mips64el 7c382180d55972ff768bb8a05222a412 8686 non-free/Contents-mips64el.gz 904ab7d197244bdfdbf6b58bc61d09ac 92244 non-free/Contents-mipsel 73868036dab5f62f60ad63ebfb7ca253 9026 non-free/Contents-mipsel.gz cc5fc574cc14fb2db6f5beaa1cecf606 638773 non-free/Contents-ppc64el e2a679bd7e206bc8f80c6834fe619f5e 45603 non-free/Contents-ppc64el.gz f3aa91e39f1d170310ec9820ea4dae2d 74537 non-free/Contents-s390x 2b363c4c14b66b56f3009f85c29415dc 7407 non-free/Contents-s390x.gz 324f4e2ea8196afd0c7877c7be1297ef 10789196 non-free/Contents-source b46a78e117cd9c87f952b4408d863ed3 1062657 non-free/Contents-source.gz d41d8cd98f00b204e9800998ecf8427e 0 non-free/Contents-udeb-all 4a4dd3598707603b3f76a2378a4504aa 20 non-free/Contents-udeb-all.gz d41d8cd98f00b204e9800998ecf8427e 0 non-free/Contents-udeb-amd64 4a4dd3598707603b3f76a2378a4504aa 20 non-free/Contents-udeb-amd64.gz d41d8cd98f00b204e9800998ecf8427e 0 non-free/Contents-udeb-arm64 4a4dd3598707603b3f76a2378a4504aa 20 non-free/Contents-udeb-arm64.gz d41d8cd98f00b204e9800998ecf8427e 0 non-free/Contents-udeb-armel 4a4dd3598707603b3f76a2378a4504aa 20 non-free/Contents-udeb-armel.gz d41d8cd98f00b204e9800998ecf8427e 0 non-free/Contents-udeb-armhf 4a4dd3598707603b3f76a2378a4504aa 20 non-free/Contents-udeb-armhf.gz d41d8cd98f00b204e9800998ecf8427e 0 non-free/Contents-udeb-i386 4a4dd3598707603b3f76a2378a4504aa 20 non-free/Contents-udeb-i386.gz d41d8cd98f00b204e9800998ecf8427e 0 non-free/Contents-udeb-mips64el 4a4dd3598707603b3f76a2378a4504aa 20 non-free/Contents-udeb-mips64el.gz d41d8cd98f00b204e9800998ecf8427e 0 non-free/Contents-udeb-mipsel 4a4dd3598707603b3f76a2378a4504aa 20 non-free/Contents-udeb-mipsel.gz d41d8cd98f00b204e9800998ecf8427e 0 non-free/Contents-udeb-ppc64el 4a4dd3598707603b3f76a2378a4504aa 20 non-free/Contents-udeb-ppc64el.gz d41d8cd98f00b204e9800998ecf8427e 0 non-free/Contents-udeb-s390x 4a4dd3598707603b3f76a2378a4504aa 20 non-free/Contents-udeb-s390x.gz 6f7e63c5d4788682f860b32b8ce97cf4 188965 non-free/binary-all/Packages ab3373bc8f42d6c6b6e381dd87b2f92f 50951 non-free/binary-all/Packages.gz f599433841570a2a9e6b62346410672d 42980 non-free/binary-all/Packages.xz f11c06afeb28ac9fdc190b06a4116b72 118 non-free/binary-all/Release cc32bc802bc2f9efdb3c60dc2e48c9c8 509424 non-free/binary-amd64/Packages 64a2b1c7a1720ead6bfcb7087bfe6001 117560 non-free/binary-amd64/Packages.gz 5504f6ab8a4c29aa47f1fcdc75a9d4e5 93776 non-free/binary-amd64/Packages.xz a943013bc70350fa18757d786f27c859 120 non-free/binary-amd64/Release 3dc871bfee975b65437c8b6906cf2f07 351705 non-free/binary-arm64/Packages 164639debc3b63f65fecfc00490905bb 83410 non-free/binary-arm64/Packages.gz 81d2a6655efa5dda066ee706ba4eaf21 69596 non-free/binary-arm64/Packages.xz f7c0f78f010169122edb4b484bb3c4eb 120 non-free/binary-arm64/Release 2dbe315aee272cb148210e0cbf8fb885 227813 non-free/binary-armel/Packages efdaf4d2d646cf1aeed066f539224fec 61840 non-free/binary-armel/Packages.gz 2a687531cfdb8929641a2e35ff4ae335 51796 non-free/binary-armel/Packages.xz 733756a76f2a7dd20fd071f56b984094 120 non-free/binary-armel/Release 259296e43bbe725dc7add8f6f2b68111 258081 non-free/binary-armhf/Packages f207a5c201ccea79fb80eae64729a7cc 67467 non-free/binary-armhf/Packages.gz ec83d3ef4d05889dd9fdca3b98ed6c53 56264 non-free/binary-armhf/Packages.xz da413c370234cdb69ee6542407ee076d 120 non-free/binary-armhf/Release d277c22f7c904381cec840d8fa6e59b5 398466 non-free/binary-i386/Packages 29abf83c18c1808627856e88ec5a6f61 93068 non-free/binary-i386/Packages.gz 07fb2fe9f670198f0f1a62163874188d 76712 non-free/binary-i386/Packages.xz 936031b80e5fc5de9416ad31e844a16f 119 non-free/binary-i386/Release 3a7a3789a3a32f5bc75f73ea7d4d30b2 225386 non-free/binary-mips64el/Packages e7f810d245d4d86d752db87210d954d2 61033 non-free/binary-mips64el/Packages.gz 9a6a83c9b5f1cb9a33499246f2e23697 51108 non-free/binary-mips64el/Packages.xz 59703ab3cabb5ee0179596114aa97bab 123 non-free/binary-mips64el/Release db28d3db1e54d964f34fc93c1c4d6d5b 226042 non-free/binary-mipsel/Packages f0a5a3755e28ca25e28ffd54d2df954d 61319 non-free/binary-mipsel/Packages.gz 6996e0d76763ab64980f2387ea306bbd 51340 non-free/binary-mipsel/Packages.xz 1a12ed0ba62c535d55240263eebe3bc8 121 non-free/binary-mipsel/Release ea10ee6fc248161ca7dde51a13c4f97e 352636 non-free/binary-ppc64el/Packages a7d91e4ffda3ebaed4a676ad04c96f57 82547 non-free/binary-ppc64el/Packages.gz d9f57ed2c98630339447d30ea65925f1 68732 non-free/binary-ppc64el/Packages.xz 4848a46e1300a374ed81fb50ed6e8c42 122 non-free/binary-ppc64el/Release bfe96d1e8e950194ff8dd3b473c26e41 220450 non-free/binary-s390x/Packages f69df57d8224224277665199579ec4a6 59870 non-free/binary-s390x/Packages.gz 42c4a614851dc7aaa4b2899999c629dd 50188 non-free/binary-s390x/Packages.xz aacfe9fd33530390a45b9dd275715b49 120 non-free/binary-s390x/Release d41d8cd98f00b204e9800998ecf8427e 0 non-free/debian-installer/binary-all/Packages 4a4dd3598707603b3f76a2378a4504aa 20 non-free/debian-installer/binary-all/Packages.gz 8dc5aea5b03dff8595f096f9e368e888 32 non-free/debian-installer/binary-all/Packages.xz f11c06afeb28ac9fdc190b06a4116b72 118 non-free/debian-installer/binary-all/Release d41d8cd98f00b204e9800998ecf8427e 0 non-free/debian-installer/binary-amd64/Packages 4a4dd3598707603b3f76a2378a4504aa 20 non-free/debian-installer/binary-amd64/Packages.gz 8dc5aea5b03dff8595f096f9e368e888 32 non-free/debian-installer/binary-amd64/Packages.xz a943013bc70350fa18757d786f27c859 120 non-free/debian-installer/binary-amd64/Release d41d8cd98f00b204e9800998ecf8427e 0 non-free/debian-installer/binary-arm64/Packages 4a4dd3598707603b3f76a2378a4504aa 20 non-free/debian-installer/binary-arm64/Packages.gz 8dc5aea5b03dff8595f096f9e368e888 32 non-free/debian-installer/binary-arm64/Packages.xz f7c0f78f010169122edb4b484bb3c4eb 120 non-free/debian-installer/binary-arm64/Release d41d8cd98f00b204e9800998ecf8427e 0 non-free/debian-installer/binary-armel/Packages 4a4dd3598707603b3f76a2378a4504aa 20 non-free/debian-installer/binary-armel/Packages.gz 8dc5aea5b03dff8595f096f9e368e888 32 non-free/debian-installer/binary-armel/Packages.xz 733756a76f2a7dd20fd071f56b984094 120 non-free/debian-installer/binary-armel/Release d41d8cd98f00b204e9800998ecf8427e 0 non-free/debian-installer/binary-armhf/Packages 4a4dd3598707603b3f76a2378a4504aa 20 non-free/debian-installer/binary-armhf/Packages.gz 8dc5aea5b03dff8595f096f9e368e888 32 non-free/debian-installer/binary-armhf/Packages.xz da413c370234cdb69ee6542407ee076d 120 non-free/debian-installer/binary-armhf/Release d41d8cd98f00b204e9800998ecf8427e 0 non-free/debian-installer/binary-i386/Packages 4a4dd3598707603b3f76a2378a4504aa 20 non-free/debian-installer/binary-i386/Packages.gz 8dc5aea5b03dff8595f096f9e368e888 32 non-free/debian-installer/binary-i386/Packages.xz 936031b80e5fc5de9416ad31e844a16f 119 non-free/debian-installer/binary-i386/Release d41d8cd98f00b204e9800998ecf8427e 0 non-free/debian-installer/binary-mips64el/Packages 4a4dd3598707603b3f76a2378a4504aa 20 non-free/debian-installer/binary-mips64el/Packages.gz 8dc5aea5b03dff8595f096f9e368e888 32 non-free/debian-installer/binary-mips64el/Packages.xz 59703ab3cabb5ee0179596114aa97bab 123 non-free/debian-installer/binary-mips64el/Release d41d8cd98f00b204e9800998ecf8427e 0 non-free/debian-installer/binary-mipsel/Packages 4a4dd3598707603b3f76a2378a4504aa 20 non-free/debian-installer/binary-mipsel/Packages.gz 8dc5aea5b03dff8595f096f9e368e888 32 non-free/debian-installer/binary-mipsel/Packages.xz 1a12ed0ba62c535d55240263eebe3bc8 121 non-free/debian-installer/binary-mipsel/Release d41d8cd98f00b204e9800998ecf8427e 0 non-free/debian-installer/binary-ppc64el/Packages 4a4dd3598707603b3f76a2378a4504aa 20 non-free/debian-installer/binary-ppc64el/Packages.gz 8dc5aea5b03dff8595f096f9e368e888 32 non-free/debian-installer/binary-ppc64el/Packages.xz 4848a46e1300a374ed81fb50ed6e8c42 122 non-free/debian-installer/binary-ppc64el/Release d41d8cd98f00b204e9800998ecf8427e 0 non-free/debian-installer/binary-s390x/Packages 4a4dd3598707603b3f76a2378a4504aa 20 non-free/debian-installer/binary-s390x/Packages.gz 8dc5aea5b03dff8595f096f9e368e888 32 non-free/debian-installer/binary-s390x/Packages.xz aacfe9fd33530390a45b9dd275715b49 120 non-free/debian-installer/binary-s390x/Release f7208886e345a2c1c5681b7bc1f891f3 278293 non-free/dep11/Components-amd64.yml ab8bcc71919bb29e6a367d9058dc0125 29634 non-free/dep11/Components-amd64.yml.gz afd21b4c476c6b604c4f998d90383234 17904 non-free/dep11/Components-amd64.yml.xz 71e3cebf69c369e3d4e6b64e48fe037b 271451 non-free/dep11/Components-arm64.yml 4b40bf8ff6579f425fd308cc4f32bb26 27686 non-free/dep11/Components-arm64.yml.gz 04fa2b6c4dc8d23f6ee6334754b725df 16392 non-free/dep11/Components-arm64.yml.xz 678290cc20fe4c69fac625c25f48577f 271451 non-free/dep11/Components-armel.yml b76376c24cdd9bb014e63503830766f8 27606 non-free/dep11/Components-armel.yml.gz b431acc1b0f700a021a3ab1305bc3c33 16448 non-free/dep11/Components-armel.yml.xz 7f659804cad02381ed7735779c211771 271451 non-free/dep11/Components-armhf.yml 0221ab3c0654617c6de5d2b74eac7b15 27691 non-free/dep11/Components-armhf.yml.gz 2df1dfb4d502d5c01f744bac99e8a0bc 16364 non-free/dep11/Components-armhf.yml.xz 1422b7cb028418049315374e46dcbf86 280613 non-free/dep11/Components-i386.yml 7a014ddef58173efeb07ce9d7b866331 31098 non-free/dep11/Components-i386.yml.gz ee2f702d30a2274d969a8e9044da54f2 19156 non-free/dep11/Components-i386.yml.xz 2f39022b38ebd28b86acd148ad0389d2 271451 non-free/dep11/Components-mips64el.yml 5e839450348a20fc9f81cdc9dd0b9663 27765 non-free/dep11/Components-mips64el.yml.gz fbf40f634081acbde994e89d8731d159 16380 non-free/dep11/Components-mips64el.yml.xz 4ff7e301bb5eaab539783f39c24b421f 271451 non-free/dep11/Components-ppc64el.yml d7c37af104343f2eb2b10a0980c96661 27592 non-free/dep11/Components-ppc64el.yml.gz afabe491b91df1be19287ea4e978e7aa 16576 non-free/dep11/Components-ppc64el.yml.xz 05dc5f141a7ca96f1aae6d571dd37361 271451 non-free/dep11/Components-s390x.yml 4a5b9e250991cd5d661db03f4bebefa8 27558 non-free/dep11/Components-s390x.yml.gz b0593a88d870f066f1a83dfb382e09c5 16356 non-free/dep11/Components-s390x.yml.xz 40dd67e0e1f81416405be5c0dc8ee47e 8192 non-free/dep11/icons-128x128.tar b117213e4fd39f9c75c1699ebaf3d610 2394 non-free/dep11/icons-128x128.tar.gz 08a465949d80332d065e6f4ec8459930 4096 non-free/dep11/icons-48x48.tar 49466a3c36fe0d0cbb5940896da60960 741 non-free/dep11/icons-48x48.tar.gz 5d6e61a41610797276e5b6f16d60f7e1 36864 non-free/dep11/icons-64x64.tar 0196f7b979db4111a6d9b988e63101a0 27667 non-free/dep11/icons-64x64.tar.gz f96d3feb24e65c9c2bede004188e593c 547848 non-free/i18n/Translation-en d4610fd70b408a3a59916caa15532e35 91490 non-free/i18n/Translation-en.bz2 88183c4a67637203d4836a08afc45c8b 121 non-free/source/Release 5888394fb12099ee969dba5caabba1d5 351318 non-free/source/Sources d8c4e0237a6eb7c981703a1726d1c769 96687 non-free/source/Sources.gz e3830f6fc5a946b5a5b46e8277e1d86f 80488 non-free/source/Sources.xz SHA256: 3957f28db16e3f28c7b34ae84f1c929c567de6970f3f1b95dac9b498dd80fe63 738242 contrib/Contents-all 3e9a121d599b56c08bc8f144e4830807c77c29d7114316d6984ba54695d3db7b 57319 contrib/Contents-all.gz 425a90016c3b1b64fd560b0f4524d53d5b3ee3aa0835859408e8413aa1145dc9 786460 contrib/Contents-amd64 1c336a418784bb0eb78318bab337b4df34b34e56683e3a7887f319a2a8985c6b 54567 contrib/Contents-amd64.gz d1301db9f59f4baf78398a8e123e76088b9963b612274e030e8c1d52720e0151 370054 contrib/Contents-arm64 1a01ec345569da804ff98ed259b2135845130a30e434909c4e69c88bf9cb8d9a 29661 contrib/Contents-arm64.gz b4985377d670dbc4ab9bf0f7fb15d11b100c442050dee7c1e9203d3f0cfd3f37 359292 contrib/Contents-armel f134666bc09535cbc917f63022ea31613da15ec3c0ce1c664981ace325acdd6a 28039 contrib/Contents-armel.gz b5363d1e3ec276a0cb10bc16685bd02bdc330719d76c275bebd344adaa91583b 367655 contrib/Contents-armhf fc4edd280f2b254dbfa98f495e5f4ca6047ec9a1539ccb8754a1f93546ea32b5 29236 contrib/Contents-armhf.gz 77d465435ba8f5bad03b76624835f91e9ebf3bb09b124ab1a06e70c8b2629b30 407328 contrib/Contents-i386 e4a82b31ac7b5b139fd3bd93ad466de75f7bf7d54410967253044895e41c36fb 33556 contrib/Contents-i386.gz c0efa60eaa3b47bd93ca71220c6fc734d54b257e16bb6dd8dde43ca722f242dc 359402 contrib/Contents-mips64el 4fccf5298ef664c2de3dc7eeb203eefa3bf8ec82b95b1c696b856a43af35e395 27962 contrib/Contents-mips64el.gz db2388b4b8d300fdc265fe064288a8de5f69958b06ed6cfeff3b8528e719015b 360549 contrib/Contents-mipsel 27db69688406433748363f4a70cac108f29b99555a6d5dc3eaba6b2e8b526dfc 27942 contrib/Contents-mipsel.gz 8c3c6314aad0eb54fd90fc0b63de426efa2b0f4c0f8c792301701466ed428680 369164 contrib/Contents-ppc64el 2b33fa8e8fed52eb5edb543bcd89f23f1bfefea7f2c3c43b4ce04b414f56f34a 29322 contrib/Contents-ppc64el.gz bb1fdc3fafd28760f57d951e96a150e8ec7d6b0fb75443de93f08a61ffbd7042 357860 contrib/Contents-s390x 009373ff8cde80de63a4303b8c6eab79af34d6c2c0c831d1b38e1f9329c396cc 27518 contrib/Contents-s390x.gz 4b86e3a94d4dd9eafaa5db0035909e7e613969fa7be681d412c40bd538ddb6f4 6714742 contrib/Contents-source 45fc40b7c482cac887804424c8cfea9229dd9fe98f1af11c65c0855573e98893 470120 contrib/Contents-source.gz e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 contrib/Contents-udeb-all f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 contrib/Contents-udeb-all.gz e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 contrib/Contents-udeb-amd64 f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 contrib/Contents-udeb-amd64.gz e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 contrib/Contents-udeb-arm64 f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 contrib/Contents-udeb-arm64.gz e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 contrib/Contents-udeb-armel f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 contrib/Contents-udeb-armel.gz e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 contrib/Contents-udeb-armhf f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 contrib/Contents-udeb-armhf.gz e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 contrib/Contents-udeb-i386 f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 contrib/Contents-udeb-i386.gz e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 contrib/Contents-udeb-mips64el f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 contrib/Contents-udeb-mips64el.gz e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 contrib/Contents-udeb-mipsel f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 contrib/Contents-udeb-mipsel.gz e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 contrib/Contents-udeb-ppc64el f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 contrib/Contents-udeb-ppc64el.gz e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 contrib/Contents-udeb-s390x f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 contrib/Contents-udeb-s390x.gz 48cfe101cd84f16baf720b99e8f2ff89fd7e063553966d8536b472677acb82f0 103223 contrib/binary-all/Packages 86057fcd3eff667ec8e3fbabb2a75e229f5e99f39ace67ff0db4a8509d0707e4 27334 contrib/binary-all/Packages.gz 706c840235798e098d4d6013d1dabbc967f894d0ffa02c92ac959dcea85ddf54 23912 contrib/binary-all/Packages.xz cad9dc69f88bb6131006acb101e4f4ea99d6f07c9b87cd518972e58651b7dc38 117 contrib/binary-all/Release 81076d23927be8e29244fed341860e9a1ac6469956f211f8a673238f02f97f7d 230485 contrib/binary-amd64/Packages 1acba5fb54960c46ce5861d2c88cc1d1d68f7feb2b259c7cef2aa06199b98c90 60785 contrib/binary-amd64/Packages.gz 104237b66d5612b700de4a80322d330a2e6b112d0bad0a961801a51c67351a11 50468 contrib/binary-amd64/Packages.xz 92b3c047edd4228962915872f7968bee427f81ca06715c69b2ec75929a152bb0 119 contrib/binary-amd64/Release 0dfd69e15b404ddf2bba8790dad3f35b74146a805b1e8d2e9887d9a48947c3e5 179482 contrib/binary-arm64/Packages e0036178cde86d8da40e1cebac138516be3a9120737cabcb1efd91c935afbaac 48744 contrib/binary-arm64/Packages.gz ef98448026c8bbf88adb9d02f258a11fa0affa6e065911d9f7c37530dc4d1384 40828 contrib/binary-arm64/Packages.xz 06fac0a1ba55e8429d512c99d01dcd0d12e98b58a69f32f733b0f0a62530ba11 119 contrib/binary-arm64/Release 0908770fd3c4157b688ed187b310e904942a93fbb4ee45a81bd601cd5d791556 163026 contrib/binary-armel/Packages 3943eab9ed85c25720580d874a13ac25a865ad7f3659b6b0ed4fecec0e1c869a 44492 contrib/binary-armel/Packages.gz 4c6bb6ca95e5c429e8e2e921097050af275436a988c33b2b51ddd57ecc94e2e2 37452 contrib/binary-armel/Packages.xz cc63bee1bf91fb2eb4c72a08ff99f91800ba1cf4ad10377541e1aef51c7538b4 119 contrib/binary-armel/Release 418c6075687ec9bdd5d5848f7390d4af0b0003979fae2d78aa660d2c1407bb8d 175400 contrib/binary-armhf/Packages 9a146a79865bf8982bc6610e2b6392f5b04a8deff552e1f291b2445014eada70 47754 contrib/binary-armhf/Packages.gz 77f7f774bf71c5f31dbcd0f4d2192935e44d9b111a16e67cd4080084838fdce3 40212 contrib/binary-armhf/Packages.xz 6014f6298d74235879217ede53ce7a825e3b2ea31e30c76a829305d71c655ffd 119 contrib/binary-armhf/Release 4107cb6821c45ece9cfff961b0e7150137622d65e9ec42d1aae5828ec3477dbf 203348 contrib/binary-i386/Packages 97026e543f5c3a854269170edbaa15d0c0de3e1c8af47fc3a82dcbf9a5757232 54188 contrib/binary-i386/Packages.gz 2a6d5d0c4a6b80441f8dd4ec83c2f5c495baa7b93fcd639ff4f4b12ea50a0071 45328 contrib/binary-i386/Packages.xz a4a8901530290206e6ad553ae894fb5899803e5118f50d9e81e4e19bb2990317 118 contrib/binary-i386/Release a774275057dee6bf6b8d38ec3b87af146f51f087d3768fa15b231f227829d2c8 163491 contrib/binary-mips64el/Packages 7c96ebafaeb2714dc8ef66765e7ab6cfcaf3714fccb8336dd34483da2bbbb94e 44721 contrib/binary-mips64el/Packages.gz 7a6a530bed2148a2dab410cb99686ac9a039260445b4056ec118b67780ae7a8b 37488 contrib/binary-mips64el/Packages.xz 660eb18c501c7914c707a601eda8b335ef47d97ec6627eec9b4176f397fbca9f 122 contrib/binary-mips64el/Release 6b45afcd8100bcd4d81518dd1eb1c5cd4b870148d6a7f3edf187ef9409203d9e 164631 contrib/binary-mipsel/Packages bb743f30604806739a9a962b8f8b291d5ebb1fea9833640a48f56d0cbdb70ec9 44755 contrib/binary-mipsel/Packages.gz 8decfb2bcac88b1ca7181a17279e02f2d2a4d7581c4471b174a6746039750810 37824 contrib/binary-mipsel/Packages.xz b330344b49f86a63fc923cff90cb000f0c7b31bfc5d8874c73b3dcb12757d45a 120 contrib/binary-mipsel/Release 00762b5332be85a172449085dad932a8c089409e80a056dd56d686ac2ea35a6b 179037 contrib/binary-ppc64el/Packages 18b7b64d14b0ce04c57f8310f66752862702d9c7e81714cdb667bebd1c8dcd38 48655 contrib/binary-ppc64el/Packages.gz 0605304f3154d1077051b1ad192b52cb20de527b814dd58e45989a20c84e8670 40668 contrib/binary-ppc64el/Packages.xz 39ba90ca6578afaff1df87f41cbe3b622bba9f50c9180f1d71a62e20c920ed99 121 contrib/binary-ppc64el/Release 625cad7d565401d78a58bba81631fd5562078decc19478d429ed041ac62db13e 162234 contrib/binary-s390x/Packages 4932972516970fc3bf7bb4a291efeac9d0a8871fcf82f042ab8f4fe7bff0c880 44289 contrib/binary-s390x/Packages.gz fdc3b18ba701f5bfba1df2c94d90d28ba6c05b92cc6b896e6f56212f5cd3cc20 37248 contrib/binary-s390x/Packages.xz d05cb182ffb8d64b588a430936bcacdbe97524f36758d20bdc96d2275bcb4bac 119 contrib/binary-s390x/Release e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 contrib/debian-installer/binary-all/Packages f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 contrib/debian-installer/binary-all/Packages.gz 0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 contrib/debian-installer/binary-all/Packages.xz cad9dc69f88bb6131006acb101e4f4ea99d6f07c9b87cd518972e58651b7dc38 117 contrib/debian-installer/binary-all/Release e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 contrib/debian-installer/binary-amd64/Packages f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 contrib/debian-installer/binary-amd64/Packages.gz 0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 contrib/debian-installer/binary-amd64/Packages.xz 92b3c047edd4228962915872f7968bee427f81ca06715c69b2ec75929a152bb0 119 contrib/debian-installer/binary-amd64/Release e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 contrib/debian-installer/binary-arm64/Packages f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 contrib/debian-installer/binary-arm64/Packages.gz 0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 contrib/debian-installer/binary-arm64/Packages.xz 06fac0a1ba55e8429d512c99d01dcd0d12e98b58a69f32f733b0f0a62530ba11 119 contrib/debian-installer/binary-arm64/Release e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 contrib/debian-installer/binary-armel/Packages f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 contrib/debian-installer/binary-armel/Packages.gz 0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 contrib/debian-installer/binary-armel/Packages.xz cc63bee1bf91fb2eb4c72a08ff99f91800ba1cf4ad10377541e1aef51c7538b4 119 contrib/debian-installer/binary-armel/Release e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 contrib/debian-installer/binary-armhf/Packages f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 contrib/debian-installer/binary-armhf/Packages.gz 0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 contrib/debian-installer/binary-armhf/Packages.xz 6014f6298d74235879217ede53ce7a825e3b2ea31e30c76a829305d71c655ffd 119 contrib/debian-installer/binary-armhf/Release e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 contrib/debian-installer/binary-i386/Packages f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 contrib/debian-installer/binary-i386/Packages.gz 0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 contrib/debian-installer/binary-i386/Packages.xz a4a8901530290206e6ad553ae894fb5899803e5118f50d9e81e4e19bb2990317 118 contrib/debian-installer/binary-i386/Release e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 contrib/debian-installer/binary-mips64el/Packages f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 contrib/debian-installer/binary-mips64el/Packages.gz 0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 contrib/debian-installer/binary-mips64el/Packages.xz 660eb18c501c7914c707a601eda8b335ef47d97ec6627eec9b4176f397fbca9f 122 contrib/debian-installer/binary-mips64el/Release e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 contrib/debian-installer/binary-mipsel/Packages f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 contrib/debian-installer/binary-mipsel/Packages.gz 0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 contrib/debian-installer/binary-mipsel/Packages.xz b330344b49f86a63fc923cff90cb000f0c7b31bfc5d8874c73b3dcb12757d45a 120 contrib/debian-installer/binary-mipsel/Release e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 contrib/debian-installer/binary-ppc64el/Packages f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 contrib/debian-installer/binary-ppc64el/Packages.gz 0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 contrib/debian-installer/binary-ppc64el/Packages.xz 39ba90ca6578afaff1df87f41cbe3b622bba9f50c9180f1d71a62e20c920ed99 121 contrib/debian-installer/binary-ppc64el/Release e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 contrib/debian-installer/binary-s390x/Packages f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 contrib/debian-installer/binary-s390x/Packages.gz 0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 contrib/debian-installer/binary-s390x/Packages.xz d05cb182ffb8d64b588a430936bcacdbe97524f36758d20bdc96d2275bcb4bac 119 contrib/debian-installer/binary-s390x/Release f0a51e6d75f883bdecf739b214104a17dba111de8b42022f6b8b053870c83851 119152 contrib/dep11/Components-amd64.yml e14a1bb3690a18ec7c5b7997fabf4d8d4fa633efdf84a25e071a1f62a2c064b2 15579 contrib/dep11/Components-amd64.yml.gz 58921318632f77413bee8d9e980689f8f139eb1169b5ce201da06e6f280d485f 13564 contrib/dep11/Components-amd64.yml.xz 26538634f90cd6f04a6be602151fa6a098075c3013b66a81439a7bbdbfaa40f5 113437 contrib/dep11/Components-arm64.yml 840908ab753dba952e073216007f93d351577792911dcc09a15a16abfc32c8a7 14251 contrib/dep11/Components-arm64.yml.gz 3afec5908036aa2d47b9a9a33c13eca12bba1aaf8d8bbb06ffb1627e93f6526f 12480 contrib/dep11/Components-arm64.yml.xz fb35649f6c32b71b9d85388c2c238011161c250df5c62e2c4d3446e369dced4c 113437 contrib/dep11/Components-armel.yml c305f1c0826e0414bbf36524d8b0fc2723ffc0fb222275e1e1728914fc334c75 14029 contrib/dep11/Components-armel.yml.gz fe15a53774801f8d9cb04aa8324cbdb9d741ec75ae0999e033873458bd6160b0 12524 contrib/dep11/Components-armel.yml.xz 0ed24b6d7ff891c82697497dddfbbbb6818c168c55b41ae710e9cc9240d0d9b2 113437 contrib/dep11/Components-armhf.yml f5260cdac915ff5eba0a48757c93f8f8b6421a673e641285f43d83f62be3eb8c 14127 contrib/dep11/Components-armhf.yml.gz db97becd2ab6a05bcef05d824b89080a1e7c03a69735df3bf5945f6989a9e504 12480 contrib/dep11/Components-armhf.yml.xz 9adf35216113140c31c2e9c169a3eaa465044f41f8803afaac955c467a1e5a49 118972 contrib/dep11/Components-i386.yml c1d4ea9c0ac26f2b62d45c8c595ec9a5bc1c737b50634d7f86a4bfac17c9b180 15566 contrib/dep11/Components-i386.yml.gz 51ff60d5f02b46e08acea4054484f5c66d721c19beff4857cb2570f43e881a69 13560 contrib/dep11/Components-i386.yml.xz 50b6970af7de299a90ac651cceb6cc011e8d165ea0701f7b1c9daf6c1be485f0 113437 contrib/dep11/Components-mips64el.yml 78aad16ddec6b18d30ce4e20f52008f72efc78ba55688fa462741f4bb514043f 14056 contrib/dep11/Components-mips64el.yml.gz efb0fb003bbd3997128bef56f12104872604fad320b38fd99bca25e68210d98e 12500 contrib/dep11/Components-mips64el.yml.xz 05c2268c20e748baf8da20f7169918e2f6dcffb6e4f6dfc22829607cec7ea564 113437 contrib/dep11/Components-ppc64el.yml 19f600014e245e7d07762b7f07d8de6884b1208a280a19274e56b4174931082a 14219 contrib/dep11/Components-ppc64el.yml.gz dc8b525d7043ba3a85154ad39d0c809e7215c5b2f3865efbd94ff3daabe54810 12496 contrib/dep11/Components-ppc64el.yml.xz 5d43b650d261ac23815d98e9a4f644d56f4113e63f8a42b1558ff1c82e925d2f 113437 contrib/dep11/Components-s390x.yml c1811e0538dad96441a4172e661b9ef7fca9c05d86c4b157a66046bf49aa70e1 14050 contrib/dep11/Components-s390x.yml.gz 42356b4c04801189947748d6fce6e28e356a114869a7895e4921a3b4901e678c 12488 contrib/dep11/Components-s390x.yml.xz 641e9a50f98d7e4921102164e7737b095c9faead09f6de4459086b598b3bf0d0 271360 contrib/dep11/icons-128x128.tar 34b531c5292651ac5a18d0477bb8cf1420f3d969ad73d45fd596641d768b853d 195507 contrib/dep11/icons-128x128.tar.gz fa3a19603046c258e647b0c1fcdc6110f0b5c1f2801ee950eb1261e8c02e03d6 83968 contrib/dep11/icons-48x48.tar 28a6f153e56e9b567cc7fc03d6faa6dfb8480ee3f36e0c8d9646e4de3898480b 47168 contrib/dep11/icons-48x48.tar.gz d882fc33534a8677ed8d3ecf81f7a076fa57e8e8135bf586f8af20371edb195b 138752 contrib/dep11/icons-64x64.tar 45c8eda64d05f1feee0040809128760f9489665d66bed0502cb179fe0ec79f6e 93294 contrib/dep11/icons-64x64.tar.gz dca6c969ea74cf24d7d51ed8df202e7ef8d7c6cf9cac073b4e0c564c63696cee 192117 contrib/i18n/Translation-en 57e6201d62bf60faf1a188ac528d0717cce16b6cfa1c9b91a0b89cc5b16e10f3 46873 contrib/i18n/Translation-en.bz2 1ce49d093924419536ccd4e479353c01339c569f7762c3bcdd26684ee0fdeb5c 120 contrib/source/Release bb50cd899875208cb9c8eda65079d17aad91022ca1d2a703134cbc6aa1699c96 176962 contrib/source/Sources 1c4f0c1d76082a9583138939dda0c05f2c9d850693d73754e6addfb20f34d942 51034 contrib/source/Sources.gz 8e59f6674b7cb4fa8d32f7f9349fc3f3fa51e9634a1774aeec5ccf421a40585a 42992 contrib/source/Sources.xz 493765cc4b94bc545796461522f6f7570f7a55f97df75ce1b87ba486a11feec2 474533023 main/Contents-all 92a2f30571440ce3ebd0b5c5aa4b916374a47bce06e84728e0917a7b011aeae0 30914666 main/Contents-all.gz 07915a57458c382b075dba952878df06bf940a4756ebe46fc022412e58f21445 128441543 main/Contents-amd64 b03bd96a44e65d2a71d8b1ad5ba28b430a2325e28e08b1872c8cbe6d43a64192 10220624 main/Contents-amd64.gz a38d3680a572e56b9a2fb83b311bb759e51e3bd618ace622003386dd952de4ab 121824568 main/Contents-arm64 e48a8f9c492a61789fd39357127cc4497d5cb1592d9c723b9c0fc68c4868d1f3 9787793 main/Contents-arm64.gz c3102bdd06fd84394d4d2f6adc9faa2e0c80ca24434323c1f3c2db1e3427de3c 104161855 main/Contents-armel 90191e28c0d0382c8e925eeeb83070f757b1aec568c7948d43773fa02bdbc7e6 8658719 main/Contents-armel.gz cf82431c05fe239295a9622466daf45d621ecc27e59a51f24dd03ee8daa08dd5 113081422 main/Contents-armhf ab3484f9e49eefc348796413ad54c15f29394da901ac212dfc7c12a327e0e3b4 9261228 main/Contents-armhf.gz 30a1696ff19270e539654d199457092bd5dda222899d8803704e0d61ab825722 128473082 main/Contents-i386 d779822ebae258765bb27f061dd4a61fdd65567d6e262484dee5b93a2e367ffd 10158425 main/Contents-i386.gz 12f2992188c668297653bffdd81c9778fdb87b5b721bf4b33e36783a6cc6dd6f 110576495 main/Contents-mips64el 119d85dfe2cc3b67c7232cd63323b5d5cd950b55bfc60df137adf7c772207ab2 9000146 main/Contents-mips64el.gz e7a6dfc0271a04d284a2868a2244a51ac4bad7d9abafd668025305b92d1bc23e 112069415 main/Contents-mipsel 300f729095210f2415012be6713562920d38f20da8f5e677cf4b0af62a65ea95 9137554 main/Contents-mipsel.gz 7e7898e049b9dcda18ca7a51e2ff9d729f7e3b2a5f0d2fc7406288078b505cd5 115472952 main/Contents-ppc64el faba66cca64895489649f5c82d9ec0ce59805ebb19cf398dd4c2bd6fa356abdc 9307164 main/Contents-ppc64el.gz f2211d7c2657e873c650b06318be5a665bc425ccf8390240c9d94240c37488db 103114399 main/Contents-s390x 2137f1f7b8473abc4f7441616d1eb9e907045142d2f1c63f0cee32017861b2fd 8665807 main/Contents-s390x.gz a1213facb87beab92b7ff3ebc9bdf2e2fb2d6ab13ce6f9e204f53ddaa9f1ec32 674687728 main/Contents-source bbe935f878573835bebcc1c4c503404c3ed26089dd9f121cf91d37c64f86471a 72485245 main/Contents-source.gz b709d41e19af82147c367d90a74eae144ab18744d78817b6395fc1344fb99c76 157382 main/Contents-udeb-all f9801d96354f0b11d5357633cb9068dff1f39b9210eaeb70455db63ee0ecbdbc 13516 main/Contents-udeb-all.gz 4f7bc51d3601d2ef6d5187e5cdc6770077eea9149b8fa780e2a26d326967ce68 475045 main/Contents-udeb-amd64 48a0e1ce7608e2f1eddf074fe632564e6085adbd03829fe9d0112c020f9ad5f5 36105 main/Contents-udeb-amd64.gz a975c3f4568234b594386d411eff4c275bde54a5ab0d3c169efc90e649a441a6 506049 main/Contents-udeb-arm64 7d397fea54ca42e6aaccd3c17adfc04c817497004cc118cde3e9f69583972d5c 37929 main/Contents-udeb-arm64.gz 063a99e3d3e8a930a63ac85e0bbe0226e2a56425413f7869a4557022e59e622c 322291 main/Contents-udeb-armel 45a439a7db11842a1b206230c52b87f95068e2a51dffbe3fd239c14dae7244ec 25681 main/Contents-udeb-armel.gz 47b031a4814b33857916042c49fe4f12f336155b378ab19c225fa4feeda48016 576604 main/Contents-udeb-armhf ca7fb925322c316ee0fcad4dad571ce06060050910b312ff74121d6a2879438d 42929 main/Contents-udeb-armhf.gz 4fe676350b59e0aa10bb59cd82fa98524ab9c69525a64e128fe524c24250a620 747165 main/Contents-udeb-i386 47adc3f7c5730edf3e1aac29b5c259da7eb503bbb50c652c2ee87391547bbfd1 54484 main/Contents-udeb-i386.gz c66020376226413012e549640f2c7af0be8c2f117889cbeccf4f0963827b0ed8 756385 main/Contents-udeb-mips64el 6a8fd4210e7bc517839456c89fd3c5879fae8e338713bdecd461aa22679aa546 52547 main/Contents-udeb-mips64el.gz c3e5f3a5caaee2a8c66750086efad5d115f105e24e8aa7cec8dcafe138e177f0 756067 main/Contents-udeb-mipsel e80238e70f0d175a5e052ab49c064b03b8aa0425065ffc9430d18761f098d5bb 53066 main/Contents-udeb-mipsel.gz 838685c46ac0937f79914f94e883a5614501a25f19a9c9ac5d3e2503d6da3fd5 400327 main/Contents-udeb-ppc64el ef950c99bf338e45dd02e0a0716d138c2009f4c53bcbe13440e8636824235f5d 29807 main/Contents-udeb-ppc64el.gz 3865db1452b465d4bcedf7057fdc90a84f2ebbd487a65efa9dc1504de86bc338 258049 main/Contents-udeb-s390x 1482fec3f3704bf52ad71f759bb72391bb1c3d0db6be24a453c3554ab6dd5386 20968 main/Contents-udeb-s390x.gz 52cd64c7b37bb821e5cb16e853d48b24e287e691359c3115457e50ead272948d 20355932 main/binary-all/Packages 9ce25e93cab92cd835feac96c21a553a37a10c343e0fc536c6807bebbfd6ebe1 5199413 main/binary-all/Packages.gz 0e380b5c8d61500217864c022590c50eb7029099859bd0fd4b9a02617ed43908 3913044 main/binary-all/Packages.xz 051b2ce8dfe02717270df9f0f2e83147043ed5522628c07f1ecf73084ab76938 114 main/binary-all/Release cf77eaa6fb5f4cda14da07de79f02af4ab532bac707b0767b0cf3a3a83008480 45491563 main/binary-amd64/Packages fa5b09643055e6913b1078f54420f5d8479fe6da4ec39ff7d2e78eb55fc6e206 11091682 main/binary-amd64/Packages.gz 6d5509534097dd37bbcc2ed7bb18e117511395ac3812562abbe019b64071e1bf 8183088 main/binary-amd64/Packages.xz bc356c71140ebbfaf16f94f9d8ed11cf353d586e4831f12765b6701cffa825aa 116 main/binary-amd64/Release c99f396518ba63363605048e0cb5b45388601dec008eaae1100bdcd47f30bab0 44774341 main/binary-arm64/Packages fe5b3e30522694191cb27cb7cc43a4457be59c883238e3106af587bb7ee284ae 10936186 main/binary-arm64/Packages.gz 66eca14926b41007cf7c2ca579f0b2d76f3c366f8b33ebcb6b9e55a640a8d41f 8069960 main/binary-arm64/Packages.xz 04c96eaea42bd0e9eb7871e73308100bbcfcab2159b4868ce788fc5f5045805c 116 main/binary-arm64/Release 131f350c769030167d058ea5fc0ec31974085768deaa0b2bfbe3de165b7ed31e 43239670 main/binary-armel/Packages 93c16ed7777c6a3b72f7c702205e3eef47995857d706866f6dfebd9fbd972c91 10666338 main/binary-armel/Packages.gz 6d92247e8bfe0791220588dc08101a75b589b9b45ca88243036bc7d85815f8fd 7864756 main/binary-armel/Packages.xz edfe9adfa0f8b000f59e3e0b27700e13effe4066c2f5f67274f60b8b9365c59b 116 main/binary-armel/Release 9e16fc35c067fc52d882b72544ea8bec7180ded621f43ecf2d6bf44e15900960 43804389 main/binary-armhf/Packages a4514d687ca19e253043a6b4ef145e356990b839a3de5c4b88b00788d2cb0212 10771139 main/binary-armhf/Packages.gz 9f3c200daba0572b3c79f94f0215bd645b7b89e3c0fdf1c114aefce1e3ce76d4 7943076 main/binary-armhf/Packages.xz 6b309611745b8323b87a48761d23188a5cfaa48a71a18dc8ff5927dd38a3dab1 116 main/binary-armhf/Release 739396a3b8ef1127841c0e4d5cb2a82ba6521deb7a991e14e903d5c1f8b0ee5b 45052618 main/binary-i386/Packages 0f4552895b383994f68dd2dbec2f49b9ef30ba731fca7eb6cd8445582ebd3a5b 11007596 main/binary-i386/Packages.gz 05e528cd0b6d926338a33f8836206270042c993a35ff6ad0168348e5aba9253a 8120844 main/binary-i386/Packages.xz 6a2b2a8ed20450d58f8f99a2b2b5e5e5253db6f14a6632cd141574e3ce7389e5 115 main/binary-i386/Release 617ad31a90feacbc9e93a9184d8bd48575504763323acc43258f0b8ce595df4e 43695918 main/binary-mips64el/Packages 652596aa03306046b624da019dfcb501b9bceb9a77855b021c0b1da65523f9b5 10718907 main/binary-mips64el/Packages.gz 44b7b96a34370cd582cccfed804e7e9598b5f77eadaeda61ae14917eaa4624df 7905640 main/binary-mips64el/Packages.xz 0acb63e5464012b1dd060f392beda00ede995df55851bd35f04a76ab09e35361 119 main/binary-mips64el/Release f72367c41af1d3040f66d53997ee7baf3401226d3340e5b4c31500d40e106441 43633475 main/binary-mipsel/Packages 058df1bc76e45e51fa38d8e6cb8a09898597502745e842505a39b94af248e7d5 10723008 main/binary-mipsel/Packages.gz 452ef2557ea11a62684f76fd70be4ccac5bd0c8fb1b4666cdd70dac107aadb66 7906536 main/binary-mipsel/Packages.xz 4dba1df0531e8b1a2bbd37cb73f617a88a9d3c608666b7c4b8f20d7f04be01d5 117 main/binary-mipsel/Release 792cb71886480fa497dbdd3c4656cc86806082bec4e035f87b0d7cf1c19c7f6a 44621124 main/binary-ppc64el/Packages 3ff899dc62df0f06859d08b3b55812d0eb53347bf14a717b0b772701940833ac 10880728 main/binary-ppc64el/Packages.gz c71cad8097b4a0b1a7b978bfa89679facc92cbf89982e1ac1d9cce0ffab86fcf 8029696 main/binary-ppc64el/Packages.xz 44dd63840b82aef2fdbc8cd34413efbbc824a317f1cc0e89cb93029e60899d54 118 main/binary-ppc64el/Release fce6dd18894b1330b436a72c8c6defb32b2665b4a42eb2239ea32fbc46e0013d 43299770 main/binary-s390x/Packages 51cd665b04f497d6ba00d7a235063eebf2402cecd0365b5828d27da5289dda67 10684109 main/binary-s390x/Packages.gz 4ac7b273c9bad3295733ca768cc53851b08e22a5133b0798b3943bb91cf06884 7875500 main/binary-s390x/Packages.xz c6468e00179550721ebe216af4280233cf5e9df0871df2dea67f8340d54d3d84 116 main/binary-s390x/Release 7d126393306c8f1b62fc5cd47d3e993110505ea03ef532d68bf26e37370615fa 61112 main/debian-installer/binary-all/Packages 58a0ff79fa50b306dd9408d61d7dd4eed2cba7a2e31d584e81c4a62cf7c5b509 16427 main/debian-installer/binary-all/Packages.gz a3ee73350c9da37fc732a13d867ddebd172eb3e8937dbeca20436264ce01f6c1 14652 main/debian-installer/binary-all/Packages.xz 051b2ce8dfe02717270df9f0f2e83147043ed5522628c07f1ecf73084ab76938 114 main/debian-installer/binary-all/Release 2621392ab71a92833c1c818912811bb5a26acc1d86338e2d289033869676b7b1 273058 main/debian-installer/binary-amd64/Packages befc4de94d907697b3ea280849a440b767769e72ba70b636fb59bf2af20a83aa 67175 main/debian-installer/binary-amd64/Packages.gz 5f1bcff6e7b0a572f7c321df6a8d064c9d68c0f17960a8daffa261dcb4eb9270 56128 main/debian-installer/binary-amd64/Packages.xz bc356c71140ebbfaf16f94f9d8ed11cf353d586e4831f12765b6701cffa825aa 116 main/debian-installer/binary-amd64/Release 7c85503c7431606d318bedcb472bf7c947cca7635b8241689e26d868b1a9ccd2 256214 main/debian-installer/binary-arm64/Packages 2eb908aebe35af3481170ea35beaa9a5b4a9d90c29c3869a935e9abcab8d0e3e 64136 main/debian-installer/binary-arm64/Packages.gz 9fb5c64981cd704766b13f92cbfb0a32c7b26de1165d14dbc58ce4f4ccf25d04 53972 main/debian-installer/binary-arm64/Packages.xz 04c96eaea42bd0e9eb7871e73308100bbcfcab2159b4868ce788fc5f5045805c 116 main/debian-installer/binary-arm64/Release 0b6711d52f27249294c6d507e7f631aa43ec598a4e6868d2d8214efd510362a5 247408 main/debian-installer/binary-armel/Packages 57dcb8570a4e835d2163720971089808bbda64542f5c702aceb97141c12b0939 63426 main/debian-installer/binary-armel/Packages.gz f2241de63b975c9e8310382e2415322bbf57a8862bf3a7ee9ad756c37484b56b 53216 main/debian-installer/binary-armel/Packages.xz edfe9adfa0f8b000f59e3e0b27700e13effe4066c2f5f67274f60b8b9365c59b 116 main/debian-installer/binary-armel/Release d66f9893559fd4f646fe8ca4252d42d0ceb3aadbfafb97bc827eea871ef3a874 250818 main/debian-installer/binary-armhf/Packages 6bae37e20672374a28114fc8bd99fa26ac7d2511d962099e0798e041aa94c85f 64303 main/debian-installer/binary-armhf/Packages.gz 32649e719e60838576f2bbcc9ed8177d1405018a54cef62adee6689c47cf8938 53892 main/debian-installer/binary-armhf/Packages.xz 6b309611745b8323b87a48761d23188a5cfaa48a71a18dc8ff5927dd38a3dab1 116 main/debian-installer/binary-armhf/Release 60ee9087e8bdd8291deab46a3605204764bbfc5524b370e6a9a4405110dfe239 347463 main/debian-installer/binary-i386/Packages 99b24f66870eaa5368d4f68e21a95f29ab2b4ffbeb9b152a36b61667c8de55ad 77236 main/debian-installer/binary-i386/Packages.gz f6ceb5b17bb8526b38659b56e0eda3afc925c04ecf3465a4e0f1abd652b7683f 64316 main/debian-installer/binary-i386/Packages.xz 6a2b2a8ed20450d58f8f99a2b2b5e5e5253db6f14a6632cd141574e3ce7389e5 115 main/debian-installer/binary-i386/Release 09b034dc521572a2a3662b3e6a9629101b4017e52eaf25a965ac338fc9f59a36 362988 main/debian-installer/binary-mips64el/Packages 0ddaeae1f57d7ee27ca0206cb43f5d546ee338631e06b34476b38489f78e030f 78951 main/debian-installer/binary-mips64el/Packages.gz 8294d9470751ba2c5c6e600a3271b622e28d502d4c909460d37973bccc449263 66556 main/debian-installer/binary-mips64el/Packages.xz 0acb63e5464012b1dd060f392beda00ede995df55851bd35f04a76ab09e35361 119 main/debian-installer/binary-mips64el/Release 281ad832a438983230be384bcbd4d8a7fd618fcefa1d8eb784f0c624e100e2ff 362474 main/debian-installer/binary-mipsel/Packages df4deb9481ed1c84d2c2587a9a8a92fcd71ce61a32ef7cacc19a42ff143664cb 79930 main/debian-installer/binary-mipsel/Packages.gz a4cbe8fc5fddf95540d6d15c292a4a5e23e44b944a944e4cca3ebe5f370ccbc1 66756 main/debian-installer/binary-mipsel/Packages.xz 4dba1df0531e8b1a2bbd37cb73f617a88a9d3c608666b7c4b8f20d7f04be01d5 117 main/debian-installer/binary-mipsel/Release 14024ba254f1152fc3238000b2ead3a41278d1f1c76593e55846ae112a7048bf 255942 main/debian-installer/binary-ppc64el/Packages 1a1c987252d0d26ff0ae3004f715ae9a384788495b7807fb4d2d3850428b38ef 64563 main/debian-installer/binary-ppc64el/Packages.gz 2aca7c688eb25c9223797fa4afa1c39536aeea9a3529c7d635f35ea294cdd727 54016 main/debian-installer/binary-ppc64el/Packages.xz 44dd63840b82aef2fdbc8cd34413efbbc824a317f1cc0e89cb93029e60899d54 118 main/debian-installer/binary-ppc64el/Release c5a323846b0242c7f6ec6637f8d8e5d144fba322b2e1d92292fceb74170ac92c 225456 main/debian-installer/binary-s390x/Packages 6c9fc3f9daef320c27f96806309d291418e58e3d78798186314450bd96035248 59861 main/debian-installer/binary-s390x/Packages.gz 605606297ba793189aeedbf8e9915a89a971fe003a43df1cebbb09cb70c9a210 50116 main/debian-installer/binary-s390x/Packages.xz c6468e00179550721ebe216af4280233cf5e9df0871df2dea67f8340d54d3d84 116 main/debian-installer/binary-s390x/Release 99d8d572b0219a7b37addc91ff4e4ff238a33b3452580d4bd2469588a2225cad 18520413 main/dep11/Components-amd64.yml 9c5522d811abead85a73407f6b56b171207105bb3641e22d76f2146482d4750b 6213469 main/dep11/Components-amd64.yml.gz 0b517038e27fe4864c35de9459537d91f5d274800a172be69f91e90bb3631589 4048504 main/dep11/Components-amd64.yml.xz ed767617ad156481cc8948fb72c2d699d6292bfd2d83fb2f24b2b155612dc539 18436837 main/dep11/Components-arm64.yml 1732a30dff783f891da2245f955becf3a43be40f0400b722087ba626316e980a 6191092 main/dep11/Components-arm64.yml.gz a02d6259b836d37804838b6de8f40568332a9a78cb4bc7668b32208f6062e782 4033216 main/dep11/Components-arm64.yml.xz aa3eea13a49b29dba27956d6fb6093817775361e29fef3f751e8e70b7065e54d 17658848 main/dep11/Components-armel.yml ca3d41da75c25408834b265c9c95f700a1241189f6bf62270e14b85920f5cdc2 5952269 main/dep11/Components-armel.yml.gz 5c90b5a79fb5cf11b4e822396183bd3b4d3712e5f8e9363c5fce4a3a6c42a58b 3879744 main/dep11/Components-armel.yml.xz 9d95db48c33d5671c96a2931458a92b6290e9c3f880c7ec7d7aef2b23a681eb3 18205252 main/dep11/Components-armhf.yml 55c47f2e4607828ad1d875c1ade2aea6565916e9dce3e043f6de2e85b6cd74c4 6110587 main/dep11/Components-armhf.yml.gz 20797715d417813ddd77d1bf746b8ea9f6353ad0e8be2e67f1700813d992268d 3983180 main/dep11/Components-armhf.yml.xz 5579083d9a290f05eeb86967fd664c46464b3bafc00c073887560523a1793a64 18485654 main/dep11/Components-i386.yml ac8dd6c8b9e575785646a7d41adc7783956e22bcc757a60c80f225328c769f08 6201776 main/dep11/Components-i386.yml.gz 589f93188296c83e394c89ccdaae1565436dc203161958e96f3a5cf2797684ca 4041608 main/dep11/Components-i386.yml.xz 2b028df6a795c2a4b058b0f239745da363ea0f8b9fb8ce1a7955bedf579cc8cc 17819116 main/dep11/Components-mips64el.yml 0865e497ec87d5d45f84106166bb035610443e87528aacc1a43f13000542a3f5 5977494 main/dep11/Components-mips64el.yml.gz 46745049532f14f438f41704b442c157ee0f2990baed5d06da8fda3b41501547 3896708 main/dep11/Components-mips64el.yml.xz c0e1c64172edc19edcc287b0e617adff28b31354028de4c755cdf1fd077de913 17947079 main/dep11/Components-ppc64el.yml ba4eb9c1ab3f03a7fd184e5fc47dce250c083a617d9e2ba49a70c920fd957b29 6023058 main/dep11/Components-ppc64el.yml.gz aa34918432eeb8a82d912d86f69d82e84a4bc0eb48056ebe321b83d2757d1052 3925796 main/dep11/Components-ppc64el.yml.xz dc222c504c71bbc9ff6b698bf5ef7942e098efff1031861e5eb8670afdd18452 17735785 main/dep11/Components-s390x.yml 29584e8fd8bc91d9d9099893ae4951601430b1df4f55659e089d34e4525540e5 5976062 main/dep11/Components-s390x.yml.gz 1f9ca828b916aabab9b41f75950df49f71dc5e8a42f674ff4cb2138f85274314 3894008 main/dep11/Components-s390x.yml.xz 057f28adb7c2452ab2c810fdfbfce0305ba8143ffe2e24969b2ece077aba7e9f 13048320 main/dep11/icons-128x128.tar 4f46415e13538a05743752a630c9b8795a9772d0ab4ebe83c9d7e19f0e4bf179 11409337 main/dep11/icons-128x128.tar.gz e0c306e3293ecdcb8392faa372b00f1fb979c327c3e4370452acf7713ab885a4 4878336 main/dep11/icons-48x48.tar 93c4366d8b6ef489bb935434d9a2c56d842978922e941dd4ee716ede2a805494 3477622 main/dep11/icons-48x48.tar.gz 910ec31c85f12f0edefbb43fa2514b9896d105ce7316272a4c55263af864c238 9378816 main/dep11/icons-64x64.tar a94629c3e4fbe9607fb2921e1c906f88343a7cadc484a1087983181ae6df66a3 7315395 main/dep11/icons-64x64.tar.gz e061ee16e4478c39875bc3d977fdd5f880a71a3ea97c9f5119ac127a4305579a 6191 main/i18n/Translation-ca ed06627194c667d774188bcf0d9b859625ec60d2098238ee3c1cd5e1c147c4f7 2673 main/i18n/Translation-ca.bz2 857bef6538df7a4e2ae01a6ef40f8a5c9e0512797a769d8813caaa57ca867f29 1205166 main/i18n/Translation-cs bdd79636af5f08f4c40bb5266a41e4707b7bdc84d5458451df0255b787c380a6 323247 main/i18n/Translation-cs.bz2 2c7c6d7013e3d04a62c457525567fac4ac2747ef59f1b2a93cad8c0904c960b9 20240560 main/i18n/Translation-da 8935ec6ddfeaeb542fe444013ad9fefd6ffd2da2afe818efeb417fb50568b52e 4411163 main/i18n/Translation-da.bz2 55e94848df1df7d0963f3cb02cfb4171031350c549e4ae64f6aed517ed08ca6d 7801238 main/i18n/Translation-de b68fe8718325ebd1e2a8dd30f52b17c003e315f3468f9b7890fe5b1b91c709cd 1717951 main/i18n/Translation-de.bz2 284169348b8bd4e0de4cc5641eeb05577e80d2bd736452e454976c052cf3cbe2 1347 main/i18n/Translation-de_DE 481a435ad350105b74c4972859c44f447b7a8b5edea0d42f6dd635792e00a461 830 main/i18n/Translation-de_DE.bz2 9f3b3bc0da0653f0ac8484024a7f77aeda681474907f3a94b8a0a0933775d14d 6257 main/i18n/Translation-el 807de361285151534654b83681415016d443e4abd1a7ba36e1e78b4ac337b973 1835 main/i18n/Translation-el.bz2 7ed818b5caaf7e82615db81378e23052d9467a0cdcc0f97101059a767d326f15 30236299 main/i18n/Translation-en 94386d39c319174708e3e43a493a0475fec450d798e306fda427785756ae87ac 6243067 main/i18n/Translation-en.bz2 abccaeb24d409c21b94883b74785053d0f8fad3e94449078ebe92af38861bc5a 2261 main/i18n/Translation-eo 747ab457a83de3b107e25b9cc5536aea2f19e0fe1f08d5357475acea0d788fae 1196 main/i18n/Translation-eo.bz2 38345d246390b3845920937338647a70b1a6a93f354615da725fbf426ac3e332 1325929 main/i18n/Translation-es d6bd3bb26fb52e553bdaa40a041aa167f8a0c207149ebf626bea65c90ff7e99f 317946 main/i18n/Translation-es.bz2 80c3ff00f3b37b64e73c85b11eab47fe88901b6f8d9f189de0e95a387e02ebed 10093 main/i18n/Translation-eu 7ce6c68ef8a577bd215da5f7a12153bee27268b0b6b9503aaf88244b225f20a1 3914 main/i18n/Translation-eu.bz2 54c5db1926c3309513d37990460a51c586ae6f01bcaaf2732e537ae400b6f5f5 269212 main/i18n/Translation-fi a0c315c9c517ac029e5981f14a3c15fa022c7c0e1e86edf123e05027343974d7 75849 main/i18n/Translation-fi.bz2 bd258bc1f5bbc6694e24f58fe4dfb5f5636afc86a431795b931225e9e336feb3 11857302 main/i18n/Translation-fr ef77125783dc8b1125ea85050ba00bfe042e6f38fa1f73613387fe30cae47c5c 2433064 main/i18n/Translation-fr.bz2 ce1a70b1000909a09166e30d574c717f3d60ba173bb65ad65e768374dc73232d 1427 main/i18n/Translation-gl fa1eb924fc1473b81f7790ccd909de1dc274f4f266df8af544261f03e1d21079 824 main/i18n/Translation-gl.bz2 22e19c218655a9a4d09e9930a66715aeb5d0b02bdc4d147e5816067873e71861 21069 main/i18n/Translation-hr 04e538e90503a9238d071bba89039e563d4c03ee038c217708a4f8c8672c28d6 4695 main/i18n/Translation-hr.bz2 a275d9da1b509fc6c1d8307ff33daea14669cec8b8f89bb4c4fdf4d50ff48135 65236 main/i18n/Translation-hu 94827a9f6e251237fb3b093360f88ba469d2be8d4a7c2c02c84298c94faceaa5 22243 main/i18n/Translation-hu.bz2 0f4bfaba954ffa37332a34df69c8844b7334cc0b61515e9510513e2c43e140b1 3983 main/i18n/Translation-id 11aebe26133b1249ebc06ec6d1a8b76f5975b9a3630daf71ecb7e2f6521a2fd2 1780 main/i18n/Translation-id.bz2 d965461960f14ff1f614bcd0ba757874e098cd460b8ae0e018fb4aba254ce641 24489940 main/i18n/Translation-it 451a92cd21dc98889f43a39223dc8863284bd1a8e515bc58633bdb7bf96dd37c 4844227 main/i18n/Translation-it.bz2 1cb8cbfe8b502cc64639b02150e6f805bdeebedae3eb69273146c03ca6c9287c 4511401 main/i18n/Translation-ja 0c00e0a8cff6fb13bdc4ed3387e3faf4f9db94f3ed4ca8e72d324c0a03d8f018 803966 main/i18n/Translation-ja.bz2 7238152be74233d91630f7100ef7ff2bb8a95598b5fbc11c21c7afeecfc0fecd 11879 main/i18n/Translation-km 01577e06c8e41b3a914ae539147af0fcdc7a0f883f50d82b57b263cf62fe1bf8 2371 main/i18n/Translation-km.bz2 232cb289feae187cf94ad451662d7ce36be8014c40b69e645d19b9534dd586df 2606190 main/i18n/Translation-ko 894aba3a34a47f3d59deca3bda07f8aa288e9f4ed6ae92422eab3fd9dd370ad5 584643 main/i18n/Translation-ko.bz2 e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 main/i18n/Translation-ml d3dda84eb03b9738d118eb2be78e246106900493c0ae07819ad60815134a8058 14 main/i18n/Translation-ml.bz2 16be336bba03786450a43321709eca2fce7fa7b50a135a97da71e16eb5e7d60b 1193 main/i18n/Translation-nb fdec5fc00fe2d0e3c7730462f95273492d278eb8a6957c1b437969833366c217 738 main/i18n/Translation-nb.bz2 ce65092fbb0a09286644912bfaf3a9535921705519e16d07617ad85ec44ccf3a 174332 main/i18n/Translation-nl e12b923a6f3f83636a31e6e1b2503d8a213e1e4112586a27700fc17bb48ce564 47973 main/i18n/Translation-nl.bz2 8999184566c11a42f9a31c810d9252316dc4f52ba738db43e0be2cd760c823a1 2051811 main/i18n/Translation-pl 17fe48deb79b044bdf5894d0d129823b1d86653b9759f848667a74b563625379 491993 main/i18n/Translation-pl.bz2 2dbf3c4316bba32571abc589b177be93c8e72885131940c9993d3fb6b8d58cb4 1074959 main/i18n/Translation-pt 991a66952f6395d7588f38e68e1032f4dcc72da61322a59460c34a24d7713400 272186 main/i18n/Translation-pt.bz2 5d7ec6fe173a67789c445369b7ebf8709cbc9ce4f3e06a75cf36c562a16580a1 3306707 main/i18n/Translation-pt_BR 1583cdd6a71e29b6eaea0d29dee9ce903fc8ced1f9f57e5ad4de154938799bd0 802734 main/i18n/Translation-pt_BR.bz2 c90708ca8975ced4acf4be98a4ac1f5c8092fd826b4d928e35c3650e705553d4 1717 main/i18n/Translation-ro 35f2449dba7bd93e0aece908f4c4de53cc864a48c8f7aeaa5a64f67384e1bcda 982 main/i18n/Translation-ro.bz2 f8b907289a1970413a47a3450c59b04e166c08cb387ee3ae4f6c0d2e4774c379 3058931 main/i18n/Translation-ru 8685feba7a33fef7ad8d7fe5db5f59e837eba69134deb87610742cf564e47258 494782 main/i18n/Translation-ru.bz2 ee2a1713ba3ccf4aa7ef3ee1b5786874c38ecc15db012bc15c3efbf5ad8facd2 5984088 main/i18n/Translation-sk 0dfec1c42d581b3fe8f95bbe26f649f45234d419c7e709dc881f1994bfb20974 1304539 main/i18n/Translation-sk.bz2 5ff9c60997a547f07d212476a8f50b4942f012d7952765c6c1925c52495711d1 323953 main/i18n/Translation-sr b4608fc3c0c7f6aefe0f6e5e19d0fbe0d5035333e74044e29358b3e3efa99536 58385 main/i18n/Translation-sr.bz2 5656d4e913760691e99cd4805e76c8f18c4441fe707a02e621a2a172da756d5b 85612 main/i18n/Translation-sv fbad8c083b9985e53a2a82d7e32f26f683bd5b8e2f1bf09a3e0fc3f8f7abf6da 27320 main/i18n/Translation-sv.bz2 2e50dd5fdf1dd6157c0db51afb4457fcfbd427ebb6d1268aeeea1daf50da78f0 14670 main/i18n/Translation-tr 401a0f8d754d92c562bafe54aa0cb2dd7686ca015425513b666b50b8c9dc36a7 5362 main/i18n/Translation-tr.bz2 6c66f49d6c9df7ef28f92aaab2620a2151fa16f74bf96deb3b74987183e43b86 3740343 main/i18n/Translation-uk bd760427bda1a65895dd7b3bd6a3e2b2a0ee6b4060ce726ec4b7c02b89a72204 576766 main/i18n/Translation-uk.bz2 c2207dfa8d62c7e2a31851842dd928739bc147515f69fb7a28db93196dd1a601 21882 main/i18n/Translation-vi e3eab47e1acdc01ee2d774dba5b0f9d29c98ff48b25a57d469eeecf60d3035ca 6510 main/i18n/Translation-vi.bz2 7133134d1b1b6c869b4b700fed9778e93a0b774391402ad3399e9ff46984efff 2007 main/i18n/Translation-zh 8cbeadbbcec613b8476f8e2aa40b15772909109e10a83317c111fcf7c28d0219 1215 main/i18n/Translation-zh.bz2 d88628c7a7a16a042234daf91a709daa6d5f9de15406ec78530891354fa25c75 425199 main/i18n/Translation-zh_CN 1ef87b145198090deb2d037bc16b5b940c0e757a2511f4ff84a7c750720b2723 113621 main/i18n/Translation-zh_CN.bz2 564fdb3059cffbe78dde61697e77edd7bc94005a358cc4b5dffb436776d1b2b0 39965 main/i18n/Translation-zh_TW 0a4d5ecccec7069a32b30de129018034b2f6f2b318f1530e1edc239182442cf8 14859 main/i18n/Translation-zh_TW.bz2 4bdd184aa7588a4e242426f966940b7a4197c36022e3a3d40e001ea02b82dd8f 57705 main/installer-amd64/20210731+deb11u2/images/MD5SUMS 2d16ca2924ac742249d2094bebd6ce42c8861795db0afe731540792222e37dc6 77333 main/installer-amd64/20210731+deb11u2/images/SHA256SUMS 91e63d03c43f9feaed6c255a510c30c35c547c517f395c2574900b0119fad790 57705 main/installer-amd64/20210731/images/MD5SUMS a3a16cc4af2d688613ce8df4d224974629ad3383a1969350c24ea68bfdd5f1e5 77333 main/installer-amd64/20210731/images/SHA256SUMS 4bdd184aa7588a4e242426f966940b7a4197c36022e3a3d40e001ea02b82dd8f 57705 main/installer-amd64/current/images/MD5SUMS 2d16ca2924ac742249d2094bebd6ce42c8861795db0afe731540792222e37dc6 77333 main/installer-amd64/current/images/SHA256SUMS b62983abb39f810f78fdfa1743a82ea07c415472e4133350448647bb031684b3 68403 main/installer-arm64/20210731+deb11u2/images/MD5SUMS 173eb5716982412d6ac9e46927d160a90e4c3d99608b83dc43492b64623ce2a9 93279 main/installer-arm64/20210731+deb11u2/images/SHA256SUMS 291e81049aa85b147063ec1aa5bec87da60d3196c06c3098de5210c3346837eb 68403 main/installer-arm64/20210731/images/MD5SUMS 5dfc89487fc8717ab9a9b75cdaaf01a295ab3021cc3310d3fe9dd3e78fc1f666 93279 main/installer-arm64/20210731/images/SHA256SUMS b62983abb39f810f78fdfa1743a82ea07c415472e4133350448647bb031684b3 68403 main/installer-arm64/current/images/MD5SUMS 173eb5716982412d6ac9e46927d160a90e4c3d99608b83dc43492b64623ce2a9 93279 main/installer-arm64/current/images/SHA256SUMS d5f28ef7735da8da63101761299cc66f0c8a739d3daba1263f9e581e39ef0539 20183 main/installer-armel/20210731+deb11u2/images/MD5SUMS c6c3285ecf64b099bf903c6a42fceeb9183d5ef3c57c0d4287935f04251d9fe0 28195 main/installer-armel/20210731+deb11u2/images/SHA256SUMS ee9f639b7a0304207f23c84f5396284720a6fc6c638ee7be6873944a0f224c95 20182 main/installer-armel/20210731/images/MD5SUMS 07353d4c378ea579803ed8c1aca3fe6df2cbc89788736c7d01102a7b3ebad859 28194 main/installer-armel/20210731/images/SHA256SUMS d5f28ef7735da8da63101761299cc66f0c8a739d3daba1263f9e581e39ef0539 20183 main/installer-armel/current/images/MD5SUMS c6c3285ecf64b099bf903c6a42fceeb9183d5ef3c57c0d4287935f04251d9fe0 28195 main/installer-armel/current/images/SHA256SUMS 0f2cd3f8cadbd3507d9a9d26d058acf65ea0696b090861cd0a658982804878a8 64240 main/installer-armhf/20210731+deb11u2/images/MD5SUMS b87c9110231dc2e3d40a4cd1e43f0e7d9b2fe972b907629ebb461036797ac425 92476 main/installer-armhf/20210731+deb11u2/images/SHA256SUMS 8c1f810a60fc7daf099e608b763cec563f59c82203a07bbf4469a6213a8946eb 64240 main/installer-armhf/20210731/images/MD5SUMS 67c5b636e3fc02747ca9593e6fc7e906a3ec95d4947740fec81b1e942f0643ae 92476 main/installer-armhf/20210731/images/SHA256SUMS 0f2cd3f8cadbd3507d9a9d26d058acf65ea0696b090861cd0a658982804878a8 64240 main/installer-armhf/current/images/MD5SUMS b87c9110231dc2e3d40a4cd1e43f0e7d9b2fe972b907629ebb461036797ac425 92476 main/installer-armhf/current/images/SHA256SUMS f8133a1fdf6727b920644f3f25332b76eb5062e55d490e35b73c3c5e9f316c8b 56286 main/installer-i386/20210731+deb11u2/images/MD5SUMS 645680303902fa5756ed260665a961c2c8e88e2b74a291436a72c2787e132808 75978 main/installer-i386/20210731+deb11u2/images/SHA256SUMS 96e8acb8eb827ce7032587400fbe848b6f53921c661d52e1b16fd243cb8e57aa 56286 main/installer-i386/20210731/images/MD5SUMS bced74c95a3688a9a2a28abb8190cb7efd7e1f6372dc8989e260771752ef571b 75978 main/installer-i386/20210731/images/SHA256SUMS f8133a1fdf6727b920644f3f25332b76eb5062e55d490e35b73c3c5e9f316c8b 56286 main/installer-i386/current/images/MD5SUMS 645680303902fa5756ed260665a961c2c8e88e2b74a291436a72c2787e132808 75978 main/installer-i386/current/images/SHA256SUMS 9116dfedbd4d105471264d5670fed2a269462e4a6b89e7b18c85afadfe41c59e 630 main/installer-mips64el/20210731+deb11u2/images/MD5SUMS 8b5769c5e8d36206933b509940470a7c5e8280cbdae3b26190b3fc64c03149eb 1026 main/installer-mips64el/20210731+deb11u2/images/SHA256SUMS af3b55dea76e91f1565bd54bc1af76a6a0bb4991eef9abe281a22d9fd8d54a7b 627 main/installer-mips64el/20210731/images/MD5SUMS 995cda8278b101eb25849d56f3ef33290fb57a940fa1c6837f19df00ceafaaff 1023 main/installer-mips64el/20210731/images/SHA256SUMS 9116dfedbd4d105471264d5670fed2a269462e4a6b89e7b18c85afadfe41c59e 630 main/installer-mips64el/current/images/MD5SUMS 8b5769c5e8d36206933b509940470a7c5e8280cbdae3b26190b3fc64c03149eb 1026 main/installer-mips64el/current/images/SHA256SUMS ef7c91f3b91a36ba8bd7cc5727254bfeb1d9e4c1ea6d9fd64fdb080e1ba4ffb4 630 main/installer-mipsel/20210731+deb11u2/images/MD5SUMS e32649101c6d5d2b04308ba1ae92fdc98021ae078cd3c174f04bb91fb52623cf 1026 main/installer-mipsel/20210731+deb11u2/images/SHA256SUMS ca77bbc823d1bf6999e141cd42c1bb4c18179cbe4a3fbb6da3e40e1055848ed7 627 main/installer-mipsel/20210731/images/MD5SUMS 28589449e1b3ac9a73bdf6f266edc83e70ebbbca587a228b15b0dbe5e1a634fa 1023 main/installer-mipsel/20210731/images/SHA256SUMS ef7c91f3b91a36ba8bd7cc5727254bfeb1d9e4c1ea6d9fd64fdb080e1ba4ffb4 630 main/installer-mipsel/current/images/MD5SUMS e32649101c6d5d2b04308ba1ae92fdc98021ae078cd3c174f04bb91fb52623cf 1026 main/installer-mipsel/current/images/SHA256SUMS d1ae12a027e00dd70133e0b414bf87d6da3bd7fe757e250b5b39fd0e19ec9438 576 main/installer-ppc64el/20210731+deb11u2/images/MD5SUMS cf2974ef73d0fd2496bf57e8850d576346b7721396fbdf104aaa7143324a36bc 972 main/installer-ppc64el/20210731+deb11u2/images/SHA256SUMS d162b2da6777c1ea0643921cc1a3dde78ae48cf022711eb98c7e9dd030b89a44 576 main/installer-ppc64el/20210731/images/MD5SUMS 73e281bce56df3c7512ffa1a1cb13886064759a461621db4acf9b1f71965c676 972 main/installer-ppc64el/20210731/images/SHA256SUMS d1ae12a027e00dd70133e0b414bf87d6da3bd7fe757e250b5b39fd0e19ec9438 576 main/installer-ppc64el/current/images/MD5SUMS cf2974ef73d0fd2496bf57e8850d576346b7721396fbdf104aaa7143324a36bc 972 main/installer-ppc64el/current/images/SHA256SUMS 308289590dd69a4f03bae178ac9665c30c95dd6de52c4ae7178a46850149d3f9 374 main/installer-s390x/20210731+deb11u2/images/MD5SUMS eb9688de1bfe9c09c7fccc0fd564e67bb2940dde72e8cdc350e23dc50958a04e 674 main/installer-s390x/20210731+deb11u2/images/SHA256SUMS b2c58a9c5b97a59742a8056e3e9d7f4f22d4d11e51c71d7a0051dc4649a717b9 374 main/installer-s390x/20210731/images/MD5SUMS 61447263ea7318c444fde199afc718a8498fe67bc0e7116f2e1103cc65ef672b 674 main/installer-s390x/20210731/images/SHA256SUMS 308289590dd69a4f03bae178ac9665c30c95dd6de52c4ae7178a46850149d3f9 374 main/installer-s390x/current/images/MD5SUMS eb9688de1bfe9c09c7fccc0fd564e67bb2940dde72e8cdc350e23dc50958a04e 674 main/installer-s390x/current/images/SHA256SUMS 7378d58c3019d8385d3b49db5bf2a224f823895175ffb3c04edbf71604e8c5c5 117 main/source/Release a385564a5df02dadb2f246545818564ad632da49305e7af68e52a24c41e2cfd7 44587617 main/source/Sources ae722d81686b5b54b0da002bf8adcde58187fd9ee381f7406f9a16a775443926 11422998 main/source/Sources.gz c5123b323b1f9bae1fc82092bca8449e99d420cbc6ec527dbae2b0c1fc3a2dee 8626200 main/source/Sources.xz 29cac69ab0fd86e224587eea8e2ed2fb9b1b2e3c936fb1dc7165b8ed8d00528a 17347341 non-free/Contents-all 3b87590d0360ae141f3688fbafb5fdad35d4dd4b1a239888c911743c4357862d 888157 non-free/Contents-all.gz c077d6a32cb01b2d986022723d262d9a9bffc98fb0d2dd4f309c661861dc95f1 1012087 non-free/Contents-amd64 0c0938e15684cdaefd82706f94bc9da5a4a4b3b17cd7ab2c66ea35991bf91eba 75128 non-free/Contents-amd64.gz 0adbec0e5b0dfbf9f5e01facc00791e96a431f481ba0363ddafe168b6c537308 421263 non-free/Contents-arm64 0afa90bc5919f445f8e37992a27a07fa591bea921d8bb5b236022293ce2a1d0f 33327 non-free/Contents-arm64.gz 386c53a056d4aedb9d48a332056c51a302e1b043480cc24fc9ea9053ff8fe002 95417 non-free/Contents-armel 5fc23867def6ff06cf0c72080f1862ea142b20d25ca0a1e8e8b9c83ca3b82519 9298 non-free/Contents-armel.gz e7fdf590abb414845aec1bc00a0b8a6dd4399c76c74d06bdc2565453e403d31c 145256 non-free/Contents-armhf a76497f60c9522271ce9976ce26fe66fa334cc56e8259df2cce67e33b079eaeb 13289 non-free/Contents-armhf.gz 970af66cb978e1bdbe469a835a198b473e80a44cccf4b91f8da6e9dcb8e0f866 323026 non-free/Contents-i386 b8af4d485f35b30174f2cad1919ce727d394219cb49651ec7a6c5b0b5044599a 28131 non-free/Contents-i386.gz 6bdcba453cc1369f93e7157d5d7f9c67198edc62e4e194b079b0572186a95b34 91215 non-free/Contents-mips64el 0986d6fc85dcf209edbf39b1ee2c84b370ea02dfe810ac33cd9cc89a2f3a2a18 8686 non-free/Contents-mips64el.gz 5102cb8d1b74daa60d4d6444e563dbdaf73ffaa2b7ce71a304987ff575da7f4e 92244 non-free/Contents-mipsel 53bd140b538ffea9c0bd8b6b073b3ef613ec1d452bb1bad5a5f86a029f11e3dc 9026 non-free/Contents-mipsel.gz 273ca286d8dd9903b06bce13af99389adfad757ce391665f8fa8179bd2b29097 638773 non-free/Contents-ppc64el cd2cbd3c82b7d74df35c428017393000bd92a5dde83cc2cd17021a9d0217db8c 45603 non-free/Contents-ppc64el.gz 6d2b11e017bf520a64870b3ceecfac7944f991928095bd2715429987a342c37e 74537 non-free/Contents-s390x 228df45a42a42dd62cc747f2abe99dccd25c384aa423c17896a6196955cd9c12 7407 non-free/Contents-s390x.gz c30e86eb95f5155603536694da4d6dcb14beee16e7af8ad13d58036e5544f981 10789196 non-free/Contents-source 25ee5d55f5df376d02988da96250a6a066825723c789891efb11502b19da04e3 1062657 non-free/Contents-source.gz e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 non-free/Contents-udeb-all f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 non-free/Contents-udeb-all.gz e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 non-free/Contents-udeb-amd64 f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 non-free/Contents-udeb-amd64.gz e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 non-free/Contents-udeb-arm64 f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 non-free/Contents-udeb-arm64.gz e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 non-free/Contents-udeb-armel f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 non-free/Contents-udeb-armel.gz e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 non-free/Contents-udeb-armhf f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 non-free/Contents-udeb-armhf.gz e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 non-free/Contents-udeb-i386 f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 non-free/Contents-udeb-i386.gz e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 non-free/Contents-udeb-mips64el f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 non-free/Contents-udeb-mips64el.gz e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 non-free/Contents-udeb-mipsel f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 non-free/Contents-udeb-mipsel.gz e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 non-free/Contents-udeb-ppc64el f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 non-free/Contents-udeb-ppc64el.gz e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 non-free/Contents-udeb-s390x f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 non-free/Contents-udeb-s390x.gz e08b35e43b600d5dbd7e43874392239dd38bcdfce5386409cb74d3732672da6c 188965 non-free/binary-all/Packages bfec7e18cf920d4c79010ecb684854c926142dc7b9acb46c6d351f0a10b9eaf1 50951 non-free/binary-all/Packages.gz 7f80b2f144fe4350fb566530c9092b44f66ea01438674f7a488b4b1772f7d702 42980 non-free/binary-all/Packages.xz 6ca03500f44d9b62e478bbd26551afe665c396f6c6c25d9e36a4f290ba53fc78 118 non-free/binary-all/Release 57290bd3407a886a55a3295d805e27f0886c2bed21b38eaedba79a7b5488556d 509424 non-free/binary-amd64/Packages 0878212cf0a9ca05133549834c01f2dfb2bb6e18643abf6d409eec9d60a5b1ea 117560 non-free/binary-amd64/Packages.gz 8193c44c1159df8a67b40d448049dbdef5382ff88c64247b3531d4ac3ff23292 93776 non-free/binary-amd64/Packages.xz 96356c0e692c6e194825a16abad1dfc61540534b0dddd5c3cc6c29b8768f3980 120 non-free/binary-amd64/Release 73cc85f51ab3c3fc92d920cc4b97fea6a5fec5b9eba93600a10b1d981a4bf454 351705 non-free/binary-arm64/Packages f4c724af1c9a9d06e6ae82b56e4e7a9f1e2167ba3c0d90c941fa9853e9f82572 83410 non-free/binary-arm64/Packages.gz 91d72fb0989dda501bc9ff0070b0af24195de07ea278646a73d64a91bea57f2a 69596 non-free/binary-arm64/Packages.xz 8b0874cfbddbd5871f95ba1ba1cfd422e6bc602d7c1e058a2cd22b89fd353ab6 120 non-free/binary-arm64/Release c22449ef0bdc148b07009fb7d8f5ba04b2baa784f000b91d37062df1f5e4399d 227813 non-free/binary-armel/Packages c1acc1b3e82c74bc6f2fc93a993e3441916f3f691dbf4b0cd7b93020516a2243 61840 non-free/binary-armel/Packages.gz 3ace0b02f8d1d1a435232bfa179a5438dc7d9800a49363951478da2e5b8889a9 51796 non-free/binary-armel/Packages.xz aced630fdca79151fb2b0e512ea67b5fcfa42347b22cec530210e7c29d4b0a14 120 non-free/binary-armel/Release 220bfa3f574e29414cfa77a49fbdb7806e1bb38bfae57656079b0e7c35b0fb3a 258081 non-free/binary-armhf/Packages 878783a09ad8f0f352938588e30d57ba321e5b3a5bdfa782eed0283d0b3b0b51 67467 non-free/binary-armhf/Packages.gz 4c5c3418ce10739e9aacfaad4a4bab0457e3591b84a4f4fef3800aad43281573 56264 non-free/binary-armhf/Packages.xz 507151634bfd1b7194e957d575c868ad23f3f3369d3a93c154a91026bd89d445 120 non-free/binary-armhf/Release f779dba87cc319e4a9ff7d4571cef7fc54c3720467909711e0f55fc2ee392282 398466 non-free/binary-i386/Packages 9157b66483a099c886207c4731be73f09abd33ffd8693abf9aefbc1e27ae193a 93068 non-free/binary-i386/Packages.gz 1c6092305fc9c59227c9a240549b064789f175bd3cd48566d13e6cbf3904ca3f 76712 non-free/binary-i386/Packages.xz 5f9231ec415006a75a3a913deabb3866dea3cd9f49a7ce33ba113d86f1b0370e 119 non-free/binary-i386/Release 6878fab99bb4bfb138cc2a8cac2463e065a950426a4c6db96a7224617f75775f 225386 non-free/binary-mips64el/Packages c5c14e9c6b22409d2ee8291b91fb08ea516624d97d94d81ecc6bff9bf2959655 61033 non-free/binary-mips64el/Packages.gz ca491e41e78ac9189bd71863db800dd263c4e14e23a5e09c336cea5f93bbe4e9 51108 non-free/binary-mips64el/Packages.xz 3be7ea25b3a69ac04b214e67f72cab526b9fdbdcae0b5ee9cc5c6e3eb4465be2 123 non-free/binary-mips64el/Release 01d1414ec219d9c6f5786be43509d3acf160426ce462d7605daf418b9599f7d7 226042 non-free/binary-mipsel/Packages d28aeafd6105c165846ef769965d7833d526c17f008f3edd4fbe488564fe4385 61319 non-free/binary-mipsel/Packages.gz 94921382a6b28ddb4f2a6cd733db0778245ad4a2b0290870afbc58143695b15f 51340 non-free/binary-mipsel/Packages.xz f520da36bb16d341cf644101bf30f6dd12eac7fbaaacbef477affe3c73c28afa 121 non-free/binary-mipsel/Release 64eec289dd47e26c31bb9edd4e815aa7a661ea26dee7b4c2c3970ac7f39114f3 352636 non-free/binary-ppc64el/Packages 27d2eb0d938f47786917d1d77a1ae0d52821e21e61d236d6d3336bd613fe370e 82547 non-free/binary-ppc64el/Packages.gz 50d21f0096c528ce484fc81e29e683ec231a83244f1d59902d9ee1e3c56e640d 68732 non-free/binary-ppc64el/Packages.xz b03761ac27ab921c620e35e3f1a0711584d8c63f9ece1c5275976c9da6fd7788 122 non-free/binary-ppc64el/Release 75d7877efc7350d7092d9d970bfbb41bf83b49b73d03d1fbb3536ec2d9df4d25 220450 non-free/binary-s390x/Packages dcef034e773b2ffa33918befaa0fab3291567fd1baa9f9cb924e45902f3eb28a 59870 non-free/binary-s390x/Packages.gz bf18ba317cc4c3f998c56360c7bdfced29fc8351f48558e8db7fdd56d9a506a0 50188 non-free/binary-s390x/Packages.xz fc8f58473165a41667a43b9d6a95986e961bf74cdf292bd8287fa0a9231b7f1b 120 non-free/binary-s390x/Release e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 non-free/debian-installer/binary-all/Packages f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 non-free/debian-installer/binary-all/Packages.gz 0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 non-free/debian-installer/binary-all/Packages.xz 6ca03500f44d9b62e478bbd26551afe665c396f6c6c25d9e36a4f290ba53fc78 118 non-free/debian-installer/binary-all/Release e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 non-free/debian-installer/binary-amd64/Packages f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 non-free/debian-installer/binary-amd64/Packages.gz 0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 non-free/debian-installer/binary-amd64/Packages.xz 96356c0e692c6e194825a16abad1dfc61540534b0dddd5c3cc6c29b8768f3980 120 non-free/debian-installer/binary-amd64/Release e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 non-free/debian-installer/binary-arm64/Packages f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 non-free/debian-installer/binary-arm64/Packages.gz 0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 non-free/debian-installer/binary-arm64/Packages.xz 8b0874cfbddbd5871f95ba1ba1cfd422e6bc602d7c1e058a2cd22b89fd353ab6 120 non-free/debian-installer/binary-arm64/Release e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 non-free/debian-installer/binary-armel/Packages f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 non-free/debian-installer/binary-armel/Packages.gz 0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 non-free/debian-installer/binary-armel/Packages.xz aced630fdca79151fb2b0e512ea67b5fcfa42347b22cec530210e7c29d4b0a14 120 non-free/debian-installer/binary-armel/Release e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 non-free/debian-installer/binary-armhf/Packages f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 non-free/debian-installer/binary-armhf/Packages.gz 0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 non-free/debian-installer/binary-armhf/Packages.xz 507151634bfd1b7194e957d575c868ad23f3f3369d3a93c154a91026bd89d445 120 non-free/debian-installer/binary-armhf/Release e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 non-free/debian-installer/binary-i386/Packages f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 non-free/debian-installer/binary-i386/Packages.gz 0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 non-free/debian-installer/binary-i386/Packages.xz 5f9231ec415006a75a3a913deabb3866dea3cd9f49a7ce33ba113d86f1b0370e 119 non-free/debian-installer/binary-i386/Release e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 non-free/debian-installer/binary-mips64el/Packages f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 non-free/debian-installer/binary-mips64el/Packages.gz 0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 non-free/debian-installer/binary-mips64el/Packages.xz 3be7ea25b3a69ac04b214e67f72cab526b9fdbdcae0b5ee9cc5c6e3eb4465be2 123 non-free/debian-installer/binary-mips64el/Release e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 non-free/debian-installer/binary-mipsel/Packages f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 non-free/debian-installer/binary-mipsel/Packages.gz 0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 non-free/debian-installer/binary-mipsel/Packages.xz f520da36bb16d341cf644101bf30f6dd12eac7fbaaacbef477affe3c73c28afa 121 non-free/debian-installer/binary-mipsel/Release e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 non-free/debian-installer/binary-ppc64el/Packages f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 non-free/debian-installer/binary-ppc64el/Packages.gz 0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 non-free/debian-installer/binary-ppc64el/Packages.xz b03761ac27ab921c620e35e3f1a0711584d8c63f9ece1c5275976c9da6fd7788 122 non-free/debian-installer/binary-ppc64el/Release e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 non-free/debian-installer/binary-s390x/Packages f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 non-free/debian-installer/binary-s390x/Packages.gz 0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 non-free/debian-installer/binary-s390x/Packages.xz fc8f58473165a41667a43b9d6a95986e961bf74cdf292bd8287fa0a9231b7f1b 120 non-free/debian-installer/binary-s390x/Release e13d055f233a81a77666f0ff8dd9d748917b2829740756e1dc2b8a350309bcb0 278293 non-free/dep11/Components-amd64.yml f51b1a07cd72a36b2a9f36742ab26819a7808aa7765cbf3e2ff4abe6be66b50c 29634 non-free/dep11/Components-amd64.yml.gz e113163e116c137577fc9d3a4f7c95e0934ddbae7bdae5e083aaa1ce095435b6 17904 non-free/dep11/Components-amd64.yml.xz 6177cb908c067306c11bd8728a5b65a205d999be63930c079e3ff4250a24ce8e 271451 non-free/dep11/Components-arm64.yml 1b6107a1fa771a8fff50e0b182362fd679dc01f58f7a1f3fe9fe0183daf3be0d 27686 non-free/dep11/Components-arm64.yml.gz 7ff5eda9a37e07b9bcfa479c89863d7b2b1aafbedbe4b37ea6c32a16f2eaa241 16392 non-free/dep11/Components-arm64.yml.xz f54eccd2dbf23fa45cab9e9e7abfafeb667397ea70b6197a3653e8499ffea8bf 271451 non-free/dep11/Components-armel.yml 5581d7f4c159a5cbd33927294f7fc9918e7deaf04b313001965c83412b6a81f7 27606 non-free/dep11/Components-armel.yml.gz 0830d150400c82255a52a74f6af9f1a11007bf4b92fc814513f9e13cfac0b22c 16448 non-free/dep11/Components-armel.yml.xz 15d1524c660c8fb1ee911775a9b59cebbc66843eb97cc0a15a361009f153e6ff 271451 non-free/dep11/Components-armhf.yml 3fa04d7715c8955987742dc376d10327a975f9583cf656da055d13895e460a67 27691 non-free/dep11/Components-armhf.yml.gz bbf5a05de96a53c0e10af6019cb7b053b83b0f5def488cde4d8359475adb08da 16364 non-free/dep11/Components-armhf.yml.xz 716cec6e00d8303375812c8c9be7cbfa5fc858fdb3d9af3f0c72a696d8f7cb2d 280613 non-free/dep11/Components-i386.yml 40f189b3b3a74bc85652829d0c67b21aad7e60ce389f26fe1959db1e1e8ec48c 31098 non-free/dep11/Components-i386.yml.gz 18507e0a03c74ed39b9bec853eb9216b458f2fe2b7535c2622c126b9cd35301e 19156 non-free/dep11/Components-i386.yml.xz d82d6fadb06b6a1f0d36c155b70a02eb2281838aee3ce1b9bf51b7ae06136721 271451 non-free/dep11/Components-mips64el.yml 25d788e157070218396bafba65ff087551830ba0d0ba3e3cec5342bb150aec57 27765 non-free/dep11/Components-mips64el.yml.gz 2d0aa3979fd6093dc6de8ba902166a985235c8c4926e07cab7aa2a9b4ad0c11d 16380 non-free/dep11/Components-mips64el.yml.xz c55445f6f87fd566212bb018f9fae1a4eb43c1a66fe1b0e198b1c7d7e500b009 271451 non-free/dep11/Components-ppc64el.yml f525af23f1a1eb26ee786c36e2afd4aa5e4102b646f33f8c6788aee395b752bf 27592 non-free/dep11/Components-ppc64el.yml.gz 0ee03164cca5098ec7c6f98a469818b40b61da7846451cc223d0b9e01585c57c 16576 non-free/dep11/Components-ppc64el.yml.xz 359af9af71c00d90265395225b75313966435729cf1f6cfb1085fe1721b01e72 271451 non-free/dep11/Components-s390x.yml 47ef508dff3dfdf17ceeed229d98a2e3992c1a26f28eb328a2d1958d2ddfe070 27558 non-free/dep11/Components-s390x.yml.gz 181db8b5130910114256e8809ff9a1637efac55b1f33d1f516983521b8d51e7b 16356 non-free/dep11/Components-s390x.yml.xz 601045de5331d63b7ef2a24f8f74a7452d7be785f94ae6c46002c5dc2608188f 8192 non-free/dep11/icons-128x128.tar 4fb59feb5d5afe99980ea36c3d7c14577a4b5f11705e7d16524767708666ed54 2394 non-free/dep11/icons-128x128.tar.gz 977a5470a45ec30f5e230361a446f4692f9cf9bc2abccf6eabac2df0291f1ee4 4096 non-free/dep11/icons-48x48.tar 07a401f7b03554c2d8ab32dea5885c43b7da7badeea0569b9ce5c9dbbb7cf66f 741 non-free/dep11/icons-48x48.tar.gz 159551b3012db94a70261cb8f88619a6bb148318da051479ade6df7211c41a34 36864 non-free/dep11/icons-64x64.tar 872b7437de6fb938db8b26d9de9a3113bc722cd6ed682973151722e2b7a190be 27667 non-free/dep11/icons-64x64.tar.gz 11a780f909b0bf2ad945050d23e827b91f7a12faf0cbc81d194cb54099f52588 547848 non-free/i18n/Translation-en 1007fbbf9dbb45c8cf5735052d49b0bfc00cd068426f550592f99c5cee2e29b2 91490 non-free/i18n/Translation-en.bz2 7ff86d9b6c4a8e9710b723d226fbda333d4419d4b7a6ff963c4e459c015efa44 121 non-free/source/Release e807f512e700cf0a70b5dc38784d6a945343a0b02456563874883f7103a7c4c7 351318 non-free/source/Sources a2ddff41b6d247bbaea1f4c81969d45d0acf79a1152f8166fc80f52b5c29217d 96687 non-free/source/Sources.gz 30f3f996941badb983141e3b29b2ed5941d28cf81f9b5f600bb48f782d386fc7 80488 non-free/source/Sources.xz -----BEGIN PGP SIGNATURE----- iQIzBAEBCAAdFiEEAUbcbUoLKRS97TTbZIrP1iLz0TgFAmG9ungACgkQZIrP1iLz 0Tg4wg//VfQ++Sifx4InDBE517IGh0w8RbrhV1EfEuMc3btIE/bGJcVvrjwNjEv8 g66prz8f0CfUoXeE2X8ype4ZLQ9B+NpN5Gx3qCNbII/F8Stk95BF4dtPQaVQmTXU A4iCrFrRpW70zMeAy98B2KuWG5pZ/48ex8a0Z/Txtk8Ee8axg38VRdbOKA++azyk durYs1BMrn6tXu4ODb6Pfe3QjXbyrReDwSZqFL6k/Z26eqLId0rw2mP97ZcEo8NE im1ZzeBfxlJNaN23puO6Ev6HLROVkUQNQVoq0fA4iIO4B8M3cmWGOUKS5QPF969G SpaRAabY5Eha20AaSnAXGf2m99RpCHMxvOSLzSRWoxi5erw9rMfCnUSvsGgE460E jYbi0Bt9y8sUJ+jQmsbf7r8hr3SKaANJnq9+1orf5NnRTi/YyrrSEAGtmBrkx9bU L/ej0jmn0UeLMwRFVUhn/JM9k4rXQgOPathaZxx33aFoVs6SEqJbjYDD/T9eoSvL jbvQeyvbx6ABkUYWomerk12dIbKn9ipgYyeBJ0l+Z+LwXGny7u98GT37WQo//lnL zHYUpCiyAZHfNDMwR138iGQeTS+T+e25IEoMh0jx43n/0B5pEHDSIwFxE3k5xOV9 7XwnbJvSzNoJC4xJq+aVDn+InofOZ5HtlWkqb+/gwyLLxmVGKQCJAjMEAQEIAB0W IQSnI2iG88zKrRSKJ/gOmEBNOG+h2QUCYb26eQAKCRAOmEBNOG+h2fxGD/9Wspea szqRhDCe1d8KROZi50CBnvKw1eJkJj12GZ2Vo9oqBzYimMFdyLaeo0zMm0Otm1D9 d+TWuYb0zYoUYZnTvNpLn9Z0alqvwbR6cXqoiMUjMcaZs3Sg3aj/gCYQGBEnjYHU fug239KRU1SVtpFSe4goT4dsqPp44SHuNaSILo0olHvc5IX4okhpfxz7rv343bhf HJg5Mgvf9Jw3BtFiWm1nhToLZiXJ3gO3IPyg8CRza/69cA1HWvcazlm7clS2ztR9 YAf98IaHbDgTX9zGGkIDgWjHODGQ31iFR2eiiQQr8OornxgTejHZgsdJW3TAOZO3 gQyq5gu8POI9H1kzR6ATZwHMQ1bt8vJU6iS6HIqEr6/r4gfU4X1B137k02MsBRs6 bg3FCbxoCIj/qJBw3Ss6MH916/R/6has2jh2Bvkpi9iykpPi2JjtuiCClDWwtNMU 7wwvlnwTpp95xkAGd6FaANkTBgMaGswTYIzvBROGuENxU41O61yxnLAT56NsvqBa gWcCvhY8gbZWXbb5La17M+P1TNeqMt3il2UlMy1fegFxDSd/5dDis8iRznQ+bYAG TEj7aNYsNhS5I1lTgAy4URoRARAGoiKLODXgh2IOiCcFE0OzG9OmMdnJHq9KY0dB /CO/V7tTNMJiK5GPfV9jzOzcE8WtdCqb38+AVokCVAQBAQgAPhYhBKQoUpX8exqB YABiqWBcZvANbJeTBQJhvbvBIBxkZWJpYW4tcmVsZWFzZUBsaXN0cy5kZWJpYW4u b3JnAAoJEGBcZvANbJeT/agP/1pKHRUObtOj/ikx6XJ5E4Af7wPu+49YA6oLV0Z3 xVLGONuqLnJF8UcGjw2EFilC18VLRwA84ucK3m1FFqXnEzs+/Y7e7cnJqCB3OhDZ MjTK95PeCPhw8Qyf6PD+EoHAx4aa8kmivEizCBjp887g2kPanHF5czqXZkKHe24c aLdr2kzMhLz5y4V8UIDzkoKIujeI7Zl8MzsFYsTwHYbLmGl08zcfa2QB4X1b6ZJr XJ4v+FVfCpah8PjJYFlDZjMp35CfouaiBf5Gf8M6ozw9ASnj6gtXxmDQvaQkpPBk WsAMazXytdMMkqakeTp8OoczA8Kb4lUKW8UmUnEAiMMyHOYBMFwyEKCKoXOeMve6 /lg1TLiyELe2c7K9CGDaEGV0OlsjoF4hZ4v0p6LL6wi9fVioN8gLGiIUJ7rZiQzO YD/CbNwjNRv0dqs5fCAbT5y7/brFzBXhiiASiAWMtbn/qH3Y9EmSwLwgRXOTumzD 8fyQh3ilvp+mWbdyJ3VO8VxbLvexnGJL3YaYKD9NpweIhZIwfxIPLyi3RXKKc3S3 npSOUalJE/jmk2ZbZhPm8iwsd94Xx6jj8pdrjtMO78m02PJnXjmBhaZznMwA460a 239z92doqhEIqP08pIaZywS68Py4ckgeXOhsshCPt0nJsdJd5OkQGgx4g/Z2ODsh YlAr =N+Iz -----END PGP SIGNATURE----- ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/messages/InRelease.msft��������������������������������������������0000644�0000000�0000000�00000007005�10461020230�0021365�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������-----BEGIN PGP SIGNED MESSAGE----- Hash: SHA256 Origin: edge stable Label: edge stable Suite: stable Codename: stable Date: Fri, 23 Feb 2024 23:40:36 +0000 Architectures: amd64 arm64 armhf all Components: main Description: Generated by aptly Acquire-By-Hash: no SHA256: ac699445592f68f41846a1155373e0f119f583eb956ca2366fc439dfd2f1e9e3 127370 main/binary-amd64/Packages 8057004629cdd7d8fe247272978c6a3b98396911ce77b945b5c103afd2f93753 7695 main/binary-amd64/Packages.gz e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 main/binary-arm64/Packages c74125ed2ac3a52a6ef388c682bc5d70736b72f25351901037478b952bbde3c9 29 main/binary-arm64/Packages.gz e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 main/binary-armhf/Packages c74125ed2ac3a52a6ef388c682bc5d70736b72f25351901037478b952bbde3c9 29 main/binary-armhf/Packages.gz e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 main/binary-all/Packages c74125ed2ac3a52a6ef388c682bc5d70736b72f25351901037478b952bbde3c9 29 main/binary-all/Packages.gz e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 main/source/Sources fff83a1af8d775bca4ea7fafcd320e99a8cb82f1d2bae6fc4a2aa889cae61a45 28 main/source/Sources.gz SHA512: cb25aea00cfc787a1753cd691dca80d31d95071eeac3d1338d347f8da2c845fa9fe9b3ce5f67db5bb0bfcc8c19e0edc4d100e29fbca671a0f7895aed43f0b3c4 127370 main/binary-amd64/Packages 9d97f1bcb5629c992d21d857a4305ae9014588d43be32eacacd224c49977a2054e93ef72a9f438213191e62002ee101da2e8a7d33df767e064c25c2b9392bcf2 7695 main/binary-amd64/Packages.gz cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e 0 main/binary-arm64/Packages 45db920e1f9eaf7c709416d25132a1d2fdf41d528ce0cf0d519555f0123afb15fabfb840d3afc91b6b7c741821fd4697a961c6145afbb013a88096a7e7609bce 29 main/binary-arm64/Packages.gz cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e 0 main/binary-armhf/Packages 45db920e1f9eaf7c709416d25132a1d2fdf41d528ce0cf0d519555f0123afb15fabfb840d3afc91b6b7c741821fd4697a961c6145afbb013a88096a7e7609bce 29 main/binary-armhf/Packages.gz cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e 0 main/binary-all/Packages 45db920e1f9eaf7c709416d25132a1d2fdf41d528ce0cf0d519555f0123afb15fabfb840d3afc91b6b7c741821fd4697a961c6145afbb013a88096a7e7609bce 29 main/binary-all/Packages.gz cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e 0 main/source/Sources d7f9bcff224a2f752a7a98daa7b6c8f90d1689d96e393a1f2312c308e40a8ea546b2044f8e943e8804c1e3668bcfd335ca68392e22ade043e761eecd5d94f3e2 28 main/source/Sources.gz -----BEGIN PGP SIGNATURE----- Version: BSN Pgp v1.0.0.0 iQEcBAEBCAAGBQJl2S1YAAoJEOs+lK2+EinP7iMH/0ld9KRAlQjfAna/qjqdy6+c Rek5mHBCpmdHtwUQq4KwZFBpesqGDdqYX8XQO2ukXlYTlSFRZVM9jbF/D0vnhPwr shahdAHDCs7RPhLryctZWft0pB7vf3PmX/lCkqoCyoyN4/I60Gu7H5D/CJePq2gB 7dr3iSlUjbzTpKZ//fuZuQ7F3dCDa4m9/In4bZLW0qp37yBYygaAH7IwVeXvvOBL uHBuiuRNEy5NfhUDmnHgv1PAnRKvhHSJk1TuYlYttM7/WX/Uia7qdY/7RUvDf9eX VCXBMjvtMK3egidkX4CT8irYuK0Sg7hasY+2vqG6+TX3Qdyeg+Kse7pd9MGY91M= -----END PGP SIGNATURE----- ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/messages/a-cypherpunks-manifesto.txt�������������������������������0000644�0000000�0000000�00000012046�10461020230�0024141�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������A Cypherpunk's Manifesto by Eric Hughes Privacy is necessary for an open society in the electronic age. Privacy is not secrecy. A private matter is something one doesn't want the whole world to know, but a secret matter is something one doesn't want anybody to know. Privacy is the power to selectively reveal oneself to the world. If two parties have some sort of dealings, then each has a memory of their interaction. Each party can speak about their own memory of this; how could anyone prevent it? One could pass laws against it, but the freedom of speech, even more than privacy, is fundamental to an open society; we seek not to restrict any speech at all. If many parties speak together in the same forum, each can speak to all the others and aggregate together knowledge about individuals and other parties. The power of electronic communications has enabled such group speech, and it will not go away merely because we might want it to. Since we desire privacy, we must ensure that each party to a transaction have knowledge only of that which is directly necessary for that transaction. Since any information can be spoken of, we must ensure that we reveal as little as possible. In most cases personal identity is not salient. When I purchase a magazine at a store and hand cash to the clerk, there is no need to know who I am. When I ask my electronic mail provider to send and receive messages, my provider need not know to whom I am speaking or what I am saying or what others are saying to me; my provider only need know how to get the message there and how much I owe them in fees. When my identity is revealed by the underlying mechanism of the transaction, I have no privacy. I cannot here selectively reveal myself; I must always reveal myself. Therefore, privacy in an open society requires anonymous transaction systems. Until now, cash has been the primary such system. An anonymous transaction system is not a secret transaction system. An anonymous system empowers individuals to reveal their identity when desired and only when desired; this is the essence of privacy. Privacy in an open society also requires cryptography. If I say something, I want it heard only by those for whom I intend it. If the content of my speech is available to the world, I have no privacy. To encrypt is to indicate the desire for privacy, and to encrypt with weak cryptography is to indicate not too much desire for privacy. Furthermore, to reveal one's identity with assurance when the default is anonymity requires the cryptographic signature. We cannot expect governments, corporations, or other large, faceless organizations to grant us privacy out of their beneficence. It is to their advantage to speak of us, and we should expect that they will speak. To try to prevent their speech is to fight against the realities of information. Information does not just want to be free, it longs to be free. Information expands to fill the available storage space. Information is Rumor's younger, stronger cousin; Information is fleeter of foot, has more eyes, knows more, and understands less than Rumor. We must defend our own privacy if we expect to have any. We must come together and create systems which allow anonymous transactions to take place. People have been defending their own privacy for centuries with whispers, darkness, envelopes, closed doors, secret handshakes, and couriers. The technologies of the past did not allow for strong privacy, but electronic technologies do. We the Cypherpunks are dedicated to building anonymous systems. We are defending our privacy with cryptography, with anonymous mail forwarding systems, with digital signatures, and with electronic money. Cypherpunks write code. We know that someone has to write software to defend privacy, and since we can't get privacy unless we all do, we're going to write it. We publish our code so that our fellow Cypherpunks may practice and play with it. Our code is free for all to use, worldwide. We don't much care if you don't approve of the software we write. We know that software can't be destroyed and that a widely dispersed system can't be shut down. Cypherpunks deplore regulations on cryptography, for encryption is fundamentally a private act. The act of encryption, in fact, removes information from the public realm. Even laws against cryptography reach only so far as a nation's border and the arm of its violence. Cryptography will ineluctably spread over the whole globe, and with it the anonymous transactions systems that it makes possible. For privacy to be widespread it must be part of a social contract. People must come and together deploy these systems for the common good. Privacy only extends so far as the cooperation of one's fellows in society. We the Cypherpunks seek your questions and your concerns and hope we may engage you so that we do not deceive ourselves. We will not, however, be moved out of our course because some may disagree with our goals. The Cypherpunks are actively engaged in making the networks safer for privacy. Let us proceed together apace. Onward. Eric Hughes <hughes@soda.berkeley.edu> 9 March 1993 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/messages/a-cypherpunks-manifesto.txt.cleartext.sig�����������������0000644�0000000�0000000�00000012473�10461020230�0026720�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������-----BEGIN PGP SIGNED MESSAGE----- Hash: SHA256 A Cypherpunk's Manifesto by Eric Hughes Privacy is necessary for an open society in the electronic age. Privacy is not secrecy. A private matter is something one doesn't want the whole world to know, but a secret matter is something one doesn't want anybody to know. Privacy is the power to selectively reveal oneself to the world. If two parties have some sort of dealings, then each has a memory of their interaction. Each party can speak about their own memory of this; how could anyone prevent it? One could pass laws against it, but the freedom of speech, even more than privacy, is fundamental to an open society; we seek not to restrict any speech at all. If many parties speak together in the same forum, each can speak to all the others and aggregate together knowledge about individuals and other parties. The power of electronic communications has enabled such group speech, and it will not go away merely because we might want it to. Since we desire privacy, we must ensure that each party to a transaction have knowledge only of that which is directly necessary for that transaction. Since any information can be spoken of, we must ensure that we reveal as little as possible. In most cases personal identity is not salient. When I purchase a magazine at a store and hand cash to the clerk, there is no need to know who I am. When I ask my electronic mail provider to send and receive messages, my provider need not know to whom I am speaking or what I am saying or what others are saying to me; my provider only need know how to get the message there and how much I owe them in fees. When my identity is revealed by the underlying mechanism of the transaction, I have no privacy. I cannot here selectively reveal myself; I must always reveal myself. Therefore, privacy in an open society requires anonymous transaction systems. Until now, cash has been the primary such system. An anonymous transaction system is not a secret transaction system. An anonymous system empowers individuals to reveal their identity when desired and only when desired; this is the essence of privacy. Privacy in an open society also requires cryptography. If I say something, I want it heard only by those for whom I intend it. If the content of my speech is available to the world, I have no privacy. To encrypt is to indicate the desire for privacy, and to encrypt with weak cryptography is to indicate not too much desire for privacy. Furthermore, to reveal one's identity with assurance when the default is anonymity requires the cryptographic signature. We cannot expect governments, corporations, or other large, faceless organizations to grant us privacy out of their beneficence. It is to their advantage to speak of us, and we should expect that they will speak. To try to prevent their speech is to fight against the realities of information. Information does not just want to be free, it longs to be free. Information expands to fill the available storage space. Information is Rumor's younger, stronger cousin; Information is fleeter of foot, has more eyes, knows more, and understands less than Rumor. We must defend our own privacy if we expect to have any. We must come together and create systems which allow anonymous transactions to take place. People have been defending their own privacy for centuries with whispers, darkness, envelopes, closed doors, secret handshakes, and couriers. The technologies of the past did not allow for strong privacy, but electronic technologies do. We the Cypherpunks are dedicated to building anonymous systems. We are defending our privacy with cryptography, with anonymous mail forwarding systems, with digital signatures, and with electronic money. Cypherpunks write code. We know that someone has to write software to defend privacy, and since we can't get privacy unless we all do, we're going to write it. We publish our code so that our fellow Cypherpunks may practice and play with it. Our code is free for all to use, worldwide. We don't much care if you don't approve of the software we write. We know that software can't be destroyed and that a widely dispersed system can't be shut down. Cypherpunks deplore regulations on cryptography, for encryption is fundamentally a private act. The act of encryption, in fact, removes information from the public realm. Even laws against cryptography reach only so far as a nation's border and the arm of its violence. Cryptography will ineluctably spread over the whole globe, and with it the anonymous transactions systems that it makes possible. For privacy to be widespread it must be part of a social contract. People must come and together deploy these systems for the common good. Privacy only extends so far as the cooperation of one's fellows in society. We the Cypherpunks seek your questions and your concerns and hope we may engage you so that we do not deceive ourselves. We will not, however, be moved out of our course because some may disagree with our goals. The Cypherpunks are actively engaged in making the networks safer for privacy. Let us proceed together apace. Onward. Eric Hughes <hughes@soda.berkeley.edu> 9 March 1993 -----BEGIN PGP SIGNATURE----- iHUEARYIAB0WIQQ50QCrZ9W9jAQBAgX7N1HxWH2u8QUCYCabqgAKCRD7N1HxWH2u 8Wt0AP9LgduU7UPE+sYJaE/7TjUcHcHrkBy2zviCXVp2H+yRYwD+IGVDAJ3fD+1F S4n1oYb+QK1HpBSpgGEwwggKQ2iqtAo= =F/g5 -----END PGP SIGNATURE----- �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/messages/a-cypherpunks-manifesto.txt.dennis-simon-anton-v3.sig�����0000644�0000000�0000000�00000000273�10461020230�0030766�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������-----BEGIN PGP SIGNATURE----- iFcDBQBjbp26R2BrGOPUXOkRCA2HAP0W6tT+3ZcqPSXPW001c6melvasBwqxOF+J Rl+x1aevOgD+IbFwMNAX7A0QmHbPKHRSciK00Eha0FUXcZzS/Lo04cM= =q6BQ -----END PGP SIGNATURE----- �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/messages/a-cypherpunks-manifesto.txt.ed25519.sig�������������������0000644�0000000�0000000�00000000344�10461020230�0025715�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������-----BEGIN PGP SIGNATURE----- wnUEABYKACcFgltzBDIWoQSOjDP6RiYzeXbZeXgGnAw0jdgsGQmQBpwMNI3YLBkA AAllAP9fLXh78uB2DBLaVtLxaQYKPLH4bWncaMOv3qhChi89+AEA4UWrbOMn9oIh IlrFPgnupgnajQl5xbq/rDG6ibv4iA4= =DpCD -----END PGP SIGNATURE----- ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/messages/a-cypherpunks-manifesto.txt.ed25519.sig.duplicated��������0000644�0000000�0000000�00000000606�10461020230�0030033�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������-----BEGIN PGP SIGNATURE----- wnUEABYKACcFgltzBDIWoQSOjDP6RiYzeXbZeXgGnAw0jdgsGQmQBpwMNI3YLBkA AAllAP9fLXh78uB2DBLaVtLxaQYKPLH4bWncaMOv3qhChi89+AEA4UWrbOMn9oIh IlrFPgnupgnajQl5xbq/rDG6ibv4iA7CdQQAFgoAJwWCW3MEMhahBI6MM/pGJjN5 dtl5eAacDDSN2CwZCZAGnAw0jdgsGQAACWUA/18teHvy4HYMEtpW0vFpBgo8sfht adxow6/eqEKGLz34AQDhRats4yf2giEiWsU+Ce6mCdqNCXnFur+sMbqJu/iIDg== =UIhc -----END PGP SIGNATURE----- ��������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/messages/a-cypherpunks-manifesto.txt.ed25519.sig.two-keys����������0000644�0000000�0000000�00000000602�10461020230�0027473�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������-----BEGIN PGP MESSAGE----- wnUEABYKACcFgluD2YcWoQQGHDykSv8OxY3GbpUi4/r+lrVsMgmQIuP6/pa1bDIA ADgtAQDFbj31RDpQuNSUDLQT6KKhtow/Pxz4rE0vjcfwfMVmQwD/QNXP3G+bPCDN JVZxXYqqklITogvdDyjrd+8jJSaXJAfCdQQAFgoAJwWCW4PZhxahBI6MM/pGJjN5 dtl5eAacDDSN2CwZCZAGnAw0jdgsGQAADxMA/j2G9ZBnBzd9yxQt+kUWRdboX+LR Wf4NrBol7m0AbXnCAP0cr1PWSiODoVYnFoM3RTeRwfO5kChw6MLsfTBirAU+Ag== =/Q1i -----END PGP MESSAGE----- ������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/messages/a-problematic-poem.txt������������������������������������0000644�0000000�0000000�00000000362�10461020230�0023040�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������This is a poem about a great escape. From the depths of the underworld, a monster roared in the morning. The villagers were wary, for they have all heard the awful stories -told by their parents when they were very young. -- Unknown Artist ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/messages/a-problematic-poem.txt.cleartext.sig����������������������0000644�0000000�0000000�00000001015�10461020230�0025607�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������-----BEGIN PGP SIGNED MESSAGE----- Hash: SHA256 This is a poem about a great escape. - From the depths of the underworld, a monster roared in the morning. The villagers were wary, for they have all heard the awful stories - -told by their parents when they were very young. - -- Unknown Artist -----BEGIN PGP SIGNATURE----- iHUEARYIAB0WIQQ50QCrZ9W9jAQBAgX7N1HxWH2u8QUCYCad2gAKCRD7N1HxWH2u 8Vs+AP4iITZS0BxoCeJbyUHMcArrZbte9msAndEO9clJG5wpCAEA2e7FSZ/GaAzq X5YLqccgcod6K94lAMGzkFgerOSHpA8= =0W4n -----END PGP SIGNATURE----- �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/messages/compressed-data-algo-0.pgp��������������������������������0000644�0000000�0000000�00000012063�10461020230�0023463�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������p�lb�����A Cypherpunk's Manifesto by Eric Hughes Privacy is necessary for an open society in the electronic age. Privacy is not secrecy. A private matter is something one doesn't want the whole world to know, but a secret matter is something one doesn't want anybody to know. Privacy is the power to selectively reveal oneself to the world. If two parties have some sort of dealings, then each has a memory of their interaction. Each party can speak about their own memory of this; how could anyone prevent it? One could pass laws against it, but the freedom of speech, even more than privacy, is fundamental to an open society; we seek not to restrict any speech at all. If many parties speak together in the same forum, each can speak to all the others and aggregate together knowledge about individuals and other parties. The power of electronic communications has enabled such group speech, and it will not go away merely because we might want it to. Since we desire privacy, we must ensure that each party to a transaction have knowledge only of that which is directly necessary for that transaction. Since any information can be spoken of, we must ensure that we reveal as little as possible. In most cases personal identity is not salient. When I purchase a magazine at a store and hand cash to the clerk, there is no need to know who I am. When I ask my electronic mail provider to send and receive messages, my provider need not know to whom I am speaking or what I am saying or what others are saying to me; my provider only need know how to get the message there and how much I owe them in fees. When my identity is revealed by the underlying mechanism of the transaction, I have no privacy. I cannot here selectively reveal myself; I must always reveal myself. Therefore, privacy in an open society requires anonymous transaction systems. Until now, cash has been the primary such system. An anonymous transaction system is not a secret transaction system. An anonymous system empowers individuals to reveal their identity when desired and only when desired; this is the essence of privacy. Privacy in an open society also requires cryptography. If I say something, I want it heard only by those for whom I intend it. If the content of my speech is available to the world, I have no privacy. To encrypt is to indicate the desire for privacy, and to encrypt with weak cryptography is to indicate not too much desire for privacy. Furthermore, to reveal one's identity with assurance when the default is anonymity requires the cryptographic signature. We cannot expect governments, corporations, or other large, faceless organizations to grant us privacy out of their beneficence. It is to their advantage to speak of us, and we should expect that they will speak. To try to prevent their speech is to fight against the realities of information. Information does not just want to be free, it longs to be free. Information expands to fill the available storage space. Information is Rumor's younger, stronger cousin; Information is fleeter of foot, has more eyes, knows more, and understands less than Rumor. We must defend our own privacy if we expect to have any. We must come together and create systems which allow anonymous transactions to take place. People have been defending their own privacy for centuries with whispers, darkness, envelopes, closed doors, secret handshakes, and couriers. The technologies of the past did not allow for strong privacy, but electronic technologies do. We the Cypherpunks are dedicated to building anonymous systems. We are defending our privacy with cryptography, with anonymous mail forwarding systems, with digital signatures, and with electronic money. Cypherpunks write code. We know that someone has to write software to defend privacy, and since we can't get privacy unless we all do, we're going to write it. We publish our code so that our fellow Cypherpunks may practice and play with it. Our code is free for all to use, worldwide. We don't much care if you don't approve of the software we write. We know that software can't be destroyed and that a widely dispersed system can't be shut down. Cypherpunks deplore regulations on cryptography, for encryption is fundamentally a private act. The act of encryption, in fact, removes information from the public realm. Even laws against cryptography reach only so far as a nation's border and the arm of its violence. Cryptography will ineluctably spread over the whole globe, and with it the anonymous transactions systems that it makes possible. For privacy to be widespread it must be part of a social contract. People must come and together deploy these systems for the common good. Privacy only extends so far as the cooperation of one's fellows in society. We the Cypherpunks seek your questions and your concerns and hope we may engage you so that we do not deceive ourselves. We will not, however, be moved out of our course because some may disagree with our goals. The Cypherpunks are actively engaged in making the networks safer for privacy. Let us proceed together apace. Onward. Eric Hughes <hughes@soda.berkeley.edu> 9 March 1993 �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/messages/compressed-data-algo-1.gpg��������������������������������0000644�0000000�0000000�00000004352�10461020230�0023455�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������uX̙o{<i$FFȑ#HHLJ31rIi4oׇ]̓fZ7߿fzI<Gs^%}7׬g]Z%&낍ceƂ`^ )ɋ=$if;y92>t6m¬}LCkh!O2ՔUS.خ'yﰁYo2&M@cxz[ON.^Mf/f8OǪ G#"/o<|ñ1\Gm<Zw=<Yn H)9y\wy9ps>dp{I"m;N?Y8Jp6JN e qBи`xwDBcj:5 /l,wct2ex*|W-_@(.IGANjD}hͷݽbء<Ph)˚U 6/KqD�FonE`fҸ% 6`7}JI)`!1sr!`>@}c#fƛ ^sV PKsji!ngT8~M1gָ 4Lg n0Y_V2L# g.`ߪ$]BBs ݸՌ C>C,TD# s[iC>5aJd Oqԭg%(CJ$=/'1SO`$?g/Ya9h`I?J"`I31y,8#N8@WH#�bDԝoxW2+*݀2/Q2?p;@)4w%iY1c\?<ˈKWeݢ(pXgI?9j՗-hs|f]w`/ycQ(?R޿-7w$(hT(iղyl-P4r%> 6iM4{Kf7^ťǬPSӛGTǽymwCvzCzߨ1l7;҉_2vܛ;ofK =ݏobܡeC=r4L! z9PS]p3Ql.yBa $vpSSg'R B5(ƜML*$IFJcW-0%7YC_Eko Q6}K.zUToK e>jtӊf bhW(TjCcyt3*H?I6EF7 }wx*Յ"OeS"5o6. %tNXn_Trه7ePUGR&+;<(Vγ:Ԫ3zl2,�ZK;^-3�ZD.:*jPmh8_dKbA# qӳsvayR}f/ȎZd[% C-R3BD%S30$a󥳗ї> Gxƒ{Ll(D*Lt^*&#F#*v(j(jvw痭j0 5-ϜGZQ'aqWpt!pt]΋7*:o܆afx٠.VTN`r<;Dprq.<\Ì*PmrJk`ifσJ'kHk4- _rN6!+|W\UpLm .Efr7leH,'<wn -�1,:gN*=F qI Zs]cN8> Gՙ<M09RvRln>}s�:afS y +ܷѤ+U#1>Jm@g=gip*0EQL%y%ԝ䩜Y<`2 RmH>hLg|ʒ-Ψ1(g7QH~K{ae�zGM21(`m!:*/e03&/)j]$MvmlW'\Rre!@m)X> xҐbSFխ˪0S k)uO9匩<H;~?��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/messages/compressed-data-algo-2.gpg��������������������������������0000644�0000000�0000000�00000004360�10461020230�0023455�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������xuX̙o{<i$FFȑ#HHLJ31rIi4oׇ]̓fZ7߿fzI<Gs^%}7׬g]Z%&낍ceƂ`^ )ɋ=$if;y92>t6m¬}LCkh!O2ՔUS.خ'yﰁYo2&M@cxz[ON.^Mf/f8OǪ G#"/o<|ñ1\Gm<Zw=<Yn H)9y\wy9ps>dp{I"m;N?Y8Jp6JN e qBи`xwDBcj:5 /l,wct2ex*|W-_@(.IGANjD}hͷݽbء<Ph)˚U 6/KqD�FonE`fҸ% 6`7}JI)`!1sr!`>@}c#fƛ ^sV PKsji!ngT8~M1gָ 4Lg n0Y_V2L# g.`ߪ$]BBs ݸՌ C>C,TD# s[iC>5aJd Oqԭg%(CJ$=/'1SO`$?g/Ya9h`I?J"`I31y,8#N8@WH#�bDԝoxW2+*݀2/Q2?p;@)4w%iY1c\?<ˈKWeݢ(pXgI?9j՗-hs|f]w`/ycQ(?R޿-7w$(hT(iղyl-P4r%> 6iM4{Kf7^ťǬPSӛGTǽymwCvzCzߨ1l7;҉_2vܛ;ofK =ݏobܡeC=r4L! z9PS]p3Ql.yBa $vpSSg'R B5(ƜML*$IFJcW-0%7YC_Eko Q6}K.zUToK e>jtӊf bhW(TjCcyt3*H?I6EF7 }wx*Յ"OeS"5o6. %tNXn_Trه7ePUGR&+;<(Vγ:Ԫ3zl2,�ZK;^-3�ZD.:*jPmh8_dKbA# qӳsvayR}f/ȎZd[% C-R3BD%S30$a󥳗ї> Gxƒ{Ll(D*Lt^*&#F#*v(j(jvw痭j0 5-ϜGZQ'aqWpt!pt]΋7*:o܆afx٠.VTN`r<;Dprq.<\Ì*PmrJk`ifσJ'kHk4- _rN6!+|W\UpLm .Efr7leH,'<wn -�1,:gN*=F qI Zs]cN8> Gՙ<M09RvRln>}s�:afS y +ܷѤ+U#1>Jm@g=gip*0EQL%y%ԝ䩜Y<`2 RmH>hLg|ʒ-Ψ1(g7QH~K{ae�zGM21(`m!:*/e03&/)j]$MvmlW'\Rre!@m)X> xҐbSFխ˪0S k)uO9匩<H;~? O��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/messages/compressed-data-algo-3.gpg��������������������������������0000644�0000000�0000000�00000004250�10461020230�0023454�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������BZh61AY&SY6 � �@(-fޠ?����` Y`�T;(iَ㰒M"zb#Qh4�=A M0��A4?E=M����ڍ@PQ=4m@��4�`�������" @)DmHhh� Y#1�J@: `b!,>L9 p=fDDI$F9D - NSX~ vNW2lG˖#{ܚ>~|1Eª)dSfFrM௪&I}IA}lĚ8u@<ѸGkuIpίa3]Vn F(r] ] wF1yֵ)v 7OO#{m-#)2m!)+v3G{xmJJw{J>5ҐzXU#ð1^ :^ 95[ "3=ͱ̇]$&W{#cƛ+J ^H(HƳ;apSMƜއwSF_9Y𑛫tTb.6Ab6 a0EdjA4n:x"$t旑{D!4k)k%sBZFeasl]v5˹*dFMҧMޯ=v2UTYH,Db1>rlγd6$Tkg?>E^s̅1ވS9Gp柋bl"^3rye'^Q(s hwօN44:G5FP*;ǺU%ε6\)F\WK  3BCRcFO:`EkIr'*nŽ .6(9/6rZDVB,bExChDJ׍&^/%˅851sS& QGq;N.ɤD%>V\.6ǭ# ca&+0D Rt$ZJ5G<Ս̆:H1M2~Q GT9v*HjelXɖrqEQF:הTg`nϦ>G@J}v<|=THo(^Q`rLkhB]w[L(5\۞1atoS1Jlm_ S$&CCٵɖ 7eb莫"GF3&*73wbzF*4l{s ` ƯEi73잞t-] u,f7d\_Nףp{ptz|4I;ӷ D [X ]i;*؅N@Ed*cQhORټ׍ YYvx nMl내..TQ쇫B25Jd̦UN�j+1eS٦.9M3:7`0&*g󫶳囎a4^0D OF 稠Yܬȡe$%^ Fm\JLk *XY>"$`ies2S12ƍy,4V9 7ha+󯪲\ES^y1Od:U1+, ecLvuJ8I%jd6jI\]:|pp0AQ4B1+%jP6!ٽȄ݊$yJY32[\UVو>cg;*~/"0Yz<OX&f W 6 RDD% w[EjM0Mn#^vfF(׾0Q .Mj-8H*#2ue4`d Y�nI`Ȝ<H<AAvR2P͜6Hq[77aQ!I(cXNLt5&d[R N >[ 'kAE=A;0S1[& (ZUrts8Cy\ƹ/gf(lu,?֕gT;%j3Ȗka/x;B/(hj lg]*4P[M#69:5\Bo2$ " TT_nʶM,"ЛEhJiwKrE8P6 ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/messages/compression-quine.gpg�������������������������������������0000644�0000000�0000000�00000000266�10461020230�0023004�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000��������������������������������������������������������������������������������������������������������������������������������������������������������������������������� ���� �p���p�������p�������B!���B!���B!���B!���B!�������� �B!�������� �3!L@���3!L@����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/messages/compression-quine.txt�������������������������������������0000644�0000000�0000000�00000000116�10461020230�0023040�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������The compression quine is from: http://mumble.net/~campbell/misc/pgp-quine/ ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/messages/crlf-straddles-chunks.txt���������������������������������0000644�0000000�0000000�00000002042�10461020230�0023562�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������***-****: *********/*****; ********="*********************************" --********************************* *******-****: ****/*****; *******=*******-**** *******-********: **-** *******-********-********: ******-********* ** **.**.** ** **:** ******* **** *. ********: >=** >=** ***** ****, ****** ******* *** ***** **** ***** *****, ********** ***** *** ******** ************* *** **** *** *****. *=******** ** *** *** ***** ******* ****** *********** ************? ****** **** *** ***** **=**=***, **** --=** **. **** *****=****** - ********************* - ***************************************** ************** ***|**** **************=*** ** ***** ****** *** *** *** ** **-* *** *** *** ** **-** ************** *********: ****://***.**************-***-****.**/*********= ***** **** - ************ *=*** *********** ***** *** ************ ******** (**=*******) **: *=***** ******* *** **. ****** ******* | ** ************** *** ******= * | ***-****. ** ********* --*********************************-- ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/messages/crlf-straddles-chunks.txt.sig�����������������������������0000644�0000000�0000000�00000003233�10461020230�0024346�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������-----BEGIN PGP MESSAGE----- kA0DAQgWIuP6/pa1bDIBy+p0AGOWT3cqKiotKioqKjogKioqKioqKioqLyoqKioq OyAqKioqKioqKj0iKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqIg0K DQotLSoqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKg0KKioqKioqKi0q KioqOiAqKioqLyoqKioqOyAqKioqKioqPSoqKioqKiotKioqKg0KKioqKioqKi0q KioqKioqKjogKiotKioNCioqKioqKiotKioqKioqKiotKioqKioqKio6ICoqKioq Ki0qKioqKioqKioNCg0KKiogKiouKiouKiogKiogKio6KiogKioqKioqKiAqKioq ICouICoqKioqKioqOg0KPj0qKg0KPj0qKg0KKioqKiogKioqKiwNCg0KKioqKioq ICoqKioqKiogKioqICoqKioqICoqKiogKioqKiogKioqKiosICoqKioqKioqKiog KioqKiogKioqICoqKioqKioqDQoqKioqKioqKioqKioqICoqKiAqKioqICoqKiAq KioqKi4NCio9KioqKioqKiogKiogKioqICoqKiAqKioqKiAqKioqKioqICoqKioq KiAqKioqKioqKioqKiAqKioqKioqKioqKio/DQoNCioqKioqKiAqKioqICoqKiAq KioqKiAqKj0qKj0qKiosICoqKioNCg0KLS09KioNCioqLiAqKioqICoqKioqPSoq KioqKg0KLSAqKioqKioqKioqKioqKioqKioqKiogLQ0KKioqKioqKioqKioqKioq KioqKioqKioqKioqKioqKioqKioqKioqKioNCg0KKioqKioqKioqKioqKiogKioq fCoqKioNCioqKioqKioqKioqKioqPSoqKiAqKg0KKioqKiogKioqKioqDQoqKiog ICoqKiAqKiogKiogKiotKg0KKioqICAqKiogKioqICoqICoqLSoqDQoqKioqKioq KioqKioqKiAqKioqKioqKio6ICoqKio6Ly8qKiouKioqKioqKioqKioqKiotKioq LSoqKiouKiovKioqKioqKioqPQ0KDQoNCioqKioqICoqKiogLSAqKioqKioqKioq KiogKj0qKiogKioqKioqKioqKiogKioqKiogKioqICoqKioqKioqKioqKg0KKioq KioqKiogKCoqPSoqKioqKiopDQoqKjogKj0qKioqKiAqKioqKioqICoqKiAqKi4g KioqKioqICoqKioqKiogfCAqKiAqKioqKioqKioqKioqKiAqKiogKioqKioqPQ0K DQoqIHwgKioqLSoqKiouICoqICoqKioqKioqKg0KDQoNKAotLSoqKioqKioqKioq KioqKioqKioqKioqKioqKioqKioqKi0tDQqIdQQBFggAHRYhBAYcPKRK/w7FjcZu lSLj+v6WtWwyBQJjlk93AAoJECLj+v6WtWwy0m0BALhtJyecpEXQxawZqwjQTU0c URq8XixAuZeXmZjl3b6PAQDfGduoy+AmNUxPyOkzqVWllQwIi+ee5SlRj1WDHZD+ Bg== =OJc6 -----END PGP MESSAGE----- ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������././@LongLink���������������������������������������������������������������������������������������0000644�0000000�0000000�00000000170�00000000000�0007771�L����������������������������������������������������������������������������������������������������ustar ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/messages/emmelie-dorothea-dina-samantha-awina-detached-signature-of-100MB-of-zeros.sig���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/messages/emmelie-dorothea-dina-samantha-awina-detached-signature-of0000644�0000000�0000000�00000000344�10461020230�0031725�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������-----BEGIN PGP SIGNATURE----- iHUEABYIAB0WIQQGHDykSv8OxY3GbpUi4/r+lrVsMgUCXbtAJwAKCRAi4/r+lrVs MgvBAQCOWK8CPy+h9jiWcOKNXxGTu6JQUqE7zoBS6q1MlHk2eQD9EAV7FtOrFvQ8 ogmLSqkkmdTJA9KlJS65zJWLTMTAewQ= =JKYW -----END PGP SIGNATURE----- ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/messages/encrypted/brainpoolP256r1.msg.pgp�������������������������0000644�0000000�0000000�00000000300�10461020230�0024711�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������~VH 2q ["u~~a$w$抰S뮞-? UxHr|vl6=;w0x}&-OH!�)AHb^ƈTb\>'0j:Y2}}LP&?@BcsdM@Ҡ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/messages/encrypted/brainpoolP256r1.sec.pgp�������������������������0000644�0000000�0000000�00000004555�10461020230�0024715�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������W`  �ĠˈIhF[fH !|c'ntYEsJЗ:`N '3t]J'ISe~8[fʹg(;)CTӴ^q>ߪ6'^"z+uA1q,bjqO3>u[e3= i9͹6C }PUQI7&Ҙ-Ъ ;蔟|K~i9RpkYW"3!A}<{sh`Lq:MΏܳ@`y@z1 ?  H: ^Ea's\,eC#k`0bQ\h1z@Ho_la��� F. |azbף׶ -u"NHYib1d^*sSUlq6kS:MS<\U=Y\!'c4Pg(:MA@mAR8>O~z4BIw UUkIg~3PD,Y#W @LO x2[X*:V37Aej|>&-d.dsRroLgA4QO@nk ¤0Cg[n7`3KpV'Pst[w]fCzqYK9`r6[[Y[fSǂD XLmIGa^vR'AZ�f<!K-{1:[r?iG3`}VxHdMwgbDιNLi&"dgM@@t &d8! NƘmxxa_4h /eE9y.H}Bt-ܽi1P/)ln^f=�y(c w{k*b?Zwo<.MyQn{nEOSڲBP"ub}f\)k?J&|H94ZOcܩMdG NyҒ/2Uo ľnWDV&^׃fz v|D5憬2)�6<@qsjRcMqp0=i|f/xsp?ڐUκ_+ uCmPr;GHv4E-te 5n;5 r}$/d:ya\weS6bjTU$ϑCƯ;t& _{j GXSj=rF2 bj֦^B>[OXXX �>!՗TA$k/,R¿.`  g�   � R¿.V �F3(2hį͜؁*<q<I=X ~'1*CRoޤr[H0o>ي=5y1 ?xr`8hsMNInA U 7NgU%=*!Z5+쎝v?=Kp >Y).57 X2,g$ր@w URu7l~ EJ0щ GvP=:ͤZ/?wG\bk%ƂCLL>}uBSOHNE- FqH:؇<h7#+H؞Bwr$j> g ` U|`! +$bktX L~ -CzC u sY.f!p GEm��]A۪gQ6" HUcϑT � !՗TA$k/,R¿.`! � R¿.>} �J9ٍgBNɑ5ӖLimE %2 r?_^~ Krnٽ y~U'\w.MQ,xFA/L ͬ-rҼc{=n"G4;?j7ٙ?:soguc6:ɤ Vz$GL~}`ܽy9 #_a}V|<�**& p l։]kX�x|#r!J )7$fHizrc-GcrC/\aB<UU'^;3 Tv@ ڷ9[teg,܇&< �ws*:eMX���������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/messages/encrypted/brainpoolP384r1.msg.pgp�������������������������0000644�0000000�0000000�00000000340�10461020230�0024717�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������~=e\>8|Hĭˑ⸞-91lrK zS +ep1 C ʣ(lG}su P@?@tڮ|%` v0&`h_mtvQAKqOhe~3N>Y 0OR0@ 蝒V7â5&ޜRhke6rYD ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/messages/encrypted/brainpoolP384r1.sec.pgp�������������������������0000644�0000000�0000000�00000004635�10461020230�0024716�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������W`  �ĠˈIhF[fH !|c'ntYEsJЗ:`N '3t]J'ISe~8[fʹg(;)CTӴ^q>ߪ6'^"z+uA1q,bjqO3>u[e3= i9͹6C }PUQI7&Ҙ-Ъ ;蔟|K~i9RpkYW"3!A}<{sh`Lq:MΏܳ@`y@z1 ?  H: ^Ea's\,eC#k`0bQ\h1z@Ho_la��� F. |azbף׶ -u"NHYib1d^*sSUlq6kS:MS<\U=Y\!'c4Pg(:MA@mAR8>O~z4BIw UUkIg~3PD,Y#W @LO x2[X*:V37Aej|>&-d.dsRroLgA4QO@nk ¤0Cg[n7`3KpV'Pst[w]fCzqYK9`r6[[Y[fSǂD XLmIGa^vR'AZ�f<!K-{1:[r?iG3`}VxHdMwgbDιNLi&"dgM@@t &d8! NƘmxxa_4h /eE9y.H}Bt-ܽi1P/)ln^f=�y(c w{k*b?Zwo<.MyQn{nEOSڲBP"ub}f\)k?J&|H94ZOcܩMdG NyҒ/2Uo ľnWDV&^׃fz v|D5憬2)�6<@qsjRcMqp0=i|f/xsp?ڐUκ_+ uCmPr;GHv4E-te 5n;5 r}$/d:ya\weS6bjTU$ϑCƯ;t& _{j GXSj=rF2 bj֦^B>[OXXX �>!՗TA$k/,R¿.`  g�   � R¿.V �F3(2hį͜؁*<q<I=X ~'1*CRoޤr[H0o>ي=5y1 ?xr`8hsMNInA U 7NgU%=*!Z5+쎝v?=Kp >Y).57 X2,g$ր@w URu7l~ EJ0щ GvP=:ͤZ/?wG\bk%ƂCLL>}uBSOHNE- FqH:؇<h7#+H؞Bwr$j> g ` U`! +$ h0dioxd -k%fh饅}Ao�KzaB\a LqR 'FBX#%K{:3AN?85W �CS~UC}0Kl%֙2})Ϻ)kkd � !՗TA$k/,R¿.`! � R¿. 5~y"? ̝nroX.RVȢ&/f6=_ TÇ^緃ӛT4Û?LrelBbJTlJ8>e/||C?M^{Pjg j$+;bL7k FU궰h֛Oa-dc 9N <vx0=ѝ"?U6ܙLmU nO|;C*j) Yߠ O~Qʀ}N<gWS_>NQ'x:B$!;:  ;:MjQb6`1wE3BWVgV],e@5���������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/messages/encrypted/brainpoolP512r1.msg.pgp�������������������������0000644�0000000�0000000�00000000400�10461020230�0024705�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������Š6gM<a]:(%u˃FYI؜/N,c峺'H]{wCy=S�5[?ReLiehDF9\֓|::Rf"Ss>@܂5Иm .҇0qɹ&MGk5ņv.{\lZK>-\*k> 8T#\E~&KcQF] "a% ̏GI+F%c6k֓n~����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/messages/encrypted/brainpoolP512r1.sec.pgp�������������������������0000644�0000000�0000000�00000004715�10461020230�0024706�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������W`  �ĠˈIhF[fH !|c'ntYEsJЗ:`N '3t]J'ISe~8[fʹg(;)CTӴ^q>ߪ6'^"z+uA1q,bjqO3>u[e3= i9͹6C }PUQI7&Ҙ-Ъ ;蔟|K~i9RpkYW"3!A}<{sh`Lq:MΏܳ@`y@z1 ?  H: ^Ea's\,eC#k`0bQ\h1z@Ho_la��� F. |azbף׶ -u"NHYib1d^*sSUlq6kS:MS<\U=Y\!'c4Pg(:MA@mAR8>O~z4BIw UUkIg~3PD,Y#W @LO x2[X*:V37Aej|>&-d.dsRroLgA4QO@nk ¤0Cg[n7`3KpV'Pst[w]fCzqYK9`r6[[Y[fSǂD XLmIGa^vR'AZ�f<!K-{1:[r?iG3`}VxHdMwgbDιNLi&"dgM@@t &d8! NƘmxxa_4h /eE9y.H}Bt-ܽi1P/)ln^f=�y(c w{k*b?Zwo<.MyQn{nEOSڲBP"ub}f\)k?J&|H94ZOcܩMdG NyҒ/2Uo ľnWDV&^׃fz v|D5憬2)�6<@qsjRcMqp0=i|f/xsp?ڐUκ_+ uCmPr;GHv4E-te 5n;5 r}$/d:ya\weS6bjTU$ϑCƯ;t& _{j GXSj=rF2 bj֦^B>[OXXX �>!՗TA$k/,R¿.`  g�   � R¿.V �F3(2hį͜؁*<q<I=X ~'1*CRoޤr[H0o>ي=5y1 ?xr`8hsMNInA U 7NgU%=*!Z5+쎝v?=Kp >Y).57 X2,g$ր@w URu7l~ EJ0щ GvP=:ͤZ/?wG\bk%ƂCLL>}uBSOHNE- FqH:؇<h7#+H؞Bwr$j> g ` U`! +$ +/Y-ztg^\Odav$`ʤ]}X]oOHN/* ;0p*�囡PUnho! 'pYI ʻا x?(Z}^'W{,  �weAn%{t2:6x G_1r.s3`m ;Z&Ykwvش`."R � !՗TA$k/,R¿.`! � R¿.+ J¥w܁H Fp}`0ژ0~A.zJ2'q7Q"Q{-ĩhB=zQ:tl_)7MLIP)qVܦ8ԝtQ{ I\jL?lK;ʐ42L@bs$Bi_H, _;S=qs~"vgSfГ(]C vSW<33q0OVNJoazu X1 &`bt>_{L~%zAQE>/ UhZ]}0ű#/A@)\HH 0up8ǬފLp Ēum/0 u3 ];���������������������������������������������������sequoia-openpgp-2.0.0/tests/data/messages/encrypted/cv25519.msg.pgp���������������������������������0000644�0000000�0000000�00000000240�10461020230�0023125�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������^̛@̘*ʕ(0- t3J%c+YUG*0}t\2mH>!֫H^<o=V_:koy'v>$a, Gr}c5RH|H3|҂ɼO(yZ:btWުF����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/messages/encrypted/cv25519.sec.pgp���������������������������������0000644�0000000�0000000�00000004516�10461020230�0023123�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������W`  �ĠˈIhF[fH !|c'ntYEsJЗ:`N '3t]J'ISe~8[fʹg(;)CTӴ^q>ߪ6'^"z+uA1q,bjqO3>u[e3= i9͹6C }PUQI7&Ҙ-Ъ ;蔟|K~i9RpkYW"3!A}<{sh`Lq:MΏܳ@`y@z1 ?  H: ^Ea's\,eC#k`0bQ\h1z@Ho_la��� F. |azbף׶ -u"NHYib1d^*sSUlq6kS:MS<\U=Y\!'c4Pg(:MA@mAR8>O~z4BIw UUkIg~3PD,Y#W @LO x2[X*:V37Aej|>&-d.dsRroLgA4QO@nk ¤0Cg[n7`3KpV'Pst[w]fCzqYK9`r6[[Y[fSǂD XLmIGa^vR'AZ�f<!K-{1:[r?iG3`}VxHdMwgbDιNLi&"dgM@@t &d8! NƘmxxa_4h /eE9y.H}Bt-ܽi1P/)ln^f=�y(c w{k*b?Zwo<.MyQn{nEOSڲBP"ub}f\)k?J&|H94ZOcܩMdG NyҒ/2Uo ľnWDV&^׃fz v|D5憬2)�6<@qsjRcMqp0=i|f/xsp?ڐUκ_+ uCmPr;GHv4E-te 5n;5 r}$/d:ya\weS6bjTU$ϑCƯ;t& _{j GXSj=rF2 bj֦^B>[OXXX �>!՗TA$k/,R¿.`  g�   � R¿.V �F3(2hį͜؁*<q<I=X ~'1*CRoޤr[H0o>ي=5y1 ?xr`8hsMNInA U 7NgU%=*!Z5+쎝v?=Kp >Y).57 X2,g$ր@w URu7l~ EJ0щ GvP=:ͤZ/?wG\bk%ƂCLL>}uBSOHNE- FqH:؇<h7#+H؞Bwr$j> g ` U]`1I +U@˰8S44i$M$ď_6(��YGnJ۰5 H;MF � !՗TA$k/,R¿.`1I � R¿.g .rn,MtavhAL҃O{0nUN~HC{fcT̂Mɻܚ1bv"g APBAs3]OuF1ZP%ES}aa@U1־bb DykR\E&pzQ@Km qº_P-HRAݾz7$<%N0V1R? exXIY:YB#UH?Y{g@+yB$+v| <oq]llZsfjuIV@c^clA]]^f([ 'O88~umo " }+zTnb̛̚IoR-����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/messages/encrypted/cv25519.unclamped.msg.pgp�����������������������0000644�0000000�0000000�00000000237�10461020230�0025102�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������^.@/6@"k_S%ϸ3BϾ0h\]0_7Tu\w;̙/#0'֞7~.'IRGA=_V⿫OϙC,X/`Ϥ` @B2Va:e |�����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/messages/encrypted/cv25519.unclamped.sec.pgp�����������������������0000644�0000000�0000000�00000001535�10461020230�0025070�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������-----BEGIN PGP PRIVATE KEY BLOCK----- Comment: 6EB1 AAF4 9909 C4D6 9DB9 A076 FABF 532A 9796 DB44 Comment: Sophia <sophia-marie@probier.email> xVgEYHlWqBYJKwYBBAHaRw8BAQdAT0rTifl9zp6j44CoZdKhXsnF/3E0yv2uzUh7 LNGQlfgAAP4q0sXWe0kwbbtzR+K39las7uzHiPZKGObOH9bprBKu9BRvzSNTb3Bo aWEgPHNvcGhpYS1tYXJpZUBwcm9iaWVyLmVtYWlsPsKRBBMWCAA5FiEEbrGq9JkJ xNaduaB2+r9TKpeW20QFAmB5VqgFCQWjmoACGwMFCwkIBwIGFQgJCgsCBRYCAwEA AAoJEPq/UyqXlttELOUBANJY+rkNIxdewZV4Zb3cjAeW8TP3JScxchc3qhIT9A06 AP9HUlo2+3Bg3Mcwe/VBBMwYvxiZydOIagSTwJAuuxuWDsddBGB5VqgSCisGAQQB l1UBBQEBB0BWG69GdC/O1fBnhecujTQ/Mvvgaj102ocw41SwKiIvYQMBCAcAAQCy n9/2FKzeog32ZQqTB8535jNrXa3upYMH4neGtdDCXBHlwn4EGBYIACYWIQRusar0 mQnE1p25oHb6v1Mql5bbRAUCYHlWqAUJBaOagAIbDAAKCRD6v1Mql5bbRAJcAP4r TlRX4mxr53ex8O65oB0v4jbGzLIQy/Q8jjkKL0F3EwD9GJpopr3nN1S02L78jZsS q7Tc7UerH2gugiEh5AA3vwM= =+v1P -----END PGP PRIVATE KEY BLOCK----- �������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/messages/encrypted/elg.msg.pgp�������������������������������������0000644�0000000�0000000�00000001521�10461020230�0022661�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������[BC bowv^A4 rCǑnq]! A]-;+o"[߆2Ae! 5p +{N,E$c� 5/Zf} =F#pq12V1}T"92QD�Jz$gꢌ}peKKM  4}R?z I<^43O(o_t_[F܋$ D ^;uP@V>UXntѤ"?OE"'Tt`Oe>8\ EyM\ѡߢ8;dx, dcDeʗ1N!ȅ�,i15Hi P,"a\|[X [M 4mY"mm@dLgU˸s1C9E.C<8pHM7$&>3H83H{w;O&SSY*rc}oX 8Nj)h: i3'4P0h~ SwJQ"ob7R3Rokdh,{OຫdVG~a<Yq? s=fY, #@vnI(1zaxVnRGQjoA FkLoκ6]؎wl$37 z^9Tտ~ZʍׯH5tNIi.A]uGjDU[_Z=B>mQgW&OAPL[�  n%~pa0R:^ڢeV�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/messages/encrypted/elg.sec.pgp�������������������������������������0000644�0000000�0000000�00000006067�10461020230�0022657�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������W`  �ĠˈIhF[fH !|c'ntYEsJЗ:`N '3t]J'ISe~8[fʹg(;)CTӴ^q>ߪ6'^"z+uA1q,bjqO3>u[e3= i9͹6C }PUQI7&Ҙ-Ъ ;蔟|K~i9RpkYW"3!A}<{sh`Lq:MΏܳ@`y@z1 ?  H: ^Ea's\,eC#k`0bQ\h1z@Ho_la��� F. |azbף׶ -u"NHYib1d^*sSUlq6kS:MS<\U=Y\!'c4Pg(:MA@mAR8>O~z4BIw UUkIg~3PD,Y#W @LO x2[X*:V37Aej|>&-d.dsRroLgA4QO@nk ¤0Cg[n7`3KpV'Pst[w]fCzqYK9`r6[[Y[fSǂD XLmIGa^vR'AZ�f<!K-{1:[r?iG3`}VxHdMwgbDιNLi&"dgM@@t &d8! NƘmxxa_4h /eE9y.H}Bt-ܽi1P/)ln^f=�y(c w{k*b?Zwo<.MyQn{nEOSڲBP"ub}f\)k?J&|H94ZOcܩMdG NyҒ/2Uo ľnWDV&^׃fz v|D5憬2)�6<@qsjRcMqp0=i|f/xsp?ڐUκ_+ uCmPr;GHv4E-te 5n;5 r}$/d:ya\weS6bjTU$ϑCƯ;t& _{j GXSj=rF2 bj֦^B>[OXXX �>!՗TA$k/,R¿.`  g�   � R¿.V �F3(2hį͜؁*<q<I=X ~'1*CRoޤr[H0o>ي=5y1 ?xr`8hsMNInA U 7NgU%=*!Z5+쎝v?=Kp >Y).57 X2,g$ր@w URu7l~ EJ0щ GvP=:ͤZ/?wG\bk%ƂCLL>}uBSOHNE- FqH:؇<h7#+H؞Bwr$j> g ` UE`! �AN;oP/IȢ0ȗ\CuXs2-jv 5|UJrQTVb6$+n\73"~۰9Hcl5 N0rx3Bn~\$vi<:>#9i+_fon#mc1=gd3 }܆n{4ͦ;MɊ@hfȴ߫(V?ub lu|C_<8YnC@mA2H ˽"QYRN?cJsbN{P_4m9jT(9k3\'ʝ+H )nhzsX�p3̛& l dNQ)Sɜš͍FBMHZُ#B?!PViҍ4'� �\v*8g;#xiMi@}iDH0WbS!إ'j>Wۘ򬃆D4HZŕ<w4d%][ Ad/2E̾Yi VǗh0*jՍe#ubfe0Hȶt>qN Nƒ|J+-c=]I\<WXL Q~`w'̄rMR=L[|Z{MOMۜbHdv0ʠ!OkWcF<ptajT[ 3\c_'`QX\FQA,W"b[y,byCrb[�ܝY:ޛ̛a|}|\@+\*iѦ=H^44el � !՗TA$k/,R¿.`! � R¿. h5:Z*BL'bj [}>Y_.MX~0o̠[ԢM)b+c!hdL/ &qڠ<ySc;yj -w3u7$kYKTwusAOtJ%��wug +#\hv ^뵤H>1Q,]-3I,`n8tv}')}[6>g1FMw")#FV!O>]w1oٱ)ڴu ޢ }]ݚɛ(le}w tLJ%sh p ^{#"&&#R$=0RS"o[�}:=SzXNY�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/messages/encrypted/nistp256.msg.pgp��������������������������������0000644�0000000�0000000�00000000300�10461020230�0023476�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������~|û؄ 9iy?TKxϏ&'8OVj!+J׊1=#V)^/sKdϫ0r\ď 7%cOmܠ,ęhE@[ubzxv>M `OFI~Ǭ:1 ʏhĈnE3 1V/O%jD)Ҙ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/messages/encrypted/nistp256.sec.pgp��������������������������������0000644�0000000�0000000�00000004554�10461020230�0023501�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������W`  �ĠˈIhF[fH !|c'ntYEsJЗ:`N '3t]J'ISe~8[fʹg(;)CTӴ^q>ߪ6'^"z+uA1q,bjqO3>u[e3= i9͹6C }PUQI7&Ҙ-Ъ ;蔟|K~i9RpkYW"3!A}<{sh`Lq:MΏܳ@`y@z1 ?  H: ^Ea's\,eC#k`0bQ\h1z@Ho_la��� F. |azbף׶ -u"NHYib1d^*sSUlq6kS:MS<\U=Y\!'c4Pg(:MA@mAR8>O~z4BIw UUkIg~3PD,Y#W @LO x2[X*:V37Aej|>&-d.dsRroLgA4QO@nk ¤0Cg[n7`3KpV'Pst[w]fCzqYK9`r6[[Y[fSǂD XLmIGa^vR'AZ�f<!K-{1:[r?iG3`}VxHdMwgbDιNLi&"dgM@@t &d8! NƘmxxa_4h /eE9y.H}Bt-ܽi1P/)ln^f=�y(c w{k*b?Zwo<.MyQn{nEOSڲBP"ub}f\)k?J&|H94ZOcܩMdG NyҒ/2Uo ľnWDV&^׃fz v|D5憬2)�6<@qsjRcMqp0=i|f/xsp?ڐUκ_+ uCmPr;GHv4E-te 5n;5 r}$/d:ya\weS6bjTU$ϑCƯ;t& _{j GXSj=rF2 bj֦^B>[OXXX �>!՗TA$k/,R¿.`  g�   � R¿.V �F3(2hį͜؁*<q<I=X ~'1*CRoޤr[H0o>ي=5y1 ?xr`8hsMNInA U 7NgU%=*!Z5+쎝v?=Kp >Y).57 X2,g$ր@w URu7l~ EJ0щ GvP=:ͤZ/?wG\bk%ƂCLL>}uBSOHNE- FqH:؇<h7#+H؞Bwr$j> g ` U{`! *H=_e _Sfcp «1N!BddP*!�l䴈��ǧՃtܧɷh 4E#aTs93d � !՗TA$k/,R¿.`!  � R¿.JA ,u&-F"S|4-i r T"h7CkCEKZ k\$#Y9R;$&QrϧO�c"LYƹW\6LTu7>#/G.cASyǥ>OK`hby-'s1�k;W\ Y8p;ɋdyJz6QAξǷf}74N>IM|lW( kaR$R`e>D17`q_EaK]7'"1W݌DXG-#WW.?X]@ yTV9(XzfY�;;dGѨK\辩?C xpH8'ɲF@����������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/messages/encrypted/nistp384.msg.pgp��������������������������������0000644�0000000�0000000�00000000340�10461020230�0023504�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������>aIwjbpaAˤƂ!ʹ>;MguTc-kE]U<T=Ie~f%Ęgh|-ݫv%$]X0Eq4_4:_^u~{"R3F¹3bùƒVzp>9ec)$yfd\ð%3|&o[6 OGZ6/{ɾ Md۪C������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/messages/encrypted/nistp384.sec.pgp��������������������������������0000644�0000000�0000000�00000004631�10461020230�0023477�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������W`  �ĠˈIhF[fH !|c'ntYEsJЗ:`N '3t]J'ISe~8[fʹg(;)CTӴ^q>ߪ6'^"z+uA1q,bjqO3>u[e3= i9͹6C }PUQI7&Ҙ-Ъ ;蔟|K~i9RpkYW"3!A}<{sh`Lq:MΏܳ@`y@z1 ?  H: ^Ea's\,eC#k`0bQ\h1z@Ho_la��� F. |azbף׶ -u"NHYib1d^*sSUlq6kS:MS<\U=Y\!'c4Pg(:MA@mAR8>O~z4BIw UUkIg~3PD,Y#W @LO x2[X*:V37Aej|>&-d.dsRroLgA4QO@nk ¤0Cg[n7`3KpV'Pst[w]fCzqYK9`r6[[Y[fSǂD XLmIGa^vR'AZ�f<!K-{1:[r?iG3`}VxHdMwgbDιNLi&"dgM@@t &d8! NƘmxxa_4h /eE9y.H}Bt-ܽi1P/)ln^f=�y(c w{k*b?Zwo<.MyQn{nEOSڲBP"ub}f\)k?J&|H94ZOcܩMdG NyҒ/2Uo ľnWDV&^׃fz v|D5憬2)�6<@qsjRcMqp0=i|f/xsp?ڐUκ_+ uCmPr;GHv4E-te 5n;5 r}$/d:ya\weS6bjTU$ϑCƯ;t& _{j GXSj=rF2 bj֦^B>[OXXX �>!՗TA$k/,R¿.`  g�   � R¿.V �F3(2hį͜؁*<q<I=X ~'1*CRoޤr[H0o>ي=5y1 ?xr`8hsMNInA U 7NgU%=*!Z5+쎝v?=Kp >Y).57 X2,g$ր@w URu7l~ EJ0щ GvP=:ͤZ/?wG\bk%ƂCLL>}uBSOHNE- FqH:؇<h7#+H؞Bwr$j> g ` U`! +�"{pv]&BɏՄ̣po&/:ת$|_О(h[n%ɮ{>U"7]'G.|^MYV#h �؈;Ái(-OMe0MXvԖG( /1GͶV � !՗TA$k/,R¿.`!  � R¿.7 t ^:Q"MnBp/Tn}LMfδzS7.#P+,˔؈=Yjӷw~8";(2R.ϬMOarRk!<{g@zmuI@ۃ}"[*vm[8GURsj,Á<Ʀx15XU 7E+LX[/zHw$;~!%[, s?}<KXZ.c"t Y */HP/|ߏc[&9 Jr +@Y0s?O¢9۪_ )9</=[$s̋)EFPHüݒm w6NbNIiE�������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/messages/encrypted/nistp521.msg.pgp��������������������������������0000644�0000000�0000000�00000000404�10461020230�0023476�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������ 8 5 #�/p;Юråw;QG60N!yoGcdbX?UYM'V ԑg8E b?\<%!8 iED7?VѼrKޡZ9aϷrX1Fp0fWsz׋_LF  $L}?NznX> (40kg>4S[5*F&~BZ=6&_5E������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/messages/encrypted/nistp521.sec.pgp��������������������������������0000644�0000000�0000000�00000004716�10461020230�0023474�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������W`  �ĠˈIhF[fH !|c'ntYEsJЗ:`N '3t]J'ISe~8[fʹg(;)CTӴ^q>ߪ6'^"z+uA1q,bjqO3>u[e3= i9͹6C }PUQI7&Ҙ-Ъ ;蔟|K~i9RpkYW"3!A}<{sh`Lq:MΏܳ@`y@z1 ?  H: ^Ea's\,eC#k`0bQ\h1z@Ho_la��� F. |azbף׶ -u"NHYib1d^*sSUlq6kS:MS<\U=Y\!'c4Pg(:MA@mAR8>O~z4BIw UUkIg~3PD,Y#W @LO x2[X*:V37Aej|>&-d.dsRroLgA4QO@nk ¤0Cg[n7`3KpV'Pst[w]fCzqYK9`r6[[Y[fSǂD XLmIGa^vR'AZ�f<!K-{1:[r?iG3`}VxHdMwgbDιNLi&"dgM@@t &d8! NƘmxxa_4h /eE9y.H}Bt-ܽi1P/)ln^f=�y(c w{k*b?Zwo<.MyQn{nEOSڲBP"ub}f\)k?J&|H94ZOcܩMdG NyҒ/2Uo ľnWDV&^׃fz v|D5憬2)�6<@qsjRcMqp0=i|f/xsp?ڐUκ_+ uCmPr;GHv4E-te 5n;5 r}$/d:ya\weS6bjTU$ϑCƯ;t& _{j GXSj=rF2 bj֦^B>[OXXX �>!՗TA$k/,R¿.`  g�   � R¿.V �F3(2hį͜؁*<q<I=X ~'1*CRoޤr[H0o>ي=5y1 ?xr`8hsMNInA U 7NgU%=*!Z5+쎝v?=Kp >Y).57 X2,g$ր@w URu7l~ EJ0щ GvP=:ͤZ/?wG\bk%ƂCLL>}uBSOHNE- FqH:؇<h7#+H؞Bwr$j> g ` U`! +�## ,",.BY" AW @uNRQRt=%/a& 4k:>XcC� ˦e}1'>="^&ѫ{γSLO\A^Wӷ:W$? �E#9^\~Ns1IZ@CQ{DI-o&zGVv Qx  � !՗TA$k/,R¿.`!  � R¿.^ & T#rq 'Q&zg._'5AIp',DjH1ύskH?Hx걇!5I E 6 #H^v0;q^gD V; ãxl.7/&=JJE!Z/8ϲ !"w0٭o`?S$ә~ˮ^ا-S!pIaUqOZt|!^IQ>+|Y;c$Uøn|334ggYssT9+·팥iTΜpJ>+<0{זáoLLWߢ$VpCgb5mRLCA�"vB ekJp`MobˆU&"��������������������������������������������������sequoia-openpgp-2.0.0/tests/data/messages/encrypted/rsa.msg.pgp�������������������������������������0000644�0000000�0000000�00000000717�10461020230�0022705�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������([ ��6P#2ťwp1IpC,u}rZݺܒ"^y !$yɍߺE9[ӕ-Wh.4>sT^sѺp|p 7^s{>pMHo 0|Yl:sjvPwrzQaeL kO  ţtO5,poU� p?W]:]ʑ}'ְwg!Wm~etJ#f3 9(-@ɩ R/V+ڼ).L=B o<LL?(\ @00X"V?S)a vI9;F.˛s\][X=pMdATn<cl>6U oM"@,ϬSLD5E[TV "{:*u  (_�������������������������������������������������sequoia-openpgp-2.0.0/tests/data/messages/encrypted/rsa.sec.pgp�������������������������������������0000644�0000000�0000000�00000007112�10461020230�0022665�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������W`  �ĠˈIhF[fH !|c'ntYEsJЗ:`N '3t]J'ISe~8[fʹg(;)CTӴ^q>ߪ6'^"z+uA1q,bjqO3>u[e3= i9͹6C }PUQI7&Ҙ-Ъ ;蔟|K~i9RpkYW"3!A}<{sh`Lq:MΏܳ@`y@z1 ?  H: ^Ea's\,eC#k`0bQ\h1z@Ho_la��� F. |azbף׶ -u"NHYib1d^*sSUlq6kS:MS<\U=Y\!'c4Pg(:MA@mAR8>O~z4BIw UUkIg~3PD,Y#W @LO x2[X*:V37Aej|>&-d.dsRroLgA4QO@nk ¤0Cg[n7`3KpV'Pst[w]fCzqYK9`r6[[Y[fSǂD XLmIGa^vR'AZ�f<!K-{1:[r?iG3`}VxHdMwgbDιNLi&"dgM@@t &d8! NƘmxxa_4h /eE9y.H}Bt-ܽi1P/)ln^f=�y(c w{k*b?Zwo<.MyQn{nEOSڲBP"ub}f\)k?J&|H94ZOcܩMdG NyҒ/2Uo ľnWDV&^׃fz v|D5憬2)�6<@qsjRcMqp0=i|f/xsp?ڐUκ_+ uCmPr;GHv4E-te 5n;5 r}$/d:ya\weS6bjTU$ϑCƯ;t& _{j GXSj=rF2 bj֦^B>[OXXX �>!՗TA$k/,R¿.`  g�   � R¿.V �F3(2hį͜؁*<q<I=X ~'1*CRoޤr[H0o>ي=5y1 ?xr`8hsMNInA U 7NgU%=*!Z5+쎝v?=Kp >Y).57 X2,g$ր@w URu7l~ EJ0щ GvP=:ͤZ/?wG\bk%ƂCLL>}uBSOHNE- FqH:؇<h7#+H؞Bwr$j> g ` UX`! �۠QD[VNud 9enU}Ёh[ܚ?Z6Ay r3 Ct܋IRzfٟ!y 5mǿqs  A:SeB3p8\R<8U>ٽq.܎/&JyxCPC{ge4DWT@-T� )78 Iᘐ2�b{[DTkX*WvP XlQO*8L# %/-{ѿ2b$RֶyW'y 󫰾deC?ru(+P hkZƐ\ʺV+л̩@=*Y8l0vE��� D׶�n‚t]ܜg[@7GWpEȱ5c頱Nuv2i3h* 'ȦZrm~K|>g2{}ˡ$EQ;tnBF3}Bܟw)`0DUe:xƿfQRz?2`j.p7 wWN?8" zYH _l#l[(�q8� דEqtBv;230# 8iK )e6.1x Sii �Pq?|o{vp >'Y.ٓzl< ѵm^ez t*1m4@g+uMᢜyewGT<#c � ©B|md4JB?INx1Maxstϒ/М],tc1$yKt(vjPX̃8J"Vg05߉ -n-%7L^c{}|5%i.Ib}t oNKK*͋mɝ�LU8e2F>i/Br/q9K+Bq_(}y* kW5!W1I܇_haoY (S#) !uP}]PW#?uy_Vk k.P rKl,vGiťkD3 6蔂*�<zv<(st`7-βllT@j)4�y Uk{b|pc%+xeCBSϝ:ՔWAjaI[~`&釨m }XgЍ_P1Zґc2ogC Mb�ד4ܮ= � !՗TA$k/,R¿.`! � R¿.J �[ V?QH|$Ђ~FAiL.H;tU2hrtOtp<ItA8\ش5E�Usv5m٦ke"V$ K̄Nh)`ؕg 5TL]ɆK`?-x, tm+WD+%N42B2onfq['0,IiCo3aAf v2iBN~ Bb"kUvq-vS`]hx˓G6<rL'|-'^Ҽ-oAe`ڥ@-Pxbb}kI31y%ӨA1:{KL<Ec'+XZPCxː9{n4QR^/`s`Z"'������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/messages/encrypted/secp256k1.msg.pgp�������������������������������0000644�0000000�0000000�00000000300�10461020230�0023527�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������~Eb|٣\1.-KĽtK /J7U 4翆znǑVemYS%0mE, 0groO:^d iVU>^G߼&ߍ˺FZש>)?w` {@\ԴLR/0CDb9  ښ R4d��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/messages/encrypted/secp256k1.sec.pgp�������������������������������0000644�0000000�0000000�00000004551�10461020230�0023527�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������W`  �ĠˈIhF[fH !|c'ntYEsJЗ:`N '3t]J'ISe~8[fʹg(;)CTӴ^q>ߪ6'^"z+uA1q,bjqO3>u[e3= i9͹6C }PUQI7&Ҙ-Ъ ;蔟|K~i9RpkYW"3!A}<{sh`Lq:MΏܳ@`y@z1 ?  H: ^Ea's\,eC#k`0bQ\h1z@Ho_la��� F. |azbף׶ -u"NHYib1d^*sSUlq6kS:MS<\U=Y\!'c4Pg(:MA@mAR8>O~z4BIw UUkIg~3PD,Y#W @LO x2[X*:V37Aej|>&-d.dsRroLgA4QO@nk ¤0Cg[n7`3KpV'Pst[w]fCzqYK9`r6[[Y[fSǂD XLmIGa^vR'AZ�f<!K-{1:[r?iG3`}VxHdMwgbDιNLi&"dgM@@t &d8! NƘmxxa_4h /eE9y.H}Bt-ܽi1P/)ln^f=�y(c w{k*b?Zwo<.MyQn{nEOSڲBP"ub}f\)k?J&|H94ZOcܩMdG NyҒ/2Uo ľnWDV&^׃fz v|D5憬2)�6<@qsjRcMqp0=i|f/xsp?ڐUκ_+ uCmPr;GHv4E-te 5n;5 r}$/d:ya\weS6bjTU$ϑCƯ;t& _{j GXSj=rF2 bj֦^B>[OXXX �>!՗TA$k/,R¿.`  g�   � R¿.V �F3(2hį͜؁*<q<I=X ~'1*CRoޤr[H0o>ي=5y1 ?xr`8hsMNInA U 7NgU%=*!Z5+쎝v?=Kp >Y).57 X2,g$ր@w URu7l~ EJ0щ GvP=:ͤZ/?wG\bk%ƂCLL>}uBSOHNE- FqH:؇<h7#+H؞Bwr$j> g ` Ux`!+� .:m`)LU|o R%NZsP%iX@}DH$%^#aT%h^��w1lmf 0| |wWG8 F)^ � !՗TA$k/,R¿.`! � R¿. m0z*dmp&_%/ o{1VKeA#̦۳Vɳ RKdfrgfŗB ]Vjlg&]u$O0Y< UT9",/ FE%LR>oծ2Z<ߡ NJRZ\n5~8X" e"%?L�#J'yٛn_d aahtZt FL UzI3֭ZJ |Xu*6$P˾B:9誤5*wdŅcBQ60TqbX'eN:캕8=ws`ǥ}I�������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/messages/encrypted/x448.msg.pgp������������������������������������0000644�0000000�0000000�00000000554�10461020230�0022626�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������-----BEGIN PGP MESSAGE----- wYUGIQZgvrb/jojKqN3MxhRNygFT4nkwt1VFwMKvWRvwHyYJoxqbPMZkZmkNdZGl VEv9dxCx3hBIpsbhJIY2ZMXM6EwvbEmETehL2CPg78DBc+8xCUiwUQksYxgSGijR KFM5Dg7i1VP4RlfT9C6rcJwhJMtvRzLCeV2KjhiAf7QSO1Askr+V0lkCCQMMqOHe q/liRinUOzVkhh/LXCm3wHBLP8WwjD4NcxYc5IsMe8diBZ03BXQcVd5yIxfZnOVR FMOTJokJdQoXdkfR8hUbc+FU+au8qkjv0+aRhBhiv360Cw== -----END PGP MESSAGE----- ����������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/messages/encrypted/x448.sec.pgp������������������������������������0000644�0000000�0000000�00000002500�10461020230�0022603�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������-----BEGIN PGP PRIVATE KEY BLOCK----- xX0GZ7hhSRwAAAA56wROsvhqr/mq7dyDVOZee/fuyf0vRbA0dNtcuk8jNaTT ZkaRdVzcMpCyEtinve8ndqbRNPusFV6AACSjVxw0Fqo+1WN1eaJIk+GIawWg YWVNuJXEQQqCzs2AJZv9Zm5cSS5yR/vniugI47ouGOJOIXK+k8LALQYfHAoA AABMBYJnuGFJAwsJBwUVCggMDgQWAAIBApsDAh4JIqEGBEWPkp2Cyx4/PDKF ouu0FJ9bFCmhWhLeHipX40lELUQNJwkDBwMJAQcBCQIHAgAAAAC2viBQgUvE N7vlbWhQE1+fv7DggErowgApsAEJvc0fypX1NSAUOcq770HlIcYXFUY5S4pt nDkxQX8v2SxZeQX5xKePNuBfOJT6ggOUYTDZRQofcgpE52xJTjO+gEYau+ci 0KZHx/AhVu+j2WdTN/sqmZqVUjGSQKE8D8RZ/IRQ7+dIShxV1gMiQ4VlfOWP /7SVnTI4AM0aQmVybmFkZXR0ZSA8YkBleGFtcGxlLm9yZz7CwA0GExwKAAAA LAWCZ7hhSQIZASKhBgRFj5KdgssePzwyhaLrtBSfWxQpoVoS3h4qV+NJRC1E AAAAAHAdIBdEfQa9D9BfEGQUO7xnBVtzbEvciD5B3uEuOaEwv6BexhYSAaNT XFJpTAjfVfUoLggv3RBEwaUGd6isdvZUb6iraIV1itaSotDHmAlBTP2uBXBR d603CikADZXlKjAwouiGdpWGQQvIcm7TCp0RwxDuZRD7ZilhQcLgoVD2P2BM IRDkpO1QMazBbF6QCpy7+i0Ax3sGZ7hhSRoAAAA4AmA2wcHGq8JPgw95FHME e+bMsXh0FVp6Fmbe6w4SBfE4BZcmif4wvfQgDj+Yrqc7jRL4hAU3P9cAWDQ1 rQlwbvvzH3oes0gUBoT3M4MeaPQoic650wyx4+DBM73mLABsMw0RAVv+eQVm HgFJpIlOxFjCwA0GGBwKAAAALAWCZ7hhSQKbDCKhBgRFj5KdgssePzwyhaLr tBSfWxQpoVoS3h4qV+NJRC1EAAAAAGVeINlwsaJ3HxCAR4b+jYcGQgEzpFuK e8sj0uDF0Yd2n/wx0dvOaUjbhclRoOf8pgEvJOJvsaA9+4qsM2/BZLrfq1uA pKXHDRe5ZLN1fHksLZApFh1UnFzrenAAjmw7m+TCvAZFKG9bOviQ/r4aQ6iV CX7ZVU+VHi8pg/hhyxwNpXLmybNdmZu9ZHi5I9VlFp0P0wgA -----END PGP PRIVATE KEY BLOCK----- ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/messages/encrypted-aes128-password-123456789.gpg�������������������0000644�0000000�0000000�00000012154�10461020230�0025173�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������ **COJ'R+ JLXRD}Z̵RȥMջgݾE<6~R;~sdHE ė'_eA|cz@(6N6!<g$VӒWcg=@PȚS}bO&y?Yr Vz$g@-y$ÈygbYq!ҋJ ='L| A.뫌"~><t(s"j+؅#� ͋҇LkFSYY|FC?W516K7}~OTXFrUY6k16bئKa qWXZP8Z*)^:4.j+y#g=9 &!z[m\!W"v5eGuu܄lBC"GrT˭vÇ8%#:uQL> $yu8L:fhgZB/Qks{RrHUX/@I IÁ;$>)ްh,R0zDܩpu1QcNڨ"3PtfLR/eŸ25}T95|"v&Ȓ<ѢvF_q1*[  �K,Dbj~ *x0D1w\ko>_SBk( ֽ3w½Svx2pG&+| B <4MҼZ^Og5ԛƗ�(46E/O)V֫͛-[ (~C-\�<Y)v'9\TծYFdnճ9I~$-RF}QHpWUsf1x~ŝAdhR*wc 1"9GSp^My΂j+KLw]Ӫj,'GdLkNMAlĨ;8jmvXp)l< 9‰x,^}X㥂WT4!d܏3=zT\2$ {`~J27r7:E@RVԃ̒I3ѡYǨX3G\HpƵi 7箬ޝloH_mESbiB@%ήU~YI 8< SY53 z迓p zDv<~406:>_*l 2v!k7[>lM Q`!Cp ՘ˤR:qI(j 0lBΤrqF,t8Co+P*=%"zj;TPԵ4Ye혉$/բ =Hc/kJvxOxZ2Q=~r e-&P-Gl'!2tMбu[A\ DnYltC1a޸ޔ -<*Rׄ&yӋrʫ޺͝$zAGإ%qˈo˫)3~\*D³LݛD2"H0;҆NKp<&Ѵ34lkdXQ: Bƴ pSe='l鵐 <gNI &e&dOdnٯQX%|DZX0b!?P{A?A2:.H%7xf.@0TRv@ʂ3X73!7Z}z 2!|3;�[;.RY~ulzg%`Og K_۽DB\O %-٤>rVN8qЯYmU6O[I�,rP/pT+^+>nNE;xW"ݮݔ$c- b&E'y!K劯[û4]yiHk I[ULp@_ĘF(&{McƌK /:4&C!f V58<@{r3cHةZ{mu)ⰄƤ1Ns1=psz:ylGV3• ')z=Y9L ]U>711ӷ]D,{> 2{|;Cp/r:Fޛ9G&|h{X'juY(HOCp<)V50n"nNtZVNl~<x;p;y,go 躰_l1fhXG|%k-!̯( N[1O5T<N-ӿOo/&[n)"yk 9aU'K>K%v;[J  <bhNAHNֺ$$i+i=r_,-rfB*aTazVG.yiT, Kf,d}meȄ)șvqؾ._CCdJnxC_ cfMTr/DOS&|ѳy@q'qgv+SW|&m}Zӫ1V>Rs<WPOmCAz׮<pz#b\/HԚ#$ǧl]df^{q@M;yzd~&v}a&(@? q⪡+g}yE[$~.Br7�`Pjn\캊 ٦#F"$ͶmKvbι|PL܅IʏNϐۮD&Mz!-Ε2U =,ot p |M; f�MOZSZ9�zy.}y\A-pHfXK>%d3P'*_C?CYəX/ $2Ԡ")ր.h~ﳱ' v )�Oď.=Ѡ@vM a-ءЇ+lr9CwJ>s7 ꃼtD3m 5/)ݽ,:_*j:<-hg-/Zsb(G+JU^WG\ ÌTXfN:*:;PpkO9pj $Mdc=1ҟZ_ 暮Iäe/r)'tFe G)<#1MDKO~K*MJrNm'I K`U ;&=털&F\?t]t {&h͆Gv^$:=]wH�}%Byԓ\Կ$p"^Tpb6_'Ij)Nfwa&GH\å?5-":1549,$Ie79x 18͑UB'%ytACZw|T@D:HP8%Dmnhb/6m:H<쯮3l$*E2~tw[�|Kd*]x`n_6kGY˸(0beאsyE+94jcY;g B�U񞫞_XډRη(oX@ L\`@͉T$r![M2a^qGm]{duLpf,RN)tS@cǒh3gvH8GjiCS/:Lb2{mguKcS? 4&ؙPZ]Ed.l'`6ӫp_>@nuT5HY:i!V:̟ѧn+藁>ƨIya 6]4Rcyouw1^"~lx[,*M}I5`פ8:1�UdV *VIcݔN8@JzTA?&uR$]yՄIu,׏Z![au)D8?Ž R\x߫!(OK\n̘$tijNJ`)#�[&LVMiEU{[& یcϽthd%m 嫖LRK5X&q<v涺t š#e,1Ő7Qi7$x3y7N~ۖڧg` 135m'<Dsyiv aUBMQy+#^:3[8fJ_ m ,8ƙ{g,7,AƆGg˸ȿNʾU˖M臒]Y:q9jfߕГÎ2%4GEw o`><� /=<%{賅dxg8^=^d@=.BzbXv >=4:gck>V"  ETAGˈyjf̷Ws}[y[3s+?9^ۧAl.@˿4gEY?`N/p p8:>]y)s7{i53țri2HV�[LQ`bsL4"67j"ypr~9)\ξ&8 \2O1`4- HE{ CƉ?M)ddYZ >bu~p [H%Gӎ9xTz mgK`˛>_4<5d[MܥZ~욟oopÃ>PDSYuXՑIh*kln]3; -BDzUldKoj'Tё4nG #l,*T<SZE?(G83k)IΏ=W^e�%8+$ LEbZ|+!|BΐВ∂WRF(J5vde-(#8XPCmѼk aA HGy+kKkccd 6[XdL}gT'{Ą4t6>0ObVPu\le]Yc@C@藍+;b-CVB<vj%?ptwۥ yZ\Q:etSj.RsuGd@s^Y0߹.g��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/messages/encrypted-aes192-password-123456.gpg����������������������0000644�0000000�0000000�00000012154�10461020230�0024724�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������ 1` =xeS9eK$C_MD`(��!~\t[t6t}@] _ܓ}Z 0ݪBĬ', lʫm<4=xNC%V}Q!pKdl˶B;+1A[9/ {Ϭ/0 5 ^72;Q@]d  9 J%2ћy:K XROjEMyѼlhKÈ+Od/0O'>I+LO;NT b&TԌj \i^+3Ďu^ΝׂZ<ζ@ ?4m o$p %-b$n-Y,FQ/TK�q2Sۮ׽xyģk̝\s l \WT(0:9.<<IjA㽲suvJ\o<9q,K<!⿕0`8?aSZ$ڦ3 6_3[U[RU�cϼup_2jr\KFr AFuzB7m;%ouQ-F[A.Lv?>/m>|/e4==.HhNN-tzR�wzZ>g@` ^d �PksJxwsq;<F6ؕD` > L"f+v_Wu2mLWq+kZyNBPYM] GDX Tk0 E9e0-_`K}td kp[ hn:ȘK4Kͮ^rO:K&]`͝iIZt 9/r^sw �{#]36` z3+U^;dd>aGR:0B{Flp7Uw[ml]@nuz=QX6u*ɔm3`ר;M&dS阕ߣ^p 6)~לHD՚a,'U~h(8D:VB2- TI9{(텡B2!�)Սs7pM~acP6xlR;*tsbHR2?>ɚssgz2Jh m͘x=yln^kv;&]ftB9-KrkrsZMS`|F*$-<{a^q`Vf`L:yMC蒒Nϟ8 vX.o@w4yM[^{rUm}e̍{iis=^X|$H;R- X&vDBc&vvEKb.N&~n/)m2L;v|FL{jU,˚3-2"||*>;S|glP=&ev ń99FfpoHi!g.}ٕ=fe@;!%PaXUjUc"O8B//r`D"A*I󚞊tڣ90`-X~mswͤӫ>OEp=t@jOhߡ#XI7PMxw_ Bnk!CP{Fr@MѴN'mE!Eÿwl`-`+;Sٹ*<"k {þHW J?rΙ{:WrG-A0=tn 8E#GY.)OTPn&DL/!h%Uh&9bIo.5I�D%asA\EGx`ljrMY\‹<iz#b e >5t9|f*K=cs|;5LWHl ?f3'~q(],7́u`/F  +tw5ߕq1J#L]aIL|;52ne}DY_=vK@7:WW8IС("wޮ}VzGZu}[ÜodɂQ1E־ b4UL9Y!5u1G@u~]WL(D#<*<8A,$qVfg9qѧ̄RɖZ)?ɖW#4 %yPWE=DP3VշDAn|Tr/:2@ĝv"BC%zt9s.ЌV6)-# @*ECDgǞM8b)zCfg,BJ_|O( 𾔪6{h1uwĢ̌d�3?!;Ko&EFq( \YgﭿVX({ZJ"3k{E4n 4$ڤN50$P66 />ja02't͋> =fF ([E;avZM7h9`]o0,Ze ݃TυQBGZ[VRK*TTd�f'y̱t_U48*y_yZhu)˚uF&JAv|f*2l+;37<MiF q_lp'hN  8뢻 z0]7YosnǑd)>+x†w@}[T�M;7 JkN- Q{"zj>|M~4h}NQr_mPI�n[⭘~h@v&<xm!IчkFO*y-Psr+1G1=Yˑ.h¼sQܤZAgBcTpfdeo:f@dgSPA53qp@[>" sB Bd9 I3agw!^:aƛrwW>.Estt n<!." |"fxbӚQ.~%+`y۹P0Q7&Au ߚD�fLd `{ Lm ;&.U~?xs 2m p[W߷j} T$H,/Ԃ]<ݰൖ0NOhz!�zEe[4sRܔ~GlD8If ϐGl!*ځPEr;4*bSa ł UZF ƣ|R%n 2ӊ$Q~4RRe(5y RHcBђ0- 4zoPvI> @:_Hob&FIN26bKUF:+8u҈_Akwo_PU 85RYK:tXT� ‚7(c0Bws 2(4]H|U2ټt">=tEK /ED͗8p#j(a>]&mVl0zA|ׁ` lDL @@: 32=kY; ]@Pƪ�#;w4i�(sbeDž(P)cVk-3PK.Oc tWk>weh0uo·XJh,O! ?Yf_͂Zp:žxDOmD&kfWa ĶFB~# :*۴j Lz @^!�9VQ,I{wz9\q $O/؅rLGً?ݼƜN5T %)62,LUpBwfK=݇hqj".;>qu?t"?c c3SgK&eA2Wn@=ߣb)Ĭ#yU,#//F]KD΀%<AwJ5x_љֽ{RazU"uaꑂ&Ϲ7tz'W]IC:jB<aO 's<GʅiG,7OFEZ'~\qw'`NdjMJ/'�d@%$3zڿ�LAY5S4 Hf}z)?pCQ )>k5 3L=WűFjdKʛҥ"J1ׇ>D3g=W< 0+u&zmRY|(C{EkL “[@OC#6uJTy*U3%au32so 6Rv)lzJy3cKu[H@[cKJ�A֏s ꢑ3I'ϻҭܤu 8@+}ƌ]Yg YR7.PtȘd@M)czW-~ΗHb-Яx;p ȧ?L�WOEӅ""Q G(B*:O UPbʼ+2P1(o 9ɶW.gks=ͣ龋t7Bq~es^mmԾPRsBnpH �t5ɘnZoX( eR?i=.[6JZaEJbt؅k!pX<BbFԁ.. 1;$$F3PPГ 3ӳ/`V&ygs9Pkd]asx}%S8dQ ^=εJr {hj݆, -x]Bz2}ʭ7;W`> ฅ2:T=ZZ:Ф5xt7nE un9P4AN#|L~54Fq7~FR$Ib0/ a1�s Yu$̖*V3k>A0! Xv:! MV@h.W5ҫ2f. " '{O䒿AY49b.M,,rb\ɏۥUX:Caҟ#]k=PD4޻3v\ %PXi7FN��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/messages/encrypted-aes256-password-123.gpg�������������������������0000644�0000000�0000000�00000012154�10461020230�0024466�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������  26oi!4}W?0Y^%.^tysִ@Ym-7 EW!;M <[X,xt5f,`"dȾoySL%Qj h@1&!6L[fRWTO d�(BI5/Dw*CJ>8t_5z+8x '椙*}MIåD�$O}M()C~2k8YJ&R8Oc!YեsWNWlauAI1G7׭0߂E!E B$uVsh%ꄳܠ]�C'>ܾާ\-y 臿YD݆*BT.! s�I'SNb@O<Q BHu $EV5 8й!|v2&D{!# 0N>laaw6Ƭ>juѾBjj-BZNtN^*J:ߓ3ΉEw8+Rp$"GlґIRUlCߋǦ&Mwg _̯̍RLYm)P~_tj19ݯ:2Akw+'oU^ԍl9q%̙MU;bt)H Lp2&1m?T_<IG9bzzN/,E4/uC!6:uaش)KvO @J9mɹ wJ[fJa稊 +-~ÑY|VR&>#2a)sC'يc:a&OB %vr8v_.\zVM ⹴1W8"\ P(bpZꚀQ WHv/!-h@b,pw\B6t?zy)C:G)>h3I[`(=@$88Zn0UB,7r/A<vGY*#Zgheke{dM}}ui!NC�⛡:)@)4Xa+6Ჴ;wGdePGgSiHcB`(j/ Ũ=i 3sH]E/Lѱ3P=c&^{o)jC|O&ƀYډDuZYONɨc,DȌZO_1;:'J02YQYT!B/� ,+2Rª݇Wx^XV)9 ޗ 0e6:?a1;u (E6tIWՃe KЃV2,lPlyرXQ"$JU GI~s0I0 'ĮKw{jIBaq(ƻ[êBe~5?j˧t4# %q0׋(< /4!o~&Cq[fR|A@ȸHx@JEFVRFޣo0r"Ls4^5lXx0&8FUW4a.08}�g{\1t <no_)kG˭lS.hR.[ٯq).]vAk G0bjtp)&#Kɬ]8'l7l.=DŸ-`P=N,Tgs+K*R.&йtPbʨ =Y V6>)-Mʫ=!ٗ5,P҆z.L#9߅3"o2L,'፥WJ:PلrClu-EQbn|WXh*MWhBvB.bm MDӉ_W|}l5^L(lVo8@H⥼S*V`.~$ n(&.0 6Uo߭NV.I$JF>T!k/ۆv!rv܋\Xk!zES˖cr-"^5Q;@PI0ηN[&, ̛5""Pk2deaM!m"mΡF 6Aǘ/a<Mt) zˏ;Ui;myivGZG tUw6t+5jOg xƒϦ:S-sv7!PLT)L_BL0y#5j"U$O~q]f6�V3{ 366-H0wYZ<pm2X9oU6&ΰPTcO+ ^"hg>A=Q |iHe -W ,nki G/&qBupQ9h> 6002A&! JOۖ?4ʕoGݮsPjaoNb&R!UtohM7^_?$p_tAuű}aՆ`yx{c \:#RN+Wq=KT*NPDmϹ2^9/6 -%+jQ<GI0jg8=5?1Dl.I[A�{׿>Ȅ-Wm?}*6Xt֜=akɻMb2oxO#ݪrVf@Ͼ{(xDLzq.e"VֲG[[ew NDn/Mk`O5!n+X=8AO5]C`MG$/ DӃL%⹙BFѽ&غpLR4'F\!%9鷒?j3%!{sy48=˓U&%^a2Ɏ2h&�3t+{tf\w{]X5+>}3pO1?{tL͆,ȋPs#K{n =6x/ {bZn'8 D|bH.Km$7tz| M 3z:ҟ6Z*[74=EDA6MՉ}v;Om{"X~LOPB]Z #uz  1p h NG] 33-ȡ7z/2B&vGט e]孏 2Bi흦F)ѧ(&B@؈/ktȆ͙!dE|tmY7 Uכ5[X3{t&Gx?_<fPn:srIDSQ81@^HȤfޓ@T )ySuplgaڛ7 Q FȽPS=XL>TS3ؘeٲ.Co!=H+6E!!aers_VWG.35BM+ۮQ<C�h0!C%$"lN+ñK!q\9Kqa; 枹P)4b{cmͫtG>+<6\n/sԤĥb)q[Øv%M@UEdwDp.ؓBZВ44QGpn1v)(,<<)f,^�^ܭCvޮه|z.$2h[uԛTl~U5GjwN>f`j"f3.LBG7:+wp 1D�naq4wɦXo2OeyS4f 0f(R/&073Gx\,e;vd4 &Bo ?_[=7w<QrLoɑ5߉ g*A9]WiBƌM^b5;CۃsKE6b--$[|AJQO5~';(.ű{=qд6s,'Upf&ʗpu1޿+8gQN1(!tD[z䡛eAFt ?͵8=J=Fu&xBA1-E#ۮC]N!5Ud.x-<vaȮ1pMQx+7]!}g)Laz4MQr0qˮ>C!ۂ-`GQL,a9YL02|H7N5SQT@cwvA{P2ǀjZSZ>ƵEa_۷ 9y+!5 D�#'ƕ^򆭺-tV%ޛDX3y #eoݛ Oo0oC+]q#e!݋A00ùGo) 4z(@`8jW8߱ WAhm1 c1Xdg1ivN =-6yt-Y:/Qn% jiU(spWb&j\cUyO"~ߢ[`~ ߋ [+/:`b%ѻ$.?Ȯώ_\K`�(M$tyL>@ AwN 4J4),C'O|{6"?Lt!|%uw!& w+`,R p_h2:2*s$O}ԑT v&KI<ITp=~L./:赓l[<nz #%kOqC o Ҩ_NL&Kg5T DJ%HFG͔L[V 记dlio2'*#TUr55J m<tPƞ1P@n%rPzo[dƂ!lĴ16"r2s#Ӗ5PAp�t%UXcPO7|W5wv LUԡߞT93 ' v=Gzۙ玎fdްEv*hT8Xɂ.fO$!(z-^0xFkCYP_߸v|I'Wms#5҆uf4ΝSRK�rkELxϪB:Θ9fȽڒ(��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/messages/encrypted-camellia128-password-123.gpg��������������������0000644�0000000�0000000�00000012226�10461020230�0025463�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������  <Pge!bdWiSӳ`X,e.%ͫw~\r�&(j?ݔ*_;iɟ ͨ ڪۆZ0˂eL ˯JKTgp5u7C R_}eA :Qb"Qt fcFWJؤDKd8 o1ʺҐ%g`h#FT=#N!(^h]~ic@8ۿAu*5C1)P,Y^ULPi$MTi@C:{B^J ,\F.pQ;xB(Ba)T!꺒m#ny"9<CtܦTn7njƗ~~A`, -$v)eJn>N@h }R}u/@[rқ'BNVT`tl|~$ϳ5Q,P>zH|6Q0"_!�Smr7`Bէ[c=lW$ۗ]XmdLp~ʂΞgR=ɤulU[(ԗqW܎%FafIёLnb&Î] D<'bTrd1ۅd RO$Mb78d;_ 1dנuJоF,Hp~5-(صc  YVӸJmlV=hAӘzd rN!5V 6B\ѩP/5Oz(֜=;pLhO" xÍZPL9z;=.d2B.IrKL 9h?nl/g >]Aޮ>B]~[HL1.)!5I(1jPp=,T{~hлZt],Y맵K:*O]wS-^P ,@d$#Gt=;5b̋nӝ B v\*1̽=]fm!)Qm+ CKPK(+*$*Iq6QßZf0qňjaBTN=orUg N:/G'}Q J/^D6$5P@Pm:q!A[PxCêNK7k~o|HVey*-*XѬNOt3u[$lDB>3u) yG4ڙWY7d68_SSIB<vtoQ :Ipy(M;tsu,>8[g_/&V/_0%UG|;]RE2b*K<)G5{UP{+%ޫB-r|j/QYRwu?% dL PLbw$Dm9Ը{A>-tOhu0ՈN*XG Aœ秗bEv )!A0)J]"d$*F./2h`'{GļB;lj<[..Ŕv/Cawx!tEd+GɅa;g-H`>lޝn$A؅>}\`0O썧!#+j'ѫ c"4pYP*[m)yc@Ư݅P4t7B Ӵ,4T2'Td>[Mu,I(Y1*鲷d`%Xi^Oϫn$)=HRS?b92�d' -][_z�i력`<֞,C`F!8"o9kK%כ&#;7a8G yXtBd Lv07}9LlEmxq/L-%aEŏ;��Eh tS>t.sfr&y;|j]1H9':!NϬ#`=\~Ef<ۘQf .wB$N H[4zF5+C+?+L!qa HJO>h+'r+&V.8TE . 6tUg32Ĝx/ ?R?Ew~ gGlBEi֎MPUjѱ lbb*[6ٮ3eC j5 ;{)ßs۫L.my~7(Y6lR,gQ4aKPh!"o\@+Jp8m)HL}:6ќh|9HXpk3%G`#Rk"^ygaCV8bYXue 13uu vn5B1!OTzRDP-DRC{2  s ?_|1~2J ʭq+-<姪< 1sMc?<Ec^ZѻxڤKƜxiK_Z2RTo[Uoc6-q ){ j͆P,|C_NbLm~n7'`Ďн wTy`Khx-C)8>|0*!U:}y8Đrb+\o:>IFD#n5 SH@.E|;щ&0w!tϑ̎C]_ <=c8yt͇X>]ܰ?jv3{5&5_9ْ*!#pXA)Mt2ymjjͨ_B['9!.}CHm˜�=uڭU9n{qTZ4 D ή*\Xf){pEm ,tpNXytO&$Sq܏0&p)g* @lR7sz1mb8J=HI\J#"&fǗp t:t5ifYATk{'is3ҋ~N70 UaJe_=ߵ  пIjb+l܈!9Lle7' )iUsOg5Nm3vH1($ytڡ݅\<,>RYk̔J Njv C l!:=~L"ߗt<U '-jx c[K*‚k';}#J!Z\'i:lF )';&ᕢA9 JԄ E67fQym\{lq3/J->V5B&n.SNg2y2\;K7W6pik̃uXh"=Nk!tM!Dl2/Q}"L$L$Mk>]$u:毯`+ ߗ0Ȭt>z⭨HY$RBlfI_V1p+ہ~$x,MR137wqP.XWeF-9gIIe MY]SzCd{&b�0G081UZ4@k4E ؍u7 Eu6%+.b}7/,Br`e.D`('I'6,Z?U 8 :b]LR2E;ISQoq,1 Ef!mfX=KW`i?+%DSF,8n) ihu7noeǿYx`BjK�{ n0_L"uN *_QiC!l=f�.h:kdu&?4tcw4'XOgK;^[L�KL I7v;3.fbIדIWTrY  hOn!AcYԽ y'q6;]-0lmk]].p(b^~_Jk[;ߕA`pB}M7-@ϊkmCg-$H(GQ֞ +45<# M<{ 3KR"ߊ"2bH9sn=-eNr{岏9Z{U&tXلg?JS5/#-k tHSp&cd`6]EQߖ6IPF#u-v܊ޒ? nQTJb| t/2H6@逧MRwYBh8~kp _J`,na LJiGSJY<N(6ŚӃߛӘ{2bͦVP:"^Ed jF*40+"ѧ\�xȒ9s!TxLE:o jOF= Wbwvz WZ8b t=x 'aӷؚTDO.~3w9s׮ӿv939, R!#9MuA( em@}o2ë7ӚlN ,76_Fq0zC e]کbWI8bت\/Z Lǜ&GFIvǫB3#3yA HbۤT([>8<[( mU A̍R4Kum#ە='[o%HN. !Yߠfn=C|vSsGDA[#V\f{mB3MFA V6> .\@ٌ#nN <[j� Syp+؜XIIȼͳd}YT}tj9q2'G@?AN5 K%[l62lBW#$ *fU4b5Qﱕm.nѴdE?c_f+CV6m%b:Ϡ%Frb.}piV"Z,\pQ|A vO}8v谏)ED?:;U4^!eDB4VzW<72p('E!\c)RJG5AˀjF<;xùuΧr鬸]b JSKnC5_(H[eBo\\xBGse IzD"S<Mg_zY5�>e n0��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/messages/encrypted-camellia192-password-123.gpg��������������������0000644�0000000�0000000�00000012236�10461020230�0025465�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������& NGgIrY,A-NVV"ӳbYkB6""%!t:EɰuP vr;H.h$k ΎJLWq^9&UEGF ݶQe zx6;1m0 uQW¦'JW\s/{m :SI[;g6{t%°FwTP@]Veu^Fӯj͒Z"9 5u$G#@U]7! [}6r$V,å88ъjwUɈ͗u|]x&};4+^UE[:z]Q{>3x5*n5=KcDOUf W֥G>3L gd5&n:;_y3Ni^ΰC{HcEO Zf,](:;E,XHVP@jlg7r,uaawC;xT+3R<`FA5qN\p)==.ZL)6 ,{3"|jrO]7kPpq*JJ]=2tfҭ=I䘒)o Kϔʉ b%z/@1zOKn$B/<}xs%U|'MUikt@`(H=B v0  [T|8�\3_]6x[vh{'TϿVUԋ#YgItx2l7Ət}NmV2iAw`@͵ LUwn%p#%”ڄYr*֍"�2*dJVc՛Li0~Ksd"rd9-9ush;:K!ȵSmpaI|tt((F^Z]O%ț}RIe.N%S?%xVo# Jzvj$8=8̔}Hs܌mqr6U?z&Oe(hP:M+'vQW8w:lɾΥ['ScR~tNYz#QzF**`?qenhEKCy ֗md13Y͆X]\,WN$>@K/i8#@w*3*I+ݰShEWV?2q#~ĆXx$0AB.nmN_2EOodS ]mkU9i^ s~Ɵ Cpn7x+<bXAm7>?ho)bd6 .WpI?y^M?:YܿYnE&ϻ@)F,;x8UM�5'7a,"hC;t\HVʎ/"17m)r_l&" tf}!n y/m7)zJADܖ  lA5syLI.9KPc,0Ÿ:٘ +H5hOByя'A;sZ\b&3sW'b*R K5L٧9{lvȄ nT9jw ¦Ҽ '#T eL7s27u =D0ma\j"@զnD^E Y`[I;VDc4 RO-mjUhC%r<177]}&Y/ X\ %’%)#n;?n`W,qI<�- w! .z8T�L= PrjB/Vȭ^h9B\Γ j} du gYq['2(BД kQ˃8'(`dꋹͳ tY{}W+\ w'ђ?P3Cu$; grk GNk}(4h$Ԕ20mogeFâυG � '8]cïp1^( ͒A) S{OmzǞ}g9-/IwDO+$AuuunÇ&^dijD!�5XTB}^3>Ezŀ VU"$q~+Oa6-ltVvtl/d�5Eȕ _*]g; w10F{Fz1]r+U9R듲JSt؞|+/-YHJӘXX3ɦU{cφ-qm/pW.qWRfX,O.|&ӘeC;O54b|n\ao:jRu ʧ_;q}C^0dzoH*4ɏpuN2U{ AݟMd?p䜄F'p|%D,st̎Qx +ҪH!YN@_nY?0s:-OsIHd7#N{gtr}sZ܍ طkz  nMԂ^etbs.9IXu9thz#ԣYA[PNRG}*Uٟ9`9i=?^#=BDW3 e%vDʵ·F6ɑTv JٙmޒI;uo$]Ȼ]"/!k=0iy jYʮ L0r9z,H<�rNhk1)8ˉ>jܢB7'9@2:WcXyiIR'LD{&i궇G� J�EB/$xy%Z ؈5Es>ñ/мç}p(^aˑ$�_tO7瑓v51/X|97͌YYm+3׾%ؠNgh׼ZQ׾e,;ί7mT_d5op֕Ud@WCtJMS4Q#zzQW?툢1x]՞�z];6sg%W/ǒ2l}R N"cɌZ1* %+[$sdGfHc?q~OƔXP vef%6 NМ>r& )q͚I%f@rB(~8_k` 0+hw(;7[`4 m#K>NF8Y*}㫪i?'j% SČQ6d"2@<њq'5:qO'6Ч3IurPEHpJe;3 !Kq= T$w%8T/ֱǺ{@( d]/^JY }NznEQpJӈ5! E֝EvCio \^%;ѦOfIѹyb X.4'4e4m rjwkH35w4 h|i4!5Z(5J@G:]vz% >4Jٻ=Zn~BD`5_œv4X�oxÉ69^BtC%ա3j~Ԧ2} Cmo[ͥ|FKCΏ5scŎ)sMTCګڔX`qq2"9W* K׃Y?L (Yw-եhr-iy9/&۰e+`eK,do5ȓ4ba"y_<yQ϶�E%�%`OR[V oIC*4=5—!u5Z/YvtAtFgv ޢ"^ ^ Z y笳l,zsW wQ'Z A 5.o,"f)y`\ 1C^CKu҇}$YΨR`]!{�ZN_6y|0-j|q_6{%[_isTRkGl-eC2@K%P)k|X#vrLH$K6벼%.XѝGc:+([GUuȱЅ^Ny#]岉ݼ-tTtNז^L>s?D>h+7ܼn)/9ѐ6>fM^͘qT|#҈ =$`*1p Vi"\irQOrl$owΝs"4%z]_R| 2sAǁ+UJ@#adn6FZ_k7͹'%N1lNe6aDFoEOiGeK޿I �\忂,TS_"{vbB_g [le30 gB!E:0l[8.By}V-m^G0Z}qxY%hBrxf}o >-TnQiagS%S1p5Z\ێus0[In5kKYlnG| -QV˪cݨqއr"K)nzbt:C"yɹqZ9�"H\5T{C/8%sҚ(VO!JЏ[;NkwR3!ք"b`GB,Mۀ|qʪ\ieKFukCX@d䆱qR f�k&b]ve �˟T70V>(CaYnDUB|<j3kB y7=m;hqCw-g/ I׷ <s/9#' o0%pPqu/1~p剣]vH$p0O,E%6sWhݹ9N},?FMeyL`{Чr"Rޞ93XMPc$4 7\(h�e������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/messages/encrypted-camellia256-password-123.gpg��������������������0000644�0000000�0000000�00000012246�10461020230�0025467�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������.  +-8F֋I, }AlGBoӳN>haN0H.뭗ng82nj3pҨB}eIr<gPZu! gz&|(S`hUBa [WaE "9"Oۖ1NIA,U˾?rKm\պ!芼Ri  M0@)zѥjg]*V8 uk94RiHo_({Ʃdk{S%oQ X.�m |Bꮖ^Q$c3jٛ0&i0MN*N< p$m}v>#E�I%%$P`oǖeQ>@?>^myY/L8SY&Z'ƱbC6y) sMF@51Y/-in-/qCAh͸ 23#gX==MoaS#X` j-nCh`A$1{y&Xvl6 ("*f'd0( ˙l?v Sdh [}/f!dn2p<#rM-t9R1@}T=)[]'"YBm]?ki0<N'(;gݚES'd:9Ɋ/7730 /1*ACKm m{*,12`H8e+50|+CA)H�fMo.l6gaFoA]iD ԋ]Hq(Ɏ$GwT"G#6Rv8 6F`t8QdN9 OB⯆I`U�jTfįK�jԲl7@ya'od@dlzߦyؽgr5SdK`%"h\IQ-?u$qgG;kg>"Y}�OgtSϨL;4LnFѩ 1A$/)e$){/ie'"񳋆%# C Ăb+܋GI> X�v%Vk`g <@4hx%nC~:Q6ߦg]^V<%8M& Xo3T #ˤl*`΍B->LUWBG~ڱ1{Uњ%OIfjm{gx@YJ*�C\-KmC5<S5pP*2vә7b Y?U ևnd:V2rRO1'Qc(_do*Dv{C�6 E[lFjkðp#xﲟxM"XTgx~ԉcA͠jNFĀ AmH (_+!X<۔y/<oo Ht*BZ{kZ{Zv#q]nRG ãE۵aX\R=MC|p{BP"JyE^F-p??YC vgܷQPNMڟ0уEf%R(c }.Y'Ԓ 49I,x{WEEr![z/d06a]Ν]=ֈ]aHhLW7RPZh}PSQ Dwg$ $c0hԥIlJ/X=um42h*h\&vן#S45rnťp=dOTygE3߇,S& 欄1<Yta赗iX9Vkl.$`i=Q`Dz޺?jDY8HDӆ(kzIV$jmK..(' LN}(s_PP`ڐ;ؔ-v:l|ڳΤJ-%�\ Ӝqæ*``Շ}euKˇ˥wP!hL2i rQpz"~tg8&o'h-J7|Wq]Ժ׹U])D2^ب(axͩbpu^4:Qȱ)H1oBb!zpqY z[]nܠ3iY~^:HƷ1Qvaj}0{g%4nE2o1uJl[ QrxCPiǯz?M2;oQu\̓IƼ+wZVfMj]̯~jvfW<:=aCp=h(F}#3qto3s3Ve ߡ@dfKm6 GɺAa1"Pya Dž{k�@BX ʜ;EY:Q6.]�V%G:۝-j9+@O688@S0r ^2|nڲn|nL2}dCW\o*/8)F'H 5qmގjS(Aj~cwِLpx"}w[0'T,LH ep'� ؇Q|eZ`3U\V<d+3/!w)$@ n/eFg]Р6&8h9q,MH f0nE$-҄bX`B":.PBSG޹s+$ 'ş1?ȋBO3W@25"ͻɐoplyj/{!L/[tt61ur-h/aBo&gPgLs~z8r|Q͛YItφQw((nf,<1Y&ӟg2[�(zmX܆1$k -Ҫ9 |.]ݎD oH!O"?){dYc ri˟v V6h3%؅5 [KJΠecii\URm=LUY:gl  z&.nN$r_c+ m 4D:<Ke�xZ#/wX+o'e+i WϾq +~6΋b܌+?x/i1]0�,0eeW Mk ?Ka !Q6:qMnAZ-Aj% ll|٦A,ZC83^h!!M7' u#>iE5.Z'" ZQ+&Tu~m/d ƥPc}/邯<ΦƱ.$(EokUFN&٤ĶY-u*ӣ<9b5#zj~oڧ<~VZ-fBӏ/uxS Qlfg~ OnBW"4q;wc,Eydݘ~C+ODcJ" OIUئK ѝ4,xy#\v"�^ٹz 􇌔C�)|М1 ޗH Ս 0؁{"_4`rlg.狖ʊ0e\O:=nQ�{n\yZ;<WQ33s+'8,׼NEn9֌ UR%*?߶zBd/ޏ2˅P6(L)e~۶fOHkf0K-:Ӕ[W;\i$|M <9Mzhqp7lBv\Z6ܪtXD`"Za5ng";35YlX8yyr"- yS:欺y+ޏi}#qԸv[O"H5o1"nhp_ghZѦX0`߇uK[44=~qP1h=l៑*jOD _:v otsb98y"m>)4[MEnSc0S {pІ^F(g{W!x~Z[&4br}8uy]!;{kޯKl}*oӸJd}bGQ{@CMn,'=&7V!B/@xpB.e#DV>k頑ڞ؄8Zp2�Hbw#DU,sK4 4;C*$n쩈JEh<RrNu$O\%ۏu`FVD(A S̰f%H"|5|%{*!@:MU=vR/"xpmĺSW*jH9ws4:Vm 0qf11-:纍ؠ hސq؆f$5EJf1d|GlGR"-OE6"q"3Mj!P42* &Zi.RҷT\f=_ Uz(X/eG8*ᡵЇ`>\{=$=DԖ?SkkZpBj~<\è73kQ]3 ZQߺA q8m;(g2eE } 83EQɀ0E1*z #R]+ bE?B{_p#Q<oT)ԝc?0|B#"6DaDt+B38b^T:]8 ]RC{C`Qϸ!|0K7 #&)~H �CSp-&+/=n#g pdxq"E-׸?x,[n?PY,xwJ<hVK|)6WzR_kK[m\yxb+P%nJ4~{Iܝ$˂I0dVfX[yQ̑&Nq`p03t#k1ޗܾ|צA)O-j$-:G8 pFy`Yeh����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/messages/encrypted-to-dsa2048-elgamal3072.pgp����������������������0000644�0000000�0000000�00000002313�10461020230�0024744�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������-----BEGIN PGP MESSAGE----- hQMOA94KOtiZYfThEAwAlLLKkVzFu89K2z9llCKN/NbLloZmICyW+9a8HRSlEssc bsklKtqdFNiAd8euW3g/G27FMUAe+x46WCn1aOlBN0uJSwXKSoiqXWBHBOa3Jzl+ 5t0eM6eE0+Bs6L5vvCebYQxxNOfpr2UYq4RU4lm9mr9tIaNxh+8/Eb948PnhCqz1 7bmdrkYUSxpJl+UqS/I0D36cRI3Xfd+nGJ/KKcaAlo+Z+cTclLzsALzD/HKtWlsg BGI45q0DE6lTUxtG7F1z0j+u/jd9DLZv2dTsPyAVYb2cD66l3yghUe51xyJjnJG1 DgqfD5rlrOFaZ+YjySIbJxjg8ntPSCU5wV+9+OT2fhGAxnsr41A8C3DrbRE5Q76J 7drMc0GcWE30+YQqR4cu/NTQvtuHefCTzR1r4Y+NasixkACJP+BLLjBexeFOBnon J2Z+WyCjN4lvtlV2Pm+hx/3aH8bUWKZuOwR5coH8eqAZqhhmohz8v+DL2SJ2pvmI nvHrsU//vt6AXtujvmllC/9aANsD6r9b4Z5z6WLm+16t2LzDAaaYIbqhNTi5VphW TTRzdwaIxfLW5d/wlJiB2NsW5I0SRARInTjd0w5RSlFBi80m6jxuBQ99hMRAKEb5 M8twcCOVQeVnQIyUku2LSSLrwRTWTKzlnOX3kpXdhqPc3j7DTek5Q9GDDoS9d5oa yZpJxxq0z1PfR/IYVcbC+8gjc9mWIQss06xesD2wxVS+Ul9X7kLyHIRcqD2tZfnJ /FGnhEWp7xwhf0HZltFQvE4Ud9N8DMSf98M1mY+VJnyIz0cz+JGtEx8LXIjP28Hr lmrPkDIg2PMJZcU16a1rGXKUyhIvR1xBc6JLXTkfLZ9ddyziz9yx7kINQhga/Z3y el+CugJw6tpohAaDUT5CcVjmbE+3qr3Wz+wi9ycy6kyvsQdJXmbWxq7K4Wavjkdd NvrQTbcVZDMadP1YubQGg6Dt3NmKpZNxCbZ+tnrZFaoDGbLox5dABYKY3X70GMuL bfFpJsaa4AH55FQjjmlaX3DSSQFM63GFK82Hbvc+KkhTAMYyFOxbQ2+8zOzhPjjm +E7760wUTWPVL3TtL/ikrSnN9mqh9ME17QNKCCwnYlliLDFChHa/vxRLQfc= =jm1f -----END PGP MESSAGE----- ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/messages/encrypted-to-testy-new.pgp��������������������������������0000644�0000000�0000000�00000000252�10461020230�0023702�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������^؎3@\U41Yt!l %1~V0RR&o0?RXPk;IHyPhFw9OdkeЖ:Bfj183?>>9�G#j(0O������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/messages/encrypted-to-testy-nistp256.pgp���������������������������0000644�0000000�0000000�00000000522�10461020230�0024503�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������-----BEGIN PGP MESSAGE----- hH4Dc7lkb6KvbkcSAgMEkyR5vjY8BpCrCGs3WhLh7/PU/yqLk6wPP7LzTjy8RHzQ 2Brk5RGHI6mwW9tWgZIqA2SW0/WjaNOXRe+z5DphJjDo/vlYwJwd1Kb6fM4kHtgb Sz01fhTSBPWio1j3zXT39OdG4HcU8GkzsA1SUoIypxrSSAEhQNRijO3xkwCaZcr+ XlrEQvl1c2mY1S3qpVJ+XnNOGEbIwORo7GAE2U6tvYdIQjeBaG7Sj7QOqHzvwB6c ATZiv5a9251nGg== =kGY5 -----END PGP MESSAGE----- ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/messages/encrypted-to-testy-nistp384.pgp���������������������������0000644�0000000�0000000�00000000572�10461020230�0024512�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������-----BEGIN PGP MESSAGE----- hJ4DtlUdMjdooeESAwME1UPuZZAuG0Yb76EBrDsTXqOMd69NCVSh/bC5iCHzZoih y2e6sKFSO0HgKuTuU6NjxLZFgcQSh9jf307Xt9PyZm8/swWD/rZeESwjige4GcTo SjWIxlizEqZsH1VSqMmVMG3TO9rjfkUKwjlIL3Em4LqvUKA87xZwEuxKGqmdbx6f NB9e47ewVExdflRwKOkpUtJIAVOS6hmlZ/rPWuefm0MDfZLJg9GxP/tuYXYmhXKg U623N3BvDarghKf+mLn4GNPCrMdUJ1hKSVh6IU18cLlIzUgWHYU/dvtx =STxP -----END PGP MESSAGE----- ��������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/messages/encrypted-to-testy-nistp521.pgp���������������������������0000644�0000000�0000000�00000000653�10461020230�0024503�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������-----BEGIN PGP MESSAGE----- hMIDYWznrtKlRzgSBCMEABQ/GJDUzBndaNZHz8DeBYqlKf98rG5vNvCsvOG0d8dX PnkzTGUKvEVqNmSm0rgYSeECdKN5BB+xnH80e6XJU54/Adu9BXp4nrfOGkGCIeFw 22Y/uTXb8EdbGWTyDMu3Dkl1cAsX0Vy75mdE7M2s/gk9LfoMgFgxlyYeIa6je2Po vgDQMLiwbkXWY+l+hpksQ4drhRdOdRCwHUq66QtPgZD5+7TrH/G1qm66dxfPN4h2 b5twqtJIAdJGgZNvwicsKmwvdDfJM7tBbcnypKO8bUr35J1Tb/UDH5IovEeL5XSA kPghG20uhqi7RCW+jqGMLFMjfdTUI1zg/ECgr1Va =KotO -----END PGP MESSAGE----- �������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/messages/encrypted-to-testy-no-compression.gpg���������������������0000644�0000000�0000000�00000000517�10461020230�0026057�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������ If&2VGUp4 SE`<,yu14#xs1]D2EO</E@o'e#"F t Ѝ]ۙU "+wQQ*@;&p zjע7,{P{߾ `M?Q)I@\D*7&9{;g@> Y ۻfoC,X-]HqY%_RlLEwu."ys'E*h$qe;n,pK>l+UYqX v31 ϲGo&Po?̈́1|U&%L𲴘���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/messages/encrypted-to-testy.gpg������������������������������������0000644�0000000�0000000�00000000531�10461020230�0023102�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������ If&2�M,wL ֐�LB�"C}@ԥS ~ɉ g[]AG69 풠:'*W4^4P2y~ 2.q$پb燲UQNktUll b7O0L=ɡK=gi⩩/zAf х]ZThE,Xgū~,)?D#:D΀ #Z$ػVAQe@$q]gUHFzdh!v U/<zv4)_KFAjS1Q#,`Szb1p,�����������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/messages/encrypted-twofish-password-red-fish-blue-fish.gpg���������0000644�0000000�0000000�00000012154�10461020230�0030214�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������  jI]Lbln^y+p+Do;.W?<a5 %i!k6rvcH %SK=]]^] )82:\tڇH o I#Ҡ,&Xo$f[u|0B,;i-:.D k an$ф 3EؔW>Vp'bvj,vݾW<'?4'8Cnm'";4ʹ ^NCsi~Oe:`t1fi"I<B.禶ueȨ[Q{5p⃃ؒJYp16t?s-p4AST@tL"T0䬇zх).M~4 (wImʺ\ ^ኲ\,." w`O�m|c tOmte(L%y.!s ޽OJ7`,1n7E~sX' g SԾ@)0m&^2A ,rϼ>}Z =ӐEgWmՃ~BXw\Ӱ۔}iTt2U-F&:(&B^nSz8t:KtpY;sT:@3q6\y cC/qrv}h3,)^dM .m?n�+̷ҀHE/j[(~d=?1/'&hLKi yM)SY'F)U5@$\IDȷN54TYgWp§pk7ל1@8 /wO}+}?1/P}|[.-Yaxvi!^*ܮF:ro0>5M&77$*K |A0؞D̊dkI{ ?3ƇPXmKp ׳f ?i`3{"5*ղ?m0'uneno}sGw߱0+bNG4EP7t& -|1T.TLa&ar yG'uS|f~νfw@[ |i^MUix,WfNV/9]I8ftp[K> K*}Z}sFpH$W~gݖ#l".9| j0gXCJV>>gqAd61czsɨу髼Euc_F .:=.k6ޣ\Ph dewY8Z4GVEu|4st7Wl9c{RP Gg %&> BM3^\ e?~ .^}(f5_KJP\Df#Ѕ'ǥFôѨ$E}KPQ29P`^In) *Ld}톰+]YpHN^R;džd+[zw=bDQAX3xf@_R<("M)玟[PJE70qJI 7;ΑNJgY`cuݭ.=S6u ijtdOĝpV JNF]Ŧr.b9sࠀkxVsZ;_@t{'\k*Ê *kE-1>sluڿPt@"(+*Vocs &[8qE|Uxݓ jA"|z�*C5ԚS1 L'E pn ŞX9]YxF\UkU!T XlTQ޻Q-';ۙdr!u#>&%hgb?sLyN#@fq -D.'Ȥ?oӉ22G[^ M&nVMgB(Ҫ+8?ȗȀC![Cdl9�*KtץLb h{%^Į|le B{ko "W펦 o؝6׆Ѥ9_tX) y~x>a <91 ŲӧiϡR}dU2+uW}P+ZD'* vE<]%a[IXMeG8AhϨaW۟*W_6<Q3T+x>qewݐ*crHqNa_&]&USה~/2L#-26{zLd+TO<.$$:�bWhԢ.@Tts*@N\Hrpdd_t sʫؖ-vS:cw:XCh1hcFy&U m:Vq6g#yK-x8PWr~3KJ^/EӥQ*VP0R`26Pt*OC3�jƢx6]Q)�?f|: d\ҢNg/#C#)ybEC~}alQ?T~ YR/J=R9 w(دT?Tj_/cuA\Uvcg|u%#DCG1En8bW_{4e]&$I3D||m}^;[dHqJc?hvh@'#W%XpF(h\D"1 hM"3mR{/ b 9Xp64J z98epD)okf@ʿA*C 8|plJt[. ;!:+JA]`%flq P@OMǤ :PKƕ[|W%F{gy `ܳ)GHWSK}?L PiiԞq-t}:f* {{mbxA}OM9-½Vbac=~5KTu}�> 9) I*^Gܚe=A7^P>ad7[=,+ x GJeŶͣÁxӍMfMpr\Vc]jl*I@=-וp?I%wApo1!>U> b!a94d+\{&d}KRLy!ejޖ$Ny+)w/͛b9jVPd ȤoSlZE. 4rA3s]bO.v{y=8h2‡ IjCxf‰b|.Jk6RzR 0u\R."E̮"CK@$ePQ2ma҇7~ ?F͔'FccP@\>Ԃ"5KNU%J/AA[u*fo6`S {#K^f L3=GgäFafxͩB<kq(rAs=zlnfuU�݁aro@N"L4ALi9&tDOP^A o涡l~l&9U)̇&8;)fn( _op$gV-GԹ݀ +>iBuF:)$uIy `1EM''^D"ECg^�<OU2 /ظY [<Wf=* a+@>HBל~p[9b_ӷtJJ wYI,aZm3;|``rШ_T"4e b�nJ3m4X>b*`NSn7XگHfHα||Y#5X.k|Cm}LYժœTJ7tK =h`i,3IKq-a^ )4: x 7ZP9vݿi/LZ3<1[}֓({ O<9LPz2Sy$qa e@F}a'q*BP0OXgɞs:ףPǙ{3p%3]~uz=J:^xz [ #Y-hȒ8 FH)>E|I^ZlkM9,<R .Y'Yߖ؛ m(\lZ$nkQ>Q,} !9n*xLQlEjyGw?qDž毤Y7%<%E矫NUxCz.B@@fW_=,vN~)y_iʂXl�vrk!]En.=A56[J<Hp>U;LH5*v�� I,?GYrd"ǔzYq2Oa`iç%R=;_9tW94ÀxF"rEC)JF�C˦BQžw;C̆Lm'x. a.2f$@q) O[ 9T1;wiG@W"7? I:oiV84[/}JT!tXMMn:;y V`K�w0}fM eDJ$[g_40�-3S7E2?睕CM ,ɮW'g@䮱Vp)O8)o di-2#X#Ibһ $m ړ]Fb8x4ă{N*Ou!`Z}cG../mS>as2yh;'`d0bY~P àeޔNe١i`B;ms]"aearq%텟:�["I4OfC{hnZ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/messages/literal-mode-b.gpg����������������������������������������0000644�0000000�0000000�00000000024�10461020230�0022111�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������bfoobarYFOOBAR������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/messages/literal-mode-t-partial-body.gpg���������������������������0000644�0000000�0000000�00000012075�10461020230�0024531�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������t manifesto.txtYCA Cypherpunk's Manifesto by Eric Hughes Privacy is necessary for an open society in the electronic age. Privacy is not secrecy. A private matter is something one doesn't want the whole world to know, but a secret matter is something one doesn't want anybody to know. Privacy is the power to selectively reveal oneself to the world. If two parties have some sort of dealings, then each has a memory of their interaction. Each party can speak about their own memory of this; how could anyone prevent it? One could pass laws against it, but the freedom of speech, even more than privacy, is fundamental to an open society; we seek not to restrict any speech at all. If many parties speak together in the same forum, each can speak to all the others and aggregate together knowledge about individuals and other parties. The power of electronic communications has enabled such group speech, and it will not go away merely because we might want it to. Since we desire privacy, we must ensure that each party to a transaction have knowledge only of that which is directly necessary for that transaction. Since any information can be spoken of, we must ensure that we reveal as little as possible. In most cases personal identity is not salient. When I purchase a magazine at a store and hand cash to the clerk, there is no need to know who I am. When I ask my electronic mail provider to send and receive messages, my provider need not know to whom I am speaking or what I am saying or what others are saying to me; my provider only need know how to get the message there and how much I owe them in fees. When my identity is revealed by the underlying mechanism of the transaction, I have no privacy. I cannot here selectively reveal myself; I must always reveal myself. Therefore, privacy in an open society requires anonymous transaction systems. Until now, cash has been the primary such system. An anonymous transaction system is not a secret transaction system. An anonymous system empowers individuals to reveal their identity when desired and only when desired; this is the essence of privacy. Privacy in an open society also requires cryptography. If I say something, I want it heard only by those for whom I intend it. If the content of my speech is available to the world, I have no privacy. To encrypt is to indicate the desire for privacy, and to encrypt with weak cryptography is to indicate not too much desire for privacy. Furthermore, to reveal one's identity with assurance when the default is anonymity requires the cryptographic signature. We cannot expect governments, corporations, or other large, faceless organizations to grant us privacy out of their beneficence. It is to their advantage to speak of us, and we should expect that they will speak. To try to prevent their speech is to fight against the realities of information. Information does not just want to be free, it longs to be free. Information expands to fill the available storage space. Information is Rumor's younger, stronger cousin; Information is fleeter of foot, has more eyes, knows more, and understands less than Rumor. We must defend our own privacy if we expect to have any. We must come together and create systems which allow anonymous transactions to take place. People have been defending their own privacy for centuries with whispers, darkness, envelopes, closed doors, secret handshakes, and couriers. The technologies of the past did not allow for strong privacy, but electronic technologies do. We the Cypherpunks are dedicated to building anonymous systems. We are defending our privacy with cryptography, with anonymous mail forwarding systems, with digital signatures, and with electronic money. Cypherpunks write code. We know that someone has to write software to defend privacy, and since we can't get privacy unless we all do, we're going to write it. We publish our code so that our fellow Cypherpunks may practice and play with it. Our code is free for all to use, worldwide. We don't much care if you don't approve of the software we write. We know that software can't be destroyed and that a widely dispersed system can't be shut down. Cypherpunks deplore regulations on cryptography, for encryption is fundamentally a private act. The act of encryption, in fact, removes information from the public realm. Even laws against cryptography reach only so far as a nation's border and the arm of its violence. Cryptography will ineluctably spread over the whole globe, and with it the anonymous transactions systems that it makes possible. For privacy to be widespread it must be part of a social contract. People must come and together deploy these systems for the common good. Privacy only extends so far as the cooperation of one's fellows in society. We the Cypherpunks seek your questions and your concerns and hope we may engage you so that we do not deceive ourselves. We will not, however, be moved out of our course because some may disagree with our goals. The Cypherpunks are actively engaged in making the networks safer for privacy. Let us proceed together apace. Onward9. Eric Hughes <hughes@soda.berkeley.edu> 9 March 1993 �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/messages/public-key-bare.gpg���������������������������������������0000644�0000000�0000000�00000000744�10461020230�0022300�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������U#R;U1RLo\G^zO`DR#2ܰ5q1~l߈J^jgL%<yxQz,N܉ў[~W ݳ.T'D6NOmV6|j&umr~Nu8>XC]?}]$ *%|ic8x1"R2'bעo{i(1< T dvWBʰűٺzv/'hrd@w1z<+͙aыfb{f?A'[}*P.6. ~(|M褮 =&_{]gAS#wq; p]B3U�x!g6}(!ScOҽ~&URgEXґ!,-=֑jiA.f= B+Rڔ*- ?DG������������������������������sequoia-openpgp-2.0.0/tests/data/messages/public-subkey-bare.gpg������������������������������������0000644�0000000�0000000�00000000420�10461020230�0023001�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������ U#�5Q$_|p|CxTsls_yAHxeYZvS2j[ױØ;?|4%߯š p-xc a!Л&d #$݀['yX2}H& ۨ9=[@S I~GP=LqW?2$c?rrw(eQ+9]:ߤ0 \.NBMenO]U��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/messages/recursive-1.gpg�������������������������������������������0000644�0000000�0000000�00000000046�10461020230�0021465�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������� t�����one t�����two t�����three������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/messages/recursive-2.gpg�������������������������������������������0000644�0000000�0000000�00000000070�10461020230�0021463�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������6�� t�����one t�����two� t�����three t�����four������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/messages/recursive-3.gpg�������������������������������������������0000644�0000000�0000000�00000000101�10461020230�0021457�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������?���� t�����one t�����two�� t�����three t�����four���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/messages/recursive-4.gpg�������������������������������������������0000644�0000000�0000000�00000000114�10461020230�0021464�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������� t�����one t�����two t�����three t�����four� t�����five t�����six����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/messages/recursive.txt���������������������������������������������0000644�0000000�0000000�00000003171�10461020230�0021373�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������Some messages with a fair amount of recursion. These messages were created by sequoia. See serialize/serialize.rs:serialize_and_parse_1. recursive-1.gpg: 1: CompressedData(CompressedData { algo: 0 }) 1: Literal(Literal { body: "one (3 bytes)" }) 2: Literal(Literal { body: "two (3 bytes)" }) 2: Literal(Literal { body: "three (5 bytes)" }) recursive-2.gpg: 1: CompressedData(CompressedData { algo: 0 }) 1: CompressedData(CompressedData { algo: 0 }) 1: Literal(Literal { body: "one (3 bytes)" }) 2: Literal(Literal { body: "two (3 bytes)" }) 2: CompressedData(CompressedData { algo: 0 }) 1: Literal(Literal { body: "three (5 bytes)" }) 2: Literal(Literal { body: "four (4 bytes)" }) recursive-3.gpg: 1: CompressedData(CompressedData { algo: 0 }) 1: CompressedData(CompressedData { algo: 0 }) 1: CompressedData(CompressedData { algo: 0 }) 1: CompressedData(CompressedData { algo: 0 }) 1: Literal(Literal { body: "one (3 bytes)" }) 2: Literal(Literal { body: "two (3 bytes)" }) 2: CompressedData(CompressedData { algo: 0 }) 1: CompressedData(CompressedData { algo: 0 }) 1: Literal(Literal { body: "three (5 bytes)" }) 2: Literal(Literal { body: "four (4 bytes)" }) recursive-4.gpg: 1: CompressedData(CompressedData { algo: 0 }) 1: Literal(Literal { body: "one (3 bytes)" }) 2: Literal(Literal { body: "two (3 bytes)" }) 2: Literal(Literal { body: "three (5 bytes)" }) 3: Literal(Literal { body: "four (4 bytes)" }) 4: CompressedData(CompressedData { algo: 0 }) 1: Literal(Literal { body: "five (4 bytes)" }) 2: Literal(Literal { body: "six (3 bytes)" }) �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/messages/s2k/iterated.max.esk.pgp����������������������������������0000644�0000000�0000000�00000000646�10461020230�0023204�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������-----BEGIN PGP MESSAGE----- Comment: Plaintext is "Hello World :)" Comment: Encrypted using AES with 256-bit key Comment: With password "password" Comment: Session key: AFCD3A8910A8ADBE5A48F033044689566E4D4BFD461844C241C4DF6388C37010 wy4ECQMIc2FsemlnZXL/PWYsKTYZu1dmAvo2NZHz1KSZ4EqNgtyCyBSaQixxQjao 0j8BrYkuY8PDV8hToxH1h+aH/NOS5x//CeiEz2tRibRJqWiqiKKqUmxphuwJQOxw iOiziEvRvA7AcERGzT0O+lo= =6uC5 -----END PGP MESSAGE----- ������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/messages/s2k/iterated.max.pgp��������������������������������������0000644�0000000�0000000�00000000571�10461020230�0022420�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������-----BEGIN PGP MESSAGE----- Comment: Plaintext is "Hello World :)" Comment: Encrypted using AES with 256-bit key Comment: With password "password" Comment: Session key: 57879CA6CEB2F7D76172125FDAC91EC77B7704B4023F4CE2CBF5B7BA65242CC0 ww0ECQMIc2FsemlnZXL/0j8BxN2QzkE7NU/J1KSGEMF5rEy5ZOH8nUaFXmeLbpra V5e4Mi/tBNgfXbOzoiZIsi0V03FaVJdNWOAdOvJ5FEI= =xQCy -----END PGP MESSAGE----- ���������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/messages/s2k/iterated.min.esk.pgp����������������������������������0000644�0000000�0000000�00000000646�10461020230�0023202�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������-----BEGIN PGP MESSAGE----- Comment: Plaintext is "Hello World :)" Comment: Encrypted using AES with 256-bit key Comment: With password "password" Comment: Session key: 7A90B25006892A1BE99CB23DD66ABA3179527A0C57727CC739A2720DF141037C wy4ECQMIc2FsemlnZXIACAONbKs8V5vyuqFuaWn9F4yw3r4upcloGvG6Cc5jgh5P 0j8BUmF4AMMTSmQhJXESmP2cK5XlHXtRRTG9zeFciL8GcTJ/RnJLyCE4mLR3ekav 5DsJyvOo6WP9B6X08yBif+g= =nAcr -----END PGP MESSAGE----- ������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/messages/s2k/iterated.min.pgp��������������������������������������0000644�0000000�0000000�00000000571�10461020230�0022416�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������-----BEGIN PGP MESSAGE----- Comment: Plaintext is "Hello World :)" Comment: Encrypted using AES with 256-bit key Comment: With password "password" Comment: Session key: CD6E0EAC04B24CCC7B4239904D7FA11D04C6280FA60BFF35C16E618558712B18 ww0ECQMIc2FsemlnZXIA0j8B+7eKr5xSCAIS7nJLIi50TLyJLEhy9PGQGp+spl95 DjEIv1oiOjgfSi/rgbbDls49Nb5J3abyuiqBmhl0uR8= =UNqy -----END PGP MESSAGE----- ���������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/messages/s2k/mode-0-password-1234.gpg������������������������������0000644�0000000�0000000�00000000006�10461020230�0023323�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������ ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/messages/s2k/mode-1-password-123456-1.gpg��������������������������0000644�0000000�0000000�00000000016�10461020230�0023636�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������  BYB*������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/messages/s2k/mode-1-password-foobar-2.gpg��������������������������0000644�0000000�0000000�00000000016�10461020230�0024343�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������  XE<|7������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/messages/s2k/mode-3-aes128-password-13-times-0123456789.gpg��������0000644�0000000�0000000�00000000017�10461020230�0026374�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������ a\H�����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/messages/s2k/mode-3-aes192-password-123.gpg������������������������0000644�0000000�0000000�00000000017�10461020230�0024246�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������ tay�����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/messages/s2k/mode-3-encrypted-key-password-bgtyhn.gpg��������������0000644�0000000�0000000�00000000060�10461020230�0027011�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������.YnڔV>}gozBjBH ND��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/messages/s2k/mode-3-password-9876-2.gpg����������������������������0000644�0000000�0000000�00000000017�10461020230�0023513�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������  gSj+�����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/messages/s2k/mode-3-password-qwerty-1.gpg��������������������������0000644�0000000�0000000�00000000017�10461020230�0024430�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������  xE[U�����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/messages/s2k/mode-3-twofish-password-13-times-0123456789.gpg�������0000644�0000000�0000000�00000000017�10461020230�0027054�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������  QE@e�����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/messages/s2k/salted.esk.pgp����������������������������������������0000644�0000000�0000000�00000000646�10461020230�0022073�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������-----BEGIN PGP MESSAGE----- Comment: Plaintext is "Hello World :)" Comment: Encrypted using AES with 256-bit key Comment: With password "password" Comment: Session key: 04E9B7F2FF576BCF58C15699A807D2BAA2FF80F32BF1418F11FE372B03091617 wy0ECQEIc2FsemlnZXKpbLXDWn+1j/z/FSsnJ0fd0p7DUlh3SPYD622+DmtCDdLS PwEQOFIMH1EyBP1bC/5zyLXvIMLZYoMyItB5avmqWqFOdBkl8oqcCuxCHc8mCDO1 Kw8czozzP6znyOXuhpVU+g== =AKER -----END PGP MESSAGE----- ������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/messages/s2k/salted.pgp��������������������������������������������0000644�0000000�0000000�00000000571�10461020230�0021307�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������-----BEGIN PGP MESSAGE----- Comment: Plaintext is "Hello World :)" Comment: Encrypted using AES with 256-bit key Comment: With password "password" Comment: Session key: 3A8B0B29434E9E1E4B766E44F1E46D7CA97DBC52FE8CAA6444CEBA16743949C9 wwwECQEIc2FsemlnZXLSPwEwiCJ1RFrZS9lZcBsKlZTaeBSKbowG2edGWnTYpDes WUqXCYxvdgzPgGtfaa03p5C+yyJX7uBWUXvwBAs9lw== =c2+S -----END PGP MESSAGE----- ���������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/messages/s2k/simple.esk.pgp����������������������������������������0000644�0000000�0000000�00000000632�10461020230�0022103�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������-----BEGIN PGP MESSAGE----- Comment: Plaintext is "Hello World :)" Comment: Encrypted using AES with 256-bit key Comment: With password "password" Comment: Session key: 4B6550EB3F56F615DF8177840B9E5FBC24F2431BB15CAC60BBDE257B8248E2C0 wyUECQAIW6I9qHFmEqeeJ/bRXD8l/xOvUrPXA0AUBjMiJ8L/Fsfy0j8BQ09Zwuee 3Dauo9MQhOzLkYID2sJRQHOYoPr86rIKa0T6olRryDgpO9i4OMRIc/54xZ0JkZNb WeWLTCyGlho= =AdKO -----END PGP MESSAGE----- ������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/messages/s2k/simple.pgp��������������������������������������������0000644�0000000�0000000�00000000555�10461020230�0021326�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������-----BEGIN PGP MESSAGE----- Comment: Plaintext is "Hello World :)" Comment: Encrypted using AES with 256-bit key Comment: With password "password" Comment: Session key: 5E884898DA28047151D0E56F8DC6292773603D0D6AABBDD62A11EF721D1542D8 wwQECQAI0j8Bx5MtUEkqounwg7oaL9wfBZc3osExb9GXKu8WcV7wlW/w23E2JEWH 2aGoKjLEmuWhx7cgzrx49ItD6HjJxFk= =GqhP -----END PGP MESSAGE----- ���������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/messages/seip/foo-priv.pgp�����������������������������������������0000644�0000000�0000000�00000004713�10461020230�0022037�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������[L0�Ťdj~VG74"ngEDD5WyNЮjQNjF%+9wԵs-kt,`]&xaT&67v~ʭgۙ11*K G0A6IC 0& l=K=P-ow/ZAZO-13y쒦08 rRW@*#c Ԕ:Ư}wkŀ~!qdI[���L,YA@מ^a\߶{g[i* ݄ʴp5[7kRs~ \QU+:X}'_/u- f Tox%<:p!n! K&$|rEw{~ }G{""{L&'җK_2UɯM*j<\Џl Yzp YUQvq=ww {Zx�{ϜKtH_�+<Xvm!(+j*r̪ߴLFቡb.= Koد_`N׀1Q}7gX >iZl}:?{_-�zZ%G�e[2/ }C'G]XfD1D =fJI?=O'&7lz KH5,8C4gl,B\|G7Db[̦?4&eV ·un++ Zۢg-@_O4EO-$Y,z:h"%G4LJ[+, ¿'_G?_x+ZiQ:S/QaB%'<(eRDeo C$EzfooT�>!|d cX4T3>X[L0 g�  � 4T3>XA�ܒVB$QdcgSM ).w;W]-Vi>FF@T1`m$^rͬ>V]l!7?][};b-^+/"54 "#.tN@'!;λA"zR vGƺfݙ \@--\\P>AtNDl ]%` Cs,x-J2Az^d[L0�(ж4N'WMH8m` i}8#O5I+;H`*x !FG+i[Ը>SSd!A3\v3EYfo z׹GpV߁t [Mb9:2ɼ*׻L`P<r}{{@Mbhଖ\HfobZ X' Qċ2q<&9þ?&O.ma�hfDXԸ=ߗ'���qxEF{E %M]}JD?~z Vm%8>J=uמ#57ر1B8MFS QD&N}fG$+ԉmx<u˲%0G;ڢ MBtw F:Z_V줲׳]0ҭ݀>9ᛙ_ۼ$N?W7E{OQq(Oo^ 5PCO>]gRA�@ԏR xQ  LnϷF.Hvྺ_ [Vӈ9 sw*BcLGrKXO,UEEZpU?z>$u qpSO?'*Wi�G2} �~&\ t<AqskaQ{mgh#mWiYrX(3V6 >+9g;z+$鿿s4q:yfcGHC>)YQev �`I?mhq.'/BC o, 8>RVodszZ9 * m鞺1+p\KŧoN}|iW;HnSEgg Qmiʤ v]#j5eGf8̉6� !|d cX4T3>X[L0 � 4T3>Xa|Ҥ&iBt񀔐-޽UOאbAɥF*~f\.^Xjcʰ+S6${i B,\2/& mqsܔp'.5=T|�4)nL(r::Cq[Dc`6!C*<ec: ]Iͤ+$M-!g=[%Mᬫ8jK=Xd䌣\;=Tcjߪ_]�����������������������������������������������������sequoia-openpgp-2.0.0/tests/data/messages/seip/foo.pgp����������������������������������������������0000644�0000000�0000000�00000002265�10461020230�0021061�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������ [L0�Ťdj~VG74"ngEDD5WyNЮjQNjF%+9wԵs-kt,`]&xaT&67v~ʭgۙ11*K G0A6IC 0& l=K=P-ow/ZAZO-13y쒦08 rRW@*#c Ԕ:Ư}wkŀ~!qdI[��fooT�>!|d cX4T3>X[L0 g�  � 4T3>XA�ܒVB$QdcgSM ).w;W]-Vi>FF@T1`m$^rͬ>V]l!7?][};b-^+/"54 "#.tN@'!;λA"zR vGƺfݙ \@--\\P>AtNDl ]%` Cs,x-J2Az^d [L0�(ж4N'WMH8m` i}8#O5I+;H`*x !FG+i[Ը>SSd!A3\v3EYfo z׹GpV߁t [Mb9:2ɼ*׻L`P<r}{{@Mbhଖ\HfobZ X' Qċ2q<&9þ?&O.ma�hfDXԸ=ߗ'��6� !|d cX4T3>X[L0 � 4T3>Xa|Ҥ&iBt񀔐-޽UOאbAɥF*~f\.^Xjcʰ+S6${i B,\2/& mqsܔp'.5=T|�4)nL(r::Cq[Dc`6!C*<ec: ]Iͤ+$M-!g=[%Mᬫ8jK=Xd䌣\;=Tcjߪ_]�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/messages/seip/msg-compression-not-signed-password-123.pgp����������0000644�0000000�0000000�00000000136�10461020230�0027626�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������ oM>xHQRJ<r'rF H眪;Ls3G3DM- 4+h3d9j?<| coV&S@D����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/messages/seip/msg-compression-signed-password-123.pgp��������������0000644�0000000�0000000�00000000641�10461020230�0027031�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������ Uy!`s Ǥۺ=ESɑtEWv ]ǙX0(p?/̆X%Qg]:KG^MUT NГƠyM_b/I~ JARo‰vK0|, mDa#g3tH�K5;PՏA&W&\+P$V~ʄ2I>#1BUBֹI3^y3K(ՙ.Q>ڞ}Uˀ%bѺ]Qeosj84 <V)9"ap5=VZ:}a<"_#g!B#EKJ`L.~9U/?]5нZ(](h{[c FV@m�����������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/messages/seip/msg-no-compression-not-signed-password-123.pgp�������0000644�0000000�0000000�00000000135�10461020230�0030237�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������ 4MnU/L9px%>$տ踆޴7hwPkYo-MZqL$=*^ěbW�����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/messages/seip/msg-no-compression-signed-password-123.pgp�����������0000644�0000000�0000000�00000000637�10461020230�0027450�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������ 괸lМ!$#ށݓv"N}\` 3)+cܒݙov|2æ�IBhZ.PǙiX$g;カ}zrS" r? x~@f<y_vm=I؋Yo:gFt1OPL`4lc*dܺ=1 K'F9g CD tJSJUٕEp`Y2Ղݑ<G#w|;{ T7wy](sl.EفYTdQ׆CTozo.tN8L Z 76Ԍ#_P/`/*+I)<äzC ${2bb_; -xjt�������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/messages/shattered-1.pdf�������������������������������������������0000644�0000000�0000000�00001471043�10461020230�0021447�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������%PDF-1.3 % 1 0 obj <</Width 2 0 R/Height 3 0 R/Type 4 0 R/Subtype 5 0 R/Filter 6 0 R/ColorSpace 7 0 R/Length 8 0 R/BitsPerComponent 8>> stream �$SHA-1 is dead!!!!!/ #9u9<LsFܑf~!Vg̨[Ly +=m EO&߳8j/rEF<WU.+173.ߓ5�M dy x,v!V`0kЯ?ͤF)������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������JFIF��H�H���C��C����������������  ������������ �'� �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������q=b"*{���������������������������� ^6o'!s�kz>GxbM}P5Pg:;@zky}Gmy$zơ1dzKo�������溁! ����������������������������\mb\p:; yF #\|]3[[d9iSm |C̼o[.j/7yΒuf_':͒ǽC3+�������:wW+0����������������������������Hz>/eT[ci_|ns$)O~h] eW~�6֘u\|۝8>{Y΂])KyEJ_;~w1~q_D|}c;s4n7cZ7v%‘8M?Y2jjF协X_U׻6)n۷'1_A���������������������������<޴E8@3LF"Iе[AQ"4=W\w]RǴw-=SJm?x{7ޯwmZ.!ψ;YQ-.iu}"3\q۵LneYu%#~*BOfK1b����������������������������Rf&<KI:~=K9Y+[ծ{@|$j|oq9 QsnqcpwR͎̿Ou3%z>gW |?{oo eg'Ґ.=+ڬWbn_/^XM9^k���������������������������[dn[ﮮ5-HSE: &к<ȯO_,8cEv7%I?Neo2�69?atl=H_,?c?]1.�^;_.>}?DzRAnӯ5Ә֌~du} +upC����������������������������, ܞXK�8s$)O~h] eW~ױ0lat9FqtֽmLY/kqLm[ޣ>3_.o3 ֽy~3+>}?DzRw++ ;\gq�SI|}RUWy+0����������������������������{)t9Sa!Sz4Srp\9س1ۮd9iSm |C̼o5ɵpjILXHF3�/ev'³/6[ɿTϓ|.6V\C]<::Udm' Y'Ґr|6i \\5k=w+q4dsWJRW1_A���������������������������� N:mW6 ), 6L+o0dHZ>I۴hqLoѧ5E!-uӹN%Nt yD-g WHT(K'A^*d\@ C^)=5Gr.a_+l'o^)�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������P/3Uf<^sU#@{hv����{v��� kq ҐDQ|̠��7Zak'�-5RP��aJdt�k'VO_x*FK:FLBa Y^x�����������Q_k^H;'^NW0V]S}M !L4xj~.׌rQX(6Mk�9޳_(XUo9i[ޡگ5˻x&W2]׹/X:BY7-m)ZBtgV=H}=QM=;=)/9sӽCk>K4+kWaW@lԊ{Ƕ:aUhKG{cF{׬EhYd)^-Hw8Ӎ.r [ޟi".|YB/9]M rîa{̓V8rǮ������������%?z_j j@3H%˧ښ^`kFN6e+?^&Pe6fU&:?뛶]EU~di{ϕKg/e~WǮhBtMDt e=%-s�}Tyq }R{Y֌}n_G>v~oYuHoyz+Jdcl3ٌQ^6dEM7dT<�u FY^_V:Őy޲ޱi^Ҭ}U-GqdIFHu*e|.������������߽k%]#ΧRMA(-E˵4WIbj[CYy>G3X ц{kYǼvX/Ҡ:R-V2y%_;wb+6vCt}KSM=׆9rSU]Q:A{ڭ-I^Z[m:K/s+ivjz'Rغi>/+gQ񚙪Ւn!guMŸwzc=chxFgu�QY]@�������������������������������������ݺ2߶t2ӡ������������������������������Cx���= ˜>ȉG#g����"hw2^���������������������������������3 �w٣]j|u/F!y_|4wG[h"r1̑nMM~=/ww\#r2af9%=V48>w3[a=Lf[L r@��������������������������������h&xzhy"a wl&7|&<u=Vі�M2Dum3/v1RAs=1-fy&E8[>j<iվ ~@��������������������������������<Zp|<G\לj_F6�JּA+Foyq^' :tV9ӽS%DѵOqAS i7}أO-,nt49l<IJ^3E:/ϘVXc]k����������������������������������-?sEW\թWߞ<ORٳCKNJ<x{~~~?<U=Sը?J~|U})~5~�����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������\?[�����������������������������������������������cfϞ>�����������������������������������������������Y-%ƀ����������������������������������������������Hƀ����������������������������������������������Hƀ����������������������������������������������Hƀ����������������������������������������������Hƀ����������������������������������������������Hƀ����������������������������������������������Hƀ��u_g������������?:U�����������������������������dh���<?kS [������������W>?1���������%BoT��������������������ou�������������UϢ`��������,CuRaд7lw Eo��������OP1~(tr�����Hƀ���`>cpWt_9�AAlj'֚2ݱ��O0]-J7(3{c��fşEٟ��������Յfz4]{mXe$5Aϱ��������IFgع3�����Hƀ���Ic7~�Ws7:B]%�!F^6v\ޜ\e8#V=zrk>ӽ@��jY]��������J>98iwXM0TQ��������(ߗ04&ux���������xdpV_`G~)~}#۾q佇_}<g>X>VRp�Lٿ_(8^Y{N1boVH���}f~c���������1z(��i2SU ~ s[+ـ??χSkyoǧ˒q.i.LaM5��������8x??=1,msO7ު~z2n|+5<�b2:��� 5^,.��������1Rܟ]GzZfw�mx<T.fĝYgd2<V 1*I*<ltJ# ˮ\};޵^Fi_/Bxv}:jS.,6y��(���1����WG^X㕇ikw7gqOW1|/iԀ���Ygvg0��������9A =k� D{fy϶x}DO)34 q/c�ig�oKoKsgR<q9tE7v;%Gyvf㮹pAkO)ޗ+g 뵸IlwљS4,nooܗ=@��� ����|Sy.sMY8?//m;. = FAm|WwS>Bq|ʴ@���,x3��������8bN1n8.@k;*(Q΂?fO_Uߘ$SyidO'ʹcr{E􇟀_6 PvyG/r[;yq9é;\p4Mnly�-%jQ~ñy��� �����S[iǀ8`d87o)y.b[!Vr_!|f-ly�)e!8ŋeZu ���jY]�������� GԹ!<yR/5KZqKy{֥tJ]ϙ @ԥƸ#WYfQHW6a+ P�*ɛ> /]}ᮏ̈́`2=M9ޗ+g`_gyZ[GW8v+H���$c@����/_6_05) ?-T4&]/q c G+m>3!ŷ*^=b>�� 5^,.��������yIcG/5f^ƧF;Z`N,W}{ϛ4[9vU(;}J5zx{K=?+:c!>;/G>J}pT>OXBO)ޗ+gy_Gy/ uR!�|-R^{���$c@����];gw=+7Z.Tk5K7*׊i.R(L8?gy?�N׊,x\C:\��������>k$nLlX/)û 'X薅NiVtե#sH%n?С=«||&;C&_L9{uo$1Mg8wuWӯO;\p4 %u Ϡ>'Zһ|ur|ƀ���dh�����a/d0W#�������� 7Z(u5^)j����������?(iJbУ= 2%ɃdVZf(lmUYux߯[4-rg6M4WzNLKݖnsu3.?.|qh^hѽNVRbj8>{���Hƀ�����;7aؠ�����*rQfnS>֎=���������������������L v*ܽr`HKZf������������?s z����?wX[/^+u7^(�����������������������~r{2Aծt%{tjSI6<]������ ������8.tY��=)wg+6nQf]M ������������������������JDUY~�,Lc/~�������#������@d?L/?dzŚٺMn�������������������������������������������q~VrSfM;��������������������������������������$c@����������������������������������������������$c@�����������������������������1"m$2O����Ѝ�������2F4������������������������������\ФiO'3 QrMY7F1EX:2@{<]0�������2F4������������������������������\˖UB?0^ÏJ%)Ѣ$Ҷ1q �������dh�����){~��~JlL@������������������ZbNo/9̥AE#G"{=T<D�������2F4������ r9t Dm@N9|Z–Ԏ ddOɏ$b-e������������������������������������_9Aŗ=2`>ID@S7c $sP$ǰl��������������ꕜ@��T-YR͌ `MHA������������4?GFDIeq&f+ٰc0@Ş4y*: a0�������������� xA)/٪N`y9D=s0fx4܋�������2F4������������������������������=Z%º"Rf: s̛!I!"o?A�������Hƀ�����ae tLY7⒓8Ȟw4c<z&jx։��������������!f+i/M v[R&<DMjKEi71������� �����"V5`!S:IDfO vٵ:7t(Yi,���������������q��������������2F4�����<NX Ol PX*s"kFgHpCų�������������� Pooi0:r<<g��������Hƀ������\�{�yLK{`~O ���������������(daI tp N:^ssXͤ~������������������������������������� `DDtA�ܴ9ʪZ""M:ZqL $,������� �������������������������������sjFlp?jFl|={�������dh�����������������������������������������������dh�����������������������������������������������dh�����������������������������������������������dh�����������������������������������������������dh�����������������������������������������������dh�����������������������������������������������dh������������������������������������������������8�����67@ 0`58Pp!13%4$&�!���^ ~h?$p%X>Ĥ.T_=�DG͐ ÍvG3%'dkPq!k<4YlC7n 5GR>Ţ0eOaL8۬=_4"z)X9zv_s{kwS|a'o3*Q~M{ziU4 [(1TA^[%!b9v_s{kwS 2`Ea?㾀)8Rjp$d#hz&FAl?ђ,`n?^D7I T)41.b)*4l <"1FrӚэw}mVMخ*-ZF+#} ѩ2`(bL*Vt{*DqanB�PFtu]bÇA6FxeyGJ. sCDKΛQٽmӂ"n)Kg5V\r/f̛HRɺ / \<[XX&'81qL[9GÿS4&㶋t\CU޲ǢylsƝa�ߗM/V?P_( & F@ޡIj2hTqx[m&hd::v`=p6:%e t�2R&|;5'"p?0ABPWz}4O5"P vav3D_dkP_( &e 3u G_!~(u}[x}hxѰw_!~(uh]7)'QgoO=HwmY"cU}/)k8Mrv_s{kwSU?ħ_V [P/}&bpgUD)ݡEMFjU C6 1sdcR0 orY)HwjJ gxm^T0a$]]]Ӻ!9l\mi/c|릂fZk+V.`V4KЪt9mY8[zGV e@j1>OUO�[֍/$oTԶ$9u2."yԏgD퍰S/bبgyGRf c^DzvW HEx̃Ԕ9 [o/'Q2(OhB/ӞeQLSC$J}sw}6zI72=%8HX&J~`]ڒ=dD#+2iDAMY%CRUB5¯hfѫU)IF|$)Х ΰ Ωg"8ZnF- *WA Γ+~R8J褟U;*[GB{R1*Z:(rֽ뭫@Գsx@Ű� ~E; QC(R(Mw Hv: /põŨx0SbfUSp�i߆Om9!ʵ%k!*Y PHHxrZj±ѝs06Qc,P$%ZA0r�o &u> \ayuQӆnS%GTTՌ4%kWƝRƝy^W@iW0q4*�k6T߿! ( OfdA*Pu |}2n p"&WoY [ $� 9 Fp}4RF�Po^)5D'X뗋fN? iW,odċXC[h`p9 Լc7}K?U,k iD<N;ax Z$P>d=RDBdzyN/$S5~1"ڱu,Xʺʵ6Ճ|mA,3 ח ;.:pڲ}eɇ\XmR`Z9YI`˨{W Vb|/}1m!g^MV`jI.0_?Į%S5žBXj.S{A﨩庭BȷsE7dU?ըCYh2=Aԯ~�B�xj`tѣ8I-2'k~NN <ЏOghXw Cj"2[ gY8u+UMѥ 1I8I *fBp|2v#>J<I[@a DxXȸzV"Xvx<do/G_VnCU@s~7yaXN H+;QPP0\<&KqkRjirrrm^�%o^J2KH.Mc'k:*tG`ly6H\<.oHgRdO㷶ea<FAKq.(rhH3z5 &: 2A[X!!2AԵu9(Ke49b=8pĸzV; ]<^<{j(QN8^HucU\{HyPd*/+KPˇ#j%QP9ES$ōVK:‹%i$`:fc �.~*&(aMl#9pZF1KqЌ>XXFDILmHת׎):"V|!b,Fya:!>i>-� bBi8RS|[3^67%�V\yUxxl V4M[eS2XU$>;'-&沙M QE7Axbo]zrSNA((ۊ,W^_9�XU8b;yi\3IĎb,xC4U*I ac빸)`D<|Rub8& $pV)Vm}׷NLr{$5lYR$!_?8)ͅ3*nHj':D;AX}9g-܆W DB&l)y &H|X?Aq#)ZދF(J`axx0rga#EeBbu 7� 0v- T#K̉WQ�­Iz/$]nB'恊WIJ%Yʰ|հ>%p5Guv"IXp^nG7Y{c&:o<;St󖘐xnEwfYɶy㶶ѾYe{cS|{s57˚)�zP:Mqh&\Z -kA5ŠMqh&\Z -kA5ŠMqh&\Z -kA5ŠMqh&\Z -kA5ŠMqh&\Z -kA5ŠMqh&\Z -kA5ŠMqh&\Z -kA5ŠMqh&\Z -kA5ŠMqh&\Z -kA5ŠMqh&\Z -kA5ŠMqh&\Z -kA5ŠMqh&\Z -kA5ŠMqh&\Z -kA5ŠMqh&\Z -kA5ŠMqh&\Z -kA5ŠMqh&\Z -kA4?�8o7i~/\[kmrm_~/\[kmrm_~/\[kmrm_~/\[kmrm_~/\[j.}sx-ywsiI8wIg8E { Jw5)/ ԂvR3}`e~S74g,)$IǶ&r5=4]?VЕtV@ 6g`Ӯƽ\ؼX=_�>jE'K_-~�dpm4dt#PYʰ!)Rb՟!ޟMvԅ{^8H z|{"i ?BZu/�SY.�gJe$gQv{u&l4éShKZe�Qvey}w0t^Hb*) fݹ႘GG5{X>ܠu'~Z$w#Tr&6Y k)ujc# λل.Ku?N-ɝ@>+, TA }{+Hlo}gE fT9!luuhƽbl" dW/u %? 9ŔTNrwҲr_&NX-iF�YC!I2{b_:w1pb62g c_kⱍ|V1PݔkR Xj ƔG<Ob<S-`iRyc9RhvDKSebv|kj(cXjHĪGpcvR?L7T1θ\(w睋3e�loOӾ#S/&;N)e'D @.VdƳ bB5"I:i)ɖIoNB凸b=YQLp}]dۢ^F,9jf9)FA"o5RwJ{Y_s& zlz$@UͲVye}{KNSz (MO km,`dmMŁ+B-{%˺?ӧ|G3 O=RG X~X&#p[\8-Ѭ<t #}ܖ])(ŶO^DƷ츠{G9�]<]HŬl͑ɈT;"I+G5{%>Pc[jtЌvLI.V4n>%(gQn#Hڒ YCV㺌u޺]"_$� ^o/]�y;?wJY|r KH)+%NdjO,JU)d J[Pܶ,[zUm}jv�yzv|wx슰Add[Rq0r/킛($CeHCNKYۖ#t#s lb/M+r= 6l%$&h_82s\�g oizud!y']N엜nnxdUcAݐ*<?e`IdUb}?:vrQOz8KKde"$b7ʦ-AݖRz;hƽ} ƪ6";c8&$]DdOu%A%Unee'!vٲ8<zA~y( )+a$L?))-+ťzYg 5DpaW^e&-L?5 Vx bۧjcjJ9GL$9ct7Jv*KUPyQ<͌1}꾕{#ip1;F6՜;oZE)HĮpwKj꧂ɟ9l5RK[R)()Q̪� [ɱ)p0AɩJXҺG=DH??}VCyHSčR2O&թ⩇e�)6lDt9Q9Z7ͅRf!&'=$YJE-1l%"6sĖ`�7LVsƼU6;$_XN UrsxͬbW3uY5͝\j!i,I4n<R^O2xoQ]8OMCMSWAvx% @'#kN?hE"S؜~E�V0zvg("œOL&59 [XB I|9]$.2NVZBiē;m;yGK/u}[HOMduӀsMEV G2 v;<&2g ʸVH&yo!XaJq*HH'iuӸEA}r1YhxP@xah"]-ᣈ#ABl<_.;��a#6E}S d$dϘb"JNǎ?1:J`waK3R̒/dsaRܕ)),N19 ٘umBxqg_/%qĥ(;�<dPT:&+?i\JU͂BD[X+ss!|mo?M8`a@űt@G".2 b.BWmʹ<ղlΣ]Xy!j|;%eg[Ea=-cI8eAyqV+zYk#{0sxːfbZ);Р)+r4mH)GfLր&�RHWDbn%-Pً@$*273ɐTzaba'A 1uˣf$TzqlBq[6* 1PFܨq[6<"  )©sz3)K~URUKHXM|v+dşD bd@Fc f%|*c рgkڋ$7yF]"+I>SX|4LDXUpw[lrS$?hYd\1.F"ׇ5^KUfg#m4&$ĒCs/(* :3'$;0RFt8lŲ4plȀ]MԵ D4T `e@(2g= #Y(F[&*>$%)GoƠ4a-3E^ϽaE:`sdjc7NSʠha%gBح2C ȏk&(*Mny64K&`<ȥc$%f6 l5OhU:BB2SkɊAXHNsyGi|LymCJ|[D~࿧H|"F6-P2-]HJ R4TÕiO:ttPj-TҖ3e?{h䟨�(ROrLQr]Ī^([CrTq Q`qPÍ_sr!eqA7O',YF1&=`-hiE@N˦̐(Ylf 4}84Hf!G` B HET,z8o-=+`-  \.$*M0x pq[0 I5"BeFBb$Ebz� <#&F5aŘ d;zQ9"8#<fqm̬rF6lZR�V[9ў{;oKF+An)H sARtt%0 .,A9Cabڐ%= Rh$DXx680ߨ^2n0#f%*? o-xi(W~~dG$bUiDal=c?�K Cq1ڎec08VM1r ?g 2!D7T$ha9ĠvקoLhI9p>Kl[h}CKJ ,o0YgbXdyް$amqڮכ_;zgQ5Ǚ<k#TYea.=^נhWdz;ƽR xKpX.>y6b9$bbclnmF">[l//*Iɺwp rzN)ĿIC]U՝\vdnRr̦.ԓBRp rzN)ĿZV0!sXꜦ4&ut7V):N5þUڙ႘GED7*ٸxFN``ް(x#" Dx>t|fm9zQ1Po q hv4Cʸ�q�??�p�   �!"1A#2QVa6BRbq $37rtv0@`su%45CPSUc&8T'Ddf��?�ׯ\tKbk(ԸG "JHppE%@˄:vb sMm'#.HXьJ`E\^>>ou{-eIE0u\4}\颩*2ۤVŻg$1HY =Z&U\q߯_:~.X�wuұjr,UϋS<PJ)п(s{*mM}sq2Br;aQ7*[bMn@Ie茣n+DoIFiԩׇ\ڥS|&s:2oGӣgE7NuԫTryB*t+}hmx&yDZXxZ->u_}\晐km,6N{tڔɈ1^E5M�dH'E"%ƛIQU pE""TW5iuaTOtj[i �菌Q*7H>u[{ST蒙޶5 .6jى4v鵈)3༦L%\V&Fߎn4jۂM .⊉~\+.ɓ%#`ןyTi#qTR%DEٌA%>hם"L[b<v+^}i Jn8᐀6dH"'Piv2r~1OgT&M6�`ő-ܺwq'IΨ9'zT5i)=6#4Q슯 =BW5[K hT8ߩDfWVĤ;(Q8\IQǍE\i  Ha -l9nǦT>B|tZ@nKdk;?St:ZղgHay!\Leρ"-YfӪ28mgޤH^k^O{~R/1;-{8?malz#Ţy=Wx"HP\T\Rҽ,<x&koӣs!L(jdOup*ŵJ$U14%ɜN.U}4/ݤ٭Zb+B7kݭQoVu >c/!Ϲʧ�u˲-;sW 5E2N"uҹvn ^5͘u2DL[%M2ArMy 4 غӠ@n p!!TQTEo+rWVbTnXˬGf;L*7"|sȷeZ1˔̲nTq0i'8G7\^O{~R/1;-{8?malzonz.DsMB!pǩED>,֍f ᧵ASsF#LKVU3Ԭrj'+s4>JqYFd"D P8EEENb�|AoKMMʬEc, \xKr.ˆm9RAiy,V!LGmY^ֶ4Vu-*:!6As2B!NtQf릩BdK2D"PoŀAiq99.9ʾSަuN FS*4)e]:X)̣Mg 0%iA4+jtEN|b1Cʐ/qUIïo!�O"? X؊dx7}YiSq\5QH̕EUrVLD׶*rNɼ<R&-9*!x x >IV"k[b\B jj pA8z+ UW6F\I]:IDErY֤3Ppda=\|JsѠK]v Ka<Y �KWOdkԋn݉FjN[·2.l_yˀMRq\B<1&1\$n #y8FS*vdh&QE_mUJ䌈 sJ}ILQZUhX`HwmܽRI{_HVmHWHP Ytrxqs\!luXR}vnL K#஁^VQ\îVj/)H4i-mS(hE4G4WXȊBB*/BE_?Chm Zrkdt3fc:|Vn:@FLNgsO )ԼkSfeҼW�mH [oV29jUC6zPse'ȹ&؝lI6#=!ܔǦR\wz0* m8$�c0y_CsO)�D$Q$BEBLQx**/EN Ӷ XrMɽAw \!PЫKio#-`K J"MkB8sQ!yԉJkHNG}rEjoO|#t*)"X.Ԇͭ!ƙ~1{?(?f tSpbG ںhBd {ミ8m~�O�~�O�~�O�{bM]괨L)r22D6U=FmY^=?v6ޕkR%@xdŠuʢ`Tl3>)"Kԏ n~bul5O*|$|S.`7Sl2vӃ=R<׭u&35GzUؑk[vLr*KƮ?"E"^"U_ ?X"Fa@{Y�l5dgSQUUknܨ ]BD)rLT2 4 磛9AN4*xOXҘI%rLi ߊ MJ&|X="=o/3F4.HmӁ 4BBD!TTi Qz4Nӭ9T6f&*`HBm%-/]nɜByd  ,D~N\M7.h,0z%徺.vr߈�'l^cWqV5PHyL62kj݇!ĠѣDґBi>-2sώRM*[Tʕ(VUROhY:R#1BMc@pmeޯڷswZhUB- Lpc我?<-7|}W?(p*=�n,p™MkEOrR$iRf\acL8GiY!Z[3vsY�qiP%Nh*t7qzy_04OBm|\Gdm)543tnd=o'"S pBڴip)7Cjlzs"KèqÓ/1fY/1t d*Zplc SjTS. a!\lfTë;zʤ:o le\wp&̳#u3qtq_muꍪ3\r!Zn^&Q0DT%R,ֳUBT3DJT3/BQY'HC>NǤS-\^,ʼ9UJFzU!S,pD95",6^զŕpӡWG[e= ,GTc4n>NL6BlK.-ZL44 ܈Fím@}&=fO;lU$pmWNQ'BJwNf#.3$v2rOv<{fӠ8ۂ&ف&D&&*I?~F)(rIT4<_Lϡhڱu/QyNs[ugjJYmұfV(T:9h$WS[.!M+*6a*/1HwmLxŨF[!rL;\qIc-Nm^O򻵳Avţ3 "HF!sTUԍ(۸mK=#�w ��Sn/�mqDv6^,0w@}%*":-?v6es{*o4|Ƞ)Ô<P/4μH/hfp:^1DUN;HIqc*{\N=[c=cV ޣqST<D"n9s\R<1h'CCy adԦ{=4/ݤ٭Zb+B7kݭQoVUWU|.Q ^ܗFc6A�v1 -k:۠?Mf`C5D! *.yNߢ w_fұEfSFx{G^4dѧ/ISf_8S܉$"fèlY,%NwDv$'C|.؛ž>꫟dm_[{'r5Wx_[FvM}+RteJ ,V^d�ՙ *T 5@\"!:.Rg�Q}w'sVPEQrA?�+A?�+"\kvsjIFT3+'eŽ{HZuTW,\V>۔D>7O%ܐ]"*.@MͥS `LqۆM2 Bf*+r7inzsd�M7}_n_CsO)�D)RX$ >lNB*"\rNQqĐ YtQ["TRDȒ:ٯ[[1GmҢԡD r^vUY5Nُ Jn},GSvZQܸɂԺs\8;\$t u!mUFqɕp8Ky1{?(?QKdpScn;Nlٺ闂 *feEU& YnVs<qmFk86~#nz>=."+0keDf*FD*;^؟6eN|11# tTK"AELBD!\ӄӦğȅ%L?*Di/NʃZt*e͉љZz オQ Wں~ƅ�۴~6�5SA{\c>-�=mM۫ʐ*&9"r,&Tvdi}t:mO*hg�)ܞT?�˷S<�j|b C$24b4#&ԓ>9i;bo~Q}m쟹gvO ^kw=06*mDARއ>59&`,qHަ̵ѐ/O*hg�)ܞT?�˷S<�l*]cpͮSg4<VeRwZK7QHOFݓMg7-b{<|ڝ/ LdK-OPͷF]>5ݴ͚{=�[_u4}~}U?B NfsxIvgS_m6 $CӴ/d$n a%^�γ"****fQQz<h'TcT*H\j2G"ndA:t=k>i-r+눨3je3[bf>mW>tU^O{~R/1;-{8?malckZq KHo&]bF?* +Bm1ۍ2Ѣ$rKM2E=#[{G<kohMmjuNGiRcifKmpQXK\6,K:yIz*ST!]ԤR <xgd*bΪ7CQ>שCy 'ʟ{Qp[B/n&KH")X*ZW""'J"yvHF*"%V"ݸ7,iZ&VØp*7K DUr;Ev5b#+µ{r4z5N:nEouN󉹐Wre5)[U`vNS!IQƜۿL1W$TA{t+FdT~Us򌍰odS?cmn:ZW*p@S7 M1 4`{$㒦^J z,9{:cGj' ̇A<};D|XaԘjLi O#:٦h@tJ>NVj:%"T&h~DR3"T'p1U|j"lz>c{Y8N 6 bݾχw=.;{|'Lj 7BF(W^ a̖uo\Nj' ҊE z'aCu!й*02fTg[~<}$6eCix8W?cBD)9* EN'ȡC hޚ4i=\>*S�Y2Ԇ] >ٲF:*'Xv0ң@KNn)⹩5$7 [MEnb]n2yKq*n| D#qOwI2[mg!R�ݫسs(ѕ<syRꑶt,q`s"E- &vaMn.N&*$*^jڟ52Z|&Ѩm:2_|e5"^*ciԿ&K N ^8[X~m6?źmnDkKYgM>21^GAXt2z'K*< B֚䔝]D\xe֜ 4gI(MG2Iֱãk8Y35wRE%^,@tgAkˇsS T#=&^ɢc>%ɤY57pqdc)R">耾Iؙ2QKڜMXK1.UJf*9z1#xO(- d״m ̀D <ɲ|8g5gڷH7DQtJ5oP!ǗI Mf;B쬮3gZ�l@5R~JAT r5)h@AH, yE:]lMQ3j6ƌ) D.fKvn9Uo`̅1%m84ۆ PkK!;Mi^nLgt& \N7XGٱA?N9 . N6Wy[얻kAaPvK\U$yFơ^s2$EL`]HSڃřWłbJc>`Wϙ&qoaW~.>7`~|ۮ׆rmnТ9*RZSnaj&\sqRXPƓQW4S]N->Sk"iQ7 $RBR͐^QLG}צ&ء.4 j\nQYɜEVPߓ>g OK4p*% T-ԫdD*p4LMd9M 1uXM.ϬIJ{(49.!"5 TրI!m$F5rҮQWhDakKa%UV^nS�-6FjU.)V6$HșRS�.1Jnj & .ZbU/Kc+e&l(ÑTޙ';f5ouVfaDZ�?ڧ?Zkӭ4ayW5 Ŀ Š#yM-HUvMiC�1S˧ڮZ.bU JGRg#M02mim�"dDOڱDW>Wk}!z y]ץzrLVCJJXGuI(d˝Dǚ-M8,~LY6)%gx:ѷA8ӣs s%UShwgS&GA%t^l*n=5Ƌ/ /=!*R]%7 e/v -6h"Dh)6&B"p׬Ȍ22"UU]iuiͧ[/pT B%]X/TCaDALDMD"eĉT|dJ*UԺ=Ufj>PShG$`ejhى*IgW'J6sHL |)qeI>4/Uc[9e#ɑ䔉OJugxD({lK |3њמٷ. P5L:p-ھʊ>)RCM6ؑ*^ƌ5ԯ\ *|XQ"?٦e@ 9�("j "f~eZ@CUݢ/<ܶXQiE$BTUDT ^s[~",U@g*t}S_v`NP-eoRlrn %\܊jsTxm�4m6"m(�i��D2DDDDOƳQV[<Rٲ;sp3%u7LUen<2DR)95:^{s{:E5:U pyS-xܗHycecCpxE7-Y*͆ep?]4LU2D[z@RD-BY?-�hzDDOaZӌ>oO2 :٦m UDDrTTڵU@Rќs548)| %gĀ-5<icJx_r:a:uRopڳi[5:SоD'6+Mv#N�& Em+k+؋#KMTe1Vun\DU�j"SEMt SC)SN3&Ŏ".<@5S$WgdHk6o4,*nMmTP@QH3d<tL£d%SOq ^Y.=>-ijrQ 1*O-RU%O4@RE&=eYfCЙhfH9!.jAUDSji+PO xq2Fq2SԬFkEoECA*ڤWGMLŎn!6]5F]IDB]:#-#2e�C/a]VBPU\4T)hҦ,'4{=˪jbrn),,)jayu wq ^{Ul]l1iaG |9,,jNA2^9E6?l14؇Zb`ɋK =SecRu:r)Q Cdm<@XnU_nOrr >2rCT&ӓ#NV}ѣ֊tmθ2P:r+߾ ii2OiE-bK94SNX3D){:ֽ4"뷭FcHjR٘�&#Sݎ=8{K޸ީǢUF|beE-lUN3RN^l:{k.dj[tmfeTde9[z99sDEE%7,6nnMNT6<58yiω*&{{9}UTmSOFePѫGnM>ӭk=9귮7qF*/Q6GQaF noUSs=Kްi]$m$Xᾛ>F=Vu*n:z)hYJ4 ⫫rt l,+J-@Z^tf^IJ(hOs*3ṛF>O-FzW0quS5eD\LIUqH tA4k!tv 3STͲ^LJXvG+\Nh7=yjLˣk(6L$}9U0}LT5hYs+θ2,RGstMrf$q\PURGVtdʹ( feU霺;<e> +7jz#&]D]22U� ;F7n|Z[vJ}l~R+EicP)ʸDVj4Gb4`¿] IE%Ĺ7:J5EmulWUm֛}M/ŕɲ&*< 5n0єR4iTfT@nN6rYiz3"1?z"+P+O8LnjvK%u#[]EթUnb\fGp;ZeҮq�b'b;llKUXZ)=SY7V7: e ;KS y\FFҫ qbSOO4Mێnl7u%nWwdPmzuv|9'ie4h+nHY}ʘqڏLJxީ h1.k;jF&"GDQ锗/+!MajUA7Qw;pwd-J7< F S|*3ITC~˂#U aQD b*hFb >gSvC| "NfpE c;z92BnR{R^*/^RiӍ+ģ3GPH* kHHSI2㬡le+-,k=о;mUIwh WNY3hyԉKh۳kT-vU249A�qlvDϵ5)#ԻfgI/(r]�m  㱪u i#:fB;ԍ�$ˉ[tl ԣaEJ:%p̷ZC #s$re ^vVv1bbm-QA*5Fd-HɕmL̗}&xdVxljtuGbIJ|#-D6 TWUO79:`nYt(T ΃M8r.oxHJ0г-5#AKJ8k\HkTቓ35iL0w}HҸq&Uҫbr;W (TPYU.4 "8i4lG!Ɋ̃p(*6(^VK1RM|ŊE7V"A %rC0F%⛬9;%1_ysؔu:mV3'1U*q51qĔEmlsV2Ht*I),+&͓%*`"Fx~?0mvu2.Ge`Nc�#ipY /ka*/N(Yٺ-XLG.Ծ6d7+r$;,?k�z/WLJ3М.UZUI4)KH'Acҳ6d8R&e}Q8&iNj\lD"L12ۨ/,8OmQ0 pc*[B:Mr) Wi+%z]Ho/2y <ז)[ q+ o'%.6D.h~25y-4γBL&[sSJV@D 7`>rW#h+#<g%؁Rp:Yfzdv1#mqul ="NFQ&q"LlLYָ͕SۂvͷV82[ kTm0؋Er; I|݃/FY9~i"i捱qMG�KgeW>[?j!֡5mƦ-Hz|&ĥӫrY|fQoyMaMhRl(Tݷ3Opոd_Mtmz2#9)gUƈyZwzԛ>M-6MhN:LNa\ h4Np&r`QɕSG؏&+mBT-hGrƤ^#Bm^pUhL"Y4'6f7 Xi\y72b6/"]nrJ1pcLyMmB^c޴AH Gdi&A2([ןqFM$�Ȋ')@ SŪV1tj&,VbA:<quPG_~Ǻ-=MkƠ*zeR%?4"rkԃ7Yi{Ɍסmʺ^&0M#ea#L7̦]�w@oAu�Q1i8ΟmM% mbV& 8en#[pFWA/͐؋,0ĐmȖh wtm_U ?hh\6 \gieN .6ƒ[x%:W9#HM[m}HrV Q/lɎQ2'wL1z45[פ:K*4qv£,WKmwf##s6r$i8{{][2X˪n3UCNs-w'h:Ƴ(pޕ&J3(ժe>) o%Y77ћf>M"N >ٗ0"ɕn9 eSENl]&܃m왁,* L ;ڔ%I� Dnmxᐶ"S1ozbZ6n," lC .6T|NtT֍Fvd$Mɇljm_(^fC2Hyss1>u+쾽a:DE)-V2]=G48]NMslH:~P§*3 _Tc LVE"ot6DIOd->&ճҝ:͕ClUM<cQ4*9r"qoH7 ߫Ko D} h&b."mRl+zWeNš :36+ںm&OpC*+ 6-%22ʺW>3uںEh W&owPS6Q.l=UQaeLyu ȱbF!9QN�"]'2bNw2-f|胧 f"N{!i.Y6lg,m7T\1Qˑ|Ľ{ ՙc& ŽXub[N0d@˄ ՌEq鱥EʥRْu:, \ɮ+P]-6EnN!]3դTU'$S1q%V~qmFv&7LPմiiVD1>F|cRUuI<1ⲥ#)h$JpmECT% u*)jIBF0&e \& d X7i1[ULG8 5UvdV b7xX _O!*o#e$Qg\iؘ"Ú[kDMWu ^nE|Fs:5T4O2׶?.{ڍHuu �vE23imܚ}a&HwdqoV@PG V\%B=!!ѵ컲踩R2e@!,'V_*dx$Je3H T(9>l(lcq}-5޼`%sr\lGA�k@h2NzL)1G:@W(ۍ *"d\2^;OeJcHdM5+D//hlAq'}[: x>N=4$= ;C&hmtdkhrB*9eŝqx^he@C\qNlU&d?iq[G15bj-:QWTk0f *Qq3Q˷QxE]uSu7�q#r~Fc־c%>R?n?{k7jy7Cb3J%DRAue5,J<BIV[6Df =eշ-BݺzCaA₌Aҙ"޶<ߴdaf18 # 8(>h$STF%ţRcOˣ 13Ao<XI8ﭝI#jm?[`7koѶ"w|eHPpLM6 l϶Q=c"9qGc5Ԋ ::ђ:\;2K`dlB`@ed:2"$ m^713+)P̜q m9ZELu A`�+ij17؅Ѧq iZ;^jU pkۥZy^թNN֦KHz޻2Ժ]Jޞ,.Ǧb4HZzb"J\ˊEb0"7Й"vKhc❷q;UO'?$<3DRϧk; ?L{[P;Ɂ8 =2N Љ۱l]Lz2L.L!B^zU4`nTO'HHK6^\>?L*}d[›D7h!\#9X%MFbj* ,ZvK}1dRQ^`^B'+=jrDα41Lo۽yL~Q<0xDZ]ZA.-Ov�+vS9$l 6]**cLp4OW'sMM& - +1j0R veAAx›+Y07;$mj\_R e8K7 5߸"g\Bɩq0l�5hWXax'zgęWm-tzyHkVҢHBHD V2@VP'Bh3=K[>98"O1!A8B<߫JxxteW>[?k+4WGf}Jui.v^CmULC\GQ]mj^p7O7ejCh>_ZةeE�uIml:C6P zTG (nL*S.jcLUSIH~U=a#61ީp#5G*! &h_$;hUmK�e(c p'U$P"E8.:B" 2Ј($Aed֮*j69,b|GPNSY0A94,W`QҮ9/E䊙ڷ *.Ydyસ_OWRNV|˴Q:ipő*Yu!DU$EsD%ڃyb"7&`RmKV Oƅ6^8=.:0օrӬ&7$*Rq{#f\˔Qr<Emƙ%tHOYhTTLJ/s*$b~;rmzb=İhD>UUj&)6.vLbL@hfj[+M]nĨH+V|7,Q"M(Li#/ɈUGhINJ[ JTUEiQs<"~|5j;8{dTimjAשݐJAnusUKf+QIЍP*UWqFd2㒪5Hɍ%%I ؇҅#}ɳ@yNzŻ&Z%6RWɻ 3H깓2ʶh bĦSHkK$DewlzβГbF2@ϦJ:V ަ5|iY5qV3јǃ?~]JaeYD'RnDs5_2Oʿ?bl{PK=}&ZS܏%DzD.'2KdH,WKNs#]V& ,]ۄ :M:BMʋ>*TZrzimdqZFcS v6n/5[? _}?[ݍC%c'_X��~ݑ+Fi䕤-)E~QO Pö1t2ZT)b̤lVŎ(&Lixuj˲qa<lP~#% %jYDTy- HΣ|΂f[Xz֭æLޯ$C8қ5nSnB0RGC@Kb~;r{u)5:bR <ȪÒTWDy.I\egt6uX3r;?G93rx$)@=-3yl6αk F)+IK1 LC~,$OU!I֝ 쐻h COL){&*طl]eu\~'~On kjhHvScQ]q 䐔�>露륩""vFR,JeS5YE'rm۠R5yŇ[AUp&\Xb֣ mzm5O*w) S&ՅLIu?mp:vX"Φ1!.>ٱ6;HBQ_rS Y,uON,U9*Сƌo0šU5eUKNjx#T)kT hYG@ -hD:{ǃ_Q?-�!^%U-+ꍻ[ m.*<vlun zQU0R٭6:5 &'7)/KO4[OɎH{Ekn5"S ~S&lN+"3I/zkj$}@-ԨTJTI-jȍ^o[dm�Sf`Yf$H]AČMթpϐ"_,�+l겤n#JzL|p)LIMHՇץ{H\/2Cwp[n;55}y*Iqs911 ]}.3o)1 vGz}`eƨ #S'zۤ: M,?;袠)""JĕQWt'lGT+5GU-Xc| a^smUܡ*f7DNl}㞦]lۦIdhIeܶۊ:7Aa.�,'GJzB)cL>F4"̹i7=Fu] \YTE2NLg0eb;I<qFl!MEd~%%#}m5E|[yZܸ tb=FckTdc%cdG0`NKb]z*:3\yלFHM/;_&Zd.x,"+؎w"ʩfH##..KCHKhXq9L !2 WB$!m BB$$LЗqW,ō()LPF"9kĤyۻӞػ"FǢTtvΟQ2ݭ@ H˾! \6XE6Q|+3ο*b[Fعr#m+ܜnH2.\"SeR^dNOLaTi3~CFnniĪh$7h}ޔAH]ZОnJvA9ClʐNHuFQI#cr1olJ[9@#Ε%e\>c컖̥ܱZok^V\@&oct siu@YX-n}l\O,ɖ̙-Az H9!%Qo|F62(2Bhb'\J#SZ$gSmȒ*1iSiZ"axVhf\P{MTzZg,$伞De?\陵yW%.n D*AEr+< 5qmsAz;X݅NۦP);M9'1LV3stl-�:lR`bzFr�TF 449{WNi%7<F.ƥƯ=_eEόn&*'c;Ql^ZN%aMЫ3-ZdbQ*쑬g[0x@wilPgcNjI٧BQYD(lY6B%O(G^pLꛈ1ګTy-ȉA, @ 2\Mث�Cu)%I}b[^xb իלq[i7e0g7h$bZ@c}ث@^4hv-SscuGBinC)+2|ƙe1/ !vj<G[Ԩ& [+,2oS8y(ti;"!Y4iDk:'$Drg'6$i+ p RW;㒓HCEuˎ /HY< 4ZeN M۵z!çyd9DrTo5(5fýx\eN M۵z!çyd9DrTo5(5fýx\լ˖N=[7v>9.Y�w2pf衭}ԥPpQq Ö42f:D)fM1\d_2TWm]\AHޱּ USaxPA=)iU@t7mJ>tBz$Q,˝#T e E!UQ' -TQkFn8uYz%V28ӅTjTXnhd-n7dlrU.I{K?1TVu5 ZG\' m`˲}&Yn j% R6͔Sqcv 'd;1 #,̹k.ʨ:YH#QGy<L&K2FJ\ݮRjVNdbFuLtoxA�u`ES-ݣaã\0_RjR|rEx䎩'^ Y1[UhT5Z: -bt>Ug;p#עT!=TL9SHxؒD1մJ|\ﻝՖݏ i `Uicŕ1oOmϑČ: Gw:jEb-WG㭱yxao66mIS^%܏oMaZ16N M*R行'u'XvⶮsJi͌#eTrk66j|dMLH|4*̙څYuIj30A^~9"z!&mM~3"tn]]|h5LIȍ7~PМ�YZU Ut(OocH8!Bf(i访e̎mb. z(߆P}]BEA2a'Ev[r3"8ӉTn{ō8w`\sԱ'aE@Q8TXY4ލn:aiׁ&L&2b o't)sdyQ.rf%91c.NnKE랍|S' 7*!ft*l"#\p@4bZiCîr#ێEx7c8`l:q~9*dβ*a.3abBRf';P6mIGV+4.mv1?uP6 C.J샼"*CcJdUn4EZsLCzm褈`svZtR!NӦHEO-[r推%/֛nG\}؊t8 л*1d>z)̸eUWFIUDirl &[.w{0i| pb*fS4jNvLmڒWh]1&ے/b(m@\xD+zUdƔDi)j9>#f6$N")I>lrRV2MZlI˩Ks)PddmAk[\LS�ʳT.Ypfj.du=N#R  ٗ-&rk5ƦSW@c\*H\ѣx+eevU0EMЫo˪,9s4͕bcƛs0pyGխʗknZQϤ+4FYb8O6<B[ȷifz:uHQޓژ U e ᅑЃo$&LG_Q0S(Z 6fh&7&S7.mwwSNlf9u.@XyP$ܒm`G1\UZEfԚ^ob#n:fNG)&߶di`4ABTR i!{T+ջO0 vfw)M䫪,6u$Ȟ7JFdeåW4|{G-Ԇũrz_E*#e= 59ՠ",1id)*|ѵG#}oW%VUs]&7] SIC6f[XsolI[HmUиdb L픪ir+8M7)͹RqxPZH7qGH^#Yw-{yU"ߛڼYHU#"I̗dJ"랱hb W,K Z[.G4#P\3m2q$paBםv/TR%FX <*.GA KrS0Hbjn}:NפHc @s{R7P9M.7RD9)MCҀΪBp�Q$AE^U@}Bi6poZ h]KS"e™Vcrdxm#)b截(ihlf<cqrqRꢸ\EˎIu^Qj$f676o;DLjY悅^KFyŒopq2J "Й% $eBB*$BH EENTݼq6:NJ|<RFS$4 9a@W4N!Ž&ј@"4sWHўB.&y ) tDَ5I$dfH!o`[o"Dթxdp:.]"9i&<t;1~I<P4{mϽ'J%vfyۓZLE9bTyqA%n5̍q棼ܑ5o }.bI B"MRE4b9\ݦݽfbI+E2(D>r ZRE1Niooc;.[o:i3QTQ0#l %1)juUDUI4:|m;v$o (5ۿ$)6UlSayBqYQq=ݽ7ؑ%nPjTj\rkxq]i'ȝ]x&E3%VՏozŷþP-Rm|&Tqh^sc"%- Auy2�iy8qPU2yͦ̅"ηmǂ.@s/ BW)6$nUDU.kwr6zjy}[rf*e�|3ș"%ȑHt!b@b&ޥM 5La%J /<mMj=)fj^{$T=+" t8ִPUBCxw%Dѡ㒤e$NxT:E$QuR4TJ^&QԘ́W˖B0mXnA8ZZ!p ]- җ7}DVcQ_PʗLy0"@,ՔdK$RDU .V1UǤT'tr #lH9;]Vli]ԙ-vfyۓZLE9bTyqA%n5̍q棼ܑ5ƫ~iv ȼb9"6xo Ib_Od;T9*/7H=UF\_B,::J9<%TloPHm@HLTI-턈ū!�E"-h(Lqs2F ]0-.zMbbCM,k6v ǑtJFbqbN֩ jB3E#6p<8`:M :d63UAMDJ jl.!Su(QGysN" j4{ܦLo<2!$E=q3CkGV~SM_U*H N6Đu*ӛ{Ma4JNAvuFCPGelQI mLvC*E4swE&KJ ^ˡ\ݡMQ/GXy\ʊ!'j -UJ[!%&jmՈ-;WIiQTWz8((QDȭF,^Gv*]*M)}k&K�}K\wǶvM4$yD͵Ż.Bo<) "Ϻ%67 DrV:Sw-dSyPmrH>-Ӡ‚24VZ+btb@yz"#Ԥ"20#V[HUC-S]*#q5M9rU9Z]rCX<6C7+o<}KiIMefhwE:KV� bw .1Aw�*M**l2㌴㌪&mDd TUSkf*2Mg-JQg'|D)$EȪ85v^H|H+IU:o*maF~SNg˟)Ц9N#jCf;ԡɋ9 42&RWT׻QPE6ܨ^Z\dމMMɽƆҎ0'oiy$y\K7y ,o CDNN>%l/ťt ʣ\cw](DQ+h�*P5+h\dF՗PS$Y*w<jrC]%RUڳY)wqbTnm6)M; iݓirpq7e^q=l޶)GWFIwxL2m8su3ώJ3YUEȦ H>~"vaڡC"fO o�tn5K B*~M7A(&*RT/1h3"u0Y|DE1*nq(JkjNl\]hbS\R%5U%UdKǓ Q1MmƜƋ QB G4ީ**PJlΩ%]\pQ 9NidtWX*uBLSuKVx}$ )yqrX͘f^-6/8ȧx  WByT[ƩQv5Q$xP#Ct(:Ĝ: 4d"O7_|ǙJd"(w3QlƋ! iI"Vǡ֝rACS$+?L^aMQ)76P2()|x"]-QUajsXԨ/}<$&Xb8&FJI Dϰ v1q" eoFE�J( }-qsA^6ǩxjŰ7 tbFDE3%=t L[.[-J+M3�WF;ݳp:.2Jq>E,%ȲjuQa6əˮIKz2[%QU0LSF@ :yIDBTu3hOzUUQ,zvRn\TgP,"UE-"8D|oQ!;Tr*H$-m5 jp:)tN%.0 <V E}!E#DB4iݠ0p#ת ʘnF$掶Һ\m]d |PDw5nCmM(b$aŷS,x/Z*)G "1P͇$z y\Wh˜Ô'Շd>댪KRl>.3"Ӌ줯|lGfmqK{oR&)F`S\!ra6%פPW4ոc)31cÊ3+-a�^U]ZmUi5h.֢Fï4zA:MoZm֕ mӄ˾^\Vc)!Bn88El`M;q]yևCcR3IpA}m<M:qL@NPbDO3�a7oJgKeMfTF\m;%Q`P7S9d ƣ1'x >á7A3[ub%r)TRYuj! "-Cq q[ӷhry 6*x�ⶆqx"۵uN-Hӎ([BdCR !iH9+咭XB.U]ME=DJӃ%Bȑy%Ҫ{~9s7aj7"}\DDSQlVІbPtjGRiDzZEI%rDm@gŀx/�s1B?=PSNԸLzYq3נB8R%>NĈ/m6*ľ/B.ޯEn<B}T$SRQtwغʦb[u)֍"e]mfȒ%H{'ONqY#c Z\VNOOʡ J8գ1;pź|hzMNmuRDHe DUl{5 M/T/YkwdyZlk*.F2rB`઄9x&{+eTu*E[G׽W WH]b"e[K׵nlCzu1q}i+/ 6J=y߂o#נ=Djuz' -j@y"b͎wAm.KZ߉KRɜx.LiϤn8H "~@ɰLrALץrL5_])$=)zQ<i[R!KdNHuuQBi>-lL#Q:%O2MzT As!BҾ1<Μ-l]m֗P@D+ndcT 1]BL%>EGyG[n8n* )�$;~P0vʨ2:�e!aAm7mMѰ�u.AWU8�TIAuUStQQQEO簈 tȉ6݂82CҚ ƙ#M#A2"^AM-6=:@P?C@IDTQ"xS\׏M4m.kƺQ3_xOUmꭻ⟕}Uw<Sv襁~TzVOʏA۾ۻ)Q;w[ws?*=nnxGUmꭻ⟕}Uw<Sv襁~TzVOʏA۾ۻ)Q;w[ws?*=nnxGUmꭻ⟕}Uw<Sv襁~TzVOʏA۾ۻ)Q;w[ws?*=nnxGUmꭻ⟕}Uw<Sv襁~TzVOʏA۾ۻ)Q;w[ws?*=nnxGUmꭻ⟕}Uw<Sv襁~TzVOʏA۾ۻ)Q;w[ws?*=nnxGUmꭻ⟕}Uw<Sv襁~TzVOʏA۾ۻ)Q;w[ws?*=nnxGUmꭻ⟕}Uw<Sv襁~TzVOʏA۾ۻ)Q;w[ws?*=nnxGUmꭻ⟕}Uw<Sv襁~TzVOʏA۾ۻ)Q;w[ws?*=nnxGUmꭻ⟕}Uw<Sv襁~TzVOʏA۾ۻ)Q;w[ws?*=nnxGUmꭻ⟕}Uw<Sv襁~TzVOʏA۾ۻ)Q;w[ws?*=nnxGUmꭻ⟕}Uw<Sv襁~TzVOʏA۾ۻ)Q;w[ws?*=nnxGUmꭻ⟕}Uw<Sv襁~TzVOʏA۾ۻ)Q;w[ws?*=nnxGUmꭻ⟕}Uw<Sv襁~TzVOʏA۾ۻ)Q;w[ws?*=nnxGUmꭻ⟕}Uw<Sv襁~TzVOʏA۾ۻ)Q;w[ws?*=nnxGUmꭻ⟕}Uw<Sv襁~TzVOʏA۾ۻ)Q;w[ws?*=nnxGUmꭻ⟕}Uw<Sv襁~TzVOʏA۾ۻ)Q;w[ws?*=n�dRS#@\ڍ uМ m̐QH3^*w_#*_qWʗ`~wqU2XG|~{w_#*_qWʗ`~wqU2XG|~{w_#*_qWʗ`~wqU2XG|~{w_#*_qWʗ`~wqU2XG|~{w_#*_qWʗ`~wqU2XG|~{w_#*_qWʗ`~wqU2XG|~{w_#*_qWʗ`~wqU2XG|~{w_#*_qWʗ`~wqU2XG|~{w_#*_qWʗ`~wqU2XG|~{w_#*_|(: :U^k΃NN [dgᐧ4Wۆˀdf. B`S**faFkB$*YN RT\4K/Zmrr\`_Я#O6eߩ<ӟ'_c%ˆm-ܢWXD>%x'[`x1]w�UΨCR 4=fI~fBU|GTv9TCxj/6XO]S'ƍض:[O~_>f `J+Qj}^(8C"_ ~6TiP 3\~>˯M݈5vU6tȲyyIm:W12l5~\ӥMHqO"h�oUJU j]:[#"w_<S8mknzpTa5%ArZ%G\y"+GxoX&tASyI3A=ٮhI7U"BkWꓦQjp謩.)؄=I7:BxH7 i]f|XS`e/+#L>^ ~2rRZl$++Th۰1m0|=dJ$%"uK_EH z8I.zY7;њ4+b$xyj_Sw;mE쩹K<ڠx(Grڒ!nrL<4ϫh2|sBŏ->!%x !y.vlbr?2>cn\0e݉!ai,R쪻r@t-L`!*'I /:67ު~DnnhtUqcřLrJnd4/3%IRVȐ"ع, 6|ɒ.>>N#L2rhj%ԩ*n�'mT�oSw;m~oU??eM#Fڢ|a;9;$BVM։T%zR3*<yMgO2]�R$?Jϭu_GU\šC)RQ5{e x=t |ȵz̢5.=r|ë^t#m8Lnj�ة 檻S-d9_6 ;BDID^6eĮI!VTu F_6g*6uWY ydWOV]9�Fs) giX8c.x)t"~ou mb}� �;-mUlX/]-ԣ~.(A:$?ƬGl�e\JEV2 )HBq$P׳2Aȓ%q"qt̋275URUU"_>s|j䵑)} GV*FhP%F@()+GLntz$`>mdǫU_mW!xNXmP ͷzJ)!q’m6kaDEiL=U$EBPUEO_/ɶ5`oZkbt9K㰆R;MmL8< dnD-䈙fuZ=s.@YOT$<ܑ 1}ZC%MI)q�m@�Q@2N""pDDکLZ˥#72N<֜L8*Z*.D*U;0݈g faqѰ'4ȧ߼e-A#|y*?Ͷ`}kE0B,kC|b8 uW|ES$qGGe#$dĈZp[#5lBTקÇI:D?0:?V%`3̗ \KKn $e'6"Q*/FR*3 e]U4zRԽ(Hpjc^ԧ;{QFL\HӪ-rcQ\CHQxg "H")"pDDV6}k�r?ꗝqIzE3U⭻\$R8*ezzh0dԥ5 +<ZG)f8"mo[0h,f<^k?'/!D!$TQ$U4Tً6F7i^`/8SN#{}oSk]Wfܒp]6cN#щtI`+Ƌ_z8P0GϊȮYZZ/2$ԝEtcw"GE<QϚo&]e)%Qd3L"fJPImIk2�P#Ʉx(Î+({=tUQWR$C4*D�ԧ,"< .~4aaffL3A?[.૒t.إN5&ke`ᝤyt^e%Oٙ[S6CZr5Si �("<QĈ'ԝ�T�5eN݊n/۶1]w�TIbRBeYt\_, (çMbwSqxeޒj_2dtXOj;N<}mc qBnB6ˏɵJƼhNTVxO<bum�<|C>r_0㨟*'ϗqg?՟-SA]3!ʊq_LG>�Q8UECJ`'"5z0FtנC_IPE41mC#BXl�¾hf:,Dπ3tؑ 1 Sh6qAx UO{vV.=zD��Vv5^]>sTT%9IdAkYJ/] e_(\ɦE.RsLyS%[ef2/D}L{"b@b$$$BB E@ҩH)j<ZHGM=yŚ|p \W6F}O;MPK|,/N&nƩ/y>y:P4immT̓j59OD㑫Q#2sޓo8邋[lD 1JrȨZ5Aʔ#rTD-@%Dyܐ(k7@GDAr<.O<'̏.Eh0Q&Y,eߩ;�5v�jnͻ_m~&?cgֺA#i e3Lj<>3J iq&ј[i>@^yϸ j7$�R"TDO:gozo�v� �ozo�v� �5L*P^x mLf!5U_y Vk&:Cj2ar bؑ"mbPiQi"",-O<<KOIML%pJ*V4 ele~G;ͬ5cUN"Kk5:>y#|6\^Wfq ��S2"%Wkoc0r`$jYz{~Gӳm4 l 6<�H'M>̓LI<J$϶yE 5ܽf?kѐt.N6d`i 䢩Ԩjd,y0?T}̼ND<{Vhx2VS%tj|3i]B$)T9*#e3Uv1ihNUTһo�:l?"+}Iyd$ֻb7ڇF6}Ѿ�}/iӽnShvH% izf g MVߵbJ .4�DD7@5K}rK<xzSހ^"!&D !^ .e#}&ĊMpP(md:!"h$ D8IQdB9lTgMttӭTT\T=VMHuՇx::IȢH$Bbē1!^$\SILA(Wn*{^\O|WwjJ%rdM>Z6 ݊]O2#ʧD m�o5.E;܍ 7BV2R#%.S* u Qh'GϪ;e|vJO�`*uc`B($t$_n<!>S9&WGx/ԝ�T�5eN݊n/۶1]w�T߂q?eO"O]jRE¯ɼ=~s>5 (K[aS;S1Hjm%G{GFb4fm[i�LŚm 3AS_ox%5"68Üzɰ%K1<UcSb# L'/lcIjDx$:ՉqRy4ڹi7e&=f+ $~3H[t<F 1"ˋ{[RrRrMLlP3>EϥQ\< 8lyđQ~M $K2j g.<Ϙ %UTrEUUDDmp3(BސUI^njB%r7yigG{N#-B^PsPUzM:zT8*"hQzzz� r3yjr p,6(6KҨx:2ٺČzE3Uᬻ[=(wV"/*'ReIˬp2.&e6[O :<]$Sl*6ǕUB_,v[j# "v)d+U&@vV}<LNQSmklF[PЦ/7?u"t(9dG�39BEje2OdXbL.]&k]$dDbV(Sl65 RNנo<Tp12yPUƫXUO-* YZV1NM\fJ=u鎺 H�bKқqN܍ZQC4T!qg^K-> 4:cD6 sEv솱bja-͋0˟8^ xy{l!!uKuF75͆:t5&RS^9U3EO t[s^m2Y�_ *NpRFhϺaf�Y-J胮Ces-)"sdh'U9][`3*޹$D{'6L +sSMn*EsӐ)gL[- eMJ(9*3D= R_[.+e.Nu6dƠ$ÀsZ_PzMB[N)42E;>+;L+.55fTx[^lnqxFr]`.+%R6B]WjF۱_ܭvcf6}k�r?IP1rhY~uGlbi$*.{R t9b%_N'ʆ R" h̆ճVNJH.h?j?Yn4,ksڏ5CU&+&{MJdN~?5ʽpR=q~Fv6қf\duQ_R:6{S'2m]K*]jK"mNBh-u }czu9@7WX4Fs\B!CgϯB{G N!dPZ5�LV`!98_>h O 5_QB5Uv+ HrTk%'z>WOaQʅx݀'Խ;MLSmY3S"Dϩt*SkYC堪AϓY�{XTFj)D^Caf$_?_ :,Hs6'^u*Ҿ4\=<nHk[b7ڇF6}Ѿ�vknV1̠یʪx.Ku5*xZl?uD$kD յo:Ě|i/yˏ1phelQs5ԤJkv9V&nu D PT.=du :ѣL-9e@CȻ pd~.:għE%"koW 5AW- 􊥹J"ɇ "ʸI|,; ?}E%.AԹ6܇:|EnVȗe~잳S^U=UHSSBXoT51rD(i^lWU_!p[426i$Z5*B &4$WMI"`+6�PDE8DN*ѷd(Fl[t) 9"SEMҧ#D6q98:Zq{⋦"R D)/YNU*<$Ah~U>cboEj>()1EOi۲@oNP1N*]BI(3^�sz권 r& ZB8HFѩDQzrDN wMGOJ D/#'�eߩ;�5v�jnͻ_m~&?cgֺA#+T=R"}2eĴ" ՗/c-QI\EѼ_ >Z"I}l ND^:SneE^`2l9TߞC2+ս{kcàv+>UHu󙎢>KrUrGe]zP:cI1n{̇$8-Ҟ=:QM$W I''\bCLI(&[bf鮢sAVdڪasdjzNC>%P5}I,>KЌ\'_?Sص\-ZBF@_1}gSH .˶O$5mC#BXl�»v2.?*ORjp!BLđDzΜ6)Ԧ9-.J||w0m"UHfLJψ}$)4Dr^$!WOduZ^=Mj)P K𛎟J0gʾTگ^NeJI.{osl&ЀP̛0p DrQ!]Hl.hՕ49 h5(b-d\7R> r'P$ _ 3lAMƜNp8"IlI‹'2/ ګ$giEH9Wf#n5ȷ+SF[+⺎FO޴zz\A :D#M.`c@a2d|~#lY-1 ۴55":qQ}hS8^IxmJ“*t\UF1Î!ESqW4CSvQMI�68YgeUW`~E6#!LQ:UUrDUvKm-K"ޣn5QS">'&uXZml{!KDRt?beB"N9좢$$*BQQxTMrL5aWEEqI8m|4;%&UݬfLw)λ gH7t8CaG4n츓*z/On#b"2#2R3%2"\Ȉ2%U⪪]w=#zC֩ 1qM:trq3ELS3NğQ$/Zm5zzŷjUe"F�}&G]""Z'Xk�zx.ץt:0*嚗58G|2mr^skњD UDPrTT⊊ [WNpgtQ7:e3񻖂UsVm0qL <bC*|Bʿ:Kl'%/myK~(6UIptEM8B㤀�)䈉׵FJ%-(>(Ds6|{v7m>lqj (K͊|"d믨LH 3EQ$ȓ.ث`̲ & 8T:HKAUh7p2X%T,CEx*1.1KufNJ1^ H2ֲPvuiǝ$u. DԈm|WݕȪR9ɾg&cbypKЩN YieS>9Ifg.:8)R _zsUM%UđCe4L(dOHpTS؉D E<Ť�QUs^ t/N+kzJTx)MN|^d"h9䋞׍Li'da:xp̋OZlA4sؑETIUEQz8*/O_ 2Er*e3^/<e8~W�l6`qx|"/EW==<nHk[b7ڇF6}Ѿ�v[-)#qΝ!I \_| cR�g))1mIm#.49;㩩cvmHLȌs"%R"UUU⪫־ŋޗ6K(cG)`7S4)(. 9;Mm֭yM@z NsiE}ͷ^l:"_c5ګ]-0&Z 6ЈY_GfCfĆ}EAƞqx(f&+$LH"țLU23I;0:ݬē;c.P JbmShpaSJ|Hb7 F`4ȃc'Z.d=b MNou";瘐n"n !".�Wf-B|L1#<+n4+|DTBI3MjcV2+/ǪEv[*T8<ewk|jC|itZ|JU23pYc5qR"%UR3"#3R3%%R]S)FŪ> E-BKlK>ۂDoj-/%m%3MZ^J۟Jgj o $$ $QQSQS$:>.^O:�mX]48&9vOݒFvvJ;PZy%aWj=?X?xmY쬬;(:qV\x1دM1&V lq!p [g(BbcFؼ f�"J*qE\dAbrʪZ5=> œH^+*N"7Zn.~ɢͲ`nPL[8v~$"7He|ʋoȑ)vK񓆿5UfzKv] bDdM+@i3.TH{p ҈P^4j6SW:̂r4@^ 'Xf$+EUk_tv�I#d>FqsV%NidbNMCvV i-> c\*Rt>AZ.k`|ͬIjy̿K}EfsDԊÍFpix�=c(1$2%$wulc:pA_ȟRo1UC.Y*'(FHON *q&^jCMÀ.n6Zz|SjI$n d܀L4>/#y.3/_gڑoRhdr w̮OG i8-2Щ*J|}U3@xo|':*fUU=8WڔWJ[`^rON/UDUE8*t*t�Ohͩ " L'<[ qMb%nCj ą @n;t=u8n)Jfd*W:8{{y>ֶܑou mb}� y2mL]i\ P*u.W0Kq \p�E")B ̳|E3i$LS32$Vߍ!uAzDQdnNj"?E^UV<Wٓxˏx~)j�شLלmPź̈́D$#LoG<ソiyL4 4,LmB "DM󷘤ɡU4Zt]MLpx4sW[K]6&PS3.%t| z;d)qF-=GwT${|J[SjKjVO4PeRo>I՜v6+*n}MC5$��jee)C@'K_z^s0i*yH/:R)-cMu<}zMgtYoK75n%  qH'�2WB'$Ur* 6Dn0h%]I3kLGbzs9�ޯr䴧ɧU2ٛ&yrr}<�TR_vDSd=Qq|�W΀'^Ԛ.`AqS'$8}↑4*^ڨ5Tzziy>\y͸+ikHAqػ/G9 Tir /ܺ|K < Qq<N)UO]Oߪ:3M%%o h2zvr:Q@䫈Y˂rj"g0ȈHHTTxUUUz} TƏEU?5VSG z69)1$SڛY M#T4׷+g-]ؽ_E8BS 1y}EڻuUkKD\IW8t9PDI2QTZؒRH.sE(r <BH.H䨐7qi/LM>pD(8f䗛O܁86|* )Is !9sT^*By{~*|ir >NյՇ5JnUN< 4!M7asڏdUhը7Zi$ 2˛UOڟQIB)S#81";ۭxzW1$Hm[ƖrPGl^<j/S9б \^C⮦~OcfW8t) Dlz.ȊE`PÈU9dTWq Q*&y'zKH돾񓎼铎8#3%R"%\UUWobmlyn* z`ԡF\UW*6*㄀Č�<Y pj&ZT#N<IErjzIy̠V;'8:\q3h4ؤ]S%�͵c铨(v+&NPcc.&WuGlS KY/7>|jH&cxRzC>/q#_fbؑ"'JLDE9:,E2M㟸m~^֕jVMI""] }:ABBNQ?u(.f~2,* 9LY|g\i"UrQjd֟!Qc=2/m%MZ|*uM{$. ޚ(_rUV@ZR)B<1|ͺ_ {V;!0“Zʬ==&_#(iY;+dVjyV$ߟjdN'Uu HtVR)6Hil_/ ]U{Du^[qlS"TEEE⊙*xvhӍV)6M&\MˎjG7VѥHrblrT_EQ\S%hx4DMgw*~rlć?/ F 1qzϻlWii u9'.kҫ)�=� )jBrRoP#B2䋘T.̺dDDD:?mvuM\BB^s*LD-B~Em^1$[aDUq�OVq hzmJu\a9Vw4fctX즴b - W1.J(Ԩľ<M0�UciE/T{-i'ȝVqO+چwVM8e-:2,zzEiS̠?YDN.dfJdK",WοYq#NYm:L 3O<DdHSg2dĔHΐM-DԹ5Z*M]e?1 "!rb5tο)8{SBH~ M.J$ 74:u6*(D2tnId,ɽ2HJ"KTjFiTh6MlL W10$&+$*+Ш)jh{sr)ĝ-H<<QA/[RkL0khMLL�9Y쭞zߴNWV4@"nR*1+j,sEHI!ɿ?)Ϟ^TUy>le!>l߯5ǫFbS!ݵG٬Rș8"|IՄ$R>WڱWBgXPmz@9q/ugSmrmX:pIQ9Ac՜A.]s�XY3�MFD#zx^�m?�j� ��!1"AQaq23Ur#BRS @b0457C`su$%6PTVcӔ'Dt&d�e�?�JT"mשM8U Ou =Dm).I_�Hq@<!,b;2ۉN A|:DUBc]Yq4N܉U1RiGt@旯<C^6p| Ct\A4{+Ip=IY Bu莰w;cnΆ!]~2Ζ])AN 7R�VuN;!H+X8)w/z@$ohE6)IcpY8*�c/vKnTܱ=2K'M!9iNlUTWukX_w,yn1dchM?yy|Klɢ#sG1/LE\.&4Ǻ}Mr+/-swR-LЩ1S4rHmN))&T[-,kR.\>4RT(8JQ8#7pLjSڥ%=S٬yNʠ%T׫qBB $}RhA"m8uD[]WwH׽庯;_km-Ca H5\a%ԩ72TT2MJYe8۩JRw>; ah1%_P#_Lrߞ* G* rHBe<ےRKS p2۹ @?RV'lq].+"[_'d6Vlq:n9vvq#F$FZU*2TQI,ʊ4H ؊6Ty*bt~_ymI쒔kF�=^ ÿ_;7r_WWv> |??xb;e饤b**&Bu-1M5'LkuB!;dy2S@4m!%2.2Et 8;7涔/˱*:kDMv%o'k OV/hG}i]GQ~,ks_?Cߘ}";OA'n{ךkRQumq փY׈n+JJ>B@S1&!ԁJPHbdv%yiAm<4kN!hPRT5j:n ycL]ybHwx"hǒѡn5:M�W.u:~ 0p>=⶧˒Pm:5]fKe5QcF�=^ ÿ_;7r_WWv> |?6)2sIX.Aw)hyDʗ! *pVgSy_SR^KʨjfhZ` F Ʒ-v<G)![Eb^(S{JaB5>gQMBT+B$%BI#Qkj3Jm ԀN!`s[.;n(@ Yl2lW*Na@UApƻ|:WC]?>갌Ư] U'<=ZڲorPqZShQ JI,@<-;2yU_MiN!dClT|Cc<DJ4oӰuaOD )Ƙ*<ʊyOQ,)RPhy&vNZR HZ%@*RT :뻔^y]ݑS6u.ٽ(s䵣C :pاh*$]3ִpT$)ysԉsF-N;m٘nZł=! <Yl}v^2!^q2`Jf[+ qkQ:绫"۔[㛢Vw|6$S&&Uur +VwK޷nk{TVwK޶lUox1 -Ψ?$ÄWj1m?||zb0M|aݺ_N�&Zu|չ8\7C.E\6_ZVu|!_Fw17̷:rP)/L)@mG\zW3ϸqMB7%inK gX)hĖPI$8JP PAD|lʨ9QIfsH ]k<v;z }5�Hri2 |vGi#y*$HV`-|gO6Ml/GQx@%<\<z�V{ M<vm^Mj< =%nJQMU>J:$W?!洶9l^ d-$j |IVBIJx`5 Wi^ 1<L?=,i$4<6/!.kTXRE%@ D[|eJL6&]*Q\q#fYMGhv1U EwS_ҿ4 <,̐WzFVGCjJHסʴeH?=R  廿L\;17 Io,mńn$WD!)A j5ܷ7wȶo|K ǖb%m8oVd!IRB(T j�{.+Wu�W�oZ˯_c=o<<K#ؘ\VaHiwІU%9U$,k #ܣϑ=':y&[پo$wܐXqk'6uS_ h_ؽRQ,+MvL&ҡqNtS |g697$$kSq&բE5Ԅ8UU7bq|<bbXY"^bk:\tN:{B?;J߲:v}g{[궜$g#Y6ӄ\|ޯ'akg'ma:w"' ^7”ҒU[@]{im�}vޯ'hZh yFڎByMm'~mU@O}++*~N>-3xt`% 6$$8.dc%j(S@RH=P\SQ&ǐ[).) 8d ZRTEREjEzam P ȤG;bSn*(."8 Ò\*|db>$DXJe%K =k:w1N<)4<ǖߕ N q[ŒsV绫"ہ~xwx_^+J�vܣϑ='"C1#*Kf<f]Ԇe\Q(BT-ث_xޓ}+f(:(Q� Ka1\w,AJ![ږQRu$o7w<Vq'۹7WkI*e) ʴl͸VEyJ+˶liѕo7*JI*IM;-sݗ#XәRi.hO$RT Em+~5nw'6 oxŷ~bY'w!v>g\'5 oxŷ~b?A^1ge #X>h}OiՇۿ0EvNE\R/}VoXףuA,Ʌ(q)-2I5_x.4 4Df(RsX_q\\5KB T5B#Qkuwra[_q1 1.3d:nURl:o|nsu*J/;60H|x{2-劉wn䮯m?||zSwYؙtkqJa9�$n/ Y*X~u9y-GT+ll n_fgo[�[ua�}VX$P.k]:q,i̲ ÔRJsW/|e'VN+Y/58JA֭v!2XNU^ЏFzGQ~,ksW=Vlj>IlUoSP=>gs=H ZuFԞ25tcb(hvD�*N_eA#0׎VuFԎ25tc-QBIqҼ枮_3xtL"$iLC ͼSo4NZneʽ0Y7Vg™BMJjגzPjmf6ڻْtt ĔӍJel)c4a56 *OA:ЛO+@cIpa.� аBn͹RN[nw� ";7+zj1UjDvr%r<;]Tsݬ.DRI ըŚ U({2-劉wn䮯m?||zLS;vap@3!ȴZ< 5apFt(eS]Y˛XV ]߰\d[7 lo&b#UCoۍbq%4po5w!v%&$֍BX((IFKR<,:N:rB!F`@|HC+o15(y褝` f&9OTi/! (Rq'q=>gs=XB j~GczҮQ-c�_Ͳ"oնpHO?ޖq9xcd:skJGH^OY#eN@H&^L?=7'�N8j $T.뒵uwz <Rs۹ם.bSMlxނF[]!Mf].Lt*gbxDeXf4vRC, JĔ9 \x7F _jF29K~3ǃkI7'^}WOƗԝÑˮ#Ҳ2hߗ׉.w%~ݓBTm4PIk=B2k7Q\UT$[nqqP{o[}bH{D}R/W]>-=I[na6Lҟ-d}w#︧^ut�*q)JͮwBnwۋ:TL42:GV+n㎸Vֵa[Kw^:-چܫBs+}Aa_�ğ�d$! BsQ \Y*QI56JMRhGߒy~S5o8bur,f=#>}D8lǿ$=TS՚./TIy $/PXTujFEkqDqxS)QKxޏ]l ˷oM UEk#'�Sd Ҥ g^־*�걋ME&}K2-j?}޵ PQ# Ũ\qE|yuS|aD6($, yȬJpvWߧߏ4uRvSssYM%,!y բt4궍"s׌(eQ  ?aJ) E3 f9� m%,We8j3`~!HaL?Qʞy:}c{'f_sgSJŒK&GН)ǶcME(+o� M ZM'�Q5ПEުDtZU1Q$ jMMuz鶞+>ΈBR|^PSUe94"8km N%E+2u+sjR6:sg2+Ezץ՗`%4^jkГѡ,Uzomt!6l\^m\@}<h4JuNtkXg@bx$S7ۨXlMWj'ߋޙ"W.uZ;<>;:iyNC-i PQב<]<~uMke4#;!kTSQ "QVS�П<օykQJmJQNn)켵䲙mH+aD쒭ӯ^KY$^z,KYU#Pi.Et>C,)FE25qiZy@Uk:V3Ny9OG%qPr-ŕq@kD2T*rl&dGG?=-:R'^Uqj歚iN(i{z- 947ѳ޼ZUu\~g�g?i�ܰi&:י*M-gIJVT#<~lŘj<|uk3%S5hy<#@Ab juj+=:JC;g9,,ԅ'8Tu$j> Q֤//xZykuG; :Ҋuk==6ה>NiNzxvm#fɗnFZ@g#y�5OefJ\qYm'dõ -a`WM~vX d ibv.E|a]d %hVfհųڬtʶ(o;AJ)_ ABEh @ ]";&/~^G[-,(,63v)N_Dv\b⮭e5�*=vNLW`VylCͯ>?,SZnO~5Gi+RԔj**?GVΑeUJO4+h� Qȕ6Jz4!!Ž>MZ@g#y�5OeԋPz}6[kGd=,D!$kGέIկ �xGg~d_WM"  biYNEslMF']XӨ%r<}iJS &}ᆵk7ߣ Kǡ>CýU6#90LN^mȭ+2K5T*Q*i[1ۛ:GڣwQgj*͸ZXX_}]5Qk>g[jʳW}\K#vۓ�*ֳR@NWNͧ0>C|ܚ8�MwL~M=*V)"=#Et6s{iK1ͼtճ8·D}:K�&ЩW.COޕ:ޯ^ q6#7�Ju$ٓE $hevVs=�9M�I}YyB!SXiȸ*ҾM9(I!6kY\WW~8h?E#5ПE�m&m:|a4XދхOOB>ŎfN[(q,iF.PQ~E{Y\W՗%N]x_}-SJwߣoZTMRh<To/Q"<D׷P{fv'ҫMɫ}Ch{],ExYRUo~" P@kۨzz{ZomOc*3v; .̺ZUiPE9G<[+-Jв#ⴾŎ X@ e5.}GOH62%{SϷX-MBhs_&�e>-jr&eohc_ uzv8-J~jvs_*ɭ5*DQ59# A;O@"*I==̣AB6V6(q eU)!CPYOT qxutVԊ&MIVY@(gԓPMA8}-BMJSEj"Ⳏ!L4xI:!eR#UD'6@}mT2%"}Vӌ%PPj5m˰VzYtJIPXͤU> z+8!:Ml\QmT~zRcbiQWd)Ig^sJQ>-)84G8]#O!)x-G>PJԱB}BӈK/$)C(u襒 19ʋK=GtVt%IôSV<$ViLrjq+л2A&dQPR+I 㦪Wt 'RF}@(kl4F^c]9@HS,UMMxXWd9H,J(C޼a!>�c6<$:"bq3mJS}.2),"ʪ*xqﭗP|'9pOmѾ4+RAJ,J-(ZO[8zk(?K4&Pz)dv$E-.:TQA6z{Rr+|FYΥ (xzz,X,-U:5UBң^hΥ Fy5slƥhβ$R^Olۍ) PyO)f )J'jh^@y^. ׆T6<\VVx+-(BhjjIڣyq) 'X<{=M6Yx T'/yymԶT,P6V`9Ma4GRk8(zFBR)Z5kNcƅ:@UDK>@S<|QKeA} <>jEl SONx万J�:O(rKQ ЍuW-9}CXk)T#BQtj&/%bXG"*Nm*5kt4SV9}eKeѝe+BHC x )[VmQ8&2C_Y Q]ߦoT^UHJ8zDo]xyJ],B#_(uS`FFƜ4m:�uZq eE(pE]4%l))K S28y}:짛KjmRӣτoL3sύy˛j.btH.Y2)i;=4Q w;wzsT m�$)kR�BTiUjHQ KF!GcZ^r'{uzP6έ I@`\xİ~27x8]KjuDey3�TQTŷ'nג^“w*S1Q ȣy8u \ JM5~͈qφ^Ro,PYY==Ǒlqk*a Ѳɘ%+!Lݐܒ6x:^ RBmJqj!(m qe(JB- I JT$j!Bv"5>zuʨe/6( qu-4K&Q6>mwb;n{@b<G }u”"@mӛ3Ԝm oDMq%8\q:iQPTPY'7 qM3޲'l8bm48zkji\,ՄTc_ۤ͋GpBL˙PP.J^zNv2z7 F縰d\oy@XjTh6g&: '}vk ]\l N+SJvcm9!dZq<#E͌nx ȄRZ4OƐ4dk}0HRJ\in2^1w 7ۼA%WE3$ FKug 6[ imp^xk^/\yܷS&,RRLu6pgS.dK-A+ɫɘZbAT8y؊%-O6-,f:jl Kb?n7K8bujcRN4[(6ݒ[s<�I^2˭ :\efx4["ћۜa=%[mOV KiRޗ:Rm)ReJ 'Az�|Wxh󃿪[W|˽'hx\hP%.r R$%qmN,hևZq(qRJ$nE6>n'2Qq86ɒ ҥʚ.!1t9f"Ls쪙vM6Ԕ6>ey -([]6h7G,"ׄvwz0#CmHCe*m-v7zqn X^}ഷvXV<vBV&$ VDp%/s {Лz* NJ`Nգi奰TU*GrJ1L7}b)w[㥰1ɥS l:BgH9Apc_ۤ͋GpBL˙PP.J^zNv2z7 F"+1k/7F˞RuT)үh?ˆqb\X㹢zu N5#OIIs^ 1-12w"?52P [nh۬u  1*4 A&\i1`oAKw2KK)KIVk\t0ZuW8kEw]f1_L6c6T^:&CI.? *Ȍ^>&Я0{ijKCd4!wid1%>LeV0 :ЀV ICLtb{%.nW 2КjrB#TT*7D wDsܘo|!y,C buO%@O nnxͦb,֟ 2n27EYKewυ[cm 3>l0`La qgRx' fbU<7JNJSPWe^\KdjۭĤ&qӉSn%.%IŬ'vc0,:3bl?? l-*@y>)jF°i+d! $A." XKM n<bZNx~lT9e}BwoۘSuoT¿i�>| xL\WPBnyO:2aQ� @SvM>M\]u3rA" sI)q6Cl/}KZ*RO";ۣI7L<Fn(y2I1AIsjE8_Z,Q1qe=wEvdYZfؒĉ\dk[.ssM}�Ed'!}>‚%%رqRZm#C*ԒT[)4j6n04].53%Yams8x!ٗSrq&+$+3js\%$#\]wʐ^h!oq3-Ύ{+` I~d 5IBiEJBT m(4% xۺ w n+"L PˌMdKHnK!Eb{qQ vA9$IP^ Za%M<PxJm0skQngxRTRbKM&)ׂt:[nGtiF gbT*nA4՝q&>u3db\IǺ[6c.%̛Է:!y y\y[quYyc)"蕂+9BٻeMݐ\Xγh_jhmym72 ʻUw Z:<7y~(8R)ʸeȍ;'!n oSb⺅JuϽymKXO 5ًɲbv3q0@}KqLXU2!p@cYQ�\GSKB u%Zbu<T8)jQ3/ =m~^ ]em78bB4(CT)tTTmf<r eo+iMdHC<H*C-B,% +Y.<:1fnԥ_75.<MB40ć%JgHE,6tؘxyۗ'{D˜hJ%Rfq]f!նqV n!nU x-uBÍIT8pRl%noq }pw\Wc-(ե!++"w;7仙ŨBr n;wV`Fj<($1Be֦R8)KEH I b_']ݔ|9rύ9!z͙k n_`-JUvE7(˖:YҸѢA66^E':uݲ7<*z]mȆY-6mה�T(\Mũ1)Uzt 8򍋒ŧ\C- m-v% J< |[\:ʶ+__. Y-upd~;yўgޕҕ44{4J6 ׼^Qd>Jj4h}n8!*#q[q\#vA- W5=Y n[XiS Yp-* )E#rI E{8bI+>2TsR}!m=\{� X ~{ CkWH}MMxC)HRېH@ YE]N]&xb +qmp$7u SDdžxy8]B3[s,{`]<Y{:qn、9/`hGYS(u XswNɹwT|BwlËEQxSTu4a)"!ĠDܧ ]w{b-nۊFS Rn#n>#4td8Gfu%Ö8ae+(~\#R_v2t%L8= &�j[8 N8'Ru]gH$! 3ܖyHzuJkm߇⹥ȳ)[iO^\f&+K ;) fbEaQ㶖@ʄS&ۦgo?a"IkCB1?b-jeLTZc|Nn2V7 fPn%Yyt ;%- :K %m3p[ KwOxtJVp#]Cj$EJhAq];Mn6RF�o3&VWȝ4Ĥ16�P k܎ƗV</Fo<4Ŕ)yrTwim5'cط7vf\73/"^hΙ)Ԇ˱ 3'FKip%{iYlØ.�gƕT@yRMVpv-ʋS�>4L%o:Can Te�$<+^?qy U&6vQRKL>JirpPe}#p7/9x*Sn擝ǠiK/%)JSm.*K![)a߄N �)bjڝjq@SD;_7ܦf} b}FC e3B�gX&Iu2+]%t7ew N׆<vͼ0~,KT&yb `!êiǔqݱMz6zr^ɼJ!$ey8)S̡yCI{ur\^n1mNe ip,8ϸG.Եn OERtoT8[o3͜.[}I,)N5lZw#\IqNS7<~*D?fzy]!)Ӛ(#)9HODeUqʛ Q <�>n庅xhjPoŻSKw)-g]kmbx+tl?KMr3xi?)1̥ A 4Pd H})a˦NoCFr�Xݑ堳&KZۊ rC^΍0bp&%-ͫoGۮVU=36ij#KKGCJT]mYmIQKhJQhIxZh-]HʗJ\JNԥt~/P&v>e?cݗrg@mЬדiJy ld:-2Hؕ9LBz:eJ 6mY+BVP\<eVmƜS+S.4eHyp'q A )P S {]i{ ^zIFq.:]Y C`g+mh$ WU|bXWsdIqm7# V�a-+ &i�ueA AJ+Pm @RUGgN)ZVfԴ%Jm_IA)W:hz�B DlBBBu%HBGBS@,Zh-]HʗJ\JNԥt}(qo%󝛩m2q/Sn2 M2+HCir_~-VCMV[m.+3 JJճ2�TUORl']|:0,Y̭ho2U {N_pswۭ=�;6Gw?{Mnpswۭ=�;6Gw?{Mnpswۭ=�;6Gw?{Mnpswۭ=�;6Gw?{Mnpswۭ=�;6Gw?{Mnpswۭ=�;6Gw?{Mnpswۭ=�;6Gw?{Mnpswۭ=�;6Gw?{Mnpswۭ=�;6Gw?{Mnpswۭ=�;6Gw?{Mnpswۭ=�;6Gw?{Mnpswۭ=�;6Gw?{Mnpswۭ=�;6Gw?{Mnpswۭ=�;6Gw?{Mnpswۭ=�;6Gw?{Mnpswۭ=�;6Gw?{Mnpswۭ=�;6Gw?{Mnpswۭ=�;6Gw?{Mnpswۭ=�;6Gw?{Mnpswۭ=�;6Gw?{Mnpswۭ=�;6Gw?{Mnpswۭ=�;6Gw?{Mnpswۭ=�;6Gw?{Mnpswۭ=�;6Gw?{Mnpswۭ=�;6Gw?{Mnpswۭ=�;6Gw?{Mnpswۭ=�;6Gw?{Mnpswۭ=�;6Gw?{Mnpswۭ=�;6Gw?{Mnpswۭ=�;6Gw?{Mnpswۭ=�;6Gw?{Mnpswۭ=�;6Gw?{M�0'`m/៬_U?X<o~y}V `[|3gm/៬_U?X<o~y}V `[|3gm/៬_U?X<o~y}V `[|3gm/៬_U?X<o~y}V `[|3gm/៬_U?X<o~y}V `Q J'`?[l4n)<[GA7oo5Aff~P8["CDY Gqڝ?d7t\1.n̔x(OhkqA#pXKr1<{RedLz yݸk\.k%fͩΧJQRqNG[C;PX#ks|~%e_O}"+E}a;:RZmP@Sh|ܟޗs]XѠ&hQWWY =W}6Tq,dqTVT,*:C?Yi(R~iO]>Uގ3ll +}<l{GcSZD}Bf9xT#͆AM8N*t |f(kl`o˳Q#id jv x?ݶG_gSZQR 5:%'9Q�kڒ'>�o}5�g(kl`^|[G.أvO?a'x! )ʹZhmjղa1tk.5"J2e6Ԕf3NHl0Jy奶HڥjJR97hbܛgH9$ )Vijg{ shJ*BXO w3$^Xu>sf(t5!u6x� b)~<}+-O,b@�:u'`:^V`#!Ro R.3IM5]IuzҝEjbkN'q�PMocW8闯Ns'ӫ`Rh�=ާVry(6}Hc‘O ZvaNB-;'D4T[YQZղPxOUhEBI4Ns{RA5鰘r`W)M8Q.8@$4Q|YkRhRu#UyE]G^ d-) I&rS;2''SonI'ޙ!\~CeӭJ[ʐ<&+ W${-sҔ8Iճ]cfUf⻤ޗ[+QWiqTJ6lkola)zE-[_Ʉ$SNT`SBkSn!AHZRk JAZfv} oC0".Z8((~{\Oj:fkN'q�P8?U vG#+)<k]{<BבSQU?8Ҿ+lEVW>=cԞRur4yȱGwU*c5Dl6˯6@ڎ^~gq[7"vy7QQ9wRyQݛ.Eün *9i*!(%<Rj6Jq mSJ:TwjʻGx/ʊﳊ=t9c0QؐBTB|Kg˦M*>0"R!P!*6� pf?m6fP61&Ѥ/M*~/Zߥt- Du�I<r ŷ0j֗ GZB*Tl(sN{OAv}zGhk.#i#N6!#Du2+P~?ٵO\qVmjPBJ@xmYUzï3~Tf_k@>+-_3fzGʼb)ԔDEMU"UJFIDZk䢼Z `[L~I F\XA^36sZ[)o+A9TS@qնG}bUV?|Sat NO쑻{}'wҥͼ ;H m-, aM!+k:d .Kjj^jsucF1Da29aΨJ@R@?wYٿٷYٿٷYٿٷYٿٴ30w 3C3Ȼi[h%#D$ŭ]9D ~}8U5j5v)K rJμ^NfuWPRMhQъ/KIHuRbŠ?,SF]7K(|δi_X*ءҥGn4X-yA 4i*ZֵP%)H$QJ^ה76�uW]ڔӗA9Qw.XLTS\_)TO9!jI[)N(sԉ.T ˃&D)M+3Rb<wP iHZOA KRj>1^ U86�|rShң͎ĸ7&,wP[O4BkI!IRH ¸Rג`] JhRܮ#5[Z68KVl-­ ƊwKyB\A̘K@* ~ksn,ה9Jx6ۉRېxU} *8K_6ݝF4RP|Y RR;9CoG6t)$ ܩ2-d\m>y!]I&"M(]Zl5CX)A#] g* ZC4Hy\)kaUP-֖͡q!m&RUy�GOmI:F-?C'L+3-igm\Oe6m}'YiZ9Fch@P#iR=Sat NO쑻{}'wRUa]�ɓ"TJ;%]iˠ_79�M>M!K=-)@#bK{ scU,ŭVVZO)>-p엜Q VdZsRy c ?; [W>dk] *8%)lŝ .W\Ėa6~rרa˚mIaQ#Ncd)+J\A̕)$l)P#oiꐦ /-]+Q2N>wȃ*[KmKqjB )H%J$�--4Q]<6#崪IFZ-l[!??r&ɗ.|woV3,)7RO=f; &_ŲT$ؗHRJ) BR-'*ФiRTRN(m`:z'&aTG]ޒOk)!җ�;GaOi eKYʐI m)kJ__�fzRU KlFv mY#wn7 [GQB9e'jMdPDkv,[yEFF\ZYO!n)7fy..7UǼZm,riP!CX+ a;|lt+2*JեتI<Ъ۵kZkN'qx"_H9pm-"CvK+y啭GlH{ ȿ]*XѣҪQAWv'a;2~/m䟗q\7?k^* $NQN6M*2ng[kf֤+) MRhhPA5TJwPO5䢹GO"Cfirz-4Ui\x'mEO"FtI!$TM~>>Qhfe(x+Q6y!gz�>�w$4m-\{@YBEwQ٦$~j'A]Вz`Q+ҟ#vO?y% R$IJI J2 S@Yíڻ M~^cbLy he4{I8w40K%mftHQiqW[8k�+ۭq5�w爰kDfJuk@ڂrTSVn$E8{ۡI+%x-0�IنU!XOdl~z(AF@Kl6z j<5Qu1އ hNe^i{yzfmD*G2zm{~I{<L')$іS,$s%Б-mq>L%r"0LΖa^�~ZK0_hP^nQ[u_ǫ\^쨥Phnfl+BOM$ܵ0eƠGSv;.JKnCȣE6x 7?qU)+)e-5\ ͝5ؠSv_p܋_ߺ7ms~S6 JPu<W8i?P:j p}c6P%ZB:EG@%,q)zƝ<Gmp޷"\�V˥y?D1XuegɳsՉQ O7@G˼T/)Ѡ(jW gMl.Q0Z;G =oBv(RJQB(h}!|-+^+v�⩛zV^A^1OߊӕR8g<koWym?y@ ZwmGxJd?d3n#N1BFOb)'U 'ש> rJs;a"{T *:~J ?e_KeC-~Q֣ȔD�TZgpGRG/�Z6L[%Ε|).-5xe]ymREv*t-vVK(•?-( @S 6_noܞ(>^-i}S6̉%U|R}ۮ+͎5!A+Apk%R"H/v^8҇ߎ -9؅9xm\ةH^@n C,9 BMUAnˌcL@xm$mCNM][z]R#ۘX[v_p܋_ߺ7ms~S6&ZoB|'!XFlK%_sfgV@(9p޷#�;Lb際hver+*Kh6 $Pa5QyIuhRih?)&ߐ6fB_ޑʞ%Դ~+&;(9k˴֞ -6*=ih P`̟ #GT>>ʿ{YZMzCǬln!\@GK5|jzmH*RR#^ ThDu(IJ�AhZDkYԥ(AͫZ|(ݽO A5nY[Wb.}]%Sb6PkN4!ᶀ9!hCSn%+BP(U*J5lwzm*qvj*(FulHemE.4ۈPڕ`)'\~2 :G}F@Z5MP{;J~>J䢡C%u8QDb%#^ud%m$@-1Bf!x'05&#O*+P 6KC0X* F A-oƯx &B[<#BT<]\q~-]qV %Ğ N8MxZIZ d% m%F<uÆFN_ (mpSt;)mHx]w-\p$)T6�Qm̱ݘ)SPa'[$p V^ko%tKjm5C* Jث \mT[k*�u/dJzD5%5Y඄KQʐIպV5{b),FXVnpEMr}ķ v$wq ûd&UN#BV%GXgtlKи1ExєH �|$,RV BRT5%BP<Ef%b<Yd1z4IXi-K %(MIʭII?^xSE7eּ)iʜ;ZdO8P(jSv_p܋_ߺ7ms~S62Sͧ3շGع˱$qΙriYӾ+kLP{. P쁥h���u/YR%*ʅc7<Mk4X7;Ɂ"y) !C@JLnqoz}뫦C#VO/Jwi$RI>2OqYr^Zr�AjiSNPRis$I$Ԟ䨃#e/�l?2/̿�(Ɇە'_OLހ=9fNI:@24yE%@I5 m7Q13l~P'"=_N*DmTuP.4S(y?"B2q;|N=QP_+(E8|=p,5|k\ ;eҹECƃ<8Yl696W^r칲 yTSI:&#t1"sq|洝ECQDTT4u9MIKEAB1m&喦LE8ůZukװcɑ?yCVxhi3Q-+ig)( Z5DFUR"%HR!uJS<a*u0f[{o+-𡴱>A쎽cfpmK8"JT*#(Ѩ+Yf]$g͈e) Ba@$A /`p#:ݲP%ݯ.+pE˴դ|n◞J-￶MN`5[qV$gb펑a\JLV m25wFzd鎥cN:4JPk47ܹ0Tۘ {<)15w241ZǾPnMټ"1og/:c?;42@$ HDPG Ơ̇̄C*qWcC+Qڣe5:΄4 ݛ8/%o^7SP̗[eMc:#6ۊC9#Ǐ EeљHmB[i B@Zv_p܋_ߺ7ms~S6ZRTD[nYJ[i+Q,9BCd8kOdEfu.-[9j(ZO1/"̠wPfR moxIPTt(q+/J@<KZԢTJ'iRM==rz,$TQ$~SI=(]c3)e '~0;=诿1^W̧}AdWX诤&#netU,Gb�oH5kmV{b }_0RhLI&A*𬤤^ߝVD㣑Θ�N0[`WSb(uM++a& IWE>G͋cJ<jHT-݉~Rc0?+@ʝyM&2- xI̡/%£Le/4%$HPRH"ז%J])#hbUR̗ UI糘Ohnӌ<iYyb frlPw+YmshP7 ~A;OO=�A @ uwCޖm};䤂A=$WFu_.n7nI[-IE7/4J9`[I.iMGӤy)[a߂zq M$S*48Cs\uNFIɪDt!L`fPS JR$IP#ZH#l+Uv<GWfBfBFR8[sj8jM%--^kTy4x m1MwEhݷ9++)2#n.m۳/OwBI�dMe XM�8遇Ė̅’ڪBsgSO(Ha-- \Vә*qZ)Jr{J̤fH*- 8O2+I|`tY_4:y RI6pvWfLz|5KiJ )J@JR5�9[UYu[WSidp)O- 9J=*c@6j�]ۏ8AqJZMwXƄMɢB,�ҡv 7?=."[ ZԤ֠Wzĭުl+U)@$Z¾P%UmЯ4ڳ6[ٲ6H`ӛ:ӷLФ1b Ih4>?fJ*;vl+S UwK%Ob+@JTE$65m1n$R/)PQ1|*%kJGR'zDz Q$ Nd)>iRhi ED7S%M)DY̤SضKm‘ŤB8ֽwew4*^ws <H.{uEDXlJuj;\ugՙj$PP[t\^(q&ƻٯ2$(WUe(EJ$DDI$v?<,l^zǣ5lVτSE 9I2~a4;"y_ k#]c3.EYyAHV!@CQݲq3+$όڞٝƑ9}hx^ ~RD'-/a)+~]1f 8Vmi.fB)CWm_IN,%ui}h/SݼY*%C[bIJvJ%D}#jOKn+BdzĘnOIWLOz)'ʘW$ JSؤ'�t$eVka/,_U 2M=Zm=9Ƿ�j�o�  �!1"AQ#25Baq$36@Rbrv 0`tu%4CPScpsDTd&'EUVe�.��?�?;%^ Xgtmj{J8G9_ $ww|UIEfzK{Gk(nQ-kz޴ fWޛ�z;n)6.c_H-c&c@dVwLFfIeiZp4ruMYe>f]gvݻIog olݰۈQΫ254�c1ש`ÕjtXhi5t` y'Lk}+Ugnȳcv6٬B("5u DKNtk13bd"Nr81%01dq&gV?kc~`~8|0ΛЃYS|2ncn[RfԲ` &0`BaBPQ>*Klٲׯ]�Msㅩ)PD"H`F&gYΊǽd֤C&ֵZkXDLLG?2?<3 �SۖSY~`uKYE* EZFcs^'sZ_u3ɡe\WX67V{Hb` #zfݓG�jK^Դ mkvI8vm.GU M(η.}./۬WȬ" FWobHc31?{<_au3߽`Zv&g�K1ׇnj1VXP*8!)l1q}uBg,o�x2gs῱zܗ˿Ŝ]~,dxg�i&7HTF(|qѶ\яńB(pڣ%gL"`Q=BQ11^*>ݓOѫjv5p&?֬ڢuУ[;$6GɞO�hUfg\=+1b{!kԵ+JVQb\{63WtiZL>˃lNd(+ 4Ӵ+G(/62OLLxqrg#W"wXZK=K]V1G0pj{D\{#ZuiyEɏDh>n,s5!lP$dVB+EthɼM &ɧc2؟-q;_u?+NHm.U#ɼ,xXDLhc X(wj j*Yf�M8hQ=ye??SU),=! MsZ԰3a IΑ[%\`}ȃW!]E0<"b:QʨdOY͟A�W>.|ۜ{MR=vq^&AoM؅k 8~9[e^R:[fn]N rرdO hzlZAq&StGwk.˘UQRQtUijT&@B3}yԥr^ʛMy:O4GXc$q%$6샷 r ~e @A9 zns%XlcLgЊ,}={ݧtj<t畐'h=Ÿ/FY> &ͬmnC`c$6c)#bDf. LLLLN11iY\bY0 Iըv& %r& ƛ/ړ)E&Svi(2Xgb:k 6nZe079{$U+mSг5&L3 �!&;&<&'*UMX�}S>Rtm. n1=]#AHctі2IE*:<Ue,K#O\?{{B�<J i=i>Kq_�Ï�m_crWUTj+]ae; #Y?sGC}Geu z1?v%xKkxҙR]iYzqOTy̓C_D\#,+YurtQ_-})PG-K a-^fgfb6grk q b@w#n f`F"BL <M39s$ZS3=ȿ/vBWqsFr%1Xs^PC<;0 r[1MS2bĉdL $HfbbcirXjjs65d@Ű1H `&'^>9O~ȬX�!̦B5G`zZzמ~ij3�8%EqZ%5mh}5Srs1b?wN�SMeq \/jɖ!KοL֎w emMhǶMURω*,!b4-xŌ/p\ʖ1`ުF@W@3i@.@U�JKd-pͷ ?ӈV-6}KeĄoi쒛up+xZKXhSȞ[9r>\S�nq^-JcCu#%< şXTlC"fAj !e PqΧZ`h##܍uk}6wkQn{ @HH*bjHVavX_}y-45 .]S UERDoZT*-,L0I6s>u ,!>pr%,b3Ps.5b(!Q]lE6*euq�#JxFK`LG %iZH]"B<d|]EYtlⲩj ~6Hi5 (]N ,b Ŕye9{jI1$ U9@=Lb$G�wŌPv%+(.ֈЊ&#lw[I�Ǽ?ړ�yn'�'1+ٮv7:HkF&umgsG4|*�T,}®\cpk. mD}MQDE4'ֱah5TOR }y;VQc.vTߎv:+އV}>,o�x2gs῱zܗ˿Ŝ]_ W Kΰ6 eiÝ_NG)i$8X.ѡk@LDƚGc�}`�{I =V^`!^ u63r \j3D:Ʊ>nKXVrrW8?/M֭X.KG';庽+BSbXϰ$MJ]u߬t:�\}�+HSd0^Z9,mUnkj:]wl$R>2G61u81cii1u3lX?|0NAwv~&QK�^G'aA#^Hƥ QF=3�%k,$ PLLaHHGbtKkձ9; XZ3,=EkI"SׯP1i0켅md1�;{c~�)x� g{=eC&֘a&fP 1LDq/M*VG| �y17el\xT %0 Gz{KQm|k-ѬIhC>3Ci<Yb7"] {:O1na2zq|1>,o�x2gs῱zܗ˿Ŝ]^, #,g,z^K e#<}ig3K�7c9_l@l۱`B|D\d ݨi:q__�r_ ֳY_}$9tl+$5l6V;`cS>s?4q3�i�M/#E~νV5n;73_ɬ~Xu~jkHuY[/Tk莣X�_8/Ӻ=kl&&}% bl-W2UX-$n,cLB;͛GƓ11>1><, ,KtWaI<ձy= ڕۧo};+p?~?B0s:~YVX:ćm=ckE ?Ml]*fRklGfϋL�ǏL�ǏL�LJ&fAink+յe3ԏOM]H}J򝾙S'娉|SLQ?$cVL|qE8?,L|>&N3>v"#fqv_/ a+N.�ݍbƣ'BzW` xلbŌ5>'dFHKm8fV>dG^ܭn? {Cʺ2h_iLKU~%~,a#saX/[6[ 'e_$d6|Нg/سnR~=J}1c<|8NYZW29[T01(Lz1x~k&56DF560V1 $QcN[!^ihkhI-"őAO-dNm6g@ 4lgM^Ɯg Ӵ]%Ktoݣs�P0"8uk 4خ!h1NQH1l ّA{O=.ۤ >U\ÁӨÌ�9oIܖT lYAJ"b}<&Mʧ0~֋ AϵOV!jeYfOI3 /�oDe^ٷcb@|}R>U>ۦq03xz,i3{O�hUfg\=2eQXu]A lRUbѹ�܁ak^+dzXi_*@_/L|O./u_<*ǜ^\1g!\ױ55Ǐł�0darV]2L6uվB6UmY ^-7e{v31vj}}k4Y)W{)Zm}yu.�7,M1ޙ^}W|u]J0p$1ɶν!Dn-H"&J&|fNW%o,L{zFQ�uj-ȁ!mt6ƞs xF[5ۻa;,ԲWdglǶ! MTby( j@Ɉ�Uٻ985V}eLJh.:~KP* v[7ؽ5*LMs﫩RH*5 Fuz6%pŌ/q9/ne5 j7,HڑFPسm(pW~JĦ yT_ PE{3G]u5lCS^N*ZJ‹9϶MY�xQ:eFlhjn_Zb!>qڻh"[qjaA$asL_ %ܘ+VyFִD DՑ^RE<r1c {-dtH&c]'BO+Ӹm[)l̳cnOC;"V3 ߵ kZqQ0ΔIc'ʣ �/vEi#E*_Jckmic}$e2E?ϭ۱;5YCivX[Wߤw<qh\]& CEU24` y'-驻:5bU~\uj\8YxS&3Brд`cZ0�DGlͦف9gh@Ǡ@D4bܢjX`|bQ{v+<IND w G#'c~nM̕ugk1( q dଡ଼Ff,5-խ9.`~\kMU&TP?laMzƼRWMv6gtK! 0F 82㦩R-4ő6>K_3 k'qɆZDjg2E:DDF>LTUqG>ŵQMIF ۧUkZV][\ӍAkpV9;hF'_+^ 2cDlaFdZL̑L3>,^;= uTӦa-֮RW~-faxLw+wiVRzqna Hn/GcS>t;W,d=ȃ Xwd#H O0W.8ܯ%( lpΡ1B|}u b\)2[Tk`Lj$33 sˬ;s˶ ~}WE7O@r &}�xܛg#`S}6JMAV`D@Y\{9d(E{V>a=MgH�㱬zݭPZEseD LQ&1'8Rɯ{ JSa k€ZIs�1$E1<M\G*P%5U| ƢR[ J;v1>11@NSUꏹhdSiv>猾9M I&IUm5 &H D"ND�dF;[GH:26Ҫ;`iD8S1Otp78Nv0JˑVuz}V1"](X0#5W'c1uoWE̖mbe5TJJgx6F$U*YdËh@Y>Pws8c\Xj:Z:]zPw.cS�ع_+[7VՋHfuu5sQm)Oxl~xc$ylPiZV,z!-dYG{-|=%Γ$J0g?WX3ԉZ JZΨm)S 6ۮw:)6%v-}U? Ic"/F>Qrnk8Ayݻþ{|g ]uD�TJ]V.41cƺr+6U1m")2bl9g#ܟld&ëW]5<'r]%cx/_QkP;]�븧cc*C {bl ?3uV1lbDRdsa3@϶Gw^ uYyFm)Jty]5>|Y!+2sgdxN+;\Tk).;#ת$:Ƅ S5A3}Pg"Td=d7/OӘ։> z~w;'u{qQp4dyֽ4 K Aщ0gdyce+ F?}po#m*虅$LƱik<e5ձxyEt.WzQnٗg�yZ{1_ոO>S,v|ܭvk`3,|ҰR&OeEZv<UN@N*zRL$AOMMR"}˿2F=''lIoS,ضk=5̭S,]B _Vp+4bi}y:+u1YgN5 9d%њm?czosiCk#(˲EmOk'YY]VbTأAEJ̰[EqX".Z,2_ѵ\-g`: hHc?gfBw.-dlfMnW ,MEXR sE}U̮yr41o,Jկ,-) av"w\)a!um%t2/*+jXԻ Sa)mOVS#0T*]:9]L/deu΢�ΊeZȮMӨI=)f1Cl^q5PͬA&UdDU"FF #NGg'ot,,{F+.XJc!UKQw01 FhNbl<}THsY4Ņr -^R$'JԢmB;}NvsxZ1?ih\o G?S!fscWbutYk azN:gpR)b~R}%V-:KO 6&GVUu63".K䞚C  ;SWWĀ  ߽=&.5P%^;3˂tDM Mm!h[ Qd,)LמcZd1֮ʼnWﭕXF➕#I61^c]0k5qvD=*)#azLdjVەyaq}` J2'pKOltP05 s@c#QV:=jM$nU <T6Uln{# Qt{DV֩OLk{ժw. ;BR(M'AڵD3vi(FG!JBֺ;)pA5L-@5Vev.PH_XL۪E2%)z:ꃙ7HbrY3Ղ$鱴XJgEKֹ*u:\yz2VnpJ匘wZʁ o1TVc#uDBdX2"4Z_d+خڻ?#k]Oc]=DbJolxFEW+n;RWP0X@О=OC.k?3c2э%=Y&jgO ;SH!<c5/+a[v6kDH!f:UbFe+]g`Ky{ǞY+:y HKY"U0"é&Cg~Uʢct?]CZ‰L5DJ'zX3NT®O2d0XdXg:n)]Bs!jTygas2osR۫ݹr ^ >r s(^5шs$FMI єjH+5 "셚<eE'57VE)z|+|c7d5lEU1i# Ǟs> JnLvSm\sB]] 7N]]3V) cJ>a@�E1k9xx[Ji@羹K4 8זM X<Fb_mkXUAD*5أ$$'eNa.] �^Dɪl{+N>Jwx>WXeyvq; t}YTVRΗd*bdd2#ɞN~�1"U<+OinjRuz=ݿv>nq*^T%N"}+L@F;G;5g¹W HMeϔd3Ub�݅_X, ]cXw v� Nׅb9י3c$lw\X:(:Η52B <,(fRFW2 /)m{�RC}r1*w{|ۥwmE˶xF4v̦EѸjTl2gږFL̀3z2_o[2d#:4Ahkݯl�\\hVLm|nC{T]ĶH9l6 >�fD]bkXJ1-uSPX-l(r9N_5:LP.S7C'5=_> 5WEv_УV4飶e2.RGa>Բ5fl� }Jٖ P6'qѢ F X{` jê{jl`VR΋4 \ܳ& ,> dM%TJZa \J̜Ĉ=|;o_{K [)iYk7)>93}^IX򄨞Yl[%v$eٻlq5S C VRI9wZ隹sά2A]NħFck!c1N2+]sQ8Uȫ ٮ b:N^4z]Y2\bDw-xV=^7m:{?qCtl%tQd1FOUՉk ,zKcZnd~d||r!˻spf⢷fh1E\d%+5$uKY-cJeҪXnsBe5gM#^=8U{UԑeLCF >%Dpαw|qR@ڽe+.<X-?91ǩVxluȭt&l)ULg025lC[h`UL#y v=ӰHM]vحd@b* h =W.rWrvj:l6jv/S \y1"�ƞ_֩Wnϔj_ =H{1&|v G_G={=M+s{sq^ m`ⱝV[޶{f'J[fffx8d6З Q�&f`Ҳ/F;[7]Sx>,4nґ>i?~5~|�ysSv�Qi.`*U>Hv [&ZN3Cdɶ%\Iօq%vI5:YJOYO]fw"̲_rQ# aU10ۦvțTKi/^S$ 019ү+(z҈IvItx{V鯶s/}3p%E|BD ytk3eb;~}g}b߀_m}Z}γ]�%}fUfdTxUW,*[<L]">fEH-.#bQo dvگIbPCx|"??㊘0}&EYS M`Ϙ&;?kZda6TQmPSكd }f}Uvy_"=>AuȁGg�S0g_.>k| dq| 5Xk,(Il6վ}+-{YbLk%7㕹{ۖl),x{%1 ė\b!az"v4$с1;9vS1>>ߏ_d0ߍ4v}&m޹%fbc"|zdPS9p.fu5"Ejz8/to! L:^WkP?=OC.kkl&s9W%B[m/E2ڱK.$16RJy)=u<FdZ&+"V�G{7-PJ"xM Rrz\r㬁O^H:D7Lm(4-׈f璭1Hgbcq;RR<t!?�*"6"#U{ FSOtjQETs˂OxqS?Ա�">H:c'nݔV;H=I1isO,[LZ;FۆkaN2#fg)PFb2ڋ^`8aecvoR?<fQv*l,%K-J (#f]*PӢTs]|etn-T*|5575m N2p5cy\}b4J̷K*ftZH�X@rlN _ڇ23\loHD]^3-XVM7c-;WSf64PG\,.>_vZ ^DWx%-7q|=,.OCʫgM@mwzIR҃!zxy˴-SeUGc!ߡhʪ=>rY`%r2\5bzH10?+x sy!eU^6;=$MiA[Z<<)Ȳ*caڍдeUo/+NwS�G�ÏU!.^|.z;3xu9vB`"M&' AN1;aKbΤ5jV<&gm8{7(eXEdD7G7-H#v5:qڊmK풓Ykp&ܳ�`Ug/y*#;VMAV9h ,γV`(j׌(wҴ4ô(;;{f;&s}PJخd UdWBH+Ε&=X*E+6Fe-zczX=;+jcmi�Aٜ݁P5„ׂJ\ڰuqܫL kf#uy! vf6A+lJ?!eG#8w! QJ'XDYz,kݤFW:BI8~vUd.ՈZ`"@Eb"#vq8kh%RkHT)]xZgEyӡݶgJ\w5 (, $SŬ nDz=!cڭW|m.ezOYlmK=^_LzTaVt˜9vk\Wf,]sT{"ܳb'Cǩ9L$+[Oe`k bFZtwk'rGl_[\Mf%J^@L"WbZ qڽC"M,IVB`5lXh&uriQ Lƺ }n-#þf}33.eo%XPj]*kk2p{^6k SAJҒѪ1kϩ; 0rOd_qTlZ?jXՍzA%ȼC!ᚲS췕*\huewh{|uiBC#Wf5MVy H -_S <hcd:i&[]8$'UX U έQ?{&F[a1.6t dJ^'9g.³^btX[ cٯ\N,ZŐu`{dI%3#,^ґt8u'[q!Ubj7֒fղD=kBsYqCz~^3߾(�n#M?nZ ,M,"[�(]guh<l-ZIP:rcs~J'sm*FTmR(hu XjB-Q�S] +5̶a*b-Ms#k VΰRS'vᆭ9췭uk)n]/d=U4մ]^{4Z߹pAz_ǀLTU)CqVľ? _̜UʞC?i Z]^Tħsd\4:ֱh*Ǫ%lښ8{LG]Y>Pz}8\ )%SFvX׀s#�Sn^ɤE}Qj3�b mߺڭiosfCHV+X\IJ+e<<ȩ-ho`@eU56o(p ܆b)<C'RsE$Q$I%-``U9Une0ō{6tؚf w\T,&1xE=UW s6(8sPS9OX҆W(Ej_WQza%5U )6VDGAuXXg}d9S-k9cEXj] @z=ƶ9c+iG';Op2[B:Se #0 >,Z6Yv%Y&ބP6ۆc>.co^iᩞdHWHBR6Br*TusڿC^ 97V LWM{!y?*d:^`m_/AS+&wޫ=ϐͼl+لګK`4CES[Yd0';Ulmb2tN2Ԋ< ^I@Mu) G3wFf29ɒa,=i:Y0 䇭nDIǐMclb<Y"W"jFڈs"=:Lİ-͸ ;漶cccV6+XzhЛuh-&#:Y<Gsev7]dD4�FܿSSxbp1eX,L�0nroiBкGg׏TK!ٵ4qu{-*+6}9~tQZn>z3b{[Ve2ZG+8jB64zW]񴻵?f8]y 받J Nk-<'rɗ圡VBЦ5 uD4PM+Q6`]eI U{.jj]&5[}z`m=Fʕ%V]#V/+<{ݕtNj&OG 8 \9/>7-OC4ϩ5Z?ɂ>m xs 0%"ӰU!Qbm׃`<qϯN.b3T ɖj Ikzj{ecڮC@Nkm V;j-ekWE2)Tܟ6rȲkٜu\$*`VԽ&"=; %ar^jYqu5Y5-UV"X`EeQ]v"gb-]yt`eLRL*Z׮/.PuXgT-vqW3Yu[qo&b�-íBܹ춯;bԤrugd, b r𲵵*�` ")Łj<f]g9H_V %\vҬB} !=[9  i'{{=0ƄNOLyn\"g; .E#XZ܍%[6Nꅐ._WeJ]do'#l•YPjq͔y1E2SFDzZұ`"7C5w/s=%ȚD(a,&䛶b9 98XصMcMy8-Se@0A94&`<vzeSկN<Rʹ;V3rta[q.]$rZ-weSm5tWVR[%sH)!8J C' ڹkvwP+WR:ןQv&ɫvix*ԬNe1ւ[rؾ`--qƶ* {$r m"#;+Ӱ㎴6MU{-jȆHD ɣ~u3ujJN1.udAq`@gHMBs 0%"ӰU!Qbm׃`<W̧(g$*VIoo-?C̍-jnL#'F^9^hr. >YWfuKNPIkl(+!eYf?^K+ʴQ?iڻ$^^ݻxW'G�C엾mo}`MOL/#gC@TE5ͶUG/t&&n!yr=B;^ R LQhӣj7ﱡAOꉃ;6S;MU]ڌ@ZfOgoNXg`ͤ]`3"k:2sUĞ2<gВܱnr%f k井Ɉ.lGW-zj&iV$+ =$:+r+0VY"Ǖ0$S13OX)=Q-t:dk[=v"ۮǯ\mI^iRn+�@06�yZǛ_t6a'cDݕhh4j+I 2-O%B6$`I#~D"1%1HHDLLwLLy' z YQT0A3)MU$V*Ҳ<6F܅R%ɉN/S/MkꒁYLlґVĸt11tZP{kܚb8 U#Olsw۹B"X#H-[JՆ|J]+v٠)ZhWL\C3 ;|q4^- b) biw{t pvE]+ мܶÔ gt&"bCE쓥 f](2(z�(I@Υk /c.PINՂ�!1VYB3::z!c"H 5VgH+o3fJ#�[ { xqwrZ {N;Ub|ZG'`R7܄IL@"Fq ԽLgx>߳wlohnt]WMѰ5M'(DD ȘLPb1iOmȺk뵅hL8JQ3vD$c:G+]kPE 0f.<p{Y4YZ9M5LFݧ${f A`d0G3òunbc,˘Ñ%.;i`|ًؙx׭i,H誑K!#q$#]5mԃ*\KSrMkft=QpM./E:)TմX`7˱E+k1 ߴ{(v 7uJ ׻]38Z;׎u3OM,C,MJo ։QLO 81 1f&8b[g(ev]cDԭAà*7H[ƪ-9 h1;k,\= 3FY#';x{)eXflRh"&/ f^`x g 0S@B<2)=<(^Pb=G)t2/+eD:wu뇒C2&LN,Y&EX)cVnkkI3ҮFtd ^]:eo)La�c |JxOW:Ev vcMmf!6\:Ě,9[qG&&<!LffFbk|Ү̾ؐ6|:@ڭNu!͒9̾> hQ<5߈ˌ*v%hgi`5qu ^|]ʆG9n27fnY{.n z#}y<'$eĜz: G٤tt Diʶ]~)k *2"|<GTpuBՍ5#1LRߤX`3ikoatM[Z,g+!Gqw� @ Z1:0bt8-{JuNA oe@ {xcuDXZˍT% Y�DvyqUYw/o$8!!s2�;τȮ J{vfiř5 3 (T#:F0 vYf[{tg l1_=/vZ1#0k*iY*_kLf";Χk#Q`2^%Xdž8c\G-dTTI% ?(z 1Xta`fk,Zq$ .hyx-K4@j*ݢ!?\fk%rW? �t63rV3YCWdN &+rqs<x<黴dݺ Jm6@1(B_^:5Yl{۝=cYtƚi*xZ"ma؆Ijzm vDPGؿq +CJZ#%kɏ,Ȟ<s6�Z/ynKTa+@򧸀J<9tΦ) !%CbDX:j2 K$+!&86XiH ΰj b? u))KeB:).|fB/l{f3:ϭܮC.~[V!7'2"2v}Gi%{Ҙ0ֹʺ7D툕^KW`l6_ty{u߬뮼Sp loPyv /Y*~ք5#(Ig_DiR�-q_ rYO~H'ӌa1\LZZH{CjkgVn|_oݭ*=)+2Q&;c:2g~f#"grX<f Ef%LtIzDi0l\4i5̌mx<}\uIΑ^%||.\ `P.PCeA0[ S}ɵD&t6 Z,TV;Vi)+0;ӝ@}-vw&�&5hӒzd25G`;Yesu43d}cpWH3k:1%Rc(ەln׸ar۪shmL<Gr=aG.vK ä�97\/pW윶ŖaxRf_<d5UY.&͋ZW!bΓ l{IcgJ`#;cjHŹ9$@Gał3$ޠ)=Ie!Ӫ ^LmӴ8QMsXQ^>7.Y{n9K w{+Ujqv>8DkqTW ъelaZ2W M ut6Aj="OdL"w dx]⚪}IgC @m*Ag><M Jl)gTdY%dymݤƺLk,mx6KvZ`عi5k9Y7DQhA ;3: {FM=9X1J6)3{<k=E<3C(;xe;q U5gn צ7ύ�o�ܶh'UMii:OtYl%!&"j�4e* >Ț-ڵUG]&`KnJ5T�XpZG<OO YDZ,+וo xz(`~wqD,[joU1يg ڛKt-#ɞ9?J"Q!%OŠZU<؋|[z[щ9E1MYKEe~/ѳ#q1${cږOxZWkY{a{“yƳC1?LR)Ć3g#\jlݽ׶d\ݘJH4|!rgӎ Ӳ'N!Ҙ=e6 `-C3ciw{&b@`,Ek$k�-#R"))%cۦeJ3sV"Md9"Ռ|yFFoD0QDLz O}mcc&<cHHJff~Y$7ɝdu> }h 3rS"? G70e%?> h133=|QѼDk�ܯ0Zk�Zf~/�qyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy?�~w�@ߣ�~�?�~w�@ߣ;VYbvҡaX?_'et^>�}"�x+E�WG�E쮏]/?_/et^>�}"�x+E�WG�E쮏]/?_/et^>�}"�x+E�WG�E쮏]/?_/et^>�}"�x+E�WG�E쮏]/?_/et^>�}"�x+E�_*Z-낮 0%<"|J?iߣ3S$30BQ\>ي^Վ1P2h%%݇]n=mnBYm?LTXCҧ-)�~*sEUk} c<u>y6ymyyca�Wwgw'iuP?2<"gG/Ula e*i%]7J?10Џ[zgce#�F3mS[2ЏLS%;Zo9SլecJaRbZR+`״[%5,V"~QGq ~'?{Sj'nӪwW~�Yg3z.:ċPc&LblDn~G#j7Oz4YMNtϏ ;~g7U<nCΗqEڮ)$񄷉ȣ)v1b0% 4E ˑ,w#nctT1&D2zn醻c샘.982蕀2|`ZndIXQHN]C©tJ fGB9u7s0i} )DiS13\_ &jŮ,Y]iK=6(gp2:s|5fjTKQ-s`Z;� IN"1?d8ϡqA_Ìd8ϡpew!g(HQ]f{E1pө]C4cz@<n.X];ZJ:2˴x}3ȺKz(Z>yL59(| Dbfg[|шQDN_YǜxUY*#t�lj u;j?'˴#Vz< �\ 6^<u\g%iPCbfzw 3]ҵoP| �Џ_C~w5WzbcFT ?NRcX%5($%,t]z EjJT:Ƃ XDDi1'VUgm*,͆Ȝ!Qm21 ]e/e3hK8`ϢxK=W֫ńFGM>NaCѝ <PcfiވqL\VZW&ը'R(V"dD{=gp+dŪu6L2@bwt'I̤LȧR")Ԉ{fgY*YR'XVНb{FcBQ"CLLyzɯf6!/[~.5-5}wN3L¶4XM6K|�a=N#ŊvR} %\)O6TY0D ǩ+-;7w#4gI!{yӊ=7Z[|71bEŬqjng!ZM#3_MX&VVZXҝ<c"")""fg3=?r]e�,ß�fx}ÙU^'٦|z| q_ARhǘc�({82FT1ghIN|0DDHg 'b}^{Н&,k*}e G휇Wx䟴G\6ߨŬ5X.G<[\+asC lC=c]!o8LY-<N1Uk`ǘ~2YyRVC7v񂊀ZO[ FU[DɖV�9[RcSSIa/X}4Y&L|#X<qʖvfjb?l֝4dqv-L�izSMa`}kT<eL1,kq$]S%3|0tqC__o?vw}rF̯)b?Zso2zfcTĴ<.8rR>`)q q |Ì=;��:tDz|-fBs?뽾S𙉞?3װ`31[Α3Įw, Rs*kςJ+d<|\{㻿Wx^[eʮMsKDcx>an؆Axَcm*]Ng~٫e^c a=|+8JyOF=%3FWCq5[bcE^X^fDbDt(4 FBR;7X*\{t⥐gK`'mY\`�i 5 Mgb�871^F{CNLj mXrHX z FHJ˔'Jmմ{A4CEdDJNKMa6,[{qͯ%XgGe_�@r�3윱�j #9?@Ʋ-z~DqV@](7rC:k띋NUt*711Z1Fg0#d8_?7d8_?7d8_?7d8_?7^ke_֜E?$zǍff<=|☘Ўb&"fVg3&~ԿIT~�Gǯ�6N?XBٓgu:;'Yp7NV؏<#3(Ԋ{"<#=%Gt#?�>HȌĊff~Y <fbcQjR=?/`$J;b|&=bx"kV :[pO9B88dR7WNk"= T]PÙu30CgLl>eI#og#C+�B<auO[̴7f}Ol:F~ylo>L Er\~{h!)bcb|p!) )DAC'zG-G=̪v :ƢC?~?fc }>qV$#>nnS . $HfDcI鉉b� ?P`T$M#s"Qw)t ~v3�)3uf.?ӎ_Hlz+ƳgGT1;m,*?=[l-z\$O0ģI]2LK'oq~]"GU9NXͭ rbx^gfo"�yՏ%Ë7c;j#?C"|<Փ{,=8ɍaΤfsOfxO2Q;;umUM/ 5)xO)0?w peai21H8`ԚOUrģn\LADq /_%cYzOcѯ r mb< ☝xrb;Ɠ?sG(4_ԣXHގrgb^�t;?Lugi=N9Ŭi'__6^ҁR@e>!�.OgRNU|13ݟY]8^?yLi}i tϘGJtԦ8ܓZ7iQ�ueĀ4_IY �)4/Ohf CPƺp?6�2_ xogTJ�8#[bI9Ozc#2Ni⫔EwBq6cg\w~Papӟ"pb8{K%v3rgz{|f Æfan:Q17.7tm+):wǶydzݔhɏ,O[Ho+u0Ԕ�A[ 38Wc6~N}DJ]nf$ c^С(^d堵VjگR_i¬jo삅I E#vdii0?Hu+cmd;ذ/ͬ|33q 0EO H)Իm/'�?YZS:h1k">o&;9s]֦=#V�>� ?PW��yld݇]l>E/zO '8͝xd�v"m^60QmrdE3Bc|?j M_q�i.,c1"վl1bjd4}=a.&z�JMgY@v^Á<S@(tSqg v[GS3g~1YXPHGKM?odj[I@οa~Rⳣ4'<]·rUR45C[`!*d9+j_}:ƾkĵI+5/Do? MC+�B<auOxΑjѹB1(8Ʀ媸{r c"g{NJt""";4)3 "ݽkՉWc?e�CG#Hg.p5Tq=ʮi��DG3~qQT='Y[ 8-\e+?;~budu,GwWՐ~�&lE,<0J{QE];lwȽSv>uR/:?~m`zcL 62b FavSW)>PxwhbC!DS"1OtDDwDpbהf #F66',85s)kXMbfcu̶5![ [1s=ѺYK<w$? ҉bi>3x<mxG(ysĻ`d!r#|ؙ̔mH갥�Ra*e',~?fD7ۡ=DyEӉ>3TD}֞rWg>L:~[YQq#B"3GpǤ{<fxoa~ ?79Bjאuy'ӅckF:'+:-y*sCG_eX$>3c#}a%?&Տȟ1Ol>&>H8YP|Lq͞~?'̫P =l!.cH~"OǬzp"!e#Wx0 W?R OuZmi{Mc1h=6Q33H=<⮡�`w4EXtXoZcPM/aOP9Ȑ|Hi1?,qH&ljH#w֟I&gYS$t5| M %sk"P*t D8Lcm-LTy/$Æ+ ǡer)M}x~[:ӊ&~y%%l/ZʔEݙVXy r1jk/gQOm(7,^LL̈\q²V:,!y/^t"3=xҝuOj?}o<`s{+"SZӵL#~%11:LO'c8kH>)�ַ񹬿N)W"j]$ \yg-"##i戎rC(VŮgtr#HׂZУIDgO0Q?LLqb@:4,)(`Ԉf)!?vw}Oty:k_dMK:KO�9>!ݠĉHLΒ3Vt\1�ihzw�f\8 Qq=y2"E[1R#?Uq =U__.Gݯu"W@cN"ӵꋧw#*<{1P> 1*9gC#0Q?w hV7Q3ȁ#n,'MG_Y\h6o9ek(N'>M%(HB12E:DwqHUkG,C \Hf[p#K�5�<y78L�'3? =>Jб?< t]V|B�djOU=U!`df'bcb}ºsZf|$W]:͂dGk'Irz{ty:LC+�B<auOe!&[GI[> O}gI~~.Ylݡ֬~ӽS@"`tݤi1Dz"##֫@WeI3SNaAk2C k:1E\j8Ʊ ~wB2^LklyGP%3 'Akڣ%giRhac9SDx{u|Vs;Ɠ}J:e~Qdpv۳v;baMi^_h)3]dg`@c2 YāHLOþ 5QmʅZՕhœ #0f&G]_KZDHR71AIiżJ-ߺ} {^}#A@ � @b"=bn:{J6)Z}C!43d}'~a�}d?0�_I>`Q fr$%cIO|όخ\ppV�Z!^?+x^se޻>% a3C,2^>$mT؛Q?' k߷n͗m۸D&{}WR+X X(q؉-os`c@|Ϗ'OmuƸ̕}))gU7e*X'}>=d񂮨q<KY}l4㙞ZYbÊjPəcǁD1{OY#2?GA 8:@+{]!4Ǔ#<.>˪YTI|]#>q-Dbc])lAԏ[+U"i$gM| <z\6ULWhVg WPOIU3ι}G^ l BQǚc"3^RQ~>4w}_.X+͎:C&e:k3<{g}i-2^#w0cyKt# g=Ũ1111<sj2h6DŽE1M-<RoKk�+ h i,{JMiI|E2S?�X|m�1d?#P�$jh@bQi113 mU:"Ju쭟A๟v|(.f^J NI0 <C11ڱ-5b:}WjS<I4@q%h|}tʡYG6ͬaI {䈋Y)3{XR=/;=+p' ch[[\6Ϊ+8\epVucBsuQ>]g� ϋvo:XuuӧJ1,z.V)im�y 9̴}qpH}#?E,ץ*�U63=E#*;GbV ~9dK,URz[V}uʥ%>r{ Wѽ �Z/g]�hQƞÒcXI~94V_M+R? N6*@cF[oo6cf|/1[LLpmr)'PpOf V�: eH v.^LgN<&W>]<MO#DwDDwDDy=}Z=7Dy/_q~tx|'+m Y1EƝi;�וV>{О$�7>0@Q"BQOLOtx'52EQEH܎! 1v2@l~r~."4C^ܰś]?S8^ȿeQ[UNs߻?+`=e(6?'9Z!Oݣu؃7qC(9a]Qaށ~RYVk<v5.08'=<[*{-& ZKx+cAaՔ\.0ٌB`}N' {>Z0��="1DDxD~>U1yIyGEzȧM 133>"q0-4s*,]xa>"Oe#�WU}K1=U &&.OpO5cW#=uy�MT&ܩ ~@\ٱ�#)f~HՈK2#:קef~jK,A9TEZ2{PL|yS%<hv +ļ>c=lEs*|uRC}>G*1"Zk*vA\\6]VOjK?Fgl(chN'>f~RRF>2&c+V_2REnd~BDOYhF*.VO'c?'y?*vJFNd1kdS? `bc-W\& utW;O IeN\C?'b{;&8Ai:v|8(nYOwn2Jذ/2#yfg :O'h΢x 1DDGDxD|j~5eC%T"54M3$4aK^+J͌/�)jrEk?1y!.N,'MB?#b#;j_QR_.}ڇQT+Z�Gx`{Y:dOq�D wG,pkdk,Ox3,rFnn1UmK2J?翉x|f<M?!l/Jx:g)Xl~Y!U^�Z9t1 ;Ɵ?ay1~wK+ xWV'I0%?|dL @ģBІc1=by-xK`6B[F7 �UޜYfN.G-/ ?d[r_s_ӟ}8c11ˈe*?(~wO!k1f')1Zȫi=$,d8t$kǹ 7XQJ} q oW2sg]�؏8[1wbSlx}Tmg'?;�SXpdņ*h\Ȥ֬<uk<}�d}J²muRm*V)5b6ϧ1IVn>Muet=+[K`""&YTiײD 5f%a�f'c+*g65rS+rNVR3"c#wtK'�*vPw[ <33숻cjٴIkÃ3څ6,C P #S8ob/rs4ְ$aLfHdgYxV7m^֝5u,$' x>cO�AܬX2ISQ̷3^޸L ˘#~!r]sF%ҌUwՌuݦ]hin\D% V٬3c-QiK& j)StL OT1\Q_V,I(\YALF:kzjsToHyӁRD6` %c&bg75וg|7Zo~kvS;}&#g[RA6Av _nzײ�'NJHrV]eNdUꦫt({Pz93!/ºkeqm p{Pi)Յ[sR΀crX[xߐ(ad;Ew.P']:uA Sa*!YRժ"}ш8gH53e-p8fUf=WİH&Lٕ)sمficc3P_RK~kJX%L0Qcֵl:@¶7 N!]1ک!:|"nrRMS+ar+3דUSl{\_BW:EPr+({+˔y**=nNAdwFA�X̃F帿@]}{6Įq>5l͋L$˥Jd1Ijrn-WAd5>-v-li֤:Qd:UV-Qiƣ==j~ڟ5�mcx̵dK jWr/QNl@A+n2,+'Zr2Uh>i>~/~1d�b7Si>kmY dԆ3]se'&KWo L);T?RyӚkrAagmX;eD;7d{ttc%: n[ZņP*( 7͛%qNK}Q8lqW9O-b§m6L0U6 }E?㯇�y r,D�p">(=8b92Wr7li;j4hLbU, ('P҉DϱwT7', 5r6UewX�)U>Rx<qLxꭒ[rLE zo!41bf2:Nl=K+UW>qsg?�˜r0iZ+NB;&ރh&VkVXf/1^K *er A,HN44CG\ '\Y02zݤk=z8A d0BC=%1<s~FD!DL||rLwF>HP#E^"^IJ#4&GI2*/Ub^xgFN*(Xzǵ<#Уf;ȗhTvr/"OHz_fg"#f}�M9ck t?VOԵb`dY 5LqSP} Lwa#@mKcbiKՠ-]憵4YUΪF)m,uhE]b;�{ک]ACnWTe ϪhEƫAK<#R~"o))ףV9f2UBA jZFվCT:4kO4KF)xضܭsl6*jiR:UJ!R;dt9`lsu˓ *k5iRIOA@l!Rg^Y;Pΰ$#K��@`�r^%>f[!RPt8"wAV[V+4RFO6r_UW?1U'4DYBߣHww6;%C SPm(-l5dHKnP6mpw'9HSpf2 i Ɍ1>Wbv+i=#`k^{v-'Qmb/QŲ_n14۱DLHF֘^;Ql6>Ved֣) @3OwY7`YZ+Ub�ʛSX+ZbaǑtSs-CRqj%qUܲbƶE &vM@}\"MXe:r~^$WZ!N*T(a3\!O@ 8IOl?-u#-/'O9a2! bhR;5Z, Esf�+1\$�؆s&X1eh! `eyڻ_]�fl$d\ӈХӾ#PqW^g}ֳ/pTv2+8 HO 1G+g+[ſe8E[�l~٤{HSe`W3M%ĥ=2~x`2m(g0둉r+ҹߵCGY/ Ub% e6ǎXGF50)M [1e.{D �`B^Ul K'G'IYD uQ5șn[ qr7=b #[lB*؍б䎤vu)V`Ɏ2,(g0Gk$Sٵ Lbg{!n<Ȅ}KiFMgZ1]JdQf[&<xs7OZ3N*Wwf5%aQҬcVX&$ �=�rh([HLdm_0r8܈sK)Ql{!}Dz~=NkJTY%=lhJIN:Oy{yn[;{l*ᰵ`OjYH$PE^uaem2^Bkr\�d(!̹C%V%\ufjo�Γȕ gW~J f�=X)~)۹!˟kf峰n{0@$T~AO ͯ7Wj"6ʇN8(It2Q(yMpr_a6qgl)CBw!/ܩo'yA^yrO;韣&BoF$hNV5Vrvkn 3G gN8nm~oMbU{QT=qBKђ@kRh5ְfmHEk61eV&|*mf( I.? Ig 6-aBW.6BNrpfjy,+mi/* ;hu_"^[DVnl#9/T^ܽwf*^ɬW ' k4W6Ifpr0<MNЙpLR 5+ƒwT?;}]f0ۦFaI2M c�Z]f~=O}MrHp\p\f fc[ٝuuƝަv�3+F1c ɶ'ҹrno]"OqW`ѥȠanEDcW%w"Ʈ]8Z -r$|O7c'㙱TYnP΄ j_wܬJkV*`(!S�lg3^fTX˶R`vH7ˈ@%gX.dѶ%jڐg^Hu"b{p,fSO֛EbXBꠦ sz$ !'eŒ.Wg|)RtK)6goTVlzڲEh{JC=>Qϒ130u(PRV?Y3|&W/:@ɳ49Zntt\Xh1 !NRs'eu. +qZ#*8םy,zc9�ֺnB3XlUe8v䫣ٌÍmgBo`MI6-Z;w?{B3F'`ne)2Aᴫm#IZ%+`+qvsSVhfPMWsCd1%<7qgr'`Q.U٠Y 'ucs囹o+ޮO02J[k{;Wup5Q>"k�a*JnsNtC&) IL#ªQn5أLžqکbZ+;V~_NM^\ز\ _'6�v<@T %ʶѝSe;c|m㛮kRZ cI6V&~>6I{Q$f<�|x Yk,ʽ<rު:=͛l;tG[gJ*eLu묜lXbjFg%ۍ.YYԚeg{M`yjw1<s%+'Tͫsǵ $Ge]+((2R13?S98Ɔ2/3^oq=RԽ/nT?;}Ķ7-50uܶ ƣ0QƱ11=]]zݯnvanü6ƝbeN͙-g7)=fDwDk:ټ:8뽪:ϭ{2F2|XAE]NE{w-Vbl,Ohu,`u@G- XbZ;F?RJfP*ϻT񌥘7+a8+UY}$ZJmmL.�X>^I]i,:"|"zYBXl;d<缙33±j5N`E:ΦXFßtSy)=/9MHXdc<< JGc- rzS$0S2p<ŏC%˪ٍI}v)4 6h=@-9{LC]N@Ry21x6m9B!D@h1'KRPQu$!BW�<&*RUWX% PF( @#F"8ͫ,yHV̑Hyt]+L:}]C,CԷ%KjZ5gMlYĉLwLq'KRPQu$!BW�<3,B%JG]bQ 0u(X QE*iVP 8lXi MLbd>g[=ۛ]>Bzk 9y]Xu;WvvMU;)A'c!P=Ldqu|m!)>"uc& es$a"#.{cRX|~) ]۶<}E>=Nruvħڶ]T/ `)/�sɕ7jkoM\MLgcyՊ4nͽ'"wӚ*9@PIZXh�Qʖ3%bf7XP6[l T`>2y75GKtSu|H-Wnjs@I Bܔe gٽ_+d_W+Eg$4M*=O9dS.m/\F Es}i6 Q sy3Kyk--YucӍs,�r/"H8H鿌|wФª%ҽLyڑ/qi%zf\ʼnί_]G>Ұ_\x3֞Fj_^f},\q<z2f?qع_-l5LٜAYHCԢX7EXm@>K9}fik+Oəo锴 HhKWy,Ď �bkgk6JA&FuM`Ln 9'KeSExU f`OUϬ =;  `T"+vZ6[6zk{1ېu f͑3nLr\eU+ysNWij^52TRQ+#jH_' V]uy~1FYV '2вΑ؇,xJj2?U]&']*7]DFyԋ^m2xibBN@VѢvHTOcC4XDVfQ=|<dlr&6c  $^i>-r܏`C ]nF;WG ȗχڡߣ@_USEٙ|,< BJ0KOȁ;Z䛔YN22A}E'�jD!u\eתddi*kZ"j�&㔿{?Kx rm`%&u"}'VF6Nͫ(bv?wY/\NTZȬ;.vE{tQ9\vHFGc ue(ɭ)&|v{(T9J+3 z)@U!+ATud|f=nT3J&IދU 4VA -UZDd8鶃 j^1\i)!و2<rJ*UI]ݪBkǪO&cUoYةQTŁIE%?r{u]XTzs(V+-n(dO|mT3yyK$ܳrՄqeYrvvƋNDXcD9rw/2oO;Uv.٣~5<c�OxOw7g_bv3}ZOtb|z93KxNr5=O?9߉j~ysw1I {JXh5~Ѐ.uk?Ig^RjM kl"[R"9ݕ7gO0"? g UڹebZ+MƪS\b_Ѷ*Y2Ůr>c NF TFOS2*gf'#7/Av"AX {#>aᶭMZ kh! XƤƹ+X x yzT^[Z)JYꜭSH!۬2wS;?P)2F1a0A7|vV% ~2S bd 2UUv9+}7uK~3%hwkZR1)3!({֩w \JXAəMsk) q97HNE^ଝuntĊH<链fuf+N7Etm&RįWYy3'թ4X`K;'RT4-"y(C֮l^cŪbj~zrbOjM>rl Wط>j%9:QEbN&KV~'-#c C]Z-AwL ! Lm:j҄iR2lks� �32ӊ&oۗ*oftc%'8-&'ec+^ջk:MNOu}rҏF;!V'Y%]Ly2ZA|xeRVزա b5cZBFQ1|?0arF~N'*Cz�g'#.U ܡszZLlO-AVB`t!)Fe#mgZsNt؉YQHܵNf1 \Rc-.c0B.y5Ӯ'e#gdyVRjsO'hбmm\mv!.| !MJZ]m*`Yݑ~azVʣP‹VYj#2Ơ]nTϴr\/ f^ZQaf�iA\z$uY] 31dT>žV%<)5l2"k+k@̙.ޚL [TR5{GgX!<dydmfr4ƇNkUk ]E5S ,:f v~rc"_!G[ئH}, ~̕xdLou٣f8X9Yo+-֕`֔S q OF++(e[!mo=ҊJ'Rl음kKC 6._=' &J$pDGj#)6uݻ5sxk1K& -9 ީbK 9 4fkwӻy؋: Yhi<62'8 tטlo%vNĸrMYƠŰdL <C3x >cfE%J&!&5p kMרrX?S b`q)*W5}+`<$y[y+dҐЩZA:ˏ<a2Pv:uc"+ *d~]Bt8cy,d=v61E^[_ 읧CMkT?;}9GjRp!0 $@t` ǟ>Y3W2ݏK;X).MvLT�sPͰkA[5[+a¹$WY iHc\ueMk 4Xy@٫Ql0& x -:S_d/n&ױ[ӣMH`εnZֲ}I6ƚ-S"g DH!*\Ǔ-]gϸWEv-O`J)"\V2K3"ݩt$=T˘۴<L^3=,Z- gF¨8[41$z]|ɪljEuhUF@pԠՌ2UdP5DW5VV+})0eQrGm&92ݰu TtRf�gtNþ)䅊e�\Rm3$-dMyҨDƱ11q~id_ B�ajN'83R;p+ڀ =C3Wg97Xǚ-TBnն" 9Tcn“"<Ō^~4!Zb mNJ8%5諙1uLm\Xl=Hx<k1[>gַ4wL5,-n'OY*̄d=any^4a"3akG2S iXB~ҡo܁dzVX*N]u)zb˩:rs9f28^} KC'Sr[KvCJ+#V zǨo $0N-w+c%\}AVjOWZ"(I>^شIqi<ש>bUb+#@Ix\g&m:k�LbFwf f$gtG|q=cy,-km4j,ٶG$kaZEy2vl&*Z,9RvJ9tEUƳ:wfwm ^�lӻn)IEzH]uo/td+L-L'hooܳs'ar?Tp|/q9kXxfv˶HIWby|yvIyqݖz02&Av]Q;w6eLY Vb҂\VrMGzIL>R�/էa}Rqآŭ0N)f*wuKwR(T4r/X<Sw\#eU fL <58%WDTT]"JItJe{ePt6??-p9E5)puZOrϹj |=&j͏Rr=DLr@2JJ}":MQeV Dʟ?cWћjrY%e3{K܆uŚ4~*O-'U{UʀH㦱3qsomor1Բ1vքB㬍tSj@+^|> {b!_c63fhlu`v!B Yhj=f1Rrgr8s.Wi⎞я/sP„u>M>ѽԢ8UNZe,eSeZ)B*QN�(ºpC$aEV1([jD$f'pW|r�ֿ-(Ss7WZ~<bY!-LiԐADs~;Cl;<&%:#"bVYUF3 ӌds-*ܓ!aeuWD{l[>N& v΅|tDo[nMvߧO>>9)}c*k\wD+lz>L[E}#n0l_Z̓zyg)wXIE:7Xz5H+vWg)XZ<&*SN떆nXj MH�m[:}m5ۯ~<tϿ-el].ܱu1([, 3!PjSn"IN3~wTXKY0= ~ Lj?ZLX~ �5/ѓ�9  a}Lqס\+%^D�6yR1IQˆz) F K5=�(&=�h"11"?x~Fa䗍F5+8gVʙ՝&vC-ӈk5W)X`PXiA2l,F1uT%~ZUo%Jjښ%Rꀆ::ůuӛRhޫTyrhСs ҫS%ƪ|%3^AuͻO77rl5[d.Y[靘Z$ i�}R7ɗu|c[ۘsQtdАg0[*ZǴ+Lveme'2B V)-ev qꉝIAg1QhFۢ=l$)ۈ:C�) ~Կ/Գ<̗Ϯja>x4G8UO2WU[ITکrU(oSY,Zg[=M9*]GG)9Jf 0[M*=e:o^jZ3>Oh]|۴p<w-U B*eيJ@h6<3ŨrݞKհx7]Q$:&]4.d{SW+]*�0s ^Mm4Ӵ29s][}0PVOy8Y\mjTjn2%:%XĶX>U{/xvovCsہhCtq3m*rQeD<VݪVxS@<޸�3a'}?jo�[<OSW_-hȆ5ykN˽8+6Yc*MM% hʦY1`LOp|\R{9^XjԍtPa[67VZc^>Þх5W\A!dCM(=U+b?(Pb̘1eq0D-|YBzGoWG+ݯH ԞiRk]0 GtDDy8?O5u�P2.@v̈o^7f쬻px~Mk:m`q8jrj/d"]^0Lf\a:8� lq[9Af''tո;k3rPy`s~ eT:R2!T 4[9F)mGQPB֝� mVVrM_AnC#XS+1h!QޣR'V[r[|Idv39\ lI"a5^-cY#͙�)>jK&lGCk~ڡ-& 0(ЄuТ{;ljQLꈥ^zo>e{H[wNK'FF4U]6x%kۨ|i<Y :}hkCS6(lC׃阉1W0؛xˍ:HSjJz )Zzk@4cqkFPFmB^6";'GSy3W_zogFԽn-p87eBP9{@:m , X&mXFKz * Y\QR.rZQg'!5{<tc1Qo^ޥZ~=jkUzzNLpttjdiN[j^񝺌ƓŜm~Zs)&fd53mR =1]x>i1#s 8o[TE6dAY1aSIMb Ob-Bn!�iD L1fLrBB^vv 6./-҉NBkFa,rլuXXڃ31ZTF.�t"tӿVږcJpkp:Nb8MZUjԴW]`!*%*\ ԥ@-`0 11k~�?�~w�@ߣ�~�?�~w�@�.����!1AQaq P`0@񐠀�6��?!� TQRN8l'L9<5y`B~ Sٶ5 ?*D589�i|y8ڠqykz-a"q@HZبϡcaa|{q E57(+BXI �h*B94 _}E9Sg�' <(tD,JTO<{g>>yi:FQsSs+aH8jS\Pt/o;F!:!Zp4l0Iz @,0>83MG�l3-4O+7>>VĚU ՘zz<$% l;Ap9A�B#dTփepZ98hdF3qklI׫X^$2"`l\Fp새X!]ű cqb ,OVohr /~/C7&48Safd7)ҙ Ճxw@ wfMe;; : U ʹeTyD9AGOhGbPsN4"K : cp~):}Y@)"@r4Jي"${a s|c}j1m8A&@iu]3|э7�uo W <).^r?r-/?Tۑ xp"𙺥z24}by)J#B,�ՌL"f�}-k˖e\6|cM�;gj�@!9ftT0C`23__1Oov7k�Xixf̄ #|hyHXX9bdUEt`Q,IUl0LX#P}`VRMgp/-}yb='me%X¬qhqOcu6 F<O4ѲC?=dU ?C6lhE�?֩ 8lTuƦը9G-n�hZEѽ ?1Y�Z�۲iQ^kz:�3.*vN&BA6epSǞzzY:"  -3l _y<Tg�-jDҠ]\:|(hbҤS $a_Vi 2;CVVo|zK//Y@Bhi4@F *q#A:?]!Sec~L( #\�`5#~~F3T];` 5tLQ^=t>LqKT<?AFg,ӓ jcV� Fp EO6h@�Gb:GcW_shx"![r ^s:>>mڏ?m g$:-zX|#^7Ce5߻p+!;Q@^vz�̠�vϩH-РT.hDM#LGANLu4zuS M ¡8?'n k@_‰{P2\};g'}5 F7X F-lXj+V+-m:9Ws45ZD C#S>>o +wԩ5Z붽uUj)5Yy1�W`8K̓4}BlЄ0`0G-h99 vbi Xg1/@B٪dL87vq_0/쏫h˸6Ex$٠IG&&?dk02b=iN9*`VV0& $OEB�1'iQ ֑݊,?F~b\Gdx{R  lEo!2K�TyZs=MųN+0Rz0‡_%c}Vh�c.QKIWSL6 ` QK`+J$RLEC8Ћ [)"Frt-j}4U}>ą^&w쐅q'!-q ɏE %)UOCJ瀄QAW*u�(ms]+S@_䏖èY븶ߴ?l^%Rw<ā3'o5>dq0�vo|H6FSyW|V!-v?RW؄( B{!bHP[&˞J!DRեe bgF=6aěaW+Ouv"t k7 qěaW+LL1+n r13WW, ϶QnymYDnSj`鳑U^rL9h_h)�#s/Y҈L}B0-zL<liG $ӤP^LZϠ2S!>w9J%�TaWi刨{<:'b>7-apa.S[,Cu=Ռ^ 7,;o65#+tHuC3!mqAX⠞ddgzg4IaEtG"rH{ `•{Qđ^j+FifŽ !f d w`^R @<7F�D5a!OE$13`2NR E"tZH>Okja#p+Nrarz�e VƃrH|@GTz@ S,24%_?mybtІdAlփ'g<s8,yUڎv-:fJaSDŽwWJ$ę0-4@!}`i]GuΎy2" q[30T]G{)~gU++4]NJ``3AmpO Y`) C8i,GllWҊA`@ 3|ve2uЮ:'d(K % Ne{zCJAEke֥KJL{p7vPኊ\X|%"^"Q'x]]J m(q֥>Aaf`HGb$oMUTUW=Q%l< !" 1AҌVR(gؿLB۸ҥҗ'ѭW4YYQb Qv®m>a]9Z Mͺk5<}e rki:QwƌM"Iҿ*DmS�༴=fVF-JFf|">Er Kh](3,k_\V&Os1'Rw@.ʞwNrljX[x e_a!I<o$*QWbt$F4;y% e0ZPj!.'FB..4� 0(�5 ]-h4|"eaqb;�-Vz ^pHBPl;@Bã@�A[1d酫q~='jbHZpbMۜx�GeH2@ AWwҸ߀$M;B $ Te~L.65I- !PYDxۅF8gݍ| L Mg+x372tvn_KP"OX,|nɬeyA*pacƌmcxcNIh'WĮ8 {WʹO@~S'UR!?1EhKu$KHfz"s@vEq3]tPҿb w5$}֚b걎: =tX4DEnѾt׊6ӧ /ûv%MC@os&]m�a4 s'"j7\XIAV+)w%[sy'69`*ɘ?WD2 NxQ@NǝF):2< C݂H J(3k/ݝ7jX̫ Mm3a`pԄ!2)i?QJ ! qG<i_X4$vP;;dpE�%"\_ A~(I~(I~QUa昴yPM"Nɘ nB!,_wp`|`rU{y^g'4L2+ǁ  q2J2DeG2_[ Wq"3HS;Zlwb(Dӡ煓{HLf�})gݤK C4'KF}+ro9@&JGRWi3䣹2pI,d<-ƱCM A/!3S뢎 "h@(K:y.ę@k1kpaRG%R.v.C/?<.l$lyt 7 In-HDfib![C ' Jؔc"RԸ,mRVqk?$d#W{F-ޛcގ/%oDt["%@yc6Z$jJ?+}{T)5Kʳ3k%@ \d7連G oit:ld=]FyB-^ďC4H+lQ<,<*ֲ@vB1P|TjCsdj.%H?$- injئnd«Qݛqw]4f]vDHe\3{ F>sh Z2B h�{F6&=+UqbDA\!d<K%`#P] Z@RqwyP! !ZP`(n⋪(wlW^A0P9;]V�))%>bV"JPxh?.B "o0uw]Px=~B@0qeiͣqZiqU kX(|B3hU<ğj,GҵO4|>G)0d{6Ci^H">` !:> 6VBqn eI�6&%42EN+h(jXN<RS@Fe)mnT_1#F`"v}{ r&(~$YDU#800ި@Sjr|GdGk?lҵO4|>G ڞl hӯaQ+Yӽ=$B0ӥ (sp_{5[Й,Od Ԡ 3\+$22g  MaTnna' /Ҿ3y  ?h;~ _ 0POrԖ Y'naHeG#Xv ,m?:DI96&s9kS\fߠĜp0X#aIG9&,BZ%5Hg54PQ(&=0 VPԓ[s}ۗݜSW kdz+ЕҀPiZ!Xv ,DS_ fBWڳ[g#{sS ude<P)C4]+*^E7z! PIH=F˲D�RXtpu)dN()\)2;%Va=_A rFށD]`R6`Xd�;D&UEk̓߯rY-Am=@C`MLKo(W B(yJql</͚NGǡPEI-=yWA_ 4C�9^88888888888888888888888888888888㈈ 3u<%(*իVZjիVZjիVZjիVZjիU<dnl?- `ůoߵ5)@ =LCp_&/p%Csml|V>p*{B@9Z777/8l>76M2S Mk9ٱ/Dk2?0@Jafm'HCOk٢ ƩG5+&-=IS%J۫kGV9"l$ * �Exe%Cί@Ш"x� (MU <I5xU IXD&LÓw\�#SW+> Q%j/pqA>XD9-5"S͗GY nn.xZ_^X<Y�)-o@8FeC%~ջhn5' Vjـ+) hHµA4_l@oRfs GA-.M0ZP8] n ,. 10hBUKaT9^p\ McW"SpDpKUb +|*xOKTL*&-}!US`"-А]O,-H"a0XlNm@sB"4VruxD q| בG\=@I9.י*()r%]pWo~wwO&�78ceʇ?nn1@1L|{N>�·u8?Tc(�9U"mGhvxƦ0< OtKNש̿*myIoZxǙpcv�a4< J; ڮ;IH][cFGV9{ؿF[�aL_;(l懋x`ȃ!fpD=?URw֮ F:Uv%�|x^Rw]{FbJC-pKCn p uȜ_tvJ#+)Jc s'R k/ {�xYo('ߤ�tn~.L5AODgrAp]~ʓ8 pScw嘴�=UW�'#;0Sc_D9EVB5U$E-p`O Zr CKPXuz />a<ioѽ[7kj&_|6=U�0jǂScy.gb&<ݛy$j^y>2mcpU&ױ p]Õ P7&+_B۵ H]DnQt�+yw >v0r= vIkPʼ59_TD�ul5VȐQ"'"12E !N"d-rCwMGw;Z �hw#v)N0Nvz:Ul6\$~>#Aԋ;Wcʵ]E 57,,Ry� [,]4x\k2"89Yݒ(W?th,"xרМ`[ jejJ 5|uj 6jx|cT4q )v@rLtΰ\rEۻ}DG;X>l<!Œ|+I[&5eZhtG�׸A|<}���׏JݗFy#Kv8y4<qw=�`c,gݲpzjڰua]6 ,>aEwS(4ѳcV>ϓ,"E7>suRO0vkGVʾB#І|pCq䯒M5i�yPIҘ튉;^B�cVUdl|D�{c3B\S:>Ċt15H rƟ.G -9A<�*2njۜ-" M^-@/"~KקΪq xmv]yf{i}`=(‡-Щ$c $[Kϰd`4pS8:ϳAqLYObgDNu /?z~G)q+ýbI<:؞߫< 웫sCNmdx1F'"Q=ĸlJ) bYH>4aM0| 2m۹ DDDDO>(\/6l LUZ.ۦ|@'5j@d&NlΞV-{x/ bX� ՇR\E34<>@"�P%9Q1Vrl"N&4$:� yصUonn\h+ZpysS5 n+r>l8qQ& adV;rz-Wf bh~/>vbd{z7Z/g%' iy8۠F�kh]ZT �UWb> jt;<[�"O ^oC[m]>Z7U_e@^Ϩ/'Q' Χ@v 4 "\Pl lao `!-ovʬ| 8 �0<\aZp|Jv<Ho^9Y=|  he\{|zM}Vz袽SoC>l=<gC**I+ݝ0Nk6{@ȰQtM J(TU]Uڻ_^5kWAKd#YӏS#O,8y3hJ%uڶĦ*ay^3uh<(!�y/Xu7`}aRdpQ}-|��L9' YcETpOW%j-H4QLUkyr=9_OŽh7h/8UܹD8?iv0"H3Z8u|M�Th,5ڈvO}�NqBH�C�뀺#Ux�vꁷ !vgD5+ې4EӬ? ҪN+D;FF*T*jیYݗfGOԉQli$.Ry<rd5t=\{3w x2''9LЃY@B#wG4& Xlc5w":� ;;g_;:W<L E?`{q?5.TWrAr0kަt �v'dxJ&wGAG �� F!\̞b W\vsxr7ҏټ!�S J4ȉ;l;DS22,]�j6[[;i|^&X?8<%�Gԥ Nb0X; ͞pb2� CyQQ] ^4@[%q�'_\.�0G?%*xWѣ#nqqQ.ۡt4V!x|NP*8 J[ ͿG~9<iŷI}HA#T6G±@TUrc H&o :b3mB?ֆ$_W: Cʈf8+@5 ^ `)o }#BH9{LPDpp0k`A%Z e.!tZ<) `A ��ǩ3 ' և!U[N/5r<1\O(b_n4�PȬvׇsĀA, Cؔ"F߈>I }-׿8dI$� qjk]'x##=bG0O}h@ILvp.:;zg掛L2 M{;+WВ(�#L~{��� �pA�ͤ∑ʈ29&CYMCtl6ab%iX~nXϒw $n9"܇,>$*$$KRPI, ]|^Z@e^1Aކ‘YB .d+cL 1D(DBP C\m@$VlW>#oWPU j9# W sx|;U1ڥ!]SPH9 ۺz&M 5,&dHUV@XbH0pt!0N@~'ߓ?c{T}DsGkA98vyv?UA$|3M2 ZF�$: j+`=BjhO]3H0=nsbsq-]h_Nd2t%vQ $} ]2_YWGPXő1Fkzl!̅FSpxPU{&Du-yQٚa4t| \č|QJYs`^Ysn[Hf}.(d*`G`*C6t;B$d.fkqt.IU­)%5APfVp5\Gvy� w_@ ::jYSNq4e>Y0%  +Њ{M JLl"NXpd!6�:<h/E#rƵ Ɇ6e{<2n/[.>ylDg{G)Sg4)̵րq d=.N?�yFIQļ*&%�W,L*Z$GS Vv`:T;ayɬAPNr YLCXltWp;݃jZW|lc-? .+P6 ! v "M;PO1& c(lR l烶.Zֲ/ޒrò|�)$t FGHح>6 xeJp^"TEzvvf嚱ti/l%-i# 4He͐0eYXv޴ȉL� [Ibbe/Y?ῤ8v�R͂i&pSXZ VaCI{<G9K}ǚV�HظA�.tljWj̞ 4p,H.1m/g$l$3i7=QӬ4ۖ$*Bp-./ׄ�xL4-ևa|=�Ԙѳh%=<ī\kp%IyAΎY=Z c}ZoBk0U@g^msOsUܠ?BiۢA,O9I\'n#5n;av tL$FW<뮨]*xH&K|.YR7fSb6~')\"_W:UP3/64qN].8䇖y`nCE‰ ǂ3B(eA.\D[eiܑ9d D NK cH2 gе3zF* |~gjԻ=¼p~ /89sw>?~Ŕ@H$rNZKaI'\x-3l6;2X5#0θ3u}hc%*ǀgpm0fY<2lfG>T4 T|Q.osv=�BT@mV`v~4d+̞c&`a%a[ⴢ%Ud-{v n:ȡ#-S⼮C.MQA,HHeC'TgP3OP^ .G:@)#,Dec Thxl+4L@牴%Iment#MPS}W)-R"'X;\Β><uE5!R\2 4Ԍ~1 L  teMG�qe &y(QS< o[(?Cb:mQz/ O` ЙU�~Yk+DLk8u]Ξ@DuL7Z$9Aʪp"SRsG%%d%8 ׁMrid 1xqHԒhy(9QZ>P\> Eq ρLsXxMw &,0ʸ8; ^6ҽR$jXa2 Nya&.gߜ4�>&ng,�4bi2uVjwKWu_|t>Q{R`ty nՇ��Y# `9׮~-�~]pgB$P/ Cz=QďShܲ :l#j 3$c)smcnv=#?~ĕt�ggBUlyQ(IɷqY6=eσܓa!4{mM@ǬMBJ*>%<Gٹߘjt@(rSܢkOo3:bDc}X zbQm $#Dae(1*[4"J$ q 1y}m X zUA8zc /pDxy,%C-+o5."XT᪵'6- +>`q"PXYTlp PVZ@�BW8CNӇx��R,O_<A!5 h9ł+R>&q"1 Xr=cEpeMqlz7(> v  om}[L%z"R +l%H,1c!`b@b`iX٘XC>b$pőmPZ"X� f83Ei"&n'�CZ`�{RC@YqGg;)O@. g|a ^Jߦʼnr TTP7Z$��!l4<vS]5'Ϣw!AdUG첼{{*D{76Q;UWE3<!AN�~iPQT3BO:XcrP*RFdXwс���@4qXM[k 'H#϶ Le| E(@BӔ6v/~%u)T4T(܌SSb mU����h: /\Uaݏfvy>܆$weTk9,`q( CBŰA�Q�� ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������� �����������������������������(H������������������������������������;h0�������������������������������������_h0U �����������������������������Cn ˤ|+-V����������������������������RQ@0�QI�����������������������������.h1{ M�����������������������������5+h0>ـ| �a<%�����������������������������8|h19g7W�����������������������������t�q(q! ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������@���������?���� ��� ࣀX������������X�y䠆B@ m=Xy!������������Xʫ�h՟$x\6ObTc0�������������2itHAsVu\w-%H8/0(����������������������������������������������������������������������@���(���� ���������������������������������'4AA]����������������������������������"9Y@��������������������������������VTzK .a����������������������������������$Є�"B �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Tmmmmp���������������������������������0������������������������ @����������������������������������������������������������������Uj�������������ET��������������������� :s@-������������8��������������������(�Pc����������������ހH�(4�&���������� �P@b|c������������$^Do����������c@%c�������������J^RjXT#|���������%�(`imc�������������T!(T#��������� p`�c��������������C8 (@����������GeU������������@ р�T02����������CZ"[A�[���������1@�^做T�������������������n����������|=;$BA���������� ������ l��������������������� ����������������T����������������������� `���������������:`�������������������������������������!70��������������������������������������������*!����������������������������������������������������������������������������������������������������������������������A �@�������������������������������������������I$�$ @��������������������������������������I$����������������A���$�������������������@�I @��������������� @ �@��������������������������������������� �����������������@�����$����������������A�� ����������������@ ���������������������������������������� � �@������������� $@�������������� A�A�@�������������� $$��H������������������������������������@$H�I �@��������������H@  �����������������������������������������H$� ���������������������������������������$@ A����������������������������������������$@$���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������-�����!1AQa q@P`0Ѱ�,[�?�^˯&)J"P4) @vHVLLeQ 0$|g "J `RS0У W.r`B̋Uo\e{Cr* *r(v_F FGj`yZAҗÍPc@'Y4+rTH$4;# iwpu8|8EC2MTprcz;n܁jqm?:]pHtª =ϝ".l 2ۈxD mBG9B죄@%h7c Uܱ)[O�?QUOBxv *}ҭ#_#1a (b4sk!yfT7];BFZ ,u!9_zU z ྰ'C P?@ZFp+9jbGgLfÓ1nUT̖j^q`r@ZU*X cwD] }q̀�f Y!I*+é9D{eLWM)ÆAD>x|5n2&? H@8v=jT0(A4^ 'ڦ4'X r#C~FncX�ނ-.R.#Ҡ/X4gS)8ʠxW19wWtkϟ%&QwknF &g͊8b el.K%q��֎84#MkrkE/@ɘ :�kv J(o< S!/\@sNSP3Rlt7*q@l花iJlʾ]83DJ`^87 V rtpsx:ZkBsH*G|*/`>2K|a!:^'P Po{V vJxPO4Vp3w"W$4XPF!ɓ"]xtU1G?v Be" hRG6aAU ӡ hsN1&(YⅥaLRe]Ts@&yjeSIxvj-$h'�@ =4DřŃ$}jIbPa](*1 rgӵPlٿ FQ:؊Rɏc E$=/O[oT1a`dU= yn堾`Z^MbsY֌\,Y3:C.[}VsH M� .GEabs<{AhlP]ɓ _ [:~"8>zFI&{,wTA?&c1l ߤ!i;([XP(غ8P`,gjeٌ-�<E#'b/veDۛH7{p< D| @]i \/~.U@�5ql%h�[[n{ mV(C20#FV .+Gj4<'tz+H'rL"} d0ZRs!�Bn^lH2j^N9y-[A #ӵ@aR)L˔;KZjrs!lJڶ J^Y(!CոTИ8zuDx",P)]3AHcl)Ze"@�;q!M8q Np\wa+[,UA( QBFTrjRDan @OIצ48(ӛHi?vsT"㙂{S3 & 8!7 F!Mq*IR>zrjjȇHvefV5x% �Q< �~VUy>`s}:З\ Pw"87%2{ ^nk7sqnux*Ϫ5|"{)sAM08Ӷ )rawpDD!8D#4 >V0G0,(e +e1Zyc=b Ļ~3GO TʥWR/FLiCvi3֔n+6!`OBSkgǪe /_sr<htZNѺ*Q^ 5 N(,OF("70CNٺ<bi^<g�&1D&Ynw+̛UX�|<lYdZEG-߈Y=AJRd&��f ¨V_}+\I7O$Ymt#r]Off؋m`8@;h7$ݴ?pb%Y<:+{dlAO\`IFUCzFaUor,P_A(yzn^*@9W?pu\$BEԼ\7?u@Fd E$�@e|vu?qgx}r@ zTl<)sd[&�ՄDDlG|' bDa%;Ws Q=<4YU/<X n>\ϯebc-*cCvHR[PHR–0$9I>FCp qIqyS\ #֊6F t{+mpLX3~<.r*b]R.�,/$> I@-άBƪ0EXbtȍr+ŗhi;98ֳknr4Mj%ܜNjR],U?tPF=׉T->ˡZ_s-v^{8 ҷw0�Z>[#f 6 $.!B �#O{vM#9{B?)Ϩm!ᝎ3lY˿F,]W~4j cN )ʓOe K+0]MWPQ8mtP*Je0;3]F \ y'xKbme�,�eV�U�[JӆQ¢¡AT/˫sGK %7{;0NڏP6S7ބ0qQrIu</yX%q}J,~T<L1ʒ Fus(r NlڢM@/K! hUfOF%q`—j!=W=V@΀gfP]tYZ&)tSP׫es*&o1B�%JUM7`FA����@�hzP VX c!HB605۫CnB XC e%(4\-ˮłvT `)"l::]P 7=9>=Ne][X@X" DhI>D]a: x kL]={diiCd �A*Dae%aUfg4 j@|gg�= {Z&!b $%3Xa YBf*gR@ gtu60;@CSg\<3\z<H{CϱïjYNv˞JƿEdU*p \15@4 l]Ĥӄx$"(M0*WQ~dɁ.g drmOnmB! v_[^b΍$:cQɭ~s)-9A�ͩD�^p|Bj# *�Z |4݀mn\(Q�iP3\36*�Z |4݀mn\"j<R/{Р͠XT ӧ&|U<vZ|�*cbRQ c [6hqcg@_>H}< 鋼rzq  Rz"PX)?gd0 b[lFB~FuL[sCEs;+ahRF3+|to?YKZe *"0{ 4�, ia9:|)3b�$g ho4QBRF1љ<$%>oۊ++<pV_H!%,Jڼ@['yQ, JxT`PeF0̉s?8!HhgFE#!s`Pw*( Jx:,\D5Ke# `#@N#$\ iCy*+cF s.TQ{Ϫ'c]HnrX,ԡaM󔂜J180լǤ(%$<(%$<J h0N=41ʳCQc+*胀u"uG@hu˾!- f Wkvb̍  ϳ# E D&jG/�@@a *@dv+ %"&!F!hp AX@)Ċ7ػ,Rk GQL@;:tp{[FfCVAٍ! c$JnqblԍpZa272;:A-r0iXd<U dQ5Eh]֋fx><mDt F,̞F-ltqL19uz#H-qA)qG_Dx,t/y#=r(؎>]5%=Ɇa|AMT.S=%nmJ, .b |3/CpF" TAJHF;7SFӚ$�w& !H0۟(\�oډ8T[4m2F '�crkQ.sb�Ud;el*0`B�e4ͱ ō GffTLbzN<!k*6jHԃ A=${1kjXU4 @U$&g;l@aaL\W@2zfۘ8:AHԓT\Jf̜�Uc|n\bh9./t]#H,QeA.If8TrёYFA9&n=;@�V?lS"z7&iWR031$6.7 adZɉݾpU!c6MCvTI/ʻ۱ce rR dPF"# c" <�-cqGʏ5$ iJCg8a5e[աPL%AӪXK_$uxXeUs@L iM.EѬ@V(:qi0?"nIP6w <mT7:凲7G<IrGyN|౲ùF[ yRQ™Z-&. Q0%;^쏁s ⊈Y5 e%xp53eh<J 7k!)zzMx[fUdpi qez)'< VmgaH}`uPIgrw'0AbxbOh[ NӅj.G+}QUf{^: 5$Y *qZzvuoHPNPP7^S1 ]5^gO>QZX,&^UM Ns'.\!U{N$q9x|^R&1]1$/Q RF'| :l0 `X-8X fMW,VS�`Mg:hk�܀ Đ"$q"k'1YƆB5Li~,bl=I86ZT]9f.Gf߬6}fЈBJ@,ey3c:Y^g8T ֠a}yf�{C'FQ H=%Hq& b@ \ƶ7=ڤ.\G{w 0Fw �@(H98X�´  $2 CD";O xvuTt,)MIQq UXJVrURe]UiUUo|O_pz=\Wpz=\Wpz=\Wpz=\Wpz=\Wpz=\Wpz=\Wpz=\Wpz=\Wpz=\Wpz=\Wpz=\Wpz=\Wpz=\Wpz=\Wpz=\W�`QǓhD%F'k߿~߿~߿~߿~߾i;4�vM<SA$9ǒ# )8=h@U�t՚ߞݳ}A3pD}i:F~%~{}(]L aN8^'8' ?A0 I&<HfxF6@ Q( vivrA Ñv<TRP*jBIګ; "/i yPYMav6w|83(n['Z:ˁT1lY= %2"ªdT)u(2EV$ NzJhy%;OG{ *J IzTY�ҏ`$h]y$ݴu}kny@&vBBL�T�mVA9xJ̼ b^L$$Ȝͤ*.LG,7`f18Dpe"a;\T wCs=XzZf JY2��`U-K&pQ^6J"1d@w;bjb04e;?Y@R“MozrB> p `(`D PC{d!GgK� �4@N%ll!b"ƃ�~)� i 2u:&~,q3[73t.2'IZA `]O2z ԿA6 hxApυoگNqIIքN[SۧζIS >\ �^FHߡI FmK?jd/U�Hm! {yŠ_C ̸"("#;|lv;qpY ڍ|A(P�+s Bd:B:&"qꏦl�xG菏rG:�I? $u4$ 짒e̪Y<1~Է�7`s͇!8l)`>x(ҹ$z[!lP:09t|8E3^t"v縘 pU>{t@H,Z~ ?njqwW E7'f!O.υ}RF<xw)X)tB&e:9;**[DG;O�"':a\9#,*�<غ߅!: ~�@��pzge'¨*UQPPm @4ےl#)U/aLRl\ ;sEpg΂N )t|dTA M&H ,]pbS ;!qM�S IƧ`a�tK\CI4ā!ciuTw]�S{<:ou Sxt =Hjh$EBwE]kA$D;h(j(GQtTIGPYJfb�rݞ. Ao "& X@@M.I&Ddq F'Յڮ'˫MPDFm%*9Z@É)Ѳx˚%܈8C:�UwQb!XD&| |ɠ8Rph5`10@Fglۚ 6U:NhDbZ5$  � D 2"Q\;Nv#66: g3 So֎IH]pV߭PU�Zj hY<ɆfBUB[ʄXЄ>PPdFH0鹩iһW8 $ ,Py51RX:V R%͊}.DM6ۼ$;qA qjx966>MF$5]Q] &Rdjv#VRa${8OH&K"7G=@nׯ~ܳakV`]+ӿgCGǾӗ~؋S_<PrM޵m"pGB81`pFwiUX'(~sq@Ѡ|(%iWd<{%#`zoS-)Bpຆ̦6ϴ۩?` RX `,w%.m_.dɴ2i^0Q #iĘZ4X1Q`wT!ߜ 3$E53 EA1$Y;ݘf,$HFhZ(�*r;9Uc_@pe|0 ɩ˚~­> |bΩĽLP/hZUtTSSq%mމ!$y$E=S-fP Lz޻ZqTkH,hi�#ͱ\lvM^x:EFskQ*Gс;4:Qg/gC]| d�gjh+خLAvA^SODD@px'7"8 ox**ES<H)x ,N"nj'O�4*_)\؊6 B'Pt]p] ,�DDJyiWO3ᎯF^[o3�";+VnGr⤻v'c9bE< F7X)sWG`VFiC*�.nhz)`PYdّR=�("c3(^RѤ)$k(IN;`*ۂc6Jyk;wZMDyd�#J@DDxGH~xwو$Aɑ@"4wQ y`.d P|0e'W=St\}}Q++c}b Rv*\aq@rMQ0@+P`f":nboUl໥ j_Zۈ?!DG Q s#v!5Gs -XHvAPYXrJT#oC;Ze�=28lԈ",[i/ &h@o"@@XgMy*dP 3wR֚/^5 �'+#qQWj?7rO<CBtv2�D\cHb"!jJKWz hIԑ7ԦQ gTe }MM H"$:GwV~PfBlt ,PPp{sB(qZׯZYFէZ8����� ��Q xFrwe�ML]�*)?Y"l ;*G0;E`q?4buA.@db{duuC̍ UQ9�كksX�`o' q$ w nΖD,^VPD(Cs"C%Da���8'9  S�DqAbuf@∺6[q�8]xTW)ً\m@G[hxDj0@'GӦ5 @4Gj;~0 &Uy%|56�| 3 &\SEap{e%BJ.;Ĝz49b-B)0x`8_ @2N9N!7&(y%,x`,`TvÀ/ njZآ;r$ E*h? ͬkG*ńK<k`z(dt>JԠ] FCiC؄7}mk w0$ \:K H`h YTrmѿ`W%_"֎`h0WٚH`)T!p1㽀ғW^USG@F"wy1ozh.. d<?Su#҄-[I% D5dV8A C풜]kV=FS$x'>g#O\l YA๛vhyAPEh݄7!q3@P^t%YYbw`%kC0V]Fȷh.r6\r""׻p!B7 рpB 5; h9CVooڪ;�+�YGX&GJp8$urNˀb]yGHWd7e|&oCd6Dk3 �h63F\ծPnėϲոRD+i@bG11$2]^;ZG 2@ ݵCl.@|F؂#(XVaq1Z0 C}�RǸ8:!(P?f.4Pcr}ٰ,'DF @ gJί wEB4i4 aWt];J:xV TE[΄K6P0Aj/vl9"*gT0T4Ո�mgלd*i2�f(xHT2`<AD8%Ʃt ~c-tΪ.JdV^llxDR}  FT%0QRUvw?�.��!1�AQaq 0@P`Ѱ�-�?�ܹ4f Gی`Vqt,12 &P6ֹct n4̚t�I=o5|w̓:XG>)q gc0\zpeМAc] Ea O r1(4ǂYTM+}@*�X�Ʋ@ UU U~ m9XSS fuxtҗ+:yW8Y</ TK4Hs,$ W"1$MNy_8+s۰N�Q9EXMRhMƐ)-`u9zL6ZRGFF8?`[pXJ _r1^M=U@ ψ}1%wbE_ʤ�+Â(.7€P1 @U`U r.uh[ʳ^W`&pց1E\ OA#RQXbgo %,3y[9i#}cOY]uU3|8$i�_Q8Hi.;d0#dBtfXp�y >d %d(ƯVyA"DS8T"8ElᙧO%5PVߎ0 #1|?d| psV]�6g21bIݚrچ!(i8I� '-߲< Jhzp @<�?V,4PL3�xN�̺1]qd!x0Mӌ%DB6b>hˆ1|cvhB\`4 ;<%+:7'}Z;TD`Sy"Adb"ۗ`@r yPu�- ^qw5G$klt9̎5M�j p]>Vwѷ9v x"L{HA .Й�D1Nާ->9 �5LL�b䗖?Af@w=_!͂|C-mDSj`YÖ=">+(j#T]3)TR8D}D+PU82TYHHɟ9O(-KgU*QSbuByF� %1xO }O/@!Q;ud]sNpF t>7KgAvyNW&zkLg>Ӗ}H”lAl6Dd5AW1T)Œ0$G.#Ud|67 @7mV 1k"|T6 `.�?9O(bƐ ~a{~}'/Oì8Ksu>2Q=Ë�5�?\{7�뻋9Z(Yr_~l*O #ꝍ1; "h6]OE�GJ RdoNxLf^|D�A.-!xL%�> Piy?�OSPcWY@$8s(3Gֻ#L2`_S<TIA$N&o1G&\DTDD}z?e@m_YTB'_gA!V ΂ޖO$[>9 J)EؘGYhZLY!H eDC,S_Y^}P`'oi_^Zg&\I5*j7�q Ӝ ' q �#] $p2`IY~=ihelEq$?ؽAb'u;RvryA<�4R:#%k(cF|�l 2Q8a xTxSe xɐq Fؕht*<LH#W1Fr&(84"hj:=_~N�X^;ò8Y~D( 5SS'2YzFz= 9I$&D,炘PSaFX30*kҧ)B0}tq|VAHp+nթӐv/_K+mV놱'ff MEe ͍z boL< &DzhxDhA5ߑ�33XaYYNB�޳@kb�-ۓ~Y1mj־-AYLl}N#.gN >˹jV*B @XMdV�Ol_Q>�T�揰y)4`:ZY "(G Gpq~|j1;O|pDxFAgx�M,`Q7@EH*� �,5_2|녌S;tӓ Xf~1D??<t"0e $#E+q]q]|k?Bv .XBk!v|p sfԑ xPS<ov)ϿCaVP̒;lQ`!!H t^c1:T F9e /kPjwexg[5ޥ\r3hXps 2WcGK]^:RJȓ7nCV!dH}B;R�Q?hp}(A!0pPQDR` ]MT%Ol Qa qf ؂@{'hP0o~xCzx^#ﰨi[l+6m`�NpmM,y�A@R!pɻc<EW,w./l =U`�]w 7"Fy&cٸX)�OYytc <HKL2 5&YZ+]h +Pag4Ys"$^� X( **Q>yߖ@4Sبڙd;n(60؈81(vt< T9Bq3�,n_a*Ӆ$@K(gt+ :0<+ R΁k * 6 0 H˼^`I;+0ӏ?Nl)'7tU Jr9cv+8;�"W LNpH>ʍY&p G]*=\ԣ ul U-C.�oNM KΆ dVT2ʂP+c9  \ 9e.$Fo.LB ripe @֪,5Ш6ǔ:8#v@2܉\*@e.WB%h/�7(Bq' } s IA[JLUZD,y{b*p K.3/!DNj@,.mee6N`JeP9B7."]4ؙ / Bk89ep dZt9�l F�S²/\`w�pĭ2跂S4ʩz& t*>*AqJK (N#u>x^̀sUX-4/� f<B _)GQq<RNympȈlC,pgGYI#&ܼPPFtv<=zIKݼN +�ɈIsX? >x#*bc5Dl;\ La~TLx.;o 埫97]ahC:1qLNÿ/X~¾CM!Ta%0eEu2TR\fhlQLSja3{)QѿlwdhUU~Es`ɒ4#qbqR/0Βwˈ`CόE7G~~Qz! �A/LKJM-L 6+*sou7y4a`3=SF۟yz(É/@߿ۍUr.5�Ҽ~@d pfpDR5r3_xw~Ry5<+Iu׹nQAP.�qH"7P�x]pJ#Ii58n+y~Y#C<u*@%Bϴ\�v^XLV;@bS-KG,Bs&D=XB%ZA?'D>#  YB"L0}xQ`�hXՆK +0g w\6"tFl�-scg%ۓrޯ?x�E%\]rީB`.z4ьJ=nAc(,!O<;p !Lqu `D2l#35,bz9XAș9k ̀l9bdi>3B+A қ^(P!ˊxb+$F0h&!P\/w Lg9.@ (U:+8P, ("F0HRV7XFO}CrLLwa ݦ!x"0[.E Y )ccL(xQC&-`2mJtE`e0^*ۑELtLR!6&CtA] &A.h= rM]1l薱J`.45=29 +H2caw)Rd<n,:{i4#2d(7SLٔSLو^lP*\rh(=^*' p *iF4DK0vJ^)j2J$v.E Y )j,e,*t\b ̐pOBؿ ?3%% BX*€ �7Z(fa,8 fJDet; Py1eYD"TUE^TP)ƃB"U5GABP0.L7<B5 務v.){A-B4apҒ@%1 S 7$ " DnAz`)mup�J͐F̅N N-zH@yn*HE)8hSÛ Î:Cl�dh6OHf$N'ZB`u{ h`P$Vj�LpbfኅtFlWڎ"PsfZ/v W*X8v8OFO 3İՒ1 d 7cع)f3Ba EN[Y`L0V�>28+A=_Ԉ['`c�,r}AX=?X~x#g 6`kx%Je2U<ĭ\E,[O΋CX*I*zzn-l9 )ކhY˖JbВ,yWBe [<WaiȫcmVnjkAvCHZJGtt(!2! 5 Yud|23"{+ܻbE5Ƌ_;R' XktBƨCWGHIx@ijCp?\*=/W"|rХIP <V�yO�[TFKT Z@@߲Mxφ24+5L!ZIDYcW_@w.X!M YʞyPn}$}�X5eh"!Ftd|hjb\6W,N;^fjQ5HE˧#HF͊%]ftCINz3F <,dP ŗÑRKG˜?0~牐JNxDDvE1PF3�< мW�UOKR (/3o;j~D0粵آ07=dpiJ @~LI2j9MuS]ց;qHq.iμzAO^/>j0\skT]>z)GrIۆfن(Pkz= aK;(_0 h8[lJp1*) I>ZI9<i-�f7SXJ6zF6# |0̔$ŕSˇ`ʹ8] @$ߎ&hܪq(*'7@pW9Ri1c1j2�'F$�ynn@ղ/|:g$l|ZVQ}dz+|R|ƆWZ?E95ġ3Ƒ#Pf�Kʸ`s:�l$T숡#$ ,Ę,cVxǘ(Yq)fRUhP�ddFPibN ~A3# +(S`bHb>T[�ګR ^(M)( %nC@A;I=&5,0]̦-w۷p;>36C#v~i:rn] 'Qhb{"<:�`F{ P}I� �1ijԀ@!Dp&3|1Y^D"z2hbB@(%R%[DKoeTOo�                                !Qq{s7?ox7?ox7?ox7?ox7? yGI6v֬,�B ӟzg>GOc,/ _�.iFY2 BUaBq.=o pP8E91|j"{#N,n{."(O5V^U:04ť{8wϞmc �ֿ0\1% Aۖ0�dk2ڄb^Tlo#7+' )A�lQw}c=NdC"GRיx +gpE_?��翣ر7v@ ՝Qq@�@U�z$�v|D=鉃G0*51CW.0OD1!8h<Fꝼ]y�08S!!ŕ} 046HQ(2( �2�T21 =n, G1E6^P?Y0]N����:<ȁ=ʐx_`K Ԁ!BǴNmx3L4FLG"1$v-Le � JH&ady+ʵ˝xQ3ؙ]EYAĭ{n}h�D \&�NV2H[D 8pfzU<>_GV# L&z|rrrk_`L( H7:&/0z(C<2pIPQC]o J7MQ,N)42.`�u?w�@ >˕ w6$? A"gF@Z H(v q|p����I s �6(2+;_*QIeyc�G瀞S;q~wȞe��_9?uN~� 7vľ#D0U �M `0ǰ<\KDF3HE�?%8[};ه;ٔ *}*#FNK6ϰp3|L}kW<3}?wG\ |NXtqJY=4Bho+R!'~}C`iou͞O>4"9qt؃ k˵ClO >^uVgT9 p )2ב3n) ^Q\Jy~c=4~ӟx?:4wkNĄwQ`H }nT^_g3 8iA"e�F!4$KnN@2xG>7l/}P4Tp&'O1.|X*4Ur1!BD:D#t.f '1RAcNw\/VVw(Ѝf6DD`gF!XX>yoS81r/)uUZيXNTb揍q ķ9BD>ɰ"u,qç82s^G~ݺ,qh' ˳ kg1WzcB.u<]bqEeB1oLiώFKSkGcG9ǃ3#X>gYS%&T�W -_ܼ(Va 0gx4*l]Fcq{)-ȰT"5Usd* qq 0# od!Sآ2=q-7Q h*�*`qA6/+5$m}*9  H|Tl#Lc8X &`6r1rbb܀@ єDm@u\�ba+S}ɼ=N0k6Ib 4yE!LYC<ZJWPvTPR!ɗ1C5OyMr 'J'>#<"B:&8* $Uzcq)i1HOAg˂9( y.6%dэo89usw*E,JqO]ÿ~?<ʫ\"-ª͉PDB29p$GvҬ ? 4/Zm㼜!a+-媦z2:KIxm5T#pQXSbd�0eMۙj-!B !ZT D!J7bQb@sO~_BDā-`JP ^&JWm!t ℅%SJn^c(xfhP(E#�#S1qb<?emީmVZ{]?#N�{%<PǺUtWN Q &0/{fHxjX=7i[3׃>G9ǃ3+xK/OQ/qvTdX.51lBҌP<"Œ!,;ì^Waix$`zI~ЋfOKs_[Ǡƃpc 4*m8%-o)<Y?�I;.͍ d*z7\D@J*VaTK۟ў16kQvd�3�v�} `wPY<=Pb4MJcR- :@�4p4:I@co[¼:Uׂ:u�n_#l &6{ ÈDŽÃΆMCHρ/tԘH5#px1=Q-zW8ϭկ}_z?x,1 ޚEnOG@>rE@ګ�! Gl 0A"v~駦F( U! @ »܁�U7e oJMdP &n^W'w}Fe2m`0%.`)�ALSX$KjDnAQ!�BQP+ }rc^ a@$N1] j|#0a20r6BDcd0;D -=z.`\2 4q`6K^#;R 4\|LӴ (K Q!Pz>sq`Q_}"P�W g˃Zi��)&\Q9<da,ZW:kZ1a4E^}9ᓤce_vT\_U\D: y�Rzf:ɥ cʫn @ $� M"ϚӕjH��� >S꤯Ӣ2{LJrN~%eϿol~f/>-F"E)uL( 4 B'Q~@ʯP�&^;sS"ǷtyYw5l0@UwBOJ 4LpsU"D`Y Uhp@`aMWnӎA�`(YyfU,>L>eHvL �a� )UBIBR "[J7jZwuѯE0k(ZGh` A^ %09ѳA[P+ł&GJB&W<HtQk ni<&<e*}U/Y_}"hĩŞjz*8&M"#:h'%{YWx]'οeT*U U4 (3KƎ F*"sp,`�_oȋx\\vC`޺>Z<*|kR:lW?8_ݝ dDPpD�d�>E�Mn"YFCfD:9F~Eg7 8SS{�H$h|Fq3N 1͐I? ĠS V:#rA&v+JI��������BwBCB.1z d2H+p_.X9IFLH qZX^ QD .,l0%̽-rw0 FY^ 8 ڬ94W.-&$=B O+a(s r Cz>gJ~{ZNx{k.N 7~}̰',>px9z+~@o_je KY�@@�} (T�y1D� Zr[сTeOS!bPnE<(@W@Ij%frcy%ɏ_{5#P==iN�V{$"/ [?ϦM�'zl )2"8'9P3D)-dR#1EoGg}kRĀ�ClБL&^fPTkJ?Q-�D_WxXG>?[Kgt=kd)n4A.r 'y2*9&`gu12$q2EJ@ RX$_o 3eq4>G>)fN!ctpcbp*m5w�<"?I*Xj5G#j3Q🥺1χL!�j�.������!1A QP`aq0@�7��?�ݍ !Wl,hH`U0]IJHST]<Wngwv�;4y*_dk�t$J. KNࢻ^:@mf,v"iinL"l_qL6OTRnY8g)[�OlECb2Ss2ICi<�_��K�1E?+ n̶*/ډuI S2mņS2q$_"5%т72]ˣA;uRh|ȁ#4I�t%)i0(FH9akQ,#EOIt`9/pz_'yݞh E]}/3BN!-=;;Liޓ1ڐ:і`G�e Q|QTny)j/khn íĂiBۆUWe|=e6dI +A05"%zhٴlEQ] ! fۭ''TJ+c:Qg^Hp9 []YRX:-J0ZB}VѯiW]fё %9C$ }#eC/jJ D!U6= sfdL BI%49 Db؂•*Uw- )9ˈ1x4: M.%1  _PWe +榟Du~h7u- TFUګUvܯp7ܽ(!b!0^ [ˬ6UA6R&8 TQVַ` @hQ`�e�#ei$ >@jÝ/�u\ e紘2xSc&|Oc)"@eHxI TN.5e5)vTE͚ ʋ?M[n r$Ii[CtPqp(z5SCL3!ѕ"| Ʀ6C8~Ti`.O1֮_ m r\xMj*-s V>E|V�~~86@JQľ�ia v' ǫk #[ mbaBMHAi~RR<VAaQLđj \W 'gMÔ-#h*0Hh=4F V�@.Ȅ SE3s'e!FU?ңBm)] 'I{~ECXȉ\B)�Ǐy`_IkE@v[ݱ+UǗg_Bx Q�W=ǛA`୸u1]|� ĕQxD<@�#8QKLFh^Ctu%т6PzMNuz2ˮtub\@x'"ЀR| pKZ Ũ3�$oGӖ+u_ +Jװ~aaaU^ g¿rĴM٤]< @U 0LQ~} oےbYO t@2!31$W�s[=XR2֨Cpō4~pʣAl![V][mV]g GK;7n)ڝ#=DK#.&葛XA TX,{8b4k:EA!L-Ɉ FJSڷ9?B<�b }x @Wa@L>3Yri0'ZVdIG()3d;VO8]mޏy/sQ�EvcY6 ǃgkc!+81fS+,ٜJ4hcU =ҙ5jNXZ U@xοa^./`yՇFOh(zFk UQV9]-z>f(lG$ RSS ""NďaP@<%0ى4G]i%H4`S<7I|!kR&"}ៅ~)1D(Z"tM7k|+ @C F}g6{<84N[:=�ZV�v tPd{z({`Bh1 "d].N1�?9r+bv} y9'5חx˾H8Ip\YOGn3D  K} Srms0H_ g" Dzjvaù$zjvaù'ŤCmm! 3w΢�$ײ-RlܤJ$JC-rmV0D\ T'9%pFL&Q"PՑFԺ)\`T x Ҽ>аz/rph OW7D\M�t [%F] ;%dL "AB GHHH='19ȓY1po`uyr$FܦNl ammg"a 1|D�)(ԽuhY(XΙAE {l(NDC׫K:,Fyk7>|SL)1ѫ,\UR�-4cbPVe6 Pk\p %#b97E[1(Q[F8p&|0KtrdO?7"8BŦTa;pO'T: V]h׋64{b9^D)yUq+�x5*} oE<:Od9CALCBc'I{ -J?X 4/!<H|i( 1YĤxLT*KLK[$*% @X2rH%xΆ Ol*~dJ@>*K"AzF/4O,.dAZ@WZ[* Ov#0]H\r'xcԂh_0冾e{9CALCBcŕ"A<ĴY+f1{ 7Mw]&<�G!D�7a!Q<�;VUqE-YˉM0F6qϹj[Yø3C7+Z(Uh L"JduR ԃ/tō>=$ElxPM<_Cl_=|eށ\ 2@<�P [U4ݮ[)c 6l/2(HQ䧩6 +Sk  ϒNN#a=Vۯ%TB@)kOz "z)]_h൩YʆF(b-0ꘔ_,`$JVTǷ{%CY!yC W\>Զ< 2]t8^USHd�d` DL&dX٘1R#Aޕ;D2.#T-'DĖo v+ PQ1f}%՘x>$:$ib]̕ϐ" jkҫ3+!>r^2R�gũzC>wEd/bC�-�[ z� \ԷH Z@MOiPСP.M)%t>n00 ԄCi8\2^Q $[=cZҰq4Z)hlZVZڸVt Z2PުE>=raW e5-=ԂD?}1~6"OHDK뒨~i8JsPq}Lc@_ej0P*+Bm�,kzN# QobϏ{4q e wv_ϾKo0F['F TbkP9[[C"5 ?J e% gOW>_6KEן7Pkޛ~.ft_t~nYghh"-YqU�STx)2~<dX"Ԉ?Q�&~ٜ e':$fַv9@*�y>3I:~ijͅ,o,a{N6TsQ :Nх,h0#$y 0Dӛ,3̆ B$(RaQg)X+1%5ct6P4ڶf#?^31dJ- �x_TP*#ƥIzUQ Ȉ#B  OQh>e?ʂr$3)u`8%i[BZDMWf2` P/JM]:g& <Zw8hK__(@{j:*eNtGv m*Ȝ yՁe7 kLp*l*- �xov hp*ATjb/QFs�/̥qe k &?vH88&ZP?D_4G�I ˢW%ZI[hi8m~8so6$.'eol<F �-tYK"JC<D CPjqmU&xfϳL|m Lz[PJ7Z")W ȶ'w*֓(8)% q߫ذ�zϸpsm5e H4sBEPkHiwTK99Fq̌$ [8iC> .  x:|%|y|=Mlt;oi(%K�1X&=ʀJU!*nZx= �'g Jt 5@.N)%MmKAP hϒu𓢕We!.w,LがtT $A$DwDѡU,uv6 6 H(KCQnA֌B41Iv{Sڄ_t,~)`]�V�l8dmzn CƠX|'7LAG5hT.@[Nu su$X:6* BJƨ,nhң{LaFbQe:;po3U2nbvR\[�dY:6V!& 33zgNXc*:hW&=[@PR \լd0K]Ș\Q<4נ,[YūFˎ`~%`hUnѥk\F`O^, 힢ÊNwPv~R6gU8J C9g) <A2\�|YO (3:/IA4pBa*b7i["}_8(nyHz0=ahԦO$tt 3!C!OƆvW6JBAhOSTg ݇ !,]AHٜ�KޞHJ+ɛd hʹohDL>`kB k܋ e�j#t$p#D~J04 OH K#!d.LB2̐Qn43X ^0z #Ga0yD1㭗gR^tU7m|!0f+?!rD g0Ȼ�3�И3^lj}@gFD'TQpyD1:V˭=yD"B(h)5~tNT.kNn2+� T] Fg['񍵍0��W_eeu;bu]u]u]u]u]u]u]u]u]u]u]u]u]u]u]u]u]u]u]u]u]u]u]u]u]u]u]u]u]u]u]uٙ iaOVlٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳ|yS�O) aI6vCbG �UG'<"j{c^㓯U_]&T [BvbL^ j;YPܴJJ=-ުSW�p7wt7$ HAyQ$9ra[l (C):Fƕ h-h3l`CP`E+ }~{^ WD#F,Ѝ76t ֙*ev[+EJpDP8>M Qf.@EW9pU*`Yr@\VtC 쩈f Pz2N 䘫K ),>$t\o7=lY$0mאl` G wtM^ 00ijV/F۰m5� C h�`le""A|4]т |7T#i[I?e),g ϶TB8c�Z%˼}HB)1*o!C\)"Ԡ;9ٛIZc•#4BR|R8]\�Nc)# PR\~G>qOc)s8hD؇B{YD~$)W〗Se|Z"<u=|�_lٍu*� �z;(_ V  5T[Wb?lޠL6<p'iCi¬&iF{pC T8}l(y: cLv~ 9 Mt\cSLlj�_.% w`t;.h,��!"lM&zt-Чw">{>ޑcՖV��wD W�m+G} #AUl T"{&5 ʅ>}"$tFy7346Ӱ.J4ttLYɇq>yOaڱo�A!!zT 1ѹ�}+xkA &""ii(E -g]A{`1:(I�aꩽۄ\-YJxzDr `|S7cd5![VLHy7v-گ%`u@sCz_OnݻUOԎY/W`Q ՎJn!Q}%5@uq55@yJ&�U_~HiEh oK.Ӏ kQD#҈DbU$TOJ˩']W"NJn`C `[î $A`}nಝ $+jOD*t$XЬɼ2P8uMF!)NA8Ltr" I$:S# yKdĦMѡ\=5DDbw*tO6}A~^*DVD, (1f!B&j;A0w! ~QFO�5DY<߰O3}Ͼ��~2}˕%<5Lwx^W!Aa%0�$zi0{!lY`z8!VW5 :_eE fӋh V/YzUW;U�2䅰8d_^KGU[zP?Б6Ģ`FYM�닉"ׁ˩hN"!*x\U4/@)L-ne,c7Vo@ a, zlby6Gܸ%kqB5CrhM85~a+'tD:Dj55�Τ >ОOgć頒wI K4! 8r޼wCsr0}2K`0U5G0@0@?E~! Fl8!@MT^j2^2#:SѺt QFEm4VL;>_ cu)-I B+8=ϽJ3O3ir ma{ 7%\<QiX}ҮGc.NG L@.›*6vr^U&5)f=I;'` mq:ofS.G@JiAip!E�Pӽ1ւ\Kb,) {py+K^˖!�ղXvR:oP+?w'm6R*"fDܿ;h,L>ƭ $& i<q1A B\Y :*Vq^n pqޕxK[a3Ad&n`E&E3cYZTɲu> T )JMRʇ nQb _B-\+% V]$:Fͱ+tz(qr=@I 1(lSE�L0>?U]j7Q4k>|']Ӯ͚wY<]x$們XPc|E)lNuv@UN}!ubT_g CDb �ZphnGq :t%~)85v \1�ۓշwlñA/†OeQ}E]B)Of 360BxՒ2kA e[cׯaұnZ�*خC>E FDD[ #X] 6x(A0<f` �LpNJphقu$Hh&�a;{ "4PQH: )nq_p*2ByZew3x*� b�!>t&_)~ @2&kÐN(�;d64EJ]=Ix"T T`հ�ڮ4cg08>MbpwA"] ! 0T KVn�1e1'a. 1l61& 73.Y <(ZDp$ ;`}+V->J@pݟ,CDkԯhM�9#)S3H:4WڐtʫeY*Լ`(JSicKUY]P rw9lƎڕ#jJ<*DDF"lD؞�P@1 6拾mX}jRǩ EDz . J�Tmq%XIrna' #op�;ݡb(Du|ZO'Xd2["~uF�&!<.ha'aNT> 6DH&?i_ J/+Rz$1-ŋU…)%Jv$UCt>ȆHPU_ -P)qҠ-+ZFMDi 4a"6تcS)4,<+:?1IM(� n ȁҝ@i€lGv1%bOh814qv,zP zRT7&c H!\ZOˆMD^ ߂:  d# 3z#?y)=O bj`Wi| aZ˾DKOd AbF `( C&$E8E b*H��hI8р@"(ͧ"r'luݐ@%bKȯ5 Qt/wpI E\] 6mo>UrX xӝ!\.&�Ɖ ta$�B Y !.e1 UDG�%q`+٭? 1';]'fቬ"v^mE^O)AWGl,{ju+</@\LzPCFئ5̵_ݿp;M)){È; ^Dp ;WmRqq0V og pyE( tBSH &@=x;Ì RCh{ʆS!6u]. ݷ`&bm֮ 1j�4M|mZz"Bx >)&tYKԥ ǔ4D&( ` �U`~m!H`=+)0BSdl ]}_'+,Ok.:%)۫WLK` 䐗NϐQ,_%8HLMy%pBct{"'bE UX:H5%>Dmri�yvx`$@ Q@ۇ)hYFG !VLL.]XW'@Ƽͨ%=)!0t|`V <2O.$<[zat$(`Xn>zŐ;PmC!`ˈIvI $S`4lr†q[`gYw; SiY; n(3-f\. ʄBɬB^󼠈6;P%DE*IU ,$2sV* � ?y2kRd!J}ENTȸV,IX3a2N[Abl0$C[A�b:cdQ$@ ]NulDQ &ǾT 3*5((^H2=:%kB&�4VTР<Y0DvY`:E$ ^s& h5x(unŧ"hfa*w"XTÇw0@knK5GR^QM - Oͪ$腍2@R!{rEQtFaaUQ+K f-}  X9iZ%\!vG6Y.e*z*pQ/pz ÑqshBVejAǩfCo=V{k+5f.7sܺ&-P'YFb[ܒ� /@:"y-Ȋ�k#q``fam1X_�2 =h8) aW9z)Q6Fu!?1� <h{E\24"́-3}O4B"8& 3 =`miuTJnU1ZAɅ+Xr7G4 % 5OGjOҍY\' 0l"̬Bٲ/Mqxp{S>{Jhb#4Ѐ%>%@:Ɉ?}Nx5f䲓 Nlb*ׯP-��&)HC'C�\#nItm^N Rfbb5eU6ͯB?v.5!x!oDI1(#Ҷ:2PMfXT%4�݅풰}wcV'1 >5naB-dE(Aֱ%hK2Xj u`VF?`m{HWP 3V:peZD!F@h�A.ʟLHey@#pXBE - ŖW(1V{ ǡ`L4/^-Q1{B{2N3Zcp"u[w3[[Gl{X�F#1̡<cT.zƌ�Vr&o)4G3O�xdJN:+Gܞ#�x&:OA;ZFO/5M+"wIi<oG|/!ؒykrVwIMd<]զ^E`C-yՑ(*]O d58 (� �^ɤmE]~)4:5jQ%iB,.bs�u�*Zc_޼Z%WY+Ѩ*;+ba2%X\8fbWC3׏&|uloEz Ad%KДg- ϋ@T S͠d'c9ML[R.|E->Gq2\Dݵh'gJommqۉ*&de[Sf%ocbY.1%�f@e&�Lої=^wR90nŪ'H1B PDzq XEӁ5P º~x4h9{tt"{:�r9L7 YZ"I9^ڋ3,ؤIaKď {�^ܰO#fQ"\<[}Vu&\'_C@� aX¨ n@姍q)zuf!@A3Zݣfw p^:%&îxZI~'rR30Kd[p`H[He}K\G&!<i͑|]ayU0e&#(ǩyܐ|�V`& jʉM]iB& OaDO̎~-8$ $\əX a` N$i~}{$*�@,P$F65pbPU۝6QqTb÷0MA>k s�nx!NxqIPf-fOh)zRfj d% 3;2 qK oC"Ζ}/"6VTl?FIϼ{z'PSZ&JI,jD't>il2i^Dg[f7Z 9(Ժ#&bpԍD>Q 6ΏRUq�n 0Y+C:;˰=PbfqV,EҗFBg5*C:::Lv́1ǿ$'6y0Gz.JH 0L5I;Kr#p_i6K8J/uJ'taI*8i e5N|*"C6X P>O@�Xl19� DĀgLM..DLx(5D`IܢS|.t  IQ'Rr4ѱ^"u63I`0#2$/>@óZZ8ұ$FXo @N=Iu}ص#4=bC4u9, ֬ۖ$epqc̲�z#>]_iGv-H.Xln0#�@m>=DSb v-Pi 7N� ;,+j`k(^ �' bDkbgG.O-ᒣY" sLT_-r?{ף5"w*ý2 9Y 3]; 9vȼV9&!?s $j4-^w,Grݙ5�‹@\7'OqS:p&<NP8wUFAG2Ad_;;ށaw[Z"3RU<*~.TùV,j!E Dd$_kM"iqOFmn =jA >4p|n)֜/."#(0r)+�1}*`?pAgk#g`/\SQ[x(_̩�+5X qZv;G1m0h 04@ ; h*Iyw+ޙiv˓/_vJ\Ny sL���{ŏ_g$ G7g$pV,NEPCF}Rj`WEwr)(&K룝>SB\8`����kջbm )Ja L0ń2\MC4̺ۍ#ÉfffANGE�JFIF���H�H���@Exif��MM�*����i������������������������������8Photoshop 3.0�8BIM������8BIM%�����ُ� B~�������������� ����}�!1AQa"q2#BR$3br %&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz�������� ���w�!1AQaq"2B #3Rbr $4%&'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz�C��C��� ��?�ǯ� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �ǯ� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �ǯ� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �ǯ� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �ǯ� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �ǯ� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �ǯ� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �ǯ� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �ǯ� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �ǯ� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �ǯ� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �ǯ� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(�=O?E7?W-n}E)nҬ--nof!b#H$<Op\SO V.S)bj­JtW$dNY%K¼qO'<tq0XuR(/g >&~ʿG (|&_2γokԯct7S?<vق"|H^*Ǽx-ͳB%p^m8rU |C>9bx|+xX5S^5gFVoiUq#s �(� �(� �(� �(� �(� �(� �(� �(� �(�?ǯ� �(� �(� �(� �(� �(� �(� �(� �(� �(�>en<~Z]PxƺR6d6Z}Z~D˧[<QBWt76KZidɨTZ2Ͱ9EZQ3t,~cQE^y*ZեO VhC?bVХWC?,ҕIe 1X =7 p5V9~tьO1eq8zc>;?gOZ7tmS6]KM[Z$n-skO+&8gOy%^jtrp8rw<AsiTr)M:xZI2�^<?G~!:f> әa<]3iPViR),M[FG77?>W_ إLqz.׏9瓆N_|x^eWOoKF%];K Qq;[Ld`~_?N�vt=Vwmo/Pu%IOt!R~tNEpt&gGYK}]F&U>d|ʝiE.Ixzr.i7߳8ibdٚ?s\ML<竂9lE2#j<qd?/?,A$XV\hzFJ7;dE:>&0/T `p?]θ 7 t)ʝ<Fa0G4T'K yYl<�HoGbfХ\}S(\ZR8u1.i:T8Ы*,G,1JxP^؋w~/qwMÏ_jMOWs{b,ͬk/dعw#| ?O/|@_LGk�|M.̮ʲ\{}n4ߵ4�z9wRle-xׇ2_ �,+3'ի}o%YbU)~OlO{U> QxZ-?S>+tԼUiZLj|+s5=d[&TO+#; /psڙUp&U!}{$|21,5{PROg*g:r]}<N1^76Ux h<C1✟./E`*l*t' _3�WO MYּ/\=WLڎ𞽩ڏ7v:UٷɾxxWp/x6pL5YRx1ܫ/<-pU bpu?kiօ9/GpWe<3b3fos Pp{a2^0<5kTJu!/՟'. ?w<+崿xwš<%?*oj>'ٸx[la__ӏG=xS|]r͸gx,ʳJ"SU}s,,aI~ Z?fO':9pG%qo v!3,0�[xLmJs'Iަ�3 �(75_ ~{Klr);~0^Vdk R̪<yO\żyϓ(͸x:ؿr+:Qi)U֥N/88gXsSe r*ٖ.u j6OJIJ1f (Amo � 漚!kɼo)5I.epG$ %1@b*/b�h+^p4+b+U~աRO F+NxUQr�@Ϣ 6o j+QJ\]n4lUZt UC YBMRN 0?_>-xw^Jl. ޒEl 3K(ƿgp4x9XiRuz4l8 Mg<}<t#J$T?|9Kx^5}gY]Zu1Y/01ɱXlZu)ILz(�gAx@_[3t4x_5P67}t_Y\3wo=fD_2\ml8ʳ?122 \>#J?kB:$?~#q]*U̲s # ^ WW(֡WruT+N/gמ߅Cik%SjgWou֗%՝V$ [іGVos+ͲK2ɳmu#GbcpeFUTziMӫ S68.5/r<3 Oe9IaJU˱#^+Е\&.*j5!V9qdy,4W_t BooZ,H']WROtX幽KxX7[ V+ b1pL% جV+Rpl6ׯ^Gtѥ TRr!IΜ0~_cqM  SUcC aFUk1 ThҌU8rTTiLugxݥhEީꚏinao%ƅ2]\$prK##2ď1L''K|F"N4URQN#)NrQm}+?08\N;0x:X^'l.NU1J 4:jԔaNe9u@��P@��P@��P@��P@��PwN?Da�Wc�&�evW���οqç[M炿w*џ9egkL/,r/E͏>C �(� �(� �(� �(� �(� �(� �(� �(� �(� �(ǯ� �(� �(� �(� �(� �(� �(� �(� �(� �(�|k x;H uOxGe注['LD+cVbTkx׊*le<#ù\*dyv'2•ӽZ҅('*Tdډ\#YW pR>x/W>_MU®"3+Fe'(UO~ |<!Ghddo*Zʊ%iԵi啕R0)񗍾$g\gUkfWƹ<-iR .SeTfܩ,0B(SRžpQY:Yg pNS[5)s<23ck}g0+)VVQ)B?_G_ⶻi~ /^hɥk C_-Oξ n$l-thK9%g�Ws~;Ἣ?hγ"O70OpEUrG),=<fxg˨׎_C3@7SK< ϳ>2שg( *Y]zV˰ ^> c+aqo]}yqt2res_Xn?Ù�쾣K%dK :'eeݣĜE,v/>αXժb3Lu|Rb*ו]=Կ�m_�<_xW߂D&/j7 tS{;i3fORB�%�?̋2]7xU l&Gr0~}Yӎ6x<sxЍ9KC9c6 f ȳ,tcrlm*\FV~%;  `k[?m3E#,5]>})JmC$WV�9.~"}Y6/#xe'pX|+S [ٹ4 1 `lVl&.Qء:xcBX.8>b~/4%*/%:U!L*:Xh?OƏ�>4s']h A4{+EI^ gwXYW nVӧ9% V?JNQs, 6ܤ ө5VFu�7<#7|<̝Iφ|*Te7$̚IE<(u!ƕJ{gտM9{OY�P�__pԟR? �dW�8 �&�χk_s2|�-x�a� ӡ�;@�}q #� 52�#=�)MGON~~�m??EտU}( ��s?oCM3y?d(�O&?¯I)↡mLs-&Io+8䈷!bP^ u>mJ�+j귄9>[yއ pl66*-Jɽ+3*<Jn/d?>3|=ʞ*8n&¥7+ƣt_m*5s nh7o;./5 k>0B'Ft2@X1[8̷5IlU<cr쏂}gY改#6]Rpx(7'^!dW(Vy`VOeuNi9)<F.?W Ju$ ɾ']:<][[7RtnYۄӠDOK> !SR<)J'ļ.OXJ\ <>)d88zwL~gj6Zӟ 7`>Z8uxoS+w^)J?!+?p?_%gO3�V׎G�p�99|S�U�>Oo�j�?0?-�.�o+?9+SNn�{�VkH�wǿEO~IW)�&ȧOAu�*8K �?q?O��d7�0?�b (� �(� �(� �(� �(� �p�.�xȟ?*1,`�[�A<98t@�ɠ\WU_�EO:3�g6�Ԍmɘ�eE�H@��P@��P@��P@��P@��P@��P@��P@��P@��P@�ǯ� �(� �(� �(� �(� �(� �(� �(� �(� �(�s5K 3mvPFsK1TFE9v!GEc_*r;7xPS[>,F[&SWRMԞ'B4:Nr?m6fX )իYҡ,ViC GRZ(C^*ҩ'MS睢4|J/WX/+Z+0EYЯIEKر@_`8CĿ5*_ qeFu'Wg~eFҜTay6g$cx;᜶if<GQee%&;/Ԕ# IɫE+Lu-:�G/VO˝?Qາ.apKyC+-̲rV6<NMi֣RgNqm4UrvQc fYn/08Spuaqf:UW:Ui)Bq^ �D?% <Ax÷هRmT֯x-;̿fk"�P#N(?gGGxÜ{3\>BeyUWGY~M_:9Fun;rr7)~ xIÞgĜCӣ3,3<Pž+RR>I4h>*/t L.-nndҖ,JOEwIY_b0\E~Spq *XU:䛄^1^;x[!xv/!2f3*cňQ߲x:Q*3{wcN^:}~ P�__p~H/\S�/(7_G>q_xe�`<y���(W>85�N�%�'_<U�'k x�`|� �7?q9[�ɴh�VQW 9�7K��m^1�4 �?اW*}ݷ�:d06XNm;AHumpvr�,�N0$82⾳ü!^<3P*Q̸uaA.|&/ AN_4!_\=/JV;hЭV[/Ȩ9}zr~+ iGMĿl~u, %ǁs?|B-6ECwZ# 6`UOlv (q|ixSqu7pur*ɱ\r|_�I:o[x#0%TdB@osG[r̩IÐ>Z|m!:ΧkthO X.ﭭ6;.I47#Ǟ_0x\qfp s7S$|=l3JSa2V+BH 6Re?4x-ƜNwdž1%Izobj1X>Z9{UxP/XI!7X)Q)#b2:0*2 A�[үJz)֣Z*ѭJqJ*SRHӜ'(i5ZU(ԩF9ҫJsVH)ԧ'ө %(NN2di4ЃS?ŏ�ďh^1Gk>4Ԭm%.C3Ć;xQ$;W"GB\u9WʡeY'lZhSqS)Tj�P|%+E_YvuW5,&?Rwў";F8iֿ�|ῇ;W}&S&/c:淩x�ξ,oԵ[ǏxL *.n,ߍ3wg(3XG FKhrуal>$�a?^[<7KC%ʞ5b3׋Y#O~wb7,%-"~i�?h?7<]7_u *t=EcQYO$ !1#㯈a8s!崲5(CPRjsV)'ZS^Z\ߗѯxŜM9<,ML.qPp()jWr" ?(;{K? >$;KּQesKۭV{c=iȿh,+$9�Rpwgx'|s%|&]V_Jir*Up\eϏi-|>�_e</.P׫jtqy^(D7%O~<i<mź^7|9ZdGp4?4ao8fH3ɱܦX/eK U# NnYk5%o<' -Yi|縺1tIԥ+ƥ8סMΜ,]g.ƕo xsK׆5kk4MҵO edrŲfY~kc1L )Tq8*4%:oIU(=$2&{f&ae U*fjLLiՍNniTJ𕤯c�й/o7_��A70e�4?y�$83sLRYs@ە8I(EVQ89%蝑gK-oRѴm>V[m?LYFd9n.g!'i]c NףT*B iEέjj5 T.S6I6kb> bRapZU+q8TСBeRj%SN2%ŷi~~�FQz/ijZdC}� Y,~.-xpc!o,vG!2i]'ҵ̿Ώ˚Fu(qU2ܲۂq8Z�)^+ U1XYק GN*Q(iWT=#?_/Ҭ?:]H�TWu+arl͞Vͭ@� #&.2inKҥK^ywכw Z xx\^m8DJlsRhXgusz�_R"/@u]C p7yZD <sx 2Q-8KUyN">)✻؉ZWF X|F, ξN}&R?ПL x˂n^l-?su&+ UB5Ql,-eЗ7ğↈV�Xۙ'<G$gsQx]GHG'ͭw~meZ^ZEgqGw8:j33SR̛4Ԅ_R:�4@<3L_ ^1a橀RC11.0X~IrϖiTL6* U!uGğG�~%3 :'3~5eյP41B3ljB.BH5⼏.PeCe(0jG ֡B5*jU&*Sjz�S|3(?pGvwS3xW#ͳ s]Sc>'*tch҃VN4L/O+x7?~*¿<7cp~47mCLF=?iٮKWAy-ɪ}'/8<5<LJ2\>֧|)c)(UrlgJ5}GW K~ḇu,짋)ᜯRy3LFҫ6XIQXeSʝi/c}k�4F<2Cyu\vliU&f$IT~c_犿HV)*S*YvMO G.5 jkI7y9VQ-GkJr$V͸.Oc'E9^+8#aP!(/.I'lq" e@9=Ns_vrRNs"۲Z?\L#OhSVWnэIF*%w{5�jEHhm|xRYǃ_kW!M7JY=Rh ů�7M,߈1VNxefyiO(rC.&FI՟<B_Oxȸ_:^ΦkbJyVKib1؈Bo,QXJ'N��fOiԗğ|J\Wz " º?:Fj3_|S 7O[2Tap>Qo,-l;`piF8�\*"S�[ei.YW[I-aeӻ/2vMF\{�5f iIko*N .FI~~/ak*ETYvM5{8TKkQtdC|t%8L7+`l� ^QTR^O%gZ�5:�f}o]񖟤|0 s/}6~Hb,xzNW,u=gR_ 3|n% ʫjFpri?vsL-zehJќӭC C3/f'"En;0:RbS3T˥zln 2p%cqS_$oR$nQt%]WVYX@#)%(()FQi&kF5ZQ%(N2'FIFQvqvjIj;YIi K|Ay&qxv|B즺AB݂׼FmKm>gcZ<7閺>+xGMA9)ԕ5xsYoӍOgV"gO Jj"t?xG.(1<H-օ<߉1T'Zj˖O/YfMU/mK*u1x2~�(cڷ5{L�|WKs;c_ xsÉCʤ$ᙿϤ"u0!J8,-ƜgKsiEkῢ,g899oNGmWfY^1͓x ONNu?Ԥkom-ٗf q[ tWoj5/GŜ*WϰۛenTpxԬ_}pӥWe˘d9eJ6L6c_1ZNeʜo�`합 ㏅>!E-?<6Zmo\i}hfӵk{NiȼRq&2욭eZ5p.ͩWEV)ՄYΜ*PF_̼</6b1+>˃36]h<<kUY}U:UR*aUFpJYo4O+ �Wtg�dm�yM3ȿ6?�I0(� �(� �(� �(� �(� �(� �(� �(� �(� �(�ǯ� �(� �(� �(� �(� �(� �(� �(� �(� �(� "X%hd%X)c`C$2:A�uiRJ ֡ZVVJ*pJ9 BNdv.Z*SFV8UVN* EBpRԣ$i=9ºg<GZ4;u*TUm 7ѻ ƭ[q]}ֵi8ٯpW<ϳ8R^#W¹,Jdٞ'6<-?<84?Sx_-!̰w.7U!F|QOBdtON2W'uK |(sq)ς|}4jd]MbFm .buV"F;t>N\? a<VSzƭIV.(¾/#dO W:1Woէϋ24gTTd}84J]:pFy)Pg'7 হ՞+H?u�:lfkn s}8l/s Cre0CGF8x�^uȡTx9"~Ox2XOa2nR~x9iWN+8>OI_;SEou j.W=.Hm/mA VFAlyKVM]ږkù9+&y~#CE?~d?x3*?uYjeWqЍSx\}OZ>�l؏O�-2?�g?~Ȯ)�pk_>�᳸ulbՓK>RT,+Xx^ } +n9GXv cnβRju<j4nUY}+<6 8<GC2%sLS)pM{Q�?+?_Sx��,?owS<'�^ �;�?�tN9?Cq�?IO%x/V�s�º��;*�� ��w}%?SY�GGǟ�|@~<9ۍvMJ[_]"4o*4)7OyBtZ}<~0}E>'q7zWG8rx 1>RnReؙA{#jH.x.=cW8\n*wa귋a5n �~4؋(�Qx�g%��JG/�Ϳg_P~_ 'x&ks>6lj\ƾЇȈj[& . q݊wVb;%_y* Y5j0�-yziż[jKi_ejOW5oī kO6V_cUFnbug{ПC9~�-g[]Z? l!$_kt"[t:l'Uxo.9 �C™x낱T%g*狝9p'4q3wY*)ԟ2eMI#Lx.V5%F8zSU;˪^kR^lTyI$I9$':�ĭ-]n[շ?a?go ϟx g{B~r}ytu_~+KqxoaxrÖV,�q>8<=;{ |�EA$xV8GL|\r:TG^J_ø37/m3U� b}@h[l*5٧۵h|EoE9EIگ 񸯬q�s{Ϛ]Nfxe&>;ߔ3nx]�)XL?xߗNXڔVyZ R/cc OcӴ 6Fо.|Nt}6Ӵ'+Ӵ h�X;!${*o cqqX^*JNKV/ZpҩRr)R}|W8˰09^ax>"\=(4a)ҥN=!N? /x(=x]<I_M^:{jb◍m-֣qyp--ൃ͙xb=F |[]0ta{,& G 8s)SС tVs>XiSwZ̳ 3lf/f8eoeŹ^VOgF:T.JP8O/)�^&^:%t6l>�[voi \\K,pi$Myx~(feùaLV;(xuMN|=JP R|b>i9?SYV%K٦ JUx{-UaTԩ9T9Jr5&H|ET}KŚ4)m�Ś爴xDu5;RK+[Wpx{ `q^#(eYjsa)юڥI9T&*iB<؎*^gα|Ib ,m3 eVQTOe^)UTUi'RlcdVg/TSR5/ GN>!xǺݕέ%եռAsm<rC<.Jߟ#8[0?q-zdy]*+RqZXac:ui1:g J-4|ǘ8bۋXLW~&q_jʝj5TҩB%(N.-\=㖥i{eĶW 絻xk{]%TI#uuVqЯJz֣ZJ*)U5(TRp'FQM"|/|{֫/k*NzxꔫQMƥ*F3R8E$��ri�˚�Y�~ �C�� �$x�ESq$I$NI<OROrM}BVh.ŷ}^C?FGMM"�}WzίO6ý4K)MWgRn/Wу�JqQ+*Xz4TjW˲rT,ƼdV\V�HaxQlV"3(5!xhPf\O^keXYPC2R9= u�w_={L<O @t]:.,S,K-2-6msiw_#zOߋ8V`Urz!cik^9SpFqXSFU}FsD>}[B_gum0Ԫu1U*xr XQuq<N>0|Z!~'B5%3>W/5.�dTWgXx\b2,?*„i[ڗN1q̱WĘSu%gy|_;JLTV$/&O4<;㿍/c�Κ[5xcŷ Eo|Cvj'nmfӵ[%l{.x�^E>q4r(ffYJ409Y qxXb0�k,4)S%էSO9o g&Ϫ,Gg⣶_27s*T㌝zl]\VV�H߳j/ltԛ�xb$kAH<q;i&ڬ6I5KXί'׏1!_8WZMѓq5h *юr</x]g0E GeX$O/Tϰi9Nlp"<ԿZ�Oc/4ٳȗoE4�!V��W' �oS`(gUSqOm"~0֭Uu_XN&X5kMJ}QgtiOnHqwU+UO) ζ/sHU҆ |5tsH})q~8OBsxL4}sxѝUzVauTX/ۏ(s~u?h/-I5ia]�=>\X'Jt}x#+pj 2x\Ү#29U٧C+�pEdߒ=^6Nx_4-&pZ׵4 qp(� +v7 e5CMG`NiktiJZyz~;>ͰVyctVq4zjկZ1iͷCX~xDNi^%+.k&]?鶱[O[Ogicl?)gxX*;OTK U}_Чnz%?i[^HP~~l5:yngę^Jf>Vs\MI<5(t�r6)_�#^!{[MӼ9Zú4%]Ym\Qu#gQRṆyŘ49X6Y^q ,U0�w,r,Uzp ??I4=rl.",e5 Nr9Q<eWUcJpXZRJxzبT?6|8E?ޯo:}ON\cԬeV}S_,l_9x߆L~ppu+4ՔV8VToԧV8$~x\3d<_Yf2EWaljY))8<>2 ]**v9E?_~ֿnnRuŏ�^[=KH5(n]v Ylum Um{WS*8k:Sկ'RxyRczթJSRW3)Uf�F<VlԸSq 4xVg4pFcNzuiÖ<vSட{֙'šjievu `mZZٛTԵ $/ч1S2:VTVI79aeSWa~/ ~!ax( VaZ4<GpJጆ+ٿrF0x;`Foc*WXC5^c3bBAkHʈYq53 `]3:҄/kԨAk:HZἃ<ApWOf9iʰqiE<<*Thu=jݧJ+F2g�᷀?f߃DOÏ M%~ ?\![FޭpJAM刭4�!8?θ9T> NWVqr?ӡO0׿%9T\#=e=Xrʾ3KPYm*ؚXc*iN2TiF!_�M5|o񆷣|(,|/{ux'rXud}L#OcyoXY:S�}K2&/rMzTcj4i캴3e:Ж=%Nxa*ԥBᇇ_?ңx:`xC9̸KP˨e5并kBٶa<d^*ΤrhhPhV&'񇄿i#~7|Qҵ.>ӑ]~^Qҵ +U徧gwo/dO|p\!ø�F jq�ibp_fxzԧ(2o<Je<~QǜYSts*w&'[ OJj?_'qU߉QCOyĶv=MoaqKm>$K}/WӯlA =GHk7Jj~bA*O6 Uɳ ^NU1ji֞KQV*u*BGeCUs'T0A)a<&3 Bcl;&' TjUa2?h6W<�WOSN�Ϳ#/8_f)c.l)`P@��P@��P@��P@��P@��P@��P@��P@��P@��P@ǯ� �(� �(� �(� �(� �(� �(� �(� �(� �(�,^ivuͅO-ݭ,)`xhX]Vfqgeٖ p;GaAӭaqCB%(UVqZmKeؼ6?/p8jx&7^Q*lMB Ԧե8Zi�ट| ⦇n 7 ( VE;j]ۨuU,WGWؼÜ~?<:p;զnxNV8d.O5a�IE|\zX\pX/2L<!F8»8(%=a'ex~.i:69~|8�~~==?i񵁵2/l<֖̼E�3|N�I~xw-/'5LbI]TΞ]ĴV 6gƼէ?+ÿ뎽4ssZܰx>3}W&}L&M,^Y)ޕm8Gmw? 0xb+m{J? j1=;_%626"cW}S4S+(jJ);*7kJT3f R{V,=\=Xµ N'y~tW}ٍ7[ R<=j)Ǖbu'^%,v_^zrJtjq? j''M~#|7y p÷XKs_P CL?,8w 9~M㫵G l>L.M,6g–MZ+ಟdo'|f!*ezن{ٶQWeHeZ e3\=*U+Q| S_\~6??GgP�__p�R? �dW�8 u�w3h˥XgspufF$2_,~da�~ >þeyqEG;K6kcpeyyR50zjקJj.IԌxũ7K\Č(LQFYbq709=7 R֚}SU?? �E(Dg)y8K��GgU�G$O�2=c?HS��Ȕ!' 3?�F? ? �E(D)y8K��A�G4Q�S�̇|�[G |2k%UִkCh[x-A,zkmyQUHo|t[o<[eo.Ur,,>(*Xԩ5sHb'N\Х(.YJ2?No1eg.3Tx.a-ɳn zuj{ZytǒKu#'xo�&][E_�CJ/\>^1�4 �o%¯8ǨmnW>DÍ'Lx5B﷿[L 6BB cY]>O,sax >:Tx<5|01vQSk?#<kagg Ԝ-%egR vi,[$A�KW.3o7G67׿i7 $I\ncg( +7ղ|kp֭(}YWΆ/S ӫX-Jq߬|zYm qT+q 4[ u9s7G3TJ).s?K??|6٦|IZ]:7| GmU7C\I ,9X��Ko 3_ 3̪SSjard8'*SϨ) = VcYJ U9ٳow;riU^n#0FֲJ*8ua46/)ŸKi_o3Wc/^4+WĒZڐ!~y>ǫG^j3|:�_o WɼUɝ, *O8)<6O=L\gK,ƌ?wY3o seS8F'GS f)jTi䘴 69[F28PP ��)�]&H�<[�g'?O�W �'W �\b_go³^�ſg%~3*^|_nȾ)�8/;i2*??�._G��w_V0Gi_�Pf_Y�s�7�߳o ᯄa�4֍kkw(F{�`Wq_?,=g8Kpuh)G% %*iM\�|-0/8ZJ<2hTX*X}i콦'[dVm(M=�nOV .4x\%/X�^hsE,>)#h+<~ĵa|lagJX̜% Q>&Q\NRoqF>,jas�˥Sx^?iUQ¦p ,%۔u/�DO�Ó�:?觯�!�I?(>��ğiKΑi tuSğnO mc}"|a1X,GW f8ޣ*U#Sma6:E&[f8^Cy�+օzR:OJqh_$Yk7WӞH)oؽddB4 $e|nBA:"x u,V`TZ2熬Q.Y+dvh�`jy]_j9 ;SЕ,E8BVIr%h5k 4SR$˦0Lw2۹#ў3Nq_>E 5*Q~?MX\M|<ו ݺkm?W�dKe+�+�VxKL�AgYo��Wijg�_)p?ǟx�ew�kcSW Ym?߶w-ݧt`` cPt[}'6ޣj*Eլ Hg {*|'s_SM&RkF?~=džeQ0ٶ~p5)jժҦI_҇��f?_ hC<ƗaK'4}%ѵ=b?5iv!K,&I,Qk<Ӈ2?s;c>&cVrx>Ǝ"k0xj^˖0+/�/'|YÜ ʨx ᣛNx~' QVtT=<V FKK�RHt�9? ??觯�!�I`Ģx�Dn�~�én$:�'�?OO¿?J��I2gg/~]h[Z_XB4M:Ku|߲QT�C׈\)xg:r*4gf4XVpyKsIE.)3DžocxÇieqUpRS<{Y1bp<|1xs.Hj/I�xjR<Su |`Ѡ|SM׼/{}@GqeJ*aB/f</޾ 1jQ)UGoy},}?ӛ+"?r4? eY,E>TG-;�?m_]?xw.&]PzAwmwy żGEJ%:w*OY?(%$9cjSJXT>f9pz˚9&YgW-Qrʮc }*ҧ?z=WnM6uG�A*wNai w�bM>9W%xMTKN/\Z,Gү+c<-IRx~֎m4k+A�z}�]�?U:cX�ɲ�]�:?xs?!3\p��A�5xtg�dm�yY�1K�QscO�# �(� �(� �(� �(� �(� �(� �(� �(� �(� �ǯ� �(� �(� �(� �(� �(� �(� �(� �(� �(� }#% DR6Œh졙㌾;R(98o:>Tk}d415=l4tՌyJLv# #NRi%sr?VEjZrգJRJtkN7&8.Z'wiZռ#/5 xEuVu-:KIeKtwXJJɸσ<g gT'ɳܯ3SB*S*„*ףJuN5+R՚dXxc3 RpYMz¥HP|^)V:5Fd(R%MÞ>|(׾|u?\Z}{2#qږ<]xk\EگH�d2DaԭXG <zkd~#4(eEK 9(WstT5Zx*'0җ^_,xuW0ȫԩSd'LO=$GMPpX�K>*>GNm(EA3izŅnyV+mJ-g8]_n<3ӎ_F%0|$.iƕ\VWV0՝P�P|fDž<FpOp;-EZYfo˳Դ]JXl Jx*^B?ϯMSCF A-0X[$QV(mm5u8,aV(lRUQ)gѳ3[<:e^cҡ1JUc1%|>vWZr<g/?]o2|,.GS2 QGխУTLގeGFTࣆGr#�Aڇ�(�?<'s�G'�~AԾ"�?9�R/wN_OE7t?x(�?a/:�A<S_ƿQO�x\$W)(�)�MGopr_.go�r|�I/5�Xg̗t:Wl46[GTӴ8F鮯og%t,qF3˸Wi`2\13LU,Yqych0 =LV/ZZTQHAN̻/ن*˰1yg %zn60\=%֥j!J S97;<-+ WM޹‘__` gx�|Xko 橁ɩ8<-i�fdl/vK+/ 2>Pጇפi3jX0ѯYGG~|Vh!I_]Gb 66u6,giIJm;~ӛ&uÙ_ d, W\f#0<elN3ׯb*έYVZp#CRߎx(."xM>'Ͱ9p8,6]Эz40ZNc.is�WZ��,Bd>+)e�g/?yxG| >IakVִK xLK9d- $+G#|~?ppOg1p8�Vfy*B3lUF U5iGp \s$,{Ù 8ʱk3֍z>֍J"=# YJ"ЭR8OgKE r?G>:XUXs3 RE!J�%Ŝ5y o\+\CVc11eQEΌЌ+S-ZR9^2j_ӜUy_puGKfYeJ˚X<[tۺXӬFՌ*FҌY<gkiU𦽪h7)Dm6[oovճVKyY2!>Q?q9΍KZ6`RxL]6*Xi1%��i|aC4l3EjغoRx|T)n50iTer/LOw�kU\}#�qo8�Y�h~? >)�ˈ_*�'_|7�s5ɟ Ͽ{O� 7{�'C7{=�õ~$ɻ"�Tx?xa�g�s�dS�%[ß�m|C�Agf~U[;JTm=*h Y:U[ui4귻 ӌrL�,*f]ZQ[F+eJG~�UsYo~ G7&eu 7:mS 0`<8+q<&6d>|.`߳RX]vokm;ӛr~;.MN Z^ Zt*Y:M2|H�φ?t�ĠqO?�!8� X??D$~)� �\�O �]��!�%#�>�ĠqO??|3N'��wx|Ak޿j[ToE 2I6(vS#ݖaerOagR7Q)Ҕ➩I_[=zocf:rOqI*pbj׍98PE֚3Zҿf"_ +[Ng�_ğ_g/�M?N�O;Wg�_)�XϏ?yM�;�ʇ51)�abTycVE7nѳ^13zῙ~O$ڍ.,ʪU$ũICAV?Ш~PoYCY(֏vB__%ʟ ⷈ</xKz?t++[_YK&e֩c+� G.8L,+ F2R:\hOQ杭hJӗs|"2q -У^{:xF*rJnlv~i�� �%?1�gO �]��!�%#�>�ĠqO??|3N'��?/9�%?1�a�Bq?e�>Mß <a}JO+뚎udm`e{:atl}_A}<7q.?2xl ]:xe>jь9`%{k}<yRp狜SrβT|2X~'V )T皬ZVVw/y </&񭝹nd|1y?zJRt'ҡEY۬^1gZ.& ^3ԧ$<Ч}q-?g}o,=o4^ڤw,W#Oڀ׫�EB5|9JQ|߲Q}JZ 'OjW^R31ry_YA�~C�dOx?l?WeΏL�W:~em?x+�M^*�3F^V}6R�"�\S$? �(� �(� �(� �(� �(� �(� �(� �(� �(�?ǯ� �(� �(� �(� �(� �(� �(� �(� �(� �(�<|C.][OT𝁐}z\r $17}s_Al:�:i燹^w/e<_i6sj':xj_`+f:3qup'1K Cx[3l hK=7,βTiB"Uaok)փ?Wm"<7k%�<9CaRjw:"]C}al$:&oje\yœxǒ)tl1n +J0as+`3MIӣvQ<RJLxs{I_M so<p1Edu̱9,1Tq <":FK#N2+\4m휏wDx$ԣJ: A�WGөN8U8TVN9)өNkxR$�8ӄNtBTSR8NPeiFQqdC* ��a^ gekk>*[dO׵)t$mfK[=G 4l"GǿJ_1=˲ y/'GYC :sR8LrQ9ƧԽ9ʔ)я{s 7F=[fPU(V3ƶ#*Sefͨ\UHG WX)&|/B<f;u ,<Ab9*6qنK`ҭNŜC嫗|3*ShÇ3R WRL�0�hyoJnNNק8SV>K%=ba�l؏Oj+#�)�+��R�_�õO/K ?9}>B� �ۇ�>�犿MOD?�p_��Xn'?sk6"*�?^�ҷQ?!o&a2}�U� o;Aad/gM>^-R>nxPOEo�x|tx/s [QTq6 O�"c#.-Jp<|1rSjT'U]~۟ |uMSJw<%kYLWW=ޥ[2)l;MP_CLHN8+o 31.cp9li2̷ ZjX�aj9}g/9rJ}4<]x?nydY\8Naj{<fLVcaR0Yb`>hp? ?ᪿi-��@(F/1^1@�D��,o�,jC__)/�(F/1^1�D��,o�,jC__)/�(F/1^1�D��,o�,?g?_� }+:_x[=GSԮS4=w;2I"NƓbm`X �/`ɳ~ 0\; pnYxh2?�cSy&qUGKbZQ :o+pcxg21~Ҭ^UMext&ҏ_S_|R�>fI$i/|1+)4t4n6!}~ʯ֟ lϚxihdOߟ q]lN247*.YN> Y[G)s&'d&nL9܏,>U*)FurF-s֫Md'�9?aO |KUR C[q,b)�8;[F2^1[i:|?(yDnh78Qn^)wqj/penO?�QȟteY_DX`I}ḋb<ö7$Ԛn82K|>YF^O?ϿexҞeՔe(u]d=Y8ii]c/eG!R8wv8UAf$�I+<FF)R(bܤLrI._~,O_ 9( d);(<GI$}IgPč$q<}uQ (�rN9򛀤<)5Nj8vR$m-YxOa)pGF1JRCI$m_+@ڧvOw0_%SN7lVh\.S^~]zʡ%A_䷌/[I✺t],6+2eZڬd0W^pVmOOsu{էdTiدnjbcFaGJ\K�gMkG5-a45[5UmWCPf%ǢhEU/�xN2*:y apmreO O֤OM tt&b})<4xwq28$ŨZ}c8˓\1Tp55\RJo �O|߇�xFw3xKŷ%tWy ɧpu.g[Q=_1y d 2)VOIɥꌥXʯ+J41^ƳPf9'd]g"UOVõJJ).RüDa*%,N *CO2�J?|6�SLo9�e ɞ>�6 K?m�;??q_韄?k|�Q`><�7,�S*,_O�??kO^/oOsL-`|gxyo/d$촭NMnd$ӌU)qw|O(8Zy_N 'ʫ:TpqZqReo׌ g8Dpf#W$*ѧ0rZ(a1x66۴#w[~_[~'&hcּA-睒8gm|AᵖwV-syvpYNjq5E9a08K1)e50XER,6"uSzJ*[Y8�2p#+^+NfX[*Q0oץu%*UZXlEEg5zݣt=c֭鷚6W>=iZ_X[ɇa6sas &R`)bU(0QJq:IS(0ܫ3,-|a<M9RaqxZ֧$ jT2:+Y�<Y}F +C4ėw׳!cY.o/.e/' |!1qr1uN(uJS:TUkU)RNdYg~A1qi0,9b++8EJje 8z0^HQ98/>xXG|9SzYl<[.,^{+Ibhy󜫈2.oٖ[᱘Z*z5TSpFcZHʝXBe ܇r^Qe՝fIүFkX|5)UUhb)J EB )N߰k/?j_cп'o k.-,T =*Mb^vƺ~3p^+ϋ25?kY ^YMz9)R�eӛrgpG<* VYVcVM*tyOR{Xc*=9E)Yh{1lj2xl"%~'Ḷ״>gg=*oG>+ q-^oc䋜q3&JbkԥVx�^0DEӋ>/s\S9|)JJTf<=*5:M7蚞jWZf_]隦} ^Z]X.nb UR+w(c01xZlUx=z3U)VZJ):QvdgCbpNBVʕ|6'RTЭJIJZ5a*u!$ŦMkc�(x7�|}"K|3Լ+kr.xĚ /Jf�u+˔p87Iqr<d3[3,>:x Ck:j7J/{?A~ͱ!O-lVQݤ,1ʱ8L&�ZX|Z q:_, U_SN�Ϳ#/?_f)c.l)`P@��P@��P@��P@��P@��P@��P@��P@��P@��P@ǯ� �(� �(� �(� �(� �(� �(� �(� �(� �(� fZ~]ai7֚}l;+㺳sG4N9YXtyvyY.o9~*eنzTVF7Q{-̱>c]|gcp]M`8LU ZuTҚ3Z'q+xO&OB;T-ܛ,m;Un4 fv.tmɵ�|s0'x aN2̳ 9NuMⲼ MxsW �zM o)ʸW-_*x8e7Ղ)c\UNL2ƷjUZXI4q8i*8^_9;Nw;;PgԹv.Ɠuky_\s}vPj =geoFڎ/0YNP$p&oR# .'\.[Kn_o/УO3W^6rB<D,Fobp̫9Jxm<.3M�Pk1aoڵ\/Gpt~`gSpS,L|BGozO6U\\<,w珍7yp6XKxQKG?YT#{A|%�x kî뉪Jm'lЕh@Mڥ vȞk8xI(u!x](Vf *yaBg j&c:ؼ >Fe_U1UiT1Ÿ�:i>qK|U,4rL j.2Zy.6:nQ+fxoө T=_>SּssjO eu/gYmE{l Ϩ]"X4د. 2}<GJ6Sd|ť cpu?p)J2`U4 (P0gy֥`m%9?G\㍱Vwxo3V6/37)&0 UhxZ'_k;N/޾T[DWF X-RZ[Gm%XE_�K#p0xwM`<& ֪ҕ|V"JXf*bukbkJUjR�.8=%0x3٦.KTb꺒*wqL5;RaRҌiRccN^:} ~?EW!Go��?N�B�ɩ|E�s�'�g_ǟ�L?^!om_P@\~_u��_xh'�HSxQ�a|S�7ߵOƏuoCJ/\[�(L^k?'_¯@>x~)j lP[U-,|xF6"!|%{n. N|*q*T,Eo r轧&w^cWZ3pOiQ/;I7zyNQS?|Z>o~hR>'R[j RVCiCxe*nw�VwR὞gv},6WRq^˰Ӧ䔩s+0\\rÓxxxqsӄļQO FSRr㩮NihW@�}�� tk۟'Cg/s"]bf@km c#nvM�+ߞg/ϼ- ֖OSxn}\0LMlRdxdmx�l}M�O1% W1J9"1|5T2w䧜bMj*�=NFX|67uC<׶(mGBWӠJﹺBw CLf+;ŸpOYow:gQɳLEY'Ʌqr?֯W< {c:zôaA{ӯusl8<؜]rrK?_-ߋ\,-KXZK*S̺!P(dk;xȟ˄+8œG-V1Z:WK8ҞW7$A9f< J8Ҿo97tЋP ZIƚ`{j?D_Mϋ4ψ yg[QtZFXs.5pu;Kmo,w kib_ ||<01F?ɪ獡N58WRT'-nzujF_b3'<ACO.0НY+<F ֨:ҥ M<dyQ/~>}Cmj-�nH5BHd �Pg#(,7Σs<6'T:8t]Уv�Hp~ qA3O<"y#*TqVɇ:4$|o}~ž&,T5#_R0Z>o۩+[t±\) ce,~jx7NѨm_kO)*Ya噆0oWo:Q^MǾ ><�x3GȎu6j|F "+kv<%͸33 υͲT]Z|~ QO I^ʴ,VT4?~ʸ.x܏btx \4}wPJ7G� ^jNi5} 7_-ezX_%% ) B?<,OE_2T1c*t ,֩֞_,C48R\p?s?g 3Zs'^u)ul.cҔh"iN\J>Uf9�:枚NiqǦ꺆�yktCN0+Yp\DjuiFm+de}mhh$$./䒔B)I-Z]�?dh^27_\|Weekme]" ,-5-% 9�xQ^ƇդRprW)zSRl y|K&G^?=T{1~v oBzV>Tg: Up K$iԦ0xz<o/�;}7x{_ZMak[KX$S ҵ[g�L:/ x;la14\UJmS N+ W3jSbpS�T�~ iU:x;StcqyF Ct(Ua1tNuBե h$Ko-|CB_ޑ ^W%rҀO重3b3=I ˱)&\e-"m/|Nx`%?s pF_kͲU䣢K/v_G? 5|NFS.cu=y"}[PLAi{\0jU.J~2,l/4J/ 2K '4Xoߡ4}�BS0Ux|/G)g+f#2XgWM)?-߉-ѿoXX~snui)tMGMm|�ٚګ\rGOed=b�qNl&7:S. (b&  t>WPOxY7Vsy \>p 4?5rK Krڎ86T_Ge�&6m�_7s8� �՞$�x<�=|-�l~�w?O�|y�oYT>YяB?&Oß|1߁|E{AFLjⷰF鐶<[;;oaԬe5K_ǿ7cDŽ3p2Y Tbj%B 'zӌiЅ�V>ILx2 PeY:)aF*X<TȳTңZgJa[SwWU?uTԼ1_xI-|f+xHU]~X>yYl-`O6׌73\?)&N\,K]9{|*-VΥ_j~ѫ<\<\;.)ψ2x”y(7V_W=+X\z T^6XzTY �i})YH'C:n>wymn6˛߹c/`bZi^&mcR֛Mmk⒖4ױLEE}b:.Oo?4?bXNlO7>-Et[-[ԚM-Mu6DKxǞ,pv1F9w T1?VhNuWVV%Mᯂ^sl0<,qp\j.T,.\haP|KJQ�~מ&/-Fxo4HKx㶿S\XL#[ el/oυׇ=+K4Jsi&Ju%mOZ΍P©)B7񛇼SpX*/ BiYz:ѥN.GpXj*"xt%\:Cgo�/~Cb*-8>urI4-KfY/Eec0/{<?a^_G�>?x7U8?7ʽܣckj,DauGV,Fp3qJ_{NwcGr,0F3| Jsq#2B0mԜ%ClD0T�4~~׎>JD]O~{/IQoq^WD&^hڝ/o |7�< .RmUcW)Np˱e099:wt(Ҕ)}/EVSQ^)b4jP̥F:YVN ԊqB(Oi׎>;x:<WlW|:;MR\@Y%x &O3O}_ :y?yv*P'bsZtu#֔TsSp?ɾ|3Squ`Sx<%jkRg<*2y)8??u?{?dhk^ Ҽ1iM|O %kHP'ubۤ:}ĶϹKaxL'7ꄱΆ_ьcIQt0 .~ZԼ9Ìχ|`f8<&\ksD*i1N2t:j{��A�5xR*џ9eM3ȿ6?�I0(� �(� �(� �(� �(� �(� �(� �(� �(� �(�ǯ� �(� �(� �(� �(� �(� �(� �(� �(� �(� �;mC|1;O MצAz?ip� #Np %=>�?~�FjqO|?kMիq#֭'yVbxrY δԧ6~�Wx�:x^'<RO()Q⌲$8L>}K0_J6MC-WԽ cH[ۘe�./| l;RNrsk0�-_F,F%W/I6 ř|ҿG,o1[+bV0ߴCգ2^'q/5&{{ᤴ+Z^yM{r;G4)F ì, Wrg<=>|^ˋ1j,GgYm;%.׆/'Vv?�?Wo :8C(rJ541im,.kAPu ZRo5=FV�Pf\\+qI]k/.reQ˲%  B? . x|=(ӥA_D ~aq4c3,VUx~&3/'JT9l]5㯄&O|;?X驨�fh,CMsO"ʛv鹍~u⏄g`IeڹO|vRr *?t^Ƨ7)ϖ<yωo/C>�!e?r<oX?TUJxgVnQ\~п<)ǿ/Ic&�)3[jVM:v{ko6 Ir7Ïѻ>08,ɡfVgⲼt~Fe f&~tB#g}+~C =x*Vଳ.aOù~aKp+Z*Ҝ/tw �('WŚG<�bxB{4S:nW.ص{;:6 \Lc 2D })~s\ynCO7ʾ[1fEXek {/eUΌNYqNSƼcq6G<UL3]Vx+-ĿfLv_[ౘ6aj{OiMFa8l�W~ּ⯉?ھ fYO6݁[&\|.I8;9\�r.4 ?x K!ögŸ'^}r?n⿦/;s9.(O[-�G0_[WGyw a19yѫ5wXֺ̧WSh^?Ҵ =/M D6z}vvs031؃$9_D5ª91i71q5q]g5]\N&Zc7h̯r̻&ʼJ,pTxßc`0<-.nrx|=*t7.XϚU$]F]_^"numcR+xC$ҋ{H'l6Aim CqƟ|!\;|1pg enᬳ ]D0x 5N'WqUlF3ZU1kթ98GiYq>aS6!19oե,^;QԭQaL5;Z8l% \5(†*08g�P;Pu WMQ/-u HZL61A<q ,-9nafZXܳ5b]9Qq*axZM7KRDni5{~?c9]2fv`CRnT^*>o`U+VY[ e#|q_C(@((>!xZwM5馚i=VoҮI^)&i4ѦYi|4<0H(4]p@ `(Q"p aF7QӡJ:իb*S]\F"u+רZJ9NRUzkMANI՚J JU$%NSF J:tЧF1<ƿ UxxD5/m̈ KYlnk;i$h$xۃ:2"q9FysLFIU>W N9:UiTJ'8?Y fL3\nM`&+Jz|p׻VXIӯ+ҔV:rOm# bidvI֯{u9uHwDфB-*8 VjKTܮ\ޜ_cp[&R_/8\$p fU5i۔2S}y0qh-Y/ KY<[xޯꚮ`:&XOn)=7wD_8,pb_\KT=Zn(G:4i|HG<V00^ a?oWa U*/mZ&z/Q<{�h_و]Y*Ė~7u+_xRk%iwϥΑCq]E1\HxQ!{:IƮ>?eC5թ̩\ܴ")S&9JRNχ7xW9ef)f9=jEJp'R0j0pЅ8U8F ?X_XT{K~xn5=O}TZ潦=Ӟ/H0E 0ՕZˈ)_%0,E+][_ _}5i<O+ʱXLD_Ycr�ô/.$ngbG\O'IGf".p�TB:4N8R qPo&{۹^\Mz؊筈RY1Vnu%iIE(JȭZ{ PA'ľ k#OAԦhmSzԦWˎk2X/:G#|'qQ⌃Qq^9RЌxiЋoPe$$ڋ_|w"#84ȝiFx>ke/+bc:j\c))}ݥ�c�l=>m\TE7efkcdaU_?Eo kL97*VKXܫke>kӇX(cՅnd-pT9ޭazB1<_ˣT]qqoJunH4#9RWb#Ñqp}ҋZUS '#7Gҟ*[ga*_S&%$p-,|#.gΥh|uus}ss{{s=]]M%č,73,+M+4Hř~N:4ѣNRiҥN1:tSBJ1RbI$Z\EZթ^zZiʭZjIΥZ&T69ɹJM6*Ϸ�~K^O CD|9/«�HNvKF/.۾y吳9gaf+4b1OPUYVW4){JT#~}&|o̟+2n7S`0f[�VGl 0jWTiZiT9J_%ƾ&�xƚϋ<]^!~ǧ3\^]}Jm<]ȱ;b_r/rKZ8,ׯ0 Jխ=j*Jהdz5|393,gaԭ[J8|-y F"V �#9v�F?kσ}� miV AkjM;kdݽoiA!S&#*W8ui1-jUJJYVGN\$JW}%e5]-MRwJ8+SK*4GݥF6(*|dl+EO:\ʻModrScgz.WU]IˉqnlތiG<>Z޵ JxC 48|I�3+3� |[h7?�KM%Idyšn%ũU^XO~w5 i.̿Ppy,^i(<̱c# z4BTQwxOQ-3<)R IeaTwVRnR޾(݅}iwzn{aX\Mg}cym"mwiunmso2,O ,R*PW*hh(aӝ*+SZ5Tե8)-peE՛a+akbipgF ԤJӚSR$tE�W?lmq} շֵ%@UƹOzkOQ%N ~gx܎Kȱܟچ ^Th }.m-<\/:4lˉrxRKݩUSVkX�li3߀mC,x–:~: Nă[:Xqrѓ}<(+W*Sg9j½:ޞƜA=O G;8z[ĸ5nXḇX{nakQKC/--mNj Z ;ŚԚņ6.׭5;[[BuwJ y1|>W60y*W#,Ҏ+aaA啰UpR\aBt\x<'+r<C9gXxlZX50VsCGR*biVU!%R0}G/O^O|^� oWחJ�3WN ;xcZ&|nW7O72쑲 xCf:~ o5yRZ~1xzTiuKGќSq+x.*㌡, V+}c*8r5g8I?)'A@��P@��P@��P@��P@��P@��P@��P@��P@��P@�ǯ� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �ǯ� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �ǯ� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �ǯ� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �ǯ� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �ǯ� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �ǯ� �(� �(� �(�;ǎ^O4mãxz Z )uVig-?NIܒ\@NZT�y9gS%slގ2^:[ Bu0)W8*TsnrIE){3"*%fo4e<f& 㱓J1 ե*iM)^_?~_jw~ƕ{_̖v66v,Xබ9&i([޿/7Э7l6JE|.* (ԫZZ\iҥJe:*J0"&?pD~?xaaplv'R4a)gZj:TSRQ"Җ?/uI4?؟|+E3ˣC#7&x">xdhBH26_fhr x3Sqy|+cpҝ7R*RrTVk@Xeߎ>dB'kQV<ԪK iV*Gބgcux߰�֖׈/|CýGo:ƂnhئrKgC[W0'W )2qvTNN#B\kG8Ǎ<28ޏ8_?Lv\4ɂXM K:~Ӗ;8__|廟M𯍼U:{!k]-% [y.䷵$+ùl߇,Pfy>Yb!B3Vbjƌg:(ԫ%N3RjRrwX|x$NL&M9V&P".q8J3:TҕiSU:T)8SZװx@�x7zߏ|[xj^ѤXRU/l"gA \F&B#-Uyi9j:8 qT\ .LEyF5N!9Z1hr,0<(癖)jjc3 M<./vZ+F9&O?پ ↵a+m<ŤQn%> {5[jΧ<V9G=xJ\x>XrgUx凟WwgS PUTìMjj9i}<,@Cܘ,⠪ѩ =2:t^&RKBzQw#ĽkEm_h-{:iڍUi4˫4ٞ]#Nmgͪ(S_\χpx4Φ.3-VURT1UhQ .&8 Nw.Y9?c&Zt5r0ü.*8:8Eϛ Z%<NI&x}a@��>uᏋZ�-3׌ONJŴbԦ[{h⺒\Ťj. .-$d fye ee_ EK:(iKVncˀ(|3,|I^PTTF]IR]__H|P@��P@��P@��P@k_GU):z>%i:rZI[#_jv५%eaTx3l<8/\4h:r8z,)r~v>~#1\{G#τ0XqYa(acBPu!1xjWjTy}}!!@��P@��Pe:N|J>tCPe�fit oZk]v;gek_kļ9񣌼9a2y'pX1j׍N*Lu\$[74gxP>0�<v{Wi9 ʗQӯ| iW̱S~|R##L:njn<y+g]u CE6[Il-=iAv`^k ݭN/=|h/qXLIù,* dsZS`SW :V>, ?yϜ8k~gxSXy-<q\XjYe}:|N1^O�]gF_i^Mԉ� F7z=ڠ$7N5fKK7N.!~ \G3E,,>#tb9Pq4jb0E#2 (>g)>1p\W%Ukh{Zse c^"8JKKJH?# ��,xwώtqkrDK;s7tԾoL ;F]K"ot$`e˰ ƬAթUQ:4(ժUqKIx_H<j[cͳ4Bpq *.8V!t(Svt??D'cਵ'Kz~&\nq%E+Ⱦ\l(CK;2X,V[,-f/N`qEMEGX6M, cW. X$%*OjR\rIN\zdž�P@��P@:|-v-CA?5{]2K<\ ~|#g}2r7ļa\C \Pb~iF'Q45.H^J1JKCq8dxa>=ǗJ1\wqCOP_?Oj^}Kx>K] MZ]\:Ɛjl\,<!!|A5\6Aa:&8Lڝ֌XN4jT麑){븟p&3<^OL$lLcO VuJ1<vMUPGFԔ| �(o��@hZnjnecPj,}^[+?&MK:~%Ep.7 8^Z~' C ƴKZ:pͿcþh%%/< G?vy[=<M*9e4#<eoBYR>_Y⒄&p>  �(� �(݅76b^ sw0 b<[dH탌VU+VTVtiTQ5Z()TaKKgú*Qu…kR0)S%8-cڋ{yx*g>)GxGJKk^ؖ. Q# {i/^闖WNkǕ<B1y^8jX\,g]aЩ3 ;[ՏMUJ/xeK®#x&rLp7,i᥊f'+ЮQ~lʂ �(�P_?Oj^}Kx>K] MZ]\:Ɛjl\,<!!|A5\6Aa:&8Lڝ֌XN4jT麑){븟p&3<^OL$lLcO VuJ1<vMUPGFԔ|g/SMݭukUkoLioAa5gY[^w+;:sĘժ`xjj?0Ĩ9Jt`VRp`SRO Ō�VFfeW2#KĪue*CKGCURR4M~�M3 tZM[w[J6}eoxj[8ceaAf<p",rG ngPy<p者aEU Tļ jY=7RxGXu}F9Dض%w/+mi ~s5=R0۴ P=ݵb#y#VCɖuxV3 ˱<VԦ>V WZ7t+UQw\A. 0x; Q2^]pS狍JUq8j2"zF&kzOiW'<Eq<ejR>73jmpm%"4VKF=KK79F&3)C<XO Z3RUc%VJVJIS/e ^3O<ҦYO$qy3՞-tg<uycI>ZЍیE~~*}s/역$[g:Q.<Beʾ(�i:>c7{)y7pK/x[Xxyi}Y/frTT5?kل<C0o/dT5T=_cMW|5~~(} [i#kZu<WF}:}_ŚWmMb�KAoEڴ3Mu�go8w<Dʼ=`qy<EarVm7% MAԗ焯V)nGG}x(Tc0e<*akgX<gӛahRtR#RKA WgPIOxzշ<&E֑"#Ȗ:t(DcEL6LL8p9V1. j."p% JF2uXY^AYe_ZX ҔӫW.q/$.NK�_<"?9Ѽ{�Ꚃ.գѴ$]?D7Zn._;+~(\⌓8~X 9)eXYc10ú1Sb}<V :qQN??qTrExRx01Qmʟ$As`ԣb �(� �(� �(� �(�?ǯ� �(� �(� �(� �ٓNK{��S37y�dg~/_ �gyiOĿ%�LgM6_�c�R~|s_wg�=c�U�M�:>{pL�O_ _?s{YѾi5[KRO XX^Aqǧįc>)&2<Oax/ɲ=aq5XhZl zg 4SOySSG>$xKӈs#KTs|}|n %c18 SaVN\ ad#e׼ ŷ>8IcC׼jMN4M6MTM;ZuM7Y }BXN<O6;]<_Oh{hNj4[ 806# S #7^JaK?xKp?3C\gxs2ļ<qj+a#K3 CK:qERa>?u_G Wi/Kd~<InihUIou(5]BWtn;k>Ű< Ĝ_ bqrJ>u*ӥZJ󩊡G*ZXUiaaig x>0.3 x`l&YJ3ϸVqFzakf,:U?c{|GH?dej2H#?Xv͵75 F>z>,xK>~UPb8*7Je+,4j0*R>^Cqi7-~'$,gqe/`ӛQ'aF"qXz0r, G A 0AAz+};OTSѫhkFC{ 7|w#\er� 4R7S-nes.[ؼC Yj:{*`w)p!µr,lxoӂY\ibї5&Zn$/yVÉmCpn ,G5fs*^)aVR=K a~WNOř�iv< (OfAipjDԼR~g3Z<6#,hxtcoTO 掬& 7u%�K 'ϋ8ΧW�TN9nSJJU2hЩ,Ib5g~?ex?W񎣬nD%]5MO x Zx~3=եNQզ<6h'G4o_yq_ympN ceu2Xe`ܱqTYn\)ac:ٸ>6xA>pWGsV;~kqX FSØ +UGgk'A{E(u~t> ~1ῆu&[w_hz]Zi/t_o]ݷyy#f;q?xxw!pX.B2ʝ\N3<S3 a}A֝'<M8JF ['_+8*9058գpG<M<]|bۯ<ڢ²):>^uGީ�[-/u?xQNdW+ԤѼ/{yKinڕ6")K/ңU\[Sg<mZ8l&xՈexx֣ѕJ*EgVRRQQ /^x,/_R$q3T'W*K Ti,N.<?֨r5O�K O$v:{ekW ^M4 6-I^9n]oO77"Xc IL+zhfͱmjkF#gQ7VRQ?O}BɳpFkϰYЧ3|=GR΍<$qq'V+8O|SoJxkǟ�OKox7VmcgOxMummNI1ϸ#,qO/ukO,BwSF\-lZiD}E7Jjqǃy^+e\16sbx2iqСK8eu0XuO ^caat1KK٪_tO_{o�_mUn� @v#ĐxbMKCo&mt' (/Cƞ<e\[e`q"K,tXU1U,-bqjG A)g_G/bqV%feUK2mP=<5&%:jz^˦̢KqF.5HA)`W 2᥌8b / 7tctƓ8T'k?jGU*Oe^xEVk *1UiQu9ahh}*~̞.~'wzU0e弗V~o%#Rytϴ}Y--oo-~  nyaJxUztus |:5?aJ+SSJ0VZ4o~1qu>/03ZUө r)S2J{j^ڣUK?SKX/of?>:?e\5ФU"F:Y5{mL,\N+\s[y{W_lRXi�f:�CG|u�g�ᷙ_ئ뼍ϖ'Eo G;3xe?nu"X g,I TMYnFkw^^}̑_ڸk;˯ؼ7)a8<Ы[ qc'pV*PAЫKGJ2Q{HQ ԡ~gqNYr BPuc*ԡO ^Rѭ(bhN +bo]=c8>$xMWQ;9/M>M�障om*KA(TX}_D3L'7O|CW%uC^:8<i*~֞Rt1`xч+ r,g>,v8F4'ѬmIaqs*3NLS2.YpK2 Oz#!K־&|9) >MdbtP^jWEhG 8|U'f,3ʧ9d4%쨧_LDN 1ucS ]EO~p+OŜ-q\:MqJ8{Z8|_zXZu_C*x8fP`7_[An"<0t:n=m_xVH2%Y29%B8xx_nU1T\ڶ2Y< TX5`q2++jA6aYvw0UL;)WikVN9噛N㇍ s5Q>ៀN x7�|C,6W -r-n,tZZ^勵\D?q s8Afy}N)f~Wթ\nZbsL$aV%ၣF%Sk()p_xY:L Knp*Up<n#&Ngb%KXSi|b̚Ԛ@Դkx?^;K�j77Km Czf#KX58⿎ Fw Rㄎ_e%V ~Xzg,>#̜:ʥYPZx6'9K4qX>qU)B|NYèbSqxLV#k*P•baBQ+2 �4~Wn/viok{֪:^pҵ]]Rip~kxSřx=fj~nwCU*xeOQJYW*8_V>GǞ>qNsˊi,GpS_ *T¶2Upʮף[Bptr0ybV<{[_?c/.L -Ծ"0of)Ҵ�NN4/5(-Y֭(p{<9GxVYSGq&ERqXNiSFRG:xJu<Go[Y~qNuS:Sq? q-: 8 5JX\h`ӥRXX|T0agwW?źuxz@j:=젗H]NTUރ{]XiR[ёG%Ͱ/t.wpUp9W*bs .qZqż=<6YV2Jϒup+si˗O�xof9cgY:,,V C< quqyn#Z*&fpZIʴx?dҿ~ȿq[O k1jVpjvom&,mIm=B {^/gM<'XIaҞ+,5Hԩ\#NOƦ9֌aN'8џ 51qcPqQ .8RJX$j+젪Rb)u*իN}/4O{įblE%RLotJgmevbmowj'ĿDXÎ쿄+Q\QVRPqzqNQ4 *ӥ>O3>><'xWq# C`.d҅Z<-՜dJxXa,T|-*Ԕ[|V_ @!_5m@}gO�risWZZ5 #S5_O�_< <[E|O|.yV;Ԇ?a)buc|^U!:_⇂<7X,qO8k?фsSU�eaS/8,^ѩko0_'w;�$$�nmt*9O|1t&4IL$8/?2\F#*:)�efu+2ĬB<\2ѣ8F_feb&;xY.$,3G/\UO3ȥNʣxgsN^xΤ8~#h_4_g'5xė6 |h"ԭ3o[&[Akc j;G{ɠ'ŵ?�֟#c\ TjORqW*֝*TZ򊔜Ս(Np{gl +3Z2f|KK)ZeK 4xV":4*֝:sğg�_o_5߉Zuz_<[;O>�k_hE&kϊ.l%+k2;Cq^q_ g { br7^ygkXRTuRjҔ*U|4*Ff#ANJ!3 d-j*kfpJujQB_#׿/xC 7sMQk9x̶7Kfڮ֝q<|XXjfnm%+'֫M-eAU4K.e ,E8B#Z|6'Sƽ_<nw}PL ͸[(;<=~G:[ VL5IlfO{y|_?cOOUi.~t^'n|˸bZnAyqayag^}Ms_=NjFq~A>*q'%iNQ8E9хz?XWF HS΅/< 7Ků|}W!˲pyW8TqNL=a`qXj}J (b+-})~a7⏂QOY|Iۅ?[t*K7tRmK[.,lu{MFPljfM/s#�VT0P^w;_\,F&5FYaZWt#|oŌ<G'ߐ`f8CSp҄~VLKYƌ1_ZbjaceR?7j |3"hYͫxĚWA{T0wMs<V^%{g?<F6᚜CXթ .SPa*B,<)U8ƍZt0n s)pS()Ч<fybΥ .R4VF.Vq`Jt*ha1>۽1�|H>?~1{{&iGoEk2}x>tTr$"f<slz��+.�YPjQx;_< ^Zwt?e䢲QG}WsS7XOᾢra3?WoMWӧM.8e-</j(t ]6Z(" i4ha1C{Au5~xṴjneb;eNt;BUp?΍ tѝ(TQxs19Qa^eùL{9)Ž; 'ӭ^|5zuQґ7 �(�Z�j[�)P�ZB촣�" �(�Z�j8R% ? <�ҏ|?oN5~93چ_t9%ӥԷbŚ%ma0;@G<7%/o:XLYqq81o6zYf}hW6Hur<\j`jβSR'ʲ_8g17qox!6SR`#V8c/8YβAU8B*~�V@ď?<u{[x\i4ru eޡ%$.}j Z+*d9dˈxˊ j:XJB8[JN*ac5jB?/qW>�pmFkc&XL%N)JZSeNRСu�f_'. )+|'u:-NHl$wt5Ėqc 2+;_s{�KxxW0ZsJy%z\q#Fe^925*#VX|~Օ:514VC/я}n*O&xZ5rXjSW02ե Vt!1Q +…UfVr߳G+wA:EߎMGMPS]f͌}͌RG;ln&W":ղ/ ׈%OXY&+/mC<<ٮ+ JKC WzT2g^ KFJxU|k~L/x*Ggⰴ1,]hK qyUq,$\4Ӈ$ _3~j!~2x-dXJ+km>sCmڕv7.6vWF/q\.#$QHa0abӝJgRpu+גtՒ"|QZ73r*5F;Y+*UZTm:ti^4ph?kYПʟʺW�iMPlu=1Η:tJ}C¶P$�e~'x;g)UEJ[͞r%O8:Q 5wS :)NQ<<Lyup1]IT#:rت˕OP.182Y=*UB~O~ußO o?�m4/<S=N饷lϨ@e%m_bi'o \̳"8eIBu**&!`SG ӥߋ~e/™_q4\ڭ+b!dtO3T0J.4a0>t8"Juj�Gڏ?,<-oj֚gմ;KGj^0ú%biO4ݰETd5hexL&019(aqX)<v ե_^kF^ǁ^�d^/pgxC+;FC[ƦWNZ%mN6vq߆K,> _2j�4OgXh <P#xb-o|#te^}j,ڴ�9|{r_c2Wf,NUq(攜aЌh{T)Nپ0~#q7 Uea:pdR/z8xsUE3~Ͽ<owԡW:>ckhZ[y?d}CGMyZw2 .;<4:YO ֫]NJ~PЭNogOӌ+*pS?|JLN8 1pKJʞ;}8N\?եSԕ'9y>څKyw}tWGmkm �$qF;_QG B'R4pjU+׫7hRFVH$|f p<-)baSW[^iQRݴ?UA$ȟ叅=cLj˯�-t4rko6zUķ1i/y<I]?|Jb)ᜇ SRxx11ZƗ<*SnJ'mZj/lo!/ÉgࣘV+WҞ+u*RXԧS`P53 ӟW-kmSDլW>)4Y!EY]Z]"EMoO?-ß84Ï2E,4JdO-ЧT: ,㊥Z2㈧:x:wB'ς'pS߅M^qp|(g6.IU*65r># VXZbK%1Zx⎣j~-i~&H:Tu` lݨ9�ɚ K|HKbrkYa?cW^\v_cCB?_Cpkخ48/S%U,tr=ycN['){χ?⎕gk�9uKcU�El#l_S<t"+aQ_Zy5j~*Y ٗL7KZoV>ՇBJ5IFqWQO_ Uc'đaЂȕ/x,4Li(֌/ӯ($,jx%¿v$ۏ'㯶v#4^�.5=b?Xq/g bc[o4TxڬsE)f<bJ`|9a%x|jX,}%1<nO?վ\^xW�~џ>!Oo&_PE�OE-- vmq ZSl`Ӓ(n/m�Ec3x,�q/aT1u=Ua%=2kNԯ:XңpS8Q??pq⏉MS<6kKm}t}cK Nձ8|=<,aN"RjKoCcc?= Y4_ֺL? 44EgƟޗotڈv� x?03 cqg Uԯ8A:ɸSYR_ bgIaeFHNpGfy'ayGu\vTpu09,3Pp,<Z!Z`Ֆ+#J ᷌GFqGcphaQm.{YRC Jo.koRʶ<!,/QžIC+؞2xXTSҔ}$I9(w gٯ3WY <fRԣRrN ћ]ѥNiR߰O=i_Y~]:g»K8ONq$WZ:0_RӞS _[/UO"¶y W‘WbxWVTiNF9yѩ([~}<(s7[6}GN%(U˱UPi}k,^' *8eXjuB=~?~?> oěcxa4UI׾mvK.Hѧ4Vl/!{߽lqG}x,vN*b^ҥyR'<V2bhbe5C/8$�x qU_幥xSac*ԥ},4kJ"q,-xRxk[ ޜ@$k��I$�>4^0{%j?X7O$y$m<Kl1'9xk�ׂu=DF~*_׉_OYŭ"]0׾#m|=o_8|@\!n%C�s6;׵2v*sUK/G<-�_|ix-/-|S<q<*ͥRU(1ܘz?OeO96[KL'h d`H  끜WJ}ZVM+z]\QvҾݕߝdsW[hڽ<=3QF AKծ䑖[+fH.n w|Q+- ,~? *õ k*-%ZΕ:t:1qE\fY R]*Х*PXmyI5 աIƝjիӧFL ß�~)m='?s[UO-;_j Z|RM΢C* ~OqoS,N+8%e\+b~LeL]j'l\تt:tjש,Eї_}`WcqpS^_G U|5:|,\puqN\E 5)iŒZ? �<E3^¯�fH<AR36 &K d֗/<Pd|9)| *ln9J87PsB *ҜҜJ?>,kxOfxx�Ɔ/.S<<&3ia>Z1yUq4+SZN_~ϟ > xC+~oAx῁ޛsgjQO7vwAxy.}[V{;n1x73E 37VTfҎ7 F5֡Fug*0HѢK <σ$Ǯ+2gxw~0e[ Z1*2v."Xz18PeKXx @� >|r_^ػI�ğ v_i>,u? :dwoqefֵFQKW_쬛83!˳~cKfp;<ގ!7U*:֡YJ χux<f/e5|Բ*ˈ¬Xx|fOGU(`0�4%N?቎M<QU|Ec?!񿋵iд؛[@ZIl,庲d {gz y/ _2?Pɲ;PƺrڍMQ^ެ)ըSJs̾ xCrL7efO3T]Jyv\BhAb1&'V'uVN3/|�]Ŷ�ϊmU<?G|x_iCxIJi7:E W֖,0Z>aſIL+8apU13QuԝYsbRѫ^\lF_e| C3<^qG-qMzy}Wdq8Bq0Ԧ 2hUsg|Qj$O!Kc^1aEg,O(n-n-5+;c!|@q,, d1Y}yg̰0XƗ~JF4\=ZS(Ԕ_|.|1fia:rƤV !Z5NuGB8Q+UOh_~j-4]gឍ+_xz$A>zlöRER-ķNO5g߉%+c0G)(biafQ ^3Uus єV0tI*jJs}$(x*gX;9Z'3ܾ:u0vWEa(BxyUUgY}oO /|o5x <A6ZKh"j6[M7zvgdWZYego,%ɼOg_x<5üIqxx ,ɰЩ3IbU*XjZ?iz#~^.Wœa|{y.?4|߈1z8XȨhPjؼN6?Z:x_π4ǿ|{k# Y̚Va.zR4ѴK6]Tմ{sIfn4[Xw2I^9x?Ō#a80>sJUzѼ3JaΞ"2JԜ!Wμ |6n=;85LnKx\4x< ^7 ^\(WSdoW$xR=~xO,'Nj'.Ja;xm$-FO.JvY-+ˇr53,d2ܛ+GEbqs*ƝYCipJeGGYՇA񋊫̩.QoJq,J%R*b5eMխJ 0b:8z�dwO�Ol~oƻ/W+[խ[6<Eج/ũK,6\\ۇ0oeu;`fg`Veԗ_qt^Ugc凌gVSt;#qVuA>sZ8l+|�Wj4)\qS:4qҫVw�~ m' xkHG'›}==~P}oZ6fͨ4s Iy<OY^_b3\ xWXl *:\NU141ԨCWMT<9? w5f%ʰU*`s^/1U,f1,&+.*᱔%@t&g� S?,~|$]fxw?do}÷)ϝ1qU�< KxX8SM3yLd^dC)K1<6?7Snq8 ,?fRc, /}YpyOV쿝? wc/?u˟ \]cuy\x[Ql浸MP:n/}vm?GRi>qft!J5RO2¬&RY*8`gFsJV?>U~3xyf|,['U<=jZ>+.RZiՄ2my<Ej x,s(b)Sn? �ӥĖ^jzlDԞ2A}6>vzD3Ƽ)8Xbk>˳hJjΎVM]^]ԏ0<mTYR_1RwxΗ^=퇪|*_%D|M ?,' {i5Sk__^K)'Yd̋#l/q>WO;Iχ)!+K5(׆*jpF%R09g'8<J *8qm\Ҧ3"|5L%Jxc15:uSrx�_t zLju/Xڭ~^׿gŁ,�e&"$؅3 _]<Na`0�j~.~X{Z)畟,y읽<,̳L/fxW~7żfSԩ(O<]s~K�›�DG'�MR)�E?-�E#��zׇZW4}SB }7YR׳Boq3VS_˙v7},67 >1J4&!5 gR'(ݒi٦ /A> 32e>)Ra10N2N D)Պ$iFJJkׇox@j牵/5~Uo`Ӵeo{bڿ3~*٦:px~2Q"GNR[t?rnMі#1ͱ<_S+_Z NOU~?߇>[8>XS+O ^K"]oK"F9}2'ox2E>3x~u4pӋSфo'_&Z7g.ŏb)>b MU^o0ʫOp,̫ԝCnќ~~ B|Lkj^fV_ׇJr'{=2 6UB2tO <q|9NjxN|+^9&ax`hJ.V-T?ϯ8^xŜ)rfU1y3wjy&d;+ң -iOA2}q><LCxNoxw Z{MPմMf}V,R;n 0$Y%ǂ8spu1~4®/)ԣ^tU,uw*PO|MJ<2̳,q3ɱ\uLR c5,e*xblZB~~ʿ}C(I^$'ƥ]bV qŭTF$efF+o/r(|s\]}_eJfZY)ɶ88/kksk51i|5K'=N68 ? �(� �(� �(� �(ǯ� �(� �(� �(� �ٓNK{��S37y�dg~/_ �gyiOĿ%�LgM6_�c�R~|s_wg�=c�U�M�:>{pLyfo_ىfo�Y,|_I9$I9'\)+%IlVEޟqͿ|g mx?۳,<gXÿ /rBm\Ek?wƟ"\VrkDyz[#<'/zq0PRzCX*nE*pVЊG_T?EߴJ|25؋Kȼ ksgv[jqϧ0$++�K:$Z5[6(gO'[ѣ<8p.Z7a BjT0q?_B RX0r<'b15%Z{WtqkS#)9o[iEqKj>Lӵ2L油k[[M:VuӬ煅ʪK#+"dk }F8>a q8>&' jTE'OV׳ںӗ <C)Sr<NyVWR UbPV(ңRUm7}ψZx�G5OxQ>/۝+A?ë́3yRnMn[!0My?0N]{9J\OR,cJ?q'cri\V[i*Pcp~֕}^tKWݮ{�1�'w�k|j�U_!�tҾ?{<5��U:(? xW�U�?ɟ�{ғO׈�7XI�ڿI?=_x�eE?^qa7үMWџp0՘4?bv[Nb;K/<ZJ`7<,K&'eu$,>ͥ~]Ɩ�T<+WѵVmlvKZ=xkΉ'To]еkY,=6K|k1OGg]Og}2Y\¹ !t+ᱴ! pX-U[:H)'J'*rZ*ќR?xSWXj82EJf9v6bNQJ%XѡZ E:s?-b TxPoW)uH/qM^wP]ޓeiq}4>d%^[? UExgS$pQBL YX*t(7UFʲھ)?D�sLNe|#Sѧʙ6bkv`P*r 2RiQuLo|c/?[&jV:f^SCũhtm*-WQ+|?_8ᬪGZgyTp|=l.cqJzTa:uTHJtkB~w|ia.9KaLNt*eբIƔ'N'R"??��L�&|X�%oK?<�q_g�?]� )+Km<'˼?ҥ?|o#dqk͹QL@$.lF5!5Fp1P ^:0|SI)4r}}W7-s7tjCݯJ8o`FyL1"zjYg+O/.gi߉ ߳m5/p#5Yrey � %៉/>QWù;^ҟXMV8gUPR\}>0EgxY|5`c Wߒ<%T^MsIO�j6Tyͦ|?Mvpɬ:T[w~t0| \V+=b)wçVnqԤBX~ux.U tg7(Sˇr\FQ,~+VPR)}�_&| |*#GfkX fhXchZfmŴE;> L�gaaUe}B⦽Ui$ҔkZ�bIp3,2taRXju b-S ̺,J%ee$2 AWҭ'5tiײ'J<WiPij.>3ֿ 4~1~k'VCXoF�)f23|϶n_oxM!}}8K«�fZ+4֋dGIa>O�pc=fuܛw:'JP7Í,B-ͤ[FH^5QWlPiHy%B"Dt?.!Xa.eNQ|LZI9yNo `aSJ[=N Ч7O 5N c�%|+Đ=X|abx5zNۅ S4r�ڬUl'=Nyeϲ.g<ܶx>|*߶(NJ8�(pq_597&`�zxIAS0VHN3ZvU&JRRC vb:IkEe FRA$W_)?O U&i5 >-,rvv|=$ѦI."y ֓Ȳ$G$$Dh綍"I^q39(ζIK(6p33WI=)RIw <:E[ J2TOE:Tjrƥ(J,_&ƥ]s_qi'IZԧьT^Y c;;>�_ �&_S)xb<|<+SF+Qu1iF)beU$cU1^!q^2cigG*x>*,6rx Ji\6ܥ{ǟ'B4i|R&<_£ǚE6Mu-n)5[og; �Z.CN4FYR! 09,-EVRa!RN\ԒyO? <=q~&x dT9sf2),^:tu*�C_+zw/�Z�A _iX�5Z3~n95/ڜJ:+:A7q?̊v a27un\T8!M8JY*I8eYڭRͥJͨhwU,uE9F󼒞.6ҕWVnisJGY k?<yKq5;g}1o9s 0tp)aaNxg5:QT38n'0̼a?ά0>xS*Js+�f��>##}B_ښ|8"_˦RPȱ]]C5qgn cwW|q|UTr:;b!bV~CpӃ*s\\ܥZmTZ<~=gxqp#13|Zjq ,e(ONJBt04Ц(_NK$"_R95~fJx6_6TT˜ cj2E+oRd2�&}<DW͡С75zθréA5_OEOeOѳ9N&aөVptpu*ӏV&|Frs??|5V� 3_G8Z734TpEYWel/?URTYFJpSMIi9't?_OqkzOgVxjFT:tܢE8~�:5<A_,cuuGE A.%WKN� qyv ŵJ/�apT'yЕ85R Jʍ>imSRY*Euu_;Ԝ</[ L-O8F4*O0zJڨ:_v�@T�s~Ě&_[Mip-ľ)S�gV,@Woderڑ+iFs y*SϳVLgs4)a0gVNug1sհGtԡJ �I~�VW�Sx�dO=Dx�(Y@?__"G�Sx�dO=Agg?ZQ��V'Z8Rׁ�ꃌ|H��?,ǿo�~.إ=gq}rɒOD/l-. ZiLef3kd<CÙ2ZҭcS*֥^4qէk*Ǝ&Uq+B5/Wxz\S�a _eahV毇Sj&+9QWc0~zM߇�`m.&GmtJ?Eh!gc}Ų:.�WOQ܂tjESXB5x:1i^d+V*QN?[ɼ&#r^#RӡVr`qPʩL�C էJ3R񯃿o>#5Ã~g5ծ4kVJڣpuXK[<ڴ3Dg\'ͼf>0�VylhPXZqWpu!QN5(FTFpR5eYGPˈr XTU&G#*5IƭBpa3AO�pƯn}%Et:|D6p$o̓Tt|E*E᜔eCk 8d1Wti]' I;/szx*M^#XN<{L g(m:O8{E(PXfRq和큨kQC\;ߧا#_f,-8T[XX"/~a3*x%AGZ\QRpq8ٸRxʸտk)9Jwm\U%>nrb0}8Vn<dSQQ?@c>�tziu}ODYYGo LH|MGL/ ʲ.T~-8a~ 1̫ 8%/sNQf+V䋊v5T~<Ƥ0񸼽NNTR(b)^XLM\e+9E3/*JYr kxEa8eb:$EW)?VgIT^kO?"ͯ,jMI5iZu?7?gh�h#2:|g\J2C*2  )pI8˄84M<k[z}򯅲|MQn2$%{M5fzc*"ؾ)eUS'�.B0"&NNQ�}[~剶ͳe;..LpI#IkFUDCz.Oh>]k2,3\k:dL̋^i#:*3,_f&_c<c*J.qQlDcFMlsL,)ζex &ή;Sۊ^8nQQm6՛?S~%|e#¿~5|fs隶�OXx{Ð_X!=o\.?xH(x)b?2~ap8#X|ϖjt0P)Jv*t~x|OǼwǵLnepa̮&,=\Ng[yUx=&Yf ju%'k񟅼 /DW5MW[Mm~$|1mF;KB) ,$(q=413,Ya7U`|=kNTҡ7ZG9:vk٤-ҳgcy[ì>zM`GG0\UOV5h8zRT))*yJ$� }_I ��S>_Wx�dR�՞v3p��/�k_|(ϣO~4�S�'?H곃O ?ej5_K _D߅|y_kKg1[>3=ٗmevz^xx \;gk[fa ˲l$F=YµI}f)*ZTc+ti~{ោ9⧋<WeY>C_5 t+NV# aSJTVZ_,E`wρ?;>mŃMsicy&\Yk2Gsl Y#.񢗇' kF_`ƴt1NUPJHxa<;p\HasN&2े]^xʼn`sJsB2Ty)Ư,/)⟲:LVo#KɣF茿p,Uxt&Q$Y6V.x2wA˖\)qrMi�a'vڕwB3MQN<q1xʼn`c(p|LdRb'8'?kkRZV_z!Zn�3[pZUe5 x EEbZYJjUݺm/_1>/xS )MƆ8t)jRRNQ5c?R|50eu<#DB ijLLs*RA\7ScR8XRo5ST5 S PRn+ Ácet0JP5ՕgRZNStE qѼ '^7WXtBMfRuh�l|yx1yQhTMx\UzPa?%NYGB,<8MX[ib)JbbNLW^+2,Xgq\5~b�S| SJ[9k omi>(ihZ [xR_kF+OFw8yW':\QV:r#uTkNIt+a`T!Fu~<;8/YL�x Nfty^]i5%PToS,JNRԡ9ʽlL)~cWq}#�m/Ï~'ĿkK k5{eEM/Kao%s\NYW<AX�fVURVPEыZ:Q?s+|L8>~?x<ڻAU **~?p|V"q8S=G~vx{u* ?tm:;umm#oś\snBe~07|F/ ̲f/xqѫNSK ʄZjʎ6TzX. Hs ,ɼ[LEO 8 -zRTq4h}7v#�~>[xd3$J j|}.dA-ٓUF@/ E)Vf2]E_]Fkne7Ak <"X^JN(UgIt�]!K>أ-Sm>Tn:V`4jJ^5*y~,K7BU*lN:QR3V]lJpKeXn[N2~Fw{(|QW!E;���Mk� ]�"짭�(}m'|u#�m/Ï~'ĿkK k5{eEM/Kao[s\NYW<AX�fVURVPEыZ:Q? +|L8>~?x<ڻAU **~?p|V"q8S=G~vx{u* ?tm:;umm#oś\snBe~07|F/ ̲f/xqѫNSK ʄZjʎ6TzX. Hs ,ɼ[LEO 8 -zRTq4h}7v#�଀/Q� ~ � #' 2rp��_ޜM%|}5~_QIxZĪ�rK[GWN{A1� u}�[_᳇�'oqy�`f'hK\` $* /ݵ|SM|WO˃1.=vv{.�~0? to691|RAIdQ,0c1x358Y-%xe5^x-> [.hjg?O~ʿmc�ho^y-%7)yLWV9VPye>M>\ 9UYKBqq*n& RN5UB=:;WS͞WO<CT질ʴ+GfQM*zz4CM��?gO ǃobZ+ZM̉z+}6m},zeӪFߓc8q dx*YmVlQUbFr)ORFaEh8gG]b# <'R2PWYb!Ɩ>+Jpi(?gO?~,R]~I>{Vy:nOd S=խŸ\5|-)q0p3bTtx|F(קV J7:uZIҜ&3|_ip+f9\GB?S`kRU8./yaШ uU:'(UJ:/�I�_ �$ֿQ_}ʜ9C�G�dV;�U~;W8z�j=#ՠH'Osakk)ip)]J9o:3 �񹴸482ˆz#ϛ Zץzk4ԗ%).WW _Ĭ,pYOŹt`c\=uСFP=hE_W[㿆l ŸpgYxMӼGuC bմǒ ܺIwo89Yf<)'9upM\F_OU>Z֨ZVp95~Jz%/>cxЎS\e G UEcpxYYVIsMOCr|OZj HVH5M�\"k}!z6:UGOHHrx<[[9j|# ˛_b`^QLRrLUxn#npOC~|GpNFhY/00hN, p9iR 0>6|G�#߅?7Oi:NvG-jtkt{079PqGĸ9|3Sbrj4UUYeWS# ž[�_,>?Uqwʵ?ay:4ڟ獽+oz7�EGK�y�%�&?Ͽ<�E~��I�_<��'W\v�QL4Iqب-lb1YChѥ+HZBߍp d|?Yಬ.EZ <8\<WVn*jRrJUch|k~ ?֞*0αKSa00<Ua`F(JrX�S y? o>xzŗZl%!Ӽ91ozIQmg?YϤɸ IfiҡAJxiR{B*%8ź*QGC &WSIYW9J0qI5iN*Lݞ'�_|`gK OŞ-1LxsN=L6Ht{*رEq_1Å8G%:^q|6WWʧ,~"TB1>�u\o_O ؼ:ᢳ~H+ӫb.H퇌Z3 |U^񆧥Z.]^/Z7Sy#N$>+&W2&"=cGqN^[ҩa†# ^^Y<E\yeu (˜";0xC|,V[:u18\^N+ G5ᛛL</<7A?4� k^t=oXOjڽZ[cH&XR8!(EE K8ju|,3W<.iu%St(Rsw&U_ѣ ||:9:<SҎ3KNTiG0iExԬ1PSQIZ?Bռ)źρ|9/<k</=亄qu$v%52@4)M#Zop%ya7`ps,B8e:q|Th0%ZsP {(?xf<Ccw+Ib,^#(*W&yn_VKRu'ZXzN4IR-ܛw?Va0Mg�U_^4?Ƙ?_`쏣?�fqZA@��P@��P@��P@��Pǯ� �(� �(� �(� �O^$|I{ T{M{Q=4]źF^ 믲?쭮.x&65nU?3/{|f7*a}yӣOV{J)҇75I Rw2/8;kW+džs\W/ΰXec[[ԩRZ/%*s�q.$DŽ}['\<3M=?`jiyfIw_~3Gĸ֖eb*a~rb918;玼�pLj>*IK_)ɰR0Oeճ<& g?wT# oNR'/�h|S")Ҥ5sm A隶|ǚY/K8' �y|/X;ʳ?mbpuȤYNW#+3o𷇞)a3O|q3YalN*U WS\ꃧ{ӗ_/>&xCn>34"z^=J�O5Vv�h_"."r,N]<;)f"0X<{,N/ЯORT GNV愥�f<یx5}c6{`k:b8jʽ:UJ5!?gZ:NR~?|$'~ <|/֛D6yoJ-[Gu=f}Ցd�𿁸|P[y78.3v#봪fT%|..+ za>\] <7+\Q'$_3 dpn"gf_<p7b퉣V6Rpg>A~�/$z.u ri:vjhM֛jzΙhFO-J� 8oPb{>$L­<5 u% 1kWa8bj'bpRbpqXOr` ^|lf+)J3᰸Te^ysPVUlà�3~Z牿:gcUӼ79t]WTk{}CVm#Gݜ3k7s&xߤ_bYw.WvUKqas,ʤ0PX_VPB塉xU֞]Nfs6w[e\;<3')Ttbq +P~_ '<N+XXyIe"HwH#i8UgVH,HTK7cIQNrQRJRPQrz)u%u9FSӌ)F\iŻERW)4)7w/|qg�xT𧅼ea뺯صG6:q&R]Ggi<͟3־2/$\3<|?9AV~j୬Ҽ <Q#Pɲ| qWbu>U(PQH;3~#x3?['gxQ&?m?[uKo* vP3^lax~7_ c ϰQͲc0 2c4sPJw^isr˖jp\iq? 㿴<cb|,V0Le.LF;Vr犔% Ow�O��c;o$>"WmǺwQ#K[_>cf@kaO4l\ \)~-fW cr<GײWp5KXMl71Yc)ЩU8T?M@\o�' _ڙpLٶ ;,00L>2O`*~B)o'a�|Yޡg?|;kxмAg#k:=mn^wѤxrOI++C){.K\>.6Ы+p峼/I">xG>u(M Waeٝ ?2M(<f!~dT?ioeϋx�é k:=Dža/o/Σ5ڛQiwzuͬW }Mo~k-u�+%%X<\3<+FzWŪMTxLM,EJu%MNtgƝ_eρ+� hq#+s5r|/^0ؗʲt; _ J(U䧈AWx_\j?" ߏ`<wOj7sj|1ޓ-M;Qzid_j?nJ�+W[/RKzPC`~8k_l=7jԽl$eSRt^+~g[Y>I<\JG:ueRȱuKF"ѪK۳-� G�Et_ <0j!SXwkuPJ6Z}7F;!1#%0"LS6ra5C JO.zFkʕʕ(U'~$ɣX VG)Y.DG7WaaεZΎ5j7_?|7OϬ:ŖCqo3a66GMΗܵڅm,4>G|_A.^]9|0%lf <%Zu>SS\?R^I3<E<,7qp^iSPxcNlը<6]Vsz<N KAΛeAZŶ{.[Ꚅ>4/o6w,z}IC-բ</Q⍁EOW62xxMT LBUq8Ҫ3Ԥ|ž?K/O R' ӎ:r9Sj*% BZq>�jyeZ*q]1p5L7wAgzZIse彵 _Pa1t ɱk23I|JIUU'ajэJkbcNtg'08ns<QStX7'*8/VTW*86'jX"- _/d/EzuI>�#3Jxb\"رiЕor.&L\K7.-~/yL3EbmHګTß��Crhİcxp33uo,a,/ݰU_cTquwm6>kgh iMSDѵԬ˩Am\ݥ {=.:kƳQ 0˜3C<x|_bBO ^*~ )UZ1j *p?&ƜooqOyO P`8;JV/+V/*֍)QSpVt%1~�.~^1Ú5ui^|cgf$nJi]-FM�Ik%t(/e8=lS1eu gլNUiѥZt#TTpUb!39 䙧ygᏉ^SpYn'39FNB18'ZXK V<i/ٷoO>E|T{u >wk-垗o7s\upiVZv}éj:p!xY?xLg =8MbiUz5q3҅?ңʮ*#:pP~*Ug~N#x<ua`SV<5zJX. J[B O_OU'No >n~+xX|a ÏmsGUKo}]F|;%$0_{Sn _70_^˰_Wºt"0[IrPujik|sĞ ^2ο1K/fZF&N>T,ALDiRI5͊?ҏ)7|cUKK,<1M \uV$Wt}kCqOm>hpV~< < qVr~(}{.{l2F˱xSչ+JNT~$gk p˳ ٶ]|cNp vԽTӝI4z\?`|#{o>)OVwGoEa.XVn̷+aeq[vê$CjZ|Ŀ >μ-2(>&<v;qX ^&2aM׭ ,jXfجIapؙ}e? Âgi3 ~G.xfl 8R/ XL.jUSѥb,f2O®_>B{*x^_7<Y[[4-ºZ^Z,Z柧אǨaoijR�ijZf8+ď!ϼN0<#+aKXԱN*ʍOl)bkL>!E7 9f<u\m\ea1v&˝T2=|.μ#S Jq*k+C =>?|$w? c|KNmCjY,lK6wjV8s�/l+GõO9}{.83ğձ88E{I|τ~$W x7xؚy]b'S'xX/Pυ~|G7~u]gNiAk{$R&sj+#Kcwq3_ڸ+s^ͩ~o(TiRQoHW<E 4kRd,8Csn|2:ic㰮iUy/ ZM9᱘yb<=zR'?OY/~*Ǿú}bNAa^i3޵[[0^iwZKΡᡨ 7=IOe^"pO<|q0%*EFt+ibEUΎ& tfYя<u48;qh�dViVӧE)pulLCJ4bS*^כ?o]4oW_扭^&è麦 o&co-݁,KvLV@i^Χ?`|\YxX,f.. T榱ձ1th5qR&th0xx7cV~,̳V"?Z3U鼷,VùF*^ {a)ׯr;Gg5W:,td^ൊ[ 'Y[]CM<4.gMN+=OL{;{O3 7�en!^Yʫb(Дe $N5XWR'psPUR6~ை^^ o(gfb!(\N'N0ʕZѧCJ:Ь7 3i?W?' G|7hj\}!xtլӡ帕>զ6R絼�S9'`)ya%JEНGNt3#/g1?WQU)NJpwx෋.�W x|kie=%Z\-:I՜Ul-I_pQ8O?xo �WG_ <Mp!Ù5FI.5Wn-9i7<äj> S,V$~Mf?H(ଛheX !as(F4 xт <z"?ܸ*%5'xyͪ<ψ8^EؙJ1*J5k'*)xe7)PQ(Ӈ?O?eR| 6u<G5^!{q4wj7\EKi!j7nroFhFg_xe$gXlN?:{ pp9Pe9:TzxFbq8V&gYτX?xI&YLÊJf|WR5#`J/m5jc8,% |__i?˟牼-m >1xvX[躕ڀM>}*4GCSEӵ++8K%xQG.̪gԽp>,%,mIrԭ֯PrV L>*6UgO8lf# WN8/QG*9] }*~ҖapJ~ѧSX>b�fĞ9{>ީ>tEkպlYk-Nmos~{ƚvM )Xlu 5u(BK_65FUiIbeR d<]? \I.c5yv+*2<.%u1\&3*nhV*pWF'/d]~/;,ngOyQ;~Pb.S>MkLb4R{O|T a2LjyFiN#50U(RTt tNJ5hJya)#்y|o,[>ɸ|'|S1,Gש*LEJiBaKGJW �%fI!Cx@s�k]|>Bp:ݷluዛCج7[*)b85O<vl}V-O*t%iYҥ -Qn]?Cvk>'we]$ 7S,%`iaԩկ:pK7o|S=7Gÿ;ry$Gcks_Z--ini~n x7uocwq>Fqj*TPe ؜N#tMS@�+g.ڙ'vYg uU:uNQF;FO`\j׎ V׮H'şok)]n^D_#NծOr"ŵKx,8β uO]N:8ʙJT�sg5)sOYч?2<cOO12ڏqqu+iexK6׳UX~C*�v}⧁"A>&Ғmu{rm|;V?mȋ׆r8q_ ,T%y*_SQ˱O6s^җ9>$Vqa̷:s;6<h]o Y^"Ͳ_/�u_WGS�x�<QLt/ `_iV3aMu'mi.<b 4g 8G q p[b0Xt}&QĘXRj^? Ot׵sPI@<IK5^ˍs~'f9vKO:Y'[FyM+au_g_JZ"~9Iz>%ͫE_[ψ|ZQ{{Kct[NPN:.a]%G^aę>(Gxv0<qWR*4J7 0񯅪+ Z%W?8#+&\>1R9N|E\<+,n"x\m1lUhR`?F{[^ <!iMy-Ō#S{S]-̓چ?9cy.#_M_NO]ž81 DaF֛ƞ:hR1RѸ? Y>q$%\nCe&' *PR(TEU)Σ*{)xVxA�o{g|3+^޼6爣a<+}xwJ42�Ux v%:j+%/ seuxNO%F3<dٝ nabpZ*sx<G2T*I<0$?baùNg[O fqSd+<b%N^FSR8Q~5oǿǞ#ψ> ՚ [Fռi"+=F-?^/˩iװZ]ŝPJdD)y>˂L8ªj L5oe^P|2(ԫIWN9 ?-xUOh'q6Ct)p8|5xaPf8 GaqxlEZnp޿E�_5�٫➭ei� MKĺ>w[e-JB$k+hm`Q;k ./Zτ:+U kV?3ocZ|mLh'V ӭNR?x32Rl�s xO+ful%*t>aIb% 40B*iU> xk $_ga~!H ņ/-KkKKE?Lt" ;J]$_EcoS C,.C h΍J ԩFl:x|>#K B|c\E\v#,R)W<Zc[bn*Tb:2Tף?JجM\.B]0|~I;~ş< ڇԳxYؗSKtm7u #}uSe9_>By;u$-G߶]{K?x+< <GxlM<.ͱ?,x F |Cwp|M{ğAxKox?@[.Ťh)ҵJP\iKkyvK+"7a%9ff<;0t=:^1X|=/kZtBҬN89pgx3ηղܧ8w31εopWRSѥVyiӜ`}!�'�w'|'j:^i`!tM<O2K5O )g,pٹ4S 3'(18;熼?SLq xcĜ!�k K,_ Tgb{ҡKzru>O5/9gt][+7]CzgW$&6`Hy7rpWʱ2oixƝi8][+kW CĘxr xGB9ZZZ|٭ _N�\~7�8E|uojZ[K_o�i:0*XA7WP鶶RhZuN<F  d\_Cf1zh}jn_k v"%7*0TOR(1%8>>7f8Oxx,Y}NGԥfZSQSWW G<F_"ӟ|Zj4i^'<G|I-񶫬执_xNtحR C%`ZexB8Ll6&wq} ,v]t|.Q)b0aS:Tj+ƶ"|Q߁ϣY57[m昜Êu[?c9u| +aXS1zaG C'/%_NM:uׄMfQi퍄7ΚV4k7+"Hsx?Sd, Zi}[^JT)J'^)Je8;\S|,~+g[EqxT+b]_L^UµBpj֋B(h߳WZ~?o~luy].;po�tM5S"Mw /UYIW 3W?V$0ؚ5aOؿbJ3߾IFs>𻇳 x^*'3_,˂j~^7)֧M٤͸_,?n5�x x~ڶдGcwk.ۋK.SW_²k�|3¸&n4cxn"qW̱4Q<[J$x< žkpwx{Õef)f8~x=|.c0([+ǀ�hٳ߱MzGxc{e=k%iŦikz4k�CԥJ_w"-x*8 !֣hQPIu:upp<�uq9>o*o߁0cs>e|φ.10R;5V T3 MUb2TʶJi/G !|OѮ!|Om:� {z# ȴ۟/B|)i];DH5׆y]Ӆ� } V],$o`xSa8,M*|e|֮,C)ԧG[֡~qg.-8?2\qXC ɗe|abaT3 5gQ|/0�8q/~>4oQkVph?oaմm[K/?W5xlV@`[YnxIcr^q>O]C- \NC MJF; RjHm^2w nᏡ<$}_fy'<%|6;ӡ+uhR:33�^1$>=*�Q᱖�O4 K{˛khcjhէLpǿ p|A ZsZy/לBz*:e/cVnt)f50�@N/nh7L*̨Щ%\`85(ap}liRUz5h'U|G~| 1i/Z?k~9*M> ^ ŭžonӮnoat}#J�4ifi⏈~m3XZ4=*էR=:tj)ң*kbxTC<`cpFK࿄lwcfC.&Ա]GB=ZxgZ|>lD0p,NO_ xS?R>5<1h~$�B6GKZ4^)|h#3<?C)>VE}Wzt*5,ڔo?G'ξ<1W<�l?a 5)}wbmWF6ߺN1K;}:M|CwGKMCġ(Լ[3&-jڰ:qKqg?ּ)}yg6_;6̏<)SfppYn6I1.;µ5OwZa3:4b"q1w:'?11y.p5K-1˪UL^PT،M+Rkkc6VZgg}wkioe[[$PjVm}6W*ݥJl=jj:hҩW Zt*VԜS Vu(ɺsj2[Vg/|e8|^+СF;OKNOB2N" U V"0j4)?y~?gGŽ*QMv~ΙחKXiiy5]$,uhڬiqmwaԶg?>8kq_ bh+fYM<DJ)VZPR3K F_!,,*R*qgѻ4✓pxWqUC)*)ԯ_P^t)N`as\†28^ld#Rl4*΋Wo%�rZ'ψ�&_ŭ/;hv-sӓz\["+by#߇uݬ>:/#)38Np9B؇::|ԣ*Tj(0#xuSqO&C`V3¬r5i+rV+b)JQl J c�� /_v>G#*nx^d~'4f[OF%Q+́Y%d|YSf?FYZYn7Upn595V.)~CcWdItxOc�38g|(f~UUj9TeUaFJrqR?�`yo>^Ob'vm;vKck0a,&l5k<j˨hzcRk/x[O¼Q]17au2ڥHS,퇞&0Uu+ Qz 7> <1>4gYa]ql/FuqqP᧘hEb|V/׿2O,><x_u<_|�!q-bx5xt uΓvbVm./>fa!o4�SNel#3=x r:RScXqU]l.."|MG s۾_7G%ᥙ?xWV'ppX U j#*Y:+ q~<G?G7Ư c:d^^i-bť6wwOx\բӷjŵ݄WRڵ�Ҿ8kq_ bh+fYM<DJ)VZPR3K F_!,,*R*qѻ4✓pxWqUC)*)ԯ_P^t)N`as\†28^ld#Rl4*΋Wo%�rZ'ψ�&_ŭ/;hv-sӓz\["+by#߇uݬ>:/#)38Np9B؇::|ԣ*Tj(0#xuSqO&C`V3¬r5i+rV+b)JQl Jo(oh/x|Ui^ D�xCIмipxBf[}v5/5]W/^⌙Vq#cc08+*~; N.*NbI8/8C3s` ,e˳0nT\R̗+ƲNjN \¿ 㿄g?xoLIbRk^1sc?:=]OmQCq]�G^ n(⼳+1N72_Vr<5:-(Gg:4N,<C�Es ?'N#e0q2f&|l~PYNHC_ '+o]Iû ũm'UkZ>M~זJ1> \6GeϭaaS2:X/T|Cݟ/ogĞ e.οp b2?ٶXʔxL 8-u0R\sA`? > ;Ҿ'x��4 `YmMkGaӶM7w,4G\ <ci̯Fno?eOabrJˏᧈ0xɅz9AI}I`nu ?e<R|Lv><='n ~> <(,9F^8%XŘO5 -fKv4o}6͸ </�eod'&2~ y{ѥVT+a+URfQJ�|]8qL <cB x 7 4/ ֣S bcjJb0e_ =1 ŚgP@n^{on[n,tbx2[d(--}V�2~qC><45V? _-3ꙶU)aⰔmRR6bxjYB_'G%ΰYf7|3K$)V/bEJu!*NnP$񔓔ϋl/iߎ~"i\hZ i|IA%K9V[dԵ JY^0^~~^qʶ#h,<VoiTxzөR1YRӧIC_<N^-qu\- .Y1SJtԫ[Zԥoµx7ş O^x~/{VW_<hlnz�eޛqi֐Wpv*fA-x_OxW-ƙ,t1\?԰BN5qj9&"J2tTvq3xSÍs.Oxr[ Y,/Tq9FT+`V<U%u81W ~ǿ~+_ǿ x,h^o`lm亴763 :S5M6Zѝ̶_FmK|Rɸ#a0եR0փj5pؼ6"G?"쇈|4%>dTZx^ 5XzRAX|.!3 O0Jx:U}8 �\=x~ η Fn'#j|<ybܝeIȒstJR�̣Mf6/DZ~UO{+8}e^9eyHE{x̪XޏD/m/S?m_><-7C֓5JŴ{]Bk^g&y5{-BUg-m sqcC2N,) a0XzVg쨪b9eJp!C Τ?ǿ8NQҍ<cɎqTpG:^aa9ש,n&+{* ]gh?:Gga?<s{k><_cfMf;h3sMt&Xq%๎/(>+"x\+d԰<l".C?,ʝ,M>z8C|N99|(lW577ψx50j Q΢܎ :mE �??xGn~!|%Ru䮲g,\_UnL~Yda*\?;R?'a4YA,lO[ c�܋5|8_w� �/�5֕m�Z7|/}ssi&Yh~MkjũxIᶺuYqgq6s_x; 个춪3*Np41*�kbUUS a*NṠľ>=kĮ>jS,,7[h2`(e?x7tpӎ+>8F)V?2k4L?MeO  /|w"7> _#]բ#x3kw6O<=}v%( k`,Lr;8^&p<�jb,aܾLWe,^+/X)Sa/B)>x68̸CW-Jty%ṛ1!NJQ?MFɁ@� dᏍ|Y㏉| `QܺvCif`߬_Fu^Ux;xR|7 ճgײ?ԩSe V")vPIەEg$W -_ g8r^�ZEӇְX F ͉ |N MJgCa@��P@��P@��P@��Pǯ� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(�r#/'?ৈ⟇zxoHcK!KZu̺Գ6cd5=B})5)v/nL>r-0pIq,(qҥPW'<0+S<4+9xGxc>2 2ำrpѓ*՞6i^X>q *4'.xc{߶7?|a/ " !|QŲRK+]6kVIqwq)ЭKkF]7DmSPV#x7{<s [!ἎGaQN"u(aKN&tⱵ1(Ў#!~9os? g]>*q7%Z+6PG N#*RY|T b2ֿ� �(� �(� �(� �(� �(� �(� �(� �(� �(� �ǯ� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(>2_2#BK/|M?/u[7:~tutM]./H^[EϜJ~7< 8#+s�\7¸~a̲5fyQ<&/QW K08RK'B0wq|/_d eY'Ys%X <FX 2tr6k V.!J<U\ (ʮ{tNPD(� �(� �(�^woxS�<C{^ ]L# 1CN$ƛF K"#q�ex/!_p1YeRpN;K RVjՄ!JQR8}OpOxx[,6&, W MiJѥJrVsrNU'KIlk kIkͤcKNYi i㹐ܩ3l$͏%VB81>(b鬛Q( x:+,6.FL-5UbJt#̫|lNUa3\FG[ Q\®UWAUq2O EeZ&Ҧҫ ҳ)Qrw_ F|wxo^VڃiwZvf_K2\L&tfIr~x*p�x;C6SU`/R<f*ꋫNJ(*q�'sՃc*agźVZ8ZYRJ[UpuiN|e}@K~.񿊵? KeoDޓ^喞4Ts[^h}iIicݵ7#x#n9gK*X�3wa vbL)1X1(կٕZu1~Z内*9!wqqFeUp^+d;xҾu,'63&`2U)"U)病!@��P@��P@��P@|χ>)hڟ4 +]mjMu _c/t~}d-�Ϥ[wy_fUaK9F+.W4eQ l<X\jjוLVX\:k*F>$xwGFeYo xay0TVe,EGXlN5Ӣ㱙m/cpquJ(xU@ÿ>Oῇ~Nkk66/kAټ1\]=Ѡ5yn 9'4|J_?8ȱy$֎#4½\> RT0؜EIBaqBG>ώUΫ7| s|N <-)PpJ*le|6lF8ʴjWW ᦆkyH'I!QbE,mHJ:0ܬ khWGNJz5)VV *EԧRSe)&Ljա^J5TjTҫNN)T$  IJ2M4h3 �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(ǯ� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(i5_[c<sk[m$2?Bfr](iI'}?|{8Y3VtÊ3 QLN;1c0G ҫ:P.h��GGrlVo>,q.nJP|98]YS`)bR>YIΧ$?ğO_'5i>%mY5λPuDϺVW:<sZH_~ gqpnq(p%)a0?iÙv[cj %|Lp?˱xQ̡aiʲ<ZW^/^U~q~g;9?0Sؚ<'ƭz|x|L'K}yҥ/�_5MkJ|itKkg޻w[IImme='W) TQ�ў=!xkyp\[G=cx}<pԱXnjOC+S+:%*P/<s ~ኹ6^n81Y- uLSSjdJIPc}POqskV~&Oτɢkh~*uMoV+k}6DL�Lq&(dX-&qktܓ%}Y.Se3L~}¦'5 `)sZ?M\ n$𳆲8W-*dپo<3|92̓ `lU<Vpcq%\uIB49ϣQ&^�~4^�5/F:fHiec{]v+G7Z,teqye3�+QgeYcSN53Hcq8^5\;,xWf Д(ю+|-+f<mq[cž!b^[3ʲYkWsQej`WRW� ez xw!$֒đj = 6SЧgyJJ:NvB|�Ux=>xx<;9 T9xfUzu8aYV+=a0ҖtK Nn<[_^2 p}�)T,ҷia,6K_0WjA>J/|5�ˬ⏆ &B|M F \KI}&Eӯ>oU #7=_(xS.;ʰT2�w!3Jlc3.`'W؏yE\D*,qT?gߌT&<]lu"\�0gPW8>Uɱ8>_?P!R8<^U)R>׌/xM];\9Ѡnl5->].p.)H^Aqo7~.p?xi,!̥W8(K yFm U SJ*ӝ^bpؚʞ(x_>csG/βN%Bq8 sib⢱X diҩ(RNp5f7|S᩼wׅ 8cZMKh&ّi}us7۵Vzcܤ֐Iu7}5<$Oh_ Ş V*x+üwѯ+ V3V,35C_/W/>'Õxގ'xN:|_yL,ʍuNң]KO ZXV|l'|城/o|/�_O~26.Ms+Ch\ZY\[-Ѵ0[.LVr_Žxn&pŜ &lMN 쪖S(aiB6WS 3ѩհX ѭOb!3<Jc|kp&6,<8s:[:8Hf0apUU*4xzx̱b,$W|.<us|}᛿[h w5R[+ImK 2_3S,O*G¿KrL;/r'7WTsL%/e<m:^ֽ\,`|?G7_I&)oR3:<v6,;3C+Kت2eITNEA 2콮c�C u&İ:y KO';'ƾ,ԍeCDz槓p!*U˱\>5M*<3;_FlG^<Pp0CmQE}} A\!^b~Э_b?~؝v5S�xoGw5Ė(}7P޹_qֿH<pYQ{ÉoCSkM0MV|9~3u4⿤?T^xܧ< \;a=T𸊘|]()P1=Nt1>z.~#~5-6ľ=\vEJC5đgZ\[-[Xiv"k(d7_E?4𿇞p ͱJU\$C f؊ةa04j0tpxyխ R3o1`W׈9|N+5%MJ?:UGf8ejXlYUquJG�q|Wdm4|E6|pm>+mxF-2h 4ImSBq67+9lx}xD$ia"sK/dbHe?H>qNa,SX,J7/ko<a|O~5"G}o۽"a N{tBqk[Y&+Ye7ܛNN�w8l|?lqsFWV&ሧFa1thX54jSmSqo3n<+)rJ:3GFF[ V֫B:~f_<{o^Þm֑xU}XԯbL^+Kۛ}/Cy|ag^C3GBϤ� xyGŘn'/<Z39^�2̿ ZmB7˰U3>Ib1/Ze`U*Cl\}8�:q=~cUK3Gk8M*f]*X\e|v2QdoҜbVp@_?gItY[.xfd|O˛.=^Y4{y-.`gk+ۦ[1G=ԳaQy¼Ul>yxna�Z' E,fGG#/M[dA.2~%^'чfcኧ aq4%9`ƆOOٳǩu|YYFY񗉮'+pkkw5A$sMiY\Z[[J7ҋ_,w~¸*yfj֞>U,V:С̱6/JW<<|~~'S0n e9q&.yG iю"xjbb1pRj\ V' jzǏa> ^-qS["Wѡ.{ X.tr-QH\6,_xyx'ߊx?D3JucUxʴX,2ʕ톎g<MZ4?B_8W^>+ կm(ʾa- VF>6*y:-T!Z,_cj>7xHGt9x[cŞ"IS.naTH٢~�J<~xv xqp2<,&7x_ ʲMsWIfO ֧:Х:ƍ_qhϲp&U�d\%<,GJ<=at3-N8N LMHRXSUvL:G%ς~֞Vڎ-61^]]^Ymgv^W <^3&Mθ8,9>yϞcf2 )өJT,T c/)qSľe.+e<'g|6aNcs\ ,Oq8ʙIF>MԼ2i�> j^/ttƑx?HL6>$W:nݰX `<- 郕32 , s|'18/ `[ c<+TJh7Elǀ1;|x,Kr~1{_9V[< -t1CXc|Nyj8,L U*_ ]q?T5-oIuXkȰ{;;ؚbѬJH}?wf+8;(G<!ʥJU}F;UʔqU)k/gW;>Bg|GeØYpmLVqγ8Ty2Zq8R8x:INvK jk{;M_ǟ5=J]x WexD 4AQ-] S0~q73qG嘩`nG)4jRs| x|N9rbɸ)�]gp<&m^p8x< qN)ӃMU#8d9h\%iO>&ѥ<eoXwG fO+f K-n^hg9f mp\ ƞPW9br\~}K6өIK+&Y:lThWp50x6/ lU kF?x~1<dr0,Y^t'̧Y|0ҫGa\VuiW֥V:旣vW>-U׿../q-χl $[^K^:ukk5�G]e|;Vk 9Oni,/:XTxڣ1 172ϼ?28i�bׯuGøL~3:1gX(5p)W?5O�&m +=6[uN"K=2Y#զn[Zw"[߈H ))C\֧ ]c2ܫIbx0LUYsJ45 *V*%syl8510!`m\ \a`)uV)ԥC 0S_�z|Zu� ]kð<Kڴ|g 1}{ <+{^GadwpO\MG N61Q,F+1Z#;ө�U;/: pb|E7l./+QUGlU|*0x|N&p/-4BKnX%eyt#FIYcD.Br/ن; ʰ:;2)a8L BXN;(СνlEIƕ:0IB-L;e+cqtp<ZV7Z8|>V^qJ8JJ8EI|iNo2m<c W<!ì\HʬlJ6oͪlsIn)xfj9i k8s!8g<g¼1FRhRl 4RӣJT�l-qcyuN3xKxG7RJ +O$C/MƝjdZ׀O9>%[:sh~2Ѧ\[L'X쁶�Aug$MKm@1~¿I;wpNf�¹flB^ug6"*L~_<g1N+.Xggs_~_$A֣]ʋ2L"Kd|'a*SL6;ۤ*�cn7˝τ>:>ARS5+WD/GB֭.<4O0_>-L�I>' x9ߌ#_?*f?g<)yuW䘜u8T0=,^T:Oo&㌷gxUcq dؼgbr̦$0LaÊgb#YG|gKv>^oSZnw_׺@y!x�%Ìx;[2:[)ogcqG05:%g_pGx8|K֛Ԧغ &1aF+UWG X|?~x;6o݉UAr�پTNmEa} oO2᧍Xn ,d3tS2paK<<Qt,GSK.HueA)dЌqK!sR#E{y|' ^!)*D{.w,ihDdHmho8n/,p`if9FmaJ״2JUiN3W,NJF&ZQnϸ7?͸[2VKxʸ ,Cҵ\ԥRl>"aXz8jhURX5'�P@��P@��P@��P@��P@��P@��P@��P@��P@��P@��P@�ǯ� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(S牵�ojw:%1{Ywx[Y]WW{NG.,>f_/�h/*gpl=+ܒyGلhI18pF:ՎeJ0`_IxCr OX<<1G3vUjqAGx<N"\?& zwoR|p?fkeu2gim4;^H+R7W(? >n*WG"8�2QK\=:񎒩6he!xWեyGuj4q MX?ʮ簣)BV|4xy|eE"�|Dе[aS ˃gQn R$#.p׈2>)I,=˥Ø Bڢt*B-J/g \a*T#1m%ɱϱœ .Nn.մ=/$ĦI<] ] V9�?E`1>, �g D R> [rZY\n~[uG/IeNu2>«�dd9u,}>ټ'd%)K~0A߳�W=l)k6i|Uخo-u? *qrYkU�"Keu.G Q<5R1|S9U++EJ'.?#} ‘;cVeGBr~$sdtx805m-oj{k n$GS:Ӿsgu5Q| RPЄ5e`BY85䴓 N׌Lg&Q2x Ӟ,\˚r2۪s~']5T<[qw(Ԇ%ŽlhlQ%v(bm  ��Yoe\\?ydڴKFp}yU<¦)UVzիT&{<SļS\=̳+fQqUV` u!S^"3Mӥ 0ʞ pJ(G_ A#V_WdTvO85}JȩzX4R\W1),}(k'8|2RL38 9qeY=NiT5AԔ?П%Ry9#+|9T#5KV1%28B&*qsGaux/~8_ ?HԾx]*ĚTH{.�Oׯ5Ff\s(Kxwq_xcs 1b<YUojp*P0~mfyGܻO%/e>-\^dL11r\-�:~q1~:g.=aq)|e߇&]{&1c<7[y:}Ҷcw6qooFw=$]OĞA.{x j=ej0>'q8U_2J9aڰl9ќ!�F_3pxN_eZ4ܫbx|JBX *s,Ul!Zcm}gejGkkwqomC}RWqE'#dDT75r~'/̿kTex^ CU׭Jupuӵ:09ܔU6g`0a0(P̰l]z|)Ztq)T<]*u-RpI^^3|o@|5h_>81^)u6km6g9 E0+'Jp�x9\iEC8,T89ZYT#x9laq˫WF3VxF0�J~o eSS2ac:NM{5}FrѼd4߆ <="KM6{;PR([^Ybx4l&~+v+;  7\/82bi؜%L0Iԭ]p_QZ9;<q"G!ld #F]῅9_MRS,a`ka0SE}j)KO.W|J>[OOCUS&u[6*! $Ӣk䷜ʱAuY.41_AxǾ"3x[V/|1˚|7Mxnt3*h'Ƅ0C?n_+Ž ?g)b0qO ?ڂ ZIFe2…gUSr,$kG/xAi.MO° B�RѯX즏KN ^Ҙhw<8,=/ʸӭJ~#r<9~uQSZ[΅O |oŮƙg{Q`2l.ΕJr ~;11ظRZ׆YW.b)QeO \Q7u%owώ6oό[_ų[�l-{MZW�]#[/u6i,CN/ }5 >=}b>\pp\X+eSd4�ݱYE2ƍJ b#o}<f8pl_� Ա|W_�k,N;as\mZl9qV0Qhї|/_?i+^M=|H..5}?Lm.{ܑ]ؼOAXJߨby̟ Cx b3hb|HG!W P #̕JΥ9x ?K� 3:O2,FmC,<?�ѫTNYB<I,>)MŸR#_xwxk:𿆴3TV60mF!P/vm'٬b|xW8=\',0ͩ;JyT/*)t $*cEbغrqlVmf xs),UF3TbgJl#,5*^M�u~z.<zƧ,K_tBɲ}OEJ<.4�J!WK)a*js)F&sD<J)')QF#.G<:S\]:rO%K88<6p4! ;h%% otSռ{Q|M,ڕΠo}kRαuea{ {k;}c⧊_F8+ɸl ,xwx*V-5bVIf8k!k)᷇H9=ak6~̰c2\GVeʣ8pu�kx m`e((E#|$}7㧌48&<v6sYꚥ KeVWx1^2q_9<x C7 .θ &/378SMhWN;1y8PJxQ9L(xQ^7qnWWxa<.$x/<N/x\fgQba5R'p*_'!rS[#*< I8-bU<~g_ߴ?ϢwaSb86.˚~߈0Y6ey.>MZ?\?iCr'<DZWyV2Ub7_%�ş,lIdݝ6 bKfd `_пG>�Rw%Ob28F>LFKf5ϙ1s6~5xQhf|}0S5+ .m._C 'ˢI'_'w�%|[/oZM_x6+[]OJyqGZU@H1K-{fh Te/y_qJJR�R?g߀)cq65m,V[Zq'(rG1FOOgJ�/][ Ζ֚Ρj[^K4T5mR$W.VڏaK߃E<Ay P'>Ͳne8\)p<ƶ'f9 iF\(Q)| [ŜKfa>x\50'0Iҡ05S 42p|@Yw[{_z}~Oަ5ap.xkg#6il""+h(~$pxsü�''xKPpZw Ibኣ:<LԭZ*jժ^o;|o&|f76ݭOVjѭO.U7•*XhҥF#�oE?egC6?:�LӵjA]}m1GFo�%d� e;|\E%>ȸs)Gq ¥:_&:Ӝ|l8ъUI1Oȱ,~ WsμS8έ  >=7N4~<G .<)MM<5 LSMӦ'[h5޸%vE>|$<Ę\�2OgҠfT3,v6C*5Z9~ !]O*xOm,F7<^7Zӏ" XG p>e2UY.e<%SWΡ|euS{EaCO� 5]!?x wQi_ص CM4J&72ZvmPeA<7B'_NwS|R�L xuK[-˳<FseP¬-. ̭9T=pAѯui^YeBS28Ƽ~'qf&t~a˨e9^cBlKU�z1Q {<*S9ٿ^վ|a[Ƒ6m?_횾Vؘ-'OKkxh9a0wai7sX嘮d[`aJXIա^2?ѓ7O8-crg*2Va0JL.-GZ0_ GMER_WmikŚ4ImuGk:[_[[l$r[O޵Q;=pHX-C(.x&kW$~#Ư&*71w SXzNqUǁ?>mƔ8cÜӍx'Bx|V[K83ʘz;φa#:<WaڝN??>&xI_\]뚍M+ 3E[{ 2mQ4-ij�_pǃpLJ)`YQ uS3e3\bV cK F*x^,qcqvub+aU9ʲm?ܟMQr+JJYUUZ?g5;N| NRN~5km|H߼KHe!$Ť]iUwAÙgcqeX<"3̪h!3QJ*xʼJ*1:Thʝ3酟g9[es'˸3*K(,=<eI^u*aig*eRXg~1� ćZğGX?Fמg_#x]V� gSճH`pEb8JYTNWo?g\D�uxepG^v(Pr8_o G(!xWO[UhRY?NryaԵMWX&'&4n_H��GZM>?ܢ5K+g5hc'($8ԡ;>ׂC|aN>kTRWx̫*_QJj9gf/|U|'UV/=~']xKu/Ku}vW7^-Kpw797V̲,g�l8W;ׁl,\d eTr:)`儣)pxl.B09N8(̷21�Z9<fX_2Tq5qLF+u%RxRjI#)擦|qmVڧ>iQ5͞iַs)OIaq䐿gsLwaYOxy(aqfx8QXUlTi8ҽI9{dY?W4ѥ2^uN"lN9r6.:Z˨ӍQݯ�:€ �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(�ǯ� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �{۫k;Xkᶷ~"k>/q8UHXiaVIv8JO> NUqzQWZF*qZ^S(+\)�xo›9>|,?q J0�+_[6#k\FÞ>(<IjkN*< [jmL9iՅU�Fh"Gx'n<1*P M,彣'<ZTM褹??kωeAn h66K0{'Hݴ+s0�;h .55�_DW>"fZ+xO:r%9qkL�_'e?o=%O Jӎ30s5HR*G:ڏ߈o-#/XiW7\3}K&#Y̶j̦kۛh#ƍ x:&+0J f9Ju/!ʸ(̳DhR*81Zbqyhѩ8'7+?0K0^'Vp:o4Z4[$e$c1zU f~�<wM/ĿxLk%.un`G jhњ(I}_7 p�Ppxk ô0Z%W..*z5*BjԥK^KxĹʫĎ*>,jbUq9\EN)`fІJjpRIR4־+_~|(O, 1 ݫV)m JwPWDt?+,�_Gß#�p;VVpqa ε)} fL}:uTS1^}'8|S5xǾf93 <,[><Qa 2F)U+ⲼN bpxΕJkו,� �Pt�Y|M.j̑ihfFƷ;:f2ı; _ӣϣ[\%$x?'Y\&}q&qCJ*Mas*1ҩ8a 8HpO�aO\'9<+%+R(,|19}YJӌu.<ƺ?G \CxV$wPk]\K["N5Qԥio<L| +|?_8 _%ܛ0ͱa3[0y\r̮>+N<N/B5d~ypCV?_ (m:xYKjh`S3jեKǿjF^%~:Aݭ saqzݰ[)|@n5Oi{<״Gn3'˜pelF+16a3ͲU'0X<8d(: l5XNIO6UĘ'8qaeb33w^SgG/)`V[eR){oğ8Eִ~τ%IqF+hbΥ}4sHw5^Mw\iVk. <5OU\1fQ0\&\ C+TMGOO'T0Y6F ?9g~q,= W_ XxS)+O1VV*"y_fZڧZ?ك_i.> 6W {jj.VKkM^Dҭ/<!vlnDnμOWx? \?X\ٝxpG)XISbln}p2sZ80ŝ_J |:4xwn>8˼Gg"{ڇ egb0fx,Ljaٶ$C,J*mF 4լ4}&I5&)l,(zw6+M^̙_y'W\U| 'qO,*1]Y$eW˰cCIz(} :\ˉo6xG59V"L*WiCFJ:X,3Iս,E5__6C_/k=#Լ/byj "!5>r믦''oK,#2,nhξ>QPx()֥:ԡG!+(<7UR�~|/~1}%f3'8)W [5%B*T\8>L@~/�ߍ좸׆~iWjxnm<T#çGu\@c/|Ex>} [sl xkV7WN:L%LtpJULm<G;"|-<Yq|\5~Y\3\?}Z X*hZG>OC*uײ:޿k2̗Ws0O+DpE#' pYUr|[aa0xx^򗳡FsI9R)koę2cgٖ77q՝"+^vI'RYB*0!*'|{xs_|/sUFѭ h_ :+.UOco}kG_<EĬq7Ζ<W̃ ,,Cl.Vu\_e�W}h?1Sgw`2 <>'g,u<rfc41ٳ?`0|@/G k+ hQ5*MӤ[+O][Lz_Ŧ ,3[�/'>+9q#qN;$p%0veK<~ LF]Хcs<9ul_gOѣ(\x<UU8e0xl<pX/143 6\,+x|Dp|UVV_<c<v5ZE3mwڕ7)?W^X(c9*Djχlv{j+f>Yajrޭ5荌ɼPjQ|{\R3+`ݰؼ~]&mS0|爟>9,[;ٵ^R9q7<g<q�R=|!OCj*QȲlX՗-[�88jqqMu8sNWrsl^`饬~�B/ cG/:`=ie9uzgrI$6<W.=Sq ]FqIتTqFUzjA+$~UᯣO 3r,Cjc}g 9M^S]I' #߀>(|?HY3Rnom,zi ZFap#O6]%Ԧ/�<3/8;|]{Kr̊c_rBLF+*ne[9 ,eV&xJ~|x/~q7 8#!xdxs0*`(2tp<ҭOƞ?/ѥfcQ&*nx{f1-|=}5o/?tRKz} :7n./&) ,f[UO8,eYCCtqxڏ8H׭C a9X\KQQO C icpuww Ef5p1RF+''lF#.L Z0_LKF+;+k_ uM7¦uCc񶈙DRWCey?vcVg x<<.c13k uհ UF<v?JsU+ь�Q0vYχ9]88Ts6TT Mʴx,]HA•YCogOƳx/ǯIcmVu+gfJ- Ovxċ62/3p? C|=kObrJ02xx)C {?aHU9TUSrc9�7x1U;gVUe0T*O*8yj�WwNt8ӫIQ~Կ 5�ƥ߇_ >?#}LCwjxFDBҮẲ3EaRȼU$|9;,<~/Gõin f^R/ &oӧ_ eN:G'9Oy8,l%G2ʍ0VvG8*:U)k^T<3sa!v>7xli:'.mB,&;9%㵎{uh4k(iLjпOoxf~ #<6ȱ\W 3جw<nYOJ:u1Up:=\e^*7y<<hتGUǼ<:SO*xSYf9T0*Ԝ0fy|upjؼe*=)]�q|S5iR_kx{?Ő!U)u[IJk?iDŽIxu>$qɼ;uT ccӭ))U&i\vgno xuՖ'5犽,n^(1YvUAs�g /x�|+C)|?ms5I!2X꺯4mԖV 3X 3낰mN82#,38\ӈXjSq12R771c'cxN?J.yN ~A]_Gr >"U$37NHЖ'*xLU 矏<F0ό[!f+wֵ{H1N1_/3 Qip B0E`ՓVt9wqrN&�&~g,5/iM�vZi&x{mÿ>:džr[H<UD>X%6;ZE{>iVs]iI_/x }0p}\3 G,4Սz|TkSe\0s|m&'u(藆<WV�2$ɸ{� 3NoÙVwLnϒ)s S09WfPw� Ɵ_o|6,ƑOO]:MWiRLNM)?]/Æxsl : 5xC8cJṟ؜t8T0x#N8:Y.-v;SӅx!pYn,Æ.)^"'Nr.8zw[T &2yQu*S_1' x+⟉<!}F #�|%s5!D. �mNyOqe )ZqxGgyn#9? 3xjn##xy̫wU2U0Х TcBQtyj#Ď0(p#c:¯ }V*8hcs,I(hb0T / [ _zQK/Ydu}2˱-'50M+G,LQ|Op,>�ɫU?4adٮ 1iVWhэJ,5؞֦# /ĕq^"q�Dž<֕*aVyk1>r)[f<|-Zӄ1`�8a�LawӰgiVmsjZ0�4?E9w w?qOx�3^>K6rⰹNY%Zzo%wFߴ7/82˖G^k[f9tdM+$|@~y-o,2ۙf $xUL֒ڜAmD7_'#\qxOZ<nl-*0%ZGJ2hUo'W *{x8�5μv :n:_ñ _"e=(NokZ|2C0<DBj_X~B~_Nj_'T ('7(ۯѣ6[x/?K9G R'r{B.:N53sr?><q_jWfyEQo(uͩ{thCE`ռ1n%%+q<UY!sE{K>&=ޛctUM>? 鋓pmjp,ֳ yVzu!8ZNiVX|mC㰑0ӖFӆ|i:Ux,~6tì^q_0YU9M)F>Y`R1* bMgtߊ__>@nP&[3LZ\A6.5B̲Ҍ׭ui_O ^<&-q�!XY|YnuBX 4b15a(cp\EZx\d -/\~m 8(qc/'⼻ߤwpwx{>Q̱Qe˲&))18=9pUs}(ʶ6X/�Ʃ~>|`{=[ hޒ$K[^֣5r(U.nzOqva73N?|˰pY.Ҟ+PգJi??IOj:5+%|#NʕH`!ECW⨩TSV(TWY0P@��P@��P@��P@��P@��P@��P@��P@��P@��P@��P@�ǯ� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �qwuxK5i&q "(T(q*n RQХMNnMsIfV+5Sj"JP RSVvi`[.nWYeY[y$QHCyn0 (ci,xxpR*TrJXtJwk rfޭ]{8T0GJh֨*EZ5'EKih(9%F>"Þ�z$}D7<f ZR<ORK7-]Eޜ�3|4>&G'gC¹cLxW+1T)aqx|%Zjׄ܏+&xQ9V}cxYX 0 KӖe*bpXn\M(^P8;{(\Lֱy.bG=P�L!)f!ӭquiZ4bT=HEin1[oh�cg/<)UI|Sst7Q+,ywj-Ͳ, 9Ǟv8e'r0X,dO⧅aWh:R{Jn2Z1x,kC a㈇Fz^δi*/$ԣj9˖7X^İM.sou7ٳN.R+;SA:<ʌḱ 0Be18la :lnt8l]:M}_F+ u)Tdz`qFLPK&1aQ?oNTku!V|N㟌^*ukipvmigamgi\<pAMO#S�'4x|d<=K%3qYc8#b% t0𩈯7K iQJ?Ooxqs,=ѮLBQa0 6 B2ZB\N#Z+~%x u}Z=l47LI,m`T$k -[. pp|oM?CfiO`ix>SSI`8l<u9A:ž: sl֏ӯ[,[ez8R08Z<}IWaW^5qꑚ2W@no.颍agv$HR4*)ڣ˅`1  ZV8\=,<jVT:zΤ% bXTӍ*rV^TAZn4ⴌ"bI �(7wV<V7GtUp$Iqr#ʛvHs sV150q8\6"ԭBZjy.Tj4MVS+jx|N"<L=&)Nu (Յ䨥efWr;$nȌUԂ2 c53*tB5)ԌR q''FQn2M6iبNt*u!%8Nq'x2RM4V$J5M<47!gsԒk<> >6CJhӏ•5AyE%^|MY֫QT^՜ΥG)9nKDKI]efmFI! 廨Wd,q,xxpR*TrJXtJwk rfޭ]{V*y#G VjL4kTXz"4Rܵ-I}{-6r]IinIKĒa%2XܔUlu7qxj4UTlD`IF))ͥeSTPmԯZxz-ݷJSmɶ~fݯU(A*C)  9<M)'%(5(tѦMhZ7M4ӳMjj4M?ŋ|뻛ɶn]W̙ݶp3 幰x_K`0x\ g?cR畹BvWݭV3N2*Wʶ9˕ke{.o*suspG=mlK$os@̱G\dW=& :0l= :4iҞ&VN*U%*9NFq8D(ӯZj~ jT WɪT䂌ngr 0671l�$**@�q)F1b*JnRvRmޭܥ+sIE]hZ1WRI%Zid!A ::r> _Gz4vh֩0#uq50�ew(*Bs\xL/E*eP*mԨӦMIM9^Mݝx~;n3txElCPg>HbJ)kbv@��P@��P@��P@��P@��P@��P@��P@��P@��P@��P@��P@ǯ� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �ǯ� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �ǯ� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �ǯ� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �ǯ� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �ǯ� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �ǯ� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �ǯ� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� � �=€ �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(ٓ}پ/�K+o_zc9п*q�!_Ї�2y�<Of�&�\�S}�̗��C�<�A� cU�˚?*q�?'? Л�sGEN=�c2_vB�dy3W�.h�ǿ>�fK��W_!�̞u� �o1�Bo\^*�8 ?ο�?M�ˋ_"��1/�;C!_Ї�2y�< qx�4T3%�h+/�O:�7�7�./{�Cd|�B_f�&�\�S}�̗��C�<�A� cU�˚?*q�?'? Л�sGEN=�c2_vB�dy3W�.h�ǿ>�fK��W_!�̞u� �o1�Bo\^*�8 ?ο�?M�ˋ_"��1/�;C!_Ї�2y�< qx�4T3%�h+/�O:�7�7�./{�Cd|�B_f�&�\�S}�̗��C�<�A� cU�˚?*q�?'? Л�sGEN=�c2_vB�dy3W�.h�ǿ>�fK��W_!�̞u� �o1�Bo\^*�8 ?ο�?M�ˋ_"��1/�;C!_Ї�2y�< qx�4T3%�h+/�O:�7�7�./{�Cd|�B_f�&�\�S}�̗��C�<�A� cU�˚?*q�?'? Л�sGEN=�c2_vB�dy3W�.h�ǿ>�fK��W_!�̞u� �o1�Bo\^*�8 ?ο�?M�ˋ_"��1/�;C!_Ї�2y�< qx�4T3%�h+/�O:�7�7�./{�Cd|�B_f�&�\�S}�̗��C�<�A� cU�˚?*q�?'? Л�sGEN=�c2_vB�dy3W�.h�ǿ>�fK��W_!�̞u� �o1�Bo\^*�8 ?ο�?M�ˋ_"��1/�;C!_Ї�2y�< qx�4T3%�h+/�O:�7�7�./{�Cd|�B_f�&�\�S}�̗��C�<�A� cU�˚?*q�?'? Л�sGEN=�c2_vB�dy3W�.h�ǿ>�fK��W_!�̞u� �o1�Bo\^*�8 ?ο�?M�ˋ_"��1/�;C!_Ї�2y�< qx�4T3%�h+/�O:�7�7�./{�Cd|�B_f�&�\�S}�̗��C�<�A� cU�˚?*q�?'? Л�sGEN=�c2_vB�dy3W�.h�ǿ>�fK��W_!�̞u� �o1�Bo\^*�8 ?ο�?M�ˋ_"��1/�;C!_Ї�2y�< qx�4T3%�h+/�O:�7�7�./{�Cd|�B_f�&�\�S}�̗��C�<�A� cU�˚?*q�?'? Л�sGEN=�c2_vB�dy3W�.h�ǿ>�fK��W_!�̞u� �o1�Bo\^*�8 ?ο�?M�ˋ_"��1/�;C!_Ї�2y�< qx�4T3%�h+/�O:�7�7�./{�Cd|�B_f�&�\�S}�̗��C�<�A� cU�˚?*q�?'? Л�sGEN=�c2_vB�dy3W�.h�ǿ>�fK��W_!�̞u� �o1�Bo\^*�8 ?ο�?M�ˋ_"��1/�;C!_Ї�2y�< qx�4T3%�h+/�O:�7�7�./{�Cd|�B_f�&�\�S}�̗��C�<�A� cU�˚?*q�?'? Л�sGEN=�c2_vB�dy3W�.h�ǿ>�fK��W_!�̞u� �o1�Bo\^*�8 ?ο�?M�ˋ_"��1/�;C!_Ї�2y�< qx�4T3%�h+/�O:�7�7�./{�Cd|�B_f�&�\�S}�̗��C�<�A� cU�˚?*q�?'? Л�sGEN=�c2_vB�dy3W�.h�ǿ>�fK��W_!�̞u� �o1�Bo\^*�8 ?ο�?M�ˋ_"��1/�;C!_Ї�2y�< qx�4T3%�h+/�O:�7�7�./{�Cd|�B_f�&�\�S}�̗��C�<�A� cU�˚?*q�?'? Л�sGEN=�c2_vB�dy3W�.h�ǿ>�fK��W_!�̞u� �o1�Bo\^*�8 ?ο�?M�ˋ_"��1/�;C!_Ї�2y�< qx�4T3%�h+/�O:�7�7�./{�Cd|�B_f�&�\�S}�̗��C�<�A� cU�˚?*q�?'? Л�sGEN=�c2_vB�dy3W�.h�ǿ>�fK��W_!�̞u� �o1�Bo\^*�8 ?ο�?M�ˋ_"��1/�;C!_Ї�2y�< qx�4T3%�h+/�O:�7�7�./{�Cd|�B_f�&�\�S}�̗��C�<�A� cU�˚?*q�?'? Л�sGEN=�c2_vB�dy3W�.h�ǿ>�fK��W_!�̞u� �o1�Bo\^*�8 ?ο�?M�ˋ_"��1/�;C!_Ї�2y�< qx�4T3%�h+/�O:�7�7�./{�Cd|�B_f�&�\�S}�̗��C�<�A� cU�˚?*q�?'? Л�sGEN=�c2_vB�dy3W�.h�ǿ>�fK��W_!�̞u� �o1�Bo\^*�8 ?ο�?M�ˋ_"��1/�;C!_Ї�2y�< qx�4T3%�h+/�O:�7�7�./{�Cd|�B_f�&�\�S}�̗��C�<�A� cU�˚?*q�?'? Л�sGEN=�c2_vB�dy3W�.h�ǿ>�fK��W_!�̞u� �o1�Bo\^*�8 ?ο�?M�ˋ_"��1/�;C!_Ї�2y�< qx�4T3%�h+/�O:�<C�.�h �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �<C�.�h �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �<C�.�h �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �<C�.�h �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �<C�.�h �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �<C�.�h �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �<C�.�h �(� �(�9xß'?xQ?%j>!ַMחkk HQȜ2aNNъnO{%]Gf|IeKx9arܳС,V;Z40xգV qZ%8�6վ�OC*_J~ q*iٯ:?;?{k�#�2h"t�E�1ĿW�?M:�ǿQ�}/W%�¼��~�mx�~=�RCđ}*q/?{k�#�2h"t$U�Fc!�߿_u�G�A$_J3K�y�=��?{�̥8/ �"Uј_+&w�Ge(D>Iҫ�^C�`�6�?�)G !H?H_f8� {= �G]�J?p_A�E�1ĿW�?M:�ǿQ�}/W%�¼��~�mx�~=�RCđ}*q/?{k�#�2h"t$U�Fc!�߿_u�G�A$_J3K�y�=��?{�̥8/ �"Uј_+&w�Ge(D>Iҫ�^C�`�6�?�)G !H?H_f8� {= �G]�J?p_A�E�1ĿW�?M:�ǿQ�}/W%�¼��~�mx�~=�RCđ}*q/?{k�#�2h"t$U�Fc!�߿_u�G�A$_J3K�y�=��?{�̥8/ �"Uј_+&w�Ge(D>Iҫ�^C�`�6�?�)G !H?H_f8� {= �G]�J?p_A�E�1ĿW�?M:�ǿQ�}/W%�¼��~�mx�~=�RCđ}*q/?{k�#�2h"t$U�Fc!�߿_u�G�A$_J3K�y�=��?{�̥8/ �"Uј_+&w�Ge(D>Iҫ�^C�`�6�?�)G !H?H_f8� {= �G]�J?p_A�E�1ĿW�?M:�ǿQ�}/W%�¼��~�mx�~=�RCđ}*q/?{k�#�2h"t$U�Fc!�.�~?|w_ �h ~+kxrJ}ާvr.;4a 2fp՜iӭNWM7ewk_֧qwG |9qwx]|9ц#4qXx|էB5*GVӫV?wJnWMsQ�P@��P@��P@��P@��P@��P@��P@��P@��P@��P@��P@��P@��P@��P@��P@��P@��P@��P@��P@�<C�.�h �(� �(�):}a{]j67ƒqku<2 d9"vGGVVqIOgohb'GOB98TZJu!((3Zi�5(w?xQ5_ÙXGu5s0�Ҽ9q1 hNPx|MJVo8KUk c-~ ZscM8l𹔪E6 ڜ^ԱW|zOނ �(� �(� �(� �(� �(� �(� �(� �(� �(�)�r�{X)?~/؇ ��E#cB �(� �(� �(� �(� �(� �(� �(4:ƈ ;EQԳ1@I5)7e}Wm6mݽ{~q?~h]_Qfc8Ρ9_$WN)S.&q�:Xie}6waɈZ-)3}xch>#uۅH4IZe<zpr-c8 -bp^(5[~NŤ.%=J~Vhޯ8 �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(<C�.�h �(� �(� �o�+?><"<Sw s?I~#67nUN7s?=(;K$W�ZM]'Y ;6re a�2՝⼞JM=#,+Zޮ+ \}|��P@��P@��P@��P@��P@��P@��P@��P@��PR;1�{�]oR�$,6?<^��WqGƅ��P@��P@��P@��P@��P@��P@�y/ xVƟ<UxW@E7IK}61 v <!\QǙ |^swXjOb1xa*SҗMX wRui՟JT)+ԯZ_f(oI~?X7{_ i d?WWC.jӴ/2]JR6?22 kȰ4|jK*t^/3icq_SX:w*Ս/n.Gf8v :n+4̩ƾ2z8;Mm"]~TB8U?x ~4JhMhZĸp�. <3=E/oC,TNJ;N2�Rf.qS3weSZ8xߤ0ԝ<<KBV�k˹=I?I+I;$D7z?^ ORRM]͝Ķ9%D矕#9a^.ROJIӧZVX)VB^҅Zj-FM[$>E�&FD*{,.d%V�Va$uQURk6$ƣڤa LkZ;3nsxrt=cj}*4SZ~NۤOk }ƛ['xZKzVzns徕u;Xc ܤ%/x8>Z}Px̾qtg9L5&C~:p�Ύ[B<?TjZyf. T Gz(VE0 2X 2#A޿ZiM;4OUH �(� �(� �(� �(� �(� �(�|>?Ꮑ^`ҥ֯2׺|S m<K@K+ljS KY"9β|)] 1P:CJ5֌t�gߤ?7vy~ TeKuFQ̰k^FF~S'_ǿ_ [|1ȴNmÚݞX\㆒U<=ǹipxCWBj%߻']zhpxe. qLghcFJLҼ#+JK3(� �(� �(� �(� �(<C�.�h �(� �(� �&|>ŏ5g>-S<ƵMze;3\6b(u(!9k=pĹ䘙3~Ͱ]jTy~&&ҝ5xc�i$~=|Sßjz\u]!d^#@,:sj$W +R (zr[�=Y�W~xxJR⬗ J'Kt[L2=_RJ.^Yz�P@��P�벱]G,[x\f<Gdo*/{-#e |M^Yq\$X�l\]Y`Ī_oJ)J(h^RӋ�| w d0fE*.#cNIJ!a(N*J:M:ex 1ֻ'{UѼ R#LѦ#3HAd+J7Rn�|E'O򯋿hsqUe|OpV s e#J`A $; \G'�%CXi~7,5 ~%ּSzh ԊsE9MkMyh7h'ҏQԓ8k!.Ws't sO:O٥ѵ�&SvV|IÚ<'ȗ$Wqu_zps^J_ po�Nߵw䲥x=8$dv֤FOagk>kK �g�`>|MO]⏍aZDtMwv7vmpXqOowln4ȾKpwԕS:'9#j>Y'FOiԞ$61?(xo0L|SWbl=xakNeTaeN*"*~惥8�^ '6FXxxLnK=JXt孜j7WV_$b^o!SQ^UNnE;je+'}vĿWcxi4xz8,^ua6WO/J5kA*RV4%'Mb+rI? ��GE�φ�a�� * '�5k>τ�hߎZ{xXbH]ΥЯn DjRI�T"qԺ65ȮX2ڧw fp#-xQ˱CO)TX\N#1(R~IUV5&6�|gj[\j~ G<)fKI;]Z*Kqg#̪Z&BFO$xpv7 86ѴkEPL=­<.uedZ GFbXSIM4d{u5k?'wе? |Hi#kt]&Wֵ$Yui^Ke^ݬ6qeMQ":1j.\ҕmh{$σ^omGr剧ШOW᥈:J3FʫEΪQǒ_ŷ)%+wC~г,QᵬyCm'.'}/upAwGB׮�/=TqԨq)9҅Iq5JJhƤ)T*QT~Bo�J��?bh��O��S׆w�W !�|?fo~~57 CG5,z RF<7a?Pm@[jo5Km3DɍcRR~<ӄSe;?{7>"[뎸<W]g3,g*SXw0XgV#<<«ZO�Q�#x|?|]xZo~Xhrkw62ֵTkB++ 6J{M!|8nS#j7%+%$Q].M(՜xX?87r6Osӈ`(CV0.ZlF&.RNY{ICW!?h�?�S�ggUO7^}O��_�d8/|3�&<?���XT`�u��J��?bh��O�UO7^}O�ӑ�? k|=20MA:g5 BKhkC֓JjqM 1"D<+xKneMžK;7MyATDy_#ӂ-LUxy.+=fk:I+qB a*Ir:LD5/k$aMbpd_h˓zaͧ{R_nhIVvNcq3\yస?,21y,HF;lc;B �(� �(�O#wc��Qb?�HYl.x�b'2+ �(� �(� �(� �(� �(� �(�>n�ikUON~%<[@sCg;KpDTn"w(d,I¾sU)a2:U+Ujua\hN<op.MW4dם0U؞[Ɯ/ӍU~8&یe|&7Mn{̺&91iELq"_\{Tq4bo YrB儳 άa,7{؜~!G^nPӂR�?xs4*fy&U3X\EG KhwU$|{DjZ'C[5+t/JsO<H qYf3 )ʶ+K ҊVZtSKYNq].amzX\+ZqFV9;F4RDI# sre|TQWMlh5=_NY-UH㟦3Z )9˹2N?˙c!*V%+4}7<FmWøy/g/<&Jn0t槺>ҿ aE>5P~Ѵ c $PbF܅_±On)Y<ѿfkc*)`]JeTx5R(U*K�kdm[cR &j-ok${ĞPt^g#V 0Uե,\<KYG|,NjZВ]?_asI/=/ G)+ nb;p�ݶDeY7�eO gS�˳fҼ<6?_Ey%.81Ji|[K4˥,^ .cW J=T ;v#\4$J+J[T,@�~)F2sڌc)K݌Rz%RrJ)6TSrrn$mY-o�Ꮐd?.RZx AOwM Fi;0˦w5l<1ͼFT.K $3uS$)ӴSh^t^2Ws-ʧspYSW6 U0T05~�P@��P@��P@��P@��P@��P@/Oy{�L֕ǿTc��H�.~vL�w�j�$n �*�G_~ �W��P@��P@��P@��P@�<C�.�h �(� �(� �(%��dj lO 阆`[M8[/H�4 Ɇq5aZg LV�e�7n7k]WqL3< mRX~#hs;{攨ܱcYd׀@��P@F'�?lOo⌱gdž eZ|Owkq&$NC;XX n~ƕWi�nVI~�dפ@9brg, JRN3UF8%(O2R#3Bcg|5*Mt{}7Htm* Hml,,H("TUTP+QI$J-K�7جV?81f#y.#TcVjb+NSINI6۔/s �xC�>Zo|-躟_Qojv2oy']?Z (ZnltK#oyw<#I^k):w8Ŧ62<e_'6a㰼C-׊FE҃'T%*~_W/e_ZCB��P]a5ih]~!(]]YmtݖӼ_C6ȿFۇS0[Wޥ(7Va�*cOOzta፧+iQKbz[R9'Y_H*l?Q?xC燴Oy_q#ïtWu&61妡qwG&{Ӯt弄RK{O{.r2HWnex2jE=cjQmic}}(d;;�犧3ySTjs:ztpK/`1&,U:x *e�m_؋ᖯm#s~Ѿx[y$hNtVHlݫ қ؊~hwjk/RN)ՌgKqJ9R :e8J-Y4M6u V¿KxO\7sf]7-֩O jΌ>%zC[]\x|7./^^|1US֔'�%7yxŶvϛгO0<=6oͲN+$Gtqc^cJXV:yagO J!q<yWxS H38:yy~^emosg?P |BM_> +׀5+Vw>"~égm�k} A[S*S rQSiZK-yZٟ7_p�x|olq5L3-|FaЭK-ɲtVP+hG�B{ۉ//.ynei.n&O<<K#3#,I#oս[{ۻ}]thQҧF TУJJPPJ"aN#BJ1RI$|5㟃<$|!6z�<Gk{2 `dx;[gy- ^6JTR;=5WN1q-f93l4qjN+ %JnP*ӒOj3j3QJpc#Y�u&FGk'X~URB%̍6YvPl_R1rFJۜbIFSi={[?Ϟ)F_g#e|O e\&8jqp3BׯQeQt4qpQi( E yJ)bʅą_RMK}#F"4(TMI%ϖ)Y&kG��i?w<mW%ܶՏKiXk^ uv`vV- uBSf�̱8գ'QsNQ NNOFYERj}~ x_(ḓ7|&MqnYcr/,_N+W<c~e*V=~V_<sQF ğ_I5մtwúqZYnuA)[2aq"i8J7[JOyEQj�il޺>nou!x=!\.{][3:n3ӫR &a f2bi֯,=EgnC /9[>}vd*6� LXz溞w(^T�W_Gt+Ty5:U'@%u+uWλ? wk>u[Y [CMaGy"5gvTgb7W)I3a)`3|AfX%QTtؚi(7.fީ+=Y+-��-.xui gzmMYOxÐChmKE-reRRl1J(q)\MFPJܮ6VcOD<Y\uq 2xjb*.e9U$Ҕ*<r7y?��7ƭ={ <EேWq42Kx)-nK$.DuWO6y6iwѸӓv7gٯos\&i\uQץ[qJjq&Sex禱Q/u"[幹Y.'yr$$;3;f$koVW{ߔSN0*T Tӊ)ӧBB)F1J$CAa@��P@��PR;1�{�]oR�$,6?<^��WqGƅ��P@��P@��P@��P@��P@��P7+º]v+ BӮ]Nv /<6� zgvYc3e'f<% iuk**tmIl#1ѩ^YFErmI�L9kO? sW44h̰>]9[+XY_<+|$ANy&GI{LfkR SSo Rxl4~FuRR#|sxάK[|0P.ʶ#]H#Ǿ,߀5_x-m_ xv�WBF}qvG;Gc#VoZmG q84Ҏ>s!ȳ.%p6SBXn2M$)y֭-U:4MF1oVI~~>{ ?KD/-$E >IlƉsz_9,E6xxXדxkY<)ſg̥U{ҩW <<c >StiSuiզYITpW? -:穬O+^Y662[]ŭͼRX. ^) WDee8 浡^,F\>"Hգ^IRJ4*S NetլEJtS*Zu":u" JҌ%(-hM=�:KH2/t/_Ӭ*l]>uCJ!73SoH:ϳ SPYE./jjNrx3-qFӏpLea1ז2'}iГt(:s?W �(� �(� �(� �(� �(� �(�?_*7+Ï��\#qy�=? �o�I\�%6U�ad:�c/#^ �(� �(� �(� �(� �<C�.�h �(� �(� �(_~G;|WT=𞡧iF%m#0 ޹h嵆�Kp*XV81QF[N6{_CI<"7C\~pE.Xrf}Wt;/ԾѪލ'$ׂEƞ,>]+ o$4-BLm*;iU% T"e |<(JP/T�r+l'<fO+A O/eV*oxTSRB �(�G�~ζ?_ 5co+C߉LAne:w-SӴB2vE>|[TtkJuiZ_wX/'>I+00JOtR(Abs ݍj';)N "�_ FZ_*i=}o1xξ#|#e <ڻgUƌZ״+li+”s<<1Y? RcBJU^M�iՄ Ҟq_/jsN3&󟯐I$VIY%-�J!L 8~FڎVм]S{4i/ ػH5ߋ42(kʪ<m+ʬgJR /m]_�F)scG0 TV8<d0٦"*1 9F3R9C� ~}k?"//�?jRۖ6veuF&ܫL\T<E+G.Q5g08=X>L xQ*YNd6)(NQ |/eI7`Qvк_H|9c곇'7u^\,۲hΝ;|�IßWܞ0g7 ]^Weoo/�|3|>�պ) [`J5އf^=u3+w_Ó_QM.Pž6ϗ}$s õ2zY&gʡMQRӢjVgra@Z^>=e~tzǟ֣\{?(R` o' ֽ+zj~{r� 8\?wY3>qUjrN֭{YA/7\�)kG3N.l𯵫.JU'n˯�d9q&[AO<Su;'KcN,O9,y{񌿙)|Y�\:tZt p0Tb$qQe+Bo�Dtќ\O�r?Wؗo1cĿQ_O7�'?>�.?׊]3k<D� �zG)^A��P@��P@�i�v�9^�=Wv[�?MEC�\}�1@��P@��P@��P@��P@��P@�9�q7i4OV679eUnQ NP< &>| OEV6|d 8ъS?K<(dz�jK4-G˺S?$gD5>0G<#Ҽi91?"XYW6֥â�L+/C2LGRQ<n{ZQ/vqЩToZ8�nxT~/,5m9*MHI֪Lq(� �(� �('[/ ij#ִJ5=oQҴŦ xU,K�x�8N NUX8j1UqiѧU*J1V]kvWx.fF.b1%OVm$ ]|Ӫܿu% "b.(5ՁG[sq!eC?aj:U&ST񐬗ETmgF]bAf#xC2()b2ZYzxMN u fψx3wSEU!!/nw�O5_y>*(cFhҩ8T/|ѩO *NUq{1 VUqz5ԣgTkRXVR`FApA8#_L~VMiVii{ @ �(Z_|8�h2 jZ~H9$'b{u~'Jh~7*n.P�Xx+jUV�FUùy>L%3I'0Tc9ѥR^rq]x] 7c n+o}e�-{sk�#o^�~y�(OmGGR�快v;M^$~f Y@25+KAxM$HT}߲pqpGSQyJKRb֠jp[|kG?g٦W+}©uSݯ%�Wڞ8P@�2Yb7iVIeu8FYتfb�IF)mF0\)=c%w)J1NRj1JI%m$>xGs12M3_>03,'n>wD"dI'\Nɼk(GwŘ5DO<R/iS Jq}$>;0⼎XYRXBKu(PIFWI- ?g7~X&`ikm>SvmR^\ R75ϟ= P+>)NS(VӊUq8juhSN+H, j9g个v´ UeNߔc'G(]&7o$m;1sW 6nZߵw}ZӿBͿ�U*/VO<?|IEnF FAa�ei'٦oi3'�A�?ҿJ0�i7N�ל�PN�V�Se_�H �2�r?j? otkzOUszۨ[D1IT}kF' *E5k*؊J+T`M\r9ev?5|pYnՓOVߔa/ϛOt,�hoOq"WΓ|MA�e> U⼙M;5 \+/ ԭ~ї}>8q*3eɫ`,o&:i;'Keÿj HV"m`4vMLx$Zlk9UeXUKγT}kϑ%Y\ NU.4t31ȳ 8K^ue5!@��P@��P@��P<C�.�h �(� �(� �(�/�S~1 ||G%Ŀ mxe(DQ?<swkr/auZ*Ь�nfjxxY_bL\JƬWYzhsIqʳYb(M:8 $`玼s( �(� 6Qyu\=|Q?L&Qv�/ʼ:-Jջ ~~G_~ `_^$PGG$YXR-*qZp_tRGEj wZrW7$̧9>f795nemO%ſ{+#-;~! +ˠxUYؾ"nznx)J]iTZ{ts1e:mp\ 9l&ֱaL}9[ j5ŤZO5մoso+qo<N)&Y"' +e`@*_wUM=SNEJtSF8UVVHF:g q'(86iv9?|d�#-Yx!tMkɯcnT{1FZnA*9ͧ:&⬽I˝7խ_VxY p O8gnڪ2MdW\Tr6b*iZU)gg2W~5+DmdRTq6q8a*1O2M7kugu $LyYVWRU4Ogm+xz(ֺޙk۲UBkԣ-^\� -g9O]8ʳ<~[Z2ѪU\-DFIq¿P ׀GMyv*y҃|q sM�n�2_�@?S>Ye|8sx-:9niKJ:]/~MMf<|G<A>x/6ZocoxZƕ# ]s&�GӴ;tqO=VvN#^ !.JPNI'){7huﭏh<5p߅+.>wVT3>]RN[q8,_ӫ5Ʃ��e|�~߳~<E�'�b𮝫{Pf!k+UiMImios%,K BRuav'i9Z\ɽW0~/'q~&q'3]¹kR5slNjts\\tUz8%j*aJ 0�Oc"%"?E<JzeŌ8=c�]|vOt.poU'r g?|/Ew,DN�- fcu^6ҷ>1<-n|6qkUFitO苑!IxAHR4YKo]4JtZJ��_Q'g>/�`܏o�$_%[�Lv5#Gq/|A�c�S�>ϿF5_K+3L�mew?ꞑW@��P@��P@�~#Wkݖ�a�/"GcQs?q_dhP@��P@��P@��P@��P@��P@�1�XO˭~ўǘk?X-֯祿*Zo�u2N0/Ї$7iWKZRahBoVkrni+=fc,O MMGRS~O;cº< 6GMkn;P988121arr%BJoFw[0a",N&# |?b^ 4+/ xwGЭF^aof`VdlxɯjygXJx3fe)9˟]gQ^Iy>Ye QIYrХ wwwΦ(>#<%_֗J=k[4>1.//%ȪwefefQfy2`0qxMYiT0!RZ! IOc1L [0u+qUaBkyիRQ"R]^?�f8os<�˚,ѴE3d>o~+k�yw�/j�ك�,ѴE3d�=?�f8os<�˚?x�Fӎ�??��? ��.h�=N;�S<�@�_�]�Ϳ�|_wŸ_4Cu]R}K -N{ ;"#8pf~xqo|_H{LV?0 S\J7isNqWk}aq.Eח%65WVV-:TJsvoGßH~T�R yw2rog<3kkΎ[MȊO,QI31GXNu29̈́T,JRMJe(IEK6|e2:ƞq;UXJ\ $:HTJ|԰ԪՌK�Mzo|T|]{$[޺ir!t[aop;vX(2oo$x}G& W40ъ-h�e�, \;#x2J1Quqe<V]^#R*B1Q<z~�^%w>m�_aUyV*})Yom{^i.k=5>F}1D?h?ٷP-~G4k}/V)h\y5�nU7O>'w!pZTዩQU2}YՂ7+Ϟve*W%G/oM| œ]R8w G#o7Ty+*ޜ18R7G7~ҿ j?w~S<;|.}X4Ra"/,<ec3ʫ^ֆ+ QbpUwF^H|/ ~),FST;1 ^a~� Wu'? ח}V<(bw<;`*Opf]7ܯ,ʰa0Vz8:t)JRz%l6#ѩ_ 4(҃JjK#v)4K_=Oϯ&\h%}'Iuи ph[?ck-cGJ8s˃|nԩq籊<*dj-qڕj`QGoJ9eQ*^f+XY/bxh|5|y}\Ku}wsys;ny'fwK,H'搳GYkgFkfysb*5bsv"[VII۔m-O6 Oa.ZT0ԡB8�-:taJ)++-, >+ ˛;<78<rBcqNm6ȳ<%0U0S=HhΎ+ VzrM_ݚ>+ QbSZTҩgNe 'Ůϰ>ԺqmEMWJuZ~xSy[7)U̷ �V~xw0x3UN'EJ8</1�X7&өCC<ˣu~9ެcyU3į[rZ/<XG9>ZjO 'hлA-.oml(綸đM 9#uee9~"3˳6eXZ \fN5hb0PJ9FPdO'_Q΍zbRZrq9Ť(Y b|J�f�xWm=^x]Wg)DV}psF;¯ 8ž$pӥIFkbT)s(ʾ"qs+BkԴ`|wML<֣IO8G-*Qm(Z*Sun?y<Iw4t�xjO`F[2\j,BQZ)s߅XJ2f)bs&R(7RF59l֬8� <WƵʜ߱5'G wV"QjxʖZ)^fnoV}۹*H9.tjtiZ4 o/i㏃,յOx~|;é.)`YٮF h'D 7x'&`srlc'Bv{b)ԯC0R^bB/F5 {n!r\Ӈ3 N#51v'zҖ/-VJ'=[Nr>3oZ�,Hn'?ݞ]л/ oyKX?o?QI: gDZn%gڶz4( Q@5F3ѡEZt)Rc%|œnf|QWz]%7Ou*x TKg0\') 4~+Q �ןi_�Q��M#�'k[�t �'WƫJ)�$x�9صLw�C<)(mbXɿH𭤡 _ rd`aB^f[k`W$x_PB?+楆q#ܭ}iЋWG?虞|E�~':pK01*vGtxTpiF5vAaIWznٖGЭ+L KHQZt{q.{ؗ3,F:|”Ʌ. Z4c윥&xy_eT| F5t(>j6uڷ>?RZ+{(N8rkv_In-&yD )cmU6ԆVypg8( qwN3z>VRhBtѫգV *A&Zѧv�أ 6xέ|@[s4xPwrs#wGk?Py"sD3fzoWq_2?h,MNX^չ%3*>8 >G üI8R']1g8Hի+bh>om@_hW<9k:aki=K5ͼ$(ꮬz8-jx6"+PJJTӨ8n~MliW2$̱>oe٦YqT,FZZr3T׽)k�P@��P@��P@<C�.�h �(� �(� �(��)|Rcie׍t5qVU3A C5m k-߇n+c\$M|isq}=c?~0KO ]4%^e .2<ʎgA[Icݮm,n,imm s^9cY$YJ"/gBthԅj5aS:ENZsZNHINPPP@ iz-Rnɾ7}�s]}Ëص/m0_3LWZ%=Ij|� #BGj|54SNI4_O]+ ?f?$l|kCIdz?_oT_})?~<D#>/a^ҮMx1Sk]R"]l(P@�($GPAQȡ ]4z3=��#N- mۋx66N.kp5\mY0K?w;ÿ ܙSPg9*veK07t7[r/ AL"gѵ zd冹4k~b1W*jVwm5]Uq xժr.͸{<[4< o/aԚzi{x\?g�RY-O;xyG$+I>}6"LaQE{x*?Wѥdyޛw&71DV>2g_/،$|צLV[**FUq_VNRr?gKOڳABX!T_+ K2[?u�^lxMVFFh|USPX5j7iԖk}SKĪx\u )px捕ԝLPM;O47CחQ{Bk[߇ ~%j4s_&ˋyрh]vh$FԀAy4e,jVt[Ϛn2�hiF}FѲ5xUa8j)MJ"8bsu |*FI٧?8s_&X�OP o8<Sq:{_ϸs䞙__:&JhkSW}:/}pk6LC<=|O;:mLBM_R�?SB/_�J#~� � Ŀ ƾ�%�?w�~ 19�ƫ�u|yCT��Y'OS?J (� �(� �(�O#wc��Qb?�HYl.x�b'2+ �(� �(� �(� �(� �(� �(� mso^x7mnmϳ%J@ 6=y<tCV_uIٵ*U<4~|~J?<Ӝ<Dǹj,H_ו/ 𨶃ƾbB.l[ss?uܸZԨ4UۓֲK][wn׹ "RܐͰݗ*ӽ޺N۟� G _մ? >G7�ؓǵ~r�8O(ŏ7|W�bGoSa?�oS?�}-jW:gI"_g`ȦX:q~S+(Io%:xZ!%u-c(߿ JH;5`WkVQŴ۵b>xv2� �ρf(wREKŤ,ьoKbeV�pw N?<n=UUncaj^6x*5#ùryקt`S 겧vdgkzuOuk:֥jWҴwד<77/,ǁ�bVjξ"I֯ZrVIsNsRm�D~_p,+PenB4L&BqIF%տzMɷ/Пs oo^A}f<C{֟H!Xe7z{25ao_w~a7VynCBP毋C5+te/v_I B[!c$yRejJaWc9'lF*ʦ| �|E/ ,a.uַ>0Һ4VcMӬc]}y |i3U>jUcF J tJG}4~G)Rxgp8xJpVV񘉵OH٫.sP=&Y,.4CPּ-sv4-jXm5_ʔF8(TMNAnjNvU|&"S7?OxK0RN^uae, g^Ӟ7a7cBm(ZGOo>toKq%,fu6Vqi{Qn. ȲA<q\E,I<=pi_(ͨ{Ux:Qox<JZ=%xM)�x~,_\X̯*ѩ vYOfXu):JZuZJibOw_7x{Ŗ7xCYy,-O^OM7nX칎hvayCR(asL:mFe{ՠکIVmSg�I|5͸~Q|Ӆ3'|=y?M;NRV!(�i:^civyj6y nm."a RD=Ձ_ߴjӯJz3U)V xΝHqz]J2M;jCsq<N_1g`q5xQ΍zS]'N% .}�k:.^8Q# #!BwZW0ikl]_>Gc^?}\¦ = jR(4,^?EQ<$9Ԅ><GʄjUsV*^G㠛|џ/Y~IG"E3#F2pu$�ؚpzU++Յ)BU*֭RJRSNRQbm'dY:$Ne9Nьbމ$菺~~6VyKx|9e'ٚ8P5;d[yRKpȿѣceKQxw]L5Xƭ%٭NJNKuL,䭋uJ4|CFbhbOút'*5sU?nԃ_Ni1 j J 5%q c�+Omٸ\i1nyE|?A? U3Zy>޺n~b-b>77+t/{�YdtwoJ]s5ԏl<$I闌<ב%mnK%#o2?+3[.W9żX55*լ޴V/TٜOnJXT�s_euOהhϰY4 ,_T'iWêJ*RdwTh䍊::te8eel29x8**աRz*QB9SF)uhե4N:*SR$oƤc8J3 Bqe-%&ZѧuMp|8.4rxq'Lz|eo{ "v~n:/~X7ձ9χձU\aBn)?a:\:XB ױ Gr?%OiO F}ڊv5e8˗RW:mOo\tڦiZԬ8-,yو�,hNIֿL1<JuxM.VQSj۔~G+GMSRY1N.sok%\7k7_2x','<}+ö22V;K`pFT㴀/ළo p'WKX>զX:)<> ~QDL�7C<_&34RQ9Წ3Вʮ#Jեk(+/ѿ߇_:r4In}VuVm�s3Gmn$h#pׅ3~&ʞXLYcs<l'G&iNVFZ" fgQzJUj{Ժkb1I)N�U~̿t7Ώ{kB+COx{Hƭ>1S:bˇq8'(a2>`_xFyVi'/GI; ^R8'T,s*©T0Xzi轼kW%{o&JS1Zk5 2BWm>{C`]iXI ;J�xsO_ĘE%"+Bb!J>eGY(ǂ^yҥG(yUjzk&Jupzԡ.md;ߵ{7Pzx+^y }J(4B ֭c!2<si#&H?o<n*?�-Tdj0qXJEN4ƥ)ڕx8NQ2[˳:ppe] QS(>&Ҝa~~vS�I/4?�H�!~z�/=�ɽs7?xf��U�?-�:g��+�cU%spgW<�oפ~sWV /#LJfU:yiTu,1' '�ߺoP~̳T'F^ga-:o]Qx9aψ/S7,~ ND[Fx>%/QjڥOu!aAmin(` aͱؼVU^w,o{B S0J$o pM=`2lC/aF1QBNTן5|EWyURI;}6uJkټ!qOQqswst?JR+RHNk<燾<uGRX 5 f?Ileε7RVrRn1?J*oBUr~ > 4kS~֎T*4qp(B+R6�c$:B|cp޷N}2[,[Ҵ<Ek,🀲 V'3OJ}ҧ)3⿦/#xp TspehݧXbqU_YZ>M(?g_^ֵz]\]Zme[ѯ-iBfHO6żT1o)hÇtR}IE6MS^$wx™_>3RP Nl4QW0XWq(?o[?fu+*񬮠{ -g{Kg٤ "쎭_0r{\=j*9ƥ>zRp%H9BW))'twfl-\eXW T1ta^V֌*EUVNjPRL�7~7Q ~#g%A[}Hw֐A,=U?Y1L^O)bbpm^8{?/~|=ӯC'Zt)ƳjYVsUB*`q5ZnjaO.O(� �(� �(�<C�.�h �(� �(� �(� 0*AAApAhѭS]�ɇb|Sh:oؾ|w[>xb[kZ;"*|?Ǔ,v>!ӠL~g4 ߼5/{�kxFMq^ߊ<:.Δ^G3 7d>IunN~yP@G1:FWU9ǧIJ*Q^N/ѫ?�P'O�tmOׂBDpּ%ut4+pp#mOkjPQOmSMm�*H^;xõʒwFQQ1Rrx,}H�>] Y' ڔ~%ռp�j;;Bnn?;3QRtB^qw&լo$ّŴ?>U!<hI1e|&wME/`1$mnP߰짬_'K�k/szx+DWN kINKOen< קA;s_N:[mdVLk^xQŞ"Qr|id}j3V<7{ԥtO W~h_W4k�a o{>>5j+?3N YM`W0ikj-></ ,Nqrԣ7ʥ9rIݵ-O;�O<:Q5sㇱ~U+ؼ[,tP,VxN*:L=>|UXhY?�ޏ8<!=ǝ?OіA5mԂIG$R�y4QOxTw(饽/�7ÿ؟I|rSJ֍I…|Z+G6S_ֿa(ҿ7WhZeK}kTމC\j0 u&x6,B.ܱ޿;wO?h3졋x\ω|/Rn%xT3<~=ݒ7a�baIj>�GȾӯY 4H.ua%~61}[ VkJi^خ�<2?pb`5<AN9O'b,M EӏN,Vsis�h~ѐ?)|g�p<i�g^qf[]!׶bmx_|Հ%ܪ:ڤ=ٮi>}(<5:T}_-w[:TcK=ژ t-ʝ<O/0s].|~_V�ĺ~?xៅ~ x2DGoi&KKr!<D'6RYUYSw *:VjJtSnֽIMԌUhg/3�K}2N)x3nsU8?$X,D7rl,nFxjRq� Vg1C_ϊ%7 GQ+Ԉ<SlȬsڞYNJswje^F"xR̲ePS70O)v? �SM�?(3�znG7�w/-�;|� #BJ> �k��?�g#S�KMg[a?OHe+?Ԁ �(� �(�?M?ُ+G߰�#g�(pˏ\?4(� �(� �(� �(� �(� �(�??,.t?>!j~ Oݪ^C+pX$<�QIK|S|NQ̩Rx\ʇaW gRwgO) sX쾦rKOm'҅y4џmvca-em2L==&1XlF<E&XՃopVTkRiR ބ?K/�>AK'4[- Céu_+_W18t8M)a%ZUpu#}\jajQz?)g9f~ޚWPPEٴkBi[>(~+|4>0|>Go\jvtJFSqk9/u=cj~ ρ'̞ec)㰔ԪVJ'xURp}T+So<n |If>upWp]S9#[f5;_Ok??R[�� �l�.\�A|G��:C-� _ GO,o3f.�#�Î �!�tc�/'p7O~K�_��Γ;7ǯ|1i{j!o'b*@ib̥`@_n7|9xel4㽅< _ hҗbqUIU~i^YEBVod)Ř#Y+R[5bN<Ї*<ɷޜ?aIo.xK֥R2 4d|U<²Dfpʼ60Za`5Ub) Roq2HaZOH>}ϲ�n >; ?Þ-ͺ1}^Ki]{KorO!.�2K'3l&T`jc%"漹�ٵ”0| e*QqaJ5Z0Y*Pox^ƾ1DŽl5/FU&y!ב+6S0`u1h%T4?$ΰ9ia\onLw~[u {Ï|66z7t+" @x"^{B/n$a 0N"ʰNЂ3}:&ܤ[Gcx3n>!Sqg+J(U奄êXj1NѧN+_u&�ū;KAsqr+ BY6h-dF;>X7~<pÆi琦 D`*۶Rp{8Y%wo7'k&N:W0қtgymXqmF5jҍli{k'_oc[G=ᎩHf<1cxNV}D`� Sֿ.-nsp5YI8iB}uKKz>\CmӍ,.ugzTrhB!E+x*4s�5�xW2KbՇO!gh헱;-95�*6qv/^'1gZqYZSpxlu\^,8/<xW*FkoNjחZsWd|m⏈i@n_.DWKx <>!Ea�f߄o>C=𔱹O9MN'8աW8u`wRRӌ~]K*8ԧ' :5iBTtqkJ� �(m"I{ae;a,H6ޗdES';`s%Ó�o=6]ᯏճ O.&xz0O;_ӥh,6.KZmG?F#x"8LeYVd8i7y ({LU)F�Vl��kxo~֡fVլ;[n:1/8sȯ?/xs*W%ܛ7 rL4bh{ӭBs4՜d֧gRxW>ʪ2/,<Tg(Mvdi;3/)K~>6͝Wxh쵛:ؓKW� �'ø~'+V1<+?)Lլ.MmQ6BJ>4%EUDzETMu便rO?s?ϳ�S|~~v~?1o:S@nfӴ=_\A}w" Vq2/1\O~^R~XP-'9TЌ98Npe,TZs8z)<Ukf柽d_'!@$~?4ߌ�oo]Z>h^'&5h\|"6Tm4~~_٥ҧrޒ-<F[aƢ|>d*VqG(p&R|>;;':X$]Zn |IF 3>�_�8?.=��I/?A�''O�0�xC{ �'=�kJ�.J0�i_H;\C�^rCpOO;�5[�W7MG#/N��qS�xKY(|QkP>iYl Hﯭg�24?)BSd\$qꘊ?Jz&HI^8o|>7+SSp.QOb3|TUIŽc'ׇ2wqkkD8{mc\&�*P\Gy0^i~ VhRZu*�NoH?;~ t_xm3šf_k.VKY8fS<1mAGXW!YSYf J5iتW4iթ*i(FsqbQ�9~!?~3e{ř?=apLG50ym8Gఴ':ӣITZ)~{�7t_^ > j ž,{‘7(W!V]o_q/Nac/\Xo>+:tlX[Ia'sճ/ 8)TA:YN ]_$:7JyDKqo|;x#dyOi:8ma :17�L7w7EtN'_Q�G2:=:3 Ҋ78iTc pTAZǃ/8xs^u ;_j׮dm8fx.% K耺_"<fJ=^A5N1[+pes+ϲʨ*u0ԃQtcRQ%&g�-?h|C'GE/5 zð? 麍.m{h.|^cOV�Wr 1|kBmTʜ*F𨽵Yvw?(2<ǏeVr$Eƞ3<M >%j `te8sG(Y5 �(� �(� �(<C�.�h �(� �(� �(�??_W4e?xLoo|Mɂ"G-.>&ಉK]6RniiJ*ĺn]xWWxYb+)c?aOeVZSqmWȟlMӳO[A@�gmG탦ZqxڝğS*ɪxG[x"v`|ж^i`+>$ħY?z-է~ƕwmGmQ%~qYwyNS-⚔p5'q)9i52-jW?O?wiO~ k>EWNOaVHd'+^z0QFjuO_cӏ3o x8$ΰY*nN44SZaWJaKO/ ^ԧ-L6H4vE h:-ՕL&iln7v|=ZU0eBj:#eo�Sž.NK\Re9IҍHO#̧2*撕^>kkۈ,溻;{k[h{G C,q;U$ٶmJI]jҡJj!F(JZgtӂNMFS&Rj͟ݷc ?ƷE|n'�z=x_S|5iԒTEXxŷYj ['N,|Vrz˖T`Otov/Ἷ|]l C3\-N|:L3_.#-(TazXF#^O e�xC�vj:,F3DG?<iNJ% "9;V*Q7'<O٫V$pqp|0# O)7I;{}-/B\�c.�]~ ڏt35'lmt}kGԦEY<;#:"wHTJ)CX_VV8q x#:WIex3 M8XE>i+_U{,�s�{Yjoi_k"<Vۖ\Og2o<:t߻zJ?;Iywe ڦ:*ӧg05g I_uҌuޒF�Jm졭Y;Y‰6ͭ|A'}9Su_M;yBK},ɽ-y�7 À<\WU5pLWNsZsx؟zjjrIchPKb[ yK<׶y(YG$Һ�澊�_CyxLKYמ&ԗ"!Nhҿ":t M%'F-�Dx|l~,/>xS@G_42R_ ܢ9'zjVRR4"׳}>zX�leFF :z|K0mZ2n&٤jMwG^)�_ҿ)�GJ_ g>/�`܏o�$_%[�Lv5#Gq/|A�c�S�>Ͼ5_K+3L�mew?ꞑW_@��P@��P@�~#Wkݖ�a�/"GcQs?q_dhP@��P@��P@��P@��P@��P@�| � ?/ _L=Q�gC*|e-%%PFX)򘃴m-KטW'9߳s1S裆ƍIT֗?&<Xj~1_)^uюJW]7DURA_?֚WO?sߵuw7T[K=nO=fpPٚG+D"![xϟXʍ8{*հx`^4myUTqr S)8P//a*OZ<UYb2JQX/`yI{uiE=A'*UP@�L߅^Ga_I&.`6Щ qu6 ?y,PC|3q3asmvu&M#N9T!)4<7d9V;7*Ǝ94#v9фUܤJ/ůZ~6n/nC5ج)!ō٫)$5Y, qVUj҅5LVzigkf9_8ؗ$'+Q�pS�s |OB՛I.^3�ox5)+Q;ѢSHpq,rU,o5)Zk-^UZJR]d~r'l~}RQskxm7ꂭ-i=%�iV^y0o#Po-!5U=28ܹ~oꔡƴfW'8駻Zz{9<1tkMkL{NO]$וG�&cNUO>$xNGu{Vr+;`oxJ,/p"*9FF5w<%#.FS83(a e:˫[]lc\� >�zv_Q8FXhaKOR0zeJ10Y5v*I{jA;kRXF ^?>9~+0Fta1క'.TwmmZO?$u]ngesim*N6 r$cV^㠮Y߱ŭ^i� x~5FM?)jZi%ND}f7VbdW#+zw_xz>" %t|.{a)hZ&D}TDea[.U:<4Qϰc[,g_MC<h~I8ALKVjN_*aR3:Zmd1(p*1K1FrKSwڜ^-GP@U2x6Tk($[o'>yHO\?ku�|,vxcx[j_d]ՆE)*U *M2 8VqU ϐ4mOO2]q&tz2+51<Ga*󬮕8]EE$wc0i`1'nX`2VTg{/Mc@ZG>Yln#/ sWe8"|_ &agU-ڍ}%#,G{zIPVKENFpJvg:X?amcO�d߂wl1xO2u+k�y*pp|¿o6  XNy,LpzԧwxME4WIe$ԣZ:vE4}g_�pK-ǎ}E,t� WL!M.[9\WG`RbsdOIE-oval]FR6ۧ?YNdse{~�AME'I?TM96mM�$`�#kj�dI��&9?^�A�?ҿˮ=� ?GW7N�ל�PN�V�Se_�H �2�r?A๚mWT&<m87RWvk18 VHSsz~+0Ny{ $^ЕkY�QT|X9p.+GJI.U1k~KyhD)H::̬8(EFKtӺk}SW?JVT::8Mr/m%nUx&|Uk5 Un,vIw,H[CBu~g]<v+םi*BmE$bQ{xw0 *l)SMhԩ)9JX׭9IUk:e&ܦr| �h&xÍ^mIV9c#SI 12[i ^Ԓ8QyFQN0'EukYEUƄj;?x:u!\c.Pܥ�lq Wm",3Tj`j|��<<,Ʒ;dCqZ'Xkr$~ QHId RM@׀^80Ѽ J�u~G+Kqfl9w5\ È84s|qwJ/g[QQhq/|17F5k'e rp j7W2^V5Uea2| aЅ.g&V}Vr?Ͼ/2ΦqƜM.cRN_Xͱձ^Kxaͬ>QңJ;Foc>L(� �(� �(�?<C�.�h �(� �(� �(�+^j6wVsg{o5ռȲE=mHF쬬WM=ЯW ^"IRB:jBN3V8-J2ij\�6)Gs~�~[IkUiB`^*Gl N/3#$$D[ⱴ>N =k5NH|^g|u+®yK+]J|Iӥ֫ۋ̨.m/qgA�7E~,#Ovox:վ1twflHHլum>l{asqm()+u'J*ӗ,.hךkd=,ϏC=~.C18SJ5 fab!TҌY_N*_Tz>x㽆}cQ f x_j k#~M .'[]reYdlh~ poi/IY�珞C?}7ళ ~*JQsqqRwO upkAΕ/h?/mK=7VZai'>.Mn�hM+WK,/E|5 JQFRw\TxA׋&Kxsƹ W(e,r^*',:z7VtnWo||x_?KFs/kz,hW;ּG&t҆*&ہYRt')যiI>sq�^_g@U} |Ojd<gnydYV[`q(d 8N{z.&=g/� izM|YYbW?cs z6 mw=*40R0]~h^Rmϊcawy=!Q2|3i{l8ʎ]J\TNڄ+[Q�~??~ �i'onk|cx,.t׋Oyt=^_\ {卜9c*t)(F_չ斑o yY1�}~D7ѿ$\1"qF9`O켟.QWKN:u1!SUGhҧA~6Hlx|9aTwurE஥.n7Zτ|GjѬQf-Mrl.JB? WfVN�-*>�̿ڍ3̼.$JS q^e9-! 㣀v*qմqW0Y[\^\ȐZA-0Hi%ٰN�8}Kvע�R^:4VHR)Ԓ"mɤWK�0(�7�گ?VO2'|3ᩭ0YR NL]h us_ Uz׺!�^Ri}P}|7y(}_1dX|;UgLΝk*\F*X媥X�noS^=h>6+!D- y$>\um2!|M #5-a?vW]M}&3~t*+$dI9G=W}3 JG]*#is43Cs W=Q ʁ)I3+RA�9t:u!*u)PNqq'(N2JQdeMYkKgg\#Yy$8ϋg'ˑ-1S g-9󍴧FO|keӯ[?fB^*N>.<3qiեKY<$ڊWV) ?Og b_~*N__Y:φAcJa-7JֶLu=u&I,DAahJJtVU'Σ.HW&m쒊?ſ㟎|Q\-\M<ØVO,0kb3lb1/,찪Q:nXLO'_~_2+ @ FF0+��K(ƿddq j?Rz5>õ+[KH!5 {h#X(E8DP�zjWRjZIիVTRNu*NR9NRwm|W?fڶ|i⾷ؾ|n$l:kkWf3).Wq&BY)TNqR#&wi74kme#⿉_8mu8w3<5USJ Ԥ.o#�oW He059mtxnm�A,r@du` fxFxz-5fuO_}/Zui=9p<9UԥR7)Z8ڗIo~? >-𧋭 kMI{]Dڜ\Ku|Aw豉..mX]5լ7bңJ$US5vz]_YD�B+Ͳz3 WF;ZXœ)T28\t:tpV>s �(� �(�)�r�{X)?~/؇ ��E#cB �(� �(� �(� �(� �(� �(F""4r# AVRAr82%Ÿ2RkT5 $i{4i^_yIc{�?>'x3LO>=ԥ-o xC-֍sX,og$p؏(l�?E"pϱp*?y8xt15LNdUb=jҏ>L{P6*˱dS>[ѭ&aT:WVee{w]_\i{g<W6V43 ")PI#u *FFy (h)SB9ҭFTUR.5)ԄBqn2Vi>JjB)ʝZsJu!'p|ќe8-&wO],AG@4'-ckvaQY]T0E4C늏es˩}PW淎�C WgeS4|RY#KrnQs}f0y᧏8LE>KMF>�6)'F}ltgHi:iŎ^j}kyi$ã콿qn*1bp5IRa1*4*GZh­9b048Zq8zS^HVViөM4(.|qW3ó⇌ XGZ\$$kh<E N #);d@Zς>JyGX)EVBaptbU>x!\eь[*M<F"K]nzNRo>+ | ѯLOo��JгD ,4Km<5 $_??G\3*|1<fi?tjG~QqQ^aJ�P_ǸB_Øj|mHs|[ç(Q~ZxZ=X\zƳ}m}O=ܫ gy$€Ǿ1_Yaʰ810zf*N =9ԩ9(-�L.#R|N*(PN.SVa&m~Ζ߳G& j_P/b-+&7;¬kA +q7R68;gg% o6&eΝ9_&pl8'y\a_4{ʱ55%0QZn3o�<%㆓h+ };\x�s ZX'P} YOуT٣iѩ'gƴc mgWG <3#Jy}BU\nUj`N*rX[{?]w6vcx uIaq$n:򮠎;wu)BJPn3%ZqkѤiSJSZT:u"8iM4?'�uskyZESZ]mIoQa%ņFSD-u#43[C/g\q /Oae%d02vs](J7Q5x#{~g> xWTn$s͡JRQXʽ|Xǒ/Rr*Ue[Uj|WgЄU�N��rI<{P4i$n-[odV�]?lJ4ί�ꍪYI@AixfʹDJÎپ" U_ <E)sSxvjܫ5)%W:J?75̷*`3n#, eRեֽ)te% 881ګjS?4:Wlt$.mGI.eX5gfwp�U'ֿӝjҋZӅ*q:QI]Z~[+q8]XPVbkTj4PNUjԜcE۲[So9`Xo Z6 ۚSXV[M9v�|pJx| &6+'W{T*w*+#i|p^,qӜsGkw0\,7l=:{Y6r/㯄'ďZ,+5>MG-#x <qXcW}4,xQ̮xi82?[ٸ`3ړ#IEW1xnҾ?<!j\O&#)xex]sӭis9~򇲩T|q>5ҼFG!푂O2$ld 82ۋ:YK+Of cM|. bQU}Ը343`+]R6Pm)+/׽[G:57u+5.XdX4g)4OG�/RpwS#a3~aSunTqezX=U+FT3ifNa5ueJ qZzJ7t*u#xN2iկ<#^t i7ֵy a M<EƒdCGyQ+όg|G&3cB(BotuqjXz֫(ӧ IG2=pVUc*ƕ )O╾ pWJjrI3⧎=Sr)Wl9OۣdH $J_MhU嘚8kVx.¤`䭈$ڥK0(? e&xƴmW])Wq v*rvr5'$t|!?>%h&{-6u}I),_<=gpWbWC6]4d7S"z4rܖqVMJ+똨O^);񋉩p5E ^aBYf:ONm7* [V�lorn>%$WVQ0}oC5]6>D6Y~`#'�� qyr/kex<[ѭ)+is46"Uj4W7dWV\YE$6Ko<#G,3BK�":taXrANJuӭFjѫUV)T:peO�6g SԌ:r' +Jdڔdig|; V-5]VM_֩<pɩ$QjyJ2ޘaґRK= 74sSx8`x )׆iSB3do ~ʬiӛ? a0qy"4>'%VJ4ZrgUSR9'P;|.>oT[Ok6s+Ӧ6al}<ROvCa_5N o^57:ZX)5ԏ4"N'{ɲ/FysO!NiO*<BN4; ̝ O�$`�#kj�dI��&9?^�A�?ҿˮ=� ?GW7N�ל�PN�V�Se_�H �2�r?s�o~'%줽~k6/A39tq]ZX]K~i P8e_ a麘 zQMt65)NUw�_n 8a9˫pVjtY_3\e xh飭u_uVӪ�FCcohɩXC[tOãho7к;Sc}} rAsx|˸5UvJ5Oat嫂U]UNE58Ǜ{GS-yW98,Tnv3^J;0>,#(ԡRPs mX!H5XTEUP�cB1"bcJ*%%CUVI֭RjdRYʥJ9ɹNRzIޭY5QWI>~˞<.WI/-|9ZׂtI&IX.d1-œ5yڭ\iG +)xRa| ֕&S&ӎx)^;_<>2 ej5-a٤%5p}8BgXq8R*֪}g)U�ef\k kL!_2\C.MM.og+U6]Q¤ R?@ߍ|.8܋4E>l&:eʱyn6lvz8ԜS|9*)B>^@��P@��P@�<C�.�h �(� �(� �(� �t/+_g߁SDZᯉ`P|ÚnLn PWpc]/z;z^_U~O l[M.b2X՛X~-)U%uP'�P@:FhꚎ1i}sz},9ahXFWSEzM'I49,c0lv Ntq8<e X.",WօJUi-ө FI٦4c��|(m} OtHR {$\k>4�EY�Pc!㩫G).e6ocs~_E-)rUiʥJ7g\3IoM`r<a#��|ELiwvSFc|?||Ucּ3?]|-(]O c+<Kҧ?K9xw1[D1Xo 0zrR,߈8ӺR湦+R:|5pӋc;5-~~=g|m}IG^-5?ϒw]꺽$Nsw)Nn)]94Wvӗ&^e<?eU.-0\B=0tЧ{+_Pz@sğ?)wo:j [OH')YgԴ' 'JqNNd=[4i{џ-Ɯ>!oc0 xƭ>e:u)ե:ua lM Tໟ� X?t?k uHiZ�ep�ݾ,K5wV1'8B-Zm.TgihՓ򟇿@?LJ<],*1qe.(ifyFY>|>"_L,Ҟuq H6WZ%K($A  9 #P &jٮ}> g{EOiЭ7x:4ZiVS_tX6ƹjW>DGw-+ѧiSTԩ˕% ΛRRjqkKIg>>mYc(ʱY>gd{K1.je^뵥:x ~<J(9{ߒ_%xYC_5xֽjSԮ#$)đZZCvCm1')u''9NSM $IhFap d<G Y"R ph9TR)U^J֝JժN9p'v ƫ5kG*|)umA�7+`$i/%k^g8].z]۩3|Q^ FrmVpm9ki�[~՟s/Uj��տy$�Y!1�ΐ�??h_=_?W�G� k<9�#?ߵg �G�4{j��տxg?p�:H.jڊ{;OOͭOHdx%4R)*Ȍ&VVu�sJ^xSB:<1jSJUp_ ӫJ:u!pZN2?WwIFgGb䳻eىff9f99&I-}[oWc*1J1QbbIY$IY-0 �(ʎVrHPKI��I$�IvrQc-v}G"8_w!jݟv?K[/¤/>ms[*�Y+K_=?#~0~�\cu9`k)b9OJWVm {K/σ|^<_Tc`�X#ȺplwSs~Βޕezh�)q+8絥OxL/)+:]u�,m5Qol]0Zn<w K+᷅OYzoO{̚n3V"vu<9>#u_c1^ڏ�xWÙ$j;|Lj*=&Yl8 N}ye_Vrc߰ǿ >M7OntxŞ'6crjVz^xeL:.1kСpSMU9JM՛Wm+4^u'Qpq)poN4l'4aVjtky"*ӄ1ex.k.��P@��P@��P@��P@��P@��P@_ּ]}2xD4*KPk3}并Y'O*wΜRӅH7 &83<19fg)J#Z<М$jgiBqjpRM&0�oxݦ㿄2O-A%`S$6^Zݣ�Pj+E>&aypW >YTJ+RQKS⩗Ug}Z<Jg7 <lׇSS 2W5J0Jk*=正�Ər ~%yi:i7d2E/X\p�E|Wxs|uOO$Ϥ N7OԢ�aESRݽ&~$&sdycKզֿT^zN7>ҿિFk8uv@SpgY!cԟ/��\_UYU5'e<RV""I?]trĪ0Pkm98'7'F/3)w?"_. FH)W瀾MR+& :jQlƗ2,4pM eNa S}RVv}FG/WYx\FrIT}{睔3_jt@�wrl Sr<.ɲ_ex<> ?cJ_ޔ\Y;c~i/2bxf"&iVM% ƺ 躗|A\gM׷ח,p[®ݨʠgWf^Eb|0eyfrVHsNrcI2X73Zf&j 6թ7#w$۲M��^W{lJeޝ {H1]XGs}0iI_gUJ1ն39{vt2IsQ;U(gׄϸ<EVRO yB^jcd-Ji+“4~|JwᏋ?X&7ZNnnȷ7+i o2ʊTםex<-eY%[ :ٚiJ/GђwRIq>K;8ȱ1*ѽ:u#:Μ];_m(xR?x^>0H7Z8rbrg$Z<o#�f|TiΦ,0Q~B8i֤Z)'9 6wF2liUq3R(~꣍jso%c�DŽ|MK%Wq6 =&1#|]fN.?,b08jR=Ҝ%>hI]8?On3ɱ|=ř&]&:3"ѯMKFtRNd7ꏁׇ4xcᯏ5Dֵ#U5yʠ]Ŏ+77ږ%q.0cGIE%ԫkj,-HPo4a}cgG:x̏<^RNO-c2N8yf*qG*�v�4 6|7?Û+U)%}j 1Hh&\3-xGqG` 2u(ԫq*Z&NpR٦7?B|4af<ga*F0Fք[0<IJ41SMI),YfbK3YI9$�?_S-VI$Z$KDJ%do%_kK|Z͗tYVᖁ[xW̠?tuQ"ȁ![3 qT8;úx,;>*qӄT)=hs/JPGP21g(⳼V\YNQE9Y8hc.ʔS_Շx/ǯk7Âm1"O,TaEL7O&>AqZ9 pّY|8l7!N7qUN*p.% SՕ d(vSJ?ʝ,Μ5xyږ:6s5R6JI{.?ڎ}ڕWs=խmM$oʰef�L#xg8xrnM+(O b˖j{^\Щ(J33.`T[TZ(I]y4OGf;Ué3Kom+nUnˆ2ZL82ۘg bjÎ-1UUlw ftap>u+exW$x <_/#主þzqӭJRf#{I ݪuZW٭\;6X}VW=�WU�a�%{>O^'t9okܯޜ~<!"5>]9w�Rj⧍"J]b[hؼunc1B7P%8RPT&)U|HFal ԡp5(8^Zu/<f.We{|I T-|¼'0x`ңJ>H'ZO}}w*CokmK49ڪI8?<b r sw,g9q2LF/% tҦ&w)Z)V?0~aiN|EyƝ*T)JRi+%K?GE�xYXդ],�4Zl.2 ~{RVI n)->ʦy>'Ď/ŘN5i崢;gC猫ɉݧR <�jqzsAJIUURkQ4(9p=؀Ad==z?:?!?)AF{导uiг]nsk#cD$(YfIo<Sϋ1СY]/7V1sS_˽޹7)F1^;U2l\BO/K�츉kVZ*5o)^ʓC$$lw+VWF�) c8g 'qR%iFQ(6֎te(J-QiQmI5iSOTֽ?F �R?j]] na [�T*G > #VXvu[PE!R+fX|e9&*" jU%0')7)0wi7~~;qЧ_ iF0ޕJpZ(e 1O,C^j;W_\Wt71<Rjo-uU׈u=Vـ9IcgW 3'##JjpfX6 /N* Ս׽ c9&sG8ya|$sN/[IbԣmMr_ |Yj>!&s%jsRndy8 ªƸEPֹv]6Y`n[L <6 FF4aݕ+NN_x_V5%V"I֭Zsܤj좒VF5v?��I/?A�''O�0�xC{ �'=�kJ�.J0�i_H;\C�^rCpOO;�5[�W7MG#/N��qM2ZoR7Se&xe+$R#a_ҕSRB#RhJHI'iQi4�e.?ZKRJ5TR'$?/#7Z,./~-n.%-č3xWWwՎt8}^+>%}ҥ|=)mYe(78]NjVӌ�+$K X8_AԩNsZt �.rcӏ6&J|O/ko'~u旬j6710dHl2 +&/`5<J=IRJkg m٭?3)f 833OP֡V2T{qR?U>ej/ivW4EچJ#Pw~մ:B^W<Rŝdy,ʳX5ؗekxJ6o&k>o-q:;"xirv2!)>fs'Fv?ʝ8J'?c UEOIv'J4R *kZIf̤>oGpW6ug¼$R.2Q<UGUvt z +1s,<Eǘ<Vqz9LjA.[ap+z8F"iq挿/5-OQֵ WW5MB[Bk˩55Wgwbŋ~ZTZJjU*֫9NISRMsov?pL. p| F QaR*zc TSQ:pR$~"�fob>+wۈ'OHXcm$7" @x k#W#Xey-hrӔӒp-jT~(S?gfKŜstLE*xp5fӒ100XUӝN_z(� �(� �(�<C�.�h �(� �(� �(� �!ß ^.|/ > 敭OÀˆS)  % +qqiMY!OrN'q50yA`s|It1~&'R/McR]gl�1�ڛ'�f $P{GH?,lE hZv7,Mt-zeROwZ�W>xxʟո%cЧ%%8˥f,iCINJ.^X��P@��P@��P@��P@��P@��gi7o_wY7~4<qU2,cM_u_rlog^0N N'7{Zro]<U%7g [QJS18e-:Gj;�M[TW�BuÒ8!{iڟwÛOs~E Gt83*̸K0BJIjә~|&�e#_- 4_^<cphc|Ks{Y2Q?2vNO}<cVxW R 3iO $yvQC:8cBOnh]� 赸7>,7<q>*4[=棨)Swfg5R_&O>2?I$Բ,O pj z7=x$t(С^Q!o�^[1O'dPx�dR؍Ć9xl=ѧB<_R/cUAJx!lPץSh)hl����::t~UPP@��P@��P@��P@��P@��P@��P@��P^49dEd7PGFʲ$2iR'EFQmJ-jj4M;ҒqM5fii?:h&G_,.xyl=fT!ܼiRh2-ñ5A�1؊eig<vPR!X0ZFR?7*UaT3*Җ'+8ԗ`'**IjM\cKHxny߁#˼jmjUQpT ©c_؜35N<E J&'/ ] 5c?>iSʳ* W]ilK&bw~g?Vp E!ǛwRo.,5{u~LCLj6k: SOqMuZ|UI7 p6N+`CN*qk^�&91~D% ^[(JCm,JWFW J\o |96sC )J^?Q\;,2<f;|xTi_Br잌/Gǚ͵_ۮ4_sje_ښi2Sm�ӷ0^̳lV*\^3sW>AkͫNN%0*7N*x."4(RoFV.G?Gۦ_~V]Uf sKky:dVVo9_<e\RŹJ*Ssd(K%jTbqSbq?8K8_:y] xFakbjk7wƝ/vO �(;+|!sm7~Z<Ogro]麍msE<mъZs*\8R*N}KԥQ5RXcR(i/׈0QY7!poa'6BtI(~ x�(xW<T�نfo"vSi!!Z;*&ɑxR~((6jJ. xTN['V'd&�A0|'QSB1x CJ418I6X\wB lx~[е vr̓�+"2$ "g,,%XڵͅvNO~g^W6f"f 8fw"T9lo(%(?)&o�)l�\B�4ػɩx p`Oo5ip,w xJ*UC-ڽl{cBUɭ?ӛÔjK (B2t\7XUZ%)etv\ɫBI6~~?H? eS$DK7M>vk�</jMk`AGsp.KV;1 =q0N.~uiG_O;.39˥:731zpX|\"ڜpJ^9m/a !QbbPh#4P@� 1b)()Y$IY$X�?jTV՜U9TRT79r9ɹJRwwwl@A/ৃ>&jqDz\ӂGw{ p*aUC"Fa}lp=_>q~S%0P*uܜg<Seʧ>jF;4L%fIIR|[/.,Ix`N*Ode$/'@&$e1凖3y߲M,F[NVIaJo1>< ӧcp0jV_ï:.W֦10yW /a>h9~ �=x�N~n_k̉a{_/5~n{[]S~#'\o˹{{,g�V翗.^V}#?W]Vh_K͙ n.oʴӞhdܩ[e8zs J/.%&7U2WU(r_ $x.R9-,Lj1Jꟳ/Y�6{kO Vmӏ ~ 3bm*ʸuKTTYB D VYj�^?Dxop]W< Ecq*8&{C.:Pd)R}N'n;W̱P= O Jϒ.*|ϗk(ݥŕvwpouksOoqXf@J:zVkVէ_V gRԥR JiBqi8.dEJp ҫԧR.8q(Zi5J�#|Cşu\xKVwKt>#|a,#-#⿸<*jq 7u^,pY:CFV%U4劷=Ya.מ"ޭlÅ1Pue)u2t#ZH҆;ۧ·Mb#Լ%j֓iVzH{ :CcI/:괜WRnSzT]o)6c}IAd+v3 5BU׏F߮�o!巁@j3a g͆{k2--,//-hT&8źx<+b'V]#]z+uwh*3lάcS(Qm)3<f`RS_ݡnZs~[�<~gƏRcaWVbCџJ%O ӗ\o%[~gJT<S>ʰy]0}67B2s1/O(~K/s\mhȱ*SP`RX de*ZB4yO9]ϫM/wEKyR&me4~=]<c1}G!^;�[ƼZd%,mK>cNP6*G6CO _1)~OSÞ?i[5M6}lCEХ[SϮ$,;19JN$])ٽ;o/T9Giv}pxHScKIN9i?f?%oŏ<K]V|%ũ^iK V;QyZ[yw[u$+,)ͰYlv<5^y’%գMFypuh LC&Ul>U#:hR_ϯ֏<) Zmi~'ιjzZEycy)U<SFRhdU)2c, _a0x:u骔E귌ZjEqq.s׎#XzwN q JH7 [[C�u8#yOenC,?monOb۩e2j W}RLO fPNRIӃz*0JKzMGal>ԥt0uW+dX|-JITM&�2?m ^=|Ե胰�I PZ5~6\ߔc<" 71ImS)Z_x<lƬ<Heri9a̟;+m]~RnzU.m_]G vIxĞ-SImyH<q(�x=:J?kJFJT)M-N!l~YXܓ8+Vk­L.Moyj4�|;/Y>X29czƥsƎx`ѭ$%e/2JX*G6NQR˔fz寋IZ3q/aqy%^!:87 gΜ]eLZuqsSN?^hzmizVZei}6v66v# 5 Q""�_ThҧB*th҄iҥJN"Bcb3,^'01Xv;^'֩qVze*jԜ9Rwo_zhr��P@��P@�<C�.�h �(� �(� �(� �(7��d't̄>|Ss}m~fЪ]ʬwZ _ΰڝO�^e~pt*p,?y:1fCH T)&HP@��P@��P@��P@��P[;ᵲE2O<QDHy"Vu&ᇥ<F"pBr^F8ϖ[M%} smK`arш5w7 '"X|CV4AHr_ ā]TXRʽyȟmo}&.NA?iٴ83ywG3FwVFn'w~| g\|s[| 7szfAF~_0cuwSq2$W+uch?|R<x/gZ  U$\ML0}Mg~T?M�?_M�%B,<+aĘ9Іld=QQ_īRzm+j?C,Tpg R+f|WB?fI&\%ׂ+?o lZIهZ5oNiӑqǗ++T^ O/ҳ6ԽG%K/M_L̀ኘn¨KGO1>[^-֭.><5 i4_ J4MUUN%#�8.Z)E-VK4ULvoLmVWUI7vb15*֛mݹMڦp��P@��P@��P@��P@��P@��P@��P@��P@��P@��P@��P@��P@��P@��P@�S5 -&SԮb-CmmnI#K1 ha0gj†5RjPN &]ٝj԰W8ңJRݣA7)II]gুFԾ,xǪ&!q^DƦNd 17o_~}71^ g9`&g!/hX,OX]a$_xy\eԋb})a Zʲ1߰'ƟFM A>*h7i -[d..gL}l!B4W}$>~Wp�aٖB# SOQԡFqqJ(F>Zwr9_67 ,&>U_ᔩҭL<jիRn zj~q�X|:~[|_RDmu{|9o4Sk-|nÛ ןCqO^N q2VS4 .ӓZ5diQ/¼|6MrHU~ӜpPe8j)Q[Q\YQU1'�($�uʹm%vDgWӫȿa e> /oBJx]Z*bEFV Wc�ۿ'R84UpGac*Y]8`dkWZVt? rø:<VTX[u!}U> �(� �(� �(� �(� �(<C�.�h �(� �(� �(� �(j_?g<S7-Ydҵڴ n[+Y  X "kѩJ[N-z;hךzSE<''B% II8?pZNq%emiՒS"xğ y/0}3>6OXm[An4FtlvO d,8J +JqNϾuc8w2+2<Dqy7eX +Ŧs,5<V*UN7Z)ބ6B �(� �(� �(@$O?j2NOW}|3;g/%Q Ğ$f{'M[bA.ժNIkEy^"}g_8xV!ʲʭ-_&iRr{$YC�-5OM\2>)xEҤXigCO8$ܥ/ lzqZK48�,q0p׌nRK3,d\KY5q .9~ }BSZ Di|2Gkڜ:R>6)q\!ja|Cm~XU~~|'� :Kcϋ~.jb %,~>³ 4C»8*zJ�_t\bq.1_I*U)`8&,ܭC8{s9g%.O߆[|#+¡oxzW]HHKss4yg7vӡF:ToS֯v*qo#ZӎeRNR{eA7{xlN*uB dV…��P@��P@��P@��P@��P@��P@��P@��P@��P@��P@��P@��P@��P@��P@��P@�Cu\E$3D)$RIuebzu'J*ӓJsHN.Ҍԣ$4h34 Qz+4O?9N<Q2y~Q<;%]<7`m/.bMb|w9OGG Xp^RZJgҡӕǖ5E9R҅hr�x'3% ˇZ󭃭 zWPj\öKWpg> >I5C~&K i,H<<LM%AgMN7" 9PqJ5`N%iҫM­9N(Q,3 ̲]l; 5R"*BKdJP[Ӵg>�h<Q6wυ6*mH^֏'t�y׺5ݎd.$i^ٜkW>3W<i_%թ)=S)6,>? _ ((,M bj?/?"YhF1x̷9Vik:ZԪSn^ʭ8_jQM(|@�W:d ZHj0+r2%6iBMQc dn>>JSdKUbUo(a*Oћ&kԡ:y Gj8SXl5\nmu-Ɵ|:$x~"srʐZ[#1OӬT4w4i%Co,'pWC*ʰҥyUVI)quzU['RYJN֊ݟqmٕ|:TkJs 4)+B]Sorr߰g췭~_tu>x:]Ѻ$چ,bՌݔhm7/\ E9qF}B[%$*[2/6u%6g[QOGxO5\:Jk&*R昇!.jxHDbeUی95]A [Đ[CbAQFDQF@�WRԝZJ'*'69osۓz61#B*0TcF1b$[- j �(� �(� �(� �(� �(�<C�.�h �(� �(� �(� �(�w"W~|1yb4o[k?^4cOf+IW|w12LM|su^+ݬ/m�/n76LìGrhթz1&(9OhRq?(� �(�64Ok%m躶][]3Fӯ5=B )&b,q,@i9;E9=o䖯sc, <ni:iʦ318XF*h%DQ7 Y?6ޔdgeNX\d\~ ^xrk1{a)5OnZIP_<_sߌ~Fμ]l^*qj8>^Q;jթ+1o |H-/gMV�AHK_ h"le%J0. O$TGr�rO.1^ڏp'ux]RrjKw_[Tamw?x@uG�/^jzO<5p×? W?nQPyy.?ĝZM.�ɼ.17>җpFJQgq6kM=Y"[)`OoMCੴ?h5 %Qmé~'][#w mU{p Qzy˙_J?3ϗyw-e%r$m9gFZhֲmaӬl,bacHE*Ftu$VKdGU֫VNukW:jM**9Nroy6-Pd�P@��P@��P@��P@��P@��P@��P@��P@��P@��P@��P@��P@��P@��P@��P@��P@��P@w;⇅uo@/uv5<1-[kwUY"+W|Kpss2Y_UV0'/Z­FgJF[6QqPtPh^ u 8JҋM.o�B|BTφݥ5K3ٷؑp=֗zUZ KJG�Mpln͢Jy[1IE(Lv[9zj,f]chev0Uj; ۜr]Zt3 gJG >.+캓V>vܥρdg$6֒ٹ^pN{FTWMw<CJ5^:LW壞ձkF mfW7O0 ӷ5Lօ)R{YE ~�gsH䕂+Xx/3۫K6ЃeE=+\ďr*rq Њrk劥cy�۔^鑂*f&콖Yp^wrWD��#5Vuk¥L:nWlI-@Fc;~3ιeN-үʥLExc_pѥSDQM_G'*q-Jy_xʥzxҬ7p*S 3ӞW8^R~y'Zw~0hzJj: YMb\Ij"(-!_;D\IyL2ľXapxt߳pɺxl-$ pJYNrW+dHa0Uoz"^|MWV7'(* �(� �(� �(� �(� �(�?<C�.�h �(� �(� �(� �(��� UO 3x%_@K?]$ԴDf#j9FXiW&;5Ji.j}=z'n�E%gJ!ON)P.P_Ğ2aV?͢N洽'VxdQ7ԩ�+U=ꟚwL�UiWJ:zס^WZ ԦN9F9'iBI,(@'8㓁SAJ2wj-Z%շf][ѿ dڗㄖ~{yz_h*<B1hvOj&UPW/\_7|3/8r/̈́x-Yj9esѥ xiM{K?�,|[75i;58`nO_'{=uy>2vP_ߋ][OiѫUjy6c|uÆv9GMqI[%$ mW5~|'�e<f_?inK @H/~"M׊eGC7#vQ8:'S)^WRS/ڷ|?'ˢӍ Ëfq[r295rٸ蝓M� 7`BSⷉw@V7�sc*u<N߹kIJWR҇=?OpT|e(b3nM˫A/ i|1k/zlqKchتCG�K3uf$l)ӦN B1-۷.qb^38)9<FfY^fڞaJ:V|P@��P@��P@��P@��P@��P@��P@��P@��P@��P@��P@��P@��P@��P@��P@��P@��P@��P@��P@��P@��P@��P@��P@��P@��P@�<C�.�h �(� �(� �(� �(��Ad==z?:�5�_DwG _#QOFX~ ϯj+t/ x^qQmYQ�fh孙汙f"xʄS߻b''v\K7ѫ 7|OB /dog96]N1aCC+4QUL ɫ ;_$~xs^C�u{E쯌ӧUvs{p_sqVL^7TMX(ͲJ״Sve_ 6~'�w�`>}B&Y'1Х0i$n<AyNva!g7ROK?9_?ڗ N4Qkf4�uZvttG_;y~͟-kzNd1>)<Fjl;k Kt)N^^󼿭ZQ/ <T|T3ϱ<^e9}\&Y,p%DXQQB" 00T��� q]͹7)7)I)7vնmzA@��P@��P@��P@��P@��P@��P@��P@��P@��P@��P@��P@��P@��P@��P@��P@��P@��P@��P@��P@��P@��P@��P@��P@��P@��P@�<C�.�h �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �<C�.�h �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(�<���>/߃'m5+]*-WW=JQsj}k)ʽ XVI|/H|O�>nK|S~_4]o:V.]3L[[KĞ`\\A,HK"DT!?,>~?o_ t_W/[N|5kIx{GuBCK]#^!\ͫqyZAp8گ:_'W5_j:֙aigmPJec6ww7V3�{��P�?N?^𧈿j ~ Ƴx{]eęeӵ}7S[oMo$ۼM,.іWBBe/nLčj_]sF0X�[VNbl$ky#XX�(� �(� �(��i߲t??'躶gjxV[}Lկ lR'KSd�r< A�-��P@��P@��P@��P@��P@�<C�.�h �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(�?,?ڧ t7 O eEȚ�?g߂~?uMcZ~TյmJzjWחחI=\M#,tx[/k ~7{yk]Υmnc-,P;Ί@�n,'ߍ%+EGH8O-b@9CҀ>�N� ?j/ٗ\snwKO0Ï͠ϣk'uKŒ_˯X̏K<pi6o[�x?ି>!M?`ػ߶nY@[OkLDnw^\c-N]GE5N.>?o~ںW &Ưz>C ^W|]O[MѵWnҵ Cu.Zd߀}@߉_�W_|4ޕWi7<_Uq�$N:mpIO<vJHJrQχ~~߿N|)O 2|j:Y%x2;}/FujvVj _EӠ0�~U1G7x�(~EO �oK?uS oR�A$i-o�j̟Ϗai¯x+}ql3`iO;?i:]M9P~(L->9<�oWͷj|}xLj|j|y j2 [/>m�r�_}zÿا1L֤u֩Q}ºxGDZq*vzx{k7HIt'�'U k?$?c??&[Z?qg=7ÿ {/<;ek^Aw:CKl]j>ۥ�F|1]_|&L|/!z-ݭ;˭6Z,wxDx�K-/Sԥ�C�omĞ!$?i:Ni_~8aqke[ڿ R(yhccid*]9_G [Ň/ 7oxv1|hҼO@j|:4m^4wzƩtm֛6yIYiY:wo7ٚ/|%m~ >7DԭnWF$]&G1x\g�lO۟Q>Y  |65c|y^[I/tX�3mwoȢٮlm--cE  [�}T> |@m4-/"xNZTLǚ_xyE5WPke)t눘_*9mg?^ǏxWu߇4={P<IB.5xN.- k<zϗj`P/)�z|wsxS|1ߋ?fl\j+\h?&Yѭ@><�I&? ~S}�K 3H񭎇jPY$վ7ӎ-7Nt3Pot<W\[?nω~ּ� h>2&xL- ew Ko*dYctP€ �(� �(� �(� �(� �(�<C�.�h �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(�?,?ڧ t7�c!S~-x@?>%|iML4>]{ҵVK+mZ++W!ljH\>xW/xG, 07^%o.8;ER̗wQ�=|u�g3�PGWĚC%>/jSZ9�.ImdZ6 bU^F(��k'q� > zf[{>ZG^~ƍ9xż+|nw�o(G;O$y'w|I6s?Zh$֯j:mbT~�8?s ~~5�~^%<5&wYk~&h%=$h ǀ@e�"mؿ_V▏gjz4CZu�zd~#${oVwG_xwT4BT� ؿE!5;ʍ?/ٱ bm䤁v>@@?A?�4Q�ʷR�ƀ??ْNg>¸-l b@0ƈ8�PnWǟ�N|#?�/\˦7 KTg4Lkkc\j.о˧ϥXZ/eY�Oks/-C dޏw7i RntO:Eq5DŽfO\\Gk>!Ҁ,�Sm|$ˑOoO|/JѤFs]VK?T>viW1/ 1w:M}ݵOؓ? n4 ޳*xFO |gl]Ii^ ˥Z͏?oO�fo!਷?�O'xA+x�7ϭ'¹"Eb%h:_ jV%<0$iSi(~_�?ϟ~�>-<HT<+|V.> +ĺe{x_,=Љ{x'܀?�'�+ ~'>/g_xm&M:}c&vRy/噮ydRؠz�j+KK�a^{] Q'hVFa@Uh!�u~Jg3HMk-ͼCѷT #�R5ByW_$ 1f]_[[ E k#EQj�(� �(� �(� �(� �(�?<C�.�h �(� �(� �(� �(�gƺdž|ZE ׇ3kv\o6\iZU1N#x0%\mPď$_PSWw^|-m+ͯi_Czֱ7;Kڋ)\jw$4P�~3�/*?/;~ "c?[)i{x+4٭|6l񮅩iZdIE5G@ �(� �iY8`7iu(K$1 ƊY݈UPI hgH�~_?||M/ُᇊ~ ;>?~!4 _fso i!^ ڌhoỽFHpM�'W?;~�^jn>B{I"z^hЭV 6y4O'ڽĚz>hWZڀ_�m_��q ~75mOF+×..O_j,u}>WhEмC#F"4:�(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(�߳�k_WTOmt=Oھ{Y4^eot-Ě &CllK{m![7�//m�{G�^|/ikPc|/ZR__7Zl!YLQ !E(o+R+>5�/xKo񥦳g↾O_x^ݴ -n.E P>�OĿa?gğ.xY|V:G l7.ׇd="[dբG+p(�a߈��5O3|ES>1^.5o x;@^|]/]u+O`w.2C�ʾ࠿!82| �g^%Zń:EGYw:%퍕ڞub$fi>hv ~Gy|gr^5M;Jɚ[kRF̶^fo^\h�7WS'٫I[IuW^b\ ON=F@o'G� _&WR|_[ _K</Z忊% o#Z=[Tм#{qfiko|Z>SZ.o>?m�O?]w'-m#O7J_ONѵHu/Wҵ- Hio-e�;Oc� G~?<u飋BC ϊ(C?F�/gz?`o̵�NO?`? ~/OCej>#5Ai}{T{-b(h-T"fPo|េyuo|?g5K6IӮ5sM/f[)nlKKYF D~o/w��n|X ⋏\| 'H㲿n<}mockYͩjKIfX,�OǏ~_nK߃>(?M}'D_\ :-2Q4-CT&t4_^ķl> ^x[-ߊ�o5K?S>2xRg#Nm~Z-E%K7HbҬS:G+vsgا_#<O _DO'|}O0R[Ӕ>v�aKIc�a?o~X/?Cobe_Zhڌu^:h qk.i=ѿ�k?7~-WöR>(+ KA!7CX:}$ ŲE6և|�l-|U�ew0~Ϻg74x緖]^5kh'~jCg,{f~Ϳ *υ$Y�5gu+~|'{w;>#k"Ԯ\%m��~Ŀjߍ7ď }hOm|iNյA߅6 ž#5gV5;bngIZ}Oi|O)x�~-Y𖁨Q힣kzFjvKiY9[#ٳO':_|0-5Gwz�.ycHu;2KO`m�Xz�(� �(� �(� �(� �(�?<C�.�h �(� �(� �(� �(�?�$_�9P*{-|�~_¿+WCǧɨ|+y\@6LZWyux�𣤖vJ'Ui?cҵKRKe<uㄇE ՐH;mKT@]etB2J7\~_"/_|;)ê?(5}#P<#o@iqw ]:s3��e[|H5OΓh_4-%7s"cmMޱ[$W Ҵ/Đ߀~R~zo��Ï)໿:>9 'd{NSZm>i֥a;-+R4Y.e8�]> % �~:UxRŐbN׊$5=%/jVu.»|o^~�imSM4z/ vi5]zeuo{/w֞ռ~"E4hfpe٧�/>ߴO 7ޯk߇?tcQt]KTXi,#9d|[u{m}�_OeO&OFcD~3T,<C-4KcJηޥi:ީxq[hV^(D4�h~m0 -^}>2L "�_g_LK~=(43WLWOee Fs5a-\G}wEqm&go&;]Y ú~*KXuC>\}[z ]xL_XYۭ<_P�~^�i�]~Ś&/HSζ_oKeb4d:^4m~о4�a>|%o|_M_ž2'_ |;D͎m)%ơ{ր>wc_v�4u_Gώ*)xKoI>$pˡX˧馕u< mcq6?ڣcǾ7Em⟉-K/Zφ>$]i:7>*I(_%{_?.2<Aqxz]{zӼaRPLޕc ~R͢|HmJ52mayYz�`?$G>3g~3�x!|wi~&|4PQ>V:b'e46C_G@>�);׺?.|uuVM[]xO>�%] ]>OM4kk��|F<3k'@<3o>u ?9~o jZMjZ=4{{;~GIYq~_�ࣿx? !;[4 J�)V'x2k퍞ֶ@F�j?f^|J/|Vo�ͯxL/4Tkk45[Z zҭki_k2OM}tM��V|E$�H>:eGxox|o'4τ^SoR?aZjVr=Zm9o?[?gM;/o0зZ.mniAhZ >LJ{{Y+UƟֈג~@��P@��P@��P@��P@��P@��P@��P@��P@��P@��P@��P@��P@��P@��P@��P@��P@��P@��P@��P@��P@��P<C�.�h �(� �(� �(� �(�?�$_�9P�4Oګt?~0i:=ڗEm5x+vk- ҭE�- �|9=|G�? +@t>37[{MkBKxSeXΛὟ i2ߌVe]�?L?mn??L>2�p�v0( c>+I?e�؏||]J+'nj!ZWof xCú-ʞ]zm c[h�7x/Z<m&¿i^m{_xgƞ)5 �fU-5MU}fW $s|/%HʼoEVR=79�ŸOciM&AƱk6M_@ѡrʂ]cV4uf*M1;ooI |=SUt—~ԵOVҮ!ҵ;=bW1g3yk,ҟ@|3;�'B7A6uƾ!}':Ư9,uV{ŕk4qvo~S'ğ 7e/'CQ+/I=᫉md$uK{]/͊Tx8eۖ�+ �#il|<ixV'(|~}ki7.au=S[K`Wn%N�4O�:�boZŰ&]_KzN}[cj߄V4 g=�T+_SO$/O>'m.5V+*v?Ëb.-m[NY9&tsP8Gᬿ�e*^-E~&n]Oï_xkZqAŢuٵ繑x�9�+�6={<H='V4&ݭ^^7-zpSa}kuesj0:('{*_?L׾0~/^xk׵/M E/4˄񅭎cǢKa= ̎O(S;/BǭC޳odI~!//[tEp2 7�p~#x_P~n+  CxsPmiԣ_[`�O?J�"/i�O OGGմ?IkW׍ hޜ!=@AX_ZY\ڭO�BN_؋U1<[j:BkQ񍭖`-4c=sn%O(L3/�Y ��]D7Ƒhߋ%;+x[PAs?ɠi+Փ@޲փNҠ[x$fu�'��CV>Coֿٟ̺7u|HQ?5<c{]WľӮ<AEXMgq[j.6 h^0f~̞7{P|4u/\.Eu/Mj*v+χ,Ե-6jǮjK@ݚ�(� �(� �(� �(� �(� �(� �(� �(� �(� �(�35gJ捫]ҴMLuNAivjR#k<$Q��|5�^|aҼ']_᾵D-#^y x]ZNE[ii0] m阽̖�K}/ۣ@��P@��P@?�/@>$~Oo5_?4ml"|oW>3cA{#uԢĖ1�~/~֟ ?kF}U oǞ}t~_}ωb|a[Jkm.F+g>e�z7qV>,h~n> x|;5'ÃNie/]Ej ɶ �(�s>-�|#P𯂼9xگ/o�<?2Xuך;iwwWim<8#g3_& |W.O?xo{\|7/?}_Y�i3ZK䟳w%OO^4' |G_x3ƾ=[n-":5J>W 5幖��mi?sZ�?~|1Ǒ~nM|:nVK SRkZ) @]P@��P@��P@��P@��P@�<C�.�h �(� �(� �(� �(� [K4OD`Vi^m,5 im/ 󭤆:i#`)ݾ)½�x3?��u?Ox'zZG^�jx^6?d�k%~�>1|XលᏋ%|O񍾭[I)ӸhWZ4 L. Z::q_j@ |m)|1/l|n=h|m� G4�GtѬoFXKW5k:}rj ��cٳ֗_/f@5ޱ×^^Դoz}f+!Y^% 2GO'ZWѠgz/7}4|4世Y- [?{#ҦeBO|d?g¿ <x �,<i*@׼5dڔ+Wֶ|K}|u`w[ B@3yѮoߦc(׀5[IG PjZ\tZI3> |+=#׀#;ݟlO6Wy5-N_j{ wu3*C�+`�ڋ7;Bzsxωkͽ/W,|E5=zSYR4V @3~?}q~ϟ|7ex^'7|>-{MҮ䷂kIgi!cf_#寎]SZj߇Ak_桥։y�+vnZ�hYܛ[cay?Cஃ<|=m΅czPƓ-OT5Zo]k7zͮZvڜV+DzR+h> ԿLO�K5ܽ:o/:w'yEcifv&e�:cAžǁ|7x;^|'? G56G].1vZA kX�7U3O<S< E$7E<%m~>Y }2tky@=W4iZizvkW:nVږiװ柨Mi}ewo$\,3+�~Zj_Do&V�Ln^ 7;<w"~1IG;F`Є�[1l_�f~O~+Rtój?iΏrx"�úici:}q&_6k�¯x/Ꮐt<oO cKѴ=3KZu}C:m5KB..`-,|7'ů]|@ğxWNjA. Ӵ߈"m�k_# �_֔47E?~: ~onm'&Ѵ;J_KSKVy.up~_fo3ÿ <ks%hu NX`WuB{sz֖Zj[Zx@�P@��P@��P@��P@��P@��P@��P@��P@��P@��P@�~<�n>;k �c_?kρt{1^,|j`*et,x*f <k>|3'o�sLO=s6~#6vk}& 3;Go?AsHDzws j%c@߷O�i�5t|y'S=jM;LՓL46K5=CIʡeuk'ݖRV?feo/  e]x`hz�/ kkxM�xKZ`� vKE4=CR�� Egj|3D%�do? >$xöZVHEψ}^[iVڤ_e{sF{aiYiֲKxC G�>wƿƯ~�O~0+z.lU]K.Huk[O4kDѼQjW� S"|I׈o~xo}{�`4([.t/k/4mk^n4YRpv?lڏ<u/m3/,4×ZkXΥjxJҠI[4+�?OW'?jٯ}'Fm_?tt XYX^jVܛXnt3k4Ei�>n<-muԼu4^,m̐j/t[k#Gd3!e_g�d&�W� д˯#7}7}s5_axfMIcjW~;"K(xso|xO�3з]kH³3^# vPV?")7B$��c{Ogτ"I)V+::m棤Zj6 gwF]GU6Y\X��RߵĿ's/iυO_?<ZxHж Vi&:}VFоt?hzwǟl~~c~?nj|9cC_S^ ޫaw:&t ^^v~Եp ߶I_|�no W^9>�eӬ-_nK5]g]֑*GWZ^mg];J�q�?q!~~!|ҵMAցIw־,xĩ�a-#E7k^i@צhZy#M'ZH>>.|-D[ڶ .BLgq n#4i=;I&v85~ֿO:Tu-#O/9-k>"UU.]GNU�k Uu�h_)W>.x[z/>$Ӣׅt&DŽ==2[hUNۼCq4sx|-2ԿU?gi[߆.f|w ꗞ  mp>--;sj:mp]jm@>ioO& n|IǸ'|,>' dIXzvh-eqgZ_gh6z�=G:�fU$O(_5BO#^Yl3Ų!}B.xp-@H�?_� �(� �(� �(� �(� �<C�.�h �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(�?i d+�~[Mc< m~,k޿pwBNJ[LdPM_I{8-4?oo3 3D~~4о=x%~Eq-?,#>E5_O_7igu{g kV."�V�T{|/u~"x'I~)AnՂxfe5,w>�F7?d&G�R]~?:?:5�k/떳5FFT{[SƟu3eUtYW' �o7:1C>#o?#B_z4SMmżS5+OkS;fhOqck:ny?O5y:xUH]͖4=DZ}cp3@&K6ൟw=wd|It� <E_ƚG΅6mF FkOHmtKGk+('-׃In~ ekE�P�kOZI/2�?ZV㻛C۫[Y.ma k2ymj'7�(�j_j_3\:|q?:/4AecjV A|@ֵ[hlg3^kP�Ɵi'Eÿx3TXi�,<TdiCnwϣ][m2M�)]Gt$Nsm{'_!Ҧ·ַsie,otxVp(I~0@�xz}}7\/|G/9hp]-kQȸMt5Y_o+c{�[?_qOr5XgOC_0]i#6f?ϦkW1n&sC&>% *�RJ~?5w|;O>-U[kH|+y{M/^ -m^�MxG9� |X<_?-$6m۝7:Oo.刺B:#1}XV �şͯ`#I%H:mDDپx*f$��'<P?A ?r#Duvfʤ0O+f@ H;�?yi�#'y�7�?k_�<s�fmi�^ �(� �(� �(� �(� �(�<C�.�h �(� �(� �(� �(oS~Y/#Npuς Y=uK*?ӭ|:!ЬmV<m^^E?oɆFy>4~S#ᯊ>| �BFԼu/ei>0džnu_ލt{k]E#M.7_(6CC �t3K?D׬[^6[MR};E񞫤ɣxz<aGns>$]Otτ ">x.o-->Ko 鰿#6u� !a}{g w^]Bq(ߵ죠x_Z1F.<|^mGzF4˝_Q]_/L/S;g@Π?xsxDMwĞ#l4=E-S̹umcSNӬm综T@M/A5ê|>*uM/*? Q5{ٴ*_ílj=Rm/OђSCۼ` __rЭyw|}-[|,4OiýoPSѼp�)h͵IԠIlR D?x&_gmgީ/t�iw4kxҟJ=2FHh8f/>!? �hUo' S+׌v"4=n%w$G.遊@@^ߍ>žo1]ۿ5�w{yK} asyi3[15Bt~�¯؏ugŝE.g3qG_6Ňa(ޱ,3[hzDWzLitK?w%Oh|N|fj �eMoX".}+)n<O{kj]j&'?~ZgEI&�<wIbuOؼAKHʌQN' p߉ -v/*~yma7<U@ZCOk7n.<w("0|?G^D_<:<a j�#cG[^KckzNsB�ö�g oŸ>>? 5vO $| x)o!֣5&~=;H]?ks~4<oG 2۷OgzatHmt-O]ԭn.c'nYph۵mkGt}K:武kZƹ_Z>ֲjK ce חSkkkʐ�xwk_c^|OI|~vs6~bZFfOz3ړi > a)>+ESĿ F|I�m0jæ!x j; յT+?ũt <isI3jյ.e{.i&B=)ǂ=C2/ i1$|MBMjڭŭO<B\\D, wU(cP~͟> Rw_ZZȑO}.kWOI@A&gPA`|4M_�?6Ԟ6fDn5`ro{± M�yk/o9Ꮚ�i>%ş>,MItٴ v NQT{h&;hed}@@��P@��P@��P@��P@��P@��P@��P@��P@��P@�|c!~? c)3G+>#K-_:׊uqQKe`\ Z&n.Q׵IUudu YX`A94 s �Okjo2C|C׾"T(ӛGSd|=FM&#N|Sm?y4^z#?'/_|W- RuK#}yy捣]ם{*Ѵy;y7*-|:�koYEa WÚv:u|kO5Mj) uOݛk]-%k? ?_+'k�� o�5WA/}h+T:W='ԗ.h7Vq\K?߀x�-s #f׼o>˿mS^r|Nk?�[Vm坥e躅5`mwzb�=g?r/ j?s$ ᆼ8mE,zM?VPgtN-2Y�ßSWwp>$wx+P 4WJѣt4mlm]:eխgZ6764񇃿a>#܏?*: ^xՒx E|#jDg{;i/kRʗ&/f�3_uW">=_|B"o/v_L>PQ>0<C XiP K|9čkJox6<1jn/'PԺ5ogmêZ7QM{rˠ4 :Ÿߵ?hI[ |Bv^h9BVsJь6I-�a5}B Tػ =70čbZ-6g6l7w7LҬcS}B%Ҁa~M׼//~aTP[gGnk%Ŧ.6iڎgy{=݉Ҧte]Q#� ) Ž~!u2a !�wj~WM]Ze4˟.�G]7Q�boZ3x [>Kñjdj5Ɛ>W+s" gM;d}giZnZ&cmΟyjmI=_XvK8)$N"?4�c�%O؛ⶻ�/oj ӾxǶ:mYqáAY] 6.j:-*jZ<p2Ŀ e?Ŀ 뿵OĹ*|BGHC>-}?^i0ƭw#rD`|*O|A.hOw<gu_jvgou[01%kK.| ;#6~x;rk mgu^rz7^-ޫ}|]$zCKc2A{g|*u_ۦ- |[>Gòj?cQi-BM<L].ܛ<a-h܁�>à �(� �(� �(� �(� �(<C�.�h �(� �(� �(� �(EGj߲�| C]^xw.|[ 85/ԟėZeR`,vUb, XߍPW_?�73_~*{X|;nDŽ<M5}m.>Dl۹OQ&gay$jB4>�eس -[U<Y­׼s˧]�vsE-֛4R{]B{[9n^ Ai�˟EYo^o>~�_wZj?E=SHT¾4 JnHu; �>+>�h�ox�%!oƹ ҏ>w:pVotu\[+@dZm:fl׉t~�S9kx[(W=7Ix_�mx Y=J8g4}#O$kS@L߲߳V>Vo-^-'�_ x/~-|?axV- KYt뛦YnJ{-"_dL?_ �hKCMf<A^|WϋPi(*xsU/;aYKrl/")w�F#wE�񧋯�gcG/zj~u>'nèx Wm"ԡ<Ox5 _i0ݟ��?#k|(j?]w¿^Kx[j >k&m6G-֡e�Vŋ�/�V[X>a)j>!muгm$%?0o=�}n?-4^>!~S?dω6,Ï\'/'ɨE5v.5ZSLC{@*)b(HhXfHEȤC#*A �7�|�j/�࢟o�j_? |8]_s=>wy i}zVGt +ݵ+ �w|So,W|)Ixwd"�X~txZ=Y_ xQ]&E:|/Iop o?/ E|1�]{'O\;i = ?/ž%Դ3W_Ckgyu_ i!fr�zSwŸ _|=O/Q|9jSZj}]G>}zfvzŔ.M�}{�[E6ᮣoeyPj~}-Gŗ7Kb[CGhcOg&34 �k�|M֧e ~?4J:v7WWKs4:.u=EWp>4m*(Ҁ�h�t~a/?~7|*Ҿ3 ĭ:; w1cow6K'QUݝkĺD2/ | gu/|e(̞0~#|9񷌮3C<AE#DŽ&Z\h3kZ{~_/S �.7O>>?J7¿cϦxWYюWS`%ӭMJ/l쥹4}J+X5kcG�?|pZc6=~5U3x[Zoq'7%:~eCiuCJ (w&fρYmB|mSK|F[ZFs}uwNaƠkzwߦa}x �L�lAo7ӴV÷Y+sMDԴ+Kڮ,RJd%嬑o"l:zo� ~Ѝσρ�>$i_5mSY~*j??O_x[MIo8|?4ziO#?Q�l,nwqGP]Ϧ8OZ`$Ht yҗAyfy �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(忍?D�|jA9'5 giy5l|C}cl#R"�zσ� xJ~x?ៃ.'Þ 4im{tT_[iwCu]yח;ϙ-�z=��P@��P@��P@��P@��P@��P@��P@��P@��P@�<C�.�h �(� �(� �(� �(1�+i^�cen?#?O a@=V1_%�>-Df&/z~yK淆T<5-~EiRMa,洴~�c�`]ᮝ~_ߴ,?༿:X�ZCǥjzMcU`nFu}⎳ X|S?fCW-}gRVZvixI4cJ3ڽsjWE#?G#�.@ &?"ZO8O �{BĿߴ�'s㷇�g;U~Ů2()iR-6^oiquwDZޖ?,W�~̿׈h(^ g]^𮥧XbI-ׅF6^@3xG\xU< u6\].9tńl\hkndN{)N{vH݀??0o=�}n?V�hs5j�~)MX!}c^*Ӕw^L-H]#ܶ5 Rp%C3W�*l�|bC;oߋ l4m#Qa&.ڟd%e𵮣mBN qo~e[�c|~}>߆�h_hWtoKmackK,֦o5"N7Z^^Bu�i[� +?pVGL~<i<Iv;|QwKY^@t6h/U�[[�ٛo�HfJ�� ɀ[K_(o{lsQ<Y��j BI_'ÿZxo /$υ_Ux]_S5?Ï{qo0� dny?/k_5YZ͆St#\fi,[}&:uH�4}FTm�S�(?O?S�[�^ �ҽ>�x��'x�V7@g�H#__F|,/ e|9?�uMRB]kzu.UOaѷho|? 7ۃ[#43oOE&ĺnomiNǥ>i%ޞSYGc &;+]:e.$O<+y{eqx涞9Qᕁ?`�n/2s�O�i�(?�j?Y� �(� �(� �(� �(� �(� �(� �(� �(� �(�`J P@u۹I #ܬd0ae'C?�l�~& x{Ϩdl9ai{9HW9H6?UsU�fuχf ߏ|AcntZĞ%$M[cA; IiyiwmKMj6�b>8~CJx�.5iKBд/j&q:ÖV:QWPqua/ɦIވܢ f{i<TزyS*11IH2"& ű!r4ndO=GTiY JI TPO ?hkw f�^7ä/$4Vh;iTE]uVP@((LYx_|Pk]>"x5i#B߉uSSk+?:oڛ"͓ʍwي�(|??>4�hi??τQx7Þׂ<G=#M{jK;ٮfּAˆty�?:�^�m_+_)fo:F_¿q'|93x~)VQxW/4i,}B_ ee5 +�?J�+yhฆgɹHIm'2d_c+`(j��[-NDuFE0Į ]TE�~�>-|U7~~-MK[ѿj�sG>#x~6tY妅&KK.ong`z�& Xy%HbRCI#")feU@@��P@��P@��P@��P@��P@�<C�.�h �(� �(� �(� �(� �(>I-5 3O,7}k-%̑<ʂL'4@��P}^]6fm_u7� IvPs>�hP@�NNK ;,g['ivG 4L/f9€\??ຟ�2~߱7>%㿂<Iq ZO6WTDM�~i=&-:8-K?68cdi`2"�n� �֠ �(9) 995)cq7I 2(;KK[ hlm-Xm!!H5uG`?��P@��P@��P@��P@��P@��P@��P@��P@��P@��P@��g'�6/Mx姴<OQCMGN7VS̶ �VoĚE@.:�  's^'ㆯVoԼ-kGhJFG,B 3 |:Isj4RX55ׂEym0KAZj mbڽM�m_�O?ػ�h?&oi�xW_�h{Q棨yxI+-R(n g4Ek@K{<' ;�0~ xJ~ $ h$I72Bb.dmX$?cO&g?vߵ/-|YUx:į>3%džuMOɦ/xn- K?^ji*a[|3�R � 79fiM<0jʤ(�~x~Pwz�{9?f}:_ k<|;%惣XxP=;ݭxSKi5,ZAӵ hZ�>#HU�]jTԼGKn=U/]3P9.<CĚe"k-3H� G-?Q/� �b_(.EW~i8EkZ{Hq wpo�gM=>$[rLgk[:0�,Ȳm]Kqc~� > /~/G3f?oOGƠ/ooԸC,7>u㈃&/|yBX@?(O�&º7'٫T5Kcח^ �<!mOm.mDvQj6Xij6~�<�b�~? q_~(OGÚk-&[ԬH6w͞x[S[5/m4П/|g�5V]*o]'▶D6/5$ ]y`>?^%~Ծ7goU_ ĖW5?&{H9Ḵ+K/CX?iy^ͧتۡCen�?Po4nmi~Hm/|&_E%źGC\.j|U.ͶSҀ?"1'�h?�j�f㯏uKuѭ'* mCWVPZEںi~o[�˷_7|KO|?|cg𮀶-&-CZ}֝yj:^xmQ =,=Oᎏ%GsO5]eht�'iL"h9\’ialY煴_P��e?c,S5]=O76+iv_iw:ΫHckMʼn$Ilqn�j i)4׿ muZѯ>5x$"f J_h !~^C+G?}(� �(� �(� �(� �(� �(<C�.�h �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �k:10 ]X2F&??U�uUT{Pv@@G,''b*P>!͗�ѿj .]cTVi |+|E7د-9[6xnU\jm}KG;-"�\~=�.dσ�~v/[_xcLu#YҧS#:oؤƛua;GR0kj"�k Sym�ek9R{K̷FC$?c4)*RH]H$s�t�i��L� �"��`Ko_;-?k>0k\,N|/6u޾ZFƟKx\^E?H%٪~ |CE;/><TKu &]YcbGo]gK[$'8mt}OÏ~@·D|K*5>Vе�ڇ^5*15꺏GO-[O4Ue/ kL.(]3&�lzzvE|HT�{kMXk k?$iFSiZ^DYn/v66F8m-a"ET(P( C ~_O:~%>%Ef º[#y Ñ=従i7[xƦԼn4{cR/O?xO>'jςU4O^ 5*{O>=<3O\\i]6NSu#+7ڇV'/yk4d2Mms?3A*2$ԂA '9gL�n�^6O�_M/&?ۗ<|K>@/O|&�/Þ#lc}7:׋o^ͣ%84k@?f�?׉io_?a� �4=s]'Yx�"x"[6Ϳk,V6v({ Nk7GN&�'jo~*>l�h>)[z+𷁾!xķ0\~N`փiZPX�O|I� +M;\O>!Eɧ'|ijZ}u5 x+HgTWk\R�G A_N,�B\_x1a־ BZ=TwZZ?-fHm-|g\ �'73oƿ|A$o9u^Z@.ͦG,&tZGqF~P@��P@��P@��P@��P@��P<C�.�h �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �{egYڍm=]YD]Z]ΒAsms L;"21Z�/gË�� I{u'G@WQ~'3L[FZ<}h|9}[ oEv>_xsG_d2[\\Km3۴GPd x#ğ |94|Ҿʹ-xo/j1j+HCϣjA/E~m͗,�v' 7ƾ>.o'u{>Oiht{W:m4[J***"DPUTaUT`*���c�EĚWÏ>~25 ?<)acP,-.]�qy}%?WO.;]W� xR_ ;o xkNŽaUж<b �Ͱ+oK|d>3Ӿ/?ٚ[VF?T; izm.I`6cbD��|"O |<swm]�x^Nj{[[<4u]-U/ \�~|/Or|^O_C|O_y~!(5w$ cA6tmʬԾ%0yQ&5�^mu-[ֿjwW|RKcg =�4<}o _x+#ڛD|q~yntmrOkiq-nʢX]Cw=%|5J6^ MSū"][/>i6^LB/@S/ [‹x׃�m<=ˤh x;U;\OhiLAm+@UUQUQ��(sW+Rо|2}Du?ßxs:VkK;wR3ivեƩuYZA,($+nop|h/9~1[X2ĞKMQiG1X.yi-̈́bGF�9|� ?~ -S@/U}C,icNҒi4˧\܉7€:χ_ | Q_ <'m,cE𖆗3]eVV6mw>7O\Ni]hc77Ǟ獼! ScxW&O vFH6zz(6Bd7ٹ?OBѼ-]H׆|9Xh^=BtM&+/GѴ:m?KҴ( mള+{x4E�נ �(� �(� �(� �(� �(�<C�.�h �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �<C�.�h �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �<C�.�h �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �<C�.�h �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �<C�.�h �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �<C�.�h �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �<C�.�h �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� � endstream endobj 2 0 obj 1024 endobj 3 0 obj 740 endobj 4 0 obj /XObject endobj 5 0 obj /Image endobj 6 0 obj /DCTDecode endobj 7 0 obj /DeviceRGB endobj 8 0 obj 421385 endobj 9 0 obj << /Type /Catalog /Pages 10 0 R >> endobj 10 0 obj << /Type /Pages /Count 1 /Kids [11 0 R] >> endobj 11 0 obj << /Type /Page /Parent 10 0 R /MediaBox [0 0 1024 740] /CropBox [0 0 1024 740] /Contents 12 0 R /Resources << /XObject <</Im0 1 0 R>> >> >> endobj 12 0 obj <</Length 35>> stream q 1024 0 0 740 0 0 cm /Im0 Do Q endstream endobj xref 0 13 0000000000 65535 f 0000000017 00000 n 0000421553 00000 n 0000421574 00000 n 0000421594 00000 n 0000421619 00000 n 0000421642 00000 n 0000421669 00000 n 0000421696 00000 n 0000421719 00000 n 0000421775 00000 n 0000421841 00000 n 0000422018 00000 n trailer << /Root 9 0 R /Size 13>> startxref 422105 %%EOF ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/messages/shattered-1.pdf.sig���������������������������������������0000644�0000000�0000000�00000000167�10461020230�0022222�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������u��!9�gս7QX}_{� 7QX}f�M>щUX=sr?qi"Y&)V�eboe*ka:~>K-���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/messages/shattered-2.pdf�������������������������������������������0000644�0000000�0000000�00001471043�10461020230�0021450�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������%PDF-1.3 % 1 0 obj <</Width 2 0 R/Height 3 0 R/Type 4 0 R/Subtype 5 0 R/Filter 6 0 R/ColorSpace 7 0 R/Length 8 0 R/BitsPerComponent 8>> stream �$SHA-1 is dead!!!!!/ #9u9<LFܓ~;V EgֈKLy+=mi kES ߷`8rr/rIF0W.+5B-*3.5M,t x0Z!Vda0`kп?ͨF)������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������JFIF��H�H���C��C����������������  ������������ �'� �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������q=b"*{���������������������������� ^6o'!s�kz>GxbM}P5Pg:;@zky}Gmy$zơ1dzKo�������溁! ����������������������������\mb\p:; yF #\|]3[[d9iSm |C̼o[.j/7yΒuf_':͒ǽC3+�������:wW+0����������������������������Hz>/eT[ci_|ns$)O~h] eW~�6֘u\|۝8>{Y΂])KyEJ_;~w1~q_D|}c;s4n7cZ7v%‘8M?Y2jjF协X_U׻6)n۷'1_A���������������������������<޴E8@3LF"Iе[AQ"4=W\w]RǴw-=SJm?x{7ޯwmZ.!ψ;YQ-.iu}"3\q۵LneYu%#~*BOfK1b����������������������������Rf&<KI:~=K9Y+[ծ{@|$j|oq9 QsnqcpwR͎̿Ou3%z>gW |?{oo eg'Ґ.=+ڬWbn_/^XM9^k���������������������������[dn[ﮮ5-HSE: &к<ȯO_,8cEv7%I?Neo2�69?atl=H_,?c?]1.�^;_.>}?DzRAnӯ5Ә֌~du} +upC����������������������������, ܞXK�8s$)O~h] eW~ױ0lat9FqtֽmLY/kqLm[ޣ>3_.o3 ֽy~3+>}?DzRw++ ;\gq�SI|}RUWy+0����������������������������{)t9Sa!Sz4Srp\9س1ۮd9iSm |C̼o5ɵpjILXHF3�/ev'³/6[ɿTϓ|.6V\C]<::Udm' Y'Ґr|6i \\5k=w+q4dsWJRW1_A���������������������������� N:mW6 ), 6L+o0dHZ>I۴hqLoѧ5E!-uӹN%Nt yD-g WHT(K'A^*d\@ C^)=5Gr.a_+l'o^)�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������P/3Uf<^sU#@{hv����{v��� kq ҐDQ|̠��7Zak'�-5RP��aJdt�k'VO_x*FK:FLBa Y^x�����������Q_k^H;'^NW0V]S}M !L4xj~.׌rQX(6Mk�9޳_(XUo9i[ޡگ5˻x&W2]׹/X:BY7-m)ZBtgV=H}=QM=;=)/9sӽCk>K4+kWaW@lԊ{Ƕ:aUhKG{cF{׬EhYd)^-Hw8Ӎ.r [ޟi".|YB/9]M rîa{̓V8rǮ������������%?z_j j@3H%˧ښ^`kFN6e+?^&Pe6fU&:?뛶]EU~di{ϕKg/e~WǮhBtMDt e=%-s�}Tyq }R{Y֌}n_G>v~oYuHoyz+Jdcl3ٌQ^6dEM7dT<�u FY^_V:Őy޲ޱi^Ҭ}U-GqdIFHu*e|.������������߽k%]#ΧRMA(-E˵4WIbj[CYy>G3X ц{kYǼvX/Ҡ:R-V2y%_;wb+6vCt}KSM=׆9rSU]Q:A{ڭ-I^Z[m:K/s+ivjz'Rغi>/+gQ񚙪Ւn!guMŸwzc=chxFgu�QY]@�������������������������������������ݺ2߶t2ӡ������������������������������Cx���= ˜>ȉG#g����"hw2^���������������������������������3 �w٣]j|u/F!y_|4wG[h"r1̑nMM~=/ww\#r2af9%=V48>w3[a=Lf[L r@��������������������������������h&xzhy"a wl&7|&<u=Vі�M2Dum3/v1RAs=1-fy&E8[>j<iվ ~@��������������������������������<Zp|<G\לj_F6�JּA+Foyq^' :tV9ӽS%DѵOqAS i7}أO-,nt49l<IJ^3E:/ϘVXc]k����������������������������������-?sEW\թWߞ<ORٳCKNJ<x{~~~?<U=Sը?J~|U})~5~�����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������\?[�����������������������������������������������cfϞ>�����������������������������������������������Y-%ƀ����������������������������������������������Hƀ����������������������������������������������Hƀ����������������������������������������������Hƀ����������������������������������������������Hƀ����������������������������������������������Hƀ����������������������������������������������Hƀ��u_g������������?:U�����������������������������dh���<?kS [������������W>?1���������%BoT��������������������ou�������������UϢ`��������,CuRaд7lw Eo��������OP1~(tr�����Hƀ���`>cpWt_9�AAlj'֚2ݱ��O0]-J7(3{c��fşEٟ��������Յfz4]{mXe$5Aϱ��������IFgع3�����Hƀ���Ic7~�Ws7:B]%�!F^6v\ޜ\e8#V=zrk>ӽ@��jY]��������J>98iwXM0TQ��������(ߗ04&ux���������xdpV_`G~)~}#۾q佇_}<g>X>VRp�Lٿ_(8^Y{N1boVH���}f~c���������1z(��i2SU ~ s[+ـ??χSkyoǧ˒q.i.LaM5��������8x??=1,msO7ު~z2n|+5<�b2:��� 5^,.��������1Rܟ]GzZfw�mx<T.fĝYgd2<V 1*I*<ltJ# ˮ\};޵^Fi_/Bxv}:jS.,6y��(���1����WG^X㕇ikw7gqOW1|/iԀ���Ygvg0��������9A =k� D{fy϶x}DO)34 q/c�ig�oKoKsgR<q9tE7v;%Gyvf㮹pAkO)ޗ+g 뵸IlwљS4,nooܗ=@��� ����|Sy.sMY8?//m;. = FAm|WwS>Bq|ʴ@���,x3��������8bN1n8.@k;*(Q΂?fO_Uߘ$SyidO'ʹcr{E􇟀_6 PvyG/r[;yq9é;\p4Mnly�-%jQ~ñy��� �����S[iǀ8`d87o)y.b[!Vr_!|f-ly�)e!8ŋeZu ���jY]�������� GԹ!<yR/5KZqKy{֥tJ]ϙ @ԥƸ#WYfQHW6a+ P�*ɛ> /]}ᮏ̈́`2=M9ޗ+g`_gyZ[GW8v+H���$c@����/_6_05) ?-T4&]/q c G+m>3!ŷ*^=b>�� 5^,.��������yIcG/5f^ƧF;Z`N,W}{ϛ4[9vU(;}J5zx{K=?+:c!>;/G>J}pT>OXBO)ޗ+gy_Gy/ uR!�|-R^{���$c@����];gw=+7Z.Tk5K7*׊i.R(L8?gy?�N׊,x\C:\��������>k$nLlX/)û 'X薅NiVtե#sH%n?С=«||&;C&_L9{uo$1Mg8wuWӯO;\p4 %u Ϡ>'Zһ|ur|ƀ���dh�����a/d0W#�������� 7Z(u5^)j����������?(iJbУ= 2%ɃdVZf(lmUYux߯[4-rg6M4WzNLKݖnsu3.?.|qh^hѽNVRbj8>{���Hƀ�����;7aؠ�����*rQfnS>֎=���������������������L v*ܽr`HKZf������������?s z����?wX[/^+u7^(�����������������������~r{2Aծt%{tjSI6<]������ ������8.tY��=)wg+6nQf]M ������������������������JDUY~�,Lc/~�������#������@d?L/?dzŚٺMn�������������������������������������������q~VrSfM;��������������������������������������$c@����������������������������������������������$c@�����������������������������1"m$2O����Ѝ�������2F4������������������������������\ФiO'3 QrMY7F1EX:2@{<]0�������2F4������������������������������\˖UB?0^ÏJ%)Ѣ$Ҷ1q �������dh�����){~��~JlL@������������������ZbNo/9̥AE#G"{=T<D�������2F4������ r9t Dm@N9|Z–Ԏ ddOɏ$b-e������������������������������������_9Aŗ=2`>ID@S7c $sP$ǰl��������������ꕜ@��T-YR͌ `MHA������������4?GFDIeq&f+ٰc0@Ş4y*: a0�������������� xA)/٪N`y9D=s0fx4܋�������2F4������������������������������=Z%º"Rf: s̛!I!"o?A�������Hƀ�����ae tLY7⒓8Ȟw4c<z&jx։��������������!f+i/M v[R&<DMjKEi71������� �����"V5`!S:IDfO vٵ:7t(Yi,���������������q��������������2F4�����<NX Ol PX*s"kFgHpCų�������������� Pooi0:r<<g��������Hƀ������\�{�yLK{`~O ���������������(daI tp N:^ssXͤ~������������������������������������� `DDtA�ܴ9ʪZ""M:ZqL $,������� �������������������������������sjFlp?jFl|={�������dh�����������������������������������������������dh�����������������������������������������������dh�����������������������������������������������dh�����������������������������������������������dh�����������������������������������������������dh�����������������������������������������������dh������������������������������������������������8�����67@ 0`58Pp!13%4$&�!���^ ~h?$p%X>Ĥ.T_=�DG͐ ÍvG3%'dkPq!k<4YlC7n 5GR>Ţ0eOaL8۬=_4"z)X9zv_s{kwS|a'o3*Q~M{ziU4 [(1TA^[%!b9v_s{kwS 2`Ea?㾀)8Rjp$d#hz&FAl?ђ,`n?^D7I T)41.b)*4l <"1FrӚэw}mVMخ*-ZF+#} ѩ2`(bL*Vt{*DqanB�PFtu]bÇA6FxeyGJ. sCDKΛQٽmӂ"n)Kg5V\r/f̛HRɺ / \<[XX&'81qL[9GÿS4&㶋t\CU޲ǢylsƝa�ߗM/V?P_( & F@ޡIj2hTqx[m&hd::v`=p6:%e t�2R&|;5'"p?0ABPWz}4O5"P vav3D_dkP_( &e 3u G_!~(u}[x}hxѰw_!~(uh]7)'QgoO=HwmY"cU}/)k8Mrv_s{kwSU?ħ_V [P/}&bpgUD)ݡEMFjU C6 1sdcR0 orY)HwjJ gxm^T0a$]]]Ӻ!9l\mi/c|릂fZk+V.`V4KЪt9mY8[zGV e@j1>OUO�[֍/$oTԶ$9u2."yԏgD퍰S/bبgyGRf c^DzvW HEx̃Ԕ9 [o/'Q2(OhB/ӞeQLSC$J}sw}6zI72=%8HX&J~`]ڒ=dD#+2iDAMY%CRUB5¯hfѫU)IF|$)Х ΰ Ωg"8ZnF- *WA Γ+~R8J褟U;*[GB{R1*Z:(rֽ뭫@Գsx@Ű� ~E; QC(R(Mw Hv: /põŨx0SbfUSp�i߆Om9!ʵ%k!*Y PHHxrZj±ѝs06Qc,P$%ZA0r�o &u> \ayuQӆnS%GTTՌ4%kWƝRƝy^W@iW0q4*�k6T߿! ( OfdA*Pu |}2n p"&WoY [ $� 9 Fp}4RF�Po^)5D'X뗋fN? iW,odċXC[h`p9 Լc7}K?U,k iD<N;ax Z$P>d=RDBdzyN/$S5~1"ڱu,Xʺʵ6Ճ|mA,3 ח ;.:pڲ}eɇ\XmR`Z9YI`˨{W Vb|/}1m!g^MV`jI.0_?Į%S5žBXj.S{A﨩庭BȷsE7dU?ըCYh2=Aԯ~�B�xj`tѣ8I-2'k~NN <ЏOghXw Cj"2[ gY8u+UMѥ 1I8I *fBp|2v#>J<I[@a DxXȸzV"Xvx<do/G_VnCU@s~7yaXN H+;QPP0\<&KqkRjirrrm^�%o^J2KH.Mc'k:*tG`ly6H\<.oHgRdO㷶ea<FAKq.(rhH3z5 &: 2A[X!!2AԵu9(Ke49b=8pĸzV; ]<^<{j(QN8^HucU\{HyPd*/+KPˇ#j%QP9ES$ōVK:‹%i$`:fc �.~*&(aMl#9pZF1KqЌ>XXFDILmHת׎):"V|!b,Fya:!>i>-� bBi8RS|[3^67%�V\yUxxl V4M[eS2XU$>;'-&沙M QE7Axbo]zrSNA((ۊ,W^_9�XU8b;yi\3IĎb,xC4U*I ac빸)`D<|Rub8& $pV)Vm}׷NLr{$5lYR$!_?8)ͅ3*nHj':D;AX}9g-܆W DB&l)y &H|X?Aq#)ZދF(J`axx0rga#EeBbu 7� 0v- T#K̉WQ�­Iz/$]nB'恊WIJ%Yʰ|հ>%p5Guv"IXp^nG7Y{c&:o<;St󖘐xnEwfYɶy㶶ѾYe{cS|{s57˚)�zP:Mqh&\Z -kA5ŠMqh&\Z -kA5ŠMqh&\Z -kA5ŠMqh&\Z -kA5ŠMqh&\Z -kA5ŠMqh&\Z -kA5ŠMqh&\Z -kA5ŠMqh&\Z -kA5ŠMqh&\Z -kA5ŠMqh&\Z -kA5ŠMqh&\Z -kA5ŠMqh&\Z -kA5ŠMqh&\Z -kA5ŠMqh&\Z -kA5ŠMqh&\Z -kA5ŠMqh&\Z -kA4?�8o7i~/\[kmrm_~/\[kmrm_~/\[kmrm_~/\[kmrm_~/\[j.}sx-ywsiI8wIg8E { Jw5)/ ԂvR3}`e~S74g,)$IǶ&r5=4]?VЕtV@ 6g`Ӯƽ\ؼX=_�>jE'K_-~�dpm4dt#PYʰ!)Rb՟!ޟMvԅ{^8H z|{"i ?BZu/�SY.�gJe$gQv{u&l4éShKZe�Qvey}w0t^Hb*) fݹ႘GG5{X>ܠu'~Z$w#Tr&6Y k)ujc# λل.Ku?N-ɝ@>+, TA }{+Hlo}gE fT9!luuhƽbl" dW/u %? 9ŔTNrwҲr_&NX-iF�YC!I2{b_:w1pb62g c_kⱍ|V1PݔkR Xj ƔG<Ob<S-`iRyc9RhvDKSebv|kj(cXjHĪGpcvR?L7T1θ\(w睋3e�loOӾ#S/&;N)e'D @.VdƳ bB5"I:i)ɖIoNB凸b=YQLp}]dۢ^F,9jf9)FA"o5RwJ{Y_s& zlz$@UͲVye}{KNSz (MO km,`dmMŁ+B-{%˺?ӧ|G3 O=RG X~X&#p[\8-Ѭ<t #}ܖ])(ŶO^DƷ츠{G9�]<]HŬl͑ɈT;"I+G5{%>Pc[jtЌvLI.V4n>%(gQn#Hڒ YCV㺌u޺]"_$� ^o/]�y;?wJY|r KH)+%NdjO,JU)d J[Pܶ,[zUm}jv�yzv|wx슰Add[Rq0r/킛($CeHCNKYۖ#t#s lb/M+r= 6l%$&h_82s\�g oizud!y']N엜nnxdUcAݐ*<?e`IdUb}?:vrQOz8KKde"$b7ʦ-AݖRz;hƽ} ƪ6";c8&$]DdOu%A%Unee'!vٲ8<zA~y( )+a$L?))-+ťzYg 5DpaW^e&-L?5 Vx bۧjcjJ9GL$9ct7Jv*KUPyQ<͌1}꾕{#ip1;F6՜;oZE)HĮpwKj꧂ɟ9l5RK[R)()Q̪� [ɱ)p0AɩJXҺG=DH??}VCyHSčR2O&թ⩇e�)6lDt9Q9Z7ͅRf!&'=$YJE-1l%"6sĖ`�7LVsƼU6;$_XN UrsxͬbW3uY5͝\j!i,I4n<R^O2xoQ]8OMCMSWAvx% @'#kN?hE"S؜~E�V0zvg("œOL&59 [XB I|9]$.2NVZBiē;m;yGK/u}[HOMduӀsMEV G2 v;<&2g ʸVH&yo!XaJq*HH'iuӸEA}r1YhxP@xah"]-ᣈ#ABl<_.;��a#6E}S d$dϘb"JNǎ?1:J`waK3R̒/dsaRܕ)),N19 ٘umBxqg_/%qĥ(;�<dPT:&+?i\JU͂BD[X+ss!|mo?M8`a@űt@G".2 b.BWmʹ<ղlΣ]Xy!j|;%eg[Ea=-cI8eAyqV+zYk#{0sxːfbZ);Р)+r4mH)GfLր&�RHWDbn%-Pً@$*273ɐTzaba'A 1uˣf$TzqlBq[6* 1PFܨq[6<"  )©sz3)K~URUKHXM|v+dşD bd@Fc f%|*c рgkڋ$7yF]"+I>SX|4LDXUpw[lrS$?hYd\1.F"ׇ5^KUfg#m4&$ĒCs/(* :3'$;0RFt8lŲ4plȀ]MԵ D4T `e@(2g= #Y(F[&*>$%)GoƠ4a-3E^ϽaE:`sdjc7NSʠha%gBح2C ȏk&(*Mny64K&`<ȥc$%f6 l5OhU:BB2SkɊAXHNsyGi|LymCJ|[D~࿧H|"F6-P2-]HJ R4TÕiO:ttPj-TҖ3e?{h䟨�(ROrLQr]Ī^([CrTq Q`qPÍ_sr!eqA7O',YF1&=`-hiE@N˦̐(Ylf 4}84Hf!G` B HET,z8o-=+`-  \.$*M0x pq[0 I5"BeFBb$Ebz� <#&F5aŘ d;zQ9"8#<fqm̬rF6lZR�V[9ў{;oKF+An)H sARtt%0 .,A9Cabڐ%= Rh$DXx680ߨ^2n0#f%*? o-xi(W~~dG$bUiDal=c?�K Cq1ڎec08VM1r ?g 2!D7T$ha9ĠvקoLhI9p>Kl[h}CKJ ,o0YgbXdyް$amqڮכ_;zgQ5Ǚ<k#TYea.=^נhWdz;ƽR xKpX.>y6b9$bbclnmF">[l//*Iɺwp rzN)ĿIC]U՝\vdnRr̦.ԓBRp rzN)ĿZV0!sXꜦ4&ut7V):N5þUڙ႘GED7*ٸxFN``ް(x#" Dx>t|fm9zQ1Po q hv4Cʸ�q�??�p�   �!"1A#2QVa6BRbq $37rtv0@`su%45CPSUc&8T'Ddf��?�ׯ\tKbk(ԸG "JHppE%@˄:vb sMm'#.HXьJ`E\^>>ou{-eIE0u\4}\颩*2ۤVŻg$1HY =Z&U\q߯_:~.X�wuұjr,UϋS<PJ)п(s{*mM}sq2Br;aQ7*[bMn@Ie茣n+DoIFiԩׇ\ڥS|&s:2oGӣgE7NuԫTryB*t+}hmx&yDZXxZ->u_}\晐km,6N{tڔɈ1^E5M�dH'E"%ƛIQU pE""TW5iuaTOtj[i �菌Q*7H>u[{ST蒙޶5 .6jى4v鵈)3༦L%\V&Fߎn4jۂM .⊉~\+.ɓ%#`ןyTi#qTR%DEٌA%>hם"L[b<v+^}i Jn8᐀6dH"'Piv2r~1OgT&M6�`ő-ܺwq'IΨ9'zT5i)=6#4Q슯 =BW5[K hT8ߩDfWVĤ;(Q8\IQǍE\i  Ha -l9nǦT>B|tZ@nKdk;?St:ZղgHay!\Leρ"-YfӪ28mgޤH^k^O{~R/1;-{8?malz#Ţy=Wx"HP\T\Rҽ,<x&koӣs!L(jdOup*ŵJ$U14%ɜN.U}4/ݤ٭Zb+B7kݭQoVu >c/!Ϲʧ�u˲-;sW 5E2N"uҹvn ^5͘u2DL[%M2ArMy 4 غӠ@n p!!TQTEo+rWVbTnXˬGf;L*7"|sȷeZ1˔̲nTq0i'8G7\^O{~R/1;-{8?malzonz.DsMB!pǩED>,֍f ᧵ASsF#LKVU3Ԭrj'+s4>JqYFd"D P8EEENb�|AoKMMʬEc, \xKr.ˆm9RAiy,V!LGmY^ֶ4Vu-*:!6As2B!NtQf릩BdK2D"PoŀAiq99.9ʾSަuN FS*4)e]:X)̣Mg 0%iA4+jtEN|b1Cʐ/qUIïo!�O"? X؊dx7}YiSq\5QH̕EUrVLD׶*rNɼ<R&-9*!x x >IV"k[b\B jj pA8z+ UW6F\I]:IDErY֤3Ppda=\|JsѠK]v Ka<Y �KWOdkԋn݉FjN[·2.l_yˀMRq\B<1&1\$n #y8FS*vdh&QE_mUJ䌈 sJ}ILQZUhX`HwmܽRI{_HVmHWHP Ytrxqs\!luXR}vnL K#஁^VQ\îVj/)H4i-mS(hE4G4WXȊBB*/BE_?Chm Zrkdt3fc:|Vn:@FLNgsO )ԼkSfeҼW�mH [oV29jUC6zPse'ȹ&؝lI6#=!ܔǦR\wz0* m8$�c0y_CsO)�D$Q$BEBLQx**/EN Ӷ XrMɽAw \!PЫKio#-`K J"MkB8sQ!yԉJkHNG}rEjoO|#t*)"X.Ԇͭ!ƙ~1{?(?f tSpbG ںhBd {ミ8m~�O�~�O�~�O�{bM]괨L)r22D6U=FmY^=?v6ޕkR%@xdŠuʢ`Tl3>)"Kԏ n~bul5O*|$|S.`7Sl2vӃ=R<׭u&35GzUؑk[vLr*KƮ?"E"^"U_ ?X"Fa@{Y�l5dgSQUUknܨ ]BD)rLT2 4 磛9AN4*xOXҘI%rLi ߊ MJ&|X="=o/3F4.HmӁ 4BBD!TTi Qz4Nӭ9T6f&*`HBm%-/]nɜByd  ,D~N\M7.h,0z%徺.vr߈�'l^cWqV5PHyL62kj݇!ĠѣDґBi>-2sώRM*[Tʕ(VUROhY:R#1BMc@pmeޯڷswZhUB- Lpc我?<-7|}W?(p*=�n,p™MkEOrR$iRf\acL8GiY!Z[3vsY�qiP%Nh*t7qzy_04OBm|\Gdm)543tnd=o'"S pBڴip)7Cjlzs"KèqÓ/1fY/1t d*Zplc SjTS. a!\lfTë;zʤ:o le\wp&̳#u3qtq_muꍪ3\r!Zn^&Q0DT%R,ֳUBT3DJT3/BQY'HC>NǤS-\^,ʼ9UJFzU!S,pD95",6^զŕpӡWG[e= ,GTc4n>NL6BlK.-ZL44 ܈Fím@}&=fO;lU$pmWNQ'BJwNf#.3$v2rOv<{fӠ8ۂ&ف&D&&*I?~F)(rIT4<_Lϡhڱu/QyNs[ugjJYmұfV(T:9h$WS[.!M+*6a*/1HwmLxŨF[!rL;\qIc-Nm^O򻵳Avţ3 "HF!sTUԍ(۸mK=#�w ��Sn/�mqDv6^,0w@}%*":-?v6es{*o4|Ƞ)Ô<P/4μH/hfp:^1DUN;HIqc*{\N=[c=cV ޣqST<D"n9s\R<1h'CCy adԦ{=4/ݤ٭Zb+B7kݭQoVUWU|.Q ^ܗFc6A�v1 -k:۠?Mf`C5D! *.yNߢ w_fұEfSFx{G^4dѧ/ISf_8S܉$"fèlY,%NwDv$'C|.؛ž>꫟dm_[{'r5Wx_[FvM}+RteJ ,V^d�ՙ *T 5@\"!:.Rg�Q}w'sVPEQrA?�+A?�+"\kvsjIFT3+'eŽ{HZuTW,\V>۔D>7O%ܐ]"*.@MͥS `LqۆM2 Bf*+r7inzsd�M7}_n_CsO)�D)RX$ >lNB*"\rNQqĐ YtQ["TRDȒ:ٯ[[1GmҢԡD r^vUY5Nُ Jn},GSvZQܸɂԺs\8;\$t u!mUFqɕp8Ky1{?(?QKdpScn;Nlٺ闂 *feEU& YnVs<qmFk86~#nz>=."+0keDf*FD*;^؟6eN|11# tTK"AELBD!\ӄӦğȅ%L?*Di/NʃZt*e͉љZz オQ Wں~ƅ�۴~6�5SA{\c>-�=mM۫ʐ*&9"r,&Tvdi}t:mO*hg�)ܞT?�˷S<�j|b C$24b4#&ԓ>9i;bo~Q}m쟹gvO ^kw=06*mDARއ>59&`,qHަ̵ѐ/O*hg�)ܞT?�˷S<�l*]cpͮSg4<VeRwZK7QHOFݓMg7-b{<|ڝ/ LdK-OPͷF]>5ݴ͚{=�[_u4}~}U?B NfsxIvgS_m6 $CӴ/d$n a%^�γ"****fQQz<h'TcT*H\j2G"ndA:t=k>i-r+눨3je3[bf>mW>tU^O{~R/1;-{8?malckZq KHo&]bF?* +Bm1ۍ2Ѣ$rKM2E=#[{G<kohMmjuNGiRcifKmpQXK\6,K:yIz*ST!]ԤR <xgd*bΪ7CQ>שCy 'ʟ{Qp[B/n&KH")X*ZW""'J"yvHF*"%V"ݸ7,iZ&VØp*7K DUr;Ev5b#+µ{r4z5N:nEouN󉹐Wre5)[U`vNS!IQƜۿL1W$TA{t+FdT~Us򌍰odS?cmn:ZW*p@S7 M1 4`{$㒦^J z,9{:cGj' ̇A<};D|XaԘjLi O#:٦h@tJ>NVj:%"T&h~DR3"T'p1U|j"lz>c{Y8N 6 bݾχw=.;{|'Lj 7BF(W^ a̖uo\Nj' ҊE z'aCu!й*02fTg[~<}$6eCix8W?cBD)9* EN'ȡC hޚ4i=\>*S�Y2Ԇ] >ٲF:*'Xv0ң@KNn)⹩5$7 [MEnb]n2yKq*n| D#qOwI2[mg!R�ݫسs(ѕ<syRꑶt,q`s"E- &vaMn.N&*$*^jڟ52Z|&Ѩm:2_|e5"^*ciԿ&K N ^8[X~m6?źmnDkKYgM>21^GAXt2z'K*< B֚䔝]D\xe֜ 4gI(MG2Iֱãk8Y35wRE%^,@tgAkˇsS T#=&^ɢc>%ɤY57pqdc)R">耾Iؙ2QKڜMXK1.UJf*9z1#xO(- d״m ̀D <ɲ|8g5gڷH7DQtJ5oP!ǗI Mf;B쬮3gZ�l@5R~JAT r5)h@AH, yE:]lMQ3j6ƌ) D.fKvn9Uo`̅1%m84ۆ PkK!;Mi^nLgt& \N7XGٱA?N9 . N6Wy[얻kAaPvK\U$yFơ^s2$EL`]HSڃřWłbJc>`Wϙ&qoaW~.>7`~|ۮ׆rmnТ9*RZSnaj&\sqRXPƓQW4S]N->Sk"iQ7 $RBR͐^QLG}צ&ء.4 j\nQYɜEVPߓ>g OK4p*% T-ԫdD*p4LMd9M 1uXM.ϬIJ{(49.!"5 TրI!m$F5rҮQWhDakKa%UV^nS�-6FjU.)V6$HșRS�.1Jnj & .ZbU/Kc+e&l(ÑTޙ';f5ouVfaDZ�?ڧ?Zkӭ4ayW5 Ŀ Š#yM-HUvMiC�1S˧ڮZ.bU JGRg#M02mim�"dDOڱDW>Wk}!z y]ץzrLVCJJXGuI(d˝Dǚ-M8,~LY6)%gx:ѷA8ӣs s%UShwgS&GA%t^l*n=5Ƌ/ /=!*R]%7 e/v -6h"Dh)6&B"p׬Ȍ22"UU]iuiͧ[/pT B%]X/TCaDALDMD"eĉT|dJ*UԺ=Ufj>PShG$`ejhى*IgW'J6sHL |)qeI>4/Uc[9e#ɑ䔉OJugxD({lK |3њמٷ. P5L:p-ھʊ>)RCM6ؑ*^ƌ5ԯ\ *|XQ"?٦e@ 9�("j "f~eZ@CUݢ/<ܶXQiE$BTUDT ^s[~",U@g*t}S_v`NP-eoRlrn %\܊jsTxm�4m6"m(�i��D2DDDDOƳQV[<Rٲ;sp3%u7LUen<2DR)95:^{s{:E5:U pyS-xܗHycecCpxE7-Y*͆ep?]4LU2D[z@RD-BY?-�hzDDOaZӌ>oO2 :٦m UDDrTTڵU@Rќs548)| %gĀ-5<icJx_r:a:uRopڳi[5:SоD'6+Mv#N�& Em+k+؋#KMTe1Vun\DU�j"SEMt SC)SN3&Ŏ".<@5S$WgdHk6o4,*nMmTP@QH3d<tL£d%SOq ^Y.=>-ijrQ 1*O-RU%O4@RE&=eYfCЙhfH9!.jAUDSji+PO xq2Fq2SԬFkEoECA*ڤWGMLŎn!6]5F]IDB]:#-#2e�C/a]VBPU\4T)hҦ,'4{=˪jbrn),,)jayu wq ^{Ul]l1iaG |9,,jNA2^9E6?l14؇Zb`ɋK =SecRu:r)Q Cdm<@XnU_nOrr >2rCT&ӓ#NV}ѣ֊tmθ2P:r+߾ ii2OiE-bK94SNX3D){:ֽ4"뷭FcHjR٘�&#Sݎ=8{K޸ީǢUF|beE-lUN3RN^l:{k.dj[tmfeTde9[z99sDEE%7,6nnMNT6<58yiω*&{{9}UTmSOFePѫGnM>ӭk=9귮7qF*/Q6GQaF noUSs=Kްi]$m$Xᾛ>F=Vu*n:z)hYJ4 ⫫rt l,+J-@Z^tf^IJ(hOs*3ṛF>O-FzW0quS5eD\LIUqH tA4k!tv 3STͲ^LJXvG+\Nh7=yjLˣk(6L$}9U0}LT5hYs+θ2,RGstMrf$q\PURGVtdʹ( feU霺;<e> +7jz#&]D]22U� ;F7n|Z[vJ}l~R+EicP)ʸDVj4Gb4`¿] IE%Ĺ7:J5EmulWUm֛}M/ŕɲ&*< 5n0єR4iTfT@nN6rYiz3"1?z"+P+O8LnjvK%u#[]EթUnb\fGp;ZeҮq�b'b;llKUXZ)=SY7V7: e ;KS y\FFҫ qbSOO4Mێnl7u%nWwdPmzuv|9'ie4h+nHY}ʘqڏLJxީ h1.k;jF&"GDQ锗/+!MajUA7Qw;pwd-J7< F S|*3ITC~˂#U aQD b*hFb >gSvC| "NfpE c;z92BnR{R^*/^RiӍ+ģ3GPH* kHHSI2㬡le+-,k=о;mUIwh WNY3hyԉKh۳kT-vU249A�qlvDϵ5)#ԻfgI/(r]�m  㱪u i#:fB;ԍ�$ˉ[tl ԣaEJ:%p̷ZC #s$re ^vVv1bbm-QA*5Fd-HɕmL̗}&xdVxljtuGbIJ|#-D6 TWUO79:`nYt(T ΃M8r.oxHJ0г-5#AKJ8k\HkTቓ35iL0w}HҸq&Uҫbr;W (TPYU.4 "8i4lG!Ɋ̃p(*6(^VK1RM|ŊE7V"A %rC0F%⛬9;%1_ysؔu:mV3'1U*q51qĔEmlsV2Ht*I),+&͓%*`"Fx~?0mvu2.Ge`Nc�#ipY /ka*/N(Yٺ-XLG.Ծ6d7+r$;,?k�z/WLJ3М.UZUI4)KH'Acҳ6d8R&e}Q8&iNj\lD"L12ۨ/,8OmQ0 pc*[B:Mr) Wi+%z]Ho/2y <ז)[ q+ o'%.6D.h~25y-4γBL&[sSJV@D 7`>rW#h+#<g%؁Rp:Yfzdv1#mqul ="NFQ&q"LlLYָ͕SۂvͷV82[ kTm0؋Er; I|݃/FY9~i"i捱qMG�KgeW>[?j!֡5mƦ-Hz|&ĥӫrY|fQoyMaMhRl(Tݷ3Opոd_Mtmz2#9)gUƈyZwzԛ>M-6MhN:LNa\ h4Np&r`QɕSG؏&+mBT-hGrƤ^#Bm^pUhL"Y4'6f7 Xi\y72b6/"]nrJ1pcLyMmB^c޴AH Gdi&A2([ןqFM$�Ȋ')@ SŪV1tj&,VbA:<quPG_~Ǻ-=MkƠ*zeR%?4"rkԃ7Yi{Ɍסmʺ^&0M#ea#L7̦]�w@oAu�Q1i8ΟmM% mbV& 8en#[pFWA/͐؋,0ĐmȖh wtm_U ?hh\6 \gieN .6ƒ[x%:W9#HM[m}HrV Q/lɎQ2'wL1z45[פ:K*4qv£,WKmwf##s6r$i8{{][2X˪n3UCNs-w'h:Ƴ(pޕ&J3(ժe>) o%Y77ћf>M"N >ٗ0"ɕn9 eSENl]&܃m왁,* L ;ڔ%I� Dnmxᐶ"S1ozbZ6n," lC .6T|NtT֍Fvd$Mɇljm_(^fC2Hyss1>u+쾽a:DE)-V2]=G48]NMslH:~P§*3 _Tc LVE"ot6DIOd->&ճҝ:͕ClUM<cQ4*9r"qoH7 ߫Ko D} h&b."mRl+zWeNš :36+ںm&OpC*+ 6-%22ʺW>3uںEh W&owPS6Q.l=UQaeLyu ȱbF!9QN�"]'2bNw2-f|胧 f"N{!i.Y6lg,m7T\1Qˑ|Ľ{ ՙc& ŽXub[N0d@˄ ՌEq鱥EʥRْu:, \ɮ+P]-6EnN!]3դTU'$S1q%V~qmFv&7LPմiiVD1>F|cRUuI<1ⲥ#)h$JpmECT% u*)jIBF0&e \& d X7i1[ULG8 5UvdV b7xX _O!*o#e$Qg\iؘ"Ú[kDMWu ^nE|Fs:5T4O2׶?.{ڍHuu �vE23imܚ}a&HwdqoV@PG V\%B=!!ѵ컲踩R2e@!,'V_*dx$Je3H T(9>l(lcq}-5޼`%sr\lGA�k@h2NzL)1G:@W(ۍ *"d\2^;OeJcHdM5+D//hlAq'}[: x>N=4$= ;C&hmtdkhrB*9eŝqx^he@C\qNlU&d?iq[G15bj-:QWTk0f *Qq3Q˷QxE]uSu7�q#r~Fc־c%>R?n?{k7jy7Cb3J%DRAue5,J<BIV[6Df =eշ-BݺzCaA₌Aҙ"޶<ߴdaf18 # 8(>h$STF%ţRcOˣ 13Ao<XI8ﭝI#jm?[`7koѶ"w|eHPpLM6 l϶Q=c"9qGc5Ԋ ::ђ:\;2K`dlB`@ed:2"$ m^713+)P̜q m9ZELu A`�+ij17؅Ѧq iZ;^jU pkۥZy^թNN֦KHz޻2Ժ]Jޞ,.Ǧb4HZzb"J\ˊEb0"7Й"vKhc❷q;UO'?$<3DRϧk; ?L{[P;Ɂ8 =2N Љ۱l]Lz2L.L!B^zU4`nTO'HHK6^\>?L*}d[›D7h!\#9X%MFbj* ,ZvK}1dRQ^`^B'+=jrDα41Lo۽yL~Q<0xDZ]ZA.-Ov�+vS9$l 6]**cLp4OW'sMM& - +1j0R veAAx›+Y07;$mj\_R e8K7 5߸"g\Bɩq0l�5hWXax'zgęWm-tzyHkVҢHBHD V2@VP'Bh3=K[>98"O1!A8B<߫JxxteW>[?k+4WGf}Jui.v^CmULC\GQ]mj^p7O7ejCh>_ZةeE�uIml:C6P zTG (nL*S.jcLUSIH~U=a#61ީp#5G*! &h_$;hUmK�e(c p'U$P"E8.:B" 2Ј($Aed֮*j69,b|GPNSY0A94,W`QҮ9/E䊙ڷ *.Ydyસ_OWRNV|˴Q:ipő*Yu!DU$EsD%ڃyb"7&`RmKV Oƅ6^8=.:0օrӬ&7$*Rq{#f\˔Qr<Emƙ%tHOYhTTLJ/s*$b~;rmzb=İhD>UUj&)6.vLbL@hfj[+M]nĨH+V|7,Q"M(Li#/ɈUGhINJ[ JTUEiQs<"~|5j;8{dTimjAשݐJAnusUKf+QIЍP*UWqFd2㒪5Hɍ%%I ؇҅#}ɳ@yNzŻ&Z%6RWɻ 3H깓2ʶh bĦSHkK$DewlzβГbF2@ϦJ:V ަ5|iY5qV3јǃ?~]JaeYD'RnDs5_2Oʿ?bl{PK=}&ZS܏%DzD.'2KdH,WKNs#]V& ,]ۄ :M:BMʋ>*TZrzimdqZFcS v6n/5[? _}?[ݍC%c'_X��~ݑ+Fi䕤-)E~QO Pö1t2ZT)b̤lVŎ(&Lixuj˲qa<lP~#% %jYDTy- HΣ|΂f[Xz֭æLޯ$C8қ5nSnB0RGC@Kb~;r{u)5:bR <ȪÒTWDy.I\egt6uX3r;?G93rx$)@=-3yl6αk F)+IK1 LC~,$OU!I֝ 쐻h COL){&*طl]eu\~'~On kjhHvScQ]q 䐔�>露륩""vFR,JeS5YE'rm۠R5yŇ[AUp&\Xb֣ mzm5O*w) S&ՅLIu?mp:vX"Φ1!.>ٱ6;HBQ_rS Y,uON,U9*Сƌo0šU5eUKNjx#T)kT hYG@ -hD:{ǃ_Q?-�!^%U-+ꍻ[ m.*<vlun zQU0R٭6:5 &'7)/KO4[OɎH{Ekn5"S ~S&lN+"3I/zkj$}@-ԨTJTI-jȍ^o[dm�Sf`Yf$H]AČMթpϐ"_,�+l겤n#JzL|p)LIMHՇץ{H\/2Cwp[n;55}y*Iqs911 ]}.3o)1 vGz}`eƨ #S'zۤ: M,?;袠)""JĕQWt'lGT+5GU-Xc| a^smUܡ*f7DNl}㞦]lۦIdhIeܶۊ:7Aa.�,'GJzB)cL>F4"̹i7=Fu] \YTE2NLg0eb;I<qFl!MEd~%%#}m5E|[yZܸ tb=FckTdc%cdG0`NKb]z*:3\yלFHM/;_&Zd.x,"+؎w"ʩfH##..KCHKhXq9L !2 WB$!m BB$$LЗqW,ō()LPF"9kĤyۻӞػ"FǢTtvΟQ2ݭ@ H˾! \6XE6Q|+3ο*b[Fعr#m+ܜnH2.\"SeR^dNOLaTi3~CFnniĪh$7h}ޔAH]ZОnJvA9ClʐNHuFQI#cr1olJ[9@#Ε%e\>c컖̥ܱZok^V\@&oct siu@YX-n}l\O,ɖ̙-Az H9!%Qo|F62(2Bhb'\J#SZ$gSmȒ*1iSiZ"axVhf\P{MTzZg,$伞De?\陵yW%.n D*AEr+< 5qmsAz;X݅NۦP);M9'1LV3stl-�:lR`bzFr�TF 449{WNi%7<F.ƥƯ=_eEόn&*'c;Ql^ZN%aMЫ3-ZdbQ*쑬g[0x@wilPgcNjI٧BQYD(lY6B%O(G^pLꛈ1ګTy-ȉA, @ 2\Mث�Cu)%I}b[^xb իלq[i7e0g7h$bZ@c}ث@^4hv-SscuGBinC)+2|ƙe1/ !vj<G[Ԩ& [+,2oS8y(ti;"!Y4iDk:'$Drg'6$i+ p RW;㒓HCEuˎ /HY< 4ZeN M۵z!çyd9DrTo5(5fýx\eN M۵z!çyd9DrTo5(5fýx\լ˖N=[7v>9.Y�w2pf衭}ԥPpQq Ö42f:D)fM1\d_2TWm]\AHޱּ USaxPA=)iU@t7mJ>tBz$Q,˝#T e E!UQ' -TQkFn8uYz%V28ӅTjTXnhd-n7dlrU.I{K?1TVu5 ZG\' m`˲}&Yn j% R6͔Sqcv 'd;1 #,̹k.ʨ:YH#QGy<L&K2FJ\ݮRjVNdbFuLtoxA�u`ES-ݣaã\0_RjR|rEx䎩'^ Y1[UhT5Z: -bt>Ug;p#עT!=TL9SHxؒD1մJ|\ﻝՖݏ i `Uicŕ1oOmϑČ: Gw:jEb-WG㭱yxao66mIS^%܏oMaZ16N M*R行'u'XvⶮsJi͌#eTrk66j|dMLH|4*̙څYuIj30A^~9"z!&mM~3"tn]]|h5LIȍ7~PМ�YZU Ut(OocH8!Bf(i访e̎mb. z(߆P}]BEA2a'Ev[r3"8ӉTn{ō8w`\sԱ'aE@Q8TXY4ލn:aiׁ&L&2b o't)sdyQ.rf%91c.NnKE랍|S' 7*!ft*l"#\p@4bZiCîr#ێEx7c8`l:q~9*dβ*a.3abBRf';P6mIGV+4.mv1?uP6 C.J샼"*CcJdUn4EZsLCzm褈`svZtR!NӦHEO-[r推%/֛nG\}؊t8 л*1d>z)̸eUWFIUDirl &[.w{0i| pb*fS4jNvLmڒWh]1&ے/b(m@\xD+zUdƔDi)j9>#f6$N")I>lrRV2MZlI˩Ks)PddmAk[\LS�ʳT.Ypfj.du=N#R  ٗ-&rk5ƦSW@c\*H\ѣx+eevU0EMЫo˪,9s4͕bcƛs0pyGխʗknZQϤ+4FYb8O6<B[ȷifz:uHQޓژ U e ᅑЃo$&LG_Q0S(Z 6fh&7&S7.mwwSNlf9u.@XyP$ܒm`G1\UZEfԚ^ob#n:fNG)&߶di`4ABTR i!{T+ջO0 vfw)M䫪,6u$Ȟ7JFdeåW4|{G-Ԇũrz_E*#e= 59ՠ",1id)*|ѵG#}oW%VUs]&7] SIC6f[XsolI[HmUиdb L픪ir+8M7)͹RqxPZH7qGH^#Yw-{yU"ߛڼYHU#"I̗dJ"랱hb W,K Z[.G4#P\3m2q$paBםv/TR%FX <*.GA KrS0Hbjn}:NפHc @s{R7P9M.7RD9)MCҀΪBp�Q$AE^U@}Bi6poZ h]KS"e™Vcrdxm#)b截(ihlf<cqrqRꢸ\EˎIu^Qj$f676o;DLjY悅^KFyŒopq2J "Й% $eBB*$BH EENTݼq6:NJ|<RFS$4 9a@W4N!Ž&ј@"4sWHўB.&y ) tDَ5I$dfH!o`[o"Dթxdp:.]"9i&<t;1~I<P4{mϽ'J%vfyۓZLE9bTyqA%n5̍q棼ܑ5o }.bI B"MRE4b9\ݦݽfbI+E2(D>r ZRE1Niooc;.[o:i3QTQ0#l %1)juUDUI4:|m;v$o (5ۿ$)6UlSayBqYQq=ݽ7ؑ%nPjTj\rkxq]i'ȝ]x&E3%VՏozŷþP-Rm|&Tqh^sc"%- Auy2�iy8qPU2yͦ̅"ηmǂ.@s/ BW)6$nUDU.kwr6zjy}[rf*e�|3ș"%ȑHt!b@b&ޥM 5La%J /<mMj=)fj^{$T=+" t8ִPUBCxw%Dѡ㒤e$NxT:E$QuR4TJ^&QԘ́W˖B0mXnA8ZZ!p ]- җ7}DVcQ_PʗLy0"@,ՔdK$RDU .V1UǤT'tr #lH9;]Vli]ԙ-vfyۓZLE9bTyqA%n5̍q棼ܑ5ƫ~iv ȼb9"6xo Ib_Od;T9*/7H=UF\_B,::J9<%TloPHm@HLTI-턈ū!�E"-h(Lqs2F ]0-.zMbbCM,k6v ǑtJFbqbN֩ jB3E#6p<8`:M :d63UAMDJ jl.!Su(QGysN" j4{ܦLo<2!$E=q3CkGV~SM_U*H N6Đu*ӛ{Ma4JNAvuFCPGelQI mLvC*E4swE&KJ ^ˡ\ݡMQ/GXy\ʊ!'j -UJ[!%&jmՈ-;WIiQTWz8((QDȭF,^Gv*]*M)}k&K�}K\wǶvM4$yD͵Ż.Bo<) "Ϻ%67 DrV:Sw-dSyPmrH>-Ӡ‚24VZ+btb@yz"#Ԥ"20#V[HUC-S]*#q5M9rU9Z]rCX<6C7+o<}KiIMefhwE:KV� bw .1Aw�*M**l2㌴㌪&mDd TUSkf*2Mg-JQg'|D)$EȪ85v^H|H+IU:o*maF~SNg˟)Ц9N#jCf;ԡɋ9 42&RWT׻QPE6ܨ^Z\dމMMɽƆҎ0'oiy$y\K7y ,o CDNN>%l/ťt ʣ\cw](DQ+h�*P5+h\dF՗PS$Y*w<jrC]%RUڳY)wqbTnm6)M; iݓirpq7e^q=l޶)GWFIwxL2m8su3ώJ3YUEȦ H>~"vaڡC"fO o�tn5K B*~M7A(&*RT/1h3"u0Y|DE1*nq(JkjNl\]hbS\R%5U%UdKǓ Q1MmƜƋ QB G4ީ**PJlΩ%]\pQ 9NidtWX*uBLSuKVx}$ )yqrX͘f^-6/8ȧx  WByT[ƩQv5Q$xP#Ct(:Ĝ: 4d"O7_|ǙJd"(w3QlƋ! iI"Vǡ֝rACS$+?L^aMQ)76P2()|x"]-QUajsXԨ/}<$&Xb8&FJI Dϰ v1q" eoFE�J( }-qsA^6ǩxjŰ7 tbFDE3%=t L[.[-J+M3�WF;ݳp:.2Jq>E,%ȲjuQa6əˮIKz2[%QU0LSF@ :yIDBTu3hOzUUQ,zvRn\TgP,"UE-"8D|oQ!;Tr*H$-m5 jp:)tN%.0 <V E}!E#DB4iݠ0p#ת ʘnF$掶Һ\m]d |PDw5nCmM(b$aŷS,x/Z*)G "1P͇$z y\Wh˜Ô'Շd>댪KRl>.3"Ӌ줯|lGfmqK{oR&)F`S\!ra6%פPW4ոc)31cÊ3+-a�^U]ZmUi5h.֢Fï4zA:MoZm֕ mӄ˾^\Vc)!Bn88El`M;q]yևCcR3IpA}m<M:qL@NPbDO3�a7oJgKeMfTF\m;%Q`P7S9d ƣ1'x >á7A3[ub%r)TRYuj! "-Cq q[ӷhry 6*x�ⶆqx"۵uN-Hӎ([BdCR !iH9+咭XB.U]ME=DJӃ%Bȑy%Ҫ{~9s7aj7"}\DDSQlVІbPtjGRiDzZEI%rDm@gŀx/�s1B?=PSNԸLzYq3נB8R%>NĈ/m6*ľ/B.ޯEn<B}T$SRQtwغʦb[u)֍"e]mfȒ%H{'ONqY#c Z\VNOOʡ J8գ1;pź|hzMNmuRDHe DUl{5 M/T/YkwdyZlk*.F2rB`઄9x&{+eTu*E[G׽W WH]b"e[K׵nlCzu1q}i+/ 6J=y߂o#נ=Djuz' -j@y"b͎wAm.KZ߉KRɜx.LiϤn8H "~@ɰLrALץrL5_])$=)zQ<i[R!KdNHuuQBi>-lL#Q:%O2MzT As!BҾ1<Μ-l]m֗P@D+ndcT 1]BL%>EGyG[n8n* )�$;~P0vʨ2:�e!aAm7mMѰ�u.AWU8�TIAuUStQQQEO簈 tȉ6݂82CҚ ƙ#M#A2"^AM-6=:@P?C@IDTQ"xS\׏M4m.kƺQ3_xOUmꭻ⟕}Uw<Sv襁~TzVOʏA۾ۻ)Q;w[ws?*=nnxGUmꭻ⟕}Uw<Sv襁~TzVOʏA۾ۻ)Q;w[ws?*=nnxGUmꭻ⟕}Uw<Sv襁~TzVOʏA۾ۻ)Q;w[ws?*=nnxGUmꭻ⟕}Uw<Sv襁~TzVOʏA۾ۻ)Q;w[ws?*=nnxGUmꭻ⟕}Uw<Sv襁~TzVOʏA۾ۻ)Q;w[ws?*=nnxGUmꭻ⟕}Uw<Sv襁~TzVOʏA۾ۻ)Q;w[ws?*=nnxGUmꭻ⟕}Uw<Sv襁~TzVOʏA۾ۻ)Q;w[ws?*=nnxGUmꭻ⟕}Uw<Sv襁~TzVOʏA۾ۻ)Q;w[ws?*=nnxGUmꭻ⟕}Uw<Sv襁~TzVOʏA۾ۻ)Q;w[ws?*=nnxGUmꭻ⟕}Uw<Sv襁~TzVOʏA۾ۻ)Q;w[ws?*=nnxGUmꭻ⟕}Uw<Sv襁~TzVOʏA۾ۻ)Q;w[ws?*=nnxGUmꭻ⟕}Uw<Sv襁~TzVOʏA۾ۻ)Q;w[ws?*=nnxGUmꭻ⟕}Uw<Sv襁~TzVOʏA۾ۻ)Q;w[ws?*=nnxGUmꭻ⟕}Uw<Sv襁~TzVOʏA۾ۻ)Q;w[ws?*=nnxGUmꭻ⟕}Uw<Sv襁~TzVOʏA۾ۻ)Q;w[ws?*=nnxGUmꭻ⟕}Uw<Sv襁~TzVOʏA۾ۻ)Q;w[ws?*=n�dRS#@\ڍ uМ m̐QH3^*w_#*_qWʗ`~wqU2XG|~{w_#*_qWʗ`~wqU2XG|~{w_#*_qWʗ`~wqU2XG|~{w_#*_qWʗ`~wqU2XG|~{w_#*_qWʗ`~wqU2XG|~{w_#*_qWʗ`~wqU2XG|~{w_#*_qWʗ`~wqU2XG|~{w_#*_qWʗ`~wqU2XG|~{w_#*_qWʗ`~wqU2XG|~{w_#*_|(: :U^k΃NN [dgᐧ4Wۆˀdf. B`S**faFkB$*YN RT\4K/Zmrr\`_Я#O6eߩ<ӟ'_c%ˆm-ܢWXD>%x'[`x1]w�UΨCR 4=fI~fBU|GTv9TCxj/6XO]S'ƍض:[O~_>f `J+Qj}^(8C"_ ~6TiP 3\~>˯M݈5vU6tȲyyIm:W12l5~\ӥMHqO"h�oUJU j]:[#"w_<S8mknzpTa5%ArZ%G\y"+GxoX&tASyI3A=ٮhI7U"BkWꓦQjp謩.)؄=I7:BxH7 i]f|XS`e/+#L>^ ~2rRZl$++Th۰1m0|=dJ$%"uK_EH z8I.zY7;њ4+b$xyj_Sw;mE쩹K<ڠx(Grڒ!nrL<4ϫh2|sBŏ->!%x !y.vlbr?2>cn\0e݉!ai,R쪻r@t-L`!*'I /:67ު~DnnhtUqcřLrJnd4/3%IRVȐ"ع, 6|ɒ.>>N#L2rhj%ԩ*n�'mT�oSw;m~oU??eM#Fڢ|a;9;$BVM։T%zR3*<yMgO2]�R$?Jϭu_GU\šC)RQ5{e x=t |ȵz̢5.=r|ë^t#m8Lnj�ة 檻S-d9_6 ;BDID^6eĮI!VTu F_6g*6uWY ydWOV]9�Fs) giX8c.x)t"~ou mb}� �;-mUlX/]-ԣ~.(A:$?ƬGl�e\JEV2 )HBq$P׳2Aȓ%q"qt̋275URUU"_>s|j䵑)} GV*FhP%F@()+GLntz$`>mdǫU_mW!xNXmP ͷzJ)!q’m6kaDEiL=U$EBPUEO_/ɶ5`oZkbt9K㰆R;MmL8< dnD-䈙fuZ=s.@YOT$<ܑ 1}ZC%MI)q�m@�Q@2N""pDDکLZ˥#72N<֜L8*Z*.D*U;0݈g faqѰ'4ȧ߼e-A#|y*?Ͷ`}kE0B,kC|b8 uW|ES$qGGe#$dĈZp[#5lBTקÇI:D?0:?V%`3̗ \KKn $e'6"Q*/FR*3 e]U4zRԽ(Hpjc^ԧ;{QFL\HӪ-rcQ\CHQxg "H")"pDDV6}k�r?ꗝqIzE3U⭻\$R8*ezzh0dԥ5 +<ZG)f8"mo[0h,f<^k?'/!D!$TQ$U4Tً6F7i^`/8SN#{}oSk]Wfܒp]6cN#щtI`+Ƌ_z8P0GϊȮYZZ/2$ԝEtcw"GE<QϚo&]e)%Qd3L"fJPImIk2�P#Ʉx(Î+({=tUQWR$C4*D�ԧ,"< .~4aaffL3A?[.૒t.إN5&ke`ᝤyt^e%Oٙ[S6CZr5Si �("<QĈ'ԝ�T�5eN݊n/۶1]w�TIbRBeYt\_, (çMbwSqxeޒj_2dtXOj;N<}mc qBnB6ˏɵJƼhNTVxO<bum�<|C>r_0㨟*'ϗqg?՟-SA]3!ʊq_LG>�Q8UECJ`'"5z0FtנC_IPE41mC#BXl�¾hf:,Dπ3tؑ 1 Sh6qAx UO{vV.=zD��Vv5^]>sTT%9IdAkYJ/] e_(\ɦE.RsLyS%[ef2/D}L{"b@b$$$BB E@ҩH)j<ZHGM=yŚ|p \W6F}O;MPK|,/N&nƩ/y>y:P4immT̓j59OD㑫Q#2sޓo8邋[lD 1JrȨZ5Aʔ#rTD-@%Dyܐ(k7@GDAr<.O<'̏.Eh0Q&Y,eߩ;�5v�jnͻ_m~&?cgֺA#i e3Lj<>3J iq&ј[i>@^yϸ j7$�R"TDO:gozo�v� �ozo�v� �5L*P^x mLf!5U_y Vk&:Cj2ar bؑ"mbPiQi"",-O<<KOIML%pJ*V4 ele~G;ͬ5cUN"Kk5:>y#|6\^Wfq ��S2"%Wkoc0r`$jYz{~Gӳm4 l 6<�H'M>̓LI<J$϶yE 5ܽf?kѐt.N6d`i 䢩Ԩjd,y0?T}̼ND<{Vhx2VS%tj|3i]B$)T9*#e3Uv1ihNUTһo�:l?"+}Iyd$ֻb7ڇF6}Ѿ�}/iӽnShvH% izf g MVߵbJ .4�DD7@5K}rK<xzSހ^"!&D !^ .e#}&ĊMpP(md:!"h$ D8IQdB9lTgMttӭTT\T=VMHuՇx::IȢH$Bbē1!^$\SILA(Wn*{^\O|WwjJ%rdM>Z6 ݊]O2#ʧD m�o5.E;܍ 7BV2R#%.S* u Qh'GϪ;e|vJO�`*uc`B($t$_n<!>S9&WGx/ԝ�T�5eN݊n/۶1]w�T߂q?eO"O]jRE¯ɼ=~s>5 (K[aS;S1Hjm%G{GFb4fm[i�LŚm 3AS_ox%5"68Üzɰ%K1<UcSb# L'/lcIjDx$:ՉqRy4ڹi7e&=f+ $~3H[t<F 1"ˋ{[RrRrMLlP3>EϥQ\< 8lyđQ~M $K2j g.<Ϙ %UTrEUUDDmp3(BސUI^njB%r7yigG{N#-B^PsPUzM:zT8*"hQzzz� r3yjr p,6(6KҨx:2ٺČzE3Uᬻ[=(wV"/*'ReIˬp2.&e6[O :<]$Sl*6ǕUB_,v[j# "v)d+U&@vV}<LNQSmklF[PЦ/7?u"t(9dG�39BEje2OdXbL.]&k]$dDbV(Sl65 RNנo<Tp12yPUƫXUO-* YZV1NM\fJ=u鎺 H�bKқqN܍ZQC4T!qg^K-> 4:cD6 sEv솱bja-͋0˟8^ xy{l!!uKuF75͆:t5&RS^9U3EO t[s^m2Y�_ *NpRFhϺaf�Y-J胮Ces-)"sdh'U9][`3*޹$D{'6L +sSMn*EsӐ)gL[- eMJ(9*3D= R_[.+e.Nu6dƠ$ÀsZ_PzMB[N)42E;>+;L+.55fTx[^lnqxFr]`.+%R6B]WjF۱_ܭvcf6}k�r?IP1rhY~uGlbi$*.{R t9b%_N'ʆ R" h̆ճVNJH.h?j?Yn4,ksڏ5CU&+&{MJdN~?5ʽpR=q~Fv6қf\duQ_R:6{S'2m]K*]jK"mNBh-u }czu9@7WX4Fs\B!CgϯB{G N!dPZ5�LV`!98_>h O 5_QB5Uv+ HrTk%'z>WOaQʅx݀'Խ;MLSmY3S"Dϩt*SkYC堪AϓY�{XTFj)D^Caf$_?_ :,Hs6'^u*Ҿ4\=<nHk[b7ڇF6}Ѿ�vknV1̠یʪx.Ku5*xZl?uD$kD յo:Ě|i/yˏ1phelQs5ԤJkv9V&nu D PT.=du :ѣL-9e@CȻ pd~.:għE%"koW 5AW- 􊥹J"ɇ "ʸI|,; ?}E%.AԹ6܇:|EnVȗe~잳S^U=UHSSBXoT51rD(i^lWU_!p[426i$Z5*B &4$WMI"`+6�PDE8DN*ѷd(Fl[t) 9"SEMҧ#D6q98:Zq{⋦"R D)/YNU*<$Ah~U>cboEj>()1EOi۲@oNP1N*]BI(3^�sz권 r& ZB8HFѩDQzrDN wMGOJ D/#'�eߩ;�5v�jnͻ_m~&?cgֺA#+T=R"}2eĴ" ՗/c-QI\EѼ_ >Z"I}l ND^:SneE^`2l9TߞC2+ս{kcàv+>UHu󙎢>KrUrGe]zP:cI1n{̇$8-Ҟ=:QM$W I''\bCLI(&[bf鮢sAVdڪasdjzNC>%P5}I,>KЌ\'_?Sص\-ZBF@_1}gSH .˶O$5mC#BXl�»v2.?*ORjp!BLđDzΜ6)Ԧ9-.J||w0m"UHfLJψ}$)4Dr^$!WOduZ^=Mj)P K𛎟J0gʾTگ^NeJI.{osl&ЀP̛0p DrQ!]Hl.hՕ49 h5(b-d\7R> r'P$ _ 3lAMƜNp8"IlI‹'2/ ګ$giEH9Wf#n5ȷ+SF[+⺎FO޴zz\A :D#M.`c@a2d|~#lY-1 ۴55":qQ}hS8^IxmJ“*t\UF1Î!ESqW4CSvQMI�68YgeUW`~E6#!LQ:UUrDUvKm-K"ޣn5QS">'&uXZml{!KDRt?beB"N9좢$$*BQQxTMrL5aWEEqI8m|4;%&UݬfLw)λ gH7t8CaG4n츓*z/On#b"2#2R3%2"\Ȉ2%U⪪]w=#zC֩ 1qM:trq3ELS3NğQ$/Zm5zzŷjUe"F�}&G]""Z'Xk�zx.ץt:0*嚗58G|2mr^skњD UDPrTT⊊ [WNpgtQ7:e3񻖂UsVm0qL <bC*|Bʿ:Kl'%/myK~(6UIptEM8B㤀�)䈉׵FJ%-(>(Ds6|{v7m>lqj (K͊|"d믨LH 3EQ$ȓ.ث`̲ & 8T:HKAUh7p2X%T,CEx*1.1KufNJ1^ H2ֲPvuiǝ$u. DԈm|WݕȪR9ɾg&cbypKЩN YieS>9Ifg.:8)R _zsUM%UđCe4L(dOHpTS؉D E<Ť�QUs^ t/N+kzJTx)MN|^d"h9䋞׍Li'da:xp̋OZlA4sؑETIUEQz8*/O_ 2Er*e3^/<e8~W�l6`qx|"/EW==<nHk[b7ڇF6}Ѿ�v[-)#qΝ!I \_| cR�g))1mIm#.49;㩩cvmHLȌs"%R"UUU⪫־ŋޗ6K(cG)`7S4)(. 9;Mm֭yM@z NsiE}ͷ^l:"_c5ګ]-0&Z 6ЈY_GfCfĆ}EAƞqx(f&+$LH"țLU23I;0:ݬē;c.P JbmShpaSJ|Hb7 F`4ȃc'Z.d=b MNou";瘐n"n !".�Wf-B|L1#<+n4+|DTBI3MjcV2+/ǪEv[*T8<ewk|jC|itZ|JU23pYc5qR"%UR3"#3R3%%R]S)FŪ> E-BKlK>ۂDoj-/%m%3MZ^J۟Jgj o $$ $QQSQS$:>.^O:�mX]48&9vOݒFvvJ;PZy%aWj=?X?xmY쬬;(:qV\x1دM1&V lq!p [g(BbcFؼ f�"J*qE\dAbrʪZ5=> œH^+*N"7Zn.~ɢͲ`nPL[8v~$"7He|ʋoȑ)vK񓆿5UfzKv] bDdM+@i3.TH{p ҈P^4j6SW:̂r4@^ 'Xf$+EUk_tv�I#d>FqsV%NidbNMCvV i-> c\*Rt>AZ.k`|ͬIjy̿K}EfsDԊÍFpix�=c(1$2%$wulc:pA_ȟRo1UC.Y*'(FHON *q&^jCMÀ.n6Zz|SjI$n d܀L4>/#y.3/_gڑoRhdr w̮OG i8-2Щ*J|}U3@xo|':*fUU=8WڔWJ[`^rON/UDUE8*t*t�Ohͩ " L'<[ qMb%nCj ą @n;t=u8n)Jfd*W:8{{y>ֶܑou mb}� y2mL]i\ P*u.W0Kq \p�E")B ̳|E3i$LS32$Vߍ!uAzDQdnNj"?E^UV<Wٓxˏx~)j�شLלmPź̈́D$#LoG<ソiyL4 4,LmB "DM󷘤ɡU4Zt]MLpx4sW[K]6&PS3.%t| z;d)qF-=GwT${|J[SjKjVO4PeRo>I՜v6+*n}MC5$��jee)C@'K_z^s0i*yH/:R)-cMu<}zMgtYoK75n%  qH'�2WB'$Ur* 6Dn0h%]I3kLGbzs9�ޯr䴧ɧU2ٛ&yrr}<�TR_vDSd=Qq|�W΀'^Ԛ.`AqS'$8}↑4*^ڨ5Tzziy>\y͸+ikHAqػ/G9 Tir /ܺ|K < Qq<N)UO]Oߪ:3M%%o h2zvr:Q@䫈Y˂rj"g0ȈHHTTxUUUz} TƏEU?5VSG z69)1$SڛY M#T4׷+g-]ؽ_E8BS 1y}EڻuUkKD\IW8t9PDI2QTZؒRH.sE(r <BH.H䨐7qi/LM>pD(8f䗛O܁86|* )Is !9sT^*By{~*|ir >NյՇ5JnUN< 4!M7asڏdUhը7Zi$ 2˛UOڟQIB)S#81";ۭxzW1$Hm[ƖrPGl^<j/S9б \^C⮦~OcfW8t) Dlz.ȊE`PÈU9dTWq Q*&y'zKH돾񓎼铎8#3%R"%\UUWobmlyn* z`ԡF\UW*6*㄀Č�<Y pj&ZT#N<IErjzIy̠V;'8:\q3h4ؤ]S%�͵c铨(v+&NPcc.&WuGlS KY/7>|jH&cxRzC>/q#_fbؑ"'JLDE9:,E2M㟸m~^֕jVMI""] }:ABBNQ?u(.f~2,* 9LY|g\i"UrQjd֟!Qc=2/m%MZ|*uM{$. ޚ(_rUV@ZR)B<1|ͺ_ {V;!0“Zʬ==&_#(iY;+dVjyV$ߟjdN'Uu HtVR)6Hil_/ ]U{Du^[qlS"TEEE⊙*xvhӍV)6M&\MˎjG7VѥHrblrT_EQ\S%hx4DMgw*~rlć?/ F 1qzϻlWii u9'.kҫ)�=� )jBrRoP#B2䋘T.̺dDDD:?mvuM\BB^s*LD-B~Em^1$[aDUq�OVq hzmJu\a9Vw4fctX즴b - W1.J(Ԩľ<M0�UciE/T{-i'ȝVqO+چwVM8e-:2,zzEiS̠?YDN.dfJdK",WοYq#NYm:L 3O<DdHSg2dĔHΐM-DԹ5Z*M]e?1 "!rb5tο)8{SBH~ M.J$ 74:u6*(D2tnId,ɽ2HJ"KTjFiTh6MlL W10$&+$*+Ш)jh{sr)ĝ-H<<QA/[RkL0khMLL�9Y쭞zߴNWV4@"nR*1+j,sEHI!ɿ?)Ϟ^TUy>le!>l߯5ǫFbS!ݵG٬Rș8"|IՄ$R>WڱWBgXPmz@9q/ugSmrmX:pIQ9Ac՜A.]s�XY3�MFD#zx^�m?�j� ��!1"AQaq23Ur#BRS @b0457C`su$%6PTVcӔ'Dt&d�e�?�JT"mשM8U Ou =Dm).I_�Hq@<!,b;2ۉN A|:DUBc]Yq4N܉U1RiGt@旯<C^6p| Ct\A4{+Ip=IY Bu莰w;cnΆ!]~2Ζ])AN 7R�VuN;!H+X8)w/z@$ohE6)IcpY8*�c/vKnTܱ=2K'M!9iNlUTWukX_w,yn1dchM?yy|Klɢ#sG1/LE\.&4Ǻ}Mr+/-swR-LЩ1S4rHmN))&T[-,kR.\>4RT(8JQ8#7pLjSڥ%=S٬yNʠ%T׫qBB $}RhA"m8uD[]WwH׽庯;_km-Ca H5\a%ԩ72TT2MJYe8۩JRw>; ah1%_P#_Lrߞ* G* rHBe<ےRKS p2۹ @?RV'lq].+"[_'d6Vlq:n9vvq#F$FZU*2TQI,ʊ4H ؊6Ty*bt~_ymI쒔kF�=^ ÿ_;7r_WWv> |??xb;e饤b**&Bu-1M5'LkuB!;dy2S@4m!%2.2Et 8;7涔/˱*:kDMv%o'k OV/hG}i]GQ~,ks_?Cߘ}";OA'n{ךkRQumq փY׈n+JJ>B@S1&!ԁJPHbdv%yiAm<4kN!hPRT5j:n ycL]ybHwx"hǒѡn5:M�W.u:~ 0p>=⶧˒Pm:5]fKe5QcF�=^ ÿ_;7r_WWv> |?6)2sIX.Aw)hyDʗ! *pVgSy_SR^KʨjfhZ` F Ʒ-v<G)![Eb^(S{JaB5>gQMBT+B$%BI#Qkj3Jm ԀN!`s[.;n(@ Yl2lW*Na@UApƻ|:WC]?>갌Ư] U'<=ZڲorPqZShQ JI,@<-;2yU_MiN!dClT|Cc<DJ4oӰuaOD )Ƙ*<ʊyOQ,)RPhy&vNZR HZ%@*RT :뻔^y]ݑS6u.ٽ(s䵣C :pاh*$]3ִpT$)ysԉsF-N;m٘nZł=! <Yl}v^2!^q2`Jf[+ qkQ:绫"۔[㛢Vw|6$S&&Uur +VwK޷nk{TVwK޶lUox1 -Ψ?$ÄWj1m?||zb0M|aݺ_N�&Zu|չ8\7C.E\6_ZVu|!_Fw17̷:rP)/L)@mG\zW3ϸqMB7%inK gX)hĖPI$8JP PAD|lʨ9QIfsH ]k<v;z }5�Hri2 |vGi#y*$HV`-|gO6Ml/GQx@%<\<z�V{ M<vm^Mj< =%nJQMU>J:$W?!洶9l^ d-$j |IVBIJx`5 Wi^ 1<L?=,i$4<6/!.kTXRE%@ D[|eJL6&]*Q\q#fYMGhv1U EwS_ҿ4 <,̐WzFVGCjJHסʴeH?=R  廿L\;17 Io,mńn$WD!)A j5ܷ7wȶo|K ǖb%m8oVd!IRB(T j�{.+Wu�W�oZ˯_c=o<<K#ؘ\VaHiwІU%9U$,k #ܣϑ=':y&[پo$wܐXqk'6uS_ h_ؽRQ,+MvL&ҡqNtS |g697$$kSq&բE5Ԅ8UU7bq|<bbXY"^bk:\tN:{B?;J߲:v}g{[궜$g#Y6ӄ\|ޯ'akg'ma:w"' ^7”ҒU[@]{im�}vޯ'hZh yFڎByMm'~mU@O}++*~N>-3xt`% 6$$8.dc%j(S@RH=P\SQ&ǐ[).) 8d ZRTEREjEzam P ȤG;bSn*(."8 Ò\*|db>$DXJe%K =k:w1N<)4<ǖߕ N q[ŒsV绫"ہ~xwx_^+J�vܣϑ='"C1#*Kf<f]Ԇe\Q(BT-ث_xޓ}+f(:(Q� Ka1\w,AJ![ږQRu$o7w<Vq'۹7WkI*e) ʴl͸VEyJ+˶liѕo7*JI*IM;-sݗ#XәRi.hO$RT Em+~5nw'6 oxŷ~bY'w!v>g\'5 oxŷ~b?A^1ge #X>h}OiՇۿ0EvNE\R/}VoXףuA,Ʌ(q)-2I5_x.4 4Df(RsX_q\\5KB T5B#Qkuwra[_q1 1.3d:nURl:o|nsu*J/;60H|x{2-劉wn䮯m?||zSwYؙtkqJa9�$n/ Y*X~u9y-GT+ll n_fgo[�[ua�}VX$P.k]:q,i̲ ÔRJsW/|e'VN+Y/58JA֭v!2XNU^ЏFzGQ~,ksW=Vlj>IlUoSP=>gs=H ZuFԞ25tcb(hvD�*N_eA#0׎VuFԎ25tc-QBIqҼ枮_3xtL"$iLC ͼSo4NZneʽ0Y7Vg™BMJjגzPjmf6ڻْtt ĔӍJel)c4a56 *OA:ЛO+@cIpa.� аBn͹RN[nw� ";7+zj1UjDvr%r<;]Tsݬ.DRI ըŚ U({2-劉wn䮯m?||zLS;vap@3!ȴZ< 5apFt(eS]Y˛XV ]߰\d[7 lo&b#UCoۍbq%4po5w!v%&$֍BX((IFKR<,:N:rB!F`@|HC+o15(y褝` f&9OTi/! (Rq'q=>gs=XB j~GczҮQ-c�_Ͳ"oնpHO?ޖq9xcd:skJGH^OY#eN@H&^L?=7'�N8j $T.뒵uwz <Rs۹ם.bSMlxނF[]!Mf].Lt*gbxDeXf4vRC, JĔ9 \x7F _jF29K~3ǃkI7'^}WOƗԝÑˮ#Ҳ2hߗ׉.w%~ݓBTm4PIk=B2k7Q\UT$[nqqP{o[}bH{D}R/W]>-=I[na6Lҟ-d}w#︧^ut�*q)JͮwBnwۋ:TL42:GV+n㎸Vֵa[Kw^:-چܫBs+}Aa_�ğ�d$! BsQ \Y*QI56JMRhGߒy~S5o8bur,f=#>}D8lǿ$=TS՚./TIy $/PXTujFEkqDqxS)QKxޏ]l ˷oM UEk#'�Sd Ҥ g^־*�걋ME&}K2-j?}޵ PQ# Ũ\qE|yuS|aD6($, yȬJpvWߧߏ4uRvSssYM%,!y բt4궍"s׌(eQ  ?aJ) E3 f9� m%,We8j3`~!HaL?Qʞy:}c{'f_sgSJŒK&GН)ǶcME(+o� M ZM'�Q5ПEުDtZU1Q$ jMMuz鶞+>ΈBR|^PSUe94"8km N%E+2u+sjR6:sg2+Ezץ՗`%4^jkГѡ,Uzomt!6l\^m\@}<h4JuNtkXg@bx$S7ۨXlMWj'ߋޙ"W.uZ;<>;:iyNC-i PQב<]<~uMke4#;!kTSQ "QVS�П<օykQJmJQNn)켵䲙mH+aD쒭ӯ^KY$^z,KYU#Pi.Et>C,)FE25qiZy@Uk:V3Ny9OG%qPr-ŕq@kD2T*rl&dGG?=-:R'^Uqj歚iN(i{z- 947ѳ޼ZUu\~g�g?i�ܰi&:י*M-gIJVT#<~lŘj<|uk3%S5hy<#@Ab juj+=:JC;g9,,ԅ'8Tu$j> Q֤//xZykuG; :Ҋuk==6ה>NiNzxvm#fɗnFZ@g#y�5OefJ\qYm'dõ -a`WM~vX d ibv.E|a]d %hVfհųڬtʶ(o;AJ)_ ABEh @ ]";&/~^G[-,(,63v)N_Dv\b⮭e5�*=vNLW`VylCͯ>?,SZnO~5Gi+RԔj**?GVΑeUJO4+h� Qȕ6Jz4!!Ž>MZ@g#y�5OeԋPz}6[kGd=,D!$kGέIկ �xGg~d_WM"  biYNEslMF']XӨ%r<}iJS &}ᆵk7ߣ Kǡ>CýU6#90LN^mȭ+2K5T*Q*i[1ۛ:GڣwQgj*͸ZXX_}]5Qk>g[jʳW}\K#vۓ�*ֳR@NWNͧ0>C|ܚ8�MwL~M=*V)"=#Et6s{iK1ͼtճ8·D}:K�&ЩW.COޕ:ޯ^ q6#7�Ju$ٓE $hevVs=�9M�I}YyB!SXiȸ*ҾM9(I!6kY\WW~8h?E#5ПE�m&m:|a4XދхOOB>ŎfN[(q,iF.PQ~E{Y\W՗%N]x_}-SJwߣoZTMRh<To/Q"<D׷P{fv'ҫMɫ}Ch{],ExYRUo~" P@kۨzz{ZomOc*3v; .̺ZUiPE9G<[+-Jв#ⴾŎ X@ e5.}GOH62%{SϷX-MBhs_&�e>-jr&eohc_ uzv8-J~jvs_*ɭ5*DQ59# A;O@"*I==̣AB6V6(q eU)!CPYOT qxutVԊ&MIVY@(gԓPMA8}-BMJSEj"Ⳏ!L4xI:!eR#UD'6@}mT2%"}Vӌ%PPj5m˰VzYtJIPXͤU> z+8!:Ml\QmT~zRcbiQWd)Ig^sJQ>-)84G8]#O!)x-G>PJԱB}BӈK/$)C(u襒 19ʋK=GtVt%IôSV<$ViLrjq+л2A&dQPR+I 㦪Wt 'RF}@(kl4F^c]9@HS,UMMxXWd9H,J(C޼a!>�c6<$:"bq3mJS}.2),"ʪ*xqﭗP|'9pOmѾ4+RAJ,J-(ZO[8zk(?K4&Pz)dv$E-.:TQA6z{Rr+|FYΥ (xzz,X,-U:5UBң^hΥ Fy5slƥhβ$R^Olۍ) PyO)f )J'jh^@y^. ׆T6<\VVx+-(BhjjIڣyq) 'X<{=M6Yx T'/yymԶT,P6V`9Ma4GRk8(zFBR)Z5kNcƅ:@UDK>@S<|QKeA} <>jEl SONx万J�:O(rKQ ЍuW-9}CXk)T#BQtj&/%bXG"*Nm*5kt4SV9}eKeѝe+BHC x )[VmQ8&2C_Y Q]ߦoT^UHJ8zDo]xyJ],B#_(uS`FFƜ4m:�uZq eE(pE]4%l))K S28y}:짛KjmRӣτoL3sύy˛j.btH.Y2)i;=4Q w;wzsT m�$)kR�BTiUjHQ KF!GcZ^r'{uzP6έ I@`\xİ~27x8]KjuDey3�TQTŷ'nג^“w*S1Q ȣy8u \ JM5~͈qφ^Ro,PYY==Ǒlqk*a Ѳɘ%+!Lݐܒ6x:^ RBmJqj!(m qe(JB- I JT$j!Bv"5>zuʨe/6( qu-4K&Q6>mwb;n{@b<G }u”"@mӛ3Ԝm oDMq%8\q:iQPTPY'7 qM3޲'l8bm48zkji\,ՄTc_ۤ͋GpBL˙PP.J^zNv2z7 F縰d\oy@XjTh6g&: '}vk ]\l N+SJvcm9!dZq<#E͌nx ȄRZ4OƐ4dk}0HRJ\in2^1w 7ۼA%WE3$ FKug 6[ imp^xk^/\yܷS&,RRLu6pgS.dK-A+ɫɘZbAT8y؊%-O6-,f:jl Kb?n7K8bujcRN4[(6ݒ[s<�I^2˭ :\efx4["ћۜa=%[mOV KiRޗ:Rm)ReJ 'Az�|Wxh󃿪[W|˽'hx\hP%.r R$%qmN,hևZq(qRJ$nE6>n'2Qq86ɒ ҥʚ.!1t9f"Ls쪙vM6Ԕ6>ey -([]6h7G,"ׄvwz0#CmHCe*m-v7zqn X^}ഷvXV<vBV&$ VDp%/s {Лz* NJ`Nգi奰TU*GrJ1L7}b)w[㥰1ɥS l:BgH9Apc_ۤ͋GpBL˙PP.J^zNv2z7 F"+1k/7F˞RuT)үh?ˆqb\X㹢zu N5#OIIs^ 1-12w"?52P [nh۬u  1*4 A&\i1`oAKw2KK)KIVk\t0ZuW8kEw]f1_L6c6T^:&CI.? *Ȍ^>&Я0{ijKCd4!wid1%>LeV0 :ЀV ICLtb{%.nW 2КjrB#TT*7D wDsܘo|!y,C buO%@O nnxͦb,֟ 2n27EYKewυ[cm 3>l0`La qgRx' fbU<7JNJSPWe^\KdjۭĤ&qӉSn%.%IŬ'vc0,:3bl?? l-*@y>)jF°i+d! $A." XKM n<bZNx~lT9e}BwoۘSuoT¿i�>| xL\WPBnyO:2aQ� @SvM>M\]u3rA" sI)q6Cl/}KZ*RO";ۣI7L<Fn(y2I1AIsjE8_Z,Q1qe=wEvdYZfؒĉ\dk[.ssM}�Ed'!}>‚%%رqRZm#C*ԒT[)4j6n04].53%Yams8x!ٗSrq&+$+3js\%$#\]wʐ^h!oq3-Ύ{+` I~d 5IBiEJBT m(4% xۺ w n+"L PˌMdKHnK!Eb{qQ vA9$IP^ Za%M<PxJm0skQngxRTRbKM&)ׂt:[nGtiF gbT*nA4՝q&>u3db\IǺ[6c.%̛Է:!y y\y[quYyc)"蕂+9BٻeMݐ\Xγh_jhmym72 ʻUw Z:<7y~(8R)ʸeȍ;'!n oSb⺅JuϽymKXO 5ًɲbv3q0@}KqLXU2!p@cYQ�\GSKB u%Zbu<T8)jQ3/ =m~^ ]em78bB4(CT)tTTmf<r eo+iMdHC<H*C-B,% +Y.<:1fnԥ_75.<MB40ć%JgHE,6tؘxyۗ'{D˜hJ%Rfq]f!նqV n!nU x-uBÍIT8pRl%noq }pw\Wc-(ե!++"w;7仙ŨBr n;wV`Fj<($1Be֦R8)KEH I b_']ݔ|9rύ9!z͙k n_`-JUvE7(˖:YҸѢA66^E':uݲ7<*z]mȆY-6mה�T(\Mũ1)Uzt 8򍋒ŧ\C- m-v% J< |[\:ʶ+__. Y-upd~;yўgޕҕ44{4J6 ׼^Qd>Jj4h}n8!*#q[q\#vA- W5=Y n[XiS Yp-* )E#rI E{8bI+>2TsR}!m=\{� X ~{ CkWH}MMxC)HRېH@ YE]N]&xb +qmp$7u SDdžxy8]B3[s,{`]<Y{:qn、9/`hGYS(u XswNɹwT|BwlËEQxSTu4a)"!ĠDܧ ]w{b-nۊFS Rn#n>#4td8Gfu%Ö8ae+(~\#R_v2t%L8= &�j[8 N8'Ru]gH$! 3ܖyHzuJkm߇⹥ȳ)[iO^\f&+K ;) fbEaQ㶖@ʄS&ۦgo?a"IkCB1?b-jeLTZc|Nn2V7 fPn%Yyt ;%- :K %m3p[ KwOxtJVp#]Cj$EJhAq];Mn6RF�o3&VWȝ4Ĥ16�P k܎ƗV</Fo<4Ŕ)yrTwim5'cط7vf\73/"^hΙ)Ԇ˱ 3'FKip%{iYlØ.�gƕT@yRMVpv-ʋS�>4L%o:Can Te�$<+^?qy U&6vQRKL>JirpPe}#p7/9x*Sn擝ǠiK/%)JSm.*K![)a߄N �)bjڝjq@SD;_7ܦf} b}FC e3B�gX&Iu2+]%t7ew N׆<vͼ0~,KT&yb `!êiǔqݱMz6zr^ɼJ!$ey8)S̡yCI{ur\^n1mNe ip,8ϸG.Եn OERtoT8[o3͜.[}I,)N5lZw#\IqNS7<~*D?fzy]!)Ӛ(#)9HODeUqʛ Q <�>n庅xhjPoŻSKw)-g]kmbx+tl?KMr3xi?)1̥ A 4Pd H})a˦NoCFr�Xݑ堳&KZۊ rC^΍0bp&%-ͫoGۮVU=36ij#KKGCJT]mYmIQKhJQhIxZh-]HʗJ\JNԥt~/P&v>e?cݗrg@mЬדiJy ld:-2Hؕ9LBz:eJ 6mY+BVP\<eVmƜS+S.4eHyp'q A )P S {]i{ ^zIFq.:]Y C`g+mh$ WU|bXWsdIqm7# V�a-+ &i�ueA AJ+Pm @RUGgN)ZVfԴ%Jm_IA)W:hz�B DlBBBu%HBGBS@,Zh-]HʗJ\JNԥt}(qo%󝛩m2q/Sn2 M2+HCir_~-VCMV[m.+3 JJճ2�TUORl']|:0,Y̭ho2U {N_pswۭ=�;6Gw?{Mnpswۭ=�;6Gw?{Mnpswۭ=�;6Gw?{Mnpswۭ=�;6Gw?{Mnpswۭ=�;6Gw?{Mnpswۭ=�;6Gw?{Mnpswۭ=�;6Gw?{Mnpswۭ=�;6Gw?{Mnpswۭ=�;6Gw?{Mnpswۭ=�;6Gw?{Mnpswۭ=�;6Gw?{Mnpswۭ=�;6Gw?{Mnpswۭ=�;6Gw?{Mnpswۭ=�;6Gw?{Mnpswۭ=�;6Gw?{Mnpswۭ=�;6Gw?{Mnpswۭ=�;6Gw?{Mnpswۭ=�;6Gw?{Mnpswۭ=�;6Gw?{Mnpswۭ=�;6Gw?{Mnpswۭ=�;6Gw?{Mnpswۭ=�;6Gw?{Mnpswۭ=�;6Gw?{Mnpswۭ=�;6Gw?{Mnpswۭ=�;6Gw?{Mnpswۭ=�;6Gw?{Mnpswۭ=�;6Gw?{Mnpswۭ=�;6Gw?{Mnpswۭ=�;6Gw?{Mnpswۭ=�;6Gw?{Mnpswۭ=�;6Gw?{Mnpswۭ=�;6Gw?{M�0'`m/៬_U?X<o~y}V `[|3gm/៬_U?X<o~y}V `[|3gm/៬_U?X<o~y}V `[|3gm/៬_U?X<o~y}V `[|3gm/៬_U?X<o~y}V `Q J'`?[l4n)<[GA7oo5Aff~P8["CDY Gqڝ?d7t\1.n̔x(OhkqA#pXKr1<{RedLz yݸk\.k%fͩΧJQRqNG[C;PX#ks|~%e_O}"+E}a;:RZmP@Sh|ܟޗs]XѠ&hQWWY =W}6Tq,dqTVT,*:C?Yi(R~iO]>Uގ3ll +}<l{GcSZD}Bf9xT#͆AM8N*t |f(kl`o˳Q#id jv x?ݶG_gSZQR 5:%'9Q�kڒ'>�o}5�g(kl`^|[G.أvO?a'x! )ʹZhmjղa1tk.5"J2e6Ԕf3NHl0Jy奶HڥjJR97hbܛgH9$ )Vijg{ shJ*BXO w3$^Xu>sf(t5!u6x� b)~<}+-O,b@�:u'`:^V`#!Ro R.3IM5]IuzҝEjbkN'q�PMocW8闯Ns'ӫ`Rh�=ާVry(6}Hc‘O ZvaNB-;'D4T[YQZղPxOUhEBI4Ns{RA5鰘r`W)M8Q.8@$4Q|YkRhRu#UyE]G^ d-) I&rS;2''SonI'ޙ!\~CeӭJ[ʐ<&+ W${-sҔ8Iճ]cfUf⻤ޗ[+QWiqTJ6lkola)zE-[_Ʉ$SNT`SBkSn!AHZRk JAZfv} oC0".Z8((~{\Oj:fkN'q�P8?U vG#+)<k]{<BבSQU?8Ҿ+lEVW>=cԞRur4yȱGwU*c5Dl6˯6@ڎ^~gq[7"vy7QQ9wRyQݛ.Eün *9i*!(%<Rj6Jq mSJ:TwjʻGx/ʊﳊ=t9c0QؐBTB|Kg˦M*>0"R!P!*6� pf?m6fP61&Ѥ/M*~/Zߥt- Du�I<r ŷ0j֗ GZB*Tl(sN{OAv}zGhk.#i#N6!#Du2+P~?ٵO\qVmjPBJ@xmYUzï3~Tf_k@>+-_3fzGʼb)ԔDEMU"UJFIDZk䢼Z `[L~I F\XA^36sZ[)o+A9TS@qնG}bUV?|Sat NO쑻{}'wҥͼ ;H m-, aM!+k:d .Kjj^jsucF1Da29aΨJ@R@?wYٿٷYٿٷYٿٷYٿٴ30w 3C3Ȼi[h%#D$ŭ]9D ~}8U5j5v)K rJμ^NfuWPRMhQъ/KIHuRbŠ?,SF]7K(|δi_X*ءҥGn4X-yA 4i*ZֵP%)H$QJ^ה76�uW]ڔӗA9Qw.XLTS\_)TO9!jI[)N(sԉ.T ˃&D)M+3Rb<wP iHZOA KRj>1^ U86�|rShң͎ĸ7&,wP[O4BkI!IRH ¸Rג`] JhRܮ#5[Z68KVl-­ ƊwKyB\A̘K@* ~ksn,ה9Jx6ۉRېxU} *8K_6ݝF4RP|Y RR;9CoG6t)$ ܩ2-d\m>y!]I&"M(]Zl5CX)A#] g* ZC4Hy\)kaUP-֖͡q!m&RUy�GOmI:F-?C'L+3-igm\Oe6m}'YiZ9Fch@P#iR=Sat NO쑻{}'wRUa]�ɓ"TJ;%]iˠ_79�M>M!K=-)@#bK{ scU,ŭVVZO)>-p엜Q VdZsRy c ?; [W>dk] *8%)lŝ .W\Ėa6~rרa˚mIaQ#Ncd)+J\A̕)$l)P#oiꐦ /-]+Q2N>wȃ*[KmKqjB )H%J$�--4Q]<6#崪IFZ-l[!??r&ɗ.|woV3,)7RO=f; &_ŲT$ؗHRJ) BR-'*ФiRTRN(m`:z'&aTG]ޒOk)!җ�;GaOi eKYʐI m)kJ__�fzRU KlFv mY#wn7 [GQB9e'jMdPDkv,[yEFF\ZYO!n)7fy..7UǼZm,riP!CX+ a;|lt+2*JեتI<Ъ۵kZkN'qx"_H9pm-"CvK+y啭GlH{ ȿ]*XѣҪQAWv'a;2~/m䟗q\7?k^* $NQN6M*2ng[kf֤+) MRhhPA5TJwPO5䢹GO"Cfirz-4Ui\x'mEO"FtI!$TM~>>Qhfe(x+Q6y!gz�>�w$4m-\{@YBEwQ٦$~j'A]Вz`Q+ҟ#vO?y% R$IJI J2 S@Yíڻ M~^cbLy he4{I8w40K%mftHQiqW[8k�+ۭq5�w爰kDfJuk@ڂrTSVn$E8{ۡI+%x-0�IنU!XOdl~z(AF@Kl6z j<5Qu1އ hNe^i{yzfmD*G2zm{~I{<L')$іS,$s%Б-mq>L%r"0LΖa^�~ZK0_hP^nQ[u_ǫ\^쨥Phnfl+BOM$ܵ0eƠGSv;.JKnCȣE6x 7?qU)+)e-5\ ͝5ؠSv_p܋_ߺ7ms~S6 JPu<W8i?P:j p}c6P%ZB:EG@%,q)zƝ<Gmp޷"\�V˥y?D1XuegɳsՉQ O7@G˼T/)Ѡ(jW gMl.Q0Z;G =oBv(RJQB(h}!|-+^+v�⩛zV^A^1OߊӕR8g<koWym?y@ ZwmGxJd?d3n#N1BFOb)'U 'ש> rJs;a"{T *:~J ?e_KeC-~Q֣ȔD�TZgpGRG/�Z6L[%Ε|).-5xe]ymREv*t-vVK(•?-( @S 6_noܞ(>^-i}S6̉%U|R}ۮ+͎5!A+Apk%R"H/v^8҇ߎ -9؅9xm\ةH^@n C,9 BMUAnˌcL@xm$mCNM][z]R#ۘX[v_p܋_ߺ7ms~S6&ZoB|'!XFlK%_sfgV@(9p޷#�;Lb際hver+*Kh6 $Pa5QyIuhRih?)&ߐ6fB_ޑʞ%Դ~+&;(9k˴֞ -6*=ih P`̟ #GT>>ʿ{YZMzCǬln!\@GK5|jzmH*RR#^ ThDu(IJ�AhZDkYԥ(AͫZ|(ݽO A5nY[Wb.}]%Sb6PkN4!ᶀ9!hCSn%+BP(U*J5lwzm*qvj*(FulHemE.4ۈPڕ`)'\~2 :G}F@Z5MP{;J~>J䢡C%u8QDb%#^ud%m$@-1Bf!x'05&#O*+P 6KC0X* F A-oƯx &B[<#BT<]\q~-]qV %Ğ N8MxZIZ d% m%F<uÆFN_ (mpSt;)mHx]w-\p$)T6�Qm̱ݘ)SPa'[$p V^ko%tKjm5C* Jث \mT[k*�u/dJzD5%5Y඄KQʐIպV5{b),FXVnpEMr}ķ v$wq ûd&UN#BV%GXgtlKи1ExєH �|$,RV BRT5%BP<Ef%b<Yd1z4IXi-K %(MIʭII?^xSE7eּ)iʜ;ZdO8P(jSv_p܋_ߺ7ms~S62Sͧ3շGع˱$qΙriYӾ+kLP{. P쁥h���u/YR%*ʅc7<Mk4X7;Ɂ"y) !C@JLnqoz}뫦C#VO/Jwi$RI>2OqYr^Zr�AjiSNPRis$I$Ԟ䨃#e/�l?2/̿�(Ɇە'_OLހ=9fNI:@24yE%@I5 m7Q13l~P'"=_N*DmTuP.4S(y?"B2q;|N=QP_+(E8|=p,5|k\ ;eҹECƃ<8Yl696W^r칲 yTSI:&#t1"sq|洝ECQDTT4u9MIKEAB1m&喦LE8ůZukװcɑ?yCVxhi3Q-+ig)( Z5DFUR"%HR!uJS<a*u0f[{o+-𡴱>A쎽cfpmK8"JT*#(Ѩ+Yf]$g͈e) Ba@$A /`p#:ݲP%ݯ.+pE˴դ|n◞J-￶MN`5[qV$gb펑a\JLV m25wFzd鎥cN:4JPk47ܹ0Tۘ {<)15w241ZǾPnMټ"1og/:c?;42@$ HDPG Ơ̇̄C*qWcC+Qڣe5:΄4 ݛ8/%o^7SP̗[eMc:#6ۊC9#Ǐ EeљHmB[i B@Zv_p܋_ߺ7ms~S6ZRTD[nYJ[i+Q,9BCd8kOdEfu.-[9j(ZO1/"̠wPfR moxIPTt(q+/J@<KZԢTJ'iRM==rz,$TQ$~SI=(]c3)e '~0;=诿1^W̧}AdWX诤&#netU,Gb�oH5kmV{b }_0RhLI&A*𬤤^ߝVD㣑Θ�N0[`WSb(uM++a& IWE>G͋cJ<jHT-݉~Rc0?+@ʝyM&2- xI̡/%£Le/4%$HPRH"ז%J])#hbUR̗ UI糘Ohnӌ<iYyb frlPw+YmshP7 ~A;OO=�A @ uwCޖm};䤂A=$WFu_.n7nI[-IE7/4J9`[I.iMGӤy)[a߂zq M$S*48Cs\uNFIɪDt!L`fPS JR$IP#ZH#l+Uv<GWfBfBFR8[sj8jM%--^kTy4x m1MwEhݷ9++)2#n.m۳/OwBI�dMe XM�8遇Ė̅’ڪBsgSO(Ha-- \Vә*qZ)Jr{J̤fH*- 8O2+I|`tY_4:y RI6pvWfLz|5KiJ )J@JR5�9[UYu[WSidp)O- 9J=*c@6j�]ۏ8AqJZMwXƄMɢB,�ҡv 7?=."[ ZԤ֠Wzĭުl+U)@$Z¾P%UmЯ4ڳ6[ٲ6H`ӛ:ӷLФ1b Ih4>?fJ*;vl+S UwK%Ob+@JTE$65m1n$R/)PQ1|*%kJGR'zDz Q$ Nd)>iRhi ED7S%M)DY̤SضKm‘ŤB8ֽwew4*^ws <H.{uEDXlJuj;\ugՙj$PP[t\^(q&ƻٯ2$(WUe(EJ$DDI$v?<,l^zǣ5lVτSE 9I2~a4;"y_ k#]c3.EYyAHV!@CQݲq3+$όڞٝƑ9}hx^ ~RD'-/a)+~]1f 8Vmi.fB)CWm_IN,%ui}h/SݼY*%C[bIJvJ%D}#jOKn+BdzĘnOIWLOz)'ʘW$ JSؤ'�t$eVka/,_U 2M=Zm=9Ƿ�j�o�  �!1"AQ#25Baq$36@Rbrv 0`tu%4CPScpsDTd&'EUVe�.��?�?;%^ Xgtmj{J8G9_ $ww|UIEfzK{Gk(nQ-kz޴ fWޛ�z;n)6.c_H-c&c@dVwLFfIeiZp4ruMYe>f]gvݻIog olݰۈQΫ254�c1ש`ÕjtXhi5t` y'Lk}+Ugnȳcv6٬B("5u DKNtk13bd"Nr81%01dq&gV?kc~`~8|0ΛЃYS|2ncn[RfԲ` &0`BaBPQ>*Klٲׯ]�Msㅩ)PD"H`F&gYΊǽd֤C&ֵZkXDLLG?2?<3 �SۖSY~`uKYE* EZFcs^'sZ_u3ɡe\WX67V{Hb` #zfݓG�jK^Դ mkvI8vm.GU M(η.}./۬WȬ" FWobHc31?{<_au3߽`Zv&g�K1ׇnj1VXP*8!)l1q}uBg,o�x2gs῱zܗ˿Ŝ]~,dxg�i&7HTF(|qѶ\яńB(pڣ%gL"`Q=BQ11^*>ݓOѫjv5p&?֬ڢuУ[;$6GɞO�hUfg\=+1b{!kԵ+JVQb\{63WtiZL>˃lNd(+ 4Ӵ+G(/62OLLxqrg#W"wXZK=K]V1G0pj{D\{#ZuiyEɏDh>n,s5!lP$dVB+EthɼM &ɧc2؟-q;_u?+NHm.U#ɼ,xXDLhc X(wj j*Yf�M8hQ=ye??SU),=! MsZ԰3a IΑ[%\`}ȃW!]E0<"b:QʨdOY͟A�W>.|ۜ{MR=vq^&AoM؅k 8~9[e^R:[fn]N rرdO hzlZAq&StGwk.˘UQRQtUijT&@B3}yԥr^ʛMy:O4GXc$q%$6샷 r ~e @A9 zns%XlcLgЊ,}={ݧtj<t畐'h=Ÿ/FY> &ͬmnC`c$6c)#bDf. LLLLN11iY\bY0 Iըv& %r& ƛ/ړ)E&Svi(2Xgb:k 6nZe079{$U+mSг5&L3 �!&;&<&'*UMX�}S>Rtm. n1=]#AHctі2IE*:<Ue,K#O\?{{B�<J i=i>Kq_�Ï�m_crWUTj+]ae; #Y?sGC}Geu z1?v%xKkxҙR]iYzqOTy̓C_D\#,+YurtQ_-})PG-K a-^fgfb6grk q b@w#n f`F"BL <M39s$ZS3=ȿ/vBWqsFr%1Xs^PC<;0 r[1MS2bĉdL $HfbbcirXjjs65d@Ű1H `&'^>9O~ȬX�!̦B5G`zZzמ~ij3�8%EqZ%5mh}5Srs1b?wN�SMeq \/jɖ!KοL֎w emMhǶMURω*,!b4-xŌ/p\ʖ1`ުF@W@3i@.@U�JKd-pͷ ?ӈV-6}KeĄoi쒛up+xZKXhSȞ[9r>\S�nq^-JcCu#%< şXTlC"fAj !e PqΧZ`h##܍uk}6wkQn{ @HH*bjHVavX_}y-45 .]S UERDoZT*-,L0I6s>u ,!>pr%,b3Ps.5b(!Q]lE6*euq�#JxFK`LG %iZH]"B<d|]EYtlⲩj ~6Hi5 (]N ,b Ŕye9{jI1$ U9@=Lb$G�wŌPv%+(.ֈЊ&#lw[I�Ǽ?ړ�yn'�'1+ٮv7:HkF&umgsG4|*�T,}®\cpk. mD}MQDE4'ֱah5TOR }y;VQc.vTߎv:+އV}>,o�x2gs῱zܗ˿Ŝ]_ W Kΰ6 eiÝ_NG)i$8X.ѡk@LDƚGc�}`�{I =V^`!^ u63r \j3D:Ʊ>nKXVrrW8?/M֭X.KG';庽+BSbXϰ$MJ]u߬t:�\}�+HSd0^Z9,mUnkj:]wl$R>2G61u81cii1u3lX?|0NAwv~&QK�^G'aA#^Hƥ QF=3�%k,$ PLLaHHGbtKkձ9; XZ3,=EkI"SׯP1i0켅md1�;{c~�)x� g{=eC&֘a&fP 1LDq/M*VG| �y17el\xT %0 Gz{KQm|k-ѬIhC>3Ci<Yb7"] {:O1na2zq|1>,o�x2gs῱zܗ˿Ŝ]^, #,g,z^K e#<}ig3K�7c9_l@l۱`B|D\d ݨi:q__�r_ ֳY_}$9tl+$5l6V;`cS>s?4q3�i�M/#E~νV5n;73_ɬ~Xu~jkHuY[/Tk莣X�_8/Ӻ=kl&&}% bl-W2UX-$n,cLB;͛GƓ11>1><, ,KtWaI<ձy= ڕۧo};+p?~?B0s:~YVX:ćm=ckE ?Ml]*fRklGfϋL�ǏL�ǏL�LJ&fAink+յe3ԏOM]H}J򝾙S'娉|SLQ?$cVL|qE8?,L|>&N3>v"#fqv_/ a+N.�ݍbƣ'BzW` xلbŌ5>'dFHKm8fV>dG^ܭn? {Cʺ2h_iLKU~%~,a#saX/[6[ 'e_$d6|Нg/سnR~=J}1c<|8NYZW29[T01(Lz1x~k&56DF560V1 $QcN[!^ihkhI-"őAO-dNm6g@ 4lgM^Ɯg Ӵ]%Ktoݣs�P0"8uk 4خ!h1NQH1l ّA{O=.ۤ >U\ÁӨÌ�9oIܖT lYAJ"b}<&Mʧ0~֋ AϵOV!jeYfOI3 /�oDe^ٷcb@|}R>U>ۦq03xz,i3{O�hUfg\=2eQXu]A lRUbѹ�܁ak^+dzXi_*@_/L|O./u_<*ǜ^\1g!\ױ55Ǐł�0darV]2L6uվB6UmY ^-7e{v31vj}}k4Y)W{)Zm}yu.�7,M1ޙ^}W|u]J0p$1ɶν!Dn-H"&J&|fNW%o,L{zFQ�uj-ȁ!mt6ƞs xF[5ۻa;,ԲWdglǶ! MTby( j@Ɉ�Uٻ985V}eLJh.:~KP* v[7ؽ5*LMs﫩RH*5 Fuz6%pŌ/q9/ne5 j7,HڑFPسm(pW~JĦ yT_ PE{3G]u5lCS^N*ZJ‹9϶MY�xQ:eFlhjn_Zb!>qڻh"[qjaA$asL_ %ܘ+VyFִD DՑ^RE<r1c {-dtH&c]'BO+Ӹm[)l̳cnOC;"V3 ߵ kZqQ0ΔIc'ʣ �/vEi#E*_Jckmic}$e2E?ϭ۱;5YCivX[Wߤw<qh\]& CEU24` y'-驻:5bU~\uj\8YxS&3Brд`cZ0�DGlͦف9gh@Ǡ@D4bܢjX`|bQ{v+<IND w G#'c~nM̕ugk1( q dଡ଼Ff,5-խ9.`~\kMU&TP?laMzƼRWMv6gtK! 0F 82㦩R-4ő6>K_3 k'qɆZDjg2E:DDF>LTUqG>ŵQMIF ۧUkZV][\ӍAkpV9;hF'_+^ 2cDlaFdZL̑L3>,^;= uTӦa-֮RW~-faxLw+wiVRzqna Hn/GcS>t;W,d=ȃ Xwd#H O0W.8ܯ%( lpΡ1B|}u b\)2[Tk`Lj$33 sˬ;s˶ ~}WE7O@r &}�xܛg#`S}6JMAV`D@Y\{9d(E{V>a=MgH�㱬zݭPZEseD LQ&1'8Rɯ{ JSa k€ZIs�1$E1<M\G*P%5U| ƢR[ J;v1>11@NSUꏹhdSiv>猾9M I&IUm5 &H D"ND�dF;[GH:26Ҫ;`iD8S1Otp78Nv0JˑVuz}V1"](X0#5W'c1uoWE̖mbe5TJJgx6F$U*YdËh@Y>Pws8c\Xj:Z:]zPw.cS�ع_+[7VՋHfuu5sQm)Oxl~xc$ylPiZV,z!-dYG{-|=%Γ$J0g?WX3ԉZ JZΨm)S 6ۮw:)6%v-}U? Ic"/F>Qrnk8Ayݻþ{|g ]uD�TJ]V.41cƺr+6U1m")2bl9g#ܟld&ëW]5<'r]%cx/_QkP;]�븧cc*C {bl ?3uV1lbDRdsa3@϶Gw^ uYyFm)Jty]5>|Y!+2sgdxN+;\Tk).;#ת$:Ƅ S5A3}Pg"Td=d7/OӘ։> z~w;'u{qQp4dyֽ4 K Aщ0gdyce+ F?}po#m*虅$LƱik<e5ձxyEt.WzQnٗg�yZ{1_ոO>S,v|ܭvk`3,|ҰR&OeEZv<UN@N*zRL$AOMMR"}˿2F=''lIoS,ضk=5̭S,]B _Vp+4bi}y:+u1YgN5 9d%њm?czosiCk#(˲EmOk'YY]VbTأAEJ̰[EqX".Z,2_ѵ\-g`: hHc?gfBw.-dlfMnW ,MEXR sE}U̮yr41o,Jկ,-) av"w\)a!um%t2/*+jXԻ Sa)mOVS#0T*]:9]L/deu΢�ΊeZȮMӨI=)f1Cl^q5PͬA&UdDU"FF #NGg'ot,,{F+.XJc!UKQw01 FhNbl<}THsY4Ņr -^R$'JԢmB;}NvsxZ1?ih\o G?S!fscWbutYk azN:gpR)b~R}%V-:KO 6&GVUu63".K䞚C  ;SWWĀ  ߽=&.5P%^;3˂tDM Mm!h[ Qd,)LמcZd1֮ʼnWﭕXF➕#I61^c]0k5qvD=*)#azLdjVەyaq}` J2'pKOltP05 s@c#QV:=jM$nU <T6Uln{# Qt{DV֩OLk{ժw. ;BR(M'AڵD3vi(FG!JBֺ;)pA5L-@5Vev.PH_XL۪E2%)z:ꃙ7HbrY3Ղ$鱴XJgEKֹ*u:\yz2VnpJ匘wZʁ o1TVc#uDBdX2"4Z_d+خڻ?#k]Oc]=DbJolxFEW+n;RWP0X@О=OC.k?3c2э%=Y&jgO ;SH!<c5/+a[v6kDH!f:UbFe+]g`Ky{ǞY+:y HKY"U0"é&Cg~Uʢct?]CZ‰L5DJ'zX3NT®O2d0XdXg:n)]Bs!jTygas2osR۫ݹr ^ >r s(^5шs$FMI єjH+5 "셚<eE'57VE)z|+|c7d5lEU1i# Ǟs> JnLvSm\sB]] 7N]]3V) cJ>a@�E1k9xx[Ji@羹K4 8זM X<Fb_mkXUAD*5أ$$'eNa.] �^Dɪl{+N>Jwx>WXeyvq; t}YTVRΗd*bdd2#ɞN~�1"U<+OinjRuz=ݿv>nq*^T%N"}+L@F;G;5g¹W HMeϔd3Ub�݅_X, ]cXw v� Nׅb9י3c$lw\X:(:Η52B <,(fRFW2 /)m{�RC}r1*w{|ۥwmE˶xF4v̦EѸjTl2gږFL̀3z2_o[2d#:4Ahkݯl�\\hVLm|nC{T]ĶH9l6 >�fD]bkXJ1-uSPX-l(r9N_5:LP.S7C'5=_> 5WEv_УV4飶e2.RGa>Բ5fl� }Jٖ P6'qѢ F X{` jê{jl`VR΋4 \ܳ& ,> dM%TJZa \J̜Ĉ=|;o_{K [)iYk7)>93}^IX򄨞Yl[%v$eٻlq5S C VRI9wZ隹sά2A]NħFck!c1N2+]sQ8Uȫ ٮ b:N^4z]Y2\bDw-xV=^7m:{?qCtl%tQd1FOUՉk ,zKcZnd~d||r!˻spf⢷fh1E\d%+5$uKY-cJeҪXnsBe5gM#^=8U{UԑeLCF >%Dpαw|qR@ڽe+.<X-?91ǩVxluȭt&l)ULg025lC[h`UL#y v=ӰHM]vحd@b* h =W.rWrvj:l6jv/S \y1"�ƞ_֩Wnϔj_ =H{1&|v G_G={=M+s{sq^ m`ⱝV[޶{f'J[fffx8d6З Q�&f`Ҳ/F;[7]Sx>,4nґ>i?~5~|�ysSv�Qi.`*U>Hv [&ZN3Cdɶ%\Iօq%vI5:YJOYO]fw"̲_rQ# aU10ۦvțTKi/^S$ 019ү+(z҈IvItx{V鯶s/}3p%E|BD ytk3eb;~}g}b߀_m}Z}γ]�%}fUfdTxUW,*[<L]">fEH-.#bQo dvگIbPCx|"??㊘0}&EYS M`Ϙ&;?kZda6TQmPSكd }f}Uvy_"=>AuȁGg�S0g_.>k| dq| 5Xk,(Il6վ}+-{YbLk%7㕹{ۖl),x{%1 ė\b!az"v4$с1;9vS1>>ߏ_d0ߍ4v}&m޹%fbc"|zdPS9p.fu5"Ejz8/to! L:^WkP?=OC.kkl&s9W%B[m/E2ڱK.$16RJy)=u<FdZ&+"V�G{7-PJ"xM Rrz\r㬁O^H:D7Lm(4-׈f璭1Hgbcq;RR<t!?�*"6"#U{ FSOtjQETs˂OxqS?Ա�">H:c'nݔV;H=I1isO,[LZ;FۆkaN2#fg)PFb2ڋ^`8aecvoR?<fQv*l,%K-J (#f]*PӢTs]|etn-T*|5575m N2p5cy\}b4J̷K*ftZH�X@rlN _ڇ23\loHD]^3-XVM7c-;WSf64PG\,.>_vZ ^DWx%-7q|=,.OCʫgM@mwzIR҃!zxy˴-SeUGc!ߡhʪ=>rY`%r2\5bzH10?+x sy!eU^6;=$MiA[Z<<)Ȳ*caڍдeUo/+NwS�G�ÏU!.^|.z;3xu9vB`"M&' AN1;aKbΤ5jV<&gm8{7(eXEdD7G7-H#v5:qڊmK풓Ykp&ܳ�`Ug/y*#;VMAV9h ,γV`(j׌(wҴ4ô(;;{f;&s}PJخd UdWBH+Ε&=X*E+6Fe-zczX=;+jcmi�Aٜ݁P5„ׂJ\ڰuqܫL kf#uy! vf6A+lJ?!eG#8w! QJ'XDYz,kݤFW:BI8~vUd.ՈZ`"@Eb"#vq8kh%RkHT)]xZgEyӡݶgJ\w5 (, $SŬ nDz=!cڭW|m.ezOYlmK=^_LzTaVt˜9vk\Wf,]sT{"ܳb'Cǩ9L$+[Oe`k bFZtwk'rGl_[\Mf%J^@L"WbZ qڽC"M,IVB`5lXh&uriQ Lƺ }n-#þf}33.eo%XPj]*kk2p{^6k SAJҒѪ1kϩ; 0rOd_qTlZ?jXՍzA%ȼC!ᚲS췕*\huewh{|uiBC#Wf5MVy H -_S <hcd:i&[]8$'UX U έQ?{&F[a1.6t dJ^'9g.³^btX[ cٯ\N,ZŐu`{dI%3#,^ґt8u'[q!Ubj7֒fղD=kBsYqCz~^3߾(�n#M?nZ ,M,"[�(]guh<l-ZIP:rcs~J'sm*FTmR(hu XjB-Q�S] +5̶a*b-Ms#k VΰRS'vᆭ9췭uk)n]/d=U4մ]^{4Z߹pAz_ǀLTU)CqVľ? _̜UʞC?i Z]^Tħsd\4:ֱh*Ǫ%lښ8{LG]Y>Pz}8\ )%SFvX׀s#�Sn^ɤE}Qj3�b mߺڭiosfCHV+X\IJ+e<<ȩ-ho`@eU56o(p ܆b)<C'RsE$Q$I%-``U9Une0ō{6tؚf w\T,&1xE=UW s6(8sPS9OX҆W(Ej_WQza%5U )6VDGAuXXg}d9S-k9cEXj] @z=ƶ9c+iG';Op2[B:Se #0 >,Z6Yv%Y&ބP6ۆc>.co^iᩞdHWHBR6Br*TusڿC^ 97V LWM{!y?*d:^`m_/AS+&wޫ=ϐͼl+لګK`4CES[Yd0';Ulmb2tN2Ԋ< ^I@Mu) G3wFf29ɒa,=i:Y0 䇭nDIǐMclb<Y"W"jFڈs"=:Lİ-͸ ;漶cccV6+XzhЛuh-&#:Y<Gsev7]dD4�FܿSSxbp1eX,L�0nroiBкGg׏TK!ٵ4qu{-*+6}9~tQZn>z3b{[Ve2ZG+8jB64zW]񴻵?f8]y 받J Nk-<'rɗ圡VBЦ5 uD4PM+Q6`]eI U{.jj]&5[}z`m=Fʕ%V]#V/+<{ݕtNj&OG 8 \9/>7-OC4ϩ5Z?ɂ>m xs 0%"ӰU!Qbm׃`<qϯN.b3T ɖj Ikzj{ecڮC@Nkm V;j-ekWE2)Tܟ6rȲkٜu\$*`VԽ&"=; %ar^jYqu5Y5-UV"X`EeQ]v"gb-]yt`eLRL*Z׮/.PuXgT-vqW3Yu[qo&b�-íBܹ춯;bԤrugd, b r𲵵*�` ")Łj<f]g9H_V %\vҬB} !=[9  i'{{=0ƄNOLyn\"g; .E#XZ܍%[6Nꅐ._WeJ]do'#l•YPjq͔y1E2SFDzZұ`"7C5w/s=%ȚD(a,&䛶b9 98XصMcMy8-Se@0A94&`<vzeSկN<Rʹ;V3rta[q.]$rZ-weSm5tWVR[%sH)!8J C' ڹkvwP+WR:ןQv&ɫvix*ԬNe1ւ[rؾ`--qƶ* {$r m"#;+Ӱ㎴6MU{-jȆHD ɣ~u3ujJN1.udAq`@gHMBs 0%"ӰU!Qbm׃`<W̧(g$*VIoo-?C̍-jnL#'F^9^hr. >YWfuKNPIkl(+!eYf?^K+ʴQ?iڻ$^^ݻxW'G�C엾mo}`MOL/#gC@TE5ͶUG/t&&n!yr=B;^ R LQhӣj7ﱡAOꉃ;6S;MU]ڌ@ZfOgoNXg`ͤ]`3"k:2sUĞ2<gВܱnr%f k井Ɉ.lGW-zj&iV$+ =$:+r+0VY"Ǖ0$S13OX)=Q-t:dk[=v"ۮǯ\mI^iRn+�@06�yZǛ_t6a'cDݕhh4j+I 2-O%B6$`I#~D"1%1HHDLLwLLy' z YQT0A3)MU$V*Ҳ<6F܅R%ɉN/S/MkꒁYLlґVĸt11tZP{kܚb8 U#Olsw۹B"X#H-[JՆ|J]+v٠)ZhWL\C3 ;|q4^- b) biw{t pvE]+ мܶÔ gt&"bCE쓥 f](2(z�(I@Υk /c.PINՂ�!1VYB3::z!c"H 5VgH+o3fJ#�[ { xqwrZ {N;Ub|ZG'`R7܄IL@"Fq ԽLgx>߳wlohnt]WMѰ5M'(DD ȘLPb1iOmȺk뵅hL8JQ3vD$c:G+]kPE 0f.<p{Y4YZ9M5LFݧ${f A`d0G3òunbc,˘Ñ%.;i`|ًؙx׭i,H誑K!#q$#]5mԃ*\KSrMkft=QpM./E:)TմX`7˱E+k1 ߴ{(v 7uJ ׻]38Z;׎u3OM,C,MJo ։QLO 81 1f&8b[g(ev]cDԭAà*7H[ƪ-9 h1;k,\= 3FY#';x{)eXflRh"&/ f^`x g 0S@B<2)=<(^Pb=G)t2/+eD:wu뇒C2&LN,Y&EX)cVnkkI3ҮFtd ^]:eo)La�c |JxOW:Ev vcMmf!6\:Ě,9[qG&&<!LffFbk|Ү̾ؐ6|:@ڭNu!͒9̾> hQ<5߈ˌ*v%hgi`5qu ^|]ʆG9n27fnY{.n z#}y<'$eĜz: G٤tt Diʶ]~)k *2"|<GTpuBՍ5#1LRߤX`3ikoatM[Z,g+!Gqw� @ Z1:0bt8-{JuNA oe@ {xcuDXZˍT% Y�DvyqUYw/o$8!!s2�;τȮ J{vfiř5 3 (T#:F0 vYf[{tg l1_=/vZ1#0k*iY*_kLf";Χk#Q`2^%Xdž8c\G-dTTI% ?(z 1Xta`fk,Zq$ .hyx-K4@j*ݢ!?\fk%rW? �t63rV3YCWdN &+rqs<x<黴dݺ Jm6@1(B_^:5Yl{۝=cYtƚi*xZ"ma؆Ijzm vDPGؿq +CJZ#%kɏ,Ȟ<s6�Z/ynKTa+@򧸀J<9tΦ) !%CbDX:j2 K$+!&86XiH ΰj b? u))KeB:).|fB/l{f3:ϭܮC.~[V!7'2"2v}Gi%{Ҙ0ֹʺ7D툕^KW`l6_ty{u߬뮼Sp loPyv /Y*~ք5#(Ig_DiR�-q_ rYO~H'ӌa1\LZZH{CjkgVn|_oݭ*=)+2Q&;c:2g~f#"grX<f Ef%LtIzDi0l\4i5̌mx<}\uIΑ^%||.\ `P.PCeA0[ S}ɵD&t6 Z,TV;Vi)+0;ӝ@}-vw&�&5hӒzd25G`;Yesu43d}cpWH3k:1%Rc(ەln׸ar۪shmL<Gr=aG.vK ä�97\/pW윶ŖaxRf_<d5UY.&͋ZW!bΓ l{IcgJ`#;cjHŹ9$@Gał3$ޠ)=Ie!Ӫ ^LmӴ8QMsXQ^>7.Y{n9K w{+Ujqv>8DkqTW ъelaZ2W M ut6Aj="OdL"w dx]⚪}IgC @m*Ag><M Jl)gTdY%dymݤƺLk,mx6KvZ`عi5k9Y7DQhA ;3: {FM=9X1J6)3{<k=E<3C(;xe;q U5gn צ7ύ�o�ܶh'UMii:OtYl%!&"j�4e* >Ț-ڵUG]&`KnJ5T�XpZG<OO YDZ,+וo xz(`~wqD,[joU1يg ڛKt-#ɞ9?J"Q!%OŠZU<؋|[z[щ9E1MYKEe~/ѳ#q1${cږOxZWkY{a{“yƳC1?LR)Ć3g#\jlݽ׶d\ݘJH4|!rgӎ Ӳ'N!Ҙ=e6 `-C3ciw{&b@`,Ek$k�-#R"))%cۦeJ3sV"Md9"Ռ|yFFoD0QDLz O}mcc&<cHHJff~Y$7ɝdu> }h 3rS"? G70e%?> h133=|QѼDk�ܯ0Zk�Zf~/�qyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy?�~w�@ߣ�~�?�~w�@ߣ;VYbvҡaX?_'et^>�}"�x+E�WG�E쮏]/?_/et^>�}"�x+E�WG�E쮏]/?_/et^>�}"�x+E�WG�E쮏]/?_/et^>�}"�x+E�WG�E쮏]/?_/et^>�}"�x+E�_*Z-낮 0%<"|J?iߣ3S$30BQ\>ي^Վ1P2h%%݇]n=mnBYm?LTXCҧ-)�~*sEUk} c<u>y6ymyyca�Wwgw'iuP?2<"gG/Ula e*i%]7J?10Џ[zgce#�F3mS[2ЏLS%;Zo9SլecJaRbZR+`״[%5,V"~QGq ~'?{Sj'nӪwW~�Yg3z.:ċPc&LblDn~G#j7Oz4YMNtϏ ;~g7U<nCΗqEڮ)$񄷉ȣ)v1b0% 4E ˑ,w#nctT1&D2zn醻c샘.982蕀2|`ZndIXQHN]C©tJ fGB9u7s0i} )DiS13\_ &jŮ,Y]iK=6(gp2:s|5fjTKQ-s`Z;� IN"1?d8ϡqA_Ìd8ϡpew!g(HQ]f{E1pө]C4cz@<n.X];ZJ:2˴x}3ȺKz(Z>yL59(| Dbfg[|шQDN_YǜxUY*#t�lj u;j?'˴#Vz< �\ 6^<u\g%iPCbfzw 3]ҵoP| �Џ_C~w5WzbcFT ?NRcX%5($%,t]z EjJT:Ƃ XDDi1'VUgm*,͆Ȝ!Qm21 ]e/e3hK8`ϢxK=W֫ńFGM>NaCѝ <PcfiވqL\VZW&ը'R(V"dD{=gp+dŪu6L2@bwt'I̤LȧR")Ԉ{fgY*YR'XVНb{FcBQ"CLLyzɯf6!/[~.5-5}wN3L¶4XM6K|�a=N#ŊvR} %\)O6TY0D ǩ+-;7w#4gI!{yӊ=7Z[|71bEŬqjng!ZM#3_MX&VVZXҝ<c"")""fg3=?r]e�,ß�fx}ÙU^'٦|z| q_ARhǘc�({82FT1ghIN|0DDHg 'b}^{Н&,k*}e G휇Wx䟴G\6ߨŬ5X.G<[\+asC lC=c]!o8LY-<N1Uk`ǘ~2YyRVC7v񂊀ZO[ FU[DɖV�9[RcSSIa/X}4Y&L|#X<qʖvfjb?l֝4dqv-L�izSMa`}kT<eL1,kq$]S%3|0tqC__o?vw}rF̯)b?Zso2zfcTĴ<.8rR>`)q q |Ì=;��:tDz|-fBs?뽾S𙉞?3װ`31[Α3Įw, Rs*kςJ+d<|\{㻿Wx^[eʮMsKDcx>an؆Axَcm*]Ng~٫e^c a=|+8JyOF=%3FWCq5[bcE^X^fDbDt(4 FBR;7X*\{t⥐gK`'mY\`�i 5 Mgb�871^F{CNLj mXrHX z FHJ˔'Jmմ{A4CEdDJNKMa6,[{qͯ%XgGe_�@r�3윱�j #9?@Ʋ-z~DqV@](7rC:k띋NUt*711Z1Fg0#d8_?7d8_?7d8_?7d8_?7^ke_֜E?$zǍff<=|☘Ўb&"fVg3&~ԿIT~�Gǯ�6N?XBٓgu:;'Yp7NV؏<#3(Ԋ{"<#=%Gt#?�>HȌĊff~Y <fbcQjR=?/`$J;b|&=bx"kV :[pO9B88dR7WNk"= T]PÙu30CgLl>eI#og#C+�B<auO[̴7f}Ol:F~ylo>L Er\~{h!)bcb|p!) )DAC'zG-G=̪v :ƢC?~?fc }>qV$#>nnS . $HfDcI鉉b� ?P`T$M#s"Qw)t ~v3�)3uf.?ӎ_Hlz+ƳgGT1;m,*?=[l-z\$O0ģI]2LK'oq~]"GU9NXͭ rbx^gfo"�yՏ%Ë7c;j#?C"|<Փ{,=8ɍaΤfsOfxO2Q;;umUM/ 5)xO)0?w peai21H8`ԚOUrģn\LADq /_%cYzOcѯ r mb< ☝xrb;Ɠ?sG(4_ԣXHގrgb^�t;?Lugi=N9Ŭi'__6^ҁR@e>!�.OgRNU|13ݟY]8^?yLi}i tϘGJtԦ8ܓZ7iQ�ueĀ4_IY �)4/Ohf CPƺp?6�2_ xogTJ�8#[bI9Ozc#2Ni⫔EwBq6cg\w~Papӟ"pb8{K%v3rgz{|f Æfan:Q17.7tm+):wǶydzݔhɏ,O[Ho+u0Ԕ�A[ 38Wc6~N}DJ]nf$ c^С(^d堵VjگR_i¬jo삅I E#vdii0?Hu+cmd;ذ/ͬ|33q 0EO H)Իm/'�?YZS:h1k">o&;9s]֦=#V�>� ?PW��yld݇]l>E/zO '8͝xd�v"m^60QmrdE3Bc|?j M_q�i.,c1"վl1bjd4}=a.&z�JMgY@v^Á<S@(tSqg v[GS3g~1YXPHGKM?odj[I@οa~Rⳣ4'<]·rUR45C[`!*d9+j_}:ƾkĵI+5/Do? MC+�B<auOxΑjѹB1(8Ʀ媸{r c"g{NJt""";4)3 "ݽkՉWc?e�CG#Hg.p5Tq=ʮi��DG3~qQT='Y[ 8-\e+?;~budu,GwWՐ~�&lE,<0J{QE];lwȽSv>uR/:?~m`zcL 62b FavSW)>PxwhbC!DS"1OtDDwDpbהf #F66',85s)kXMbfcu̶5![ [1s=ѺYK<w$? ҉bi>3x<mxG(ysĻ`d!r#|ؙ̔mH갥�Ra*e',~?fD7ۡ=DyEӉ>3TD}֞rWg>L:~[YQq#B"3GpǤ{<fxoa~ ?79Bjאuy'ӅckF:'+:-y*sCG_eX$>3c#}a%?&Տȟ1Ol>&>H8YP|Lq͞~?'̫P =l!.cH~"OǬzp"!e#Wx0 W?R OuZmi{Mc1h=6Q33H=<⮡�`w4EXtXoZcPM/aOP9Ȑ|Hi1?,qH&ljH#w֟I&gYS$t5| M %sk"P*t D8Lcm-LTy/$Æ+ ǡer)M}x~[:ӊ&~y%%l/ZʔEݙVXy r1jk/gQOm(7,^LL̈\q²V:,!y/^t"3=xҝuOj?}o<`s{+"SZӵL#~%11:LO'c8kH>)�ַ񹬿N)W"j]$ \yg-"##i戎rC(VŮgtr#HׂZУIDgO0Q?LLqb@:4,)(`Ԉf)!?vw}Oty:k_dMK:KO�9>!ݠĉHLΒ3Vt\1�ihzw�f\8 Qq=y2"E[1R#?Uq =U__.Gݯu"W@cN"ӵꋧw#*<{1P> 1*9gC#0Q?w hV7Q3ȁ#n,'MG_Y\h6o9ek(N'>M%(HB12E:DwqHUkG,C \Hf[p#K�5�<y78L�'3? =>Jб?< t]V|B�djOU=U!`df'bcb}ºsZf|$W]:͂dGk'Irz{ty:LC+�B<auOe!&[GI[> O}gI~~.Ylݡ֬~ӽS@"`tݤi1Dz"##֫@WeI3SNaAk2C k:1E\j8Ʊ ~wB2^LklyGP%3 'Akڣ%giRhac9SDx{u|Vs;Ɠ}J:e~Qdpv۳v;baMi^_h)3]dg`@c2 YāHLOþ 5QmʅZՕhœ #0f&G]_KZDHR71AIiżJ-ߺ} {^}#A@ � @b"=bn:{J6)Z}C!43d}'~a�}d?0�_I>`Q fr$%cIO|όخ\ppV�Z!^?+x^se޻>% a3C,2^>$mT؛Q?' k߷n͗m۸D&{}WR+X X(q؉-os`c@|Ϗ'OmuƸ̕}))gU7e*X'}>=d񂮨q<KY}l4㙞ZYbÊjPəcǁD1{OY#2?GA 8:@+{]!4Ǔ#<.>˪YTI|]#>q-Dbc])lAԏ[+U"i$gM| <z\6ULWhVg WPOIU3ι}G^ l BQǚc"3^RQ~>4w}_.X+͎:C&e:k3<{g}i-2^#w0cyKt# g=Ũ1111<sj2h6DŽE1M-<RoKk�+ h i,{JMiI|E2S?�X|m�1d?#P�$jh@bQi113 mU:"Ju쭟A๟v|(.f^J NI0 <C11ڱ-5b:}WjS<I4@q%h|}tʡYG6ͬaI {䈋Y)3{XR=/;=+p' ch[[\6Ϊ+8\epVucBsuQ>]g� ϋvo:XuuӧJ1,z.V)im�y 9̴}qpH}#?E,ץ*�U63=E#*;GbV ~9dK,URz[V}uʥ%>r{ Wѽ �Z/g]�hQƞÒcXI~94V_M+R? N6*@cF[oo6cf|/1[LLpmr)'PpOf V�: eH v.^LgN<&W>]<MO#DwDDwDDy=}Z=7Dy/_q~tx|'+m Y1EƝi;�וV>{О$�7>0@Q"BQOLOtx'52EQEH܎! 1v2@l~r~."4C^ܰś]?S8^ȿeQ[UNs߻?+`=e(6?'9Z!Oݣu؃7qC(9a]Qaށ~RYVk<v5.08'=<[*{-& ZKx+cAaՔ\.0ٌB`}N' {>Z0��="1DDxD~>U1yIyGEzȧM 133>"q0-4s*,]xa>"Oe#�WU}K1=U &&.OpO5cW#=uy�MT&ܩ ~@\ٱ�#)f~HՈK2#:קef~jK,A9TEZ2{PL|yS%<hv +ļ>c=lEs*|uRC}>G*1"Zk*vA\\6]VOjK?Fgl(chN'>f~RRF>2&c+V_2REnd~BDOYhF*.VO'c?'y?*vJFNd1kdS? `bc-W\& utW;O IeN\C?'b{;&8Ai:v|8(nYOwn2Jذ/2#yfg :O'h΢x 1DDGDxD|j~5eC%T"54M3$4aK^+J͌/�)jrEk?1y!.N,'MB?#b#;j_QR_.}ڇQT+Z�Gx`{Y:dOq�D wG,pkdk,Ox3,rFnn1UmK2J?翉x|f<M?!l/Jx:g)Xl~Y!U^�Z9t1 ;Ɵ?ay1~wK+ xWV'I0%?|dL @ģBІc1=by-xK`6B[F7 �UޜYfN.G-/ ?d[r_s_ӟ}8c11ˈe*?(~wO!k1f')1Zȫi=$,d8t$kǹ 7XQJ} q oW2sg]�؏8[1wbSlx}Tmg'?;�SXpdņ*h\Ȥ֬<uk<}�d}J²muRm*V)5b6ϧ1IVn>Muet=+[K`""&YTiײD 5f%a�f'c+*g65rS+rNVR3"c#wtK'�*vPw[ <33숻cjٴIkÃ3څ6,C P #S8ob/rs4ְ$aLfHdgYxV7m^֝5u,$' x>cO�AܬX2ISQ̷3^޸L ˘#~!r]sF%ҌUwՌuݦ]hin\D% V٬3c-QiK& j)StL OT1\Q_V,I(\YALF:kzjsToHyӁRD6` %c&bg75וg|7Zo~kvS;}&#g[RA6Av _nzײ�'NJHrV]eNdUꦫt({Pz93!/ºkeqm p{Pi)Յ[sR΀crX[xߐ(ad;Ew.P']:uA Sa*!YRժ"}ш8gH53e-p8fUf=WİH&Lٕ)sمficc3P_RK~kJX%L0Qcֵl:@¶7 N!]1ک!:|"nrRMS+ar+3דUSl{\_BW:EPr+({+˔y**=nNAdwFA�X̃F帿@]}{6Įq>5l͋L$˥Jd1Ijrn-WAd5>-v-li֤:Qd:UV-Qiƣ==j~ڟ5�mcx̵dK jWr/QNl@A+n2,+'Zr2Uh>i>~/~1d�b7Si>kmY dԆ3]se'&KWo L);T?RyӚkrAagmX;eD;7d{ttc%: n[ZņP*( 7͛%qNK}Q8lqW9O-b§m6L0U6 }E?㯇�y r,D�p">(=8b92Wr7li;j4hLbU, ('P҉DϱwT7', 5r6UewX�)U>Rx<qLxꭒ[rLE zo!41bf2:Nl=K+UW>qsg?�˜r0iZ+NB;&ރh&VkVXf/1^K *er A,HN44CG\ '\Y02zݤk=z8A d0BC=%1<s~FD!DL||rLwF>HP#E^"^IJ#4&GI2*/Ub^xgFN*(Xzǵ<#Уf;ȗhTvr/"OHz_fg"#f}�M9ck t?VOԵb`dY 5LqSP} Lwa#@mKcbiKՠ-]憵4YUΪF)m,uhE]b;�{ک]ACnWTe ϪhEƫAK<#R~"o))ףV9f2UBA jZFվCT:4kO4KF)xضܭsl6*jiR:UJ!R;dt9`lsu˓ *k5iRIOA@l!Rg^Y;Pΰ$#K��@`�r^%>f[!RPt8"wAV[V+4RFO6r_UW?1U'4DYBߣHww6;%C SPm(-l5dHKnP6mpw'9HSpf2 i Ɍ1>Wbv+i=#`k^{v-'Qmb/QŲ_n14۱DLHF֘^;Ql6>Ved֣) @3OwY7`YZ+Ub�ʛSX+ZbaǑtSs-CRqj%qUܲbƶE &vM@}\"MXe:r~^$WZ!N*T(a3\!O@ 8IOl?-u#-/'O9a2! bhR;5Z, Esf�+1\$�؆s&X1eh! `eyڻ_]�fl$d\ӈХӾ#PqW^g}ֳ/pTv2+8 HO 1G+g+[ſe8E[�l~٤{HSe`W3M%ĥ=2~x`2m(g0둉r+ҹߵCGY/ Ub% e6ǎXGF50)M [1e.{D �`B^Ul K'G'IYD uQ5șn[ qr7=b #[lB*؍б䎤vu)V`Ɏ2,(g0Gk$Sٵ Lbg{!n<Ȅ}KiFMgZ1]JdQf[&<xs7OZ3N*Wwf5%aQҬcVX&$ �=�rh([HLdm_0r8܈sK)Ql{!}Dz~=NkJTY%=lhJIN:Oy{yn[;{l*ᰵ`OjYH$PE^uaem2^Bkr\�d(!̹C%V%\ufjo�Γȕ gW~J f�=X)~)۹!˟kf峰n{0@$T~AO ͯ7Wj"6ʇN8(It2Q(yMpr_a6qgl)CBw!/ܩo'yA^yrO;韣&BoF$hNV5Vrvkn 3G gN8nm~oMbU{QT=qBKђ@kRh5ְfmHEk61eV&|*mf( I.? Ig 6-aBW.6BNrpfjy,+mi/* ;hu_"^[DVnl#9/T^ܽwf*^ɬW ' k4W6Ifpr0<MNЙpLR 5+ƒwT?;}]f0ۦFaI2M c�Z]f~=O}MrHp\p\f fc[ٝuuƝަv�3+F1c ɶ'ҹrno]"OqW`ѥȠanEDcW%w"Ʈ]8Z -r$|O7c'㙱TYnP΄ j_wܬJkV*`(!S�lg3^fTX˶R`vH7ˈ@%gX.dѶ%jڐg^Hu"b{p,fSO֛EbXBꠦ sz$ !'eŒ.Wg|)RtK)6goTVlzڲEh{JC=>Qϒ130u(PRV?Y3|&W/:@ɳ49Zntt\Xh1 !NRs'eu. +qZ#*8םy,zc9�ֺnB3XlUe8v䫣ٌÍmgBo`MI6-Z;w?{B3F'`ne)2Aᴫm#IZ%+`+qvsSVhfPMWsCd1%<7qgr'`Q.U٠Y 'ucs囹o+ޮO02J[k{;Wup5Q>"k�a*JnsNtC&) IL#ªQn5أLžqکbZ+;V~_NM^\ز\ _'6�v<@T %ʶѝSe;c|m㛮kRZ cI6V&~>6I{Q$f<�|x Yk,ʽ<rު:=͛l;tG[gJ*eLu묜lXbjFg%ۍ.YYԚeg{M`yjw1<s%+'Tͫsǵ $Ge]+((2R13?S98Ɔ2/3^oq=RԽ/nT?;}Ķ7-50uܶ ƣ0QƱ11=]]zݯnvanü6ƝbeN͙-g7)=fDwDk:ټ:8뽪:ϭ{2F2|XAE]NE{w-Vbl,Ohu,`u@G- XbZ;F?RJfP*ϻT񌥘7+a8+UY}$ZJmmL.�X>^I]i,:"|"zYBXl;d<缙33±j5N`E:ΦXFßtSy)=/9MHXdc<< JGc- rzS$0S2p<ŏC%˪ٍI}v)4 6h=@-9{LC]N@Ry21x6m9B!D@h1'KRPQu$!BW�<&*RUWX% PF( @#F"8ͫ,yHV̑Hyt]+L:}]C,CԷ%KjZ5gMlYĉLwLq'KRPQu$!BW�<3,B%JG]bQ 0u(X QE*iVP 8lXi MLbd>g[=ۛ]>Bzk 9y]Xu;WvvMU;)A'c!P=Ldqu|m!)>"uc& es$a"#.{cRX|~) ]۶<}E>=Nruvħڶ]T/ `)/�sɕ7jkoM\MLgcyՊ4nͽ'"wӚ*9@PIZXh�Qʖ3%bf7XP6[l T`>2y75GKtSu|H-Wnjs@I Bܔe gٽ_+d_W+Eg$4M*=O9dS.m/\F Es}i6 Q sy3Kyk--YucӍs,�r/"H8H鿌|wФª%ҽLyڑ/qi%zf\ʼnί_]G>Ұ_\x3֞Fj_^f},\q<z2f?qع_-l5LٜAYHCԢX7EXm@>K9}fik+Oəo锴 HhKWy,Ď �bkgk6JA&FuM`Ln 9'KeSExU f`OUϬ =;  `T"+vZ6[6zk{1ېu f͑3nLr\eU+ysNWij^52TRQ+#jH_' V]uy~1FYV '2вΑ؇,xJj2?U]&']*7]DFyԋ^m2xibBN@VѢvHTOcC4XDVfQ=|<dlr&6c  $^i>-r܏`C ]nF;WG ȗχڡߣ@_USEٙ|,< BJ0KOȁ;Z䛔YN22A}E'�jD!u\eתddi*kZ"j�&㔿{?Kx rm`%&u"}'VF6Nͫ(bv?wY/\NTZȬ;.vE{tQ9\vHFGc ue(ɭ)&|v{(T9J+3 z)@U!+ATud|f=nT3J&IދU 4VA -UZDd8鶃 j^1\i)!و2<rJ*UI]ݪBkǪO&cUoYةQTŁIE%?r{u]XTzs(V+-n(dO|mT3yyK$ܳrՄqeYrvvƋNDXcD9rw/2oO;Uv.٣~5<c�OxOw7g_bv3}ZOtb|z93KxNr5=O?9߉j~ysw1I {JXh5~Ѐ.uk?Ig^RjM kl"[R"9ݕ7gO0"? g UڹebZ+MƪS\b_Ѷ*Y2Ůr>c NF TFOS2*gf'#7/Av"AX {#>aᶭMZ kh! XƤƹ+X x yzT^[Z)JYꜭSH!۬2wS;?P)2F1a0A7|vV% ~2S bd 2UUv9+}7uK~3%hwkZR1)3!({֩w \JXAəMsk) q97HNE^ଝuntĊH<链fuf+N7Etm&RįWYy3'թ4X`K;'RT4-"y(C֮l^cŪbj~zrbOjM>rl Wط>j%9:QEbN&KV~'-#c C]Z-AwL ! Lm:j҄iR2lks� �32ӊ&oۗ*oftc%'8-&'ec+^ջk:MNOu}rҏF;!V'Y%]Ly2ZA|xeRVزա b5cZBFQ1|?0arF~N'*Cz�g'#.U ܡszZLlO-AVB`t!)Fe#mgZsNt؉YQHܵNf1 \Rc-.c0B.y5Ӯ'e#gdyVRjsO'hбmm\mv!.| !MJZ]m*`Yݑ~azVʣP‹VYj#2Ơ]nTϴr\/ f^ZQaf�iA\z$uY] 31dT>žV%<)5l2"k+k@̙.ޚL [TR5{GgX!<dydmfr4ƇNkUk ]E5S ,:f v~rc"_!G[ئH}, ~̕xdLou٣f8X9Yo+-֕`֔S q OF++(e[!mo=ҊJ'Rl음kKC 6._=' &J$pDGj#)6uݻ5sxk1K& -9 ީbK 9 4fkwӻy؋: Yhi<62'8 tטlo%vNĸrMYƠŰdL <C3x >cfE%J&!&5p kMרrX?S b`q)*W5}+`<$y[y+dҐЩZA:ˏ<a2Pv:uc"+ *d~]Bt8cy,d=v61E^[_ 읧CMkT?;}9GjRp!0 $@t` ǟ>Y3W2ݏK;X).MvLT�sPͰkA[5[+a¹$WY iHc\ueMk 4Xy@٫Ql0& x -:S_d/n&ױ[ӣMH`εnZֲ}I6ƚ-S"g DH!*\Ǔ-]gϸWEv-O`J)"\V2K3"ݩt$=T˘۴<L^3=,Z- gF¨8[41$z]|ɪljEuhUF@pԠՌ2UdP5DW5VV+})0eQrGm&92ݰu TtRf�gtNþ)䅊e�\Rm3$-dMyҨDƱ11q~id_ B�ajN'83R;p+ڀ =C3Wg97Xǚ-TBnն" 9Tcn“"<Ō^~4!Zb mNJ8%5諙1uLm\Xl=Hx<k1[>gַ4wL5,-n'OY*̄d=any^4a"3akG2S iXB~ҡo܁dzVX*N]u)zb˩:rs9f28^} KC'Sr[KvCJ+#V zǨo $0N-w+c%\}AVjOWZ"(I>^شIqi<ש>bUb+#@Ix\g&m:k�LbFwf f$gtG|q=cy,-km4j,ٶG$kaZEy2vl&*Z,9RvJ9tEUƳ:wfwm ^�lӻn)IEzH]uo/td+L-L'hooܳs'ar?Tp|/q9kXxfv˶HIWby|yvIyqݖz02&Av]Q;w6eLY Vb҂\VrMGzIL>R�/էa}Rqآŭ0N)f*wuKwR(T4r/X<Sw\#eU fL <58%WDTT]"JItJe{ePt6??-p9E5)puZOrϹj |=&j͏Rr=DLr@2JJ}":MQeV Dʟ?cWћjrY%e3{K܆uŚ4~*O-'U{UʀH㦱3qsomor1Բ1vքB㬍tSj@+^|> {b!_c63fhlu`v!B Yhj=f1Rrgr8s.Wi⎞я/sP„u>M>ѽԢ8UNZe,eSeZ)B*QN�(ºpC$aEV1([jD$f'pW|r�ֿ-(Ss7WZ~<bY!-LiԐADs~;Cl;<&%:#"bVYUF3 ӌds-*ܓ!aeuWD{l[>N& v΅|tDo[nMvߧO>>9)}c*k\wD+lz>L[E}#n0l_Z̓zyg)wXIE:7Xz5H+vWg)XZ<&*SN떆nXj MH�m[:}m5ۯ~<tϿ-el].ܱu1([, 3!PjSn"IN3~wTXKY0= ~ Lj?ZLX~ �5/ѓ�9  a}Lqס\+%^D�6yR1IQˆz) F K5=�(&=�h"11"?x~Fa䗍F5+8gVʙ՝&vC-ӈk5W)X`PXiA2l,F1uT%~ZUo%Jjښ%Rꀆ::ůuӛRhޫTyrhСs ҫS%ƪ|%3^AuͻO77rl5[d.Y[靘Z$ i�}R7ɗu|c[ۘsQtdАg0[*ZǴ+Lveme'2B V)-ev qꉝIAg1QhFۢ=l$)ۈ:C�) ~Կ/Գ<̗Ϯja>x4G8UO2WU[ITکrU(oSY,Zg[=M9*]GG)9Jf 0[M*=e:o^jZ3>Oh]|۴p<w-U B*eيJ@h6<3ŨrݞKհx7]Q$:&]4.d{SW+]*�0s ^Mm4Ӵ29s][}0PVOy8Y\mjTjn2%:%XĶX>U{/xvovCsہhCtq3m*rQeD<VݪVxS@<޸�3a'}?jo�[<OSW_-hȆ5ykN˽8+6Yc*MM% hʦY1`LOp|\R{9^XjԍtPa[67VZc^>Þх5W\A!dCM(=U+b?(Pb̘1eq0D-|YBzGoWG+ݯH ԞiRk]0 GtDDy8?O5u�P2.@v̈o^7f쬻px~Mk:m`q8jrj/d"]^0Lf\a:8� lq[9Af''tո;k3rPy`s~ eT:R2!T 4[9F)mGQPB֝� mVVrM_AnC#XS+1h!QޣR'V[r[|Idv39\ lI"a5^-cY#͙�)>jK&lGCk~ڡ-& 0(ЄuТ{;ljQLꈥ^zo>e{H[wNK'FF4U]6x%kۨ|i<Y :}hkCS6(lC׃阉1W0؛xˍ:HSjJz )Zzk@4cqkFPFmB^6";'GSy3W_zogFԽn-p87eBP9{@:m , X&mXFKz * Y\QR.rZQg'!5{<tc1Qo^ޥZ~=jkUzzNLpttjdiN[j^񝺌ƓŜm~Zs)&fd53mR =1]x>i1#s 8o[TE6dAY1aSIMb Ob-Bn!�iD L1fLrBB^vv 6./-҉NBkFa,rլuXXڃ31ZTF.�t"tӿVږcJpkp:Nb8MZUjԴW]`!*%*\ ԥ@-`0 11k~�?�~w�@ߣ�~�?�~w�@�.����!1AQaq P`0@񐠀�6��?!� TQRN8l'L9<5y`B~ Sٶ5 ?*D589�i|y8ڠqykz-a"q@HZبϡcaa|{q E57(+BXI �h*B94 _}E9Sg�' <(tD,JTO<{g>>yi:FQsSs+aH8jS\Pt/o;F!:!Zp4l0Iz @,0>83MG�l3-4O+7>>VĚU ՘zz<$% l;Ap9A�B#dTփepZ98hdF3qklI׫X^$2"`l\Fp새X!]ű cqb ,OVohr /~/C7&48Safd7)ҙ Ճxw@ wfMe;; : U ʹeTyD9AGOhGbPsN4"K : cp~):}Y@)"@r4Jي"${a s|c}j1m8A&@iu]3|э7�uo W <).^r?r-/?Tۑ xp"𙺥z24}by)J#B,�ՌL"f�}-k˖e\6|cM�;gj�@!9ftT0C`23__1Oov7k�Xixf̄ #|hyHXX9bdUEt`Q,IUl0LX#P}`VRMgp/-}yb='me%X¬qhqOcu6 F<O4ѲC?=dU ?C6lhE�?֩ 8lTuƦը9G-n�hZEѽ ?1Y�Z�۲iQ^kz:�3.*vN&BA6epSǞzzY:"  -3l _y<Tg�-jDҠ]\:|(hbҤS $a_Vi 2;CVVo|zK//Y@Bhi4@F *q#A:?]!Sec~L( #\�`5#~~F3T];` 5tLQ^=t>LqKT<?AFg,ӓ jcV� Fp EO6h@�Gb:GcW_shx"![r ^s:>>mڏ?m g$:-zX|#^7Ce5߻p+!;Q@^vz�̠�vϩH-РT.hDM#LGANLu4zuS M ¡8?'n k@_‰{P2\};g'}5 F7X F-lXj+V+-m:9Ws45ZD C#S>>o +wԩ5Z붽uUj)5Yy1�W`8K̓4}BlЄ0`0G-h99 vbi Xg1/@B٪dL87vq_0/쏫h˸6Ex$٠IG&&?dk02b=iN9*`VV0& $OEB�1'iQ ֑݊,?F~b\Gdx{R  lEo!2K�TyZs=MųN+0Rz0‡_%c}Vh�c.QKIWSL6 ` QK`+J$RLEC8Ћ [)"Frt-j}4U}>ą^&w쐅q'!-q ɏE %)UOCJ瀄QAW*u�(ms]+S@_䏖èY븶ߴ?l^%Rw<ā3'o5>dq0�vo|H6FSyW|V!-v?RW؄( B{!bHP[&˞J!DRեe bgF=6aěaW+Ouv"t k7 qěaW+LL1+n r13WW, ϶QnymYDnSj`鳑U^rL9h_h)�#s/Y҈L}B0-zL<liG $ӤP^LZϠ2S!>w9J%�TaWi刨{<:'b>7-apa.S[,Cu=Ռ^ 7,;o65#+tHuC3!mqAX⠞ddgzg4IaEtG"rH{ `•{Qđ^j+FifŽ !f d w`^R @<7F�D5a!OE$13`2NR E"tZH>Okja#p+Nrarz�e VƃrH|@GTz@ S,24%_?mybtІdAlփ'g<s8,yUڎv-:fJaSDŽwWJ$ę0-4@!}`i]GuΎy2" q[30T]G{)~gU++4]NJ``3AmpO Y`) C8i,GllWҊA`@ 3|ve2uЮ:'d(K % Ne{zCJAEke֥KJL{p7vPኊ\X|%"^"Q'x]]J m(q֥>Aaf`HGb$oMUTUW=Q%l< !" 1AҌVR(gؿLB۸ҥҗ'ѭW4YYQb Qv®m>a]9Z Mͺk5<}e rki:QwƌM"Iҿ*DmS�༴=fVF-JFf|">Er Kh](3,k_\V&Os1'Rw@.ʞwNrljX[x e_a!I<o$*QWbt$F4;y% e0ZPj!.'FB..4� 0(�5 ]-h4|"eaqb;�-Vz ^pHBPl;@Bã@�A[1d酫q~='jbHZpbMۜx�GeH2@ AWwҸ߀$M;B $ Te~L.65I- !PYDxۅF8gݍ| L Mg+x372tvn_KP"OX,|nɬeyA*pacƌmcxcNIh'WĮ8 {WʹO@~S'UR!?1EhKu$KHfz"s@vEq3]tPҿb w5$}֚b걎: =tX4DEnѾt׊6ӧ /ûv%MC@os&]m�a4 s'"j7\XIAV+)w%[sy'69`*ɘ?WD2 NxQ@NǝF):2< C݂H J(3k/ݝ7jX̫ Mm3a`pԄ!2)i?QJ ! qG<i_X4$vP;;dpE�%"\_ A~(I~(I~QUa昴yPM"Nɘ nB!,_wp`|`rU{y^g'4L2+ǁ  q2J2DeG2_[ Wq"3HS;Zlwb(Dӡ煓{HLf�})gݤK C4'KF}+ro9@&JGRWi3䣹2pI,d<-ƱCM A/!3S뢎 "h@(K:y.ę@k1kpaRG%R.v.C/?<.l$lyt 7 In-HDfib![C ' Jؔc"RԸ,mRVqk?$d#W{F-ޛcގ/%oDt["%@yc6Z$jJ?+}{T)5Kʳ3k%@ \d7連G oit:ld=]FyB-^ďC4H+lQ<,<*ֲ@vB1P|TjCsdj.%H?$- injئnd«Qݛqw]4f]vDHe\3{ F>sh Z2B h�{F6&=+UqbDA\!d<K%`#P] Z@RqwyP! !ZP`(n⋪(wlW^A0P9;]V�))%>bV"JPxh?.B "o0uw]Px=~B@0qeiͣqZiqU kX(|B3hU<ğj,GҵO4|>G)0d{6Ci^H">` !:> 6VBqn eI�6&%42EN+h(jXN<RS@Fe)mnT_1#F`"v}{ r&(~$YDU#800ި@Sjr|GdGk?lҵO4|>G ڞl hӯaQ+Yӽ=$B0ӥ (sp_{5[Й,Od Ԡ 3\+$22g  MaTnna' /Ҿ3y  ?h;~ _ 0POrԖ Y'naHeG#Xv ,m?:DI96&s9kS\fߠĜp0X#aIG9&,BZ%5Hg54PQ(&=0 VPԓ[s}ۗݜSW kdz+ЕҀPiZ!Xv ,DS_ fBWڳ[g#{sS ude<P)C4]+*^E7z! PIH=F˲D�RXtpu)dN()\)2;%Va=_A rFށD]`R6`Xd�;D&UEk̓߯rY-Am=@C`MLKo(W B(yJql</͚NGǡPEI-=yWA_ 4C�9^88888888888888888888888888888888㈈ 3u<%(*իVZjիVZjիVZjիVZjիU<dnl?- `ůoߵ5)@ =LCp_&/p%Csml|V>p*{B@9Z777/8l>76M2S Mk9ٱ/Dk2?0@Jafm'HCOk٢ ƩG5+&-=IS%J۫kGV9"l$ * �Exe%Cί@Ш"x� (MU <I5xU IXD&LÓw\�#SW+> Q%j/pqA>XD9-5"S͗GY nn.xZ_^X<Y�)-o@8FeC%~ջhn5' Vjـ+) hHµA4_l@oRfs GA-.M0ZP8] n ,. 10hBUKaT9^p\ McW"SpDpKUb +|*xOKTL*&-}!US`"-А]O,-H"a0XlNm@sB"4VruxD q| בG\=@I9.י*()r%]pWo~wwO&�78ceʇ?nn1@1L|{N>�·u8?Tc(�9U"mGhvxƦ0< OtKNש̿*myIoZxǙpcv�a4< J; ڮ;IH][cFGV9{ؿF[�aL_;(l懋x`ȃ!fpD=?URw֮ F:Uv%�|x^Rw]{FbJC-pKCn p uȜ_tvJ#+)Jc s'R k/ {�xYo('ߤ�tn~.L5AODgrAp]~ʓ8 pScw嘴�=UW�'#;0Sc_D9EVB5U$E-p`O Zr CKPXuz />a<ioѽ[7kj&_|6=U�0jǂScy.gb&<ݛy$j^y>2mcpU&ױ p]Õ P7&+_B۵ H]DnQt�+yw >v0r= vIkPʼ59_TD�ul5VȐQ"'"12E !N"d-rCwMGw;Z �hw#v)N0Nvz:Ul6\$~>#Aԋ;Wcʵ]E 57,,Ry� [,]4x\k2"89Yݒ(W?th,"xרМ`[ jejJ 5|uj 6jx|cT4q )v@rLtΰ\rEۻ}DG;X>l<!Œ|+I[&5eZhtG�׸A|<}���׏JݗFy#Kv8y4<qw=�`c,gݲpzjڰua]6 ,>aEwS(4ѳcV>ϓ,"E7>suRO0vkGVʾB#І|pCq䯒M5i�yPIҘ튉;^B�cVUdl|D�{c3B\S:>Ċt15H rƟ.G -9A<�*2njۜ-" M^-@/"~KקΪq xmv]yf{i}`=(‡-Щ$c $[Kϰd`4pS8:ϳAqLYObgDNu /?z~G)q+ýbI<:؞߫< 웫sCNmdx1F'"Q=ĸlJ) bYH>4aM0| 2m۹ DDDDO>(\/6l LUZ.ۦ|@'5j@d&NlΞV-{x/ bX� ՇR\E34<>@"�P%9Q1Vrl"N&4$:� yصUonn\h+ZpysS5 n+r>l8qQ& adV;rz-Wf bh~/>vbd{z7Z/g%' iy8۠F�kh]ZT �UWb> jt;<[�"O ^oC[m]>Z7U_e@^Ϩ/'Q' Χ@v 4 "\Pl lao `!-ovʬ| 8 �0<\aZp|Jv<Ho^9Y=|  he\{|zM}Vz袽SoC>l=<gC**I+ݝ0Nk6{@ȰQtM J(TU]Uڻ_^5kWAKd#YӏS#O,8y3hJ%uڶĦ*ay^3uh<(!�y/Xu7`}aRdpQ}-|��L9' YcETpOW%j-H4QLUkyr=9_OŽh7h/8UܹD8?iv0"H3Z8u|M�Th,5ڈvO}�NqBH�C�뀺#Ux�vꁷ !vgD5+ې4EӬ? ҪN+D;FF*T*jیYݗfGOԉQli$.Ry<rd5t=\{3w x2''9LЃY@B#wG4& Xlc5w":� ;;g_;:W<L E?`{q?5.TWrAr0kަt �v'dxJ&wGAG �� F!\̞b W\vsxr7ҏټ!�S J4ȉ;l;DS22,]�j6[[;i|^&X?8<%�Gԥ Nb0X; ͞pb2� CyQQ] ^4@[%q�'_\.�0G?%*xWѣ#nqqQ.ۡt4V!x|NP*8 J[ ͿG~9<iŷI}HA#T6G±@TUrc H&o :b3mB?ֆ$_W: Cʈf8+@5 ^ `)o }#BH9{LPDpp0k`A%Z e.!tZ<) `A ��ǩ3 ' և!U[N/5r<1\O(b_n4�PȬvׇsĀA, Cؔ"F߈>I }-׿8dI$� qjk]'x##=bG0O}h@ILvp.:;zg掛L2 M{;+WВ(�#L~{��� �pA�ͤ∑ʈ29&CYMCtl6ab%iX~nXϒw $n9"܇,>$*$$KRPI, ]|^Z@e^1Aކ‘YB .d+cL 1D(DBP C\m@$VlW>#oWPU j9# W sx|;U1ڥ!]SPH9 ۺz&M 5,&dHUV@XbH0pt!0N@~'ߓ?c{T}DsGkA98vyv?UA$|3M2 ZF�$: j+`=BjhO]3H0=nsbsq-]h_Nd2t%vQ $} ]2_YWGPXő1Fkzl!̅FSpxPU{&Du-yQٚa4t| \č|QJYs`^Ysn[Hf}.(d*`G`*C6t;B$d.fkqt.IU­)%5APfVp5\Gvy� w_@ ::jYSNq4e>Y0%  +Њ{M JLl"NXpd!6�:<h/E#rƵ Ɇ6e{<2n/[.>ylDg{G)Sg4)̵րq d=.N?�yFIQļ*&%�W,L*Z$GS Vv`:T;ayɬAPNr YLCXltWp;݃jZW|lc-? .+P6 ! v "M;PO1& c(lR l烶.Zֲ/ޒrò|�)$t FGHح>6 xeJp^"TEzvvf嚱ti/l%-i# 4He͐0eYXv޴ȉL� [Ibbe/Y?ῤ8v�R͂i&pSXZ VaCI{<G9K}ǚV�HظA�.tljWj̞ 4p,H.1m/g$l$3i7=QӬ4ۖ$*Bp-./ׄ�xL4-ևa|=�Ԙѳh%=<ī\kp%IyAΎY=Z c}ZoBk0U@g^msOsUܠ?BiۢA,O9I\'n#5n;av tL$FW<뮨]*xH&K|.YR7fSb6~')\"_W:UP3/64qN].8䇖y`nCE‰ ǂ3B(eA.\D[eiܑ9d D NK cH2 gе3zF* |~gjԻ=¼p~ /89sw>?~Ŕ@H$rNZKaI'\x-3l6;2X5#0θ3u}hc%*ǀgpm0fY<2lfG>T4 T|Q.osv=�BT@mV`v~4d+̞c&`a%a[ⴢ%Ud-{v n:ȡ#-S⼮C.MQA,HHeC'TgP3OP^ .G:@)#,Dec Thxl+4L@牴%Iment#MPS}W)-R"'X;\Β><uE5!R\2 4Ԍ~1 L  teMG�qe &y(QS< o[(?Cb:mQz/ O` ЙU�~Yk+DLk8u]Ξ@DuL7Z$9Aʪp"SRsG%%d%8 ׁMrid 1xqHԒhy(9QZ>P\> Eq ρLsXxMw &,0ʸ8; ^6ҽR$jXa2 Nya&.gߜ4�>&ng,�4bi2uVjwKWu_|t>Q{R`ty nՇ��Y# `9׮~-�~]pgB$P/ Cz=QďShܲ :l#j 3$c)smcnv=#?~ĕt�ggBUlyQ(IɷqY6=eσܓa!4{mM@ǬMBJ*>%<Gٹߘjt@(rSܢkOo3:bDc}X zbQm $#Dae(1*[4"J$ q 1y}m X zUA8zc /pDxy,%C-+o5."XT᪵'6- +>`q"PXYTlp PVZ@�BW8CNӇx��R,O_<A!5 h9ł+R>&q"1 Xr=cEpeMqlz7(> v  om}[L%z"R +l%H,1c!`b@b`iX٘XC>b$pőmPZ"X� f83Ei"&n'�CZ`�{RC@YqGg;)O@. g|a ^Jߦʼnr TTP7Z$��!l4<vS]5'Ϣw!AdUG첼{{*D{76Q;UWE3<!AN�~iPQT3BO:XcrP*RFdXwс���@4qXM[k 'H#϶ Le| E(@BӔ6v/~%u)T4T(܌SSb mU����h: /\Uaݏfvy>܆$weTk9,`q( CBŰA�Q�� ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������� �����������������������������(H������������������������������������;h0�������������������������������������_h0U �����������������������������Cn ˤ|+-V����������������������������RQ@0�QI�����������������������������.h1{ M�����������������������������5+h0>ـ| �a<%�����������������������������8|h19g7W�����������������������������t�q(q! ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������@���������?���� ��� ࣀX������������X�y䠆B@ m=Xy!������������Xʫ�h՟$x\6ObTc0�������������2itHAsVu\w-%H8/0(����������������������������������������������������������������������@���(���� ���������������������������������'4AA]����������������������������������"9Y@��������������������������������VTzK .a����������������������������������$Є�"B �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Tmmmmp���������������������������������0������������������������ @����������������������������������������������������������������Uj�������������ET��������������������� :s@-������������8��������������������(�Pc����������������ހH�(4�&���������� �P@b|c������������$^Do����������c@%c�������������J^RjXT#|���������%�(`imc�������������T!(T#��������� p`�c��������������C8 (@����������GeU������������@ р�T02����������CZ"[A�[���������1@�^做T�������������������n����������|=;$BA���������� ������ l��������������������� ����������������T����������������������� `���������������:`�������������������������������������!70��������������������������������������������*!����������������������������������������������������������������������������������������������������������������������A �@�������������������������������������������I$�$ @��������������������������������������I$����������������A���$�������������������@�I @��������������� @ �@��������������������������������������� �����������������@�����$����������������A�� ����������������@ ���������������������������������������� � �@������������� $@�������������� A�A�@�������������� $$��H������������������������������������@$H�I �@��������������H@  �����������������������������������������H$� ���������������������������������������$@ A����������������������������������������$@$���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������-�����!1AQa q@P`0Ѱ�,[�?�^˯&)J"P4) @vHVLLeQ 0$|g "J `RS0У W.r`B̋Uo\e{Cr* *r(v_F FGj`yZAҗÍPc@'Y4+rTH$4;# iwpu8|8EC2MTprcz;n܁jqm?:]pHtª =ϝ".l 2ۈxD mBG9B죄@%h7c Uܱ)[O�?QUOBxv *}ҭ#_#1a (b4sk!yfT7];BFZ ,u!9_zU z ྰ'C P?@ZFp+9jbGgLfÓ1nUT̖j^q`r@ZU*X cwD] }q̀�f Y!I*+é9D{eLWM)ÆAD>x|5n2&? H@8v=jT0(A4^ 'ڦ4'X r#C~FncX�ނ-.R.#Ҡ/X4gS)8ʠxW19wWtkϟ%&QwknF &g͊8b el.K%q��֎84#MkrkE/@ɘ :�kv J(o< S!/\@sNSP3Rlt7*q@l花iJlʾ]83DJ`^87 V rtpsx:ZkBsH*G|*/`>2K|a!:^'P Po{V vJxPO4Vp3w"W$4XPF!ɓ"]xtU1G?v Be" hRG6aAU ӡ hsN1&(YⅥaLRe]Ts@&yjeSIxvj-$h'�@ =4DřŃ$}jIbPa](*1 rgӵPlٿ FQ:؊Rɏc E$=/O[oT1a`dU= yn堾`Z^MbsY֌\,Y3:C.[}VsH M� .GEabs<{AhlP]ɓ _ [:~"8>zFI&{,wTA?&c1l ߤ!i;([XP(غ8P`,gjeٌ-�<E#'b/veDۛH7{p< D| @]i \/~.U@�5ql%h�[[n{ mV(C20#FV .+Gj4<'tz+H'rL"} d0ZRs!�Bn^lH2j^N9y-[A #ӵ@aR)L˔;KZjrs!lJڶ J^Y(!CոTИ8zuDx",P)]3AHcl)Ze"@�;q!M8q Np\wa+[,UA( QBFTrjRDan @OIצ48(ӛHi?vsT"㙂{S3 & 8!7 F!Mq*IR>zrjjȇHvefV5x% �Q< �~VUy>`s}:З\ Pw"87%2{ ^nk7sqnux*Ϫ5|"{)sAM08Ӷ )rawpDD!8D#4 >V0G0,(e +e1Zyc=b Ļ~3GO TʥWR/FLiCvi3֔n+6!`OBSkgǪe /_sr<htZNѺ*Q^ 5 N(,OF("70CNٺ<bi^<g�&1D&Ynw+̛UX�|<lYdZEG-߈Y=AJRd&��f ¨V_}+\I7O$Ymt#r]Off؋m`8@;h7$ݴ?pb%Y<:+{dlAO\`IFUCzFaUor,P_A(yzn^*@9W?pu\$BEԼ\7?u@Fd E$�@e|vu?qgx}r@ zTl<)sd[&�ՄDDlG|' bDa%;Ws Q=<4YU/<X n>\ϯebc-*cCvHR[PHR–0$9I>FCp qIqyS\ #֊6F t{+mpLX3~<.r*b]R.�,/$> I@-άBƪ0EXbtȍr+ŗhi;98ֳknr4Mj%ܜNjR],U?tPF=׉T->ˡZ_s-v^{8 ҷw0�Z>[#f 6 $.!B �#O{vM#9{B?)Ϩm!ᝎ3lY˿F,]W~4j cN )ʓOe K+0]MWPQ8mtP*Je0;3]F \ y'xKbme�,�eV�U�[JӆQ¢¡AT/˫sGK %7{;0NڏP6S7ބ0qQrIu</yX%q}J,~T<L1ʒ Fus(r NlڢM@/K! hUfOF%q`—j!=W=V@΀gfP]tYZ&)tSP׫es*&o1B�%JUM7`FA����@�hzP VX c!HB605۫CnB XC e%(4\-ˮłvT `)"l::]P 7=9>=Ne][X@X" DhI>D]a: x kL]={diiCd �A*Dae%aUfg4 j@|gg�= {Z&!b $%3Xa YBf*gR@ gtu60;@CSg\<3\z<H{CϱïjYNv˞JƿEdU*p \15@4 l]Ĥӄx$"(M0*WQ~dɁ.g drmOnmB! v_[^b΍$:cQɭ~s)-9A�ͩD�^p|Bj# *�Z |4݀mn\(Q�iP3\36*�Z |4݀mn\"j<R/{Р͠XT ӧ&|U<vZ|�*cbRQ c [6hqcg@_>H}< 鋼rzq  Rz"PX)?gd0 b[lFB~FuL[sCEs;+ahRF3+|to?YKZe *"0{ 4�, ia9:|)3b�$g ho4QBRF1љ<$%>oۊ++<pV_H!%,Jڼ@['yQ, JxT`PeF0̉s?8!HhgFE#!s`Pw*( Jx:,\D5Ke# `#@N#$\ iCy*+cF s.TQ{Ϫ'c]HnrX,ԡaM󔂜J180լǤ(%$<(%$<J h0N=41ʳCQc+*胀u"uG@hu˾!- f Wkvb̍  ϳ# E D&jG/�@@a *@dv+ %"&!F!hp AX@)Ċ7ػ,Rk GQL@;:tp{[FfCVAٍ! c$JnqblԍpZa272;:A-r0iXd<U dQ5Eh]֋fx><mDt F,̞F-ltqL19uz#H-qA)qG_Dx,t/y#=r(؎>]5%=Ɇa|AMT.S=%nmJ, .b |3/CpF" TAJHF;7SFӚ$�w& !H0۟(\�oډ8T[4m2F '�crkQ.sb�Ud;el*0`B�e4ͱ ō GffTLbzN<!k*6jHԃ A=${1kjXU4 @U$&g;l@aaL\W@2zfۘ8:AHԓT\Jf̜�Uc|n\bh9./t]#H,QeA.If8TrёYFA9&n=;@�V?lS"z7&iWR031$6.7 adZɉݾpU!c6MCvTI/ʻ۱ce rR dPF"# c" <�-cqGʏ5$ iJCg8a5e[աPL%AӪXK_$uxXeUs@L iM.EѬ@V(:qi0?"nIP6w <mT7:凲7G<IrGyN|౲ùF[ yRQ™Z-&. Q0%;^쏁s ⊈Y5 e%xp53eh<J 7k!)zzMx[fUdpi qez)'< VmgaH}`uPIgrw'0AbxbOh[ NӅj.G+}QUf{^: 5$Y *qZzvuoHPNPP7^S1 ]5^gO>QZX,&^UM Ns'.\!U{N$q9x|^R&1]1$/Q RF'| :l0 `X-8X fMW,VS�`Mg:hk�܀ Đ"$q"k'1YƆB5Li~,bl=I86ZT]9f.Gf߬6}fЈBJ@,ey3c:Y^g8T ֠a}yf�{C'FQ H=%Hq& b@ \ƶ7=ڤ.\G{w 0Fw �@(H98X�´  $2 CD";O xvuTt,)MIQq UXJVrURe]UiUUo|O_pz=\Wpz=\Wpz=\Wpz=\Wpz=\Wpz=\Wpz=\Wpz=\Wpz=\Wpz=\Wpz=\Wpz=\Wpz=\Wpz=\Wpz=\Wpz=\W�`QǓhD%F'k߿~߿~߿~߿~߾i;4�vM<SA$9ǒ# )8=h@U�t՚ߞݳ}A3pD}i:F~%~{}(]L aN8^'8' ?A0 I&<HfxF6@ Q( vivrA Ñv<TRP*jBIګ; "/i yPYMav6w|83(n['Z:ˁT1lY= %2"ªdT)u(2EV$ NzJhy%;OG{ *J IzTY�ҏ`$h]y$ݴu}kny@&vBBL�T�mVA9xJ̼ b^L$$Ȝͤ*.LG,7`f18Dpe"a;\T wCs=XzZf JY2��`U-K&pQ^6J"1d@w;bjb04e;?Y@R“MozrB> p `(`D PC{d!GgK� �4@N%ll!b"ƃ�~)� i 2u:&~,q3[73t.2'IZA `]O2z ԿA6 hxApυoگNqIIքN[SۧζIS >\ �^FHߡI FmK?jd/U�Hm! {yŠ_C ̸"("#;|lv;qpY ڍ|A(P�+s Bd:B:&"qꏦl�xG菏rG:�I? $u4$ 짒e̪Y<1~Է�7`s͇!8l)`>x(ҹ$z[!lP:09t|8E3^t"v縘 pU>{t@H,Z~ ?njqwW E7'f!O.υ}RF<xw)X)tB&e:9;**[DG;O�"':a\9#,*�<غ߅!: ~�@��pzge'¨*UQPPm @4ےl#)U/aLRl\ ;sEpg΂N )t|dTA M&H ,]pbS ;!qM�S IƧ`a�tK\CI4ā!ciuTw]�S{<:ou Sxt =Hjh$EBwE]kA$D;h(j(GQtTIGPYJfb�rݞ. Ao "& X@@M.I&Ddq F'Յڮ'˫MPDFm%*9Z@É)Ѳx˚%܈8C:�UwQb!XD&| |ɠ8Rph5`10@Fglۚ 6U:NhDbZ5$  � D 2"Q\;Nv#66: g3 So֎IH]pV߭PU�Zj hY<ɆfBUB[ʄXЄ>PPdFH0鹩iһW8 $ ,Py51RX:V R%͊}.DM6ۼ$;qA qjx966>MF$5]Q] &Rdjv#VRa${8OH&K"7G=@nׯ~ܳakV`]+ӿgCGǾӗ~؋S_<PrM޵m"pGB81`pFwiUX'(~sq@Ѡ|(%iWd<{%#`zoS-)Bpຆ̦6ϴ۩?` RX `,w%.m_.dɴ2i^0Q #iĘZ4X1Q`wT!ߜ 3$E53 EA1$Y;ݘf,$HFhZ(�*r;9Uc_@pe|0 ɩ˚~­> |bΩĽLP/hZUtTSSq%mމ!$y$E=S-fP Lz޻ZqTkH,hi�#ͱ\lvM^x:EFskQ*Gс;4:Qg/gC]| d�gjh+خLAvA^SODD@px'7"8 ox**ES<H)x ,N"nj'O�4*_)\؊6 B'Pt]p] ,�DDJyiWO3ᎯF^[o3�";+VnGr⤻v'c9bE< F7X)sWG`VFiC*�.nhz)`PYdّR=�("c3(^RѤ)$k(IN;`*ۂc6Jyk;wZMDyd�#J@DDxGH~xwو$Aɑ@"4wQ y`.d P|0e'W=St\}}Q++c}b Rv*\aq@rMQ0@+P`f":nboUl໥ j_Zۈ?!DG Q s#v!5Gs -XHvAPYXrJT#oC;Ze�=28lԈ",[i/ &h@o"@@XgMy*dP 3wR֚/^5 �'+#qQWj?7rO<CBtv2�D\cHb"!jJKWz hIԑ7ԦQ gTe }MM H"$:GwV~PfBlt ,PPp{sB(qZׯZYFէZ8����� ��Q xFrwe�ML]�*)?Y"l ;*G0;E`q?4buA.@db{duuC̍ UQ9�كksX�`o' q$ w nΖD,^VPD(Cs"C%Da���8'9  S�DqAbuf@∺6[q�8]xTW)ً\m@G[hxDj0@'GӦ5 @4Gj;~0 &Uy%|56�| 3 &\SEap{e%BJ.;Ĝz49b-B)0x`8_ @2N9N!7&(y%,x`,`TvÀ/ njZآ;r$ E*h? ͬkG*ńK<k`z(dt>JԠ] FCiC؄7}mk w0$ \:K H`h YTrmѿ`W%_"֎`h0WٚH`)T!p1㽀ғW^USG@F"wy1ozh.. d<?Su#҄-[I% D5dV8A C풜]kV=FS$x'>g#O\l YA๛vhyAPEh݄7!q3@P^t%YYbw`%kC0V]Fȷh.r6\r""׻p!B7 рpB 5; h9CVooڪ;�+�YGX&GJp8$urNˀb]yGHWd7e|&oCd6Dk3 �h63F\ծPnėϲոRD+i@bG11$2]^;ZG 2@ ݵCl.@|F؂#(XVaq1Z0 C}�RǸ8:!(P?f.4Pcr}ٰ,'DF @ gJί wEB4i4 aWt];J:xV TE[΄K6P0Aj/vl9"*gT0T4Ո�mgלd*i2�f(xHT2`<AD8%Ʃt ~c-tΪ.JdV^llxDR}  FT%0QRUvw?�.��!1�AQaq 0@P`Ѱ�-�?�ܹ4f Gی`Vqt,12 &P6ֹct n4̚t�I=o5|w̓:XG>)q gc0\zpeМAc] Ea O r1(4ǂYTM+}@*�X�Ʋ@ UU U~ m9XSS fuxtҗ+:yW8Y</ TK4Hs,$ W"1$MNy_8+s۰N�Q9EXMRhMƐ)-`u9zL6ZRGFF8?`[pXJ _r1^M=U@ ψ}1%wbE_ʤ�+Â(.7€P1 @U`U r.uh[ʳ^W`&pց1E\ OA#RQXbgo %,3y[9i#}cOY]uU3|8$i�_Q8Hi.;d0#dBtfXp�y >d %d(ƯVyA"DS8T"8ElᙧO%5PVߎ0 #1|?d| psV]�6g21bIݚrچ!(i8I� '-߲< Jhzp @<�?V,4PL3�xN�̺1]qd!x0Mӌ%DB6b>hˆ1|cvhB\`4 ;<%+:7'}Z;TD`Sy"Adb"ۗ`@r yPu�- ^qw5G$klt9̎5M�j p]>Vwѷ9v x"L{HA .Й�D1Nާ->9 �5LL�b䗖?Af@w=_!͂|C-mDSj`YÖ=">+(j#T]3)TR8D}D+PU82TYHHɟ9O(-KgU*QSbuByF� %1xO }O/@!Q;ud]sNpF t>7KgAvyNW&zkLg>Ӗ}H”lAl6Dd5AW1T)Œ0$G.#Ud|67 @7mV 1k"|T6 `.�?9O(bƐ ~a{~}'/Oì8Ksu>2Q=Ë�5�?\{7�뻋9Z(Yr_~l*O #ꝍ1; "h6]OE�GJ RdoNxLf^|D�A.-!xL%�> Piy?�OSPcWY@$8s(3Gֻ#L2`_S<TIA$N&o1G&\DTDD}z?e@m_YTB'_gA!V ΂ޖO$[>9 J)EؘGYhZLY!H eDC,S_Y^}P`'oi_^Zg&\I5*j7�q Ӝ ' q �#] $p2`IY~=ihelEq$?ؽAb'u;RvryA<�4R:#%k(cF|�l 2Q8a xTxSe xɐq Fؕht*<LH#W1Fr&(84"hj:=_~N�X^;ò8Y~D( 5SS'2YzFz= 9I$&D,炘PSaFX30*kҧ)B0}tq|VAHp+nթӐv/_K+mV놱'ff MEe ͍z boL< &DzhxDhA5ߑ�33XaYYNB�޳@kb�-ۓ~Y1mj־-AYLl}N#.gN >˹jV*B @XMdV�Ol_Q>�T�揰y)4`:ZY "(G Gpq~|j1;O|pDxFAgx�M,`Q7@EH*� �,5_2|녌S;tӓ Xf~1D??<t"0e $#E+q]q]|k?Bv .XBk!v|p sfԑ xPS<ov)ϿCaVP̒;lQ`!!H t^c1:T F9e /kPjwexg[5ޥ\r3hXps 2WcGK]^:RJȓ7nCV!dH}B;R�Q?hp}(A!0pPQDR` ]MT%Ol Qa qf ؂@{'hP0o~xCzx^#ﰨi[l+6m`�NpmM,y�A@R!pɻc<EW,w./l =U`�]w 7"Fy&cٸX)�OYytc <HKL2 5&YZ+]h +Pag4Ys"$^� X( **Q>yߖ@4Sبڙd;n(60؈81(vt< T9Bq3�,n_a*Ӆ$@K(gt+ :0<+ R΁k * 6 0 H˼^`I;+0ӏ?Nl)'7tU Jr9cv+8;�"W LNpH>ʍY&p G]*=\ԣ ul U-C.�oNM KΆ dVT2ʂP+c9  \ 9e.$Fo.LB ripe @֪,5Ш6ǔ:8#v@2܉\*@e.WB%h/�7(Bq' } s IA[JLUZD,y{b*p K.3/!DNj@,.mee6N`JeP9B7."]4ؙ / Bk89ep dZt9�l F�S²/\`w�pĭ2跂S4ʩz& t*>*AqJK (N#u>x^̀sUX-4/� f<B _)GQq<RNympȈlC,pgGYI#&ܼPPFtv<=zIKݼN +�ɈIsX? >x#*bc5Dl;\ La~TLx.;o 埫97]ahC:1qLNÿ/X~¾CM!Ta%0eEu2TR\fhlQLSja3{)QѿlwdhUU~Es`ɒ4#qbqR/0Βwˈ`CόE7G~~Qz! �A/LKJM-L 6+*sou7y4a`3=SF۟yz(É/@߿ۍUr.5�Ҽ~@d pfpDR5r3_xw~Ry5<+Iu׹nQAP.�qH"7P�x]pJ#Ii58n+y~Y#C<u*@%Bϴ\�v^XLV;@bS-KG,Bs&D=XB%ZA?'D>#  YB"L0}xQ`�hXՆK +0g w\6"tFl�-scg%ۓrޯ?x�E%\]rީB`.z4ьJ=nAc(,!O<;p !Lqu `D2l#35,bz9XAș9k ̀l9bdi>3B+A қ^(P!ˊxb+$F0h&!P\/w Lg9.@ (U:+8P, ("F0HRV7XFO}CrLLwa ݦ!x"0[.E Y )ccL(xQC&-`2mJtE`e0^*ۑELtLR!6&CtA] &A.h= rM]1l薱J`.45=29 +H2caw)Rd<n,:{i4#2d(7SLٔSLو^lP*\rh(=^*' p *iF4DK0vJ^)j2J$v.E Y )j,e,*t\b ̐pOBؿ ?3%% BX*€ �7Z(fa,8 fJDet; Py1eYD"TUE^TP)ƃB"U5GABP0.L7<B5 務v.){A-B4apҒ@%1 S 7$ " DnAz`)mup�J͐F̅N N-zH@yn*HE)8hSÛ Î:Cl�dh6OHf$N'ZB`u{ h`P$Vj�LpbfኅtFlWڎ"PsfZ/v W*X8v8OFO 3İՒ1 d 7cع)f3Ba EN[Y`L0V�>28+A=_Ԉ['`c�,r}AX=?X~x#g 6`kx%Je2U<ĭ\E,[O΋CX*I*zzn-l9 )ކhY˖JbВ,yWBe [<WaiȫcmVnjkAvCHZJGtt(!2! 5 Yud|23"{+ܻbE5Ƌ_;R' XktBƨCWGHIx@ijCp?\*=/W"|rХIP <V�yO�[TFKT Z@@߲Mxφ24+5L!ZIDYcW_@w.X!M YʞyPn}$}�X5eh"!Ftd|hjb\6W,N;^fjQ5HE˧#HF͊%]ftCINz3F <,dP ŗÑRKG˜?0~牐JNxDDvE1PF3�< мW�UOKR (/3o;j~D0粵آ07=dpiJ @~LI2j9MuS]ց;qHq.iμzAO^/>j0\skT]>z)GrIۆfن(Pkz= aK;(_0 h8[lJp1*) I>ZI9<i-�f7SXJ6zF6# |0̔$ŕSˇ`ʹ8] @$ߎ&hܪq(*'7@pW9Ri1c1j2�'F$�ynn@ղ/|:g$l|ZVQ}dz+|R|ƆWZ?E95ġ3Ƒ#Pf�Kʸ`s:�l$T숡#$ ,Ę,cVxǘ(Yq)fRUhP�ddFPibN ~A3# +(S`bHb>T[�ګR ^(M)( %nC@A;I=&5,0]̦-w۷p;>36C#v~i:rn] 'Qhb{"<:�`F{ P}I� �1ijԀ@!Dp&3|1Y^D"z2hbB@(%R%[DKoeTOo�                                !Qq{s7?ox7?ox7?ox7?ox7? yGI6v֬,�B ӟzg>GOc,/ _�.iFY2 BUaBq.=o pP8E91|j"{#N,n{."(O5V^U:04ť{8wϞmc �ֿ0\1% Aۖ0�dk2ڄb^Tlo#7+' )A�lQw}c=NdC"GRיx +gpE_?��翣ر7v@ ՝Qq@�@U�z$�v|D=鉃G0*51CW.0OD1!8h<Fꝼ]y�08S!!ŕ} 046HQ(2( �2�T21 =n, G1E6^P?Y0]N����:<ȁ=ʐx_`K Ԁ!BǴNmx3L4FLG"1$v-Le � JH&ady+ʵ˝xQ3ؙ]EYAĭ{n}h�D \&�NV2H[D 8pfzU<>_GV# L&z|rrrk_`L( H7:&/0z(C<2pIPQC]o J7MQ,N)42.`�u?w�@ >˕ w6$? A"gF@Z H(v q|p����I s �6(2+;_*QIeyc�G瀞S;q~wȞe��_9?uN~� 7vľ#D0U �M `0ǰ<\KDF3HE�?%8[};ه;ٔ *}*#FNK6ϰp3|L}kW<3}?wG\ |NXtqJY=4Bho+R!'~}C`iou͞O>4"9qt؃ k˵ClO >^uVgT9 p )2ב3n) ^Q\Jy~c=4~ӟx?:4wkNĄwQ`H }nT^_g3 8iA"e�F!4$KnN@2xG>7l/}P4Tp&'O1.|X*4Ur1!BD:D#t.f '1RAcNw\/VVw(Ѝf6DD`gF!XX>yoS81r/)uUZيXNTb揍q ķ9BD>ɰ"u,qç82s^G~ݺ,qh' ˳ kg1WzcB.u<]bqEeB1oLiώFKSkGcG9ǃ3#X>gYS%&T�W -_ܼ(Va 0gx4*l]Fcq{)-ȰT"5Usd* qq 0# od!Sآ2=q-7Q h*�*`qA6/+5$m}*9  H|Tl#Lc8X &`6r1rbb܀@ єDm@u\�ba+S}ɼ=N0k6Ib 4yE!LYC<ZJWPvTPR!ɗ1C5OyMr 'J'>#<"B:&8* $Uzcq)i1HOAg˂9( y.6%dэo89usw*E,JqO]ÿ~?<ʫ\"-ª͉PDB29p$GvҬ ? 4/Zm㼜!a+-媦z2:KIxm5T#pQXSbd�0eMۙj-!B !ZT D!J7bQb@sO~_BDā-`JP ^&JWm!t ℅%SJn^c(xfhP(E#�#S1qb<?emީmVZ{]?#N�{%<PǺUtWN Q &0/{fHxjX=7i[3׃>G9ǃ3+xK/OQ/qvTdX.51lBҌP<"Œ!,;ì^Waix$`zI~ЋfOKs_[Ǡƃpc 4*m8%-o)<Y?�I;.͍ d*z7\D@J*VaTK۟ў16kQvd�3�v�} `wPY<=Pb4MJcR- :@�4p4:I@co[¼:Uׂ:u�n_#l &6{ ÈDŽÃΆMCHρ/tԘH5#px1=Q-zW8ϭկ}_z?x,1 ޚEnOG@>rE@ګ�! Gl 0A"v~駦F( U! @ »܁�U7e oJMdP &n^W'w}Fe2m`0%.`)�ALSX$KjDnAQ!�BQP+ }rc^ a@$N1] j|#0a20r6BDcd0;D -=z.`\2 4q`6K^#;R 4\|LӴ (K Q!Pz>sq`Q_}"P�W g˃Zi��)&\Q9<da,ZW:kZ1a4E^}9ᓤce_vT\_U\D: y�Rzf:ɥ cʫn @ $� M"ϚӕjH��� >S꤯Ӣ2{LJrN~%eϿol~f/>-F"E)uL( 4 B'Q~@ʯP�&^;sS"ǷtyYw5l0@UwBOJ 4LpsU"D`Y Uhp@`aMWnӎA�`(YyfU,>L>eHvL �a� )UBIBR "[J7jZwuѯE0k(ZGh` A^ %09ѳA[P+ł&GJB&W<HtQk ni<&<e*}U/Y_}"hĩŞjz*8&M"#:h'%{YWx]'οeT*U U4 (3KƎ F*"sp,`�_oȋx\\vC`޺>Z<*|kR:lW?8_ݝ dDPpD�d�>E�Mn"YFCfD:9F~Eg7 8SS{�H$h|Fq3N 1͐I? ĠS V:#rA&v+JI��������BwBCB.1z d2H+p_.X9IFLH qZX^ QD .,l0%̽-rw0 FY^ 8 ڬ94W.-&$=B O+a(s r Cz>gJ~{ZNx{k.N 7~}̰',>px9z+~@o_je KY�@@�} (T�y1D� Zr[сTeOS!bPnE<(@W@Ij%frcy%ɏ_{5#P==iN�V{$"/ [?ϦM�'zl )2"8'9P3D)-dR#1EoGg}kRĀ�ClБL&^fPTkJ?Q-�D_WxXG>?[Kgt=kd)n4A.r 'y2*9&`gu12$q2EJ@ RX$_o 3eq4>G>)fN!ctpcbp*m5w�<"?I*Xj5G#j3Q🥺1χL!�j�.������!1A QP`aq0@�7��?�ݍ !Wl,hH`U0]IJHST]<Wngwv�;4y*_dk�t$J. KNࢻ^:@mf,v"iinL"l_qL6OTRnY8g)[�OlECb2Ss2ICi<�_��K�1E?+ n̶*/ډuI S2mņS2q$_"5%т72]ˣA;uRh|ȁ#4I�t%)i0(FH9akQ,#EOIt`9/pz_'yݞh E]}/3BN!-=;;Liޓ1ڐ:і`G�e Q|QTny)j/khn íĂiBۆUWe|=e6dI +A05"%zhٴlEQ] ! fۭ''TJ+c:Qg^Hp9 []YRX:-J0ZB}VѯiW]fё %9C$ }#eC/jJ D!U6= sfdL BI%49 Db؂•*Uw- )9ˈ1x4: M.%1  _PWe +榟Du~h7u- TFUګUvܯp7ܽ(!b!0^ [ˬ6UA6R&8 TQVַ` @hQ`�e�#ei$ >@jÝ/�u\ e紘2xSc&|Oc)"@eHxI TN.5e5)vTE͚ ʋ?M[n r$Ii[CtPqp(z5SCL3!ѕ"| Ʀ6C8~Ti`.O1֮_ m r\xMj*-s V>E|V�~~86@JQľ�ia v' ǫk #[ mbaBMHAi~RR<VAaQLđj \W 'gMÔ-#h*0Hh=4F V�@.Ȅ SE3s'e!FU?ңBm)] 'I{~ECXȉ\B)�Ǐy`_IkE@v[ݱ+UǗg_Bx Q�W=ǛA`୸u1]|� ĕQxD<@�#8QKLFh^Ctu%т6PzMNuz2ˮtub\@x'"ЀR| pKZ Ũ3�$oGӖ+u_ +Jװ~aaaU^ g¿rĴM٤]< @U 0LQ~} oےbYO t@2!31$W�s[=XR2֨Cpō4~pʣAl![V][mV]g GK;7n)ڝ#=DK#.&葛XA TX,{8b4k:EA!L-Ɉ FJSڷ9?B<�b }x @Wa@L>3Yri0'ZVdIG()3d;VO8]mޏy/sQ�EvcY6 ǃgkc!+81fS+,ٜJ4hcU =ҙ5jNXZ U@xοa^./`yՇFOh(zFk UQV9]-z>f(lG$ RSS ""NďaP@<%0ى4G]i%H4`S<7I|!kR&"}ៅ~)1D(Z"tM7k|+ @C F}g6{<84N[:=�ZV�v tPd{z({`Bh1 "d].N1�?9r+bv} y9'5חx˾H8Ip\YOGn3D  K} Srms0H_ g" Dzjvaù$zjvaù'ŤCmm! 3w΢�$ײ-RlܤJ$JC-rmV0D\ T'9%pFL&Q"PՑFԺ)\`T x Ҽ>аz/rph OW7D\M�t [%F] ;%dL "AB GHHH='19ȓY1po`uyr$FܦNl ammg"a 1|D�)(ԽuhY(XΙAE {l(NDC׫K:,Fyk7>|SL)1ѫ,\UR�-4cbPVe6 Pk\p %#b97E[1(Q[F8p&|0KtrdO?7"8BŦTa;pO'T: V]h׋64{b9^D)yUq+�x5*} oE<:Od9CALCBc'I{ -J?X 4/!<H|i( 1YĤxLT*KLK[$*% @X2rH%xΆ Ol*~dJ@>*K"AzF/4O,.dAZ@WZ[* Ov#0]H\r'xcԂh_0冾e{9CALCBcŕ"A<ĴY+f1{ 7Mw]&<�G!D�7a!Q<�;VUqE-YˉM0F6qϹj[Yø3C7+Z(Uh L"JduR ԃ/tō>=$ElxPM<_Cl_=|eށ\ 2@<�P [U4ݮ[)c 6l/2(HQ䧩6 +Sk  ϒNN#a=Vۯ%TB@)kOz "z)]_h൩YʆF(b-0ꘔ_,`$JVTǷ{%CY!yC W\>Զ< 2]t8^USHd�d` DL&dX٘1R#Aޕ;D2.#T-'DĖo v+ PQ1f}%՘x>$:$ib]̕ϐ" jkҫ3+!>r^2R�gũzC>wEd/bC�-�[ z� \ԷH Z@MOiPСP.M)%t>n00 ԄCi8\2^Q $[=cZҰq4Z)hlZVZڸVt Z2PުE>=raW e5-=ԂD?}1~6"OHDK뒨~i8JsPq}Lc@_ej0P*+Bm�,kzN# QobϏ{4q e wv_ϾKo0F['F TbkP9[[C"5 ?J e% gOW>_6KEן7Pkޛ~.ft_t~nYghh"-YqU�STx)2~<dX"Ԉ?Q�&~ٜ e':$fַv9@*�y>3I:~ijͅ,o,a{N6TsQ :Nх,h0#$y 0Dӛ,3̆ B$(RaQg)X+1%5ct6P4ڶf#?^31dJ- �x_TP*#ƥIzUQ Ȉ#B  OQh>e?ʂr$3)u`8%i[BZDMWf2` P/JM]:g& <Zw8hK__(@{j:*eNtGv m*Ȝ yՁe7 kLp*l*- �xov hp*ATjb/QFs�/̥qe k &?vH88&ZP?D_4G�I ˢW%ZI[hi8m~8so6$.'eol<F �-tYK"JC<D CPjqmU&xfϳL|m Lz[PJ7Z")W ȶ'w*֓(8)% q߫ذ�zϸpsm5e H4sBEPkHiwTK99Fq̌$ [8iC> .  x:|%|y|=Mlt;oi(%K�1X&=ʀJU!*nZx= �'g Jt 5@.N)%MmKAP hϒu𓢕We!.w,LがtT $A$DwDѡU,uv6 6 H(KCQnA֌B41Iv{Sڄ_t,~)`]�V�l8dmzn CƠX|'7LAG5hT.@[Nu su$X:6* BJƨ,nhң{LaFbQe:;po3U2nbvR\[�dY:6V!& 33zgNXc*:hW&=[@PR \լd0K]Ș\Q<4נ,[YūFˎ`~%`hUnѥk\F`O^, 힢ÊNwPv~R6gU8J C9g) <A2\�|YO (3:/IA4pBa*b7i["}_8(nyHz0=ahԦO$tt 3!C!OƆvW6JBAhOSTg ݇ !,]AHٜ�KޞHJ+ɛd hʹohDL>`kB k܋ e�j#t$p#D~J04 OH K#!d.LB2̐Qn43X ^0z #Ga0yD1㭗gR^tU7m|!0f+?!rD g0Ȼ�3�И3^lj}@gFD'TQpyD1:V˭=yD"B(h)5~tNT.kNn2+� T] Fg['񍵍0��W_eeu;bu]u]u]u]u]u]u]u]u]u]u]u]u]u]u]u]u]u]u]u]u]u]u]u]u]u]u]u]u]u]u]uٙ iaOVlٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳ|yS�O) aI6vCbG �UG'<"j{c^㓯U_]&T [BvbL^ j;YPܴJJ=-ުSW�p7wt7$ HAyQ$9ra[l (C):Fƕ h-h3l`CP`E+ }~{^ WD#F,Ѝ76t ֙*ev[+EJpDP8>M Qf.@EW9pU*`Yr@\VtC 쩈f Pz2N 䘫K ),>$t\o7=lY$0mאl` G wtM^ 00ijV/F۰m5� C h�`le""A|4]т |7T#i[I?e),g ϶TB8c�Z%˼}HB)1*o!C\)"Ԡ;9ٛIZc•#4BR|R8]\�Nc)# PR\~G>qOc)s8hD؇B{YD~$)W〗Se|Z"<u=|�_lٍu*� �z;(_ V  5T[Wb?lޠL6<p'iCi¬&iF{pC T8}l(y: cLv~ 9 Mt\cSLlj�_.% w`t;.h,��!"lM&zt-Чw">{>ޑcՖV��wD W�m+G} #AUl T"{&5 ʅ>}"$tFy7346Ӱ.J4ttLYɇq>yOaڱo�A!!zT 1ѹ�}+xkA &""ii(E -g]A{`1:(I�aꩽۄ\-YJxzDr `|S7cd5![VLHy7v-گ%`u@sCz_OnݻUOԎY/W`Q ՎJn!Q}%5@uq55@yJ&�U_~HiEh oK.Ӏ kQD#҈DbU$TOJ˩']W"NJn`C `[î $A`}nಝ $+jOD*t$XЬɼ2P8uMF!)NA8Ltr" I$:S# yKdĦMѡ\=5DDbw*tO6}A~^*DVD, (1f!B&j;A0w! ~QFO�5DY<߰O3}Ͼ��~2}˕%<5Lwx^W!Aa%0�$zi0{!lY`z8!VW5 :_eE fӋh V/YzUW;U�2䅰8d_^KGU[zP?Б6Ģ`FYM�닉"ׁ˩hN"!*x\U4/@)L-ne,c7Vo@ a, zlby6Gܸ%kqB5CrhM85~a+'tD:Dj55�Τ >ОOgć頒wI K4! 8r޼wCsr0}2K`0U5G0@0@?E~! Fl8!@MT^j2^2#:SѺt QFEm4VL;>_ cu)-I B+8=ϽJ3O3ir ma{ 7%\<QiX}ҮGc.NG L@.›*6vr^U&5)f=I;'` mq:ofS.G@JiAip!E�Pӽ1ւ\Kb,) {py+K^˖!�ղXvR:oP+?w'm6R*"fDܿ;h,L>ƭ $& i<q1A B\Y :*Vq^n pqޕxK[a3Ad&n`E&E3cYZTɲu> T )JMRʇ nQb _B-\+% V]$:Fͱ+tz(qr=@I 1(lSE�L0>?U]j7Q4k>|']Ӯ͚wY<]x$們XPc|E)lNuv@UN}!ubT_g CDb �ZphnGq :t%~)85v \1�ۓշwlñA/†OeQ}E]B)Of 360BxՒ2kA e[cׯaұnZ�*خC>E FDD[ #X] 6x(A0<f` �LpNJphقu$Hh&�a;{ "4PQH: )nq_p*2ByZew3x*� b�!>t&_)~ @2&kÐN(�;d64EJ]=Ix"T T`հ�ڮ4cg08>MbpwA"] ! 0T KVn�1e1'a. 1l61& 73.Y <(ZDp$ ;`}+V->J@pݟ,CDkԯhM�9#)S3H:4WڐtʫeY*Լ`(JSicKUY]P rw9lƎڕ#jJ<*DDF"lD؞�P@1 6拾mX}jRǩ EDz . J�Tmq%XIrna' #op�;ݡb(Du|ZO'Xd2["~uF�&!<.ha'aNT> 6DH&?i_ J/+Rz$1-ŋU…)%Jv$UCt>ȆHPU_ -P)qҠ-+ZFMDi 4a"6تcS)4,<+:?1IM(� n ȁҝ@i€lGv1%bOh814qv,zP zRT7&c H!\ZOˆMD^ ߂:  d# 3z#?y)=O bj`Wi| aZ˾DKOd AbF `( C&$E8E b*H��hI8р@"(ͧ"r'luݐ@%bKȯ5 Qt/wpI E\] 6mo>UrX xӝ!\.&�Ɖ ta$�B Y !.e1 UDG�%q`+٭? 1';]'fቬ"v^mE^O)AWGl,{ju+</@\LzPCFئ5̵_ݿp;M)){È; ^Dp ;WmRqq0V og pyE( tBSH &@=x;Ì RCh{ʆS!6u]. ݷ`&bm֮ 1j�4M|mZz"Bx >)&tYKԥ ǔ4D&( ` �U`~m!H`=+)0BSdl ]}_'+,Ok.:%)۫WLK` 䐗NϐQ,_%8HLMy%pBct{"'bE UX:H5%>Dmri�yvx`$@ Q@ۇ)hYFG !VLL.]XW'@Ƽͨ%=)!0t|`V <2O.$<[zat$(`Xn>zŐ;PmC!`ˈIvI $S`4lr†q[`gYw; SiY; n(3-f\. ʄBɬB^󼠈6;P%DE*IU ,$2sV* � ?y2kRd!J}ENTȸV,IX3a2N[Abl0$C[A�b:cdQ$@ ]NulDQ &ǾT 3*5((^H2=:%kB&�4VTР<Y0DvY`:E$ ^s& h5x(unŧ"hfa*w"XTÇw0@knK5GR^QM - Oͪ$腍2@R!{rEQtFaaUQ+K f-}  X9iZ%\!vG6Y.e*z*pQ/pz ÑqshBVejAǩfCo=V{k+5f.7sܺ&-P'YFb[ܒ� /@:"y-Ȋ�k#q``fam1X_�2 =h8) aW9z)Q6Fu!?1� <h{E\24"́-3}O4B"8& 3 =`miuTJnU1ZAɅ+Xr7G4 % 5OGjOҍY\' 0l"̬Bٲ/Mqxp{S>{Jhb#4Ѐ%>%@:Ɉ?}Nx5f䲓 Nlb*ׯP-��&)HC'C�\#nItm^N Rfbb5eU6ͯB?v.5!x!oDI1(#Ҷ:2PMfXT%4�݅풰}wcV'1 >5naB-dE(Aֱ%hK2Xj u`VF?`m{HWP 3V:peZD!F@h�A.ʟLHey@#pXBE - ŖW(1V{ ǡ`L4/^-Q1{B{2N3Zcp"u[w3[[Gl{X�F#1̡<cT.zƌ�Vr&o)4G3O�xdJN:+Gܞ#�x&:OA;ZFO/5M+"wIi<oG|/!ؒykrVwIMd<]զ^E`C-yՑ(*]O d58 (� �^ɤmE]~)4:5jQ%iB,.bs�u�*Zc_޼Z%WY+Ѩ*;+ba2%X\8fbWC3׏&|uloEz Ad%KДg- ϋ@T S͠d'c9ML[R.|E->Gq2\Dݵh'gJommqۉ*&de[Sf%ocbY.1%�f@e&�Lої=^wR90nŪ'H1B PDzq XEӁ5P º~x4h9{tt"{:�r9L7 YZ"I9^ڋ3,ؤIaKď {�^ܰO#fQ"\<[}Vu&\'_C@� aX¨ n@姍q)zuf!@A3Zݣfw p^:%&îxZI~'rR30Kd[p`H[He}K\G&!<i͑|]ayU0e&#(ǩyܐ|�V`& jʉM]iB& OaDO̎~-8$ $\əX a` N$i~}{$*�@,P$F65pbPU۝6QqTb÷0MA>k s�nx!NxqIPf-fOh)zRfj d% 3;2 qK oC"Ζ}/"6VTl?FIϼ{z'PSZ&JI,jD't>il2i^Dg[f7Z 9(Ժ#&bpԍD>Q 6ΏRUq�n 0Y+C:;˰=PbfqV,EҗFBg5*C:::Lv́1ǿ$'6y0Gz.JH 0L5I;Kr#p_i6K8J/uJ'taI*8i e5N|*"C6X P>O@�Xl19� DĀgLM..DLx(5D`IܢS|.t  IQ'Rr4ѱ^"u63I`0#2$/>@óZZ8ұ$FXo @N=Iu}ص#4=bC4u9, ֬ۖ$epqc̲�z#>]_iGv-H.Xln0#�@m>=DSb v-Pi 7N� ;,+j`k(^ �' bDkbgG.O-ᒣY" sLT_-r?{ף5"w*ý2 9Y 3]; 9vȼV9&!?s $j4-^w,Grݙ5�‹@\7'OqS:p&<NP8wUFAG2Ad_;;ށaw[Z"3RU<*~.TùV,j!E Dd$_kM"iqOFmn =jA >4p|n)֜/."#(0r)+�1}*`?pAgk#g`/\SQ[x(_̩�+5X qZv;G1m0h 04@ ; h*Iyw+ޙiv˓/_vJ\Ny sL���{ŏ_g$ G7g$pV,NEPCF}Rj`WEwr)(&K룝>SB\8`����kջbm )Ja L0ń2\MC4̺ۍ#ÉfffANGE�JFIF���H�H���@Exif��MM�*����i������������������������������8Photoshop 3.0�8BIM������8BIM%�����ُ� B~�������������� ����}�!1AQa"q2#BR$3br %&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz�������� ���w�!1AQaq"2B #3Rbr $4%&'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz�C��C��� ��?�ǯ� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �ǯ� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �ǯ� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �ǯ� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �ǯ� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �ǯ� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �ǯ� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �ǯ� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �ǯ� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �ǯ� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �ǯ� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �ǯ� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(�=O?E7?W-n}E)nҬ--nof!b#H$<Op\SO V.S)bj­JtW$dNY%K¼qO'<tq0XuR(/g >&~ʿG (|&_2γokԯct7S?<vق"|H^*Ǽx-ͳB%p^m8rU |C>9bx|+xX5S^5gFVoiUq#s �(� �(� �(� �(� �(� �(� �(� �(� �(�?ǯ� �(� �(� �(� �(� �(� �(� �(� �(� �(�>en<~Z]PxƺR6d6Z}Z~D˧[<QBWt76KZidɨTZ2Ͱ9EZQ3t,~cQE^y*ZեO VhC?bVХWC?,ҕIe 1X =7 p5V9~tьO1eq8zc>;?gOZ7tmS6]KM[Z$n-skO+&8gOy%^jtrp8rw<AsiTr)M:xZI2�^<?G~!:f> әa<]3iPViR),M[FG77?>W_ إLqz.׏9瓆N_|x^eWOoKF%];K Qq;[Ld`~_?N�vt=Vwmo/Pu%IOt!R~tNEpt&gGYK}]F&U>d|ʝiE.Ixzr.i7߳8ibdٚ?s\ML<竂9lE2#j<qd?/?,A$XV\hzFJ7;dE:>&0/T `p?]θ 7 t)ʝ<Fa0G4T'K yYl<�HoGbfХ\}S(\ZR8u1.i:T8Ы*,G,1JxP^؋w~/qwMÏ_jMOWs{b,ͬk/dعw#| ?O/|@_LGk�|M.̮ʲ\{}n4ߵ4�z9wRle-xׇ2_ �,+3'ի}o%YbU)~OlO{U> QxZ-?S>+tԼUiZLj|+s5=d[&TO+#; /psڙUp&U!}{$|21,5{PROg*g:r]}<N1^76Ux h<C1✟./E`*l*t' _3�WO MYּ/\=WLڎ𞽩ڏ7v:UٷɾxxWp/x6pL5YRx1ܫ/<-pU bpu?kiօ9/GpWe<3b3fos Pp{a2^0<5kTJu!/՟'. ?w<+崿xwš<%?*oj>'ٸx[la__ӏG=xS|]r͸gx,ʳJ"SU}s,,aI~ Z?fO':9pG%qo v!3,0�[xLmJs'Iަ�3 �(75_ ~{Klr);~0^Vdk R̪<yO\żyϓ(͸x:ؿr+:Qi)U֥N/88gXsSe r*ٖ.u j6OJIJ1f (Amo � 漚!kɼo)5I.epG$ %1@b*/b�h+^p4+b+U~աRO F+NxUQr�@Ϣ 6o j+QJ\]n4lUZt UC YBMRN 0?_>-xw^Jl. ޒEl 3K(ƿgp4x9XiRuz4l8 Mg<}<t#J$T?|9Kx^5}gY]Zu1Y/01ɱXlZu)ILz(�gAx@_[3t4x_5P67}t_Y\3wo=fD_2\ml8ʳ?122 \>#J?kB:$?~#q]*U̲s # ^ WW(֡WruT+N/gמ߅Cik%SjgWou֗%՝V$ [іGVos+ͲK2ɳmu#GbcpeFUTziMӫ S68.5/r<3 Oe9IaJU˱#^+Е\&.*j5!V9qdy,4W_t BooZ,H']WROtX幽KxX7[ V+ b1pL% جV+Rpl6ׯ^Gtѥ TRr!IΜ0~_cqM  SUcC aFUk1 ThҌU8rTTiLugxݥhEީꚏinao%ƅ2]\$prK##2ď1L''K|F"N4URQN#)NrQm}+?08\N;0x:X^'l.NU1J 4:jԔaNe9u@��P@��P@��P@��P@��PwN?Da�Wc�&�evW���οqç[M炿w*џ9egkL/,r/E͏>C �(� �(� �(� �(� �(� �(� �(� �(� �(� �(ǯ� �(� �(� �(� �(� �(� �(� �(� �(� �(�|k x;H uOxGe注['LD+cVbTkx׊*le<#ù\*dyv'2•ӽZ҅('*Tdډ\#YW pR>x/W>_MU®"3+Fe'(UO~ |<!Ghddo*Zʊ%iԵi啕R0)񗍾$g\gUkfWƹ<-iR .SeTfܩ,0B(SRžpQY:Yg pNS[5)s<23ck}g0+)VVQ)B?_G_ⶻi~ /^hɥk C_-Oξ n$l-thK9%g�Ws~;Ἣ?hγ"O70OpEUrG),=<fxg˨׎_C3@7SK< ϳ>2שg( *Y]zV˰ ^> c+aqo]}yqt2res_Xn?Ù�쾣K%dK :'eeݣĜE,v/>αXժb3Lu|Rb*ו]=Կ�m_�<_xW߂D&/j7 tS{;i3fORB�%�?̋2]7xU l&Gr0~}Yӎ6x<sxЍ9KC9c6 f ȳ,tcrlm*\FV~%;  `k[?m3E#,5]>})JmC$WV�9.~"}Y6/#xe'pX|+S [ٹ4 1 `lVl&.Qء:xcBX.8>b~/4%*/%:U!L*:Xh?OƏ�>4s']h A4{+EI^ gwXYW nVӧ9% V?JNQs, 6ܤ ө5VFu�7<#7|<̝Iφ|*Te7$̚IE<(u!ƕJ{gտM9{OY�P�__pԟR? �dW�8 �&�χk_s2|�-x�a� ӡ�;@�}q #� 52�#=�)MGON~~�m??EտU}( ��s?oCM3y?d(�O&?¯I)↡mLs-&Io+8䈷!bP^ u>mJ�+j귄9>[yއ pl66*-Jɽ+3*<Jn/d?>3|=ʞ*8n&¥7+ƣt_m*5s nh7o;./5 k>0B'Ft2@X1[8̷5IlU<cr쏂}gY改#6]Rpx(7'^!dW(Vy`VOeuNi9)<F.?W Ju$ ɾ']:<][[7RtnYۄӠDOK> !SR<)J'ļ.OXJ\ <>)d88zwL~gj6Zӟ 7`>Z8uxoS+w^)J?!+?p?_%gO3�V׎G�p�99|S�U�>Oo�j�?0?-�.�o+?9+SNn�{�VkH�wǿEO~IW)�&ȧOAu�*8K �?q?O��d7�0?�b (� �(� �(� �(� �(� �p�.�xȟ?*1,`�[�A<98t@�ɠ\WU_�EO:3�g6�Ԍmɘ�eE�H@��P@��P@��P@��P@��P@��P@��P@��P@��P@�ǯ� �(� �(� �(� �(� �(� �(� �(� �(� �(�s5K 3mvPFsK1TFE9v!GEc_*r;7xPS[>,F[&SWRMԞ'B4:Nr?m6fX )իYҡ,ViC GRZ(C^*ҩ'MS睢4|J/WX/+Z+0EYЯIEKر@_`8CĿ5*_ qeFu'Wg~eFҜTay6g$cx;᜶if<GQee%&;/Ԕ# IɫE+Lu-:�G/VO˝?Qາ.apKyC+-̲rV6<NMi֣RgNqm4UrvQc fYn/08Spuaqf:UW:Ui)Bq^ �D?% <Ax÷هRmT֯x-;̿fk"�P#N(?gGGxÜ{3\>BeyUWGY~M_:9Fun;rr7)~ xIÞgĜCӣ3,3<Pž+RR>I4h>*/t L.-nndҖ,JOEwIY_b0\E~Spq *XU:䛄^1^;x[!xv/!2f3*cňQ߲x:Q*3{wcN^:}~ P�__p~H/\S�/(7_G>q_xe�`<y���(W>85�N�%�'_<U�'k x�`|� �7?q9[�ɴh�VQW 9�7K��m^1�4 �?اW*}ݷ�:d06XNm;AHumpvr�,�N0$82⾳ü!^<3P*Q̸uaA.|&/ AN_4!_\=/JV;hЭV[/Ȩ9}zr~+ iGMĿl~u, %ǁs?|B-6ECwZ# 6`UOlv (q|ixSqu7pur*ɱ\r|_�I:o[x#0%TdB@osG[r̩IÐ>Z|m!:ΧkthO X.ﭭ6;.I47#Ǟ_0x\qfp s7S$|=l3JSa2V+BH 6Re?4x-ƜNwdž1%Izobj1X>Z9{UxP/XI!7X)Q)#b2:0*2 A�[үJz)֣Z*ѭJqJ*SRHӜ'(i5ZU(ԩF9ҫJsVH)ԧ'ө %(NN2di4ЃS?ŏ�ďh^1Gk>4Ԭm%.C3Ć;xQ$;W"GB\u9WʡeY'lZhSqS)Tj�P|%+E_YvuW5,&?Rwў";F8iֿ�|ῇ;W}&S&/c:淩x�ξ,oԵ[ǏxL *.n,ߍ3wg(3XG FKhrуal>$�a?^[<7KC%ʞ5b3׋Y#O~wb7,%-"~i�?h?7<]7_u *t=EcQYO$ !1#㯈a8s!崲5(CPRjsV)'ZS^Z\ߗѯxŜM9<,ML.qPp()jWr" ?(;{K? >$;KּQesKۭV{c=iȿh,+$9�Rpwgx'|s%|&]V_Jir*Up\eϏi-|>�_e</.P׫jtqy^(D7%O~<i<mź^7|9ZdGp4?4ao8fH3ɱܦX/eK U# NnYk5%o<' -Yi|縺1tIԥ+ƥ8סMΜ,]g.ƕo xsK׆5kk4MҵO edrŲfY~kc1L )Tq8*4%:oIU(=$2&{f&ae U*fjLLiՍNniTJ𕤯c�й/o7_��A70e�4?y�$83sLRYs@ە8I(EVQ89%蝑gK-oRѴm>V[m?LYFd9n.g!'i]c NףT*B iEέjj5 T.S6I6kb> bRapZU+q8TСBeRj%SN2%ŷi~~�FQz/ijZdC}� Y,~.-xpc!o,vG!2i]'ҵ̿Ώ˚Fu(qU2ܲۂq8Z�)^+ U1XYק GN*Q(iWT=#?_/Ҭ?:]H�TWu+arl͞Vͭ@� #&.2inKҥK^ywכw Z xx\^m8DJlsRhXgusz�_R"/@u]C p7yZD <sx 2Q-8KUyN">)✻؉ZWF X|F, ξN}&R?ПL x˂n^l-?su&+ UB5Ql,-eЗ7ğↈV�Xۙ'<G$gsQx]GHG'ͭw~meZ^ZEgqGw8:j33SR̛4Ԅ_R:�4@<3L_ ^1a橀RC11.0X~IrϖiTL6* U!uGğG�~%3 :'3~5eյP41B3ljB.BH5⼏.PeCe(0jG ֡B5*jU&*Sjz�S|3(?pGvwS3xW#ͳ s]Sc>'*tch҃VN4L/O+x7?~*¿<7cp~47mCLF=?iٮKWAy-ɪ}'/8<5<LJ2\>֧|)c)(UrlgJ5}GW K~ḇu,짋)ᜯRy3LFҫ6XIQXeSʝi/c}k�4F<2Cyu\vliU&f$IT~c_犿HV)*S*YvMO G.5 jkI7y9VQ-GkJr$V͸.Oc'E9^+8#aP!(/.I'lq" e@9=Ns_vrRNs"۲Z?\L#OhSVWnэIF*%w{5�jEHhm|xRYǃ_kW!M7JY=Rh ů�7M,߈1VNxefyiO(rC.&FI՟<B_Oxȸ_:^ΦkbJyVKib1؈Bo,QXJ'N��fOiԗğ|J\Wz " º?:Fj3_|S 7O[2Tap>Qo,-l;`piF8�\*"S�[ei.YW[I-aeӻ/2vMF\{�5f iIko*N .FI~~/ak*ETYvM5{8TKkQtdC|t%8L7+`l� ^QTR^O%gZ�5:�f}o]񖟤|0 s/}6~Hb,xzNW,u=gR_ 3|n% ʫjFpri?vsL-zehJќӭC C3/f'"En;0:RbS3T˥zln 2p%cqS_$oR$nQt%]WVYX@#)%(()FQi&kF5ZQ%(N2'FIFQvqvjIj;YIi K|Ay&qxv|B즺AB݂׼FmKm>gcZ<7閺>+xGMA9)ԕ5xsYoӍOgV"gO Jj"t?xG.(1<H-օ<߉1T'Zj˖O/YfMU/mK*u1x2~�(cڷ5{L�|WKs;c_ xsÉCʤ$ᙿϤ"u0!J8,-ƜgKsiEkῢ,g899oNGmWfY^1͓x ONNu?Ԥkom-ٗf q[ tWoj5/GŜ*WϰۛenTpxԬ_}pӥWe˘d9eJ6L6c_1ZNeʜo�`합 ㏅>!E-?<6Zmo\i}hfӵk{NiȼRq&2욭eZ5p.ͩWEV)ՄYΜ*PF_̼</6b1+>˃36]h<<kUY}U:UR*aUFpJYo4O+ �Wtg�dm�yM3ȿ6?�I0(� �(� �(� �(� �(� �(� �(� �(� �(� �(�ǯ� �(� �(� �(� �(� �(� �(� �(� �(� �(� "X%hd%X)c`C$2:A�uiRJ ֡ZVVJ*pJ9 BNdv.Z*SFV8UVN* EBpRԣ$i=9ºg<GZ4;u*TUm 7ѻ ƭ[q]}ֵi8ٯpW<ϳ8R^#W¹,Jdٞ'6<-?<84?Sx_-!̰w.7U!F|QOBdtON2W'uK |(sq)ς|}4jd]MbFm .buV"F;t>N\? a<VSzƭIV.(¾/#dO W:1Woէϋ24gTTd}84J]:pFy)Pg'7 হ՞+H?u�:lfkn s}8l/s Cre0CGF8x�^uȡTx9"~Ox2XOa2nR~x9iWN+8>OI_;SEou j.W=.Hm/mA VFAlyKVM]ږkù9+&y~#CE?~d?x3*?uYjeWqЍSx\}OZ>�l؏O�-2?�g?~Ȯ)�pk_>�᳸ulbՓK>RT,+Xx^ } +n9GXv cnβRju<j4nUY}+<6 8<GC2%sLS)pM{Q�?+?_Sx��,?owS<'�^ �;�?�tN9?Cq�?IO%x/V�s�º��;*�� ��w}%?SY�GGǟ�|@~<9ۍvMJ[_]"4o*4)7OyBtZ}<~0}E>'q7zWG8rx 1>RnReؙA{#jH.x.=cW8\n*wa귋a5n �~4؋(�Qx�g%��JG/�Ϳg_P~_ 'x&ks>6lj\ƾЇȈj[& . q݊wVb;%_y* Y5j0�-yziż[jKi_ejOW5oī kO6V_cUFnbug{ПC9~�-g[]Z? l!$_kt"[t:l'Uxo.9 �C™x낱T%g*狝9p'4q3wY*)ԟ2eMI#Lx.V5%F8zSU;˪^kR^lTyI$I9$':�ĭ-]n[շ?a?go ϟx g{B~r}ytu_~+KqxoaxrÖV,�q>8<=;{ |�EA$xV8GL|\r:TG^J_ø37/m3U� b}@h[l*5٧۵h|EoE9EIگ 񸯬q�s{Ϛ]Nfxe&>;ߔ3nx]�)XL?xߗNXڔVyZ R/cc OcӴ 6Fо.|Nt}6Ӵ'+Ӵ h�X;!${*o cqqX^*JNKV/ZpҩRr)R}|W8˰09^ax>"\=(4a)ҥN=!N? /x(=x]<I_M^:{jb◍m-֣qyp--ൃ͙xb=F |[]0ta{,& G 8s)SС tVs>XiSwZ̳ 3lf/f8eoeŹ^VOgF:T.JP8O/)�^&^:%t6l>�[voi \\K,pi$Myx~(feùaLV;(xuMN|=JP R|b>i9?SYV%K٦ JUx{-UaTԩ9T9Jr5&H|ET}KŚ4)m�Ś爴xDu5;RK+[Wpx{ `q^#(eYjsa)юڥI9T&*iB<؎*^gα|Ib ,m3 eVQTOe^)UTUi'RlcdVg/TSR5/ GN>!xǺݕέ%եռAsm<rC<.Jߟ#8[0?q-zdy]*+RqZXac:ui1:g J-4|ǘ8bۋXLW~&q_jʝj5TҩB%(N.-\=㖥i{eĶW 絻xk{]%TI#uuVqЯJz֣ZJ*)U5(TRp'FQM"|/|{֫/k*NzxꔫQMƥ*F3R8E$��ri�˚�Y�~ �C�� �$x�ESq$I$NI<OROrM}BVh.ŷ}^C?FGMM"�}WzίO6ý4K)MWgRn/Wу�JqQ+*Xz4TjW˲rT,ƼdV\V�HaxQlV"3(5!xhPf\O^keXYPC2R9= u�w_={L<O @t]:.,S,K-2-6msiw_#zOߋ8V`Urz!cik^9SpFqXSFU}FsD>}[B_gum0Ԫu1U*xr XQuq<N>0|Z!~'B5%3>W/5.�dTWgXx\b2,?*„i[ڗN1q̱WĘSu%gy|_;JLTV$/&O4<;㿍/c�Κ[5xcŷ Eo|Cvj'nmfӵ[%l{.x�^E>q4r(ffYJ409Y qxXb0�k,4)S%էSO9o g&Ϫ,Gg⣶_27s*T㌝zl]\VV�H߳j/ltԛ�xb$kAH<q;i&ڬ6I5KXί'׏1!_8WZMѓq5h *юr</x]g0E GeX$O/Tϰi9Nlp"<ԿZ�Oc/4ٳȗoE4�!V��W' �oS`(gUSqOm"~0֭Uu_XN&X5kMJ}QgtiOnHqwU+UO) ζ/sHU҆ |5tsH})q~8OBsxL4}sxѝUzVauTX/ۏ(s~u?h/-I5ia]�=>\X'Jt}x#+pj 2x\Ү#29U٧C+�pEdߒ=^6Nx_4-&pZ׵4 qp(� +v7 e5CMG`NiktiJZyz~;>ͰVyctVq4zjկZ1iͷCX~xDNi^%+.k&]?鶱[O[Ogicl?)gxX*;OTK U}_Чnz%?i[^HP~~l5:yngę^Jf>Vs\MI<5(t�r6)_�#^!{[MӼ9Zú4%]Ym\Qu#gQRṆyŘ49X6Y^q ,U0�w,r,Uzp ??I4=rl.",e5 Nr9Q<eWUcJpXZRJxzبT?6|8E?ޯo:}ON\cԬeV}S_,l_9x߆L~ppu+4ՔV8VToԧV8$~x\3d<_Yf2EWaljY))8<>2 ]**v9E?_~ֿnnRuŏ�^[=KH5(n]v Ylum Um{WS*8k:Sկ'RxyRczթJSRW3)Uf�F<VlԸSq 4xVg4pFcNzuiÖ<vSட{֙'šjievu `mZZٛTԵ $/ч1S2:VTVI79aeSWa~/ ~!ax( VaZ4<GpJጆ+ٿrF0x;`Foc*WXC5^c3bBAkHʈYq53 `]3:҄/kԨAk:HZἃ<ApWOf9iʰqiE<<*Thu=jݧJ+F2g�᷀?f߃DOÏ M%~ ?\![FޭpJAM刭4�!8?θ9T> NWVqr?ӡO0׿%9T\#=e=Xrʾ3KPYm*ؚXc*iN2TiF!_�M5|o񆷣|(,|/{ux'rXud}L#OcyoXY:S�}K2&/rMzTcj4i캴3e:Ж=%Nxa*ԥBᇇ_?ңx:`xC9̸KP˨e5并kBٶa<d^*ΤrhhPhV&'񇄿i#~7|Qҵ.>ӑ]~^Qҵ +U徧gwo/dO|p\!ø�F jq�ibp_fxzԧ(2o<Je<~QǜYSts*w&'[ OJj?_'qU߉QCOyĶv=MoaqKm>$K}/WӯlA =GHk7Jj~bA*O6 Uɳ ^NU1ji֞KQV*u*BGeCUs'T0A)a<&3 Bcl;&' TjUa2?h6W<�WOSN�Ϳ#/8_f)c.l)`P@��P@��P@��P@��P@��P@��P@��P@��P@��P@ǯ� �(� �(� �(� �(� �(� �(� �(� �(� �(�,^ivuͅO-ݭ,)`xhX]Vfqgeٖ p;GaAӭaqCB%(UVqZmKeؼ6?/p8jx&7^Q*lMB Ԧե8Zi�ट| ⦇n 7 ( VE;j]ۨuU,WGWؼÜ~?<:p;զnxNV8d.O5a�IE|\zX\pX/2L<!F8»8(%=a'ex~.i:69~|8�~~==?i񵁵2/l<֖̼E�3|N�I~xw-/'5LbI]TΞ]ĴV 6gƼէ?+ÿ뎽4ssZܰx>3}W&}L&M,^Y)ޕm8Gmw? 0xb+m{J? j1=;_%626"cW}S4S+(jJ);*7kJT3f R{V,=\=Xµ N'y~tW}ٍ7[ R<=j)Ǖbu'^%,v_^zrJtjq? j''M~#|7y p÷XKs_P CL?,8w 9~M㫵G l>L.M,6g–MZ+ಟdo'|f!*ezن{ٶQWeHeZ e3\=*U+Q| S_\~6??GgP�__p�R? �dW�8 u�w3h˥XgspufF$2_,~da�~ >þeyqEG;K6kcpeyyR50zjקJj.IԌxũ7K\Č(LQFYbq709=7 R֚}SU?? �E(Dg)y8K��GgU�G$O�2=c?HS��Ȕ!' 3?�F? ? �E(D)y8K��A�G4Q�S�̇|�[G |2k%UִkCh[x-A,zkmyQUHo|t[o<[eo.Ur,,>(*Xԩ5sHb'N\Х(.YJ2?No1eg.3Tx.a-ɳn zuj{ZytǒKu#'xo�&][E_�CJ/\>^1�4 �o%¯8ǨmnW>DÍ'Lx5B﷿[L 6BB cY]>O,sax >:Tx<5|01vQSk?#<kagg Ԝ-%egR vi,[$A�KW.3o7G67׿i7 $I\ncg( +7ղ|kp֭(}YWΆ/S ӫX-Jq߬|zYm qT+q 4[ u9s7G3TJ).s?K??|6٦|IZ]:7| GmU7C\I ,9X��Ko 3_ 3̪SSjard8'*SϨ) = VcYJ U9ٳow;riU^n#0FֲJ*8ua46/)ŸKi_o3Wc/^4+WĒZڐ!~y>ǫG^j3|:�_o WɼUɝ, *O8)<6O=L\gK,ƌ?wY3o seS8F'GS f)jTi䘴 69[F28PP ��)�]&H�<[�g'?O�W �'W �\b_go³^�ſg%~3*^|_nȾ)�8/;i2*??�._G��w_V0Gi_�Pf_Y�s�7�߳o ᯄa�4֍kkw(F{�`Wq_?,=g8Kpuh)G% %*iM\�|-0/8ZJ<2hTX*X}i콦'[dVm(M=�nOV .4x\%/X�^hsE,>)#h+<~ĵa|lagJX̜% Q>&Q\NRoqF>,jas�˥Sx^?iUQ¦p ,%۔u/�DO�Ó�:?觯�!�I?(>��ğiKΑi tuSğnO mc}"|a1X,GW f8ޣ*U#Sma6:E&[f8^Cy�+օzR:OJqh_$Yk7WӞH)oؽddB4 $e|nBA:"x u,V`TZ2熬Q.Y+dvh�`jy]_j9 ;SЕ,E8BVIr%h5k 4SR$˦0Lw2۹#ў3Nq_>E 5*Q~?MX\M|<ו ݺkm?W�dKe+�+�VxKL�AgYo��Wijg�_)p?ǟx�ew�kcSW Ym?߶w-ݧt`` cPt[}'6ޣj*Eլ Hg {*|'s_SM&RkF?~=džeQ0ٶ~p5)jժҦI_҇��f?_ hC<ƗaK'4}%ѵ=b?5iv!K,&I,Qk<Ӈ2?s;c>&cVrx>Ǝ"k0xj^˖0+/�/'|YÜ ʨx ᣛNx~' QVtT=<V FKK�RHt�9? ??觯�!�I`Ģx�Dn�~�én$:�'�?OO¿?J��I2gg/~]h[Z_XB4M:Ku|߲QT�C׈\)xg:r*4gf4XVpyKsIE.)3DžocxÇieqUpRS<{Y1bp<|1xs.Hj/I�xjR<Su |`Ѡ|SM׼/{}@GqeJ*aB/f</޾ 1jQ)UGoy},}?ӛ+"?r4? eY,E>TG-;�?m_]?xw.&]PzAwmwy żGEJ%:w*OY?(%$9cjSJXT>f9pz˚9&YgW-Qrʮc }*ҧ?z=WnM6uG�A*wNai w�bM>9W%xMTKN/\Z,Gү+c<-IRx~֎m4k+A�z}�]�?U:cX�ɲ�]�:?xs?!3\p��A�5xtg�dm�yY�1K�QscO�# �(� �(� �(� �(� �(� �(� �(� �(� �(� �ǯ� �(� �(� �(� �(� �(� �(� �(� �(� �(� }#% DR6Œh졙㌾;R(98o:>Tk}d415=l4tՌyJLv# #NRi%sr?VEjZrգJRJtkN7&8.Z'wiZռ#/5 xEuVu-:KIeKtwXJJɸσ<g gT'ɳܯ3SB*S*„*ףJuN5+R՚dXxc3 RpYMz¥HP|^)V:5Fd(R%MÞ>|(׾|u?\Z}{2#qږ<]xk\EگH�d2DaԭXG <zkd~#4(eEK 9(WstT5Zx*'0җ^_,xuW0ȫԩSd'LO=$GMPpX�K>*>GNm(EA3izŅnyV+mJ-g8]_n<3ӎ_F%0|$.iƕ\VWV0՝P�P|fDž<FpOp;-EZYfo˳Դ]JXl Jx*^B?ϯMSCF A-0X[$QV(mm5u8,aV(lRUQ)gѳ3[<:e^cҡ1JUc1%|>vWZr<g/?]o2|,.GS2 QGխУTLގeGFTࣆGr#�Aڇ�(�?<'s�G'�~AԾ"�?9�R/wN_OE7t?x(�?a/:�A<S_ƿQO�x\$W)(�)�MGopr_.go�r|�I/5�Xg̗t:Wl46[GTӴ8F鮯og%t,qF3˸Wi`2\13LU,Yqych0 =LV/ZZTQHAN̻/ن*˰1yg %zn60\=%֥j!J S97;<-+ WM޹‘__` gx�|Xko 橁ɩ8<-i�fdl/vK+/ 2>Pጇפi3jX0ѯYGG~|Vh!I_]Gb 66u6,giIJm;~ӛ&uÙ_ d, W\f#0<elN3ׯb*έYVZp#CRߎx(."xM>'Ͱ9p8,6]Эz40ZNc.is�WZ��,Bd>+)e�g/?yxG| >IakVִK xLK9d- $+G#|~?ppOg1p8�Vfy*B3lUF U5iGp \s$,{Ù 8ʱk3֍z>֍J"=# YJ"ЭR8OgKE r?G>:XUXs3 RE!J�%Ŝ5y o\+\CVc11eQEΌЌ+S-ZR9^2j_ӜUy_puGKfYeJ˚X<[tۺXӬFՌ*FҌY<gkiU𦽪h7)Dm6[oovճVKyY2!>Q?q9΍KZ6`RxL]6*Xi1%��i|aC4l3EjغoRx|T)n50iTer/LOw�kU\}#�qo8�Y�h~? >)�ˈ_*�'_|7�s5ɟ Ͽ{O� 7{�'C7{=�õ~$ɻ"�Tx?xa�g�s�dS�%[ß�m|C�Agf~U[;JTm=*h Y:U[ui4귻 ӌrL�,*f]ZQ[F+eJG~�UsYo~ G7&eu 7:mS 0`<8+q<&6d>|.`߳RX]vokm;ӛr~;.MN Z^ Zt*Y:M2|H�φ?t�ĠqO?�!8� X??D$~)� �\�O �]��!�%#�>�ĠqO??|3N'��wx|Ak޿j[ToE 2I6(vS#ݖaerOagR7Q)Ҕ➩I_[=zocf:rOqI*pbj׍98PE֚3Zҿf"_ +[Ng�_ğ_g/�M?N�O;Wg�_)�XϏ?yM�;�ʇ51)�abTycVE7nѳ^13zῙ~O$ڍ.,ʪU$ũICAV?Ш~PoYCY(֏vB__%ʟ ⷈ</xKz?t++[_YK&e֩c+� G.8L,+ F2R:\hOQ杭hJӗs|"2q -У^{:xF*rJnlv~i�� �%?1�gO �]��!�%#�>�ĠqO??|3N'��?/9�%?1�a�Bq?e�>Mß <a}JO+뚎udm`e{:atl}_A}<7q.?2xl ]:xe>jь9`%{k}<yRp狜SrβT|2X~'V )T皬ZVVw/y </&񭝹nd|1y?zJRt'ҡEY۬^1gZ.& ^3ԧ$<Ч}q-?g}o,=o4^ڤw,W#Oڀ׫�EB5|9JQ|߲Q}JZ 'OjW^R31ry_YA�~C�dOx?l?WeΏL�W:~em?x+�M^*�3F^V}6R�"�\S$? �(� �(� �(� �(� �(� �(� �(� �(� �(�?ǯ� �(� �(� �(� �(� �(� �(� �(� �(� �(�<|C.][OT𝁐}z\r $17}s_Al:�:i燹^w/e<_i6sj':xj_`+f:3qup'1K Cx[3l hK=7,βTiB"Uaok)փ?Wm"<7k%�<9CaRjw:"]C}al$:&oje\yœxǒ)tl1n +J0as+`3MIӣvQ<RJLxs{I_M so<p1Edu̱9,1Tq <":FK#N2+\4m휏wDx$ԣJ: A�WGөN8U8TVN9)өNkxR$�8ӄNtBTSR8NPeiFQqdC* ��a^ gekk>*[dO׵)t$mfK[=G 4l"GǿJ_1=˲ y/'GYC :sR8LrQ9ƧԽ9ʔ)я{s 7F=[fPU(V3ƶ#*Sefͨ\UHG WX)&|/B<f;u ,<Ab9*6qنK`ҭNŜC嫗|3*ShÇ3R WRL�0�hyoJnNNק8SV>K%=ba�l؏Oj+#�)�+��R�_�õO/K ?9}>B� �ۇ�>�犿MOD?�p_��Xn'?sk6"*�?^�ҷQ?!o&a2}�U� o;Aad/gM>^-R>nxPOEo�x|tx/s [QTq6 O�"c#.-Jp<|1rSjT'U]~۟ |uMSJw<%kYLWW=ޥ[2)l;MP_CLHN8+o 31.cp9li2̷ ZjX�aj9}g/9rJ}4<]x?nydY\8Naj{<fLVcaR0Yb`>hp? ?ᪿi-��@(F/1^1@�D��,o�,jC__)/�(F/1^1�D��,o�,jC__)/�(F/1^1�D��,o�,?g?_� }+:_x[=GSԮS4=w;2I"NƓbm`X �/`ɳ~ 0\; pnYxh2?�cSy&qUGKbZQ :o+pcxg21~Ҭ^UMext&ҏ_S_|R�>fI$i/|1+)4t4n6!}~ʯ֟ lϚxihdOߟ q]lN247*.YN> Y[G)s&'d&nL9܏,>U*)FurF-s֫Md'�9?aO |KUR C[q,b)�8;[F2^1[i:|?(yDnh78Qn^)wqj/penO?�QȟteY_DX`I}ḋb<ö7$Ԛn82K|>YF^O?ϿexҞeՔe(u]d=Y8ii]c/eG!R8wv8UAf$�I+<FF)R(bܤLrI._~,O_ 9( d);(<GI$}IgPč$q<}uQ (�rN9򛀤<)5Nj8vR$m-YxOa)pGF1JRCI$m_+@ڧvOw0_%SN7lVh\.S^~]zʡ%A_䷌/[I✺t],6+2eZڬd0W^pVmOOsu{էdTiدnjbcFaGJ\K�gMkG5-a45[5UmWCPf%ǢhEU/�xN2*:y apmreO O֤OM tt&b})<4xwq28$ŨZ}c8˓\1Tp55\RJo �O|߇�xFw3xKŷ%tWy ɧpu.g[Q=_1y d 2)VOIɥꌥXʯ+J41^ƳPf9'd]g"UOVõJJ).RüDa*%,N *CO2�J?|6�SLo9�e ɞ>�6 K?m�;??q_韄?k|�Q`><�7,�S*,_O�??kO^/oOsL-`|gxyo/d$촭NMnd$ӌU)qw|O(8Zy_N 'ʫ:TpqZqReo׌ g8Dpf#W$*ѧ0rZ(a1x66۴#w[~_[~'&hcּA-睒8gm|AᵖwV-syvpYNjq5E9a08K1)e50XER,6"uSzJ*[Y8�2p#+^+NfX[*Q0oץu%*UZXlEEg5zݣt=c֭鷚6W>=iZ_X[ɇa6sas &R`)bU(0QJq:IS(0ܫ3,-|a<M9RaqxZ֧$ jT2:+Y�<Y}F +C4ėw׳!cY.o/.e/' |!1qr1uN(uJS:TUkU)RNdYg~A1qi0,9b++8EJje 8z0^HQ98/>xXG|9SzYl<[.,^{+Ibhy󜫈2.oٖ[᱘Z*z5TSpFcZHʝXBe ܇r^Qe՝fIүFkX|5)UUhb)J EB )N߰k/?j_cп'o k.-,T =*Mb^vƺ~3p^+ϋ25?kY ^YMz9)R�eӛrgpG<* VYVcVM*tyOR{Xc*=9E)Yh{1lj2xl"%~'Ḷ״>gg=*oG>+ q-^oc䋜q3&JbkԥVx�^0DEӋ>/s\S9|)JJTf<=*5:M7蚞jWZf_]隦} ^Z]X.nb UR+w(c01xZlUx=z3U)VZJ):QvdgCbpNBVʕ|6'RTЭJIJZ5a*u!$ŦMkc�(x7�|}"K|3Լ+kr.xĚ /Jf�u+˔p87Iqr<d3[3,>:x Ck:j7J/{?A~ͱ!O-lVQݤ,1ʱ8L&�ZX|Z q:_, U_SN�Ϳ#/?_f)c.l)`P@��P@��P@��P@��P@��P@��P@��P@��P@��P@ǯ� �(� �(� �(� �(� �(� �(� �(� �(� �(� fZ~]ai7֚}l;+㺳sG4N9YXtyvyY.o9~*eنzTVF7Q{-̱>c]|gcp]M`8LU ZuTҚ3Z'q+xO&OB;T-ܛ,m;Un4 fv.tmɵ�|s0'x aN2̳ 9NuMⲼ MxsW �zM o)ʸW-_*x8e7Ղ)c\UNL2ƷjUZXI4q8i*8^_9;Nw;;PgԹv.Ɠuky_\s}vPj =geoFڎ/0YNP$p&oR# .'\.[Kn_o/УO3W^6rB<D,Fobp̫9Jxm<.3M�Pk1aoڵ\/Gpt~`gSpS,L|BGozO6U\\<,w珍7yp6XKxQKG?YT#{A|%�x kî뉪Jm'lЕh@Mڥ vȞk8xI(u!x](Vf *yaBg j&c:ؼ >Fe_U1UiT1Ÿ�:i>qK|U,4rL j.2Zy.6:nQ+fxoө T=_>SּssjO eu/gYmE{l Ϩ]"X4د. 2}<GJ6Sd|ť cpu?p)J2`U4 (P0gy֥`m%9?G\㍱Vwxo3V6/37)&0 UhxZ'_k;N/޾T[DWF X-RZ[Gm%XE_�K#p0xwM`<& ֪ҕ|V"JXf*bukbkJUjR�.8=%0x3٦.KTb꺒*wqL5;RaRҌiRccN^:} ~?EW!Go��?N�B�ɩ|E�s�'�g_ǟ�L?^!om_P@\~_u��_xh'�HSxQ�a|S�7ߵOƏuoCJ/\[�(L^k?'_¯@>x~)j lP[U-,|xF6"!|%{n. N|*q*T,Eo r轧&w^cWZ3pOiQ/;I7zyNQS?|Z>o~hR>'R[j RVCiCxe*nw�VwR὞gv},6WRq^˰Ӧ䔩s+0\\rÓxxxqsӄļQO FSRr㩮NihW@�}�� tk۟'Cg/s"]bf@km c#nvM�+ߞg/ϼ- ֖OSxn}\0LMlRdxdmx�l}M�O1% W1J9"1|5T2w䧜bMj*�=NFX|67uC<׶(mGBWӠJﹺBw CLf+;ŸpOYow:gQɳLEY'Ʌqr?֯W< {c:zôaA{ӯusl8<؜]rrK?_-ߋ\,-KXZK*S̺!P(dk;xȟ˄+8œG-V1Z:WK8ҞW7$A9f< J8Ҿo97tЋP ZIƚ`{j?D_Mϋ4ψ yg[QtZFXs.5pu;Kmo,w kib_ ||<01F?ɪ獡N58WRT'-nzujF_b3'<ACO.0НY+<F ֨:ҥ M<dyQ/~>}Cmj-�nH5BHd �Pg#(,7Σs<6'T:8t]Уv�Hp~ qA3O<"y#*TqVɇ:4$|o}~ž&,T5#_R0Z>o۩+[t±\) ce,~jx7NѨm_kO)*Ya噆0oWo:Q^MǾ ><�x3GȎu6j|F "+kv<%͸33 υͲT]Z|~ QO I^ʴ,VT4?~ʸ.x܏btx \4}wPJ7G� ^jNi5} 7_-ezX_%% ) B?<,OE_2T1c*t ,֩֞_,C48R\p?s?g 3Zs'^u)ul.cҔh"iN\J>Uf9�:枚NiqǦ꺆�yktCN0+Yp\DjuiFm+de}mhh$$./䒔B)I-Z]�?dh^27_\|Weekme]" ,-5-% 9�xQ^ƇդRprW)zSRl y|K&G^?=T{1~v oBzV>Tg: Up K$iԦ0xz<o/�;}7x{_ZMak[KX$S ҵ[g�L:/ x;la14\UJmS N+ W3jSbpS�T�~ iU:x;StcqyF Ct(Ua1tNuBե h$Ko-|CB_ޑ ^W%rҀO重3b3=I ˱)&\e-"m/|Nx`%?s pF_kͲU䣢K/v_G? 5|NFS.cu=y"}[PLAi{\0jU.J~2,l/4J/ 2K '4Xoߡ4}�BS0Ux|/G)g+f#2XgWM)?-߉-ѿoXX~snui)tMGMm|�ٚګ\rGOed=b�qNl&7:S. (b&  t>WPOxY7Vsy \>p 4?5rK Krڎ86T_Ge�&6m�_7s8� �՞$�x<�=|-�l~�w?O�|y�oYT>YяB?&Oß|1߁|E{AFLjⷰF鐶<[;;oaԬe5K_ǿ7cDŽ3p2Y Tbj%B 'zӌiЅ�V>ILx2 PeY:)aF*X<TȳTңZgJa[SwWU?uTԼ1_xI-|f+xHU]~X>yYl-`O6׌73\?)&N\,K]9{|*-VΥ_j~ѫ<\<\;.)ψ2x”y(7V_W=+X\z T^6XzTY �i})YH'C:n>wymn6˛߹c/`bZi^&mcR֛Mmk⒖4ױLEE}b:.Oo?4?bXNlO7>-Et[-[ԚM-Mu6DKxǞ,pv1F9w T1?VhNuWVV%Mᯂ^sl0<,qp\j.T,.\haP|KJQ�~מ&/-Fxo4HKx㶿S\XL#[ el/oυׇ=+K4Jsi&Ju%mOZ΍P©)B7񛇼SpX*/ BiYz:ѥN.GpXj*"xt%\:Cgo�/~Cb*-8>urI4-KfY/Eec0/{<?a^_G�>?x7U8?7ʽܣckj,DauGV,Fp3qJ_{NwcGr,0F3| Jsq#2B0mԜ%ClD0T�4~~׎>JD]O~{/IQoq^WD&^hڝ/o |7�< .RmUcW)Np˱e099:wt(Ҕ)}/EVSQ^)b4jP̥F:YVN ԊqB(Oi׎>;x:<WlW|:;MR\@Y%x &O3O}_ :y?yv*P'bsZtu#֔TsSp?ɾ|3Squ`Sx<%jkRg<*2y)8??u?{?dhk^ Ҽ1iM|O %kHP'ubۤ:}ĶϹKaxL'7ꄱΆ_ьcIQt0 .~ZԼ9Ìχ|`f8<&\ksD*i1N2t:j{��A�5xR*џ9eM3ȿ6?�I0(� �(� �(� �(� �(� �(� �(� �(� �(� �(�ǯ� �(� �(� �(� �(� �(� �(� �(� �(� �(� �;mC|1;O MצAz?ip� #Np %=>�?~�FjqO|?kMիq#֭'yVbxrY δԧ6~�Wx�:x^'<RO()Q⌲$8L>}K0_J6MC-WԽ cH[ۘe�./| l;RNrsk0�-_F,F%W/I6 ř|ҿG,o1[+bV0ߴCգ2^'q/5&{{ᤴ+Z^yM{r;G4)F ì, Wrg<=>|^ˋ1j,GgYm;%.׆/'Vv?�?Wo :8C(rJ541im,.kAPu ZRo5=FV�Pf\\+qI]k/.reQ˲%  B? . x|=(ӥA_D ~aq4c3,VUx~&3/'JT9l]5㯄&O|;?X驨�fh,CMsO"ʛv鹍~u⏄g`IeڹO|vRr *?t^Ƨ7)ϖ<yωo/C>�!e?r<oX?TUJxgVnQ\~п<)ǿ/Ic&�)3[jVM:v{ko6 Ir7Ïѻ>08,ɡfVgⲼt~Fe f&~tB#g}+~C =x*Vଳ.aOù~aKp+Z*Ҝ/tw �('WŚG<�bxB{4S:nW.ص{;:6 \Lc 2D })~s\ynCO7ʾ[1fEXek {/eUΌNYqNSƼcq6G<UL3]Vx+-ĿfLv_[ౘ6aj{OiMFa8l�W~ּ⯉?ھ fYO6݁[&\|.I8;9\�r.4 ?x K!ögŸ'^}r?n⿦/;s9.(O[-�G0_[WGyw a19yѫ5wXֺ̧WSh^?Ҵ =/M D6z}vvs031؃$9_D5ª91i71q5q]g5]\N&Zc7h̯r̻&ʼJ,pTxßc`0<-.nrx|=*t7.XϚU$]F]_^"numcR+xC$ҋ{H'l6Aim CqƟ|!\;|1pg enᬳ ]D0x 5N'WqUlF3ZU1kթ98GiYq>aS6!19oե,^;QԭQaL5;Z8l% \5(†*08g�P;Pu WMQ/-u HZL61A<q ,-9nafZXܳ5b]9Qq*axZM7KRDni5{~?c9]2fv`CRnT^*>o`U+VY[ e#|q_C(@((>!xZwM5馚i=VoҮI^)&i4ѦYi|4<0H(4]p@ `(Q"p aF7QӡJ:իb*S]\F"u+רZJ9NRUzkMANI՚J JU$%NSF J:tЧF1<ƿ UxxD5/m̈ KYlnk;i$h$xۃ:2"q9FysLFIU>W N9:UiTJ'8?Y fL3\nM`&+Jz|p׻VXIӯ+ҔV:rOm# bidvI֯{u9uHwDфB-*8 VjKTܮ\ޜ_cp[&R_/8\$p fU5i۔2S}y0qh-Y/ KY<[xޯꚮ`:&XOn)=7wD_8,pb_\KT=Zn(G:4i|HG<V00^ a?oWa U*/mZ&z/Q<{�h_و]Y*Ė~7u+_xRk%iwϥΑCq]E1\HxQ!{:IƮ>?eC5թ̩\ܴ")S&9JRNχ7xW9ef)f9=jEJp'R0j0pЅ8U8F ?X_XT{K~xn5=O}TZ潦=Ӟ/H0E 0ՕZˈ)_%0,E+][_ _}5i<O+ʱXLD_Ycr�ô/.$ngbG\O'IGf".p�TB:4N8R qPo&{۹^\Mz؊筈RY1Vnu%iIE(JȭZ{ PA'ľ k#OAԦhmSzԦWˎk2X/:G#|'qQ⌃Qq^9RЌxiЋoPe$$ڋ_|w"#84ȝiFx>ke/+bc:j\c))}ݥ�c�l=>m\TE7efkcdaU_?Eo kL97*VKXܫke>kӇX(cՅnd-pT9ޭazB1<_ˣT]qqoJunH4#9RWb#Ñqp}ҋZUS '#7Gҟ*[ga*_S&%$p-,|#.gΥh|uus}ss{{s=]]M%č,73,+M+4Hř~N:4ѣNRiҥN1:tSBJ1RbI$Z\EZթ^zZiʭZjIΥZ&T69ɹJM6*Ϸ�~K^O CD|9/«�HNvKF/.۾y吳9gaf+4b1OPUYVW4){JT#~}&|o̟+2n7S`0f[�VGl 0jWTiZiT9J_%ƾ&�xƚϋ<]^!~ǧ3\^]}Jm<]ȱ;b_r/rKZ8,ׯ0 Jխ=j*Jהdz5|393,gaԭ[J8|-y F"V �#9v�F?kσ}� miV AkjM;kdݽoiA!S&#*W8ui1-jUJJYVGN\$JW}%e5]-MRwJ8+SK*4GݥF6(*|dl+EO:\ʻModrScgz.WU]IˉqnlތiG<>Z޵ JxC 48|I�3+3� |[h7?�KM%Idyšn%ũU^XO~w5 i.̿Ppy,^i(<̱c# z4BTQwxOQ-3<)R IeaTwVRnR޾(݅}iwzn{aX\Mg}cym"mwiunmso2,O ,R*PW*hh(aӝ*+SZ5Tե8)-peE՛a+akbipgF ԤJӚSR$tE�W?lmq} շֵ%@UƹOzkOQ%N ~gx܎Kȱܟچ ^Th }.m-<\/:4lˉrxRKݩUSVkX�li3߀mC,x–:~: Nă[:Xqrѓ}<(+W*Sg9j½:ޞƜA=O G;8z[ĸ5nXḇX{nakQKC/--mNj Z ;ŚԚņ6.׭5;[[BuwJ y1|>W60y*W#,Ҏ+aaA啰UpR\aBt\x<'+r<C9gXxlZX50VsCGR*biVU!%R0}G/O^O|^� oWחJ�3WN ;xcZ&|nW7O72쑲 xCf:~ o5yRZ~1xzTiuKGќSq+x.*㌡, V+}c*8r5g8I?)'A@��P@��P@��P@��P@��P@��P@��P@��P@��P@�ǯ� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �ǯ� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �ǯ� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �ǯ� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �ǯ� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �ǯ� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �ǯ� �(� �(� �(�;ǎ^O4mãxz Z )uVig-?NIܒ\@NZT�y9gS%slގ2^:[ Bu0)W8*TsnrIE){3"*%fo4e<f& 㱓J1 ե*iM)^_?~_jw~ƕ{_̖v66v,Xබ9&i([޿/7Э7l6JE|.* (ԫZZ\iҥJe:*J0"&?pD~?xaaplv'R4a)gZj:TSRQ"Җ?/uI4?؟|+E3ˣC#7&x">xdhBH26_fhr x3Sqy|+cpҝ7R*RrTVk@Xeߎ>dB'kQV<ԪK iV*Gބgcux߰�֖׈/|CýGo:ƂnhئrKgC[W0'W )2qvTNN#B\kG8Ǎ<28ޏ8_?Lv\4ɂXM K:~Ӗ;8__|廟M𯍼U:{!k]-% [y.䷵$+ùl߇,Pfy>Yb!B3Vbjƌg:(ԫ%N3RjRrwX|x$NL&M9V&P".q8J3:TҕiSU:T)8SZװx@�x7zߏ|[xj^ѤXRU/l"gA \F&B#-Uyi9j:8 qT\ .LEyF5N!9Z1hr,0<(癖)jjc3 M<./vZ+F9&O?پ ↵a+m<ŤQn%> {5[jΧ<V9G=xJ\x>XrgUx凟WwgS PUTìMjj9i}<,@Cܘ,⠪ѩ =2:t^&RKBzQw#ĽkEm_h-{:iڍUi4˫4ٞ]#Nmgͪ(S_\χpx4Φ.3-VURT1UhQ .&8 Nw.Y9?c&Zt5r0ü.*8:8Eϛ Z%<NI&x}a@��>uᏋZ�-3׌ONJŴbԦ[{h⺒\Ťj. .-$d fye ee_ EK:(iKVncˀ(|3,|I^PTTF]IR]__H|P@��P@��P@��P@k_GU):z>%i:rZI[#_jv५%eaTx3l<8/\4h:r8z,)r~v>~#1\{G#τ0XqYa(acBPu!1xjWjTy}}!!@��P@��Pe:N|J>tCPe�fit oZk]v;gek_kļ9񣌼9a2y'pX1j׍N*Lu\$[74gxP>0�<v{Wi9 ʗQӯ| iW̱S~|R##L:njn<y+g]u CE6[Il-=iAv`^k ݭN/=|h/qXLIù,* dsZS`SW :V>, ?yϜ8k~gxSXy-<q\XjYe}:|N1^O�]gF_i^Mԉ� F7z=ڠ$7N5fKK7N.!~ \G3E,,>#tb9Pq4jb0E#2 (>g)>1p\W%Ukh{Zse c^"8JKKJH?# ��,xwώtqkrDK;s7tԾoL ;F]K"ot$`e˰ ƬAթUQ:4(ժUqKIx_H<j[cͳ4Bpq *.8V!t(Svt??D'cਵ'Kz~&\nq%E+Ⱦ\l(CK;2X,V[,-f/N`qEMEGX6M, cW. X$%*OjR\rIN\zdž�P@��P@:|-v-CA?5{]2K<\ ~|#g}2r7ļa\C \Pb~iF'Q45.H^J1JKCq8dxa>=ǗJ1\wqCOP_?Oj^}Kx>K] MZ]\:Ɛjl\,<!!|A5\6Aa:&8Lڝ֌XN4jT麑){븟p&3<^OL$lLcO VuJ1<vMUPGFԔ| �(o��@hZnjnecPj,}^[+?&MK:~%Ep.7 8^Z~' C ƴKZ:pͿcþh%%/< G?vy[=<M*9e4#<eoBYR>_Y⒄&p>  �(� �(݅76b^ sw0 b<[dH탌VU+VTVtiTQ5Z()TaKKgú*Qu…kR0)S%8-cڋ{yx*g>)GxGJKk^ؖ. Q# {i/^闖WNkǕ<B1y^8jX\,g]aЩ3 ;[ՏMUJ/xeK®#x&rLp7,i᥊f'+ЮQ~lʂ �(�P_?Oj^}Kx>K] MZ]\:Ɛjl\,<!!|A5\6Aa:&8Lڝ֌XN4jT麑){븟p&3<^OL$lLcO VuJ1<vMUPGFԔ|g/SMݭukUkoLioAa5gY[^w+;:sĘժ`xjj?0Ĩ9Jt`VRp`SRO Ō�VFfeW2#KĪue*CKGCURR4M~�M3 tZM[w[J6}eoxj[8ceaAf<p",rG ngPy<p者aEU Tļ jY=7RxGXu}F9Dض%w/+mi ~s5=R0۴ P=ݵb#y#VCɖuxV3 ˱<VԦ>V WZ7t+UQw\A. 0x; Q2^]pS狍JUq8j2"zF&kzOiW'<Eq<ejR>73jmpm%"4VKF=KK79F&3)C<XO Z3RUc%VJVJIS/e ^3O<ҦYO$qy3՞-tg<uycI>ZЍیE~~*}s/역$[g:Q.<Beʾ(�i:>c7{)y7pK/x[Xxyi}Y/frTT5?kل<C0o/dT5T=_cMW|5~~(} [i#kZu<WF}:}_ŚWmMb�KAoEڴ3Mu�go8w<Dʼ=`qy<EarVm7% MAԗ焯V)nGG}x(Tc0e<*akgX<gӛahRtR#RKA WgPIOxzշ<&E֑"#Ȗ:t(DcEL6LL8p9V1. j."p% JF2uXY^AYe_ZX ҔӫW.q/$.NK�_<"?9Ѽ{�Ꚃ.գѴ$]?D7Zn._;+~(\⌓8~X 9)eXYc10ú1Sb}<V :qQN??qTrExRx01Qmʟ$As`ԣb �(� �(� �(� �(�?ǯ� �(� �(� �(� �ٓNK{��S37y�dg~/_ �gyiOĿ%�LgM6_�c�R~|s_wg�=c�U�M�:>{pL�O_ _?s{YѾi5[KRO XX^Aqǧįc>)&2<Oax/ɲ=aq5XhZl zg 4SOySSG>$xKӈs#KTs|}|n %c18 SaVN\ ad#e׼ ŷ>8IcC׼jMN4M6MTM;ZuM7Y }BXN<O6;]<_Oh{hNj4[ 806# S #7^JaK?xKp?3C\gxs2ļ<qj+a#K3 CK:qERa>?u_G Wi/Kd~<InihUIou(5]BWtn;k>Ű< Ĝ_ bqrJ>u*ӥZJ󩊡G*ZXUiaaig x>0.3 x`l&YJ3ϸVqFzakf,:U?c{|GH?dej2H#?Xv͵75 F>z>,xK>~UPb8*7Je+,4j0*R>^Cqi7-~'$,gqe/`ӛQ'aF"qXz0r, G A 0AAz+};OTSѫhkFC{ 7|w#\er� 4R7S-nes.[ؼC Yj:{*`w)p!µr,lxoӂY\ibї5&Zn$/yVÉmCpn ,G5fs*^)aVR=K a~WNOř�iv< (OfAipjDԼR~g3Z<6#,hxtcoTO 掬& 7u%�K 'ϋ8ΧW�TN9nSJJU2hЩ,Ib5g~?ex?W񎣬nD%]5MO x Zx~3=եNQզ<6h'G4o_yq_ympN ceu2Xe`ܱqTYn\)ac:ٸ>6xA>pWGsV;~kqX FSØ +UGgk'A{E(u~t> ~1ῆu&[w_hz]Zi/t_o]ݷyy#f;q?xxw!pX.B2ʝ\N3<S3 a}A֝'<M8JF ['_+8*9058գpG<M<]|bۯ<ڢ²):>^uGީ�[-/u?xQNdW+ԤѼ/{yKinڕ6")K/ңU\[Sg<mZ8l&xՈexx֣ѕJ*EgVRRQQ /^x,/_R$q3T'W*K Ti,N.<?֨r5O�K O$v:{ekW ^M4 6-I^9n]oO77"Xc IL+zhfͱmjkF#gQ7VRQ?O}BɳpFkϰYЧ3|=GR΍<$qq'V+8O|SoJxkǟ�OKox7VmcgOxMummNI1ϸ#,qO/ukO,BwSF\-lZiD}E7Jjqǃy^+e\16sbx2iqСK8eu0XuO ^caat1KK٪_tO_{o�_mUn� @v#ĐxbMKCo&mt' (/Cƞ<e\[e`q"K,tXU1U,-bqjG A)g_G/bqV%feUK2mP=<5&%:jz^˦̢KqF.5HA)`W 2᥌8b / 7tctƓ8T'k?jGU*Oe^xEVk *1UiQu9ahh}*~̞.~'wzU0e弗V~o%#Rytϴ}Y--oo-~  nyaJxUztus |:5?aJ+SSJ0VZ4o~1qu>/03ZUө r)S2J{j^ڣUK?SKX/of?>:?e\5ФU"F:Y5{mL,\N+\s[y{W_lRXi�f:�CG|u�g�ᷙ_ئ뼍ϖ'Eo G;3xe?nu"X g,I TMYnFkw^^}̑_ڸk;˯ؼ7)a8<Ы[ qc'pV*PAЫKGJ2Q{HQ ԡ~gqNYr BPuc*ԡO ^Rѭ(bhN +bo]=c8>$xMWQ;9/M>M�障om*KA(TX}_D3L'7O|CW%uC^:8<i*~֞Rt1`xч+ r,g>,v8F4'ѬmIaqs*3NLS2.YpK2 Oz#!K־&|9) >MdbtP^jWEhG 8|U'f,3ʧ9d4%쨧_LDN 1ucS ]EO~p+OŜ-q\:MqJ8{Z8|_zXZu_C*x8fP`7_[An"<0t:n=m_xVH2%Y29%B8xx_nU1T\ڶ2Y< TX5`q2++jA6aYvw0UL;)WikVN9噛N㇍ s5Q>ៀN x7�|C,6W -r-n,tZZ^勵\D?q s8Afy}N)f~Wթ\nZbsL$aV%ၣF%Sk()p_xY:L Knp*Up<n#&Ngb%KXSi|b̚Ԛ@Դkx?^;K�j77Km Czf#KX58⿎ Fw Rㄎ_e%V ~Xzg,>#̜:ʥYPZx6'9K4qX>qU)B|NYèbSqxLV#k*P•baBQ+2 �4~Wn/viok{֪:^pҵ]]Rip~kxSřx=fj~nwCU*xeOQJYW*8_V>GǞ>qNsˊi,GpS_ *T¶2Upʮף[Bptr0ybV<{[_?c/.L -Ծ"0of)Ҵ�NN4/5(-Y֭(p{<9GxVYSGq&ERqXNiSFRG:xJu<Go[Y~qNuS:Sq? q-: 8 5JX\h`ӥRXX|T0agwW?źuxz@j:=젗H]NTUރ{]XiR[ёG%Ͱ/t.wpUp9W*bs .qZqż=<6YV2Jϒup+si˗O�xof9cgY:,,V C< quqyn#Z*&fpZIʴx?dҿ~ȿq[O k1jVpjvom&,mIm=B {^/gM<'XIaҞ+,5Hԩ\#NOƦ9֌aN'8џ 51qcPqQ .8RJX$j+젪Rb)u*իN}/4O{įblE%RLotJgmevbmowj'ĿDXÎ쿄+Q\QVRPqzqNQ4 *ӥ>O3>><'xWq# C`.d҅Z<-՜dJxXa,T|-*Ԕ[|V_ @!_5m@}gO�risWZZ5 #S5_O�_< <[E|O|.yV;Ԇ?a)buc|^U!:_⇂<7X,qO8k?фsSU�eaS/8,^ѩko0_'w;�$$�nmt*9O|1t&4IL$8/?2\F#*:)�efu+2ĬB<\2ѣ8F_feb&;xY.$,3G/\UO3ȥNʣxgsN^xΤ8~#h_4_g'5xė6 |h"ԭ3o[&[Akc j;G{ɠ'ŵ?�֟#c\ TjORqW*֝*TZ򊔜Ս(Np{gl +3Z2f|KK)ZeK 4xV":4*֝:sğg�_o_5߉Zuz_<[;O>�k_hE&kϊ.l%+k2;Cq^q_ g { br7^ygkXRTuRjҔ*U|4*Ff#ANJ!3 d-j*kfpJujQB_#׿/xC 7sMQk9x̶7Kfڮ֝q<|XXjfnm%+'֫M-eAU4K.e ,E8B#Z|6'Sƽ_<nw}PL ͸[(;<=~G:[ VL5IlfO{y|_?cOOUi.~t^'n|˸bZnAyqayag^}Ms_=NjFq~A>*q'%iNQ8E9хz?XWF HS΅/< 7Ků|}W!˲pyW8TqNL=a`qXj}J (b+-})~a7⏂QOY|Iۅ?[t*K7tRmK[.,lu{MFPljfM/s#�VT0P^w;_\,F&5FYaZWt#|oŌ<G'ߐ`f8CSp҄~VLKYƌ1_ZbjaceR?7j |3"hYͫxĚWA{T0wMs<V^%{g?<F6᚜CXթ .SPa*B,<)U8ƍZt0n s)pS()Ч<fybΥ .R4VF.Vq`Jt*ha1>۽1�|H>?~1{{&iGoEk2}x>tTr$"f<slz��+.�YPjQx;_< ^Zwt?e䢲QG}WsS7XOᾢra3?WoMWӧM.8e-</j(t ]6Z(" i4ha1C{Au5~xṴjneb;eNt;BUp?΍ tѝ(TQxs19Qa^eùL{9)Ž; 'ӭ^|5zuQґ7 �(�Z�j[�)P�ZB촣�" �(�Z�j8R% ? <�ҏ|?oN5~93چ_t9%ӥԷbŚ%ma0;@G<7%/o:XLYqq81o6zYf}hW6Hur<\j`jβSR'ʲ_8g17qox!6SR`#V8c/8YβAU8B*~�V@ď?<u{[x\i4ru eޡ%$.}j Z+*d9dˈxˊ j:XJB8[JN*ac5jB?/qW>�pmFkc&XL%N)JZSeNRСu�f_'. )+|'u:-NHl$wt5Ėqc 2+;_s{�KxxW0ZsJy%z\q#Fe^925*#VX|~Օ:514VC/я}n*O&xZ5rXjSW02ե Vt!1Q +…UfVr߳G+wA:EߎMGMPS]f͌}͌RG;ln&W":ղ/ ׈%OXY&+/mC<<ٮ+ JKC WzT2g^ KFJxU|k~L/x*Ggⰴ1,]hK qyUq,$\4Ӈ$ _3~j!~2x-dXJ+km>sCmڕv7.6vWF/q\.#$QHa0abӝJgRpu+גtՒ"|QZ73r*5F;Y+*UZTm:ti^4ph?kYПʟʺW�iMPlu=1Η:tJ}C¶P$�e~'x;g)UEJ[͞r%O8:Q 5wS :)NQ<<Lyup1]IT#:rت˕OP.182Y=*UB~O~ußO o?�m4/<S=N饷lϨ@e%m_bi'o \̳"8eIBu**&!`SG ӥߋ~e/™_q4\ڭ+b!dtO3T0J.4a0>t8"Juj�Gڏ?,<-oj֚gմ;KGj^0ú%biO4ݰETd5hexL&019(aqX)<v ե_^kF^ǁ^�d^/pgxC+;FC[ƦWNZ%mN6vq߆K,> _2j�4OgXh <P#xb-o|#te^}j,ڴ�9|{r_c2Wf,NUq(攜aЌh{T)Nپ0~#q7 Uea:pdR/z8xsUE3~Ͽ<owԡW:>ckhZ[y?d}CGMyZw2 .;<4:YO ֫]NJ~PЭNogOӌ+*pS?|JLN8 1pKJʞ;}8N\?եSԕ'9y>څKyw}tWGmkm �$qF;_QG B'R4pjU+׫7hRFVH$|f p<-)baSW[^iQRݴ?UA$ȟ叅=cLj˯�-t4rko6zUķ1i/y<I]?|Jb)ᜇ SRxx11ZƗ<*SnJ'mZj/lo!/ÉgࣘV+WҞ+u*RXԧS`P53 ӟW-kmSDլW>)4Y!EY]Z]"EMoO?-ß84Ï2E,4JdO-ЧT: ,㊥Z2㈧:x:wB'ς'pS߅M^qp|(g6.IU*65r># VXZbK%1Zx⎣j~-i~&H:Tu` lݨ9�ɚ K|HKbrkYa?cW^\v_cCB?_Cpkخ48/S%U,tr=ycN['){χ?⎕gk�9uKcU�El#l_S<t"+aQ_Zy5j~*Y ٗL7KZoV>ՇBJ5IFqWQO_ Uc'đaЂȕ/x,4Li(֌/ӯ($,jx%¿v$ۏ'㯶v#4^�.5=b?Xq/g bc[o4TxڬsE)f<bJ`|9a%x|jX,}%1<nO?վ\^xW�~џ>!Oo&_PE�OE-- vmq ZSl`Ӓ(n/m�Ec3x,�q/aT1u=Ua%=2kNԯ:XңpS8Q??pq⏉MS<6kKm}t}cK Nձ8|=<,aN"RjKoCcc?= Y4_ֺL? 44EgƟޗotڈv� x?03 cqg Uԯ8A:ɸSYR_ bgIaeFHNpGfy'ayGu\vTpu09,3Pp,<Z!Z`Ֆ+#J ᷌GFqGcphaQm.{YRC Jo.koRʶ<!,/QžIC+؞2xXTSҔ}$I9(w gٯ3WY <fRԣRrN ћ]ѥNiR߰O=i_Y~]:g»K8ONq$WZ:0_RӞS _[/UO"¶y W‘WbxWVTiNF9yѩ([~}<(s7[6}GN%(U˱UPi}k,^' *8eXjuB=~?~?> oěcxa4UI׾mvK.Hѧ4Vl/!{߽lqG}x,vN*b^ҥyR'<V2bhbe5C/8$�x qU_幥xSac*ԥ},4kJ"q,-xRxk[ ޜ@$k��I$�>4^0{%j?X7O$y$m<Kl1'9xk�ׂu=DF~*_׉_OYŭ"]0׾#m|=o_8|@\!n%C�s6;׵2v*sUK/G<-�_|ix-/-|S<q<*ͥRU(1ܘz?OeO96[KL'h d`H  끜WJ}ZVM+z]\QvҾݕߝdsW[hڽ<=3QF AKծ䑖[+fH.n w|Q+- ,~? *õ k*-%ZΕ:t:1qE\fY R]*Х*PXmyI5 աIƝjիӧFL ß�~)m='?s[UO-;_j Z|RM΢C* ~OqoS,N+8%e\+b~LeL]j'l\تt:tjש,Eї_}`WcqpS^_G U|5:|,\puqN\E 5)iŒZ? �<E3^¯�fH<AR36 &K d֗/<Pd|9)| *ln9J87PsB *ҜҜJ?>,kxOfxx�Ɔ/.S<<&3ia>Z1yUq4+SZN_~ϟ > xC+~oAx῁ޛsgjQO7vwAxy.}[V{;n1x73E 37VTfҎ7 F5֡Fug*0HѢK <σ$Ǯ+2gxw~0e[ Z1*2v."Xz18PeKXx @� >|r_^ػI�ğ v_i>,u? :dwoqefֵFQKW_쬛83!˳~cKfp;<ގ!7U*:֡YJ χux<f/e5|Բ*ˈ¬Xx|fOGU(`0�4%N?቎M<QU|Ec?!񿋵iд؛[@ZIl,庲d {gz y/ _2?Pɲ;PƺrڍMQ^ެ)ըSJs̾ xCrL7efO3T]Jyv\BhAb1&'V'uVN3/|�]Ŷ�ϊmU<?G|x_iCxIJi7:E W֖,0Z>aſIL+8apU13QuԝYsbRѫ^\lF_e| C3<^qG-qMzy}Wdq8Bq0Ԧ 2hUsg|Qj$O!Kc^1aEg,O(n-n-5+;c!|@q,, d1Y}yg̰0XƗ~JF4\=ZS(Ԕ_|.|1fia:rƤV !Z5NuGB8Q+UOh_~j-4]gឍ+_xz$A>zlöRER-ķNO5g߉%+c0G)(biafQ ^3Uus єV0tI*jJs}$(x*gX;9Z'3ܾ:u0vWEa(BxyUUgY}oO /|o5x <A6ZKh"j6[M7zvgdWZYego,%ɼOg_x<5üIqxx ,ɰЩ3IbU*XjZ?iz#~^.Wœa|{y.?4|߈1z8XȨhPjؼN6?Z:x_π4ǿ|{k# Y̚Va.zR4ѴK6]Tմ{sIfn4[Xw2I^9x?Ō#a80>sJUzѼ3JaΞ"2JԜ!Wμ |6n=;85LnKx\4x< ^7 ^\(WSdoW$xR=~xO,'Nj'.Ja;xm$-FO.JvY-+ˇr53,d2ܛ+GEbqs*ƝYCipJeGGYՇA񋊫̩.QoJq,J%R*b5eMխJ 0b:8z�dwO�Ol~oƻ/W+[խ[6<Eج/ũK,6\\ۇ0oeu;`fg`Veԗ_qt^Ugc凌gVSt;#qVuA>sZ8l+|�Wj4)\qS:4qҫVw�~ m' xkHG'›}==~P}oZ6fͨ4s Iy<OY^_b3\ xWXl *:\NU141ԨCWMT<9? w5f%ʰU*`s^/1U,f1,&+.*᱔%@t&g� S?,~|$]fxw?do}÷)ϝ1qU�< KxX8SM3yLd^dC)K1<6?7Snq8 ,?fRc, /}YpyOV쿝? wc/?u˟ \]cuy\x[Ql浸MP:n/}vm?GRi>qft!J5RO2¬&RY*8`gFsJV?>U~3xyf|,['U<=jZ>+.RZiՄ2my<Ej x,s(b)Sn? �ӥĖ^jzlDԞ2A}6>vzD3Ƽ)8Xbk>˳hJjΎVM]^]ԏ0<mTYR_1RwxΗ^=퇪|*_%D|M ?,' {i5Sk__^K)'Yd̋#l/q>WO;Iχ)!+K5(׆*jpF%R09g'8<J *8qm\Ҧ3"|5L%Jxc15:uSrx�_t zLju/Xڭ~^׿gŁ,�e&"$؅3 _]<Na`0�j~.~X{Z)畟,y읽<,̳L/fxW~7żfSԩ(O<]s~K�›�DG'�MR)�E?-�E#��zׇZW4}SB }7YR׳Boq3VS_˙v7},67 >1J4&!5 gR'(ݒi٦ /A> 32e>)Ra10N2N D)Պ$iFJJkׇox@j牵/5~Uo`Ӵeo{bڿ3~*٦:px~2Q"GNR[t?rnMі#1ͱ<_S+_Z NOU~?߇>[8>XS+O ^K"]oK"F9}2'ox2E>3x~u4pӋSфo'_&Z7g.ŏb)>b MU^o0ʫOp,̫ԝCnќ~~ B|Lkj^fV_ׇJr'{=2 6UB2tO <q|9NjxN|+^9&ax`hJ.V-T?ϯ8^xŜ)rfU1y3wjy&d;+ң -iOA2}q><LCxNoxw Z{MPմMf}V,R;n 0$Y%ǂ8spu1~4®/)ԣ^tU,uw*PO|MJ<2̳,q3ɱ\uLR c5,e*xblZB~~ʿ}C(I^$'ƥ]bV qŭTF$efF+o/r(|s\]}_eJfZY)ɶ88/kksk51i|5K'=N68 ? �(� �(� �(� �(ǯ� �(� �(� �(� �ٓNK{��S37y�dg~/_ �gyiOĿ%�LgM6_�c�R~|s_wg�=c�U�M�:>{pLyfo_ىfo�Y,|_I9$I9'\)+%IlVEޟqͿ|g mx?۳,<gXÿ /rBm\Ek?wƟ"\VrkDyz[#<'/zq0PRzCX*nE*pVЊG_T?EߴJ|25؋Kȼ ksgv[jqϧ0$++�K:$Z5[6(gO'[ѣ<8p.Z7a BjT0q?_B RX0r<'b15%Z{WtqkS#)9o[iEqKj>Lӵ2L油k[[M:VuӬ煅ʪK#+"dk }F8>a q8>&' jTE'OV׳ںӗ <C)Sr<NyVWR UbPV(ңRUm7}ψZx�G5OxQ>/۝+A?ë́3yRnMn[!0My?0N]{9J\OR,cJ?q'cri\V[i*Pcp~֕}^tKWݮ{�1�'w�k|j�U_!�tҾ?{<5��U:(? xW�U�?ɟ�{ғO׈�7XI�ڿI?=_x�eE?^qa7үMWџp0՘4?bv[Nb;K/<ZJ`7<,K&'eu$,>ͥ~]Ɩ�T<+WѵVmlvKZ=xkΉ'To]еkY,=6K|k1OGg]Og}2Y\¹ !t+ᱴ! pX-U[:H)'J'*rZ*ќR?xSWXj82EJf9v6bNQJ%XѡZ E:s?-b TxPoW)uH/qM^wP]ޓeiq}4>d%^[? UExgS$pQBL YX*t(7UFʲھ)?D�sLNe|#Sѧʙ6bkv`P*r 2RiQuLo|c/?[&jV:f^SCũhtm*-WQ+|?_8ᬪGZgyTp|=l.cqJzTa:uTHJtkB~w|ia.9KaLNt*eբIƔ'N'R"??��L�&|X�%oK?<�q_g�?]� )+Km<'˼?ҥ?|o#dqk͹QL@$.lF5!5Fp1P ^:0|SI)4r}}W7-s7tjCݯJ8o`FyL1"zjYg+O/.gi߉ ߳m5/p#5Yrey � %៉/>QWù;^ҟXMV8gUPR\}>0EgxY|5`c Wߒ<%T^MsIO�j6Tyͦ|?Mvpɬ:T[w~t0| \V+=b)wçVnqԤBX~ux.U tg7(Sˇr\FQ,~+VPR)}�_&| |*#GfkX fhXchZfmŴE;> L�gaaUe}B⦽Ui$ҔkZ�bIp3,2taRXju b-S ̺,J%ee$2 AWҭ'5tiײ'J<WiPij.>3ֿ 4~1~k'VCXoF�)f23|϶n_oxM!}}8K«�fZ+4֋dGIa>O�pc=fuܛw:'JP7Í,B-ͤ[FH^5QWlPiHy%B"Dt?.!Xa.eNQ|LZI9yNo `aSJ[=N Ч7O 5N c�%|+Đ=X|abx5zNۅ S4r�ڬUl'=Nyeϲ.g<ܶx>|*߶(NJ8�(pq_597&`�zxIAS0VHN3ZvU&JRRC vb:IkEe FRA$W_)?O U&i5 >-,rvv|=$ѦI."y ֓Ȳ$G$$Dh綍"I^q39(ζIK(6p33WI=)RIw <:E[ J2TOE:Tjrƥ(J,_&ƥ]s_qi'IZԧьT^Y c;;>�_ �&_S)xb<|<+SF+Qu1iF)beU$cU1^!q^2cigG*x>*,6rx Ji\6ܥ{ǟ'B4i|R&<_£ǚE6Mu-n)5[og; �Z.CN4FYR! 09,-EVRa!RN\ԒyO? <=q~&x dT9sf2),^:tu*�C_+zw/�Z�A _iX�5Z3~n95/ڜJ:+:A7q?̊v a27un\T8!M8JY*I8eYڭRͥJͨhwU,uE9F󼒞.6ҕWVnisJGY k?<yKq5;g}1o9s 0tp)aaNxg5:QT38n'0̼a?ά0>xS*Js+�f��>##}B_ښ|8"_˦RPȱ]]C5qgn cwW|q|UTr:;b!bV~CpӃ*s\\ܥZmTZ<~=gxqp#13|Zjq ,e(ONJBt04Ц(_NK$"_R95~fJx6_6TT˜ cj2E+oRd2�&}<DW͡С75zθréA5_OEOeOѳ9N&aөVptpu*ӏV&|Frs??|5V� 3_G8Z734TpEYWel/?URTYFJpSMIi9't?_OqkzOgVxjFT:tܢE8~�:5<A_,cuuGE A.%WKN� qyv ŵJ/�apT'yЕ85R Jʍ>imSRY*Euu_;Ԝ</[ L-O8F4*O0zJڨ:_v�@T�s~Ě&_[Mip-ľ)S�gV,@Woderڑ+iFs y*SϳVLgs4)a0gVNug1sհGtԡJ �I~�VW�Sx�dO=Dx�(Y@?__"G�Sx�dO=Agg?ZQ��V'Z8Rׁ�ꃌ|H��?,ǿo�~.إ=gq}rɒOD/l-. ZiLef3kd<CÙ2ZҭcS*֥^4qէk*Ǝ&Uq+B5/Wxz\S�a _eahV毇Sj&+9QWc0~zM߇�`m.&GmtJ?Eh!gc}Ų:.�WOQ܂tjESXB5x:1i^d+V*QN?[ɼ&#r^#RӡVr`qPʩL�C էJ3R񯃿o>#5Ã~g5ծ4kVJڣpuXK[<ڴ3Dg\'ͼf>0�VylhPXZqWpu!QN5(FTFpR5eYGPˈr XTU&G#*5IƭBpa3AO�pƯn}%Et:|D6p$o̓Tt|E*E᜔eCk 8d1Wti]' I;/szx*M^#XN<{L g(m:O8{E(PXfRq和큨kQC\;ߧا#_f,-8T[XX"/~a3*x%AGZ\QRpq8ٸRxʸտk)9Jwm\U%>nrb0}8Vn<dSQQ?@c>�tziu}ODYYGo LH|MGL/ ʲ.T~-8a~ 1̫ 8%/sNQf+V䋊v5T~<Ƥ0񸼽NNTR(b)^XLM\e+9E3/*JYr kxEa8eb:$EW)?VgIT^kO?"ͯ,jMI5iZu?7?gh�h#2:|g\J2C*2  )pI8˄84M<k[z}򯅲|MQn2$%{M5fzc*"ؾ)eUS'�.B0"&NNQ�}[~剶ͳe;..LpI#IkFUDCz.Oh>]k2,3\k:dL̋^i#:*3,_f&_c<c*J.qQlDcFMlsL,)ζex &ή;Sۊ^8nQQm6՛?S~%|e#¿~5|fs隶�OXx{Ð_X!=o\.?xH(x)b?2~ap8#X|ϖjt0P)Jv*t~x|OǼwǵLnepa̮&,=\Ng[yUx=&Yf ju%'k񟅼 /DW5MW[Mm~$|1mF;KB) ,$(q=413,Ya7U`|=kNTҡ7ZG9:vk٤-ҳgcy[ì>zM`GG0\UOV5h8zRT))*yJ$� }_I ��S>_Wx�dR�՞v3p��/�k_|(ϣO~4�S�'?H곃O ?ej5_K _D߅|y_kKg1[>3=ٗmevz^xx \;gk[fa ˲l$F=YµI}f)*ZTc+ti~{ោ9⧋<WeY>C_5 t+NV# aSJTVZ_,E`wρ?;>mŃMsicy&\Yk2Gsl Y#.񢗇' kF_`ƴt1NUPJHxa<;p\HasN&2े]^xʼn`sJsB2Ty)Ư,/)⟲:LVo#KɣF茿p,Uxt&Q$Y6V.x2wA˖\)qrMi�a'vڕwB3MQN<q1xʼn`c(p|LdRb'8'?kkRZV_z!Zn�3[pZUe5 x EEbZYJjUݺm/_1>/xS )MƆ8t)jRRNQ5c?R|50eu<#DB ijLLs*RA\7ScR8XRo5ST5 S PRn+ Ácet0JP5ՕgRZNStE qѼ '^7WXtBMfRuh�l|yx1yQhTMx\UzPa?%NYGB,<8MX[ib)JbbNLW^+2,Xgq\5~b�S| SJ[9k omi>(ihZ [xR_kF+OFw8yW':\QV:r#uTkNIt+a`T!Fu~<;8/YL�x Nfty^]i5%PToS,JNRԡ9ʽlL)~cWq}#�m/Ï~'ĿkK k5{eEM/Kao%s\NYW<AX�fVURVPEыZ:Q?s+|L8>~?x<ڻAU **~?p|V"q8S=G~vx{u* ?tm:;umm#oś\snBe~07|F/ ̲f/xqѫNSK ʄZjʎ6TzX. Hs ,ɼ[LEO 8 -zRTq4h}7v#�~>[xd3$J j|}.dA-ٓUF@/ E)Vf2]E_]Fkne7Ak <"X^JN(UgIt�]!K>أ-Sm>Tn:V`4jJ^5*y~,K7BU*lN:QR3V]lJpKeXn[N2~Fw{(|QW!E;���Mk� ]�"짭�(}m'|u#�m/Ï~'ĿkK k5{eEM/Kao[s\NYW<AX�fVURVPEыZ:Q? +|L8>~?x<ڻAU **~?p|V"q8S=G~vx{u* ?tm:;umm#oś\snBe~07|F/ ̲f/xqѫNSK ʄZjʎ6TzX. Hs ,ɼ[LEO 8 -zRTq4h}7v#�଀/Q� ~ � #' 2rp��_ޜM%|}5~_QIxZĪ�rK[GWN{A1� u}�[_᳇�'oqy�`f'hK\` $* /ݵ|SM|WO˃1.=vv{.�~0? to691|RAIdQ,0c1x358Y-%xe5^x-> [.hjg?O~ʿmc�ho^y-%7)yLWV9VPye>M>\ 9UYKBqq*n& RN5UB=:;WS͞WO<CT질ʴ+GfQM*zz4CM��?gO ǃobZ+ZM̉z+}6m},zeӪFߓc8q dx*YmVlQUbFr)ORFaEh8gG]b# <'R2PWYb!Ɩ>+Jpi(?gO?~,R]~I>{Vy:nOd S=խŸ\5|-)q0p3bTtx|F(קV J7:uZIҜ&3|_ip+f9\GB?S`kRU8./yaШ uU:'(UJ:/�I�_ �$ֿQ_}ʜ9C�G�dV;�U~;W8z�j=#ՠH'Osakk)ip)]J9o:3 �񹴸482ˆz#ϛ Zץzk4ԗ%).WW _Ĭ,pYOŹt`c\=uСFP=hE_W[㿆l ŸpgYxMӼGuC bմǒ ܺIwo89Yf<)'9upM\F_OU>Z֨ZVp95~Jz%/>cxЎS\e G UEcpxYYVIsMOCr|OZj HVH5M�\"k}!z6:UGOHHrx<[[9j|# ˛_b`^QLRrLUxn#npOC~|GpNFhY/00hN, p9iR 0>6|G�#߅?7Oi:NvG-jtkt{079PqGĸ9|3Sbrj4UUYeWS# ž[�_,>?Uqwʵ?ay:4ڟ獽+oz7�EGK�y�%�&?Ͽ<�E~��I�_<��'W\v�QL4Iqب-lb1YChѥ+HZBߍp d|?Yಬ.EZ <8\<WVn*jRrJUch|k~ ?֞*0αKSa00<Ua`F(JrX�S y? o>xzŗZl%!Ӽ91ozIQmg?YϤɸ IfiҡAJxiR{B*%8ź*QGC &WSIYW9J0qI5iN*Lݞ'�_|`gK OŞ-1LxsN=L6Ht{*رEq_1Å8G%:^q|6WWʧ,~"TB1>�u\o_O ؼ:ᢳ~H+ӫb.H퇌Z3 |U^񆧥Z.]^/Z7Sy#N$>+&W2&"=cGqN^[ҩa†# ^^Y<E\yeu (˜";0xC|,V[:u18\^N+ G5ᛛL</<7A?4� k^t=oXOjڽZ[cH&XR8!(EE K8ju|,3W<.iu%St(Rsw&U_ѣ ||:9:<SҎ3KNTiG0iExԬ1PSQIZ?Bռ)źρ|9/<k</=亄qu$v%52@4)M#Zop%ya7`ps,B8e:q|Th0%ZsP {(?xf<Ccw+Ib,^#(*W&yn_VKRu'ZXzN4IR-ܛw?Va0Mg�U_^4?Ƙ?_`쏣?�fqZA@��P@��P@��P@��Pǯ� �(� �(� �(� �O^$|I{ T{M{Q=4]źF^ 믲?쭮.x&65nU?3/{|f7*a}yӣOV{J)҇75I Rw2/8;kW+džs\W/ΰXec[[ԩRZ/%*s�q.$DŽ}['\<3M=?`jiyfIw_~3Gĸ֖eb*a~rb918;玼�pLj>*IK_)ɰR0Oeճ<& g?wT# oNR'/�h|S")Ҥ5sm A隶|ǚY/K8' �y|/X;ʳ?mbpuȤYNW#+3o𷇞)a3O|q3YalN*U WS\ꃧ{ӗ_/>&xCn>34"z^=J�O5Vv�h_"."r,N]<;)f"0X<{,N/ЯORT GNV愥�f<یx5}c6{`k:b8jʽ:UJ5!?gZ:NR~?|$'~ <|/֛D6yoJ-[Gu=f}Ցd�𿁸|P[y78.3v#봪fT%|..+ za>\] <7+\Q'$_3 dpn"gf_<p7b퉣V6Rpg>A~�/$z.u ri:vjhM֛jzΙhFO-J� 8oPb{>$L­<5 u% 1kWa8bj'bpRbpqXOr` ^|lf+)J3᰸Te^ysPVUlà�3~Z牿:gcUӼ79t]WTk{}CVm#Gݜ3k7s&xߤ_bYw.WvUKqas,ʤ0PX_VPB塉xU֞]Nfs6w[e\;<3')Ttbq +P~_ '<N+XXyIe"HwH#i8UgVH,HTK7cIQNrQRJRPQrz)u%u9FSӌ)F\iŻERW)4)7w/|qg�xT𧅼ea뺯صG6:q&R]Ggi<͟3־2/$\3<|?9AV~j୬Ҽ <Q#Pɲ| qWbu>U(PQH;3~#x3?['gxQ&?m?[uKo* vP3^lax~7_ c ϰQͲc0 2c4sPJw^isr˖jp\iq? 㿴<cb|,V0Le.LF;Vr犔% Ow�O��c;o$>"WmǺwQ#K[_>cf@kaO4l\ \)~-fW cr<GײWp5KXMl71Yc)ЩU8T?M@\o�' _ڙpLٶ ;,00L>2O`*~B)o'a�|Yޡg?|;kxмAg#k:=mn^wѤxrOI++C){.K\>.6Ы+p峼/I">xG>u(M Waeٝ ?2M(<f!~dT?ioeϋx�é k:=Dža/o/Σ5ڛQiwzuͬW }Mo~k-u�+%%X<\3<+FzWŪMTxLM,EJu%MNtgƝ_eρ+� hq#+s5r|/^0ؗʲt; _ J(U䧈AWx_\j?" ߏ`<wOj7sj|1ޓ-M;Qzid_j?nJ�+W[/RKzPC`~8k_l=7jԽl$eSRt^+~g[Y>I<\JG:ueRȱuKF"ѪK۳-� G�Et_ <0j!SXwkuPJ6Z}7F;!1#%0"LS6ra5C JO.zFkʕʕ(U'~$ɣX VG)Y.DG7WaaεZΎ5j7_?|7OϬ:ŖCqo3a66GMΗܵڅm,4>G|_A.^]9|0%lf <%Zu>SS\?R^I3<E<,7qp^iSPxcNlը<6]Vsz<N KAΛeAZŶ{.[Ꚅ>4/o6w,z}IC-բ</Q⍁EOW62xxMT LBUq8Ҫ3Ԥ|ž?K/O R' ӎ:r9Sj*% BZq>�jyeZ*q]1p5L7wAgzZIse彵 _Pa1t ɱk23I|JIUU'ajэJkbcNtg'08ns<QStX7'*8/VTW*86'jX"- _/d/EzuI>�#3Jxb\"رiЕor.&L\K7.-~/yL3EbmHګTß��Crhİcxp33uo,a,/ݰU_cTquwm6>kgh iMSDѵԬ˩Am\ݥ {=.:kƳQ 0˜3C<x|_bBO ^*~ )UZ1j *p?&ƜooqOyO P`8;JV/+V/*֍)QSpVt%1~�.~^1Ú5ui^|cgf$nJi]-FM�Ik%t(/e8=lS1eu gլNUiѥZt#TTpUb!39 䙧ygᏉ^SpYn'39FNB18'ZXK V<i/ٷoO>E|T{u >wk-垗o7s\upiVZv}éj:p!xY?xLg =8MbiUz5q3҅?ңʮ*#:pP~*Ug~N#x<ua`SV<5zJX. J[B O_OU'No >n~+xX|a ÏmsGUKo}]F|;%$0_{Sn _70_^˰_Wºt"0[IrPujik|sĞ ^2ο1K/fZF&N>T,ALDiRI5͊?ҏ)7|cUKK,<1M \uV$Wt}kCqOm>hpV~< < qVr~(}{.{l2F˱xSչ+JNT~$gk p˳ ٶ]|cNp vԽTӝI4z\?`|#{o>)OVwGoEa.XVn̷+aeq[vê$CjZ|Ŀ >μ-2(>&<v;qX ^&2aM׭ ,jXfجIapؙ}e? Âgi3 ~G.xfl 8R/ XL.jUSѥb,f2O®_>B{*x^_7<Y[[4-ºZ^Z,Z柧אǨaoijR�ijZf8+ď!ϼN0<#+aKXԱN*ʍOl)bkL>!E7 9f<u\m\ea1v&˝T2=|.μ#S Jq*k+C =>?|$w? c|KNmCjY,lK6wjV8s�/l+GõO9}{.83ğձ88E{I|τ~$W x7xؚy]b'S'xX/Pυ~|G7~u]gNiAk{$R&sj+#Kcwq3_ڸ+s^ͩ~o(TiRQoHW<E 4kRd,8Csn|2:ic㰮iUy/ ZM9᱘yb<=zR'?OY/~*Ǿú}bNAa^i3޵[[0^iwZKΡᡨ 7=IOe^"pO<|q0%*EFt+ibEUΎ& tfYя<u48;qh�dViVӧE)pulLCJ4bS*^כ?o]4oW_扭^&è麦 o&co-݁,KvLV@i^Χ?`|\YxX,f.. T榱ձ1th5qR&th0xx7cV~,̳V"?Z3U鼷,VùF*^ {a)ׯr;Gg5W:,td^ൊ[ 'Y[]CM<4.gMN+=OL{;{O3 7�en!^Yʫb(Дe $N5XWR'psPUR6~ை^^ o(gfb!(\N'N0ʕZѧCJ:Ь7 3i?W?' G|7hj\}!xtլӡ帕>զ6R絼�S9'`)ya%JEНGNt3#/g1?WQU)NJpwx෋.�W x|kie=%Z\-:I՜Ul-I_pQ8O?xo �WG_ <Mp!Ù5FI.5Wn-9i7<äj> S,V$~Mf?H(ଛheX !as(F4 xт <z"?ܸ*%5'xyͪ<ψ8^EؙJ1*J5k'*)xe7)PQ(Ӈ?O?eR| 6u<G5^!{q4wj7\EKi!j7nroFhFg_xe$gXlN?:{ pp9Pe9:TzxFbq8V&gYτX?xI&YLÊJf|WR5#`J/m5jc8,% |__i?˟牼-m >1xvX[躕ڀM>}*4GCSEӵ++8K%xQG.̪gԽp>,%,mIrԭ֯PrV L>*6UgO8lf# WN8/QG*9] }*~ҖapJ~ѧSX>b�fĞ9{>ީ>tEkպlYk-Nmos~{ƚvM )Xlu 5u(BK_65FUiIbeR d<]? \I.c5yv+*2<.%u1\&3*nhV*pWF'/d]~/;,ngOyQ;~Pb.S>MkLb4R{O|T a2LjyFiN#50U(RTt tNJ5hJya)#்y|o,[>ɸ|'|S1,Gש*LEJiBaKGJW �%fI!Cx@s�k]|>Bp:ݷluዛCج7[*)b85O<vl}V-O*t%iYҥ -Qn]?Cvk>'we]$ 7S,%`iaԩկ:pK7o|S=7Gÿ;ry$Gcks_Z--ini~n x7uocwq>Fqj*TPe ؜N#tMS@�+g.ڙ'vYg uU:uNQF;FO`\j׎ V׮H'şok)]n^D_#NծOr"ŵKx,8β uO]N:8ʙJT�sg5)sOYч?2<cOO12ڏqqu+iexK6׳UX~C*�v}⧁"A>&Ғmu{rm|;V?mȋ׆r8q_ ,T%y*_SQ˱O6s^җ9>$Vqa̷:s;6<h]o Y^"Ͳ_/�u_WGS�x�<QLt/ `_iV3aMu'mi.<b 4g 8G q p[b0Xt}&QĘXRj^? Ot׵sPI@<IK5^ˍs~'f9vKO:Y'[FyM+au_g_JZ"~9Iz>%ͫE_[ψ|ZQ{{Kct[NPN:.a]%G^aę>(Gxv0<qWR*4J7 0񯅪+ Z%W?8#+&\>1R9N|E\<+,n"x\m1lUhR`?F{[^ <!iMy-Ō#S{S]-̓چ?9cy.#_M_NO]ž81 DaF֛ƞ:hR1RѸ? Y>q$%\nCe&' *PR(TEU)Σ*{)xVxA�o{g|3+^޼6爣a<+}xwJ42�Ux v%:j+%/ seuxNO%F3<dٝ nabpZ*sx<G2T*I<0$?baùNg[O fqSd+<b%N^FSR8Q~5oǿǞ#ψ> ՚ [Fռi"+=F-?^/˩iװZ]ŝPJdD)y>˂L8ªj L5oe^P|2(ԫIWN9 ?-xUOh'q6Ct)p8|5xaPf8 GaqxlEZnp޿E�_5�٫➭ei� MKĺ>w[e-JB$k+hm`Q;k ./Zτ:+U kV?3ocZ|mLh'V ӭNR?x32Rl�s xO+ful%*t>aIb% 40B*iU> xk $_ga~!H ņ/-KkKKE?Lt" ;J]$_EcoS C,.C h΍J ԩFl:x|>#K B|c\E\v#,R)W<Zc[bn*Tb:2Tף?JجM\.B]0|~I;~ş< ڇԳxYؗSKtm7u #}uSe9_>By;u$-G߶]{K?x+< <GxlM<.ͱ?,x F |Cwp|M{ğAxKox?@[.Ťh)ҵJP\iKkyvK+"7a%9ff<;0t=:^1X|=/kZtBҬN89pgx3ηղܧ8w31εopWRSѥVyiӜ`}!�'�w'|'j:^i`!tM<O2K5O )g,pٹ4S 3'(18;熼?SLq xcĜ!�k K,_ Tgb{ҡKzru>O5/9gt][+7]CzgW$&6`Hy7rpWʱ2oixƝi8][+kW CĘxr xGB9ZZZ|٭ _N�\~7�8E|uojZ[K_o�i:0*XA7WP鶶RhZuN<F  d\_Cf1zh}jn_k v"%7*0TOR(1%8>>7f8Oxx,Y}NGԥfZSQSWW G<F_"ӟ|Zj4i^'<G|I-񶫬执_xNtحR C%`ZexB8Ll6&wq} ,v]t|.Q)b0aS:Tj+ƶ"|Q߁ϣY57[m昜Êu[?c9u| +aXS1zaG C'/%_NM:uׄMfQi퍄7ΚV4k7+"Hsx?Sd, Zi}[^JT)J'^)Je8;\S|,~+g[EqxT+b]_L^UµBpj֋B(h߳WZ~?o~luy].;po�tM5S"Mw /UYIW 3W?V$0ؚ5aOؿbJ3߾IFs>𻇳 x^*'3_,˂j~^7)֧M٤͸_,?n5�x x~ڶдGcwk.ۋK.SW_²k�|3¸&n4cxn"qW̱4Q<[J$x< žkpwx{Õef)f8~x=|.c0([+ǀ�hٳ߱MzGxc{e=k%iŦikz4k�CԥJ_w"-x*8 !֣hQPIu:upp<�uq9>o*o߁0cs>e|φ.10R;5V T3 MUb2TʶJi/G !|OѮ!|Om:� {z# ȴ۟/B|)i];DH5׆y]Ӆ� } V],$o`xSa8,M*|e|֮,C)ԧG[֡~qg.-8?2\qXC ɗe|abaT3 5gQ|/0�8q/~>4oQkVph?oaմm[K/?W5xlV@`[YnxIcr^q>O]C- \NC MJF; RjHm^2w nᏡ<$}_fy'<%|6;ӡ+uhR:33�^1$>=*�Q᱖�O4 K{˛khcjhէLpǿ p|A ZsZy/לBz*:e/cVnt)f50�@N/nh7L*̨Щ%\`85(ap}liRUz5h'U|G~| 1i/Z?k~9*M> ^ ŭžonӮnoat}#J�4ifi⏈~m3XZ4=*էR=:tj)ң*kbxTC<`cpFK࿄lwcfC.&Ա]GB=ZxgZ|>lD0p,NO_ xS?R>5<1h~$�B6GKZ4^)|h#3<?C)>VE}Wzt*5,ڔo?G'ξ<1W<�l?a 5)}wbmWF6ߺN1K;}:M|CwGKMCġ(Լ[3&-jڰ:qKqg?ּ)}yg6_;6̏<)SfppYn6I1.;µ5OwZa3:4b"q1w:'?11y.p5K-1˪UL^PT،M+Rkkc6VZgg}wkioe[[$PjVm}6W*ݥJl=jj:hҩW Zt*VԜS Vu(ɺsj2[Vg/|e8|^+СF;OKNOB2N" U V"0j4)?y~?gGŽ*QMv~ΙחKXiiy5]$,uhڬiqmwaԶg?>8kq_ bh+fYM<DJ)VZPR3K F_!,,*R*qgѻ4✓pxWqUC)*)ԯ_P^t)N`as\†28^ld#Rl4*΋Wo%�rZ'ψ�&_ŭ/;hv-sӓz\["+by#߇uݬ>:/#)38Np9B؇::|ԣ*Tj(0#xuSqO&C`V3¬r5i+rV+b)JQl J c�� /_v>G#*nx^d~'4f[OF%Q+́Y%d|YSf?FYZYn7Upn595V.)~CcWdItxOc�38g|(f~UUj9TeUaFJrqR?�`yo>^Ob'vm;vKck0a,&l5k<j˨hzcRk/x[O¼Q]17au2ڥHS,퇞&0Uu+ Qz 7> <1>4gYa]ql/FuqqP᧘hEb|V/׿2O,><x_u<_|�!q-bx5xt uΓvbVm./>fa!o4�SNel#3=x r:RScXqU]l.."|MG s۾_7G%ᥙ?xWV'ppX U j#*Y:+ q~<G?G7Ư c:d^^i-bť6wwOx\բӷjŵ݄WRڵ�Ҿ8kq_ bh+fYM<DJ)VZPR3K F_!,,*R*qѻ4✓pxWqUC)*)ԯ_P^t)N`as\†28^ld#Rl4*΋Wo%�rZ'ψ�&_ŭ/;hv-sӓz\["+by#߇uݬ>:/#)38Np9B؇::|ԣ*Tj(0#xuSqO&C`V3¬r5i+rV+b)JQl Jo(oh/x|Ui^ D�xCIмipxBf[}v5/5]W/^⌙Vq#cc08+*~; N.*NbI8/8C3s` ,e˳0nT\R̗+ƲNjN \¿ 㿄g?xoLIbRk^1sc?:=]OmQCq]�G^ n(⼳+1N72_Vr<5:-(Gg:4N,<C�Es ?'N#e0q2f&|l~PYNHC_ '+o]Iû ũm'UkZ>M~זJ1> \6GeϭaaS2:X/T|Cݟ/ogĞ e.οp b2?ٶXʔxL 8-u0R\sA`? > ;Ҿ'x��4 `YmMkGaӶM7w,4G\ <ci̯Fno?eOabrJˏᧈ0xɅz9AI}I`nu ?e<R|Lv><='n ~> <(,9F^8%XŘO5 -fKv4o}6͸ </�eod'&2~ y{ѥVT+a+URfQJ�|]8qL <cB x 7 4/ ֣S bcjJb0e_ =1 ŚgP@n^{on[n,tbx2[d(--}V�2~qC><45V? _-3ꙶU)aⰔmRR6bxjYB_'G%ΰYf7|3K$)V/bEJu!*NnP$񔓔ϋl/iߎ~"i\hZ i|IA%K9V[dԵ JY^0^~~^qʶ#h,<VoiTxzөR1YRӧIC_<N^-qu\- .Y1SJtԫ[Zԥoµx7ş O^x~/{VW_<hlnz�eޛqi֐Wpv*fA-x_OxW-ƙ,t1\?԰BN5qj9&"J2tTvq3xSÍs.Oxr[ Y,/Tq9FT+`V<U%u81W ~ǿ~+_ǿ x,h^o`lm亴763 :S5M6Zѝ̶_FmK|Rɸ#a0եR0փj5pؼ6"G?"쇈|4%>dTZx^ 5XzRAX|.!3 O0Jx:U}8 �\=x~ η Fn'#j|<ybܝeIȒstJR�̣Mf6/DZ~UO{+8}e^9eyHE{x̪XޏD/m/S?m_><-7C֓5JŴ{]Bk^g&y5{-BUg-m sqcC2N,) a0XzVg쨪b9eJp!C Τ?ǿ8NQҍ<cɎqTpG:^aa9ש,n&+{* ]gh?:Gga?<s{k><_cfMf;h3sMt&Xq%๎/(>+"x\+d԰<l".C?,ʝ,M>z8C|N99|(lW577ψx50j Q΢܎ :mE �??xGn~!|%Ru䮲g,\_UnL~Yda*\?;R?'a4YA,lO[ c�܋5|8_w� �/�5֕m�Z7|/}ssi&Yh~MkjũxIᶺuYqgq6s_x; 个춪3*Np41*�kbUUS a*NṠľ>=kĮ>jS,,7[h2`(e?x7tpӎ+>8F)V?2k4L?MeO  /|w"7> _#]բ#x3kw6O<=}v%( k`,Lr;8^&p<�jb,aܾLWe,^+/X)Sa/B)>x68̸CW-Jty%ṛ1!NJQ?MFɁ@� dᏍ|Y㏉| `QܺvCif`߬_Fu^Ux;xR|7 ճgײ?ԩSe V")vPIەEg$W -_ g8r^�ZEӇְX F ͉ |N MJgCa@��P@��P@��P@��Pǯ� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(�r#/'?ৈ⟇zxoHcK!KZu̺Գ6cd5=B})5)v/nL>r-0pIq,(qҥPW'<0+S<4+9xGxc>2 2ำrpѓ*՞6i^X>q *4'.xc{߶7?|a/ " !|QŲRK+]6kVIqwq)ЭKkF]7DmSPV#x7{<s [!ἎGaQN"u(aKN&tⱵ1(Ў#!~9os? g]>*q7%Z+6PG N#*RY|T b2ֿ� �(� �(� �(� �(� �(� �(� �(� �(� �(� �ǯ� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(>2_2#BK/|M?/u[7:~tutM]./H^[EϜJ~7< 8#+s�\7¸~a̲5fyQ<&/QW K08RK'B0wq|/_d eY'Ys%X <FX 2tr6k V.!J<U\ (ʮ{tNPD(� �(� �(�^woxS�<C{^ ]L# 1CN$ƛF K"#q�ex/!_p1YeRpN;K RVjՄ!JQR8}OpOxx[,6&, W MiJѥJrVsrNU'KIlk kIkͤcKNYi i㹐ܩ3l$͏%VB81>(b鬛Q( x:+,6.FL-5UbJt#̫|lNUa3\FG[ Q\®UWAUq2O EeZ&Ҧҫ ҳ)Qrw_ F|wxo^VڃiwZvf_K2\L&tfIr~x*p�x;C6SU`/R<f*ꋫNJ(*q�'sՃc*agźVZ8ZYRJ[UpuiN|e}@K~.񿊵? KeoDޓ^喞4Ts[^h}iIicݵ7#x#n9gK*X�3wa vbL)1X1(կٕZu1~Z内*9!wqqFeUp^+d;xҾu,'63&`2U)"U)病!@��P@��P@��P@|χ>)hڟ4 +]mjMu _c/t~}d-�Ϥ[wy_fUaK9F+.W4eQ l<X\jjוLVX\:k*F>$xwGFeYo xay0TVe,EGXlN5Ӣ㱙m/cpquJ(xU@ÿ>Oῇ~Nkk66/kAټ1\]=Ѡ5yn 9'4|J_?8ȱy$֎#4½\> RT0؜EIBaqBG>ώUΫ7| s|N <-)PpJ*le|6lF8ʴjWW ᦆkyH'I!QbE,mHJ:0ܬ khWGNJz5)VV *EԧRSe)&Ljա^J5TjTҫNN)T$  IJ2M4h3 �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(ǯ� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(i5_[c<sk[m$2?Bfr](iI'}?|{8Y3VtÊ3 QLN;1c0G ҫ:P.h��GGrlVo>,q.nJP|98]YS`)bR>YIΧ$?ğO_'5i>%mY5λPuDϺVW:<sZH_~ gqpnq(p%)a0?iÙv[cj %|Lp?˱xQ̡aiʲ<ZW^/^U~q~g;9?0Sؚ<'ƭz|x|L'K}yҥ/�_5MkJ|itKkg޻w[IImme='W) TQ�ў=!xkyp\[G=cx}<pԱXnjOC+S+:%*P/<s ~ኹ6^n81Y- uLSSjdJIPc}POqskV~&Oτɢkh~*uMoV+k}6DL�Lq&(dX-&qktܓ%}Y.Se3L~}¦'5 `)sZ?M\ n$𳆲8W-*dپo<3|92̓ `lU<Vpcq%\uIB49ϣQ&^�~4^�5/F:fHiec{]v+G7Z,teqye3�+QgeYcSN53Hcq8^5\;,xWf Д(ю+|-+f<mq[cž!b^[3ʲYkWsQej`WRW� ez xw!$֒đj = 6SЧgyJJ:NvB|�Ux=>xx<;9 T9xfUzu8aYV+=a0ҖtK Nn<[_^2 p}�)T,ҷia,6K_0WjA>J/|5�ˬ⏆ &B|M F \KI}&Eӯ>oU #7=_(xS.;ʰT2�w!3Jlc3.`'W؏yE\D*,qT?gߌT&<]lu"\�0gPW8>Uɱ8>_?P!R8<^U)R>׌/xM];\9Ѡnl5->].p.)H^Aqo7~.p?xi,!̥W8(K yFm U SJ*ӝ^bpؚʞ(x_>csG/βN%Bq8 sib⢱X diҩ(RNp5f7|S᩼wׅ 8cZMKh&ّi}us7۵Vzcܤ֐Iu7}5<$Oh_ Ş V*x+üwѯ+ V3V,35C_/W/>'Õxގ'xN:|_yL,ʍuNң]KO ZXV|l'|城/o|/�_O~26.Ms+Ch\ZY\[-Ѵ0[.LVr_Žxn&pŜ &lMN 쪖S(aiB6WS 3ѩհX ѭOb!3<Jc|kp&6,<8s:[:8Hf0apUU*4xzx̱b,$W|.<us|}᛿[h w5R[+ImK 2_3S,O*G¿KrL;/r'7WTsL%/e<m:^ֽ\,`|?G7_I&)oR3:<v6,;3C+Kت2eITNEA 2콮c�C u&İ:y KO';'ƾ,ԍeCDz槓p!*U˱\>5M*<3;_FlG^<Pp0CmQE}} A\!^b~Э_b?~؝v5S�xoGw5Ė(}7P޹_qֿH<pYQ{ÉoCSkM0MV|9~3u4⿤?T^xܧ< \;a=T𸊘|]()P1=Nt1>z.~#~5-6ľ=\vEJC5đgZ\[-[Xiv"k(d7_E?4𿇞p ͱJU\$C f؊ةa04j0tpxyխ R3o1`W׈9|N+5%MJ?:UGf8ejXlYUquJG�q|Wdm4|E6|pm>+mxF-2h 4ImSBq67+9lx}xD$ia"sK/dbHe?H>qNa,SX,J7/ko<a|O~5"G}o۽"a N{tBqk[Y&+Ye7ܛNN�w8l|?lqsFWV&ሧFa1thX54jSmSqo3n<+)rJ:3GFF[ V֫B:~f_<{o^Þm֑xU}XԯbL^+Kۛ}/Cy|ag^C3GBϤ� xyGŘn'/<Z39^�2̿ ZmB7˰U3>Ib1/Ze`U*Cl\}8�:q=~cUK3Gk8M*f]*X\e|v2QdoҜbVp@_?gItY[.xfd|O˛.=^Y4{y-.`gk+ۦ[1G=ԳaQy¼Ul>yxna�Z' E,fGG#/M[dA.2~%^'чfcኧ aq4%9`ƆOOٳǩu|YYFY񗉮'+pkkw5A$sMiY\Z[[J7ҋ_,w~¸*yfj֞>U,V:С̱6/JW<<|~~'S0n e9q&.yG iю"xjbb1pRj\ V' jzǏa> ^-qS["Wѡ.{ X.tr-QH\6,_xyx'ߊx?D3JucUxʴX,2ʕ톎g<MZ4?B_8W^>+ կm(ʾa- VF>6*y:-T!Z,_cj>7xHGt9x[cŞ"IS.naTH٢~�J<~xv xqp2<,&7x_ ʲMsWIfO ֧:Х:ƍ_qhϲp&U�d\%<,GJ<=at3-N8N LMHRXSUvL:G%ς~֞Vڎ-61^]]^Ymgv^W <^3&Mθ8,9>yϞcf2 )өJT,T c/)qSľe.+e<'g|6aNcs\ ,Oq8ʙIF>MԼ2i�> j^/ttƑx?HL6>$W:nݰX `<- 郕32 , s|'18/ `[ c<+TJh7Elǀ1;|x,Kr~1{_9V[< -t1CXc|Nyj8,L U*_ ]q?T5-oIuXkȰ{;;ؚbѬJH}?wf+8;(G<!ʥJU}F;UʔqU)k/gW;>Bg|GeØYpmLVqγ8Ty2Zq8R8x:INvK jk{;M_ǟ5=J]x WexD 4AQ-] S0~q73qG嘩`nG)4jRs| x|N9rbɸ)�]gp<&m^p8x< qN)ӃMU#8d9h\%iO>&ѥ<eoXwG fO+f K-n^hg9f mp\ ƞPW9br\~}K6өIK+&Y:lThWp50x6/ lU kF?x~1<dr0,Y^t'̧Y|0ҫGa\VuiW֥V:旣vW>-U׿../q-χl $[^K^:ukk5�G]e|;Vk 9Oni,/:XTxڣ1 172ϼ?28i�bׯuGøL~3:1gX(5p)W?5O�&m +=6[uN"K=2Y#զn[Zw"[߈H ))C\֧ ]c2ܫIbx0LUYsJ45 *V*%syl8510!`m\ \a`)uV)ԥC 0S_�z|Zu� ]kð<Kڴ|g 1}{ <+{^GadwpO\MG N61Q,F+1Z#;ө�U;/: pb|E7l./+QUGlU|*0x|N&p/-4BKnX%eyt#FIYcD.Br/ن; ʰ:;2)a8L BXN;(СνlEIƕ:0IB-L;e+cqtp<ZV7Z8|>V^qJ8JJ8EI|iNo2m<c W<!ì\HʬlJ6oͪlsIn)xfj9i k8s!8g<g¼1FRhRl 4RӣJT�l-qcyuN3xKxG7RJ +O$C/MƝjdZ׀O9>%[:sh~2Ѧ\[L'X쁶�Aug$MKm@1~¿I;wpNf�¹flB^ug6"*L~_<g1N+.Xggs_~_$A֣]ʋ2L"Kd|'a*SL6;ۤ*�cn7˝τ>:>ARS5+WD/GB֭.<4O0_>-L�I>' x9ߌ#_?*f?g<)yuW䘜u8T0=,^T:Oo&㌷gxUcq dؼgbr̦$0LaÊgb#YG|gKv>^oSZnw_׺@y!x�%Ìx;[2:[)ogcqG05:%g_pGx8|K֛Ԧغ &1aF+UWG X|?~x;6o݉UAr�پTNmEa} oO2᧍Xn ,d3tS2paK<<Qt,GSK.HueA)dЌqK!sR#E{y|' ^!)*D{.w,ihDdHmho8n/,p`if9FmaJ״2JUiN3W,NJF&ZQnϸ7?͸[2VKxʸ ,Cҵ\ԥRl>"aXz8jhURX5'�P@��P@��P@��P@��P@��P@��P@��P@��P@��P@��P@�ǯ� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(S牵�ojw:%1{Ywx[Y]WW{NG.,>f_/�h/*gpl=+ܒyGلhI18pF:ՎeJ0`_IxCr OX<<1G3vUjqAGx<N"\?& zwoR|p?fkeu2gim4;^H+R7W(? >n*WG"8�2QK\=:񎒩6he!xWեyGuj4q MX?ʮ簣)BV|4xy|eE"�|Dе[aS ˃gQn R$#.p׈2>)I,=˥Ø Bڢt*B-J/g \a*T#1m%ɱϱœ .Nn.մ=/$ĦI<] ] V9�?E`1>, �g D R> [rZY\n~[uG/IeNu2>«�dd9u,}>ټ'd%)K~0A߳�W=l)k6i|Uخo-u? *qrYkU�"Keu.G Q<5R1|S9U++EJ'.?#} ‘;cVeGBr~$sdtx805m-oj{k n$GS:Ӿsgu5Q| RPЄ5e`BY85䴓 N׌Lg&Q2x Ӟ,\˚r2۪s~']5T<[qw(Ԇ%ŽlhlQ%v(bm  ��Yoe\\?ydڴKFp}yU<¦)UVzիT&{<SļS\=̳+fQqUV` u!S^"3Mӥ 0ʞ pJ(G_ A#V_WdTvO85}JȩzX4R\W1),}(k'8|2RL38 9qeY=NiT5AԔ?П%Ry9#+|9T#5KV1%28B&*qsGaux/~8_ ?HԾx]*ĚTH{.�Oׯ5Ff\s(Kxwq_xcs 1b<YUojp*P0~mfyGܻO%/e>-\^dL11r\-�:~q1~:g.=aq)|e߇&]{&1c<7[y:}Ҷcw6qooFw=$]OĞA.{x j=ej0>'q8U_2J9aڰl9ќ!�F_3pxN_eZ4ܫbx|JBX *s,Ul!Zcm}gejGkkwqomC}RWqE'#dDT75r~'/̿kTex^ CU׭Jupuӵ:09ܔU6g`0a0(P̰l]z|)Ztq)T<]*u-RpI^^3|o@|5h_>81^)u6km6g9 E0+'Jp�x9\iEC8,T89ZYT#x9laq˫WF3VxF0�J~o eSS2ac:NM{5}FrѼd4߆ <="KM6{;PR([^Ybx4l&~+v+;  7\/82bi؜%L0Iԭ]p_QZ9;<q"G!ld #F]῅9_MRS,a`ka0SE}j)KO.W|J>[OOCUS&u[6*! $Ӣk䷜ʱAuY.41_AxǾ"3x[V/|1˚|7Mxnt3*h'Ƅ0C?n_+Ž ?g)b0qO ?ڂ ZIFe2…gUSr,$kG/xAi.MO° B�RѯX즏KN ^Ҙhw<8,=/ʸӭJ~#r<9~uQSZ[΅O |oŮƙg{Q`2l.ΕJr ~;11ظRZ׆YW.b)QeO \Q7u%owώ6oό[_ų[�l-{MZW�]#[/u6i,CN/ }5 >=}b>\pp\X+eSd4�ݱYE2ƍJ b#o}<f8pl_� Ա|W_�k,N;as\mZl9qV0Qhї|/_?i+^M=|H..5}?Lm.{ܑ]ؼOAXJߨby̟ Cx b3hb|HG!W P #̕JΥ9x ?K� 3:O2,FmC,<?�ѫTNYB<I,>)MŸR#_xwxk:𿆴3TV60mF!P/vm'٬b|xW8=\',0ͩ;JyT/*)t $*cEbغrqlVmf xs),UF3TbgJl#,5*^M�u~z.<zƧ,K_tBɲ}OEJ<.4�J!WK)a*js)F&sD<J)')QF#.G<:S\]:rO%K88<6p4! ;h%% otSռ{Q|M,ڕΠo}kRαuea{ {k;}c⧊_F8+ɸl ,xwx*V-5bVIf8k!k)᷇H9=ak6~̰c2\GVeʣ8pu�kx m`e((E#|$}7㧌48&<v6sYꚥ KeVWx1^2q_9<x C7 .θ &/378SMhWN;1y8PJxQ9L(xQ^7qnWWxa<.$x/<N/x\fgQba5R'p*_'!rS[#*< I8-bU<~g_ߴ?ϢwaSb86.˚~߈0Y6ey.>MZ?\?iCr'<DZWyV2Ub7_%�ş,lIdݝ6 bKfd `_пG>�Rw%Ob28F>LFKf5ϙ1s6~5xQhf|}0S5+ .m._C 'ˢI'_'w�%|[/oZM_x6+[]OJyqGZU@H1K-{fh Te/y_qJJR�R?g߀)cq65m,V[Zq'(rG1FOOgJ�/][ Ζ֚Ρj[^K4T5mR$W.VڏaK߃E<Ay P'>Ͳne8\)p<ƶ'f9 iF\(Q)| [ŜKfa>x\50'0Iҡ05S 42p|@Yw[{_z}~Oަ5ap.xkg#6il""+h(~$pxsü�''xKPpZw Ibኣ:<LԭZ*jժ^o;|o&|f76ݭOVjѭO.U7•*XhҥF#�oE?egC6?:�LӵjA]}m1GFo�%d� e;|\E%>ȸs)Gq ¥:_&:Ӝ|l8ъUI1Oȱ,~ WsμS8έ  >=7N4~<G .<)MM<5 LSMӦ'[h5޸%vE>|$<Ę\�2OgҠfT3,v6C*5Z9~ !]O*xOm,F7<^7Zӏ" XG p>e2UY.e<%SWΡ|euS{EaCO� 5]!?x wQi_ص CM4J&72ZvmPeA<7B'_NwS|R�L xuK[-˳<FseP¬-. ̭9T=pAѯui^YeBS28Ƽ~'qf&t~a˨e9^cBlKU�z1Q {<*S9ٿ^վ|a[Ƒ6m?_횾Vؘ-'OKkxh9a0wai7sX嘮d[`aJXIա^2?ѓ7O8-crg*2Va0JL.-GZ0_ GMER_WmikŚ4ImuGk:[_[[l$r[O޵Q;=pHX-C(.x&kW$~#Ư&*71w SXzNqUǁ?>mƔ8cÜӍx'Bx|V[K83ʘz;φa#:<WaڝN??>&xI_\]뚍M+ 3E[{ 2mQ4-ij�_pǃpLJ)`YQ uS3e3\bV cK F*x^,qcqvub+aU9ʲm?ܟMQr+JJYUUZ?g5;N| NRN~5km|H߼KHe!$Ť]iUwAÙgcqeX<"3̪h!3QJ*xʼJ*1:Thʝ3酟g9[es'˸3*K(,=<eI^u*aig*eRXg~1� ćZğGX?Fמg_#x]V� gSճH`pEb8JYTNWo?g\D�uxepG^v(Pr8_o G(!xWO[UhRY?NryaԵMWX&'&4n_H��GZM>?ܢ5K+g5hc'($8ԡ;>ׂC|aN>kTRWx̫*_QJj9gf/|U|'UV/=~']xKu/Ku}vW7^-Kpw797V̲,g�l8W;ׁl,\d eTr:)`儣)pxl.B09N8(̷21�Z9<fX_2Tq5qLF+u%RxRjI#)擦|qmVڧ>iQ5͞iַs)OIaq䐿gsLwaYOxy(aqfx8QXUlTi8ҽI9{dY?W4ѥ2^uN"lN9r6.:Z˨ӍQݯ�:€ �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(�ǯ� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �{۫k;Xkᶷ~"k>/q8UHXiaVIv8JO> NUqzQWZF*qZ^S(+\)�xo›9>|,?q J0�+_[6#k\FÞ>(<IjkN*< [jmL9iՅU�Fh"Gx'n<1*P M,彣'<ZTM褹??kωeAn h66K0{'Hݴ+s0�;h .55�_DW>"fZ+xO:r%9qkL�_'e?o=%O Jӎ30s5HR*G:ڏ߈o-#/XiW7\3}K&#Y̶j̦kۛh#ƍ x:&+0J f9Ju/!ʸ(̳DhR*81Zbqyhѩ8'7+?0K0^'Vp:o4Z4[$e$c1zU f~�<wM/ĿxLk%.un`G jhњ(I}_7 p�Ppxk ô0Z%W..*z5*BjԥK^KxĹʫĎ*>,jbUq9\EN)`fІJjpRIR4־+_~|(O, 1 ݫV)m JwPWDt?+,�_Gß#�p;VVpqa ε)} fL}:uTS1^}'8|S5xǾf93 <,[><Qa 2F)U+ⲼN bpxΕJkו,� �Pt�Y|M.j̑ihfFƷ;:f2ı; _ӣϣ[\%$x?'Y\&}q&qCJ*Mas*1ҩ8a 8HpO�aO\'9<+%+R(,|19}YJӌu.<ƺ?G \CxV$wPk]\K["N5Qԥio<L| +|?_8 _%ܛ0ͱa3[0y\r̮>+N<N/B5d~ypCV?_ (m:xYKjh`S3jեKǿjF^%~:Aݭ saqzݰ[)|@n5Oi{<״Gn3'˜pelF+16a3ͲU'0X<8d(: l5XNIO6UĘ'8qaeb33w^SgG/)`V[eR){oğ8Eִ~τ%IqF+hbΥ}4sHw5^Mw\iVk. <5OU\1fQ0\&\ C+TMGOO'T0Y6F ?9g~q,= W_ XxS)+O1VV*"y_fZڧZ?ك_i.> 6W {jj.VKkM^Dҭ/<!vlnDnμOWx? \?X\ٝxpG)XISbln}p2sZ80ŝ_J |:4xwn>8˼Gg"{ڇ egb0fx,Ljaٶ$C,J*mF 4լ4}&I5&)l,(zw6+M^̙_y'W\U| 'qO,*1]Y$eW˰cCIz(} :\ˉo6xG59V"L*WiCFJ:X,3Iս,E5__6C_/k=#Լ/byj "!5>r믦''oK,#2,nhξ>QPx()֥:ԡG!+(<7UR�~|/~1}%f3'8)W [5%B*T\8>L@~/�ߍ좸׆~iWjxnm<T#çGu\@c/|Ex>} [sl xkV7WN:L%LtpJULm<G;"|-<Yq|\5~Y\3\?}Z X*hZG>OC*uײ:޿k2̗Ws0O+DpE#' pYUr|[aa0xx^򗳡FsI9R)koę2cgٖ77q՝"+^vI'RYB*0!*'|{xs_|/sUFѭ h_ :+.UOco}kG_<EĬq7Ζ<W̃ ,,Cl.Vu\_e�W}h?1Sgw`2 <>'g,u<rfc41ٳ?`0|@/G k+ hQ5*MӤ[+O][Lz_Ŧ ,3[�/'>+9q#qN;$p%0veK<~ LF]Хcs<9ul_gOѣ(\x<UU8e0xl<pX/143 6\,+x|Dp|UVV_<c<v5ZE3mwڕ7)?W^X(c9*Djχlv{j+f>Yajrޭ5荌ɼPjQ|{\R3+`ݰؼ~]&mS0|爟>9,[;ٵ^R9q7<g<q�R=|!OCj*QȲlX՗-[�88jqqMu8sNWrsl^`饬~�B/ cG/:`=ie9uzgrI$6<W.=Sq ]FqIتTqFUzjA+$~UᯣO 3r,Cjc}g 9M^S]I' #߀>(|?HY3Rnom,zi ZFap#O6]%Ԧ/�<3/8;|]{Kr̊c_rBLF+*ne[9 ,eV&xJ~|x/~q7 8#!xdxs0*`(2tp<ҭOƞ?/ѥfcQ&*nx{f1-|=}5o/?tRKz} :7n./&) ,f[UO8,eYCCtqxڏ8H׭C a9X\KQQO C icpuww Ef5p1RF+''lF#.L Z0_LKF+;+k_ uM7¦uCc񶈙DRWCey?vcVg x<<.c13k uհ UF<v?JsU+ь�Q0vYχ9]88Ts6TT Mʴx,]HA•YCogOƳx/ǯIcmVu+gfJ- Ovxċ62/3p? C|=kObrJ02xx)C {?aHU9TUSrc9�7x1U;gVUe0T*O*8yj�WwNt8ӫIQ~Կ 5�ƥ߇_ >?#}LCwjxFDBҮẲ3EaRȼU$|9;,<~/Gõin f^R/ &oӧ_ eN:G'9Oy8,l%G2ʍ0VvG8*:U)k^T<3sa!v>7xli:'.mB,&;9%㵎{uh4k(iLjпOoxf~ #<6ȱ\W 3جw<nYOJ:u1Up:=\e^*7y<<hتGUǼ<:SO*xSYf9T0*Ԝ0fy|upjؼe*=)]�q|S5iR_kx{?Ő!U)u[IJk?iDŽIxu>$qɼ;uT ccӭ))U&i\vgno xuՖ'5犽,n^(1YvUAs�g /x�|+C)|?ms5I!2X꺯4mԖV 3X 3낰mN82#,38\ӈXjSq12R771c'cxN?J.yN ~A]_Gr >"U$37NHЖ'*xLU 矏<F0ό[!f+wֵ{H1N1_/3 Qip B0E`ՓVt9wqrN&�&~g,5/iM�vZi&x{mÿ>:džr[H<UD>X%6;ZE{>iVs]iI_/x }0p}\3 G,4Սz|TkSe\0s|m&'u(藆<WV�2$ɸ{� 3NoÙVwLnϒ)s S09WfPw� Ɵ_o|6,ƑOO]:MWiRLNM)?]/Æxsl : 5xC8cJṟ؜t8T0x#N8:Y.-v;SӅx!pYn,Æ.)^"'Nr.8zw[T &2yQu*S_1' x+⟉<!}F #�|%s5!D. �mNyOqe )ZqxGgyn#9? 3xjn##xy̫wU2U0Х TcBQtyj#Ď0(p#c:¯ }V*8hcs,I(hb0T / [ _zQK/Ydu}2˱-'50M+G,LQ|Op,>�ɫU?4adٮ 1iVWhэJ,5؞֦# /ĕq^"q�Dž<֕*aVyk1>r)[f<|-Zӄ1`�8a�LawӰgiVmsjZ0�4?E9w w?qOx�3^>K6rⰹNY%Zzo%wFߴ7/82˖G^k[f9tdM+$|@~y-o,2ۙf $xUL֒ڜAmD7_'#\qxOZ<nl-*0%ZGJ2hUo'W *{x8�5μv :n:_ñ _"e=(NokZ|2C0<DBj_X~B~_Nj_'T ('7(ۯѣ6[x/?K9G R'r{B.:N53sr?><q_jWfyEQo(uͩ{thCE`ռ1n%%+q<UY!sE{K>&=ޛctUM>? 鋓pmjp,ֳ yVzu!8ZNiVX|mC㰑0ӖFӆ|i:Ux,~6tì^q_0YU9M)F>Y`R1* bMgtߊ__>@nP&[3LZ\A6.5B̲Ҍ׭ui_O ^<&-q�!XY|YnuBX 4b15a(cp\EZx\d -/\~m 8(qc/'⼻ߤwpwx{>Q̱Qe˲&))18=9pUs}(ʶ6X/�Ʃ~>|`{=[ hޒ$K[^֣5r(U.nzOqva73N?|˰pY.Ҟ+PգJi??IOj:5+%|#NʕH`!ECW⨩TSV(TWY0P@��P@��P@��P@��P@��P@��P@��P@��P@��P@��P@�ǯ� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �qwuxK5i&q "(T(q*n RQХMNnMsIfV+5Sj"JP RSVvi`[.nWYeY[y$QHCyn0 (ci,xxpR*TrJXtJwk rfޭ]{8T0GJh֨*EZ5'EKih(9%F>"Þ�z$}D7<f ZR<ORK7-]Eޜ�3|4>&G'gC¹cLxW+1T)aqx|%Zjׄ܏+&xQ9V}cxYX 0 KӖe*bpXn\M(^P8;{(\Lֱy.bG=P�L!)f!ӭquiZ4bT=HEin1[oh�cg/<)UI|Sst7Q+,ywj-Ͳ, 9Ǟv8e'r0X,dO⧅aWh:R{Jn2Z1x,kC a㈇Fz^δi*/$ԣj9˖7X^İM.sou7ٳN.R+;SA:<ʌḱ 0Be18la :lnt8l]:M}_F+ u)Tdz`qFLPK&1aQ?oNTku!V|N㟌^*ukipvmigamgi\<pAMO#S�'4x|d<=K%3qYc8#b% t0𩈯7K iQJ?Ooxqs,=ѮLBQa0 6 B2ZB\N#Z+~%x u}Z=l47LI,m`T$k -[. pp|oM?CfiO`ix>SSI`8l<u9A:ž: sl֏ӯ[,[ez8R08Z<}IWaW^5qꑚ2W@no.颍agv$HR4*)ڣ˅`1  ZV8\=,<jVT:zΤ% bXTӍ*rV^TAZn4ⴌ"bI �(7wV<V7GtUp$Iqr#ʛvHs sV150q8\6"ԭBZjy.Tj4MVS+jx|N"<L=&)Nu (Յ䨥efWr;$nȌUԂ2 c53*tB5)ԌR q''FQn2M6iبNt*u!%8Nq'x2RM4V$J5M<47!gsԒk<> >6CJhӏ•5AyE%^|MY֫QT^՜ΥG)9nKDKI]efmFI! 廨Wd,q,xxpR*TrJXtJwk rfޭ]{V*y#G VjL4kTXz"4Rܵ-I}{-6r]IinIKĒa%2XܔUlu7qxj4UTlD`IF))ͥeSTPmԯZxz-ݷJSmɶ~fݯU(A*C)  9<M)'%(5(tѦMhZ7M4ӳMjj4M?ŋ|뻛ɶn]W̙ݶp3 幰x_K`0x\ g?cR畹BvWݭV3N2*Wʶ9˕ke{.o*suspG=mlK$os@̱G\dW=& :0l= :4iҞ&VN*U%*9NFq8D(ӯZj~ jT WɪT䂌ngr 0671l�$**@�q)F1b*JnRvRmޭܥ+sIE]hZ1WRI%Zid!A ::r> _Gz4vh֩0#uq50�ew(*Bs\xL/E*eP*mԨӦMIM9^Mݝx~;n3txElCPg>HbJ)kbv@��P@��P@��P@��P@��P@��P@��P@��P@��P@��P@��P@ǯ� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �ǯ� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �ǯ� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �ǯ� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �ǯ� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �ǯ� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �ǯ� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �ǯ� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� � �=€ �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(ٓ}پ/�K+o_zc9п*q�!_Ї�2y�<Of�&�\�S}�̗��C�<�A� cU�˚?*q�?'? Л�sGEN=�c2_vB�dy3W�.h�ǿ>�fK��W_!�̞u� �o1�Bo\^*�8 ?ο�?M�ˋ_"��1/�;C!_Ї�2y�< qx�4T3%�h+/�O:�7�7�./{�Cd|�B_f�&�\�S}�̗��C�<�A� cU�˚?*q�?'? Л�sGEN=�c2_vB�dy3W�.h�ǿ>�fK��W_!�̞u� �o1�Bo\^*�8 ?ο�?M�ˋ_"��1/�;C!_Ї�2y�< qx�4T3%�h+/�O:�7�7�./{�Cd|�B_f�&�\�S}�̗��C�<�A� cU�˚?*q�?'? Л�sGEN=�c2_vB�dy3W�.h�ǿ>�fK��W_!�̞u� �o1�Bo\^*�8 ?ο�?M�ˋ_"��1/�;C!_Ї�2y�< qx�4T3%�h+/�O:�7�7�./{�Cd|�B_f�&�\�S}�̗��C�<�A� cU�˚?*q�?'? Л�sGEN=�c2_vB�dy3W�.h�ǿ>�fK��W_!�̞u� �o1�Bo\^*�8 ?ο�?M�ˋ_"��1/�;C!_Ї�2y�< qx�4T3%�h+/�O:�7�7�./{�Cd|�B_f�&�\�S}�̗��C�<�A� cU�˚?*q�?'? Л�sGEN=�c2_vB�dy3W�.h�ǿ>�fK��W_!�̞u� �o1�Bo\^*�8 ?ο�?M�ˋ_"��1/�;C!_Ї�2y�< qx�4T3%�h+/�O:�7�7�./{�Cd|�B_f�&�\�S}�̗��C�<�A� cU�˚?*q�?'? Л�sGEN=�c2_vB�dy3W�.h�ǿ>�fK��W_!�̞u� �o1�Bo\^*�8 ?ο�?M�ˋ_"��1/�;C!_Ї�2y�< qx�4T3%�h+/�O:�7�7�./{�Cd|�B_f�&�\�S}�̗��C�<�A� cU�˚?*q�?'? Л�sGEN=�c2_vB�dy3W�.h�ǿ>�fK��W_!�̞u� �o1�Bo\^*�8 ?ο�?M�ˋ_"��1/�;C!_Ї�2y�< qx�4T3%�h+/�O:�7�7�./{�Cd|�B_f�&�\�S}�̗��C�<�A� cU�˚?*q�?'? Л�sGEN=�c2_vB�dy3W�.h�ǿ>�fK��W_!�̞u� �o1�Bo\^*�8 ?ο�?M�ˋ_"��1/�;C!_Ї�2y�< qx�4T3%�h+/�O:�7�7�./{�Cd|�B_f�&�\�S}�̗��C�<�A� cU�˚?*q�?'? Л�sGEN=�c2_vB�dy3W�.h�ǿ>�fK��W_!�̞u� �o1�Bo\^*�8 ?ο�?M�ˋ_"��1/�;C!_Ї�2y�< qx�4T3%�h+/�O:�7�7�./{�Cd|�B_f�&�\�S}�̗��C�<�A� cU�˚?*q�?'? Л�sGEN=�c2_vB�dy3W�.h�ǿ>�fK��W_!�̞u� �o1�Bo\^*�8 ?ο�?M�ˋ_"��1/�;C!_Ї�2y�< qx�4T3%�h+/�O:�7�7�./{�Cd|�B_f�&�\�S}�̗��C�<�A� cU�˚?*q�?'? Л�sGEN=�c2_vB�dy3W�.h�ǿ>�fK��W_!�̞u� �o1�Bo\^*�8 ?ο�?M�ˋ_"��1/�;C!_Ї�2y�< qx�4T3%�h+/�O:�7�7�./{�Cd|�B_f�&�\�S}�̗��C�<�A� cU�˚?*q�?'? Л�sGEN=�c2_vB�dy3W�.h�ǿ>�fK��W_!�̞u� �o1�Bo\^*�8 ?ο�?M�ˋ_"��1/�;C!_Ї�2y�< qx�4T3%�h+/�O:�7�7�./{�Cd|�B_f�&�\�S}�̗��C�<�A� cU�˚?*q�?'? Л�sGEN=�c2_vB�dy3W�.h�ǿ>�fK��W_!�̞u� �o1�Bo\^*�8 ?ο�?M�ˋ_"��1/�;C!_Ї�2y�< qx�4T3%�h+/�O:�7�7�./{�Cd|�B_f�&�\�S}�̗��C�<�A� cU�˚?*q�?'? Л�sGEN=�c2_vB�dy3W�.h�ǿ>�fK��W_!�̞u� �o1�Bo\^*�8 ?ο�?M�ˋ_"��1/�;C!_Ї�2y�< qx�4T3%�h+/�O:�7�7�./{�Cd|�B_f�&�\�S}�̗��C�<�A� cU�˚?*q�?'? Л�sGEN=�c2_vB�dy3W�.h�ǿ>�fK��W_!�̞u� �o1�Bo\^*�8 ?ο�?M�ˋ_"��1/�;C!_Ї�2y�< qx�4T3%�h+/�O:�7�7�./{�Cd|�B_f�&�\�S}�̗��C�<�A� cU�˚?*q�?'? Л�sGEN=�c2_vB�dy3W�.h�ǿ>�fK��W_!�̞u� �o1�Bo\^*�8 ?ο�?M�ˋ_"��1/�;C!_Ї�2y�< qx�4T3%�h+/�O:�<C�.�h �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �<C�.�h �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �<C�.�h �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �<C�.�h �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �<C�.�h �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �<C�.�h �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �<C�.�h �(� �(�9xß'?xQ?%j>!ַMחkk HQȜ2aNNъnO{%]Gf|IeKx9arܳС,V;Z40xգV qZ%8�6վ�OC*_J~ q*iٯ:?;?{k�#�2h"t�E�1ĿW�?M:�ǿQ�}/W%�¼��~�mx�~=�RCđ}*q/?{k�#�2h"t$U�Fc!�߿_u�G�A$_J3K�y�=��?{�̥8/ �"Uј_+&w�Ge(D>Iҫ�^C�`�6�?�)G !H?H_f8� {= �G]�J?p_A�E�1ĿW�?M:�ǿQ�}/W%�¼��~�mx�~=�RCđ}*q/?{k�#�2h"t$U�Fc!�߿_u�G�A$_J3K�y�=��?{�̥8/ �"Uј_+&w�Ge(D>Iҫ�^C�`�6�?�)G !H?H_f8� {= �G]�J?p_A�E�1ĿW�?M:�ǿQ�}/W%�¼��~�mx�~=�RCđ}*q/?{k�#�2h"t$U�Fc!�߿_u�G�A$_J3K�y�=��?{�̥8/ �"Uј_+&w�Ge(D>Iҫ�^C�`�6�?�)G !H?H_f8� {= �G]�J?p_A�E�1ĿW�?M:�ǿQ�}/W%�¼��~�mx�~=�RCđ}*q/?{k�#�2h"t$U�Fc!�߿_u�G�A$_J3K�y�=��?{�̥8/ �"Uј_+&w�Ge(D>Iҫ�^C�`�6�?�)G !H?H_f8� {= �G]�J?p_A�E�1ĿW�?M:�ǿQ�}/W%�¼��~�mx�~=�RCđ}*q/?{k�#�2h"t$U�Fc!�.�~?|w_ �h ~+kxrJ}ާvr.;4a 2fp՜iӭNWM7ewk_֧qwG |9qwx]|9ц#4qXx|էB5*GVӫV?wJnWMsQ�P@��P@��P@��P@��P@��P@��P@��P@��P@��P@��P@��P@��P@��P@��P@��P@��P@��P@��P@�<C�.�h �(� �(�):}a{]j67ƒqku<2 d9"vGGVVqIOgohb'GOB98TZJu!((3Zi�5(w?xQ5_ÙXGu5s0�Ҽ9q1 hNPx|MJVo8KUk c-~ ZscM8l𹔪E6 ڜ^ԱW|zOނ �(� �(� �(� �(� �(� �(� �(� �(� �(�)�r�{X)?~/؇ ��E#cB �(� �(� �(� �(� �(� �(� �(4:ƈ ;EQԳ1@I5)7e}Wm6mݽ{~q?~h]_Qfc8Ρ9_$WN)S.&q�:Xie}6waɈZ-)3}xch>#uۅH4IZe<zpr-c8 -bp^(5[~NŤ.%=J~Vhޯ8 �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(<C�.�h �(� �(� �o�+?><"<Sw s?I~#67nUN7s?=(;K$W�ZM]'Y ;6re a�2՝⼞JM=#,+Zޮ+ \}|��P@��P@��P@��P@��P@��P@��P@��P@��PR;1�{�]oR�$,6?<^��WqGƅ��P@��P@��P@��P@��P@��P@�y/ xVƟ<UxW@E7IK}61 v <!\QǙ |^swXjOb1xa*SҗMX wRui՟JT)+ԯZ_f(oI~?X7{_ i d?WWC.jӴ/2]JR6?22 kȰ4|jK*t^/3icq_SX:w*Ս/n.Gf8v :n+4̩ƾ2z8;Mm"]~TB8U?x ~4JhMhZĸp�. <3=E/oC,TNJ;N2�Rf.qS3weSZ8xߤ0ԝ<<KBV�k˹=I?I+I;$D7z?^ ORRM]͝Ķ9%D矕#9a^.ROJIӧZVX)VB^҅Zj-FM[$>E�&FD*{,.d%V�Va$uQURk6$ƣڤa LkZ;3nsxrt=cj}*4SZ~NۤOk }ƛ['xZKzVzns徕u;Xc ܤ%/x8>Z}Px̾qtg9L5&C~:p�Ύ[B<?TjZyf. T Gz(VE0 2X 2#A޿ZiM;4OUH �(� �(� �(� �(� �(� �(�|>?Ꮑ^`ҥ֯2׺|S m<K@K+ljS KY"9β|)] 1P:CJ5֌t�gߤ?7vy~ TeKuFQ̰k^FF~S'_ǿ_ [|1ȴNmÚݞX\㆒U<=ǹipxCWBj%߻']zhpxe. qLghcFJLҼ#+JK3(� �(� �(� �(� �(<C�.�h �(� �(� �&|>ŏ5g>-S<ƵMze;3\6b(u(!9k=pĹ䘙3~Ͱ]jTy~&&ҝ5xc�i$~=|Sßjz\u]!d^#@,:sj$W +R (zr[�=Y�W~xxJR⬗ J'Kt[L2=_RJ.^Yz�P@��P�벱]G,[x\f<Gdo*/{-#e |M^Yq\$X�l\]Y`Ī_oJ)J(h^RӋ�| w d0fE*.#cNIJ!a(N*J:M:ex 1ֻ'{UѼ R#LѦ#3HAd+J7Rn�|E'O򯋿hsqUe|OpV s e#J`A $; \G'�%CXi~7,5 ~%ּSzh ԊsE9MkMyh7h'ҏQԓ8k!.Ws't sO:O٥ѵ�&SvV|IÚ<'ȗ$Wqu_zps^J_ po�Nߵw䲥x=8$dv֤FOagk>kK �g�`>|MO]⏍aZDtMwv7vmpXqOowln4ȾKpwԕS:'9#j>Y'FOiԞ$61?(xo0L|SWbl=xakNeTaeN*"*~惥8�^ '6FXxxLnK=JXt孜j7WV_$b^o!SQ^UNnE;je+'}vĿWcxi4xz8,^ua6WO/J5kA*RV4%'Mb+rI? ��GE�φ�a�� * '�5k>τ�hߎZ{xXbH]ΥЯn DjRI�T"qԺ65ȮX2ڧw fp#-xQ˱CO)TX\N#1(R~IUV5&6�|gj[\j~ G<)fKI;]Z*Kqg#̪Z&BFO$xpv7 86ѴkEPL=­<.uedZ GFbXSIM4d{u5k?'wе? |Hi#kt]&Wֵ$Yui^Ke^ݬ6qeMQ":1j.\ҕmh{$σ^omGr剧ШOW᥈:J3FʫEΪQǒ_ŷ)%+wC~г,QᵬyCm'.'}/upAwGB׮�/=TqԨq)9҅Iq5JJhƤ)T*QT~Bo�J��?bh��O��S׆w�W !�|?fo~~57 CG5,z RF<7a?Pm@[jo5Km3DɍcRR~<ӄSe;?{7>"[뎸<W]g3,g*SXw0XgV#<<«ZO�Q�#x|?|]xZo~Xhrkw62ֵTkB++ 6J{M!|8nS#j7%+%$Q].M(՜xX?87r6Osӈ`(CV0.ZlF&.RNY{ICW!?h�?�S�ggUO7^}O��_�d8/|3�&<?���XT`�u��J��?bh��O�UO7^}O�ӑ�? k|=20MA:g5 BKhkC֓JjqM 1"D<+xKneMžK;7MyATDy_#ӂ-LUxy.+=fk:I+qB a*Ir:LD5/k$aMbpd_h˓zaͧ{R_nhIVvNcq3\yస?,21y,HF;lc;B �(� �(�O#wc��Qb?�HYl.x�b'2+ �(� �(� �(� �(� �(� �(�>n�ikUON~%<[@sCg;KpDTn"w(d,I¾sU)a2:U+Ujua\hN<op.MW4dם0U؞[Ɯ/ӍU~8&یe|&7Mn{̺&91iELq"_\{Tq4bo YrB儳 άa,7{؜~!G^nPӂR�?xs4*fy&U3X\EG KhwU$|{DjZ'C[5+t/JsO<H qYf3 )ʶ+K ҊVZtSKYNq].amzX\+ZqFV9;F4RDI# sre|TQWMlh5=_NY-UH㟦3Z )9˹2N?˙c!*V%+4}7<FmWøy/g/<&Jn0t槺>ҿ aE>5P~Ѵ c $PbF܅_±On)Y<ѿfkc*)`]JeTx5R(U*K�kdm[cR &j-ok${ĞPt^g#V 0Uե,\<KYG|,NjZВ]?_asI/=/ G)+ nb;p�ݶDeY7�eO gS�˳fҼ<6?_Ey%.81Ji|[K4˥,^ .cW J=T ;v#\4$J+J[T,@�~)F2sڌc)K݌Rz%RrJ)6TSrrn$mY-o�Ꮐd?.RZx AOwM Fi;0˦w5l<1ͼFT.K $3uS$)ӴSh^t^2Ws-ʧspYSW6 U0T05~�P@��P@��P@��P@��P@��P@/Oy{�L֕ǿTc��H�.~vL�w�j�$n �*�G_~ �W��P@��P@��P@��P@�<C�.�h �(� �(� �(%��dj lO 阆`[M8[/H�4 Ɇq5aZg LV�e�7n7k]WqL3< mRX~#hs;{攨ܱcYd׀@��P@F'�?lOo⌱gdž eZ|Owkq&$NC;XX n~ƕWi�nVI~�dפ@9brg, JRN3UF8%(O2R#3Bcg|5*Mt{}7Htm* Hml,,H("TUTP+QI$J-K�7جV?81f#y.#TcVjb+NSINI6۔/s �xC�>Zo|-躟_Qojv2oy']?Z (ZnltK#oyw<#I^k):w8Ŧ62<e_'6a㰼C-׊FE҃'T%*~_W/e_ZCB��P]a5ih]~!(]]YmtݖӼ_C6ȿFۇS0[Wޥ(7Va�*cOOzta፧+iQKbz[R9'Y_H*l?Q?xC燴Oy_q#ïtWu&61妡qwG&{Ӯt弄RK{O{.r2HWnex2jE=cjQmic}}(d;;�犧3ySTjs:ztpK/`1&,U:x *e�m_؋ᖯm#s~Ѿx[y$hNtVHlݫ қ؊~hwjk/RN)ՌgKqJ9R :e8J-Y4M6u V¿KxO\7sf]7-֩O jΌ>%zC[]\x|7./^^|1US֔'�%7yxŶvϛгO0<=6oͲN+$Gtqc^cJXV:yagO J!q<yWxS H38:yy~^emosg?P |BM_> +׀5+Vw>"~égm�k} A[S*S rQSiZK-yZٟ7_p�x|olq5L3-|FaЭK-ɲtVP+hG�B{ۉ//.ynei.n&O<<K#3#,I#oս[{ۻ}]thQҧF TУJJPPJ"aN#BJ1RI$|5㟃<$|!6z�<Gk{2 `dx;[gy- ^6JTR;=5WN1q-f93l4qjN+ %JnP*ӒOj3j3QJpc#Y�u&FGk'X~URB%̍6YvPl_R1rFJۜbIFSi={[?Ϟ)F_g#e|O e\&8jqp3BׯQeQt4qpQi( E yJ)bʅą_RMK}#F"4(TMI%ϖ)Y&kG��i?w<mW%ܶՏKiXk^ uv`vV- uBSf�̱8գ'QsNQ NNOFYERj}~ x_(ḓ7|&MqnYcr/,_N+W<c~e*V=~V_<sQF ğ_I5մtwúqZYnuA)[2aq"i8J7[JOyEQj�il޺>nou!x=!\.{][3:n3ӫR &a f2bi֯,=EgnC /9[>}vd*6� LXz溞w(^T�W_Gt+Ty5:U'@%u+uWλ? wk>u[Y [CMaGy"5gvTgb7W)I3a)`3|AfX%QTtؚi(7.fީ+=Y+-��-.xui gzmMYOxÐChmKE-reRRl1J(q)\MFPJܮ6VcOD<Y\uq 2xjb*.e9U$Ҕ*<r7y?��7ƭ={ <EேWq42Kx)-nK$.DuWO6y6iwѸӓv7gٯos\&i\uQץ[qJjq&Sex禱Q/u"[幹Y.'yr$$;3;f$koVW{ߔSN0*T Tӊ)ӧBB)F1J$CAa@��P@��PR;1�{�]oR�$,6?<^��WqGƅ��P@��P@��P@��P@��P@��P7+º]v+ BӮ]Nv /<6� zgvYc3e'f<% iuk**tmIl#1ѩ^YFErmI�L9kO? sW44h̰>]9[+XY_<+|$ANy&GI{LfkR SSo Rxl4~FuRR#|sxάK[|0P.ʶ#]H#Ǿ,߀5_x-m_ xv�WBF}qvG;Gc#VoZmG q84Ҏ>s!ȳ.%p6SBXn2M$)y֭-U:4MF1oVI~~>{ ?KD/-$E >IlƉsz_9,E6xxXדxkY<)ſg̥U{ҩW <<c >StiSuiզYITpW? -:穬O+^Y662[]ŭͼRX. ^) WDee8 浡^,F\>"Hգ^IRJ4*S NetլEJtS*Zu":u" JҌ%(-hM=�:KH2/t/_Ӭ*l]>uCJ!73SoH:ϳ SPYE./jjNrx3-qFӏpLea1ז2'}iГt(:s?W �(� �(� �(� �(� �(� �(�?_*7+Ï��\#qy�=? �o�I\�%6U�ad:�c/#^ �(� �(� �(� �(� �<C�.�h �(� �(� �(_~G;|WT=𞡧iF%m#0 ޹h嵆�Kp*XV81QF[N6{_CI<"7C\~pE.Xrf}Wt;/ԾѪލ'$ׂEƞ,>]+ o$4-BLm*;iU% T"e |<(JP/T�r+l'<fO+A O/eV*oxTSRB �(�G�~ζ?_ 5co+C߉LAne:w-SӴB2vE>|[TtkJuiZ_wX/'>I+00JOtR(Abs ݍj';)N "�_ FZ_*i=}o1xξ#|#e <ڻgUƌZ״+li+”s<<1Y? RcBJU^M�iՄ Ҟq_/jsN3&󟯐I$VIY%-�J!L 8~FڎVм]S{4i/ ػH5ߋ42(kʪ<m+ʬgJR /m]_�F)scG0 TV8<d0٦"*1 9F3R9C� ~}k?"//�?jRۖ6veuF&ܫL\T<E+G.Q5g08=X>L xQ*YNd6)(NQ |/eI7`Qvк_H|9c곇'7u^\,۲hΝ;|�IßWܞ0g7 ]^Weoo/�|3|>�պ) [`J5އf^=u3+w_Ó_QM.Pž6ϗ}$s õ2zY&gʡMQRӢjVgra@Z^>=e~tzǟ֣\{?(R` o' ֽ+zj~{r� 8\?wY3>qUjrN֭{YA/7\�)kG3N.l𯵫.JU'n˯�d9q&[AO<Su;'KcN,O9,y{񌿙)|Y�\:tZt p0Tb$qQe+Bo�Dtќ\O�r?Wؗo1cĿQ_O7�'?>�.?׊]3k<D� �zG)^A��P@��P@�i�v�9^�=Wv[�?MEC�\}�1@��P@��P@��P@��P@��P@�9�q7i4OV679eUnQ NP< &>| OEV6|d 8ъS?K<(dz�jK4-G˺S?$gD5>0G<#Ҽi91?"XYW6֥â�L+/C2LGRQ<n{ZQ/vqЩToZ8�nxT~/,5m9*MHI֪Lq(� �(� �('[/ ij#ִJ5=oQҴŦ xU,K�x�8N NUX8j1UqiѧU*J1V]kvWx.fF.b1%OVm$ ]|Ӫܿu% "b.(5ՁG[sq!eC?aj:U&ST񐬗ETmgF]bAf#xC2()b2ZYzxMN u fψx3wSEU!!/nw�O5_y>*(cFhҩ8T/|ѩO *NUq{1 VUqz5ԣgTkRXVR`FApA8#_L~VMiVii{ @ �(Z_|8�h2 jZ~H9$'b{u~'Jh~7*n.P�Xx+jUV�FUùy>L%3I'0Tc9ѥR^rq]x] 7c n+o}e�-{sk�#o^�~y�(OmGGR�快v;M^$~f Y@25+KAxM$HT}߲pqpGSQyJKRb֠jp[|kG?g٦W+}©uSݯ%�Wڞ8P@�2Yb7iVIeu8FYتfb�IF)mF0\)=c%w)J1NRj1JI%m$>xGs12M3_>03,'n>wD"dI'\Nɼk(GwŘ5DO<R/iS Jq}$>;0⼎XYRXBKu(PIFWI- ?g7~X&`ikm>SvmR^\ R75ϟ= P+>)NS(VӊUq8juhSN+H, j9g个v´ UeNߔc'G(]&7o$m;1sW 6nZߵw}ZӿBͿ�U*/VO<?|IEnF FAa�ei'٦oi3'�A�?ҿJ0�i7N�ל�PN�V�Se_�H �2�r?j? otkzOUszۨ[D1IT}kF' *E5k*؊J+T`M\r9ev?5|pYnՓOVߔa/ϛOt,�hoOq"WΓ|MA�e> U⼙M;5 \+/ ԭ~ї}>8q*3eɫ`,o&:i;'Keÿj HV"m`4vMLx$Zlk9UeXUKγT}kϑ%Y\ NU.4t31ȳ 8K^ue5!@��P@��P@��P<C�.�h �(� �(� �(�/�S~1 ||G%Ŀ mxe(DQ?<swkr/auZ*Ь�nfjxxY_bL\JƬWYzhsIqʳYb(M:8 $`玼s( �(� 6Qyu\=|Q?L&Qv�/ʼ:-Jջ ~~G_~ `_^$PGG$YXR-*qZp_tRGEj wZrW7$̧9>f795nemO%ſ{+#-;~! +ˠxUYؾ"nznx)J]iTZ{ts1e:mp\ 9l&ֱaL}9[ j5ŤZO5մoso+qo<N)&Y"' +e`@*_wUM=SNEJtSF8UVVHF:g q'(86iv9?|d�#-Yx!tMkɯcnT{1FZnA*9ͧ:&⬽I˝7խ_VxY p O8gnڪ2MdW\Tr6b*iZU)gg2W~5+DmdRTq6q8a*1O2M7kugu $LyYVWRU4Ogm+xz(ֺޙk۲UBkԣ-^\� -g9O]8ʳ<~[Z2ѪU\-DFIq¿P ׀GMyv*y҃|q sM�n�2_�@?S>Ye|8sx-:9niKJ:]/~MMf<|G<A>x/6ZocoxZƕ# ]s&�GӴ;tqO=VvN#^ !.JPNI'){7huﭏh<5p߅+.>wVT3>]RN[q8,_ӫ5Ʃ��e|�~߳~<E�'�b𮝫{Pf!k+UiMImios%,K BRuav'i9Z\ɽW0~/'q~&q'3]¹kR5slNjts\\tUz8%j*aJ 0�Oc"%"?E<JzeŌ8=c�]|vOt.poU'r g?|/Ew,DN�- fcu^6ҷ>1<-n|6qkUFitO苑!IxAHR4YKo]4JtZJ��_Q'g>/�`܏o�$_%[�Lv5#Gq/|A�c�S�>ϿF5_K+3L�mew?ꞑW@��P@��P@�~#Wkݖ�a�/"GcQs?q_dhP@��P@��P@��P@��P@��P@�1�XO˭~ўǘk?X-֯祿*Zo�u2N0/Ї$7iWKZRahBoVkrni+=fc,O MMGRS~O;cº< 6GMkn;P988121arr%BJoFw[0a",N&# |?b^ 4+/ xwGЭF^aof`VdlxɯjygXJx3fe)9˟]gQ^Iy>Ye QIYrХ wwwΦ(>#<%_֗J=k[4>1.//%ȪwefefQfy2`0qxMYiT0!RZ! IOc1L [0u+qUaBkyիRQ"R]^?�f8os<�˚,ѴE3d>o~+k�yw�/j�ك�,ѴE3d�=?�f8os<�˚?x�Fӎ�??��? ��.h�=N;�S<�@�_�]�Ϳ�|_wŸ_4Cu]R}K -N{ ;"#8pf~xqo|_H{LV?0 S\J7isNqWk}aq.Eח%65WVV-:TJsvoGßH~T�R yw2rog<3kkΎ[MȊO,QI31GXNu29̈́T,JRMJe(IEK6|e2:ƞq;UXJ\ $:HTJ|԰ԪՌK�Mzo|T|]{$[޺ir!t[aop;vX(2oo$x}G& W40ъ-h�e�, \;#x2J1Quqe<V]^#R*B1Q<z~�^%w>m�_aUyV*})Yom{^i.k=5>F}1D?h?ٷP-~G4k}/V)h\y5�nU7O>'w!pZTዩQU2}YՂ7+Ϟve*W%G/oM| œ]R8w G#o7Ty+*ޜ18R7G7~ҿ j?w~S<;|.}X4Ra"/,<ec3ʫ^ֆ+ QbpUwF^H|/ ~),FST;1 ^a~� Wu'? ח}V<(bw<;`*Opf]7ܯ,ʰa0Vz8:t)JRz%l6#ѩ_ 4(҃JjK#v)4K_=Oϯ&\h%}'Iuи ph[?ck-cGJ8s˃|nԩq籊<*dj-qڕj`QGoJ9eQ*^f+XY/bxh|5|y}\Ku}wsys;ny'fwK,H'搳GYkgFkfysb*5bsv"[VII۔m-O6 Oa.ZT0ԡB8�-:taJ)++-, >+ ˛;<78<rBcqNm6ȳ<%0U0S=HhΎ+ VzrM_ݚ>+ QbSZTҩgNe 'Ůϰ>ԺqmEMWJuZ~xSy[7)U̷ �V~xw0x3UN'EJ8</1�X7&өCC<ˣu~9ެcyU3į[rZ/<XG9>ZjO 'hлA-.oml(綸đM 9#uee9~"3˳6eXZ \fN5hb0PJ9FPdO'_Q΍zbRZrq9Ť(Y b|J�f�xWm=^x]Wg)DV}psF;¯ 8ž$pӥIFkbT)s(ʾ"qs+BkԴ`|wML<֣IO8G-*Qm(Z*Sun?y<Iw4t�xjO`F[2\j,BQZ)s߅XJ2f)bs&R(7RF59l֬8� <WƵʜ߱5'G wV"QjxʖZ)^fnoV}۹*H9.tjtiZ4 o/i㏃,յOx~|;é.)`YٮF h'D 7x'&`srlc'Bv{b)ԯC0R^bB/F5 {n!r\Ӈ3 N#51v'zҖ/-VJ'=[Nr>3oZ�,Hn'?ݞ]л/ oyKX?o?QI: gDZn%gڶz4( Q@5F3ѡEZt)Rc%|œnf|QWz]%7Ou*x TKg0\') 4~+Q �ןi_�Q��M#�'k[�t �'WƫJ)�$x�9صLw�C<)(mbXɿH𭤡 _ rd`aB^f[k`W$x_PB?+楆q#ܭ}iЋWG?虞|E�~':pK01*vGtxTpiF5vAaIWznٖGЭ+L KHQZt{q.{ؗ3,F:|”Ʌ. Z4c윥&xy_eT| F5t(>j6uڷ>?RZ+{(N8rkv_In-&yD )cmU6ԆVypg8( qwN3z>VRhBtѫգV *A&Zѧv�أ 6xέ|@[s4xPwrs#wGk?Py"sD3fzoWq_2?h,MNX^չ%3*>8 >G üI8R']1g8Hի+bh>om@_hW<9k:aki=K5ͼ$(ꮬz8-jx6"+PJJTӨ8n~MliW2$̱>oe٦YqT,FZZr3T׽)k�P@��P@��P@<C�.�h �(� �(� �(��)|Rcie׍t5qVU3A C5m k-߇n+c\$M|isq}=c?~0KO ]4%^e .2<ʎgA[Icݮm,n,imm s^9cY$YJ"/gBthԅj5aS:ENZsZNHINPPP@ iz-Rnɾ7}�s]}Ëص/m0_3LWZ%=Ij|� #BGj|54SNI4_O]+ ?f?$l|kCIdz?_oT_})?~<D#>/a^ҮMx1Sk]R"]l(P@�($GPAQȡ ]4z3=��#N- mۋx66N.kp5\mY0K?w;ÿ ܙSPg9*veK07t7[r/ AL"gѵ zd冹4k~b1W*jVwm5]Uq xժr.͸{<[4< o/aԚzi{x\?g�RY-O;xyG$+I>}6"LaQE{x*?Wѥdyޛw&71DV>2g_/،$|צLV[**FUq_VNRr?gKOڳABX!T_+ K2[?u�^lxMVFFh|USPX5j7iԖk}SKĪx\u )px捕ԝLPM;O47CחQ{Bk[߇ ~%j4s_&ˋyрh]vh$FԀAy4e,jVt[Ϛn2�hiF}FѲ5xUa8j)MJ"8bsu |*FI٧?8s_&X�OP o8<Sq:{_ϸs䞙__:&JhkSW}:/}pk6LC<=|O;:mLBM_R�?SB/_�J#~� � Ŀ ƾ�%�?w�~ 19�ƫ�u|yCT��Y'OS?J (� �(� �(�O#wc��Qb?�HYl.x�b'2+ �(� �(� �(� �(� �(� �(� mso^x7mnmϳ%J@ 6=y<tCV_uIٵ*U<4~|~J?<Ӝ<Dǹj,H_ו/ 𨶃ƾbB.l[ss?uܸZԨ4UۓֲK][wn׹ "RܐͰݗ*ӽ޺N۟� G _մ? >G7�ؓǵ~r�8O(ŏ7|W�bGoSa?�oS?�}-jW:gI"_g`ȦX:q~S+(Io%:xZ!%u-c(߿ JH;5`WkVQŴ۵b>xv2� �ρf(wREKŤ,ьoKbeV�pw N?<n=UUncaj^6x*5#ùryקt`S 겧vdgkzuOuk:֥jWҴwד<77/,ǁ�bVjξ"I֯ZrVIsNsRm�D~_p,+PenB4L&BqIF%տzMɷ/Пs oo^A}f<C{֟H!Xe7z{25ao_w~a7VynCBP毋C5+te/v_I B[!c$yRejJaWc9'lF*ʦ| �|E/ ,a.uַ>0Һ4VcMӬc]}y |i3U>jUcF J tJG}4~G)Rxgp8xJpVV񘉵OH٫.sP=&Y,.4CPּ-sv4-jXm5_ʔF8(TMNAnjNvU|&"S7?OxK0RN^uae, g^Ӟ7a7cBm(ZGOo>toKq%,fu6Vqi{Qn. ȲA<q\E,I<=pi_(ͨ{Ux:Qox<JZ=%xM)�x~,_\X̯*ѩ vYOfXu):JZuZJibOw_7x{Ŗ7xCYy,-O^OM7nX칎hvayCR(asL:mFe{ՠکIVmSg�I|5͸~Q|Ӆ3'|=y?M;NRV!(�i:^civyj6y nm."a RD=Ձ_ߴjӯJz3U)V xΝHqz]J2M;jCsq<N_1g`q5xQ΍zS]'N% .}�k:.^8Q# #!BwZW0ikl]_>Gc^?}\¦ = jR(4,^?EQ<$9Ԅ><GʄjUsV*^G㠛|џ/Y~IG"E3#F2pu$�ؚpzU++Յ)BU*֭RJRSNRQbm'dY:$Ne9Nьbމ$菺~~6VyKx|9e'ٚ8P5;d[yRKpȿѣceKQxw]L5Xƭ%٭NJNKuL,䭋uJ4|CFbhbOút'*5sU?nԃ_Ni1 j J 5%q c�+Omٸ\i1nyE|?A? U3Zy>޺n~b-b>77+t/{�YdtwoJ]s5ԏl<$I闌<ב%mnK%#o2?+3[.W9żX55*լ޴V/TٜOnJXT�s_euOהhϰY4 ,_T'iWêJ*RdwTh䍊::te8eel29x8**աRz*QB9SF)uhե4N:*SR$oƤc8J3 Bqe-%&ZѧuMp|8.4rxq'Lz|eo{ "v~n:/~X7ձ9χձU\aBn)?a:\:XB ױ Gr?%OiO F}ڊv5e8˗RW:mOo\tڦiZԬ8-,yو�,hNIֿL1<JuxM.VQSj۔~G+GMSRY1N.sok%\7k7_2x','<}+ö22V;K`pFT㴀/ළo p'WKX>զX:)<> ~QDL�7C<_&34RQ9Წ3Вʮ#Jեk(+/ѿ߇_:r4In}VuVm�s3Gmn$h#pׅ3~&ʞXLYcs<l'G&iNVFZ" fgQzJUj{Ժkb1I)N�U~̿t7Ώ{kB+COx{Hƭ>1S:bˇq8'(a2>`_xFyVi'/GI; ^R8'T,s*©T0Xzi轼kW%{o&JS1Zk5 2BWm>{C`]iXI ;J�xsO_ĘE%"+Bb!J>eGY(ǂ^yҥG(yUjzk&Jupzԡ.md;ߵ{7Pzx+^y }J(4B ֭c!2<si#&H?o<n*?�-Tdj0qXJEN4ƥ)ڕx8NQ2[˳:ppe] QS(>&Ҝa~~vS�I/4?�H�!~z�/=�ɽs7?xf��U�?-�:g��+�cU%spgW<�oפ~sWV /#LJfU:yiTu,1' '�ߺoP~̳T'F^ga-:o]Qx9aψ/S7,~ ND[Fx>%/QjڥOu!aAmin(` aͱؼVU^w,o{B S0J$o pM=`2lC/aF1QBNTן5|EWyURI;}6uJkټ!qOQqswst?JR+RHNk<燾<uGRX 5 f?Ileε7RVrRn1?J*oBUr~ > 4kS~֎T*4qp(B+R6�c$:B|cp޷N}2[,[Ҵ<Ek,🀲 V'3OJ}ҧ)3⿦/#xp TspehݧXbqU_YZ>M(?g_^ֵz]\]Zme[ѯ-iBfHO6żT1o)hÇtR}IE6MS^$wx™_>3RP Nl4QW0XWq(?o[?fu+*񬮠{ -g{Kg٤ "쎭_0r{\=j*9ƥ>zRp%H9BW))'twfl-\eXW T1ta^V֌*EUVNjPRL�7~7Q ~#g%A[}Hw֐A,=U?Y1L^O)bbpm^8{?/~|=ӯC'Zt)ƳjYVsUB*`q5ZnjaO.O(� �(� �(�<C�.�h �(� �(� �(� 0*AAApAhѭS]�ɇb|Sh:oؾ|w[>xb[kZ;"*|?Ǔ,v>!ӠL~g4 ߼5/{�kxFMq^ߊ<:.Δ^G3 7d>IunN~yP@G1:FWU9ǧIJ*Q^N/ѫ?�P'O�tmOׂBDpּ%ut4+pp#mOkjPQOmSMm�*H^;xõʒwFQQ1Rrx,}H�>] Y' ڔ~%ռp�j;;Bnn?;3QRtB^qw&լo$ّŴ?>U!<hI1e|&wME/`1$mnP߰짬_'K�k/szx+DWN kINKOen< קA;s_N:[mdVLk^xQŞ"Qr|id}j3V<7{ԥtO W~h_W4k�a o{>>5j+?3N YM`W0ikj-></ ,Nqrԣ7ʥ9rIݵ-O;�O<:Q5sㇱ~U+ؼ[,tP,VxN*:L=>|UXhY?�ޏ8<!=ǝ?OіA5mԂIG$R�y4QOxTw(饽/�7ÿ؟I|rSJ֍I…|Z+G6S_ֿa(ҿ7WhZeK}kTމC\j0 u&x6,B.ܱ޿;wO?h3졋x\ω|/Rn%xT3<~=ݒ7a�baIj>�GȾӯY 4H.ua%~61}[ VkJi^خ�<2?pb`5<AN9O'b,M EӏN,Vsis�h~ѐ?)|g�p<i�g^qf[]!׶bmx_|Հ%ܪ:ڤ=ٮi>}(<5:T}_-w[:TcK=ژ t-ʝ<O/0s].|~_V�ĺ~?xៅ~ x2DGoi&KKr!<D'6RYUYSw *:VjJtSnֽIMԌUhg/3�K}2N)x3nsU8?$X,D7rl,nFxjRq� Vg1C_ϊ%7 GQ+Ԉ<SlȬsڞYNJswje^F"xR̲ePS70O)v? �SM�?(3�znG7�w/-�;|� #BJ> �k��?�g#S�KMg[a?OHe+?Ԁ �(� �(�?M?ُ+G߰�#g�(pˏ\?4(� �(� �(� �(� �(� �(�??,.t?>!j~ Oݪ^C+pX$<�QIK|S|NQ̩Rx\ʇaW gRwgO) sX쾦rKOm'҅y4џmvca-em2L==&1XlF<E&XՃopVTkRiR ބ?K/�>AK'4[- Céu_+_W18t8M)a%ZUpu#}\jajQz?)g9f~ޚWPPEٴkBi[>(~+|4>0|>Go\jvtJFSqk9/u=cj~ ρ'̞ec)㰔ԪVJ'xURp}T+So<n |If>upWp]S9#[f5;_Ok??R[�� �l�.\�A|G��:C-� _ GO,o3f.�#�Î �!�tc�/'p7O~K�_��Γ;7ǯ|1i{j!o'b*@ib̥`@_n7|9xel4㽅< _ hҗbqUIU~i^YEBVod)Ř#Y+R[5bN<Ї*<ɷޜ?aIo.xK֥R2 4d|U<²Dfpʼ60Za`5Ub) Roq2HaZOH>}ϲ�n >; ?Þ-ͺ1}^Ki]{KorO!.�2K'3l&T`jc%"漹�ٵ”0| e*QqaJ5Z0Y*Pox^ƾ1DŽl5/FU&y!ב+6S0`u1h%T4?$ΰ9ia\onLw~[u {Ï|66z7t+" @x"^{B/n$a 0N"ʰNЂ3}:&ܤ[Gcx3n>!Sqg+J(U奄êXj1NѧN+_u&�ū;KAsqr+ BY6h-dF;>X7~<pÆi琦 D`*۶Rp{8Y%wo7'k&N:W0қtgymXqmF5jҍli{k'_oc[G=ᎩHf<1cxNV}D`� Sֿ.-nsp5YI8iB}uKKz>\CmӍ,.ugzTrhB!E+x*4s�5�xW2KbՇO!gh헱;-95�*6qv/^'1gZqYZSpxlu\^,8/<xW*FkoNjחZsWd|m⏈i@n_.DWKx <>!Ea�f߄o>C=𔱹O9MN'8աW8u`wRRӌ~]K*8ԧ' :5iBTtqkJ� �(m"I{ae;a,H6ޗdES';`s%Ó�o=6]ᯏճ O.&xz0O;_ӥh,6.KZmG?F#x"8LeYVd8i7y ({LU)F�Vl��kxo~֡fVլ;[n:1/8sȯ?/xs*W%ܛ7 rL4bh{ӭBs4՜d֧gRxW>ʪ2/,<Tg(Mvdi;3/)K~>6͝Wxh쵛:ؓKW� �'ø~'+V1<+?)Lլ.MmQ6BJ>4%EUDzETMu便rO?s?ϳ�S|~~v~?1o:S@nfӴ=_\A}w" Vq2/1\O~^R~XP-'9TЌ98Npe,TZs8z)<Ukf柽d_'!@$~?4ߌ�oo]Z>h^'&5h\|"6Tm4~~_٥ҧrޒ-<F[aƢ|>d*VqG(p&R|>;;':X$]Zn |IF 3>�_�8?.=��I/?A�''O�0�xC{ �'=�kJ�.J0�i_H;\C�^rCpOO;�5[�W7MG#/N��qS�xKY(|QkP>iYl Hﯭg�24?)BSd\$qꘊ?Jz&HI^8o|>7+SSp.QOb3|TUIŽc'ׇ2wqkkD8{mc\&�*P\Gy0^i~ VhRZu*�NoH?;~ t_xm3šf_k.VKY8fS<1mAGXW!YSYf J5iتW4iթ*i(FsqbQ�9~!?~3e{ř?=apLG50ym8Gఴ':ӣITZ)~{�7t_^ > j ž,{‘7(W!V]o_q/Nac/\Xo>+:tlX[Ia'sճ/ 8)TA:YN ]_$:7JyDKqo|;x#dyOi:8ma :17�L7w7EtN'_Q�G2:=:3 Ҋ78iTc pTAZǃ/8xs^u ;_j׮dm8fx.% K耺_"<fJ=^A5N1[+pes+ϲʨ*u0ԃQtcRQ%&g�-?h|C'GE/5 zð? 麍.m{h.|^cOV�Wr 1|kBmTʜ*F𨽵Yvw?(2<ǏeVr$Eƞ3<M >%j `te8sG(Y5 �(� �(� �(<C�.�h �(� �(� �(�??_W4e?xLoo|Mɂ"G-.>&ಉK]6RniiJ*ĺn]xWWxYb+)c?aOeVZSqmWȟlMӳO[A@�gmG탦ZqxڝğS*ɪxG[x"v`|ж^i`+>$ħY?z-է~ƕwmGmQ%~qYwyNS-⚔p5'q)9i52-jW?O?wiO~ k>EWNOaVHd'+^z0QFjuO_cӏ3o x8$ΰY*nN44SZaWJaKO/ ^ԧ-L6H4vE h:-ՕL&iln7v|=ZU0eBj:#eo�Sž.NK\Re9IҍHO#̧2*撕^>kkۈ,溻;{k[h{G C,q;U$ٶmJI]jҡJj!F(JZgtӂNMFS&Rj͟ݷc ?ƷE|n'�z=x_S|5iԒTEXxŷYj ['N,|Vrz˖T`Otov/Ἷ|]l C3\-N|:L3_.#-(TazXF#^O e�xC�vj:,F3DG?<iNJ% "9;V*Q7'<O٫V$pqp|0# O)7I;{}-/B\�c.�]~ ڏt35'lmt}kGԦEY<;#:"wHTJ)CX_VV8q x#:WIex3 M8XE>i+_U{,�s�{Yjoi_k"<Vۖ\Og2o<:t߻zJ?;Iywe ڦ:*ӧg05g I_uҌuޒF�Jm졭Y;Y‰6ͭ|A'}9Su_M;yBK},ɽ-y�7 À<\WU5pLWNsZsx؟zjjrIchPKb[ yK<׶y(YG$Һ�澊�_CyxLKYמ&ԗ"!Nhҿ":t M%'F-�Dx|l~,/>xS@G_42R_ ܢ9'zjVRR4"׳}>zX�leFF :z|K0mZ2n&٤jMwG^)�_ҿ)�GJ_ g>/�`܏o�$_%[�Lv5#Gq/|A�c�S�>Ͼ5_K+3L�mew?ꞑW_@��P@��P@�~#Wkݖ�a�/"GcQs?q_dhP@��P@��P@��P@��P@��P@�| � ?/ _L=Q�gC*|e-%%PFX)򘃴m-KטW'9߳s1S裆ƍIT֗?&<Xj~1_)^uюJW]7DURA_?֚WO?sߵuw7T[K=nO=fpPٚG+D"![xϟXʍ8{*հx`^4myUTqr S)8P//a*OZ<UYb2JQX/`yI{uiE=A'*UP@�L߅^Ga_I&.`6Щ qu6 ?y,PC|3q3asmvu&M#N9T!)4<7d9V;7*Ǝ94#v9фUܤJ/ůZ~6n/nC5ج)!ō٫)$5Y, qVUj҅5LVzigkf9_8ؗ$'+Q�pS�s |OB՛I.^3�ox5)+Q;ѢSHpq,rU,o5)Zk-^UZJR]d~r'l~}RQskxm7ꂭ-i=%�iV^y0o#Po-!5U=28ܹ~oꔡƴfW'8駻Zz{9<1tkMkL{NO]$וG�&cNUO>$xNGu{Vr+;`oxJ,/p"*9FF5w<%#.FS83(a e:˫[]lc\� >�zv_Q8FXhaKOR0zeJ10Y5v*I{jA;kRXF ^?>9~+0Fta1క'.TwmmZO?$u]ngesim*N6 r$cV^㠮Y߱ŭ^i� x~5FM?)jZi%ND}f7VbdW#+zw_xz>" %t|.{a)hZ&D}TDea[.U:<4Qϰc[,g_MC<h~I8ALKVjN_*aR3:Zmd1(p*1K1FrKSwڜ^-GP@U2x6Tk($[o'>yHO\?ku�|,vxcx[j_d]ՆE)*U *M2 8VqU ϐ4mOO2]q&tz2+51<Ga*󬮕8]EE$wc0i`1'nX`2VTg{/Mc@ZG>Yln#/ sWe8"|_ &agU-ڍ}%#,G{zIPVKENFpJvg:X?amcO�d߂wl1xO2u+k�y*pp|¿o6  XNy,LpzԧwxME4WIe$ԣZ:vE4}g_�pK-ǎ}E,t� WL!M.[9\WG`RbsdOIE-oval]FR6ۧ?YNdse{~�AME'I?TM96mM�$`�#kj�dI��&9?^�A�?ҿˮ=� ?GW7N�ל�PN�V�Se_�H �2�r?A๚mWT&<m87RWvk18 VHSsz~+0Ny{ $^ЕkY�QT|X9p.+GJI.U1k~KyhD)H::̬8(EFKtӺk}SW?JVT::8Mr/m%nUx&|Uk5 Un,vIw,H[CBu~g]<v+םi*BmE$bQ{xw0 *l)SMhԩ)9JX׭9IUk:e&ܦr| �h&xÍ^mIV9c#SI 12[i ^Ԓ8QyFQN0'EukYEUƄj;?x:u!\c.Pܥ�lq Wm",3Tj`j|��<<,Ʒ;dCqZ'Xkr$~ QHId RM@׀^80Ѽ J�u~G+Kqfl9w5\ È84s|qwJ/g[QQhq/|17F5k'e rp j7W2^V5Uea2| aЅ.g&V}Vr?Ͼ/2ΦqƜM.cRN_Xͱձ^Kxaͬ>QңJ;Foc>L(� �(� �(�?<C�.�h �(� �(� �(�+^j6wVsg{o5ռȲE=mHF쬬WM=ЯW ^"IRB:jBN3V8-J2ij\�6)Gs~�~[IkUiB`^*Gl N/3#$$D[ⱴ>N =k5NH|^g|u+®yK+]J|Iӥ֫ۋ̨.m/qgA�7E~,#Ovox:վ1twflHHլum>l{asqm()+u'J*ӗ,.hךkd=,ϏC=~.C18SJ5 fab!TҌY_N*_Tz>x㽆}cQ f x_j k#~M .'[]reYdlh~ poi/IY�珞C?}7ళ ~*JQsqqRwO upkAΕ/h?/mK=7VZai'>.Mn�hM+WK,/E|5 JQFRw\TxA׋&Kxsƹ W(e,r^*',:z7VtnWo||x_?KFs/kz,hW;ּG&t҆*&ہYRt')যiI>sq�^_g@U} |Ojd<gnydYV[`q(d 8N{z.&=g/� izM|YYbW?cs z6 mw=*40R0]~h^Rmϊcawy=!Q2|3i{l8ʎ]J\TNڄ+[Q�~??~ �i'onk|cx,.t׋Oyt=^_\ {卜9c*t)(F_չ斑o yY1�}~D7ѿ$\1"qF9`O켟.QWKN:u1!SUGhҧA~6Hlx|9aTwurE஥.n7Zτ|GjѬQf-Mrl.JB? WfVN�-*>�̿ڍ3̼.$JS q^e9-! 㣀v*qմqW0Y[\^\ȐZA-0Hi%ٰN�8}Kvע�R^:4VHR)Ԓ"mɤWK�0(�7�گ?VO2'|3ᩭ0YR NL]h us_ Uz׺!�^Ri}P}|7y(}_1dX|;UgLΝk*\F*X媥X�noS^=h>6+!D- y$>\um2!|M #5-a?vW]M}&3~t*+$dI9G=W}3 JG]*#is43Cs W=Q ʁ)I3+RA�9t:u!*u)PNqq'(N2JQdeMYkKgg\#Yy$8ϋg'ˑ-1S g-9󍴧FO|keӯ[?fB^*N>.<3qiեKY<$ڊWV) ?Og b_~*N__Y:φAcJa-7JֶLu=u&I,DAahJJtVU'Σ.HW&m쒊?ſ㟎|Q\-\M<ØVO,0kb3lb1/,찪Q:nXLO'_~_2+ @ FF0+��K(ƿddq j?Rz5>õ+[KH!5 {h#X(E8DP�zjWRjZIիVTRNu*NR9NRwm|W?fڶ|i⾷ؾ|n$l:kkWf3).Wq&BY)TNqR#&wi74kme#⿉_8mu8w3<5USJ Ԥ.o#�oW He059mtxnm�A,r@du` fxFxz-5fuO_}/Zui=9p<9UԥR7)Z8ڗIo~? >-𧋭 kMI{]Dڜ\Ku|Aw豉..mX]5լ7bңJ$US5vz]_YD�B+Ͳz3 WF;ZXœ)T28\t:tpV>s �(� �(�)�r�{X)?~/؇ ��E#cB �(� �(� �(� �(� �(� �(F""4r# AVRAr82%Ÿ2RkT5 $i{4i^_yIc{�?>'x3LO>=ԥ-o xC-֍sX,og$p؏(l�?E"pϱp*?y8xt15LNdUb=jҏ>L{P6*˱dS>[ѭ&aT:WVee{w]_\i{g<W6V43 ")PI#u *FFy (h)SB9ҭFTUR.5)ԄBqn2Vi>JjB)ʝZsJu!'p|ќe8-&wO],AG@4'-ckvaQY]T0E4C늏es˩}PW淎�C WgeS4|RY#KrnQs}f0y᧏8LE>KMF>�6)'F}ltgHi:iŎ^j}kyi$ã콿qn*1bp5IRa1*4*GZh­9b048Zq8zS^HVViөM4(.|qW3ó⇌ XGZ\$$kh<E N #);d@Zς>JyGX)EVBaptbU>x!\eь[*M<F"K]nzNRo>+ | ѯLOo��JгD ,4Km<5 $_??G\3*|1<fi?tjG~QqQ^aJ�P_ǸB_Øj|mHs|[ç(Q~ZxZ=X\zƳ}m}O=ܫ gy$€Ǿ1_Yaʰ810zf*N =9ԩ9(-�L.#R|N*(PN.SVa&m~Ζ߳G& j_P/b-+&7;¬kA +q7R68;gg% o6&eΝ9_&pl8'y\a_4{ʱ55%0QZn3o�<%㆓h+ };\x�s ZX'P} YOуT٣iѩ'gƴc mgWG <3#Jy}BU\nUj`N*rX[{?]w6vcx uIaq$n:򮠎;wu)BJPn3%ZqkѤiSJSZT:u"8iM4?'�uskyZESZ]mIoQa%ņFSD-u#43[C/g\q /Oae%d02vs](J7Q5x#{~g> xWTn$s͡JRQXʽ|Xǒ/Rr*Ue[Uj|WgЄU�N��rI<{P4i$n-[odV�]?lJ4ί�ꍪYI@AixfʹDJÎپ" U_ <E)sSxvjܫ5)%W:J?75̷*`3n#, eRեֽ)te% 881ګjS?4:Wlt$.mGI.eX5gfwp�U'ֿӝjҋZӅ*q:QI]Z~[+q8]XPVbkTj4PNUjԜcE۲[So9`Xo Z6 ۚSXV[M9v�|pJx| &6+'W{T*w*+#i|p^,qӜsGkw0\,7l=:{Y6r/㯄'ďZ,+5>MG-#x <qXcW}4,xQ̮xi82?[ٸ`3ړ#IEW1xnҾ?<!j\O&#)xex]sӭis9~򇲩T|q>5ҼFG!푂O2$ld 82ۋ:YK+Of cM|. bQU}Ը343`+]R6Pm)+/׽[G:57u+5.XdX4g)4OG�/RpwS#a3~aSunTqezX=U+FT3ifNa5ueJ qZzJ7t*u#xN2iկ<#^t i7ֵy a M<EƒdCGyQ+όg|G&3cB(BotuqjXz֫(ӧ IG2=pVUc*ƕ )O╾ pWJjrI3⧎=Sr)Wl9OۣdH $J_MhU嘚8kVx.¤`䭈$ڥK0(? e&xƴmW])Wq v*rvr5'$t|!?>%h&{-6u}I),_<=gpWbWC6]4d7S"z4rܖqVMJ+똨O^);񋉩p5E ^aBYf:ONm7* [V�lorn>%$WVQ0}oC5]6>D6Y~`#'�� qyr/kex<[ѭ)+is46"Uj4W7dWV\YE$6Ko<#G,3BK�":taXrANJuӭFjѫUV)T:peO�6g SԌ:r' +Jdڔdig|; V-5]VM_֩<pɩ$QjyJ2ޘaґRK= 74sSx8`x )׆iSB3do ~ʬiӛ? a0qy"4>'%VJ4ZrgUSR9'P;|.>oT[Ok6s+Ӧ6al}<ROvCa_5N o^57:ZX)5ԏ4"N'{ɲ/FysO!NiO*<BN4; ̝ O�$`�#kj�dI��&9?^�A�?ҿˮ=� ?GW7N�ל�PN�V�Se_�H �2�r?s�o~'%줽~k6/A39tq]ZX]K~i P8e_ a麘 zQMt65)NUw�_n 8a9˫pVjtY_3\e xh飭u_uVӪ�FCcohɩXC[tOãho7к;Sc}} rAsx|˸5UvJ5Oat嫂U]UNE58Ǜ{GS-yW98,Tnv3^J;0>,#(ԡRPs mX!H5XTEUP�cB1"bcJ*%%CUVI֭RjdRYʥJ9ɹNRzIޭY5QWI>~˞<.WI/-|9ZׂtI&IX.d1-œ5yڭ\iG +)xRa| ֕&S&ӎx)^;_<>2 ej5-a٤%5p}8BgXq8R*֪}g)U�ef\k kL!_2\C.MM.og+U6]Q¤ R?@ߍ|.8܋4E>l&:eʱyn6lvz8ԜS|9*)B>^@��P@��P@�<C�.�h �(� �(� �(� �t/+_g߁SDZᯉ`P|ÚnLn PWpc]/z;z^_U~O l[M.b2X՛X~-)U%uP'�P@:FhꚎ1i}sz},9ahXFWSEzM'I49,c0lv Ntq8<e X.",WօJUi-ө FI٦4c��|(m} OtHR {$\k>4�EY�Pc!㩫G).e6ocs~_E-)rUiʥJ7g\3IoM`r<a#��|ELiwvSFc|?||Ucּ3?]|-(]O c+<Kҧ?K9xw1[D1Xo 0zrR,߈8ӺR湦+R:|5pӋc;5-~~=g|m}IG^-5?ϒw]꺽$Nsw)Nn)]94Wvӗ&^e<?eU.-0\B=0tЧ{+_Pz@sğ?)wo:j [OH')YgԴ' 'JqNNd=[4i{џ-Ɯ>!oc0 xƭ>e:u)ե:ua lM Tໟ� X?t?k uHiZ�ep�ݾ,K5wV1'8B-Zm.TgihՓ򟇿@?LJ<],*1qe.(ifyFY>|>"_L,Ҟuq H6WZ%K($A  9 #P &jٮ}> g{EOiЭ7x:4ZiVS_tX6ƹjW>DGw-+ѧiSTԩ˕% ΛRRjqkKIg>>mYc(ʱY>gd{K1.je^뵥:x ~<J(9{ߒ_%xYC_5xֽjSԮ#$)đZZCvCm1')u''9NSM $IhFap d<G Y"R ph9TR)U^J֝JժN9p'v ƫ5kG*|)umA�7+`$i/%k^g8].z]۩3|Q^ FrmVpm9ki�[~՟s/Uj��տy$�Y!1�ΐ�??h_=_?W�G� k<9�#?ߵg �G�4{j��տxg?p�:H.jڊ{;OOͭOHdx%4R)*Ȍ&VVu�sJ^xSB:<1jSJUp_ ӫJ:u!pZN2?WwIFgGb䳻eىff9f99&I-}[oWc*1J1QbbIY$IY-0 �(ʎVrHPKI��I$�IvrQc-v}G"8_w!jݟv?K[/¤/>ms[*�Y+K_=?#~0~�\cu9`k)b9OJWVm {K/σ|^<_Tc`�X#ȺplwSs~Βޕezh�)q+8絥OxL/)+:]u�,m5Qol]0Zn<w K+᷅OYzoO{̚n3V"vu<9>#u_c1^ڏ�xWÙ$j;|Lj*=&Yl8 N}ye_Vrc߰ǿ >M7OntxŞ'6crjVz^xeL:.1kСpSMU9JM՛Wm+4^u'Qpq)poN4l'4aVjtky"*ӄ1ex.k.��P@��P@��P@��P@��P@��P@_ּ]}2xD4*KPk3}并Y'O*wΜRӅH7 &83<19fg)J#Z<М$jgiBqjpRM&0�oxݦ㿄2O-A%`S$6^Zݣ�Pj+E>&aypW >YTJ+RQKS⩗Ug}Z<Jg7 <lׇSS 2W5J0Jk*=正�Ər ~%yi:i7d2E/X\p�E|Wxs|uOO$Ϥ N7OԢ�aESRݽ&~$&sdycKզֿT^zN7>ҿિFk8uv@SpgY!cԟ/��\_UYU5'e<RV""I?]trĪ0Pkm98'7'F/3)w?"_. FH)W瀾MR+& :jQlƗ2,4pM eNa S}RVv}FG/WYx\FrIT}{睔3_jt@�wrl Sr<.ɲ_ex<> ?cJ_ޔ\Y;c~i/2bxf"&iVM% ƺ 躗|A\gM׷ח,p[®ݨʠgWf^Eb|0eyfrVHsNrcI2X73Zf&j 6թ7#w$۲M��^W{lJeޝ {H1]XGs}0iI_gUJ1ն39{vt2IsQ;U(gׄϸ<EVRO yB^jcd-Ji+“4~|JwᏋ?X&7ZNnnȷ7+i o2ʊTםex<-eY%[ :ٚiJ/GђwRIq>K;8ȱ1*ѽ:u#:Μ];_m(xR?x^>0H7Z8rbrg$Z<o#�f|TiΦ,0Q~B8i֤Z)'9 6wF2liUq3R(~꣍jso%c�DŽ|MK%Wq6 =&1#|]fN.?,b08jR=Ҝ%>hI]8?On3ɱ|=ř&]&:3"ѯMKFtRNd7ꏁׇ4xcᯏ5Dֵ#U5yʠ]Ŏ+77ږ%q.0cGIE%ԫkj,-HPo4a}cgG:x̏<^RNO-c2N8yf*qG*�v�4 6|7?Û+U)%}j 1Hh&\3-xGqG` 2u(ԫq*Z&NpR٦7?B|4af<ga*F0Fք[0<IJ41SMI),YfbK3YI9$�?_S-VI$Z$KDJ%do%_kK|Z͗tYVᖁ[xW̠?tuQ"ȁ![3 qT8;úx,;>*qӄT)=hs/JPGP21g(⳼V\YNQE9Y8hc.ʔS_Շx/ǯk7Âm1"O,TaEL7O&>AqZ9 pّY|8l7!N7qUN*p.% SՕ d(vSJ?ʝ,Μ5xyږ:6s5R6JI{.?ڎ}ڕWs=խmM$oʰef�L#xg8xrnM+(O b˖j{^\Щ(J33.`T[TZ(I]y4OGf;Ué3Kom+nUnˆ2ZL82ۘg bjÎ-1UUlw ftap>u+exW$x <_/#主þzqӭJRf#{I ݪuZW٭\;6X}VW=�WU�a�%{>O^'t9okܯޜ~<!"5>]9w�Rj⧍"J]b[hؼunc1B7P%8RPT&)U|HFal ԡp5(8^Zu/<f.We{|I T-|¼'0x`ңJ>H'ZO}}w*CokmK49ڪI8?<b r sw,g9q2LF/% tҦ&w)Z)V?0~aiN|EyƝ*T)JRi+%K?GE�xYXդ],�4Zl.2 ~{RVI n)->ʦy>'Ď/ŘN5i崢;gC猫ɉݧR <�jqzsAJIUURkQ4(9p=؀Ad==z?:?!?)AF{导uiг]nsk#cD$(YfIo<Sϋ1СY]/7V1sS_˽޹7)F1^;U2l\BO/K�츉kVZ*5o)^ʓC$$lw+VWF�) c8g 'qR%iFQ(6֎te(J-QiQmI5iSOTֽ?F �R?j]] na [�T*G > #VXvu[PE!R+fX|e9&*" jU%0')7)0wi7~~;qЧ_ iF0ޕJpZ(e 1O,C^j;W_\Wt71<Rjo-uU׈u=Vـ9IcgW 3'##JjpfX6 /N* Ս׽ c9&sG8ya|$sN/[IbԣmMr_ |Yj>!&s%jsRndy8 ªƸEPֹv]6Y`n[L <6 FF4aݕ+NN_x_V5%V"I֭Zsܤj좒VF5v?��I/?A�''O�0�xC{ �'=�kJ�.J0�i_H;\C�^rCpOO;�5[�W7MG#/N��qM2ZoR7Se&xe+$R#a_ҕSRB#RhJHI'iQi4�e.?ZKRJ5TR'$?/#7Z,./~-n.%-č3xWWwՎt8}^+>%}ҥ|=)mYe(78]NjVӌ�+$K X8_AԩNsZt �.rcӏ6&J|O/ko'~u旬j6710dHl2 +&/`5<J=IRJkg m٭?3)f 833OP֡V2T{qR?U>ej/ivW4EچJ#Pw~մ:B^W<Rŝdy,ʳX5ؗekxJ6o&k>o-q:;"xirv2!)>fs'Fv?ʝ8J'?c UEOIv'J4R *kZIf̤>oGpW6ug¼$R.2Q<UGUvt z +1s,<Eǘ<Vqz9LjA.[ap+z8F"iq挿/5-OQֵ WW5MB[Bk˩55Wgwbŋ~ZTZJjU*֫9NISRMsov?pL. p| F QaR*zc TSQ:pR$~"�fob>+wۈ'OHXcm$7" @x k#W#Xey-hrӔӒp-jT~(S?gfKŜstLE*xp5fӒ100XUӝN_z(� �(� �(�<C�.�h �(� �(� �(� �!ß ^.|/ > 敭OÀˆS)  % +qqiMY!OrN'q50yA`s|It1~&'R/McR]gl�1�ڛ'�f $P{GH?,lE hZv7,Mt-zeROwZ�W>xxʟո%cЧ%%8˥f,iCINJ.^X��P@��P@��P@��P@��P@��gi7o_wY7~4<qU2,cM_u_rlog^0N N'7{Zro]<U%7g [QJS18e-:Gj;�M[TW�BuÒ8!{iڟwÛOs~E Gt83*̸K0BJIjә~|&�e#_- 4_^<cphc|Ks{Y2Q?2vNO}<cVxW R 3iO $yvQC:8cBOnh]� 赸7>,7<q>*4[=棨)Swfg5R_&O>2?I$Բ,O pj z7=x$t(С^Q!o�^[1O'dPx�dR؍Ć9xl=ѧB<_R/cUAJx!lPץSh)hl����::t~UPP@��P@��P@��P@��P@��P@��P@��P^49dEd7PGFʲ$2iR'EFQmJ-jj4M;ҒqM5fii?:h&G_,.xyl=fT!ܼiRh2-ñ5A�1؊eig<vPR!X0ZFR?7*UaT3*Җ'+8ԗ`'**IjM\cKHxny߁#˼jmjUQpT ©c_؜35N<E J&'/ ] 5c?>iSʳ* W]ilK&bw~g?Vp E!ǛwRo.,5{u~LCLj6k: SOqMuZ|UI7 p6N+`CN*qk^�&91~D% ^[(JCm,JWFW J\o |96sC )J^?Q\;,2<f;|xTi_Br잌/Gǚ͵_ۮ4_sje_ښi2Sm�ӷ0^̳lV*\^3sW>AkͫNN%0*7N*x."4(RoFV.G?Gۦ_~V]Uf sKky:dVVo9_<e\RŹJ*Ssd(K%jTbqSbq?8K8_:y] xFakbjk7wƝ/vO �(;+|!sm7~Z<Ogro]麍msE<mъZs*\8R*N}KԥQ5RXcR(i/׈0QY7!poa'6BtI(~ x�(xW<T�نfo"vSi!!Z;*&ɑxR~((6jJ. xTN['V'd&�A0|'QSB1x CJ418I6X\wB lx~[е vr̓�+"2$ "g,,%XڵͅvNO~g^W6f"f 8fw"T9lo(%(?)&o�)l�\B�4ػɩx p`Oo5ip,w xJ*UC-ڽl{cBUɭ?ӛÔjK (B2t\7XUZ%)etv\ɫBI6~~?H? eS$DK7M>vk�</jMk`AGsp.KV;1 =q0N.~uiG_O;.39˥:731zpX|\"ڜpJ^9m/a !QbbPh#4P@� 1b)()Y$IY$X�?jTV՜U9TRT79r9ɹJRwwwl@A/ৃ>&jqDz\ӂGw{ p*aUC"Fa}lp=_>q~S%0P*uܜg<Seʧ>jF;4L%fIIR|[/.,Ix`N*Ode$/'@&$e1凖3y߲M,F[NVIaJo1>< ӧcp0jV_ï:.W֦10yW /a>h9~ �=x�N~n_k̉a{_/5~n{[]S~#'\o˹{{,g�V翗.^V}#?W]Vh_K͙ n.oʴӞhdܩ[e8zs J/.%&7U2WU(r_ $x.R9-,Lj1Jꟳ/Y�6{kO Vmӏ ~ 3bm*ʸuKTTYB D VYj�^?Dxop]W< Ecq*8&{C.:Pd)R}N'n;W̱P= O Jϒ.*|ϗk(ݥŕvwpouksOoqXf@J:zVkVէ_V gRԥR JiBqi8.dEJp ҫԧR.8q(Zi5J�#|Cşu\xKVwKt>#|a,#-#⿸<*jq 7u^,pY:CFV%U4劷=Ya.מ"ޭlÅ1Pue)u2t#ZH҆;ۧ·Mb#Լ%j֓iVzH{ :CcI/:괜WRnSzT]o)6c}IAd+v3 5BU׏F߮�o!巁@j3a g͆{k2--,//-hT&8źx<+b'V]#]z+uwh*3lάcS(Qm)3<f`RS_ݡnZs~[�<~gƏRcaWVbCџJ%O ӗ\o%[~gJT<S>ʰy]0}67B2s1/O(~K/s\mhȱ*SP`RX de*ZB4yO9]ϫM/wEKyR&me4~=]<c1}G!^;�[ƼZd%,mK>cNP6*G6CO _1)~OSÞ?i[5M6}lCEХ[SϮ$,;19JN$])ٽ;o/T9Giv}pxHScKIN9i?f?%oŏ<K]V|%ũ^iK V;QyZ[yw[u$+,)ͰYlv<5^y’%գMFypuh LC&Ul>U#:hR_ϯ֏<) Zmi~'ιjzZEycy)U<SFRhdU)2c, _a0x:u骔E귌ZjEqq.s׎#XzwN q JH7 [[C�u8#yOenC,?monOb۩e2j W}RLO fPNRIӃz*0JKzMGal>ԥt0uW+dX|-JITM&�2?m ^=|Ե胰�I PZ5~6\ߔc<" 71ImS)Z_x<lƬ<Heri9a̟;+m]~RnzU.m_]G vIxĞ-SImyH<q(�x=:J?kJFJT)M-N!l~YXܓ8+Vk­L.Moyj4�|;/Y>X29czƥsƎx`ѭ$%e/2JX*G6NQR˔fz寋IZ3q/aqy%^!:87 gΜ]eLZuqsSN?^hzmizVZei}6v66v# 5 Q""�_ThҧB*th҄iҥJN"Bcb3,^'01Xv;^'֩qVze*jԜ9Rwo_zhr��P@��P@�<C�.�h �(� �(� �(� �(7��d't̄>|Ss}m~fЪ]ʬwZ _ΰڝO�^e~pt*p,?y:1fCH T)&HP@��P@��P@��P@��P[;ᵲE2O<QDHy"Vu&ᇥ<F"pBr^F8ϖ[M%} smK`arш5w7 '"X|CV4AHr_ ā]TXRʽyȟmo}&.NA?iٴ83ywG3FwVFn'w~| g\|s[| 7szfAF~_0cuwSq2$W+uch?|R<x/gZ  U$\ML0}Mg~T?M�?_M�%B,<+aĘ9Іld=QQ_īRzm+j?C,Tpg R+f|WB?fI&\%ׂ+?o lZIهZ5oNiӑqǗ++T^ O/ҳ6ԽG%K/M_L̀ኘn¨KGO1>[^-֭.><5 i4_ J4MUUN%#�8.Z)E-VK4ULvoLmVWUI7vb15*֛mݹMڦp��P@��P@��P@��P@��P@��P@��P@��P@��P@��P@��P@��P@��P@��P@�S5 -&SԮb-CmmnI#K1 ha0gj†5RjPN &]ٝj԰W8ңJRݣA7)II]gুFԾ,xǪ&!q^DƦNd 17o_~}71^ g9`&g!/hX,OX]a$_xy\eԋb})a Zʲ1߰'ƟFM A>*h7i -[d..gL}l!B4W}$>~Wp�aٖB# SOQԡFqqJ(F>Zwr9_67 ,&>U_ᔩҭL<jիRn zj~q�X|:~[|_RDmu{|9o4Sk-|nÛ ןCqO^N q2VS4 .ӓZ5diQ/¼|6MrHU~ӜpPe8j)Q[Q\YQU1'�($�uʹm%vDgWӫȿa e> /oBJx]Z*bEFV Wc�ۿ'R84UpGac*Y]8`dkWZVt? rø:<VTX[u!}U> �(� �(� �(� �(� �(<C�.�h �(� �(� �(� �(j_?g<S7-Ydҵڴ n[+Y  X "kѩJ[N-z;hךzSE<''B% II8?pZNq%emiՒS"xğ y/0}3>6OXm[An4FtlvO d,8J +JqNϾuc8w2+2<Dqy7eX +Ŧs,5<V*UN7Z)ބ6B �(� �(� �(@$O?j2NOW}|3;g/%Q Ğ$f{'M[bA.ժNIkEy^"}g_8xV!ʲʭ-_&iRr{$YC�-5OM\2>)xEҤXigCO8$ܥ/ lzqZK48�,q0p׌nRK3,d\KY5q .9~ }BSZ Di|2Gkڜ:R>6)q\!ja|Cm~XU~~|'� :Kcϋ~.jb %,~>³ 4C»8*zJ�_t\bq.1_I*U)`8&,ܭC8{s9g%.O߆[|#+¡oxzW]HHKss4yg7vӡF:ToS֯v*qo#ZӎeRNR{eA7{xlN*uB dV…��P@��P@��P@��P@��P@��P@��P@��P@��P@��P@��P@��P@��P@��P@��P@�Cu\E$3D)$RIuebzu'J*ӓJsHN.Ҍԣ$4h34 Qz+4O?9N<Q2y~Q<;%]<7`m/.bMb|w9OGG Xp^RZJgҡӕǖ5E9R҅hr�x'3% ˇZ󭃭 zWPj\öKWpg> >I5C~&K i,H<<LM%AgMN7" 9PqJ5`N%iҫM­9N(Q,3 ̲]l; 5R"*BKdJP[Ӵg>�h<Q6wυ6*mH^֏'t�y׺5ݎd.$i^ٜkW>3W<i_%թ)=S)6,>? _ ((,M bj?/?"YhF1x̷9Vik:ZԪSn^ʭ8_jQM(|@�W:d ZHj0+r2%6iBMQc dn>>JSdKUbUo(a*Oћ&kԡ:y Gj8SXl5\nmu-Ɵ|:$x~"srʐZ[#1OӬT4w4i%Co,'pWC*ʰҥyUVI)quzU['RYJN֊ݟqmٕ|:TkJs 4)+B]Sorr߰g췭~_tu>x:]Ѻ$چ,bՌݔhm7/\ E9qF}B[%$*[2/6u%6g[QOGxO5\:Jk&*R昇!.jxHDbeUی95]A [Đ[CbAQFDQF@�WRԝZJ'*'69osۓz61#B*0TcF1b$[- j �(� �(� �(� �(� �(�<C�.�h �(� �(� �(� �(�w"W~|1yb4o[k?^4cOf+IW|w12LM|su^+ݬ/m�/n76LìGrhթz1&(9OhRq?(� �(�64Ok%m躶][]3Fӯ5=B )&b,q,@i9;E9=o䖯sc, <ni:iʦ318XF*h%DQ7 Y?6ޔdgeNX\d\~ ^xrk1{a)5OnZIP_<_sߌ~Fμ]l^*qj8>^Q;jթ+1o |H-/gMV�AHK_ h"le%J0. O$TGr�rO.1^ڏp'ux]RrjKw_[Tamw?x@uG�/^jzO<5p×? W?nQPyy.?ĝZM.�ɼ.17>җpFJQgq6kM=Y"[)`OoMCੴ?h5 %Qmé~'][#w mU{p Qzy˙_J?3ϗyw-e%r$m9gFZhֲmaӬl,bacHE*Ftu$VKdGU֫VNukW:jM**9Nroy6-Pd�P@��P@��P@��P@��P@��P@��P@��P@��P@��P@��P@��P@��P@��P@��P@��P@��P@w;⇅uo@/uv5<1-[kwUY"+W|Kpss2Y_UV0'/Z­FgJF[6QqPtPh^ u 8JҋM.o�B|BTφݥ5K3ٷؑp=֗zUZ KJG�Mpln͢Jy[1IE(Lv[9zj,f]chev0Uj; ۜr]Zt3 gJG >.+캓V>vܥρdg$6֒ٹ^pN{FTWMw<CJ5^:LW壞ձkF mfW7O0 ӷ5Lօ)R{YE ~�gsH䕂+Xx/3۫K6ЃeE=+\ďr*rq Њrk劥cy�۔^鑂*f&콖Yp^wrWD��#5Vuk¥L:nWlI-@Fc;~3ιeN-үʥLExc_pѥSDQM_G'*q-Jy_xʥzxҬ7p*S 3ӞW8^R~y'Zw~0hzJj: YMb\Ij"(-!_;D\IyL2ľXapxt߳pɺxl-$ pJYNrW+dHa0Uoz"^|MWV7'(* �(� �(� �(� �(� �(�?<C�.�h �(� �(� �(� �(��� UO 3x%_@K?]$ԴDf#j9FXiW&;5Ji.j}=z'n�E%gJ!ON)P.P_Ğ2aV?͢N洽'VxdQ7ԩ�+U=ꟚwL�UiWJ:zס^WZ ԦN9F9'iBI,(@'8㓁SAJ2wj-Z%շf][ѿ dڗㄖ~{yz_h*<B1hvOj&UPW/\_7|3/8r/̈́x-Yj9esѥ xiM{K?�,|[75i;58`nO_'{=uy>2vP_ߋ][OiѫUjy6c|uÆv9GMqI[%$ mW5~|'�e<f_?inK @H/~"M׊eGC7#vQ8:'S)^WRS/ڷ|?'ˢӍ Ëfq[r295rٸ蝓M� 7`BSⷉw@V7�sc*u<N߹kIJWR҇=?OpT|e(b3nM˫A/ i|1k/zlqKchتCG�K3uf$l)ӦN B1-۷.qb^38)9<FfY^fڞaJ:V|P@��P@��P@��P@��P@��P@��P@��P@��P@��P@��P@��P@��P@��P@��P@��P@��P@��P@��P@��P@��P@��P@��P@��P@��P@�<C�.�h �(� �(� �(� �(��Ad==z?:�5�_DwG _#QOFX~ ϯj+t/ x^qQmYQ�fh孙汙f"xʄS߻b''v\K7ѫ 7|OB /dog96]N1aCC+4QUL ɫ ;_$~xs^C�u{E쯌ӧUvs{p_sqVL^7TMX(ͲJ״Sve_ 6~'�w�`>}B&Y'1Х0i$n<AyNva!g7ROK?9_?ڗ N4Qkf4�uZvttG_;y~͟-kzNd1>)<Fjl;k Kt)N^^󼿭ZQ/ <T|T3ϱ<^e9}\&Y,p%DXQQB" 00T��� q]͹7)7)I)7vնmzA@��P@��P@��P@��P@��P@��P@��P@��P@��P@��P@��P@��P@��P@��P@��P@��P@��P@��P@��P@��P@��P@��P@��P@��P@��P@�<C�.�h �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �<C�.�h �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(�<���>/߃'m5+]*-WW=JQsj}k)ʽ XVI|/H|O�>nK|S~_4]o:V.]3L[[KĞ`\\A,HK"DT!?,>~?o_ t_W/[N|5kIx{GuBCK]#^!\ͫqyZAp8گ:_'W5_j:֙aigmPJec6ww7V3�{��P�?N?^𧈿j ~ Ƴx{]eęeӵ}7S[oMo$ۼM,.іWBBe/nLčj_]sF0X�[VNbl$ky#XX�(� �(� �(��i߲t??'躶gjxV[}Lկ lR'KSd�r< A�-��P@��P@��P@��P@��P@�<C�.�h �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(�?,?ڧ t7 O eEȚ�?g߂~?uMcZ~TյmJzjWחחI=\M#,tx[/k ~7{yk]Υmnc-,P;Ί@�n,'ߍ%+EGH8O-b@9CҀ>�N� ?j/ٗ\snwKO0Ï͠ϣk'uKŒ_˯X̏K<pi6o[�x?ି>!M?`ػ߶nY@[OkLDnw^\c-N]GE5N.>?o~ںW &Ưz>C ^W|]O[MѵWnҵ Cu.Zd߀}@߉_�W_|4ޕWi7<_Uq�$N:mpIO<vJHJrQχ~~߿N|)O 2|j:Y%x2;}/FujvVj _EӠ0�~U1G7x�(~EO �oK?uS oR�A$i-o�j̟Ϗai¯x+}ql3`iO;?i:]M9P~(L->9<�oWͷj|}xLj|j|y j2 [/>m�r�_}zÿا1L֤u֩Q}ºxGDZq*vzx{k7HIt'�'U k?$?c??&[Z?qg=7ÿ {/<;ek^Aw:CKl]j>ۥ�F|1]_|&L|/!z-ݭ;˭6Z,wxDx�K-/Sԥ�C�omĞ!$?i:Ni_~8aqke[ڿ R(yhccid*]9_G [Ň/ 7oxv1|hҼO@j|:4m^4wzƩtm֛6yIYiY:wo7ٚ/|%m~ >7DԭnWF$]&G1x\g�lO۟Q>Y  |65c|y^[I/tX�3mwoȢٮlm--cE  [�}T> |@m4-/"xNZTLǚ_xyE5WPke)t눘_*9mg?^ǏxWu߇4={P<IB.5xN.- k<zϗj`P/)�z|wsxS|1ߋ?fl\j+\h?&Yѭ@><�I&? ~S}�K 3H񭎇jPY$վ7ӎ-7Nt3Pot<W\[?nω~ּ� h>2&xL- ew Ko*dYctP€ �(� �(� �(� �(� �(�<C�.�h �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(�?,?ڧ t7�c!S~-x@?>%|iML4>]{ҵVK+mZ++W!ljH\>xW/xG, 07^%o.8;ER̗wQ�=|u�g3�PGWĚC%>/jSZ9�.ImdZ6 bU^F(��k'q� > zf[{>ZG^~ƍ9xż+|nw�o(G;O$y'w|I6s?Zh$֯j:mbT~�8?s ~~5�~^%<5&wYk~&h%=$h ǀ@e�"mؿ_V▏gjz4CZu�zd~#${oVwG_xwT4BT� ؿE!5;ʍ?/ٱ bm䤁v>@@?A?�4Q�ʷR�ƀ??ْNg>¸-l b@0ƈ8�PnWǟ�N|#?�/\˦7 KTg4Lkkc\j.о˧ϥXZ/eY�Oks/-C dޏw7i RntO:Eq5DŽfO\\Gk>!Ҁ,�Sm|$ˑOoO|/JѤFs]VK?T>viW1/ 1w:M}ݵOؓ? n4 ޳*xFO |gl]Ii^ ˥Z͏?oO�fo!਷?�O'xA+x�7ϭ'¹"Eb%h:_ jV%<0$iSi(~_�?ϟ~�>-<HT<+|V.> +ĺe{x_,=Љ{x'܀?�'�+ ~'>/g_xm&M:}c&vRy/噮ydRؠz�j+KK�a^{] Q'hVFa@Uh!�u~Jg3HMk-ͼCѷT #�R5ByW_$ 1f]_[[ E k#EQj�(� �(� �(� �(� �(�?<C�.�h �(� �(� �(� �(�gƺdž|ZE ׇ3kv\o6\iZU1N#x0%\mPď$_PSWw^|-m+ͯi_Czֱ7;Kڋ)\jw$4P�~3�/*?/;~ "c?[)i{x+4٭|6l񮅩iZdIE5G@ �(� �iY8`7iu(K$1 ƊY݈UPI hgH�~_?||M/ُᇊ~ ;>?~!4 _fso i!^ ڌhoỽFHpM�'W?;~�^jn>B{I"z^hЭV 6y4O'ڽĚz>hWZڀ_�m_��q ~75mOF+×..O_j,u}>WhEмC#F"4:�(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(�߳�k_WTOmt=Oھ{Y4^eot-Ě &CllK{m![7�//m�{G�^|/ikPc|/ZR__7Zl!YLQ !E(o+R+>5�/xKo񥦳g↾O_x^ݴ -n.E P>�OĿa?gğ.xY|V:G l7.ׇd="[dբG+p(�a߈��5O3|ES>1^.5o x;@^|]/]u+O`w.2C�ʾ࠿!82| �g^%Zń:EGYw:%퍕ڞub$fi>hv ~Gy|gr^5M;Jɚ[kRF̶^fo^\h�7WS'٫I[IuW^b\ ON=F@o'G� _&WR|_[ _K</Z忊% o#Z=[Tм#{qfiko|Z>SZ.o>?m�O?]w'-m#O7J_ONѵHu/Wҵ- Hio-e�;Oc� G~?<u飋BC ϊ(C?F�/gz?`o̵�NO?`? ~/OCej>#5Ai}{T{-b(h-T"fPo|េyuo|?g5K6IӮ5sM/f[)nlKKYF D~o/w��n|X ⋏\| 'H㲿n<}mockYͩjKIfX,�OǏ~_nK߃>(?M}'D_\ :-2Q4-CT&t4_^ķl> ^x[-ߊ�o5K?S>2xRg#Nm~Z-E%K7HbҬS:G+vsgا_#<O _DO'|}O0R[Ӕ>v�aKIc�a?o~X/?Cobe_Zhڌu^:h qk.i=ѿ�k?7~-WöR>(+ KA!7CX:}$ ŲE6և|�l-|U�ew0~Ϻg74x緖]^5kh'~jCg,{f~Ϳ *υ$Y�5gu+~|'{w;>#k"Ԯ\%m��~Ŀjߍ7ď }hOm|iNյA߅6 ž#5gV5;bngIZ}Oi|O)x�~-Y𖁨Q힣kzFjvKiY9[#ٳO':_|0-5Gwz�.ycHu;2KO`m�Xz�(� �(� �(� �(� �(�?<C�.�h �(� �(� �(� �(�?�$_�9P*{-|�~_¿+WCǧɨ|+y\@6LZWyux�𣤖vJ'Ui?cҵKRKe<uㄇE ՐH;mKT@]etB2J7\~_"/_|;)ê?(5}#P<#o@iqw ]:s3��e[|H5OΓh_4-%7s"cmMޱ[$W Ҵ/Đ߀~R~zo��Ï)໿:>9 'd{NSZm>i֥a;-+R4Y.e8�]> % �~:UxRŐbN׊$5=%/jVu.»|o^~�imSM4z/ vi5]zeuo{/w֞ռ~"E4hfpe٧�/>ߴO 7ޯk߇?tcQt]KTXi,#9d|[u{m}�_OeO&OFcD~3T,<C-4KcJηޥi:ީxq[hV^(D4�h~m0 -^}>2L "�_g_LK~=(43WLWOee Fs5a-\G}wEqm&go&;]Y ú~*KXuC>\}[z ]xL_XYۭ<_P�~^�i�]~Ś&/HSζ_oKeb4d:^4m~о4�a>|%o|_M_ž2'_ |;D͎m)%ơ{ր>wc_v�4u_Gώ*)xKoI>$pˡX˧馕u< mcq6?ڣcǾ7Em⟉-K/Zφ>$]i:7>*I(_%{_?.2<Aqxz]{zӼaRPLޕc ~R͢|HmJ52mayYz�`?$G>3g~3�x!|wi~&|4PQ>V:b'e46C_G@>�);׺?.|uuVM[]xO>�%] ]>OM4kk��|F<3k'@<3o>u ?9~o jZMjZ=4{{;~GIYq~_�ࣿx? !;[4 J�)V'x2k퍞ֶ@F�j?f^|J/|Vo�ͯxL/4Tkk45[Z zҭki_k2OM}tM��V|E$�H>:eGxox|o'4τ^SoR?aZjVr=Zm9o?[?gM;/o0зZ.mniAhZ >LJ{{Y+UƟֈג~@��P@��P@��P@��P@��P@��P@��P@��P@��P@��P@��P@��P@��P@��P@��P@��P@��P@��P@��P@��P@��P<C�.�h �(� �(� �(� �(�?�$_�9P�4Oګt?~0i:=ڗEm5x+vk- ҭE�- �|9=|G�? +@t>37[{MkBKxSeXΛὟ i2ߌVe]�?L?mn??L>2�p�v0( c>+I?e�؏||]J+'nj!ZWof xCú-ʞ]zm c[h�7x/Z<m&¿i^m{_xgƞ)5 �fU-5MU}fW $s|/%HʼoEVR=79�ŸOciM&AƱk6M_@ѡrʂ]cV4uf*M1;ooI |=SUt—~ԵOVҮ!ҵ;=bW1g3yk,ҟ@|3;�'B7A6uƾ!}':Ư9,uV{ŕk4qvo~S'ğ 7e/'CQ+/I=᫉md$uK{]/͊Tx8eۖ�+ �#il|<ixV'(|~}ki7.au=S[K`Wn%N�4O�:�boZŰ&]_KzN}[cj߄V4 g=�T+_SO$/O>'m.5V+*v?Ëb.-m[NY9&tsP8Gᬿ�e*^-E~&n]Oï_xkZqAŢuٵ繑x�9�+�6={<H='V4&ݭ^^7-zpSa}kuesj0:('{*_?L׾0~/^xk׵/M E/4˄񅭎cǢKa= ̎O(S;/BǭC޳odI~!//[tEp2 7�p~#x_P~n+  CxsPmiԣ_[`�O?J�"/i�O OGGմ?IkW׍ hޜ!=@AX_ZY\ڭO�BN_؋U1<[j:BkQ񍭖`-4c=sn%O(L3/�Y ��]D7Ƒhߋ%;+x[PAs?ɠi+Փ@޲փNҠ[x$fu�'��CV>Coֿٟ̺7u|HQ?5<c{]WľӮ<AEXMgq[j.6 h^0f~̞7{P|4u/\.Eu/Mj*v+χ,Ե-6jǮjK@ݚ�(� �(� �(� �(� �(� �(� �(� �(� �(� �(�35gJ捫]ҴMLuNAivjR#k<$Q��|5�^|aҼ']_᾵D-#^y x]ZNE[ii0] m阽̖�K}/ۣ@��P@��P@?�/@>$~Oo5_?4ml"|oW>3cA{#uԢĖ1�~/~֟ ?kF}U oǞ}t~_}ωb|a[Jkm.F+g>e�z7qV>,h~n> x|;5'ÃNie/]Ej ɶ �(�s>-�|#P𯂼9xگ/o�<?2Xuך;iwwWim<8#g3_& |W.O?xo{\|7/?}_Y�i3ZK䟳w%OO^4' |G_x3ƾ=[n-":5J>W 5幖��mi?sZ�?~|1Ǒ~nM|:nVK SRkZ) @]P@��P@��P@��P@��P@�<C�.�h �(� �(� �(� �(� [K4OD`Vi^m,5 im/ 󭤆:i#`)ݾ)½�x3?��u?Ox'zZG^�jx^6?d�k%~�>1|XលᏋ%|O񍾭[I)ӸhWZ4 L. Z::q_j@ |m)|1/l|n=h|m� G4�GtѬoFXKW5k:}rj ��cٳ֗_/f@5ޱ×^^Դoz}f+!Y^% 2GO'ZWѠgz/7}4|4世Y- [?{#ҦeBO|d?g¿ <x �,<i*@׼5dڔ+Wֶ|K}|u`w[ B@3yѮoߦc(׀5[IG PjZ\tZI3> |+=#׀#;ݟlO6Wy5-N_j{ wu3*C�+`�ڋ7;Bzsxωkͽ/W,|E5=zSYR4V @3~?}q~ϟ|7ex^'7|>-{MҮ䷂kIgi!cf_#寎]SZj߇Ak_桥։y�+vnZ�hYܛ[cay?Cஃ<|=m΅czPƓ-OT5Zo]k7zͮZvڜV+DzR+h> ԿLO�K5ܽ:o/:w'yEcifv&e�:cAžǁ|7x;^|'? G56G].1vZA kX�7U3O<S< E$7E<%m~>Y }2tky@=W4iZizvkW:nVږiװ柨Mi}ewo$\,3+�~Zj_Do&V�Ln^ 7;<w"~1IG;F`Є�[1l_�f~O~+Rtój?iΏrx"�úici:}q&_6k�¯x/Ꮐt<oO cKѴ=3KZu}C:m5KB..`-,|7'ů]|@ğxWNjA. Ӵ߈"m�k_# �_֔47E?~: ~onm'&Ѵ;J_KSKVy.up~_fo3ÿ <ks%hu NX`WuB{sz֖Zj[Zx@�P@��P@��P@��P@��P@��P@��P@��P@��P@��P@�~<�n>;k �c_?kρt{1^,|j`*et,x*f <k>|3'o�sLO=s6~#6vk}& 3;Go?AsHDzws j%c@߷O�i�5t|y'S=jM;LՓL46K5=CIʡeuk'ݖRV?feo/  e]x`hz�/ kkxM�xKZ`� vKE4=CR�� Egj|3D%�do? >$xöZVHEψ}^[iVڤ_e{sF{aiYiֲKxC G�>wƿƯ~�O~0+z.lU]K.Huk[O4kDѼQjW� S"|I׈o~xo}{�`4([.t/k/4mk^n4YRpv?lڏ<u/m3/,4×ZkXΥjxJҠI[4+�?OW'?jٯ}'Fm_?tt XYX^jVܛXnt3k4Ei�>n<-muԼu4^,m̐j/t[k#Gd3!e_g�d&�W� д˯#7}7}s5_axfMIcjW~;"K(xso|xO�3з]kH³3^# vPV?")7B$��c{Ogτ"I)V+::m棤Zj6 gwF]GU6Y\X��RߵĿ's/iυO_?<ZxHж Vi&:}VFоt?hzwǟl~~c~?nj|9cC_S^ ޫaw:&t ^^v~Եp ߶I_|�no W^9>�eӬ-_nK5]g]֑*GWZ^mg];J�q�?q!~~!|ҵMAցIw־,xĩ�a-#E7k^i@צhZy#M'ZH>>.|-D[ڶ .BLgq n#4i=;I&v85~ֿO:Tu-#O/9-k>"UU.]GNU�k Uu�h_)W>.x[z/>$Ӣׅt&DŽ==2[hUNۼCq4sx|-2ԿU?gi[߆.f|w ꗞ  mp>--;sj:mp]jm@>ioO& n|IǸ'|,>' dIXzvh-eqgZ_gh6z�=G:�fU$O(_5BO#^Yl3Ų!}B.xp-@H�?_� �(� �(� �(� �(� �<C�.�h �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(�?i d+�~[Mc< m~,k޿pwBNJ[LdPM_I{8-4?oo3 3D~~4о=x%~Eq-?,#>E5_O_7igu{g kV."�V�T{|/u~"x'I~)AnՂxfe5,w>�F7?d&G�R]~?:?:5�k/떳5FFT{[SƟu3eUtYW' �o7:1C>#o?#B_z4SMmżS5+OkS;fhOqck:ny?O5y:xUH]͖4=DZ}cp3@&K6ൟw=wd|It� <E_ƚG΅6mF FkOHmtKGk+('-׃In~ ekE�P�kOZI/2�?ZV㻛C۫[Y.ma k2ymj'7�(�j_j_3\:|q?:/4AecjV A|@ֵ[hlg3^kP�Ɵi'Eÿx3TXi�,<TdiCnwϣ][m2M�)]Gt$Nsm{'_!Ҧ·ַsie,otxVp(I~0@�xz}}7\/|G/9hp]-kQȸMt5Y_o+c{�[?_qOr5XgOC_0]i#6f?ϦkW1n&sC&>% *�RJ~?5w|;O>-U[kH|+y{M/^ -m^�MxG9� |X<_?-$6m۝7:Oo.刺B:#1}XV �şͯ`#I%H:mDDپx*f$��'<P?A ?r#Duvfʤ0O+f@ H;�?yi�#'y�7�?k_�<s�fmi�^ �(� �(� �(� �(� �(�<C�.�h �(� �(� �(� �(oS~Y/#Npuς Y=uK*?ӭ|:!ЬmV<m^^E?oɆFy>4~S#ᯊ>| �BFԼu/ei>0džnu_ލt{k]E#M.7_(6CC �t3K?D׬[^6[MR};E񞫤ɣxz<aGns>$]Otτ ">x.o-->Ko 鰿#6u� !a}{g w^]Bq(ߵ죠x_Z1F.<|^mGzF4˝_Q]_/L/S;g@Π?xsxDMwĞ#l4=E-S̹umcSNӬm综T@M/A5ê|>*uM/*? Q5{ٴ*_ílj=Rm/OђSCۼ` __rЭyw|}-[|,4OiýoPSѼp�)h͵IԠIlR D?x&_gmgީ/t�iw4kxҟJ=2FHh8f/>!? �hUo' S+׌v"4=n%w$G.遊@@^ߍ>žo1]ۿ5�w{yK} asyi3[15Bt~�¯؏ugŝE.g3qG_6Ňa(ޱ,3[hzDWzLitK?w%Oh|N|fj �eMoX".}+)n<O{kj]j&'?~ZgEI&�<wIbuOؼAKHʌQN' p߉ -v/*~yma7<U@ZCOk7n.<w("0|?G^D_<:<a j�#cG[^KckzNsB�ö�g oŸ>>? 5vO $| x)o!֣5&~=;H]?ks~4<oG 2۷OgzatHmt-O]ԭn.c'nYph۵mkGt}K:武kZƹ_Z>ֲjK ce חSkkkʐ�xwk_c^|OI|~vs6~bZFfOz3ړi > a)>+ESĿ F|I�m0jæ!x j; յT+?ũt <isI3jյ.e{.i&B=)ǂ=C2/ i1$|MBMjڭŭO<B\\D, wU(cP~͟> Rw_ZZȑO}.kWOI@A&gPA`|4M_�?6Ԟ6fDn5`ro{± M�yk/o9Ꮚ�i>%ş>,MItٴ v NQT{h&;hed}@@��P@��P@��P@��P@��P@��P@��P@��P@��P@�|c!~? c)3G+>#K-_:׊uqQKe`\ Z&n.Q׵IUudu YX`A94 s �Okjo2C|C׾"T(ӛGSd|=FM&#N|Sm?y4^z#?'/_|W- RuK#}yy捣]ם{*Ѵy;y7*-|:�koYEa WÚv:u|kO5Mj) uOݛk]-%k? ?_+'k�� o�5WA/}h+T:W='ԗ.h7Vq\K?߀x�-s #f׼o>˿mS^r|Nk?�[Vm坥e躅5`mwzb�=g?r/ j?s$ ᆼ8mE,zM?VPgtN-2Y�ßSWwp>$wx+P 4WJѣt4mlm]:eխgZ6764񇃿a>#܏?*: ^xՒx E|#jDg{;i/kRʗ&/f�3_uW">=_|B"o/v_L>PQ>0<C XiP K|9čkJox6<1jn/'PԺ5ogmêZ7QM{rˠ4 :Ÿߵ?hI[ |Bv^h9BVsJь6I-�a5}B Tػ =70čbZ-6g6l7w7LҬcS}B%Ҁa~M׼//~aTP[gGnk%Ŧ.6iڎgy{=݉Ҧte]Q#� ) Ž~!u2a !�wj~WM]Ze4˟.�G]7Q�boZ3x [>Kñjdj5Ɛ>W+s" gM;d}giZnZ&cmΟyjmI=_XvK8)$N"?4�c�%O؛ⶻ�/oj ӾxǶ:mYqáAY] 6.j:-*jZ<p2Ŀ e?Ŀ 뿵OĹ*|BGHC>-}?^i0ƭw#rD`|*O|A.hOw<gu_jvgou[01%kK.| ;#6~x;rk mgu^rz7^-ޫ}|]$zCKc2A{g|*u_ۦ- |[>Gòj?cQi-BM<L].ܛ<a-h܁�>à �(� �(� �(� �(� �(<C�.�h �(� �(� �(� �(EGj߲�| C]^xw.|[ 85/ԟėZeR`,vUb, XߍPW_?�73_~*{X|;nDŽ<M5}m.>Dl۹OQ&gay$jB4>�eس -[U<Y­׼s˧]�vsE-֛4R{]B{[9n^ Ai�˟EYo^o>~�_wZj?E=SHT¾4 JnHu; �>+>�h�ox�%!oƹ ҏ>w:pVotu\[+@dZm:fl׉t~�S9kx[(W=7Ix_�mx Y=J8g4}#O$kS@L߲߳V>Vo-^-'�_ x/~-|?axV- KYt뛦YnJ{-"_dL?_ �hKCMf<A^|WϋPi(*xsU/;aYKrl/")w�F#wE�񧋯�gcG/zj~u>'nèx Wm"ԡ<Ox5 _i0ݟ��?#k|(j?]w¿^Kx[j >k&m6G-֡e�Vŋ�/�V[X>a)j>!muгm$%?0o=�}n?-4^>!~S?dω6,Ï\'/'ɨE5v.5ZSLC{@*)b(HhXfHEȤC#*A �7�|�j/�࢟o�j_? |8]_s=>wy i}zVGt +ݵ+ �w|So,W|)Ixwd"�X~txZ=Y_ xQ]&E:|/Iop o?/ E|1�]{'O\;i = ?/ž%Դ3W_Ckgyu_ i!fr�zSwŸ _|=O/Q|9jSZj}]G>}zfvzŔ.M�}{�[E6ᮣoeyPj~}-Gŗ7Kb[CGhcOg&34 �k�|M֧e ~?4J:v7WWKs4:.u=EWp>4m*(Ҁ�h�t~a/?~7|*Ҿ3 ĭ:; w1cow6K'QUݝkĺD2/ | gu/|e(̞0~#|9񷌮3C<AE#DŽ&Z\h3kZ{~_/S �.7O>>?J7¿cϦxWYюWS`%ӭMJ/l쥹4}J+X5kcG�?|pZc6=~5U3x[Zoq'7%:~eCiuCJ (w&fρYmB|mSK|F[ZFs}uwNaƠkzwߦa}x �L�lAo7ӴV÷Y+sMDԴ+Kڮ,RJd%嬑o"l:zo� ~Ѝσρ�>$i_5mSY~*j??O_x[MIo8|?4ziO#?Q�l,nwqGP]Ϧ8OZ`$Ht yҗAyfy �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(忍?D�|jA9'5 giy5l|C}cl#R"�zσ� xJ~x?ៃ.'Þ 4im{tT_[iwCu]yח;ϙ-�z=��P@��P@��P@��P@��P@��P@��P@��P@��P@�<C�.�h �(� �(� �(� �(1�+i^�cen?#?O a@=V1_%�>-Df&/z~yK淆T<5-~EiRMa,洴~�c�`]ᮝ~_ߴ,?༿:X�ZCǥjzMcU`nFu}⎳ X|S?fCW-}gRVZvixI4cJ3ڽsjWE#?G#�.@ &?"ZO8O �{BĿߴ�'s㷇�g;U~Ů2()iR-6^oiquwDZޖ?,W�~̿׈h(^ g]^𮥧XbI-ׅF6^@3xG\xU< u6\].9tńl\hkndN{)N{vH݀??0o=�}n?V�hs5j�~)MX!}c^*Ӕw^L-H]#ܶ5 Rp%C3W�*l�|bC;oߋ l4m#Qa&.ڟd%e𵮣mBN qo~e[�c|~}>߆�h_hWtoKmackK,֦o5"N7Z^^Bu�i[� +?pVGL~<i<Iv;|QwKY^@t6h/U�[[�ٛo�HfJ�� ɀ[K_(o{lsQ<Y��j BI_'ÿZxo /$υ_Ux]_S5?Ï{qo0� dny?/k_5YZ͆St#\fi,[}&:uH�4}FTm�S�(?O?S�[�^ �ҽ>�x��'x�V7@g�H#__F|,/ e|9?�uMRB]kzu.UOaѷho|? 7ۃ[#43oOE&ĺnomiNǥ>i%ޞSYGc &;+]:e.$O<+y{eqx涞9Qᕁ?`�n/2s�O�i�(?�j?Y� �(� �(� �(� �(� �(� �(� �(� �(� �(�`J P@u۹I #ܬd0ae'C?�l�~& x{Ϩdl9ai{9HW9H6?UsU�fuχf ߏ|AcntZĞ%$M[cA; IiyiwmKMj6�b>8~CJx�.5iKBд/j&q:ÖV:QWPqua/ɦIވܢ f{i<TزyS*11IH2"& ű!r4ndO=GTiY JI TPO ?hkw f�^7ä/$4Vh;iTE]uVP@((LYx_|Pk]>"x5i#B߉uSSk+?:oڛ"͓ʍwي�(|??>4�hi??τQx7Þׂ<G=#M{jK;ٮfּAˆty�?:�^�m_+_)fo:F_¿q'|93x~)VQxW/4i,}B_ ee5 +�?J�+yhฆgɹHIm'2d_c+`(j��[-NDuFE0Į ]TE�~�>-|U7~~-MK[ѿj�sG>#x~6tY妅&KK.ong`z�& Xy%HbRCI#")feU@@��P@��P@��P@��P@��P@�<C�.�h �(� �(� �(� �(� �(>I-5 3O,7}k-%̑<ʂL'4@��P}^]6fm_u7� IvPs>�hP@�NNK ;,g['ivG 4L/f9€\??ຟ�2~߱7>%㿂<Iq ZO6WTDM�~i=&-:8-K?68cdi`2"�n� �֠ �(9) 995)cq7I 2(;KK[ hlm-Xm!!H5uG`?��P@��P@��P@��P@��P@��P@��P@��P@��P@��P@��g'�6/Mx姴<OQCMGN7VS̶ �VoĚE@.:�  's^'ㆯVoԼ-kGhJFG,B 3 |:Isj4RX55ׂEym0KAZj mbڽM�m_�O?ػ�h?&oi�xW_�h{Q棨yxI+-R(n g4Ek@K{<' ;�0~ xJ~ $ h$I72Bb.dmX$?cO&g?vߵ/-|YUx:į>3%džuMOɦ/xn- K?^ji*a[|3�R � 79fiM<0jʤ(�~x~Pwz�{9?f}:_ k<|;%惣XxP=;ݭxSKi5,ZAӵ hZ�>#HU�]jTԼGKn=U/]3P9.<CĚe"k-3H� G-?Q/� �b_(.EW~i8EkZ{Hq wpo�gM=>$[rLgk[:0�,Ȳm]Kqc~� > /~/G3f?oOGƠ/ooԸC,7>u㈃&/|yBX@?(O�&º7'٫T5Kcח^ �<!mOm.mDvQj6Xij6~�<�b�~? q_~(OGÚk-&[ԬH6w͞x[S[5/m4П/|g�5V]*o]'▶D6/5$ ]y`>?^%~Ծ7goU_ ĖW5?&{H9Ḵ+K/CX?iy^ͧتۡCen�?Po4nmi~Hm/|&_E%źGC\.j|U.ͶSҀ?"1'�h?�j�f㯏uKuѭ'* mCWVPZEںi~o[�˷_7|KO|?|cg𮀶-&-CZ}֝yj:^xmQ =,=Oᎏ%GsO5]eht�'iL"h9\’ialY煴_P��e?c,S5]=O76+iv_iw:ΫHckMʼn$Ilqn�j i)4׿ muZѯ>5x$"f J_h !~^C+G?}(� �(� �(� �(� �(� �(<C�.�h �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �k:10 ]X2F&??U�uUT{Pv@@G,''b*P>!͗�ѿj .]cTVi |+|E7د-9[6xnU\jm}KG;-"�\~=�.dσ�~v/[_xcLu#YҧS#:oؤƛua;GR0kj"�k Sym�ek9R{K̷FC$?c4)*RH]H$s�t�i��L� �"��`Ko_;-?k>0k\,N|/6u޾ZFƟKx\^E?H%٪~ |CE;/><TKu &]YcbGo]gK[$'8mt}OÏ~@·D|K*5>Vе�ڇ^5*15꺏GO-[O4Ue/ kL.(]3&�lzzvE|HT�{kMXk k?$iFSiZ^DYn/v66F8m-a"ET(P( C ~_O:~%>%Ef º[#y Ñ=従i7[xƦԼn4{cR/O?xO>'jςU4O^ 5*{O>=<3O\\i]6NSu#+7ڇV'/yk4d2Mms?3A*2$ԂA '9gL�n�^6O�_M/&?ۗ<|K>@/O|&�/Þ#lc}7:׋o^ͣ%84k@?f�?׉io_?a� �4=s]'Yx�"x"[6Ϳk,V6v({ Nk7GN&�'jo~*>l�h>)[z+𷁾!xķ0\~N`փiZPX�O|I� +M;\O>!Eɧ'|ijZ}u5 x+HgTWk\R�G A_N,�B\_x1a־ BZ=TwZZ?-fHm-|g\ �'73oƿ|A$o9u^Z@.ͦG,&tZGqF~P@��P@��P@��P@��P@��P<C�.�h �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �{egYڍm=]YD]Z]ΒAsms L;"21Z�/gË�� I{u'G@WQ~'3L[FZ<}h|9}[ oEv>_xsG_d2[\\Km3۴GPd x#ğ |94|Ҿʹ-xo/j1j+HCϣjA/E~m͗,�v' 7ƾ>.o'u{>Oiht{W:m4[J***"DPUTaUT`*���c�EĚWÏ>~25 ?<)acP,-.]�qy}%?WO.;]W� xR_ ;o xkNŽaUж<b �Ͱ+oK|d>3Ӿ/?ٚ[VF?T; izm.I`6cbD��|"O |<swm]�x^Nj{[[<4u]-U/ \�~|/Or|^O_C|O_y~!(5w$ cA6tmʬԾ%0yQ&5�^mu-[ֿjwW|RKcg =�4<}o _x+#ڛD|q~yntmrOkiq-nʢX]Cw=%|5J6^ MSū"][/>i6^LB/@S/ [‹x׃�m<=ˤh x;U;\OhiLAm+@UUQUQ��(sW+Rо|2}Du?ßxs:VkK;wR3ivեƩuYZA,($+nop|h/9~1[X2ĞKMQiG1X.yi-̈́bGF�9|� ?~ -S@/U}C,icNҒi4˧\܉7€:χ_ | Q_ <'m,cE𖆗3]eVV6mw>7O\Ni]hc77Ǟ獼! ScxW&O vFH6zz(6Bd7ٹ?OBѼ-]H׆|9Xh^=BtM&+/GѴ:m?KҴ( mള+{x4E�נ �(� �(� �(� �(� �(�<C�.�h �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �<C�.�h �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �<C�.�h �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �<C�.�h �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �<C�.�h �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �<C�.�h �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �<C�.�h �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� �(� � endstream endobj 2 0 obj 1024 endobj 3 0 obj 740 endobj 4 0 obj /XObject endobj 5 0 obj /Image endobj 6 0 obj /DCTDecode endobj 7 0 obj /DeviceRGB endobj 8 0 obj 421385 endobj 9 0 obj << /Type /Catalog /Pages 10 0 R >> endobj 10 0 obj << /Type /Pages /Count 1 /Kids [11 0 R] >> endobj 11 0 obj << /Type /Page /Parent 10 0 R /MediaBox [0 0 1024 740] /CropBox [0 0 1024 740] /Contents 12 0 R /Resources << /XObject <</Im0 1 0 R>> >> >> endobj 12 0 obj <</Length 35>> stream q 1024 0 0 740 0 0 cm /Im0 Do Q endstream endobj xref 0 13 0000000000 65535 f 0000000017 00000 n 0000421553 00000 n 0000421574 00000 n 0000421594 00000 n 0000421619 00000 n 0000421642 00000 n 0000421669 00000 n 0000421696 00000 n 0000421719 00000 n 0000421775 00000 n 0000421841 00000 n 0000422018 00000 n trailer << /Root 9 0 R /Size 13>> startxref 422105 %%EOF ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/messages/shattered-2.pdf.sig���������������������������������������0000644�0000000�0000000�00000000167�10461020230�0022223�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������u��!9�gս7QX}_| � 7QX}t�Z3DxTC45y�qF3|L<b QK���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/messages/sig.gpg���������������������������������������������������0000644�0000000�0000000�00000000466�10461020230�0020110�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������3� �!?AWdar#fx%(Yܗ� r#fx%(et4MSTX3c|R#9[)>AFS|޽x\bN@ڔaS= }1zc(# .3f求ggu e[mNvX'-t̹l0Ga/2%.sS=E(F^9rR9+g}#`QzL9#}vd/Q2 nd ̄Yv 4p>I\U7'0&Yfp/[VCݷ-q����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/messages/signature-with-broken-mpis.sig����������������������������0000644�0000000�0000000�00000000343�10461020230�0024523�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������-----BEGIN PGP SIGNATURE----- iHUEARYIAB0WIQSHd0YfKgdOvEgNNZQZzByeCFsQegUCYDa9/wAKCRAZzByeCFsQ eptbAP4r+RChXfvGaTNgG9iTUygu35EK+9FZX/5SeJ9GzMP+5gUAAG244XB4HazA QQdXt6Lj55M7m0EeBbsKghZqnE6IgAY= =SHhh -----END PGP SIGNATURE-----���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/messages/signed-1-dsa.pgp������������������������������������������0000644�0000000�0000000�00000012320�10461020230�0021503�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������ �G`k\Gba-cypherpunks-manifesto.txt[SA Cypherpunk's Manifesto by Eric Hughes Privacy is necessary for an open society in the electronic age. Privacy is not secrecy. A private matter is something one doesn't want the whole world to know, but a secret matter is something one doesn't want anybody to know. Privacy is the power to selectively reveal oneself to the world. If two parties have some sort of dealings, then each has a memory of their interaction. Each party can speak about their own memory of this; how could anyone prevent it? One could pass laws against it, but the freedom of speech, even more than privacy, is fundamental to an open society; we seek not to restrict any speech at all. If many parties speak together in the same forum, each can speak to all the others and aggregate together knowledge about individuals and other parties. The power of electronic communications has enabled such group speech, and it will not go away merely because we might want it to. Since we desire privacy, we must ensure that each party to a transaction have knowledge only of that which is directly necessary for that transaction. Since any information can be spoken of, we must ensure that we reveal as little as possible. In most cases personal identity is not salient. When I purchase a magazine at a store and hand cash to the clerk, there is no need to know who I am. When I ask my electronic mail provider to send and receive messages, my provider need not know to whom I am speaking or what I am saying or what others are saying to me; my provider only need know how to get the message there and how much I owe them in fees. When my identity is revealed by the underlying mechanism of the transaction, I have no privacy. I cannot here selectively reveal myself; I must always reveal myself. Therefore, privacy in an open society requires anonymous transaction systems. Until now, cash has been the primary such system. An anonymous transaction system is not a secret transaction system. An anonymous system empowers individuals to reveal their identity when desired and only when desired; this is the essence of privacy. Privacy in an open society also requires cryptography. If I say something, I want it heard only by those for whom I intend it. If the content of my speech is available to the world, I have no privacy. To encrypt is to indicate the desire for privacy, and to encrypt with weak cryptography is to indicate not too much desire for privacy. Furthermore, to reveal one's identity with assurance when the default is anonymity requires the cryptographic signature. We cannot expect governments, corporations, or other large, faceless organizations to grant us privacy out of their beneficence. It is to their advantage to speak of us, and we should expect that they will speak. To try to prevent their speech is to fight against the realities of information. Information does not just want to be free, it longs to be free. Information expands to fill the available storage space. Information is Rumor's younger, stronger cousin; Information is fleeter of foot, has more eyes, knows more, and understands less than Rumor. We must defend our own privacy if we expect to have any. We must come together and create systems which allow anonymous transactions to take place. People have been defending their own privacy for centuries with whispers, darkness, envelopes, closed doors, secret handshakes, and couriers. The technologies of the past did not allow for strong privacy, but electronic technologies do. We the Cypherpunks are dedicated to building anonymous systems. We are defending our privacy with cryptography, with anonymous mail forwarding systems, with digital signatures, and with electronic money. Cypherpunks write code. We know that someone has to write software to defend privacy, and since we can't get privacy unless we all do, we're going to write it. We publish our code so that our fellow Cypherpunks may practice and play with it. Our code is free for all to use, worldwide. We don't much care if you don't approve of the software we write. We know that software can't be destroyed and that a widely dispersed system can't be shut down. Cypherpunks deplore regulations on cryptography, for encryption is fundamentally a private act. The act of encryption, in fact, removes information from the public realm. Even laws against cryptography reach only so far as a nation's border and the arm of its violence. Cryptography will ineluctably spread over the whole globe, and with it the anonymous transactions systems that it makes possible. For privacy to be widespread it must be part of a social contract. People must come and together deploy these systems for the common good. Privacy only extends so far as the cooperation of one's fellows in society. We the Cypherpunks seek your questions and your concerns and hope we may engage you so that we do not deceive ourselves. We will not, however, be moved out of our course because some may disagree with our goals. The Cypherpunks are actively engaged in making the networks safer for privacy. Let us proceed together apace. Onward. Eric Hughes <hughes@soda.berkeley.edu> 9 March 1993 u��![*#kwG`k\[S� G`k\K�6L$P3R}֬`FNlHk�7HrB<lSwKvk⒄sEW����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/messages/signed-1-ecdsa-nistp256.pgp�������������������������������0000644�0000000�0000000�00000012320�10461020230�0023403�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������ �hFGba-cypherpunks-manifesto.txt[hA Cypherpunk's Manifesto by Eric Hughes Privacy is necessary for an open society in the electronic age. Privacy is not secrecy. A private matter is something one doesn't want the whole world to know, but a secret matter is something one doesn't want anybody to know. Privacy is the power to selectively reveal oneself to the world. If two parties have some sort of dealings, then each has a memory of their interaction. Each party can speak about their own memory of this; how could anyone prevent it? One could pass laws against it, but the freedom of speech, even more than privacy, is fundamental to an open society; we seek not to restrict any speech at all. If many parties speak together in the same forum, each can speak to all the others and aggregate together knowledge about individuals and other parties. The power of electronic communications has enabled such group speech, and it will not go away merely because we might want it to. Since we desire privacy, we must ensure that each party to a transaction have knowledge only of that which is directly necessary for that transaction. Since any information can be spoken of, we must ensure that we reveal as little as possible. In most cases personal identity is not salient. When I purchase a magazine at a store and hand cash to the clerk, there is no need to know who I am. When I ask my electronic mail provider to send and receive messages, my provider need not know to whom I am speaking or what I am saying or what others are saying to me; my provider only need know how to get the message there and how much I owe them in fees. When my identity is revealed by the underlying mechanism of the transaction, I have no privacy. I cannot here selectively reveal myself; I must always reveal myself. Therefore, privacy in an open society requires anonymous transaction systems. Until now, cash has been the primary such system. An anonymous transaction system is not a secret transaction system. An anonymous system empowers individuals to reveal their identity when desired and only when desired; this is the essence of privacy. Privacy in an open society also requires cryptography. If I say something, I want it heard only by those for whom I intend it. If the content of my speech is available to the world, I have no privacy. To encrypt is to indicate the desire for privacy, and to encrypt with weak cryptography is to indicate not too much desire for privacy. Furthermore, to reveal one's identity with assurance when the default is anonymity requires the cryptographic signature. We cannot expect governments, corporations, or other large, faceless organizations to grant us privacy out of their beneficence. It is to their advantage to speak of us, and we should expect that they will speak. To try to prevent their speech is to fight against the realities of information. Information does not just want to be free, it longs to be free. Information expands to fill the available storage space. Information is Rumor's younger, stronger cousin; Information is fleeter of foot, has more eyes, knows more, and understands less than Rumor. We must defend our own privacy if we expect to have any. We must come together and create systems which allow anonymous transactions to take place. People have been defending their own privacy for centuries with whispers, darkness, envelopes, closed doors, secret handshakes, and couriers. The technologies of the past did not allow for strong privacy, but electronic technologies do. We the Cypherpunks are dedicated to building anonymous systems. We are defending our privacy with cryptography, with anonymous mail forwarding systems, with digital signatures, and with electronic money. Cypherpunks write code. We know that someone has to write software to defend privacy, and since we can't get privacy unless we all do, we're going to write it. We publish our code so that our fellow Cypherpunks may practice and play with it. Our code is free for all to use, worldwide. We don't much care if you don't approve of the software we write. We know that software can't be destroyed and that a widely dispersed system can't be shut down. Cypherpunks deplore regulations on cryptography, for encryption is fundamentally a private act. The act of encryption, in fact, removes information from the public realm. Even laws against cryptography reach only so far as a nation's border and the arm of its violence. Cryptography will ineluctably spread over the whole globe, and with it the anonymous transactions systems that it makes possible. For privacy to be widespread it must be part of a social contract. People must come and together deploy these systems for the common good. Privacy only extends so far as the cooperation of one's fellows in society. We the Cypherpunks seek your questions and your concerns and hope we may engage you so that we do not deceive ourselves. We will not, however, be moved out of our course because some may disagree with our goals. The Cypherpunks are actively engaged in making the networks safer for privacy. Let us proceed together apace. Onward. Eric Hughes <hughes@soda.berkeley.edu> 9 March 1993 u��!_{"|}k֐hF[h� hF�P"ܮ3;S"֑9Wgjgδ}$�7hsoE|rkx*E_p����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/messages/signed-1-ecdsa-nistp384.pgp�������������������������������0000644�0000000�0000000�00000012360�10461020230�0023411�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������ �  ժ}Gba-cypherpunks-manifesto.txt[6A Cypherpunk's Manifesto by Eric Hughes Privacy is necessary for an open society in the electronic age. Privacy is not secrecy. A private matter is something one doesn't want the whole world to know, but a secret matter is something one doesn't want anybody to know. Privacy is the power to selectively reveal oneself to the world. If two parties have some sort of dealings, then each has a memory of their interaction. Each party can speak about their own memory of this; how could anyone prevent it? One could pass laws against it, but the freedom of speech, even more than privacy, is fundamental to an open society; we seek not to restrict any speech at all. If many parties speak together in the same forum, each can speak to all the others and aggregate together knowledge about individuals and other parties. The power of electronic communications has enabled such group speech, and it will not go away merely because we might want it to. Since we desire privacy, we must ensure that each party to a transaction have knowledge only of that which is directly necessary for that transaction. Since any information can be spoken of, we must ensure that we reveal as little as possible. In most cases personal identity is not salient. When I purchase a magazine at a store and hand cash to the clerk, there is no need to know who I am. When I ask my electronic mail provider to send and receive messages, my provider need not know to whom I am speaking or what I am saying or what others are saying to me; my provider only need know how to get the message there and how much I owe them in fees. When my identity is revealed by the underlying mechanism of the transaction, I have no privacy. I cannot here selectively reveal myself; I must always reveal myself. Therefore, privacy in an open society requires anonymous transaction systems. Until now, cash has been the primary such system. An anonymous transaction system is not a secret transaction system. An anonymous system empowers individuals to reveal their identity when desired and only when desired; this is the essence of privacy. Privacy in an open society also requires cryptography. If I say something, I want it heard only by those for whom I intend it. If the content of my speech is available to the world, I have no privacy. To encrypt is to indicate the desire for privacy, and to encrypt with weak cryptography is to indicate not too much desire for privacy. Furthermore, to reveal one's identity with assurance when the default is anonymity requires the cryptographic signature. We cannot expect governments, corporations, or other large, faceless organizations to grant us privacy out of their beneficence. It is to their advantage to speak of us, and we should expect that they will speak. To try to prevent their speech is to fight against the realities of information. Information does not just want to be free, it longs to be free. Information expands to fill the available storage space. Information is Rumor's younger, stronger cousin; Information is fleeter of foot, has more eyes, knows more, and understands less than Rumor. We must defend our own privacy if we expect to have any. We must come together and create systems which allow anonymous transactions to take place. People have been defending their own privacy for centuries with whispers, darkness, envelopes, closed doors, secret handshakes, and couriers. The technologies of the past did not allow for strong privacy, but electronic technologies do. We the Cypherpunks are dedicated to building anonymous systems. We are defending our privacy with cryptography, with anonymous mail forwarding systems, with digital signatures, and with electronic money. Cypherpunks write code. We know that someone has to write software to defend privacy, and since we can't get privacy unless we all do, we're going to write it. We publish our code so that our fellow Cypherpunks may practice and play with it. Our code is free for all to use, worldwide. We don't much care if you don't approve of the software we write. We know that software can't be destroyed and that a widely dispersed system can't be shut down. Cypherpunks deplore regulations on cryptography, for encryption is fundamentally a private act. The act of encryption, in fact, removes information from the public realm. Even laws against cryptography reach only so far as a nation's border and the arm of its violence. Cryptography will ineluctably spread over the whole globe, and with it the anonymous transactions systems that it makes possible. For privacy to be widespread it must be part of a social contract. People must come and together deploy these systems for the common good. Privacy only extends so far as the cooperation of one's fellows in society. We the Cypherpunks seek your questions and your concerns and hope we may engage you so that we do not deceive ourselves. We will not, however, be moved out of our course because some may disagree with our goals. The Cypherpunks are actively engaged in making the networks safer for privacy. Let us proceed together apace. Onward. Eric Hughes <hughes@soda.berkeley.edu> 9 March 1993 � �!7cfL ժ}[6�  ժ}>Q%hPlǂb NGY2$2-mx9uW?}gYRDZw&,*Mz Bq%|s70P��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/messages/signed-1-ecdsa-nistp521.pgp�������������������������������0000644�0000000�0000000�00000012423�10461020230�0023402�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������ � @LAGba-cypherpunks-manifesto.txt[6A Cypherpunk's Manifesto by Eric Hughes Privacy is necessary for an open society in the electronic age. Privacy is not secrecy. A private matter is something one doesn't want the whole world to know, but a secret matter is something one doesn't want anybody to know. Privacy is the power to selectively reveal oneself to the world. If two parties have some sort of dealings, then each has a memory of their interaction. Each party can speak about their own memory of this; how could anyone prevent it? One could pass laws against it, but the freedom of speech, even more than privacy, is fundamental to an open society; we seek not to restrict any speech at all. If many parties speak together in the same forum, each can speak to all the others and aggregate together knowledge about individuals and other parties. The power of electronic communications has enabled such group speech, and it will not go away merely because we might want it to. Since we desire privacy, we must ensure that each party to a transaction have knowledge only of that which is directly necessary for that transaction. Since any information can be spoken of, we must ensure that we reveal as little as possible. In most cases personal identity is not salient. When I purchase a magazine at a store and hand cash to the clerk, there is no need to know who I am. When I ask my electronic mail provider to send and receive messages, my provider need not know to whom I am speaking or what I am saying or what others are saying to me; my provider only need know how to get the message there and how much I owe them in fees. When my identity is revealed by the underlying mechanism of the transaction, I have no privacy. I cannot here selectively reveal myself; I must always reveal myself. Therefore, privacy in an open society requires anonymous transaction systems. Until now, cash has been the primary such system. An anonymous transaction system is not a secret transaction system. An anonymous system empowers individuals to reveal their identity when desired and only when desired; this is the essence of privacy. Privacy in an open society also requires cryptography. If I say something, I want it heard only by those for whom I intend it. If the content of my speech is available to the world, I have no privacy. To encrypt is to indicate the desire for privacy, and to encrypt with weak cryptography is to indicate not too much desire for privacy. Furthermore, to reveal one's identity with assurance when the default is anonymity requires the cryptographic signature. We cannot expect governments, corporations, or other large, faceless organizations to grant us privacy out of their beneficence. It is to their advantage to speak of us, and we should expect that they will speak. To try to prevent their speech is to fight against the realities of information. Information does not just want to be free, it longs to be free. Information expands to fill the available storage space. Information is Rumor's younger, stronger cousin; Information is fleeter of foot, has more eyes, knows more, and understands less than Rumor. We must defend our own privacy if we expect to have any. We must come together and create systems which allow anonymous transactions to take place. People have been defending their own privacy for centuries with whispers, darkness, envelopes, closed doors, secret handshakes, and couriers. The technologies of the past did not allow for strong privacy, but electronic technologies do. We the Cypherpunks are dedicated to building anonymous systems. We are defending our privacy with cryptography, with anonymous mail forwarding systems, with digital signatures, and with electronic money. Cypherpunks write code. We know that someone has to write software to defend privacy, and since we can't get privacy unless we all do, we're going to write it. We publish our code so that our fellow Cypherpunks may practice and play with it. Our code is free for all to use, worldwide. We don't much care if you don't approve of the software we write. We know that software can't be destroyed and that a widely dispersed system can't be shut down. Cypherpunks deplore regulations on cryptography, for encryption is fundamentally a private act. The act of encryption, in fact, removes information from the public realm. Even laws against cryptography reach only so far as a nation's border and the arm of its violence. Cryptography will ineluctably spread over the whole globe, and with it the anonymous transactions systems that it makes possible. For privacy to be widespread it must be part of a social contract. People must come and together deploy these systems for the common good. Privacy only extends so far as the cooperation of one's fellows in society. We the Cypherpunks seek your questions and your concerns and hope we may engage you so that we do not deceive ourselves. We will not, however, be moved out of our course because some may disagree with our goals. The Cypherpunks are actively engaged in making the networks safer for privacy. Let us proceed together apace. Onward. Eric Hughes <hughes@soda.berkeley.edu> 9 March 1993 � �!I;ٚ@LA[6� @LA L!ʡ'dRPF*2O_P[V@UU{A[>uv\\?a3OCEUL.'[;K%w `Й*aXy^Nc28<+Y 0g2`yePO���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/messages/signed-1-eddsa-ed25519.pgp��������������������������������0000644�0000000�0000000�00000012320�10461020230�0023010�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������ � 4,Gba-cypherpunks-manifesto.txt[zA Cypherpunk's Manifesto by Eric Hughes Privacy is necessary for an open society in the electronic age. Privacy is not secrecy. A private matter is something one doesn't want the whole world to know, but a secret matter is something one doesn't want anybody to know. Privacy is the power to selectively reveal oneself to the world. If two parties have some sort of dealings, then each has a memory of their interaction. Each party can speak about their own memory of this; how could anyone prevent it? One could pass laws against it, but the freedom of speech, even more than privacy, is fundamental to an open society; we seek not to restrict any speech at all. If many parties speak together in the same forum, each can speak to all the others and aggregate together knowledge about individuals and other parties. The power of electronic communications has enabled such group speech, and it will not go away merely because we might want it to. Since we desire privacy, we must ensure that each party to a transaction have knowledge only of that which is directly necessary for that transaction. Since any information can be spoken of, we must ensure that we reveal as little as possible. In most cases personal identity is not salient. When I purchase a magazine at a store and hand cash to the clerk, there is no need to know who I am. When I ask my electronic mail provider to send and receive messages, my provider need not know to whom I am speaking or what I am saying or what others are saying to me; my provider only need know how to get the message there and how much I owe them in fees. When my identity is revealed by the underlying mechanism of the transaction, I have no privacy. I cannot here selectively reveal myself; I must always reveal myself. Therefore, privacy in an open society requires anonymous transaction systems. Until now, cash has been the primary such system. An anonymous transaction system is not a secret transaction system. An anonymous system empowers individuals to reveal their identity when desired and only when desired; this is the essence of privacy. Privacy in an open society also requires cryptography. If I say something, I want it heard only by those for whom I intend it. If the content of my speech is available to the world, I have no privacy. To encrypt is to indicate the desire for privacy, and to encrypt with weak cryptography is to indicate not too much desire for privacy. Furthermore, to reveal one's identity with assurance when the default is anonymity requires the cryptographic signature. We cannot expect governments, corporations, or other large, faceless organizations to grant us privacy out of their beneficence. It is to their advantage to speak of us, and we should expect that they will speak. To try to prevent their speech is to fight against the realities of information. Information does not just want to be free, it longs to be free. Information expands to fill the available storage space. Information is Rumor's younger, stronger cousin; Information is fleeter of foot, has more eyes, knows more, and understands less than Rumor. We must defend our own privacy if we expect to have any. We must come together and create systems which allow anonymous transactions to take place. People have been defending their own privacy for centuries with whispers, darkness, envelopes, closed doors, secret handshakes, and couriers. The technologies of the past did not allow for strong privacy, but electronic technologies do. We the Cypherpunks are dedicated to building anonymous systems. We are defending our privacy with cryptography, with anonymous mail forwarding systems, with digital signatures, and with electronic money. Cypherpunks write code. We know that someone has to write software to defend privacy, and since we can't get privacy unless we all do, we're going to write it. We publish our code so that our fellow Cypherpunks may practice and play with it. Our code is free for all to use, worldwide. We don't much care if you don't approve of the software we write. We know that software can't be destroyed and that a widely dispersed system can't be shut down. Cypherpunks deplore regulations on cryptography, for encryption is fundamentally a private act. The act of encryption, in fact, removes information from the public realm. Even laws against cryptography reach only so far as a nation's border and the arm of its violence. Cryptography will ineluctably spread over the whole globe, and with it the anonymous transactions systems that it makes possible. For privacy to be widespread it must be part of a social contract. People must come and together deploy these systems for the common good. Privacy only extends so far as the cooperation of one's fellows in society. We the Cypherpunks seek your questions and your concerns and hope we may engage you so that we do not deceive ourselves. We will not, however, be moved out of our course because some may disagree with our goals. The Cypherpunks are actively engaged in making the networks safer for privacy. Let us proceed together apace. Onward. Eric Hughes <hughes@soda.berkeley.edu> 9 March 1993 u��!3F&3yvyx 4,[z�  4,�I6$o |VULBѳI{�F:|d\Du}����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/messages/signed-1-notarized-by-ed25519.pgp�������������������������0000644�0000000�0000000�00000012772�10461020230�0024352�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������ �  4, � r#fx%(lb�����A Cypherpunk's Manifesto by Eric Hughes Privacy is necessary for an open society in the electronic age. Privacy is not secrecy. A private matter is something one doesn't want the whole world to know, but a secret matter is something one doesn't want anybody to know. Privacy is the power to selectively reveal oneself to the world. If two parties have some sort of dealings, then each has a memory of their interaction. Each party can speak about their own memory of this; how could anyone prevent it? One could pass laws against it, but the freedom of speech, even more than privacy, is fundamental to an open society; we seek not to restrict any speech at all. If many parties speak together in the same forum, each can speak to all the others and aggregate together knowledge about individuals and other parties. The power of electronic communications has enabled such group speech, and it will not go away merely because we might want it to. Since we desire privacy, we must ensure that each party to a transaction have knowledge only of that which is directly necessary for that transaction. Since any information can be spoken of, we must ensure that we reveal as little as possible. In most cases personal identity is not salient. When I purchase a magazine at a store and hand cash to the clerk, there is no need to know who I am. When I ask my electronic mail provider to send and receive messages, my provider need not know to whom I am speaking or what I am saying or what others are saying to me; my provider only need know how to get the message there and how much I owe them in fees. When my identity is revealed by the underlying mechanism of the transaction, I have no privacy. I cannot here selectively reveal myself; I must always reveal myself. Therefore, privacy in an open society requires anonymous transaction systems. Until now, cash has been the primary such system. An anonymous transaction system is not a secret transaction system. An anonymous system empowers individuals to reveal their identity when desired and only when desired; this is the essence of privacy. Privacy in an open society also requires cryptography. If I say something, I want it heard only by those for whom I intend it. If the content of my speech is available to the world, I have no privacy. To encrypt is to indicate the desire for privacy, and to encrypt with weak cryptography is to indicate not too much desire for privacy. Furthermore, to reveal one's identity with assurance when the default is anonymity requires the cryptographic signature. We cannot expect governments, corporations, or other large, faceless organizations to grant us privacy out of their beneficence. It is to their advantage to speak of us, and we should expect that they will speak. To try to prevent their speech is to fight against the realities of information. Information does not just want to be free, it longs to be free. Information expands to fill the available storage space. Information is Rumor's younger, stronger cousin; Information is fleeter of foot, has more eyes, knows more, and understands less than Rumor. We must defend our own privacy if we expect to have any. We must come together and create systems which allow anonymous transactions to take place. People have been defending their own privacy for centuries with whispers, darkness, envelopes, closed doors, secret handshakes, and couriers. The technologies of the past did not allow for strong privacy, but electronic technologies do. We the Cypherpunks are dedicated to building anonymous systems. We are defending our privacy with cryptography, with anonymous mail forwarding systems, with digital signatures, and with electronic money. Cypherpunks write code. We know that someone has to write software to defend privacy, and since we can't get privacy unless we all do, we're going to write it. We publish our code so that our fellow Cypherpunks may practice and play with it. Our code is free for all to use, worldwide. We don't much care if you don't approve of the software we write. We know that software can't be destroyed and that a widely dispersed system can't be shut down. Cypherpunks deplore regulations on cryptography, for encryption is fundamentally a private act. The act of encryption, in fact, removes information from the public realm. Even laws against cryptography reach only so far as a nation's border and the arm of its violence. Cryptography will ineluctably spread over the whole globe, and with it the anonymous transactions systems that it makes possible. For privacy to be widespread it must be part of a social contract. People must come and together deploy these systems for the common good. Privacy only extends so far as the cooperation of one's fellows in society. We the Cypherpunks seek your questions and your concerns and hope we may engage you so that we do not deceive ourselves. We will not, however, be moved out of our course because some may disagree with our goals. The Cypherpunks are actively engaged in making the networks safer for privacy. Let us proceed together apace. Onward. Eric Hughes <hughes@soda.berkeley.edu> 9 March 1993 s� �!?AWdar#fx%(Z� r#fx%(p iBN+8ٱ2swڈG2:7" $qI/ۜnp(KK>$q ye*ujQK}ۤAɓ[Gwn=<[C@zs=#F}Tu�ڂv?St.b+ԸĒܽf 9m_y|L3UG.D$S"PشwJOy#WPNRl9wQ' 3Xw"zᄷN%?�I'*M'7藨㎆su� �'[} M3F&3yvyx 4,  4,���º"πyx>Prg^P&p� =FD7`:cu!1֥^y������sequoia-openpgp-2.0.0/tests/data/messages/signed-1-sha1-neal.gpg������������������������������������0000644�0000000�0000000�00000005275�10461020230�0022507�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������uXmEnKph6Hk2xMi$[RҦ -T8wwg;{߻5m14*G"QCb4!?RCc@!>{oˏowgΜ99ye+V}4Z|ڏ׫MAS&ږu16H#*3[-Vڕ{bȝ6 uh'~.s622VBRҙ Jԙ*k4*ֳrTꠙeIo*-sUUgr]%iKSh튙JLTQIb2mlLFr\WRy՛)ɦTьMt&Hǔfx.aVӈ޲Gˬdbdlj'e+i"b3q#VH<unQ I{(kY֑F+jljVIBdb&205R KrOmr_9 UZ8z$GddMNT$#IF$8Δ>j# [TqBШB 1''�@0ƩhKe٬D(FtwLtRj=N#s:?h0+8:&(cF4-t! k-cV졁LQhyR8PcXg5u) @L"E# 0ci XIؔH߅ڥVOS@kdP|X<v($pX"#}$F$5A7} {^406+[˴##SL{)^Y[Z[V*J\9\-Ph(OSL!ЖVS^A}ɬ<D[ M|K{LQP.`69ȴ k`Y!͋!Wi TD#`gR9t>pV·yT3Y<S y eu6r=7<ħOSo:&eZwg(_:4�ևq$V6cwr=gz`D"-w�QD؝wxb9e e䖾D�u {GkMnj7OU:ǥUnw8Tc='~N͑oA+%m :%lt\䖰S+߿%]n&HP-sGM5\u (4mJUe0&P%S;^K;PUbz̘l8=0j7PzDKz%uЀI[ɉ_)dX횤U"&lW]n7;KhjU~-[-l;e*f T<:!Ϧ΁\"lK/(,m XFD1. 6 P ccTXJ*0UuU_�X:Jن=*\VS&q쫝<5KoAn @iEp7>^EU;Cq$XjA3f 9xIjP 93wKs\u˓d)R6;k@hL]ڎHɹ[AD֕*1PuStC?fv;He'XfX�j^vtQ_nhF($NvqEQ!- lqԼeگMYL9n|0<R}ʲ/^i /]֨2PZR3 K04d&nAQR>ѳ 0 Rh|tbC[b*4M:[D`ƈrEecrz;$jHM@մۮ 8%KWr0 5j#[LlZ<jvҋ*n4pI :gjBo`\Ю6 {$lKh6 $6V'Pe=Rw 9[ Ҝ5DHeT6dq_g&%03 G[$mF@EǪ$9;!hW({17$iu�/RƐū~KE,.3VuvKDm[gGPy_B_McVkG8+G\Hi'KfܡgD2d8RVH^Mذe,k5XHM;َFV �g.4ˆ33F*IXsGy=5*m|G"a.Xa"-e9-t*9ˤδI(m(N?pL"77-ꂜa#|Q4%y9Gh sxSzD b\;~F!"&iAE2Іi9s e7h@E� waj<9Юnjg'TL饕FE<;Rr?Q� ] *B2H=\&jdYH Ə7=5?Lfǘyͬ["*L-[6Kqr+VZu׭~bW 5ݧ5[ wtO{^:W;7̼g} ⩧vo<s=7lON!:n~W՚~sWz/~'|_;sEp-{~s_gׅ[oɭ_pS_Zӟqڽ/&_Yc<WO<+W<Mߺp~O͋ #g_~~wb;?}WuÚ7(>t?mr-~;wɤS/|OǾOpk�����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/messages/signed-1-sha256-testy.gpg���������������������������������0000644�0000000�0000000�00000005273�10461020230�0023112�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������uXmEnkʈ6 mhؾԷJӖV0]vfnӘhLQ_$Ghh4фHAPDsmvw̙s왷eڕ/4_Co|ycm^)XۢnSd]icݯ|grWz}{1My{ q&2q2ׁvNZ*йt&Ht,c-uҚ< 7t::孲7*Kmi3.$ɵ v\)*/46)66 ei87ӁVTlfDcJ)C֭%.1:K2i-h1<[|bL,-d&=_f$Cl3n@rUc "jFo ǚ|^.:Z�vVc`K,$nAf*S!H) q)e‰H%NrD֡wx ɈQgÙGm@aUy2!4*}h,)"H8y4%Z6ΥNJGi*`C)~9BEVG%?a&yLR»[? fU(es5e*xYS "N�.R4"x;U5h39ԁeI7M(j١$]]b,Bu*҇cB"Jr:S7lDRCxսE=cһELr8!2U1`F%5IY0%!@倆4JEmI9#T_{dQ�TaW%!i1f㶚T1W(V]\]w\C*Qn,L%)bC|XjP PFLJ= YQDfwOU.Bf>OI}'iAF|o8O$k&)1.8} p�#o �"KV/`/C_(#%J0m(5MWX޻>ZT&3Sծ.I\}C>sg\\fo.]FOgEn k1^fd O zPI y-PTU4~8̂غ�ZU5Z2X+۸Q1nȌvCf]j#]қ/ N} 0wJNtJ!úv4)c1fӿrc_B3cwe2F,whPd00 (S1nHHU)_#l%QJ/RyD˦646b -l@`#8:R9al>=bKIFJ`\pd6oD ' a6}'fi-(5Q(nvgwVP-I38̺)Hgl$5H( å9ҸIԔ{]e HԦ#mXn_\/, F֥*#cꈧX:ѿ<JvRO,̰�բtD j3c|A E4VZ]AG4H64˲%ǭTc-#t@q㳘s:azH e_ ;*;/֨RPZP3 K04,ܤ&jAQ>ѱ 0= Rh|lbC[b*4M*I[D`ňrFcr:;$jHMAմ۬ (!K[7 f5j#[LmR<jvҋ*n4pI :gF.h]+mHشr5lsIdO>?j&h&r'{Hs"{"EP rJ ~|NyTnUӤBh{onօd_86}HސHI:Cn苂&2].FCղHW0[Ui H/]i-AE4~} @}5Yݮ+O< p?l,.DPN4.q#,>ᑲGmpƆ c1_[VI$1d :i v'89ifwUx&ײ$*cݛ=*M|6G"a.Xa"-e9 vt(9ˤδIe(m(>pL1o[V%9jG"5jJr5U 8n@ĸfv)ByD҂-d r2 &e;h@E� waj<9Юvjg'TD饕F%<;R2?Q�r] jd,wFpOme!!H?z1{3bK:vMS۶mʭW\bkW}5[ q'^qqrϯutި7xoY}q];nr|>|}[wr5է4ܴ+7}n͡6n5^aGDG;qWyO_|G\X֓>ua˵/^~}ۿ>=KxO_ }W?>?;?~?}?8<9[?>WoԱ߯_6_p''c㓯W_,\]~O�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/messages/signed-1.gpg����������������������������������������������0000644�0000000�0000000�00000012617�10461020230�0020736�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������ � r#fx%(Gba-cypherpunks-manifesto.txtZA Cypherpunk's Manifesto by Eric Hughes Privacy is necessary for an open society in the electronic age. Privacy is not secrecy. A private matter is something one doesn't want the whole world to know, but a secret matter is something one doesn't want anybody to know. Privacy is the power to selectively reveal oneself to the world. If two parties have some sort of dealings, then each has a memory of their interaction. Each party can speak about their own memory of this; how could anyone prevent it? One could pass laws against it, but the freedom of speech, even more than privacy, is fundamental to an open society; we seek not to restrict any speech at all. If many parties speak together in the same forum, each can speak to all the others and aggregate together knowledge about individuals and other parties. The power of electronic communications has enabled such group speech, and it will not go away merely because we might want it to. Since we desire privacy, we must ensure that each party to a transaction have knowledge only of that which is directly necessary for that transaction. Since any information can be spoken of, we must ensure that we reveal as little as possible. In most cases personal identity is not salient. When I purchase a magazine at a store and hand cash to the clerk, there is no need to know who I am. When I ask my electronic mail provider to send and receive messages, my provider need not know to whom I am speaking or what I am saying or what others are saying to me; my provider only need know how to get the message there and how much I owe them in fees. When my identity is revealed by the underlying mechanism of the transaction, I have no privacy. I cannot here selectively reveal myself; I must always reveal myself. Therefore, privacy in an open society requires anonymous transaction systems. Until now, cash has been the primary such system. An anonymous transaction system is not a secret transaction system. An anonymous system empowers individuals to reveal their identity when desired and only when desired; this is the essence of privacy. Privacy in an open society also requires cryptography. If I say something, I want it heard only by those for whom I intend it. If the content of my speech is available to the world, I have no privacy. To encrypt is to indicate the desire for privacy, and to encrypt with weak cryptography is to indicate not too much desire for privacy. Furthermore, to reveal one's identity with assurance when the default is anonymity requires the cryptographic signature. We cannot expect governments, corporations, or other large, faceless organizations to grant us privacy out of their beneficence. It is to their advantage to speak of us, and we should expect that they will speak. To try to prevent their speech is to fight against the realities of information. Information does not just want to be free, it longs to be free. Information expands to fill the available storage space. Information is Rumor's younger, stronger cousin; Information is fleeter of foot, has more eyes, knows more, and understands less than Rumor. We must defend our own privacy if we expect to have any. We must come together and create systems which allow anonymous transactions to take place. People have been defending their own privacy for centuries with whispers, darkness, envelopes, closed doors, secret handshakes, and couriers. The technologies of the past did not allow for strong privacy, but electronic technologies do. We the Cypherpunks are dedicated to building anonymous systems. We are defending our privacy with cryptography, with anonymous mail forwarding systems, with digital signatures, and with electronic money. Cypherpunks write code. We know that someone has to write software to defend privacy, and since we can't get privacy unless we all do, we're going to write it. We publish our code so that our fellow Cypherpunks may practice and play with it. Our code is free for all to use, worldwide. We don't much care if you don't approve of the software we write. We know that software can't be destroyed and that a widely dispersed system can't be shut down. Cypherpunks deplore regulations on cryptography, for encryption is fundamentally a private act. The act of encryption, in fact, removes information from the public realm. Even laws against cryptography reach only so far as a nation's border and the arm of its violence. Cryptography will ineluctably spread over the whole globe, and with it the anonymous transactions systems that it makes possible. For privacy to be widespread it must be part of a social contract. People must come and together deploy these systems for the common good. Privacy only extends so far as the cooperation of one's fellows in society. We the Cypherpunks seek your questions and your concerns and hope we may engage you so that we do not deceive ourselves. We will not, however, be moved out of our course because some may disagree with our goals. The Cypherpunks are actively engaged in making the networks safer for privacy. Let us proceed together apace. Onward. Eric Hughes <hughes@soda.berkeley.edu> 9 March 1993 3� �!?AWdar#fx%(Z� r#fx%(p iBN+8ٱ2swڈG2:7" $qI/ۜnp(KK>$q ye*ujQK}ۤAɓ[Gwn=<[C@zs=#F}Tu�ڂv?St.b+ԸĒܽf 9m_y|L3UG.D$S"PشwJOy#WPNRl9wQ' 3Xw"zᄷN%?�I'*M'7藨㎆s�����������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/messages/signed-2-partial-body.gpg���������������������������������0000644�0000000�0000000�00000012565�10461020230�0023326�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������ � r#fx%(b�Z rA Cypherpunk's Manifesto by Eric Hughes Privacy is necessary for an open society in the electronic age. Privacy is not secrecy. A private matter is something one doesn't want the whole world to know, but a secret matter is something one doesn't want anybody to know. Privacy is the power to selectively reveal oneself to the world. If two parties have some sort of dealings, then each has a memory of their interaction. Each party can speak about their own memory of this; how could anyone prevent it? One could pass laws against it, but the freedom of speech, even more than privacy, is fundamental to an open society; we seek not to restrict any speech at all. If many parties speak together in the same forum, each can speak to all the others and aggregate together knowledge about individuals and other parties. The power of electronic communications has enabled such group speech, and it will not go away merely because we might want it to. Since we desire privacy, we must ensure that each party to a transaction have knowledge only of that which is directly necessary for that transaction. Since any information can be spoken of, we must ensure that we reveal as little as possible. In most cases personal identity is not salient. When I purchase a magazine at a store and hand cash to the clerk, there is no need to know who I am. When I ask my electronic mail provider to send and receive messages, my provider need not know to whom I am speaking or what I am saying or what others are saying to me; my provider only need know how to get the message there and how much I owe them in fees. When my identity is revealed by the underlying mechanism of the transaction, I have no privacy. I cannot here selectively reveal myself; I must always reveal myself. Therefore, privacy in an open society requires anonymous transaction systems. Until now, cash has been the primary such system. An anonymous transaction system is not a secret transaction system. An anonymous system empowers individuals to reveal their identity when desired and only when desired; this is the essence of privacy. Privacy in an open society also requires cryptography. If I say something, I want it heard only by those for whom I intend it. If the content of my speech is available to the world, I have no privacy. To encrypt is to indicate the desire for privacy, and to encrypt with weak cryptography is to indicate not too much desire for privacy. Furthermore, to reveal one's identity with assurance when the default is anonymity requires the cryptographic signature. We cannot expect governments, corporations, or other large, faceless organizations to grant us privacy out of their beneficence. It is to their advantage to speak of us, and we should expect that they will speak. To try to prevent their speech is to fight against the realities of information. Information does not just want to be free, it longs to be free. Information expands to fill the available storage space. Information is Rumor's younger, stronger cousin; Information is fleeter of foot, has more eyes, knows more, and understands less than Rumor. We must defend our own privacy if we expect to have any. We must come together and create systems which allow anonymous transactions to take place. People have been defending their own privacy for centuries with whispers, darkness, envelopes, closed doors, secret handshakes, and couriers. The technologies of the past did not allow for strong privacy, but electronic technologies do. We the Cypherpunks are dedicated to building anonymous systems. We are defending our privacy with cryptography, with anonymous mail forwarding systems, with digital signatures, and with electronic money. Cypherpunks write code. We know that someone has to write software to defend privacy, and since we can't get privacy unless we all do, we're going to write it. We publish our code so that our fellow Cypherpunks may practice and play with it. Our code is free for all to use, worldwide. We don't much care if you don't approve of the software we write. We know that software can't be destroyed and that a widely dispersed system can't be shut down. Cypherpunks deplore regulations on cryptography, for encryption is fundamentally a private act. The act of encryption, in fact, removes information from the public realm. Even laws against cryptography reach only so far as a nation's border and the arm of its violence. Cryptography will ineluctably spread over the whole globe, and with it the anonymous transactions systems that it makes possible. For privacy to be widespread it must be part of a social contract. People must come and together deploy these systems for the common good. Privacy only extends so far as the cooperation of one's fellows in society. We the Cypherpunks seek your questions and your concerns and hope we may engage you so that we do not deceive ourselves. We will not, however, be moved out of our course because some may disagree with our goals. The Cypherpunks are actively engaged in making the networks safer for privacy. Let us proceed together apace. Onward. Eric Hughe,s <hughes@soda.berkeley.edu> 9 March 1993 3� �!?AWdar#fx%(Z r� r#fx%(/UOf$C:Nv^5 9P'+(j1EX)!v ,%%a<3@I'I(]|#e'wB$�Rؽ!?pO:}|y&ccԝz}kī3 w,@z?<aSSupc74j7Z!V�dK"X�5ǎ !::/0db b]7%>Vdc?k J+6�������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/messages/signed-3-partial-body-multiple-sigs.gpg�������������������0000644�0000000�0000000�00000005464�10461020230�0026123�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������uXkUiAhlay`xAF -%hyxgfg<#ŦM FԴ> 6bѠXm1$hĤ􇨀(A3s6k}[ߚ S~l>x;%{?~ɿ߇W?vw/<E^e˭DeqmiİkؓWaj[iO[Z*&יƋu,#-u0Y IoJiWhʜޔZ,uA IuY(Mo͖bM"S$,h U)3[)јlJeukbtLnF*!cx[|bUQHh�4mˤV^U:5 = 5٤\Cj-=ZMK54(e!S22# AM)99R^ZUYz$GdPh훔|ǩڋHq6]| QCcJ x_�i1/JIPx,X;&D:)e t9$JڂeE:$u(CD4x&+-cRn蠁QhiRf9:SCXgaa\A$1EFoGF L*)`iF bc`vEy6.8Buf+҅aB"Bet|%o؈8+{Auϋz&srgXr$8#Cd*7Ӏ :/EK<kJ qKDWnC0y 9i2r+/<ɼ*<BSb( H(0% k`Y!N!Uq!TD#`gR9t>pVʇ9T3x;?sHHTf>OI}p'iAJ^|/TX'e5XA 16u8} p�#o �"KZOa/C_%(#;%Jfm(5MWۻ }MSݛNMe [RF\}C>qLfA ߮dfN,bj74 JRh[ 6!=%pxp+,[<(Eג)-EZ;Ped,z̘l83j5fPzDzBk|_17DRȰ5H:טkvƕتX[Tl)s;l(qp#73PUWp@f>;jqċTnҲ.9Ga "bLQ B@y(Fk)BTM#&)(h[iU]C WHfF([>LƱ.",E>*xͮ<#xpWmű iRGq7#4 �}{8{?<U<Aq͕7g#ܾD�TY)9gSs6 ѺtB%0x.Ӷd), Q-ΎNJP b�-.:*Fr@.[rJ5Ep.&G,&`7NR)w>e/Ȏ@/֨𐏤ZP3 K0,iXcIL ;Ů/.}cazb%߸ Ć/07 8mb#a> 쐨!7UnΏØt/m4רo1*_NPqSхKjY#8x,<a#a^xGoH.GHBh%wW$F{0s8ٹE͉]CTAs (mK/mФfps£@pM|Cs(0T~X$t.$#- űyBjJ×!1*htq2f#GUɠU4,<}2x`DWݷ�Е~J+FP@/pQ<I鴆fYCn3~DRBLM0~CYL׸b&6ke:I`v(89C@ #Ly$v IUL_9uoDbX4 MAb)T1oF4Y&y혵0H,EmCB-c7zز,1 Y'/;UVT2E cyJ6Tm܍ѨD-J!0/n0aF;lMAP oecՎ,qD 924n 9U;8G}B> @K0^H, Q(.Q$4#ƏGEuo5bOדگ>(ĹYsϑbKu1tɲ#oؓϭ?~O|cx['g53X68,M۞|ƃ#̂w? &^=~ѢO+ՉLryܭ9rb; #&_GO^.:yK~t &_rՕ<+[.K~/Ǯ}۾kOX\y׉߹߻7ÕO]~|ҝOxUCw]l<{Ԧ?w}+λɽ[ؽ_' OW\VQtN~S{( V>̝c>Ocѩ\݋v>on{俋or:fG|E'ٽ9K[nٷqOġOM?j7������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/messages/signed-twice-by-ed25519.pgp�������������������������������0000644�0000000�0000000�00000012473�10461020230�0023326�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������ �  4,� �  4,lb�����A Cypherpunk's Manifesto by Eric Hughes Privacy is necessary for an open society in the electronic age. Privacy is not secrecy. A private matter is something one doesn't want the whole world to know, but a secret matter is something one doesn't want anybody to know. Privacy is the power to selectively reveal oneself to the world. If two parties have some sort of dealings, then each has a memory of their interaction. Each party can speak about their own memory of this; how could anyone prevent it? One could pass laws against it, but the freedom of speech, even more than privacy, is fundamental to an open society; we seek not to restrict any speech at all. If many parties speak together in the same forum, each can speak to all the others and aggregate together knowledge about individuals and other parties. The power of electronic communications has enabled such group speech, and it will not go away merely because we might want it to. Since we desire privacy, we must ensure that each party to a transaction have knowledge only of that which is directly necessary for that transaction. Since any information can be spoken of, we must ensure that we reveal as little as possible. In most cases personal identity is not salient. When I purchase a magazine at a store and hand cash to the clerk, there is no need to know who I am. When I ask my electronic mail provider to send and receive messages, my provider need not know to whom I am speaking or what I am saying or what others are saying to me; my provider only need know how to get the message there and how much I owe them in fees. When my identity is revealed by the underlying mechanism of the transaction, I have no privacy. I cannot here selectively reveal myself; I must always reveal myself. Therefore, privacy in an open society requires anonymous transaction systems. Until now, cash has been the primary such system. An anonymous transaction system is not a secret transaction system. An anonymous system empowers individuals to reveal their identity when desired and only when desired; this is the essence of privacy. Privacy in an open society also requires cryptography. If I say something, I want it heard only by those for whom I intend it. If the content of my speech is available to the world, I have no privacy. To encrypt is to indicate the desire for privacy, and to encrypt with weak cryptography is to indicate not too much desire for privacy. Furthermore, to reveal one's identity with assurance when the default is anonymity requires the cryptographic signature. We cannot expect governments, corporations, or other large, faceless organizations to grant us privacy out of their beneficence. It is to their advantage to speak of us, and we should expect that they will speak. To try to prevent their speech is to fight against the realities of information. Information does not just want to be free, it longs to be free. Information expands to fill the available storage space. Information is Rumor's younger, stronger cousin; Information is fleeter of foot, has more eyes, knows more, and understands less than Rumor. We must defend our own privacy if we expect to have any. We must come together and create systems which allow anonymous transactions to take place. People have been defending their own privacy for centuries with whispers, darkness, envelopes, closed doors, secret handshakes, and couriers. The technologies of the past did not allow for strong privacy, but electronic technologies do. We the Cypherpunks are dedicated to building anonymous systems. We are defending our privacy with cryptography, with anonymous mail forwarding systems, with digital signatures, and with electronic money. Cypherpunks write code. We know that someone has to write software to defend privacy, and since we can't get privacy unless we all do, we're going to write it. We publish our code so that our fellow Cypherpunks may practice and play with it. Our code is free for all to use, worldwide. We don't much care if you don't approve of the software we write. We know that software can't be destroyed and that a widely dispersed system can't be shut down. Cypherpunks deplore regulations on cryptography, for encryption is fundamentally a private act. The act of encryption, in fact, removes information from the public realm. Even laws against cryptography reach only so far as a nation's border and the arm of its violence. Cryptography will ineluctably spread over the whole globe, and with it the anonymous transactions systems that it makes possible. For privacy to be widespread it must be part of a social contract. People must come and together deploy these systems for the common good. Privacy only extends so far as the cooperation of one's fellows in society. We the Cypherpunks seek your questions and your concerns and hope we may engage you so that we do not deceive ourselves. We will not, however, be moved out of our course because some may disagree with our goals. The Cypherpunks are actively engaged in making the networks safer for privacy. Let us proceed together apace. Onward. Eric Hughes <hughes@soda.berkeley.edu> 9 March 1993 u� �'[zЛ3F&3yvyx 4,  4,��j�cQg(zW 8+-)�ƅ6v}r#" rJR u� �'[zЛ3F&3yvyx 4,  4,��j�cQg(zW 8+-)�ƅ6v}r#" rJR �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/messages/signed.gpg������������������������������������������������0000644�0000000�0000000�00000000710�10461020230�0020567�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������X5W,#h_ˌNO(eabSd_w]9~%ZV&B.Nȓ ϹSZ$cә^vy9Y*0йꌨ6~zd>\w}HNFcF9J3 OI9a/Ld*E.9uoᩃ&ߝӸ.{MޤKWͿGgT ,Fׄfw.sej.}df)6q{9ŒXrxpŠKIq|g.7Wy4ki}3ksiu1u^Y%)me,ϰ⥕nmܯo_xv&7=3G6?Vۮv𢡊Xn5n\,=Y3<<���������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/messages/text-signature-notation-has-lf.txt������������������������0000644�0000000�0000000�00000002042�10461020230�0025344�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������***-****: *********/*****; ********="*********************************" --********************************* *******-****: ****/*****; *******=*******-**** *******-********: **-** *******-********-********: ******-********* ** **.**.** ** **:** ******* **** *. ********: >=** >=** ***** ****, ****** ******* *** ***** **** ***** *****, ********** ***** *** ******** ************* *** **** *** *****. *=******** ** *** *** ***** ******* ****** *********** ************? ****** **** *** ***** **=**=***, **** --=** **. **** *****=****** - ********************* - ***************************************** ************** ***|**** **************=*** ** ***** ****** *** *** *** ** **-* *** *** *** ** **-** ************** *********: ****://***.**************-***-****.**/*********= ***** **** - ************ *=*** *********** ***** *** ************ ******** (**=*******) **: *=***** ******* *** **. ****** ******* | ** ************** *** ******= * | ***-****. ** ********* --*********************************-- ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/messages/text-signature-notation-has-lf.txt.sig��������������������0000644�0000000�0000000�00000003277�10461020230�0026140�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������-----BEGIN PGP MESSAGE----- kA0DAQgWIuP6/pa1bDIBy+p0AGOWUTcqKiotKioqKjogKioqKioqKioqLyoqKioq OyAqKioqKioqKj0iKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqIg0K DQotLSoqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKg0KKioqKioqKi0q KioqOiAqKioqLyoqKioqOyAqKioqKioqPSoqKioqKiotKioqKg0KKioqKioqKi0q KioqKioqKjogKiotKioNCioqKioqKiotKioqKioqKiotKioqKioqKio6ICoqKioq Ki0qKioqKioqKioNCg0KKiogKiouKiouKiogKiogKio6KiogKioqKioqKiAqKioq ICouICoqKioqKioqOg0KPj0qKg0KPj0qKg0KKioqKiogKioqKiwNCg0KKioqKioq ICoqKioqKiogKioqICoqKioqICoqKiogKioqKiogKioqKiosICoqKioqKioqKiog KioqKiogKioqICoqKioqKioqDQoqKioqKioqKioqKioqICoqKiAqKioqICoqKiAq KioqKi4NCio9KioqKioqKiogKiogKioqICoqKiAqKioqKiAqKioqKioqICoqKioq KiAqKioqKioqKioqKiAqKioqKioqKioqKio/DQoNCioqKioqKiAqKioqICoqKiAq KioqKiAqKj0qKj0qKiosICoqKioNCg0KLS09KioNCioqLiAqKioqICoqKioqPSoq KioqKg0KLSAqKioqKioqKioqKioqKioqKioqKiogLQ0KKioqKioqKioqKioqKioq KioqKioqKioqKioqKioqKioqKioqKioqKioNCg0KKioqKioqKioqKioqKiogKioq fCoqKioNCioqKioqKioqKioqKioqPSoqKiAqKg0KKioqKiogKioqKioqDQoqKiog ICoqKiAqKiogKiogKiotKg0KKioqICAqKiogKioqICoqICoqLSoqDQoqKioqKioq KioqKioqKiAqKioqKioqKio6ICoqKio6Ly8qKiouKioqKioqKioqKioqKiotKioq LSoqKiouKiovKioqKioqKioqPQ0KDQoNCioqKioqICoqKiogLSAqKioqKioqKioq KiogKj0qKiogKioqKioqKioqKiogKioqKiogKioqICoqKioqKioqKioqKg0KKioq KioqKiogKCoqPSoqKioqKiopDQoqKjogKj0qKioqKiAqKioqKioqICoqKiAqKi4g KioqKioqICoqKioqKiogfCAqKiAqKioqKioqKioqKioqKiAqKiogKioqKioqPQ0K DQoqIHwgKioqLSoqKiouICoqICoqKioqKioqKg0KDQoNKAotLSoqKioqKioqKioq KioqKioqKioqKioqKioqKioqKioqKi0tDQqIkgQBFggAOhYhBAYcPKRK/w7FjcZu lSLj+v6WtWwyBQJjllE3HBSAAAAAABAAA2NybGZAZXhhbXBsZS5vcmd4CnkACgkQ IuP6/pa1bDIfFwD/UqobVcQPz912db//JwoVK2Cv39ws6hCKOVa+FE21JCQA/2Gg bURJotQMIZVKy1cdC3C4F5/YOThxfot3JAwLBtkD =LmbW -----END PGP MESSAGE----- ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/messages/userid-bare.gpg�������������������������������������������0000644�0000000�0000000�00000000046�10461020230�0021522�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������$Neal H. Walfield <neal@walfield.org>������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/raw/a-cypherpunks-manifesto.aes128.key_ascending_from_0������������0000644�0000000�0000000�00000012046�10461020230�0027353�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������zt/j_DҚ?|p*'/>Τ:!9X✃W =05{lw^rͩJAml?zXo2wÓi q$$<F"ڃdU%4&9:4kb1+osUϖ%{M[ I&<Ƌ6z5I =Aօ!W n]{}Ӱ_JuXv2WslH!v>\XTsM�,iH$;m@wq32<"n5E3U0v\x uS6VeD5trdPդUI"3NFy.Nd)Q 0h f`RAlYL=w d|w`h5o#E" \eȫgL<2 }@Ft4)-{.h1Ee8}w-I1@-XW<Ke{MӦ8svt _&5߲ 8y790j-=/"u$J;nStXT>L8Jڵ_ONE^zd~G7h}pa)s =k+3 43(]b>1ƙM.n. 0Jm$ИSf]b$} 9d1>!O7@`شH�^3 ?ZeX+ bގ5 ?ʆ`0: {J>%( uhj!x:3[絟Ԅ MrDN<ӻ`,:Q2&Ҹ5HuiLV*-) Q8oeP?f4 sѶ;d 6px5 e^T� QM%/.}.kmZ;T]NӅ+\t1'="GdcTL-dծ-l*,<g3f.}tW�/+c }޹PGWPPf%Áhȼ8,rhH,>U2Y<'•KgKU*VNIX^ƊN}K^jK{{^vc!wZdY a' _"LAE3.,D6At [\^TGsIFZɀI&gjawzan>/[hx}oLL!aZyu6dMmރkr;! ;rFq|d⏮]cx߭=js=^KXAFБhndk ?6y5R�OP 5Tg>Q-6 ?A?WKKO o0 5r FkS4bo?Zo td1 c/GOE9qGiLO3:!ժ>ub;}۽B).# ο>o\e1vQ>R-HI̢#RNɠmg))@}z \cGAPEKs@-7[jj~r8wSW5iI.x 0o([K3x<M!o6$6x<M3%a|X%M7(7mozfA%b/o4~+ \ �WNjhZѕp).:E`�)~ܻ Mʱ;&hB%cy.xy}bʟl2 $J7+R{ESpsl@4Y&w}XMؙTRB]JW~kIH/J`xkYnrNzMVi-rź)ŁR_(cl]#PsYCkVK$@J/iq嗤Wo{ ?"�6vA*OƦ#i%zy.V\ZeS,׫+x4%o,VxSC2Ӥ\!:H\ȗ \1 CW©� d.3fP7r5>kUW(%8lñ7|@0S{_=հ˦rt�ɲjv!qʅ\砳 pr/B0b yN3�Ҕ>f.e6u7_,څ�<#`awA[`GQlye2$`m�oך'!©SvZxFkz冞H3rhpoй-RF|yQ-kCC@MI 2i؃(`p`sK r6@|Fzor: +ug Tn0:p*OڅX3&$<lS/' 2pcYJfsۘMvo| | I ʏl3ĉr&/Idm%P@XhVHAs%[x.Cϕ<8[*my,+휎+@5ZZ=ɬC[wdgRU(.? 9!ۑߥ.֏F,iv=:Jc}>ED!+pߛͤ@610]24aٙz-d\JyQ釫S #3:m@;7dKp(&yWw,(O$ >ɾ~ENw ,ӣvtV<RjTQTA $s,K.B{s2Wo^d ced ~#Y)e�l+ @G+Nv'veA6GXGn)6tV?'{ '1 &OU> t>ѨhʤBϣL62>NdY[@7dxeP̓4 28~s. e!>C00ڢ`HQH?7k94A2}) ƓOHS9ݬOj{;I!}~oVg)X.4oCщ3b<wQAԞn[NwV~# zu%Y'' RE7t}t`3ǝzH:f 1pTjl$@S?pL4\LqG*Ͽysz6kyY }/lɽ=pF4L(AN› 2y'̲S>p[e*/G,L 3AgqG tr*S'l?,1;3z!k]9!CkDkվY MR{=%"˧j%D(dh<po,Phj46F! 2F@'h}qO]Z:8Ѭ%, \?_|rpt' blz%oj$.(Vk@<=%4굾Bq^:t Mҥbp3# \ o[Kls~Bx?mi- -2k÷ Z=Qd<١h~pbgy XⴢĦ9Q40_1%l򉉒"/DDm3J&(ށQovDhJ)VJ/@0KYw{QU"p52ϴ/SNl ]w!ҭfV^?U c<vu.{]~)AhĮ%ք�FՕ%Ac4L၌ +arޟ6>9�!-MC7n+ܭ҄N !KtWûR$'wwNkv`"Ԁ]!si(X(`\ 3dYҚnu t[|4'x?0gԞߙގ*?W_}\UM`b}Z&YFVȴ�TuZ$SG^d\�"ܭ"2zD\ExTٚ_eRg2;@]6Fm^<7Ƀ  **kLe}MtyЙ.KoȦ|6ݸa.9KL;S'⪺<uJA?m|=(.7֐ƙLI|0(YP7+Q݃�%5G�j! ՈJ4 "%cCHT' uFi/ioF/HѡF#9 6J 𫔞wL+;@Ce76}C0OCi#a _Dr} ߐ noh!)^">m|!=t_UFw "[JE"ȀEmoӴFj(K 6:Τ@-'u vKuαn1nq8*R=O&hgGG񛏅 -KmŁQМnWj+T6mp&%"]rhIEVV&rk'm|Y("h}:A&$t_ؼy16v.uF Ұ/Nf* 6/ �5 pm#x<@,$dڌյ/AxYGyRݠ&iP vzȝjo/ЪpZU''Hʖ!lO7䐰Re䲆daR!o߷.S4 aF%9, {, %m޶E. EEȈ㞵>O^I VSJP uA�xs:jrsPFRUK4EM�lD*D; cs0ST4<R72oclEgkw$ClEi m|ሣ'nPU%i| Z؃Koa܅5Oa (\b7=,$QS0l"�lh/������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/raw/a-cypherpunks-manifesto.aes192.key_ascending_from_0������������0000644�0000000�0000000�00000012046�10461020230�0027354�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������qlFKF'?vtՍ+5lSXPX >c3zP}`#GVN#A ,,^Vax[@F^ Ha;5QryL?Vc5ˡOJw{̽|.\vUkEgoH6l$ ;{a5d.%PN%n*t}ah; Qo- 7hlVHF)jHr 1fi\b<LZ� O'a_Y*weFH]3P !=Y ]x-lM1Q4~phڙe0kH 2ukkV "]*9cܞmi2*z [g(UYٳz)~p ث`0C:.k\G{* ~96nqt(;(b*76hda8ނyz}Us ʬ1j~hWD;_`i0݋uBjTs|Ƴw$$%kR5Ŝm,lU̍GE"qyKpD2.꛿f!: wÔ7NH�jo=xj;`O3&ŝMBWRĄuR}V?.MFF[nUPTc89DxK<xxP&>K(A7r͹0ySyl)ĸ68'`z7g!..df KEߚ`ZE8�:"֥)l\bkzt5!ȓ'L"-[fbnZTsR cLe% i'Rz-/LW&^6Zlm(/k;<y~~ә<C+mHsf W aQˠEquAV|DƏ{esLjȠH#T 6ef"{4l<yb9zEig퉠dN:]yvuk8!2# .w q+#KP*8<lV}<ɍB� SD;p~NNd_Ǻ|-X&&z[8xnA1H4{2G(0HNj8Mمtx) 6bIN85-lwHm`B4,79I>JW2n9e1?R+Z0 d[/qd͕郇󘽯ы!2mh$qqN e׷x=%?!ù U(OL1:+5R2<-/ۚ49EHj%CL| 'gtz[1RC3b[;'K`!O K26>gYq ݢXx5t'V<yO,9d2ќI3EҰg`Lj{yFKyP Kp-XziZKm{PoGD%[8 ̇gCV JBbԽQ7l 8aQ•4М2+su,>7lG0ۮRju;b08>ӢTQ3LaU1*cm{tC�z// A4(xEtd{𳨀OI4lxoD]YpR J\*wv 5N#"`Kb,LgG4A= zld|td@$B)OWmsXݾMFר7\ӤXր y ]dsJE]S]_Fc]LeڲVלߒ ;_QAWᐁHSJmjRd+Nyji|ƒ\ %M74J4h2*T&8pTPxE!IE@+^0..oڍPoˉ{bvj`|t<QxqI~?C'wYud]/oL0YNm:h_HԢPn#Ϻ9P"\H?ҪQH6HM+�?GQSBڂ`_//T4izPŒ'sSIkeSm}.}S::]Io &]YB~sl[(gN썈EHեoʼA9;bwi>Ahrn14ÞVv$]LA=yja˛~«bo'0x;_+ F<Tq$^Z}f?߰xx8 !tG8ɤt/Pev1}&!,Ε*Yl"*ղk}*^3SḨq ;xϭ$ŞFy|倣3y!d߶u=BeC;~с"XL~M�"W'/az.K�NB!Gy"|B`L@=VFø|C":_v !é=+SL9VM2pƯO⻐^'iI*|L=*V>7jm55j<s!{8 lH"ʈ.T O(Js~πg[{ #6j~t3핈]yYπn}e}&eh krA}˘ J>B6yhBebSMH05<$zEsSkz欺!h_ Z@dnCG\&;<_)ePjk%?pcy{H4i= aBS[7gADv}#GI؛jxDvܛJJϞr"0dsC=KJ++WۏNTa(Trs77PnۮېZ۵N`+4ǾqZ[/ő$e8.%N~׍`YpQ\vW LG g9_8;|Du^+5vsP"pMg+2 RTs .'ZT0f>j=qST2ӈC@q1Fd&{rZ-yqL=.D X*w鋲;8tfa=>o]'iW;g0wV>ʁW1GR!Co *iL  4"EP;ܔ@lE>c,a]1dkY#OR]F a@h1>QȒ=hju$8u:{'HF` : v,e dtFn@cxSVMoBE^\]/ϫUxO y|6-y:m,IbP .r*J88NjԨ[ ^Km.09l\hΚLa1?$0ܬOPE-V=eӂيL5Lo&I{QY-76#J}٧B nO3'gW)w3 uG0xS<.orsy"Ar\M@YԎN~�Kct\4d5d+aײěz>T8iWR<xL<X3t3 ,! x�F,hR[G(YI,~3# a)O|Mry/kJt镏,ݙy`28˜bFFGRSRmtx<)CfUnds;DuI Guh=l}o+Hەⱉq1A>ay{hYo%/=)�ߜLY6ף-L@nůMj lamXU d}qı oi\5ҖC:pޔ"8лe%wK`6^&^aP^Q_hk3dvQ\]%U40+cfL~BKd$q%?I۞i*h$[7F>N,(҃jCݏ?\Ħ Vn)\yOoP wƚ> FWle !;1n1(7  �lB\d'2ځhJ4?*[sW*E&W0yp ln`z[ 颗 n9zkiu _E$]F zilG"L ,jb(j[ x URP4e pljNSnV"_›q6YW¢kQyqWd!fB)[y~7&b5|_ uF2Ĕp$pP( #rKKX$ysGc$,ʡ^)*`>QEA5Aw5n�q/[_BEzۃi?9Nx͸H5:*VHqY$D!4I%8TD>q,d,"͌/1%gBeA@hYrs*^;ɭs~=I=g&rڋ2v5ITVh$,G2d3Bh uO+m?!gOq eEȲL1M4irT81,Ug6b9�9N-Sepn2é@x�QC$)0L p7cԨ nu?t\VտM|8[xlwFPR5EdF%:`6:mZr\3?&bTfJ<X.%UUԦނ O&U6&m~sBa'FR|Ec4J-6ΥW9x60Ib刯 H`JTжoQ)z%XJoV _>Q^R,Fbv^<;: G8p}jک{lo+f||5d(SkQm+'/;glDaJ4v<y9SEj5%MSO,i>2ץ3&p#n'7JW������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/data/raw/a-cypherpunks-manifesto.aes256.key_ascending_from_0������������0000644�0000000�0000000�00000012046�10461020230�0027355�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������ 2t wp07?LtfbhW1jWރ"o$qֹ]yѳܬ\v,/Jt L/_oS ¾Npb�D թ{Q,n [<n\Oj+PkqF ~gЛ ̩ɺl !` sKQKJ&A }@C}Ňb)d9NG;'ӯ AZ%@xsb Fc z6Iϥx .PPu1۟Cʹ|0Z6V&ptYxtYSTH]]:PqzB |i RV8Ө<;7kv4*y*'u &J*Ek5fYE@}nV|=8ŶJ4R̕Ћ.a)|O:9:k2pMф 󭬁 ] n%>O@*Q}b]xrw_GA-u:Ƚ^ ?JeDDU,B|p#Jّ(95 'vWv!Hv WGs _'"\O)D01 V]_OSpb\/(չC[ �%@!%?5/m5'Tk v<c3 1ܩ4SǑ @B[`hVw VJꤐ+'zʫ:E Wi$}zl)2!Cp"Gf\uD #Eq`QЉNZs+b lP`x'xt1;p䒢Up5e;Vt?Z7sn.sQ4U ^CWO3_hLxb{g0=ۆgiEyɠ= 3*_m+d h�Ǫ>֕ңke@õ_k*D\σ3:+uU;-mlG7%<<�x( JT2\1ShɔG`oq_]W1X7&㮊\ňRib0Q Պ1ڮY!@Pⷮ2%hU) b'N7pZ.|w8$ڃ BBhmc\-$}hUwGQ?]A͐ʢŇPo LS+32|Yԗ:O)s G2e/0hʊn븬Yv[;p\큪BݽM&3q8˳ԮuxU [ZCD OwjE%> 1P50Ѳ\s6Ɗfᙁ$X&pr^R+FIأ<l5 Q͓ⓐ主; 2o<"w'\�E82W}3HSg@4DגnwY%hY�u65~6qa:tT4$U+,$Nvb`6-$Na6ч(&OZJ|DD7#5~7Hp Sy_ ^;=`Y{G{lBe1RmuN6F}{$-S-Z0ە{ (cłdWoS4`Ϡ^-HhI~~=Q NB^ s1iA;.1qK d'np\GD#;<Q WEo?swW&FԫԿ1pR]w!p^3U�OSP k[F9u~]"Y&{~X!N2`V7 尼V-@՜g7@8b69ڃe*(<1*hqY}"" ٚ^m {hDO4}]WhY3Wߠ:C@J˲WcRڥ YHUw3ʲ̡k�tyʛuO@iYzk𵒖JtyLQj1 _F behhqF亦璬�`5k@" @(΁,դ9>ަve5C2M6"-xDГ=ejm!B7o4q#] aobVqńiILEATrk]]( ɔ (B<cfmJΑ@;[ H~`\v<݈^$a=zEϣR<jEuKg^st.)aćOrJCm.*!`# 'G3RȺHs<Tx䞒&^Aj⌿DQh\wh+сDڕ?t.+ lUNIBOV28!.$QXS}G~HQF9 ȱ!FTxBErq bH;0Q:VapjOhsC]�fq # x�6l+e̓-{Klȃm k*I`)Zm1U M8 ' kfu,xwNEYp@~NӋZ E #%}%#;-.z'Z?Pk?>#fk .%=::B'M&b�&/ DUSq2yN fbϟ8O'2O4`}<U,*024O57#>?bI?ƿN7vyɤ+ω0Q 4@"x|˼�DF790H]w%pw1?& c)T{}cתF$#/wFPAU>V:[ * &/aD5Lt>syV=byW{}X,!gzۚcrT $C}¸9yr MB9孺Mv>/jxC p'_7h+ui1`(W!ga;`M\`A2ٓ2%(3>[hݜK(+\4TM͖ Q� r80# Zw H3_H%(3M2-8)E)%l{?R]#曎1*k ѵ wڥDL9Mv^n ˆ9VñmK%:ac2`.Rp/f3:8@E2{o]’NjqFX)IKkDHXs-w. Ž)e&w*[Βnq. ѲN@cЭ0jBK۟DOlW=[q-SG<mwu0}?"]_ק'%|=o b.^BTvV8PPNJ@ % z[oҒ554Ӻ@EH"p..SieD}DE0O5Td,g%8jJ&V>`5IsMhռ49!D4/H σ ]y )iXm˶wq6*#r-Iq6(t`fg)&Z5<¯繣=]֙,JSF#+a{U6^:h3` _l< }ִe R~ˣPxOJoB!6m?l`* aQ v^D ,X(*:491 D}αx~#e8(Jي|OGBv`uyO6tO9x{(#!Ee5Q\/<M$tE‚5jUBEI0B~OGŷdo+|33;FУdBYt9sgo!$*xk!il!ӝ7 uZiW'TfƖBzGe:A*x]Bvq˟ړowI/2�A6O}Y ً8sLl Bj^ە9D&0=t5ϝs ǃYԚ&Y2B+3+HXW^opf!j.k0f7F>SfDq Ysthyy1J9uҀHr*}L*"#@昜$>*Ef+υ\-7u<|xI5$얖]Q-ְQKd/NhwY﹎׊Rv Ha(J`+_zq z ^Q tSMe)FeTb%�i'õ={LE3ڢ33Z/cH!IZbܿ.M(vj7&MÞ7%M~0 0bfG1͔C>YLÆ+p"]RCC! x͘*n7{JQXrtO Yz+@3N$kԨ`f2YX}�+j^yy:U?!&SXpVq[ _?,= gˈ-"sS#2X04vE$Vf|ɁPQl*[u&F肧BaJ鷊Qϊe,Ioow]o̻ @$TYn E Fpڀhg|h C(~͚V3^[=Ig482v_ @Y-N4ҿY vʙsvDrF2Qz%b)xY*]WHOIZ3KiCbw4,ΗI=>^9^_Zq�ߠ8}=i( "il|EU_4*¦6>[�ܶ<fdnt6UvҒ鼢/59H|b+l< f)$قغ*ֺHOd+Z=�Cq)/l ji1STBsn(g@ƛ3Rs` nϬ7`z͆Du]d⇒phb󮕆:d㻈\]Zh������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/for-each-artifact.rs����������������������������������������������������0000644�0000000�0000000�00000030404�10461020230�0017727�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������use std::env; use std::fmt::Write; use std::fs; use std::io; use std::path::{Path, PathBuf}; use anyhow::anyhow; use sequoia_openpgp as openpgp; use crate::openpgp::parse::*; use crate::openpgp::PacketPile; use crate::openpgp::Result; use crate::openpgp::serialize::{Serialize, SerializeInto}; mod for_each_artifact { use super::*; // Pretty print buf as a hexadecimal string. fn hex(buf: &[u8]) -> Result<String> { let mut s = String::new(); for (i, b) in buf.iter().enumerate() { if i % 32 == 0 { if i > 0 { write!(s, "\n")?; } write!(s, "{:04}:", i)?; } if i % 2 == 0 { write!(s, " ")?; } if i % 8 == 0 { write!(s, " ")?; } write!(s, "{:02X}", b)?; } writeln!(s, "")?; Ok(s) } // Find the first difference between two serialized messages. fn diff_serialized(a: &[u8], b: &[u8]) -> Result<()> { if a == b { return Ok(()) } // There's a difference. Find it. let p = PacketPile::from_bytes(a)?.into_children(); let p_len = p.len(); let q = PacketPile::from_bytes(b)?.into_children(); let q_len = q.len(); let mut offset = 0; for (i, (p, q)) in p.zip(q).enumerate() { let a = &a[offset..offset+p.serialized_len()]; let b = &b[offset..offset+q.serialized_len()]; if a == b { offset += p.serialized_len(); continue; } eprintln!("Difference detected at packet #{}, offset: {}", i, offset); eprintln!(" left packet: {:?}", p); eprintln!("right packet: {:?}", q); eprintln!(" left hex ({} bytes):\n{}", a.len(), hex(a)?); eprintln!("right hex ({} bytes):\n{}", b.len(), hex(b)?); return Err(anyhow!("Packets #{} differ at offset {}", i, offset)); } assert!(p_len != q_len); eprintln!("Differing number of packets: {} bytes vs. {} bytes", p_len, q_len); return Err( anyhow!("Differing number of packets: {} bytes vs. {} bytes", p_len, q_len)); } #[test] fn packet_roundtrip() { for_all_files(&test_data_dir(), |src| { for_all_packets(src, |p| { let mut v = Vec::new(); p.serialize(&mut v)?; let q = openpgp::Packet::from_bytes(&v)?; if p != &q { return Err(anyhow::anyhow!( "assertion failed: p == q\np = {:?}\nq = {:?}", p, q)); } let w = p.to_vec()?; if v != w { return Err(anyhow::anyhow!( "assertion failed: v == w\nv = {:?}\nw = {:?}", v, w)); } Ok(()) }) }).unwrap(); } #[test] fn cert_roundtrip() { for_all_files(&test_data_dir(), |src| { let p = if let Ok(cert) = openpgp::Cert::from_file(src) { cert } else { // Ignore non-Cert files. return Ok(()); }; if ! p.primary_key().key().pk_algo().is_supported() { eprintln!("Skipping {} because we don't support {}", src.display(), p.primary_key().key().pk_algo()); return Ok(()); } let mut v = Vec::new(); p.as_tsk().serialize(&mut v)?; let q = openpgp::Cert::from_bytes(&v)?; if p != q { eprintln!("roundtripping {:?} failed", src); let p_: Vec<_> = p.clone().as_tsk().into_packets().collect(); let q_: Vec<_> = q.clone().as_tsk().into_packets().collect(); eprintln!("original: {} packets; roundtripped: {} packets", p_.len(), q_.len()); for (i, (p, q)) in p_.iter().zip(q_.iter()).enumerate() { if p != q { eprintln!("First difference at packet {}:\nOriginal: {:?}\nNew: {:?}", i, p, q); break; } } eprintln!("This is the recovered cert:\n{}", String::from_utf8_lossy( &q.armored().to_vec().unwrap())); } assert_eq!(p, q, "roundtripping {:?} failed", src); let w = p.as_tsk().to_vec().unwrap(); assert_eq!(v, w, "Serialize and SerializeInto disagree on {:?}", p); // Check that Cert::into_packets() and Cert::to_vec() // agree. (Cert::into_packets() returns no secret keys if // secret key material is present; Cert::to_vec only ever // returns public keys.) let v = p.to_vec()?; let mut buf = Vec::new(); for p in p.clone().into_packets() { p.serialize(&mut buf)?; } if let Err(_err) = diff_serialized(&buf, &v) { panic!("Checking that \ Cert::into_packets() \ and Cert::to_vec() agree."); } // Check that Cert::as_tsk().into_packets() and // Cert::as_tsk().to_vec() agree. let v = p.as_tsk().to_vec()?; let mut buf = Vec::new(); for p in p.as_tsk().into_packets() { p.serialize(&mut buf)?; } if let Err(_err) = diff_serialized(&buf, &v) { panic!("Checking that Cert::as_tsk().into_packets() \ and Cert::as_tsk().to_vec() agree."); } // Check that // Cert::strip_secret_key_material().into_packets() and // Cert::to_vec() agree. (Cert::into_packets() returns // secret keys if secret key material is present; // Cert::to_vec only ever returns public keys.) let v = p.to_vec()?; let mut buf = Vec::new(); #[allow(deprecated)] for p in p.clone().strip_secret_key_material().into_packets() { p.serialize(&mut buf)?; } if let Err(_err) = diff_serialized(&buf, &v) { panic!("Checking that \ Cert::strip_secret_key_material().into_packets() \ and Cert::to_vec() agree."); } // Check that TSK::into_packets() and // Cert::as_tsk().to_vec() agree. let v = p.as_tsk().to_vec()?; let mut buf = Vec::new(); for p in p.as_tsk().into_packets() { p.serialize(&mut buf)?; } if let Err(_err) = diff_serialized(&buf, &v) { panic!("Checking that Cert::into_packets() \ and Cert::as_tsk().to_vec() agree."); } Ok(()) }).unwrap(); } #[test] fn message_roundtrip() { for_all_files(&test_data_dir(), |src| { let p = if let Ok(msg) = openpgp::Message::from_file(src) { msg } else { // Ignore non-Message files. return Ok(()); }; let mut v = Vec::new(); p.serialize(&mut v)?; let q = openpgp::Message::from_bytes(&v)?; assert_eq!(p, q, "roundtripping {:?} failed", src); let w = p.to_vec().unwrap(); assert_eq!(v, w, "Serialize and SerializeInto disagree on {:?}", p); Ok(()) }).unwrap(); } #[test] fn raw_cert_roundtrip() { use openpgp::cert::raw::RawCert; for_all_files(&test_data_dir(), |src| { let p = if let Ok(cert) = RawCert::from_file(src) { cert } else { // Ignore non-Cert files. return Ok(()); }; let mut v = Vec::new(); p.packets().for_each(|p| v.extend_from_slice(p.as_bytes())); let q = RawCert::from_bytes(&v)?; assert_eq!(p, q, "roundtripping {:?} failed", src); Ok(()) }).unwrap(); } #[test] fn raw_cert_parser_roundtrip() { use openpgp::cert::raw::{RawCert, RawCertParser}; for_all_files(&test_data_dir(), |src| { let mut parser = if let Ok(p) = RawCertParser::from_file(src) { p } else { // Ignore non-Cert files. return Ok(()); }; while let Some(p) = parser.next() { let p = if let Ok(p) = p { p } else { // Ignore non-Cert files. continue; }; let mut v = Vec::new(); p.packets().for_each(|p| v.extend_from_slice(p.as_bytes())); let q = RawCert::from_bytes(&v)?; assert_eq!(p, q, "roundtripping {:?} failed", src); } Ok(()) }).unwrap(); } } /// Computes the path to the test directory. fn test_data_dir() -> PathBuf { let manifest_dir = PathBuf::from( env::var_os("CARGO_MANIFEST_DIR") .as_ref() .expect("CARGO_MANIFEST_DIR not set")); manifest_dir.join("tests").join("data") } /// Maps the given function `fun` over all Rust files in `src`. fn for_all_files<F>(src: &Path, mut fun: F) -> openpgp::Result<()> where F: FnMut(&Path) -> openpgp::Result<()> { let mut dirs = vec![src.to_path_buf()]; while let Some(dir) = dirs.pop() { for entry in fs::read_dir(dir).unwrap() { let entry = entry?; let path = entry.path(); if path.is_file() { // XXX: Look at the file extension and skip non-PGP // files. We need to do this because the // Armor-heuristic is so slow. See #204. if let Some(extension) = path.extension().and_then(|e| e.to_str()) { match extension { "pgp" | "gpg" | "asc" | "key" => (), e if e.contains("key") => (), _ => continue, } } else { // No extension or not valid UTF-8. continue; } eprintln!("Processing {:?}", path); match fun(&path) { Ok(_) => (), Err(e) => { eprintln!("Failed on file {:?}:\n", path); return Err(e); }, } } if path.is_dir() { dirs.push(path.clone()); } } } Ok(()) } /// Maps the given function `fun` over all packets in `src`. fn for_all_packets<F>(src: &Path, mut fun: F) -> openpgp::Result<()> where F: FnMut(&openpgp::Packet) -> openpgp::Result<()> { let ppb = PacketParserBuilder::from_file(src)?.buffer_unread_content(); let mut ppr = if let Ok(ppr) = ppb.build() { ppr } else { // Ignore junk. return Ok(()); }; while let PacketParserResult::Some(pp) = ppr { match pp.recurse() { Ok((packet, ppr_)) => { ppr = ppr_; if let openpgp::Packet::Unknown(_) = packet { continue; // Ignore packets that we cannot parse. } match fun(&packet) { Ok(_) => (), Err(e) => { eprintln!("Failed on packet {:?}:\n", packet); let mut sink = io::stderr(); let mut w = openpgp::armor::Writer::new( &mut sink, openpgp::armor::Kind::File)?; packet.serialize(&mut w)?; w.finalize()?; return Err(e); }, } }, Err(_) => break, } } Ok(()) } ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/secret-leak-detector/detector.rs����������������������������������������0000644�0000000�0000000�00000025127�10461020230�0022275�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������//! Scans a process memory for secret leaks. //! //! This is the code doing the experiments and scanning itself for //! leaks. See ../secret-leak-detector.rs for details. use std::{ alloc::{GlobalAlloc, System, Layout}, collections::HashMap, io::{Read, Write}, }; use sequoia_openpgp::{ crypto::{mem, mpi::ProtectedMPI, Password, SessionKey, Signer}, fmt::hex, packet::{ key::{Key4, PrimaryRole}, PKESK, SKESK, }, serialize::stream::{ Message, Encryptor, LiteralWriter, }, parse::{ stream::*, Parse, }, policy::StandardPolicy, types::{ HashAlgorithm, SymmetricAlgorithm, }, Cert, KeyHandle, Result, }; /// An allocator that leaks all allocations making it easier to spot /// secret leaks. struct LeakingAllocator; unsafe impl GlobalAlloc for LeakingAllocator { unsafe fn alloc(&self, layout: Layout) -> *mut u8 { System.alloc(layout) } unsafe fn dealloc(&self, _ptr: *mut u8, _layout: Layout) { // Leaking. } } #[global_allocator] static GLOBAL: LeakingAllocator = LeakingAllocator; /// How often to repeat a test. const N: usize = 1; /// The secret to use and scan for. const NEEDLE: &[u8] = b"@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@"; const N0: u8 = NEEDLE[0]; /// A clean base case that does nothing. fn clean_basecase() { } /// Constructs a vector carefully, without leaking the secret during /// construction. fn careful_to_vec<B: AsRef<[u8]>>(b: B) -> Vec<u8> { let b = b.as_ref(); let mut r = vec![0; b.len()]; b.iter().zip(r.iter_mut()).for_each(|(f, t)| *t = *f); r } /// Checks that a secret has been written. /// /// Notably, this also prevents optimizing the secret creation away. fn check_secret(v: &[u8]) { assert_eq!(v.iter().cloned().map(usize::from).sum::<usize>(), NEEDLE.len() * N0 as usize); std::hint::black_box(v); // Avoid optimizing away. unsafe { // Likewise. assert_eq!(libc::memcmp(v.as_ptr() as *const _, NEEDLE.as_ptr() as *const _, NEEDLE.len()), 0); } } /// A leaky base case that allocates a Vector. fn leak_basecase() { let v = NEEDLE.to_vec(); check_secret(&v); } /// A test case that allocates a Vector and securely overwrites it /// using [`memsec::memzero`]. fn test_memzero() { let mut v = careful_to_vec(NEEDLE); check_secret(&v); let len = v.len(); unsafe { memsec::memzero(v.as_mut_ptr(), len); } } /// A test case that allocates a Vector and securely overwrites it /// using [`libc::memset`]. fn test_libc_memset() { let mut v = careful_to_vec(NEEDLE); check_secret(&v); let len = v.len(); let p = unsafe { libc::memset(v.as_mut_ptr() as _, 0, len) }; std::hint::black_box(p); // Avoid optimizing the memset away. } /// A test case that allocates a mem::Protected and drops it. fn test_protected() { let v: mem::Protected = NEEDLE.into(); check_secret(&v); let v: mem::Protected = NEEDLE.to_vec().into(); check_secret(&v); } /// A test case that allocates a mem::Protected and drops it. fn test_protected_mpi() { let v: ProtectedMPI = NEEDLE.to_vec().into_boxed_slice().into(); check_secret(v.value()); let v: ProtectedMPI = NEEDLE.to_vec().into(); check_secret(v.value()); let v: ProtectedMPI = mem::Protected::from(NEEDLE).into(); check_secret(v.value()); } /// A test case that allocates a SessionKey and drops it. fn test_session_key() { let v: SessionKey = NEEDLE.into(); check_secret(&v); let v: SessionKey = NEEDLE.to_vec().into(); check_secret(&v); } /// A test case that allocates a mem::Encrypted, uses it once, then /// drops it. fn test_encrypted() { let m = mem::Encrypted::new(NEEDLE.into()).unwrap(); m.map(|v| check_secret(&v)); } /// A test case that allocates a Password, uses it once, then drops /// it. fn test_password() { let p = Password::from(NEEDLE); p.map(|v| check_secret(&v)); } /// A test case that allocates a Key4, uses it once, then /// drops it. fn test_ed25519() { let k = Key4::<_, PrimaryRole>::import_secret_ed25519(NEEDLE, None) .unwrap(); let mut kp = k.into_keypair().unwrap(); kp.sign(HashAlgorithm::SHA256, b"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa") .unwrap(); } /// A test case that encrypts a message using AES-256. fn test_aes_256_encryption() { aes_256_encryption().unwrap(); } fn aes_256_encryption() -> Result<Vec<u8>> { let mut sink = Vec::new(); let message = Message::new(&mut sink); let message = Encryptor::with_session_key( message, SymmetricAlgorithm::AES256, NEEDLE.into())? .add_passwords(Some(Password::from(NEEDLE))) .build()?; let mut w = LiteralWriter::new(message).build()?; w.write_all(b"Hello world.")?; w.finalize()?; Ok(sink) } /// A test case that decrypts a message using AES-256. fn test_aes_256_decryption() { let ciphertext = aes_256_encryption().unwrap(); aes_256_decryption(&ciphertext).unwrap(); } fn aes_256_decryption(ciphertext: &[u8]) -> Result<()> { let p = &StandardPolicy::new(); struct Helper {} impl VerificationHelper for Helper { fn get_certs(&mut self, _ids: &[KeyHandle]) -> Result<Vec<Cert>> { Ok(Vec::new()) } fn check(&mut self, _structure: MessageStructure) -> Result<()> { Ok(()) } } impl DecryptionHelper for Helper { fn decrypt(&mut self, _: &[PKESK], skesks: &[SKESK], _sym_algo: Option<SymmetricAlgorithm>, decrypt: &mut dyn FnMut(Option<SymmetricAlgorithm>, &SessionKey) -> bool) -> Result<Option<Cert>> { skesks[0].decrypt(&Password::from(NEEDLE)) .map(|(algo, session_key)| decrypt(algo, &session_key))?; Ok(None) } } let h = Helper {}; let mut v = DecryptorBuilder::from_bytes(ciphertext)? .with_policy(p, None, h)?; let mut content = Vec::new(); v.read_to_end(&mut content)?; assert_eq!(content, b"Hello world."); Ok(()) } type Test = fn() -> (); fn main() { let tests: HashMap<&'static str, Test> = [ ("clean_basecase", clean_basecase as Test), ("leak_basecase", leak_basecase as Test), ("test_memzero", test_memzero as Test), ("test_libc_memset", test_libc_memset as Test), ("test_protected", test_protected as Test), ("test_protected_mpi", test_protected_mpi as Test), ("test_session_key", test_session_key as Test), ("test_encrypted", test_encrypted as Test), ("test_password", test_password as Test), ("test_ed25519", test_ed25519 as Test), ("test_aes_256_encryption", test_aes_256_encryption as Test), ("test_aes_256_decryption", test_aes_256_decryption as Test), ].into_iter().collect(); let test = if let Some(t) = std::env::args().nth(1) { t } else { eprintln!("Usage: {} <TEST>\n\nAvailable tests:\n", std::env::args().nth(0).unwrap()); for t in tests.keys() { println!("{}", t); } return; }; eprintln!("{}: running test", test); for _ in 0..N { if let Some(test_fn) = tests.get(test.as_str()) { (test_fn)(); } else { panic!("unknown test case {:?}", test); } } scan(&test).unwrap(); } fn scan(name: &str) -> Result<()> { let mut found_secret = false; let mut sink = std::io::stderr(); for map in Map::iter()? { let map = map?; if map.read && map.write { let mut header_printed = false; let view = map.as_bytes()?; const CS: usize = 16; for (i, c) in view.chunks(CS).enumerate() { if c.iter().filter(|&b| *b == N0).count() > 7 { found_secret = true; if ! header_printed { eprintln!("{}: {} bytes", map.pathname, map.len); header_printed = true; } let mut d = hex::Dumper::with_offset( &mut sink, "", map.start as usize + i * CS); d.write_labeled(c, |_, buf| { let mut s = String::with_capacity(16); for b in buf { assert!(N0 != b'!'); s.push(if *b == N0 { '!' } else { '.' }); } Some(s) })?; } } } } if found_secret { eprintln!("{}: secret leaked", name); std::process::exit(1); } else { eprintln!("{}: passed", name); Ok(()) } } use std::{ fs::File, io::{BufReader, BufRead}, }; #[derive(Debug)] #[allow(dead_code)] struct Map { start: u64, len: u64, read: bool, write: bool, execute: bool, offset: u64, device: String, inode: u64, pathname: String, } impl Map { fn iter() -> Result<impl Iterator<Item = Result<Map>>> { let f = File::open("/proc/self/maps")?; let f = BufReader::new(f); Ok(f.lines().filter_map(|l| l.ok()).map(Self::parse_line)) } fn parse_line(l: String) -> Result<Self> { let f = l.splitn(6, ' ').collect::<Vec<_>>(); let a = f[0].splitn(2, '-').collect::<Vec<_>>(); let parse_hex = |s| -> Result<u64> { let b = hex::decode(s)?; let mut a = [0; 8]; // XXX word size <= u64 let l = a.len().min(b.len()); a[8 - l..].copy_from_slice(&b[..l]); Ok(u64::from_be_bytes(a)) }; let start = parse_hex(&a[0])?; let end = parse_hex(&a[1])?; assert!(start <= end); Ok(Map { start, len: end - start, read: f[1].as_bytes()[0] == b'r', write: f[1].as_bytes()[1] == b'w', execute: f[1].as_bytes()[2] == b'x', offset: parse_hex(&f[2])?, device: f[3].into(), inode: f[4].parse()?, pathname: f[5].trim_start().into(), }) } fn as_bytes(&self) -> Result<&[u8]> { if self.read { let s = unsafe { std::slice::from_raw_parts(self.start as usize as *const _, self.len as usize) }; Ok(s) } else { Err(anyhow::anyhow!("No read permissions")) } } } �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-2.0.0/tests/secret-leak-detector.rs�������������������������������������������������0000644�0000000�0000000�00000020235�10461020230�0020457�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������//! Scans a process memory for secret leaks. //! //! These tests attempt to detect secrets leaking into the stack and //! heap without being zeroed after use. To that end, we do the //! following: //! //! 1. We break free(3) by using a custom allocator that leaks all //! heap allocations, so that memory will not be reused and we //! can robustly detect secret leaking into the heap because //! there is no risk of them being overwritten. //! //! 2. We do an operation involving secrets. We use a fixed secret //! with a simple pattern (currently repeated '@'s) that is easy //! to find in memory later. //! //! 3. After the operation, we scan the processes memory (only //! readable and writable regions) for the secret. //! //! # Example of a test failure //! //! This shows the secret leaking into the stack: //! //! ``` //! test_ed25519: running test //! [stack]: 139264 bytes //! 7ffed1a8ee10 40 40 40 40 40 40 40 40 40 40 40 40 40 40 40 40 !!!!!!!!!!!!!!!! //! 7ffed1a8ee20 40 40 40 40 40 40 40 40 40 40 40 40 40 40 40 40 !!!!!!!!!!!!!!!! //! 7ffed1a90080 40 40 40 40 40 40 40 40 40 40 40 40 40 40 40 40 !!!!!!!!!!!!!!!! //! 7ffed1a90090 40 40 40 40 40 40 40 40 40 40 40 40 40 40 40 40 !!!!!!!!!!!!!!!! //! 7ffed1a900a0 40 40 40 40 40 40 40 40 40 40 40 40 40 40 40 40 !!!!!!!!!!!!!!!! //! 7ffed1a900e0 40 40 40 40 40 40 40 40 40 40 40 40 40 40 40 40 !!!!!!!!!!!!!!!! //! 7ffed1a90110 40 40 40 40 40 40 40 40 40 40 40 40 40 40 40 40 !!!!!!!!!!!!!!!! //! 7ffed1a90120 40 40 40 40 40 40 40 40 40 40 40 40 40 40 40 40 !!!!!!!!!!!!!!!! //! 7ffed1a90130 40 40 40 40 40 40 40 40 40 40 40 40 40 40 40 40 !!!!!!!!!!!!!!!! //! 7ffed1a90170 40 40 40 40 40 40 40 40 40 40 40 40 40 40 40 40 !!!!!!!!!!!!!!!! //! test_ed25519: secret leaked //! test leak_tests::test_ed25519 ... FAILED //! ``` //! //! # Debugging secret leaks //! //! To find what code leaks the secret, we use rr, the lightweight //! recording & deterministic debugging tool. Deterministic is key //! here, we can see where the secret leaks to, and then replay the //! execution and set a watchpoint on the address, and know that it //! will leak to the exact same address again: //! //! ```sh //! $ rr record target/debug/examples/secret-leak-detector test_ed25519 //! [stack]: 139264 bytes //! 7ffc9119dab0 40 40 40 40 40 40 40 40 40 40 40 40 40 40 40 40 !!!!!!!!!!!!!!!! //! 7ffc9119dac0 40 40 40 40 40 40 40 40 40 40 40 40 40 40 40 40 !!!!!!!!!!!!!!!! //! 7ffc9119ece0 40 40 40 40 40 40 40 40 40 40 40 40 40 40 40 40 !!!!!!!!!!!!!!!! //! 7ffc9119ecf0 40 40 40 40 40 40 40 40 40 40 40 40 40 40 40 40 !!!!!!!!!!!!!!!! //! 7ffc9119ed00 40 40 40 40 40 40 40 40 40 40 40 40 40 40 40 40 !!!!!!!!!!!!!!!! //! 7ffc9119ed40 40 40 40 40 40 40 40 40 40 40 40 40 40 40 40 40 !!!!!!!!!!!!!!!! //! 7ffc9119ed70 40 40 40 40 40 40 40 40 40 40 40 40 40 40 40 40 !!!!!!!!!!!!!!!! //! 7ffc9119ed80 40 40 40 40 40 40 40 40 40 40 40 40 40 40 40 40 !!!!!!!!!!!!!!!! //! 7ffc9119ed90 40 40 40 40 40 40 40 40 40 40 40 40 40 40 40 40 !!!!!!!!!!!!!!!! //! 7ffc9119edd0 40 40 40 40 40 40 40 40 40 40 40 40 40 40 40 40 !!!!!!!!!!!!!!!! //! test_ed25519: secret leaked //! $ rr replay -d rust-gdb //! [...] //! (rr) watch *0x7ffc9119dab0 if *0x7ffc9119dab0 == 0x40404040 //! Hardware watchpoint 1: *0x7ffc9119dab0 //! (rr) c //! Continuing. //! test_ed25519: running test //! //! Hardware watchpoint 1: *0x7ffc9119dab0 //! //! Old value = 0 //! New value = 1077952576 //! 0x000055a463e40969 in sha2::sha512::x86::sha512_update_x_avx (x=0x7ffc9119eba0, k64=...) //! at src/sha512/x86.rs:260 //! 260 let mut t2 = $SRL64(t0, 1); //! (rr) c //! Continuing. //! //! Hardware watchpoint 1: *0x7ffc9119dab0 //! //! Old value = -997709592 //! New value = 1077952576 //! 0x000055a463e3bde0 in sha2::sha512::x86::load_data_avx (x=0x7ffc9119e200, ms=0x7ffc9119e180, //! data=0x7ffc911a1658) at src/sha512/x86.rs:89 //! 89 unrolled_iterations!(0, 1, 2, 3, 4, 5, 6, 7); //! (rr) c //! Continuing. //! [stack]: 139264 bytes //! 7ffc9119dab0 40 40 40 40 40 40 40 40 40 40 40 40 40 40 40 40 !!!!!!!!!!!!!!!! //! 7ffc9119dac0 40 40 40 40 40 40 40 40 40 40 40 40 40 40 40 40 !!!!!!!!!!!!!!!! //! 7ffc9119ece0 40 40 40 40 40 40 40 40 40 40 40 40 40 40 40 40 !!!!!!!!!!!!!!!! //! 7ffc9119ecf0 40 40 40 40 40 40 40 40 40 40 40 40 40 40 40 40 !!!!!!!!!!!!!!!! //! 7ffc9119ed00 40 40 40 40 40 40 40 40 40 40 40 40 40 40 40 40 !!!!!!!!!!!!!!!! //! 7ffc9119ed40 40 40 40 40 40 40 40 40 40 40 40 40 40 40 40 40 !!!!!!!!!!!!!!!! //! 7ffc9119ed70 40 40 40 40 40 40 40 40 40 40 40 40 40 40 40 40 !!!!!!!!!!!!!!!! //! 7ffc9119ed80 40 40 40 40 40 40 40 40 40 40 40 40 40 40 40 40 !!!!!!!!!!!!!!!! //! 7ffc9119ed90 40 40 40 40 40 40 40 40 40 40 40 40 40 40 40 40 !!!!!!!!!!!!!!!! //! 7ffc9119edd0 40 40 40 40 40 40 40 40 40 40 40 40 40 40 40 40 !!!!!!!!!!!!!!!! //! test_ed25519: secret leaked //! //! Program received signal SIGKILL, Killed. //! 0x0000000070000002 in syscall_traced () //! ``` #![allow(dead_code)] use std::{ env, io::{self, Write}, path::{Path, PathBuf}, process::Command, sync::OnceLock, }; use anyhow::Result; /// Locates the detector program. /// /// The detector program is built as an example. This has the /// advantage that it is built using the same feature flags as the /// test is invoked, and it is built with as little overhead as /// possible. The downside is that it may not have been built: if /// cargo test --test secret-leak-detector is invoked to only run this /// integration test, then the examples are not implicitly built /// before running this test. fn locate_detector() -> Option<&'static Path> { static NOFREE: OnceLock<Option<PathBuf>> = OnceLock::new(); Some(NOFREE.get_or_init(|| -> Option<PathBuf> { let mut p = PathBuf::from(env::var_os("OUT_DIR")?); loop { let q = p.join("examples").join("secret-leak-detector"); if q.exists() { break Some(q); } if let Some(parent) = p.parent() { p = parent.to_path_buf(); } else { break None; } } }).as_ref()?.as_path()) } /// Emits a message to stderr that is not captured by the test /// framework, and returns success. fn skip() -> Result<()> { // Write directly to stderr. This way, we can emit the message // even though the test output is captured. writeln!(&mut io::stderr(), "Detector not built, skipping test: \ run cargo build -p sequoia-openpgp \ --example secret-leak-detector first")?; Ok(()) } macro_rules! make_test { ($name: ident) => { #[cfg_attr(target_os = "linux", test)] fn $name() -> Result<()> { if let Some(d) = locate_detector() { let result = Command::new(d) .arg(stringify!($name)) .output()?; if result.status.success() { Ok(()) } else { io::stderr().write_all(&result.stderr)?; Err(anyhow::anyhow!("leak detected")) } } else { skip() } } } } mod leak_tests { use super::*; /// Tests that we actually detect leaks. #[cfg_attr(target_os = "linux", test)] fn leak_basecase() -> Result<()> { if let Some(d) = locate_detector() { let result = Command::new(d) .arg("leak_basecase") .output()?; if ! result.status.success() { Ok(()) } else { Err(anyhow::anyhow!("base case failed: no leak detected")) } } else { skip() } } // The tests. make_test!(clean_basecase); make_test!(test_memzero); make_test!(test_libc_memset); make_test!(test_protected); make_test!(test_protected_mpi); make_test!(test_session_key); make_test!(test_encrypted); make_test!(test_password); make_test!(test_ed25519); #[cfg(not(feature = "crypto-rust"))] make_test!(test_aes_256_encryption); #[cfg(not(feature = "crypto-rust"))] make_test!(test_aes_256_decryption); } �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������