bitwarden_uniffi/platform/
fido2.rsuse std::sync::Arc;
use bitwarden_fido::{
CheckUserOptions, ClientData, ClientFido2Ext, Fido2CallbackError as BitFido2CallbackError,
Fido2CredentialAutofillView, GetAssertionRequest, GetAssertionResult, MakeCredentialRequest,
MakeCredentialResult, Origin, PublicKeyCredentialAuthenticatorAssertionResponse,
PublicKeyCredentialAuthenticatorAttestationResponse, PublicKeyCredentialRpEntity,
PublicKeyCredentialUserEntity,
};
use bitwarden_vault::{Cipher, CipherView, Fido2CredentialNewView};
use crate::{
error::{Error, Result},
Client,
};
#[derive(uniffi::Object)]
pub struct ClientFido2(pub(crate) Arc<Client>);
#[uniffi::export]
impl ClientFido2 {
pub fn authenticator(
self: Arc<Self>,
user_interface: Arc<dyn Fido2UserInterface>,
credential_store: Arc<dyn Fido2CredentialStore>,
) -> Arc<ClientFido2Authenticator> {
Arc::new(ClientFido2Authenticator(
self.0.clone(),
user_interface,
credential_store,
))
}
pub fn client(
self: Arc<Self>,
user_interface: Arc<dyn Fido2UserInterface>,
credential_store: Arc<dyn Fido2CredentialStore>,
) -> Arc<ClientFido2Client> {
Arc::new(ClientFido2Client(ClientFido2Authenticator(
self.0.clone(),
user_interface,
credential_store,
)))
}
pub fn decrypt_fido2_autofill_credentials(
self: Arc<Self>,
cipher_view: CipherView,
) -> Result<Vec<Fido2CredentialAutofillView>> {
let result = self
.0
.0
.fido2()
.decrypt_fido2_autofill_credentials(cipher_view)
.map_err(Error::DecryptFido2AutofillCredentials)?;
Ok(result)
}
}
#[derive(uniffi::Object)]
pub struct ClientFido2Authenticator(
pub(crate) Arc<Client>,
pub(crate) Arc<dyn Fido2UserInterface>,
pub(crate) Arc<dyn Fido2CredentialStore>,
);
#[uniffi::export]
impl ClientFido2Authenticator {
pub async fn make_credential(
&self,
request: MakeCredentialRequest,
) -> Result<MakeCredentialResult> {
let fido2 = self.0 .0.fido2();
let ui = UniffiTraitBridge(self.1.as_ref());
let cs = UniffiTraitBridge(self.2.as_ref());
let mut auth = fido2.create_authenticator(&ui, &cs);
let result = auth
.make_credential(request)
.await
.map_err(Error::MakeCredential)?;
Ok(result)
}
pub async fn get_assertion(&self, request: GetAssertionRequest) -> Result<GetAssertionResult> {
let fido2 = self.0 .0.fido2();
let ui = UniffiTraitBridge(self.1.as_ref());
let cs = UniffiTraitBridge(self.2.as_ref());
let mut auth = fido2.create_authenticator(&ui, &cs);
let result = auth
.get_assertion(request)
.await
.map_err(Error::GetAssertion)?;
Ok(result)
}
pub async fn silently_discover_credentials(
&self,
rp_id: String,
) -> Result<Vec<Fido2CredentialAutofillView>> {
let fido2 = self.0 .0.fido2();
let ui = UniffiTraitBridge(self.1.as_ref());
let cs = UniffiTraitBridge(self.2.as_ref());
let mut auth = fido2.create_authenticator(&ui, &cs);
let result = auth
.silently_discover_credentials(rp_id)
.await
.map_err(Error::SilentlyDiscoverCredentials)?;
Ok(result)
}
pub async fn credentials_for_autofill(&self) -> Result<Vec<Fido2CredentialAutofillView>> {
let fido2 = self.0 .0.fido2();
let ui = UniffiTraitBridge(self.1.as_ref());
let cs = UniffiTraitBridge(self.2.as_ref());
let mut auth = fido2.create_authenticator(&ui, &cs);
let result = auth
.credentials_for_autofill()
.await
.map_err(Error::CredentialsForAutofill)?;
Ok(result)
}
}
#[derive(uniffi::Object)]
pub struct ClientFido2Client(pub(crate) ClientFido2Authenticator);
#[uniffi::export]
impl ClientFido2Client {
pub async fn register(
&self,
origin: Origin,
request: String,
client_data: ClientData,
) -> Result<PublicKeyCredentialAuthenticatorAttestationResponse> {
let fido2 = self.0 .0 .0.fido2();
let ui = UniffiTraitBridge(self.0 .1.as_ref());
let cs = UniffiTraitBridge(self.0 .2.as_ref());
let mut client = fido2.create_client(&ui, &cs);
let result = client
.register(origin, request, client_data)
.await
.map_err(Error::Fido2Client)?;
Ok(result)
}
pub async fn authenticate(
&self,
origin: Origin,
request: String,
client_data: ClientData,
) -> Result<PublicKeyCredentialAuthenticatorAssertionResponse> {
let fido2 = self.0 .0 .0.fido2();
let ui = UniffiTraitBridge(self.0 .1.as_ref());
let cs = UniffiTraitBridge(self.0 .2.as_ref());
let mut client = fido2.create_client(&ui, &cs);
let result = client
.authenticate(origin, request, client_data)
.await
.map_err(Error::Fido2Client)?;
Ok(result)
}
}
#[allow(dead_code)]
#[derive(uniffi::Record)]
pub struct CheckUserResult {
user_present: bool,
user_verified: bool,
}
impl From<CheckUserResult> for bitwarden_fido::CheckUserResult {
fn from(val: CheckUserResult) -> Self {
Self {
user_present: val.user_present,
user_verified: val.user_verified,
}
}
}
#[allow(dead_code)]
#[derive(uniffi::Record)]
pub struct CheckUserAndPickCredentialForCreationResult {
cipher: CipherViewWrapper,
check_user_result: CheckUserResult,
}
#[derive(Debug, thiserror::Error, uniffi::Error)]
pub enum Fido2CallbackError {
#[error("The operation requires user interaction")]
UserInterfaceRequired,
#[error("The operation was cancelled by the user")]
OperationCancelled,
#[error("Unknown error: {reason}")]
Unknown { reason: String },
}
impl From<uniffi::UnexpectedUniFFICallbackError> for Fido2CallbackError {
fn from(e: uniffi::UnexpectedUniFFICallbackError) -> Self {
Self::Unknown { reason: e.reason }
}
}
impl From<Fido2CallbackError> for BitFido2CallbackError {
fn from(val: Fido2CallbackError) -> Self {
match val {
Fido2CallbackError::UserInterfaceRequired => Self::UserInterfaceRequired,
Fido2CallbackError::OperationCancelled => Self::OperationCancelled,
Fido2CallbackError::Unknown { reason } => Self::Unknown(reason),
}
}
}
#[uniffi::export(with_foreign)]
#[async_trait::async_trait]
pub trait Fido2UserInterface: Send + Sync {
async fn check_user(
&self,
options: CheckUserOptions,
hint: UIHint,
) -> Result<CheckUserResult, Fido2CallbackError>;
async fn pick_credential_for_authentication(
&self,
available_credentials: Vec<CipherView>,
) -> Result<CipherViewWrapper, Fido2CallbackError>;
async fn check_user_and_pick_credential_for_creation(
&self,
options: CheckUserOptions,
new_credential: Fido2CredentialNewView,
) -> Result<CheckUserAndPickCredentialForCreationResult, Fido2CallbackError>;
async fn is_verification_enabled(&self) -> bool;
}
#[uniffi::export(with_foreign)]
#[async_trait::async_trait]
pub trait Fido2CredentialStore: Send + Sync {
async fn find_credentials(
&self,
ids: Option<Vec<Vec<u8>>>,
rip_id: String,
) -> Result<Vec<CipherView>, Fido2CallbackError>;
async fn all_credentials(&self) -> Result<Vec<CipherView>, Fido2CallbackError>;
async fn save_credential(&self, cred: Cipher) -> Result<(), Fido2CallbackError>;
}
struct UniffiTraitBridge<T>(T);
#[async_trait::async_trait]
impl bitwarden_fido::Fido2CredentialStore for UniffiTraitBridge<&dyn Fido2CredentialStore> {
async fn find_credentials(
&self,
ids: Option<Vec<Vec<u8>>>,
rip_id: String,
) -> Result<Vec<CipherView>, BitFido2CallbackError> {
self.0
.find_credentials(ids, rip_id)
.await
.map_err(Into::into)
}
async fn all_credentials(&self) -> Result<Vec<CipherView>, BitFido2CallbackError> {
self.0.all_credentials().await.map_err(Into::into)
}
async fn save_credential(&self, cred: Cipher) -> Result<(), BitFido2CallbackError> {
self.0.save_credential(cred).await.map_err(Into::into)
}
}
#[derive(uniffi::Record)]
pub struct CipherViewWrapper {
cipher: CipherView,
}
#[derive(uniffi::Enum)]
pub enum UIHint {
InformExcludedCredentialFound(CipherView),
InformNoCredentialsFound,
RequestNewCredential(PublicKeyCredentialUserEntity, PublicKeyCredentialRpEntity),
RequestExistingCredential(CipherView),
}
impl From<bitwarden_fido::UIHint<'_, CipherView>> for UIHint {
fn from(hint: bitwarden_fido::UIHint<'_, CipherView>) -> Self {
use bitwarden_fido::UIHint as BWUIHint;
match hint {
BWUIHint::InformExcludedCredentialFound(cipher) => {
UIHint::InformExcludedCredentialFound(cipher.clone())
}
BWUIHint::InformNoCredentialsFound => UIHint::InformNoCredentialsFound,
BWUIHint::RequestNewCredential(user, rp) => UIHint::RequestNewCredential(
PublicKeyCredentialUserEntity {
id: user.id.clone().into(),
name: user.name.clone().unwrap_or_default(),
display_name: user.display_name.clone().unwrap_or_default(),
},
PublicKeyCredentialRpEntity {
id: rp.id.clone(),
name: rp.name.clone(),
},
),
BWUIHint::RequestExistingCredential(cipher) => {
UIHint::RequestExistingCredential(cipher.clone())
}
}
}
}
#[async_trait::async_trait]
impl bitwarden_fido::Fido2UserInterface for UniffiTraitBridge<&dyn Fido2UserInterface> {
async fn check_user<'a>(
&self,
options: CheckUserOptions,
hint: bitwarden_fido::UIHint<'a, CipherView>,
) -> Result<bitwarden_fido::CheckUserResult, BitFido2CallbackError> {
self.0
.check_user(options.clone(), hint.into())
.await
.map(Into::into)
.map_err(Into::into)
}
async fn pick_credential_for_authentication(
&self,
available_credentials: Vec<CipherView>,
) -> Result<CipherView, BitFido2CallbackError> {
self.0
.pick_credential_for_authentication(available_credentials)
.await
.map(|v| v.cipher)
.map_err(Into::into)
}
async fn check_user_and_pick_credential_for_creation(
&self,
options: CheckUserOptions,
new_credential: Fido2CredentialNewView,
) -> Result<(CipherView, bitwarden_fido::CheckUserResult), BitFido2CallbackError> {
self.0
.check_user_and_pick_credential_for_creation(options, new_credential)
.await
.map(|v| (v.cipher.cipher, v.check_user_result.into()))
.map_err(Into::into)
}
async fn is_verification_enabled(&self) -> bool {
self.0.is_verification_enabled().await
}
}