diff --git a/crates/bitwarden-crypto/Cargo.toml b/crates/bitwarden-crypto/Cargo.toml index 51b1d77b2..ce0424c63 100644 --- a/crates/bitwarden-crypto/Cargo.toml +++ b/crates/bitwarden-crypto/Cargo.toml @@ -16,7 +16,7 @@ keywords.workspace = true [features] default = [] -mobile = ["uniffi"] +mobile = ["dep:uniffi"] # Mobile-specific features [dependencies] aes = { version = ">=0.8.2, <0.9", features = ["zeroize"] } diff --git a/crates/bitwarden-generators/Cargo.toml b/crates/bitwarden-generators/Cargo.toml index ae1698d0c..e43d4530f 100644 --- a/crates/bitwarden-generators/Cargo.toml +++ b/crates/bitwarden-generators/Cargo.toml @@ -14,7 +14,7 @@ license-file.workspace = true keywords.workspace = true [features] -mobile = ["uniffi"] # Mobile-specific features +mobile = ["dep:uniffi"] # Mobile-specific features [dependencies] bitwarden-crypto = { workspace = true } diff --git a/crates/bitwarden/Cargo.toml b/crates/bitwarden/Cargo.toml index 04fcf62b5..59e7dd8e5 100644 --- a/crates/bitwarden/Cargo.toml +++ b/crates/bitwarden/Cargo.toml @@ -17,10 +17,13 @@ license-file.workspace = true default = ["secrets"] secrets = [] # Secrets manager API -internal = [] # Internal testing methods +internal = [ + "dep:bitwarden-exporters", + "dep:bitwarden-generators", +] # Internal testing methods mobile = [ - "uniffi", "internal", + "dep:uniffi", "bitwarden-crypto/mobile", "bitwarden-generators/mobile", ] # Mobile-specific features @@ -32,8 +35,8 @@ base64 = ">=0.21.2, <0.22" bitwarden-api-api = { workspace = true } bitwarden-api-identity = { workspace = true } bitwarden-crypto = { workspace = true } -bitwarden-exporters = { workspace = true } -bitwarden-generators = { workspace = true } +bitwarden-exporters = { workspace = true, optional = true } +bitwarden-generators = { workspace = true, optional = true } chrono = { version = ">=0.4.26, <0.5", features = [ "clock", "serde", diff --git a/crates/bitwarden/src/error.rs b/crates/bitwarden/src/error.rs index ed5d27c3e..bd040e51b 100644 --- a/crates/bitwarden/src/error.rs +++ b/crates/bitwarden/src/error.rs @@ -4,7 +4,9 @@ use std::{borrow::Cow, fmt::Debug}; use bitwarden_api_api::apis::Error as ApiError; use bitwarden_api_identity::apis::Error as IdentityError; +#[cfg(feature = "internal")] use bitwarden_exporters::ExportError; +#[cfg(feature = "internal")] use bitwarden_generators::{PassphraseError, PasswordError, UsernameError}; use reqwest::StatusCode; use thiserror::Error; @@ -52,13 +54,17 @@ pub enum Error { InvalidStateFile, // Generators + #[cfg(feature = "internal")] #[error(transparent)] UsernameError(#[from] UsernameError), + #[cfg(feature = "internal")] #[error(transparent)] PassphraseError(#[from] PassphraseError), + #[cfg(feature = "internal")] #[error(transparent)] PasswordError(#[from] PasswordError), + #[cfg(feature = "internal")] #[error(transparent)] ExportError(#[from] ExportError), diff --git a/crates/bitwarden/src/lib.rs b/crates/bitwarden/src/lib.rs index 28eb521de..17774b002 100644 --- a/crates/bitwarden/src/lib.rs +++ b/crates/bitwarden/src/lib.rs @@ -75,6 +75,7 @@ pub use client::Client; #[doc = include_str!("../README.md")] mod readme {} +#[cfg(feature = "internal")] pub mod generators { pub use bitwarden_generators::{ PassphraseGeneratorRequest, PasswordGeneratorRequest, UsernameGeneratorRequest, diff --git a/crates/bitwarden/src/tool/exporters/mod.rs b/crates/bitwarden/src/tool/exporters/mod.rs index 9e9e99ed5..45bdfd3fd 100644 --- a/crates/bitwarden/src/tool/exporters/mod.rs +++ b/crates/bitwarden/src/tool/exporters/mod.rs @@ -240,6 +240,7 @@ mod tests { uris: None, totp: None, autofill_on_page_load: None, + fido2_credentials: None, }), id: "fd411a1a-fec8-4070-985d-0e6560860e69".parse().ok(), organization_id: None, diff --git a/crates/bitwarden/src/vault/cipher/cipher.rs b/crates/bitwarden/src/vault/cipher/cipher.rs index 24207251f..47b58694d 100644 --- a/crates/bitwarden/src/vault/cipher/cipher.rs +++ b/crates/bitwarden/src/vault/cipher/cipher.rs @@ -433,6 +433,7 @@ mod tests { uris: None, totp: None, autofill_on_page_load: None, + fido2_credentials: None, }), id: "fd411a1a-fec8-4070-985d-0e6560860e69".parse().ok(), organization_id: None, diff --git a/crates/bitwarden/src/vault/cipher/login.rs b/crates/bitwarden/src/vault/cipher/login.rs index 5e2156a83..5d00c41e1 100644 --- a/crates/bitwarden/src/vault/cipher/login.rs +++ b/crates/bitwarden/src/vault/cipher/login.rs @@ -38,6 +38,25 @@ pub struct LoginUriView { pub r#match: Option, } +#[derive(Serialize, Deserialize, Debug, JsonSchema, Clone)] +#[serde(rename_all = "camelCase", deny_unknown_fields)] +#[cfg_attr(feature = "mobile", derive(uniffi::Record))] +pub struct Fido2Credential { + pub credential_id: EncString, + pub key_type: EncString, + pub key_algorithm: EncString, + pub key_curve: EncString, + pub key_value: EncString, + pub rp_id: EncString, + pub user_handle: Option, + pub user_name: Option, + pub counter: EncString, + pub rp_name: Option, + pub user_display_name: Option, + pub discoverable: EncString, + pub creation_date: DateTime, +} + #[derive(Serialize, Deserialize, Debug, JsonSchema)] #[serde(rename_all = "camelCase", deny_unknown_fields)] #[cfg_attr(feature = "mobile", derive(uniffi::Record))] @@ -49,6 +68,8 @@ pub struct Login { pub uris: Option>, pub totp: Option, pub autofill_on_page_load: Option, + + pub fido2_credentials: Option>, } #[derive(Serialize, Deserialize, Debug, JsonSchema)] @@ -62,6 +83,9 @@ pub struct LoginView { pub uris: Option>, pub totp: Option, pub autofill_on_page_load: Option, + + // TODO: Remove this once the SDK supports state + pub fido2_credentials: Option>, } impl KeyEncryptable for LoginUriView { @@ -82,6 +106,7 @@ impl KeyEncryptable for LoginView { uris: self.uris.encrypt_with_key(key)?, totp: self.totp.encrypt_with_key(key)?, autofill_on_page_load: self.autofill_on_page_load, + fido2_credentials: self.fido2_credentials, }) } } @@ -104,6 +129,7 @@ impl KeyDecryptable for Login { uris: self.uris.decrypt_with_key(key).ok().flatten(), totp: self.totp.decrypt_with_key(key).ok().flatten(), autofill_on_page_load: self.autofill_on_page_load, + fido2_credentials: self.fido2_credentials.clone(), }) } } @@ -125,6 +151,10 @@ impl TryFrom for Login { .transpose()?, totp: EncString::try_from_optional(login.totp)?, autofill_on_page_load: login.autofill_on_page_load, + fido2_credentials: login + .fido2_credentials + .map(|v| v.into_iter().map(|c| c.try_into()).collect()) + .transpose()?, }) } } @@ -152,3 +182,29 @@ impl From for UriMatchType { } } } + +impl TryFrom for Fido2Credential { + type Error = Error; + + fn try_from(value: bitwarden_api_api::models::CipherFido2CredentialModel) -> Result { + Ok(Self { + credential_id: value.credential_id.ok_or(Error::MissingFields)?.parse()?, + key_type: value.key_type.ok_or(Error::MissingFields)?.parse()?, + key_algorithm: value.key_algorithm.ok_or(Error::MissingFields)?.parse()?, + key_curve: value.key_curve.ok_or(Error::MissingFields)?.parse()?, + key_value: value.key_value.ok_or(Error::MissingFields)?.parse()?, + rp_id: value.rp_id.ok_or(Error::MissingFields)?.parse()?, + user_handle: EncString::try_from_optional(value.user_handle) + .ok() + .flatten(), + user_name: EncString::try_from_optional(value.user_name).ok().flatten(), + counter: value.counter.ok_or(Error::MissingFields)?.parse()?, + rp_name: EncString::try_from_optional(value.rp_name).ok().flatten(), + user_display_name: EncString::try_from_optional(value.user_display_name) + .ok() + .flatten(), + discoverable: value.discoverable.ok_or(Error::MissingFields)?.parse()?, + creation_date: value.creation_date.parse().unwrap(), + }) + } +}