mirror of
https://github.com/kanidm/kanidm.git
synced 2025-05-16 14:03:55 +02:00
Drop fernet in favour of JWE
This drops the use of fernet from OAuth2 in favour of JWE. To achieve this cleanly, we swap OAuth2 to using our internel key object handler so that in future we can consider the use of pkcs11 devices. This also makes it easier in general to handle any future cryptographic changes.
This commit is contained in:
parent
be4818e121
commit
7236bee837
Cargo.lockCargo.toml
libs/client/src
proto/src
server
Dockerfile
daemon
lib
testkit/tests/testkit
tools/cli
289
Cargo.lock
generated
289
Cargo.lock
generated
|
@ -24,7 +24,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"getrandom 0.2.15",
|
||||
"getrandom 0.2.16",
|
||||
"once_cell",
|
||||
"serde",
|
||||
"version_check",
|
||||
|
@ -232,9 +232,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "async-compression"
|
||||
version = "0.4.22"
|
||||
version = "0.4.23"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "59a194f9d963d8099596278594b3107448656ba73831c9d8c783e613ce86da64"
|
||||
checksum = "b37fc50485c4f3f736a4fb14199f6d5f5ba008d7f28fe710306c92780f004c07"
|
||||
dependencies = [
|
||||
"flate2",
|
||||
"futures-core",
|
||||
|
@ -621,9 +621,9 @@ checksum = "3eeab4423108c5d7c744f4d234de88d18d636100093ae04caf4825134b9c3a32"
|
|||
|
||||
[[package]]
|
||||
name = "bstr"
|
||||
version = "1.11.3"
|
||||
version = "1.12.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "531a9155a481e2ee699d4f98f43c0ca4ff8ee1bfd55c31e9e98fb29d2b176fe0"
|
||||
checksum = "234113d19d0d7d613b40e86fb654acf958910802bcceab913a4f9e7cda03b1a4"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
"regex-automata 0.4.9",
|
||||
|
@ -668,9 +668,9 @@ checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a"
|
|||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.2.17"
|
||||
version = "1.2.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1fcb57c740ae1daf453ae85f16e37396f672b039e00d9d866e07ddb24e328e3a"
|
||||
checksum = "8e3a13707ac958681c13b39b458c073d0d9bc8a22cb1b2f4c8e55eb72c13f362"
|
||||
dependencies = [
|
||||
"shlex",
|
||||
]
|
||||
|
@ -792,8 +792,7 @@ checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990"
|
|||
[[package]]
|
||||
name = "compact_jwt"
|
||||
version = "0.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "12bbab6445446e8d0b07468a01d0bfdae15879de5c440c5e47ae4ae0e18a1fba"
|
||||
source = "git+https://github.com/Firstyear/compact-jwt.git?rev=b3d2b5700cfe567d384c81df35d25537fbf7f110#b3d2b5700cfe567d384c81df35d25537fbf7f110"
|
||||
dependencies = [
|
||||
"base64 0.21.7",
|
||||
"base64urlsafedata",
|
||||
|
@ -1053,9 +1052,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "darling"
|
||||
version = "0.20.10"
|
||||
version = "0.20.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6f63b86c8a8826a49b8c21f08a2d07338eec8d900540f8630dc76284be802989"
|
||||
checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee"
|
||||
dependencies = [
|
||||
"darling_core",
|
||||
"darling_macro",
|
||||
|
@ -1063,9 +1062,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "darling_core"
|
||||
version = "0.20.10"
|
||||
version = "0.20.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "95133861a8032aaea082871032f5815eb9e98cef03fa916ab4500513994df9e5"
|
||||
checksum = "0d00b9596d185e565c2207a0b01f8bd1a135483d02d9b7b0a54b11da8d53412e"
|
||||
dependencies = [
|
||||
"fnv",
|
||||
"ident_case",
|
||||
|
@ -1077,9 +1076,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "darling_macro"
|
||||
version = "0.20.10"
|
||||
version = "0.20.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806"
|
||||
checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead"
|
||||
dependencies = [
|
||||
"darling_core",
|
||||
"quote",
|
||||
|
@ -1088,15 +1087,15 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "data-encoding"
|
||||
version = "2.8.0"
|
||||
version = "2.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "575f75dfd25738df5b91b8e43e14d44bda14637a58fae779fd2b064f8bf3e010"
|
||||
checksum = "2a2330da5de22e8a3cb63252ce2abb30116bf5265e89c0e01bc17015ce30a476"
|
||||
|
||||
[[package]]
|
||||
name = "der"
|
||||
version = "0.7.9"
|
||||
version = "0.7.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f55bf8e7b65898637379c1b74eb1551107c8294ed26d855ceb9fd1a09cfc9bc0"
|
||||
checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb"
|
||||
dependencies = [
|
||||
"const-oid",
|
||||
"der_derive",
|
||||
|
@ -1132,9 +1131,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "deranged"
|
||||
version = "0.4.1"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "28cfac68e08048ae1883171632c2aef3ebc555621ae56fbccce1cbf22dd7f058"
|
||||
checksum = "9c9e6a11ca8224451684bc0d7d5a7adbf8f2fd6887261a1cfc3c0432f9d4068e"
|
||||
dependencies = [
|
||||
"powerfmt",
|
||||
"serde",
|
||||
|
@ -1223,22 +1222,23 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "dirs"
|
||||
version = "4.0.0"
|
||||
version = "6.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ca3aa72a6f96ea37bbc5aa912f6788242832f75369bdfdadcb0e38423f100059"
|
||||
checksum = "c3e8aa94d75141228480295a7d0e7feb620b1a5ad9f12bc40be62411e38cce4e"
|
||||
dependencies = [
|
||||
"dirs-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dirs-sys"
|
||||
version = "0.3.7"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1b1d1d91c932ef41c0f2663aa8b0ca0342d444d842c06914aa0a7e352d0bada6"
|
||||
checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"option-ext",
|
||||
"redox_users",
|
||||
"winapi",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -1351,9 +1351,9 @@ checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
|
|||
|
||||
[[package]]
|
||||
name = "errno"
|
||||
version = "0.3.10"
|
||||
version = "0.3.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d"
|
||||
checksum = "976dd42dc7e85965fe702eb8164f21f450704bdde31faefd6471dba214cb594e"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"windows-sys 0.59.0",
|
||||
|
@ -1361,9 +1361,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "escargot"
|
||||
version = "0.5.13"
|
||||
version = "0.5.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "05a3ac187a16b5382fef8c69fd1bad123c67b7cf3932240a2d43dcdd32cded88"
|
||||
checksum = "83f351750780493fc33fa0ce8ba3c7d61f9736cfa3b3bb9ee2342643ffe40211"
|
||||
dependencies = [
|
||||
"log",
|
||||
"once_cell",
|
||||
|
@ -1444,19 +1444,6 @@ version = "2.3.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be"
|
||||
|
||||
[[package]]
|
||||
name = "fernet"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c66b725fe9483b9ee72ccaec072b15eb8ad95a3ae63a8c798d5748883b72fd33"
|
||||
dependencies = [
|
||||
"base64 0.22.1",
|
||||
"byteorder",
|
||||
"getrandom 0.2.15",
|
||||
"openssl",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "file-id"
|
||||
version = "0.2.2"
|
||||
|
@ -1486,15 +1473,15 @@ checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99"
|
|||
|
||||
[[package]]
|
||||
name = "flagset"
|
||||
version = "0.4.6"
|
||||
version = "0.4.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b3ea1ec5f8307826a5b71094dd91fc04d4ae75d5709b20ad351c7fb4815c86ec"
|
||||
checksum = "b7ac824320a75a52197e8f2d787f6a38b6718bb6897a35142d749af3c0e8f4fe"
|
||||
|
||||
[[package]]
|
||||
name = "flate2"
|
||||
version = "1.1.0"
|
||||
version = "1.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "11faaf5a5236997af9848be0bef4db95824b1d534ebc64d0f0c6cf3e67bd38dc"
|
||||
checksum = "7ced92e76e966ca2fd84c8f7aa01a4aea65b0eb6648d72f7c8f3e2764a67fece"
|
||||
dependencies = [
|
||||
"crc32fast",
|
||||
"miniz_oxide",
|
||||
|
@ -1573,7 +1560,7 @@ version = "0.13.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8640e34b88f7652208ce9e88b1a37a2ae95227d84abec377ccd3c5cfeb141ed4"
|
||||
dependencies = [
|
||||
"rustix 1.0.3",
|
||||
"rustix 1.0.5",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
|
@ -1687,9 +1674,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.2.15"
|
||||
version = "0.2.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7"
|
||||
checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"js-sys",
|
||||
|
@ -2277,7 +2264,7 @@ dependencies = [
|
|||
"futures-sink",
|
||||
"futures-util",
|
||||
"http 0.2.12",
|
||||
"indexmap 2.8.0",
|
||||
"indexmap 2.9.0",
|
||||
"slab",
|
||||
"tokio",
|
||||
"tokio-util",
|
||||
|
@ -2286,9 +2273,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "h2"
|
||||
version = "0.4.8"
|
||||
version = "0.4.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5017294ff4bb30944501348f6f8e42e6ad28f42c8bbef7a74029aff064a4e3c2"
|
||||
checksum = "75249d144030531f8dee69fe9cea04d3edf809a017ae445e2abdff6629e86633"
|
||||
dependencies = [
|
||||
"atomic-waker",
|
||||
"bytes",
|
||||
|
@ -2296,7 +2283,7 @@ dependencies = [
|
|||
"futures-core",
|
||||
"futures-sink",
|
||||
"http 1.3.1",
|
||||
"indexmap 2.8.0",
|
||||
"indexmap 2.9.0",
|
||||
"slab",
|
||||
"tokio",
|
||||
"tokio-util",
|
||||
|
@ -2503,7 +2490,7 @@ dependencies = [
|
|||
"bytes",
|
||||
"futures-channel",
|
||||
"futures-util",
|
||||
"h2 0.4.8",
|
||||
"h2 0.4.9",
|
||||
"http 1.3.1",
|
||||
"http-body 1.0.1",
|
||||
"httparse",
|
||||
|
@ -2565,9 +2552,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "hyper-util"
|
||||
version = "0.1.10"
|
||||
version = "0.1.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "df2dcfbe0677734ab2f3ffa7fa7bfd4706bfdc1ef393f2ee30184aed67e631b4"
|
||||
checksum = "497bbc33a26fdd4af9ed9c70d63f61cf56a938375fbb32df34db9b1cd6d643f2"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"futures-channel",
|
||||
|
@ -2575,6 +2562,7 @@ dependencies = [
|
|||
"http 1.3.1",
|
||||
"http-body 1.0.1",
|
||||
"hyper 1.6.0",
|
||||
"libc",
|
||||
"pin-project-lite",
|
||||
"socket2",
|
||||
"tokio",
|
||||
|
@ -2584,9 +2572,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "iana-time-zone"
|
||||
version = "0.1.62"
|
||||
version = "0.1.63"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b2fd658b06e56721792c5df4475705b6cda790e9298d19d2f8af083457bcd127"
|
||||
checksum = "b0c919e5debc312ad217002b8048a17b7d83f80703865bbfcfebb0458b0b27d8"
|
||||
dependencies = [
|
||||
"android_system_properties",
|
||||
"core-foundation-sys",
|
||||
|
@ -2801,9 +2789,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "indexmap"
|
||||
version = "2.8.0"
|
||||
version = "2.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3954d50fe15b02142bf25d3b8bdadb634ec3948f103d04ffe3031bc8fe9d7058"
|
||||
checksum = "cea70ddb795996207ad57735b50c5982d8844f38ba9ee5f1aedcfb708a2aa11e"
|
||||
dependencies = [
|
||||
"equivalent",
|
||||
"hashbrown 0.15.2",
|
||||
|
@ -3277,7 +3265,6 @@ dependencies = [
|
|||
"concread",
|
||||
"dhat",
|
||||
"dyn-clone",
|
||||
"fernet",
|
||||
"futures",
|
||||
"hashbrown 0.15.2",
|
||||
"hex",
|
||||
|
@ -3473,9 +3460,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "libm"
|
||||
version = "0.2.11"
|
||||
version = "0.2.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8355be11b20d696c8f18f6cc018c4e372165b1fa8126cef092399c9951984ffa"
|
||||
checksum = "c9627da5196e5d8ed0b0495e61e518847578da83483c37288316d9b2e03a7f72"
|
||||
|
||||
[[package]]
|
||||
name = "libmimalloc-sys"
|
||||
|
@ -3547,9 +3534,9 @@ checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab"
|
|||
|
||||
[[package]]
|
||||
name = "linux-raw-sys"
|
||||
version = "0.9.3"
|
||||
version = "0.9.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fe7db12097d22ec582439daf8618b8fdd1a7bef6270e9af3b1ebcd30893cf413"
|
||||
checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12"
|
||||
|
||||
[[package]]
|
||||
name = "litemap"
|
||||
|
@ -3732,18 +3719,18 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
|
|||
|
||||
[[package]]
|
||||
name = "miniz_oxide"
|
||||
version = "0.8.5"
|
||||
version = "0.8.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8e3e04debbb59698c15bacbb6d93584a8c0ca9cc3213cb423d31f760d8843ce5"
|
||||
checksum = "3be647b768db090acb35d5ec5db2b0e1f1de11133ca123b9eacf5137868f892a"
|
||||
dependencies = [
|
||||
"adler2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mintex"
|
||||
version = "0.1.3"
|
||||
version = "0.1.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9bec4598fddb13cc7b528819e697852653252b760f1228b7642679bf2ff2cd07"
|
||||
checksum = "c505b3e17ed6b70a7ed2e67fbb2c560ee327353556120d6e72f5232b6880d536"
|
||||
|
||||
[[package]]
|
||||
name = "mio"
|
||||
|
@ -4034,7 +4021,7 @@ checksum = "c38841cdd844847e3e7c8d29cef9dcfed8877f8f56f9071f77843ecf3baf937f"
|
|||
dependencies = [
|
||||
"base64 0.13.1",
|
||||
"chrono",
|
||||
"getrandom 0.2.15",
|
||||
"getrandom 0.2.16",
|
||||
"http 0.2.12",
|
||||
"rand 0.8.5",
|
||||
"reqwest 0.11.27",
|
||||
|
@ -4054,7 +4041,7 @@ checksum = "51e219e79014df21a225b1860a479e2dcd7cbd9130f4defd4bd0e191ea31d67d"
|
|||
dependencies = [
|
||||
"base64 0.22.1",
|
||||
"chrono",
|
||||
"getrandom 0.2.15",
|
||||
"getrandom 0.2.16",
|
||||
"http 1.3.1",
|
||||
"rand 0.8.5",
|
||||
"reqwest 0.12.15",
|
||||
|
@ -4095,9 +4082,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.21.2"
|
||||
version = "1.21.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c2806eaa3524762875e21c3dcd057bc4b7bfa01ce4da8d46be1cd43649e1cc6b"
|
||||
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
|
||||
|
||||
[[package]]
|
||||
name = "openssl"
|
||||
|
@ -4228,6 +4215,12 @@ dependencies = [
|
|||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "option-ext"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d"
|
||||
|
||||
[[package]]
|
||||
name = "orca"
|
||||
version = "1.6.0-dev"
|
||||
|
@ -4372,7 +4365,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "3672b37090dbd86368a4145bc067582552b29c27377cad4e0a306c97f9bd7772"
|
||||
dependencies = [
|
||||
"fixedbitset",
|
||||
"indexmap 2.8.0",
|
||||
"indexmap 2.9.0",
|
||||
"serde",
|
||||
]
|
||||
|
||||
|
@ -4491,9 +4484,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "prettyplease"
|
||||
version = "0.2.31"
|
||||
version = "0.2.32"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5316f57387668042f561aae71480de936257848f9c43ce528e311d89a07cadeb"
|
||||
checksum = "664ec5419c51e34154eec046ebcba56312d5a2fc3b09a06da188e1ad21afadf6"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"syn 2.0.100",
|
||||
|
@ -4501,12 +4494,11 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "proc-macro-crate"
|
||||
version = "1.3.1"
|
||||
version = "3.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919"
|
||||
checksum = "edce586971a4dfaa28950c6f18ed55e0406c1ab88bbce2c6f6293a7aaba73d35"
|
||||
dependencies = [
|
||||
"once_cell",
|
||||
"toml_edit 0.19.15",
|
||||
"toml_edit",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -4628,9 +4620,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "quinn-proto"
|
||||
version = "0.11.10"
|
||||
version = "0.11.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b820744eb4dc9b57a3398183639c511b5a26d2ed702cedd3febaa1393caa22cc"
|
||||
checksum = "bcbafbbdbb0f638fe3f35f3c56739f77a8a1d070cb25603226c83339b391472b"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"getrandom 0.3.2",
|
||||
|
@ -4722,7 +4714,7 @@ version = "0.6.4"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
|
||||
dependencies = [
|
||||
"getrandom 0.2.15",
|
||||
"getrandom 0.2.16",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -4742,22 +4734,22 @@ checksum = "60a357793950651c4ed0f3f52338f53b2f809f32d83a07f72909fa13e4c6c1e3"
|
|||
|
||||
[[package]]
|
||||
name = "redox_syscall"
|
||||
version = "0.5.10"
|
||||
version = "0.5.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0b8c0c260b63a8219631167be35e6a988e9554dbd323f8bd08439c8ed1302bd1"
|
||||
checksum = "d2f103c6d277498fbceb16e84d317e2a400f160f46904d5f5410848c829511a3"
|
||||
dependencies = [
|
||||
"bitflags 2.9.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "redox_users"
|
||||
version = "0.4.6"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43"
|
||||
checksum = "dd6f9d3d47bdd2ad6945c5015a226ec6155d0bcdfd8f7cd29f86b71f8de99d2b"
|
||||
dependencies = [
|
||||
"getrandom 0.2.15",
|
||||
"getrandom 0.2.16",
|
||||
"libredox",
|
||||
"thiserror 1.0.69",
|
||||
"thiserror 2.0.12",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -4888,7 +4880,7 @@ dependencies = [
|
|||
"futures-channel",
|
||||
"futures-core",
|
||||
"futures-util",
|
||||
"h2 0.4.8",
|
||||
"h2 0.4.9",
|
||||
"http 1.3.1",
|
||||
"http-body 1.0.1",
|
||||
"http-body-util",
|
||||
|
@ -4942,7 +4934,7 @@ checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7"
|
|||
dependencies = [
|
||||
"cc",
|
||||
"cfg-if",
|
||||
"getrandom 0.2.15",
|
||||
"getrandom 0.2.16",
|
||||
"libc",
|
||||
"untrusted",
|
||||
"windows-sys 0.52.0",
|
||||
|
@ -4980,9 +4972,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "rust-embed"
|
||||
version = "8.6.0"
|
||||
version = "8.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0b3aba5104622db5c9fc61098de54708feb732e7763d7faa2fa625899f00bf6f"
|
||||
checksum = "e5fbc0ee50fcb99af7cebb442e5df7b5b45e9460ffa3f8f549cd26b862bec49d"
|
||||
dependencies = [
|
||||
"rust-embed-impl",
|
||||
"rust-embed-utils",
|
||||
|
@ -4991,9 +4983,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "rust-embed-impl"
|
||||
version = "8.6.0"
|
||||
version = "8.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1f198c73be048d2c5aa8e12f7960ad08443e56fd39cc26336719fdb4ea0ebaae"
|
||||
checksum = "6bf418c9a2e3f6663ca38b8a7134cc2c2167c9d69688860e8961e3faa731702e"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
@ -5004,9 +4996,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "rust-embed-utils"
|
||||
version = "8.6.0"
|
||||
version = "8.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5a2fcdc9f40c8dc2922842ca9add611ad19f332227fc651d015881ad1552bd9a"
|
||||
checksum = "08d55b95147fe01265d06b3955db798bdaed52e60e2211c41137701b3aba8e21"
|
||||
dependencies = [
|
||||
"sha2",
|
||||
"walkdir",
|
||||
|
@ -5054,22 +5046,22 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "rustix"
|
||||
version = "1.0.3"
|
||||
version = "1.0.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e56a18552996ac8d29ecc3b190b4fdbb2d91ca4ec396de7bbffaf43f3d637e96"
|
||||
checksum = "d97817398dd4bb2e6da002002db259209759911da105da92bec29ccb12cf58bf"
|
||||
dependencies = [
|
||||
"bitflags 2.9.0",
|
||||
"errno",
|
||||
"libc",
|
||||
"linux-raw-sys 0.9.3",
|
||||
"linux-raw-sys 0.9.4",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustls"
|
||||
version = "0.23.25"
|
||||
version = "0.23.26"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "822ee9188ac4ec04a2f0531e55d035fb2de73f18b41a63c70c2712503b6fb13c"
|
||||
checksum = "df51b5869f3a441595eac5e8ff14d486ff285f7b8c0df8770e49c3b56351f0f0"
|
||||
dependencies = [
|
||||
"once_cell",
|
||||
"ring",
|
||||
|
@ -5352,7 +5344,7 @@ dependencies = [
|
|||
"chrono",
|
||||
"hex",
|
||||
"indexmap 1.9.3",
|
||||
"indexmap 2.8.0",
|
||||
"indexmap 2.9.0",
|
||||
"serde",
|
||||
"serde_derive",
|
||||
"serde_json",
|
||||
|
@ -5448,9 +5440,9 @@ checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
|
|||
|
||||
[[package]]
|
||||
name = "signal-hook-registry"
|
||||
version = "1.4.2"
|
||||
version = "1.4.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1"
|
||||
checksum = "9203b8055f63a2a00e2f593bb0510367fe707d7ff1e5c872de2f537b339e5410"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
@ -5483,9 +5475,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "smallvec"
|
||||
version = "1.14.0"
|
||||
version = "1.15.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7fcf8323ef1faaee30a44a340193b1ac6814fd9b7b4e88e9d4519a3e4abe1cfd"
|
||||
checksum = "8917285742e9f3e1683f0a9c4e6b57960b7314d0b08d30d1ecd426713ee2eee9"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
@ -5513,9 +5505,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "socket2"
|
||||
version = "0.5.8"
|
||||
version = "0.5.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c970269d99b64e60ec3bd6ad27270092a5394c4e309314b18ae3fe575695fbe8"
|
||||
checksum = "4f5fd57c80058a56cf5c777ab8a126398ece8e442983605d280a44ce79d0edef"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"windows-sys 0.52.0",
|
||||
|
@ -5686,7 +5678,7 @@ dependencies = [
|
|||
"fastrand",
|
||||
"getrandom 0.3.2",
|
||||
"once_cell",
|
||||
"rustix 1.0.3",
|
||||
"rustix 1.0.5",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
|
@ -5917,9 +5909,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "tokio-util"
|
||||
version = "0.7.14"
|
||||
version = "0.7.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6b9590b93e6fcc1739458317cccd391ad3955e2bde8913edf6f95f9e65a8f034"
|
||||
checksum = "66a539a9ad6d5d281510d5bd368c973d636c02dbf8a67300bfb6b950696ad7df"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"futures-core",
|
||||
|
@ -5937,7 +5929,7 @@ dependencies = [
|
|||
"serde",
|
||||
"serde_spanned",
|
||||
"toml_datetime",
|
||||
"toml_edit 0.22.24",
|
||||
"toml_edit",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -5949,24 +5941,13 @@ dependencies = [
|
|||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_edit"
|
||||
version = "0.19.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421"
|
||||
dependencies = [
|
||||
"indexmap 2.8.0",
|
||||
"toml_datetime",
|
||||
"winnow 0.5.40",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_edit"
|
||||
version = "0.22.24"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "17b4795ff5edd201c7cd6dca065ae59972ce77d1b80fa0a84d94950ece7d1474"
|
||||
dependencies = [
|
||||
"indexmap 2.8.0",
|
||||
"indexmap 2.9.0",
|
||||
"serde",
|
||||
"serde_spanned",
|
||||
"toml_datetime",
|
||||
|
@ -5984,7 +5965,7 @@ dependencies = [
|
|||
"axum",
|
||||
"base64 0.22.1",
|
||||
"bytes",
|
||||
"h2 0.4.8",
|
||||
"h2 0.4.9",
|
||||
"http 1.3.1",
|
||||
"http-body 1.0.1",
|
||||
"http-body-util",
|
||||
|
@ -6309,7 +6290,7 @@ version = "4.2.3"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c5afb1a60e207dca502682537fefcfd9921e71d0b83e9576060f09abc6efab23"
|
||||
dependencies = [
|
||||
"indexmap 2.8.0",
|
||||
"indexmap 2.9.0",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"utoipa-gen",
|
||||
|
@ -6729,11 +6710,37 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "windows-core"
|
||||
version = "0.52.0"
|
||||
version = "0.61.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9"
|
||||
checksum = "4763c1de310c86d75a878046489e2e5ba02c649d185f21c67d4cf8a56d098980"
|
||||
dependencies = [
|
||||
"windows-targets 0.52.6",
|
||||
"windows-implement",
|
||||
"windows-interface",
|
||||
"windows-link",
|
||||
"windows-result",
|
||||
"windows-strings 0.4.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-implement"
|
||||
version = "0.60.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.100",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-interface"
|
||||
version = "0.59.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.100",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -6749,7 +6756,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "4286ad90ddb45071efd1a66dfa43eb02dd0dfbae1545ad6cc3c51cf34d7e8ba3"
|
||||
dependencies = [
|
||||
"windows-result",
|
||||
"windows-strings",
|
||||
"windows-strings 0.3.1",
|
||||
"windows-targets 0.53.0",
|
||||
]
|
||||
|
||||
|
@ -6771,6 +6778,15 @@ dependencies = [
|
|||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-strings"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7a2ba9642430ee452d5a7aa78d72907ebe8cfda358e8cb7918a2050581322f97"
|
||||
dependencies = [
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.48.0"
|
||||
|
@ -7025,15 +7041,6 @@ version = "0.53.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486"
|
||||
|
||||
[[package]]
|
||||
name = "winnow"
|
||||
version = "0.5.40"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winnow"
|
||||
version = "0.6.26"
|
||||
|
|
|
@ -101,7 +101,7 @@ codegen-units = 256
|
|||
## As Kanidm maintains a number of libraries, sometimes during development we need to override them
|
||||
## with local or git versions. This patch table allows quick uncommenting to achieve that.
|
||||
|
||||
# compact_jwt = { path = "../compact_jwt" }
|
||||
# compact_jwt = { path = "../compact-jwt" }
|
||||
# concread = { path = "../concread" }
|
||||
|
||||
# idlset = { path = "../idlset" }
|
||||
|
@ -122,7 +122,10 @@ codegen-units = 256
|
|||
libnss = { git = "https://github.com/Firstyear/libnss-rs.git", branch = "20250207-freebsd" }
|
||||
# Allow ssh keys to have comments with spaces.
|
||||
sshkeys = { git = "https://github.com/Firstyear/rust-sshkeys.git", rev = "3a081cbf7480628223bcb96fc8aaa8c19109d007" }
|
||||
|
||||
# Branch currently carrying some needed rs256 signer patches for jwk handling,
|
||||
# as main is currently working to drop openssl and may need more work before
|
||||
# we commit to that change here.
|
||||
compact_jwt = { git = "https://github.com/Firstyear/compact-jwt.git", rev = "b3d2b5700cfe567d384c81df35d25537fbf7f110" }
|
||||
|
||||
[workspace.dependencies]
|
||||
kanidmd_core = { path = "./server/core", version = "=1.6.0-dev" }
|
||||
|
@ -173,7 +176,6 @@ csv = "1.3.1"
|
|||
dialoguer = "0.11.0"
|
||||
dhat = "0.3.3"
|
||||
dyn-clone = "^1.0.17"
|
||||
fernet = "^0.2.1"
|
||||
filetime = "^0.2.24"
|
||||
fs4 = "^0.13.0"
|
||||
futures = "^0.3.31"
|
||||
|
|
|
@ -1,16 +1,18 @@
|
|||
use crate::{ClientError, KanidmClient};
|
||||
use kanidm_proto::attribute::Attribute;
|
||||
use kanidm_proto::constants::{
|
||||
ATTR_DISPLAYNAME, ATTR_ES256_PRIVATE_KEY_DER, ATTR_NAME,
|
||||
ATTR_DISPLAYNAME, ATTR_KEY_ACTION_REVOKE, ATTR_KEY_ACTION_ROTATE, ATTR_NAME,
|
||||
ATTR_OAUTH2_ALLOW_INSECURE_CLIENT_DISABLE_PKCE, ATTR_OAUTH2_ALLOW_LOCALHOST_REDIRECT,
|
||||
ATTR_OAUTH2_JWT_LEGACY_CRYPTO_ENABLE, ATTR_OAUTH2_PREFER_SHORT_USERNAME,
|
||||
ATTR_OAUTH2_RS_BASIC_SECRET, ATTR_OAUTH2_RS_ORIGIN, ATTR_OAUTH2_RS_ORIGIN_LANDING,
|
||||
ATTR_OAUTH2_RS_TOKEN_KEY, ATTR_OAUTH2_STRICT_REDIRECT_URI, ATTR_RS256_PRIVATE_KEY_DER,
|
||||
ATTR_OAUTH2_STRICT_REDIRECT_URI,
|
||||
};
|
||||
use kanidm_proto::internal::{ImageValue, Oauth2ClaimMapJoin};
|
||||
use kanidm_proto::v1::Entry;
|
||||
use reqwest::multipart;
|
||||
use std::collections::BTreeMap;
|
||||
use time::format_description::well_known::Rfc3339;
|
||||
use time::OffsetDateTime;
|
||||
use url::Url;
|
||||
|
||||
impl KanidmClient {
|
||||
|
@ -84,7 +86,34 @@ impl KanidmClient {
|
|||
.await
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub async fn idm_oauth2_rs_revoke_key(
|
||||
&self,
|
||||
id: &str,
|
||||
key_id: &str,
|
||||
) -> Result<(), ClientError> {
|
||||
self.perform_post_request(
|
||||
&format!("/v1/oauth2/{}/_attr/{}", id, ATTR_KEY_ACTION_REVOKE),
|
||||
vec![key_id.to_string()],
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn idm_oauth2_rs_rotate_keys(
|
||||
&self,
|
||||
id: &str,
|
||||
rotate_at_time: OffsetDateTime,
|
||||
) -> Result<(), ClientError> {
|
||||
let rfc_3339_str = rotate_at_time.format(&Rfc3339).map_err(|_| {
|
||||
ClientError::InvalidRequest("Unable to format rfc 3339 datetime".into())
|
||||
})?;
|
||||
|
||||
self.perform_post_request(
|
||||
&format!("/v1/oauth2/{}/_attr/{}", id, ATTR_KEY_ACTION_ROTATE),
|
||||
vec![rfc_3339_str],
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn idm_oauth2_rs_update(
|
||||
&self,
|
||||
id: &str,
|
||||
|
@ -92,8 +121,6 @@ impl KanidmClient {
|
|||
displayname: Option<&str>,
|
||||
landing: Option<&str>,
|
||||
reset_secret: bool,
|
||||
reset_token_key: bool,
|
||||
reset_sign_key: bool,
|
||||
) -> Result<(), ClientError> {
|
||||
let mut update_oauth2_rs = Entry {
|
||||
attrs: BTreeMap::new(),
|
||||
|
@ -121,19 +148,6 @@ impl KanidmClient {
|
|||
.attrs
|
||||
.insert(ATTR_OAUTH2_RS_BASIC_SECRET.to_string(), Vec::new());
|
||||
}
|
||||
if reset_token_key {
|
||||
update_oauth2_rs
|
||||
.attrs
|
||||
.insert(ATTR_OAUTH2_RS_TOKEN_KEY.to_string(), Vec::new());
|
||||
}
|
||||
if reset_sign_key {
|
||||
update_oauth2_rs
|
||||
.attrs
|
||||
.insert(ATTR_ES256_PRIVATE_KEY_DER.to_string(), Vec::new());
|
||||
update_oauth2_rs
|
||||
.attrs
|
||||
.insert(ATTR_RS256_PRIVATE_KEY_DER.to_string(), Vec::new());
|
||||
}
|
||||
self.perform_patch_request(format!("/v1/oauth2/{}", id).as_str(), update_oauth2_rs)
|
||||
.await
|
||||
}
|
||||
|
|
|
@ -89,6 +89,7 @@ pub enum Attribute {
|
|||
KeyActionRotate,
|
||||
KeyActionRevoke,
|
||||
KeyActionImportJwsEs256,
|
||||
KeyActionImportJwsRs256,
|
||||
KeyInternalData,
|
||||
KeyProvider,
|
||||
LastModifiedCid,
|
||||
|
@ -323,6 +324,7 @@ impl Attribute {
|
|||
Attribute::KeyActionRotate => ATTR_KEY_ACTION_ROTATE,
|
||||
Attribute::KeyActionRevoke => ATTR_KEY_ACTION_REVOKE,
|
||||
Attribute::KeyActionImportJwsEs256 => ATTR_KEY_ACTION_IMPORT_JWS_ES256,
|
||||
Attribute::KeyActionImportJwsRs256 => ATTR_KEY_ACTION_IMPORT_JWS_RS256,
|
||||
Attribute::KeyInternalData => ATTR_KEY_INTERNAL_DATA,
|
||||
Attribute::KeyProvider => ATTR_KEY_PROVIDER,
|
||||
Attribute::LastModifiedCid => ATTR_LAST_MODIFIED_CID,
|
||||
|
@ -510,6 +512,7 @@ impl Attribute {
|
|||
ATTR_KEY_ACTION_ROTATE => Attribute::KeyActionRotate,
|
||||
ATTR_KEY_ACTION_REVOKE => Attribute::KeyActionRevoke,
|
||||
ATTR_KEY_ACTION_IMPORT_JWS_ES256 => Attribute::KeyActionImportJwsEs256,
|
||||
ATTR_KEY_ACTION_IMPORT_JWS_RS256 => Attribute::KeyActionImportJwsRs256,
|
||||
ATTR_KEY_INTERNAL_DATA => Attribute::KeyInternalData,
|
||||
ATTR_KEY_PROVIDER => Attribute::KeyProvider,
|
||||
ATTR_LAST_MODIFIED_CID => Attribute::LastModifiedCid,
|
||||
|
|
|
@ -133,6 +133,7 @@ pub const ATTR_JWS_ES256_PRIVATE_KEY: &str = "jws_es256_private_key";
|
|||
pub const ATTR_KEY_ACTION_ROTATE: &str = "key_action_rotate";
|
||||
pub const ATTR_KEY_ACTION_REVOKE: &str = "key_action_revoke";
|
||||
pub const ATTR_KEY_ACTION_IMPORT_JWS_ES256: &str = "key_action_import_jws_es256";
|
||||
pub const ATTR_KEY_ACTION_IMPORT_JWS_RS256: &str = "key_action_import_jws_rs256";
|
||||
pub const ATTR_KEY_INTERNAL_DATA: &str = "key_internal_data";
|
||||
pub const ATTR_KEY_PROVIDER: &str = "key_provider";
|
||||
pub const ATTR_LAST_MODIFIED_CID: &str = "last_modified_cid";
|
||||
|
@ -319,5 +320,6 @@ pub const ENTRYCLASS_KEY_PROVIDER: &str = "key_provider";
|
|||
pub const ENTRYCLASS_KEY_PROVIDER_INTERNAL: &str = "key_provider_internal";
|
||||
pub const ENTRYCLASS_KEY_OBJECT: &str = "key_object";
|
||||
pub const ENTRYCLASS_KEY_OBJECT_JWT_ES256: &str = "key_object_jwt_es256";
|
||||
pub const ENTRYCLASS_KEY_OBJECT_JWT_RS256: &str = "key_object_jwt_rs256";
|
||||
pub const ENTRYCLASS_KEY_OBJECT_JWE_A128GCM: &str = "key_object_jwe_a128gcm";
|
||||
pub const ENTRYCLASS_KEY_OBJECT_INTERNAL: &str = "key_object_internal";
|
||||
|
|
|
@ -270,6 +270,25 @@ pub enum OperationError {
|
|||
KP0043KeyObjectJweA128GCMEncryption,
|
||||
KP0044KeyObjectJwsPublicJwk,
|
||||
|
||||
KP0045KeyObjectImportJwsRs256DerInvalid,
|
||||
KP0046KeyObjectSignerToVerifier,
|
||||
KP0047KeyObjectPublicToDer,
|
||||
KP0048KeyObjectJwtRs256Generation,
|
||||
KP0049KeyObjectSignerToVerifier,
|
||||
KP0050KeyObjectPrivateToDer,
|
||||
KP0051KeyObjectPublicToDer,
|
||||
KP0052KeyObjectJwsRs256DerInvalid,
|
||||
KP0053KeyObjectSignerToVerifier,
|
||||
KP0054KeyObjectJwsRs256DerInvalid,
|
||||
KP0055KeyObjectJwsRs256DerInvalid,
|
||||
KP0056KeyObjectJwsRs256Signature,
|
||||
KP0057KeyObjectJwsNotAssociated,
|
||||
KP0058KeyObjectJwsInvalid,
|
||||
KP0059KeyObjectJwsKeyRevoked,
|
||||
KP0060KeyObjectJwsPublicJwk,
|
||||
KP0061KeyObjectNoActiveSigningKeys,
|
||||
KP0062KeyProviderNoSuchKey,
|
||||
|
||||
// Plugins
|
||||
PL0001GidOverlapsSystemRange,
|
||||
|
||||
|
@ -448,6 +467,26 @@ impl OperationError {
|
|||
Self::KP0042KeyObjectNoActiveEncryptionKeys => None,
|
||||
Self::KP0043KeyObjectJweA128GCMEncryption => None,
|
||||
Self::KP0044KeyObjectJwsPublicJwk => None,
|
||||
|
||||
Self::KP0045KeyObjectImportJwsRs256DerInvalid => None,
|
||||
Self::KP0046KeyObjectSignerToVerifier => None,
|
||||
Self::KP0047KeyObjectPublicToDer => None,
|
||||
Self::KP0048KeyObjectJwtRs256Generation => None,
|
||||
Self::KP0049KeyObjectSignerToVerifier => None,
|
||||
Self::KP0050KeyObjectPrivateToDer => None,
|
||||
Self::KP0051KeyObjectPublicToDer => None,
|
||||
Self::KP0052KeyObjectJwsRs256DerInvalid => None,
|
||||
Self::KP0053KeyObjectSignerToVerifier => None,
|
||||
Self::KP0054KeyObjectJwsRs256DerInvalid => None,
|
||||
Self::KP0055KeyObjectJwsRs256DerInvalid => None,
|
||||
Self::KP0056KeyObjectJwsRs256Signature => None,
|
||||
Self::KP0057KeyObjectJwsNotAssociated => None,
|
||||
Self::KP0058KeyObjectJwsInvalid => None,
|
||||
Self::KP0059KeyObjectJwsKeyRevoked => None,
|
||||
Self::KP0060KeyObjectJwsPublicJwk => None,
|
||||
Self::KP0061KeyObjectNoActiveSigningKeys => None,
|
||||
Self::KP0062KeyProviderNoSuchKey => None,
|
||||
|
||||
Self::KU001InitWhileSessionActive => Some("The session was active when the init function was called.".into()),
|
||||
Self::KU002ContinueWhileSessionInActive => Some("Attempted to continue auth session while current session is inactive".into()),
|
||||
Self::KU003PamAuthFailed => Some("Failed PAM account authentication step".into()),
|
||||
|
|
|
@ -65,6 +65,8 @@ RUN <<EOF
|
|||
ls -Rla /out/libs-root
|
||||
EOF
|
||||
|
||||
RUN ls /usr/src/kanidm/target/release/kanidmd
|
||||
|
||||
# ======================
|
||||
|
||||
FROM scratch
|
||||
|
|
|
@ -13,9 +13,9 @@ tls_key = "/tmp/kanidm/key.pem"
|
|||
# NOTE: this is overridden by KANIDM_LOG_LEVEL environment variable
|
||||
# Defaults to "info"
|
||||
#
|
||||
log_level = "info"
|
||||
# log_level = "info"
|
||||
# log_level = "debug"
|
||||
# log_level = "trace"
|
||||
log_level = "trace"
|
||||
|
||||
# otel_grpc_url = "http://localhost:4317"
|
||||
|
||||
|
|
|
@ -32,7 +32,6 @@ compact_jwt = { workspace = true, features = ["openssl", "hsm-crypto"] }
|
|||
concread = { workspace = true }
|
||||
dhat = { workspace = true, optional = true }
|
||||
dyn-clone = { workspace = true }
|
||||
fernet = { workspace = true, features = ["fernet_danger_timestamps"] }
|
||||
# futures-util = { workspace = true }
|
||||
hashbrown = { workspace = true }
|
||||
idlset = { workspace = true }
|
||||
|
|
|
@ -692,6 +692,7 @@ pub enum DbValueImage {
|
|||
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)]
|
||||
pub enum DbValueKeyUsage {
|
||||
JwsEs256,
|
||||
JwsRs256,
|
||||
JweA128GCM,
|
||||
}
|
||||
|
||||
|
|
|
@ -40,6 +40,7 @@ pub enum EntryClass {
|
|||
KeyProviderInternal,
|
||||
KeyObject,
|
||||
KeyObjectJwtEs256,
|
||||
KeyObjectJwtRs256,
|
||||
KeyObjectJweA128GCM,
|
||||
KeyObjectInternal,
|
||||
MemberOf,
|
||||
|
@ -94,6 +95,7 @@ impl From<EntryClass> for &'static str {
|
|||
EntryClass::KeyProviderInternal => ENTRYCLASS_KEY_PROVIDER_INTERNAL,
|
||||
EntryClass::KeyObject => ENTRYCLASS_KEY_OBJECT,
|
||||
EntryClass::KeyObjectJwtEs256 => ENTRYCLASS_KEY_OBJECT_JWT_ES256,
|
||||
EntryClass::KeyObjectJwtRs256 => ENTRYCLASS_KEY_OBJECT_JWT_RS256,
|
||||
EntryClass::KeyObjectJweA128GCM => ENTRYCLASS_KEY_OBJECT_JWE_A128GCM,
|
||||
EntryClass::KeyObjectInternal => ENTRYCLASS_KEY_OBJECT_INTERNAL,
|
||||
EntryClass::MemberOf => ENTRYCLASS_MEMBER_OF,
|
||||
|
|
|
@ -334,6 +334,10 @@ pub const UUID_SCHEMA_ATTR_ACP_MODIFY_PRESENT_CLASS: Uuid =
|
|||
uuid!("00000000-0000-0000-0000-ffff00000189");
|
||||
pub const UUID_SCHEMA_ATTR_ACP_MODIFY_REMOVE_CLASS: Uuid =
|
||||
uuid!("00000000-0000-0000-0000-ffff00000190");
|
||||
pub const UUID_SCHEMA_ATTR_KEY_ACTION_IMPORT_JWS_RS256: Uuid =
|
||||
uuid!("00000000-0000-0000-0000-ffff00000191");
|
||||
pub const UUID_SCHEMA_CLASS_KEY_OBJECT_JWT_RS256: Uuid =
|
||||
uuid!("00000000-0000-0000-0000-ffff00000192");
|
||||
|
||||
// System and domain infos
|
||||
// I'd like to strongly criticise william of the past for making poor choices about these allocations.
|
||||
|
|
|
@ -4,57 +4,49 @@
|
|||
//! integrations, which are then able to be used an accessed from the IDM layer
|
||||
//! for operations involving OAuth2 authentication processing.
|
||||
|
||||
use std::collections::btree_map::Entry as BTreeEntry;
|
||||
use std::collections::{BTreeMap, BTreeSet};
|
||||
use std::convert::TryFrom;
|
||||
use std::fmt;
|
||||
use std::str::FromStr;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
|
||||
use crate::idm::account::Account;
|
||||
use crate::idm::server::{
|
||||
IdmServerProxyReadTransaction, IdmServerProxyWriteTransaction, IdmServerTransaction,
|
||||
};
|
||||
use crate::prelude::*;
|
||||
use crate::server::keys::{KeyObject, KeyProvidersTransaction, KeyProvidersWriteTransaction};
|
||||
use crate::value::{Oauth2Session, OauthClaimMapJoin, SessionState, OAUTHSCOPE_RE};
|
||||
use base64::{engine::general_purpose, Engine as _};
|
||||
use hashbrown::HashSet;
|
||||
|
||||
pub use compact_jwt::{compact::JwkKeySet, OidcToken};
|
||||
use compact_jwt::{
|
||||
crypto::JwsRs256Signer, jws::JwsBuilder, JwsCompact, JwsEs256Signer, JwsSigner,
|
||||
JwsSignerToVerifier, JwsVerifier, OidcClaims, OidcSubject,
|
||||
crypto::{JweA128GCMEncipher, JweA128KWEncipher},
|
||||
jwe::Jwe,
|
||||
jws::JwsBuilder,
|
||||
JweCompact, JwsCompact, OidcClaims, OidcSubject,
|
||||
};
|
||||
use concread::cowcell::*;
|
||||
use fernet::Fernet;
|
||||
use hashbrown::HashMap;
|
||||
use hashbrown::HashSet;
|
||||
use kanidm_proto::constants::*;
|
||||
|
||||
// #[cfg(feature = "dev-oauth2-device-flow")]
|
||||
// use kanidm_proto::oauth2::OAUTH2_DEVICE_CODE_EXPIRY_SECONDS;
|
||||
|
||||
pub use kanidm_proto::oauth2::{
|
||||
AccessTokenIntrospectRequest, AccessTokenIntrospectResponse, AccessTokenRequest,
|
||||
AccessTokenResponse, AuthorisationRequest, CodeChallengeMethod, ErrorResponse, GrantTypeReq,
|
||||
OAuth2RFC9068Token, OAuth2RFC9068TokenExtensions, Oauth2Rfc8414MetadataResponse,
|
||||
OidcDiscoveryResponse, OidcWebfingerRel, OidcWebfingerResponse, PkceAlg, TokenRevokeRequest,
|
||||
};
|
||||
|
||||
use kanidm_proto::oauth2::{
|
||||
AccessTokenType, ClaimType, DeviceAuthorizationResponse, DisplayValue, GrantType,
|
||||
IdTokenSignAlg, ResponseMode, ResponseType, SubjectType, TokenEndpointAuthMethod,
|
||||
};
|
||||
use openssl::sha;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_with::{formats, serde_as};
|
||||
use std::collections::btree_map::Entry as BTreeEntry;
|
||||
use std::collections::{BTreeMap, BTreeSet};
|
||||
use std::fmt;
|
||||
use std::str::FromStr;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
use time::OffsetDateTime;
|
||||
use tracing::trace;
|
||||
use uri::{OAUTH2_TOKEN_INTROSPECT_ENDPOINT, OAUTH2_TOKEN_REVOKE_ENDPOINT};
|
||||
use url::{Host, Origin, Url};
|
||||
|
||||
use crate::idm::account::Account;
|
||||
use crate::idm::server::{
|
||||
IdmServerProxyReadTransaction, IdmServerProxyWriteTransaction, IdmServerTransaction,
|
||||
};
|
||||
use crate::prelude::*;
|
||||
use crate::value::{Oauth2Session, OauthClaimMapJoin, SessionState, OAUTHSCOPE_RE};
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, PartialEq)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum Oauth2Error {
|
||||
|
@ -134,6 +126,8 @@ struct ConsentToken {
|
|||
pub client_id: String,
|
||||
// Must match the session id of the Uat,
|
||||
pub session_id: Uuid,
|
||||
pub expiry: u64,
|
||||
|
||||
// So we can ensure that we really match the same uat to prevent confusions.
|
||||
pub ident_id: IdentityId,
|
||||
// CSRF
|
||||
|
@ -158,10 +152,11 @@ struct ConsentToken {
|
|||
struct TokenExchangeCode {
|
||||
// We don't need the client_id here, because it's signed with an RS specific
|
||||
// key which gives us the assurance that it's the correct combination.
|
||||
// pub uat: UserAuthToken,
|
||||
pub account_uuid: Uuid,
|
||||
pub session_id: Uuid,
|
||||
|
||||
pub expiry: u64,
|
||||
|
||||
// The S256 code challenge.
|
||||
#[serde_as(
|
||||
as = "Option<serde_with::base64::Base64<serde_with::base64::UrlSafe, formats::Unpadded>>"
|
||||
|
@ -359,12 +354,6 @@ impl std::fmt::Debug for OauthRSType {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
enum Oauth2JwsSigner {
|
||||
ES256 { signer: JwsEs256Signer },
|
||||
RS256 { signer: JwsRs256Signer },
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
struct ClaimValue {
|
||||
join: OauthClaimMapJoin,
|
||||
|
@ -398,6 +387,12 @@ impl ClaimValue {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
enum SignatureAlgo {
|
||||
Es256,
|
||||
Rs256,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Oauth2RS {
|
||||
name: String,
|
||||
|
@ -416,8 +411,8 @@ pub struct Oauth2RS {
|
|||
client_scopes: BTreeSet<String>,
|
||||
client_sup_scopes: BTreeSet<String>,
|
||||
// Our internal exchange encryption material for this rs.
|
||||
token_fernet: Fernet,
|
||||
jws_signer: Oauth2JwsSigner,
|
||||
sign_alg: SignatureAlgo,
|
||||
key_object: Arc<KeyObject>,
|
||||
|
||||
// For oidc we also need our issuer url.
|
||||
iss: Url,
|
||||
|
@ -486,7 +481,7 @@ impl std::fmt::Debug for Oauth2RS {
|
|||
#[derive(Clone)]
|
||||
struct Oauth2RSInner {
|
||||
origin: Url,
|
||||
fernet: Fernet,
|
||||
consent_key: JweA128KWEncipher,
|
||||
rs_set: HashMap<String, Oauth2RS>,
|
||||
}
|
||||
|
||||
|
@ -502,31 +497,20 @@ pub struct Oauth2ResourceServersWriteTransaction<'a> {
|
|||
inner: CowCellWriteTxn<'a, Oauth2RSInner>,
|
||||
}
|
||||
|
||||
impl TryFrom<(Vec<Arc<EntrySealedCommitted>>, Url, DomainVersion)> for Oauth2ResourceServers {
|
||||
type Error = OperationError;
|
||||
impl Oauth2ResourceServers {
|
||||
pub fn new(origin: Url) -> Result<Self, OperationError> {
|
||||
let consent_key = JweA128KWEncipher::generate_ephemeral()
|
||||
.map_err(|_| OperationError::CryptographyError)?;
|
||||
|
||||
fn try_from(
|
||||
value: (Vec<Arc<EntrySealedCommitted>>, Url, DomainVersion),
|
||||
) -> Result<Self, Self::Error> {
|
||||
let (value, origin, domain_level) = value;
|
||||
let fernet =
|
||||
Fernet::new(&Fernet::generate_key()).ok_or(OperationError::CryptographyError)?;
|
||||
let oauth2rs = Oauth2ResourceServers {
|
||||
Ok(Oauth2ResourceServers {
|
||||
inner: CowCell::new(Oauth2RSInner {
|
||||
origin,
|
||||
fernet,
|
||||
consent_key,
|
||||
rs_set: HashMap::new(),
|
||||
}),
|
||||
};
|
||||
|
||||
let mut oauth2rs_wr = oauth2rs.write();
|
||||
oauth2rs_wr.reload(value, domain_level)?;
|
||||
oauth2rs_wr.commit();
|
||||
Ok(oauth2rs)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
impl Oauth2ResourceServers {
|
||||
pub fn read(&self) -> Oauth2ResourceServersReadTransaction {
|
||||
Oauth2ResourceServersReadTransaction {
|
||||
inner: self.inner.read(),
|
||||
|
@ -544,6 +528,7 @@ impl Oauth2ResourceServersWriteTransaction<'_> {
|
|||
pub fn reload(
|
||||
&mut self,
|
||||
value: Vec<Arc<EntrySealedCommitted>>,
|
||||
key_providers: &KeyProvidersWriteTransaction,
|
||||
domain_level: DomainVersion,
|
||||
) -> Result<(), OperationError> {
|
||||
let rs_set: Result<HashMap<_, _>, _> = value
|
||||
|
@ -552,13 +537,23 @@ impl Oauth2ResourceServersWriteTransaction<'_> {
|
|||
let uuid = ent.get_uuid();
|
||||
trace!(?uuid, "Checking OAuth2 configuration");
|
||||
// From each entry, attempt to make an OAuth2 configuration.
|
||||
if !ent.attribute_equality(Attribute::Class, &EntryClass::OAuth2ResourceServer.into()) {
|
||||
if !ent
|
||||
.attribute_equality(Attribute::Class, &EntryClass::OAuth2ResourceServer.into())
|
||||
{
|
||||
error!("Missing class oauth2_resource_server");
|
||||
// Check we have oauth2_resource_server class
|
||||
return Err(OperationError::InvalidEntryState);
|
||||
}
|
||||
|
||||
let type_ = if ent.attribute_equality(Attribute::Class, &EntryClass::OAuth2ResourceServerBasic.into()) {
|
||||
let Some(key_object) = key_providers.get_key_object_handle(uuid) else {
|
||||
error!("OAuth2 rs is missing it's key object!");
|
||||
return Err(OperationError::InvalidEntryState);
|
||||
};
|
||||
|
||||
let type_ = if ent.attribute_equality(
|
||||
Attribute::Class,
|
||||
&EntryClass::OAuth2ResourceServerBasic.into(),
|
||||
) {
|
||||
let authz_secret = ent
|
||||
.get_ava_single_secret(Attribute::OAuth2RsBasicSecret)
|
||||
.map(str::to_string)
|
||||
|
@ -573,13 +568,16 @@ impl Oauth2ResourceServersWriteTransaction<'_> {
|
|||
authz_secret,
|
||||
enable_pkce,
|
||||
}
|
||||
} else if ent.attribute_equality(Attribute::Class, &EntryClass::OAuth2ResourceServerPublic.into()) {
|
||||
} else if ent.attribute_equality(
|
||||
Attribute::Class,
|
||||
&EntryClass::OAuth2ResourceServerPublic.into(),
|
||||
) {
|
||||
let allow_localhost_redirect = ent
|
||||
.get_ava_single_bool(Attribute::OAuth2AllowLocalhostRedirect)
|
||||
.unwrap_or(false);
|
||||
|
||||
OauthRSType::Public {
|
||||
allow_localhost_redirect
|
||||
allow_localhost_redirect,
|
||||
}
|
||||
} else {
|
||||
error!("Missing class determining OAuth2 rs type");
|
||||
|
@ -597,7 +595,6 @@ impl Oauth2ResourceServersWriteTransaction<'_> {
|
|||
.map(str::to_string)
|
||||
.ok_or(OperationError::InvalidValueState)?;
|
||||
|
||||
|
||||
// Setup the landing uri and its implied origin, as well as
|
||||
// the supplemental origins.
|
||||
let landing_url = ent
|
||||
|
@ -605,14 +602,17 @@ impl Oauth2ResourceServersWriteTransaction<'_> {
|
|||
.cloned()
|
||||
.ok_or(OperationError::InvalidValueState)?;
|
||||
|
||||
let maybe_extra_urls = ent.get_ava_set(Attribute::OAuth2RsOrigin).and_then(|s| s.as_url_set());
|
||||
let maybe_extra_urls = ent
|
||||
.get_ava_set(Attribute::OAuth2RsOrigin)
|
||||
.and_then(|s| s.as_url_set());
|
||||
|
||||
let len_uris = maybe_extra_urls.map(|s| s.len() + 1).unwrap_or(1);
|
||||
|
||||
// If we are DL8, then strict enforcement is always required.
|
||||
let strict_redirect_uri = cfg!(test) ||
|
||||
domain_level >= DOMAIN_LEVEL_8 ||
|
||||
ent.get_ava_single_bool(Attribute::OAuth2StrictRedirectUri)
|
||||
let strict_redirect_uri = cfg!(test)
|
||||
|| domain_level >= DOMAIN_LEVEL_8
|
||||
|| ent
|
||||
.get_ava_single_bool(Attribute::OAuth2StrictRedirectUri)
|
||||
.unwrap_or(false);
|
||||
|
||||
// The reason we have to allocate this is that we need to do some processing on these
|
||||
|
@ -651,13 +651,6 @@ impl Oauth2ResourceServersWriteTransaction<'_> {
|
|||
}
|
||||
}
|
||||
|
||||
let token_fernet = ent
|
||||
.get_ava_single_secret(Attribute::OAuth2RsTokenKey)
|
||||
.ok_or(OperationError::InvalidValueState)
|
||||
.and_then(|key| {
|
||||
Fernet::new(key).ok_or(OperationError::CryptographyError)
|
||||
})?;
|
||||
|
||||
let scope_maps = ent
|
||||
.get_ava_as_oauthscopemaps(Attribute::OAuth2RsScopeMap)
|
||||
.cloned()
|
||||
|
@ -670,9 +663,9 @@ impl Oauth2ResourceServersWriteTransaction<'_> {
|
|||
|
||||
// From our scope maps we can now determine what scopes would be granted to our
|
||||
// client during a client credentials authentication.
|
||||
let (client_scopes, client_sup_scopes) = if let Some(client_member_of) = ent.get_ava_refer(Attribute::MemberOf) {
|
||||
let client_scopes =
|
||||
scope_maps
|
||||
let (client_scopes, client_sup_scopes) =
|
||||
if let Some(client_member_of) = ent.get_ava_refer(Attribute::MemberOf) {
|
||||
let client_scopes = scope_maps
|
||||
.iter()
|
||||
.filter_map(|(u, m)| {
|
||||
if client_member_of.contains(u) {
|
||||
|
@ -720,23 +713,21 @@ impl Oauth2ResourceServersWriteTransaction<'_> {
|
|||
// to be unique.
|
||||
match claim_map.entry(*group_uuid) {
|
||||
BTreeEntry::Vacant(e) => {
|
||||
e.insert(
|
||||
vec![
|
||||
(
|
||||
claim_name.clone(), ClaimValue {
|
||||
e.insert(vec![(
|
||||
claim_name.clone(),
|
||||
ClaimValue {
|
||||
join: claim_mapping.join(),
|
||||
values: claim_values.clone()
|
||||
}
|
||||
)
|
||||
]
|
||||
);
|
||||
values: claim_values.clone(),
|
||||
},
|
||||
)]);
|
||||
}
|
||||
BTreeEntry::Occupied(mut e) => {
|
||||
e.get_mut().push((
|
||||
claim_name.clone(), ClaimValue {
|
||||
claim_name.clone(),
|
||||
ClaimValue {
|
||||
join: claim_mapping.join(),
|
||||
values: claim_values.clone()
|
||||
}
|
||||
values: claim_values.clone(),
|
||||
},
|
||||
));
|
||||
}
|
||||
}
|
||||
|
@ -748,33 +739,13 @@ impl Oauth2ResourceServersWriteTransaction<'_> {
|
|||
BTreeMap::default()
|
||||
};
|
||||
|
||||
trace!("{}", Attribute::OAuth2JwtLegacyCryptoEnable);
|
||||
let jws_signer = if ent.get_ava_single_bool(Attribute::OAuth2JwtLegacyCryptoEnable).unwrap_or(false) {
|
||||
trace!("{}", Attribute::Rs256PrivateKeyDer);
|
||||
ent
|
||||
.get_ava_single_private_binary(Attribute::Rs256PrivateKeyDer)
|
||||
.ok_or(OperationError::InvalidValueState)
|
||||
.and_then(|key_der| {
|
||||
JwsRs256Signer::from_rs256_der(key_der)
|
||||
.map(|signer| Oauth2JwsSigner::RS256 { signer })
|
||||
.map_err(|e| {
|
||||
admin_error!(err = ?e, "Unable to load Legacy RS256 JwsSigner from DER");
|
||||
OperationError::CryptographyError
|
||||
})
|
||||
})?
|
||||
let sign_alg = if ent
|
||||
.get_ava_single_bool(Attribute::OAuth2JwtLegacyCryptoEnable)
|
||||
.unwrap_or(false)
|
||||
{
|
||||
SignatureAlgo::Rs256
|
||||
} else {
|
||||
trace!("{}", Attribute::Es256PrivateKeyDer);
|
||||
ent
|
||||
.get_ava_single_private_binary(Attribute::Es256PrivateKeyDer)
|
||||
.ok_or(OperationError::InvalidValueState)
|
||||
.and_then(|key_der| {
|
||||
JwsEs256Signer::from_es256_der(key_der)
|
||||
.map(|signer| Oauth2JwsSigner::ES256 { signer })
|
||||
.map_err(|e| {
|
||||
admin_error!(err = ?e, "Unable to load ES256 JwsSigner from DER");
|
||||
OperationError::CryptographyError
|
||||
})
|
||||
})?
|
||||
SignatureAlgo::Es256
|
||||
};
|
||||
|
||||
let prefer_short_username = ent
|
||||
|
@ -804,33 +775,31 @@ impl Oauth2ResourceServersWriteTransaction<'_> {
|
|||
let mut iss = self.inner.origin.clone();
|
||||
iss.set_path(&format!("/oauth2/openid/{name}"));
|
||||
|
||||
let scopes_supported: BTreeSet<String> =
|
||||
scope_maps
|
||||
let scopes_supported: BTreeSet<String> = scope_maps
|
||||
.values()
|
||||
.flat_map(|bts| bts.iter())
|
||||
|
||||
.chain(
|
||||
sup_scope_maps
|
||||
.values()
|
||||
.flat_map(|bts| bts.iter())
|
||||
)
|
||||
|
||||
.chain(sup_scope_maps.values().flat_map(|bts| bts.iter()))
|
||||
.cloned()
|
||||
.collect();
|
||||
|
||||
|
||||
let device_authorization_endpoint: Option<Url> = match cfg!(feature="dev-oauth2-device-flow") {
|
||||
let device_authorization_endpoint: Option<Url> =
|
||||
match cfg!(feature = "dev-oauth2-device-flow") {
|
||||
true => {
|
||||
match ent.get_ava_single_bool(Attribute::OAuth2DeviceFlowEnable).unwrap_or(false) {
|
||||
match ent
|
||||
.get_ava_single_bool(Attribute::OAuth2DeviceFlowEnable)
|
||||
.unwrap_or(false)
|
||||
{
|
||||
true => {
|
||||
let mut device_authorization_endpoint = self.inner.origin.clone();
|
||||
device_authorization_endpoint.set_path(uri::OAUTH2_AUTHORISE_DEVICE);
|
||||
let mut device_authorization_endpoint =
|
||||
self.inner.origin.clone();
|
||||
device_authorization_endpoint
|
||||
.set_path(uri::OAUTH2_AUTHORISE_DEVICE);
|
||||
Some(device_authorization_endpoint)
|
||||
},
|
||||
false => None
|
||||
}
|
||||
},
|
||||
false => {None}
|
||||
false => None,
|
||||
}
|
||||
}
|
||||
false => None,
|
||||
};
|
||||
let client_id = name.clone();
|
||||
let rscfg = Oauth2RS {
|
||||
|
@ -847,8 +816,8 @@ impl Oauth2ResourceServersWriteTransaction<'_> {
|
|||
client_scopes,
|
||||
client_sup_scopes,
|
||||
claim_map,
|
||||
token_fernet,
|
||||
jws_signer,
|
||||
sign_alg,
|
||||
key_object,
|
||||
iss,
|
||||
authorization_endpoint,
|
||||
token_endpoint,
|
||||
|
@ -919,14 +888,9 @@ impl IdmServerProxyWriteTransaction<'_> {
|
|||
// are either signed *or* encrypted, we need to check both options.
|
||||
|
||||
let (session_id, expiry, uuid) = if let Ok(jwsc) = JwsCompact::from_str(&revoke_req.token) {
|
||||
let access_token = match &o2rs.jws_signer {
|
||||
Oauth2JwsSigner::ES256 { signer } => signer
|
||||
.get_verifier()
|
||||
.and_then(|verifier| verifier.verify(&jwsc)),
|
||||
Oauth2JwsSigner::RS256 { signer } => signer
|
||||
.get_verifier()
|
||||
.and_then(|verifier| verifier.verify(&jwsc)),
|
||||
}
|
||||
let access_token = o2rs
|
||||
.key_object
|
||||
.jws_verify(&jwsc)
|
||||
.map_err(|err| {
|
||||
admin_error!(?err, "Unable to verify access token");
|
||||
Oauth2Error::InvalidRequest
|
||||
|
@ -948,17 +912,21 @@ impl IdmServerProxyWriteTransaction<'_> {
|
|||
(session_id, exp, uuid)
|
||||
} else {
|
||||
// Assume it's encrypted.
|
||||
let jwe_compact = JweCompact::from_str(&revoke_req.token).map_err(|_| {
|
||||
error!("Failed to deserialise a valid JWE");
|
||||
Oauth2Error::InvalidRequest
|
||||
})?;
|
||||
|
||||
let token: Oauth2TokenType = o2rs
|
||||
.token_fernet
|
||||
.decrypt(&revoke_req.token)
|
||||
.key_object
|
||||
.jwe_decrypt(&jwe_compact)
|
||||
.map_err(|_| {
|
||||
admin_error!("Failed to decrypt token revoke request");
|
||||
error!("Failed to decrypt token revoke request");
|
||||
Oauth2Error::InvalidRequest
|
||||
})
|
||||
.and_then(|data| {
|
||||
serde_json::from_slice(&data).map_err(|e| {
|
||||
admin_error!("Failed to deserialise token - {:?}", e);
|
||||
.and_then(|jwe| {
|
||||
jwe.from_json().map_err(|err| {
|
||||
error!(?err, "Failed to deserialise token");
|
||||
Oauth2Error::InvalidRequest
|
||||
})
|
||||
})?;
|
||||
|
@ -1185,19 +1153,23 @@ impl IdmServerProxyWriteTransaction<'_> {
|
|||
return Err(OperationError::InvalidSessionState);
|
||||
};
|
||||
|
||||
// Decode the consent req with our system fernet key. Use a ttl of 5 minutes.
|
||||
let consent_token_jwe = JweCompact::from_str(consent_token).map_err(|err| {
|
||||
error!(?err, "Consent token is not a valid jwe compact");
|
||||
OperationError::InvalidSessionState
|
||||
})?;
|
||||
|
||||
let consent_req: ConsentToken = self
|
||||
.oauth2rs
|
||||
.inner
|
||||
.fernet
|
||||
.decrypt_at_time(consent_token, Some(300), ct.as_secs())
|
||||
.map_err(|_| {
|
||||
admin_error!("Failed to decrypt consent request");
|
||||
.consent_key
|
||||
.decipher(&consent_token_jwe)
|
||||
.map_err(|err| {
|
||||
error!(?err, "Failed to decrypt consent request");
|
||||
OperationError::CryptographyError
|
||||
})
|
||||
.and_then(|data| {
|
||||
serde_json::from_slice(&data).map_err(|e| {
|
||||
admin_error!(err = ?e, "Failed to deserialise consent request");
|
||||
.and_then(|jwe| {
|
||||
jwe.from_json().map_err(|err| {
|
||||
error!(?err, "Failed to deserialise consent request");
|
||||
OperationError::SerdeJsonError
|
||||
})
|
||||
})?;
|
||||
|
@ -1214,6 +1186,15 @@ impl IdmServerProxyWriteTransaction<'_> {
|
|||
return Err(OperationError::InvalidSessionState);
|
||||
}
|
||||
|
||||
if consent_req.expiry <= ct.as_secs() {
|
||||
// Token is expired
|
||||
error!("Failed to decrypt consent request");
|
||||
return Err(OperationError::CryptographyError);
|
||||
}
|
||||
|
||||
// The exchange must be performed in the next 60 seconds.
|
||||
let expiry = ct.as_secs() + 60;
|
||||
|
||||
// Get the resource server config based on this client_id.
|
||||
let o2rs = self
|
||||
.oauth2rs
|
||||
|
@ -1227,22 +1208,29 @@ impl IdmServerProxyWriteTransaction<'_> {
|
|||
|
||||
// Extract the state, code challenge, redirect_uri
|
||||
let xchg_code = TokenExchangeCode {
|
||||
// uat: uat.clone(),
|
||||
account_uuid,
|
||||
session_id: ident.get_session_id(),
|
||||
expiry,
|
||||
code_challenge: consent_req.code_challenge,
|
||||
redirect_uri: consent_req.redirect_uri.clone(),
|
||||
scopes: consent_req.scopes.clone(),
|
||||
nonce: consent_req.nonce,
|
||||
};
|
||||
|
||||
// Encrypt the exchange token with the fernet key of the client resource server
|
||||
let code_data = serde_json::to_vec(&xchg_code).map_err(|e| {
|
||||
admin_error!(err = ?e, "Unable to encode xchg_code data");
|
||||
// Encrypt the exchange token
|
||||
let code_data_jwe = Jwe::into_json(&xchg_code).map_err(|err| {
|
||||
error!(?err, "Unable to encode xchg_code data");
|
||||
OperationError::SerdeJsonError
|
||||
})?;
|
||||
|
||||
let code = o2rs.token_fernet.encrypt_at_time(&code_data, ct.as_secs());
|
||||
let code = o2rs
|
||||
.key_object
|
||||
.jwe_a128gcm_encrypt(&code_data_jwe, ct)
|
||||
.map(|code| code.to_string())
|
||||
.map_err(|err| {
|
||||
error!(?err, "Unable to encrypt xchg_code");
|
||||
OperationError::CryptographyError
|
||||
})?;
|
||||
|
||||
// Everything is DONE! Now submit that it's all happy and the user consented correctly.
|
||||
// this will let them bypass consent steps in the future.
|
||||
|
@ -1283,21 +1271,31 @@ impl IdmServerProxyWriteTransaction<'_> {
|
|||
) -> Result<AccessTokenResponse, Oauth2Error> {
|
||||
// Check the token_req is within the valid time, and correctly signed for
|
||||
// this client.
|
||||
let jwe_compact = JweCompact::from_str(token_req_code).map_err(|_| {
|
||||
error!("Failed to deserialise a valid JWE");
|
||||
Oauth2Error::InvalidRequest
|
||||
})?;
|
||||
|
||||
let code_xchg: TokenExchangeCode = o2rs
|
||||
.token_fernet
|
||||
.decrypt_at_time(token_req_code, Some(60), ct.as_secs())
|
||||
.key_object
|
||||
.jwe_decrypt(&jwe_compact)
|
||||
.map_err(|_| {
|
||||
admin_error!("Failed to decrypt token exchange request");
|
||||
Oauth2Error::InvalidRequest
|
||||
})
|
||||
.and_then(|data| {
|
||||
serde_json::from_slice(&data).map_err(|e| {
|
||||
admin_error!("Failed to deserialise token exchange code - {:?}", e);
|
||||
.and_then(|jwe| {
|
||||
debug!(?jwe);
|
||||
jwe.from_json::<TokenExchangeCode>().map_err(|err| {
|
||||
error!(?err, "Failed to deserialise token exchange code");
|
||||
Oauth2Error::InvalidRequest
|
||||
})
|
||||
})?;
|
||||
|
||||
if code_xchg.expiry <= ct.as_secs() {
|
||||
error!("Expired token exchange request");
|
||||
return Err(Oauth2Error::InvalidRequest);
|
||||
}
|
||||
|
||||
// If we have a verifier present, we MUST assert that a code challenge is present!
|
||||
// It is worth noting here that code_xchg is *server issued* and encrypted, with
|
||||
// a short validity period. The client controlled value is in token_req.code_verifier
|
||||
|
@ -1380,17 +1378,22 @@ impl IdmServerProxyWriteTransaction<'_> {
|
|||
req_scopes: Option<&BTreeSet<String>>,
|
||||
ct: Duration,
|
||||
) -> Result<AccessTokenResponse, Oauth2Error> {
|
||||
let jwe_compact = JweCompact::from_str(refresh_token).map_err(|_| {
|
||||
error!("Failed to deserialise a valid JWE");
|
||||
Oauth2Error::InvalidRequest
|
||||
})?;
|
||||
|
||||
// Validate the refresh token decrypts and it's expiry is within the valid window.
|
||||
let token: Oauth2TokenType = o2rs
|
||||
.token_fernet
|
||||
.decrypt(refresh_token)
|
||||
.key_object
|
||||
.jwe_decrypt(&jwe_compact)
|
||||
.map_err(|_| {
|
||||
admin_error!("Failed to decrypt refresh token request");
|
||||
Oauth2Error::InvalidRequest
|
||||
})
|
||||
.and_then(|data| {
|
||||
serde_json::from_slice(&data).map_err(|e| {
|
||||
admin_error!("Failed to deserialise token - {:?}", e);
|
||||
.and_then(|jwe| {
|
||||
jwe.from_json().map_err(|err| {
|
||||
error!(?err, "Failed to deserialise token");
|
||||
Oauth2Error::InvalidRequest
|
||||
})
|
||||
})?;
|
||||
|
@ -1568,14 +1571,19 @@ impl IdmServerProxyWriteTransaction<'_> {
|
|||
nbf: iat,
|
||||
};
|
||||
|
||||
let access_token_data = serde_json::to_vec(&access_token_raw).map_err(|e| {
|
||||
admin_error!(err = ?e, "Unable to encode token data");
|
||||
let access_token_data = Jwe::into_json(&access_token_raw).map_err(|err| {
|
||||
error!(?err, "Unable to encode token data");
|
||||
Oauth2Error::ServerError(OperationError::SerdeJsonError)
|
||||
})?;
|
||||
|
||||
let access_token = o2rs
|
||||
.token_fernet
|
||||
.encrypt_at_time(&access_token_data, ct.as_secs());
|
||||
.key_object
|
||||
.jwe_a128gcm_encrypt(&access_token_data, ct)
|
||||
.map(|jwe| jwe.to_string())
|
||||
.map_err(|err| {
|
||||
error!(?err, "Unable to encode token data");
|
||||
Oauth2Error::ServerError(OperationError::CryptographyError)
|
||||
})?;
|
||||
|
||||
// Write the session to the db
|
||||
let session = Value::Oauth2Session(
|
||||
|
@ -1703,13 +1711,19 @@ impl IdmServerProxyWriteTransaction<'_> {
|
|||
};
|
||||
|
||||
trace!(?oidc);
|
||||
let oidc = JwsBuilder::into_json(&oidc)
|
||||
.map(|builder| builder.build())
|
||||
.map_err(|err| {
|
||||
admin_error!(?err, "Unable to encode access token data");
|
||||
Oauth2Error::ServerError(OperationError::InvalidState)
|
||||
})?;
|
||||
|
||||
let jwt_signed = match &o2rs.jws_signer {
|
||||
Oauth2JwsSigner::ES256 { signer } => signer.sign(&oidc),
|
||||
Oauth2JwsSigner::RS256 { signer } => signer.sign(&oidc),
|
||||
let jwt_signed = match o2rs.sign_alg {
|
||||
SignatureAlgo::Es256 => o2rs.key_object.jws_es256_sign(&oidc, ct),
|
||||
SignatureAlgo::Rs256 => o2rs.key_object.jws_rs256_sign(&oidc, ct),
|
||||
}
|
||||
.map_err(|e| {
|
||||
admin_error!(err = ?e, "Unable to encode uat data");
|
||||
.map_err(|err| {
|
||||
error!(?err, "Unable to encode oidc token data");
|
||||
Oauth2Error::ServerError(OperationError::InvalidState)
|
||||
})?;
|
||||
|
||||
|
@ -1742,14 +1756,14 @@ impl IdmServerProxyWriteTransaction<'_> {
|
|||
|
||||
let access_token_data = JwsBuilder::into_json(&access_token_data)
|
||||
.map(|builder| builder.set_typ(Some("at+jwt")).build())
|
||||
.map_err(|e| {
|
||||
admin_error!(err = ?e, "Unable to encode access token data");
|
||||
.map_err(|err| {
|
||||
error!(?err, "Unable to encode access token data");
|
||||
Oauth2Error::ServerError(OperationError::InvalidState)
|
||||
})?;
|
||||
|
||||
let access_token = match &o2rs.jws_signer {
|
||||
Oauth2JwsSigner::ES256 { signer } => signer.sign(&access_token_data),
|
||||
Oauth2JwsSigner::RS256 { signer } => signer.sign(&access_token_data),
|
||||
let access_token = match o2rs.sign_alg {
|
||||
SignatureAlgo::Es256 => o2rs.key_object.jws_es256_sign(&access_token_data, ct),
|
||||
SignatureAlgo::Rs256 => o2rs.key_object.jws_rs256_sign(&access_token_data, ct),
|
||||
}
|
||||
.map_err(|e| {
|
||||
admin_error!(err = ?e, "Unable to sign access token data");
|
||||
|
@ -1767,14 +1781,19 @@ impl IdmServerProxyWriteTransaction<'_> {
|
|||
nonce,
|
||||
};
|
||||
|
||||
let refresh_token_data = serde_json::to_vec(&refresh_token_raw).map_err(|e| {
|
||||
admin_error!(err = ?e, "Unable to encode token data");
|
||||
let refresh_token_data = Jwe::into_json(&refresh_token_raw).map_err(|err| {
|
||||
error!(?err, "Unable to encode token data");
|
||||
Oauth2Error::ServerError(OperationError::SerdeJsonError)
|
||||
})?;
|
||||
|
||||
let refresh_token = o2rs
|
||||
.token_fernet
|
||||
.encrypt_at_time(&refresh_token_data, ct.as_secs());
|
||||
.key_object
|
||||
.jwe_a128gcm_encrypt(&refresh_token_data, ct)
|
||||
.map(|jwe| jwe.to_string())
|
||||
.map_err(|err| {
|
||||
error!(?err, "Unable to encrypt token data");
|
||||
Oauth2Error::ServerError(OperationError::CryptographyError)
|
||||
})?;
|
||||
|
||||
// Write the session to the db even with the refresh path, we need to do
|
||||
// this to update the "not issued before" time.
|
||||
|
@ -1846,15 +1865,20 @@ impl IdmServerProxyWriteTransaction<'_> {
|
|||
}
|
||||
}
|
||||
|
||||
o2rs.token_fernet
|
||||
.decrypt(token)
|
||||
.map_err(|_| {
|
||||
admin_error!("Failed to decrypt token reflection request");
|
||||
let jwe_compact = JweCompact::from_str(token).map_err(|err| {
|
||||
error!(?err, "Failed to deserialise a valid JWE");
|
||||
OperationError::InvalidSessionState
|
||||
})?;
|
||||
|
||||
o2rs.key_object
|
||||
.jwe_decrypt(&jwe_compact)
|
||||
.map_err(|err| {
|
||||
error!(?err, "Failed to decrypt token reflection request");
|
||||
OperationError::CryptographyError
|
||||
})
|
||||
.and_then(|data| {
|
||||
serde_json::from_slice(&data).map_err(|e| {
|
||||
admin_error!("Failed to deserialise token exchange code - {:?}", e);
|
||||
.and_then(|jwe| {
|
||||
jwe.from_json().map_err(|err| {
|
||||
error!(?err, "Failed to deserialise token for reflection");
|
||||
OperationError::SerdeJsonError
|
||||
})
|
||||
})
|
||||
|
@ -2129,23 +2153,34 @@ impl IdmServerProxyReadTransaction<'_> {
|
|||
);
|
||||
}
|
||||
|
||||
// Xchg token expires in
|
||||
let expiry = ct.as_secs() + 60;
|
||||
|
||||
// Setup for the permit success
|
||||
let xchg_code = TokenExchangeCode {
|
||||
account_uuid,
|
||||
session_id,
|
||||
expiry,
|
||||
code_challenge,
|
||||
redirect_uri: auth_req.redirect_uri.clone(),
|
||||
scopes: granted_scopes.into_iter().collect(),
|
||||
nonce: auth_req.nonce.clone(),
|
||||
};
|
||||
|
||||
// Encrypt the exchange token with the fernet key of the client resource server
|
||||
let code_data = serde_json::to_vec(&xchg_code).map_err(|e| {
|
||||
admin_error!(err = ?e, "Unable to encode xchg_code data");
|
||||
// Encrypt the exchange token with the key of the client
|
||||
let code_data_jwe = Jwe::into_json(&xchg_code).map_err(|err| {
|
||||
error!(?err, "Unable to encode xchg_code data");
|
||||
Oauth2Error::ServerError(OperationError::SerdeJsonError)
|
||||
})?;
|
||||
|
||||
let code = o2rs.token_fernet.encrypt_at_time(&code_data, ct.as_secs());
|
||||
let code = o2rs
|
||||
.key_object
|
||||
.jwe_a128gcm_encrypt(&code_data_jwe, ct)
|
||||
.map(|jwe| jwe.to_string())
|
||||
.map_err(|err| {
|
||||
error!(?err, "Unable to encrypt xchg_code data");
|
||||
Oauth2Error::ServerError(OperationError::CryptographyError)
|
||||
})?;
|
||||
|
||||
Ok(AuthoriseResponse::Permitted(AuthorisePermitSuccess {
|
||||
redirect_uri: auth_req.redirect_uri.clone(),
|
||||
|
@ -2180,6 +2215,9 @@ impl IdmServerProxyReadTransaction<'_> {
|
|||
pii_scopes.insert(OAUTH2_SCOPE_SSH_PUBLICKEYS.to_string());
|
||||
}
|
||||
|
||||
// Consent token expires in
|
||||
let expiry = ct.as_secs() + 300;
|
||||
|
||||
// Subsequent we then return an encrypted session handle which allows
|
||||
// the user to indicate their consent to this authorisation.
|
||||
//
|
||||
|
@ -2188,6 +2226,7 @@ impl IdmServerProxyReadTransaction<'_> {
|
|||
let consent_req = ConsentToken {
|
||||
client_id: auth_req.client_id.clone(),
|
||||
ident_id: ident.get_event_origin_id(),
|
||||
expiry,
|
||||
session_id,
|
||||
state: auth_req.state.clone(),
|
||||
code_challenge,
|
||||
|
@ -2197,16 +2236,21 @@ impl IdmServerProxyReadTransaction<'_> {
|
|||
response_mode,
|
||||
};
|
||||
|
||||
let consent_data = serde_json::to_vec(&consent_req).map_err(|e| {
|
||||
admin_error!(err = ?e, "Unable to encode consent data");
|
||||
let consent_jwe = Jwe::into_json(&consent_req).map_err(|err| {
|
||||
error!(?err, "Unable to encode consent data");
|
||||
Oauth2Error::ServerError(OperationError::SerdeJsonError)
|
||||
})?;
|
||||
|
||||
let consent_token = self
|
||||
.oauth2rs
|
||||
.inner
|
||||
.fernet
|
||||
.encrypt_at_time(&consent_data, ct.as_secs());
|
||||
.consent_key
|
||||
.encipher::<JweA128GCMEncipher>(&consent_jwe)
|
||||
.map(|jwe_compact| jwe_compact.to_string())
|
||||
.map_err(|err| {
|
||||
error!(?err, "Unable to encrypt jwe");
|
||||
Oauth2Error::ServerError(OperationError::CryptographyError)
|
||||
})?;
|
||||
|
||||
Ok(AuthoriseResponse::ConsentRequested {
|
||||
client_name: o2rs.displayname.clone(),
|
||||
|
@ -2224,19 +2268,24 @@ impl IdmServerProxyReadTransaction<'_> {
|
|||
consent_token: &str,
|
||||
ct: Duration,
|
||||
) -> Result<AuthoriseReject, OperationError> {
|
||||
let jwe_compact = JweCompact::from_str(consent_token).map_err(|_| {
|
||||
error!("Failed to deserialise a valid JWE");
|
||||
OperationError::CryptographyError
|
||||
})?;
|
||||
|
||||
// Decode the consent req with our system fernet key. Use a ttl of 5 minutes.
|
||||
let consent_req: ConsentToken = self
|
||||
.oauth2rs
|
||||
.inner
|
||||
.fernet
|
||||
.decrypt_at_time(consent_token, Some(300), ct.as_secs())
|
||||
.consent_key
|
||||
.decipher(&jwe_compact)
|
||||
.map_err(|_| {
|
||||
admin_error!("Failed to decrypt consent request");
|
||||
OperationError::CryptographyError
|
||||
})
|
||||
.and_then(|data| {
|
||||
serde_json::from_slice(&data).map_err(|e| {
|
||||
admin_error!(err = ?e, "Failed to deserialise consent request");
|
||||
.and_then(|jwe| {
|
||||
jwe.from_json().map_err(|err| {
|
||||
error!(?err, "Failed to deserialise consent request");
|
||||
OperationError::SerdeJsonError
|
||||
})
|
||||
})?;
|
||||
|
@ -2253,6 +2302,12 @@ impl IdmServerProxyReadTransaction<'_> {
|
|||
return Err(OperationError::InvalidSessionState);
|
||||
}
|
||||
|
||||
if consent_req.expiry <= ct.as_secs() {
|
||||
// Token is expired
|
||||
error!("Failed to decrypt consent request");
|
||||
return Err(OperationError::CryptographyError);
|
||||
}
|
||||
|
||||
// Get the resource server config based on this client_id.
|
||||
let _o2rs = self
|
||||
.oauth2rs
|
||||
|
@ -2308,21 +2363,16 @@ impl IdmServerProxyReadTransaction<'_> {
|
|||
let prefer_short_username = o2rs.prefer_short_username;
|
||||
|
||||
if let Ok(jwsc) = JwsCompact::from_str(&intr_req.token) {
|
||||
let access_token = match &o2rs.jws_signer {
|
||||
Oauth2JwsSigner::ES256 { signer } => signer
|
||||
.get_verifier()
|
||||
.and_then(|verifier| verifier.verify(&jwsc)),
|
||||
Oauth2JwsSigner::RS256 { signer } => signer
|
||||
.get_verifier()
|
||||
.and_then(|verifier| verifier.verify(&jwsc)),
|
||||
}
|
||||
let access_token = o2rs
|
||||
.key_object
|
||||
.jws_verify(&jwsc)
|
||||
.map_err(|err| {
|
||||
admin_error!(?err, "Unable to verify access token");
|
||||
error!(?err, "Unable to verify access token");
|
||||
Oauth2Error::InvalidRequest
|
||||
})
|
||||
.and_then(|jws| {
|
||||
jws.from_json().map_err(|err| {
|
||||
admin_error!(?err, "Unable to deserialise access token");
|
||||
error!(?err, "Unable to deserialise access token");
|
||||
Oauth2Error::InvalidRequest
|
||||
})
|
||||
})?;
|
||||
|
@ -2398,16 +2448,21 @@ impl IdmServerProxyReadTransaction<'_> {
|
|||
jti: None,
|
||||
})
|
||||
} else {
|
||||
let jwe_compact = JweCompact::from_str(&intr_req.token).map_err(|_| {
|
||||
error!("Failed to deserialise a valid JWE");
|
||||
Oauth2Error::InvalidRequest
|
||||
})?;
|
||||
|
||||
let token: Oauth2TokenType = o2rs
|
||||
.token_fernet
|
||||
.decrypt(&intr_req.token)
|
||||
.key_object
|
||||
.jwe_decrypt(&jwe_compact)
|
||||
.map_err(|_| {
|
||||
admin_error!("Failed to decrypt token introspection request");
|
||||
Oauth2Error::InvalidRequest
|
||||
})
|
||||
.and_then(|data| {
|
||||
serde_json::from_slice(&data).map_err(|e| {
|
||||
admin_error!("Failed to deserialise token - {:?}", e);
|
||||
.and_then(|jwe| {
|
||||
jwe.from_json().map_err(|err| {
|
||||
error!(?err, "Failed to deserialise token");
|
||||
Oauth2Error::InvalidRequest
|
||||
})
|
||||
})?;
|
||||
|
@ -2495,21 +2550,16 @@ impl IdmServerProxyReadTransaction<'_> {
|
|||
&*(s as *const _)
|
||||
};
|
||||
|
||||
let access_token = match &o2rs.jws_signer {
|
||||
Oauth2JwsSigner::ES256 { signer } => signer
|
||||
.get_verifier()
|
||||
.and_then(|verifier| verifier.verify(&token)),
|
||||
Oauth2JwsSigner::RS256 { signer } => signer
|
||||
.get_verifier()
|
||||
.and_then(|verifier| verifier.verify(&token)),
|
||||
}
|
||||
let access_token = o2rs
|
||||
.key_object
|
||||
.jws_verify(&token)
|
||||
.map_err(|err| {
|
||||
admin_error!(?err, "Unable to verify access token");
|
||||
error!(?err, "Unable to verify access token");
|
||||
Oauth2Error::InvalidRequest
|
||||
})
|
||||
.and_then(|jws| {
|
||||
jws.from_json().map_err(|err| {
|
||||
admin_error!(?err, "Unable to deserialise access token");
|
||||
error!(?err, "Unable to deserialise access token");
|
||||
Oauth2Error::InvalidRequest
|
||||
})
|
||||
})?;
|
||||
|
@ -2685,9 +2735,9 @@ impl IdmServerProxyReadTransaction<'_> {
|
|||
|
||||
let subject_types_supported = vec![SubjectType::Public];
|
||||
|
||||
let id_token_signing_alg_values_supported = match &o2rs.jws_signer {
|
||||
Oauth2JwsSigner::ES256 { .. } => vec![IdTokenSignAlg::ES256],
|
||||
Oauth2JwsSigner::RS256 { .. } => vec![IdTokenSignAlg::RS256],
|
||||
let id_token_signing_alg_values_supported = match &o2rs.sign_alg {
|
||||
SignatureAlgo::Es256 => vec![IdTokenSignAlg::ES256],
|
||||
SignatureAlgo::Rs256 => vec![IdTokenSignAlg::RS256],
|
||||
};
|
||||
|
||||
let userinfo_signing_alg_values_supported = None;
|
||||
|
@ -2818,15 +2868,18 @@ impl IdmServerProxyReadTransaction<'_> {
|
|||
OperationError::NoMatchingEntries
|
||||
})?;
|
||||
|
||||
match &o2rs.jws_signer {
|
||||
Oauth2JwsSigner::ES256 { signer } => signer.public_key_as_jwk(),
|
||||
Oauth2JwsSigner::RS256 { signer } => signer.public_key_as_jwk(),
|
||||
// How do we return only the active signing algo types?
|
||||
|
||||
error!(sign_alg = ?o2rs.sign_alg);
|
||||
|
||||
match o2rs.sign_alg {
|
||||
SignatureAlgo::Es256 => o2rs.key_object.jws_es256_jwks(),
|
||||
SignatureAlgo::Rs256 => o2rs.key_object.jws_rs256_jwks(),
|
||||
}
|
||||
.map_err(|e| {
|
||||
admin_error!("Unable to retrieve public key for {} - {:?}", o2rs.name, e);
|
||||
.ok_or_else(|| {
|
||||
error!(o2_client = ?o2rs.name, "Unable to retrieve public keys");
|
||||
OperationError::InvalidState
|
||||
})
|
||||
.map(|jwk| JwkKeySet { keys: vec![jwk] })
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -4699,11 +4752,11 @@ mod tests {
|
|||
);
|
||||
|
||||
// Invalid consent token
|
||||
assert!(
|
||||
assert_eq!(
|
||||
idms_prox_read
|
||||
.check_oauth2_authorise_reject(&ident, "not a token", ct)
|
||||
.unwrap_err()
|
||||
== OperationError::CryptographyError
|
||||
.unwrap_err(),
|
||||
OperationError::CryptographyError
|
||||
);
|
||||
|
||||
// Wrong ident
|
||||
|
|
|
@ -1,28 +1,3 @@
|
|||
use std::convert::TryFrom;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
|
||||
use kanidm_lib_crypto::CryptoPolicy;
|
||||
|
||||
use compact_jwt::{Jwk, JwsCompact};
|
||||
use concread::bptree::{BptreeMap, BptreeMapReadTxn, BptreeMapWriteTxn};
|
||||
use concread::cowcell::CowCellReadTxn;
|
||||
use concread::hashmap::HashMap;
|
||||
use kanidm_proto::internal::{
|
||||
ApiToken, BackupCodesView, CredentialStatus, PasswordFeedback, RadiusAuthToken, ScimSyncToken,
|
||||
UatPurpose, UserAuthToken,
|
||||
};
|
||||
use kanidm_proto::v1::{UnixGroupToken, UnixUserToken};
|
||||
use rand::prelude::*;
|
||||
use tokio::sync::mpsc::{
|
||||
unbounded_channel as unbounded, UnboundedReceiver as Receiver, UnboundedSender as Sender,
|
||||
};
|
||||
use tokio::sync::{Mutex, Semaphore};
|
||||
use tracing::trace;
|
||||
use url::Url;
|
||||
use webauthn_rs::prelude::{Webauthn, WebauthnBuilder};
|
||||
use zxcvbn::{zxcvbn, Score};
|
||||
|
||||
use super::event::ReadBackupCodeEvent;
|
||||
use super::ldap::{LdapBoundToken, LdapSession};
|
||||
use crate::credential::{softlock::CredSoftLock, Credential};
|
||||
|
@ -38,9 +13,6 @@ use crate::idm::delayed::{
|
|||
AuthSessionRecord, BackupCodeRemoval, DelayedAction, PasswordUpgrade, UnixPasswordUpgrade,
|
||||
WebauthnCounterIncrement,
|
||||
};
|
||||
|
||||
#[cfg(test)]
|
||||
use crate::idm::event::PasswordChangeEvent;
|
||||
use crate::idm::event::{AuthEvent, AuthEventStep, AuthResult};
|
||||
use crate::idm::event::{
|
||||
CredentialStatusEvent, LdapAuthEvent, LdapTokenAuthEvent, RadiusAuthTokenEvent,
|
||||
|
@ -61,6 +33,31 @@ use crate::server::keys::KeyProvidersTransaction;
|
|||
use crate::server::DomainInfo;
|
||||
use crate::utils::{password_from_random, readable_password_from_random, uuid_from_duration, Sid};
|
||||
use crate::value::{Session, SessionState};
|
||||
use compact_jwt::{Jwk, JwsCompact};
|
||||
use concread::bptree::{BptreeMap, BptreeMapReadTxn, BptreeMapWriteTxn};
|
||||
use concread::cowcell::CowCellReadTxn;
|
||||
use concread::hashmap::HashMap;
|
||||
use kanidm_lib_crypto::CryptoPolicy;
|
||||
use kanidm_proto::internal::{
|
||||
ApiToken, BackupCodesView, CredentialStatus, PasswordFeedback, RadiusAuthToken, ScimSyncToken,
|
||||
UatPurpose, UserAuthToken,
|
||||
};
|
||||
use kanidm_proto::v1::{UnixGroupToken, UnixUserToken};
|
||||
use rand::prelude::*;
|
||||
use std::convert::TryFrom;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
use tokio::sync::mpsc::{
|
||||
unbounded_channel as unbounded, UnboundedReceiver as Receiver, UnboundedSender as Sender,
|
||||
};
|
||||
use tokio::sync::{Mutex, Semaphore};
|
||||
use tracing::trace;
|
||||
use url::Url;
|
||||
use webauthn_rs::prelude::{Webauthn, WebauthnBuilder};
|
||||
use zxcvbn::{zxcvbn, Score};
|
||||
|
||||
#[cfg(test)]
|
||||
use crate::idm::event::PasswordChangeEvent;
|
||||
|
||||
pub(crate) type AuthSessionMutex = Arc<Mutex<AuthSession>>;
|
||||
pub(crate) type CredSoftLockMutex = Arc<Mutex<CredSoftLock>>;
|
||||
|
@ -158,14 +155,12 @@ impl IdmServer {
|
|||
let (audit_tx, audit_rx) = unbounded();
|
||||
|
||||
// Get the domain name, as the relying party id.
|
||||
let (rp_id, rp_name, domain_level, oauth2rs_set, application_set) = {
|
||||
let (rp_id, rp_name, application_set) = {
|
||||
let mut qs_read = qs.read().await?;
|
||||
(
|
||||
qs_read.get_domain_name().to_string(),
|
||||
qs_read.get_domain_display_name().to_string(),
|
||||
qs_read.get_domain_version(),
|
||||
// Add a read/reload of all oauth2 configurations.
|
||||
qs_read.get_oauth2rs_set()?,
|
||||
qs_read.get_applications_set()?,
|
||||
)
|
||||
};
|
||||
|
@ -201,10 +196,9 @@ impl IdmServer {
|
|||
OperationError::InvalidState
|
||||
})?;
|
||||
|
||||
let oauth2rs = Oauth2ResourceServers::try_from((oauth2rs_set, origin_url, domain_level))
|
||||
.map_err(|e| {
|
||||
admin_error!("Failed to load oauth2 resource servers - {:?}", e);
|
||||
e
|
||||
let oauth2rs = Oauth2ResourceServers::new(origin_url).map_err(|err| {
|
||||
error!(?err, "Failed to load oauth2 resource servers");
|
||||
err
|
||||
})?;
|
||||
|
||||
let applications = LdapApplications::try_from(application_set).map_err(|e| {
|
||||
|
@ -2121,6 +2115,12 @@ impl IdmServerProxyWriteTransaction<'_> {
|
|||
|
||||
#[instrument(level = "debug", skip_all)]
|
||||
pub fn commit(mut self) -> Result<(), OperationError> {
|
||||
// The problem we have here is that we need the qs_write layer to reload *first*
|
||||
// so that things like schema and key objects are ready.
|
||||
self.qs_write.reload()?;
|
||||
|
||||
// Now that's done, let's proceed.
|
||||
|
||||
if self.qs_write.get_changed_app() {
|
||||
self.qs_write
|
||||
.get_applications_set()
|
||||
|
@ -2128,9 +2128,11 @@ impl IdmServerProxyWriteTransaction<'_> {
|
|||
}
|
||||
if self.qs_write.get_changed_oauth2() {
|
||||
let domain_level = self.qs_write.get_domain_version();
|
||||
self.qs_write
|
||||
.get_oauth2rs_set()
|
||||
.and_then(|oauth2rs_set| self.oauth2rs.reload(oauth2rs_set, domain_level))?;
|
||||
self.qs_write.get_oauth2rs_set().and_then(|oauth2rs_set| {
|
||||
let key_providers = self.qs_write.get_key_providers();
|
||||
self.oauth2rs
|
||||
.reload(oauth2rs_set, key_providers, domain_level)
|
||||
})?;
|
||||
// Clear the flag to indicate we completed the reload.
|
||||
self.qs_write.clear_changed_oauth2();
|
||||
}
|
||||
|
|
|
@ -614,315 +614,7 @@ lazy_static! {
|
|||
}
|
||||
|
||||
lazy_static! {
|
||||
pub static ref IDM_ACP_OAUTH2_MANAGE_DL4: BuiltinAcp = BuiltinAcp {
|
||||
classes: vec![
|
||||
EntryClass::Object,
|
||||
EntryClass::AccessControlProfile,
|
||||
EntryClass::AccessControlCreate,
|
||||
EntryClass::AccessControlDelete,
|
||||
EntryClass::AccessControlModify,
|
||||
EntryClass::AccessControlSearch
|
||||
],
|
||||
name: "idm_acp_hp_oauth2_manage_priv",
|
||||
uuid: UUID_IDM_ACP_OAUTH2_MANAGE_V1,
|
||||
description: "Builtin IDM Control for managing oauth2 resource server integrations.",
|
||||
receiver: BuiltinAcpReceiver::Group(vec![UUID_IDM_OAUTH2_ADMINS]),
|
||||
target: BuiltinAcpTarget::Filter(ProtoFilter::And(vec![
|
||||
match_class_filter!(EntryClass::OAuth2ResourceServer),
|
||||
FILTER_ANDNOT_TOMBSTONE_OR_RECYCLED.clone(),
|
||||
])),
|
||||
search_attrs: vec![
|
||||
Attribute::Class,
|
||||
Attribute::Description,
|
||||
Attribute::DisplayName,
|
||||
Attribute::OAuth2RsName,
|
||||
Attribute::OAuth2RsOrigin,
|
||||
Attribute::OAuth2RsOriginLanding,
|
||||
Attribute::OAuth2RsScopeMap,
|
||||
Attribute::OAuth2RsSupScopeMap,
|
||||
Attribute::OAuth2RsBasicSecret,
|
||||
Attribute::OAuth2RsTokenKey,
|
||||
Attribute::Es256PrivateKeyDer,
|
||||
Attribute::OAuth2AllowInsecureClientDisablePkce,
|
||||
Attribute::Rs256PrivateKeyDer,
|
||||
Attribute::OAuth2JwtLegacyCryptoEnable,
|
||||
Attribute::OAuth2PreferShortUsername,
|
||||
Attribute::OAuth2AllowLocalhostRedirect,
|
||||
Attribute::OAuth2RsClaimMap,
|
||||
Attribute::Image,
|
||||
],
|
||||
modify_removed_attrs: vec![
|
||||
Attribute::Description,
|
||||
Attribute::DisplayName,
|
||||
Attribute::OAuth2RsName,
|
||||
Attribute::OAuth2RsOrigin,
|
||||
Attribute::OAuth2RsOriginLanding,
|
||||
Attribute::OAuth2RsScopeMap,
|
||||
Attribute::OAuth2RsSupScopeMap,
|
||||
Attribute::OAuth2RsBasicSecret,
|
||||
Attribute::OAuth2RsTokenKey,
|
||||
Attribute::Es256PrivateKeyDer,
|
||||
Attribute::OAuth2AllowInsecureClientDisablePkce,
|
||||
Attribute::Rs256PrivateKeyDer,
|
||||
Attribute::OAuth2JwtLegacyCryptoEnable,
|
||||
Attribute::OAuth2PreferShortUsername,
|
||||
Attribute::OAuth2AllowLocalhostRedirect,
|
||||
Attribute::OAuth2RsClaimMap,
|
||||
Attribute::Image,
|
||||
],
|
||||
modify_present_attrs: vec![
|
||||
Attribute::Description,
|
||||
Attribute::DisplayName,
|
||||
Attribute::OAuth2RsName,
|
||||
Attribute::OAuth2RsOrigin,
|
||||
Attribute::OAuth2RsOriginLanding,
|
||||
Attribute::OAuth2RsSupScopeMap,
|
||||
Attribute::OAuth2RsScopeMap,
|
||||
Attribute::OAuth2AllowInsecureClientDisablePkce,
|
||||
Attribute::OAuth2JwtLegacyCryptoEnable,
|
||||
Attribute::OAuth2PreferShortUsername,
|
||||
Attribute::OAuth2AllowLocalhostRedirect,
|
||||
Attribute::OAuth2RsClaimMap,
|
||||
Attribute::Image,
|
||||
],
|
||||
create_attrs: vec![
|
||||
Attribute::Class,
|
||||
Attribute::Description,
|
||||
Attribute::DisplayName,
|
||||
Attribute::OAuth2RsName,
|
||||
Attribute::OAuth2RsOrigin,
|
||||
Attribute::OAuth2RsOriginLanding,
|
||||
Attribute::OAuth2RsSupScopeMap,
|
||||
Attribute::OAuth2RsScopeMap,
|
||||
Attribute::OAuth2AllowInsecureClientDisablePkce,
|
||||
Attribute::OAuth2JwtLegacyCryptoEnable,
|
||||
Attribute::OAuth2PreferShortUsername,
|
||||
Attribute::OAuth2AllowLocalhostRedirect,
|
||||
Attribute::OAuth2RsClaimMap,
|
||||
Attribute::Image,
|
||||
],
|
||||
create_classes: vec![
|
||||
EntryClass::Object,
|
||||
EntryClass::OAuth2ResourceServer,
|
||||
EntryClass::OAuth2ResourceServerBasic,
|
||||
EntryClass::OAuth2ResourceServerPublic,
|
||||
],
|
||||
..Default::default()
|
||||
};
|
||||
}
|
||||
|
||||
lazy_static! {
|
||||
pub static ref IDM_ACP_OAUTH2_MANAGE_DL5: BuiltinAcp = BuiltinAcp {
|
||||
classes: vec![
|
||||
EntryClass::Object,
|
||||
EntryClass::AccessControlProfile,
|
||||
EntryClass::AccessControlCreate,
|
||||
EntryClass::AccessControlDelete,
|
||||
EntryClass::AccessControlModify,
|
||||
EntryClass::AccessControlSearch
|
||||
],
|
||||
name: "idm_acp_hp_oauth2_manage_priv",
|
||||
uuid: UUID_IDM_ACP_OAUTH2_MANAGE_V1,
|
||||
description: "Builtin IDM Control for managing oauth2 resource server integrations.",
|
||||
receiver: BuiltinAcpReceiver::Group(vec![UUID_IDM_OAUTH2_ADMINS]),
|
||||
target: BuiltinAcpTarget::Filter(ProtoFilter::And(vec![
|
||||
match_class_filter!(EntryClass::OAuth2ResourceServer),
|
||||
FILTER_ANDNOT_TOMBSTONE_OR_RECYCLED.clone(),
|
||||
])),
|
||||
search_attrs: vec![
|
||||
Attribute::Class,
|
||||
Attribute::Description,
|
||||
Attribute::DisplayName,
|
||||
Attribute::Name,
|
||||
Attribute::Spn,
|
||||
Attribute::OAuth2Session,
|
||||
Attribute::OAuth2RsOrigin,
|
||||
Attribute::OAuth2RsOriginLanding,
|
||||
Attribute::OAuth2RsScopeMap,
|
||||
Attribute::OAuth2RsSupScopeMap,
|
||||
Attribute::OAuth2RsBasicSecret,
|
||||
Attribute::OAuth2RsTokenKey,
|
||||
Attribute::Es256PrivateKeyDer,
|
||||
Attribute::OAuth2AllowInsecureClientDisablePkce,
|
||||
Attribute::Rs256PrivateKeyDer,
|
||||
Attribute::OAuth2JwtLegacyCryptoEnable,
|
||||
Attribute::OAuth2PreferShortUsername,
|
||||
Attribute::OAuth2AllowLocalhostRedirect,
|
||||
Attribute::OAuth2RsClaimMap,
|
||||
Attribute::Image,
|
||||
],
|
||||
modify_removed_attrs: vec![
|
||||
Attribute::Description,
|
||||
Attribute::DisplayName,
|
||||
Attribute::Name,
|
||||
Attribute::OAuth2Session,
|
||||
Attribute::OAuth2RsOrigin,
|
||||
Attribute::OAuth2RsOriginLanding,
|
||||
Attribute::OAuth2RsScopeMap,
|
||||
Attribute::OAuth2RsSupScopeMap,
|
||||
Attribute::OAuth2RsBasicSecret,
|
||||
Attribute::OAuth2RsTokenKey,
|
||||
Attribute::Es256PrivateKeyDer,
|
||||
Attribute::OAuth2AllowInsecureClientDisablePkce,
|
||||
Attribute::Rs256PrivateKeyDer,
|
||||
Attribute::OAuth2JwtLegacyCryptoEnable,
|
||||
Attribute::OAuth2PreferShortUsername,
|
||||
Attribute::OAuth2AllowLocalhostRedirect,
|
||||
Attribute::OAuth2RsClaimMap,
|
||||
Attribute::Image,
|
||||
],
|
||||
modify_present_attrs: vec![
|
||||
Attribute::Description,
|
||||
Attribute::DisplayName,
|
||||
Attribute::Name,
|
||||
Attribute::OAuth2RsOrigin,
|
||||
Attribute::OAuth2RsOriginLanding,
|
||||
Attribute::OAuth2RsSupScopeMap,
|
||||
Attribute::OAuth2RsScopeMap,
|
||||
Attribute::OAuth2AllowInsecureClientDisablePkce,
|
||||
Attribute::OAuth2JwtLegacyCryptoEnable,
|
||||
Attribute::OAuth2PreferShortUsername,
|
||||
Attribute::OAuth2AllowLocalhostRedirect,
|
||||
Attribute::OAuth2RsClaimMap,
|
||||
Attribute::Image,
|
||||
],
|
||||
create_attrs: vec![
|
||||
Attribute::Class,
|
||||
Attribute::Description,
|
||||
Attribute::Name,
|
||||
Attribute::DisplayName,
|
||||
Attribute::OAuth2RsName,
|
||||
Attribute::OAuth2RsOrigin,
|
||||
Attribute::OAuth2RsOriginLanding,
|
||||
Attribute::OAuth2RsSupScopeMap,
|
||||
Attribute::OAuth2RsScopeMap,
|
||||
Attribute::OAuth2AllowInsecureClientDisablePkce,
|
||||
Attribute::OAuth2JwtLegacyCryptoEnable,
|
||||
Attribute::OAuth2PreferShortUsername,
|
||||
Attribute::OAuth2AllowLocalhostRedirect,
|
||||
Attribute::OAuth2RsClaimMap,
|
||||
Attribute::Image,
|
||||
],
|
||||
create_classes: vec![
|
||||
EntryClass::Object,
|
||||
EntryClass::Account,
|
||||
EntryClass::OAuth2ResourceServer,
|
||||
EntryClass::OAuth2ResourceServerBasic,
|
||||
EntryClass::OAuth2ResourceServerPublic,
|
||||
],
|
||||
..Default::default()
|
||||
};
|
||||
}
|
||||
|
||||
lazy_static! {
|
||||
pub static ref IDM_ACP_OAUTH2_MANAGE_DL7: BuiltinAcp = BuiltinAcp {
|
||||
classes: vec![
|
||||
EntryClass::Object,
|
||||
EntryClass::AccessControlProfile,
|
||||
EntryClass::AccessControlCreate,
|
||||
EntryClass::AccessControlDelete,
|
||||
EntryClass::AccessControlModify,
|
||||
EntryClass::AccessControlSearch
|
||||
],
|
||||
name: "idm_acp_hp_oauth2_manage_priv",
|
||||
uuid: UUID_IDM_ACP_OAUTH2_MANAGE_V1,
|
||||
description: "Builtin IDM Control for managing oauth2 resource server integrations.",
|
||||
receiver: BuiltinAcpReceiver::Group(vec![UUID_IDM_OAUTH2_ADMINS]),
|
||||
target: BuiltinAcpTarget::Filter(ProtoFilter::And(vec![
|
||||
match_class_filter!(EntryClass::OAuth2ResourceServer),
|
||||
FILTER_ANDNOT_TOMBSTONE_OR_RECYCLED.clone(),
|
||||
])),
|
||||
search_attrs: vec![
|
||||
Attribute::Class,
|
||||
Attribute::Description,
|
||||
Attribute::DisplayName,
|
||||
Attribute::Name,
|
||||
Attribute::Spn,
|
||||
Attribute::OAuth2Session,
|
||||
Attribute::OAuth2RsOrigin,
|
||||
Attribute::OAuth2RsOriginLanding,
|
||||
Attribute::OAuth2RsScopeMap,
|
||||
Attribute::OAuth2RsSupScopeMap,
|
||||
Attribute::OAuth2RsBasicSecret,
|
||||
Attribute::OAuth2RsTokenKey,
|
||||
Attribute::Es256PrivateKeyDer,
|
||||
Attribute::OAuth2AllowInsecureClientDisablePkce,
|
||||
Attribute::Rs256PrivateKeyDer,
|
||||
Attribute::OAuth2JwtLegacyCryptoEnable,
|
||||
Attribute::OAuth2PreferShortUsername,
|
||||
Attribute::OAuth2AllowLocalhostRedirect,
|
||||
Attribute::OAuth2RsClaimMap,
|
||||
Attribute::Image,
|
||||
Attribute::OAuth2StrictRedirectUri,
|
||||
],
|
||||
modify_removed_attrs: vec![
|
||||
Attribute::Description,
|
||||
Attribute::DisplayName,
|
||||
Attribute::Name,
|
||||
Attribute::OAuth2Session,
|
||||
Attribute::OAuth2RsOrigin,
|
||||
Attribute::OAuth2RsOriginLanding,
|
||||
Attribute::OAuth2RsScopeMap,
|
||||
Attribute::OAuth2RsSupScopeMap,
|
||||
Attribute::OAuth2RsBasicSecret,
|
||||
Attribute::OAuth2RsTokenKey,
|
||||
Attribute::Es256PrivateKeyDer,
|
||||
Attribute::OAuth2AllowInsecureClientDisablePkce,
|
||||
Attribute::Rs256PrivateKeyDer,
|
||||
Attribute::OAuth2JwtLegacyCryptoEnable,
|
||||
Attribute::OAuth2PreferShortUsername,
|
||||
Attribute::OAuth2AllowLocalhostRedirect,
|
||||
Attribute::OAuth2RsClaimMap,
|
||||
Attribute::Image,
|
||||
Attribute::OAuth2StrictRedirectUri,
|
||||
],
|
||||
modify_present_attrs: vec![
|
||||
Attribute::Description,
|
||||
Attribute::DisplayName,
|
||||
Attribute::Name,
|
||||
Attribute::OAuth2RsOrigin,
|
||||
Attribute::OAuth2RsOriginLanding,
|
||||
Attribute::OAuth2RsSupScopeMap,
|
||||
Attribute::OAuth2RsScopeMap,
|
||||
Attribute::OAuth2AllowInsecureClientDisablePkce,
|
||||
Attribute::OAuth2JwtLegacyCryptoEnable,
|
||||
Attribute::OAuth2PreferShortUsername,
|
||||
Attribute::OAuth2AllowLocalhostRedirect,
|
||||
Attribute::OAuth2RsClaimMap,
|
||||
Attribute::Image,
|
||||
Attribute::OAuth2StrictRedirectUri,
|
||||
],
|
||||
create_attrs: vec![
|
||||
Attribute::Class,
|
||||
Attribute::Description,
|
||||
Attribute::Name,
|
||||
Attribute::DisplayName,
|
||||
Attribute::OAuth2RsName,
|
||||
Attribute::OAuth2RsOrigin,
|
||||
Attribute::OAuth2RsOriginLanding,
|
||||
Attribute::OAuth2RsSupScopeMap,
|
||||
Attribute::OAuth2RsScopeMap,
|
||||
Attribute::OAuth2AllowInsecureClientDisablePkce,
|
||||
Attribute::OAuth2JwtLegacyCryptoEnable,
|
||||
Attribute::OAuth2PreferShortUsername,
|
||||
Attribute::OAuth2AllowLocalhostRedirect,
|
||||
Attribute::OAuth2RsClaimMap,
|
||||
Attribute::Image,
|
||||
Attribute::OAuth2StrictRedirectUri,
|
||||
],
|
||||
create_classes: vec![
|
||||
EntryClass::Object,
|
||||
EntryClass::Account,
|
||||
EntryClass::OAuth2ResourceServer,
|
||||
EntryClass::OAuth2ResourceServerBasic,
|
||||
EntryClass::OAuth2ResourceServerPublic,
|
||||
],
|
||||
..Default::default()
|
||||
};
|
||||
}
|
||||
|
||||
lazy_static! {
|
||||
pub static ref IDM_ACP_OAUTH2_MANAGE_DL9: BuiltinAcp = BuiltinAcp {
|
||||
pub static ref IDM_ACP_OAUTH2_MANAGE: BuiltinAcp = BuiltinAcp {
|
||||
classes: vec![
|
||||
EntryClass::Object,
|
||||
EntryClass::AccessControlProfile,
|
||||
|
@ -951,10 +643,7 @@ lazy_static! {
|
|||
Attribute::OAuth2RsScopeMap,
|
||||
Attribute::OAuth2RsSupScopeMap,
|
||||
Attribute::OAuth2RsBasicSecret,
|
||||
Attribute::OAuth2RsTokenKey,
|
||||
Attribute::Es256PrivateKeyDer,
|
||||
Attribute::OAuth2AllowInsecureClientDisablePkce,
|
||||
Attribute::Rs256PrivateKeyDer,
|
||||
Attribute::OAuth2JwtLegacyCryptoEnable,
|
||||
Attribute::OAuth2PreferShortUsername,
|
||||
Attribute::OAuth2AllowLocalhostRedirect,
|
||||
|
@ -962,6 +651,7 @@ lazy_static! {
|
|||
Attribute::Image,
|
||||
Attribute::OAuth2StrictRedirectUri,
|
||||
Attribute::OAuth2DeviceFlowEnable,
|
||||
Attribute::KeyInternalData,
|
||||
],
|
||||
modify_removed_attrs: vec![
|
||||
Attribute::Description,
|
||||
|
@ -973,10 +663,7 @@ lazy_static! {
|
|||
Attribute::OAuth2RsScopeMap,
|
||||
Attribute::OAuth2RsSupScopeMap,
|
||||
Attribute::OAuth2RsBasicSecret,
|
||||
Attribute::OAuth2RsTokenKey,
|
||||
Attribute::Es256PrivateKeyDer,
|
||||
Attribute::OAuth2AllowInsecureClientDisablePkce,
|
||||
Attribute::Rs256PrivateKeyDer,
|
||||
Attribute::OAuth2JwtLegacyCryptoEnable,
|
||||
Attribute::OAuth2PreferShortUsername,
|
||||
Attribute::OAuth2AllowLocalhostRedirect,
|
||||
|
@ -984,6 +671,8 @@ lazy_static! {
|
|||
Attribute::Image,
|
||||
Attribute::OAuth2StrictRedirectUri,
|
||||
Attribute::OAuth2DeviceFlowEnable,
|
||||
Attribute::KeyActionRevoke,
|
||||
Attribute::KeyActionRotate,
|
||||
],
|
||||
modify_present_attrs: vec![
|
||||
Attribute::Description,
|
||||
|
@ -1001,6 +690,8 @@ lazy_static! {
|
|||
Attribute::Image,
|
||||
Attribute::OAuth2StrictRedirectUri,
|
||||
Attribute::OAuth2DeviceFlowEnable,
|
||||
Attribute::KeyActionRevoke,
|
||||
Attribute::KeyActionRotate,
|
||||
],
|
||||
create_attrs: vec![
|
||||
Attribute::Class,
|
||||
|
@ -1032,122 +723,6 @@ lazy_static! {
|
|||
};
|
||||
}
|
||||
|
||||
lazy_static! {
|
||||
pub static ref IDM_ACP_DOMAIN_ADMIN_DL6: BuiltinAcp = BuiltinAcp {
|
||||
classes: vec![
|
||||
EntryClass::Object,
|
||||
EntryClass::AccessControlProfile,
|
||||
EntryClass::AccessControlModify,
|
||||
EntryClass::AccessControlSearch
|
||||
],
|
||||
name: "idm_acp_domain_admin",
|
||||
uuid: UUID_IDM_ACP_DOMAIN_ADMIN_V1,
|
||||
description: "Builtin IDM Control for granting domain info administration locally",
|
||||
receiver: BuiltinAcpReceiver::Group(vec![UUID_DOMAIN_ADMINS]),
|
||||
target: BuiltinAcpTarget::Filter(ProtoFilter::And(vec![
|
||||
ProtoFilter::Eq(
|
||||
Attribute::Uuid.to_string(),
|
||||
STR_UUID_DOMAIN_INFO.to_string()
|
||||
),
|
||||
FILTER_ANDNOT_TOMBSTONE_OR_RECYCLED.clone()
|
||||
])),
|
||||
search_attrs: vec![
|
||||
Attribute::Class,
|
||||
Attribute::Name,
|
||||
Attribute::Uuid,
|
||||
Attribute::DomainDisplayName,
|
||||
Attribute::DomainName,
|
||||
Attribute::DomainLdapBasedn,
|
||||
Attribute::LdapMaxQueryableAttrs,
|
||||
Attribute::DomainSsid,
|
||||
Attribute::DomainUuid,
|
||||
// Grants read access to the key object.
|
||||
// But this means we have to specify every type of key object?
|
||||
// Future william problem ...
|
||||
Attribute::KeyInternalData,
|
||||
Attribute::LdapAllowUnixPwBind,
|
||||
Attribute::Version,
|
||||
],
|
||||
modify_removed_attrs: vec![
|
||||
Attribute::DomainDisplayName,
|
||||
Attribute::DomainSsid,
|
||||
Attribute::DomainLdapBasedn,
|
||||
Attribute::LdapMaxQueryableAttrs,
|
||||
Attribute::LdapAllowUnixPwBind,
|
||||
Attribute::KeyActionRevoke,
|
||||
Attribute::KeyActionRotate,
|
||||
],
|
||||
modify_present_attrs: vec![
|
||||
Attribute::DomainDisplayName,
|
||||
Attribute::DomainLdapBasedn,
|
||||
Attribute::LdapMaxQueryableAttrs,
|
||||
Attribute::DomainSsid,
|
||||
Attribute::LdapAllowUnixPwBind,
|
||||
Attribute::KeyActionRevoke,
|
||||
Attribute::KeyActionRotate,
|
||||
],
|
||||
..Default::default()
|
||||
};
|
||||
}
|
||||
|
||||
lazy_static! {
|
||||
pub static ref IDM_ACP_DOMAIN_ADMIN_DL8: BuiltinAcp = BuiltinAcp {
|
||||
classes: vec![
|
||||
EntryClass::Object,
|
||||
EntryClass::AccessControlProfile,
|
||||
EntryClass::AccessControlModify,
|
||||
EntryClass::AccessControlSearch
|
||||
],
|
||||
name: "idm_acp_domain_admin",
|
||||
uuid: UUID_IDM_ACP_DOMAIN_ADMIN_V1,
|
||||
description: "Builtin IDM Control for granting domain info administration locally",
|
||||
receiver: BuiltinAcpReceiver::Group(vec![UUID_DOMAIN_ADMINS]),
|
||||
target: BuiltinAcpTarget::Filter(ProtoFilter::And(vec![
|
||||
ProtoFilter::Eq(
|
||||
Attribute::Uuid.to_string(),
|
||||
STR_UUID_DOMAIN_INFO.to_string()
|
||||
),
|
||||
FILTER_ANDNOT_TOMBSTONE_OR_RECYCLED.clone()
|
||||
])),
|
||||
search_attrs: vec![
|
||||
Attribute::Class,
|
||||
Attribute::Name,
|
||||
Attribute::Uuid,
|
||||
Attribute::DomainDisplayName,
|
||||
Attribute::DomainName,
|
||||
Attribute::DomainLdapBasedn,
|
||||
Attribute::LdapMaxQueryableAttrs,
|
||||
Attribute::DomainSsid,
|
||||
Attribute::DomainUuid,
|
||||
Attribute::KeyInternalData,
|
||||
Attribute::LdapAllowUnixPwBind,
|
||||
Attribute::Version,
|
||||
Attribute::Image,
|
||||
],
|
||||
modify_removed_attrs: vec![
|
||||
Attribute::DomainDisplayName,
|
||||
Attribute::DomainSsid,
|
||||
Attribute::DomainLdapBasedn,
|
||||
Attribute::LdapMaxQueryableAttrs,
|
||||
Attribute::LdapAllowUnixPwBind,
|
||||
Attribute::KeyActionRevoke,
|
||||
Attribute::KeyActionRotate,
|
||||
Attribute::Image,
|
||||
],
|
||||
modify_present_attrs: vec![
|
||||
Attribute::DomainDisplayName,
|
||||
Attribute::DomainLdapBasedn,
|
||||
Attribute::LdapMaxQueryableAttrs,
|
||||
Attribute::DomainSsid,
|
||||
Attribute::LdapAllowUnixPwBind,
|
||||
Attribute::KeyActionRevoke,
|
||||
Attribute::KeyActionRotate,
|
||||
Attribute::Image,
|
||||
],
|
||||
..Default::default()
|
||||
};
|
||||
}
|
||||
|
||||
lazy_static! {
|
||||
pub static ref IDM_ACP_DOMAIN_ADMIN_DL9: BuiltinAcp = BuiltinAcp {
|
||||
classes: vec![
|
||||
|
|
|
@ -105,6 +105,7 @@ pub fn phase_1_schema_attrs() -> Vec<EntryInitNew> {
|
|||
// DL10
|
||||
SCHEMA_ATTR_DENIED_NAME_DL10.clone().into(),
|
||||
SCHEMA_ATTR_LDAP_MAXIMUM_QUERYABLE_ATTRIBUTES.clone().into(),
|
||||
SCHEMA_ATTR_KEY_ACTION_IMPORT_JWS_RS256_DL6.clone().into(),
|
||||
]
|
||||
}
|
||||
|
||||
|
@ -140,6 +141,7 @@ pub fn phase_2_schema_classes() -> Vec<EntryInitNew> {
|
|||
SCHEMA_CLASS_OAUTH2_RS_DL9.clone().into(),
|
||||
// DL10
|
||||
SCHEMA_CLASS_DOMAIN_INFO_DL10.clone().into(),
|
||||
SCHEMA_CLASS_KEY_OBJECT_JWT_RS256.clone().into(),
|
||||
]
|
||||
}
|
||||
|
||||
|
@ -260,8 +262,9 @@ pub fn phase_7_builtin_access_control_profiles() -> Vec<EntryInitNew> {
|
|||
IDM_ACP_MAIL_SERVERS_DL8.clone().into(),
|
||||
IDM_ACP_GROUP_ACCOUNT_POLICY_MANAGE_DL8.clone().into(),
|
||||
// DL9
|
||||
IDM_ACP_OAUTH2_MANAGE_DL9.clone().into(),
|
||||
IDM_ACP_GROUP_MANAGE_DL9.clone().into(),
|
||||
IDM_ACP_DOMAIN_ADMIN_DL9.clone().into(),
|
||||
// DL10
|
||||
IDM_ACP_OAUTH2_MANAGE.clone().into(),
|
||||
]
|
||||
}
|
||||
|
|
|
@ -657,6 +657,17 @@ pub static ref SCHEMA_ATTR_KEY_ACTION_IMPORT_JWS_ES256_DL6: SchemaAttribute = Sc
|
|||
..Default::default()
|
||||
};
|
||||
|
||||
pub static ref SCHEMA_ATTR_KEY_ACTION_IMPORT_JWS_RS256_DL6: SchemaAttribute = SchemaAttribute {
|
||||
uuid: UUID_SCHEMA_ATTR_KEY_ACTION_IMPORT_JWS_RS256,
|
||||
name: Attribute::KeyActionImportJwsRs256,
|
||||
description: "".to_string(),
|
||||
multivalue: true,
|
||||
// Ephemeral action.
|
||||
phantom: true,
|
||||
syntax: SyntaxType::PrivateBinary,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
pub static ref SCHEMA_ATTR_PATCH_LEVEL_DL7: SchemaAttribute = SchemaAttribute {
|
||||
uuid: UUID_SCHEMA_ATTR_PATCH_LEVEL,
|
||||
name: Attribute::PatchLevel,
|
||||
|
@ -948,13 +959,12 @@ pub static ref SCHEMA_CLASS_SYSTEM_CONFIG: SchemaClass = SchemaClass {
|
|||
pub static ref SCHEMA_CLASS_OAUTH2_RS_DL9: SchemaClass = SchemaClass {
|
||||
uuid: UUID_SCHEMA_CLASS_OAUTH2_RS,
|
||||
name: EntryClass::OAuth2ResourceServer.into(),
|
||||
description: "The class representing a configured OAuth2 Client".to_string(),
|
||||
description: "The class epresenting a configured OAuth2 Client".to_string(),
|
||||
|
||||
systemmay: vec![
|
||||
Attribute::Description,
|
||||
Attribute::OAuth2RsScopeMap,
|
||||
Attribute::OAuth2RsSupScopeMap,
|
||||
Attribute::Rs256PrivateKeyDer,
|
||||
Attribute::OAuth2JwtLegacyCryptoEnable,
|
||||
Attribute::OAuth2PreferShortUsername,
|
||||
Attribute::Image,
|
||||
|
@ -963,11 +973,13 @@ pub static ref SCHEMA_CLASS_OAUTH2_RS_DL9: SchemaClass = SchemaClass {
|
|||
Attribute::OAuth2RsOrigin,
|
||||
Attribute::OAuth2StrictRedirectUri,
|
||||
Attribute::OAuth2DeviceFlowEnable,
|
||||
// Deprecated
|
||||
Attribute::Rs256PrivateKeyDer,
|
||||
Attribute::OAuth2RsTokenKey,
|
||||
Attribute::Es256PrivateKeyDer,
|
||||
],
|
||||
systemmust: vec![
|
||||
Attribute::OAuth2RsOriginLanding,
|
||||
Attribute::OAuth2RsTokenKey,
|
||||
Attribute::Es256PrivateKeyDer,
|
||||
],
|
||||
..Default::default()
|
||||
};
|
||||
|
@ -1045,6 +1057,16 @@ pub static ref SCHEMA_CLASS_KEY_OBJECT_JWT_ES256_DL6: SchemaClass = SchemaClass
|
|||
..Default::default()
|
||||
};
|
||||
|
||||
pub static ref SCHEMA_CLASS_KEY_OBJECT_JWT_RS256: SchemaClass = SchemaClass {
|
||||
uuid: UUID_SCHEMA_CLASS_KEY_OBJECT_JWT_RS256,
|
||||
name: EntryClass::KeyObjectJwtRs256.into(),
|
||||
description: "A marker class indicating that this keyobject must provide jwt rs256 capability.".to_string(),
|
||||
systemsupplements: vec![
|
||||
EntryClass::KeyObject.into(),
|
||||
],
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
pub static ref SCHEMA_CLASS_KEY_OBJECT_JWE_A128GCM_DL6: SchemaClass = SchemaClass {
|
||||
uuid: UUID_SCHEMA_CLASS_KEY_OBJECT_JWE_A128GCM,
|
||||
name: EntryClass::KeyObjectJweA128GCM.into(),
|
||||
|
|
|
@ -135,11 +135,13 @@ impl KeyObjectManagement {
|
|||
qs: &mut QueryServerWriteTransaction,
|
||||
cand: &mut [Entry<EntryInvalid, T>],
|
||||
) -> Result<(), OperationError> {
|
||||
// Valid from right meow!
|
||||
// New keys will be valid from right meow!
|
||||
let valid_from = qs.get_curtime();
|
||||
let txn_cid = qs.get_cid().clone();
|
||||
|
||||
let key_providers = qs.get_key_providers_mut();
|
||||
// ====================================================================
|
||||
// Transform any found KeyObjects and manage any related key operations
|
||||
// for them
|
||||
|
||||
cand.iter_mut()
|
||||
.filter(|entry| {
|
||||
|
@ -172,6 +174,14 @@ impl KeyObjectManagement {
|
|||
key_object.jws_es256_import(import_keys, valid_from, &txn_cid)?;
|
||||
}
|
||||
|
||||
let maybe_import = entry.pop_ava(Attribute::KeyActionImportJwsRs256);
|
||||
if let Some(import_keys) = maybe_import
|
||||
.as_ref()
|
||||
.and_then(|vs| vs.as_private_binary_set())
|
||||
{
|
||||
key_object.jws_rs256_import(import_keys, valid_from, &txn_cid)?;
|
||||
}
|
||||
|
||||
// If revoke. This weird looking let dance is to ensure that the inner hexstring set
|
||||
// lives long enough.
|
||||
let maybe_revoked = entry.pop_ava(Attribute::KeyActionRevoke);
|
||||
|
@ -208,6 +218,11 @@ impl KeyObjectManagement {
|
|||
key_object.jws_es256_assert(Duration::ZERO, &txn_cid)?;
|
||||
}
|
||||
|
||||
if entry.attribute_equality(Attribute::Class, &EntryClass::KeyObjectJwtRs256.into())
|
||||
{
|
||||
key_object.jws_rs256_assert(Duration::ZERO, &txn_cid)?;
|
||||
}
|
||||
|
||||
if entry
|
||||
.attribute_equality(Attribute::Class, &EntryClass::KeyObjectJweA128GCM.into())
|
||||
{
|
||||
|
|
|
@ -18,10 +18,10 @@ mod domain;
|
|||
pub(crate) mod dyngroup;
|
||||
mod eckeygen;
|
||||
pub(crate) mod gidnumber;
|
||||
mod jwskeygen;
|
||||
mod keyobject;
|
||||
mod memberof;
|
||||
mod namehistory;
|
||||
mod oauth2;
|
||||
mod refint;
|
||||
mod session;
|
||||
mod spn;
|
||||
|
@ -230,15 +230,17 @@ impl Plugins {
|
|||
) -> Result<(), OperationError> {
|
||||
base::Base::pre_create_transform(qs, cand, ce)?;
|
||||
valuedeny::ValueDeny::pre_create_transform(qs, cand, ce)?;
|
||||
cred_import::CredImport::pre_create_transform(qs, cand, ce)?;
|
||||
|
||||
oauth2::OAuth2::pre_create_transform(qs, cand, ce)?;
|
||||
eckeygen::EcdhKeyGen::pre_create_transform(qs, cand, ce)?;
|
||||
keyobject::KeyObjectManagement::pre_create_transform(qs, cand, ce)?;
|
||||
jwskeygen::JwsKeygen::pre_create_transform(qs, cand, ce)?;
|
||||
cred_import::CredImport::pre_create_transform(qs, cand, ce)?;
|
||||
|
||||
gidnumber::GidNumber::pre_create_transform(qs, cand, ce)?;
|
||||
domain::Domain::pre_create_transform(qs, cand, ce)?;
|
||||
spn::Spn::pre_create_transform(qs, cand, ce)?;
|
||||
default_values::DefaultValues::pre_create_transform(qs, cand, ce)?;
|
||||
namehistory::NameHistory::pre_create_transform(qs, cand, ce)?;
|
||||
eckeygen::EcdhKeyGen::pre_create_transform(qs, cand, ce)?;
|
||||
// Should always be last
|
||||
attrunique::AttrUnique::pre_create_transform(qs, cand, ce)
|
||||
}
|
||||
|
@ -271,16 +273,18 @@ impl Plugins {
|
|||
) -> Result<(), OperationError> {
|
||||
base::Base::pre_modify(qs, pre_cand, cand, me)?;
|
||||
valuedeny::ValueDeny::pre_modify(qs, pre_cand, cand, me)?;
|
||||
cred_import::CredImport::pre_modify(qs, pre_cand, cand, me)?;
|
||||
jwskeygen::JwsKeygen::pre_modify(qs, pre_cand, cand, me)?;
|
||||
|
||||
oauth2::OAuth2::pre_modify(qs, pre_cand, cand, me)?;
|
||||
eckeygen::EcdhKeyGen::pre_modify(qs, pre_cand, cand, me)?;
|
||||
keyobject::KeyObjectManagement::pre_modify(qs, pre_cand, cand, me)?;
|
||||
cred_import::CredImport::pre_modify(qs, pre_cand, cand, me)?;
|
||||
|
||||
gidnumber::GidNumber::pre_modify(qs, pre_cand, cand, me)?;
|
||||
domain::Domain::pre_modify(qs, pre_cand, cand, me)?;
|
||||
spn::Spn::pre_modify(qs, pre_cand, cand, me)?;
|
||||
session::SessionConsistency::pre_modify(qs, pre_cand, cand, me)?;
|
||||
default_values::DefaultValues::pre_modify(qs, pre_cand, cand, me)?;
|
||||
namehistory::NameHistory::pre_modify(qs, pre_cand, cand, me)?;
|
||||
eckeygen::EcdhKeyGen::pre_modify(qs, pre_cand, cand, me)?;
|
||||
// attr unique should always be last
|
||||
attrunique::AttrUnique::pre_modify(qs, pre_cand, cand, me)
|
||||
}
|
||||
|
@ -306,16 +310,18 @@ impl Plugins {
|
|||
) -> Result<(), OperationError> {
|
||||
base::Base::pre_batch_modify(qs, pre_cand, cand, me)?;
|
||||
valuedeny::ValueDeny::pre_batch_modify(qs, pre_cand, cand, me)?;
|
||||
cred_import::CredImport::pre_batch_modify(qs, pre_cand, cand, me)?;
|
||||
jwskeygen::JwsKeygen::pre_batch_modify(qs, pre_cand, cand, me)?;
|
||||
|
||||
oauth2::OAuth2::pre_batch_modify(qs, pre_cand, cand, me)?;
|
||||
eckeygen::EcdhKeyGen::pre_batch_modify(qs, pre_cand, cand, me)?;
|
||||
keyobject::KeyObjectManagement::pre_batch_modify(qs, pre_cand, cand, me)?;
|
||||
cred_import::CredImport::pre_batch_modify(qs, pre_cand, cand, me)?;
|
||||
|
||||
gidnumber::GidNumber::pre_batch_modify(qs, pre_cand, cand, me)?;
|
||||
domain::Domain::pre_batch_modify(qs, pre_cand, cand, me)?;
|
||||
spn::Spn::pre_batch_modify(qs, pre_cand, cand, me)?;
|
||||
session::SessionConsistency::pre_batch_modify(qs, pre_cand, cand, me)?;
|
||||
default_values::DefaultValues::pre_batch_modify(qs, pre_cand, cand, me)?;
|
||||
namehistory::NameHistory::pre_batch_modify(qs, pre_cand, cand, me)?;
|
||||
eckeygen::EcdhKeyGen::pre_batch_modify(qs, pre_cand, cand, me)?;
|
||||
// attr unique should always be last
|
||||
attrunique::AttrUnique::pre_batch_modify(qs, pre_cand, cand, me)
|
||||
}
|
||||
|
|
|
@ -6,14 +6,14 @@ use crate::plugins::Plugin;
|
|||
use crate::prelude::*;
|
||||
use crate::utils::password_from_random;
|
||||
|
||||
pub struct JwsKeygen {}
|
||||
pub struct OAuth2 {}
|
||||
|
||||
impl Plugin for JwsKeygen {
|
||||
impl Plugin for OAuth2 {
|
||||
fn id() -> &'static str {
|
||||
"plugin_jws_keygen"
|
||||
"plugin_oauth2"
|
||||
}
|
||||
|
||||
#[instrument(level = "debug", name = "jwskeygen_pre_create_transform", skip_all)]
|
||||
#[instrument(level = "debug", name = "oauth2_pre_create_transform", skip_all)]
|
||||
fn pre_create_transform(
|
||||
qs: &mut QueryServerWriteTransaction,
|
||||
cand: &mut Vec<Entry<EntryInvalid, EntryNew>>,
|
||||
|
@ -22,7 +22,7 @@ impl Plugin for JwsKeygen {
|
|||
Self::modify_inner(qs, cand)
|
||||
}
|
||||
|
||||
#[instrument(level = "debug", name = "jwskeygen_pre_modify", skip_all)]
|
||||
#[instrument(level = "debug", name = "oauth2_pre_modify", skip_all)]
|
||||
fn pre_modify(
|
||||
qs: &mut QueryServerWriteTransaction,
|
||||
_pre_cand: &[Arc<EntrySealedCommitted>],
|
||||
|
@ -32,7 +32,7 @@ impl Plugin for JwsKeygen {
|
|||
Self::modify_inner(qs, cand)
|
||||
}
|
||||
|
||||
#[instrument(level = "debug", name = "jwskeygen_pre_batch_modify", skip_all)]
|
||||
#[instrument(level = "debug", name = "oauth2_pre_batch_modify", skip_all)]
|
||||
fn pre_batch_modify(
|
||||
qs: &mut QueryServerWriteTransaction,
|
||||
_pre_cand: &[Arc<EntrySealedCommitted>],
|
||||
|
@ -43,27 +43,45 @@ impl Plugin for JwsKeygen {
|
|||
}
|
||||
}
|
||||
|
||||
impl JwsKeygen {
|
||||
impl OAuth2 {
|
||||
fn modify_inner<T: Clone>(
|
||||
_qs: &mut QueryServerWriteTransaction,
|
||||
qs: &mut QueryServerWriteTransaction,
|
||||
cand: &mut [Entry<EntryInvalid, T>],
|
||||
) -> Result<(), OperationError> {
|
||||
cand.iter_mut().try_for_each(|e| {
|
||||
if e.attribute_equality(Attribute::Class, &EntryClass::OAuth2ResourceServerBasic.into()) &&
|
||||
!e.attribute_pres(Attribute::OAuth2RsBasicSecret) {
|
||||
let domain_level = qs.get_domain_version();
|
||||
|
||||
cand.iter_mut()
|
||||
.filter(|entry| {
|
||||
entry.attribute_equality(Attribute::Class, &EntryClass::OAuth2ResourceServer.into())
|
||||
})
|
||||
.try_for_each(|entry| {
|
||||
// Regenerate the basic secret, if needed
|
||||
if entry.attribute_equality(Attribute::Class, &EntryClass::OAuth2ResourceServerBasic.into()) &&
|
||||
!entry.attribute_pres(Attribute::OAuth2RsBasicSecret) {
|
||||
security_info!("regenerating oauth2 basic secret");
|
||||
let v = Value::SecretValue(password_from_random());
|
||||
e.add_ava(Attribute::OAuth2RsBasicSecret, v);
|
||||
entry.add_ava(Attribute::OAuth2RsBasicSecret, v);
|
||||
}
|
||||
|
||||
if e.attribute_equality(Attribute::Class, &EntryClass::OAuth2ResourceServer.into()) {
|
||||
if !e.attribute_pres(Attribute::OAuth2RsTokenKey) {
|
||||
security_info!("regenerating oauth2 token key");
|
||||
let k = fernet::Fernet::generate_key();
|
||||
let v = Value::new_secret_str(&k);
|
||||
e.add_ava(Attribute::OAuth2RsTokenKey, v);
|
||||
let has_rs256 = entry.get_ava_single_bool(Attribute::OAuth2JwtLegacyCryptoEnable).unwrap_or(false);
|
||||
|
||||
if domain_level >= DOMAIN_LEVEL_10 {
|
||||
debug!("Generating OAuth2 Key Object");
|
||||
// OAuth2 now requires a KeyObject, configure it now.
|
||||
entry.add_ava(Attribute::Class, EntryClass::KeyObject.to_value());
|
||||
entry.add_ava(Attribute::Class, EntryClass::KeyObjectJwtEs256.to_value());
|
||||
entry.add_ava(Attribute::Class, EntryClass::KeyObjectJweA128GCM.to_value());
|
||||
if has_rs256 {
|
||||
entry.add_ava(Attribute::Class, EntryClass::KeyObjectJwtRs256.to_value());
|
||||
}
|
||||
if !e.attribute_pres(Attribute::Es256PrivateKeyDer) {
|
||||
} else {
|
||||
if !entry.attribute_pres(Attribute::OAuth2RsTokenKey) {
|
||||
security_info!("regenerating oauth2 token key");
|
||||
let k = password_from_random();
|
||||
let v = Value::new_secret_str(&k);
|
||||
entry.add_ava(Attribute::OAuth2RsTokenKey, v);
|
||||
}
|
||||
if !entry.attribute_pres(Attribute::Es256PrivateKeyDer) {
|
||||
security_info!("regenerating oauth2 es256 private key");
|
||||
let der = JwsEs256Signer::generate_es256()
|
||||
.and_then(|jws| jws.private_key_to_der())
|
||||
|
@ -72,10 +90,9 @@ impl JwsKeygen {
|
|||
OperationError::CryptographyError
|
||||
})?;
|
||||
let v = Value::new_privatebinary(&der);
|
||||
e.add_ava(Attribute::Es256PrivateKeyDer, v);
|
||||
entry.add_ava(Attribute::Es256PrivateKeyDer, v);
|
||||
}
|
||||
if e.get_ava_single_bool(Attribute::OAuth2JwtLegacyCryptoEnable).unwrap_or(false)
|
||||
&& !e.attribute_pres(Attribute::Rs256PrivateKeyDer) {
|
||||
if has_rs256 && !entry.attribute_pres(Attribute::Rs256PrivateKeyDer) {
|
||||
security_info!("regenerating oauth2 legacy rs256 private key");
|
||||
let der = JwsRs256Signer::generate_legacy_rs256()
|
||||
.and_then(|jws| jws.private_key_to_der())
|
||||
|
@ -84,7 +101,7 @@ impl JwsKeygen {
|
|||
OperationError::CryptographyError
|
||||
})?;
|
||||
let v = Value::new_privatebinary(&der);
|
||||
e.add_ava(Attribute::Rs256PrivateKeyDer, v);
|
||||
entry.add_ava(Attribute::Rs256PrivateKeyDer, v);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -145,7 +162,6 @@ mod tests {
|
|||
.internal_search_uuid(uuid)
|
||||
.expect("failed to get oauth2 config");
|
||||
assert!(e.attribute_pres(Attribute::OAuth2RsBasicSecret));
|
||||
assert!(e.attribute_pres(Attribute::OAuth2RsTokenKey));
|
||||
}
|
||||
);
|
||||
}
|
||||
|
@ -186,8 +202,7 @@ mod tests {
|
|||
(
|
||||
Attribute::OAuth2RsBasicSecret,
|
||||
Value::new_secret_str("12345")
|
||||
),
|
||||
(Attribute::OAuth2RsTokenKey, Value::new_secret_str("12345"))
|
||||
)
|
||||
);
|
||||
|
||||
let preload = vec![e];
|
||||
|
@ -207,10 +222,8 @@ mod tests {
|
|||
.internal_search_uuid(uuid)
|
||||
.expect("failed to get oauth2 config");
|
||||
assert!(e.attribute_pres(Attribute::OAuth2RsBasicSecret));
|
||||
assert!(e.attribute_pres(Attribute::OAuth2RsTokenKey));
|
||||
// Check the values are different.
|
||||
assert!(e.get_ava_single_secret(Attribute::OAuth2RsBasicSecret) != Some("12345"));
|
||||
assert!(e.get_ava_single_secret(Attribute::OAuth2RsTokenKey) != Some("12345"));
|
||||
}
|
||||
);
|
||||
}
|
|
@ -1,24 +1,22 @@
|
|||
use super::object::{KeyObject, KeyObjectT};
|
||||
use super::KeyId;
|
||||
use crate::prelude::*;
|
||||
|
||||
use smolset::SmolSet;
|
||||
|
||||
use std::collections::{BTreeMap, BTreeSet};
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::value::{KeyStatus, KeyUsage};
|
||||
use crate::valueset::{KeyInternalData, ValueSetKeyInternal};
|
||||
use compact_jwt::compact::{JweAlg, JweCompact, JweEnc};
|
||||
use compact_jwt::crypto::{JweA128GCMEncipher, JweA128KWEncipher};
|
||||
use compact_jwt::crypto::{
|
||||
JweA128GCMEncipher, JweA128KWEncipher, JwsRs256Signer, JwsRs256Verifier,
|
||||
};
|
||||
use compact_jwt::jwe::Jwe;
|
||||
use compact_jwt::traits::*;
|
||||
use compact_jwt::{
|
||||
JwaAlg, Jwk, Jws, JwsCompact, JwsEs256Signer, JwsEs256Verifier, JwsSigner, JwsSignerToVerifier,
|
||||
JwaAlg, Jwk, JwkKeySet, Jws, JwsCompact, JwsEs256Signer, JwsEs256Verifier, JwsSigner,
|
||||
JwsSignerToVerifier,
|
||||
};
|
||||
|
||||
use smolset::SmolSet;
|
||||
use std::collections::{BTreeMap, BTreeSet};
|
||||
use std::ops::Bound::{Included, Unbounded};
|
||||
|
||||
use crate::value::{KeyStatus, KeyUsage};
|
||||
use crate::valueset::{KeyInternalData, ValueSetKeyInternal};
|
||||
use std::sync::Arc;
|
||||
|
||||
pub struct KeyProviderInternal {
|
||||
uuid: Uuid,
|
||||
|
@ -61,6 +59,7 @@ impl KeyProviderInternal {
|
|||
uuid,
|
||||
jws_es256: None,
|
||||
jwe_a128gcm: None,
|
||||
jws_rs256: None,
|
||||
}))
|
||||
}
|
||||
|
||||
|
@ -73,6 +72,7 @@ impl KeyProviderInternal {
|
|||
debug!(?uuid, "Loading key object ...");
|
||||
|
||||
let mut jws_es256: Option<KeyObjectInternalJwtEs256> = None;
|
||||
let mut jws_rs256: Option<KeyObjectInternalJwtRs256> = None;
|
||||
let mut jwe_a128gcm: Option<KeyObjectInternalJweA128GCM> = None;
|
||||
|
||||
if let Some(key_internal_map) = entry
|
||||
|
@ -104,6 +104,18 @@ impl KeyProviderInternal {
|
|||
*valid_from,
|
||||
)?;
|
||||
}
|
||||
KeyUsage::JwsRs256 => {
|
||||
let jws_rs256_ref =
|
||||
jws_rs256.get_or_insert_with(KeyObjectInternalJwtRs256::default);
|
||||
|
||||
jws_rs256_ref.load(
|
||||
key_id,
|
||||
*status,
|
||||
status_cid.clone(),
|
||||
der,
|
||||
*valid_from,
|
||||
)?;
|
||||
}
|
||||
KeyUsage::JweA128GCM => {
|
||||
let jwe_a128gcm_ref =
|
||||
jwe_a128gcm.get_or_insert_with(KeyObjectInternalJweA128GCM::default);
|
||||
|
@ -125,6 +137,7 @@ impl KeyProviderInternal {
|
|||
uuid,
|
||||
jws_es256,
|
||||
jwe_a128gcm,
|
||||
jws_rs256,
|
||||
})))
|
||||
}
|
||||
|
||||
|
@ -194,7 +207,7 @@ impl KeyObjectInternalJweA128GCM {
|
|||
fn assert_active(&mut self, valid_from: Duration, cid: &Cid) -> Result<(), OperationError> {
|
||||
if self.get_valid_cipher(valid_from).is_none() {
|
||||
// This means there is no active signing key, so we need to create one.
|
||||
warn!("no active jwe a128gcm found, creating a new one ...");
|
||||
debug!("no active jwe a128gcm found, creating a new one ...");
|
||||
self.new_active(valid_from, cid)
|
||||
} else {
|
||||
Ok(())
|
||||
|
@ -422,7 +435,7 @@ impl KeyObjectInternalJwtEs256 {
|
|||
fn assert_active(&mut self, valid_from: Duration, cid: &Cid) -> Result<(), OperationError> {
|
||||
if self.get_valid_signer(valid_from).is_none() {
|
||||
// This means there is no active signing key, so we need to create one.
|
||||
warn!("no active jwt es256 found, creating a new one ...");
|
||||
debug!("no active jwt es256 found, creating a new one ...");
|
||||
self.new_active(valid_from, cid)
|
||||
} else {
|
||||
Ok(())
|
||||
|
@ -437,8 +450,8 @@ impl KeyObjectInternalJwtEs256 {
|
|||
) -> Result<(), OperationError> {
|
||||
let valid_from = valid_from.as_secs();
|
||||
|
||||
for der in import_keys {
|
||||
let signer = JwsEs256Signer::from_es256_der(der).map_err(|err| {
|
||||
for private_der in import_keys {
|
||||
let signer = JwsEs256Signer::from_es256_der(private_der).map_err(|err| {
|
||||
error!(?err, "Unable to load imported es256 DER signer");
|
||||
OperationError::KP0028KeyObjectImportJwsEs256DerInvalid
|
||||
})?;
|
||||
|
@ -451,22 +464,19 @@ impl KeyObjectInternalJwtEs256 {
|
|||
OperationError::KP0029KeyObjectSignerToVerifier
|
||||
})?;
|
||||
|
||||
let public_der = verifier.public_key_to_der().map_err(|jwt_error| {
|
||||
error!(?jwt_error, "Unable to convert public key to DER");
|
||||
OperationError::KP0030KeyObjectPublicToDer
|
||||
})?;
|
||||
|
||||
// We need to use the legacy KID for imported objects
|
||||
let kid = signer.get_legacy_kid().to_string();
|
||||
debug!(?kid, "imported key");
|
||||
|
||||
self.active.insert(valid_from, signer.clone());
|
||||
|
||||
self.all.insert(
|
||||
kid,
|
||||
InternalJwtEs256 {
|
||||
valid_from,
|
||||
status: InternalJwtEs256Status::Retained {
|
||||
status: InternalJwtEs256Status::Valid {
|
||||
verifier,
|
||||
public_der,
|
||||
private_der: private_der.clone(),
|
||||
},
|
||||
status_cid: cid.clone(),
|
||||
},
|
||||
|
@ -696,6 +706,25 @@ impl KeyObjectInternalJwtEs256 {
|
|||
}
|
||||
}
|
||||
|
||||
fn public_jwks(&self) -> JwkKeySet {
|
||||
let keys = self
|
||||
.all
|
||||
.iter()
|
||||
.filter_map(|(_, es256)| match &es256.status {
|
||||
InternalJwtEs256Status::Valid { verifier, .. }
|
||||
| InternalJwtEs256Status::Retained { verifier, .. } => verifier
|
||||
.public_key_as_jwk()
|
||||
.map_err(|err| {
|
||||
error!(?err);
|
||||
})
|
||||
.ok(),
|
||||
InternalJwtEs256Status::Revoked { .. } => None,
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
JwkKeySet { keys }
|
||||
}
|
||||
|
||||
fn public_jwk(&self, key_id: &str) -> Result<Option<Jwk>, OperationError> {
|
||||
if let Some(key_to_check) = self.all.get(key_id) {
|
||||
match &key_to_check.status {
|
||||
|
@ -728,11 +757,387 @@ impl KeyObjectInternalJwtEs256 {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
enum InternalJwtRs256Status {
|
||||
Valid {
|
||||
verifier: JwsRs256Verifier,
|
||||
private_der: Vec<u8>,
|
||||
},
|
||||
Retained {
|
||||
verifier: JwsRs256Verifier,
|
||||
public_der: Vec<u8>,
|
||||
},
|
||||
Revoked {
|
||||
untrusted_verifier: JwsRs256Verifier,
|
||||
public_der: Vec<u8>,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct InternalJwtRs256 {
|
||||
valid_from: u64,
|
||||
status: InternalJwtRs256Status,
|
||||
status_cid: Cid,
|
||||
}
|
||||
|
||||
#[derive(Default, Clone)]
|
||||
struct KeyObjectInternalJwtRs256 {
|
||||
// active signing keys are in a BTreeMap indexed by their valid_from
|
||||
// time so that we can retrieve the active key.
|
||||
//
|
||||
// We don't need to worry about manipulating this at runtime, since any expiry
|
||||
// event will cause the keyObject to reload, which will reflect to this map.
|
||||
active: BTreeMap<u64, JwsRs256Signer>,
|
||||
|
||||
// All keys are stored by their KeyId for fast lookup. Keys internally have a
|
||||
// current status which is checked for signature validation.
|
||||
all: BTreeMap<KeyId, InternalJwtRs256>,
|
||||
}
|
||||
|
||||
impl KeyObjectInternalJwtRs256 {
|
||||
fn get_valid_signer(&self, time: Duration) -> Option<&JwsRs256Signer> {
|
||||
let ct_secs = time.as_secs();
|
||||
|
||||
self.active
|
||||
.range((Unbounded, Included(ct_secs)))
|
||||
.next_back()
|
||||
.map(|(_time, signer)| signer)
|
||||
}
|
||||
|
||||
fn assert_active(&mut self, valid_from: Duration, cid: &Cid) -> Result<(), OperationError> {
|
||||
if self.get_valid_signer(valid_from).is_none() {
|
||||
// This means there is no active signing key, so we need to create one.
|
||||
debug!("no active jwt rs256 found, creating a new one ...");
|
||||
self.new_active(valid_from, cid)
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn import(
|
||||
&mut self,
|
||||
import_keys: &SmolSet<[Vec<u8>; 1]>,
|
||||
valid_from: Duration,
|
||||
cid: &Cid,
|
||||
) -> Result<(), OperationError> {
|
||||
let valid_from = valid_from.as_secs();
|
||||
|
||||
for private_der in import_keys {
|
||||
let signer = JwsRs256Signer::from_rs256_der(private_der).map_err(|err| {
|
||||
error!(?err, "Unable to load imported rs256 DER signer");
|
||||
OperationError::KP0045KeyObjectImportJwsRs256DerInvalid
|
||||
})?;
|
||||
|
||||
let verifier = signer.get_verifier().map_err(|jwt_error| {
|
||||
error!(
|
||||
?jwt_error,
|
||||
"Unable to produce jwt rs256 verifier from signer"
|
||||
);
|
||||
OperationError::KP0046KeyObjectSignerToVerifier
|
||||
})?;
|
||||
|
||||
// We need to use the legacy KID for imported objects
|
||||
let kid = signer.get_legacy_kid().to_string();
|
||||
debug!(?kid, "imported key");
|
||||
|
||||
self.active.insert(valid_from, signer.clone());
|
||||
|
||||
self.all.insert(
|
||||
kid,
|
||||
InternalJwtRs256 {
|
||||
valid_from,
|
||||
status: InternalJwtRs256Status::Valid {
|
||||
verifier,
|
||||
private_der: private_der.clone(),
|
||||
},
|
||||
status_cid: cid.clone(),
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn new_active(&mut self, valid_from: Duration, cid: &Cid) -> Result<(), OperationError> {
|
||||
let valid_from = valid_from.as_secs();
|
||||
|
||||
let signer = JwsRs256Signer::generate_legacy_rs256().map_err(|jwt_error| {
|
||||
error!(?jwt_error, "Unable to generate new jwt rs256 signing key");
|
||||
OperationError::KP0048KeyObjectJwtRs256Generation
|
||||
})?;
|
||||
|
||||
let verifier = signer.get_verifier().map_err(|jwt_error| {
|
||||
error!(
|
||||
?jwt_error,
|
||||
"Unable to produce jwt rs256 verifier from signer"
|
||||
);
|
||||
OperationError::KP0049KeyObjectSignerToVerifier
|
||||
})?;
|
||||
|
||||
let private_der = signer.private_key_to_der().map_err(|jwt_error| {
|
||||
error!(?jwt_error, "Unable to convert signing key to DER");
|
||||
OperationError::KP0050KeyObjectPrivateToDer
|
||||
})?;
|
||||
|
||||
self.active.insert(valid_from, signer.clone());
|
||||
|
||||
let kid = signer.get_kid().to_string();
|
||||
|
||||
self.all.insert(
|
||||
kid,
|
||||
InternalJwtRs256 {
|
||||
valid_from,
|
||||
status: InternalJwtRs256Status::Valid {
|
||||
// signer,
|
||||
verifier,
|
||||
private_der,
|
||||
},
|
||||
status_cid: cid.clone(),
|
||||
},
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn revoke(&mut self, revoke_key_id: &KeyId, cid: &Cid) -> Result<bool, OperationError> {
|
||||
if let Some(key_to_revoke) = self.all.get_mut(revoke_key_id) {
|
||||
let untrusted_verifier = match &key_to_revoke.status {
|
||||
InternalJwtRs256Status::Valid { verifier, .. }
|
||||
| InternalJwtRs256Status::Retained { verifier, .. } => verifier,
|
||||
InternalJwtRs256Status::Revoked {
|
||||
untrusted_verifier, ..
|
||||
} => untrusted_verifier,
|
||||
}
|
||||
.clone();
|
||||
|
||||
let public_der = untrusted_verifier
|
||||
.public_key_to_der()
|
||||
.map_err(|jwt_error| {
|
||||
error!(?jwt_error, "Unable to convert public key to DER");
|
||||
OperationError::KP0051KeyObjectPublicToDer
|
||||
})?;
|
||||
|
||||
key_to_revoke.status = InternalJwtRs256Status::Revoked {
|
||||
untrusted_verifier,
|
||||
public_der,
|
||||
};
|
||||
key_to_revoke.status_cid = cid.clone();
|
||||
|
||||
let valid_from = key_to_revoke.valid_from;
|
||||
|
||||
// Remove it from the active set.
|
||||
self.active.remove(&valid_from);
|
||||
|
||||
Ok(true)
|
||||
} else {
|
||||
// We didn't revoke anything
|
||||
Ok(false)
|
||||
}
|
||||
}
|
||||
|
||||
fn load(
|
||||
&mut self,
|
||||
id: &str,
|
||||
status: KeyStatus,
|
||||
status_cid: Cid,
|
||||
der: &[u8],
|
||||
valid_from: u64,
|
||||
) -> Result<(), OperationError> {
|
||||
let id: KeyId = id.to_string();
|
||||
|
||||
let status = match status {
|
||||
KeyStatus::Valid => {
|
||||
let signer = JwsRs256Signer::from_rs256_der(der).map_err(|err| {
|
||||
error!(?err, ?id, "Unable to load rs256 DER signer");
|
||||
OperationError::KP0052KeyObjectJwsRs256DerInvalid
|
||||
})?;
|
||||
|
||||
let verifier = signer.get_verifier().map_err(|err| {
|
||||
error!(?err, "Unable to retrieve verifier from signer");
|
||||
OperationError::KP0053KeyObjectSignerToVerifier
|
||||
})?;
|
||||
|
||||
self.active.insert(valid_from, signer);
|
||||
|
||||
InternalJwtRs256Status::Valid {
|
||||
// signer,
|
||||
verifier,
|
||||
private_der: der.to_vec(),
|
||||
}
|
||||
}
|
||||
KeyStatus::Retained => {
|
||||
let verifier = JwsRs256Verifier::from_rs256_der(der).map_err(|err| {
|
||||
error!(?err, ?id, "Unable to load rs256 DER verifier");
|
||||
OperationError::KP0054KeyObjectJwsRs256DerInvalid
|
||||
})?;
|
||||
|
||||
InternalJwtRs256Status::Retained {
|
||||
verifier,
|
||||
public_der: der.to_vec(),
|
||||
}
|
||||
}
|
||||
KeyStatus::Revoked => {
|
||||
let untrusted_verifier = JwsRs256Verifier::from_rs256_der(der).map_err(|err| {
|
||||
error!(?err, ?id, "Unable to load rs256 DER revoked verifier");
|
||||
OperationError::KP0055KeyObjectJwsRs256DerInvalid
|
||||
})?;
|
||||
|
||||
InternalJwtRs256Status::Revoked {
|
||||
untrusted_verifier,
|
||||
public_der: der.to_vec(),
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let internal_jwt = InternalJwtRs256 {
|
||||
valid_from,
|
||||
status,
|
||||
status_cid,
|
||||
};
|
||||
|
||||
self.all.insert(id, internal_jwt);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn to_key_iter(&self) -> impl Iterator<Item = (KeyId, KeyInternalData)> + '_ {
|
||||
self.all.iter().map(|(key_id, internal_jwt)| {
|
||||
let usage = KeyUsage::JwsRs256;
|
||||
|
||||
let valid_from = internal_jwt.valid_from;
|
||||
let status_cid = internal_jwt.status_cid.clone();
|
||||
|
||||
let (status, der) = match &internal_jwt.status {
|
||||
InternalJwtRs256Status::Valid { private_der, .. } => {
|
||||
(KeyStatus::Valid, private_der.clone())
|
||||
}
|
||||
InternalJwtRs256Status::Retained { public_der, .. } => {
|
||||
(KeyStatus::Retained, public_der.clone())
|
||||
}
|
||||
InternalJwtRs256Status::Revoked { public_der, .. } => {
|
||||
(KeyStatus::Revoked, public_der.clone())
|
||||
}
|
||||
};
|
||||
|
||||
(
|
||||
key_id.clone(),
|
||||
KeyInternalData {
|
||||
usage,
|
||||
valid_from,
|
||||
der,
|
||||
status,
|
||||
status_cid,
|
||||
},
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
fn sign<V: JwsSignable>(
|
||||
&self,
|
||||
jws: &V,
|
||||
current_time: Duration,
|
||||
) -> Result<V::Signed, OperationError> {
|
||||
let Some(signing_key) = self.get_valid_signer(current_time) else {
|
||||
error!("No signing keys available. This may indicate that no keys are valid yet!");
|
||||
return Err(OperationError::KP0061KeyObjectNoActiveSigningKeys);
|
||||
};
|
||||
|
||||
signing_key.sign(jws).map_err(|jwt_err| {
|
||||
error!(?jwt_err, "Unable to sign jws");
|
||||
OperationError::KP0056KeyObjectJwsRs256Signature
|
||||
})
|
||||
}
|
||||
|
||||
fn verify<V: JwsVerifiable>(&self, jwsc: &V) -> Result<V::Verified, OperationError> {
|
||||
let internal_jws = jwsc
|
||||
.kid()
|
||||
.and_then(|kid| {
|
||||
debug!(?kid);
|
||||
self.all.get(kid)
|
||||
})
|
||||
.ok_or_else(|| {
|
||||
error!("JWS is signed by a key that is not present in this KeyObject");
|
||||
for pres_kid in self.all.keys() {
|
||||
debug!(?pres_kid);
|
||||
}
|
||||
OperationError::KP0057KeyObjectJwsNotAssociated
|
||||
})?;
|
||||
|
||||
match &internal_jws.status {
|
||||
InternalJwtRs256Status::Valid { verifier, .. }
|
||||
| InternalJwtRs256Status::Retained { verifier, .. } => {
|
||||
verifier.verify(jwsc).map_err(|jwt_err| {
|
||||
error!(?jwt_err, "Failed to verify jws");
|
||||
OperationError::KP0058KeyObjectJwsInvalid
|
||||
})
|
||||
}
|
||||
InternalJwtRs256Status::Revoked { .. } => {
|
||||
error!("The key used to sign this JWS has been revoked.");
|
||||
Err(OperationError::KP0059KeyObjectJwsKeyRevoked)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn public_jwks(&self) -> JwkKeySet {
|
||||
let keys = self
|
||||
.all
|
||||
.iter()
|
||||
.filter_map(|(key_id, rs256)| {
|
||||
error!(?key_id);
|
||||
match &rs256.status {
|
||||
InternalJwtRs256Status::Valid { verifier, .. }
|
||||
| InternalJwtRs256Status::Retained { verifier, .. } => verifier
|
||||
.public_key_as_jwk()
|
||||
.map_err(|err| {
|
||||
error!(?err);
|
||||
})
|
||||
.ok(),
|
||||
InternalJwtRs256Status::Revoked { .. } => None,
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
JwkKeySet { keys }
|
||||
}
|
||||
|
||||
fn public_jwk(&self, key_id: &str) -> Result<Option<Jwk>, OperationError> {
|
||||
if let Some(key_to_check) = self.all.get(key_id) {
|
||||
match &key_to_check.status {
|
||||
InternalJwtRs256Status::Valid { verifier, .. }
|
||||
| InternalJwtRs256Status::Retained { verifier, .. } => {
|
||||
verifier.public_key_as_jwk().map(Some).map_err(|err| {
|
||||
error!(?err, "Unable to construct public JWK.");
|
||||
OperationError::KP0060KeyObjectJwsPublicJwk
|
||||
})
|
||||
}
|
||||
InternalJwtRs256Status::Revoked { .. } => Ok(None),
|
||||
}
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
fn kid_status(&self, key_id: &KeyId) -> Result<Option<KeyStatus>, OperationError> {
|
||||
if let Some(key_to_check) = self.all.get(key_id) {
|
||||
let status = match &key_to_check.status {
|
||||
InternalJwtRs256Status::Valid { .. } => KeyStatus::Valid,
|
||||
InternalJwtRs256Status::Retained { .. } => KeyStatus::Retained,
|
||||
InternalJwtRs256Status::Revoked { .. } => KeyStatus::Revoked,
|
||||
};
|
||||
Ok(Some(status))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct KeyObjectInternal {
|
||||
provider: Arc<KeyProviderInternal>,
|
||||
uuid: Uuid,
|
||||
jws_es256: Option<KeyObjectInternalJwtEs256>,
|
||||
jws_rs256: Option<KeyObjectInternalJwtRs256>,
|
||||
jwe_a128gcm: Option<KeyObjectInternalJweA128GCM>,
|
||||
// If you add more types here you need to add these to rotate
|
||||
// and revoke.
|
||||
|
@ -769,6 +1174,10 @@ impl KeyObjectT for KeyObjectInternal {
|
|||
jws_es256_object.new_active(rotation_time, cid)?;
|
||||
}
|
||||
|
||||
if let Some(jws_rs256_object) = &mut self.jws_rs256 {
|
||||
jws_rs256_object.new_active(rotation_time, cid)?;
|
||||
}
|
||||
|
||||
if let Some(jwe_a128_gcm) = &mut self.jwe_a128gcm {
|
||||
jwe_a128_gcm.new_active(rotation_time, cid)?;
|
||||
}
|
||||
|
@ -790,6 +1199,12 @@ impl KeyObjectT for KeyObjectInternal {
|
|||
}
|
||||
};
|
||||
|
||||
if let Some(jws_rs256_object) = &mut self.jws_rs256 {
|
||||
if jws_rs256_object.revoke(revoke_key_id, cid)? {
|
||||
has_revoked = true;
|
||||
}
|
||||
};
|
||||
|
||||
if let Some(jwe_a128_gcm) = &mut self.jwe_a128gcm {
|
||||
if jwe_a128_gcm.revoke(revoke_key_id, cid)? {
|
||||
has_revoked = true;
|
||||
|
@ -831,6 +1246,14 @@ impl KeyObjectT for KeyObjectInternal {
|
|||
Err(OperationError::KP0018KeyProviderNoSuchKey)
|
||||
}
|
||||
}
|
||||
JwaAlg::RS256 => {
|
||||
if let Some(jws_rs256_object) = &self.jws_rs256 {
|
||||
jws_rs256_object.verify(jwsc)
|
||||
} else {
|
||||
error!(provider_uuid = ?self.uuid, "jwt rs256 not available on this provider");
|
||||
Err(OperationError::KP0018KeyProviderNoSuchKey)
|
||||
}
|
||||
}
|
||||
unsupported_alg => {
|
||||
// unsupported rn.
|
||||
error!(provider_uuid = ?self.uuid, ?unsupported_alg, "algorithm not available on this provider");
|
||||
|
@ -839,12 +1262,26 @@ impl KeyObjectT for KeyObjectInternal {
|
|||
}
|
||||
}
|
||||
|
||||
fn jws_public_jwk(&self, kid: &str) -> Result<Option<Jwk>, OperationError> {
|
||||
if let Some(jws_es256_object) = &self.jws_es256 {
|
||||
jws_es256_object.public_jwk(kid)
|
||||
} else {
|
||||
Ok(None)
|
||||
fn jws_es256_jwks(&self) -> Option<JwkKeySet> {
|
||||
self.jws_es256
|
||||
.as_ref()
|
||||
.map(|jws_es256_object| jws_es256_object.public_jwks())
|
||||
}
|
||||
|
||||
fn jws_public_jwk(&self, key_id: &str) -> Result<Option<Jwk>, OperationError> {
|
||||
if let Some(jws_es256_object) = &self.jws_es256 {
|
||||
if let Some(status) = jws_es256_object.public_jwk(key_id)? {
|
||||
return Ok(Some(status));
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(jws_rs256_object) = &self.jws_rs256 {
|
||||
if let Some(status) = jws_rs256_object.public_jwk(key_id)? {
|
||||
return Ok(Some(status));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
fn jws_es256_import(
|
||||
|
@ -921,6 +1358,12 @@ impl KeyObjectT for KeyObjectInternal {
|
|||
}
|
||||
}
|
||||
|
||||
if let Some(jws_rs256_object) = &self.jws_rs256 {
|
||||
if let Some(status) = jws_rs256_object.kid_status(key_id)? {
|
||||
return Ok(Some(status));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
|
@ -933,6 +1376,11 @@ impl KeyObjectT for KeyObjectInternal {
|
|||
self.jwe_a128gcm
|
||||
.iter()
|
||||
.flat_map(|jwe_a128gcm| jwe_a128gcm.to_key_iter()),
|
||||
)
|
||||
.chain(
|
||||
self.jws_rs256
|
||||
.iter()
|
||||
.flat_map(|jws_rs256| jws_rs256.to_key_iter()),
|
||||
);
|
||||
let key_vs = ValueSetKeyInternal::from_key_iter(key_iter)? as ValueSet;
|
||||
|
||||
|
@ -948,6 +1396,46 @@ impl KeyObjectT for KeyObjectInternal {
|
|||
(Attribute::KeyInternalData, key_vs),
|
||||
])
|
||||
}
|
||||
|
||||
fn jws_rs256_import(
|
||||
&mut self,
|
||||
import_keys: &SmolSet<[Vec<u8>; 1]>,
|
||||
valid_from: Duration,
|
||||
cid: &Cid,
|
||||
) -> Result<(), OperationError> {
|
||||
let koi = self
|
||||
.jws_rs256
|
||||
.get_or_insert_with(KeyObjectInternalJwtRs256::default);
|
||||
|
||||
koi.import(import_keys, valid_from, cid)
|
||||
}
|
||||
|
||||
fn jws_rs256_assert(&mut self, valid_from: Duration, cid: &Cid) -> Result<(), OperationError> {
|
||||
let koi = self
|
||||
.jws_rs256
|
||||
.get_or_insert_with(KeyObjectInternalJwtRs256::default);
|
||||
|
||||
koi.assert_active(valid_from, cid)
|
||||
}
|
||||
|
||||
fn jws_rs256_sign(
|
||||
&self,
|
||||
jws: &Jws,
|
||||
current_time: Duration,
|
||||
) -> Result<JwsCompact, OperationError> {
|
||||
if let Some(jws_rs256_object) = &self.jws_rs256 {
|
||||
jws_rs256_object.sign(jws, current_time)
|
||||
} else {
|
||||
error!(provider_uuid = ?self.uuid, "jwt rs256 not available on this provider");
|
||||
Err(OperationError::KP0062KeyProviderNoSuchKey)
|
||||
}
|
||||
}
|
||||
|
||||
fn jws_rs256_jwks(&self) -> Option<JwkKeySet> {
|
||||
self.jws_rs256
|
||||
.as_ref()
|
||||
.map(|jws_rs256_object| jws_rs256_object.public_jwks())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use crate::prelude::*;
|
||||
use compact_jwt::{compact::JweCompact, jwe::Jwe};
|
||||
use compact_jwt::{Jwk, Jws, JwsCompact};
|
||||
use compact_jwt::{Jwk, JwkKeySet, Jws, JwsCompact};
|
||||
use smolset::SmolSet;
|
||||
use std::collections::BTreeSet;
|
||||
use uuid::Uuid;
|
||||
|
@ -29,6 +29,25 @@ pub trait KeyObjectT {
|
|||
current_time: Duration,
|
||||
) -> Result<JwsCompact, OperationError>;
|
||||
|
||||
fn jws_es256_jwks(&self) -> Option<JwkKeySet>;
|
||||
|
||||
fn jws_rs256_import(
|
||||
&mut self,
|
||||
import_keys: &SmolSet<[Vec<u8>; 1]>,
|
||||
valid_from: Duration,
|
||||
cid: &Cid,
|
||||
) -> Result<(), OperationError>;
|
||||
|
||||
fn jws_rs256_assert(&mut self, valid_from: Duration, cid: &Cid) -> Result<(), OperationError>;
|
||||
|
||||
fn jws_rs256_sign(
|
||||
&self,
|
||||
jws: &Jws,
|
||||
current_time: Duration,
|
||||
) -> Result<JwsCompact, OperationError>;
|
||||
|
||||
fn jws_rs256_jwks(&self) -> Option<JwkKeySet>;
|
||||
|
||||
fn jws_verify(&self, jwsc: &JwsCompact) -> Result<Jws, OperationError>;
|
||||
|
||||
fn jws_public_jwk(&self, kid: &str) -> Result<Option<Jwk>, OperationError>;
|
||||
|
|
|
@ -532,6 +532,78 @@ impl QueryServerWriteTransaction<'_> {
|
|||
|
||||
self.reload()?;
|
||||
|
||||
// =========== OAuth2 Cryptography Migration ==============
|
||||
|
||||
debug!("START OAUTH2 MIGRATION");
|
||||
|
||||
// Load all the OAuth2 providers.
|
||||
let all_oauth2_rs_entries = self.internal_search(filter!(f_eq(
|
||||
Attribute::Class,
|
||||
EntryClass::OAuth2ResourceServer.into()
|
||||
)))?;
|
||||
|
||||
if !all_oauth2_rs_entries.is_empty() {
|
||||
let entry_iter = all_oauth2_rs_entries.iter().map(|tgt_entry| {
|
||||
let entry_uuid = tgt_entry.get_uuid();
|
||||
let mut modlist = ModifyList::new_list(vec![
|
||||
Modify::Present(Attribute::Class, EntryClass::KeyObject.to_value()),
|
||||
Modify::Present(Attribute::Class, EntryClass::KeyObjectJwtEs256.to_value()),
|
||||
Modify::Present(Attribute::Class, EntryClass::KeyObjectJweA128GCM.to_value()),
|
||||
// Delete the fernet key, rs256 if any, and the es256 key
|
||||
Modify::Purged(Attribute::OAuth2RsTokenKey),
|
||||
Modify::Purged(Attribute::Es256PrivateKeyDer),
|
||||
Modify::Purged(Attribute::Rs256PrivateKeyDer),
|
||||
]);
|
||||
|
||||
trace!(?tgt_entry);
|
||||
|
||||
// Import the ES256 Key
|
||||
if let Some(es256_private_der) =
|
||||
tgt_entry.get_ava_single_private_binary(Attribute::Es256PrivateKeyDer)
|
||||
{
|
||||
modlist.push_mod(Modify::Present(
|
||||
Attribute::KeyActionImportJwsEs256,
|
||||
Value::PrivateBinary(es256_private_der.to_vec()),
|
||||
))
|
||||
} else {
|
||||
warn!("Unable to migrate es256 key");
|
||||
}
|
||||
|
||||
let has_rs256 = tgt_entry
|
||||
.get_ava_single_bool(Attribute::OAuth2JwtLegacyCryptoEnable)
|
||||
.unwrap_or(false);
|
||||
|
||||
// If there is an rs256 key, import it.
|
||||
// Import the RS256 Key
|
||||
if has_rs256 {
|
||||
modlist.push_mod(Modify::Present(
|
||||
Attribute::Class,
|
||||
EntryClass::KeyObjectJwtEs256.to_value(),
|
||||
));
|
||||
|
||||
if let Some(rs256_private_der) =
|
||||
tgt_entry.get_ava_single_private_binary(Attribute::Rs256PrivateKeyDer)
|
||||
{
|
||||
modlist.push_mod(Modify::Present(
|
||||
Attribute::KeyActionImportJwsRs256,
|
||||
Value::PrivateBinary(rs256_private_der.to_vec()),
|
||||
))
|
||||
} else {
|
||||
warn!("Unable to migrate rs256 key");
|
||||
}
|
||||
}
|
||||
|
||||
(entry_uuid, modlist)
|
||||
});
|
||||
|
||||
self.internal_batch_modify(entry_iter)?;
|
||||
}
|
||||
|
||||
// Reload for new keys, and updated oauth2
|
||||
self.reload()?;
|
||||
|
||||
// Done!
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
|
|
@ -1251,6 +1251,7 @@ pub struct Oauth2Session {
|
|||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub enum KeyUsage {
|
||||
JwsEs256,
|
||||
JwsRs256,
|
||||
JweA128GCM,
|
||||
}
|
||||
|
||||
|
@ -1261,6 +1262,7 @@ impl fmt::Display for KeyUsage {
|
|||
"{}",
|
||||
match self {
|
||||
KeyUsage::JwsEs256 => "jws_es256",
|
||||
KeyUsage::JwsRs256 => "jws_rs256",
|
||||
KeyUsage::JweA128GCM => "jwe_a128gcm",
|
||||
}
|
||||
)
|
||||
|
|
|
@ -83,6 +83,7 @@ impl ValueSetKeyInternal {
|
|||
let id: KeyId = id;
|
||||
let usage = match usage {
|
||||
DbValueKeyUsage::JwsEs256 => KeyUsage::JwsEs256,
|
||||
DbValueKeyUsage::JwsRs256 => KeyUsage::JwsRs256,
|
||||
DbValueKeyUsage::JweA128GCM => KeyUsage::JweA128GCM,
|
||||
};
|
||||
let status_cid = status_cid.into();
|
||||
|
@ -131,6 +132,7 @@ impl ValueSetKeyInternal {
|
|||
let id: String = id.clone();
|
||||
let usage = match usage {
|
||||
KeyUsage::JwsEs256 => DbValueKeyUsage::JwsEs256,
|
||||
KeyUsage::JwsRs256 => DbValueKeyUsage::JwsRs256,
|
||||
KeyUsage::JweA128GCM => DbValueKeyUsage::JweA128GCM,
|
||||
};
|
||||
let status_cid = status_cid.into();
|
||||
|
|
|
@ -1,9 +1,6 @@
|
|||
#![deny(warnings)]
|
||||
use std::collections::{BTreeMap, BTreeSet};
|
||||
use std::convert::TryFrom;
|
||||
use std::str::FromStr;
|
||||
|
||||
use compact_jwt::{JwkKeySet, JwsEs256Verifier, JwsVerifier, OidcToken, OidcUnverified};
|
||||
use kanidm_client::{http::header, KanidmClient, StatusCode};
|
||||
use kanidm_proto::constants::uri::{OAUTH2_AUTHORISE, OAUTH2_AUTHORISE_PERMIT};
|
||||
use kanidm_proto::constants::*;
|
||||
use kanidm_proto::internal::Oauth2ClaimMapJoin;
|
||||
|
@ -14,18 +11,20 @@ use kanidm_proto::oauth2::{
|
|||
};
|
||||
use kanidmd_lib::constants::NAME_IDM_ALL_ACCOUNTS;
|
||||
use kanidmd_lib::prelude::Attribute;
|
||||
use oauth2_ext::PkceCodeChallenge;
|
||||
use reqwest::header::{HeaderValue, CONTENT_TYPE};
|
||||
use uri::{OAUTH2_TOKEN_ENDPOINT, OAUTH2_TOKEN_INTROSPECT_ENDPOINT, OAUTH2_TOKEN_REVOKE_ENDPOINT};
|
||||
use url::{form_urlencoded::parse as query_parse, Url};
|
||||
|
||||
use kanidm_client::{http::header, KanidmClient, StatusCode};
|
||||
use kanidmd_testkit::{
|
||||
assert_no_cache, ADMIN_TEST_PASSWORD, ADMIN_TEST_USER, NOT_ADMIN_TEST_EMAIL,
|
||||
NOT_ADMIN_TEST_PASSWORD, NOT_ADMIN_TEST_USERNAME, TEST_INTEGRATION_RS_DISPLAY,
|
||||
TEST_INTEGRATION_RS_GROUP_ALL, TEST_INTEGRATION_RS_ID, TEST_INTEGRATION_RS_REDIRECT_URL,
|
||||
TEST_INTEGRATION_RS_URL,
|
||||
};
|
||||
use oauth2_ext::PkceCodeChallenge;
|
||||
use reqwest::header::{HeaderValue, CONTENT_TYPE};
|
||||
use std::collections::{BTreeMap, BTreeSet};
|
||||
use std::convert::TryFrom;
|
||||
use std::str::FromStr;
|
||||
use time::OffsetDateTime;
|
||||
use uri::{OAUTH2_TOKEN_ENDPOINT, OAUTH2_TOKEN_INTROSPECT_ENDPOINT, OAUTH2_TOKEN_REVOKE_ENDPOINT};
|
||||
use url::{form_urlencoded::parse as query_parse, Url};
|
||||
|
||||
/// Tests an OAuth 2.0 / OpenID confidential client Authorisation Client flow.
|
||||
///
|
||||
|
@ -91,10 +90,15 @@ async fn test_oauth2_openid_basic_flow_impl(
|
|||
.expect("Failed to configure account password");
|
||||
|
||||
rsclient
|
||||
.idm_oauth2_rs_update(TEST_INTEGRATION_RS_ID, None, None, None, true, true, true)
|
||||
.idm_oauth2_rs_update(TEST_INTEGRATION_RS_ID, None, None, None, true)
|
||||
.await
|
||||
.expect("Failed to update oauth2 config");
|
||||
|
||||
rsclient
|
||||
.idm_oauth2_rs_rotate_keys(TEST_INTEGRATION_RS_ID, OffsetDateTime::now_utc())
|
||||
.await
|
||||
.expect("Failed to rotate oauth2 keys");
|
||||
|
||||
rsclient
|
||||
.idm_oauth2_rs_update_scope_map(
|
||||
TEST_INTEGRATION_RS_ID,
|
||||
|
@ -621,7 +625,7 @@ async fn test_oauth2_openid_public_flow_impl(
|
|||
.expect("Failed to configure account password");
|
||||
|
||||
rsclient
|
||||
.idm_oauth2_rs_update(TEST_INTEGRATION_RS_ID, None, None, None, true, true, true)
|
||||
.idm_oauth2_rs_update(TEST_INTEGRATION_RS_ID, None, None, None, true)
|
||||
.await
|
||||
.expect("Failed to update oauth2 config");
|
||||
|
||||
|
|
|
@ -1,9 +1,7 @@
|
|||
#![deny(warnings)]
|
||||
use std::path::Path;
|
||||
use std::time::SystemTime;
|
||||
|
||||
use compact_jwt::{traits::JwsVerifiable, JwsCompact, JwsEs256Verifier, JwsVerifier};
|
||||
use kanidm_client::{ClientError, KanidmClient};
|
||||
use kanidm_proto::constants::{ATTR_GIDNUMBER, KSESSIONID};
|
||||
|
||||
use kanidm_proto::internal::{
|
||||
ApiToken, CURegState, Filter, ImageValue, Modify, ModifyList, UatPurpose, UserAuthToken,
|
||||
};
|
||||
|
@ -13,19 +11,16 @@ use kanidm_proto::v1::{
|
|||
};
|
||||
use kanidmd_lib::constants::{NAME_IDM_ADMINS, NAME_SYSTEM_ADMINS};
|
||||
use kanidmd_lib::credential::totp::Totp;
|
||||
|
||||
use kanidmd_lib::prelude::Attribute;
|
||||
use tracing::{debug, trace};
|
||||
|
||||
use kanidmd_testkit::{ADMIN_TEST_PASSWORD, ADMIN_TEST_USER};
|
||||
use std::path::Path;
|
||||
use std::str::FromStr;
|
||||
|
||||
use compact_jwt::{traits::JwsVerifiable, JwsCompact, JwsEs256Verifier, JwsVerifier};
|
||||
use std::time::SystemTime;
|
||||
use time::OffsetDateTime;
|
||||
use tracing::{debug, trace};
|
||||
use webauthn_authenticator_rs::softpasskey::SoftPasskey;
|
||||
use webauthn_authenticator_rs::WebauthnAuthenticator;
|
||||
|
||||
use kanidm_client::{ClientError, KanidmClient};
|
||||
use kanidmd_testkit::{ADMIN_TEST_PASSWORD, ADMIN_TEST_USER};
|
||||
|
||||
const UNIX_TEST_PASSWORD: &str = "unix test user password";
|
||||
|
||||
#[kanidmd_testkit::test]
|
||||
|
@ -851,8 +846,7 @@ async fn test_server_rest_oauth2_basic_lifecycle(rsclient: &KanidmClient) {
|
|||
|
||||
assert_eq!(initial_configs.len(), 1);
|
||||
|
||||
// Get the value. Assert we have oauth2_rs_basic_secret,
|
||||
// but can NOT see the token_secret.
|
||||
// Get the value. Assert we have oauth2_rs_basic_secret.
|
||||
let oauth2_config = rsclient
|
||||
.idm_oauth2_rs_get("test_integration")
|
||||
.await
|
||||
|
@ -866,10 +860,6 @@ async fn test_server_rest_oauth2_basic_lifecycle(rsclient: &KanidmClient) {
|
|||
assert!(oauth2_config
|
||||
.attrs
|
||||
.contains_key(Attribute::OAuth2RsBasicSecret.as_str()));
|
||||
// This is present, but redacted.
|
||||
assert!(oauth2_config
|
||||
.attrs
|
||||
.contains_key(Attribute::OAuth2RsTokenKey.as_str()));
|
||||
|
||||
// Mod delete the secret/key and check them again.
|
||||
// Check we can patch the oauth2_rs_name / oauth2_rs_origin
|
||||
|
@ -880,12 +870,15 @@ async fn test_server_rest_oauth2_basic_lifecycle(rsclient: &KanidmClient) {
|
|||
Some("Test Integration"),
|
||||
Some("https://new_demo.example.com"),
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
)
|
||||
.await
|
||||
.expect("Failed to update config");
|
||||
|
||||
rsclient
|
||||
.idm_oauth2_rs_rotate_keys("test_integration", OffsetDateTime::now_utc())
|
||||
.await
|
||||
.expect("Failed to rotate oauth2 keys");
|
||||
|
||||
let oauth2_config_updated = rsclient
|
||||
.idm_oauth2_rs_get("test_integration")
|
||||
.await
|
||||
|
|
|
@ -73,6 +73,7 @@ serde = { workspace = true }
|
|||
serde_json = { workspace = true }
|
||||
uuid = { workspace = true }
|
||||
url = { workspace = true }
|
||||
time = { workspace = true }
|
||||
|
||||
## See src/cli/webauthn/mod.rs for which features are
|
||||
## required for which target_os here
|
||||
|
|
|
@ -1,12 +1,11 @@
|
|||
use crate::common::OpType;
|
||||
use crate::Oauth2ClaimMapJoin;
|
||||
use crate::{handle_client_error, Oauth2Opt, OutputMode};
|
||||
use anyhow::{Context, Error};
|
||||
use kanidm_proto::internal::{ImageValue, Oauth2ClaimMapJoin as ProtoOauth2ClaimMapJoin};
|
||||
use std::fs::read;
|
||||
use std::process::exit;
|
||||
|
||||
use crate::Oauth2ClaimMapJoin;
|
||||
use kanidm_proto::internal::{ImageValue, Oauth2ClaimMapJoin as ProtoOauth2ClaimMapJoin};
|
||||
|
||||
impl Oauth2Opt {
|
||||
pub fn debug(&self) -> bool {
|
||||
match self {
|
||||
|
@ -47,7 +46,9 @@ impl Oauth2Opt {
|
|||
| Oauth2Opt::EnableStrictRedirectUri { copt, .. }
|
||||
| Oauth2Opt::DisableStrictRedirectUri { copt, .. }
|
||||
| Oauth2Opt::AddOrigin { copt, .. }
|
||||
| Oauth2Opt::RemoveOrigin { copt, .. } => copt.debug,
|
||||
| Oauth2Opt::RemoveOrigin { copt, .. }
|
||||
| Oauth2Opt::RotateCryptographicKeys { copt, .. }
|
||||
| Oauth2Opt::RevokeCryptographicKey { copt, .. } => copt.debug,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -196,7 +197,7 @@ impl Oauth2Opt {
|
|||
Oauth2Opt::ResetSecrets(cbopt) => {
|
||||
let client = cbopt.copt.to_client(OpType::Write).await;
|
||||
match client
|
||||
.idm_oauth2_rs_update(cbopt.name.as_str(), None, None, None, true, true, true)
|
||||
.idm_oauth2_rs_update(cbopt.name.as_str(), None, None, None, true)
|
||||
.await
|
||||
{
|
||||
Ok(_) => println!("Success"),
|
||||
|
@ -238,8 +239,6 @@ impl Oauth2Opt {
|
|||
Some(cbopt.displayname.as_str()),
|
||||
None,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
)
|
||||
.await
|
||||
{
|
||||
|
@ -256,8 +255,6 @@ impl Oauth2Opt {
|
|||
None,
|
||||
None,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
)
|
||||
.await
|
||||
{
|
||||
|
@ -268,15 +265,7 @@ impl Oauth2Opt {
|
|||
Oauth2Opt::SetLandingUrl { nopt, url } => {
|
||||
let client = nopt.copt.to_client(OpType::Write).await;
|
||||
match client
|
||||
.idm_oauth2_rs_update(
|
||||
nopt.name.as_str(),
|
||||
None,
|
||||
None,
|
||||
Some(url.as_str()),
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
)
|
||||
.idm_oauth2_rs_update(nopt.name.as_str(), None, None, Some(url.as_str()), false)
|
||||
.await
|
||||
{
|
||||
Ok(_) => println!("Success"),
|
||||
|
@ -522,6 +511,30 @@ impl Oauth2Opt {
|
|||
Err(e) => handle_client_error(e, copt.output_mode),
|
||||
}
|
||||
}
|
||||
Oauth2Opt::RotateCryptographicKeys {
|
||||
copt,
|
||||
name,
|
||||
rotate_at,
|
||||
} => {
|
||||
let client = copt.to_client(OpType::Write).await;
|
||||
match client
|
||||
.idm_oauth2_rs_rotate_keys(name.as_str(), *rotate_at)
|
||||
.await
|
||||
{
|
||||
Ok(_) => println!("Success"),
|
||||
Err(e) => handle_client_error(e, copt.output_mode),
|
||||
}
|
||||
}
|
||||
Oauth2Opt::RevokeCryptographicKey { copt, name, key_id } => {
|
||||
let client = copt.to_client(OpType::Write).await;
|
||||
match client
|
||||
.idm_oauth2_rs_revoke_key(name.as_str(), key_id.as_str())
|
||||
.await
|
||||
{
|
||||
Ok(_) => println!("Success"),
|
||||
Err(e) => handle_client_error(e, copt.output_mode),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,12 @@
|
|||
use clap::{builder::PossibleValue, Args, Subcommand, ValueEnum};
|
||||
use kanidm_proto::internal::ImageType;
|
||||
use std::fmt;
|
||||
use time::format_description::well_known::Rfc3339;
|
||||
use time::OffsetDateTime;
|
||||
|
||||
fn parse_rfc3339(input: &str) -> Result<OffsetDateTime, time::error::Parse > {
|
||||
OffsetDateTime::parse(input, &Rfc3339)
|
||||
}
|
||||
|
||||
#[derive(Debug, Args)]
|
||||
pub struct Named {
|
||||
|
@ -1131,8 +1137,9 @@ pub enum Oauth2Opt {
|
|||
group: String,
|
||||
},
|
||||
|
||||
#[clap(name = "reset-secrets")]
|
||||
/// Reset the secrets associated to this client
|
||||
#[clap(name = "reset-basic-secret")]
|
||||
/// Reset the client basic secret. You will need to update your client after
|
||||
/// executing this.
|
||||
ResetSecrets(Named),
|
||||
#[clap(name = "show-basic-secret")]
|
||||
/// Show the associated basic secret for this client
|
||||
|
@ -1143,7 +1150,7 @@ pub enum Oauth2Opt {
|
|||
/// Set a new display name for a client
|
||||
#[clap(name = "set-displayname")]
|
||||
SetDisplayname(Oauth2SetDisplayname),
|
||||
/// Set a new name for this client. You may need to update
|
||||
/// Set a new name for this client. You will need to update
|
||||
/// your integrated applications after this so that they continue to
|
||||
/// function correctly.
|
||||
#[clap(name = "set-name")]
|
||||
|
@ -1256,6 +1263,28 @@ pub enum Oauth2Opt {
|
|||
#[cfg(feature = "dev-oauth2-device-flow")]
|
||||
/// Disable OAuth2 Device Flow authentication
|
||||
DeviceFlowDisable(Named),
|
||||
/// Rotate the signing and encryption keys used by this client. The rotation
|
||||
/// will occur at the specified time, or immediately if the time is in the past.
|
||||
/// Past signatures will continue to operate even after a rotation occurs. If you
|
||||
/// have concerns a key is compromised, then you should revoke it instead.
|
||||
#[clap(name = "rotate-cryptographic-keys")]
|
||||
RotateCryptographicKeys {
|
||||
#[clap(flatten)]
|
||||
copt: CommonOpt,
|
||||
name: String,
|
||||
#[clap(value_parser = parse_rfc3339)]
|
||||
rotate_at: OffsetDateTime,
|
||||
},
|
||||
/// Revoke the signing and encryption keys used by this client. This will immediately
|
||||
/// trigger a rotation of the key in question, and signtatures or tokens issued by
|
||||
/// the revoked key will not be considered valid.
|
||||
#[clap(name = "revoke-cryptographic-key")]
|
||||
RevokeCryptographicKey {
|
||||
#[clap(flatten)]
|
||||
copt: CommonOpt,
|
||||
name: String,
|
||||
key_id: String,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Args, Debug)]
|
||||
|
|
Loading…
Reference in a new issue