Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .idea/.gitignore

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

12 changes: 12 additions & 0 deletions .idea/linux-webauthn-platform-api.iml

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

8 changes: 8 additions & 0 deletions .idea/modules.xml

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

6 changes: 6 additions & 0 deletions .idea/vcs.xml

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

24 changes: 17 additions & 7 deletions xyz-iinuwa-credential-manager-portal-gtk/Cargo.lock

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

2 changes: 1 addition & 1 deletion xyz-iinuwa-credential-manager-portal-gtk/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ serde_json = "1.0.140"
tracing = "0.1.41"
tracing-subscriber = "0.3"
zbus = { version = "5.5.0", default-features = false, features = ["blocking-api", "tokio"] }
libwebauthn = { git = "https://github.com/linux-credentials/libwebauthn", rev = "528af8de3adfbc329b6bd6dca7bf48714e674f96" }
libwebauthn = { git = "https://github.com/linux-credentials/libwebauthn", rev = "c61492dcc66cc53b33e9d3eb3377017019332964" }
async-trait = "0.1.88"
tokio = { version = "1.45.0", features = ["rt-multi-thread"] }
futures-lite = "2.6.0"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,31 @@
</object>
</child>

<child>
<object class="GtkStackPage">
<property name="name">failed</property>
<property name="title">Something went wrong.</property>
<property name="child">
<object class="GtkBox">
<property name="orientation">vertical</property>
<child>
<object class="GtkLabel">
<binding name="label">
<lookup name="prompt">
<lookup name="view-model">
ExampleApplicationWindow
</lookup>
</lookup>
</binding>
<property name="label">Something went wrong while retrieving a credential. Please try again later or use a different authenticator.</property>
</object>
</child>
</object>
</property>
</object>
</child>


</object>
</child>
</object>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use libwebauthn::webauthn::{Error as WebAuthnError, WebAuthn};

use crate::dbus::CredentialRequest;

use super::AuthenticatorResponse;
use super::{AuthenticatorResponse, Error};

pub(crate) trait HybridHandler {
fn start(
Expand All @@ -30,6 +30,7 @@ impl HybridHandler for InternalHybridHandler {
&self,
request: &CredentialRequest,
) -> impl Stream<Item = HybridEvent> + Unpin + Send + Sized + 'static {
tracing::debug!("Starting hybrid operation");
let request = request.clone();
let (tx, rx) = async_std::channel::unbounded();
tokio::spawn(async move {
Expand Down Expand Up @@ -58,38 +59,64 @@ impl HybridHandler for InternalHybridHandler {
if let Err(err) = tx.send(HybridStateInternal::Connected).await {
tracing::error!("Failed to send caBLE update: {:?}", err)
}
let response: AuthenticatorResponse = loop {
tracing::debug!("Polling hybrid channel for updates.");
let response: Result<AuthenticatorResponse, Error> = loop {
match &request {
CredentialRequest::CreatePublicKeyCredentialRequest(make_request) => {
match channel.webauthn_make_credential(make_request).await {
Ok(response) => break Ok(response.into()),
Err(WebAuthnError::Ctap(ctap_error)) => {
if ctap_error.is_retryable_user_error() {
tracing::debug!("Oops, try again! Error: {}", ctap_error);
tracing::debug!("Retrying credential creation operation because of CTAP error: {:?}", ctap_error);
continue;
} else {
tracing::error!(
"Received CTAP unrecoverable CTAP error: {:?}",
ctap_error
);
break Err(Error::AuthenticatorError);
}
break Err(WebAuthnError::Ctap(ctap_error));
}
Err(err) => break Err(err),
Err(err) => {
tracing::error!(
"Received unrecoverable error from authenticator: {:?}",
err
);
break Err(Error::AuthenticatorError);
}
};
}
CredentialRequest::GetPublicKeyCredentialRequest(get_request) => {
match channel.webauthn_get_assertion(get_request).await {
Ok(response) => break Ok(response.into()),
Err(WebAuthnError::Ctap(ctap_error)) => {
if ctap_error.is_retryable_user_error() {
println!("Oops, try again! Error: {}", ctap_error);
tracing::debug!("Retrying assertion operation because of CTAP error: {:?}", ctap_error);
continue;
} else {
tracing::error!(
"Received CTAP unrecoverable CTAP error: {:?}",
ctap_error
);
break Err(Error::AuthenticatorError);
}
break Err(WebAuthnError::Ctap(ctap_error));
}
Err(err) => break Err(err),
Err(err) => {
tracing::error!(
"Received unrecoverable error from authenticator: {:?}",
err
);
break Err(Error::AuthenticatorError);
}
};
}
}
}
.unwrap();
if let Err(err) = tx.send(HybridStateInternal::Completed(response)).await {
};
let terminal_state = match response {
Ok(auth_response) => HybridStateInternal::Completed(auth_response),
Err(_) => HybridStateInternal::Failed,
};
if let Err(err) = tx.send(terminal_state).await {
tracing::error!("Failed to send caBLE update: {:?}", err)
}
});
Expand All @@ -102,6 +129,7 @@ impl HybridHandler for InternalHybridHandler {
}
}

/// Used to communicate privileged state between handler and credential service.
#[derive(Clone, Debug)]
pub(super) enum HybridStateInternal {
/// Awaiting BLE advert from phone. Content is the FIDO string to be
Expand All @@ -117,16 +145,20 @@ pub(super) enum HybridStateInternal {
/// Authenticator data
Completed(AuthenticatorResponse),

Failed,
// TODO(cancellation)
// This isn't actually sent from the server.
#[allow(dead_code)]
UserCancelled,
}

// this is here to prevent making HybridStateInternal public to the whole crate.
/// Messages between hybrid handler and credential service.
pub struct HybridEvent {
pub(super) state: HybridStateInternal,
}

/// Used to communicate privileged state between credential service and UI.
#[derive(Clone, Debug)]
pub enum HybridState {
/// Awaiting BLE advert from phone. Content is the FIDO string to be displayed to the user, which contains QR secret
Expand All @@ -139,9 +171,12 @@ pub enum HybridState {
/// Tunnel is established, waiting for user to release credential on their device.
Connected,

/// Authenticator data
/// Authenticator data has been received
Completed,

/// Hybrid operation failed.
Failed,

// This isn't actually sent from the server.
UserCancelled,
}
Expand All @@ -154,6 +189,7 @@ impl From<HybridStateInternal> for HybridState {
HybridStateInternal::Connected => HybridState::Connected,
HybridStateInternal::Completed(_) => HybridState::Completed,
HybridStateInternal::UserCancelled => HybridState::UserCancelled,
HybridStateInternal::Failed => HybridState::Failed,
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,23 @@ enum AuthenticatorResponse {
CredentialsAsserted(GetAssertionResponse),
}

#[derive(Debug, Clone)]
pub enum Error {
/// Some unknown error with the authenticator occurred.
AuthenticatorError,
/// No matching credentials were found on the device.
NoCredentials,
/// Too many incorrect PIN attempts, and authenticator must be removed and
/// reinserted to continue any more PIN attempts.
///
/// Note that this is different than exhausting the PIN count that fully
/// locks out the device.
PinAttemptsExhausted,
// TODO: We may want to hide the details on this variant from the public API.
/// Something went wrong with the credential service itself, not the authenticator.
Internal(String),
}

impl From<MakeCredentialResponse> for AuthenticatorResponse {
fn from(value: MakeCredentialResponse) -> Self {
Self::CredentialCreated(value)
Expand Down
Loading