From 7c99bc1f15b44973675e0ccd4bfbe82a51732c73 Mon Sep 17 00:00:00 2001 From: Pierre Hubert Date: Thu, 27 Apr 2023 19:17:05 +0200 Subject: [PATCH] Redirect user for authentication --- Cargo.lock | 580 ++++++++++++++++++++++++++++++++++++++- Cargo.toml | 12 +- src/app_config.rs | 58 ++++ src/crypto_wrapper.rs | 97 +++++++ src/lib.rs | 10 + src/main.rs | 92 +++---- src/openid_primitives.rs | 24 ++ src/remote_ip.rs | 202 ++++++++++++++ src/state_manager.rs | 40 +++ src/time_utils.rs | 9 + 10 files changed, 1067 insertions(+), 57 deletions(-) create mode 100644 src/app_config.rs create mode 100644 src/crypto_wrapper.rs create mode 100644 src/lib.rs create mode 100644 src/openid_primitives.rs create mode 100644 src/remote_ip.rs create mode 100644 src/state_manager.rs create mode 100644 src/time_utils.rs diff --git a/Cargo.lock b/Cargo.lock index f681ad2..71c2f64 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -189,6 +189,41 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" +[[package]] +name = "aead" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" +dependencies = [ + "crypto-common", + "generic-array", +] + +[[package]] +name = "aes" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "433cfd6710c9986c576a25ca913c39d66a6474107b406f34f91d4a8923395241" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures", +] + +[[package]] +name = "aes-gcm" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82e1366e0c69c9f927b1fa5ce2c7bf9eafc8f9268c0b9800729e8b267612447c" +dependencies = [ + "aead", + "aes", + "cipher", + "ctr", + "ghash", + "subtle", +] + [[package]] name = "ahash" version = "0.7.6" @@ -341,6 +376,25 @@ dependencies = [ "serde", ] +[[package]] +name = "bincode" +version = "2.0.0-rc.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f11ea1a0346b94ef188834a65c068a03aec181c94896d481d7a0a40d85b0ce95" +dependencies = [ + "bincode_derive", + "serde", +] + +[[package]] +name = "bincode_derive" +version = "2.0.0-rc.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e30759b3b99a1b802a7a3aa21c85c3ded5c28e1c83170d82d70f08bbf7f3e4c" +dependencies = [ + "virtue", +] + [[package]] name = "bitflags" version = "1.3.2" @@ -377,6 +431,12 @@ dependencies = [ "alloc-stdlib", ] +[[package]] +name = "bumpalo" +version = "3.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b1ce199063694f33ffb7dd4e0ee620741495c32833cde5aa08f02a0bf96f0c8" + [[package]] name = "bytes" version = "1.4.0" @@ -407,6 +467,16 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "cipher" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +dependencies = [ + "crypto-common", + "inout", +] + [[package]] name = "clap" version = "4.2.4" @@ -472,6 +542,22 @@ dependencies = [ "version_check", ] +[[package]] +name = "core-foundation" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" + [[package]] name = "cpufeatures" version = "0.2.7" @@ -497,9 +583,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" dependencies = [ "generic-array", + "rand_core", "typenum", ] +[[package]] +name = "ctr" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835" +dependencies = [ + "cipher", +] + [[package]] name = "derive_more" version = "0.99.17" @@ -566,6 +662,15 @@ dependencies = [ "libc", ] +[[package]] +name = "fastrand" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be" +dependencies = [ + "instant", +] + [[package]] name = "flate2" version = "1.0.25" @@ -582,6 +687,21 @@ 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.1.0" @@ -591,12 +711,32 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "futures-channel" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "955518d47e09b25bbebc7a18df10b81f0c766eaf4c4f1cccef2fca5f2a4fb5f2" +dependencies = [ + "futures-core", +] + [[package]] name = "futures-core" version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c" +[[package]] +name = "futures-macro" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.15", +] + [[package]] name = "futures-sink" version = "0.3.28" @@ -616,9 +756,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26b01e40b772d54cf6c6d721c1d1abd0647a0106a12ecaa1c186273392a69533" dependencies = [ "futures-core", + "futures-macro", "futures-task", "pin-project-lite", "pin-utils", + "slab", ] [[package]] @@ -642,6 +784,16 @@ dependencies = [ "wasi", ] +[[package]] +name = "ghash" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d930750de5717d2dd0b8c0d42c076c0e884c81a73e6cab859bbd2339c71e3e40" +dependencies = [ + "opaque-debug", + "polyval", +] + [[package]] name = "h2" version = "0.3.18" @@ -699,6 +851,17 @@ dependencies = [ "itoa", ] +[[package]] +name = "http-body" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1" +dependencies = [ + "bytes", + "http", + "pin-project-lite", +] + [[package]] name = "httparse" version = "1.8.0" @@ -726,6 +889,43 @@ version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" +[[package]] +name = "hyper" +version = "0.14.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab302d72a6f11a3b910431ff93aae7e773078c769f0a3ef15fb9ec692ed147d4" +dependencies = [ + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", + "want", +] + +[[package]] +name = "hyper-tls" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" +dependencies = [ + "bytes", + "hyper", + "native-tls", + "tokio", + "tokio-native-tls", +] + [[package]] name = "idna" version = "0.3.0" @@ -746,6 +946,24 @@ dependencies = [ "hashbrown", ] +[[package]] +name = "inout" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" +dependencies = [ + "generic-array", +] + +[[package]] +name = "instant" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" +dependencies = [ + "cfg-if", +] + [[package]] name = "io-lifetimes" version = "1.0.10" @@ -757,6 +975,12 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "ipnet" +version = "2.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12b6ee2129af8d4fb011108c73d99a1b83a85977f23b82460c0ae2e25bb4b57f" + [[package]] name = "is-terminal" version = "0.4.7" @@ -784,6 +1008,15 @@ dependencies = [ "libc", ] +[[package]] +name = "js-sys" +version = "0.3.61" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "445dde2150c55e483f3d8416706b97ec8e8237c307e5b7b4b8dd15e6af2a0730" +dependencies = [ + "wasm-bindgen", +] + [[package]] name = "language-tags" version = "0.3.2" @@ -900,6 +1133,24 @@ dependencies = [ "windows-sys 0.45.0", ] +[[package]] +name = "native-tls" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07226173c32f2926027b63cce4bcd8076c3552846cbe7925f3aaffeac0a3b92e" +dependencies = [ + "lazy_static", + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", +] + [[package]] name = "nom" version = "7.1.3" @@ -934,11 +1185,19 @@ name = "oidc-test-client" version = "0.1.0" dependencies = [ "actix-web", + "aes-gcm", "askama", + "base64", + "bincode", "clap", "env_logger", + "futures-util", "lazy_static", "log", + "rand", + "reqwest", + "serde", + "urlencoding", ] [[package]] @@ -947,6 +1206,56 @@ version = "1.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3" +[[package]] +name = "opaque-debug" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" + +[[package]] +name = "openssl" +version = "0.10.52" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01b8574602df80f7b85fdfc5392fa884a4e3b3f4f35402c070ab34c3d3f78d56" +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 2.0.15", +] + +[[package]] +name = "openssl-probe" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" + +[[package]] +name = "openssl-sys" +version = "0.9.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e17f59264b2809d77ae94f0e1ebabc434773f370d6ca667bd223ea10e06cc7e" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + [[package]] name = "parking_lot" version = "0.12.1" @@ -965,7 +1274,7 @@ checksum = "9069cbb9f99e3a5083476ccb29ceb1de18b9118cafa53e90c9551235de2b9521" dependencies = [ "cfg-if", "libc", - "redox_syscall", + "redox_syscall 0.2.16", "smallvec", "windows-sys 0.45.0", ] @@ -1000,6 +1309,18 @@ version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ac9a59f73473f1b8d852421e59e64809f025994837ef743615c6d0c5b305160" +[[package]] +name = "polyval" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ef234e08c11dfcb2e56f79fd70f6f2eb7f025c0ce2333e82f4f0518ecad30c6" +dependencies = [ + "cfg-if", + "cpufeatures", + "opaque-debug", + "universal-hash", +] + [[package]] name = "ppv-lite86" version = "0.2.17" @@ -1063,6 +1384,15 @@ dependencies = [ "bitflags", ] +[[package]] +name = "redox_syscall" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" +dependencies = [ + "bitflags", +] + [[package]] name = "regex" version = "1.8.1" @@ -1080,6 +1410,43 @@ version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a5996294f19bd3aae0453a862ad728f60e6600695733dd5df01da90c54363a3c" +[[package]] +name = "reqwest" +version = "0.11.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27b71749df584b7f4cac2c426c127a7c785a5106cc98f7a8feb044115f0fa254" +dependencies = [ + "base64", + "bytes", + "encoding_rs", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "hyper", + "hyper-tls", + "ipnet", + "js-sys", + "log", + "mime", + "native-tls", + "once_cell", + "percent-encoding", + "pin-project-lite", + "serde", + "serde_json", + "serde_urlencoded", + "tokio", + "tokio-native-tls", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "winreg", +] + [[package]] name = "rustc_version" version = "0.4.0" @@ -1109,12 +1476,44 @@ version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f91339c0467de62360649f8d3e185ca8de4224ff281f66000de5eb2a77a79041" +[[package]] +name = "schannel" +version = "0.1.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "713cfb06c7059f3588fb8044c0fad1d09e3c01d225e25b9220dbfdcf16dbb1b3" +dependencies = [ + "windows-sys 0.42.0", +] + [[package]] name = "scopeguard" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" +[[package]] +name = "security-framework" +version = "2.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a332be01508d814fed64bf28f798a146d73792121129962fdf335bb3c49a4254" +dependencies = [ + "bitflags", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31c9bb296072e961fcbd8853511dd39c2d8be2deb1e17c6860b1d30732b323b4" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "semver" version = "1.0.17" @@ -1215,6 +1614,12 @@ version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" +[[package]] +name = "subtle" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" + [[package]] name = "syn" version = "1.0.109" @@ -1237,6 +1642,19 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "tempfile" +version = "3.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9fbec84f381d5795b08656e4912bec604d162bff9291d6189a78f4c8ab87998" +dependencies = [ + "cfg-if", + "fastrand", + "redox_syscall 0.3.5", + "rustix", + "windows-sys 0.45.0", +] + [[package]] name = "termcolor" version = "1.2.0" @@ -1305,6 +1723,16 @@ dependencies = [ "windows-sys 0.48.0", ] +[[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-util" version = "0.7.8" @@ -1319,6 +1747,12 @@ dependencies = [ "tracing", ] +[[package]] +name = "tower-service" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" + [[package]] name = "tracing" version = "0.1.38" @@ -1339,6 +1773,12 @@ dependencies = [ "once_cell", ] +[[package]] +name = "try-lock" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed" + [[package]] name = "typenum" version = "1.16.0" @@ -1375,6 +1815,16 @@ dependencies = [ "tinyvec", ] +[[package]] +name = "universal-hash" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d3160b73c9a19f7e2939a2fdad446c57c1bbbbf4d919d3213ff1267a580d8b5" +dependencies = [ + "crypto-common", + "subtle", +] + [[package]] name = "url" version = "2.3.1" @@ -1386,24 +1836,128 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "urlencoding" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8db7427f936968176eaa7cdf81b7f98b980b18495ec28f1b5791ac3bfe3eea9" + [[package]] name = "utf8parse" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + [[package]] name = "version_check" version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +[[package]] +name = "virtue" +version = "0.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9dcc60c0624df774c82a0ef104151231d37da4962957d691c011c852b2473314" + +[[package]] +name = "want" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0" +dependencies = [ + "log", + "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.84" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31f8dcbc21f30d9b8f2ea926ecb58f6b91192c17e9d33594b3df58b2007ca53b" +dependencies = [ + "cfg-if", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.84" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95ce90fd5bcc06af55a641a86428ee4229e44e07033963a2290a8e241607ccb9" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn 1.0.109", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f219e0d211ba40266969f6dbdd90636da12f75bee4fc9d6c23d1260dadb51454" +dependencies = [ + "cfg-if", + "js-sys", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.84" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c21f77c0bedc37fd5dc21f897894a5ca01e7bb159884559461862ae90c0b4c5" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.84" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2aff81306fcac3c7515ad4e177f521b5c9a15f2b08f4e32d823066102f35a5f6" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.84" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0046fef7e28c3804e5e38bfa31ea2a0f73905319b677e57ebe37e49358989b5d" + +[[package]] +name = "web-sys" +version = "0.3.61" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e33b99f4b23ba3eec1a53ac264e35a755f00e966e0065077d6027c0f575b0b97" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + [[package]] name = "winapi" version = "0.3.9" @@ -1435,6 +1989,21 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "windows-sys" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" +dependencies = [ + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", +] + [[package]] name = "windows-sys" version = "0.45.0" @@ -1567,6 +2136,15 @@ version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" +[[package]] +name = "winreg" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80d0f4e272c85def139476380b12f9ac60926689dd2e01d4923222f40580869d" +dependencies = [ + "winapi", +] + [[package]] name = "zstd" version = "0.12.3+zstd.1.5.2" diff --git a/Cargo.toml b/Cargo.toml index 64b35a3..cd6f9d5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,7 +8,15 @@ edition = "2021" [dependencies] log = "0.4.17" env_logger = "0.10.0" -clap = {version="4.2.4", features=["derive", "env"]} +clap = { version = "4.2.4", features = ["derive", "env"] } lazy_static = "1.4.0" actix-web = "4.3.1" -askama = "0.12.0" \ No newline at end of file +askama = "0.12.0" +serde = { version = "1.0.160", features = ["derive"] } +reqwest = { version = "0.11.16", features = ["json"] } +urlencoding = "2.1.2" +futures-util = "0.3.28" +aes-gcm = "0.10.1" +base64 = "0.21.0" +rand = "0.8.5" +bincode = {version="2.0.0-rc.3",features=["serde"]} \ No newline at end of file diff --git a/src/app_config.rs b/src/app_config.rs new file mode 100644 index 0000000..5a2dc5b --- /dev/null +++ b/src/app_config.rs @@ -0,0 +1,58 @@ +use clap::Parser; + +const REDIRECT_URI: &str = "/redirect"; + +/// Basic OpenID test client +#[derive(Parser, Debug)] +pub struct AppConfig { + /// Listen URL + #[arg(short, long, env, default_value = "0.0.0.0:7510")] + pub listen_addr: String, + + /// Public URL, the URL where this service is accessible, without the trailing slash + #[arg(short, long, env, default_value = "http://localhost:7510")] + pub public_url: String, + + /// URL where the OpenID configuration can be found + #[arg(short, long, env)] + pub configuration_url: String, + + /// OpenID client ID + #[arg(long, env)] + pub client_id: String, + + /// OpenID client secret + #[arg(long, env)] + pub client_secret: String, + + /// Proxy IP, might end with a "*" + #[clap(long, env)] + pub proxy_ip: Option, +} + +impl AppConfig { + pub fn get() -> &'static Self { + &CONF + } + + pub fn redirect_url(&self) -> String { + format!("{}{}", self.public_url, REDIRECT_URI) + } +} + +lazy_static::lazy_static! { + static ref CONF: AppConfig = { + AppConfig::parse() + }; +} + +#[cfg(test)] +mod test { + use crate::app_config::AppConfig; + + #[test] + fn verify_cli() { + use clap::CommandFactory; + AppConfig::command().debug_assert(); + } +} diff --git a/src/crypto_wrapper.rs b/src/crypto_wrapper.rs new file mode 100644 index 0000000..e7fef4b --- /dev/null +++ b/src/crypto_wrapper.rs @@ -0,0 +1,97 @@ +use std::io::ErrorKind; + +use crate::Res; +use aes_gcm::aead::{Aead, OsRng}; +use aes_gcm::{Aes256Gcm, Key, KeyInit, Nonce}; +use base64::engine::general_purpose::STANDARD as BASE64_STANDARD; +use base64::Engine as _; +use bincode::{Decode, Encode}; +use rand::Rng; + +const NONCE_LEN: usize = 12; + +pub struct CryptoWrapper { + key: Key, +} + +impl CryptoWrapper { + /// Generate a new memory wrapper + pub fn new_random() -> Self { + Self { + key: Aes256Gcm::generate_key(&mut OsRng), + } + } + + /// Encrypt some data + pub fn encrypt(&self, data: &T) -> Res { + let aes_key = Aes256Gcm::new(&self.key); + let nonce_bytes = rand::thread_rng().gen::<[u8; NONCE_LEN]>(); + + let serialized_data = bincode::encode_to_vec(data, bincode::config::standard())?; + + let mut enc = aes_key + .encrypt(Nonce::from_slice(&nonce_bytes), serialized_data.as_slice()) + .unwrap(); + enc.extend_from_slice(&nonce_bytes); + + Ok(BASE64_STANDARD.encode(enc)) + } + + /// Decrypt some data previously encrypted using the [`CryptoWrapper::encrypt`] method + pub fn decrypt(&self, input: &str) -> Res { + let bytes = BASE64_STANDARD.decode(input)?; + + if bytes.len() < NONCE_LEN { + return Err(Box::new(std::io::Error::new( + ErrorKind::Other, + "Input string is smaller than nonce!", + ))); + } + + let (enc, nonce) = bytes.split_at(bytes.len() - NONCE_LEN); + assert_eq!(nonce.len(), NONCE_LEN); + + let aes_key = Aes256Gcm::new(&self.key); + + let dec = match aes_key.decrypt(Nonce::from_slice(nonce), enc) { + Ok(d) => d, + Err(e) => { + log::error!("Failed to decrypt wrapped data! {:#?}", e); + return Err(Box::new(std::io::Error::new( + ErrorKind::Other, + "Failed to decrypt wrapped data!", + ))); + } + }; + + Ok(bincode::decode_from_slice(&dec, bincode::config::standard())?.0) + } +} + +#[cfg(test)] +mod test { + use crate::crypto_wrapper::CryptoWrapper; + use bincode::{Decode, Encode}; + + #[derive(Encode, Decode, Eq, PartialEq, Debug)] + struct Message(String); + + #[test] + fn encrypt_and_decrypt() { + let wrapper = CryptoWrapper::new_random(); + let msg = Message("Pierre was here".to_string()); + let enc = wrapper.encrypt(&msg).unwrap(); + let dec: Message = wrapper.decrypt(&enc).unwrap(); + + assert_eq!(dec, msg) + } + + #[test] + fn encrypt_and_decrypt_invalid() { + let wrapper_1 = CryptoWrapper::new_random(); + let wrapper_2 = CryptoWrapper::new_random(); + let msg = Message("Pierre was here".to_string()); + let enc = wrapper_1.encrypt(&msg).unwrap(); + wrapper_2.decrypt::(&enc).unwrap_err(); + } +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..1def6df --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,10 @@ +use std::error::Error; + +pub type Res = Result>; + +pub mod app_config; +pub mod crypto_wrapper; +pub mod openid_primitives; +pub mod remote_ip; +pub mod state_manager; +pub mod time_utils; diff --git a/src/main.rs b/src/main.rs index 371026b..1663a2a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,42 +1,10 @@ use actix_web::{get, App, HttpResponse, HttpServer}; use askama::Template; -use clap::Parser; -/// Basic OpenID test client -#[derive(Parser, Debug)] -struct AppConfig { - /// Listen URL - #[arg(short, long, env, default_value = "0.0.0.0:7510")] - listen_addr: String, - - /// Public URL, the URL where this service is accessible, without the trailing slash - #[arg(short, long, env, default_value = "http://localhost:7510")] - public_url: String, - - /// URL where the OpenID configuration can be found - #[arg(short, long, env)] - configuration_url: String, - - /// OpenID client ID - #[arg(long, env)] - client_id: String, - - /// OpenID client secret - #[arg(long, env)] - client_secret: String, -} - -impl AppConfig { - pub fn redirect_url(&self) -> String { - format!("{}/redirect", self.public_url) - } -} - -lazy_static::lazy_static! { - static ref CONF: AppConfig = { - AppConfig::parse() - }; -} +use oidc_test_client::app_config::AppConfig; +use oidc_test_client::openid_primitives::OpenIDConfig; +use oidc_test_client::remote_ip::RemoteIP; +use oidc_test_client::state_manager::StateManager; #[get("/assets/bootstrap.min.css")] async fn bootstrap() -> HttpResponse { @@ -62,32 +30,48 @@ struct HomeTemplate { async fn home() -> HttpResponse { HttpResponse::Ok().content_type("text/html").body( HomeTemplate { - redirect_url: CONF.redirect_url(), + redirect_url: AppConfig::get().redirect_url(), } .render() .unwrap(), ) } +#[get("/start")] +async fn start(remote_ip: RemoteIP) -> HttpResponse { + let config = OpenIDConfig::load_from(&AppConfig::get().configuration_url) + .await + .expect("Failed to load provider configuration!"); + + let authorization_url = config.authorization_url( + &AppConfig::get().client_id, + &StateManager::gen_state(&remote_ip).expect("Failed to generate state!"), + &AppConfig::get().redirect_url(), + ); + + HttpResponse::Found() + .append_header(("Location", authorization_url)) + .finish() +} + #[actix_web::main] async fn main() -> std::io::Result<()> { env_logger::init_from_env(env_logger::Env::new().default_filter_or("info")); - log::info!("Will listen on {}", CONF.listen_addr); - HttpServer::new(|| App::new().service(bootstrap).service(cover).service(home)) - .bind(&CONF.listen_addr) - .expect("Failed to bind server!") - .run() - .await -} - -#[cfg(test)] -mod test { - use crate::AppConfig; - - #[test] - fn verify_cli() { - use clap::CommandFactory; - AppConfig::command().debug_assert(); - } + log::info!("Init state manager"); + StateManager::init(); + + log::info!("Will listen on {}", AppConfig::get().listen_addr); + + HttpServer::new(|| { + App::new() + .service(bootstrap) + .service(cover) + .service(home) + .service(start) + }) + .bind(&AppConfig::get().listen_addr) + .expect("Failed to bind server!") + .run() + .await } diff --git a/src/openid_primitives.rs b/src/openid_primitives.rs new file mode 100644 index 0000000..bfc4b40 --- /dev/null +++ b/src/openid_primitives.rs @@ -0,0 +1,24 @@ +use crate::Res; + +#[derive(Debug, Clone, serde::Deserialize)] +pub struct OpenIDConfig { + pub authorization_endpoint: String, + pub token_endpoint: String, + pub userinfo_endpoint: String, +} + +impl OpenIDConfig { + /// Load OpenID configuration from a given URL + pub async fn load_from(url: &str) -> Res { + Ok(reqwest::get(url).await?.json().await?) + } + + /// Get the authorization URL where a user should be redirect + pub fn authorization_url(&self, client_id: &str, state: &str, redirect_uri: &str) -> String { + let client_id = urlencoding::encode(client_id); + let state = urlencoding::encode(state); + let redirect_uri = urlencoding::encode(redirect_uri); + + format!("{}?response_type=code&scope=openid%20profile%20email&client_id={client_id}&state={state}&redirect_uri={redirect_uri}", self.authorization_endpoint) + } +} diff --git a/src/remote_ip.rs b/src/remote_ip.rs new file mode 100644 index 0000000..31b8706 --- /dev/null +++ b/src/remote_ip.rs @@ -0,0 +1,202 @@ +use std::net::{IpAddr, Ipv6Addr}; + +use crate::app_config::AppConfig; +use actix_web::dev::Payload; +use actix_web::{Error, FromRequest, HttpRequest}; +use futures_util::future::{ready, Ready}; +use std::str::FromStr; + +/// Parse an IP address +pub fn parse_ip(ip: &str) -> Option { + let mut ip = match IpAddr::from_str(ip) { + Ok(ip) => ip, + Err(e) => { + log::warn!("Failed to parse an IP address: {}", e); + return None; + } + }; + + if let IpAddr::V6(ipv6) = &mut ip { + let mut octets = ipv6.octets(); + for o in octets.iter_mut().skip(8) { + *o = 0; + } + ip = IpAddr::V6(Ipv6Addr::from(octets)); + } + + Some(ip) +} + +/// Check if two ips matches +pub fn match_ip(pattern: &str, ip: &str) -> bool { + if pattern.eq(ip) { + return true; + } + + if pattern.ends_with('*') && ip.starts_with(&pattern.replace('*', "")) { + return true; + } + + false +} + +/// Get the remote IP address +pub fn get_remote_ip(req: &HttpRequest, proxy_ip: Option<&str>) -> IpAddr { + let mut ip = req.peer_addr().unwrap().ip(); + + // We check if the request comes from a trusted reverse proxy + if let Some(proxy) = proxy_ip.as_ref() { + if match_ip(proxy, &ip.to_string()) { + if let Some(header) = req.headers().get("X-Forwarded-For") { + let header = header.to_str().unwrap(); + + let remote_ip = if let Some((upstream_ip, _)) = header.split_once(',') { + upstream_ip + } else { + header + }; + + if let Some(upstream_ip) = parse_ip(remote_ip) { + ip = upstream_ip; + } + } + } + } + + ip +} + +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub struct RemoteIP(pub IpAddr); + +impl From for IpAddr { + fn from(i: RemoteIP) -> Self { + i.0 + } +} + +impl FromRequest for RemoteIP { + type Error = Error; + type Future = Ready>; + + #[inline] + fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future { + ready(Ok(RemoteIP(get_remote_ip( + req, + AppConfig::get().proxy_ip.as_deref(), + )))) + } +} + +#[cfg(test)] +mod test { + use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr}; + use std::str::FromStr; + + use crate::remote_ip::{get_remote_ip, parse_ip}; + use actix_web::test::TestRequest; + + #[test] + fn test_get_remote_ip() { + let req = TestRequest::default() + .peer_addr(SocketAddr::from_str("192.168.1.1:1000").unwrap()) + .to_http_request(); + assert_eq!( + get_remote_ip(&req, None), + "192.168.1.1".parse::().unwrap() + ); + } + + #[test] + fn test_get_remote_ip_from_proxy() { + let req = TestRequest::default() + .peer_addr(SocketAddr::from_str("192.168.1.1:1000").unwrap()) + .insert_header(("X-Forwarded-For", "1.1.1.1")) + .to_http_request(); + assert_eq!( + get_remote_ip(&req, Some("192.168.1.1")), + "1.1.1.1".parse::().unwrap() + ); + } + + #[test] + fn test_get_remote_ip_from_proxy_2() { + let req = TestRequest::default() + .peer_addr(SocketAddr::from_str("192.168.1.1:1000").unwrap()) + .insert_header(("X-Forwarded-For", "1.1.1.1, 1.2.2.2")) + .to_http_request(); + assert_eq!( + get_remote_ip(&req, Some("192.168.1.1")), + "1.1.1.1".parse::().unwrap() + ); + } + + #[test] + fn test_get_remote_ip_from_proxy_ipv6() { + let req = TestRequest::default() + .peer_addr(SocketAddr::from_str("192.168.1.1:1000").unwrap()) + .insert_header(("X-Forwarded-For", "10::1, 1.2.2.2")) + .to_http_request(); + assert_eq!( + get_remote_ip(&req, Some("192.168.1.1")), + "10::".parse::().unwrap() + ); + } + + #[test] + fn test_get_remote_ip_from_no_proxy() { + let req = TestRequest::default() + .peer_addr(SocketAddr::from_str("192.168.1.1:1000").unwrap()) + .insert_header(("X-Forwarded-For", "1.1.1.1, 1.2.2.2")) + .to_http_request(); + assert_eq!( + get_remote_ip(&req, None), + "192.168.1.1".parse::().unwrap() + ); + } + + #[test] + fn test_get_remote_ip_from_other_proxy() { + let req = TestRequest::default() + .peer_addr(SocketAddr::from_str("192.168.1.1:1000").unwrap()) + .insert_header(("X-Forwarded-For", "1.1.1.1, 1.2.2.2")) + .to_http_request(); + assert_eq!( + get_remote_ip(&req, Some("192.168.1.2")), + "192.168.1.1".parse::().unwrap() + ); + } + + #[test] + fn parse_bad_ip() { + let ip = parse_ip("badbad"); + assert_eq!(None, ip); + } + + #[test] + fn parse_ip_v4_address() { + let ip = parse_ip("192.168.1.1").unwrap(); + assert_eq!(ip, IpAddr::V4(Ipv4Addr::new(192, 168, 1, 1))); + } + + #[test] + fn parse_ip_v6_address() { + let ip = parse_ip("2a00:1450:4007:813::200e").unwrap(); + assert_eq!( + ip, + IpAddr::V6(Ipv6Addr::new(0x2a00, 0x1450, 0x4007, 0x813, 0, 0, 0, 0)) + ); + } + + #[test] + fn parse_ip_v6_address_2() { + let ip = parse_ip("::1").unwrap(); + assert_eq!(ip, IpAddr::V6(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 0))); + } + + #[test] + fn parse_ip_v6_address_3() { + let ip = parse_ip("a::1").unwrap(); + assert_eq!(ip, IpAddr::V6(Ipv6Addr::new(0xa, 0, 0, 0, 0, 0, 0, 0))); + } +} diff --git a/src/state_manager.rs b/src/state_manager.rs new file mode 100644 index 0000000..d15521d --- /dev/null +++ b/src/state_manager.rs @@ -0,0 +1,40 @@ +use crate::crypto_wrapper::CryptoWrapper; +use crate::remote_ip::RemoteIP; +use crate::time_utils::time; +use crate::Res; +use bincode::{Decode, Encode}; +use std::net::IpAddr; + +pub struct StateManager; + +static mut WRAPPER: Option = None; + +#[derive(Encode, Decode, Debug)] +struct State { + ip: IpAddr, + expire: u64, +} + +impl State { + pub fn new(ip: IpAddr) -> Self { + Self { + ip, + expire: time() + 15 * 60, + } + } +} + +impl StateManager { + pub fn init() { + unsafe { + WRAPPER = Some(CryptoWrapper::new_random()); + } + } + + /// Generate a new state + pub fn gen_state(ip: &RemoteIP) -> Res { + let state = State::new(ip.0); + + unsafe { WRAPPER.as_ref().unwrap() }.encrypt(&state) + } +} diff --git a/src/time_utils.rs b/src/time_utils.rs new file mode 100644 index 0000000..adcccfb --- /dev/null +++ b/src/time_utils.rs @@ -0,0 +1,9 @@ +use std::time::{SystemTime, UNIX_EPOCH}; + +/// Get current time since epoch +pub fn time() -> u64 { + SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap() + .as_secs() +}