WIP - Improve Auth Proto to Support Webauthn (#333)

This is a rewrite of the "on the wire" json for auth. This is a breaking change required to allow webauthn to work given limitations within Webauthn as a standard and how mixed credentials are challenged for.
This commit is contained in:
Firstyear 2020-12-26 13:58:32 +10:00 committed by GitHub
parent a008ca3cf1
commit ebdb57bbe7
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
19 changed files with 1628 additions and 1229 deletions

355
Cargo.lock generated
View file

@ -62,9 +62,9 @@ checksum = "e8fd72866655d1904d6b0997d0b07ba561047d070fbe29de039031c641b61217"
[[package]] [[package]]
name = "ahash" name = "ahash"
version = "0.4.6" version = "0.4.7"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f6789e291be47ace86a60303502173d84af8327e3627ecf334356ee0f87a164c" checksum = "739f4a8db6605981345c5654f3a85b056ce52f37a39d34da03f25bf2151ea16e"
dependencies = [ dependencies = [
"const-random", "const-random",
] ]
@ -80,9 +80,9 @@ dependencies = [
[[package]] [[package]]
name = "anyhow" name = "anyhow"
version = "1.0.34" version = "1.0.36"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bf8dcb5b4bbaa28653b647d8c77bd4ed40183b48882e130c1f1ffb73de069fd7" checksum = "68803225a7b13e47191bab76f2687382b60d259e8cf37f6e1893658b84bb9479"
[[package]] [[package]]
name = "arrayref" name = "arrayref"
@ -146,10 +146,12 @@ dependencies = [
[[package]] [[package]]
name = "async-h1" name = "async-h1"
version = "2.1.4" version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3fd9a5f3dbb5065856974e08c2ac24e6f81da6e39d2328de1c03a9a2b34ffb01" checksum = "e5c68a75f812ff0f299e142c06dd0c34e3295a594d935e61eeb6c77041d1d4dc"
dependencies = [ dependencies = [
"async-channel",
"async-dup",
"async-std", "async-std",
"byte-pool", "byte-pool",
"futures-core", "futures-core",
@ -157,7 +159,7 @@ dependencies = [
"httparse", "httparse",
"lazy_static", "lazy_static",
"log", "log",
"pin-project-lite 0.1.11", "pin-project 1.0.2",
] ]
[[package]] [[package]]
@ -189,6 +191,22 @@ dependencies = [
"event-listener", "event-listener",
] ]
[[package]]
name = "async-process"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4c8cea09c1fb10a317d1b5af8024eeba256d6554763e85ecd90ff8df31c7bbda"
dependencies = [
"async-io",
"blocking",
"cfg-if 0.1.10",
"event-listener",
"futures-lite",
"once_cell",
"signal-hook",
"winapi 0.3.9",
]
[[package]] [[package]]
name = "async-session" name = "async-session"
version = "2.0.1" version = "2.0.1"
@ -226,13 +244,15 @@ dependencies = [
[[package]] [[package]]
name = "async-std" name = "async-std"
version = "1.7.0" version = "1.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a7e82538bc65a25dbdff70e4c5439d52f068048ab97cdea0acd73f131594caa1" checksum = "8f9f84f1280a2b436a2c77c2582602732b6c2f4321d5494d6e799e6c367859a8"
dependencies = [ dependencies = [
"async-channel",
"async-global-executor", "async-global-executor",
"async-io", "async-io",
"async-mutex", "async-mutex",
"async-process",
"blocking", "blocking",
"crossbeam-utils 0.8.1", "crossbeam-utils 0.8.1",
"futures-channel", "futures-channel",
@ -245,7 +265,7 @@ dependencies = [
"memchr", "memchr",
"num_cpus", "num_cpus",
"once_cell", "once_cell",
"pin-project-lite 0.1.11", "pin-project-lite 0.2.0",
"pin-utils", "pin-utils",
"slab", "slab",
"wasm-bindgen-futures", "wasm-bindgen-futures",
@ -259,9 +279,9 @@ checksum = "e91831deabf0d6d7ec49552e489aed63b7456a7a3c46cff62adad428110b0af0"
[[package]] [[package]]
name = "async-tls" name = "async-tls"
version = "0.10.0" version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d85a97c4a0ecce878efd3f945f119c78a646d8975340bca0398f9bb05c30cc52" checksum = "2f23d769dbf1838d5df5156e7b1ad404f4c463d1ac2c6aeb6cd943630f8a8400"
dependencies = [ dependencies = [
"futures-core", "futures-core",
"futures-io", "futures-io",
@ -366,9 +386,9 @@ dependencies = [
[[package]] [[package]]
name = "bit-vec" name = "bit-vec"
version = "0.6.2" version = "0.6.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5f0dc55f2d8a1a85650ac47858bb001b4c0dd73d79e3c455a842925e68d29cd3" checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb"
[[package]] [[package]]
name = "bitflags" name = "bitflags"
@ -376,17 +396,6 @@ version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
[[package]]
name = "blake2b_simd"
version = "0.5.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "afa748e348ad3be8263be728124b24a24f268266f6f5d58af9d75f6a40b5c587"
dependencies = [
"arrayref",
"arrayvec",
"constant_time_eq",
]
[[package]] [[package]]
name = "blake3" name = "blake3"
version = "0.3.7" version = "0.3.7"
@ -515,9 +524,9 @@ dependencies = [
[[package]] [[package]]
name = "cc" name = "cc"
version = "1.0.65" version = "1.0.66"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "95752358c8f7552394baf48cd82695b345628ad3f170d607de3ca03b8dacca15" checksum = "4c0496836a84f8d0495758516b8621a622beb77c0fed418570e50764093ced48"
[[package]] [[package]]
name = "cfg-if" name = "cfg-if"
@ -537,11 +546,13 @@ version = "0.4.19"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73" checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73"
dependencies = [ dependencies = [
"js-sys",
"libc", "libc",
"num-integer", "num-integer",
"num-traits", "num-traits",
"serde", "serde",
"time 0.1.44", "time 0.1.44",
"wasm-bindgen",
"winapi 0.3.9", "winapi 0.3.9",
] ]
@ -565,22 +576,13 @@ dependencies = [
"unicode-width", "unicode-width",
] ]
[[package]]
name = "cloudabi"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4344512281c643ae7638bbabc3af17a11307803ec8f0fcad9fae512a8bf36467"
dependencies = [
"bitflags",
]
[[package]] [[package]]
name = "concread" name = "concread"
version = "0.2.6" version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "14fe52c39ed4e846fb3e6ad4bfe46224ef24db64ff7c5f496d2501c88c270b14" checksum = "14fe52c39ed4e846fb3e6ad4bfe46224ef24db64ff7c5f496d2501c88c270b14"
dependencies = [ dependencies = [
"ahash 0.4.6", "ahash 0.4.7",
"crossbeam", "crossbeam",
"crossbeam-epoch 0.8.2", "crossbeam-epoch 0.8.2",
"crossbeam-utils 0.7.2", "crossbeam-utils 0.7.2",
@ -599,21 +601,11 @@ dependencies = [
"cache-padded", "cache-padded",
] ]
[[package]]
name = "console_error_panic_hook"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b8d976903543e0c48546a91908f21588a680a8c8f984df9a5d69feccb2b2a211"
dependencies = [
"cfg-if 0.1.10",
"wasm-bindgen",
]
[[package]] [[package]]
name = "const-random" name = "const-random"
version = "0.1.12" version = "0.1.13"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "486d435a7351580347279f374cb8a3c16937485441db80181357b7c4d70f17ed" checksum = "f590d95d011aa80b063ffe3253422ed5aa462af4e9867d43ce8337562bac77c4"
dependencies = [ dependencies = [
"const-random-macro", "const-random-macro",
"proc-macro-hack", "proc-macro-hack",
@ -621,9 +613,9 @@ dependencies = [
[[package]] [[package]]
name = "const-random-macro" name = "const-random-macro"
version = "0.1.12" version = "0.1.13"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49a84d8ff70e3ec52311109b019c27672b4c1929e4cf7c18bcf0cd9fb5e230be" checksum = "615f6e27d000a2bffbc7f2f6a8669179378fa27ee4d0a509e985dfc0a7defb40"
dependencies = [ dependencies = [
"getrandom 0.2.0", "getrandom 0.2.0",
"lazy_static", "lazy_static",
@ -633,9 +625,9 @@ dependencies = [
[[package]] [[package]]
name = "const_fn" name = "const_fn"
version = "0.4.3" version = "0.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c478836e029dcef17fb47c89023448c64f781a046e0300e257ad8225ae59afab" checksum = "cd51eab21ab4fd6a3bf889e2d0958c0a6e3a61ad04260325e919e652a2a62826"
[[package]] [[package]]
name = "constant_time_eq" name = "constant_time_eq"
@ -698,6 +690,12 @@ version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8aebca1129a03dc6dc2b127edd729435bbc4a37e1d5f4d7513165089ceb02634" checksum = "8aebca1129a03dc6dc2b127edd729435bbc4a37e1d5f4d7513165089ceb02634"
[[package]]
name = "cpuid-bool"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dcb25d077389e53838a8158c8e99174c5a9d902dee4904320db714f3c653ffba"
[[package]] [[package]]
name = "criterion" name = "criterion"
version = "0.3.3" version = "0.3.3"
@ -709,7 +707,7 @@ dependencies = [
"clap", "clap",
"criterion-plot", "criterion-plot",
"csv", "csv",
"itertools 0.9.0", "itertools",
"lazy_static", "lazy_static",
"num-traits", "num-traits",
"oorandom", "oorandom",
@ -731,7 +729,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e022feadec601fba1649cfa83586381a4ad31c6bf3a9ab7d408118b05dd9889d" checksum = "e022feadec601fba1649cfa83586381a4ad31c6bf3a9ab7d408118b05dd9889d"
dependencies = [ dependencies = [
"cast", "cast",
"itertools 0.9.0", "itertools",
] ]
[[package]] [[package]]
@ -1004,20 +1002,20 @@ dependencies = [
] ]
[[package]] [[package]]
name = "dirs" name = "dirs-next"
version = "2.0.2" version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13aea89a5c93364a98e9b37b2fa237effbb694d5cfe01c5b70941f7eb087d5e3" checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1"
dependencies = [ dependencies = [
"cfg-if 0.1.10", "cfg-if 1.0.0",
"dirs-sys", "dirs-sys-next",
] ]
[[package]] [[package]]
name = "dirs-sys" name = "dirs-sys-next"
version = "0.3.5" version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e93d7f5705de3e49895a2b5e0b8855a1c27f080192ae9c32a6432d50741a57a" checksum = "99de365f605554ae33f115102a02057d4fc18b01f3284d6870be0938743cfe7d"
dependencies = [ dependencies = [
"libc", "libc",
"redox_users", "redox_users",
@ -1093,9 +1091,9 @@ checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a"
[[package]] [[package]]
name = "fancy-regex" name = "fancy-regex"
version = "0.3.5" version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ae91abf6555234338687bb47913978d275539235fcb77ba9863b779090b42b14" checksum = "36996e5f56f32ca51a937f325094fa450b32df871af1a89be331b7145b931bfc"
dependencies = [ dependencies = [
"bit-set", "bit-set",
"regex", "regex",
@ -1129,7 +1127,7 @@ dependencies = [
[[package]] [[package]]
name = "fernet" name = "fernet"
version = "0.1.3" version = "0.1.3"
source = "git+https://github.com/mozilla-services/fernet-rs.git#401fde478c63e868f126ff7c92abad1f96107ca4" source = "git+https://github.com/mozilla-services/fernet-rs.git#ec7f9091e0761c0dfe92ad77321ca161b929dbe4"
dependencies = [ dependencies = [
"base64 0.12.3", "base64 0.12.3",
"byteorder", "byteorder",
@ -1235,16 +1233,16 @@ checksum = "611834ce18aaa1bd13c4b374f5d653e1027cf99b6b502584ff8c9a64413b30bb"
[[package]] [[package]]
name = "futures-lite" name = "futures-lite"
version = "1.11.2" version = "1.11.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5e6c079abfac3ab269e2927ec048dabc89d009ebfdda6b8ee86624f30c689658" checksum = "b4481d0cd0de1d204a4fa55e7d45f07b1d958abcb06714b3446438e2eff695fb"
dependencies = [ dependencies = [
"fastrand", "fastrand",
"futures-core", "futures-core",
"futures-io", "futures-io",
"memchr", "memchr",
"parking", "parking",
"pin-project-lite 0.1.11", "pin-project-lite 0.2.0",
"waker-fn", "waker-fn",
] ]
@ -1402,9 +1400,9 @@ checksum = "d7afe4a420e3fe79967a00898cc1f4db7c8a49a9333a29f8a4bd76a253d5cd04"
[[package]] [[package]]
name = "heck" name = "heck"
version = "0.3.1" version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "20564e78d53d2bb135c343b3f47714a56af2061f1c928fdb541dc7b9fdd94205" checksum = "87cbf45460356b7deeb5e3415b5563308c0a9b057c85e12b06ad551f98d0a6ac"
dependencies = [ dependencies = [
"unicode-segmentation", "unicode-segmentation",
] ]
@ -1450,9 +1448,9 @@ dependencies = [
[[package]] [[package]]
name = "http" name = "http"
version = "0.2.1" version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "28d569972648b2c512421b5f2a405ad6ac9666547189d0c5477a3f200f3e02f9" checksum = "84129d298a6d57d246960ff8eb831ca4af3f96d29e2e28848dae275408658e26"
dependencies = [ dependencies = [
"bytes", "bytes",
"fnv", "fnv",
@ -1482,9 +1480,9 @@ dependencies = [
[[package]] [[package]]
name = "http-types" name = "http-types"
version = "2.8.0" version = "2.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6f316f6a06306570e899238d3b85375f350cfceda60ec47807c4164d6e169e58" checksum = "f2ab8d0085fb82859c9adf050bd53992297ecdd03a665a230dfa50c8c964bf3d"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"async-channel", "async-channel",
@ -1520,7 +1518,7 @@ version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df004cfca50ef23c36850aaaa59ad52cc70d0e90243c3c7737a4dd32dc7a3c4f" checksum = "df004cfca50ef23c36850aaaa59ad52cc70d0e90243c3c7737a4dd32dc7a3c4f"
dependencies = [ dependencies = [
"quick-error", "quick-error 1.2.3",
] ]
[[package]] [[package]]
@ -1591,9 +1589,9 @@ dependencies = [
[[package]] [[package]]
name = "indexmap" name = "indexmap"
version = "1.6.0" version = "1.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "55e2e4c765aa53a0424761bf9f41aa7a6ac1efa87238f59560640e27fca028f2" checksum = "4fb1fa934250de4de8aef298d81c729a7d33d8c239daa3a7575e6b92bfc7313b"
dependencies = [ dependencies = [
"autocfg", "autocfg",
"hashbrown 0.9.1", "hashbrown 0.9.1",
@ -1629,15 +1627,6 @@ version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "47be2f14c678be2fdcab04ab1171db51b2762ce6f0a8ee87c8dd4a04ed216135" checksum = "47be2f14c678be2fdcab04ab1171db51b2762ce6f0a8ee87c8dd4a04ed216135"
[[package]]
name = "itertools"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f56a2d0bc861f9165be4eb3442afd3c236d8a98afd426f65d92324ae1091a484"
dependencies = [
"either",
]
[[package]] [[package]]
name = "itertools" name = "itertools"
version = "0.9.0" version = "0.9.0"
@ -1848,9 +1837,9 @@ dependencies = [
[[package]] [[package]]
name = "libc" name = "libc"
version = "0.2.80" version = "0.2.81"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4d58d1b70b004888f764dfbf6a26a3b0342a1632d33968e4a179d8011c760614" checksum = "1482821306169ec4d07f6aca392a4681f66c75c9918aa49641a2595db64053cb"
[[package]] [[package]]
name = "libnss" name = "libnss"
@ -1979,9 +1968,9 @@ dependencies = [
[[package]] [[package]]
name = "mio" name = "mio"
version = "0.6.22" version = "0.6.23"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fce347092656428bc8eaf6201042cb551b8d67855af7374542a92a0fbfcac430" checksum = "4afd66f5b91bf2a3bc13fad0e21caedac168ca4c707504e75585648ae80e4cc4"
dependencies = [ dependencies = [
"cfg-if 0.1.10", "cfg-if 0.1.10",
"fuchsia-zircon", "fuchsia-zircon",
@ -2071,9 +2060,9 @@ dependencies = [
[[package]] [[package]]
name = "net2" name = "net2"
version = "0.2.36" version = "0.2.37"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d7cf75f38f16cb05ea017784dc6dbfd354f76c223dba37701734c4f5a9337d02" checksum = "391630d12b68002ae1e25e8f974306474966550ad82dac6886fb8910c19568ae"
dependencies = [ dependencies = [
"cfg-if 0.1.10", "cfg-if 0.1.10",
"libc", "libc",
@ -2229,12 +2218,12 @@ checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5"
[[package]] [[package]]
name = "openssl" name = "openssl"
version = "0.10.30" version = "0.10.32"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8d575eff3665419f9b83678ff2815858ad9d11567e082f5ac1814baba4e2bcb4" checksum = "038d43985d1ddca7a9900630d8cd031b56e4794eecc2e9ea39dd17aa04399a70"
dependencies = [ dependencies = [
"bitflags", "bitflags",
"cfg-if 0.1.10", "cfg-if 1.0.0",
"foreign-types", "foreign-types",
"lazy_static", "lazy_static",
"libc", "libc",
@ -2249,9 +2238,9 @@ checksum = "77af24da69f9d9341038eba93a073b1fdaaa1b788221b00a69bce9e762cb32de"
[[package]] [[package]]
name = "openssl-sys" name = "openssl-sys"
version = "0.9.58" version = "0.9.60"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a842db4709b604f0fe5d1170ae3565899be2ad3d9cbc72dedc789ac0511f78de" checksum = "921fc71883267538946025deffb622905ecad223c28efbfdef9bb59a0175f3e6"
dependencies = [ dependencies = [
"autocfg", "autocfg",
"cc", "cc",
@ -2289,12 +2278,11 @@ dependencies = [
[[package]] [[package]]
name = "parking_lot_core" name = "parking_lot_core"
version = "0.8.0" version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c361aa727dd08437f2f1447be8b59a33b0edd15e0fcee698f935613d9efbca9b" checksum = "9ccb628cad4f84851442432c60ad8e1f607e29752d0bf072cbd0baf28aa34272"
dependencies = [ dependencies = [
"cfg-if 0.1.10", "cfg-if 1.0.0",
"cloudabi",
"instant", "instant",
"libc", "libc",
"redox_syscall", "redox_syscall",
@ -2418,11 +2406,11 @@ dependencies = [
[[package]] [[package]]
name = "polyval" name = "polyval"
version = "0.4.2" version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b3fd900a291ceb8b99799cc8cd3d1d3403a51721e015bc533528b2ceafcc443c" checksum = "b4fd92d8e0c06d08525d2e2643cc2b5c80c69ae8eb12c18272d501cd7079ccc0"
dependencies = [ dependencies = [
"cfg-if 1.0.0", "cpuid-bool 0.2.0",
"universal-hash", "universal-hash",
] ]
@ -2497,10 +2485,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0"
[[package]] [[package]]
name = "quote" name = "quick-error"
version = "1.0.7" version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aa563d17ecb180e500da1cfd2b028310ac758de548efdd203e18f283af693f37" checksum = "3ac73b1112776fc109b2e61909bc46c7e1bf0d7f690ffb1676553acce16d5cda"
[[package]]
name = "quote"
version = "1.0.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "991431c3519a3f36861882da93630ce66b52918dcf1b8e2fd66b397fc96f28df"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
] ]
@ -2606,7 +2600,6 @@ checksum = "de0737333e7a9502c789a36d7c7fa6092a49895d4faa31ca5df163857ded2e9d"
dependencies = [ dependencies = [
"getrandom 0.1.15", "getrandom 0.1.15",
"redox_syscall", "redox_syscall",
"rust-argon2",
] ]
[[package]] [[package]]
@ -2647,9 +2640,9 @@ dependencies = [
[[package]] [[package]]
name = "reqwest" name = "reqwest"
version = "0.10.9" version = "0.10.10"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fb15d6255c792356a0f578d8a645c677904dc02e862bebe2ecc18e0c01b9a0ce" checksum = "0718f81a8e14c4dbb3b34cf23dc6aaf9ab8a0dfec160c534b3dbca1aaa21f47c"
dependencies = [ dependencies = [
"base64 0.13.0", "base64 0.13.0",
"bytes", "bytes",
@ -2680,16 +2673,15 @@ dependencies = [
"url", "url",
"wasm-bindgen", "wasm-bindgen",
"wasm-bindgen-futures", "wasm-bindgen-futures",
"wasm-bindgen-test",
"web-sys", "web-sys",
"winreg", "winreg",
] ]
[[package]] [[package]]
name = "ring" name = "ring"
version = "0.16.18" version = "0.16.19"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "70017ed5c555d79ee3538fc63ca09c70ad8f317dcadc1adc2c496b60c22bb24f" checksum = "024a1e66fea74c66c66624ee5622a7ff0e4b73a13b4f5c326ddb50c708944226"
dependencies = [ dependencies = [
"cc", "cc",
"libc", "libc",
@ -2738,18 +2730,6 @@ dependencies = [
"time 0.1.44", "time 0.1.44",
] ]
[[package]]
name = "rust-argon2"
version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4b18820d944b33caa75a71378964ac46f58517c92b6ae5f762636247c09e78fb"
dependencies = [
"base64 0.13.0",
"blake2b_simd",
"constant_time_eq",
"crossbeam-utils 0.8.1",
]
[[package]] [[package]]
name = "rustc_version" name = "rustc_version"
version = "0.2.3" version = "0.2.3"
@ -2761,11 +2741,11 @@ dependencies = [
[[package]] [[package]]
name = "rustls" name = "rustls"
version = "0.18.1" version = "0.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5d1126dcf58e93cee7d098dbda643b5f92ed724f1f6a63007c1116eed6700c81" checksum = "064fd21ff87c6e87ed4506e68beb42459caa4a0e2eb144932e6776768556980b"
dependencies = [ dependencies = [
"base64 0.12.3", "base64 0.13.0",
"log", "log",
"ring", "ring",
"sct", "sct",
@ -2806,12 +2786,6 @@ dependencies = [
"parking_lot", "parking_lot",
] ]
[[package]]
name = "scoped-tls"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ea6a9290e3c9cf0f18145ef7ffa62d68ee0bf5fcd651017e586dc7fd5da448c2"
[[package]] [[package]]
name = "scopeguard" name = "scopeguard"
version = "1.1.0" version = "1.1.0"
@ -2868,9 +2842,9 @@ checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3"
[[package]] [[package]]
name = "serde" name = "serde"
version = "1.0.117" version = "1.0.118"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b88fa983de7720629c9387e9f517353ed404164b1e482c970a90c1a4aaf7dc1a" checksum = "06c64263859d87aa2eb554587e2d23183398d617427327cf2b3d0ed8c69e4800"
dependencies = [ dependencies = [
"serde_derive", "serde_derive",
] ]
@ -2896,9 +2870,9 @@ dependencies = [
[[package]] [[package]]
name = "serde_derive" name = "serde_derive"
version = "1.0.117" version = "1.0.118"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cbd1ae72adb44aab48f325a02444a5fc079349a8d804c1fc922aed3f7454c74e" checksum = "c84d3526699cd55261af4b941e4e725444df67aa4f9e6a3564f18030d12672df"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@ -2907,9 +2881,9 @@ dependencies = [
[[package]] [[package]]
name = "serde_json" name = "serde_json"
version = "1.0.59" version = "1.0.60"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dcac07dbffa1c65e7f816ab9eba78eb142c6d44410f4eeba1e26e4f5dfa56b95" checksum = "1500e84d27fe482ed1dc791a56eddc2f230046a040fa908c08bda1d9fb615779"
dependencies = [ dependencies = [
"itoa", "itoa",
"ryu", "ryu",
@ -2918,9 +2892,9 @@ dependencies = [
[[package]] [[package]]
name = "serde_qs" name = "serde_qs"
version = "0.7.0" version = "0.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9408a61dabe404c76cec504ec510f7d92f41dc0a9362a0db8ab73d141cfbf93f" checksum = "5af82de3c6549b001bec34961ff2d6a54339a87bab37ce901b693401f27de6cb"
dependencies = [ dependencies = [
"data-encoding", "data-encoding",
"percent-encoding", "percent-encoding",
@ -2966,18 +2940,28 @@ checksum = "6e7aab86fe2149bad8c507606bdb3f4ef5e7b2380eb92350f56122cca72a42a8"
dependencies = [ dependencies = [
"block-buffer 0.9.0", "block-buffer 0.9.0",
"cfg-if 1.0.0", "cfg-if 1.0.0",
"cpuid-bool", "cpuid-bool 0.1.2",
"digest 0.9.0", "digest 0.9.0",
"opaque-debug 0.3.0", "opaque-debug 0.3.0",
] ]
[[package]] [[package]]
name = "shellexpand" name = "shellexpand"
version = "2.0.0" version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a2b22262a9aaf9464d356f656fea420634f78c881c5eebd5ef5e66d8b9bc603" checksum = "83bdb7831b2d85ddf4a7b148aa19d0587eddbe8671a436b7bd1182eaad0f2829"
dependencies = [ dependencies = [
"dirs", "dirs-next",
]
[[package]]
name = "signal-hook"
version = "0.1.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "604508c1418b99dfe1925ca9224829bb2a8a9a04dda655cc01fcad46f4ab05ed"
dependencies = [
"libc",
"signal-hook-registry",
] ]
[[package]] [[package]]
@ -3006,22 +2990,21 @@ checksum = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8"
[[package]] [[package]]
name = "smallvec" name = "smallvec"
version = "1.5.0" version = "1.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7acad6f34eb9e8a259d3283d1e8c1d34d7415943d4895f65cc73813c7396fc85" checksum = "ae524f056d7d770e174287294f562e95044c68e88dec909a00d2094805db9d75"
dependencies = [ dependencies = [
"serde", "serde",
] ]
[[package]] [[package]]
name = "socket2" name = "socket2"
version = "0.3.17" version = "0.3.19"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2c29947abdee2a218277abeca306f25789c938e500ea5a9d4b12a5a504466902" checksum = "122e570113d28d773067fab24266b66753f6ea915758651696b6e35e49f88d6e"
dependencies = [ dependencies = [
"cfg-if 1.0.0", "cfg-if 1.0.0",
"libc", "libc",
"redox_syscall",
"winapi 0.3.9", "winapi 0.3.9",
] ]
@ -3138,15 +3121,15 @@ dependencies = [
[[package]] [[package]]
name = "subtle" name = "subtle"
version = "2.3.0" version = "2.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "343f3f510c2915908f155e94f17220b19ccfacf2a64a2a5d8004f2c3e311e7fd" checksum = "1e81da0851ada1f3e9d4312c704aa4f8806f0f9d69faaf8df2f3464b4a9437c2"
[[package]] [[package]]
name = "syn" name = "syn"
version = "1.0.53" version = "1.0.55"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8833e20724c24de12bbaba5ad230ea61c3eafb05b881c7c9d3cfe8638b187e68" checksum = "a571a711dddd09019ccc628e1b17fe87c59b09d513c06c026877aa708334f37a"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@ -3251,9 +3234,9 @@ dependencies = [
[[package]] [[package]]
name = "tide-rustls" name = "tide-rustls"
version = "0.1.4" version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b34fba7cb60c3c465c82ff5d215f341774f8f11b0e45691a7134af4238e395a" checksum = "8b2faeed43463ab96a5362256554787c10752f1173c9ffaf7b553842ef12b6c5"
dependencies = [ dependencies = [
"async-dup", "async-dup",
"async-h1", "async-h1",
@ -3349,9 +3332,9 @@ checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c"
[[package]] [[package]]
name = "tokio" name = "tokio"
version = "0.2.23" version = "0.2.24"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a6d7ad61edd59bfcc7e80dababf0f4aed2e6d5e0ba1659356ae889752dfc12ff" checksum = "099837d3464c16a808060bb3f02263b412f6fafcb5d01c533d309985fbeebe48"
dependencies = [ dependencies = [
"bytes", "bytes",
"fnv", "fnv",
@ -3418,9 +3401,9 @@ dependencies = [
[[package]] [[package]]
name = "toml" name = "toml"
version = "0.5.7" version = "0.5.8"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "75cf45bb0bef80604d001caaec0d09da99611b3c0fd39d3080468875cdb65645" checksum = "a31142970826733df8241ef35dc040ef98c679ab14d7c3e54d827099b3acecaa"
dependencies = [ dependencies = [
"serde", "serde",
] ]
@ -3570,9 +3553,9 @@ dependencies = [
[[package]] [[package]]
name = "vcpkg" name = "vcpkg"
version = "0.2.10" version = "0.2.11"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6454029bf181f092ad1b853286f23e2c507d8e8194d01d92da4a55c274a5508c" checksum = "b00bca6106a5e23f3eee943593759b7fcddb00554332e856d990c893966879fb"
[[package]] [[package]]
name = "vec-arena" name = "vec-arena"
@ -3699,30 +3682,6 @@ version = "0.2.69"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7e7811dd7f9398f14cc76efd356f98f03aa30419dea46aa810d71e819fc97158" checksum = "7e7811dd7f9398f14cc76efd356f98f03aa30419dea46aa810d71e819fc97158"
[[package]]
name = "wasm-bindgen-test"
version = "0.3.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0355fa0c1f9b792a09b6dcb6a8be24d51e71e6d74972f9eb4a44c4c004d24a25"
dependencies = [
"console_error_panic_hook",
"js-sys",
"scoped-tls",
"wasm-bindgen",
"wasm-bindgen-futures",
"wasm-bindgen-test-macro",
]
[[package]]
name = "wasm-bindgen-test-macro"
version = "0.3.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "27e07b46b98024c2ba2f9e83a10c2ef0515f057f2da299c1762a2017de80438b"
dependencies = [
"proc-macro2",
"quote",
]
[[package]] [[package]]
name = "web-sys" name = "web-sys"
version = "0.3.46" version = "0.3.46"
@ -3770,9 +3729,9 @@ dependencies = [
[[package]] [[package]]
name = "webpki" name = "webpki"
version = "0.21.3" version = "0.21.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ab146130f5f790d45f82aeeb09e55a256573373ec64409fc19a6fb82fb1032ae" checksum = "b8e38c0608262c46d4a56202ebabdeb094cef7e560ca7a226c6bf055188aa4ea"
dependencies = [ dependencies = [
"ring", "ring",
"untrusted", "untrusted",
@ -3780,9 +3739,9 @@ dependencies = [
[[package]] [[package]]
name = "webpki-roots" name = "webpki-roots"
version = "0.20.0" version = "0.21.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0f20dea7535251981a9670857150d571846545088359b28e4951d350bdaf179f" checksum = "82015b7e0b8bad8185994674a13a93306bea76cf5a16c5a181382fd3a5ec2376"
dependencies = [ dependencies = [
"webpki", "webpki",
] ]
@ -3860,9 +3819,9 @@ dependencies = [
[[package]] [[package]]
name = "zeroize" name = "zeroize"
version = "1.1.1" version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "05f33972566adbd2d3588b0491eb94b98b43695c4ef897903470ede4f3f5a28a" checksum = "81a974bcdd357f0dca4d41677db03436324d45a4c9ed2d0b873a5a360ce41c36"
dependencies = [ dependencies = [
"zeroize_derive", "zeroize_derive",
] ]
@ -3881,16 +3840,16 @@ dependencies = [
[[package]] [[package]]
name = "zxcvbn" name = "zxcvbn"
version = "2.0.1" version = "2.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a7b69cd8a6484379ef04457ba1c00aaadad166c693b1b6a625b01bcc694b212b" checksum = "a5f9db3a05b2ee81dcda4602487314ab654eca316f517be2e2e64175658f0dd0"
dependencies = [ dependencies = [
"chrono", "chrono",
"derive_builder", "derive_builder",
"fancy-regex", "fancy-regex",
"itertools 0.8.2", "itertools",
"lazy_static", "lazy_static",
"quick-error", "quick-error 2.0.0",
"regex", "regex",
"serde", "serde",
"serde_derive", "serde_derive",

View file

@ -47,8 +47,8 @@ New Design (late 2020 - future)
=============================== ===============================
A clearer configuration of credentials and how they function for the account is needed. This means A clearer configuration of credentials and how they function for the account is needed. This means
changing how the current credential memory representation works. The database format does *not* need changing how the current credential memory representation works. The database format does need to
to change, but *may* be extended. change.
Currently Credentials can have *any* combination of factors. Currently Credentials can have *any* combination of factors.
@ -75,5 +75,125 @@ This will simplify the state machines in authsession, as well as allowing better
UI decisions in clients for how we want to interact with possible credentials UI decisions in clients for how we want to interact with possible credentials
on the client system. on the client system.
Credentials In Memory
---------------------
To support this the representation of credentials in memory must change. The current struct (2020-12)
is:
pub struct Credential {
pub(crate) password: Option<Password>,
pub(crate) webauthn: Option<Map<String, WebauthnCredential>>,
pub(crate) totp: Option<TOTP>,
pub(crate) claims: Vec<String>,
pub(crate) uuid: Uuid,
}
There are numerous issues with this design. It does not express clearly the combination
of credentials that are valid in the credential, nor their capabilities and usage. A situation
such as `Password && (TOTP || Webauthn no verification)` OR `Password && Webauthn no verification`
are now ambiguous with this representation. It also does not clearly allow us to see where is the
correct entry/starting point for the authentication session.
An improved design will have Credential become an enum representing the valid authentication methods
pub enum Credential {
Anonymous,
Password(),
GeneratedPassword(),
PasswordMFA(),
PasswordWebauthn(),
Webauthn(),
WebauthnVerified(),
PasswordWebauthnVerified(),
}
This allows a clearer set of logic flows in the credential and it's handling, as well as defined
transforms between credential states and type level guarantees of the consistency of the credential.
Database Credentials
--------------------
Database Credentials are currently stored as:
#[derive(Serialize, Deserialize, Debug)]
pub struct DbCredV1 {
pub password: Option<DbPasswordV1>,
pub webauthn: Option<Vec<DbWebauthnV1>>,
pub totp: Option<DbTotpV1>,
pub claims: Vec<String>,
pub uuid: Uuid,
}
This will be extended with an enum to represent the correct type/policy to deserialise into:
pub struct DbCredV1 {
pub type_: DbCredTypeV1,
}
An in place upgrade will be required to add this type to all existing credentials in the
database.
Protocol
--------
The current design of the step/response is as follows.
pub enum AuthStep {
Init(String),
Creds(Vec<AuthCredential>),
}
pub enum AuthState {
Success(String),
Denied(String),
Continue(Vec<AuthAllowed>),
}
This will be extended to include the selection criteria to choose which method to use and be presented
with. Additionally, only one AuthCredential can be presented by the server at a time, and the server
may respond with many choices for the next step. This allows the server to propose TOTP *OR* Webauthn
but the client must choose which (It can not supply both, creating an AND situation or ambiguity around
the correct way to handle these).
pub enum AuthMech {
Anonymous,
Password,
// This covers PasswordWebauthn as well.
PasswordMFA,
Webauthn,
WebauthnVerified,
PasswordWebauthnVerified,
}
pub enum AuthStep {
Init(String), // server responds with AuthState::Choose|Denied
Begin(AuthMech), // server responds with AuthState::Continue|Success|Denied
Cred(AuthCredential), // server response with AuthState::Continue|Success|Denied
}
pub enum AuthState {
Choose(Vec<AuthMech>),
Continue(Vec<AuthAllowed>),
Success(String),
Denied(String),
}
A key reason to have this "Choose" step is related to an issue in the design and construction of
Webauthn Challenges. For more details see: https://fy.blackhats.net.au/blog/html/2020/11/21/webauthn_userverificationpolicy_curiosities.html
AuthSession
-----------
Once these other changes are made, AuthSession will need to be simplified but it's core state machines
will remain mostly unchanged. This set of changes will likely result in the AuthSession being much
clearer due to the enforcement of credential presentation order instead of the current design that
may allow "all in one" submissions.
Other Benefits
==============
* During an MFA authentication, the Password if incorrect can be re-prompted for a number of times subsequent to the TOTP/Webauthn having been found valid, improving user experience.
* Allows Webauthn Verified credentials to be used with clearer expression of the claims of the device associated to the credential.
* Policy will be simpler to enforce on credentials due to them more clearly stating their design and layouts.

View file

@ -2,6 +2,7 @@ use crate::{ClientError, KanidmClientBuilder, APPLICATION_JSON, KOPID};
use reqwest::header::CONTENT_TYPE; use reqwest::header::CONTENT_TYPE;
use serde::de::DeserializeOwned; use serde::de::DeserializeOwned;
use serde::Serialize; use serde::Serialize;
use std::collections::BTreeSet as Set;
use kanidm_proto::v1::*; use kanidm_proto::v1::*;
@ -189,13 +190,39 @@ impl KanidmAsyncClient {
.map_err(|e| ClientError::JSONDecode(e, opid)) .map_err(|e| ClientError::JSONDecode(e, opid))
} }
pub async fn auth_step_init(&self, ident: &str) -> Result<AuthState, ClientError> { pub async fn auth_step_init(&self, ident: &str) -> Result<Set<AuthMech>, ClientError> {
let auth_init = AuthRequest { let auth_init = AuthRequest {
step: AuthStep::Init(ident.to_string()), step: AuthStep::Init(ident.to_string()),
}; };
let r: Result<AuthResponse, _> = self.perform_post_request("/v1/auth", auth_init).await; let r: Result<AuthResponse, _> = self.perform_post_request("/v1/auth", auth_init).await;
r.map(|v| v.state) r.map(|v| {
debug!("Authentication Session ID -> {:?}", v.sessionid);
v.state
})
.and_then(|state| match state {
AuthState::Choose(mechs) => Ok(mechs),
_ => Err(ClientError::AuthenticationFailed),
})
.map(|mechs| mechs.into_iter().collect())
}
pub async fn auth_step_begin(&self, mech: AuthMech) -> Result<Vec<AuthAllowed>, ClientError> {
let auth_begin = AuthRequest {
step: AuthStep::Begin(mech),
};
let r: Result<AuthResponse, _> = self.perform_post_request("/v1/auth", auth_begin).await;
r.map(|v| {
debug!("Authentication Session ID -> {:?}", v.sessionid);
v.state
})
.and_then(|state| match state {
AuthState::Continue(allowed) => Ok(allowed),
_ => Err(ClientError::AuthenticationFailed),
})
// For converting to a Set
// .map(|allowed| allowed.into_iter().collect())
} }
pub async fn auth_simple_password( pub async fn auth_simple_password(
@ -203,13 +230,23 @@ impl KanidmAsyncClient {
ident: &str, ident: &str,
password: &str, password: &str,
) -> Result<(), ClientError> { ) -> Result<(), ClientError> {
let _state = match self.auth_step_init(ident).await { let mechs = match self.auth_step_init(ident).await {
Ok(s) => s,
Err(e) => return Err(e),
};
if !mechs.contains(&AuthMech::Password) {
debug!("Password mech not presented");
return Err(ClientError::AuthenticationFailed);
}
let _state = match self.auth_step_begin(AuthMech::Password).await {
Ok(s) => s, Ok(s) => s,
Err(e) => return Err(e), Err(e) => return Err(e),
}; };
let auth_req = AuthRequest { let auth_req = AuthRequest {
step: AuthStep::Creds(vec![AuthCredential::Password(password.to_string())]), step: AuthStep::Cred(AuthCredential::Password(password.to_string())),
}; };
let r: Result<AuthResponse, _> = self.perform_post_request("/v1/auth", auth_req).await; let r: Result<AuthResponse, _> = self.perform_post_request("/v1/auth", auth_req).await;
@ -225,15 +262,23 @@ impl KanidmAsyncClient {
} }
pub async fn auth_anonymous(&mut self) -> Result<(), ClientError> { pub async fn auth_anonymous(&mut self) -> Result<(), ClientError> {
// TODO #251: Check state for auth continue contains anonymous. let mechs = match self.auth_step_init("anonymous").await {
// #251 will remove the need for this check. Ok(s) => s,
let _state = match self.auth_step_init("anonymous").await { Err(e) => return Err(e),
};
if !mechs.contains(&AuthMech::Anonymous) {
debug!("Anonymous mech not presented");
return Err(ClientError::AuthenticationFailed);
}
let _state = match self.auth_step_begin(AuthMech::Anonymous).await {
Ok(s) => s, Ok(s) => s,
Err(e) => return Err(e), Err(e) => return Err(e),
}; };
let auth_anon = AuthRequest { let auth_anon = AuthRequest {
step: AuthStep::Creds(vec![AuthCredential::Anonymous]), step: AuthStep::Cred(AuthCredential::Anonymous),
}; };
let r: Result<AuthResponse, _> = self.perform_post_request("/v1/auth", auth_anon).await; let r: Result<AuthResponse, _> = self.perform_post_request("/v1/auth", auth_anon).await;

View file

@ -17,6 +17,7 @@ use serde::Serialize;
use serde_derive::Deserialize; use serde_derive::Deserialize;
use serde_json::error::Error as SerdeJsonError; use serde_json::error::Error as SerdeJsonError;
use std::collections::BTreeMap; use std::collections::BTreeMap;
use std::collections::BTreeSet as Set;
use std::fs::{metadata, File, Metadata}; use std::fs::{metadata, File, Metadata};
use std::io::Read; use std::io::Read;
use std::os::unix::fs::MetadataExt; use std::os::unix::fs::MetadataExt;
@ -32,11 +33,11 @@ use webauthn_rs::proto::{
// use users::{get_current_uid, get_effective_uid}; // use users::{get_current_uid, get_effective_uid};
use kanidm_proto::v1::{ use kanidm_proto::v1::{
AccountUnixExtend, AuthAllowed, AuthCredential, AuthRequest, AuthResponse, AuthState, AuthStep, AccountUnixExtend, AuthAllowed, AuthCredential, AuthMech, AuthRequest, AuthResponse, AuthState,
CreateRequest, DeleteRequest, Entry, Filter, GroupUnixExtend, ModifyList, ModifyRequest, AuthStep, CreateRequest, DeleteRequest, Entry, Filter, GroupUnixExtend, ModifyList,
OperationError, OperationResponse, RadiusAuthToken, SearchRequest, SearchResponse, ModifyRequest, OperationError, OperationResponse, RadiusAuthToken, SearchRequest,
SetCredentialRequest, SetCredentialResponse, SingleStringRequest, TOTPSecret, UnixGroupToken, SearchResponse, SetCredentialRequest, SetCredentialResponse, SingleStringRequest, TOTPSecret,
UnixUserToken, UserAuthToken, WhoamiResponse, UnixGroupToken, UnixUserToken, UserAuthToken, WhoamiResponse,
}; };
pub mod asynchronous; pub mod asynchronous;
@ -555,14 +556,23 @@ impl KanidmClient {
// auth // auth
pub fn auth_anonymous(&mut self) -> Result<(), ClientError> { pub fn auth_anonymous(&mut self) -> Result<(), ClientError> {
// TODO #251: Check state for auth continue contains anonymous. let mechs = match self.auth_step_init("anonymous") {
let _state = match self.auth_step_init("anonymous") { Ok(s) => s,
Err(e) => return Err(e),
};
if !mechs.contains(&AuthMech::Anonymous) {
debug!("Anonymous mech not presented");
return Err(ClientError::AuthenticationFailed);
}
let _state = match self.auth_step_begin(AuthMech::Anonymous) {
Ok(s) => s, Ok(s) => s,
Err(e) => return Err(e), Err(e) => return Err(e),
}; };
let auth_anon = AuthRequest { let auth_anon = AuthRequest {
step: AuthStep::Creds(vec![AuthCredential::Anonymous]), step: AuthStep::Cred(AuthCredential::Anonymous),
}; };
let r: Result<AuthResponse, _> = self.perform_post_request("/v1/auth", auth_anon); let r: Result<AuthResponse, _> = self.perform_post_request("/v1/auth", auth_anon);
@ -579,13 +589,23 @@ impl KanidmClient {
} }
pub fn auth_simple_password(&mut self, ident: &str, password: &str) -> Result<(), ClientError> { pub fn auth_simple_password(&mut self, ident: &str, password: &str) -> Result<(), ClientError> {
let _state = match self.auth_step_init(ident) { let mechs = match self.auth_step_init(ident) {
Ok(s) => s,
Err(e) => return Err(e),
};
if !mechs.contains(&AuthMech::Password) {
debug!("Password mech not presented");
return Err(ClientError::AuthenticationFailed);
}
let _state = match self.auth_step_begin(AuthMech::Password) {
Ok(s) => s, Ok(s) => s,
Err(e) => return Err(e), Err(e) => return Err(e),
}; };
let auth_req = AuthRequest { let auth_req = AuthRequest {
step: AuthStep::Creds(vec![AuthCredential::Password(password.to_string())]), step: AuthStep::Cred(AuthCredential::Password(password.to_string())),
}; };
let r: Result<AuthResponse, _> = self.perform_post_request("/v1/auth", auth_req); let r: Result<AuthResponse, _> = self.perform_post_request("/v1/auth", auth_req);
@ -607,19 +627,52 @@ impl KanidmClient {
password: &str, password: &str,
totp: u32, totp: u32,
) -> Result<(), ClientError> { ) -> Result<(), ClientError> {
let _state = match self.auth_step_init(ident) { let mechs = match self.auth_step_init(ident) {
Ok(s) => s, Ok(s) => s,
Err(e) => return Err(e), Err(e) => return Err(e),
}; };
let auth_req = AuthRequest { if !mechs.contains(&AuthMech::PasswordMFA) {
step: AuthStep::Creds(vec![ debug!("PasswordMFA mech not presented");
AuthCredential::TOTP(totp), return Err(ClientError::AuthenticationFailed);
AuthCredential::Password(password.to_string()), }
]),
};
let r: Result<AuthResponse, _> = self.perform_post_request("/v1/auth", auth_req);
let state = match self.auth_step_begin(AuthMech::PasswordMFA) {
Ok(s) => s,
Err(e) => return Err(e),
};
if !state.contains(&AuthAllowed::TOTP) {
debug!("TOTP step not offered.");
return Err(ClientError::AuthenticationFailed);
}
let auth_req = AuthRequest {
step: AuthStep::Cred(AuthCredential::TOTP(totp)),
};
let r: Result<AuthResponse, _> = self.perform_post_request("/v1/auth", auth_req);
let r = r?;
// Should need to continue.
match r.state {
AuthState::Continue(allowed) => {
if !allowed.contains(&AuthAllowed::Password) {
debug!("Password step not offered.");
return Err(ClientError::AuthenticationFailed);
}
}
_ => {
debug!("Invalid AuthState presented.");
return Err(ClientError::AuthenticationFailed);
}
};
let auth_req = AuthRequest {
step: AuthStep::Cred(AuthCredential::Password(password.to_string())),
};
let r: Result<AuthResponse, _> = self.perform_post_request("/v1/auth", auth_req);
let r = r?; let r = r?;
match r.state { match r.state {
@ -636,27 +689,31 @@ impl KanidmClient {
&mut self, &mut self,
ident: &str, ident: &str,
) -> Result<RequestChallengeResponse, ClientError> { ) -> Result<RequestChallengeResponse, ClientError> {
let state = match self.auth_step_init(ident) { let mechs = match self.auth_step_init(ident) {
Ok(s) => s, Ok(s) => s,
Err(e) => return Err(e), Err(e) => return Err(e),
}; };
match state { if !mechs.contains(&AuthMech::Webauthn) {
AuthState::Continue(mut proc) => { debug!("Webauthn mech not presented");
// get the webauthn chal out of the state. return Err(ClientError::AuthenticationFailed);
let chal = proc.pop(); }
match chal {
Some(AuthAllowed::Webauthn(r)) => Ok(r), let mut state = match self.auth_step_begin(AuthMech::Webauthn) {
_ => Err(ClientError::AuthenticationFailed), Ok(s) => s,
} Err(e) => return Err(e),
} };
// State is now a set of auth continues.
match state.pop() {
Some(AuthAllowed::Webauthn(r)) => Ok(r),
_ => Err(ClientError::AuthenticationFailed), _ => Err(ClientError::AuthenticationFailed),
} }
} }
pub fn auth_webauthn_complete(&mut self, pkc: PublicKeyCredential) -> Result<(), ClientError> { pub fn auth_webauthn_complete(&mut self, pkc: PublicKeyCredential) -> Result<(), ClientError> {
let auth_req = AuthRequest { let auth_req = AuthRequest {
step: AuthStep::Creds(vec![AuthCredential::Webauthn(pkc)]), step: AuthStep::Cred(AuthCredential::Webauthn(pkc)),
}; };
let r: Result<AuthResponse, _> = self.perform_post_request("/v1/auth", auth_req); let r: Result<AuthResponse, _> = self.perform_post_request("/v1/auth", auth_req);
@ -709,13 +766,39 @@ impl KanidmClient {
r.map(|_| true) r.map(|_| true)
} }
pub fn auth_step_init(&self, ident: &str) -> Result<AuthState, ClientError> { pub fn auth_step_init(&self, ident: &str) -> Result<Set<AuthMech>, ClientError> {
let auth_init = AuthRequest { let auth_init = AuthRequest {
step: AuthStep::Init(ident.to_string()), step: AuthStep::Init(ident.to_string()),
}; };
let r: Result<AuthResponse, _> = self.perform_post_request("/v1/auth", auth_init); let r: Result<AuthResponse, _> = self.perform_post_request("/v1/auth", auth_init);
r.map(|v| v.state) r.map(|v| {
debug!("Authentication Session ID -> {:?}", v.sessionid);
v.state
})
.and_then(|state| match state {
AuthState::Choose(mechs) => Ok(mechs),
_ => Err(ClientError::AuthenticationFailed),
})
.map(|mechs| mechs.into_iter().collect())
}
pub fn auth_step_begin(&self, mech: AuthMech) -> Result<Vec<AuthAllowed>, ClientError> {
let auth_begin = AuthRequest {
step: AuthStep::Begin(mech),
};
let r: Result<AuthResponse, _> = self.perform_post_request("/v1/auth", auth_begin);
r.map(|v| {
debug!("Authentication Session ID -> {:?}", v.sessionid);
v.state
})
.and_then(|state| match state {
AuthState::Continue(allowed) => Ok(allowed),
_ => Err(ClientError::AuthenticationFailed),
})
// For converting to a Set
// .map(|allowed| allowed.into_iter().collect())
} }
// ===== GROUPS // ===== GROUPS

View file

@ -2,6 +2,7 @@ use std::collections::BTreeMap;
use std::fmt; use std::fmt;
use uuid::Uuid; use uuid::Uuid;
// use zxcvbn::feedback; // use zxcvbn::feedback;
use std::cmp::Ordering;
use webauthn_rs::proto::{ use webauthn_rs::proto::{
CreationChallengeResponse, PublicKeyCredential, RegisterPublicKeyCredential, CreationChallengeResponse, PublicKeyCredential, RegisterPublicKeyCredential,
RequestChallengeResponse, RequestChallengeResponse,
@ -426,6 +427,7 @@ impl ModifyRequest {
// On loginSuccess, we send a cookie, and that allows the token to be // On loginSuccess, we send a cookie, and that allows the token to be
// generated. The cookie can be shared between servers. // generated. The cookie can be shared between servers.
#[derive(Serialize, Deserialize)] #[derive(Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum AuthCredential { pub enum AuthCredential {
Anonymous, Anonymous,
Password(String), Password(String),
@ -444,17 +446,32 @@ impl fmt::Debug for AuthCredential {
} }
} }
#[derive(Debug, Serialize, Deserialize, Eq, PartialOrd, Ord)]
#[serde(rename_all = "lowercase")]
pub enum AuthMech {
Anonymous,
Password,
PasswordMFA,
Webauthn,
// WebauthnVerified,
// PasswordWebauthnVerified
}
impl PartialEq for AuthMech {
fn eq(&self, other: &Self) -> bool {
std::mem::discriminant(self) == std::mem::discriminant(other)
}
}
#[derive(Debug, Serialize, Deserialize)] #[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")] #[serde(rename_all = "lowercase")]
pub enum AuthStep { pub enum AuthStep {
// name // name
Init(String), Init(String),
/* // We want to talk to you like this.
Step( Begin(AuthMech),
Type(params ....) // Step
), Cred(AuthCredential),
*/
Creds(Vec<AuthCredential>),
// Should we have a "finalise" type to attempt to finish based on // Should we have a "finalise" type to attempt to finish based on
// what we have given? // what we have given?
} }
@ -482,17 +499,47 @@ impl PartialEq for AuthAllowed {
} }
} }
impl Eq for AuthAllowed {}
impl Ord for AuthAllowed {
fn cmp(&self, other: &Self) -> Ordering {
if self.eq(other) {
Ordering::Equal
} else {
// Relies on the fact that match is executed in order!
match (self, other) {
(AuthAllowed::Anonymous, _) => Ordering::Less,
(_, AuthAllowed::Anonymous) => Ordering::Greater,
(AuthAllowed::Password, _) => Ordering::Less,
(_, AuthAllowed::Password) => Ordering::Greater,
(AuthAllowed::TOTP, _) => Ordering::Less,
(_, AuthAllowed::TOTP) => Ordering::Greater,
(AuthAllowed::Webauthn(_), _) => Ordering::Less,
// Unreachable
// (_, AuthAllowed::Webauthn(_)) => Ordering::Greater,
}
}
}
}
impl PartialOrd for AuthAllowed {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
#[derive(Debug, Serialize, Deserialize)] #[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")] #[serde(rename_all = "lowercase")]
pub enum AuthState { pub enum AuthState {
// Everything is good, your bearer header has been issued and is within // You need to select how you want to talk to me.
// the result. Choose(Vec<AuthMech>),
// Success(UserAuthToken), // Continue to auth, allowed mechanisms/challenges listed.
Success(String), Continue(Vec<AuthAllowed>),
// Something was bad, your session is terminated and no cookie. // Something was bad, your session is terminated and no cookie.
Denied(String), Denied(String),
// Continue to auth, allowed mechanisms listed. // Everything is good, your bearer header has been issued and is within
Continue(Vec<AuthAllowed>), // the result.
Success(String),
} }
#[derive(Debug, Serialize, Deserialize)] #[derive(Debug, Serialize, Deserialize)]

View file

@ -0,0 +1,27 @@
Testing Process
===============
cd kanidmd
cargo run -- recover_account -c ./server.toml -n admin
cargo run -- server -c ./server.toml
cd kanidm_tools
cargo run -- login -D admin
cargo run -- account list -D admin
cargo run -- account create -D admin radius_service_account radius_service_account
cargo run -- group add_members -D admin idm_radius_servers radius_service_account
cargo run -- account credential set_password radius_service_account -D admin
cargo run -- account radius generate_secret admin -D admin
cd kanidm_rlm_python/
KANIDM_RLM_CONFIG=./test_data/config.ini python3 kanidmradius.py test
KANIDM_RLM_CONFIG=./test_data/config.ini python3 kanidmradius.py admin

View file

@ -16,9 +16,10 @@ else:
# Setup the config too # Setup the config too
print(os.getcwd()) print(os.getcwd())
CONFIG_PATH = os.environ.get('KANIDM_RLM_CONFIG', '/data/config.ini')
CONFIG = configparser.ConfigParser() CONFIG = configparser.ConfigParser()
CONFIG.read('/data/config.ini') CONFIG.read(CONFIG_PATH)
# CONFIG.read('/tmp/config.ini')
GROUPS = [ GROUPS = [
{ {
@ -50,7 +51,19 @@ def _authenticate(s, acct, pw):
print(r.json()) print(r.json())
raise Exception("AuthInitFailed") raise Exception("AuthInitFailed")
cred_auth = {"step": { "creds": [{"Password": pw}]}} # {'sessionid': '00000000-5fe5-46e1-06b6-b830dd035a10', 'state': {'choose': ['password']}}
if 'password' not in r.json().get('state', {'choose': None}).get('choose', None):
print("invalid auth mech presented %s" % r.json())
raise Exception("AuthMechUnknown")
begin_auth = {"step": {"begin": "password"}}
r = s.post(AUTH_URL, json=begin_auth, verify=CA, timeout=TIMEOUT)
if r.status_code != 200:
print(r.json())
raise Exception("AuthBeginFailed")
cred_auth = {"step": { "cred": {"password": pw}}}
r = s.post(AUTH_URL, json=cred_auth, verify=CA, timeout=TIMEOUT) r = s.post(AUTH_URL, json=cred_auth, verify=CA, timeout=TIMEOUT)
response = r.json() response = r.json()
if r.status_code != 200: if r.status_code != 200:

View file

@ -1,5 +1,5 @@
[kanidm_client] [kanidm_client]
url = https://172.17.0.2:8080 url = https://localhost:8443
strict = false strict = false
ca = /data/ca.crt ca = /data/ca.crt
user = radius_service_account user = radius_service_account

View file

@ -39,10 +39,30 @@ pub struct DbWebauthnV1 {
pub v: bool, pub v: bool,
} }
#[derive(Serialize, Deserialize, Debug)]
pub enum DbCredTypeV1 {
Pw,
GPw,
PwMfa,
// PwWn,
Wn,
// WnVer,
// PwWnVer,
}
fn dbcred_type_default_pw() -> DbCredTypeV1 {
DbCredTypeV1::Pw
}
#[derive(Serialize, Deserialize, Debug)] #[derive(Serialize, Deserialize, Debug)]
pub struct DbCredV1 { pub struct DbCredV1 {
#[serde(default = "dbcred_type_default_pw")]
pub type_: DbCredTypeV1,
#[serde(skip_serializing_if = "Option::is_none")]
pub password: Option<DbPasswordV1>, pub password: Option<DbPasswordV1>,
#[serde(skip_serializing_if = "Option::is_none")]
pub webauthn: Option<Vec<DbWebauthnV1>>, pub webauthn: Option<Vec<DbWebauthnV1>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub totp: Option<DbTotpV1>, pub totp: Option<DbTotpV1>,
pub claims: Vec<String>, pub claims: Vec<String>,
pub uuid: Uuid, pub uuid: Uuid,

View file

@ -1009,6 +1009,30 @@ pub async fn auth(mut req: tide::Request<AppState>) -> tide::Result {
} }
// Do some response/state management. // Do some response/state management.
match state { match state {
AuthState::Choose(allowed) => {
debug!("🧩 -> AuthState::Choose");
let msession = req.session_mut();
// Force a new cookie session.
// msession.regenerate();
// Ensure the auth-session-id is set
msession.remove("auth-session-id");
msession
.insert("auth-session-id", sessionid)
.map(|_| ProtoAuthState::Choose(allowed))
.map_err(|_| OperationError::InvalidSessionState)
}
AuthState::Continue(allowed) => {
debug!("🧩 -> AuthState::Continue");
let msession = req.session_mut();
// Force a new cookie session.
// msession.regenerate();
// Ensure the auth-session-id is set
msession.remove("auth-session-id");
msession
.insert("auth-session-id", sessionid)
.map(|_| ProtoAuthState::Continue(allowed))
.map_err(|_| OperationError::InvalidSessionState)
}
AuthState::Success(uat) => { AuthState::Success(uat) => {
debug!("🧩 -> AuthState::Success"); debug!("🧩 -> AuthState::Success");
// Remove the auth-session-id // Remove the auth-session-id
@ -1030,18 +1054,6 @@ pub async fn auth(mut req: tide::Request<AppState>) -> tide::Result {
msession.remove("auth-session-id"); msession.remove("auth-session-id");
Err(OperationError::AccessDenied) Err(OperationError::AccessDenied)
} }
AuthState::Continue(allowed) => {
debug!("🧩 -> AuthState::Continue");
let msession = req.session_mut();
// Force a new cookie session.
// msession.regenerate();
// Ensure the auth-session-id is set
msession.remove("auth-session-id");
msession
.insert("auth-session-id", sessionid)
.map(|_| ProtoAuthState::Continue(allowed))
.map_err(|_| OperationError::InvalidSessionState)
}
} }
.map(|state| AuthResponse { state, sessionid }) .map(|state| AuthResponse { state, sessionid })
} }

View file

@ -1,4 +1,4 @@
use crate::be::dbvalue::{DbCredV1, DbPasswordV1, DbWebauthnV1}; use crate::be::dbvalue::{DbCredTypeV1, DbCredV1, DbPasswordV1, DbWebauthnV1};
use hashbrown::HashMap as Map; use hashbrown::HashMap as Map;
use kanidm_proto::v1::OperationError; use kanidm_proto::v1::OperationError;
use openssl::hash::MessageDigest; use openssl::hash::MessageDigest;
@ -225,15 +225,10 @@ impl Password {
/// B requires both the password and otp to be valid. /// B requires both the password and otp to be valid.
/// ///
/// In this way, each Credential provides it's own password requirements and policy, and requires /// In this way, each Credential provides it's own password requirements and policy, and requires
/// some metadata to support this such as it's source and strength etc. Some of these details are /// some metadata to support this such as it's source and strength etc.
/// to be resolved ...
pub struct Credential { pub struct Credential {
// Source (machine, user, ....). Strength?
// policy: Policy, // policy: Policy,
pub(crate) password: Option<Password>, pub(crate) type_: CredentialType,
pub(crate) webauthn: Option<Map<String, WebauthnCredential>>,
// totp: Option<NonEmptyVec<TOTP>>
pub(crate) totp: Option<TOTP>,
pub(crate) claims: Vec<String>, pub(crate) claims: Vec<String>,
// Uuid of Credential, used by auth session to lock this specific credential // Uuid of Credential, used by auth session to lock this specific credential
// if required. // if required.
@ -242,12 +237,25 @@ pub struct Credential {
// locked: bool // locked: bool
} }
#[derive(Clone, Debug)]
pub enum CredentialType {
// Anonymous,
Password(Password),
GeneratedPassword(Password),
Webauthn(Map<String, WebauthnCredential>),
PasswordMFA(Password, Option<TOTP>, Map<String, WebauthnCredential>),
// PasswordWebauthn(Password, Map<String, WebauthnCredential>),
// WebauthnVerified(Map<String, WebauthnCredential>),
// PasswordWebauthnVerified(Password, Map<String, WebauthnCredential>),
}
impl TryFrom<DbCredV1> for Credential { impl TryFrom<DbCredV1> for Credential {
type Error = (); type Error = ();
fn try_from(value: DbCredV1) -> Result<Self, Self::Error> { fn try_from(value: DbCredV1) -> Result<Self, Self::Error> {
// Work out what the policy is? // Work out what the policy is?
let DbCredV1 { let DbCredV1 {
type_,
password, password,
webauthn, webauthn,
totp, totp,
@ -284,10 +292,21 @@ impl TryFrom<DbCredV1> for Credential {
None => None, None => None,
}; };
let type_ = match type_ {
DbCredTypeV1::Pw => v_password.map(CredentialType::Password),
DbCredTypeV1::GPw => v_password.map(CredentialType::GeneratedPassword),
// In the future this could use .zip
DbCredTypeV1::PwMfa => match (v_password, v_webauthn) {
(Some(pw), Some(wn)) => Some(CredentialType::PasswordMFA(pw, v_totp, wn)),
_ => None,
},
DbCredTypeV1::Wn => v_webauthn.map(CredentialType::Webauthn),
}
.filter(|v| v.is_valid())
.ok_or(())?;
Ok(Credential { Ok(Credential {
password: v_password, type_,
webauthn: v_webauthn,
totp: v_totp,
claims, claims,
uuid, uuid,
}) })
@ -306,9 +325,7 @@ impl Credential {
let mut webauthn_map = Map::new(); let mut webauthn_map = Map::new();
webauthn_map.insert(label, cred); webauthn_map.insert(label, cred);
Credential { Credential {
password: None, type_: CredentialType::Webauthn(webauthn_map),
webauthn: Some(webauthn_map),
totp: None,
claims: Vec::new(), claims: Vec::new(),
uuid: Uuid::new_v4(), uuid: Uuid::new_v4(),
} }
@ -319,13 +336,7 @@ impl Credential {
policy: &CryptoPolicy, policy: &CryptoPolicy,
cleartext: &str, cleartext: &str,
) -> Result<Self, OperationError> { ) -> Result<Self, OperationError> {
Password::new(policy, cleartext).map(|pw| Credential { Password::new(policy, cleartext).map(|pw| self.update_password(pw))
password: Some(pw),
webauthn: self.webauthn.clone(),
totp: self.totp.clone(),
claims: self.claims.clone(),
uuid: self.uuid,
})
} }
pub fn append_webauthn( pub fn append_webauthn(
@ -333,30 +344,37 @@ impl Credential {
label: String, label: String,
cred: WebauthnCredential, cred: WebauthnCredential,
) -> Result<Self, OperationError> { ) -> Result<Self, OperationError> {
let webauthn_map = match &self.webauthn { let type_ = match &self.type_ {
Some(map) => { CredentialType::Password(pw) | CredentialType::GeneratedPassword(pw) => {
let mut nmap = map.clone(); let mut wan = Map::new();
match nmap.insert(label.clone(), cred) { wan.insert(label, cred);
Some(_) => { CredentialType::PasswordMFA(pw.clone(), None, wan)
return Err(OperationError::InvalidAttribute(format!(
"Webauthn label '{:?}' already exists",
label
)));
}
None => nmap,
}
} }
None => { CredentialType::PasswordMFA(pw, totp, map) => {
let mut map = Map::new(); let mut nmap = map.clone();
map.insert(label, cred); if let Some(_) = nmap.insert(label.clone(), cred) {
map return Err(OperationError::InvalidAttribute(format!(
"Webauthn label '{:?}' already exists",
label
)));
}
CredentialType::PasswordMFA(pw.clone(), totp.clone(), nmap)
}
CredentialType::Webauthn(map) => {
let mut nmap = map.clone();
if let Some(_) = nmap.insert(label.clone(), cred) {
return Err(OperationError::InvalidAttribute(format!(
"Webauthn label '{:?}' already exists",
label
)));
}
CredentialType::Webauthn(nmap)
} }
}; };
// Check stuff // Check stuff
Ok(Credential { Ok(Credential {
password: self.password.clone(), type_,
webauthn: Some(webauthn_map),
totp: self.totp.clone(),
claims: self.claims.clone(), claims: self.claims.clone(),
uuid: self.uuid, uuid: self.uuid,
}) })
@ -367,69 +385,155 @@ impl Credential {
cid: &CredentialID, cid: &CredentialID,
counter: Counter, counter: Counter,
) -> Result<Option<Self>, OperationError> { ) -> Result<Option<Self>, OperationError> {
let opt_label = self.webauthn.as_ref().and_then(|m| { let nmap = match &self.type_ {
m.iter().fold(None, |acc, (k, v)| { CredentialType::Password(_pw) | CredentialType::GeneratedPassword(_pw) => {
if acc.is_none() && &v.cred_id == cid && v.counter < counter { // No action required
Some(k) return Ok(None);
} else { }
acc CredentialType::PasswordMFA(_, _, map) | CredentialType::Webauthn(map) => map
} .iter()
}) .fold(None, |acc, (k, v)| {
}); if acc.is_none() && &v.cred_id == cid && v.counter < counter {
Some(k)
} else {
acc
}
})
.map(|label| {
let mut webauthn_map = map.clone();
if let Some(label) = opt_label { webauthn_map
let mut webauthn_map = self.webauthn.clone(); .get_mut(label)
.map(|cred| cred.counter = counter);
webauthn_map
}),
};
webauthn_map let map = match nmap {
.as_mut() Some(map) => map,
.and_then(|m| m.get_mut(label)) None => {
.map(|cred| cred.counter = counter); // No action needed.
return Ok(None);
}
};
Ok(Some(Credential { let type_ = match &self.type_ {
password: self.password.clone(), CredentialType::Password(_pw) | CredentialType::GeneratedPassword(_pw) => {
webauthn: webauthn_map, // Should not be possible!
totp: self.totp.clone(), unreachable!();
claims: self.claims.clone(), }
uuid: self.uuid, CredentialType::Webauthn(_) => CredentialType::Webauthn(map),
})) CredentialType::PasswordMFA(pw, totp, _) => {
} else { CredentialType::PasswordMFA(pw.clone(), totp.clone(), map)
Ok(None) }
};
Ok(Some(Credential {
type_,
claims: self.claims.clone(),
uuid: self.uuid,
}))
}
pub fn webauthn_ref(&self) -> Result<&Map<String, WebauthnCredential>, OperationError> {
match &self.type_ {
CredentialType::Password(_) | CredentialType::GeneratedPassword(_) => Err(
OperationError::InvalidAccountState("non-webauthn cred type?".to_string()),
),
CredentialType::PasswordMFA(_, _, map) | CredentialType::Webauthn(map) => Ok(map),
}
}
pub fn password_ref(&self) -> Result<&Password, OperationError> {
match &self.type_ {
CredentialType::Password(pw)
| CredentialType::GeneratedPassword(pw)
| CredentialType::PasswordMFA(pw, _, _) => Ok(pw),
CredentialType::Webauthn(_) => Err(OperationError::InvalidAccountState(
"non-password cred type?".to_string(),
)),
} }
} }
#[cfg(test)] #[cfg(test)]
pub fn verify_password(&self, cleartext: &str) -> bool { pub fn verify_password(&self, cleartext: &str) -> Result<bool, OperationError> {
match &self.password { self.password_ref().and_then(|pw| pw.verify(cleartext))
Some(pw) => pw.verify(cleartext).unwrap_or(false),
None => false,
}
} }
pub fn to_db_valuev1(&self) -> DbCredV1 { pub fn to_db_valuev1(&self) -> DbCredV1 {
DbCredV1 { let claims = self.claims.clone();
password: self.password.as_ref().map(|pw| pw.to_dbpasswordv1()), let uuid = self.uuid;
webauthn: self.webauthn.as_ref().map(|map| { match &self.type_ {
map.iter() CredentialType::Password(pw) => DbCredV1 {
.map(|(k, v)| DbWebauthnV1 { type_: DbCredTypeV1::Pw,
l: k.clone(), password: Some(pw.to_dbpasswordv1()),
i: v.cred_id.clone(), webauthn: None,
c: v.cred.clone(), totp: None,
t: v.counter, claims,
v: v.verified, uuid,
}) },
.collect() CredentialType::GeneratedPassword(pw) => DbCredV1 {
}), type_: DbCredTypeV1::GPw,
totp: self.totp.as_ref().map(|t| t.to_dbtotpv1()), password: Some(pw.to_dbpasswordv1()),
claims: self.claims.clone(), webauthn: None,
uuid: self.uuid, totp: None,
claims,
uuid,
},
CredentialType::PasswordMFA(pw, totp, map) => DbCredV1 {
type_: DbCredTypeV1::PwMfa,
password: Some(pw.to_dbpasswordv1()),
webauthn: Some(
map.iter()
.map(|(k, v)| DbWebauthnV1 {
l: k.clone(),
i: v.cred_id.clone(),
c: v.cred.clone(),
t: v.counter,
v: v.verified,
})
.collect(),
),
totp: totp.as_ref().map(|t| t.to_dbtotpv1()),
claims,
uuid,
},
CredentialType::Webauthn(map) => DbCredV1 {
type_: DbCredTypeV1::Wn,
password: None,
webauthn: Some(
map.iter()
.map(|(k, v)| DbWebauthnV1 {
l: k.clone(),
i: v.cred_id.clone(),
c: v.cred.clone(),
t: v.counter,
v: v.verified,
})
.collect(),
),
totp: None,
claims,
uuid,
},
} }
} }
pub(crate) fn update_password(&self, pw: Password) -> Self { pub(crate) fn update_password(&self, pw: Password) -> Self {
let type_ = match &self.type_ {
CredentialType::Password(_) => CredentialType::Password(pw),
CredentialType::GeneratedPassword(_) => CredentialType::GeneratedPassword(pw),
CredentialType::PasswordMFA(_, totp, wan) => {
CredentialType::PasswordMFA(pw, totp.clone(), wan.clone())
}
CredentialType::Webauthn(wan) => {
// Or should this become PasswordWebauthn?
debug_assert!(false);
CredentialType::Webauthn(wan.clone())
}
};
Credential { Credential {
password: Some(pw), type_,
webauthn: self.webauthn.clone(),
totp: self.totp.clone(),
claims: self.claims.clone(), claims: self.claims.clone(),
uuid: self.uuid, uuid: self.uuid,
} }
@ -437,10 +541,20 @@ impl Credential {
// We don't make totp accessible from outside the crate for now. // We don't make totp accessible from outside the crate for now.
pub(crate) fn update_totp(&self, totp: TOTP) -> Self { pub(crate) fn update_totp(&self, totp: TOTP) -> Self {
let type_ = match &self.type_ {
CredentialType::Password(pw) | CredentialType::GeneratedPassword(pw) => {
CredentialType::PasswordMFA(pw.clone(), Some(totp), Map::new())
}
CredentialType::PasswordMFA(pw, _, wan) => {
CredentialType::PasswordMFA(pw.clone(), Some(totp), wan.clone())
}
CredentialType::Webauthn(wan) => {
debug_assert!(false);
CredentialType::Webauthn(wan.clone())
}
};
Credential { Credential {
password: self.password.clone(), type_,
webauthn: self.webauthn.clone(),
totp: Some(totp),
claims: self.claims.clone(), claims: self.claims.clone(),
uuid: self.uuid, uuid: self.uuid,
} }
@ -448,24 +562,27 @@ impl Credential {
pub(crate) fn new_from_password(pw: Password) -> Self { pub(crate) fn new_from_password(pw: Password) -> Self {
Credential { Credential {
password: Some(pw), type_: CredentialType::Password(pw),
webauthn: None,
totp: None,
claims: Vec::new(), claims: Vec::new(),
uuid: Uuid::new_v4(), uuid: Uuid::new_v4(),
} }
} }
pub(crate) fn softlock_policy(&self) -> Option<CredSoftLockPolicy> { pub(crate) fn softlock_policy(&self) -> Option<CredSoftLockPolicy> {
match (&self.webauthn, &self.totp, &self.password) { match &self.type_ {
// Has any kind of Webauthn .... CredentialType::Password(_pw) | CredentialType::GeneratedPassword(_pw) => {
(Some(_webauthn), _, _) => Some(CredSoftLockPolicy::Webauthn), Some(CredSoftLockPolicy::Password)
// Has any kind of totp. }
(None, Some(totp), _) => Some(CredSoftLockPolicy::TOTP(totp.step)), CredentialType::PasswordMFA(_pw, totp, wan) => {
// No totp, pw if let Some(r_totp) = totp {
(None, None, Some(_)) => Some(CredSoftLockPolicy::Password), Some(CredSoftLockPolicy::TOTP(r_totp.step))
// Indeterminate } else if wan.len() > 0 {
_ => None, Some(CredSoftLockPolicy::Webauthn)
} else {
None
}
}
CredentialType::Webauthn(_wan) => Some(CredSoftLockPolicy::Webauthn),
} }
} }
@ -490,6 +607,18 @@ impl Credential {
*/ */
} }
impl CredentialType {
fn is_valid(&self) -> bool {
match self {
CredentialType::Password(_) | CredentialType::GeneratedPassword(_) => true,
CredentialType::PasswordMFA(_, m_totp, webauthn) => {
m_totp.is_some() || !webauthn.is_empty()
}
CredentialType::Webauthn(webauthn) => !webauthn.is_empty(),
}
}
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use crate::credential::policy::CryptoPolicy; use crate::credential::policy::CryptoPolicy;
@ -500,11 +629,11 @@ mod tests {
fn test_credential_simple() { fn test_credential_simple() {
let p = CryptoPolicy::minimum(); let p = CryptoPolicy::minimum();
let c = Credential::new_password_only(&p, "password").unwrap(); let c = Credential::new_password_only(&p, "password").unwrap();
assert!(c.verify_password("password")); assert!(c.verify_password("password").unwrap());
assert!(!c.verify_password("password1")); assert!(!c.verify_password("password1").unwrap());
assert!(!c.verify_password("Password1")); assert!(!c.verify_password("Password1").unwrap());
assert!(!c.verify_password("It Works!")); assert!(!c.verify_password("It Works!").unwrap());
assert!(!c.verify_password("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")); assert!(!c.verify_password("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa").unwrap());
} }
#[test] #[test]

View file

@ -6,7 +6,9 @@ use crate::schema::SchemaTransaction;
use crate::value::PartialValue; use crate::value::PartialValue;
use kanidm_proto::v1::Entry as ProtoEntry; use kanidm_proto::v1::Entry as ProtoEntry;
use kanidm_proto::v1::ModifyList as ProtoModifyList; use kanidm_proto::v1::ModifyList as ProtoModifyList;
use kanidm_proto::v1::{AuthCredential, AuthStep, SearchResponse, UserAuthToken, WhoamiResponse}; use kanidm_proto::v1::{
AuthCredential, AuthMech, AuthStep, SearchResponse, UserAuthToken, WhoamiResponse,
};
// use error::OperationError; // use error::OperationError;
use crate::modify::{ModifyInvalid, ModifyList, ModifyValid}; use crate::modify::{ModifyInvalid, ModifyList, ModifyValid};
use crate::server::{ use crate::server::{
@ -925,15 +927,22 @@ pub struct AuthEventStepInit {
} }
#[derive(Debug)] #[derive(Debug)]
pub struct AuthEventStepCreds { pub struct AuthEventStepCred {
pub sessionid: Uuid, pub sessionid: Uuid,
pub creds: Vec<AuthCredential>, pub cred: AuthCredential,
}
#[derive(Debug)]
pub struct AuthEventStepMech {
pub sessionid: Uuid,
pub mech: AuthMech,
} }
#[derive(Debug)] #[derive(Debug)]
pub enum AuthEventStep { pub enum AuthEventStep {
Init(AuthEventStepInit), Init(AuthEventStepInit),
Creds(AuthEventStepCreds), Begin(AuthEventStepMech),
Cred(AuthEventStepCred),
} }
impl AuthEventStep { impl AuthEventStep {
@ -948,10 +957,19 @@ impl AuthEventStep {
Ok(AuthEventStep::Init(AuthEventStepInit { name, appid: None })) Ok(AuthEventStep::Init(AuthEventStepInit { name, appid: None }))
} }
} }
AuthStep::Creds(creds) => match sid { AuthStep::Begin(mech) => match sid {
Some(ssid) => Ok(AuthEventStep::Creds(AuthEventStepCreds { Some(ssid) => Ok(AuthEventStep::Begin(AuthEventStepMech {
sessionid: ssid, sessionid: ssid,
creds, mech,
})),
None => Err(OperationError::InvalidAuthState(
"session id not present in cred".to_string(),
)),
},
AuthStep::Cred(cred) => match sid {
Some(ssid) => Ok(AuthEventStep::Cred(AuthEventStepCred {
sessionid: ssid,
cred,
})), })),
None => Err(OperationError::InvalidAuthState( None => Err(OperationError::InvalidAuthState(
"session id not present in cred".to_string(), "session id not present in cred".to_string(),
@ -976,19 +994,24 @@ impl AuthEventStep {
}) })
} }
#[cfg(test)]
pub fn begin_mech(sessionid: Uuid, mech: AuthMech) -> Self {
AuthEventStep::Begin(AuthEventStepMech { sessionid, mech })
}
#[cfg(test)] #[cfg(test)]
pub fn cred_step_anonymous(sid: Uuid) -> Self { pub fn cred_step_anonymous(sid: Uuid) -> Self {
AuthEventStep::Creds(AuthEventStepCreds { AuthEventStep::Cred(AuthEventStepCred {
sessionid: sid, sessionid: sid,
creds: vec![AuthCredential::Anonymous], cred: AuthCredential::Anonymous,
}) })
} }
#[cfg(test)] #[cfg(test)]
pub fn cred_step_password(sid: Uuid, pw: &str) -> Self { pub fn cred_step_password(sid: Uuid, pw: &str) -> Self {
AuthEventStep::Creds(AuthEventStepCreds { AuthEventStep::Cred(AuthEventStepCred {
sessionid: sid, sessionid: sid,
creds: vec![AuthCredential::Password(pw.to_string())], cred: AuthCredential::Password(pw.to_string()),
}) })
} }
} }
@ -1024,6 +1047,14 @@ impl AuthEvent {
} }
} }
#[cfg(test)]
pub fn begin_mech(sessionid: Uuid, mech: AuthMech) -> Self {
AuthEvent {
event: None,
step: AuthEventStep::begin_mech(sessionid, mech),
}
}
#[cfg(test)] #[cfg(test)]
pub fn cred_step_anonymous(sid: Uuid) -> Self { pub fn cred_step_anonymous(sid: Uuid) -> Self {
AuthEvent { AuthEvent {

View file

@ -289,17 +289,11 @@ impl Account {
) -> Result<bool, OperationError> { ) -> Result<bool, OperationError> {
match appid { match appid {
Some(_) => Err(OperationError::InvalidState), Some(_) => Err(OperationError::InvalidState),
None => { None => self
match &self.primary { .primary
// Check the cred's associated pw. .as_ref()
Some(ref primary) => primary .ok_or(OperationError::InvalidState)
.password .and_then(|cred| cred.password_ref().and_then(|pw| pw.verify(cleartext))),
.as_ref()
.ok_or(OperationError::InvalidState)
.and_then(|pw| pw.verify(cleartext)),
None => Err(OperationError::InvalidState),
}
} // no appid
} }
} }

File diff suppressed because it is too large Load diff

View file

@ -10,11 +10,12 @@ pub(crate) mod server;
pub(crate) mod unix; pub(crate) mod unix;
// mod identity; // mod identity;
use kanidm_proto::v1::{AuthAllowed, UserAuthToken}; use kanidm_proto::v1::{AuthAllowed, AuthMech, UserAuthToken};
#[derive(Debug)] #[derive(Debug)]
pub enum AuthState { pub enum AuthState {
Success(UserAuthToken), Choose(Vec<AuthMech>),
Denied(String),
Continue(Vec<AuthAllowed>), Continue(Vec<AuthAllowed>),
Denied(String),
Success(UserAuthToken),
} }

View file

@ -418,9 +418,58 @@ impl<'a> IdmServerWriteTransaction<'a> {
state, state,
delay, delay,
}) })
// }) } // AuthEventStep::Init
} AuthEventStep::Begin(mech) => {
AuthEventStep::Creds(creds) => { // lperf_segment!(au, "idm::server::auth<Begin>", || {
let _session_ticket = self.session_ticket.acquire().await;
let _softlock_ticket = self.softlock_ticket.acquire().await;
let mut session_write = self.sessions.write();
// Do we have a session?
let auth_session = session_write
// Why is the session missing?
.get_mut(&mech.sessionid)
.ok_or_else(|| {
ladmin_error!(au, "Invalid Session State (no present session uuid)");
OperationError::InvalidSessionState
})?;
// From the auth_session, determine if the current account
// credential that we are using has become softlocked or not.
let mut softlock_write = self.softlocks.write();
let cred_uuid = auth_session.get_account().primary_cred_uuid();
let is_valid = softlock_write
.get_mut(&cred_uuid)
.map(|slock| {
// Apply the current time.
slock.apply_time_step(ct);
// Now check the results
slock.is_valid()
})
.unwrap_or(true);
let r = if is_valid {
// Indicate to the session which auth mech we now want to proceed with.
auth_session.start_session(au, &mech.mech)
} else {
// Fail the session
auth_session.end_session("Account is temporarily locked")
}
.map(|aus| {
let delay = None;
AuthResult {
sessionid: mech.sessionid,
state: aus,
delay,
}
});
softlock_write.commit();
session_write.commit();
r
} // End AuthEventStep::Mech
AuthEventStep::Cred(creds) => {
// lperf_segment!(au, "idm::server::auth<Creds>", || { // lperf_segment!(au, "idm::server::auth<Creds>", || {
let _session_ticket = self.session_ticket.acquire().await; let _session_ticket = self.session_ticket.acquire().await;
let _softlock_ticket = self.softlock_ticket.acquire().await; let _softlock_ticket = self.softlock_ticket.acquire().await;
@ -456,7 +505,7 @@ impl<'a> IdmServerWriteTransaction<'a> {
// Basically throw them at the auth_session and see what // Basically throw them at the auth_session and see what
// falls out. // falls out.
auth_session auth_session
.validate_creds(au, &creds.creds, &ct, &self.async_tx, self.webauthn) .validate_creds(au, &creds.cred, &ct, &self.async_tx, self.webauthn)
.map(|aus| { .map(|aus| {
// Inspect the result: // Inspect the result:
// if it was a failure, we need to inc the softlock. // if it was a failure, we need to inc the softlock.
@ -479,7 +528,7 @@ impl<'a> IdmServerWriteTransaction<'a> {
}) })
} else { } else {
// Fail the session // Fail the session
auth_session.end_session("Account is temporarily locked".to_string()) auth_session.end_session("Account is temporarily locked")
} }
.map(|aus| { .map(|aus| {
// TODO: Change this william! // TODO: Change this william!
@ -495,8 +544,7 @@ impl<'a> IdmServerWriteTransaction<'a> {
softlock_write.commit(); softlock_write.commit();
session_write.commit(); session_write.commit();
r r
// }) } // End AuthEventStep::Cred
}
} }
} }
@ -1382,9 +1430,9 @@ mod tests {
use crate::idm::AuthState; use crate::idm::AuthState;
use crate::modify::{Modify, ModifyList}; use crate::modify::{Modify, ModifyList};
use crate::value::{PartialValue, Value}; use crate::value::{PartialValue, Value};
use kanidm_proto::v1::AuthAllowed;
use kanidm_proto::v1::OperationError; use kanidm_proto::v1::OperationError;
use kanidm_proto::v1::SetCredentialResponse; use kanidm_proto::v1::SetCredentialResponse;
use kanidm_proto::v1::{AuthAllowed, AuthMech};
use crate::audit::AuditScope; use crate::audit::AuditScope;
use crate::idm::server::IdmServer; use crate::idm::server::IdmServer;
@ -1430,12 +1478,12 @@ mod tests {
} = ar; } = ar;
debug_assert!(delay.is_none()); debug_assert!(delay.is_none());
match state { match state {
AuthState::Continue(mut conts) => { AuthState::Choose(mut conts) => {
// Should only be one auth mech // Should only be one auth mech
assert!(conts.len() == 1); assert!(conts.len() == 1);
// And it should be anonymous // And it should be anonymous
let m = conts.pop().expect("Should not fail"); let m = conts.pop().expect("Should not fail");
assert!(m == AuthAllowed::Anonymous); assert!(m == AuthMech::Anonymous);
} }
_ => { _ => {
error!( error!(
@ -1460,6 +1508,49 @@ mod tests {
sid sid
}; };
{
let mut idms_write = idms.write();
let anon_begin = AuthEvent::begin_mech(sid, AuthMech::Anonymous);
let r2 = task::block_on(idms_write.auth(
au,
&anon_begin,
Duration::from_secs(TEST_CURRENT_TIME),
));
debug!("r2 ==> {:?}", r2);
match r2 {
Ok(ar) => {
let AuthResult {
sessionid: _,
state,
delay,
} = ar;
debug_assert!(delay.is_none());
match state {
AuthState::Continue(allowed) => {
// Check the uat.
assert!(allowed.len() == 1);
assert!(allowed.first() == Some(&AuthAllowed::Anonymous));
}
_ => {
error!(
"A critical error has occured! We have a non-continue result!"
);
panic!();
}
}
}
Err(e) => {
error!("A critical error has occured! {:?}", e);
// Should not occur!
panic!();
}
};
idms_write.commit(au).expect("Must not fail");
};
{ {
let mut idms_write = idms.write(); let mut idms_write = idms.write();
// Now send the anonymous request, given the session id. // Now send the anonymous request, given the session id.
@ -1566,9 +1657,14 @@ mod tests {
qs_write.commit(au) qs_write.commit(au)
} }
fn init_admin_authsession_sid(idms: &IdmServer, au: &mut AuditScope, ct: Duration) -> Uuid { fn init_admin_authsession_sid(
idms: &IdmServer,
au: &mut AuditScope,
ct: Duration,
name: &str,
) -> Uuid {
let mut idms_write = idms.write(); let mut idms_write = idms.write();
let admin_init = AuthEvent::named_init("admin"); let admin_init = AuthEvent::named_init(name);
let r1 = task::block_on(idms_write.auth(au, &admin_init, ct)); let r1 = task::block_on(idms_write.auth(au, &admin_init, ct));
let ar = r1.unwrap(); let ar = r1.unwrap();
@ -1578,6 +1674,26 @@ mod tests {
delay, delay,
} = ar; } = ar;
debug_assert!(delay.is_none());
match state {
AuthState::Choose(_) => {}
_ => {
error!("Sessions was not initialised");
panic!();
}
};
// Now push that we want the Password Mech.
let admin_begin = AuthEvent::begin_mech(sessionid, AuthMech::Password);
let r2 = task::block_on(idms_write.auth(au, &admin_begin, ct));
let ar = r2.unwrap();
let AuthResult {
sessionid,
state,
delay,
} = ar;
debug_assert!(delay.is_none()); debug_assert!(delay.is_none());
match state { match state {
@ -1594,7 +1710,8 @@ mod tests {
} }
fn check_admin_password(idms: &IdmServer, au: &mut AuditScope, pw: &str) { fn check_admin_password(idms: &IdmServer, au: &mut AuditScope, pw: &str) {
let sid = init_admin_authsession_sid(idms, au, Duration::from_secs(TEST_CURRENT_TIME)); let sid =
init_admin_authsession_sid(idms, au, Duration::from_secs(TEST_CURRENT_TIME), "admin");
let mut idms_write = idms.write(); let mut idms_write = idms.write();
let anon_step = AuthEvent::cred_step_password(sid, pw); let anon_step = AuthEvent::cred_step_password(sid, pw);
@ -1652,33 +1769,13 @@ mod tests {
_idms_delayed: &IdmServerDelayed, _idms_delayed: &IdmServerDelayed,
au: &mut AuditScope| { au: &mut AuditScope| {
init_admin_w_password(au, qs, TEST_PASSWORD).expect("Failed to setup admin account"); init_admin_w_password(au, qs, TEST_PASSWORD).expect("Failed to setup admin account");
let mut idms_write = idms.write();
let admin_init = AuthEvent::named_init("admin@example.com");
let r1 = task::block_on(idms_write.auth( let sid = init_admin_authsession_sid(
idms,
au, au,
&admin_init,
Duration::from_secs(TEST_CURRENT_TIME), Duration::from_secs(TEST_CURRENT_TIME),
)); "admin@example.com",
let ar = r1.unwrap(); );
let AuthResult {
sessionid,
state,
delay,
} = ar;
debug_assert!(delay.is_none());
match state {
AuthState::Continue(_) => {}
_ => {
error!("Sessions was not initialised");
panic!();
}
};
idms_write.commit(au).expect("Must not fail");
let sid = sessionid;
let mut idms_write = idms.write(); let mut idms_write = idms.write();
let anon_step = AuthEvent::cred_step_password(sid, TEST_PASSWORD); let anon_step = AuthEvent::cred_step_password(sid, TEST_PASSWORD);
@ -1727,7 +1824,12 @@ mod tests {
_idms_delayed: &IdmServerDelayed, _idms_delayed: &IdmServerDelayed,
au: &mut AuditScope| { au: &mut AuditScope| {
init_admin_w_password(au, qs, TEST_PASSWORD).expect("Failed to setup admin account"); init_admin_w_password(au, qs, TEST_PASSWORD).expect("Failed to setup admin account");
let sid = init_admin_authsession_sid(idms, au, Duration::from_secs(TEST_CURRENT_TIME)); let sid = init_admin_authsession_sid(
idms,
au,
Duration::from_secs(TEST_CURRENT_TIME),
"admin",
);
let mut idms_write = idms.write(); let mut idms_write = idms.write();
let anon_step = AuthEvent::cred_step_password(sid, TEST_PASSWORD_INC); let anon_step = AuthEvent::cred_step_password(sid, TEST_PASSWORD_INC);
@ -1804,7 +1906,12 @@ mod tests {
_idms_delayed: &IdmServerDelayed, _idms_delayed: &IdmServerDelayed,
au: &mut AuditScope| { au: &mut AuditScope| {
init_admin_w_password(au, qs, TEST_PASSWORD).expect("Failed to setup admin account"); init_admin_w_password(au, qs, TEST_PASSWORD).expect("Failed to setup admin account");
let sid = init_admin_authsession_sid(idms, au, Duration::from_secs(TEST_CURRENT_TIME)); let sid = init_admin_authsession_sid(
idms,
au,
Duration::from_secs(TEST_CURRENT_TIME),
"admin",
);
let mut idms_write = idms.write(); let mut idms_write = idms.write();
assert!(idms_write.is_sessionid_present(&sid)); assert!(idms_write.is_sessionid_present(&sid));
// Expire like we are currently "now". Should not affect our session. // Expire like we are currently "now". Should not affect our session.
@ -2503,7 +2610,12 @@ mod tests {
init_admin_w_password(au, qs, TEST_PASSWORD).expect("Failed to setup admin account"); init_admin_w_password(au, qs, TEST_PASSWORD).expect("Failed to setup admin account");
// Auth invalid, no softlock present. // Auth invalid, no softlock present.
let sid = init_admin_authsession_sid(idms, au, Duration::from_secs(TEST_CURRENT_TIME)); let sid = init_admin_authsession_sid(
idms,
au,
Duration::from_secs(TEST_CURRENT_TIME),
"admin",
);
let mut idms_write = idms.write(); let mut idms_write = idms.write();
let anon_step = AuthEvent::cred_step_password(sid, TEST_PASSWORD_INC); let anon_step = AuthEvent::cred_step_password(sid, TEST_PASSWORD_INC);
@ -2574,8 +2686,12 @@ mod tests {
// Tested in the softlock state machine. // Tested in the softlock state machine.
// Auth valid once softlock pass, valid. Count remains. // Auth valid once softlock pass, valid. Count remains.
let sid = let sid = init_admin_authsession_sid(
init_admin_authsession_sid(idms, au, Duration::from_secs(TEST_CURRENT_TIME + 2)); idms,
au,
Duration::from_secs(TEST_CURRENT_TIME + 2),
"admin",
);
let mut idms_write = idms.write(); let mut idms_write = idms.write();
let anon_step = AuthEvent::cred_step_password(sid, TEST_PASSWORD); let anon_step = AuthEvent::cred_step_password(sid, TEST_PASSWORD);
@ -2632,12 +2748,20 @@ mod tests {
init_admin_w_password(au, qs, TEST_PASSWORD).expect("Failed to setup admin account"); init_admin_w_password(au, qs, TEST_PASSWORD).expect("Failed to setup admin account");
// Start an *early* auth session. // Start an *early* auth session.
let sid_early = let sid_early = init_admin_authsession_sid(
init_admin_authsession_sid(idms, au, Duration::from_secs(TEST_CURRENT_TIME)); idms,
au,
Duration::from_secs(TEST_CURRENT_TIME),
"admin",
);
// Start a second auth session // Start a second auth session
let sid_later = let sid_later = init_admin_authsession_sid(
init_admin_authsession_sid(idms, au, Duration::from_secs(TEST_CURRENT_TIME)); idms,
au,
Duration::from_secs(TEST_CURRENT_TIME),
"admin",
);
// Get the detail wrong in sid_later. // Get the detail wrong in sid_later.
let mut idms_write = idms.write(); let mut idms_write = idms.write();
let anon_step = AuthEvent::cred_step_password(sid_later, TEST_PASSWORD_INC); let anon_step = AuthEvent::cred_step_password(sid_later, TEST_PASSWORD_INC);
@ -2822,7 +2946,7 @@ mod tests {
let cred = account.primary.expect("Must exist."); let cred = account.primary.expect("Must exist.");
let wcred = cred let wcred = cred
.webauthn .webauthn_ref()
.expect("must have webauthn") .expect("must have webauthn")
.values() .values()
.next() .next()

View file

@ -228,39 +228,33 @@ impl UnixUserAccount {
// is the cred some or none? // is the cred some or none?
match &self.cred { match &self.cred {
Some(cred) => { Some(cred) => {
match &cred.password { cred.password_ref().and_then(|pw| {
Some(pw) => { if pw.verify(cleartext)? {
if pw.verify(cleartext)? { lsecurity!(au, "Successful unix cred handling");
lsecurity!(au, "Successful unix cred handling"); if pw.requires_upgrade() {
if pw.requires_upgrade() { async_tx
async_tx.send( .send(DelayedAction::UnixPwUpgrade(UnixPasswordUpgrade {
DelayedAction::UnixPwUpgrade(UnixPasswordUpgrade {
target_uuid: self.uuid, target_uuid: self.uuid,
existing_password: cleartext.to_string(), existing_password: cleartext.to_string(),
}) }))
).map_err(|_| { .map_err(|_| {
ladmin_error!(au, "failed to queue delayed action - unix password upgrade"); ladmin_error!(
OperationError::InvalidState au,
})?; "failed to queue delayed action - unix password upgrade"
} );
OperationError::InvalidState
// Technically this means we check the times twice, but that doesn't })?;
// seem like a big deal when we want to short cut return on invalid.
Some(self.to_unixusertoken(ct)).transpose()
} else {
// Failed to auth
lsecurity!(au, "Failed unix cred handling (denied)");
Ok(None)
} }
// Technically this means we check the times twice, but that doesn't
// seem like a big deal when we want to short cut return on invalid.
Some(self.to_unixusertoken(ct)).transpose()
} else {
// Failed to auth
lsecurity!(au, "Failed unix cred handling (denied)");
Ok(None)
} }
// We have a cred but it's not a password, that's weird })
None => {
lsecurity!(au, "Invalid unix cred request");
Err(OperationError::InvalidAccountState(
"non-password cred type?".to_string(),
))
}
}
} }
// They don't have a unix cred, fail the auth. // They don't have a unix cred, fail the auth.
None => { None => {
@ -272,10 +266,7 @@ impl UnixUserAccount {
pub(crate) fn check_existing_pw(&self, cleartext: &str) -> Result<bool, OperationError> { pub(crate) fn check_existing_pw(&self, cleartext: &str) -> Result<bool, OperationError> {
match &self.cred { match &self.cred {
Some(cred) => match &cred.password { Some(cred) => cred.password_ref().and_then(|pw| pw.verify(cleartext)),
Some(pw) => pw.verify(cleartext),
None => Err(OperationError::InvalidState),
},
None => Err(OperationError::InvalidState), None => Err(OperationError::InvalidState),
} }
} }

View file

@ -128,7 +128,7 @@ impl Plugin for PasswordImport {
mod tests { mod tests {
use crate::credential::policy::CryptoPolicy; use crate::credential::policy::CryptoPolicy;
use crate::credential::totp::{TOTP, TOTP_DEFAULT_STEP}; use crate::credential::totp::{TOTP, TOTP_DEFAULT_STEP};
use crate::credential::Credential; use crate::credential::{Credential, CredentialType};
use crate::entry::{Entry, EntryInit, EntryNew}; use crate::entry::{Entry, EntryInit, EntryNew};
use crate::modify::{Modify, ModifyList}; use crate::modify::{Modify, ModifyList};
use crate::server::{QueryServerTransaction, QueryServerWriteTransaction}; use crate::server::{QueryServerTransaction, QueryServerWriteTransaction};
@ -268,8 +268,13 @@ mod tests {
let c = e let c = e
.get_ava_single_credential("primary_credential") .get_ava_single_credential("primary_credential")
.expect("failed to get primary cred."); .expect("failed to get primary cred.");
assert!(c.totp.is_some()); match &c.type_ {
assert!(c.password.is_some()); CredentialType::PasswordMFA(_pw, totp, webauthn) => {
assert!(totp.is_some());
assert!(webauthn.is_empty());
}
_ => assert!(false),
};
} }
); );
} }

View file

@ -3556,7 +3556,7 @@ mod tests {
.get_ava_single_credential("primary_credential") .get_ava_single_credential("primary_credential")
.expect("Failed"); .expect("Failed");
// do a pw check. // do a pw check.
assert!(cred_ref.verify_password("test_password")); assert!(cred_ref.verify_password("test_password").unwrap());
}) })
} }