commit c380d9c3792f6587b22e417c82adf4de54695d18
parent cea7a30d829cb68220ec51de0846ec3227077d4d
Author: Daniel GarcĂa <dani-garcia@users.noreply.github.com>
Date: Mon, 7 Jun 2021 23:34:00 +0200
Support for webauthn and u2f->webauthn migrations
Diffstat:
18 files changed, 654 insertions(+), 126 deletions(-)
diff --git a/Cargo.lock b/Cargo.lock
@@ -317,7 +317,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "03a5d7b21829bc7b4bf4754a978a241ae54ea55a40f92bb20216e54096f4b951"
dependencies = [
"percent-encoding 2.1.0",
- "time 0.2.26",
+ "time 0.2.27",
"version_check 0.9.3",
]
@@ -328,7 +328,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ffdf8865bac3d9a3bde5bde9088ca431b11f5d37c7a578b8086af77248b76627"
dependencies = [
"percent-encoding 2.1.0",
- "time 0.2.26",
+ "time 0.2.27",
"version_check 0.9.3",
]
@@ -344,7 +344,7 @@ dependencies = [
"publicsuffix 1.5.6",
"serde",
"serde_json",
- "time 0.2.26",
+ "time 0.2.27",
"url 2.2.2",
]
@@ -360,7 +360,7 @@ dependencies = [
"publicsuffix 2.1.0",
"serde",
"serde_json",
- "time 0.2.26",
+ "time 0.2.27",
"url 2.2.2",
]
@@ -469,7 +469,7 @@ dependencies = [
"bitflags",
"proc-macro2 1.0.27",
"quote 1.0.9",
- "syn 1.0.72",
+ "syn 1.0.73",
]
[[package]]
@@ -497,7 +497,7 @@ checksum = "45f5098f628d02a7a0f68ddba586fb61e80edec3bdc1be3b921f4ceec60858d3"
dependencies = [
"proc-macro2 1.0.27",
"quote 1.0.9",
- "syn 1.0.72",
+ "syn 1.0.73",
]
[[package]]
@@ -747,7 +747,7 @@ dependencies = [
"proc-macro-hack",
"proc-macro2 1.0.27",
"quote 1.0.9",
- "syn 1.0.72",
+ "syn 1.0.73",
]
[[package]]
@@ -866,10 +866,16 @@ dependencies = [
]
[[package]]
+name = "half"
+version = "1.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "62aca2aba2d62b4a7f5b33f3712cb1b0692779a56fb510499d5c0aa594daeaf3"
+
+[[package]]
name = "handlebars"
-version = "3.5.5"
+version = "4.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4498fc115fa7d34de968184e473529abb40eeb6be8bc5f7faba3d08c316cb3e3"
+checksum = "2060119114dd8a8bc87facce6384751af8280a7adc8e203c023c95cbb11f5663"
dependencies = [
"log 0.4.14",
"pest",
@@ -944,7 +950,7 @@ dependencies = [
"markup5ever",
"proc-macro2 1.0.27",
"quote 1.0.9",
- "syn 1.0.72",
+ "syn 1.0.73",
]
[[package]]
@@ -1101,9 +1107,9 @@ dependencies = [
[[package]]
name = "ipnet"
-version = "2.3.0"
+version = "2.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "47be2f14c678be2fdcab04ab1171db51b2762ce6f0a8ee87c8dd4a04ed216135"
+checksum = "68f2d64f2edebec4ce84ad108148e67e1064789bee435edc5b60ad398714a3a9"
[[package]]
name = "itoa"
@@ -1195,9 +1201,9 @@ dependencies = [
[[package]]
name = "libc"
-version = "0.2.96"
+version = "0.2.97"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5600b4e6efc5421841a2138a6b082e07fe12f9aaa12783d50e5d13325b26b4fc"
+checksum = "12b8adadd720df158f4d70dfe7ccc6adb0472d7c55ca83445f6a5ab3e36f8fb6"
[[package]]
name = "libsqlite3-sys"
@@ -1317,7 +1323,7 @@ dependencies = [
"migrations_internals",
"proc-macro2 1.0.27",
"quote 1.0.9",
- "syn 1.0.72",
+ "syn 1.0.73",
]
[[package]]
@@ -1376,9 +1382,9 @@ dependencies = [
[[package]]
name = "mio"
-version = "0.7.11"
+version = "0.7.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cf80d3e903b34e0bd7282b218398aec54e082c840d9baf8339e0080a0c542956"
+checksum = "8c2bdb6314ec10835cd3293dd268473a835c02b7b352e788be788b3c6ca6bb16"
dependencies = [
"libc",
"log 0.4.14",
@@ -1422,9 +1428,9 @@ dependencies = [
[[package]]
name = "multipart"
-version = "0.17.1"
+version = "0.18.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d050aeedc89243f5347c3e237e3e13dc76fbe4ae3742a57b94dc14f69acf76d4"
+checksum = "00dec633863867f29cb39df64a397cdf4a6354708ddd7759f70c7fb51c5f9182"
dependencies = [
"buf_redux",
"httparse",
@@ -1432,7 +1438,7 @@ dependencies = [
"mime 0.3.16",
"mime_guess",
"quick-error 1.2.3",
- "rand 0.7.3",
+ "rand 0.8.4",
"safemem",
"tempfile",
"twoway",
@@ -1538,7 +1544,7 @@ checksum = "876a53fff98e03a936a674b29568b0e605f06b29372c2489ff4de23f1949743d"
dependencies = [
"proc-macro2 1.0.27",
"quote 1.0.9",
- "syn 1.0.72",
+ "syn 1.0.73",
]
[[package]]
@@ -1585,18 +1591,18 @@ dependencies = [
[[package]]
name = "object"
-version = "0.25.2"
+version = "0.25.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f8bc1d42047cf336f0f939c99e97183cf31551bf0f2865a2ec9c8d91fd4ffb5e"
+checksum = "a38f2be3697a57b4060074ff41b44c16870d916ad7877c17696e063257482bc7"
dependencies = [
"memchr",
]
[[package]]
name = "once_cell"
-version = "1.7.2"
+version = "1.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "af8b08b04175473088b46763e51ee54da5f9a164bc162f615b91bc179dbf15a3"
+checksum = "692fcb63b64b1758029e0a96ee63e049ce8c5948587f2f7208df04625e5f6b56"
[[package]]
name = "opaque-debug"
@@ -1816,7 +1822,7 @@ dependencies = [
"pest_meta",
"proc-macro2 1.0.27",
"quote 1.0.9",
- "syn 1.0.72",
+ "syn 1.0.73",
]
[[package]]
@@ -2053,14 +2059,14 @@ dependencies = [
[[package]]
name = "rand"
-version = "0.8.3"
+version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0ef9e7e66b4468674bfcb0c81af8b7fa0bb154fa9f28eb840da5c447baeb8d7e"
+checksum = "2e7573632e6454cf6b99d7aac4ccca54be06da05aca2ef7423d22d27d4d4bcd8"
dependencies = [
"libc",
- "rand_chacha 0.3.0",
- "rand_core 0.6.2",
- "rand_hc 0.3.0",
+ "rand_chacha 0.3.1",
+ "rand_core 0.6.3",
+ "rand_hc 0.3.1",
]
[[package]]
@@ -2075,12 +2081,12 @@ dependencies = [
[[package]]
name = "rand_chacha"
-version = "0.3.0"
+version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e12735cf05c9e10bf21534da50a147b924d555dc7a547c42e6bb2d5b6017ae0d"
+checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
dependencies = [
"ppv-lite86",
- "rand_core 0.6.2",
+ "rand_core 0.6.3",
]
[[package]]
@@ -2109,9 +2115,9 @@ dependencies = [
[[package]]
name = "rand_core"
-version = "0.6.2"
+version = "0.6.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "34cf66eb183df1c5876e2dcf6b13d57340741e8dc255b48e40a26de954d06ae7"
+checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7"
dependencies = [
"getrandom 0.2.3",
]
@@ -2127,11 +2133,11 @@ dependencies = [
[[package]]
name = "rand_hc"
-version = "0.3.0"
+version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3190ef7066a446f2e7f42e239d161e905420ccab01eb967c9eb27d21b2322a73"
+checksum = "d51e9f596de227fda2ea6c84607f5558e196eeaf43c986b724ba4fb8fdf497e7"
dependencies = [
- "rand_core 0.6.2",
+ "rand_core 0.6.3",
]
[[package]]
@@ -2154,9 +2160,9 @@ dependencies = [
[[package]]
name = "redox_syscall"
-version = "0.2.8"
+version = "0.2.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "742739e41cd49414de871ea5e549afb7e2a3ac77b589bcbebe8c82fab37147fc"
+checksum = "5ab49abadf3f9e1c4bc499e8845e152ad87d2ad2d30371841171169e9d75feee"
dependencies = [
"bitflags",
]
@@ -2216,7 +2222,7 @@ dependencies = [
"serde",
"serde_json",
"serde_urlencoded",
- "time 0.2.26",
+ "time 0.2.27",
"tokio",
"tokio-native-tls",
"tokio-socks",
@@ -2277,7 +2283,7 @@ dependencies = [
"rocket_codegen",
"rocket_http",
"state",
- "time 0.2.26",
+ "time 0.2.27",
"toml",
"version_check 0.9.3",
"yansi",
@@ -2322,7 +2328,7 @@ dependencies = [
"rustls",
"smallvec 1.6.1",
"state",
- "time 0.2.26",
+ "time 0.2.27",
"unicode-xid 0.2.2",
]
@@ -2464,6 +2470,25 @@ dependencies = [
]
[[package]]
+name = "serde_bytes"
+version = "0.11.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "16ae07dd2f88a366f15bd0632ba725227018c69a1c8550a927324f8eb8368bb9"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "serde_cbor"
+version = "0.11.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1e18acfa2f90e8b735b2836ab8d538de304cbb6729a7360729ea5a895d15a622"
+dependencies = [
+ "half",
+ "serde",
+]
+
+[[package]]
name = "serde_derive"
version = "1.0.126"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2471,7 +2496,7 @@ checksum = "963a7dbc9895aeac7ac90e74f34a5d5261828f79df35cbed41e10189d3804d43"
dependencies = [
"proc-macro2 1.0.27",
"quote 1.0.9",
- "syn 1.0.72",
+ "syn 1.0.73",
]
[[package]]
@@ -2653,7 +2678,7 @@ dependencies = [
"quote 1.0.9",
"serde",
"serde_derive",
- "syn 1.0.72",
+ "syn 1.0.73",
]
[[package]]
@@ -2669,7 +2694,7 @@ dependencies = [
"serde_derive",
"serde_json",
"sha1",
- "syn 1.0.72",
+ "syn 1.0.73",
]
[[package]]
@@ -2722,9 +2747,9 @@ dependencies = [
[[package]]
name = "syn"
-version = "1.0.72"
+version = "1.0.73"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a1e8cdbefb79a9a5a65e0db8b47b723ee907b7c7f8496c76a1770b5c310bab82"
+checksum = "f71489ff30030d2ae598524f61326b902466f72a0fb1a8564c001cc63425bcc7"
dependencies = [
"proc-macro2 1.0.27",
"quote 1.0.9",
@@ -2757,7 +2782,7 @@ checksum = "dac1c663cfc93810f88aed9b8941d48cabf856a1b111c29a40439018d870eb22"
dependencies = [
"cfg-if 1.0.0",
"libc",
- "rand 0.8.3",
+ "rand 0.8.4",
"redox_syscall",
"remove_dir_all",
"winapi 0.3.9",
@@ -2791,7 +2816,7 @@ checksum = "8a36768c0fbf1bb15eca10defa29526bda730a2376c2ab4393ccfa16fb1a318d"
dependencies = [
"proc-macro2 1.0.27",
"quote 1.0.9",
- "syn 1.0.72",
+ "syn 1.0.73",
]
[[package]]
@@ -2816,9 +2841,9 @@ dependencies = [
[[package]]
name = "time"
-version = "0.2.26"
+version = "0.2.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "08a8cbfbf47955132d0202d1662f49b2423ae35862aee471f3ba4b133358f372"
+checksum = "4752a97f8eebd6854ff91f1c1824cd6160626ac4bd44287f7f4ea2035a02a242"
dependencies = [
"const_fn",
"libc",
@@ -2841,15 +2866,15 @@ dependencies = [
[[package]]
name = "time-macros-impl"
-version = "0.1.1"
+version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e5c3be1edfad6027c69f5491cf4cb310d1a71ecd6af742788c6ff8bced86b8fa"
+checksum = "fd3c141a1b43194f3f56a1411225df8646c55781d5f26db825b3d98507eb482f"
dependencies = [
"proc-macro-hack",
"proc-macro2 1.0.27",
"quote 1.0.9",
"standback",
- "syn 1.0.72",
+ "syn 1.0.73",
]
[[package]]
@@ -2869,17 +2894,18 @@ checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c"
[[package]]
name = "tokio"
-version = "1.6.1"
+version = "1.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0a38d31d7831c6ed7aad00aa4c12d9375fd225a6dd77da1d25b707346319a975"
+checksum = "c79ba603c337335df6ba6dd6afc38c38a7d5e1b0c871678439ea973cd62a118e"
dependencies = [
"autocfg",
"bytes 1.0.1",
"libc",
"memchr",
- "mio 0.7.11",
+ "mio 0.7.13",
"num_cpus",
"pin-project-lite",
+ "winapi 0.3.9",
]
[[package]]
@@ -2954,7 +2980,7 @@ checksum = "c42e6fa53307c8a17e4ccd4dc81cf5ec38db9209f59b222210375b54ee40d1e2"
dependencies = [
"proc-macro2 1.0.27",
"quote 1.0.9",
- "syn 1.0.72",
+ "syn 1.0.73",
]
[[package]]
@@ -3150,7 +3176,7 @@ dependencies = [
"paste",
"percent-encoding 2.1.0",
"pico-args",
- "rand 0.8.3",
+ "rand 0.8.4",
"regex",
"reqwest",
"ring",
@@ -3160,11 +3186,12 @@ dependencies = [
"serde",
"serde_json",
"syslog",
- "time 0.2.26",
+ "time 0.2.27",
"tracing",
"u2f",
"url 2.2.2",
"uuid",
+ "webauthn-rs",
"yubico",
]
@@ -3242,7 +3269,7 @@ dependencies = [
"log 0.4.14",
"proc-macro2 1.0.27",
"quote 1.0.9",
- "syn 1.0.72",
+ "syn 1.0.73",
"wasm-bindgen-shared",
]
@@ -3276,7 +3303,7 @@ checksum = "be2241542ff3d9f241f5e2cb6dd09b37efe786df8851c54957683a49f0987a97"
dependencies = [
"proc-macro2 1.0.27",
"quote 1.0.9",
- "syn 1.0.72",
+ "syn 1.0.73",
"wasm-bindgen-backend",
"wasm-bindgen-shared",
]
@@ -3298,6 +3325,24 @@ dependencies = [
]
[[package]]
+name = "webauthn-rs"
+version = "0.3.0-alpha.7"
+source = "git+https://github.com/dani-garcia/webauthn-rs?rev=70b458f246d207e5b6333ada9f1d5794c5e01da1#70b458f246d207e5b6333ada9f1d5794c5e01da1"
+dependencies = [
+ "base64 0.13.0",
+ "log 0.4.14",
+ "nom 4.1.1",
+ "openssl",
+ "rand 0.8.4",
+ "serde",
+ "serde_bytes",
+ "serde_cbor",
+ "serde_derive",
+ "serde_json",
+ "thiserror",
+]
+
+[[package]]
name = "webpki"
version = "0.21.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -3412,7 +3457,7 @@ dependencies = [
"crypto-mac 0.10.0",
"futures",
"hmac 0.10.1",
- "rand 0.8.3",
+ "rand 0.8.4",
"reqwest",
"sha-1 0.9.6",
"threadpool",
diff --git a/Cargo.toml b/Cargo.toml
@@ -28,8 +28,8 @@ syslog = "4.0.1"
[dependencies]
# Web framework for nightly with a focus on ease-of-use, expressibility, and speed.
-rocket = { version = "0.5.0-dev", features = ["tls"], default-features = false }
-rocket_contrib = "0.5.0-dev"
+rocket = { version = "=0.5.0-dev", features = ["tls"], default-features = false }
+rocket_contrib = "=0.5.0-dev"
# HTTP client
reqwest = { version = "0.11.3", features = ["blocking", "json", "gzip", "brotli", "socks", "cookies"] }
@@ -41,7 +41,7 @@ bytes = "1.0.1"
url = "2.2.2"
# multipart/form-data support
-multipart = { version = "0.17.1", features = ["server"], default-features = false }
+multipart = { version = "0.18.0", features = ["server"], default-features = false }
# WebSockets library
ws = { version = "0.10.0", package = "parity-ws" }
@@ -77,7 +77,7 @@ uuid = { version = "0.8.2", features = ["v4"] }
# Date and time libraries
chrono = { version = "0.4.19", features = ["serde"] }
chrono-tz = "0.5.3"
-time = "0.2.26"
+time = "0.2.27"
# Job scheduler
job_scheduler = "1.2.1"
@@ -93,6 +93,7 @@ jsonwebtoken = "7.2.0"
# U2F library
u2f = "0.2.0"
+webauthn-rs = "0.3.0-alpha.7"
# Yubico Library
yubico = { version = "0.10.0", features = ["online-tokio"], default-features = false }
@@ -101,7 +102,7 @@ yubico = { version = "0.10.0", features = ["online-tokio"], default-features = f
dotenv = { version = "0.15.0", default-features = false }
# Lazy initialization
-once_cell = "1.7.2"
+once_cell = "1.8.0"
# Numerical libraries
num-traits = "0.2.14"
@@ -109,10 +110,10 @@ num-derive = "0.3.3"
# Email libraries
tracing = { version = "0.1.26", features = ["log"] } # Needed to have lettre trace logging used when SMTP_DEBUG is enabled.
-lettre = { version = "0.10.0-rc.1", features = ["smtp-transport", "builder", "serde", "native-tls", "hostname", "tracing"], default-features = false }
+lettre = { version = "0.10.0-rc.3", features = ["smtp-transport", "builder", "serde", "native-tls", "hostname", "tracing"], default-features = false }
# Template library
-handlebars = { version = "3.5.5", features = ["dir_source"] }
+handlebars = { version = "4.0.0", features = ["dir_source"] }
# For favicon extraction from main website
html5ever = "0.25.1"
@@ -129,10 +130,10 @@ percent-encoding = "2.1.0"
idna = "0.2.3"
# CLI argument parsing
-pico-args = "0.4.1"
+pico-args = "0.4.2"
# Logging panics to logfile instead stderr only
-backtrace = "0.3.59"
+backtrace = "0.3.60"
# Macro ident concatenation
paste = "1.0.5"
@@ -151,3 +152,6 @@ data-url = { git = 'https://github.com/servo/rust-url', package="data-url", rev
# In particular, `cron` has since implemented parsing of some common syntax
# that wasn't previously supported (https://github.com/zslayton/cron/pull/64).
job_scheduler = { git = 'https://github.com/jjlin/job_scheduler', rev = 'ee023418dbba2bfe1e30a5fd7d937f9e33739806' }
+
+# Add support for U2F appid extension compatibility
+webauthn-rs = { git = 'https://github.com/dani-garcia/webauthn-rs', rev = '70b458f246d207e5b6333ada9f1d5794c5e01da1' }
diff --git a/docker/Dockerfile.j2 b/docker/Dockerfile.j2
@@ -44,8 +44,8 @@
# https://docs.docker.com/develop/develop-images/multistage-build/
# https://whitfin.io/speeding-up-rust-docker-builds/
####################### VAULT BUILD IMAGE #######################
-{% set vault_version = "2.19.0d" %}
-{% set vault_image_digest = "sha256:a7bd6bc4db33bd45f723c4b1ac90918b7f80204560683cfc8efd9efd03a9b233" %}
+{% set vault_version = "2.20.4" %}
+{% set vault_image_digest = "sha256:810919341388a50d3a88225ce234333f72eb80382953997e9fd5590cca829e1b" %}
# The web-vault digest specifies a particular web-vault build on Docker Hub.
# Using the digest instead of the tag name provides better security,
# as the digest of an image is immutable, whereas a tag name can later
diff --git a/docker/amd64/Dockerfile b/docker/amd64/Dockerfile
@@ -14,15 +14,15 @@
# - From https://hub.docker.com/r/vaultwarden/web-vault/tags,
# click the tag name to view the digest of the image it currently points to.
# - From the command line:
-# $ docker pull vaultwarden/web-vault:v2.19.0d
-# $ docker image inspect --format "{{.RepoDigests}}" vaultwarden/web-vault:v2.19.0d
-# [vaultwarden/web-vault@sha256:a7bd6bc4db33bd45f723c4b1ac90918b7f80204560683cfc8efd9efd03a9b233]
+# $ docker pull vaultwarden/web-vault:v2.20.4
+# $ docker image inspect --format "{{.RepoDigests}}" vaultwarden/web-vault:v2.20.4
+# [vaultwarden/web-vault@sha256:810919341388a50d3a88225ce234333f72eb80382953997e9fd5590cca829e1b]
#
# - Conversely, to get the tag name from the digest:
-# $ docker image inspect --format "{{.RepoTags}}" vaultwarden/web-vault@sha256:a7bd6bc4db33bd45f723c4b1ac90918b7f80204560683cfc8efd9efd03a9b233
-# [vaultwarden/web-vault:v2.19.0d]
+# $ docker image inspect --format "{{.RepoTags}}" vaultwarden/web-vault@sha256:810919341388a50d3a88225ce234333f72eb80382953997e9fd5590cca829e1b
+# [vaultwarden/web-vault:v2.20.4]
#
-FROM vaultwarden/web-vault@sha256:a7bd6bc4db33bd45f723c4b1ac90918b7f80204560683cfc8efd9efd03a9b233 as vault
+FROM vaultwarden/web-vault@sha256:810919341388a50d3a88225ce234333f72eb80382953997e9fd5590cca829e1b as vault
########################## BUILD IMAGE ##########################
FROM rust:1.51 as build
diff --git a/docker/amd64/Dockerfile.alpine b/docker/amd64/Dockerfile.alpine
@@ -14,15 +14,15 @@
# - From https://hub.docker.com/r/vaultwarden/web-vault/tags,
# click the tag name to view the digest of the image it currently points to.
# - From the command line:
-# $ docker pull vaultwarden/web-vault:v2.19.0d
-# $ docker image inspect --format "{{.RepoDigests}}" vaultwarden/web-vault:v2.19.0d
-# [vaultwarden/web-vault@sha256:a7bd6bc4db33bd45f723c4b1ac90918b7f80204560683cfc8efd9efd03a9b233]
+# $ docker pull vaultwarden/web-vault:v2.20.4
+# $ docker image inspect --format "{{.RepoDigests}}" vaultwarden/web-vault:v2.20.4
+# [vaultwarden/web-vault@sha256:810919341388a50d3a88225ce234333f72eb80382953997e9fd5590cca829e1b]
#
# - Conversely, to get the tag name from the digest:
-# $ docker image inspect --format "{{.RepoTags}}" vaultwarden/web-vault@sha256:a7bd6bc4db33bd45f723c4b1ac90918b7f80204560683cfc8efd9efd03a9b233
-# [vaultwarden/web-vault:v2.19.0d]
+# $ docker image inspect --format "{{.RepoTags}}" vaultwarden/web-vault@sha256:810919341388a50d3a88225ce234333f72eb80382953997e9fd5590cca829e1b
+# [vaultwarden/web-vault:v2.20.4]
#
-FROM vaultwarden/web-vault@sha256:a7bd6bc4db33bd45f723c4b1ac90918b7f80204560683cfc8efd9efd03a9b233 as vault
+FROM vaultwarden/web-vault@sha256:810919341388a50d3a88225ce234333f72eb80382953997e9fd5590cca829e1b as vault
########################## BUILD IMAGE ##########################
FROM clux/muslrust:nightly-2021-04-14 as build
diff --git a/docker/arm64/Dockerfile b/docker/arm64/Dockerfile
@@ -14,15 +14,15 @@
# - From https://hub.docker.com/r/vaultwarden/web-vault/tags,
# click the tag name to view the digest of the image it currently points to.
# - From the command line:
-# $ docker pull vaultwarden/web-vault:v2.19.0d
-# $ docker image inspect --format "{{.RepoDigests}}" vaultwarden/web-vault:v2.19.0d
-# [vaultwarden/web-vault@sha256:a7bd6bc4db33bd45f723c4b1ac90918b7f80204560683cfc8efd9efd03a9b233]
+# $ docker pull vaultwarden/web-vault:v2.20.4
+# $ docker image inspect --format "{{.RepoDigests}}" vaultwarden/web-vault:v2.20.4
+# [vaultwarden/web-vault@sha256:810919341388a50d3a88225ce234333f72eb80382953997e9fd5590cca829e1b]
#
# - Conversely, to get the tag name from the digest:
-# $ docker image inspect --format "{{.RepoTags}}" vaultwarden/web-vault@sha256:a7bd6bc4db33bd45f723c4b1ac90918b7f80204560683cfc8efd9efd03a9b233
-# [vaultwarden/web-vault:v2.19.0d]
+# $ docker image inspect --format "{{.RepoTags}}" vaultwarden/web-vault@sha256:810919341388a50d3a88225ce234333f72eb80382953997e9fd5590cca829e1b
+# [vaultwarden/web-vault:v2.20.4]
#
-FROM vaultwarden/web-vault@sha256:a7bd6bc4db33bd45f723c4b1ac90918b7f80204560683cfc8efd9efd03a9b233 as vault
+FROM vaultwarden/web-vault@sha256:810919341388a50d3a88225ce234333f72eb80382953997e9fd5590cca829e1b as vault
########################## BUILD IMAGE ##########################
FROM rust:1.51 as build
diff --git a/docker/armv6/Dockerfile b/docker/armv6/Dockerfile
@@ -14,15 +14,15 @@
# - From https://hub.docker.com/r/vaultwarden/web-vault/tags,
# click the tag name to view the digest of the image it currently points to.
# - From the command line:
-# $ docker pull vaultwarden/web-vault:v2.19.0d
-# $ docker image inspect --format "{{.RepoDigests}}" vaultwarden/web-vault:v2.19.0d
-# [vaultwarden/web-vault@sha256:a7bd6bc4db33bd45f723c4b1ac90918b7f80204560683cfc8efd9efd03a9b233]
+# $ docker pull vaultwarden/web-vault:v2.20.4
+# $ docker image inspect --format "{{.RepoDigests}}" vaultwarden/web-vault:v2.20.4
+# [vaultwarden/web-vault@sha256:810919341388a50d3a88225ce234333f72eb80382953997e9fd5590cca829e1b]
#
# - Conversely, to get the tag name from the digest:
-# $ docker image inspect --format "{{.RepoTags}}" vaultwarden/web-vault@sha256:a7bd6bc4db33bd45f723c4b1ac90918b7f80204560683cfc8efd9efd03a9b233
-# [vaultwarden/web-vault:v2.19.0d]
+# $ docker image inspect --format "{{.RepoTags}}" vaultwarden/web-vault@sha256:810919341388a50d3a88225ce234333f72eb80382953997e9fd5590cca829e1b
+# [vaultwarden/web-vault:v2.20.4]
#
-FROM vaultwarden/web-vault@sha256:a7bd6bc4db33bd45f723c4b1ac90918b7f80204560683cfc8efd9efd03a9b233 as vault
+FROM vaultwarden/web-vault@sha256:810919341388a50d3a88225ce234333f72eb80382953997e9fd5590cca829e1b as vault
########################## BUILD IMAGE ##########################
FROM rust:1.51 as build
diff --git a/docker/armv7/Dockerfile b/docker/armv7/Dockerfile
@@ -14,15 +14,15 @@
# - From https://hub.docker.com/r/vaultwarden/web-vault/tags,
# click the tag name to view the digest of the image it currently points to.
# - From the command line:
-# $ docker pull vaultwarden/web-vault:v2.19.0d
-# $ docker image inspect --format "{{.RepoDigests}}" vaultwarden/web-vault:v2.19.0d
-# [vaultwarden/web-vault@sha256:a7bd6bc4db33bd45f723c4b1ac90918b7f80204560683cfc8efd9efd03a9b233]
+# $ docker pull vaultwarden/web-vault:v2.20.4
+# $ docker image inspect --format "{{.RepoDigests}}" vaultwarden/web-vault:v2.20.4
+# [vaultwarden/web-vault@sha256:810919341388a50d3a88225ce234333f72eb80382953997e9fd5590cca829e1b]
#
# - Conversely, to get the tag name from the digest:
-# $ docker image inspect --format "{{.RepoTags}}" vaultwarden/web-vault@sha256:a7bd6bc4db33bd45f723c4b1ac90918b7f80204560683cfc8efd9efd03a9b233
-# [vaultwarden/web-vault:v2.19.0d]
+# $ docker image inspect --format "{{.RepoTags}}" vaultwarden/web-vault@sha256:810919341388a50d3a88225ce234333f72eb80382953997e9fd5590cca829e1b
+# [vaultwarden/web-vault:v2.20.4]
#
-FROM vaultwarden/web-vault@sha256:a7bd6bc4db33bd45f723c4b1ac90918b7f80204560683cfc8efd9efd03a9b233 as vault
+FROM vaultwarden/web-vault@sha256:810919341388a50d3a88225ce234333f72eb80382953997e9fd5590cca829e1b as vault
########################## BUILD IMAGE ##########################
FROM rust:1.51 as build
diff --git a/docker/armv7/Dockerfile.alpine b/docker/armv7/Dockerfile.alpine
@@ -14,15 +14,15 @@
# - From https://hub.docker.com/r/vaultwarden/web-vault/tags,
# click the tag name to view the digest of the image it currently points to.
# - From the command line:
-# $ docker pull vaultwarden/web-vault:v2.19.0d
-# $ docker image inspect --format "{{.RepoDigests}}" vaultwarden/web-vault:v2.19.0d
-# [vaultwarden/web-vault@sha256:a7bd6bc4db33bd45f723c4b1ac90918b7f80204560683cfc8efd9efd03a9b233]
+# $ docker pull vaultwarden/web-vault:v2.20.4
+# $ docker image inspect --format "{{.RepoDigests}}" vaultwarden/web-vault:v2.20.4
+# [vaultwarden/web-vault@sha256:810919341388a50d3a88225ce234333f72eb80382953997e9fd5590cca829e1b]
#
# - Conversely, to get the tag name from the digest:
-# $ docker image inspect --format "{{.RepoTags}}" vaultwarden/web-vault@sha256:a7bd6bc4db33bd45f723c4b1ac90918b7f80204560683cfc8efd9efd03a9b233
-# [vaultwarden/web-vault:v2.19.0d]
+# $ docker image inspect --format "{{.RepoTags}}" vaultwarden/web-vault@sha256:810919341388a50d3a88225ce234333f72eb80382953997e9fd5590cca829e1b
+# [vaultwarden/web-vault:v2.20.4]
#
-FROM vaultwarden/web-vault@sha256:a7bd6bc4db33bd45f723c4b1ac90918b7f80204560683cfc8efd9efd03a9b233 as vault
+FROM vaultwarden/web-vault@sha256:810919341388a50d3a88225ce234333f72eb80382953997e9fd5590cca829e1b as vault
########################## BUILD IMAGE ##########################
FROM messense/rust-musl-cross:armv7-musleabihf as build
diff --git a/src/api/core/two_factor/mod.rs b/src/api/core/two_factor/mod.rs
@@ -17,6 +17,7 @@ pub mod authenticator;
pub mod duo;
pub mod email;
pub mod u2f;
+pub mod webauthn;
pub mod yubikey;
pub fn routes() -> Vec<Route> {
@@ -26,6 +27,7 @@ pub fn routes() -> Vec<Route> {
routes.append(&mut duo::routes());
routes.append(&mut email::routes());
routes.append(&mut u2f::routes());
+ routes.append(&mut webauthn::routes());
routes.append(&mut yubikey::routes());
routes
diff --git a/src/api/core/two_factor/u2f.rs b/src/api/core/two_factor/u2f.rs
@@ -94,13 +94,14 @@ struct RegistrationDef {
}
#[derive(Serialize, Deserialize)]
-struct U2FRegistration {
- id: i32,
- name: String,
+pub struct U2FRegistration {
+ pub id: i32,
+ pub name: String,
#[serde(with = "RegistrationDef")]
- reg: Registration,
- counter: u32,
+ pub reg: Registration,
+ pub counter: u32,
compromised: bool,
+ pub migrated: Option<bool>,
}
impl U2FRegistration {
@@ -168,6 +169,7 @@ fn activate_u2f(data: JsonUpcase<EnableU2FData>, headers: Headers, conn: DbConn)
reg: registration,
compromised: false,
counter: 0,
+ migrated: None,
};
let mut regs = get_u2f_registrations(&user.uuid, &conn)?.1;
@@ -273,6 +275,7 @@ fn get_u2f_registrations(user_uuid: &str, conn: &DbConn) -> Result<(bool, Vec<U2
reg: old_regs.remove(0),
compromised: false,
counter: 0,
+ migrated: None,
}];
// Save new format
diff --git a/src/api/core/two_factor/webauthn.rs b/src/api/core/two_factor/webauthn.rs
@@ -0,0 +1,394 @@
+use rocket::Route;
+use rocket_contrib::json::Json;
+use serde_json::Value;
+use webauthn_rs::{base64_data::Base64UrlSafeData, proto::*, AuthenticationState, RegistrationState, Webauthn};
+
+use crate::{
+ api::{
+ core::two_factor::_generate_recover_code, EmptyResult, JsonResult, JsonUpcase, NumberOrString, PasswordData,
+ },
+ auth::Headers,
+ db::{
+ models::{TwoFactor, TwoFactorType},
+ DbConn,
+ },
+ error::Error,
+ CONFIG,
+};
+
+pub fn routes() -> Vec<Route> {
+ routes![get_webauthn, generate_webauthn_challenge, activate_webauthn, activate_webauthn_put, delete_webauthn,]
+}
+
+struct WebauthnConfig {
+ url: String,
+ rpid: String,
+}
+
+impl WebauthnConfig {
+ fn load() -> Webauthn<Self> {
+ let domain = CONFIG.domain();
+ Webauthn::new(Self {
+ rpid: reqwest::Url::parse(&domain)
+ .map(|u| u.domain().map(str::to_owned))
+ .ok()
+ .flatten()
+ .unwrap_or_default(),
+ url: domain,
+ })
+ }
+}
+
+impl webauthn_rs::WebauthnConfig for WebauthnConfig {
+ fn get_relying_party_name(&self) -> &str {
+ &self.url
+ }
+
+ fn get_origin(&self) -> &str {
+ &self.url
+ }
+
+ fn get_relying_party_id(&self) -> &str {
+ &self.rpid
+ }
+}
+
+impl webauthn_rs::WebauthnConfig for &WebauthnConfig {
+ fn get_relying_party_name(&self) -> &str {
+ &self.url
+ }
+
+ fn get_origin(&self) -> &str {
+ &self.url
+ }
+
+ fn get_relying_party_id(&self) -> &str {
+ &self.rpid
+ }
+}
+
+#[derive(Debug, Serialize, Deserialize)]
+pub struct WebauthnRegistration {
+ pub id: i32,
+ pub name: String,
+ pub migrated: bool,
+
+ pub credential: Credential,
+}
+
+impl WebauthnRegistration {
+ fn to_json(&self) -> Value {
+ json!({
+ "Id": self.id,
+ "Name": self.name,
+ "migrated": self.migrated,
+ })
+ }
+}
+
+#[post("/two-factor/get-webauthn", data = "<data>")]
+fn get_webauthn(data: JsonUpcase<PasswordData>, headers: Headers, conn: DbConn) -> JsonResult {
+ if !CONFIG.domain_set() {
+ err!("`DOMAIN` environment variable is not set. Webauthn disabled")
+ }
+
+ if !headers.user.check_valid_password(&data.data.MasterPasswordHash) {
+ err!("Invalid password");
+ }
+
+ let (enabled, registrations) = get_webauthn_registrations(&headers.user.uuid, &conn)?;
+ let registrations_json: Vec<Value> = registrations.iter().map(WebauthnRegistration::to_json).collect();
+
+ Ok(Json(json!({
+ "Enabled": enabled,
+ "Keys": registrations_json,
+ "Object": "twoFactorWebAuthn"
+ })))
+}
+
+#[post("/two-factor/get-webauthn-challenge", data = "<data>")]
+fn generate_webauthn_challenge(data: JsonUpcase<PasswordData>, headers: Headers, conn: DbConn) -> JsonResult {
+ if !headers.user.check_valid_password(&data.data.MasterPasswordHash) {
+ err!("Invalid password");
+ }
+
+ let registrations = get_webauthn_registrations(&headers.user.uuid, &conn)?
+ .1
+ .into_iter()
+ .map(|r| r.credential.cred_id) // We return the credentialIds to the clients to avoid double registering
+ .collect();
+
+ let (challenge, state) = WebauthnConfig::load().generate_challenge_register_options(
+ headers.user.uuid.as_bytes().to_vec(),
+ headers.user.email,
+ headers.user.name,
+ Some(registrations),
+ None,
+ None,
+ )?;
+
+ let type_ = TwoFactorType::WebauthnRegisterChallenge;
+ TwoFactor::new(headers.user.uuid.clone(), type_, serde_json::to_string(&state)?).save(&conn)?;
+
+ let mut challenge_value = serde_json::to_value(challenge.public_key)?;
+ challenge_value["status"] = "ok".into();
+ challenge_value["errorMessage"] = "".into();
+ Ok(Json(challenge_value))
+}
+
+#[derive(Debug, Deserialize)]
+#[allow(non_snake_case)]
+struct EnableWebauthnData {
+ Id: NumberOrString, // 1..5
+ Name: String,
+ MasterPasswordHash: String,
+ DeviceResponse: RegisterPublicKeyCredentialCopy,
+}
+
+// This is copied from RegisterPublicKeyCredential to change the Response objects casing
+#[derive(Debug, Deserialize)]
+#[allow(non_snake_case)]
+struct RegisterPublicKeyCredentialCopy {
+ pub Id: String,
+ pub RawId: Base64UrlSafeData,
+ pub Response: AuthenticatorAttestationResponseRawCopy,
+ pub Type: String,
+}
+
+// This is copied from AuthenticatorAttestationResponseRaw to change clientDataJSON to clientDataJson
+#[derive(Debug, Deserialize)]
+#[allow(non_snake_case)]
+pub struct AuthenticatorAttestationResponseRawCopy {
+ pub AttestationObject: Base64UrlSafeData,
+ pub ClientDataJson: Base64UrlSafeData,
+}
+
+impl From<RegisterPublicKeyCredentialCopy> for RegisterPublicKeyCredential {
+ fn from(r: RegisterPublicKeyCredentialCopy) -> Self {
+ Self {
+ id: r.Id,
+ raw_id: r.RawId,
+ response: AuthenticatorAttestationResponseRaw {
+ attestation_object: r.Response.AttestationObject,
+ client_data_json: r.Response.ClientDataJson,
+ },
+ type_: r.Type,
+ }
+ }
+}
+
+// This is copied from PublicKeyCredential to change the Response objects casing
+#[derive(Debug, Deserialize)]
+#[allow(non_snake_case)]
+pub struct PublicKeyCredentialCopy {
+ pub Id: String,
+ pub RawId: Base64UrlSafeData,
+ pub Response: AuthenticatorAssertionResponseRawCopy,
+ pub Extensions: Option<AuthenticationExtensionsClientOutputsCopy>,
+ pub Type: String,
+}
+
+// This is copied from AuthenticatorAssertionResponseRaw to change clientDataJSON to clientDataJson
+#[derive(Debug, Deserialize)]
+#[allow(non_snake_case)]
+pub struct AuthenticatorAssertionResponseRawCopy {
+ pub AuthenticatorData: Base64UrlSafeData,
+ pub ClientDataJson: Base64UrlSafeData,
+ pub Signature: Base64UrlSafeData,
+ pub UserHandle: Option<Base64UrlSafeData>,
+}
+
+#[derive(Debug, Deserialize)]
+#[allow(non_snake_case)]
+pub struct AuthenticationExtensionsClientOutputsCopy {
+ #[serde(default)]
+ pub Appid: bool,
+}
+
+impl From<PublicKeyCredentialCopy> for PublicKeyCredential {
+ fn from(r: PublicKeyCredentialCopy) -> Self {
+ Self {
+ id: r.Id,
+ raw_id: r.RawId,
+ response: AuthenticatorAssertionResponseRaw {
+ authenticator_data: r.Response.AuthenticatorData,
+ client_data_json: r.Response.ClientDataJson,
+ signature: r.Response.Signature,
+ user_handle: r.Response.UserHandle,
+ },
+ extensions: r.Extensions.map(|e| AuthenticationExtensionsClientOutputs {
+ appid: e.Appid,
+ }),
+ type_: r.Type,
+ }
+ }
+}
+
+#[post("/two-factor/webauthn", data = "<data>")]
+fn activate_webauthn(data: JsonUpcase<EnableWebauthnData>, headers: Headers, conn: DbConn) -> JsonResult {
+ let data: EnableWebauthnData = data.into_inner().data;
+ let mut user = headers.user;
+
+ if !user.check_valid_password(&data.MasterPasswordHash) {
+ err!("Invalid password");
+ }
+
+ // Retrieve and delete the saved challenge state
+ let type_ = TwoFactorType::WebauthnRegisterChallenge as i32;
+ let state = match TwoFactor::find_by_user_and_type(&user.uuid, type_, &conn) {
+ Some(tf) => {
+ let state: RegistrationState = serde_json::from_str(&tf.data)?;
+ tf.delete(&conn)?;
+ state
+ }
+ None => err!("Can't recover challenge"),
+ };
+
+ // Verify the credentials with the saved state
+ let (credential, _data) =
+ WebauthnConfig::load().register_credential(&data.DeviceResponse.into(), &state, |_| Ok(false))?;
+
+ let mut registrations: Vec<_> = get_webauthn_registrations(&user.uuid, &conn)?.1;
+ // TODO: Check for repeated ID's
+ registrations.push(WebauthnRegistration {
+ id: data.Id.into_i32()?,
+ name: data.Name,
+ migrated: false,
+
+ credential,
+ });
+
+ // Save the registrations and return them
+ TwoFactor::new(user.uuid.clone(), TwoFactorType::Webauthn, serde_json::to_string(®istrations)?).save(&conn)?;
+ _generate_recover_code(&mut user, &conn);
+
+ let keys_json: Vec<Value> = registrations.iter().map(WebauthnRegistration::to_json).collect();
+ Ok(Json(json!({
+ "Enabled": true,
+ "Keys": keys_json,
+ "Object": "twoFactorU2f"
+ })))
+}
+
+#[put("/two-factor/webauthn", data = "<data>")]
+fn activate_webauthn_put(data: JsonUpcase<EnableWebauthnData>, headers: Headers, conn: DbConn) -> JsonResult {
+ activate_webauthn(data, headers, conn)
+}
+
+#[derive(Deserialize, Debug)]
+#[allow(non_snake_case)]
+struct DeleteU2FData {
+ Id: NumberOrString,
+ MasterPasswordHash: String,
+}
+
+#[delete("/two-factor/webauthn", data = "<data>")]
+fn delete_webauthn(data: JsonUpcase<DeleteU2FData>, headers: Headers, conn: DbConn) -> JsonResult {
+ let id = data.data.Id.into_i32()?;
+ if !headers.user.check_valid_password(&data.data.MasterPasswordHash) {
+ err!("Invalid password");
+ }
+
+ let type_ = TwoFactorType::Webauthn as i32;
+ let mut tf = match TwoFactor::find_by_user_and_type(&headers.user.uuid, type_, &conn) {
+ Some(tf) => tf,
+ None => err!("Webauthn data not found!"),
+ };
+
+ let mut data: Vec<WebauthnRegistration> = serde_json::from_str(&tf.data)?;
+
+ let item_pos = match data.iter().position(|r| r.id != id) {
+ Some(p) => p,
+ None => err!("Webauthn entry not found"),
+ };
+
+ let removed_item = data.remove(item_pos);
+ tf.data = serde_json::to_string(&data)?;
+ tf.save(&conn)?;
+ drop(tf);
+
+ // If entry is migrated from u2f, delete the u2f entry as well
+ if let Some(mut u2f) = TwoFactor::find_by_user_and_type(&headers.user.uuid, TwoFactorType::U2f as i32, &conn) {
+ use crate::api::core::two_factor::u2f::U2FRegistration;
+ let mut data: Vec<U2FRegistration> = match serde_json::from_str(&u2f.data) {
+ Ok(d) => d,
+ Err(_) => err!("Error parsing U2F data"),
+ };
+
+ data.retain(|r| r.reg.key_handle != removed_item.credential.cred_id);
+ let new_data_str = serde_json::to_string(&data)?;
+
+ u2f.data = new_data_str;
+ u2f.save(&conn)?;
+ }
+
+ let keys_json: Vec<Value> = data.iter().map(WebauthnRegistration::to_json).collect();
+
+ Ok(Json(json!({
+ "Enabled": true,
+ "Keys": keys_json,
+ "Object": "twoFactorU2f"
+ })))
+}
+
+pub fn get_webauthn_registrations(user_uuid: &str, conn: &DbConn) -> Result<(bool, Vec<WebauthnRegistration>), Error> {
+ let type_ = TwoFactorType::Webauthn as i32;
+ match TwoFactor::find_by_user_and_type(user_uuid, type_, conn) {
+ Some(tf) => Ok((tf.enabled, serde_json::from_str(&tf.data)?)),
+ None => Ok((false, Vec::new())), // If no data, return empty list
+ }
+}
+
+pub fn generate_webauthn_login(user_uuid: &str, conn: &DbConn) -> JsonResult {
+ // Load saved credentials
+ let creds: Vec<Credential> =
+ get_webauthn_registrations(user_uuid, conn)?.1.into_iter().map(|r| r.credential).collect();
+
+ if creds.is_empty() {
+ err!("No Webauthn devices registered")
+ }
+
+ // Generate a challenge based on the credentials
+ let ext = RequestAuthenticationExtensions::builder().appid(format!("{}/app-id.json", &CONFIG.domain())).build();
+ let (response, state) = WebauthnConfig::load().generate_challenge_authenticate_options(creds, Some(ext))?;
+
+ // Save the challenge state for later validation
+ TwoFactor::new(user_uuid.into(), TwoFactorType::WebauthnLoginChallenge, serde_json::to_string(&state)?)
+ .save(&conn)?;
+
+ // Return challenge to the clients
+ Ok(Json(serde_json::to_value(response.public_key)?))
+}
+
+pub fn validate_webauthn_login(user_uuid: &str, response: &str, conn: &DbConn) -> EmptyResult {
+ let type_ = TwoFactorType::WebauthnLoginChallenge as i32;
+ let state = match TwoFactor::find_by_user_and_type(user_uuid, type_, conn) {
+ Some(tf) => {
+ let state: AuthenticationState = serde_json::from_str(&tf.data)?;
+ tf.delete(&conn)?;
+ state
+ }
+ None => err!("Can't recover login challenge"),
+ };
+
+ let rsp: crate::util::UpCase<PublicKeyCredentialCopy> = serde_json::from_str(response)?;
+ let rsp: PublicKeyCredential = rsp.data.into();
+
+ let mut registrations = get_webauthn_registrations(user_uuid, conn)?.1;
+
+ // If the credential we received is migrated from U2F, enable the U2F compatibility
+ //let use_u2f = registrations.iter().any(|r| r.migrated && r.credential.cred_id == rsp.raw_id.0);
+ let (cred_id, auth_data) = WebauthnConfig::load().authenticate_credential(&rsp, &state)?;
+
+ for reg in &mut registrations {
+ if ®.credential.cred_id == cred_id {
+ reg.credential.counter = auth_data.counter;
+
+ TwoFactor::new(user_uuid.to_string(), TwoFactorType::Webauthn, serde_json::to_string(®istrations)?)
+ .save(&conn)?;
+ return Ok(());
+ }
+ }
+
+ err!("Credential not present")
+}
diff --git a/src/api/identity.rs b/src/api/identity.rs
@@ -240,6 +240,7 @@ fn twofactor_auth(
_tf::authenticator::validate_totp_code_str(user_uuid, twofactor_code, &selected_data?, ip, conn)?
}
Some(TwoFactorType::U2f) => _tf::u2f::validate_u2f_login(user_uuid, twofactor_code, conn)?,
+ Some(TwoFactorType::Webauthn) => _tf::webauthn::validate_webauthn_login(user_uuid, twofactor_code, conn)?,
Some(TwoFactorType::YubiKey) => _tf::yubikey::validate_yubikey_login(twofactor_code, &selected_data?)?,
Some(TwoFactorType::Duo) => {
_tf::duo::validate_duo_login(data.username.as_ref().unwrap(), twofactor_code, conn)?
@@ -309,6 +310,11 @@ fn _json_err_twofactor(providers: &[i32], user_uuid: &str, conn: &DbConn) -> Api
});
}
+ Some(TwoFactorType::Webauthn) if CONFIG.domain_set() => {
+ let request = two_factor::webauthn::generate_webauthn_login(user_uuid, conn)?;
+ result["TwoFactorProviders2"][provider.to_string()] = request.0;
+ }
+
Some(TwoFactorType::Duo) => {
let email = match User::find_by_uuid(user_uuid, &conn) {
Some(u) => u.email,
diff --git a/src/api/mod.rs b/src/api/mod.rs
@@ -51,10 +51,10 @@ impl NumberOrString {
}
}
- fn into_i32(self) -> ApiResult<i32> {
+ fn into_i32(&self) -> ApiResult<i32> {
use std::num::ParseIntError as PIE;
match self {
- NumberOrString::Number(n) => Ok(n),
+ NumberOrString::Number(n) => Ok(*n),
NumberOrString::String(s) => {
s.parse().map_err(|e: PIE| crate::Error::new("Can't convert to number", e.to_string()))
}
diff --git a/src/db/mod.rs b/src/db/mod.rs
@@ -114,7 +114,7 @@ macro_rules! db_run {
};
// Different code for each db
- ( $conn:ident: $( $($db:ident),+ $body:block )+ ) => {
+ ( $conn:ident: $( $($db:ident),+ $body:block )+ ) => {{
#[allow(unused)] use diesel::prelude::*;
match $conn {
$($(
@@ -128,7 +128,7 @@ macro_rules! db_run {
$body
},
)+)+
- }
+ }}
};
// Same for all dbs
diff --git a/src/db/models/two_factor.rs b/src/db/models/two_factor.rs
@@ -31,11 +31,14 @@ pub enum TwoFactorType {
U2f = 4,
Remember = 5,
OrganizationDuo = 6,
+ Webauthn = 7,
// These are implementation details
U2fRegisterChallenge = 1000,
U2fLoginChallenge = 1001,
EmailVerificationChallenge = 1002,
+ WebauthnRegisterChallenge = 1003,
+ WebauthnLoginChallenge = 1004,
}
/// Local methods
@@ -146,4 +149,73 @@ impl TwoFactor {
.map_res("Error deleting twofactors")
}}
}
+
+ pub fn migrate_u2f_to_webauthn(conn: &DbConn) -> EmptyResult {
+ let u2f_factors = db_run! { conn: {
+ twofactor::table
+ .filter(twofactor::atype.eq(TwoFactorType::U2f as i32))
+ .load::<TwoFactorDb>(conn)
+ .expect("Error loading twofactor")
+ .from_db()
+ }};
+
+ use crate::api::core::two_factor::u2f::U2FRegistration;
+ use crate::api::core::two_factor::webauthn::{get_webauthn_registrations, WebauthnRegistration};
+ use std::convert::TryInto;
+ use webauthn_rs::proto::*;
+
+ for mut u2f in u2f_factors {
+ let mut regs: Vec<U2FRegistration> = serde_json::from_str(&u2f.data)?;
+ // If there are no registrations or they are migrated (we do the migration in batch so we can consider them all migrated when the first one is)
+ if regs.is_empty() || regs[0].migrated == Some(true) {
+ continue;
+ }
+
+ let (_, mut webauthn_regs) = get_webauthn_registrations(&u2f.user_uuid, &conn)?;
+
+ // If the user already has webauthn registrations saved, don't overwrite them
+ if !webauthn_regs.is_empty() {
+ continue;
+ }
+
+ for reg in &mut regs {
+ let x: [u8; 32] = reg.reg.pub_key[1..33].try_into().unwrap();
+ let y: [u8; 32] = reg.reg.pub_key[33..65].try_into().unwrap();
+
+ let key = COSEKey {
+ type_: COSEAlgorithm::ES256,
+ key: COSEKeyType::EC_EC2(COSEEC2Key {
+ curve: ECDSACurve::SECP256R1,
+ x,
+ y,
+ }),
+ };
+
+ let new_reg = WebauthnRegistration {
+ id: reg.id,
+ migrated: true,
+ name: reg.name.clone(),
+ credential: Credential {
+ counter: reg.counter,
+ verified: false,
+ cred: key,
+ cred_id: reg.reg.key_handle.clone(),
+ registration_policy: UserVerificationPolicy::Discouraged,
+ },
+ };
+
+ webauthn_regs.push(new_reg);
+
+ reg.migrated = Some(true);
+ }
+
+ u2f.data = serde_json::to_string(®s)?;
+ u2f.save(&conn)?;
+
+ TwoFactor::new(u2f.user_uuid.clone(), TwoFactorType::Webauthn, serde_json::to_string(&webauthn_regs)?)
+ .save(&conn)?;
+ }
+
+ Ok(())
+ }
}
diff --git a/src/error.rs b/src/error.rs
@@ -39,19 +39,18 @@ use diesel::ConnectionError as DieselConErr;
use diesel_migrations::RunMigrationsError as DieselMigErr;
use handlebars::RenderError as HbErr;
use jsonwebtoken::errors::Error as JwtErr;
+use lettre::address::AddressError as AddrErr;
+use lettre::error::Error as LettreErr;
+use lettre::transport::smtp::Error as SmtpErr;
use regex::Error as RegexErr;
use reqwest::Error as ReqErr;
use serde_json::{Error as SerdeErr, Value};
use std::io::Error as IoErr;
-
use std::time::SystemTimeError as TimeErr;
use u2f::u2ferror::U2fError as U2fErr;
+use webauthn_rs::error::WebauthnError as WebauthnErr;
use yubico::yubicoerror::YubicoError as YubiErr;
-use lettre::address::AddressError as AddrErr;
-use lettre::error::Error as LettreErr;
-use lettre::transport::smtp::Error as SmtpErr;
-
#[derive(Serialize)]
pub struct Empty {}
@@ -86,6 +85,7 @@ make_error! {
DieselConError(DieselConErr): _has_source, _api_error,
DieselMigError(DieselMigErr): _has_source, _api_error,
+ WebauthnError(WebauthnErr): _has_source, _api_error,
}
impl std::fmt::Debug for Error {
diff --git a/src/main.rs b/src/main.rs
@@ -60,6 +60,8 @@ fn main() {
let pool = create_db_pool();
schedule_jobs(pool.clone());
+ crate::db::models::TwoFactor::migrate_u2f_to_webauthn(&pool.get().unwrap()).unwrap();
+
launch_rocket(pool, extra_debug); // Blocks until program termination.
}