diff --git a/Cargo.lock b/Cargo.lock index a460193..dfd663b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,2301 +2,6 @@ # It is not intended for manual editing. version = 3 -[[package]] -name = "acme_common" -version = "0.24.0" -dependencies = [ - "base64", - "daemonize", - "env_logger", - "glob", - "log", - "minijinja", - "native-tls", - "nix", - "openssl", - "openssl-sys", - "punycode", - "reqwest", - "serde_json", - "syslog", - "toml", -] - [[package]] name = "acmed" -version = "0.24.0" -dependencies = [ - "acme_common", - "async-lock", - "async-process", - "bincode", - "clap", - "futures", - "glob", - "log", - "minijinja", - "nix", - "nom", - "rand", - "reqwest", - "serde", - "serde_json", - "tokio", - "toml", -] - -[[package]] -name = "addr2line" -version = "0.24.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" -dependencies = [ - "gimli", -] - -[[package]] -name = "adler2" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" - -[[package]] -name = "aho-corasick" -version = "1.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" -dependencies = [ - "memchr", -] - -[[package]] -name = "anstream" -version = "0.6.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b" -dependencies = [ - "anstyle", - "anstyle-parse", - "anstyle-query", - "anstyle-wincon", - "colorchoice", - "is_terminal_polyfill", - "utf8parse", -] - -[[package]] -name = "anstyle" -version = "1.0.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" - -[[package]] -name = "anstyle-parse" -version = "0.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" -dependencies = [ - "utf8parse", -] - -[[package]] -name = "anstyle-query" -version = "1.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" -dependencies = [ - "windows-sys 0.59.0", -] - -[[package]] -name = "anstyle-wincon" -version = "3.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2109dbce0e72be3ec00bed26e6a7479ca384ad226efdd66db8fa2e3a38c83125" -dependencies = [ - "anstyle", - "windows-sys 0.59.0", -] - -[[package]] -name = "anyhow" -version = "1.0.94" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1fd03a028ef38ba2276dce7e33fcd6369c158a1bca17946c4b1b701891c1ff7" - -[[package]] -name = "async-channel" -version = "2.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89b47800b0be77592da0afd425cc03468052844aff33b84e33cc696f64e77b6a" -dependencies = [ - "concurrent-queue", - "event-listener-strategy", - "futures-core", - "pin-project-lite", -] - -[[package]] -name = "async-io" -version = "2.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43a2b323ccce0a1d90b449fd71f2a06ca7faa7c54c2751f06c9bd851fc061059" -dependencies = [ - "async-lock", - "cfg-if", - "concurrent-queue", - "futures-io", - "futures-lite", - "parking", - "polling", - "rustix", - "slab", - "tracing", - "windows-sys 0.59.0", -] - -[[package]] -name = "async-lock" -version = "3.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff6e472cdea888a4bd64f342f09b3f50e1886d32afe8df3d663c01140b811b18" -dependencies = [ - "event-listener", - "event-listener-strategy", - "pin-project-lite", -] - -[[package]] -name = "async-process" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63255f1dc2381611000436537bbedfe83183faa303a5a0edaf191edef06526bb" -dependencies = [ - "async-channel", - "async-io", - "async-lock", - "async-signal", - "async-task", - "blocking", - "cfg-if", - "event-listener", - "futures-lite", - "rustix", - "tracing", -] - -[[package]] -name = "async-signal" -version = "0.2.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "637e00349800c0bdf8bfc21ebbc0b6524abea702b0da4168ac00d070d0c0b9f3" -dependencies = [ - "async-io", - "async-lock", - "atomic-waker", - "cfg-if", - "futures-core", - "futures-io", - "rustix", - "signal-hook-registry", - "slab", - "windows-sys 0.59.0", -] - -[[package]] -name = "async-task" -version = "4.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" - -[[package]] -name = "atomic-waker" -version = "1.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" - -[[package]] -name = "autocfg" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" - -[[package]] -name = "backtrace" -version = "0.3.74" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" -dependencies = [ - "addr2line", - "cfg-if", - "libc", - "miniz_oxide", - "object", - "rustc-demangle", - "windows-targets", -] - -[[package]] -name = "base64" -version = "0.22.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" - -[[package]] -name = "bincode" -version = "1.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" -dependencies = [ - "serde", -] - -[[package]] -name = "bitflags" -version = "2.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" - -[[package]] -name = "blocking" -version = "1.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "703f41c54fc768e63e091340b424302bb1c29ef4aa0c7f10fe849dfb114d29ea" -dependencies = [ - "async-channel", - "async-task", - "futures-io", - "futures-lite", - "piper", -] - -[[package]] -name = "bumpalo" -version = "3.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" - -[[package]] -name = "byteorder" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" - -[[package]] -name = "bytes" -version = "1.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "325918d6fe32f23b19878fe4b34794ae41fc19ddbe53b10571a4874d44ffd39b" - -[[package]] -name = "cc" -version = "1.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c31a0499c1dc64f458ad13872de75c0eb7e3fdb0e67964610c914b034fc5956e" -dependencies = [ - "shlex", -] - -[[package]] -name = "cfg-if" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" - -[[package]] -name = "cfg_aliases" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" - -[[package]] -name = "clap" -version = "4.5.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3135e7ec2ef7b10c6ed8950f0f792ed96ee093fa088608f1c76e569722700c84" -dependencies = [ - "clap_builder", -] - -[[package]] -name = "clap_builder" -version = "4.5.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30582fc632330df2bd26877bde0c1f4470d57c582bbc070376afcd04d8cb4838" -dependencies = [ - "anstream", - "anstyle", - "clap_lex", - "strsim", -] - -[[package]] -name = "clap_lex" -version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" - -[[package]] -name = "colorchoice" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" - -[[package]] -name = "concurrent-queue" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" -dependencies = [ - "crossbeam-utils", -] - -[[package]] -name = "core-foundation" -version = "0.9.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" -dependencies = [ - "core-foundation-sys", - "libc", -] - -[[package]] -name = "core-foundation-sys" -version = "0.8.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" - -[[package]] -name = "crossbeam-utils" -version = "0.8.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" - -[[package]] -name = "daemonize" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab8bfdaacb3c887a54d41bdf48d3af8873b3f5566469f8ba21b92057509f116e" -dependencies = [ - "libc", -] - -[[package]] -name = "deranged" -version = "0.3.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" -dependencies = [ - "powerfmt", -] - -[[package]] -name = "displaydoc" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "encoding_rs" -version = "0.8.35" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "env_filter" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "186e05a59d4c50738528153b83b0b0194d3a29507dfec16eccd4b342903397d0" -dependencies = [ - "log", - "regex", -] - -[[package]] -name = "env_logger" -version = "0.11.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcaee3d8e3cfc3fd92428d477bc97fc29ec8716d180c0d74c643bb26166660e0" -dependencies = [ - "anstream", - "anstyle", - "env_filter", - "humantime", - "log", -] - -[[package]] -name = "equivalent" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" - -[[package]] -name = "errno" -version = "0.3.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" -dependencies = [ - "libc", - "windows-sys 0.59.0", -] - -[[package]] -name = "event-listener" -version = "5.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6032be9bd27023a771701cc49f9f053c751055f71efb2e0ae5c15809093675ba" -dependencies = [ - "concurrent-queue", - "parking", - "pin-project-lite", -] - -[[package]] -name = "event-listener-strategy" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c3e4e0dd3673c1139bf041f3008816d9cf2946bbfac2945c09e523b8d7b05b2" -dependencies = [ - "event-listener", - "pin-project-lite", -] - -[[package]] -name = "fastrand" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" - -[[package]] -name = "fnv" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" - -[[package]] -name = "foreign-types" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" -dependencies = [ - "foreign-types-shared", -] - -[[package]] -name = "foreign-types-shared" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" - -[[package]] -name = "form_urlencoded" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" -dependencies = [ - "percent-encoding", -] - -[[package]] -name = "futures" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" -dependencies = [ - "futures-channel", - "futures-core", - "futures-executor", - "futures-io", - "futures-sink", - "futures-task", - "futures-util", -] - -[[package]] -name = "futures-channel" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" -dependencies = [ - "futures-core", - "futures-sink", -] - -[[package]] -name = "futures-core" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" - -[[package]] -name = "futures-executor" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" -dependencies = [ - "futures-core", - "futures-task", - "futures-util", -] - -[[package]] -name = "futures-io" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" - -[[package]] -name = "futures-lite" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cef40d21ae2c515b51041df9ed313ed21e572df340ea58a922a0aefe7e8891a1" -dependencies = [ - "fastrand", - "futures-core", - "futures-io", - "parking", - "pin-project-lite", -] - -[[package]] -name = "futures-macro" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "futures-sink" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" - -[[package]] -name = "futures-task" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" - -[[package]] -name = "futures-util" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" -dependencies = [ - "futures-channel", - "futures-core", - "futures-io", - "futures-macro", - "futures-sink", - "futures-task", - "memchr", - "pin-project-lite", - "pin-utils", - "slab", -] - -[[package]] -name = "getrandom" -version = "0.2.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" -dependencies = [ - "cfg-if", - "libc", - "wasi", -] - -[[package]] -name = "gimli" -version = "0.31.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" - -[[package]] -name = "glob" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" - -[[package]] -name = "h2" -version = "0.4.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ccae279728d634d083c00f6099cb58f01cc99c145b84b8be2f6c74618d79922e" -dependencies = [ - "atomic-waker", - "bytes", - "fnv", - "futures-core", - "futures-sink", - "http", - "indexmap", - "slab", - "tokio", - "tokio-util", - "tracing", -] - -[[package]] -name = "hashbrown" -version = "0.15.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" - -[[package]] -name = "hermit-abi" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbf6a919d6cf397374f7dfeeea91d974c7c0a7221d0d0f4f20d859d329e53fcc" - -[[package]] -name = "hostname" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9c7c7c8ac16c798734b8a24560c1362120597c40d5e1459f09498f8f6c8f2ba" -dependencies = [ - "cfg-if", - "libc", - "windows", -] - -[[package]] -name = "http" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f16ca2af56261c99fba8bac40a10251ce8188205a4c448fbb745a2e4daa76fea" -dependencies = [ - "bytes", - "fnv", - "itoa", -] - -[[package]] -name = "http-body" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" -dependencies = [ - "bytes", - "http", -] - -[[package]] -name = "http-body-util" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "793429d76616a256bcb62c2a2ec2bed781c8307e797e2598c50010f2bee2544f" -dependencies = [ - "bytes", - "futures-util", - "http", - "http-body", - "pin-project-lite", -] - -[[package]] -name = "httparse" -version = "1.9.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d71d3574edd2771538b901e6549113b4006ece66150fb69c0fb6d9a2adae946" - -[[package]] -name = "humantime" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" - -[[package]] -name = "hyper" -version = "1.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "256fb8d4bd6413123cc9d91832d78325c48ff41677595be797d90f42969beae0" -dependencies = [ - "bytes", - "futures-channel", - "futures-util", - "h2", - "http", - "http-body", - "httparse", - "itoa", - "pin-project-lite", - "smallvec", - "tokio", - "want", -] - -[[package]] -name = "hyper-rustls" -version = "0.27.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d191583f3da1305256f22463b9bb0471acad48a4e534a5218b9963e9c1f59b2" -dependencies = [ - "futures-util", - "http", - "hyper", - "hyper-util", - "rustls", - "rustls-pki-types", - "tokio", - "tokio-rustls", - "tower-service", -] - -[[package]] -name = "hyper-tls" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" -dependencies = [ - "bytes", - "http-body-util", - "hyper", - "hyper-util", - "native-tls", - "tokio", - "tokio-native-tls", - "tower-service", -] - -[[package]] -name = "hyper-util" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df2dcfbe0677734ab2f3ffa7fa7bfd4706bfdc1ef393f2ee30184aed67e631b4" -dependencies = [ - "bytes", - "futures-channel", - "futures-util", - "http", - "http-body", - "hyper", - "pin-project-lite", - "socket2", - "tokio", - "tower-service", - "tracing", -] - -[[package]] -name = "icu_collections" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526" -dependencies = [ - "displaydoc", - "yoke", - "zerofrom", - "zerovec", -] - -[[package]] -name = "icu_locid" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637" -dependencies = [ - "displaydoc", - "litemap", - "tinystr", - "writeable", - "zerovec", -] - -[[package]] -name = "icu_locid_transform" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e" -dependencies = [ - "displaydoc", - "icu_locid", - "icu_locid_transform_data", - "icu_provider", - "tinystr", - "zerovec", -] - -[[package]] -name = "icu_locid_transform_data" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdc8ff3388f852bede6b579ad4e978ab004f139284d7b28715f773507b946f6e" - -[[package]] -name = "icu_normalizer" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f" -dependencies = [ - "displaydoc", - "icu_collections", - "icu_normalizer_data", - "icu_properties", - "icu_provider", - "smallvec", - "utf16_iter", - "utf8_iter", - "write16", - "zerovec", -] - -[[package]] -name = "icu_normalizer_data" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8cafbf7aa791e9b22bec55a167906f9e1215fd475cd22adfcf660e03e989516" - -[[package]] -name = "icu_properties" -version = "1.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93d6020766cfc6302c15dbbc9c8778c37e62c14427cb7f6e601d849e092aeef5" -dependencies = [ - "displaydoc", - "icu_collections", - "icu_locid_transform", - "icu_properties_data", - "icu_provider", - "tinystr", - "zerovec", -] - -[[package]] -name = "icu_properties_data" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67a8effbc3dd3e4ba1afa8ad918d5684b8868b3b26500753effea8d2eed19569" - -[[package]] -name = "icu_provider" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9" -dependencies = [ - "displaydoc", - "icu_locid", - "icu_provider_macros", - "stable_deref_trait", - "tinystr", - "writeable", - "yoke", - "zerofrom", - "zerovec", -] - -[[package]] -name = "icu_provider_macros" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "idna" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" -dependencies = [ - "idna_adapter", - "smallvec", - "utf8_iter", -] - -[[package]] -name = "idna_adapter" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "daca1df1c957320b2cf139ac61e7bd64fed304c5040df000a745aa1de3b4ef71" -dependencies = [ - "icu_normalizer", - "icu_properties", -] - -[[package]] -name = "indexmap" -version = "2.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62f822373a4fe84d4bb149bf54e584a7f4abec90e072ed49cda0edea5b95471f" -dependencies = [ - "equivalent", - "hashbrown", -] - -[[package]] -name = "ipnet" -version = "2.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddc24109865250148c2e0f3d25d4f0f479571723792d3802153c60922a4fb708" - -[[package]] -name = "is_terminal_polyfill" -version = "1.70.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" - -[[package]] -name = "itoa" -version = "1.0.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" - -[[package]] -name = "js-sys" -version = "0.3.76" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6717b6b5b077764fb5966237269cb3c64edddde4b14ce42647430a78ced9e7b7" -dependencies = [ - "once_cell", - "wasm-bindgen", -] - -[[package]] -name = "libc" -version = "0.2.169" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a" - -[[package]] -name = "linux-raw-sys" -version = "0.4.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" - -[[package]] -name = "litemap" -version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ee93343901ab17bd981295f2cf0026d4ad018c7c31ba84549a4ddbb47a45104" - -[[package]] -name = "lock_api" -version = "0.4.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" -dependencies = [ - "autocfg", - "scopeguard", -] - -[[package]] -name = "log" -version = "0.4.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" - -[[package]] -name = "memchr" -version = "2.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" - -[[package]] -name = "mime" -version = "0.3.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" - -[[package]] -name = "minijinja" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c37e1b517d1dcd0e51dc36c4567b9d5a29262b3ec8da6cb5d35e27a8fb529b5" -dependencies = [ - "serde", -] - -[[package]] -name = "minimal-lexical" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" - -[[package]] -name = "miniz_oxide" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ffbe83022cedc1d264172192511ae958937694cd57ce297164951b8b3568394" -dependencies = [ - "adler2", -] - -[[package]] -name = "mio" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" -dependencies = [ - "libc", - "wasi", - "windows-sys 0.52.0", -] - -[[package]] -name = "native-tls" -version = "0.2.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8614eb2c83d59d1c8cc974dd3f920198647674a0a035e1af1fa58707e317466" -dependencies = [ - "libc", - "log", - "openssl", - "openssl-probe", - "openssl-sys", - "schannel", - "security-framework", - "security-framework-sys", - "tempfile", -] - -[[package]] -name = "nix" -version = "0.29.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" -dependencies = [ - "bitflags", - "cfg-if", - "cfg_aliases", - "libc", -] - -[[package]] -name = "nom" -version = "7.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" -dependencies = [ - "memchr", - "minimal-lexical", -] - -[[package]] -name = "num-conv" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" - -[[package]] -name = "num_threads" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c7398b9c8b70908f6371f47ed36737907c87c52af34c268fed0bf0ceb92ead9" -dependencies = [ - "libc", -] - -[[package]] -name = "object" -version = "0.36.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aedf0a2d09c573ed1d8d85b30c119153926a2b36dce0ab28322c09a117a4683e" -dependencies = [ - "memchr", -] - -[[package]] -name = "once_cell" -version = "1.20.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" - -[[package]] -name = "openssl" -version = "0.10.68" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6174bc48f102d208783c2c84bf931bb75927a617866870de8a4ea85597f871f5" -dependencies = [ - "bitflags", - "cfg-if", - "foreign-types", - "libc", - "once_cell", - "openssl-macros", - "openssl-sys", -] - -[[package]] -name = "openssl-macros" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "openssl-probe" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" - -[[package]] -name = "openssl-src" -version = "300.4.1+3.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "faa4eac4138c62414b5622d1b31c5c304f34b406b013c079c2bbc652fdd6678c" -dependencies = [ - "cc", -] - -[[package]] -name = "openssl-sys" -version = "0.9.104" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45abf306cbf99debc8195b66b7346498d7b10c210de50418b5ccd7ceba08c741" -dependencies = [ - "cc", - "libc", - "openssl-src", - "pkg-config", - "vcpkg", -] - -[[package]] -name = "parking" -version = "2.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" - -[[package]] -name = "parking_lot" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" -dependencies = [ - "lock_api", - "parking_lot_core", -] - -[[package]] -name = "parking_lot_core" -version = "0.9.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" -dependencies = [ - "cfg-if", - "libc", - "redox_syscall", - "smallvec", - "windows-targets", -] - -[[package]] -name = "percent-encoding" -version = "2.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" - -[[package]] -name = "pin-project-lite" -version = "0.2.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "915a1e146535de9163f3987b8944ed8cf49a18bb0056bcebcdcece385cece4ff" - -[[package]] -name = "pin-utils" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" - -[[package]] -name = "piper" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96c8c490f422ef9a4efd2cb5b42b76c8613d7e7dfc1caf667b8a3350a5acc066" -dependencies = [ - "atomic-waker", - "fastrand", - "futures-io", -] - -[[package]] -name = "pkg-config" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" - -[[package]] -name = "polling" -version = "3.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a604568c3202727d1507653cb121dbd627a58684eb09a820fd746bee38b4442f" -dependencies = [ - "cfg-if", - "concurrent-queue", - "hermit-abi", - "pin-project-lite", - "rustix", - "tracing", - "windows-sys 0.59.0", -] - -[[package]] -name = "powerfmt" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" - -[[package]] -name = "ppv-lite86" -version = "0.2.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" -dependencies = [ - "zerocopy", -] - -[[package]] -name = "proc-macro2" -version = "1.0.92" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "punycode" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9e1dcb320d6839f6edb64f7a4a59d39b30480d4d1765b56873f7c858538a5fe" - -[[package]] -name = "quote" -version = "1.0.37" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" -dependencies = [ - "proc-macro2", -] - -[[package]] -name = "rand" -version = "0.8.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" -dependencies = [ - "libc", - "rand_chacha", - "rand_core", -] - -[[package]] -name = "rand_chacha" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" -dependencies = [ - "ppv-lite86", - "rand_core", -] - -[[package]] -name = "rand_core" -version = "0.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" -dependencies = [ - "getrandom", -] - -[[package]] -name = "redox_syscall" -version = "0.5.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03a862b389f93e68874fbf580b9de08dd02facb9a788ebadaf4a3fd33cf58834" -dependencies = [ - "bitflags", -] - -[[package]] -name = "regex" -version = "1.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" -dependencies = [ - "aho-corasick", - "memchr", - "regex-automata", - "regex-syntax", -] - -[[package]] -name = "regex-automata" -version = "0.4.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" -dependencies = [ - "aho-corasick", - "memchr", - "regex-syntax", -] - -[[package]] -name = "regex-syntax" -version = "0.8.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" - -[[package]] -name = "reqwest" -version = "0.12.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a77c62af46e79de0a562e1a9849205ffcb7fc1238876e9bd743357570e04046f" -dependencies = [ - "base64", - "bytes", - "encoding_rs", - "futures-core", - "futures-util", - "h2", - "http", - "http-body", - "http-body-util", - "hyper", - "hyper-rustls", - "hyper-tls", - "hyper-util", - "ipnet", - "js-sys", - "log", - "mime", - "native-tls", - "once_cell", - "percent-encoding", - "pin-project-lite", - "rustls-pemfile", - "serde", - "serde_json", - "serde_urlencoded", - "sync_wrapper", - "system-configuration", - "tokio", - "tokio-native-tls", - "tower-service", - "url", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", - "windows-registry", -] - -[[package]] -name = "ring" -version = "0.17.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" -dependencies = [ - "cc", - "cfg-if", - "getrandom", - "libc", - "spin", - "untrusted", - "windows-sys 0.52.0", -] - -[[package]] -name = "rustc-demangle" -version = "0.1.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" - -[[package]] -name = "rustix" -version = "0.38.42" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f93dc38ecbab2eb790ff964bb77fa94faf256fd3e73285fd7ba0903b76bedb85" -dependencies = [ - "bitflags", - "errno", - "libc", - "linux-raw-sys", - "windows-sys 0.59.0", -] - -[[package]] -name = "rustls" -version = "0.23.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5065c3f250cbd332cd894be57c40fa52387247659b14a2d6041d121547903b1b" -dependencies = [ - "once_cell", - "rustls-pki-types", - "rustls-webpki", - "subtle", - "zeroize", -] - -[[package]] -name = "rustls-pemfile" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50" -dependencies = [ - "rustls-pki-types", -] - -[[package]] -name = "rustls-pki-types" -version = "1.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2bf47e6ff922db3825eb750c4e2ff784c6ff8fb9e13046ef6a1d1c5401b0b37" - -[[package]] -name = "rustls-webpki" -version = "0.102.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64ca1bc8749bd4cf37b5ce386cc146580777b4e8572c7b97baf22c83f444bee9" -dependencies = [ - "ring", - "rustls-pki-types", - "untrusted", -] - -[[package]] -name = "ryu" -version = "1.0.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" - -[[package]] -name = "schannel" -version = "0.1.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f29ebaa345f945cec9fbbc532eb307f0fdad8161f281b6369539c8d84876b3d" -dependencies = [ - "windows-sys 0.59.0", -] - -[[package]] -name = "scopeguard" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" - -[[package]] -name = "security-framework" -version = "2.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" -dependencies = [ - "bitflags", - "core-foundation", - "core-foundation-sys", - "libc", - "security-framework-sys", -] - -[[package]] -name = "security-framework-sys" -version = "2.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1863fd3768cd83c56a7f60faa4dc0d403f1b6df0a38c3c25f44b7894e45370d5" -dependencies = [ - "core-foundation-sys", - "libc", -] - -[[package]] -name = "serde" -version = "1.0.216" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b9781016e935a97e8beecf0c933758c97a5520d32930e460142b4cd80c6338e" -dependencies = [ - "serde_derive", -] - -[[package]] -name = "serde_derive" -version = "1.0.216" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46f859dbbf73865c6627ed570e78961cd3ac92407a2d117204c49232485da55e" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "serde_json" -version = "1.0.133" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7fceb2473b9166b2294ef05efcb65a3db80803f0b03ef86a5fc88a2b85ee377" -dependencies = [ - "itoa", - "memchr", - "ryu", - "serde", -] - -[[package]] -name = "serde_spanned" -version = "0.6.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1" -dependencies = [ - "serde", -] - -[[package]] -name = "serde_urlencoded" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" -dependencies = [ - "form_urlencoded", - "itoa", - "ryu", - "serde", -] - -[[package]] -name = "shlex" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" - -[[package]] -name = "signal-hook-registry" -version = "1.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" -dependencies = [ - "libc", -] - -[[package]] -name = "slab" -version = "0.4.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" -dependencies = [ - "autocfg", -] - -[[package]] -name = "smallvec" -version = "1.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" - -[[package]] -name = "socket2" -version = "0.5.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c970269d99b64e60ec3bd6ad27270092a5394c4e309314b18ae3fe575695fbe8" -dependencies = [ - "libc", - "windows-sys 0.52.0", -] - -[[package]] -name = "spin" -version = "0.9.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" - -[[package]] -name = "stable_deref_trait" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" - -[[package]] -name = "strsim" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" - -[[package]] -name = "subtle" -version = "2.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" - -[[package]] -name = "syn" -version = "2.0.90" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "919d3b74a5dd0ccd15aeb8f93e7006bd9e14c295087c9896a110f490752bcf31" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "sync_wrapper" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" -dependencies = [ - "futures-core", -] - -[[package]] -name = "synstructure" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "syslog" -version = "7.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "019f1500a13379b7d051455df397c75770de6311a7a188a699499502704d9f10" -dependencies = [ - "hostname", - "libc", - "log", - "time", -] - -[[package]] -name = "system-configuration" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" -dependencies = [ - "bitflags", - "core-foundation", - "system-configuration-sys", -] - -[[package]] -name = "system-configuration-sys" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" -dependencies = [ - "core-foundation-sys", - "libc", -] - -[[package]] -name = "tacd" -version = "0.24.0" -dependencies = [ - "acme_common", - "anyhow", - "clap", - "log", - "openssl", - "thiserror", -] - -[[package]] -name = "tempfile" -version = "3.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28cce251fcbc87fac86a866eeb0d6c2d536fc16d06f184bb61aeae11aa4cee0c" -dependencies = [ - "cfg-if", - "fastrand", - "once_cell", - "rustix", - "windows-sys 0.59.0", -] - -[[package]] -name = "thiserror" -version = "2.0.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08f5383f3e0071702bf93ab5ee99b52d26936be9dedd9413067cbdcddcb6141a" -dependencies = [ - "thiserror-impl", -] - -[[package]] -name = "thiserror-impl" -version = "2.0.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2f357fcec90b3caef6623a099691be676d033b40a058ac95d2a6ade6fa0c943" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "time" -version = "0.3.37" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35e7868883861bd0e56d9ac6efcaaca0d6d5d82a2a7ec8209ff492c07cf37b21" -dependencies = [ - "deranged", - "itoa", - "libc", - "num-conv", - "num_threads", - "powerfmt", - "serde", - "time-core", - "time-macros", -] - -[[package]] -name = "time-core" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" - -[[package]] -name = "time-macros" -version = "0.2.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2834e6017e3e5e4b9834939793b282bc03b37a3336245fa820e35e233e2a85de" -dependencies = [ - "num-conv", - "time-core", -] - -[[package]] -name = "tinystr" -version = "0.7.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f" -dependencies = [ - "displaydoc", - "zerovec", -] - -[[package]] -name = "tokio" -version = "1.42.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5cec9b21b0450273377fc97bd4c33a8acffc8c996c987a7c5b319a0083707551" -dependencies = [ - "backtrace", - "bytes", - "libc", - "mio", - "parking_lot", - "pin-project-lite", - "signal-hook-registry", - "socket2", - "tokio-macros", - "windows-sys 0.52.0", -] - -[[package]] -name = "tokio-macros" -version = "2.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "tokio-native-tls" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" -dependencies = [ - "native-tls", - "tokio", -] - -[[package]] -name = "tokio-rustls" -version = "0.26.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f6d0975eaace0cf0fcadee4e4aaa5da15b5c079146f2cffb67c113be122bf37" -dependencies = [ - "rustls", - "tokio", -] - -[[package]] -name = "tokio-util" -version = "0.7.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7fcaa8d55a2bdd6b83ace262b016eca0d79ee02818c5c1bcdf0305114081078" -dependencies = [ - "bytes", - "futures-core", - "futures-sink", - "pin-project-lite", - "tokio", -] - -[[package]] -name = "toml" -version = "0.8.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1ed1f98e3fdc28d6d910e6737ae6ab1a93bf1985935a1193e68f93eeb68d24e" -dependencies = [ - "serde", - "serde_spanned", - "toml_datetime", - "toml_edit", -] - -[[package]] -name = "toml_datetime" -version = "0.6.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" -dependencies = [ - "serde", -] - -[[package]] -name = "toml_edit" -version = "0.22.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5" -dependencies = [ - "indexmap", - "serde", - "serde_spanned", - "toml_datetime", - "winnow", -] - -[[package]] -name = "tower-service" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" - -[[package]] -name = "tracing" -version = "0.1.41" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" -dependencies = [ - "pin-project-lite", - "tracing-core", -] - -[[package]] -name = "tracing-core" -version = "0.1.33" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" -dependencies = [ - "once_cell", -] - -[[package]] -name = "try-lock" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" - -[[package]] -name = "unicode-ident" -version = "1.0.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" - -[[package]] -name = "untrusted" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" - -[[package]] -name = "url" -version = "2.5.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" -dependencies = [ - "form_urlencoded", - "idna", - "percent-encoding", -] - -[[package]] -name = "utf16_iter" -version = "1.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246" - -[[package]] -name = "utf8_iter" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" - -[[package]] -name = "utf8parse" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" - -[[package]] -name = "vcpkg" -version = "0.2.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" - -[[package]] -name = "want" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" -dependencies = [ - "try-lock", -] - -[[package]] -name = "wasi" -version = "0.11.0+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" - -[[package]] -name = "wasm-bindgen" -version = "0.2.99" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a474f6281d1d70c17ae7aa6a613c87fce69a127e2624002df63dcb39d6cf6396" -dependencies = [ - "cfg-if", - "once_cell", - "wasm-bindgen-macro", -] - -[[package]] -name = "wasm-bindgen-backend" -version = "0.2.99" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f89bb38646b4f81674e8f5c3fb81b562be1fd936d84320f3264486418519c79" -dependencies = [ - "bumpalo", - "log", - "proc-macro2", - "quote", - "syn", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-futures" -version = "0.4.49" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38176d9b44ea84e9184eff0bc34cc167ed044f816accfe5922e54d84cf48eca2" -dependencies = [ - "cfg-if", - "js-sys", - "once_cell", - "wasm-bindgen", - "web-sys", -] - -[[package]] -name = "wasm-bindgen-macro" -version = "0.2.99" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2cc6181fd9a7492eef6fef1f33961e3695e4579b9872a6f7c83aee556666d4fe" -dependencies = [ - "quote", - "wasm-bindgen-macro-support", -] - -[[package]] -name = "wasm-bindgen-macro-support" -version = "0.2.99" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30d7a95b763d3c45903ed6c81f156801839e5ee968bb07e534c44df0fcd330c2" -dependencies = [ - "proc-macro2", - "quote", - "syn", - "wasm-bindgen-backend", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-shared" -version = "0.2.99" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "943aab3fdaaa029a6e0271b35ea10b72b943135afe9bffca82384098ad0e06a6" - -[[package]] -name = "web-sys" -version = "0.3.76" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04dd7223427d52553d3702c004d3b2fe07c148165faa56313cb00211e31c12bc" -dependencies = [ - "js-sys", - "wasm-bindgen", -] - -[[package]] -name = "windows" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e48a53791691ab099e5e2ad123536d0fff50652600abaf43bbf952894110d0be" -dependencies = [ - "windows-core", - "windows-targets", -] - -[[package]] -name = "windows-core" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" -dependencies = [ - "windows-targets", -] - -[[package]] -name = "windows-registry" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e400001bb720a623c1c69032f8e3e4cf09984deec740f007dd2b03ec864804b0" -dependencies = [ - "windows-result", - "windows-strings", - "windows-targets", -] - -[[package]] -name = "windows-result" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d1043d8214f791817bab27572aaa8af63732e11bf84aa21a45a78d6c317ae0e" -dependencies = [ - "windows-targets", -] - -[[package]] -name = "windows-strings" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10" -dependencies = [ - "windows-result", - "windows-targets", -] - -[[package]] -name = "windows-sys" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" -dependencies = [ - "windows-targets", -] - -[[package]] -name = "windows-sys" -version = "0.59.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" -dependencies = [ - "windows-targets", -] - -[[package]] -name = "windows-targets" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" -dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_gnullvm", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", -] - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" - -[[package]] -name = "windows_i686_gnu" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" - -[[package]] -name = "windows_i686_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" - -[[package]] -name = "windows_i686_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" - -[[package]] -name = "winnow" -version = "0.6.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36c1fec1a2bb5866f07c25f68c26e565c4c200aebb96d7e55710c19d3e8ac49b" -dependencies = [ - "memchr", -] - -[[package]] -name = "write16" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936" - -[[package]] -name = "writeable" -version = "0.5.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" - -[[package]] -name = "yoke" -version = "0.7.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "120e6aef9aa629e3d4f52dc8cc43a015c7724194c97dfaf45180d2daf2b77f40" -dependencies = [ - "serde", - "stable_deref_trait", - "yoke-derive", - "zerofrom", -] - -[[package]] -name = "yoke-derive" -version = "0.7.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154" -dependencies = [ - "proc-macro2", - "quote", - "syn", - "synstructure", -] - -[[package]] -name = "zerocopy" -version = "0.7.35" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" -dependencies = [ - "byteorder", - "zerocopy-derive", -] - -[[package]] -name = "zerocopy-derive" -version = "0.7.35" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "zerofrom" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cff3ee08c995dee1859d998dea82f7374f2826091dd9cd47def953cae446cd2e" -dependencies = [ - "zerofrom-derive", -] - -[[package]] -name = "zerofrom-derive" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "595eed982f7d355beb85837f651fa22e90b3c044842dc7f2c2842c086f295808" -dependencies = [ - "proc-macro2", - "quote", - "syn", - "synstructure", -] - -[[package]] -name = "zeroize" -version = "1.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" - -[[package]] -name = "zerovec" -version = "0.10.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa2b893d79df23bfb12d5461018d408ea19dfafe76c2c7ef6d4eba614f8ff079" -dependencies = [ - "yoke", - "zerofrom", - "zerovec-derive", -] - -[[package]] -name = "zerovec-derive" -version = "0.10.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] +version = "0.25.0-dev" diff --git a/Cargo.toml b/Cargo.toml index d112f5a..287e66e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,11 +1,37 @@ -[workspace] -members = [ - "acmed", - "tacd", -] +[package] +name = "acmed" +version = "0.25.0-dev" +authors = ["Rodolphe Bréard "] +edition = "2021" +description = "ACME (RFC 8555) client daemon" +readme = "README.md" +repository = "https://github.com/breard-r/acmed" +license = "MIT OR Apache-2.0" +keywords = ["acme", "tls", "X.509"] +categories = ["cryptography"] +include = ["src/**/*", "Cargo.toml", "LICENSE-*.txt"] +publish = false +rust-version = "1.74.0" + +[features] +default = ["openssl_dyn"] +crypto_openssl = [] +openssl_dyn = ["crypto_openssl"] +openssl_vendored = ["crypto_openssl"] +ed25519 = [] +ed448 = [] + +[dependencies] + +[target.'cfg(unix)'.dependencies] + +[build-dependencies] [profile.release] -opt-level = 'z' -lto = 'thin' +opt-level = "z" +debug = false +lto = true codegen-units = 1 -panic = 'abort' +panic = "abort" +strip = true +incremental = false diff --git a/Makefile b/Makefile index 240a390..31ad99c 100644 --- a/Makefile +++ b/Makefile @@ -14,27 +14,15 @@ MAN_DST_DIR = $(TARGET_DIR)/man FEATURES = openssl_dyn -all: acmed tacd - -acmed: man_dir +all: man_dir if test -n "$(TARGET)"; then \ - VARLIBDIR="$(VARLIBDIR)" SYSCONFDIR="$(SYSCONFDIR)" RUNSTATEDIR="$(RUNSTATEDIR)" cargo build --bin acmed --release --no-default-features --features "$(FEATURES)" --target "$(TARGET)"; \ + VARLIBDIR="$(VARLIBDIR)" SYSCONFDIR="$(SYSCONFDIR)" RUNSTATEDIR="$(RUNSTATEDIR)" cargo build --release --no-default-features --features "$(FEATURES)" --target "$(TARGET)"; \ else \ - VARLIBDIR="$(VARLIBDIR)" SYSCONFDIR="$(SYSCONFDIR)" RUNSTATEDIR="$(RUNSTATEDIR)" cargo build --bin acmed --release --no-default-features --features "$(FEATURES)"; \ + VARLIBDIR="$(VARLIBDIR)" SYSCONFDIR="$(SYSCONFDIR)" RUNSTATEDIR="$(RUNSTATEDIR)" cargo build --release --no-default-features --features "$(FEATURES)"; \ fi - strip "$(TARGET_DIR)/acmed" gzip <"$(MAN_SRC_DIR)/acmed.8" >"$(MAN_DST_DIR)/acmed.8.gz" gzip <"$(MAN_SRC_DIR)/acmed.toml.5" >"$(MAN_DST_DIR)/acmed.toml.5.gz" -tacd: man_dir - if test -n "$(TARGET)"; then \ - VARLIBDIR="$(VARLIBDIR)" SYSCONFDIR="$(SYSCONFDIR)" RUNSTATEDIR="$(RUNSTATEDIR)" cargo build --bin tacd --release --no-default-features --features "$(FEATURES)" --target "$(TARGET)"; \ - else \ - VARLIBDIR="$(VARLIBDIR)" SYSCONFDIR="$(SYSCONFDIR)" RUNSTATEDIR="$(RUNSTATEDIR)" cargo build --bin tacd --release --no-default-features --features "$(FEATURES)"; \ - fi - strip "$(TARGET_DIR)/tacd" - gzip <"$(MAN_SRC_DIR)/tacd.8" >"$(MAN_DST_DIR)/tacd.8.gz" - man_dir: @mkdir -p $(MAN_DST_DIR) @@ -53,12 +41,8 @@ install: install -m 0644 acmed/config/default_hooks.toml $(DESTDIR)$(SYSCONFDIR)/acmed/default_hooks.toml; \ install -m 0644 acmed/config/letsencrypt.toml $(DESTDIR)$(SYSCONFDIR)/acmed/letsencrypt.toml; \ fi - if test -f "$(TARGET_DIR)/tacd"; then \ - install -m 0755 $(TARGET_DIR)/tacd $(DESTDIR)$(BINDIR)/tacd; \ - install -m 0644 $(TARGET_DIR)/man/tacd.8.gz $(DESTDIR)$(MAN8DIR)/tacd.8.gz; \ - fi clean: cargo clean -.PHONY: all acmed tacd man_dir install clean +.PHONY: all man_dir install clean diff --git a/acme_common/Cargo.toml b/acme_common/Cargo.toml deleted file mode 100644 index dcb4ff8..0000000 --- a/acme_common/Cargo.toml +++ /dev/null @@ -1,41 +0,0 @@ -[package] -name = "acme_common" -version = "0.24.0" -authors = ["Rodolphe Breard "] -edition = "2018" -readme = "../README.md" -repository = "https://github.com/breard-r/libreauth" -license = "MIT OR Apache-2.0" -include = ["src/**/*", "Cargo.toml", "Licence_*.txt"] -publish = false -rust-version = "1.74.0" - -[lib] -name = "acme_common" - -[features] -default = [] -crypto_openssl = [] -openssl_dyn = ["crypto_openssl", "openssl", "openssl-sys"] -openssl_vendored = ["crypto_openssl", "openssl/vendored", "openssl-sys/vendored"] -ed25519 = [] -ed448 = [] - -[dependencies] -base64 = "0.22.0" -daemonize = "0.5.0" -env_logger = "0.11.3" -glob = "0.3.1" -log = "0.4.21" -minijinja = "2.5.0" -native-tls = "0.2.11" -openssl = { version = "0.10.64", optional = true } -openssl-sys = { version = "0.9.101", optional = true } -punycode = "0.4.1" -reqwest = { version = "0.12.1", default-features = false } -serde_json = "1.0.114" -syslog = "7.0.0" -toml = "0.8.12" - -[target.'cfg(unix)'.dependencies] -nix = "0.29.0" diff --git a/acme_common/build.rs b/acme_common/build.rs deleted file mode 100644 index 42ce14c..0000000 --- a/acme_common/build.rs +++ /dev/null @@ -1,23 +0,0 @@ -use std::env; - -macro_rules! set_rustc_env_var { - ($name: expr, $value: expr) => {{ - println!("cargo:rustc-env={}={}", $name, $value); - }}; -} - -#[allow(clippy::unusual_byte_groupings)] -fn main() { - if let Ok(v) = env::var("DEP_OPENSSL_VERSION_NUMBER") { - let version = u64::from_str_radix(&v, 16).unwrap(); - // OpenSSL 1.1.1 - if version >= 0x1_01_01_00_0 { - println!("cargo:rustc-cfg=feature=\"ed25519\""); - println!("cargo:rustc-cfg=feature=\"ed448\""); - } - set_rustc_env_var!("ACMED_TLS_LIB_NAME", "OpenSSL"); - } - if env::var("DEP_OPENSSL_LIBRESSL_VERSION_NUMBER").is_ok() { - set_rustc_env_var!("ACMED_TLS_LIB_NAME", "LibreSSL"); - } -} diff --git a/acme_common/src/crypto.rs b/acme_common/src/crypto.rs deleted file mode 100644 index e0fdd5f..0000000 --- a/acme_common/src/crypto.rs +++ /dev/null @@ -1,93 +0,0 @@ -use crate::error::Error; -use std::fmt; -use std::str::FromStr; - -mod jws_signature_algorithm; -mod key_type; -#[cfg(feature = "crypto_openssl")] -mod openssl_certificate; -#[cfg(feature = "crypto_openssl")] -mod openssl_hash; -#[cfg(feature = "crypto_openssl")] -mod openssl_keys; -#[cfg(feature = "crypto_openssl")] -mod openssl_subject_attribute; -#[cfg(feature = "crypto_openssl")] -mod openssl_version; - -const APP_ORG: &str = "ACMEd"; -const APP_NAME: &str = "ACMEd"; -const X509_VERSION: i32 = 0x02; -const CRT_SERIAL_NB_BITS: i32 = 32; -const INVALID_EXT_MSG: &str = "invalid acmeIdentifier extension"; -pub const CRT_NB_DAYS_VALIDITY: u32 = 7; - -#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] -pub enum BaseSubjectAttribute { - CountryName, - GenerationQualifier, - GivenName, - Initials, - LocalityName, - Name, - OrganizationName, - OrganizationalUnitName, - Pkcs9EmailAddress, - PostalAddress, - PostalCode, - StateOrProvinceName, - Street, - Surname, - Title, -} - -#[derive(Clone, Copy, Debug, PartialEq)] -pub enum BaseHashFunction { - Sha256, - Sha384, - Sha512, -} - -impl BaseHashFunction { - pub fn list_possible_values() -> Vec<&'static str> { - vec!["sha256", "sha384", "sha512"] - } -} - -impl FromStr for BaseHashFunction { - type Err = Error; - - fn from_str(s: &str) -> Result { - let s = s.to_lowercase().replace(['-', '_'], ""); - match s.as_str() { - "sha256" => Ok(BaseHashFunction::Sha256), - "sha384" => Ok(BaseHashFunction::Sha384), - "sha512" => Ok(BaseHashFunction::Sha512), - _ => Err(format!("{s}: unknown hash function.").into()), - } - } -} - -impl fmt::Display for BaseHashFunction { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - let s = match self { - BaseHashFunction::Sha256 => "sha256", - BaseHashFunction::Sha384 => "sha384", - BaseHashFunction::Sha512 => "sha512", - }; - write!(f, "{s}") - } -} - -pub use jws_signature_algorithm::JwsSignatureAlgorithm; -pub use key_type::KeyType; -#[cfg(feature = "crypto_openssl")] -pub use openssl_certificate::{Csr, X509Certificate}; -#[cfg(feature = "crypto_openssl")] -pub use openssl_hash::HashFunction; -#[cfg(feature = "crypto_openssl")] -pub use openssl_keys::{gen_keypair, KeyPair}; -#[cfg(feature = "crypto_openssl")] -pub use openssl_subject_attribute::SubjectAttribute; -#[cfg(feature = "crypto_openssl")] -pub use openssl_version::{get_lib_name, get_lib_version}; diff --git a/acme_common/src/crypto/jws_signature_algorithm.rs b/acme_common/src/crypto/jws_signature_algorithm.rs deleted file mode 100644 index 2513e9e..0000000 --- a/acme_common/src/crypto/jws_signature_algorithm.rs +++ /dev/null @@ -1,81 +0,0 @@ -use crate::error::Error; -use std::fmt; -use std::str::FromStr; - -#[derive(Clone, Copy, Debug, PartialEq)] -pub enum JwsSignatureAlgorithm { - Hs256, - Hs384, - Hs512, - Rs256, - Es256, - Es384, - Es512, - #[cfg(feature = "ed25519")] - Ed25519, - #[cfg(feature = "ed448")] - Ed448, -} - -impl FromStr for JwsSignatureAlgorithm { - type Err = Error; - - fn from_str(s: &str) -> Result { - match s.to_lowercase().as_str() { - "hs256" => Ok(JwsSignatureAlgorithm::Hs256), - "hs384" => Ok(JwsSignatureAlgorithm::Hs384), - "hs512" => Ok(JwsSignatureAlgorithm::Hs512), - "rs256" => Ok(JwsSignatureAlgorithm::Rs256), - "es256" => Ok(JwsSignatureAlgorithm::Es256), - "es384" => Ok(JwsSignatureAlgorithm::Es384), - "es512" => Ok(JwsSignatureAlgorithm::Es512), - #[cfg(feature = "ed25519")] - "ed25519" => Ok(JwsSignatureAlgorithm::Ed25519), - #[cfg(feature = "ed448")] - "ed448" => Ok(JwsSignatureAlgorithm::Ed448), - _ => Err(format!("{s}: unknown algorithm.").into()), - } - } -} - -impl fmt::Display for JwsSignatureAlgorithm { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - let s = match self { - JwsSignatureAlgorithm::Hs256 => "HS256", - JwsSignatureAlgorithm::Hs384 => "HS384", - JwsSignatureAlgorithm::Hs512 => "HS512", - JwsSignatureAlgorithm::Rs256 => "RS256", - JwsSignatureAlgorithm::Es256 => "ES256", - JwsSignatureAlgorithm::Es384 => "ES384", - JwsSignatureAlgorithm::Es512 => "ES512", - #[cfg(feature = "ed25519")] - JwsSignatureAlgorithm::Ed25519 => "Ed25519", - #[cfg(feature = "ed448")] - JwsSignatureAlgorithm::Ed448 => "Ed448", - }; - write!(f, "{s}") - } -} - -#[cfg(test)] -mod tests { - use super::JwsSignatureAlgorithm; - use std::str::FromStr; - - #[test] - fn test_es256_from_str() { - let variants = ["ES256", "Es256", "es256"]; - for v in variants.iter() { - let a = JwsSignatureAlgorithm::from_str(v); - assert!(a.is_ok()); - let a = a.unwrap(); - assert_eq!(a, JwsSignatureAlgorithm::Es256); - } - } - - #[test] - fn test_es256_to_str() { - let a = JwsSignatureAlgorithm::Es256; - assert_eq!(a.to_string().as_str(), "ES256"); - } -} diff --git a/acme_common/src/crypto/key_type.rs b/acme_common/src/crypto/key_type.rs deleted file mode 100644 index 422abdb..0000000 --- a/acme_common/src/crypto/key_type.rs +++ /dev/null @@ -1,103 +0,0 @@ -use crate::crypto::JwsSignatureAlgorithm; -use crate::error::Error; -use std::fmt; -use std::str::FromStr; - -#[derive(Clone, Copy, Debug, PartialEq)] -pub enum KeyType { - Rsa2048, - Rsa4096, - EcdsaP256, - EcdsaP384, - EcdsaP521, - #[cfg(feature = "ed25519")] - Ed25519, - #[cfg(feature = "ed448")] - Ed448, -} - -impl KeyType { - pub fn get_default_signature_alg(&self) -> JwsSignatureAlgorithm { - match self { - KeyType::Rsa2048 | KeyType::Rsa4096 => JwsSignatureAlgorithm::Rs256, - KeyType::EcdsaP256 => JwsSignatureAlgorithm::Es256, - KeyType::EcdsaP384 => JwsSignatureAlgorithm::Es384, - KeyType::EcdsaP521 => JwsSignatureAlgorithm::Es512, - #[cfg(feature = "ed25519")] - KeyType::Ed25519 => JwsSignatureAlgorithm::Ed25519, - #[cfg(feature = "ed448")] - KeyType::Ed448 => JwsSignatureAlgorithm::Ed448, - } - } - - pub fn check_alg_compatibility(&self, alg: &JwsSignatureAlgorithm) -> Result<(), Error> { - let ok = match self { - KeyType::Rsa2048 | KeyType::Rsa4096 => *alg == JwsSignatureAlgorithm::Rs256, - KeyType::EcdsaP256 | KeyType::EcdsaP384 | KeyType::EcdsaP521 => { - *alg == self.get_default_signature_alg() - } - #[cfg(feature = "ed25519")] - KeyType::Ed25519 => *alg == self.get_default_signature_alg(), - #[cfg(feature = "ed448")] - KeyType::Ed448 => *alg == self.get_default_signature_alg(), - }; - if ok { - Ok(()) - } else { - let err_msg = format!( - "incompatible signature algorithm: {alg} cannot be used with an {self} key" - ); - Err(err_msg.into()) - } - } - - pub fn list_possible_values() -> Vec<&'static str> { - vec![ - "rsa2048", - "rsa4096", - "ecdsa-p256", - "ecdsa-p384", - "ecdsa-p521", - #[cfg(feature = "ed25519")] - "ed25519", - #[cfg(feature = "ed448")] - "ed448", - ] - } -} - -impl FromStr for KeyType { - type Err = Error; - - fn from_str(s: &str) -> Result { - match s.to_lowercase().replace('-', "_").as_str() { - "rsa2048" => Ok(KeyType::Rsa2048), - "rsa4096" => Ok(KeyType::Rsa4096), - "ecdsa_p256" => Ok(KeyType::EcdsaP256), - "ecdsa_p384" => Ok(KeyType::EcdsaP384), - "ecdsa_p521" => Ok(KeyType::EcdsaP521), - #[cfg(feature = "ed25519")] - "ed25519" => Ok(KeyType::Ed25519), - #[cfg(feature = "ed448")] - "ed448" => Ok(KeyType::Ed448), - _ => Err(format!("{s}: unknown algorithm").into()), - } - } -} - -impl fmt::Display for KeyType { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - let s = match self { - KeyType::Rsa2048 => "rsa2048", - KeyType::Rsa4096 => "rsa4096", - KeyType::EcdsaP256 => "ecdsa-p256", - KeyType::EcdsaP384 => "ecdsa-p384", - KeyType::EcdsaP521 => "ecdsa-p521", - #[cfg(feature = "ed25519")] - KeyType::Ed25519 => "ed25519", - #[cfg(feature = "ed448")] - KeyType::Ed448 => "ed448", - }; - write!(f, "{s}") - } -} diff --git a/acme_common/src/crypto/openssl_certificate.rs b/acme_common/src/crypto/openssl_certificate.rs deleted file mode 100644 index 2e8e16e..0000000 --- a/acme_common/src/crypto/openssl_certificate.rs +++ /dev/null @@ -1,203 +0,0 @@ -use super::{gen_keypair, KeyPair, KeyType, SubjectAttribute}; -use crate::b64_encode; -use crate::crypto::HashFunction; -use crate::error::Error; -use openssl::asn1::Asn1Time; -use openssl::bn::{BigNum, MsbOption}; -use openssl::hash::MessageDigest; -use openssl::stack::Stack; -use openssl::x509::extension::{BasicConstraints, SubjectAlternativeName}; -use openssl::x509::{X509Builder, X509Extension, X509NameBuilder, X509Req, X509ReqBuilder, X509}; -use std::collections::{HashMap, HashSet}; -use std::net::IpAddr; -use std::time::Duration; - -fn get_digest(digest: HashFunction, key_pair: &KeyPair) -> MessageDigest { - #[cfg(not(any(feature = "ed25519", feature = "ed448")))] - let digest = digest.native_digest(); - let _ = key_pair; - #[cfg(any(feature = "ed25519", feature = "ed448"))] - let digest = match key_pair.key_type { - #[cfg(feature = "ed25519")] - KeyType::Ed25519 => MessageDigest::null(), - #[cfg(feature = "ed448")] - KeyType::Ed448 => MessageDigest::null(), - _ => digest.native_digest(), - }; - digest -} - -pub struct Csr { - inner_csr: X509Req, -} - -impl Csr { - pub fn new( - key_pair: &KeyPair, - digest: HashFunction, - domains: &[String], - ips: &[String], - subject_attributes: &HashMap, - ) -> Result { - let mut builder = X509ReqBuilder::new()?; - builder.set_pubkey(&key_pair.inner_key)?; - if !subject_attributes.is_empty() { - let mut snb = X509NameBuilder::new()?; - for (sattr, val) in subject_attributes.iter() { - snb.append_entry_by_nid(sattr.get_nid(), val)?; - } - let name = snb.build(); - builder.set_subject_name(&name)?; - } - let ctx = builder.x509v3_context(None); - let mut san = SubjectAlternativeName::new(); - for dns in domains.iter() { - san.dns(dns); - } - for ip in ips.iter() { - san.ip(ip); - } - let san = san.build(&ctx)?; - let mut ext_stack = Stack::new()?; - ext_stack.push(san)?; - builder.add_extensions(&ext_stack)?; - let digest = get_digest(digest, key_pair); - builder.sign(&key_pair.inner_key, digest)?; - Ok(Csr { - inner_csr: builder.build(), - }) - } - - pub fn to_der_base64(&self) -> Result { - let csr = self.inner_csr.to_der()?; - let csr = b64_encode(&csr); - Ok(csr) - } - - pub fn to_pem(&self) -> Result { - let csr = self.inner_csr.to_pem()?; - Ok(String::from_utf8(csr)?) - } -} - -pub struct X509Certificate { - pub inner_cert: X509, -} - -impl X509Certificate { - pub fn from_pem(pem_data: &[u8]) -> Result { - Ok(X509Certificate { - inner_cert: X509::from_pem(pem_data)?, - }) - } - - pub fn from_pem_native(pem_data: &[u8]) -> Result { - Ok(native_tls::Certificate::from_pem(pem_data)?) - } - - pub fn from_acme_ext( - domain: &str, - acme_ext: &str, - key_type: KeyType, - digest: HashFunction, - ) -> Result<(KeyPair, Self), Error> { - let key_pair = gen_keypair(key_type)?; - let digest = get_digest(digest, &key_pair); - let inner_cert = gen_certificate(domain, &key_pair, &digest, acme_ext)?; - let cert = X509Certificate { inner_cert }; - Ok((key_pair, cert)) - } - - pub fn expires_in(&self) -> Result { - let now = Asn1Time::days_from_now(0)?; - let not_after = self.inner_cert.not_after(); - let diff = now.diff(not_after)?; - let nb_secs = diff.days * 24 * 60 * 60 + diff.secs; - let nb_secs = if nb_secs > 0 { nb_secs as u64 } else { 0 }; - Ok(Duration::from_secs(nb_secs)) - } - - pub fn subject_alt_names(&self) -> HashSet { - match self.inner_cert.subject_alt_names() { - Some(s) => s - .iter() - .filter(|v| v.dnsname().is_some() || v.ipaddress().is_some()) - .map(|v| match v.dnsname() { - Some(d) => d.to_string(), - None => match v.ipaddress() { - Some(i) => match i.len() { - 4 => { - let ipv4: [u8; 4] = [i[0], i[1], i[2], i[3]]; - IpAddr::from(ipv4).to_string() - } - 16 => { - let ipv6: [u8; 16] = [ - i[0], i[1], i[2], i[3], i[4], i[5], i[6], i[7], i[8], i[9], - i[10], i[11], i[12], i[13], i[14], i[15], - ]; - IpAddr::from(ipv6).to_string() - } - _ => String::new(), - }, - None => String::new(), - }, - }) - .collect(), - None => HashSet::new(), - } - } -} - -fn gen_certificate( - domain: &str, - key_pair: &KeyPair, - digest: &MessageDigest, - acme_ext: &str, -) -> Result { - let mut x509_name = X509NameBuilder::new()?; - x509_name.append_entry_by_text("O", super::APP_ORG)?; - let ca_name = format!("{} TLS-ALPN-01 Authority", super::APP_NAME); - x509_name.append_entry_by_text("CN", &ca_name)?; - let x509_name = x509_name.build(); - - let mut builder = X509Builder::new()?; - builder.set_version(super::X509_VERSION)?; - let serial_number = { - let mut serial = BigNum::new()?; - serial.rand(super::CRT_SERIAL_NB_BITS - 1, MsbOption::MAYBE_ZERO, false)?; - serial.to_asn1_integer()? - }; - builder.set_serial_number(&serial_number)?; - builder.set_subject_name(&x509_name)?; - builder.set_issuer_name(&x509_name)?; - builder.set_pubkey(&key_pair.inner_key)?; - let not_before = Asn1Time::days_from_now(0)?; - builder.set_not_before(¬_before)?; - let not_after = Asn1Time::days_from_now(super::CRT_NB_DAYS_VALIDITY)?; - builder.set_not_after(¬_after)?; - - builder.append_extension(BasicConstraints::new().build()?)?; - let ctx = builder.x509v3_context(None, None); - let san_ext = SubjectAlternativeName::new().dns(domain).build(&ctx)?; - builder.append_extension(san_ext)?; - - if !acme_ext.is_empty() { - let ctx = builder.x509v3_context(None, None); - let mut v: Vec<&str> = acme_ext.split('=').collect(); - let value = v.pop().ok_or_else(|| Error::from(super::INVALID_EXT_MSG))?; - let acme_ext_name = v.pop().ok_or_else(|| Error::from(super::INVALID_EXT_MSG))?; - if !v.is_empty() { - return Err(Error::from(super::INVALID_EXT_MSG)); - } - #[allow(deprecated)] - let acme_ext = X509Extension::new(None, Some(&ctx), acme_ext_name, value) - .map_err(|_| Error::from(super::INVALID_EXT_MSG))?; - builder - .append_extension(acme_ext) - .map_err(|_| Error::from(super::INVALID_EXT_MSG))?; - } - - builder.sign(&key_pair.inner_key, *digest)?; - let cert = builder.build(); - Ok(cert) -} diff --git a/acme_common/src/crypto/openssl_hash.rs b/acme_common/src/crypto/openssl_hash.rs deleted file mode 100644 index 18fdcd6..0000000 --- a/acme_common/src/crypto/openssl_hash.rs +++ /dev/null @@ -1,34 +0,0 @@ -use crate::error::Error; -use openssl::hash::MessageDigest; -use openssl::pkey::PKey; -use openssl::sha::{sha256, sha384, sha512}; -use openssl::sign::Signer; - -pub type HashFunction = super::BaseHashFunction; - -impl HashFunction { - pub fn hash(&self, data: &[u8]) -> Vec { - match self { - HashFunction::Sha256 => sha256(data).to_vec(), - HashFunction::Sha384 => sha384(data).to_vec(), - HashFunction::Sha512 => sha512(data).to_vec(), - } - } - - pub fn hmac(&self, key: &[u8], data: &[u8]) -> Result, Error> { - let key = PKey::hmac(key)?; - let h_func = self.native_digest(); - let mut signer = Signer::new(h_func, &key)?; - signer.update(data)?; - let res = signer.sign_to_vec()?; - Ok(res) - } - - pub(crate) fn native_digest(&self) -> MessageDigest { - match self { - HashFunction::Sha256 => MessageDigest::sha256(), - HashFunction::Sha384 => MessageDigest::sha384(), - HashFunction::Sha512 => MessageDigest::sha512(), - } - } -} diff --git a/acme_common/src/crypto/openssl_keys.rs b/acme_common/src/crypto/openssl_keys.rs deleted file mode 100644 index fc1b03b..0000000 --- a/acme_common/src/crypto/openssl_keys.rs +++ /dev/null @@ -1,343 +0,0 @@ -use crate::b64_encode; -use crate::crypto::{HashFunction, JwsSignatureAlgorithm, KeyType}; -use crate::error::Error; -use openssl::bn::{BigNum, BigNumContext}; -use openssl::ec::{Asn1Flag, EcGroup, EcKey}; -use openssl::ecdsa::EcdsaSig; -use openssl::hash::MessageDigest; -use openssl::nid::Nid; -use openssl::pkey::{Id, PKey, Private}; -use openssl::rsa::Rsa; -use openssl::sign::Signer; -use serde_json::json; -use serde_json::value::Value; - -macro_rules! get_key_type { - ($key: expr) => { - match $key.id() { - Id::RSA => match $key.rsa()?.size() { - 256 => KeyType::Rsa2048, - 512 => KeyType::Rsa4096, - s => { - return Err(format!("{}: unsupported RSA key size", s * 8).into()); - } - }, - Id::EC => match $key.ec_key()?.group().curve_name() { - Some(Nid::X9_62_PRIME256V1) => KeyType::EcdsaP256, - Some(Nid::SECP384R1) => KeyType::EcdsaP384, - Some(Nid::SECP521R1) => KeyType::EcdsaP521, - Some(nid) => { - return Err(format!("{:?}: unsupported EC key", nid).into()); - } - None => { - return Err("unsupported EC key".into()); - } - }, - #[cfg(feature = "ed25519")] - Id::ED25519 => KeyType::Ed25519, - #[cfg(feature = "ed448")] - Id::ED448 => KeyType::Ed448, - _ => { - return Err("unsupported key type".into()); - } - } - }; -} - -macro_rules! get_ecdsa_sig_part { - ($part: expr, $size: ident) => {{ - let mut p = $part.to_vec(); - let length = p.len(); - if length != $size { - let mut s: Vec = Vec::with_capacity($size); - s.resize_with($size - length, || 0); - s.append(&mut p); - s - } else { - p - } - }}; -} - -#[derive(Clone, Debug)] -pub struct KeyPair { - pub key_type: KeyType, - pub inner_key: PKey, -} - -impl KeyPair { - pub fn from_der(der_data: &[u8]) -> Result { - let inner_key = PKey::private_key_from_der(der_data)?; - let key_type = get_key_type!(inner_key); - Ok(KeyPair { - key_type, - inner_key, - }) - } - - pub fn from_pem(pem_data: &[u8]) -> Result { - let inner_key = PKey::private_key_from_pem(pem_data)?; - let key_type = get_key_type!(inner_key); - Ok(KeyPair { - key_type, - inner_key, - }) - } - - pub fn private_key_to_der(&self) -> Result, Error> { - self.inner_key.private_key_to_der().map_err(Error::from) - } - - pub fn private_key_to_pem(&self) -> Result, Error> { - self.inner_key - .private_key_to_pem_pkcs8() - .map_err(Error::from) - } - - pub fn public_key_to_pem(&self) -> Result, Error> { - self.inner_key.public_key_to_pem().map_err(Error::from) - } - - pub fn sign(&self, alg: &JwsSignatureAlgorithm, data: &[u8]) -> Result, Error> { - self.key_type.check_alg_compatibility(alg)?; - match alg { - JwsSignatureAlgorithm::Hs256 - | JwsSignatureAlgorithm::Hs384 - | JwsSignatureAlgorithm::Hs512 => Err(format!( - "{} key pair cannot be used for the {alg} signature algorithm", - self.key_type - ) - .into()), - JwsSignatureAlgorithm::Rs256 => self.sign_rsa(&MessageDigest::sha256(), data), - JwsSignatureAlgorithm::Es256 => self.sign_ecdsa(&HashFunction::Sha256, data), - JwsSignatureAlgorithm::Es384 => self.sign_ecdsa(&HashFunction::Sha384, data), - JwsSignatureAlgorithm::Es512 => self.sign_ecdsa(&HashFunction::Sha512, data), - #[cfg(feature = "ed25519")] - JwsSignatureAlgorithm::Ed25519 => self.sign_eddsa(data), - #[cfg(feature = "ed448")] - JwsSignatureAlgorithm::Ed448 => self.sign_eddsa(data), - } - } - - fn sign_rsa(&self, hash_func: &MessageDigest, data: &[u8]) -> Result, Error> { - let mut signer = Signer::new(*hash_func, &self.inner_key)?; - signer.update(data)?; - let signature = signer.sign_to_vec()?; - Ok(signature) - } - - fn sign_ecdsa(&self, hash_func: &HashFunction, data: &[u8]) -> Result, Error> { - let fingerprint = hash_func.hash(data); - let signature = EcdsaSig::sign(&fingerprint, self.inner_key.ec_key()?.as_ref())?; - let sig_size = match self.key_type { - KeyType::EcdsaP256 => 32, - KeyType::EcdsaP384 => 48, - KeyType::EcdsaP521 => 66, - _ => { - return Err("not an ecdsa key".into()); - } - }; - let r = get_ecdsa_sig_part!(signature.r(), sig_size); - let mut s = get_ecdsa_sig_part!(signature.s(), sig_size); - let mut signature = r; - signature.append(&mut s); - Ok(signature) - } - - #[cfg(any(feature = "ed25519", feature = "ed448"))] - fn sign_eddsa(&self, data: &[u8]) -> Result, Error> { - let mut signer = Signer::new_without_digest(&self.inner_key)?; - let signature = signer.sign_oneshot_to_vec(data)?; - Ok(signature) - } - - pub fn jwk_public_key(&self) -> Result { - self.get_jwk_public_key(false) - } - - pub fn jwk_public_key_thumbprint(&self) -> Result { - self.get_jwk_public_key(true) - } - - fn get_jwk_public_key(&self, thumbprint: bool) -> Result { - match self.key_type { - KeyType::Rsa2048 | KeyType::Rsa4096 => self.get_rsa_jwk(thumbprint), - KeyType::EcdsaP256 | KeyType::EcdsaP384 | KeyType::EcdsaP521 => { - self.get_ecdsa_jwk(thumbprint) - } - #[cfg(feature = "ed25519")] - KeyType::Ed25519 => self.get_eddsa_jwk(thumbprint), - #[cfg(feature = "ed448")] - KeyType::Ed448 => self.get_eddsa_jwk(thumbprint), - } - } - - fn get_rsa_jwk(&self, thumbprint: bool) -> Result { - let rsa = self.inner_key.rsa().unwrap(); - let e = rsa.e(); - let n = rsa.n(); - let e = b64_encode(&e.to_vec()); - let n = b64_encode(&n.to_vec()); - let jwk = if thumbprint { - json!({ - "kty": "RSA", - "e": e, - "n": n, - }) - } else { - json!({ - "alg": "RS256", - "kty": "RSA", - "use": "sig", - "e": e, - "n": n, - }) - }; - Ok(jwk) - } - - fn get_ecdsa_jwk(&self, thumbprint: bool) -> Result { - let (crv, alg, size, curve) = match self.key_type { - KeyType::EcdsaP256 => ("P-256", "ES256", 32, Nid::X9_62_PRIME256V1), - KeyType::EcdsaP384 => ("P-384", "ES384", 48, Nid::SECP384R1), - KeyType::EcdsaP521 => ("P-521", "ES512", 66, Nid::SECP521R1), - _ => { - return Err("not an ECDSA elliptic curve".into()); - } - }; - let group = EcGroup::from_curve_name(curve).unwrap(); - let mut ctx = BigNumContext::new().unwrap(); - let mut x = BigNum::new().unwrap(); - let mut y = BigNum::new().unwrap(); - self.inner_key - .ec_key() - .unwrap() - .public_key() - .affine_coordinates_gfp(&group, &mut x, &mut y, &mut ctx)?; - let x = b64_encode(&x.to_vec_padded(size)?); - let y = b64_encode(&y.to_vec_padded(size)?); - let jwk = if thumbprint { - json!({ - "crv": crv, - "kty": "EC", - "x": x, - "y": y, - }) - } else { - json!({ - "alg": alg, - "crv": crv, - "kty": "EC", - "use": "sig", - "x": x, - "y": y, - }) - }; - Ok(jwk) - } - - #[cfg(any(feature = "ed25519", feature = "ed448"))] - fn get_eddsa_jwk(&self, thumbprint: bool) -> Result { - let crv = match self.key_type { - #[cfg(feature = "ed25519")] - KeyType::Ed25519 => "Ed25519", - #[cfg(feature = "ed448")] - KeyType::Ed448 => "Ed448", - _ => { - return Err("not an EdDSA elliptic curve".into()); - } - }; - - // /!\ WARNING: HAZARDOUS AND UGLY CODE /!\ - // - // I couldn't find a way to get the value of `x` using the OpenSSL - // interface, therefore I had to hack my way arround. - // - // The idea behind this hack is to export the public key in PEM, then - // get the PEM base64 part, convert it to base64url without padding - // and finally truncate the first part so only the value of `x` - // remains. - - // -----BEGIN UGLY----- - let mut x = String::new(); - let public_pem = self.public_key_to_pem()?; - let public_pem = String::from_utf8(public_pem)?; - for pem_line in public_pem.lines() { - if !pem_line.is_empty() && !pem_line.starts_with("-----") { - x += &pem_line - .trim() - .trim_end_matches('=') - .replace('/', "_") - .replace('+', "-"); - } - } - x.replace_range(..16, ""); - // -----END UGLY----- - - let jwk = if thumbprint { - json!({ - "crv": crv, - "kty": "OKP", - "x": &x, - }) - } else { - json!({ - "alg": "EdDSA", - "crv": crv, - "kty": "OKP", - "use": "sig", - "x": &x, - }) - }; - Ok(jwk) - } -} - -fn gen_rsa_pair(nb_bits: u32) -> Result, Error> { - let priv_key = Rsa::generate(nb_bits)?; - let pk = PKey::from_rsa(priv_key).map_err(|_| Error::from(""))?; - Ok(pk) -} - -fn gen_ec_pair(nid: Nid) -> Result, Error> { - let mut group = EcGroup::from_curve_name(nid)?; - - // Use NAMED_CURVE format; OpenSSL 1.0.1 and 1.0.2 default to EXPLICIT_CURVE which won't work (see #9) - group.set_asn1_flag(Asn1Flag::NAMED_CURVE); - - let ec_priv_key = EcKey::generate(&group).map_err(|_| Error::from(""))?; - let pk = PKey::from_ec_key(ec_priv_key).map_err(|_| Error::from(""))?; - Ok(pk) -} - -#[cfg(feature = "ed25519")] -fn gen_ed25519_pair() -> Result, Error> { - let pk = PKey::generate_ed25519().map_err(|_| Error::from(""))?; - Ok(pk) -} - -#[cfg(feature = "ed448")] -fn gen_ed448_pair() -> Result, Error> { - let pk = PKey::generate_ed448().map_err(|_| Error::from(""))?; - Ok(pk) -} - -pub fn gen_keypair(key_type: KeyType) -> Result { - let priv_key = match key_type { - KeyType::Rsa2048 => gen_rsa_pair(2048), - KeyType::Rsa4096 => gen_rsa_pair(4096), - KeyType::EcdsaP256 => gen_ec_pair(Nid::X9_62_PRIME256V1), - KeyType::EcdsaP384 => gen_ec_pair(Nid::SECP384R1), - KeyType::EcdsaP521 => gen_ec_pair(Nid::SECP521R1), - #[cfg(feature = "ed25519")] - KeyType::Ed25519 => gen_ed25519_pair(), - #[cfg(feature = "ed448")] - KeyType::Ed448 => gen_ed448_pair(), - } - .map_err(|_| Error::from(format!("unable to generate a {key_type} key pair")))?; - let key_pair = KeyPair { - key_type, - inner_key: priv_key, - }; - Ok(key_pair) -} diff --git a/acme_common/src/crypto/openssl_subject_attribute.rs b/acme_common/src/crypto/openssl_subject_attribute.rs deleted file mode 100644 index 35477a6..0000000 --- a/acme_common/src/crypto/openssl_subject_attribute.rs +++ /dev/null @@ -1,25 +0,0 @@ -use openssl::nid::Nid; - -pub type SubjectAttribute = super::BaseSubjectAttribute; - -impl SubjectAttribute { - pub fn get_nid(&self) -> Nid { - match self { - SubjectAttribute::CountryName => Nid::COUNTRYNAME, - SubjectAttribute::GenerationQualifier => Nid::GENERATIONQUALIFIER, - SubjectAttribute::GivenName => Nid::GIVENNAME, - SubjectAttribute::Initials => Nid::INITIALS, - SubjectAttribute::LocalityName => Nid::LOCALITYNAME, - SubjectAttribute::Name => Nid::NAME, - SubjectAttribute::OrganizationName => Nid::ORGANIZATIONNAME, - SubjectAttribute::OrganizationalUnitName => Nid::ORGANIZATIONALUNITNAME, - SubjectAttribute::Pkcs9EmailAddress => Nid::PKCS9_EMAILADDRESS, - SubjectAttribute::PostalAddress => Nid::POSTALADDRESS, - SubjectAttribute::PostalCode => Nid::POSTALCODE, - SubjectAttribute::StateOrProvinceName => Nid::STATEORPROVINCENAME, - SubjectAttribute::Street => Nid::STREETADDRESS, - SubjectAttribute::Surname => Nid::SURNAME, - SubjectAttribute::Title => Nid::TITLE, - } - } -} diff --git a/acme_common/src/crypto/openssl_version.rs b/acme_common/src/crypto/openssl_version.rs deleted file mode 100644 index 682f019..0000000 --- a/acme_common/src/crypto/openssl_version.rs +++ /dev/null @@ -1,27 +0,0 @@ -pub fn get_lib_name() -> String { - env!("ACMED_TLS_LIB_NAME").to_string() -} - -pub fn get_lib_version() -> String { - let v = openssl::version::number() as u64; - let mut version = vec![]; - for i in 0..3 { - let n = get_openssl_version_unit(v, i); - version.push(format!("{n}")); - } - let version = version.join("."); - let p = get_openssl_version_unit(v, 3); - if p != 0 { - let p = p + 0x60; - let p = std::char::from_u32(p as u32).unwrap(); - format!("{version}{p}") - } else { - version - } -} - -fn get_openssl_version_unit(n: u64, pos: u32) -> u64 { - let p = 0x000f_f000_0000 >> (8 * pos); - let n = n & p; - n >> (8 * (3 - pos) + 4) -} diff --git a/acme_common/src/error.rs b/acme_common/src/error.rs deleted file mode 100644 index 7f25c7b..0000000 --- a/acme_common/src/error.rs +++ /dev/null @@ -1,133 +0,0 @@ -use std::fmt; - -#[derive(Clone, Debug)] -pub struct Error { - pub message: String, -} - -impl Error { - pub fn prefix(&self, prefix: &str) -> Self { - Error { - message: format!("{prefix}: {}", &self.message), - } - } -} - -impl fmt::Display for Error { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{}", self.message) - } -} - -impl From<&str> for Error { - fn from(error: &str) -> Self { - Error { - message: error.to_string(), - } - } -} - -impl From for Error { - fn from(error: String) -> Self { - error.as_str().into() - } -} - -impl From<&String> for Error { - fn from(error: &String) -> Self { - error.as_str().into() - } -} - -impl From for Error { - fn from(error: std::io::Error) -> Self { - format!("IO error: {error}").into() - } -} - -impl From for Error { - fn from(error: std::net::AddrParseError) -> Self { - format!("{error}").into() - } -} - -impl From for Error { - fn from(error: std::string::FromUtf8Error) -> Self { - format!("UTF-8 error: {error}").into() - } -} - -impl From for Error { - fn from(error: std::sync::mpsc::RecvError) -> Self { - format!("MSPC receiver error: {error}").into() - } -} - -impl From for Error { - fn from(error: std::time::SystemTimeError) -> Self { - format!("SystemTimeError difference: {:?}", error.duration()).into() - } -} - -impl From for Error { - fn from(error: base64::DecodeError) -> Self { - format!("base 64 decode error: {error}").into() - } -} - -impl From for Error { - fn from(error: syslog::Error) -> Self { - format!("syslog error: {error}").into() - } -} - -impl From for Error { - fn from(error: toml::de::Error) -> Self { - format!("IO error: {error}").into() - } -} - -impl From for Error { - fn from(error: serde_json::error::Error) -> Self { - format!("IO error: {error}").into() - } -} - -impl From for Error { - fn from(error: reqwest::Error) -> Self { - format!("HTTP error: {error}").into() - } -} - -impl From for Error { - fn from(error: glob::PatternError) -> Self { - format!("pattern error: {error}").into() - } -} - -impl From for Error { - fn from(error: minijinja::Error) -> Self { - format!("template error: {error}").into() - } -} - -#[cfg(feature = "crypto_openssl")] -impl From for Error { - fn from(error: native_tls::Error) -> Self { - format!("{error}").into() - } -} - -#[cfg(feature = "crypto_openssl")] -impl From for Error { - fn from(error: openssl::error::ErrorStack) -> Self { - format!("{error}").into() - } -} - -#[cfg(unix)] -impl From for Error { - fn from(error: nix::Error) -> Self { - format!("{error}").into() - } -} diff --git a/acme_common/src/lib.rs b/acme_common/src/lib.rs deleted file mode 100644 index 86c3fe0..0000000 --- a/acme_common/src/lib.rs +++ /dev/null @@ -1,76 +0,0 @@ -use base64::Engine; -use daemonize::Daemonize; -use std::fs::File; -use std::io::prelude::*; -use std::{fs, process}; - -pub mod crypto; -pub mod error; -pub mod logs; -#[cfg(test)] -mod tests; - -macro_rules! exit_match { - ($e: expr) => { - match $e { - Ok(_) => {} - Err(e) => { - log::error!("error: {e}"); - std::process::exit(3); - } - } - }; -} - -pub fn to_idna(domain_name: &str) -> Result { - let mut idna_parts = vec![]; - let parts: Vec<&str> = domain_name.split('.').collect(); - for name in parts.iter() { - let raw_name = name.to_lowercase(); - let idna_name = if name.is_ascii() { - raw_name - } else { - let idna_name = punycode::encode(&raw_name) - .map_err(|_| error::Error::from("IDNA encoding failed."))?; - format!("xn--{idna_name}") - }; - idna_parts.push(idna_name); - } - Ok(idna_parts.join(".")) -} - -pub fn b64_encode>(input: &T) -> String { - base64::engine::general_purpose::URL_SAFE_NO_PAD.encode(input) -} - -pub fn b64_decode>(input: &T) -> Result, error::Error> { - let res = base64::engine::general_purpose::URL_SAFE_NO_PAD.decode(input)?; - Ok(res) -} - -pub fn init_server(foreground: bool, pid_file: Option<&str>) { - if !foreground { - let mut daemonize = Daemonize::new(); - if let Some(f) = pid_file { - daemonize = daemonize.pid_file(f); - } - exit_match!(daemonize.start()); - } else if let Some(f) = pid_file { - exit_match!(write_pid_file(f).map_err(|e| e.prefix(f))); - } -} - -fn write_pid_file(pid_file: &str) -> Result<(), error::Error> { - let data = format!("{}\n", process::id()).into_bytes(); - let mut file = File::create(pid_file)?; - file.write_all(&data)?; - file.sync_all()?; - Ok(()) -} - -pub fn clean_pid_file(pid_file: Option<&str>) -> Result<(), error::Error> { - if let Some(f) = pid_file { - fs::remove_file(f)?; - } - Ok(()) -} diff --git a/acme_common/src/logs.rs b/acme_common/src/logs.rs deleted file mode 100644 index eacede0..0000000 --- a/acme_common/src/logs.rs +++ /dev/null @@ -1,86 +0,0 @@ -use crate::error::Error; -use env_logger::Builder; -use log::LevelFilter; -use syslog::Facility; - -pub const DEFAULT_LOG_SYSTEM: LogSystem = LogSystem::SysLog; -pub const DEFAULT_LOG_LEVEL: LevelFilter = LevelFilter::Warn; - -#[derive(Debug, PartialEq, Eq)] -pub enum LogSystem { - SysLog, - StdErr, -} - -fn get_loglevel(log_level: Option<&str>) -> Result { - let level = match log_level { - Some(v) => match v { - "error" => LevelFilter::Error, - "warn" => LevelFilter::Warn, - "info" => LevelFilter::Info, - "debug" => LevelFilter::Debug, - "trace" => LevelFilter::Trace, - _ => { - return Err(format!("{v}: invalid log level").into()); - } - }, - None => DEFAULT_LOG_LEVEL, - }; - Ok(level) -} - -fn set_log_syslog(log_level: LevelFilter) -> Result<(), Error> { - syslog::init( - Facility::LOG_DAEMON, - log_level, - Some(env!("CARGO_PKG_NAME")), - )?; - Ok(()) -} - -fn set_log_stderr(log_level: LevelFilter) -> Result<(), Error> { - let mut builder = Builder::from_env("ACMED_LOG_LEVEL"); - builder.filter_level(log_level); - builder.init(); - Ok(()) -} - -pub fn set_log_system( - log_level: Option<&str>, - has_syslog: bool, - has_stderr: bool, -) -> Result<(LogSystem, LevelFilter), Error> { - let log_level = get_loglevel(log_level)?; - let logtype = if has_syslog { - LogSystem::SysLog - } else if has_stderr { - LogSystem::StdErr - } else { - DEFAULT_LOG_SYSTEM - }; - match logtype { - LogSystem::SysLog => set_log_syslog(log_level)?, - LogSystem::StdErr => set_log_stderr(log_level)?, - }; - Ok((logtype, log_level)) -} - -#[cfg(test)] -mod tests { - use super::{set_log_system, DEFAULT_LOG_LEVEL, DEFAULT_LOG_SYSTEM}; - - #[test] - fn test_invalid_level() { - let ret = set_log_system(Some("invalid"), false, false); - assert!(ret.is_err()); - } - - #[test] - fn test_default_values() { - let ret = set_log_system(None, false, false); - assert!(ret.is_ok()); - let (logtype, log_level) = ret.unwrap(); - assert_eq!(logtype, DEFAULT_LOG_SYSTEM); - assert_eq!(log_level, DEFAULT_LOG_LEVEL); - } -} diff --git a/acme_common/src/tests.rs b/acme_common/src/tests.rs deleted file mode 100644 index afa7203..0000000 --- a/acme_common/src/tests.rs +++ /dev/null @@ -1,5 +0,0 @@ -mod certificate; -mod crypto_keys; -mod hash; -mod idna; -mod jws_signature_algorithm; diff --git a/acme_common/src/tests/certificate.rs b/acme_common/src/tests/certificate.rs deleted file mode 100644 index aef03c9..0000000 --- a/acme_common/src/tests/certificate.rs +++ /dev/null @@ -1,181 +0,0 @@ -use crate::crypto::{HashFunction, KeyType, X509Certificate, CRT_NB_DAYS_VALIDITY}; -use std::collections::HashSet; -use std::iter::FromIterator; - -const CERTIFICATE_P256_DOMAINS_PEM: &str = r#"-----BEGIN CERTIFICATE----- -MIICtDCCAZygAwIBAgIIf5BEPlNrrYkwDQYJKoZIhvcNAQELBQAwKDEmMCQGA1UE -AxMdUGViYmxlIEludGVybWVkaWF0ZSBDQSAyZDE2ODgwHhcNMjAwODI1MTMwMzE3 -WhcNMjUwODI1MTMwMzE3WjAYMRYwFAYDVQQDEw1sb2NhbC53aGF0LnRmMFkwEwYH -KoZIzj0CAQYIKoZIzj0DAQcDQgAE0c/unUqpoOMxxc8e1pkpPQTSsh2irQruOJgd -ITN9WLC4mzFSJ/ad64TFi4HsCFNd7mv/QRH6rW1s3LbocEvBuqOBvDCBuTAOBgNV -HQ8BAf8EBAMCBaAwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMAwGA1Ud -EwEB/wQCMAAwHQYDVR0OBBYEFGjf1TWIZyE+QP9SGkBN6dfviIsaMB8GA1UdIwQY -MBaAFLgD5DMU2ijpIxlxaAv82sQvb5ofMDoGA1UdEQQzMDGCDWxvY2FsLndoYXQu -dGaCDzEubG9jYWwud2hhdC50ZoIPMi5sb2NhbC53aGF0LnRmMA0GCSqGSIb3DQEB -CwUAA4IBAQDREOAU2JwHfSPGt4SYlQ3OmFl4HHI2f+XyNE/09uZVteM0aChkntgX -rAZltuAAX+coSlgv3a04hJBqioDG1R9MFtf4LZBhfkgZwbzucMt8Ga3QL3XFXOkn -FlOwb/ZEIjFsBFQWt1ZSA85WxIVkGsgMfQeGpu/p8gEmJAE5l0qHEVFP9cYNsIqg -wsUGwZzPZFLsBXurM2cEA7cTt2HryVXlQWl8QI5YFpIpa43itYaerfMldfIfNdJ9 -8GLZPEfJb6t/UYYexXEkpQY9wGZkaTWvYeItuC0YlPY9RUCAl48Q85Yjf37Wbm5z -f810HGl+/c6ttyoHKmLfY/GcX07AUcLc ------END CERTIFICATE-----"#; -const CERTIFICATE_P256_IP_PEM: &str = r#"-----BEGIN CERTIFICATE----- -MIICkTCCAXmgAwIBAgIIMW1X7DjQOFgwDQYJKoZIhvcNAQELBQAwKDEmMCQGA1UE -AxMdUGViYmxlIEludGVybWVkaWF0ZSBDQSAxYWM3MzcwHhcNMjAwODI1MTQzMjQw -WhcNMjUwODI1MTQzMjQwWjAOMQwwCgYDVQQDEwM6OjEwWTATBgcqhkjOPQIBBggq -hkjOPQMBBwNCAASF+MvxX7GBAVe3McuAc+0emdFpBfAQG4mt9j8417qT76qHHyJ6 -oIHRNXAUxh4J78ihrvyph8TvqND73Nxk8Jj9o4GjMIGgMA4GA1UdDwEB/wQEAwIF -oDAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwDAYDVR0TAQH/BAIwADAd -BgNVHQ4EFgQU5R7EGzjpZqrs2o/ZwuBqNHlMB2AwHwYDVR0jBBgwFoAUhEUnWREW -GoAScr1wv/aXHTGOVoswIQYDVR0RBBowGIcQAAAAAAAAAAAAAAAAAAAAAYcEfwAA -ATANBgkqhkiG9w0BAQsFAAOCAQEAS8oRpjGakUU+KRtXCGoVlXgKYFe3u/G2aFMF -soApjvwd3L1W9b3bsT4FquF7F5qB6TGBwiXoNBoDAeVhRcUsHbmN8GZRUaq2TEsm -MwpPr8L4rqeRIuxY85AqmbGfMuFUie6r4FbwelnBniO0eMQkTW/XY41rbhGZ+lmj -DTQy08oj0892py2U/YbkL3JnCBwBba//f/Ji7nnSKdJl4Yd1iguA0nbdElcWaKk3 -ij3t17FSNeI5uMOI3TRBr4k4bu3ZMnuN2DYFPnL6GiSEhyNrxaiac8xKuOXBICmJ -oyO7pZVvc5cDcP/USPcWJYcnR9gvuL8snQdFpWND8H19eZ+i0g== ------END CERTIFICATE-----"#; -const CERTIFICATE_P256_DOMAINS_IP_PEM: &str = r#"-----BEGIN CERTIFICATE----- -MIICzDCCAbSgAwIBAgIIff0SyxJBhtMwDQYJKoZIhvcNAQELBQAwKDEmMCQGA1UE -AxMdUGViYmxlIEludGVybWVkaWF0ZSBDQSAxYWM3MzcwHhcNMjAwODI1MTQzNjE1 -WhcNMjUwODI1MTQzNjE1WjAYMRYwFAYDVQQDEw1sb2NhbC53aGF0LnRmMFkwEwYH -KoZIzj0CAQYIKoZIzj0DAQcDQgAE7Jp4AmF0TTcYfUy4TtZhN4bXn4DXWnqF0I6i -Yvz4kc0r2L01nrUrICg2bmCFM7BU9pr9fcCDodH3ZuhlRqBAf6OB1DCB0TAOBgNV -HQ8BAf8EBAMCBaAwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMAwGA1Ud -EwEB/wQCMAAwHQYDVR0OBBYEFHV0lnh55aQGfljcsjNkzZa4lTG6MB8GA1UdIwQY -MBaAFIRFJ1kRFhqAEnK9cL/2lx0xjlaLMFIGA1UdEQRLMEmCDWxvY2FsLndoYXQu -dGaCDzEubG9jYWwud2hhdC50ZoIPMi5sb2NhbC53aGF0LnRmhwR/AAABhxAAAAAA -AAAAAAAAAAAAAAABMA0GCSqGSIb3DQEBCwUAA4IBAQC3VmoTlrrTCWCd4eUB4RSB -+080uco6Jl7VMqcY5F+eG1S7p4Kqz6kc1wiiKB8ILA94hdP1qTbfphdGllYiEvbs -urj0x62cm5URahEDx4xn+dQkmh4XiiZgZVw2ccphjqJqJa28GsuR2zAxSkKMDnB7 -eX1G4/Av0XE7RqJ3Frq8qa5EjjLJTw0iEaWS5NGtZxMqWEIetCgb0IDZNxNvbeAv -mmH6qnF3xQPx5FkwP/Yw4d9T4KhSHNf2/tImIlbuk3SEsOglGbKNY1juor8uw+J2 -5XsUZxD5QiDbCFd3dGmH58XmkiQHXs8hhIbhu9ZLgp+fNv0enVMHTTI1gGpZ5MPm ------END CERTIFICATE-----"#; -const CERTIFICATE_EXPIRED_PEM: &str = r#"-----BEGIN CERTIFICATE----- -MIIEsTCCA5mgAwIBAgISBApMImYflPdX7BYLjinQ+ErUMA0GCSqGSIb3DQEBCwUA -MEoxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1MZXQncyBFbmNyeXB0MSMwIQYDVQQD -ExpMZXQncyBFbmNyeXB0IEF1dGhvcml0eSBYMzAeFw0xOTExMzAxODQxNTZaFw0y -MDAyMjgxODQxNTZaMBExDzANBgNVBAMTBmJ6aC50ZjB2MBAGByqGSM49AgEGBSuB -BAAiA2IABLSEIYJpT2SM+F9mEzFypkqbBm64dgX0KnyZuYGB2qHHsBLIBBK5Ev9Y -vPvYb8lzX3uJFHPn0JwPpGR0YBzPHBspyvwrhedokt8pNFEDC1eE4BH9XVN35utt -EGP1ZT92mKOCAnYwggJyMA4GA1UdDwEB/wQEAwIHgDAdBgNVHSUEFjAUBggrBgEF -BQcDAQYIKwYBBQUHAwIwDAYDVR0TAQH/BAIwADAdBgNVHQ4EFgQUOALpvHYbvHbQ -GcrtL0I4s/W/S58wHwYDVR0jBBgwFoAUqEpqYwR93brm0Tm3pkVl7/Oo7KEwbwYI -KwYBBQUHAQEEYzBhMC4GCCsGAQUFBzABhiJodHRwOi8vb2NzcC5pbnQteDMubGV0 -c2VuY3J5cHQub3JnMC8GCCsGAQUFBzAChiNodHRwOi8vY2VydC5pbnQteDMubGV0 -c2VuY3J5cHQub3JnLzAtBgNVHREEJjAkggZiemgudGaCDm10YS1zdHMuYnpoLnRm -ggp3d3cuYnpoLnRmMEwGA1UdIARFMEMwCAYGZ4EMAQIBMDcGCysGAQQBgt8TAQEB -MCgwJgYIKwYBBQUHAgEWGmh0dHA6Ly9jcHMubGV0c2VuY3J5cHQub3JnMIIBAwYK -KwYBBAHWeQIEAgSB9ASB8QDvAHYAb1N2rDHwMRnYmQCkURX/dxUcEdkCwQApBo2y -CJo32RMAAAFuvdWC7QAABAMARzBFAiBgCoazSI4unyx09P8KYxdIfMZsG/fMtzkF -ciBDB9gcJQIhAPZMsnjqr4IqpyHyvauqrWoGqlFBcBCmogZCuhQXAnv5AHUAB7dc -G+V9aP/xsMYdIxXHuuZXfFeUt2ruvGE6GmnTohwAAAFuvdWC7gAABAMARjBEAiAO -z7sHUA42VEQkicrWb5A4WjNGWV7NxpSDdb2XQ2Q1OwIgRaiEMrHfyT797O7Fvbk2 -cL6rnnmDJOyxIAC4Dxe7NVwwDQYJKoZIhvcNAQELBQADggEBAFaNvfsGKqBuJ9m7 -qRNqVmC7UHzGym+TPBLiXncwFIaWt0ncRHb6qfGCCETeAplhPv8uoOrzQQwTKwr3 -eMDtdmK+9smnQZ4AjUsscsrbkGwMWOOmIRm/tCwQZ0dFnl1ySZDuaoCG7v/uRE4A -HXtNAeVOKuE7BOISvvssFajxLifmFixifWRwEnimTffjnIX6xqol+2bcxMuLWxt9 -HmjTgcY4JMMcOAiNk3roJK9ayMi7jn0Cd097BFnvx08+oWSMOZ29hFHMHp3KCSzT -bQg4DAU6E9VT+pvyGsc1NNyREKxOlDkam3CqfYc0oAowjn11MmDac2aKP8Pyt4pk -ehm+yKg= ------END CERTIFICATE-----"#; - -#[test] -fn test_san_domains() { - let san = vec!["local.what.tf", "1.local.what.tf", "2.local.what.tf"]; - let san = HashSet::from_iter(san.iter().map(|v| v.to_string())); - let crt = X509Certificate::from_pem(CERTIFICATE_P256_DOMAINS_PEM.as_bytes()).unwrap(); - assert_eq!(crt.subject_alt_names(), san); -} - -#[test] -fn test_san_ip() { - let san = vec!["127.0.0.1", "::1"]; - let san = HashSet::from_iter(san.iter().map(|v| v.to_string())); - let crt = X509Certificate::from_pem(CERTIFICATE_P256_IP_PEM.as_bytes()).unwrap(); - assert_eq!(crt.subject_alt_names(), san); -} - -#[test] -fn test_san_domains_and_ip() { - let san = vec![ - "127.0.0.1", - "::1", - "local.what.tf", - "1.local.what.tf", - "2.local.what.tf", - ]; - let san = HashSet::from_iter(san.iter().map(|v| v.to_string())); - let crt = X509Certificate::from_pem(CERTIFICATE_P256_DOMAINS_IP_PEM.as_bytes()).unwrap(); - assert_eq!(crt.subject_alt_names(), san); -} - -#[test] -fn generate_rsa2048_certificate() { - let (kp, _) = - X509Certificate::from_acme_ext("example.org", "", KeyType::Rsa2048, HashFunction::Sha256) - .unwrap(); - assert_eq!(kp.key_type, KeyType::Rsa2048); -} - -#[test] -fn generate_rsa4096_certificate() { - let (kp, _) = - X509Certificate::from_acme_ext("example.org", "", KeyType::Rsa4096, HashFunction::Sha256) - .unwrap(); - assert_eq!(kp.key_type, KeyType::Rsa4096); -} - -#[test] -fn generate_ecdsa_p256_certificate() { - let (kp, _) = - X509Certificate::from_acme_ext("example.org", "", KeyType::EcdsaP256, HashFunction::Sha256) - .unwrap(); - assert_eq!(kp.key_type, KeyType::EcdsaP256); -} - -#[test] -fn generate_ecdsa_p384_certificate() { - let (kp, _) = - X509Certificate::from_acme_ext("example.org", "", KeyType::EcdsaP384, HashFunction::Sha256) - .unwrap(); - assert_eq!(kp.key_type, KeyType::EcdsaP384); -} - -#[cfg(feature = "ed25519")] -#[test] -fn generate_ed25519_certificate() { - let (kp, _) = - X509Certificate::from_acme_ext("example.org", "", KeyType::Ed25519, HashFunction::Sha256) - .unwrap(); - assert_eq!(kp.key_type, KeyType::Ed25519); -} - -#[cfg(feature = "ed448")] -#[test] -fn generate_ed448_certificate() { - let (kp, _) = - X509Certificate::from_acme_ext("example.org", "", KeyType::Ed448, HashFunction::Sha256) - .unwrap(); - assert_eq!(kp.key_type, KeyType::Ed448); -} - -#[test] -fn cert_expiration_date_future() { - let (_, crt) = - X509Certificate::from_acme_ext("example.org", "", KeyType::EcdsaP256, HashFunction::Sha256) - .unwrap(); - let duration = crt.expires_in().unwrap().as_secs(); - let validity_sec = CRT_NB_DAYS_VALIDITY as u64 * 24 * 60 * 60; - let delta = 60; - assert!(duration > validity_sec - delta); - assert!(duration < validity_sec + delta); -} - -#[test] -fn cert_expiration_date_past() { - let crt = X509Certificate::from_pem(CERTIFICATE_EXPIRED_PEM.as_bytes()).unwrap(); - let duration = crt.expires_in().unwrap().as_secs(); - assert_eq!(duration, 0); -} diff --git a/acme_common/src/tests/crypto_keys.rs b/acme_common/src/tests/crypto_keys.rs deleted file mode 100644 index 6062b16..0000000 --- a/acme_common/src/tests/crypto_keys.rs +++ /dev/null @@ -1,411 +0,0 @@ -use crate::crypto::KeyPair; - -const KEY_RSA_2048_PEM: &str = r#"-----BEGIN PRIVATE KEY----- -MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCzfwZGF8zKNAg2 -9mdZ9ieE7V2clY3oeI+2V7eV5kUwOGqhhpDaDyDmju+l0dKFwF8xeDeeGmTSED10 -e38ZsHqJF0cZqKDrB3hOeDAsn7Z6stHf/RZozQO5sAmZpN7g0P0lhXJnyAr+WL58 -X41kWuufiPVbvURQv/tK3yN2K+rC6MdZ2lLsLemKiwAlbyGrPfUzuVc6dXrU8JvX -kkwuIpAyEEJ7OTXdBaT4VAHHtm2YDWIwW+34Otyp2FvbSJYsIwJjC00t7Phmah9b -MjiypCZB6OknZV7WAZ55jaF/rypARB/zzTieSyn4Qi/VjipWE7nO/GjubyzrJtQm -q+o7Pm71AgMBAAECggEAVAXEFA+UB5svtTrGym/Vs/3A8kl3sjitXTfWck7mWFow -YAgzyj+GsSZ7u+1qVL3mUavqrRHB3CtJ+TrOFmJsGbxRxgsPuLU4ddMBCgKBUxJd -+DHqyYgelE95TvjEdAygU24STc5whvtXv7Si5TVCUt2zrQv97KbRpQyq9ug77pxp -iQGiZ4spUH47TrYtw85HqU1Vb+hJamvcwLv1jv6sKOKv4A4nF3OsqJOqH1FAcFf7 -f2Co2Zz83LV6WZ+yFAVG4C1OFMYJABHb3Sq+a5BOipkcCqQqK016NBcIsPvMGTuK -sHUBa2Reh9jLdOehfUa3p+Ir9ZALD+gs5jStRxFqCQKBgQD6Vcpsspsrl069fIJ2 -gWd37saM0b0DTqf5Pb3JFKyD5yCyRQD0UtgrUSP8wPxhRtJN0Jku+X1IW3FjLPeg -S/VWEp2nmRTpvHGZ1KYD0gn3RQne8mbt43+f9AwlEfjhvrWDUQhb1TOdCwa/9/xY -HPRM0xV4UiYJG+GVLla4Rbs60wKBgQC3jtvZh/Nd8DwtuS8wXMHQxTLjiHdd6r5n -Lm1m6236NHs7NMA3NlcH9lOP+YfU3I0Ti4CnYI8YWyIrJAbck6maCzLlzUluSzeo -kJ+Ax0/H7DOM0ix7EMkUMCU8m5qi684qg1yngmWobd0Y3aCWjPgQa0oG04+uXb0A -w+GbrB+CFwKBgQDGfF1a4CauYnMZRO7AfYwHiPg+0VH3nFcNBQpEtDKxBwJittmx -3zns5pINJws1Kg03i6zZlRHj3DVEOHRC0dc9ntcH+xWc2kCMgxH6t4AVYdUYw8Qe -3KHltoAmqGBYxXhwHUDuZ1ZcL1DzxvF6/8IoY7mDREdKM6QiP7KcuxVf5wKBgHx/ -NnnqDZRvNkHE0k64+vPAbG2Kx3s5lf6hrK4bjDIhmltjweMwxgKufaqvEgO7uyvA -eHgNs8BPP3OHMeg1dtj2M4VNoTpfZda8kJJlnKT6fVRL0MN/dQJuTTM4Tr+ls+V9 -x0AN3ylHqqgM2biC0FVCj6jloRQgm+qC8OgG7C/tAoGAeMToPctEvifliZkiyA6P -INrBwWyg3d0Kk3Wlyne3HP9PwS5KrtbKqwkAXsFWNW0HMpG7lMkedvoYjmL71i/5 -jIkjfccxlH1fRp/YOZ0wJ4ZWS6G/QgfqmvTeIpEcbokOmBGvHeuFLA8pyzfZa9rP -hEeZwTjgMoKYaVZ4q+23m/0= ------END PRIVATE KEY-----"#; -const KEY_RSA_4096_PEM: &str = r#"-----BEGIN PRIVATE KEY----- -MIIJQwIBADANBgkqhkiG9w0BAQEFAASCCS0wggkpAgEAAoICAQCObQmHxmT5pGpT -EwTTIt4eEG10MWroi9V6yv998XAaq2Uji0hroypi644TmqWqgGvG3V+lxyoYXzaP -jsmp6hZ6uQ11nFiHW4oZBr0ba8Hg4E4UfFi6NP4008+XCdU+unX+mPErSGI4bNlG -WI2E7rmUxsS55AJottDrCy2vhpWxYqLDw7gwbwJzZl6CRaViDRkc4Cf64mUC3Xs0 -l9fzHnvpdHBfYoKyw9h7KLAG6RGd7dqFVWqtzfkSZX0kfcLENob0x4EujsVRXk/U -Y04izfXdxsCB0xhXoCGvYu+xou5wW+FLCYeHjb76Z4O6L7TB0UguOvPsywhNnLjF -eOXz/OoxnZ0Fo7fzA/mj1UJ8JdpWUPchgH0Dj5j0hDrARV6h3MaWg9DJ11RnvSRn -s7cTWRi7S0EqGgAvVVwqJS86PUAEaiip/xAgBYKIBaoOqRknkeJHrU0L2Uncwf5q -phfN4dS47F2V+vehY9AqVHJihRb3fEPh1eMdDIewhLiBZgT1kn1oiiY0eP3dQ819 -HxJzFBNAElX1O2CX+85R2aVqxOajQ5dtMsx7HNYJUhK9S6gGhNxNjgNcIV5gfPcQ -TeKMn1+cORfdKy9XjfHxfnynL2sTIBCdHF95qGlYSjFbpMDmoHGDUYwFC7oJjYy6 -mqyYjRcdNfPLzEK3g+p39rnM/paH8QIDAQABAoICAFT0jV7D5K9Ud2eeTJ50ifF8 -8wz//TlBT9GzDLtfLPN7kRSmnEg4R6xBvbnL4U3W1HMG0WrdZiqrgKwZDAmibE4/ -29tvqw7yd2l+L4cPu9IbefeWRIat3YQ9Y/JAF0cXihKXwCOFRbFKnD/tylyk2WX5 -Opd3fkhf5DaPsGym5tusblI/iLq7PMcBJRan3IKkNXqX6sEoEgCnhDpW6KVIZblX -j0AWTse7MoIkPvugQrXljxdBYCTUW+GxT/hYW7kWnWGdL11KJEDo9M1HfvAb0rC7 -QVEvTbHW/sDTTw6ylW/IHpbX1FPzJRvQay7ADh4ea+PHnoB8izNgbIa+Gsxy7G5M -sc2aCrQu5ywRBmYLkTzxHu08Xfl7ZB1R7hczqznMd769MnlpIiQd6QbsbTrh2s5N -Yq4EtxOOFjU66XNBtYjn7h1yN2nWzjwVONgxcDQwacdkYD//IUryma6rF0UEXEDD -gBrdS4Q/28f0HmbWOh+qpqERb8YVLWL+VQy1OI4/9VIDDJDM76KxZKkJ/uE0FD9Z -Fj97ZjUfxg9D14ynJ6rsp0cEx8Q+h8tep6yEj1hdO6+72JhvR2IrJMOPDooV5hnY -7fZMOceKGKE+N1afZXqZXW7vRlSnpmE+HMgYHVQyPWbZ1I1KC9RhtI5fxiyc8V5j -c9dqdstEruvZ9cAPrfABAoIBAQDju4k7gtd6AFOnybYFQ027KJWD3QGharlWN9dL -r4D0yHj/btCdiwSf1WbQm9uFTjArRgahraQ7WbtvHBRM1JQ+BNAoY7Djaf/fHkYX -OAXSXE/56I6YwxTd/iVFiYs+G9wD90waC5/dMjcp7kTA63oIIVCgOln20wYwCZUO -4pf6qSi9tLrEvg5EWeZHCDay7As8xZXELsa9ao2Bt/zhNDrpSPR/AY7MYcbwqh5o -iWI43FADSL2k6dU12IRPTOyAxiV+oYYeJcI7BJrADVay7zZmIMilyKfWNp3yHUc+ -AdSOSSDmoz2cKej8ScoHaiOtnvwy3wG11eWzSeoqRy63DA+xAoIBAQCgGsjld9Y+ -YWQi+k/6CUPSbAolDGo6eZ1YAVS6fJ2e7P09Ou0txCqPWjxJeVBMkEJoTERmjikZ -lC8NDLCr3PmHA/1AY7D40AhMVrroUa3wDI6KnB/LMT3l6L1sCt4N/EEalTAKomOT -jpMp2IWtHMGbYr7x6hg2CIoWSZUfpVDipMxAcT2ak18xRyWxJDnl2HgX4NbMKKwI -zQXy0vF5NrP5eg+9d2hvfpf+opGNdtVANkkXGpFxKqO6HuVYccijxY0NcrQ8r1gp -CnIFIVqNpAFoBqtwFaeHrkg1/GlYOajMLnRW+qZIV6K+n9n+SYbLu1KRHrl9xkn/ -0MZSInMTPkxBAoIBAQDAMQwfIkxRlScEqrIn/OYD9rtAHut6W8RwZA4ZvNL7QpkD -EXWUD7fmYEY19eMsvJDgZGfCWPYKdK8/lRX4xUsakBtQitnFAzdDCJykic43+1ov -kbmOaM0akJrJ9cuCriZfXnxmWrsfBXsSsxhpLBG//MW7g6NbMDq/ncajWk5i6BIP -EBCza6ZEvw4dkmv/UkAlmKbNe6CUSPGFsU4EjXzOVpio+xqVmEs53ohtNsyjKiOI -sgICxKkAmWsINeY+w3rvRMgYd0tVXYxwWpF5z3I8fJx5dT9YBJ4Fr/no9ch6EHNo -0glz2tba3DdZTJUxuMQk9pnN6OfDCLVL2uks6EvxAoIBAQCCb0/cIpVYnN+H34Xo -nkOy2nIpXMPuf8XAPNVaWMvQ/iISED/KWVaTE2CqOztAJQb1Ea1oH8k8HY13hC8q -1Qw1Avr/yjgTfOhFySLcwi6CsrguFKOSVrum4sXvj6r4mdowXfqVr1aQkEc0gEHn -ltXkUb5eN+khnDNjlO74qSYMf1Yn6hnWJNoYu23psym4J3MvgO19xmThhqah/Vjc -98QIK3lHUlCzBN+vg6IxLe7uMUu6ltqG58Ybi7AtLgXX5snTeu97wR6B0RCzPUkY -u9Spe0WQOxQRZdtOoCTyy4bJUc9WTT3LEhp0Uqa2lBBNSn8p224jGbiPwPbRU1+M -/eQBAoIBAGTYVbha9dMdxlFnP63Cf2Ec7nraVSzm+6x414pCSosFTrl9eKI5dTV9 -zUsLfYVgqWcqGN3S5Q/8lM6ppmZapaUFrgKHtKdYEUnWBeobnrKR4iUSyqxlAKtJ -fYfcw5ZfX8GHABopmKUC9UzarqhmM3Am423EGd1CUzseaWme52EUiAbbxSjlzhwM -Q2ZTyps7X64dx6yOIRv6pPd3qZGRz2VoKW2x/sLoeErPsVtUW0u+NSKgR6O5sh7v -Mc5vg/2W9HWaAXdjyrXIJyypitp0Q9M1cSowzt/BaWNvb3i/En8uEXR5zZjl/CFG -yr9E4nQyE5YlYlPUK6iIRBu9j1N2MhY= ------END PRIVATE KEY-----"#; -const KEY_ECDSA_P256_PEM: &str = r#"-----BEGIN PRIVATE KEY----- -MEECAQAwEwYHKoZIzj0CAQYIKoZIzj0DAQcEJzAlAgEBBCCQc9OXwvygYqOFT4fN -NpXynr1lu+1sSplFdYoWu7hE4g== ------END PRIVATE KEY-----"#; -const KEY_ECDSA_P384_PEM: &str = r#"-----BEGIN PRIVATE KEY----- -ME4CAQAwEAYHKoZIzj0CAQYFK4EEACIENzA1AgEBBDCMsN9kHPueLABk+0PKi7WO -PO2/53dpt/yV5zOPrYPEoKs4t973nbt46IUN19lLF/s= ------END PRIVATE KEY-----"#; -#[cfg(feature = "ed25519")] -const KEY_ECDSA_ED25519_PEM: &str = r#"-----BEGIN PRIVATE KEY----- -MC4CAQAwBQYDK2VwBCIEIJhpRNsiUzoWqNkpJKCtKV5++Tttz3locu1gQKkQnrOa ------END PRIVATE KEY-----"#; -#[cfg(feature = "ed25519")] -const KEY_ECDSA_ED25519_PEM_BIS: &str = r#"-----BEGIN PRIVATE KEY----- -MC4CAQAwBQYDK2VwBCIEIKa3WD0qeUToPQKSwa9cTsLPgCovqAtXMhlMX2KYBz0o ------END PRIVATE KEY-----"#; -#[cfg(feature = "ed448")] -const KEY_ECDSA_ED448_PEM: &str = r#"-----BEGIN PRIVATE KEY----- -MEcCAQAwBQYDK2VxBDsEOcFBwsH4zU7u5RgFh48MgJPzXyjN5uXxDapZv4rG6opU -uMXco2JR1CSjKWgqgu1CAKadJIYiv2EgIw== ------END PRIVATE KEY-----"#; - -#[test] -fn test_rsa_2048_jwk() { - let k = KeyPair::from_pem(KEY_RSA_2048_PEM.as_bytes()).unwrap(); - let jwk = k.jwk_public_key().unwrap(); - assert!(jwk.is_object()); - let jwk = jwk.as_object().unwrap(); - assert_eq!(jwk.len(), 5); - assert!(jwk.contains_key("kty")); - assert!(jwk.contains_key("e")); - assert!(jwk.contains_key("n")); - assert!(jwk.contains_key("use")); - assert!(jwk.contains_key("alg")); - assert_eq!(jwk.get("kty").unwrap(), "RSA"); - assert_eq!(jwk.get("e").unwrap(), "AQAB"); - assert_eq!(jwk.get("n").unwrap(), "s38GRhfMyjQINvZnWfYnhO1dnJWN6HiPtle3leZFMDhqoYaQ2g8g5o7vpdHShcBfMXg3nhpk0hA9dHt_GbB6iRdHGaig6wd4TngwLJ-2erLR3_0WaM0DubAJmaTe4ND9JYVyZ8gK_li-fF-NZFrrn4j1W71EUL_7St8jdivqwujHWdpS7C3piosAJW8hqz31M7lXOnV61PCb15JMLiKQMhBCezk13QWk-FQBx7ZtmA1iMFvt-Drcqdhb20iWLCMCYwtNLez4ZmofWzI4sqQmQejpJ2Ve1gGeeY2hf68qQEQf8804nksp-EIv1Y4qVhO5zvxo7m8s6ybUJqvqOz5u9Q"); - assert_eq!(jwk.get("use").unwrap(), "sig"); - assert_eq!(jwk.get("alg").unwrap(), "RS256"); -} - -#[test] -fn test_rsa_2048_jwk_thumbprint() { - let k = KeyPair::from_pem(KEY_RSA_2048_PEM.as_bytes()).unwrap(); - let jwk = k.jwk_public_key_thumbprint().unwrap(); - assert!(jwk.is_object()); - let jwk = jwk.as_object().unwrap(); - assert_eq!(jwk.len(), 3); - assert!(jwk.contains_key("kty")); - assert!(jwk.contains_key("e")); - assert!(jwk.contains_key("n")); - assert!(!jwk.contains_key("use")); - assert!(!jwk.contains_key("alg")); - assert_eq!(jwk.get("kty").unwrap(), "RSA"); - assert_eq!(jwk.get("e").unwrap(), "AQAB"); - assert_eq!(jwk.get("n").unwrap(), "s38GRhfMyjQINvZnWfYnhO1dnJWN6HiPtle3leZFMDhqoYaQ2g8g5o7vpdHShcBfMXg3nhpk0hA9dHt_GbB6iRdHGaig6wd4TngwLJ-2erLR3_0WaM0DubAJmaTe4ND9JYVyZ8gK_li-fF-NZFrrn4j1W71EUL_7St8jdivqwujHWdpS7C3piosAJW8hqz31M7lXOnV61PCb15JMLiKQMhBCezk13QWk-FQBx7ZtmA1iMFvt-Drcqdhb20iWLCMCYwtNLez4ZmofWzI4sqQmQejpJ2Ve1gGeeY2hf68qQEQf8804nksp-EIv1Y4qVhO5zvxo7m8s6ybUJqvqOz5u9Q"); -} - -#[test] -fn test_rsa_4096_jwk() { - let k = KeyPair::from_pem(KEY_RSA_4096_PEM.as_bytes()).unwrap(); - let jwk = k.jwk_public_key().unwrap(); - assert!(jwk.is_object()); - let jwk = jwk.as_object().unwrap(); - assert_eq!(jwk.len(), 5); - assert!(jwk.contains_key("kty")); - assert!(jwk.contains_key("e")); - assert!(jwk.contains_key("n")); - assert!(jwk.contains_key("use")); - assert!(jwk.contains_key("alg")); - assert_eq!(jwk.get("kty").unwrap(), "RSA"); - assert_eq!(jwk.get("e").unwrap(), "AQAB"); - assert_eq!(jwk.get("n").unwrap(), "jm0Jh8Zk-aRqUxME0yLeHhBtdDFq6IvVesr_ffFwGqtlI4tIa6MqYuuOE5qlqoBrxt1fpccqGF82j47JqeoWerkNdZxYh1uKGQa9G2vB4OBOFHxYujT-NNPPlwnVPrp1_pjxK0hiOGzZRliNhO65lMbEueQCaLbQ6wstr4aVsWKiw8O4MG8Cc2ZegkWlYg0ZHOAn-uJlAt17NJfX8x576XRwX2KCssPYeyiwBukRne3ahVVqrc35EmV9JH3CxDaG9MeBLo7FUV5P1GNOIs313cbAgdMYV6Ahr2LvsaLucFvhSwmHh42--meDui-0wdFILjrz7MsITZy4xXjl8_zqMZ2dBaO38wP5o9VCfCXaVlD3IYB9A4-Y9IQ6wEVeodzGloPQyddUZ70kZ7O3E1kYu0tBKhoAL1VcKiUvOj1ABGooqf8QIAWCiAWqDqkZJ5HiR61NC9lJ3MH-aqYXzeHUuOxdlfr3oWPQKlRyYoUW93xD4dXjHQyHsIS4gWYE9ZJ9aIomNHj93UPNfR8ScxQTQBJV9Ttgl_vOUdmlasTmo0OXbTLMexzWCVISvUuoBoTcTY4DXCFeYHz3EE3ijJ9fnDkX3SsvV43x8X58py9rEyAQnRxfeahpWEoxW6TA5qBxg1GMBQu6CY2MupqsmI0XHTXzy8xCt4Pqd_a5zP6Wh_E"); - assert_eq!(jwk.get("use").unwrap(), "sig"); - assert_eq!(jwk.get("alg").unwrap(), "RS256"); -} - -#[test] -fn test_rsa_4096_jwk_thumbprint() { - let k = KeyPair::from_pem(KEY_RSA_4096_PEM.as_bytes()).unwrap(); - let jwk = k.jwk_public_key_thumbprint().unwrap(); - assert!(jwk.is_object()); - let jwk = jwk.as_object().unwrap(); - assert_eq!(jwk.len(), 3); - assert!(jwk.contains_key("kty")); - assert!(jwk.contains_key("e")); - assert!(jwk.contains_key("n")); - assert!(!jwk.contains_key("use")); - assert!(!jwk.contains_key("alg")); - assert_eq!(jwk.get("kty").unwrap(), "RSA"); - assert_eq!(jwk.get("e").unwrap(), "AQAB"); - assert_eq!(jwk.get("n").unwrap(), "jm0Jh8Zk-aRqUxME0yLeHhBtdDFq6IvVesr_ffFwGqtlI4tIa6MqYuuOE5qlqoBrxt1fpccqGF82j47JqeoWerkNdZxYh1uKGQa9G2vB4OBOFHxYujT-NNPPlwnVPrp1_pjxK0hiOGzZRliNhO65lMbEueQCaLbQ6wstr4aVsWKiw8O4MG8Cc2ZegkWlYg0ZHOAn-uJlAt17NJfX8x576XRwX2KCssPYeyiwBukRne3ahVVqrc35EmV9JH3CxDaG9MeBLo7FUV5P1GNOIs313cbAgdMYV6Ahr2LvsaLucFvhSwmHh42--meDui-0wdFILjrz7MsITZy4xXjl8_zqMZ2dBaO38wP5o9VCfCXaVlD3IYB9A4-Y9IQ6wEVeodzGloPQyddUZ70kZ7O3E1kYu0tBKhoAL1VcKiUvOj1ABGooqf8QIAWCiAWqDqkZJ5HiR61NC9lJ3MH-aqYXzeHUuOxdlfr3oWPQKlRyYoUW93xD4dXjHQyHsIS4gWYE9ZJ9aIomNHj93UPNfR8ScxQTQBJV9Ttgl_vOUdmlasTmo0OXbTLMexzWCVISvUuoBoTcTY4DXCFeYHz3EE3ijJ9fnDkX3SsvV43x8X58py9rEyAQnRxfeahpWEoxW6TA5qBxg1GMBQu6CY2MupqsmI0XHTXzy8xCt4Pqd_a5zP6Wh_E"); -} - -#[test] -fn test_ecdsa_p256_jwk() { - let k = KeyPair::from_pem(KEY_ECDSA_P256_PEM.as_bytes()).unwrap(); - let jwk = k.jwk_public_key().unwrap(); - assert!(jwk.is_object()); - let jwk = jwk.as_object().unwrap(); - assert_eq!(jwk.len(), 6); - assert!(jwk.contains_key("kty")); - assert!(jwk.contains_key("crv")); - assert!(jwk.contains_key("x")); - assert!(jwk.contains_key("y")); - assert!(jwk.contains_key("use")); - assert!(jwk.contains_key("alg")); - assert_eq!(jwk.get("kty").unwrap(), "EC"); - assert_eq!(jwk.get("crv").unwrap(), "P-256"); - assert_eq!( - jwk.get("x").unwrap(), - "VpJrz2a8rASzmbHStuDxNCjQc8ZiDnrGvVeRayNskrQ" - ); - assert_eq!( - jwk.get("y").unwrap(), - "GrVCHhF5hN68efEgdoYS7acUT88qhMKQbULVcBgPBUg" - ); - assert_eq!(jwk.get("use").unwrap(), "sig"); - assert_eq!(jwk.get("alg").unwrap(), "ES256"); -} - -#[test] -fn test_ecdsa_p256_jwk_thumbprint() { - let k = KeyPair::from_pem(KEY_ECDSA_P256_PEM.as_bytes()).unwrap(); - let jwk = k.jwk_public_key_thumbprint().unwrap(); - assert!(jwk.is_object()); - let jwk = jwk.as_object().unwrap(); - assert_eq!(jwk.len(), 4); - assert!(jwk.contains_key("kty")); - assert!(jwk.contains_key("crv")); - assert!(jwk.contains_key("x")); - assert!(jwk.contains_key("y")); - assert!(!jwk.contains_key("use")); - assert!(!jwk.contains_key("alg")); - assert_eq!(jwk.get("kty").unwrap(), "EC"); - assert_eq!(jwk.get("crv").unwrap(), "P-256"); - assert_eq!( - jwk.get("x").unwrap(), - "VpJrz2a8rASzmbHStuDxNCjQc8ZiDnrGvVeRayNskrQ" - ); - assert_eq!( - jwk.get("y").unwrap(), - "GrVCHhF5hN68efEgdoYS7acUT88qhMKQbULVcBgPBUg" - ); -} - -#[test] -fn test_ecdsa_p384_jwk() { - let k = KeyPair::from_pem(KEY_ECDSA_P384_PEM.as_bytes()).unwrap(); - let jwk = k.jwk_public_key().unwrap(); - assert!(jwk.is_object()); - let jwk = jwk.as_object().unwrap(); - assert_eq!(jwk.len(), 6); - assert!(jwk.contains_key("kty")); - assert!(jwk.contains_key("crv")); - assert!(jwk.contains_key("x")); - assert!(jwk.contains_key("y")); - assert!(jwk.contains_key("use")); - assert!(jwk.contains_key("alg")); - assert_eq!(jwk.get("kty").unwrap(), "EC"); - assert_eq!(jwk.get("crv").unwrap(), "P-384"); - assert_eq!( - jwk.get("x").unwrap(), - "N7TmS8prIp0DAGvwg1saML4UK61oe2PPJTeGLJt0iW-PMNcetFPcMF4WCa0ez80a" - ); - assert_eq!( - jwk.get("y").unwrap(), - "RE5dtMDKV9Y8hsKf3fqLzMx75WORJaGswqC68xkRNjo0HcTar4tCB9VF9eSFfTMU" - ); - assert_eq!(jwk.get("use").unwrap(), "sig"); - assert_eq!(jwk.get("alg").unwrap(), "ES384"); -} - -#[test] -fn test_ecdsa_p384_jwk_thumbprint() { - let k = KeyPair::from_pem(KEY_ECDSA_P384_PEM.as_bytes()).unwrap(); - let jwk = k.jwk_public_key_thumbprint().unwrap(); - assert!(jwk.is_object()); - let jwk = jwk.as_object().unwrap(); - assert_eq!(jwk.len(), 4); - assert!(jwk.contains_key("kty")); - assert!(jwk.contains_key("crv")); - assert!(jwk.contains_key("x")); - assert!(jwk.contains_key("y")); - assert!(!jwk.contains_key("use")); - assert!(!jwk.contains_key("alg")); - assert_eq!(jwk.get("kty").unwrap(), "EC"); - assert_eq!(jwk.get("crv").unwrap(), "P-384"); - assert_eq!( - jwk.get("x").unwrap(), - "N7TmS8prIp0DAGvwg1saML4UK61oe2PPJTeGLJt0iW-PMNcetFPcMF4WCa0ez80a" - ); - assert_eq!( - jwk.get("y").unwrap(), - "RE5dtMDKV9Y8hsKf3fqLzMx75WORJaGswqC68xkRNjo0HcTar4tCB9VF9eSFfTMU" - ); -} - -#[cfg(feature = "ed25519")] -#[test] -fn test_ed25519_jwk() { - let k = KeyPair::from_pem(KEY_ECDSA_ED25519_PEM.as_bytes()).unwrap(); - let jwk = k.jwk_public_key().unwrap(); - assert!(jwk.is_object()); - let jwk = jwk.as_object().unwrap(); - assert_eq!(jwk.len(), 5); - assert!(jwk.contains_key("kty")); - assert!(jwk.contains_key("crv")); - assert!(jwk.contains_key("x")); - assert!(jwk.contains_key("use")); - assert!(jwk.contains_key("alg")); - assert_eq!(jwk.get("kty").unwrap(), "OKP"); - assert_eq!(jwk.get("crv").unwrap(), "Ed25519"); - assert_eq!( - jwk.get("x").unwrap(), - "DUX9ja8pq2wfkxuIaHzmhkdcVXMav_3rk5Y5ozOcp4o" - ); - assert_eq!(jwk.get("use").unwrap(), "sig"); - assert_eq!(jwk.get("alg").unwrap(), "EdDSA"); -} - -#[cfg(feature = "ed25519")] -#[test] -fn test_ed25519_jwk_thumbprint() { - let k = KeyPair::from_pem(KEY_ECDSA_ED25519_PEM.as_bytes()).unwrap(); - let jwk = k.jwk_public_key_thumbprint().unwrap(); - assert!(jwk.is_object()); - let jwk = jwk.as_object().unwrap(); - assert_eq!(jwk.len(), 3); - assert!(jwk.contains_key("kty")); - assert!(jwk.contains_key("crv")); - assert!(jwk.contains_key("x")); - assert!(!jwk.contains_key("use")); - assert!(!jwk.contains_key("alg")); - assert_eq!(jwk.get("kty").unwrap(), "OKP"); - assert_eq!(jwk.get("crv").unwrap(), "Ed25519"); - assert_eq!( - jwk.get("x").unwrap(), - "DUX9ja8pq2wfkxuIaHzmhkdcVXMav_3rk5Y5ozOcp4o" - ); -} - -#[cfg(feature = "ed25519")] -#[test] -fn test_ed25519_jwk_bis() { - let k = KeyPair::from_pem(KEY_ECDSA_ED25519_PEM_BIS.as_bytes()).unwrap(); - let jwk = k.jwk_public_key().unwrap(); - assert!(jwk.is_object()); - let jwk = jwk.as_object().unwrap(); - assert_eq!(jwk.len(), 5); - assert!(jwk.contains_key("kty")); - assert!(jwk.contains_key("crv")); - assert!(jwk.contains_key("x")); - assert!(jwk.contains_key("use")); - assert!(jwk.contains_key("alg")); - assert_eq!(jwk.get("kty").unwrap(), "OKP"); - assert_eq!(jwk.get("crv").unwrap(), "Ed25519"); - assert_eq!( - jwk.get("x").unwrap(), - "i9K0eV5qOJ_l_TWjWFLm8R-JbyGdlqFFeL_J0eEXFnc" - ); - assert_eq!(jwk.get("use").unwrap(), "sig"); - assert_eq!(jwk.get("alg").unwrap(), "EdDSA"); -} - -#[cfg(feature = "ed25519")] -#[test] -fn test_ed25519_jwk_thumbprint_bis() { - let k = KeyPair::from_pem(KEY_ECDSA_ED25519_PEM_BIS.as_bytes()).unwrap(); - let jwk = k.jwk_public_key_thumbprint().unwrap(); - assert!(jwk.is_object()); - let jwk = jwk.as_object().unwrap(); - assert_eq!(jwk.len(), 3); - assert!(jwk.contains_key("kty")); - assert!(jwk.contains_key("crv")); - assert!(jwk.contains_key("x")); - assert!(!jwk.contains_key("use")); - assert!(!jwk.contains_key("alg")); - assert_eq!(jwk.get("kty").unwrap(), "OKP"); - assert_eq!(jwk.get("crv").unwrap(), "Ed25519"); - assert_eq!( - jwk.get("x").unwrap(), - "i9K0eV5qOJ_l_TWjWFLm8R-JbyGdlqFFeL_J0eEXFnc" - ); -} - -#[cfg(feature = "ed448")] -#[test] -fn test_ed448_jwk() { - let k = KeyPair::from_pem(KEY_ECDSA_ED448_PEM.as_bytes()).unwrap(); - let jwk = k.jwk_public_key().unwrap(); - assert!(jwk.is_object()); - let jwk = jwk.as_object().unwrap(); - assert_eq!(jwk.len(), 5); - assert!(jwk.contains_key("kty")); - assert!(jwk.contains_key("crv")); - assert!(jwk.contains_key("x")); - assert!(jwk.contains_key("use")); - assert!(jwk.contains_key("alg")); - assert_eq!(jwk.get("kty").unwrap(), "OKP"); - assert_eq!(jwk.get("crv").unwrap(), "Ed448"); - assert_eq!( - jwk.get("x").unwrap(), - "b9GZ8b1hip3UMzkkNBdMF4JWBTZojxsNHK-jQBH94SY3boVs4Oeo291E1dGXz7RUMqIXjkSbU4EA" - ); - assert_eq!(jwk.get("use").unwrap(), "sig"); - assert_eq!(jwk.get("alg").unwrap(), "EdDSA"); -} - -#[cfg(feature = "ed448")] -#[test] -fn test_ed448_jwk_thumbprint() { - let k = KeyPair::from_pem(KEY_ECDSA_ED448_PEM.as_bytes()).unwrap(); - let jwk = k.jwk_public_key_thumbprint().unwrap(); - assert!(jwk.is_object()); - let jwk = jwk.as_object().unwrap(); - assert_eq!(jwk.len(), 3); - assert!(jwk.contains_key("kty")); - assert!(jwk.contains_key("crv")); - assert!(jwk.contains_key("x")); - assert!(!jwk.contains_key("use")); - assert!(!jwk.contains_key("alg")); - assert_eq!(jwk.get("kty").unwrap(), "OKP"); - assert_eq!(jwk.get("crv").unwrap(), "Ed448"); - assert_eq!( - jwk.get("x").unwrap(), - "b9GZ8b1hip3UMzkkNBdMF4JWBTZojxsNHK-jQBH94SY3boVs4Oeo291E1dGXz7RUMqIXjkSbU4EA" - ); -} diff --git a/acme_common/src/tests/hash.rs b/acme_common/src/tests/hash.rs deleted file mode 100644 index 039bcf4..0000000 --- a/acme_common/src/tests/hash.rs +++ /dev/null @@ -1,344 +0,0 @@ -use crate::crypto::HashFunction; - -#[test] -fn test_hash_from_str() { - let test_vectors = vec![ - ("sha256", HashFunction::Sha256), - ("Sha256", HashFunction::Sha256), - ("sha-256", HashFunction::Sha256), - ("SHA_256", HashFunction::Sha256), - ("sha384", HashFunction::Sha384), - ("Sha-512", HashFunction::Sha512), - ]; - for (s, ref_h) in test_vectors { - let h: HashFunction = s.parse().unwrap(); - assert_eq!(h, ref_h); - } -} - -#[test] -fn test_hash_from_invalid_str() { - let test_vectors = vec!["sha42", "sha", "", "plop"]; - for s in test_vectors { - let h = s.parse::(); - assert!(h.is_err()); - } -} - -#[test] -fn test_hash_sha256() { - let test_vectors = vec![ - ( - "Hello World!".as_bytes(), - vec![ - 127, 131, 177, 101, 127, 241, 252, 83, 185, 45, 193, 129, 72, 161, 214, 93, 252, - 45, 75, 31, 163, 214, 119, 40, 74, 221, 210, 0, 18, 109, 144, 105, - ], - ), - ( - &[], - vec![ - 227, 176, 196, 66, 152, 252, 28, 20, 154, 251, 244, 200, 153, 111, 185, 36, 39, - 174, 65, 228, 100, 155, 147, 76, 164, 149, 153, 27, 120, 82, 184, 85, - ], - ), - ( - &[ - 194, 43, 6, 43, 252, 50, 206, 26, 240, 105, 85, 119, 40, 153, 213, 123, 158, 59, 8, - 45, 114, - ], - vec![ - 65, 72, 199, 76, 128, 174, 196, 223, 91, 235, 87, 119, 200, 212, 133, 13, 219, 223, - 60, 4, 73, 70, 65, 41, 226, 83, 221, 107, 112, 29, 205, 28, - ], - ), - ]; - for (data, expected) in test_vectors { - let h = HashFunction::Sha256; - let res = h.hash(data); - assert_eq!(res, expected); - } -} - -#[test] -fn test_hmac_sha256() { - let test_vectors = vec![ - ( - vec![ - 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, - ], - vec![72, 105, 32, 84, 104, 101, 114, 101], - vec![ - 176, 52, 76, 97, 216, 219, 56, 83, 92, 168, 175, 206, 175, 11, 241, 43, 136, 29, - 194, 0, 201, 131, 61, 167, 38, 233, 55, 108, 46, 50, 207, 247, - ], - ), - ( - vec![74, 101, 102, 101], - vec![ - 119, 104, 97, 116, 32, 100, 111, 32, 121, 97, 32, 119, 97, 110, 116, 32, 102, 111, - 114, 32, 110, 111, 116, 104, 105, 110, 103, 63, - ], - vec![ - 91, 220, 193, 70, 191, 96, 117, 78, 106, 4, 36, 38, 8, 149, 117, 199, 90, 0, 63, 8, - 157, 39, 57, 131, 157, 236, 88, 185, 100, 236, 56, 67, - ], - ), - ( - vec![ - 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, - 170, 170, 170, 170, - ], - vec![ - 221, 221, 221, 221, 221, 221, 221, 221, 221, 221, 221, 221, 221, 221, 221, 221, - 221, 221, 221, 221, 221, 221, 221, 221, 221, 221, 221, 221, 221, 221, 221, 221, - 221, 221, 221, 221, 221, 221, 221, 221, 221, 221, 221, 221, 221, 221, 221, 221, - 221, 221, - ], - vec![ - 119, 62, 169, 30, 54, 128, 14, 70, 133, 77, 184, 235, 208, 145, 129, 167, 41, 89, - 9, 139, 62, 248, 193, 34, 217, 99, 85, 20, 206, 213, 101, 254, - ], - ), - ( - vec![ - 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, - 24, 25, - ], - vec![ - 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, - 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, - 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, - 205, 205, - ], - vec![ - 130, 85, 138, 56, 154, 68, 60, 14, 164, 204, 129, 152, 153, 242, 8, 58, 133, 240, - 250, 163, 229, 120, 248, 7, 122, 46, 63, 244, 103, 41, 102, 91, - ], - ), - ]; - for (key, data, expected) in test_vectors { - let h = HashFunction::Sha256; - let res = h.hmac(&key, &data).unwrap(); - assert_eq!(res, expected); - } -} - -#[test] -fn test_hash_sha384() { - let test_vectors = vec![ - ( - "Hello World!".as_bytes(), - vec![ - 191, 215, 108, 14, 187, 208, 6, 254, 229, 131, 65, 5, 71, 193, 136, 123, 2, 146, - 190, 118, 213, 130, 217, 108, 36, 45, 42, 121, 39, 35, 227, 253, 111, 208, 97, 249, - 213, 207, 209, 59, 143, 150, 19, 88, 230, 173, 186, 74, - ], - ), - ( - &[], - vec![ - 56, 176, 96, 167, 81, 172, 150, 56, 76, 217, 50, 126, 177, 177, 227, 106, 33, 253, - 183, 17, 20, 190, 7, 67, 76, 12, 199, 191, 99, 246, 225, 218, 39, 78, 222, 191, - 231, 111, 101, 251, 213, 26, 210, 241, 72, 152, 185, 91, - ], - ), - ( - &[ - 194, 43, 6, 43, 252, 50, 206, 26, 240, 105, 85, 119, 40, 153, 213, 123, 158, 59, 8, - 45, 114, - ], - vec![ - 170, 126, 84, 2, 141, 91, 106, 70, 80, 53, 98, 101, 184, 3, 34, 146, 130, 238, 146, - 221, 113, 197, 154, 91, 4, 208, 229, 15, 8, 179, 51, 29, 224, 200, 187, 127, 9, - 243, 29, 171, 189, 124, 60, 39, 3, 74, 171, 156, - ], - ), - ]; - for (data, expected) in test_vectors { - let h = HashFunction::Sha384; - let res = h.hash(data); - assert_eq!(res, expected); - } -} - -#[test] -fn test_hmac_sha384() { - let test_vectors = vec![ - ( - vec![ - 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, - ], - vec![72, 105, 32, 84, 104, 101, 114, 101], - vec![ - 175, 208, 57, 68, 216, 72, 149, 98, 107, 8, 37, 244, 171, 70, 144, 127, 21, 249, - 218, 219, 228, 16, 30, 198, 130, 170, 3, 76, 124, 235, 197, 156, 250, 234, 158, - 169, 7, 110, 222, 127, 74, 241, 82, 232, 178, 250, 156, 182, - ], - ), - ( - vec![74, 101, 102, 101], - vec![ - 119, 104, 97, 116, 32, 100, 111, 32, 121, 97, 32, 119, 97, 110, 116, 32, 102, 111, - 114, 32, 110, 111, 116, 104, 105, 110, 103, 63, - ], - vec![ - 175, 69, 210, 227, 118, 72, 64, 49, 97, 127, 120, 210, 181, 138, 107, 27, 156, 126, - 244, 100, 245, 160, 27, 71, 228, 46, 195, 115, 99, 34, 68, 94, 142, 34, 64, 202, - 94, 105, 226, 199, 139, 50, 57, 236, 250, 178, 22, 73, - ], - ), - ( - vec![ - 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, - 170, 170, 170, 170, - ], - vec![ - 221, 221, 221, 221, 221, 221, 221, 221, 221, 221, 221, 221, 221, 221, 221, 221, - 221, 221, 221, 221, 221, 221, 221, 221, 221, 221, 221, 221, 221, 221, 221, 221, - 221, 221, 221, 221, 221, 221, 221, 221, 221, 221, 221, 221, 221, 221, 221, 221, - 221, 221, - ], - vec![ - 136, 6, 38, 8, 211, 230, 173, 138, 10, 162, 172, 224, 20, 200, 168, 111, 10, 166, - 53, 217, 71, 172, 159, 235, 232, 62, 244, 229, 89, 102, 20, 75, 42, 90, 179, 157, - 193, 56, 20, 185, 78, 58, 182, 225, 1, 163, 79, 39, - ], - ), - ( - vec![ - 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, - 24, 25, - ], - vec![ - 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, - 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, - 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, - 205, 205, - ], - vec![ - 62, 138, 105, 183, 120, 60, 37, 133, 25, 51, 171, 98, 144, 175, 108, 167, 122, 153, - 129, 72, 8, 80, 0, 156, 197, 87, 124, 110, 31, 87, 59, 78, 104, 1, 221, 35, 196, - 167, 214, 121, 204, 248, 163, 134, 198, 116, 207, 251, - ], - ), - ]; - for (key, data, expected) in test_vectors { - let h = HashFunction::Sha384; - let res = h.hmac(&key, &data).unwrap(); - assert_eq!(res, expected); - } -} - -#[test] -fn test_hash_sha512() { - let test_vectors = vec![ - ( - "Hello World!".as_bytes(), - vec![ - 134, 24, 68, 214, 112, 78, 133, 115, 254, 195, 77, 150, 126, 32, 188, 254, 243, - 212, 36, 207, 72, 190, 4, 230, 220, 8, 242, 189, 88, 199, 41, 116, 51, 113, 1, 94, - 173, 137, 28, 195, 207, 28, 157, 52, 180, 146, 100, 181, 16, 117, 27, 31, 249, 229, - 55, 147, 123, 196, 107, 93, 111, 244, 236, 200, - ], - ), - ( - &[], - vec![ - 207, 131, 225, 53, 126, 239, 184, 189, 241, 84, 40, 80, 214, 109, 128, 7, 214, 32, - 228, 5, 11, 87, 21, 220, 131, 244, 169, 33, 211, 108, 233, 206, 71, 208, 209, 60, - 93, 133, 242, 176, 255, 131, 24, 210, 135, 126, 236, 47, 99, 185, 49, 189, 71, 65, - 122, 129, 165, 56, 50, 122, 249, 39, 218, 62, - ], - ), - ( - &[ - 194, 43, 6, 43, 252, 50, 206, 26, 240, 105, 85, 119, 40, 153, 213, 123, 158, 59, 8, - 45, 114, - ], - vec![ - 58, 93, 210, 174, 119, 179, 246, 25, 14, 148, 182, 109, 28, 14, 16, 80, 45, 231, - 104, 169, 130, 43, 39, 221, 12, 112, 85, 159, 123, 6, 227, 35, 61, 24, 158, 190, - 162, 11, 247, 204, 98, 41, 242, 5, 52, 116, 149, 220, 124, 82, 159, 181, 74, 210, - 85, 190, 59, 130, 209, 8, 181, 247, 192, 65, - ], - ), - ]; - for (data, expected) in test_vectors { - let h = HashFunction::Sha512; - let res = h.hash(data); - assert_eq!(res, expected); - } -} - -#[test] -fn test_hmac_sha512() { - let test_vectors = vec![ - ( - vec![ - 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, - ], - vec![72, 105, 32, 84, 104, 101, 114, 101], - vec![ - 135, 170, 124, 222, 165, 239, 97, 157, 79, 240, 180, 36, 26, 29, 108, 176, 35, 121, - 244, 226, 206, 78, 194, 120, 122, 208, 179, 5, 69, 225, 124, 222, 218, 168, 51, - 183, 214, 184, 167, 2, 3, 139, 39, 78, 174, 163, 244, 228, 190, 157, 145, 78, 235, - 97, 241, 112, 46, 105, 108, 32, 58, 18, 104, 84, - ], - ), - ( - vec![74, 101, 102, 101], - vec![ - 119, 104, 97, 116, 32, 100, 111, 32, 121, 97, 32, 119, 97, 110, 116, 32, 102, 111, - 114, 32, 110, 111, 116, 104, 105, 110, 103, 63, - ], - vec![ - 22, 75, 122, 123, 252, 248, 25, 226, 227, 149, 251, 231, 59, 86, 224, 163, 135, - 189, 100, 34, 46, 131, 31, 214, 16, 39, 12, 215, 234, 37, 5, 84, 151, 88, 191, 117, - 192, 90, 153, 74, 109, 3, 79, 101, 248, 240, 230, 253, 202, 234, 177, 163, 77, 74, - 107, 75, 99, 110, 7, 10, 56, 188, 231, 55, - ], - ), - ( - vec![ - 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, - 170, 170, 170, 170, - ], - vec![ - 221, 221, 221, 221, 221, 221, 221, 221, 221, 221, 221, 221, 221, 221, 221, 221, - 221, 221, 221, 221, 221, 221, 221, 221, 221, 221, 221, 221, 221, 221, 221, 221, - 221, 221, 221, 221, 221, 221, 221, 221, 221, 221, 221, 221, 221, 221, 221, 221, - 221, 221, - ], - vec![ - 250, 115, 176, 8, 157, 86, 162, 132, 239, 176, 240, 117, 108, 137, 11, 233, 177, - 181, 219, 221, 142, 232, 26, 54, 85, 248, 62, 51, 178, 39, 157, 57, 191, 62, 132, - 130, 121, 167, 34, 200, 6, 180, 133, 164, 126, 103, 200, 7, 185, 70, 163, 55, 190, - 232, 148, 38, 116, 39, 136, 89, 225, 50, 146, 251, - ], - ), - ( - vec![ - 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, - 24, 25, - ], - vec![ - 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, - 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, - 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, - 205, 205, - ], - vec![ - 176, 186, 70, 86, 55, 69, 140, 105, 144, 229, 168, 197, 246, 29, 74, 247, 229, 118, - 217, 127, 249, 75, 135, 45, 231, 111, 128, 80, 54, 30, 227, 219, 169, 28, 165, 193, - 26, 162, 94, 180, 214, 121, 39, 92, 197, 120, 128, 99, 165, 241, 151, 65, 18, 12, - 79, 45, 226, 173, 235, 235, 16, 162, 152, 221, - ], - ), - ]; - for (key, data, expected) in test_vectors { - let h = HashFunction::Sha512; - let res = h.hmac(&key, &data).unwrap(); - assert_eq!(res, expected); - } -} diff --git a/acme_common/src/tests/idna.rs b/acme_common/src/tests/idna.rs deleted file mode 100644 index 7ff055d..0000000 --- a/acme_common/src/tests/idna.rs +++ /dev/null @@ -1,42 +0,0 @@ -use crate::to_idna; - -#[test] -fn test_no_idna() { - let idna_res = to_idna("HeLo.example.com"); - assert!(idna_res.is_ok()); - assert_eq!(idna_res.unwrap(), "helo.example.com"); -} - -#[test] -fn test_simple_idna() { - let idna_res = to_idna("Hélo.Example.com"); - assert!(idna_res.is_ok()); - assert_eq!(idna_res.unwrap(), "xn--hlo-bma.example.com"); -} - -#[test] -fn test_multiple_idna() { - let idna_res = to_idna("ns1.hÉlo.aç-éièè.example.com"); - assert!(idna_res.is_ok()); - assert_eq!( - idna_res.unwrap(), - "ns1.xn--hlo-bma.xn--a-i-2lahae.example.com" - ); -} - -#[test] -fn test_already_idna() { - let idna_res = to_idna("xn--hlo-bma.example.com"); - assert!(idna_res.is_ok()); - assert_eq!(idna_res.unwrap(), "xn--hlo-bma.example.com"); -} - -#[test] -fn test_mixed_idna_parts() { - let idna_res = to_idna("ns1.xn--hlo-bma.aç-éièè.example.com"); - assert!(idna_res.is_ok()); - assert_eq!( - idna_res.unwrap(), - "ns1.xn--hlo-bma.xn--a-i-2lahae.example.com" - ); -} diff --git a/acme_common/src/tests/jws_signature_algorithm.rs b/acme_common/src/tests/jws_signature_algorithm.rs deleted file mode 100644 index 8860d71..0000000 --- a/acme_common/src/tests/jws_signature_algorithm.rs +++ /dev/null @@ -1,62 +0,0 @@ -use crate::crypto::{gen_keypair, JwsSignatureAlgorithm, KeyType}; - -const TEST_DATA: &'static [u8] = &[72, 101, 108, 108, 111, 32, 119, 111, 114, 108, 100, 33]; - -#[test] -fn test_rs256_sign_rsa2048() { - let k = gen_keypair(KeyType::Rsa2048).unwrap(); - let _ = k.sign(&JwsSignatureAlgorithm::Rs256, TEST_DATA).unwrap(); -} - -#[test] -fn test_rs256_sign_rsa4096() { - let k = gen_keypair(KeyType::Rsa4096).unwrap(); - let _ = k.sign(&JwsSignatureAlgorithm::Rs256, TEST_DATA).unwrap(); -} - -#[test] -fn test_rs256_sign_ecdsa() { - let k = gen_keypair(KeyType::EcdsaP256).unwrap(); - let res = k.sign(&JwsSignatureAlgorithm::Rs256, TEST_DATA); - assert!(res.is_err()); -} - -#[test] -fn test_es256_sign_p256() { - let k = gen_keypair(KeyType::EcdsaP256).unwrap(); - let _ = k.sign(&JwsSignatureAlgorithm::Es256, TEST_DATA).unwrap(); -} - -#[test] -fn test_es256_sign_p384() { - let k = gen_keypair(KeyType::EcdsaP384).unwrap(); - let res = k.sign(&JwsSignatureAlgorithm::Es256, TEST_DATA); - assert!(res.is_err()); -} - -#[test] -fn test_es384_sign_p384() { - let k = gen_keypair(KeyType::EcdsaP384).unwrap(); - let _ = k.sign(&JwsSignatureAlgorithm::Es384, TEST_DATA).unwrap(); -} - -#[test] -fn test_es384_sign_p256() { - let k = gen_keypair(KeyType::EcdsaP256).unwrap(); - let res = k.sign(&JwsSignatureAlgorithm::Es384, TEST_DATA); - assert!(res.is_err()); -} - -#[cfg(feature = "ed25519")] -#[test] -fn test_ed25519_sign() { - let k = gen_keypair(KeyType::Ed25519).unwrap(); - let _ = k.sign(&JwsSignatureAlgorithm::Ed25519, TEST_DATA).unwrap(); -} - -#[cfg(feature = "ed448")] -#[test] -fn test_ed448_sign() { - let k = gen_keypair(KeyType::Ed448).unwrap(); - let _ = k.sign(&JwsSignatureAlgorithm::Ed448, TEST_DATA).unwrap(); -} diff --git a/acmed/Cargo.toml b/acmed/Cargo.toml deleted file mode 100644 index a901323..0000000 --- a/acmed/Cargo.toml +++ /dev/null @@ -1,46 +0,0 @@ -[package] -name = "acmed" -version = "0.24.0" -authors = ["Rodolphe Breard "] -edition = "2018" -description = "ACME (RFC 8555) client daemon" -readme = "../README.md" -repository = "https://github.com/breard-r/acmed" -license = "MIT OR Apache-2.0" -keywords = ["acme", "tls", "X.509"] -categories = ["cryptography"] -build = "build.rs" -include = ["src/**/*", "Cargo.toml", "LICENSE-*.txt"] -publish = false -rust-version = "1.74.0" - -[features] -default = ["openssl_dyn"] -crypto_openssl = [] -openssl_dyn = ["crypto_openssl", "acme_common/openssl_dyn"] -openssl_vendored = ["crypto_openssl", "acme_common/openssl_vendored"] - -[dependencies] -acme_common = { path = "../acme_common" } -async-lock = "3.3.0" -async-process = "2.1.0" -bincode = "1.3.3" -clap = { version = "4.5.3", features = ["string"] } -futures = "0.3.30" -glob = "0.3.1" -log = "0.4.21" -nom = { version = "7.1.3", default-features = false, features = [] } -serde = { version = "1.0.197", features = ["derive"] } -serde_json = "1.0.114" -toml = "0.8.12" -tokio = { version = "1.36.0", features = ["full"] } -rand = "0.8.5" -reqwest = "0.12.1" -minijinja = "2.5.0" - -[target.'cfg(unix)'.dependencies] -nix = { version = "0.29.0", features = ["fs", "user"] } - -[build-dependencies] -serde = { version = "1.0.197", features = ["derive"] } -toml = "0.8.12" diff --git a/acmed/build.rs b/acmed/build.rs deleted file mode 100644 index 7a60ac4..0000000 --- a/acmed/build.rs +++ /dev/null @@ -1,130 +0,0 @@ -extern crate serde; -extern crate toml; - -use serde::Deserialize; -use std::env; -use std::fs::File; -use std::io::prelude::*; -use std::path::PathBuf; - -macro_rules! set_rustc_env_var { - ($name: expr, $value: expr) => {{ - println!("cargo:rustc-env={}={}", $name, $value); - }}; -} - -macro_rules! set_env_var_if_absent { - ($name: expr, $default_value: expr) => {{ - if let Err(_) = env::var($name) { - set_rustc_env_var!($name, $default_value); - } - }}; -} - -macro_rules! set_specific_path_if_absent { - ($env_name: expr, $env_default: expr, $with_dir: expr, $name: expr, $default_value: expr) => {{ - let prefix = env::var($env_name).unwrap_or(String::from($env_default)); - let mut value = PathBuf::new(); - value.push(prefix); - if ($with_dir) { - value.push("acmed"); - } - value.push($default_value); - set_env_var_if_absent!($name, value.to_str().unwrap()); - }}; -} - -macro_rules! set_data_path_if_absent { - ($name: expr, $default_value: expr) => {{ - set_specific_path_if_absent!("VARLIBDIR", "/var/lib", true, $name, $default_value); - }}; -} - -macro_rules! set_cfg_path_if_absent { - ($name: expr, $default_value: expr) => {{ - set_specific_path_if_absent!("SYSCONFDIR", "/etc", true, $name, $default_value); - }}; -} - -macro_rules! set_runstate_path_if_absent { - ($name: expr, $default_value: expr) => {{ - set_specific_path_if_absent!("RUNSTATEDIR", "/run", false, $name, $default_value); - }}; -} - -#[derive(Deserialize)] -pub struct Lock { - package: Vec, -} - -#[derive(Deserialize)] -struct Package { - name: String, - version: String, -} - -struct Error; - -impl From for Error { - fn from(_error: std::io::Error) -> Self { - Error {} - } -} - -impl From for Error { - fn from(_error: toml::de::Error) -> Self { - Error {} - } -} - -fn get_lock() -> Result { - let mut path = PathBuf::from(env!("CARGO_MANIFEST_DIR")); - path.pop(); - path.push("Cargo.lock"); - let mut file = File::open(path)?; - let mut contents = String::new(); - file.read_to_string(&mut contents)?; - let ret: Lock = toml::from_str(&contents)?; - Ok(ret) -} - -fn set_lock() { - let lock = match get_lock() { - Ok(l) => l, - Err(_) => { - return; - } - }; - for p in lock.package.iter() { - if p.name == "reqwest" { - let agent = format!("{}/{}", p.name, p.version); - set_rustc_env_var!("ACMED_HTTP_LIB_AGENT", agent); - set_rustc_env_var!("ACMED_HTTP_LIB_NAME", p.name); - set_rustc_env_var!("ACMED_HTTP_LIB_VERSION", p.version); - return; - } - } -} - -fn set_target() { - if let Ok(target) = env::var("TARGET") { - set_rustc_env_var!("ACMED_TARGET", target); - }; -} - -fn set_default_values() { - set_data_path_if_absent!("ACMED_DEFAULT_ACCOUNTS_DIR", "accounts"); - set_data_path_if_absent!("ACMED_DEFAULT_CERT_DIR", "certs"); - set_env_var_if_absent!( - "ACMED_DEFAULT_CERT_FORMAT", - "{{ name }}_{{ key_type }}.{{ file_type }}.{{ ext }}" - ); - set_cfg_path_if_absent!("ACMED_DEFAULT_CONFIG_FILE", "acmed.toml"); - set_runstate_path_if_absent!("ACMED_DEFAULT_PID_FILE", "acmed.pid"); -} - -fn main() { - set_target(); - set_lock(); - set_default_values(); -} diff --git a/acmed/src/account.rs b/acmed/src/account.rs deleted file mode 100644 index 0c07946..0000000 --- a/acmed/src/account.rs +++ /dev/null @@ -1,319 +0,0 @@ -use crate::acme_proto::account::{register_account, update_account_contacts, update_account_key}; -use crate::endpoint::Endpoint; -use crate::logs::HasLogger; -use crate::storage::FileManager; -use acme_common::crypto::{gen_keypair, HashFunction, JwsSignatureAlgorithm, KeyPair, KeyType}; -use acme_common::error::Error; -use std::collections::HashMap; -use std::fmt; -use std::str::FromStr; -use std::time::SystemTime; - -mod contact; -mod storage; - -#[derive(Clone, Debug)] -pub struct ExternalAccount { - pub identifier: String, - pub key: Vec, - pub signature_algorithm: JwsSignatureAlgorithm, -} - -#[derive(Clone, Debug)] -pub enum AccountContactType { - Mailfrom, -} - -impl FromStr for AccountContactType { - type Err = Error; - - fn from_str(s: &str) -> Result { - match s.to_lowercase().as_str() { - "mailfrom" => Ok(AccountContactType::Mailfrom), - _ => Err(format!("{s}: unknown contact type.").into()), - } - } -} - -impl fmt::Display for AccountContactType { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - let s = match self { - AccountContactType::Mailfrom => "mailfrom", - }; - write!(f, "{s}") - } -} - -#[derive(Clone, Debug)] -pub struct AccountKey { - pub creation_date: SystemTime, - pub key: KeyPair, - pub signature_algorithm: JwsSignatureAlgorithm, -} - -impl AccountKey { - fn new(key_type: KeyType, signature_algorithm: JwsSignatureAlgorithm) -> Result { - Ok(AccountKey { - creation_date: SystemTime::now(), - key: gen_keypair(key_type)?, - signature_algorithm, - }) - } -} - -#[derive(Clone, Debug, Hash)] -pub struct AccountEndpoint { - pub creation_date: SystemTime, - pub account_url: String, - pub orders_url: String, - pub key_hash: Vec, - pub contacts_hash: Vec, - pub external_account_hash: Vec, -} - -impl AccountEndpoint { - pub fn new() -> Self { - AccountEndpoint { - creation_date: SystemTime::UNIX_EPOCH, - account_url: String::new(), - orders_url: String::new(), - key_hash: Vec::new(), - contacts_hash: Vec::new(), - external_account_hash: Vec::new(), - } - } -} - -#[derive(Clone, Debug)] -pub struct Account { - pub name: String, - pub endpoints: HashMap, - pub contacts: Vec, - pub current_key: AccountKey, - pub past_keys: Vec, - pub file_manager: FileManager, - pub external_account: Option, -} - -impl HasLogger for Account { - fn warn(&self, msg: &str) { - log::warn!("account \"{}\": {msg}", &self.name); - } - - fn info(&self, msg: &str) { - log::info!("account \"{}\": {msg}", &self.name); - } - - fn debug(&self, msg: &str) { - log::debug!("account \"{}\": {msg}", &self.name); - } - - fn trace(&self, msg: &str) { - log::trace!("account \"{}\": {msg}", &self.name); - } -} - -impl Account { - pub fn get_endpoint_mut(&mut self, endpoint_name: &str) -> Result<&mut AccountEndpoint, Error> { - match self.endpoints.get_mut(endpoint_name) { - Some(ep) => Ok(ep), - None => { - let msg = format!( - "\"{}\": unknown endpoint for account \"{}\"", - endpoint_name, self.name - ); - Err(msg.into()) - } - } - } - - pub fn get_endpoint(&self, endpoint_name: &str) -> Result<&AccountEndpoint, Error> { - match self.endpoints.get(endpoint_name) { - Some(ep) => Ok(ep), - None => { - let msg = format!( - "\"{}\": unknown endpoint for account \"{}\"", - endpoint_name, self.name - ); - Err(msg.into()) - } - } - } - - pub fn get_past_key(&self, key_hash: &[u8]) -> Result<&AccountKey, Error> { - let key_hash = key_hash.to_vec(); - for key in &self.past_keys { - let past_key_hash = hash_key(key)?; - if past_key_hash == key_hash { - return Ok(key); - } - } - Err("key not found".into()) - } - - pub async fn load( - file_manager: &FileManager, - name: &str, - contacts: &[(String, String)], - key_type: &Option, - signature_algorithm: &Option, - external_account: &Option, - ) -> Result { - let contacts = contacts - .iter() - .map(|(k, v)| contact::AccountContact::new(k, v)) - .collect::, Error>>()?; - let key_type = match key_type { - Some(kt) => kt.parse()?, - None => crate::DEFAULT_ACCOUNT_KEY_TYPE, - }; - let signature_algorithm = match signature_algorithm { - Some(sa) => sa.parse()?, - None => key_type.get_default_signature_alg(), - }; - key_type.check_alg_compatibility(&signature_algorithm)?; - let account = match storage::fetch(file_manager, name).await? { - Some(mut a) => { - a.update_keys(key_type, signature_algorithm).await?; - a.contacts = contacts; - a.external_account = external_account.to_owned(); - a - } - None => { - let account = Account { - name: name.to_string(), - endpoints: HashMap::new(), - contacts, - current_key: AccountKey::new(key_type, signature_algorithm)?, - past_keys: Vec::new(), - file_manager: file_manager.clone(), - external_account: external_account.to_owned(), - }; - account.debug("initializing a new account"); - account - } - }; - Ok(account) - } - - pub fn add_endpoint_name(&mut self, endpoint_name: &str) { - self.endpoints - .entry(endpoint_name.to_string()) - .or_insert_with(AccountEndpoint::new); - } - - pub async fn synchronize(&mut self, endpoint: &mut Endpoint) -> Result<(), Error> { - let acc_ep = self.get_endpoint(&endpoint.name)?; - if !acc_ep.account_url.is_empty() { - if let Some(ec) = &self.external_account { - let external_account_hash = hash_external_account(ec); - if external_account_hash != acc_ep.external_account_hash { - let msg = format!( - "external account changed on endpoint \"{}\"", - &endpoint.name - ); - self.info(&msg); - register_account(endpoint, self).await?; - return Ok(()); - } - } - let ct_hash = hash_contacts(&self.contacts); - let key_hash = hash_key(&self.current_key)?; - let contacts_changed = ct_hash != acc_ep.contacts_hash; - let key_changed = key_hash != acc_ep.key_hash; - if contacts_changed { - update_account_contacts(endpoint, self).await?; - } - if key_changed { - update_account_key(endpoint, self).await?; - } - } else { - register_account(endpoint, self).await?; - } - Ok(()) - } - - pub async fn register(&mut self, endpoint: &mut Endpoint) -> Result<(), Error> { - register_account(endpoint, self).await - } - - pub async fn save(&self) -> Result<(), Error> { - storage::save(&self.file_manager, self).await - } - - pub fn set_account_url(&mut self, endpoint_name: &str, account_url: &str) -> Result<(), Error> { - let ep = self.get_endpoint_mut(endpoint_name)?; - ep.account_url = account_url.to_string(); - Ok(()) - } - - pub fn set_orders_url(&mut self, endpoint_name: &str, orders_url: &str) -> Result<(), Error> { - let ep = self.get_endpoint_mut(endpoint_name)?; - ep.orders_url = orders_url.to_string(); - Ok(()) - } - - pub fn update_key_hash(&mut self, endpoint_name: &str) -> Result<(), Error> { - let key = self.current_key.clone(); - let ep = self.get_endpoint_mut(endpoint_name)?; - ep.key_hash = hash_key(&key)?; - Ok(()) - } - - pub fn update_contacts_hash(&mut self, endpoint_name: &str) -> Result<(), Error> { - let ct = self.contacts.clone(); - let ep = self.get_endpoint_mut(endpoint_name)?; - ep.contacts_hash = hash_contacts(&ct); - Ok(()) - } - - pub fn update_external_account_hash(&mut self, endpoint_name: &str) -> Result<(), Error> { - if let Some(ec) = &self.external_account { - let ec = ec.clone(); - let ep = self.get_endpoint_mut(endpoint_name)?; - ep.external_account_hash = hash_external_account(&ec); - } - Ok(()) - } - - async fn update_keys( - &mut self, - key_type: KeyType, - signature_algorithm: JwsSignatureAlgorithm, - ) -> Result<(), Error> { - if self.current_key.key.key_type != key_type - || self.current_key.signature_algorithm != signature_algorithm - { - self.debug("account key has been changed in the configuration, creating a new one..."); - self.past_keys.push(self.current_key.to_owned()); - self.current_key = AccountKey::new(key_type, signature_algorithm)?; - self.save().await?; - let msg = format!("new {key_type} account key created, using {signature_algorithm} as signing algorithm"); - self.info(&msg); - } else { - self.trace("account key is up to date"); - } - Ok(()) - } -} - -fn hash_contacts(contacts: &[contact::AccountContact]) -> Vec { - let msg = contacts - .iter() - .map(|v| v.to_string()) - .collect::>() - .join("") - .into_bytes(); - HashFunction::Sha256.hash(&msg) -} - -fn hash_key(key: &AccountKey) -> Result, Error> { - let pem = key.key.public_key_to_pem()?; - Ok(HashFunction::Sha256.hash(&pem)) -} - -fn hash_external_account(ec: &ExternalAccount) -> Vec { - let mut msg = ec.key.clone(); - msg.extend(ec.identifier.as_bytes()); - HashFunction::Sha256.hash(&msg) -} diff --git a/acmed/src/account/contact.rs b/acmed/src/account/contact.rs deleted file mode 100644 index 2a6b5f2..0000000 --- a/acmed/src/account/contact.rs +++ /dev/null @@ -1,110 +0,0 @@ -use acme_common::error::Error; -use std::fmt; -use std::str::FromStr; - -fn clean_mailto(value: &str) -> Result { - // TODO: implement a simple RFC 6068 parser - // - no "hfields" - // - max one "addr-spec" in the "to" component - Ok(value.to_string()) -} - -// TODO: implement other URI shemes -// https://www.iana.org/assignments/uri-schemes/uri-schemes.xhtml -// https://en.wikipedia.org/wiki/List_of_URI_schemes -// Exemples: -// - P1: tel, sms -// - P2: geo, maps -// - P3: irc, irc6, ircs, xmpp -// - P4: sip, sips -#[derive(Clone, Debug, PartialEq)] -pub enum ContactType { - Mailto, -} - -impl ContactType { - pub fn clean_value(&self, value: &str) -> Result { - match self { - ContactType::Mailto => clean_mailto(value), - } - } -} - -impl FromStr for ContactType { - type Err = Error; - - fn from_str(s: &str) -> Result { - match s.to_lowercase().as_str() { - "mailto" => Ok(ContactType::Mailto), - _ => Err(format!("{s}: unknown contact type.").into()), - } - } -} - -impl fmt::Display for ContactType { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - let s = match self { - ContactType::Mailto => "mailto", - }; - write!(f, "{s}") - } -} - -#[derive(Clone, Debug, PartialEq)] -pub struct AccountContact { - pub contact_type: ContactType, - pub value: String, -} - -impl AccountContact { - pub fn new(contact_type: &str, value: &str) -> Result { - let contact_type: ContactType = contact_type.parse()?; - let value = contact_type.clean_value(value)?; - Ok(AccountContact { - contact_type, - value, - }) - } -} - -impl fmt::Display for AccountContact { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{}:{}", self.contact_type, self.value) - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_account_contact_eq() { - let c1 = AccountContact::new("mailto", "derp.derpson@example.com").unwrap(); - let c2 = AccountContact::new("mailto", "derp.derpson@example.com").unwrap(); - let c3 = AccountContact::new("mailto", "derp@example.com").unwrap(); - assert_eq!(c1, c2); - assert_eq!(c2, c1); - assert_ne!(c1, c3); - assert_ne!(c2, c3); - } - - #[test] - fn test_account_contact_in_vec() { - let contacts = vec![ - AccountContact::new("mailto", "derp.derpson@example.com").unwrap(), - AccountContact::new("mailto", "derp@example.com").unwrap(), - ]; - let c = AccountContact::new("mailto", "derp@example.com").unwrap(); - assert!(contacts.contains(&c)); - } - - #[test] - fn test_account_contact_not_in_vec() { - let contacts = vec![ - AccountContact::new("mailto", "derp.derpson@example.com").unwrap(), - AccountContact::new("mailto", "derp@example.com").unwrap(), - ]; - let c = AccountContact::new("mailto", "derpina@example.com").unwrap(); - assert!(!contacts.contains(&c)); - } -} diff --git a/acmed/src/account/storage.rs b/acmed/src/account/storage.rs deleted file mode 100644 index d3f045f..0000000 --- a/acmed/src/account/storage.rs +++ /dev/null @@ -1,186 +0,0 @@ -use crate::account::contact::AccountContact; -use crate::account::{Account, AccountEndpoint, AccountKey, ExternalAccount}; -use crate::storage::{account_files_exists, get_account_data, set_account_data, FileManager}; -use acme_common::crypto::KeyPair; -use acme_common::error::Error; -use serde::{Deserialize, Serialize}; -use std::collections::HashMap; -use std::time::SystemTime; - -#[derive(Serialize, Deserialize, PartialEq, Debug)] -pub struct ExternalAccountStorage { - pub identifier: String, - pub key: Vec, - pub signature_algorithm: String, -} - -impl ExternalAccountStorage { - fn new(external_account: &ExternalAccount) -> Self { - ExternalAccountStorage { - identifier: external_account.identifier.to_owned(), - key: external_account.key.to_owned(), - signature_algorithm: external_account.signature_algorithm.to_string(), - } - } - - fn to_generic(&self) -> Result { - Ok(ExternalAccount { - identifier: self.identifier.to_owned(), - key: self.key.to_owned(), - signature_algorithm: self.signature_algorithm.parse()?, - }) - } -} - -#[derive(Serialize, Deserialize, PartialEq, Debug)] -struct AccountKeyStorage { - creation_date: SystemTime, - key: Vec, - signature_algorithm: String, -} - -impl AccountKeyStorage { - fn new(key: &AccountKey) -> Result { - Ok(AccountKeyStorage { - creation_date: key.creation_date, - key: key.key.private_key_to_der()?, - signature_algorithm: key.signature_algorithm.to_string(), - }) - } - - fn to_generic(&self) -> Result { - Ok(AccountKey { - creation_date: self.creation_date, - key: KeyPair::from_der(&self.key)?, - signature_algorithm: self.signature_algorithm.parse()?, - }) - } -} - -#[derive(Serialize, Deserialize, PartialEq, Debug)] -struct AccountEndpointStorage { - creation_date: SystemTime, - account_url: String, - orders_url: String, - key_hash: Vec, - contacts_hash: Vec, - external_account_hash: Vec, -} - -impl AccountEndpointStorage { - fn new(account_endpoint: &AccountEndpoint) -> Self { - AccountEndpointStorage { - creation_date: account_endpoint.creation_date, - account_url: account_endpoint.account_url.clone(), - orders_url: account_endpoint.orders_url.clone(), - key_hash: account_endpoint.key_hash.clone(), - contacts_hash: account_endpoint.contacts_hash.clone(), - external_account_hash: account_endpoint.external_account_hash.clone(), - } - } - - fn to_generic(&self) -> AccountEndpoint { - AccountEndpoint { - creation_date: self.creation_date, - account_url: self.account_url.clone(), - orders_url: self.orders_url.clone(), - key_hash: self.key_hash.clone(), - contacts_hash: self.contacts_hash.clone(), - external_account_hash: self.external_account_hash.clone(), - } - } -} - -#[derive(Serialize, Deserialize, PartialEq, Debug)] -struct AccountStorage { - name: String, - endpoints: HashMap, - contacts: Vec<(String, String)>, - current_key: AccountKeyStorage, - past_keys: Vec, - external_account: Option, -} - -async fn do_fetch(file_manager: &FileManager, name: &str) -> Result, Error> { - if account_files_exists(file_manager) { - let data = get_account_data(file_manager).await?; - let obj: AccountStorage = bincode::deserialize(&data[..]) - .map_err(|e| Error::from(&e.to_string()).prefix(name))?; - let endpoints = obj - .endpoints - .iter() - .map(|(k, v)| (k.clone(), v.to_generic())) - .collect(); - let contacts = obj - .contacts - .iter() - .map(|(t, v)| AccountContact::new(t, v)) - .collect::, Error>>()?; - let current_key = obj.current_key.to_generic()?; - let past_keys = obj - .past_keys - .iter() - .map(|k| k.to_generic()) - .collect::, Error>>()?; - let external_account = match obj.external_account { - Some(a) => Some(a.to_generic()?), - None => None, - }; - Ok(Some(Account { - name: obj.name, - endpoints, - contacts, - current_key, - past_keys, - file_manager: file_manager.clone(), - external_account, - })) - } else { - Ok(None) - } -} - -async fn do_save(file_manager: &FileManager, account: &Account) -> Result<(), Error> { - let endpoints: HashMap = account - .endpoints - .iter() - .map(|(k, v)| (k.to_owned(), AccountEndpointStorage::new(v))) - .collect(); - let contacts: Vec<(String, String)> = account - .contacts - .iter() - .map(|c| (c.contact_type.to_string(), c.value.to_owned())) - .collect(); - let past_keys = account - .past_keys - .iter() - .map(AccountKeyStorage::new) - .collect::, Error>>()?; - let external_account = account - .external_account - .as_ref() - .map(ExternalAccountStorage::new); - let account_storage = AccountStorage { - name: account.name.to_owned(), - endpoints, - contacts, - current_key: AccountKeyStorage::new(&account.current_key)?, - past_keys, - external_account, - }; - let encoded: Vec = bincode::serialize(&account_storage) - .map_err(|e| Error::from(&e.to_string()).prefix(&account.name))?; - set_account_data(file_manager, &encoded).await -} - -pub async fn fetch(file_manager: &FileManager, name: &str) -> Result, Error> { - do_fetch(file_manager, name).await.map_err(|_| { - format!("account \"{name}\": unable to load account file: file may be corrupted").into() - }) -} - -pub async fn save(file_manager: &FileManager, account: &Account) -> Result<(), Error> { - do_save(file_manager, account) - .await - .map_err(|e| format!("unable to save account file: {e}").into()) -} diff --git a/acmed/src/acme_proto.rs b/acmed/src/acme_proto.rs deleted file mode 100644 index 9aa0526..0000000 --- a/acmed/src/acme_proto.rs +++ /dev/null @@ -1,292 +0,0 @@ -use crate::acme_proto::structs::{ - AcmeError, ApiError, Authorization, AuthorizationStatus, NewOrder, Order, OrderStatus, -}; -use crate::certificate::Certificate; -use crate::http::HttpError; -use crate::identifier::IdentifierType; -use crate::jws::encode_kid; -use crate::logs::HasLogger; -use crate::storage; -use crate::{AccountSync, EndpointSync}; -use acme_common::crypto::Csr; -use acme_common::error::Error; -use serde_json::json; -use std::fmt; - -pub mod account; -mod certificate; -mod http; -pub mod structs; - -#[derive(Clone, Copy, Debug, PartialEq)] -pub enum Challenge { - Http01, - Dns01, - TlsAlpn01, -} - -impl Challenge { - pub fn from_str(s: &str) -> Result { - match s.to_lowercase().as_str() { - "http-01" => Ok(Challenge::Http01), - "dns-01" => Ok(Challenge::Dns01), - "tls-alpn-01" => Ok(Challenge::TlsAlpn01), - _ => Err(format!("{s}: unknown challenge.").into()), - } - } -} - -impl fmt::Display for Challenge { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - let s = match self { - Challenge::Http01 => "http-01", - Challenge::Dns01 => "dns-01", - Challenge::TlsAlpn01 => "tls-alpn-01", - }; - write!(f, "{s}") - } -} - -impl PartialEq for Challenge { - fn eq(&self, other: &structs::Challenge) -> bool { - matches!( - (self, other), - (Challenge::Http01, structs::Challenge::Http01(_)) - | (Challenge::Dns01, structs::Challenge::Dns01(_)) - | (Challenge::TlsAlpn01, structs::Challenge::TlsAlpn01(_)) - ) - } -} - -#[macro_export] -macro_rules! set_data_builder_sync { - ($account: ident, $endpoint_name: ident, $data: expr) => {{ - let endpoint_name = &$endpoint_name; - move |n: &str, url: &str| { - encode_kid( - &$account.current_key.key, - &$account.current_key.signature_algorithm, - &($account.get_endpoint(endpoint_name)?.account_url), - $data, - url, - n, - ) - } - }}; -} - -#[macro_export] -macro_rules! set_data_builder { - ($account: ident, $endpoint_name: ident, $data: expr) => { - async { - let account = $account.read().await; - set_data_builder_sync!(account, $endpoint_name, $data) - } - }; -} - -pub async fn request_certificate( - cert: &Certificate, - account_s: AccountSync, - endpoint_s: EndpointSync, -) -> Result<(), Error> { - let mut hook_datas = vec![]; - let endpoint_name = endpoint_s.read().await.name.clone(); - - // Refresh the directory - http::refresh_directory(&mut *(endpoint_s.write().await)) - .await - .map_err(HttpError::in_err)?; - - // Synchronize the account - account_s - .write() - .await - .synchronize(&mut *(endpoint_s.write().await)) - .await?; - - // Create a new order - let mut new_reg = false; - let (order, order_url) = loop { - let new_order = NewOrder::new(&cert.identifiers); - let new_order = serde_json::to_string(&new_order)?; - let data_builder = set_data_builder!(account_s, endpoint_name, new_order.as_bytes()).await; - match http::new_order(&mut *(endpoint_s.write().await), &data_builder).await { - Ok((order, order_url)) => { - if let Some(e) = order.get_error() { - cert.warn(&e.prefix("Error").message); - } - break (order, order_url); - } - Err(e) => { - if !new_reg && e.is_acme_err(AcmeError::AccountDoesNotExist) { - drop(data_builder); - account_s - .write() - .await - .register(&mut *(endpoint_s.write().await)) - .await?; - new_reg = true; - } else { - return Err(HttpError::in_err(e)); - } - } - }; - }; - - // Begin iter over authorizations - for auth_url in order.authorizations.iter() { - // Fetch the authorization - let data_builder = set_data_builder!(account_s, endpoint_name, b"").await; - let auth = - http::get_authorization(&mut *(endpoint_s.write().await), &data_builder, auth_url) - .await - .map_err(HttpError::in_err)?; - drop(data_builder); - if let Some(e) = auth.get_error() { - cert.warn(&e.prefix("error").message); - } - if auth.status == AuthorizationStatus::Valid { - continue; - } - if auth.status != AuthorizationStatus::Pending { - let msg = format!( - "{}: authorization status is {}", - auth.identifier, auth.status - ); - return Err(msg.into()); - } - - // Fetch the associated challenges - let current_identifier = cert.get_identifier_from_str(&auth.identifier.value)?; - let current_challenge = current_identifier.challenge; - for challenge in auth.challenges.iter() { - if current_challenge == *challenge { - let (proof, raw_proof) = - challenge.get_proof(&account_s.read().await.current_key.key)?; - let file_name = challenge.get_file_name(); - let identifier = auth.identifier.value.to_owned(); - - // Call the challenge hook in order to complete it - let mut data = cert - .call_challenge_hooks(&file_name, &proof, raw_proof, &identifier) - .await?; - data.0.is_clean_hook = true; - hook_datas.push(data); - - // Tell the server the challenge has been completed - let chall_url = challenge.get_url(); - let data_builder = set_data_builder!(account_s, endpoint_name, b"{}").await; - http::post_jose_no_response( - &mut *(endpoint_s.write().await), - &data_builder, - &chall_url, - ) - .await - .map_err(HttpError::in_err)?; - drop(data_builder); - } - } - - // Pool the authorization in order to see whether or not it is valid - let data_builder = set_data_builder!(account_s, endpoint_name, b"").await; - let break_fn = |a: &Authorization| a.status == AuthorizationStatus::Valid; - let _ = http::pool_authorization( - &mut *(endpoint_s.write().await), - &data_builder, - &break_fn, - auth_url, - ) - .await - .map_err(HttpError::in_err)?; - drop(data_builder); - for (data, hook_type) in hook_datas.iter() { - cert.call_challenge_hooks_clean(data, (*hook_type).to_owned()) - .await?; - } - hook_datas.clear(); - } - // End iter over authorizations - - // Pool the order in order to see whether or not it is ready - let data_builder = set_data_builder!(account_s, endpoint_name, b"").await; - let break_fn = |o: &Order| o.status == OrderStatus::Ready; - let order = http::pool_order( - &mut *(endpoint_s.write().await), - &data_builder, - &break_fn, - &order_url, - ) - .await - .map_err(HttpError::in_err)?; - drop(data_builder); - - // Finalize the order by sending the CSR - let key_pair = certificate::get_key_pair(cert).await?; - let domains: Vec = cert - .identifiers - .iter() - .filter(|e| e.id_type == IdentifierType::Dns) - .map(|e| e.value.to_owned()) - .collect(); - let ips: Vec = cert - .identifiers - .iter() - .filter(|e| e.id_type == IdentifierType::Ip) - .map(|e| e.value.to_owned()) - .collect(); - let csr = Csr::new( - &key_pair, - cert.csr_digest, - domains.as_slice(), - ips.as_slice(), - &cert.subject_attributes, - )?; - cert.trace(&format!("new CSR:\n{}", csr.to_pem()?)); - let csr = json!({ - "csr": csr.to_der_base64()?, - }); - let csr = csr.to_string(); - let data_builder = set_data_builder!(account_s, endpoint_name, csr.as_bytes()).await; - let order = http::finalize_order( - &mut *(endpoint_s.write().await), - &data_builder, - &order.finalize, - ) - .await - .map_err(HttpError::in_err)?; - drop(data_builder); - if let Some(e) = order.get_error() { - cert.warn(&e.prefix("error").message); - } - - // Pool the order in order to see whether or not it is valid - let data_builder = set_data_builder!(account_s, endpoint_name, b"").await; - let break_fn = |o: &Order| o.status == OrderStatus::Valid; - let order = http::pool_order( - &mut *(endpoint_s.write().await), - &data_builder, - &break_fn, - &order_url, - ) - .await - .map_err(HttpError::in_err)?; - drop(data_builder); - - // Download the certificate - let crt_url = order - .certificate - .ok_or_else(|| Error::from("no certificate available for download"))?; - let data_builder = set_data_builder!(account_s, endpoint_name, b"").await; - let crt = http::get_certificate(&mut *(endpoint_s.write().await), &data_builder, &crt_url) - .await - .map_err(HttpError::in_err)?; - drop(data_builder); - storage::write_certificate(&cert.file_manager, crt.as_bytes()).await?; - - cert.info(&format!( - "certificate renewed (identifiers: {})", - cert.identifier_list() - )); - Ok(()) -} diff --git a/acmed/src/acme_proto/account.rs b/acmed/src/acme_proto/account.rs deleted file mode 100644 index 8b8fec0..0000000 --- a/acmed/src/acme_proto/account.rs +++ /dev/null @@ -1,154 +0,0 @@ -use crate::account::Account as BaseAccount; -use crate::acme_proto::http; -use crate::acme_proto::structs::{Account, AccountKeyRollover, AccountUpdate, AcmeError}; -use crate::endpoint::Endpoint; -use crate::http::HttpError; -use crate::jws::{encode_jwk, encode_kid}; -use crate::logs::HasLogger; -use crate::set_data_builder_sync; -use acme_common::error::Error; - -macro_rules! create_account_if_does_not_exist { - ($e: expr, $endpoint: ident, $account: ident) => { - match $e { - Ok(r) => Ok(r), - Err(he) => match he { - HttpError::ApiError(ref e) => match e.get_acme_type() { - AcmeError::AccountDoesNotExist => { - let msg = format!( - "account has been dropped by endpoint \"{}\"", - $endpoint.name - ); - $account.debug(&msg); - return register_account($endpoint, $account).await; - } - _ => Err(HttpError::in_err(he.to_owned())), - }, - HttpError::GenericError(e) => Err(e), - }, - } - }; -} - -pub async fn register_account( - endpoint: &mut Endpoint, - account: &mut BaseAccount, -) -> Result<(), Error> { - account.debug(&format!( - "creating account on endpoint \"{}\"...", - &endpoint.name - )); - let account_struct = Account::new(account, endpoint)?; - let account_struct = serde_json::to_string(&account_struct)?; - let acc_ref = &account_struct; - let kp_ref = &account.current_key.key; - let signature_algorithm = &account.current_key.signature_algorithm; - let data_builder = |n: &str, url: &str| { - encode_jwk( - kp_ref, - signature_algorithm, - acc_ref.as_bytes(), - url, - Some(n.to_string()), - ) - }; - let (acc_rep, account_url) = http::new_account(endpoint, &data_builder) - .await - .map_err(HttpError::in_err)?; - account.set_account_url(&endpoint.name, &account_url)?; - let orders_url = match acc_rep.orders { - Some(url) => url, - None => { - let msg = format!( - "endpoint \"{}\": account \"{}\": the server has not provided an order URL upon account creation", - &endpoint.name, - &account.name - ); - account.warn(&msg); - String::new() - } - }; - account.set_orders_url(&endpoint.name, &orders_url)?; - account.update_key_hash(&endpoint.name)?; - account.update_contacts_hash(&endpoint.name)?; - account.update_external_account_hash(&endpoint.name)?; - account.save().await?; - account.info(&format!( - "account created on endpoint \"{}\"", - &endpoint.name - )); - Ok(()) -} - -pub async fn update_account_contacts( - endpoint: &mut Endpoint, - account: &mut BaseAccount, -) -> Result<(), Error> { - let endpoint_name = endpoint.name.clone(); - account.debug(&format!( - "updating account contacts on endpoint \"{endpoint_name}\"..." - )); - let new_contacts: Vec = account.contacts.iter().map(|c| c.to_string()).collect(); - let acc_up_struct = AccountUpdate::new(&new_contacts); - let acc_up_struct = serde_json::to_string(&acc_up_struct)?; - let account_owned = account.clone(); - let data_builder = - set_data_builder_sync!(account_owned, endpoint_name, acc_up_struct.as_bytes()); - let url = account.get_endpoint(&endpoint_name)?.account_url.clone(); - create_account_if_does_not_exist!( - http::post_jose_no_response(endpoint, &data_builder, &url).await, - endpoint, - account - )?; - account.update_contacts_hash(&endpoint_name)?; - account.save().await?; - account.info(&format!( - "account contacts updated on endpoint \"{endpoint_name}\"" - )); - Ok(()) -} - -pub async fn update_account_key( - endpoint: &mut Endpoint, - account: &mut BaseAccount, -) -> Result<(), Error> { - let endpoint_name = endpoint.name.clone(); - account.debug(&format!( - "updating account key on endpoint \"{endpoint_name}\"..." - )); - let url = endpoint.dir.key_change.clone(); - let ep = account.get_endpoint(&endpoint_name)?; - let old_account_key = account.get_past_key(&ep.key_hash)?; - let old_key = &old_account_key.key; - let account_url = account.get_endpoint(&endpoint_name)?.account_url.clone(); - let rollover_struct = AccountKeyRollover::new(&account_url, old_key)?; - let rollover_struct = serde_json::to_string(&rollover_struct)?; - let rollover_payload = encode_jwk( - &account.current_key.key, - &account.current_key.signature_algorithm, - rollover_struct.as_bytes(), - &url, - None, - )?; - let data_builder = |n: &str, url: &str| { - encode_kid( - old_key, - &old_account_key.signature_algorithm, - &account_url, - rollover_payload.as_bytes(), - url, - n, - ) - }; - create_account_if_does_not_exist!( - http::post_jose_no_response(endpoint, &data_builder, &url).await, - endpoint, - account - )?; - account.update_key_hash(&endpoint_name)?; - account.save().await?; - account.info(&format!( - "account key updated on endpoint \"{endpoint_name}\"" - )); - Ok(()) -} diff --git a/acmed/src/acme_proto/certificate.rs b/acmed/src/acme_proto/certificate.rs deleted file mode 100644 index c7ca47d..0000000 --- a/acmed/src/acme_proto/certificate.rs +++ /dev/null @@ -1,25 +0,0 @@ -use crate::certificate::Certificate; -use crate::storage; -use acme_common::crypto::{gen_keypair, KeyPair}; -use acme_common::error::Error; - -async fn gen_key_pair(cert: &Certificate) -> Result { - let key_pair = gen_keypair(cert.key_type)?; - storage::set_keypair(&cert.file_manager, &key_pair).await?; - Ok(key_pair) -} - -async fn read_key_pair(cert: &Certificate) -> Result { - storage::get_keypair(&cert.file_manager).await -} - -pub async fn get_key_pair(cert: &Certificate) -> Result { - if cert.kp_reuse { - match read_key_pair(cert).await { - Ok(key_pair) => Ok(key_pair), - Err(_) => gen_key_pair(cert).await, - } - } else { - gen_key_pair(cert).await - } -} diff --git a/acmed/src/acme_proto/http.rs b/acmed/src/acme_proto/http.rs deleted file mode 100644 index 212ef1c..0000000 --- a/acmed/src/acme_proto/http.rs +++ /dev/null @@ -1,149 +0,0 @@ -use crate::acme_proto::structs::{AccountResponse, Authorization, Directory, Order}; -use crate::endpoint::Endpoint; -use crate::http; -use acme_common::error::Error; -use std::{thread, time}; - -macro_rules! pool_object { - ($obj_type: ty, $obj_name: expr, $endpoint: expr, $url: expr, $data_builder: expr, $break: expr) => {{ - for _ in 0..crate::DEFAULT_POOL_NB_TRIES { - thread::sleep(time::Duration::from_secs(crate::DEFAULT_POOL_WAIT_SEC)); - let response = http::post_jose($endpoint, $url, $data_builder).await?; - let obj = response.json::<$obj_type>()?; - if $break(&obj) { - return Ok(obj); - } - } - let msg = format!("{} pooling failed on {}", $obj_name, $url); - Err(msg.into()) - }}; -} - -pub async fn refresh_directory(endpoint: &mut Endpoint) -> Result<(), http::HttpError> { - let url = endpoint.url.clone(); - let response = http::get(endpoint, &url).await?; - endpoint.dir = response.json::()?; - Ok(()) -} - -pub async fn post_jose_no_response( - endpoint: &mut Endpoint, - data_builder: &F, - url: &str, -) -> Result<(), http::HttpError> -where - F: Fn(&str, &str) -> Result, -{ - let _ = http::post_jose(endpoint, url, data_builder).await?; - Ok(()) -} - -pub async fn new_account( - endpoint: &mut Endpoint, - data_builder: &F, -) -> Result<(AccountResponse, String), http::HttpError> -where - F: Fn(&str, &str) -> Result, -{ - let url = endpoint.dir.new_account.clone(); - let response = http::post_jose(endpoint, &url, data_builder).await?; - let acc_uri = response - .get_header(http::HEADER_LOCATION) - .ok_or_else(|| Error::from("no account location found"))?; - let acc_resp = response.json::()?; - Ok((acc_resp, acc_uri)) -} - -pub async fn new_order( - endpoint: &mut Endpoint, - data_builder: &F, -) -> Result<(Order, String), http::HttpError> -where - F: Fn(&str, &str) -> Result, -{ - let url = endpoint.dir.new_order.clone(); - let response = http::post_jose(endpoint, &url, data_builder).await?; - let order_uri = response - .get_header(http::HEADER_LOCATION) - .ok_or_else(|| Error::from("no account location found"))?; - let order_resp = response.json::()?; - Ok((order_resp, order_uri)) -} - -pub async fn get_authorization( - endpoint: &mut Endpoint, - data_builder: &F, - url: &str, -) -> Result -where - F: Fn(&str, &str) -> Result, -{ - let response = http::post_jose(endpoint, url, data_builder).await?; - let auth = response.json::()?; - Ok(auth) -} - -pub async fn pool_authorization( - endpoint: &mut Endpoint, - data_builder: &F, - break_fn: &S, - url: &str, -) -> Result -where - F: Fn(&str, &str) -> Result, - S: Fn(&Authorization) -> bool, -{ - pool_object!( - Authorization, - "authorization", - endpoint, - url, - data_builder, - break_fn - ) -} - -pub async fn pool_order( - endpoint: &mut Endpoint, - data_builder: &F, - break_fn: &S, - url: &str, -) -> Result -where - F: Fn(&str, &str) -> Result, - S: Fn(&Order) -> bool, -{ - pool_object!(Order, "order", endpoint, url, data_builder, break_fn) -} - -pub async fn finalize_order( - endpoint: &mut Endpoint, - data_builder: &F, - url: &str, -) -> Result -where - F: Fn(&str, &str) -> Result, -{ - let response = http::post_jose(endpoint, url, data_builder).await?; - let order = response.json::()?; - Ok(order) -} - -pub async fn get_certificate( - endpoint: &mut Endpoint, - data_builder: &F, - url: &str, -) -> Result -where - F: Fn(&str, &str) -> Result, -{ - let response = http::post( - endpoint, - url, - data_builder, - http::CONTENT_TYPE_JOSE, - http::CONTENT_TYPE_PEM, - ) - .await?; - Ok(response.body) -} diff --git a/acmed/src/acme_proto/structs.rs b/acmed/src/acme_proto/structs.rs deleted file mode 100644 index 6473d97..0000000 --- a/acmed/src/acme_proto/structs.rs +++ /dev/null @@ -1,29 +0,0 @@ -#[macro_export] -macro_rules! deserialize_from_str { - ($t: ty) => { - impl FromStr for $t { - type Err = Error; - - fn from_str(data: &str) -> Result { - let res = serde_json::from_str(data)?; - Ok(res) - } - } - }; -} - -mod account; -mod authorization; -mod directory; -mod error; -mod order; - -#[allow(unused_imports)] -pub use account::{ - Account, AccountDeactivation, AccountKeyRollover, AccountResponse, AccountUpdate, -}; -pub use authorization::{Authorization, AuthorizationStatus, Challenge}; -pub use deserialize_from_str; -pub use directory::Directory; -pub use error::{AcmeError, ApiError, HttpApiError}; -pub use order::{Identifier, NewOrder, Order, OrderStatus}; diff --git a/acmed/src/acme_proto/structs/account.rs b/acmed/src/acme_proto/structs/account.rs deleted file mode 100644 index f9acdd4..0000000 --- a/acmed/src/acme_proto/structs/account.rs +++ /dev/null @@ -1,202 +0,0 @@ -use crate::endpoint::Endpoint; -use crate::jws::encode_kid_mac; -use acme_common::crypto::KeyPair; -use acme_common::error::Error; -use serde::{Deserialize, Serialize}; -use serde_json::value::Value; -use std::str::FromStr; - -#[derive(Serialize)] -#[serde(rename_all = "camelCase")] -pub struct Account { - pub contact: Vec, - pub terms_of_service_agreed: bool, - pub only_return_existing: bool, - #[serde(skip_serializing_if = "Option::is_none")] - pub external_account_binding: Option, -} - -impl Account { - pub fn new(account: &crate::account::Account, endpoint: &Endpoint) -> Result { - let external_account_binding = match &account.external_account { - Some(a) => { - let k_ref = &a.key; - let signature_algorithm = &a.signature_algorithm; - let kid = &a.identifier; - let payload = account.current_key.key.jwk_public_key()?; - let payload = serde_json::to_string(&payload)?; - let data = encode_kid_mac( - k_ref, - signature_algorithm, - kid, - payload.as_bytes(), - &endpoint.dir.new_account, - )?; - let data: Value = serde_json::from_str(&data)?; - Some(data) - } - None => None, - }; - Ok(Account { - contact: account.contacts.iter().map(|e| e.to_string()).collect(), - terms_of_service_agreed: endpoint.tos_agreed, - only_return_existing: false, - external_account_binding, - }) - } -} - -#[derive(Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct AccountResponse { - #[allow(dead_code)] - pub status: String, - #[allow(dead_code)] - pub contact: Option>, - #[allow(dead_code)] - pub terms_of_service_agreed: Option, - #[allow(dead_code)] - pub external_account_binding: Option, - pub orders: Option, -} - -deserialize_from_str!(AccountResponse); - -#[derive(Serialize)] -pub struct AccountUpdate { - pub contact: Vec, -} - -impl AccountUpdate { - pub fn new(contact: &[String]) -> Self { - AccountUpdate { - contact: contact.into(), - } - } -} - -#[derive(Serialize)] -#[serde(rename_all = "camelCase")] -pub struct AccountKeyRollover { - pub account: String, - pub old_key: Value, -} - -impl AccountKeyRollover { - pub fn new(account_str: &str, old_key: &KeyPair) -> Result { - Ok(AccountKeyRollover { - account: account_str.to_string(), - old_key: old_key.jwk_public_key()?, - }) - } -} - -// TODO: implement account deactivation -#[allow(dead_code)] -#[derive(Serialize)] -pub struct AccountDeactivation { - pub status: String, -} - -impl AccountDeactivation { - #[allow(dead_code)] - pub fn new() -> Self { - AccountDeactivation { - status: "deactivated".into(), - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_account_new() { - let emails = vec![ - "mailto:derp@example.com".to_string(), - "mailto:derp.derpson@example.com".to_string(), - ]; - let a = Account { - contact: emails, - terms_of_service_agreed: true, - only_return_existing: false, - external_account_binding: None, - }; - assert_eq!(a.contact.len(), 2); - assert_eq!(a.terms_of_service_agreed, true); - assert_eq!(a.only_return_existing, false); - let a_str = serde_json::to_string(&a); - assert!(a_str.is_ok()); - let a_str = a_str.unwrap(); - assert!(a_str.starts_with("{")); - assert!(a_str.ends_with("}")); - assert!(a_str.contains("\"contact\"")); - assert!(a_str.contains("\"mailto:derp@example.com\"")); - assert!(a_str.contains("\"mailto:derp.derpson@example.com\"")); - assert!(a_str.contains("\"termsOfServiceAgreed\"")); - assert!(a_str.contains("\"onlyReturnExisting\"")); - assert!(a_str.contains("true")); - assert!(a_str.contains("false")); - } - - #[test] - fn test_account_response() { - let data = "{ - \"status\": \"valid\", - \"contact\": [ - \"mailto:cert-admin@example.org\", - \"mailto:admin@example.org\" - ], - \"termsOfServiceAgreed\": true, - \"orders\": \"https://example.com/acme/orders/rzGoeA\" -}"; - let account_resp = AccountResponse::from_str(data); - assert!(account_resp.is_ok()); - let account_resp = account_resp.unwrap(); - assert_eq!(account_resp.status, "valid"); - assert!(account_resp.contact.is_some()); - let contacts = account_resp.contact.unwrap(); - assert_eq!(contacts.len(), 2); - assert_eq!(contacts[0], "mailto:cert-admin@example.org"); - assert_eq!(contacts[1], "mailto:admin@example.org"); - assert!(account_resp.external_account_binding.is_none()); - assert!(account_resp.terms_of_service_agreed.is_some()); - assert!(account_resp.terms_of_service_agreed.unwrap()); - assert_eq!( - account_resp.orders, - Some("https://example.com/acme/orders/rzGoeA".into()) - ); - } - - #[test] - fn test_account_update() { - let emails = vec![ - "mailto:derp@example.com".to_string(), - "mailto:derp.derpson@example.com".to_string(), - ]; - let au = AccountUpdate::new(&emails); - assert_eq!(au.contact.len(), 2); - let au_str = serde_json::to_string(&au); - assert!(au_str.is_ok()); - let au_str = au_str.unwrap(); - assert!(au_str.starts_with("{")); - assert!(au_str.ends_with("}")); - assert!(au_str.contains("\"contact\"")); - assert!(au_str.contains("\"mailto:derp@example.com\"")); - assert!(au_str.contains("\"mailto:derp.derpson@example.com\"")); - } - - #[test] - fn test_account_deactivation() { - let ad = AccountDeactivation::new(); - assert_eq!(ad.status, "deactivated"); - let ad_str = serde_json::to_string(&ad); - assert!(ad_str.is_ok()); - let ad_str = ad_str.unwrap(); - assert!(ad_str.starts_with("{")); - assert!(ad_str.ends_with("}")); - assert!(ad_str.contains("\"status\"")); - assert!(ad_str.contains("\"deactivated\"")); - } -} diff --git a/acmed/src/acme_proto/structs/authorization.rs b/acmed/src/acme_proto/structs/authorization.rs deleted file mode 100644 index 119a991..0000000 --- a/acmed/src/acme_proto/structs/authorization.rs +++ /dev/null @@ -1,349 +0,0 @@ -use crate::acme_proto::structs::{ApiError, HttpApiError, Identifier}; -use acme_common::b64_encode; -use acme_common::crypto::{HashFunction, KeyPair}; -use acme_common::error::Error; -use serde::Deserialize; -use std::fmt; -use std::str::FromStr; - -const ACME_OID: &str = "1.3.6.1.5.5.7.1"; -const ID_PE_ACME_ID: usize = 31; -const DER_OCTET_STRING_ID: usize = 0x04; -const DER_STRUCT_NAME: &str = "DER"; - -#[derive(Deserialize)] -pub struct Authorization { - pub identifier: Identifier, - pub status: AuthorizationStatus, - #[allow(dead_code)] - pub expires: Option, - pub challenges: Vec, - #[allow(dead_code)] - pub wildcard: Option, -} - -impl FromStr for Authorization { - type Err = Error; - - fn from_str(data: &str) -> Result { - let mut res: Self = serde_json::from_str(data)?; - res.challenges.retain(|c| *c != Challenge::Unknown); - Ok(res) - } -} - -impl ApiError for Authorization { - fn get_error(&self) -> Option { - for challenge in self.challenges.iter() { - let err = challenge.get_error(); - if err.is_some() { - return err; - } - } - None - } -} - -#[derive(Debug, PartialEq, Eq, Deserialize)] -#[serde(rename_all = "lowercase")] -pub enum AuthorizationStatus { - Pending, - Valid, - Invalid, - Deactivated, - Expired, - Revoked, -} - -impl fmt::Display for AuthorizationStatus { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - let s = match self { - AuthorizationStatus::Pending => "pending", - AuthorizationStatus::Valid => "valid", - AuthorizationStatus::Invalid => "invalid", - AuthorizationStatus::Deactivated => "deactivated", - AuthorizationStatus::Expired => "expired", - AuthorizationStatus::Revoked => "revoked", - }; - write!(f, "{s}") - } -} - -#[derive(PartialEq, Deserialize)] -#[serde(tag = "type")] -pub enum Challenge { - #[serde(rename = "http-01")] - Http01(TokenChallenge), - #[serde(rename = "dns-01")] - Dns01(TokenChallenge), - #[serde(rename = "tls-alpn-01")] - TlsAlpn01(TokenChallenge), - #[serde(other)] - Unknown, -} - -deserialize_from_str!(Challenge); - -impl Challenge { - pub fn get_url(&self) -> String { - match self { - Challenge::Http01(tc) | Challenge::Dns01(tc) | Challenge::TlsAlpn01(tc) => { - tc.url.to_owned() - } - Challenge::Unknown => String::new(), - } - } - - pub fn get_proof(&self, key_pair: &KeyPair) -> Result<(String, Option), Error> { - match self { - Challenge::Http01(tc) => { - let ka = tc.key_authorization(key_pair)?; - Ok((ka, None)) - } - Challenge::Dns01(tc) => { - let ka = tc.key_authorization(key_pair)?; - let a = HashFunction::Sha256.hash(ka.as_bytes()); - let a = b64_encode(&a); - Ok((a, None)) - } - Challenge::TlsAlpn01(tc) => { - let acme_ext_name = format!("{ACME_OID}.{ID_PE_ACME_ID}"); - let ka = tc.key_authorization(key_pair)?; - let proof = HashFunction::Sha256.hash(ka.as_bytes()); - let b64_hash = b64_encode(&proof); - let proof_str = proof - .iter() - .map(|e| format!("{e:02x}")) - .collect::>() - .join(":"); - let value = format!( - "critical,{DER_STRUCT_NAME}:{DER_OCTET_STRING_ID:02x}:{:02x}:{proof_str}", - proof.len(), - ); - let acme_ext = format!("{acme_ext_name}={value}"); - Ok((acme_ext, Some(b64_hash))) - } - Challenge::Unknown => Ok((String::new(), None)), - } - } - - pub fn get_file_name(&self) -> String { - match self { - Challenge::Http01(tc) => tc.token.to_owned(), - Challenge::Dns01(_) | Challenge::TlsAlpn01(_) => String::new(), - Challenge::Unknown => String::new(), - } - } -} - -impl ApiError for Challenge { - fn get_error(&self) -> Option { - match self { - Challenge::Http01(tc) | Challenge::Dns01(tc) | Challenge::TlsAlpn01(tc) => { - tc.error.to_owned().map(Error::from) - } - Challenge::Unknown => None, - } - } -} - -#[derive(PartialEq, Deserialize)] -pub struct TokenChallenge { - pub url: String, - pub status: Option, - pub validated: Option, - pub error: Option, - pub token: String, -} - -impl TokenChallenge { - fn key_authorization(&self, key_pair: &KeyPair) -> Result { - let thumbprint = key_pair.jwk_public_key_thumbprint()?; - let thumbprint = HashFunction::Sha256.hash(thumbprint.to_string().as_bytes()); - let thumbprint = b64_encode(&thumbprint); - let auth = format!("{}.{thumbprint}", self.token); - Ok(auth) - } -} - -#[derive(Debug, PartialEq, Eq, Deserialize)] -#[serde(rename_all = "lowercase")] -pub enum ChallengeStatus { - Pending, - Processing, - Valid, - Invalid, -} - -#[cfg(test)] -mod tests { - use super::{Authorization, AuthorizationStatus, Challenge, ChallengeStatus}; - use crate::identifier::IdentifierType; - use std::str::FromStr; - - #[test] - fn test_authorization() { - let data = "{ - \"status\": \"pending\", - \"identifier\": { - \"type\": \"dns\", - \"value\": \"example.com\" - }, - \"challenges\": [] -}"; - let a = Authorization::from_str(data); - assert!(a.is_ok()); - let a = a.unwrap(); - assert_eq!(a.status, AuthorizationStatus::Pending); - assert!(a.challenges.is_empty()); - let i = a.identifier; - assert_eq!(i.id_type, IdentifierType::Dns); - assert_eq!(i.value, "example.com".to_string()); - } - - #[test] - fn test_authorization_challenge() { - let data = "{ - \"status\": \"pending\", - \"identifier\": { - \"type\": \"dns\", - \"value\": \"example.com\" - }, - \"challenges\": [ - { - \"type\": \"dns-01\", - \"status\": \"pending\", - \"url\": \"https://example.com/chall/jYWxob3N0OjE\", - \"token\": \"1y9UVMUvkqQVljCsnwlRLsbJcwN9nx-qDd6JHzXQQsw\" - } - ] -}"; - let a = Authorization::from_str(data); - assert!(a.is_ok()); - let a = a.unwrap(); - assert_eq!(a.status, AuthorizationStatus::Pending); - assert_eq!(a.challenges.len(), 1); - let i = a.identifier; - assert_eq!(i.id_type, IdentifierType::Dns); - assert_eq!(i.value, "example.com".to_string()); - } - - #[test] - fn test_authorization_unknown_challenge() { - let data = "{ - \"status\": \"pending\", - \"identifier\": { - \"type\": \"dns\", - \"value\": \"example.com\" - }, - \"challenges\": [ - { - \"type\": \"invalid-challenge-01\", - \"status\": \"pending\", - \"url\": \"https://example.com/chall/jYWxob3N0OjE\", - \"token\": \"1y9UVMUvkqQVljCsnwlRLsbJcwN9nx-qDd6JHzXQQsw\" - } - ] -}"; - let a = Authorization::from_str(data); - assert!(a.is_ok()); - let a = a.unwrap(); - assert_eq!(a.status, AuthorizationStatus::Pending); - assert!(a.challenges.is_empty()); - let i = a.identifier; - assert_eq!(i.id_type, IdentifierType::Dns); - assert_eq!(i.value, "example.com".to_string()); - } - - #[test] - fn test_invalid_authorization() { - let data = "{ - \"status\": \"pending\", - \"identifier\": { - \"type\": \"foo\", - \"value\": \"bar\" - }, - \"challenges\": [] -}"; - let a = Authorization::from_str(data); - assert!(a.is_err()); - } - - #[test] - fn test_http01_challenge() { - let data = "{ - \"type\": \"http-01\", - \"url\": \"https://example.com/acme/chall/prV_B7yEyA4\", - \"status\": \"pending\", - \"token\": \"LoqXcYV8q5ONbJQxbmR7SCTNo3tiAXDfowyjxAjEuX0\" -}"; - let challenge = Challenge::from_str(data); - assert!(challenge.is_ok()); - let challenge = challenge.unwrap(); - let c = match challenge { - Challenge::Http01(c) => c, - _ => { - assert!(false); - return; - } - }; - assert_eq!( - c.url, - "https://example.com/acme/chall/prV_B7yEyA4".to_string() - ); - assert_eq!(c.status, Some(ChallengeStatus::Pending)); - assert_eq!( - c.token, - "LoqXcYV8q5ONbJQxbmR7SCTNo3tiAXDfowyjxAjEuX0".to_string() - ); - assert!(c.validated.is_none()); - assert!(c.error.is_none()); - } - - #[test] - fn test_dns01_challenge() { - let data = "{ - \"type\": \"http-01\", - \"url\": \"https://example.com/acme/chall/prV_B7yEyA4\", - \"status\": \"valid\", - \"token\": \"LoqXcYV8q5ONbJQxbmR7SCTNo3tiAXDfowyjxAjEuX0\" -}"; - let challenge = Challenge::from_str(data); - assert!(challenge.is_ok()); - let challenge = challenge.unwrap(); - let c = match challenge { - Challenge::Http01(c) => c, - _ => { - assert!(false); - return; - } - }; - assert_eq!( - c.url, - "https://example.com/acme/chall/prV_B7yEyA4".to_string() - ); - assert_eq!(c.status, Some(ChallengeStatus::Valid)); - assert_eq!( - c.token, - "LoqXcYV8q5ONbJQxbmR7SCTNo3tiAXDfowyjxAjEuX0".to_string() - ); - assert!(c.validated.is_none()); - assert!(c.error.is_none()); - } - - #[test] - fn test_unknown_challenge_type() { - let data = "{ - \"type\": \"invalid-01\", - \"url\": \"https://example.com/acme/chall/prV_B7yEyA4\", - \"status\": \"pending\", - \"token\": \"LoqXcYV8q5ONbJQxbmR7SCTNo3tiAXDfowyjxAjEuX0\" -}"; - let challenge = Challenge::from_str(data); - assert!(challenge.is_ok()); - match challenge.unwrap() { - Challenge::Unknown => assert!(true), - _ => assert!(false), - } - } -} diff --git a/acmed/src/acme_proto/structs/directory.rs b/acmed/src/acme_proto/structs/directory.rs deleted file mode 100644 index 7f89b87..0000000 --- a/acmed/src/acme_proto/structs/directory.rs +++ /dev/null @@ -1,151 +0,0 @@ -use acme_common::error::Error; -use serde::Deserialize; -use std::str::FromStr; - -#[derive(Clone, Debug, Deserialize)] -#[serde(rename_all = "camelCase")] -#[allow(dead_code)] -pub struct DirectoryMeta { - pub terms_of_service: Option, - pub website: Option, - pub caa_identities: Option>, - pub external_account_required: Option, -} - -#[derive(Clone, Debug, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct Directory { - #[allow(dead_code)] - pub meta: Option, - pub new_nonce: String, - pub new_account: String, - pub new_order: String, - #[allow(dead_code)] - pub new_authz: Option, - #[allow(dead_code)] - pub revoke_cert: String, - pub key_change: String, -} - -deserialize_from_str!(Directory); - -#[cfg(test)] -mod tests { - use super::Directory; - use std::str::FromStr; - - #[test] - fn test_directory() { - let data = "{ - \"newAccount\": \"https://example.org/acme/new-acct\", - \"newNonce\": \"https://example.org/acme/new-nonce\", - \"newOrder\": \"https://example.org/acme/new-order\", - \"revokeCert\": \"https://example.org/acme/revoke-cert\", - \"newAuthz\": \"https://example.org/acme/new-authz\", - \"keyChange\": \"https://example.org/acme/key-change\" -}"; - let parsed_dir = Directory::from_str(data); - assert!(parsed_dir.is_ok()); - let parsed_dir = parsed_dir.unwrap(); - assert_eq!(parsed_dir.new_nonce, "https://example.org/acme/new-nonce"); - assert_eq!(parsed_dir.new_account, "https://example.org/acme/new-acct"); - assert_eq!(parsed_dir.new_order, "https://example.org/acme/new-order"); - assert_eq!( - parsed_dir.new_authz, - Some("https://example.org/acme/new-authz".to_string()) - ); - assert_eq!( - parsed_dir.revoke_cert, - "https://example.org/acme/revoke-cert" - ); - assert_eq!(parsed_dir.key_change, "https://example.org/acme/key-change"); - assert!(parsed_dir.meta.is_none()); - } - - #[test] - fn test_directory_no_authz() { - let data = "{ - \"newAccount\": \"https://example.org/acme/new-acct\", - \"newNonce\": \"https://example.org/acme/new-nonce\", - \"newOrder\": \"https://example.org/acme/new-order\", - \"revokeCert\": \"https://example.org/acme/revoke-cert\", - \"keyChange\": \"https://example.org/acme/key-change\" -}"; - let parsed_dir = Directory::from_str(data); - assert!(parsed_dir.is_ok()); - let parsed_dir = parsed_dir.unwrap(); - assert_eq!(parsed_dir.new_nonce, "https://example.org/acme/new-nonce"); - assert_eq!(parsed_dir.new_account, "https://example.org/acme/new-acct"); - assert_eq!(parsed_dir.new_order, "https://example.org/acme/new-order"); - assert!(parsed_dir.new_authz.is_none()); - assert_eq!( - parsed_dir.revoke_cert, - "https://example.org/acme/revoke-cert" - ); - assert_eq!(parsed_dir.key_change, "https://example.org/acme/key-change"); - assert!(parsed_dir.meta.is_none()); - } - - #[test] - fn test_directory_meta() { - let data = "{ - \"keyChange\": \"https://example.org/acme/key-change\", - \"meta\": { - \"caaIdentities\": [ - \"example.org\" - ], - \"termsOfService\": \"https://example.org/documents/tos.pdf\", - \"website\": \"https://example.org/\" - }, - \"newAccount\": \"https://example.org/acme/new-acct\", - \"newNonce\": \"https://example.org/acme/new-nonce\", - \"newOrder\": \"https://example.org/acme/new-order\", - \"revokeCert\": \"https://example.org/acme/revoke-cert\" -}"; - let parsed_dir = Directory::from_str(&data); - assert!(parsed_dir.is_ok()); - let parsed_dir = parsed_dir.unwrap(); - assert!(parsed_dir.meta.is_some()); - let meta = parsed_dir.meta.unwrap(); - assert_eq!( - meta.terms_of_service, - Some("https://example.org/documents/tos.pdf".to_string()) - ); - assert_eq!(meta.website, Some("https://example.org/".to_string())); - assert!(meta.caa_identities.is_some()); - let caa_identities = meta.caa_identities.unwrap(); - assert_eq!(caa_identities.len(), 1); - assert_eq!(caa_identities.first(), Some(&"example.org".to_string())); - assert!(meta.external_account_required.is_none()); - } - - #[test] - fn test_directory_extra_fields() { - let data = "{ - \"foo\": \"bar\", - \"keyChange\": \"https://example.org/acme/key-change\", - \"newAccount\": \"https://example.org/acme/new-acct\", - \"baz\": \"quz\", - \"newNonce\": \"https://example.org/acme/new-nonce\", - \"newAuthz\": \"https://example.org/acme/new-authz\", - \"newOrder\": \"https://example.org/acme/new-order\", - \"revokeCert\": \"https://example.org/acme/revoke-cert\" -}"; - let parsed_dir = Directory::from_str(&data); - assert!(parsed_dir.is_ok()); - let parsed_dir = parsed_dir.unwrap(); - assert_eq!(parsed_dir.new_nonce, "https://example.org/acme/new-nonce"); - assert_eq!(parsed_dir.new_account, "https://example.org/acme/new-acct"); - assert_eq!(parsed_dir.new_order, "https://example.org/acme/new-order"); - assert_eq!( - parsed_dir.new_authz, - Some("https://example.org/acme/new-authz".to_string()) - ); - assert_eq!( - parsed_dir.revoke_cert, - "https://example.org/acme/revoke-cert" - ); - assert_eq!(parsed_dir.key_change, "https://example.org/acme/key-change"); - assert!(parsed_dir.meta.is_none()); - } -} diff --git a/acmed/src/acme_proto/structs/error.rs b/acmed/src/acme_proto/structs/error.rs deleted file mode 100644 index c5a8f6e..0000000 --- a/acmed/src/acme_proto/structs/error.rs +++ /dev/null @@ -1,173 +0,0 @@ -use acme_common::error::Error; -use serde::Deserialize; -use std::fmt; -use std::str::FromStr; - -pub trait ApiError { - fn get_error(&self) -> Option; -} - -#[derive(Clone, Debug, PartialEq)] -pub enum AcmeError { - AccountDoesNotExist, - AlreadyRevoked, - BadCSR, - BadNonce, - BadPublicKey, - BadRevocationReason, - BadSignatureAlgorithm, - Caa, - Compound, - Connection, - Dns, - ExternalAccountRequired, - IncorrectResponse, - InvalidContact, - Malformed, - OrderNotReady, - RateLimited, - RejectedIdentifier, - ServerInternal, - Tls, - Unauthorized, - UnsupportedContact, - UnsupportedIdentifier, - UserActionRequired, - Unknown, -} - -impl From for AcmeError { - fn from(error: String) -> Self { - match error.as_str() { - "urn:ietf:params:acme:error:accountDoesNotExist" => AcmeError::AccountDoesNotExist, - "urn:ietf:params:acme:error:alreadyRevoked" => AcmeError::AlreadyRevoked, - "urn:ietf:params:acme:error:badCSR" => AcmeError::BadCSR, - "urn:ietf:params:acme:error:badNonce" => AcmeError::BadNonce, - "urn:ietf:params:acme:error:badPublicKey" => AcmeError::BadPublicKey, - "urn:ietf:params:acme:error:badRevocationReason" => AcmeError::BadRevocationReason, - "urn:ietf:params:acme:error:badSignatureAlgorithm" => AcmeError::BadSignatureAlgorithm, - "urn:ietf:params:acme:error:caa" => AcmeError::Caa, - "urn:ietf:params:acme:error:compound" => AcmeError::Compound, - "urn:ietf:params:acme:error:connection" => AcmeError::Connection, - "urn:ietf:params:acme:error:dns" => AcmeError::Dns, - "urn:ietf:params:acme:error:externalAccountRequired" => { - AcmeError::ExternalAccountRequired - } - "urn:ietf:params:acme:error:incorrectResponse" => AcmeError::IncorrectResponse, - "urn:ietf:params:acme:error:invalidContact" => AcmeError::InvalidContact, - "urn:ietf:params:acme:error:malformed" => AcmeError::Malformed, - "urn:ietf:params:acme:error:orderNotReady" => AcmeError::OrderNotReady, - "urn:ietf:params:acme:error:rateLimited" => AcmeError::RateLimited, - "urn:ietf:params:acme:error:rejectedIdentifier" => AcmeError::RejectedIdentifier, - "urn:ietf:params:acme:error:serverInternal" => AcmeError::ServerInternal, - "urn:ietf:params:acme:error:tls" => AcmeError::Tls, - "urn:ietf:params:acme:error:unauthorized" => AcmeError::Unauthorized, - "urn:ietf:params:acme:error:unsupportedContact" => AcmeError::UnsupportedContact, - "urn:ietf:params:acme:error:unsupportedIdentifier" => AcmeError::UnsupportedIdentifier, - "urn:ietf:params:acme:error:userActionRequired" => AcmeError::UserActionRequired, - _ => AcmeError::Unknown, - } - } -} - -impl fmt::Display for AcmeError { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - let msg = match self { - AcmeError::AccountDoesNotExist => "the request specified an account that does not exist", - AcmeError::AlreadyRevoked => "the request specified a certificate to be revoked that has already been revoked", - AcmeError::BadCSR => "the CSR is unacceptable (e.g., due to a short key)", - AcmeError::BadNonce => "the client sent an unacceptable anti-replay nonce", - AcmeError::BadPublicKey => "the JWS was signed by a public key the server does not support", - AcmeError::BadRevocationReason => "the revocation reason provided is not allowed by the server", - AcmeError::BadSignatureAlgorithm => "the JWS was signed with an algorithm the server does not support", - AcmeError::Caa => "Certification Authority Authorization (CAA) records forbid the CA from issuing a certificate", - AcmeError::Compound => "specific error conditions are indicated in the \"subproblems\" array", - AcmeError::Connection => "the server could not connect to validation target", - AcmeError::Dns => "there was a problem with a DNS query during identifier validation", - AcmeError::ExternalAccountRequired => "the request must include a value for the \"externalAccountBinding\" field", - AcmeError::IncorrectResponse => "response received didn't match the challenge's requirements", - AcmeError::InvalidContact => "a contact URL for an account was invalid", - AcmeError::Malformed => "the request message was malformed", - AcmeError::OrderNotReady => "the request attempted to finalize an order that is not ready to be finalized", - AcmeError::RateLimited => "the request exceeds a rate limit", - AcmeError::RejectedIdentifier => "the server will not issue certificates for the identifier", - AcmeError::ServerInternal => "the server experienced an internal error", - AcmeError::Tls => "the server received a TLS error during validation", - AcmeError::Unauthorized => "the client lacks sufficient authorization", - AcmeError::UnsupportedContact => "a contact URL for an account used an unsupported protocol scheme", - AcmeError::UnsupportedIdentifier => "an identifier is of an unsupported type", - AcmeError::UserActionRequired => "visit the \"instance\" URL and take actions specified there", - AcmeError::Unknown => "unknown error", - }; - write!(f, "{msg}") - } -} - -impl AcmeError { - pub fn is_recoverable(&self) -> bool { - *self == AcmeError::BadNonce - || *self == AcmeError::Connection - || *self == AcmeError::Dns - || *self == AcmeError::Malformed - || *self == AcmeError::RateLimited - || *self == AcmeError::ServerInternal - || *self == AcmeError::Tls - } -} - -impl From for AcmeError { - fn from(_error: Error) -> Self { - AcmeError::Unknown - } -} - -impl From for Error { - fn from(error: AcmeError) -> Self { - error.to_string().into() - } -} - -#[derive(Clone, Debug, PartialEq, Deserialize)] -pub struct HttpApiError { - #[serde(rename = "type")] - error_type: Option, - // title: Option, - status: Option, - detail: Option, - // instance: Option, - // TODO: implement subproblems -} - -crate::acme_proto::structs::deserialize_from_str!(HttpApiError); - -impl fmt::Display for HttpApiError { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - let msg = self - .detail - .to_owned() - .unwrap_or_else(|| self.get_acme_type().to_string()); - let msg = match self.status { - Some(s) => format!("status {s}: {msg}"), - None => msg, - }; - write!(f, "{msg}") - } -} - -impl HttpApiError { - pub fn get_type(&self) -> String { - self.error_type - .to_owned() - .unwrap_or_else(|| String::from("about:blank")) - } - - pub fn get_acme_type(&self) -> AcmeError { - self.get_type().into() - } -} - -impl From for Error { - fn from(error: HttpApiError) -> Self { - error.to_string().into() - } -} diff --git a/acmed/src/acme_proto/structs/order.rs b/acmed/src/acme_proto/structs/order.rs deleted file mode 100644 index c01cd63..0000000 --- a/acmed/src/acme_proto/structs/order.rs +++ /dev/null @@ -1,135 +0,0 @@ -use crate::acme_proto::structs::{ApiError, HttpApiError}; -use crate::identifier::{self, IdentifierType}; -use acme_common::error::Error; -use serde::{Deserialize, Serialize}; -use std::fmt; -use std::str::FromStr; - -#[derive(Serialize)] -#[serde(rename_all = "camelCase")] -pub struct NewOrder { - pub identifiers: Vec, - #[serde(skip_serializing_if = "Option::is_none")] - pub not_before: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub not_after: Option, -} - -impl NewOrder { - pub fn new(identifiers: &[identifier::Identifier]) -> Self { - NewOrder { - identifiers: identifiers.iter().map(Identifier::from_generic).collect(), - not_before: None, - not_after: None, - } - } -} - -#[derive(Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct Order { - pub status: OrderStatus, - #[allow(dead_code)] - pub expires: Option, - #[allow(dead_code)] - pub identifiers: Vec, - #[allow(dead_code)] - pub not_before: Option, - #[allow(dead_code)] - pub not_after: Option, - pub error: Option, - pub authorizations: Vec, - pub finalize: String, - pub certificate: Option, -} - -impl ApiError for Order { - fn get_error(&self) -> Option { - self.error.to_owned().map(Error::from) - } -} - -deserialize_from_str!(Order); - -#[derive(Debug, PartialEq, Eq, Deserialize)] -#[serde(rename_all = "lowercase")] -pub enum OrderStatus { - Pending, - Ready, - Processing, - Valid, - Invalid, -} - -impl fmt::Display for OrderStatus { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - let s = match self { - OrderStatus::Pending => "pending", - OrderStatus::Ready => "ready", - OrderStatus::Processing => "processing", - OrderStatus::Valid => "valid", - OrderStatus::Invalid => "invalid", - }; - write!(f, "{s}") - } -} - -#[derive(Deserialize, Serialize)] -pub struct Identifier { - #[serde(rename = "type")] - pub id_type: IdentifierType, - pub value: String, -} - -impl Identifier { - pub fn from_generic(id: &identifier::Identifier) -> Self { - Identifier { - id_type: id.id_type.to_owned(), - value: id.value.to_owned(), - } - } -} - -deserialize_from_str!(Identifier); - -impl fmt::Display for Identifier { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{}:{}", self.id_type, self.value) - } -} - -#[cfg(test)] -mod tests { - use super::{Identifier, IdentifierType}; - use std::str::FromStr; - - #[test] - fn id_serialize() { - let reference = "{\"type\":\"dns\",\"value\":\"test.example.org\"}"; - let id = Identifier { - id_type: IdentifierType::Dns, - value: "test.example.org".to_string(), - }; - let id_json = serde_json::to_string(&id); - assert!(id_json.is_ok()); - let id_json = id_json.unwrap(); - assert_eq!(id_json, reference.to_string()); - } - - #[test] - fn id_deserialize_valid() { - let id_str = "{\"type\":\"dns\",\"value\":\"test.example.org\"}"; - let id = Identifier::from_str(id_str); - assert!(id.is_ok()); - let id = id.unwrap(); - assert_eq!(id.id_type, IdentifierType::Dns); - assert_eq!(id.value, "test.example.org".to_string()); - } - - #[test] - fn id_deserialize_invalid_type() { - let id_str = "{\"type\":\"trololo\",\"value\":\"test.example.org\"}"; - let id = Identifier::from_str(id_str); - assert!(id.is_err()); - } -} diff --git a/acmed/src/certificate.rs b/acmed/src/certificate.rs deleted file mode 100644 index c7918cf..0000000 --- a/acmed/src/certificate.rs +++ /dev/null @@ -1,203 +0,0 @@ -use crate::acme_proto::Challenge; -use crate::hooks::{self, ChallengeHookData, Hook, HookEnvData, HookType, PostOperationHookData}; -use crate::identifier::{Identifier, IdentifierType}; -use crate::logs::HasLogger; -use crate::storage::{certificate_files_exists, get_certificate, FileManager}; -use acme_common::crypto::{HashFunction, KeyType, SubjectAttribute, X509Certificate}; -use acme_common::error::Error; -use log::{debug, info, trace, warn}; -use rand::{thread_rng, Rng}; -use std::collections::{HashMap, HashSet}; -use std::fmt; -use std::time::Duration; - -#[derive(Clone, Debug)] -pub struct Certificate { - pub account_name: String, - pub identifiers: Vec, - pub subject_attributes: HashMap, - pub key_type: KeyType, - pub csr_digest: HashFunction, - pub kp_reuse: bool, - pub endpoint_name: String, - pub hooks: Vec, - pub crt_name: String, - pub env: HashMap, - pub random_early_renew: Duration, - pub renew_delay: Duration, - pub file_manager: FileManager, -} - -impl fmt::Display for Certificate { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{}", self.get_id()) - } -} - -impl HasLogger for Certificate { - fn warn(&self, msg: &str) { - warn!("certificate \"{self}\": {msg}"); - } - - fn info(&self, msg: &str) { - info!("certificate \"{self}\": {msg}"); - } - - fn debug(&self, msg: &str) { - debug!("certificate \"{self}\": {msg}"); - } - - fn trace(&self, msg: &str) { - trace!("certificate \"{self}\": {msg}"); - } -} - -impl Certificate { - pub fn get_id(&self) -> String { - format!("{}_{}", self.crt_name, self.key_type) - } - - pub fn get_identifier_from_str(&self, identifier: &str) -> Result { - let identifier = identifier.to_string(); - for d in self.identifiers.iter() { - let val = match d.id_type { - // strip wildcards from domain before matching - IdentifierType::Dns => d.value.trim_start_matches("*.").to_string(), - IdentifierType::Ip => d.value.to_owned(), - }; - if identifier == val { - return Ok(d.clone()); - } - } - Err(format!("{identifier}: identifier not found").into()) - } - - fn renew_in(&self, cert: &X509Certificate) -> Result { - let expires_in = cert.expires_in()?; - self.debug(&format!( - "certificate expires in {} days ({} days delay)", - expires_in.as_secs() / 86400, - self.renew_delay.as_secs() / 86400, - )); - let expires_in = expires_in.saturating_sub(self.renew_delay); - let expires_in = if !self.random_early_renew.is_zero() { - expires_in - .saturating_sub(thread_rng().gen_range(Duration::ZERO..self.random_early_renew)) - } else { - expires_in - }; - Ok(expires_in) - } - - fn has_missing_identifiers(&self, cert: &X509Certificate) -> bool { - let cert_names = cert.subject_alt_names(); - let req_names = self - .identifiers - .iter() - .map(|v| v.value.to_owned()) - .collect::>(); - let has_miss = req_names.difference(&cert_names).count() != 0; - if has_miss { - let domains = req_names - .difference(&cert_names) - .map(std::borrow::ToOwned::to_owned) - .collect::>() - .join(", "); - self.debug(&format!( - "the certificate does not include the following domains: {domains}" - )); - } - has_miss - } - - /// Return a comma-separated list of the domains this certificate is valid for. - pub fn identifier_list(&self) -> String { - self.identifiers - .iter() - .map(|d| d.value.as_str()) - .collect::>() - .join(",") - } - - pub async fn schedule_renewal(&self) -> Result { - self.debug(&format!( - "checking for renewal (identifiers: {})", - self.identifier_list() - )); - if !certificate_files_exists(&self.file_manager) { - self.debug("certificate does not exist: requesting one"); - return Ok(Duration::ZERO); - } - let cert = get_certificate(&self.file_manager).await?; - - if self.has_missing_identifiers(&cert) { - self.debug("the current certificate doesn't include all the required identifiers"); - return Ok(Duration::ZERO); - } - self.renew_in(&cert) - } - - pub async fn call_challenge_hooks( - &self, - file_name: &str, - proof: &str, - raw_proof: Option, - identifier: &str, - ) -> Result<(ChallengeHookData, HookType), Error> { - let identifier = self.get_identifier_from_str(identifier)?; - let mut hook_data = ChallengeHookData { - challenge: identifier.challenge.to_string(), - identifier: identifier.value.to_owned(), - identifier_tls_alpn: identifier.get_tls_alpn_name().unwrap_or_default(), - file_name: file_name.to_string(), - proof: proof.to_string(), - raw_proof: raw_proof.unwrap_or_default().to_string(), - is_clean_hook: false, - env: HashMap::new(), - }; - hook_data.set_env(&self.env); - hook_data.set_env(&identifier.env); - let hook_type = match identifier.challenge { - Challenge::Http01 => (HookType::ChallengeHttp01, HookType::ChallengeHttp01Clean), - Challenge::Dns01 => (HookType::ChallengeDns01, HookType::ChallengeDns01Clean), - Challenge::TlsAlpn01 => ( - HookType::ChallengeTlsAlpn01, - HookType::ChallengeTlsAlpn01Clean, - ), - }; - hooks::call(self, &self.hooks, &hook_data, hook_type.0).await?; - Ok((hook_data, hook_type.1)) - } - - pub async fn call_challenge_hooks_clean( - &self, - data: &ChallengeHookData, - hook_type: HookType, - ) -> Result<(), Error> { - hooks::call(self, &self.hooks, data, hook_type).await - } - - pub async fn call_post_operation_hooks( - &self, - status: &str, - is_success: bool, - ) -> Result<(), Error> { - let identifiers = self - .identifiers - .iter() - .map(|d| d.value.to_owned()) - .collect::>(); - let mut hook_data = PostOperationHookData { - identifiers, - key_type: self.key_type.to_string(), - status: status.to_string(), - is_success, - certificate_path: crate::storage::get_certificate_path(&self.file_manager).await?, - private_key_path: crate::storage::get_keypair_path(&self.file_manager).await?, - env: HashMap::new(), - }; - hook_data.set_env(&self.env); - hooks::call(self, &self.hooks, &hook_data, HookType::PostOperation).await?; - Ok(()) - } -} diff --git a/acmed/src/config.rs b/acmed/src/config.rs deleted file mode 100644 index f21d5ec..0000000 --- a/acmed/src/config.rs +++ /dev/null @@ -1,797 +0,0 @@ -use crate::duration::parse_duration; -use crate::hooks; -use crate::identifier::IdentifierType; -use crate::storage::FileManager; -use acme_common::b64_decode; -use acme_common::crypto::{HashFunction, JwsSignatureAlgorithm, KeyType, SubjectAttribute}; -use acme_common::error::Error; -use glob::glob; -use log::info; -use serde::{de, Deserialize, Deserializer}; -use std::collections::{BTreeSet, HashMap}; -use std::fmt; -use std::fs::{self, File}; -use std::io::prelude::*; -use std::path::{Path, PathBuf}; -use std::result::Result; -use std::time::Duration; - -macro_rules! set_cfg_attr { - ($to: expr, $from: expr) => { - if let Some(v) = $from { - $to = Some(v); - }; - }; -} - -macro_rules! push_subject_attr { - ($hm: expr, $attr: expr, $attr_type: ident) => { - if let Some(v) = &$attr { - $hm.insert(SubjectAttribute::$attr_type, v.to_owned()); - } - }; -} - -fn get_stdin(hook: &Hook) -> Result { - match &hook.stdin { - Some(file) => match &hook.stdin_str { - Some(_) => { - let msg = format!( - "{}: a hook cannot have both stdin and stdin_str", - &hook.name - ); - Err(msg.into()) - } - None => Ok(hooks::HookStdin::File(file.to_string())), - }, - None => match &hook.stdin_str { - Some(s) => Ok(hooks::HookStdin::Str(s.to_string())), - None => Ok(hooks::HookStdin::None), - }, - } -} - -#[derive(Default, Deserialize)] -#[serde(deny_unknown_fields)] -pub struct Config { - pub global: Option, - #[serde(default)] - pub endpoint: Vec, - #[serde(default, rename = "rate-limit")] - pub rate_limit: Vec, - #[serde(default)] - pub hook: Vec, - #[serde(default)] - pub group: Vec, - #[serde(default)] - pub account: Vec, - #[serde(default)] - pub certificate: Vec, - #[serde(default)] - pub include: Vec, -} - -impl Config { - fn get_rate_limit(&self, name: &str) -> Result<(usize, String), Error> { - for rl in self.rate_limit.iter() { - if rl.name == name { - return Ok((rl.number, rl.period.to_owned())); - } - } - Err(format!("{name}: rate limit not found").into()) - } - - pub fn get_account_dir(&self) -> String { - let account_dir = match &self.global { - Some(g) => match &g.accounts_directory { - Some(d) => d, - None => crate::DEFAULT_ACCOUNTS_DIR, - }, - None => crate::DEFAULT_ACCOUNTS_DIR, - }; - account_dir.to_string() - } - - pub fn get_hook(&self, name: &str) -> Result, Error> { - for hook in self.hook.iter() { - if name == hook.name { - let h = hooks::Hook { - name: hook.name.to_owned(), - hook_type: hook.hook_type.iter().map(|e| e.to_owned()).collect(), - cmd: hook.cmd.to_owned(), - args: hook.args.to_owned(), - stdin: get_stdin(hook)?, - stdout: hook.stdout.to_owned(), - stderr: hook.stderr.to_owned(), - allow_failure: hook - .allow_failure - .unwrap_or(crate::DEFAULT_HOOK_ALLOW_FAILURE), - }; - return Ok(vec![h]); - } - } - for grp in self.group.iter() { - if name == grp.name { - let mut ret = vec![]; - for hook_name in grp.hooks.iter() { - let mut h = self.get_hook(hook_name)?; - ret.append(&mut h); - } - return Ok(ret); - } - } - Err(format!("{name}: hook not found").into()) - } - - pub fn get_cert_file_mode(&self) -> u32 { - match &self.global { - Some(g) => match g.cert_file_mode { - Some(m) => m, - None => crate::DEFAULT_CERT_FILE_MODE, - }, - None => crate::DEFAULT_CERT_FILE_MODE, - } - } - - pub fn get_cert_file_user(&self) -> Option { - match &self.global { - Some(g) => g.cert_file_user.to_owned(), - None => None, - } - } - - pub fn get_cert_file_group(&self) -> Option { - match &self.global { - Some(g) => g.cert_file_group.to_owned(), - None => None, - } - } - - pub fn get_cert_file_ext(&self) -> Option { - match &self.global { - Some(g) => g.cert_file_ext.to_owned(), - None => None, - } - } - - pub fn get_pk_file_mode(&self) -> u32 { - match &self.global { - Some(g) => match g.pk_file_mode { - Some(m) => m, - None => crate::DEFAULT_PK_FILE_MODE, - }, - None => crate::DEFAULT_PK_FILE_MODE, - } - } - - pub fn get_pk_file_user(&self) -> Option { - match &self.global { - Some(g) => g.pk_file_user.to_owned(), - None => None, - } - } - - pub fn get_pk_file_group(&self) -> Option { - match &self.global { - Some(g) => g.pk_file_group.to_owned(), - None => None, - } - } - - pub fn get_pk_file_ext(&self) -> Option { - match &self.global { - Some(g) => g.pk_file_ext.to_owned(), - None => None, - } - } -} - -#[derive(Clone, Deserialize)] -#[serde(deny_unknown_fields)] -pub struct GlobalOptions { - pub accounts_directory: Option, - pub cert_file_group: Option, - pub cert_file_mode: Option, - pub cert_file_user: Option, - pub cert_file_ext: Option, - pub certificates_directory: Option, - #[serde(default)] - pub env: HashMap, - pub file_name_format: Option, - pub pk_file_group: Option, - pub pk_file_mode: Option, - pub pk_file_user: Option, - pub pk_file_ext: Option, - pub random_early_renew: Option, - pub renew_delay: Option, - pub root_certificates: Option>, -} - -impl GlobalOptions { - pub fn get_random_early_renew(&self) -> Result { - match &self.random_early_renew { - Some(d) => parse_duration(d), - None => Ok(Duration::new(crate::DEFAULT_CERT_RANDOM_EARLY_RENEW, 0)), - } - } - - pub fn get_renew_delay(&self) -> Result { - match &self.renew_delay { - Some(d) => parse_duration(d), - None => Ok(Duration::new(crate::DEFAULT_CERT_RENEW_DELAY, 0)), - } - } - - pub fn get_crt_name_format(&self) -> String { - match &self.file_name_format { - Some(n) => n.to_string(), - None => crate::DEFAULT_CERT_FORMAT.to_string(), - } - } -} - -#[derive(Clone, Deserialize)] -#[serde(deny_unknown_fields)] -pub struct Endpoint { - pub file_name_format: Option, - pub name: String, - pub random_early_renew: Option, - #[serde(default)] - pub rate_limits: Vec, - pub renew_delay: Option, - pub root_certificates: Option>, - pub tos_agreed: bool, - pub url: String, -} - -impl Endpoint { - pub fn get_random_early_renew(&self, cnf: &Config) -> Result { - match &self.random_early_renew { - Some(d) => parse_duration(d), - None => match &cnf.global { - Some(g) => g.get_random_early_renew(), - None => Ok(Duration::new(crate::DEFAULT_CERT_RANDOM_EARLY_RENEW, 0)), - }, - } - } - - pub fn get_renew_delay(&self, cnf: &Config) -> Result { - match &self.renew_delay { - Some(d) => parse_duration(d), - None => match &cnf.global { - Some(g) => g.get_renew_delay(), - None => Ok(Duration::new(crate::DEFAULT_CERT_RENEW_DELAY, 0)), - }, - } - } - - pub fn get_crt_name_format(&self, cnf: &Config) -> String { - match &self.file_name_format { - Some(n) => n.to_string(), - None => match &cnf.global { - Some(g) => g.get_crt_name_format(), - None => crate::DEFAULT_CERT_FORMAT.to_string(), - }, - } - } - - fn to_generic( - &self, - cnf: &Config, - root_certs: &[&str], - ) -> Result { - let mut limits = vec![]; - for rl_name in self.rate_limits.iter() { - let (nb, timeframe) = cnf.get_rate_limit(rl_name)?; - limits.push((nb, timeframe)); - } - let mut root_lst: Vec = vec![]; - root_lst.extend(root_certs.iter().map(|v| v.to_string())); - if let Some(crt_lst) = &self.root_certificates { - root_lst.extend(crt_lst.iter().map(|v| v.to_owned())); - } - if let Some(glob) = &cnf.global { - if let Some(crt_lst) = &glob.root_certificates { - root_lst.extend(crt_lst.iter().map(|v| v.to_owned())); - } - } - crate::endpoint::Endpoint::new( - &self.name, - &self.url, - self.tos_agreed, - &limits, - root_lst.as_slice(), - ) - } -} - -#[derive(Clone, Deserialize)] -#[serde(deny_unknown_fields)] -pub struct RateLimit { - pub name: String, - pub number: usize, - pub period: String, -} - -#[derive(Deserialize)] -#[serde(deny_unknown_fields)] -pub struct Hook { - pub allow_failure: Option, - pub args: Option>, - pub cmd: String, - pub name: String, - pub stderr: Option, - pub stdin: Option, - pub stdin_str: Option, - pub stdout: Option, - #[serde(rename = "type")] - pub hook_type: Vec, -} - -#[derive(Clone, Debug, Eq, Hash, PartialEq, Deserialize)] -#[serde(rename_all = "kebab-case")] -pub enum HookType { - FilePreCreate, - FilePostCreate, - FilePreEdit, - FilePostEdit, - #[serde(rename = "challenge-http-01")] - ChallengeHttp01, - #[serde(rename = "challenge-http-01-clean")] - ChallengeHttp01Clean, - #[serde(rename = "challenge-dns-01")] - ChallengeDns01, - #[serde(rename = "challenge-dns-01-clean")] - ChallengeDns01Clean, - #[serde(rename = "challenge-tls-alpn-01")] - ChallengeTlsAlpn01, - #[serde(rename = "challenge-tls-alpn-01-clean")] - ChallengeTlsAlpn01Clean, - PostOperation, -} - -#[derive(Deserialize)] -#[serde(deny_unknown_fields)] -pub struct Group { - pub hooks: Vec, - pub name: String, -} - -#[derive(Clone, Debug, Deserialize)] -#[serde(deny_unknown_fields)] -pub struct ExternalAccount { - pub identifier: String, - pub key: String, - pub signature_algorithm: Option, -} - -impl ExternalAccount { - pub fn to_generic(&self) -> Result { - let signature_algorithm = match &self.signature_algorithm { - Some(a) => a.parse()?, - None => crate::DEFAULT_EXTERNAL_ACCOUNT_JWA, - }; - match signature_algorithm { - JwsSignatureAlgorithm::Hs256 - | JwsSignatureAlgorithm::Hs384 - | JwsSignatureAlgorithm::Hs512 => {} - _ => { - return Err(format!("{signature_algorithm}: invalid signature algorithm for external account binding").into()); - } - }; - Ok(crate::account::ExternalAccount { - identifier: self.identifier.to_owned(), - key: b64_decode(&self.key)?, - signature_algorithm, - }) - } -} - -#[derive(Clone, Debug, Deserialize)] -#[serde(deny_unknown_fields)] -pub struct Account { - pub contacts: Vec, - #[serde(default)] - pub env: HashMap, - pub external_account: Option, - pub hooks: Option>, - pub key_type: Option, - pub name: String, - pub signature_algorithm: Option, -} - -impl Account { - pub fn get_hooks(&self, cnf: &Config) -> Result, Error> { - let lst = match &self.hooks { - Some(h) => { - let mut res = vec![]; - for name in h.iter() { - let mut h = cnf.get_hook(name)?; - res.append(&mut h); - } - res - } - None => vec![], - }; - Ok(lst) - } - - pub async fn to_generic( - &self, - file_manager: &FileManager, - ) -> Result { - let contacts: Vec<(String, String)> = self - .contacts - .iter() - .map(|e| (e.get_type(), e.get_value())) - .collect(); - let external_account = match &self.external_account { - Some(a) => Some(a.to_generic()?), - None => None, - }; - crate::account::Account::load( - file_manager, - &self.name, - &contacts, - &self.key_type, - &self.signature_algorithm, - &external_account, - ) - .await - } -} - -#[derive(Clone, Debug, Deserialize)] -#[serde(deny_unknown_fields)] -pub struct AccountContact { - pub mailto: String, -} - -impl AccountContact { - pub fn get_type(&self) -> String { - "mailto".to_string() - } - - pub fn get_value(&self) -> String { - self.mailto.clone() - } -} - -#[derive(Deserialize)] -#[serde(deny_unknown_fields)] -pub struct Certificate { - pub account: String, - pub csr_digest: Option, - pub directory: Option, - pub endpoint: String, - #[serde(default)] - pub env: HashMap, - pub file_name_format: Option, - pub hooks: Vec, - pub identifiers: Vec, - pub key_type: Option, - pub kp_reuse: Option, - pub name: Option, - pub random_early_renew: Option, - pub renew_delay: Option, - #[serde(default)] - pub subject_attributes: SubjectAttributes, -} - -impl Certificate { - pub fn get_key_type(&self) -> Result { - match &self.key_type { - Some(a) => a.parse(), - None => Ok(crate::DEFAULT_CERT_KEY_TYPE), - } - } - - pub fn get_csr_digest(&self) -> Result { - match &self.csr_digest { - Some(d) => d.parse(), - None => Ok(crate::DEFAULT_CSR_DIGEST), - } - } - - pub fn get_identifiers(&self) -> Result, Error> { - let mut ret = vec![]; - for id in self.identifiers.iter() { - ret.push(id.to_generic()?); - } - Ok(ret) - } - - pub fn get_kp_reuse(&self) -> bool { - match self.kp_reuse { - Some(b) => b, - None => crate::DEFAULT_KP_REUSE, - } - } - - pub fn get_crt_name(&self) -> Result { - let name = match &self.name { - Some(n) => n.to_string(), - None => { - let id = self - .identifiers - .first() - .ok_or_else(|| Error::from("certificate has no identifiers"))?; - id.to_string() - } - }; - let name = name.replace(['*', ':', '/'], "_"); - Ok(name) - } - - pub fn get_crt_name_format(&self, cnf: &Config) -> Result { - match &self.file_name_format { - Some(n) => Ok(n.to_string()), - None => { - let ep = self.do_get_endpoint(cnf)?; - Ok(ep.get_crt_name_format(cnf)) - } - } - } - - pub fn get_crt_dir(&self, cnf: &Config) -> String { - let crt_directory = match &self.directory { - Some(d) => d, - None => match &cnf.global { - Some(g) => match &g.certificates_directory { - Some(d) => d, - None => crate::DEFAULT_CERT_DIR, - }, - None => crate::DEFAULT_CERT_DIR, - }, - }; - crt_directory.to_string() - } - - fn do_get_endpoint(&self, cnf: &Config) -> Result { - for endpoint in cnf.endpoint.iter() { - if endpoint.name == self.endpoint { - return Ok(endpoint.clone()); - } - } - Err(format!("{}: unknown endpoint", self.endpoint).into()) - } - - pub fn get_endpoint( - &self, - cnf: &Config, - root_certs: &[&str], - ) -> Result { - let endpoint = self.do_get_endpoint(cnf)?; - endpoint.to_generic(cnf, root_certs) - } - - pub fn get_hooks(&self, cnf: &Config) -> Result, Error> { - let mut res = vec![]; - for name in self.hooks.iter() { - let mut h = cnf.get_hook(name)?; - res.append(&mut h); - } - Ok(res) - } - - pub fn get_random_early_renew(&self, cnf: &Config) -> Result { - match &self.random_early_renew { - Some(d) => parse_duration(d), - None => { - let endpoint = self.do_get_endpoint(cnf)?; - endpoint.get_random_early_renew(cnf) - } - } - } - - pub fn get_renew_delay(&self, cnf: &Config) -> Result { - match &self.renew_delay { - Some(d) => parse_duration(d), - None => { - let endpoint = self.do_get_endpoint(cnf)?; - endpoint.get_renew_delay(cnf) - } - } - } -} - -#[derive(Clone, Debug, Deserialize)] -#[serde(remote = "Self")] -#[serde(deny_unknown_fields)] -pub struct Identifier { - pub challenge: String, - pub dns: Option, - #[serde(default)] - pub env: HashMap, - pub ip: Option, -} - -impl<'de> Deserialize<'de> for Identifier { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - let unchecked = Identifier::deserialize(deserializer)?; - let filled_nb: u8 = [unchecked.dns.is_some(), unchecked.ip.is_some()] - .iter() - .copied() - .map(u8::from) - .sum(); - if filled_nb != 1 { - return Err(de::Error::custom( - "one and only one of `dns` or `ip` must be specified", - )); - } - Ok(unchecked) - } -} - -impl fmt::Display for Identifier { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - let s = String::new(); - let msg = self.dns.as_ref().or(self.ip.as_ref()).unwrap_or(&s); - write!(f, "{msg}") - } -} - -impl Identifier { - fn to_generic(&self) -> Result { - let (t, v) = match &self.dns { - Some(d) => (IdentifierType::Dns, d), - None => match &self.ip { - Some(ip) => (IdentifierType::Ip, ip), - None => { - return Err("no identifier found".into()); - } - }, - }; - crate::identifier::Identifier::new(t, v, &self.challenge, &self.env) - } -} - -#[derive(Clone, Debug, Default, Deserialize)] -#[serde(deny_unknown_fields)] -pub struct SubjectAttributes { - pub country_name: Option, - pub generation_qualifier: Option, - pub given_name: Option, - pub initials: Option, - pub locality_name: Option, - pub name: Option, - pub organization_name: Option, - pub organizational_unit_name: Option, - pub pkcs9_email_address: Option, - pub postal_address: Option, - pub postal_code: Option, - pub state_or_province_name: Option, - pub street: Option, - pub surname: Option, - pub title: Option, -} - -impl SubjectAttributes { - pub fn to_generic(&self) -> HashMap { - let mut ret = HashMap::new(); - push_subject_attr!(ret, self.country_name, CountryName); - push_subject_attr!(ret, self.generation_qualifier, GenerationQualifier); - push_subject_attr!(ret, self.given_name, GivenName); - push_subject_attr!(ret, self.initials, Initials); - push_subject_attr!(ret, self.locality_name, LocalityName); - push_subject_attr!(ret, self.name, Name); - push_subject_attr!(ret, self.organization_name, OrganizationName); - push_subject_attr!(ret, self.organizational_unit_name, OrganizationalUnitName); - push_subject_attr!(ret, self.pkcs9_email_address, Pkcs9EmailAddress); - push_subject_attr!(ret, self.postal_address, PostalAddress); - push_subject_attr!(ret, self.postal_code, PostalCode); - push_subject_attr!(ret, self.state_or_province_name, StateOrProvinceName); - push_subject_attr!(ret, self.street, Street); - push_subject_attr!(ret, self.surname, Surname); - push_subject_attr!(ret, self.title, Title); - ret - } -} - -fn create_dir(path: &str) -> Result<(), Error> { - if Path::new(path).is_dir() { - Ok(()) - } else { - fs::create_dir_all(path)?; - Ok(()) - } -} - -fn init_directories(config: &Config) -> Result<(), Error> { - create_dir(&config.get_account_dir())?; - for crt in config.certificate.iter() { - create_dir(&crt.get_crt_dir(config))?; - } - Ok(()) -} - -fn get_cnf_path(from: &Path, file: &str) -> Result, Error> { - let mut path = from.to_path_buf().canonicalize()?; - path.pop(); - path.push(file); - let err = format!("{path:?}: invalid UTF-8 path"); - let raw_path = path.to_str().ok_or(err)?; - let g = glob(raw_path)? - .filter_map(Result::ok) - .collect::>(); - if g.is_empty() { - log::warn!( - "pattern `{file}` (expanded as `{raw_path}`): no matching configuration file found" - ); - } - Ok(g) -} - -fn read_cnf(path: &Path, loaded_files: &mut BTreeSet) -> Result { - let path = path - .canonicalize() - .map_err(|e| Error::from(e).prefix(&path.display().to_string()))?; - if loaded_files.contains(&path) { - info!("{}: configuration file already loaded", path.display()); - return Ok(Config::default()); - } - loaded_files.insert(path.clone()); - info!("{}: loading configuration file", &path.display()); - let mut file = - File::open(&path).map_err(|e| Error::from(e).prefix(&path.display().to_string()))?; - let mut contents = String::new(); - file.read_to_string(&mut contents) - .map_err(|e| Error::from(e).prefix(&path.display().to_string()))?; - let mut config: Config = toml::from_str(&contents) - .map_err(|e| Error::from(e).prefix(&path.display().to_string()))?; - for cnf_name in config.include.iter() { - for cnf_path in get_cnf_path(&path, cnf_name)? { - let mut add_cnf = read_cnf(&cnf_path, loaded_files)?; - config.endpoint.append(&mut add_cnf.endpoint); - config.rate_limit.append(&mut add_cnf.rate_limit); - config.hook.append(&mut add_cnf.hook); - config.group.append(&mut add_cnf.group); - config.account.append(&mut add_cnf.account); - config.certificate.append(&mut add_cnf.certificate); - if config.global.is_none() { - config.global = add_cnf.global; - } else if let Some(new_glob) = add_cnf.global { - let mut tmp_glob = config.global.clone().unwrap(); - set_cfg_attr!(tmp_glob.accounts_directory, new_glob.accounts_directory); - set_cfg_attr!( - tmp_glob.certificates_directory, - new_glob.certificates_directory - ); - set_cfg_attr!(tmp_glob.cert_file_mode, new_glob.cert_file_mode); - set_cfg_attr!(tmp_glob.cert_file_user, new_glob.cert_file_user); - set_cfg_attr!(tmp_glob.cert_file_group, new_glob.cert_file_group); - set_cfg_attr!(tmp_glob.pk_file_mode, new_glob.pk_file_mode); - set_cfg_attr!(tmp_glob.pk_file_user, new_glob.pk_file_user); - set_cfg_attr!(tmp_glob.pk_file_group, new_glob.pk_file_group); - config.global = Some(tmp_glob); - } - } - } - Ok(config) -} - -fn dispatch_global_env_vars(config: &mut Config) { - if let Some(glob) = &config.global { - if !glob.env.is_empty() { - for cert in config.certificate.iter_mut() { - let mut new_vars = glob.env.clone(); - for (k, v) in cert.env.iter() { - new_vars.insert(k.to_string(), v.to_string()); - } - cert.env = new_vars; - } - } - } -} - -pub fn from_file(file_name: &str) -> Result { - let path = PathBuf::from(file_name); - let mut loaded_files = BTreeSet::new(); - let mut config = read_cnf(&path, &mut loaded_files)?; - dispatch_global_env_vars(&mut config); - init_directories(&config)?; - Ok(config) -} diff --git a/acmed/src/duration.rs b/acmed/src/duration.rs deleted file mode 100644 index bcaa986..0000000 --- a/acmed/src/duration.rs +++ /dev/null @@ -1,51 +0,0 @@ -use acme_common::error::Error; -use nom::bytes::complete::take_while_m_n; -use nom::character::complete::digit1; -use nom::combinator::map_res; -use nom::multi::fold_many1; -use nom::IResult; -use std::time::Duration; - -fn is_duration_chr(c: char) -> bool { - c == 's' || c == 'm' || c == 'h' || c == 'd' || c == 'w' -} - -fn get_multiplicator(input: &str) -> IResult<&str, u64> { - let (input, nb) = take_while_m_n(1, 1, is_duration_chr)(input)?; - let mult = match nb.chars().next() { - Some('s') => 1, - Some('m') => 60, - Some('h') => 3_600, - Some('d') => 86_400, - Some('w') => 604_800, - _ => 0, - }; - Ok((input, mult)) -} - -fn get_duration_part(input: &str) -> IResult<&str, Duration> { - let (input, nb) = map_res(digit1, |s: &str| s.parse::())(input)?; - let (input, mult) = get_multiplicator(input)?; - Ok((input, Duration::from_secs(nb * mult))) -} - -fn get_duration(input: &str) -> IResult<&str, Duration> { - fold_many1( - get_duration_part, - || Duration::new(0, 0), - |mut acc: Duration, item| { - acc += item; - acc - }, - )(input) -} - -pub fn parse_duration(input: &str) -> Result { - match get_duration(input) { - Ok((r, d)) => match r.len() { - 0 => Ok(d), - _ => Err(format!("{input}: invalid duration").into()), - }, - Err(_) => Err(format!("{input}: invalid duration").into()), - } -} diff --git a/acmed/src/endpoint.rs b/acmed/src/endpoint.rs deleted file mode 100644 index 5279c7b..0000000 --- a/acmed/src/endpoint.rs +++ /dev/null @@ -1,130 +0,0 @@ -use crate::acme_proto::structs::Directory; -use crate::duration::parse_duration; -use acme_common::error::Error; -use std::cmp; -use std::time::{Duration, Instant}; -use tokio::time::sleep; - -#[derive(Clone, Debug)] -pub struct Endpoint { - pub name: String, - pub url: String, - pub tos_agreed: bool, - pub nonce: Option, - pub rl: RateLimit, - pub dir: Directory, - pub root_certificates: Vec, -} - -impl Endpoint { - pub fn new( - name: &str, - url: &str, - tos_agreed: bool, - limits: &[(usize, String)], - root_certs: &[String], - ) -> Result { - Ok(Self { - name: name.to_string(), - url: url.to_string(), - tos_agreed, - nonce: None, - rl: RateLimit::new(limits)?, - dir: Directory { - meta: None, - new_nonce: String::new(), - new_account: String::new(), - new_order: String::new(), - new_authz: None, - revoke_cert: String::new(), - key_change: String::new(), - }, - root_certificates: root_certs.to_vec(), - }) - } -} - -#[derive(Clone, Debug)] -pub struct RateLimit { - limits: Vec<(usize, Duration)>, - query_log: Vec, -} - -impl RateLimit { - pub fn new(raw_limits: &[(usize, String)]) -> Result { - let mut limits = vec![]; - for (nb, raw_duration) in raw_limits.iter() { - let parsed_duration = parse_duration(raw_duration)?; - limits.push((*nb, parsed_duration)); - } - limits.sort_by(|a, b| a.1.partial_cmp(&b.1).unwrap()); - limits.reverse(); - Ok(Self { - limits, - query_log: vec![], - }) - } - - pub async fn block_until_allowed(&mut self) { - if self.limits.is_empty() { - return; - } - let mut sleep_duration = self.get_sleep_duration(); - loop { - sleep(sleep_duration).await; - self.prune_log(); - if self.request_allowed() { - self.query_log.push(Instant::now()); - return; - } - sleep_duration = self.get_sleep_duration(); - } - } - - fn get_sleep_duration(&self) -> Duration { - let (nb_req, min_duration) = match self.limits.last() { - Some((n, d)) => (*n as u64, *d), - None => { - return Duration::from_millis(0); - } - }; - let nb_mili = match min_duration.as_secs() { - 0 | 1 => crate::MIN_RATE_LIMIT_SLEEP_MILISEC, - n => { - let a = n * 200 / nb_req; - let a = cmp::min(a, crate::MAX_RATE_LIMIT_SLEEP_MILISEC); - cmp::max(a, crate::MIN_RATE_LIMIT_SLEEP_MILISEC) - } - }; - Duration::from_millis(nb_mili) - } - - fn request_allowed(&self) -> bool { - for (max_allowed, duration) in self.limits.iter() { - match Instant::now().checked_sub(*duration) { - Some(max_date) => { - let nb_req = self - .query_log - .iter() - .filter(move |x| **x > max_date) - .count(); - if nb_req >= *max_allowed { - return false; - } - } - None => { - return false; - } - }; - } - true - } - - fn prune_log(&mut self) { - if let Some((_, max_limit)) = self.limits.first() { - if let Some(prune_date) = Instant::now().checked_sub(*max_limit) { - self.query_log.retain(move |&d| d > prune_date); - } - } - } -} diff --git a/acmed/src/hooks.rs b/acmed/src/hooks.rs deleted file mode 100644 index b38c3a1..0000000 --- a/acmed/src/hooks.rs +++ /dev/null @@ -1,215 +0,0 @@ -pub use crate::config::HookType; -use crate::logs::HasLogger; -use crate::template::render_template; -use acme_common::error::Error; -use async_process::{Command, Stdio}; -use futures::AsyncWriteExt; -use serde::Serialize; -use std::collections::hash_map::Iter; -use std::collections::{HashMap, HashSet}; -use std::fs::File; -use std::io::prelude::*; -use std::io::BufReader; -use std::path::PathBuf; -use std::{env, fmt}; - -pub trait HookEnvData { - fn set_env(&mut self, env: &HashMap); - fn get_env(&self) -> Iter; -} - -fn deref(t: (&F, &G)) -> (F, G) -where - F: Clone, - G: Clone, -{ - ((*(t.0)).to_owned(), (*(t.1)).to_owned()) -} - -macro_rules! imple_hook_data_env { - ($t: ty) => { - impl HookEnvData for $t { - fn set_env(&mut self, env: &HashMap) { - for (key, value) in env::vars().chain(env.iter().map(deref)) { - self.env.insert(key, value); - } - } - - fn get_env(&self) -> Iter { - self.env.iter() - } - } - }; -} - -#[derive(Clone, Serialize)] -pub struct PostOperationHookData { - pub identifiers: Vec, - pub key_type: String, - pub status: String, - pub is_success: bool, - pub certificate_path: PathBuf, - pub private_key_path: PathBuf, - pub env: HashMap, -} - -imple_hook_data_env!(PostOperationHookData); - -#[derive(Clone, Serialize)] -pub struct ChallengeHookData { - pub identifier: String, - pub identifier_tls_alpn: String, - pub challenge: String, - pub file_name: String, - pub proof: String, - pub raw_proof: String, - pub is_clean_hook: bool, - pub env: HashMap, -} - -imple_hook_data_env!(ChallengeHookData); - -#[derive(Clone, Serialize)] -pub struct FileStorageHookData { - // TODO: add the current operation (create/edit) - pub file_name: String, - pub file_directory: String, - pub file_path: PathBuf, - pub env: HashMap, -} - -imple_hook_data_env!(FileStorageHookData); - -#[derive(Clone, Debug)] -pub enum HookStdin { - File(String), - Str(String), - None, -} - -#[derive(Clone, Debug)] -pub struct Hook { - pub name: String, - pub hook_type: HashSet, - pub cmd: String, - pub args: Option>, - pub stdin: HookStdin, - pub stdout: Option, - pub stderr: Option, - pub allow_failure: bool, -} - -impl fmt::Display for Hook { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{}", self.name) - } -} - -macro_rules! get_hook_output { - ($logger: expr, $out: expr, $data: expr, $hook_name: expr, $out_name: expr) => {{ - match $out { - Some(path) => { - let path = render_template(path, $data)?; - $logger.trace(&format!("hook \"{}\": {}: {path}", $hook_name, $out_name)); - let file = File::create(&path)?; - Stdio::from(file) - } - None => Stdio::null(), - } - }}; -} - -async fn call_single(logger: &L, data: &T, hook: &Hook) -> Result<(), Error> -where - L: HasLogger, - T: Clone + HookEnvData + Serialize, -{ - logger.debug(&format!("calling hook \"{}\"", hook.name)); - let mut v = vec![]; - let args = match &hook.args { - Some(lst) => { - for fmt in lst.iter() { - let s = render_template(fmt, &data)?; - v.push(s); - } - v.as_slice() - } - None => &[], - }; - logger.trace(&format!("hook \"{}\": cmd: {}", hook.name, hook.cmd)); - logger.trace(&format!("hook \"{}\": args: {args:?}", hook.name)); - let mut cmd = Command::new(&hook.cmd) - .envs(data.get_env()) - .args(args) - .stdout(get_hook_output!( - logger, - &hook.stdout, - &data, - &hook.name, - "stdout" - )) - .stderr(get_hook_output!( - logger, - &hook.stderr, - &data, - &hook.name, - "stderr" - )) - .stdin(match &hook.stdin { - HookStdin::Str(_) | HookStdin::File(_) => Stdio::piped(), - HookStdin::None => Stdio::null(), - }) - .spawn()?; - match &hook.stdin { - HookStdin::Str(s) => { - let data_in = render_template(s, &data)?; - logger.trace(&format!("hook \"{}\": string stdin: {data_in}", hook.name)); - let stdin = cmd.stdin.as_mut().ok_or("stdin not found")?; - stdin.write_all(data_in.as_bytes()).await?; - } - HookStdin::File(f) => { - let file_name = render_template(f, &data)?; - logger.trace(&format!("hook \"{}\": file stdin: {file_name}", hook.name)); - let stdin = cmd.stdin.as_mut().ok_or("stdin not found")?; - let file = File::open(&file_name).map_err(|e| Error::from(e).prefix(&file_name))?; - let buf_reader = BufReader::new(file); - for line in buf_reader.lines() { - let line = format!("{}\n", line?); - stdin.write_all(line.as_bytes()).await?; - } - } - HookStdin::None => {} - } - // TODO: add a timeout - let status = cmd.status().await?; - if !status.success() && !hook.allow_failure { - let msg = match status.code() { - Some(code) => format!("unrecoverable failure: code {code}").into(), - None => "unrecoverable failure".into(), - }; - return Err(msg); - } - match status.code() { - Some(code) => logger.debug(&format!("hook \"{}\": exited: code {code}", hook.name)), - None => logger.debug(&format!("hook \"{}\": exited", hook.name)), - }; - Ok(()) -} - -pub async fn call( - logger: &L, - hooks: &[Hook], - data: &T, - hook_type: HookType, -) -> Result<(), Error> -where - L: HasLogger, - T: Clone + HookEnvData + Serialize, -{ - for hook in hooks.iter().filter(|h| h.hook_type.contains(&hook_type)) { - call_single(logger, data, hook) - .await - .map_err(|e| e.prefix(&hook.name))?; - } - Ok(()) -} diff --git a/acmed/src/http.rs b/acmed/src/http.rs deleted file mode 100644 index 8ac6d8a..0000000 --- a/acmed/src/http.rs +++ /dev/null @@ -1,288 +0,0 @@ -use crate::acme_proto::structs::{AcmeError, HttpApiError}; -use crate::endpoint::Endpoint; -#[cfg(feature = "crypto_openssl")] -use acme_common::error::Error; -use reqwest::header::{HeaderMap, HeaderValue}; -use reqwest::{header, Client, ClientBuilder, Response}; -use std::fs::File; -#[cfg(feature = "crypto_openssl")] -use std::io::prelude::*; -use std::{thread, time}; - -pub const CONTENT_TYPE_JOSE: &str = "application/jose+json"; -pub const CONTENT_TYPE_JSON: &str = "application/json"; -pub const CONTENT_TYPE_PEM: &str = "application/pem-certificate-chain"; -pub const HEADER_NONCE: &str = "Replay-Nonce"; -pub const HEADER_LOCATION: &str = "Location"; - -pub struct ValidHttpResponse { - headers: HeaderMap, - pub body: String, -} - -impl ValidHttpResponse { - pub fn get_header(&self, name: &str) -> Option { - match self.headers.get(name) { - Some(r) => match header_to_string(r) { - Ok(h) => Some(h), - Err(_) => None, - }, - None => None, - } - } - - pub fn json(&self) -> Result - where - T: serde::de::DeserializeOwned, - { - serde_json::from_str(&self.body).map_err(Error::from) - } - - async fn from_response(response: Response) -> Result { - let headers = response.headers().clone(); - let body = response.text().await?; - log::trace!("HTTP response headers: {headers:?}"); - log::trace!("HTTP response body: {body}"); - Ok(ValidHttpResponse { headers, body }) - } -} - -#[derive(Clone, Debug)] -pub enum HttpError { - ApiError(HttpApiError), - GenericError(Error), -} - -impl HttpError { - pub fn in_err(error: HttpError) -> Error { - match error { - HttpError::ApiError(e) => e.to_string().into(), - HttpError::GenericError(e) => e, - } - } - - pub fn is_acme_err(&self, acme_error: AcmeError) -> bool { - match self { - HttpError::ApiError(aerr) => aerr.get_acme_type() == acme_error, - HttpError::GenericError(_) => false, - } - } -} - -impl From for HttpError { - fn from(error: Error) -> Self { - HttpError::GenericError(error) - } -} - -impl From for HttpError { - fn from(error: HttpApiError) -> Self { - HttpError::ApiError(error) - } -} - -impl From<&str> for HttpError { - fn from(error: &str) -> Self { - HttpError::GenericError(error.into()) - } -} - -impl From for HttpError { - fn from(error: String) -> Self { - HttpError::GenericError(error.into()) - } -} - -impl From for HttpError { - fn from(error: reqwest::Error) -> Self { - HttpError::GenericError(error.into()) - } -} - -fn is_nonce(data: &str) -> bool { - !data.is_empty() - && data - .bytes() - .all(|c| c.is_ascii_alphanumeric() || c == b'-' || c == b'_') -} - -async fn new_nonce(endpoint: &mut Endpoint) -> Result<(), HttpError> { - rate_limit(endpoint).await; - let url = endpoint.dir.new_nonce.clone(); - let _ = get(endpoint, &url).await?; - Ok(()) -} - -fn update_nonce(endpoint: &mut Endpoint, response: &Response) -> Result<(), Error> { - if let Some(nonce) = response.headers().get(HEADER_NONCE) { - let nonce = header_to_string(nonce)?; - if !is_nonce(&nonce) { - let msg = format!("{nonce}: invalid nonce."); - return Err(msg.into()); - } - endpoint.nonce = Some(nonce); - } - Ok(()) -} - -fn check_status(response: &Response) -> Result<(), Error> { - if !response.status().is_success() { - let status = response.status(); - let msg = format!("HTTP error: {}: {}", status.as_u16(), status.as_str()); - return Err(msg.into()); - } - Ok(()) -} - -async fn rate_limit(endpoint: &mut Endpoint) { - endpoint.rl.block_until_allowed().await; -} - -fn header_to_string(header_value: &HeaderValue) -> Result { - let s = header_value - .to_str() - .map_err(|_| Error::from("invalid header format"))?; - Ok(s.to_string()) -} - -fn get_client(root_certs: &[String]) -> Result { - let useragent = format!( - "{}/{} ({}) {}", - crate::APP_NAME, - crate::APP_VERSION, - env!("ACMED_TARGET"), - env!("ACMED_HTTP_LIB_AGENT") - ); - // TODO: allow to change the language - let mut client_builder = ClientBuilder::new(); - let mut default_headers = HeaderMap::new(); - default_headers.append(header::ACCEPT_LANGUAGE, "en-US,en;q=0.5".parse().unwrap()); - default_headers.append(header::USER_AGENT, useragent.parse().unwrap()); - client_builder = client_builder.default_headers(default_headers); - for crt_file in root_certs.iter() { - #[cfg(feature = "crypto_openssl")] - { - let mut buff = Vec::new(); - File::open(crt_file) - .map_err(|e| Error::from(e).prefix(crt_file))? - .read_to_end(&mut buff)?; - let crt = reqwest::Certificate::from_pem(&buff)?; - client_builder = client_builder.add_root_certificate(crt); - } - } - Ok(client_builder.build()?) -} - -pub async fn get(endpoint: &mut Endpoint, url: &str) -> Result { - let client = get_client(&endpoint.root_certificates)?; - rate_limit(endpoint).await; - let response = client - .get(url) - .header(header::ACCEPT, CONTENT_TYPE_JSON) - .send() - .await?; - update_nonce(endpoint, &response)?; - check_status(&response)?; - ValidHttpResponse::from_response(response) - .await - .map_err(HttpError::from) -} - -pub async fn post( - endpoint: &mut Endpoint, - url: &str, - data_builder: &F, - content_type: &str, - accept: &str, -) -> Result -where - F: Fn(&str, &str) -> Result, -{ - let client = get_client(&endpoint.root_certificates)?; - if endpoint.nonce.is_none() { - let _ = new_nonce(endpoint).await; - } - for _ in 0..crate::DEFAULT_HTTP_FAIL_NB_RETRY { - let mut request = client.post(url); - request = request.header(header::ACCEPT, accept); - request = request.header(header::CONTENT_TYPE, content_type); - let nonce = &endpoint.nonce.clone().unwrap_or_default(); - let body = data_builder(nonce, url)?; - rate_limit(endpoint).await; - log::trace!("POST request body: {body}"); - let response = request.body(body).send().await?; - update_nonce(endpoint, &response)?; - match check_status(&response) { - Ok(_) => { - return ValidHttpResponse::from_response(response) - .await - .map_err(HttpError::from); - } - Err(_) => { - let resp = ValidHttpResponse::from_response(response).await?; - let api_err = resp.json::()?; - let acme_err = api_err.get_acme_type(); - if !acme_err.is_recoverable() { - return Err(api_err.into()); - } - } - } - thread::sleep(time::Duration::from_secs(crate::DEFAULT_HTTP_FAIL_WAIT_SEC)); - } - Err("too much errors, will not retry".into()) -} - -pub async fn post_jose( - endpoint: &mut Endpoint, - url: &str, - data_builder: &F, -) -> Result -where - F: Fn(&str, &str) -> Result, -{ - post( - endpoint, - url, - data_builder, - CONTENT_TYPE_JOSE, - CONTENT_TYPE_JSON, - ) - .await -} - -#[cfg(test)] -mod tests { - use super::is_nonce; - - #[test] - fn test_nonce_valid() { - let lst = [ - "XFHw3qcgFNZAdw", - "XFHw3qcg-NZAdw", - "XFHw3qcg_NZAdw", - "XFHw3qcg-_ZAdw", - "a", - "1", - "-", - "_", - ]; - for n in lst.iter() { - assert!(is_nonce(n)); - } - } - - #[test] - fn test_nonce_invalid() { - let lst = [ - "", - "rdo9x8gS4K/mZg==", - "rdo9x8gS4K/mZg", - "rdo9x8gS4K+mZg", - "৬", - "京", - ]; - for n in lst.iter() { - assert!(!is_nonce(n)); - } - } -} diff --git a/acmed/src/identifier.rs b/acmed/src/identifier.rs deleted file mode 100644 index 94b6c8b..0000000 --- a/acmed/src/identifier.rs +++ /dev/null @@ -1,147 +0,0 @@ -use crate::acme_proto::Challenge; -use acme_common::error::Error; -use acme_common::to_idna; -use serde::{Deserialize, Serialize}; -use std::collections::HashMap; -use std::fmt; -use std::net::IpAddr; -use std::str::FromStr; - -// RFC 3596, section 2.5 -fn u8_to_nibbles_string(value: &u8) -> String { - let bytes = value.to_ne_bytes(); - let first = bytes[0] & 0x0f; - let second = (bytes[0] >> 4) & 0x0f; - format!("{first:x}.{second:x}") -} - -#[derive(Clone, Debug, Deserialize, Serialize, Eq, PartialEq)] -pub enum IdentifierType { - #[serde(rename = "dns")] - Dns, - #[serde(rename = "ip")] - Ip, -} - -impl IdentifierType { - pub fn supported_challenges(&self) -> Vec { - match self { - IdentifierType::Dns => vec![Challenge::Http01, Challenge::Dns01, Challenge::TlsAlpn01], - IdentifierType::Ip => vec![Challenge::Http01, Challenge::TlsAlpn01], - } - } -} - -impl fmt::Display for IdentifierType { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - let name = match self { - IdentifierType::Dns => "dns", - IdentifierType::Ip => "ip", - }; - write!(f, "{name}") - } -} - -#[derive(Clone, Debug)] -pub struct Identifier { - pub id_type: IdentifierType, - pub value: String, - pub challenge: Challenge, - pub env: HashMap, -} - -impl Identifier { - pub fn new( - id_type: IdentifierType, - value: &str, - challenge: &str, - env: &HashMap, - ) -> Result { - let value = match id_type { - IdentifierType::Dns => to_idna(value)?, - IdentifierType::Ip => IpAddr::from_str(value)?.to_string(), - }; - let challenge = Challenge::from_str(challenge)?; - if !id_type.supported_challenges().contains(&challenge) { - let msg = - format!("challenge {challenge} cannot be used with identifier of type {id_type}"); - return Err(msg.into()); - } - Ok(Identifier { - id_type, - value, - challenge, - env: env.clone(), - }) - } - - pub fn get_tls_alpn_name(&self) -> Result { - match &self.id_type { - IdentifierType::Dns => Ok(self.value.to_owned()), - IdentifierType::Ip => match IpAddr::from_str(&self.value)? { - IpAddr::V4(ip) => { - let dn = ip - .octets() - .iter() - .rev() - .map(|v| v.to_string()) - .collect::>() - .join("."); - let dn = format!("{dn}.in-addr.arpa"); - Ok(dn) - } - IpAddr::V6(ip) => { - let dn = ip - .octets() - .iter() - .rev() - .map(u8_to_nibbles_string) - .collect::>() - .join("."); - let dn = format!("{dn}.ip6.arpa"); - Ok(dn) - } - }, - } - } -} - -impl fmt::Display for Identifier { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{}: {} ({})", self.id_type, self.value, self.challenge) - } -} - -#[cfg(test)] -mod tests { - use super::*; - use std::collections::HashMap; - - #[test] - fn test_ipv4_tls_alpn_name() { - let env = HashMap::new(); - let id = Identifier::new(IdentifierType::Ip, "203.0.113.1", "http-01", &env).unwrap(); - assert_eq!(&id.get_tls_alpn_name().unwrap(), "1.113.0.203.in-addr.arpa"); - } - - #[test] - fn test_ipv6_tls_alpn_name() { - let env = HashMap::new(); - let id = Identifier::new(IdentifierType::Ip, "2001:db8::1", "http-01", &env).unwrap(); - assert_eq!( - &id.get_tls_alpn_name().unwrap(), - "1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.8.b.d.0.1.0.0.2.ip6.arpa" - ); - let id = Identifier::new( - IdentifierType::Ip, - "4321:0:1:2:3:4:567:89ab", - "http-01", - &env, - ) - .unwrap(); - assert_eq!( - &id.get_tls_alpn_name().unwrap(), - "b.a.9.8.7.6.5.0.4.0.0.0.3.0.0.0.2.0.0.0.1.0.0.0.0.0.0.0.1.2.3.4.ip6.arpa" - ); - } -} diff --git a/acmed/src/jws.rs b/acmed/src/jws.rs deleted file mode 100644 index 8035dc8..0000000 --- a/acmed/src/jws.rs +++ /dev/null @@ -1,191 +0,0 @@ -use acme_common::b64_encode; -use acme_common::crypto::{HashFunction, JwsSignatureAlgorithm, KeyPair}; -use acme_common::error::Error; -use serde::Serialize; -use serde_json::value::Value; - -#[derive(Serialize)] -struct JwsData { - protected: String, - payload: String, - signature: String, -} - -#[derive(Serialize)] -struct JwsProtectedHeader { - alg: String, - #[serde(skip_serializing_if = "Option::is_none")] - jwk: Option, - #[serde(skip_serializing_if = "Option::is_none")] - kid: Option, - #[serde(skip_serializing_if = "Option::is_none")] - nonce: Option, - url: String, -} - -fn get_jws_data( - key_pair: &KeyPair, - sign_alg: &JwsSignatureAlgorithm, - protected: &str, - payload: &[u8], -) -> Result { - let protected = b64_encode(protected); - let payload = b64_encode(payload); - let signing_input = format!("{protected}.{payload}"); - let signature = key_pair.sign(sign_alg, signing_input.as_bytes())?; - let signature = b64_encode(&signature); - let data = JwsData { - protected, - payload, - signature, - }; - let str_data = serde_json::to_string(&data)?; - Ok(str_data) -} - -pub fn encode_jwk( - key_pair: &KeyPair, - sign_alg: &JwsSignatureAlgorithm, - payload: &[u8], - url: &str, - nonce: Option, -) -> Result { - let protected = JwsProtectedHeader { - alg: sign_alg.to_string(), - jwk: Some(key_pair.jwk_public_key()?), - kid: None, - nonce, - url: url.into(), - }; - let protected = serde_json::to_string(&protected)?; - get_jws_data(key_pair, sign_alg, &protected, payload) -} - -pub fn encode_kid( - key_pair: &KeyPair, - sign_alg: &JwsSignatureAlgorithm, - key_id: &str, - payload: &[u8], - url: &str, - nonce: &str, -) -> Result { - let protected = JwsProtectedHeader { - alg: sign_alg.to_string(), - jwk: None, - kid: Some(key_id.to_string()), - nonce: Some(nonce.into()), - url: url.into(), - }; - let protected = serde_json::to_string(&protected)?; - get_jws_data(key_pair, sign_alg, &protected, payload) -} - -pub fn encode_kid_mac( - key: &[u8], - sign_alg: &JwsSignatureAlgorithm, - key_id: &str, - payload: &[u8], - url: &str, -) -> Result { - let protected = JwsProtectedHeader { - alg: sign_alg.to_string(), - jwk: None, - kid: Some(key_id.to_string()), - nonce: None, - url: url.into(), - }; - let protected = serde_json::to_string(&protected)?; - let protected = b64_encode(&protected); - let payload = b64_encode(payload); - let signing_input = format!("{protected}.{payload}"); - let hash_func = match sign_alg { - JwsSignatureAlgorithm::Hs256 => HashFunction::Sha256, - JwsSignatureAlgorithm::Hs384 => HashFunction::Sha384, - JwsSignatureAlgorithm::Hs512 => HashFunction::Sha512, - _ => { - return Err(format!("{sign_alg}: not a HMAC-based signature algorithm").into()); - } - }; - let signature = hash_func.hmac(key, signing_input.as_bytes())?; - let signature = b64_encode(&signature); - let data = JwsData { - protected, - payload, - signature, - }; - let str_data = serde_json::to_string(&data)?; - Ok(str_data) -} - -#[cfg(test)] -mod tests { - use super::{encode_jwk, encode_kid}; - use acme_common::crypto::{gen_keypair, KeyType}; - - #[test] - fn test_default_jwk() { - let key_type = KeyType::EcdsaP256; - let key_pair = gen_keypair(key_type).unwrap(); - let payload = "Dummy payload 1"; - let payload_b64 = "RHVtbXkgcGF5bG9hZCAx"; - let s = encode_jwk( - &key_pair, - &key_type.get_default_signature_alg(), - payload.as_bytes(), - "", - Some(String::new()), - ); - assert!(s.is_ok()); - let s = s.unwrap(); - assert!(s.contains("\"protected\"")); - assert!(s.contains("\"payload\"")); - assert!(s.contains("\"signature\"")); - assert!(s.contains(payload_b64)); - } - - #[test] - fn test_default_nopad_jwk() { - let key_type = KeyType::EcdsaP256; - let key_pair = gen_keypair(key_type).unwrap(); - let payload = "Dummy payload"; - let payload_b64 = "RHVtbXkgcGF5bG9hZA"; - let payload_b64_pad = "RHVtbXkgcGF5bG9hZA=="; - let s = encode_jwk( - &key_pair, - &key_type.get_default_signature_alg(), - payload.as_bytes(), - "", - Some(String::new()), - ); - assert!(s.is_ok()); - let s = s.unwrap(); - assert!(s.contains("\"protected\"")); - assert!(s.contains("\"payload\"")); - assert!(s.contains("\"signature\"")); - assert!(s.contains(payload_b64)); - assert!(!s.contains(payload_b64_pad)); - } - - #[test] - fn test_default_kid() { - let key_type = KeyType::EcdsaP256; - let key_pair = gen_keypair(key_type).unwrap(); - let payload = "Dummy payload 1"; - let payload_b64 = "RHVtbXkgcGF5bG9hZCAx"; - let key_id = "0x2a"; - let s = encode_kid( - &key_pair, - &key_type.get_default_signature_alg(), - key_id, - payload.as_bytes(), - "", - "", - ); - assert!(s.is_ok()); - let s = s.unwrap(); - assert!(s.contains("\"protected\"")); - assert!(s.contains("\"payload\"")); - assert!(s.contains("\"signature\"")); - assert!(s.contains(payload_b64)); - } -} diff --git a/acmed/src/logs.rs b/acmed/src/logs.rs deleted file mode 100644 index 16d8c85..0000000 --- a/acmed/src/logs.rs +++ /dev/null @@ -1,6 +0,0 @@ -pub trait HasLogger { - fn warn(&self, msg: &str); - fn info(&self, msg: &str); - fn debug(&self, msg: &str); - fn trace(&self, msg: &str); -} diff --git a/acmed/src/main.rs b/acmed/src/main.rs deleted file mode 100644 index ba4f3ff..0000000 --- a/acmed/src/main.rs +++ /dev/null @@ -1,180 +0,0 @@ -use crate::main_event_loop::MainEventLoop; -use acme_common::crypto::{ - get_lib_name, get_lib_version, HashFunction, JwsSignatureAlgorithm, KeyType, -}; -use acme_common::logs::{set_log_system, DEFAULT_LOG_LEVEL}; -use acme_common::{clean_pid_file, init_server}; -use async_lock::RwLock; -use clap::{Arg, ArgAction, Command}; -use log::error; -use std::sync::Arc; -use tokio::runtime::Builder; - -mod account; -mod acme_proto; -mod certificate; -mod config; -mod duration; -mod endpoint; -mod hooks; -mod http; -mod identifier; -mod jws; -mod logs; -mod main_event_loop; -mod storage; -mod template; - -pub const APP_NAME: &str = "ACMEd"; -pub const APP_THREAD_NAME: &str = "acmed-runtime"; -pub const APP_VERSION: &str = env!("CARGO_PKG_VERSION"); -pub const DEFAULT_ACCOUNTS_DIR: &str = env!("ACMED_DEFAULT_ACCOUNTS_DIR"); -pub const DEFAULT_CERT_DIR: &str = env!("ACMED_DEFAULT_CERT_DIR"); -pub const DEFAULT_CERT_FORMAT: &str = env!("ACMED_DEFAULT_CERT_FORMAT"); -pub const DEFAULT_CONFIG_FILE: &str = env!("ACMED_DEFAULT_CONFIG_FILE"); -pub const DEFAULT_PID_FILE: &str = env!("ACMED_DEFAULT_PID_FILE"); -pub const DEFAULT_POOL_TIME: u64 = 5000; -pub const DEFAULT_CSR_DIGEST: HashFunction = HashFunction::Sha256; -pub const DEFAULT_CERT_KEY_TYPE: KeyType = KeyType::Rsa2048; -pub const DEFAULT_CERT_FILE_MODE: u32 = 0o644; -pub const DEFAULT_CERT_RANDOM_EARLY_RENEW: u64 = 0; // default to not renewing early -pub const DEFAULT_CERT_RENEW_DELAY: u64 = 30 * 24 * 60 * 60; // 30 days -pub const DEFAULT_PK_FILE_MODE: u32 = 0o600; -pub const DEFAULT_ACCOUNT_FILE_MODE: u32 = 0o600; -pub const DEFAULT_KP_REUSE: bool = false; -pub const DEFAULT_ACCOUNT_KEY_TYPE: KeyType = KeyType::EcdsaP256; -pub const DEFAULT_EXTERNAL_ACCOUNT_JWA: JwsSignatureAlgorithm = JwsSignatureAlgorithm::Hs256; -pub const DEFAULT_POOL_NB_TRIES: usize = 20; -pub const DEFAULT_POOL_WAIT_SEC: u64 = 5; -pub const DEFAULT_HTTP_FAIL_NB_RETRY: usize = 10; -pub const DEFAULT_HTTP_FAIL_WAIT_SEC: u64 = 1; -pub const DEFAULT_HOOK_ALLOW_FAILURE: bool = false; -pub const MAX_RATE_LIMIT_SLEEP_MILISEC: u64 = 3_600_000; -pub const MIN_RATE_LIMIT_SLEEP_MILISEC: u64 = 100; - -type AccountSync = Arc>; -type EndpointSync = Arc>; - -fn main() { - Builder::new_multi_thread() - .enable_all() - .thread_name(APP_THREAD_NAME) - .build() - .unwrap() - .block_on(inner_main()); -} - -async fn inner_main() { - let full_version = format!( - "{APP_VERSION} built for {}\n\nCryptographic library:\n - {} {}\nHTTP client library:\n - {} {}", - env!("ACMED_TARGET"), - get_lib_name(), - get_lib_version(), - env!("ACMED_HTTP_LIB_NAME"), - env!("ACMED_HTTP_LIB_VERSION") - ); - let default_log_level = DEFAULT_LOG_LEVEL.to_string().to_lowercase(); - let matches = Command::new(APP_NAME) - .version(APP_VERSION) - .long_version(full_version) - .arg( - Arg::new("config") - .short('c') - .long("config") - .help("Path to the main configuration file") - .num_args(1) - .value_name("FILE") - .default_value(DEFAULT_CONFIG_FILE), - ) - .arg( - Arg::new("log-level") - .long("log-level") - .help("Specify the log level") - .num_args(1) - .value_name("LEVEL") - .value_parser(["error", "warn", "info", "debug", "trace"]) - .default_value(default_log_level), - ) - .arg( - Arg::new("to-syslog") - .long("log-syslog") - .help("Sends log messages via syslog") - .conflicts_with("to-stderr") - .action(ArgAction::SetTrue), - ) - .arg( - Arg::new("to-stderr") - .long("log-stderr") - .help("Prints log messages to the standard error output") - .conflicts_with("to-syslog") - .action(ArgAction::SetTrue), - ) - .arg( - Arg::new("foreground") - .short('f') - .long("foreground") - .help("Runs in the foreground") - .action(ArgAction::SetTrue), - ) - .arg( - Arg::new("pid-file") - .long("pid-file") - .help("Path to the PID file") - .num_args(1) - .value_name("FILE") - .default_value(DEFAULT_PID_FILE) - .default_value_if("no-pid-file", clap::builder::ArgPredicate::IsPresent, None) - .conflicts_with("no-pid-file"), - ) - .arg( - Arg::new("no-pid-file") - .long("no-pid-file") - .help("Do not create any PID file") - .conflicts_with("pid-file") - .action(ArgAction::SetTrue), - ) - .arg( - Arg::new("root-cert") - .long("root-cert") - .help("Add a root certificate to the trust store (can be set multiple times)") - .num_args(1) - .action(ArgAction::Append) - .value_name("FILE"), - ) - .get_matches(); - - match set_log_system( - matches.get_one::("log-level").map(|e| e.as_str()), - matches.get_flag("to-syslog"), - matches.get_flag("to-stderr"), - ) { - Ok(_) => {} - Err(e) => { - eprintln!("Error: {e}"); - std::process::exit(2); - } - }; - - let root_certs = match matches.get_many::("root-cert") { - Some(v) => v.map(|e| e.as_str()).collect(), - None => vec![], - }; - - let config_file = matches - .get_one::("config") - .map(|e| e.as_str()) - .unwrap_or(DEFAULT_CONFIG_FILE); - let pid_file = matches.get_one::("pid-file").map(|e| e.as_str()); - - init_server(matches.get_flag("foreground"), pid_file); - - let mut srv = match MainEventLoop::new(config_file, &root_certs).await { - Ok(s) => s, - Err(e) => { - error!("{e}"); - let _ = clean_pid_file(pid_file); - std::process::exit(1); - } - }; - srv.run().await; -} diff --git a/acmed/src/main_event_loop.rs b/acmed/src/main_event_loop.rs deleted file mode 100644 index 8befdb7..0000000 --- a/acmed/src/main_event_loop.rs +++ /dev/null @@ -1,223 +0,0 @@ -use crate::account::Account; -use crate::acme_proto::request_certificate; -use crate::certificate::Certificate; -use crate::config; -use crate::endpoint::Endpoint; -use crate::hooks::HookType; -use crate::logs::HasLogger; -use crate::storage::FileManager; -use crate::{AccountSync, EndpointSync}; -use acme_common::error::Error; -use async_lock::RwLock; -use futures::stream::FuturesUnordered; -use futures::StreamExt; -use std::collections::HashMap; -use std::sync::Arc; -use std::time::Duration; -use tokio::time::sleep; - -pub struct MainEventLoop { - certificates: HashMap, - accounts: HashMap, - endpoints: HashMap, -} - -impl MainEventLoop { - pub async fn new(config_file: &str, root_certs: &[&str]) -> Result { - let cnf = config::from_file(config_file)?; - let file_hooks = vec![ - HookType::FilePreCreate, - HookType::FilePostCreate, - HookType::FilePreEdit, - HookType::FilePostEdit, - ] - .into_iter() - .collect(); - let cert_hooks = vec![ - HookType::ChallengeHttp01, - HookType::ChallengeHttp01Clean, - HookType::ChallengeDns01, - HookType::ChallengeDns01Clean, - HookType::ChallengeTlsAlpn01, - HookType::ChallengeTlsAlpn01Clean, - HookType::PostOperation, - ] - .into_iter() - .collect(); - - let mut accounts: HashMap = HashMap::new(); - for acc in &cnf.account { - let fm = FileManager { - account_directory: cnf.get_account_dir(), - account_name: acc.name.clone(), - crt_name: String::new(), - crt_name_format: String::new(), - crt_directory: String::new(), - crt_key_type: String::new(), - cert_file_mode: cnf.get_cert_file_mode(), - cert_file_owner: cnf.get_cert_file_user(), - cert_file_group: cnf.get_cert_file_group(), - cert_file_ext: cnf.get_cert_file_ext(), - pk_file_mode: cnf.get_pk_file_mode(), - pk_file_owner: cnf.get_pk_file_user(), - pk_file_group: cnf.get_pk_file_group(), - pk_file_ext: cnf.get_pk_file_ext(), - hooks: acc - .get_hooks(&cnf)? - .iter() - .filter(|h| !h.hook_type.is_disjoint(&file_hooks)) - .map(|e| e.to_owned()) - .collect(), - env: acc.env.clone(), - }; - let account = acc.to_generic(&fm).await?; - let name = acc.name.clone(); - accounts.insert(name, account); - } - - let mut endpoints: HashMap = HashMap::new(); - let mut certificates: HashMap = HashMap::new(); - for crt in cnf.certificate.iter() { - let endpoint = crt.get_endpoint(&cnf, root_certs)?; - let endpoint_name = endpoint.name.clone(); - let crt_name = crt.get_crt_name()?; - let key_type = crt.get_key_type()?; - let hooks = crt.get_hooks(&cnf)?; - let fm = FileManager { - account_directory: cnf.get_account_dir(), - account_name: crt.account.clone(), - crt_name: crt_name.clone(), - crt_name_format: crt.get_crt_name_format(&cnf)?, - crt_directory: crt.get_crt_dir(&cnf), - crt_key_type: key_type.to_string(), - cert_file_mode: cnf.get_cert_file_mode(), - cert_file_owner: cnf.get_cert_file_user(), - cert_file_group: cnf.get_cert_file_group(), - cert_file_ext: cnf.get_cert_file_ext(), - pk_file_mode: cnf.get_pk_file_mode(), - pk_file_owner: cnf.get_pk_file_user(), - pk_file_group: cnf.get_pk_file_group(), - pk_file_ext: cnf.get_pk_file_ext(), - hooks: hooks - .iter() - .filter(|h| !h.hook_type.is_disjoint(&file_hooks)) - .map(|e| e.to_owned()) - .collect(), - env: crt.env.clone(), - }; - let cert = Certificate { - account_name: crt.account.clone(), - identifiers: crt.get_identifiers()?, - subject_attributes: crt.subject_attributes.to_generic(), - key_type, - csr_digest: crt.get_csr_digest()?, - kp_reuse: crt.get_kp_reuse(), - endpoint_name: endpoint_name.clone(), - hooks: hooks - .iter() - .filter(|h| !h.hook_type.is_disjoint(&cert_hooks)) - .map(|e| e.to_owned()) - .collect(), - crt_name, - env: crt.env.to_owned(), - random_early_renew: crt.get_random_early_renew(&cnf)?, - renew_delay: crt.get_renew_delay(&cnf)?, - file_manager: fm, - }; - let crt_id = cert.get_id(); - if certificates.contains_key(&crt_id) { - let msg = format!("{crt_id}: duplicate certificate id"); - return Err(msg.into()); - } - match accounts.get_mut(&crt.account) { - Some(acc) => acc.add_endpoint_name(&endpoint_name), - None => { - let msg = format!("{}: account not found", &crt.account); - return Err(msg.into()); - } - }; - if !endpoints.contains_key(&endpoint.name) { - endpoints.insert(endpoint.name.clone(), endpoint); - } - certificates.insert(crt_id, cert); - } - - Ok(MainEventLoop { - certificates, - accounts: accounts - .iter() - .map(|(k, v)| (k.to_owned(), Arc::new(RwLock::new(v.to_owned())))) - .collect(), - endpoints: endpoints - .iter() - .map(|(k, v)| (k.to_owned(), Arc::new(RwLock::new(v.to_owned())))) - .collect(), - }) - } - - pub async fn run(&mut self) { - let mut renewals = FuturesUnordered::new(); - for (_, crt) in self.certificates.iter_mut() { - log::trace!("Adding certificate: {}", crt.get_id()); - if let Some(acc) = self.accounts.get(&crt.account_name) { - if let Some(ept) = self.endpoints.get(&crt.endpoint_name) { - renewals.push(renew_certificate(crt, acc.clone(), ept.clone())); - } - } - } - loop { - if renewals.is_empty() { - log::error!("No certificate found."); - return; - } - if let Some((crt, acc, ept)) = renewals.next().await { - renewals.push(renew_certificate(crt, acc, ept)); - } - } - } -} - -async fn renew_certificate( - certificate: &mut Certificate, - account_s: AccountSync, - endpoint_s: EndpointSync, -) -> (&mut Certificate, AccountSync, EndpointSync) { - let backoff = [60, 10 * 60, 100 * 60, 24 * 60 * 60]; - let mut scheduling_retries = 0; - loop { - match certificate.schedule_renewal().await { - Ok(duration) => { - sleep(duration).await; - break; - } - Err(e) => { - certificate.warn(&e.message); - sleep(Duration::from_secs( - backoff[scheduling_retries.min(backoff.len() - 1)], - )) - .await; - scheduling_retries += 1; - } - } - } - let (status, is_success) = - match request_certificate(certificate, account_s.clone(), endpoint_s.clone()).await { - Ok(_) => ("success".to_string(), true), - Err(e) => { - let e = e.prefix("unable to renew the certificate"); - certificate.warn(&e.message); - (e.message, false) - } - }; - match certificate - .call_post_operation_hooks(&status, is_success) - .await - { - Ok(_) => {} - Err(e) => { - let e = e.prefix("post-operation hook error"); - certificate.warn(&e.message); - } - }; - (certificate, account_s.clone(), endpoint_s.clone()) -} diff --git a/acmed/src/storage.rs b/acmed/src/storage.rs deleted file mode 100644 index 4fbbf1f..0000000 --- a/acmed/src/storage.rs +++ /dev/null @@ -1,312 +0,0 @@ -use crate::hooks::{self, FileStorageHookData, Hook, HookEnvData, HookType}; -use crate::logs::HasLogger; -use crate::template::render_template; -use acme_common::b64_encode; -use acme_common::crypto::{KeyPair, X509Certificate}; -use acme_common::error::Error; -use serde::Serialize; -use std::collections::HashMap; -use std::fmt; -use std::path::{Path, PathBuf}; -use tokio::fs::{File, OpenOptions}; -use tokio::io::{AsyncReadExt, AsyncWriteExt}; - -#[derive(Clone, Debug)] -pub struct FileManager { - pub account_name: String, - pub account_directory: String, - pub crt_name: String, - pub crt_name_format: String, - pub crt_directory: String, - pub crt_key_type: String, - pub cert_file_mode: u32, - pub cert_file_owner: Option, - pub cert_file_group: Option, - pub cert_file_ext: Option, - pub pk_file_mode: u32, - pub pk_file_owner: Option, - pub pk_file_group: Option, - pub pk_file_ext: Option, - pub hooks: Vec, - pub env: HashMap, -} - -impl HasLogger for FileManager { - fn warn(&self, msg: &str) { - log::warn!("{self}: {msg}"); - } - - fn info(&self, msg: &str) { - log::info!("{self}: {msg}"); - } - - fn debug(&self, msg: &str) { - log::debug!("{self}: {msg}"); - } - - fn trace(&self, msg: &str) { - log::trace!("{self}: {msg}"); - } -} - -impl fmt::Display for FileManager { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - let s = if !self.crt_name.is_empty() { - format!("certificate \"{}_{}\"", self.crt_name, self.crt_key_type) - } else { - format!("account \"{}\"", self.account_name) - }; - write!(f, "{s}") - } -} - -#[derive(Clone)] -enum FileType { - Account, - PrivateKey, - Certificate, -} - -impl fmt::Display for FileType { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - let s = match self { - FileType::Account => "account", - FileType::PrivateKey => "pk", - FileType::Certificate => "crt", - }; - write!(f, "{s}") - } -} - -#[derive(Clone, Serialize)] -pub struct CertFileFormat { - pub ext: String, - pub file_type: String, - pub key_type: String, - pub name: String, -} - -fn get_file_full_path( - fm: &FileManager, - file_type: FileType, -) -> Result<(String, String, PathBuf), Error> { - let base_path = match file_type { - FileType::Account => &fm.account_directory, - FileType::PrivateKey => &fm.crt_directory, - FileType::Certificate => &fm.crt_directory, - }; - let ext = match file_type { - FileType::Account => "bin".to_string(), - FileType::PrivateKey => fm.pk_file_ext.clone().unwrap_or("pem".to_string()), - FileType::Certificate => fm.cert_file_ext.clone().unwrap_or("pem".to_string()), - }; - let file_name = match file_type { - FileType::Account => format!( - "{account}.{file_type}.{ext}", - account = b64_encode(&fm.account_name), - file_type = file_type, - ext = ext - ), - FileType::PrivateKey | FileType::Certificate => { - let fmt_data = CertFileFormat { - key_type: fm.crt_key_type.to_string(), - ext, - file_type: file_type.to_string(), - name: fm.crt_name.to_owned(), - }; - render_template(&fm.crt_name_format, &fmt_data)? - } - }; - let mut path = PathBuf::from(&base_path); - path.push(&file_name); - Ok((base_path.to_string(), file_name, path)) -} - -fn get_file_path(fm: &FileManager, file_type: FileType) -> Result { - let (_, _, path) = get_file_full_path(fm, file_type)?; - Ok(path) -} - -async fn read_file(fm: &FileManager, path: &Path) -> Result, Error> { - fm.trace(&format!("reading file {path:?}")); - let mut file = File::open(path) - .await - .map_err(|e| Error::from(e).prefix(&path.display().to_string()))?; - let mut contents = vec![]; - file.read_to_end(&mut contents).await?; - Ok(contents) -} - -#[cfg(unix)] -fn set_owner(fm: &FileManager, path: &Path, file_type: FileType) -> Result<(), Error> { - let (uid, gid) = match file_type { - FileType::Certificate => (fm.cert_file_owner.to_owned(), fm.cert_file_group.to_owned()), - FileType::PrivateKey => (fm.pk_file_owner.to_owned(), fm.pk_file_group.to_owned()), - FileType::Account => { - // The account file does not need to be accessible to users other different from the current one. - return Ok(()); - } - }; - let uid = match uid { - Some(u) => { - if u.bytes().all(|b| b.is_ascii_digit()) { - let raw_uid = u - .parse::() - .map_err(|_| Error::from("unable to parse the UID"))?; - let nix_uid = nix::unistd::Uid::from_raw(raw_uid); - Some(nix_uid) - } else { - let user = nix::unistd::User::from_name(&u)?; - user.map(|u| u.uid) - } - } - None => None, - }; - let gid = match gid { - Some(g) => { - if g.bytes().all(|b| b.is_ascii_digit()) { - let raw_gid = g - .parse::() - .map_err(|_| Error::from("unable to parse the GID"))?; - let nix_gid = nix::unistd::Gid::from_raw(raw_gid); - Some(nix_gid) - } else { - let grp = nix::unistd::Group::from_name(&g)?; - grp.map(|g| g.gid) - } - } - None => None, - }; - match uid { - Some(u) => fm.trace(&format!("{path:?}: setting the uid to {}", u.as_raw())), - None => fm.trace(&format!("{path:?}: uid unchanged")), - }; - match gid { - Some(g) => fm.trace(&format!("{path:?}: setting the gid to {}", g.as_raw())), - None => fm.trace(&format!("{path:?}: gid unchanged")), - }; - match nix::unistd::chown(path, uid, gid) { - Ok(_) => Ok(()), - Err(e) => Err(format!("{e}").into()), - } -} - -async fn write_file(fm: &FileManager, file_type: FileType, data: &[u8]) -> Result<(), Error> { - let (file_directory, file_name, path) = get_file_full_path(fm, file_type.clone())?; - let mut hook_data = FileStorageHookData { - file_name, - file_directory, - file_path: path.to_owned(), - env: HashMap::new(), - }; - hook_data.set_env(&fm.env); - let is_new = !path.is_file(); - - if is_new { - hooks::call(fm, &fm.hooks, &hook_data, HookType::FilePreCreate).await?; - } else { - hooks::call(fm, &fm.hooks, &hook_data, HookType::FilePreEdit).await?; - } - - fm.trace(&format!("writing file {path:?}")); - let mut file = if cfg!(unix) { - let mut options = OpenOptions::new(); - options.mode(match &file_type { - FileType::Certificate => fm.cert_file_mode, - FileType::PrivateKey => fm.pk_file_mode, - FileType::Account => crate::DEFAULT_ACCOUNT_FILE_MODE, - }); - options - .write(true) - .create(true) - .open(&path) - .await - .map_err(|e| Error::from(e).prefix(&path.display().to_string()))? - } else { - File::create(&path) - .await - .map_err(|e| Error::from(e).prefix(&path.display().to_string()))? - }; - file.write_all(data) - .await - .map_err(|e| Error::from(e).prefix(&path.display().to_string()))?; - if cfg!(unix) { - set_owner(fm, &path, file_type).map_err(|e| e.prefix(&path.display().to_string()))?; - } - - if is_new { - hooks::call(fm, &fm.hooks, &hook_data, HookType::FilePostCreate).await?; - } else { - hooks::call(fm, &fm.hooks, &hook_data, HookType::FilePostEdit).await?; - } - Ok(()) -} - -pub async fn get_account_data(fm: &FileManager) -> Result, Error> { - let path = get_file_path(fm, FileType::Account)?; - read_file(fm, &path).await -} - -pub async fn set_account_data(fm: &FileManager, data: &[u8]) -> Result<(), Error> { - write_file(fm, FileType::Account, data).await -} - -pub async fn get_keypair_path(fm: &FileManager) -> Result { - get_file_path(fm, FileType::PrivateKey) -} - -pub async fn get_keypair(fm: &FileManager) -> Result { - let path = get_keypair_path(fm).await?; - let raw_key = read_file(fm, &path).await?; - let key = KeyPair::from_pem(&raw_key)?; - Ok(key) -} - -pub async fn set_keypair(fm: &FileManager, key_pair: &KeyPair) -> Result<(), Error> { - let data = key_pair.private_key_to_pem()?; - write_file(fm, FileType::PrivateKey, &data).await -} - -pub async fn get_certificate_path(fm: &FileManager) -> Result { - get_file_path(fm, FileType::Certificate) -} - -pub async fn get_certificate(fm: &FileManager) -> Result { - let path = get_certificate_path(fm).await?; - let raw_crt = read_file(fm, &path).await?; - let crt = X509Certificate::from_pem(&raw_crt)?; - Ok(crt) -} - -pub async fn write_certificate(fm: &FileManager, data: &[u8]) -> Result<(), Error> { - write_file(fm, FileType::Certificate, data).await -} - -fn check_files(fm: &FileManager, file_types: &[FileType]) -> bool { - for t in file_types.iter().cloned() { - let path = match get_file_path(fm, t) { - Ok(p) => p, - Err(_) => { - return false; - } - }; - fm.trace(&format!( - "testing file path: {}", - path.to_str().unwrap_or_default() - )); - if !path.is_file() { - return false; - } - } - true -} - -pub fn account_files_exists(fm: &FileManager) -> bool { - let file_types = vec![FileType::Account]; - check_files(fm, &file_types) -} - -pub fn certificate_files_exists(fm: &FileManager) -> bool { - let file_types = vec![FileType::PrivateKey, FileType::Certificate]; - check_files(fm, &file_types) -} diff --git a/acmed/src/template.rs b/acmed/src/template.rs deleted file mode 100644 index 7bc07bb..0000000 --- a/acmed/src/template.rs +++ /dev/null @@ -1,60 +0,0 @@ -use acme_common::error::Error; -use minijinja::{value::Value, Environment}; -use serde::Serialize; - -fn formatter_rev_labels(value: Value) -> Result { - if let Some(value) = value.as_str() { - Ok(value.rsplit('.').collect::>().join(".").into()) - } else { - Ok(value) - } -} - -pub fn render_template(template: &str, data: &T) -> Result -where - T: Serialize, -{ - let mut environment = Environment::new(); - environment.add_filter("rev_labels", formatter_rev_labels); - environment.add_template("template", template)?; - let template = environment.get_template("template")?; - Ok(template.render(data)?) -} - -#[cfg(test)] -mod tests { - use super::render_template; - use serde::Serialize; - - #[derive(Serialize)] - struct TplTest { - foo: String, - bar: u64, - } - - #[test] - fn test_basic_template() { - let c = TplTest { - foo: String::from("test"), - bar: 42, - }; - let tpl = "This is {{ foo }} {{ bar -}} !"; - let rendered = render_template(tpl, &c); - assert!(rendered.is_ok()); - let rendered = rendered.unwrap(); - assert_eq!(rendered, "This is test 42!"); - } - - #[test] - fn test_formatter_rev_labels() { - let c = TplTest { - foo: String::from("mx1.example.org"), - bar: 42, - }; - let tpl = "{{ foo }} - {{ foo | rev_labels }}"; - let rendered = render_template(tpl, &c); - assert!(rendered.is_ok()); - let rendered = rendered.unwrap(); - assert_eq!(rendered, "mx1.example.org - org.example.mx1"); - } -} diff --git a/acmed/config/acmed.toml b/config/acmed.toml similarity index 100% rename from acmed/config/acmed.toml rename to config/acmed.toml diff --git a/acmed/config/default_hooks.toml b/config/default_hooks.toml similarity index 100% rename from acmed/config/default_hooks.toml rename to config/default_hooks.toml diff --git a/acmed/config/letsencrypt.toml b/config/letsencrypt.toml similarity index 100% rename from acmed/config/letsencrypt.toml rename to config/letsencrypt.toml diff --git a/release.sh b/release.sh index 68a581c..47f4631 100755 --- a/release.sh +++ b/release.sh @@ -12,19 +12,17 @@ abort_release() display_crate_version() { - local crate_name="$1" local crate_version - crate_version=$(grep "^version" "${crate_name}/Cargo.toml" | cut -d '"' -f2) - echo "Current version for crate ${crate_name}: ${crate_version}" + crate_version=$(grep "^version" "Cargo.toml" | cut -d '"' -f2) + echo "Current version: ${crate_version}" } update_crate_version() { - local crate_name="$1" local new_version="$2" - sed -i "s/^version = .*/version = \"${new_version}\"/" "${crate_name}/Cargo.toml" + sed -i "s/^version = .*/version = \"${new_version}\"/" "/Cargo.toml" } display_man_date() @@ -84,13 +82,10 @@ release_new_version() local current_date="$2" local confirm_git_diff - update_crate_version "acme_common" "${new_version}" - update_crate_version "acmed" "${new_version}" - update_crate_version "tacd" "${new_version}" + update_crate_version "${new_version}" update_man_date "acmed.8" "${current_date}" update_man_date "acmed.toml.5" "${current_date}" - update_man_date "tacd.8" "${current_date}" update_changelog "${new_version}" @@ -118,14 +113,11 @@ main() check_working_directory - display_crate_version "acme_common" - display_crate_version "acmed" - display_crate_version "tacd" + display_crate_version echo display_man_date "acmed.8" display_man_date "acmed.toml.5" - display_man_date "tacd.8" echo echo -n "Enter the new version: " diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..f328e4d --- /dev/null +++ b/src/main.rs @@ -0,0 +1 @@ +fn main() {} diff --git a/tacd/Cargo.toml b/tacd/Cargo.toml deleted file mode 100644 index a19b080..0000000 --- a/tacd/Cargo.toml +++ /dev/null @@ -1,28 +0,0 @@ -[package] -name = "tacd" -version = "0.24.0" -authors = ["Rodolphe Breard "] -edition = "2018" -description = "TLS-ALPN Challenge Daemon" -readme = "../README.md" -repository = "https://github.com/breard-r/acmed" -license = "MIT OR Apache-2.0" -keywords = ["acme", "tls", "alpn", "X.509"] -categories = ["cryptography"] -include = ["src/**/*", "Cargo.toml", "LICENSE-*.txt"] -publish = false -rust-version = "1.74.0" - -[features] -default = ["openssl_dyn"] -crypto_openssl = [] -openssl_dyn = ["crypto_openssl", "acme_common/openssl_dyn"] -openssl_vendored = ["crypto_openssl", "acme_common/openssl_vendored"] - -[dependencies] -acme_common = { path = "../acme_common" } -anyhow = "1.0.81" -clap = { version = "4.5.3", features = ["string"] } -log = "0.4.21" -openssl = "0.10.64" -thiserror = "2.0.3" diff --git a/tacd/build.rs b/tacd/build.rs deleted file mode 100644 index 75370d7..0000000 --- a/tacd/build.rs +++ /dev/null @@ -1,40 +0,0 @@ -use std::env; -use std::path::PathBuf; - -macro_rules! set_rustc_env_var { - ($name: expr, $value: expr) => {{ - println!("cargo:rustc-env={}={}", $name, $value); - }}; -} - -macro_rules! set_env_var_if_absent { - ($name: expr, $default_value: expr) => {{ - if let Err(_) = env::var($name) { - set_rustc_env_var!($name, $default_value); - } - }}; -} - -macro_rules! set_specific_path_if_absent { - ($env_name: expr, $env_default: expr, $name: expr, $default_value: expr) => {{ - let prefix = env::var($env_name).unwrap_or(String::from($env_default)); - let mut value = PathBuf::new(); - value.push(prefix); - value.push($default_value); - set_env_var_if_absent!($name, value.to_str().unwrap()); - }}; -} - -macro_rules! set_runstate_path_if_absent { - ($name: expr, $default_value: expr) => {{ - set_specific_path_if_absent!("RUNSTATEDIR", "/run", $name, $default_value); - }}; -} - -fn main() { - if let Ok(target) = env::var("TARGET") { - println!("cargo:rustc-env=TACD_TARGET={target}"); - }; - - set_runstate_path_if_absent!("TACD_DEFAULT_PID_FILE", "tacd.pid"); -} diff --git a/tacd/src/main.rs b/tacd/src/main.rs deleted file mode 100644 index 0890fe8..0000000 --- a/tacd/src/main.rs +++ /dev/null @@ -1,224 +0,0 @@ -#[cfg(feature = "crypto_openssl")] -mod openssl_server; - -#[cfg(feature = "crypto_openssl")] -use crate::openssl_server::start as server_start; -use acme_common::crypto::{get_lib_name, get_lib_version, HashFunction, KeyType, X509Certificate}; -use acme_common::logs::{set_log_system, DEFAULT_LOG_LEVEL}; -use acme_common::{clean_pid_file, to_idna}; -use anyhow::{anyhow, Result}; -use clap::builder::PossibleValuesParser; -use clap::{Arg, ArgAction, ArgMatches, Command}; -use log::{debug, error, info}; -use std::fs::File; -use std::io::{self, Read}; - -const APP_NAME: &str = env!("CARGO_PKG_NAME"); -const APP_VERSION: &str = env!("CARGO_PKG_VERSION"); -const DEFAULT_PID_FILE: &str = env!("TACD_DEFAULT_PID_FILE"); -const DEFAULT_LISTEN_ADDR: &str = "127.0.0.1:5001"; -const DEFAULT_CRT_KEY_TYPE: KeyType = KeyType::EcdsaP256; -const DEFAULT_CRT_DIGEST: HashFunction = HashFunction::Sha256; -const ALPN_ACME_PROTO_NAME: &[u8] = b"\x0aacme-tls/1"; - -fn read_line(path: Option<&String>) -> Result { - let mut input = String::new(); - match path { - Some(p) => File::open(p)?.read_to_string(&mut input)?, - None => io::stdin().read_line(&mut input)?, - }; - let line = input.trim().to_string(); - Ok(line) -} - -fn get_acme_value(cnf: &ArgMatches, opt: &str, opt_file: &str) -> Result { - match cnf.get_one::(opt) { - Some(v) => Ok(v.to_string()), - None => { - debug!( - "reading {opt} from {}", - cnf.get_one::(opt_file) - .map(|e| e.as_str()) - .unwrap_or("stdin") - ); - read_line(cnf.get_one::(opt_file)) - } - } -} - -fn init(cnf: &ArgMatches) -> Result<()> { - acme_common::init_server( - cnf.get_flag("foreground"), - cnf.get_one::("pid-file").map(|e| e.as_str()), - ); - let domain = get_acme_value(cnf, "domain", "domain-file")?; - let domain = to_idna(&domain).map_err(|e| anyhow!(e))?; - let ext = get_acme_value(cnf, "acme-ext", "acme-ext-file")?; - let listen_addr = cnf - .get_one::("listen") - .map(|e| e.as_str()) - .unwrap_or(DEFAULT_LISTEN_ADDR); - let crt_signature_alg = match cnf.get_one::<&str>("crt-signature-alg") { - Some(alg) => alg - .parse() - .map_err(|e: acme_common::error::Error| anyhow!(e))?, - None => DEFAULT_CRT_KEY_TYPE, - }; - let crt_digest = match cnf.get_one::<&str>("crt-digest") { - Some(alg) => alg - .parse() - .map_err(|e: acme_common::error::Error| anyhow!(e))?, - None => DEFAULT_CRT_DIGEST, - }; - let (pk, cert) = X509Certificate::from_acme_ext(&domain, &ext, crt_signature_alg, crt_digest) - .map_err(|e| anyhow!(e))?; - info!("starting {APP_NAME} on {listen_addr} for {domain}"); - server_start(listen_addr, &cert, &pk)?; - Ok(()) -} - -fn main() { - let full_version = format!( - "{APP_VERSION} built for {}\n\nCryptographic library:\n - {} {}", - env!("TACD_TARGET"), - get_lib_name(), - get_lib_version(), - ); - let default_crt_key_type = DEFAULT_CRT_KEY_TYPE.to_string(); - let default_crt_digest = DEFAULT_CRT_DIGEST.to_string(); - let default_log_level = DEFAULT_LOG_LEVEL.to_string().to_lowercase(); - let matches = Command::new(APP_NAME) - .version(APP_VERSION) - .long_version(full_version) - .arg( - Arg::new("listen") - .long("listen") - .short('l') - .help("Host and port to listen on") - .num_args(1) - .value_name("host:port|unix:path") - .default_value(DEFAULT_LISTEN_ADDR), - ) - .arg( - Arg::new("domain") - .long("domain") - .short('d') - .help("The domain that is being validated") - .num_args(1) - .value_name("STRING") - .conflicts_with("domain-file"), - ) - .arg( - Arg::new("domain-file") - .long("domain-file") - .help("File from which is read the domain that is being validated") - .num_args(1) - .value_name("FILE") - .conflicts_with("domain"), - ) - .arg( - Arg::new("acme-ext") - .long("acme-ext") - .short('e') - .help("The acmeIdentifier extension to set in the self-signed certificate") - .num_args(1) - .value_name("STRING") - .conflicts_with("acme-ext-file"), - ) - .arg( - Arg::new("acme-ext-file") - .long("acme-ext-file") - .help("File from which is read the acmeIdentifier extension to set in the self-signed certificate") - .num_args(1) - .value_name("FILE") - .conflicts_with("acme-ext"), - ) - .arg( - Arg::new("crt-signature-alg") - .long("crt-signature-alg") - .help("The certificate's signature algorithm") - .num_args(1) - .value_name("STRING") - .value_parser(PossibleValuesParser::new(KeyType::list_possible_values())) - .default_value(default_crt_key_type), - ) - .arg( - Arg::new("crt-digest") - .long("crt-digest") - .help("The certificate's digest algorithm") - .num_args(1) - .value_name("STRING") - .value_parser(PossibleValuesParser::new(HashFunction::list_possible_values())) - .default_value(default_crt_digest), - ) - .arg( - Arg::new("log-level") - .long("log-level") - .help("Specify the log level") - .num_args(1) - .value_name("LEVEL") - .value_parser(["error", "warn", "info", "debug", "trace"]) - .default_value(default_log_level), - ) - .arg( - Arg::new("to-syslog") - .long("log-syslog") - .help("Sends log messages via syslog") - .conflicts_with("to-stderr") - .action(ArgAction::SetTrue), - ) - .arg( - Arg::new("to-stderr") - .long("log-stderr") - .help("Prints log messages to the standard error output") - .conflicts_with("to-syslog") - .action(ArgAction::SetTrue), - ) - .arg( - Arg::new("foreground") - .long("foreground") - .short('f') - .help("Runs in the foreground") - .action(ArgAction::SetTrue), - ) - .arg( - Arg::new("pid-file") - .long("pid-file") - .help("Path to the PID file") - .num_args(1) - .value_name("FILE") - .default_value(DEFAULT_PID_FILE) - .default_value_if("no-pid-file", clap::builder::ArgPredicate::IsPresent, None) - .conflicts_with("no-pid-file"), - ) - .arg( - Arg::new("no-pid-file") - .long("no-pid-file") - .help("Do not create any PID file") - .conflicts_with("pid-file") - .action(ArgAction::SetTrue), - ) - .get_matches(); - - match set_log_system( - matches.get_one::("log-level").map(|e| e.as_str()), - matches.get_flag("to-syslog"), - matches.get_flag("to-stderr"), - ) { - Ok(_) => {} - Err(e) => { - eprintln!("Error: {e}"); - std::process::exit(2); - } - }; - - match init(&matches) { - Ok(_) => {} - Err(e) => { - error!("{e}"); - let pid_file = matches.get_one::("pid-file").map(|e| e.as_str()); - let _ = clean_pid_file(pid_file); - std::process::exit(1); - } - }; -} diff --git a/tacd/src/openssl_server.rs b/tacd/src/openssl_server.rs deleted file mode 100644 index 7e57d92..0000000 --- a/tacd/src/openssl_server.rs +++ /dev/null @@ -1,48 +0,0 @@ -use acme_common::crypto::{KeyPair, X509Certificate}; -use anyhow::{bail, Result}; -use log::debug; -use openssl::ssl::{self, AlpnError, SslAcceptor, SslMethod}; -use std::net::TcpListener; -use std::sync::Arc; -use std::thread; - -#[cfg(target_family = "unix")] -use std::os::unix::net::UnixListener; - -const ALPN_ERROR: AlpnError = AlpnError::ALERT_FATAL; - -macro_rules! listen_and_accept { - ($lt: ident, $addr: ident, $acceptor: ident) => { - let listener = $lt::bind($addr)?; - for stream in listener.incoming() { - if let Ok(stream) = stream { - let acceptor = $acceptor.clone(); - thread::spawn(move || { - debug!("new client"); - let _ = acceptor.accept(stream).unwrap(); - }); - }; - } - }; -} - -pub fn start(listen_addr: &str, certificate: &X509Certificate, key_pair: &KeyPair) -> Result<()> { - let mut acceptor = SslAcceptor::mozilla_intermediate(SslMethod::tls())?; - acceptor.set_alpn_select_callback(|_, client| { - debug!("ALPN negociation"); - ssl::select_next_proto(crate::ALPN_ACME_PROTO_NAME, client).ok_or(ALPN_ERROR) - }); - acceptor.set_private_key(&key_pair.inner_key)?; - acceptor.set_certificate(&certificate.inner_cert)?; - acceptor.check_private_key()?; - let acceptor = Arc::new(acceptor.build()); - if cfg!(unix) && listen_addr.starts_with("unix:") { - let listen_addr = &listen_addr[5..]; - debug!("listening on unix socket {listen_addr}"); - listen_and_accept!(UnixListener, listen_addr, acceptor); - } else { - debug!("listening on {listen_addr}"); - listen_and_accept!(TcpListener, listen_addr, acceptor); - } - bail!("main thread loop unexpectedly exited") -}