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

View file

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

View file

@ -2,6 +2,7 @@ use std::collections::BTreeMap;
use std::fmt;
use uuid::Uuid;
// use zxcvbn::feedback;
use std::cmp::Ordering;
use webauthn_rs::proto::{
CreationChallengeResponse, PublicKeyCredential, RegisterPublicKeyCredential,
RequestChallengeResponse,
@ -426,6 +427,7 @@ impl ModifyRequest {
// On loginSuccess, we send a cookie, and that allows the token to be
// generated. The cookie can be shared between servers.
#[derive(Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum AuthCredential {
Anonymous,
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)]
#[serde(rename_all = "lowercase")]
pub enum AuthStep {
// name
Init(String),
/*
Step(
Type(params ....)
),
*/
Creds(Vec<AuthCredential>),
// We want to talk to you like this.
Begin(AuthMech),
// Step
Cred(AuthCredential),
// Should we have a "finalise" type to attempt to finish based on
// 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)]
#[serde(rename_all = "lowercase")]
pub enum AuthState {
// Everything is good, your bearer header has been issued and is within
// the result.
// Success(UserAuthToken),
Success(String),
// You need to select how you want to talk to me.
Choose(Vec<AuthMech>),
// Continue to auth, allowed mechanisms/challenges listed.
Continue(Vec<AuthAllowed>),
// Something was bad, your session is terminated and no cookie.
Denied(String),
// Continue to auth, allowed mechanisms listed.
Continue(Vec<AuthAllowed>),
// Everything is good, your bearer header has been issued and is within
// the result.
Success(String),
}
#[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
print(os.getcwd())
CONFIG_PATH = os.environ.get('KANIDM_RLM_CONFIG', '/data/config.ini')
CONFIG = configparser.ConfigParser()
CONFIG.read('/data/config.ini')
# CONFIG.read('/tmp/config.ini')
CONFIG.read(CONFIG_PATH)
GROUPS = [
{
@ -50,7 +51,19 @@ def _authenticate(s, acct, pw):
print(r.json())
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)
response = r.json()
if r.status_code != 200:

View file

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

View file

@ -39,10 +39,30 @@ pub struct DbWebauthnV1 {
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)]
pub struct DbCredV1 {
#[serde(default = "dbcred_type_default_pw")]
pub type_: DbCredTypeV1,
#[serde(skip_serializing_if = "Option::is_none")]
pub password: Option<DbPasswordV1>,
#[serde(skip_serializing_if = "Option::is_none")]
pub webauthn: Option<Vec<DbWebauthnV1>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub totp: Option<DbTotpV1>,
pub claims: Vec<String>,
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.
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) => {
debug!("🧩 -> AuthState::Success");
// 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");
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 })
}

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 kanidm_proto::v1::OperationError;
use openssl::hash::MessageDigest;
@ -225,15 +225,10 @@ impl Password {
/// 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
/// some metadata to support this such as it's source and strength etc. Some of these details are
/// to be resolved ...
/// some metadata to support this such as it's source and strength etc.
pub struct Credential {
// Source (machine, user, ....). Strength?
// policy: Policy,
pub(crate) password: Option<Password>,
pub(crate) webauthn: Option<Map<String, WebauthnCredential>>,
// totp: Option<NonEmptyVec<TOTP>>
pub(crate) totp: Option<TOTP>,
pub(crate) type_: CredentialType,
pub(crate) claims: Vec<String>,
// Uuid of Credential, used by auth session to lock this specific credential
// if required.
@ -242,12 +237,25 @@ pub struct Credential {
// 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 {
type Error = ();
fn try_from(value: DbCredV1) -> Result<Self, Self::Error> {
// Work out what the policy is?
let DbCredV1 {
type_,
password,
webauthn,
totp,
@ -284,10 +292,21 @@ impl TryFrom<DbCredV1> for Credential {
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 {
password: v_password,
webauthn: v_webauthn,
totp: v_totp,
type_,
claims,
uuid,
})
@ -306,9 +325,7 @@ impl Credential {
let mut webauthn_map = Map::new();
webauthn_map.insert(label, cred);
Credential {
password: None,
webauthn: Some(webauthn_map),
totp: None,
type_: CredentialType::Webauthn(webauthn_map),
claims: Vec::new(),
uuid: Uuid::new_v4(),
}
@ -319,13 +336,7 @@ impl Credential {
policy: &CryptoPolicy,
cleartext: &str,
) -> Result<Self, OperationError> {
Password::new(policy, cleartext).map(|pw| Credential {
password: Some(pw),
webauthn: self.webauthn.clone(),
totp: self.totp.clone(),
claims: self.claims.clone(),
uuid: self.uuid,
})
Password::new(policy, cleartext).map(|pw| self.update_password(pw))
}
pub fn append_webauthn(
@ -333,30 +344,37 @@ impl Credential {
label: String,
cred: WebauthnCredential,
) -> Result<Self, OperationError> {
let webauthn_map = match &self.webauthn {
Some(map) => {
let mut nmap = map.clone();
match nmap.insert(label.clone(), cred) {
Some(_) => {
return Err(OperationError::InvalidAttribute(format!(
"Webauthn label '{:?}' already exists",
label
)));
}
None => nmap,
}
let type_ = match &self.type_ {
CredentialType::Password(pw) | CredentialType::GeneratedPassword(pw) => {
let mut wan = Map::new();
wan.insert(label, cred);
CredentialType::PasswordMFA(pw.clone(), None, wan)
}
None => {
let mut map = Map::new();
map.insert(label, cred);
map
CredentialType::PasswordMFA(pw, totp, 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::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
Ok(Credential {
password: self.password.clone(),
webauthn: Some(webauthn_map),
totp: self.totp.clone(),
type_,
claims: self.claims.clone(),
uuid: self.uuid,
})
@ -367,69 +385,155 @@ impl Credential {
cid: &CredentialID,
counter: Counter,
) -> Result<Option<Self>, OperationError> {
let opt_label = self.webauthn.as_ref().and_then(|m| {
m.iter().fold(None, |acc, (k, v)| {
if acc.is_none() && &v.cred_id == cid && v.counter < counter {
Some(k)
} else {
acc
}
})
});
let nmap = match &self.type_ {
CredentialType::Password(_pw) | CredentialType::GeneratedPassword(_pw) => {
// No action required
return Ok(None);
}
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 {
let mut webauthn_map = self.webauthn.clone();
webauthn_map
.get_mut(label)
.map(|cred| cred.counter = counter);
webauthn_map
}),
};
webauthn_map
.as_mut()
.and_then(|m| m.get_mut(label))
.map(|cred| cred.counter = counter);
let map = match nmap {
Some(map) => map,
None => {
// No action needed.
return Ok(None);
}
};
Ok(Some(Credential {
password: self.password.clone(),
webauthn: webauthn_map,
totp: self.totp.clone(),
claims: self.claims.clone(),
uuid: self.uuid,
}))
} else {
Ok(None)
let type_ = match &self.type_ {
CredentialType::Password(_pw) | CredentialType::GeneratedPassword(_pw) => {
// Should not be possible!
unreachable!();
}
CredentialType::Webauthn(_) => CredentialType::Webauthn(map),
CredentialType::PasswordMFA(pw, totp, _) => {
CredentialType::PasswordMFA(pw.clone(), totp.clone(), map)
}
};
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)]
pub fn verify_password(&self, cleartext: &str) -> bool {
match &self.password {
Some(pw) => pw.verify(cleartext).unwrap_or(false),
None => false,
}
pub fn verify_password(&self, cleartext: &str) -> Result<bool, OperationError> {
self.password_ref().and_then(|pw| pw.verify(cleartext))
}
pub fn to_db_valuev1(&self) -> DbCredV1 {
DbCredV1 {
password: self.password.as_ref().map(|pw| pw.to_dbpasswordv1()),
webauthn: self.webauthn.as_ref().map(|map| {
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: self.totp.as_ref().map(|t| t.to_dbtotpv1()),
claims: self.claims.clone(),
uuid: self.uuid,
let claims = self.claims.clone();
let uuid = self.uuid;
match &self.type_ {
CredentialType::Password(pw) => DbCredV1 {
type_: DbCredTypeV1::Pw,
password: Some(pw.to_dbpasswordv1()),
webauthn: None,
totp: None,
claims,
uuid,
},
CredentialType::GeneratedPassword(pw) => DbCredV1 {
type_: DbCredTypeV1::GPw,
password: Some(pw.to_dbpasswordv1()),
webauthn: None,
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 {
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 {
password: Some(pw),
webauthn: self.webauthn.clone(),
totp: self.totp.clone(),
type_,
claims: self.claims.clone(),
uuid: self.uuid,
}
@ -437,10 +541,20 @@ impl Credential {
// We don't make totp accessible from outside the crate for now.
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 {
password: self.password.clone(),
webauthn: self.webauthn.clone(),
totp: Some(totp),
type_,
claims: self.claims.clone(),
uuid: self.uuid,
}
@ -448,24 +562,27 @@ impl Credential {
pub(crate) fn new_from_password(pw: Password) -> Self {
Credential {
password: Some(pw),
webauthn: None,
totp: None,
type_: CredentialType::Password(pw),
claims: Vec::new(),
uuid: Uuid::new_v4(),
}
}
pub(crate) fn softlock_policy(&self) -> Option<CredSoftLockPolicy> {
match (&self.webauthn, &self.totp, &self.password) {
// Has any kind of Webauthn ....
(Some(_webauthn), _, _) => Some(CredSoftLockPolicy::Webauthn),
// Has any kind of totp.
(None, Some(totp), _) => Some(CredSoftLockPolicy::TOTP(totp.step)),
// No totp, pw
(None, None, Some(_)) => Some(CredSoftLockPolicy::Password),
// Indeterminate
_ => None,
match &self.type_ {
CredentialType::Password(_pw) | CredentialType::GeneratedPassword(_pw) => {
Some(CredSoftLockPolicy::Password)
}
CredentialType::PasswordMFA(_pw, totp, wan) => {
if let Some(r_totp) = totp {
Some(CredSoftLockPolicy::TOTP(r_totp.step))
} else if wan.len() > 0 {
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)]
mod tests {
use crate::credential::policy::CryptoPolicy;
@ -500,11 +629,11 @@ mod tests {
fn test_credential_simple() {
let p = CryptoPolicy::minimum();
let c = Credential::new_password_only(&p, "password").unwrap();
assert!(c.verify_password("password"));
assert!(!c.verify_password("password1"));
assert!(!c.verify_password("Password1"));
assert!(!c.verify_password("It Works!"));
assert!(!c.verify_password("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"));
assert!(c.verify_password("password").unwrap());
assert!(!c.verify_password("password1").unwrap());
assert!(!c.verify_password("Password1").unwrap());
assert!(!c.verify_password("It Works!").unwrap());
assert!(!c.verify_password("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa").unwrap());
}
#[test]

View file

@ -6,7 +6,9 @@ use crate::schema::SchemaTransaction;
use crate::value::PartialValue;
use kanidm_proto::v1::Entry as ProtoEntry;
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 crate::modify::{ModifyInvalid, ModifyList, ModifyValid};
use crate::server::{
@ -925,15 +927,22 @@ pub struct AuthEventStepInit {
}
#[derive(Debug)]
pub struct AuthEventStepCreds {
pub struct AuthEventStepCred {
pub sessionid: Uuid,
pub creds: Vec<AuthCredential>,
pub cred: AuthCredential,
}
#[derive(Debug)]
pub struct AuthEventStepMech {
pub sessionid: Uuid,
pub mech: AuthMech,
}
#[derive(Debug)]
pub enum AuthEventStep {
Init(AuthEventStepInit),
Creds(AuthEventStepCreds),
Begin(AuthEventStepMech),
Cred(AuthEventStepCred),
}
impl AuthEventStep {
@ -948,10 +957,19 @@ impl AuthEventStep {
Ok(AuthEventStep::Init(AuthEventStepInit { name, appid: None }))
}
}
AuthStep::Creds(creds) => match sid {
Some(ssid) => Ok(AuthEventStep::Creds(AuthEventStepCreds {
AuthStep::Begin(mech) => match sid {
Some(ssid) => Ok(AuthEventStep::Begin(AuthEventStepMech {
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(
"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)]
pub fn cred_step_anonymous(sid: Uuid) -> Self {
AuthEventStep::Creds(AuthEventStepCreds {
AuthEventStep::Cred(AuthEventStepCred {
sessionid: sid,
creds: vec![AuthCredential::Anonymous],
cred: AuthCredential::Anonymous,
})
}
#[cfg(test)]
pub fn cred_step_password(sid: Uuid, pw: &str) -> Self {
AuthEventStep::Creds(AuthEventStepCreds {
AuthEventStep::Cred(AuthEventStepCred {
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)]
pub fn cred_step_anonymous(sid: Uuid) -> Self {
AuthEvent {

View file

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

File diff suppressed because it is too large Load diff

View file

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

View file

@ -418,9 +418,58 @@ impl<'a> IdmServerWriteTransaction<'a> {
state,
delay,
})
// })
}
AuthEventStep::Creds(creds) => {
} // AuthEventStep::Init
AuthEventStep::Begin(mech) => {
// 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>", || {
let _session_ticket = self.session_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
// falls out.
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| {
// Inspect the result:
// if it was a failure, we need to inc the softlock.
@ -479,7 +528,7 @@ impl<'a> IdmServerWriteTransaction<'a> {
})
} else {
// Fail the session
auth_session.end_session("Account is temporarily locked".to_string())
auth_session.end_session("Account is temporarily locked")
}
.map(|aus| {
// TODO: Change this william!
@ -495,8 +544,7 @@ impl<'a> IdmServerWriteTransaction<'a> {
softlock_write.commit();
session_write.commit();
r
// })
}
} // End AuthEventStep::Cred
}
}
@ -1382,9 +1430,9 @@ mod tests {
use crate::idm::AuthState;
use crate::modify::{Modify, ModifyList};
use crate::value::{PartialValue, Value};
use kanidm_proto::v1::AuthAllowed;
use kanidm_proto::v1::OperationError;
use kanidm_proto::v1::SetCredentialResponse;
use kanidm_proto::v1::{AuthAllowed, AuthMech};
use crate::audit::AuditScope;
use crate::idm::server::IdmServer;
@ -1430,12 +1478,12 @@ mod tests {
} = ar;
debug_assert!(delay.is_none());
match state {
AuthState::Continue(mut conts) => {
AuthState::Choose(mut conts) => {
// Should only be one auth mech
assert!(conts.len() == 1);
// And it should be anonymous
let m = conts.pop().expect("Should not fail");
assert!(m == AuthAllowed::Anonymous);
assert!(m == AuthMech::Anonymous);
}
_ => {
error!(
@ -1460,6 +1508,49 @@ mod tests {
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();
// Now send the anonymous request, given the session id.
@ -1566,9 +1657,14 @@ mod tests {
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 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 ar = r1.unwrap();
@ -1578,6 +1674,26 @@ mod tests {
delay,
} = 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());
match state {
@ -1594,7 +1710,8 @@ mod tests {
}
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 anon_step = AuthEvent::cred_step_password(sid, pw);
@ -1652,33 +1769,13 @@ mod tests {
_idms_delayed: &IdmServerDelayed,
au: &mut AuditScope| {
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,
&admin_init,
Duration::from_secs(TEST_CURRENT_TIME),
));
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;
"admin@example.com",
);
let mut idms_write = idms.write();
let anon_step = AuthEvent::cred_step_password(sid, TEST_PASSWORD);
@ -1727,7 +1824,12 @@ mod tests {
_idms_delayed: &IdmServerDelayed,
au: &mut AuditScope| {
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 anon_step = AuthEvent::cred_step_password(sid, TEST_PASSWORD_INC);
@ -1804,7 +1906,12 @@ mod tests {
_idms_delayed: &IdmServerDelayed,
au: &mut AuditScope| {
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();
assert!(idms_write.is_sessionid_present(&sid));
// 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");
// 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 anon_step = AuthEvent::cred_step_password(sid, TEST_PASSWORD_INC);
@ -2574,8 +2686,12 @@ mod tests {
// Tested in the softlock state machine.
// Auth valid once softlock pass, valid. Count remains.
let sid =
init_admin_authsession_sid(idms, au, Duration::from_secs(TEST_CURRENT_TIME + 2));
let sid = init_admin_authsession_sid(
idms,
au,
Duration::from_secs(TEST_CURRENT_TIME + 2),
"admin",
);
let mut idms_write = idms.write();
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");
// Start an *early* auth session.
let sid_early =
init_admin_authsession_sid(idms, au, Duration::from_secs(TEST_CURRENT_TIME));
let sid_early = init_admin_authsession_sid(
idms,
au,
Duration::from_secs(TEST_CURRENT_TIME),
"admin",
);
// Start a second auth session
let sid_later =
init_admin_authsession_sid(idms, au, Duration::from_secs(TEST_CURRENT_TIME));
let sid_later = init_admin_authsession_sid(
idms,
au,
Duration::from_secs(TEST_CURRENT_TIME),
"admin",
);
// Get the detail wrong in sid_later.
let mut idms_write = idms.write();
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 wcred = cred
.webauthn
.webauthn_ref()
.expect("must have webauthn")
.values()
.next()

View file

@ -228,39 +228,33 @@ impl UnixUserAccount {
// is the cred some or none?
match &self.cred {
Some(cred) => {
match &cred.password {
Some(pw) => {
if pw.verify(cleartext)? {
lsecurity!(au, "Successful unix cred handling");
if pw.requires_upgrade() {
async_tx.send(
DelayedAction::UnixPwUpgrade(UnixPasswordUpgrade {
cred.password_ref().and_then(|pw| {
if pw.verify(cleartext)? {
lsecurity!(au, "Successful unix cred handling");
if pw.requires_upgrade() {
async_tx
.send(DelayedAction::UnixPwUpgrade(UnixPasswordUpgrade {
target_uuid: self.uuid,
existing_password: cleartext.to_string(),
})
).map_err(|_| {
ladmin_error!(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)
}))
.map_err(|_| {
ladmin_error!(
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)
}
// 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.
None => {
@ -272,10 +266,7 @@ impl UnixUserAccount {
pub(crate) fn check_existing_pw(&self, cleartext: &str) -> Result<bool, OperationError> {
match &self.cred {
Some(cred) => match &cred.password {
Some(pw) => pw.verify(cleartext),
None => Err(OperationError::InvalidState),
},
Some(cred) => cred.password_ref().and_then(|pw| pw.verify(cleartext)),
None => Err(OperationError::InvalidState),
}
}

View file

@ -128,7 +128,7 @@ impl Plugin for PasswordImport {
mod tests {
use crate::credential::policy::CryptoPolicy;
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::modify::{Modify, ModifyList};
use crate::server::{QueryServerTransaction, QueryServerWriteTransaction};
@ -268,8 +268,13 @@ mod tests {
let c = e
.get_ava_single_credential("primary_credential")
.expect("failed to get primary cred.");
assert!(c.totp.is_some());
assert!(c.password.is_some());
match &c.type_ {
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")
.expect("Failed");
// do a pw check.
assert!(cred_ref.verify_password("test_password"));
assert!(cred_ref.verify_password("test_password").unwrap());
})
}