Skip to content

Commit

Permalink
Add messaging for macos provider
Browse files Browse the repository at this point in the history
  • Loading branch information
dani-garcia committed Dec 6, 2024
1 parent cb2f7c3 commit 646c0bc
Show file tree
Hide file tree
Showing 27 changed files with 1,117 additions and 190 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import { BrowserScriptInjectorService } from "../../../platform/services/browser
import { AbortManager } from "../../../vault/background/abort-manager";
import { Fido2ContentScript, Fido2ContentScriptId } from "../enums/fido2-content-script.enum";
import { Fido2PortName } from "../enums/fido2-port-name.enum";
import { BrowserFido2ParentWindowReference } from "../services/browser-fido2-user-interface.service";

import { Fido2ExtensionMessage } from "./abstractions/fido2.background";
import { Fido2Background } from "./fido2.background";
Expand Down Expand Up @@ -56,7 +57,7 @@ describe("Fido2Background", () => {
let senderMock!: MockProxy<chrome.runtime.MessageSender>;
let logService!: MockProxy<LogService>;
let fido2ActiveRequestManager: MockProxy<Fido2ActiveRequestManager>;
let fido2ClientService!: MockProxy<Fido2ClientService>;
let fido2ClientService!: MockProxy<Fido2ClientService<BrowserFido2ParentWindowReference>>;
let vaultSettingsService!: MockProxy<VaultSettingsService>;
let scriptInjectorServiceMock!: MockProxy<BrowserScriptInjectorService>;
let configServiceMock!: MockProxy<ConfigService>;
Expand All @@ -73,7 +74,7 @@ describe("Fido2Background", () => {
});
senderMock = mock<chrome.runtime.MessageSender>({ id: "1", tab: tabMock });
logService = mock<LogService>();
fido2ClientService = mock<Fido2ClientService>();
fido2ClientService = mock<Fido2ClientService<BrowserFido2ParentWindowReference>>();
vaultSettingsService = mock<VaultSettingsService>();
abortManagerMock = mock<AbortManager>();
abortController = mock<AbortController>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,11 @@ import { ScriptInjectorService } from "../../../platform/services/abstractions/s
import { AbortManager } from "../../../vault/background/abort-manager";
import { Fido2ContentScript, Fido2ContentScriptId } from "../enums/fido2-content-script.enum";
import { Fido2PortName } from "../enums/fido2-port-name.enum";
import { BrowserFido2ParentWindowReference } from "../services/browser-fido2-user-interface.service";

import {
Fido2Background as Fido2BackgroundInterface,
Fido2BackgroundExtensionMessageHandlers,
Fido2Background as Fido2BackgroundInterface,
Fido2ExtensionMessage,
SharedFido2ScriptInjectionDetails,
SharedFido2ScriptRegistrationOptions,
Expand Down Expand Up @@ -54,7 +55,7 @@ export class Fido2Background implements Fido2BackgroundInterface {
constructor(
private logService: LogService,
private fido2ActiveRequestManager: Fido2ActiveRequestManager,
private fido2ClientService: Fido2ClientService,
private fido2ClientService: Fido2ClientService<BrowserFido2ParentWindowReference>,
private vaultSettingsService: VaultSettingsService,
private scriptInjectorService: ScriptInjectorService,
private configService: ConfigService,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -109,11 +109,15 @@ export type BrowserFido2Message = { sessionId: string } & (
}
);

export type BrowserFido2ParentWindowReference = chrome.tabs.Tab;

/**
* Browser implementation of the {@link Fido2UserInterfaceService}.
* The user interface is implemented as a popout and the service uses the browser's messaging API to communicate with it.
*/
export class BrowserFido2UserInterfaceService implements Fido2UserInterfaceServiceAbstraction {
export class BrowserFido2UserInterfaceService
implements Fido2UserInterfaceServiceAbstraction<BrowserFido2ParentWindowReference>
{
constructor(private authService: AuthService) {}

async newSession(
Expand Down
15 changes: 9 additions & 6 deletions apps/browser/src/background/main.background.ts
Original file line number Diff line number Diff line change
Expand Up @@ -199,11 +199,11 @@ import {
ImportServiceAbstraction,
} from "@bitwarden/importer/core";
import {
DefaultKdfConfigService,
KdfConfigService,
BiometricStateService,
BiometricsService,
DefaultBiometricStateService,
DefaultKdfConfigService,
KdfConfigService,
KeyService as KeyServiceAbstraction,
} from "@bitwarden/key-management";
import {
Expand All @@ -230,7 +230,10 @@ import { MainContextMenuHandler } from "../autofill/browser/main-context-menu-ha
import LegacyOverlayBackground from "../autofill/deprecated/background/overlay.background.deprecated";
import { Fido2Background as Fido2BackgroundAbstraction } from "../autofill/fido2/background/abstractions/fido2.background";
import { Fido2Background } from "../autofill/fido2/background/fido2.background";
import { BrowserFido2UserInterfaceService } from "../autofill/fido2/services/browser-fido2-user-interface.service";
import {

Check warning on line 233 in apps/browser/src/background/main.background.ts

View check run for this annotation

Codecov / codecov/patch

apps/browser/src/background/main.background.ts#L233

Added line #L233 was not covered by tests
BrowserFido2ParentWindowReference,
BrowserFido2UserInterfaceService,
} from "../autofill/fido2/services/browser-fido2-user-interface.service";
import { AutofillService as AutofillServiceAbstraction } from "../autofill/services/abstractions/autofill.service";
import AutofillService from "../autofill/services/autofill.service";
import { InlineMenuFieldQualificationService } from "../autofill/services/inline-menu-field-qualification.service";
Expand Down Expand Up @@ -335,10 +338,10 @@ export default class MainBackground {
policyApiService: PolicyApiServiceAbstraction;
sendApiService: SendApiServiceAbstraction;
userVerificationApiService: UserVerificationApiServiceAbstraction;
fido2UserInterfaceService: Fido2UserInterfaceServiceAbstraction;
fido2AuthenticatorService: Fido2AuthenticatorServiceAbstraction;
fido2UserInterfaceService: Fido2UserInterfaceServiceAbstraction<BrowserFido2ParentWindowReference>;
fido2AuthenticatorService: Fido2AuthenticatorServiceAbstraction<BrowserFido2ParentWindowReference>;
fido2ActiveRequestManager: Fido2ActiveRequestManagerAbstraction;
fido2ClientService: Fido2ClientServiceAbstraction;
fido2ClientService: Fido2ClientServiceAbstraction<BrowserFido2ParentWindowReference>;
avatarService: AvatarServiceAbstraction;
mainContextMenuHandler: MainContextMenuHandler;
cipherContextMenuHandler: CipherContextMenuHandler;
Expand Down
2 changes: 2 additions & 0 deletions apps/desktop/desktop_native/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

46 changes: 46 additions & 0 deletions apps/desktop/desktop_native/macos_provider/src/assertion.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
use std::sync::Arc;

use serde::{Deserialize, Serialize};

use crate::{BitwardenError, Callback, UserVerification};

#[derive(uniffi::Record, Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct PasskeyAssertionRequest {
rp_id: String,
credential_id: Vec<u8>,
user_name: String,
user_handle: Vec<u8>,
record_identifier: Option<String>,
client_data_hash: Vec<u8>,
user_verification: UserVerification,
}

#[derive(uniffi::Record, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct PasskeyAssertionResponse {
rp_id: String,
user_handle: Vec<u8>,
signature: Vec<u8>,
client_data_hash: Vec<u8>,
authenticator_data: Vec<u8>,
credential_id: Vec<u8>,
}

#[uniffi::export(with_foreign)]
pub trait PreparePasskeyAssertionCallback: Send + Sync {
fn on_complete(&self, credential: PasskeyAssertionResponse);
fn on_error(&self, error: BitwardenError);
}

impl Callback for Arc<dyn PreparePasskeyAssertionCallback> {
fn complete(&self, credential: serde_json::Value) -> Result<(), serde_json::Error> {
let credential = serde_json::from_value(credential)?;
PreparePasskeyAssertionCallback::on_complete(self.as_ref(), credential);
Ok(())
}

fn error(&self, error: BitwardenError) {
PreparePasskeyAssertionCallback::on_error(self.as_ref(), error);
}
}
67 changes: 48 additions & 19 deletions apps/desktop/desktop_native/macos_provider/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,25 +3,30 @@
use std::{
collections::HashMap,
sync::{atomic::AtomicU32, Arc, Mutex},
time::Instant,
};

use futures::FutureExt;
use log::{error, info, warn};
use registration::{PasskeyRegistrationRequest, PreparePasskeyRegistrationCallback};
use log::{error, info};
use serde::{de::DeserializeOwned, Deserialize, Serialize};

uniffi::setup_scaffolding!();

mod assertion;
mod registration;

use assertion::{PasskeyAssertionRequest, PreparePasskeyAssertionCallback};
use registration::{PasskeyRegistrationRequest, PreparePasskeyRegistrationCallback};

#[derive(uniffi::Enum, Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub enum UserVerification {
Preferred,
Required,
Discouraged,
}

#[derive(uniffi::Error, Serialize, Deserialize)]
#[derive(Debug, uniffi::Error, Serialize, Deserialize)]
pub enum BitwardenError {
Internal(String),
}
Expand All @@ -40,7 +45,8 @@ pub struct MacOSProviderClient {

// We need to keep track of the callbacks so we can call them when we receive a response
response_callbacks_counter: AtomicU32,
response_callbacks_queue: Arc<Mutex<HashMap<u32, Box<dyn Callback>>>>,
#[allow(clippy::type_complexity)]
response_callbacks_queue: Arc<Mutex<HashMap<u32, (Box<dyn Callback>, Instant)>>>,
}

#[uniffi::export]
Expand All @@ -63,6 +69,7 @@ impl MacOSProviderClient {
let path = desktop_core::ipc::path("autofill");

let queue = client.response_callbacks_queue.clone();

std::thread::spawn(move || {
let rt = tokio::runtime::Builder::new_current_thread()
.enable_all()
Expand All @@ -77,30 +84,39 @@ impl MacOSProviderClient {
rt.block_on(async move {
while let Some(message) = from_server_recv.recv().await {
match serde_json::from_str::<SerializedMessage>(&message) {
Ok(SerializedMessage::Connected) => {
Ok(SerializedMessage::Command(CommandMessage::Connected)) => {
info!("Connected to server");
}
Ok(SerializedMessage::Disconnected) => {
Ok(SerializedMessage::Command(CommandMessage::Disconnected)) => {
info!("Disconnected from server");
}
Ok(SerializedMessage::Message {
sequence_number,
value,
}) => match queue.lock().unwrap().remove(&sequence_number) {
Some(cb) => match value {
Ok(value) => {
if let Err(e) = cb.complete(value) {
error!("Error deserializing message: {}", e);
Some((cb, request_start_time)) => {
info!(
"Time to process request: {:?}",
request_start_time.elapsed()
);
match value {
Ok(value) => {
if let Err(e) = cb.complete(value) {
error!("Error deserializing message: {e}");
}
}
Err(e) => {
error!("Error processing message: {e:?}");
cb.error(e)
}
}
Err(e) => cb.error(e),
},
}
None => {
error!("No callback found for sequence number: {}", sequence_number)
error!("No callback found for sequence number: {sequence_number}")
}
},
Err(e) => {
error!("Error deserializing message: {}", e);
error!("Error deserializing message: {e}");
}
};
}
Expand All @@ -115,16 +131,29 @@ impl MacOSProviderClient {
request: PasskeyRegistrationRequest,
callback: Arc<dyn PreparePasskeyRegistrationCallback>,
) {
warn!("prepare_passkey_registration: {:?}", request);
self.send_message(request, Box::new(callback));
}

pub fn prepare_passkey_assertion(
&self,
request: PasskeyAssertionRequest,
callback: Arc<dyn PreparePasskeyAssertionCallback>,
) {
self.send_message(request, Box::new(callback));
}
}

#[derive(Serialize, Deserialize)]
#[serde(tag = "command")]
enum SerializedMessage {
#[serde(tag = "command", rename_all = "camelCase")]
enum CommandMessage {
Connected,
Disconnected,
}

#[derive(Serialize, Deserialize)]
#[serde(untagged, rename_all = "camelCase")]
enum SerializedMessage {
Command(CommandMessage),
Message {
sequence_number: u32,
value: Result<serde_json::Value, BitwardenError>,
Expand All @@ -140,7 +169,7 @@ impl MacOSProviderClient {
self.response_callbacks_queue
.lock()
.unwrap()
.insert(sequence_number, callback);
.insert(sequence_number, (callback, Instant::now()));

sequence_number
}
Expand All @@ -160,7 +189,7 @@ impl MacOSProviderClient {

if let Err(e) = self.to_server_send.blocking_send(message) {
// Make sure we remove the callback from the queue if we can't send the message
if let Some(cb) = self
if let Some((cb, _)) = self
.response_callbacks_queue
.lock()
.unwrap()
Expand Down
12 changes: 7 additions & 5 deletions apps/desktop/desktop_native/macos_provider/src/registration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,26 +5,28 @@ use serde::{Deserialize, Serialize};
use crate::{BitwardenError, Callback, UserVerification};

#[derive(uniffi::Record, Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct PasskeyRegistrationRequest {
relying_party_id: String,
rp_id: String,
user_name: String,
user_handle: Vec<u8>,

client_data_hash: Vec<u8>,
user_verification: UserVerification,
supported_algorithms: Vec<i32>,
}

#[derive(uniffi::Record, Serialize, Deserialize)]
pub struct PasskeyRegistrationCredential {
relying_party: String,
#[serde(rename_all = "camelCase")]
pub struct PasskeyRegistrationResponse {
rp_id: String,
client_data_hash: Vec<u8>,
credential_id: Vec<u8>,
attestation_object: Vec<u8>,
}

#[uniffi::export(with_foreign)]
pub trait PreparePasskeyRegistrationCallback: Send + Sync {
fn on_complete(&self, credential: PasskeyRegistrationCredential);
fn on_complete(&self, credential: PasskeyRegistrationResponse);
fn on_error(&self, error: BitwardenError);
}

Expand Down
2 changes: 2 additions & 0 deletions apps/desktop/desktop_native/napi/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ anyhow = "=1.0.93"
desktop_core = { path = "../core" }
napi = { version = "=2.16.13", features = ["async"] }
napi-derive = "=2.16.13"
serde = { version = "1.0.209", features = ["derive"] }
serde_json = "1.0.127"
tokio = { version = "=1.41.1" }
tokio-util = "=0.7.12"
tokio-stream = "=0.1.15"
Expand Down
Loading

0 comments on commit 646c0bc

Please sign in to comment.