From 53a540139c70d94ce2be1ce68955084b9eab12fd Mon Sep 17 00:00:00 2001 From: Pierre Hubert Date: Wed, 13 Apr 2022 19:07:58 +0200 Subject: [PATCH] Emit id_token --- Cargo.lock | 603 ++++++++++++++++++++++++++- Cargo.toml | 5 +- src/actors/openid_sessions_actor.rs | 1 + src/constants.rs | 1 + src/controllers/openid_controller.rs | 43 +- src/data/id_token.rs | 42 ++ src/data/jwt_signer.rs | 48 +++ src/data/mod.rs | 4 +- src/data/session_identity.rs | 7 + src/main.rs | 5 + 10 files changed, 734 insertions(+), 25 deletions(-) create mode 100644 src/data/id_token.rs create mode 100644 src/data/jwt_signer.rs diff --git a/Cargo.lock b/Cargo.lock index e4e1de3..6c3a603 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -307,6 +307,12 @@ dependencies = [ "alloc-no-stdlib", ] +[[package]] +name = "anyhow" +version = "1.0.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4361135be9122e0870de935d7c439aef945b9f9ddd4199a553b5270b49c82a27" + [[package]] name = "askama" version = "0.11.1" @@ -366,18 +372,39 @@ dependencies = [ "winapi", ] +[[package]] +name = "autocfg" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dde43e75fd43e8a1bf86103336bc699aa8d17ad1be60c76c0bdfd4828e19b78" +dependencies = [ + "autocfg 1.1.0", +] + [[package]] name = "autocfg" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +[[package]] +name = "base16ct" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "349a06037c7bf932dd7e7d1f653678b2038b9ad46a74102f1fc7bd7872678cce" + [[package]] name = "base64" version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" +[[package]] +name = "base64ct" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6b4d9b1225d28d360ec6a231d65af1fd99a2a095154c8040689617290569c5c" + [[package]] name = "basic-oidc" version = "0.1.0" @@ -392,6 +419,7 @@ dependencies = [ "env_logger", "futures-util", "include_dir", + "jwt-simple", "log", "mime_guess", "rand", @@ -404,14 +432,13 @@ dependencies = [ [[package]] name = "bcrypt" -version = "0.12.1" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fe4fef31efb0f76133ae8e3576a88e58edb7cfc5584c81c758c349ba46b43fc" +checksum = "c8d70a6d9cd7179c1020c7f48512203ffe48cd1a442359e5f81881bf2cc165ac" dependencies = [ "base64", "blowfish", "getrandom", - "zeroize", ] [[package]] @@ -420,6 +447,15 @@ version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" +[[package]] +name = "block-buffer" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" +dependencies = [ + "generic-array", +] + [[package]] name = "block-buffer" version = "0.10.2" @@ -460,6 +496,12 @@ dependencies = [ "alloc-stdlib", ] +[[package]] +name = "bumpalo" +version = "3.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4a45a46ab1f2412e53d3a0ade76ffad2025804294569aae387231a0cd6e0899" + [[package]] name = "byteorder" version = "1.4.3" @@ -545,6 +587,30 @@ dependencies = [ "syn", ] +[[package]] +name = "coarsetime" +version = "0.1.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "454038500439e141804c655b4cd1bc6a70bcb95cd2bc9463af5661b6956f0e46" +dependencies = [ + "libc", + "once_cell", + "wasi 0.11.0+wasi-snapshot-preview1", + "wasm-bindgen", +] + +[[package]] +name = "const-oid" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d6f2aa4d0537bcc1c74df8755072bd31c1ef1a3a1b85a68e8404a8c353b7b8b" + +[[package]] +name = "const-oid" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4c78c047431fee22c1a7bb92e00ad095a02a983affe4d8a72e2a2c62c1b94f3" + [[package]] name = "convert_case" version = "0.4.0" @@ -560,10 +626,10 @@ dependencies = [ "aes-gcm", "base64", "hkdf", - "hmac", + "hmac 0.12.1", "percent-encoding", "rand", - "sha2", + "sha2 0.10.2", "subtle", "time", "version_check", @@ -607,6 +673,29 @@ dependencies = [ "lazy_static", ] +[[package]] +name = "crypto-bigint" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f83bd3bb4314701c568e340cd8cf78c975aa0ca79e03d3f6d1677d5b0c9c0c03" +dependencies = [ + "generic-array", + "rand_core", + "subtle", +] + +[[package]] +name = "crypto-bigint" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03c6a1d5fa1de37e071642dfa44ec552ca5b299adb128fab16138e24b548fd21" +dependencies = [ + "generic-array", + "rand_core", + "subtle", + "zeroize", +] + [[package]] name = "crypto-common" version = "0.1.3" @@ -617,6 +706,22 @@ dependencies = [ "typenum", ] +[[package]] +name = "crypto-mac" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1d1a86f49236c215f271d40892d5fc950490551400b02ef360692c29815c714" +dependencies = [ + "generic-array", + "subtle", +] + +[[package]] +name = "ct-codecs" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3b7eb4404b8195a9abb6356f4ac07d8ba267045c8d6d220ac4dc992e6cc75df" + [[package]] name = "ctr" version = "0.8.0" @@ -626,6 +731,26 @@ dependencies = [ "cipher 0.3.0", ] +[[package]] +name = "der" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79b71cca7d95d7681a4b3b9cdf63c8dbc3730d0584c2c74e31416d64a90493f4" +dependencies = [ + "const-oid 0.6.2", + "crypto-bigint 0.2.11", +] + +[[package]] +name = "der" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6919815d73839e7ad218de758883aae3a257ba6759ce7a9992501efbb53d705c" +dependencies = [ + "const-oid 0.7.1", + "pem-rfc7468 0.3.1", +] + [[package]] name = "derive_more" version = "0.99.17" @@ -639,17 +764,67 @@ dependencies = [ "syn", ] +[[package]] +name = "digest" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" +dependencies = [ + "generic-array", +] + [[package]] name = "digest" version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2fb860ca6fafa5552fb6d0e816a69c8e49f0908bf524e30a90d97c85892d506" dependencies = [ - "block-buffer", + "block-buffer 0.10.2", "crypto-common", "subtle", ] +[[package]] +name = "ecdsa" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0d69ae62e0ce582d56380743515fefaf1a8c70cec685d9677636d7e30ae9dc9" +dependencies = [ + "der 0.5.1", + "elliptic-curve", + "rfc6979", + "signature", +] + +[[package]] +name = "ed25519-compact" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24e1f30f0312ac83726c1197abeacd91c9557f8a623e904a009ae6bc529ae8d8" +dependencies = [ + "ct-codecs", + "getrandom", +] + +[[package]] +name = "elliptic-curve" +version = "0.11.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25b477563c2bfed38a3b7a60964c49e058b2510ad3f12ba3483fd8f62c2306d6" +dependencies = [ + "base16ct", + "crypto-bigint 0.3.2", + "der 0.5.1", + "ff", + "generic-array", + "group", + "pem-rfc7468 0.3.1", + "rand_core", + "sec1", + "subtle", + "zeroize", +] + [[package]] name = "encoding_rs" version = "0.8.30" @@ -672,6 +847,16 @@ dependencies = [ "termcolor", ] +[[package]] +name = "ff" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2958d04124b9f27f175eaeb9a9f383d026098aa837eadd8ba22c11f13a05b9e" +dependencies = [ + "rand_core", + "subtle", +] + [[package]] name = "firestorm" version = "0.5.0" @@ -780,6 +965,17 @@ dependencies = [ "polyval", ] +[[package]] +name = "group" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc5ac374b108929de78460075f3dc439fa66df9d8fc77e8f12caa5165fcf0c89" +dependencies = [ + "ff", + "rand_core", + "subtle", +] + [[package]] name = "h2" version = "0.3.12" @@ -826,7 +1022,17 @@ version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "791a029f6b9fc27657f6f188ec6e5e43f6911f6f878e0dc5501396e09809d437" dependencies = [ - "hmac", + "hmac 0.12.1", +] + +[[package]] +name = "hmac" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a2a2320eb7ec0ebe8da8f744d7812d9fc4cb4d09344ac01898dbcb6a20ae69b" +dependencies = [ + "crypto-mac", + "digest 0.9.0", ] [[package]] @@ -835,7 +1041,31 @@ version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" dependencies = [ - "digest", + "digest 0.10.3", +] + +[[package]] +name = "hmac-sha1-compact" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d103cfecf6edf3f7d1dc7c5ab64e99488c0f8d11786e43b40873e66e8489d014" + +[[package]] +name = "hmac-sha256" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b45e85b74de4f2610b0c832e3a532f3b64cddb5d8923923bc00d70206fb035f7" +dependencies = [ + "digest 0.9.0", +] + +[[package]] +name = "hmac-sha512" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b2ce076d8070f292037093a825343f6341fe0ce873268c2477e2f49abd57b10" +dependencies = [ + "digest 0.9.0", ] [[package]] @@ -909,7 +1139,7 @@ version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282a6247722caba404c065016bbfa522806e51714c34f5dfc3e4a3a46fcb4223" dependencies = [ - "autocfg", + "autocfg 1.1.0", "hashbrown", ] @@ -937,6 +1167,42 @@ dependencies = [ "libc", ] +[[package]] +name = "jwt-simple" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c036f167883e073626dbc41f92966acb27a30172bf04a3bbe8b24ac45c02d728" +dependencies = [ + "anyhow", + "coarsetime", + "ct-codecs", + "ed25519-compact", + "hmac-sha1-compact", + "hmac-sha256", + "hmac-sha512", + "k256", + "p256", + "rand", + "rsa", + "serde", + "serde_json", + "thiserror", + "zeroize", +] + +[[package]] +name = "k256" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19c3a5e0a0b8450278feda242592512e09f61c72e018b8cd5c859482802daf2d" +dependencies = [ + "cfg-if", + "ecdsa", + "elliptic-curve", + "sec1", + "sha2 0.9.9", +] + [[package]] name = "language-tags" version = "0.3.2" @@ -948,6 +1214,9 @@ name = "lazy_static" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +dependencies = [ + "spin", +] [[package]] name = "libc" @@ -955,6 +1224,12 @@ version = "0.2.121" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "efaa7b300f3b5fe8eb6bf21ce3895e1751d9665086af2d64b42f19701015ff4f" +[[package]] +name = "libm" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33a33a362ce288760ec6a508b94caaec573ae7d3bbbd91b87aa0bad4456839db" + [[package]] name = "linked-hash-map" version = "0.5.4" @@ -1038,7 +1313,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a92518e98c078586bc6c934028adcca4c92a53d6a958196de835170a01d84e4b" dependencies = [ "adler", - "autocfg", + "autocfg 1.1.0", ] [[package]] @@ -1083,13 +1358,53 @@ dependencies = [ "winapi", ] +[[package]] +name = "num-bigint-dig" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4547ee5541c18742396ae2c895d0717d0f886d8823b8399cdaf7b07d63ad0480" +dependencies = [ + "autocfg 0.1.8", + "byteorder", + "lazy_static", + "libm", + "num-integer", + "num-iter", + "num-traits", + "rand", + "smallvec", + "zeroize", +] + +[[package]] +name = "num-integer" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db" +dependencies = [ + "autocfg 1.1.0", + "num-traits", +] + +[[package]] +name = "num-iter" +version = "0.1.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2021c8337a54d21aca0d59a92577a029af9431cb59b909b03252b9c164fad59" +dependencies = [ + "autocfg 1.1.0", + "num-integer", + "num-traits", +] + [[package]] name = "num-traits" version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290" dependencies = [ - "autocfg", + "autocfg 1.1.0", + "libm", ] [[package]] @@ -1132,6 +1447,18 @@ dependencies = [ "memchr", ] +[[package]] +name = "p256" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19736d80675fbe9fe33426268150b951a3fb8f5cfca2a23a17c85ef3adb24e3b" +dependencies = [ + "ecdsa", + "elliptic-curve", + "sec1", + "sha2 0.9.9", +] + [[package]] name = "parking_lot" version = "0.12.0" @@ -1161,6 +1488,24 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c520e05135d6e763148b6426a837e239041653ba7becd2e538c076c738025fc" +[[package]] +name = "pem-rfc7468" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84e93a3b1cc0510b03020f33f21e62acdde3dcaef432edc95bea377fbd4c2cd4" +dependencies = [ + "base64ct", +] + +[[package]] +name = "pem-rfc7468" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01de5d978f34aa4b2296576379fcc416034702fd94117c56ffd8a1a767cefb30" +dependencies = [ + "base64ct", +] + [[package]] name = "percent-encoding" version = "2.1.0" @@ -1179,6 +1524,41 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +[[package]] +name = "pkcs1" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "116bee8279d783c0cf370efa1a94632f2108e5ef0bb32df31f051647810a4e2c" +dependencies = [ + "der 0.4.5", + "pem-rfc7468 0.2.4", + "zeroize", +] + +[[package]] +name = "pkcs8" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee3ef9b64d26bad0536099c816c6734379e45bbd5f14798def6809e5cc350447" +dependencies = [ + "der 0.4.5", + "pem-rfc7468 0.2.4", + "pkcs1", + "spki 0.4.1", + "zeroize", +] + +[[package]] +name = "pkcs8" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cabda3fb821068a9a4fab19a683eac3af12edf0f34b94a8be53c4972b8149d0" +dependencies = [ + "der 0.5.1", + "spki 0.5.4", + "zeroize", +] + [[package]] name = "polyval" version = "0.5.3" @@ -1295,6 +1675,37 @@ version = "0.6.25" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b" +[[package]] +name = "rfc6979" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96ef608575f6392792f9ecf7890c00086591d29a83910939d430753f7c050525" +dependencies = [ + "crypto-bigint 0.3.2", + "hmac 0.11.0", + "zeroize", +] + +[[package]] +name = "rsa" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e05c2603e2823634ab331437001b411b9ed11660fbc4066f3908c84a9439260d" +dependencies = [ + "byteorder", + "digest 0.9.0", + "lazy_static", + "num-bigint-dig", + "num-integer", + "num-iter", + "num-traits", + "pkcs1", + "pkcs8 0.7.6", + "rand", + "subtle", + "zeroize", +] + [[package]] name = "rustc_version" version = "0.4.0" @@ -1316,6 +1727,19 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" +[[package]] +name = "sec1" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08da66b8b0965a5555b6bd6639e68ccba85e1e2506f5fbb089e93f8a04e1a2d1" +dependencies = [ + "der 0.5.1", + "generic-array", + "pkcs8 0.8.0", + "subtle", + "zeroize", +] + [[package]] name = "semver" version = "1.0.7" @@ -1385,7 +1809,20 @@ checksum = "028f48d513f9678cda28f6e4064755b3fbb2af6acd672f2c209b62323f7aea0f" dependencies = [ "cfg-if", "cpufeatures", - "digest", + "digest 0.10.3", +] + +[[package]] +name = "sha2" +version = "0.9.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d58a1e1bf39749807d89cf2d98ac2dfa0ff1cb3faa38fbb64dd88ac8013d800" +dependencies = [ + "block-buffer 0.9.0", + "cfg-if", + "cpufeatures", + "digest 0.9.0", + "opaque-debug", ] [[package]] @@ -1396,7 +1833,7 @@ checksum = "55deaec60f81eefe3cce0dc50bda92d6d8e88f2a27df7c5033b42afeb1ed2676" dependencies = [ "cfg-if", "cpufeatures", - "digest", + "digest 0.10.3", ] [[package]] @@ -1408,6 +1845,16 @@ dependencies = [ "libc", ] +[[package]] +name = "signature" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02658e48d89f2bec991f9a78e69cfa4c316f8d6a6c4ec12fae1aeb263d486788" +dependencies = [ + "digest 0.9.0", + "rand_core", +] + [[package]] name = "slab" version = "0.4.5" @@ -1430,6 +1877,31 @@ dependencies = [ "winapi", ] +[[package]] +name = "spin" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" + +[[package]] +name = "spki" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c01a0c15da1b0b0e1494112e7af814a678fec9bd157881b49beac661e9b6f32" +dependencies = [ + "der 0.4.5", +] + +[[package]] +name = "spki" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44d01ac02a6ccf3e07db148d2be087da624fea0221a16152ed01f0496a6b0a27" +dependencies = [ + "base64ct", + "der 0.5.1", +] + [[package]] name = "strsim" version = "0.10.0" @@ -1453,6 +1925,18 @@ dependencies = [ "unicode-xid", ] +[[package]] +name = "synstructure" +version = "0.12.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f36bdaa60a83aca3921b5259d5400cbf5e90fc51931376a9bd4a0eb79aa7210f" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "unicode-xid", +] + [[package]] name = "termcolor" version = "1.1.3" @@ -1468,6 +1952,26 @@ version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b1141d4d61095b28419e22cb0bbf02755f5e54e0526f97f1e3d1d160e60885fb" +[[package]] +name = "thiserror" +version = "1.0.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "854babe52e4df1653706b98fcfc05843010039b406875930a70e4d9644e5c417" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa32fd3f627f367fe16f893e2597ae3c05020f8bba2666a4e6ea73d377e5714b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "time" version = "0.3.9" @@ -1680,6 +2184,60 @@ 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.80" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27370197c907c55e3f1a9fbe26f44e937fe6451368324e009cba39e139dc08ad" +dependencies = [ + "cfg-if", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.80" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53e04185bfa3a779273da532f5025e33398409573f348985af9a1cbf3774d3f4" +dependencies = [ + "bumpalo", + "lazy_static", + "log", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.80" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17cae7ff784d7e83a2fe7611cfe766ecf034111b49deb850a3dc7699c08251f5" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.80" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99ec0dc7a4756fffc231aab1b9f2f578d23cd391390ab27f952ae0c9b3ece20b" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.80" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d554b7f530dee5964d9a9468d95c1f8b8acae4f282807e7d27d4b03099a46744" + [[package]] name = "winapi" version = "0.3.9" @@ -1765,9 +2323,24 @@ dependencies = [ [[package]] name = "zeroize" -version = "1.5.4" +version = "1.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7eb5728b8afd3f280a869ce1d4c554ffaed35f45c231fc41bfbd0381bef50317" +checksum = "d68d9dcec5f9b43a30d38c49f91dfedfaac384cb8f085faca366c26207dd1619" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f8f187641dad4f680d25c4bfc4225b418165984179f26ca76ec4fb6441d3a17" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] [[package]] name = "zstd" diff --git a/Cargo.toml b/Cargo.toml index 9b565c1..abda5ac 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,11 +16,12 @@ serde_json = "1.0.79" serde_yaml = "0.8.23" env_logger = "0.9.0" serde = { version = "1.0.136", features = ["derive"] } -bcrypt = "0.12.1" +bcrypt = "0.11.0" uuid = { version = "0.8.2", features = ["v4"] } mime_guess = "2.0.4" askama = "0.11.1" futures-util = "0.3.21" urlencoding = "2.1.0" rand = "0.8.5" -base64 = "0.13.0" \ No newline at end of file +base64 = "0.13.0" +jwt-simple = "0.10.9" \ No newline at end of file diff --git a/src/actors/openid_sessions_actor.rs b/src/actors/openid_sessions_actor.rs index 9856343..9964147 100644 --- a/src/actors/openid_sessions_actor.rs +++ b/src/actors/openid_sessions_actor.rs @@ -14,6 +14,7 @@ pub struct Session { pub session_id: SessionID, pub client: ClientID, pub user: UserID, + pub auth_time: u64, pub redirect_uri: String, pub authorization_code: String, diff --git a/src/constants.rs b/src/constants.rs index a96ce1e..502f2c1 100644 --- a/src/constants.rs +++ b/src/constants.rs @@ -45,6 +45,7 @@ pub const TEMPORARY_PASSWORDS_LEN: usize = 20; /// Open ID routes pub const AUTHORIZE_URI: &str = "/openid/authorize"; pub const TOKEN_URI: &str = "/openid/token"; +pub const CERT_URI: &str = "/openid/jwks_uri"; /// Open ID constants pub const OPEN_ID_SESSION_CLEANUP_INTERVAL: Duration = Duration::from_secs(60); diff --git a/src/controllers/openid_controller.rs b/src/controllers/openid_controller.rs index 8171eff..97c4e26 100644 --- a/src/controllers/openid_controller.rs +++ b/src/controllers/openid_controller.rs @@ -1,16 +1,20 @@ use actix::Addr; +use actix_identity::Identity; use actix_web::{HttpRequest, HttpResponse, Responder, web}; use actix_web::error::ErrorUnauthorized; use askama::Template; use crate::actors::openid_sessions_actor; use crate::actors::openid_sessions_actor::{OpenIDSessionsActor, Session, SessionID}; -use crate::constants::{AUTHORIZE_URI, OPEN_ID_ACCESS_TOKEN_LEN, OPEN_ID_ACCESS_TOKEN_TIMEOUT, OPEN_ID_AUTHORIZATION_CODE_LEN, OPEN_ID_AUTHORIZATION_CODE_TIMEOUT, OPEN_ID_REFRESH_TOKEN_LEN, OPEN_ID_REFRESH_TOKEN_TIMEOUT, OPEN_ID_SESSION_LEN, TOKEN_URI}; +use crate::constants::{AUTHORIZE_URI, CERT_URI, OPEN_ID_ACCESS_TOKEN_LEN, OPEN_ID_ACCESS_TOKEN_TIMEOUT, OPEN_ID_AUTHORIZATION_CODE_LEN, OPEN_ID_AUTHORIZATION_CODE_TIMEOUT, OPEN_ID_REFRESH_TOKEN_LEN, OPEN_ID_REFRESH_TOKEN_TIMEOUT, OPEN_ID_SESSION_LEN, TOKEN_URI}; use crate::controllers::base_controller::FatalErrorPage; use crate::data::app_config::AppConfig; use crate::data::client::{ClientID, ClientManager}; use crate::data::current_user::CurrentUser; +use crate::data::id_token::IdToken; +use crate::data::jwt_signer::{JsonWebKey, JWTSigner}; use crate::data::openid_config::OpenIDConfig; +use crate::data::session_identity::SessionIdentity; use crate::utils::string_utils::rand_str; use crate::utils::time::time; @@ -20,7 +24,7 @@ pub async fn get_configuration(app_conf: web::Data) -> impl Responder authorization_endpoint: app_conf.full_url(AUTHORIZE_URI), token_endpoint: app_conf.full_url(TOKEN_URI), userinfo_endpoint: app_conf.full_url("openid/userinfo"), - jwks_uri: app_conf.full_url("openid/jwks_uri"), + jwks_uri: app_conf.full_url(CERT_URI), scopes_supported: vec!["openid", "profile", "email"], response_types_supported: vec!["code", "id_token", "token id_token"], subject_types_supported: vec!["public"], @@ -41,7 +45,7 @@ pub struct AuthorizeQuery { /// REQUIRED. OAuth 2.0 Client Identifier valid at the Authorization Server. client_id: ClientID, - /// REQUIRED. Redirection URI to which the response will be sent. This URI MUST exactly match one of the Redirection URI values for the Client pre-registered at the OpenID Provider, with the matching performed as described in Section 6.2.1 of [RFC3986] (Simple String Comparison). When using this flow, the Redirection URI SHOULD use the https scheme; however, it MAY use the http scheme, provided that the Client Type is confidential, as defined in Section 2.1 of OAuth 2.0, and provided the OP allows the use of http Redirection URIs in this case. The Redirection URI MAY use an alternate scheme, such as one that is intended to identify a callback into a native application. + /// REQUIRED. Redirection URI to which the response will be sent. This URI MUST exactly match one of the Redirection URI values for the Client pre-registered at the OpenID Provider, with the matching performed as described in Section 6.2.1 of RFC3986 (Simple String Comparison). When using this flow, the Redirection URI SHOULD use the https scheme; however, it MAY use the http scheme, provided that the Client Type is confidential, as defined in Section 2.1 of OAuth 2.0, and provided the OP allows the use of http Redirection URIs in this case. The Redirection URI MAY use an alternate scheme, such as one that is intended to identify a callback into a native application. redirect_uri: String, /// RECOMMENDED. Opaque value used to maintain state between the request and the callback. Typically, Cross-Site Request Forgery (CSRF, XSRF) mitigation is done by cryptographically binding the value of this parameter with a browser cookie. @@ -50,7 +54,7 @@ pub struct AuthorizeQuery { /// OPTIONAL. String value used to associate a Client session with an ID Token, and to mitigate replay attacks. The value is passed through unmodified from the Authentication Request to the ID Token. Sufficient entropy MUST be present in the nonce values used to prevent attackers from guessing values. nonce: Option, - /// OPTIONAL - https://ldapwiki.com/wiki/Code_challenge_method + /// OPTIONAL - code_challenge: Option, code_challenge_method: Option, } @@ -70,7 +74,7 @@ fn error_redirect(query: &AuthorizeQuery, error: &str, description: &str) -> Htt .finish() } -pub async fn authorize(user: CurrentUser, query: web::Query, +pub async fn authorize(user: CurrentUser, id: Identity, query: web::Query, clients: web::Data, sessions: web::Data>) -> impl Responder { let client = match clients.find_by_id(&query.client_id) { @@ -123,6 +127,7 @@ pub async fn authorize(user: CurrentUser, query: web::Query, session_id: SessionID(rand_str(OPEN_ID_SESSION_LEN)), client: client.id, user: user.uid.clone(), + auth_time: SessionIdentity(&id).auth_time(), redirect_uri, authorization_code: rand_str(OPEN_ID_AUTHORIZATION_CODE_LEN), authorization_code_expire_at: time() + OPEN_ID_AUTHORIZATION_CODE_TIMEOUT, @@ -168,7 +173,11 @@ pub struct TokenResponse { pub async fn token(req: HttpRequest, query: web::Form, clients: web::Data, - sessions: web::Data>) -> actix_web::Result { + app_config: web::Data, + sessions: web::Data>, + jwt_signer: web::Data) -> actix_web::Result { + // TODO : add refresh tokens : https://openid.net/specs/openid-connect-core-1_0.html#RefreshTokens + // TODO : check auth challenge // Extraction authentication information let authorization_header = req.headers().get("authorization"); @@ -258,6 +267,17 @@ pub async fn token(req: HttpRequest, .await.unwrap(); + // Generate id token + let token = IdToken { + issuer: app_config.website_origin.to_string(), + subject_identifier: session.user, + audience: session.client.0.to_string(), + expiration_time: session.access_token_expire_at, + issued_at: time(), + auth_time: session.auth_time, + nonce: session.nonce, + }; + Ok(HttpResponse::Ok() .append_header(("Cache-Control", "no-store")) .append_header(("Pragam", "no-cache")) @@ -266,6 +286,15 @@ pub async fn token(req: HttpRequest, token_type: "Bearer", refresh_token: session.refresh_token, expires_in: session.access_token_expire_at - time(), - id_token: session.session_id.0, + id_token: jwt_signer.sign_token(token.to_jwt_claims())? })) +} + +#[derive(serde::Serialize)] +struct CertsResponse { + keys: Vec, +} + +pub async fn cert_uri(jwt_signer: web::Data) -> impl Responder { + HttpResponse::Ok().json(CertsResponse { keys: vec![jwt_signer.get_json_web_key()] }) } \ No newline at end of file diff --git a/src/data/id_token.rs b/src/data/id_token.rs new file mode 100644 index 0000000..72fb41f --- /dev/null +++ b/src/data/id_token.rs @@ -0,0 +1,42 @@ +use jwt_simple::claims::Audiences; +use jwt_simple::prelude::{Duration, JWTClaims}; + +#[derive(serde::Serialize)] +pub struct IdToken { + /// REQUIRED. Issuer Identifier for the Issuer of the response. The iss value is a case sensitive URL using the https scheme that contains scheme, host, and optionally, port number and path components and no query or fragment components. + #[serde(rename = "iss")] + pub issuer: String, + /// REQUIRED. Subject Identifier. A locally unique and never reassigned identifier within the Issuer for the End-User, which is intended to be consumed by the Client, e.g., 24400320 or AItOawmwtWwcT0k51BayewNvutrJUqsvl6qs7A4. It MUST NOT exceed 255 ASCII characters in length. The sub value is a case sensitive string. + #[serde(rename = "sub")] + pub subject_identifier: String, + /// REQUIRED. Audience(s) that this ID Token is intended for. It MUST contain the OAuth 2.0 client_id of the Relying Party as an audience value. It MAY also contain identifiers for other audiences. In the general case, the aud value is an array of case sensitive strings. In the common special case when there is one audience, the aud value MAY be a single case sensitive string. + #[serde(rename = "aud")] + pub audience: String, + /// REQUIRED. Expiration time on or after which the ID Token MUST NOT be accepted for processing. The processing of this parameter requires that the current date/time MUST be before the expiration date/time listed in the value. Implementers MAY provide for some small leeway, usually no more than a few minutes, to account for clock skew. Its value is a JSON number representing the number of seconds from 1970-01-01T0:0:0Z as measured in UTC until the date/time. See RFC 3339 [RFC3339] for details regarding date/times in general and UTC in particular. + #[serde(rename = "exp")] + pub expiration_time: u64, + /// REQUIRED. Time at which the JWT was issued. Its value is a JSON number representing the number of seconds from 1970-01-01T0:0:0Z as measured in UTC until the date/time. + #[serde(rename = "iat")] + pub issued_at: u64, + /// Time when the End-User authentication occurred. Its value is a JSON number representing the number of seconds from 1970-01-01T0:0:0Z as measured in UTC until the date/time. When a max_age request is made or when auth_time is requested as an Essential Claim, then this Claim is REQUIRED; otherwise, its inclusion is OPTIONAL. (The auth_time Claim semantically corresponds to the OpenID 2.0 PAPE [OpenID.PAPE] auth_time response parameter.) + pub auth_time: u64, + /// String value used to associate a Client session with an ID Token, and to mitigate replay attacks. The value is passed through unmodified from the Authentication Request to the ID Token. If present in the ID Token, Clients MUST verify that the nonce Claim Value is equal to the value of the nonce parameter sent in the Authentication Request. If present in the Authentication Request, Authorization Servers MUST include a nonce Claim in the ID Token with the Claim Value being the nonce value sent in the Authentication Request. Authorization Servers SHOULD perform no other processing on nonce values used. The nonce value is a case sensitive string. + #[serde(skip_serializing_if = "Option::is_none")] + pub nonce: Option, +} + +impl IdToken { + pub fn to_jwt_claims(self) -> JWTClaims<()> { + JWTClaims { + issued_at: Some(Duration::from_secs(self.issued_at)), + expires_at: Some(Duration::from_secs(self.expiration_time)), + invalid_before: None, + issuer: Some(self.issuer), + subject: Some(self.subject_identifier), + audiences: Some(Audiences::AsString(self.audience)), + jwt_id: None, + nonce: self.nonce, + custom: (), + } + } +} \ No newline at end of file diff --git a/src/data/jwt_signer.rs b/src/data/jwt_signer.rs new file mode 100644 index 0000000..5aa40b1 --- /dev/null +++ b/src/data/jwt_signer.rs @@ -0,0 +1,48 @@ +use jwt_simple::algorithms::RSAKeyPairLike; +use jwt_simple::claims::JWTClaims; +use jwt_simple::prelude::RS256KeyPair; +use serde::de::DeserializeOwned; +use serde::Serialize; + +use crate::utils::err::Res; +use crate::utils::string_utils::rand_str; + +/// Json Web Key +#[derive(serde::Serialize, serde::Deserialize)] +pub struct JsonWebKey { + #[serde(rename = "alg")] + algorithm: String, + #[serde(rename = "kty")] + key_type: String, + #[serde(rename = "kid")] + key_id: String, + #[serde(rename = "n")] + modulus: String, + #[serde(rename = "e")] + public_exponent: String, +} + +#[derive(Debug, Clone)] +pub struct JWTSigner(RS256KeyPair); + +impl JWTSigner { + pub fn gen_from_memory() -> Res { + Ok(Self(RS256KeyPair::generate(2048)? + .with_key_id(&format!("key-{}", rand_str(15))))) + } + + pub fn get_json_web_key(&self) -> JsonWebKey { + let components = self.0.public_key().to_components(); + JsonWebKey { + algorithm: "RS256".to_string(), + key_type: "RSA".to_string(), + key_id: self.0.key_id().as_ref().unwrap().to_string(), + public_exponent: base64::encode_config(components.e, base64::URL_SAFE), + modulus: base64::encode_config(components.n, base64::URL_SAFE).replace('=', ""), + } + } + + pub fn sign_token(&self, c: JWTClaims) -> Res { + Ok(self.0.sign(c)?) + } +} \ No newline at end of file diff --git a/src/data/mod.rs b/src/data/mod.rs index 9d43bc5..66ff2f6 100644 --- a/src/data/mod.rs +++ b/src/data/mod.rs @@ -5,4 +5,6 @@ pub mod user; pub mod client; pub mod remote_ip; pub mod current_user; -pub mod openid_config; \ No newline at end of file +pub mod openid_config; +pub mod jwt_signer; +pub mod id_token; \ No newline at end of file diff --git a/src/data/session_identity.rs b/src/data/session_identity.rs index 60f6ef0..c982774 100644 --- a/src/data/session_identity.rs +++ b/src/data/session_identity.rs @@ -2,6 +2,7 @@ use actix_identity::Identity; use serde::{Deserialize, Serialize}; use crate::data::user::{User, UserID}; +use crate::utils::time::time; #[derive(Debug, Serialize, Deserialize, Eq, PartialEq)] pub enum SessionStatus { @@ -21,6 +22,7 @@ impl Default for SessionStatus { pub struct SessionIdentityData { pub id: UserID, pub is_admin: bool, + pub auth_time: u64, pub status: SessionStatus, } @@ -64,6 +66,7 @@ impl<'a> SessionIdentity<'a> { self.set_session_data(&SessionIdentityData { id: user.uid.clone(), is_admin: user.admin, + auth_time: time(), status: SessionStatus::SignedIn, }); } @@ -93,4 +96,8 @@ impl<'a> SessionIdentity<'a> { pub fn user_id(&self) -> UserID { self.get_session_data().unwrap_or_default().id } + + pub fn auth_time(&self) -> u64 { + self.get_session_data().unwrap_or_default().auth_time + } } diff --git a/src/main.rs b/src/main.rs index 4c3b4a4..49f185f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -16,6 +16,7 @@ use basic_oidc::controllers::login_controller::{login_route, logout_route}; use basic_oidc::data::app_config::AppConfig; use basic_oidc::data::client::ClientManager; use basic_oidc::data::entity_manager::EntityManager; +use basic_oidc::data::jwt_signer::JWTSigner; use basic_oidc::data::user::{hash_password, User}; use basic_oidc::middlewares::auth_middleware::AuthMiddleware; @@ -66,6 +67,8 @@ async fn main() -> std::io::Result<()> { let users_actor = UsersActor::new(users).start(); let bruteforce_actor = BruteForceActor::default().start(); let openid_sessions_actor = OpenIDSessionsActor::default().start(); + let jwt_signer = JWTSigner::gen_from_memory() + .expect("Failed to generate JWKS key"); log::info!("Server will listen on {}", config.listen_address); let listen_address = config.listen_address.to_string(); @@ -87,6 +90,7 @@ async fn main() -> std::io::Result<()> { .app_data(web::Data::new(openid_sessions_actor.clone())) .app_data(web::Data::new(config.clone())) .app_data(web::Data::new(clients)) + .app_data(web::Data::new(jwt_signer.clone())) .wrap(Logger::default()) .wrap(AuthMiddleware {}) @@ -131,6 +135,7 @@ async fn main() -> std::io::Result<()> { .route("/.well-known/openid-configuration", web::get().to(openid_controller::get_configuration)) .route(AUTHORIZE_URI, web::get().to(openid_controller::authorize)) .route(TOKEN_URI, web::post().to(openid_controller::token)) + .route(CERT_URI, web::get().to(openid_controller::cert_uri)) }) .bind(listen_address)? .run()