Browse Source

update webauthn-rs crate

pull/4485/head
Stefan Melmuk 1 year ago
parent
commit
6c3109d517
No known key found for this signature in database GPG Key ID: 817020C608FE9C09
  1. 286
      Cargo.lock
  2. 3
      Cargo.toml
  3. 162
      src/api/core/two_factor/webauthn.rs
  4. 18
      src/config.rs
  5. 21
      src/db/models/two_factor.rs
  6. 4
      src/error.rs
  7. 2
      src/main.rs

286
Cargo.lock

@ -86,6 +86,45 @@ dependencies = [
"password-hash",
]
[[package]]
name = "asn1-rs"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "30ff05a702273012438132f449575dbc804e27b2f3cbe3069aa237d26c98fa33"
dependencies = [
"asn1-rs-derive",
"asn1-rs-impl",
"displaydoc",
"nom",
"num-traits",
"rusticata-macros",
"thiserror",
"time",
]
[[package]]
name = "asn1-rs-derive"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "db8b7511298d5b7784b40b092d9e9dcd3a627a5707e4b5e507931ab0d44eeebf"
dependencies = [
"proc-macro2",
"quote",
"syn 1.0.109",
"synstructure",
]
[[package]]
name = "asn1-rs-impl"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2777730b2039ac0f95f093556e61b6d26cebed5393ca6f152717777cec3a42ed"
dependencies = [
"proc-macro2",
"quote",
"syn 1.0.109",
]
[[package]]
name = "async-channel"
version = "1.9.0"
@ -291,7 +330,7 @@ checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn 2.0.72",
]
[[package]]
@ -308,7 +347,7 @@ checksum = "6e0c28dcc82d7c8ead5cb13beb15405b57b8546e93215673ff8ca0349a028107"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn 2.0.72",
]
[[package]]
@ -377,6 +416,28 @@ version = "1.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b"
[[package]]
name = "base64urlsafedata"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "18b3d30abb74120a9d5267463b9e0045fdccc4dd152e7249d966612dc1721384"
dependencies = [
"base64 0.21.7",
"serde",
"serde_json",
]
[[package]]
name = "base64urlsafedata"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a56894edf5cd1efa7068d7454adeb7ce0b3da4ffa5ab08cfc06165bbc62f0c7"
dependencies = [
"base64 0.21.7",
"paste",
"serde",
]
[[package]]
name = "bigdecimal"
version = "0.4.5"
@ -511,7 +572,7 @@ dependencies = [
"darling",
"proc-macro2",
"quote",
"syn",
"syn 2.0.72",
]
[[package]]
@ -577,6 +638,23 @@ dependencies = [
"stacker",
]
[[package]]
name = "compact_jwt"
version = "0.2.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7aa76ef19968577838a34d02848136bb9b6bdbfd7675fb968fe9c931bc434b33"
dependencies = [
"base64 0.13.1",
"base64urlsafedata 0.1.3",
"hex",
"openssl",
"serde",
"serde_json",
"tracing",
"url",
"uuid",
]
[[package]]
name = "concurrent-queue"
version = "2.5.0"
@ -696,7 +774,7 @@ dependencies = [
"proc-macro2",
"quote",
"strsim",
"syn",
"syn 2.0.72",
]
[[package]]
@ -707,7 +785,7 @@ checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806"
dependencies = [
"darling_core",
"quote",
"syn",
"syn 2.0.72",
]
[[package]]
@ -749,6 +827,20 @@ version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c297a1c74b71ae29df00c3e22dd9534821d60eb9af5a0192823fa2acea70c2a"
[[package]]
name = "der-parser"
version = "7.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fe398ac75057914d7d07307bf67dc7f3f574a26783b4fc7805a20ffa9f506e82"
dependencies = [
"asn1-rs",
"displaydoc",
"nom",
"num-bigint",
"num-traits",
"rusticata-macros",
]
[[package]]
name = "deranged"
version = "0.3.11"
@ -788,7 +880,7 @@ dependencies = [
"proc-macro2",
"proc-macro2-diagnostics",
"quote",
"syn",
"syn 2.0.72",
]
[[package]]
@ -825,7 +917,7 @@ dependencies = [
"dsl_auto_type",
"proc-macro2",
"quote",
"syn",
"syn 2.0.72",
]
[[package]]
@ -855,7 +947,7 @@ version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "209c735641a413bc68c4923a9d6ad4bcb3ca306b794edaa7eb0b3228a99ffb25"
dependencies = [
"syn",
"syn 2.0.72",
]
[[package]]
@ -869,6 +961,17 @@ dependencies = [
"subtle",
]
[[package]]
name = "displaydoc"
version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.72",
]
[[package]]
name = "dotenvy"
version = "0.15.7"
@ -886,7 +989,7 @@ dependencies = [
"heck 0.5.0",
"proc-macro2",
"quote",
"syn",
"syn 2.0.72",
]
[[package]]
@ -932,7 +1035,7 @@ dependencies = [
"heck 0.4.1",
"proc-macro2",
"quote",
"syn",
"syn 2.0.72",
]
[[package]]
@ -1163,7 +1266,7 @@ checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn 2.0.72",
]
[[package]]
@ -1375,6 +1478,12 @@ version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fbf6a919d6cf397374f7dfeeea91d974c7c0a7221d0d0f4f20d859d329e53fcc"
[[package]]
name = "hex"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
[[package]]
name = "hickory-proto"
version = "0.24.1"
@ -2145,7 +2254,7 @@ checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn 2.0.72",
]
[[package]]
@ -2194,6 +2303,15 @@ dependencies = [
"memchr",
]
[[package]]
name = "oid-registry"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "38e20717fa0541f39bd146692035c37bedfa532b3e5071b35761082407546b2a"
dependencies = [
"asn1-rs",
]
[[package]]
name = "once_cell"
version = "1.19.0"
@ -2223,7 +2341,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn 2.0.72",
]
[[package]]
@ -2335,7 +2453,7 @@ dependencies = [
"proc-macro2",
"proc-macro2-diagnostics",
"quote",
"syn",
"syn 2.0.72",
]
[[package]]
@ -2385,7 +2503,7 @@ dependencies = [
"pest_meta",
"proc-macro2",
"quote",
"syn",
"syn 2.0.72",
]
[[package]]
@ -2460,7 +2578,7 @@ checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn 2.0.72",
]
[[package]]
@ -2567,7 +2685,7 @@ checksum = "af066a9c399a26e020ada66a034357a868728e72cd426f3adcd35f80d88d88c8"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn 2.0.72",
"version_check",
"yansi",
]
@ -2709,7 +2827,7 @@ checksum = "bcc303e793d3734489387d205e9b186fac9c6cfacedd98cbb2e8a5943595f3e6"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn 2.0.72",
]
[[package]]
@ -2952,7 +3070,7 @@ dependencies = [
"proc-macro2",
"quote",
"rocket_http",
"syn",
"syn 2.0.72",
"unicode-xid",
"version_check",
]
@ -3024,6 +3142,15 @@ version = "0.1.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f"
[[package]]
name = "rusticata-macros"
version = "4.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "faf0c4a6ece9950b9abdb62b1cfcf2a68b3b67a10ba445b3bb85be2a293d0632"
dependencies = [
"nom",
]
[[package]]
name = "rustix"
version = "0.37.27"
@ -3222,10 +3349,10 @@ dependencies = [
]
[[package]]
name = "serde_cbor"
version = "0.11.2"
name = "serde_cbor_2"
version = "0.12.0-dev"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2bef2ebfde456fb76bbcf9f59315333decc4fda0b2b44b420243c11e0f5ec1f5"
checksum = "b46d75f449e01f1eddbe9b00f432d616fbbd899b809c837d0fbc380496a0dd55"
dependencies = [
"half",
"serde",
@ -3239,7 +3366,7 @@ checksum = "e0cd7e117be63d3c3678776753929474f3b04a43a080c744d6b0ae2a8c28e222"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn 2.0.72",
]
[[package]]
@ -3435,6 +3562,17 @@ version = "2.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
[[package]]
name = "syn"
version = "1.0.109"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "syn"
version = "2.0.72"
@ -3458,6 +3596,18 @@ version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a7065abeca94b6a8a577f9bd45aa0867a2238b74e8eb67cf10d492bc39351394"
[[package]]
name = "synstructure"
version = "0.12.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f36bdaa60a83aca3921b5259d5400cbf5e90fc51931376a9bd4a0eb79aa7210f"
dependencies = [
"proc-macro2",
"quote",
"syn 1.0.109",
"unicode-xid",
]
[[package]]
name = "syslog"
version = "6.1.1"
@ -3521,7 +3671,7 @@ checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn 2.0.72",
]
[[package]]
@ -3617,7 +3767,7 @@ checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn 2.0.72",
]
[[package]]
@ -3792,7 +3942,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn 2.0.72",
]
[[package]]
@ -3948,6 +4098,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "81dfa00651efa65069b0b6b651f4aaa31ba9e3c3ce0137aaad053604ee7e0314"
dependencies = [
"getrandom",
"serde",
]
[[package]]
@ -4020,6 +4171,7 @@ dependencies = [
"url",
"uuid",
"webauthn-rs",
"webauthn-rs-core",
"which",
"yubico",
]
@ -4088,7 +4240,7 @@ dependencies = [
"once_cell",
"proc-macro2",
"quote",
"syn",
"syn 2.0.72",
"wasm-bindgen-shared",
]
@ -4122,7 +4274,7 @@ checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn 2.0.72",
"wasm-bindgen-backend",
"wasm-bindgen-shared",
]
@ -4166,23 +4318,71 @@ dependencies = [
"wasm-bindgen",
]
[[package]]
name = "webauthn-attestation-ca"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b0f2ebaf5650ca15b515a761f31ed6477fa2312491cf632a71102ac22b82784"
dependencies = [
"base64urlsafedata 0.5.0",
"openssl",
"serde",
"tracing",
"uuid",
]
[[package]]
name = "webauthn-rs"
version = "0.3.2"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "90b266eccb4b32595876f5c73ea443b0516da0b1df72ca07bc08ed9ba7f96ec1"
checksum = "fb9d7cdc9ec26e3e06f7e8ee1433e6fa3627c6c075ab3effbc3a2280c2f526c0"
dependencies = [
"base64 0.13.1",
"base64urlsafedata 0.5.0",
"serde",
"tracing",
"url",
"uuid",
"webauthn-rs-core",
]
[[package]]
name = "webauthn-rs-core"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf1ee1dc7f4138b8fd05a74a6eae93ddaf504c5a60861f1eb95d9de3172900b3"
dependencies = [
"base64 0.21.7",
"base64urlsafedata 0.5.0",
"compact_jwt",
"der-parser",
"hex",
"nom",
"openssl",
"rand",
"rand_chacha",
"serde",
"serde_cbor",
"serde_derive",
"serde_cbor_2",
"serde_json",
"thiserror",
"tracing",
"url",
"uuid",
"webauthn-attestation-ca",
"webauthn-rs-proto",
"x509-parser",
]
[[package]]
name = "webauthn-rs-proto"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1f1c6dc254607f48eec3bdb35b86b377202436859ca1e4c9290afafd7349dcc3"
dependencies = [
"base64 0.21.7",
"base64urlsafedata 0.5.0",
"serde",
"serde_json",
"url",
]
[[package]]
@ -4436,6 +4636,24 @@ version = "0.0.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d135d17ab770252ad95e9a872d365cf3090e3be864a34ab46f48555993efc904"
[[package]]
name = "x509-parser"
version = "0.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9fb9bace5b5589ffead1afb76e43e34cff39cd0f3ce7e170ae0c29e53b88eb1c"
dependencies = [
"asn1-rs",
"base64 0.13.1",
"data-encoding",
"der-parser",
"lazy_static",
"nom",
"oid-registry",
"rusticata-macros",
"thiserror",
"time",
]
[[package]]
name = "yansi"
version = "1.0.1"
@ -4478,7 +4696,7 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn 2.0.72",
]
[[package]]

3
Cargo.toml

@ -109,7 +109,8 @@ totp-lite = "2.0.1"
yubico = { version = "0.11.0", features = ["online-tokio"], default-features = false }
# WebAuthn libraries
webauthn-rs = "0.3.2"
webauthn-rs = { version = "0.5.0", features = ["danger-allow-state-serialisation", "danger-credential-internals"] }
webauthn-rs-core = { version = "0.5.0" }
# Handling of URL's for WebAuthn and favicons
url = "2.5.2"

162
src/api/core/two_factor/webauthn.rs

@ -1,8 +1,7 @@
use rocket::serde::json::Json;
use rocket::Route;
use serde_json::Value;
use url::Url;
use webauthn_rs::{base64_data::Base64UrlSafeData, proto::*, AuthenticationState, RegistrationState, Webauthn};
use webauthn_rs::prelude::*;
use crate::{
api::{
@ -16,7 +15,7 @@ use crate::{
},
error::Error,
util::NumberOrString,
CONFIG,
CONFIG, WEBAUTHN,
};
pub fn routes() -> Vec<Route> {
@ -28,7 +27,7 @@ pub fn routes() -> Vec<Route> {
#[derive(Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Registration {
pub key_handle: Vec<u8>,
pub key_handle: CredentialID,
pub pub_key: Vec<u8>,
pub attestation_cert: Option<Vec<u8>>,
pub device_name: Option<String>,
@ -45,45 +44,6 @@ pub struct U2FRegistration {
pub migrated: Option<bool>,
}
struct WebauthnConfig {
url: String,
origin: Url,
rpid: String,
}
impl WebauthnConfig {
fn load() -> Webauthn<Self> {
let domain = CONFIG.domain();
let domain_origin = CONFIG.domain_origin();
Webauthn::new(Self {
rpid: Url::parse(&domain).map(|u| u.domain().map(str::to_owned)).ok().flatten().unwrap_or_default(),
url: domain,
origin: Url::parse(&domain_origin).unwrap(),
})
}
}
impl webauthn_rs::WebauthnConfig for WebauthnConfig {
fn get_relying_party_name(&self) -> &str {
&self.url
}
fn get_origin(&self) -> &Url {
&self.origin
}
fn get_relying_party_id(&self) -> &str {
&self.rpid
}
/// We have WebAuthn configured to discourage user verification
/// if we leave this enabled, it will cause verification issues when a keys send UV=1.
/// Upstream (the library they use) ignores this when set to discouraged, so we should too.
fn get_require_uv_consistency(&self) -> bool {
false
}
}
#[derive(Debug, Serialize, Deserialize)]
pub struct WebauthnRegistration {
pub id: i32,
@ -131,21 +91,16 @@ async fn generate_webauthn_challenge(data: Json<PasswordOrOtpData>, headers: Hea
data.validate(&user, false, &mut conn).await?;
let registrations = get_webauthn_registrations(&user.uuid, &mut conn)
let registrations: Vec<CredentialID> = get_webauthn_registrations(&user.uuid, &mut conn)
.await?
.1
.into_iter()
.map(|r| r.credential.cred_id) // We return the credentialIds to the clients to avoid double registering
.map(|r| r.credential.cred_id.clone()) // We return the credentialIds to the clients to avoid double registering
.collect();
let (challenge, state) = WebauthnConfig::load().generate_challenge_register_options(
user.uuid.as_bytes().to_vec(),
user.email,
user.name,
Some(registrations),
None,
None,
)?;
let user_uuid = Uuid::parse_str(&user.uuid)?;
let (challenge, state) =
WEBAUTHN.start_securitykey_registration(user_uuid, &user.email, &user.name, Some(registrations), None, None)?;
let type_ = TwoFactorType::WebauthnRegisterChallenge;
TwoFactor::new(user.uuid, type_, serde_json::to_string(&state)?).save(&mut conn).await?;
@ -161,82 +116,11 @@ async fn generate_webauthn_challenge(data: Json<PasswordOrOtpData>, headers: Hea
struct EnableWebauthnData {
id: NumberOrString, // 1..5
name: String,
device_response: RegisterPublicKeyCredentialCopy,
device_response: RegisterPublicKeyCredential,
master_password_hash: Option<String>,
otp: Option<String>,
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
struct RegisterPublicKeyCredentialCopy {
pub id: String,
pub raw_id: Base64UrlSafeData,
pub response: AuthenticatorAttestationResponseRawCopy,
pub r#type: String,
}
// This is copied from AuthenticatorAttestationResponseRaw to change clientDataJSON to clientDataJson
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct AuthenticatorAttestationResponseRawCopy {
#[serde(rename = "AttestationObject", alias = "attestationObject")]
pub attestation_object: Base64UrlSafeData,
#[serde(rename = "clientDataJson", alias = "clientDataJSON")]
pub client_data_json: Base64UrlSafeData,
}
impl From<RegisterPublicKeyCredentialCopy> for RegisterPublicKeyCredential {
fn from(r: RegisterPublicKeyCredentialCopy) -> Self {
Self {
id: r.id,
raw_id: r.raw_id,
response: AuthenticatorAttestationResponseRaw {
attestation_object: r.response.attestation_object,
client_data_json: r.response.client_data_json,
},
type_: r.r#type,
}
}
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct PublicKeyCredentialCopy {
pub id: String,
pub raw_id: Base64UrlSafeData,
pub response: AuthenticatorAssertionResponseRawCopy,
pub extensions: Option<AuthenticationExtensionsClientOutputs>,
pub r#type: String,
}
// This is copied from AuthenticatorAssertionResponseRaw to change clientDataJSON to clientDataJson
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct AuthenticatorAssertionResponseRawCopy {
pub authenticator_data: Base64UrlSafeData,
#[serde(rename = "clientDataJson", alias = "clientDataJSON")]
pub client_data_json: Base64UrlSafeData,
pub signature: Base64UrlSafeData,
pub user_handle: Option<Base64UrlSafeData>,
}
impl From<PublicKeyCredentialCopy> for PublicKeyCredential {
fn from(r: PublicKeyCredentialCopy) -> Self {
Self {
id: r.id,
raw_id: r.raw_id,
response: AuthenticatorAssertionResponseRaw {
authenticator_data: r.response.authenticator_data,
client_data_json: r.response.client_data_json,
signature: r.response.signature,
user_handle: r.response.user_handle,
},
extensions: r.extensions,
type_: r.r#type,
}
}
}
#[post("/two-factor/webauthn", data = "<data>")]
async fn activate_webauthn(data: Json<EnableWebauthnData>, headers: Headers, mut conn: DbConn) -> JsonResult {
let data: EnableWebauthnData = data.into_inner();
@ -253,7 +137,7 @@ async fn activate_webauthn(data: Json<EnableWebauthnData>, headers: Headers, mut
let type_ = TwoFactorType::WebauthnRegisterChallenge as i32;
let state = match TwoFactor::find_by_user_and_type(&user.uuid, type_, &mut conn).await {
Some(tf) => {
let state: RegistrationState = serde_json::from_str(&tf.data)?;
let state: SecurityKeyRegistration = serde_json::from_str(&tf.data)?;
tf.delete(&mut conn).await?;
state
}
@ -261,8 +145,7 @@ async fn activate_webauthn(data: Json<EnableWebauthnData>, headers: Headers, mut
};
// Verify the credentials with the saved state
let (credential, _data) =
WebauthnConfig::load().register_credential(&data.device_response.into(), &state, |_| Ok(false))?;
let credential = WEBAUTHN.finish_securitykey_registration(&data.device_response, &state)?;
let mut registrations: Vec<_> = get_webauthn_registrations(&user.uuid, &mut conn).await?.1;
// TODO: Check for repeated ID's
@ -271,7 +154,7 @@ async fn activate_webauthn(data: Json<EnableWebauthnData>, headers: Headers, mut
name: data.name,
migrated: false,
credential,
credential: credential.into(),
});
// Save the registrations and return them
@ -365,16 +248,15 @@ pub async fn get_webauthn_registrations(
pub async fn generate_webauthn_login(user_uuid: &str, conn: &mut DbConn) -> JsonResult {
// Load saved credentials
let creds: Vec<Credential> =
get_webauthn_registrations(user_uuid, conn).await?.1.into_iter().map(|r| r.credential).collect();
let creds: Vec<SecurityKey> =
get_webauthn_registrations(user_uuid, conn).await?.1.into_iter().map(|r| r.credential.into()).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))?;
let (response, state) = WEBAUTHN.start_securitykey_authentication(&creds)?; //, Some(ext))?;
// Save the challenge state for later validation
TwoFactor::new(user_uuid.into(), TwoFactorType::WebauthnLoginChallenge, serde_json::to_string(&state)?)
@ -389,7 +271,7 @@ pub async fn validate_webauthn_login(user_uuid: &str, response: &str, conn: &mut
let type_ = TwoFactorType::WebauthnLoginChallenge as i32;
let state = match TwoFactor::find_by_user_and_type(user_uuid, type_, conn).await {
Some(tf) => {
let state: AuthenticationState = serde_json::from_str(&tf.data)?;
let state: SecurityKeyAuthentication = serde_json::from_str(&tf.data)?;
tf.delete(conn).await?;
state
}
@ -401,19 +283,15 @@ pub async fn validate_webauthn_login(user_uuid: &str, response: &str, conn: &mut
),
};
let rsp: PublicKeyCredentialCopy = serde_json::from_str(response)?;
let rsp: PublicKeyCredential = rsp.into();
let rsp: PublicKeyCredential = serde_json::from_str(response)?;
let mut registrations = get_webauthn_registrations(user_uuid, conn).await?.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)?;
let auth_result = WEBAUTHN.finish_securitykey_authentication(&rsp, &state)?;
for reg in &mut registrations {
if &reg.credential.cred_id == cred_id {
reg.credential.counter = auth_data.counter;
let mut security_key = SecurityKey::from(reg.credential.clone());
if security_key.cred_id() == auth_result.cred_id() && security_key.update_credential(&auth_result).is_some() {
TwoFactor::new(user_uuid.to_string(), TwoFactorType::Webauthn, serde_json::to_string(&registrations)?)
.save(conn)
.await?;

18
src/config.rs

@ -5,6 +5,7 @@ use std::sync::RwLock;
use job_scheduler_ng::Schedule;
use once_cell::sync::Lazy;
use reqwest::Url;
use webauthn_rs::{Webauthn, WebauthnBuilder};
use crate::{
db::DbConnType,
@ -24,6 +25,23 @@ pub static CONFIG: Lazy<Config> = Lazy::new(|| {
})
});
pub static WEBAUTHN: Lazy<Webauthn> = Lazy::new(|| {
let domain = CONFIG.domain();
let domain_origin = CONFIG.domain_origin();
let android_app_url = Url::parse("android:apk-key-hash:dUGFzUzf3lmHSLBDBIv+WaFyZMI").unwrap();
let ios_app_url = Url::parse("ios:bundle-id:com.8bit.bitwarden").unwrap();
let rp_id = Url::parse(&domain).map(|u| u.domain().map(str::to_owned)).ok().flatten().unwrap_or_default();
let rp_name = domain;
let rp_origin = Url::parse(&domain_origin).unwrap();
let builder = WebauthnBuilder::new(&rp_id, &rp_origin)
.expect("Invalid configuration")
.rp_name(&rp_name)
.append_allowed_origin(&ios_app_url)
.append_allowed_origin(&android_app_url);
builder.build().expect("Invalid configuration")
});
pub type Pass = String;
macro_rules! make_config {

21
src/db/models/two_factor.rs

@ -159,7 +159,7 @@ impl TwoFactor {
use crate::api::core::two_factor::webauthn::U2FRegistration;
use crate::api::core::two_factor::webauthn::{get_webauthn_registrations, WebauthnRegistration};
use webauthn_rs::proto::*;
use webauthn_rs_core::proto::*;
for mut u2f in u2f_factors {
let mut regs: Vec<U2FRegistration> = serde_json::from_str(&u2f.data)?;
@ -183,22 +183,23 @@ impl TwoFactor {
type_: COSEAlgorithm::ES256,
key: COSEKeyType::EC_EC2(COSEEC2Key {
curve: ECDSACurve::SECP256R1,
x,
y,
x: x.to_vec().into(),
y: y.to_vec().into(),
}),
};
let credential = CredentialV3 {
counter: reg.counter,
verified: false,
cred: key,
cred_id: reg.reg.key_handle.clone().into(),
registration_policy: UserVerificationPolicy::Discouraged_DO_NOT_USE,
};
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,
},
credential: credential.into(),
};
webauthn_regs.push(new_reg);

4
src/error.rs

@ -53,7 +53,8 @@ use rocket::error::Error as RocketErr;
use serde_json::{Error as SerdeErr, Value};
use std::io::Error as IoErr;
use std::time::SystemTimeError as TimeErr;
use webauthn_rs::error::WebauthnError as WebauthnErr;
use uuid::Error as UuidErr;
use webauthn_rs::prelude::WebauthnError as WebauthnErr;
use yubico::yubicoerror::YubicoError as YubiErr;
#[derive(Serialize)]
@ -92,6 +93,7 @@ make_error! {
Smtp(SmtpErr): _has_source, _api_error,
OpenSSL(SSLErr): _has_source, _api_error,
Rocket(RocketErr): _has_source, _api_error,
Uuid(UuidErr): _has_source, _api_error,
DieselCon(DieselConErr): _has_source, _api_error,
Webauthn(WebauthnErr): _has_source, _api_error,

2
src/main.rs

@ -56,7 +56,7 @@ mod util;
use crate::api::core::two_factor::duo_oidc::purge_duo_contexts;
use crate::api::purge_auth_requests;
use crate::api::{WS_ANONYMOUS_SUBSCRIPTIONS, WS_USERS};
pub use config::CONFIG;
pub use config::{CONFIG, WEBAUTHN};
pub use error::{Error, MapResult};
use rocket::data::{Limits, ToByteUnit};
use std::sync::Arc;

Loading…
Cancel
Save