sequoia-openpgp-1.7.0/.cargo_vcs_info.json0000644000000001120000000000100141470ustar { "git": { "sha1": "65cd6fc513741cf9c1187c9750b226251ade8f20" } } sequoia-openpgp-1.7.0/Cargo.lock0000644000001473250000000000100121440ustar # This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 3 [[package]] name = "addr2line" version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9ecd88a8c8378ca913a680cd98f0f13ac67383d35993f86c90a70e3f137816b" dependencies = [ "gimli", ] [[package]] name = "adler" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" [[package]] name = "aead" version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7fc95d1bdb8e6666b2b217308eeeb09f2d6728d104be3e31916cc74d15420331" dependencies = [ "generic-array", ] [[package]] name = "aes" version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "884391ef1066acaa41e766ba8f596341b96e93ce34f9a43e7d24bf0a0eaf0561" dependencies = [ "aes-soft", "aesni", "cipher", ] [[package]] name = "aes-soft" version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "be14c7498ea50828a38d0e24a765ed2effe92a705885b57d029cd67d45744072" dependencies = [ "cipher", "opaque-debug", ] [[package]] name = "aesni" version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ea2e11f5e94c2f7d386164cc2aa1f97823fed6f259e486940a71c174dd01b0ce" dependencies = [ "cipher", "opaque-debug", ] [[package]] name = "aho-corasick" version = "0.7.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f" dependencies = [ "memchr", ] [[package]] name = "anyhow" version = "1.0.51" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b26702f315f53b6071259e15dd9d64528213b44d61de1ec926eca7715d62203" [[package]] name = "ascii-canvas" version = "3.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8824ecca2e851cec16968d54a01dd372ef8f95b244fb84b84e70128be347c3c6" dependencies = [ "term", ] [[package]] name = "atty" version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" dependencies = [ "hermit-abi", "libc", "winapi", ] [[package]] name = "autocfg" version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d49d90015b3c36167a20fe2810c5cd875ad504b39cff3d4eae7977e6b7c1cb2" [[package]] name = "autocfg" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" [[package]] name = "backtrace" version = "0.3.63" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "321629d8ba6513061f26707241fa9bc89524ff1cd7a915a97ef0c62c666ce1b6" dependencies = [ "addr2line", "cc", "cfg-if", "libc", "miniz_oxide", "object", "rustc-demangle", ] [[package]] name = "base64" version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" [[package]] name = "bindgen" version = "0.57.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fd4865004a46a0aafb2a0a5eb19d3c9fc46ee5f063a6cfc605c69ac9ecf5263d" dependencies = [ "bitflags", "cexpr", "clang-sys", "lazy_static", "lazycell", "peeking_take_while", "proc-macro2", "quote", "regex", "rustc-hash", "shlex", ] [[package]] name = "bit-set" version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6e11e16035ea35e4e5997b393eacbf6f63983188f7a2ad25bfb13465f5ad59de" dependencies = [ "bit-vec", ] [[package]] name = "bit-vec" version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" [[package]] name = "bitflags" version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitvec" version = "0.20.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7774144344a4faa177370406a7ff5f1da24303817368584c6206c8303eb07848" dependencies = [ "funty", "radium", "tap", "wyz", ] [[package]] name = "block-buffer" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" dependencies = [ "generic-array", ] [[package]] name = "block-modes" version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57a0e8073e8baa88212fb5823574c02ebccb395136ba9a164ab89379ec6072f0" dependencies = [ "block-padding", "cipher", ] [[package]] name = "block-padding" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8d696c370c750c948ada61c69a0ee2cbbb9c50b1019ddb86d9317157a99c2cae" [[package]] name = "blowfish" version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32fa6a061124e37baba002e496d203e23ba3d7b73750be82dbfbc92913048a5b" dependencies = [ "byteorder", "cipher", "opaque-debug", ] [[package]] name = "bstr" version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba3569f383e8f1598449f1a423e72e99569137b47740b1da11ef19af3d5c3223" dependencies = [ "lazy_static", "memchr", "regex-automata", "serde", ] [[package]] name = "buffered-reader" version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4212de05390ccc7d0e0511d0560c91f278fa407c9153c60aa94fa513b8ec6c9" dependencies = [ "bzip2", "flate2", "libc", ] [[package]] name = "bumpalo" version = "3.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f1e260c3a9040a7c19a12468758f4c16f31a81a1fe087482be9570ec864bb6c" [[package]] name = "byteorder" version = "1.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" [[package]] name = "bzip2" version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6afcd980b5f3a45017c57e57a2fcccbb351cc43a356ce117ef760ef8052b89b0" dependencies = [ "bzip2-sys", "libc", ] [[package]] name = "bzip2-sys" version = "0.1.11+1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "736a955f3fa7875102d57c82b8cac37ec45224a07fd32d58f9f7a186b6cd4cdc" dependencies = [ "cc", "libc", "pkg-config", ] [[package]] name = "cast" version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4c24dab4283a142afa2fdca129b80ad2c6284e073930f964c3a1293c225ee39a" dependencies = [ "rustc_version", ] [[package]] name = "cast5" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1285caf81ea1f1ece6b24414c521e625ad0ec94d880625c20f2e65d8d3f78823" dependencies = [ "byteorder", "cipher", "opaque-debug", ] [[package]] name = "cc" version = "1.0.72" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "22a9137b95ea06864e018375b72adfb7db6e6f68cfc8df5a04d00288050485ee" [[package]] name = "cexpr" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f4aedb84272dbe89af497cf81375129abda4fc0a9e7c5d317498c15cc30c0d27" dependencies = [ "nom", ] [[package]] name = "cfg-if" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "chrono" version = "0.4.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73" dependencies = [ "js-sys", "libc", "num-integer", "num-traits", "time", "wasm-bindgen", "winapi", ] [[package]] name = "cipher" version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "12f8e7987cbd042a63249497f41aed09f8e65add917ea6566effbc56578d6801" dependencies = [ "generic-array", ] [[package]] name = "clang-sys" version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fa66045b9cb23c2e9c1520732030608b02ee07e5cfaa5a521ec15ded7fa24c90" dependencies = [ "glob", "libc", "libloading", ] [[package]] name = "clap" version = "2.34.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c" dependencies = [ "bitflags", "textwrap", "unicode-width", ] [[package]] name = "cmac" version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73d4de4f7724e5fe70addfb2bd37c2abd2f95084a429d7773b0b9645499b4272" dependencies = [ "crypto-mac 0.10.1", "dbl", ] [[package]] name = "const-oid" version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "279bc8fc53f788a75c7804af68237d1fce02cde1e275a886a4b320604dc2aeda" [[package]] name = "cpufeatures" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95059428f66df56b63431fdb4e1947ed2190586af5c5a8a8b71122bdf5a7f469" dependencies = [ "libc", ] [[package]] name = "crc32fast" version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "738c290dfaea84fc1ca15ad9c168d083b05a714e1efddd8edaab678dc28d2836" dependencies = [ "cfg-if", ] [[package]] name = "criterion" version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1604dafd25fba2fe2d5895a9da139f8dc9b319a5fe5354ca137cbbce4e178d10" dependencies = [ "atty", "cast", "clap", "criterion-plot", "csv", "itertools", "lazy_static", "num-traits", "oorandom", "plotters", "rayon", "regex", "serde", "serde_cbor", "serde_derive", "serde_json", "tinytemplate", "walkdir", ] [[package]] name = "criterion-plot" version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d00996de9f2f7559f7f4dc286073197f83e92256a59ed395f9aac01fe717da57" dependencies = [ "cast", "itertools", ] [[package]] name = "crossbeam-channel" version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06ed27e177f16d65f0f0c22a213e17c696ace5dd64b14258b52f9417ccb52db4" dependencies = [ "cfg-if", "crossbeam-utils", ] [[package]] name = "crossbeam-deque" version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6455c0ca19f0d2fbf751b908d5c55c1f5cbc65e03c4225427254b46890bdde1e" dependencies = [ "cfg-if", "crossbeam-epoch", "crossbeam-utils", ] [[package]] name = "crossbeam-epoch" version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ec02e091aa634e2c3ada4a392989e7c3116673ef0ac5b72232439094d73b7fd" dependencies = [ "cfg-if", "crossbeam-utils", "lazy_static", "memoffset", "scopeguard", ] [[package]] name = "crossbeam-utils" version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d82cfc11ce7f2c3faef78d8a684447b40d503d9681acebed6cb728d45940c4db" dependencies = [ "cfg-if", "lazy_static", ] [[package]] name = "crunchy" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" [[package]] name = "crypto-mac" version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bff07008ec701e8028e2ceb8f83f0e4274ee62bd2dbdc4fefff2e9a91824081a" dependencies = [ "cipher", "generic-array", "subtle", ] [[package]] name = "crypto-mac" version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b1d1a86f49236c215f271d40892d5fc950490551400b02ef360692c29815c714" dependencies = [ "generic-array", "subtle", ] [[package]] name = "csv" version = "1.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "22813a6dc45b335f9bade10bf7271dc477e81113e89eb251a0bc2a8a81c536e1" dependencies = [ "bstr", "csv-core", "itoa 0.4.8", "ryu", "serde", ] [[package]] name = "csv-core" version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b2466559f260f48ad25fe6317b3c8dac77b5bdb5763ac7d9d6103530663bc90" dependencies = [ "memchr", ] [[package]] name = "ctr" version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fb4a30d54f7443bf3d6191dcd486aca19e67cb3c49fa7a06a319966346707e7f" dependencies = [ "cipher", ] [[package]] name = "curve25519-dalek" version = "3.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b9fdf9972b2bd6af2d913799d9ebc165ea4d2e65878e329d9c6b372c4491b61" dependencies = [ "byteorder", "digest", "rand_core 0.5.1", "subtle", "zeroize", ] [[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.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2eeb9d92785d1facb50567852ce75d0858630630e7eabea59cf7eb7474051087" dependencies = [ "const-oid", "typenum", ] [[package]] name = "des" version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b24e7c748888aa2fa8bce21d8c64a52efc810663285315ac7476f7197a982fae" dependencies = [ "byteorder", "cipher", "opaque-debug", ] [[package]] name = "diff" version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0e25ea47919b1560c4e3b7fe0aaab9becf5b84a10325ddf7db0f0ba5e1026499" [[package]] name = "digest" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" dependencies = [ "generic-array", ] [[package]] name = "dirs-next" version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1" dependencies = [ "cfg-if", "dirs-sys-next", ] [[package]] name = "dirs-sys-next" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d" dependencies = [ "libc", "redox_users", "winapi", ] [[package]] name = "doc-comment" version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" [[package]] name = "dyn-clone" version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ee2626afccd7561a06cf1367e2950c4718ea04565e20fb5029b6c7d8ad09abcf" [[package]] name = "eax" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e1f76e7a5e594b299a0fa9a99de627530725e341df41376aa342aecb2c5eb76e" dependencies = [ "aead", "cipher", "cmac", "ctr", "subtle", ] [[package]] name = "ecdsa" version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34d33b390ab82f2e1481e331dbd0530895640179d2128ef9a79cc690b78d1eba" dependencies = [ "der", "elliptic-curve", "hmac", "signature", ] [[package]] name = "ed25519" version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "74e1069e39f1454367eb2de793ed062fac4c35c2934b76a81d90dd9abcd28816" dependencies = [ "signature", ] [[package]] name = "ed25519-dalek" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c762bae6dcaf24c4c84667b8579785430908723d5c889f469d76a41d59cc7a9d" dependencies = [ "curve25519-dalek", "ed25519", "rand 0.7.3", "sha2", "zeroize", ] [[package]] name = "either" version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" [[package]] name = "elliptic-curve" version = "0.9.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c13e9b0c3c4170dcc2a12783746c4205d98e18957f57854251eea3f9750fe005" dependencies = [ "bitvec", "ff", "generic-array", "group", "pkcs8", "rand_core 0.6.3", "subtle", "zeroize", ] [[package]] name = "ena" version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d7402b94a93c24e742487327a7cd839dc9d36fec9de9fb25b09f2dae459f36c3" dependencies = [ "log", ] [[package]] name = "ff" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72a4d941a5b7c2a75222e2d44fcdf634a67133d9db31e177ae5ff6ecda852bfe" dependencies = [ "bitvec", "rand_core 0.6.3", "subtle", ] [[package]] name = "fixedbitset" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37ab347416e802de484e4d03c7316c48f1ecb56574dfd4a46a80f173ce1de04d" [[package]] name = "flate2" version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e6988e897c1c9c485f43b47a529cef42fde0547f9d8d41a7062518f1d8fc53f" dependencies = [ "cfg-if", "crc32fast", "libc", "miniz_oxide", ] [[package]] name = "funty" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fed34cd105917e91daa4da6b3728c47b068749d6a62c59811f06ed2ac71d9da7" [[package]] name = "generic-array" version = "0.14.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "501466ecc8a30d1d3b7fc9229b122b2ce8ed6e9d9223f1138d4babb253e51817" dependencies = [ "typenum", "version_check", ] [[package]] name = "getrandom" version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" dependencies = [ "cfg-if", "js-sys", "libc", "wasi 0.9.0+wasi-snapshot-preview1", "wasm-bindgen", ] [[package]] name = "getrandom" version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7fcd999463524c52659517fe2cea98493cfe485d10565e7b0fb07dbba7ad2753" dependencies = [ "cfg-if", "js-sys", "libc", "wasi 0.10.2+wasi-snapshot-preview1", "wasm-bindgen", ] [[package]] name = "gimli" version = "0.26.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78cc372d058dcf6d5ecd98510e7fbc9e5aec4d21de70f65fea8fecebcd881bd4" [[package]] name = "glob" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574" [[package]] name = "group" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "61b3c1e8b4f1ca07e6605ea1be903a5f6956aec5c8a67fd44d56076631675ed8" dependencies = [ "ff", "rand_core 0.6.3", "subtle", ] [[package]] name = "half" version = "1.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eabb4a44450da02c90444cf74558da904edde8fb4e9035a9a6a4e15445af0bd7" [[package]] name = "hashbrown" version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" [[package]] name = "hermit-abi" version = "0.1.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" dependencies = [ "libc", ] [[package]] name = "hmac" version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2a2a2320eb7ec0ebe8da8f744d7812d9fc4cb4d09344ac01898dbcb6a20ae69b" dependencies = [ "crypto-mac 0.11.1", "digest", ] [[package]] name = "idea" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fcdd4b114cf2265123bbdc5d32a39f96a343fbdf141267d2b5232b7e14caacb3" dependencies = [ "cipher", "opaque-debug", ] [[package]] name = "idna" version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "418a0a6fab821475f634efe3ccc45c013f742efe03d853e8d3355d5cb850ecf8" dependencies = [ "matches", "unicode-bidi", "unicode-normalization", ] [[package]] name = "indexmap" version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bc633605454125dec4b66843673f01c7df2b89479b32e0ed634e43a91cff62a5" dependencies = [ "autocfg 1.0.1", "hashbrown", ] [[package]] name = "instant" version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" dependencies = [ "cfg-if", ] [[package]] name = "itertools" version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a9a9d19fa1e79b6215ff29b9d6880b706147f16e9b1dbb1e4e5947b5b02bc5e3" dependencies = [ "either", ] [[package]] name = "itoa" version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4" [[package]] name = "itoa" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1aab8fc367588b89dcee83ab0fd66b72b50b72fa1904d7095045ace2b0c81c35" [[package]] name = "js-sys" version = "0.3.55" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7cc9ffccd38c451a86bf13657df244e9c3f37493cce8e5e21e940963777acc84" dependencies = [ "wasm-bindgen", ] [[package]] name = "lalrpop" version = "0.19.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b15174f1c529af5bf1283c3bc0058266b483a67156f79589fab2a25e23cf8988" dependencies = [ "ascii-canvas", "atty", "bit-set", "diff", "ena", "itertools", "lalrpop-util", "petgraph", "pico-args", "regex", "regex-syntax", "string_cache", "term", "tiny-keccak", "unicode-xid", ] [[package]] name = "lalrpop-util" version = "0.19.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3e58cce361efcc90ba8a0a5f982c741ff86b603495bb15a998412e957dcd278" dependencies = [ "regex", ] [[package]] name = "lazy_static" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" dependencies = [ "spin", ] [[package]] name = "lazycell" version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" [[package]] name = "libc" version = "0.2.112" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b03d17f364a3a042d5e5d46b053bbbf82c92c9430c592dd4c064dc6ee997125" [[package]] name = "libloading" version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "afe203d669ec979b7128619bae5a63b7b42e9203c1b29146079ee05e2f604b52" dependencies = [ "cfg-if", "winapi", ] [[package]] name = "libm" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c7d73b3f436185384286bd8098d17ec07c9a7d2388a6599f824d8502b529702a" [[package]] name = "lock_api" version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712a4d093c9976e24e7dbca41db895dabcbac38eb5f4045393d17a95bdfb1109" dependencies = [ "scopeguard", ] [[package]] name = "log" version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" dependencies = [ "cfg-if", ] [[package]] name = "matches" version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f" [[package]] name = "md-5" version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7b5a279bb9607f9f53c22d496eade00d138d1bdcccd07d74650387cf94942a15" dependencies = [ "block-buffer", "digest", "opaque-debug", ] [[package]] name = "memchr" version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a" [[package]] name = "memoffset" version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce" dependencies = [ "autocfg 1.0.1", ] [[package]] name = "memsec" version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2af4f95d8737f4ffafbd1fb3c703cdc898868a244a59786793cba0520ebdcbdd" [[package]] name = "miniz_oxide" version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a92518e98c078586bc6c934028adcca4c92a53d6a958196de835170a01d84e4b" dependencies = [ "adler", "autocfg 1.0.1", ] [[package]] name = "nettle" version = "7.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a04390f9570e0c8949ed8f15d9e6f6f8a0a37a82b8c084803e7cd2f7f5c09ae2" dependencies = [ "getrandom 0.2.3", "libc", "nettle-sys", "thiserror", ] [[package]] name = "nettle-sys" version = "2.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b95aff9e61c8d8132e41dceae74c6e526edcac8d120072c87a300b9ab7e75226" dependencies = [ "bindgen", "pkg-config", "vcpkg", ] [[package]] name = "new_debug_unreachable" version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e4a24736216ec316047a1fc4252e27dabb04218aa4a3f37c6e7ddbf1f9782b54" [[package]] name = "nom" version = "5.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ffb4262d26ed83a1c0a33a38fe2bb15797329c85770da05e6b828ddb782627af" dependencies = [ "memchr", "version_check", ] [[package]] name = "num-bigint" version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "090c7f9998ee0ff65aa5b723e4009f7b217707f1fb5ea551329cc4d6231fb304" dependencies = [ "autocfg 1.0.1", "num-integer", "num-traits", ] [[package]] name = "num-bigint-dig" version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5d51546d704f52ef14b3c962b5776e53d5b862e5790e40a350d366c209bd7f7a" dependencies = [ "autocfg 0.1.7", "byteorder", "lazy_static", "libm", "num-integer", "num-iter", "num-traits", "rand 0.7.3", "serde", "smallvec", "zeroize", ] [[package]] name = "num-integer" version = "0.1.44" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db" dependencies = [ "autocfg 1.0.1", "num-traits", ] [[package]] name = "num-iter" version = "0.1.42" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b2021c8337a54d21aca0d59a92577a029af9431cb59b909b03252b9c164fad59" dependencies = [ "autocfg 1.0.1", "num-integer", "num-traits", ] [[package]] name = "num-traits" version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290" dependencies = [ "autocfg 1.0.1", ] [[package]] name = "num_cpus" version = "1.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "19e64526ebdee182341572e50e9ad03965aa510cd94427a4549448f285e957a1" dependencies = [ "hermit-abi", "libc", ] [[package]] name = "object" version = "0.27.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "67ac1d3f9a1d3616fd9a60c8d74296f22406a238b6a72f5cc1e6f314df4ffbf9" dependencies = [ "memchr", ] [[package]] name = "once_cell" version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "da32515d9f6e6e489d7bc9d84c71b060db7247dc035bbe44eac88cf87486d8d5" [[package]] name = "oorandom" version = "11.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ab1bc2a289d34bd04a330323ac98a1b4bc82c9d9fcb1e66b63caa84da26b575" [[package]] name = "opaque-debug" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" [[package]] name = "p256" version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2f05f5287453297c4c16af5e2b04df8fd2a3008d70f252729650bc6d7ace5844" dependencies = [ "ecdsa", "elliptic-curve", "sha2", ] [[package]] name = "parking_lot" version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99" dependencies = [ "instant", "lock_api", "parking_lot_core", ] [[package]] name = "parking_lot_core" version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d76e8e1493bcac0d2766c42737f34458f1c8c50c0d23bcb24ea953affb273216" dependencies = [ "cfg-if", "instant", "libc", "redox_syscall", "smallvec", "winapi", ] [[package]] name = "peeking_take_while" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099" [[package]] name = "pem" version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fd56cbd21fea48d0c440b41cd69c589faacade08c992d9a54e471b79d0fd13eb" dependencies = [ "base64", "once_cell", "regex", ] [[package]] name = "petgraph" version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "467d164a6de56270bd7c4d070df81d07beace25012d5103ced4e9ff08d6afdb7" dependencies = [ "fixedbitset", "indexmap", ] [[package]] name = "phf_shared" version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c00cf8b9eafe68dde5e9eaa2cef8ee84a9336a47d566ec55ca16589633b65af7" dependencies = [ "siphasher", ] [[package]] name = "pico-args" version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "db8bcd96cb740d03149cbad5518db9fd87126a10ab519c011893b1754134c468" [[package]] name = "pkcs8" version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c9c2f795bc591cb3384cb64082a578b89207ac92bb89c9d98c1ea2ace7cd8110" dependencies = [ "der", "spki", ] [[package]] name = "pkg-config" version = "0.3.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "58893f751c9b0412871a09abd62ecd2a00298c6c83befa223ef98c52aef40cbe" [[package]] name = "plotters" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a3fd9ec30b9749ce28cd91f255d569591cdf937fe280c312143e3c4bad6f2a" dependencies = [ "num-traits", "plotters-backend", "plotters-svg", "wasm-bindgen", "web-sys", ] [[package]] name = "plotters-backend" version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d88417318da0eaf0fdcdb51a0ee6c3bed624333bff8f946733049380be67ac1c" [[package]] name = "plotters-svg" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "521fa9638fa597e1dc53e9412a4f9cefb01187ee1f7413076f9e6749e2885ba9" dependencies = [ "plotters-backend", ] [[package]] name = "ppv-lite86" version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed0cfbc8191465bed66e1718596ee0b0b35d5ee1f41c5df2189d0fe8bde535ba" [[package]] name = "precomputed-hash" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" [[package]] name = "proc-macro2" version = "1.0.34" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2f84e92c0f7c9d58328b85a78557813e4bd845130db68d7184635344399423b1" dependencies = [ "unicode-xid", ] [[package]] name = "quickcheck" version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "588f6378e4dd99458b60ec275b4477add41ce4fa9f64dcba6f15adccb19b50d6" dependencies = [ "rand 0.8.4", ] [[package]] name = "quickcheck_macros" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b22a693222d716a9587786f37ac3f6b4faedb5b80c23914e7303ff5a1d8016e9" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "quote" version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "38bc8cc6a5f2e3655e0899c1b848643b2562f853f114bfec7be120678e3ace05" dependencies = [ "proc-macro2", ] [[package]] name = "radium" version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "643f8f41a8ebc4c5dc4515c82bb8abd397b527fc20fd681b7c011c2aee5d44fb" [[package]] name = "rand" version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" dependencies = [ "getrandom 0.1.16", "libc", "rand_chacha 0.2.2", "rand_core 0.5.1", "rand_hc 0.2.0", ] [[package]] name = "rand" version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2e7573632e6454cf6b99d7aac4ccca54be06da05aca2ef7423d22d27d4d4bcd8" dependencies = [ "libc", "rand_chacha 0.3.1", "rand_core 0.6.3", "rand_hc 0.3.1", ] [[package]] name = "rand_chacha" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" dependencies = [ "ppv-lite86", "rand_core 0.5.1", ] [[package]] name = "rand_chacha" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" dependencies = [ "ppv-lite86", "rand_core 0.6.3", ] [[package]] name = "rand_core" version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" dependencies = [ "getrandom 0.1.16", ] [[package]] name = "rand_core" version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7" dependencies = [ "getrandom 0.2.3", ] [[package]] name = "rand_hc" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" dependencies = [ "rand_core 0.5.1", ] [[package]] name = "rand_hc" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d51e9f596de227fda2ea6c84607f5558e196eeaf43c986b724ba4fb8fdf497e7" dependencies = [ "rand_core 0.6.3", ] [[package]] name = "rayon" version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c06aca804d41dbc8ba42dfd964f0d01334eceb64314b9ecf7c5fad5188a06d90" dependencies = [ "autocfg 1.0.1", "crossbeam-deque", "either", "rayon-core", ] [[package]] name = "rayon-core" version = "1.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d78120e2c850279833f1dd3582f730c4ab53ed95aeaaaa862a2a5c71b1656d8e" dependencies = [ "crossbeam-channel", "crossbeam-deque", "crossbeam-utils", "lazy_static", "num_cpus", ] [[package]] name = "redox_syscall" version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8383f39639269cde97d255a32bdb68c047337295414940c68bdd30c2e13203ff" dependencies = [ "bitflags", ] [[package]] name = "redox_users" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "528532f3d801c87aec9def2add9ca802fe569e44a544afe633765267840abe64" dependencies = [ "getrandom 0.2.3", "redox_syscall", ] [[package]] name = "regex" version = "1.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461" dependencies = [ "aho-corasick", "memchr", "regex-syntax", ] [[package]] name = "regex-automata" version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" [[package]] name = "regex-syntax" version = "0.6.25" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b" [[package]] name = "ripemd160" version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2eca4ecc81b7f313189bf73ce724400a07da2a6dac19588b03c8bd76a2dcc251" dependencies = [ "block-buffer", "digest", "opaque-debug", ] [[package]] name = "rpassword" version = "5.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ffc936cf8a7ea60c58f030fd36a612a48f440610214dc54bc36431f9ea0c3efb" dependencies = [ "libc", "winapi", ] [[package]] name = "rsa" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3648b669b10afeab18972c105e284a7b953a669b0be3514c27f9b17acab2f9cd" dependencies = [ "byteorder", "digest", "lazy_static", "num-bigint-dig", "num-integer", "num-iter", "num-traits", "pem", "rand 0.7.3", "sha2", "simple_asn1", "subtle", "thiserror", "zeroize", ] [[package]] name = "rustc-demangle" version = "0.1.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ef03e0a2b150c7a90d01faf6254c9c48a41e95fb2a8c2ac1c6f0d2b9aefc342" [[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.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" dependencies = [ "semver", ] [[package]] name = "rustversion" version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2cc38e8fa666e2de3c4aba7edeb5ffc5246c1c2ed0e3d17e560aeeba736b23f" [[package]] name = "ryu" version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73b4b750c782965c211b42f022f59af1fbceabdd026623714f104152f1ec149f" [[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.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" [[package]] name = "semver" version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "568a8e6258aa33c13358f81fd834adb854c6f7c9468520910a9b1e8fac068012" [[package]] name = "sequoia-openpgp" version = "1.7.0" dependencies = [ "aes", "anyhow", "backtrace", "base64", "block-modes", "block-padding", "blowfish", "buffered-reader", "bzip2", "cast5", "chrono", "cipher", "criterion", "des", "digest", "dyn-clone", "eax", "ecdsa", "ed25519-dalek", "flate2", "generic-array", "getrandom 0.2.3", "idea", "idna", "lalrpop", "lalrpop-util", "lazy_static", "libc", "md-5", "memsec", "nettle", "num-bigint-dig", "p256", "quickcheck", "quickcheck_macros", "rand 0.7.3", "rand 0.8.4", "rand_core 0.6.3", "regex", "regex-syntax", "ripemd160", "rpassword", "rsa", "sha-1", "sha1collisiondetection", "sha2", "thiserror", "twofish", "typenum", "win-crypto-ng", "winapi", "x25519-dalek", "xxhash-rust", ] [[package]] name = "serde" version = "1.0.132" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b9875c23cf305cd1fd7eb77234cbb705f21ea6a72c637a5c6db5fe4b8e7f008" [[package]] name = "serde_cbor" version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2bef2ebfde456fb76bbcf9f59315333decc4fda0b2b44b420243c11e0f5ec1f5" dependencies = [ "half", "serde", ] [[package]] name = "serde_derive" version = "1.0.132" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ecc0db5cb2556c0e558887d9bbdcf6ac4471e83ff66cf696e5419024d1606276" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "serde_json" version = "1.0.73" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bcbd0344bc6533bc7ec56df11d42fb70f1b912351c0825ccb7211b59d8af7cf5" dependencies = [ "itoa 1.0.1", "ryu", "serde", ] [[package]] name = "sha-1" version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "99cd6713db3cf16b6c84e06321e049a9b9f699826e16096d23bbcc44d15d51a6" dependencies = [ "block-buffer", "cfg-if", "cpufeatures", "digest", "opaque-debug", ] [[package]] name = "sha1collisiondetection" version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f31bf4e9fe5cd8cea8e0887e2e4eb1b4d736ff11b776c8537bf0912a4b381285" dependencies = [ "digest", "generic-array", ] [[package]] name = "sha2" version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b69f9a4c9740d74c5baa3fd2e547f9525fa8088a8a958e0ca2409a514e33f5fa" dependencies = [ "block-buffer", "cfg-if", "cpufeatures", "digest", "opaque-debug", ] [[package]] name = "shlex" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7fdf1b9db47230893d76faad238fd6097fd6d6a9245cd7a4d90dbd639536bbd2" [[package]] name = "signature" version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2807892cfa58e081aa1f1111391c7a0649d4fa127a4ffbe34bcbfb35a1171a4" dependencies = [ "digest", "rand_core 0.6.3", ] [[package]] name = "simple_asn1" version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "692ca13de57ce0613a363c8c2f1de925adebc81b04c923ac60c5488bb44abe4b" dependencies = [ "chrono", "num-bigint", "num-traits", ] [[package]] name = "siphasher" version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "533494a8f9b724d33625ab53c6c4800f7cc445895924a8ef649222dcb76e938b" [[package]] name = "smallvec" version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1ecab6c735a6bb4139c0caafd0cc3635748bbb3acf4550e8138122099251f309" [[package]] name = "spin" version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" [[package]] name = "spki" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9dae7e047abc519c96350e9484a96c6bf1492348af912fd3446dd2dc323f6268" dependencies = [ "der", ] [[package]] name = "string_cache" version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "923f0f39b6267d37d23ce71ae7235602134b250ace715dd2c90421998ddac0c6" dependencies = [ "lazy_static", "new_debug_unreachable", "parking_lot", "phf_shared", "precomputed-hash", ] [[package]] name = "subtle" version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" [[package]] name = "syn" version = "1.0.83" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "23a1dfb999630e338648c83e91c59a4e9fb7620f520c3194b6b89e276f2f1959" dependencies = [ "proc-macro2", "quote", "unicode-xid", ] [[package]] name = "synstructure" version = "0.12.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f36bdaa60a83aca3921b5259d5400cbf5e90fc51931376a9bd4a0eb79aa7210f" dependencies = [ "proc-macro2", "quote", "syn", "unicode-xid", ] [[package]] name = "tap" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" [[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 = "textwrap" version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" dependencies = [ "unicode-width", ] [[package]] name = "thiserror" version = "1.0.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "854babe52e4df1653706b98fcfc05843010039b406875930a70e4d9644e5c417" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" version = "1.0.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aa32fd3f627f367fe16f893e2597ae3c05020f8bba2666a4e6ea73d377e5714b" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "time" version = "0.1.43" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ca8a50ef2360fbd1eeb0ecd46795a87a19024eb4b53c5dc916ca1fd95fe62438" dependencies = [ "libc", "winapi", ] [[package]] name = "tiny-keccak" version = "2.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" dependencies = [ "crunchy", ] [[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 = "tinyvec" version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2c1c1d5a42b6245520c249549ec267180beaffcc0615401ac8e31853d4b6d8d2" dependencies = [ "tinyvec_macros", ] [[package]] name = "tinyvec_macros" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" [[package]] name = "twofish" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0028f5982f23ecc9a1bc3008ead4c664f843ed5d78acd3d213b99ff50c441bc2" dependencies = [ "byteorder", "cipher", "opaque-debug", ] [[package]] name = "typenum" version = "1.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b63708a265f51345575b27fe43f9500ad611579e764c79edbc2037b1121959ec" [[package]] name = "unicode-bidi" version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1a01404663e3db436ed2746d9fefef640d868edae3cceb81c3b8d5732fda678f" [[package]] name = "unicode-normalization" version = "0.1.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d54590932941a9e9266f0832deed84ebe1bf2e4c9e4a3554d393d18f5e854bf9" dependencies = [ "tinyvec", ] [[package]] name = "unicode-width" version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973" [[package]] name = "unicode-xid" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" [[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.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe" [[package]] name = "walkdir" version = "2.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "808cf2735cd4b6866113f648b791c6adc5714537bc222d9347bb203386ffda56" dependencies = [ "same-file", "winapi", "winapi-util", ] [[package]] name = "wasi" version = "0.9.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" [[package]] name = "wasi" version = "0.10.2+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6" [[package]] name = "wasm-bindgen" version = "0.2.78" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "632f73e236b219150ea279196e54e610f5dbafa5d61786303d4da54f84e47fce" dependencies = [ "cfg-if", "wasm-bindgen-macro", ] [[package]] name = "wasm-bindgen-backend" version = "0.2.78" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a317bf8f9fba2476b4b2c85ef4c4af8ff39c3c7f0cdfeed4f82c34a880aa837b" dependencies = [ "bumpalo", "lazy_static", "log", "proc-macro2", "quote", "syn", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-macro" version = "0.2.78" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d56146e7c495528bf6587663bea13a8eb588d39b36b679d83972e1a2dbbdacf9" dependencies = [ "quote", "wasm-bindgen-macro-support", ] [[package]] name = "wasm-bindgen-macro-support" version = "0.2.78" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7803e0eea25835f8abdc585cd3021b3deb11543c6fe226dcd30b228857c5c5ab" dependencies = [ "proc-macro2", "quote", "syn", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" version = "0.2.78" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0237232789cf037d5480773fe568aac745bfe2afbc11a863e97901780a6b47cc" [[package]] name = "web-sys" version = "0.3.55" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "38eb105f1c59d9eaa6b5cdc92b859d85b926e82cb2e0945cd0c9259faa6fe9fb" dependencies = [ "js-sys", "wasm-bindgen", ] [[package]] name = "win-crypto-ng" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24cf92e98e8f4ade45b5140795415a0f256fd9b69a1919248dcda11ba5d6466c" dependencies = [ "cipher", "doc-comment", "rand_core 0.5.1", "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.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" dependencies = [ "winapi", ] [[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 = "wyz" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "85e60b0d1b5f99db2556934e21937020776a5d31520bf169e851ac44e6420214" [[package]] name = "x25519-dalek" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2392b6b94a576b4e2bf3c5b2757d63f10ada8020a2e4d08ac849ebcf6ea8e077" dependencies = [ "curve25519-dalek", "rand_core 0.5.1", "zeroize", ] [[package]] name = "xxhash-rust" version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e575e15bedf6e57b5c2d763ffc6c3c760143466cbd09d762d539680ab5992ded" [[package]] name = "zeroize" version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4756f7db3f7b5574938c3eb1c117038b8e07f95ee6718c0efad4ac21508f1efd" dependencies = [ "zeroize_derive", ] [[package]] name = "zeroize_derive" version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "65f1a51723ec88c66d5d1fe80c841f17f63587d6691901d66be9bec6c3b51f73" dependencies = [ "proc-macro2", "quote", "syn", "synstructure", ] sequoia-openpgp-1.7.0/Cargo.toml0000644000000133270000000000100121610ustar # 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 = "2018" rust-version = "1.56" name = "sequoia-openpgp" version = "1.7.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"] [[bench]] name = "run_benchmarks" harness = false [dependencies.aes] version = "0.6.0" optional = true [dependencies.anyhow] version = "1.0.18" [dependencies.backtrace] version = "0.3.3" [dependencies.base64] version = ">=0.12" [dependencies.block-modes] version = "0.7.0" optional = true [dependencies.block-padding] version = "0.2.1" optional = true [dependencies.blowfish] version = "0.7.0" optional = true [dependencies.buffered-reader] version = "1.0.0" default-features = false [dependencies.bzip2] version = "0.4" optional = true [dependencies.cast5] version = "0.9.0" optional = true [dependencies.cipher] version = "0.2.5" features = ["std"] optional = true [dependencies.des] version = "0.6.0" optional = true [dependencies.digest] version = "0.9.0" optional = true [dependencies.dyn-clone] version = "1" [dependencies.eax] version = "0.3.0" optional = true [dependencies.ecdsa] version = "0.11" features = ["hazmat", "arithmetic"] optional = true [dependencies.ed25519-dalek] version = "1" features = ["rand", "u64_backend"] optional = true default-features = false [dependencies.flate2] version = "1.0.1" optional = true [dependencies.generic-array] version = "0.14.4" optional = true [dependencies.idea] version = "0.3.0" optional = true [dependencies.idna] version = "0.2" [dependencies.lalrpop-util] version = ">=0.17" [dependencies.lazy_static] version = "1.4.0" [dependencies.libc] version = "0.2.66" [dependencies.md-5] version = "0.9.1" optional = true [dependencies.memsec] version = ">=0.5" default-features = false [dependencies.nettle] version = "7.0.2" optional = true [dependencies.num-bigint-dig] version = "0.6" optional = true default-features = false [dependencies.p256] version = "0.8" features = ["ecdh", "ecdsa"] optional = true [dependencies.rand07] version = "0.7.3" optional = true package = "rand" [dependencies.rand_core] version = "0.6" optional = true [dependencies.regex] version = "1" [dependencies.regex-syntax] version = "0.6" [dependencies.ripemd160] version = "0.9.1" optional = true [dependencies.rsa] version = "0.3.0" optional = true [dependencies.sha-1] version = "0.9.2" optional = true [dependencies.sha1collisiondetection] version = "0.2.3" features = ["std"] default-features = false [dependencies.sha2] version = "0.9.2" optional = true [dependencies.thiserror] version = "1.0.2" [dependencies.twofish] version = "0.5.0" optional = true [dependencies.typenum] version = "1.12.0" optional = true [dependencies.x25519-dalek] version = "1.1.0" optional = true [dependencies.xxhash-rust] version = "0.8" features = ["xxh3"] [dev-dependencies.criterion] version = "0.3.4" features = ["html_reports"] [dev-dependencies.quickcheck] version = "1" default-features = false [dev-dependencies.quickcheck_macros] version = "1" default-features = false [dev-dependencies.rand] version = "0.8" [dev-dependencies.rpassword] version = "5.0" [build-dependencies.lalrpop] version = ">=0.17" [features] allow-experimental-crypto = [] allow-variable-time-crypto = [] compression = ["compression-deflate", "compression-bzip2"] compression-bzip2 = ["bzip2", "buffered-reader/compression-bzip2"] compression-deflate = ["flate2", "buffered-reader/compression-deflate"] crypto-cng = ["eax", "winapi", "win-crypto-ng", "ed25519-dalek", "num-bigint-dig"] crypto-nettle = ["nettle"] crypto-rust = ["aes", "block-modes", "block-padding", "blowfish", "cast5", "cipher", "des", "digest", "eax", "ed25519-dalek", "generic-array", "idea", "md-5", "num-bigint-dig", "rand07", "ripemd160", "rsa", "sha-1", "sha2", "twofish", "typenum", "x25519-dalek", "p256", "rand_core", "rand_core/getrandom", "ecdsa"] default = ["compression", "crypto-nettle"] [target."cfg(all(target_arch = \"wasm32\", target_os = \"unknown\"))".dependencies.chrono] version = "0.4.10" features = ["std", "wasmbind"] default-features = false [target."cfg(all(target_arch = \"wasm32\", target_os = \"unknown\"))".dependencies.getrandom] version = "0.2" features = ["js"] [target."cfg(all(target_arch = \"wasm32\", target_os = \"unknown\"))".dependencies.rand07] version = "0.7" features = ["wasm-bindgen"] package = "rand" [target."cfg(windows)".dependencies.win-crypto-ng] version = "0.4" 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" sequoia-openpgp-1.7.0/Cargo.toml.orig000064400000000000000000000114010072674642500156610ustar 00000000000000[package] name = "sequoia-openpgp" description = "OpenPGP data types and associated machinery" version = "1.7.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 = "2018" rust-version = "1.56" [badges] gitlab = { repository = "sequoia-pgp/sequoia" } maintenance = { status = "actively-developed" } [dependencies] anyhow = "1.0.18" buffered-reader = { path = "../buffered-reader", version = "1.0.0", default-features = false } base64 = ">=0.12" bzip2 = { version = "0.4", optional = true } dyn-clone = "1" flate2 = { version = "1.0.1", optional = true } idna = "0.2" lalrpop-util = ">=0.17" lazy_static = "1.4.0" libc = "0.2.66" memsec = { version = ">=0.5", default-features = false } nettle = { version = "7.0.2", optional = true } regex = "1" regex-syntax = "0.6" sha1collisiondetection = { version = "0.2.3", default-features = false, features = ["std"] } thiserror = "1.0.2" xxhash-rust = { version = "0.8", features = ["xxh3"] } backtrace = "0.3.3" # RustCrypto crates. aes = { version = "0.6.0", optional = true } block-modes = { version = "0.7.0", optional = true } block-padding = { version = "0.2.1", optional = true } blowfish = { version = "0.7.0", optional = true } cast5 = { version = "0.9.0", optional = true } cipher = { version = "0.2.5", optional = true, features = ["std"] } des = { version = "0.6.0", optional = true } digest = { version = "0.9.0", optional = true } eax = { version = "0.3.0", optional = true } ecdsa = { version = "0.11", optional = true, features = ["hazmat", "arithmetic"] } # XXX ed25519-dalek = { version = "1", default-features = false, features = ["rand", "u64_backend"], optional = true } generic-array = { version = "0.14.4", optional = true } idea = { version = "0.3.0", optional = true } md-5 = { version = "0.9.1", optional = true } num-bigint-dig = { version = "0.6", default-features = false, optional = true } p256 = { version = "0.8", optional = true, features = ["ecdh", "ecdsa"] } rand07 = { package = "rand", version = "0.7.3", optional = true } rand_core = { version = "0.6", optional = true } ripemd160 = { version = "0.9.1", optional = true } rsa = { version = "0.3.0", optional = true } sha-1 = { version = "0.9.2", optional = true } sha2 = { version = "0.9.2", optional = true } twofish = { version = "0.5.0", optional = true } typenum = { version = "1.12.0", optional = true } x25519-dalek = { version = "1.1.0", optional = true } [target.'cfg(windows)'.dependencies] win-crypto-ng = { version = "0.4", 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"] } getrandom = { version = "0.2", features = ["js"] } rand07 = { package = "rand", version = "0.7", features = ["wasm-bindgen"] } [build-dependencies] lalrpop = ">=0.17" [dev-dependencies] quickcheck = { version = "1", default-features = false } quickcheck_macros = { version = "1", default-features = false } rand = { version = "0.8" } rpassword = "5.0" criterion = { version = "0.3.4", features = ["html_reports"] } [features] default = ["compression", "crypto-nettle"] # TODO(#333): Allow for/implement more backends crypto-nettle = ["nettle"] crypto-rust = [ "aes", "block-modes", "block-padding", "blowfish", "cast5", "cipher", "des", "digest", "eax", "ed25519-dalek", "generic-array", "idea", "md-5", "num-bigint-dig", "rand07", "ripemd160", "rsa", "sha-1", "sha2", "twofish", "typenum", "x25519-dalek", "p256", "rand_core", "rand_core/getrandom", "ecdsa" ] crypto-cng = ["eax", "winapi", "win-crypto-ng", "ed25519-dalek", "num-bigint-dig"] # 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 = ["flate2", "buffered-reader/compression-deflate"] compression-bzip2 = ["bzip2", "buffered-reader/compression-bzip2"] [lib] bench = false [[example]] name = "pad" required-features = ["compression-deflate"] [[bench]] name = "run_benchmarks" harness = false sequoia-openpgp-1.7.0/LICENSE.txt000064400000000000000000000627340072674642500146340ustar 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-1.7.0/NEWS000064400000000000000000000070430072674642500135000ustar 00000000000000 -*- org -*- #+TITLE: sequoia-openpgp NEWS – history of user-visible changes #+STARTUP: content hidestars * 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-1.7.0/README.md000064400000000000000000000124140072674642500142560ustar 00000000000000This crate aims to provide a complete implementation of OpenPGP as defined by [RFC 4880] as well as some extensions (e.g., [RFC 6637], which describes ECC cryptography for OpenPGP. This 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 4880] does not define trust models, such as the web of trust, direct trust, or TOFU. Neither does this crate. [RFC 4880] 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 4880]: https://tools.ietf.org/html/rfc4880 [RFC 6637]: https://tools.ietf.org/html/rfc6637 # Experimental Features This crate implements functionality from [RFC 4880bis], notably AEAD encryption containers. As of this writing, this RFC is still a draft and the syntax or semantic defined in it may change or go away. Therefore, all related functionality may change and artifacts created using this functionality may not be usable in the future. Do not use it for things other than experiments. [RFC 4880bis]: https://tools.ietf.org/html/draft-ietf-openpgp-rfc4880bis-08 This crate aims to provide a complete implementation of OpenPGP as defined by RFC 4880 as well as several extensions (e.g., RFC 6637, which describes ECC cryptography for OpenPGP, and RFC 4880bis, the draft of the next OpenPGP standard). This includes support for unbuffered message processing. # 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 = ["crypto-nettle", ...] } ``` By default, Sequoia is built using Nettle as cryptographic backend with all compression algorithms enabled. Note that if you use `default-features = false`, you need to explicitly enable a crypto backend. ## 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 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`. ## 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.56. sequoia-openpgp-1.7.0/benches/README.md000064400000000000000000000021760072674642500156710ustar 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-1.7.0/benches/common/decrypt.rs000064400000000000000000000166150072674642500177250ustar 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::{Fingerprint, 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, mut decrypt: D, ) -> Result> where D: FnMut(SymmetricAlgorithm, &SessionKey) -> bool, { // 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, mut decrypt: D, ) -> Result> where D: FnMut(SymmetricAlgorithm, &SessionKey) -> bool, { 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() == &key.keyid()) .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(key) => Ok(Some(key)), 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-1.7.0/benches/common/encrypt.rs000064400000000000000000000057100072674642500177310ustar 00000000000000use sequoia_openpgp as openpgp; use openpgp::cert::Cert; use openpgp::policy::StandardPolicy; use openpgp::serialize::stream::{ padding::Padder, 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 = Padder::new(message).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) } sequoia-openpgp-1.7.0/benches/common/mod.rs000064400000000000000000000000600072674642500170150ustar 00000000000000pub(super) mod decrypt; pub(super) mod encrypt; sequoia-openpgp-1.7.0/benches/decrypt_message.rs000064400000000000000000000037000072674642500201300ustar 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::{decrypt, encrypt}; static PASSWORD: &str = "password"; lazy_static::lazy_static! { static ref TESTY: Cert = Cert::from_bytes(&include_bytes!("../tests/data/keys/testy-private.pgp")[..]) .unwrap(); static ref ZEROS_1_MB: Vec = vec![0; 1024 * 1024]; static ref ZEROS_10_MB: Vec = vec![0; 10 * 1024 * 1024]; } 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 a very short, medium and very long message, // and then benchmark decryption. let messages = &[b"Hello world.", &ZEROS_1_MB[..], &ZEROS_10_MB[..]]; // Encrypt and decrypt with password messages.iter().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 messages.iter().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-1.7.0/benches/decrypt_verify_message.rs000064400000000000000000000031300072674642500215110ustar 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::{decrypt, encrypt}; lazy_static::lazy_static! { static ref SENDER: Cert = Cert::from_bytes(&include_bytes!("../tests/data/keys/sender.pgp")[..]) .unwrap(); static ref RECIPIENT: Cert = Cert::from_bytes(&include_bytes!("../tests/data/keys/recipient.pgp")[..]) .unwrap(); static ref ZEROS_1_MB: Vec = vec![0; 1024 * 1024]; static ref ZEROS_10_MB: Vec = vec![0; 10 * 1024 * 1024]; } 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 a very short, medium and very long message, // and then benchmark decryption. let messages = &[b"Hello world.", &ZEROS_1_MB[..], &ZEROS_10_MB[..]]; messages.iter().for_each(|m| { let encrypted = encrypt::encrypt_to_cert_and_sign(m, &SENDER, &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, &SENDER, &RECIPIENT)), ); }); group.finish(); } criterion_group!(benches, bench_decrypt_verify); criterion_main!(benches); sequoia-openpgp-1.7.0/benches/encrypt_message.rs000064400000000000000000000026470072674642500201530ustar 00000000000000use criterion::{criterion_group, BenchmarkId, Criterion, Throughput}; use sequoia_openpgp as openpgp; use openpgp::cert::Cert; use openpgp::parse::Parse; use crate::common::encrypt; lazy_static::lazy_static! { static ref ZEROS_1_MB: Vec = vec![0; 1024 * 1024]; static ref ZEROS_10_MB: Vec = vec![0; 10 * 1024 * 1024]; } 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"); // Encrypt a very short, medium and very long message. let messages = &[b"Hello world.", &ZEROS_1_MB[..], &ZEROS_10_MB[..]]; for message in 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-1.7.0/benches/encrypt_sign_message.rs000064400000000000000000000025050072674642500211640ustar 00000000000000use criterion::{criterion_group, BenchmarkId, Criterion, Throughput}; use sequoia_openpgp as openpgp; use openpgp::cert::Cert; use openpgp::parse::Parse; use crate::common::encrypt; lazy_static::lazy_static! { static ref ZEROS_1_MB: Vec = vec![0; 1024 * 1024]; static ref ZEROS_10_MB: Vec = vec![0; 10 * 1024 * 1024]; } 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"); // Encrypt a very short, medium and very long message. let messages = &[b"Hello world.", &ZEROS_1_MB[..], &ZEROS_10_MB[..]]; for message in 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-1.7.0/benches/generate_cert.rs000064400000000000000000000021110072674642500175540ustar 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( cipher, Some("Alice Lovelace "), ) .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-1.7.0/benches/merge_cert.rs000064400000000000000000000011060072674642500170640ustar 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-1.7.0/benches/mod.rs000064400000000000000000000000140072674642500155240ustar 00000000000000mod common; sequoia-openpgp-1.7.0/benches/parse_cert.rs000064400000000000000000000062150072674642500171050ustar 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, &floodme_cloned, sigs_per_key) .unwrap() }); floodme = floodme.insert_packets(certifications)?; 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-1.7.0/benches/run_benchmarks.rs000064400000000000000000000014510072674642500177540ustar 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-1.7.0/benches/sign_message.rs000064400000000000000000000022040072674642500174140ustar 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; lazy_static::lazy_static! { static ref ZEROS_1_MB: Vec = vec![0; 1024 * 1024]; static ref ZEROS_10_MB: Vec = vec![0; 10 * 1024 * 1024]; } 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"); // Encrypt a very short, medium and very long message. let messages = &[b"Hello world.", &ZEROS_1_MB[..], &ZEROS_10_MB[..]]; for message in 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-1.7.0/benches/verify_message.rs000064400000000000000000000024260072674642500177660ustar 00000000000000use criterion::{criterion_group, BenchmarkId, Criterion, Throughput}; use sequoia_openpgp as openpgp; use openpgp::cert::Cert; use openpgp::parse::Parse; use crate::common::{decrypt, encrypt}; lazy_static::lazy_static! { static ref SENDER: Cert = Cert::from_bytes(&include_bytes!("../tests/data/keys/sender.pgp")[..]) .unwrap(); static ref ZEROS_1_MB: Vec = vec![0; 1024 * 1024]; static ref ZEROS_10_MB: Vec = vec![0; 10 * 1024 * 1024]; } 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"); // Sign a very short, medium and very long message, // and then benchmark verification. let messages = &[b"Hello world.", &ZEROS_1_MB[..]]; messages .iter() .for_each(|m| { let signed = encrypt::sign(m, &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, &SENDER)), ); }); group.finish(); } criterion_group!(benches, bench_verify); sequoia-openpgp-1.7.0/build.rs000064400000000000000000000103430072674642500144430ustar 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(); lalrpop::process_root().unwrap(); include_test_data().unwrap(); } /// 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!(feature = "crypto-nettle"), Backend { name: "Nettle", production_ready: true, constant_time: true, }), (cfg!(feature = "crypto-cng"), Backend { name: "Windows CNG", production_ready: true, constant_time: true, }), (cfg!(feature = "crypto-rust"), Backend { name: "RustCrypto", 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-1.7.0/examples/decrypt-with.rs000064400000000000000000000113110072674642500176010ustar 00000000000000/// Decrypts asymmetrically-encrypted OpenPGP messages using the /// openpgp crate, Sequoia's low-level API. use std::collections::HashMap; use std::env; use std::io; use anyhow::Context; use sequoia_openpgp as openpgp; use openpgp::*; use openpgp::cert::prelude::*; 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, } impl Helper { /// Creates a Helper for the given Certs with appropriate secrets. fn new(p: &dyn Policy, certs: Vec) -> Self { // Map (sub)KeyIDs to primary fingerprints and secrets. let mut keys = HashMap::new(); for cert in certs { for ka in cert.keys().unencrypted_secret().with_policy(p, None) .supported() .for_storage_encryption().for_transport_encryption() { keys.insert(ka.key().keyid(), (cert.fingerprint(), 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, mut decrypt: D) -> openpgp::Result> where D: FnMut(SymmetricAlgorithm, &SessionKey) -> bool { // Try each PKESK until we succeed. let mut recipient = None; for pkesk in pkesks { if let Some((fp, pair)) = self.keys.get_mut(pkesk.recipient()) { if pkesk.decrypt(pair, sym_algo) .map(|(algo, session_key)| decrypt(algo, &session_key)) .unwrap_or(false) { recipient = Some(fp.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-1.7.0/examples/encrypt-for.rs000064400000000000000000000053070072674642500174360ustar 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-1.7.0/examples/generate-encrypt-decrypt.rs000064400000000000000000000077050072674642500221160ustar 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, mut decrypt: D) -> openpgp::Result> where D: FnMut(SymmetricAlgorithm, &SessionKey) -> bool { 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-1.7.0/examples/generate-group-key.rs000064400000000000000000000072740072674642500207050ustar 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-1.7.0/examples/generate-sign-verify.rs000064400000000000000000000102260072674642500212140ustar 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-1.7.0/examples/notarize.rs000064400000000000000000000103510072674642500170140ustar 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 key in tsk.keys() .with_policy(p, None).alive().revoked(false).for_signing().secret() .map(|ka| ka.key()) { keys.push({ let mut key = key.clone(); if key.secret().is_encrypted() { let password = rpassword::read_password_from_tty( Some(&format!("Please enter password to decrypt \ {}/{}: ",tsk, key)))?; let algo = key.pk_algo(); key.secret_mut() .decrypt_in_place(algo, &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-1.7.0/examples/pad.rs000064400000000000000000000054150072674642500157320ustar 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-1.7.0/examples/reply-encrypted.rs000064400000000000000000000205560072674642500203170ustar 00000000000000//! Demonstates 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::collections::HashMap; use std::env; use std::io; use anyhow::Context; use sequoia_openpgp as openpgp; use openpgp::{KeyID, Fingerprint}; 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; 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, 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, (Fingerprint, KeyPair)>, recycling_bin: 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 primary fingerprints and secrets. let mut keys = HashMap::new(); for cert in certs { for ka in cert.keys().unencrypted_secret().with_policy(p, None) .supported() .for_storage_encryption().for_transport_encryption() { keys.insert(ka.key().keyid(), (cert.fingerprint(), ka.key().clone().into_keypair().unwrap())); } } Helper { keys, recycling_bin: None, } } } impl DecryptionHelper for Helper { fn decrypt<D>(&mut self, pkesks: &[openpgp::packet::PKESK], _skesks: &[openpgp::packet::SKESK], sym_algo: Option<SymmetricAlgorithm>, mut decrypt: D) -> openpgp::Result<Option<openpgp::Fingerprint>> where D: FnMut(SymmetricAlgorithm, &SessionKey) -> bool { // Try each PKESK until we succeed. let mut recipient = None; let mut encryption_context = None; for pkesk in pkesks { if let Some((fp, pair)) = self.keys.get_mut(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(fp.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-1.7.0/examples/sign-detached.rs�����������������������������������������������������0000644�0000000�0000000�00000005215�00726746425�0017663�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 key in tsk .keys().with_policy(p, None).alive().revoked(false).for_signing().secret() .map(|ka| ka.key()) { keys.push({ let mut key = key.clone(); if key.secret().is_encrypted() { let password = rpassword::read_password_from_tty( Some(&format!("Please enter password to decrypt \ {}/{}: ",tsk, key)))?; let algo = key.pk_algo(); key.secret_mut() .decrypt_in_place(algo, &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-1.7.0/examples/sign.rs��������������������������������������������������������������0000644�0000000�0000000�00000005370�00726746425�0016126�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 key in tsk.keys() .with_policy(p, None).alive().revoked(false).for_signing().secret() .map(|ka| ka.key()) { keys.push({ let mut key = key.clone(); if key.secret().is_encrypted() { let password = rpassword::read_password_from_tty( Some(&format!("Please enter password to decrypt \ {}/{}: ",tsk, key)))?; let algo = key.pk_algo(); key.secret_mut() .decrypt_in_place(algo, &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-1.7.0/examples/statistics.rs��������������������������������������������������������0000644�0000000�0000000�00000066143�00726746425�0017365�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::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: HashMap<Vec<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_unknown_count = vec![0; 256]; let mut ua_invalid_count = 0; // Key statistics. let mut pk_algo_size: HashMap<PublicKeyAlgorithm, HashMap<usize, usize>> = Default::default(); // Current certificate. let mut current_fingerprint = KeyHandle::Fingerprint(Fingerprint::from_bytes(&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; } 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::PreferredAEADAlgorithms(a) => if let Some(count) = p_aead.get_mut(a) { *count += 1; } else { p_aead.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, }, Ok(Subpacket::Unknown(n, _)) => ua_unknown_count[n as usize] += 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); } }; 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.is_empty() { println!(); println!("# PreferredAEADAlgorithms statistics"); println!(); println!("{:>70} {:>9}", "", "count",); println!("----------------------------------------\ ----------------------------------------"); for (a, n) in p_aead.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!("{:>18} {:>9}", "", "count",); println!("----------------------------"); for t in 0..256 { let n = ua_image_count[t]; if n > 0 { println!("{:>18} {:>9}", match t { 1 => "Image::JPEG".into(), 100..=110 => format!("Image::Private({})", t), _ => format!("Image::Unknown({})", t), }, n); } } for t in 0..256 { let n = ua_unknown_count[t]; if n > 0 { println!("{:>18} {:>9}", format!("Unknown({})", t), n); } } if ua_invalid_count > 0 { println!("{:>18} {:>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); } } } 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-1.7.0/examples/supported-algorithms.rs����������������������������������������������0000644�0000000�0000000�00000005154�00726746425�0021362�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::Cv25519, CipherSuite::P256, CipherSuite::P384, CipherSuite::P521, CipherSuite::RSA2k, CipherSuite::RSA3k, CipherSuite::RSA4k, ] { println!(" - {:70} {:?}", format!("{:?}", a), a.is_supported().is_ok()); } println!(); println!("Public-Key algorithms:"); for a in &[ PublicKeyAlgorithm::RSAEncryptSign, PublicKeyAlgorithm::ElGamalEncrypt, PublicKeyAlgorithm::DSA, PublicKeyAlgorithm::ECDH, PublicKeyAlgorithm::ECDSA, PublicKeyAlgorithm::EdDSA, ] { println!(" - {:70} {:?}", a.to_string(), a.is_supported()); } println!(); println!("ECC algorithms:"); for a in &[ Curve::NistP256, Curve::NistP384, Curve::NistP521, Curve::BrainpoolP256, Curve::BrainpoolP512, Curve::Ed25519, Curve::Cv25519, ] { println!(" - {:70} {:?}", a.to_string(), a.is_supported()); } println!(); println!("Symmetric algorithms:"); for a in &[ SymmetricAlgorithm::IDEA, SymmetricAlgorithm::TripleDES, SymmetricAlgorithm::CAST5, SymmetricAlgorithm::Blowfish, SymmetricAlgorithm::AES128, SymmetricAlgorithm::AES192, SymmetricAlgorithm::AES256, SymmetricAlgorithm::Twofish, SymmetricAlgorithm::Camellia128, SymmetricAlgorithm::Camellia192, SymmetricAlgorithm::Camellia256, ] { println!(" - {:70} {:?}", a.to_string(), a.is_supported()); } println!(); println!("AEAD algorithms:"); for a in &[ AEADAlgorithm::EAX, AEADAlgorithm::OCB, ] { println!(" - {:70} {:?}", a.to_string(), a.is_supported()); } println!(); println!("Hash algorithms:"); for a in &[ HashAlgorithm::MD5, HashAlgorithm::SHA1, HashAlgorithm::RipeMD, HashAlgorithm::SHA256, HashAlgorithm::SHA384, HashAlgorithm::SHA512, HashAlgorithm::SHA224, ] { println!(" - {:70} {:?}", a.to_string(), a.is_supported()); } println!(); println!("Compression algorithms:"); for a in &[ CompressionAlgorithm::Zip, CompressionAlgorithm::Zlib, CompressionAlgorithm::BZip2, ] { println!(" - {:70} {:?}", a.to_string(), a.is_supported()); } println!(); } ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-1.7.0/examples/web-of-trust.rs������������������������������������������������������0000644�0000000�0000000�00000004311�00726746425�0017516�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-1.7.0/examples/wrap-literal.rs������������������������������������������������������0000644�0000000�0000000�00000002502�00726746425�0017563�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-1.7.0/src/armor/base64_utils.rs�����������������������������������������������������0000644�0000000�0000000�00000015224�00726746425�0017562�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������use std::{ borrow::Cow, }; use crate::{ packet::Header, }; /// Remove whitespace, etc. from the base64 data. /// /// This function returns the filtered base64 data (i.e., stripped of /// all skipable 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. #[allow(clippy::single_match)] 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 base64::decode_config(&bytes, base64::STANDARD) { 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-1.7.0/src/armor/crc.rs��������������������������������������������������������������0000644�0000000�0000000�00000005431�00726746425�0016024�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������//! Computes the CRC-24, (see [RFC 4880, section 6.1]). //! //! [RFC 4880, section 6.1]: https://tools.ietf.org/html/rfc4880#section-6.1 const CRC24_INIT: u32 = 0xB704CE; const CRC24_POLY: u32 = 0x864CFB; #[derive(Debug)] pub struct Crc { n: u32, } /// Computes the CRC-24, (see [RFC 4880, section 6.1]). /// /// [RFC 4880, section 6.1]: https://tools.ietf.org/html/rfc4880#section-6.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 { lazy_static::lazy_static! { static ref TABLE: Vec<u32> = { 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-1.7.0/src/armor.rs������������������������������������������������������������������0000644�0000000�0000000�00000243016�00726746425�0015260�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������//! ASCII Armor. //! //! This module deals with ASCII Armored data (see [Section 6 of RFC //! 4880]). //! //! [Section 6 of RFC 4880]: https://tools.ietf.org/html/rfc4880#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::new(&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 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; /// The encoded output stream must be represented in lines of no more /// than 76 characters each (see (see [RFC 4880, section /// 6.3](https://tools.ietf.org/html/rfc4880#section-6.3). GnuPG uses /// 64. pub(crate) const LINE_LENGTH: usize = 64; const LINE_ENDING: &str = "\n"; /// Specifies the type of data (see [RFC 4880, section 6.2]). /// /// [RFC 4880, section 6.2]: https://tools.ietf.org/html/rfc4880#section-6.2 #[derive(Copy, Clone, Debug, PartialEq)] 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 4880]. /// /// [Section 7 of RFC 4880]: https://tools.ietf.org/html/rfc4880#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> { 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, 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, 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) } /// 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(base64::encode_config( &self.stash, base64::STANDARD).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)?; } // 24-bit CRC let crc = self.crc.finalize(); let bytes = &crc.to_be_bytes()[1..4]; // CRC and footer. write!(self.sink, "={}{}{}{}", base64::encode_config(&bytes, base64::STANDARD_NO_PAD), LINE_ENDING, 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); // Update CRC on the unencoded data. self.crc.update(buf); 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(base64::encode_config( &self.stash, base64::STANDARD_NO_PAD).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; base64::encode_config_slice(&input[..input_bytes], base64::STANDARD_NO_PAD, &mut self.scratch[..encoded_bytes]); 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)] 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<Box<[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>, // The user settable cookie. cookie: Cookie, // End fields of the embedded generic reader. kind: Option<Kind>, mode: ReaderMode, decode_buffer: Vec<u8>, crc: Crc, expect_crc: Option<u32>, 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 = 8 * 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)] #[allow(clippy::upper_case_acronyms)] enum CSFTransformer { OPS, Literal, Signatures, } impl Default for CSFTransformer { fn default() -> Self { CSFTransformer::OPS } } impl<'a> Reader<'a> { /// Constructs a new filter for the given type of data. /// /// [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 robust mode. It will /// extract the first armored OpenPGP data block it can find, even /// if the armor frame is damaged, or missing. /// /// To select strict mode, specify a kind argument. In strict /// mode, the reader will match on the armor frame. The reader /// ignores any data in front of the Armor Header Line, as long as /// the line the header is only prefixed by whitespace. /// /// [ASCII Armor]: https://tools.ietf.org/html/rfc4880#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::new(&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::new(&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 new<R, M>(inner: R, mode: M) -> Self where R: 'a + Read + Send + Sync, M: Into<Option<ReaderMode>> { Self::from_buffered_reader( Box::new(buffered_reader::Generic::with_cookie(inner, None, Default::default())), mode, Default::default()) } /// Creates a `Reader` from an `io::Read`er. pub fn from_reader<R, M>(reader: R, mode: M) -> Self where R: 'a + Read + Send + Sync, M: Into<Option<ReaderMode>> { Self::from_buffered_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_buffered_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_buffered_reader( Box::new(buffered_reader::Memory::with_cookie(bytes, Default::default())), mode, Default::default()) } pub(crate) fn from_buffered_reader<M>( inner: Box<dyn BufferedReader<Cookie> + 'a>, mode: M, cookie: Cookie) -> Self where M: Into<Option<ReaderMode>> { Self::from_buffered_reader_csft(inner, mode.into(), cookie, false) } pub(crate) fn from_buffered_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, cursor: 0, preferred_chunk_size: DEFAULT_BUF_SIZE, source: inner, error: None, cookie, // End of the embedded generic reader's fields. kind: None, mode, decode_buffer: Vec::<u8>::with_capacity(1024), crc: Crc::new(), expect_crc: None, 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::new(&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. #[allow(clippy::nonminimal_bool)] fn initialize(&mut self) -> Result<()> { 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. lazy_static::lazy_static!{ static ref START_CHARS_VERY_TOLERANT: Vec<u8> = { 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(); base64::encode_config_slice(&ctb[..], base64::STANDARD, &mut o[..]); valid_start.push(o[0]); CTBOld::new(tag, BodyLength::Full(0)).unwrap() .serialize_into(&mut ctb[..]).unwrap(); base64::encode_config_slice(&ctb[..], base64::STANDARD, &mut o[..]); 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 }; static ref START_CHARS_TOLERANT: Vec<u8> = { 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) { 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); 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<()> { // 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); 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(); 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> { 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::new(...).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 = base64::decode_config( &base64data, base64::STANDARD) .map_err(|e| Error::new(ErrorKind::InvalidData, e))?; self.crc.update(&self.decode_buffer); 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. let decoded = base64::decode_config_slice( &base64data, base64::STANDARD, buf) .map_err(|e| Error::new(ErrorKind::InvalidData, e))?; self.crc.update(&buf[..decoded]); decoded }; 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. */ let crc = match base64::decode_config( &data[1..5], base64::STANDARD) { Ok(d) => d, Err(e) => return Err(Error::new(ErrorKind::InvalidInput, e)), }; assert_eq!(crc.len(), 3); let crc = (crc[0] as u32) << 16 | (crc[1] as u32) << 8 | crc[2] as u32; self.expect_crc = Some(crc); 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); if let Some(crc) = self.expect_crc { if self.crc.finalize() != crc { return Err(Error::new(ErrorKind::InvalidInput, "Bad CRC sum.")); } } } Ok(decoded) } fn read_clearsigned_message(&mut self, buf: &mut [u8]) -> Result<usize> { // XXX: We're not terribly concerned with performance at this // point, there is room for improvement. use std::collections::HashSet; use crate::{ types::{DataFormat, HashAlgorithm, SignatureType}, serialize::Serialize, }; assert!(self.csft.is_some()); if self.decode_buffer.is_empty() { match self.csft.as_ref().expect("CSFT has been initialized") { CSFTransformer::OPS => { // Determine the set of hash algorithms. let mut algos: HashSet<HashAlgorithm> = self.headers.iter() .filter(|(key, _value)| key == "Hash") .flat_map(|(_key, value)| { value.split(',') .filter_map(|hash| hash.parse().ok()) }).collect(); if algos.is_empty() { // The default is MD5. #[allow(deprecated)] algos.insert(HashAlgorithm::MD5); } // Now create an OPS packet for every algorithm. let count = algos.len(); for (i, &algo) in algos.iter().enumerate() { let mut ops = OnePassSig3::new(SignatureType::Text); ops.set_hash_algo(algo); ops.set_last(i + 1 == count); Packet::from(ops).serialize(&mut self.decode_buffer) .expect("writing to vec does not fail"); } // We will let the caller consume the buffer. // Once drained, we start decoding the message. self.csft = Some(CSFTransformer::Literal); }, CSFTransformer::Literal => { // XXX: We should create a partial-body encoded // literal packet, but for now we construct the // whole packet in core. 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")); } 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() - 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() - 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); } // Now, we have the whole text. let mut literal = Literal::new(DataFormat::Text); literal.set_body(text); Packet::from(literal).serialize(&mut self.decode_buffer) .expect("writing to vec does not fail"); // We will let the caller consume the buffer. // Once drained, we start streaming the // signatures. self.csft = Some(CSFTransformer::Signatures); }, CSFTransformer::Signatures => { // Drop transformer to revert to normal armor // reader. self.csft = None; // Consume any headers. self.read_headers()?; // Then start streaming the signatures. We call // this function explicitly once, but next time // the caller reads, it will shortcut to that // function. return self.read_armored_data(buf); }, } } 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) } 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) -> Result<&[u8]> { // println!("Generic.data_helper(\ // amount: {}, hard: {}, and_consume: {} (cursor: {}, buffer: {:?})", // amount, hard, and_consume, // self.cursor, // if let Some(ref buffer) = self.buffer { Some(buffer.len()) } // else { None }); // See if there is an error from the last invocation. if let Some(e) = self.error.take() { return Err(e); } 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 = cmp::max(cmp::max( DEFAULT_BUF_SIZE, 2 * self.preferred_chunk_size), amount); let mut buffer_new : Vec<u8> = vec![0u8; capacity]; let mut amount_read = 0; while amount_buffered + amount_read < amount { match self.do_read(&mut buffer_new [amount_buffered + amount_read..]) { Ok(read) => { if read == 0 { 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); buffer_new.shrink_to_fit(); self.buffer = Some(buffer_new.into_boxed_slice()); self.cursor = 0; } } let amount_buffered = self.buffer.as_ref().map(|b| b.len() - self.cursor).unwrap_or(0); if self.error.is_some() { // 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 { return Err(self.error.take().unwrap()); } if !hard && amount_buffered == 0 { return Err(self.error.take().unwrap()); } } if hard && amount_buffered < amount { Err(Error::new(ErrorKind::UnexpectedEof, "EOF")) } else if amount == 0 || amount_buffered == 0 { 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()); Ok(&buffer[self.cursor-amount_consumed..]) } else { 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::new(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::new( 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::new( 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::new( 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); assert!(e.is_err()); } #[test] fn dearmor_wrong_footer() { let mut r = Reader::new( 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::new( 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::new( 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::new( 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::new(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::new(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::new( 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::new( 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::new(Cursor::new(yuge_key), ReaderMode::VeryTolerant); let mut dearmored = Vec::<u8>::new(); r.read_to_end(&mut dearmored).unwrap(); let r = Reader::new(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::new( 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::new( 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::new( 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::new( 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::new(Cursor::new(&encoded), ReaderMode::Tolerant(Some(kind))) .read_to_end(&mut recovered) .unwrap(); let mut recovered_any = Vec::new(); Reader::new(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) -> crate::Result<()> where R: AsRef<[u8]> { let mut reader = Reader::from_buffered_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.children().count(), 3); // First, an one-pass-signature packet. if let Some(Packet::OnePassSig(ops)) = message.path_ref(&[0]) { assert_eq!(ops.hash_algo(), HashAlgorithm::SHA256); } 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.path_ref(&[2]) { assert_eq!(sig.hash_algo(), HashAlgorithm::SHA256); } 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_buffered_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(), HashAlgorithm::SHA256); } else { panic!("expected an signature packet"); } Ok(()) } f(crate::tests::message("a-problematic-poem.txt.cleartext.sig"), crate::tests::message("a-problematic-poem.txt"))?; f(crate::tests::message("a-cypherpunks-manifesto.txt.cleartext.sig"), { // 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(); let ws_at = manifesto.len() - 2; let ws = manifesto.remove(ws_at); assert_eq!(ws, b' '); manifesto })?; Ok(()) } } ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-1.7.0/src/cert/amalgamation/iter.rs�������������������������������������������������0000644�0000000�0000000�00000030452�00726746425�0020470�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(None, 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(None, 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(None, 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 or not 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(None, 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 { 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(None, 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-1.7.0/src/cert/amalgamation/key/iter.rs���������������������������������������������0000644�0000000�0000000�00000165462�00726746425�0021272�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, packet::key::SecretKeyMaterial, types::KeyFlags, cert::prelude::*, policy::Policy, }; /// 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, 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 a secret. 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: Option<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("secret", &self.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 let Some(key_handles) = self.key_handles.as_ref() { if !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(want_secret) = self.secret { if ka.key().has_secret() { // We have a secret. if ! want_secret { t!("Have a secret... skipping."); continue; } } else if want_secret { t!("No secret... skipping."); continue; } } if let Some(want_unencrypted_secret) = self.unencrypted_secret { if let Some(secret) = ka.key().optional_secret() { if let SecretKeyMaterial::Unencrypted { .. } = secret { if ! want_unencrypted_secret { t!("Unencrypted secret... skipping."); continue; } } else if want_unencrypted_secret { t!("Encrypted secret... skipping."); continue; } } else { // No secret. t!("No secret... skipping."); 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. secret: None, unencrypted_secret: None, key_handles: None, 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::general_purpose(None, Some("alice@example.org")) /// # .generate()?; /// for ka in cert.keys().secret() { /// // Use it. /// } /// # Ok(()) /// # } /// ``` pub fn secret(self) -> KeyAmalgamationIter<'a, key::SecretParts, R> { KeyAmalgamationIter { cert: self.cert, primary: self.primary, subkey_iter: self.subkey_iter, // The filters. 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::general_purpose(None, Some("alice@example.org")) /// # .generate()?; /// for ka in cert.keys().unencrypted_secret() { /// // Use it. /// } /// # 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. secret: self.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(None, Some("alice@example.org")) /// # .generate()?; /// # let key_handle = cert.primary_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> { if self.key_handles.is_none() { self.key_handles = Some(Vec::new()); } self.key_handles.as_mut().unwrap().push(h.into()); self } /// 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_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(None, Some("alice@example.org")) /// # .generate()?; /// # let key_handles = &[cert.primary_key().key_handle()][..]; /// # let mut i = 0; /// for ka in cert.keys().key_handles(key_handles.iter()) { /// // 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<'b>(mut self, h: impl Iterator<Item=&'b KeyHandle>) -> Self where 'a: 'b { if self.key_handles.is_none() { self.key_handles = Some(Vec::new()); } self.key_handles.as_mut().unwrap().extend(h.cloned()); 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(None, 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. secret: self.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(None, 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. secret: self.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 or not 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(None, 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 a secret. 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: Option<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("secret", &self.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 let Some(key_handles) = self.key_handles.as_ref() { if !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.", 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(want_secret) = self.secret { if key.has_secret() { // We have a secret. if ! want_secret { t!("Have a secret... skipping."); continue; } } else if want_secret { t!("No secret... skipping."); continue; } } if let Some(want_unencrypted_secret) = self.unencrypted_secret { if let Some(secret) = key.optional_secret() { if let SecretKeyMaterial::Unencrypted { .. } = secret { if ! want_unencrypted_secret { t!("Unencrypted secret... skipping."); continue; } } else if want_unencrypted_secret { t!("Encrypted secret... skipping."); continue; } } else { // No secret. t!("No secret... skipping."); 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 12.1 of RFC 4880] 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 12.1 of RFC 4880]: https://tools.ietf.org/html/rfc4880#section-5.2.3.21 /// [`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 12.1 of RFC 4880] 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 12.1 of RFC 4880]: https://tools.ietf.org/html/rfc4880#section-5.2.3.21 /// [`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(None, 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::general_purpose(None, Some("alice@example.org")) /// # .generate()?; /// for ka in cert.keys().with_policy(p, None).secret() { /// // Use it. /// } /// # 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. 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::general_purpose(None, Some("alice@example.org")) /// # .generate()?; /// for ka in cert.keys().with_policy(p, None).unencrypted_secret() { /// // Use it. /// } /// # 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. secret: self.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(None, Some("alice@example.org")) /// # .generate()?; /// # let key_handle = cert.primary_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> { if self.key_handles.is_none() { self.key_handles = Some(Vec::new()); } self.key_handles.as_mut().unwrap().push(h.into()); self } /// 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_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(None, Some("alice@example.org")) /// # .generate()?; /// # let key_handles = &[cert.primary_key().key_handle()][..]; /// # let mut i = 0; /// for ka in cert.keys().with_policy(p, None).key_handles(key_handles.iter()) { /// // Use it. /// # i += 1; /// } /// # assert_eq!(i, 1); /// # Ok(()) /// # } /// ``` /// /// [`KeyHandle`s]: super::super::super::KeyHandle /// [`key_handle`]: ValidKeyAmalgamationIter::key_handle() /// [`KeyHandle::aliases`]: super::super::super::KeyHandle::aliases() pub fn key_handles<'b>(mut self, h: impl Iterator<Item=&'b KeyHandle>) -> Self where 'a: 'b { if self.key_handles.is_none() { self.key_handles = Some(Vec::new()); } self.key_handles.as_mut().unwrap().extend(h.cloned()); 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(None, 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. secret: self.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] 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 = &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-1.7.0/src/cert/amalgamation/key.rs��������������������������������������������������0000644�0000000�0000000�00000252163�00726746425�0020322�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(None, 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(None, Some("alice@example.org")) //! # .generate()?; //! # let timestamp = None; //! # let issuer = cert.with_policy(p, None)?.keys() //! # .for_signing().nth(0).unwrap().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(None, 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::ops::Deref; use std::borrow::Borrow; use std::convert::TryFrom; use std::convert::TryInto; use anyhow::Context; use crate::{ Cert, cert::bundle::KeyBundle, cert::amalgamation::{ ComponentAmalgamation, ValidAmalgamation, 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(None, 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(None, 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(None, 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(None, 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)] 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, ); // 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, ()>; /// 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, R, R2> Deref for KeyAmalgamation<'a, P, R, R2> where P: 'a + key::KeyParts, R: 'a + key::KeyRole, { type Target = ComponentAmalgamation<'a, Key<P, R>>; fn deref(&self) -> &Self::Target { &self.ca } } 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.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.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.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 consider 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: '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: (), } } } 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 `KeyAmalgamation`'s `ComponentAmalgamation`. pub fn component_amalgamation(&self) -> &ComponentAmalgamation<'a, Key<P, R>> { &self.ca } /// Returns the `KeyAmalgamation`'s key. /// /// Normally, a type implementing `KeyAmalgamation` eventually /// derefs to a `Key`, however, this method provides a more /// accurate lifetime. See the documentation for /// `ComponentAmalgamation::component` for an explanation. pub fn key(&self) -> &'a Key<P, R> { self.ca.component() } } /// 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, ); /// 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> Deref for ValidKeyAmalgamation<'a, P, R, R2> where P: 'a + key::KeyParts, R: 'a + key::KeyRole, R2: Copy, { type Target = KeyAmalgamation<'a, P, R, R2>; fn deref(&self) -> &Self::Target { &self.ka } } 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 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().hash_algo_security(); // All valid self-signatures. let sec = self.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> 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.3 of RFC 4880]. /// /// [Section 5.2.3.3 of RFC 4880]: https://tools.ietf.org/html/rfc4880#section-5.2.3.3 /// /// 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.cert().alive()?; } 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()) } else { // There is no key expiration time on the binding // signature. This key does not expire. Ok(()) } } /// Returns the wrapped `KeyAmalgamation`. /// /// # 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` takes ownership of `ka`. /// let vka = ka.with_policy(p, None)?; /// /// // And here we get it back: /// let ka = vka.into_key_amalgamation(); /// # Ok(()) } /// ``` pub fn into_key_amalgamation(self) -> KeyAmalgamation<'a, P, R, R2> { self.ka } } 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)?; /// /// // 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 an non signing-capable subkey. /// /// [primary key binding signature]: https://tools.ietf.org/html/rfc4880#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)?; /// /// // 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.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.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)?); } } 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() { 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(), 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 an non /// signing-capable subkey. /// /// [primary key binding signature]: https://tools.ietf.org/html/rfc4880#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)?; /// /// // 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.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>> { /// Returns the key's `Key Flags`. /// /// A Key's [`Key Flags`] holds information about the key. As of /// RFC 4880, 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.3 of RFC 4880], 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. /// /// [`Key Flags`]: https://tools.ietf.org/html/rfc4880#section-5.2.3.21 /// [SSS]: https://de.wikipedia.org/wiki/Shamir%E2%80%99s_Secret_Sharing /// [Section 5.2.3.3 of RFC 4880]: https://tools.ietf.org/html/rfc4880#section-5.2.3.3 /// /// # 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(None, 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()) } /// 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(None, 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 12.1 of RFC 4880] 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(None, 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 12.1 of RFC 4880]: https://tools.ietf.org/html/rfc4880#section-5.2.3.21 /// [`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(None, 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(None, 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(None, 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(None, 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.3 of RFC 4880]. /// /// # 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(None, 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.3 of RFC 4880]: https://tools.ietf.org/html/rfc4880#section-5.2.3.3 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.3 of RFC 4880]. /// /// # 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(None, 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.3 of RFC 4880]: https://tools.ietf.org/html/rfc4880#section-5.2.3.3 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 crate::policy::StandardPolicy as P; use crate::cert::prelude::*; use crate::packet::Packet; 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(); 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(None, Some("alice@example.org")) .set_validity_period(None) .generate()?; // Remove the direct key signatures. let cert = Cert::from_packets(Vec::from(cert) .into_iter() .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)?; // 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(()) } } �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-1.7.0/src/cert/amalgamation.rs������������������������������������������������������0000644�0000000�0000000�00000215142�00726746425�0017526�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 of the //! aforementioned problems. //! //! # Lifetimes //! //! `ComponentAmalgamation` autoderefs to `ComponentBundle`. //! Unfortunately, due to the definition of the [`Deref` trait], //! `ComponentBundle` is assigned the same lifetime as //! `ComponentAmalgamation`. However, it's lifetime is actually `'a`. //! Particularly when using combinators like [`std::iter::map`], the //! `ComponentBundle`'s lifetime is longer. Consider the following //! code, which doesn't compile: //! //! ```compile_fail //! # 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| { //! // Use auto deref to get the containing `&ComponentBundle`. //! let b: &ComponentBundle<_> = &ua; //! b //! }) //! .collect::<Vec<&UserID>>(); //! # Ok(()) } //! ``` //! //! Compiling it results in the following error: //! //! > `b` returns a value referencing data owned by the current //! > function //! //! This error occurs because the `Deref` trait says that the lifetime //! of the target, i.e., `&ComponentBundle`, is bounded by `ua`'s //! lifetime, whose lifetime is indeed limited to the closure. But, //! `&ComponentBundle` is independent of `ua`; it is a copy of the //! `ComponentAmalgamation`'s reference to the `ComponentBundle` whose //! lifetime is `'a`! Unfortunately, this can't be expressed using //! `Deref`. But, it can be done using separate methods as shown //! below for the [`ComponentAmalgamation::component`] method: //! //! ``` //! # 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's lifetime is this closure. But `component()` //! // returns a reference whose lifetime is that of //! // `cert`. //! ua.component() //! }) //! .collect::<Vec<&UserID>>(); //! # Ok(()) } //! ``` //! //! [`ComponentBundle`]: super::bundle //! [`Signature`]: crate::packet::signature //! [`Cert`]: super //! [is supposed to]: https://tools.ietf.org/html/rfc4880#section-5.2.3.3 //! [`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://tools.ietf.org/html/draft-ietf-openpgp-rfc4880bis-09.html#name-intended-recipient-fingerpr //! [signature expirations]: https://tools.ietf.org/html/rfc4880#section-5.2.3.10 //! [`Deref` trait]: std::ops::Deref //! [`ComponentAmalgamation::component`]: ComponentAmalgamation::component() use std::time; use std::time::SystemTime; use std::clone::Clone; use std::borrow::Borrow; use crate::{ cert::prelude::*, crypto::{Signer, hash::{Hash, Digest}}, Error, packet::{ Signature, Unknown, UserAttribute, UserID, }, Result, policy::{ HashAlgoSecurity, Policy, }, seal, types::{ AEADAlgorithm, CompressionAlgorithm, Features, HashAlgorithm, KeyServerPreferences, RevocationKey, RevocationStatus, 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(None, 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 { /// 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.3 of RFC 4880]. /// /// [Section 5.2.3.3 of RFC 4880]: https://tools.ietf.org/html/rfc4880#section-5.2.3.3 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)) } /// 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 cert = ua.cert(); /// // ... /// } /// # fn main() -> openpgp::Result<()> { /// # let p = &StandardPolicy::new(); /// # let (cert, _) = /// # CertBuilder::general_purpose(None, 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 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(None, 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(None, 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(None, 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.3 of RFC 4880]. /// /// [Section 5.2.3.3 of RFC 4880]: https://tools.ietf.org/html/rfc4880#section-5.2.3.3 /// /// # 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(None, 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.cert().cert.primary.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 [RFC 4880], 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. /// /// [RFC 4880]: https://tools.ietf.org/html/rfc4880#section-5.2.3.23 /// /// # 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(None, 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'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(None, Some("alice@example.org")) /// .generate()?; /// // Make Alice a designated revoker for Bob. /// let (bob, _) = /// CertBuilder::general_purpose(None, 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>; } /// 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(None, 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> std::ops::Deref for ComponentAmalgamation<'a, C> { type Target = ComponentBundle<C>; fn deref(&self) -> &Self::Target { 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 component's associated certificate. /// /// ``` /// # use sequoia_openpgp as openpgp; /// # use openpgp::cert::prelude::*; /// # /// # fn main() -> openpgp::Result<()> { /// # let (cert, _) = /// # CertBuilder::general_purpose(None, 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 } /// Selects a binding signature. /// /// Uses the provided policy and reference time to select an /// appropriate binding signature. /// /// Note: this function is not exported. Users of this interface /// should do: ca.with_policy(policy, time)?.binding_signature(). 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. /// /// Note: although `ComponentAmalgamation` derefs to a /// `&ComponentBundle`, this method provides a more accurate /// lifetime, which is helpful when returning the reference from a /// function. [See the module's documentation] for more details. /// /// [See the module's documentation]: self /// /// # 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| { /// // The following doesn't work: /// // /// // let b: &ComponentBundle<_> = &ua; b /// // /// // Because ua's lifetime is this closure and autoderef /// // assigns `b` the same lifetime as `ua`. `bundle()`, /// // however, returns a reference whose lifetime is that /// // of `cert`. /// ua.bundle() /// }) /// .collect::<Vec<&ComponentBundle<_>>>(); /// # Ok(()) } /// ``` pub fn bundle(&self) -> &'a ComponentBundle<C> { self.bundle } /// Returns this amalgamation's component. /// /// Note: although `ComponentAmalgamation` derefs to a /// `&Component` (via `&ComponentBundle`), this method provides a /// more accurate lifetime, which is helpful when returning the /// reference from a function. [See the module's documentation] /// for more details. /// /// [See the module's documentation]: self pub fn component(&self) -> &'a C { self.bundle().component() } /// The component's self-signatures. pub fn self_signatures(&self) -> impl Iterator<Item=&'a Signature> + Send + Sync { self.bundle().self_signatures().iter() } /// The component's third-party certifications. pub fn certifications(&self) -> impl Iterator<Item=&'a Signature> + Send + Sync { self.bundle().certifications().iter() } /// The component's revocations that were issued by the /// certificate holder. pub fn self_revocations(&self) -> impl Iterator<Item=&'a Signature> + Send + Sync { self.bundle().self_revocations().iter() } /// The component's revocations that were issued by other /// certificates. pub fn other_revocations(&self) -> impl Iterator<Item=&'a Signature> + Send + Sync { self.bundle().other_revocations().iter() } /// Returns all of the component's signatures. pub fn signatures(&self) -> impl Iterator<Item = &'a Signature> + Send + Sync { self.bundle().signatures() } } 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, 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. /// /// Note: although `ComponentAmalgamation<UserID>` derefs to a /// `&UserID` (via `&ComponentBundle`), this method provides a /// more accurate lifetime, which is helpful when returning the /// reference from a function. [See the module's documentation] /// for more details. /// /// [See the module's documentation]: self pub fn userid(&self) -> &'a UserID { self.component() } /// Attests to third-party certifications. /// /// This feature is [experimental](crate#experimental-features). /// /// Allows the certificate owner to attest to third party /// certifications. See [Section 5.2.3.30 of RFC 4880bis] 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. /// /// [Section 5.2.3.30 of RFC 4880bis]: https://tools.ietf.org/html/draft-ietf-openpgp-rfc4880bis-10.html#section-5.2.3.30 /// /// # 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()])?; /// /// // Have Bob attest that certification. /// let bobs_uid = bob.userids().next().unwrap(); /// let attestations = /// bobs_uid.attest_certifications( /// policy, /// &mut bob_signer, /// bobs_uid.certifications())?; /// let bob = bob.insert_packets(attestations)?; /// /// assert_eq!(bob.bad_signatures().count(), 0); /// assert_eq!(bob.userids().next().unwrap().certifications().next(), /// Some(&alice_certifies_bob)); /// # Ok(()) } /// ``` pub fn attest_certifications<C, S>(&self, policy: &dyn Policy, primary_signer: &mut dyn Signer, certifications: C) -> Result<Vec<Signature>> where C: IntoIterator<Item = S>, S: Borrow<Signature>, { // Hash the components like in a binding signature. let mut hash = HashAlgorithm::default().context()?; self.cert().primary_key().hash(&mut hash); self.userid().hash(&mut hash); // Check if there is a previous attestation. If so, we need // that to robustly override it. let old = self.clone() .with_policy(policy, None) .ok() .and_then(|v| v.attestation_key_signatures().cloned().next()); attest_certifications_common(hash, old, primary_signer, certifications) } } impl<'a> UserAttributeAmalgamation<'a> { /// Returns a reference to the User Attribute. /// /// Note: although `ComponentAmalgamation<UserAttribute>` derefs /// to a `&UserAttribute` (via `&ComponentBundle`), this method /// provides a more accurate lifetime, which is helpful when /// returning the reference from a function. [See the module's /// documentation] for more details. /// /// [See the module's documentation]: self pub fn user_attribute(&self) -> &'a UserAttribute { self.component() } /// Attests to third-party certifications. /// /// This feature is [experimental](crate#experimental-features). /// /// Allows the certificate owner to attest to third party /// certifications. See [Section 5.2.3.30 of RFC 4880bis] 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. /// /// [Section 5.2.3.30 of RFC 4880bis]: https://tools.ietf.org/html/draft-ietf-openpgp-rfc4880bis-10.html#section-5.2.3.30 /// /// # Examples /// /// See [`UserIDAmalgamation::attest_certifications#examples`]. /// /// [`UserIDAmalgamation::attest_certifications#examples`]: UserIDAmalgamation#examples // The explicit link works around a bug in rustdoc. pub fn attest_certifications<C, S>(&self, policy: &dyn Policy, primary_signer: &mut dyn Signer, certifications: C) -> Result<Vec<Signature>> where C: IntoIterator<Item = S>, S: Borrow<Signature>, { // Hash the components like in a binding signature. let mut hash = HashAlgorithm::default().context()?; self.cert().primary_key().hash(&mut hash); self.user_attribute().hash(&mut hash); // Check if there is a previous attestation. If so, we need // that to robustly override it. let old = self.clone() .with_policy(policy, None) .ok() .and_then(|v| v.attestation_key_signatures().cloned().next()); attest_certifications_common(hash, old, primary_signer, certifications) } } /// Attests to third-party certifications. #[allow(clippy::let_and_return)] fn attest_certifications_common<C, S>(hash: Box<dyn Digest>, old_attestation: Option<Signature>, primary_signer: &mut dyn Signer, certifications: C) -> Result<Vec<Signature>> where C: IntoIterator<Item = S>, S: Borrow<Signature>, { use crate::{ packet::signature::{SignatureBuilder, subpacket::SubpacketArea}, serialize::MarshalInto, }; let hash_algo = hash.algo(); let digest_size = hash.digest_size(); let mut attestations = Vec::new(); for certification in certifications.into_iter() { let mut h = hash_algo.context()?; 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 = old.into(); 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 creation_time = crate::now() - time::Duration::new(SIG_BACKDATE_BY, 0); let template = SignatureBuilder::new(SignatureType::AttestationKey) .set_signature_creation_time(creation_time)?; template }; let template = template .set_hash_algo(hash_algo) // Important for size calculation. .pre_sign(primary_signer)?; // Compute the available space in the hashed area. For this, // it is important that template.pre_sign has been called. let available_space = SubpacketArea::MAX_SIZE - template.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) { sigs.push( template.clone() .set_attested_certifications(digests)? .sign_hash(primary_signer, hash.clone())?); } if attestations.is_empty() { // The certificate owner can withdraw attestations by issuing // an empty attestation key signature. assert!(sigs.is_empty()); sigs.push( template .set_attested_certifications(Option::<&[u8]>::None)? .sign_hash(primary_signer, hash.clone())?); } Ok(sigs) } /// 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.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); /// 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 the userid's attested third-party certifications. /// /// This feature is [experimental](crate#experimental-features). /// /// Allows the certificate owner to attest to third party /// certifications. See [Section 5.2.3.30 of RFC 4880bis] 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 attested by the certificate holder. /// /// [Section 5.2.3.30 of RFC 4880bis]: https://tools.ietf.org/html/draft-ietf-openpgp-rfc4880bis-10.html#section-5.2.3.30 pub fn attested_certifications(&self) -> impl Iterator<Item=&Signature> + Send + Sync { let mut hash_algo = None; let digests: std::collections::HashSet<_> = self.attestation_key_signatures() .filter_map(|sig| { sig.attested_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())?; sig.hash_for_confirmation(&mut hash); let digest = hash.into_digest().ok()?; if digests.contains(&digest[..]) { Some(sig) } else { None } }) } /// Returns set of active attestation 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 attested /// third-party certifications. /// /// This interface is useful for pruning old attestation key /// signatures when filtering a certificate. /// /// Note: This is a low-level interface. Consider using /// [`ValidUserIDAmalgamation::attested_certifications`] to /// iterate over all attested certifications. /// /// [`ValidUserIDAmalgamation::attested_certifications`]: ValidUserIDAmalgamation#method.attested_certifications // The explicit link works around a bug in rustdoc. pub fn attestation_key_signatures(&'a self) -> impl Iterator<Item=&'a Signature> + Send + Sync { let mut first = None; // The newest valid signature will be returned first. self.attestations() // 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 } }) } /// Attests to third-party certifications. /// /// This feature is [experimental](crate#experimental-features). /// /// Allows the certificate owner to attest to third party /// certifications. See [Section 5.2.3.30 of RFC 4880bis] for /// details. This can be used to address certificate flooding /// concerns. /// /// [Section 5.2.3.30 of RFC 4880bis]: https://tools.ietf.org/html/draft-ietf-openpgp-rfc4880bis-10.html#section-5.2.3.30 /// /// # 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()])?; /// /// // Have Bob attest that certification. /// let bobs_uid = bob.userids().next().unwrap(); /// let attestations = /// bobs_uid.attest_certifications( /// policy, /// &mut bob_signer, /// bobs_uid.certifications())?; /// let bob = bob.insert_packets(attestations)?; /// /// assert_eq!(bob.bad_signatures().count(), 0); /// assert_eq!(bob.userids().next().unwrap().certifications().next(), /// Some(&alice_certifies_bob)); /// # Ok(()) } /// ``` pub fn attest_certifications<C, S>(&self, primary_signer: &mut dyn Signer, certifications: C) -> Result<Vec<Signature>> where C: IntoIterator<Item = S>, S: Borrow<Signature>, { std::ops::Deref::deref(self) .attest_certifications(self.policy(), 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 the user attributes's attested third-party certifications. /// /// This feature is [experimental](crate#experimental-features). /// /// Allows the certificate owner to attest to third party /// certifications. See [Section 5.2.3.30 of RFC 4880bis] 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 attested by the certificate holder. /// /// [Section 5.2.3.30 of RFC 4880bis]: https://tools.ietf.org/html/draft-ietf-openpgp-rfc4880bis-10.html#section-5.2.3.30 pub fn attested_certifications(&self) -> impl Iterator<Item=&Signature> + Send + Sync { let mut hash_algo = None; let digests: std::collections::HashSet<_> = self.attestation_key_signatures() .filter_map(|sig| { sig.attested_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())?; sig.hash_for_confirmation(&mut hash); let digest = hash.into_digest().ok()?; if digests.contains(&digest[..]) { Some(sig) } else { None } }) } /// Returns set of active attestation 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 attested /// third-party certifications. /// /// This interface is useful for pruning old attestation key /// signatures when filtering a certificate. /// /// Note: This is a low-level interface. Consider using /// [`ValidUserAttributeAmalgamation::attested_certifications`] to /// iterate over all attested certifications. /// /// [`ValidUserAttributeAmalgamation::attested_certifications`]: ValidUserAttributeAmalgamation#method.attested_certifications // The explicit link works around a bug in rustdoc. pub fn attestation_key_signatures(&'a self) -> impl Iterator<Item=&'a Signature> + Send + Sync { let mut first = None; // The newest valid signature will be returned first. self.attestations() // 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 } }) } /// Attests to third-party certifications. /// /// This feature is [experimental](crate#experimental-features). /// /// Allows the certificate owner to attest to third party /// certifications. See [Section 5.2.3.30 of RFC 4880bis] for /// details. This can be used to address certificate flooding /// concerns. /// /// [Section 5.2.3.30 of RFC 4880bis]: https://tools.ietf.org/html/draft-ietf-openpgp-rfc4880bis-10.html#section-5.2.3.30 /// /// # Examples /// /// See [`ValidUserIDAmalgamation::attest_certifications#examples`]. /// /// [`ValidUserIDAmalgamation::attest_certifications#examples`]: ValidUserIDAmalgamation#examples // The explicit link works around a bug in rustdoc. pub fn attest_certifications<C, S>(&self, primary_signer: &mut dyn Signer, certifications: C) -> Result<Vec<Signature>> where C: IntoIterator<Item = S>, S: Borrow<Signature>, { std::ops::Deref::deref(self) .attest_certifications(self.policy(), 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> std::ops::Deref for ValidComponentAmalgamation<'a, C> { type Target = ComponentAmalgamation<'a, C>; fn deref(&self) -> &Self::Target { assert!(std::ptr::eq(self.ca.cert(), self.cert.cert())); &self.ca } } 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, than 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 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. 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)) } /// The component's self-signatures. /// /// This method only returns signatures that are valid under the current policy. pub fn self_signatures(&self) -> impl Iterator<Item=&Signature> + Send + Sync { std::ops::Deref::deref(self).self_signatures() .filter(move |sig| self.cert.policy().signature(sig, self.hash_algo_security).is_ok()) } /// The component's third-party certifications. /// /// This method only returns signatures that are valid under the current policy. pub fn certifications(&self) -> impl Iterator<Item=&Signature> + Send + Sync { std::ops::Deref::deref(self).certifications() .filter(move |sig| self.cert.policy().signature(sig, HashAlgoSecurity::CollisionResistance).is_ok()) } /// The component's revocations that were issued by the /// certificate holder. /// /// This method only returns signatures that are valid under the current policy. pub fn self_revocations(&self) -> impl Iterator<Item=&Signature> + Send + Sync { std::ops::Deref::deref(self).self_revocations() .filter(move |sig|self.cert.policy().signature(sig, self.hash_algo_security).is_ok()) } /// The component's revocations that were issued by other /// certificates. /// /// This method only returns signatures that are valid under the current policy. pub fn other_revocations(&self) -> impl Iterator<Item=&Signature> + Send + Sync { std::ops::Deref::deref(self).other_revocations() .filter(move |sig| self.cert.policy().signature(sig, HashAlgoSecurity::CollisionResistance).is_ok()) } /// Returns all of the component's signatures. /// /// This method only returns signatures that are valid under the /// current policy. pub fn signatures(&self) -> impl Iterator<Item = &Signature> + Send + Sync { std::ops::Deref::deref(self).signatures() .filter(move |sig| self.cert.policy().signature(sig, HashAlgoSecurity::CollisionResistance).is_ok()) } } 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> { fn 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().hash_algo_security(); // All valid self-signatures. let sec = self.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> crate::cert::Preferences<'a> for ValidComponentAmalgamation<'a, C> { 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_algorithms(&self) -> Option<&'a [AEADAlgorithm]> { self.map(|s| s.preferred_aead_algorithms()) } 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 crate::policy::StandardPolicy as P; use crate::cert::prelude::*; // 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<_>>(); } } ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-1.7.0/src/cert/bindings.rs����������������������������������������������������������0000644�0000000�0000000�00000033655�00726746425�0016700�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://tools.ietf.org/html/rfc4880#section-5.2.1 /// [`SignatureBuilder::sign_primary_key_binding`]: signature::SignatureBuilder::sign_primary_key_binding() /// /// This function adds a creation time subpacket, a issuer /// fingerprint subpacket, and a 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> = /// Key4::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()])?; /// /// // 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, a issuer /// fingerprint subpacket, and a 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()])?; /// /// // 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, a issuer /// fingerprint subpacket, and a 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() /// .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)?; /// /// // 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, a issuer /// fingerprint subpacket, and a 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()])?; /// /// // 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, a issuer /// fingerprint subpacket, and a 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() /// .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)?; /// /// // 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-1.7.0/src/cert/builder.rs�����������������������������������������������������������0000644�0000000�0000000�00000207436�00726746425�0016531�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������use std::time; use std::marker::PhantomData; use crate::packet; use crate::packet::{ key, Key, key::Key4, }; 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, }; /// 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(None, Some("alice@example.org")) /// .set_cipher_suite(CipherSuite::Cv25519) /// .generate()?; /// assert_eq!(ecc.primary_key().pk_algo(), PublicKeyAlgorithm::EdDSA); /// /// let (rsa, _) = /// CertBuilder::general_purpose(None, Some("alice@example.org")) /// .set_cipher_suite(CipherSuite::RSA4k) /// .generate()?; /// assert_eq!(rsa.primary_key().pk_algo(), PublicKeyAlgorithm::RSAEncryptSign); /// # Ok(()) /// # } /// ``` #[derive(Clone, Copy, PartialEq, PartialOrd, Eq, Ord, Debug)] 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, } assert_send_and_sync!(CipherSuite); impl Default for CipherSuite { fn default() -> Self { CipherSuite::Cv25519 } } impl CipherSuite { /// 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, R>(self, flags: K) -> Result<Key<key::SecretParts, R>> where R: key::KeyRole, 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()), } }, }.map(|key| key.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 /// /// # 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(None, Some("alice@example.org")) /// .generate()?; /// # Ok(()) /// # } /// ``` pub struct CertBuilder<'a> { creation_time: Option<std::time::SystemTime>, ciphersuite: CipherSuite, 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>>, phantom: PhantomData<&'a ()>, } assert_send_and_sync!(CertBuilder<'_>); #[allow(clippy::new_without_default)] 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.). /// /// [`CertBuilder::add_signing_subkey`]: CertBuilder::add_signing_subkey() /// [`CertBuilder::add_transport_encryption_subkey`]: CertBuilder::add_transport_encryption_subkey() /// [`CertBuilder::add_userid`]: CertBuilder::add_userid() /// /// # 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(), primary: KeyBlueprint{ flags: KeyFlags::empty().set_certification(), validity: None, ciphersuite: None, }, subkeys: vec![], userids: vec![], user_attributes: vec![], password: None, revocation_keys: None, 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. /// /// # Examples /// /// ``` /// use sequoia_openpgp as openpgp; /// use openpgp::cert::prelude::*; /// /// # fn main() -> openpgp::Result<()> { /// let (cert, rev) = /// CertBuilder::general_purpose(None, /// Some("Alice Lovelace <alice@example.org>")) /// .generate()?; /// # assert_eq!(cert.keys().count(), 3); /// # assert_eq!(cert.userids().count(), 1); /// # Ok(()) /// # } /// ``` pub fn general_purpose<C, U>(ciphersuite: C, userid: Option<U>) -> Self where C: Into<Option<CipherSuite>>, U: Into<packet::UserID> { let mut builder = Self::new() .set_cipher_suite(ciphersuite.into().unwrap_or_default()) .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); if let Some(u) = userid.map(Into::into) { builder = builder.add_userid(u); } 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(None, /// 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(None, Some("alice@example.org")) /// .set_cipher_suite(CipherSuite::Cv25519) /// .generate()?; /// assert_eq!(ecc.primary_key().pk_algo(), PublicKeyAlgorithm::EdDSA); /// /// let (rsa, _) = /// CertBuilder::general_purpose(None, Some("alice@example.org")) /// .set_cipher_suite(CipherSuite::RSA2k) /// .generate()?; /// assert_eq!(rsa.primary_key().pk_algo(), PublicKeyAlgorithm::RSAEncryptSign); /// # Ok(()) /// # } /// ``` pub fn set_cipher_suite(mut self, cs: CipherSuite) -> Self { self.ciphersuite = cs; 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://tools.ietf.org/html/rfc4880#section-5.2.3.19 /// /// # 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(None, /// 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.value().cmp(b.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://tools.ietf.org/html/rfc4880#section-5.2.3.19 /// /// # 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( /// None, 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.value().cmp(b.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://tools.ietf.org/html/rfc4880#section-5.2.3.19 /// /// # 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://tools.ietf.org/html/rfc4880#section-5.2.3.19 /// /// # 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( /// None, 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 an 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 `expiration` is `None`, the subkey uses the same expiration /// time 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 /// /// # 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( /// None, 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 /// /// Make the primary key certification and signing 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(None, /// 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().set_certification())); /// # 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(None, /// Some("Alice Lovelace <alice@example.org>")) /// .set_password(Some("1234".into())) /// .generate()?; /// /// for ka in cert.keys() { /// assert!(ka.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. /// /// A value of None means that the certificate never expires. // /// # 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(None, Some("alice@example.org")) /// .generate()?; /// let (bob, _) = /// CertBuilder::general_purpose(None, 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(None, Some("alice@example.org")) /// .generate()?; /// # Ok(()) } /// ``` pub fn generate(mut self) -> Result<(Cert, Signature)> { use crate::Packet; use crate::types::ReasonForRevocation; use std::convert::TryFrom; 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) }); // make sure the primary key can sign subkeys if !self.subkeys.is_empty() { self.primary.flags = self.primary.flags.set_certification(); } // Generate & self-sign primary key. let (primary, sig, mut signer) = self.primary_key(creation_time)?; let mut cert = Cert::try_from(vec![ Packet::SecretKey({ let mut primary = primary.clone(); if let Some(ref password) = self.password { primary.secret_mut().encrypt_in_place(password)?; } primary }), sig.into(), ])?; // 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 self.userids.into_iter() { let sig = template.unwrap_or_else( || SignatureBuilder::new(SignatureType::PositiveCertification)); let sig = Self::signature_common(sig, creation_time)?; let mut sig = Self::add_primary_key_metadata(sig, &self.primary)?; // 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)?; cert = cert.insert_packets( vec![Packet::from(uid), signature.into()])?; } // Sign UserAttributes. for (template, ua) in self.user_attributes.into_iter() { let sig = template.unwrap_or_else( || SignatureBuilder::new(SignatureType::PositiveCertification)); let sig = Self::signature_common(sig, creation_time)?; let mut sig = Self::add_primary_key_metadata(sig, &self.primary)?; // 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)?; cert = cert.insert_packets( vec![Packet::from(ua), 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)?; subkey.set_creation_time(creation_time)?; let sig = template.unwrap_or_else( || SignatureBuilder::new(SignatureType::SubkeyBinding)); let sig = Self::signature_common(sig, creation_time)?; 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() { // 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 { subkey.secret_mut().encrypt_in_place(password)?; } cert = cert.insert_packets(vec![Packet::SecretSubkey(subkey), signature.into()])?; } 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<(key::SecretKey, Signature, Box<dyn Signer>)> { let mut key = self.primary.ciphersuite .unwrap_or(self.ciphersuite) .generate_key(KeyFlags::empty().set_certification())?; key.set_creation_time(creation_time)?; let sig = SignatureBuilder::new(SignatureType::DirectKey); let sig = Self::signature_common(sig, creation_time)?; let mut sig = Self::add_primary_key_metadata(sig, &self.primary)?; if let Some(ref revocation_keys) = self.revocation_keys { sig = sig.set_revocation_key(revocation_keys.clone())?; } 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) -> Result<SignatureBuilder> { builder // GnuPG wants at least a 512-bit hash for P521 keys. .set_hash_algo(HashAlgorithm::SHA512) .set_signature_creation_time(creation_time) } /// Adds primary key metadata to the signature. fn add_primary_key_metadata(builder: SignatureBuilder, primary: &KeyBlueprint) -> Result<SignatureBuilder> { builder .set_features(Features::sequoia())? .set_key_flags(primary.flags.clone())? .set_key_validity_period(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::policy::StandardPolicy as P; #[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_mdc()); } #[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().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().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().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_mdc()); } #[test] fn 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[0].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(); 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().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()[0]; 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().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(None, Some("alice@example.org")) .set_revocation_keys(revokers.clone()) .generate()?; let cert = cert.with_policy(p, None)?; assert_eq!(cert.revocation_keys(None).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(None).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 hash = HashAlgorithm::SHA512.context()?; cert.primary_key().hash(&mut hash); let mut primary_signer = cert.primary_key().key().clone().parts_into_secret()? .into_keypair()?; let sig = signature::SignatureBuilder::new(SignatureType::DirectKey) .set_signature_creation_time(then)? .sign_hash(&mut primary_signer, hash)?; let cert = cert.insert_packets(sig)?; 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()?.value(), b"bar"); assert_eq!(count_primary_user_things(c), 1); Ok(()) } } ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-1.7.0/src/cert/bundle.rs������������������������������������������������������������0000644�0000000�0000000�00000111455�00726746425�0016347�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 a 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://tools.ietf.org/html/rfc4880#section-5.2.3.15 //! [`ComponentAmalgamation`]: super::amalgamation //! [`KeyAmalgamation`]: super::amalgamation::key::KeyAmalgamation //! [complicated semantics]: https://tools.ietf.org/html/rfc4880#section-5.2.3.3 use std::time; use std::ops::Deref; use crate::{ 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> { pub(crate) component: C, pub(crate) hash_algo_security: HashAlgoSecurity, // Self signatures. pub(crate) self_signatures: Vec<Signature>, // Third-party certifications. (In general, this will only be by // designated revokers.) pub(crate) certifications: Vec<Signature>, // Attestation key signatures. pub(crate) attestations: Vec<Signature>, // Self revocations. pub(crate) self_revocations: Vec<Signature>, // Third-party revocations (e.g., designated revokers). pub(crate) 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> Deref for ComponentBundle<C> { type Target = C; fn deref(&self) -> &Self::Target { &self.component } } impl<C> ComponentBundle<C> { /// 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(None, 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) -> &C { &self.component } /// Returns a mutable reference to the component. 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(None, 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, t: T) -> Result<&Signature> where T: Into<Option<time::SystemTime>> { let t = t.into().unwrap_or_else(crate::now); // 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`. let i = // Usually, the first signature is what we are looking for. // Short circuit the binary search. if Some(t) >= self.self_signatures.get(0) .and_then(|s| s.signature_creation_time()) { 0 } else { match self.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 && self.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 s in self.self_signatures[i..].iter() { 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, self.hash_algo_security) { if error.is_none() { error = Some(e); } 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, self.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()) } } /// 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(None, Some("alice@example.org")) /// # .generate()?; /// for (i, ka) in cert.keys().enumerate() { /// eprintln!("Key #{} ({}) has {:?} self signatures", /// i, ka.fingerprint(), /// ka.bundle().self_signatures().len()); /// } /// # Ok(()) } /// ``` pub fn self_signatures(&self) -> &[Signature] { &self.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(None, 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().len()); /// } /// # Ok(()) } /// ``` pub fn certifications(&self) -> &[Signature] { &self.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(None, 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().len()); /// } /// # Ok(()) } /// ``` pub fn self_revocations(&self) -> &[Signature] { &self.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(None, 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().len()); /// } /// # Ok(()) } /// ``` pub fn other_revocations(&self) -> &[Signature] { &self.other_revocations } /// Returns all of the component's Attestation 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 Attestation Key Signatures to /// attest to third party certifications. Currently, only userid /// and user attribute certifications can be attested. See /// [Section 5.2.3.30 of RFC 4880bis] for details. /// /// [Section 5.2.3.30 of RFC 4880bis]: https://tools.ietf.org/html/draft-ietf-openpgp-rfc4880bis-10.html#section-5.2.3.30 /// /// # 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(None, Some("alice@example.org")) /// # .generate()?; /// for (i, uid) in cert.userids().enumerate() { /// eprintln!("UserID #{} ({:?}) has {:?} attestation key signatures", /// i, uid.email(), /// uid.attestations().count()); /// } /// # Ok(()) } /// ``` pub fn attestations(&self) -> impl Iterator<Item = &Signature> + Send + Sync { self.attestations.iter() } /// 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 key attestation 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(None, Some("alice@example.org")) /// # .generate()?; /// for (i, ka) in cert.keys().enumerate() { /// eprintln!("Key #{} ({}) has {:?} signatures", /// i, ka.fingerprint(), /// ka.signatures().count()); /// } /// # Ok(()) } /// ``` pub fn signatures(&self) -> impl Iterator<Item = &Signature> + Send + Sync { self.self_revocations.iter() .chain(self.self_signatures.iter()) .chain(self.attestations.iter()) .chain(self.certifications.iter()) .chain(self.other_revocations.iter()) } /// 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`. #[allow(clippy::blocks_in_if_conditions)] 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: &'a [Signature], sec: HashAlgoSecurity| -> Option<Vec<&'a Signature>> { let revs = revs.iter().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(&self.self_revocations, self.hash_algo_security) { RevocationStatus::Revoked(revs) } else if let Some(revs) = check(&self.other_revocations, Default::default()) { RevocationStatus::CouldBe(revs) } else { 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_iter().map(|s| s.into())) .chain(self.self_signatures.into_iter().map(|s| s.into())) .chain(self.attestations.into_iter().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().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().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().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(None, 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() } /// Returns a mut reference to the key. pub(crate) fn key_mut(&mut self) -> &mut Key<P, R> { self.component_mut() } } impl<P: key::KeyParts> ComponentBundle<Key<P, key::SubordinateRole>> { /// 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(None, Some("alice@example.org")) /// # .generate()?; /// // Display the subkeys' revocation status. /// for ka in cert.keys().subkeys() { /// eprintln!(" Revocation status of {}: {:?}", /// ka.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._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(None, 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) -> &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(None, 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._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(None, 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) -> &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(None, 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._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(None, 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() } } �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-1.7.0/src/cert/parser/low_level/grammar.lalrpop�������������������������������������0000644�0000000�0000000�00000023523�00726746425�0023033�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������// -*- mode: Rust; -*- use crate::Error; 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::cert::prelude::*; use crate::cert::parser::low_level::lexer; use crate::cert::parser::low_level::lexer::{Token, Component}; use crate::cert::ComponentBundles; use crate::cert::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 mut cert = Cert { primary: PrimaryKeyBundle { component: key, hash_algo_security: sec, self_signatures: vec![], certifications: sigs, attestations: vec![], self_revocations: vec![], other_revocations: vec![], }, subkeys: ComponentBundles::new(), userids: ComponentBundles::new(), user_attributes: ComponentBundles::new(), unknowns: ComponentBundles::new(), bad: vec![], }; for c in c.into_iter() { match c { Component::SubkeyBundle(b) => cert.subkeys.push(b), Component::UserIDBundle(b) => cert.userids.push(b), Component::UserAttributeBundle(b) => cert.user_attributes.push(b), Component::UnknownBundle(b) => cert.unknowns.push(b), } } Ok(Some(cert)) } Some((Packet::Unknown(unknown), _sigs)) => { Err(ParseError::User { error: Error::UnsupportedCert(format!( "Unsupported primary key: Unparsable {} ({:?}).", unknown.tag(), unknown).into()) }) } None => { // Just validating a message... 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 message... 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 message... 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 message... None } }, } Component: Option<Component> = { <key:Subkey> <sigs:OptionalSignatures> => { match key { Some(key) => { let sigs = sigs.unwrap(); let sec = key.hash_algo_security(); Some(Component::SubkeyBundle(SubkeyBundle { component: key, hash_algo_security: sec, self_signatures: vec![], certifications: sigs, attestations: vec![], self_revocations: vec![], other_revocations: vec![], })) }, // Just validating a message... None => None, } }, <u:UserID> <sigs:OptionalSignatures> => { match u { Some(u) => { let sigs = sigs.unwrap(); let sec = u.hash_algo_security(); Some(Component::UserIDBundle(UserIDBundle { component: u, hash_algo_security: sec, self_signatures: vec![], certifications: sigs, attestations: vec![], self_revocations: vec![], other_revocations: vec![], })) }, // Just validating a message... None => None, } }, <u:UserAttribute> <sigs:OptionalSignatures> => { match u { Some(u) => { let sigs = sigs.unwrap(); let sec = u.hash_algo_security(); Some(Component::UserAttributeBundle(UserAttributeBundle { component: u, hash_algo_security: sec, self_signatures: vec![], certifications: sigs, attestations: vec![], self_revocations: vec![], other_revocations: vec![], })) }, // Just validating a message... None => None, } }, <u:Unknown> <sigs:OptionalSignatures> => { match u { Some(u) => { let sigs = sigs.unwrap(); let sec = u.hash_algo_security(); Some(Component::UnknownBundle(UnknownBundle { component: u, hash_algo_security: sec, self_signatures: vec![], certifications: sigs, attestations: vec![], self_revocations: vec![], other_revocations: vec![], })) }, // Just validating a message... None => None, } }, } Subkey: Option<Key<key::PublicParts, key::SubordinateRole>> = { <t:PUBLIC_SUBKEY> => { match t.into() { Some(Packet::PublicSubkey(key)) => Some(key), // Just validating a message... None => None, Some(pkt) => unreachable!("Expected public subkey packet, got {:?}", pkt), } }, <t:SECRET_SUBKEY> => { match t.into() { Some(Packet::SecretSubkey(key)) => Some(key.parts_into_public()), // Just validating a message... None => None, Some(pkt) => unreachable!("Expected secret subkey packet, got {:?}", pkt), } }, } UserID: Option<UserID> = { <t:USERID> => { match t.into() { Some(Packet::UserID(u)) => Some(u), // Just validating a message... None => None, Some(pkt) => unreachable!("Expected user id packet, got {:?}", pkt), } }, } UserAttribute: Option<UserAttribute> = { <t:USER_ATTRIBUTE> => { match t.into() { Some(Packet::UserAttribute(u)) => Some(u), // Just validating a message... None => None, Some(pkt) => unreachable!("Expected user attribute packet, got {:?}", pkt), } }, } Unknown: Option<Unknown> = { <t:UNKNOWN> => { match t.into() { Some(Packet::Unknown(u)) => Some(u), // Just validating a message... None => None, Some(pkt) => unreachable!("Expected unknown packet, got {:?}", pkt), } }, } 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-1.7.0/src/cert/parser/low_level/lexer.rs��������������������������������������������0000644�0000000�0000000�00000011365�00726746425�0021500�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������use std::fmt; use crate::Error; use crate::Packet; use crate::packet::Tag; use crate::cert::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. #[allow(clippy::enum_variant_names)] 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 From<Packet> for Option<Token> { fn from(p: Packet) -> Self { match p { p @ Packet::PublicKey(_) => Some(Token::PublicKey(Some(p))), p @ Packet::SecretKey(_) => Some(Token::SecretKey(Some(p))), p @ Packet::PublicSubkey(_) => Some(Token::PublicSubkey(Some(p))), p @ Packet::SecretSubkey(_) => Some(Token::SecretSubkey(Some(p))), p @ Packet::UserID(_) => Some(Token::UserID(Some(p))), p @ Packet::UserAttribute(_) => Some(Token::UserAttribute(Some(p))), p @ Packet::Signature(_) => Some(Token::Signature(Some(p))), p @ Packet::Trust(_) => Some(Token::Trust(Some(p))), p @ Packet::Unknown(_) => Some(Token::Unknown(p.tag(), Some(p))), _ => None, } } } 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-1.7.0/src/cert/parser/low_level/mod.rs����������������������������������������������0000644�0000000�0000000�00000004553�00726746425�0021141�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) 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 is // necessary, because packets are not async, but Fail, which we want // to convert ParseErrors to, is. 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. 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-1.7.0/src/cert/parser/mod.rs��������������������������������������������������������0000644�0000000�0000000�00000165011�00726746425�0017146�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������use std::io; use std::mem; use std::vec; use std::path::Path; use lalrpop_util::ParseError; use crate::{ Error, KeyHandle, packet::Tag, Packet, parse::{ 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), _ => { // Unknown token. self.error = Some(CertParserError::OpenPGP( Error::MalformedMessage( format!("Invalid Cert: {:?} packet (at {}) 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. /// Its 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://tools.ietf.org/html/rfc4880#section-11.1 /// [`TSK`]: https://tools.ietf.org/html/rfc4880#section-11.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(None, Some("alice@example.org")) /// # .generate()?; /// # let (bob, _) = /// # CertBuilder::general_purpose(None, 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::Text); /// lit.set_body(b"test".to_vec()); /// /// let (alice, _) = /// CertBuilder::general_purpose(None, Some("alice@example.org")) /// .generate()?; /// let (bob, _) = /// CertBuilder::general_purpose(None, Some("bob@example.org")) /// .generate()?; /// /// let mut packets : Vec<Packet> = Vec::new(); /// packets.extend(alice.clone()); /// packets.push(lit.clone().into()); /// packets.push(lit.clone().into()); /// packets.extend(bob.clone()); /// /// 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>, saw_error: bool, 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 || { if let Some(reader) = retry_with_reader.take() { // Try to find the next (armored) blob. match PacketParser::from_buffered_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()), } Some(Ok(packet)) }, Err(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 `Read`er. fn from_reader<R: 'a + io::Read + Send + Sync>(reader: R) -> Result<Self> { Ok(Self::from(PacketParser::from_reader(reader)?)) } /// Initializes a `CertParser` from a `File`. fn from_file<P: AsRef<Path>>(path: P) -> Result<Self> { Ok(Self::from(PacketParser::from_file(path)?)) } /// Initializes a `CertParser` from a byte string. fn from_bytes<D: AsRef<[u8]> + ?Sized + Send + Sync>(data: &'a D) -> Result<Self> { Ok(Self::from(PacketParser::from_bytes(data)?)) } } #[allow(clippy::should_implement_trait)] 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(None, Some("alice@example.org")) /// # .generate()?; /// # let (bob, _) = /// # CertBuilder::general_purpose(None, 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: 'a>(mut self, filter: F) -> Self where F: 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); if let Packet::Marker(_) = p { // Ignore Marker Packet. RFC4880, section 5.8: // // Such a packet MUST be ignored when received. 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 packets = orig.packets.len(); t!("Finalizing certificate with {} packets", packets); let tokens = orig.packets .into_iter() .filter_map(|p| p.into()) .collect::<Vec<Token>>(); if tokens.len() != packets { // 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()); t!("Invalid certificate: {}", err); return Err(err.into()); } 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(); let primary_keyid = KeyHandle::KeyID(primary_fp.clone().into()); // The parser puts all of the signatures on the // certifications field. Split them now. split_sigs(&primary_fp, &primary_keyid, &mut cert.primary); for b in cert.userids.iter_mut() { split_sigs(&primary_fp, &primary_keyid, b); } for b in cert.user_attributes.iter_mut() { split_sigs(&primary_fp, &primary_keyid, b); } for b in cert.subkeys.iter_mut() { split_sigs(&primary_fp, &primary_keyid, 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()), packets); Ok(certo) } } /// Splits the signatures in b.certifications into the correct /// vectors. pub(crate) fn split_sigs<C>(primary: &KeyHandle, primary_keyid: &KeyHandle, b: &mut ComponentBundle<C>) { let mut self_signatures = Vec::with_capacity(0); let mut certifications = Vec::with_capacity(0); let mut self_revs = Vec::with_capacity(0); let mut other_revs = Vec::with_capacity(0); 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.contains(primary) || issuers.contains(primary_keyid); use crate::SignatureType::*; if typ == KeyRevocation || typ == SubkeyRevocation || typ == CertificationRevocation { if is_selfsig { self_revs.push(sig); } else { other_revs.push(sig); } } else if is_selfsig { self_signatures.push(sig); } else { certifications.push(sig); } } b.self_signatures = self_signatures; b.certifications = certifications; b.self_revocations = self_revs; b.other_revocations = other_revs; } impl<'a> Iterator for CertParser<'a> { type Item = Result<Cert>; fn next(&mut self) -> Option<Self::Item> { tracer!(TRACE, "CertParser::next", 0); loop { match self.source.take() { None => { t!("EOF."); if let Some(err) = self.queued_error.take() { return Some(Err(err)); } 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)) => { t!("Error getting packet: {}", err); self.saw_error = true; 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(err) => { 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 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, }, ]; 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 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(); } #[test] fn invalid_packets() -> Result<()> { tracer!(TRACE, "invalid_packets", 0); fn cert_cmp(a: &Result<Cert>, b: &Vec<Packet>) { let a : Vec<Packet> = a.as_ref().unwrap().clone().into(); 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(None, Some("alice@example.org")) .generate()?; let cert : Vec<Packet> = cert.into(); // 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::Text); 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( None, Some(format!("{}@example.org", i))) .generate()?; cert.as_tsk().serialize(&mut data)?; certs_orig.push(cert); if literal { let mut lit = Literal::new(DataFormat::Text); 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.into_packets().collect(); let ps_parsed: Vec<Packet> = c_parsed.into_packets().collect(); if bad > 0 && ! literal && i == n - 1 { // On a parse error, we lose the last successfully // parsed packet. This is annoying. But, the // file is corrupted anyway, so... assert_eq!(ps_orig.len() - 1, ps_parsed.len(), "number of packets: expected vs. got"); } else { 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( None, Some("a@example.org")) .generate()?; let cert_1_packets: Vec<Packet> = cert_1.into_packets().collect(); let (cert_2, _) = CertBuilder::general_purpose( None, 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)?; 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); } } �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-1.7.0/src/cert/prelude.rs�����������������������������������������������������������0000644�0000000�0000000�00000004076�00726746425�0016536�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, 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-1.7.0/src/cert/revoke.rs������������������������������������������������������������0000644�0000000�0000000�00000147325�00726746425�0016376�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������use std::convert::TryFrom; use std::ops::Deref; 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://tools.ietf.org/html/rfc4880#section-5.2.3.15 /// /// # 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())?; /// /// // 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); #[allow(clippy::new_without_default)] 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())?; /// # /// # // 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 Deref for CertRevocationBuilder { type Target = signature::SignatureBuilder; fn deref(&self) -> &Self::Target { &self.builder } } 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://tools.ietf.org/html/rfc4880#section-5.2.3.15 /// /// # 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())?; /// /// // 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); #[allow(clippy::new_without_default)] 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())?; /// # /// # // 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 Deref for SubkeyRevocationBuilder { type Target = signature::SignatureBuilder; fn deref(&self) -> &Self::Target { &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://tools.ietf.org/html/rfc4880#section-5.2.3.15 /// /// # 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())?; /// /// // 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); #[allow(clippy::new_without_default)] 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())?; /// # /// # // 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 Deref for UserIDRevocationBuilder { type Target = signature::SignatureBuilder; fn deref(&self) -> &Self::Target { &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://tools.ietf.org/html/rfc4880#section-5.2.3.15 /// /// # 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())?; /// /// // 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); #[allow(clippy::new_without_default)] 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())?; /// # /// # // 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 Deref for UserAttributeRevocationBuilder { type Target = signature::SignatureBuilder; fn deref(&self) -> &Self::Target { &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(); 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(); 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-1.7.0/src/cert.rs�������������������������������������������������������������������0000644�0000000�0000000�00000775366�00726746425�0015117�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 11.1] of //! RFC 4880: 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 11.1]: https://tools.ietf.org/html/rfc4880#section-11.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::io; use std::collections::btree_map::BTreeMap; use std::collections::btree_map::Entry; use std::collections::hash_map::DefaultHasher; use std::cmp; use std::cmp::Ordering; use std::convert::TryFrom; use std::convert::TryInto; use std::hash::Hasher; use std::path::Path; use std::mem; use std::fmt; use std::ops::{Deref, DerefMut}; use std::time; use crate::{ crypto::{ Signer, hash::Digest, }, 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::{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; mod parser; mod revoke; pub use self::builder::{CertBuilder, CipherSuite}; 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()) } } /// A collection of `ComponentBundles`. /// /// Note: we need this, because we can't `impl Vec<ComponentBundles>`. #[derive(Debug, Clone, PartialEq)] struct ComponentBundles<C> where ComponentBundle<C>: cmp::PartialEq { bundles: Vec<ComponentBundle<C>>, } 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> 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 { fn new() -> Self { Self { bundles: vec![] } } } 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. 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_unstable_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. 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. type KeyBundles<KeyPart, KeyRole> = ComponentBundles<Key<KeyPart, KeyRole>>; /// A vector of subkeys and any associated signatures. type SubkeyBundles<KeyPart> = KeyBundles<KeyPart, key::SubordinateRole>; /// A vector of key (primary or subkey, public or private) and any /// associated signatures. #[allow(dead_code)] type GenericKeyBundles = ComponentBundles<Key<key::UnspecifiedParts, key::UnspecifiedRole>>; /// A vector of User ID bundles and any associated signatures. type UserIDBundles = ComponentBundles<UserID>; /// A vector of User Attribute bundles and any associated signatures. type UserAttributeBundles = ComponentBundles<UserAttribute>; /// A vector of unknown components and any associated signatures. /// /// Note: all signatures are stored as certifications. type UnknownBundles = ComponentBundles<Unknown>; /// 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.3] of RFC 4880. /// /// 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, RFC 4880bis 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.3]: https://tools.ietf.org/html/rfc4880#section-5.2.3.3 /// [WKD]: https://tools.ietf.org/html/draft-koch-openpgp-webkey-service-09#section-5 /// [without any User ID packets]: https://tools.ietf.org/html/draft-ietf-openpgp-rfc4880bis-09#section-11.1 /// /// # Algorithm Preferences /// /// Algorithms are ordered with the most preferred algorithm first. /// According to RFC 4880, if an algorithm is not listed, then the /// implementation should assume that it is not supported by the /// certificate holder's software. /// /// # 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(None, 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 by 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 by 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 by the certificate /// holder's preference. fn preferred_compression_algorithms(&self) -> Option<&'a [CompressionAlgorithm]>; /// Returns the supported AEAD algorithms ordered by preference. /// /// The algorithms are ordered according by the certificate holder's /// preference. fn preferred_aead_algorithms(&self) -> Option<&'a [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 4880. 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://tools.ietf.org/html/rfc4880#section-11 /// [`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://tools.ietf.org/html/rfc4880#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 of 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.attestations() { 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.attestations() { 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(None, 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 4880. 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(None, 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; fn from_str(s: &str) -> std::result::Result<Self, Self::Err> { Self::from_bytes(s.as_bytes()) } } impl<'a> Parse<'a, Cert> for Cert { /// Returns the first Cert encountered in the reader. fn from_reader<R: io::Read + Send + Sync>(reader: R) -> Result<Self> { Cert::try_from(PacketParser::from_reader(reader)?) } /// Returns the first Cert encountered in the file. fn from_file<P: AsRef<Path>>(path: P) -> Result<Self> { Cert::try_from(PacketParser::from_file(path)?) } /// Returns the first Cert found in `buf`. /// /// `buf` must be an OpenPGP-encoded message. fn from_bytes<D: AsRef<[u8]> + ?Sized + Send + Sync>(data: &'a D) -> Result<Self> { Cert::try_from(PacketParser::from_bytes(data)?) } } 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(None, 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())?; /// 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)?; /// 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)?; self.insert_packets(sigs) } /// 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)?; /// 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. /// /// # Examples /// /// ``` /// # use sequoia_openpgp as openpgp; /// # use openpgp::cert::prelude::*; /// # use openpgp::packet::prelude::*; /// # /// # fn main() -> openpgp::Result<()> { /// # let (cert, rev) = /// # CertBuilder::general_purpose(None, Some("alice@example.org")) /// # .generate()?; /// println!("{}'s User IDs:", cert.fingerprint()); /// for ua in cert.userids() { /// println!(" {}", String::from_utf8_lossy(ua.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)?; /// # 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. /// /// # Examples /// /// ``` /// # use sequoia_openpgp as openpgp; /// # use openpgp::cert::prelude::*; /// # /// # fn main() -> openpgp::Result<()> { /// # let (cert, rev) = /// # CertBuilder::general_purpose(None, 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. /// /// 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. pub(crate) fn subkeys(&self) -> ComponentAmalgamationIter<Key<key::PublicParts, key::SubordinateRole>> { ComponentAmalgamationIter::new(self, self.subkeys.iter()) } /// 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(None, 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)?; /// println!("{}'s has {} unknown components.", /// cert.fingerprint(), /// cert.unknowns().count()); /// for ua in cert.unknowns() { /// println!(" Unknown component with tag {} ({}), error: {}", /// ua.tag(), u8::from(ua.tag()), ua.error()); /// } /// # assert_eq!(cert.unknowns().count(), 1); /// # assert_eq!(cert.unknowns().nth(0).unwrap().tag(), tag); /// # Ok(()) /// # } /// ``` pub fn unknowns(&self) -> UnknownComponentAmalgamationIter { ComponentAmalgamationIter::new(self, self.unknowns.iter()) } /// Returns a slice containing 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(None, 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.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(None, Some("alice@example.org")) /// .generate()?; /// // Make Alice a designated revoker for Bob. /// let (bob, _) = /// CertBuilder::general_purpose(None, 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) -> Box<dyn Iterator<Item = &'a RevocationKey> + 'a> { let mut keys = std::collections::HashSet::new(); let pk_sec = self.primary_key().hash_algo_security(); // All user ids. self.userids() .flat_map(|ua| { // All valid self-signatures. let sec = ua.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); }); Box::new(keys.into_iter()) } /// Converts the certificate into an iterator over a sequence of /// packets. /// /// **WARNING**: When serializing a `Cert`, any secret key /// material is dropped. In order to serialize the secret key /// material, it is first necessary to convert the `Cert` into a /// [`TSK`] and serialize that. This behavior makes it harder to /// accidentally leak secret key material. *This function is /// different.* If a key contains secret key material, it is /// exported as a [`SecretKey`] or [`SecretSubkey`], as /// appropriate. This means that **if you serialize the resulting /// packets, the secret key material will be serialized too**. /// /// [`TSK`]: crate::serialize::TSK /// [`SecretKey`]: Packet::SecretKey /// [`SecretSubkey`]: Packet::SecretSubkey /// /// # Examples /// /// ``` /// # use sequoia_openpgp as openpgp; /// # use openpgp::cert::prelude::*; /// # /// # fn main() -> openpgp::Result<()> { /// # let (cert, _) = /// # CertBuilder::general_purpose(None, Some("alice@example.org")) /// # .generate()?; /// println!("Cert contains {} packets", /// cert.into_packets().count()); /// # Ok(()) /// # } /// ``` pub fn into_packets(self) -> impl Iterator<Item=Packet> + Send + Sync { 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) => { if k.has_secret() { Packet::SecretKey(k.parts_into_secret().unwrap()) } else { Packet::PublicKey(k) } } Packet::PublicSubkey(k) => { if k.has_secret() { Packet::SecretSubkey(k.parts_into_secret().unwrap()) } else { Packet::PublicSubkey(k) } } _ => 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(None, 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(None, 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. 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 one will be dropped // (non-deterministicly)! // // This can happen if: // // - One is corrupted // - There are two versions that are encrypted differently self.subkeys.sort_and_dedup(Key::public_cmp, |a, b| { // Recall: if a and b are equal, a will be dropped. if ! b.has_secret() && 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); // 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 // verify_method: the method to call on a signature to verify it // verify_args: additional arguments to pass to verify_method macro_rules! check { ($desc:expr, $binding:expr, $sigs:ident, $verify_method:ident, $($verify_args:expr),*) => ({ t!("check!({}, {}, {:?}, {}, ...)", $desc, stringify!($binding), $binding.$sigs, stringify!($verify_method)); for mut sig in mem::take(&mut $binding.$sigs).into_iter() { match sig.$verify_method(self.primary.key(), self.primary.key(), $($verify_args),*) { Ok(()) => $binding.$sigs.push(sig), Err(err) => { t!("Sig {:02X}{:02X}, type = {} \ doesn't belong to {}: {:?}", sig.digest_prefix()[0], sig.digest_prefix()[1], sig.typ(), $desc, err); self.bad.push(sig); } } } }); ($desc:expr, $binding:expr, $sigs:ident, $verify_method:ident) => ({ check!($desc, $binding, $sigs, $verify_method,) }); } // 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 $($verify_args:expr),* // additional arguments to pass to the above ) => ({ t!("check_3rd_party!({}, {}, {:?}, {}, {}, ...)", $desc, stringify!($binding), $binding.$sigs, stringify!($verify_method), stringify!($hash_method)); for mut sig in mem::take(&mut $binding.$sigs) { // Use hash prefix as heuristic. let key = self.primary.key(); match sig.hash_algo().context().and_then(|mut ctx| { sig.$hash_method(&mut ctx, key, $($verify_args),*); ctx.into_digest() }) { 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. $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. t!("Sig {:02X}{:02X}, type = {}: \ Hashing failed: {}", 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) => ({ check_3rd_party!($desc, $binding, $sigs, $lookup_fn, $verify_method, $hash_method, ) }); } // Placeholder lookup function. fn lookup_fn(_: &Signature) -> Option<Key<key::PublicParts, key::UnspecifiedRole>> { None } check!("primary key", self.primary, self_signatures, verify_direct_key); check!("primary key", self.primary, self_revocations, verify_primary_key_revocation); check_3rd_party!("primary key", self.primary, certifications, lookup_fn, verify_direct_key, hash_direct_key); check_3rd_party!("primary key", self.primary, other_revocations, lookup_fn, verify_primary_key_revocation, hash_direct_key); for ua in self.userids.iter_mut() { check!(format!("userid \"{}\"", String::from_utf8_lossy(ua.userid().value())), ua, self_signatures, verify_userid_binding, ua.userid()); check!(format!("userid \"{}\"", String::from_utf8_lossy(ua.userid().value())), ua, self_revocations, verify_userid_revocation, ua.userid()); check_3rd_party!( format!("userid \"{}\"", String::from_utf8_lossy(ua.userid().value())), ua, certifications, lookup_fn, verify_userid_binding, hash_userid_binding, 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, ua.userid()); } for binding in self.user_attributes.iter_mut() { check!("user attribute", binding, self_signatures, verify_user_attribute_binding, binding.user_attribute()); check!("user attribute", binding, self_revocations, verify_user_attribute_revocation, binding.user_attribute()); check_3rd_party!( "user attribute", binding, certifications, lookup_fn, verify_user_attribute_binding, hash_user_attribute_binding, binding.user_attribute()); check_3rd_party!( "user attribute", binding, other_revocations, lookup_fn, verify_user_attribute_revocation, hash_user_attribute_binding, binding.user_attribute()); } for binding in self.subkeys.iter_mut() { check!(format!("subkey {}", binding.key().keyid()), binding, self_signatures, verify_subkey_binding, binding.key()); check!(format!("subkey {}", binding.key().keyid()), binding, self_revocations, verify_subkey_revocation, binding.key()); check_3rd_party!( format!("subkey {}", binding.key().keyid()), binding, certifications, lookup_fn, verify_subkey_binding, hash_subkey_binding, binding.key()); check_3rd_party!( format!("subkey {}", binding.key().keyid()), binding, other_revocations, lookup_fn, verify_subkey_revocation, hash_subkey_binding, binding.key()); } // 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(); // 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 std::mem::take(&mut c.self_signatures).into_iter() .chain( std::mem::take(&mut c.certifications).into_iter()) .chain( std::mem::take(&mut c.attestations).into_iter()) .chain( std::mem::take(&mut c.self_revocations).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(); let primary_keyid = KeyHandle::KeyID(primary_fp.clone().into()); 'outer: for (unknown_idx, mut 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.contains(&primary_fp) || issuers.contains(&primary_keyid); macro_rules! check_one { ($desc:expr, $sigs:expr, $sig:expr, $verify_method:ident, $($verify_args:expr),*) => ({ if is_selfsig { t!("check_one!({}, {:?}, {:?}, {}, ...)", $desc, $sigs, $sig, stringify!($verify_method)); if let Ok(()) = $sig.$verify_method(self.primary.key(), self.primary.key(), $($verify_args),*) { 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; } } }); ($desc:expr, $sigs:expr, $sig:expr, $verify_method:ident) => ({ check_one!($desc, $sigs, $sig, $verify_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) { if let Ok(()) = sig.$verify_method(&key, self.primary.key(), $($verify_args),*) { 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; } } else { // Use hash prefix as heuristic. let key = self.primary.key(); if let Ok(hash) = sig.hash_algo().context() .and_then(|mut ctx| { sig.$hash_method(&mut ctx, key, $($verify_args),*); ctx.into_digest() }) { 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($sig.clone()); // 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; } } } } }); ($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, ) }); } use SignatureType::*; match sig.typ() { DirectKey => { check_one!("primary key", self.primary.self_signatures, sig, verify_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, verify_primary_key_revocation); 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, verify_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, verify_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::AttestationKey => { for binding in self.userids.iter_mut() { check_one!(format!("userid \"{}\"", String::from_utf8_lossy( binding.userid().value())), binding.attestations, sig, verify_userid_attestation, binding.userid()); } for binding in self.user_attributes.iter_mut() { check_one!("user attribute", binding.attestations, sig, verify_user_attribute_attestation, 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, verify_userid_revocation, 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, verify_user_attribute_revocation, 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, verify_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, verify_subkey_revocation, 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}, {:?} 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()); if let Some(i) = unknown_idx { self.unknowns[i].certifications.push(sig); } else { self.bad.push(sig); } } if !self.bad.is_empty() { t!("{}: ignoring {} bad self signatures", self.keyid(), self.bad.len()); } // Split signatures on unknown components. let primary_fp: KeyHandle = self.key_handle(); let primary_keyid = KeyHandle::KeyID(primary_fp.clone().into()); for c in self.unknowns.iter_mut() { parser::split_sigs(&primary_fp, &primary_keyid, c); } // 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(None, 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(None, 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(None, 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. /// /// This routine merges duplicate packets. This is different from /// [`Cert::insert_packets`], which prefers keys in the packets that /// are being merged into the certificate. /// /// [`Cert::insert_packets`]: Cert::insert_packets() /// /// This function is appropriate to merge certificate material /// from untrusted sources like keyservers. If `other` contains /// secret key material, it is ignored. See /// [`Cert::merge_public_and_secret`] on how to merge certificates /// containing secret key material from trusted sources. /// /// [`Cert::merge_public_and_secret`]: Cert::merge_public_and_secret() /// /// # Examples /// /// ``` /// # use sequoia_openpgp as openpgp; /// # use openpgp::cert::prelude::*; /// # /// # fn main() -> openpgp::Result<()> { /// # let (local, _) = /// # CertBuilder::general_purpose(None, 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(()) } /// ``` 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 routine merges duplicate packets. This is different from /// [`Cert::insert_packets`], which prefers keys in the packets that /// are being merged into the certificate. /// /// [`Cert::insert_packets`]: Cert::insert_packets() /// /// 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. /// /// # Examples /// /// ``` /// # use sequoia_openpgp as openpgp; /// # use openpgp::cert::prelude::*; /// # /// # fn main() -> openpgp::Result<()> { /// # let (local, _) = /// # CertBuilder::general_purpose(None, 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(()) } /// ``` 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()); } if ! self.primary.key().has_secret() && 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. /// /// [Known packets that don't belong in a TPK or TSK]: https://tools.ietf.org/html/rfc4880#section-11 /// [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(None, Some("alice@example.org")) /// .generate()?; /// assert!(cert.is_tsk()); /// /// /// // Merge in the revocation certificate. /// assert_eq!(cert.primary_key().self_revocations().count(), 0); /// let cert = cert.insert_packets(rev)?; /// 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 = cert.insert_packets(unknown)?; /// assert_eq!(cert.unknowns().count(), 1); /// for p in cert.unknowns() { /// assert_eq!(p.tag(), tag); /// } /// /// /// // Try and merge a literal data packet. /// let mut lit = Literal::new(DataFormat::Text); /// 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(None, Some("alice@example.org")) /// .generate()?; /// assert!(cert.is_tsk()); /// /// // We just created the key, so all of 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 = cert.insert_packets(pk)?; /// /// // The secret key material is stripped. /// assert!(! cert.primary_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(None, 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 = cert.insert_packets(sig)?; /// /// // 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> where I: IntoIterator, I::Item: Into<Packet>, { let mut combined = self.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 { let p = p.into(); 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) => combined[i] = p, Insert => { // New packet. Add it to combined. combined.push(p); // 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) } /// 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(None, 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.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(None, 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 { let (pk, _sk) = self.primary.component.take_secret(); self.primary.component = pk; let subkeys = self.subkeys.into_iter() .map(|mut kb| { let (pk, _sk) = kb.component.take_secret(); kb.component = pk; kb }) .collect::<Vec<_>>(); self.subkeys = ComponentBundles { bundles: subkeys, }; 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(None, 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.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().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(None, 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(None, 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, }) } } 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> { vec![ p ].try_into() } } 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(None, 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()) } } impl From<Cert> for Vec<Packet> { fn from(cert: Cert) -> Self { cert.into_packets().collect::<Vec<_>>() } } /// An iterator that moves out of a `Cert`. /// /// This structure is created by the `into_iter` method on [`Cert`] /// (provided by the [`IntoIterator`] trait). /// /// [`IntoIterator`]: std::iter::IntoIterator // We can't use a generic type, and due to the use of closures, we // can't write down the concrete type. So, just use a Box. pub struct IntoIter(Box<dyn Iterator<Item=Packet> + Send + Sync>); assert_send_and_sync!(IntoIter); impl Iterator for IntoIter { type Item = Packet; fn next(&mut self) -> Option<Self::Item> { self.0.next() } } impl IntoIterator for Cert { type Item = Packet; type IntoIter = IntoIter; fn into_iter(self) -> Self::IntoIter { IntoIter(Box::new(self.into_packets())) } } /// 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> std::ops::Deref for ValidCert<'a> { type Target = Cert; fn deref(&self) -> &Self::Target { self.cert } } impl<'a> fmt::Display for ValidCert<'a> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{}", self.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(None, 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(None, 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 sp = &StandardPolicy::new(); /// let np = &NullPolicy::new(); /// /// # let (cert, _) = /// # CertBuilder::general_purpose(None, Some("alice@example.org")) /// # .generate()?; /// let vc = cert.with_policy(sp, None)?; /// /// // ... /// /// // Now with a different policy. /// 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.3 of RFC 4880]. /// /// [Section 5.2.3.3 of RFC 4880]: https://tools.ietf.org/html/rfc4880#section-5.2.3.3 /// /// # 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(None, 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())?; /// 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 or not 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.fingerprint(), primary.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://tools.ietf.org/html/rfc4880#section-5.2.3.19 /// /// # 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.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()])?; /// # 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.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()])?; /// # 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.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(None, 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()])?; /// /// // 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(None, Some("alice@example.org")) /// # .generate()?; /// let vc = cert.with_policy(p, None)?; /// let ua = vc.primary_user_attribute(); /// # // We don't have an 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(None, 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)?; /// 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(None, Some("alice@example.org")) /// .generate()?; /// // Make Alice a designated revoker for Bob. /// let (bob, _) = /// CertBuilder::general_purpose(None, 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(None) /// .collect::<Vec<&RevocationKey>>(), /// vec![&(&alice).into()]); /// # Ok(()) } /// ``` pub fn revocation_keys<P>(&self, policy: P) -> Box<dyn Iterator<Item = &'a RevocationKey> + 'a> where P: Into<Option<&'a dyn Policy>>, { self.cert.revocation_keys( policy.into().unwrap_or_else(|| self.policy())) } } macro_rules! impl_pref { ($subpacket:ident, $rt:ty) => { 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. 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_algorithms, &'a [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 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.len(), 1); assert_eq!(cert.userids[0].self_signatures[0].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.len(), 1); assert_eq!(cert.userids[0].self_signatures[0].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[0].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.len(), 1); assert_eq!(cert.userids[0].self_signatures[0].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.len() == 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.len() == 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.len() == 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.len() == 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.len() == 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 of 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.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 of 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.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(), ]); } // lutz's key is a v3 key. // // dkg's includes some v3 signatures. #[test] fn v3_packets() { let dkg = crate::tests::key("dkg.gpg"); let lutz = crate::tests::key("lutz.gpg"); // v3 primary keys are not supported. let cert = Cert::from_bytes(lutz); assert_match!(Error::MalformedCert(_) = cert.err().unwrap().downcast::<Error>().unwrap()); let cert = Cert::from_bytes(dkg); 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::new(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(); 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(None, 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())?; 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())?; 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() ])?; 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() ])?; 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(None, 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())?; 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())?; 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(); assert!(key.has_secret()); let key_a = key.clone().encrypt_secret(&Password::from("a"))? .role_into_primary(); let key_b = key.clone().encrypt_secret(&Password::from("b"))? .role_into_primary(); // Insert variant a. let cert2 = cert.clone().insert_packets(key_a.clone())?; 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())?; 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() ])?; 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() ])?; 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(None, 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( None, 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().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(None, 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(); assert_match!(RevocationStatus::Revoked(_) = cert.revocation_status(p, None)); // Have other revoke cert. let (other, _) = CertBuilder::general_purpose(None, 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(); 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(); 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::Key4; 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 = Key4::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(); 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 trumps // 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(None, 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().len(); 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().len(), 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().len(), 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().len(), 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().len(), 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::Key4; 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 = Key4::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(); 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(); // A time that matches multiple signatures. let direct_signatures = cert.primary_key().bundle().self_signatures(); assert_eq!(cert.primary_key().with_policy(p, *t).unwrap() .direct_key_signature().ok(), direct_signatures.get(*offset)); // 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)); // 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)); // 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 mut 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(); // 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())? } else { cert.insert_packets(key.role_into_subordinate())? }; 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())? } else { cert.insert_packets(key.role_into_subordinate())? }; 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::Key4::generate_ecc(true, Curve::Ed25519)?.into(); let mut primary_pair = primary.clone().into_keypair()?; 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)?; 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)?; 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::Key4::generate_ecc(true, Curve::Ed25519)?.into(); let _signing_subkey_pair = signing_subkey.clone().into_keypair()?; let cert = cert.insert_packets(signing_subkey)?; 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 fake_binding = signature::SignatureBuilder::new( SignatureType::Unknown(SignatureType::SubkeyBinding.into())) .sign_standalone(&mut primary_pair)?; let cert = cert.insert_packets(vec![Packet::from(fake_key), fake_binding.clone().into()])?; assert_eq!(cert.unknowns().count(), 1); assert_eq!(cert.unknowns().next().unwrap().unknown().tag(), packet::Tag::PublicSubkey); assert_eq!(cert.unknowns().next().unwrap().self_signatures().collect::<Vec<_>>(), vec![&fake_binding]); Ok(()) } #[test] 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 primary_sec: Key<_, key::PrimaryRole> = key::Key4::generate_ecc(true, Curve::Ed25519)?.into(); let primary_pub = primary_sec.clone().take_secret().0; let cert_p = Cert::try_from(vec![primary_pub.clone().into()])?; let cert_s = Cert::try_from(vec![primary_sec.clone().into()])?; let cert = cert_p.merge_public_and_secret(cert_s)?; assert!(cert.primary_key().has_secret()); let cert_p = Cert::try_from(vec![primary_pub.clone().into()])?; let cert_s = Cert::try_from(vec![primary_sec.clone().into()])?; let cert = cert_s.merge_public_and_secret(cert_p)?; assert!(cert.primary_key().has_secret()); Ok(()) } /// Tests that secrets are kept when canonicalizing. #[test] fn canonicalizing_keeps_secrets() -> Result<()> { let primary: Key<_, key::PrimaryRole> = key::Key4::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::Key4::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().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(), ])?; let cert_at = cert.with_policy(p, cert.primary_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().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] 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_algorithms(), None); // assert_eq!(userid.key_server_preferences(), // Some(KeyServerPreferences::new(&[]))); assert_eq!(userid.features(), Some(Features::new(&[]).set_mdc())); } 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_algorithms(), None); assert_eq!(userid.key_server_preferences(), Some(KeyServerPreferences::new(&[0x80]))); assert_eq!(userid.features(), Some(Features::new(&[]).set_mdc())); // 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_algorithms(), cert.preferred_aead_algorithms()); 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_algorithms(), None); assert_eq!(userid.key_server_preferences(), Some(KeyServerPreferences::new(&[0x80]))); assert_eq!(userid.features(), Some(Features::new(&[]).set_mdc())); } 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 = &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 = &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( None, 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::Key4, 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 = Key4::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::Key4, 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 = Key4::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); // Specifically, the issuer information should have been added // back by the canonicalization. assert_eq!( cert.userids().next().unwrap().self_signatures().next().unwrap() .unhashed_area().subpackets(SubpacketTag::Issuer).count(), 1); assert_eq!( cert.keys().subkeys().next().unwrap().self_signatures().next().unwrap() .unhashed_area().subpackets(SubpacketTag::Issuer).count(), 1); 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.fingerprint() == cert.primary_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.fingerprint() == vc.primary_key().fingerprint() })); Ok(()) } /// Makes sure that attested key signatures are correctly handled. #[test] fn attested_key_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(), ])?; assert_eq!(bob.with_policy(p, None)?.userids().next().unwrap() .certifications().count(), 1); assert_eq!(bob.with_policy(p, None)?.userids().next().unwrap() .attested_certifications().count(), 0); // Have Bob attest that certification. let attestations = bob.userids().next().unwrap().attest_certifications( p, &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(), ])?; 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().attestations[0], &attestation); assert_eq!(bob.with_policy(p, None)?.userids().next().unwrap() .certifications().count(), 1); assert_eq!(bob.with_policy(p, None)?.userids().next().unwrap() .attested_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().attestations[0], &attestation); assert_eq!(bob_.with_policy(p, None)?.userids().next().unwrap() .attested_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().attestations[0], &attestation); assert_eq!(bob_.with_policy(p, None)?.userids().next().unwrap() .attested_certifications().count(), 1); // Have Bob withdraw any prior attestations. let attestations = bob.userids().next().unwrap().attest_certifications( p, &mut bob_signer, &[])?; assert_eq!(attestations.len(), 1); let attestation = attestations[0].clone(); let bob = bob.insert_packets(vec![ attestation.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().attestations[0], &attestation); assert_eq!(bob.with_policy(p, None)?.userids().next().unwrap() .certifications().count(), 1); assert_eq!(bob.with_policy(p, None)?.userids().next().unwrap() .attested_certifications().count(), 0); Ok(()) } /// Makes sure that attested key signatures are correctly handled. #[test] fn attested_key_signatures_dkgpg() -> Result<()> { const DUMP: bool = false; use crate::{ crypto::hash::Digest, }; 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().attestations.len(), 1); let attestation = &test.userids().next().unwrap().bundle().attestations[0]; if DUMP { for (i, d) in attestation.attested_certifications()?.enumerate() { crate::fmt::hex::Dumper::new(std::io::stderr(), "") .write(d, format!("expected digest {}", i))?; } } let digests: std::collections::HashSet<_> = attestation.attested_certifications()?.collect(); for (i, certification) in test.userids().next().unwrap().certifications().enumerate() { // Hash the certification. let mut h = attestation.hash_algo().context()?; 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() .attested_certifications().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()))?; 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]) })))?; 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]) })))?; 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(()) } } ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-1.7.0/src/crypto/aead.rs������������������������������������������������������������0000644�0000000�0000000�00000072423�00726746425�0016354�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::crypto::mem::secure_cmp; use crate::seal; use crate::parse::Cookie; /// 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 /// Disables authentication checks. /// /// This is DANGEROUS, and is only useful for debugging problems with /// malformed AEAD-encrypted messages. const DANGER_DISABLE_AUTHENTICATION: bool = false; /// 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 { /// Adds associated data `ad`. fn update(&mut self, ad: &[u8]); /// Encrypts one block `src` to `dst`. fn encrypt(&mut self, dst: &mut [u8], src: &[u8]); /// Decrypts one block `src` to `dst`. fn decrypt(&mut self, dst: &mut [u8], src: &[u8]); /// Produce the digest. fn digest(&mut self, digest: &mut [u8]); /// Length of the digest in bytes. fn digest_size(&self) -> usize; } /// 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 { // According to RFC4880bis, Section 5.16.1. EAX => Ok(16), // According to RFC4880bis, Section 5.16.2. OCB => Ok(16), _ => Err(Error::UnsupportedAEADAlgorithm(*self).into()), } } /// Returns the initialization vector size of the AEAD algorithm. pub fn iv_size(&self) -> Result<usize> { use self::AEADAlgorithm::*; match self { // According to RFC4880bis, Section 5.16.1. EAX => Ok(16), // According to RFC4880bis, Section 5.16.2, the IV is "at // least 15 octets long". GnuPG hardcodes 15 in // openpgp_aead_algo_info. OCB => Ok(15), _ => Err(Error::UnsupportedAEADAlgorithm(*self).into()), } } } const AD_PREFIX_LEN: usize = 5; /// A `Read`er for decrypting AEAD-encrypted data. pub struct Decryptor<'a> { // The encrypted data. source: Box<dyn BufferedReader<Cookie> + 'a>, sym_algo: SymmetricAlgorithm, aead: AEADAlgorithm, key: SessionKey, iv: Box<[u8]>, ad: [u8; AD_PREFIX_LEN + 8 + 8], 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<'_>); impl<'a> Decryptor<'a> { /// Instantiate a new AEAD decryptor. /// /// `source` is the source to wrap. pub fn new<R: io::Read + Send + Sync>(version: u8, sym_algo: SymmetricAlgorithm, aead: AEADAlgorithm, chunk_size: usize, iv: &[u8], key: &SessionKey, source: R) -> Result<Self> where R: 'a { Self::from_buffered_reader( version, sym_algo, aead, chunk_size, iv, key, Box::new(buffered_reader::Generic::with_cookie( source, None, Default::default()))) } fn from_buffered_reader(version: u8, sym_algo: SymmetricAlgorithm, aead: AEADAlgorithm, chunk_size: usize, iv: &[u8], key: &SessionKey, source: Box<dyn 'a + BufferedReader<Cookie>>) -> Result<Self> { if !(MIN_CHUNK_SIZE..=MAX_CHUNK_SIZE).contains(&chunk_size) { return Err(Error::InvalidArgument( format!("Invalid AEAD chunk size: {}", chunk_size)).into()); } Ok(Decryptor { source, sym_algo, aead, key: key.clone(), iv: Vec::from(iv).into_boxed_slice(), ad: [ // Prefix. 0xd4, version, sym_algo.into(), aead.into(), chunk_size.trailing_zeros() as u8 - 6, // Chunk index. 0, 0, 0, 0, 0, 0, 0, 0, // Message size. 0, 0, 0, 0, 0, 0, 0, 0, ], digest_size: aead.digest_size()?, chunk_size, chunk_index: 0, bytes_decrypted: 0, buffer: Vec::with_capacity(chunk_size), }) } fn hash_associated_data(&mut self, aead: &mut Box<dyn Aead>, final_digest: bool) { // Prepare the associated data. write_be_u64(&mut self.ad[AD_PREFIX_LEN..AD_PREFIX_LEN + 8], self.chunk_index); if final_digest { write_be_u64(&mut self.ad[AD_PREFIX_LEN + 8..], self.bytes_decrypted); aead.update(&self.ad); } else { aead.update(&self.ad[..AD_PREFIX_LEN + 8]); } } fn make_aead(&mut self, op: CipherOp) -> Result<Box<dyn Aead>> { // The chunk index is XORed into the IV. let chunk_index: [u8; 8] = self.chunk_index.to_be_bytes(); match self.aead { AEADAlgorithm::EAX => { // The nonce for EAX mode is computed by treating the // starting initialization vector as a 16-octet, // big-endian value and exclusive-oring the low eight // octets of it with the chunk index. let iv_len = self.iv.len(); for (i, o) in &mut self.iv[iv_len - 8..].iter_mut() .enumerate() { // The lower eight octets of the associated data // are the big endian representation of the chunk // index. *o ^= chunk_index[i]; } // Instantiate the AEAD cipher. let aead = self.aead.context(self.sym_algo, &self.key, &self.iv, op)?; // Restore the IV. for (i, o) in &mut self.iv[iv_len - 8..].iter_mut() .enumerate() { *o ^= chunk_index[i]; } Ok(aead) } _ => Err(Error::UnsupportedAEADAlgorithm(self.aead).into()), } } // 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> { use std::cmp::Ordering; let mut pos = 0; // Buffer to hold a digest. let mut digest = vec![0u8; self.digest_size]; // 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 { let mut aead = self.make_aead(CipherOp::Decrypt)?; // Digest the associated data. self.hash_associated_data(&mut aead, false); // 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 { // 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(buffer, &chunk[..to_decrypt]); // Check digest. aead.digest(&mut digest); if secure_cmp(&digest[..], &chunk[to_decrypt..]) != Ordering::Equal && ! DANGER_DISABLE_AUTHENTICATION { return Err(Error::ManipulatedMessage.into()); } 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.make_aead(CipherOp::Decrypt)?; self.hash_associated_data(&mut aead, true); aead.digest(&mut digest); let final_digest = self.source.data(final_digest_size)?; if final_digest.len() != final_digest_size || secure_cmp(&digest[..], final_digest) != Ordering::Equal && ! DANGER_DISABLE_AUTHENTICATION { return Err(Error::ManipulatedMessage.into()); } // 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> io::Read for Decryptor<'a> { 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> { 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(version: u8, sym_algo: SymmetricAlgorithm, aead: AEADAlgorithm, chunk_size: usize, iv: &[u8], key: &SessionKey, source: Box<dyn BufferedReader<Cookie> + 'a>, cookie: Cookie) -> Result<Self> { Ok(BufferedReaderDecryptor { reader: buffered_reader::Generic::with_cookie( Decryptor::from_buffered_reader( version, sym_algo, aead, chunk_size, iv, key, source)?, 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.as_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> { inner: Option<W>, sym_algo: SymmetricAlgorithm, aead: AEADAlgorithm, key: SessionKey, iv: Box<[u8]>, ad: [u8; AD_PREFIX_LEN + 8 + 8], 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> where W: io::Write); impl<W: io::Write> Encryptor<W> { /// Instantiate a new AEAD encryptor. pub fn new(version: u8, sym_algo: SymmetricAlgorithm, aead: AEADAlgorithm, chunk_size: usize, iv: &[u8], key: &SessionKey, sink: W) -> Result<Self> { if !(MIN_CHUNK_SIZE..=MAX_CHUNK_SIZE).contains(&chunk_size) { return Err(Error::InvalidArgument( format!("Invalid AEAD chunk size: {}", chunk_size)).into()); } let mut scratch = Vec::with_capacity(chunk_size); unsafe { scratch.set_len(chunk_size); } Ok(Encryptor { inner: Some(sink), sym_algo, aead, key: key.clone(), iv: Vec::from(iv).into_boxed_slice(), ad: [ // Prefix. 0xd4, version, sym_algo.into(), aead.into(), chunk_size.trailing_zeros() as u8 - 6, // Chunk index. 0, 0, 0, 0, 0, 0, 0, 0, // Message size. 0, 0, 0, 0, 0, 0, 0, 0, ], digest_size: aead.digest_size()?, chunk_size, chunk_index: 0, bytes_encrypted: 0, buffer: Vec::with_capacity(chunk_size), scratch, }) } fn hash_associated_data(&mut self, aead: &mut Box<dyn Aead>, final_digest: bool) { // Prepare the associated data. write_be_u64(&mut self.ad[AD_PREFIX_LEN..AD_PREFIX_LEN + 8], self.chunk_index); if final_digest { write_be_u64(&mut self.ad[AD_PREFIX_LEN + 8..], self.bytes_encrypted); aead.update(&self.ad); } else { aead.update(&self.ad[..AD_PREFIX_LEN + 8]); } } fn make_aead(&mut self, op: CipherOp) -> Result<Box<dyn Aead>> { // The chunk index is XORed into the IV. let chunk_index: [u8; 8] = self.chunk_index.to_be_bytes(); match self.aead { AEADAlgorithm::EAX => { // The nonce for EAX mode is computed by treating the // starting initialization vector as a 16-octet, // big-endian value and exclusive-oring the low eight // octets of it with the chunk index. let iv_len = self.iv.len(); for (i, o) in &mut self.iv[iv_len - 8..].iter_mut() .enumerate() { // The lower eight octets of the associated data // are the big endian representation of the chunk // index. *o ^= chunk_index[i]; } // Instantiate the AEAD cipher. let aead = self.aead.context(self.sym_algo, &self.key, &self.iv, op)?; // Restore the IV. for (i, o) in &mut self.iv[iv_len - 8..].iter_mut() .enumerate() { *o ^= chunk_index[i]; } Ok(aead) } _ => Err(Error::UnsupportedAEADAlgorithm(self.aead).into()), } } // 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.make_aead(CipherOp::Encrypt)?; self.hash_associated_data(&mut aead, false); let inner = self.inner.as_mut().unwrap(); // Encrypt the chunk. aead.encrypt(&mut self.scratch, &self.buffer); self.bytes_encrypted += self.scratch.len() as u64; self.chunk_index += 1; crate::vec_truncate(&mut self.buffer, 0); inner.write_all(&self.scratch)?; // Write digest. aead.digest(&mut self.scratch[..self.digest_size]); inner.write_all(&self.scratch[..self.digest_size])?; } } // 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.make_aead(CipherOp::Encrypt)?; self.hash_associated_data(&mut aead, false); let inner = self.inner.as_mut().unwrap(); // Encrypt the chunk. aead.encrypt(&mut self.scratch, chunk); self.bytes_encrypted += self.scratch.len() as u64; self.chunk_index += 1; inner.write_all(&self.scratch)?; // Write digest. aead.digest(&mut self.scratch[..self.digest_size]); inner.write_all(&self.scratch[..self.digest_size])?; } 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.make_aead(CipherOp::Encrypt)?; self.hash_associated_data(&mut aead, false); // Encrypt the chunk. unsafe { self.scratch.set_len(self.buffer.len()) } aead.encrypt(&mut self.scratch, &self.buffer); self.bytes_encrypted += self.scratch.len() as u64; self.chunk_index += 1; crate::vec_truncate(&mut self.buffer, 0); inner.write_all(&self.scratch)?; // Write digest. unsafe { self.scratch.set_len(self.digest_size) } aead.digest(&mut self.scratch[..self.digest_size]); inner.write_all(&self.scratch[..self.digest_size])?; } // Write final digest. let mut aead = self.make_aead(CipherOp::Decrypt)?; self.hash_associated_data(&mut aead, true); aead.digest(&mut self.scratch[..self.digest_size]); 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> io::Write for Encryptor<W> { 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> 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::{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()) { if cfg!(feature = "crypto-rust") && sym_algo == &SymmetricAlgorithm::Twofish { eprintln!("XXX: Skipping Twofish until Twofish \ implements Clone"); continue; } for aead in [ AEADAlgorithm::EAX, AEADAlgorithm::OCB, ].iter().filter(|algo| algo.is_supported()) { let version = 1; let chunk_size = 64; let mut key = vec![0; sym_algo.key_size().unwrap()]; crate::crypto::random(&mut key); let key: SessionKey = key.into(); let mut iv = vec![0; aead.iv_size().unwrap()]; crate::crypto::random(&mut iv); let mut ciphertext = Vec::new(); { let mut encryptor = Encryptor::new(version, *sym_algo, *aead, chunk_size, &iv, &key, &mut ciphertext) .unwrap(); encryptor.write_all(crate::tests::manifesto()).unwrap(); } let mut plaintext = Vec::new(); { let mut decryptor = Decryptor::new(version, *sym_algo, *aead, chunk_size, &iv, &key, Cursor::new(&ciphertext)) .unwrap(); decryptor.read_to_end(&mut plaintext).unwrap(); } assert_eq!(&plaintext[..], crate::tests::manifesto()); } } } } ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-1.7.0/src/crypto/asymmetric.rs������������������������������������������������������0000644�0000000�0000000�00000013113�00726746425�0017626�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������//! Asymmetric crypto operations. use crate::packet::{self, key, Key}; use crate::crypto::SessionKey; use crate::crypto::mpi; use crate::types::HashAlgorithm; use crate::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>; /// 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 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 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>; } /// 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(None, 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 } } �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-1.7.0/src/crypto/backend/cng/aead.rs������������������������������������������������0000644�0000000�0000000�00000011017�00726746425�0020502�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������//! Implementation of AEAD using Windows CNG API. use crate::{Error, Result}; use crate::crypto::aead::{Aead, CipherOp}; use crate::seal; use crate::types::{AEADAlgorithm, SymmetricAlgorithm}; use eax::online::{EaxOnline, Encrypt, Decrypt}; use win_crypto_ng::symmetric::{BlockCipherKey, Aes}; use win_crypto_ng::symmetric::block_cipher::generic_array::{GenericArray, ArrayLength}; use win_crypto_ng::symmetric::block_cipher::generic_array::typenum::{U128, U192, U256}; trait GenericArrayExt { const LEN: usize; } impl<T, N: ArrayLength<T>> GenericArrayExt for GenericArray<T, N> { const LEN: usize = N::USIZE; } impl AEADAlgorithm { pub(crate) fn context( &self, sym_algo: SymmetricAlgorithm, key: &[u8], nonce: &[u8], op: CipherOp, ) -> Result<Box<dyn Aead>> { let nonce = GenericArray::from_slice(nonce); match self { AEADAlgorithm::EAX => match sym_algo { | SymmetricAlgorithm::AES128 => { let key = GenericArray::from_slice(key); Ok(match op { CipherOp::Encrypt => Box::new(EaxOnline::<BlockCipherKey<Aes, U128>, Encrypt>::with_key_and_nonce(key, nonce)), CipherOp::Decrypt => Box::new(EaxOnline::<BlockCipherKey<Aes, U128>, Decrypt>::with_key_and_nonce(key, nonce)), }) } SymmetricAlgorithm::AES192 => { let key = GenericArray::from_slice(key); Ok(match op { CipherOp::Encrypt => Box::new(EaxOnline::<BlockCipherKey<Aes, U192>, Encrypt>::with_key_and_nonce(key, nonce)), CipherOp::Decrypt => Box::new(EaxOnline::<BlockCipherKey<Aes, U192>, Decrypt>::with_key_and_nonce(key, nonce)), }) } SymmetricAlgorithm::AES256 => { let key = GenericArray::from_slice(key); Ok(match op { CipherOp::Encrypt => Box::new(EaxOnline::<BlockCipherKey<Aes, U256>, Encrypt>::with_key_and_nonce(key, nonce)), CipherOp::Decrypt => Box::new(EaxOnline::<BlockCipherKey<Aes, U256>, Decrypt>::with_key_and_nonce(key, nonce)), }) } _ => Err(Error::UnsupportedSymmetricAlgorithm(sym_algo).into()), }, _ => Err(Error::UnsupportedAEADAlgorithm(self.clone()).into()), } } } macro_rules! impl_aead { ($($type: ty),*) => { $( impl Aead for EaxOnline<$type, Encrypt> { fn update(&mut self, ad: &[u8]) { self.update_assoc(ad) } fn digest_size(&self) -> usize { <eax::Tag as GenericArrayExt>::LEN } fn digest(&mut self, digest: &mut [u8]) { let tag = self.tag_clone(); digest[..tag.len()].copy_from_slice(&tag[..]); } fn encrypt(&mut self, dst: &mut [u8], src: &[u8]) { let len = core::cmp::min(dst.len(), src.len()); dst[..len].copy_from_slice(&src[..len]); EaxOnline::<$type, Encrypt>::encrypt(self, &mut dst[..len]) } fn decrypt(&mut self, _dst: &mut [u8], _src: &[u8]) { panic!("AEAD decryption called in the encryption context") } } impl seal::Sealed for EaxOnline<$type, Encrypt> {} )* $( impl Aead for EaxOnline<$type, Decrypt> { fn update(&mut self, ad: &[u8]) { self.update_assoc(ad) } fn digest_size(&self) -> usize { <eax::Tag as GenericArrayExt>::LEN } fn digest(&mut self, digest: &mut [u8]) { let tag = self.tag_clone(); digest[..tag.len()].copy_from_slice(&tag[..]); } fn encrypt(&mut self, _dst: &mut [u8], _src: &[u8]) { panic!("AEAD encryption called in the decryption context") } fn decrypt(&mut self, dst: &mut [u8], src: &[u8]) { let len = core::cmp::min(dst.len(), src.len()); dst[..len].copy_from_slice(&src[..len]); self.decrypt_unauthenticated_hazmat(&mut dst[..len]) } } impl seal::Sealed for EaxOnline<$type, Decrypt> {} )* }; } impl_aead!(BlockCipherKey<Aes, U128>, BlockCipherKey<Aes, U192>, BlockCipherKey<Aes, U256>); �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-1.7.0/src/crypto/backend/cng/asymmetric.rs������������������������������������������0000644�0000000�0000000�00000127673�00726746425�0022005�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::{Decryptor, KeyPair, Signer}; use crate::crypto::mem::Protected; use crate::crypto::mpi; 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, SymmetricAlgorithm}; use crate::types::{Curve, HashAlgorithm}; use num_bigint_dig::{traits::ModInverse, BigInt, BigUint}; use win_crypto_ng as cng; const CURVE25519_SIZE: usize = 32; 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 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)] self.secret().map(|secret| { 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 4880], // 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 4880]: // https://tools.ietf.org/html/rfc4880#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_bits = curve.bits().ok_or_else(|| Error::UnsupportedEllipticCurve(curve.clone()) )?; let curve_bytes = (curve_bits + 7) / 8; 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::EdDSA, mpi::PublicKey::EdDSA { curve, q }, mpi::SecretKeyMaterial::EdDSA { scalar }, ) => match curve { Curve::Ed25519 => { // CNG doesn't support EdDSA, use ed25519-dalek instead use ed25519_dalek::{Keypair, Signer}; use ed25519_dalek::{PUBLIC_KEY_LENGTH, SECRET_KEY_LENGTH}; let (public, ..) = q.decode_point(&Curve::Ed25519)?; // MPI::decode_point ensures we got the right length. assert_eq!(public.len(), PUBLIC_KEY_LENGTH); // It's expected for the private key to be exactly // SECRET_KEY_LENGTH bytes long but OpenPGP allows leading // zeros to be stripped. // Padding has to be unconditional; otherwise we have a // secret-dependent branch. let mut keypair = Protected::from( vec![0u8; SECRET_KEY_LENGTH + PUBLIC_KEY_LENGTH] ); keypair[..SECRET_KEY_LENGTH] .copy_from_slice( &scalar.value_padded(SECRET_KEY_LENGTH)); keypair[SECRET_KEY_LENGTH..] .copy_from_slice(&public); let pair = Keypair::from_bytes(&keypair).unwrap(); let sig = pair.sign(digest).to_bytes(); // https://tools.ietf.org/html/rfc8032#section-5.1.6 let (r, s) = sig.split_at(sig.len() / 2); mpi::Signature::EdDSA { r: mpi::MPI::new(r), s: mpi::MPI::new(s), } }, _ => return Err( Error::UnsupportedEllipticCurve(curve.clone()).into()), }, (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()]); 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: std::cmp::min(q.value().len(), 32) as u32, cbKey: y.len() as u32, cbSeedLength: seed.len() as u32, hashAlgorithm: hash, standardVersion: 1, // FIPS 186-3 }, &DsaKeyPrivateV2Payload { seed: &seed, group: q.value(), modulus: p.value(), generator: g.value(), public: &y, priv_exp: x.value(), }, )) }, }; 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 Decryptor for KeyPair { fn public(&self) -> &Key<key::PublicParts, key::UnspecifiedRole> { KeyPair::public(self) } /// Creates a signature over the `digest` produced by `hash_algo`. fn decrypt( &mut self, ciphertext: &mpi::Ciphertext, plaintext_len: Option<usize>, ) -> Result<SessionKey> { use crate::PublicKeyAlgorithm::*; self.secret().map( |secret| 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)?, (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 fn encrypt(&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), algo => Err(Error::UnsupportedPublicKeyAlgorithm(algo).into()), } } /// Verifies the given signature. pub fn verify(&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 4880], // 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 4880]: // https://tools.ietf.org/html/rfc4880#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()), } }, (mpi::PublicKey::EdDSA { curve, q }, mpi::Signature::EdDSA { r, s }) => match curve { Curve::Ed25519 => { // CNG doesn't support EdDSA, use ed25519-dalek instead use ed25519_dalek::{PublicKey, Signature, SIGNATURE_LENGTH}; use ed25519_dalek::{Verifier}; let (public, ..) = q.decode_point(&Curve::Ed25519)?; assert_eq!(public.len(), 32); let key = PublicKey::from_bytes(public).map_err(|e| { Error::InvalidKey(e.to_string()) })?; // ed25519 expects full-sized signatures but OpenPGP allows // for stripped leading zeroes, pad each part with zeroes. let mut sig_bytes = [0u8; SIGNATURE_LENGTH]; // We need to zero-pad them at the front, because // the MPI encoding drops leading zero bytes. let half = SIGNATURE_LENGTH / 2; sig_bytes[..half].copy_from_slice( &r.value_padded(half).map_err(bad)?); sig_bytes[half..].copy_from_slice( &s.value_padded(half).map_err(bad)?); let signature = Signature::from(sig_bytes); key.verify(digest, &signature) .map(|_| true) .map_err(|e| Error::BadSignature(e.to_string()))? }, _ => 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 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. 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<SystemTime>>, { use cng::asymmetric::{AsymmetricAlgorithm, AsymmetricAlgorithmId, Ecdh, Private}; use cng::asymmetric::{AsymmetricKey, Export}; use cng::asymmetric::ecc::{Curve25519, NamedCurve}; let provider = AsymmetricAlgorithm::open( AsymmetricAlgorithmId::Ecdh(NamedCurve::Curve25519) )?; let key = AsymmetricKey::<Ecdh<Curve25519>, Private>::import_from_parts( &provider, private_key )?; let blob = key.export()?; // Mark MPI as compressed point with 0x40 prefix. See // https://tools.ietf.org/html/draft-ietf-openpgp-rfc4880bis-07#section-13.2. let mut public = [0u8; 1 + CURVE25519_SIZE]; public[0] = 0x40; public[1..].copy_from_slice(blob.x()); // Reverse the scalar. See // https://lists.gnupg.org/pipermail/gnupg-devel/2018-February/033437.html. let mut private = blob.d().to_vec(); private.reverse(); Self::with_secret( ctime.into().unwrap_or_else(crate::now), PublicKeyAlgorithm::ECDH, mpi::PublicKey::ECDH { curve: Curve::Cv25519, hash: hash.into().unwrap_or(HashAlgorithm::SHA512), sym: sym.into().unwrap_or(SymmetricAlgorithm::AES256), q: mpi::MPI::new(&public), }, mpi::SecretKeyMaterial::ECDH { scalar: private.into() }.into() ) } /// Creates a new OpenPGP secret key packet for an existing Ed25519 key. /// /// The key will have it's 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<SystemTime>>, { // CNG doesn't support EdDSA, use ed25519-dalek instead use ed25519_dalek::{PublicKey, SecretKey}; let private = SecretKey::from_bytes(private_key).map_err(|e| { Error::InvalidKey(e.to_string()) })?; // Mark MPI as compressed point with 0x40 prefix. See // https://tools.ietf.org/html/draft-ietf-openpgp-rfc4880bis-07#section-13.2. let mut public = [0u8; 1 + CURVE25519_SIZE]; public[0] = 0x40; public[1..].copy_from_slice(Into::<PublicKey>::into(&private).as_bytes()); Self::with_secret( ctime.into().unwrap_or_else(crate::now), PublicKeyAlgorithm::EdDSA, mpi::PublicKey::EdDSA { curve: Curve::Ed25519, q: mpi::MPI::new(&public) }, mpi::SecretKeyMaterial::EdDSA { scalar: mpi::MPI::new(&private_key).into(), }.into() ) } /// 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 it's 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) = if p < q { (p, q) } else { (q, p) }; // 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: mpi::MPI::new(d).into(), p: mpi::MPI::new(p).into(), q: mpi::MPI::new(q).into(), u: mpi::MPI::new(&u.to_bytes_be()).into(), }.into() ) } /// Generates a new RSA key with a public modulos 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::MPI::new(blob.prime1()); let q = mpi::MPI::new(blob.prime2()); // RSA prime generation in CNG returns them in arbitrary order but // RFC 4880 expects `p < q` let (p, q) = if p < q { (p, q) } else { (q, p) }; // 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.into(), q: q.into(), d: mpi::MPI::new(blob.priv_exp()).into(), u: mpi::MPI::new(&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 fn generate_ecc(for_signing: bool, curve: Curve) -> Result<Self> { use crate::PublicKeyAlgorithm::*; use cng::asymmetric::{ecc, Export}; use cng::asymmetric::{AsymmetricKey, AsymmetricAlgorithmId, Ecdh}; let (algo, public, private) = match (curve.clone(), for_signing) { (Curve::NistP256, ..) | (Curve::NistP384, ..) | (Curve::NistP521, ..) => { let (cng_curve, hash) = match curve { Curve::NistP256 => (ecc::NamedCurve::NistP256, HashAlgorithm::SHA256), Curve::NistP384 => (ecc::NamedCurve::NistP384, HashAlgorithm::SHA384), Curve::NistP521 => (ecc::NamedCurve::NistP521, HashAlgorithm::SHA512), _ => 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::MPI::new(blob.d()); if for_signing { ( ECDSA, mpi::PublicKey::ECDSA { curve, q }, mpi::SecretKeyMaterial::ECDSA { scalar: scalar.into() }, ) } else { let sym = SymmetricAlgorithm::AES256; ( ECDH, mpi::PublicKey::ECDH { curve, q, hash, sym }, mpi::SecretKeyMaterial::ECDH { scalar: scalar.into() }, ) } }, (Curve::Cv25519, false) => { let blob = AsymmetricKey::builder(Ecdh(ecc::Curve25519)).build()?.export()?; // Mark MPI as compressed point with 0x40 prefix. See // https://tools.ietf.org/html/draft-ietf-openpgp-rfc4880bis-07#section-13.2. let mut public = [0u8; 1 + CURVE25519_SIZE]; public[0] = 0x40; public[1..].copy_from_slice(blob.x()); // Reverse the scalar. See // https://lists.gnupg.org/pipermail/gnupg-devel/2018-February/033437.html. let mut private: Protected = blob.d().into(); private.reverse(); ( ECDH, mpi::PublicKey::ECDH { curve, q: mpi::MPI::new(&public), hash: HashAlgorithm::SHA256, sym: SymmetricAlgorithm::AES256, }, mpi::SecretKeyMaterial::ECDH { scalar: private.into() } ) }, (Curve::Ed25519, true) => { // CNG doesn't support EdDSA, use ed25519-dalek instead use ed25519_dalek::Keypair; let mut rng = cng::random::RandomNumberGenerator::system_preferred(); let Keypair { public, secret } = Keypair::generate(&mut rng); let secret: Protected = secret.as_bytes().as_ref().into(); // Mark MPI as compressed point with 0x40 prefix. See // https://tools.ietf.org/html/draft-ietf-openpgp-rfc4880bis-07#section-13.2. let mut compressed_public = [0u8; 1 + CURVE25519_SIZE]; compressed_public[0] = 0x40; compressed_public[1..].copy_from_slice(public.as_bytes()); ( EdDSA, mpi::PublicKey::EdDSA { curve, q: mpi::MPI::new(&compressed_public) }, mpi::SecretKeyMaterial::EdDSA { scalar: secret.into() }, ) }, // TODO: Support Brainpool curves (curve, ..) => { return Err(Error::UnsupportedEllipticCurve(curve).into()); } }; Self::with_secret(crate::now(), algo, public, private.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 } #[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-1.7.0/src/crypto/backend/cng/ecdh.rs������������������������������������������������0000644�0000000�0000000�00000030210�00726746425�0020507�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, Curve25519}; use cng::key_blob::{EccKeyPublicPayload, EccKeyPrivatePayload}; 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<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 => { // Obtain the authenticated recipient public key R let R = q.decode_point(curve)?.0; let provider = AsymmetricAlgorithm::open(AsymmetricAlgorithmId::Ecdh(NamedCurve::Curve25519))?; let recipient_key = AsymmetricKey::<Ecdh<Curve25519>, Public>::import_from_parts( &provider, R, )?; // Generate an ephemeral key pair {v, V=vG} let ephemeral = AsymmetricKey::builder(Ecdh(Curve25519)).build().unwrap(); // Compute the public key. We need to add an encoding // octet in front of the key. let blob = ephemeral.export().unwrap(); let mut VB = [0; 33]; VB[0] = 0x40; VB[1..].copy_from_slice(blob.x()); let VB = MPI::new(&VB); // Compute the shared point S = vR; let secret = cng::asymmetric::agreement::secret_agreement(&ephemeral, &recipient_key)?; let mut S = Protected::from(secret.derive_raw()?); // Returned secret is little-endian, flip it to big-endian S.reverse(); encrypt_wrap(recipient, session_key, VB, &S) } 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 Nettle Curve::BrainpoolP256 | 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, ) -> 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 => { // Get the public part V of the ephemeral key. let V = e.decode_point(curve)?.0; let provider = AsymmetricAlgorithm::open(AsymmetricAlgorithmId::Ecdh(NamedCurve::Curve25519))?; let V = AsymmetricKey::<Ecdh<Curve25519>, Public>::import_from_parts( &provider, V, )?; let mut scalar = scalar.value_padded(32); // Reverse the scalar. See // https://lists.gnupg.org/pipermail/gnupg-devel/2018-February/033437.html. scalar.reverse(); // Some bits must be clamped. Usually, this is done // during key creation. However, if that is not done, we // should do that before handing it to CNG. See // 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. scalar[0] &= 0b1111_1000; scalar[CURVE25519_SIZE - 1] &= !0b1000_0000; scalar[CURVE25519_SIZE - 1] |= 0b0100_0000; let r = AsymmetricKey::<Ecdh<Curve25519>, Private>::import_from_parts( &provider, &scalar, )?; 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::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, &S, ciphertext) } ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-1.7.0/src/crypto/backend/cng/hash.rs������������������������������������������������0000644�0000000�0000000�00000010654�00726746425�0020541�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������use core::convert::{TryFrom, TryInto}; 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 algo(&self) -> HashAlgorithm { self.0.lock().expect("Mutex not to be poisoned") .hash_algorithm().expect("CNG to not fail internally") .try_into() .expect("We created the object, algo is representable") } fn digest_size(&self) -> usize { self.0.lock().expect("Mutex not to be poisoned") .hash_size().expect("CNG to not fail internally") } 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, algo => Err(Error::UnsupportedHashAlgorithm(algo))?, }) } } 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, _ => 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-1.7.0/src/crypto/backend/cng/symmetric.rs�������������������������������������������0000644�0000000�0000000�00000015055�00726746425�0021632�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> { 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. /// /// 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 fn is_supported(&self) -> bool { use self::SymmetricAlgorithm::*; match self { AES128 | AES192 | AES256 | TripleDES => true, _ => false, } } /// Length of a key for this algorithm in bytes. /// /// Fails if the crypto backend does not support this algorithm. pub fn key_size(self) -> Result<usize> { Ok(match self { SymmetricAlgorithm::TripleDES => 24, SymmetricAlgorithm::AES128 => 16, SymmetricAlgorithm::AES192 => 24, SymmetricAlgorithm::AES256 => 32, _ => Err(UnsupportedAlgorithm(self))?, }) } /// Length of a block for this algorithm in bytes. /// /// Fails if the crypto backend does not support this algorithm. pub fn block_size(self) -> Result<usize> { Ok(match self { SymmetricAlgorithm::TripleDES => 8, SymmetricAlgorithm::AES128 => 16, SymmetricAlgorithm::AES192 => 16, SymmetricAlgorithm::AES256 => 16, _ => Err(UnsupportedAlgorithm(self))?, }) } /// 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-1.7.0/src/crypto/backend/cng.rs�����������������������������������������������������0000644�0000000�0000000�00000002651�00726746425�0017614�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 symmetric; /// Fills the given buffer with random data. pub fn random<B: AsMut<[u8]>>(mut buf: B) { RandomNumberGenerator::system_preferred() .gen_random(buf.as_mut()) .expect("system-preferred RNG not to fail") } impl PublicKeyAlgorithm { pub(crate) fn is_supported_by_backend(&self) -> bool { use PublicKeyAlgorithm::*; #[allow(deprecated)] match &self { RSAEncryptSign | RSAEncrypt | RSASign | DSA | ECDH | ECDSA | EdDSA => true, ElGamalEncrypt | ElGamalEncryptSign | Private(_) | Unknown(_) => false, } } } impl Curve { pub(crate) fn is_supported_by_backend(&self) -> bool { use self::Curve::*; match &self { NistP256 | NistP384 | NistP521 | Ed25519 | Cv25519 => true, BrainpoolP256 | BrainpoolP512 | Unknown(_) => false, } } } impl AEADAlgorithm { pub(crate) fn is_supported_by_backend(&self) -> bool { use self::AEADAlgorithm::*; match &self { EAX => true, OCB | Private(_) | Unknown(_) => false, } } } ���������������������������������������������������������������������������������������sequoia-openpgp-1.7.0/src/crypto/backend/nettle/aead.rs���������������������������������������������0000644�0000000�0000000�00000004454�00726746425�0021235�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������//! Implementation of AEAD using Nettle cryptographic library. use nettle::{aead, cipher}; use crate::{Error, Result}; use crate::crypto::aead::{Aead, CipherOp}; use crate::seal; use crate::types::{AEADAlgorithm, SymmetricAlgorithm}; impl<T: nettle::aead::Aead> seal::Sealed for T {} impl<T: nettle::aead::Aead> Aead for T { fn update(&mut self, ad: &[u8]) { self.update(ad) } fn encrypt(&mut self, dst: &mut [u8], src: &[u8]) { self.encrypt(dst, src) } fn decrypt(&mut self, dst: &mut [u8], src: &[u8]) { self.decrypt(dst, src) } fn digest(&mut self, digest: &mut [u8]) { self.digest(digest) } fn digest_size(&self) -> usize { self.digest_size() } } impl AEADAlgorithm { pub(crate) fn context( &self, sym_algo: SymmetricAlgorithm, key: &[u8], nonce: &[u8], _op: CipherOp, ) -> Result<Box<dyn Aead>> { match self { AEADAlgorithm::EAX => match sym_algo { SymmetricAlgorithm::AES128 => Ok(Box::new( aead::Eax::<cipher::Aes128>::with_key_and_nonce(key, nonce)?, )), SymmetricAlgorithm::AES192 => Ok(Box::new( aead::Eax::<cipher::Aes192>::with_key_and_nonce(key, nonce)?, )), SymmetricAlgorithm::AES256 => Ok(Box::new( aead::Eax::<cipher::Aes256>::with_key_and_nonce(key, nonce)?, )), SymmetricAlgorithm::Twofish => Ok(Box::new( aead::Eax::<cipher::Twofish>::with_key_and_nonce(key, nonce)?, )), SymmetricAlgorithm::Camellia128 => Ok(Box::new( aead::Eax::<cipher::Camellia128>::with_key_and_nonce(key, nonce)?, )), SymmetricAlgorithm::Camellia192 => Ok(Box::new( aead::Eax::<cipher::Camellia192>::with_key_and_nonce(key, nonce)?, )), SymmetricAlgorithm::Camellia256 => Ok(Box::new( aead::Eax::<cipher::Camellia256>::with_key_and_nonce(key, nonce)?, )), _ => Err(Error::UnsupportedSymmetricAlgorithm(sym_algo).into()), }, _ => Err(Error::UnsupportedAEADAlgorithm(*self).into()), } } } ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-1.7.0/src/crypto/backend/nettle/asymmetric.rs���������������������������������������0000644�0000000�0000000�00000055174�00726746425�0022525�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 nettle::{curve25519, ecc, ecdh, ecdsa, ed25519, dsa, rsa, random::Yarrow}; use crate::{Error, Result}; use crate::packet::{key, Key}; use crate::crypto::asymmetric::{KeyPair, Decryptor, Signer}; use crate::crypto::mpi::{self, MPI, PublicKey}; use crate::crypto::SessionKey; use crate::types::{Curve, HashAlgorithm}; 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::PublicKeyAlgorithm::*; let mut rng = Yarrow::default(); self.secret().map(|secret| { #[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 4880], // 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 4880]: // https://tools.ietf.org/html/rfc4880#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()), }) }, (EdDSA, &PublicKey::EdDSA { ref curve, ref q }, &mpi::SecretKeyMaterial::EdDSA { ref scalar }) => match curve { Curve::Ed25519 => { let public = q.decode_point(&Curve::Ed25519)?.0; let mut sig = vec![0; ed25519::ED25519_SIGNATURE_SIZE]; // Nettle expects the private key to be exactly // ED25519_KEY_SIZE bytes long but OpenPGP allows leading // zeros to be stripped. // Padding has to be unconditional; otherwise we have a // secret-dependent branch. let sec = scalar.value_padded(ed25519::ED25519_KEY_SIZE); ed25519::sign(public, &sec[..], digest, &mut sig)?; Ok(mpi::Signature::EdDSA { r: MPI::new(&sig[..ed25519::ED25519_KEY_SIZE]), s: MPI::new(&sig[ed25519::ED25519_KEY_SIZE..]), }) }, _ => Err( Error::UnsupportedEllipticCurve(curve.clone()).into()), }, (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 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::PublicKeyAlgorithm::*; self.secret().map( |secret| 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{ .. }) => return Err( Error::UnsupportedPublicKeyAlgorithm(ElGamalEncrypt).into()), (PublicKey::ECDH{ .. }, mpi::SecretKeyMaterial::ECDH { .. }, mpi::Ciphertext::ECDH { .. }) => crate::crypto::ecdh::decrypt(self.public(), secret, ciphertext)?, (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 fn encrypt(&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), algo => Err(Error::UnsupportedPublicKeyAlgorithm(algo).into()), } } /// Verifies the given signature. pub fn verify(&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 key = rsa::PublicKey::new(n.value(), e.value())?; // As described in [Section 5.2.2 and 5.2.3 of RFC 4880], // 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 4880]: // https://tools.ietf.org/html/rfc4880#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::EdDSA { curve, q }, Signature::EdDSA { r, s }) => match curve { Curve::Ed25519 => { if q.value().get(0).map(|&b| b != 0x40).unwrap_or(true) { return Err(Error::MalformedPacket( "Invalid point encoding".into()).into()); } // OpenPGP encodes R and S separately, but our // cryptographic library expects them to be // concatenated. let mut signature = Vec::with_capacity(ed25519::ED25519_SIGNATURE_SIZE); // We need to zero-pad them at the front, because // the MPI encoding drops leading zero bytes. let half = ed25519::ED25519_SIGNATURE_SIZE / 2; signature.extend_from_slice( &r.value_padded(half).map_err(bad)?); signature.extend_from_slice( &s.value_padded(half).map_err(bad)?); // Let's see if we got it right. assert_eq!(signature.len(), ed25519::ED25519_SIGNATURE_SIZE); ed25519::verify(&q.value()[1..], digest, &signature)? }, _ => return Err(Error::UnsupportedEllipticCurve(curve.clone()).into()), }, (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::crypto::mem::Protected; use crate::packet::key::{Key4, SecretParts}; use crate::types::{PublicKeyAlgorithm, SymmetricAlgorithm}; impl<R> Key4<SecretParts, R> where R: key::KeyRole, { /// 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 it's creation date set to /// `ctime` or the current time if `None` is given. 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<SystemTime>> { let mut public_key = [0; curve25519::CURVE25519_SIZE]; curve25519::mul_g(&mut public_key, private_key).unwrap(); let mut private_key = Vec::from(private_key); private_key.reverse(); Self::with_secret( ctime.into().unwrap_or_else(crate::now), PublicKeyAlgorithm::ECDH, mpi::PublicKey::ECDH { curve: Curve::Cv25519, hash: hash.into().unwrap_or(HashAlgorithm::SHA512), sym: sym.into().unwrap_or(SymmetricAlgorithm::AES256), q: 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 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 it's 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<SystemTime>> { let mut public_key = [0; ed25519::ED25519_KEY_SIZE]; ed25519::public_key(&mut public_key, private_key).unwrap(); Self::with_secret( ctime.into().unwrap_or_else(crate::now), PublicKeyAlgorithm::EdDSA, mpi::PublicKey::EdDSA { curve: Curve::Ed25519, q: MPI::new_compressed_point(&public_key), }, mpi::SecretKeyMaterial::EdDSA { scalar: mpi::MPI::new(private_key).into(), }.into()) } /// 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 it's 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>> { 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: mpi::MPI::new(d).into(), p: mpi::MPI::new(&a[..]).into(), q: mpi::MPI::new(&b[..]).into(), u: mpi::MPI::new(&c[..]).into(), }.into()) } /// Generates a new RSA key with a public modulos 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: MPI::new(&*private.d()).into(), p: MPI::new(&*p).into(), q: MPI::new(&*q).into(), u: MPI::new(&*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 fn generate_ecc(for_signing: bool, curve: Curve) -> Result<Self> { use crate::PublicKeyAlgorithm::*; let mut rng = Yarrow::default(); let (mpis, secret, pk_algo) = match (curve.clone(), for_signing) { (Curve::Ed25519, true) => { let mut public = [0; ed25519::ED25519_KEY_SIZE]; let private: Protected = ed25519::private_key(&mut rng).into(); ed25519::public_key(&mut public, &private)?; let public_mpis = PublicKey::EdDSA { curve: Curve::Ed25519, q: MPI::new_compressed_point(&public), }; let private_mpis = mpi::SecretKeyMaterial::EdDSA { scalar: private.into(), }; let sec = private_mpis.into(); (public_mpis, sec, EdDSA) } (Curve::Cv25519, false) => { let mut public = [0; curve25519::CURVE25519_SIZE]; let mut private: Protected = curve25519::private_key(&mut rng).into(); curve25519::mul_g(&mut public, &private)?; // Reverse the scalar. See // https://lists.gnupg.org/pipermail/gnupg-devel/2018-February/033437.html. private.reverse(); let public_mpis = PublicKey::ECDH { curve: Curve::Cv25519, q: MPI::new_compressed_point(&public), hash: HashAlgorithm::SHA256, sym: SymmetricAlgorithm::AES256, }; let private_mpis = mpi::SecretKeyMaterial::ECDH { scalar: private.into(), }; let sec = private_mpis.into(); (public_mpis, sec, ECDH) } (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: MPI::new(&private.as_bytes()).into(), }; let sec = private_mpis.into(); (public_mpis, sec, ECDSA) } (Curve::NistP256, false) | (Curve::NistP384, false) | (Curve::NistP521, false) => { let (private, hash, field_sz) = match curve { Curve::NistP256 => { let pv = ecc::Scalar::new_random::<ecc::Secp256r1, _>(&mut rng); (pv, HashAlgorithm::SHA256, 256) } Curve::NistP384 => { let pv = ecc::Scalar::new_random::<ecc::Secp384r1, _>(&mut rng); (pv, HashAlgorithm::SHA384, 384) } Curve::NistP521 => { let pv = ecc::Scalar::new_random::<ecc::Secp521r1, _>(&mut rng); (pv, HashAlgorithm::SHA512, 521) } _ => unreachable!(), }; let public = ecdh::point_mul_g(&private); let (pub_x, pub_y) = public.as_bytes(); let public_mpis = mpi::PublicKey::ECDH{ curve, q: MPI::new_point(&pub_x, &pub_y, field_sz), hash, sym: SymmetricAlgorithm::AES256, }; let private_mpis = mpi::SecretKeyMaterial::ECDH{ scalar: MPI::new(&private.as_bytes()).into(), }; let sec = private_mpis.into(); (public_mpis, sec, ECDH) } (cv, _) => { return Err(Error::UnsupportedEllipticCurve(cv).into()); } }; Self::with_secret( crate::now(), pk_algo, mpis, secret) } } ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-1.7.0/src/crypto/backend/nettle/ecdh.rs���������������������������������������������0000644�0000000�0000000�00000021115�00726746425�0021237�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������//! Elliptic Curve Diffie-Hellman. use nettle::{curve25519, 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 => { // Obtain the authenticated recipient public key R let R = q.decode_point(curve)?.0; // Generate an ephemeral key pair {v, V=vG} let v: Protected = curve25519::private_key(&mut rng).into(); // Compute the public key. let mut VB = [0; curve25519::CURVE25519_SIZE]; curve25519::mul_g(&mut VB, &v) .expect("buffers are of the wrong size"); let VB = MPI::new_compressed_point(&VB); // Compute the shared point S = vR; let mut S: Protected = vec![0; curve25519::CURVE25519_SIZE].into(); curve25519::mul(&mut S, &v, R) .expect("buffers are of the wrong size"); encrypt_wrap(recipient, session_key, VB, &S) } 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::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) -> 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 => { // Get the public part V of the ephemeral key. let V = e.decode_point(curve)?.0; // Nettle expects the private key to be exactly // CURVE25519_SIZE 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(curve25519::CURVE25519_SIZE); // 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 mut S: Protected = vec![0; curve25519::CURVE25519_SIZE].into(); curve25519::mul(&mut S, &r[..], V) .expect("buffers are of the wrong size"); S } 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::BrainpoolP512 => return Err(Error::UnsupportedEllipticCurve(curve.clone()).into()), // N/A Curve::Unknown(_) | Curve::Ed25519 => return Err(Error::UnsupportedEllipticCurve(curve.clone()).into()), }; decrypt_unwrap(recipient, &S, ciphertext) } _ => Err(Error::InvalidArgument("Expected an ECDHPublicKey".into()).into()), } } ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-1.7.0/src/crypto/backend/nettle/hash.rs���������������������������������������������0000644�0000000�0000000�00000007045�00726746425�0021265�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 algo(&self) -> crate::types::HashAlgorithm { crate::types::HashAlgorithm::$algo } fn digest_size(&self) -> usize { nettle::hash::Hash::digest_size(self) } 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::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::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}; 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::MD5 => Ok(Box::new(Md5::default())), HashAlgorithm::RipeMD => Ok(Box::new(Ripemd160::default())), HashAlgorithm::Private(_) | HashAlgorithm::Unknown(_) => Err(Error::UnsupportedHashAlgorithm(self).into()), } } /// Returns the ASN.1 OID of this hash algorithm. pub fn oid(self) -> Result<&'static [u8]> { use nettle::rsa; match self { HashAlgorithm::SHA1 => Ok(rsa::ASN1_OID_SHA1), HashAlgorithm::SHA224 => Ok(rsa::ASN1_OID_SHA224), HashAlgorithm::SHA256 => Ok(rsa::ASN1_OID_SHA256), HashAlgorithm::SHA384 => Ok(rsa::ASN1_OID_SHA384), HashAlgorithm::SHA512 => Ok(rsa::ASN1_OID_SHA512), HashAlgorithm::MD5 => Ok(rsa::ASN1_OID_MD5), HashAlgorithm::RipeMD => Ok(rsa::ASN1_OID_RIPEMD160), HashAlgorithm::Private(_) | HashAlgorithm::Unknown(_) => Err(Error::UnsupportedHashAlgorithm(self).into()), } } } �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-1.7.0/src/crypto/backend/nettle/symmetric.rs����������������������������������������0000644�0000000�0000000�00000025163�00726746425�0022357�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, } #[allow(clippy::new_ret_no_self)] 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. /// /// 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 fn is_supported(&self) -> bool { use self::SymmetricAlgorithm::*; match &self { TripleDES | CAST5 | Blowfish | AES128 | AES192 | AES256 | Twofish | Camellia128 | Camellia192 | Camellia256 => true, Unencrypted | IDEA | Private(_) | Unknown(_) => false, } } /// Length of a key for this algorithm in bytes. /// /// Fails if Sequoia does not support this algorithm. pub fn key_size(self) -> Result<usize> { match self { SymmetricAlgorithm::TripleDES => Ok(cipher::Des3::KEY_SIZE), SymmetricAlgorithm::CAST5 => Ok(cipher::Cast128::KEY_SIZE), // RFC4880, Section 9.2: Blowfish (128 bit key, 16 rounds) SymmetricAlgorithm::Blowfish => Ok(16), SymmetricAlgorithm::AES128 => Ok(cipher::Aes128::KEY_SIZE), SymmetricAlgorithm::AES192 => Ok(cipher::Aes192::KEY_SIZE), SymmetricAlgorithm::AES256 => Ok(cipher::Aes256::KEY_SIZE), SymmetricAlgorithm::Twofish => Ok(cipher::Twofish::KEY_SIZE), SymmetricAlgorithm::Camellia128 => Ok(cipher::Camellia128::KEY_SIZE), SymmetricAlgorithm::Camellia192 => Ok(cipher::Camellia192::KEY_SIZE), SymmetricAlgorithm::Camellia256 => Ok(cipher::Camellia256::KEY_SIZE), _ => Err(Error::UnsupportedSymmetricAlgorithm(self).into()), } } /// Length of a block for this algorithm in bytes. /// /// Fails if Sequoia does not support this algorithm. pub fn block_size(self) -> Result<usize> { match self { SymmetricAlgorithm::TripleDES => Ok(cipher::Des3::BLOCK_SIZE), SymmetricAlgorithm::CAST5 => Ok(cipher::Cast128::BLOCK_SIZE), SymmetricAlgorithm::Blowfish => Ok(cipher::Blowfish::BLOCK_SIZE), SymmetricAlgorithm::AES128 => Ok(cipher::Aes128::BLOCK_SIZE), SymmetricAlgorithm::AES192 => Ok(cipher::Aes192::BLOCK_SIZE), SymmetricAlgorithm::AES256 => Ok(cipher::Aes256::BLOCK_SIZE), SymmetricAlgorithm::Twofish => Ok(cipher::Twofish::BLOCK_SIZE), SymmetricAlgorithm::Camellia128 => Ok(cipher::Camellia128::BLOCK_SIZE), SymmetricAlgorithm::Camellia192 => Ok(cipher::Camellia192::BLOCK_SIZE), SymmetricAlgorithm::Camellia256 => Ok(cipher::Camellia256::BLOCK_SIZE), _ => Err(Error::UnsupportedSymmetricAlgorithm(self).into()), } } /// 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>> { 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>> { 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>> { 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>> { 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()) } } } �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-1.7.0/src/crypto/backend/nettle.rs��������������������������������������������������0000644�0000000�0000000�00000003236�00726746425�0020340�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 symmetric; /// 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) { Yarrow::default().random(buf.as_mut()); } impl PublicKeyAlgorithm { pub(crate) fn is_supported_by_backend(&self) -> bool { use PublicKeyAlgorithm::*; #[allow(deprecated)] match &self { RSAEncryptSign | RSAEncrypt | RSASign | DSA | ECDH | ECDSA | EdDSA => true, ElGamalEncrypt | ElGamalEncryptSign | Private(_) | Unknown(_) => false, } } } impl Curve { pub(crate) fn is_supported_by_backend(&self) -> bool { use self::Curve::*; match &self { NistP256 | NistP384 | NistP521 | Ed25519 | Cv25519 => true, BrainpoolP256 | BrainpoolP512 | Unknown(_) => false, } } } impl AEADAlgorithm { pub(crate) fn is_supported_by_backend(&self) -> bool { use self::AEADAlgorithm::*; match &self { EAX => true, OCB | Private(_) | Unknown(_) => false, } } } ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-1.7.0/src/crypto/backend/rust/aead.rs�����������������������������������������������0000644�0000000�0000000�00000011004�00726746425�0020724�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������//! Implementation of AEAD using pure Rust cryptographic libraries. use std::cmp; use cipher::{BlockCipher, NewBlockCipher}; use cipher::block::Block; use cipher::consts::U16; use eax::online::{Eax, Encrypt, Decrypt}; use generic_array::{ArrayLength, GenericArray}; use crate::{Error, Result}; use crate::crypto::aead::{Aead, CipherOp}; use crate::seal; use crate::types::{AEADAlgorithm, SymmetricAlgorithm}; trait GenericArrayExt { const LEN: usize; } impl<T, N: ArrayLength<T>> GenericArrayExt for GenericArray<T, N> { const LEN: usize = N::USIZE; } impl<Cipher> Aead for Eax<Cipher, Encrypt> where Cipher: BlockCipher<BlockSize = U16> + NewBlockCipher + Clone, Cipher::ParBlocks: ArrayLength<Block<Cipher>>, { fn update(&mut self, ad: &[u8]) { self.update_assoc(ad) } fn digest_size(&self) -> usize { eax::Tag::LEN } fn digest(&mut self, digest: &mut [u8]) { let tag = self.tag_clone(); digest[..tag.len()].copy_from_slice(&tag[..]); } fn encrypt(&mut self, dst: &mut [u8], src: &[u8]) { let len = cmp::min(dst.len(), src.len()); dst[..len].copy_from_slice(&src[..len]); Self::encrypt(self, &mut dst[..len]) } fn decrypt(&mut self, _dst: &mut [u8], _src: &[u8]) { panic!("AEAD decryption called in the encryption context") } } impl<Cipher> Aead for Eax<Cipher, Decrypt> where Cipher: BlockCipher<BlockSize = U16> + NewBlockCipher + Clone, Cipher::ParBlocks: ArrayLength<Block<Cipher>>, { fn update(&mut self, ad: &[u8]) { self.update_assoc(ad) } fn digest_size(&self) -> usize { eax::Tag::LEN } fn digest(&mut self, digest: &mut [u8]) { let tag = self.tag_clone(); digest[..tag.len()].copy_from_slice(&tag[..]); } fn encrypt(&mut self, _dst: &mut [u8], _src: &[u8]) { panic!("AEAD encryption called in the decryption context") } fn decrypt(&mut self, dst: &mut [u8], src: &[u8]) { let len = core::cmp::min(dst.len(), src.len()); dst[..len].copy_from_slice(&src[..len]); self.decrypt_unauthenticated_hazmat(&mut dst[..len]) } } impl<Cipher, Op> seal::Sealed for Eax<Cipher, Op> where Cipher: BlockCipher<BlockSize = U16> + NewBlockCipher + Clone, Cipher::ParBlocks: ArrayLength<Block<Cipher>>, Op: eax::online::CipherOp, {} impl AEADAlgorithm { pub(crate) fn context( &self, sym_algo: SymmetricAlgorithm, key: &[u8], nonce: &[u8], op: CipherOp, ) -> Result<Box<dyn Aead>> { match self { AEADAlgorithm::EAX => match sym_algo { SymmetricAlgorithm::AES128 => match op { CipherOp::Encrypt => Ok(Box::new( Eax::<aes::Aes128, Encrypt>::with_key_and_nonce(key.into(), nonce.into()))), CipherOp::Decrypt => Ok(Box::new( Eax::<aes::Aes128, Decrypt>::with_key_and_nonce(key.into(), nonce.into()))), } SymmetricAlgorithm::AES192 => match op { CipherOp::Encrypt => Ok(Box::new( Eax::<aes::Aes192, Encrypt>::with_key_and_nonce(key.into(), nonce.into()))), CipherOp::Decrypt => Ok(Box::new( Eax::<aes::Aes192, Decrypt>::with_key_and_nonce(key.into(), nonce.into()))), } SymmetricAlgorithm::AES256 => match op { CipherOp::Encrypt => Ok(Box::new( Eax::<aes::Aes256, Encrypt>::with_key_and_nonce(key.into(), nonce.into()))), CipherOp::Decrypt => Ok(Box::new( Eax::<aes::Aes256, Decrypt>::with_key_and_nonce(key.into(), nonce.into()))), } | 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::OCB | AEADAlgorithm::Private(_) | AEADAlgorithm::Unknown(_) => Err(Error::UnsupportedAEADAlgorithm(*self).into()), } } } ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-1.7.0/src/crypto/backend/rust/asymmetric.rs�����������������������������������������0000644�0000000�0000000�00000056556�00726746425�0022234�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 rand07::rngs::OsRng; use rsa::{PaddingScheme, RSAPublicKey, RSAPrivateKey, PublicKey, PublicKeyParts, Hash}; use crate::{Error, Result}; use crate::crypto::asymmetric::{KeyPair, Decryptor, Signer}; 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, SymmetricAlgorithm}; const CURVE25519_SIZE: usize = 32; fn pkcs1_padding(hash_algo: HashAlgorithm) -> Result<PaddingScheme> { let hash = match hash_algo { HashAlgorithm::MD5 => Hash::MD5, HashAlgorithm::SHA1 => Hash::SHA1, HashAlgorithm::SHA224 => Hash::SHA2_224, HashAlgorithm::SHA256 => Hash::SHA2_256, HashAlgorithm::SHA384 => Hash::SHA2_384, HashAlgorithm::SHA512 => Hash::SHA2_512, HashAlgorithm::RipeMD => Hash::RIPEMD160, _ => return Err(Error::InvalidArgument(format!( "Algorithm {:?} not representable", hash_algo)).into()), }; Ok(PaddingScheme::PKCS1v15Sign { hash: Some(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)?) } #[allow(clippy::many_single_char_names)] fn rsa_private_key(e: &MPI, n: &MPI, p: &ProtectedMPI, q: &ProtectedMPI, d: &ProtectedMPI) -> 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()); RSAPrivateKey::from_components(n, e, d, vec![p, q]) } 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::PublicKeyAlgorithm::*; #[allow(deprecated)] self.secret().map(|secret| 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 { .. }, mpi::SecretKeyMaterial::DSA { .. }) => { Err(Error::UnsupportedPublicKeyAlgorithm(PublicKeyAlgorithm::DSA).into()) }, (PublicKeyAlgorithm::ECDSA, mpi::PublicKey::ECDSA { curve, .. }, mpi::SecretKeyMaterial::ECDSA { scalar }) => match curve { Curve::NistP256 => { use p256::{ elliptic_curve::{ generic_array::GenericArray as GA, }, Scalar, }; use ecdsa::{ hazmat::SignPrimitive, }; const LEN: usize = 32; let key = Scalar::from_bytes_reduced( GA::from_slice(&scalar.value_padded(LEN))); let dig = Scalar::from_bytes_reduced( GA::from_slice(&pad_truncating(digest, LEN))); let sig = loop { let mut k: Protected = vec![0; LEN].into(); crate::crypto::random(&mut k); let k = Scalar::from_bytes_reduced( GA::from_slice(&k)); if let Ok(s) = key.try_sign_prehashed(&k, &dig) { break s; } }; Ok(mpi::Signature::ECDSA { r: MPI::new(&sig.r().to_bytes()), s: MPI::new(&sig.s().to_bytes()), }) }, _ => Err(Error::UnsupportedEllipticCurve(curve.clone()).into()), }, (EdDSA, mpi::PublicKey::EdDSA { curve, q }, mpi::SecretKeyMaterial::EdDSA { scalar }) => match curve { Curve::Ed25519 => { use ed25519_dalek::{Keypair, Signer}; use ed25519_dalek::{PUBLIC_KEY_LENGTH, SECRET_KEY_LENGTH}; let (public, ..) = q.decode_point(&Curve::Ed25519)?; assert_eq!(public.len(), PUBLIC_KEY_LENGTH); // It's expected for the private key to be exactly // SECRET_KEY_LENGTH bytes long but OpenPGP allows leading // zeros to be stripped. // Padding has to be unconditional; otherwise we have a // secret-dependent branch. let mut keypair = Protected::from( vec![0u8; SECRET_KEY_LENGTH + PUBLIC_KEY_LENGTH] ); keypair.as_mut()[..SECRET_KEY_LENGTH] .copy_from_slice( &scalar.value_padded(SECRET_KEY_LENGTH)); keypair.as_mut()[SECRET_KEY_LENGTH..] .copy_from_slice(public); let pair = Keypair::from_bytes(&keypair)?; let sig = pair.sign(digest).to_bytes(); // https://tools.ietf.org/html/rfc8032#section-5.1.6 let (r, s) = sig.split_at(sig.len() / 2); Ok(mpi::Signature::EdDSA { r: mpi::MPI::new(r), s: mpi::MPI::new(s), }) }, _ => 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 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::PublicKeyAlgorithm::*; self.secret().map(|secret| 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 padding = PaddingScheme::PKCS1v15Encrypt; let decrypted = key.decrypt(padding, 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), (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 fn encrypt(&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 padding = PaddingScheme::PKCS1v15Encrypt; let ciphertext = key.encrypt(&mut OsRng, padding, 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), algo => Err(Error::UnsupportedPublicKeyAlgorithm(algo).into()), } } /// Verifies the given signature. pub fn verify(&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)?; key.verify(padding, digest, s.value())?; Ok(()) } (mpi::PublicKey::DSA { .. }, mpi::Signature::DSA { .. }) => { Err(Error::UnsupportedPublicKeyAlgorithm(PublicKeyAlgorithm::DSA).into()) }, (mpi::PublicKey::ECDSA { curve, q }, mpi::Signature::ECDSA { r, s }) => match curve { Curve::NistP256 => { use p256::{ AffinePoint, ecdsa::Signature, elliptic_curve::{ generic_array::GenericArray as GA, sec1::FromEncodedPoint, }, Scalar, }; use ecdsa::{ EncodedPoint, hazmat::VerifyPrimitive, }; const LEN: usize = 32; let key = AffinePoint::from_encoded_point( &EncodedPoint::from_bytes(q.value())?) .ok_or_else(|| Error::InvalidKey( "Point is not on the curve".into()))?; let sig = Signature::from_scalars( Scalar::from_bytes_reduced( GA::from_slice(&r.value_padded(LEN).map_err(bad)?)), Scalar::from_bytes_reduced( GA::from_slice(&s.value_padded(LEN).map_err(bad)?))) .map_err(bad)?; let dig = Scalar::from_bytes_reduced( GA::from_slice(&pad_truncating(digest, LEN))); key.verify_prehashed(&dig, &sig).map_err(bad) }, _ => Err(Error::UnsupportedEllipticCurve(curve.clone()).into()), }, (mpi::PublicKey::EdDSA { curve, q }, mpi::Signature::EdDSA { r, s }) => match curve { Curve::Ed25519 => { use ed25519_dalek::{PublicKey, Signature, SIGNATURE_LENGTH}; use ed25519_dalek::{Verifier}; let (public, ..) = q.decode_point(&Curve::Ed25519)?; assert_eq!(public.len(), 32); let key = PublicKey::from_bytes(public).map_err(|e| { Error::InvalidKey(e.to_string()) })?; // OpenPGP encodes R and S separately, but our // cryptographic library expects them to be // concatenated. let mut sig_bytes = [0u8; SIGNATURE_LENGTH]; // We need to zero-pad them at the front, because // the MPI encoding drops leading zero bytes. let half = SIGNATURE_LENGTH / 2; sig_bytes[..half].copy_from_slice( &r.value_padded(half).map_err(bad)?); sig_bytes[half..].copy_from_slice( &s.value_padded(half).map_err(bad)?); let signature = Signature::from(sig_bytes); key.verify(digest, &signature) .map_err(|e| Error::BadSignature(e.to_string()))?; Ok(()) }, _ => 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 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 it's creation date set to /// `ctime` or the current time if `None` is given. 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<SystemTime>> { use x25519_dalek::{PublicKey, StaticSecret}; let secret = StaticSecret::from(<[u8; 32]>::try_from(private_key)?); let public_key = PublicKey::from(&secret); let mut private_key = Vec::from(private_key); private_key.reverse(); Self::with_secret( ctime.into().unwrap_or_else(crate::now), PublicKeyAlgorithm::ECDH, mpi::PublicKey::ECDH { curve: Curve::Cv25519, hash: hash.into().unwrap_or(HashAlgorithm::SHA512), sym: sym.into().unwrap_or(SymmetricAlgorithm::AES256), q: MPI::new_compressed_point(&*public_key.as_bytes()), }, mpi::SecretKeyMaterial::ECDH { scalar: private_key.into(), }.into()) } /// Creates a new OpenPGP secret key packet for an existing Ed25519 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 it's 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<SystemTime>> { use ed25519_dalek::{PublicKey, SecretKey}; let private = SecretKey::from_bytes(private_key).map_err(|e| { Error::InvalidKey(e.to_string()) })?; // Mark MPI as compressed point with 0x40 prefix. See // https://tools.ietf.org/html/draft-ietf-openpgp-rfc4880bis-07#section-13.2. let mut public = [0u8; 1 + CURVE25519_SIZE]; public[0] = 0x40; public[1..].copy_from_slice(Into::<PublicKey>::into(&private).as_bytes()); Self::with_secret( ctime.into().unwrap_or_else(crate::now), PublicKeyAlgorithm::EdDSA, mpi::PublicKey::EdDSA { curve: Curve::Ed25519, q: mpi::MPI::new(&public) }, mpi::SecretKeyMaterial::EdDSA { scalar: mpi::MPI::new(private_key).into(), }.into() ) } /// 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 it's 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>> { // RFC 4880: `p < q` let (p, q) = if p < q { (p, q) } else { (q, p) }; // 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: mpi::MPI::new(d).into(), p: mpi::MPI::new(p).into(), q: mpi::MPI::new(q).into(), u: mpi::MPI::new(&u.to_bytes_be()).into(), }.into() ) } /// Generates a new RSA key with a public modulos of size `bits`. pub fn generate_rsa(bits: usize) -> Result<Self> { let key = RSAPrivateKey::new(&mut OsRng, bits)?; let (p, q) = match key.primes() { [p, q] => (p, q), _ => panic!("RSA key generation resulted in wrong number of primes"), }; 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: mpi::MPI::new(&p.to_bytes_be()).into(), q: mpi::MPI::new(&q.to_bytes_be()).into(), d: mpi::MPI::new(&key.d().to_bytes_be()).into(), u: mpi::MPI::new(&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 fn generate_ecc(for_signing: bool, curve: Curve) -> Result<Self> { let (algo, public, private) = match (&curve, for_signing) { (Curve::Ed25519, true) => { use ed25519_dalek::Keypair; let Keypair { public, secret } = Keypair::generate(&mut OsRng); let secret: Protected = secret.as_bytes().as_ref().into(); // Mark MPI as compressed point with 0x40 prefix. See // https://tools.ietf.org/html/draft-ietf-openpgp-rfc4880bis-07#section-13.2. let mut compressed_public = [0u8; 1 + CURVE25519_SIZE]; compressed_public[0] = 0x40; compressed_public[1..].copy_from_slice(public.as_bytes()); ( PublicKeyAlgorithm::EdDSA, mpi::PublicKey::EdDSA { curve, q: mpi::MPI::new(&compressed_public) }, mpi::SecretKeyMaterial::EdDSA { scalar: secret.into() }, ) } (Curve::Cv25519, false) => { use x25519_dalek::{StaticSecret, PublicKey}; let private_key = StaticSecret::new(OsRng); let public_key = PublicKey::from(&private_key); let mut private_key = Vec::from(private_key.to_bytes()); private_key.reverse(); let public_mpis = mpi::PublicKey::ECDH { curve: Curve::Cv25519, q: MPI::new_compressed_point(&*public_key.as_bytes()), hash: HashAlgorithm::SHA256, sym: SymmetricAlgorithm::AES256, }; let private_mpis = mpi::SecretKeyMaterial::ECDH { scalar: private_key.into(), }; (PublicKeyAlgorithm::ECDH, public_mpis, private_mpis) } (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(), }; (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 { curve, q: MPI::new(public.as_bytes()), hash: HashAlgorithm::SHA256, sym: SymmetricAlgorithm::AES256, }; let private_mpis = mpi::SecretKeyMaterial::ECDH { scalar: Vec::from(secret.to_bytes().as_slice()).into(), }; (PublicKeyAlgorithm::ECDH, public_mpis, private_mpis) }, _ => { return Err(Error::UnsupportedEllipticCurve(curve).into()); } }; Self::with_secret(crate::now(), algo, public, private.into()) } } ��������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-1.7.0/src/crypto/backend/rust/ecdh.rs�����������������������������������������������0000644�0000000�0000000�00000011430�00726746425�0020740�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������//! Elliptic Curve Diffie-Hellman. use std::convert::TryInto; use rand07::rngs::OsRng; 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; 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 => { use x25519_dalek::{EphemeralSecret, PublicKey}; // Decode the recipient's public key. let R: [u8; CURVE25519_SIZE] = q.decode_point(curve)?.0.try_into()?; let recipient_key = PublicKey::from(R); // Generate a keypair and perform Diffie-Hellman. let secret = EphemeralSecret::new(OsRng); let public = PublicKey::from(&secret); let shared = secret.diffie_hellman(&recipient_key); // Encode our public key. We need to add an encoding // octet in front of the key. let mut VB = [0; 1 + CURVE25519_SIZE]; VB[0] = 0x40; VB[1..].copy_from_slice(public.as_bytes()); let VB = MPI::new(&VB); // Encode the shared secret. let shared: &[u8] = shared.as_bytes(); let shared = Protected::from(shared); (VB, shared) }, 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.as_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) -> 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 => { use x25519_dalek::{PublicKey, StaticSecret}; // Get the public part V of the ephemeral key. let V: [u8; CURVE25519_SIZE] = e.decode_point(curve)?.0.try_into()?; let V = PublicKey::from(V); let mut scalar: [u8; CURVE25519_SIZE] = scalar.value_padded(CURVE25519_SIZE).as_ref().try_into()?; scalar.reverse(); let r = StaticSecret::from(scalar); let secret = r.diffie_hellman(&V); Vec::from(secret.to_bytes()).into() }, Curve::NistP256 => { use p256::{ SecretKey, PublicKey, elliptic_curve::ecdh::diffie_hellman, }; const NISTP256_SIZE: usize = 32; // Get the public part V of the ephemeral key. let V = dbg!(PublicKey::from_sec1_bytes(e.value()))?; let scalar: [u8; NISTP256_SIZE] = scalar.value_padded(NISTP256_SIZE).as_ref().try_into()?; let r = SecretKey::from_bytes(scalar)?; let secret = diffie_hellman(r.secret_scalar(), V.as_affine()); Vec::from(secret.as_bytes().as_slice()).into() }, _ => { return Err(Error::UnsupportedEllipticCurve(curve.clone()).into()); }, }; decrypt_unwrap(recipient, &S, ciphertext) } ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-1.7.0/src/crypto/backend/rust/hash.rs�����������������������������������������������0000644�0000000�0000000�00000005202�00726746425�0020760�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 algo(&self) -> HashAlgorithm { HashAlgorithm::$algo } fn digest_size(&self) -> usize { Self::output_size() } 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!(ripemd160::Ripemd160, RipeMD); impl_digest_for!(sha1::Sha1, SHA1); 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 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::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 { HashAlgorithm::SHA1 => Ok(Box::new(sha1::Sha1::new())), 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::RipeMD => Ok(Box::new(ripemd160::Ripemd160::new())), HashAlgorithm::MD5 => Ok(Box::new(md5::Md5::new())), HashAlgorithm::Private(_) | HashAlgorithm::Unknown(_) => Err(Error::UnsupportedHashAlgorithm(self).into()), } } } ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-1.7.0/src/crypto/backend/rust/symmetric.rs������������������������������������������0000644�0000000�0000000�00000020220�00726746425�0022046�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������use std::slice; use block_modes::{BlockMode, Cfb, Ecb}; use block_padding::ZeroPadding; use cipher::{BlockCipher, NewBlockCipher}; use generic_array::{ArrayLength, GenericArray}; use typenum::Unsigned; use crate::{Error, Result}; use crate::crypto::symmetric::Mode; use crate::types::SymmetricAlgorithm; macro_rules! impl_mode { ($mode:ident) => { impl<C> Mode for $mode<C, ZeroPadding> where C: BlockCipher + NewBlockCipher + Send + Sync, { fn block_size(&self) -> usize { C::BlockSize::to_usize() } fn encrypt( &mut self, dst: &mut [u8], src: &[u8], ) -> Result<()> { 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); self.encrypt_blocks(to_blocks(&mut buf)); dst.copy_from_slice(&buf[..dst.len()]); } else { dst.copy_from_slice(src); self.encrypt_blocks(to_blocks(dst)); } Ok(()) } fn decrypt( &mut self, dst: &mut [u8], src: &[u8], ) -> Result<()> { 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); self.decrypt_blocks(to_blocks(&mut buf)); dst.copy_from_slice(&buf[..dst.len()]); } else { dst.copy_from_slice(src); self.decrypt_blocks(to_blocks(dst)); } Ok(()) } } } } impl_mode!(Cfb); impl_mode!(Ecb); 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) } } 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 fn is_supported(&self) -> bool { use SymmetricAlgorithm::*; match self { IDEA => true, TripleDES => true, CAST5 => true, Blowfish => true, AES128 => true, AES192 => true, AES256 => true, Twofish => true, Camellia128 => false, Camellia192 => false, Camellia256 => false, Private(_) => false, Unknown(_) => false, Unencrypted => false, } } /// Length of a key for this algorithm in bytes. /// /// Fails if Sequoia does not support this algorithm. pub fn key_size(self) -> Result<usize> { use SymmetricAlgorithm::*; match self { IDEA => Ok(<idea::Idea as NewBlockCipher>::KeySize::to_usize()), TripleDES => Ok(<des::TdesEde2 as NewBlockCipher>::KeySize::to_usize()), CAST5 => Ok(<cast5::Cast5 as NewBlockCipher>::KeySize::to_usize()), Blowfish => Ok(<blowfish::Blowfish as NewBlockCipher>::KeySize::to_usize()), AES128 => Ok(<aes::Aes128 as NewBlockCipher>::KeySize::to_usize()), AES192 => Ok(<aes::Aes192 as NewBlockCipher>::KeySize::to_usize()), AES256 => Ok(<aes::Aes256 as NewBlockCipher>::KeySize::to_usize()), Twofish => Ok(<twofish::Twofish as NewBlockCipher>::KeySize::to_usize()), Camellia128 | Camellia192 | Camellia256 | Private(_) | Unknown(_) | Unencrypted => Err(Error::UnsupportedSymmetricAlgorithm(self).into()), } } /// Length of a block for this algorithm in bytes. /// /// Fails if Sequoia does not support this algorithm. pub fn block_size(self) -> Result<usize> { use SymmetricAlgorithm::*; match self { IDEA => Ok(<idea::Idea as BlockCipher>::BlockSize::to_usize()), TripleDES => Ok(<des::TdesEde2 as BlockCipher>::BlockSize::to_usize()), CAST5 => Ok(<cast5::Cast5 as BlockCipher>::BlockSize::to_usize()), Blowfish => Ok(<blowfish::Blowfish as BlockCipher>::BlockSize::to_usize()), AES128 => Ok(<aes::Aes128 as BlockCipher>::BlockSize::to_usize()), AES192 => Ok(<aes::Aes192 as BlockCipher>::BlockSize::to_usize()), AES256 => Ok(<aes::Aes256 as BlockCipher>::BlockSize::to_usize()), Twofish => Ok(<twofish::Twofish as BlockCipher>::BlockSize::to_usize()), Camellia128 | Camellia192 | Camellia256 | Private(_) | Unknown(_) | Unencrypted => 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>> { use SymmetricAlgorithm::*; match self { IDEA => Ok(Box::new(Cfb::<idea::Idea, ZeroPadding>::new_var(key, &iv)?)), TripleDES => Ok(Box::new(Cfb::<des::TdesEde2, ZeroPadding>::new_var(key, &iv)?)), CAST5 => Ok(Box::new(Cfb::<cast5::Cast5, ZeroPadding>::new_var(key, &iv)?)), Blowfish => Ok(Box::new(Cfb::<blowfish::Blowfish, ZeroPadding>::new_var(key, &iv)?)), AES128 => Ok(Box::new(Cfb::<aes::Aes128, ZeroPadding>::new_var(key, &iv)?)), AES192 => Ok(Box::new(Cfb::<aes::Aes192, ZeroPadding>::new_var(key, &iv)?)), AES256 => Ok(Box::new(Cfb::<aes::Aes256, ZeroPadding>::new_var(key, &iv)?)), Twofish => Ok(Box::new(Cfb::<twofish::Twofish, ZeroPadding>::new_var(key, &iv)?)), Camellia128 | Camellia192 | Camellia256 | Private(_) | Unknown(_) | Unencrypted => Err(Error::UnsupportedSymmetricAlgorithm(self).into()), } } /// Creates a 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(key, iv) } /// Creates a context for encrypting in ECB mode. pub(crate) fn make_encrypt_ecb(self, key: &[u8]) -> Result<Box<dyn Mode>> { use SymmetricAlgorithm::*; match self { IDEA => Ok(Box::new(Ecb::<idea::Idea, ZeroPadding>::new_var(key, &[])?)), TripleDES => Ok(Box::new(Ecb::<des::TdesEde2, ZeroPadding>::new_var(key, &[])?)), CAST5 => Ok(Box::new(Ecb::<cast5::Cast5, ZeroPadding>::new_var(key, &[])?)), Blowfish => Ok(Box::new(Ecb::<blowfish::Blowfish, ZeroPadding>::new_var(key, &[])?)), AES128 => Ok(Box::new(Ecb::<aes::Aes128, ZeroPadding>::new_var(key, &[])?)), AES192 => Ok(Box::new(Ecb::<aes::Aes192, ZeroPadding>::new_var(key, &[])?)), AES256 => Ok(Box::new(Ecb::<aes::Aes256, ZeroPadding>::new_var(key, &[])?)), Twofish => Ok(Box::new(Ecb::<twofish::Twofish, ZeroPadding>::new_var(key, &[])?)), Camellia128 | Camellia192 | Camellia256 | Private(_) | Unknown(_) | Unencrypted => Err(Error::UnsupportedSymmetricAlgorithm(self).into()), } } /// 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-1.7.0/src/crypto/backend/rust.rs����������������������������������������������������0000644�0000000�0000000�00000003213�00726746425�0020035�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������//! Implementation of Sequoia crypto API using pure Rust cryptographic //! libraries. use crate::types::*; pub mod aead; pub mod asymmetric; pub mod ecdh; pub mod hash; pub mod symmetric; /// 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. pub fn random<B: AsMut<[u8]>>(mut buf: B) { use rand07::rngs::OsRng; use rand07::RngCore; OsRng.fill_bytes(buf.as_mut()) } impl PublicKeyAlgorithm { pub(crate) fn is_supported_by_backend(&self) -> bool { use PublicKeyAlgorithm::*; #[allow(deprecated)] match &self { RSAEncryptSign | RSAEncrypt | RSASign | ECDH | EdDSA | ECDSA => true, DSA => false, ElGamalEncrypt | ElGamalEncryptSign | Private(_) | Unknown(_) => false, } } } impl Curve { pub(crate) fn is_supported_by_backend(&self) -> bool { use self::Curve::*; match &self { NistP256 => true, NistP384 | NistP521 => false, Ed25519 | Cv25519 => true, BrainpoolP256 | BrainpoolP512 | Unknown(_) => false, } } } impl AEADAlgorithm { pub(crate) fn is_supported_by_backend(&self) -> bool { use self::AEADAlgorithm::*; match &self { EAX => true, OCB | Private(_) | Unknown(_) => false, } } } �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-1.7.0/src/crypto/backend/sha1cd.rs��������������������������������������������������0000644�0000000�0000000�00000012165�00726746425�0020211�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 algo(&self) -> crate::types::HashAlgorithm { crate::types::HashAlgorithm::SHA1 } fn digest_size(&self) -> usize { 20 } 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-1.7.0/src/crypto/backend.rs���������������������������������������������������������0000644�0000000�0000000�00000000637�00726746425�0017047�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������//! Concrete implementation of the crypto primitives used by the rest of the //! crypto API. pub(crate) mod sha1cd; #[cfg(feature = "crypto-nettle")] mod nettle; #[cfg(feature = "crypto-nettle")] pub use self::nettle::*; #[cfg(feature = "crypto-rust")] mod rust; #[cfg(feature = "crypto-rust")] pub use self::rust::*; #[cfg(feature = "crypto-cng")] mod cng; #[cfg(feature = "crypto-cng")] pub use self::cng::*; �������������������������������������������������������������������������������������������������sequoia-openpgp-1.7.0/src/crypto/ecdh.rs������������������������������������������������������������0000644�0000000�0000000�00000050062�00726746425�0016360�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::hash::Digest; 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}; pub(crate) use crate::crypto::backend::ecdh::{encrypt, decrypt}; /// 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)] 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<R>(recipient: &Key<key::PublicParts, R>, S: &Protected, ciphertext: &mpi::Ciphertext) -> Result<SessionKey> where R: key::KeyRole { 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 cipher = SymmetricAlgorithm::from(m[0]); let m = pkcs5_unpad(m, 1 + cipher.key_size()? + 2)?; 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()?; 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 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, target_len: usize) -> Result<Protected> { if sk.len() > 0xff { return Err(Error::InvalidArgument("message too large".into()).into()); } if sk.len() < target_len { return Err(Error::InvalidArgument("message too small".into()).into()); } let mut buf: Vec<u8> = sk.expose_into_unprotected_vec(); let mut good = true; let missing = (buf.len() - target_len) as u8; for &b in &buf[target_len..] { good = b == missing && 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 fn aes_key_wrap(algo: SymmetricAlgorithm, key: &Protected, plaintext: &Protected) -> Result<Vec<u8>> { use crate::SymmetricAlgorithm::*; 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 = match algo { AES128 | AES192 | AES256 => algo.make_encrypt_ecb(key)?, _ => return Err(Error::UnsupportedSymmetricAlgorithm(algo).into()), }; // 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 fn aes_key_unwrap(algo: SymmetricAlgorithm, key: &Protected, ciphertext: &[u8]) -> Result<Protected> { use crate::SymmetricAlgorithm::*; 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 = match algo { AES128 | AES192 | AES256 => algo.make_decrypt_ecb(key)?, _ => return Err(Error::UnsupportedSymmetricAlgorithm(algo).into()), }; // 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()); param.push(3); param.push(1); 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, 3).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, 0).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); assert_eq!(s[LAST] & 0b1100_0000, 0b0100_0000); } 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-1.7.0/src/crypto/hash.rs������������������������������������������������������������0000644�0000000�0000000�00000046500�00726746425�0016402�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������//! Cryptographic hash functions and hashing of OpenPGP data //! structures. //! //! This module provides trait [`Digest`] 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()?; //! 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; 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; use crate::packet::Signature; use crate::packet::signature::{self, Signature4}; use crate::Result; use crate::types::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; /// Hasher capable of calculating a digest for the input byte stream. /// /// This provides an abstract interface to the hash functions used in /// OpenPGP. `Digest`s can be are created using [`HashAlgorithm::context`]. /// /// [`HashAlgorithm::context`]: crate::types::HashAlgorithm::context() pub trait Digest: DynClone + Write + Send + Sync { /// Returns the algorithm. fn algo(&self) -> HashAlgorithm; /// Size of the digest in bytes fn digest_size(&self) -> usize; /// 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<()>; /// Finalizes the hash function and computes the digest. 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) } } dyn_clone::clone_trait_object!(Digest); impl Digest for Box<dyn Digest> { fn algo(&self) -> HashAlgorithm { self.as_ref().algo() } fn digest_size(&self) -> usize { self.as_ref().digest_size() } /// Writes data into the hash function. fn update(&mut self, data: &[u8]) { self.as_mut().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. /// /// [`self.digest_size()`]: Box::digest_size() fn digest(&mut self, digest: &mut [u8]) -> Result<()>{ self.as_mut().digest(digest) } } 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<Box<dyn Digest>> { let hasher: Box<dyn Digest> = match self { HashAlgorithm::SHA1 => Box::new(crate::crypto::backend::sha1cd::build()), _ => self.new_hasher()?, }; Ok(if let Some(prefix) = DUMP_HASHED_VALUES { Box::new(HashDumper::new(hasher, prefix)) } else { hasher }) } } 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 algo(&self) -> HashAlgorithm { self.hasher.algo() } fn digest_size(&self) -> usize { self.hasher.digest_size() } 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.hasher.write(buf) } 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 dyn Digest); } impl Hash for UserID { fn hash(&self, hash: &mut dyn Digest) { 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()); } } impl Hash for UserAttribute { fn hash(&self, hash: &mut dyn Digest) { 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()); } } impl<P, R> Hash for Key4<P, R> where P: key::KeyParts, R: key::KeyRole, { fn hash(&self, hash: &mut dyn Digest) { 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() as u16; let mut header: Vec<u8> = Vec::with_capacity(9); // Tag. Note: we use this whether header.push(0x99); // Length (2 bytes, big endian). header.extend_from_slice(&len.to_be_bytes()); // Version. header.push(4); // Creation time. let creation_time: u32 = Timestamp::try_from(self.creation_time()) .unwrap_or_else(|_| Timestamp::from(0)) .into(); header.extend_from_slice(&creation_time.to_be_bytes()); // Algorithm. header.push(self.pk_algo().into()); hash.update(&header[..]); // MPIs. self.mpis().hash(hash); } } impl Hash for Signature { fn hash(&self, hash: &mut dyn Digest) { match self { Signature::V4(sig) => sig.hash(hash), } } } impl Hash for Signature4 { fn hash(&self, hash: &mut dyn Digest) { self.fields.hash(hash); } } impl Hash for signature::SignatureFields { fn hash(&self, hash: &mut dyn Digest) { use crate::serialize::MarshalInto; // XXX: Annoyingly, we have no proper way of handling errors // here. let hashed_area = self.hashed_area().to_vec() .unwrap_or_else(|_| Vec::new()); // 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] = self.typ().into(); header[2] = self.pk_algo().into(); header[3] = self.hash_algo().into(); // The length of the hashed area, as a 16-bit big endian number. let len = hashed_area.len() as u16; header[4..6].copy_from_slice(&len.to_be_bytes()); hash.update(&header[..]); hash.update(&hashed_area); // 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://tools.ietf.org/html/rfc4880#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[..]); } } /// Hashing-related functionality. /// /// <a id="hashing-functions"></a> impl signature::SignatureFields { /// Hashes this standalone signature. pub fn hash_standalone(&self, hash: &mut dyn Digest) { self.hash(hash); } /// Hashes this timestamp signature. pub fn hash_timestamp(&self, hash: &mut dyn Digest) { self.hash_standalone(hash); } /// Hashes this direct key signature over the specified primary /// key, and the primary key. pub fn hash_direct_key<P>(&self, hash: &mut dyn Digest, key: &Key<P, key::PrimaryRole>) where P: key::KeyParts, { key.hash(hash); self.hash(hash); } /// 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 dyn Digest, key: &Key<P, key::PrimaryRole>, subkey: &Key<Q, key::SubordinateRole>) where P: key::KeyParts, Q: key::KeyParts, { key.hash(hash); subkey.hash(hash); self.hash(hash); } /// 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 dyn Digest, key: &Key<P, key::PrimaryRole>, subkey: &Key<Q, key::SubordinateRole>) where P: key::KeyParts, Q: key::KeyParts, { self.hash_subkey_binding(hash, key, subkey); } /// 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 dyn Digest, key: &Key<P, key::PrimaryRole>, userid: &UserID) where P: key::KeyParts, { key.hash(hash); userid.hash(hash); self.hash(hash); } /// 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 dyn Digest, key: &Key<P, key::PrimaryRole>, ua: &UserAttribute) where P: key::KeyParts, { key.hash(hash); ua.hash(hash); self.hash(hash); } } /// Hashing-related functionality. /// /// <a id="hashing-functions"></a> impl Signature { /// Hashes this signature for use in a Third-Party Confirmation /// signature. pub fn hash_for_confirmation(&self, hash: &mut dyn Digest) { match self { Signature::V4(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 dyn Digest) { 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. let _ = 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()); let _ = self.mpis().serialize(&mut body); hash.update(&[0x88]); hash.update(&(body.len() as u32).to_be_bytes()); hash.update(&body); } } #[cfg(test)] mod test { use super::*; 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(); selfsig.hash_userid_binding( &mut hash, cert.primary_key().key(), binding.userid()); 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(); selfsig.hash_user_attribute_binding( &mut hash, cert.primary_key().key(), a.user_attribute()); 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(); selfsig.hash_subkey_binding( &mut hash, cert.primary_key().key(), binding.key()); 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-1.7.0/src/crypto/mem.rs�������������������������������������������������������������0000644�0000000�0000000�00000025344�00726746425�0016240�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}; /// 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 { /// 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 vector with the correct size to avoid potential // reallocations when turning it into a `Protected`. let mut p = Vec::with_capacity(v.len()); p.extend_from_slice(&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.into_boxed_slice().into() } } 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 { Vec::from(v).into() } } impl Drop for Protected { fn drop(&mut self) { unsafe { let len = self.len(); memsec::memzero(self.as_mut().as_mut_ptr(), len); 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 /// 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. /// }); /// ``` #[derive(Clone, Debug)] pub struct Encrypted { ciphertext: Protected, iv: Protected, } 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)); } } /// 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, Cursor, Write}; use crate::types::{AEADAlgorithm, HashAlgorithm, SymmetricAlgorithm}; use crate::crypto::{aead, SessionKey}; use crate::crypto::hash::Digest; use super::*; lazy_static::lazy_static! { static ref PREKEY: 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()); } pages.into() }; } // 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::EAX; impl Encrypted { /// Computes the sealing key used to encrypt the memory. fn sealing_key() -> SessionKey { let mut ctx = HASH_ALGO.context() .expect("Mandatory algorithm unsupported"); PREKEY.iter().for_each(|page| ctx.update(page)); let mut sk: SessionKey = vec![0; 256/8].into(); let _ = ctx.digest(&mut sk); sk } /// Encrypts the given chunk of memory. pub fn new(p: Protected) -> Self { let mut iv = vec![0; AEAD_ALGO.iv_size() .expect("Mandatory algorithm unsupported")]; crate::crypto::random(&mut iv); let mut ciphertext = Vec::new(); { let mut encryptor = aead::Encryptor::new(1, SYMMETRIC_ALGO, AEAD_ALGO, 4096, &iv, &Self::sealing_key(), &mut ciphertext) .expect("Mandatory algorithm unsupported"); encryptor.write_all(&p).unwrap(); encryptor.finish().unwrap(); } Encrypted { ciphertext: ciphertext.into(), iv: iv.into(), } } /// Maps the given function over the temporarily decrypted /// memory. pub fn map<F, T>(&self, mut fun: F) -> T where F: FnMut(&Protected) -> T { let mut plaintext = Vec::new(); let mut decryptor = aead::Decryptor::new(1, SYMMETRIC_ALGO, AEAD_ALGO, 4096, &self.iv, &Self::sealing_key(), Cursor::new(&self.ciphertext)) .expect("Mandatory algorithm unsupported"); io::copy(&mut decryptor, &mut plaintext) .expect("Encrypted memory modified or corrupted"); let plaintext: Protected = plaintext.into(); fun(&plaintext) } } } /// 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-1.7.0/src/crypto/mod.rs�������������������������������������������������������������0000644�0000000�0000000�00000017077�00726746425�0016245�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}; mod backend; pub use backend::random; pub mod ecdh; pub mod hash; pub mod mem; pub mod mpi; mod s2k; pub use s2k::S2K; pub(crate) mod symmetric; #[cfg(test)] mod tests; /// 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().unwrap()); /// /// let key: Key<key::SecretParts, key::UnspecifiedRole> = /// Key4::generate_ecc(false, Curve::Cv25519)?.into(); /// /// let pkesk: PKESK = /// PKESK3::for_recipient(cipher, &sk, &key)?.into(); /// # Ok(()) } /// ``` pub fn new(size: usize) -> Self { let mut sk: mem::Protected = vec![0; size].into(); random(&mut sk); Self(sk) } } 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())) } } impl From<Box<[u8]>> for Password { fn from(v: Box<[u8]>) -> Self { Password(mem::Encrypted::new(v.into())) } } 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)] #[allow(clippy::unnecessary_lazy_evaluations)] 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) } } �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-1.7.0/src/crypto/mpi.rs�������������������������������������������������������������0000644�0000000�0000000�00000112545�00726746425�0016247�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 4880]). Multiprecision integers in OpenPGP are extended by //! [RFC 6637] 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 4880]: https://tools.ietf.org/html/rfc4880#section-3.2 //! [RFC 6637]: https://tools.ietf.org/html/rfc6637 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 { Self::new(&v) } } impl MPI { /// Creates a new MPI. /// /// This function takes care of removing leading zeros. pub fn new(value: &[u8]) -> Self { let mut leading_zeros = 0; for b in value { leading_zeros += b.leading_zeros() as usize; if *b != 0 { break; } } let offset = leading_zeros / 8; let value = Vec::from(&value[offset..]).into_boxed_slice(); MPI { value, } } /// Creates new MPI encoding an uncompressed EC point. /// /// Encodes the given point on a 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 a 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 } /// 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. 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..], &[])) }, _ => { // Length of one coordinate in bytes, rounded up. let coordinate_length = (curve.len()? + 7) / 8; // 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..])) }, } } /// 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 dyn hash::Digest) { let len = self.bits() as u16; hash.update(&len.to_be_bytes()); hash.update(&self.value); } } #[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.secure_memcmp(other)) } } impl Ord for MPI { fn cmp(&self, other: &MPI) -> Ordering { self.partial_cmp(other).unwrap() } } 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<Vec<u8>> for ProtectedMPI { fn from(m: Vec<u8>) -> Self { MPI::from(m).into() } } impl From<Protected> for ProtectedMPI { fn from(m: Protected) -> Self { MPI::new(&m).into() } } impl From<MPI> for ProtectedMPI { fn from(m: MPI) -> Self { ProtectedMPI { value: m.value.into(), } } } impl PartialOrd for ProtectedMPI { fn partial_cmp(&self, other: &ProtectedMPI) -> Option<Ordering> { Some(self.secure_memcmp(other)) } } impl Ord for ProtectedMPI { fn cmp(&self, other: &ProtectedMPI) -> Ordering { self.partial_cmp(other).unwrap() } } 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); } } impl ProtectedMPI { /// Creates new MPI encoding an uncompressed EC point. /// /// Encodes the given point on a 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 a 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. 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 /// /// Note: This enum cannot be exhaustively matched to allow future /// extensions. #[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, }, /// DJBs "Twisted" Edwards curve DSA public key. EdDSA { /// Curve we're using. Must be curve 25519. curve: Curve, /// Public point. q: MPI, }, /// NISTs Elliptic curve DSA public key. ECDSA { /// Curve we're using. curve: Curve, /// Public point. q: MPI, }, /// Elliptic curve ElGamal public key. ECDH { /// Curve we're using. curve: Curve, /// Public point. q: MPI, /// Hash algorithm used for key derivation. hash: HashAlgorithm, /// Algorithm used w/the derived key. sym: SymmetricAlgorithm, }, /// 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(), ECDSA { ref curve,.. } => curve.bits(), ECDH { ref curve,.. } => curve.bits(), Unknown { .. } => None, } } /// Returns, if known, the public-key algorithm for this public /// key. pub fn algo(&self) -> Option<PublicKeyAlgorithm> { use self::PublicKey::*; 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), Unknown { .. } => None, } } } impl Hash for PublicKey { fn hash(&self, mut hash: &mut dyn hash::Digest) { self.serialize(&mut hash as &mut dyn Write) .expect("hashing does not fail") } } #[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..6, 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), }, _ => unreachable!(), } } } /// 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 /// /// Note: This enum cannot be exhaustively matched to allow future /// extensions. // Deriving Hash here is okay: PartialEq is manually implemented to // ensure that secrets are compared in constant-time. #[allow(clippy::derive_hash_xor_eq)] #[non_exhaustive] #[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, }, /// DJBs "Twisted" Edwards curve DSA secret key. EdDSA { /// Secret scalar. scalar: ProtectedMPI, }, /// NISTs Elliptic curve DSA secret key. ECDSA { /// Secret scalar. scalar: ProtectedMPI, }, /// Elliptic curve ElGamal secret key. ECDH { /// Secret scalar. scalar: ProtectedMPI, }, /// 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::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::Unknown{ .. } => f.write_str("Unknown { <Redacted> }"), } } } } impl PartialOrd for SecretKeyMaterial { fn partial_cmp(&self, other: &SecretKeyMaterial) -> Option<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::Unknown{ .. } => 6, } } 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 Some(o1); } if o2 != Ordering::Equal { return Some(o2); } if o3 != Ordering::Equal { return Some(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::Unknown{ mpis: ref mpis1, rest: ref rest1 } ,&SecretKeyMaterial::Unknown{ mpis: ref mpis2, rest: ref rest2 }) => { let o1 = secure_cmp(rest1, rest2); let on = mpis1.iter().zip(mpis2.iter()).map(|(a,b)| { a.cmp(b) }).collect::<Vec<_>>(); iter::once(o1) .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 } }; Some(ret) } } impl Ord for SecretKeyMaterial { fn cmp(&self, other: &Self) -> Ordering { self.partial_cmp(other).unwrap() } } 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::*; 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), Unknown { .. } => None, } } } impl Hash for SecretKeyMaterial { fn hash(&self, mut hash: &mut dyn hash::Digest) { self.serialize(&mut hash as &mut dyn Write) .expect("hashing does not fail") } } #[cfg(test)] impl Arbitrary for SecretKeyMaterial { fn arbitrary(g: &mut Gen) -> Self { use crate::arbitrary_helper::gen_arbitrary_from_range; match gen_arbitrary_from_range(0..6, g) { 0 => SecretKeyMaterial::RSA { d: MPI::arbitrary(g).into(), p: MPI::arbitrary(g).into(), q: MPI::arbitrary(g).into(), u: MPI::arbitrary(g).into(), }, 1 => SecretKeyMaterial::DSA { x: MPI::arbitrary(g).into(), }, 2 => SecretKeyMaterial::ElGamal { x: MPI::arbitrary(g).into(), }, 3 => SecretKeyMaterial::EdDSA { scalar: MPI::arbitrary(g).into(), }, 4 => SecretKeyMaterial::ECDSA { scalar: MPI::arbitrary(g).into(), }, 5 => SecretKeyMaterial::ECDH { scalar: MPI::arbitrary(g).into(), }, _ => unreachable!(), } } } /// Checksum method for secret key material. /// /// Secret key material may be protected by a checksum. See [Section /// 5.5.3 of RFC 4880] for details. /// /// [Section 5.5.3 of RFC 4880]: https://tools.ietf.org/html/rfc4880#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 } } /// An encrypted session key. /// /// Provides a typed and structured way of storing multiple MPIs in /// [`PKESK`] packets. /// /// [`PKESK`]: crate::packet::PKESK /// /// Note: This enum cannot be exhaustively matched to allow future /// extensions. #[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, /// . c: MPI, }, /// Elliptic curve ElGamal public key. ECDH { /// Ephemeral key. e: MPI, /// 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. match self { RSA { .. } => Some(PublicKeyAlgorithm::RSAEncryptSign), ElGamal { .. } => Some(PublicKeyAlgorithm::ElGamalEncrypt), ECDH { .. } => Some(PublicKeyAlgorithm::ECDH), Unknown { .. } => None, } } } impl Hash for Ciphertext { fn hash(&self, mut hash: &mut dyn hash::Digest) { self.serialize(&mut hash as &mut dyn Write) .expect("hashing does not fail") } } #[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..3, 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() }, }, _ => unreachable!(), } } } /// A cryptographic signature. /// /// Provides a typed and structured way of storing multiple MPIs in /// [`Signature`] packets. /// /// [`Signature`]: crate::packet::Signature /// /// Note: This enum cannot be exhaustively matched to allow future /// extensions. #[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, }, /// 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 dyn hash::Digest) { self.serialize(&mut hash as &mut dyn Write) .expect("hashing does not fail") } } #[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..4, 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), }, _ => 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::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::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::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::Unknown { .. } => unreachable!(), }; sig == sig_ } } } �����������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-1.7.0/src/crypto/s2k.rs�������������������������������������������������������������0000644�0000000�0000000�00000054335�00726746425�0016163�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 4880]. //! //! [Section 3.7 of RFC 4880]: https://tools.ietf.org/html/rfc4880#section-3.7 use crate::Error; use crate::Result; use crate::HashAlgorithm; use crate::crypto::Password; use crate::crypto::SessionKey; use crate::crypto::hash::Digest; 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 4880]. This is used to encrypt messages with /// a password (see [`SKESK`]), and to protect secret keys (see /// [`key::Encrypted`]). /// /// [Section 3.7 of RFC 4880]: https://tools.ietf.org/html/rfc4880#section-3.7 /// [`SKESK`]: crate::packet::SKESK /// [`key::Encrypted`]: crate::packet::key::Encrypted /// /// Note: This enum cannot be exhaustively matched to allow future /// extensions. #[non_exhaustive] #[derive(Clone, PartialEq, Eq, Hash, Debug)] pub enum S2K { /// 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 }, /// 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 smart phones. /// /// [`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::Simple { hash } | &S2K::Salted { hash, .. } | &S2K::Iterated { hash, .. } => password.map(|string| { let mut hash = hash.context()?; // 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::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::Unknown { .. } | &S2K::Private { .. } => unreachable!(), } let _ = hash.digest(data); zeros.push(0); } Ok(ret.into()) }), 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)] { matches!(self, Simple { .. } | Salted { .. } | Iterated { .. }) } } /// 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::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::gen_arbitrary_from_range; #[allow(deprecated)] match gen_arbitrary_from_range(0..7, g) { 0 => S2K::Simple{ hash: HashAlgorithm::arbitrary(g) }, 1 => S2K::Salted{ hash: HashAlgorithm::arbitrary(g), salt: [<u8>::arbitrary(g); 8], }, 2 => S2K::Iterated{ hash: HashAlgorithm::arbitrary(g), salt: [<u8>::arbitrary(g); 8], hash_bytes: S2K::nearest_hash_count(Arbitrary::arbitrary(g)), }, 3 => S2K::Private { tag: gen_arbitrary_from_range(100..111, g), parameters: Option::<Vec<u8>>::arbitrary(g).map(|v| v.into()), }, 4 => S2K::Unknown { tag: 2, parameters: Option::<Vec<u8>>::arbitrary(g).map(|v| v.into()), }, 5 => S2K::Unknown { tag: gen_arbitrary_from_range(4..100, g), parameters: Option::<Vec<u8>>::arbitrary(g).map(|v| v.into()), }, 6 => S2K::Unknown { tag: gen_arbitrary_from_range(111..256, g) as u8, parameters: Option::<Vec<u8>>::arbitrary(g).map(|v| v.into()), }, _ => unreachable!(), } } } #[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-1.7.0/src/crypto/symmetric.rs�������������������������������������������������������0000644�0000000�0000000�00000045573�00726746425�0017504�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. 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_buffered_reader( algo, key, Box::new(buffered_reader::Generic::with_cookie( source, None, Default::default()))) } /// Instantiate a new symmetric decryptor. fn from_buffered_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_buffered_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.as_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; 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); 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-1.7.0/src/crypto/tests/dsa.rs�������������������������������������������������������0000644�0000000�0000000�00002261721�00726746425�0017376�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������//! Low-level DSA tests. use crate::Result; use crate::crypto::{mpi, hash::Digest}; use crate::packet::{prelude::*, signature::subpacket::*}; use crate::types::*; #[test] 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()?; h.update(msg); let mut d = h.into_digest()?; let mut 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-1.7.0/src/crypto/tests/ecdsa.rs�����������������������������������������������������0000644�0000000�0000000�00000045645�00726746425�0017711�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������//! Low-level ECDSA tests. use crate::Result; use crate::crypto::{mpi, hash::Digest}; 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()?; h.update(msg); let mut d = h.into_digest()?; let mut 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-1.7.0/src/crypto/tests/rsa.rs�������������������������������������������������������0000644�0000000�0000000�00000465150�00726746425�0017414�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������//! Low-level RSA tests. use crate::Result; use crate::crypto::{mpi, hash::Digest}; use crate::packet::{prelude::*, signature::subpacket::*}; use crate::types::*; #[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()?; h.update(msg); let mut d = h.into_digest()?; let mut 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-1.7.0/src/crypto/tests.rs�����������������������������������������������������������0000644�0000000�0000000�00000000072�00726746425�0016613�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������//! Low-level crypto tests. mod rsa; mod dsa; mod ecdsa; ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-1.7.0/src/fingerprint.rs������������������������������������������������������������0000644�0000000�0000000�00000027071�00726746425�0016470�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������use std::fmt; #[cfg(test)] use quickcheck::{Arbitrary, Gen}; /// A long identifier for certificates and keys. /// /// A `Fingerprint` uniquely identifies a public key. /// /// Currently, Sequoia supports *version 4* fingerprints and Key IDs /// only. *Version 3* fingerprints and Key IDs were deprecated by /// [RFC 4880] in 2007. /// /// Essentially, a *v4* fingerprint is a SHA-1 hash over the key's /// public key packet. For details, see [Section 12.2 of RFC 4880]. /// /// 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 12.2 of RFC 4880]: https://tools.ietf.org/html/rfc4880#section-12.2 /// [`IssuerFingerprint`]: crate::packet::signature::subpacket::SubpacketValue::IssuerFingerprint /// [birthday attack]: https://nullprogram.com/blog/2019/07/22/ /// [`KeyID`]: crate::KeyID /// [`KeyHandle`]: crate::KeyHandle /// /// Note: This enum cannot be exhaustively matched to allow future /// extensions. /// /// # 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 { /// A 20 byte SHA-1 hash of the public key packet as defined in the RFC. V4([u8;20]), /// Used for holding fingerprint data that is not a V4 fingerprint, e.g. a /// V3 fingerprint (deprecated) or otherwise wrong-length data. Invalid(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 { f.debug_tuple("Fingerprint") .field(&self.to_string()) .finish() } } impl fmt::UpperHex for Fingerprint { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.write_str(&self.convert_to_string(false)) } } impl fmt::LowerHex for Fingerprint { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let mut hex = self.convert_to_string(false); hex.make_ascii_lowercase(); f.write_str(&hex) } } impl std::str::FromStr for Fingerprint { type Err = anyhow::Error; fn from_str(s: &str) -> std::result::Result<Self, Self::Err> { Ok(Self::from_bytes(&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(&bytes), fp); /// # Ok(()) } /// ``` pub fn from_bytes(raw: &[u8]) -> Fingerprint { if raw.len() == 20 { let mut fp : [u8; 20] = Default::default(); fp.copy_from_slice(raw); Fingerprint::V4(fp) } else { Fingerprint::Invalid(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::Invalid(ref fp) => fp, } } /// 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 { format!("{:X}", self) } /// 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 { self.convert_to_string(true) } /// 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 convert_to_string(&self, pretty: bool) -> String { let raw = match self { Fingerprint::V4(ref fp) => &fp[..], Fingerprint::Invalid(ref fp) => &fp[..], }; // 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. let mut output = Vec::with_capacity( // Each byte results in to hex characters. raw.len() * 2 + if pretty { // Every 2 bytes of output, we insert a space. raw.len() / 2 // After 5 groups, there is another space. + raw.len() / 10 } else { 0 }); for (i, b) in raw.iter().enumerate() { if pretty && i > 0 && i % 2 == 0 { output.push(b' '); } if pretty && i > 0 && i % 10 == 0 { output.push(b' '); } let top = b >> 4; let bottom = b & 0xFu8; if top < 10u8 { output.push(b'0' + top) } else { output.push(b'A' + (top - 10u8)) } if bottom < 10u8 { output.push(b'0' + bottom) } else { output.push(b'A' + (bottom - 10u8)) } } // We know the content is valid UTF-8. String::from_utf8(output).unwrap() } /// 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.convert_to_string(false).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 } } #[cfg(test)] impl Arbitrary for Fingerprint { fn arbitrary(g: &mut Gen) -> Self { let mut fp = [0; 20]; fp.iter_mut().for_each(|p| *p = Arbitrary::arbitrary(g)); Fingerprint::V4(fp) } } #[cfg(test)] mod tests { use super::*; #[test] fn hex_formatting() { let fp = "0123 4567 89AB CDEF 0123 4567 89AB CDEF 0123 4567" .parse::<Fingerprint>().unwrap(); assert_eq!(format!("{:X}", fp), "0123456789ABCDEF0123456789ABCDEF01234567"); assert_eq!(format!("{:x}", fp), "0123456789abcdef0123456789abcdef01234567"); } } �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-1.7.0/src/fmt.rs��������������������������������������������������������������������0000644�0000000�0000000�00000037135�00726746425�0014731�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 { Dumper { inner, indent: indent.as_ref().into(), offset: 0, } } /// 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) = labeler( first_label_offset, &buf[data_start..i + 1]) { 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) = labeler( first_label_offset, &buf[data_start..]) { 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 { return Err(Error::InvalidArgument("Odd number of nibbles".into()).into()); } 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(), None); 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(), None); 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(), None); 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(), None); 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(), None); 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(), None); assert_eq!(fh(" 0x", true).ok(), Some(vec![])); assert_eq!(fh(" 0x0", true).ok(), None); 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-1.7.0/src/keyhandle.rs��������������������������������������������������������������0000644�0000000�0000000�00000035724�00726746425�0016111�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������use std::convert::TryFrom; use std::cmp; 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 4* fingerprints and Key ID /// only. *Version 3* fingerprints and Key ID were deprecated by [RFC /// 4880] in 2007. /// /// A *v4* fingerprint is, essentially, a 20-byte SHA-1 hash over the /// key's public key packet. A *v4* Key ID is defined as the /// fingerprint's lower 8 bytes. /// /// For the exact definition, see [Section 12.2 of RFC 4880]. /// /// 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 12.2 of RFC 4880]: https://tools.ietf.org/html/rfc4880#section-12.2 /// /// # Examples /// /// ```rust /// # fn main() -> sequoia_openpgp::Result<()> { /// # use sequoia_openpgp as openpgp; /// use openpgp::KeyHandle; /// 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); /// assert_eq!(&issuers[0], /// &KeyHandle::Fingerprint( /// "C03F A641 1B03 AE12 5764 6118 7223 B566 78E0 2528" /// .parse()?)); /// assert_eq!(&issuers[1], /// &KeyHandle::KeyID("7223 B566 78E0 2528".parse()?)); /// } 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<&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(); let l = cmp::min(a.len(), b.len()); // Do a little endian comparison so that for v4 keys (where // the KeyID is a suffix of the Fingerprint) equivalent KeyIDs // and Fingerprints sort next to each other. for (a, b) in a[a.len()-l..].iter().zip(b[b.len()-l..].iter()) { let cmp = a.cmp(b); if cmp != Ordering::Equal { return Some(cmp); } } if a.len() == b.len() { Some(Ordering::Equal) } else { // One (a KeyID) is the suffix of the other (a // Fingerprint). None } } } 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(bytes) { fpr @ Fingerprint::Invalid(_) => { 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. /// ``` /// /// In these cases (and only these cases) `KeyHandle`'s /// `PartialOrd` implementation returns `None` to correctly /// indicate that a comparison is not possible. /// /// 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!(! fpr1.eq(&fpr2)); /// assert!(fpr1.aliases(&keyid)); /// assert!(fpr2.aliases(&keyid)); /// assert!(! fpr1.aliases(&fpr2)); /// # Ok(()) } /// ``` pub fn aliases<H>(&self, other: H) -> bool where H: Borrow<KeyHandle> { // This works, because the PartialOrd implementation only // returns None if one value is a fingerprint and the other is // a key id that matches the fingerprint's key id. self.partial_cmp(other.borrow()).unwrap_or(Ordering::Equal) == Ordering::Equal } /// 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::Invalid(_)) | KeyHandle::KeyID(KeyID::Invalid(_))) } /// 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 super::*; #[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::Invalid(Box::new([10, 2, 3, 4]))); assert_eq!(format!("{:X}", handle), "0A020304"); let handle = KeyHandle::KeyID(KeyID::V4([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::Invalid(Box::new([10, 2, 3, 4]))); assert_eq!(format!("{:x}", handle), "0a020304"); let handle = KeyHandle::KeyID(KeyID::V4([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::V4(_)) = &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::Invalid(_)) = &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(()) } } ��������������������������������������������sequoia-openpgp-1.7.0/src/keyid.rs������������������������������������������������������������������0000644�0000000�0000000�00000031532�00726746425�0015243�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������use std::fmt; #[cfg(test)] use quickcheck::{Arbitrary, Gen}; use crate::Error; use crate::Fingerprint; use crate::Result; /// A short identifier for certificates and keys. /// /// A `KeyID` identifies a public key. It is used, for example, to /// reference the issuing key of a signature in its [`Issuer`] /// subpacket. /// /// Currently, Sequoia supports *version 4* fingerprints and Key IDs /// only. *Version 3* fingerprints and Key IDs were deprecated by /// [RFC 4880] in 2007. /// /// A *v4* `KeyID` is defined as a fragment (the lower 8 bytes) of the /// key's fingerprint, which in turn is essentially a SHA-1 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 /// 12.2 of RFC 4880]. /// /// 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 12.2 of RFC 4880]: https://tools.ietf.org/html/rfc4880#section-12.2 /// [birthday attack]: https://nullprogram.com/blog/2019/07/22/ /// [`Issuer`]: crate::packet::signature::subpacket::SubpacketValue::Issuer /// [`Fingerprint`]: crate::Fingerprint /// [`KeyHandle`]: crate::KeyHandle /// /// Note: This enum cannot be exhaustively matched to allow future /// extensions. /// /// # 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 { /// Lower 8 byte SHA-1 hash. V4([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 { f.write_str(&self.convert_to_string(false)) } } impl fmt::LowerHex for KeyID { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let mut hex = self.convert_to_string(false); hex.make_ascii_lowercase(); f.write_str(&hex) } } impl std::str::FromStr for KeyID { type Err = anyhow::Error; fn from_str(s: &str) -> std::result::Result<Self, Self::Err> { 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 { // 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::V4(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::Invalid(fp) => { KeyID::Invalid(fp.clone()) } } } } impl From<Fingerprint> for KeyID { fn from(fp: Fingerprint) -> Self { match fp { Fingerprint::V4(fp) => KeyID::from_bytes(&fp[fp.len() - 8..]), Fingerprint::Invalid(fp) => { KeyID::Invalid(fp) } } } } 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::V4(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::V4(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::V4(ref id) => id, KeyID::Invalid(ref id) => id, } } /// Creates a wildcard `KeyID`. /// /// Refer to [Section 5.1 of RFC 4880] for details. /// /// [Section 5.1 of RFC 4880]: https://tools.ietf.org/html/rfc4880#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 4880] for details. /// /// [Section 5.1 of RFC 4880]: https://tools.ietf.org/html/rfc4880#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 { format!("{:X}", self) } /// 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 { self.convert_to_string(true) } /// 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 convert_to_string(&self, pretty: bool) -> String { let raw = match self { KeyID::V4(ref fp) => &fp[..], KeyID::Invalid(ref fp) => &fp[..], }; // We currently only handle V4 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. let mut output = Vec::with_capacity( // Each byte results in to hex characters. raw.len() * 2 + if pretty { // Every 2 bytes of output, we insert a space. raw.len() / 2 } else { 0 }); for (i, b) in raw.iter().enumerate() { if pretty && i > 0 && i % 2 == 0 { output.push(b' '); } let top = b >> 4; let bottom = b & 0xFu8; if top < 10u8 { output.push(b'0' + top) } else { output.push(b'A' + (top - 10u8)) } if bottom < 10u8 { output.push(b'0' + bottom) } else { output.push(b'A' + (bottom - 10u8)) } } // We know the content is valid UTF-8. String::from_utf8(output).unwrap() } } #[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(); assert_match!(KeyID::Invalid(_) = "587DAEF1".parse().unwrap()); assert_match!(KeyID::Invalid(_) = "0x587DAEF1".parse().unwrap()); } #[test] fn hex_formatting() { let keyid = "FB3751F1587DAEF1".parse::<KeyID>().unwrap(); assert_eq!(format!("{:X}", keyid), "FB3751F1587DAEF1"); assert_eq!(format!("{:x}", keyid), "fb3751f1587daef1"); } } ����������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-1.7.0/src/lib.rs��������������������������������������������������������������������0000644�0000000�0000000�00000026274�00726746425�0014713�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������//! OpenPGP data types and associated machinery. //! //! This crate aims to provide a complete implementation of OpenPGP as //! defined by [RFC 4880] as well as some extensions (e.g., [RFC //! 6637], which describes ECC cryptography for OpenPGP. This //! 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 4880] does not define //! trust models, such as the web of trust, direct trust, or TOFU. //! Neither does this crate. [RFC 4880] 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 4880]: https://tools.ietf.org/html/rfc4880 //! [RFC 6637]: https://tools.ietf.org/html/rfc6637 //! //! # Experimental Features //! //! This crate implements functionality from [RFC 4880bis], notably //! AEAD encryption containers. As of this writing, this RFC is still //! a draft and the syntax or semantic defined in it may change or go //! away. Therefore, all related functionality may change and //! artifacts created using this functionality may not be usable in //! the future. Do not use it for things other than experiments. //! //! [RFC 4880bis]: https://tools.ietf.org/html/draft-ietf-openpgp-rfc4880bis-08 #![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)] #[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"); /// Crate result specialization. pub type Result<T> = ::std::result::Result<T, anyhow::Error>; /// Errors used in this crate. /// /// Note: This enum cannot be exhaustively matched to allow future /// extensions. #[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), /// 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), /// 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("{0} is not considered secure{}", .1.as_ref().map(|t| format!(" since {}", crate::fmt::time(t))) .unwrap_or_else(|| "".into()))] PolicyViolation(String, Option<std::time::SystemTime>), } 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!() } } } ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-1.7.0/src/macros.rs�����������������������������������������������������������������0000644�0000000�0000000�00000015577�00726746425�0015435�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 { use std::convert::TryFrom; 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 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 milli-seconds), then it /// displays the output on stderr. The output is prefixed with label, /// if it is provided. /// /// ``` /// let result = time_it!("Some code", 10, { /// // Some code. /// 5 /// }); /// assert_eq!(result, 5); /// ``` #[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) }; } /// A simple shortcut for ensuring a type is 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> {} /// assert_send_and_sync!(MyStruct<'_>); /// ``` /// /// 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: io::Write> {} /// assert_send_and_sync!(MyWriterStruct<W> where W: 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: io::Write> {}; /// assert_send_and_sync!(MyWriterStruct<'_, C, W> where C, W: 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 `io::Write`) may not be followed /// by `+` characters. 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 {} }; } ���������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-1.7.0/src/message/grammar.lalrpop���������������������������������������������������0000644�0000000�0000000�00000002651�00726746425�0020235�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������// -*- mode: Rust; -*- use crate::message::lexer; grammar; pub Message: () = { LITERAL, CompressedData, EncryptedPart, SignedPart, OPAQUE_CONTENT, }; CompressedData: () = { COMPRESSED_DATA Message POP }; SeipPart: () = { SEIP Message MDC POP, SEIP OPAQUE_CONTENT POP, } AedPart: () = { AED Message POP, } // An encrypted part is 0 or more ESKs followed by an encryption container. EncryptedPart: () = { EncryptionContainer, ESKS EncryptionContainer, }; EncryptionContainer: () = { SeipPart, 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, SEIP => lexer::Token::SEIP, 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-1.7.0/src/message/lexer.rs����������������������������������������������������������0000644�0000000�0000000�00000004064�00726746425�0016701�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. /// /// Note: This enum cannot be exhaustively matched to allow future /// extensions. #[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 SEIP packet. SEIP, /// 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-1.7.0/src/message/mod.rs������������������������������������������������������������0000644�0000000�0000000�00000114363�00726746425�0016345�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 11.3 of RFC 4880]. //! //! 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 11.3 of RFC 4880]: https://tools.ietf.org/html/rfc4880#section-11.3 use std::convert::TryFrom; use std::fmt; use std::io; use std::path::Path; use crate::Result; use crate::Error; use crate::Packet; use crate::PacketPile; use crate::packet::Literal; use crate::packet::Tag; use crate::parse::Parse; mod lexer; lalrpop_util::lalrpop_mod!(#[allow(clippy::all)] grammar, "/message/grammar.rs"); use self::lexer::{Lexer, LexicalError}; pub use self::lexer::Token; use lalrpop_util::ParseError; use self::grammar::MessageParser; /// Errors that MessageValidator::check may return. /// /// Note: This enum cannot be exhaustively matched to allow future /// extensions. #[non_exhaustive] #[derive(Debug, Clone)] pub 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, 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 => Token::SEIP, 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; }, _ => { // 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 11.3 of RFC /// 4880]. /// /// [Section 11.3 of RFC 4880]: https://tools.ietf.org/html/rfc4880#section-11.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://tools.ietf.org/html/rfc4880#section-6.6 /// /// # 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_reader<R: 'a + io::Read + Send + Sync>(reader: R) -> Result<Self> { Self::try_from(PacketPile::from_reader(reader)?) } /// Reads a `Message` from the specified file. /// /// See [`Message::try_from`] for more details. /// /// [`Message::try_from`]: Message::try_from() fn from_file<P: AsRef<Path>>(path: P) -> Result<Self> { Self::try_from(PacketPile::from_file(path)?) } /// Reads a `Message` from `buf`. /// /// See [`Message::try_from`] for more details. /// /// [`Message::try_from`]: Message::try_from() fn from_bytes<D: AsRef<[u8]> + ?Sized + Send + Sync>(data: &'a D) -> Result<Self> { Self::try_from(PacketPile::from_bytes(data)?) } } 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::new(&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 } } 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 11.3 of /// RFC 4880]. /// /// Caveats: this function assumes that any still encrypted parts /// or still compressed parts are valid messages. /// /// [Section 11.3 of RFC 4880]: https://tools.ietf.org/html/rfc4880#section-11.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(), &path), } match packet { Packet::CompressedData(_) | Packet::SEIP(_) | Packet::AED(_) => { // 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 } } impl ::std::ops::Deref for Message { type Target = PacketPile; fn deref(&self) -> &Self::Target { &self.pile } } #[cfg(test)] mod tests { use super::*; use crate::types::DataFormat::Text; 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: &[SEIP, Literal, MDC, Pop], result: true, }, TestVector { s: &[CompressedData, SEIP, Literal, MDC, Pop, Pop], result: true, }, TestVector { s: &[CompressedData, SEIP, CompressedData, Literal, Pop, MDC, Pop, Pop], result: true, }, TestVector { s: &[SEIP, MDC, Pop], result: false, }, TestVector { s: &[SKESK, SEIP, Literal, MDC, Pop], result: true, }, TestVector { s: &[PKESK, SEIP, Literal, MDC, Pop], result: true, }, TestVector { s: &[SKESK, SKESK, SEIP, Literal, MDC, Pop], result: true, }, TestVector { s: &[AED, Literal, Pop], result: true, }, TestVector { s: &[CompressedData, AED, Literal, Pop, Pop], result: true, }, TestVector { s: &[CompressedData, AED, CompressedData, Literal, Pop, Pop, Pop], result: true, }, TestVector { s: &[AED, Pop], result: false, }, TestVector { s: &[SKESK, AED, Literal, Pop], result: true, }, TestVector { s: &[PKESK, AED, Literal, Pop], result: true, }, TestVector { s: &[SKESK, SKESK, AED, 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, SEIP, OPS, SEIP, 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: &[SEIP, CompressedData, OpaqueContent, Pop, MDC, Pop], result: true, }, TestVector { s: &[SEIP, 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, isize)], result: bool, } let test_vectors = [ TestVector { s: &[(Literal, 0)][..], result: true, }, TestVector { s: &[(CompressedData, 0), (Literal, 1)], result: true, }, TestVector { s: &[(CompressedData, 0), (CompressedData, 1), (Literal, 2)], result: true, }, TestVector { s: &[(SEIP, 0), (Literal, 1), (MDC, 1)], result: true, }, TestVector { s: &[(CompressedData, 0), (SEIP, 1), (Literal, 2), (MDC, 2)], result: true, }, TestVector { s: &[(CompressedData, 0), (SEIP, 1), (CompressedData, 2), (Literal, 3), (MDC, 2)], result: true, }, TestVector { s: &[(CompressedData, 0), (SEIP, 1), (CompressedData, 2), (Literal, 3), (MDC, 3)], result: false, }, TestVector { s: &[(SEIP, 0), (MDC, 0)], result: false, }, TestVector { s: &[(SKESK, 0), (SEIP, 0), (Literal, 1), (MDC, 1)], result: true, }, TestVector { s: &[(PKESK, 0), (SEIP, 0), (Literal, 1), (MDC, 1)], result: true, }, TestVector { s: &[(PKESK, 0), (SEIP, 0), (CompressedData, 1), (Literal, 2), (MDC, 1)], result: true, }, TestVector { s: &[(SKESK, 0), (SKESK, 0), (SEIP, 0), (Literal, 1), (MDC, 1)], result: true, }, TestVector { s: &[(OnePassSig, 0), (Literal, 0), (Signature, 0)], result: true, }, TestVector { s: &[(OnePassSig, 0), (CompressedData, 0), (Literal, 1), (Signature, 0)], result: true, }, TestVector { s: &[(OnePassSig, 0), (OnePassSig, 0), (Literal, 0), (Signature, 0), (Signature, 0)], result: true, }, TestVector { s: &[(OnePassSig, 0), (OnePassSig, 0), (Literal, 0), (Signature, 0)], result: false, }, TestVector { s: &[(OnePassSig, 0), (OnePassSig, 0), (SEIP, 0), (OnePassSig, 1), (SEIP, 1), (Literal, 2), (MDC, 2), (Signature, 1), (MDC, 1), (Signature, 0), (Signature, 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, 0), (OnePassSig, 0), (Literal, 0), (Signature, 0)], result: true, }, ]; for v in &test_vectors { let mut l = MessageValidator::new(); for (token, depth) in v.s.iter() { l.push(*token, &(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(Text); lit.set_body(b"data".to_vec()); packets.push(lit.into()); let message = Message::try_from(packets); assert!(message.is_ok(), "{:?}", message); } #[allow(clippy::vec_init_then_push)] #[test] fn compressed_part() { let mut lit = Literal::new(Text); 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); } #[allow(clippy::vec_init_then_push)] #[test] fn one_pass_sig_part() { let mut lit = Literal::new(Text); lit.set_body(b"data".to_vec()); let hash = crate::types::HashAlgorithm::SHA512; let key: key::SecretKey = crate::packet::key::Key4::generate_ecc(true, crate::types::Curve::Ed25519) .unwrap().into(); let mut pair = key.clone().into_keypair().unwrap(); let sig = crate::packet::signature::SignatureBuilder::new(SignatureType::Binary) .sign_hash(&mut pair, hash.context().unwrap()).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); } #[allow(clippy::vec_init_then_push)] #[test] fn signature_part() { let mut lit = Literal::new(Text); lit.set_body(b"data".to_vec()); let hash = crate::types::HashAlgorithm::SHA512; let key: key::SecretKey = crate::packet::key::Key4::generate_ecc(true, crate::types::Curve::Ed25519) .unwrap().into(); let mut pair = key.clone().into_keypair().unwrap(); let sig = crate::packet::signature::SignatureBuilder::new(SignatureType::Binary) .sign_hash(&mut pair, hash.context().unwrap()).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(Text); 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()); #[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.children_mut().unwrap().push( lit.clone().into()); seip.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( "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, &[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, &[0]); assert!(matches!(l.check(), MessageValidity::MessagePrefix)); l.finish(); assert!(matches!(l.check(), MessageValidity::Message)); } } �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-1.7.0/src/packet/aed.rs�������������������������������������������������������������0000644�0000000�0000000�00000021357�00726746425�0016142�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������//! AEAD encrypted data packets. //! //! An encryption container using [Authenticated Encryption with //! Additional Data]. //! //! The AED packet is a new packet specified in [Section 5.16 of RFC //! 4880bis]. Its aim is to replace the [SEIP packet], whose security //! has been partially compromised. SEIP's weaknesses includes its //! use of CFB mode (e.g., EFAIL-style CFB gadgets, see Section 5.3 of //! the [EFAIL paper]), its use of [SHA-1] for integrity protection, and //! the ability to [downgrade SEIP packets] to much weaker SED //! packets. //! //! Although the decision to use AEAD is uncontroversial, the design //! specified in RFC 4880bis is. According to [RFC 5116], decrypted //! AEAD data can only be released for processing after its //! authenticity has been checked: //! //! > [The authenticated decryption operation] has only a single //! > output, either a plaintext value P or a special symbol FAIL that //! > indicates that the inputs are not authentic. //! //! The controversy has to do with streaming, which OpenPGP has //! traditionally supported. Streaming a message means that the //! amount of data that needs to be buffered when processing a message //! is independent of the message's length. //! //! At first glance, the AEAD mechanism in RFC 4880bis appears to //! support this mode of operation: instead of encrypting the whole //! message using AEAD, which would require buffering all of the //! plaintext when decrypting the message, the message is chunked, the //! individual chunks are linked together, and AEAD is used to encrypt //! and protect each individual chunk. Because the plaintext from an //! individual chunk can be integrity checked, an implementation only //! needs to buffer a chunk worth of data. //! //! Unfortunately, RFC 4880bis allows chunk sizes that are, in //! practice, unbounded. Specifically, a chunk can be up to 4 //! exbibytes in size. Thus when encountering messages that can't be //! buffered, an OpenPGP implementation has a choice: it can either //! release data that has not been integrity checked and violate RFC //! 5116, or it can fail to process the message. As of 2020, [GnuPG] //! and [RNP] process unauthenticated plaintext. From a user //! perspective, it then appears that implementations that choose to //! follow RFC 5116 are impaired: "GnuPG can decrypt it," they think, //! "why can't Sequoia?" This creates pressure on other //! implementations to also behave insecurely. //! //! [Werner argues] that AEAD is not about authenticating the data. //! That is the purpose of the signature. The reason to introduce //! AEAD is to get the benefits of more modern cryptography, and to be //! able to more quickly detect rare transmission errors. Our //! position is that an integrity check provides real protection: it //! can detect modified ciphertext. And, if we are going to stream, //! then this protection is essential as it protects the user from //! real, demonstrated attacks like [EFAIL]. //! //! RFC 4880bis has not been finalized. So, it is still possible that //! the AEAD mechanism will change (which is why the AED packet is //! marked as experimental). Despite our concerns, because other //! OpenPGP implementations already emit the AEAD packet, we provide //! *experimental* support for it in Sequoia. //! //! [Authenticated Encryption with Additional Data]: https://en.wikipedia.org/wiki/Authenticated_encryption //! [Section 5.16 of RFC 4880bis]: https://tools.ietf.org/html/draft-ietf-openpgp-rfc4880bis-09#section-5.16 //! [EFAIL paper]: https://www.usenix.org/conference/usenixsecurity18/presentation/poddebniak //! [SHA-1]: https://sha-mbles.github.io/ //! [SEIP packet]: https://tools.ietf.org/html/rfc4880#section-5.13 //! [RFC 5116]: https://tools.ietf.org/html/rfc5116#section-2.2 //! [downgrade SEIP packets]: https://mailarchive.ietf.org/arch/msg/openpgp/JLn7sL6TqikUf-cD34lN7kof7_A/ //! [GnuPG]: https://mailarchive.ietf.org/arch/msg/openpgp/fmQgRm94jhvPLEOi0J-o7A8LpkY/ //! [RNP]: https://github.com/rnpgp/rnp/issues/807 //! [Werner argues]: https://mailarchive.ietf.org/arch/msg/openpgp/J428Mqq3-pHTU4C76EgP5sPkvtA //! [EFAIL]: https://efail.de/ use crate::types::{ AEADAlgorithm, SymmetricAlgorithm, }; use crate::packet; use crate::Packet; use crate::Error; use crate::Result; /// Holds an AEAD encrypted data packet. /// /// An AEAD encrypted data packet holds encrypted data. The data /// contains additional OpenPGP packets. See [Section 5.16 of RFC /// 4880bis] for details. /// /// An AED 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`]. /// /// This feature is /// [experimental](super::super#experimental-features). It has /// not been standardized and we advise users to not emit AED packets. /// /// [Section 5.16 of RFC 4880bis]: https://tools.ietf.org/html/draft-ietf-openpgp-rfc4880bis-05#section-5.16 /// [streaming serializer]: crate::serialize::stream /// [`PacketParser`]: crate::parse::PacketParser /// /// # A note on equality /// /// An unprocessed (encrypted) `AED` 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 AED1 { /// CTB packet header fields. pub(crate) common: packet::Common, /// Symmetric algorithm. sym_algo: SymmetricAlgorithm, /// AEAD algorithm. aead: AEADAlgorithm, /// Chunk size. chunk_size: u64, /// Initialization vector for the AEAD algorithm. iv: Box<[u8]>, /// This is a container packet. container: packet::Container, } assert_send_and_sync!(AED1); impl std::ops::Deref for AED1 { type Target = packet::Container; fn deref(&self) -> &Self::Target { &self.container } } impl std::ops::DerefMut for AED1 { fn deref_mut(&mut self) -> &mut Self::Target { &mut self.container } } impl AED1 { /// Creates a new AED1 object. pub fn new(sym_algo: SymmetricAlgorithm, aead: AEADAlgorithm, chunk_size: u64, iv: Box<[u8]>) -> 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(AED1 { common: Default::default(), sym_algo, aead, chunk_size, iv, 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 initialization vector for the AEAD algorithm. pub fn iv(&self) -> &[u8] { &self.iv } /// Sets the initialization vector for the AEAD algorithm. pub fn set_iv(&mut self, iv: Box<[u8]>) -> Box<[u8]> { ::std::mem::replace(&mut self.iv, iv) } } impl From<AED1> for Packet { fn from(p: AED1) -> Self { super::AED::from(p).into() } } impl From<AED1> for super::AED { fn from(p: AED1) -> Self { super::AED::V1(p) } } ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-1.7.0/src/packet/compressed_data.rs�������������������������������������������������0000644�0000000�0000000�00000006746�00726746425�0020553�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 /// 4880] 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 4880]: https://tools.ietf.org/html/rfc4880#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 std::ops::Deref for CompressedData { type Target = packet::Container; fn deref(&self) -> &Self::Target { &self.container } } impl std::ops::DerefMut for CompressedData { fn deref_mut(&mut self) -> &mut Self::Target { &mut self.container } } 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 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-1.7.0/src/packet/container.rs�������������������������������������������������������0000644�0000000�0000000�00000034513�00726746425�0017371�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, }; /// 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`] or /// [`AED`]) 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 /// [`AED`]: crate::packet::AED 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 std::ops::Deref for Container { type Target = Body; fn deref(&self) -> &Self::Target { &self.body } } 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 { lazy_static::lazy_static!{ static ref DIGEST: u64 = { Container::make_body_hash().digest() }; } *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. 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. 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_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"), } } } }; } impl Packet { pub(crate) // for packet_pile.rs fn container_ref(&self) -> Option<&Container> { use std::ops::Deref; match self { Packet::CompressedData(p) => Some(p.deref()), Packet::SEIP(p) => Some(p.deref()), Packet::AED(p) => Some(p.deref()), 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> { use std::ops::DerefMut; match self { Packet::CompressedData(p) => Some(p.deref_mut()), Packet::SEIP(p) => Some(p.deref_mut()), Packet::AED(p) => Some(p.deref_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 of 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)] 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-1.7.0/src/packet/header/ctb.rs������������������������������������������������������0000644�0000000�0000000�00000020670�00726746425�0017406�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 4880] for more details. //! //! [Section 4.2 of RFC 4880]: https://tools.ietf.org/html/rfc4880#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 4880] for more details. /// /// [Section 4.2 of RFC 4880]: https://tools.ietf.org/html/rfc4880#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 4880] for more details. /// /// [Section 4.2 of RFC 4880]: https://tools.ietf.org/html/rfc4880#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.1 of RFC 4880] for more details. /// /// [old CTB]: CTBOld /// [Section 4.2.1 of RFC 4880]: https://tools.ietf.org/html/rfc4880#section-4.2.1 #[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 4880] for more details. /// /// [Section 4.2 of RFC 4880]: https://tools.ietf.org/html/rfc4880#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 [`Error::InvalidArgument`] if the tag or the body /// length cannot be expressed using an old-style CTB. /// /// [`Error::InvalidArgument`]: super::super::Error::InvalidArgument 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 4880]. This /// function parses both new and old format CTBs. /// /// [Section 4.2 of RFC 4880]: https://tools.ietf.org/html/rfc4880#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-1.7.0/src/packet/header/mod.rs������������������������������������������������������0000644�0000000�0000000�00000023762�00726746425�0017422�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://tools.ietf.org/html/rfc4880#section-4.3 //! [packet's length]: https://tools.ietf.org/html/rfc4880#section-4.2.1 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 4880] for details. /// /// [Section 4.2 of RFC 4880]: https://tools.ietf.org/html/rfc4880#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 consider 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://tools.ietf.org/html/rfc4880#section-4.2.2.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://tools.ietf.org/html/rfc4880#section-4.2.2.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. An s2k // specification (at least 1 byte), an // optional encryption session key. (3..10 * 1024).contains(&l), Tag::PKESK => // 10 bytes of fixed header, plus the // encrypted session key. 10 < l && l < 10 * 1024, Tag::OnePassSig if ! future_compatible => l == 13, 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, }; 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 4880] for more details. /// /// [Section 4.2 of RFC 4880]: https://tools.ietf.org/html/rfc4880#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); ��������������sequoia-openpgp-1.7.0/src/packet/key/conversions.rs�������������������������������������������������0000644�0000000�0000000�00000046472�00726746425�0020556�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? unsafe { std::mem::transmute($x) } } } macro_rules! convert_ref { ( $x:ident ) => { // XXX: This is ugly, but how can we do better? #[allow(clippy::transmute_ptr_to_ptr)] 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) } } } } // 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()) } } } } } 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 `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 `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) -> &$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 { convert!(p) } } 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) } } } } 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 { convert!(p) } } 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) } } } } // 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!(<PublicParts, PrimaryRole> -> <SecretParts, SubordinateRole>); f!(<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!(<PublicParts, SubordinateRole> -> <SecretParts, PrimaryRole>); //f!(<PublicParts, SubordinateRole> -> <SecretParts, SubordinateRole>); f!(<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!(<PublicParts, UnspecifiedRole> -> <SecretParts, PrimaryRole>); f!(<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!(<UnspecifiedParts, PrimaryRole> -> <SecretParts, SubordinateRole>); f!(<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!(<UnspecifiedParts, SubordinateRole> -> <SecretParts, PrimaryRole>); //f!(<UnspecifiedParts, SubordinateRole> -> <SecretParts, SubordinateRole>); f!(<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!(<UnspecifiedParts, UnspecifiedRole> -> <SecretParts, PrimaryRole>); f!(<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> { convert!(self) } /// 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 `SubordinateRole`. pub fn role_into_subordinate(self) -> $Key<$($l, )* P, SubordinateRole> { convert!(self) } /// 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 `UnspecifiedRole`. pub fn role_into_unspecified(self) -> $Key<$($l, )* P, UnspecifiedRole> { convert!(self) } /// Changes the key's role tag to `UnspecifiedRole`. pub fn role_as_unspecified(&$($l)* self) -> &$($l)* $Key<$($l, )* P, UnspecifiedRole> { convert_ref!(self) } } } } create_conversions!(Key<>); create_conversions!(Key4<>); 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-1.7.0/src/packet/key.rs�������������������������������������������������������������0000644�0000000�0000000�00000244206�00726746425�0016201�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 deprecated [version //! 3], the current [version 4], and the proposed [version 5] //! formats). 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 4 keys ([`Key4`]). //! However, future versions may include limited support for version 3 //! keys to allow working with archived messages, and we intend to add //! support for version 5 keys once the new version of the //! specification has been finalized. //! //! 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://tools.ietf.org/html/rfc4880#section-5.5.2 //! [version 5]: https://tools.ietf.org/html/draft-ietf-openpgp-rfc4880bis-09.html#name-public-key-packet-formats //! [public keys]: https://tools.ietf.org/html/rfc4880#section-5.5.1.1 //! [secret keys]: https://tools.ietf.org/html/rfc4880#section-5.5.1.3 //! [public subkeys]: https://tools.ietf.org/html/rfc4880#section-5.5.1.2 //! [secret subkeys]: https://tools.ietf.org/html/rfc4880#section-5.5.1.4 //! [`Key::decrypt_secret`]: super::Key::decrypt_secret() //! //! # Key Creation //! //! 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::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 for the key to be usable. //! //! [`Key4::generate_rsa`]: Key4::generate_rsa() //! [`Key4::generate_ecc`]: Key4::generate_ecc() //! [`Key4::import_public_cv25519`]: Key4::import_public_cv25519() //! [`Key4::import_public_ed25519`]: Key4::import_public_ed25519() //! [`Key4::import_public_rsa`]: Key4::import_public_rsa() //! [`Key4::import_secret_cv25519`]: Key4::import_secret_cv25519() //! [`Key4::import_secret_ed25519`]: Key4::import_secret_ed25519() //! [`Key4::import_secret_rsa`]: Key4::import_secret_rsa() //! //! # 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::cmp::Ordering; use std::convert::TryInto; use std::hash::Hasher; use std::time; #[cfg(test)] use quickcheck::{Arbitrary, Gen}; use crate::Error; use crate::cert::prelude::*; use crate::crypto::{self, mem, mpi, hash::{Hash, Digest}}; use crate::packet; use crate::packet::prelude::*; use crate::PublicKeyAlgorithm; use crate::seal; use crate::SymmetricAlgorithm; use crate::HashAlgorithm; use crate::types::{Curve, Timestamp}; use crate::crypto::S2K; use crate::Result; use crate::crypto::Password; use crate::KeyID; use crate::Fingerprint; use crate::KeyHandle; use crate::policy::HashAlgoSecurity; mod conversions; /// 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(None, 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(None, 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; } /// 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() } } /// A marker that indicates the `Key` should 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() } } /// 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 } } /// 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>; /// 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 4880] and [the documentation for `Key`] /// for more details. /// /// [Section 5.5 of RFC 4880]: https://tools.ietf.org/html/rfc4880#section-5.5 /// [the documentation for `Key`]: super::Key /// [`Key`]: super::Key #[derive(Clone)] pub struct Key4<P, R> where P: KeyParts, R: KeyRole { /// CTB packet header fields. pub(crate) common: packet::Common, /// When the key was created. creation_time: Timestamp, /// Public key algorithm of this signature. pk_algo: PublicKeyAlgorithm, /// Public key MPIs. mpis: mpi::PublicKey, /// Optional secret part of the key. secret: Option<SecretKeyMaterial>, p: std::marker::PhantomData<P>, r: std::marker::PhantomData<R>, } 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, { match self.mpis.cmp(&b.mpis) { Ordering::Equal => (), o => return o, } match self.creation_time.cmp(&b.creation_time) { Ordering::Equal => (), o => return o, } 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<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, 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); Self::new( ctime.into().unwrap_or_else(crate::now), PublicKeyAlgorithm::ECDH, mpi::PublicKey::ECDH { curve: Curve::Cv25519, hash: hash.into().unwrap_or(HashAlgorithm::SHA512), sym: sym.into().unwrap_or(SymmetricAlgorithm::AES256), q: mpi::MPI::new(&point), }) } /// Creates an OpenPGP public key packet from existing Ed25519 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_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), p: std::marker::PhantomData, r: std::marker::PhantomData, }) } } 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() } /// 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> { 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 { ::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 { &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 { ::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 12.2 of RFC 4880]. /// /// [Section 12.2 of RFC 4880]: https://tools.ietf.org/html/rfc4880#section-12.2 pub fn key_handle(&self) -> KeyHandle { self.fingerprint().into() } /// Computes and returns the `Key`'s `Fingerprint`. /// /// See [Section 12.2 of RFC 4880]. /// /// [Section 12.2 of RFC 4880]: https://tools.ietf.org/html/rfc4880#section-12.2 pub fn fingerprint(&self) -> Fingerprint { let mut h = HashAlgorithm::SHA1.context().unwrap(); self.hash(&mut h); let mut digest = vec![0u8; h.digest_size()]; let _ = h.digest(&mut digest); Fingerprint::from_bytes(digest.as_slice()) } /// Computes and returns the `Key`'s `Key ID`. /// /// See [Section 12.2 of RFC 4880]. /// /// [Section 12.2 of RFC 4880]: https://tools.ietf.org/html/rfc4880#section-12.2 pub fn keyid(&self) -> KeyID { self.fingerprint().into() } } 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) } } } } 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://tools.ietf.org/html/rfc4880#section-5.5.3 /// [KDF]: https://tools.ietf.org/html/rfc4880#section-3.7 /// [`Key::decrypt_secret`]: super::Key::decrypt_secret() pub fn decrypt_secret(mut self, password: &Password) -> Result<Self> { let pk_algo = self.pk_algo; self.secret_mut().decrypt_in_place(pk_algo, password)?; Ok(self) } /// 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://tools.ietf.org/html/rfc4880#section-5.5.3 /// [KDF]: https://tools.ietf.org/html/rfc4880#section-3.7 /// [`Key::encrypt_secret`]: super::Key::encrypt_secret() pub fn encrypt_secret(mut self, password: &Password) -> Result<Key4<SecretParts, R>> { self.secret_mut().encrypt_in_place(password)?; Ok(self) } } 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) } } /// 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(mut self, pk_algo: PublicKeyAlgorithm, password: &Password) -> Result<Self> { self.decrypt_in_place(pk_algo, 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(&mut self, pk_algo: PublicKeyAlgorithm, password: &Password) -> Result<()> { match self { SecretKeyMaterial::Encrypted(e) => { *self = e.decrypt(pk_algo, 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(mut self, password: &Password) -> Result<Self> { self.encrypt_in_place(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(&mut self, password: &Password) -> Result<()> { match self { SecretKeyMaterial::Unencrypted(ref u) => { *self = SecretKeyMaterial::Encrypted( u.encrypt(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 an 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::Marshal; // We need to store the type. let mut plaintext = vec![mpis.algo().unwrap_or(PublicKeyAlgorithm::Unknown(0)).into()]; mpis.serialize(&mut plaintext) .expect("MPI serialization to vec failed"); Unencrypted { mpis: mem::Encrypted::new(plaintext.into()), } } } 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::parse(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 an [AES 256] key /// derived from the `password` using the default [`S2K`] scheme. /// /// [AES 256]: crate::types::SymmetricAlgorithm::AES256 /// [`S2K`]: super::super::crypto::S2K pub fn encrypt(&self, password: &Password) -> Result<Encrypted> { use std::io::Write; use crate::crypto::symmetric::Encryptor; let s2k = S2K::default(); let algo = SymmetricAlgorithm::AES256; let key = s2k.derive_key(password, algo.key_size()?)?; // Ciphertext is preceded by a random block. let mut trash = vec![0u8; algo.block_size()?]; crypto::random(&mut trash); let checksum = Default::default(); let mut esk = Vec::new(); { let mut encryptor = Encryptor::new(algo, &key, &mut esk)?; encryptor.write_all(&trash)?; self.map(|mpis| mpis.serialize_with_checksum(&mut encryptor, checksum))?; } Ok(Encrypted::new(s2k, algo, 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, /// 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<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.checksum == other.checksum // 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(self.raw_ciphertext()); b.extend_from_slice(other.raw_ciphertext()); a == b } } } 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.checksum.hash(state); // 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(self.raw_ciphertext()); 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(ciphertext)) } /// Creates a new encrypted key object. pub(crate) fn new_raw(s2k: S2K, algo: SymmetricAlgorithm, checksum: Option<mpi::SecretKeyChecksum>, ciphertext: std::result::Result<Box<[u8]>, Box<[u8]>>) -> Self { Encrypted { s2k, algo, 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 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(|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(ciphertext) => &ciphertext[..], Err(s2k_ciphertext) => &s2k_ciphertext[..], } } /// 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(&self, pk_algo: PublicKeyAlgorithm, password: &Password) -> Result<Unencrypted> { use std::io::{Cursor, Read}; use crate::crypto::symmetric::Decryptor; let key = self.s2k.derive_key(password, self.algo.key_size()?)?; let cur = Cursor::new(self.ciphertext()?); let mut dec = Decryptor::new(self.algo, &key, cur)?; // Consume the first block. let mut trash = vec![0u8; self.algo.block_size()?]; dec.read_exact(&mut trash)?; mpi::SecretKeyMaterial::parse_with_checksum( pk_algo, &mut dec, self.checksum.unwrap_or_default()) .map(|m| m.into()) } } #[cfg(test)] impl<P, R> Arbitrary for super::Key<P, R> where P: KeyParts, P: Clone, R: KeyRole, R: Clone, Key4<P, R>: Arbitrary, { fn arbitrary(g: &mut Gen) -> Self { Key4::arbitrary(g).into() } } #[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, p: std::marker::PhantomData, r: std::marker::PhantomData, } } } #[cfg(test)] impl Arbitrary for Key4<SecretParts, PrimaryRole> { fn arbitrary(g: &mut Gen) -> Self { Key4::<SecretParts, UnspecifiedRole>::arbitrary(g).into() } } #[cfg(test)] impl Arbitrary for Key4<SecretParts, SubordinateRole> { fn arbitrary(g: &mut Gen) -> Self { Key4::<SecretParts, UnspecifiedRole>::arbitrary(g).into() } } #[cfg(test)] impl Arbitrary for Key4<SecretParts, UnspecifiedRole> { fn arbitrary(g: &mut Gen) -> Self { use PublicKeyAlgorithm::*; use mpi::MPI; let key = Key4::arbitrary(g); let mut secret: SecretKeyMaterial = match key.pk_algo() { RSAEncryptSign => mpi::SecretKeyMaterial::RSA { d: MPI::arbitrary(g).into(), p: MPI::arbitrary(g).into(), q: MPI::arbitrary(g).into(), u: MPI::arbitrary(g).into(), }, DSA => mpi::SecretKeyMaterial::DSA { x: MPI::arbitrary(g).into(), }, ElGamalEncrypt => mpi::SecretKeyMaterial::ElGamal { x: MPI::arbitrary(g).into(), }, EdDSA => mpi::SecretKeyMaterial::EdDSA { scalar: MPI::arbitrary(g).into(), }, ECDSA => mpi::SecretKeyMaterial::ECDSA { scalar: MPI::arbitrary(g).into(), }, ECDH => mpi::SecretKeyMaterial::ECDH { scalar: MPI::arbitrary(g).into(), }, _ => unreachable!("only valid algos, normalizes to these values"), }.into(); if <bool>::arbitrary(g) { secret.encrypt_in_place(&Password::from(Vec::arbitrary(g))) .unwrap(); } Key4::<PublicParts, UnspecifiedRole>::add_secret(key, secret).0 } } #[cfg(test)] mod tests { 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 mut pair = cert.primary_key().key().clone(); let pk_algo = pair.pk_algo(); let secret = pair.secret.as_mut().unwrap(); assert!(secret.is_encrypted()); secret.decrypt_in_place(pk_algo, &"123".into()).unwrap(); assert!(!secret.is_encrypted()); match secret { SecretKeyMaterial::Unencrypted(ref u) => u.map(|mpis| match mpis { mpi::SecretKeyMaterial::RSA { .. } => (), _ => panic!(), }), _ => panic!(), } } #[test] fn key_encrypt_decrypt() -> Result<()> { let mut g = quickcheck::Gen::new(256); let p: Password = Vec::<u8>::arbitrary(&mut g).into(); let check = |key: Key4<SecretParts, UnspecifiedRole>| -> 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<_, key::UnspecifiedRole> = 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<_, key::UnspecifiedRole> = 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 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() })); 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()); let pkesk = PKESK3::for_recipient(cipher, &sk, &key).unwrap(); let (cipher_, sk_) = pkesk.decrypt(&mut keypair, None).unwrap(); 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 secret_encryption_roundtrip() { use crate::types::Curve::*; let keys = vec![NistP256, NistP384, NistP521].into_iter() .filter_map(|cv| -> Option<Key4<key::SecretParts, key::PrimaryRole>> { Key4::generate_ecc(false, cv).ok() }).chain(vec![1024, 2048, 3072, 4096].into_iter().filter_map(|b| { Key4::generate_rsa(b).ok() })); for key in keys { assert!(! key.secret().is_encrypted()); let password = Password::from("foobarbaz"); let mut encrypted_key = key.clone(); encrypted_key.secret_mut().encrypt_in_place(&password).unwrap(); assert!(encrypted_key.secret().is_encrypted()); encrypted_key.secret_mut() .decrypt_in_place(key.pk_algo, &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() { use crate::crypto::ecdh; 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() }; // 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 key = key.parts_into_public(); let got_dek = match key.optional_secret() { Some(SecretKeyMaterial::Unencrypted(ref u)) => u.map(|mpis| { ecdh::decrypt(&key, mpis, &ciphertext) .unwrap() }), _ => unreachable!(), }; assert_eq!(&dek[..], &got_dek[..]); } #[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(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 mut 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 a 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); 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(()) } 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. let bit = i.saturating_add(2 * 8) % (buf.len() * 8); 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-1.7.0/src/packet/literal.rs���������������������������������������������������������0000644�0000000�0000000�00000017133�00726746425�0017042�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 4880] for details. /// /// [Section 5.9 of RFC 4880]: https://tools.ietf.org/html/rfc4880#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_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-1.7.0/src/packet/marker.rs����������������������������������������������������������0000644�0000000�0000000�00000002146�00726746425�0016665�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 4880] for details. /// /// [Section 5.8 of RFC 4880]: https://tools.ietf.org/html/rfc4880#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-1.7.0/src/packet/mdc.rs�������������������������������������������������������������0000644�0000000�0000000�00000005013�00726746425�0016143�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.14 of RFC 4880] for details. /// /// [Section 5.14 of RFC 4880]: https://tools.ietf.org/html/rfc4880#section-5.14 /// /// # 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 { 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<Box<dyn crypto::hash::Digest>> for MDC { fn from(mut hash: Box<dyn crypto::hash::Digest>) -> Self { let mut value : [u8; 20] = Default::default(); let _ = hash.digest(&mut value[..]); value.into() } } ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-1.7.0/src/packet/mod.rs�������������������������������������������������������������0000644�0000000�0000000�00000211642�00726746425�0016166�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 [`Deref`] //! 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 4880 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://tools.ietf.org/html/rfc4880#section-5 //! [the grammar]: https://tools.ietf.org/html/rfc4880#section-11 //! [v3]: https://tools.ietf.org/html/rfc4880#section-5.2.2 //! [v4]: https://tools.ietf.org/html/rfc4880#section-5.2.3 //! [parsing a message]: crate::parse //! [creating a message]: crate::serialize::stream //! [`SignatureBuilder`]: signature::SignatureBuilder //! [`SED`]: https://tools.ietf.org/html/rfc4880#section-5.7 //! [private extensions]: https://tools.ietf.org/html/rfc4880#section-4.3 //! [`Compressed Data`]: CompressedData //! [parses]: crate::parse //! [OpenPGP Message]: https://tools.ietf.org/html/rfc4880#section-11.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://tools.ietf.org/html/rfc4880#section-4.2 //! [partial body encoding]: https://tools.ietf.org/html/rfc4880#section-4.2.2.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::Error; use crate::Result; #[macro_use] mod container; pub use container::Container; pub use container::Body; pub mod prelude; use crate::crypto::{ KeyPair, Password, }; 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 mod key; use key::{ Key4, SecretKeyMaterial }; 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 mod pkesk; mod mdc; pub use self::mdc::MDC; pub mod aed; /// Enumeration of packet types. /// /// The different OpenPGP packets are detailed in [Section 5 of RFC 4880]. /// /// [Section 5 of RFC 4880]: https://tools.ietf.org/html/rfc4880#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. /// /// Note: This enum cannot be exhaustively matched to allow future /// extensions. /// /// # A note on equality /// /// We define equality on `Packet` as the equality of the serialized /// form of their packet bodies as defined by RFC 4880. 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://tools.ietf.org/html/rfc4880#section-4.3 /// [module-level documentation]: crate::packet#unknown-packets /// [`CTB`]: crate::packet::header::CTB /// [length style]: https://tools.ietf.org/html/rfc4880#section-4.2 /// [partial body encoding]: https://tools.ietf.org/html/rfc4880#section-4.2.2.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. MDC(MDC), /// AEAD Encrypted Data Packet. AED(AED), } 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!(AED); 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 4.3 of RFC 4880]. /// /// [Section 4.3 of RFC 4880]: https://tools.ietf.org/html/rfc4880#section-4.3 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, Packet::MDC(_) => Tag::MDC, Packet::AED(_) => Tag::AED, } } /// 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()), } } /// 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), Packet::MDC(x) => Hash::hash(&x, state), Packet::AED(x) => Hash::hash(&x, state), Packet::Unknown(x) => Hash::hash(&x, state), } } } // Allow transparent access of common fields. impl Deref for Packet { type Target = Common; fn deref(&self) -> &Self::Target { match self { Packet::Unknown(ref packet) => &packet.common, Packet::Signature(ref packet) => &packet.common, Packet::OnePassSig(ref packet) => &packet.common, Packet::PublicKey(ref packet) => &packet.common, Packet::PublicSubkey(ref packet) => &packet.common, Packet::SecretKey(ref packet) => &packet.common, Packet::SecretSubkey(ref packet) => &packet.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(ref packet) => &packet.common, Packet::SKESK(SKESK::V4(ref packet)) => &packet.common, Packet::SKESK(SKESK::V5(ref packet)) => &packet.skesk4.common, Packet::SEIP(ref packet) => &packet.common, Packet::MDC(ref packet) => &packet.common, Packet::AED(ref packet) => &packet.common, } } } impl DerefMut for Packet { fn deref_mut(&mut self) -> &mut Common { match self { Packet::Unknown(ref mut packet) => &mut packet.common, Packet::Signature(ref mut packet) => &mut packet.common, Packet::OnePassSig(ref mut packet) => &mut packet.common, Packet::PublicKey(ref mut packet) => &mut packet.common, Packet::PublicSubkey(ref mut packet) => &mut packet.common, Packet::SecretKey(ref mut packet) => &mut packet.common, Packet::SecretSubkey(ref mut packet) => &mut packet.common, Packet::Marker(ref mut packet) => &mut packet.common, Packet::Trust(ref mut packet) => &mut packet.common, Packet::UserID(ref mut packet) => &mut packet.common, Packet::UserAttribute(ref mut packet) => &mut packet.common, Packet::Literal(ref mut packet) => &mut packet.common, Packet::CompressedData(ref mut packet) => &mut packet.common, Packet::PKESK(ref mut packet) => &mut packet.common, Packet::SKESK(SKESK::V4(ref mut packet)) => &mut packet.common, Packet::SKESK(SKESK::V5(ref mut packet)) => &mut packet.skesk4.common, Packet::SEIP(ref mut packet) => &mut packet.common, Packet::MDC(ref mut packet) => &mut packet.common, Packet::AED(ref mut packet) => &mut 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 { use Packet::*; match p { Unknown(v) => write!(f, "Unknown({:?})", v), Signature(v) => write!(f, "Signature({:?})", v), OnePassSig(v) => write!(f, "OnePassSig({:?})", v), PublicKey(v) => write!(f, "PublicKey({:?})", v), PublicSubkey(v) => write!(f, "PublicSubkey({:?})", v), SecretKey(v) => write!(f, "SecretKey({:?})", v), SecretSubkey(v) => write!(f, "SecretSubkey({:?})", v), Marker(v) => write!(f, "Marker({:?})", v), Trust(v) => write!(f, "Trust({:?})", v), UserID(v) => write!(f, "UserID({:?})", v), UserAttribute(v) => write!(f, "UserAttribute({:?})", v), Literal(v) => write!(f, "Literal({:?})", v), CompressedData(v) => write!(f, "CompressedData({:?})", v), PKESK(v) => write!(f, "PKESK({:?})", v), SKESK(v) => write!(f, "SKESK({:?})", v), SEIP(v) => write!(f, "SEIP({:?})", v), MDC(v) => write!(f, "MDC({:?})", v), AED(v) => write!(f, "AED({:?})", 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..15, g) { 0 => Signature::arbitrary(g).into(), 1 => OnePassSig::arbitrary(g).into(), 2 => Key::<key::PublicParts, key::UnspecifiedRole>::arbitrary(g) .role_into_primary().into(), 3 => Key::<key::PublicParts, key::UnspecifiedRole>::arbitrary(g) .role_into_subordinate().into(), 4 => Key::<key::SecretParts, key::UnspecifiedRole>::arbitrary(g) .role_into_primary().into(), 5 => Key::<key::SecretParts, key::UnspecifiedRole>::arbitrary(g) .role_into_subordinate().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 => 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 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); #[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, _: &Self) -> Option<std::cmp::Ordering> { Some(std::cmp::Ordering::Equal) } } 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`], [`SEIP`], and [`AED`], 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 an 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::Text); /// # 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 4880] for details. /// /// [Section 5.2 of RFC 4880]: https://tools.ietf.org/html/rfc4880#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 /// /// Note: This enum cannot be exhaustively matched to allow future /// extensions. /// /// # 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()])?; /// # 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 4. V4(self::signature::Signature4), } assert_send_and_sync!(Signature); impl Signature { /// Gets the version. pub fn version(&self) -> u8 { match self { Signature::V4(_) => 4, } } } impl From<Signature> for Packet { fn from(s: Signature) -> Self { Packet::Signature(s) } } // Trivial forwarder for singleton enum. impl Deref for Signature { type Target = signature::Signature4; fn deref(&self) -> &Self::Target { match self { Signature::V4(sig) => sig, } } } // Trivial forwarder for singleton enum. impl DerefMut for Signature { fn deref_mut(&mut self) -> &mut Self::Target { match self { Signature::V4(ref mut sig) => sig, } } } /// Holds a one-pass signature packet. /// /// See [Section 5.4 of RFC 4880] 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`]. /// /// Note: This enum cannot be exhaustively matched to allow future /// extensions. /// /// [Section 5.4 of RFC 4880]: https://tools.ietf.org/html/rfc4880#section-5.4 /// [`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(self::one_pass_sig::OnePassSig3), } assert_send_and_sync!(OnePassSig); impl OnePassSig { /// Gets the version. pub fn version(&self) -> u8 { match self { OnePassSig::V3(_) => 3, } } } impl From<OnePassSig> for Packet { fn from(s: OnePassSig) -> Self { Packet::OnePassSig(s) } } // Trivial forwarder for singleton enum. impl Deref for OnePassSig { type Target = one_pass_sig::OnePassSig3; fn deref(&self) -> &Self::Target { match self { OnePassSig::V3(ops) => ops, } } } // Trivial forwarder for singleton enum. impl DerefMut for OnePassSig { fn deref_mut(&mut self) -> &mut Self::Target { match self { OnePassSig::V3(ref mut ops) => ops, } } } /// Holds an asymmetrically encrypted session key. /// /// The session key is used to decrypt the actual ciphertext, which is /// typically stored in a [SEIP] or [AED] packet. See [Section 5.1 of /// RFC 4880] 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`]. /// /// Note: This enum cannot be exhaustively matched to allow future /// extensions. /// /// [Section 5.1 of RFC 4880]: https://tools.ietf.org/html/rfc4880#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(self::pkesk::PKESK3), } assert_send_and_sync!(PKESK); impl PKESK { /// Gets the version. pub fn version(&self) -> u8 { match self { PKESK::V3(_) => 3, } } } impl From<PKESK> for Packet { fn from(p: PKESK) -> Self { Packet::PKESK(p) } } // Trivial forwarder for singleton enum. impl Deref for PKESK { type Target = self::pkesk::PKESK3; fn deref(&self) -> &Self::Target { match self { PKESK::V3(ref p) => p, } } } // Trivial forwarder for singleton enum. impl DerefMut for PKESK { fn deref_mut(&mut self) -> &mut Self::Target { match self { PKESK::V3(ref mut p) => p, } } } /// Holds a symmetrically encrypted session key. /// /// The session key is used to decrypt the actual ciphertext, which is /// typically stored in a [SEIP] or [AED] packet. See [Section 5.3 of /// RFC 4880] 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`]. /// /// Note: This enum cannot be exhaustively matched to allow future /// extensions. /// /// [Section 5.3 of RFC 4880]: https://tools.ietf.org/html/rfc4880#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::skesk::SKESK4), /// SKESK packet version 5. /// /// This feature is [experimental](super#experimental-features). V5(self::skesk::SKESK5), } assert_send_and_sync!(SKESK); impl SKESK { /// Gets the version. pub fn version(&self) -> u8 { match self { SKESK::V4(_) => 4, SKESK::V5(_) => 5, } } } impl From<SKESK> for Packet { fn from(p: SKESK) -> Self { Packet::SKESK(p) } } /// Holds a public key, public subkey, private key or private subkey packet. /// /// The different `Key` packets are described in [Section 5.5 of RFC 4880]. /// /// [Section 5.5 of RFC 4880]: https://tools.ietf.org/html/rfc4880#section-5.5 /// /// Note: This enum cannot be exhaustively matched to allow future /// extensions. /// /// # 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://tools.ietf.org/html/rfc4880#section-5.5.1.1 /// [secret keys]: https://tools.ietf.org/html/rfc4880#section-5.5.1.3 /// [public subkeys]: https://tools.ietf.org/html/rfc4880#section-5.5.1.2 /// [secret subkeys]: https://tools.ietf.org/html/rfc4880#section-5.5.1.4 /// [`Cert::keys`]: super::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 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`]: super::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`]: super::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)?; /// // 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 4 keys, however, future /// versions may add limited support for version 3 keys to facilitate /// working with achieved messages, and RFC 4880bis includes [a /// proposal for a new key format].) As such, it doesn't provide a /// mechanism to generate keys or import existing key material. /// Instead, use the format-specific functions (e.g., /// [`Key4::generate_ecc`]) and then convert the result into a `Key` /// packet, as the following example demonstrates. /// /// [the different key formats]: https://tools.ietf.org/html/rfc4880#section-5.5.2 /// [a proposal for a new key format]: https://tools.ietf.org/html/draft-ietf-openpgp-rfc4880bis-09.html#section-5.5.2 /// [`Key4::generate_ecc`]: key::Key4::generate_ecc() /// /// /// ## 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(Key4::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://tools.ietf.org/html/rfc4880#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, Clone, Debug)] pub enum Key<P: key::KeyParts, R: key::KeyRole> { /// A version 4 `Key` packet. V4(Key4<P, R>), } assert_send_and_sync!(Key<P, R> where P: key::KeyParts, R: key::KeyRole); 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), } } } impl<P: key::KeyParts, R: key::KeyRole> Key<P, R> { /// Gets the version. pub fn version(&self) -> u8 { match self { Key::V4(_) => 4, } } /// Compares the public bits of two keys. /// /// This returns `Ordering::Equal` if the public MPIs, version, /// creation time and algorithm of the two `Key`s match. This /// does not consider the packet's encoding, packet's tag or the /// 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), } } /// This method tests for self and other values to be equal modulo /// the secret key material. /// /// This returns true if the public MPIs, creation time and /// algorithm of the two `Key`s match. This does not consider /// the packet's encoding, packet's tag or the 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 } } 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> { /// 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(None, /// 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(), } } /// 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://tools.ietf.org/html/rfc4880#section-5.5.3 /// [KDF]: https://tools.ietf.org/html/rfc4880#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(None, /// 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)?)), } } /// 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://tools.ietf.org/html/rfc4880#section-5.5.3 /// [KDF]: https://tools.ietf.org/html/rfc4880#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(None, /// 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.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)?; /// /// // 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)?)), } } } impl<R: key::KeyRole> Key4<key::SecretParts, R> { /// 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: 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) }, } } /// 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) }, } } } } } 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) }, } } /// 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) }, } } } // Trivial forwarder for singleton enum. impl<P: key::KeyParts, R: key::KeyRole> Deref for Key<P, R> { type Target = Key4<P, R>; fn deref(&self) -> &Self::Target { match self { Key::V4(ref p) => p, } } } // Trivial forwarder for singleton enum. impl<P: key::KeyParts, R: key::KeyRole> DerefMut for Key<P, R> { fn deref_mut(&mut self) -> &mut Self::Target { match self { Key::V4(ref mut p) => p, } } } /// Holds a SEIP packet. /// /// A SEIP packet holds encrypted data. The data contains additional /// OpenPGP packets. See [Section 5.13 of RFC 4880] 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 4880]: https://tools.ietf.org/html/rfc4880#section-5.13 /// [streaming serializer]: crate::serialize::stream /// [`PacketParser`]: crate::parse::PacketParser #[derive(PartialEq, Eq, Hash, Clone, Debug)] pub enum SEIP { /// SEIP packet version 1. V1(self::seip::SEIP1), } assert_send_and_sync!(SEIP); impl SEIP { /// Gets the version. pub fn version(&self) -> u8 { match self { SEIP::V1(_) => 1, } } } impl From<SEIP> for Packet { fn from(p: SEIP) -> Self { Packet::SEIP(p) } } // Trivial forwarder for singleton enum. impl Deref for SEIP { type Target = self::seip::SEIP1; fn deref(&self) -> &Self::Target { match self { SEIP::V1(ref p) => p, } } } // Trivial forwarder for singleton enum. impl DerefMut for SEIP { fn deref_mut(&mut self) -> &mut Self::Target { match self { SEIP::V1(ref mut p) => p, } } } /// Holds an AEAD encrypted data packet. /// /// An AEAD packet holds encrypted data. It is contains additional /// OpenPGP packets. See [Section 5.16 of RFC 4880bis] for details. /// /// [Section 5.16 of RFC 4880bis]: https://tools.ietf.org/html/draft-ietf-openpgp-rfc4880bis-05#section-5.16 /// /// Note: This enum cannot be exhaustively matched to allow future /// extensions. /// /// An AEAD 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`]. /// /// [streaming serializer]: crate::serialize::stream /// [`PacketParser`]: crate::parse::PacketParser /// /// This feature is [experimental](super#experimental-features). #[non_exhaustive] #[derive(PartialEq, Eq, Hash, Clone, Debug)] pub enum AED { /// AED packet version 1. V1(self::aed::AED1), } assert_send_and_sync!(AED); impl AED { /// Gets the version. pub fn version(&self) -> u8 { match self { AED::V1(_) => 1, } } } impl From<AED> for Packet { fn from(p: AED) -> Self { Packet::AED(p) } } // Trivial forwarder for singleton enum. impl Deref for AED { type Target = self::aed::AED1; fn deref(&self) -> &Self::Target { match self { AED::V1(ref p) => p, } } } // Trivial forwarder for singleton enum. impl DerefMut for AED { fn deref_mut(&mut self) -> &mut Self::Target { match self { AED::V1(ref mut p) => 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(); let bit = // Avoid first two bytes so that we don't change the // type and reduce the chance of changing the length. i.saturating_add(16) % (buf.len() * 8); buf[bit / 8] ^= 1 << (bit % 8); match Packet::from_bytes(&buf) { Ok(q) => p != q, Err(_) => true, // Packet failed to parse. } } } } ����������������������������������������������������������������������������������������������sequoia-openpgp-1.7.0/src/packet/one_pass_sig.rs����������������������������������������������������0000644�0000000�0000000�00000012463�00726746425�0020060�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������//! One-pass signature packets. //! //! See [Section 5.4 of RFC 4880] for details. //! //! [Section 5.4 of RFC 4880]: https://tools.ietf.org/html/rfc4880#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::Result; use crate::KeyID; use crate::HashAlgorithm; use crate::PublicKeyAlgorithm; use crate::SignatureType; /// Holds a one-pass signature packet. /// /// See [Section 5.4 of RFC 4880] for details. /// /// [Section 5.4 of RFC 4880]: https://tools.ietf.org/html/rfc4880#section-5.4 /// /// # 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 `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 super::OnePassSig { fn from(s: OnePassSig3) -> Self { super::OnePassSig::V3(s) } } impl From<OnePassSig3> for Packet { fn from(p: OnePassSig3) -> Self { super::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 super::OnePassSig { fn arbitrary(g: &mut Gen) -> Self { OnePassSig3::arbitrary(g).into() } } #[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-1.7.0/src/packet/pkesk.rs�����������������������������������������������������������0000644�0000000�0000000�00000041504�00726746425�0016522�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 4880] for details. //! //! [Section 5.1 of RFC 4880]: https://tools.ietf.org/html/rfc4880#section-5.1 #[cfg(test)] use quickcheck::{Arbitrary, Gen}; use crate::Error; 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 4880] for details. /// /// [Section 5.1 of RFC 4880]: https://tools.ietf.org/html/rfc4880#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: 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: KeyID, pk_algo: PublicKeyAlgorithm, encrypted_session_key: Ciphertext) -> Result<PKESK3> { Ok(PKESK3 { common: Default::default(), recipient, pk_algo, esk: encrypted_session_key, }) } /// Creates a new PKESK3 packet for the given recipent. /// /// 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, { // We need to prefix the cipher specifier to the session key, // and a two-octet checksum. let mut psk = Vec::with_capacity(1 + session_key.len() + 2); psk.push(algo.into()); psk.extend_from_slice(session_key); // 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()); let psk: SessionKey = psk.into(); let esk = recipient.encrypt(&psk)?; Ok(PKESK3{ common: Default::default(), recipient: recipient.keyid(), pk_algo: recipient.pk_algo(), esk, }) } /// Gets the recipient. pub fn recipient(&self) -> &KeyID { &self.recipient } /// Sets the recipient. pub fn set_recipient(&mut self, recipient: KeyID) -> 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 14 of /// RFC 4880]. /// /// [Section 14 of RFC 4880]: https://tools.ietf.org/html/rfc4880#section-14 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)> { let plaintext_len = if let Some(s) = sym_algo_hint { Some(1 /* cipher octet */ + s.key_size()? + 2 /* chksum */) } else { None }; let plain = decryptor.decrypt(&self.esk, plaintext_len)?; let key_rgn = 1..(plain.len() - 2); let sym_algo: SymmetricAlgorithm = plain[0].into(); let mut key: SessionKey = vec![0u8; sym_algo.key_size()?].into(); 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()) } key.copy_from_slice(&plain[key_rgn]); 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 { Ok((sym_algo, key)) } else { Err(Error::MalformedPacket("key checksum wrong".to_string()) .into()) } } } impl From<PKESK3> for super::PKESK { fn from(p: PKESK3) -> Self { super::PKESK::V3(p) } } impl From<PKESK3> for Packet { fn from(p: PKESK3) -> Self { Packet::PKESK(p.into()) } } #[cfg(test)] impl Arbitrary for super::PKESK { fn arbitrary(g: &mut Gen) -> Self { PKESK3::arbitrary(g).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); } }; PKESK3::new(KeyID::arbitrary(g), 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 pkg = pile.descendants().skip(0).next(); if let Some(Packet::PKESK(ref pkesk)) = pkg { 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); } else { panic!("message is not a PKESK packet"); } } #[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 pkg = pile.descendants().skip(0).next(); if let Some(Packet::PKESK(ref pkesk)) = pkg { 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); } else { panic!("message is not a PKESK packet"); } } #[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 pkg = pile.descendants().skip(0).next(); if let Some(Packet::PKESK(ref pkesk)) = pkg { 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); } else { panic!("message is not a PKESK packet"); } } #[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 pkg = pile.descendants().skip(0).next(); if let Some(Packet::PKESK(ref pkesk)) = pkg { 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); } else { panic!("message is not a PKESK packet"); } } #[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 pkg = pile.descendants().skip(0).next(); if let Some(Packet::PKESK(ref pkesk)) = pkg { 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); } else { panic!("message is not a PKESK packet"); } } #[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); 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 pkg = pile.descendants().next(); if let Some(Packet::PKESK(ref pkesk)) = pkg { // Boom goes the assertion. let _ = pkesk.decrypt(&mut keypair, None); } else { panic!("message is not a PKESK packet"); } 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 pkg = pile.descendants().next(); if let Some(Packet::PKESK(ref pkesk)) = pkg { // Boom goes the memory safety. let _ = pkesk.decrypt(&mut keypair, None); } else { panic!("message is not a PKESK packet"); } Ok(()) } } ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-1.7.0/src/packet/prelude.rs���������������������������������������������������������0000644�0000000�0000000�00000002441�00726746425�0017042�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::{ AED, Body, CompressedData, Container, Header, Key, Literal, MDC, Marker, OnePassSig, PKESK, Packet, SEIP, SKESK, Signature, Tag, Trust, Unknown, UserAttribute, UserID, aed::AED1, key, key::Key4, key::SecretKeyMaterial, one_pass_sig::OnePassSig3, pkesk::PKESK3, seip::SEIP1, signature, signature::Signature4, signature::SignatureBuilder, skesk::SKESK4, skesk::SKESK5, user_attribute, }; �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-1.7.0/src/packet/seip.rs������������������������������������������������������������0000644�0000000�0000000�00000003421�00726746425�0016341�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������//! Symmetrically Encrypted Integrity Protected data packets. //! //! An encrypted data packet is a container. See [Section 5.13 of RFC //! 4880] for details. //! //! [Section 5.13 of RFC 4880]: https://tools.ietf.org/html/rfc4880#section-5.13 use crate::packet; use crate::Packet; /// Holds an encrypted data packet. /// /// An encrypted data packet is a container. See [Section 5.13 of RFC /// 4880] for details. /// /// [Section 5.13 of RFC 4880]: https://tools.ietf.org/html/rfc4880#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 std::ops::Deref for SEIP1 { type Target = packet::Container; fn deref(&self) -> &Self::Target { &self.container } } impl std::ops::DerefMut for SEIP1 { fn deref_mut(&mut self) -> &mut Self::Target { &mut self.container } } #[allow(clippy::new_without_default)] impl SEIP1 { /// Creates a new SEIP1 packet. pub fn new() -> Self { Self { common: Default::default(), container: Default::default(), } } } 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-1.7.0/src/packet/signature/subpacket.rs���������������������������������������������0000644�0000000�0000000�00001165021�00726746425�0021371�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.1 of RFC 4880]. //! //! [Section 5.2.3.1 of RFC 4880]: https://tools.ietf.org/html/rfc4880#section-5.2.3.1 //! //! 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::cell::RefCell; use std::cmp::Ordering; use std::collections::HashMap; use std::convert::{TryInto, TryFrom}; use std::hash::{Hash, Hasher}; use std::sync::Mutex; use std::ops::{Deref, DerefMut}; use std::fmt; use std::cmp; 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, }; lazy_static::lazy_static!{ /// 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 of 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 static ref 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. /// /// /// Note: This enum cannot be exhaustively matched to allow future /// extensions. #[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.4 of RFC 4880] for details. /// /// [Section 5.2.3.4 of RFC 4880]: https://tools.ietf.org/html/rfc4880#section-5.2.3.4 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.10 of RFC 4880] for details. /// /// [Section 5.2.3.10 of RFC 4880]: https://tools.ietf.org/html/rfc4880#section-5.2.3.10 SignatureExpirationTime, /// Whether a signature should be published. /// /// See [Section 5.2.3.11 of RFC 4880] for details. /// /// [Section 5.2.3.11 of RFC 4880]: https://tools.ietf.org/html/rfc4880#section-5.2.3.11 ExportableCertification, /// Signer asserts that the key is not only valid but also trustworthy at /// the specified level. /// /// See [Section 5.2.3.13 of RFC 4880] for details. /// /// [Section 5.2.3.13 of RFC 4880]: https://tools.ietf.org/html/rfc4880#section-5.2.3.13 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.14 of RFC 4880] for details. /// /// [Section 5.2.3.14 of RFC 4880]: https://tools.ietf.org/html/rfc4880#section-5.2.3.14 RegularExpression, /// Whether a signature can later be revoked. /// /// See [Section 5.2.3.12 of RFC 4880] for details. /// /// [Section 5.2.3.12 of RFC 4880]: https://tools.ietf.org/html/rfc4880#section-5.2.3.12 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.6 of RFC 4880] for details. /// /// [Section 5.2.3.6 of RFC 4880]: https://tools.ietf.org/html/rfc4880#section-5.2.3.6 KeyExpirationTime, /// Deprecated PlaceholderForBackwardCompatibility, /// The Symmetric algorithms that the certificate holder prefers. /// /// See [Section 5.2.3.7 of RFC 4880] for details. /// /// [Section 5.2.3.7 of RFC 4880]: https://tools.ietf.org/html/rfc4880#section-5.2.3.7 PreferredSymmetricAlgorithms, /// Authorizes the specified key to issue revocation signatures for this /// certificate. /// /// See [Section 5.2.3.15 of RFC 4880] for details. /// /// [Section 5.2.3.15 of RFC 4880]: https://tools.ietf.org/html/rfc4880#section-5.2.3.15 RevocationKey, /// The OpenPGP Key ID of the key issuing the signature. /// /// See [Section 5.2.3.5 of RFC 4880] for details. /// /// [Section 5.2.3.5 of RFC 4880]: https://tools.ietf.org/html/rfc4880#section-5.2.3.5 Issuer, /// A "notation" on the signature. /// /// See [Section 5.2.3.16 of RFC 4880] for details. /// /// [Section 5.2.3.16 of RFC 4880]: https://tools.ietf.org/html/rfc4880#section-5.2.3.16 NotationData, /// The Hash algorithms that the certificate holder prefers. /// /// See [Section 5.2.3.8 of RFC 4880] for details. /// /// [Section 5.2.3.8 of RFC 4880]: https://tools.ietf.org/html/rfc4880#section-5.2.3.8 PreferredHashAlgorithms, /// The compression algorithms that the certificate holder prefers. /// /// See [Section 5.2.3.9 of RFC 4880] for details. /// /// [Section 5.2.3.9 of RFC 4880]: https://tools.ietf.org/html/rfc4880#section-5.2.3.9 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.17 of RFC 4880] for details. /// /// [Section 5.2.3.17 of RFC 4880]: https://tools.ietf.org/html/rfc4880#section-5.2.3.17 KeyServerPreferences, /// The URI of a key server where the certificate holder keeps /// their certificate up to date. /// /// See [Section 5.2.3.18 of RFC 4880] for details. /// /// [Section 5.2.3.18 of RFC 4880]: https://tools.ietf.org/html/rfc4880#section-5.2.3.18 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.19 of RFC 4880] for details. /// /// [Section 5.2.3.19 of RFC 4880]: https://tools.ietf.org/html/rfc4880#section-5.2.3.19 PrimaryUserID, /// The URI of a document that describes the policy under which /// the signature was issued. /// /// See [Section 5.2.3.20 of RFC 4880] for details. /// /// [Section 5.2.3.20 of RFC 4880]: https://tools.ietf.org/html/rfc4880#section-5.2.3.20 PolicyURI, /// A list of flags that hold information about a key. /// /// See [Section 5.2.3.21 of RFC 4880] for details. /// /// [Section 5.2.3.21 of RFC 4880]: https://tools.ietf.org/html/rfc4880#section-5.2.3.21 KeyFlags, /// The User ID that is responsible for the signature. /// /// See [Section 5.2.3.22 of RFC 4880] for details. /// /// [Section 5.2.3.22 of RFC 4880]: https://tools.ietf.org/html/rfc4880#section-5.2.3.22 SignersUserID, /// The reason for a revocation, used in key revocations and /// certification revocation signatures. /// /// See [Section 5.2.3.23 of RFC 4880] for details. /// /// [Section 5.2.3.23 of RFC 4880]: https://tools.ietf.org/html/rfc4880#section-5.2.3.23 ReasonForRevocation, /// The OpenPGP features a user's implementation supports. /// /// See [Section 5.2.3.24 of RFC 4880] for details. /// /// [Section 5.2.3.24 of RFC 4880]: https://tools.ietf.org/html/rfc4880#section-5.2.3.24 Features, /// A signature to which this signature refers. /// /// See [Section 5.2.3.25 of RFC 4880] for details. /// /// [Section 5.2.3.25 of RFC 4880]: https://tools.ietf.org/html/rfc4880#section-5.2.3.25 SignatureTarget, /// A complete Signature packet body. /// /// This is used to store a backsig in a subkey binding signature. /// /// See [Section 5.2.3.26 of RFC 4880] for details. /// /// [Section 5.2.3.26 of RFC 4880]: https://tools.ietf.org/html/rfc4880#section-5.2.3.26 EmbeddedSignature, /// The Fingerprint of the key that issued the signature (proposed). /// /// See [Section 5.2.3.28 of RFC 4880bis] for details. /// /// [Section 5.2.3.28 of RFC 4880bis]: https://tools.ietf.org/html/draft-ietf-openpgp-rfc4880bis-09.html#section-5.2.3.28 IssuerFingerprint, /// The AEAD algorithms that the certificate holder prefers (proposed). /// /// See [Section 5.2.3.8 of RFC 4880bis] for details. /// /// [Section 5.2.3.8 of RFC 4880bis]: https://tools.ietf.org/html/draft-ietf-openpgp-rfc4880bis-09.html#section-5.2.3.8 PreferredAEADAlgorithms, /// Who the signed message was intended for (proposed). /// /// See [Section 5.2.3.29 of RFC 4880bis] for details. /// /// [Section 5.2.3.29 of RFC 4880bis]: https://tools.ietf.org/html/draft-ietf-openpgp-rfc4880bis-09.html#section-5.2.3.29 IntendedRecipient, /// The Attested Certifications subpacket (proposed). /// /// 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 5.2.3.30 of RFC 4880bis] for details. /// /// [Section 5.2.3.30 of RFC 4880bis]: https://tools.ietf.org/html/draft-ietf-openpgp-rfc4880bis-10.html#section-5.2.3.30 AttestedCertifications, /// Reserved subpacket tag. Reserved(u8), /// Private subpacket tag. Private(u8), /// Unknown subpacket tag. Unknown(u8), } 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 { 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::AttestedCertifications, 0| 1| 8| 13| 14| 15| 17| 18| 19 => SubpacketTag::Reserved(u), 100..=110 => SubpacketTag::Private(u), _ => SubpacketTag::Unknown(u), } } } impl From<SubpacketTag> for u8 { fn from(t: SubpacketTag) -> Self { 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::AttestedCertifications => 37, SubpacketTag::Reserved(u) => u, SubpacketTag::Private(u) => u, SubpacketTag::Unknown(u) => u, } } } #[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), SubpacketTag::Private(u) => (100..=110).contains(&u), SubpacketTag::Unknown(u) => (u > 33 && u < 100) || u > 110, _ => true } } } } /// 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://tools.ietf.org/html/rfc4880#section-5.2.3.1 /// [`Issuer`]: https://tools.ietf.org/html/rfc4880#section-5.2.3.5 /// /// # 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(()) /// # } /// ``` pub struct SubpacketArea { /// The subpackets. packets: Vec<Subpacket>, // The subpacket area, but parsed so that the map is indexed by // the subpacket tag, and the value corresponds to 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. // // This is an option, because we parse the subpacket area lazily. parsed: Mutex<RefCell<Option<HashMap<SubpacketTag, usize>>>>, } assert_send_and_sync!(SubpacketArea); #[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 Clone for SubpacketArea { fn clone(&self) -> Self { Self::new(self.packets.clone()).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: Mutex::new(RefCell::new(None)), }; if area.serialized_len() > std::u16::MAX as usize { Err(Error::InvalidArgument( format!("Subpacket area exceeds maximum size: {}", area.serialized_len())).into()) } else { Ok(area) } } // Initialize `Signature::hashed_area_parsed` from // `Signature::hashed_area`, if necessary. fn cache_init(&self) { if self.parsed.lock().unwrap().borrow().is_none() { let mut hash = HashMap::new(); for (i, sp) in self.packets.iter().enumerate() { hash.insert(sp.tag(), i); } *self.parsed.lock().unwrap().borrow_mut() = Some(hash); } } /// Invalidates the cache. fn cache_invalidate(&self) { *self.parsed.lock().unwrap().borrow_mut() = None; } /// 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.4.1 of RFC 4880] 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://tools.ietf.org/html/rfc4880#section-5.2.3.16 /// [`Signature Creation Time`]: https://tools.ietf.org/html/rfc4880#section-5.2.3.4 /// [Section 5.2.4.1 of RFC 4880]: https://tools.ietf.org/html/rfc4880#section-5.2.4.1 /// /// 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> { self.cache_init(); match self.parsed.lock().unwrap().borrow().as_ref().unwrap().get(&tag) { Some(&n) => Some(&self.packets[n]), None => 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.4.1 of RFC 4880] 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://tools.ietf.org/html/rfc4880#section-5.2.3.16 /// [`Signature Creation Time`]: https://tools.ietf.org/html/rfc4880#section-5.2.3.4 /// [Section 5.2.4.1 of RFC 4880]: https://tools.ietf.org/html/rfc4880#section-5.2.4.1 /// /// 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> { self.cache_init(); match self.parsed.lock().unwrap().borrow().as_ref().unwrap().get(&tag) { Some(&n) => Some(&mut self.packets[n]), None => 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://tools.ietf.org/html/rfc4880#section-5.2.3.16 /// /// # 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, mut packet: Subpacket) -> 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(false); 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, mut 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://tools.ietf.org/html/rfc4880#section-5.2.3.16 /// [`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://tools.ietf.org/html/rfc4880#section-5.2.3.16 /// /// 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.16 of RFC 4880] for details. /// /// [Section 5.2.3.16 of RFC 4880]: https://tools.ietf.org/html/rfc4880#section-5.2.3.16 #[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() { 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 slice containing the raw values. pub(crate) fn as_slice(&self) -> &[u8] { self.0.as_slice() } /// 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.raw.len(), 4); let byte = bit / 8; if byte < 4 { self.0.raw[byte] |= 1 << (bit % 8); 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.raw.len(), 4); let byte = bit / 8; if byte < 4 { self.0.raw[byte] &= !(1 << (bit % 8)); 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 /// /// Note: This enum cannot be exhaustively matched to allow future /// extensions. #[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.4 of RFC 4880] for details. /// /// [Section 5.2.3.4 of RFC 4880]: https://tools.ietf.org/html/rfc4880#section-5.2.3.4 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.10 of RFC 4880] for details. /// /// [Section 5.2.3.10 of RFC 4880]: https://tools.ietf.org/html/rfc4880#section-5.2.3.10 SignatureExpirationTime(Duration), /// Whether a signature should be published. /// /// See [Section 5.2.3.11 of RFC 4880] for details. /// /// [Section 5.2.3.11 of RFC 4880]: https://tools.ietf.org/html/rfc4880#section-5.2.3.11 ExportableCertification(bool), /// Signer asserts that the key is not only valid but also trustworthy at /// the specified level. /// /// See [Section 5.2.3.13 of RFC 4880] for details. /// /// [Section 5.2.3.13 of RFC 4880]: https://tools.ietf.org/html/rfc4880#section-5.2.3.13 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.14 of RFC 4880] for details. /// /// [Section 5.2.3.14 of RFC 4880]: https://tools.ietf.org/html/rfc4880#section-5.2.3.14 /// /// 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.12 of RFC 4880] for details. /// /// [Section 5.2.3.12 of RFC 4880]: https://tools.ietf.org/html/rfc4880#section-5.2.3.12 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.6 of RFC 4880] for details. /// /// [Section 5.2.3.6 of RFC 4880]: https://tools.ietf.org/html/rfc4880#section-5.2.3.6 KeyExpirationTime(Duration), /// The Symmetric algorithms that the certificate holder prefers. /// /// See [Section 5.2.3.7 of RFC 4880] for details. /// /// [Section 5.2.3.7 of RFC 4880]: https://tools.ietf.org/html/rfc4880#section-5.2.3.7 PreferredSymmetricAlgorithms(Vec<SymmetricAlgorithm>), /// Authorizes the specified key to issue revocation signatures for this /// certificate. /// /// See [Section 5.2.3.15 of RFC 4880] for details. /// /// [Section 5.2.3.15 of RFC 4880]: https://tools.ietf.org/html/rfc4880#section-5.2.3.15 RevocationKey(RevocationKey), /// The OpenPGP Key ID of the key issuing the signature. /// /// See [Section 5.2.3.5 of RFC 4880] for details. /// /// [Section 5.2.3.5 of RFC 4880]: https://tools.ietf.org/html/rfc4880#section-5.2.3.5 Issuer(KeyID), /// A "notation" on the signature. /// /// See [Section 5.2.3.16 of RFC 4880] for details. /// /// [Section 5.2.3.16 of RFC 4880]: https://tools.ietf.org/html/rfc4880#section-5.2.3.16 NotationData(NotationData), /// The Hash algorithms that the certificate holder prefers. /// /// See [Section 5.2.3.8 of RFC 4880] for details. /// /// [Section 5.2.3.8 of RFC 4880]: https://tools.ietf.org/html/rfc4880#section-5.2.3.8 PreferredHashAlgorithms(Vec<HashAlgorithm>), /// The compression algorithms that the certificate holder prefers. /// /// See [Section 5.2.3.9 of RFC 4880] for details. /// /// [Section 5.2.3.9 of RFC 4880]: https://tools.ietf.org/html/rfc4880#section-5.2.3.9 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.17 of RFC 4880] for details. /// /// [Section 5.2.3.17 of RFC 4880]: https://tools.ietf.org/html/rfc4880#section-5.2.3.17 KeyServerPreferences(KeyServerPreferences), /// The URI of a key server where the certificate holder keeps /// their certificate up to date. /// /// See [Section 5.2.3.18 of RFC 4880] for details. /// /// [Section 5.2.3.18 of RFC 4880]: https://tools.ietf.org/html/rfc4880#section-5.2.3.18 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.19 of RFC 4880] for details. /// /// [Section 5.2.3.19 of RFC 4880]: https://tools.ietf.org/html/rfc4880#section-5.2.3.19 PrimaryUserID(bool), /// The URI of a document that describes the policy under which /// the signature was issued. /// /// See [Section 5.2.3.20 of RFC 4880] for details. /// /// [Section 5.2.3.20 of RFC 4880]: https://tools.ietf.org/html/rfc4880#section-5.2.3.20 PolicyURI(Vec<u8>), /// A list of flags that hold information about a key. /// /// See [Section 5.2.3.21 of RFC 4880] for details. /// /// [Section 5.2.3.21 of RFC 4880]: https://tools.ietf.org/html/rfc4880#section-5.2.3.21 KeyFlags(KeyFlags), /// The User ID that is responsible for the signature. /// /// See [Section 5.2.3.22 of RFC 4880] for details. /// /// [Section 5.2.3.22 of RFC 4880]: https://tools.ietf.org/html/rfc4880#section-5.2.3.22 SignersUserID(Vec<u8>), /// The reason for a revocation, used in key revocations and /// certification revocation signatures. /// /// See [Section 5.2.3.23 of RFC 4880] for details. /// /// [Section 5.2.3.23 of RFC 4880]: https://tools.ietf.org/html/rfc4880#section-5.2.3.23 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.24 of RFC 4880] for details. /// /// [Section 5.2.3.24 of RFC 4880]: https://tools.ietf.org/html/rfc4880#section-5.2.3.24 Features(Features), /// A signature to which this signature refers. /// /// See [Section 5.2.3.25 of RFC 4880] for details. /// /// [Section 5.2.3.25 of RFC 4880]: https://tools.ietf.org/html/rfc4880#section-5.2.3.25 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.26 of RFC 4880] for details. /// /// [Section 5.2.3.26 of RFC 4880]: https://tools.ietf.org/html/rfc4880#section-5.2.3.26 EmbeddedSignature(Signature), /// The Fingerprint of the key that issued the signature (proposed). /// /// See [Section 5.2.3.28 of RFC 4880bis] for details. /// /// [Section 5.2.3.28 of RFC 4880bis]: https://tools.ietf.org/html/draft-ietf-openpgp-rfc4880bis-09.html#section-5.2.3.28 IssuerFingerprint(Fingerprint), /// The AEAD algorithms that the certificate holder prefers (proposed). /// /// See [Section 5.2.3.8 of RFC 4880bis] for details. /// /// [Section 5.2.3.8 of RFC 4880bis]: https://tools.ietf.org/html/draft-ietf-openpgp-rfc4880bis-09.html#section-5.2.3.8 PreferredAEADAlgorithms(Vec<AEADAlgorithm>), /// Who the signed message was intended for (proposed). /// /// See [Section 5.2.3.29 of RFC 4880bis] for details. /// /// [Section 5.2.3.29 of RFC 4880bis]: https://tools.ietf.org/html/draft-ietf-openpgp-rfc4880bis-09.html#section-5.2.3.29 IntendedRecipient(Fingerprint), /// The Attested Certifications subpacket (proposed). /// /// 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 5.2.3.30 of RFC 4880bis] for details. /// /// [Section 5.2.3.30 of RFC 4880bis]: https://tools.ietf.org/html/draft-ietf-openpgp-rfc4880bis-10.html#section-5.2.3.30 AttestedCertifications(Vec<Box<[u8]>>), } 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; loop { break match gen_arbitrary_from_range(0..26, 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 => PreferredAEADAlgorithms(Arbitrary::arbitrary(g)), 25 => IntendedRecipient(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::*; 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, PreferredAEADAlgorithms(_) => SubpacketTag::PreferredAEADAlgorithms, IntendedRecipient(_) => SubpacketTag::IntendedRecipient, AttestedCertifications(_) => SubpacketTag::AttestedCertifications, 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://tools.ietf.org/html/rfc4880#section-5.2.3.16 /// /// Subpackets are described in [Section 5.2.3.1 of RFC 4880]. /// /// [Section 5.2.3.1 of RFC 4880]: https://tools.ietf.org/html/rfc4880#section-5.2.3.1 #[derive(Clone)] 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 or not the information in this subpacket are /// authenticated in the context of its signature. authenticated: bool, } assert_send_and_sync!(Subpacket); 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, } } /// 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 the Subpacket's value. pub(crate) fn value_mut(&mut self) -> &mut SubpacketValue { &mut 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 is 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 `Subpacket` is cloned, /// or a `Subpacket` is added to a [`SubpacketArea`], the flag is /// cleared. /// /// [`Subpacket::set_authenticated`]: Self::set_authenticated() pub fn authenticated(&self) -> bool { self.authenticated } /// Marks the information in this subpacket as authenticated or /// not. /// /// See [`Subpacket::authenticated`] for more information. /// /// [`Subpacket::authenticated`]: Self::authenticated() pub fn set_authenticated(&mut self, authenticated: bool) -> bool { std::mem::replace(&mut self.authenticated, authenticated) } } #[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. #[allow(clippy::redundant_pattern_matching)] 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://tools.ietf.org/html/rfc4880#section-5.2.3.4 /// /// 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://tools.ietf.org/html/rfc4880#section-5.2.3.4 /// /// 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 fallback to an older binding /// signature. /// /// [Signature Expiration Time subpacket]: https://tools.ietf.org/html/rfc4880#section-5.2.3.10 /// [Key Expiration Time subpacket]: https://tools.ietf.org/html/rfc4880#section-5.2.3.6 /// [`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://tools.ietf.org/html/rfc4880#section-5.2.3.10 /// [`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 fallback to an older binding /// signature. /// /// [Key Expiration Time subpacket]: https://tools.ietf.org/html/rfc4880#section-5.2.3.6 /// [`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. 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 or not 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 [`struct@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.4 of RFC 4880] 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.4 of RFC 4880]: https://tools.ietf.org/html/rfc4880#section-5.2.3.4 /// [`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(None, 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://tools.ietf.org/html/rfc4880#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 fallback to an older binding /// signature. /// /// [Key Expiration Time subpacket]: https://tools.ietf.org/html/rfc4880#section-5.2.3.6 /// [Signature Expiration Time subpacket]: https://tools.ietf.org/html/rfc4880#section-5.2.3.6 /// [`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://tools.ietf.org/html/rfc4880#section-5.2.3.6 /// [Key]: https://tools.ietf.org/html/rfc4880#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 fallback to an older binding /// signature. /// /// [Signature Expiration Time subpacket]: https://tools.ietf.org/html/rfc4880#section-5.2.3.10 /// [`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 or not 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://tools.ietf.org/html/rfc4880#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://tools.ietf.org/html/rfc4880#section-5.2.3.6 /// /// # 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://tools.ietf.org/html/rfc4880#section-5.2.3.11 /// [`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://tools.ietf.org/html/rfc4880#section-5.2.3.15 /// /// 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://tools.ietf.org/html/rfc4880#section-5.2.3.13 /// /// 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://tools.ietf.org/html/rfc4880#section-5.2.3.13 /// [Regular Expression subpacket]: https://tools.ietf.org/html/rfc4880#section-5.2.3.14 /// [`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://tools.ietf.org/html/rfc4880#section-5.2.3.12 /// [Certification revocation signature]: https://tools.ietf.org/html/rfc4880#section-5.2.1 /// [Signature Target subpacket]: https://tools.ietf.org/html/rfc4880#section-5.2.3.25 /// [`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://tools.ietf.org/html/rfc4880#section-5.2.3.15 /// /// 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://tools.ietf.org/html/rfc4880#section-5.2.3.5 /// /// 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://tools.ietf.org/html/draft-ietf-openpgp-rfc4880bis-09.html#section-5.2.3.28 /// /// 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://tools.ietf.org/html/rfc4880#section-5.2.3.16 /// [Intended Recipient subpacket]: https://tools.ietf.org/html/draft-ietf-openpgp-rfc4880bis-09.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.16 of RFC 4880] for details. /// /// [Section 5.2.3.16 of RFC 4880]: https://tools.ietf.org/html/rfc4880#section-5.2.3.16 /// /// 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://tools.ietf.org/html/rfc4880#section-5.2.3.16 /// [Intended Recipient subpacket]: https://tools.ietf.org/html/draft-ietf-openpgp-rfc4880bis-09.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.16 of RFC 4880] for details. /// /// [Section 5.2.3.16 of RFC 4880]: https://tools.ietf.org/html/rfc4880#section-5.2.3.16 /// /// 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://tools.ietf.org/html/rfc4880#section-5.2.3.7 /// /// 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://tools.ietf.org/html/rfc4880#section-5.2.3.8 /// /// 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://tools.ietf.org/html/rfc4880#section-5.2.3.9 /// /// 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 Algorithms subpacket. /// /// The [Preferred AEAD Algorithms subpacket] indicates what AEAD /// algorithms the key holder prefers ordered by preference. If /// this is set, then the AEAD feature flag should in the /// [Features subpacket] should also be set. /// /// Note: because support for AEAD has not yet been standardized, /// we recommend not yet advertising support for it. /// /// [Preferred AEAD Algorithms subpacket]: https://tools.ietf.org/html/draft-ietf-openpgp-rfc4880bis-09.html#section-5.2.3.8 /// [Features subpacket]: https://tools.ietf.org/html/draft-ietf-openpgp-rfc4880bis-09.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 /// /// 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_algorithms(&self) -> Option<&[AEADAlgorithm]> { // array of one-octet values if let Some(sb) = self.subpacket( SubpacketTag::PreferredAEADAlgorithms) { if let SubpacketValue::PreferredAEADAlgorithms(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://tools.ietf.org/html/rfc4880#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 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://tools.ietf.org/html/rfc4880#section-5.2.3.18 /// /// 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://tools.ietf.org/html/rfc4880#section-5.2.3.20 /// /// 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://tools.ietf.org/html/rfc4880#section-5.2.3.19 /// [`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://tools.ietf.org/html/rfc4880#section-5.2.3.21 /// /// 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://tools.ietf.org/html/rfc4880#section-5.2.3.22 /// /// 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://tools.ietf.org/html/rfc4880#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 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://tools.ietf.org/html/rfc4880#section-5.2.3.24 /// [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://tools.ietf.org/html/rfc4880#section-5.2.3.25 /// /// 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://tools.ietf.org/html/rfc4880#section-5.2.3.26 /// [Primary Key Binding signature]: https://tools.ietf.org/html/rfc4880#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://tools.ietf.org/html/rfc4880#section-5.2.3.26 /// [Primary Key Binding signature]: https://tools.ietf.org/html/rfc4880#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://tools.ietf.org/html/draft-ietf-openpgp-rfc4880bis-09.html#section-5.2.3.29 /// /// 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 attested 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 attested to more /// certifications, the digests are split across multiple attested /// 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 attested /// key signatures with the most recent creation time into a data /// structure that allows efficient lookups, such as [`HashSet`] /// or [`BTreeSet`]. /// /// See [Section 5.2.3.30 of RFC 4880bis] for details. /// /// [`HashSet`]: std::collections::HashSet /// [`BTreeSet`]: std::collections::BTreeSet /// [Section 5.2.3.30 of RFC 4880bis]: https://tools.ietf.org/html/draft-ietf-openpgp-rfc4880bis-10.html#section-5.2.3.30 pub fn attested_certifications(&self) -> Result<impl Iterator<Item=&[u8]> + Send + Sync> { if self.hashed_area() .subpackets(SubpacketTag::AttestedCertifications).count() > 1 || self.unhashed_area() .subpackets(SubpacketTag::AttestedCertifications).count() != 0 { return Err(Error::BadSignature( "Wrong number of attested certification subpackets".into()) .into()); } Ok(self.subpackets(SubpacketTag::AttestedCertifications) .flat_map(|sb| { match sb.value() { SubpacketValue::AttestedCertifications(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), // XXX: Once there are more signature variants: //sig => Err( // Error::InvalidArgument( // format!("Got a v{}, require a v4 signature", sig.version()) // .into()) // .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> /// # = Key4::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> /// # = Key4::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(None, 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> /// # = Key4::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> /// # = Key4::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)?; /// # 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)?; /// # 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 both the /// [Issuer Fingerprint subpacket] and the Issuer subpacket, if /// they have not been set explicitly. /// /// [Signature Creation Time subpacket]: https://tools.ietf.org/html/rfc4880#section-5.2.3.4 /// [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(None, 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.overrode_creation_time = true; self.hashed_area.replace(Subpacket::new( SubpacketValue::SignatureCreationTime( creation_time.into().try_into()?), 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://tools.ietf.org/html/rfc4880#section-5.2.3.4 /// /// 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(None, Some("alice@example.org")) /// # .generate()?; /// # let mut alices_signer = alice.primary_key().key().clone() /// # .parts_into_secret()?.into_keypair()?; /// # let (bob, _) = /// # CertBuilder::general_purpose(None, 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://tools.ietf.org/html/rfc4880#section-5.2.3.4 /// /// [Section 5.2.3.4 of RFC 4880] 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.4 of RFC 4880]: https://tools.ietf.org/html/rfc4880#section-5.2.3.4 /// /// # 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(None, 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.overrode_creation_time = true; self.hashed_area.remove_all(SubpacketTag::SignatureCreationTime); self.unhashed_area.remove_all(SubpacketTag::SignatureCreationTime); Ok(self) } /// 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://tools.ietf.org/html/rfc4880#section-5.2.3.10 /// /// 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://tools.ietf.org/html/rfc4880#section-5.2.3.4 /// [`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 fallback to an older binding /// signature. /// /// [Key Expiration Time subpacket]: https://tools.ietf.org/html/rfc4880#section-5.2.3.6 /// [`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(None, 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(None, 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://tools.ietf.org/html/rfc4880#section-5.2.3.11 /// /// 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(None, Some("alice@example.org")) /// .generate()?; /// let mut alices_signer = alice.primary_key().key().clone() /// .parts_into_secret()?.into_keypair()?; /// /// let (bob, _) /// = CertBuilder::general_purpose(None, 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)?; /// # 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://tools.ietf.org/html/rfc4880#section-5.2.3.13 /// /// The Trust Signature subpacket indicates to 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(None, Some("alice@example.org")) /// .generate()?; /// let mut alices_signer = alice.primary_key().key().clone() /// .parts_into_secret()?.into_keypair()?; /// /// let (bob, _) /// = CertBuilder::general_purpose(None, 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)?; /// # 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://tools.ietf.org/html/rfc4880#section-5.2.3.14 /// /// 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://tools.ietf.org/html/rfc4880#section-5.2.3.13 /// [`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(None, 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(None, 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)?; /// # 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://tools.ietf.org/html/rfc4880#section-5.2.3.14 /// /// 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://tools.ietf.org/html/rfc4880#section-5.2.3.13 /// [`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(None, 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(None, 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)?; /// # 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://tools.ietf.org/html/rfc4880#section-5.2.3.12 /// /// 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://tools.ietf.org/html/rfc4880#section-5.2.1 /// [Signature Target subpacket]: https://tools.ietf.org/html/rfc4880#section-5.2.3.25 /// [`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(None, Some("alice@example.org")) /// .generate()?; /// let mut alices_signer = alice.primary_key().key().clone() /// .parts_into_secret()?.into_keypair()?; /// /// let (bob, _) /// = CertBuilder::general_purpose(None, 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)?; /// # 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://tools.ietf.org/html/rfc4880#section-5.2.3.6 /// /// 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://tools.ietf.org/html/rfc4880#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 fallback to an older binding /// signature. /// /// [Signature Expiration Time subpacket]: https://tools.ietf.org/html/rfc4880#section-5.2.3.6 /// [`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(None, 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 key in cert.with_policy(p, None)?.keys().subkeys() { /// // This reuses any existing backsignature. /// let sig = SignatureBuilder::from(key.binding_signature().clone()) /// .set_key_validity_period(std::time::Duration::new(10 * 60, 0))? /// .sign_subkey_binding(&mut signer, None, &key)?; /// sigs.push(sig); /// } /// /// let cert = cert.insert_packets(sigs)?; /// # assert_eq!(cert.bad_signatures().count(), 0); /// # /// # // "Before" /// # for key in cert.with_policy(p, None)?.keys().subkeys() { /// # assert_eq!(key.bundle().self_signatures().len(), 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://tools.ietf.org/html/rfc4880#section-5.2.3.6 /// /// 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://tools.ietf.org/html/rfc4880#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 fallback to an older binding /// signature. /// /// [Signature Expiration Time subpacket]: https://tools.ietf.org/html/rfc4880#section-5.2.3.6 /// [`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(None, 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 key in cert.with_policy(p, None)?.keys().subkeys() { /// // This reuses any existing backsignature. /// let sig = SignatureBuilder::from(key.binding_signature().clone()) /// .set_key_expiration_time(&key, /// time::SystemTime::now() /// + time::Duration::new(10 * 60, 0))? /// .sign_subkey_binding(&mut signer, None, &key)?; /// sigs.push(sig); /// } /// /// let cert = cert.insert_packets(sigs)?; /// # assert_eq!(cert.bad_signatures().count(), 0); /// # /// # // "Before" /// # for key in cert.with_policy(p, None)?.keys().subkeys() { /// # assert_eq!(key.bundle().self_signatures().len(), 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://tools.ietf.org/html/rfc4880#section-5.2.3.7 /// /// 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)?; /// # 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 a new subpacket containing the specified /// value. That is, this function first removes any Revocation /// Key subpacket from the hashed subpacket area, and then adds a /// new one. /// /// [Revocation Key subpacket]: https://tools.ietf.org/html/rfc4880#section-5.2.3.15 /// /// 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()) /// .set_revocation_key(vec![ /// RevocationKey::new(bob.primary_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)?; /// # 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: Vec<RevocationKey>) -> Result<Self> { self.hashed_area.remove_all(SubpacketTag::RevocationKey); for rk in rk.into_iter() { self.hashed_area.add(Subpacket::new( SubpacketValue::RevocationKey(rk), true)?)?; } Ok(self) } /// Adds 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://tools.ietf.org/html/rfc4880#section-5.2.3.5 /// [`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 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://tools.ietf.org/html/draft-ietf-openpgp-rfc4880bis-09.html#section-5.2.3.28 /// [`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(None, Some("alice@example.org")) /// # .generate()?; /// # let mut alices_signer = alicev4.primary_key().key().clone().parts_into_secret()?.into_keypair()?; /// # let (alicev5, _) = /// # CertBuilder::general_purpose(None, 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://tools.ietf.org/html/rfc4880#section-5.2.3.5 /// [`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 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://tools.ietf.org/html/draft-ietf-openpgp-rfc4880bis-09.html#section-5.2.3.28 /// [`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(None, Some("alice@example.org")) /// # .generate()?; /// # let mut alices_signer = alicev4.primary_key().key().clone().parts_into_secret()?.into_keypair()?; /// # let (alicev5, _) = /// # CertBuilder::general_purpose(None, 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://tools.ietf.org/html/rfc4880#section-5.2.3.16 /// [`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://tools.ietf.org/html/draft-ietf-openpgp-rfc4880bis-09.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.16 of RFC 4880] for details. /// /// [Section 5.2.3.16 of RFC 4880]: https://tools.ietf.org/html/rfc4880#section-5.2.3.16 /// /// # 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 userid = vc.primary_userid().expect("Added a User ID"); /// /// let template = userid.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, &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)?; /// # 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://tools.ietf.org/html/rfc4880#section-5.2.3.16 /// [`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://tools.ietf.org/html/draft-ietf-openpgp-rfc4880bis-09.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.16 of RFC 4880] for details. /// /// [Section 5.2.3.16 of RFC 4880]: https://tools.ietf.org/html/rfc4880#section-5.2.3.16 /// /// # 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 userid = vc.primary_userid().expect("Added a User ID"); /// /// let template = userid.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, &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)?; /// # 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://tools.ietf.org/html/rfc4880#section-5.2.3.8 /// /// 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)?; /// # 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://tools.ietf.org/html/rfc4880#section-5.2.3.9 /// /// 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)?; /// # 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://tools.ietf.org/html/rfc4880#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::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)?; /// # 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://tools.ietf.org/html/rfc4880#section-5.2.3.18 /// /// 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)?; /// # 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://tools.ietf.org/html/rfc4880#section-5.2.3.19 /// /// 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)?; /// /// 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://tools.ietf.org/html/rfc4880#section-5.2.3.20 /// /// 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)?; /// # /// # 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://tools.ietf.org/html/rfc4880#section-5.2.3.21 /// /// 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> /// = Key4::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()])?; /// /// # 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://tools.ietf.org/html/rfc4880#section-5.2.3.22 /// /// 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://tools.ietf.org/html/rfc4880#section-5.2.3.23 /// /// 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())?; /// /// // 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://tools.ietf.org/html/rfc4880#section-5.2.3.24 /// [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<()> { /// 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_algorithms(vec![ AEADAlgorithm::EAX ])? /// .set_features( /// sig.features().unwrap_or_else(Features::sequoia) /// .set_aead())? /// .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_algorithms(vec![ AEADAlgorithm::EAX ])? /// .set_features( /// sig.features().unwrap_or_else(Features::sequoia) /// .set_aead())? /// .sign_userid_binding(&mut signer, pk, ua.userid())?); /// } /// /// // Merge in the new signatures. /// let cert = cert.insert_packets(sigs)?; /// # 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://tools.ietf.org/html/rfc4880#section-5.2.3.25 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://tools.ietf.org/html/rfc4880#section-5.2.3.26 /// /// 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://tools.ietf.org/html/rfc4880#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()])?; /// /// 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://tools.ietf.org/html/draft-ietf-openpgp-rfc4880bis-09.html#section-5.2.3.28 /// [`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 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://tools.ietf.org/html/rfc4880#section-5.2.3.5 /// [`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(None, Some("alice@example.org")) /// # .generate()?; /// # let mut alices_signer = alicev4.primary_key().key().clone().parts_into_secret()?.into_keypair()?; /// # let (alicev5, _) = /// # CertBuilder::general_purpose(None, 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://tools.ietf.org/html/draft-ietf-openpgp-rfc4880bis-09.html#section-5.2.3.28 /// [`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 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://tools.ietf.org/html/rfc4880#section-5.2.3.5 /// [`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(None, Some("alice@example.org")) /// # .generate()?; /// # let mut alices_signer = alicev4.primary_key().key().clone().parts_into_secret()?.into_keypair()?; /// # let (alicev5, _) = /// # CertBuilder::general_purpose(None, 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 Algorithms subpacket. /// /// Replaces any [Preferred AEAD Algorithms subpacket] in the /// hashed subpacket area with a new subpacket containing the /// specified value. That is, this function first removes any /// Preferred AEAD Algorithms subpacket from the hashed subpacket /// area, and then adds a Preferred AEAD Algorithms subpacket. /// /// [Preferred AEAD Algorithms subpacket]: https://tools.ietf.org/html/draft-ietf-openpgp-rfc4880bis-09.html#section-5.2.3.8 /// /// The Preferred AEAD Algorithms subpacket indicates what AEAD /// algorithms the key holder prefers ordered by preference. If /// this is set, then the AEAD feature flag should in the /// [Features subpacket] should also be set. /// /// Note: because support for AEAD has not yet been standardized, /// we recommend not yet advertising support for it. /// /// [Features subpacket]: https://tools.ietf.org/html/draft-ietf-openpgp-rfc4880bis-09.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 /// /// 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<()> { /// 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_algorithms(vec![ AEADAlgorithm::EAX ])? /// .set_features( /// sig.features().unwrap_or_else(Features::sequoia) /// .set_aead())? /// .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_algorithms(vec![ AEADAlgorithm::EAX ])? /// .set_features( /// sig.features().unwrap_or_else(Features::sequoia) /// .set_aead())? /// .sign_userid_binding(&mut signer, pk, ua.userid())?); /// } /// /// // Merge in the new signatures. /// let cert = cert.insert_packets(sigs)?; /// # assert_eq!(cert.bad_signatures().count(), 0); /// # Ok(()) /// # } /// ``` pub fn set_preferred_aead_algorithms(mut self, preferences: Vec<AEADAlgorithm>) -> Result<Self> { self.hashed_area.replace(Subpacket::new( SubpacketValue::PreferredAEADAlgorithms(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://tools.ietf.org/html/draft-ietf-openpgp-rfc4880bis-09.html#section-5.2.3.29 /// [`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(None, Some("alice@example.org")) /// # .generate()?; /// # let mut alices_signer = alice.primary_key().key().clone().parts_into_secret()?.into_keypair()?; /// # let (bob, _) = /// # CertBuilder::general_purpose(None, Some("bob@example.org")) /// # .generate()?; /// # let (carol, _) = /// # CertBuilder::general_purpose(None, 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://tools.ietf.org/html/draft-ietf-openpgp-rfc4880bis-09.html#section-5.2.3.29 /// [`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(None, Some("alice@example.org")) /// # .generate()?; /// # let mut alices_signer = alice.primary_key().key().clone().parts_into_secret()?.into_keypair()?; /// # let (bob, _) = /// # CertBuilder::general_purpose(None, Some("bob@example.org")) /// # .generate()?; /// # let (carol, _) = /// # CertBuilder::general_purpose(None, 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 attested 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 [Attested 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 attested key /// signatures with the same creation time. /// /// See [Section 5.2.3.30 of RFC 4880bis] for details. /// /// [Section 5.2.3.30 of RFC 4880bis]: https://tools.ietf.org/html/draft-ietf-openpgp-rfc4880bis-10.html#section-5.2.3.30 /// [Attested Certification subpacket]: https://tools.ietf.org/html/draft-ietf-openpgp-rfc4880bis-10.html#section-5.2.3.30 pub fn set_attested_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::AttestedCertifications(digests), true)?)?; Ok(self) } } #[test] fn accessors() { use crate::types::Curve; let pk_algo = PublicKeyAlgorithm::EdDSA; let hash_algo = HashAlgorithm::SHA512; let hash = hash_algo.context().unwrap(); let mut sig = signature::SignatureBuilder::new(crate::types::SignatureType::Binary); let mut key: crate::packet::key::SecretKey = crate::packet::key::Key4::generate_ecc(true, Curve::Ed25519).unwrap().into(); let mut keypair = key.clone().into_keypair().unwrap(); // 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(b"bbbbbbbbbbbbbbbbbbbb"); let rk = RevocationKey::new(pk_algo, fp.clone(), true); sig = sig.set_revocation_key(vec![ rk.clone() ]).unwrap(); let sig_ = sig.clone().sign_hash(&mut keypair, hash.clone()).unwrap(); assert_eq!(sig_.revocation_keys().next().unwrap(), &rk); 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_mdc(); 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_aead(); 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.context().unwrap().digest_size()]; 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![AEADAlgorithm::EAX, AEADAlgorithm::OCB]; sig = sig.set_preferred_aead_algorithms(pref.clone()).unwrap(); let sig_ = sig.clone().sign_hash(&mut keypair, hash.clone()).unwrap(); assert_eq!(sig_.preferred_aead_algorithms(), Some(&pref[..])); let fps = vec![ Fingerprint::from_bytes(b"aaaaaaaaaaaaaaaaaaaa"), Fingerprint::from_bytes(b"bbbbbbbbbbbbbbbbbbbb"), ]; 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] 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, })); // 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, })); // 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, })); 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, })); 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, })); 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, })); 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, })); assert_eq!(sig.features().unwrap(), Features::empty().set_mdc()); assert_eq!(sig.subpacket(SubpacketTag::Features), Some(&Subpacket { length: 2.into(), critical: false, value: SubpacketValue::Features( Features::empty().set_mdc()), authenticated: false, })); 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, })); 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, })); 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, })); assert_eq!(sig.hashed_area().subpackets(SubpacketTag::NotationData) .collect::<Vec<_>>(), vec![&Subpacket { length: 32.into(), critical: false, value: SubpacketValue::NotationData(n.clone()), authenticated: false, }]); } 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, })); 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, })); } 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, })); 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, })); 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, })); 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, })); 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, })); // 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, })); 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, })); } // 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, })); 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, })); // 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, }, &Subpacket { length: 24.into(), critical: false, value: SubpacketValue::NotationData(n2), authenticated: false, }, &Subpacket { length: 35.into(), critical: false, value: SubpacketValue::NotationData(n3), authenticated: false, }, ]); } // # 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, })); 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, })); // 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, })); } // 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, })); 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, })); 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, })); 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() -> Result<()> { use crate::types::Curve; let hash_algo = HashAlgorithm::SHA512; let hash = hash_algo.context()?; 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()?; // 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(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(()) } ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-1.7.0/src/packet/signature.rs�������������������������������������������������������0000644�0000000�0000000�00000466465�00726746425�0017427�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 4880]. //! //! # Data Types //! //! The main signature-related data type is the [`Signature`] enum. //! This enum abstracts away the differences between the signature //! formats (the deprecated [version 3], the current [version 4], and //! the proposed [version 5] 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 only supports version 4 signatures //! ([`Signature4`]). However, future versions may include limited //! support for version 3 signatures to allow working with archived //! messages, and we intend to add support for version 5 signatures //! once the new version of the specification has been finalized. //! //! 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 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 4880]: https://tools.ietf.org/html/rfc4880#section-5.2 //! [`Signature`]: super::Signature //! [version 3]: https://tools.ietf.org/html/rfc1991#section-5.2.2 //! [version 4]: https://tools.ietf.org/html/rfc4880#section-5.2.3 //! [version 5]: https://tools.ietf.org/html/draft-ietf-openpgp-rfc4880bis-09.html#name-version-4-and-5-signature-p //! [notations]: https://tools.ietf.org/html/rfc4880#section-5.2.3.16 //! [streaming `Signer`]: crate::serialize::stream::Signer //! [`PacketParser`]: crate::parse::PacketParser //! [hints]: https://tools.ietf.org/html/rfc4880#section-5.13 //! [`Signature::normalize`]: super::Signature::normalize() //! [`SubpacketArea`]: subpacket::SubpacketArea //! [`SubpacketAreas`]: subpacket::SubpacketAreas //! [its documentation]: subpacket::SubpacketAreas use std::cmp::Ordering; use std::fmt; use std::hash::Hasher; use std::ops::{Deref, DerefMut}; use std::time::SystemTime; #[cfg(test)] use quickcheck::{Arbitrary, Gen}; use crate::Error; use crate::Result; use crate::crypto::{ mpi, hash::{self, Hash, Digest}, Signer, }; 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, }; #[cfg(test)] /// Like quickcheck::Arbitrary, but bounded. 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; /// 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://tools.ietf.org/html/rfc4880#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 } } /// 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.4 of RFC 4880], `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.4 of RFC 4880]: https://tools.ietf.org/html/rfc4880#section-5.2.3.4 /// [`Signature Creation Time`]: https://tools.ietf.org/html/rfc4880#section-5.2.3.4 /// [`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://tools.ietf.org/html/rfc4880#section-5.2.3.5 /// [`Issuer Fingerprint`]: https://tools.ietf.org/html/draft-ietf-openpgp-rfc4880bis-09.html#section-5.2.3.28 /// [`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))?; /// # 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 { overrode_creation_time: bool, original_creation_time: Option<SystemTime>, fields: SignatureFields, } 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 { 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(), } } } /// 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://tools.ietf.org/html/rfc4880#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://tools.ietf.org/html/rfc4880#section-5.2.3.5 /// [`SignatureBuilder::set_issuer`]: SignatureBuilder::set_issuer() /// [`Issuer Fingerprint`]: https://tools.ietf.org/html/draft-ietf-openpgp-rfc4880bis-09.html#section-5.2.3.28 /// [`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://tools.ietf.org/html/rfc4880#section-5.2.3.4 /// [`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> { match self.typ { SignatureType::Standalone => (), SignatureType::Unknown(_) => (), _ => return Err(Error::UnsupportedSignatureType(self.typ).into()), } self = self.pre_sign(signer)?; let mut hash = self.hash_algo().context()?; 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://tools.ietf.org/html/rfc4880#section-5.2.1 /// [`SignatureBuilder::sign_standalone`]: SignatureBuilder::sign_standalone() /// [Timestamp Signature]: https://tools.ietf.org/html/rfc4880#section-5.2.1 /// [`Signature Creation Time`]: https://tools.ietf.org/html/rfc4880#section-5.2.3.4 /// [timestamping services]: https://en.wikipedia.org/wiki/Trusted_timestamping /// [Signature Target subpacket]: https://tools.ietf.org/html/rfc4880#section-5.2.3.25 /// [`SignatureBuilder::set_signature_target`]: SignatureBuilder::set_signature_target() /// [Embedded Signature]: https://tools.ietf.org/html/rfc4880#section-5.2.3.26 /// [`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://tools.ietf.org/html/rfc4880#section-5.2.3.5 /// [`SignatureBuilder::set_issuer`]: SignatureBuilder::set_issuer() /// [`Issuer Fingerprint`]: https://tools.ietf.org/html/draft-ietf-openpgp-rfc4880bis-09.html#section-5.2.3.28 /// [`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://tools.ietf.org/html/rfc4880#section-5.2.3.4 /// [`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> { match self.typ { SignatureType::Timestamp => (), SignatureType::Unknown(_) => (), _ => return Err(Error::UnsupportedSignatureType(self.typ).into()), } self = self.pre_sign(signer)?; let mut hash = self.hash_algo().context()?; 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://tools.ietf.org/html/rfc4880#section-5.2.1 /// [Preferred Symmetric Algorithms]: https://tools.ietf.org/html/rfc4880#section-5.2.3.7 /// [Key Revocation Signature]: https://tools.ietf.org/html/rfc4880#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://tools.ietf.org/html/rfc4880#section-5.2.3.5 /// [`SignatureBuilder::set_issuer`]: SignatureBuilder::set_issuer() /// [`Issuer Fingerprint`]: https://tools.ietf.org/html/draft-ietf-openpgp-rfc4880bis-09.html#section-5.2.3.28 /// [`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://tools.ietf.org/html/rfc4880#section-5.2.3.4 /// [`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>>> { match self.typ { SignatureType::DirectKey => (), SignatureType::KeyRevocation => (), SignatureType::Unknown(_) => (), _ => return Err(Error::UnsupportedSignatureType(self.typ).into()), } self = self.pre_sign(signer)?; let mut hash = self.hash_algo().context()?; 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://tools.ietf.org/html/rfc4880#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://tools.ietf.org/html/rfc4880#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://tools.ietf.org/html/rfc4880#section-5.2.3.5 /// [`SignatureBuilder::set_issuer`]: SignatureBuilder::set_issuer() /// [`Issuer Fingerprint`]: https://tools.ietf.org/html/draft-ietf-openpgp-rfc4880bis-09.html#section-5.2.3.28 /// [`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://tools.ietf.org/html/rfc4880#section-5.2.3.4 /// [`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>>> { match self.typ { SignatureType::GenericCertification => (), SignatureType::PersonaCertification => (), SignatureType::CasualCertification => (), SignatureType::PositiveCertification => (), SignatureType::CertificationRevocation => (), SignatureType::Unknown(_) => (), _ => return Err(Error::UnsupportedSignatureType(self.typ).into()), } self = self.pre_sign(signer)?; let key = key.into().unwrap_or_else(|| signer.public().role_as_primary()); let mut hash = self.hash_algo().context()?; 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://tools.ietf.org/html/rfc4880#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://tools.ietf.org/html/rfc4880#section-5.2.3.5 /// [`SignatureBuilder::set_issuer`]: SignatureBuilder::set_issuer() /// [`Issuer Fingerprint`]: https://tools.ietf.org/html/draft-ietf-openpgp-rfc4880bis-09.html#section-5.2.3.28 /// [`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://tools.ietf.org/html/rfc4880#section-5.2.3.4 /// [`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<_, _> = /// Key4::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()])?; /// /// 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>>>, { match self.typ { SignatureType::SubkeyBinding => (), SignatureType::SubkeyRevocation => (), SignatureType::Unknown(_) => (), _ => return Err(Error::UnsupportedSignatureType(self.typ).into()), } self = self.pre_sign(signer)?; let primary = primary.into().unwrap_or_else(|| signer.public().role_as_primary()); let mut hash = self.hash_algo().context()?; 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://tools.ietf.org/html/rfc4880#section-5.2.1 /// [`SignatureBuilder::sign_subkey_binding`]: SignatureBuilder::sign_subkey_binding() /// [`Embedded Signature`]: https://tools.ietf.org/html/rfc4880#section-5.2.3.26 /// [`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://tools.ietf.org/html/rfc4880#section-5.2.3.5 /// [`SignatureBuilder::set_issuer`]: SignatureBuilder::set_issuer() /// [`Issuer Fingerprint`]: https://tools.ietf.org/html/draft-ietf-openpgp-rfc4880bis-09.html#section-5.2.3.28 /// [`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://tools.ietf.org/html/rfc4880#section-5.2.3.4 /// [`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<_, _> = /// Key4::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()])?; /// /// 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, { match self.typ { SignatureType::PrimaryKeyBinding => (), SignatureType::Unknown(_) => (), _ => return Err(Error::UnsupportedSignatureType(self.typ).into()), } self = self.pre_sign(subkey_signer)?; let mut hash = self.hash_algo().context()?; 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://tools.ietf.org/html/rfc4880#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://tools.ietf.org/html/rfc4880#section-5.2.3.5 /// [`SignatureBuilder::set_issuer`]: SignatureBuilder::set_issuer() /// [`Issuer Fingerprint`]: https://tools.ietf.org/html/draft-ietf-openpgp-rfc4880bis-09.html#section-5.2.3.28 /// [`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://tools.ietf.org/html/rfc4880#section-5.2.3.4 /// [`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()])?; /// 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>>> { match self.typ { SignatureType::GenericCertification => (), SignatureType::PersonaCertification => (), SignatureType::CasualCertification => (), SignatureType::PositiveCertification => (), SignatureType::CertificationRevocation => (), SignatureType::Unknown(_) => (), _ => return Err(Error::UnsupportedSignatureType(self.typ).into()), } self = self.pre_sign(signer)?; let key = key.into().unwrap_or_else(|| signer.public().role_as_primary()); let mut hash = self.hash_algo().context()?; 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://tools.ietf.org/html/rfc4880#section-5.2.3.5 /// [`SignatureBuilder::set_issuer`]: SignatureBuilder::set_issuer() /// [`Issuer Fingerprint`]: https://tools.ietf.org/html/draft-ietf-openpgp-rfc4880bis-09.html#section-5.2.3.28 /// [`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://tools.ietf.org/html/rfc4880#section-5.2.3.4 /// [`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: Box<dyn hash::Digest>) -> 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://tools.ietf.org/html/rfc4880#section-5.2.3.5 /// [`SignatureBuilder::set_issuer`]: SignatureBuilder::set_issuer() /// [`Issuer Fingerprint`]: https://tools.ietf.org/html/draft-ietf-openpgp-rfc4880bis-09.html#section-5.2.3.28 /// [`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://tools.ietf.org/html/rfc4880#section-5.2.3.4 /// [`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().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()?; /// /// // 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()), } // Hash the message let mut hash = self.hash_algo.context()?; hash.update(msg.as_ref()); 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) } /// 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(Key4::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> { use std::time; self.pk_algo = signer.public().pk_algo(); // Set the creation time. if ! self.overrode_creation_time { self = // See if we want to backdate the signature. if let Some(oct) = self.original_creation_time { let t = (oct + time::Duration::new(1, 0)).max( crate::now() - time::Duration::new(SIG_BACKDATE_BY, 0)); if t > crate::now() { return Err(Error::InvalidOperation( "Cannot create valid signature newer than template" .into()).into()); } self.set_signature_creation_time(t)? } else { self.set_signature_creation_time(crate::now())? }; } // 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 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)?; self.sort(); Ok(self) } fn sign(self, signer: &mut dyn Signer, digest: Vec<u8>) -> Result<Signature> { let mpis = signer.sign(self.hash_algo, &digest)?; Ok(Signature4 { common: Default::default(), fields: self.fields, digest_prefix: [digest[0], digest[1]], mpis, computed_digest: Some(digest), level: 0, additional_issuers: Vec::with_capacity(0), }.into()) } } impl From<Signature> for SignatureBuilder { fn from(sig: Signature) -> Self { match sig { Signature::V4(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 { overrode_creation_time: false, original_creation_time: creation_time, fields, } } } /// 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://tools.ietf.org/html/rfc4880#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, /// When used in conjunction with a one-pass signature, this is the /// hash computed over the enclosed message. computed_digest: Option<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: 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 .as_ref() .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: None, level: 0, additional_issuers: Vec::with_capacity(0), } } /// 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. /// /// [`PacketParser`]: crate::parse::PacketParser pub fn computed_digest(&self) -> Option<&[u8]> { self.computed_digest.as_ref().map(|d| &d[..]) } /// Sets the computed hash value. pub(crate) fn set_computed_digest(&mut self, hash: Option<Vec<u8>>) -> Option<Vec<u8>> { ::std::mem::replace(&mut self.computed_digest, hash) } /// 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 or not 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://tools.ietf.org/html/rfc4880#section-5.2.3.11 /// [`Revocation Key`]: https://tools.ietf.org/html/rfc4880#section-5.2.3.15 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 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://tools.ietf.org/html/rfc4880#section-5.2.3.5 /// [Issuer Fingerprint subpacket]: https://tools.ietf.org/html/draft-ietf-openpgp-rfc4880bis-09.html#section-5.2.3.28 /// /// 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. /// /// 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(()); } /// Makes an authenticated subpacket. fn authenticated_subpacket(v: SubpacketValue) -> Result<Subpacket> { let mut p = Subpacket::new(v, false)?; p.set_authenticated(true); Ok(p) } let issuers = self.get_issuers(); for id in std::mem::replace(&mut self.additional_issuers, Vec::with_capacity(0)) { if ! issuers.contains(&id) { match id { KeyHandle::KeyID(id) => self.unhashed_area_mut().add(authenticated_subpacket( SubpacketValue::Issuer(id))?)?, KeyHandle::Fingerprint(fp) => self.unhashed_area_mut().add(authenticated_subpacket( SubpacketValue::IssuerFingerprint(fp))?)?, } } } 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::*; 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 | AttestedCertifications | 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 std::mem::replace(&mut self.additional_issuers, Vec::with_capacity(0)).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.clone()) { 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. If inserting fails at // any moment, stop. 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 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 made /// 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>(&mut self, key: &Key<P, R>, mut hash: Box<dyn hash::Digest>) -> Result<()> where P: key::KeyParts, R: key::KeyRole, { self.hash(&mut hash); let mut digest = vec![0u8; hash.digest_size()]; hash.digest(&mut digest)?; self.verify_digest(key, digest) } /// 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 made /// 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>(&mut self, key: &Key<P, R>, digest: D) -> Result<()> where P: key::KeyParts, R: key::KeyRole, D: AsRef<[u8]>, { 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()); } let result = key.verify(self.mpis(), self.hash_algo(), digest.as_ref()); if result.is_ok() { // Mark information in this signature as authenticated. // The hashed subpackets are authenticated by the // signature. self.hashed_area_mut().iter_mut().for_each(|p| { p.set_authenticated(true); }); // The self-authenticating unhashed subpackets are // authenticated by the key's identity. self.unhashed_area_mut().iter_mut().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 id = KeyHandle::from(key.keyid()); if ! (issuers.contains(&id) || self.additional_issuers.contains(&id)) { self.additional_issuers.push(id); } let fp = KeyHandle::from(key.fingerprint()); if ! (issuers.contains(&fp) || self.additional_issuers.contains(&fp)) { self.additional_issuers.push(fp); } } 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<P, R>(&mut 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()); } if let Some(hash) = self.computed_digest.take() { let result = self.verify_digest(key, &hash); self.computed_digest = Some(hash); result } else { Err(Error::BadSignature("Hash not computed.".to_string()).into()) } } /// 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>(&mut 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()?; self.hash_standalone(&mut hash); self.verify_digest(key, &hash.into_digest()?[..]) } /// 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>(&mut 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()?; self.hash_timestamp(&mut hash); self.verify_digest(key, &hash.into_digest()?[..]) } /// 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 /// made 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>(&mut 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()?; self.hash_direct_key(&mut hash, pk); self.verify_digest(signer, &hash.into_digest()?[..]) } /// 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 /// made 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>(&mut 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()?; self.hash_direct_key(&mut hash, pk); self.verify_digest(signer, &hash.into_digest()?[..]) } /// 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 /// made 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>( &mut 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()?; self.hash_subkey_binding(&mut hash, pk, subkey); self.verify_digest(signer, &hash.into_digest()?[..])?; // 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_mut(SubpacketTag::EmbeddedSignature) { let result = if let SubpacketValue::EmbeddedSignature(sig) = backsig.value_mut() { sig.verify_primary_key_binding(pk, subkey) } else { unreachable!("subpackets_mut(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 /// made 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>( &mut 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()?; self.hash_primary_key_binding(&mut hash, pk, subkey); self.verify_digest(subkey, &hash.into_digest()?[..]) } /// 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 /// made 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>( &mut 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()?; self.hash_subkey_binding(&mut hash, pk, subkey); self.verify_digest(signer, &hash.into_digest()?[..]) } /// 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 /// made 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>(&mut 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()?; self.hash_userid_binding(&mut hash, pk, userid); self.verify_digest(signer, &hash.into_digest()?[..]) } /// 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 /// made 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>(&mut 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()?; self.hash_userid_binding(&mut hash, pk, userid); self.verify_digest(signer, &hash.into_digest()?[..]) } /// Verifies an attested key signature on a user id. /// /// This feature is [experimental](crate#experimental-features). /// /// Allows the certificate owner to attest to third party /// certifications. See [Section 5.2.3.30 of RFC 4880bis] for /// details. /// /// `self` is the attested 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 /// made 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. /// /// [Section 5.2.3.30 of RFC 4880bis]: https://tools.ietf.org/html/draft-ietf-openpgp-rfc4880bis-10.html#section-5.2.3.30 pub fn verify_userid_attestation<P, Q, R>( &mut 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::AttestationKey { return Err(Error::UnsupportedSignatureType(self.typ()).into()); } let mut hash = self.hash_algo().context()?; if self.attested_certifications()? .any(|d| d.len() != hash.digest_size()) { return Err(Error::BadSignature( "Wrong number of bytes in certification subpacket".into()) .into()); } self.hash_userid_binding(&mut hash, pk, userid); self.verify_digest(signer, &hash.into_digest()?[..]) } /// 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 /// made 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>(&mut 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()?; self.hash_user_attribute_binding(&mut hash, pk, ua); self.verify_digest(signer, &hash.into_digest()?[..]) } /// 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 /// made 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>( &mut 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()?; self.hash_user_attribute_binding(&mut hash, pk, ua); self.verify_digest(signer, &hash.into_digest()?[..]) } /// Verifies an attested key signature on a user attribute. /// /// This feature is [experimental](crate#experimental-features). /// /// Allows the certificate owner to attest to third party /// certifications. See [Section 5.2.3.30 of RFC 4880bis] for /// details. /// /// `self` is the attested 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 /// made 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. /// /// [Section 5.2.3.30 of RFC 4880bis]: https://tools.ietf.org/html/draft-ietf-openpgp-rfc4880bis-10.html#section-5.2.3.30 pub fn verify_user_attribute_attestation<P, Q, R>( &mut 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::AttestationKey { return Err(Error::UnsupportedSignatureType(self.typ()).into()); } let mut hash = self.hash_algo().context()?; if self.attested_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_binding(&mut hash, pk, ua); self.verify_digest(signer, &hash.into_digest()?[..]) } /// 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 /// made 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>(&mut 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()?; let mut digest = vec![0u8; hash.digest_size()]; hash.update(msg.as_ref()); self.hash(&mut hash); hash.digest(&mut digest)?; self.verify_digest(signer, &digest[..]) } } 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 { Signature4::arbitrary_bounded(g, depth).into() } } #[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), }, _ => unreachable!(), }; Signature4 { common: Arbitrary::arbitrary(g), fields, digest_prefix: [Arbitrary::arbitrary(g), Arbitrary::arbitrary(g)], mpis, computed_digest: None, level: 0, additional_issuers: Vec::with_capacity(0), } } } #[cfg(test)] impl_arbitrary_with_bound!(Signature4); #[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; 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::{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(|k| k.pk_algo().is_supported()) { eprintln!("Skipping because one algorithm is not supported"); continue; } if let Some(curve) = match cert.primary_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(cert.primary_key().key()) .map(|_| true).unwrap_or(false); eprintln!(" Primary {:?}: {:?}", cert.fingerprint(), result); if result { good += 1; } for sk in cert.subkeys() { let result = sig.verify(sk.key()) .map(|_| true).unwrap_or(false); 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."); } } #[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.context().unwrap().digest_size()]; crypto::random(&mut hash); 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().pk_algo().is_supported() { eprintln!("Skipping because we don't support the algo"); continue; } if let Some(curve) = match cert.primary_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(); // Make signature. let mut sig = sig.sign_hash(&mut pair, hash).unwrap(); // Good signature. let mut hash = hash_algo.context().unwrap(); sig.hash(&mut hash); 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; let key: Key<key::SecretParts, key::PrimaryRole> = Key4::generate_ecc(true, Curve::Ed25519) .unwrap().into(); let msg = b"Hello, World"; let mut pair = key.into_keypair().unwrap(); let mut 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 mut 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] 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(); 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 mut 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 = Key4::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(); hash.update(&msg[..]); let fp = Fingerprint::from_bytes(b"bbbbbbbbbbbbbbbbbbbb"); 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 = Key4::generate_ecc(true, Curve::Ed25519).unwrap().into(); let mut pair = key.into_keypair().unwrap(); let mut sig = SignatureBuilder::new(SignatureType::Standalone) .sign_standalone(&mut pair) .unwrap(); sig.verify_standalone(pair.public()).unwrap(); } #[test] 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(mut sig) = p { let mut hash = sig.hash_algo().context().unwrap(); sig.hash_standalone(&mut hash); 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 = Key4::generate_ecc(true, Curve::Ed25519).unwrap().into(); let mut pair = key.into_keypair().unwrap(); let mut 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(None, 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); let creation_time = alice.userids().next().unwrap().self_signatures().next().unwrap() .signature_creation_time().unwrap(); for i in 0..2 * SIG_BACKDATE_BY { 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(); assert_eq!(sig.signature_creation_time().unwrap(), creation_time + std::time::Duration::new(i, 0)); 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()) { Ok(v) => v, Err(e) => if i < SIG_BACKDATE_BY { return Err(e); // Not cool. } else { assert!(e.to_string().contains( "Cannot create valid signature newer than \ template")); return Ok(()); // Cool. }, }; // Merge it and check that the new binding signature is // the current one. alice = alice.insert_packets(new_sig.clone())?; let sig = alice.with_policy(p, None)?.userids().next().unwrap() .binding_signature(); assert_eq!(sig, &new_sig); } panic!("We were unexpectedly able to update binding signatures {} \ times. This is either a very slow build environment, or \ there is a bug. Please get in contact.", 2 * SIG_BACKDATE_BY); } /// 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 mut 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 = Key4::generate_ecc(true, Curve::Ed25519)?.into(); let mut pair = key.into_keypair()?; let msg = b"Hello, World"; let mut hash = HashAlgorithm::SHA256.context()?; 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(); assert_eq!(issuers.len(), 3); assert!(issuers.contains(&KeyHandle::from(&fp))); assert!(issuers.contains(&KeyHandle::from(&keyid))); assert!(issuers.contains(&KeyHandle::from(&dummy))); // Same, but the other way around. let merged = malicious.clone().merge(sig.clone())?; let issuers = merged.get_issuers(); assert_eq!(issuers.len(), 3); assert!(issuers.contains(&KeyHandle::from(&fp))); assert!(issuers.contains(&KeyHandle::from(&keyid))); assert!(issuers.contains(&KeyHandle::from(&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(); assert_eq!(issuers.len(), 2); assert!(issuers.contains(&KeyHandle::from(&fp))); assert!(issuers.contains(&KeyHandle::from(&keyid))); // Same, but the other way around. let merged = malicious.clone().merge(sig.clone())?; let issuers = merged.get_issuers(); assert_eq!(issuers.len(), 2); assert!(issuers.contains(&KeyHandle::from(&fp))); assert!(issuers.contains(&KeyHandle::from(&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 mut verified = sig.clone(); verified.verify_hash(pair.public(), hash.clone())?; let merged = verified.clone().merge(malicious.clone())?; let issuers = merged.get_issuers(); assert!(issuers.contains(&KeyHandle::from(&fp))); assert!(issuers.contains(&KeyHandle::from(&keyid))); // Same, but the other way around. let merged = malicious.clone().merge(verified.clone())?; let issuers = merged.get_issuers(); assert!(issuers.contains(&KeyHandle::from(&fp))); assert!(issuers.contains(&KeyHandle::from(&keyid))); Ok(()) } } �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-1.7.0/src/packet/skesk.rs�����������������������������������������������������������0000644�0000000�0000000�00000055402�00726746425�0016527�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 4880] for details. //! //! [Section 5.3 of RFC 4880]: https://tools.ietf.org/html/rfc4880#section-5.3 use std::ops::{Deref, DerefMut}; #[cfg(test)] use quickcheck::{Arbitrary, Gen}; use crate::Result; use crate::crypto::{self, S2K, Password, SessionKey}; use crate::crypto::aead::CipherOp; use crate::Error; use crate::types::{ AEADAlgorithm, SymmetricAlgorithm, }; use crate::packet::{self, SKESK}; use crate::Packet; impl SKESK { /// Derives the key inside this SKESK 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)> { match self { SKESK::V4(ref s) => s.decrypt(password), SKESK::V5(ref s) => s.decrypt(password), } } } #[cfg(test)] impl Arbitrary for SKESK { fn arbitrary(g: &mut Gen) -> Self { if bool::arbitrary(g) { SKESK::V4(SKESK4::arbitrary(g)) } else { SKESK::V5(SKESK5::arbitrary(g)) } } } /// Holds an symmetrically encrypted session key version 4. /// /// Holds an symmetrically encrypted session key. The session key is /// needed to decrypt the actual ciphertext. See [Section 5.3 of RFC /// 4880] for details. /// /// [Section 5.3 of RFC 4880]: https://tools.ietf.org/html/rfc4880#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 SKESK5, hence we have a version /// field. version: u8, /// Symmetric algorithm used to encrypt the session key. sym_algo: SymmetricAlgorithm, /// Key derivation method for the symmetric key. 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. 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`] or /// [`AED`] packet), 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`]: super::SEIP /// [`AED`]: super::AED 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`]: super::super::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. #[allow(deprecated)] match self.s2k { S2K::Simple{ .. } => Err(Error::InvalidOperation( "SKESK4: Cannot use Simple S2K without ESK".into()) .into()), _ => 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() } } /// Holds an symmetrically encrypted session key version 5. /// /// Holds an symmetrically encrypted session key. The session key is /// needed to decrypt the actual ciphertext. See [Section 5.3 of RFC /// 4880bis] for details. /// /// [Section 5.3 of RFC 4880]: https://tools.ietf.org/html/draft-ietf-openpgp-rfc4880bis-05#section-5.3 /// /// This feature is [experimental](super::super#experimental-features). #[derive(Clone, Debug)] pub struct SKESK5 { /// Common fields. pub(crate) skesk4: SKESK4, /// AEAD algorithm. aead_algo: AEADAlgorithm, /// Initialization vector for the AEAD algorithm. /// /// If we recognized the S2K object during parsing, we can /// successfully parse the data into S2K, AEAED 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. In this case, the raw data is put into /// the `esk` field, and `aead_iv` is set to `None`. aead_iv: Option<Box<[u8]>>, /// Digest for the AEAD algorithm. aead_digest: Box<[u8]>, } assert_send_and_sync!(SKESK5); // Because the S2K, IV, and ESK cannot be cleanly separated at parse // time, we need to carefully compare and hash SKESK5 packets. impl PartialEq for SKESK5 { fn eq(&self, other: &SKESK5) -> bool { self.skesk4.version == other.skesk4.version && self.skesk4.sym_algo == other.skesk4.sym_algo && self.aead_digest == other.aead_digest // Treat S2K, IV, and ESK as opaque blob. && { // XXX: This would be nicer without the allocations. use crate::serialize::MarshalInto; let mut a = self.skesk4.s2k.to_vec().unwrap(); let mut b = other.skesk4.s2k.to_vec().unwrap(); if let Ok(iv) = self.aead_iv() { a.extend_from_slice(iv); } if let Ok(iv) = other.aead_iv() { b.extend_from_slice(iv); } a.extend_from_slice(self.skesk4.raw_esk()); b.extend_from_slice(other.skesk4.raw_esk()); a == b } } } impl Eq for SKESK5 {} impl std::hash::Hash for SKESK5 { fn hash<H: std::hash::Hasher>(&self, state: &mut H) { self.skesk4.version.hash(state); self.skesk4.sym_algo.hash(state); self.aead_digest.hash(state); // Treat S2K, IV, and ESK as opaque blob. // XXX: This would be nicer without the allocations. use crate::serialize::MarshalInto; let mut a = self.skesk4.s2k.to_vec().unwrap(); if let Some(iv) = self.aead_iv.as_ref() { a.extend_from_slice(iv); } a.extend_from_slice(self.skesk4.raw_esk()); a.hash(state); } } impl Deref for SKESK5 { type Target = SKESK4; fn deref(&self) -> &Self::Target { &self.skesk4 } } impl DerefMut for SKESK5 { fn deref_mut(&mut self) -> &mut Self::Target { &mut self.skesk4 } } impl SKESK5 { /// Creates a new SKESK version 5 packet. /// /// The given symmetric algorithm is the one used to encrypt the /// session key. pub fn new(esk_algo: SymmetricAlgorithm, esk_aead: AEADAlgorithm, s2k: S2K, iv: Box<[u8]>, esk: Box<[u8]>, digest: Box<[u8]>) -> Result<Self> { Self::new_raw(esk_algo, esk_aead, s2k, Ok((iv, esk)), digest) } /// Creates a new SKESK version 5 packet. /// /// The given symmetric algorithm is the one used to encrypt the /// session key. pub(crate) fn new_raw(esk_algo: SymmetricAlgorithm, esk_aead: AEADAlgorithm, s2k: S2K, iv_esk: std::result::Result<(Box<[u8]>, Box<[u8]>), Box<[u8]>>, digest: Box<[u8]>) -> Result<Self> { let (iv, esk) = match iv_esk { Ok((iv, esk)) => (Some(iv), Ok(Some(esk))), Err(raw) => (None, Err(raw)), }; Ok(SKESK5{ skesk4: SKESK4{ common: Default::default(), version: 5, sym_algo: esk_algo, s2k, esk, }, aead_algo: esk_aead, aead_iv: iv, aead_digest: digest, }) } /// Creates a new SKESK version 5 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`] or /// [`AED`] packet), 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`]: super::SEIP /// [`AED`]: super::AED 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 key = s2k.derive_key(password, esk_algo.key_size()?)?; let mut iv = vec![0u8; esk_aead.iv_size()?]; crypto::random(&mut iv); let mut ctx = esk_aead.context(esk_algo, &key, &iv, CipherOp::Encrypt)?; // Prepare associated data. let ad = [0xc3, 5, esk_algo.into(), esk_aead.into()]; ctx.update(&ad); // We need to prefix the cipher specifier to the session key. let mut esk = vec![0u8; session_key.len()]; ctx.encrypt(&mut esk, session_key); // Digest. let mut digest = vec![0u8; esk_aead.digest_size()?]; ctx.digest(&mut digest); SKESK5::new(esk_algo, esk_aead, s2k, iv.into_boxed_slice(), esk.into(), digest.into_boxed_slice()) } /// Derives the key inside this `SKESK5` from `password`. /// /// Returns a tuple containing a placeholder symmetric cipher and /// the key itself. `SKESK5` packets do not contain the symmetric /// cipher algorithm and instead rely on the `AED` packet that /// contains it. // XXX: This function should return Result<SessionKey>, but then // SKESK::decrypt must return an // Result<(Option<SymmetricAlgorithm>, _)> and // DecryptionHelper::decrypt and PacketParser::decrypt must be // adapted as well. pub fn decrypt(&self, password: &Password) -> Result<(SymmetricAlgorithm, SessionKey)> { let key = self.s2k().derive_key(password, self.symmetric_algo().key_size()?)?; if let Some(esk) = self.esk()? { // Use the derived key to decrypt the ESK. let mut cipher = self.aead_algo.context( self.symmetric_algo(), &key, self.aead_iv()?, CipherOp::Decrypt)?; let ad = [0xc3, 5 /* Version. */, self.symmetric_algo().into(), self.aead_algo.into()]; cipher.update(&ad); let mut plain: SessionKey = vec![0; esk.len()].into(); let mut digest = vec![0; self.aead_algo.digest_size()?]; cipher.decrypt(&mut plain, esk); cipher.digest(&mut digest); if digest[..] == self.aead_digest[..] { Ok((SymmetricAlgorithm::Unencrypted, plain)) } else { Err(Error::ManipulatedMessage.into()) } } else { Err(Error::MalformedPacket( "No encrypted session key in v5 SKESK packet".into()) .into()) } } /// 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. /// /// 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 aead_iv(&self) -> Result<&[u8]> { self.aead_iv.as_ref() .map(|iv| &iv[..]) .ok_or_else(|| Error::MalformedPacket( format!("Unknown S2K: {:?}", self.s2k)).into()) } /// Sets the AEAD initialization vector. pub fn set_aead_iv(&mut self, iv: Box<[u8]>) -> Option<Box<[u8]>> { ::std::mem::replace(&mut self.aead_iv, Some(iv)) } /// Gets the AEAD digest. pub fn aead_digest(&self) -> &[u8] { &self.aead_digest } /// Sets the AEAD digest. pub fn set_aead_digest(&mut self, digest: Box<[u8]>) -> Box<[u8]> { ::std::mem::replace(&mut self.aead_digest, digest) } } impl From<SKESK5> for super::SKESK { fn from(p: SKESK5) -> Self { super::SKESK::V5(p) } } impl From<SKESK5> for Packet { fn from(s: SKESK5) -> Self { Packet::SKESK(SKESK::V5(s)) } } #[cfg(test)] impl Arbitrary for SKESK5 { fn arbitrary(g: &mut Gen) -> Self { let algo = AEADAlgorithm::EAX; // The only one we dig. let mut iv = vec![0u8; algo.iv_size().unwrap()]; for b in iv.iter_mut() { *b = u8::arbitrary(g); } let mut digest = vec![0u8; algo.digest_size().unwrap()]; for b in digest.iter_mut() { *b = u8::arbitrary(g); } SKESK5::new(SymmetricAlgorithm::arbitrary(g), algo, S2K::arbitrary(g), iv.into_boxed_slice(), Vec::<u8>::arbitrary(g).into(), digest.into_boxed_slice()) .unwrap() } } #[cfg(test)] mod test { use super::*; use crate::PacketPile; use crate::parse::Parse; use crate::serialize::{Marshal, MarshalInto}; quickcheck! { fn roundtrip(p: SKESK) -> bool { let q = SKESK::from_bytes(&p.to_vec().unwrap()).unwrap(); assert_eq!(p, q); true } } #[test] fn sample_skesk5_packet() { // This sample packet is from RFC4880bis-05, section A.3. let password: Password = String::from("password").into(); let raw = [ // Packet header: 0xc3, 0x3e, // Version, algorithms, S2K fields: 0x05, 0x07, 0x01, 0x03, 0x08, 0xcd, 0x5a, 0x9f, 0x70, 0xfb, 0xe0, 0xbc, 0x65, 0x90, // AEAD IV: 0xbc, 0x66, 0x9e, 0x34, 0xe5, 0x00, 0xdc, 0xae, 0xdc, 0x5b, 0x32, 0xaa, 0x2d, 0xab, 0x02, 0x35, // AEAD encrypted CEK: 0x9d, 0xee, 0x19, 0xd0, 0x7c, 0x34, 0x46, 0xc4, 0x31, 0x2a, 0x34, 0xae, 0x19, 0x67, 0xa2, 0xfb, // Authentication tag: 0x7e, 0x92, 0x8e, 0xa5, 0xb4, 0xfa, 0x80, 0x12, 0xbd, 0x45, 0x6d, 0x17, 0x38, 0xc6, 0x3c, 0x36, ]; let packets: Vec<Packet> = PacketPile::from_bytes(&raw[..]).unwrap().into_children().collect(); assert_eq!(packets.len(), 1); if let Packet::SKESK(SKESK::V5(ref s)) = packets[0] { assert_eq!(&s.s2k().derive_key( &password, s.symmetric_algo().key_size().unwrap()).unwrap()[..], &[0xb2, 0x55, 0x69, 0xb9, 0x54, 0x32, 0x45, 0x66, 0x45, 0x27, 0xc4, 0x97, 0x6e, 0x7a, 0x5d, 0x6e][..]); assert_eq!(&s.decrypt(&password).unwrap().1[..], &[0x86, 0xf1, 0xef, 0xb8, 0x69, 0x52, 0x32, 0x9f, 0x24, 0xac, 0xd3, 0xbf, 0xd0, 0xe5, 0x34, 0x6d][..]); } else { panic!("bad packet"); } let mut serialized = Vec::new(); packets[0].serialize(&mut serialized).unwrap(); assert_eq!(&raw[..], &serialized[..]); } } ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-1.7.0/src/packet/tag.rs�������������������������������������������������������������0000644�0000000�0000000�00000017556�00726746425�0016172�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 4.3 of RFC 4880]. /// /// [Section 4.3 of RFC 4880]: https://tools.ietf.org/html/rfc4880#section-4.3 /// /// The values correspond to the serialized format. #[derive(Clone, Copy, Debug)] 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, /// AEAD Encrypted Data Packet. /// /// This feature is [experimental](crate#experimental-features). AED, /// 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, 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::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::Private(u) => f.write_fmt(format_args!("Private/Experimental Packet {}", u)), Tag::Unknown(u) => f.write_fmt(format_args!("Unknown Packet {}", u)), } } } #[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` can be at the start of a valid /// message. /// /// [Certs] can start with `PublicKey`, [TSKs] with a `SecretKey`. /// /// [Certs]: https://tools.ietf.org/html/rfc4880#section-11.1 /// [TSKs]: https://tools.ietf.org/html/rfc4880#section-11.2 /// /// [Messages] start with a `OnePassSig`, `Signature` (old style /// non-one pass signatures), `PKESK`, `SKESK`, `CompressedData`, /// or `Literal`. /// /// [Messages]: https://tools.ietf.org/html/rfc4880#section-11.3 /// /// Signatures can standalone either as a [detached signature], a /// third-party certification, or a revocation certificate. /// /// [detached signature]: https://tools.ietf.org/html/rfc4880#section-11.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 } } #[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); } } } ��������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-1.7.0/src/packet/trust.rs�����������������������������������������������������������0000644�0000000�0000000�00000003605�00726746425�0016566�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 4880] for details. /// /// [Section 5.10 of RFC 4880]: https://tools.ietf.org/html/rfc4880#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-1.7.0/src/packet/unknown.rs���������������������������������������������������������0000644�0000000�0000000�00000010641�00726746425�0017102�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::anyhow!("{}", self.error), 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) } /// 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_body_forwards!(Unknown); impl From<Unknown> for Packet { fn from(s: Unknown) -> Self { Packet::Unknown(s) } } �����������������������������������������������������������������������������������������������sequoia-openpgp-1.7.0/src/packet/user_attribute.rs��������������������������������������������������0000644�0000000�0000000�00000032270�00726746425�0020446�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������//! User Attribute packets and subpackets. //! //! See [Section 5.12 of RFC 4880] for details. //! //! [Section 5.12 of RFC 4880]: https://tools.ietf.org/html/rfc4880#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 4880] for details. /// /// [Section 5.12 of RFC 4880]: https://tools.ietf.org/html/rfc4880#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 4880] for details. /// /// [Section 5.12 of RFC 4880]: https://tools.ietf.org/html/rfc4880#section-5.12 #[derive(Clone, Debug, PartialEq, Eq, Hash)] pub enum Subpacket { /// Image subpacket. /// /// See [Section 5.12.1 of RFC 4880] for details. /// /// [Section 5.12.1 of RFC 4880]: https://tools.ietf.org/html/rfc4880#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 4880] for details. /// /// [Section 5.12.1 of RFC 4880]: https://tools.ietf.org/html/rfc4880#section-5.12.1 #[derive(Clone, Debug, PartialEq, Eq, Hash)] 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 an 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-1.7.0/src/packet/userid.rs����������������������������������������������������������0000644�0000000�0000000�00000145133�00726746425�0016703�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������use std::fmt; use std::str; use std::hash::{Hash, Hasher}; use std::cell::RefCell; use std::cmp::Ordering; use std::sync::Mutex; #[cfg(test)] use quickcheck::{Arbitrary, Gen}; use anyhow::Context; 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> { 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> { lazy_static::lazy_static!{ static ref USER_ID_PARSER: Regex = { // 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 4880] 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 4880]: https://tools.ietf.org/html/rfc4880#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 [RFC 4880], the text is by convention UTF-8 encoded /// and in "mail name-addr" form, i.e., "Name (Comment) /// <email@example.com>". /// /// [RFC 4880]: https://tools.ietf.org/html/rfc4880#section-5.11 /// /// Use `UserID::default()` to get a UserID with a default settings. value: Vec<u8>, hash_algo_security: HashAlgoSecurity, parsed: Mutex<RefCell<Option<ConventionallyParsedUserID>>>, } assert_send_and_sync!(UserID); impl From<Vec<u8>> for UserID { fn from(u: Vec<u8>) -> Self { UserID { common: Default::default(), hash_algo_security: UserID::determine_hash_algo_security(&u), value: u, parsed: Mutex::new(RefCell::new(None)), } } } 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 { let b = u.as_bytes(); let mut v = Vec::with_capacity(b.len()); v.extend_from_slice(b); v.into() } } impl From<String> for UserID { fn from(u: String) -> Self { let u = &u[..]; u.into() } } impl<'a> From<::std::borrow::Cow<'a, str>> for UserID { fn from(u: ::std::borrow::Cow<'a, str>) -> Self { let b = u.as_bytes(); let mut v = Vec::with_capacity(b.len()); v.extend_from_slice(b); v.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, value: self.value.clone(), parsed: Mutex::new(RefCell::new(None)), } } } 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_none() && 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()); } } } } let something = !value.is_empty(); if something { value.push_str(" <"); } value.push_str(address); if something { 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 } // 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".into(), /// None, "boat@example.org")?.value(), /// &b"John Smith <boat@example.org>"[..]); /// # Ok(()) } /// ``` pub fn from_address<O, S>(name: O, comment: O, email: S) -> Result<Self> where S: AsRef<str>, O: Into<Option<S>> { 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".into(), /// None, "ssh://host.example.org")?.value(), /// &b"NAS <ssh://host.example.org>"[..]); /// # Ok(()) } /// ``` pub fn from_unchecked_address<O, S>(name: O, comment: O, address: S) -> Result<Self> where S: AsRef<str>, O: Into<Option<S>> { 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.as_slice() } fn do_parse(&self) -> Result<()> { if self.parsed.lock().unwrap().borrow().is_none() { let s = str::from_utf8(&self.value)?; *self.parsed.lock().unwrap().borrow_mut() = Some(match ConventionallyParsedUserID::new(s) { Ok(puid) => puid, Err(err) => { // Return the error from the NameAddrOrOther parser. return Err(err).context(format!( "Failed to parse User ID: {:?}", s))?; } }); } Ok(()) } /// 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<String>> { self.do_parse()?; match *self.parsed.lock().unwrap().borrow() { Some(ref puid) => Ok(puid.name().map(|s| s.to_string())), None => unreachable!(), } } /// 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<String>> { self.do_parse()?; match *self.parsed.lock().unwrap().borrow() { Some(ref puid) => Ok(puid.comment().map(|s| s.to_string())), None => unreachable!(), } } /// 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<String>> { self.do_parse()?; match *self.parsed.lock().unwrap().borrow() { Some(ref puid) => Ok(puid.email().map(|s| s.to_string())), None => unreachable!(), } } /// 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<String>> { self.do_parse()?; match *self.parsed.lock().unwrap().borrow() { Some(ref puid) => Ok(puid.uri().map(|s| s.to_string())), None => unreachable!(), } } /// 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() { e @ Err(_) => e, Ok(None) => Ok(None), Ok(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::anyhow!( "punycode conversion failed: {:?}", e))?; // 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 { 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"); } } // 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".into(), 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-1.7.0/src/packet_pile.rs������������������������������������������������������������0000644�0000000�0000000�00000112261�00726746425�0016415�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������use std::convert::TryFrom; use std::fmt; use std::vec; use std::io; use std::path::Path; 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://tools.ietf.org/html/rfc4880#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::{Revoked, CouldBe}; /// /// # 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 with its /// // revocation signature /// let pp: PacketPile = PacketPile::from_bytes(&buffer)?; /// let cert = Cert::try_from(pp)?; /// if let 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 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 a `std::io::Read` /// object. /// /// 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_reader<R: 'a + io::Read + Send + Sync>(reader: R) -> Result<PacketPile> { let bio = buffered_reader::Generic::with_cookie( reader, None, Cookie::default()); PacketPile::from_buffered_reader(Box::new(bio)) } /// Deserializes the OpenPGP message stored in the file named by /// `path`. /// /// See `from_reader` for more details and caveats. fn from_file<P: AsRef<Path>>(path: P) -> Result<PacketPile> { PacketPile::from_buffered_reader( Box::new(buffered_reader::File::with_cookie(path, Cookie::default())?)) } /// Deserializes the OpenPGP message stored in the provided buffer. /// /// See `from_reader` for more details and caveats. fn from_bytes<D: AsRef<[u8]> + ?Sized>(data: &'a D) -> Result<PacketPile> { let bio = buffered_reader::Memory::with_cookie( data.as_ref(), Cookie::default()); PacketPile::from_buffered_reader(Box::new(bio)) } } 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. 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::Text); /// # 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::Text); /// # 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 sub-tree 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::Text); /// literal.set_body(b"old".to_vec()); /// let mut compressed = /// CompressedData::new(CompressionAlgorithm::Uncompressed); /// compressed.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::Text); /// 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(_) | Packet::AED(_) => (), // 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 of 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::Text); /// 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::Text); /// 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::Text); /// 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_buffered_reader<'a>(bio: Box<dyn BufferedReader<Cookie> + 'a>) -> Result<PacketPile> { PacketParserBuilder::from_buffered_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 not dropped, as it is when serializing a `Cert`. fn from(cert: Cert) -> PacketPile { PacketPile::from(cert.into_packets().collect::<Vec<Packet>>()) } } impl<'a> TryFrom<PacketParserResult<'a>> for PacketPile { type Error = anyhow::Error; /// Reads all of 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::Text; use crate::packet::Literal; use crate::packet::CompressedData; use crate::packet::seip::SEIP1; use crate::packet::Tag; use crate::parse::{Parse, PacketParser}; #[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.is_some() { 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.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; // 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(Text); lit.set_body(t.to_vec()); cd = cd.push(lit.into()) } let mut seip = SEIP1::new(); seip.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(Text); one.set_body(b"one".to_vec()); let mut two = Literal::new(Text); 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(Text); 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(Text); 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(Text); 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(Text); 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(Text); 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-1.7.0/src/parse/hashed_reader.rs����������������������������������������������������0000644�0000000�0000000�00000040716�00726746425�0020032�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::{ Result, types::HashAlgorithm, }; use crate::parse::{Cookie, HashesFor, Hashing, HashingMode}; const TRACE : bool = false; 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>>) -> Self { let mut cookie = Cookie::default(); for mode in &algos { cookie.sig_group_mut().hashes .push(mode.map(|algo| algo.context().unwrap())); // XXX: Don't unwrap. } cookie.hashes_for = hashes_for; HashedReader { reader, cookie, } } } /// Updates the given hash context normalizing line endings to "\r\n" /// on the fly. pub(crate) fn hash_update_text(h: &mut dyn crate::crypto::hash::Digest, text: &[u8]) { let mut line = text; while ! line.is_empty() { let mut next = 0; for (i, c) in line.iter().cloned().enumerate() { match c { 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; }, _ => (), } } if next > 0 { line = &line[next..]; } else { h.update(line); break; } } } 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 mode in self.sig_groups[ngroups-2].hashes.iter_mut() { t!("({:?}): group {} {:?} hashing {} stashed bytes.", hashes_for, ngroups-2, mode.map(|ctx| ctx.algo()), data.len()); match mode { HashingMode::Binary(h) => h.update(&stashed_data), HashingMode::Text(h) => hash_update_text(h, &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 mode in sig_group.hashes.iter_mut() { t!("{:?}: group {} {:?} hashing {} bytes.", hashes_for, i, mode.map(|ctx| ctx.algo()), data.len()); match mode { HashingMode::Binary(h) => h.update(data), HashingMode::Text(h) => hash_update_text(h, data), } } } } fn hash_update_csf(&mut self, mut 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. assert_eq!(ngroups, 1); tracer!(TRACE, "Cookie::hash_update_csf", level); t!("Cleartext Signature Framework message"); // If we stashed half of a \r\n newline away, see if we get // the second half now. If we do, and data is empty then, we // return without hashing it. This is important so that we // can avoid hashing the final newline, even if we happen to // read it in two invocations of this function. if self.hash_stash.as_ref().map(|buf| buf.as_slice() == &b"\r"[..]) .unwrap_or(false) && data.get(0).cloned() == Some(b'\n') { self.hash_stash.as_mut().expect("checked above").push(b'\n'); data = &data[1..]; } 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 stashed data first. if let Some(stashed_data) = self.hash_stash.take() { for mode in self.sig_groups[0].hashes.iter_mut() { t!("{:?}: {:?} hashing {} stashed bytes.", hashes_for, mode.map(|ctx| ctx.algo()), stashed_data.len()); match mode { HashingMode::Binary(_) => unreachable!("CSF transformation uses \ text signatures"), HashingMode::Text(h) => hash_update_text(h, &stashed_data[..]), } } } // We hash everything but the last newline. // There is exactly one group. assert_eq!(ngroups, 1); // Compute the length of data that should be hashed. // If it ends in a newline, we delay hashing it. let l = data.len() - if data.ends_with(b"\r\n") { 2 } else if data.ends_with(b"\n") || data.ends_with(b"\r") { 1 } else { 0 }; // Hash everything but the last newline now. for mode in self.sig_groups[0].hashes.iter_mut() { t!("{:?}: {:?} hashing {} bytes.", hashes_for, mode.map(|ctx| ctx.algo()), l); match mode { HashingMode::Binary(_) => unreachable!("CSF transformation uses text signatures"), HashingMode::Text(h) => hash_update_text(h, &data[..l]), } } // The newline we stash away. If more text is written // later, we will hash it then. Otherwise, it is // implicitly omitted when the filter is dropped. if ! data[l..].is_empty() { t!("Stashing newline: {:?}", &data[l..]); self.hash_stash = Some(data[l..].to_vec()); } } } 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.as_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<Box<dyn crate::crypto::hash::Digest>>>> where R: BufferedReader<crate::parse::Cookie>, { let mut reader = HashedReader::new(reader, HashesFor::Signature, algos.to_vec()); // Hash all of 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(HashingMode::Binary) .collect()); 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", ] { let mut ctx = HashAlgorithm::SHA256.context()?; super::hash_update_text(&mut ctx, text.as_bytes()); let mut digest = vec![0; ctx.digest_size()]; let _ = ctx.digest(&mut digest); assert_eq!( &crate::fmt::hex::encode(&digest), "5536758151607BB81CE8D6F49189B2E84763DA9EA84965AB7327E704DAE415EB"); } 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(HashingMode::Binary). 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-1.7.0/src/parse/map.rs��������������������������������������������������������������0000644�0000000�0000000�00000016205�00726746425�0016025�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. //! .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. /// .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>> { // Old-style CTB with indeterminate length emits no length // field. let has_length = map.header.len() > 1; if i == 0 { 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_length = if has_length { 1 } else { 0 }; map.entries.get(i - 1 - offset_length).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. /// .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"); /// # 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. /// .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); /// # 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. /// .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()); /// # 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-1.7.0/src/parse/mpis.rs�������������������������������������������������������������0000644�0000000�0000000�00000043417�00726746425�0016225�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::mpi::{self, MPI}; use crate::parse::{ PacketHeaderParser, Cookie, }; impl mpi::PublicKey { /// Parses a set of OpenPGP MPIs representing a public key. /// /// See [Section 3.2 of RFC 4880] for details. /// /// [Section 3.2 of RFC 4880]: https://tools.ietf.org/html/rfc4880#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); Self::_parse(algo, &mut php) } /// Parses a set of OpenPGP MPIs representing a public key. /// /// See [Section 3.2 of RFC 4880] for details. /// /// [Section 3.2 of RFC 4880]: https://tools.ietf.org/html/rfc4880#section-3.2 pub(crate) fn _parse<'a, T: 'a + BufferedReader<Cookie>>( algo: PublicKeyAlgorithm, php: &mut PacketHeaderParser<T>) -> 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 }) } 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. 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); Self::_parse(algo, &mut php, Some(checksum)) } /// Parses a set of OpenPGP MPIs representing a secret key. /// /// See [Section 3.2 of RFC 4880] for details. /// /// [Section 3.2 of RFC 4880]: https://tools.ietf.org/html/rfc4880#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); Self::_parse(algo, &mut php, None) } /// Parses a set of OpenPGP MPIs representing a secret key. /// /// See [Section 3.2 of RFC 4880] for details. /// /// [Section 3.2 of RFC 4880]: https://tools.ietf.org/html/rfc4880#section-3.2 pub(crate) fn _parse<'a, T: 'a + BufferedReader<Cookie>>( algo: PublicKeyAlgorithm, php: &mut PacketHeaderParser<T>, checksum: Option<mpi::SecretKeyChecksum>, ) -> Result<Self> { use crate::PublicKeyAlgorithm::*; #[allow(deprecated)] let mpis: Result<Self> = match algo { RSAEncryptSign | RSAEncrypt | RSASign => { let d = MPI::parse("rsa_secret_d_len", "rsa_secret_d", php)?; let p = MPI::parse("rsa_secret_p_len", "rsa_secret_p", php)?; let q = MPI::parse("rsa_secret_q_len", "rsa_secret_q", php)?; let u = MPI::parse("rsa_secret_u_len", "rsa_secret_u", php)?; Ok(mpi::SecretKeyMaterial::RSA { d: d.into(), p: p.into(), q: q.into(), u: u.into(), }) } DSA => { let x = MPI::parse("dsa_secret_len", "dsa_secret", php)?; Ok(mpi::SecretKeyMaterial::DSA { x: x.into(), }) } ElGamalEncrypt | ElGamalEncryptSign => { let x = MPI::parse("elgamal_secret_len", "elgamal_secret", php)?; Ok(mpi::SecretKeyMaterial::ElGamal { x: x.into(), }) } EdDSA => { Ok(mpi::SecretKeyMaterial::EdDSA { scalar: MPI::parse("eddsa_secret_len", "eddsa_secret", php)? .into() }) } ECDSA => { Ok(mpi::SecretKeyMaterial::ECDSA { scalar: MPI::parse("ecdsa_secret_len", "ecdsa_secret", php)? .into() }) } ECDH => { Ok(mpi::SecretKeyMaterial::ECDH { scalar: MPI::parse("ecdh_secret_len", "ecdh_secret", php)? .into() }) } Unknown(_) | Private(_) => { let mut mpis = Vec::new(); while let Ok(mpi) = MPI::parse("unknown_len", "unknown", php) { mpis.push(mpi.into()); } 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 { use crate::serialize::{Marshal, MarshalInto}; let good = match checksum { mpi::SecretKeyChecksum::SHA1 => { // Read expected SHA1 hash of the MPIs. let their_chksum = php.parse_bytes("checksum", 20)?; // Compute SHA1 hash. let mut hsh = HashAlgorithm::SHA1.context().unwrap(); mpis.serialize(&mut hsh)?; let mut our_chksum = [0u8; 20]; let _ = hsh.digest(&mut our_chksum); our_chksum == their_chksum[..] }, mpi::SecretKeyChecksum::Sum16 => { // Read expected sum of the MPIs. let their_chksum = php.parse_bytes("checksum", 2)?; // Compute sum. let our_chksum = mpis.to_vec()?.iter() .fold(0u16, |acc, v| acc.wrapping_add(*v as u16)) .to_be_bytes(); our_chksum == their_chksum[..] }, }; 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 4880] for details. /// /// [Section 3.2 of RFC 4880]: https://tools.ietf.org/html/rfc4880#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); 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 4880] for details. /// /// [Section 3.2 of RFC 4880]: https://tools.ietf.org/html/rfc4880#section-3.2 pub(crate) fn _parse<'a, T: 'a + BufferedReader<Cookie>>( algo: PublicKeyAlgorithm, php: &mut PacketHeaderParser<T>) -> 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() }) } 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 => 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 4880] for details. /// /// [Section 3.2 of RFC 4880]: https://tools.ietf.org/html/rfc4880#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); 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 4880] for details. /// /// [Section 3.2 of RFC 4880]: https://tools.ietf.org/html/rfc4880#section-3.2 pub(crate) fn _parse<'a, T: 'a + BufferedReader<Cookie>>( algo: PublicKeyAlgorithm, php: &mut PacketHeaderParser<T>) -> 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, }) } 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 => 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-1.7.0/src/parse/packet_parser_builder.rs��������������������������������������������0000644�0000000�0000000�00000043457�00726746425�0021612�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������use std::io; use std::path::Path; 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 4880]). This automatism can be disabled /// and fine-tuned using [`PacketParserBuilder::dearmor`]. /// /// [Section 6 of RFC 4880]: https://tools.ietf.org/html/rfc4880#section-6 /// [`PacketParserBuilder::dearmor`]: PacketParserBuilder::dearmor() #[derive(PartialEq)] 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> { /// Creates a `PacketParserBuilder` for an OpenPGP message stored /// in a `std::io::Read` object. fn from_reader<R: io::Read + 'a + Send + Sync>(reader: R) -> Result<Self> { PacketParserBuilder::from_buffered_reader( Box::new(buffered_reader::Generic::with_cookie( reader, None, Cookie::default()))) } /// Creates a `PacketParserBuilder` for an OpenPGP message stored /// in the file named `path`. fn from_file<P: AsRef<Path>>(path: P) -> Result<Self> { PacketParserBuilder::from_buffered_reader( Box::new(buffered_reader::File::with_cookie(path, Cookie::default())?)) } /// Creates a `PacketParserBuilder` for an OpenPGP message stored /// in the specified buffer. fn from_bytes<D: AsRef<[u8]> + ?Sized>(data: &'a D) -> Result<PacketParserBuilder<'a>> { PacketParserBuilder::from_buffered_reader( Box::new(buffered_reader::Memory::with_cookie( data.as_ref(), Cookie::default()))) } } 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_buffered_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 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_buffered_reader_csft(self.bio, Some(mode), Cookie::new(ARMOR_READER_LEVEL), self.csf_transformation) .as_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(), &[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-1.7.0/src/parse/packet_pile_parser.rs�����������������������������������������������0000644�0000000�0000000�00000044711�00726746425�0021107�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������use std::convert::TryFrom; use std::io; use std::ops::{Deref, DerefMut}; use std::path::Path; use crate::{ Result, Packet, PacketPile, }; use crate::parse::{ PacketParserBuilder, 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.is_some() { /// // 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.is_some() { /// // 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.as_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.as_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> Deref for PacketPileParser<'a> { type Target = PacketParserResult<'a>; fn deref(&self) -> &Self::Target { &self.ppr } } impl<'a> DerefMut for PacketPileParser<'a> { fn deref_mut(&mut self) -> &mut Self::Target { &mut self.ppr } } 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> { /// Creates a `PacketPileParser` to parse the OpenPGP message stored /// in the `io::Read` object. fn from_reader<R: io::Read + 'a + Send + Sync>(reader: R) -> Result<PacketPileParser<'a>> { let bio = Box::new(buffered_reader::Generic::with_cookie( reader, None, Cookie::default())); PacketPileParser::from_buffered_reader(bio) } /// Creates a `PacketPileParser` to parse the OpenPGP message stored /// in the file named by `path`. fn from_file<P: AsRef<Path>>(path: P) -> Result<PacketPileParser<'a>> { PacketPileParser::from_buffered_reader( Box::new(buffered_reader::File::with_cookie(path, Cookie::default())?)) } /// Creates a `PacketPileParser` to parse the OpenPGP message stored /// in the provided buffer. fn from_bytes<D: AsRef<[u8]> + ?Sized + Send + Sync>(data: &'a D) -> Result<PacketPileParser<'a>> { let bio = Box::new(buffered_reader::Memory::with_cookie( data.as_ref(), Cookie::default())); PacketPileParser::from_buffered_reader(bio) } } #[allow(clippy::should_implement_trait)] 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_buffered_reader(bio: Box<dyn BufferedReader<Cookie> + 'a>) -> Result<PacketPileParser<'a>> { Self::from_packet_parser(PacketParser::from_buffered_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); } /// 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.as_ref() { /// // 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.as_ref() { /// // 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.as_ref() { /// 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.is_some() { /// // 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.is_some() { 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.is_some() { 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.as_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-1.7.0/src/parse/partial_body.rs�����������������������������������������������������0000644�0000000�0000000�00000035535�00726746425�0017730�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.as_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-1.7.0/src/parse/stream.rs�����������������������������������������������������������0000644�0000000�0000000�00000407107�00726746425�0016550�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������//! Streaming decryption and verification. //! //! This module provides convenient filters for decryption and //! verification of OpenPGP messages (see [Section 11.3 of RFC 4880]). //! It is the preferred interface to process OpenPGP messages: //! //! [Section 11.3 of RFC 4880]: https://tools.ietf.org/html/rfc4880#section-11.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 11.3 of RFC //! 4880]), 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, SKESK, }, KeyHandle, Packet, Result, packet, packet::Signature, 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 of 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 anyway 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 #[derive(Debug)] pub enum VerificationError<'a> { /// Malformed signature (no signature creation subpacket, etc.) MalformedSignature { /// The signature. sig: &'a Signature, /// The reason why the signature is malformed. error: anyhow::Error, }, /// 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, }, } 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), 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> From<VerificationError<'a>> for Error { fn from(e: VerificationError<'a>) -> Self { use self::VerificationError::*; match e { MalformedSignature { .. } => Error::MalformedPacket(e.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 11.3 of RFC 4880]). /// 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 11.3 of RFC 4880]: https://tools.ietf.org/html/rfc4880#section-11.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"); } } } impl<'a> std::ops::Deref for MessageStructure<'a> { type Target = [MessageLayer<'a>]; fn deref(&self) -> &Self::Target { &self.0[..] } } 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 11.3 of RFC 4880]). This enum /// represents the layers. The [`MessageStructure`] is communicated /// to the [`VerificationHelper::check`]. Iterating over the /// [`MessageStructure`] yields the individual message layers. /// /// [Section 11.3 of RFC 4880]: https://tools.ietf.org/html/rfc4880#section-11.3 #[derive(Debug)] pub enum MessageLayer<'a> { /// Represents an 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 /// 4880]). /// /// [Section 5.6 of RFC 4880]: https://tools.ietf.org/html/rfc4880#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 4880]). If you expect encrypted /// messages, make sure that there is at least one encryption /// container present. /// /// [Section 5.13 of RFC 4880]: https://tools.ietf.org/html/rfc4880#section-5.13 Encryption { /// Symmetric algorithm used. sym_algo: SymmetricAlgorithm, /// AEAD algorithm used, if any. /// /// This feature is [experimental](super::super#experimental-features). 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 4880]). 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 4880]: https://tools.ietf.org/html/rfc4880#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) { 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>) { self.insert_missing_signature_group(); self.layers.push(IMessageLayer::Encryption { depth, expect_mdc, sym_algo, aead_algo, }); } /// Returns whether or not 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) { if self.sig_group_counter > 0 { 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) { 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: Signature) { for layer in self.layers.iter_mut().rev() { match layer { IMessageLayer::SignatureGroup { ref mut sigs, ref mut count, } if *count > 0 => { sigs.push(sig); *count -= 1; return; }, _ => (), } } panic!("signature unaccounted for"); } fn push_bare_signature(&mut self, sig: Signature) { 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 SEIP container? expect_mdc: bool, sym_algo: SymmetricAlgorithm, aead_algo: Option<AEADAlgorithm>, }, SignatureGroup { sigs: Vec<Signature>, count: usize, } } /// 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 an 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 of 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) } } impl<V: VerificationHelper> DecryptionHelper for NoDecryptionHelper<V> { fn decrypt<D>(&mut self, _: &[PKESK], _: &[SKESK], _: Option<SymmetricAlgorithm>, _: D) -> Result<Option<Fingerprint>> where D: FnMut(SymmetricAlgorithm, &SessionKey) -> bool { 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_reader<R>(reader: R) -> Result<VerifierBuilder<'a>> where R: io::Read + 'a + Send + Sync, { VerifierBuilder::new(buffered_reader::Generic::with_cookie( reader, None, Default::default())) } fn from_file<P>(path: P) -> Result<VerifierBuilder<'a>> where P: AsRef<Path>, { VerifierBuilder::new(buffered_reader::File::with_cookie( path, Default::default())?) } fn from_bytes<D>(data: &'a D) -> Result<VerifierBuilder<'a>> where D: AsRef<[u8]> + ?Sized, { VerifierBuilder::new(buffered_reader::Memory::with_cookie( data.as_ref(), Default::default())) } } 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_buffered_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_reader<R>(reader: R) -> Result<DetachedVerifierBuilder<'a>> where R: io::Read + 'a + Send + Sync, { DetachedVerifierBuilder::new(buffered_reader::Generic::with_cookie( reader, None, Default::default())) } fn from_file<P>(path: P) -> Result<DetachedVerifierBuilder<'a>> where P: AsRef<Path>, { DetachedVerifierBuilder::new(buffered_reader::File::with_cookie( path, Default::default())?) } fn from_bytes<D>(data: &'a D) -> Result<DetachedVerifierBuilder<'a>> where D: AsRef<[u8]> + ?Sized, { DetachedVerifierBuilder::new(buffered_reader::Memory::with_cookie( data.as_ref(), Default::default())) } } 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_buffered_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_reader<R: io::Read + Send + Sync>(&mut self, reader: R) -> Result<()> { self.verify(buffered_reader::Generic::with_cookie( reader, None, Default::default()).as_boxed()) } /// Verifies the given data. pub fn verify_file<P: AsRef<Path>>(&mut self, path: P) -> Result<()> { self.verify(buffered_reader::File::with_cookie( path, Default::default())?.as_boxed()) } /// Verifies the given data. pub fn verify_bytes<B: AsRef<[u8]>>(&mut self, buf: B) -> Result<()> { self.verify(buffered_reader::Memory::with_cookie( buf.as_ref(), Default::default()).as_boxed()) } /// Verifies the given data. fn verify<R>(&mut self, reader: R) -> Result<()> where R: BufferedReader<Cookie>, { self.decryptor.verify_detached(reader) } /// 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<D>(&mut self, _: &[PKESK], skesks: &[SKESK], /// _sym_algo: Option<SymmetricAlgorithm>, /// mut decrypt: D) -> Result<Option<openpgp::Fingerprint>> /// where D: FnMut(SymmetricAlgorithm, &SessionKey) -> bool /// { /// 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, /// 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_reader<R>(reader: R) -> Result<DecryptorBuilder<'a>> where R: io::Read + 'a + Send + Sync, { DecryptorBuilder::new(buffered_reader::Generic::with_cookie( reader, None, Default::default())) } fn from_file<P>(path: P) -> Result<DecryptorBuilder<'a>> where P: AsRef<Path>, { DecryptorBuilder::new(buffered_reader::File::with_cookie( path, Default::default())?) } fn from_bytes<D>(data: &'a D) -> Result<DecryptorBuilder<'a>> where D: AsRef<[u8]> + ?Sized, { DecryptorBuilder::new(buffered_reader::Memory::with_cookie( data.as_ref(), Default::default())) } } 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<D>(&mut self, _: &[PKESK], skesks: &[SKESK], /// # _sym_algo: Option<SymmetricAlgorithm>, /// # mut decrypt: D) -> Result<Option<Fingerprint>> /// # where D: FnMut(SymmetricAlgorithm, &SessionKey) -> bool /// # { /// # 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<D>(&mut self, _: &[PKESK], skesks: &[SKESK], /// # _sym_algo: Option<SymmetricAlgorithm>, /// # mut decrypt: D) -> Result<Option<Fingerprint>> /// # where D: FnMut(SymmetricAlgorithm, &SessionKey) -> bool /// # { /// # 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<D>(&mut self, _: &[PKESK], skesks: &[SKESK], /// # _sym_algo: Option<SymmetricAlgorithm>, /// # mut decrypt: D) -> Result<Option<Fingerprint>> /// # where D: FnMut(SymmetricAlgorithm, &SessionKey) -> bool /// # { /// # 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_buffered_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 [Section 5.2.3.29 of RFC /// 4880bis]) to prevent [*Surreptitious Forwarding*]. /// /// [Section 5.2.3.29 of RFC 4880bis]: https://tools.ietf.org/html/draft-ietf-openpgp-rfc4880bis-08#section-5.2.3.29 /// [*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::{Fingerprint, Cert, Result}; /// # use openpgp::KeyID; /// 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<Fingerprint>, SymmetricAlgorithm, SessionKey)> { /// # unimplemented!() /// # } /// # fn lookup_key(_: &KeyID) /// # -> Option<(Fingerprint, Key<SecretParts, UnspecifiedRole>)> { /// # unimplemented!() /// # } /// # fn all_keys() -> impl Iterator<Item = (Fingerprint, Key<SecretParts, UnspecifiedRole>)> { /// # Vec::new().into_iter() /// # } /// /// struct Helper { /* ... */ }; /// impl DecryptionHelper for Helper { /// fn decrypt<D>(&mut self, pkesks: &[PKESK], skesks: &[SKESK], /// sym_algo: Option<SymmetricAlgorithm>, /// mut decrypt: D) -> Result<Option<Fingerprint>> /// where D: FnMut(SymmetricAlgorithm, &SessionKey) -> bool /// { /// // Try to decrypt, from the most convenient method to the /// // least convenient one. /// /// // First, see if it is in the cache. /// if let Some((fp, algo, sk)) = lookup_cache(pkesks, skesks) { /// if decrypt(algo, &sk) { /// return Ok(fp); /// } /// } /// /// // Second, we try those keys that we can use without /// // prompting for a password. /// for pkesk in pkesks { /// if let Some((fp, 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(fp)); /// } /// } /// } /// } /// /// // 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_wildcard()) { /// for (fp, 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(fp)); /// } /// } /// } /// } /// /// // 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<D>(&mut self, pkesks: &[PKESK], skesks: &[SKESK], sym_algo: Option<SymmetricAlgorithm>, decrypt: D) -> Result<Option<Fingerprint>> where D: FnMut(SymmetricAlgorithm, &SessionKey) -> bool; } 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`. #[allow(clippy::redundant_pattern_matching)] fn from_buffered_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_buffered_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_buffered_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, }; let mut pkesks: Vec<packet::PKESK> = Vec::new(); let mut skesks: Vec<packet::SKESK> = Vec::new(); while let PacketParserResult::Some(mut pp) = ppr { 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() { t!("Malformed message: {}", err); return Err(err.context("Malformed OpenPGP message")); } let sym_algo_hint = if let Packet::AED(ref aed) = pp.packet { Some(aed.symmetric_algo()) } else { None }; match pp.packet { Packet::CompressedData(ref p) => v.structure.new_compression_layer(p.algo()), Packet::SEIP(_) | Packet::AED(_) if v.mode == Mode::Decrypt => { // 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 decryption_proxy = |algo, secret: &SessionKey| { let result = pp.decrypt(algo, secret); if let Ok(_) = result { sym_algo = Some(algo); true } else { false } }; v.identity = v.helper.decrypt(&pkesks[..], &skesks[..], sym_algo_hint, decryption_proxy)?; } if pp.encrypted() { return Err( Error::MissingSessionKey( "No session key decrypted".into()).into()); } let sym_algo = sym_algo.expect("if we got here, sym_algo is set"); v.policy.symmetric_algorithm(sym_algo)?; if let Packet::AED(ref p) = pp.packet { v.policy.aead_algorithm(p.aead())?; } v.structure.new_encryption_layer( pp.recursion_depth(), pp.packet.tag() == packet::Tag::SEIP, sym_algo, if let Packet::AED(ref 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); }, 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(sig); } _ => (), } 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: D) -> Result<()> where D: BufferedReader<Cookie> { 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().map(|s| { HashingMode::for_signature(s.hash_algo(), s.typ()) }).collect(); let hashes = crate::parse::hashed_reader::hash_buffered_reader(data, &algos)?; // Attach the digests. for sig in sigs.iter_mut() { let need_hash = HashingMode::for_signature(sig.hash_algo(), sig.typ()); // 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. #[allow(clippy::single_match)] 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(sig); }, _ => (), } 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 { // 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 { 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 { return Err(err.context( "Malformed OpenPGP message")); } self.push_sig(p)?; } self.verify_signatures() } else { t!("Didn't hit EOF."); self.oppr = Some(PacketParserResult::Some(pp)); Ok(()) } } else { panic!("No ppr."); } } /// Verifies the signatures. #[allow(clippy::blocks_in_if_conditions)] 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 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. // // If there are no issuers, this iterator will // not yield any keys, hence this verification // will fail with the default error, // `VerificationError::MissingKey`. for ka in self.certs.iter() .flat_map(|cert| { cert.keys().key_handles(issuers.iter()) }) { let cert = ka.cert(); let fingerprint = ka.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.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.fingerprint(), error); VerificationErrorInternal::BadKey { ka, error, } } else if let RevocationStatus::Revoked(rev) = ka.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.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.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(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.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.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)] mod test { use std::io::Read; use super::*; use std::convert::TryFrom; use crate::parse::Parse; use crate::policy::StandardPolicy as P; use crate::serialize::Serialize; use crate::{ crypto::Password, }; #[derive(PartialEq)] 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, } 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, } } } impl VHelper { 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, } } 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, } } } impl VerificationHelper for VHelper { 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(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<D>(&mut self, pkesks: &[PKESK], _skesks: &[SKESK], sym_algo: Option<SymmetricAlgorithm>, mut decrypt: D) -> Result<Option<Fingerprint>> where D: FnMut(SymmetricAlgorithm, &SessionKey) -> bool { let p = P::new(); if ! self.for_decryption { unreachable!("Shouldn't be called for verifications"); } for pkesk in pkesks { for key in &self.keys { for subkey in key.with_policy(&p, None)?.keys().secret() .key_handle(pkesk.recipient()) { 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) { return Ok(None); } } } } } Err(Error::MissingSessionKey("Decryption failed".into()).into()) } } #[test] fn verifier() -> Result<()> { let p = P::new(); let certs = [ "neal.pgp", "testy-new.pgp", "emmelie-dorothea-dina-samantha-awina-ed25519.pgp" ].iter() .map(|f| Cert::from_bytes(crate::tests::key(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 transformation process trims trailing whitespace, // and the manifesto has a trailing whitespace right at // the end. let mut manifesto = crate::tests::manifesto().to_vec(); let ws_at = manifesto.len() - 2; let ws = manifesto.remove(ws_at); assert_eq!(ws, b' '); manifesto }, false, None, VHelper::new(1, 0, 0, 0, certs.clone())), (crate::tests::message("a-problematic-poem.txt.cleartext.sig") .to_vec(), crate::tests::message("a-problematic-poem.txt").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())), ]; 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()); assert_eq!(v.helper_ref(), r); 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()); assert_eq!(v.helper_ref(), r); 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", ] { eprintln!("Test vector {:?}...", alg); let key = Cert::from_bytes(crate::tests::message( &format!("encrypted/{}.sec.pgp", alg)))?; if let Some(k) = key.with_policy(&p, None)?.keys().subkeys().supported().next() { use crate::crypto::mpi::PublicKey; match k.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.len(), 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<D>(&mut self, _: &[PKESK], _: &[SKESK], _: Option<SymmetricAlgorithm>, _: D) -> Result<Option<Fingerprint>> where D: FnMut(SymmetricAlgorithm, &SessionKey) -> bool { 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<()> { lazy_static::lazy_static! { static ref ZEROS: Vec<u8> = 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 test_streaming_verifier_bug_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 result = DetachedVerifierBuilder::from_bytes(sig)? .with_policy(&p, None, h); if let Err(e) = result { let error = e.downcast::<crate::Error>()?; assert!(matches!(error, Error::MalformedMessage(..))); } else { unreachable!("Should error out as the signature is broken."); } 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<D>(&mut self, _: &[PKESK], s: &[SKESK], _: Option<SymmetricAlgorithm>, mut decrypt: D) -> Result<Option<Fingerprint>> where D: FnMut(SymmetricAlgorithm, &SessionKey) -> bool { 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(()) } } ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-1.7.0/src/parse.rs������������������������������������������������������������������0000644�0000000�0000000�00000671653�00726746425�0015266�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 //! 4880]). Parsing is the process of interpreting the binary //! representation. //! //! [Section 3 ff. of RFC 4880]: https://tools.ietf.org/html/rfc4880#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 4880]). //! This automatism can be disabled and fine-tuned using //! [`PacketParserBuilder::dearmor`]. //! //! [Section 6 of RFC 4880]: https://tools.ietf.org/html/rfc4880#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 an //! 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 [`SEIP`] packet's //! [`MDC`] mechanism, but the integrity can only be checked after //! decrypting the whole container. Proper authenticated //! encryption is provided by the [`AED`] container, but as of this //! writing it is not standardized. //! //! [`DEFAULT_BUFFER_SIZE`]: stream::DEFAULT_BUFFER_SIZE //! [`PKESK`]: crate::packet::PKESK //! [`SKESK`]: crate::packet::PKESK //! [`Literal`]: crate::packet::Literal //! [`Signature`]: crate::packet::Signature //! [`SEIP`]: crate::packet::SEIP //! [`MDC`]: crate::packet::MDC //! [`AED`]: crate::packet::AED 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; 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::Signature4, 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}}; 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::{HashedReader, hash_update_text}; 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. pub trait Parse<'a, T> { /// Reads from the given reader. fn from_reader<R: 'a + Read + Send + Sync>(reader: R) -> Result<T>; /// Reads from the given file. /// /// The default implementation just uses [`from_reader(..)`], but /// implementations can provide their own specialized version. /// /// [`from_reader(..)`]: Parse::from_reader fn from_file<P: AsRef<Path>>(path: P) -> Result<T> { Self::from_reader(::std::fs::File::open(path)?) } /// Reads from the given slice. /// /// The default implementation just uses [`from_reader(..)`], but /// implementations can provide their own specialized version. /// /// [`from_reader(..)`]: Parse::from_reader fn from_bytes<D: AsRef<[u8]> + ?Sized + Send + Sync>(data: &'a D) -> Result<T> { Self::from_reader(io::Cursor::new(data)) } } macro_rules! impl_parse_generic_packet { ($typ: ident) => { impl<'a> Parse<'a, $typ> for $typ { fn from_reader<R: 'a + Read + Send + Sync>(reader: R) -> Result<Self> { let bio = buffered_reader::Generic::with_cookie( reader, None, Cookie::default()); let parser = PacketHeaderParser::new_naked(bio); let mut pp = Self::parse(parser)?; pp.buffer_unread_content()?; match pp.next()? { (Packet::$typ(o), PacketParserResult::EOF(_)) => Ok(o), (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()), } } } }; } /// The default amount of acceptable nesting. /// /// The default is `16`. /// /// Typically, we expect a message to looking 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<T: BufferedReader<Cookie>> { // 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<T, 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) => { 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<T: BufferedReader<Cookie>> std::fmt::Debug for PacketHeaderParser<T> { 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, T: 'a + BufferedReader<Cookie>> PacketHeaderParser<T> { // 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: T, 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: T) -> 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>> { let total_out = self.reader.total_out(); let mut reader = if self.state.settings.map { // Read the body for the map. Note that // `total_out` does not account for the body. // // XXX avoid the extra copy. let body = self.reader.steal_eof()?; if !body.is_empty() { self.field("body", body.len()); } // This is a buffered_reader::Dup, so this always has an // inner. let inner = Box::new(self.reader).into_inner().unwrap(); // Combine the header with the body for the map. let mut data = Vec::with_capacity(total_out + body.len()); // We know that the inner reader must have at least // `total_out` bytes buffered, otherwise we could never // have read that much from the `buffered_reader::Dup`. data.extend_from_slice(&inner.buffer()[..total_out]); data.extend(body); self.map.as_mut().unwrap().finalize(data); inner } else { // This is a buffered_reader::Dup, so this always has an // inner. 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, encrypted: false, 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_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_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 } } /// What the hash in the Cookie is for. #[derive(Copy, Clone, PartialEq, Debug)] #[allow(clippy::upper_case_acronyms)] pub(crate) enum HashesFor { Nothing, MDC, Signature, CleartextSignature, } /// Controls whether or not 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. pub(crate) hashes: Vec<HashingMode<Box<dyn crypto::hash::Digest>>>, } /// Controls line-ending normalization during hashing. /// /// OpenPGP normalizes line endings when signing or verifying text /// signatures. pub(crate) enum HashingMode<T> { /// Hash for a binary signature. /// /// The data is hashed as-is. Binary(T), /// Hash for a text signature. /// /// The data is hashed with line endings normalized to `\r\n`. Text(T), } impl<T: Clone> Clone for HashingMode<T> { fn clone(&self) -> Self { use self::HashingMode::*; match self { Binary(t) => Binary(t.clone()), Text(t) => Text(t.clone()), } } } 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(t) => write!(f, "Binary({:?})", t), Text(t) => write!(f, "Text({:?})", t), } } } impl<T: PartialEq> PartialEq for HashingMode<T> { fn eq(&self, other: &Self) -> bool { use self::HashingMode::*; match (self, other) { (Binary(s), Binary(o)) => s.eq(o), (Text(s), Text(o)) => s.eq(o), _ => false, } } } impl<T: Eq> Eq for HashingMode<T> { } impl<T> HashingMode<T> { fn map<U, F: Fn(&T) -> U>(&self, f: F) -> HashingMode<U> { use self::HashingMode::*; match self { Binary(t) => Binary(f(t)), Text(t) => Text(f(t)), } } pub(crate) fn as_ref(&self) -> &T { use self::HashingMode::*; match self { Binary(t) => t, Text(t) => t, } } pub(crate) fn as_mut(&mut self) -> &mut T { use self::HashingMode::*; match self { Binary(t) => t, Text(t) => t, } } fn for_signature(t: T, typ: SignatureType) -> Self { if typ == SignatureType::Text { HashingMode::Text(t) } else { HashingMode::Binary(t) } } } 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 or not to create a map. map: 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, } } } impl S2K { /// Reads an S2K from `php`. fn parse<T: BufferedReader<Cookie>>(php: &mut PacketHeaderParser<T>) -> Result<Self> { let s2k = php.parse_u8("s2k_type")?; #[allow(deprecated)] let ret = match s2k { 0 => S2K::Simple { hash: HashAlgorithm::from(php.parse_u8("s2k_hash_algo")?), }, 1 => S2K::Salted { hash: HashAlgorithm::from(php.parse_u8("s2k_hash_algo")?), salt: Self::read_salt(php)?, }, 3 => 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")?), }, 100..=110 => S2K::Private { tag: s2k, parameters: None, }, u => S2K::Unknown { tag: u, parameters: None, }, }; Ok(ret) } fn read_salt<'a, T: 'a + BufferedReader<Cookie>>(php: &mut PacketHeaderParser<T>) -> Result<[u8; 8]> { let mut b = [0u8; 8]; b.copy_from_slice(&php.parse_bytes("s2k_salt", 8)?); Ok(b) } } impl<'a> Parse<'a, S2K> for S2K { /// Reads an S2K from `reader`. fn from_reader<R: 'a + Read + Send + Sync>(reader: R) -> Result<Self> { let bio = buffered_reader::Generic::with_cookie( reader, None, Cookie::default()); let mut parser = PacketHeaderParser::new_naked(bio); Self::parse(&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<'a> Parse<'a, Header> for Header { /// Parses an OpenPGP packet's header as described in [Section 4.2 /// of RFC 4880]. /// /// [Section 4.2 of RFC 4880]: https://tools.ietf.org/html/rfc4880#section-4.2 fn from_reader<R: 'a + Read + Send + Sync>(reader: R) -> Result<Self> { let mut reader = buffered_reader::Generic::with_cookie( reader, None, Cookie::default()); Header::parse(&mut reader) } } impl BodyLength { /// Decodes a new format body length as described in [Section /// 4.2.2 of RFC 4880]. /// /// [Section 4.2.2 of RFC 4880]: https://tools.ietf.org/html/rfc4880#section-4.2.2 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.1 of RFC 4880]. /// /// [Section 4.2.1 of RFC 4880]: https://tools.ietf.org/html/rfc4880#section-4.2.1 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<'a, T: 'a + BufferedReader<Cookie>>(php: PacketHeaderParser<T>, error: anyhow::Error) -> Result<PacketParser<'a>> { let tag = php.header.ctb().tag(); php.ok(Packet::Unknown(Unknown::new(tag, error))) .map(|pp| pp.set_encrypted(true)) } } // 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<'a, T: 'a + BufferedReader<Cookie>>(mut php: PacketHeaderParser<T>) -> Result<PacketParser<'a>> { let indent = php.recursion_depth(); tracer!(TRACE, "Signature::parse", indent); make_php_try!(php); let version = php_try!(php.parse_u8("version")); match version { 4 => Signature4::parse(php), _ => { t!("Ignoring version {} packet.", version); php.fail("unknown version") }, } } /// Returns whether the data appears to be a signature (no promises). fn plausible<T: BufferedReader<Cookie>>( bio: &mut buffered_reader::Dup<T, Cookie>, header: &Header) -> Result<()> { Signature4::plausible(bio, header) } } impl Signature4 { // Parses a signature packet. fn parse<'a, T: 'a + BufferedReader<Cookie>>(mut php: PacketHeaderParser<T>) -> Result<PacketParser<'a>> { 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 need_hash = HashingMode::for_signature(hash_algo, typ); let mut pp = php.ok(Packet::Signature(Signature4::new( typ, pk_algo, hash_algo, hashed_area, unhashed_area, [digest_prefix1, digest_prefix2], mpis).into()))?; // 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 it's 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 { 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 { 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) } /// Returns whether the data appears to be a signature (no promises). fn plausible<T: BufferedReader<Cookie>>( bio: &mut buffered_reader::Dup<T, Cookie>, header: &Header) -> Result<()> { // 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_parse_generic_packet!(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<'a, T>(php: &mut PacketHeaderParser<T>, mut limit: usize, hash_algo: HashAlgorithm) -> Result<Self> where T: 'a + BufferedReader<Cookie>, { 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<'a, T>(php: &mut PacketHeaderParser<T>, limit: usize, hash_algo: HashAlgorithm) -> Result<Self> where T: 'a + BufferedReader<Cookie>, { 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(); 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 v5 // 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( &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), 5 => 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( match version { 4 => Fingerprint::from_bytes(&bytes), // XXX: Fix once we dig V5. 5 => Fingerprint::Invalid(bytes.into()), _ => Fingerprint::Invalid(bytes.into()), }) }, SubpacketTag::PreferredAEADAlgorithms => SubpacketValue::PreferredAEADAlgorithms( php.parse_bytes("pref aead algos", len)? .iter().map(|o| (*o).into()).collect()), 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), 5 => 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( match version { 4 => Fingerprint::from_bytes(&bytes), // XXX: Fix once we dig V5. 5 => Fingerprint::Invalid(bytes.into()), _ => Fingerprint::Invalid(bytes.into()), }) }, SubpacketTag::AttestedCertifications => { // 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.digest_size()) .unwrap_or(len); if digest_size == 0 { // Empty body with unknown hash algorithm. SubpacketValue::AttestedCertifications( 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::AttestedCertifications( bytes.chunks(digest_size).map(Into::into).collect()) } }, SubpacketTag::Reserved(_) | SubpacketTag::PlaceholderForBackwardCompatibility | 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<'a, T: 'a + BufferedReader<Cookie>>(php: PacketHeaderParser<T>) -> Result<PacketParser<'a>> { OnePassSig3::parse(php) } } impl_parse_generic_packet!(OnePassSig); impl OnePassSig3 { #[allow(clippy::blocks_in_if_conditions)] fn parse<'a, T: 'a + BufferedReader<Cookie>>(mut php: PacketHeaderParser<T>) -> Result<PacketParser<'a>> { let indent = php.recursion_depth(); tracer!(TRACE, "OnePassSig", indent); make_php_try!(php); let version = php_try!(php.parse_u8("version")); if version != 3 { t!("Ignoring version {} packet", version); // Unknown version. Return an unknown packet. return php.fail("unknown version"); } 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_signature(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 ! cookie.sig_group().hashes.iter() .any(|mode| { mode.map(|ctx| ctx.algo()) == need_hash }) { if let Ok(ctx) = hash_algo.context() { cookie.sig_group_mut().hashes.push( HashingMode::for_signature(Box::new(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. let mut algos = Vec::new(); if hash_algo.is_supported() { algos.push(HashingMode::for_signature(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) } } #[test] fn one_pass_sig_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<'a> Parse<'a, OnePassSig3> for OnePassSig3 { fn from_reader<R: 'a + Read + Send + Sync>(reader: R) -> Result<Self> { OnePassSig::from_reader(reader).map(|p| match p { OnePassSig::V3(p) => p, // XXX: Once we have a second variant. // // p => Err(Error::InvalidOperation( // format!("Not a OnePassSig::V3 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<'a, T: 'a + BufferedReader<Cookie>>(mut php: PacketHeaderParser<T>) -> Result<PacketParser<'a>> { 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), _ => php.fail("unknown version"), } } /// Returns whether the data appears to be a key (no promises). fn plausible<T: BufferedReader<Cookie>>( bio: &mut buffered_reader::Dup<T, Cookie>, header: &Header) -> Result<()> { Key4::plausible(bio, header) } } // 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<'a, T: 'a + BufferedReader<Cookie>>(mut php: PacketHeaderParser<T>) -> Result<PacketParser<'a>> { 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() } // Encrypted & MD5 for key derivation: unsupported 1..=253 => { return php.fail("unsupported secret key encryption"); } // Encrypted, S2K & SHA-1 checksum 254 | 255 => { let sk: SymmetricAlgorithm = php_try!(php.parse_u8("sym_algo")).into(); let s2k = php_try!(S2K::parse(&mut php)); 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, if s2k_usage == 254 { Some(mpi::SecretKeyChecksum::SHA1) } else { Some(mpi::SecretKeyChecksum::Sum16) }, if s2k_supported { Ok(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::new(Timestamp::from(creation_time), pk_algo, mpis) } fn s<R>(creation_time: u32, pk_algo: PublicKeyAlgorithm, mpis: PublicKey, secret: SecretKeyMaterial) -> Result<Key4<key::SecretParts, R>> where R: key::KeyRole { Key4::with_secret(Timestamp::from(creation_time), pk_algo, mpis, 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 key (no promises). fn plausible<T: BufferedReader<Cookie>>( bio: &mut buffered_reader::Dup<T, Cookie>, header: &Header) -> Result<()> { // 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]; 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()) } } } impl<'a> Parse<'a, key::UnspecifiedKey> for key::UnspecifiedKey { fn from_reader<R: 'a + Read + Send + Sync>(reader: R) -> Result<Self> { let bio = buffered_reader::Generic::with_cookie( reader, None, Cookie::default()); let parser = PacketHeaderParser::new_naked(bio); 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()), (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<'a, T: 'a + BufferedReader<Cookie>>(mut php: PacketHeaderParser<T>) -> Result<PacketParser<'a>> { make_php_try!(php); let value = php_try!(php.parse_bytes_eof("value")); php.ok(Packet::Trust(Trust::from(value))) } } impl_parse_generic_packet!(Trust); impl UserID { /// Parses the body of a user id packet. fn parse<'a, T: 'a + BufferedReader<Cookie>>(mut php: PacketHeaderParser<T>) -> Result<PacketParser<'a>> { make_php_try!(php); let value = php_try!(php.parse_bytes_eof("value")); php.ok(Packet::UserID(UserID::from(value))) } } impl_parse_generic_packet!(UserID); impl UserAttribute { /// Parses the body of a user attribute packet. fn parse<'a, T: 'a + BufferedReader<Cookie>>(mut php: PacketHeaderParser<T>) -> Result<PacketParser<'a>> { make_php_try!(php); let value = php_try!(php.parse_bytes_eof("value")); php.ok(Packet::UserAttribute(UserAttribute::from(value))) } } impl_parse_generic_packet!(UserAttribute); impl Marker { /// Parses the body of a marker packet. fn parse<'a, T: 'a + BufferedReader<Cookie>>(mut php: PacketHeaderParser<T>) -> Result<PacketParser<'a>> { 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<T>(bio: &mut buffered_reader::Dup<T, Cookie>, header: &Header) -> Result<()> where T: BufferedReader<Cookie>, { 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_generic_packet!(Marker); impl Literal { /// Parses the body of a literal packet. /// /// Condition: Hashing has been disabled by the callee. fn parse<'a, T: 'a + BufferedReader<Cookie>>(mut php: PacketHeaderParser<T>) -> Result<PacketParser<'a>> { 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_generic_packet!(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 { 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<'a, T: 'a + BufferedReader<Cookie>>(mut php: PacketHeaderParser<T>) -> Result<PacketParser<'a>> { 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(); #[allow(unreachable_patterns)] match algo { CompressionAlgorithm::Uncompressed => (), #[cfg(feature = "compression-deflate")] CompressionAlgorithm::Zip | CompressionAlgorithm::Zlib => (), #[cfg(feature = "compression-bzip2")] CompressionAlgorithm::BZip2 => (), CompressionAlgorithm::Unknown(_) | CompressionAlgorithm::Private(_) => return php.fail("unknown compression algorithm"), _ => return php.fail("unsupported compression algorithm"), } let recursion_depth = php.recursion_depth(); let mut pp = php.ok(Packet::CompressedData(CompressedData::new(algo)))?; 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_generic_packet!(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<'a, T: 'a + BufferedReader<Cookie>>(mut php: PacketHeaderParser<T>) -> Result<PacketParser<'a>> { make_php_try!(php); let version = php_try!(php.parse_u8("version")); let skesk = match version { 4 => { let sym_algo = php_try!(php.parse_u8("sym_algo")); let s2k = php_try!(S2K::parse(&mut php)); let s2k_supported = s2k.is_supported(); let esk = php_try!(php.parse_bytes_eof("esk")); SKESK::V4(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()) }, ))) }, 5 => { 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(&mut php)); let s2k_supported = s2k.is_supported(); let iv_size = php_try!(aead_algo.iv_size()); let digest_size = php_try!(aead_algo.digest_size()); // The rest of the packet is (potentially) the S2K // parameters, the AEAD IV, the ESK, and the AEAD // digest. We don't know the size of the S2K // parameters if the S2K method is not supported, and // we don't know the size of the ESK. let mut esk = php_try!(php.reader.steal_eof() .map_err(anyhow::Error::from)); let aead_iv = if s2k_supported && esk.len() >= iv_size { // We know the S2K method, so the parameters have // been parsed into the S2K object. So, `esk` // starts with iv_size bytes of IV. let mut iv = esk; esk = iv.split_off(iv_size); iv } else { Vec::with_capacity(0) // A dummy value. }; let l = esk.len(); let aead_digest = esk.split_off(l.saturating_sub(digest_size)); // Now fix the map. if s2k_supported { php.field("aead_iv", iv_size); } php.field("esk", esk.len()); php.field("aead_digest", aead_digest.len()); SKESK::V5(php_try!(SKESK5::new_raw( sym_algo, aead_algo, s2k, if s2k_supported { Ok((aead_iv.into(), esk.into())) } else { Err(esk.into()) }, aead_digest.into_boxed_slice(), ))) }, _ => { // We only support version 4 and 5 SKESK packets. return php.fail("unknown version"); } }; php.ok(Packet::SKESK(skesk)) } } impl_parse_generic_packet!(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<'a, T: 'a + BufferedReader<Cookie>>(mut php: PacketHeaderParser<T>) -> Result<PacketParser<'a>> { make_php_try!(php); let version = php_try!(php.parse_u8("version")); if version != 1 { return php.fail("unknown version"); } php.ok(SEIP1::new().into()) .map(|pp| pp.set_encrypted(true)) } } impl_parse_generic_packet!(SEIP); impl MDC { /// Parses the body of an MDC packet. fn parse<'a, T: 'a + BufferedReader<Cookie>>(mut php: PacketHeaderParser<T>) -> Result<PacketParser<'a>> { make_php_try!(php); // Find the HashedReader pushed by the containing SEIP packet. // In a well-formed message, this will be the outer most // 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 mode.map(|ctx| ctx.algo()) == HashingMode::Binary(HashAlgorithm::SHA1) { Some(mode.as_mut()) } else { None }).unwrap(); let _ = h.digest(&mut computed_digest); } // If the outer most 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))); php.ok(Packet::MDC(MDC::new(digest, computed_digest))) } } impl_parse_generic_packet!(MDC); impl AED { /// Parses the body of a AED packet. fn parse<'a, T: 'a + BufferedReader<Cookie>>(mut php: PacketHeaderParser<T>) -> Result<PacketParser<'a>> { make_php_try!(php); let version = php_try!(php.parse_u8("version")); match version { 1 => AED1::parse(php), _ => php.fail("unknown version"), } } } impl_parse_generic_packet!(AED); impl AED1 { /// Parses the body of a AED packet. fn parse<'a, T: 'a + BufferedReader<Cookie>>(mut php: PacketHeaderParser<T>) -> Result<PacketParser<'a>> { 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")); // DRAFT 4880bis-08, section 5.16: "An implementation MUST // support chunk size octets with values from 0 to 56. Chunk // size octets with other values are reserved for future // extensions." if chunk_size > 56 { return php.fail("unsupported chunk size"); } let chunk_size: u64 = 1 << (chunk_size + 6); let iv_size = php_try!(aead.iv_size()); let iv = php_try!(php.parse_bytes("iv", iv_size)); let aed = php_try!(Self::new( cipher, aead, chunk_size, iv.into_boxed_slice() )); php.ok(aed.into()).map(|pp| pp.set_encrypted(true)) } } impl MPI { /// Parses an OpenPGP MPI. /// /// See [Section 3.2 of RFC 4880] for details. /// /// [Section 3.2 of RFC 4880]: https://tools.ietf.org/html/rfc4880#section-3.2 fn parse<'a, T: 'a + BufferedReader<Cookie>>( name_len: &'static str, name: &'static str, php: &mut PacketHeaderParser<T>) -> Result<Self> { // 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![].into()); } 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 { return Err(Error::MalformedMPI( format!("{} unused bits not zeroed: ({:x})", unused_bits, unused_value)).into()); } } let first_used_bit = 8 - unused_bits; if value[0] & (1 << (first_used_bit - 1)) == 0 { return Err(Error::MalformedMPI( format!("leading bit is not set: \ expected bit {} to be set in {:8b} ({:x})", first_used_bit, value[0], value[0])).into()); } // Now consume the data. php.parse_be_u16(name_len).expect("worked before"); php.parse_bytes(name, bytes).expect("worked before"); Ok(value.into()) } } impl<'a> Parse<'a, MPI> for MPI { // Reads an MPI from `reader`. fn from_reader<R: io::Read + Send + Sync>(reader: R) -> Result<Self> { let bio = buffered_reader::Generic::with_cookie( reader, None, Cookie::default()); let mut parser = PacketHeaderParser::new_naked(bio); Self::parse("(none_len)", "(none)", &mut parser) } } impl PKESK { /// Parses the body of an PK-ESK packet. fn parse<'a, T: 'a + BufferedReader<Cookie>>(mut php: PacketHeaderParser<T>) -> Result<PacketParser<'a>> { make_php_try!(php); let version = php_try!(php.parse_u8("version")); match version { 3 => PKESK3::parse(php), _ => php.fail("unknown version"), } } } impl_parse_generic_packet!(PKESK); impl PKESK3 { /// Parses the body of an PK-ESK packet. fn parse<'a, T: 'a + BufferedReader<Cookie>>(mut php: PacketHeaderParser<T>) -> Result<PacketParser<'a>> { make_php_try!(php); let mut keyid = [0u8; 8]; keyid.copy_from_slice(&php_try!(php.parse_bytes("keyid", 8))); 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::from_bytes(&keyid), pk_algo, mpis)); php.ok(pkesk.into()) } } impl<'a> Parse<'a, PKESK3> for PKESK3 { fn from_reader<R: 'a + Read + Send + Sync>(reader: R) -> Result<Self> { PKESK::from_reader(reader).map(|p| match p { PKESK::V3(p) => p, // XXX: Once we have a second variant. // // p => Err(Error::InvalidOperation( // format!("Not a PKESKv3 packet: {:?}", p)).into()), }) } } impl<'a> Parse<'a, Packet> for Packet { fn from_reader<R: 'a + Read + Send + Sync>(reader: R) -> Result<Self> { let ppr = PacketParserBuilder::from_reader(reader) ?.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, } impl PacketParserState { fn new(settings: PacketParserSettings) -> Self { PacketParserState { settings, message_validator: Default::default(), keyring_validator: Default::default(), cert_validator: Default::default(), first_packet: true, } } } /// 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 is encrypted. encrypted: 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("encrypted", &self.encrypted) .field("content_was_read", &self.content_was_read) .field("settings", &self.state.settings) .field("map", &self.map) .finish() } } /// The return value of PacketParser::parse. #[allow(clippy::upper_case_acronyms)] 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 or not 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()) .as_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 message stored in a `std::io::Read` object. /// /// This function returns a `PacketParser` for the first packet in /// the stream. fn from_reader<R: io::Read + 'a + Send + Sync>(reader: R) -> Result<PacketParserResult<'a>> { PacketParserBuilder::from_reader(reader)?.build() } /// Starts parsing an OpenPGP message stored in a file named `path`. /// /// This function returns a `PacketParser` for the first packet in /// the stream. fn from_file<P: AsRef<Path>>(path: P) -> Result<PacketParserResult<'a>> { PacketParserBuilder::from_file(path)?.build() } /// Starts parsing an OpenPGP message stored in a buffer. /// /// This function returns a `PacketParser` for the first packet in /// the stream. fn from_bytes<D: AsRef<[u8]> + ?Sized + Send + Sync>(data: &'a D) -> Result<PacketParserResult<'a>> { PacketParserBuilder::from_bytes(data)?.build() } } 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_buffered_reader(bio: Box<dyn BufferedReader<Cookie> + 'a>) -> Result<PacketParserResult<'a>> { PacketParserBuilder::from_buffered_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 encrypted or not. fn set_encrypted(mut self, v: bool) -> Self { self.encrypted = v; self } /// Returns whether the packet's contents are encrypted. /// /// 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.encrypted()); /// pp.decrypt(SymmetricAlgorithm::AES256, /// &hex::decode("7EF4F08C44F780BEA866961423306166\ /// B8912C43352F3D9617F745E4E3939710")? /// .into())?; /// assert!(! pp.encrypted()); /// } /// /// // Start parsing the next packet, recursing. /// ppr = pp.recurse()?.1; /// } /// # Ok(()) } /// ``` pub fn encrypted(&self) -> bool { self.encrypted } /// 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), } } /// Returns Ok if the data appears to be a legal 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. fn plausible<T: BufferedReader<Cookie>>( bio: &mut buffered_reader::Dup<T, Cookie>, header: &Header) -> Result<()> { 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::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>, state: PacketParserState, path: Vec<usize>) -> Result<ParserResult<'a>> { assert!(!path.is_empty()); let indent = path.len() as isize - 1; tracer!(TRACE, "PacketParser::parse", indent); 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 mut header; // Read the header. let mut skip = 0; let mut orig_error : Option<anyhow::Error> = None; loop { bio.rewind(); bio.data_consume_hard(skip)?; match Header::parse(&mut bio) { Ok(header_) => { if skip == 0 { header = header_; break; } match Self::plausible(&mut bio, &header_) { Ok(()) => { header = header_; break; } Err(_err) => (), } } Err(err) => { if orig_error.is_none() { orig_error = Some(err); } if state.first_packet || skip > RECOVERY_THRESHOLD { // Limit the search space. This should be // enough to find a reasonable recovery point // in a Cert. return Err(orig_error.unwrap()); } } } 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); // Fabricate a header. header = Header::new(CTB::new(Tag::Reserved), BodyLength::Full(skip as u32)); 0 }; let tag = header.ctb().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::AED => AED::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 {:?} from: {:?}", self.path, reader); 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(), &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(_) | Packet::AED(_) if ! self.encrypted => { 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(), &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()); }, } } }, // decrypted should always be true. Packet::CompressedData(_) => unreachable!(), // Packets that don't recurse. 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::AED(_) => { // 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()), } } use std::ops::DerefMut; 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.deref_mut(), true), Packet::SEIP(p) => set_or_extend(rest, p.deref_mut(), ! self.encrypted), Packet::AED(p) => set_or_extend(rest, p.deref_mut(), ! self.encrypted), 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() } } /// 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 of 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 clears the packet parser's /// `encrypted` flag (see [`PacketParser::encrypted`]). /// /// [`PacketParser::encrypted`]: PacketParser::encrypted() /// /// 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. /// See [Section 14 of RFC 4880]. Do not relay these errors in /// situations where an attacker can request decryption of /// messages in an automated fashion. /// /// [Section 14 of RFC 4880]: https://tools.ietf.org/html/rfc4880#section-14 pub fn decrypt(&mut self, algo: 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.encrypted { return Err(Error::InvalidOperation( "Packet not encrypted.".to_string()).into()); } if algo.key_size()? != key.len () { return Err(Error::InvalidOperation( format!("Bad key size: {} expected: {}", key.len(), algo.key_size()?)).into()); } match self.packet.clone() { Packet::SEIP(_) => { // 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( format!( "Last two 16-bit quantities don't match: {}", crate::fmt::to_hex(&header[..], false))) .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(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.encrypted = false; Ok(()) }, Packet::AED(AED::V1(aed)) => { let chunk_size = aead::chunk_size_usize(aed.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( aed.chunk_digest_size()? + aed.aead().digest_size()? as u64)?; let data = self.data(amount)?; let dec = aead::Decryptor::new( 1, aed.symmetric_algo(), aed.aead(), chunk_size, aed.iv(), key, &data[..cmp::min(data.len(), amount)])?; let mut chunk = Vec::new(); dec.take(aed.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 reader = self.take_reader(); let mut reader = aead::BufferedReaderDecryptor::with_cookie( 1, aed.symmetric_algo(), aed.aead(), chunk_size, aed.iv(), 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.encrypted = false; Ok(()) }, _ => Err(Error::InvalidOperation( format!("Can't decrypt {:?} packets.", self.packet.tag())).into()) } } } #[cfg(test)] mod test { use super::*; 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, 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, 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, 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, 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 ]), ], }, DecryptTest { filename: "encrypted-twofish-password-red-fish-blue-fish.gpg", algo: SymmetricAlgorithm::Twofish, 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, 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, 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, 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, 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 ]), ], }, // AEAD encrypted messages. DecryptTest { filename: "aed/msg-aes128-eax-chunk-size-64-password-123.pgp", algo: SymmetricAlgorithm::AES128, key_hex: "E88151F2B6F6F6F0AE6B56ED247AA61B", plaintext: Data::File("a-cypherpunks-manifesto.txt"), paths: &[ (Tag::SKESK, &[ 0 ]), (Tag::AED, &[ 1 ]), (Tag::Literal, &[ 1, 0 ]), ], }, DecryptTest { filename: "aed/msg-aes128-eax-chunk-size-4194304-password-123.pgp", algo: SymmetricAlgorithm::AES128, key_hex: "918E6BF5C6CE4320D014735AF27BFA76", plaintext: Data::File("a-cypherpunks-manifesto.txt"), paths: &[ (Tag::SKESK, &[ 0 ]), (Tag::AED, &[ 1 ]), (Tag::Literal, &[ 1, 0 ]), ], }, ]; // 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); } fn decrypt_test_common(stream: bool) { for test in DECRYPT_TESTS.iter() { if !test.algo.is_supported() { eprintln!("Algorithm {} unsupported, skipping", test.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(test.algo, &key).unwrap(); } else { panic!("Expected a SEIP/AED 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 test in DECRYPT_TESTS.iter() { if !test.algo.is_supported() { eprintln!("Algorithm {} unsupported, skipping", test.algo); continue; } let mut ppr = PacketParserBuilder::from_bytes( crate::tests::message(test.filename)).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 { assert!(pp.possible_message().is_ok()); match pp.packet { Packet::SEIP(_) | Packet::AED(_) => { let key = crate::fmt::from_hex(test.key_hex, false) .unwrap().into(); pp.decrypt(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 { assert!(eof.is_message().is_ok()); } else { unreachable!(); } } } #[test] fn keyring_validator() { use std::io::Cursor; for test in &["testy.pgp", "lutz.gpg", "testy-new.pgp", "neal.pgp"] { let mut ppr = PacketParserBuilder::from_reader( Cursor::new(crate::tests::key("testy.pgp")).chain( Cursor::new(crate::tests::key(test)))).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 test in &["testy.pgp", "lutz.gpg", "testy-new.pgp", "neal.pgp"] { let mut ppr = PacketParserBuilder::from_bytes(crate::tests::key(test)) .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; } 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(_) | Packet::AED(_) => { 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 u) => { unknown += 1; assert_match!(Some(&Error::MalformedPacket(_)) = u.error().downcast_ref()); }, _ => (), } 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 a 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 or /// not 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(()) } } �������������������������������������������������������������������������������������sequoia-openpgp-1.7.0/src/policy/cutofflist.rs������������������������������������������������������0000644�0000000�0000000�00000016767�00726746425�0017634�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������use std::fmt; use std::ops::{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 2020, includes rustc version 1.34.2. This // requires a bit of creativity. #[derive(Debug, Clone)] pub(super) enum VecOrSlice<'a, T> { Vec(Vec<T>), Slice(&'a [T]), Empty(), } // Make a `VecOrSlice` act like a `Vec`. impl<'a, T> VecOrSlice<'a, T> { // Returns an empty `VecOrSlice`. const fn empty() -> Self { VecOrSlice::Empty() } // Like `Vec::get`. fn get(&self, i: usize) -> Option<&T> { match self { VecOrSlice::Vec(v) => v.get(i), VecOrSlice::Slice(s) => s.get(i), VecOrSlice::Empty() => None, } } // Like `Vec::len`. fn len(&self) -> usize { match self { VecOrSlice::Vec(v) => v.len(), VecOrSlice::Slice(s) => s.len(), VecOrSlice::Empty() => 0, } } // Like `Vec::resize`. fn resize(&mut self, size: usize, value: T) where T: Clone { let mut v : Vec<T> = match self { VecOrSlice::Vec(ref mut v) => std::mem::take(v), VecOrSlice::Slice(s) => s.to_vec(), VecOrSlice::Empty() => Vec::with_capacity(size), }; v.resize(size, value); *self = VecOrSlice::Vec(v); } } 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], VecOrSlice::Empty() => &[][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!(), VecOrSlice::Empty() => panic!("index out of bounds: the len is 0 but the index is {}", i), } } } /// 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. 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>), } 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) } 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), } } } } } ���������sequoia-openpgp-1.7.0/src/policy.rs�����������������������������������������������������������������0000644�0000000�0000000�00000315721�00726746425�0015442�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 [RFC 4880] 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. //! //! [RFC 4880]: https://tools.ietf.org/html/rfc4880#section-9.4 //! [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, }; /// 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(anyhow::anyhow!("By default all signatures are rejected.")) } /// 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. fn key(&self, _ka: &ValidErasedKeyAmalgamation<key::PublicParts>) -> Result<()> { Err(anyhow::anyhow!("By default all keys are rejected.")) } /// 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(anyhow::anyhow!("By default all symmetric algorithms are rejected.")) } /// 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(anyhow::anyhow!("By default all AEAD algorithms are rejected.")) } /// 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(anyhow::anyhow!("By default all packets are rejected.")) } } /// 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://tools.ietf.org/html/rfc4880#section-5.2.3 /// [the following is signed]: https://tools.ietf.org/html/rfc4880#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://tools.ietf.org/html/rfc4880#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 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 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://tools.ietf.org/html/rfc4880#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. /// /// 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::new(&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, 12, [ 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 ]); // Signatures that *only* require a hash with Second Pre-image // Resistance. See the documentation for HashAlgoSecurity for more // details. a_cutoff_list!(SecondPreImageResistantHashCutoffList, HashAlgorithm, 12, [ 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 ]); a_cutoff_list!(SubpacketTagCutoffList, SubpacketTag, 38, [ 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. ACCEPT, // 34. PreferredAEADAlgorithms. ACCEPT, // 35. IntendedRecipient. REJECT, // 36. Reserved. ACCEPT, // 37. AttestedCertifications. ]); a_cutoff_list!(AsymmetricAlgorithmCutoffList, AsymmetricAlgorithm, 18, [ Some(Timestamp::Y2014M2), // 0. RSA1024. ACCEPT, // 1. RSA2048. ACCEPT, // 2. RSA3072. ACCEPT, // 3. RSA4096. Some(Timestamp::Y2014M2), // 4. ElGamal1024. ACCEPT, // 5. ElGamal2048. ACCEPT, // 6. ElGamal3072. ACCEPT, // 7. ElGamal4096. Some(Timestamp::Y2014M2), // 8. DSA1024. ACCEPT, // 9. DSA2048. ACCEPT, // 10. DSA3072. ACCEPT, // 11. DSA4096. ACCEPT, // 12. NistP256. ACCEPT, // 13. NistP384. ACCEPT, // 14. NistP521. ACCEPT, // 15. BrainpoolP256. ACCEPT, // 16. BrainpoolP512. ACCEPT, // 17. Cv25519. ]); a_cutoff_list!(SymmetricAlgorithmCutoffList, SymmetricAlgorithm, 14, [ REJECT, // 0. Unencrypted. ACCEPT, // 1. IDEA. Some(Timestamp::Y2017M2), // 2. TripleDES. ACCEPT, // 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, 3, [ REJECT, // 0. Reserved. ACCEPT, // 1. EAX. ACCEPT, // 2. OCB. ]); a_cutoff_list!(PacketTagCutoffList, Tag, 21, [ 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. ACCEPT, // 20. AED. ]); // 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.collision_resistant_hash_algos.set(h, ACCEPT); self.second_pre_image_resistant_hash_algos.set(h, ACCEPT); } /// 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 `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 `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 `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 `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://tools.ietf.org/html/rfc4880#section-9.2 /// [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 `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 packets with the given tag. pub fn accept_packet_tag(&mut self, tag: Tag) { self.packet_tags.set(tag, ACCEPT); } /// Always reject packets with the given tag. pub fn reject_packet_tag(&mut self, tag: Tag) { self.packet_tags.set(tag, REJECT); } /// Start rejecting 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 a 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_at<C>(&mut self, tag: Tag, cutoff: C) where C: Into<Option<SystemTime>>, { self.packet_tags.set( tag, cutoff.into().and_then(system_time_cutoff_to_timestamp)); } /// Returns the cutoff times for the specified hash algorithm. pub fn packet_tag_cutoff(&self, tag: Tag) -> Option<SystemTime> { self.packet_tags.cutoff(tag).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)) .context(format!( "Policy rejected revocation signature ({}) requiring \ collision resistance", sig.typ()))? } else { self .collision_resistant_hash_algos .check(sig.hash_algo(), time, None) .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)) .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) .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(Error::PolicyViolation( format!("Policy rejected critical notation {:?}", n.name()), None).into()); } } } Ok(()) } fn key(&self, ka: &ValidErasedKeyAmalgamation<key::PublicParts>) -> Result<()> { use self::AsymmetricAlgorithm::{*, Unknown}; use crate::types::PublicKeyAlgorithm::*; use crate::crypto::mpi::PublicKey; #[allow(deprecated)] let a = match (ka.pk_algo(), ka.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.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::BrainpoolP512 => BrainpoolP512, Curve::Ed25519 => Cv25519, Curve::Cv25519 => Cv25519, Curve::Unknown(_) => Unknown, } }, _ => Unknown, }; let time = self.time.unwrap_or_else(Timestamp::now); self.asymmetric_algos.check(a, time, None) .context("Policy rejected asymmetric algorithm") } fn packet(&self, packet: &Packet) -> Result<()> { let time = self.time.unwrap_or_else(Timestamp::now); self.packet_tags.check(packet.tag(), 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`. /// /// Note: This enum cannot be exhaustively matched to allow future /// extensions. #[non_exhaustive] #[derive(Clone, Debug)] 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, /// brainpoolP512r1. BrainpoolP512, /// D.J. Bernstein's Curve25519. Cv25519, /// Unknown algorithm. Unknown, } assert_send_and_sync!(AsymmetricAlgorithm); 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, BrainpoolP512 => 16, Cv25519 => 17, 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 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::Fingerprint; 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())?; // 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())?; 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<D>(&mut self, _: &[PKESK], _: &[SKESK], _: Option<SymmetricAlgorithm>,_: D) -> Result<Option<Fingerprint>> where D: FnMut(SymmetricAlgorithm, &SessionKey) -> bool { 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)?; 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() ])?; 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::Key4::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() ])?; 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::Serialize; 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.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(_) => self.errors += 1, } } MessageLayer::Compression { .. } => (), _ => unreachable!(), } } Ok(()) } } impl DecryptionHelper for VHelper { fn decrypt<D>(&mut self, _: &[PKESK], _: &[SKESK], _: Option<SymmetricAlgorithm>,_: D) -> Result<Option<Fingerprint>> where D: FnMut(SymmetricAlgorithm, &SessionKey) -> bool { 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.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 mut 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 = { let mut v = Vec::new(); let sig : Packet = sig.into(); sig.serialize(&mut v).unwrap(); v }; 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<D>(&mut self, _: &[PKESK], _: &[SKESK], _: Option<SymmetricAlgorithm>, _: D) -> Result<Option<Fingerprint>> where D: FnMut(SymmetricAlgorithm, &SessionKey) -> bool { 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<D>(&mut self, pkesks: &[PKESK], _: &[SKESK], algo: Option<SymmetricAlgorithm>, mut decrypt: D) -> Result<Option<Fingerprint>> where D: FnMut(SymmetricAlgorithm, &SessionKey) -> bool { 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(()) } } �����������������������������������������������sequoia-openpgp-1.7.0/src/regex/grammar.lalrpop�����������������������������������������������������0000644�0000000�0000000�00000013736�00726746425�0017731�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://tools.ietf.org/html/rfc4880#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().is_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().is_empty()) { // All pieces are empty. Just return empty. hir::Hir::empty() } else { hir::Hir::group(hir::Group { kind: hir::GroupKind::NonCapturing, hir: Box::new(hir::Hir::concat(p)), }) } }, } Piece : Hir = { <a:Atom> => a, <a:Atom> STAR => { if a.kind().is_empty() { // Piece is empty. This is equivalent to empty so just // return it. a } else { hir::Hir::repetition(hir::Repetition { kind: hir::RepetitionKind::ZeroOrMore, greedy: true, hir: Box::new(a) }) } }, <a:Atom> PLUS => { if a.kind().is_empty() { // Piece is empty. This is equivalent to empty so just // return it. a } else { hir::Hir::repetition(hir::Repetition { kind: hir::RepetitionKind::OneOrMore, greedy: true, hir: Box::new(a) }) } }, <a:Atom> QUESTION => { if a.kind().is_empty() { // Piece is empty. This is equivalent to empty so just // return it. a } else { hir::Hir::repetition(hir::Repetition { kind: hir::RepetitionKind::ZeroOrOne, greedy: true, hir: Box::new(a) }) } }, } Atom : Hir = { LPAREN <r:Regex> RPAREN => { if r.kind().is_empty() { r } else { hir::Hir::group(hir::Group { kind: hir::GroupKind::NonCapturing, hir: Box::new(r), }) } }, Range, DOT => { hir::Hir::any(false) }, CARET => { hir::Hir::anchor(hir::Anchor::StartText) }, DOLLAR => { hir::Hir::anchor(hir::Anchor::EndText) }, BACKSLASH <t:AnyChar> => { hir::Hir::literal(hir::Literal::Unicode(t.to_char())) }, <t:OTHER> => { hir::Hir::literal(hir::Literal::Unicode(t.to_char())) }, } 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-1.7.0/src/regex/lexer.rs������������������������������������������������������������0000644�0000000�0000000�00000012577�00726746425�0016377�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)] #[allow(clippy::upper_case_acronyms)] 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-1.7.0/src/regex/mod.rs��������������������������������������������������������������0000644�0000000�0000000�00000175202�00726746425�0016032�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://tools.ietf.org/html/rfc4880#section-8 //! [trust signatures]: https://tools.ietf.org/html/rfc4880#section-5.2.3.13 //! //! Compared with most regular expression lanugages, 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://tools.ietf.org/html/rfc4880#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://tools.ietf.org/html/rfc4880#section-5.2.3.13 //! [`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://tools.ietf.org/html/rfc4880#section-5.2.3.14 //! [`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(None, 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(None, 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())?; //! # 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(None, 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())?; //! # 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(None, 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())?; //! # 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 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)); //! 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); //! } //! } //! } //! 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)); //! 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); //! } //! } //! } //! 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 4880] 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 4880]: https://tools.ietf.org/html/rfc4880#section-8 /// [trust signatures]: https://tools.ietf.org/html/rfc4880#section-5.2.3.13 /// [`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 #[derive(Clone, Debug)] pub struct Regex { regex: regex::Regex, disable_sanitizations: bool, } assert_send_and_sync!(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 { 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)?) } /// 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 4880] 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 4880]: https://tools.ietf.org/html/rfc4880#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://tools.ietf.org/html/rfc4880#section-5.2.3.14 /// [Trust Signatures]: https://tools.ietf.org/html/rfc4880#section-5.2.3.13 /// /// `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 #[derive(Clone)] pub struct RegexSet { re_set: RegexSet_, disable_sanitizations: bool, } assert_send_and_sync!(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; for re in res { let re = re.borrow(); let lexer = Lexer::new(re); match grammar::RegexParser::new().parse(re, lexer) { Ok(hir) => { had_good = true; let hir = hir::Hir::group(hir::Group { kind: hir::GroupKind::NonCapturing, hir: Box::new(hir), }); 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_set: RegexSet_::Invalid, disable_sanitizations: false, }) } else if ! had_bad && ! had_good { // Match everything. t!("No regular expressions provided."); Ok(RegexSet { re_set: RegexSet_::Everything, disable_sanitizations: false, }) } else { // Match any of the regular expressions. Ok(RegexSet { re_set: RegexSet_::Regex( Regex { 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 re_set = Self::new( res .into_iter() .scan((&mut have_valid_utf8, &mut have_invalid_utf8), |(valid, invalid), re| { 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_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 } } /// 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://tools.ietf.org/html/rfc4880#section-5.2.3.13 /// /// 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://tools.ietf.org/html/rfc4880#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(None, 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(None, 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 contrain the path. pub fn everything() -> Result<Self> { Ok(Self { 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 { let u = u.borrow(); 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"), (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"), ]); 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("xxcd")); // 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")); Ok(()) } } ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-1.7.0/src/seal.rs�������������������������������������������������������������������0000644�0000000�0000000�00000003703�00726746425�0015061�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 of 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-1.7.0/src/serialize/cert.rs���������������������������������������������������������0000644�0000000�0000000�00000102077�00726746425�0017065�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������use crate::Result; use crate::cert::prelude::*; use crate::packet::{header::BodyLength, key, Signature, Tag}; use crate::seal; use crate::serialize::{ PacketRef, Marshal, MarshalInto, NetLength, generic_serialize_into, generic_export_into, }; impl Cert { /// 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<()> { 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.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.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) } } /// 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: &'a Cert, filter: Box<dyn Fn(&'a key::UnspecifiedSecret) -> bool + '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.optional_secret() != b.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<'a> TSK<'a> { /// Creates a new view for the given `Cert`. fn new(cert: &'a Cert) -> Self { Self { cert, filter: Box::new(|_| true), emit_stubs: false, } } /// 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().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: 'a + Fn(&'a key::UnspecifiedSecret) -> bool { 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 11.2 of RFC 4880]. /// /// 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 11.2 of RFC 4880]: https://tools.ietf.org/html/rfc4880#section-11.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().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().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 } /// 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::UnspecifiedSecret, tag_public, tag_secret| { let tag = if key.has_secret() && (self.filter)(key) { tag_secret } else { tag_public }; if self.emit_stubs && (tag == Tag::PublicKey || tag == Tag::PublicSubkey) { // 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()), }; let key_with_stub = key.clone() .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; 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.into()).serialize(o), Tag::SecretSubkey => PacketRef::SecretSubkey(key.into()).serialize(o), _ => unreachable!(), } }; 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.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::UnspecifiedSecret, tag_public, tag_secret| { let tag = if key.has_secret() && (self.filter)(key) { 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. let l = key.parts_as_public().net_len() + 8; 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.into()), Tag::SecretSubkey => PacketRef::SecretSubkey(key.into()), _ => 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.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::Key4, }; 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 = Key4::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(); 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().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 serialized_eq = a.to_vec()? == b.to_vec()?; let eq = a == b; assert_eq!(serialized_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(()) } } �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-1.7.0/src/serialize/cert_armored.rs�������������������������������������������������0000644�0000000�0000000�00000032216�00726746425�0020573�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 or not 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(None, 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(None, 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. #[allow(clippy::upper_case_acronyms)] 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) } fn serialize_common(&self, o: &mut dyn io::Write, export: bool) -> Result<()> { let (prelude, headers) = match self { Encoder::Cert(cert) => (armor::Kind::PublicKey, cert.armor_headers()), Encoder::TSK(ref tsk) => (armor::Kind::SecretKey, 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 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 word = match self { Self::Cert(_) => "PUBLIC", Self::TSK(_) => "PRIVATE", }.len(); "-----BEGIN PGP ".len() + word + " KEY BLOCK-----\n\n".len() + headers_len + body_len + (body_len + armor::LINE_LENGTH - 1) / armor::LINE_LENGTH // NLs + "=FUaG\n-----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::new( &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(None, 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::new( &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-1.7.0/src/serialize/stream/dash_escape.rs�������������������������������������������0000644�0000000�0000000�00000016364�00726746425�0021665�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.1 of RFC 4880]). //! //! [Section 7.1 of RFC 4880]: https://tools.ietf.org/html/rfc4880#section-7.1 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); #[allow(clippy::new_ret_no_self)] 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, and writes a final /// newline. 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); if done && ! self.buffer.is_empty() && ! self.buffer.ends_with(b"\n") { self.buffer.push(b'\n'); } // 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); } 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\n"[..]); 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-\n"[..]); 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 \n"[..]); Ok(()) } } ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-1.7.0/src/serialize/stream/padding.rs�����������������������������������������������0000644�0000000�0000000�00000040273�00726746425�0021030�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������//! Padding for OpenPGP messages. //! //! To reduce the amount of information leaked via the message length, //! encrypted OpenPGP messages (see [Section 11.3 of RFC 4880]) should //! be padded. //! //! [Section 11.3 of RFC 4880]: https://tools.ietf.org/html/rfc4880#section-11.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 //! //! There are a number of ways to pad messages within the boundaries //! of the OpenPGP protocol, keeping an eye on backwards-compatibility //! with common implementations: //! //! - Add a decoy notation to a signature packet (up to about 60k) //! //! - Add a signature with a private algorithm and store the decoy //! traffic in the MPIs (up to 4 GB) //! //! - Use a compression container and store the decoy traffic in a //! chunk that decompresses to the empty string (unlimited) //! //! - Use a bunch of marker packets, which are ignored (each packet: //! 3 bytes for the body, 5 bytes for the header) //! //! - Apparently, GnuPG understands a comment packet (tag: 61), //! which is not standardized (up to 64k) //! //! We believe that padding the compressed data stream is the best //! option, because as far as OpenPGP is concerned, it is completely //! transparent for the recipient (for example, no weird packets are //! inserted). //! //! Unfortunately, [testing] discovered problems when the resulting //! messages are consumed by (at the time of this writing) OpenPGP.js, //! RNP, and GnuPG. If compatibility with these implementations is a //! concern, using this padding method is not advisable. //! //! [testing]: https://tests.sequoia-pgp.org/#Packet_excess_consumption //! //! 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 (padding ops literal signature))`, the //! exact structure GnuPG emits by default. use std::fmt; use std::io::{self, Write}; use crate::{ Result, packet::prelude::*, }; use crate::packet::header::CTB; use crate::serialize::{ Marshal, stream::{ writer, Cookie, Message, PartialBodyFilter, }, }; use crate::types::{ CompressionAlgorithm, CompressionLevel, }; /// 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 /// /// This implementation uses the [DEFLATE] compression format. The /// packet structure contains a flag signaling the end of the stream /// (see [Section 3.2.3 of RFC 1951]), and any data appended after /// that is not part of the stream. /// /// [DEFLATE]: https://tools.ietf.org/html/rfc1951 /// [Section 3.2.3 of RFC 1951]: https://tools.ietf.org/html/rfc1951#page-9 /// /// [Section 9.3 of RFC 4880] recommends that this algorithm should be /// implemented, therefore support across various implementations /// should be good. /// /// [Section 9.3 of RFC 4880]: https://tools.ietf.org/html/rfc4880#section-9.3 /// /// # 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()?; /// } /// assert!(unpadded.len() < padded.len()); /// # Ok(()) /// # } pub struct Padder<'a> { inner: writer::BoxStack<'a, Cookie>, policy: fn(u64) -> u64, } assert_send_and_sync!(Padder<'_>); impl<'a> Padder<'a> { /// 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![]); /// let message = Padder::new(message).build()?; /// // Optionally add a `Signer` here. /// // Add a `LiteralWriter` here. /// # let _ = message; /// # Ok(()) } /// ``` pub fn new(inner: Message<'a>) -> Self { Self { inner: writer::BoxStack::from(inner), policy: padme, } } /// 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![]); /// let message = Padder::new(message).with_policy(padme).build()?; /// // Optionally add a `Signer` here. /// // Add a `LiteralWriter` here. /// # let _ = message; /// # Ok(()) } /// ``` pub fn with_policy(mut self, p: fn(u64) -> u64) -> Self { self.policy = 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![]); /// let message = Padder::new(message).build()?; /// // Optionally add a `Signer` here. /// // Add a `LiteralWriter` here. /// # let _ = message; /// # Ok(()) } /// ``` pub fn build(mut self) -> Result<Message<'a>> { let mut inner = self.inner; let level = inner.cookie_ref().level + 1; // Packet header. CTB::new(Tag::CompressedData).serialize(&mut inner)?; let mut inner: Message<'a> = PartialBodyFilter::new(Message::from(inner), Cookie::new(level)); // Compressed data header. inner.as_mut().write_u8(CompressionAlgorithm::Zip.into())?; // Create an appropriate filter. self.inner = writer::ZIP::new(inner, Cookie::new(level), CompressionLevel::none()).into(); Ok(Message::from(Box::new(self))) } } impl<'a> fmt::Debug for Padder<'a> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.debug_struct("Padder") .field("inner", &self.inner) .finish() } } impl<'a> io::Write for Padder<'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 Padder<'a> { fn into_inner(self: Box<Self>) -> Result<Option<writer::BoxStack<'a, Cookie>>> { // Make a note of the amount of data written to this filter. let uncompressed_size = self.position(); // Pop-off us and the compression filter, leaving only our // partial body encoder on the stack. This finalizes the // compression. let mut pb_writer = Box::new(self.inner).into_inner()?.unwrap(); // Compressed size is what we've actually written out, modulo // partial body encoding. let compressed_size = pb_writer.position(); // Sometimes, the compression step expands the data. Handle // this by padding the maximum of both sizes. let size = std::cmp::max(uncompressed_size, compressed_size); // 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 mut amount = padded_size - compressed_size; if false { eprintln!("u: {}, c: {}, amount: {}", uncompressed_size, compressed_size, amount); } // Write 'amount' of padding. const BUFFER_SIZE: usize = 4096; let mut padding = vec![0; BUFFER_SIZE]; while amount > 0 { let n = std::cmp::min(BUFFER_SIZE as u64, amount) as usize; crate::crypto::random(&mut padding[..n]); pb_writer.write_all(&padding[..n])?; amount -= n as u64; } pb_writer.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() } } /// 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 #[allow(clippy::many_single_char_names)] 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); 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-1.7.0/src/serialize/stream/partial_body.rs������������������������������������������0000644�0000000�0000000�00000023304�00726746425�0022067�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; #[allow(clippy::new_ret_no_self)] 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-1.7.0/src/serialize/stream/trim_whitespace.rs���������������������������������������0000644�0000000�0000000�00000020716�00726746425�0022611�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������//! Trims trailing whitespace. //! //! This filter is used to generate messages using the Cleartext //! Signature Framework (see [Section 7.1 of RFC 4880]). //! //! [Section 7.1 of RFC 4880]: https://tools.ietf.org/html/rfc4880#section-7.1 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); #[allow(clippy::new_ret_no_self)] 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, and writes a final /// newline. 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); if done && ! self.buffer.is_empty() && ! self.buffer.ends_with(b"\n") { self.buffer.push(b'\n'); } // 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() { // 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]; } // To simplify the logic in Signer::write, we emit the // newline in one write. if l.ends_with(b"\r") { self.inner.write_all(&l[..l.len() - 1])?; self.inner.write_all(b"\r\n")?; } else { self.inner.write_all(l)?; self.inner.write_all(b"\n")?; } } last_line = Some(line); } 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\n"[..]); 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\n"[..]); 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\n"[..]); 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\n"[..]); Ok(()) } } ��������������������������������������������������sequoia-openpgp-1.7.0/src/serialize/stream/writer/mod.rs��������������������������������������������0000644�0000000�0000000�00000043570�00726746425�0021520�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::{ 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. 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. 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]) } /// Writes a big endian `u16`. fn write_be_u16(&mut self, n: u16) -> io::Result<()> { self.write_all(&n.to_be_bytes()) } /// Writes a big endian `u32`. fn write_be_u32(&mut self, n: u32) -> io::Result<()> { self.write_all(&n.to_be_bytes()) } } /// 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>(head: &(dyn Stackable<C> + Send + Sync), mut fun: F) where F: FnMut(&(dyn Stackable<C> + Send + Sync)) -> bool { let mut ow = Some(head); while let Some(w) = ow { if ! fun(w) { break; } ow = w.inner_ref() } } /// Maps a function over the stack of mutable writers. #[allow(dead_code)] pub(crate) fn map_mut<C, F>(head: &mut (dyn Stackable<C> + Send + Sync), mut fun: F) where F: FnMut(&mut (dyn Stackable<C> + Send + Sync)) -> bool { let mut ow = Some(head); while let Some(w) = ow { if ! fun(w) { break; } ow = w.inner_mut() } } /// 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; true }); } /// 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); #[allow(clippy::new_ret_no_self)] 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); #[allow(clippy::new_ret_no_self)] 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); #[allow(clippy::new_ret_no_self)] 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), }))) } } 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, C: 'a> io::Write for Armorer<'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 Armorer<'a, C> { fn into_inner(self: Box<Self>) -> Result<Option<BoxStack<'a, C>>> { let inner = self.inner.inner.finalize()?; 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().as_mut()) } fn inner_ref(&self) -> Option<&(dyn Stackable<'a, C> + Send + Sync)> { Some(self.inner.inner.get_ref().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 } } /// 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); #[allow(clippy::new_ret_no_self)] 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> { inner: Generic<aead::Encryptor<BoxStack<'a, C>>, C>, } assert_send_and_sync!(AEADEncryptor<'_, C> where C); #[allow(clippy::new_ret_no_self)] impl<'a> AEADEncryptor<'a, Cookie> { /// Makes an encrypting writer. pub fn new(inner: Message<'a>, cookie: Cookie, cipher: SymmetricAlgorithm, aead: AEADAlgorithm, chunk_size: usize, iv: &[u8], key: &SessionKey) -> Result<Message<'a>> { Ok(Message::from(Box::new(AEADEncryptor { inner: Generic::new_unboxed( aead::Encryptor::new(1, cipher, aead, chunk_size, iv, key, inner.into())?, cookie), }))) } } impl<'a, C: 'a> fmt::Debug for AEADEncryptor<'a, C> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.debug_struct("writer::AEADEncryptor") .field("inner", &self.inner) .finish() } } impl<'a, C: 'a> io::Write for AEADEncryptor<'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 AEADEncryptor<'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 } } #[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| { let new = Cookie::new(0); let old = g.cookie_set(new); assert_eq!(old.level, 1); count += 1; true }); 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| { assert_eq!(g.cookie_ref().level, 0); count += 1; true }); assert_eq!(count, 2); } } } ����������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-1.7.0/src/serialize/stream/writer/writer_bzip2.rs�����������������������������������0000644�0000000�0000000�00000004232�00726746425�0023353�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); #[allow(clippy::new_ret_no_self)] 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-1.7.0/src/serialize/stream/writer/writer_deflate.rs���������������������������������0000644�0000000�0000000�00000010453�00726746425�0023733�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. #[allow(clippy::upper_case_acronyms)] pub struct ZIP<'a, C: 'a> { inner: Generic<DeflateEncoder<BoxStack<'a, C>>, C>, } assert_send_and_sync!(ZIP<'_, C> where C); #[allow(clippy::new_ret_no_self)] 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. #[allow(clippy::upper_case_acronyms)] pub struct ZLIB<'a, C: 'a> { inner: Generic<ZlibEncoder<BoxStack<'a, C>>, C>, } assert_send_and_sync!(ZLIB<'_, C> where C); #[allow(clippy::new_ret_no_self)] 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-1.7.0/src/serialize/stream.rs�������������������������������������������������������0000644�0000000�0000000�00000412623�00726746425�0017424�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������//! Streaming packet serialization. //! //! This interface provides a convenient way to create signed and/or //! encrypted OpenPGP messages (see [Section 11.3 of RFC 4880]) and is //! the preferred interface to generate messages using Sequoia. It //! takes advantage of OpenPGP's streaming nature to avoid unnecessary //! buffering. //! //! [Section 11.3 of RFC 4880]: https://tools.ietf.org/html/rfc4880#section-11.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 11.3 of RFC 4880]) containing just a //! literal data packet (see [Section 5.9 of RFC 4880]): //! //! [Section 5.9 of RFC 4880]: https://tools.ietf.org/html/rfc4880#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 11.3 of RFC 4880]). The plaintext //! is first signed, then 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, //! }; //! # 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 = 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, KeyID, Result, crypto::Password, crypto::SessionKey, packet::prelude::*, packet::signature, packet::key, cert::prelude::*, }; use crate::packet::header::CTB; use crate::packet::header::BodyLength; use super::{ Marshal, }; use crate::types::{ AEADAlgorithm, CompressionAlgorithm, CompressionLevel, DataFormat, SignatureType, SymmetricAlgorithm, }; pub(crate) mod writer; #[cfg(feature = "compression-deflate")] 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, } #[derive(Debug)] enum Private { Nothing, Signer, } 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 4880]) 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 handled by end users (e.g. copied /// and pasted). /// /// [Section 6 of RFC 4880]: https://tools.ietf.org/html/rfc4880#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 4880]), but in practice, any key may be used, as /// implementations should simply ignore unknown keys. /// /// [Section 6 of RFC 4880]: https://tools.ietf.org/html/rfc4880#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; writer::Armorer::new( self.inner, Cookie::new(level + 1), 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<'_>); #[allow(clippy::new_ret_no_self)] 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>>, intended_recipients: Vec<Fingerprint>, mode: SignatureMode, template: signature::SignatureBuilder, creation_time: Option<SystemTime>, hash: Box<dyn crypto::hash::Digest>, cookie: Cookie, position: u64, /// When creating a message using the cleartext signature /// framework, the final newline is not part of the signature, /// hence, we delay hashing up to two bytes so that we can omit /// them when the message is finalized. hash_stash: Vec<u8>, } 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 11.4 of RFC /// 4880]), 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 11.4 of RFC 4880]: https://tools.ietf.org/html/rfc4880#section-11.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) -> 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) -> 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: vec![Box::new(signer)], intended_recipients: Vec::new(), mode: SignatureMode::Inline, template: template.into(), creation_time: None, hash: HashAlgorithm::default().context().unwrap(), cookie: Cookie { level, private: Private::Signer, }, position: 0, hash_stash: Vec::with_capacity(0), } } /// Creates a signer for a detached signature. /// /// Changes the `Signer` to create a detached signature (see /// [Section 11.4 of RFC 4880]). Note that the literal data *must /// not* be wrapped using the [`LiteralWriter`]. /// /// This overrides any prior call to [`Signer::cleartext`]. /// /// [Section 11.4 of RFC 4880]: https://tools.ietf.org/html/rfc4880#section-11.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 4880]). 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: /// /// - If your message does not end in a newline, creating a signed /// message using the Cleartext Signature Framework will add /// one. /// /// - The cleartext signature framework does not hash trailing /// whitespace (in this case, space and tab, see [Section 7.1 of /// RFC 4880] 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 4880]). /// /// This overrides any prior call to [`Signer::detached`]. /// /// [Section 7 of RFC 4880]: https://tools.ietf.org/html/rfc4880#section-7 /// [Section 7.1 of RFC 4880]: https://tools.ietf.org/html/rfc4880#section-7.1 /// [`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!\n"); /// # 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. /// /// # 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) -> Self where S: crypto::Signer + Send + Sync + 'a { self.signers.push(Box::new(signer)); 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 hash algorithm to use for the signatures. /// /// # 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> { self.hash = algo.context()?; Ok(self) } /// 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 11.3 of RFC 4880]), literal data *must* /// be wrapped using the [`LiteralWriter`]. On the other hand, if /// you are creating a detached signature (see [Section 11.4 of /// RFC 4880]), the literal data *must not* be wrapped using the /// [`LiteralWriter`]. /// /// [Section 11.3 of RFC 4880]: https://tools.ietf.org/html/rfc4880#section-11.3 /// [Section 11.4 of RFC 4880]: https://tools.ietf.org/html/rfc4880#section-11.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."); match self.mode { SignatureMode::Inline => { // For every key we collected, build and emit a one pass // signature packet. for (i, keypair) in self.signers.iter().enumerate() { let key = keypair.public(); let mut ops = OnePassSig3::new(self.template.typ()); ops.set_pk_algo(key.pk_algo()); ops.set_hash_algo(self.hash.algo()); ops.set_issuer(key.keyid()); ops.set_last(i == self.signers.len() - 1); Packet::OnePassSig(ops.into()) .serialize(self.inner.as_mut().unwrap())?; } }, 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-----")?; writeln!(sink, "Hash: {}", self.hash.algo().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 inner = self.inner.take().expect("It's the DashEscapeFilter") .into_inner()?.expect("It's the DashEscapeFilter"); // 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 in self.signers.iter_mut() { // Part of the signature packet is hashed in, // therefore we need to clone the hash. let hash = self.hash.clone(); // Make and hash a signature packet. let mut sig = self.template.clone() .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)?; // 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]; if self.mode == Cleartext { // Delay hashing the last two bytes, because we the // final newline is not part of the signature (see // Section 7.1 of RFC4880). // First, hash the stashed bytes. We know that it is // a newline, but we know that more text follows (buf // is not empty), so it cannot be the last. assert!(! buf.is_empty()); crate::parse::hash_update_text(&mut self.hash, &self.hash_stash[..]); crate::vec_truncate(&mut self.hash_stash, 0); // Compute the length of data that should be hashed. // If it ends in a newline, we delay hashing it. let l = data.len() - if data.ends_with(b"\r\n") { 2 } else if data.ends_with(b"\n") { 1 } else { 0 }; // XXX: This logic breaks if we get a b"\r\n" in two // writes. However, TrailingWSFilter will only emit // b"\r\n" in one write. // Hash everything but the last newline now. crate::parse::hash_update_text(&mut self.hash, &data[..l]); // The newline we stash away. If more text is written // later, we will hash it then. Otherwise, it is // implicitly omitted when the signer is finalized. self.hash_stash.extend_from_slice(&data[l..]); } else if self.template.typ() == SignatureType::Text { crate::parse::hash_update_text(&mut self.hash, data); } else { self.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 4880]). The body will be written using partial length /// encoding, or, if the body is short, using full length encoding. /// /// [Section 5.9 of RFC 4880]: https://tools.ietf.org/html/rfc4880#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::Text) /// .build()?; /// message.write_all(b"Hello world.")?; /// message.finalize()?; /// } /// assert_eq!(b"\xcb\x12t\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 4880]). 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 4880]: https://tools.ietf.org/html/rfc4880#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> { keyid: KeyID, key: &'a Key<key::PublicParts, key::UnspecifiedRole>, } assert_send_and_sync!(Recipient<'_>); impl<'a, P, R> From<&'a Key<P, R>> for Recipient<'a> where P: key::KeyParts, R: key::KeyRole, { fn from(key: &'a Key<P, R>) -> Self { Self::new(key.keyid(), key.parts_as_public().role_as_unspecified()) } } impl<'a, P, R, R2> From<ValidKeyAmalgamation<'a, P, R, R2>> for Recipient<'a> where P: key::KeyParts, R: key::KeyRole, R2: Copy, { fn from(ka: ValidKeyAmalgamation<'a, P, R, R2>) -> Self { ka.key().into() } } 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() /// .map(|ka| Recipient::new(ka.key().keyid(), 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<P, R>(keyid: KeyID, key: &'a Key<P, R>) -> Recipient<'a> where P: key::KeyParts, R: key::KeyRole, { Recipient { keyid, 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].keyid(), /// &"8BD8 8E94 C0D2 0333".parse()?); /// # Ok(()) } /// ``` pub fn keyid(&self) -> &KeyID { &self.keyid } /// Sets the recipient keyid. /// /// # Examples /// /// ``` /// # fn main() -> sequoia_openpgp::Result<()> { /// use std::io::Write; /// use sequoia_openpgp as openpgp; /// use openpgp::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_keyid(KeyID::wildcard()) /// ); /// /// # let mut sink = vec![]; /// let message = Message::new(&mut sink); /// let message = Encryptor::for_recipients(message, recipients).build()?; /// # let _ = message; /// # Ok(()) } /// ``` pub fn set_keyid(mut self, keyid: KeyID) -> Self { self.keyid = keyid; 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(None, Some("Mr. Pink ☮☮☮")) /// # .generate()?; /// # let (cert_1, _) = /// # CertBuilder::general_purpose(None, 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> { inner: writer::BoxStack<'a, Cookie>, session_key: Option<SessionKey>, recipients: Vec<Recipient<'a>>, passwords: Vec<Password>, sym_algo: SymmetricAlgorithm, aead_algo: Option<AEADAlgorithm>, hash: Box<dyn crypto::hash::Digest>, cookie: Cookie, } assert_send_and_sync!(Encryptor<'_>); impl<'a> Encryptor<'a> { /// 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<'a>>, { 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(), 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(), 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`](crate::packet::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( /// None, Some("Alice Lovelace <alice@example.org>")).generate()?; /// let (bob, _) = CertBuilder::general_purpose( /// None, 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, sk)?.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<(SymmetricAlgorithm, SessionKey, Vec<PKESK>)>, /// } /// /// # impl Helper { /// # fn new(key: Cert) -> Self { /// # Helper { key, recycling_bin: None, } /// # } /// # } /// # /// impl DecryptionHelper for Helper { /// fn decrypt<D>(&mut self, pkesks: &[PKESK], _skesks: &[SKESK], /// sym_algo: Option<SymmetricAlgorithm>, mut decrypt: D) /// -> Result<Option<Fingerprint>> /// where D: FnMut(SymmetricAlgorithm, &SessionKey) -> bool /// { /// 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_handle(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.fingerprint())) /// } /// } /// /// 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(), 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<'a>>, { 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. /// /// This feature is [experimental](super::super#experimental-features). /// /// # 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::EAX) /// .build()?; /// let mut message = LiteralWriter::new(message).build()?; /// message.write_all(b"Hello world.")?; /// message.finalize()?; /// # Ok(()) } /// ``` // Function hidden from the public API due to // https://gitlab.com/sequoia-pgp/sequoia/-/issues/550 // It is used only for tests so that it does not bit-rot. #[cfg(test)] 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()); } struct AEADParameters { algo: AEADAlgorithm, chunk_size: usize, nonce: Box<[u8]>, } let aead = if let Some(algo) = self.aead_algo { let mut nonce = vec![0; algo.iv_size()?]; crypto::random(&mut nonce); Some(AEADParameters { algo, chunk_size: Self::AEAD_CHUNK_SIZE, nonce: nonce.into_boxed_slice(), }) } 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() .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() { let mut pkesk = PKESK3::for_recipient(self.sym_algo, &sk, recipient.key)?; pkesk.set_recipient(recipient.keyid.clone()); 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 = SKESK5::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 AED packet. CTB::new(Tag::AED).serialize(&mut inner)?; let mut inner = PartialBodyFilter::new(Message::from(inner), Cookie::new(level)); let aed = AED1::new(self.sym_algo, aead.algo, aead.chunk_size as u64, aead.nonce)?; aed.serialize_headers(&mut inner)?; writer::AEADEncryptor::new( inner, Cookie::new(level), aed.symmetric_algo(), aed.aead(), aead.chunk_size, aed.iv(), &sk, ) } else { // Write the SEIP 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); // 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. 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); 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> fmt::Debug for Encryptor<'a> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.debug_struct("Encryptor") .field("inner", &self.inner) .finish() } } impl<'a> Write for Encryptor<'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> writer::Stackable<'a, Cookie> for Encryptor<'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, packet::CompressedData}; use crate::parse::{Parse, PacketParserResult, PacketParser}; use super::*; use crate::types::DataFormat::Text 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"t").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::Text); 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()); for s in signers.into_iter() { signer = signer.add_signer(s); } 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(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<(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. 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_messages() -> Result<()> { // 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::*; use crate::serialize::stream::{LiteralWriter, Message}; let (tsk, _) = CertBuilder::new() .set_cipher_suite(CipherSuite::Cv25519) .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(()) } } impl<'a> DecryptionHelper for Helper<'a> { fn decrypt<D>(&mut self, pkesks: &[PKESK], _skesks: &[SKESK], sym_algo: Option<SymmetricAlgorithm>, mut decrypt: D) -> Result<Option<crate::Fingerprint>> where D: FnMut(SymmetricAlgorithm, &SessionKey) -> bool { 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 = &P::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(AEADAlgorithm::EAX) .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 of 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); 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(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(()) } } �������������������������������������������������������������������������������������������������������������sequoia-openpgp-1.7.0/src/serialize.rs��������������������������������������������������������������0000644�0000000�0000000�00000342435�00726746425�0016134�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 //! 4880]). Serialization is the process of creating the binary //! representation. //! //! [Section 3 ff. of RFC 4880]: https://tools.ietf.org/html/rfc4880#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 11.3 of RFC 4880]). 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 11.3 of RFC 4880]) containing just a //! literal data packet (see [Section 5.9 of RFC 4880]): //! //! [Section 11.3 of RFC 4880]: https://tools.ietf.org/html/rfc4880#section-11.3 //! [Section 5.9 of RFC 4880]: https://tools.ietf.org/html/rfc4880#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 4880]): //! //! ``` //! # 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 std::ops::Deref; 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::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(None, 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(None, 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>(&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(()) } } 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)?; self.length().serialize(o)?; Ok(()) } } impl MarshalInto for Header { fn serialized_len(&self) -> usize { self.ctb().serialized_len() + self.length().serialized_len() } fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> { generic_serialize_into(self, MarshalInto::serialized_len(self), buf) } } 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::V4(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::V4(_) => 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 { match self { Fingerprint::V4(_) => 20, Fingerprint::Invalid(ref fp) => fp.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)])?; } 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 } 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)?; } 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() } 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(); 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)?; } 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() } 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)?; } 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() } 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::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::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::*; 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_slice())?; 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_slice())?, 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_slice())?, 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_slice())?, 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())?; }, _ => return Err(Error::InvalidArgument( "Unknown kind of fingerprint".into()).into()), } PreferredAEADAlgorithms(ref p) => for a in p { o.write_all(&[(*a).into()])?; }, IntendedRecipient(ref fp) => match fp { Fingerprint::V4(_) => { o.write_all(&[4])?; o.write_all(fp.as_bytes())?; }, _ => return Err(Error::InvalidArgument( "Unknown kind of fingerprint".into()).into()), } AttestedCertifications(digests) => { for digest in digests { o.write_all(digest)?; } }, Unknown { body, .. } => o.write_all(body)?, } Ok(()) } } impl MarshalInto for SubpacketValue { fn serialized_len(&self) -> usize { use self::SubpacketValue::*; 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(ref p) => p.as_slice().len(), PreferredKeyServer(ref p) => p.len(), PrimaryUserID(_) => 1, PolicyURI(ref p) => p.len(), KeyFlags(ref f) => f.as_slice().len(), SignersUserID(ref uid) => uid.len(), ReasonForRevocation { ref reason, .. } => 1 + reason.len(), Features(ref f) => f.as_slice().len(), SignatureTarget { ref digest, .. } => 2 + digest.len(), EmbeddedSignature(sig) => sig.serialized_len(), IssuerFingerprint(ref fp) => match fp { Fingerprint::V4(_) => 1 + (fp as &dyn MarshalInto).serialized_len(), // Educated guess for unknown versions. Fingerprint::Invalid(_) => 1 + fp.as_bytes().len(), }, PreferredAEADAlgorithms(ref p) => p.len(), IntendedRecipient(ref fp) => match fp { Fingerprint::V4(_) => 1 + (fp as &dyn MarshalInto).serialized_len(), // Educated guess for unknown versions. Fingerprint::Invalid(_) => 1 + fp.as_bytes().len(), }, AttestedCertifications(digests) => digests.iter().map(|d| d.len()).sum(), 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::V4(ref s) => s.serialize(o), } } fn export(&self, o: &mut dyn std::io::Write) -> Result<()> { match self { Signature::V4(ref s) => s.export(o), } } } impl MarshalInto for Signature { fn serialized_len(&self) -> usize { match self { Signature::V4(ref s) => s.serialized_len(), } } fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> { match self { Signature::V4(ref s) => s.serialize_into(buf), } } fn export_into(&self, buf: &mut [u8]) -> Result<usize> { match self { Signature::V4(ref s) => s.export_into(buf), } } fn export_to_vec(&self) -> Result<Vec<u8>> { match self { Signature::V4(ref s) => s.export_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 { 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 OnePassSig {} impl Marshal for OnePassSig { fn serialize(&self, o: &mut dyn std::io::Write) -> Result<()> { match self { OnePassSig::V3(ref s) => s.serialize(o), } } } impl MarshalInto for OnePassSig { fn serialized_len(&self) -> usize { match self { OnePassSig::V3(ref s) => s.serialized_len(), } } fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> { match self { OnePassSig::V3(ref s) => s.serialize_into(buf), } } } 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<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), } } } 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(), } } fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> { match self { Key::V4(ref p) => p.serialize_into(buf), } } } 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, Timestamp::try_from(self.creation_time())?.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) => { // S2K usage. write_byte(o, match e.checksum() { Some(SecretKeyChecksum::SHA1) => 254, Some(SecretKeyChecksum::Sum16) => 255, None => return Err(Error::InvalidOperation( "In Key4 packets, 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, { 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) => 1 + e.s2k().serialized_len() + 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 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), } } } impl MarshalInto for PKESK { fn serialized_len(&self) -> usize { match self { PKESK::V3(ref 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), } } } impl seal::Sealed for PKESK3 {} impl Marshal for PKESK3 { fn serialize(&self, o: &mut dyn std::io::Write) -> Result<()> { write_byte(o, 3)?; // Version. (self.recipient() 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 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::V5(ref s) => s.serialize(o), } } } impl NetLength for SKESK { fn net_len(&self) -> usize { match self { SKESK::V4(ref s) => s.net_len(), SKESK::V5(ref s) => s.net_len(), } } } impl MarshalInto for SKESK { fn serialized_len(&self) -> usize { match self { SKESK::V4(ref s) => s.serialized_len(), SKESK::V5(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::V5(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 SKESK5 {} impl Marshal for SKESK5 { fn serialize(&self, o: &mut dyn std::io::Write) -> Result<()> { write_byte(o, 5)?; // Version. write_byte(o, self.symmetric_algo().into())?; write_byte(o, self.aead_algo().into())?; self.s2k().serialize(o)?; if let Ok(iv) = self.aead_iv() { o.write_all(iv)?; } o.write_all(self.raw_esk())?; o.write_all(self.aead_digest())?; Ok(()) } } impl NetLength for SKESK5 { fn net_len(&self) -> usize { 1 // Version. + 1 // Cipher algo. + 1 // AEAD algo. + self.s2k().serialized_len() + self.aead_iv().map(|iv| iv.len()).unwrap_or(0) + self.raw_esk().len() + self.aead_digest().len() } } impl MarshalInto for SKESK5 { 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 { /// Writes a serialized version of the specified `SEIP` /// 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(&[self.version()])?; o.write_all(bytes)?; Ok(()) }, _ => Err(Error::InvalidOperation( "Cannot encrypt, use serialize::stream::Encryptor".into()) .into()), } } } impl NetLength for SEIP { fn net_len(&self) -> usize { match self.body() { Body::Unprocessed(bytes) => 1 /* Version */ + bytes.len(), _ => 0, } } } 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 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 AED {} impl Marshal for AED { fn serialize(&self, o: &mut dyn std::io::Write) -> Result<()> { match self { AED::V1(ref p) => p.serialize(o), } } } impl MarshalInto for AED { fn serialized_len(&self) -> usize { match self { AED::V1(ref p) => p.serialized_len(), } } fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> { match self { AED::V1(ref p) => p.serialize_into(buf), } } } impl AED1 { /// Writes the headers of the `AED` data packet to `o`. fn serialize_headers(&self, o: &mut dyn std::io::Write) -> Result<()> { o.write_all(&[1, // Version. self.symmetric_algo().into(), self.aead().into(), self.chunk_size().trailing_zeros() as u8 - 6])?; o.write_all(self.iv())?; Ok(()) } } impl seal::Sealed for AED1 {} impl Marshal for AED1 { /// Writes a serialized version of the specified `AED` /// 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 AED1 { fn net_len(&self) -> usize { match self.body() { Body::Unprocessed(bytes) => 4 // Headers. + self.iv().len() + bytes.len(), _ => 0, } } } impl MarshalInto for AED1 { 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), Packet::MDC(ref p) => p.serialize(o), Packet::AED(ref 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), Packet::MDC(ref p) => p.export(o), Packet::AED(ref 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(), Packet::MDC(ref p) => p.net_len(), Packet::AED(ref 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)] #[allow(clippy::upper_case_acronyms)] 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), /// AEAD Encrypted Data Packet. AED(&'a packet::AED), } impl<'a> PacketRef<'a> { /// Returns the `PacketRef's` corresponding OpenPGP tag. /// /// Tags are explained in [Section 4.3 of RFC 4880]. /// /// [Section 4.3 of RFC 4880]: https://tools.ietf.org/html/rfc4880#section-4.3 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::AED(_) => Tag::AED, } } } 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::AED(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::AED(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::AED(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<()> { (self.deref() as &dyn Marshal).serialize(o) } } impl SerializeInto for Message {} impl MarshalInto for Message { fn serialized_len(&self) -> usize { (self.deref() as &dyn MarshalInto).serialized_len() } fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> { (self.deref() as &dyn MarshalInto).serialize_into(buf) } fn export_into(&self, buf: &mut [u8]) -> Result<usize> { (self.deref() as &dyn MarshalInto).export_into(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[..]); } } #[cfg(feature = "compression-deflate")] #[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 { // 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::Text 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-1.7.0/src/tests.rs������������������������������������������������������������������0000644�0000000�0000000�00000005721�00726746425�0015301�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] { lazy_static::lazy_static! { static ref FILES: BTreeMap<&'static str, &'static [u8]> = { 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 }; } FILES.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-1.7.0/src/types/bitfield.rs���������������������������������������������������������0000644�0000000�0000000�00000005233�00726746425�0017063�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/// A bitfield. #[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub(crate) struct Bitfield { pub(crate) raw: Vec<u8>, } impl From<Vec<u8>> for Bitfield { fn from(raw: Vec<u8>) -> Self { Self { raw } } } impl Bitfield { pub fn iter(&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 }) } pub fn padding_len(&self) -> usize { let mut padding = 0; for i in (0..self.raw.len()).rev() { if self.raw[i] == 0 { padding += 1; } else { break; } } padding } /// Compares two feature sets for semantic equality. 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. pub(crate) fn as_slice(&self) -> &[u8] { &self.raw } /// Returns whether the specified flag is set. 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 } } /// Remove any trailing padding. fn clear_padding(mut self) -> Self { while !self.raw.is_empty() && self.raw[self.raw.len() - 1] == 0 { self.raw.truncate(self.raw.len() - 1); } self } /// Sets the specified flag. /// /// This also clears any padding (trailing NUL bytes). pub fn set(mut self, bit: usize) -> Self { let byte = bit / 8; while self.raw.len() <= byte { self.raw.push(0); } self.raw[byte] |= 1 << (bit % 8); self.clear_padding() } /// Clears the specified flag. /// /// This also clears any padding (trailing NUL bytes). pub fn clear(mut self, bit: usize) -> Self { let byte = bit / 8; if byte < self.raw.len() { self.raw[byte] &= !(1 << (bit % 8)); } self.clear_padding() } } ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-1.7.0/src/types/compression_level.rs������������������������������������������������0000644�0000000�0000000�00000006206�00726746425�0021032�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]. To mitigate some of /// these issues messages should [use padding]. /// /// [dangerous when used naively]: https://mailarchive.ietf.org/arch/msg/openpgp/2FQUVt6Dw8XAsaMELyo5BNlh2pM /// [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-1.7.0/src/types/features.rs���������������������������������������������������������0000644�0000000�0000000�00000030076�00726746425�0017122�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.24 of RFC 4880], /// and [Section 5.2.3.25 of RFC 4880bis]. /// /// [Section 5.2.3.24 of RFC 4880]: https://tools.ietf.org/html/rfc4880#section-5.2.3.24 /// [Section 5.2.3.25 of RFC 4880bis]: https://tools.ietf.org/html/draft-ietf-openpgp-rfc4880bis-09#section-5.2.3.25 /// /// 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(None, 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_mdc()); /// assert!(!features.supports_aead()); /// } /// 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_mdc() { f.write_str("MDC")?; need_comma = true; } if self.supports_aead() { if need_comma { f.write_str(", ")?; } f.write_str("AEAD")?; need_comma = true; } // Now print any unknown features. for i in self.0.iter() { match i { FEATURE_FLAG_MDC => (), FEATURE_FLAG_AEAD => (), i => { if need_comma { f.write_str(", ")?; } write!(f, "#{}", i)?; need_comma = true; } } } // Mention any padding, as equality is sensitive to this. let padding = self.0.padding_len(); if padding > 0 { 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_mdc() } /// 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 a slice containing the raw values. pub(crate) fn as_slice(&self) -> &[u8] { self.0.as_slice() } /// 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 2. /// let f = Features::new(&[0x5]); /// /// assert!(f.get(0)); /// assert!(! f.get(1)); /// assert!(f.get(2)); /// assert!(! f.get(3)); /// assert!(! f.get(8)); /// assert!(! f.get(80)); /// # assert!(f.supports_mdc()); /// # assert!(! f.supports_aead()); /// # 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(2); /// /// assert!(f.get(0)); /// assert!(! f.get(1)); /// assert!(f.get(2)); /// assert!(! f.get(3)); /// # assert!(f.supports_mdc()); /// # assert!(! f.supports_aead()); /// # Ok(()) } /// ``` pub fn set(self, bit: usize) -> Self { Self(self.0.set(bit)) } /// 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(2).clear(2); /// /// assert!(f.get(0)); /// assert!(! f.get(1)); /// assert!(! f.get(2)); /// assert!(! f.get(3)); /// # assert!(f.supports_mdc()); /// # assert!(! f.supports_aead()); /// # Ok(()) } /// ``` pub fn clear(self, bit: usize) -> Self { Self(self.0.clear(bit)) } /// Returns whether the MDC 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_mdc()); /// # Ok(()) } /// ``` pub fn supports_mdc(&self) -> bool { self.get(FEATURE_FLAG_MDC) } /// Sets the MDC feature flag. /// /// # Examples /// /// ``` /// use sequoia_openpgp as openpgp; /// # use openpgp::Result; /// use openpgp::types::Features; /// /// # fn main() -> Result<()> { /// let f = Features::empty().set_mdc(); /// /// assert!(f.supports_mdc()); /// # assert!(f.get(0)); /// # Ok(()) } /// ``` pub fn set_mdc(self) -> Self { self.set(FEATURE_FLAG_MDC) } /// Clears the MDC 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_mdc()); /// /// let f = f.clear_mdc(); /// assert!(! f.supports_mdc()); /// # Ok(()) } /// ``` pub fn clear_mdc(self) -> Self { self.clear(FEATURE_FLAG_MDC) } /// Returns whether the AEAD 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_aead()); /// # Ok(()) } /// ``` pub fn supports_aead(&self) -> bool { self.get(FEATURE_FLAG_AEAD) } /// Sets the AEAD feature flag. /// /// # Examples /// /// ``` /// use sequoia_openpgp as openpgp; /// # use openpgp::Result; /// use openpgp::types::Features; /// /// # fn main() -> Result<()> { /// let f = Features::empty().set_aead(); /// /// assert!(f.supports_aead()); /// # assert!(f.get(1)); /// # Ok(()) } /// ``` pub fn set_aead(self) -> Self { self.set(FEATURE_FLAG_AEAD) } /// Clears the AEAD feature flag. /// /// # Examples /// /// ``` /// use sequoia_openpgp as openpgp; /// # use openpgp::Result; /// use openpgp::types::Features; /// /// # fn main() -> Result<()> { /// let f = Features::new(&[0x2]); /// assert!(f.supports_aead()); /// /// let f = f.clear_aead(); /// assert!(! f.supports_aead()); /// # Ok(()) } /// ``` pub fn clear_aead(self) -> Self { self.clear(FEATURE_FLAG_AEAD) } } /// Modification Detection (packets 18 and 19). const FEATURE_FLAG_MDC: usize = 0; /// AEAD Encrypted Data Packet (packet 20) and version 5 Symmetric-Key /// Encrypted Session Key Packets (packet 3). const FEATURE_FLAG_AEAD: usize = 1; #[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 = Features::new(val.as_slice()); assert_eq!(val, q); assert!(val.normalized_eq(&q)); // Add some padding to q. Make sure they are still equal. q.0.raw.push(0); assert!(val != q); assert!(val.normalized_eq(&q)); q.0.raw.push(0); 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_mdc(); let b = Features::new(&[ 0x1 ]); assert_eq!(a, b); assert!(a.normalized_eq(&b)); let a = Features::empty().set_aead(); let b = Features::new(&[ 0x2 ]); assert_eq!(a, b); assert!(a.normalized_eq(&b)); let a = Features::empty().set_mdc().set_aead(); let b = Features::new(&[ 0x1 | 0x2 ]); assert_eq!(a, b); assert!(a.normalized_eq(&b)); } } ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-1.7.0/src/types/key_flags.rs��������������������������������������������������������0000644�0000000�0000000�00000030017�00726746425�0017243�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.21 of RFC 4880] and [Section 5.2.3.22 /// of RFC 4880bis]. /// /// [Section 5.2.3.21 of RFC 4880]: https://tools.ietf.org/html/rfc4880#section-5.2.3.21 /// [Section 5.2.3.22 of RFC 4880bis]: https://tools.ietf.org/html/draft-ietf-openpgp-rfc4880bis-09#section-5.2.3.22 /// /// # 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() { 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. let padding = self.0.padding_len(); if padding > 0 { 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_slice(); let r = rhs.as_slice(); 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_slice(); let r = rhs.as_slice(); // 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 slice containing the raw values. pub(crate) fn as_slice(&self) -> &[u8] { self.0.as_slice() } /// 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(self, bit: usize) -> Self { Self(self.0.set(bit)) } /// 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(self, bit: usize) -> Self { Self(self.0.clear(bit)) } /// This key may be used to certify other keys. pub fn for_certification(&self) -> bool { self.get(KEY_FLAG_CERTIFY) } /// 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) } /// This key may be used to sign data. pub fn for_signing(&self) -> bool { self.get(KEY_FLAG_SIGN) } /// 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) } /// This key may be used to encrypt communications. pub fn for_transport_encryption(&self) -> bool { self.get(KEY_FLAG_ENCRYPT_FOR_TRANSPORT) } /// 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) } /// This key may be used to encrypt storage. pub fn for_storage_encryption(&self) -> bool { self.get(KEY_FLAG_ENCRYPT_AT_REST) } /// 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) } /// This key may be used for authentication. pub fn for_authentication(&self) -> bool { self.get(KEY_FLAG_AUTHENTICATE) } /// 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) } /// 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) } /// 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) } /// 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) } /// 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) } /// Returns whether no flags are set. pub fn is_empty(&self) -> bool { self.as_slice().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 = KeyFlags::new(&val.as_slice()); assert_eq!(val, q); assert!(val.normalized_eq(&q)); // Add some padding to q. Make sure they are still equal. q.0.raw.push(0); assert!(val != q); assert!(val.normalized_eq(&q)); q.0.raw.push(0); assert!(val != q); assert!(val.normalized_eq(&q)); true } } } �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-1.7.0/src/types/mod.rs��������������������������������������������������������������0000644�0000000�0000000�00000166517�00726746425�0016075�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 imformation 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; use std::str::FromStr; use std::result; #[cfg(test)] use quickcheck::{Arbitrary, Gen}; use crate::Error; use crate::Result; mod bitfield; pub(crate) 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; pub(crate) trait Sendable : Send {} pub(crate) trait Syncable : Sync {} /// The OpenPGP public key algorithms as defined in [Section 9.1 of /// RFC 4880], and [Section 5 of RFC 6637]. /// /// Note: This enum cannot be exhaustively matched to allow future /// extensions. /// /// # 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().pk_algo(), PublicKeyAlgorithm::EdDSA); /// # Ok(()) } /// ``` /// /// [Section 9.1 of RFC 4880]: https://tools.ietf.org/html/rfc4880#section-9.1 /// [Section 5 of RFC 6637]: https://tools.ietf.org/html/rfc6637 #[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) ElGamalEncrypt, /// DSA (Digital Signature Algorithm) DSA, /// Elliptic curve DH ECDH, /// Elliptic curve DSA ECDSA, /// ElGamal (Encrypt or Sign), deprecated in RFC 4880. #[deprecated(note = "If you really must, use \ `PublicKeyAlgorithm::ElGamalEncrypt`.")] ElGamalEncryptSign, /// "Twisted" Edwards curve DSA EdDSA, /// Private algorithm identifier. Private(u8), /// Unknown algorithm identifier. Unknown(u8), } assert_send_and_sync!(PublicKeyAlgorithm); 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 | 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 | 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::ElGamalEncrypt.is_supported()); /// assert!(!PublicKeyAlgorithm::Private(101).is_supported()); /// ``` pub fn is_supported(&self) -> bool { self.is_supported_by_backend() } } 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, 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, Private(u) => u, Unknown(u) => u, } } } impl fmt::Display for PublicKeyAlgorithm { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { use crate::PublicKeyAlgorithm::*; #[allow(deprecated)] 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"), 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)), } } } #[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]).unwrap(); assert!(a.for_signing()); *a } } /// 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. /// /// Note: This enum cannot be exhaustively matched to allow future /// extensions. #[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)] pub enum Curve { /// NIST curve P-256. NistP256, /// NIST curve P-384. NistP384, /// NIST curve P-521. NistP521, /// brainpoolP256r1. BrainpoolP256, /// 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); 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. /// /// 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. /// /// Returns `None` for unknown curves. /// /// # Examples /// /// ```rust /// use sequoia_openpgp as openpgp; /// use openpgp::types::Curve; /// /// assert_eq!(Curve::NistP256.bits(), Some(256)); /// assert_eq!(Curve::NistP384.bits(), Some(384)); /// assert_eq!(Curve::Ed25519.bits(), Some(256)); /// assert_eq!(Curve::Unknown(Box::new([0x2B, 0x11])).bits(), None); /// ``` pub fn bits(&self) -> Option<usize> { use self::Curve::*; match self { NistP256 => Some(256), NistP384 => Some(384), NistP521 => Some(521), BrainpoolP256 => Some(256), BrainpoolP512 => Some(512), Ed25519 => Some(256), Cv25519 => Some(256), Unknown(_) => None, } } } impl fmt::Display for Curve { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { use self::Curve::*; 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"), 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: {:?})", 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_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]; #[allow(clippy::len_without_is_empty)] 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_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::BrainpoolP512 => BRAINPOOL_P512_OID, Curve::Ed25519 => ED25519_OID, Curve::Cv25519 => CV25519_OID, Curve::Unknown(ref oid) => oid, } } /// Returns the length of a coordinate in bits. /// /// # Examples /// /// ```rust /// use sequoia_openpgp as openpgp; /// use openpgp::types::Curve; /// /// assert!(if let Ok(256) = Curve::NistP256.len() { true } else { false }); /// assert!(if let Ok(384) = Curve::NistP384.len() { true } else { false }); /// assert!(if let Ok(256) = Curve::Ed25519.len() { true } else { false }); /// assert!(if let Err(_) = Curve::Unknown(Box::new([0x2B, 0x11])).len() { true } else { false }); /// ``` /// /// # Errors /// /// Returns `Error::UnsupportedEllipticCurve` if the curve is not /// supported. pub fn len(&self) -> Result<usize> { match self { Curve::NistP256 => Ok(256), Curve::NistP384 => Ok(384), Curve::NistP521 => Ok(521), Curve::BrainpoolP256 => Ok(256), Curve::BrainpoolP512 => Ok(512), Curve::Ed25519 => Ok(256), Curve::Cv25519 => Ok(256), Curve::Unknown(_) => Err(Error::UnsupportedEllipticCurve(self.clone()) .into()), } } /// 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 { self.is_supported_by_backend() } } #[cfg(test)] impl Arbitrary for Curve { fn arbitrary(g: &mut Gen) -> Self { match u8::arbitrary(g) % 8 { 0 => Curve::NistP256, 1 => Curve::NistP384, 2 => Curve::NistP521, 3 => Curve::BrainpoolP256, 4 => Curve::BrainpoolP512, 5 => Curve::Ed25519, 6 => Curve::Cv25519, 7 => Curve::Unknown({ let mut k = <Vec<u8>>::arbitrary(g); k.truncate(255); k.into_boxed_slice() }), _ => unreachable!(), } } } /// The symmetric-key algorithms as defined in [Section 9.2 of RFC 4880]. /// /// [Section 9.2 of RFC 4880]: https://tools.ietf.org/html/rfc4880#section-9.2 /// /// 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 /// /// Note: This enum cannot be exhaustively matched to allow future /// extensions. /// /// # 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. IDEA, /// 3-DES in EDE configuration. TripleDES, /// CAST5/CAST128 block cipher. 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); impl Default for SymmetricAlgorithm { fn default() -> Self { SymmetricAlgorithm::AES256 } } impl From<u8> for SymmetricAlgorithm { fn from(u: u8) -> Self { 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 { 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, } } } impl fmt::Display for SymmetricAlgorithm { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 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)), } } } #[cfg(test)] impl Arbitrary for SymmetricAlgorithm { fn arbitrary(g: &mut Gen) -> Self { u8::arbitrary(g).into() } } /// The AEAD algorithms as defined in [Section 9.6 of RFC 4880bis]. /// /// [Section 9.6 of RFC 4880bis]: https://tools.ietf.org/html/draft-ietf-openpgp-rfc4880bis-05#section-9.6 /// /// 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 /// /// Note: This enum cannot be exhaustively matched to allow future /// extensions. /// /// This feature is [experimental](super#experimental-features). /// /// # 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::{Features, HashAlgorithm, AEADAlgorithm, SignatureType}; /// /// # fn main() -> openpgp::Result<()> { /// let features = Features::empty().set_aead(); /// let mut builder = SignatureBuilder::new(SignatureType::DirectKey) /// .set_features(features)? /// .set_preferred_aead_algorithms(vec![ /// AEADAlgorithm::EAX, /// ])?; /// # Ok(()) } #[non_exhaustive] #[derive(Clone, Copy, Hash, PartialEq, Eq, Debug, PartialOrd, Ord)] pub enum AEADAlgorithm { /// EAX mode. EAX, /// OCB mode. OCB, /// Private algorithm identifier. Private(u8), /// Unknown algorithm identifier. Unknown(u8), } assert_send_and_sync!(AEADAlgorithm); impl AEADAlgorithm { /// Returns whether this algorithm is supported. /// /// # Examples /// /// ```rust /// use sequoia_openpgp as openpgp; /// use openpgp::types::AEADAlgorithm; /// /// assert!(AEADAlgorithm::EAX.is_supported()); /// /// assert!(!AEADAlgorithm::OCB.is_supported()); /// ``` pub fn is_supported(&self) -> bool { self.is_supported_by_backend() } } impl From<u8> for AEADAlgorithm { fn from(u: u8) -> Self { match u { 1 => AEADAlgorithm::EAX, 2 => AEADAlgorithm::OCB, 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::Private(u) => u, AEADAlgorithm::Unknown(u) => u, } } } impl fmt::Display for AEADAlgorithm { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match *self { AEADAlgorithm::EAX => f.write_str("EAX mode"), AEADAlgorithm::OCB => f.write_str("OCB 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)), } } } #[cfg(test)] impl Arbitrary for AEADAlgorithm { fn arbitrary(g: &mut Gen) -> Self { u8::arbitrary(g).into() } } /// The OpenPGP compression algorithms as defined in [Section 9.3 of RFC 4880]. /// /// [Section 9.3 of RFC 4880]: https://tools.ietf.org/html/rfc4880#section-9.3 /// /// Note: This enum cannot be exhaustively matched to allow future /// extensions. /// /// # 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.3 of RFC 4880] /// recommends that this algorithm should be implemented. /// /// [RFC 1951]: https://tools.ietf.org/html/rfc1951 /// [Section 9.3 of RFC 4880]: https://tools.ietf.org/html/rfc4880#section-9.3 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); 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::Zip.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, } } } 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 comppression algorithm {}", u)), } } } #[cfg(test)] impl Arbitrary for CompressionAlgorithm { fn arbitrary(g: &mut Gen) -> Self { u8::arbitrary(g).into() } } /// The OpenPGP hash algorithms as defined in [Section 9.4 of RFC 4880]. /// /// Note: This enum cannot be exhaustively matched to allow future /// extensions. /// /// # 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.4 of RFC 4880]: https://tools.ietf.org/html/rfc4880#section-9.4 #[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, /// Private hash algorithm identifier. Private(u8), /// Unknown hash algorithm identifier. Unknown(u8), } assert_send_and_sync!(HashAlgorithm); 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, 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::Private(u) => u, HashAlgorithm::Unknown(u) => u, } } } impl FromStr for HashAlgorithm { type Err = (); fn from_str(s: &str) -> result::Result<Self, ()> { 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 { Err(()) } } } 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::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.4 of RFC 4880] defines a textual representation of /// hash algorithms. This is used in cleartext signed messages /// (see [Section 7 of RFC 4880]). /// /// [Section 9.4 of RFC 4880]: https://tools.ietf.org/html/rfc4880#section-9.4 /// [Section 7 of RFC 4880]: https://tools.ietf.org/html/rfc4880#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::Private(_) => Err(Error::UnsupportedHashAlgorithm(*self).into()), HashAlgorithm::Unknown(_) => Err(Error::UnsupportedHashAlgorithm(*self).into()), } } } #[cfg(test)] impl Arbitrary for HashAlgorithm { fn arbitrary(g: &mut Gen) -> Self { u8::arbitrary(g).into() } } /// Signature type as defined in [Section 5.2.1 of RFC 4880]. /// /// [Section 5.2.1 of RFC 4880]: https://tools.ietf.org/html/rfc4880#section-5.2.1 /// /// Note: This enum cannot be exhaustively matched to allow future /// extensions. /// /// # 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, /// Attestation Key Signature (proposed). /// /// Allows the certificate owner to attest to third party /// certifications. See [Section 5.2.3.30 of RFC 4880bis] for /// details. /// /// [Section 5.2.3.30 of RFC 4880bis]: https://tools.ietf.org/html/draft-ietf-openpgp-rfc4880bis-10.html#section-5.2.3.30 AttestationKey, /// 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); 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::AttestationKey, 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::AttestationKey => 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::AttestationKey => f.write_str("AttestationKey"), 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() } } /// Describes the reason for a revocation. /// /// See the description of revocation subpackets [Section 5.2.3.23 of RFC 4880]. /// /// [Section 5.2.3.23 of RFC 4880]: https://tools.ietf.org/html/rfc4880#section-5.2.3.23 /// /// Note: This enum cannot be exhaustively matched to allow future /// extensions. /// /// # 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.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); 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 provence 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(None, 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())?; /// 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())?; /// 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, } } } /// Describes the format of the body of a literal data packet. /// /// See the description of literal data packets [Section 5.9 of RFC 4880]. /// /// [Section 5.9 of RFC 4880]: https://tools.ietf.org/html/rfc4880#section-5.9 /// /// Note: This enum cannot be exhaustively matched to allow future /// extensions. /// /// # 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::Text); /// 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. /// /// This is a hint that the content is probably text; the encoding /// is not specified. Text, /// Text data, probably valid UTF-8. /// /// This is a hint that the content is probably UTF-8 encoded. Unicode, /// MIME message. /// /// This is defined in [Section 5.10 of RFC4880bis]. /// /// [Section 5.10 of RFC4880bis]: https://tools.ietf.org/html/draft-ietf-openpgp-rfc4880bis-05#section-5.10 MIME, /// Unknown format specifier. Unknown(char), } assert_send_and_sync!(DataFormat); impl Default for DataFormat { fn default() -> Self { DataFormat::Binary } } impl From<u8> for DataFormat { fn from(u: u8) -> Self { (u as char).into() } } impl From<char> for DataFormat { fn from(c: char) -> Self { use self::DataFormat::*; match c { 'b' => Binary, 't' => Text, 'u' => Unicode, 'm' => MIME, c => Unknown(c), } } } impl From<DataFormat> for u8 { fn from(f: DataFormat) -> u8 { char::from(f) as u8 } } impl From<DataFormat> for char { fn from(f: DataFormat) -> char { use self::DataFormat::*; match f { Binary => 'b', Text => 't', Unicode => 'u', MIME => 'm', 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"), Text => f.write_str("Text data"), Unicode => f.write_str("Text data (UTF-8)"), MIME => f.write_str("MIME message body part"), 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() } } /// 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(None, 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 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 } } } 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 } } } 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 } } } quickcheck! { fn curve_roundtrip(curve: Curve) -> bool { curve == Curve::from_oid(curve.oid()) } } 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 hash_roundtrip(hash: HashAlgorithm) -> bool { let val: u8 = hash.into(); hash == HashAlgorithm::from(val) } } quickcheck! { fn hash_roundtrip_str(hash: HashAlgorithm) -> bool { 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 { 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 } } } 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' && u != 't' && u != 'u' && u != 'm', _ => true } } } } ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-1.7.0/src/types/revocation_key.rs���������������������������������������������������0000644�0000000�0000000�00000011604�00726746425�0020321�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.15 of RFC 4880]. /// /// [Section 5.2.3.15 of RFC 4880]: https://tools.ietf.org/html/rfc4880#section-5.2.3.15 /// /// 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(None, Some("alice@example.org")) /// .generate()?; /// /// // Make Alice a designated revoker for Bob. /// let (bob, _) = /// CertBuilder::general_purpose(None, 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(None) /// .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().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 or not the relation between revoker and /// revokee is of a sensitive nature. pub fn sensitive(&self) -> bool { self.sensitive } /// Sets whether or not 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-1.7.0/src/types/server_preferences.rs�����������������������������������������������0000644�0000000�0000000�00000022367�00726746425�0021177�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.17 of RFC 4880] and /// [Section 5.2.3.18 of RFC 4880bis]. /// /// [Section 5.2.3.17 of RFC 4880]: https://tools.ietf.org/html/rfc4880#section-5.2.3.17 /// [Section 5.2.3.18 of RFC 4880bis]: https://tools.ietf.org/html/draft-ietf-openpgp-rfc4880bis-09#section-5.2.3.18 /// /// 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(None, 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() { 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. let padding = self.0.padding_len(); if padding > 0 { 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 slice containing the raw values. pub(crate) fn as_slice(&self) -> &[u8] { self.0.as_slice() } /// 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(self, bit: usize) -> Self { Self(self.0.set(bit)) } /// 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(self, bit: usize) -> Self { Self(self.0.clear(bit)) } /// 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 = KeyServerPreferences::new(val.as_slice()); assert_eq!(val, q); assert!(val.normalized_eq(&q)); // Add some padding to q. Make sure they are still equal. q.0.raw.push(0); assert!(val != q); assert!(val.normalized_eq(&q)); q.0.raw.push(0); assert!(val != q); assert!(val.normalized_eq(&q)); true } } } �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-1.7.0/src/types/timestamp.rs��������������������������������������������������������0000644�0000000�0000000�00000067563�00726746425�0017322�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 4880]). /// /// They cannot express dates further than 7th February of 2106 or earlier than /// the [UNIX epoch]. Unlike Unix's `time_t`, OpenPGP's timestamp is unsigned so /// it rollsover 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(None, Some("alice@example.org")) /// .generate()?; /// /// let subkey = cert.keys().subkeys().next().unwrap(); /// let packets = subkey.bundle().self_signatures()[0].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 4880]: https://tools.ietf.org/html/rfc4880#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 exceeds u32 epoch: {:?}", 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-1.7.0/src/utils.rs������������������������������������������������������������������0000644�0000000�0000000�00000001003�00726746425�0015264�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-1.7.0/tests/data/armor/README�������������������������������������������������������0000644�0000000�0000000�00000001115�00726746425�0017046�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-1.7.0/tests/data/armor/literal-0-no-header-with-chksum.asc��������������������������0000644�0000000�0000000�00000000024�00726746425�0024546�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������ ywZiAAAAAAA= =JgGx ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-1.7.0/tests/data/armor/literal-0-no-header.asc��������������������������������������0000644�0000000�0000000�00000000016�00726746425�0022306�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������ ywZiAAAAAAA= ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-1.7.0/tests/data/armor/literal-0-no-newlines.asc������������������������������������0000644�0000000�0000000�00000000112�00726746425�0022677�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������-----BEGIN PGP MESSAGE----- ywZiAAAAAAA= =JgGx -----END PGP MESSAGE----- ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-1.7.0/tests/data/armor/literal-0.asc������������������������������������������������0000644�0000000�0000000�00000000112�00726746425�0020443�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������-----BEGIN PGP MESSAGE----- ywZiAAAAAAA= =JgGx -----END PGP MESSAGE----- ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-1.7.0/tests/data/armor/literal-0.bin������������������������������������������������0000644�0000000�0000000�00000000010�00726746425�0020442�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������b�����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-1.7.0/tests/data/armor/literal-1-no-header-with-chksum.asc��������������������������0000644�0000000�0000000�00000000024�00726746425�0024547�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������ ywdiAAAAAAAG =+GtG ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-1.7.0/tests/data/armor/literal-1-no-header.asc��������������������������������������0000644�0000000�0000000�00000000016�00726746425�0022307�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������ ywdiAAAAAAAG ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-1.7.0/tests/data/armor/literal-1-no-newlines.asc������������������������������������0000644�0000000�0000000�00000000112�00726746425�0022700�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������-----BEGIN PGP MESSAGE----- ywdiAAAAAAAG =+GtG -----END PGP MESSAGE----- ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-1.7.0/tests/data/armor/literal-1.asc������������������������������������������������0000644�0000000�0000000�00000000112�00726746425�0020444�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������-----BEGIN PGP MESSAGE----- ywdiAAAAAAAG =+GtG -----END PGP MESSAGE----- ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-1.7.0/tests/data/armor/literal-1.bin������������������������������������������������0000644�0000000�0000000�00000000011�00726746425�0020444�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������b����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-1.7.0/tests/data/armor/literal-2-no-header-with-chksum.asc��������������������������0000644�0000000�0000000�00000000030�00726746425�0024545�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������ ywhiAAAAAACbFA== =wkzG ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-1.7.0/tests/data/armor/literal-2-no-header.asc��������������������������������������0000644�0000000�0000000�00000000022�00726746425�0022305�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������ ywhiAAAAAACbFA== ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-1.7.0/tests/data/armor/literal-2-no-newlines.asc������������������������������������0000644�0000000�0000000�00000000116�00726746425�0022705�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������-----BEGIN PGP MESSAGE----- ywhiAAAAAACbFA== =wkzG -----END PGP MESSAGE----- ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-1.7.0/tests/data/armor/literal-2.asc������������������������������������������������0000644�0000000�0000000�00000000116�00726746425�0020451�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������-----BEGIN PGP MESSAGE----- ywhiAAAAAACbFA== =wkzG -----END PGP MESSAGE----- ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-1.7.0/tests/data/armor/literal-2.bin������������������������������������������������0000644�0000000�0000000�00000000012�00726746425�0020446�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������b���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-1.7.0/tests/data/armor/literal-3-no-header-with-chksum.asc��������������������������0000644�0000000�0000000�00000000030�00726746425�0024546�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������ ywliAAAAAACC7XQ= =EBpN ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-1.7.0/tests/data/armor/literal-3-no-header.asc��������������������������������������0000644�0000000�0000000�00000000022�00726746425�0022306�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������ ywliAAAAAACC7XQ= ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-1.7.0/tests/data/armor/literal-3-no-newlines.asc������������������������������������0000644�0000000�0000000�00000000116�00726746425�0022706�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������-----BEGIN PGP MESSAGE----- ywliAAAAAACC7XQ= =EBpN -----END PGP MESSAGE----- ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-1.7.0/tests/data/armor/literal-3.asc������������������������������������������������0000644�0000000�0000000�00000000116�00726746425�0020452�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������-----BEGIN PGP MESSAGE----- ywliAAAAAACC7XQ= =EBpN -----END PGP MESSAGE----- ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-1.7.0/tests/data/armor/literal-3.bin������������������������������������������������0000644�0000000�0000000�00000000013�00726746425�0020450�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������ b�����t���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-1.7.0/tests/data/armor/literal-47-no-header-with-chksum.asc�������������������������0000644�0000000�0000000�00000000125�00726746425�0024643�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������ yzViAAAAAAAu+RbYYqLiPxrbAE2b5G/9FLK/59YiRxzH2rVW60Uor4I9dPLill2d kS2Upbc15A== =jgHY �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-1.7.0/tests/data/armor/literal-47-no-header.asc�������������������������������������0000644�0000000�0000000�00000000117�00726746425�0022403�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������ yzViAAAAAAAu+RbYYqLiPxrbAE2b5G/9FLK/59YiRxzH2rVW60Uor4I9dPLill2d kS2Upbc15A== �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-1.7.0/tests/data/armor/literal-47-no-newlines.asc�����������������������������������0000644�0000000�0000000�00000000213�00726746425�0022774�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������-----BEGIN PGP MESSAGE----- yzViAAAAAAAu+RbYYqLiPxrbAE2b5G/9FLK/59YiRxzH2rVW60Uor4I9dPLill2d kS2Upbc15A== =jgHY -----END PGP MESSAGE----- �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-1.7.0/tests/data/armor/literal-47.asc�����������������������������������������������0000644�0000000�0000000�00000000213�00726746425�0020540�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������-----BEGIN PGP MESSAGE----- yzViAAAAAAAu+RbYYqLiPxrbAE2b5G/9FLK/59YiRxzH2rVW60Uor4I9dPLill2d kS2Upbc15A== =jgHY -----END PGP MESSAGE----- �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-1.7.0/tests/data/armor/literal-47.bin�����������������������������������������������0000644�0000000�0000000�00000000067�00726746425�0020551�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������5b�����.b?�Mo"GڵVE(=t]-5�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-1.7.0/tests/data/armor/literal-48-no-header-with-chksum.asc�������������������������0000644�0000000�0000000�00000000125�00726746425�0024644�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������ yzZiAAAAAAAB+RqN/31/ZIrDdjPkr0f0tmBIyR6up5Dbhqbqbc0bcSNq6sTGByhT KBToa+1N4BM= =py+D �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-1.7.0/tests/data/armor/literal-48-no-header.asc�������������������������������������0000644�0000000�0000000�00000000117�00726746425�0022404�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������ yzZiAAAAAAAB+RqN/31/ZIrDdjPkr0f0tmBIyR6up5Dbhqbqbc0bcSNq6sTGByhT KBToa+1N4BM= �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-1.7.0/tests/data/armor/literal-48-no-newlines.asc�����������������������������������0000644�0000000�0000000�00000000213�00726746425�0022775�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������-----BEGIN PGP MESSAGE----- yzZiAAAAAAAB+RqN/31/ZIrDdjPkr0f0tmBIyR6up5Dbhqbqbc0bcSNq6sTGByhT KBToa+1N4BM= =py+D -----END PGP MESSAGE----- �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-1.7.0/tests/data/armor/literal-48.asc�����������������������������������������������0000644�0000000�0000000�00000000213�00726746425�0020541�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������-----BEGIN PGP MESSAGE----- yzZiAAAAAAAB+RqN/31/ZIrDdjPkr0f0tmBIyR6up5Dbhqbqbc0bcSNq6sTGByhT KBToa+1N4BM= =py+D -----END PGP MESSAGE----- �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-1.7.0/tests/data/armor/literal-48.bin�����������������������������������������������0000644�0000000�0000000�00000000070�00726746425�0020544�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������6b�����}dv3G`Hۆmq#j(S(kM������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-1.7.0/tests/data/armor/literal-49-no-header-with-chksum.asc�������������������������0000644�0000000�0000000�00000000125�00726746425�0024645�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������ yzdiAAAAAAA13Wsm+LgnhFS3W4A1wbcLJVaznjMZjj5EXAQPG2c99zr6XiJ1KisS kJFNlVbsWkvL =PrpW �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-1.7.0/tests/data/armor/literal-49-no-header.asc�������������������������������������0000644�0000000�0000000�00000000117�00726746425�0022405�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������ yzdiAAAAAAA13Wsm+LgnhFS3W4A1wbcLJVaznjMZjj5EXAQPG2c99zr6XiJ1KisS kJFNlVbsWkvL �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-1.7.0/tests/data/armor/literal-49-no-newlines.asc�����������������������������������0000644�0000000�0000000�00000000213�00726746425�0022776�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������-----BEGIN PGP MESSAGE----- yzdiAAAAAAA13Wsm+LgnhFS3W4A1wbcLJVaznjMZjj5EXAQPG2c99zr6XiJ1KisS kJFNlVbsWkvL =PrpW -----END PGP MESSAGE----- �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-1.7.0/tests/data/armor/literal-49.asc�����������������������������������������������0000644�0000000�0000000�00000000213�00726746425�0020542�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������-----BEGIN PGP MESSAGE----- yzdiAAAAAAA13Wsm+LgnhFS3W4A1wbcLJVaznjMZjj5EXAQPG2c99zr6XiJ1KisS kJFNlVbsWkvL =PrpW -----END PGP MESSAGE----- �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-1.7.0/tests/data/armor/literal-49.bin�����������������������������������������������0000644�0000000�0000000�00000000071�00726746425�0020546�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������7b�����5k&'T[5 %V3>D\g=:^"u*+MVZK�����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-1.7.0/tests/data/armor/literal-50-no-header-with-chksum.asc�������������������������0000644�0000000�0000000�00000000131�00726746425�0024632�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������ yzhiAAAAAAA0SHlL3w6uPWhW1tZyUCn6sfzWbCmepdvPGK8QoxGrMpjfAYDIf2RX 2DstnrFcBeR3ow== =SZ+v ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-1.7.0/tests/data/armor/literal-50-no-header.asc�������������������������������������0000644�0000000�0000000�00000000123�00726746425�0022372�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������ yzhiAAAAAAA0SHlL3w6uPWhW1tZyUCn6sfzWbCmepdvPGK8QoxGrMpjfAYDIf2RX 2DstnrFcBeR3ow== ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-1.7.0/tests/data/armor/literal-50-no-newlines.asc�����������������������������������0000644�0000000�0000000�00000000217�00726746425�0022772�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������-----BEGIN PGP MESSAGE----- yzhiAAAAAAA0SHlL3w6uPWhW1tZyUCn6sfzWbCmepdvPGK8QoxGrMpjfAYDIf2RX 2DstnrFcBeR3ow== =SZ+v -----END PGP MESSAGE----- ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-1.7.0/tests/data/armor/literal-50.asc�����������������������������������������������0000644�0000000�0000000�00000000217�00726746425�0020536�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������-----BEGIN PGP MESSAGE----- yzhiAAAAAAA0SHlL3w6uPWhW1tZyUCn6sfzWbCmepdvPGK8QoxGrMpjfAYDIf2RX 2DstnrFcBeR3ow== =SZ+v -----END PGP MESSAGE----- ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-1.7.0/tests/data/armor/literal-50.bin�����������������������������������������������0000644�0000000�0000000�00000000072�00726746425�0020537�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������8b�����4HyK=hVrP)l)2dW;-\w����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-1.7.0/tests/data/armor/literal-51-no-header-with-chksum.asc�������������������������0000644�0000000�0000000�00000000131�00726746425�0024633�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������ yzliAAAAAADRPEBKydU3g0wATjZaEJUR4IEP/WsRKpBAQesOu8y8Q5o0lc/u0M9q ruzIG+/3/L6kTe0= =Mv+C ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-1.7.0/tests/data/armor/literal-51-no-header.asc�������������������������������������0000644�0000000�0000000�00000000123�00726746425�0022373�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������ yzliAAAAAADRPEBKydU3g0wATjZaEJUR4IEP/WsRKpBAQesOu8y8Q5o0lc/u0M9q ruzIG+/3/L6kTe0= ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-1.7.0/tests/data/armor/literal-51-no-newlines.asc�����������������������������������0000644�0000000�0000000�00000000217�00726746425�0022773�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������-----BEGIN PGP MESSAGE----- yzliAAAAAADRPEBKydU3g0wATjZaEJUR4IEP/WsRKpBAQesOu8y8Q5o0lc/u0M9q ruzIG+/3/L6kTe0= =Mv+C -----END PGP MESSAGE----- ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-1.7.0/tests/data/armor/literal-51.asc�����������������������������������������������0000644�0000000�0000000�00000000217�00726746425�0020537�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������-----BEGIN PGP MESSAGE----- yzliAAAAAADRPEBKydU3g0wATjZaEJUR4IEP/WsRKpBAQesOu8y8Q5o0lc/u0M9q ruzIG+/3/L6kTe0= =Mv+C -----END PGP MESSAGE----- ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-1.7.0/tests/data/armor/literal-51.bin�����������������������������������������������0000644�0000000�0000000�00000000073�00726746425�0020541�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������9b�����<@J7L�N6Zk*@A̼C4jM���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-1.7.0/tests/data/armor/test-0.asc���������������������������������������������������0000644�0000000�0000000�00000000107�00726746425�0017772�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������-----BEGIN PGP ARMORED FILE----- =twTO -----END PGP ARMORED FILE----- ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-1.7.0/tests/data/armor/test-0.bad-crc.asc�������������������������������������������0000644�0000000�0000000�00000000107�00726746425�0021264�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������-----BEGIN PGP ARMORED FILE----- =twTP -----END PGP ARMORED FILE----- ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-1.7.0/tests/data/armor/test-0.bin���������������������������������������������������0000644�0000000�0000000�00000000000�00726746425�0017764�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-1.7.0/tests/data/armor/test-1.asc���������������������������������������������������0000644�0000000�0000000�00000000114�00726746425�0017771�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������-----BEGIN PGP ARMORED FILE----- 3g== =NnH5 -----END PGP ARMORED FILE----- ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-1.7.0/tests/data/armor/test-1.bin���������������������������������������������������0000644�0000000�0000000�00000000001�00726746425�0017766�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-1.7.0/tests/data/armor/test-1.no-crc.asc��������������������������������������������0000644�0000000�0000000�00000000106�00726746425�0021152�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������-----BEGIN PGP ARMORED FILE----- 3g== -----END PGP ARMORED FILE----- ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-1.7.0/tests/data/armor/test-2.asc���������������������������������������������������0000644�0000000�0000000�00000000114�00726746425�0017772�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������-----BEGIN PGP ARMORED FILE----- 5to= =zwFF -----END PGP ARMORED FILE----- ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-1.7.0/tests/data/armor/test-2.bad-footer.asc����������������������������������������0000644�0000000�0000000�00000000117�00726746425�0022016�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������-----BEGIN PGP ARMORED FILE----- 5to= =zwFF -----END PGP ARMORED MESSAGE----- �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-1.7.0/tests/data/armor/test-2.bin���������������������������������������������������0000644�0000000�0000000�00000000002�00726746425�0017770�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-1.7.0/tests/data/armor/test-3.asc���������������������������������������������������0000644�0000000�0000000�00000000114�00726746425�0017773�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������-----BEGIN PGP ARMORED FILE----- NdWx =oSKZ -----END PGP ARMORED FILE----- ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-1.7.0/tests/data/armor/test-3.bin���������������������������������������������������0000644�0000000�0000000�00000000003�00726746425�0017772�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������5ձ�����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-1.7.0/tests/data/armor/test-3.no-dashes.asc�����������������������������������������0000644�0000000�0000000�00000000070�00726746425�0021654�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������BEGIN PGP ARMORED FILE NdWx =oSKZ END PGP ARMORED FILE ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-1.7.0/tests/data/armor/test-3.unbalanced-dashes.asc���������������������������������0000644�0000000�0000000�00000000107�00726746425�0023335�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������-------BEGIN PGP ARMORED FILE- NdWx =oSKZ -END PGP ARMORED FILE------ ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-1.7.0/tests/data/armor/test-3.unicode-dashes.asc������������������������������������0000644�0000000�0000000�00000000162�00726746425�0022670�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������֊־᐀᠆‐BEGIN PGP ARMORED FILE‑‒–—― NdWx =oSKZ ⸗⸚⸺⸻⹀END PGP ARMORED FILE〜〰゠﹘﹣ ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-1.7.0/tests/data/armor/test-3.with-headers-quoted-a-lot.asc�������������������������0000644�0000000�0000000�00000000256�00726746425�0024676�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������] } >>> -----BEGIN PGP ARMORED FILE----- ] } >>> Comment: Some Header ] } >>> Comment: Another one ] } >>> ] } >>> NdWx ] } >>> =oSKZ ] } >>> -----END PGP ARMORED FILE----- ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-1.7.0/tests/data/armor/test-3.with-headers-quoted-badly.asc�������������������������0000644�0000000�0000000�00000000212�00726746425�0024745�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������>> -----BEGIN PGP ARMORED FILE----- >> Comment: Some Header >> Comment: Another one >> > NdWx >> =oSKZ >> -----END PGP ARMORED FILE----- ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-1.7.0/tests/data/armor/test-3.with-headers-quoted-stripped.asc����������������������0000644�0000000�0000000�00000000203�00726746425�0025504�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������> -----BEGIN PGP ARMORED FILE----- > Comment: Some Header > Comment: Another one > > NdWx > =oSKZ > -----END PGP ARMORED FILE----- ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-1.7.0/tests/data/armor/test-3.with-headers-quoted.asc�������������������������������0000644�0000000�0000000�00000000204�00726746425�0023655�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������> -----BEGIN PGP ARMORED FILE----- > Comment: Some Header > Comment: Another one > > NdWx > =oSKZ > -----END PGP ARMORED FILE----- ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-1.7.0/tests/data/armor/test-3.with-headers.asc��������������������������������������0000644�0000000�0000000�00000000166�00726746425�0022365�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������-----BEGIN PGP ARMORED FILE----- Comment: Some Header Comment: Another one NdWx =oSKZ -----END PGP ARMORED FILE----- ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-1.7.0/tests/data/armor/test-47.asc��������������������������������������������������0000644�0000000�0000000�00000000210�00726746425�0020060�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������-----BEGIN PGP ARMORED FILE----- VLC0mzt0PHN3ltTJyAerW70C+qwT7xu3vWD0+hteywQZ5fNVhx6OuOBWZgvrB4Y= =F7up -----END PGP ARMORED FILE----- ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-1.7.0/tests/data/armor/test-47.bin��������������������������������������������������0000644�0000000�0000000�00000000057�00726746425�0020073�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������T;t<sw[`^UVf ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-1.7.0/tests/data/armor/test-48.asc��������������������������������������������������0000644�0000000�0000000�00000000210�00726746425�0020061�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������-----BEGIN PGP ARMORED FILE----- d2g7QIIm+Qlt2/MjQGqSP6rjq6Q3UV72TKfx1pv7weHfatKMpHlt2A8EIwz9K/dM =vZCz -----END PGP ARMORED FILE----- ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-1.7.0/tests/data/armor/test-48.bin��������������������������������������������������0000644�0000000�0000000�00000000060�00726746425�0020066�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������wh;@& m#@j?㫤7Q^L֛jҌym# +L��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-1.7.0/tests/data/armor/test-49.asc��������������������������������������������������0000644�0000000�0000000�00000000215�00726746425�0020067�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������-----BEGIN PGP ARMORED FILE----- p8wnk/jjQRUkq9wWI6ZUPx1iQ51HiItFN/aJSfOGcHH9s4freOWCm9iYadHE4sns Ow== =s29R -----END PGP ARMORED FILE----- �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-1.7.0/tests/data/armor/test-49.bin��������������������������������������������������0000644�0000000�0000000�00000000061�00726746425�0020070�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������'A$#T?bCGE7Ipqx傛ؘi;�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-1.7.0/tests/data/armor/test-50.asc��������������������������������������������������0000644�0000000�0000000�00000000215�00726746425�0020057�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������-----BEGIN PGP ARMORED FILE----- oTSs55iwdhccW8tp2VOlAnxgeopNJaHojjVDgs/jhTJYTm5sz9fiOW20VsPyZ3Kv JPw= =YITk -----END PGP ARMORED FILE----- �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-1.7.0/tests/data/armor/test-50.bin��������������������������������������������������0000644�0000000�0000000�00000000062�00726746425�0020061�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������4瘰v[iS|`zM%5C2XNnl9mVgr$������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-1.7.0/tests/data/armor/test-51.asc��������������������������������������������������0000644�0000000�0000000�00000000215�00726746425�0020060�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������-----BEGIN PGP ARMORED FILE----- PIDR4EXgeoMOwwTtbKTwBGMPVeTiQnJi9V0t686F1VhgNYrY14HoFLQEQt6jHLbN Eyon =yNnc -----END PGP ARMORED FILE----- �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-1.7.0/tests/data/armor/test-51.bin��������������������������������������������������0000644�0000000�0000000�00000000063�00726746425�0020063�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������<EzlcUBrb]-΅X`5ׁBޣ*'�����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-1.7.0/tests/data/contrib/gnupg/keys/alpha.pgp���������������������������������������0000644�0000000�0000000�00000002150�00726746425�0022376�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-1.7.0/tests/data/contrib/gnupg/timestamp-signature-by-alice.asc���������������������0000644�0000000�0000000�00000001302�00726746425�0026001�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-1.7.0/tests/data/contrib/pep/pEpkey-netpgp.asc��������������������������������������0000644�0000000�0000000�00000003242�00726746425�0022515�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-1.7.0/tests/data/edge-cases/malformed-embedded-sig.pgp������������������������������0000644�0000000�0000000�00000001202�00726746425�0024030�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-1.7.0/tests/data/edge-cases/malformed-notation-name.pgp�����������������������������0000644�0000000�0000000�00000000612�00726746425�0024274�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-1.7.0/tests/data/keys/1pa3pc-dkgpg.pgp����������������������������������������������0000644�0000000�0000000�00000004137�00726746425�0020721�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-1.7.0/tests/data/keys/about-to-expire-private.pgp�����������������������������������0000644�0000000�0000000�00000000375�00726746425�0023234�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-1.7.0/tests/data/keys/about-to-expire.expired.pgp�����������������������������������0000644�0000000�0000000�00000000336�00726746425�0023220�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-1.7.0/tests/data/keys/about-to-expire.update-no-uid.pgp�����������������������������0000644�0000000�0000000�00000000307�00726746425�0024231�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-1.7.0/tests/data/keys/already-revoked-direct-revocation.pgp�������������������������0000644�0000000�0000000�00000002772�00726746425�0025240�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-1.7.0/tests/data/keys/already-revoked-private.pgp�����������������������������������0000644�0000000�0000000�00000004727�00726746425�0023273�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-1.7.0/tests/data/keys/already-revoked-subkey-revocation.pgp�������������������������0000644�0000000�0000000�00000002772�00726746425�0025270�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-1.7.0/tests/data/keys/already-revoked-userid-revocation.pgp�������������������������0000644�0000000�0000000�00000002772�00726746425�0025261�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-1.7.0/tests/data/keys/already-revoked.pgp�������������������������������������������0000644�0000000�0000000�00000002301�00726746425�0021605�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-1.7.0/tests/data/keys/already-revoked.rev�������������������������������������������0000644�0000000�0000000�00000001044�00726746425�0021616�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-1.7.0/tests/data/keys/bad-subkey-keyring.pgp����������������������������������������0000644�0000000�0000000�00000043220�00726746425�0022230�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-1.7.0/tests/data/keys/bannon-add-subkey-1-private.gpg�������������������������������0000644�0000000�0000000�00000007763�00726746425�0023646�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-1.7.0/tests/data/keys/bannon-add-subkey-1.gpg���������������������������������������0000644�0000000�0000000�00000004122�00726746425�0022160�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-1.7.0/tests/data/keys/bannon-add-subkey-2-private.gpg�������������������������������0000644�0000000�0000000�00000007275�00726746425�0023645�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-1.7.0/tests/data/keys/bannon-add-subkey-2.gpg���������������������������������������0000644�0000000�0000000�00000003434�00726746425�0022166�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-1.7.0/tests/data/keys/bannon-add-subkey-3-private.gpg�������������������������������0000644�0000000�0000000�00000012163�00726746425�0023636�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-1.7.0/tests/data/keys/bannon-add-subkey-3.gpg���������������������������������������0000644�0000000�0000000�00000005122�00726746425�0022163�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-1.7.0/tests/data/keys/bannon-add-uid-1-whitehouse.gov.gpg���������������������������0000644�0000000�0000000�00000003117�00726746425�0024416�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-1.7.0/tests/data/keys/bannon-add-uid-2-fox.com.gpg����������������������������������0000644�0000000�0000000�00000003110�00726746425�0023003�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-1.7.0/tests/data/keys/bannon-add-uid-3-whitehouse.gov-dup.gpg�����������������������0000644�0000000�0000000�00000003117�00726746425�0025206�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-1.7.0/tests/data/keys/bannon-all-subkeys.gpg����������������������������������������0000644�0000000�0000000�00000010032�00726746425�0022222�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-1.7.0/tests/data/keys/bannon-all-uids-subkeys.gpg�����������������������������������0000644�0000000�0000000�00000012142�00726746425�0023170�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-1.7.0/tests/data/keys/bannon-all-uids.gpg�������������������������������������������0000644�0000000�0000000�00000004433�00726746425�0021511�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-1.7.0/tests/data/keys/bannon-base-private.gpg���������������������������������������0000644�0000000�0000000�00000004751�00726746425�0022364�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-1.7.0/tests/data/keys/bannon-base.gpg�����������������������������������������������0000644�0000000�0000000�00000002323�00726746425�0020705�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-1.7.0/tests/data/keys/bannon-ivanka-signs-all-uids.gpg������������������������������0000644�0000000�0000000�00000006275�00726746425�0024107�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-1.7.0/tests/data/keys/bannon-ivanka-signs-base.gpg����������������������������������0000644�0000000�0000000�00000003011�00726746425�0023270�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-1.7.0/tests/data/keys/bannon-the-donald-signs-all-uids.gpg��������������������������0000644�0000000�0000000�00000006275�00726746425�0024655�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-1.7.0/tests/data/keys/bannon-the-donald-signs-base.gpg������������������������������0000644�0000000�0000000�00000003011�00726746425�0024036�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-1.7.0/tests/data/keys/bobs-cert-badly-mangled.asc�����������������������������������0000644�0000000�0000000�00000004657�00726746425�0023105�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-1.7.0/tests/data/keys/certificate-with-unsigned-components.asc����������������������0000644�0000000�0000000�00000024360�00726746425�0025750�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-1.7.0/tests/data/keys/corrupted.pgp�������������������������������������������������0000644�0000000�0000000�00000153031�00726746425�0020545�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-1.7.0/tests/data/keys/dennis-simon-anton-private.pgp��������������������������������0000644�0000000�0000000�00000001774�00726746425�0023734�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-1.7.0/tests/data/keys/dennis-simon-anton.pgp����������������������������������������0000644�0000000�0000000�00000001727�00726746425�0022262�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-1.7.0/tests/data/keys/different-preferences.asc�������������������������������������0000644�0000000�0000000�00000006103�00726746425�0022760�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-1.7.0/tests/data/keys/dkg-sigs-out-of-order.pgp�������������������������������������0000644�0000000�0000000�00000065640�00726746425�0022576�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-1.7.0/tests/data/keys/dkg.gpg�������������������������������������������������������0000644�0000000�0000000�00002437333�00726746425�0017306�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-1.7.0/tests/data/keys/dsa2048-elgamal3072-private.pgp�������������������������������0000644�0000000�0000000�00000003705�00726746425�0023211�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-1.7.0/tests/data/keys/dsa2048-elgamal3072.pgp���������������������������������������0000644�0000000�0000000�00000003550�00726746425�0021537�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-1.7.0/tests/data/keys/eike-v3-v4.pgp������������������������������������������������0000644�0000000�0000000�00000001610�00726746425�0020323�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-1.7.0/tests/data/keys/emmelie-dorothea-dina-samantha-awina-ed25519-private.pgp������0000644�0000000�0000000�00000001145�00726746425�0030220�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-1.7.0/tests/data/keys/emmelie-dorothea-dina-samantha-awina-ed25519.pgp��������������0000644�0000000�0000000�00000001033�00726746425�0026544�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-1.7.0/tests/data/keys/erika-corinna-daniela-simone-antonia-nistp256-private.pgp�����0000644�0000000�0000000�00000000466�00726746425�0030635�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-1.7.0/tests/data/keys/erika-corinna-daniela-simone-antonia-nistp256.pgp�������������0000644�0000000�0000000�00000000421�00726746425�0027154�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-1.7.0/tests/data/keys/erika-corinna-daniela-simone-antonia-nistp384-private.pgp�����0000644�0000000�0000000�00000000610�00726746425�0030626�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-1.7.0/tests/data/keys/erika-corinna-daniela-simone-antonia-nistp384.pgp�������������0000644�0000000�0000000�00000000523�00726746425�0027161�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-1.7.0/tests/data/keys/erika-corinna-daniela-simone-antonia-nistp521-private.pgp�����0000644�0000000�0000000�00000000740�00726746425�0030623�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-1.7.0/tests/data/keys/erika-corinna-daniela-simone-antonia-nistp521.pgp�������������0000644�0000000�0000000�00000000631�00726746425�0027152�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-1.7.0/tests/data/keys/hash-algos/MD5-private.gpg������������������������������������0000644�0000000�0000000�00000007112�00726746425�0022606�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-1.7.0/tests/data/keys/hash-algos/MD5.gpg��������������������������������������������0000644�0000000�0000000�00000003265�00726746425�0021143�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-1.7.0/tests/data/keys/hash-algos/README���������������������������������������������0000644�0000000�0000000�00000000344�00726746425�0020732�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-1.7.0/tests/data/keys/hash-algos/RipeMD160-private.gpg������������������������������0000644�0000000�0000000�00000007121�00726746425�0023570�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-1.7.0/tests/data/keys/hash-algos/RipeMD160.gpg��������������������������������������0000644�0000000�0000000�00000003273�00726746425�0022124�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-1.7.0/tests/data/keys/hash-algos/SHA1-private.gpg�����������������������������������0000644�0000000�0000000�00000007114�00726746425�0022717�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-1.7.0/tests/data/keys/hash-algos/SHA1.gpg�������������������������������������������0000644�0000000�0000000�00000003266�00726746425�0021253�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-1.7.0/tests/data/keys/hash-algos/SHA224-private.gpg���������������������������������0000644�0000000�0000000�00000004716�00726746425�0023073�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-1.7.0/tests/data/keys/hash-algos/SHA224.gpg�����������������������������������������0000644�0000000�0000000�00000002270�00726746425�0021414�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-1.7.0/tests/data/keys/hash-algos/SHA256-private.gpg���������������������������������0000644�0000000�0000000�00000004716�00726746425�0023100�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-1.7.0/tests/data/keys/hash-algos/SHA256.gpg�����������������������������������������0000644�0000000�0000000�00000002270�00726746425�0021421�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-1.7.0/tests/data/keys/hash-algos/SHA384-private.gpg���������������������������������0000644�0000000�0000000�00000004716�00726746425�0023102�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-1.7.0/tests/data/keys/hash-algos/SHA384.gpg�����������������������������������������0000644�0000000�0000000�00000002270�00726746425�0021423�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-1.7.0/tests/data/keys/hash-algos/SHA512-private.gpg���������������������������������0000644�0000000�0000000�00000004716�00726746425�0023073�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-1.7.0/tests/data/keys/hash-algos/SHA512.gpg�����������������������������������������0000644�0000000�0000000�00000002270�00726746425�0021414�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-1.7.0/tests/data/keys/issue-215-expiration-on-direct-key-sig.pgp��������������������0000644�0000000�0000000�00000001037�00726746425�0025601�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-1.7.0/tests/data/keys/ivanka-private.gpg��������������������������������������������0000644�0000000�0000000�00000004746�00726746425�0021456�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-1.7.0/tests/data/keys/lutz.gpg������������������������������������������������������0000644�0000000�0000000�00000026712�00726746425�0017530�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-1.7.0/tests/data/keys/neal-sigs-out-of-order.pgp������������������������������������0000644�0000000�0000000�00000020372�00726746425�0022741�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-1.7.0/tests/data/keys/neal.pgp������������������������������������������������������0000644�0000000�0000000�00000062215�00726746425�0017460�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-1.7.0/tests/data/keys/peter-expired-backsig.pgp�������������������������������������0000644�0000000�0000000�00000004137�00726746425�0022716�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-1.7.0/tests/data/keys/peter-sha1-backsig.pgp����������������������������������������0000644�0000000�0000000�00000004131�00726746425�0022104�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-1.7.0/tests/data/keys/primary-key-0-public.pgp��������������������������������������0000644�0000000�0000000�00000003261�00726746425�0022417�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-1.7.0/tests/data/keys/primary-key-0-secret.pgp��������������������������������������0000644�0000000�0000000�00000007107�00726746425�0022431�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-1.7.0/tests/data/keys/primary-key-1-add-userid-bbbbb.pgp����������������������������0000644�0000000�0000000�00000004211�00726746425�0024206�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-1.7.0/tests/data/keys/primary-key-2-make-aaaaa-primary.pgp��������������������������0000644�0000000�0000000�00000004214�00726746425�0024562�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-1.7.0/tests/data/keys/primary-key-3-make-bbbbb-new-self-sig.pgp���������������������0000644�0000000�0000000�00000004214�00726746425�0025405�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-1.7.0/tests/data/keys/primary-key-4-make-bbbbb-primary.pgp��������������������������0000644�0000000�0000000�00000006056�00726746425�0024577�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-1.7.0/tests/data/keys/primary-key-5-make-aaaaa-self-sig.pgp�������������������������0000644�0000000�0000000�00000006056�00726746425�0024621�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-1.7.0/tests/data/keys/primary-key-6-revoked-aaaaa.pgp�������������������������������0000644�0000000�0000000�00000006747�00726746425�0023644�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-1.7.0/tests/data/keys/primary-key-is-also-subkey.pgp��������������������������������0000644�0000000�0000000�00000003210�00726746425�0023645�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-1.7.0/tests/data/keys/public-key.gpg������������������������������������������������0000644�0000000�0000000�00000061074�00726746425�0020576�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-1.7.0/tests/data/keys/really-revoked-cert-0-public.pgp������������������������������0000644�0000000�0000000�00000003327�00726746425�0024031�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-1.7.0/tests/data/keys/really-revoked-cert-0-secret.pgp������������������������������0000644�0000000�0000000�00000007155�00726746425�0024043�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-1.7.0/tests/data/keys/really-revoked-cert-1-soft-revocation.pgp���������������������0000644�0000000�0000000�00000004257�00726746425�0025701�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-1.7.0/tests/data/keys/really-revoked-cert-2-new-self-sig.pgp������������������������0000644�0000000�0000000�00000003327�00726746425�0025055�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-1.7.0/tests/data/keys/really-revoked-cert-3-hard-revocation.pgp���������������������0000644�0000000�0000000�00000004234�00726746425�0025641�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-1.7.0/tests/data/keys/really-revoked-cert-4-new-self-sig.pgp������������������������0000644�0000000�0000000�00000003327�00726746425�0025057�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-1.7.0/tests/data/keys/really-revoked-subkey-0-public.pgp����������������������������0000644�0000000�0000000�00000003276�00726746425�0024401�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-1.7.0/tests/data/keys/really-revoked-subkey-0-secret.pgp����������������������������0000644�0000000�0000000�00000007124�00726746425�0024404�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-1.7.0/tests/data/keys/really-revoked-subkey-1-soft-revocation.pgp�������������������0000644�0000000�0000000�00000004207�00726746425�0026241�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-1.7.0/tests/data/keys/really-revoked-subkey-2-new-self-sig.pgp����������������������0000644�0000000�0000000�00000003276�00726746425�0025425�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-1.7.0/tests/data/keys/really-revoked-subkey-3-hard-revocation.pgp�������������������0000644�0000000�0000000�00000004206�00726746425�0026205�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-1.7.0/tests/data/keys/really-revoked-subkey-4-new-self-sig.pgp����������������������0000644�0000000�0000000�00000003276�00726746425�0025427�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-1.7.0/tests/data/keys/really-revoked-user-attribute-0-public.pgp��������������������0000644�0000000�0000000�00000005313�00726746425�0026050�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-1.7.0/tests/data/keys/really-revoked-user-attribute-0-secret.pgp��������������������0000644�0000000�0000000�00000011141�00726746425�0026053�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-1.7.0/tests/data/keys/really-revoked-user-attribute-1-soft-revocation.pgp�����������0000644�0000000�0000000�00000006223�00726746425�0027716�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-1.7.0/tests/data/keys/really-revoked-user-attribute-2-new-self-sig.pgp��������������0000644�0000000�0000000�00000010225�00726746425�0027072�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-1.7.0/tests/data/keys/really-revoked-user-attribute-3-hard-revocation.pgp�����������0000644�0000000�0000000�00000010054�00726746425�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�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-1.7.0/tests/data/keys/really-revoked-user-attribute-4-new-self-sig.pgp��������������0000644�0000000�0000000�00000012056�00726746425�0027100�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-1.7.0/tests/data/keys/really-revoked-userid-0-public.pgp����������������������������0000644�0000000�0000000�00000004217�00726746425�0024366�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-1.7.0/tests/data/keys/really-revoked-userid-0-secret.pgp����������������������������0000644�0000000�0000000�00000010045�00726746425�0024371�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-1.7.0/tests/data/keys/really-revoked-userid-1-soft-revocation.pgp�������������������0000644�0000000�0000000�00000005130�00726746425�0026226�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-1.7.0/tests/data/keys/really-revoked-userid-2-new-self-sig.pgp����������������������0000644�0000000�0000000�00000004217�00726746425�0025412�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-1.7.0/tests/data/keys/really-revoked-userid-3-hard-revocation.pgp�������������������0000644�0000000�0000000�00000005130�00726746425�0026173�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-1.7.0/tests/data/keys/really-revoked-userid-4-new-self-sig.pgp����������������������0000644�0000000�0000000�00000004217�00726746425�0025414�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-1.7.0/tests/data/keys/recipient.pgp�������������������������������������������������0000644�0000000�0000000�00000003622�00726746425�0020520�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-1.7.0/tests/data/keys/sender.pgp����������������������������������������������������0000644�0000000�0000000�00000003613�00726746425�0020016�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-1.7.0/tests/data/keys/sha-mbles.alice.asc�������������������������������������������0000644�0000000�0000000�00000006533�00726746425�0021451�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-1.7.0/tests/data/keys/sha-mbles.bob.asc���������������������������������������������0000644�0000000�0000000�00000011052�00726746425�0021126�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-1.7.0/tests/data/keys/subpackets/marven-private.gpg���������������������������������0000644�0000000�0000000�00000012663�00726746425�0023636�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-1.7.0/tests/data/keys/subpackets/marven.gpg�����������������������������������������0000644�0000000�0000000�00000007553�00726746425�0022170�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-1.7.0/tests/data/keys/subpackets/shaw-private.gpg�����������������������������������0000644�0000000�0000000�00000005005�00726746425�0023300�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-1.7.0/tests/data/keys/subpackets/shaw.gpg�������������������������������������������0000644�0000000�0000000�00000003050�00726746425�0021626�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-1.7.0/tests/data/keys/test1-certification-key.pgp�����������������������������������0000644�0000000�0000000�00000002303�00726746425�0023200�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-1.7.0/tests/data/keys/test2-signed-by-test1.pgp�������������������������������������0000644�0000000�0000000�00000002771�00726746425�0022520�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-1.7.0/tests/data/keys/testy-broken-no-pk.pgp����������������������������������������0000644�0000000�0000000�00000001706�00726746425�0022207�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-1.7.0/tests/data/keys/testy-broken-no-sig-on-subkey.pgp�����������������������������0000644�0000000�0000000�00000001635�00726746425�0024272�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-1.7.0/tests/data/keys/testy-broken-no-uid.pgp���������������������������������������0000644�0000000�0000000�00000001531�00726746425�0022352�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-1.7.0/tests/data/keys/testy-new-encrypted-with-123.pgp������������������������������0000644�0000000�0000000�00000005132�00726746425�0023742�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-1.7.0/tests/data/keys/testy-new-private.pgp�����������������������������������������0000644�0000000�0000000�00000000770�00726746425�0022146�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-1.7.0/tests/data/keys/testy-new-with-sig.pgp����������������������������������������0000644�0000000�0000000�00000001344�00726746425�0022225�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-1.7.0/tests/data/keys/testy-new.pgp�������������������������������������������������0000644�0000000�0000000�00000000656�00726746425�0020501�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-1.7.0/tests/data/keys/testy-nistp256-private.pgp������������������������������������0000644�0000000�0000000�00000001033�00726746425�0022740�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-1.7.0/tests/data/keys/testy-nistp384-private.pgp������������������������������������0000644�0000000�0000000�00000001265�00726746425�0022751�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-1.7.0/tests/data/keys/testy-nistp521-private.pgp������������������������������������0000644�0000000�0000000�00000001547�00726746425�0022745�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-1.7.0/tests/data/keys/testy-no-subkey.pgp�������������������������������������������0000644�0000000�0000000�00000001215�00726746425�0021614�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-1.7.0/tests/data/keys/testy-only-a-pk.pgp�������������������������������������������0000644�0000000�0000000�00000000420�00726746425�0021504�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-1.7.0/tests/data/keys/testy-private.pgp���������������������������������������������0000644�0000000�0000000�00000004754�00726746425�0021365�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-1.7.0/tests/data/keys/testy.asc�����������������������������������������������������0000644�0000000�0000000�00000003335�00726746425�0017667�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-1.7.0/tests/data/keys/testy.pgp�����������������������������������������������������0000644�0000000�0000000�00000002326�00726746425�0017706�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-1.7.0/tests/data/keys/the-donald-private.gpg����������������������������������������0000644�0000000�0000000�00000004745�00726746425�0022223�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-1.7.0/tests/data/keys/un-revoked-userid.pgp�����������������������������������������0000644�0000000�0000000�00000004275�00726746425�0022113�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-1.7.0/tests/data/keys/yuge-key-so-yuge-the-yugest.asc�������������������������������0000644�0000000�0000000�00000256266�00726746425�0023755�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-1.7.0/tests/data/messages/a-cypherpunks-manifesto.txt�������������������������������0000644�0000000�0000000�00000012046�00726746425�0024177�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-1.7.0/tests/data/messages/a-cypherpunks-manifesto.txt.cleartext.sig�����������������0000644�0000000�0000000�00000012473�00726746425�0026756�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-1.7.0/tests/data/messages/a-cypherpunks-manifesto.txt.ed25519.sig�������������������0000644�0000000�0000000�00000000344�00726746425�0025753�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������-----BEGIN PGP SIGNATURE----- wnUEABYKACcFgltzBDIWoQSOjDP6RiYzeXbZeXgGnAw0jdgsGQmQBpwMNI3YLBkA AAllAP9fLXh78uB2DBLaVtLxaQYKPLH4bWncaMOv3qhChi89+AEA4UWrbOMn9oIh IlrFPgnupgnajQl5xbq/rDG6ibv4iA4= =DpCD -----END PGP SIGNATURE----- ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-1.7.0/tests/data/messages/a-cypherpunks-manifesto.txt.ed25519.sig.duplicated��������0000644�0000000�0000000�00000000606�00726746425�0030071�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-1.7.0/tests/data/messages/a-cypherpunks-manifesto.txt.ed25519.sig.two-keys����������0000644�0000000�0000000�00000000602�00726746425�0027531�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-1.7.0/tests/data/messages/a-problematic-poem.txt������������������������������������0000644�0000000�0000000�00000000362�00726746425�0023076�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-1.7.0/tests/data/messages/a-problematic-poem.txt.cleartext.sig����������������������0000644�0000000�0000000�00000001015�00726746425�0025645�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-1.7.0/tests/data/messages/aed/msg-aes128-eax-chunk-size-134217728-password-123.pgp��0000644�0000000�0000000�00000012302�00726746425�0027701�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������>W\Zo{jnDz9ѭ#K=ѹp)bD sr20N~/u@Nk[ϴ@IH~ꋭX,P\&%x+r5Dck%3'YqՂN )V2*G=#Z'UlXN+}x"A*7{5}1oQkEȜື̭K:R`ZlčW�O&cvl 8x@:pgY} B`rKcRC/Uc%{IY\f_}AY:P?CoW mX bKU;4*<n2"јrgR$+Pj kAey5g46!0V /n1G#lA@lO/,f=$bAw3εB? ͳ{܊<M>#=Vl<>E i >oAjK It]Dzθ?@]RKmgV+|26-ԧt,ҳ#V6^x`81D3 Og iAy&SfSPkL}j\QOI45,jCs[hsh("PS6YE0Df_I^\֔x+ܪiO<a=oooZhzy.$<G`ɂ;pf=5AIPT\i !E\t4S^ ?6L8Ye3B$hS|8RK8}Zo_j]82,GOegTž;=~7IL+I=Zt7,_@Z!W"l7ly#,Lղ"NV8bqMͪ@@{m=룸m>0F_3p#1]4%CZN(#)G]cr_" 5Bi~ytP }%<?*p6}lmVm=_l-rrV"x gJ gPM`9u%)0J Zcz�/%'g ҿP}AI)>xgͮ,/jP(�1]Pn-\iflmE )4ʵy/-n>0ֻxb4g.,ƒy}cpV+gGa*<KPNo4_[t.(Z!gz9h^y n>Kfb8JnK6ރݥZ8"'K6U%WF\<rKn$rF Q]͔m7GUE^/Nmu;-ͱq ׈q.rqDkEN %QF&؜4Ǻ1h9!\yt߂]IE_ kj%<޿aUܱ'nqg. ?$pJRL{۟6<iV]",m/Zv)ePMZ.}&֎3Qj(nDhڍ4ppQYkdy.IyLx,G9"_{L@QS$:W/$+n�CskQϖҵ-C H=sj ѧ] 8]r sםLwመ\4E4Gʒ׹ >UFa*ɍǷ'z"c5wr7T,Xs {h6|]5:(mW mT-n!< /e0O;]>oԎK20kԃ8g=W 0BZ \//m:"_RjR෶nUU:CD4&R|O!P=@Oq\@}2CtH6+֧<zpjo}u`i<l*| mau6Rÿk1wJ|Yk6o`X`Sh`žEo @Ak[J"UaDE 7Hd{@{pkwLӑ9>E~HP–TO}YLR'ۗ7|l=ԉER]6p(ytAM1P>*PZPyJobL7&?j^@<˝1o(gŜ^)%5nj EpI Ifa6}7N9ሽG-MvL(֞:AFP{~bp"ρ;]ؖ{UN-*-gEfzLPiI鉦9EnGXT줨PE].kyӱz=_[FcijgDߦO:41 /eƞ).{+lyŽMtf=27 @>Y˗]vyoz" DːF'鸑t= `unr9Un/dj9eōq3]!]1)_ʮX/J؅{Sk7B _j {ⷵ#\KZLQKyk.$/537w$"ԶQ";jcPsC)ׯ[C[D1c3IS{&oH4d Q=wͳNx?:8|S~ַPz,@, 97xyr%$2FרJCM ;:^J vth#;ODI4Q ^r_I5ҾIOLNh <'yJTD$ kfge(S|  QŐpe͂UD% t0pv]j|{y%dڇD!tW V8P-Oe16?(Jz<R57!#!XP`185gZfi<gw ,}`՟A1C@eY5J>j-rAFOf6R\-^ޝZ.0ܵ_Fq8g�r(g.ZWH/Ei&K >gh''k@u,< Z�A%;h8Z8C>p0/:b9!f� j> \e>)-71*ҵ .#$k-J>b!)~ =KFsYpg J.[w]Y<S5VIL &:`AU `YwaℹSr_QB/'ݗ0 <UpF ,璜6x`ˣC %|?@mocB{}P쩉 ِo uMxmb .dP&8O"eQ$pR^>a3icCoƟA:|/2%홸u.ϷVu=w2ZcP МOjLph ݧbƥTdͲ!I$Ź\} 1AڅZE f*f{O龦zjDF.1?;O{f(LTū@A6x+YYmCZ {VI3D!k @eUzpݬj$gX7Pv<I8{w1�T7Wsng=GO/;K>55$Ŭw.KmmfQmaV GD4^Uz,$EPp7\~m2_;LJYj6!<m A<Jnݪuu@R^~\9mofRtt yp<o?J_9؎%WkYQ *]c><{FNCY xxFMmfcݸIQ>Xe5XgܻI5}{jFrx#nCHnvÆ"= I.#%6u˔X'IT0$䳣i P(t3}}}[a\Krb|X(Y0Qw%Gn.:s/Xi�$x׌fQ7f .S15,8I5ʍͥ3İf!`3.@}0 r(鼺4ܶ5+n-ָoPKtXw$%aG MCt 6 "WQ S{V(׈AiR}7mj)%E,d6*OfClڧx8Miw:tSm\n`H�Rlzhn2`@-'r]ֳ%[.Ri/zmK9Uyf@:8#I\}^xre\ U.BnD687O'l׫汏 JZZ|!h dh:Q7Qi-;r,�LA6^:㜩ޫ+TƐv-w>4SUƘ|ObFav=$<jjAe7}.L Y(E=]Y{6$/cƢ~ >~"ѠNun]FqWZ?\L9v>(z 8m `(6G.ܓIS}g(Նs7Zo12*)s"J3VLFw)X~t+)z,@iğmh?.9Xv TI:L 0ia+lLQZeXh:`Jrܫ|w6h@ 7Zu5{ F6:Q1*>i�%z1#{ƛAYvQN d9-vE;vxo[U! }6:%Vd0HHrC}yMyz-AVc&|D)`U.lQfYJDw8|DS2B ~Uimע"^sZY,Wn WTJt^ggB统~^܌& nLۢƗӎ V04͘wYclN)�_6BJ~]EqN{Ǐ^&A#ؼ]?%iƠ.(f*5I/d_ZP?l������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-1.7.0/tests/data/messages/aed/msg-aes128-eax-chunk-size-4194304-password-123.pgp����0000644�0000000�0000000�00000012302�00726746425�0027527�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������>-.9i!`<Jua_�3b(˭ceՃ?W<IIG?)3b+ǵ=>C6:W֬fTx­+}J�wn52H%{<DJm\JOo@XJ#z5P]D%;9O'E{,b:AySGu.>;<'+S ـඅ�kbY=ޟ g(j-qtpuHF):Rd/dWQ]{.]u_I'SCwjɌT Z+YʁZ�u/2"Ѓɠ />tD;=S cz֯lF%/Vϔ ^HJK8!W-aЇJɼ2L% ?+\wdtt`Ü0jF '0~?Gmq & � 4xuD!PIO Y ;QVἯ!iIU)\Db]O.f&ɞ,PEg( iP (|<sU΄F0+ոpKmz'6EeNڸ&xk_yZրK=Kk9]/0?P$^B$s" L9}\,XyM#[Pٷ:<@؎ƦfhƝ_oﱳ Z dy%B5z8e lk!ߒqQҳ9%CB ,./{Oឱ}#Spq3myl,NHȾ4(b(3HP^ʒ^{N!xd .7wlzruřgYKHN/b|_h+@m)OPjxGq⸘E\Is M/oz^l۝xT'EB.a%Z:VGLq<Gx/J}&Йr޿\9Heɸ/Ns$w⮒il!L"zB1ydVr9iO*btJf"ؗ"GyR5:u [Y7ޛQioaǾMVh,1 @Ǩmӡ cAzBKUf5[lRU =Aٕ�vt`6:`7*cK<z(vCnU*QaC<gg73*]v�)7F@-asDq.<Vb5]cE)mmr*P}VN]w[+Ջ/$#u&}Qml~mЙ3M>üy,Ƃ^vU/w)ݯJ-AV /[$BŗU$ OƅlL.e餲3߷BHgm7R+y D}h6I2\yGE)1/봡k3Ϳ@m)kX0èGlZ' %z8Ku*")v;.Z/\'HV7hg)N-\ڈs 33{m$X B4k${h1E\,V9.~!|=> `WSLG 'pw8M[j_wJy``Ёˆ{Vݕs\WG_6 e`' "h&?"s>Y|$X<&ĸ%DƱY;@~Dn]7¦;S 8/zyr/a }2�hR:А:J&Iogi1\:/ '6})*W5ci0R#Z z֢wӐiVg[(k 9<P1WnkAV�eGw0zV$8'VcHӜgbLiƾ5Y $a!?lK懖Zg'KN1#>3/*܋IĒыNzgOhƵtʝ %rV~<+#2vRkC󲬁53j Sݞv4+]y& _m\91g_Q?}l>ow>S>gY,.Fza_(/`)@.0WF{}cQa| 4~HC9McPtPq pPmݬ/kz> b"gj Ӂ;x*4 vy1I2^"r!SZ9rۣƥ/">)}2Lց &w+Ӊ hO݊+a阆,adRrKR(Zs6ߜY˛,T!NVH|T)==X5Y#U*CW8G䥧8uEX$Ҍ,VAZ:"{+qk3103Ĥ$</c8}5�#ߔ. 5CC>ϦKv�e$#sT O}Le"fVC8Үr yBL;'BUuWh+6-f1a+y"7S)Gǁ8rm"zda]i|62^D8^JC4]B.n,Jdˋ>VL]'G\HC*΅Ft0Zɟ5%sΥ26Go[)⬓ocS**3fy~u;26܆<b(9 c3m 3(ixTW:l>b@xIE9P,Xyw: <73d|41l# vE`N=35z u w D%ՑG<^ʲs]}P}B{W` ľ͋rxdʄ5M&d+fzO5jpyolVj 4v2z"gefҊudi'e:"e?:,}mSs Sc+շ0t!6TŷG`u-t*/ah)ԾF=�2u$qȿ]y[K2)MsE#|&."~@im2PJ9I;ubr{҅Y9Hww_ dD :y"{h <G¯?.!Q($4j!֙%{u�4?\43~cq~ jR܂ ||&,HOc9PV<:ܖrYf/v$"CD c-ku3myY{!ܔ)~v#}9AB|/ͅU@fn| d=D4pS@&^Ibk:b8%{J]F&̹'ĕ!K+#l4i 9o]MK%"G0KG1ȗ-{r;2eR "҉wNs]b>}- sd%}D`zˍ+3c1<:+OVf5m2kK&|Ȱ"VaUw]L43 yi ԚI}LdzXyL;ʀjw?\͝Ϣإ{]G)\ ХF〸,p(zלmspe=S%"4g۟-a2˯ 18uݪs.s yQD2|g r,X]N>Ό8l7ː-_wq4.<nXkb2l 7O}q^Oւ",J'ĺD+fqT_6ϮA?^o{IB$#LC* qqhV,(j3E4LU\xxS'y;{e-ՍyMu'rʮ!hVnhN+1T\?V0H=Q_]8=ljgp�˛C ՄZ>ŭD7/h;,C=j9;am+7i{JSO ?__+Nuf aLTjG%օ&\m`$U& $[6~iL,*v|zJ2MlլG7ٌ@U&=e݋lЩy\jWhYUFL~>o^ �Zszk]b*)i;SFovDBd(cgw2c>gK04# f<WPS^b6G\mL\PĊ'08\$w<TfͺlLmRG N"8t8,Vz_ˉ>h[Ž@)R]B薴zcw.F9~)mhaAYp~NŐX%a5yd\1`귷aIȁ<aadyVӚ3ܴ0 7<| ᡕ&N% m =^(ڋu[;އ'E/sh<mYq4TSAFi:?{1 $̢09?0`-`*FU^(#CDbH}x?09qS+V۔>ݼka-_P�4̖�Fo RUr͉{m͘4 cGP[ܤwMXuapb3R4mx[b 0$Op3nB߷j/ 5qz5 WbdA*((,'c\ϸp0¶xJLg(J'h߾1E˞MH 7ӻ<*eeu_ȚKS>b4P-ޙv!=IuO@J& M-P^6wbT47 pxX-,2ت<⺶U)_n Eҳe2)B=4|krt&YeG?[H }69W~/ж9ra]>넿\nA >ֈ@~3<c ̭ b}W^iG>tf+ :? p"apf^ʋ? "Hglc2������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-1.7.0/tests/data/messages/aed/msg-aes128-eax-chunk-size-64-password-123.pgp���������0000644�0000000�0000000�00000014723�00726746425�0027141�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������>щ8elYVzޠ)ot4P= `N{2QLei-�e7>6y10e ,뉱7 ʍ--]%Kc<KϻPBZ+yYxDI|z|* 7se" >ˌuc`k]㥓Cѷk0AHNU|MySzu!ܰ{E,2@vuչ>{x5 ]*ƵZνtzݾڜFNG~Z[җ,otkZgF]@"-.L^6b)()`D ׼e['M890!zjbK'[ ]V&U%}xJyg1gҤA;}Oɫی|C- J\N0#61.xVh(eS"hҜiiuD^4Nw[̍tjXN:rhH~>V.� `~"Cl01t$c-v{;0ve|q:o[8, &wlkݩ~ NvӥL:I+nXỢ3aCL+){IC]U͊BhaVkX�A!ax1$u\oGMy3R�^AvO_�dzݔ*`^b~eoT|)rRfoԵm(! 5 29uWh{&95uM02;js`FXH<}2"BdJM0jݮ_[Pi_FsSqft< !34"2Xt+E2ח>\J�بQw^еo G)rwƗ^g^;ڲou&<\:%ELVIй{ZLDOb-J[|n5E>Y`9?b/ cv./&'ʨ>*J2W#Ē YXvḱ);$z+vt]nN '7Ib9뀃KM;$@߰ǗoӺEK X]@iEd`Z 3Sq|Bq >+두#Qxb#Idע| 'H[”1TQ$[; a-jwi?="^ο{+nWn&eH{]'I Nl|Vj@]fSH''T*sWoIVk ߵM Zk6b!+/RWWb g1o= CcNf1_,&bKt&5:ߞHT}bCY>6RIE=]nC!띯OJ%ȿYcXM^{Z@<wl {Z^uL(9y"9,C -#Y}`EJGrDt}o<:-Igm-ǨZgn_ p ]K'$yc"flS_^]!nswV ԨQOj)-2dZQPu4q~5ٍDmx=]#e5^R%38s*jKTjq{eAD{G;(FLHA@S_,L#y)wro=fƯ`{77Qm#(0n˦ $wNfj[kˀLMkHSO(u#Uj<qk#O mwe3֋ϼq qSaX$@-R3nb O5 wg[%KdI3 CܟbȜfsܿ5VanS d\u}\8DqpȄ! ,%yLDz4fnETPu/3WƷƅ �9J"엧 OֿIr`o[dfU<;C3 sN(^ul=CN#!^ս6~g /:&Zz_$ERúw>fsWA-�]Σ0( q`DҎ-(Jlo67[^ V(ie{-Ө`cWP˹`Uz_6T͡ Cœ^DrpC|aoPA;�mʖ[pҫ-ŗݟe= +m}8M? f1ZK7aK݌/Kd{&Q?_e^I?wgtPT”ek2eNׂ;WmP,@F6ev*&doW1}pDM_l("k!/SJ;\}6,q;]bR%0C4cݔG<Z {/qבaVVd5r4+.t5Ewo?Jd Y-XLy0䮡$eoI70-/Ǿ{-m) kE}PT 2,VF~xYs J :d;pbh/ Ά.HIr ^Qk0Gojkt ElnS(g'ȒMxiK̑\0uw_#JcR,9ʼ0"|v^yb\0�E5fgi%!+UdiqJ܄yhtx&lm1mUM "G3|H ]m5ӡ ;&LfЫHK745izyYcuh̷.@o!_lfMxd6+ FWŅq"6Z1~]/i)rZeU8N$<'*Q~iHê͌{630 le&aā Ҳ @-Rٙ2OWx|]{EHEt!~|tӭQ1ZuSR+_iKfE|0%X7} yt6X']X)Vo.]L7˳;ReV�ǽ\eNw9Y гLϊ؁6EQY,^q!_@lޏiy @U^ O1sUNJ|Fa+2U ]1O#bq]}Ty0FO-6a,LH3C [=X$b*ĵ}xЛ0mX{bDacq 4ÁW`Q Yrb \a2Do80tuk$kEOd̈y76Tz5y|9lhbDJH?c Gm:xUZM]N~�dZK5)х=d5$~i ؘ_i\|738<킞`ff]'xPQ={6gT<q 77ZA Y`Yuޠu :)8އ]r#.(45jrμ73ڬ4zoaR*wQ{KI?<O3/HF1gg ,аEbK.1 0AaO7s$TG"pVih&(hbT9iN\tL|뒓!!J[PN)qeQQDjnEKXmlJryfW|IȓDϙ ?L#*0}?a)"Xi Al'FU$1O, sNQ P Lҩ8|JV;a71*~^> 8SxX3Ƞ1yY.V҈?ZF7Ԯ&mFpX*/3yrKwvPVJ^޿GRb:`YĔʙP�3gin5#80qd�է~7 \Ҕ QsotY RP|S�.)B4}L^)\+tV;b*=] 9[63 r̄wFIO zV(iH%�"'G0{W|A,w$vW&j|]o5[$v|l&cIQF#\a&s¤#zCJ΃)GW\O= X?ڹ(d<S ւ̬W _"ˎ`yԍhZ?hQ@ќl¬čܭbD;c; B'zZB[/'Z"8{8ڃQpHz_3Yv[V 4|dDTbP46eDJhXU2!N W9z;+Pi'Ks,�-W9 PJ"g{2+lK ßJ c@ع1jU A7S)y]&iRᧁ}ޫn .0͓ `"]-{ 7ؤtN$.Mj p(C!B]4:hI{&,UPQY"EdDK@�z?C4<W{ftM3QF.(2h5lyD5{ J3cV71mA;oͪjWW v]^K""z۪=t"C�bxӇ_7R[\IaEouJC %|o^lNCU;a4|mJ%K:"([(?8/k *6\9sJ9páJ6tP#sIݏ]&y*2ƥep9q O^5`A4c)lkaoH h??ǚ�Lbʿpl^2=6�CGS@!p|:43͜D? ȡ5K˿<'*ULahw/`,dfKPŕAnO�;8jp}pk2i[R50Txq JQs B 3BS:wxC�uIc`=rviYM:(S.Mk$ϒM|sŠg%:*X ơ_/+:pan1m uffHZj:mt?|űL*8U!fC!#aQb7^̹s}�g׷Qb4Qu^7]u8DD6G% #JPOP%|*(]]8!@:{_+,_B:R5s"\%Lc. Oi&4m r5`Xe\'Jf?<M&H0۝%7J9-]U Շ%rxtm\7Ŗ D3 6Ezp w DƪEԤ}n%#m޺$kW<\ܞ^"Xp՘ =Qe/!XB=+,PSMZTFVw("S-6)-Yl8h{v\ RUiE@(5E\RgYY,-,ȈG}"0.hl x ͡ ׇbpZa4p ;0Dh#ь�z*pV4qh`Ej 0] #TF2tD<;:nN{GˏatoښLT^C^${{J"_'Hwں<#4nhTZ [) 7?D%Ȯ�YAi X1ɻɱ̴W{&e^[0? }[r_�Fn ~Y~5 ! -e]e(Hzr.G]%W\_,45qyQɣ(rVEA]k̶@c _uF7BNƕLYIO9EWN~E;Ze`I}R {J|8glLΪހ[[rS+ ]i鿧nHd}&[.xh:%87eζuG d:aZdrLlcM\{L3r13./ ٩bEjL$? 6%-H╣ 8h[1;U (|B\ܯpKXWCgd۶7 FGhRV)ȞN;3� 9&V�G "D9C%sBtWo L�wya:���������������������������������������������sequoia-openpgp-1.7.0/tests/data/messages/compressed-data-algo-0.pgp��������������������������������0000644�0000000�0000000�00000012063�00726746425�0023521�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-1.7.0/tests/data/messages/compressed-data-algo-1.gpg��������������������������������0000644�0000000�0000000�00000004352�00726746425�0023513�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-1.7.0/tests/data/messages/compressed-data-algo-2.gpg��������������������������������0000644�0000000�0000000�00000004360�00726746425�0023513�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-1.7.0/tests/data/messages/compressed-data-algo-3.gpg��������������������������������0000644�0000000�0000000�00000004250�00726746425�0023512�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-1.7.0/tests/data/messages/compression-quine.gpg�������������������������������������0000644�0000000�0000000�00000000266�00726746425�0023042�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000��������������������������������������������������������������������������������������������������������������������������������������������������������������������������� ���� �p���p�������p�������B!���B!���B!���B!���B!�������� �B!�������� �3!L@���3!L@����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-1.7.0/tests/data/messages/compression-quine.txt�������������������������������������0000644�0000000�0000000�00000000116�00726746425�0023076�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������The compression quine is from: http://mumble.net/~campbell/misc/pgp-quine/ ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������././@LongLink���������������������������������������������������������������������������������������0000644�0000000�0000000�00000000170�00000000000�0007771�L����������������������������������������������������������������������������������������������������ustar ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-1.7.0/tests/data/messages/emmelie-dorothea-dina-samantha-awina-detached-signature-of-100MB-of-zeros.sig���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-1.7.0/tests/data/messages/emmelie-dorothea-dina-samantha-awina-detached-signature-of0000644�0000000�0000000�00000000344�00726746425�0031763�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������-----BEGIN PGP SIGNATURE----- iHUEABYIAB0WIQQGHDykSv8OxY3GbpUi4/r+lrVsMgUCXbtAJwAKCRAi4/r+lrVs MgvBAQCOWK8CPy+h9jiWcOKNXxGTu6JQUqE7zoBS6q1MlHk2eQD9EAV7FtOrFvQ8 ogmLSqkkmdTJA9KlJS65zJWLTMTAewQ= =JKYW -----END PGP SIGNATURE----- ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-1.7.0/tests/data/messages/encrypted/brainpoolP256r1.msg.pgp�������������������������0000644�0000000�0000000�00000000300�00726746425�0024747�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������~VH 2q ["u~~a$w$抰S뮞-? UxHr|vl6=;w0x}&-OH!�)AHb^ƈTb\>'0j:Y2}}LP&?@BcsdM@Ҡ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-1.7.0/tests/data/messages/encrypted/brainpoolP256r1.sec.pgp�������������������������0000644�0000000�0000000�00000004555�00726746425�0024753�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-1.7.0/tests/data/messages/encrypted/brainpoolP384r1.msg.pgp�������������������������0000644�0000000�0000000�00000000340�00726746425�0024755�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-1.7.0/tests/data/messages/encrypted/brainpoolP384r1.sec.pgp�������������������������0000644�0000000�0000000�00000004635�00726746425�0024754�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-1.7.0/tests/data/messages/encrypted/brainpoolP512r1.msg.pgp�������������������������0000644�0000000�0000000�00000000400�00726746425�0024743�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-1.7.0/tests/data/messages/encrypted/brainpoolP512r1.sec.pgp�������������������������0000644�0000000�0000000�00000004715�00726746425�0024744�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-1.7.0/tests/data/messages/encrypted/cv25519.msg.pgp���������������������������������0000644�0000000�0000000�00000000240�00726746425�0023163�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-1.7.0/tests/data/messages/encrypted/cv25519.sec.pgp���������������������������������0000644�0000000�0000000�00000004516�00726746425�0023161�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-1.7.0/tests/data/messages/encrypted/cv25519.unclamped.msg.pgp�����������������������0000644�0000000�0000000�00000000237�00726746425�0025140�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������^.@/6@"k_S%ϸ3BϾ0h\]0_7Tu\w;̙/#0'֞7~.'IRGA=_V⿫OϙC,X/`Ϥ` @B2Va:e |�����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-1.7.0/tests/data/messages/encrypted/cv25519.unclamped.sec.pgp�����������������������0000644�0000000�0000000�00000001535�00726746425�0025126�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-1.7.0/tests/data/messages/encrypted/elg.msg.pgp�������������������������������������0000644�0000000�0000000�00000001521�00726746425�0022717�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-1.7.0/tests/data/messages/encrypted/elg.sec.pgp�������������������������������������0000644�0000000�0000000�00000006067�00726746425�0022715�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-1.7.0/tests/data/messages/encrypted/nistp256.msg.pgp��������������������������������0000644�0000000�0000000�00000000300�00726746425�0023534�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-1.7.0/tests/data/messages/encrypted/nistp256.sec.pgp��������������������������������0000644�0000000�0000000�00000004554�00726746425�0023537�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-1.7.0/tests/data/messages/encrypted/nistp384.msg.pgp��������������������������������0000644�0000000�0000000�00000000340�00726746425�0023542�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-1.7.0/tests/data/messages/encrypted/nistp384.sec.pgp��������������������������������0000644�0000000�0000000�00000004631�00726746425�0023535�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-1.7.0/tests/data/messages/encrypted/nistp521.msg.pgp��������������������������������0000644�0000000�0000000�00000000404�00726746425�0023534�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-1.7.0/tests/data/messages/encrypted/nistp521.sec.pgp��������������������������������0000644�0000000�0000000�00000004716�00726746425�0023532�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-1.7.0/tests/data/messages/encrypted/rsa.msg.pgp�������������������������������������0000644�0000000�0000000�00000000717�00726746425�0022743�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-1.7.0/tests/data/messages/encrypted/rsa.sec.pgp�������������������������������������0000644�0000000�0000000�00000007112�00726746425�0022723�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-1.7.0/tests/data/messages/encrypted/secp256k1.msg.pgp�������������������������������0000644�0000000�0000000�00000000300�00726746425�0023565�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������~Eb|٣\1.-KĽtK /J7U 4翆znǑVemYS%0mE, 0groO:^d iVU>^G߼&ߍ˺FZש>)?w` {@\ԴLR/0CDb9  ښ R4d��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-1.7.0/tests/data/messages/encrypted/secp256k1.sec.pgp�������������������������������0000644�0000000�0000000�00000004551�00726746425�0023565�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-1.7.0/tests/data/messages/encrypted-aes128-password-123456789.gpg�������������������0000644�0000000�0000000�00000012154�00726746425�0025231�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-1.7.0/tests/data/messages/encrypted-aes192-password-123456.gpg����������������������0000644�0000000�0000000�00000012154�00726746425�0024762�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-1.7.0/tests/data/messages/encrypted-aes256-password-123.gpg�������������������������0000644�0000000�0000000�00000012154�00726746425�0024524�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-1.7.0/tests/data/messages/encrypted-to-testy-new.pgp��������������������������������0000644�0000000�0000000�00000000252�00726746425�0023740�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������^؎3@\U41Yt!l %1~V0RR&o0?RXPk;IHyPhFw9OdkeЖ:Bfj183?>>9�G#j(0O������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-1.7.0/tests/data/messages/encrypted-to-testy-nistp256.pgp���������������������������0000644�0000000�0000000�00000000522�00726746425�0024541�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������-----BEGIN PGP MESSAGE----- hH4Dc7lkb6KvbkcSAgMEkyR5vjY8BpCrCGs3WhLh7/PU/yqLk6wPP7LzTjy8RHzQ 2Brk5RGHI6mwW9tWgZIqA2SW0/WjaNOXRe+z5DphJjDo/vlYwJwd1Kb6fM4kHtgb Sz01fhTSBPWio1j3zXT39OdG4HcU8GkzsA1SUoIypxrSSAEhQNRijO3xkwCaZcr+ XlrEQvl1c2mY1S3qpVJ+XnNOGEbIwORo7GAE2U6tvYdIQjeBaG7Sj7QOqHzvwB6c ATZiv5a9251nGg== =kGY5 -----END PGP MESSAGE----- ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-1.7.0/tests/data/messages/encrypted-to-testy-nistp384.pgp���������������������������0000644�0000000�0000000�00000000572�00726746425�0024550�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-1.7.0/tests/data/messages/encrypted-to-testy-nistp521.pgp���������������������������0000644�0000000�0000000�00000000653�00726746425�0024541�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-1.7.0/tests/data/messages/encrypted-to-testy-no-compression.gpg���������������������0000644�0000000�0000000�00000000517�00726746425�0026115�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-1.7.0/tests/data/messages/encrypted-to-testy.gpg������������������������������������0000644�0000000�0000000�00000000531�00726746425�0023140�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-1.7.0/tests/data/messages/encrypted-twofish-password-red-fish-blue-fish.gpg���������0000644�0000000�0000000�00000012154�00726746425�0030252�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-1.7.0/tests/data/messages/literal-mode-b.gpg����������������������������������������0000644�0000000�0000000�00000000024�00726746425�0022147�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������bfoobarYFOOBAR������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-1.7.0/tests/data/messages/literal-mode-t-partial-body.gpg���������������������������0000644�0000000�0000000�00000012075�00726746425�0024567�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-1.7.0/tests/data/messages/public-key-bare.gpg���������������������������������������0000644�0000000�0000000�00000000744�00726746425�0022336�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-1.7.0/tests/data/messages/public-subkey-bare.gpg������������������������������������0000644�0000000�0000000�00000000420�00726746425�0023037�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-1.7.0/tests/data/messages/recursive-1.gpg�������������������������������������������0000644�0000000�0000000�00000000046�00726746425�0021523�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������� t�����one t�����two t�����three������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-1.7.0/tests/data/messages/recursive-2.gpg�������������������������������������������0000644�0000000�0000000�00000000070�00726746425�0021521�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������6�� t�����one t�����two� t�����three t�����four������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-1.7.0/tests/data/messages/recursive-3.gpg�������������������������������������������0000644�0000000�0000000�00000000101�00726746425�0021515�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������?���� t�����one t�����two�� t�����three t�����four���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-1.7.0/tests/data/messages/recursive-4.gpg�������������������������������������������0000644�0000000�0000000�00000000114�00726746425�0021522�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������� t�����one t�����two t�����three t�����four� t�����five t�����six����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-1.7.0/tests/data/messages/recursive.txt���������������������������������������������0000644�0000000�0000000�00000003171�00726746425�0021431�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-1.7.0/tests/data/messages/s2k/mode-0-password-1234.gpg������������������������������0000644�0000000�0000000�00000000006�00726746425�0023361�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������ ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-1.7.0/tests/data/messages/s2k/mode-1-password-123456-1.gpg��������������������������0000644�0000000�0000000�00000000016�00726746425�0023674�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������  BYB*������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-1.7.0/tests/data/messages/s2k/mode-1-password-foobar-2.gpg��������������������������0000644�0000000�0000000�00000000016�00726746425�0024401�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������  XE<|7������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-1.7.0/tests/data/messages/s2k/mode-3-aes128-password-13-times-0123456789.gpg��������0000644�0000000�0000000�00000000017�00726746425�0026432�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������ a\H�����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-1.7.0/tests/data/messages/s2k/mode-3-aes192-password-123.gpg������������������������0000644�0000000�0000000�00000000017�00726746425�0024304�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������ tay�����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-1.7.0/tests/data/messages/s2k/mode-3-encrypted-key-password-bgtyhn.gpg��������������0000644�0000000�0000000�00000000060�00726746425�0027047�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������.YnڔV>}gozBjBH ND��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-1.7.0/tests/data/messages/s2k/mode-3-password-9876-2.gpg����������������������������0000644�0000000�0000000�00000000017�00726746425�0023551�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������  gSj+�����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-1.7.0/tests/data/messages/s2k/mode-3-password-qwerty-1.gpg��������������������������0000644�0000000�0000000�00000000017�00726746425�0024466�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������  xE[U�����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-1.7.0/tests/data/messages/s2k/mode-3-twofish-password-13-times-0123456789.gpg�������0000644�0000000�0000000�00000000017�00726746425�0027112�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������  QE@e�����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-1.7.0/tests/data/messages/seip/foo-priv.pgp�����������������������������������������0000644�0000000�0000000�00000004713�00726746425�0022075�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-1.7.0/tests/data/messages/seip/foo.pgp����������������������������������������������0000644�0000000�0000000�00000002265�00726746425�0021117�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-1.7.0/tests/data/messages/seip/msg-compression-not-signed-password-123.pgp����������0000644�0000000�0000000�00000000136�00726746425�0027664�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������ oM>xHQRJ<r'rF H眪;Ls3G3DM- 4+h3d9j?<| coV&S@D����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-1.7.0/tests/data/messages/seip/msg-compression-signed-password-123.pgp��������������0000644�0000000�0000000�00000000641�00726746425�0027067�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-1.7.0/tests/data/messages/seip/msg-no-compression-not-signed-password-123.pgp�������0000644�0000000�0000000�00000000135�00726746425�0030275�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������ 4MnU/L9px%>$տ踆޴7hwPkYo-MZqL$=*^ěbW�����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-1.7.0/tests/data/messages/seip/msg-no-compression-signed-password-123.pgp�����������0000644�0000000�0000000�00000000637�00726746425�0027506�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-1.7.0/tests/data/messages/shattered-1.pdf�������������������������������������������0000644�0000000�0000000�00001471043�00726746425�0021505�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-1.7.0/tests/data/messages/shattered-1.pdf.sig���������������������������������������0000644�0000000�0000000�00000000167�00726746425�0022260�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������u��!9�gս7QX}_{� 7QX}f�M>щUX=sr?qi"Y&)V�eboe*ka:~>K-���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-1.7.0/tests/data/messages/shattered-2.pdf�������������������������������������������0000644�0000000�0000000�00001471043�00726746425�0021506�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-1.7.0/tests/data/messages/shattered-2.pdf.sig���������������������������������������0000644�0000000�0000000�00000000167�00726746425�0022261�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������u��!9�gս7QX}_| � 7QX}t�Z3DxTC45y�qF3|L<b QK���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-1.7.0/tests/data/messages/sig.gpg���������������������������������������������������0000644�0000000�0000000�00000000466�00726746425�0020146�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-1.7.0/tests/data/messages/signature-with-broken-mpis.sig����������������������������0000644�0000000�0000000�00000000343�00726746425�0024561�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������-----BEGIN PGP SIGNATURE----- iHUEARYIAB0WIQSHd0YfKgdOvEgNNZQZzByeCFsQegUCYDa9/wAKCRAZzByeCFsQ eptbAP4r+RChXfvGaTNgG9iTUygu35EK+9FZX/5SeJ9GzMP+5gUAAG244XB4HazA QQdXt6Lj55M7m0EeBbsKghZqnE6IgAY= =SHhh -----END PGP SIGNATURE-----���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-1.7.0/tests/data/messages/signed-1-dsa.pgp������������������������������������������0000644�0000000�0000000�00000012320�00726746425�0021541�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-1.7.0/tests/data/messages/signed-1-ecdsa-nistp256.pgp�������������������������������0000644�0000000�0000000�00000012320�00726746425�0023441�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-1.7.0/tests/data/messages/signed-1-ecdsa-nistp384.pgp�������������������������������0000644�0000000�0000000�00000012360�00726746425�0023447�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-1.7.0/tests/data/messages/signed-1-ecdsa-nistp521.pgp�������������������������������0000644�0000000�0000000�00000012423�00726746425�0023440�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-1.7.0/tests/data/messages/signed-1-eddsa-ed25519.pgp��������������������������������0000644�0000000�0000000�00000012320�00726746425�0023046�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-1.7.0/tests/data/messages/signed-1-notarized-by-ed25519.pgp�������������������������0000644�0000000�0000000�00000012772�00726746425�0024410�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-1.7.0/tests/data/messages/signed-1-sha1-neal.gpg������������������������������������0000644�0000000�0000000�00000005275�00726746425�0022545�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-1.7.0/tests/data/messages/signed-1-sha256-testy.gpg���������������������������������0000644�0000000�0000000�00000005273�00726746425�0023150�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-1.7.0/tests/data/messages/signed-1.gpg����������������������������������������������0000644�0000000�0000000�00000012617�00726746425�0020774�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-1.7.0/tests/data/messages/signed-2-partial-body.gpg���������������������������������0000644�0000000�0000000�00000012565�00726746425�0023364�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-1.7.0/tests/data/messages/signed-3-partial-body-multiple-sigs.gpg�������������������0000644�0000000�0000000�00000005464�00726746425�0026161�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-1.7.0/tests/data/messages/signed-twice-by-ed25519.pgp�������������������������������0000644�0000000�0000000�00000012473�00726746425�0023364�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-1.7.0/tests/data/messages/signed.gpg������������������������������������������������0000644�0000000�0000000�00000000710�00726746425�0020625�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-1.7.0/tests/data/messages/userid-bare.gpg�������������������������������������������0000644�0000000�0000000�00000000046�00726746425�0021560�0����������������������������������������������������������������������������������������������������ustar �����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������$Neal H. Walfield <neal@walfield.org>������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sequoia-openpgp-1.7.0/tests/data/raw/a-cypherpunks-manifesto.aes128.key_ascending_from_0������������0000644�0000000�0000000�00000012046�00726746425�0027411�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-1.7.0/tests/data/raw/a-cypherpunks-manifesto.aes192.key_ascending_from_0������������0000644�0000000�0000000�00000012046�00726746425�0027412�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-1.7.0/tests/data/raw/a-cypherpunks-manifesto.aes256.key_ascending_from_0������������0000644�0000000�0000000�00000012046�00726746425�0027413�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-1.7.0/tests/for-each-artifact.rs����������������������������������������������������0000644�0000000�0000000�00000022575�00726746425�0017777�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(()); }; 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().into_packets().collect(); let q_: Vec<_> = q.clone().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::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(); 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 Cert::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.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(); } } /// 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(()) } ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������