commit c5c9e3fd65df3624e259818b6c23c2816bb8bb66
parent f9ecb7201b0059dbe142ff6744c08550bd5b0dcf
Author: Daniel GarcĂa <dani-garcia@users.noreply.github.com>
Date: Fri, 16 Nov 2018 20:39:03 +0100
Merge pull request #254 from Step7750/master
Adds Yubikey OTP Support
Diffstat:
8 files changed, 394 insertions(+), 1 deletion(-)
diff --git a/.env b/.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
diff --git a/Cargo.lock b/Cargo.lock
@@ -82,6 +82,14 @@ dependencies = [
]
[[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"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -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]]
@@ -1350,6 +1359,16 @@ dependencies = [
[[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"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
@@ -1371,6 +1390,33 @@ dependencies = [
]
[[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"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1384,6 +1430,39 @@ 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"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1560,6 +1639,18 @@ dependencies = [
]
[[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"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1873,6 +1964,14 @@ dependencies = [
]
[[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"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -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>"
diff --git a/Cargo.toml b/Cargo.toml
@@ -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' }
diff --git a/README.md b/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:
diff --git a/src/api/core/mod.rs b/src/api/core/mod.rs
@@ -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,
diff --git a/src/api/core/two_factor.rs b/src/api/core/two_factor.rs
@@ -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"),
+ }
+}
diff --git a/src/api/identity.rs b/src/api/identity.rs
@@ -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"),
}
diff --git a/src/main.rs b/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(),
}
}