mirror of
https://github.com/dani-garcia/vaultwarden.git
synced 2026-07-10 18:24:43 -06:00
feat: Added support for passkey vault unlock feature
Co-authored-by: Keng <39216134+kengzzzz@users.noreply.github.com>
This commit is contained in:
parent
3b669264bb
commit
9e4aa5efe7
@ -12,9 +12,11 @@ use serde_json::Value;
|
||||
|
||||
use crate::{
|
||||
CONFIG,
|
||||
api::{self, EmptyResult, JsonResult, Notify, PasswordOrOtpData, UpdateType, core::log_event},
|
||||
auth::ClientVersion,
|
||||
auth::{Headers, OrgIdGuard, OwnerHeaders},
|
||||
api::{
|
||||
self, EmptyResult, JsonResult, Notify, PasswordOrOtpData, UpdateType,
|
||||
core::{log_event, webauthn_prf_option},
|
||||
},
|
||||
auth::{ClientVersion, Headers, OrgIdGuard, OwnerHeaders},
|
||||
config::PathType,
|
||||
crypto,
|
||||
db::{
|
||||
@ -22,7 +24,7 @@ use crate::{
|
||||
models::{
|
||||
Archive, Attachment, AttachmentId, Cipher, CipherId, Collection, CollectionCipher, CollectionGroup,
|
||||
CollectionId, CollectionUser, EventType, Favorite, Folder, FolderCipher, FolderId, Group, Membership,
|
||||
MembershipType, OrgPolicy, OrgPolicyType, OrganizationId, RepromptType, Send, UserId,
|
||||
MembershipType, OrgPolicy, OrgPolicyType, OrganizationId, RepromptType, Send, UserId, WebauthnCredential,
|
||||
},
|
||||
},
|
||||
util::{NumberOrString, deser_opt_nonempty_str, save_temp_file},
|
||||
@ -188,6 +190,12 @@ async fn sync(data: SyncData, headers: Headers, client_version: Option<ClientVer
|
||||
Value::Null
|
||||
};
|
||||
|
||||
let webauthn_prf_options = WebauthnCredential::find_all_by_user(&headers.user.uuid, &conn)
|
||||
.await
|
||||
.iter()
|
||||
.filter_map(|wac| webauthn_prf_option(wac, false))
|
||||
.collect();
|
||||
|
||||
Ok(Json(json!({
|
||||
"profile": user_json,
|
||||
"folders": folders_json,
|
||||
@ -196,13 +204,21 @@ async fn sync(data: SyncData, headers: Headers, client_version: Option<ClientVer
|
||||
"ciphers": ciphers_json,
|
||||
"domains": domains_json,
|
||||
"sends": sends_json,
|
||||
"userDecryption": {
|
||||
"masterPasswordUnlock": master_password_unlock,
|
||||
},
|
||||
"userDecryption": sync_user_decryption(&master_password_unlock, webauthn_prf_options),
|
||||
"object": "sync"
|
||||
})))
|
||||
}
|
||||
|
||||
fn sync_user_decryption(master_password_unlock: &Value, webauthn_prf_options: Vec<Value>) -> Value {
|
||||
let mut user_decryption = json!({
|
||||
"masterPasswordUnlock": master_password_unlock,
|
||||
});
|
||||
if !webauthn_prf_options.is_empty() {
|
||||
user_decryption["webAuthnPrfOptions"] = webauthn_prf_options.into();
|
||||
}
|
||||
user_decryption
|
||||
}
|
||||
|
||||
#[get("/ciphers")]
|
||||
async fn get_ciphers(headers: Headers, conn: DbConn) -> JsonResult {
|
||||
let ciphers = Cipher::find_by_user_visible(&headers.user.uuid, &conn).await;
|
||||
|
||||
@ -15,7 +15,7 @@ pub use ciphers::{CipherData, CipherSyncData, CipherSyncType, purge_trashed_ciph
|
||||
pub use emergency_access::{emergency_notification_reminder_job, emergency_request_timeout_job};
|
||||
pub use events::{event_cleanup_job, log_event, log_user_event};
|
||||
pub use sends::purge_sends;
|
||||
pub use webauthn::WEBAUTHN_PASSWORDLESS;
|
||||
pub use webauthn::{WEBAUTHN_PASSWORDLESS, webauthn_prf_option};
|
||||
|
||||
use reqwest::Method;
|
||||
use rocket::{Catcher, Route, serde::json::Json, serde::json::Value};
|
||||
@ -211,6 +211,11 @@ fn config() -> Json<Value> {
|
||||
&FeatureFlagFilter::ValidOnly,
|
||||
);
|
||||
feature_states.insert("pm-19148-innovation-archive".to_owned(), true);
|
||||
if CONFIG.passkey_login_allowed() {
|
||||
feature_states.insert("pm-2035-passkey-unlock".to_owned(), true);
|
||||
} else {
|
||||
feature_states.remove("pm-2035-passkey-unlock");
|
||||
}
|
||||
|
||||
Json(json!({
|
||||
// Note: The clients use this version to handle backwards compatibility concerns
|
||||
|
||||
@ -14,7 +14,7 @@ use crate::{
|
||||
auth::Headers,
|
||||
db::{
|
||||
DbConn,
|
||||
models::{TwoFactor, TwoFactorType, WebauthnCredential, WebauthnCredentialId},
|
||||
models::{TwoFactor, TwoFactorType, WebauthnCredential, WebauthnCredentialId, WebauthnCredentialPrfStatus},
|
||||
},
|
||||
};
|
||||
|
||||
@ -44,6 +44,30 @@ pub fn routes() -> Vec<rocket::Route> {
|
||||
routes![get_webauthn, post_webauthn, post_webauthn_attestation_options, post_webauthn_delete]
|
||||
}
|
||||
|
||||
pub fn webauthn_prf_option(wac: &WebauthnCredential, pascal_case: bool) -> Option<Value> {
|
||||
if !matches!(wac.get_prf_status(), WebauthnCredentialPrfStatus::Enabled) {
|
||||
return None;
|
||||
}
|
||||
|
||||
let passkey: Passkey = serde_json::from_str(&wac.credential).ok()?;
|
||||
|
||||
if pascal_case {
|
||||
Some(json!({
|
||||
"CredentialId": passkey.cred_id().to_owned(),
|
||||
"Transports": [],
|
||||
"EncryptedPrivateKey": wac.encrypted_private_key.as_ref()?,
|
||||
"EncryptedUserKey": wac.encrypted_user_key.as_ref()?,
|
||||
}))
|
||||
} else {
|
||||
Some(json!({
|
||||
"credentialId": passkey.cred_id().to_owned(),
|
||||
"transports": [],
|
||||
"encryptedPrivateKey": wac.encrypted_private_key.as_ref()?,
|
||||
"encryptedUserKey": wac.encrypted_user_key.as_ref()?,
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
#[get("/webauthn")]
|
||||
async fn get_webauthn(headers: Headers, conn: DbConn) -> JsonResult {
|
||||
if !CONFIG.passkey_login_allowed() {
|
||||
|
||||
@ -23,6 +23,7 @@ use crate::{
|
||||
authenticator, duo, duo_oidc, email, enforce_2fa_policy, is_twofactor_provider_usable, webauthn,
|
||||
yubikey,
|
||||
},
|
||||
webauthn_prf_option,
|
||||
},
|
||||
master_password_policy,
|
||||
push::register_push_device,
|
||||
@ -633,13 +634,10 @@ async fn webauthn_login(data: ConnectData, user_id: &mut Option<UserId>, conn: &
|
||||
|
||||
let mut result = authenticated_response(&user, &mut device, auth_tokens, None, conn, ip).await?;
|
||||
|
||||
// Add WebAuthnPrfOption if the credential has encrypted keys (PRF-based decryption)
|
||||
if matched_wac.encrypted_private_key.is_some() && matched_wac.encrypted_user_key.is_some() {
|
||||
// Add WebAuthnPrfOption if the credential has enabled PRF-based decryption.
|
||||
if let Some(prf_option) = webauthn_prf_option(matched_wac, true) {
|
||||
let Json(ref mut val) = result;
|
||||
val["UserDecryptionOptions"]["WebAuthnPrfOption"] = json!({
|
||||
"EncryptedPrivateKey": matched_wac.encrypted_private_key,
|
||||
"EncryptedUserKey": matched_wac.encrypted_user_key,
|
||||
});
|
||||
val["UserDecryptionOptions"]["WebAuthnPrfOption"] = prf_option;
|
||||
}
|
||||
|
||||
Ok(result)
|
||||
|
||||
@ -1406,6 +1406,7 @@ pub const SUPPORTED_FEATURE_FLAGS: &[&str] = &[
|
||||
"ssh-agent-v2",
|
||||
// Key Management Team
|
||||
"ssh-key-vault-item",
|
||||
"pm-2035-passkey-unlock",
|
||||
"pm-25373-windows-biometrics-v2",
|
||||
// Mobile Team
|
||||
"anon-addy-self-host-alias",
|
||||
|
||||
@ -44,4 +44,4 @@ pub use self::two_factor::{TwoFactor, TwoFactorType};
|
||||
pub use self::two_factor_duo_context::TwoFactorDuoContext;
|
||||
pub use self::two_factor_incomplete::TwoFactorIncomplete;
|
||||
pub use self::user::{Invitation, SsoUser, User, UserId, UserKdfType, UserStampException};
|
||||
pub use self::webauthn_credential::{WebauthnCredential, WebauthnCredentialId};
|
||||
pub use self::webauthn_credential::{WebauthnCredential, WebauthnCredentialId, WebauthnCredentialPrfStatus};
|
||||
|
||||
Loading…
Reference in New Issue
Block a user