1
0
Fork 1
Spiegel von https://github.com/dani-garcia/vaultwarden.git synchronisiert 2024-11-05 02:28:00 +01:00

update webauthn-rs crate

Dieser Commit ist enthalten in:
Stefan Melmuk 2024-04-06 11:33:25 +02:00
Ursprung 0fe93edea6
Commit 257829c6ab
Es konnte kein GPG-Schlüssel zu dieser Signatur gefunden werden
GPG-Schlüssel-ID: 817020C608FE9C09
6 geänderte Dateien mit 285 neuen und 243 gelöschten Zeilen

253
Cargo.lock generiert
Datei anzeigen

@ -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"
@ -292,7 +331,7 @@ checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn 2.0.60",
]
[[package]]
@ -309,7 +348,7 @@ checksum = "c6fa2087f2753a7da8cc1c0dbfcf89579dd57458e36769de5ac750b4671737ca"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn 2.0.60",
]
[[package]]
@ -378,6 +417,17 @@ 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 = "bigdecimal"
version = "0.4.3"
@ -515,7 +565,7 @@ dependencies = [
"darling",
"proc-macro2",
"quote",
"syn",
"syn 2.0.60",
]
[[package]]
@ -581,6 +631,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",
"hex",
"openssl",
"serde",
"serde_json",
"tracing",
"url",
"uuid",
]
[[package]]
name = "concurrent-queue"
version = "2.5.0"
@ -728,7 +795,7 @@ dependencies = [
"proc-macro2",
"quote",
"strsim",
"syn",
"syn 2.0.60",
]
[[package]]
@ -739,7 +806,7 @@ checksum = "a668eda54683121533a393014d8692171709ff57a7d61f187b6e782719f8933f"
dependencies = [
"darling_core",
"quote",
"syn",
"syn 2.0.60",
]
[[package]]
@ -767,6 +834,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"
@ -806,7 +887,7 @@ dependencies = [
"proc-macro2",
"proc-macro2-diagnostics",
"quote",
"syn",
"syn 2.0.60",
]
[[package]]
@ -842,7 +923,7 @@ dependencies = [
"diesel_table_macro_syntax",
"proc-macro2",
"quote",
"syn",
"syn 2.0.60",
]
[[package]]
@ -872,7 +953,7 @@ version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fc5557efc453706fed5e4fa85006fe9817c224c3f480a34c7e5959fd700921c5"
dependencies = [
"syn",
"syn 2.0.60",
]
[[package]]
@ -886,6 +967,17 @@ dependencies = [
"subtle",
]
[[package]]
name = "displaydoc"
version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "487585f4d0c6655fe74905e2504d8ad6908e4db67f744eb140876906c2f3175d"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.60",
]
[[package]]
name = "dotenvy"
version = "0.15.7"
@ -935,7 +1027,7 @@ dependencies = [
"heck",
"proc-macro2",
"quote",
"syn",
"syn 2.0.60",
]
[[package]]
@ -1187,7 +1279,7 @@ checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn 2.0.60",
]
[[package]]
@ -1387,6 +1479,12 @@ version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024"
[[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"
@ -2142,7 +2240,7 @@ checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn 2.0.60",
]
[[package]]
@ -2191,6 +2289,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"
@ -2220,7 +2327,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn 2.0.60",
]
[[package]]
@ -2332,7 +2439,7 @@ dependencies = [
"proc-macro2",
"proc-macro2-diagnostics",
"quote",
"syn",
"syn 2.0.60",
]
[[package]]
@ -2382,7 +2489,7 @@ dependencies = [
"pest_meta",
"proc-macro2",
"quote",
"syn",
"syn 2.0.60",
]
[[package]]
@ -2457,7 +2564,7 @@ checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn 2.0.60",
]
[[package]]
@ -2564,7 +2671,7 @@ checksum = "af066a9c399a26e020ada66a034357a868728e72cd426f3adcd35f80d88d88c8"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn 2.0.60",
"version_check",
"yansi",
]
@ -2706,7 +2813,7 @@ checksum = "5fddb4f8d99b0a2ebafc65a87a69a7b9875e4b1ae1f00db265d300ef7f28bccc"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn 2.0.60",
]
[[package]]
@ -2948,7 +3055,7 @@ dependencies = [
"proc-macro2",
"quote",
"rocket_http",
"syn",
"syn 2.0.60",
"unicode-xid",
"version_check",
]
@ -3020,6 +3127,15 @@ version = "0.1.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76"
[[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"
@ -3194,10 +3310,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",
@ -3211,7 +3327,7 @@ checksum = "11bd257a6541e141e42ca6d24ae26f7714887b47e89aa739099104c7e4d3b7fc"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn 2.0.60",
]
[[package]]
@ -3407,6 +3523,17 @@ version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc"
[[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.60"
@ -3424,6 +3551,18 @@ version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160"
[[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"
@ -3487,7 +3626,7 @@ checksum = "d1cd413b5d558b4c5bf3680e324a6fa5014e7b7c067a51e69dbdf47eb7148b66"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn 2.0.60",
]
[[package]]
@ -3584,7 +3723,7 @@ checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn 2.0.60",
]
[[package]]
@ -3775,7 +3914,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn 2.0.60",
]
[[package]]
@ -3931,6 +4070,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a183cf7feeba97b4dd1c0d46788634f6221d87fa961b305bed08c851829efcc0"
dependencies = [
"getrandom",
"serde",
]
[[package]]
@ -4071,7 +4211,7 @@ dependencies = [
"once_cell",
"proc-macro2",
"quote",
"syn",
"syn 2.0.60",
"wasm-bindgen-shared",
]
@ -4105,7 +4245,7 @@ checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn 2.0.60",
"wasm-bindgen-backend",
"wasm-bindgen-shared",
]
@ -4141,21 +4281,52 @@ dependencies = [
[[package]]
name = "webauthn-rs"
version = "0.3.2"
version = "0.4.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "90b266eccb4b32595876f5c73ea443b0516da0b1df72ca07bc08ed9ba7f96ec1"
checksum = "2db00711c712414e93b019c4596315085792215bc2ac2d5872f9e8913b0a6316"
dependencies = [
"base64urlsafedata",
"serde",
"tracing",
"url",
"uuid",
"webauthn-rs-core",
]
[[package]]
name = "webauthn-rs-core"
version = "0.4.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "294c78c83f12153a51e1cf1e6970b5da1397645dada39033a9c3173a8fc4fc2b"
dependencies = [
"base64 0.13.1",
"base64urlsafedata",
"compact_jwt",
"der-parser",
"nom",
"openssl",
"rand",
"serde",
"serde_cbor",
"serde_derive",
"serde_cbor_2",
"serde_json",
"thiserror",
"tracing",
"url",
"uuid",
"webauthn-rs-proto",
"x509-parser",
]
[[package]]
name = "webauthn-rs-proto"
version = "0.4.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d24e638361a63ba5c0a0be6a60229490fcdf33740ed63df5bb6bdb627b52a138"
dependencies = [
"base64urlsafedata",
"serde",
"serde_json",
"url",
]
[[package]]
@ -4418,6 +4589,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"
@ -4460,5 +4649,5 @@ checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn 2.0.60",
]

Datei anzeigen

@ -109,7 +109,7 @@ 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.4.8", features = ["danger-allow-state-serialisation"] }
# Handling of URL's for WebAuthn and favicons
url = "2.5.0"

Datei anzeigen

@ -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,93 +15,13 @@ use crate::{
},
error::Error,
util::NumberOrString,
CONFIG,
CONFIG, WEBAUTHN,
};
pub fn routes() -> Vec<Route> {
routes![get_webauthn, generate_webauthn_challenge, activate_webauthn, activate_webauthn_put, delete_webauthn,]
}
// Some old u2f structs still needed for migrating from u2f to WebAuthn
// Both `struct Registration` and `struct U2FRegistration` can be removed if we remove the u2f to WebAuthn migration
#[derive(Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Registration {
pub key_handle: Vec<u8>,
pub pub_key: Vec<u8>,
pub attestation_cert: Option<Vec<u8>>,
pub device_name: Option<String>,
}
#[derive(Serialize, Deserialize)]
pub struct U2FRegistration {
pub id: i32,
pub name: String,
#[serde(with = "Registration")]
pub reg: Registration,
pub counter: u32,
compromised: bool,
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,
pub name: String,
pub migrated: bool,
pub credential: Credential,
}
impl WebauthnRegistration {
fn to_json(&self) -> Value {
json!({
"Id": self.id,
"Name": self.name,
"migrated": self.migrated,
})
}
}
#[post("/two-factor/get-webauthn", data = "<data>")]
async fn get_webauthn(data: JsonUpcase<PasswordOrOtpData>, headers: Headers, mut conn: DbConn) -> JsonResult {
if !CONFIG.domain_set() {
@ -135,21 +54,16 @@ async fn generate_webauthn_challenge(
data.validate(&user, false, &mut conn).await?;
let registrations = get_webauthn_registrations(&user.uuid, &mut conn)
let registrations: Vec<Base64UrlSafeData> = 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.security_key.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?;
@ -165,90 +79,11 @@ async fn generate_webauthn_challenge(
struct EnableWebauthnData {
Id: NumberOrString, // 1..5
Name: String,
DeviceResponse: RegisterPublicKeyCredentialCopy,
DeviceResponse: RegisterPublicKeyCredential,
MasterPasswordHash: Option<String>,
Otp: Option<String>,
}
// This is copied from RegisterPublicKeyCredential to change the Response objects casing
#[derive(Debug, Deserialize)]
#[allow(non_snake_case)]
struct RegisterPublicKeyCredentialCopy {
pub Id: String,
pub RawId: Base64UrlSafeData,
pub Response: AuthenticatorAttestationResponseRawCopy,
pub Type: String,
}
// This is copied from AuthenticatorAttestationResponseRaw to change clientDataJSON to clientDataJson
#[derive(Debug, Deserialize)]
#[allow(non_snake_case)]
pub struct AuthenticatorAttestationResponseRawCopy {
pub AttestationObject: Base64UrlSafeData,
pub ClientDataJson: Base64UrlSafeData,
}
impl From<RegisterPublicKeyCredentialCopy> for RegisterPublicKeyCredential {
fn from(r: RegisterPublicKeyCredentialCopy) -> Self {
Self {
id: r.Id,
raw_id: r.RawId,
response: AuthenticatorAttestationResponseRaw {
attestation_object: r.Response.AttestationObject,
client_data_json: r.Response.ClientDataJson,
},
type_: r.Type,
}
}
}
// This is copied from PublicKeyCredential to change the Response objects casing
#[derive(Debug, Deserialize)]
#[allow(non_snake_case)]
pub struct PublicKeyCredentialCopy {
pub Id: String,
pub RawId: Base64UrlSafeData,
pub Response: AuthenticatorAssertionResponseRawCopy,
pub Extensions: Option<AuthenticationExtensionsClientOutputsCopy>,
pub Type: String,
}
// This is copied from AuthenticatorAssertionResponseRaw to change clientDataJSON to clientDataJson
#[derive(Debug, Deserialize)]
#[allow(non_snake_case)]
pub struct AuthenticatorAssertionResponseRawCopy {
pub AuthenticatorData: Base64UrlSafeData,
pub ClientDataJson: Base64UrlSafeData,
pub Signature: Base64UrlSafeData,
pub UserHandle: Option<Base64UrlSafeData>,
}
#[derive(Debug, Deserialize)]
#[allow(non_snake_case)]
pub struct AuthenticationExtensionsClientOutputsCopy {
#[serde(default)]
pub Appid: bool,
}
impl From<PublicKeyCredentialCopy> for PublicKeyCredential {
fn from(r: PublicKeyCredentialCopy) -> Self {
Self {
id: r.Id,
raw_id: r.RawId,
response: AuthenticatorAssertionResponseRaw {
authenticator_data: r.Response.AuthenticatorData,
client_data_json: r.Response.ClientDataJson,
signature: r.Response.Signature,
user_handle: r.Response.UserHandle,
},
extensions: r.Extensions.map(|e| AuthenticationExtensionsClientOutputs {
appid: e.Appid,
}),
type_: r.Type,
}
}
}
#[post("/two-factor/webauthn", data = "<data>")]
async fn activate_webauthn(data: JsonUpcase<EnableWebauthnData>, headers: Headers, mut conn: DbConn) -> JsonResult {
let data: EnableWebauthnData = data.into_inner().data;
@ -265,7 +100,7 @@ async fn activate_webauthn(data: JsonUpcase<EnableWebauthnData>, headers: Header
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
}
@ -273,8 +108,7 @@ async fn activate_webauthn(data: JsonUpcase<EnableWebauthnData>, headers: Header
};
// Verify the credentials with the saved state
let (credential, _data) =
WebauthnConfig::load().register_credential(&data.DeviceResponse.into(), &state, |_| Ok(false))?;
let security_key = WEBAUTHN.finish_securitykey_registration(&data.DeviceResponse, &state)?;
let mut registrations: Vec<_> = get_webauthn_registrations(&user.uuid, &mut conn).await?.1;
// TODO: Check for repeated ID's
@ -283,7 +117,7 @@ async fn activate_webauthn(data: JsonUpcase<EnableWebauthnData>, headers: Header
name: data.Name,
migrated: false,
credential,
security_key,
});
// Save the registrations and return them
@ -334,27 +168,11 @@ async fn delete_webauthn(data: JsonUpcase<DeleteU2FData>, headers: Headers, mut
None => err!("Webauthn entry not found"),
};
let removed_item = data.remove(item_pos);
let _removed_item = data.remove(item_pos);
tf.data = serde_json::to_string(&data)?;
tf.save(&mut conn).await?;
drop(tf);
// If entry is migrated from u2f, delete the u2f entry as well
if let Some(mut u2f) =
TwoFactor::find_by_user_and_type(&headers.user.uuid, TwoFactorType::U2f as i32, &mut conn).await
{
let mut data: Vec<U2FRegistration> = match serde_json::from_str(&u2f.data) {
Ok(d) => d,
Err(_) => err!("Error parsing U2F data"),
};
data.retain(|r| r.reg.key_handle != removed_item.credential.cred_id);
let new_data_str = serde_json::to_string(&data)?;
u2f.data = new_data_str;
u2f.save(&mut conn).await?;
}
let keys_json: Vec<Value> = data.iter().map(WebauthnRegistration::to_json).collect();
Ok(Json(json!({
@ -364,7 +182,26 @@ async fn delete_webauthn(data: JsonUpcase<DeleteU2FData>, headers: Headers, mut
})))
}
pub async fn get_webauthn_registrations(
#[derive(Debug, Serialize, Deserialize)]
struct WebauthnRegistration {
pub id: i32,
pub name: String,
pub migrated: bool,
pub security_key: SecurityKey,
}
impl WebauthnRegistration {
fn to_json(&self) -> Value {
json!({
"Id": self.id,
"Name": self.name,
"migrated": self.migrated,
})
}
}
async fn get_webauthn_registrations(
user_uuid: &str,
conn: &mut DbConn,
) -> Result<(bool, Vec<WebauthnRegistration>), Error> {
@ -377,16 +214,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.security_key).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)?)
@ -401,7 +237,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
}
@ -413,19 +249,16 @@ pub async fn validate_webauthn_login(user_uuid: &str, response: &str, conn: &mut
),
};
let rsp: crate::util::UpCase<PublicKeyCredentialCopy> = serde_json::from_str(response)?;
let rsp: PublicKeyCredential = rsp.data.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;
if reg.security_key.cred_id() == auth_result.cred_id()
&& reg.security_key.update_credential(&auth_result).is_some()
{
TwoFactor::new(user_uuid.to_string(), TwoFactorType::Webauthn, serde_json::to_string(&registrations)?)
.save(conn)
.await?;

Datei anzeigen

@ -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 {

Datei anzeigen

@ -52,7 +52,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)]
@ -87,6 +88,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,

Datei anzeigen

@ -53,7 +53,7 @@ mod util;
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;