diff --git a/Cargo.lock b/Cargo.lock index 1ac8794d..4b676996 100644 --- a/Cargo.lock +++ b/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]] diff --git a/Cargo.toml b/Cargo.toml index c320ff0b..51bcf326 100644 --- a/Cargo.toml +++ b/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" diff --git a/src/api/core/two_factor/webauthn.rs b/src/api/core/two_factor/webauthn.rs index 52ca70c4..f08c4b1c 100644 --- a/src/api/core/two_factor/webauthn.rs +++ b/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 { @@ -28,7 +27,7 @@ pub fn routes() -> Vec { #[derive(Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct Registration { - pub key_handle: Vec, + pub key_handle: CredentialID, pub pub_key: Vec, pub attestation_cert: Option>, pub device_name: Option, @@ -45,45 +44,6 @@ pub struct U2FRegistration { pub migrated: Option, } -struct WebauthnConfig { - url: String, - origin: Url, - rpid: String, -} - -impl WebauthnConfig { - fn load() -> Webauthn { - 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, headers: Hea data.validate(&user, false, &mut conn).await?; - let registrations = get_webauthn_registrations(&user.uuid, &mut conn) + let registrations: Vec = 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, headers: Hea struct EnableWebauthnData { id: NumberOrString, // 1..5 name: String, - device_response: RegisterPublicKeyCredentialCopy, + device_response: RegisterPublicKeyCredential, master_password_hash: Option, otp: Option, } -#[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 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, - 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, -} - -impl From 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 = "")] async fn activate_webauthn(data: Json, headers: Headers, mut conn: DbConn) -> JsonResult { let data: EnableWebauthnData = data.into_inner(); @@ -253,7 +137,7 @@ async fn activate_webauthn(data: Json, 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, 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, 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 = - get_webauthn_registrations(user_uuid, conn).await?.1.into_iter().map(|r| r.credential).collect(); + let creds: Vec = + 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 ®.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(®istrations)?) .save(conn) .await?; diff --git a/src/config.rs b/src/config.rs index 93944131..c3d51c9e 100644 --- a/src/config.rs +++ b/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 = Lazy::new(|| { }) }); +pub static WEBAUTHN: Lazy = 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 { diff --git a/src/db/models/two_factor.rs b/src/db/models/two_factor.rs index 9155c518..5948faf5 100644 --- a/src/db/models/two_factor.rs +++ b/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 = 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); diff --git a/src/error.rs b/src/error.rs index b2872775..22598566 100644 --- a/src/error.rs +++ b/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, diff --git a/src/main.rs b/src/main.rs index 9f96dc60..f254f01d 100644 --- a/src/main.rs +++ b/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;