Spiegel von
https://github.com/dani-garcia/vaultwarden.git
synchronisiert 2024-11-22 05:10:29 +01:00
Merge pull request #254 from Step7750/master
Adds Yubikey OTP Support
Dieser Commit ist enthalten in:
Commit
c5c9e3fd65
8 geänderte Dateien mit 394 neuen und 1 gelöschten Zeilen
8
.env
8
.env
|
@ -40,6 +40,14 @@
|
|||
## For U2F to work, the server must use HTTPS, you can use Let's Encrypt for free certs
|
||||
# DOMAIN=https://bw.domain.tld:8443
|
||||
|
||||
## Yubico (Yubikey) Settings
|
||||
## Set your Client ID and Secret Key for Yubikey OTP
|
||||
## You can generate it here: https://upgrade.yubico.com/getapikey/
|
||||
## You can optionally specify a custom OTP server
|
||||
# YUBICO_CLIENT_ID=11111
|
||||
# YUBICO_SECRET_KEY=AAAAAAAAAAAAAAAAAAAAAAAA
|
||||
# YUBICO_SERVER=http://yourdomain.com/wsapi/2.0/verify
|
||||
|
||||
## Rocket specific settings, check Rocket documentation to learn more
|
||||
# ROCKET_ENV=staging
|
||||
# ROCKET_ADDRESS=0.0.0.0 # Enable this to test mobile app
|
||||
|
|
124
Cargo.lock
generiert
124
Cargo.lock
generiert
|
@ -81,6 +81,14 @@ dependencies = [
|
|||
"safemem 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "base64"
|
||||
version = "0.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"byteorder 1.2.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "1.0.4"
|
||||
|
@ -119,6 +127,7 @@ dependencies = [
|
|||
"u2f 0.1.2 (git+https://github.com/wisespace-io/u2f-rs?rev=193de35093a44)",
|
||||
"uuid 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"ws 0.7.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"yubico 0.4.0 (git+https://github.com/dani-garcia/yubico-rs)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -1348,6 +1357,16 @@ dependencies = [
|
|||
"scheduled-thread-pool 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand"
|
||||
version = "0.3.22"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rand 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand"
|
||||
version = "0.4.3"
|
||||
|
@ -1370,6 +1389,33 @@ dependencies = [
|
|||
"winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand"
|
||||
version = "0.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rand_chacha 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rand_core 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rand_hc 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rand_isaac 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rand_pcg 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rand_xorshift 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_chacha"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"rand_core 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_core"
|
||||
version = "0.2.2"
|
||||
|
@ -1383,6 +1429,39 @@ name = "rand_core"
|
|||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "rand_hc"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"rand_core 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_isaac"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"rand_core 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_pcg"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"rand_core 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_xorshift"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"rand_core 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rayon"
|
||||
version = "0.7.1"
|
||||
|
@ -1559,6 +1638,18 @@ dependencies = [
|
|||
"serde_json 1.0.31 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rust-crypto"
|
||||
version = "0.2.36"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"gcc 0.3.54 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rand 0.3.22 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rustc-serialize 0.3.24 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"time 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustc-demangle"
|
||||
version = "0.1.9"
|
||||
|
@ -1872,6 +1963,14 @@ dependencies = [
|
|||
"lazy_static 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "threadpool"
|
||||
version = "1.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"num_cpus 1.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "time"
|
||||
version = "0.1.40"
|
||||
|
@ -2319,6 +2418,20 @@ name = "yansi"
|
|||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "yubico"
|
||||
version = "0.4.0"
|
||||
source = "git+https://github.com/dani-garcia/yubico-rs#f93a88834e4ebaaed0169850ff1a5e32a7d01719"
|
||||
dependencies = [
|
||||
"base64 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rand 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"reqwest 0.9.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rust-crypto 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"threadpool 1.7.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"url 1.7.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[metadata]
|
||||
"checksum adler32 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "7e522997b529f05601e05166c07ed17789691f562762c7f3b987263d2dedee5c"
|
||||
"checksum aho-corasick 0.6.8 (registry+https://github.com/rust-lang/crates.io-index)" = "68f56c7353e5a9547cbd76ed90f7bb5ffc3ba09d4ea9bd1d8c06c8b1142eeb5a"
|
||||
|
@ -2328,6 +2441,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
"checksum ascii_utils 0.9.3 (registry+https://github.com/rust-lang/crates.io-index)" = "71938f30533e4d95a6d17aa530939da3842c2ab6f4f84b9dae68447e4129f74a"
|
||||
"checksum backtrace 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)" = "89a47830402e9981c5c41223151efcced65a0510c13097c769cede7efb34782a"
|
||||
"checksum backtrace-sys 0.1.24 (registry+https://github.com/rust-lang/crates.io-index)" = "c66d56ac8dabd07f6aacdaf633f4b8262f5b3601a810a0dcddffd5c22c69daa0"
|
||||
"checksum base64 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "621fc7ecb8008f86d7fb9b95356cd692ce9514b80a86d85b397f32a22da7b9e2"
|
||||
"checksum base64 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "30e93c03064e7590d0466209155251b90c22e37fab1daf2771582598b5827557"
|
||||
"checksum base64 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "96434f987501f0ed4eb336a411e0631ecd1afa11574fe148587adc4ff96143c9"
|
||||
"checksum base64 0.9.3 (registry+https://github.com/rust-lang/crates.io-index)" = "489d6c0ed21b11d038c31b6ceccca973e65d73ba3bd8ecb9a2babf5546164643"
|
||||
|
@ -2470,10 +2584,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
"checksum quote 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "9949cfe66888ffe1d53e6ec9d9f3b70714083854be20fd5e271b232a017401e8"
|
||||
"checksum quote 0.6.8 (registry+https://github.com/rust-lang/crates.io-index)" = "dd636425967c33af890042c483632d33fa7a18f19ad1d7ea72e8998c6ef8dea5"
|
||||
"checksum r2d2 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f9078ca6a8a5568ed142083bb2f7dc9295b69d16f867ddcc9849e51b17d8db46"
|
||||
"checksum rand 0.3.22 (registry+https://github.com/rust-lang/crates.io-index)" = "15a732abf9d20f0ad8eeb6f909bf6868722d9a06e1e50802b6a70351f40b4eb1"
|
||||
"checksum rand 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)" = "8356f47b32624fef5b3301c1be97e5944ecdd595409cc5da11d05f211db6cfbd"
|
||||
"checksum rand 0.5.5 (registry+https://github.com/rust-lang/crates.io-index)" = "e464cd887e869cddcae8792a4ee31d23c7edd516700695608f5b98c67ee0131c"
|
||||
"checksum rand 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "de3f08319b5395bd19b70e73c4c465329495db02dafeb8ca711a20f1c2bd058c"
|
||||
"checksum rand_chacha 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "771b009e3a508cb67e8823dda454aaa5368c7bc1c16829fb77d3e980440dd34a"
|
||||
"checksum rand_core 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "1961a422c4d189dfb50ffa9320bf1f2a9bd54ecb92792fb9477f99a1045f3372"
|
||||
"checksum rand_core 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "0905b6b7079ec73b314d4c748701f6931eb79fd97c668caa3f1899b22b32c6db"
|
||||
"checksum rand_hc 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7b40677c7be09ae76218dc623efbf7b18e34bced3f38883af07bb75630a21bc4"
|
||||
"checksum rand_isaac 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2d6ecfe9ebf36acd47a49d150990b047a5f7db0a7236ee2414b7ff5cc1097c7b"
|
||||
"checksum rand_pcg 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "086bd09a33c7044e56bb44d5bdde5a60e7f119a9e95b0775f545de759a32fe05"
|
||||
"checksum rand_xorshift 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "effa3fcaa47e18db002bdde6060944b6d2f9cfd8db471c30e873448ad9187be3"
|
||||
"checksum rayon 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a77c51c07654ddd93f6cb543c7a849863b03abc7e82591afda6dc8ad4ac3ac4a"
|
||||
"checksum rayon-core 1.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b055d1e92aba6877574d8fe604a63c8b5df60f60e5982bf7ccbb1338ea527356"
|
||||
"checksum redox_syscall 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)" = "c214e91d3ecf43e9a4e41e578973adeb14b474f2bee858742d127af75a0112b1"
|
||||
|
@ -2489,6 +2610,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
"checksum rocket 0.3.17 (registry+https://github.com/rust-lang/crates.io-index)" = "a61d746c68f1d357f6e011985570474c4af368aa81900320074098d34ed0c64e"
|
||||
"checksum rocket_codegen 0.3.17 (registry+https://github.com/rust-lang/crates.io-index)" = "7873d65adfa3e440ac373a28240341853da170913aad7e4207c0198389e5d0e9"
|
||||
"checksum rocket_contrib 0.3.17 (registry+https://github.com/rust-lang/crates.io-index)" = "4b2348e3b2458e173203f1e51f3b2e00495a092b70bd9506d2ce2ac64e129d14"
|
||||
"checksum rust-crypto 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)" = "f76d05d3993fd5f4af9434e8e436db163a12a9d40e1a58a726f27a01dfd12a2a"
|
||||
"checksum rustc-demangle 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "bcfe5b13211b4d78e5c2cadfebd7769197d95c639c35a50057eb4c05de811395"
|
||||
"checksum rustc-hex 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "0ceb8ce7a5e520de349e1fa172baeba4a9e8d5ef06c47471863530bc4972ee1e"
|
||||
"checksum rustc-serialize 0.3.24 (registry+https://github.com/rust-lang/crates.io-index)" = "dcf128d1287d2ea9d80910b5f1120d0b8eede3fbf1abe91c40d39ea7d51e6fda"
|
||||
|
@ -2528,6 +2650,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
"checksum tempfile 3.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "55c1195ef8513f3273d55ff59fe5da6940287a0d7a98331254397f464833675b"
|
||||
"checksum thread-id 3.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c7fbf4c9d56b320106cd64fd024dadfa0be7cb4706725fc44a7d7ce952d820c1"
|
||||
"checksum thread_local 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "c6b53e329000edc2b34dbe8545fd20e55a333362d0a321909685a19bd28c3f1b"
|
||||
"checksum threadpool 1.7.1 (registry+https://github.com/rust-lang/crates.io-index)" = "e2f0c90a5f3459330ac8bc0d2f879c693bb7a2f59689c1083fc4ef83834da865"
|
||||
"checksum time 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)" = "d825be0eb33fda1a7e68012d51e9c7f451dc1a69391e7fdc197060bb8c56667b"
|
||||
"checksum tiny_http 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a442681f9f72e440be192700eeb2861e4174b9983f16f4877c93a134cb5e5f63"
|
||||
"checksum tokio 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)" = "6e93c78d23cc61aa245a8acd2c4a79c4d7fa7fb5c3ca90d5737029f043a84895"
|
||||
|
@ -2579,3 +2702,4 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
"checksum ws 0.7.8 (registry+https://github.com/rust-lang/crates.io-index)" = "d2c221321dca56e6a80aa179d562e1fbe6ae116aeaa9205c76fa64e9e3c49dfc"
|
||||
"checksum ws2_32-sys 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d59cefebd0c892fa2dd6de581e937301d8552cb44489cdff035c6187cb63fa5e"
|
||||
"checksum yansi 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d60c3b48c9cdec42fb06b3b84b5b087405e1fa1c644a1af3930e4dfafe93de48"
|
||||
"checksum yubico 0.4.0 (git+https://github.com/dani-garcia/yubico-rs)" = "<none>"
|
||||
|
|
|
@ -57,6 +57,9 @@ jsonwebtoken = "= 4.0.1"
|
|||
# U2F library
|
||||
u2f = "0.1.2"
|
||||
|
||||
# Yubico Library
|
||||
yubico= { version = "0.4.0", default-features = false }
|
||||
|
||||
# A `dotenv` implementation for Rust
|
||||
dotenv = { version = "0.13.0", default-features = false }
|
||||
|
||||
|
@ -84,3 +87,6 @@ lettre_email = { git = 'https://github.com/lettre/lettre', rev = 'c988b1760ad81'
|
|||
|
||||
# Version 0.1.2 from crates.io lacks a commit that fixes a certificate error
|
||||
u2f = { git = 'https://github.com/wisespace-io/u2f-rs', rev = '193de35093a44' }
|
||||
|
||||
# Allows optional libusb support
|
||||
yubico = { git = 'https://github.com/dani-garcia/yubico-rs' }
|
||||
|
|
19
README.md
19
README.md
|
@ -28,6 +28,7 @@ _*Note, that this project is not associated with the [Bitwarden](https://bitward
|
|||
- [Enabling HTTPS](#enabling-https)
|
||||
- [Enabling WebSocket notifications](#enabling-websocket-notifications)
|
||||
- [Enabling U2F authentication](#enabling-u2f-authentication)
|
||||
- [Enabling YubiKey OTP authentication](#enabling-yubikey-otp-authentication)
|
||||
- [Changing persistent data location](#changing-persistent-data-location)
|
||||
- [/data prefix:](#data-prefix)
|
||||
- [database name and location](#database-name-and-location)
|
||||
|
@ -68,11 +69,11 @@ Basically full implementation of Bitwarden API is provided including:
|
|||
* Serving the static files for Vault interface
|
||||
* Website icons API
|
||||
* Authenticator and U2F support
|
||||
* YubiKey OTP
|
||||
|
||||
## Missing features
|
||||
* Email confirmation
|
||||
* Other two-factor systems:
|
||||
* YubiKey OTP (if your key supports U2F, you can use that)
|
||||
* Duo
|
||||
* Email codes
|
||||
|
||||
|
@ -252,6 +253,22 @@ docker run -d --name bitwarden \
|
|||
|
||||
Note that the value has to include the `https://` and it may include a port at the end (in the format of `https://bw.domain.tld:port`) when not using `443`.
|
||||
|
||||
### Enabling YubiKey OTP authentication
|
||||
To enable YubiKey authentication, you must set the `YUBICO_CLIENT_ID` and `YUBICO_SECRET_KEY` env variables.
|
||||
|
||||
If `YUBICO_SERVER` is not specified, it will use the default YubiCloud servers. You can generate `YUBICO_CLIENT_ID` and `YUBICO_SECRET_KEY` for the default YubiCloud [here](https://upgrade.yubico.com/getapikey/).
|
||||
|
||||
Note: In order to generate API keys or use a YubiKey with an OTP server, it must be registered. After configuring your key in the [YubiKey Personalization Tool](https://www.yubico.com/products/services-software/personalization-tools/use/), you can register it with the default servers [here](https://upload.yubico.com/).
|
||||
|
||||
```sh
|
||||
docker run -d --name bitwarden \
|
||||
-e YUBICO_CLIENT_ID=12345 \
|
||||
-e YUBICO_SECRET_KEY=ABCDEABCDEABCDEABCDE= \
|
||||
-v /bw-data/:/data/ \
|
||||
-p 80:80 \
|
||||
mprasil/bitwarden:latest
|
||||
```
|
||||
|
||||
### Changing persistent data location
|
||||
|
||||
#### /data prefix:
|
||||
|
|
|
@ -83,6 +83,9 @@ pub fn routes() -> Vec<Route> {
|
|||
generate_u2f_challenge,
|
||||
activate_u2f,
|
||||
activate_u2f_put,
|
||||
generate_yubikey,
|
||||
activate_yubikey,
|
||||
activate_yubikey_put,
|
||||
|
||||
get_organization,
|
||||
create_organization,
|
||||
|
|
|
@ -491,3 +491,218 @@ pub fn validate_u2f_login(user_uuid: &str, response: &str, conn: &DbConn) -> Api
|
|||
}
|
||||
err!("error verifying response")
|
||||
}
|
||||
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
#[allow(non_snake_case)]
|
||||
struct EnableYubikeyData {
|
||||
MasterPasswordHash: String,
|
||||
Key1: Option<String>,
|
||||
Key2: Option<String>,
|
||||
Key3: Option<String>,
|
||||
Key4: Option<String>,
|
||||
Key5: Option<String>,
|
||||
Nfc: bool,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, Debug)]
|
||||
#[allow(non_snake_case)]
|
||||
struct YubikeyMetadata {
|
||||
Keys: Vec<String>,
|
||||
Nfc: bool,
|
||||
}
|
||||
|
||||
use yubico::Yubico;
|
||||
use yubico::config::Config;
|
||||
|
||||
fn parse_yubikeys(data: &EnableYubikeyData) -> Vec<String> {
|
||||
let mut yubikeys: Vec<String> = Vec::new();
|
||||
|
||||
if data.Key1.is_some() {
|
||||
yubikeys.push(data.Key1.as_ref().unwrap().to_owned());
|
||||
}
|
||||
|
||||
if data.Key2.is_some() {
|
||||
yubikeys.push(data.Key2.as_ref().unwrap().to_owned());
|
||||
}
|
||||
|
||||
if data.Key3.is_some() {
|
||||
yubikeys.push(data.Key3.as_ref().unwrap().to_owned());
|
||||
}
|
||||
|
||||
if data.Key4.is_some() {
|
||||
yubikeys.push(data.Key4.as_ref().unwrap().to_owned());
|
||||
}
|
||||
|
||||
if data.Key5.is_some() {
|
||||
yubikeys.push(data.Key5.as_ref().unwrap().to_owned());
|
||||
}
|
||||
|
||||
yubikeys
|
||||
}
|
||||
|
||||
fn jsonify_yubikeys(yubikeys: Vec<String>) -> serde_json::Value {
|
||||
let mut result = json!({});
|
||||
|
||||
for i in 0..yubikeys.len() {
|
||||
let ref key = &yubikeys[i];
|
||||
result[format!("Key{}", i+1)] = Value::String(key.to_string());
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
fn verify_yubikey_otp(otp: String) -> JsonResult {
|
||||
if !CONFIG.yubico_cred_set {
|
||||
err!("`YUBICO_CLIENT_ID` or `YUBICO_SECRET_KEY` environment variable is not set. \
|
||||
Yubikey OTP Disabled")
|
||||
}
|
||||
|
||||
let yubico = Yubico::new();
|
||||
let config = Config::default().set_client_id(CONFIG.yubico_client_id.to_owned()).set_key(CONFIG.yubico_secret_key.to_owned());
|
||||
|
||||
let result = match CONFIG.yubico_server {
|
||||
Some(ref server) => yubico.verify(otp, config.set_api_hosts(vec![server.to_owned()])),
|
||||
None => yubico.verify(otp, config)
|
||||
};
|
||||
|
||||
match result {
|
||||
Ok(_answer) => Ok(Json(json!({}))),
|
||||
Err(_e) => err!("Failed to verify OTP"),
|
||||
}
|
||||
}
|
||||
|
||||
#[post("/two-factor/get-yubikey", data = "<data>")]
|
||||
fn generate_yubikey(data: JsonUpcase<PasswordData>, headers: Headers, conn: DbConn) -> JsonResult {
|
||||
if !CONFIG.yubico_cred_set {
|
||||
err!("`YUBICO_CLIENT_ID` or `YUBICO_SECRET_KEY` environment variable is not set. \
|
||||
Yubikey OTP Disabled")
|
||||
}
|
||||
|
||||
let data: PasswordData = data.into_inner().data;
|
||||
|
||||
if !headers.user.check_valid_password(&data.MasterPasswordHash) {
|
||||
err!("Invalid password");
|
||||
}
|
||||
|
||||
let user_uuid = &headers.user.uuid;
|
||||
let yubikey_type = TwoFactorType::YubiKey as i32;
|
||||
|
||||
let r = TwoFactor::find_by_user_and_type(user_uuid, yubikey_type, &conn);
|
||||
|
||||
if let Some(r) = r {
|
||||
let yubikey_metadata: YubikeyMetadata =
|
||||
serde_json::from_str(&r.data).expect("Can't parse YubikeyMetadata data");
|
||||
|
||||
let mut result = jsonify_yubikeys(yubikey_metadata.Keys);
|
||||
|
||||
result["Enabled"] = Value::Bool(true);
|
||||
result["Nfc"] = Value::Bool(yubikey_metadata.Nfc);
|
||||
result["Object"] = Value::String("twoFactorU2f".to_owned());
|
||||
|
||||
Ok(Json(result))
|
||||
} else {
|
||||
Ok(Json(json!({
|
||||
"Enabled": false,
|
||||
"Object": "twoFactorU2f",
|
||||
})))
|
||||
}
|
||||
}
|
||||
|
||||
#[post("/two-factor/yubikey", data = "<data>")]
|
||||
fn activate_yubikey(data: JsonUpcase<EnableYubikeyData>, headers: Headers, conn: DbConn) -> JsonResult {
|
||||
let data: EnableYubikeyData = data.into_inner().data;
|
||||
|
||||
if !headers.user.check_valid_password(&data.MasterPasswordHash) {
|
||||
err!("Invalid password");
|
||||
}
|
||||
|
||||
// Check if we already have some data
|
||||
let yubikey_data = TwoFactor::find_by_user_and_type(
|
||||
&headers.user.uuid,
|
||||
TwoFactorType::YubiKey as i32,
|
||||
&conn,
|
||||
);
|
||||
|
||||
if let Some(yubikey_data) = yubikey_data {
|
||||
yubikey_data.delete(&conn).expect("Error deleting current Yubikeys");
|
||||
}
|
||||
|
||||
let yubikeys = parse_yubikeys(&data);
|
||||
|
||||
if yubikeys.len() == 0 {
|
||||
return Ok(Json(json!({
|
||||
"Enabled": false,
|
||||
"Object": "twoFactorU2f",
|
||||
})));
|
||||
}
|
||||
|
||||
// Ensure they are valid OTPs
|
||||
for yubikey in &yubikeys {
|
||||
if yubikey.len() == 12 {
|
||||
// YubiKey ID
|
||||
continue
|
||||
}
|
||||
|
||||
let result = verify_yubikey_otp(yubikey.to_owned());
|
||||
|
||||
if let Err(_e) = result {
|
||||
err!("Invalid Yubikey OTP provided");
|
||||
}
|
||||
}
|
||||
|
||||
let yubikey_ids: Vec<String> = yubikeys.into_iter().map(|x| (&x[..12]).to_owned()).collect();
|
||||
|
||||
let yubikey_metadata = YubikeyMetadata {
|
||||
Keys: yubikey_ids,
|
||||
Nfc: data.Nfc,
|
||||
};
|
||||
|
||||
let yubikey_registration = TwoFactor::new(
|
||||
headers.user.uuid.clone(),
|
||||
TwoFactorType::YubiKey,
|
||||
serde_json::to_string(&yubikey_metadata).unwrap(),
|
||||
);
|
||||
yubikey_registration
|
||||
.save(&conn).expect("Failed to save Yubikey info");
|
||||
|
||||
let mut result = jsonify_yubikeys(yubikey_metadata.Keys);
|
||||
|
||||
result["Enabled"] = Value::Bool(true);
|
||||
result["Nfc"] = Value::Bool(yubikey_metadata.Nfc);
|
||||
result["Object"] = Value::String("twoFactorU2f".to_owned());
|
||||
|
||||
Ok(Json(result))
|
||||
}
|
||||
|
||||
#[put("/two-factor/yubikey", data = "<data>")]
|
||||
fn activate_yubikey_put(data: JsonUpcase<EnableYubikeyData>, headers: Headers, conn: DbConn) -> JsonResult {
|
||||
activate_yubikey(data, headers, conn)
|
||||
}
|
||||
|
||||
pub fn validate_yubikey_login(user_uuid: &str, response: &str, conn: &DbConn) -> ApiResult<()> {
|
||||
if response.len() != 44 {
|
||||
err!("Invalid Yubikey OTP length");
|
||||
}
|
||||
|
||||
let yubikey_type = TwoFactorType::YubiKey as i32;
|
||||
|
||||
let twofactor = match TwoFactor::find_by_user_and_type(user_uuid, yubikey_type, &conn) {
|
||||
Some(tf) => tf,
|
||||
None => err!("No YubiKey devices registered"),
|
||||
};
|
||||
|
||||
let yubikey_metadata: YubikeyMetadata = serde_json::from_str(&twofactor.data).expect("Can't parse Yubikey Metadata");
|
||||
let response_id = &response[..12];
|
||||
|
||||
if !yubikey_metadata.Keys.contains(&response_id.to_owned()) {
|
||||
err!("Given Yubikey is not registered");
|
||||
}
|
||||
|
||||
let result = verify_yubikey_otp(response.to_owned());
|
||||
|
||||
match result {
|
||||
Ok(_answer) => Ok(()),
|
||||
Err(_e) => err!("Failed to verify Yubikey against OTP server"),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -209,6 +209,12 @@ fn twofactor_auth(
|
|||
two_factor::validate_u2f_login(user_uuid, twofactor_code, conn)?;
|
||||
}
|
||||
|
||||
Some(TwoFactorType::YubiKey) => {
|
||||
use api::core::two_factor;
|
||||
|
||||
two_factor::validate_yubikey_login(user_uuid, twofactor_code, conn)?;
|
||||
}
|
||||
|
||||
_ => err!("Invalid two factor provider"),
|
||||
}
|
||||
|
||||
|
|
14
src/main.rs
14
src/main.rs
|
@ -25,6 +25,7 @@ extern crate oath;
|
|||
extern crate data_encoding;
|
||||
extern crate jsonwebtoken as jwt;
|
||||
extern crate u2f;
|
||||
extern crate yubico;
|
||||
extern crate dotenv;
|
||||
#[macro_use]
|
||||
extern crate lazy_static;
|
||||
|
@ -245,6 +246,11 @@ pub struct Config {
|
|||
domain: String,
|
||||
domain_set: bool,
|
||||
|
||||
yubico_cred_set: bool,
|
||||
yubico_client_id: String,
|
||||
yubico_secret_key: String,
|
||||
yubico_server: Option<String>,
|
||||
|
||||
mail: Option<MailConfig>,
|
||||
}
|
||||
|
||||
|
@ -258,6 +264,9 @@ impl Config {
|
|||
|
||||
let domain = get_env("DOMAIN");
|
||||
|
||||
let yubico_client_id = get_env("YUBICO_CLIENT_ID");
|
||||
let yubico_secret_key = get_env("YUBICO_SECRET_KEY");
|
||||
|
||||
Config {
|
||||
database_url: get_env_or("DATABASE_URL", format!("{}/{}", &df, "db.sqlite3")),
|
||||
icon_cache_folder: get_env_or("ICON_CACHE_FOLDER", format!("{}/{}", &df, "icon_cache")),
|
||||
|
@ -283,6 +292,11 @@ impl Config {
|
|||
domain_set: domain.is_some(),
|
||||
domain: domain.unwrap_or("http://localhost".into()),
|
||||
|
||||
yubico_cred_set: yubico_client_id.is_some() && yubico_secret_key.is_some(),
|
||||
yubico_client_id: yubico_client_id.unwrap_or("00000".into()),
|
||||
yubico_secret_key: yubico_secret_key.unwrap_or("AAAAAAA".into()),
|
||||
yubico_server: get_env("YUBICO_SERVER"),
|
||||
|
||||
mail: MailConfig::load(),
|
||||
}
|
||||
}
|
||||
|
|
Laden …
In neuem Issue referenzieren