Skip to content
40 changes: 36 additions & 4 deletions credentialsd-common/src/model.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ pub const BACKGROUND_EVENT_ERROR_PIN_NOT_SET: u32 = 0x80000008;
pub enum BackgroundEvent {
CeremonyCompleted,
NeedsPin { attempts_left: Option<u32> },
PinNotSet { error: PinNotSetError },
NeedsUserVerification { attempts_left: Option<u32> },
NeedsUserPresence,
SelectingCredential { creds: Vec<Credential> },
Expand Down Expand Up @@ -51,6 +52,11 @@ pub enum BackgroundEvent {
#[zvariant(signature = "dict")]
pub struct ClientPinEnteredOptions {}

/// Emitted when a client enters a new PIN for a device.
#[derive(Debug, SerializeDict, DeserializeDict, PartialEq, Type)]
#[zvariant(signature = "dict")]
pub struct SetDevicePinOptions {}

#[derive(Clone, Debug, Default, SerializeDict, DeserializeDict, PartialEq, Type, Value)]
#[zvariant(signature = "dict")]
pub struct Credential {
Expand Down Expand Up @@ -81,6 +87,24 @@ impl From<DiscoveryRequestedOptions> for UserInteractedEvent {
}
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Type)]
#[zvariant(signature = "s")]
#[serde(rename_all = "PascalCase")]
pub enum PinNotSetError {
/// PIN too short
PinTooShort,
/// PIN too long
PinTooLong,
/// PIN violates PinPolicy
PinPolicyViolation,
/// PIN change is required by the device
PinChangeRequired,
/// When no specific error is given (happens either in the initial PinNotSet-iteration,
/// or if dbus receives an unknown string)
#[serde(other)]
PinNotSet,
}

#[derive(Debug, Clone)]
pub enum Error {
/// Some unknown error with the authenticator occurred.
Expand All @@ -95,8 +119,6 @@ pub enum Error {
/// Note that this is different than exhausting the PIN count that fully
/// locks out the device.
PinAttemptsExhausted,
/// The RP requires user verification, but the device has no PIN/Biometrics set.
PinNotSet,
// 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),
Expand All @@ -108,7 +130,6 @@ impl Display for Error {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::AuthenticatorError => f.write_str("AuthenticatorError"),
Self::PinNotSet => f.write_str("PinNotSet"),
Self::NoCredentials => f.write_str("NoCredentials"),
Self::CredentialExcluded => f.write_str("CredentialExcluded"),
Self::PinAttemptsExhausted => f.write_str("PinAttemptsExhausted"),
Expand All @@ -124,7 +145,6 @@ impl TryFrom<&Value<'_>> for Error {
let err_code: &str = value.downcast_ref()?;
let err = match err_code {
"AuthenticatorError" => crate::model::Error::AuthenticatorError,
"PinNotSet" => crate::model::Error::PinNotSet,
"NoCredentials" => crate::model::Error::NoCredentials,
"CredentialExcluded" => crate::model::Error::CredentialExcluded,
"PinAttemptsExhausted" => crate::model::Error::PinAttemptsExhausted,
Expand All @@ -138,6 +158,10 @@ impl TryFrom<&Value<'_>> for Error {
#[zvariant(signature = "dict")]
pub struct NotifyNeedsPinOptions {}

#[derive(Debug, PartialEq, SerializeDict, DeserializeDict, Type)]
#[zvariant(signature = "dict")]
pub struct NotifyPinNotSetOptions {}

#[derive(Debug, SerializeDict, DeserializeDict, Type)]
#[zvariant(signature = "dict")]
pub struct NotifyNeedsUserVerificationOptions {}
Expand Down Expand Up @@ -253,6 +277,10 @@ pub enum UserInteractedEvent {
/// File descriptor must be memory-mapped to be read.
ClientPinEntered(OwnedFd),

/// Set new client PIN. Length of the PIN MUST not be greater than 63 bytes.
/// File descriptor must be memory-mapped to be read.
SetDevicePin(OwnedFd),

/// Select a credential by credential ID
CredentialSelected(String),

Expand All @@ -267,6 +295,10 @@ impl std::fmt::Debug for UserInteractedEvent {
.debug_tuple(stringify!(ClientPinEntered))
.field(&"******".to_string())
.finish(),
Self::SetDevicePin(_) => f
.debug_tuple(stringify!(SetDevicePin))
.field(&"******".to_string())
.finish(),
Self::CredentialSelected(arg0) => f
.debug_tuple(stringify!(CredentialSelected))
.field(arg0)
Expand Down
94 changes: 94 additions & 0 deletions credentialsd-ui/data/resources/ui/window.blp
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,81 @@ template $CredentialsUiWindow: ApplicationWindow {
};
}

StackPage {
name: "set_new_pin";
title: _("Set a PIN");

child: Box {
orientation: vertical;
margin-start: 16;

Image {
icon-name: "media-removable-symbolic";
pixel-size: 120;
icon-size: large;
height-request: 120;
width-request: 120;

styles [
"hero",
]

accessibility {
labelled-by: set_pin_instructions_label;
}
}

Label set_pin_instructions_label {
label: _("Please choose a new PIN for your device.");
wrap: true;

styles [
"instructions",
]
}

Box {
orientation: vertical;
spacing: 8;

PasswordEntry new_pin_primary_entry {
placeholder-text: _("New PIN");
hexpand: false;
max-width-chars: 64;
changed => $handle_setting_pin_change() swapped;
}

PasswordEntry new_pin_confirm_entry {
placeholder-text: _("Confirm PIN");
hexpand: false;
max-width-chars: 64;
changed => $handle_setting_pin_change() swapped;
}
}

Box {
halign: end;
spacing: 6;

Button new_pin_btn_close_window {
label: _("Close");
clicked => $handle_close_window() swapped;
}

Button new_pin_btn_continue {
label: _("Continue");
sensitive: bind template.view-model as <$CredentialManagerViewModel>.pin_fields_match;

styles [
"suggested-action",
]

clicked => $handle_commit_new_pin() swapped;
}
}
};
}

StackPage {
name: "completed";
title: _("Complete");
Expand Down Expand Up @@ -301,9 +376,28 @@ template $CredentialsUiWindow: ApplicationWindow {

child: Box {
orientation: vertical;
margin-start: 8;

Label {
label: bind template.view-model as <$CredentialManagerViewModel>.prompt;
margin-start: 24;
wrap: true;
}

Box {
halign: end;
spacing: 6;
visible: bind template.view-model as <$CredentialManagerViewModel>.start_setting_new_pin_visible;

Button failed_close_window {
label: _("Close");
clicked => $handle_close_window() swapped;
}

Button start_setting_new_pin {
label: _("Set PIN on device");
clicked => $handle_start_setting_new_pin() swapped;
}
}
};
}
Expand Down
1 change: 0 additions & 1 deletion credentialsd-ui/po/de_DE.po
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,6 @@ msgstr "Scannen Sie den QR-Code, um Ihr Gerät zu verbinden"
#: credentialsd-ui/data/resources/ui/window.blp:253
msgid "Choose credential"
msgstr "Wählen Sie Zugangsdaten aus"

#: credentialsd-ui/data/resources/ui/window.blp:266
msgid "Complete"
msgstr "Abgeschlossen"
Expand Down
21 changes: 20 additions & 1 deletion credentialsd-ui/src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use async_std::{

use credentialsd_common::{
memfd::write_secret,
model::{BackgroundEvent, UserInteractedEvent},
model::{BackgroundEvent, PinNotSetError, UserInteractedEvent},
};

const CTAP_CLIENT_SECRET_MAX_LEN: usize = 63;
Expand Down Expand Up @@ -39,6 +39,25 @@ impl FlowControlClient {
.await
}

pub async fn set_device_pin(&mut self, pin: String) -> Result<(), Option<PinNotSetError>> {
if pin.len() > CTAP_CLIENT_SECRET_MAX_LEN {
tracing::warn!("PIN is too long");
return Err(Some(PinNotSetError::PinTooLong));
}
let fd = match write_secret(pin.into_bytes()) {
Ok(fd) => fd,
Err(err) => {
tracing::error!(%err, "Failed to write secret to file descriptor");
// TODO: need to send a message back to GUI thread that there was an error.
_ = self.cancel_request().await;
return Err(None);
}
};
self.send(UserInteractedEvent::SetDevicePin(fd.into()))
.await
.map_err(|_| None)
}

pub async fn select_credential(&self, credential_id: String) -> Result<(), ()> {
self.send(UserInteractedEvent::CredentialSelected(credential_id))
.await
Expand Down
36 changes: 33 additions & 3 deletions credentialsd-ui/src/dbus.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,9 @@ use credentialsd_common::model::{
ClientPinEnteredOptions, Credential, CredentialSelectedOptions, Device,
DiscoveryRequestedOptions, NotifyHybridConnectedOptions, NotifyHybridConnectingOptions,
NotifyHybridStartedOptions, NotifyNeedsPinOptions, NotifyNeedsUserPresenceOptions,
NotifyNeedsUserVerificationOptions, NotifyNfcConnectedOptions,
NotifySelectingCredentialOptions, NotifyUsbConnectedOptions, Operation, PortalBackendOptions,
UserInteractedEvent, WindowHandle,
NotifyNeedsUserVerificationOptions, NotifyNfcConnectedOptions, NotifyPinNotSetOptions,
NotifySelectingCredentialOptions, NotifyUsbConnectedOptions, Operation, PinNotSetError,
PortalBackendOptions, SetDevicePinOptions, UserInteractedEvent, WindowHandle,
};

use crate::{RequestingApplication, ViewRequest, client::FlowControlClient};
Expand Down Expand Up @@ -168,6 +168,23 @@ impl CredentialPortalBackend {
.await
}

/// Called when the authenticator needs a client PIN, but the device
/// has no PIN set yet. This flow allows to set a new PIN on the fly.
async fn notify_pin_not_set(
&self,
#[zbus(object_server)] object_server: &ObjectServer,
session_handle: ObjectPath<'_>,
error: PinNotSetError,
_options: NotifyPinNotSetOptions,
) -> fdo::Result<()> {
self.notify_state_changed(
object_server,
session_handle,
BackgroundEvent::PinNotSet { error },
)
.await
}

/// Called when the authenticator needs a user verification gesture.
async fn notify_needs_user_verification(
&self,
Expand Down Expand Up @@ -345,6 +362,14 @@ impl CredentialPortalBackend {
options: ClientPinEnteredOptions,
) -> zbus::Result<()>;

#[zbus(signal)]
async fn set_device_pin(
emitter: SignalEmitter<'_>,
session_handle: ObjectPath<'_>,
pin_fd: OwnedFd,
options: SetDevicePinOptions,
) -> zbus::Result<()>;

#[zbus(signal)]
async fn credential_selected(
emitter: SignalEmitter<'_>,
Expand Down Expand Up @@ -621,6 +646,11 @@ impl CeremonyObject {
.client_pin_entered(session_handle, pin_fd, ClientPinEnteredOptions {})
.await?;
}
UserInteractedEvent::SetDevicePin(pin_fd) => {
emitter
.set_device_pin(session_handle, pin_fd, SetDevicePinOptions {})
.await?;
}
UserInteractedEvent::CredentialSelected(id) => {
emitter
.credential_selected(session_handle, id, CredentialSelectedOptions {})
Expand Down
5 changes: 4 additions & 1 deletion credentialsd-ui/src/gui/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use std::{sync::Arc, thread::JoinHandle};
use async_std::{channel::Receiver, sync::Mutex as AsyncMutex};

use credentialsd_common::model::Device;
use credentialsd_common::model::{Credential, WindowHandle};
use credentialsd_common::model::{Credential, PinNotSetError, WindowHandle};

use crate::{ViewRequest, client::FlowControlClient};

Expand Down Expand Up @@ -83,6 +83,9 @@ pub enum ViewUpdate {
NeedsPin {
attempts_left: Option<u32>,
},
PinNotSet {
error: PinNotSetError,
},
NeedsUserVerification {
attempts_left: Option<u32>,
},
Expand Down
Loading